diff --git a/.codechecker.json b/.codechecker.json new file mode 100644 index 00000000..790b678d --- /dev/null +++ b/.codechecker.json @@ -0,0 +1,22 @@ +{ + "analyze": [ + "-d", + "clang-diagnostic-reserved-macro-identifier", + "-d", + "clang-diagnostic-reserved-identifier", + "-d", + "cert-err33-c", + "-d", + "clang-diagnostic-sign-compare", + "-d", + "clang-diagnostic-implicit-int-float-conversion", + "-d", + "clang-diagnostic-switch-enum", + "--analyzers", + "clangsa", + "clang-tidy", + "gcc", + "-i", + ".codechecker.skipfile" + ] +} \ No newline at end of file diff --git a/.codechecker.skipfile b/.codechecker.skipfile new file mode 100644 index 00000000..10051fa6 --- /dev/null +++ b/.codechecker.skipfile @@ -0,0 +1,2 @@ ++*/flutter-pi/src +-* diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index fa505e51..ed9fbf8f 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -95,7 +95,7 @@ jobs: matrix: container: - 'debian:bullseye' - - 'debian:buster' + - 'debian:bookworm' graphics-backends: - vulkan-only - opengl-only diff --git a/.github/workflows/codeql-buildscript.sh b/.github/workflows/codeql-buildscript.sh old mode 100644 new mode 100755 index 2eff8eef..546c0380 --- a/.github/workflows/codeql-buildscript.sh +++ b/.github/workflows/codeql-buildscript.sh @@ -1,6 +1,23 @@ #!/usr/bin/env bash -sudo apt install -y cmake libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev ttf-mscorefonts-installer fontconfig libsystemd-dev libinput-dev libudev-dev libxkbcommon-dev -mkdir build && cd build -cmake .. -make -j`nproc` +# gstreamer and libc++ want different versions of libunwind-dev. +# We explicitly install the version that gstreamer wants so +# we don't get install errors. + +sudo apt-get install -y --no-install-recommends \ + git cmake pkg-config ninja-build clang clang-tools \ + libgl-dev libgles-dev libegl-dev libvulkan-dev libdrm-dev libgbm-dev libsystemd-dev libinput-dev libudev-dev libxkbcommon-dev \ + libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ + libunwind-dev + +$WRAPPER cmake \ + -S . -B build \ + -GNinja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DBUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN=ON \ + -DBUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN=ON \ + -DENABLE_VULKAN=ON \ + -DENABLE_SESSION_SWITCHING=ON \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + +$WRAPPER cmake --build build diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 3ef873fc..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,126 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ "main", "master" ] - schedule: - - cron: '0 0 * * *' - pull_request: - branches: '*' - -jobs: - analyze: - name: Analyze - # Runner size impacts CodeQL analysis time. To learn more, please see: - # - https://gh.io/recommended-hardware-resources-for-running-codeql - # - https://gh.io/supported-runners-and-hardware-resources - # - https://gh.io/using-larger-runners - # Consider using larger runners for possible analysis time improvements. - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-20.04' }} - timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'cpp' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ] - # Use only 'java' to analyze code written in Java, Kotlin or both - # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - queries: security-and-quality - - - # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). - # If this step fails, then you should remove it and run the build manually (see below) - #- name: Autobuild - # uses: github/codeql-action/autobuild@v2 - - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - - run: | - ./.github/workflows/codeql-buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{matrix.language}}" - upload: false - id: step1 - - # Filter out rules with low severity or high false positve rate - # Also filter out warnings in third-party code - - name: Filter out unwanted errors and warnings - uses: advanced-security/filter-sarif@v1 - with: - patterns: | - -**:cpp/path-injection - -**:cpp/world-writable-file-creation - -**:cpp/poorly-documented-function - -**:cpp/potentially-dangerous-function - -**:cpp/use-of-goto - -**:cpp/integer-multiplication-cast-to-long - -**:cpp/comparison-with-wider-type - -**:cpp/leap-year/* - -**:cpp/ambiguously-signed-bit-field - -**:cpp/suspicious-pointer-scaling - -**:cpp/suspicious-pointer-scaling-void - -**:cpp/unsigned-comparison-zero - -**/cmake*/Modules/** - input: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif - output: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif - - - name: Upload CodeQL results to code scanning - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: ${{ steps.step1.outputs.sarif-output }} - category: "/language:${{matrix.language}}" - - - name: Upload CodeQL results as an artifact - if: success() || failure() - uses: actions/upload-artifact@v3 - with: - name: codeql-results - path: ${{ steps.step1.outputs.sarif-output }} - retention-days: 5 - - - name: Fail if an error is found - run: | - ./.github/workflows/fail_on_error.py \ - ${{ steps.step1.outputs.sarif-output }}/cpp.sarif diff --git a/.github/workflows/fail_on_error.py b/.github/workflows/fail_on_warning.py similarity index 68% rename from .github/workflows/fail_on_error.py rename to .github/workflows/fail_on_warning.py index 29791742..b6ce953e 100755 --- a/.github/workflows/fail_on_error.py +++ b/.github/workflows/fail_on_warning.py @@ -20,13 +20,18 @@ def codeql_sarif_contain_error(filename): rule_index = res['rule']['index'] else: continue + try: rule_level = rules_metadata[rule_index]['defaultConfiguration']['level'] - except IndexError as e: - print(e, rule_index, len(rules_metadata)) - else: - if rule_level == 'error': - return True + except LookupError: + # According to the SARIF schema (https://www.schemastore.org/schemas/json/sarif-2.1.0-rtm.6.json), + # the defalt level is "warning" if not specified. + rule_level = 'warning' + + if rule_level == 'error': + return True + elif rule_level == 'warning': + return True return False if __name__ == "__main__": diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 00000000..c263ac61 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,64 @@ +name: "Static Analysis" + +on: + push: + branches: [ "main", "master" ] + schedule: + - cron: '0 0 * * *' + pull_request: + branches: '*' + +jobs: + codechecker: + name: CodeChecker + + # Use latest Ubuntu 24.04 for latest GCC. + # CodeChecker requires gcc >= 13.0.0. + # ubuntu-latest is ubuntu 22.04 (atm) + runs-on: ubuntu-24.04 + + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Deps, Configure and Build + run: | + ./.github/workflows/codeql-buildscript.sh + + - name: Run CodeChecker + uses: ardera/CodeChecker-Action@master + id: codechecker + with: + ctu: true + logfile: ${{ github.workspace }}/build/compile_commands.json + config: ${{ github.workspace }}/.codechecker.json + + - uses: actions/upload-artifact@v4 + id: upload + with: + name: "CodeChecker Bug Reports" + path: ${{ steps.codechecker.outputs.result-html-dir }} + + - name: Fail on Warnings + if: ${{ steps.codechecker.outputs.warnings == 'true' }} + run: | + cat <>$GITHUB_STEP_SUMMARY + ## ⚠ī¸ CodeChecker found warnings + Please see the 'CodeChecker Bug Reports' artifact for more details: + - ${{ steps.upload.outputs.artifact-url }} + + **\`CodeChecker parse\`:** + EOF + + echo '```' >>$GITHUB_STEP_SUMMARY + cat ${{ steps.codechecker.outputs.result-log }} >>$GITHUB_STEP_SUMMARY + echo '```' >>$GITHUB_STEP_SUMMARY + + exit 1 diff --git a/.gitignore b/.gitignore index c4a9d2c9..a482b09b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /.vscode /build /out +/.codechecker # CMake docs says it should not be checked in. CMakeUserPresets.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 734aba51..6eb4544b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,7 +55,7 @@ option(ENABLE_MTRACE "True if flutter-pi should call GNU mtrace() on startup." O option(ENABLE_TESTS "True if tests should be built. Requires Unity to be checked out at third_party/Unity." OFF) option(ENABLE_SESSION_SWITCHING "True if flutter-pi should be built with session switching support. Requires libseat-dev to be installed." ON) option(TRY_ENABLE_SESSION_SWITCHING "Don't throw an error if libseat isn't found, instead just build without session switching support in that case." ON) -option(LTO "Check for IPO/LTO support and enable, if supported. May require gold/lld when building with clang. (Either using `-fuse-ld` in CMAKE_C_FLAGS or by setting as the default system linker.) Only applies to Release or RelWithDebInfo build types." ON) +option(LTO "Enable LTO. Does not work with all toolchains. May require gold/lld when building with clang. (Either using `-fuse-ld` in CMAKE_C_FLAGS or by setting as the default system linker.) Only applies to Release or RelWithDebInfo build types." OFF) option(LINT_EGL_HEADERS "Set an define that'll make the egl.h only export the extension definitions, prototypes that are explicitly marked as required." OFF) option(DEBUG_DRM_PLANE_ALLOCATIONS "Add logging in modesetting.c for debugging the process of choosing a fitting DRM plane for a framebuffer layer." OFF) option(USE_LEGACY_KMS "Force the use of legacy KMS." OFF) @@ -80,7 +80,7 @@ endif() if (BUILD_SENTRY_PLUGIN) set(flutterpi_languages C CXX ASM) else() - set(flutterpi_languages C ASM) + set(flutterpi_languages C) endif() project(flutter-pi LANGUAGES ${flutterpi_languages} VERSION "1.0.0") @@ -89,12 +89,13 @@ message(STATUS "Generator .............. ${CMAKE_GENERATOR}") message(STATUS "Build Type ............. ${CMAKE_BUILD_TYPE}") include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) # Those libraries we definitely need. include(FindPkgConfig) pkg_check_modules(DRM REQUIRED IMPORTED_TARGET libdrm) pkg_check_modules(GBM REQUIRED IMPORTED_TARGET gbm) -pkg_check_modules(LIBSYSTEMD REQUIRED IMPORTED_TARGET libsystemd) +pkg_check_modules(LIBSYSTEMD REQUIRED IMPORTED_TARGET libsystemd>=243) pkg_check_modules(LIBINPUT REQUIRED IMPORTED_TARGET libinput) pkg_check_modules(LIBXKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon) pkg_check_modules(LIBUDEV REQUIRED IMPORTED_TARGET libudev) @@ -127,8 +128,13 @@ add_library( src/platformchannel.c src/pluginregistry.c src/texture_registry.c - src/modesetting.c - src/util/collection.c + src/kms/drmdev.c + src/kms/req_builder.c + src/kms/resources.c + src/kms/monitor.c + src/kms/kms_window.c + src/util/event_loop.c + src/util/lock_ops.c src/util/bitscan.c src/util/vector.c src/cursor.c @@ -146,6 +152,7 @@ add_library( src/frame_scheduler.c src/window.c src/dummy_render_surface.c + src/dummy_window.c src/plugins/services.c ) @@ -162,13 +169,15 @@ target_link_libraries(flutterpi_module PUBLIC ) target_include_directories(flutterpi_module PUBLIC - ${CMAKE_SOURCE_DIR}/third_party/flutter_embedder_header/include ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/third_party/mesa3d/include + ${CMAKE_SOURCE_DIR}/third_party/klib/include + ${CMAKE_SOURCE_DIR}/third_party/flutter_embedder_header/include ) target_compile_options(flutterpi_module PUBLIC - $<$:-O0 -Wall -Wextra -Wno-sign-compare -Werror -ggdb -U_FORTIFY_SOURCE -DDEBUG> + $<$:-O0 -Wall -Wextra -Wno-sign-compare -Wswitch-enum -Wformat -Wdouble-promotion -Wno-overlength-strings -Wno-gnu-zero-variadic-macro-arguments -pedantic -Werror -ggdb -U_FORTIFY_SOURCE -DDEBUG> $<$:-O3 -Wall -Wextra -Wno-sign-compare -ggdb -DNDEBUG> $<$:-O3 -Wall -Wextra -Wno-sign-compare -DNDEBUG> ) @@ -237,6 +246,7 @@ if (ENABLE_VULKAN) target_sources(flutterpi_module PRIVATE src/vk_gbm_render_surface.c src/vk_renderer.c + src/vulkan.c ) target_link_libraries(flutterpi_module PUBLIC PkgConfig::VULKAN @@ -423,6 +433,7 @@ if (ENABLE_TSAN) target_link_options(flutterpi_module PUBLIC -fsanitize=thread) target_compile_options(flutterpi_module PUBLIC -fsanitize=thread) endif() + if (ENABLE_ASAN) # when we use asan, we need to force linking against the C++ stdlib. # If we don't link against it, and load a dynamic library that's linked against the C++ stdlib (like the flutter engine), @@ -433,8 +444,9 @@ if (ENABLE_ASAN) target_link_options(flutterpi_module PUBLIC -fsanitize=address -fno-omit-frame-pointer -Wl,--no-as-needed) target_compile_options(flutterpi_module PUBLIC -fsanitize=address) - check_c_compiler_flag(-static-libasan HAVE_STATIC_LIBASAN) - if (HAVE_STATIC_LIBASAN) + check_c_compiler_flag(-static-libasan HAVE_C_STATIC_LIBASAN) + check_cxx_compiler_flag(-static-libasan HAVE_CXX_STATIC_LIBASAN) + if (HAVE_C_STATIC_LIBASAN AND HAVE_CXX_STATIC_LIBASAN) target_link_options(flutterpi_module PUBLIC -static-libasan) endif() endif() @@ -462,84 +474,19 @@ install(TARGETS flutter-pi RUNTIME DESTINATION bin) # Enable lto if supported. cmake_policy(SET CMP0069 NEW) -include(CheckIPOSupported) # include(CheckLinkerFlag) set(USE_LTO OFF) # set(NEEDS_GOLD OFF) # set(NEEDS_LLD OFF) if(LTO AND (CMAKE_BUILD_TYPE STREQUAL Release OR CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo)) - # So people can specify `-fuse-ld=lld` in the CMAKE_C_FLAGS. - # Otherwise check_ipo_supported will not use CMAKE_C_FLAGS. - if (POLICY CMP0138) - cmake_policy(SET CMP0138 NEW) - endif() - - check_ipo_supported(RESULT IPO_SUPPORTED OUTPUT IPO_SUPPORT_OUTPUT) - if (NOT IPO_SUPPORTED) - message(WARNING "IPO/LTO was requested in the configure options, but is not supported by the toolchain. Check CMakeFiles/CMakeError.log for details.") - endif() - - # Try to enable IPO with gold and lld. - # Needs CMP0138. - # (untested because CMP0138 required CMake 3.24, that's why it's commented out) - # if (NOT IPO_SUPPORTED) - # check_linker_flag(C "-fuse-ld=gold" SUPPORTS_GOLD) - # if (SUPPORTS_GOLD) - # set(OLD_CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - # - # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fuse-ld=gold") - # try_compile() - # check_ipo_supported(RESULT IPO_SUPPORTED_WITH_GOLD OUTPUT IPO_SUPPORT_OUTPUT) - # if (IPO_SUPPORTED_WITH_GOLD) - # set(IPO_SUPPORTED ON) - # set(NEEDS_GOLD ON) - # endif() - # - # set(CMAKE_C_FLAGS "${OLD_CMAKE_C_FLAGS}") - # endif() - # - # check_linker_flag(C "-fuse-ld=lld" SUPPORTS_LLD) - # if (SUPPORTS_LLD) - # set(OLD_CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - # - # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fuse-ld=lld") - # check_ipo_supported(RESULT IPO_SUPPORTED_WITH_LLD OUTPUT IPO_SUPPORT_OUTPUT) - # if (IPO_SUPPORTED_WITH_LLD) - # set(IPO_SUPPORTED ON) - # set(NEEDS_LLD ON) - # endif() - # - # set(CMAKE_C_FLAGS "${OLD_CMAKE_C_FLAGS}") - # endif() - # endif() - - # clang doesn't support LTO when using GNU ld. - if(IPO_SUPPORTED AND ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")) - execute_process(COMMAND ${CMAKE_C_COMPILER} -Wl,--version OUTPUT_VARIABLE LINKER_VERSION_OUTPUT ERROR_QUIET) - if("${LINKER_VERSION_OUTPUT}" MATCHES "GNU ld") - message(WARNING "IPO/LTO was requested, but is not supported when using clang with GNU ld as the linker. Try setting gold or lld as the system linker.") - set(IPO_SUPPORTED OFF) - endif() - endif() - - if (IPO_SUPPORTED) - set(USE_LTO ON) - endif() + set(USE_LTO ON) endif() message(STATUS "IPO/LTO ................ ${USE_LTO}") if (USE_LTO) set_property(TARGET flutterpi_module PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) set_property(TARGET flutter-pi PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) - # if (NEEDS_GOLD) - # Technically specifying only for one would suffice. - # target_link_options(flutterpi_module PUBLIC "-fuse-ld=gold") - # target_link_options(flutter-pi PUBLIC "-fuse-ld=gold") - # elseif (NEEDS_LLD) - # target_link_options(flutterpi_module PUBLIC "-fuse-ld=lld") - # target_link_options(flutter-pi PUBLIC "-fuse-ld=lld") - # endif() endif() if(ENABLE_TESTS) diff --git a/CMakePresets.json b/CMakePresets.json index 866cc51f..a7fdfdd2 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -7,13 +7,100 @@ "description": "Sets Ninja generator, build and install directory", "generator": "Ninja", "binaryDir": "${sourceDir}/out/build/${presetName}", + "hidden": true, "cacheVariables": { - "CMAKE_BUILD_TYPE": "Debug", "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "TRY_ENABLE_OPENGL": false, "ENABLE_OPENGL": true, + "BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN": true, + "TRY_BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN": false, "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN": true, + "TRY_BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN": false, "BUILD_SENTRY_PLUGIN": true, - "ENABLE_TESTS": true + "ENABLE_VULKAN": true, + "TRY_ENABLE_VULKAN": false, + "ENABLE_SESSION_SWITCHING": true, + "ENABLE_TESTS": false + } + }, + { + "name": "default-debug", + "displayName": "Default OpenGL host build (Debug)", + "inherits": "default", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "ENABLE_TESTS": true, + "ENABLE_ASAN": true + } + }, + { + "name": "default-debug-clang", + "displayName": "Default OpenGL host build (Debug, Clang)", + "inherits": "default-debug", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "default-debug-gcc", + "displayName": "Default OpenGL host build (Debug, gcc)", + "inherits": "default-debug", + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++" + } + }, + { + "name": "default-relwithdebinfo", + "displayName": "Default OpenGL host build (Release with Debug Info)", + "inherits": "default", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "default-release", + "displayName": "Default OpenGL host build (Release)", + "inherits": "default", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "default-clang", + "displayName": "Default OpenGL host build (clang)", + "description": "Sets Ninja generator, build and install directory", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "inherits": "default", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "default-clang-20", + "displayName": "Default OpenGL host build (clang-20)", + "description": "Sets Ninja generator, build and install directory", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "inherits": "default", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-20", + "CMAKE_CXX_COMPILER": "clang++-20" + } + }, + { + "name": "default-gcc", + "displayName": "Default OpenGL host build (gcc)", + "description": "Sets Ninja generator, build and install directory", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "inherits": "default", + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++" } }, { @@ -41,4 +128,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/compositor_ng.c b/src/compositor_ng.c index d6f6718b..bf59e647 100644 --- a/src/compositor_ng.c +++ b/src/compositor_ng.c @@ -20,20 +20,28 @@ #include #include +#include +#include +#include #include #include "cursor.h" #include "dummy_render_surface.h" #include "flutter-pi.h" #include "frame_scheduler.h" -#include "modesetting.h" +#include "kms/drmdev.h" +#include "kms/monitor.h" +#include "kms/req_builder.h" +#include "kms/resources.h" #include "notifier_listener.h" #include "pixel_format.h" #include "render_surface.h" #include "surface.h" #include "tracer.h" +#include "user_input.h" #include "util/collection.h" -#include "util/dynarray.h" +#include "util/event_loop.h" +#include "util/khash_uint32.h" #include "util/logging.h" #include "util/refcounting.h" #include "window.h" @@ -55,11 +63,17 @@ #include "vk_renderer.h" #endif +KHASH_MAP_INIT_UINT32(connector_display_ids, int64_t) +KHASH_SET_INIT_UINT32(connector_set) + +KHASH_MAP_INIT_INT64(view, struct window *) +KHASH_MAP_INIT_INT64(platform_view, struct surface *) + /** * @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: + * - for platform 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 @@ -111,6 +125,234 @@ void fl_layer_composition_destroy(struct fl_layer_composition *composition) { DEFINE_REF_OPS(fl_layer_composition, n_refs) +struct display { + size_t fl_display_id; + + // This depends on the current mode of the CRTC. + double refresh_rate; + struct vec2i size; + + struct vec2i physical_size; + double device_pixel_ratio; +}; + +struct connector { + char *name; + enum connector_type type; + const char *type_name; + + bool has_display; + struct display display; +}; + +struct display_setup { + refcount_t n_refs; + + size_t n_connectors; + struct connector connectors[]; +}; + +/** + * @brief Gets the name for a DRM connector type. + * + * Follows the kernel naming. + */ +static const char *get_connector_type_name(uint32_t connector_type) { + switch (connector_type) { + case DRM_MODE_CONNECTOR_Unknown: return "Unknown"; + case DRM_MODE_CONNECTOR_VGA: return "VGA"; + case DRM_MODE_CONNECTOR_DVII: return "DVI-I"; + case DRM_MODE_CONNECTOR_DVID: return "DVI-D"; + case DRM_MODE_CONNECTOR_DVIA: return "DVI-A"; + case DRM_MODE_CONNECTOR_Composite: return "Composite"; + case DRM_MODE_CONNECTOR_SVIDEO: return "SVIDEO"; + case DRM_MODE_CONNECTOR_LVDS: return "LVDS"; + case DRM_MODE_CONNECTOR_Component: return "Component"; + case DRM_MODE_CONNECTOR_9PinDIN: return "DIN"; + case DRM_MODE_CONNECTOR_DisplayPort: return "DP"; + case DRM_MODE_CONNECTOR_HDMIA: return "HDMI-A"; + case DRM_MODE_CONNECTOR_HDMIB: return "HDMI-B"; + case DRM_MODE_CONNECTOR_TV: return "TV"; + case DRM_MODE_CONNECTOR_eDP: return "eDP"; + case DRM_MODE_CONNECTOR_VIRTUAL: return "Virtual"; + case DRM_MODE_CONNECTOR_DSI: return "DSI"; + case DRM_MODE_CONNECTOR_DPI: return "DPI"; + case DRM_MODE_CONNECTOR_WRITEBACK: return "Writeback"; +#ifdef DRM_MODE_CONNECTOR_SPI + case DRM_MODE_CONNECTOR_SPI: return "SPI"; +#endif +#ifdef DRM_MODE_CONNECTOR_USB + case DRM_MODE_CONNECTOR_USB: return "USB"; +#endif + default: return NULL; + } +} + +static const enum connector_type connector_types[] = { + [DRM_MODE_CONNECTOR_Unknown] = CONNECTOR_TYPE_OTHER, + [DRM_MODE_CONNECTOR_VGA] = CONNECTOR_TYPE_VGA, + [DRM_MODE_CONNECTOR_DVII] = CONNECTOR_TYPE_DVI, + [DRM_MODE_CONNECTOR_DVID] = CONNECTOR_TYPE_DVI, + [DRM_MODE_CONNECTOR_DVIA] = CONNECTOR_TYPE_DVI, + [DRM_MODE_CONNECTOR_Composite] = CONNECTOR_TYPE_OTHER, + [DRM_MODE_CONNECTOR_SVIDEO] = CONNECTOR_TYPE_OTHER, + [DRM_MODE_CONNECTOR_LVDS] = CONNECTOR_TYPE_LVDS, + [DRM_MODE_CONNECTOR_Component] = CONNECTOR_TYPE_OTHER, + [DRM_MODE_CONNECTOR_9PinDIN] = CONNECTOR_TYPE_OTHER, + [DRM_MODE_CONNECTOR_DisplayPort] = CONNECTOR_TYPE_DISPLAY_PORT, + [DRM_MODE_CONNECTOR_HDMIA] = CONNECTOR_TYPE_HDMI, + [DRM_MODE_CONNECTOR_HDMIB] = CONNECTOR_TYPE_HDMI, + [DRM_MODE_CONNECTOR_TV] = CONNECTOR_TYPE_TV, + [DRM_MODE_CONNECTOR_eDP] = CONNECTOR_TYPE_EDP, + [DRM_MODE_CONNECTOR_VIRTUAL] = CONNECTOR_TYPE_OTHER, + [DRM_MODE_CONNECTOR_DSI] = CONNECTOR_TYPE_DSI, + [DRM_MODE_CONNECTOR_DPI] = CONNECTOR_TYPE_DPI, + [DRM_MODE_CONNECTOR_WRITEBACK] = CONNECTOR_TYPE_OTHER, +#ifdef DRM_MODE_CONNECTOR_SPI + [DRM_MODE_CONNECTOR_SPI] = CONNECTOR_TYPE_OTHER, +#endif +#ifdef DRM_MODE_CONNECTOR_USB + [DRM_MODE_CONNECTOR_USB] = CONNECTOR_TYPE_OTHER, +#endif +}; + +bool connector_init(const struct drm_connector *connector, int64_t fl_display_id, struct connector *out) { + // We unfortunately can't use drmModeGetConnectorTypeName yet + // because it's too new. + const char *type_name = get_connector_type_name(connector->type); + if (type_name == NULL) { + // if we don't know this type, skip it. + return false; + } + + out->name = NULL; + asprintf(&out->name, "%s-%" PRIu32, type_name, connector->id); + if (out->name == NULL) { + return false; + } + + assert(connector->type < ARRAY_SIZE(connector_types)); + out->type = connector_types[connector->type]; + out->type_name = type_name; + + if (connector->variable_state.connection_state == DRM_CONNSTATE_CONNECTED) { + out->has_display = true; + + out->display.fl_display_id = fl_display_id; + out->display.refresh_rate = 60.0; + out->display.size = VEC2I(0, 0); + out->display.device_pixel_ratio = 1.0; + + out->display.physical_size = VEC2I(connector->variable_state.width_mm, connector->variable_state.height_mm); + } else { + out->has_display = false; + } + + return true; +} + +void connector_fini(struct connector *connector) { + free(connector->name); +} + +struct display_setup *display_setup_new(struct drm_resources *resources, khash_t(connector_display_ids) * connectors) { + struct display_setup *s; + + s = calloc(1, sizeof *s + sizeof(struct connector) * resources->n_connectors); + if (s == NULL) { + return NULL; + } + + s->n_connectors = 0; + for (size_t i = 0; i < resources->n_connectors; i++) { + if (resources->connectors[i].variable_state.connection_state != DRM_CONNSTATE_CONNECTED) { + continue; + } + + khiter_t entry = kh_get(connector_display_ids, connectors, resources->connectors[s->n_connectors].id); + if (entry == kh_end(connectors)) { + continue; + } + + int64_t fl_display_id = kh_value(connectors, entry); + + bool bucket_status = connector_init(resources->connectors + i, fl_display_id, s->connectors + s->n_connectors); + if (!bucket_status) { + continue; + } + + s->n_connectors++; + } + + return s; +} + +void display_setup_destroy(struct display_setup *setup) { + for (int i = 0; i < setup->n_connectors; i++) { + connector_fini(setup->connectors + i); + } + free(setup); +} + +DEFINE_REF_OPS(display_setup, n_refs) + +size_t display_setup_get_n_connectors(struct display_setup *setup) { + return setup->n_connectors; +} + +const struct connector *display_setup_get_connector(struct display_setup *setup, size_t index) { + return setup->connectors + index; +} + +const char *connector_get_name(const struct connector *connector) { + return connector->name; +} + +enum connector_type connector_get_type(const struct connector *connector) { + return connector->type; +} + +const char *connector_get_type_name(const struct connector *connector) { + return connector->type_name; +} + +bool connector_has_display(const struct connector *connector) { + return connector->has_display; +} + +const struct display *connector_get_display(const struct connector *connector) { + return &connector->display; +} + +size_t display_get_fl_display_id(const struct display *display) { + return display->fl_display_id; +} + +double display_get_refresh_rate(const struct display *display) { + return display->refresh_rate; +} + +struct vec2i display_get_size(const struct display *display) { + return display->size; +} + +struct vec2i display_get_physical_size(const struct display *display) { + return display->physical_size; +} + +double display_get_device_pixel_ratio(const struct display *display) { + return display->device_pixel_ratio; +} + +const char *display_get_connector_id(const struct display *display) { + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wpedantic") + + return CONTAINER_OF(display, struct connector, display)->name; + + PRAGMA_DIAGNOSTIC_POP +} + /** * @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. @@ -123,17 +365,43 @@ struct compositor { pthread_mutex_t mutex; struct tracer *tracer; - struct window *main_window; - struct util_dynarray views; + struct window *implicit_view_fallback; + + int64_t next_display_id; + khash_t(connector_display_ids) * connectors; + + int64_t next_view_id; + khash_t(view) * views; + + int64_t next_platform_view_id; + khash_t(platform_view) * platform_views; FlutterCompositor flutter_compositor; - struct vec2f cursor_pos; -}; + struct drmdev *drmdev; + struct drm_monitor *monitor; + struct drm_resources *resources; -struct platform_view_with_id { - int64_t id; - struct surface *surface; + struct evloop *raster_loop; + struct evsrc *drm_monitor_evsrc; + + struct notifier display_setup_notifier; + + bool is_startup; + bool has_display_interface; + struct fl_display_interface display_interface; + + bool has_pointer_event_interface; + struct fl_pointer_event_interface pointer_event_interface; + + kvec_t(int64_t) view_layout; + + struct { + bool enabled; + struct vec2f pos_view; + int64_t fl_view_id; + struct window *window; + } cursor; }; static bool on_flutter_present_layers(const FlutterLayer **layers, size_t layers_count, void *userdata); @@ -143,54 +411,306 @@ on_flutter_create_backing_store(const FlutterBackingStoreConfig *config, Flutter static bool on_flutter_collect_backing_store(const FlutterBackingStore *fl_store, void *userdata); -MUST_CHECK struct compositor *compositor_new(struct tracer *tracer, struct window *main_window) { +static bool on_flutter_present_view(const FlutterPresentViewInfo *present_info); + +static int update_flutter_displays(struct compositor *c) { + int bucket_status; + + // Allocate the display list on the stack. + FlutterEngineDisplay *displays = NULL; + if (c->has_display_interface) { + displays = alloca(c->resources->n_connectors * sizeof(FlutterEngineDisplay)); + if (displays == NULL) { + return ENOMEM; + } + } + + // Populate the display list, and also allocate ids for new displays, + // remove entries for disconnected displays. + size_t n_displays = 0; + drm_resources_for_each_connector(c->resources, connector) { + if (connector->variable_state.connection_state == DRM_CONNSTATE_CONNECTED) { + int64_t id; + + khiter_t entry = kh_put(connector_display_ids, c->connectors, connector->id, &bucket_status); + if (bucket_status == -1) { + return ENOMEM; + } else if (bucket_status == 0) { + // We already know this display. + id = kh_value(c->connectors, entry); + } else { + // We don't know this display yet. + // Allocate an id for it. + id = c->next_display_id++; + kh_value(c->connectors, entry) = id; + } + + if (displays != NULL) { + memset(displays + n_displays, 0, sizeof(FlutterEngineDisplay)); + + displays[n_displays].struct_size = sizeof(FlutterEngineDisplay); + displays[n_displays].display_id = id; + displays[n_displays].single_display = n_displays == 1; + + /// TODO: Calculate these + displays[n_displays].refresh_rate = 0.0; + displays[n_displays].width = 0; + displays[n_displays].height = 0; + displays[n_displays].device_pixel_ratio = 0.0; + n_displays++; + } + } else { + // Remove this display. + khiter_t entry = kh_get(connector_display_ids, c->connectors, connector->id); + if (entry != kh_end(c->connectors)) { + kh_del(connector_display_ids, c->connectors, entry); + } + } + } + + /// TODO: Remove display entries for removed connectors. + if (displays != NULL) { + FlutterEngineResult engine_result = c->display_interface.notify_display_update( + c->display_interface.engine, + kFlutterEngineDisplaysUpdateTypeStartup, + displays, + n_displays + ); + if (engine_result != kSuccess) { + LOG_ERROR( + "Couldn't register displays to flutter engine. FlutterEngineNotifyDisplayUpdate: %s\n", + FLUTTER_RESULT_TO_STRING(engine_result) + ); + return EIO; + } + + c->is_startup = false; + } + + return 0; +} + +static void send_window_metrics_events(struct compositor *compositor) { + int64_t view_id; + struct window *window; + kh_foreach(compositor->views, view_id, window, { + struct view_geometry geo = window_get_view_geometry(window); + + COMPILE_ASSERT(sizeof(FlutterWindowMetricsEvent) == 80 || sizeof(FlutterWindowMetricsEvent) == 96); + + FlutterWindowMetricsEvent event; + memset(&event, 0, sizeof event); + + event.struct_size = sizeof(FlutterWindowMetricsEvent); + event.width = (size_t) geo.view_size.x; + event.height = (size_t) geo.view_size.y; + event.pixel_ratio = (double) geo.device_pixel_ratio; + event.left = 0; + event.top = 0; + event.physical_view_inset_top = 0; + event.physical_view_inset_right = 0; + event.physical_view_inset_bottom = 0; + event.physical_view_inset_left = 0; + event.display_id = 0; + event.view_id = view_id; + + FlutterEngineResult engine_result = + compositor->display_interface.send_window_metrics_event(compositor->display_interface.engine, &event); + if (engine_result != kSuccess) { + LOG_ERROR( + "Could not send window metrics to flutter engine. FlutterEngineSendWindowMetricsEvent: %s\n", + FLUTTER_RESULT_TO_STRING(engine_result) + ); + } + }); +} + +static enum event_handler_return on_drm_monitor_ready(int fd, uint32_t events, void *userdata) { struct compositor *compositor; - int ok; - compositor = malloc(sizeof *compositor); - if (compositor == NULL) { - goto fail_return_null; + ASSERT_NOT_NULL(userdata); + (void) fd; + (void) events; + + compositor = userdata; + + // This will in turn probobly call on_drm_uevent. + drm_monitor_dispatch(compositor->monitor); + + return EVENT_HANDLER_CONTINUE; +} + +static void on_drm_uevent(const struct drm_uevent *event, void *userdata) { + struct compositor *compositor; + + ASSERT_NOT_NULL(event); + ASSERT_NOT_NULL(userdata); + compositor = userdata; + + drm_resources_update(compositor->resources, drmdev_get_modesetting_fd(compositor->drmdev), event); + drm_resources_apply_rockchip_workaround(compositor->resources); + + update_flutter_displays(compositor); + + struct display_setup *display_setup = display_setup_new(compositor->resources, compositor->connectors); + if (display_setup == NULL) { + LOG_ERROR("Couldn't create display setup.\n"); + return; } - ok = pthread_mutex_init(&compositor->mutex, NULL); - if (ok != 0) { - goto fail_free_compositor; + notifier_notify(&compositor->display_setup_notifier, display_setup); +} + +static const struct drm_uevent_listener uevent_listener = { + .on_uevent = on_drm_uevent, +}; + +static void on_input(void *userdata, size_t n_events, const struct user_input_event *events); + +MUST_CHECK struct compositor *compositor_new_multiview( + struct tracer *tracer, + struct evloop *raster_loop, + struct udev *udev, + struct drmdev *drmdev, + struct drm_resources *resources, + struct user_input *input +) { + struct compositor *c; + int bucket_status; + + ASSERT_NOT_NULL(tracer); + ASSERT_NOT_NULL(raster_loop); + ASSERT_NOT_NULL(drmdev); + + c = calloc(1, sizeof *c); + if (c == NULL) { + return NULL; } - util_dynarray_init(&compositor->views); + mutex_init(&c->mutex); + c->views = kh_init(view); + c->platform_views = kh_init(platform_view); + c->connectors = kh_init(connector_display_ids); - compositor->n_refs = REFCOUNT_INIT_1; - compositor->main_window = window_ref(main_window); + c->n_refs = REFCOUNT_INIT_1; + c->implicit_view_fallback = NULL; // just so we get an error if the FlutterCompositor struct was updated - COMPILE_ASSERT(sizeof(FlutterCompositor) == 24 || sizeof(FlutterCompositor) == 48); - memset(&compositor->flutter_compositor, 0, sizeof(FlutterCompositor)); + COMPILE_ASSERT(sizeof(FlutterCompositor) == 28 || sizeof(FlutterCompositor) == 56); + memset(&c->flutter_compositor, 0, sizeof(FlutterCompositor)); + + c->flutter_compositor.struct_size = sizeof(FlutterCompositor); + c->flutter_compositor.user_data = c; + c->flutter_compositor.create_backing_store_callback = on_flutter_create_backing_store; + c->flutter_compositor.collect_backing_store_callback = on_flutter_collect_backing_store; + c->flutter_compositor.present_layers_callback = NULL; + c->flutter_compositor.avoid_backing_store_cache = true; + c->flutter_compositor.present_view_callback = on_flutter_present_view; + + c->tracer = tracer_ref(tracer); + + kv_init(c->view_layout); + c->cursor.enabled = false; + c->cursor.pos_view = VEC2F(0, 0); + c->cursor.fl_view_id = -1; + c->cursor.window = NULL; + + c->next_view_id = 0; + c->next_platform_view_id = 1; + c->next_display_id = 1; + + c->raster_loop = evloop_ref(raster_loop); + c->drmdev = drmdev_ref(drmdev); + + if (udev == NULL) { + udev = udev_new(); + if (udev == NULL) { + LOG_ERROR("Couldn't create udev.\n"); + goto fail_return_null; + } + } else { + udev_ref(udev); + } - compositor->flutter_compositor.struct_size = sizeof(FlutterCompositor); - compositor->flutter_compositor.user_data = compositor; - compositor->flutter_compositor.create_backing_store_callback = on_flutter_create_backing_store; - compositor->flutter_compositor.collect_backing_store_callback = on_flutter_collect_backing_store; - compositor->flutter_compositor.present_layers_callback = on_flutter_present_layers; - compositor->flutter_compositor.avoid_backing_store_cache = true; + c->monitor = drm_monitor_new(NULL, udev, &uevent_listener, c); - compositor->tracer = tracer_ref(tracer); - compositor->cursor_pos = VEC2F(0, 0); - return compositor; + udev_unref(udev); -fail_free_compositor: - free(compositor); + if (c->monitor == NULL) { + goto fail_return_null; + } + + c->resources = resources != NULL ? drm_resources_ref(resources) : drmdev_query_resources(drmdev); + c->drm_monitor_evsrc = evloop_add_io(raster_loop, drm_monitor_get_fd(c->monitor), EPOLLIN, on_drm_monitor_ready, c); + + c->is_startup = true; + c->has_display_interface = false; + + bucket_status = update_flutter_displays(c); + if (bucket_status != 0) { + goto fail_return_null; + } + + struct display_setup *display_setup = display_setup_new(c->resources, c->connectors); + if (display_setup == NULL) { + LOG_ERROR("Couldn't create display setup.\n"); + goto fail_return_null; + } + + value_notifier_init(&c->display_setup_notifier, display_setup, display_setup_unref_void); + + user_input_add_primary_listener( + input, + USER_INPUT_DEVICE_ADDED | USER_INPUT_DEVICE_REMOVED | USER_INPUT_SLOT_ADDED | USER_INPUT_SLOT_REMOVED | USER_INPUT_POINTER | + USER_INPUT_TOUCH | USER_INPUT_TABLET_TOOL, + on_input, + c + ); + + return c; fail_return_null: return NULL; } +struct compositor * +compositor_new_singleview(struct tracer *tracer, struct evloop *raster_loop, struct window *window, struct user_input *input) { + struct compositor *c = compositor_new_multiview(tracer, raster_loop, NULL, NULL, NULL, input); + if (c == NULL) { + return NULL; + } + + compositor_add_view(c, window); + return c; +} + void compositor_destroy(struct compositor *compositor) { - util_dynarray_foreach(&compositor->views, struct platform_view_with_id, view) { - surface_unref(view->surface); + struct window *window; + kh_foreach_value(compositor->views, window, window_unref(window);); + + kh_destroy(view, compositor->views); + + struct surface *surface; + kh_foreach_value(compositor->platform_views, surface, surface_unref(surface);) + + kh_destroy(platform_view, compositor->platform_views); + + if (compositor->drm_monitor_evsrc != NULL) { + evsrc_destroy(compositor->drm_monitor_evsrc); + } + if (compositor->monitor != NULL) { + drm_monitor_destroy(compositor->monitor); + } + if (compositor->resources != NULL) { + drm_resources_unref(compositor->resources); + } + if (compositor->drmdev != NULL) { + drmdev_unref(compositor->drmdev); } - util_dynarray_fini(&compositor->views); + evloop_unref(compositor->raster_loop); + tracer_unref(compositor->tracer); - window_unref(compositor->main_window); + window_unref(compositor->implicit_view_fallback); pthread_mutex_destroy(&compositor->mutex); free(compositor); } @@ -199,28 +719,129 @@ 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) { - *view_geometry_out = window_get_view_geometry(compositor->main_window); +static struct surface *compositor_get_platform_view_by_id_locked(struct compositor *compositor, int64_t view_id) { + khiter_t entry = kh_get(platform_view, compositor->platform_views, view_id); + if (entry != kh_end(compositor->platform_views)) { + return surface_ref(kh_value(compositor->platform_views, entry)); + } + + return NULL; +} + +static struct surface *compositor_get_platform_view_by_id(struct compositor *compositor, int64_t view_id) { + compositor_lock(compositor); + struct surface *surface = compositor_get_platform_view_by_id_locked(compositor, view_id); + compositor_unlock(compositor); + + return surface; +} + +static struct window *compositor_get_view_by_id_locked(struct compositor *compositor, int64_t view_id) { + struct window *window = NULL; + + khiter_t entry = kh_get(view, compositor->views, view_id); + if (entry != kh_end(compositor->views)) { + window = kh_value(compositor->views, entry); + } + + return window; +} + +static struct window *compositor_get_view_by_id(struct compositor *compositor, int64_t view_id) { + compositor_lock(compositor); + struct window *window = compositor_get_view_by_id_locked(compositor, view_id); + if (window) { + window = window_ref(window); + } + compositor_unlock(compositor); + + return window; +} + +struct window *compositor_ref_implicit_view(struct compositor *compositor) { + ASSERT_NOT_NULL(compositor); + + mutex_lock(&compositor->mutex); + + if (compositor->implicit_view_fallback == NULL) { + mutex_unlock(&compositor->mutex); + return NULL; + } + + struct window *w = window_ref(compositor->implicit_view_fallback); + + mutex_unlock(&compositor->mutex); + + return w; } -ATTR_PURE double compositor_get_refresh_rate(struct compositor *compositor) { - return window_get_refresh_rate(compositor->main_window); +void compositor_set_fl_display_interface(struct compositor *compositor, const struct fl_display_interface *display_interface) { + ASSERT_NOT_NULL(compositor); + ASSERT_NOT_NULL(display_interface); + + if (display_interface == NULL) { + compositor->has_display_interface = false; + return; + } + + compositor->has_display_interface = true; + compositor->display_interface = *display_interface; + + // register flutter displays + update_flutter_displays(compositor); + + // send window metrics event + send_window_metrics_events(compositor); +} + +void compositor_set_fl_pointer_event_interface( + struct compositor *compositor, + const struct fl_pointer_event_interface *pointer_event_interface +) { + ASSERT_NOT_NULL(compositor); + ASSERT_NOT_NULL(pointer_event_interface); + + if (pointer_event_interface == NULL) { + compositor->has_pointer_event_interface = false; + return; + } else { + compositor->has_pointer_event_interface = true; + compositor->pointer_event_interface = *pointer_event_interface; + } +} + +ATTR_PURE float compositor_get_refresh_rate(struct compositor *compositor) { + return window_get_refresh_rate(compositor->implicit_view_fallback); } int compositor_get_next_vblank(struct compositor *compositor, uint64_t *next_vblank_ns_out) { ASSERT_NOT_NULL(compositor); ASSERT_NOT_NULL(next_vblank_ns_out); - return window_get_next_vblank(compositor->main_window, next_vblank_ns_out); + return window_get_next_vblank(compositor->implicit_view_fallback, next_vblank_ns_out); } -static int compositor_push_composition(struct compositor *compositor, struct fl_layer_composition *composition) { - int ok; +static int +compositor_push_composition(struct compositor *compositor, bool has_view_id, int64_t view_id, struct fl_layer_composition *composition) { + struct window *window; + int bucket_status; + + if (has_view_id) { + window = compositor_get_view_by_id(compositor, view_id); + if (window == NULL) { + LOG_ERROR("Couldn't find window with id %" PRId64 " to push composition to.\n", view_id); + return EINVAL; + } + } else { + window = window_ref(compositor->implicit_view_fallback); + } TRACER_BEGIN(compositor->tracer, "window_push_composition"); - ok = window_push_composition(compositor->main_window, composition); + bucket_status = window_push_composition(window, composition); TRACER_END(compositor->tracer, "window_push_composition"); - return ok; + window_unref(window); + + return bucket_status; } static void fill_platform_view_layer_props( @@ -231,7 +852,7 @@ static void fill_platform_view_layer_props( size_t n_mutations, const struct mat3f *display_to_view_transform, const struct mat3f *view_to_display_transform, - double device_pixel_ratio + float device_pixel_ratio ) { (void) view_to_display_transform; @@ -262,8 +883,8 @@ static void fill_platform_view_layer_props( * ``` */ - rect.size.x /= device_pixel_ratio; - rect.size.y /= device_pixel_ratio; + rect.size.x /= (double) device_pixel_ratio; + rect.size.y /= (double) 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. @@ -308,17 +929,33 @@ static void fill_platform_view_layer_props( props_out->clip_rects = NULL; } -static int compositor_push_fl_layers(struct compositor *compositor, size_t n_fl_layers, const FlutterLayer **fl_layers) { +static int compositor_push_fl_layers( + struct compositor *compositor, + bool has_view_id, + int64_t view_id, + size_t n_fl_layers, + const FlutterLayer **fl_layers +) { struct fl_layer_composition *composition; - int ok; + struct view_geometry geometry; + struct window *window; + int bucket_status; + + window = has_view_id ? compositor_get_view_by_id(compositor, view_id) : window_ref(compositor->implicit_view_fallback); + if (window == NULL) { + LOG_ERROR("Couldn't find window with id %" PRId64 " to push flutter layers to.\n", view_id); + return EINVAL; + } + + geometry = window_get_view_geometry(window); + + window_unrefp(&window); 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); @@ -339,27 +976,17 @@ static int compositor_push_fl_layers(struct compositor *compositor, size_t n_fl_ layer->props.n_clip_rects = 0; layer->props.clip_rects = NULL; } else { - ASSERT_EQUALS(fl_layer->type, kFlutterLayerContentTypePlatformView); + ASSUME(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 = compositor_get_view_by_id_locked(compositor, fl_layer->platform_view->identifier); + layer->surface = compositor_get_platform_view_by_id(compositor, fl_layer->platform_view->identifier); if (layer->surface == NULL) { - layer->surface = - CAST_SURFACE(dummy_render_surface_new(compositor->tracer, VEC2I(fl_layer->size.width, fl_layer->size.height))); - } -#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 + /// TODO: Just leave the layer away in this case. + LOG_ERROR("Invalid platform view id %" PRId64 " in flutter layer.\n", fl_layer->platform_view->identifier); - struct view_geometry geometry = window_get_view_geometry(compositor->main_window); + layer->surface = CAST_SURFACE( + dummy_render_surface_new(compositor->tracer, VEC2I((int) fl_layer->size.width, (int) fl_layer->size.height)) + ); + } // 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. @@ -376,24 +1003,18 @@ static int compositor_push_fl_layers(struct compositor *compositor, size_t n_fl_ } } - compositor_unlock(compositor); - TRACER_BEGIN(compositor->tracer, "compositor_push_composition"); - ok = compositor_push_composition(compositor, composition); + bucket_status = compositor_push_composition(compositor, has_view_id, view_id, 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; + return bucket_status; } -static bool on_flutter_present_layers(const FlutterLayer **layers, size_t layers_count, void *userdata) { +/// TODO: Remove +UNUSED static bool on_flutter_present_layers(const FlutterLayer **layers, size_t layers_count, void *userdata) { struct compositor *compositor; - int ok; + int bucket_status; ASSERT_NOT_NULL(layers); assert(layers_count > 0); @@ -401,78 +1022,80 @@ static bool on_flutter_present_layers(const FlutterLayer **layers, size_t layers compositor = userdata; TRACER_BEGIN(compositor->tracer, "compositor_push_fl_layers"); - ok = compositor_push_fl_layers(compositor, layers_count, layers); + bucket_status = compositor_push_fl_layers(compositor, false, -1, layers_count, layers); TRACER_END(compositor->tracer, "compositor_push_fl_layers"); - if (ok != 0) { + if (bucket_status != 0) { return false; } return true; } -ATTR_PURE static bool platform_view_with_id_equal(const struct platform_view_with_id lhs, const struct platform_view_with_id rhs) { - return lhs.id == rhs.id; +static bool on_flutter_present_view(const FlutterPresentViewInfo *present_info) { + struct compositor *compositor; + int bucket_status; + + ASSERT_NOT_NULL(present_info); + compositor = present_info->user_data; + + TRACER_BEGIN(compositor->tracer, "compositor_push_fl_layers"); + bucket_status = compositor_push_fl_layers(compositor, true, present_info->view_id, present_info->layers_count, present_info->layers); + TRACER_END(compositor->tracer, "compositor_push_fl_layers"); + + if (bucket_status != 0) { + return false; + } + + return true; } -int compositor_set_platform_view(struct compositor *compositor, int64_t id, struct surface *surface) { - struct platform_view_with_id *view; +int compositor_add_platform_view(struct compositor *compositor, struct surface *surface) { + khiter_t entry; + int bucket_status; ASSERT_NOT_NULL(compositor); - assert(id != 0); ASSERT_NOT_NULL(surface); compositor_lock(compositor); - view = NULL; - util_dynarray_foreach(&compositor->views, struct platform_view_with_id, view_iter) { - if (view_iter->id == id) { - view = view_iter; - break; - } - } - - if (view == NULL) { - if (surface != NULL) { - struct platform_view_with_id v = { - .id = id, - .surface = surface_ref(surface), - }; + int64_t id = compositor->next_platform_view_id++; - util_dynarray_append(&compositor->views, struct platform_view_with_id, v); - } - } else { - ASSERT_NOT_NULL(view->surface); - if (surface == NULL) { - util_dynarray_delete_unordered_ext(&compositor->views, struct platform_view_with_id, *view, platform_view_with_id_equal); - surface_unref(view->surface); - free(view); - } else { - surface_swap_ptrs(&view->surface, surface); - } + entry = kh_put(platform_view, compositor->platform_views, id, &bucket_status); + if (bucket_status == -1) { + compositor_unlock(compositor); + return ENOMEM; } + kh_value(compositor->platform_views, entry) = surface_ref(surface); + compositor_unlock(compositor); return 0; } -struct surface *compositor_get_view_by_id_locked(struct compositor *compositor, int64_t view_id) { - util_dynarray_foreach(&compositor->views, struct platform_view_with_id, view) { - if (view->id == view_id) { - return view->surface; - } +void compositor_remove_platform_view(struct compositor *compositor, int64_t id) { + khiter_t entry; + + ASSERT_NOT_NULL(compositor); + + compositor_lock(compositor); + + entry = kh_get(platform_view, compositor->platform_views, id); + if (entry != kh_end(compositor->platform_views)) { + surface_unref(kh_value(compositor->platform_views, entry)); + kh_del(platform_view, compositor->platform_views, entry); } - return NULL; + compositor_unlock(compositor); } #ifdef HAVE_EGL_GLES2 bool compositor_has_egl_surface(struct compositor *compositor) { - return window_has_egl_surface(compositor->main_window); + return window_has_egl_surface(compositor->implicit_view_fallback); } EGLSurface compositor_get_egl_surface(struct compositor *compositor) { - return window_get_egl_surface(compositor->main_window); + return window_get_egl_surface(compositor->implicit_view_fallback); } #endif @@ -480,18 +1103,25 @@ static bool on_flutter_create_backing_store(const FlutterBackingStoreConfig *config, FlutterBackingStore *backing_store_out, void *userdata) { struct render_surface *s; struct compositor *compositor; - int ok; + struct window *window; + int bucket_status; ASSERT_NOT_NULL(config); ASSERT_NOT_NULL(backing_store_out); ASSERT_NOT_NULL(userdata); compositor = userdata; + window = compositor_get_view_by_id(compositor, config->view_id); + if (window == NULL) { + LOG_ERROR("Couldn't find window with id %" PRId64 " to create backing store for.\n", config->view_id); + return false; + } + // this will not increase the refcount on the surface. - s = window_get_render_surface(compositor->main_window, VEC2I((int) config->size.width, (int) config->size.height)); + s = window_get_render_surface(window, VEC2I((int) config->size.width, (int) config->size.height)); if (s == NULL) { LOG_ERROR("Couldn't create render surface for flutter to render into.\n"); - return false; + goto fail_unref_window; } COMPILE_ASSERT(sizeof(FlutterBackingStore) == 56 || sizeof(FlutterBackingStore) == 80); @@ -504,16 +1134,20 @@ on_flutter_create_backing_store(const FlutterBackingStoreConfig *config, Flutter // render_surface_fill asserts that the user_data is null so it can make sure // any concrete render_surface_fill implementation doesn't try to set the user_data. // so we set the user_data after the fill - ok = render_surface_fill(s, backing_store_out); - if (ok != 0) { + bucket_status = render_surface_fill(s, backing_store_out); + if (bucket_status != 0) { LOG_ERROR("Couldn't fill flutter backing store with concrete OpenGL framebuffer/texture or Vulkan image.\n"); - return false; + goto fail_unref_window; } // now we can set the user_data. backing_store_out->user_data = s; - + window_unref(window); return true; + +fail_unref_window: + window_unref(window); + return false; } static bool on_flutter_collect_backing_store(const FlutterBackingStore *fl_store, void *userdata) { @@ -535,49 +1169,671 @@ const FlutterCompositor *compositor_get_flutter_compositor(struct compositor *co return &compositor->flutter_compositor; } -void compositor_set_cursor( +void compositor_set_cursor(struct compositor *compositor, bool has_enabled, bool enabled, bool has_kind, enum pointer_kind kind) { + if (!has_enabled && !has_kind) { + return; + } + + compositor_lock(compositor); + + if (compositor->cursor.window) { + window_set_cursor(compositor->cursor.window, has_enabled, enabled, has_kind, kind, false, VEC2I(0, 0)); + } + + compositor_unlock(compositor); +} + +int64_t compositor_add_view(struct compositor *compositor, struct window *window) { + ASSERT_NOT_NULL(compositor); + ASSERT_NOT_NULL(window); + int64_t view_id; + int bucket_status; + + compositor_lock(compositor); + + view_id = compositor->next_view_id++; + + khiter_t entry = kh_put(view, compositor->views, view_id, &bucket_status); + if (bucket_status == -1) { + compositor_unlock(compositor); + return -1; + } + + kh_val(compositor->views, entry) = window_ref(window); + + if (compositor->implicit_view_fallback == NULL) { + compositor->implicit_view_fallback = window_ref(window); + } + + kv_push(compositor->view_layout, view_id); + + compositor_unlock(compositor); + + return view_id; +} + +void compositor_remove_view(struct compositor *compositor, int64_t view_id) { + ASSERT_NOT_NULL(compositor); + ASSERT(view_id != 0); + + compositor_lock(compositor); + + khiter_t entry = kh_get(view, compositor->views, view_id); + if (entry != kh_end(compositor->views)) { + struct window *window = kh_val(compositor->views, entry); + + // If the view we're removing is the view we use as an implicit view fallback, + // unref it. + if (compositor->implicit_view_fallback == window) { + window_unrefp(&compositor->implicit_view_fallback); + } + + window_unref(kh_val(compositor->views, entry)); + + kh_del(view, compositor->views, entry); + } + + // remove the view from the view layout + for (size_t i = 0; i < kv_size(compositor->view_layout); i++) { + if (kv_A(compositor->view_layout, i) == view_id) { + for (size_t j = i; j < kv_size(compositor->view_layout) - 1; j++) { + kv_A(compositor->view_layout, j) = kv_A(compositor->view_layout, j + 1); + } + kv_drop(compositor->view_layout, 1); + break; + } + } + + compositor_unlock(compositor); +} + +struct notifier *compositor_get_display_setup_notifier(struct compositor *compositor) { + ASSERT_NOT_NULL(compositor); + return &compositor->display_setup_notifier; +} + +static int64_t determine_window_for_input_device(struct compositor *compositor, struct user_input_device *device) { + input_device_match_score_t best_score = -1; + int64_t best_window = -1; + + { + int64_t view_id; + struct window *window; + kh_foreach(compositor->views, view_id, window, { + input_device_match_score_t score = window_match_input_device(window, device); + if (score > best_score) { + best_score = score; + best_window = view_id; + } + }); + } + + if (best_score >= 0) { + return best_window; + } + + return -1; +} + +static int64_t get_view_id_for_input_device(struct compositor *compositor, struct user_input_device *device) { + (void) compositor; + + int64_t *view_id = user_input_device_get_primary_listener_userdata(device); + return view_id == NULL ? -1 : *view_id; +} + +UNUSED static struct window *get_window_for_input_device(struct compositor *compositor, struct user_input_device *device) { + (void) compositor; + + int64_t *view_id = user_input_device_get_primary_listener_userdata(device); + + if (view_id == NULL) { + return NULL; + } + + return compositor_get_view_by_id(compositor, *view_id); +} + +struct fl_event_buffer { + struct fl_pointer_event_interface pointer_event_interface; + + size_t n_events; + FlutterPointerEvent events[64]; +}; + +static void flush_fl_events(struct fl_event_buffer *buffer) { + if (buffer->n_events > 0) { + buffer->pointer_event_interface.send_pointer_event(buffer->pointer_event_interface.engine, buffer->events, buffer->n_events); + buffer->n_events = 0; + } +} + +static void emit_fl_event(struct fl_event_buffer *buffer, const FlutterPointerEvent event) { + if (buffer->n_events >= ARRAY_SIZE(buffer->events)) { + flush_fl_events(buffer); + } + + buffer->events[buffer->n_events++] = event; +} + +static FlutterPointerEvent make_fl_pointer_add_event( + uint64_t timestamp, + struct vec2f pos_view, + int64_t fl_device_id, + FlutterPointerDeviceKind device_kind, + int64_t view_id +) { + FlutterPointerEvent event; + memset(&event, 0, sizeof event); + + event.struct_size = sizeof(FlutterPointerEvent); + event.phase = kAdd; + event.timestamp = timestamp; + event.x = pos_view.x; + event.y = pos_view.y; + event.device = fl_device_id; + event.signal_kind = kFlutterPointerSignalKindNone; + event.scroll_delta_x = 0; + event.scroll_delta_y = 0; + event.device_kind = device_kind; + event.buttons = 0; + event.pan_x = 0.0; + event.pan_y = 0.0; + event.scale = 1.0; + event.rotation = 0.0; + event.view_id = view_id; + + return event; +} + +static FlutterPointerEvent make_fl_pointer_remove_event( + uint64_t timestamp, + struct vec2f pos_view, + int64_t fl_device_id, + FlutterPointerDeviceKind device_kind, + int64_t view_id +) { + FlutterPointerEvent event; + memset(&event, 0, sizeof event); + + event.struct_size = sizeof(FlutterPointerEvent); + event.phase = kRemove; + event.timestamp = timestamp; + event.x = pos_view.x; + event.y = pos_view.y; + event.device = fl_device_id; + event.signal_kind = kFlutterPointerSignalKindNone; + event.scroll_delta_x = 0; + event.scroll_delta_y = 0; + event.device_kind = device_kind; + event.buttons = 0; + event.pan_x = 0.0; + event.pan_y = 0.0; + event.scale = 1.0; + event.rotation = 0.0; + event.view_id = view_id; + + return event; +} + +static FlutterPointerEvent make_fl_mouse_event( + FlutterPointerPhase phase, + uint64_t timestamp, + struct vec2f pos_view, + int64_t fl_device_id, + int64_t buttons, + int64_t view_id +) { + FlutterPointerEvent event; + memset(&event, 0, sizeof event); + + event.struct_size = sizeof(FlutterPointerEvent); + event.phase = phase; + event.timestamp = timestamp; + event.x = pos_view.x; + event.y = pos_view.y; + event.device = fl_device_id; + event.signal_kind = kFlutterPointerSignalKindNone; + event.scroll_delta_x = 0; + event.scroll_delta_y = 0; + event.device_kind = kFlutterPointerDeviceKindMouse; + event.buttons = buttons; + event.pan_x = 0.0; + event.pan_y = 0.0; + event.scale = 1.0; + event.rotation = 0.0; + event.view_id = view_id; + + return event; +} + +UNUSED static FlutterPointerEvent make_fl_mouse_scroll_event( + FlutterPointerPhase phase, + uint64_t timestamp, + struct vec2f pos_view, + int64_t fl_device_id, + struct vec2f scroll_delta, + int64_t buttons, + int64_t view_id +) { + FlutterPointerEvent event; + memset(&event, 0, sizeof event); + + event.struct_size = sizeof(FlutterPointerEvent); + event.phase = phase; + event.timestamp = timestamp; + event.x = pos_view.x; + event.y = pos_view.y; + event.device = fl_device_id; + event.signal_kind = kFlutterPointerSignalKindScroll; + event.scroll_delta_x = scroll_delta.x; + event.scroll_delta_y = scroll_delta.y; + event.device_kind = kFlutterPointerDeviceKindMouse; + event.buttons = buttons; + event.pan_x = 0.0; + event.pan_y = 0.0; + event.scale = 1.0; + event.rotation = 0.0; + event.view_id = view_id; + + return event; +} + +static void on_device_added(struct compositor *compositor, struct user_input_device *device) { + int64_t view_id = determine_window_for_input_device(compositor, device); + if (view_id == -1) { + return; + } + + int64_t *id = malloc(sizeof *id); + if (id == NULL) { + return; + } + + *id = view_id; + + user_input_device_set_primary_listener_userdata(device, id); +} + +static void on_device_removed(struct compositor *compositor, struct user_input_device *device) { + (void) compositor; + + int64_t *id = user_input_device_get_primary_listener_userdata(device); + if (id != NULL) { + free(id); + } + + user_input_device_set_primary_listener_userdata(device, NULL); +} + +static void on_slot_added(struct compositor *compositor, struct fl_event_buffer *buffer, const struct user_input_event *event) { + int64_t view_id = get_view_id_for_input_device(compositor, event->device); + if (view_id == -1) { + return; + } + + FlutterPointerEvent fl_event; + memset(&fl_event, 0, sizeof fl_event); + + fl_event.struct_size = sizeof(FlutterPointerEvent); + fl_event.timestamp = event->timestamp; + fl_event.phase = kAdd; + /// TODO: Implement + UNIMPLEMENTED(); + fl_event.view_id = view_id; + + emit_fl_event(buffer, fl_event); +} + +static void on_slot_removed(struct compositor *compositor, struct fl_event_buffer *buffer, const struct user_input_event *event) { + int64_t view_id = get_view_id_for_input_device(compositor, event->device); + if (view_id == -1) { + return; + } + + FlutterPointerEvent fl_event; + memset(&fl_event, 0, sizeof fl_event); + + fl_event.struct_size = sizeof(FlutterPointerEvent); + fl_event.timestamp = event->timestamp; + fl_event.phase = kRemove; + /// TODO: Implement + UNIMPLEMENTED(); + fl_event.view_id = view_id; + + emit_fl_event(buffer, fl_event); +} + +static void on_absolute_pointer_event(struct compositor *compositor, struct fl_event_buffer *buffer, const struct user_input_event *event) { + ASSERT(event->pointer.is_absolute); + + int64_t view_id = get_view_id_for_input_device(compositor, event->device); + if (view_id == -1) { + return; + } + + struct window *window = compositor_get_view_by_id(compositor, view_id); + if (window == NULL) { + return; + } + + struct vec2f pos_view = window_transform_ndc_to_view(window, event->pointer.position_ndc); + + mutex_lock(&compositor->mutex); + + if (compositor->cursor.enabled && compositor->cursor.fl_view_id != view_id) { + window_set_cursor( + // clang-format off + compositor->cursor.window, + true, false, + false, 0, + false, VEC2I(0, 0) + // clang-format on + ); + window_unrefp(&compositor->cursor.window); + + // remove the mouse device from the old view + // and add it to the new view. + emit_fl_event( + buffer, + make_fl_pointer_remove_event( + event->timestamp, + compositor->cursor.pos_view, + event->global_slot_id, + kFlutterPointerDeviceKindMouse, + compositor->cursor.fl_view_id + ) + ); + + emit_fl_event( + buffer, + make_fl_pointer_add_event(event->timestamp, pos_view, event->global_slot_id, kFlutterPointerDeviceKindMouse, view_id) + ); + } + + compositor->cursor.enabled = true; + compositor->cursor.fl_view_id = view_id; + // compositor_get_view_by_id increments the refcount of the window so we don't need to do it here. + compositor->cursor.window = window; + compositor->cursor.pos_view = pos_view; + + mutex_unlock(&compositor->mutex); +} + +static void move_cursor_to_view( struct compositor *compositor, - bool has_enabled, - bool enabled, - bool has_kind, - enum pointer_kind kind, - bool has_delta, - struct vec2f delta + struct fl_event_buffer *buffer, + uint64_t timestamp, + uint64_t device_id, + int64_t new_view, + struct window *new_window, + struct vec2f new_pos ) { - if (!has_enabled && !has_kind && !has_delta) { + if (compositor->cursor.enabled) { + if (compositor->cursor.fl_view_id != new_view) { + window_set_cursor( + // clang-format off + compositor->cursor.window, + true, false, + false, 0, + false, VEC2I(0, 0) + // clang-format on + ); + window_unrefp(&compositor->cursor.window); + + // remove the mouse device from the old view + // and add it to the new view. + emit_fl_event( + buffer, + make_fl_pointer_remove_event( + timestamp, + compositor->cursor.pos_view, + device_id, + kFlutterPointerDeviceKindMouse, + compositor->cursor.fl_view_id + ) + ); + + emit_fl_event(buffer, make_fl_pointer_add_event(timestamp, new_pos, device_id, kFlutterPointerDeviceKindMouse, new_view)); + + window_set_cursor( + // clang-format off + compositor->cursor.window, + true, true, + false, 0, + false, vec2f_round_to_integer(new_pos) + // clang-format on + ); + } + + compositor->cursor.fl_view_id = new_view; + compositor->cursor.window = window_ref(new_window); + compositor->cursor.pos_view = new_pos; + } else { + compositor->cursor.enabled = true; + compositor->cursor.fl_view_id = new_view; + compositor->cursor.window = window_ref(new_window); + compositor->cursor.pos_view = new_pos; + } +} + +static void maybe_enable_cursor_locked(struct compositor *compositor) { + if (!compositor->cursor.enabled) { + int64_t first_view = kv_A(compositor->view_layout, 0); + struct window *w = compositor_get_view_by_id_locked(compositor, first_view); + + if (w != NULL) { + compositor->cursor.enabled = true; + compositor->cursor.fl_view_id = first_view; + compositor->cursor.window = window_ref(w); + compositor->cursor.pos_view = VEC2F(0, 0); + + window_unref(w); + } + } +} + +static void +on_relative_pointer_event_locked(struct compositor *compositor, struct fl_event_buffer *buffer, const struct user_input_event *event) { + ASSERT(!event->pointer.is_absolute); + + // If the cursor is not enabled, we need to enable it. + maybe_enable_cursor_locked(compositor); + + // If the cursor is still not enabled, we can't do anything. + if (!compositor->cursor.enabled) { return; } - compositor_lock(compositor); + int64_t current_view_id = compositor->cursor.fl_view_id; + struct window *current_window = compositor->cursor.window; + + // move the cursor + struct vec2f current_view_size = window_get_view_geometry(current_window).view_size; + struct vec2f pos_current_view = vec2f_add(compositor->cursor.pos_view, event->pointer.delta); + + int64_t new_view_id = -1; + if (pos_current_view.x < 0) { + // look if there's a view to the left of this one. + for (size_t i = 0; i < kv_size(compositor->view_layout); i++) { + if (kv_A(compositor->view_layout, i) == current_view_id) { + if (i > 0) { + new_view_id = kv_A(compositor->view_layout, i - 1); + } + break; + } + } + } else if (pos_current_view.x > current_view_size.x) { + // look if there's a view to the left of this one. + for (size_t i = 0; i < kv_size(compositor->view_layout); i++) { + if (kv_A(compositor->view_layout, i) == current_view_id) { + if (i > 0) { + new_view_id = kv_A(compositor->view_layout, i - 1); + } + break; + } + } + } - if (has_delta) { - // move cursor - compositor->cursor_pos = vec2f_add(compositor->cursor_pos, delta); + struct window *new_window = NULL; + if (new_view_id != -1) { + // move the cursor to the right side of the view located left from the current one. + new_window = compositor_get_view_by_id_locked(compositor, new_view_id); + } - struct view_geometry viewgeo = window_get_view_geometry(compositor->main_window); + // If we have a window next to this one, + // try to move the cursor onto that window. + if (new_window != NULL) { + struct view_geometry new_geo = window_get_view_geometry(new_window); + + // Translate the cursor position on the current view to the + // cursor position on the new view. + if (pos_current_view.x < 0) { + pos_current_view.x = new_geo.view_size.x + pos_current_view.x; + } else if (pos_current_view.x > current_view_size.x) { + pos_current_view.x = pos_current_view.x - current_view_size.x; + } - if (compositor->cursor_pos.x < 0.0f) { - compositor->cursor_pos.x = 0.0f; - } else if (compositor->cursor_pos.x > viewgeo.view_size.x) { - compositor->cursor_pos.x = viewgeo.view_size.x; + if (0 <= pos_current_view.x && pos_current_view.x <= new_geo.view_size.x && 0 <= pos_current_view.y && + pos_current_view.y <= new_geo.view_size.y) { + // If the cursor is within the bounds of the new view, + // move it there. + move_cursor_to_view(compositor, buffer, event->timestamp, event->global_slot_id, new_view_id, new_window, pos_current_view); + } else { + // If the cursor is not within bounds, + // just emit a move event. + window_unrefp(&new_window); } + } + + // If we're not moving the cursor to a new window, + // just emit a move event. + if (new_window == NULL) { + pos_current_view.x = CLAMP(pos_current_view.x, 0, current_view_size.x); + pos_current_view.y = CLAMP(pos_current_view.y, 0, current_view_size.y); + + emit_fl_event( + buffer, + make_fl_mouse_event( + event->pointer.buttons & kFlutterPointerButtonMousePrimary ? kMove : kHover, + event->timestamp, + pos_current_view, + event->global_slot_id, + event->pointer.buttons, + current_view_id + ) + ); + + compositor->cursor.pos_view = pos_current_view; + } + + window_unrefp(¤t_window); +} + +static void on_pointer_scroll_event(struct compositor *compositor, struct fl_event_buffer *buffer, const struct user_input_event *event) { + (void) compositor; + (void) buffer; + (void) event; + /// TODO: Implement + UNIMPLEMENTED(); +} + +static void on_pointer_button_event(struct compositor *compositor, struct fl_event_buffer *buffer, const struct user_input_event *event) { + (void) compositor; + (void) buffer; + (void) event; + /// TODO: Implement + UNIMPLEMENTED(); +} - if (compositor->cursor_pos.y < 0.0f) { - compositor->cursor_pos.y = 0.0f; - } else if (compositor->cursor_pos.y > viewgeo.view_size.y) { - compositor->cursor_pos.y = viewgeo.view_size.y; +static void on_pointer_event(struct compositor *compositor, struct fl_event_buffer *buffer, const struct user_input_event *event) { + if (event->pointer.is_absolute) { + on_absolute_pointer_event(compositor, buffer, event); + } else if (event->pointer.delta.x != 0 || event->pointer.delta.y != 0) { + on_relative_pointer_event_locked(compositor, buffer, event); + } else if (event->pointer.scroll_delta.x != 0 || event->pointer.scroll_delta.y != 0) { + on_pointer_scroll_event(compositor, buffer, event); + } else if (event->pointer.changed_buttons != 0) { + on_pointer_button_event(compositor, buffer, event); + } +} + +static void on_touch_event(struct compositor *compositor, struct fl_event_buffer *buffer, const struct user_input_event *event) { + int64_t view_id = get_view_id_for_input_device(compositor, event->device); + if (view_id == -1) { + return; + } + + FlutterPointerEvent fl_event; + memset(&fl_event, 0, sizeof fl_event); + + fl_event.struct_size = sizeof(FlutterPointerEvent); + fl_event.timestamp = event->timestamp; + if (event->touch.down_changed) { + if (event->touch.down) { + fl_event.phase = kDown; + } else { + fl_event.phase = kUp; } + } else { + ASSERT(event->touch.down); + fl_event.phase = kMove; } - window_set_cursor( - compositor->main_window, - has_enabled, - enabled, - has_kind, - kind, - has_delta, - VEC2I((int) round(compositor->cursor_pos.x), (int) round(compositor->cursor_pos.y)) - ); + emit_fl_event(buffer, fl_event); +} - compositor_unlock(compositor); +static void on_tablet_tool_event(struct compositor *compositor, struct fl_event_buffer *buffer, const struct user_input_event *event) { + int64_t view_id = get_view_id_for_input_device(compositor, event->device); + if (view_id == -1) { + return; + } + + FlutterPointerEvent fl_event; + memset(&fl_event, 0, sizeof fl_event); + + fl_event.struct_size = sizeof(FlutterPointerEvent); + fl_event.timestamp = event->timestamp; + fl_event.phase = kMove; + fl_event.x = event->tablet.position_ndc.x; + fl_event.y = event->tablet.position_ndc.y; + fl_event.device = kFlutterPointerDeviceKindStylus; + fl_event.view_id = view_id; + + emit_fl_event(buffer, fl_event); +} + +static void on_input(void *userdata, size_t n_events, const struct user_input_event *events) { + struct compositor *compositor; + size_t i; + + ASSERT_NOT_NULL(userdata); + compositor = userdata; + + if (!compositor->has_pointer_event_interface) { + return; + } + + struct fl_event_buffer buffer = { + .n_events = 0, + .pointer_event_interface = compositor->pointer_event_interface, + }; + + for (i = 0; i < n_events; i++) { + const struct user_input_event *event = events + i; + + switch (event->type) { + case USER_INPUT_DEVICE_ADDED: on_device_added(compositor, event->device); break; + case USER_INPUT_DEVICE_REMOVED: on_device_removed(compositor, event->device); break; + case USER_INPUT_SLOT_ADDED: on_slot_added(compositor, &buffer, event); break; + case USER_INPUT_SLOT_REMOVED: on_slot_removed(compositor, &buffer, event); break; + case USER_INPUT_TOUCH: on_touch_event(compositor, &buffer, event); break; + case USER_INPUT_TABLET_TOOL: on_tablet_tool_event(compositor, &buffer, event); break; + case USER_INPUT_POINTER: on_pointer_event(compositor, &buffer, event); break; + case USER_INPUT_KEY: UNREACHABLE(); break; + default: LOG_DEBUG("Unhandled enum user_input_event: %u\n", event->type); break; + } + } + + flush_fl_events(&buffer); } diff --git a/src/compositor_ng.h b/src/compositor_ng.h index d7360964..3d6b4778 100644 --- a/src/compositor_ng.h +++ b/src/compositor_ng.h @@ -15,7 +15,6 @@ #include "cursor.h" #include "flutter-pi.h" #include "frame_scheduler.h" -#include "modesetting.h" #include "pixel_format.h" #include "util/collection.h" #include "util/refcounting.h" @@ -28,71 +27,6 @@ struct compositor; -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 clip_rect { - struct quad rect; - bool is_aa; - - struct aa_rect aa_rect; - - bool is_rounded; - struct vec2f upper_left_corner_radius; - struct vec2f upper_right_corner_radius; - struct vec2f lower_right_corner_radius; - struct vec2f lower_left_corner_radius; -}; - struct fl_layer_props { /** * @brief True if the presentation quadrangle (the quadrangle on the target window into which the @@ -151,24 +85,100 @@ struct frame_scheduler; struct view_geometry; struct window; struct tracer; +struct drm_resources; typedef void (*compositor_frame_begin_cb_t)(void *userdata, uint64_t vblank_ns, uint64_t next_vblank_ns); -struct compositor *compositor_new(struct tracer *tracer, struct window *main_window); +struct fl_display_interface { + FlutterEngineNotifyDisplayUpdateFnPtr notify_display_update; + FlutterEngineSendWindowMetricsEventFnPtr send_window_metrics_event; + FlutterEngine engine; +}; + +struct fl_pointer_event_interface { + FlutterEngineSendPointerEventFnPtr send_pointer_event; + FlutterEngine engine; +}; + +struct user_input; + +MUST_CHECK struct compositor *compositor_new_multiview( + struct tracer *tracer, + struct evloop *raster_loop, + struct udev *udev, + struct drmdev *drmdev, + struct drm_resources *resources, + struct user_input *input +); + +struct compositor * +compositor_new_singleview(struct tracer *tracer, struct evloop *raster_loop, struct window *window, struct user_input *input); void compositor_destroy(struct compositor *compositor); DECLARE_REF_OPS(compositor) +/** + * @brief Sets the callback & flutter engine that flutter displays will be registered to. + * + * This will immediately call the notify_display_update callback with the current connected displays, + * and call the send_window_metrics_event callback for all views. + * + * @param compositor The compositor to set the display interface for. + * @param display_interface The display interface to set. NULL is not allowed. The struct is copied. + */ +void compositor_set_fl_display_interface(struct compositor *compositor, const struct fl_display_interface *display_interface); + +/** + * @brief Sets the callback & flutter engine that pointer events will be sent to. + * + * @param compositor The compositor to set the pointer event interface for. + * @param pointer_event_interface The pointer event interface to set. NULL is not allowed. The struct is copied. + */ +void compositor_set_fl_pointer_event_interface( + struct compositor *compositor, + const struct fl_pointer_event_interface *pointer_event_interface +); + void compositor_get_view_geometry(struct compositor *compositor, struct view_geometry *view_geometry_out); -ATTR_PURE double compositor_get_refresh_rate(struct compositor *compositor); +ATTR_PURE float 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); +/** + * @brief Adds a (non-implicit) view to the compositor, returning the view id. + */ +int64_t compositor_add_view(struct compositor *compositor, struct window *window); -struct surface *compositor_get_view_by_id_locked(struct compositor *compositor, int64_t view_id); +/** + * @brief Removes a view from the compositor. + */ +void compositor_remove_view(struct compositor *compositor, int64_t view_id); + +/** + * @brief Gets the implicit view of the compositor. + * + * Some flutter APIs still assume a single view, so this is a way to get a view to use for those. + * + * @attention Since the view setup of the compositor can change at any time, and views + * might be removed and destroyed at any time, this function increases the refcount + * of the view before returning. The caller must call @ref window_unref on the view + * to avoid a memory leak. + * + * @return The implicit view of the compositor. + */ +MUST_CHECK struct window *compositor_ref_implicit_view(struct compositor *compositor); + +/** + * @brief Adds a platform view to the compositor, returning the platform view id. + */ +int compositor_add_platform_view(struct compositor *compositor, struct surface *surface); + +/** + * @brief Removes a platform view from the compositor. + */ +void compositor_remove_platform_view(struct compositor *compositor, int64_t view_id); const FlutterCompositor *compositor_get_flutter_compositor(struct compositor *compositor); @@ -184,15 +194,104 @@ int compositor_get_event_fd(struct compositor *compositor); int compositor_on_event_fd_ready(struct compositor *compositor); -void compositor_set_cursor( - struct compositor *compositor, - bool has_enabled, - bool enabled, - bool has_kind, - enum pointer_kind kind, - bool has_delta, - struct vec2f delta -); +void compositor_set_cursor(struct compositor *compositor, bool has_enabled, bool enabled, bool has_kind, enum pointer_kind kind); + +enum connector_type { + CONNECTOR_TYPE_VGA, + CONNECTOR_TYPE_DVI, + CONNECTOR_TYPE_LVDS, + CONNECTOR_TYPE_DISPLAY_PORT, + CONNECTOR_TYPE_HDMI, + CONNECTOR_TYPE_TV, + CONNECTOR_TYPE_EDP, + CONNECTOR_TYPE_DSI, + CONNECTOR_TYPE_DPI, + CONNECTOR_TYPE_OTHER, +}; + +struct display_setup; +struct connector; +struct display; + +DECLARE_REF_OPS(display_setup) + +/** + * @brief Gets the number of connectors present in this display setup. + */ +size_t display_setup_get_n_connectors(struct display_setup *s); + +/** + * @brief Gets the connector at the given index. + */ +const struct connector *display_setup_get_connector(struct display_setup *s, size_t i); + +/** + * @brief Gets the name of the connector. Can be useful for identification. + * + * e.g. `HDMI-A-1`, `DP-1`, `LVDS-1`, etc. + */ +const char *connector_get_name(const struct connector *connector); + +/** + * @brief Gets the type of the connector. + */ +enum connector_type connector_get_type(const struct connector *connector); + +/** + * @brief Gets the name of the type of the connector. + * + * @attention This can be NULL if the type is unrecognized. + */ +const char *connector_get_type_name(const struct connector *connector); + +/** + * @brief Checks if the connector has a display attached. + */ +bool connector_has_display(const struct connector *connector); + +/** + * @brief Gets the display attached to the connector. + * + * @attention This can be NULL if the connector has no display attached. + */ +const struct display *connector_get_display(const struct connector *connector); + +/** + * @brief Gets the ID of the display as reported to flutter. + */ +size_t display_get_fl_display_id(const struct display *display); + +/** + * @brief Gets the refresh rate of the display in the current mode. + */ +double display_get_refresh_rate(const struct display *display); + +/** + * @brief Gets the width of the display in the current mode, in physical pixels. + */ +struct vec2i display_get_size(const struct display *display); + +/** + * @brief Gets the physical size of the display in millimeters. + */ +struct vec2i display_get_physical_size(const struct display *display); + +/** + * @brief Gets the device pixel ratio of the display in the current mode. + */ +double display_get_device_pixel_ratio(const struct display *display); + +/** + * @brief Gets the ID of the connector. + */ +const char *display_get_connector_id(const struct display *display); + +/** + * @brief Gets a value notifier for the displays & connectors attached to the compositor. + * + * The value is a @ref struct display_setup. + */ +struct notifier *compositor_get_display_setup_notifier(struct compositor *compositor); struct fl_layer_composition; diff --git a/src/cursor.c b/src/cursor.c index 823d5346..94cc56f5 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -7,12 +7,12 @@ #include "util/collection.h" #include "util/geometry.h" -#define PIXEL_RATIO_LDPI 1.25 -#define PIXEL_RATIO_MDPI 1.6666 -#define PIXEL_RATIO_HDPI 2.5 -#define PIXEL_RATIO_XHDPI 3.3333 -#define PIXEL_RATIO_XXHDPI 5 -#define PIXEL_RATIO_XXXHDPI 6.6666 +#define PIXEL_RATIO_LDPI 1.25f +#define PIXEL_RATIO_MDPI 1.6666f +#define PIXEL_RATIO_HDPI 2.5f +#define PIXEL_RATIO_XHDPI 3.3333f +#define PIXEL_RATIO_XXHDPI 5.0f +#define PIXEL_RATIO_XXXHDPI 6.6666f struct pointer_icon { enum pointer_kind kind; @@ -1450,7 +1450,7 @@ static void run_length_decode(void *image_buf, const void *rle_data, size_t size } } -const struct pointer_icon *pointer_icon_for_details(enum pointer_kind kind, double pixel_ratio) { +const struct pointer_icon *pointer_icon_for_details(enum pointer_kind kind, float pixel_ratio) { const struct pointer_icon *best; best = NULL; @@ -1461,7 +1461,7 @@ const struct pointer_icon *pointer_icon_for_details(enum pointer_kind kind, doub if (best == NULL) { best = icon; continue; - } else if (fabs(pixel_ratio - icon->pixel_ratio) < fabs(pixel_ratio - best->pixel_ratio)) { + } else if (fabsf(pixel_ratio - icon->pixel_ratio) < fabsf(pixel_ratio - best->pixel_ratio)) { best = icon; continue; } diff --git a/src/cursor.h b/src/cursor.h index 270404e7..049b9fb4 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -56,7 +56,7 @@ enum pointer_kind { struct pointer_icon; -const struct pointer_icon *pointer_icon_for_details(enum pointer_kind kind, double pixel_ratio); +const struct pointer_icon *pointer_icon_for_details(enum pointer_kind kind, float pixel_ratio); enum pointer_kind pointer_icon_get_kind(const struct pointer_icon *icon); diff --git a/src/dmabuf_surface.c b/src/dmabuf_surface.c index e8e3f310..6945e8cf 100644 --- a/src/dmabuf_surface.c +++ b/src/dmabuf_surface.c @@ -40,6 +40,9 @@ #include #include +#include "kms/resources.h" +#include "kms/drmdev.h" +#include "kms/req_builder.h" #include "compositor_ng.h" #include "surface.h" #include "surface_private.h" @@ -68,7 +71,7 @@ void refcounted_dmabuf_destroy(struct refcounted_dmabuf *dmabuf) { free(dmabuf); } -DEFINE_STATIC_REF_OPS(refcounted_dmabuf, n_refs); +DEFINE_STATIC_REF_OPS(refcounted_dmabuf, n_refs) struct dmabuf_surface { struct surface surface; @@ -298,10 +301,10 @@ static int dmabuf_surface_present_kms(struct surface *_s, const struct fl_layer_ .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, + .dst_x = (int) round(props->aa_rect.offset.x), + .dst_y = (int) round(props->aa_rect.offset.y), + .dst_w = (int) round(props->aa_rect.size.x), + .dst_h = (int) round(props->aa_rect.size.y), .has_rotation = false, .rotation = PLANE_TRANSFORM_ROTATE_0, diff --git a/src/dummy_window.c b/src/dummy_window.c new file mode 100644 index 00000000..81177945 --- /dev/null +++ b/src/dummy_window.c @@ -0,0 +1,212 @@ +#include "dummy_window.h" + +#include "window.h" +#include "window_private.h" + +static const struct window_ops dummy_window_ops; + +MUST_CHECK struct window *dummy_window_new( + // clang-format off + struct tracer *tracer, + struct frame_scheduler *scheduler, + enum renderer_type renderer_type, + struct gl_renderer *gl_renderer, + struct vk_renderer *vk_renderer, + struct vec2i size, + bool has_explicit_dimensions, int width_mm, int height_mm, + float refresh_rate + // clang-format on +) { + struct window *window; + + window = malloc(sizeof *window); + if (window == NULL) { + return NULL; + } + + window_init( + // clang-format off + window, + tracer, + scheduler, + false, PLANE_TRANSFORM_NONE, + false, kLandscapeLeft, + size.x, size.y, + has_explicit_dimensions, width_mm, height_mm, + refresh_rate, + false, PIXFMT_RGB565, + renderer_type, + gl_renderer, + vk_renderer + // clang-format on + ); + + window->renderer_type = renderer_type; + if (gl_renderer != NULL) { +#ifdef HAVE_EGL_GLES2 + window->gl_renderer = gl_renderer_ref(gl_renderer); +#else + UNREACHABLE(); +#endif + } + if (vk_renderer != NULL) { +#ifdef HAVE_VULKAN + window->vk_renderer = vk_renderer_ref(vk_renderer); +#else + UNREACHABLE(); +#endif + } else { + window->vk_renderer = NULL; + } + window->ops = dummy_window_ops; + return window; +} + +static int dummy_window_push_composition(struct window *window, struct fl_layer_composition *composition) { + (void) window; + (void) composition; + /// TODO: Maybe allow to export the layer composition as an image, for testing purposes. + return 0; +} + +static struct render_surface *dummy_window_get_render_surface_internal(struct window *window, bool has_size, UNUSED struct vec2i size) { + struct render_surface *render_surface; + + ASSERT_NOT_NULL(window); + + if (!has_size) { + size = vec2f_round_to_integer(window->view_size); + } + + if (window->render_surface != NULL) { + return window->render_surface; + } + + if (window->renderer_type == kOpenGL_RendererType) { + // opengl +#ifdef HAVE_EGL_GLES2 + // EGL_NO_CONFIG_KHR is defined by EGL_KHR_no_config_context. + #ifndef EGL_KHR_no_config_context + #error "EGL header definitions for extension EGL_KHR_no_config_context are required." + #endif + + struct egl_gbm_render_surface *egl_surface = egl_gbm_render_surface_new_with_egl_config( + window->tracer, + size, + gl_renderer_get_gbm_device(window->gl_renderer), + window->gl_renderer, + window->has_forced_pixel_format ? window->forced_pixel_format : PIXFMT_ARGB8888, + EGL_NO_CONFIG_KHR, + NULL, + 0 + ); + if (egl_surface == NULL) { + LOG_ERROR("Couldn't create EGL GBM rendering surface.\n"); + render_surface = NULL; + } else { + render_surface = CAST_RENDER_SURFACE(egl_surface); + } + +#else + UNREACHABLE(); +#endif + } else { + ASSUME(window->renderer_type == kVulkan_RendererType); + + // vulkan +#ifdef HAVE_VULKAN + UNIMPLEMENTED(); +#else + UNREACHABLE(); +#endif + } + + window->render_surface = render_surface; + return render_surface; +} + +static struct render_surface *dummy_window_get_render_surface(struct window *window, struct vec2i size) { + ASSERT_NOT_NULL(window); + return dummy_window_get_render_surface_internal(window, true, size); +} + +#ifdef HAVE_EGL_GLES2 +static bool dummy_window_has_egl_surface(struct window *window) { + ASSERT_NOT_NULL(window); + + if (window->renderer_type == kOpenGL_RendererType) { + return window->render_surface != NULL; + } else { + return false; + } +} + +static EGLSurface dummy_window_get_egl_surface(struct window *window) { + ASSERT_NOT_NULL(window); + + if (window->renderer_type == kOpenGL_RendererType) { + struct render_surface *render_surface = dummy_window_get_render_surface_internal(window, false, VEC2I(0, 0)); + return egl_gbm_render_surface_get_egl_surface(CAST_EGL_GBM_RENDER_SURFACE(render_surface)); + } else { + return EGL_NO_SURFACE; + } +} +#endif + +static void dummy_window_deinit(struct window *window) { + ASSERT_NOT_NULL(window); + + if (window->render_surface != NULL) { + surface_unref(CAST_SURFACE(window->render_surface)); + } + + if (window->gl_renderer != NULL) { +#ifdef HAVE_EGL_GLES2 + gl_renderer_unref(window->gl_renderer); +#else + UNREACHABLE(); +#endif + } + + if (window->vk_renderer != NULL) { +#ifdef HAVE_VULKAN + vk_renderer_unref(window->vk_renderer); +#else + UNREACHABLE(); +#endif + } + + window_deinit(window); +} + +static int dummy_window_set_cursor_locked( + // clang-format off + struct window *window, + bool has_enabled, bool enabled, + bool has_kind, enum pointer_kind kind, + bool has_pos, struct vec2i pos + // clang-format on +) { + ASSERT_NOT_NULL(window); + + (void) window; + (void) has_enabled; + (void) enabled; + (void) has_kind; + (void) kind; + (void) has_pos; + (void) pos; + + return 0; +} + +static const struct window_ops dummy_window_ops = { + .deinit = dummy_window_deinit, + .push_composition = dummy_window_push_composition, + .get_render_surface = dummy_window_get_render_surface, +#ifdef HAVE_EGL_GLES2 + .has_egl_surface = dummy_window_has_egl_surface, + .get_egl_surface = dummy_window_get_egl_surface, +#endif + .set_cursor_locked = dummy_window_set_cursor_locked, +}; diff --git a/src/dummy_window.h b/src/dummy_window.h new file mode 100644 index 00000000..2062b34c --- /dev/null +++ b/src/dummy_window.h @@ -0,0 +1,25 @@ +#ifndef _FLUTTERPI_SRC_DUMMY_WINDOW_H +#define _FLUTTERPI_SRC_DUMMY_WINDOW_H + +#include "util/geometry.h" +#include "window.h" + +struct tracer; +struct frame_scheduler; +struct gl_renderer; +struct vk_renderer; + +MUST_CHECK struct window *dummy_window_new( + // clang-format off + struct tracer *tracer, + struct frame_scheduler *scheduler, + enum renderer_type renderer_type, + struct gl_renderer *gl_renderer, + struct vk_renderer *vk_renderer, + struct vec2i size, + bool has_explicit_dimensions, int width_mm, int height_mm, + float refresh_rate + // clang-format on +); + +#endif // _FLUTTERPI_SRC_DUMMY_WINDOW_H diff --git a/src/egl.h b/src/egl.h index 9a9a936c..ca7b9efe 100644 --- a/src/egl.h +++ b/src/egl.h @@ -475,7 +475,8 @@ static inline const char *egl_strerror(EGLenum result) { } } - #define LOG_EGL_ERROR(result, fmt, ...) LOG_ERROR(fmt ": %s\n", __VA_ARGS__ egl_strerror(result)) + #define LOG_EGL_ERROR_FMT(result, fmt, ...) LOG_ERROR(fmt ": %s\n", __VA_ARGS__ egl_strerror(result)) + #define LOG_EGL_ERROR(result, fmt) LOG_ERROR(fmt ": %s\n", egl_strerror(result)) #endif #endif // _FLUTTERPI_SRC_EGL_H diff --git a/src/egl_gbm_render_surface.c b/src/egl_gbm_render_surface.c index 30484b45..fcae0f8e 100644 --- a/src/egl_gbm_render_surface.c +++ b/src/egl_gbm_render_surface.c @@ -16,7 +16,9 @@ #include "egl.h" #include "gl_renderer.h" #include "gles.h" -#include "modesetting.h" +#include "kms/drmdev.h" +#include "kms/req_builder.h" +#include "kms/resources.h" #include "pixel_format.h" #include "render_surface.h" #include "render_surface_private.h" @@ -146,6 +148,7 @@ static int egl_gbm_render_surface_init( } #endif + int with_modifiers_errno = 0; gbm_surface = NULL; if (allowed_modifiers != NULL) { gbm_surface = gbm_surface_create_with_modifiers( @@ -157,11 +160,10 @@ static int egl_gbm_render_surface_init( n_allowed_modifiers ); if (gbm_surface == NULL) { - ok = errno; - LOG_ERROR("Couldn't create GBM surface for rendering. gbm_surface_create_with_modifiers: %s\n", strerror(ok)); - LOG_ERROR("Will retry without modifiers\n"); + with_modifiers_errno = errno; } } + if (gbm_surface == NULL) { gbm_surface = gbm_surface_create( gbm_device, @@ -172,8 +174,20 @@ static int egl_gbm_render_surface_init( ); if (gbm_surface == NULL) { ok = errno; - LOG_ERROR("Couldn't create GBM surface for rendering. gbm_surface_create: %s\n", strerror(ok)); - return ok; + + if (allowed_modifiers != NULL) { + LOG_ERROR( + "Couldn't create GBM surface for rendering. gbm_surface_create_with_modifiers: %s, gbm_surface_create: %s\n", + strerror(with_modifiers_errno), + strerror(ok) + ); + } else { + LOG_ERROR("Couldn't create GBM surface for rendering. gbm_surface_create: %s\n", strerror(ok)); + } + + // Return an error != 0 in any case, so the caller doesn't think + // that the surface was created successfully. + return ok ? ok : EIO; } } @@ -383,10 +397,8 @@ static void on_release_layer(void *userdata) { static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder) { struct egl_gbm_render_surface *egl_surface; struct gbm_bo_meta *meta; - struct drmdev *drmdev; struct gbm_bo *bo; enum pixfmt pixel_format; - uint32_t fb_id, opaque_fb_id; int ok; egl_surface = CAST_THIS(s); @@ -410,16 +422,18 @@ static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl goto fail_unlock; } - drmdev = kms_req_builder_get_drmdev(builder); - ASSERT_NOT_NULL(drmdev); + meta->drmdev = kms_req_builder_get_drmdev(builder); + ASSERT_NOT_NULL(meta->drmdev); + + drmdev_ref(meta->drmdev); struct drm_crtc *crtc = kms_req_builder_get_crtc(builder); ASSERT_NOT_NULL(crtc); - if (drm_crtc_any_plane_supports_format(drmdev, crtc, egl_surface->pixel_format)) { + if (drm_resources_any_crtc_plane_supports_format(kms_req_builder_peek_resources(builder), crtc->id, egl_surface->pixel_format)) { TRACER_BEGIN(egl_surface->surface.tracer, "drmdev_add_fb (non-opaque)"); - fb_id = drmdev_add_fb_from_gbm_bo( - drmdev, + uint32_t fb_id = drmdev_add_fb_from_gbm_bo( + meta->drmdev, bo, /* cast_opaque */ false ); @@ -428,7 +442,7 @@ static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl if (fb_id == 0) { ok = EIO; LOG_ERROR("Couldn't add GBM buffer as DRM framebuffer.\n"); - goto fail_free_meta; + goto fail_unref_drmdev; } meta->has_nonopaque_fb_id = true; @@ -441,20 +455,23 @@ static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl // if this EGL surface is non-opaque and has an opaque equivalent if (!get_pixfmt_info(egl_surface->pixel_format)->is_opaque && pixfmt_opaque(egl_surface->pixel_format) != egl_surface->pixel_format && - drm_crtc_any_plane_supports_format(drmdev, crtc, pixfmt_opaque(egl_surface->pixel_format))) { - opaque_fb_id = drmdev_add_fb_from_gbm_bo( - drmdev, + drm_resources_any_crtc_plane_supports_format( + kms_req_builder_peek_resources(builder), + crtc->id, + pixfmt_opaque(egl_surface->pixel_format) + )) { + meta->opaque_fb_id = drmdev_add_fb_from_gbm_bo( + meta->drmdev, bo, /* cast_opaque */ true ); - if (opaque_fb_id == 0) { + if (meta->opaque_fb_id == 0) { ok = EIO; LOG_ERROR("Couldn't add GBM buffer as opaque DRM framebuffer.\n"); - goto fail_remove_fb; + goto fail_rm_nonopaque_fb; } meta->has_opaque_fb_id = true; - meta->opaque_fb_id = opaque_fb_id; } else { meta->has_opaque_fb_id = false; meta->opaque_fb_id = 0; @@ -463,11 +480,9 @@ static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl if (!meta->has_nonopaque_fb_id && !meta->has_opaque_fb_id) { ok = EIO; LOG_ERROR("Couldn't add GBM buffer as DRM framebuffer.\n"); - goto fail_free_meta; + goto fail_remove_opaque_fb; } - meta->drmdev = drmdev_ref(drmdev); - meta->nonopaque_fb_id = 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. @@ -493,6 +508,8 @@ static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl ); */ + uint32_t fb_id; + // So we just cast our fb to an XRGB8888 framebuffer and scanout that instead. if (meta->has_nonopaque_fb_id && !meta->has_opaque_fb_id) { fb_id = meta->nonopaque_fb_id; @@ -555,10 +572,18 @@ static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl locked_fb_unref(egl_surface->locked_front_fb); goto fail_unlock; -fail_remove_fb: - drmdev_rm_fb(drmdev, fb_id); +fail_remove_opaque_fb: + if (meta->has_opaque_fb_id) { + drmdev_rm_fb(meta->drmdev, meta->opaque_fb_id); + } + +fail_rm_nonopaque_fb: + if (meta->has_nonopaque_fb_id) { + drmdev_rm_fb(meta->drmdev, meta->nonopaque_fb_id); + } -fail_free_meta: +fail_unref_drmdev: + drmdev_unref(meta->drmdev); free(meta); fail_unlock: @@ -647,10 +672,10 @@ static int egl_gbm_render_surface_queue_present(struct render_surface *s, const LOG_DEBUG( "using fourcc %c%c%c%c (%s) with modifier 0x%" PRIx64 "\n", - fourcc & 0xFF, - (fourcc >> 8) & 0xFF, - (fourcc >> 16) & 0xFF, - (fourcc >> 24) & 0xFF, + (char) (fourcc & 0xFF), + (char) ((fourcc >> 8) & 0xFF), + (char) ((fourcc >> 16) & 0xFF), + (char) ((fourcc >> 24) & 0xFF), has_format ? get_pixfmt_info(format)->name : "?", modifier ); @@ -736,8 +761,12 @@ static int egl_gbm_render_surface_queue_present(struct render_surface *s, const goto fail_release_bo; locked: - /// TODO: Remove this once we're using triple buffering - //ASSERT_MSG(atomic_fetch_add(&render_surface->n_locked_fbs, 1) <= 1, "sanity check failed: too many locked fbs for double-buffered vsync"); +#ifdef DEBUG + ASSERT_MSG( + atomic_fetch_add(&egl_surface->n_locked_fbs, 1) + 1 <= 3, + "Sanity check failed: Too many locked framebuffers for triple buffering." + ); +#endif egl_surface->locked_fbs[i].bo = bo; egl_surface->locked_fbs[i].surface = CAST_THIS(surface_ref(CAST_SURFACE(s))); egl_surface->locked_fbs[i].n_refs = REFCOUNT_INIT_1; diff --git a/src/event_loop.h b/src/event_loop.h deleted file mode 100644 index 62edabe7..00000000 --- a/src/event_loop.h +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: MIT -/* - * Event Loop - * - * - multithreaded event loop - * - * Copyright (c) 2023, Hannes Winkler - */ - -#ifndef _FLUTTERPI_SRC_EVENT_LOOP_H -#define _FLUTTERPI_SRC_EVENT_LOOP_H - -#include "util/refcounting.h" - -struct evloop; - -struct evloop *evloop_new(); - -void evloop_destroy(struct evloop *loop); - -DECLARE_REF_OPS(evloop) - -int evloop_get_fd_locked(struct evloop *loop); - -int evloop_get_fd(struct evloop *loop); - -int evloop_run(struct evloop *loop); - -int evloop_schedule_exit_locked(struct evloop *loop); - -int evloop_schedule_exit(struct evloop *loop); - -int evloop_post_task_locked(struct evloop *loop, void_callback_t callback, void *userdata); - -int evloop_post_task(struct evloop *loop, void_callback_t callback, void *userdata); - -int evloop_post_delayed_task_locked(struct evloop *loop, void_callback_t callback, void *userdata, uint64_t target_time_usec); - -int evloop_post_delayed_task(struct evloop *loop, void_callback_t callback, void *userdata, uint64_t target_time_usec); - -struct evsrc; -void evsrc_destroy_locked(struct evsrc *src); -void evsrc_destroy(struct evsrc *src); - -enum event_handler_return { kNoAction_EventHandlerReturn, kRemoveSrc_EventHandlerReturn }; - -typedef enum event_handler_return (*evloop_io_handler_t)(int fd, uint32_t revents, void *userdata); - -struct evsrc *evloop_add_io_locked(struct evloop *loop, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata); - -struct evsrc *evloop_add_io(struct evloop *loop, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata); - -struct evthread; - -struct evthread *evthread_start(); - -struct evloop *evthread_get_evloop(struct evthread *thread); - -void evthread_join(struct evthread *thread); - -#endif // _FLUTTERPI_SRC_EVENT_LOOP_H diff --git a/src/filesystem_layout.c b/src/filesystem_layout.c index 39924c30..4f73cb89 100644 --- a/src/filesystem_layout.c +++ b/src/filesystem_layout.c @@ -135,16 +135,14 @@ static struct flutter_paths *resolve( // We still haven't found it. Fail because we need it to run flutter. if (path_exists(icudtl_path) == false) { LOG_DEBUG("icudtl file not found at %s.\n", icudtl_path); - free(icudtl_path); - LOG_ERROR("icudtl file not found!\n"); - goto fail_free_asset_bundle_path; + goto fail_free_icudtl_path; } // Find the kernel_blob.bin file. Only necessary for JIT (debug) mode. ok = asprintf(&kernel_blob_path, "%s/%s", app_bundle_path_real, kernel_blob_subpath); if (ok == -1) { - goto fail_free_asset_bundle_path; + goto fail_free_icudtl_path; } if (FLUTTER_RUNTIME_MODE_IS_JIT(runtime_mode) && !path_exists(kernel_blob_path)) { @@ -222,6 +220,9 @@ static struct flutter_paths *resolve( fail_free_kernel_blob_path: free(kernel_blob_path); +fail_free_icudtl_path: + free(icudtl_path); + fail_free_asset_bundle_path: free(asset_bundle_path); diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 31b8c1ce..412fb57f 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -41,11 +41,15 @@ #include #include "compositor_ng.h" +#include "dummy_window.h" #include "filesystem_layout.h" #include "frame_scheduler.h" #include "keyboard.h" +#include "kms/drmdev.h" +#include "kms/kms_window.h" +#include "kms/req_builder.h" +#include "kms/resources.h" #include "locales.h" -#include "modesetting.h" #include "pixel_format.h" #include "platformchannel.h" #include "pluginregistry.h" @@ -54,6 +58,7 @@ #include "texture_registry.h" #include "tracer.h" #include "user_input.h" +#include "util/event_loop.h" #include "util/list.h" #include "util/logging.h" #include "window.h" @@ -185,26 +190,6 @@ struct flutterpi { */ struct compositor *compositor; - /** - * @brief Event source which represents the compositor event fd as registered to the - * event loop. - * - */ - // sd_event_source *compositor_event_source; - - /** - * @brief The user input instance. - * - * Handles touch, mouse and keyboard input and calls the callbacks. - */ - struct user_input *user_input; - - /** - * @brief The user input instance event fd registered to the event loop. - * - */ - // sd_event_source *user_input_event_source; - /** * @brief The locales instance. Provides the system locales to flutter. * @@ -234,13 +219,23 @@ struct flutterpi { bool next_frame_request_is_secondary; } flutter; - /// main event loop - pthread_t event_loop_thread; - pthread_mutex_t event_loop_mutex; - sd_event *event_loop; - int wakeup_event_loop_fd; + /** + * @brief The platform (main) thread event loop. + */ + struct evloop *platform_loop; + pthread_t platform_thread; + + struct evsrc *drmdev_evrsc; - struct evloop *evloop; + struct udev *udev; + + /** + * @brief The user input instance. + * + * Handles touch, mouse and keyboard input and calls the callbacks. + */ + struct user_input *user_input; + struct evsrc *user_input_evsrc; /** * @brief Manages all plugins. @@ -258,10 +253,14 @@ struct flutterpi { struct vk_renderer *vk_renderer; struct libseat *libseat; + struct list_head fd_for_device_id; bool session_active; char *desired_videomode; + + struct evloop *raster_evloop; + struct evthread *raster_thread; }; struct device_id_and_fd { @@ -394,7 +393,8 @@ static void *proc_resolver(void *userdata, const char *name) { flutterpi = userdata; ASSERT_NOT_NULL(flutterpi->gl_renderer); - return gl_renderer_get_proc_address(flutterpi->gl_renderer, name); + fn_ptr_t fn = gl_renderer_get_proc_address(flutterpi->gl_renderer, name); + return *((void **) &fn); } #endif @@ -408,7 +408,8 @@ UNUSED static void *on_get_vulkan_proc_address(void *userdata, FlutterVulkanInst name = "vkGetInstanceProcAddr"; } - return (void *) vkGetInstanceProcAddr((VkInstance) instance, name); + PFN_vkVoidFunction fn = vkGetInstanceProcAddr((VkInstance) instance, name); + return *(void **) (&fn); #else (void) userdata; (void) instance; @@ -458,7 +459,13 @@ static void on_platform_message(const FlutterPlatformMessage *message, void *use static bool flutterpi_runs_platform_tasks_on_current_thread(struct flutterpi *flutterpi) { ASSERT_NOT_NULL(flutterpi); - return pthread_equal(pthread_self(), flutterpi->event_loop_thread) != 0; + return pthread_equal(pthread_self(), flutterpi->platform_thread) != 0; +} + +static bool flutterpi_runs_raster_tasks_on_current_thread(struct flutterpi *flutterpi) { + ASSERT_NOT_NULL(flutterpi); + ASSERT_NOT_NULL_MSG(flutterpi->raster_thread, "The raster thread is not started yet."); + return pthread_equal(pthread_self(), evthread_get_pthread(flutterpi->raster_thread)) != 0; } struct frame_req { @@ -467,7 +474,7 @@ struct frame_req { uint64_t vblank_ns, next_vblank_ns; }; -static int on_deferred_begin_frame(void *userdata) { +static void on_deferred_begin_frame(void *userdata) { FlutterEngineResult engine_result; struct frame_req *req; @@ -483,10 +490,7 @@ static int on_deferred_begin_frame(void *userdata) { 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; } UNUSED static void on_begin_frame(void *userdata, uint64_t vblank_ns, uint64_t next_vblank_ns) { @@ -510,7 +514,7 @@ UNUSED static void on_begin_frame(void *userdata, uint64_t vblank_ns, uint64_t n } else { req->vblank_ns = vblank_ns; req->next_vblank_ns = next_vblank_ns; - ok = flutterpi_post_platform_task(on_deferred_begin_frame, req); + ok = flutterpi_post_platform_task(req->flutterpi, on_deferred_begin_frame, req); if (ok != 0) { LOG_ERROR("Couldn't defer signalling frame begin.\n"); goto fail_free_req; @@ -546,13 +550,12 @@ UNUSED static void on_frame_request(void *userdata, intptr_t baton) { 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)); + req->next_vblank_ns = req->vblank_ns + (uint64_t) (1000000000.0f / compositor_get_refresh_rate(flutterpi->compositor)); - if (flutterpi_runs_platform_tasks_on_current_thread(req->flutterpi)) { + if (flutterpi_runs_platform_tasks_on_current_thread(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); + engine_result = req->flutterpi->flutter.procs.OnVsync(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; @@ -560,7 +563,7 @@ UNUSED static void on_frame_request(void *userdata, intptr_t baton) { free(req); } else { - ok = flutterpi_post_platform_task(on_deferred_begin_frame, req); + ok = flutterpi_post_platform_task(flutterpi, on_deferred_begin_frame, req); if (ok != 0) { LOG_ERROR("Couldn't defer signalling frame begin.\n"); goto fail_free_req; @@ -580,225 +583,104 @@ UNUSED static FlutterTransformation on_get_transformation(void *userdata) { ASSERT_NOT_NULL(userdata); flutterpi = userdata; - compositor_get_view_geometry(flutterpi->compositor, &geometry); + struct window *w = compositor_ref_implicit_view(flutterpi->compositor); + if (w == NULL) { + return MAT3F_AS_FLUTTER_TRANSFORM(MAT3F_IDENTITY()); + } + + geometry = window_get_view_geometry(w); + window_unref(w); return MAT3F_AS_FLUTTER_TRANSFORM(geometry.view_to_display_transform); } atomic_int_least64_t platform_task_counter = 0; -/// platform tasks -static int on_execute_platform_task(sd_event_source *s, void *userdata) { - struct platform_task *task; - int ok; - - task = userdata; - ok = task->callback(task->userdata); - if (ok != 0) { - LOG_ERROR("Error executing platform task: %s\n", strerror(ok)); - } - - free(task); - - sd_event_source_set_enabled(s, SD_EVENT_OFF); - sd_event_source_unrefp(&s); +struct task { + void_callback_t callback; + void *userdata; +}; - return 0; +int flutterpi_post_platform_task(struct flutterpi *flutterpi, void_callback_t callback, void *userdata) { + return evloop_post_task(flutterpi->platform_loop, callback, userdata); } -int flutterpi_post_platform_task(int (*callback)(void *userdata), void *userdata) { - struct platform_task *task; - sd_event_source *src; - int ok; - - task = malloc(sizeof *task); - if (task == NULL) { - return ENOMEM; - } - - task->callback = callback; - task->userdata = userdata; - - if (pthread_self() != flutterpi->event_loop_thread) { - pthread_mutex_lock(&flutterpi->event_loop_mutex); - } - - ok = sd_event_add_defer(flutterpi->event_loop, &src, on_execute_platform_task, task); - if (ok < 0) { - LOG_ERROR("Error posting platform task to main loop. sd_event_add_defer: %s\n", strerror(-ok)); - ok = -ok; - goto fail_unlock_event_loop; - } - - // Higher values mean lower priority. So later platform tasks are handled later too. - sd_event_source_set_priority(src, atomic_fetch_add(&platform_task_counter, 1)); - - if (pthread_self() != flutterpi->event_loop_thread) { - ok = write(flutterpi->wakeup_event_loop_fd, (uint8_t[8]){ 0, 0, 0, 0, 0, 0, 0, 1 }, 8); - if (ok < 0) { - ok = errno; - LOG_ERROR("Error arming main loop for platform task. write: %s\n", strerror(ok)); - goto fail_unlock_event_loop; - } - } - - if (pthread_self() != flutterpi->event_loop_thread) { - pthread_mutex_unlock(&flutterpi->event_loop_mutex); - } - - return 0; - -fail_unlock_event_loop: - if (pthread_self() != flutterpi->event_loop_thread) { - pthread_mutex_unlock(&flutterpi->event_loop_mutex); - } - - return ok; +int flutterpi_post_delayed_platform_task(struct flutterpi *flutterpi, void_callback_t callback, void *userdata, uint64_t target_time_usec) { + return evloop_post_delayed_task(flutterpi->platform_loop, callback, userdata, target_time_usec); } -/// timed platform tasks -static int on_execute_platform_task_with_time(sd_event_source *s, uint64_t usec, void *userdata) { - struct platform_task *task; - int ok; - - (void) usec; - - task = userdata; - ok = task->callback(task->userdata); - if (ok != 0) { - LOG_ERROR("Error executing timed platform task: %s\n", strerror(ok)); - } - - free(task); - - sd_event_source_set_enabled(s, SD_EVENT_OFF); - sd_event_source_unrefp(&s); - - return 0; +struct evloop *flutterpi_get_platform_event_loop(struct flutterpi *flutterpi) { + return flutterpi->platform_loop; } -int flutterpi_post_platform_task_with_time(int (*callback)(void *userdata), void *userdata, uint64_t target_time_usec) { - struct platform_task *task; - //sd_event_source *source; - int ok; - - task = malloc(sizeof *task); - if (task == NULL) { - return ENOMEM; - } - - task->callback = callback; - task->userdata = userdata; - - if (pthread_self() != flutterpi->event_loop_thread) { - pthread_mutex_lock(&flutterpi->event_loop_mutex); - } +struct fl_task { + FlutterTask fl_task; + FlutterEngineRunTaskFnPtr fl_run_task; + FlutterEngine fl_engine; +}; - ok = sd_event_add_time(flutterpi->event_loop, NULL, CLOCK_MONOTONIC, target_time_usec, 1, on_execute_platform_task_with_time, task); - if (ok < 0) { - LOG_ERROR("Error posting platform task to main loop. sd_event_add_time: %s\n", strerror(-ok)); - ok = -ok; - goto fail_unlock_event_loop; - } +/// flutter tasks +static void on_execute_fl_task(void *userdata) { + FlutterEngineResult result; + struct fl_task *task; - if (pthread_self() != flutterpi->event_loop_thread) { - ok = write(flutterpi->wakeup_event_loop_fd, (uint8_t[8]){ 0, 0, 0, 0, 0, 0, 0, 1 }, 8); - if (ok < 0) { - perror("[flutter-pi] Error arming main loop for platform task. write"); - ok = errno; - goto fail_unlock_event_loop; - } - } + task = userdata; - if (pthread_self() != flutterpi->event_loop_thread) { - pthread_mutex_unlock(&flutterpi->event_loop_mutex); + result = task->fl_run_task(task->fl_engine, &task->fl_task); + if (result != kSuccess) { + LOG_ERROR("Error running platform task. FlutterEngineRunTask: %u\n", result); } - return 0; - -fail_unlock_event_loop: - if (pthread_self() != flutterpi->event_loop_thread) { - pthread_mutex_unlock(&flutterpi->event_loop_mutex); - } free(task); - return ok; } -int flutterpi_sd_event_add_io(sd_event_source **source_out, int fd, uint32_t events, sd_event_io_handler_t callback, void *userdata) { +static void on_post_fl_platform_task(FlutterTask fl_task, uint64_t target_time_ns, void *userdata) { + struct flutterpi *fpi; + struct fl_task *task; int ok; - if (pthread_self() != flutterpi->event_loop_thread) { - pthread_mutex_lock(&flutterpi->event_loop_mutex); - } - - ok = sd_event_add_io(flutterpi->event_loop, source_out, fd, events, callback, userdata); - if (ok < 0) { - LOG_ERROR("Could not add IO callback to event loop. sd_event_add_io: %s\n", strerror(-ok)); - return -ok; - } - - if (pthread_self() != flutterpi->event_loop_thread) { - ok = write(flutterpi->wakeup_event_loop_fd, (uint8_t[8]){ 0, 0, 0, 0, 0, 0, 0, 1 }, 8); - if (ok < 0) { - perror("[flutter-pi] Error arming main loop for io callback. write"); - ok = errno; - goto fail_unlock_event_loop; - } - } - - if (pthread_self() != flutterpi->event_loop_thread) { - pthread_mutex_unlock(&flutterpi->event_loop_mutex); - } - - return 0; + fpi = userdata; -fail_unlock_event_loop: - if (pthread_self() != flutterpi->event_loop_thread) { - pthread_mutex_unlock(&flutterpi->event_loop_mutex); + task = malloc(sizeof *task); + if (task == NULL) { + return; } - return ok; -} - -/// flutter tasks -static int on_execute_flutter_task(void *userdata) { - FlutterEngineResult result; - FlutterTask *task; - task = userdata; + task->fl_task = fl_task; + task->fl_run_task = flutterpi->flutter.procs.RunTask; + task->fl_engine = flutterpi->flutter.engine; - result = flutterpi->flutter.procs.RunTask(flutterpi->flutter.engine, task); - if (result != kSuccess) { - LOG_ERROR("Error running platform task. FlutterEngineRunTask: %d\n", result); + ok = flutterpi_post_delayed_platform_task(fpi, on_execute_fl_task, task, target_time_ns / 1000); + if (ok != 0) { free(task); - return EINVAL; } - - free(task); - - return 0; } -static void on_post_flutter_task(FlutterTask task, uint64_t target_time, void *userdata) { - FlutterTask *dup_task; +static void on_post_fl_raster_task(FlutterTask fl_task, uint64_t target_time, void *userdata) { + struct flutterpi *fpi; + struct fl_task *task; int ok; (void) userdata; + fpi = userdata; - dup_task = malloc(sizeof *dup_task); - if (dup_task == NULL) { + task = malloc(sizeof *task); + if (task == NULL) { return; } - *dup_task = task; + task->fl_task = fl_task; + task->fl_run_task = fpi->flutter.procs.RunTask; + task->fl_engine = fpi->flutter.engine; - ok = flutterpi_post_platform_task_with_time(on_execute_flutter_task, dup_task, target_time / 1000); + ok = evloop_post_delayed_task(fpi->raster_evloop, on_execute_fl_task, task, target_time / 1000); if (ok != 0) { - free(dup_task); + free(task); } } /// platform messages -static int on_send_platform_message(void *userdata) { +static void on_send_platform_message(void *userdata) { struct platform_message *msg; FlutterEngineResult result; @@ -832,8 +714,6 @@ static int on_send_platform_message(void *userdata) { if (result != kSuccess) { LOG_ERROR("Error sending platform message. FlutterEngineSendPlatformMessage: %s\n", FLUTTER_RESULT_TO_STRING(result)); } - - return 0; } int flutterpi_send_platform_message( @@ -890,7 +770,7 @@ int flutterpi_send_platform_message( msg->message_size = 0; } - ok = flutterpi_post_platform_task(on_send_platform_message, msg); + ok = flutterpi_post_platform_task(flutterpi, on_send_platform_message, msg); if (ok != 0) { if (message && message_size) { free(msg->message); @@ -942,7 +822,7 @@ int flutterpi_respond_to_platform_message( msg->message = 0; } - ok = flutterpi_post_platform_task(on_send_platform_message, msg); + ok = flutterpi_post_platform_task(flutterpi, on_send_platform_message, msg); if (ok != 0) { if (msg->message) { free(msg->message); @@ -1028,6 +908,14 @@ struct gbm_device *flutterpi_get_gbm_device(struct flutterpi *flutterpi) { return drmdev_get_gbm_device(flutterpi->drmdev); } +struct compositor *flutterpi_get_compositor(struct flutterpi *flutterpi) { + return compositor_ref(flutterpi->compositor); +} + +struct compositor *flutterpi_peek_compositor(struct flutterpi *flutterpi) { + return flutterpi->compositor; +} + bool flutterpi_has_gl_renderer(struct flutterpi *flutterpi) { ASSERT_NOT_NULL(flutterpi); return flutterpi->gl_renderer != NULL; @@ -1039,7 +927,7 @@ struct gl_renderer *flutterpi_get_gl_renderer(struct flutterpi *flutterpi) { } void flutterpi_set_pointer_kind(struct flutterpi *flutterpi, enum pointer_kind kind) { - return compositor_set_cursor(flutterpi->compositor, false, false, true, kind, false, VEC2F(0, 0)); + compositor_set_cursor(flutterpi->compositor, false, false, true, kind); } void flutterpi_trace_event_instant(struct flutterpi *flutterpi, const char *name) { @@ -1058,38 +946,24 @@ static bool runs_platform_tasks_on_current_thread(void *userdata) { return flutterpi_runs_platform_tasks_on_current_thread(userdata); } -static int on_wakeup_main_loop(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - uint8_t buffer[8]; - int ok; - - (void) s; - (void) revents; - (void) userdata; - - ok = read(fd, buffer, 8); - if (ok < 0) { - perror("[flutter-pi] Could not read mainloop wakeup userdata. read"); - return errno; - } - - return 0; +static bool runs_raster_tasks_on_current_thread(void *userdata) { + return flutterpi_runs_raster_tasks_on_current_thread(userdata); } /************************** * DISPLAY INITIALIZATION * **************************/ -static int on_drmdev_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { +static enum event_handler_return on_drmdev_modesetting_ready(int fd, uint32_t revents, void *userdata) { struct drmdev *drmdev; - (void) s; (void) fd; (void) revents; - (void) userdata; - ASSERT_NOT_NULL(userdata); drmdev = userdata; - return drmdev_on_event_fd_ready(drmdev); + drmdev_dispatch_modesetting(drmdev); + + return EVENT_HANDLER_CONTINUE; } static const FlutterLocale *on_compute_platform_resolved_locales(const FlutterLocale **locales, size_t n_locales) { @@ -1165,19 +1039,20 @@ static void unload_flutter_engine_lib(void *handle) { dlclose(handle); } -static int get_flutter_engine_procs(void *engine_handle, FlutterEngineProcTable *procs_out) { - // clang-format off - FlutterEngineResult (*get_proc_addresses)(FlutterEngineProcTable *table); - // clang-format on +typedef FlutterEngineResult (*flutter_engine_get_proc_addresses_t)(FlutterEngineProcTable *table); +static int get_flutter_engine_procs(void *engine_handle, FlutterEngineProcTable *procs_out) { FlutterEngineResult engine_result; - get_proc_addresses = dlsym(engine_handle, "FlutterEngineGetProcAddresses"); - if (get_proc_addresses == NULL) { + void *fn = dlsym(engine_handle, "FlutterEngineGetProcAddresses"); + if (fn == NULL) { LOG_ERROR("Could not resolve flutter engine function FlutterEngineGetProcAddresses.\n"); return EINVAL; } + flutter_engine_get_proc_addresses_t get_proc_addresses; + *((void **) &get_proc_addresses) = fn; + procs_out->struct_size = sizeof(FlutterEngineProcTable); engine_result = get_proc_addresses(procs_out); if (engine_result != kSuccess) { @@ -1258,6 +1133,7 @@ static FlutterEngine create_flutter_engine( char **engine_argv, struct compositor *compositor, FlutterEngineAOTData aot_data, + pthread_t raster_thread, const FlutterEngineProcTable *procs ) { FlutterEngineResult engine_result; @@ -1313,14 +1189,24 @@ static FlutterEngine create_flutter_engine( platform_task_runner.struct_size = sizeof(FlutterTaskRunnerDescription); platform_task_runner.user_data = flutterpi; platform_task_runner.runs_task_on_current_thread_callback = runs_platform_tasks_on_current_thread; - platform_task_runner.post_task_callback = on_post_flutter_task; + platform_task_runner.post_task_callback = on_post_fl_platform_task; + platform_task_runner.identifier = pthread_self(); + + FlutterTaskRunnerDescription raster_task_runner; + memset(&raster_task_runner, 0, sizeof(raster_task_runner)); + + raster_task_runner.struct_size = sizeof(FlutterTaskRunnerDescription); + raster_task_runner.user_data = flutterpi; + raster_task_runner.runs_task_on_current_thread_callback = runs_raster_tasks_on_current_thread; + raster_task_runner.post_task_callback = on_post_fl_raster_task; + raster_task_runner.identifier = raster_thread; FlutterCustomTaskRunners custom_task_runners; memset(&custom_task_runners, 0, sizeof(custom_task_runners)); custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners); custom_task_runners.platform_task_runner = &platform_task_runner; - custom_task_runners.render_task_runner = NULL; + custom_task_runners.render_task_runner = &raster_task_runner; custom_task_runners.thread_priority_setter = NULL; // configure the project @@ -1374,10 +1260,8 @@ static FlutterEngine create_flutter_engine( static int flutterpi_run(struct flutterpi *flutterpi) { FlutterEngineProcTable *procs; - struct view_geometry geometry; FlutterEngineResult engine_result; - FlutterEngine engine; - int ok, evloop_fd; + int ok; procs = &flutterpi->flutter.procs; @@ -1398,159 +1282,66 @@ static int flutterpi_run(struct flutterpi *flutterpi) { return EINVAL; } - engine = create_flutter_engine( + flutterpi->raster_thread = evthread_start_with_loop(flutterpi->raster_evloop); + if (flutterpi->raster_thread == NULL) { + LOG_ERROR("Could not start raster thread.\n"); + return EINVAL; + } + + flutterpi->flutter.engine = create_flutter_engine( flutterpi->vk_renderer, flutterpi->flutter.paths, flutterpi->flutter.engine_argc, flutterpi->flutter.engine_argv, flutterpi->compositor, flutterpi->flutter.aot_data, + evthread_get_pthread(flutterpi->raster_thread), &flutterpi->flutter.procs ); - if (engine == NULL) { - return EINVAL; + if (flutterpi->flutter.engine == NULL) { + ok = EINVAL; + goto fail_stop_raster_thread; } - flutterpi->flutter.engine = engine; - - engine_result = procs->RunInitialized(engine); + 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)); ok = EIO; goto fail_deinitialize_engine; } - ok = locales_add_to_fl_engine(flutterpi->locales, engine, procs->UpdateLocales); + ok = locales_add_to_fl_engine(flutterpi->locales, flutterpi->flutter.engine, procs->UpdateLocales); if (ok != 0) { goto fail_shutdown_engine; } - FlutterEngineDisplay display; - memset(&display, 0, sizeof(display)); - - display.struct_size = sizeof(FlutterEngineDisplay); - display.display_id = 0; - display.single_display = true; - display.refresh_rate = compositor_get_refresh_rate(flutterpi->compositor); - - engine_result = procs->NotifyDisplayUpdate(engine, kFlutterEngineDisplaysUpdateTypeStartup, &display, 1); - if (engine_result != kSuccess) { - ok = EINVAL; - LOG_ERROR( - "Could not send display update to flutter engine. FlutterEngineNotifyDisplayUpdate: %s\n", - FLUTTER_RESULT_TO_STRING(engine_result) - ); - goto fail_shutdown_engine; - } - - compositor_get_view_geometry(flutterpi->compositor, &geometry); - - // just so we get an error if the window metrics event was expanded without us noticing - FlutterWindowMetricsEvent window_metrics_event; - memset(&window_metrics_event, 0, sizeof(window_metrics_event)); - - window_metrics_event.struct_size = sizeof(FlutterWindowMetricsEvent); - window_metrics_event.width = geometry.view_size.x; - window_metrics_event.height = geometry.view_size.y; - window_metrics_event.pixel_ratio = geometry.device_pixel_ratio; - window_metrics_event.left = 0; - window_metrics_event.top = 0; - window_metrics_event.physical_view_inset_top = 0; - window_metrics_event.physical_view_inset_right = 0; - window_metrics_event.physical_view_inset_bottom = 0; - window_metrics_event.physical_view_inset_left = 0; - - // update window size - engine_result = procs->SendWindowMetricsEvent(engine, &window_metrics_event); - if (engine_result != kSuccess) { - LOG_ERROR( - "Could not send window metrics to flutter engine. FlutterEngineSendWindowMetricsEvent: %s\n", - FLUTTER_RESULT_TO_STRING(engine_result) - ); - goto fail_shutdown_engine; - } - - pthread_mutex_lock(&flutterpi->event_loop_mutex); - - ok = sd_event_get_fd(flutterpi->event_loop); - if (ok < 0) { - ok = -ok; - LOG_ERROR("Could not get fd for main event loop. sd_event_get_fd: %s\n", strerror(ok)); - pthread_mutex_unlock(&flutterpi->event_loop_mutex); - goto fail_shutdown_engine; - } - - pthread_mutex_unlock(&flutterpi->event_loop_mutex); - - evloop_fd = ok; + // This will register all displays to flutter + // and trigger window metrics events. + compositor_set_fl_display_interface( + flutterpi->compositor, + &(const struct fl_display_interface){ + .notify_display_update = procs->NotifyDisplayUpdate, + .send_window_metrics_event = procs->SendWindowMetricsEvent, + .engine = flutterpi->flutter.engine, + } + ); - { - fd_set rfds, wfds, xfds; - int state; - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_ZERO(&xfds); - FD_SET(evloop_fd, &rfds); - FD_SET(evloop_fd, &wfds); - FD_SET(evloop_fd, &xfds); - - const fd_set const_fds = rfds; - - pthread_mutex_lock(&flutterpi->event_loop_mutex); - - do { - state = sd_event_get_state(flutterpi->event_loop); - switch (state) { - case SD_EVENT_INITIAL: - ok = sd_event_prepare(flutterpi->event_loop); - if (ok < 0) { - ok = -ok; - LOG_ERROR("Could not prepare event loop. sd_event_prepare: %s\n", strerror(ok)); - goto fail_shutdown_engine; - } + compositor_set_fl_pointer_event_interface( + flutterpi->compositor, + &(const struct fl_pointer_event_interface){ + .send_pointer_event = procs->SendPointerEvent, + .engine = flutterpi->flutter.engine, + } + ); - break; - case SD_EVENT_ARMED: - pthread_mutex_unlock(&flutterpi->event_loop_mutex); - - do { - rfds = const_fds; - wfds = const_fds; - xfds = const_fds; - ok = select(evloop_fd + 1, &rfds, &wfds, &xfds, NULL); - if ((ok < 0) && (errno != EINTR)) { - ok = errno; - LOG_ERROR("Could not wait for event loop events. select: %s\n", strerror(ok)); - goto fail_shutdown_engine; - } - } while ((ok < 0) && (errno == EINTR)); - - pthread_mutex_lock(&flutterpi->event_loop_mutex); - - ok = sd_event_wait(flutterpi->event_loop, 0); - if (ok < 0) { - ok = -ok; - LOG_ERROR("Could not check for event loop events. sd_event_wait: %s\n", strerror(ok)); - goto fail_shutdown_engine; - } + user_input_resume(flutterpi->user_input); - break; - case SD_EVENT_PENDING: - ok = sd_event_dispatch(flutterpi->event_loop); - if (ok < 0) { - ok = -ok; - LOG_ERROR("Could not dispatch event loop events. sd_event_dispatch: %s\n", strerror(ok)); - goto fail_shutdown_engine; - } + evloop_run(flutterpi->platform_loop); - break; - case SD_EVENT_FINISHED: break; - default: UNREACHABLE(); - } - } while (state != SD_EVENT_FINISHED); + compositor_set_fl_pointer_event_interface(flutterpi->compositor, NULL); + user_input_suspend(flutterpi->user_input); - pthread_mutex_unlock(&flutterpi->event_loop_mutex); - } + compositor_set_fl_display_interface(flutterpi->compositor, NULL); // We deinitialize the plugins here so plugins don't attempt to use the // flutter engine anymore. @@ -1558,26 +1349,27 @@ static int flutterpi_run(struct flutterpi *flutterpi) { // texture_push_frame in another thread. plugin_registry_ensure_plugins_deinitialized(flutterpi->plugin_registry); - flutterpi->flutter.procs.Shutdown(engine); + flutterpi->flutter.procs.Shutdown(flutterpi->flutter.engine); flutterpi->flutter.engine = NULL; + + evthread_stop(flutterpi->raster_thread); return 0; fail_shutdown_engine: - flutterpi->flutter.procs.Shutdown(engine); - return ok; + flutterpi->flutter.procs.Shutdown(flutterpi->flutter.engine); + goto fail_stop_raster_thread; fail_deinitialize_engine: - flutterpi->flutter.procs.Deinitialize(engine); + flutterpi->flutter.procs.Deinitialize(flutterpi->flutter.engine); + flutterpi->flutter.engine = NULL; + +fail_stop_raster_thread: + evthread_stop(flutterpi->raster_thread); + flutterpi->raster_thread = NULL; return ok; } void flutterpi_schedule_exit(struct flutterpi *flutterpi) { - int ok; - - if (pthread_self() != flutterpi->event_loop_thread) { - pthread_mutex_lock(&flutterpi->event_loop_mutex); - } - // There's a race condition here: // // Other threads can always call flutterpi_post_platform_task(). We can only @@ -1594,18 +1386,7 @@ void flutterpi_schedule_exit(struct flutterpi *flutterpi) { // leaks. // // There's not really a nice solution here, but we use the 2nd option here. - ok = sd_event_exit(flutterpi->event_loop, 0); - if (ok < 0) { - LOG_ERROR("Could not schedule application exit. sd_event_exit: %s\n", strerror(-ok)); - if (pthread_self() != flutterpi->event_loop_thread) { - pthread_mutex_unlock(&flutterpi->event_loop_mutex); - } - return; - } - - if (pthread_self() != flutterpi->event_loop_thread) { - pthread_mutex_unlock(&flutterpi->event_loop_mutex); - } + evloop_schedule_exit(flutterpi->platform_loop); return; } @@ -1613,235 +1394,47 @@ void flutterpi_schedule_exit(struct flutterpi *flutterpi) { /************** * USER INPUT * **************/ -static void on_flutter_pointer_event(void *userdata, const FlutterPointerEvent *events, size_t n_events) { +UNUSED static void on_flutter_pointer_event(void *userdata, const FlutterPointerEvent *events, size_t n_events) { FlutterEngineResult engine_result; struct flutterpi *flutterpi; ASSERT_NOT_NULL(userdata); flutterpi = userdata; - /// 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); } } -static void on_utf8_character(void *userdata, uint8_t *character) { - struct flutterpi *flutterpi; - int ok; +static enum event_handler_return on_user_input_fd_ready(int fd, uint32_t revents, void *userdata) { + struct user_input *input; - flutterpi = userdata; + (void) fd; + (void) fd; + (void) revents; - (void) flutterpi; + input = userdata; -#ifdef BUILD_TEXT_INPUT_PLUGIN - ok = textin_on_utf8_char(character); - if (ok != 0) { - LOG_ERROR("Error handling keyboard event. textin_on_utf8_char: %s\n", strerror(ok)); - //flutterpi_schedule_exit(flutterpi); - } -#endif + user_input_on_fd_ready(input); + return EVENT_HANDLER_CONTINUE; } -static void on_xkb_keysym(void *userdata, xkb_keysym_t keysym) { - struct flutterpi *flutterpi; - int ok; - - flutterpi = userdata; - (void) flutterpi; - -#ifdef BUILD_TEXT_INPUT_PLUGIN - ok = textin_on_xkb_keysym(keysym); - if (ok != 0) { - LOG_ERROR("Error handling keyboard event. textin_on_xkb_keysym: %s\n", strerror(ok)); - //flutterpi_schedule_exit(flutterpi); - } +static struct flutter_paths *setup_paths(enum flutter_runtime_mode runtime_mode, const char *app_bundle_path) { +#if defined(FILESYSTEM_LAYOUT_DEFAULT) + return fs_layout_flutterpi_resolve(app_bundle_path, runtime_mode); +#elif defined(FILESYSTEM_LAYOUT_METAFLUTTER) + return fs_layout_metaflutter_resolve(app_bundle_path, runtime_mode); +#else + #error "Exactly one of FILESYSTEM_LAYOUT_DEFAULT or FILESYSTEM_LAYOUT_METAFLUTTER must be defined." + return NULL; #endif } -static void -on_gtk_keyevent(void *userdata, uint32_t unicode_scalar_values, uint32_t key_code, uint32_t scan_code, uint32_t modifiers, bool is_down) { - struct flutterpi *flutterpi; - int ok; - - flutterpi = userdata; - (void) flutterpi; - -#ifdef BUILD_RAW_KEYBOARD_PLUGIN - ok = rawkb_send_gtk_keyevent(unicode_scalar_values, key_code, scan_code, modifiers, is_down); - if (ok != 0) { - LOG_ERROR("Error handling keyboard event. rawkb_send_gtk_keyevent: %s\n", strerror(ok)); - //flutterpi_schedule_exit(flutterpi); - } -#endif -} - -static void on_switch_vt(void *userdata, int vt) { - struct flutterpi *flutterpi; - - ASSERT_NOT_NULL(userdata); - flutterpi = userdata; - (void) flutterpi; - (void) vt; - - LOG_DEBUG("on_switch_vt(%d)\n", vt); - - if (flutterpi->libseat != NULL) { -#ifdef HAVE_LIBSEAT - int ok; - - ok = libseat_switch_session(flutterpi->libseat, vt); - if (ok < 0) { - LOG_ERROR("Could not switch session. libseat_switch_session: %s\n", strerror(errno)); - } -#else - UNREACHABLE(); -#endif - } -} - -static void on_set_cursor_enabled(void *userdata, bool enabled) { - struct flutterpi *flutterpi; - - flutterpi = userdata; - (void) flutterpi; - - compositor_set_cursor(flutterpi->compositor, true, enabled, false, POINTER_KIND_NONE, false, VEC2F(0, 0)); -} - -static void on_move_cursor(void *userdata, struct vec2f delta) { - struct flutterpi *flutterpi; - - flutterpi = userdata; - - compositor_set_cursor(flutterpi->compositor, true, true, false, POINTER_KIND_NONE, true, delta); -} - -static int on_user_input_open(const char *path, int flags, void *userdata) { - struct flutterpi *flutterpi; - int ok, fd; - - ASSERT_NOT_NULL(path); - ASSERT_NOT_NULL(userdata); - flutterpi = userdata; - (void) flutterpi; - - if (flutterpi->libseat != NULL) { -#ifdef HAVE_LIBSEAT - struct device_id_and_fd *entry; - int device_id; - - ok = libseat_open_device(flutterpi->libseat, path, &fd); - if (ok < 0) { - ok = errno; - LOG_ERROR("Couldn't open evdev device. libseat_open_device: %s\n", strerror(ok)); - return -ok; - } - - device_id = ok; - - entry = malloc(sizeof *entry); - if (entry == NULL) { - libseat_close_device(flutterpi->libseat, device_id); - return -ENOMEM; - } - - entry->entry = (struct list_head){ NULL, NULL }; - entry->fd = fd; - entry->device_id = device_id; - - list_add(&entry->entry, &flutterpi->fd_for_device_id); - return fd; -#else - UNREACHABLE(); -#endif - } else { - ok = open(path, flags); - if (ok < 0) { - ok = errno; - LOG_ERROR("Couldn't open evdev device. open: %s\n", strerror(ok)); - return -ok; - } - - fd = ok; - return fd; - } -} - -static void on_user_input_close(int fd, void *userdata) { - struct flutterpi *flutterpi; - int ok; - - ASSERT_NOT_NULL(userdata); - flutterpi = userdata; - (void) flutterpi; - - if (flutterpi->libseat != NULL) { -#ifdef HAVE_LIBSEAT - struct device_id_and_fd *entry = NULL; - - list_for_each_entry_safe(struct device_id_and_fd, entry_iter, &flutterpi->fd_for_device_id, entry) { - if (entry_iter->fd == fd) { - entry = entry_iter; - break; - } - } - - if (entry == NULL) { - LOG_ERROR("Could not find the device id for the evdev device that should be closed.\n"); - return; - } - - ok = libseat_close_device(flutterpi->libseat, entry->device_id); - if (ok < 0) { - LOG_ERROR("Couldn't close evdev device. libseat_close_device: %s\n", strerror(errno)); - } - - list_del(&entry->entry); - free(entry); - return; -#else - UNREACHABLE(); -#endif - } else { - ok = close(fd); - if (ok < 0) { - LOG_ERROR("Could not close evdev device. close: %s\n", strerror(errno)); - } - } -} - -static int on_user_input_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - struct user_input *input; - - (void) s; - (void) fd; - (void) revents; - - input = userdata; - - return user_input_on_fd_ready(input); -} - -static struct flutter_paths *setup_paths(enum flutter_runtime_mode runtime_mode, const char *app_bundle_path) { -#if defined(FILESYSTEM_LAYOUT_DEFAULT) - return fs_layout_flutterpi_resolve(app_bundle_path, runtime_mode); -#elif defined(FILESYSTEM_LAYOUT_METAFLUTTER) - return fs_layout_metaflutter_resolve(app_bundle_path, runtime_mode); -#else - #error "Exactly one of FILESYSTEM_LAYOUT_DEFAULT or FILESYSTEM_LAYOUT_METAFLUTTER must be defined." - return NULL; -#endif -} - -static bool parse_vec2i(const char *str, struct vec2i *out) { +static bool parse_vec2i(const char *str, struct vec2i *out) { int ok; ok = sscanf(str, "%d,%d", &out->x, &out->y); @@ -1919,7 +1512,7 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin "%s", usage ); - return false; + goto fail; } break; @@ -1933,7 +1526,7 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin "%s", usage ); - return false; + goto fail; } result_out->rotation = rotation; @@ -1944,13 +1537,13 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin ok = parse_vec2i(optarg, &result_out->physical_dimensions); if (!ok) { LOG_ERROR("ERROR: Invalid argument for --dimensions passed.\n"); - return false; + goto fail; } if (result_out->physical_dimensions.x < 0 || result_out->physical_dimensions.y < 0) { LOG_ERROR("ERROR: Invalid argument for --dimensions passed.\n"); result_out->physical_dimensions = VEC2I(0, 0); - return false; + goto fail; } result_out->has_physical_dimensions = true; @@ -1973,7 +1566,7 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin "%s", usage ); - return false; + goto fail; valid_format: break; @@ -1981,7 +1574,11 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin case 'v':; char *vmode_dup = strdup(optarg); if (vmode_dup == NULL) { - return false; + goto fail; + } + + if (result_out->desired_videomode != NULL) { + free(result_out->desired_videomode); } result_out->desired_videomode = vmode_dup; @@ -1991,15 +1588,15 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin ok = parse_vec2i(optarg, &result_out->dummy_display_size); if (!ok) { LOG_ERROR("ERROR: Invalid argument for --dummy-display-size passed.\n"); - return false; + goto fail; } break; - case 'h': printf("%s", usage); return false; + case 'h': printf("%s", usage); goto fail; case '?': - case ':': LOG_ERROR("Invalid option specified.\n%s", usage); return false; + case ':': LOG_ERROR("Invalid option specified.\n%s", usage); goto fail; case -1: finished_parsing_options = true; break; @@ -2010,10 +1607,10 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin if (optind >= argc) { LOG_ERROR("ERROR: Expected asset bundle path after options.\n"); printf("%s", usage); - return false; + goto fail; } - result_out->bundle_path = strdup(argv[optind]); + result_out->bundle_path = argv[optind]; result_out->runtime_mode = runtime_mode_int; result_out->has_runtime_mode = runtime_mode_int != 0; @@ -2026,6 +1623,17 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin result_out->dummy_display = !!dummy_display_int; return true; + +fail: + if (result_out->bundle_path != NULL) { + free(result_out->bundle_path); + } + + if (result_out->desired_videomode != NULL) { + free(result_out->desired_videomode); + } + + return false; } static int on_drmdev_open(const char *path, int flags, void **fd_metadata_out, void *userdata) { @@ -2096,81 +1704,14 @@ static void on_drmdev_close(int fd, void *fd_metadata, void *userdata) { } } -static const struct drmdev_interface drmdev_interface = { .open = on_drmdev_open, .close = on_drmdev_close }; - -static struct drmdev *find_drmdev(struct libseat *libseat) { - struct drm_connector *connector; - struct drmdev *drmdev; - drmDevicePtr devices[64]; - int ok, n_devices; - -#ifndef HAVE_LIBSEAT - ASSERT_EQUALS(libseat, NULL); -#endif - - ok = drmGetDevices2(0, devices, sizeof(devices) / sizeof(*devices)); - if (ok < 0) { - LOG_ERROR("Could not query DRM device list: %s\n", strerror(-ok)); - return NULL; - } - - n_devices = ok; - - // find a GPU that has a primary node - drmdev = NULL; - for (int i = 0; i < n_devices; i++) { - drmDevicePtr device; - - device = devices[i]; - - if (!(device->available_nodes & (1 << DRM_NODE_PRIMARY))) { - // We need a primary node. - continue; - } - - drmdev = drmdev_new_from_path(device->nodes[DRM_NODE_PRIMARY], &drmdev_interface, libseat); - if (drmdev == NULL) { - LOG_ERROR("Could not create drmdev from device at \"%s\". Continuing.\n", device->nodes[DRM_NODE_PRIMARY]); - continue; - } +static const struct file_interface file_interface = { .open = on_drmdev_open, .close = on_drmdev_close }; - for_each_connector_in_drmdev(drmdev, connector) { - if (connector->variable_state.connection_state == kConnected_DrmConnectionState) { - goto found_connected_connector; - } - } - LOG_ERROR("Device \"%s\" doesn't have a display connected. Skipping.\n", device->nodes[DRM_NODE_PRIMARY]); - drmdev_unref(drmdev); - continue; - -found_connected_connector: - break; - } - - drmFreeDevices(devices, n_devices); - - 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" - ); - goto fail_free_devices; - } - - return drmdev; - -fail_free_devices: - drmFreeDevices(devices, n_devices); - return NULL; -} - -static struct gbm_device *open_rendernode_as_gbm_device() { +static struct gbm_device *open_rendernode_as_gbm_device(void) { struct gbm_device *gbm; drmDevicePtr devices[64]; int ok, n_devices; - ok = drmGetDevices2(0, devices, sizeof(devices) / sizeof(*devices)); + ok = drmGetDevices2(0, devices, ARRAY_SIZE(devices)); if (ok < 0) { LOG_ERROR("Could not query DRM device list: %s\n", strerror(-ok)); return NULL; @@ -2274,14 +1815,12 @@ static void on_session_disable(struct libseat *seat, void *userdata) { fpi->session_active = false; } -static int on_libseat_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { +static enum event_handler_return on_libseat_fd_ready(int fd, uint32_t revents, void *userdata) { struct flutterpi *fpi; int ok; - ASSERT_NOT_NULL(s); ASSERT_NOT_NULL(userdata); fpi = userdata; - (void) s; (void) fd; (void) revents; @@ -2294,34 +1833,125 @@ static int on_libseat_fd_ready(sd_event_source *s, int fd, uint32_t revents, voi } #endif +static void switch_vt(struct flutterpi *flutterpi, int vt) { + ASSERT_NOT_NULL(flutterpi); + (void) vt; + + LOG_DEBUG("switch_vt(%d)\n", vt); + + if (flutterpi->libseat != NULL) { +#ifdef HAVE_LIBSEAT + int ok; + + ok = libseat_switch_session(flutterpi->libseat, vt); + if (ok < 0) { + LOG_ERROR("Could not switch session. libseat_switch_session: %s\n", strerror(errno)); + } +#else + UNREACHABLE(); +#endif + } +} + +static void on_input(void *userdata, size_t n_events, const struct user_input_event *events) { + for (size_t i = 0; i < n_events; i++) { + if (events[i].type != USER_INPUT_KEY) { + break; + } + + const struct user_input_event *event = events + i; + + xkb_keysym_t keysym = event->key.xkb_keysym; + if (keysym && XKB_KEY_XF86Switch_VT_1 <= keysym && keysym <= XKB_KEY_XF86Switch_VT_12) { + switch_vt(userdata, keysym - XKB_KEY_XF86Switch_VT_1 + 1); + } + +#ifdef BUILD_TEXT_INPUT_PLUGIN + if (event->key.text[0] != '\0') { + textin_on_text(event->key.text); + } + + if (keysym) { + textin_on_xkb_keysym(keysym); + } +#endif + +#ifdef BUILD_RAW_KEYBOARD_PLUGIN + uint32_t gtk_modifiers = 0; + gtk_modifiers |= event->key.modifiers.shift ? 1 : 0; + gtk_modifiers |= event->key.modifiers.capslock ? 1 << 1 : 0; + gtk_modifiers |= event->key.modifiers.ctrl ? 1 << 2 : 0; + gtk_modifiers |= event->key.modifiers.alt ? 1 << 3 : 0; + gtk_modifiers |= event->key.modifiers.numlock ? 1 << 4 : 0; + gtk_modifiers |= event->key.modifiers.meta ? 1 << 28 : 0; + + rawkb_send_gtk_keyevent( + event->key.plain_codepoint, + event->key.xkb_keysym, + event->key.xkb_keycode, + gtk_modifiers, + event->key.is_down + ); +#endif + } +} + +static void init_input(struct flutterpi *fpi) { + fpi->user_input = NULL; + fpi->user_input_evsrc = NULL; + + fpi->user_input = user_input_new_suspended(&file_interface, fpi->libseat, fpi->udev, NULL); + if (fpi->user_input == NULL) { + LOG_ERROR("Couldn't initialize user input. flutter-pi will run without user input.\n"); + return; + } + + user_input_add_listener(fpi->user_input, USER_INPUT_KEY, on_input, fpi); + + fpi->user_input_evsrc = evloop_add_io( + fpi->platform_loop, + user_input_get_fd(fpi->user_input), + EPOLLIN | EPOLLRDHUP | EPOLLPRI, + on_user_input_fd_ready, + fpi->user_input + ); + if (fpi->user_input_evsrc == NULL) { + LOG_ERROR("Couldn't listen for user input. flutter-pi will run without user input.\n"); + goto fail_destroy_input; + } + + return; + +fail_destroy_input: + user_input_destroy(fpi->user_input); + fpi->user_input = NULL; +} + +static void fini_input(struct flutterpi *fpi) { + if (fpi->user_input_evsrc != NULL) { + evsrc_destroy(fpi->user_input_evsrc); + fpi->user_input_evsrc = NULL; + } + + if (fpi->user_input != NULL) { + user_input_destroy(fpi->user_input); + fpi->user_input = NULL; + } +} + struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { enum flutter_runtime_mode runtime_mode; enum renderer_type renderer_type; - struct texture_registry *texture_registry; - struct plugin_registry *plugin_registry; - struct frame_scheduler *scheduler; struct flutter_paths *paths; - struct view_geometry geometry; FlutterEngineAOTData aot_data; FlutterEngineResult engine_result; - struct gl_renderer *gl_renderer; - struct vk_renderer *vk_renderer; struct gbm_device *gbm_device; - struct user_input *input; - struct compositor *compositor; struct flutterpi *fpi; - struct sd_event *event_loop; struct flutterpi_cmdline_args cmd_args; - struct libseat *libseat; - struct locales *locales; - struct drmdev *drmdev; - struct tracer *tracer; - struct window *window; - void *engine_handle; - char *bundle_path, **engine_argv, *desired_videomode; - int ok, engine_argc, wakeup_fd; + char **engine_argv, *desired_videomode; + int ok, engine_argc; - fpi = malloc(sizeof *fpi); + fpi = calloc(1, sizeof *fpi); if (fpi == NULL) { return NULL; } @@ -2338,15 +1968,14 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { if (cmd_args.use_vulkan == true) { LOG_ERROR("ERROR: --vulkan was specified, but flutter-pi was built without vulkan support.\n"); printf("%s", usage); - return NULL; + goto fail_free_cmd_args; } #endif runtime_mode = cmd_args.has_runtime_mode ? cmd_args.runtime_mode : FLUTTER_RUNTIME_MODE_DEBUG; - bundle_path = cmd_args.bundle_path; + engine_argc = cmd_args.engine_argc; engine_argv = cmd_args.engine_argv; - #if defined(HAVE_EGL_GLES2) && defined(HAVE_VULKAN) renderer_type = cmd_args.use_vulkan ? kVulkan_RendererType : kOpenGL_RendererType; #elif defined(HAVE_EGL_GLES2) && !defined(HAVE_VULKAN) @@ -2360,84 +1989,84 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { desired_videomode = cmd_args.desired_videomode; - if (bundle_path == NULL) { - LOG_ERROR("ERROR: Bundle path does not exist.\n"); - goto fail_free_cmd_args; - } - - paths = setup_paths(runtime_mode, bundle_path); + paths = setup_paths(runtime_mode, cmd_args.bundle_path); if (paths == NULL) { goto fail_free_cmd_args; } - wakeup_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); - if (wakeup_fd < 0) { - LOG_ERROR("Could not create fd for waking up the main loop. eventfd: %s\n", strerror(errno)); + fpi->platform_loop = evloop_new(); + if (fpi->platform_loop == NULL) { + LOG_ERROR("Couldn't create platform event loop.\n"); goto fail_free_paths; } - ok = sd_event_new(&event_loop); - if (ok < 0) { - LOG_ERROR("Could not create main event loop. sd_event_new: %s\n", strerror(-ok)); - goto fail_close_wakeup_fd; - } - - ok = sd_event_add_io(event_loop, NULL, wakeup_fd, EPOLLIN, on_wakeup_main_loop, NULL); - if (ok < 0) { - LOG_ERROR("Error adding wakeup callback to main loop. sd_event_add_io: %s\n", strerror(-ok)); - goto fail_unref_event_loop; - } - #ifdef HAVE_LIBSEAT - static const struct libseat_seat_listener libseat_interface = { .enable_seat = on_session_enable, .disable_seat = on_session_disable }; - - libseat = libseat_open_seat(&libseat_interface, fpi); - if (libseat == NULL) { - LOG_DEBUG("Couldn't open libseat. Flutter-pi will run without session switching support. libseat_open_seat: %s\n", strerror(errno)); - } + { + static const struct libseat_seat_listener libseat_interface = { .enable_seat = on_session_enable, + .disable_seat = on_session_disable }; - if (libseat != NULL) { - ok = libseat_get_fd(libseat); - if (ok < 0) { - LOG_ERROR( - "Couldn't get an event fd from libseat. Flutter-pi will run without session switching support. libseat_get_fd: %s\n", + fpi->libseat = libseat_open_seat(&libseat_interface, fpi); + if (fpi->libseat == NULL) { + LOG_DEBUG( + "Couldn't open libseat. Flutter-pi will run without session switching support. libseat_open_seat: %s\n", strerror(errno) ); - libseat_close_seat(libseat); - libseat = NULL; } - } - if (libseat != NULL) { - ok = sd_event_add_io(event_loop, NULL, ok, EPOLLIN, on_libseat_fd_ready, fpi); - if (ok < 0) { - LOG_ERROR( - "Couldn't listen for libseat events. Flutter-pi will run without session switching support. sd_event_add_io: %s\n", - strerror(-ok) - ); - libseat_close_seat(libseat); - libseat = NULL; + int fd = -1; + if (fpi->libseat != NULL) { + fd = libseat_get_fd(fpi->libseat); + if (fd < 0) { + LOG_ERROR( + "Couldn't get an event fd from libseat. Flutter-pi will run without session switching support. libseat_get_fd: " + "%s\n", + strerror(errno) + ); + libseat_close_seat(fpi->libseat); + fpi->libseat = NULL; + } } - } - if (libseat != NULL) { - libseat_set_log_level(LIBSEAT_LOG_LEVEL_DEBUG); + if (fpi->libseat != NULL) { + struct evsrc *libseat_evsrc = evloop_add_io(fpi->platform_loop, fd, EPOLLIN, on_libseat_fd_ready, fpi); + if (libseat_evsrc == NULL) { + LOG_ERROR( + "Couldn't listen for libseat events. Flutter-pi will run without session switching support. evloop_add_io: %s\n", + strerror(-ok) + ); + libseat_close_seat(fpi->libseat); + fpi->libseat = NULL; + } + } + + if (fpi->libseat != NULL) { + libseat_set_log_level(LIBSEAT_LOG_LEVEL_DEBUG); + } } -#else - libseat = NULL; #endif - locales = locales_new(); - if (locales == NULL) { - LOG_ERROR("Couldn't setup locales.\n"); + fpi->raster_evloop = evloop_new(); + if (fpi->raster_evloop == NULL) { + LOG_ERROR("Couldn't create raster event loop.\n"); goto fail_destroy_libseat; } - locales_print(locales); + fpi->locales = locales_new(); + if (fpi->locales == NULL) { + LOG_ERROR("Couldn't setup locales.\n"); + goto fail_destroy_raster_evloop; + } - if (cmd_args.dummy_display) { - drmdev = NULL; + locales_print(fpi->locales); + fpi->udev = udev_new(); + if (fpi->udev == NULL) { + LOG_ERROR("Couldn't create udev context.\n"); + goto fail_destroy_locales; + } + + struct drm_resources *drm_resources = NULL; + if (cmd_args.dummy_display) { // for off-screen rendering, we just open the unprivileged /dev/dri/renderD128 (or whatever) // render node as a GBM device. // There's other ways to get an offscreen EGL display, but we need the gbm_device for other things @@ -2447,57 +2076,55 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { goto fail_destroy_locales; } } else { - drmdev = find_drmdev(libseat); - if (drmdev == NULL) { + fpi->drmdev = drmdev_new_from_udev_primary(fpi->udev, "seat0", &file_interface, fpi->libseat); + if (fpi->drmdev == NULL) { goto fail_destroy_locales; } - gbm_device = drmdev_get_gbm_device(drmdev); + drm_resources = drmdev_query_resources(fpi->drmdev); + if (drm_resources == NULL) { + LOG_ERROR("Couldn't query DRM resources.\n"); + goto fail_destroy_drmdev; + } + + gbm_device = drmdev_get_gbm_device(fpi->drmdev); if (gbm_device == NULL) { LOG_ERROR("Couldn't create GBM device.\n"); goto fail_destroy_drmdev; } } - tracer = tracer_new_with_stubs(); - if (tracer == NULL) { + fpi->tracer = tracer_new_with_stubs(); + if (fpi->tracer == NULL) { LOG_ERROR("Couldn't create event tracer.\n"); - goto fail_destroy_drmdev; - } - - scheduler = frame_scheduler_new(false, kDoubleBufferedVsync_PresentMode, NULL, NULL); - if (scheduler == NULL) { - LOG_ERROR("Couldn't create frame scheduler.\n"); - goto fail_unref_tracer; + goto fail_destroy_drm_resources; } if (renderer_type == kVulkan_RendererType) { #ifdef HAVE_VULKAN - gl_renderer = NULL; - vk_renderer = vk_renderer_new(); - if (vk_renderer == NULL) { + fpi->gl_renderer = NULL; + fpi->vk_renderer = vk_renderer_new(); + if (fpi->vk_renderer == NULL) { LOG_ERROR("Couldn't create vulkan renderer.\n"); - ok = EIO; - goto fail_unref_scheduler; + goto fail_unref_tracer; } #else UNREACHABLE(); #endif } else if (renderer_type == kOpenGL_RendererType) { #ifdef HAVE_EGL_GLES2 - vk_renderer = NULL; - gl_renderer = gl_renderer_new_from_gbm_device(tracer, gbm_device, cmd_args.has_pixel_format, cmd_args.pixel_format); - if (gl_renderer == NULL) { + fpi->vk_renderer = NULL; + fpi->gl_renderer = gl_renderer_new_from_gbm_device(fpi->tracer, gbm_device, cmd_args.has_pixel_format, cmd_args.pixel_format); + if (fpi->gl_renderer == NULL) { LOG_ERROR("Couldn't create EGL/OpenGL renderer.\n"); - ok = EIO; - goto fail_unref_scheduler; + goto fail_unref_tracer; } // 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)) { + if (gl_renderer_is_llvmpipe(fpi->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" @@ -2511,139 +2138,121 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { #endif } else { UNREACHABLE(); - goto fail_unref_scheduler; + goto fail_unref_tracer; } - if (cmd_args.dummy_display) { - window = dummy_window_new( - tracer, - scheduler, - renderer_type, - gl_renderer, - vk_renderer, - cmd_args.dummy_display_size, - cmd_args.has_physical_dimensions, - cmd_args.physical_dimensions.x, - cmd_args.physical_dimensions.y, - 60.0 - ); - } else { - window = kms_window_new( - // clang-format off - tracer, - scheduler, - renderer_type, - gl_renderer, - vk_renderer, - 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.physical_dimensions.x, cmd_args.physical_dimensions.y, - cmd_args.has_pixel_format, cmd_args.pixel_format, - drmdev, - desired_videomode - // clang-format on - ); - if (window == NULL) { - LOG_ERROR("Couldn't create KMS window.\n"); - goto fail_unref_renderer; - } - } + init_input(fpi); - compositor = compositor_new(tracer, window); - if (compositor == NULL) { - LOG_ERROR("Couldn't create compositor.\n"); - goto fail_unref_window; - } + { + struct window *window; - /// TODO: Do we really need the window after this? - if (drmdev != NULL) { - ok = sd_event_add_io(event_loop, NULL, drmdev_get_event_fd(drmdev), EPOLLIN | EPOLLHUP | EPOLLPRI, on_drmdev_ready, drmdev); - if (ok < 0) { - LOG_ERROR("Could not add DRM pageflip event listener. sd_event_add_io: %s\n", strerror(-ok)); - goto fail_unref_compositor; + { + struct frame_scheduler *scheduler = frame_scheduler_new(false, kDoubleBufferedVsync_PresentMode, NULL, NULL); + if (scheduler == NULL) { + LOG_ERROR("Couldn't create frame scheduler.\n"); + goto fail_unref_renderer; + } + + if (cmd_args.dummy_display) { + window = dummy_window_new( + fpi->tracer, + scheduler, + renderer_type, + fpi->gl_renderer, + fpi->vk_renderer, + cmd_args.dummy_display_size, + cmd_args.has_physical_dimensions, + cmd_args.physical_dimensions.x, + cmd_args.physical_dimensions.y, + 60.0 + ); + if (window == NULL) { + LOG_ERROR("Couldn't create dummy window.\n"); + frame_scheduler_unref(scheduler); + goto fail_unref_renderer; + } + } else { + window = kms_window_new( + // clang-format off + fpi->tracer, + scheduler, + renderer_type, + fpi->gl_renderer, + fpi->vk_renderer, + 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.physical_dimensions.x, cmd_args.physical_dimensions.y, + cmd_args.has_pixel_format, cmd_args.pixel_format, + fpi->drmdev, + drm_resources, + desired_videomode + // clang-format on + ); + if (window == NULL) { + LOG_ERROR("Couldn't create KMS window.\n"); + frame_scheduler_unref(scheduler); + goto fail_unref_renderer; + } + } + + frame_scheduler_unref(scheduler); } - } - compositor_get_view_geometry(compositor, &geometry); + fpi->compositor = compositor_new_multiview(fpi->tracer, fpi->raster_evloop, NULL, fpi->drmdev, drm_resources, fpi->user_input); - static const struct user_input_interface user_input_interface = { - .on_flutter_pointer_event = on_flutter_pointer_event, - .on_utf8_character = on_utf8_character, - .on_xkb_keysym = on_xkb_keysym, - .on_gtk_keyevent = on_gtk_keyevent, - .on_set_cursor_enabled = on_set_cursor_enabled, - .on_move_cursor = on_move_cursor, - .open = on_user_input_open, - .close = on_user_input_close, - .on_switch_vt = on_switch_vt, - .on_key_event = NULL, - }; + compositor_add_view(fpi->compositor, window); - fpi->libseat = libseat; - list_inithead(&fpi->fd_for_device_id); + window_unref(window); - input = user_input_new( - &user_input_interface, - fpi, - &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 { - sd_event_source *user_input_event_source; - - ok = sd_event_add_io( - event_loop, - &user_input_event_source, - user_input_get_fd(input), - EPOLLIN | EPOLLRDHUP | EPOLLPRI, - on_user_input_fd_ready, - input - ); - if (ok < 0) { - LOG_ERROR("Couldn't listen for user input. flutter-pi will run without user input. sd_event_add_io: %s\n", strerror(-ok)); - user_input_destroy(input); - input = NULL; + if (fpi->compositor == NULL) { + LOG_ERROR("Couldn't create compositor.\n"); + goto fail_unref_tracer; } + } - sd_event_source_set_priority(user_input_event_source, SD_EVENT_PRIORITY_IDLE - 10); - - sd_event_source_set_floating(user_input_event_source, true); - sd_event_source_unref(user_input_event_source); + /// TODO: Do we really need the window after this? + if (fpi->drmdev != NULL) { + fpi->drmdev_evrsc = evloop_add_io( + fpi->platform_loop, + drmdev_get_modesetting_fd(fpi->drmdev), + EPOLLIN | EPOLLHUP | EPOLLPRI, + on_drmdev_modesetting_ready, + fpi->drmdev + ); + if (fpi->drmdev_evrsc == NULL) { + goto fail_unref_compositor; + } } - engine_handle = load_flutter_engine_lib(paths); - if (engine_handle == NULL) { + fpi->flutter.engine_handle = load_flutter_engine_lib(paths); + if (fpi->flutter.engine_handle == NULL) { goto fail_destroy_user_input; } - ok = get_flutter_engine_procs(engine_handle, &fpi->flutter.procs); + ok = get_flutter_engine_procs(fpi->flutter.engine_handle, &fpi->flutter.procs); if (ok != 0) { goto fail_unload_engine; } tracer_set_cbs( - tracer, + fpi->tracer, fpi->flutter.procs.TraceEventDurationBegin, fpi->flutter.procs.TraceEventDurationEnd, fpi->flutter.procs.TraceEventInstant ); - plugin_registry = plugin_registry_new(fpi); - if (plugin_registry == NULL) { + fpi->plugin_registry = plugin_registry_new(fpi); + if (fpi->plugin_registry == NULL) { LOG_ERROR("Could not create plugin registry.\n"); goto fail_unload_engine; } - ok = plugin_registry_add_plugins_from_static_registry(plugin_registry); + ok = plugin_registry_add_plugins_from_static_registry(fpi->plugin_registry); if (ok != 0) { LOG_ERROR("Could not register plugins to plugin registry.\n"); goto fail_destroy_plugin_registry; @@ -2655,8 +2264,8 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { .mark_frame_available = on_mark_texture_frame_available, }; - texture_registry = texture_registry_new(&texture_registry_interface, fpi); - if (texture_registry == NULL) { + fpi->texture_registry = texture_registry_new(&texture_registry_interface, fpi); + if (fpi->texture_registry == NULL) { LOG_ERROR("Could not create texture registry.\n"); goto fail_destroy_plugin_registry; } @@ -2691,99 +2300,81 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { } } - // We don't need these anymore. - frame_scheduler_unref(scheduler); - window_unref(window); - - pthread_mutex_init(&fpi->event_loop_mutex, get_default_mutex_attrs()); - fpi->event_loop_thread = pthread_self(); - fpi->wakeup_event_loop_fd = wakeup_fd; - fpi->event_loop = event_loop; - fpi->locales = locales; - fpi->tracer = tracer; - fpi->compositor = compositor; - fpi->gl_renderer = gl_renderer; - fpi->vk_renderer = vk_renderer; - fpi->user_input = input; + fpi->platform_thread = pthread_self(); fpi->flutter.runtime_mode = runtime_mode; - fpi->flutter.bundle_path = realpath(bundle_path, NULL); fpi->flutter.engine_argc = engine_argc; fpi->flutter.engine_argv = engine_argv; fpi->flutter.paths = paths; - fpi->flutter.engine_handle = engine_handle; fpi->flutter.aot_data = aot_data; - fpi->drmdev = drmdev; - fpi->plugin_registry = plugin_registry; - fpi->texture_registry = texture_registry; - fpi->libseat = libseat; return fpi; fail_destroy_texture_registry: - texture_registry_destroy(texture_registry); + texture_registry_destroy(fpi->texture_registry); fail_destroy_plugin_registry: - plugin_registry_destroy(plugin_registry); + plugin_registry_destroy(fpi->plugin_registry); fail_unload_engine: - unload_flutter_engine_lib(engine_handle); + unload_flutter_engine_lib(fpi->flutter.engine_handle); fail_destroy_user_input: - user_input_destroy(input); + fini_input(fpi); -fail_unref_compositor: - compositor_unref(compositor); + if (fpi->drmdev_evrsc) { + evsrc_destroy(fpi->drmdev_evrsc); + } -fail_unref_window: - window_unref(window); +fail_unref_compositor: + compositor_unref(fpi->compositor); fail_unref_renderer: - if (gl_renderer) { + if (fpi->gl_renderer) { #ifdef HAVE_EGL_GLES2 - gl_renderer_unref(gl_renderer); + gl_renderer_unref(fpi->gl_renderer); #else UNREACHABLE(); #endif } - if (vk_renderer) { + if (fpi->vk_renderer) { #ifdef HAVE_VULKAN - vk_renderer_unref(vk_renderer); + vk_renderer_unref(fpi->vk_renderer); #else UNREACHABLE(); #endif } -fail_unref_scheduler: - frame_scheduler_unref(scheduler); - fail_unref_tracer: - tracer_unref(tracer); + tracer_unref(fpi->tracer); + +fail_destroy_drm_resources: + drm_resources_unref(drm_resources); fail_destroy_drmdev: - drmdev_unref(drmdev); + drmdev_unref(fpi->drmdev); fail_destroy_locales: - locales_destroy(locales); + locales_destroy(fpi->locales); + +fail_destroy_raster_evloop: + evloop_unref(fpi->raster_evloop); fail_destroy_libseat: - if (libseat != NULL) { + if (fpi->libseat != NULL) { #ifdef HAVE_LIBSEAT - libseat_close_seat(libseat); + libseat_close_seat(fpi->libseat); #else UNREACHABLE(); #endif } -fail_unref_event_loop: - sd_event_unrefp(&event_loop); - -fail_close_wakeup_fd: - close(wakeup_fd); + evloop_unref(fpi->platform_loop); fail_free_paths: flutter_paths_free(paths); fail_free_cmd_args: free(cmd_args.bundle_path); + free(cmd_args.desired_videomode); fail_free_fpi: free(fpi); @@ -2791,45 +2382,49 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { return NULL; } -void flutterpi_destroy(struct flutterpi *flutterpi) { - (void) flutterpi; - LOG_DEBUG("deinit\n"); - - pthread_mutex_destroy(&flutterpi->event_loop_mutex); - texture_registry_destroy(flutterpi->texture_registry); - plugin_registry_destroy(flutterpi->plugin_registry); - unload_flutter_engine_lib(flutterpi->flutter.engine_handle); - user_input_destroy(flutterpi->user_input); - compositor_unref(flutterpi->compositor); - if (flutterpi->gl_renderer) { +void flutterpi_destroy(struct flutterpi *fpi) { + texture_registry_destroy(fpi->texture_registry); + plugin_registry_destroy(fpi->plugin_registry); + unload_flutter_engine_lib(fpi->flutter.engine_handle); + if (fpi->user_input_evsrc) { + evsrc_destroy(fpi->user_input_evsrc); + } + if (fpi->user_input) { + user_input_destroy(fpi->user_input); + } + if (fpi->drmdev_evrsc) { + evsrc_destroy(fpi->drmdev_evrsc); + } + compositor_unref(fpi->compositor); + if (fpi->gl_renderer) { #ifdef HAVE_EGL_GLES2 - gl_renderer_unref(flutterpi->gl_renderer); + gl_renderer_unref(fpi->gl_renderer); #else UNREACHABLE(); #endif } - if (flutterpi->vk_renderer) { + if (fpi->vk_renderer) { #ifdef HAVE_VULKAN - vk_renderer_unref(flutterpi->vk_renderer); + vk_renderer_unref(fpi->vk_renderer); #else UNREACHABLE(); #endif } - tracer_unref(flutterpi->tracer); - drmdev_unref(flutterpi->drmdev); - locales_destroy(flutterpi->locales); - if (flutterpi->libseat != NULL) { + tracer_unref(fpi->tracer); + drmdev_unref(fpi->drmdev); + locales_destroy(fpi->locales); + evloop_unref(fpi->raster_evloop); + if (fpi->libseat != NULL) { #ifdef HAVE_LIBSEAT - libseat_close_seat(flutterpi->libseat); + libseat_close_seat(fpi->libseat); #else UNREACHABLE(); #endif } - sd_event_unrefp(&flutterpi->event_loop); - close(flutterpi->wakeup_event_loop_fd); - flutter_paths_free(flutterpi->flutter.paths); - free(flutterpi->flutter.bundle_path); - free(flutterpi); + evloop_unref(fpi->platform_loop); + flutter_paths_free(fpi->flutter.paths); + free(fpi->flutter.bundle_path); + free(fpi); return; } diff --git a/src/flutter-pi.h b/src/flutter-pi.h index d2e831d9..881cfa7e 100644 --- a/src/flutter-pi.h +++ b/src/flutter-pi.h @@ -96,11 +96,6 @@ struct flutterpi; /// TODO: Remove this extern struct flutterpi *flutterpi; -struct platform_task { - int (*callback)(void *userdata); - void *userdata; -}; - struct platform_message { bool is_response; union { @@ -145,11 +140,13 @@ struct flutterpi_cmdline_args { int flutterpi_fill_view_properties(bool has_orientation, enum device_orientation orientation, bool has_rotation, int rotation); -int flutterpi_post_platform_task(int (*callback)(void *userdata), void *userdata); +int flutterpi_post_platform_task(struct flutterpi *flutterpi, void_callback_t callback, void *userdata); + +int flutterpi_post_delayed_platform_task(struct flutterpi *flutterpi, void_callback_t callback, void *userdata, uint64_t target_time_usec); -int flutterpi_post_platform_task_with_time(int (*callback)(void *userdata), void *userdata, uint64_t target_time_usec); +struct evloop *flutterpi_get_platform_event_loop(struct flutterpi *flutterpi); -int flutterpi_sd_event_add_io(sd_event_source **source_out, int fd, uint32_t events, sd_event_io_handler_t callback, void *userdata); +struct evloop *flutterpi_get_raster_event_loop(struct flutterpi *flutterpi); int flutterpi_send_platform_message( struct flutterpi *flutterpi, @@ -184,6 +181,10 @@ void flutterpi_schedule_exit(struct flutterpi *flutterpi); struct gbm_device *flutterpi_get_gbm_device(struct flutterpi *flutterpi); +struct compositor *flutterpi_peek_compositor(struct flutterpi *flutterpi); + +struct compositor *flutterpi_get_compositor(struct flutterpi *flutterpi); + bool flutterpi_has_gl_renderer(struct flutterpi *flutterpi); struct gl_renderer *flutterpi_get_gl_renderer(struct flutterpi *flutterpi); diff --git a/src/flutter_embedder.h b/src/flutter_embedder.h new file mode 100644 index 00000000..b5fbe6a7 --- /dev/null +++ b/src/flutter_embedder.h @@ -0,0 +1,11 @@ +#ifndef _FLUTTERPI_SRC_FLUTTER_EMBEDDER_H +#define _FLUTTERPI_SRC_FLUTTER_EMBEDDER_H + +#include "util/macros.h" + +PRAGMA_DIAGNOSTIC_PUSH +PRAGMA_DIAGNOSTIC_IGNORED("-Wstrict-prototypes") +#include "flutter_embedder_header/flutter_embedder.h" +PRAGMA_DIAGNOSTIC_POP + +#endif // _FLUTTERPI_SRC_FLUTTER_EMBEDDER_H diff --git a/src/frame_scheduler.c b/src/frame_scheduler.c index 0adf7b9a..2929ec79 100644 --- a/src/frame_scheduler.c +++ b/src/frame_scheduler.c @@ -18,13 +18,21 @@ struct frame_scheduler { refcount_t n_refs; + mutex_t mutex; bool uses_frame_requests; enum present_mode present_mode; fl_vsync_callback_t vsync_cb; void *userdata; - pthread_mutex_t mutex; + bool waiting_for_scanout; + + bool has_scheduled_frame; + struct { + void_callback_t present_cb; + void_callback_t cancel_cb; + void *userdata; + } scheduled_frame; }; DEFINE_REF_OPS(frame_scheduler, n_refs) @@ -43,10 +51,15 @@ frame_scheduler_new(bool uses_frame_requests, enum present_mode present_mode, fl } scheduler->n_refs = REFCOUNT_INIT_1; + mutex_init(&scheduler->mutex); + scheduler->uses_frame_requests = uses_frame_requests; scheduler->present_mode = present_mode; scheduler->vsync_cb = vsync_cb; scheduler->userdata = userdata; + + scheduler->waiting_for_scanout = false; + scheduler->has_scheduled_frame = false; return scheduler; } @@ -118,14 +131,44 @@ void frame_scheduler_request_fb(struct frame_scheduler *scheduler, uint64_t scan UNIMPLEMENTED(); } -void frame_scheduler_present_frame(struct frame_scheduler *scheduler, void_callback_t present_cb, void *userdata, void_callback_t cancel_cb) { +void frame_scheduler_present_frame(struct frame_scheduler *scheduler, void_callback_t present_cb, void *userdata, void_callback_t cancel_cb) { ASSERT_NOT_NULL(scheduler); ASSERT_NOT_NULL(present_cb); - (void) scheduler; - (void) cancel_cb; + + frame_scheduler_lock(scheduler); - /// TODO: Implement - present_cb(userdata); + if (scheduler->waiting_for_scanout) { + void_callback_t cancel_prev_sched_frame = NULL; + void *prev_sched_frame_userdata = NULL; + + // We're already waiting for a scanout, so we can't present a frame right now. + // Wait till the previous frame is scanned out, and then present. + + if (scheduler->has_scheduled_frame) { + // If we already have a frame scheduled, cancel it. + cancel_prev_sched_frame = scheduler->scheduled_frame.cancel_cb; + prev_sched_frame_userdata = scheduler->scheduled_frame.userdata; + + memset(&scheduler->scheduled_frame, 0, sizeof scheduler->scheduled_frame); + } + + scheduler->has_scheduled_frame = true; + scheduler->scheduled_frame.present_cb = present_cb; + scheduler->scheduled_frame.cancel_cb = cancel_cb; + scheduler->scheduled_frame.userdata = userdata; + + frame_scheduler_unlock(scheduler); + + if (cancel_prev_sched_frame != NULL) { + cancel_prev_sched_frame(prev_sched_frame_userdata); + } + } else { + // We're not waiting for a scanout right now. + scheduler->waiting_for_scanout = true; + frame_scheduler_unlock(scheduler); + + present_cb(userdata); + } } void frame_scheduler_on_scanout(struct frame_scheduler *scheduler, bool has_timestamp, uint64_t timestamp_ns) { @@ -135,6 +178,27 @@ void frame_scheduler_on_scanout(struct frame_scheduler *scheduler, bool has_time (void) has_timestamp; (void) timestamp_ns; - /// TODO: Implement - UNIMPLEMENTED(); + void_callback_t present_cb = NULL; + void *userdata = NULL; + + frame_scheduler_lock(scheduler); + + if (scheduler->waiting_for_scanout) { + scheduler->waiting_for_scanout = false; + + if (scheduler->has_scheduled_frame) { + present_cb = scheduler->scheduled_frame.present_cb; + userdata = scheduler->scheduled_frame.userdata; + + memset(&scheduler->scheduled_frame, 0, sizeof scheduler->scheduled_frame); + + scheduler->has_scheduled_frame = false; + } + } + + frame_scheduler_unlock(scheduler); + + if (present_cb != NULL) { + present_cb(userdata); + } } diff --git a/src/frame_scheduler.h b/src/frame_scheduler.h index 229d9e59..4f66d8d6 100644 --- a/src/frame_scheduler.h +++ b/src/frame_scheduler.h @@ -80,4 +80,6 @@ void frame_scheduler_on_fb_released(struct frame_scheduler *scheduler, bool has_ */ void frame_scheduler_present_frame(struct frame_scheduler *scheduler, void_callback_t present_cb, void *userdata, void_callback_t cancel_cb); +void frame_scheduler_on_scanout(struct frame_scheduler *scheduler, bool has_timestamp, uint64_t timestamp_ns); + #endif // _FLUTTERPI_SRC_FRAME_SCHEDULER_H diff --git a/src/gl_renderer.c b/src/gl_renderer.c index 58b25ea2..a151310e 100644 --- a/src/gl_renderer.c +++ b/src/gl_renderer.c @@ -62,24 +62,23 @@ struct gl_renderer { #endif }; -static void *try_get_proc_address(const char *name) { - void *address; - - address = eglGetProcAddress(name); - if (address) { - return address; +static fn_ptr_t try_get_proc_address(const char *name) { + fn_ptr_t fn = eglGetProcAddress(name); + if (fn) { + return fn; } - address = dlsym(RTLD_DEFAULT, name); - if (address) { - return address; + void *void_fn = dlsym(RTLD_DEFAULT, name); + if (void_fn) { + *((void **) &fn) = void_fn; + return fn; } - return NULL; + return (fn_ptr_t) NULL; } -static void *get_proc_address(const char *name) { - void *address; +static fn_ptr_t get_proc_address(const char *name) { + fn_ptr_t address; address = try_get_proc_address(name); if (address == NULL) { @@ -177,13 +176,13 @@ struct gl_renderer *gl_renderer_new_from_gbm_device( // PFNEGLGETPLATFORMDISPLAYEXTPROC, PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC // are defined by EGL_EXT_platform_base. #ifdef EGL_EXT_platform_base - PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_ext; - PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC egl_create_platform_window_surface_ext; + PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_ext = NULL; + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC egl_create_platform_window_surface_ext = NULL; #endif if (supports_egl_ext_platform_base) { #ifdef EGL_EXT_platform_base - egl_get_platform_display_ext = try_get_proc_address("eglGetPlatformDisplayEXT"); + egl_get_platform_display_ext = (PFNEGLGETPLATFORMDISPLAYEXTPROC) try_get_proc_address("eglGetPlatformDisplayEXT"); if (egl_get_platform_display_ext == NULL) { LOG_ERROR("Couldn't resolve \"eglGetPlatformDisplayEXT\" even though \"EGL_EXT_platform_base\" was listed as supported.\n"); supports_egl_ext_platform_base = false; @@ -195,7 +194,8 @@ struct gl_renderer *gl_renderer_new_from_gbm_device( if (supports_egl_ext_platform_base) { #ifdef EGL_EXT_platform_base - egl_create_platform_window_surface_ext = try_get_proc_address("eglCreatePlatformWindowSurfaceEXT"); + egl_create_platform_window_surface_ext = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC + ) try_get_proc_address("eglCreatePlatformWindowSurfaceEXT"); if (egl_create_platform_window_surface_ext == NULL) { LOG_ERROR( "Couldn't resolve \"eglCreatePlatformWindowSurfaceEXT\" even though \"EGL_EXT_platform_base\" was listed as supported.\n" @@ -217,8 +217,9 @@ struct gl_renderer *gl_renderer_new_from_gbm_device( bool failed_before = false; #ifdef EGL_VERSION_1_5 - PFNEGLGETPLATFORMDISPLAYPROC egl_get_platform_display = try_get_proc_address("eglGetPlatformDisplay"); - PFNEGLCREATEPLATFORMWINDOWSURFACEPROC egl_create_platform_window_surface = try_get_proc_address("eglCreatePlatformWindowSurface"); + PFNEGLGETPLATFORMDISPLAYPROC egl_get_platform_display = (PFNEGLGETPLATFORMDISPLAYPROC) try_get_proc_address("eglGetPlatformDisplay"); + PFNEGLCREATEPLATFORMWINDOWSURFACEPROC egl_create_platform_window_surface = (PFNEGLCREATEPLATFORMWINDOWSURFACEPROC + ) try_get_proc_address("eglCreatePlatformWindowSurface"); if (egl_display == EGL_NO_DISPLAY && egl_get_platform_display != NULL) { egl_display = egl_get_platform_display(EGL_PLATFORM_GBM_KHR, gbm_device, NULL); @@ -550,7 +551,7 @@ int gl_renderer_clear_current(struct gl_renderer *renderer) { return 0; } -void *gl_renderer_get_proc_address(ASSERTED struct gl_renderer *renderer, const char *name) { +fn_ptr_t gl_renderer_get_proc_address(ASSERTED struct gl_renderer *renderer, const char *name) { ASSERT_NOT_NULL(renderer); ASSERT_NOT_NULL(name); return get_proc_address(name); @@ -628,7 +629,7 @@ int gl_renderer_make_this_a_render_thread(struct gl_renderer *renderer) { return 0; } -void gl_renderer_cleanup_this_render_thread() { +void gl_renderer_cleanup_this_render_thread(void) { EGLDisplay display; EGLContext context; EGLBoolean egl_ok; diff --git a/src/gl_renderer.h b/src/gl_renderer.h index d6c8160a..6afcd577 100644 --- a/src/gl_renderer.h +++ b/src/gl_renderer.h @@ -26,6 +26,8 @@ #include "egl.h" +typedef void (*fn_ptr_t)(void); + struct tracer; struct gl_renderer *gl_renderer_new_from_gbm_device( @@ -59,7 +61,7 @@ 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); +fn_ptr_t gl_renderer_get_proc_address(struct gl_renderer *renderer, const char *name); EGLDisplay gl_renderer_get_egl_display(struct gl_renderer *renderer); @@ -71,7 +73,7 @@ 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(); +void gl_renderer_cleanup_this_render_thread(void); ATTR_PURE EGLConfig gl_renderer_choose_config(struct gl_renderer *renderer, bool has_desired_pixel_format, enum pixfmt desired_pixel_format); diff --git a/src/kms/drmdev.c b/src/kms/drmdev.c new file mode 100644 index 00000000..191b7941 --- /dev/null +++ b/src/kms/drmdev.c @@ -0,0 +1,939 @@ +#include "drmdev.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "pixel_format.h" +#include "resources.h" +#include "util/bitset.h" +#include "util/khash.h" +#include "util/list.h" +#include "util/lock_ops.h" +#include "util/logging.h" +#include "util/macros.h" +#include "util/refcounting.h" + +struct pageflip_callbacks { + uint32_t crtc_id; + + int index; + struct { + drmdev_scanout_cb_t scanout_callback; + void *scanout_callback_userdata; + + void_callback_t void_callback; + void *void_callback_userdata; + } callbacks[2]; +}; + +KHASH_MAP_INIT_INT(pageflip_callbacks, struct pageflip_callbacks) + +struct drm_fb { + struct list_head entry; + + uint32_t id; + + uint32_t width, height; + + enum pixfmt format; + + bool has_modifier; + uint64_t modifier; + + uint32_t flags; + + uint32_t handles[4]; + uint32_t pitches[4]; + uint32_t offsets[4]; +}; + +struct drmdev { + int fd; + void *fd_metadata; + + refcount_t n_refs; + pthread_mutex_t mutex; + + bool supports_atomic_modesetting; + bool supports_dumb_buffers; + + struct { + drmdev_scanout_cb_t scanout_callback; + void *userdata; + void_callback_t destroy_callback; + + struct kms_req *last_flipped; + } per_crtc_state[32]; + + struct gbm_device *gbm_device; + + struct file_interface interface; + void *interface_userdata; + + struct list_head fbs; + khash_t(pageflip_callbacks) * pageflip_callbacks; + + struct udev *udev; + struct udev_device *kms_udev; + const char *sysnum; +}; + +/** + * @brief Check if the given file descriptor is a DRM master. + */ +static bool is_drm_master(int fd) { + return drmAuthMagic(fd, 0) != -EACCES; +} + +/** + * @brief Check if the given path is a path to a KMS device. + */ +static bool is_kms_device(const char *path, const struct file_interface *interface, void *userdata) { + void *fd_metadata; + + int fd = interface->open(path, O_RDWR, &fd_metadata, userdata); + if (fd < 0) { + return false; + } + + // Ideally we'd use drmIsKMS() here, but it's not available everywhere. + + struct drm_mode_card_res res = { 0 }; + if (drmIoctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res) != 0) { + interface->close(fd, fd_metadata, userdata); + return false; + } + + interface->close(fd, fd_metadata, userdata); + return res.count_crtcs > 0 && res.count_connectors > 0 && res.count_encoders > 0; +} + +static void assert_rotations_work(void) { + assert(PLANE_TRANSFORM_ROTATE_0.rotate_0 == true); + assert(PLANE_TRANSFORM_ROTATE_0.rotate_90 == false); + assert(PLANE_TRANSFORM_ROTATE_0.rotate_180 == false); + assert(PLANE_TRANSFORM_ROTATE_0.rotate_270 == false); + assert(PLANE_TRANSFORM_ROTATE_0.reflect_x == false); + assert(PLANE_TRANSFORM_ROTATE_0.reflect_y == false); + + assert(PLANE_TRANSFORM_ROTATE_90.rotate_0 == false); + assert(PLANE_TRANSFORM_ROTATE_90.rotate_90 == true); + assert(PLANE_TRANSFORM_ROTATE_90.rotate_180 == false); + assert(PLANE_TRANSFORM_ROTATE_90.rotate_270 == false); + assert(PLANE_TRANSFORM_ROTATE_90.reflect_x == false); + assert(PLANE_TRANSFORM_ROTATE_90.reflect_y == false); + + assert(PLANE_TRANSFORM_ROTATE_180.rotate_0 == false); + assert(PLANE_TRANSFORM_ROTATE_180.rotate_90 == false); + assert(PLANE_TRANSFORM_ROTATE_180.rotate_180 == true); + assert(PLANE_TRANSFORM_ROTATE_180.rotate_270 == false); + assert(PLANE_TRANSFORM_ROTATE_180.reflect_x == false); + assert(PLANE_TRANSFORM_ROTATE_180.reflect_y == false); + + assert(PLANE_TRANSFORM_ROTATE_270.rotate_0 == false); + assert(PLANE_TRANSFORM_ROTATE_270.rotate_90 == false); + assert(PLANE_TRANSFORM_ROTATE_270.rotate_180 == false); + assert(PLANE_TRANSFORM_ROTATE_270.rotate_270 == true); + assert(PLANE_TRANSFORM_ROTATE_270.reflect_x == false); + assert(PLANE_TRANSFORM_ROTATE_270.reflect_y == false); + + assert(PLANE_TRANSFORM_REFLECT_X.rotate_0 == false); + assert(PLANE_TRANSFORM_REFLECT_X.rotate_90 == false); + assert(PLANE_TRANSFORM_REFLECT_X.rotate_180 == false); + assert(PLANE_TRANSFORM_REFLECT_X.rotate_270 == false); + assert(PLANE_TRANSFORM_REFLECT_X.reflect_x == true); + assert(PLANE_TRANSFORM_REFLECT_X.reflect_y == false); + + assert(PLANE_TRANSFORM_REFLECT_Y.rotate_0 == false); + assert(PLANE_TRANSFORM_REFLECT_Y.rotate_90 == false); + assert(PLANE_TRANSFORM_REFLECT_Y.rotate_180 == false); + assert(PLANE_TRANSFORM_REFLECT_Y.rotate_270 == false); + assert(PLANE_TRANSFORM_REFLECT_Y.reflect_x == false); + assert(PLANE_TRANSFORM_REFLECT_Y.reflect_y == true); + + drm_plane_transform_t r = PLANE_TRANSFORM_NONE; + + r.rotate_0 = true; + r.reflect_x = true; + assert(r.u32 == (DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_X)); + + r.u32 = DRM_MODE_ROTATE_90 | DRM_MODE_REFLECT_Y; + assert(r.rotate_0 == false); + assert(r.rotate_90 == true); + assert(r.rotate_180 == false); + assert(r.rotate_270 == false); + assert(r.reflect_x == false); + assert(r.reflect_y == true); + (void) r; +} + +static int set_drm_client_caps(int fd, bool *supports_atomic_modesetting) { + int ok; + + ok = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not set DRM client universal planes capable. drmSetClientCap: %s\n", strerror(ok)); + return ok; + } + +#ifdef USE_LEGACY_KMS + *supports_atomic_modesetting = false; +#else + ok = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1); + if ((ok < 0) && (errno == EOPNOTSUPP)) { + if (supports_atomic_modesetting != NULL) { + *supports_atomic_modesetting = false; + } + } else if (ok < 0) { + ok = errno; + LOG_ERROR("Could not set DRM client atomic capable. drmSetClientCap: %s\n", strerror(ok)); + return ok; + } else { + if (supports_atomic_modesetting != NULL) { + *supports_atomic_modesetting = true; + } + } +#endif + + return 0; +} + +static struct udev_device * +find_udev_kms_device(struct udev *udev, const char *seat, const struct file_interface *interface, void *interface_userdata) { + struct udev_enumerate *enumerator; + + enumerator = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(enumerator, "drm"); + udev_enumerate_add_match_sysname(enumerator, "card[0-9]*"); + + udev_enumerate_scan_devices(enumerator); + + struct udev_list_entry *entry; + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerator)) { + const char *syspath = udev_list_entry_get_name(entry); + + struct udev_device *udev_device = udev_device_new_from_syspath(udev, syspath); + if (udev_device == NULL) { + LOG_ERROR("Could not create udev device from syspath. udev_device_new_from_syspath: %s\n", strerror(errno)); + continue; + } + + // Find out if the drm card is connected to our seat. + // This could also be part of the enumerator filter, e.g.: + // + // udev_enumerate_add_match_property(enumerator, "ID_SEAT", seat), + // + // if we didn't have to handle a NULL value for ID_SEAT. + const char *device_seat = udev_device_get_property_value(udev_device, "ID_SEAT"); + if (device_seat == NULL) { + device_seat = "seat0"; + } + + if (!streq(device_seat, seat)) { + udev_device_unref(udev_device); + continue; + } + + // devnode is the path to the /dev/dri/cardX device. + const char *devnode = udev_device_get_devnode(udev_device); + if (devnode == NULL) { + // likely a connector, not a card. + udev_device_unref(udev_device); + continue; + } + + if (access(devnode, R_OK | W_OK) != 0) { + LOG_ERROR("Insufficient permissions to open KMS device \"%s\" for display output. access: %s\n", devnode, strerror(errno)); + udev_device_unref(udev_device); + continue; + } + + if (!is_kms_device(devnode, interface, interface_userdata)) { + udev_device_unref(udev_device); + continue; + } + + udev_enumerate_unref(enumerator); + return udev_device; + } + + udev_enumerate_unref(enumerator); + return NULL; +} + +static void drmdev_on_page_flip(struct drmdev *drmdev, uint32_t crtc_id, uint64_t vblank_ns) { + ASSERT_NOT_NULL(drmdev); + struct pageflip_callbacks cbs_copy; + + { + mutex_lock(&drmdev->mutex); + + khint_t cbs_bucket = kh_get(pageflip_callbacks, drmdev->pageflip_callbacks, crtc_id); + if (cbs_bucket == kh_end(drmdev->pageflip_callbacks)) { + // No callbacks for this CRTC. + mutex_unlock(&drmdev->mutex); + return; + } + + struct pageflip_callbacks *cbs = &kh_value(drmdev->pageflip_callbacks, cbs_bucket); + memcpy(&cbs_copy, cbs, sizeof *cbs); + + cbs->callbacks[cbs->index].scanout_callback = NULL; + cbs->callbacks[cbs->index].void_callback = NULL; + cbs->callbacks[cbs->index].scanout_callback_userdata = NULL; + cbs->callbacks[cbs->index].void_callback_userdata = NULL; + cbs->index = cbs->index ^ 1; + + mutex_unlock(&drmdev->mutex); + } + + if (cbs_copy.callbacks[cbs_copy.index].void_callback != NULL) { + cbs_copy.callbacks[cbs_copy.index].void_callback(cbs_copy.callbacks[cbs_copy.index].void_callback_userdata); + } + + if (cbs_copy.callbacks[cbs_copy.index].scanout_callback != NULL) { + cbs_copy.callbacks[cbs_copy.index].scanout_callback(vblank_ns, cbs_copy.callbacks[cbs_copy.index].scanout_callback_userdata); + } +} + +static void on_page_flip(int fd, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, unsigned int crtc_id, void *userdata) { + struct drmdev *drmdev; + + ASSERT_NOT_NULL(userdata); + drmdev = userdata; + + (void) fd; + (void) sequence; + + uint64_t vblank_ns = tv_sec * 1000000000ull + tv_usec * 1000ull; + drmdev_on_page_flip(drmdev, crtc_id, vblank_ns); + + drmdev_unref(drmdev); +} + +/** + * @brief Should be called when the drmdev modesetting fd is ready. + */ +void drmdev_dispatch_modesetting(struct drmdev *drmdev) { + int ok; + + static drmEventContext ctx = { + .version = DRM_EVENT_CONTEXT_VERSION, + .vblank_handler = NULL, + .page_flip_handler = NULL, + .page_flip_handler2 = on_page_flip, + .sequence_handler = NULL, + }; + + ok = drmHandleEvent(drmdev->fd, &ctx); + if (ok != 0) { + LOG_ERROR("Could not handle DRM event. drmHandleEvent: %s\n", strerror(errno)); + } +} + +/** + * @brief Create a new drmdev from the primary drm device for the given udev & seat. + */ +struct drmdev * +drmdev_new_from_udev_primary(struct udev *udev, const char *seat, const struct file_interface *interface, void *interface_userdata) { + struct drmdev *d; + uint64_t cap; + int ok; + + assert_rotations_work(); + + d = malloc(sizeof *d); + if (d == NULL) { + return NULL; + } + + d->n_refs = REFCOUNT_INIT_1; + mutex_init(&d->mutex); + d->interface = *interface; + d->interface_userdata = interface_userdata; + + // find a KMS device for the given seat. + d->kms_udev = find_udev_kms_device(udev, seat, interface, interface_userdata); + if (d->kms_udev == NULL) { + LOG_ERROR("Could not find a KMS device for seat %s.\n", seat); + goto fail_free_dev; + } + + d->sysnum = udev_device_get_sysnum(d->kms_udev); + + d->fd = interface->open(udev_device_get_devnode(d->kms_udev), O_RDWR, &d->fd_metadata, interface_userdata); + if (d->fd < 0) { + LOG_ERROR("Could not open KMS device. interface->open: %s\n", strerror(errno)); + goto fail_unref_kms_udev; + } + + set_drm_client_caps(d->fd, &d->supports_atomic_modesetting); + + cap = 0; + ok = drmGetCap(d->fd, DRM_CAP_DUMB_BUFFER, &cap); + if (ok < 0) { + d->supports_dumb_buffers = false; + } else { + d->supports_dumb_buffers = !!cap; + } + + d->gbm_device = gbm_create_device(d->fd); + if (d->gbm_device == NULL) { + LOG_ERROR("Could not create GBM device.\n"); + goto fail_close_fd; + } + + list_inithead(&d->fbs); + d->pageflip_callbacks = kh_init(pageflip_callbacks); + + return d; + +fail_close_fd: + interface->close(d->fd, d->fd_metadata, interface_userdata); + +fail_unref_kms_udev: + udev_device_unref(d->kms_udev); + +fail_free_dev: + free(d); + return NULL; +} + +static void drmdev_destroy(struct drmdev *drmdev) { + assert(refcount_is_zero(&drmdev->n_refs)); + + gbm_device_destroy(drmdev->gbm_device); + drmdev->interface.close(drmdev->fd, drmdev->fd_metadata, drmdev->interface_userdata); + free(drmdev); +} + +DEFINE_REF_OPS(drmdev, n_refs) + +struct drm_resources *drmdev_query_resources(struct drmdev *drmdev) { + ASSERT_NOT_NULL(drmdev); + return drm_resources_new(drmdev->fd); +} + +int drmdev_get_modesetting_fd(struct drmdev *drmdev) { + ASSERT_NOT_NULL(drmdev); + return drmdev->fd; +} + +bool drmdev_supports_dumb_buffers(struct drmdev *drmdev) { + return drmdev->supports_dumb_buffers; +} + +int drmdev_create_dumb_buffer( + struct drmdev *drmdev, + int width, + int height, + int bpp, + uint32_t *gem_handle_out, + uint32_t *pitch_out, + size_t *size_out +) { + struct drm_mode_create_dumb create_req; + int ok; + + ASSERT_NOT_NULL(drmdev); + ASSERT_NOT_NULL(gem_handle_out); + ASSERT_NOT_NULL(pitch_out); + ASSERT_NOT_NULL(size_out); + + memset(&create_req, 0, sizeof create_req); + create_req.width = width; + create_req.height = height; + create_req.bpp = bpp; + create_req.flags = 0; + + ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not create dumb buffer. ioctl: %s\n", strerror(errno)); + goto fail_return_ok; + } + + *gem_handle_out = create_req.handle; + *pitch_out = create_req.pitch; + *size_out = create_req.size; + return 0; + +fail_return_ok: + return ok; +} + +void drmdev_destroy_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle) { + struct drm_mode_destroy_dumb destroy_req; + int ok; + + ASSERT_NOT_NULL(drmdev); + + memset(&destroy_req, 0, sizeof destroy_req); + destroy_req.handle = gem_handle; + + ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); + if (ok < 0) { + LOG_ERROR("Could not destroy dumb buffer. ioctl: %s\n", strerror(errno)); + } +} + +void *drmdev_map_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle, size_t size) { + struct drm_mode_map_dumb map_req; + void *map; + int ok; + + ASSERT_NOT_NULL(drmdev); + + memset(&map_req, 0, sizeof map_req); + map_req.handle = gem_handle; + + ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req); + if (ok < 0) { + LOG_ERROR("Could not prepare dumb buffer mmap. ioctl: %s\n", strerror(errno)); + return NULL; + } + + map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, drmdev->fd, map_req.offset); + if (map == MAP_FAILED) { + LOG_ERROR("Could not mmap dumb buffer. mmap: %s\n", strerror(errno)); + return NULL; + } + + return map; +} + +void drmdev_unmap_dumb_buffer(struct drmdev *drmdev, void *map, size_t size) { + int ok; + + ASSERT_NOT_NULL(drmdev); + ASSERT_NOT_NULL(map); + (void) drmdev; + + ok = munmap(map, size); + if (ok < 0) { + LOG_ERROR("Couldn't unmap dumb buffer. munmap: %s\n", strerror(errno)); + } +} + +struct gbm_device *drmdev_get_gbm_device(struct drmdev *drmdev) { + 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; + + ASSERT_NOT_NULL(drmdev); + 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; +} + +uint32_t drmdev_add_fb_multiplanar( + struct drmdev *drmdev, + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + const uint32_t bo_handles[4], + const uint32_t pitches[4], + const uint32_t offsets[4], + bool has_modifiers, + const uint64_t modifiers[4] +) { + struct drm_fb *fb; + uint32_t fb_id; + int ok; + + /// TODO: Code in https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/drm_framebuffer.c#L257 + /// assumes handles, pitches, offsets and modifiers for unused planes are zero. Make sure that's the + /// case here. + ASSERT_NOT_NULL(drmdev); + assert(width > 0 && height > 0); + assert(bo_handles[0] != 0); + assert(pitches[0] != 0); + + fb = malloc(sizeof *fb); + if (fb == NULL) { + return 0; + } + + list_inithead(&fb->entry); + fb->id = 0; + fb->width = width; + fb->height = height; + fb->format = pixel_format; + fb->has_modifier = has_modifiers; + fb->modifier = modifiers[0]; + fb->flags = 0; + memcpy(fb->handles, bo_handles, sizeof(fb->handles)); + memcpy(fb->pitches, pitches, sizeof(fb->pitches)); + memcpy(fb->offsets, offsets, sizeof(fb->offsets)); + + fb_id = 0; + if (has_modifiers) { + ok = drmModeAddFB2WithModifiers( + drmdev->fd, + width, + height, + get_pixfmt_info(pixel_format)->drm_format, + bo_handles, + pitches, + offsets, + modifiers, + &fb_id, + DRM_MODE_FB_MODIFIERS + ); + if (ok < 0) { + LOG_ERROR("Couldn't add buffer as DRM fb. drmModeAddFB2WithModifiers: %s\n", strerror(-ok)); + goto fail_free_fb; + } + } else { + ok = drmModeAddFB2(drmdev->fd, width, height, get_pixfmt_info(pixel_format)->drm_format, bo_handles, pitches, offsets, &fb_id, 0); + if (ok < 0) { + LOG_ERROR("Couldn't add buffer as DRM fb. drmModeAddFB2: %s\n", strerror(-ok)); + goto fail_free_fb; + } + } + + fb->id = fb_id; + + pthread_mutex_lock(&drmdev->mutex); + + list_add(&fb->entry, &drmdev->fbs); + + pthread_mutex_unlock(&drmdev->mutex); + + assert(fb_id != 0); + return fb_id; + +fail_free_fb: + free(fb); + return 0; +} + +uint32_t drmdev_add_fb( + struct drmdev *drmdev, + 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 +) { + return drmdev_add_fb_multiplanar( + drmdev, + width, + height, + pixel_format, + (uint32_t[4]){ bo_handle, 0 }, + (uint32_t[4]){ pitch, 0 }, + (uint32_t[4]){ offset, 0 }, + has_modifier, + (const uint64_t[4]){ modifier, 0 } + ); +} + +uint32_t drmdev_add_fb_from_dmabuf( + struct drmdev *drmdev, + 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 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 drmdev_add_fb(drmdev, width, height, pixel_format, prime_fd, pitch, offset, has_modifier, modifier); +} + +uint32_t drmdev_add_fb_from_dmabuf_multiplanar( + struct drmdev *drmdev, + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + const int prime_fds[4], + const uint32_t pitches[4], + const uint32_t offsets[4], + bool has_modifiers, + const uint64_t modifiers[4] +) { + uint32_t bo_handles[4] = { 0 }; + int ok; + + 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; + } + } + + return drmdev_add_fb_multiplanar(drmdev, width, height, pixel_format, bo_handles, pitches, offsets, has_modifiers, modifiers); +} + +uint32_t drmdev_add_fb_from_gbm_bo(struct drmdev *drmdev, struct gbm_bo *bo, bool cast_opaque) { + enum pixfmt format; + uint32_t fourcc; + int n_planes; + + n_planes = gbm_bo_get_plane_count(bo); + ASSERT(0 <= n_planes && n_planes <= 4); + + fourcc = gbm_bo_get_format(bo); + + if (!has_pixfmt_for_gbm_format(fourcc)) { + LOG_ERROR("GBM pixel format is not supported.\n"); + return 0; + } + + format = get_pixfmt_for_gbm_format(fourcc); + + if (cast_opaque) { + format = pixfmt_opaque(format); + } + + uint32_t handles[4]; + uint32_t pitches[4]; + + // Returns DRM_FORMAT_MOD_INVALID on failure, or DRM_FORMAT_MOD_LINEAR + // for dumb buffers. + uint64_t modifier = gbm_bo_get_modifier(bo); + bool has_modifiers = modifier != DRM_FORMAT_MOD_INVALID; + + for (int i = 0; i < n_planes; i++) { + // gbm_bo_get_handle_for_plane will return -1 (in gbm_bo_handle.s32) and + // set errno on failure. + errno = 0; + union gbm_bo_handle handle = gbm_bo_get_handle_for_plane(bo, i); + if (handle.s32 == -1) { + LOG_ERROR("Could not get GEM handle for plane %d: %s\n", i, strerror(errno)); + return 0; + } + + handles[i] = handle.u32; + + // gbm_bo_get_stride_for_plane will return 0 and set errno on failure. + errno = 0; + uint32_t pitch = gbm_bo_get_stride_for_plane(bo, i); + if (pitch == 0 && errno != 0) { + LOG_ERROR("Could not get framebuffer stride for plane %d: %s\n", i, strerror(errno)); + return 0; + } + + pitches[i] = pitch; + } + + for (int i = n_planes; i < 4; i++) { + handles[i] = 0; + pitches[i] = 0; + } + + return drmdev_add_fb_multiplanar( + drmdev, + gbm_bo_get_width(bo), + gbm_bo_get_height(bo), + format, + handles, + pitches, + (uint32_t[4]){ + n_planes >= 1 ? gbm_bo_get_offset(bo, 0) : 0, + n_planes >= 2 ? gbm_bo_get_offset(bo, 1) : 0, + n_planes >= 3 ? gbm_bo_get_offset(bo, 2) : 0, + n_planes >= 4 ? gbm_bo_get_offset(bo, 3) : 0, + }, + has_modifiers, + (uint64_t[4]){ + n_planes >= 1 ? modifier : 0, + n_planes >= 2 ? modifier : 0, + n_planes >= 3 ? modifier : 0, + n_planes >= 4 ? modifier : 0, + } + ); +} + +int drmdev_rm_fb(struct drmdev *drmdev, uint32_t fb_id) { + int ok; + + pthread_mutex_lock(&drmdev->mutex); + + list_for_each_entry(struct drm_fb, fb, &drmdev->fbs, entry) { + if (fb->id == fb_id) { + list_del(&fb->entry); + free(fb); + break; + } + } + + pthread_mutex_unlock(&drmdev->mutex); + + ok = drmModeRmFB(drmdev->fd, fb_id); + if (ok < 0) { + ok = -ok; + LOG_ERROR("Could not remove DRM framebuffer. drmModeRmFB: %s\n", strerror(ok)); + return ok; + } + + return 0; +} + +int drmdev_move_cursor(struct drmdev *drmdev, uint32_t crtc_id, struct vec2i pos) { + int ok = drmModeMoveCursor(drmdev->fd, crtc_id, pos.x, pos.y); + if (ok < 0) { + LOG_ERROR("Couldn't move mouse cursor. drmModeMoveCursor: %s\n", strerror(-ok)); + return -ok; + } + + return 0; +} + +bool drmdev_can_commit(struct drmdev *drmdev) { + return is_drm_master(drmdev->fd); +} + +static int commit_atomic_common( + struct drmdev *drmdev, + drmModeAtomicReq *req, + bool sync, + bool allow_modeset, + uint32_t crtc_id, + drmdev_scanout_cb_t on_scanout, + void *scanout_cb_userdata, + void_callback_t on_release, + void *release_cb_userdata +) { + int bucket_status, ok; + + // If we don't get an event, we need to call the page flip callbacks manually. + uint64_t flags = 0; + if (allow_modeset) { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + if (!sync) { + flags |= DRM_MODE_PAGE_FLIP_EVENT; + flags |= DRM_MODE_ATOMIC_NONBLOCK; + } + + bool pageflip_event = !sync; + + if (on_scanout != NULL || on_release != NULL) { + mutex_lock(&drmdev->mutex); + + khint_t cbs_it = kh_put(pageflip_callbacks, drmdev->pageflip_callbacks, crtc_id, &bucket_status); + if (bucket_status == -1) { + mutex_unlock(&drmdev->mutex); + return ENOMEM; + } + + ok = drmModeAtomicCommit(drmdev->fd, req, flags, pageflip_event ? drmdev_ref(drmdev) : NULL); + if (ok != 0) { + mutex_unlock(&drmdev->mutex); + + ok = -errno; + LOG_ERROR("Could not commit atomic request. drmModeAtomicCommit: %s\n", strerror(-ok)); + return ok; + } + + struct pageflip_callbacks *cbs = &kh_value(drmdev->pageflip_callbacks, cbs_it); + + // If the entry didn't exist, we clear the memory. + if (bucket_status != 0) { + memset(cbs, 0, sizeof *cbs); + } + + cbs->callbacks[cbs->index].scanout_callback = on_scanout; + cbs->callbacks[cbs->index].scanout_callback_userdata = scanout_cb_userdata; + cbs->callbacks[cbs->index ^ 1].void_callback = on_release; + cbs->callbacks[cbs->index ^ 1].void_callback_userdata = release_cb_userdata; + + mutex_unlock(&drmdev->mutex); + } else { + ok = drmModeAtomicCommit(drmdev->fd, req, flags, pageflip_event ? drmdev_ref(drmdev) : NULL); + if (ok != 0) { + ok = -errno; + LOG_ERROR("Could not commit atomic request. drmModeAtomicCommit: %s\n", strerror(-ok)); + return ok; + } + } + + /// TODO: Use a more accurate timestamp, e.g. call drmCrtcGetSequence, + /// or queue a pageflip event even for synchronous (blocking) commits + /// and handle here. + if (!pageflip_event) { + drmdev_on_page_flip(drmdev, crtc_id, get_monotonic_time()); + } + + return 0; +} + +void set_vblank_timestamp(uint64_t vblank_ns, void *userdata) { + uint64_t *vblank_ns_out = userdata; + *vblank_ns_out = vblank_ns; +} + +int drmdev_commit_atomic_sync( + struct drmdev *drmdev, + drmModeAtomicReq *req, + bool allow_modeset, + uint32_t crtc_id, + void_callback_t on_release, + void *userdata, + uint64_t *vblank_ns_out +) { + return commit_atomic_common( + drmdev, + req, + true, + allow_modeset, + crtc_id, + vblank_ns_out ? set_vblank_timestamp : NULL, + vblank_ns_out, + on_release, + userdata + ); +} + +int drmdev_commit_atomic_async( + struct drmdev *drmdev, + drmModeAtomicReq *req, + bool allow_modeset, + uint32_t crtc_id, + drmdev_scanout_cb_t on_scanout, + void_callback_t on_release, + void *userdata +) { + return commit_atomic_common(drmdev, req, false, allow_modeset, crtc_id, on_scanout, userdata, on_release, userdata); +} diff --git a/src/kms/drmdev.h b/src/kms/drmdev.h new file mode 100644 index 00000000..1a804a9e --- /dev/null +++ b/src/kms/drmdev.h @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT +/* + * KMS Modesetting + * + * - implements the interface to linux kernel modesetting + * - allows querying connected screens, crtcs, planes, etc + * - allows setting video modes, showing things on screen + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_MODESETTING_H +#define _FLUTTERPI_SRC_MODESETTING_H + +#include + +#include + +#include +#include + +#include "pixel_format.h" +#include "util/collection.h" +#include "util/file_interface.h" +#include "util/geometry.h" +#include "util/refcounting.h" + +struct drmdev; + +typedef void (*drmdev_scanout_cb_t)(uint64_t vblank_ns, void *userdata); + +struct drmdev; +struct udev; + +struct drmdev *drmdev_new_from_interface_fd(int fd, void *fd_metadata, const struct file_interface *interface, void *userdata); + +struct drmdev *drmdev_new_from_path(const char *path, const struct file_interface *interface, void *userdata); + +struct drmdev * +drmdev_new_from_udev_primary(struct udev *udev, const char *seat, const struct file_interface *interface, void *interface_userdata); + +DECLARE_REF_OPS(drmdev) + +/** + * @brief Get the drm_resources for this drmdev, taking a reference on it. + * + * @param drmdev The drmdev. + * @returns The drm_resources for this drmdev. + */ +struct drm_resources *drmdev_query_resources(struct drmdev *drmdev); + +/** + * @brief Get the file descriptor for the modesetting-capable /dev/dri/cardX device. + * + * @param drmdev The drmdev. + * @returns The file descriptor for the device. + */ +int drmdev_get_modesetting_fd(struct drmdev *drmdev); + +/** + * @brief Notify the drmdev that the modesetting fd has available data. + */ +void drmdev_dispatch_modesetting(struct drmdev *drmdev); + +bool drmdev_supports_dumb_buffers(struct drmdev *drmdev); +int drmdev_create_dumb_buffer( + struct drmdev *drmdev, + int width, + int height, + int bpp, + uint32_t *gem_handle_out, + uint32_t *pitch_out, + size_t *size_out +); +void drmdev_destroy_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle); +void *drmdev_map_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle, size_t size); +void drmdev_unmap_dumb_buffer(struct drmdev *drmdev, void *map, size_t size); + +struct gbm_device *drmdev_get_gbm_device(struct drmdev *drmdev); + +uint32_t drmdev_add_fb( + struct drmdev *drmdev, + 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 drmdev_add_fb_multiplanar( + struct drmdev *drmdev, + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + const uint32_t bo_handles[4], + const uint32_t pitches[4], + const uint32_t offsets[4], + bool has_modifiers, + const uint64_t modifiers[4] +); + +uint32_t drmdev_add_fb_from_dmabuf( + struct drmdev *drmdev, + 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 drmdev_add_fb_from_dmabuf_multiplanar( + struct drmdev *drmdev, + uint32_t width, + uint32_t height, + enum pixfmt pixel_format, + const int prime_fds[4], + const uint32_t pitches[4], + const uint32_t offsets[4], + bool has_modifiers, + const uint64_t modifiers[4] +); + +uint32_t drmdev_add_fb_from_gbm_bo(struct drmdev *drmdev, struct gbm_bo *bo, bool cast_opaque); + +int drmdev_rm_fb_locked(struct drmdev *drmdev, uint32_t fb_id); + +int drmdev_rm_fb(struct drmdev *drmdev, uint32_t fb_id); + +int drmdev_get_last_vblank(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out); + +bool drmdev_can_commit(struct drmdev *drmdev); + +void drmdev_suspend(struct drmdev *drmdev); + +int drmdev_resume(struct drmdev *drmdev); + +int drmdev_move_cursor(struct drmdev *drmdev, uint32_t crtc_id, struct vec2i pos); + +int drmdev_commit_atomic_sync( + struct drmdev *drmdev, + drmModeAtomicReq *req, + bool allow_modeset, + uint32_t crtc_id, + void_callback_t on_release, + void *userdata, + uint64_t *vblank_ns_out +); + +int drmdev_commit_atomic_async( + struct drmdev *drmdev, + drmModeAtomicReq *req, + bool allow_modeset, + uint32_t crtc_id, + drmdev_scanout_cb_t on_scanout, + void_callback_t on_release, + void *userdata +); + +#endif // _FLUTTERPI_SRC_MODESETTING_H diff --git a/src/kms/kms_window.c b/src/kms/kms_window.c new file mode 100644 index 00000000..9ac4de3c --- /dev/null +++ b/src/kms/kms_window.c @@ -0,0 +1,1066 @@ +#define _GNU_SOURCE /* for asprintf */ + +#include "kms_window.h" + +#include "util/refcounting.h" +#include "window.h" +#include "window_private.h" + +struct cursor_buffer { + refcount_t n_refs; + + const struct pointer_icon *icon; + enum pixfmt format; + int width, height; + drm_plane_transform_t rotation; + + struct drmdev *drmdev; + int drm_fb_id; + struct gbm_bo *bo; + + struct vec2i hotspot; +}; + +static struct vec2i get_rotated_hotspot(const struct pointer_icon *icon, drm_plane_transform_t rotation) { + struct vec2i size; + struct vec2i hotspot; + + assert(PLANE_TRANSFORM_IS_ONLY_ROTATION(rotation)); + size = pointer_icon_get_size(icon); + hotspot = pointer_icon_get_hotspot(icon); + + if (rotation.rotate_0) { + return hotspot; + } else if (rotation.rotate_90) { + return VEC2I(size.y - hotspot.y - 1, hotspot.x); + } else if (rotation.rotate_180) { + return VEC2I(size.x - hotspot.x - 1, size.y - hotspot.y - 1); + } else { + ASSUME(rotation.rotate_270); + return VEC2I(hotspot.y, size.x - hotspot.x - 1); + } +} + +static struct cursor_buffer *cursor_buffer_new(struct drmdev *drmdev, const struct pointer_icon *icon, drm_plane_transform_t rotation) { + struct cursor_buffer *b; + struct gbm_bo *bo; + uint32_t fb_id; + struct vec2i size, rotated_size; + int ok; + + ASSERT_NOT_NULL(drmdev); + assert(PLANE_TRANSFORM_IS_ONLY_ROTATION(rotation)); + + size = pointer_icon_get_size(icon); + rotated_size = size; + + b = malloc(sizeof *b); + if (b == NULL) { + return NULL; + } + + if (rotation.rotate_90 || rotation.rotate_270) { + rotated_size = vec2i_swap_xy(size); + } + + bo = gbm_bo_create( + drmdev_get_gbm_device(drmdev), + rotated_size.x, + rotated_size.y, + get_pixfmt_info(PIXFMT_ARGB8888)->gbm_format, + GBM_BO_USE_LINEAR | GBM_BO_USE_SCANOUT | GBM_BO_USE_WRITE | GBM_BO_USE_CURSOR + ); + if (bo == NULL) { + LOG_ERROR("Could not create GBM buffer for uploading mouse cursor icon. gbm_bo_create: %s\n", strerror(errno)); + goto fail_free_b; + } + + if (gbm_bo_get_stride(bo) != rotated_size.x * 4) { + LOG_ERROR("GBM BO has unsupported framebuffer stride %u, expected was: %d\n", gbm_bo_get_stride(bo), size.x * 4); + goto fail_destroy_bo; + } + + uint32_t *pixel_data = pointer_icon_dup_pixels(icon); + if (pixel_data == NULL) { + goto fail_destroy_bo; + } + + if (rotation.rotate_0) { + ok = gbm_bo_write(bo, pixel_data, gbm_bo_get_stride(bo) * size.y); + if (ok != 0) { + LOG_ERROR("Couldn't write cursor icon to GBM BO. gbm_bo_write: %s\n", strerror(errno)); + goto fail_free_duped_pixel_data; + } + } else { + ASSUME(rotation.rotate_90 || rotation.rotate_180 || rotation.rotate_270); + + uint32_t *rotated = malloc(size.x * size.y * 4); + if (rotated == NULL) { + goto fail_free_duped_pixel_data; + } + + for (int y = 0; y < size.y; y++) { + for (int x = 0; x < size.x; x++) { + int buffer_x, buffer_y; + if (rotation.rotate_90) { + buffer_x = size.y - y - 1; + buffer_y = x; + } else if (rotation.rotate_180) { + buffer_x = size.y - y - 1; + buffer_y = size.x - x - 1; + } else { + ASSUME(rotation.rotate_270); + buffer_x = y; + buffer_y = size.x - x - 1; + } + + int buffer_offset = rotated_size.x * buffer_y + buffer_x; + int cursor_offset = size.x * y + x; + + rotated[buffer_offset] = pixel_data[cursor_offset]; + } + } + + ok = gbm_bo_write(bo, rotated, gbm_bo_get_stride(bo) * rotated_size.y); + + free(rotated); + + if (ok != 0) { + LOG_ERROR("Couldn't write rotated cursor icon to GBM BO. gbm_bo_write: %s\n", strerror(errno)); + goto fail_free_duped_pixel_data; + } + } + + free(pixel_data); + + fb_id = drmdev_add_fb( + drmdev, + rotated_size.x, + rotated_size.y, + PIXFMT_ARGB8888, + gbm_bo_get_handle(bo).u32, + gbm_bo_get_stride(bo), + gbm_bo_get_offset(bo, 0), + gbm_bo_get_modifier(bo) != DRM_FORMAT_MOD_INVALID, + gbm_bo_get_modifier(bo) + ); + if (fb_id == 0) { + goto fail_destroy_bo; + } + + b->n_refs = REFCOUNT_INIT_1; + b->icon = icon; + b->format = PIXFMT_ARGB8888; + b->width = rotated_size.x; + b->height = rotated_size.y; + b->rotation = rotation; + b->drmdev = drmdev_ref(drmdev); + b->drm_fb_id = fb_id; + b->bo = bo; + b->hotspot = get_rotated_hotspot(icon, rotation); + return b; + +fail_free_duped_pixel_data: + free(pixel_data); + +fail_destroy_bo: + gbm_bo_destroy(bo); + +fail_free_b: + free(b); + return NULL; +} + +static void cursor_buffer_destroy(struct cursor_buffer *buffer) { + drmdev_rm_fb(buffer->drmdev, buffer->drm_fb_id); + gbm_bo_destroy(buffer->bo); + drmdev_unref(buffer->drmdev); + free(buffer); +} + +DEFINE_STATIC_REF_OPS(cursor_buffer, n_refs) + +static int select_mode( + struct drm_resources *resources, + struct drm_connector **connector_out, + struct drm_encoder **encoder_out, + struct drm_crtc **crtc_out, + drmModeModeInfo **mode_out, + const char *desired_videomode +) { + int ok; + + // find any connected connector + struct drm_connector *connector = NULL; + drm_resources_for_each_connector(resources, connector_it) { + if (connector_it->variable_state.connection_state == DRM_CONNSTATE_CONNECTED) { + connector = connector_it; + break; + } + } + + if (connector == NULL) { + LOG_ERROR("Could not find a connected connector!\n"); + return EINVAL; + } + + drmModeModeInfo *mode = NULL; + if (desired_videomode != NULL) { + drm_connector_for_each_mode(connector, mode_iter) { + char *modeline = NULL, *modeline_nohz = NULL; + + ok = asprintf(&modeline, "%" PRIu16 "x%" PRIu16 "@%" PRIu32, mode_iter->hdisplay, mode_iter->vdisplay, mode_iter->vrefresh); + if (ok < 0) { + return ENOMEM; + } + + ok = asprintf(&modeline_nohz, "%" PRIu16 "x%" PRIu16, mode_iter->hdisplay, mode_iter->vdisplay); + if (ok < 0) { + return ENOMEM; + } + + if (streq(modeline, desired_videomode)) { + // Probably a bit superfluos, but the refresh rate can still vary in the decimal places. + if (mode == NULL || (mode_get_vrefresh(mode_iter) > mode_get_vrefresh(mode))) { + mode = mode_iter; + } + } else if (streq(modeline_nohz, desired_videomode)) { + if (mode == NULL || (mode_get_vrefresh(mode_iter) > mode_get_vrefresh(mode))) { + mode = mode_iter; + } + } + + free(modeline); + free(modeline_nohz); + } + + if (mode == NULL) { + LOG_ERROR("Didn't find a videomode matching \"%s\"! Falling back to display preferred mode.\n", desired_videomode); + } + } + + // Find the preferred mode (GPU drivers _should_ always supply a preferred mode, but of course, they don't) + // Alternatively, find the mode with the highest width*height. If there are multiple modes with the same w*h, + // prefer higher refresh rates. After that, prefer progressive scanout modes. + if (mode == NULL) { + drm_connector_for_each_mode(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; + } + } + + ASSERT_NOT_NULL(mode); + + // Find the encoder that's linked to the connector right now + struct drm_encoder *encoder = NULL; + drm_resources_for_each_encoder(resources, encoder_it) { + if (encoder_it->id == connector->committed_state.encoder_id) { + encoder = encoder_it; + 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) { + drm_resources_for_each_encoder(resources, encoder_it) { + if (encoder_it->id == connector->encoders[i]) { + encoder = encoder_it; + 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 + struct drm_crtc *crtc = NULL; + drm_resources_for_each_crtc(resources, crtc_it) { + if (crtc_it->id == encoder->variable_state.crtc_id) { + crtc = crtc_it; + break; + } + } + + // Otherwise use any CRTC that this encoder supports linking to + if (crtc == NULL) { + drm_resources_for_each_crtc(resources, crtc_it) { + if (encoder->possible_crtcs & crtc_it->bitmask) { + // find a CRTC that is possible to use with this encoder + crtc = crtc_it; + 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 const struct window_ops kms_window_ops; + +struct kms_window { + struct window base; + + struct drmdev *drmdev; + struct drm_resources *resources; + struct drm_connector *connector; + struct drm_encoder *encoder; + struct drm_crtc *crtc; + drmModeModeInfo *mode; + + bool should_apply_mode; + + const struct pointer_icon *pointer_icon; + struct cursor_buffer *cursor; + + bool cursor_works; +}; + +MUST_CHECK struct window *kms_window_new( + // clang-format off + struct tracer *tracer, + struct frame_scheduler *scheduler, + enum renderer_type renderer_type, + struct gl_renderer *gl_renderer, + struct vk_renderer *vk_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, + struct drmdev *drmdev, + struct drm_resources *resources, + const char *desired_videomode + // clang-format on +) { + struct drm_connector *selected_connector; + struct drm_encoder *selected_encoder; + struct drm_crtc *selected_crtc; + drmModeModeInfo *selected_mode; + bool has_dimensions; + int ok; + + ASSERT_NOT_NULL(drmdev); + ASSERT_NOT_NULL(resources); + + struct kms_window *window = malloc(sizeof *window); + if (window == NULL) { + return NULL; + } + + ok = select_mode(resources, &selected_connector, &selected_encoder, &selected_crtc, &selected_mode, desired_videomode); + if (ok != 0) { + goto fail_free_window; + } + + if (has_explicit_dimensions) { + has_dimensions = true; + } else if (selected_connector->variable_state.width_mm % 10 || selected_connector->variable_state.height_mm % 10) { + // as a heuristic, assume the physical dimensions are valid if they're not both multiples of 10. + // dimensions like 160x90mm, 150x100mm are often bogus. + 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) { + // assume this is the official Raspberry Pi DSI display. + has_dimensions = true; + width_mm = 155; + height_mm = 86; + } else { + has_dimensions = false; + } + + ok = window_init( + // clang-format off + &window->base, + tracer, + scheduler, + has_rotation, rotation, + has_orientation, orientation, + selected_mode->hdisplay, selected_mode->vdisplay, + has_dimensions, width_mm, height_mm, + mode_get_vrefresh(selected_mode), + has_forced_pixel_format, forced_pixel_format, + renderer_type, + gl_renderer, + vk_renderer + // clang-format on + ); + if (ok != 0) { + free(window); + return NULL; + } + + LOG_DEBUG_UNPREFIXED( + "display mode:\n" + " resolution: %" PRIu16 " x %" PRIu16 + "\n" + " refresh rate: %fHz\n" + " physical size: %dmm x %dmm\n" + " flutter device pixel ratio: %f\n" + " pixel format: %s\n", + selected_mode->hdisplay, + selected_mode->vdisplay, + (double) mode_get_vrefresh(selected_mode), + width_mm, + height_mm, + (double) window->base.pixel_ratio, + has_forced_pixel_format ? get_pixfmt_info(forced_pixel_format)->name : "(any)" + ); + + window->drmdev = drmdev_ref(drmdev); + window->resources = drm_resources_ref(resources); + window->connector = selected_connector; + window->encoder = selected_encoder; + window->crtc = selected_crtc; + window->mode = selected_mode; + window->should_apply_mode = true; + window->cursor = NULL; + window->pointer_icon = NULL; + window->cursor_works = true; + window->base.ops = kms_window_ops; + return (struct window *) window; + +fail_free_window: + free(window); + return NULL; +} + +void kms_window_deinit(struct window *window) { + /// TODO: Do we really need to do this? + /* + struct kms_req_builder *builder; + struct kms_req *req; + int ok; + + builder = drmdev_create_request_builder(window->kms.drmdev, window->kms.crtc->id); + ASSERT_NOT_NULL(builder); + + ok = kms_req_builder_unset_mode(builder); + ASSERT_EQUALS(ok, 0); + + req = kms_req_builder_build(builder); + ASSERT_NOT_NULL(req); + + kms_req_builder_unref(builder); + + ok = kms_req_commit_blocking(req, NULL); + ASSERT_EQUALS(ok, 0); + (void) ok; + + kms_req_unref(req); + */ + + struct kms_window *kms_window = (struct kms_window *) window; + + if (kms_window->cursor != NULL) { + cursor_buffer_unref(kms_window->cursor); + } + drm_resources_unref(kms_window->resources); + drmdev_unref(kms_window->drmdev); + window_deinit(window); +} + +struct frame { + struct tracer *tracer; + struct kms_req *req; + struct drmdev *drmdev; + struct frame_scheduler *scheduler; + bool unset_should_apply_mode_on_commit; +}; + +UNUSED static void on_scanout(uint64_t vblank_ns, void *userdata) { + struct frame *frame; + + ASSERT_NOT_NULL(userdata); + frame = userdata; + + // This potentially presents a new frame. + frame_scheduler_on_scanout(frame->scheduler, true, vblank_ns); + + frame_scheduler_unref(frame->scheduler); + tracer_unref(frame->tracer); + kms_req_unref(frame->req); + free(frame); +} + +static void on_present_frame(void *userdata) { + struct frame *frame; + int ok; + + ASSERT_NOT_NULL(userdata); + + frame = userdata; + + { + // Keep our own reference on tracer, because the frame might be destroyed + // after kms_req_commit_nonblocking returns. + struct tracer *tracer = tracer_ref(frame->tracer); + + // The pageflip events might be handled on a different thread, so on_scanout + // might already be executed and the frame instance already freed once + // kms_req_commit_nonblocking returns. + TRACER_BEGIN(tracer, "kms_req_commit_nonblocking"); + ok = kms_req_commit_nonblocking(frame->req, frame->drmdev, on_scanout, frame, NULL); + TRACER_END(tracer, "kms_req_commit_nonblocking"); + + tracer_unref(tracer); + } + + if (ok != 0) { + LOG_ERROR("Could not commit frame request.\n"); + frame_scheduler_unref(frame->scheduler); + + // Analyzer thinks the tracer might already be destroyed by the tracer_unref + // above. We know that's not possible. + ANALYZER_SUPPRESS(tracer_unref(frame->tracer)); + + kms_req_unref(frame->req); + free(frame); + } +} + +static void on_cancel_frame(void *userdata) { + struct frame *frame; + ASSERT_NOT_NULL(userdata); + + frame = userdata; + + frame_scheduler_unref(frame->scheduler); + tracer_unref(frame->tracer); + kms_req_unref(frame->req); + drmdev_unref(frame->drmdev); + free(frame); +} + +static int kms_window_push_composition_locked(struct kms_window *w, struct fl_layer_composition *composition) { + struct kms_req_builder *builder; + struct kms_req *req; + struct frame *frame; + int ok; + + ASSERT_NOT_NULL(w); + 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; + // } + // } + + /// TODO: If we don't have new revisions, we don't need to scanout anything. + fl_layer_composition_swap_ptrs(&w->base.composition, composition); + + builder = kms_req_builder_new_atomic(w->drmdev, w->resources, w->crtc->id); + if (builder == NULL) { + ok = ENOMEM; + goto fail_unref_builder; + } + + // We only set the mode once, at the first atomic request. + if (w->should_apply_mode) { + ok = kms_req_builder_set_connector(builder, w->connector->id); + if (ok != 0) { + LOG_ERROR("Couldn't select connector.\n"); + ok = EIO; + goto fail_unref_builder; + } + + ok = kms_req_builder_set_mode(builder, w->mode); + if (ok != 0) { + LOG_ERROR("Couldn't apply output mode.\n"); + ok = EIO; + goto fail_unref_builder; + } + } + + 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); + + 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_unref_builder; + } + } + + // add cursor infos + if (w->cursor != NULL) { + ok = kms_req_builder_push_fb_layer( + builder, + &(const struct kms_fb_layer){ + .drm_fb_id = w->cursor->drm_fb_id, + .format = w->cursor->format, + .has_modifier = true, + .modifier = DRM_FORMAT_MOD_LINEAR, + .src_x = 0, + .src_y = 0, + .src_w = ((uint16_t) w->cursor->width) << 16, + .src_h = ((uint16_t) w->cursor->height) << 16, + .dst_x = (int32_t) (w->base.cursor_pos.x) - w->cursor->hotspot.x, + .dst_y = (int32_t) (w->base.cursor_pos.y) - w->cursor->hotspot.y, + .dst_w = w->cursor->width, + .dst_h = w->cursor->height, + .has_rotation = false, + .rotation = PLANE_TRANSFORM_NONE, + .has_in_fence_fd = false, + .in_fence_fd = 0, + .prefer_cursor = true, + }, + cursor_buffer_unref_void, + NULL, + w->cursor + ); + if (ok != 0) { + LOG_ERROR("Couldn't present cursor. Hardware cursor will be disabled.\n"); + + w->cursor_works = false; + w->base.cursor_enabled = false; + cursor_buffer_unrefp(&w->cursor); + } else { + cursor_buffer_ref(w->cursor); + } + } + + req = kms_req_builder_build(builder); + if (req == NULL) { + ok = ENOMEM; + goto fail_unref_builder; + } + + kms_req_builder_unref(builder); + builder = NULL; + + frame = malloc(sizeof *frame); + if (frame == NULL) { + ok = ENOMEM; + goto fail_unref_req; + } + + frame->req = req; + frame->tracer = tracer_ref(w->base.tracer); + frame->drmdev = drmdev_ref(w->drmdev); + frame->scheduler = frame_scheduler_ref(w->base.frame_scheduler); + frame->unset_should_apply_mode_on_commit = w->should_apply_mode; + + frame_scheduler_present_frame(w->base.frame_scheduler, on_present_frame, frame, on_cancel_frame); + + // 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 { + // 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; + // } + // } + + // 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_req: + kms_req_unref(req); + return ok; + +fail_unref_builder: + kms_req_builder_unref(builder); + return ok; +} + +static int kms_window_push_composition(struct window *window, struct fl_layer_composition *composition) { + mutex_lock(&window->lock); + + int ok = kms_window_push_composition_locked((struct kms_window *) window, composition); + + mutex_unlock(&window->lock); + + return ok; +} + +static bool count_modifiers_for_pixel_format( + UNUSED struct drm_plane *plane, + UNUSED int index, + enum pixfmt pixel_format, + UNUSED uint64_t modifier, + void *userdata +) { + struct { + enum pixfmt format; + uint64_t *modifiers; + size_t n_modifiers; + int index; + } *context = userdata; + + if (pixel_format == context->format) { + context->n_modifiers++; + } + + return true; +} + +static bool extract_modifiers_for_pixel_format( + UNUSED struct drm_plane *plane, + UNUSED int index, + enum pixfmt pixel_format, + uint64_t modifier, + void *userdata +) { + struct { + enum pixfmt format; + uint64_t *modifiers; + size_t n_modifiers; + int index; + } *context = userdata; + + if (pixel_format == context->format) { + context->modifiers[context->index++] = modifier; + } + + return true; +} + +static struct render_surface *kms_window_get_render_surface_internal(struct kms_window *window, bool has_size, UNUSED struct vec2i size) { + struct render_surface *render_surface; + + ASSERT_NOT_NULL(window); + + if (window->base.render_surface != NULL) { + return window->base.render_surface; + } + + if (!has_size) { + // Flutter wants a render surface, but hasn't told us the backing store dimensions yet. + // Just make a good guess about the dimensions. + LOG_DEBUG("Flutter requested render surface before supplying surface dimensions.\n"); + size = VEC2I(window->mode->hdisplay, window->mode->vdisplay); + } + + enum pixfmt pixel_format; + if (window->base.has_forced_pixel_format) { + pixel_format = window->base.forced_pixel_format; + } else { + // Actually, more devices support ARGB8888 might sometimes not be supported by devices, + // for example for primary planes. But we can just cast ARGB8888 to XRGB8888 if we need to, + // and ARGB8888 is still a good default choice because casting XRGB to ARGB might not work, + // and sometimes we need alpha for overlay planes. + // Also vulkan doesn't work with XRGB yet so we definitely need to use ARGB to vulkan too. + pixel_format = PIXFMT_ARGB8888; + } + + // Possibly populate this with the supported modifiers for this pixel format. + // If no plane lists modifiers for this pixel format, this will be left at NULL, + // and egl_gbm_render_surface_new... will create the GBM surface using usage flags + // (GBM_USE_SCANOUT | GBM_USE_RENDER) instead. + uint64_t *allowed_modifiers = NULL; + size_t n_allowed_modifiers = 0; + + // For now just set the supported modifiers for the first plane that supports this pixel format + // as the allowed modifiers. + /// TODO: Find a way to rank pixel formats, maybe by number of planes that support them for scanout. + drm_resources_for_each_plane(window->resources, plane_it) { + if (!(plane_it->possible_crtcs & window->crtc->bitmask)) { + // Only query planes that are possible to connect to the CRTC we're using. + continue; + } + + if (plane_it->type != DRM_PRIMARY_PLANE && plane_it->type != DRM_OVERLAY_PLANE) { + // We explicitly only look for primary and overlay planes. + continue; + } + + if (!plane_it->supports_modifiers) { + // The plane does not have an IN_FORMATS property and does not support + // explicit modifiers. + // + // Calling drm_plane_for_each_modified_format below will segfault. + continue; + } + + struct { + enum pixfmt format; + uint64_t *modifiers; + size_t n_modifiers; + int index; + } context = { + .format = pixel_format, + .modifiers = NULL, + .n_modifiers = 0, + .index = 0, + }; + + // First, count the allowed modifiers for this pixel format. + drm_plane_for_each_modified_format(plane_it, count_modifiers_for_pixel_format, &context); + + if (context.n_modifiers > 0) { + n_allowed_modifiers = context.n_modifiers; + allowed_modifiers = calloc(n_allowed_modifiers, sizeof(*context.modifiers)); + context.modifiers = allowed_modifiers; + + // Next, fill context.modifiers with the allowed modifiers. + drm_plane_for_each_modified_format(plane_it, extract_modifiers_for_pixel_format, &context); + } else { + n_allowed_modifiers = 0; + allowed_modifiers = NULL; + } + break; + } + + if (window->base.renderer_type == kOpenGL_RendererType) { + // opengl +#ifdef HAVE_EGL_GLES2 + // EGL_NO_CONFIG_KHR is defined by EGL_KHR_no_config_context. + #ifndef EGL_KHR_no_config_context + #error "EGL header definitions for extension EGL_KHR_no_config_context are required." + #endif + + struct egl_gbm_render_surface *egl_surface = egl_gbm_render_surface_new_with_egl_config( + window->base.tracer, + size, + gl_renderer_get_gbm_device(window->base.gl_renderer), + window->base.gl_renderer, + pixel_format, + EGL_NO_CONFIG_KHR, + allowed_modifiers, + n_allowed_modifiers + ); + if (egl_surface == NULL) { + LOG_ERROR("Couldn't create EGL GBM rendering surface.\n"); + render_surface = NULL; + } else { + render_surface = CAST_RENDER_SURFACE(egl_surface); + } + +#else + UNREACHABLE(); +#endif + } else { + ASSUME(window->base.renderer_type == kVulkan_RendererType); + + // vulkan +#ifdef HAVE_VULKAN + struct vk_gbm_render_surface *vk_surface = vk_gbm_render_surface_new( + window->base.tracer, + size, + drmdev_get_gbm_device(window->drmdev), + window->base.vk_renderer, + pixel_format + ); + if (vk_surface == NULL) { + LOG_ERROR("Couldn't create Vulkan GBM rendering surface.\n"); + render_surface = NULL; + } else { + render_surface = CAST_RENDER_SURFACE(vk_surface); + } +#else + UNREACHABLE(); +#endif + } + + if (allowed_modifiers != NULL) { + free(allowed_modifiers); + } + + window->base.render_surface = render_surface; + return render_surface; +} + +static struct render_surface *kms_window_get_render_surface(struct window *window, struct vec2i size) { + ASSERT_NOT_NULL(window); + return kms_window_get_render_surface_internal((struct kms_window *) window, true, size); +} + +#ifdef HAVE_EGL_GLES2 +static bool kms_window_has_egl_surface(struct window *window) { + if (window->renderer_type == kOpenGL_RendererType) { + return window->render_surface != NULL; + } else { + return false; + } +} + +static EGLSurface kms_window_get_egl_surface(struct window *window) { + if (window->renderer_type == kOpenGL_RendererType) { + struct render_surface *render_surface = kms_window_get_render_surface_internal((struct kms_window *) window, false, VEC2I(0, 0)); + return egl_gbm_render_surface_get_egl_surface(CAST_EGL_GBM_RENDER_SURFACE(render_surface)); + } else { + return EGL_NO_SURFACE; + } +} +#endif + +static int kms_window_set_cursor_locked( + // clang-format off + struct window *window, + bool has_enabled, bool enabled, + bool has_kind, enum pointer_kind kind, + bool has_pos, struct vec2i pos + // clang-format on +) { + const struct pointer_icon *icon; + struct cursor_buffer *cursor; + struct kms_window *w; + + ASSERT_NOT_NULL(window); + w = (struct kms_window *) window; + + if (has_kind) { + if (w->pointer_icon == NULL || pointer_icon_get_kind(w->pointer_icon) != kind) { + w->pointer_icon = pointer_icon_for_details(kind, w->base.pixel_ratio); + ASSERT_NOT_NULL(w->pointer_icon); + } + } + + enabled = has_enabled ? enabled : w->base.cursor_enabled; + icon = has_kind ? pointer_icon_for_details(kind, w->base.pixel_ratio) : w->pointer_icon; + pos = has_pos ? pos : vec2f_round_to_integer(w->base.cursor_pos); + cursor = w->cursor; + + if (enabled && !w->cursor_works) { + // hardware cursor is disabled, so we can't enable it. + return EIO; + } + + if (enabled && icon == NULL) { + // default to the arrow icon. + icon = pointer_icon_for_details(POINTER_KIND_BASIC, w->base.pixel_ratio); + ASSERT_NOT_NULL(icon); + } + + if (w->pointer_icon != icon) { + w->pointer_icon = icon; + } + + if (enabled) { + if (cursor == NULL || icon != cursor->icon) { + cursor = cursor_buffer_new(w->drmdev, w->pointer_icon, w->base.rotation); + if (cursor == NULL) { + return EIO; + } + + cursor_buffer_swap_ptrs(&w->cursor, cursor); + + // cursor is created with refcount 1. cursor_buffer_swap_ptrs + // increases refcount by one. deref here so we don't leak a + // reference. + cursor_buffer_unrefp(&cursor); + + // apply the new cursor icon & position by scanning out a new frame. + w->base.cursor_pos = VEC2F(pos.x, pos.y); + if (w->base.composition != NULL) { + kms_window_push_composition_locked(w, w->base.composition); + } + } else if (has_pos) { + // apply the new cursor position using drmModeMoveCursor + w->base.cursor_pos = VEC2F(pos.x, pos.y); + drmdev_move_cursor(w->drmdev, w->crtc->id, vec2i_sub(pos, w->cursor->hotspot)); + } + } else { + if (w->cursor != NULL) { + cursor_buffer_unrefp(&w->cursor); + } + } + + w->base.cursor_enabled = enabled; + return 0; +} + +static input_device_match_score_t kms_window_match_input_device(UNUSED struct window *window, UNUSED struct user_input_device *device) { + return 1; +} + +static const struct window_ops kms_window_ops = { + .deinit = kms_window_deinit, + .push_composition = kms_window_push_composition, + .get_render_surface = kms_window_get_render_surface, +#ifdef HAVE_EGL_GLES2 + .has_egl_surface = kms_window_has_egl_surface, + .get_egl_surface = kms_window_get_egl_surface, +#endif + .set_cursor_locked = kms_window_set_cursor_locked, + .match_input_device = kms_window_match_input_device, +}; diff --git a/src/kms/kms_window.h b/src/kms/kms_window.h new file mode 100644 index 00000000..437c5fc3 --- /dev/null +++ b/src/kms/kms_window.h @@ -0,0 +1,33 @@ +#ifndef _FLUTTERPI_SRC_KMS_KMS_WINDOW_H +#define _FLUTTERPI_SRC_KMS_KMS_WINDOW_H + +#include "pixel_format.h" +#include "window.h" + +#include "config.h" + +struct tracer; +struct frame_scheduler; +struct gl_renderer; +struct vk_renderer; +struct drmdev; +struct drm_resources; + +MUST_CHECK struct window *kms_window_new( + // clang-format off + struct tracer *tracer, + struct frame_scheduler *scheduler, + enum renderer_type renderer_type, + struct gl_renderer *gl_renderer, + struct vk_renderer *vk_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, + struct drmdev *drmdev, + struct drm_resources *resources, + const char *desired_videomode + // clang-format on +); + +#endif // _FLUTTERPI_SRC_KMS_KMS_WINDOW_H diff --git a/src/kms/monitor.c b/src/kms/monitor.c new file mode 100644 index 00000000..45ae0fb9 --- /dev/null +++ b/src/kms/monitor.c @@ -0,0 +1,126 @@ +#include "monitor.h" + +#include +#include +#include + +#include + +#include "resources.h" + +struct drm_monitor { + struct udev *udev; + struct udev_monitor *monitor; + char *sysnum_filter; + + struct drm_uevent_listener listener; + void *listener_userdata; +}; + +struct drm_monitor * +drm_monitor_new(const char *sysnum_filter, struct udev *udev, const struct drm_uevent_listener *listener, void *listener_userdata) { + struct drm_monitor *m; + + ASSERT_NOT_NULL(udev); + ASSERT_NOT_NULL(listener); + + m = calloc(1, sizeof *m); + if (m == NULL) { + return NULL; + } + + struct udev_monitor *monitor = udev_monitor_new_from_netlink(udev, "udev"); + if (monitor == NULL) { + LOG_ERROR("Could not create udev monitor.\n"); + free(m); + return NULL; + } + + udev_monitor_filter_add_match_subsystem_devtype(monitor, "drm", NULL); + udev_monitor_enable_receiving(monitor); + + m->udev = udev_ref(udev); + m->monitor = monitor; + m->sysnum_filter = sysnum_filter != NULL ? strdup(sysnum_filter) : NULL; + m->listener = *listener; + m->listener_userdata = listener_userdata; + return m; +} + +void drm_monitor_destroy(struct drm_monitor *m) { + udev_monitor_unref(m->monitor); + udev_unref(m->udev); + free(m); +} + +void drm_monitor_dispatch(struct drm_monitor *m) { + bool hotplug, have_connector, have_property; + uint32_t connector_id, property_id; + const char *str, *sysnum, *action; + + struct udev_device *event_device = udev_monitor_receive_device(m->monitor); + if (event_device == NULL) { + LOG_ERROR("Could not receive udev device from monitor.\n"); + return; + } + + // sysname is the filename of the sysfs device file, e.g. card1. + // sysnum is the numeric digits at the end of the sysname, e.g. 1. + // e.g. /sys/.../card1 -> sysname = card1, sysnum = 1 + // /sys/.../spi0.0 -> sysname = spi0.0, sysnum = 0 + + sysnum = udev_device_get_sysnum(event_device); + + if (m->sysnum_filter != NULL) { + if (sysnum == NULL || !streq(sysnum, m->sysnum_filter)) { + // This event is not for our drm device. + udev_device_unref(event_device); + return; + } + } + + action = udev_device_get_action(event_device); + + str = udev_device_get_property_value(event_device, "HOTPLUG"); + hotplug = str != NULL && streq(str, "1"); + + // DRM subsystem uevents can have: + // - a CONNECTOR and PROPERTY property to signify that a specific drm connector property has changed + // see: https://github.com/torvalds/linux/blob/b311c1b497e51a628aa89e7cb954481e5f9dced2/drivers/gpu/drm/drm_sysfs.c#L460 + // + // - only a CONNECTOR property to signify that only this drm connector has changed + // see: https://github.com/torvalds/linux/blob/b311c1b497e51a628aa89e7cb954481e5f9dced2/drivers/gpu/drm/drm_sysfs.c#L487 + // + // - no properties at all + // see: https://github.com/torvalds/linux/blob/b311c1b497e51a628aa89e7cb954481e5f9dced2/drivers/gpu/drm/drm_sysfs.c#L441 + // + // The additional properties are only given as hints, they're not authoritative. E.g. even if the uevent + // has no CONNECTOR and PROPERTY properties, the event could still be that a single drm connector property changed. + + str = udev_device_get_property_value(event_device, "CONNECTOR"); + connector_id = 0; + have_connector = str != NULL && safe_string_to_uint32(str, &connector_id); + + property_id = 0; + str = udev_device_get_property_value(event_device, "PROPERTY"); + have_property = str != NULL && safe_string_to_uint32(str, &property_id); + + struct drm_uevent uevent = { .action = action, + .sysnum = sysnum, + .hotplug = hotplug, + .have_connector = have_connector, + .connector_id = connector_id, + .have_property = have_property, + .property_id = property_id }; + + m->listener.on_uevent(&uevent, m->listener_userdata); + + // The sysnum and action string is owned by the udev device, and we use it without dup-ing, so we + // need to free the udev_device after on_drm_subsystem_change. + udev_device_unref(event_device); + return; +} + +int drm_monitor_get_fd(struct drm_monitor *m) { + return udev_monitor_get_fd(m->monitor); +} diff --git a/src/kms/monitor.h b/src/kms/monitor.h new file mode 100644 index 00000000..7da2f470 --- /dev/null +++ b/src/kms/monitor.h @@ -0,0 +1,34 @@ +#ifndef _FLUTTERPI_SRC_MODESETTING_RESOURCE_MONITOR_H +#define _FLUTTERPI_SRC_MODESETTING_RESOURCE_MONITOR_H + +#include +#include + +struct drm_resources; +struct udev; +struct drm_monitor; + +struct drm_uevent { + const char *sysnum; + const char *action; + bool hotplug; + bool have_connector; + uint32_t connector_id; + bool have_property; + uint32_t property_id; +}; + +struct drm_uevent_listener { + void (*on_uevent)(const struct drm_uevent *uevent, void *userdata); +}; + +struct drm_monitor * +drm_monitor_new(const char *sysnum_filter, struct udev *udev, const struct drm_uevent_listener *listener, void *listener_userdata); + +void drm_monitor_destroy(struct drm_monitor *m); + +void drm_monitor_dispatch(struct drm_monitor *m); + +int drm_monitor_get_fd(struct drm_monitor *m); + +#endif diff --git a/src/kms/req_builder.c b/src/kms/req_builder.c new file mode 100644 index 00000000..dce8a442 --- /dev/null +++ b/src/kms/req_builder.c @@ -0,0 +1,977 @@ +#include "req_builder.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "drmdev.h" +#include "pixel_format.h" +#include "resources.h" +#include "util/bitset.h" +#include "util/list.h" +#include "util/lock_ops.h" +#include "util/logging.h" +#include "util/macros.h" +#include "util/refcounting.h" + +#ifdef DEBUG_DRM_PLANE_ALLOCATIONS + #define LOG_DRM_PLANE_ALLOCATION_DEBUG LOG_DEBUG +#else + #define LOG_DRM_PLANE_ALLOCATION_DEBUG(...) +#endif + +struct kms_req_layer { + struct kms_fb_layer layer; + + uint32_t plane_id; + struct drm_plane *plane; + + bool set_zpos; + int64_t zpos; + + bool set_rotation; + drm_plane_transform_t rotation; + + void_callback_t release_callback; + kmsreq_syncfile_cb_t deferred_release_callback; + void *release_callback_userdata; +}; + +struct kms_req_builder { + refcount_t n_refs; + + struct drmdev *drmdev; + struct drm_resources *res; + struct drm_connector *connector; + struct drm_crtc *crtc; + uint32_t available_planes; + + bool use_atomic; + drmModeAtomicReq *req; + + int64_t next_zpos; + bool unset_mode; + bool has_mode; + drmModeModeInfo mode; + + int n_layers; + struct kms_req_layer layers[32]; + + kmsreq_scanout_cb_t scanout_cb; + void *scanout_cb_userdata; + + void_callback_t release_cb; + void *release_cb_userdata; +}; + +COMPILE_ASSERT(BITSET_SIZE(((struct kms_req_builder *) 0)->available_planes) == 32); + +static bool plane_qualifies( + // clang-format off + struct drm_plane *plane, + bool allow_primary, + bool allow_overlay, + bool allow_cursor, + enum pixfmt format, + bool has_modifier, uint64_t modifier, + bool has_zpos, int64_t zpos_lower_limit, int64_t zpos_upper_limit, + bool has_rotation, drm_plane_transform_t rotation, + bool has_id_range, uint32_t id_lower_limit + // clang-format on +) { + LOG_DRM_PLANE_ALLOCATION_DEBUG(" checking if plane with id %" PRIu32 " qualifies...\n", plane->id); + + if (plane->type == DRM_PRIMARY_PLANE) { + if (!allow_primary) { + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane type is primary but allow_primary is false\n"); + return false; + } + } else if (plane->type == DRM_OVERLAY_PLANE) { + if (!allow_overlay) { + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane type is overlay but allow_overlay is false\n"); + return false; + } + } else if (plane->type == DRM_CURSOR_PLANE) { + if (!allow_cursor) { + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane type is cursor but allow_cursor is false\n"); + return false; + } + } else { + ASSERT(false); + } + + if (has_modifier) { + if (drm_plane_supports_modified_formats(plane)) { + // return false if we want a modified format but the plane doesn't support modified formats + LOG_DRM_PLANE_ALLOCATION_DEBUG( + " does not qualify: framebuffer has modifier %" PRIu64 " but plane does not support modified formats\n", + modifier + ); + return false; + } + + if (!drm_plane_supports_modified_format(plane, format, modifier)) { + LOG_DRM_PLANE_ALLOCATION_DEBUG( + " does not qualify: plane does not support the modified format %s, %" PRIu64 ".\n", + get_pixfmt_info(format)->name, + modifier + ); + + // not found in the supported modified format list + return false; + } + } else { + // we don't want a modified format, return false if the format is not in the list + // of supported (unmodified) formats + if (!plane->supported_formats[format]) { + LOG_DRM_PLANE_ALLOCATION_DEBUG( + " does not qualify: plane does not support the (unmodified) format %s.\n", + get_pixfmt_info(format)->name + ); + return false; + } + } + + if (has_zpos) { + if (!plane->has_zpos) { + // return false if we want a zpos but the plane doesn't support one + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: zpos constraints specified but plane doesn't have a zpos property.\n"); + 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 + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane limits cannot satisfy the specified zpos constraints.\n"); + LOG_DRM_PLANE_ALLOCATION_DEBUG( + " plane zpos range: %" PRIi64 " <= zpos <= %" PRIi64 ", given zpos constraints: %" PRIi64 " <= zpos <= %" PRIi64 ".\n", + plane->min_zpos, + plane->max_zpos, + zpos_lower_limit, + zpos_upper_limit + ); + return false; + } + } + if (has_id_range && plane->id < id_lower_limit) { + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane id does not satisfy the given plane id constrains.\n"); + LOG_DRM_PLANE_ALLOCATION_DEBUG(" plane id: %" PRIu32 ", plane id lower limit: %" PRIu32 "\n", plane->id, id_lower_limit); + return false; + } + if (has_rotation) { + if (!plane->has_rotation) { + // return false if the plane doesn't support rotation + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: explicit rotation requested but plane has no rotation property.\n"); + 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 + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane has hardcoded rotation that doesn't match the requested rotation.\n" + ); + return false; + } else if (rotation.u32 & ~plane->supported_rotations.u32) { + // return false if we can't construct the rotation using the rotation + // bits that are supported by the plane + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: requested rotation is not supported by the plane.\n"); + return false; + } + } + + LOG_DRM_PLANE_ALLOCATION_DEBUG(" does qualify.\n"); + return true; +} + +UNUSED static struct drm_plane *allocate_plane( + // clang-format off + struct kms_req_builder *builder, + bool allow_primary, + bool allow_overlay, + bool allow_cursor, + enum pixfmt format, + bool has_modifier, uint64_t modifier, + bool has_zpos, int64_t zpos_lower_limit, int64_t zpos_upper_limit, + bool has_rotation, drm_plane_transform_t rotation, + bool has_id_range, uint32_t id_lower_limit + // clang-format on +) { + for (unsigned int i = 0; i < builder->res->n_planes; i++) { + struct drm_plane *plane = builder->res->planes + i; + + if (builder->available_planes & BIT(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 + builder->available_planes &= ~BIT(i); + return plane; + } + } + + // we didn't find an available plane matching our criteria + return NULL; +} + +UNUSED static void release_plane(struct kms_req_builder *builder, uint32_t plane_id) { + unsigned int index = drm_resources_get_plane_index(builder->res, plane_id); + if (index == UINT_MAX) { + LOG_ERROR("Could not find plane with id %" PRIu32 ".\n", plane_id); + return; + } + + assert(!(builder->available_planes & BIT(index))); + builder->available_planes |= BIT(index); +} + +struct kms_req_builder *kms_req_builder_new_atomic(struct drmdev *drmdev, struct drm_resources *resources, uint32_t crtc_id) { + struct kms_req_builder *builder; + + ASSERT_NOT_NULL(resources); + assert(crtc_id != 0 && crtc_id != 0xFFFFFFFF); + + builder = calloc(1, sizeof *builder); + if (builder == NULL) { + return NULL; + } + + builder->n_refs = REFCOUNT_INIT_1; + builder->res = drm_resources_ref(resources); + builder->use_atomic = true; + builder->drmdev = drmdev_ref(drmdev); + builder->connector = NULL; + builder->n_layers = 0; + builder->has_mode = false; + builder->unset_mode = false; + + builder->crtc = drm_resources_get_crtc(resources, crtc_id); + if (builder->crtc == NULL) { + LOG_ERROR("Invalid CRTC: %" PRIu32 "\n", crtc_id); + goto fail_unref_drmdev; + } + + builder->req = drmModeAtomicAlloc(); + if (builder->req == NULL) { + goto fail_unref_drmdev; + } + + // set the CRTC to active + drmModeAtomicAddProperty(builder->req, crtc_id, builder->crtc->ids.active, 1); + + builder->next_zpos = drm_resources_get_min_zpos_for_crtc(resources, crtc_id); + builder->available_planes = drm_resources_get_possible_planes_for_crtc(resources, crtc_id); + return builder; + +fail_unref_drmdev: + drmdev_unref(builder->drmdev); + drm_resources_unref(builder->res); + free(builder); + return NULL; +} + +struct kms_req_builder *kms_req_builder_new_legacy(struct drmdev *drmdev, struct drm_resources *resources, uint32_t crtc_id) { + struct kms_req_builder *builder; + + ASSERT_NOT_NULL(resources); + assert(crtc_id != 0 && crtc_id != 0xFFFFFFFF); + + builder = calloc(1, sizeof *builder); + if (builder == NULL) { + return NULL; + } + + builder->n_refs = REFCOUNT_INIT_1; + builder->res = drm_resources_ref(resources); + builder->use_atomic = false; + builder->drmdev = drmdev_ref(drmdev); + builder->connector = NULL; + builder->n_layers = 0; + builder->has_mode = false; + builder->unset_mode = false; + + builder->crtc = drm_resources_get_crtc(resources, crtc_id); + if (builder->crtc == NULL) { + LOG_ERROR("Invalid CRTC: %" PRIu32 "\n", crtc_id); + goto fail_unref_drmdev; + } + + builder->req = NULL; + builder->next_zpos = drm_resources_get_min_zpos_for_crtc(resources, crtc_id); + builder->available_planes = drm_resources_get_possible_planes_for_crtc(resources, crtc_id); + return builder; + +fail_unref_drmdev: + drmdev_unref(builder->drmdev); + drm_resources_unref(builder->res); + free(builder); + return NULL; +} + +static void kms_req_builder_destroy(struct kms_req_builder *builder) { + /// TODO: Is this complete? + for (int i = 0; i < builder->n_layers; i++) { + if (builder->layers[i].release_callback != NULL) { + builder->layers[i].release_callback(builder->layers[i].release_callback_userdata); + } + } + if (builder->req != NULL) { + drmModeAtomicFree(builder->req); + builder->req = NULL; + } + drm_resources_unref(builder->res); + free(builder); +} + +DEFINE_REF_OPS(kms_req_builder, n_refs) + +struct drmdev *kms_req_builder_get_drmdev(struct kms_req_builder *builder) { + ASSERT_NOT_NULL(builder); + return drmdev_ref(builder->drmdev); +} + +struct drmdev *kms_req_builder_peek_drmdev(struct kms_req_builder *builder) { + ASSERT_NOT_NULL(builder); + return builder->drmdev; +} + +struct drm_resources *kms_req_builder_get_resources(struct kms_req_builder *builder) { + ASSERT_NOT_NULL(builder); + return drm_resources_ref(builder->res); +} + +struct drm_resources *kms_req_builder_peek_resources(struct kms_req_builder *builder) { + ASSERT_NOT_NULL(builder); + return builder->res; +} + +struct drm_crtc *kms_req_builder_get_crtc(struct kms_req_builder *builder) { + return builder->crtc; +} + +bool kms_req_builder_prefer_next_layer_opaque(struct kms_req_builder *builder) { + ASSERT_NOT_NULL(builder); + return builder->n_layers == 0; +} + +int kms_req_builder_set_mode(struct kms_req_builder *builder, const drmModeModeInfo *mode) { + ASSERT_NOT_NULL(builder); + ASSERT_NOT_NULL(mode); + builder->has_mode = true; + builder->mode = *mode; + return 0; +} + +int kms_req_builder_unset_mode(struct kms_req_builder *builder) { + ASSERT_NOT_NULL(builder); + assert(!builder->has_mode); + builder->unset_mode = true; + return 0; +} + +int kms_req_builder_set_connector(struct kms_req_builder *builder, uint32_t connector_id) { + struct drm_connector *conn; + + ASSERT_NOT_NULL(builder); + assert(DRM_ID_IS_VALID(connector_id)); + + conn = drm_resources_get_connector(builder->res, connector_id); + if (conn == NULL) { + LOG_ERROR("Could not find connector with id %" PRIu32 "\n", connector_id); + return EINVAL; + } + + builder->connector = conn; + return 0; +} + +int kms_req_builder_push_fb_layer( + struct kms_req_builder *builder, + const struct kms_fb_layer *layer, + void_callback_t release_callback, + kmsreq_syncfile_cb_t deferred_release_callback, + void *userdata +) { + struct drm_plane *plane; + int64_t zpos; + bool has_zpos; + bool close_in_fence_fd_after; + int ok, index; + + ASSERT_NOT_NULL(builder); + ASSERT_NOT_NULL(layer); + ASSERT_NOT_NULL(release_callback); + ASSERT_EQUALS_MSG(deferred_release_callback, NULL, "deferred release callbacks are not supported right now."); + + if (!builder->use_atomic && builder->n_layers > 1) { + // Multi-plane commits are un-vsynced without atomic modesetting. + // And when atomic modesetting is supported but we're still using legacy, + // every individual plane commit is vsynced. + LOG_DEBUG("Can't do multi-plane commits when using legacy modesetting.\n"); + return EINVAL; + } + + close_in_fence_fd_after = false; + if (!builder->use_atomic && 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; + + // If we should prefer a cursor plane, try to find one first. + plane = NULL; + if (layer->prefer_cursor) { + plane = allocate_plane( + // clang-format off + builder, + /* allow_primary */ false, + /* allow_overlay */ false, + /* allow_cursor */ true, + /* format */ layer->format, + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ false, 0, 0, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ false, 0 + // clang-format on + ); + if (plane == NULL) { + LOG_DEBUG("Couldn't find a fitting cursor plane.\n"); + } + } + + /// TODO: Not sure we can use crtc_x, crtc_y, etc with primary planes + if (plane == NULL && 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( + // clang-format off + builder, + /* allow_primary */ true, + /* allow_overlay */ false, + /* allow_cursor */ false, + /* format */ layer->format, + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ false, 0, 0, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ false, 0 + // clang-format on + ); + + 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( + // clang-format off + builder, + /* allow_primary */ true, + /* allow_overlay */ false, + /* allow_cursor */ false, + /* format */ pixfmt_opaque(layer->format), + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ false, 0, 0, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ false, 0 + // clang-format on + ); + } + } else if (plane == NULL) { + // First try to find an overlay plane with a higher zpos. + plane = allocate_plane( + // clang-format off + builder, + /* allow_primary */ false, + /* allow_overlay */ true, + /* allow_cursor */ false, + /* format */ layer->format, + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ true, builder->next_zpos, INT64_MAX, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ false, 0 + // clang-format on + ); + + // 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( + // clang-format off + builder, + /* allow_primary */ false, + /* allow_overlay */ true, + /* allow_cursor */ false, + /* format */ layer->format, + /* modifier */ layer->has_modifier, layer->modifier, + /* zpos */ false, 0, 0, + /* rotation */ layer->has_rotation, layer->rotation, + /* id_range */ true, builder->layers[index - 1].plane_id + 1 + // clang-format on + ); + } + } + + if (plane == NULL) { + LOG_ERROR("Could not find a suitable unused DRM plane for pushing the framebuffer.\n"); + return EIO; + } + + // Now that we have a plane, use the minimum zpos + // that's both higher than the last layers zpos and + // also supported by the plane. + // This will also work for planes with hardcoded zpos. + has_zpos = plane->has_zpos; + if (has_zpos) { + zpos = builder->next_zpos; + if (plane->min_zpos > zpos) { + zpos = plane->min_zpos; + } + } else { + // just to silence an uninitialized use warning below. + zpos = 0; + } + + if (!builder->use_atomic) { + } 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); + } + + if (layer->has_rotation && plane->has_rotation && !plane->has_hardcoded_rotation) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.rotation, layer->rotation.u64); + } + + if (index == 0) { + if (plane->has_alpha) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.alpha, DRM_BLEND_ALPHA_OPAQUE); + } + + if (plane->has_blend_mode && plane->supported_blend_modes[DRM_BLEND_MODE_NONE]) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.pixel_blend_mode, DRM_BLEND_MODE_NONE); + } + } + } + + // 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; + } + } + + /// 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 (has_zpos) { + builder->next_zpos = zpos + 1; + } + builder->layers[index].layer = *layer; + builder->layers[index].plane_id = plane->id; + builder->layers[index].plane = plane; + builder->layers[index].set_zpos = has_zpos; + builder->layers[index].zpos = zpos; + builder->layers[index].set_rotation = layer->has_rotation; + builder->layers[index].rotation = layer->rotation; + builder->layers[index].release_callback = release_callback; + builder->layers[index].deferred_release_callback = deferred_release_callback; + builder->layers[index].release_callback_userdata = userdata; + return 0; + +fail_release_plane: + release_plane(builder, plane->id); + return ok; +} + +int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder, int64_t *zpos_out) { + ASSERT_NOT_NULL(builder); + ASSERT_NOT_NULL(zpos_out); + *zpos_out = builder->next_zpos++; + return 0; +} + +struct kms_req *kms_req_builder_build(struct kms_req_builder *builder) { + return (struct kms_req *) kms_req_builder_ref(builder); +} + +UNUSED struct kms_req *kms_req_ref(struct kms_req *req) { + return (struct kms_req *) kms_req_builder_ref((struct kms_req_builder *) req); +} + +UNUSED void kms_req_unref(struct kms_req *req) { + kms_req_builder_unref((struct kms_req_builder *) req); +} + +UNUSED void kms_req_unrefp(struct kms_req **req) { + kms_req_builder_unrefp((struct kms_req_builder **) req); +} + +UNUSED void kms_req_swap_ptrs(struct kms_req **oldp, struct kms_req *new) { + kms_req_builder_swap_ptrs((struct kms_req_builder **) oldp, (struct kms_req_builder *) new); +} + +UNUSED static bool drm_plane_is_active(struct drm_plane *plane) { + return plane->committed_state.fb_id != 0 && plane->committed_state.crtc_id != 0; +} + +static void on_kms_req_scanout(uint64_t vblank_ns, void *userdata) { + struct kms_req_builder *b; + + ASSERT_NOT_NULL(userdata); + b = (struct kms_req_builder *) userdata; + + if (b->scanout_cb != NULL) { + b->scanout_cb(vblank_ns, b->scanout_cb_userdata); + } +} + +static void on_kms_req_release(void *userdata) { + struct kms_req_builder *b; + + ASSERT_NOT_NULL(userdata); + b = (struct kms_req_builder *) userdata; + + if (b->release_cb != NULL) { + b->release_cb(b->release_cb_userdata); + } + + ASSERT(refcount_is_one(&b->n_refs)); + kms_req_builder_unref(b); +} + +static int kms_req_commit_common( + struct kms_req *req, + struct drmdev *drmdev, + bool blocking, + kmsreq_scanout_cb_t scanout_cb, + void *scanout_cb_userdata, + uint64_t *vblank_ns_out, + void_callback_t release_cb, + void *release_cb_userdata +) { + struct kms_req_builder *builder; + int ok; + + ASSERT_NOT_NULL(req); + builder = (struct kms_req_builder *) req; + + if (!drmdev_can_commit(drmdev)) { + LOG_ERROR("Commit requested, but drmdev is paused right now.\n"); + return EBUSY; + } + + // only change the mode if the new mode differs from the old one + + /// TOOD: If this is not a standard mode reported by connector/CRTC, + /// is there a way to verify if it is valid? (maybe use DRM_MODE_ATOMIC_TEST) + + // this could be a single expression but this way you see a bit better what's going on. + // We need to upload the new mode blob if: + // - we have a new mode + // - and: we don't have an old mode + // - or: the old mode differs from the new mode + bool upload_mode = false; + if (builder->has_mode) { + if (!builder->crtc->committed_state.has_mode) { + upload_mode = true; + } else if (memcmp(&builder->crtc->committed_state.mode, &builder->mode, sizeof(drmModeModeInfo)) != 0) { + upload_mode = true; + } + } + + bool update_mode = false; + struct drm_blob *mode_blob = NULL; + if (upload_mode) { + update_mode = true; + mode_blob = drm_blob_new_mode(drmdev_get_modesetting_fd(drmdev), &builder->mode, true); + if (mode_blob == NULL) { + return EIO; + } + } else if (builder->unset_mode) { + update_mode = true; + mode_blob = NULL; + } + + if (builder->use_atomic) { + /// 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? + + // For every plane that was previously active with our CRTC, but is not used by us anymore, + // disable it. + for (unsigned int i = 0; i < builder->res->n_planes; i++) { + if (!(builder->available_planes & BIT(i))) { + continue; + } + + struct drm_plane *plane = builder->res->planes + i; + if (drm_plane_is_active(plane) && plane->committed_state.crtc_id == builder->crtc->id) { + drmModeAtomicAddProperty(builder->req, plane->id, plane->ids.crtc_id, 0); + drmModeAtomicAddProperty(builder->req, plane->id, plane->ids.fb_id, 0); + } + } + + if (builder->connector != NULL) { + // add the CRTC_ID property if that was explicitly set + drmModeAtomicAddProperty(builder->req, builder->connector->id, builder->connector->ids.crtc_id, builder->crtc->id); + } + + if (update_mode) { + if (mode_blob != NULL) { + drmModeAtomicAddProperty(builder->req, builder->crtc->id, builder->crtc->ids.mode_id, drm_blob_get_id(mode_blob)); + } else { + drmModeAtomicAddProperty(builder->req, builder->crtc->id, builder->crtc->ids.mode_id, 0); + } + } + + builder->scanout_cb = scanout_cb; + builder->scanout_cb_userdata = scanout_cb_userdata; + builder->release_cb = release_cb; + builder->release_cb_userdata = release_cb_userdata; + + if (blocking) { + ok = drmdev_commit_atomic_sync( + drmdev, + builder->req, + update_mode, + builder->crtc->id, + on_kms_req_release, + kms_req_ref(req), + vblank_ns_out + ); + } else { + ok = drmdev_commit_atomic_async( + drmdev, + builder->req, + update_mode, + builder->crtc->id, + on_kms_req_scanout, + on_kms_req_release, + kms_req_ref(req) + ); + } + + if (ok != 0) { + ok = errno; + goto fail_unref_builder; + } + } else { + ASSERT_EQUALS(builder->layers[0].layer.dst_x, 0); + ASSERT_EQUALS(builder->layers[0].layer.dst_y, 0); + ASSERT_EQUALS(builder->layers[0].layer.dst_w, builder->mode.hdisplay); + ASSERT_EQUALS(builder->layers[0].layer.dst_h, builder->mode.vdisplay); + + /// TODO: Do we really need to assert this? + ASSERT_NOT_NULL(builder->connector); + + bool needs_set_crtc = update_mode; + + // check if the plane pixel format changed. + // that needs a drmModeSetCrtc for legacy KMS as well. + // get the current, committed fb for the plane, check if we have info + // for it (we can't use drmModeGetFB2 since that's not present on debian buster) + // and if we're not absolutely sure the formats match, set needs_set_crtc + // too. + if (!needs_set_crtc) { + struct kms_req_layer *layer = builder->layers + 0; + struct drm_plane *plane = layer->plane; + ASSERT_NOT_NULL(plane); + + if (plane->committed_state.has_format && plane->committed_state.format == layer->layer.format) { + needs_set_crtc = false; + } else { + needs_set_crtc = true; + } + } + + /// TODO: Handle {src,dst}_{x,y,w,h} here + /// TODO: Handle setting other properties as well + + /// TODO: Implement + (void) needs_set_crtc; + UNIMPLEMENTED(); + + // if (needs_set_crtc) { + // /// TODO: Fetch new connector or current connector here since we seem to need it for drmModeSetCrtc + // ok = drmModeSetCrtc( + // , + // builder->crtc->id, + // builder->layers[0].layer.drm_fb_id, + // 0, + // 0, + // (uint32_t[1]){ builder->connector->id }, + // 1, + // builder->unset_mode ? NULL : &builder->mode + // ); + // if (ok != 0) { + // ok = errno; + // LOG_ERROR("Could not commit display update. drmModeSetCrtc: %s\n", strerror(ok)); + // goto fail_maybe_destroy_mode_blob; + // } + + // internally_blocking = true; + // } else { + // ok = drmModePageFlip( + // builder->drmdev->master_fd, + // builder->crtc->id, + // builder->layers[0].layer.drm_fb_id, + // DRM_MODE_PAGE_FLIP_EVENT, + // kms_req_builder_ref(builder) + // ); + // if (ok != 0) { + // ok = errno; + // LOG_ERROR("Could not commit display update. drmModePageFlip: %s\n", strerror(ok)); + // goto fail_unref_builder; + // } + // } + + // This should also be ensured by kms_req_builder_push_fb_layer + ASSERT_MSG( + builder->use_atomic || builder->n_layers <= 1, + "There can be at most one framebuffer layer when using legacy modesetting." + ); + + /// TODO: Call drmModeSetPlane for all other layers + /// TODO: Assert here + } + + // update struct drm_plane.committed_state for all planes + for (int i = 0; i < builder->n_layers; i++) { + struct drm_plane *plane = builder->layers[i].plane; + struct kms_req_layer *layer = builder->layers + i; + + plane->committed_state.crtc_id = builder->crtc->id; + plane->committed_state.fb_id = layer->layer.drm_fb_id; + plane->committed_state.src_x = layer->layer.src_x; + plane->committed_state.src_y = layer->layer.src_y; + plane->committed_state.src_w = layer->layer.src_w; + plane->committed_state.src_h = layer->layer.src_h; + plane->committed_state.crtc_x = layer->layer.dst_x; + plane->committed_state.crtc_y = layer->layer.dst_y; + plane->committed_state.crtc_w = layer->layer.dst_w; + plane->committed_state.crtc_h = layer->layer.dst_h; + + if (builder->layers[i].set_zpos) { + plane->committed_state.zpos = layer->zpos; + } + if (builder->layers[i].set_rotation) { + plane->committed_state.rotation = layer->rotation; + } + + plane->committed_state.has_format = true; + plane->committed_state.format = layer->layer.format; + + // builder->layers[i].plane->committed_state.alpha = layer->alpha; + // builder->layers[i].plane->committed_state.blend_mode = builder->layers[i].layer.blend_mode; + } + + // update struct drm_crtc.committed_state + if (update_mode) { + // destroy the old mode blob + if (builder->crtc->committed_state.mode_blob != NULL) { + drm_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; + } + } + + // update struct drm_connector.committed_state + builder->connector->committed_state.crtc_id = builder->crtc->id; + // builder->connector->committed_state.encoder_id = 0; + + return 0; + +fail_unref_builder: + kms_req_builder_unref(builder); + + if (mode_blob != NULL) + drm_blob_destroy(mode_blob); + + return ok; +} + +void set_vblank_ns(uint64_t vblank_ns, void *userdata) { + uint64_t *vblank_ns_out; + + ASSERT_NOT_NULL(userdata); + vblank_ns_out = userdata; + + *vblank_ns_out = vblank_ns; +} + +int kms_req_commit_blocking(struct kms_req *req, struct drmdev *drmdev, uint64_t *vblank_ns_out) { + int ok; + + ok = kms_req_commit_common(req, drmdev, true, NULL, NULL, vblank_ns_out, NULL, NULL); + if (ok != 0) { + return ok; + } + + return 0; +} + +int kms_req_commit_nonblocking( + struct kms_req *req, + struct drmdev *drmdev, + kmsreq_scanout_cb_t scanout_cb, + void *userdata, + void_callback_t release_cb +) { + return kms_req_commit_common(req, drmdev, false, scanout_cb, userdata, NULL, release_cb, userdata); +} diff --git a/src/kms/req_builder.h b/src/kms/req_builder.h new file mode 100644 index 00000000..2610a4df --- /dev/null +++ b/src/kms/req_builder.h @@ -0,0 +1,211 @@ +#ifndef _FLUTTERPI_SRC_MODESETTING_REQ_BUILDER_H_ +#define _FLUTTERPI_SRC_MODESETTING_REQ_BUILDER_H_ + +#include +#include + +#include "resources.h" + +struct kms_fb_layer { + uint32_t drm_fb_id; + enum pixfmt format; + bool has_modifier; + uint64_t modifier; + + int32_t src_x, src_y, src_w, src_h; + int32_t dst_x, dst_y, dst_w, dst_h; + + bool has_rotation; + drm_plane_transform_t rotation; + + bool has_in_fence_fd; + int in_fence_fd; + + bool prefer_cursor; +}; + +typedef void (*kmsreq_scanout_cb_t)(uint64_t vblank_ns, void *userdata); + +typedef void (*kmsreq_syncfile_cb_t)(void *userdata, int syncfile_fd); + +struct drmdev; +struct kms_req_builder; + +struct kms_req_builder *kms_req_builder_new_atomic(struct drmdev *drmdev, struct drm_resources *resources, uint32_t crtc_id); + +struct kms_req_builder *kms_req_builder_new_legacy(struct drmdev *drmdev, struct drm_resources *resources, uint32_t crtc_id); + +DECLARE_REF_OPS(kms_req_builder) + +struct drmdev *kms_req_builder_get_drmdev(struct kms_req_builder *builder); + +struct drmdev *kms_req_builder_peek_drmdev(struct kms_req_builder *builder); + +struct drm_resources *kms_req_builder_get_resources(struct kms_req_builder *builder); + +struct drm_resources *kms_req_builder_peek_resources(struct kms_req_builder *builder); + +/** + * @brief Gets the CRTC associated with this KMS request builder. + * + * @param builder The KMS request builder. + * @returns The CRTC associated with this KMS request builder. + */ +struct drm_crtc *kms_req_builder_get_crtc(struct kms_req_builder *builder); + +/** + * @brief Adds a property to the KMS request that will set the given video mode + * on this CRTC on commit, regardless of whether the currently committed output + * mode is the same. + * + * @param builder The KMS request builder. + * @param mode The output mode to set (on @ref kms_req_commit) + * @returns Zero if successful, positive errno-style error on failure. + */ +int kms_req_builder_set_mode(struct kms_req_builder *builder, const drmModeModeInfo *mode); + +/** + * @brief Adds a property to the KMS request that will unset the configured + * output mode for this CRTC on commit, regardless of whether the currently + * committed output mdoe is already unset. + * + * @param builder The KMS request builder. + * @returns Zero if successful, positive errno-style error on failure. + */ +int kms_req_builder_unset_mode(struct kms_req_builder *builder); + +/** + * @brief Adds a property to the KMS request that will change the connector + * that this CRTC is displaying content on to @param connector_id. + * + * @param builder The KMS request builder. + * @param connector_id The connector that this CRTC should display contents on. + * @returns Zero if successful, EINVAL if the @param connector_id is invalid. + */ +int kms_req_builder_set_connector(struct kms_req_builder *builder, uint32_t connector_id); + +/** + * @brief True if the next layer pushed using @ref kms_req_builder_push_fb_layer + * should be opaque, i.e. use a framebuffer which has a pixel format that has no + * alpha channel. + * + * This is true for the bottom-most layer. There are some display controllers + * that don't support non-opaque pixel formats for the bottom-most (primary) + * plane. So ignoring this might lead to an EINVAL on commit. + * + * @param builder The KMS request builder. + * @returns True if the next layer should preferably be opaque, false if there's + * no preference. + */ +bool kms_req_builder_prefer_next_layer_opaque(struct kms_req_builder *builder); + +/** + * @brief Adds a new framebuffer (display) layer on top of the last layer. + * + * If this is the first layer, the framebuffer should cover the entire screen + * (CRTC). + * + * To allow the use of explicit fencing, specify an in_fence_fd in @param layer + * and a @param deferred_release_callback. + * + * If explicit fencing is supported: + * - the in_fence_fd should be a DRM syncobj fd that signals + * when the GPU has finished rendering to the framebuffer and is ready + * to be scanned out. + * - @param deferred_release_callback will be called + * with a DRM syncobj fd that is signaled once the framebuffer is no longer + * being displayed on screen (and can be rendered into again) + * + * If explicit fencing is not supported: + * - the in_fence_fd in @param layer will be closed by this procedure. + * - @param deferred_release_callback will NOT be called and + * @param release_callback will be called instead. + * + * Explicit fencing is supported: When atomic modesetting is being used and + * the driver supports it. (Driver has IN_FENCE_FD plane and OUT_FENCE_PTR crtc + * properties) + * + * @param builder The KMS request builder. + * @param layer The exact details (src pos, output pos, rotation, + * framebuffer) of the layer that should be shown on + * screen. + * @param release_callback Called when the framebuffer of this layer is no + * longer being shown on screen. This is called with the + * drmdev locked, so make sure to use _locked variants + * of any drmdev calls. + * @param deferred_release_callback (Unimplemented right now) If this is present, + * this callback might be called instead of + * @param release_callback. + * This is called with a DRM syncobj fd that is + * signaled when the framebuffer is no longer + * shown on screen. + * Legacy DRM modesetting does not support + * explicit fencing, in which case + * @param release_callback will be called + * instead. + * @param userdata Userdata pointer that's passed to the release_callback or + * deferred_release_callback as-is. + * @returns Zero on success, otherwise: + * - EINVAL: if attempting to push a second framebuffer layer, if + * driver supports atomic modesetting but legacy modesetting is + * being used. + * - EIO: if no DRM plane could be found that supports displaying + * this framebuffer layer. Either the pixel format is not + * supported, the modifier, the rotation or the drm device + * doesn't have enough planes. + * - The error returned by @ref close if closing the in_fence_fd + * fails. + */ +int kms_req_builder_push_fb_layer( + struct kms_req_builder *builder, + const struct kms_fb_layer *layer, + void_callback_t release_callback, + kmsreq_syncfile_cb_t deferred_release_callback, + void *userdata +); + +/** + * @brief Push a "fake" layer that just keeps one zpos free, incase something + * other than KMS wants to display contents there. (e.g. omxplayer) + * + * @param builder The KMS request builder. + * @param zpos_out Filled with the zpos that won't be occupied by the request + * builder. + * @returns Zero. + */ +int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder, int64_t *zpos_out); + +/** + * @brief A KMS request (atomic or legacy modesetting) that can be committed to + * change the state of a single CRTC. + * + * Only way to construct this is by building a KMS request using + * @ref kms_req_builder and then calling @ref kms_req_builder_build. + */ +struct kms_req; + +DECLARE_REF_OPS(kms_req) + +/** + * @brief Build the KMS request builder into an actual, immutable KMS request + * that can be committed. Internally this doesn't do much at all. + * + * @param builder The KMS request builder that should be built. + * @returns KMS request that can be committed using @ref kms_req_commit_blocking + * or @ref kms_req_commit_nonblocking. + * The returned KMS request has refcount 1. Unref using + * @ref kms_req_unref after usage. + */ +struct kms_req *kms_req_builder_build(struct kms_req_builder *builder); + +int kms_req_commit_blocking(struct kms_req *req, struct drmdev *drmdev, uint64_t *vblank_ns_out); + +int kms_req_commit_nonblocking( + struct kms_req *req, + struct drmdev *drmdev, + kmsreq_scanout_cb_t scanout_cb, + void *userdata, + void_callback_t release_cb +); + +#endif // _FLUTTERPI_SRC_MODESETTING_REQ_BUILDER_H_ diff --git a/src/kms/resources.c b/src/kms/resources.c new file mode 100644 index 00000000..a0b41edc --- /dev/null +++ b/src/kms/resources.c @@ -0,0 +1,1524 @@ +#include "resources.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "monitor.h" +#include "pixel_format.h" +#include "util/bitset.h" +#include "util/list.h" +#include "util/lock_ops.h" +#include "util/logging.h" +#include "util/macros.h" +#include "util/refcounting.h" + +struct _drmModeFB2; + +struct drm_mode_fb2 { + uint32_t fb_id; + uint32_t width, height; + uint32_t pixel_format; /* fourcc code from drm_fourcc.h */ + uint64_t modifier; /* applies to all buffers */ + uint32_t flags; + + /* per-plane GEM handle; may be duplicate entries for multiple planes */ + uint32_t handles[4]; + uint32_t pitches[4]; /* bytes */ + uint32_t offsets[4]; /* bytes */ +}; + +#ifdef HAVE_FUNC_ATTRIBUTE_WEAK +extern struct _drmModeFB2 *drmModeGetFB2(int fd, uint32_t bufferId) __attribute__((weak)); +extern void drmModeFreeFB2(struct _drmModeFB2 *ptr) __attribute__((weak)); + #define HAVE_WEAK_DRM_MODE_GET_FB2 +#endif + +#ifdef HAVE_WEAK_DRM_MODE_GET_FB2 +static bool drm_fb_get_format(int drm_fd, uint32_t fb_id, enum pixfmt *format_out) { + struct drm_mode_fb2 *fb; + + // drmModeGetFB2 might not be present. + // If __attribute__((weak)) is supported by the compiler, we redefine it as + // weak above. + // If we don't have weak, we can't check for it here. + if (drmModeGetFB2 && drmModeFreeFB2) { + fb = (struct drm_mode_fb2 *) drmModeGetFB2(drm_fd, fb_id); + if (fb == NULL) { + return false; + } + + for (int i = 0; i < PIXFMT_COUNT; i++) { + if (get_pixfmt_info(i)->drm_format == fb->pixel_format) { + *format_out = i; + drmModeFreeFB2((struct _drmModeFB2 *) fb); + return true; + } + } + + drmModeFreeFB2((struct _drmModeFB2 *) fb); + return false; + } else { + return false; + } +} +#else +static bool drm_fb_get_format(int drm_fd, uint32_t fb_id, enum pixfmt *format_out) { + (void) drm_fd; + (void) fb_id; + (void) format_out; + return false; +} +#endif + +static size_t sizeof_drm_format_modifier_blob(struct drm_format_modifier_blob *blob) { + return MAX3( + sizeof(struct drm_format_modifier_blob), + blob->formats_offset + sizeof(uint32_t) * blob->count_formats, + blob->modifiers_offset + sizeof(struct drm_format_modifier) * blob->count_modifiers + ); +} + +static int drm_connector_init(int drm_fd, uint32_t connector_id, struct drm_connector *out) { + memset(out, 0, sizeof(*out)); + + drm_connector_prop_ids_init(&out->ids); + + { + drmModeConnector *connector = drmModeGetConnector(drm_fd, connector_id); + if (connector == NULL) { + return ENOMEM; + } + + out->id = connector->connector_id; + out->type = connector->connector_type; + out->type_id = connector->connector_type_id; + out->n_encoders = connector->count_encoders; + + assert(connector->count_encoders <= 32); + memcpy(out->encoders, connector->encoders, connector->count_encoders * sizeof(uint32_t)); + + out->variable_state.connection_state = (enum drm_connection_state) connector->connection; + out->variable_state.subpixel_layout = (enum drm_subpixel_layout) connector->subpixel; + out->variable_state.width_mm = connector->mmWidth; + out->variable_state.height_mm = connector->mmHeight; + + assert((connector->modes == NULL) == (connector->count_modes == 0)); + if (connector->modes != NULL) { + out->variable_state.n_modes = connector->count_modes; + out->variable_state.modes = memdup(connector->modes, connector->count_modes * sizeof(*connector->modes)); + if (out->variable_state.modes == NULL) { + drmModeFreeConnector(connector); + return ENOMEM; + } + } + + out->committed_state.encoder_id = connector->encoder_id; + + drmModeFreeConnector(connector); + } + + { + drmModeObjectProperties *props = drmModeObjectGetProperties(drm_fd, connector_id, DRM_MODE_OBJECT_CONNECTOR); + if (props == NULL) { + return ENOMEM; + } + + out->committed_state.crtc_id = DRM_ID_NONE; + for (uint32_t i = 0; i < props->count_props; i++) { + uint32_t id = props->props[i]; + + drmModePropertyRes *prop_info = drmModeGetProperty(drm_fd, id); + if (prop_info == NULL) { + drmModeFreeObjectProperties(props); + return ENOMEM; + } + +#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ + if (strncmp(prop_info->name, _name_str, DRM_PROP_NAME_LEN) == 0) { \ + out->ids._name = prop_info->prop_id; \ + } else + + DRM_CONNECTOR_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { + // this is the trailing else case + LOG_DEBUG("Unknown DRM connector property: %s\n", prop_info->name); + } + +#undef CHECK_ASSIGN_PROPERTY_ID + + if (id == out->ids.crtc_id) { + out->committed_state.crtc_id = props->prop_values[i]; + } + + drmModeFreeProperty(prop_info); + } + + drmModeFreeObjectProperties(props); + } + + return 0; +} + +static void drm_connector_fini(struct drm_connector *connector) { + free(connector->variable_state.modes); +} + +static int drm_connector_copy(struct drm_connector *dst, const struct drm_connector *src) { + *dst = *src; + + if (src->variable_state.modes != NULL) { + dst->variable_state.modes = memdup(src->variable_state.modes, src->variable_state.n_modes * sizeof(*src->variable_state.modes)); + if (dst->variable_state.modes == NULL) { + return ENOMEM; + } + } + + return 0; +} + +static int drm_connector_update(struct drm_connector *connector, int drm_fd, bool have_property_id, uint32_t property_id) { + uint32_t connector_id = connector->id; + + (void) have_property_id; + (void) property_id; + + // for now, just reinit the whole connector. + drm_connector_fini(connector); + return drm_connector_init(drm_fd, connector_id, connector); +} + +static int drm_encoder_init(int drm_fd, uint32_t encoder_id, struct drm_encoder *out) { + drmModeEncoder *encoder = drmModeGetEncoder(drm_fd, encoder_id); + if (encoder == NULL) { + return errno ? errno : ENOMEM; + } + + out->id = encoder->encoder_id; + out->type = encoder->encoder_type; + if (out->type > DRM_ENCODER_TYPE_MAX) { + out->type = DRM_ENCODER_TYPE_NONE; + } + + out->possible_crtcs = encoder->possible_crtcs; + out->possible_clones = encoder->possible_clones; + + out->variable_state.crtc_id = encoder->crtc_id; + + drmModeFreeEncoder(encoder); + return 0; +} + +static int drm_encoder_copy(struct drm_encoder *dst, const struct drm_encoder *src) { + *dst = *src; + return 0; +} + +static void drm_encoder_fini(struct drm_encoder *encoder) { + (void) encoder; +} + +static int drm_crtc_init(int drm_fd, int crtc_index, uint32_t crtc_id, struct drm_crtc *out) { + memset(out, 0, sizeof(*out)); + + drm_crtc_prop_ids_init(&out->ids); + + { + drmModeCrtc *crtc = drmModeGetCrtc(drm_fd, crtc_id); + if (crtc == NULL) { + return errno ? errno : ENOMEM; + } + + ASSERT(0 <= crtc_index && crtc_index < 32); + + out->id = crtc->crtc_id; + out->index = (uint8_t) crtc_index; + out->bitmask = 1u << crtc_index; + out->committed_state.has_mode = crtc->mode_valid; + out->committed_state.mode = crtc->mode; + out->committed_state.mode_blob = NULL; + + drmModeFreeCrtc(crtc); + } + + { + drmModeObjectProperties *props = drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC); + if (props == NULL) { + return errno ? errno : ENOMEM; + } + + for (int i = 0; i < props->count_props; i++) { + drmModePropertyRes *prop_info = drmModeGetProperty(drm_fd, props->props[i]); + if (prop_info == NULL) { + drmModeFreeObjectProperties(props); + return errno ? errno : ENOMEM; + } + +#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ + if (strncmp(prop_info->name, _name_str, ARRAY_SIZE(prop_info->name)) == 0) { \ + out->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); + } + + drmModeFreeObjectProperties(props); + } + + return 0; +} + +static int drm_crtc_copy(struct drm_crtc *dst, const struct drm_crtc *src) { + *dst = *src; + return 0; +} + +static void drm_crtc_fini(struct drm_crtc *crtc) { + (void) crtc; +} + +bool drm_resources_any_crtc_plane_supports_format(struct drm_resources *r, uint32_t crtc_id, enum pixfmt pixel_format) { + struct drm_crtc *crtc = drm_resources_get_crtc(r, crtc_id); + if (crtc == NULL) { + return false; + } + + drm_resources_for_each_plane(r, plane) { + if (!(plane->possible_crtcs & crtc->bitmask)) { + // Only query planes that are possible to connect to the CRTC we're using. + continue; + } + + if (plane->type != DRM_PRIMARY_PLANE && plane->type != DRM_OVERLAY_PLANE) { + // We explicitly only look for primary and overlay planes. + continue; + } + + if (drm_plane_supports_unmodified_format(plane, pixel_format)) { + return true; + } + } + + return false; +} + +static void drm_plane_init_rotation(drmModePropertyRes *info, uint64_t value, struct drm_plane *out) { + assert(out->has_rotation == false); + out->has_rotation = true; + + out->supported_rotations = PLANE_TRANSFORM_NONE; + assert(info->flags & DRM_MODE_PROP_BITMASK); + + for (int k = 0; k < info->count_enums; k++) { + out->supported_rotations.u32 |= 1 << info->enums[k].value; + } + + assert(PLANE_TRANSFORM_IS_VALID(out->supported_rotations)); + + if (info->flags & DRM_MODE_PROP_IMMUTABLE) { + out->has_hardcoded_rotation = true; + out->hardcoded_rotation.u64 = value; + } + + out->committed_state.rotation.u64 = value; +} + +static void drm_plane_init_zpos(drmModePropertyRes *info, uint64_t value, struct drm_plane *out) { + assert(out->has_zpos == false); + out->has_zpos = true; + + if (info->flags & DRM_MODE_PROP_SIGNED_RANGE) { + out->min_zpos = uint64_to_int64(info->values[0]); + out->max_zpos = uint64_to_int64(info->values[1]); + out->committed_state.zpos = uint64_to_int64(value); + assert(out->min_zpos <= out->max_zpos); + assert(out->min_zpos <= out->committed_state.zpos); + assert(out->committed_state.zpos <= out->max_zpos); + } else if (info->flags & DRM_MODE_PROP_RANGE) { + assert(info->values[0] <= INT64_MAX); + assert(info->values[1] <= INT64_MAX); + + out->min_zpos = info->values[0]; + out->max_zpos = info->values[1]; + out->committed_state.zpos = value; + assert(out->min_zpos <= out->max_zpos); + } else { + ASSERT_MSG(false, "Invalid property type for zpos property."); + } + + if (info->flags & DRM_MODE_PROP_IMMUTABLE) { + out->has_hardcoded_zpos = true; + assert(value <= INT64_MAX); + + out->hardcoded_zpos = value; + if (out->min_zpos != out->max_zpos) { + LOG_DEBUG( + "DRM plane minimum supported zpos does not equal maximum supported zpos, even though zpos is " + "immutable.\n" + ); + out->min_zpos = out->max_zpos = out->hardcoded_zpos; + } + } +} + +static int drm_plane_init_in_formats(int drm_fd, drmModePropertyRes *info, uint64_t value, struct drm_plane *out) { + drmModePropertyBlobRes *blob; + + (void) info; + + blob = drmModeGetPropertyBlob(drm_fd, value); + if (blob == NULL) { + return errno ? errno : ENOMEM; + } + + out->supports_modifiers = true; + out->supported_modified_formats_blob = memdup(blob->data, blob->length); + if (out->supported_modified_formats_blob == NULL) { + drmModeFreePropertyBlob(blob); + return ENOMEM; + } + + drmModeFreePropertyBlob(blob); + return 0; +} + +static void drm_plane_init_alpha(drmModePropertyRes *info, uint64_t value, struct drm_plane *out) { + out->has_alpha = true; + assert(info->flags == DRM_MODE_PROP_RANGE); + assert(info->values[0] == 0); + assert(info->values[1] == 0xFFFF); + assert(value <= 0xFFFF); + + out->committed_state.alpha = (uint16_t) value; +} + +static void drm_plane_init_blend_mode(drmModePropertyRes *info, uint64_t value, struct drm_plane *out) { + out->has_blend_mode = true; + assert(info->flags == DRM_MODE_PROP_ENUM); + + for (int i = 0; i < info->count_enums; i++) { + if (streq(info->enums[i].name, "None")) { + ASSERT_EQUALS(info->enums[i].value, DRM_BLEND_MODE_NONE); + out->supported_blend_modes[DRM_BLEND_MODE_NONE] = true; + } else if (streq(info->enums[i].name, "Pre-multiplied")) { + ASSERT_EQUALS(info->enums[i].value, DRM_BLEND_MODE_PREMULTIPLIED); + out->supported_blend_modes[DRM_BLEND_MODE_PREMULTIPLIED] = true; + } else if (streq(info->enums[i].name, "Coverage")) { + ASSERT_EQUALS(info->enums[i].value, DRM_BLEND_MODE_COVERAGE); + out->supported_blend_modes[DRM_BLEND_MODE_COVERAGE] = true; + } else { + LOG_DEBUG("Unknown KMS pixel blend mode: %s (value: %" PRIu64 ")\n", info->enums[i].name, (uint64_t) info->enums[i].value); + } + } + + out->committed_state.blend_mode = value; + assert(out->committed_state.blend_mode >= 0 && out->committed_state.blend_mode <= DRM_BLEND_MODE_MAX); + assert(out->supported_blend_modes[out->committed_state.blend_mode]); +} + +static int drm_plane_init(int drm_fd, uint32_t plane_id, struct drm_plane *out) { + bool has_type; + int ok; + + memset(out, 0, sizeof(*out)); + + drm_plane_prop_ids_init(&out->ids); + + { + drmModePlane *plane = drmModeGetPlane(drm_fd, plane_id); + if (plane == NULL) { + return errno ? errno : ENOMEM; + } + + out->id = plane->plane_id; + out->possible_crtcs = plane->possible_crtcs; + out->committed_state.fb_id = plane->fb_id; + out->committed_state.crtc_id = plane->crtc_id; + + for (int i = 0; i < plane->count_formats; i++) { + for (int j = 0; j < PIXFMT_COUNT; j++) { + if (get_pixfmt_info(j)->drm_format == plane->formats[i]) { + out->supported_formats[j] = true; + break; + } + } + } + + drmModeFreePlane(plane); + } + + drmModeObjectProperties *props = drmModeObjectGetProperties(drm_fd, plane_id, DRM_MODE_OBJECT_PLANE); + if (props == NULL) { + return errno ? errno : ENOMEM; + } + + has_type = false; + for (int j = 0; j < props->count_props; j++) { + uint32_t id = props->props[j]; + uint64_t value = props->prop_values[j]; + + drmModePropertyRes *info = drmModeGetProperty(drm_fd, id); + if (info == NULL) { + ok = errno ? errno : ENOMEM; + goto fail_maybe_free_supported_modified_formats_blob; + } + +#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ + if (strncmp(info->name, _name_str, ARRAY_SIZE(info->name)) == 0) { \ + out->ids._name = info->prop_id; \ + } else + + DRM_PLANE_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { + // do nothing + } + +#undef CHECK_ASSIGN_PROPERTY_ID + + if (id == out->ids.type) { + assert(has_type == false); + has_type = true; + + out->type = value; + } else if (id == out->ids.rotation) { + drm_plane_init_rotation(info, value, out); + } else if (id == out->ids.zpos) { + drm_plane_init_zpos(info, value, out); + } else if (id == out->ids.src_x) { + out->committed_state.src_x = value; + } else if (id == out->ids.src_y) { + out->committed_state.src_y = value; + } else if (id == out->ids.src_w) { + out->committed_state.src_w = value; + } else if (id == out->ids.src_h) { + out->committed_state.src_h = value; + } else if (id == out->ids.crtc_x) { + out->committed_state.crtc_x = value; + } else if (id == out->ids.crtc_y) { + out->committed_state.crtc_y = value; + } else if (id == out->ids.crtc_w) { + out->committed_state.crtc_w = value; + } else if (id == out->ids.crtc_h) { + out->committed_state.crtc_h = value; + } else if (id == out->ids.in_formats) { + ok = drm_plane_init_in_formats(drm_fd, info, value, out); + if (ok != 0) { + drmModeFreeProperty(info); + goto fail_maybe_free_supported_modified_formats_blob; + } + } else if (id == out->ids.alpha) { + drm_plane_init_alpha(info, value, out); + } else if (id == out->ids.pixel_blend_mode) { + drm_plane_init_blend_mode(info, value, out); + } + + drmModeFreeProperty(info); + } + + drmModeFreeObjectProperties(props); + + assert(has_type); + (void) has_type; + + out->committed_state.has_format = drm_fb_get_format(drm_fd, out->committed_state.fb_id, &out->committed_state.format); + return 0; + +fail_maybe_free_supported_modified_formats_blob: + if (out->supported_modified_formats_blob != NULL) + free(out->supported_modified_formats_blob); + + drmModeFreeObjectProperties(props); + return ok; +} + +static int drm_plane_copy(struct drm_plane *dst, const struct drm_plane *src) { + *dst = *src; + + if (src->supported_modified_formats_blob != NULL) { + /// TODO: Implement + dst->supported_modified_formats_blob = + memdup(src->supported_modified_formats_blob, sizeof_drm_format_modifier_blob(src->supported_modified_formats_blob)); + if (dst->supported_modified_formats_blob == NULL) { + return ENOMEM; + } + } + + return 0; +} + +static void drm_plane_fini(struct drm_plane *plane) { + if (plane->supported_modified_formats_blob != NULL) { + free(plane->supported_modified_formats_blob); + } +} + +void drm_plane_for_each_modified_format(struct drm_plane *plane, drm_plane_modified_format_callback_t callback, void *userdata) { + struct drm_format_modifier_blob *blob; + struct drm_format_modifier *modifiers; + uint32_t *formats; + + ASSERT_NOT_NULL(plane); + ASSERT_NOT_NULL(callback); + ASSERT(plane->supports_modifiers); + ASSERT_EQUALS(plane->supported_modified_formats_blob->version, FORMAT_BLOB_CURRENT); + + blob = plane->supported_modified_formats_blob; + + modifiers = (void *) (((char *) blob) + blob->modifiers_offset); + formats = (void *) (((char *) blob) + blob->formats_offset); + + int index = 0; + for (int i = 0; i < blob->count_modifiers; i++) { + for (int j = modifiers[i].offset; (j < blob->count_formats) && (j < modifiers[i].offset + 64); j++) { + bool is_format_bit_set = (modifiers[i].formats & (1ull << (j % 64))) != 0; + if (!is_format_bit_set) { + continue; + } + + if (has_pixfmt_for_drm_format(formats[j])) { + enum pixfmt format = get_pixfmt_for_drm_format(formats[j]); + + bool should_continue = callback(plane, index, format, modifiers[i].modifier, userdata); + if (!should_continue) { + goto exit; + } + + index++; + } + } + } + +exit: + return; +} + +static bool +check_modified_format_supported(UNUSED struct drm_plane *plane, UNUSED int index, enum pixfmt format, uint64_t modifier, void *userdata) { + struct { + enum pixfmt format; + uint64_t modifier; + bool found; + } *context = userdata; + + if (format == context->format && modifier == context->modifier) { + context->found = true; + return false; + } else { + return true; + } +} + +bool drm_plane_supports_modified_formats(struct drm_plane *plane) { + return plane->supports_modifiers; +} + +bool drm_plane_supports_modified_format(struct drm_plane *plane, enum pixfmt format, uint64_t modifier) { + if (!plane->supported_modified_formats_blob) { + // return false if we want a modified format but the plane doesn't support modified formats + return false; + } + + struct { + enum pixfmt format; + uint64_t modifier; + bool found; + } context = { + .format = format, + .modifier = modifier, + .found = false, + }; + + // Check if the requested format & modifier is supported. + drm_plane_for_each_modified_format(plane, check_modified_format_supported, &context); + + return context.found; +} + +bool drm_plane_supports_unmodified_format(struct drm_plane *plane, enum pixfmt format) { + // we don't want a modified format, return false if the format is not in the list + // of supported (unmodified) formats + return plane->supported_formats[format]; +} + +struct drm_resources *drm_resources_new(int drm_fd) { + struct drm_resources *r; + int ok; + + r = calloc(1, sizeof *r); + if (r == NULL) { + return NULL; + } + + r->n_refs = REFCOUNT_INIT_1; + + r->have_filter = false; + + drmModeRes *res = drmModeGetResources(drm_fd); + if (res == NULL) { + ok = errno ? errno : EINVAL; + LOG_ERROR("Could not get DRM device resources. drmModeGetResources: %s\n", strerror(ok)); + goto fail_free_r; + } + + r->min_width = res->min_width; + r->max_width = res->max_width; + r->min_height = res->min_height; + r->max_height = res->max_height; + + r->connectors = calloc(res->count_connectors, sizeof *(r->connectors)); + if (r->connectors == NULL) { + goto fail_free_res; + } + + r->n_connectors = 0; + for (int i = 0; i < res->count_connectors; i++) { + ok = drm_connector_init(drm_fd, res->connectors[i], r->connectors + i); + if (ok != 0) { + goto fail_free_connectors; + } + + r->n_connectors++; + } + + r->encoders = calloc(res->count_encoders, sizeof *(r->encoders)); + if (r->encoders == NULL) { + goto fail_free_connectors; + } + + r->n_encoders = 0; + for (int i = 0; i < res->count_encoders; i++) { + ok = drm_encoder_init(drm_fd, res->encoders[i], r->encoders + i); + if (ok != 0) { + goto fail_free_encoders; + } + + r->n_encoders++; + } + + r->crtcs = calloc(res->count_crtcs, sizeof *(r->crtcs)); + if (r->crtcs == NULL) { + goto fail_free_encoders; + } + + r->n_crtcs = 0; + for (int i = 0; i < res->count_crtcs; i++) { + ok = drm_crtc_init(drm_fd, i, res->crtcs[i], r->crtcs + i); + if (ok != 0) { + goto fail_free_crtcs; + } + + r->n_crtcs++; + } + + drmModeFreeResources(res); + res = NULL; + + drmModePlaneRes *plane_res = drmModeGetPlaneResources(drm_fd); + if (plane_res == NULL) { + ok = errno ? errno : EINVAL; + LOG_ERROR("Could not get DRM device planes resources. drmModeGetPlaneResources: %s\n", strerror(ok)); + goto fail_free_crtcs; + } + + r->planes = calloc(plane_res->count_planes, sizeof *(r->planes)); + if (r->planes == NULL) { + goto fail_free_plane_res; + } + + r->n_planes = 0; + for (int i = 0; i < plane_res->count_planes; i++) { + ok = drm_plane_init(drm_fd, plane_res->planes[i], r->planes + i); + if (ok != 0) { + goto fail_free_planes; + } + + r->n_planes++; + } + + drmModeFreePlaneResources(plane_res); + return r; + +fail_free_planes: + for (int i = 0; i < r->n_planes; i++) + drm_plane_fini(r->planes + i); + free(r->planes); + +fail_free_plane_res: + if (plane_res != NULL) { + drmModeFreePlaneResources(plane_res); + } + +fail_free_crtcs: + for (int i = 0; i < r->n_crtcs; i++) + drm_crtc_fini(r->crtcs + i); + free(r->crtcs); + +fail_free_encoders: + for (int i = 0; i < r->n_encoders; i++) + drm_encoder_fini(r->encoders + i); + free(r->encoders); + +fail_free_connectors: + for (int i = 0; i < r->n_connectors; i++) + drm_connector_fini(r->connectors + i); + free(r->connectors); + +fail_free_res: + if (res != NULL) { + drmModeFreeResources(res); + } + +fail_free_r: + free(r); + return NULL; +} + +struct drm_resources *drm_resources_new_filtered( + int drm_fd, + uint32_t connector_id, + uint32_t encoder_id, + uint32_t crtc_id, + size_t n_planes, + const uint32_t *plane_ids +) { + struct drm_resources *r; + int ok; + + r = calloc(1, sizeof *r); + if (r == NULL) { + return NULL; + } + + r->n_refs = REFCOUNT_INIT_1; + r->have_filter = true; + r->filter.connector_id = connector_id; + r->filter.encoder_id = encoder_id; + r->filter.crtc_id = crtc_id; + r->filter.n_planes = n_planes; + memcpy(r->filter.plane_ids, plane_ids, n_planes * sizeof *plane_ids); + + { + drmModeRes *res = drmModeGetResources(drm_fd); + if (res == NULL) { + ok = errno ? errno : EINVAL; + LOG_ERROR("Could not get DRM device resources. drmModeGetResources: %s\n", strerror(ok)); + goto fail_free_r; + } + + r->min_width = res->min_width; + r->max_width = res->max_width; + r->min_height = res->min_height; + r->max_height = res->max_height; + + drmModeFreeResources(res); + } + + r->connectors = calloc(1, sizeof *(r->connectors)); + if (r->connectors == NULL) { + goto fail_free_r; + } + + ok = drm_connector_init(drm_fd, r->filter.connector_id, r->connectors + 0); + if (ok == 0) { + r->n_connectors = 1; + } else { + r->n_connectors = 0; + } + + if (r->n_connectors == 0) { + free(r->connectors); + r->connectors = NULL; + } + + r->encoders = calloc(1, sizeof *(r->encoders)); + if (r->encoders == NULL) { + goto fail_free_connectors; + } + + ok = drm_encoder_init(drm_fd, r->filter.encoder_id, r->encoders + 0); + if (ok == 0) { + r->n_encoders = 1; + } else { + r->n_encoders = 0; + } + + if (r->n_encoders == 0) { + free(r->encoders); + r->encoders = NULL; + } + + r->crtcs = calloc(1, sizeof *(r->crtcs)); + if (r->crtcs == NULL) { + goto fail_free_encoders; + } + + /// TODO: Implement + UNIMPLEMENTED(); + + ok = drm_crtc_init(drm_fd, (TRAP(), 0), crtc_id, r->crtcs); + if (ok == 0) { + r->n_crtcs = 1; + } else { + r->n_crtcs = 0; + } + + if (r->n_crtcs == 0) { + free(r->crtcs); + r->crtcs = NULL; + } + + r->planes = calloc(r->filter.n_planes, sizeof *(r->planes)); + if (r->planes == NULL) { + goto fail_free_crtcs; + } + + r->n_planes = 0; + for (int i = 0; i < r->filter.n_planes; i++) { + assert(r->n_planes <= r->filter.n_planes); + + uint32_t plane_id = r->filter.plane_ids[i]; + struct drm_plane *plane = r->planes + r->n_planes; + + ok = drm_plane_init(drm_fd, plane_id, plane); + if (ok != 0) { + continue; + } + + r->n_planes++; + } + + if (r->n_planes == 0) { + free(r->planes); + r->planes = NULL; + } + + return 0; + +fail_free_crtcs: + for (int i = 0; i < r->n_crtcs; i++) { + drm_crtc_fini(r->crtcs + i); + } + + if (r->crtcs != NULL) { + free(r->crtcs); + } + +fail_free_encoders: + for (int i = 0; i < r->n_encoders; i++) { + drm_encoder_fini(r->encoders + i); + } + + if (r->encoders != NULL) { + free(r->encoders); + } + +fail_free_connectors: + for (int i = 0; i < r->n_connectors; i++) { + drm_connector_fini(r->connectors + i); + } + + if (r->connectors != NULL) { + free(r->connectors); + } + +fail_free_r: + free(r); + return NULL; +} + +struct drm_resources *drm_resources_dup_filtered( + struct drm_resources *res, + uint32_t connector_id, + uint32_t encoder_id, + uint32_t crtc_id, + size_t n_planes, + const uint32_t *plane_ids +) { + struct drm_resources *r; + int ok; + + ASSERT_NOT_NULL(res); + + r = calloc(1, sizeof *r); + if (r == NULL) { + return NULL; + } + + r->n_refs = REFCOUNT_INIT_1; + + r->have_filter = true; + r->filter.connector_id = connector_id; + r->filter.encoder_id = encoder_id; + r->filter.crtc_id = crtc_id; + r->filter.n_planes = n_planes; + memcpy(r->filter.plane_ids, plane_ids, n_planes * sizeof *plane_ids); + + r->min_width = res->min_width; + r->max_width = res->max_width; + r->min_height = res->min_height; + r->max_height = res->max_height; + + { + r->connectors = calloc(1, sizeof(struct drm_connector)); + if (r->connectors == NULL) { + goto fail_free_r; + } + + struct drm_connector *conn = drm_resources_get_connector(res, connector_id); + if (conn != NULL) { + drm_connector_copy(r->connectors, conn); + r->n_connectors = 1; + } else { + r->n_connectors = 0; + } + + if (r->n_connectors == 0) { + free(r->connectors); + r->connectors = NULL; + } + } + + { + r->encoders = calloc(1, sizeof(struct drm_encoder)); + if (r->encoders == NULL) { + goto fail_free_connectors; + } + + struct drm_encoder *enc = drm_resources_get_encoder(res, encoder_id); + if (enc != NULL) { + drm_encoder_copy(r->encoders, enc); + r->n_encoders = 1; + } else { + r->n_encoders = 0; + } + + if (r->n_encoders == 0) { + free(r->encoders); + r->encoders = NULL; + } + } + + { + r->crtcs = calloc(1, sizeof(struct drm_crtc)); + if (r->crtcs == NULL) { + goto fail_free_encoders; + } + + struct drm_crtc *crtc = drm_resources_get_crtc(res, crtc_id); + if (crtc != NULL) { + drm_crtc_copy(r->crtcs, crtc); + r->n_crtcs = 1; + } else { + r->n_crtcs = 0; + } + + if (r->n_crtcs == 0) { + free(r->crtcs); + r->crtcs = NULL; + } + } + + r->planes = calloc(r->filter.n_planes, sizeof *(r->planes)); + if (r->planes == NULL) { + goto fail_free_crtcs; + } + + r->n_planes = 0; + for (int i = 0; i < r->filter.n_planes; i++) { + assert(r->n_planes <= r->filter.n_planes); + + uint32_t plane_id = r->filter.plane_ids[i]; + struct drm_plane *dst_plane = r->planes + r->n_planes; + + struct drm_plane *src_plane = drm_resources_get_plane(res, plane_id); + if (src_plane == NULL) { + continue; + } + + ok = drm_plane_copy(dst_plane, src_plane); + if (ok != 0) { + for (int j = 0; j < r->n_planes; j++) { + drm_plane_fini(r->planes + j); + } + free(r->planes); + goto fail_free_crtcs; + } + + r->n_planes++; + } + + if (r->n_planes == 0) { + free(r->planes); + r->planes = NULL; + } + + return r; + +fail_free_crtcs: + for (int i = 0; i < r->n_crtcs; i++) { + drm_crtc_fini(r->crtcs + i); + } + + if (r->crtcs != NULL) { + free(r->crtcs); + } + +fail_free_encoders: + for (int i = 0; i < r->n_encoders; i++) { + drm_encoder_fini(r->encoders + i); + } + + if (r->encoders != NULL) { + free(r->encoders); + } + +fail_free_connectors: + for (int i = 0; i < r->n_connectors; i++) { + drm_connector_fini(r->connectors + i); + } + + if (r->connectors != NULL) { + free(r->connectors); + } + +fail_free_r: + free(r); + return NULL; +} + +void drm_resources_destroy(struct drm_resources *r) { + for (int i = 0; i < r->n_planes; i++) { + drm_plane_fini(r->planes + i); + } + if (r->planes != NULL) { + free(r->planes); + } + + for (int i = 0; i < r->n_crtcs; i++) { + drm_crtc_fini(r->crtcs + i); + } + if (r->crtcs != NULL) { + free(r->crtcs); + } + + for (int i = 0; i < r->n_encoders; i++) { + drm_encoder_fini(r->encoders + i); + } + if (r->encoders != NULL) { + free(r->encoders); + } + + for (int i = 0; i < r->n_connectors; i++) { + drm_connector_fini(r->connectors + i); + } + + if (r->connectors != NULL) { + free(r->connectors); + } + + free(r); +} + +DEFINE_REF_OPS(drm_resources, n_refs) + +int drm_resources_update(struct drm_resources *r, int drm_fd, const struct drm_uevent *event) { + int ok; + + enum { + CHANGE, + ADD, + REMOVE, + } action_type; + + if (streq(event->action, "change")) { + action_type = CHANGE; + } else if (streq(event->action, "add")) { + action_type = ADD; + } else if (streq(event->action, "remove")) { + action_type = REMOVE; + } else { + // We don't care about this action for now. + return 0; + } + + /// TODO: Hotplug is set for every CHANGE event, but it's not really clear what it means. + (void) event->hotplug; + + switch (action_type) { + case ADD: + case REMOVE: + /// TODO: Implement + UNIMPLEMENTED(); + break; + case CHANGE: + if (event->have_connector) { + // If we don't care about this connector, skip it. + if (r->have_filter && r->filter.connector_id != event->connector_id) { + return 0; + } + + struct drm_connector *connector = drm_resources_get_connector(r, event->connector_id); + if (connector == NULL) { + // should not be possible with a CHANGE event. + assert(false); + return 0; + } + + ok = drm_connector_update(connector, drm_fd, event->have_property, event->property_id); + if (ok != 0) { + return ok; + } + } else { + // Everything could have changed. + /// TODO: Implement + UNIMPLEMENTED(); + } + break; + default: UNREACHABLE(); + } + + return 0; +} + +void drm_resources_apply_rockchip_workaround(struct drm_resources *r) { + // Rockchip driver always wants the N-th primary/cursor plane to be associated with the N-th CRTC. + // If we don't respect this, commits will work but not actually show anything on screen. + int primary_plane_index = 0; + int cursor_plane_index = 0; + drm_resources_for_each_plane(r, plane) { + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + if ((plane->possible_crtcs & (1 << primary_plane_index)) != 0) { + plane->possible_crtcs = (1 << primary_plane_index); + } else { + LOG_DEBUG("Primary plane %d does not support CRTC %d.\n", primary_plane_index, primary_plane_index); + } + + primary_plane_index++; + } else if (plane->type == DRM_PLANE_TYPE_CURSOR) { + if ((plane->possible_crtcs & (1 << cursor_plane_index)) != 0) { + plane->possible_crtcs = (1 << cursor_plane_index); + } else { + LOG_DEBUG("Cursor plane %d does not support CRTC %d.\n", cursor_plane_index, cursor_plane_index); + } + + cursor_plane_index++; + } + } +} + +bool drm_resources_has_connector(struct drm_resources *r, uint32_t connector_id) { + ASSERT_NOT_NULL(r); + + return drm_resources_get_connector(r, connector_id) != NULL; +} + +struct drm_connector *drm_resources_get_connector(struct drm_resources *r, uint32_t connector_id) { + ASSERT_NOT_NULL(r); + + for (int i = 0; i < r->n_connectors; i++) { + if (r->connectors[i].id == connector_id) { + return r->connectors + i; + } + } + + return NULL; +} + +bool drm_resources_has_encoder(struct drm_resources *r, uint32_t encoder_id) { + ASSERT_NOT_NULL(r); + + return drm_resources_get_encoder(r, encoder_id) != NULL; +} + +struct drm_encoder *drm_resources_get_encoder(struct drm_resources *r, uint32_t encoder_id) { + ASSERT_NOT_NULL(r); + + for (int i = 0; i < r->n_encoders; i++) { + if (r->encoders[i].id == encoder_id) { + return r->encoders + i; + } + } + + return NULL; +} + +bool drm_resources_has_crtc(struct drm_resources *r, uint32_t crtc_id) { + ASSERT_NOT_NULL(r); + + return drm_resources_get_crtc(r, crtc_id) != NULL; +} + +struct drm_crtc *drm_resources_get_crtc(struct drm_resources *r, uint32_t crtc_id) { + ASSERT_NOT_NULL(r); + + for (int i = 0; i < r->n_crtcs; i++) { + if (r->crtcs[i].id == crtc_id) { + return r->crtcs + i; + } + } + + return NULL; +} + +int64_t drm_resources_get_min_zpos_for_crtc(struct drm_resources *r, uint32_t crtc_id) { + struct drm_crtc *crtc; + int64_t min_zpos; + + crtc = drm_resources_get_crtc(r, crtc_id); + if (crtc == NULL) { + return INT64_MIN; + } + + min_zpos = INT64_MAX; + for (int i = 0; i < r->n_planes; i++) { + struct drm_plane *plane = r->planes + i; + + if (plane->possible_crtcs & crtc->bitmask) { + if (plane->has_zpos && plane->min_zpos < min_zpos) { + min_zpos = plane->min_zpos; + } + } + } + + return min_zpos; +} + +uint32_t drm_resources_get_possible_planes_for_crtc(struct drm_resources *r, uint32_t crtc_id) { + struct drm_crtc *crtc; + uint32_t possible_planes; + + crtc = drm_resources_get_crtc(r, crtc_id); + if (crtc == NULL) { + return 0; + } + + possible_planes = 0; + for (int i = 0; i < r->n_planes; i++) { + struct drm_plane *plane = r->planes + i; + + if (plane->possible_crtcs & crtc->bitmask) { + possible_planes |= 1u << i; + } + } + + return possible_planes; +} + +bool drm_resources_has_plane(struct drm_resources *r, uint32_t plane_id) { + ASSERT_NOT_NULL(r); + + return drm_resources_get_plane(r, plane_id) != NULL; +} + +struct drm_plane *drm_resources_get_plane(struct drm_resources *r, uint32_t plane_id) { + ASSERT_NOT_NULL(r); + + for (int i = 0; i < r->n_planes; i++) { + if (r->planes[i].id == plane_id) { + return r->planes + i; + } + } + + return NULL; +} + +unsigned int drm_resources_get_plane_index(struct drm_resources *r, uint32_t plane_id) { + ASSERT_NOT_NULL(r); + + for (unsigned int i = 0; i < r->n_planes; i++) { + if (r->planes[i].id == plane_id) { + return i; + } + } + + return UINT_MAX; +} + +struct drm_connector *drm_resources_connector_first(struct drm_resources *r) { + ASSERT_NOT_NULL(r); + + return r->n_connectors > 0 ? r->connectors : NULL; +} + +struct drm_connector *drm_resources_connector_end(struct drm_resources *r) { + ASSERT_NOT_NULL(r); + + return r->n_connectors > 0 ? r->connectors + r->n_connectors : NULL; +} + +struct drm_connector *drm_resources_connector_next(struct drm_resources *r, struct drm_connector *current) { + ASSERT_NOT_NULL(r); + ASSERT_NOT_NULL(current); + + return current + 1; +} + +drmModeModeInfo *drm_connector_mode_first(struct drm_connector *c) { + ASSERT_NOT_NULL(c); + + return c->variable_state.n_modes > 0 ? c->variable_state.modes : NULL; +} + +drmModeModeInfo *drm_connector_mode_end(struct drm_connector *c) { + ASSERT_NOT_NULL(c); + + return c->variable_state.n_modes > 0 ? c->variable_state.modes + c->variable_state.n_modes : NULL; +} + +drmModeModeInfo *drm_connector_mode_next(struct drm_connector *c, drmModeModeInfo *current) { + ASSERT_NOT_NULL(c); + ASSERT_NOT_NULL(current); + + return current + 1; +} + +struct drm_encoder *drm_resources_encoder_first(struct drm_resources *r) { + ASSERT_NOT_NULL(r); + + return r->n_encoders > 0 ? r->encoders : NULL; +} + +struct drm_encoder *drm_resources_encoder_end(struct drm_resources *r) { + ASSERT_NOT_NULL(r); + + return r->n_encoders > 0 ? r->encoders + r->n_encoders : NULL; +} + +struct drm_encoder *drm_resources_encoder_next(struct drm_resources *r, struct drm_encoder *current) { + ASSERT_NOT_NULL(r); + ASSERT_NOT_NULL(current); + + return current + 1; +} + +struct drm_crtc *drm_resources_crtc_first(struct drm_resources *r) { + ASSERT_NOT_NULL(r); + + return r->n_crtcs > 0 ? r->crtcs : NULL; +} + +struct drm_crtc *drm_resources_crtc_end(struct drm_resources *r) { + ASSERT_NOT_NULL(r); + + return r->n_crtcs > 0 ? r->crtcs + r->n_crtcs : NULL; +} + +struct drm_crtc *drm_resources_crtc_next(struct drm_resources *r, struct drm_crtc *current) { + ASSERT_NOT_NULL(r); + ASSERT_NOT_NULL(current); + + return current + 1; +} + +struct drm_plane *drm_resources_plane_first(struct drm_resources *r) { + ASSERT_NOT_NULL(r); + + return r->n_planes > 0 ? r->planes : NULL; +} + +struct drm_plane *drm_resources_plane_end(struct drm_resources *r) { + ASSERT_NOT_NULL(r); + + return r->n_planes > 0 ? r->planes + r->n_planes : NULL; +} + +struct drm_plane *drm_resources_plane_next(struct drm_resources *r, struct drm_plane *current) { + ASSERT_NOT_NULL(r); + ASSERT_NOT_NULL(current); + + return current + 1; +} + +struct drm_blob { + int drm_fd; + bool close_fd; + + uint32_t blob_id; + drmModeModeInfo mode; +}; + +struct drm_blob *drm_blob_new_mode(int drm_fd, const drmModeModeInfo *mode, bool dup_fd) { + struct drm_blob *blob; + uint32_t blob_id; + int ok; + + blob = malloc(sizeof *blob); + if (blob == NULL) { + return NULL; + } + + if (dup_fd) { + blob->drm_fd = dup(drm_fd); + if (blob->drm_fd < 0) { + LOG_ERROR("Couldn't duplicate DRM fd. dup: %s\n", strerror(errno)); + goto fail_free_blob; + } + + blob->close_fd = true; + } else { + blob->drm_fd = drm_fd; + blob->close_fd = false; + } + + 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)); + goto fail_maybe_close_fd; + } + + blob->blob_id = blob_id; + blob->mode = *mode; + return blob; + +fail_maybe_close_fd: + if (blob->close_fd) { + close(blob->drm_fd); + } + +fail_free_blob: + free(blob); + return NULL; +} + +void drm_blob_destroy(struct drm_blob *blob) { + int ok; + + 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_blob_new_mode. + if (blob->close_fd) { + close(blob->drm_fd); + } + + free(blob); +} + +int drm_blob_get_id(struct drm_blob *blob) { + ASSERT_NOT_NULL(blob); + return blob->blob_id; +} diff --git a/src/modesetting.h b/src/kms/resources.h similarity index 60% rename from src/modesetting.h rename to src/kms/resources.h index 7e5a9562..ed93fbb1 100644 --- a/src/modesetting.h +++ b/src/kms/resources.h @@ -1,27 +1,31 @@ -// SPDX-License-Identifier: MIT -/* - * KMS Modesetting - * - * - implements the interface to linux kernel modesetting - * - allows querying connected screens, crtcs, planes, etc - * - allows setting video modes, showing things on screen - * - * Copyright (c) 2022, Hannes Winkler - */ +#ifndef _FLUTTERPI_MODESETTING_RESOURCES_H +#define _FLUTTERPI_MODESETTING_RESOURCES_H -#ifndef _FLUTTERPI_SRC_MODESETTING_H -#define _FLUTTERPI_SRC_MODESETTING_H - -#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include #include +#include "monitor.h" #include "pixel_format.h" -#include "util/collection.h" -#include "util/geometry.h" +#include "util/bitset.h" +#include "util/list.h" +#include "util/lock_ops.h" +#include "util/logging.h" +#include "util/macros.h" #include "util/refcounting.h" #define DRM_ID_NONE ((uint32_t) 0xFFFFFFFF) @@ -218,12 +222,12 @@ #define DRM_BLEND_ALPHA_OPAQUE 0xFFFF enum drm_blend_mode { - kPremultiplied_DrmBlendMode, - kCoverage_DrmBlendMode, - kNone_DrmBlendMode, + DRM_BLEND_MODE_PREMULTIPLIED, + DRM_BLEND_MODE_COVERAGE, + DRM_BLEND_MODE_NONE, - kMax_DrmBlendMode = kNone_DrmBlendMode, - kCount_DrmBlendMode = kMax_DrmBlendMode + 1 + DRM_BLEND_MODE_MAX = DRM_BLEND_MODE_NONE, + DRM_BLEND_MODE_COUNT = DRM_BLEND_MODE_MAX + 1 }; struct drm_connector_prop_ids { @@ -308,55 +312,52 @@ enum drm_plane_rotation { */ 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; + DRM_PRIMARY_PLANE = DRM_PLANE_TYPE_PRIMARY, + DRM_OVERLAY_PLANE = DRM_PLANE_TYPE_OVERLAY, + DRM_CURSOR_PLANE = DRM_PLANE_TYPE_CURSOR }; 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, + DRM_CONNECTOR_TYPE_UNKNOWN = DRM_MODE_CONNECTOR_Unknown, + DRM_CONNECTOR_TYPE_VGA = DRM_MODE_CONNECTOR_VGA, + DRM_CONNECTOR_TYPE_DVII = DRM_MODE_CONNECTOR_DVII, + DRM_CONNECTOR_TYPE_DVID = DRM_MODE_CONNECTOR_DVID, + DRM_CONNECTOR_TYPE_DVIA = DRM_MODE_CONNECTOR_DVIA, + DRM_CONNECTOR_TYPE_COMPOSITE = DRM_MODE_CONNECTOR_Composite, + DRM_CONNECTOR_TYPE_SVIDEO = DRM_MODE_CONNECTOR_SVIDEO, + DRM_CONNECTOR_TYPE_LVDS = DRM_MODE_CONNECTOR_LVDS, + DRM_CONNECTOR_TYPE_COMPONENT = DRM_MODE_CONNECTOR_Component, + DRM_CONNECTOR_TYPE_DIN = DRM_MODE_CONNECTOR_9PinDIN, + DRM_CONNECTOR_TYPE_DISPLAYPORT = DRM_MODE_CONNECTOR_DisplayPort, + DRM_CONNECTOR_TYPE_HDMIA = DRM_MODE_CONNECTOR_HDMIA, + DRM_CONNECTOR_TYPE_HDMIB = DRM_MODE_CONNECTOR_HDMIB, + DRM_CONNECTOR_TYPE_TV = DRM_MODE_CONNECTOR_TV, + DRM_CONNECTOR_TYPE_EDP = DRM_MODE_CONNECTOR_eDP, + DRM_CONNECTOR_TYPE_VIRTUAL = DRM_MODE_CONNECTOR_VIRTUAL, + DRM_CONNECTOR_TYPE_DSI = DRM_MODE_CONNECTOR_DSI, + DRM_CONNECTOR_TYPE_DPI = DRM_MODE_CONNECTOR_DPI, + DRM_CONNECTOR_TYPE_WRITEBACK = DRM_MODE_CONNECTOR_WRITEBACK, #ifdef DRM_MODE_CONNECTOR_SPI - kSPI_DrmConnectorType = DRM_MODE_CONNECTOR_SPI + DRM_CONNECTOR_TYPE_SPI = DRM_MODE_CONNECTOR_SPI, +#endif +#ifdef DRM_MODE_CONNECTOR_USB + DRM_CONNECTOR_TYPE_USB = DRM_MODE_CONNECTOR_USB, #endif }; enum drm_connection_state { - kConnected_DrmConnectionState = DRM_MODE_CONNECTED, - kDisconnected_DrmConnectionState = DRM_MODE_DISCONNECTED, - kUnknown_DrmConnectionState = DRM_MODE_UNKNOWNCONNECTION + DRM_CONNSTATE_CONNECTED = DRM_MODE_CONNECTED, + DRM_CONNSTATE_DISCONNECTED = DRM_MODE_DISCONNECTED, + DRM_CONNSTATE_UNKNOWN = 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 + DRM_SUBPIXEL_UNKNOWN = DRM_MODE_SUBPIXEL_UNKNOWN, + DRM_SUBPIXEL_HORIZONTAL = DRM_MODE_SUBPIXEL_HORIZONTAL_RGB, + DRM_SUBPIXEL_HORIZONTAL_BGR = DRM_MODE_SUBPIXEL_HORIZONTAL_BGR, + DRM_SUBPIXEL_VERTICAL_RGB = DRM_MODE_SUBPIXEL_VERTICAL_RGB, + DRM_SUBPIXEL_VERTICAL_BGR = DRM_MODE_SUBPIXEL_VERTICAL_BGR, + DRM_SUBPIXEL_NONE = DRM_MODE_SUBPIXEL_NONE }; struct drm_connector { @@ -384,8 +385,29 @@ struct drm_connector { } committed_state; }; +enum drm_encoder_type { + DRM_ENCODER_TYPE_NONE = DRM_MODE_ENCODER_NONE, + DRM_ENCODER_TYPE_TMDS = DRM_MODE_ENCODER_TMDS, + DRM_ENCODER_TYPE_DAC = DRM_MODE_ENCODER_DAC, + DRM_ENCODER_TYPE_LVDS = DRM_MODE_ENCODER_LVDS, + DRM_ENCODER_TYPE_TVDAC = DRM_MODE_ENCODER_TVDAC, + DRM_ENCODER_TYPE_VIRTUAL = DRM_MODE_ENCODER_VIRTUAL, + DRM_ENCODER_TYPE_DSI = DRM_MODE_ENCODER_DSI, + DRM_ENCODER_TYPE_DPMST = DRM_MODE_ENCODER_DPMST, + DRM_ENCODER_TYPE_DPI = DRM_MODE_ENCODER_DPI, + DRM_ENCODER_TYPE_MAX = DRM_MODE_ENCODER_DPI, +}; + struct drm_encoder { - drmModeEncoder *encoder; + uint32_t id; + enum drm_encoder_type type; + + uint32_t possible_crtcs; + uint32_t possible_clones; + + struct { + uint32_t crtc_id; + } variable_state; }; struct drm_crtc { @@ -398,7 +420,7 @@ struct drm_crtc { struct { bool has_mode; drmModeModeInfo mode; - struct drm_mode_blob *mode_blob; + struct drm_blob *mode_blob; } committed_state; }; @@ -508,7 +530,7 @@ struct drm_plane { /// @brief The supported blend modes. /// /// Only valid if @ref has_blend_mode is true. - bool supported_blend_modes[kCount_DrmBlendMode]; + bool supported_blend_modes[DRM_BLEND_MODE_COUNT]; struct { /// @brief The committed CRTC id. @@ -596,346 +618,196 @@ typedef bool (*drm_plane_modified_format_callback_t)( void *userdata ); -struct drmdev; - /** - * @brief Iterates over every supported pixel-format & modifier pair. - * - * See @ref drm_plane_modified_format_callback_t for documentation on the callback. + * @brief A set of DRM resources, e.g. connectors, encoders, CRTCs, planes. + * + * This struct is refcounted, so you should use @ref drm_resources_ref and @ref drm_resources_unref + * to manage its lifetime. + * + * DRM resources can change, e.g. when a monitor is plugged in or out, or a connector is added. + * You can update the resources with @ref drm_resources_update. + * + * @attention DRM resources are not thread-safe. They should only be accessed on a single thread + * in their entire lifetime. This includes updates using @ref drm_resources_update. + * */ -void drm_plane_for_each_modified_format(struct drm_plane *plane, drm_plane_modified_format_callback_t callback, void *userdata); +struct drm_resources { + refcount_t n_refs; -bool drm_plane_supports_modified_format(struct drm_plane *plane, enum pixfmt format, uint64_t modifier); + bool have_filter; + struct { + uint32_t connector_id; + uint32_t encoder_id; + uint32_t crtc_id; -bool drm_plane_supports_unmodified_format(struct drm_plane *plane, enum pixfmt format); + size_t n_planes; + uint32_t plane_ids[32]; + } filter; -bool drm_crtc_any_plane_supports_format(struct drmdev *drmdev, struct drm_crtc *crtc, enum pixfmt pixel_format); + uint32_t min_width, max_width; + uint32_t min_height, max_height; -struct _drmModeModeInfo; + size_t n_connectors; + struct drm_connector *connectors; -struct drmdev_interface { - int (*open)(const char *path, int flags, void **fd_metadata_out, void *userdata); - void (*close)(int fd, void *fd_metadata, void *userdata); -}; + size_t n_encoders; + struct drm_encoder *encoders; -struct drmdev *drmdev_new_from_interface_fd(int fd, void *fd_metadata, const struct drmdev_interface *interface, void *userdata); + size_t n_crtcs; + struct drm_crtc *crtcs; -struct drmdev *drmdev_new_from_path(const char *path, const struct drmdev_interface *interface, void *userdata); + size_t n_planes; + struct drm_plane *planes; +}; -DECLARE_REF_OPS(drmdev) +/** + * @brief Iterates over every supported pixel-format & modifier pair. + * + * See @ref drm_plane_modified_format_callback_t for documentation on the callback. + */ +void drm_plane_for_each_modified_format(struct drm_plane *plane, drm_plane_modified_format_callback_t callback, void *userdata); -struct drmdev; -struct _drmModeModeInfo; +bool drm_plane_supports_modified_formats(struct drm_plane *plane); -int drmdev_get_fd(struct drmdev *drmdev); -int drmdev_get_event_fd(struct drmdev *drmdev); -bool drmdev_supports_dumb_buffers(struct drmdev *drmdev); -int drmdev_create_dumb_buffer( - struct drmdev *drmdev, - int width, - int height, - int bpp, - uint32_t *gem_handle_out, - uint32_t *pitch_out, - size_t *size_out -); -void drmdev_destroy_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle); -void *drmdev_map_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle, size_t size); -void drmdev_unmap_dumb_buffer(struct drmdev *drmdev, void *map, size_t size); -int drmdev_on_event_fd_ready(struct drmdev *drmdev); -const struct drm_connector *drmdev_get_selected_connector(struct drmdev *drmdev); -const struct drm_encoder *drmdev_get_selected_encoder(struct drmdev *drmdev); -const struct drm_crtc *drmdev_get_selected_crtc(struct drmdev *drmdev); -const struct _drmModeModeInfo *drmdev_get_selected_mode(struct drmdev *drmdev); - -struct gbm_device *drmdev_get_gbm_device(struct drmdev *drmdev); - -uint32_t drmdev_add_fb( - struct drmdev *drmdev, - 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 -); +bool drm_plane_supports_modified_format(struct drm_plane *plane, enum pixfmt format, uint64_t modifier); -uint32_t drmdev_add_fb_multiplanar( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - const uint32_t bo_handles[4], - const uint32_t pitches[4], - const uint32_t offsets[4], - bool has_modifiers, - const uint64_t modifiers[4] -); +bool drm_plane_supports_unmodified_format(struct drm_plane *plane, enum pixfmt format); -uint32_t drmdev_add_fb_from_dmabuf( - struct drmdev *drmdev, - 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 -); +bool drm_resources_any_crtc_plane_supports_format(struct drm_resources *res, uint32_t crtc_id, enum pixfmt pixel_format); -uint32_t drmdev_add_fb_from_dmabuf_multiplanar( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - const int prime_fds[4], - const uint32_t pitches[4], - const uint32_t offsets[4], - bool has_modifiers, - const uint64_t modifiers[4] +/** + * @brief Create a new drm_resources object + */ +struct drm_resources *drm_resources_new(int drm_fd); + +struct drm_resources *drm_resources_new_filtered( + int drm_fd, + uint32_t connector_id, + uint32_t encoder_id, + uint32_t crtc_id, + size_t n_planes, + const uint32_t *plane_ids ); -uint32_t drmdev_add_fb_from_gbm_bo(struct drmdev *drmdev, struct gbm_bo *bo, bool cast_opaque); - -int drmdev_rm_fb_locked(struct drmdev *drmdev, uint32_t fb_id); - -int drmdev_rm_fb(struct drmdev *drmdev, uint32_t fb_id); - -int drmdev_get_last_vblank(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out); - -bool drmdev_can_modeset(struct drmdev *drmdev); +struct drm_resources *drm_resources_dup_filtered( + struct drm_resources *res, + uint32_t connector_id, + uint32_t encoder_id, + uint32_t crtc_id, + size_t n_planes, + const uint32_t *plane_ids +); -void drmdev_suspend(struct drmdev *drmdev); +void drm_resources_destroy(struct drm_resources *r); -int drmdev_resume(struct drmdev *drmdev); +DECLARE_REF_OPS(drm_resources) -int drmdev_move_cursor(struct drmdev *drmdev, uint32_t crtc_id, struct vec2i pos); +/** + * @brief Update the resources with the current state of the DRM device. + * + * @param r The resources to update. + * @param drm_fd The file descriptor of the DRM device. + * @param event The event that triggered the update, or NULL if the update was not triggered by an event. + * @returns 0 on success, negative error code on failure. + */ +int drm_resources_update(struct drm_resources *r, int drm_fd, const struct drm_uevent *event); -static inline double mode_get_vrefresh(const drmModeModeInfo *mode) { - return mode->clock * 1000.0 / (mode->htotal * mode->vtotal); -} +/** + * @brief Apply a workaround for the Rockchip DRM driver. + * + * The rockchip driver has special requirements as to which CRTCs can be used with which planes. + * This function will restrict the possible_crtcs property for each plane to satisfy that requirement. + * + * @attention This function can only be called on un-filtered resources, and should be called after each drm_resources_update. + * + * @param r The resources to apply the workaround to. + * + */ +void drm_resources_apply_rockchip_workaround(struct drm_resources *r); -typedef void (*kms_scanout_cb_t)(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata); +bool drm_resources_has_connector(struct drm_resources *r, uint32_t connector_id); -struct kms_fb_layer { - uint32_t drm_fb_id; - enum pixfmt format; - bool has_modifier; - uint64_t modifier; +struct drm_connector *drm_resources_get_connector(struct drm_resources *r, uint32_t connector_id); - int32_t src_x, src_y, src_w, src_h; - int32_t dst_x, dst_y, dst_w, dst_h; +bool drm_resources_has_encoder(struct drm_resources *r, uint32_t encoder_id); - bool has_rotation; - drm_plane_transform_t rotation; +struct drm_encoder *drm_resources_get_encoder(struct drm_resources *r, uint32_t encoder_id); - bool has_in_fence_fd; - int in_fence_fd; +bool drm_resources_has_crtc(struct drm_resources *r, uint32_t crtc_id); - bool prefer_cursor; -}; +struct drm_crtc *drm_resources_get_crtc(struct drm_resources *r, uint32_t crtc_id); -typedef void (*kms_fb_release_cb_t)(void *userdata); +int64_t drm_resources_get_min_zpos_for_crtc(struct drm_resources *r, uint32_t crtc_id); -typedef void (*kms_deferred_fb_release_cb_t)(void *userdata, int syncfile_fd); +uint32_t drm_resources_get_possible_planes_for_crtc(struct drm_resources *r, uint32_t crtc_id); -struct kms_req_builder; +bool drm_resources_has_plane(struct drm_resources *r, uint32_t plane_id); -struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uint32_t crtc_id); +struct drm_plane *drm_resources_get_plane(struct drm_resources *r, uint32_t plane_id); -DECLARE_REF_OPS(kms_req_builder); +unsigned int drm_resources_get_plane_index(struct drm_resources *r, uint32_t plane_id); -/** - * @brief Gets the @ref drmdev associated with this KMS request builder. - * - * @param builder The KMS request builder. - * @returns The drmdev associated with this KMS request builder. - */ -struct drmdev *kms_req_builder_get_drmdev(struct kms_req_builder *builder); +struct drm_connector *drm_resources_connector_first(struct drm_resources *r); -/** - * @brief Gets the CRTC associated with this KMS request builder. - * - * @param builder The KMS request builder. - * @returns The CRTC associated with this KMS request builder. - */ -struct drm_crtc *kms_req_builder_get_crtc(struct kms_req_builder *builder); +struct drm_connector *drm_resources_connector_end(struct drm_resources *r); -/** - * @brief Adds a property to the KMS request that will set the given video mode - * on this CRTC on commit, regardless of whether the currently committed output - * mode is the same. - * - * @param builder The KMS request builder. - * @param mode The output mode to set (on @ref kms_req_commit) - * @returns Zero if successful, positive errno-style error on failure. - */ -int kms_req_builder_set_mode(struct kms_req_builder *builder, const drmModeModeInfo *mode); +struct drm_connector *drm_resources_connector_next(struct drm_resources *r, struct drm_connector *current); -/** - * @brief Adds a property to the KMS request that will unset the configured - * output mode for this CRTC on commit, regardless of whether the currently - * committed output mdoe is already unset. - * - * @param builder The KMS request builder. - * @returns Zero if successful, positive errno-style error on failure. - */ -int kms_req_builder_unset_mode(struct kms_req_builder *builder); +drmModeModeInfo *drm_connector_mode_first(struct drm_connector *c); -/** - * @brief Adds a property to the KMS request that will change the connector - * that this CRTC is displaying content on to @param connector_id. - * - * @param builder The KMS request builder. - * @param connector_id The connector that this CRTC should display contents on. - * @returns Zero if successful, EINVAL if the @param connector_id is invalid. - */ -int kms_req_builder_set_connector(struct kms_req_builder *builder, uint32_t connector_id); +drmModeModeInfo *drm_connector_mode_end(struct drm_connector *c); -/** - * @brief True if the next layer pushed using @ref kms_req_builder_push_fb_layer - * should be opaque, i.e. use a framebuffer which has a pixel format that has no - * alpha channel. - * - * This is true for the bottom-most layer. There are some display controllers - * that don't support non-opaque pixel formats for the bottom-most (primary) - * plane. So ignoring this might lead to an EINVAL on commit. - * - * @param builder The KMS request builder. - * @returns True if the next layer should preferably be opaque, false if there's - * no preference. - */ -bool kms_req_builder_prefer_next_layer_opaque(struct kms_req_builder *builder); +drmModeModeInfo *drm_connector_mode_next(struct drm_connector *c, drmModeModeInfo *current); -/** - * @brief Adds a new framebuffer (display) layer on top of the last layer. - * - * If this is the first layer, the framebuffer should cover the entire screen - * (CRTC). - * - * To allow the use of explicit fencing, specify an in_fence_fd in @param layer - * and a @param deferred_release_callback. - * - * If explicit fencing is supported: - * - the in_fence_fd should be a DRM syncobj fd that signals - * when the GPU has finished rendering to the framebuffer and is ready - * to be scanned out. - * - @param deferred_release_callback will be called - * with a DRM syncobj fd that is signaled once the framebuffer is no longer - * being displayed on screen (and can be rendered into again) - * - * If explicit fencing is not supported: - * - the in_fence_fd in @param layer will be closed by this procedure. - * - @param deferred_release_callback will NOT be called and - * @param release_callback will be called instead. - * - * Explicit fencing is supported: When atomic modesetting is being used and - * the driver supports it. (Driver has IN_FENCE_FD plane and OUT_FENCE_PTR crtc - * properties) - * - * @param builder The KMS request builder. - * @param layer The exact details (src pos, output pos, rotation, - * framebuffer) of the layer that should be shown on - * screen. - * @param release_callback Called when the framebuffer of this layer is no - * longer being shown on screen. This is called with the - * drmdev locked, so make sure to use _locked variants - * of any drmdev calls. - * @param deferred_release_callback (Unimplemented right now) If this is present, - * this callback might be called instead of - * @param release_callback. - * This is called with a DRM syncobj fd that is - * signaled when the framebuffer is no longer - * shown on screen. - * Legacy DRM modesetting does not support - * explicit fencing, in which case - * @param release_callback will be called - * instead. - * @param userdata Userdata pointer that's passed to the release_callback or - * deferred_release_callback as-is. - * @returns Zero on success, otherwise: - * - EINVAL: if attempting to push a second framebuffer layer, if - * driver supports atomic modesetting but legacy modesetting is - * being used. - * - EIO: if no DRM plane could be found that supports displaying - * this framebuffer layer. Either the pixel format is not - * supported, the modifier, the rotation or the drm device - * doesn't have enough planes. - * - The error returned by @ref close if closing the in_fence_fd - * fails. - */ -int kms_req_builder_push_fb_layer( - struct kms_req_builder *builder, - const struct kms_fb_layer *layer, - kms_fb_release_cb_t release_callback, - kms_deferred_fb_release_cb_t deferred_release_callback, - void *userdata -); +struct drm_encoder *drm_resources_encoder_first(struct drm_resources *r); -/** - * @brief Push a "fake" layer that just keeps one zpos free, incase something - * other than KMS wants to display contents there. (e.g. omxplayer) - * - * @param builder The KMS request builder. - * @param zpos_out Filled with the zpos that won't be occupied by the request - * builder. - * @returns Zero. - */ -int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder, int64_t *zpos_out); +struct drm_encoder *drm_resources_encoder_end(struct drm_resources *r); -/** - * @brief A KMS request (atomic or legacy modesetting) that can be committed to - * change the state of a single CRTC. - * - * Only way to construct this is by building a KMS request using - * @ref kms_req_builder and then calling @ref kms_req_builder_build. - */ -struct kms_req; +struct drm_encoder *drm_resources_encoder_next(struct drm_resources *r, struct drm_encoder *current); -DECLARE_REF_OPS(kms_req); +struct drm_crtc *drm_resources_crtc_first(struct drm_resources *r); -/** - * @brief Build the KMS request builder into an actual, immutable KMS request - * that can be committed. Internally this doesn't do much at all. - * - * @param builder The KMS request builder that should be built. - * @returns KMS request that can be committed using @ref kms_req_commit_blocking - * or @ref kms_req_commit_nonblocking. - * The returned KMS request has refcount 1. Unref using - * @ref kms_req_unref after usage. - */ -struct kms_req *kms_req_builder_build(struct kms_req_builder *builder); +struct drm_crtc *drm_resources_crtc_end(struct drm_resources *r); -int kms_req_commit_blocking(struct kms_req *req, uint64_t *vblank_ns_out); +struct drm_crtc *drm_resources_crtc_next(struct drm_resources *r, struct drm_crtc *current); -int kms_req_commit_nonblocking(struct kms_req *req, kms_scanout_cb_t scanout_cb, void *userdata, void_callback_t destroy_cb); +struct drm_plane *drm_resources_plane_first(struct drm_resources *r); -struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector); +struct drm_plane *drm_resources_plane_end(struct drm_resources *r); -struct drm_encoder *__next_encoder(const struct drmdev *drmdev, const struct drm_encoder *encoder); +struct drm_plane *drm_resources_plane_next(struct drm_resources *r, struct drm_plane *current); -struct drm_crtc *__next_crtc(const struct drmdev *drmdev, const struct drm_crtc *crtc); +#define drm_resources_for_each_connector(res, connector) \ + for (struct drm_connector * (connector) = drm_resources_connector_first(res); (connector) != drm_resources_connector_end(res); \ + (connector) = drm_resources_connector_next((res), (connector))) -struct drm_plane *__next_plane(const struct drmdev *drmdev, const struct drm_plane *plane); +#define drm_connector_for_each_mode(connector, mode) \ + for (drmModeModeInfo * (mode) = drm_connector_mode_first(connector); (mode) != drm_connector_mode_end(connector); \ + (mode) = drm_connector_mode_next((connector), (mode))) -drmModeModeInfo *__next_mode(const struct drm_connector *connector, const drmModeModeInfo *mode); +#define drm_resources_for_each_encoder(res, encoder) \ + for (struct drm_encoder * (encoder) = drm_resources_encoder_first(res); (encoder) != drm_resources_encoder_end(res); \ + (encoder) = drm_resources_encoder_next((res), (encoder))) -#define for_each_connector_in_drmdev(drmdev, connector) \ - for (connector = __next_connector(drmdev, NULL); connector != NULL; connector = __next_connector(drmdev, connector)) +#define drm_resources_for_each_crtc(res, crtc) \ + for (struct drm_crtc * (crtc) = drm_resources_crtc_first(res); (crtc) != drm_resources_crtc_end(res); \ + (crtc) = drm_resources_crtc_next((res), (crtc))) -#define for_each_encoder_in_drmdev(drmdev, encoder) \ - for (encoder = __next_encoder(drmdev, NULL); encoder != NULL; encoder = __next_encoder(drmdev, encoder)) +#define drm_resources_for_each_plane(res, plane) \ + for (struct drm_plane * (plane) = drm_resources_plane_first(res); (plane) != drm_resources_plane_end(res); \ + (plane) = drm_resources_plane_next((res), (plane))) -#define for_each_crtc_in_drmdev(drmdev, crtc) for (crtc = __next_crtc(drmdev, NULL); crtc != NULL; crtc = __next_crtc(drmdev, crtc)) +struct drm_blob *drm_blob_new_mode(int drm_fd, const drmModeModeInfo *mode, bool dup_fd); -#define for_each_plane_in_drmdev(drmdev, plane) for (plane = __next_plane(drmdev, NULL); plane != NULL; plane = __next_plane(drmdev, plane)) +void drm_blob_destroy(struct drm_blob *blob); -#define for_each_mode_in_connector(connector, mode) \ - for (mode = __next_mode(connector, NULL); mode != NULL; mode = __next_mode(connector, mode)) +int drm_blob_get_id(struct drm_blob *blob); -#define for_each_unreserved_plane_in_atomic_req(atomic_req, plane) for_each_pointer_in_pset(&(atomic_req)->available_planes, plane) +/** + * @brief Get the precise refresh rate of a video mode. + */ +static inline float mode_get_vrefresh(const drmModeModeInfo *mode) { + return mode->clock * 1000.0f / (mode->htotal * mode->vtotal); +} -#endif // _FLUTTERPI_SRC_MODESETTING_H +#endif // _FLUTTERPI_MODESETTING_RESOURCES_H diff --git a/src/locales.c b/src/locales.c index ff66a098..7f13b1a6 100644 --- a/src/locales.c +++ b/src/locales.c @@ -220,7 +220,7 @@ static int add_locale_variants(struct list_head *locales, const char *locale_des } // then append all possible combinations - for (int i = 0b111; i >= 0; i--) { + for (int i = 7; i >= 0; i--) { char *territory_2 = NULL, *codeset_2 = NULL, *modifier_2 = NULL; if ((i & 1) != 0) { @@ -311,7 +311,7 @@ struct locales *locales_new(void) { // Use those to create our flutter locales. n_locales = list_length(&locales->locales); - fl_locales = calloc(n_locales, sizeof *fl_locales); + fl_locales = calloc(n_locales == 0 ? 1 : n_locales, sizeof(const FlutterLocale *)); if (fl_locales == NULL) { goto fail_free_allocated_locales; } @@ -322,6 +322,18 @@ struct locales *locales_new(void) { i++; } + // If we have no locales, add a default "C" locale. + if (i == 0) { + fl_locales[0] = &(const FlutterLocale){ + .struct_size = sizeof(FlutterLocale), + .language_code = "C", + .country_code = NULL, + .script_code = NULL, + .variant_code = NULL, + }; + i++; + } + if (streq(fl_locales[0]->language_code, "C")) { LOG_LOCALES_ERROR("Warning: The system has no configured locale. The default \"C\" locale may or may not be supported by the app.\n" ); diff --git a/src/modesetting.c b/src/modesetting.c deleted file mode 100644 index d72ff5d3..00000000 --- a/src/modesetting.c +++ /dev/null @@ -1,2987 +0,0 @@ -#include "modesetting.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include "pixel_format.h" -#include "util/bitset.h" -#include "util/list.h" -#include "util/lock_ops.h" -#include "util/logging.h" -#include "util/macros.h" -#include "util/refcounting.h" - -struct drm_fb { - struct list_head entry; - - uint32_t id; - - uint32_t width, height; - - enum pixfmt format; - - bool has_modifier; - uint64_t modifier; - - uint32_t flags; - - uint32_t handles[4]; - uint32_t pitches[4]; - uint32_t offsets[4]; -}; - -struct kms_req_layer { - struct kms_fb_layer layer; - - uint32_t plane_id; - struct drm_plane *plane; - - bool set_zpos; - int64_t zpos; - - bool set_rotation; - drm_plane_transform_t rotation; - - kms_fb_release_cb_t release_callback; - kms_deferred_fb_release_cb_t deferred_release_callback; - void *release_callback_userdata; -}; - -struct kms_req_builder { - refcount_t n_refs; - - struct drmdev *drmdev; - bool use_legacy; - bool supports_atomic; - - struct drm_connector *connector; - struct drm_crtc *crtc; - - BITSET_DECLARE(available_planes, 32); - drmModeAtomicReq *req; - int64_t next_zpos; - - int n_layers; - struct kms_req_layer layers[32]; - - bool unset_mode; - bool has_mode; - drmModeModeInfo mode; -}; - -COMPILE_ASSERT(BITSET_SIZE(((struct kms_req_builder *) 0)->available_planes) == 32); - -struct drmdev { - int fd; - - refcount_t n_refs; - pthread_mutex_t mutex; - bool supports_atomic_modesetting; - bool supports_dumb_buffers; - - size_t n_connectors; - struct drm_connector *connectors; - - size_t n_encoders; - struct drm_encoder *encoders; - - size_t n_crtcs; - struct drm_crtc *crtcs; - - size_t n_planes; - struct drm_plane *planes; - - drmModeRes *res; - drmModePlaneRes *plane_res; - - struct gbm_device *gbm_device; - - int event_fd; - - struct { - kms_scanout_cb_t scanout_callback; - void *userdata; - void_callback_t destroy_callback; - - struct kms_req *last_flipped; - } per_crtc_state[32]; - - int master_fd; - void *master_fd_metadata; - - struct drmdev_interface interface; - void *userdata; - - struct list_head fbs; -}; - -static bool is_drm_master(int fd) { - return drmAuthMagic(fd, 0) != -EACCES; -} - -static struct drm_mode_blob *drm_mode_blob_new(int drm_fd, const drmModeModeInfo *mode) { - struct drm_mode_blob *blob; - uint32_t blob_id; - 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; -} - -void drm_mode_blob_destroy(struct drm_mode_blob *blob) { - int ok; - - 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); -} - -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; - - 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; - } - - 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; - } - - 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; - LOG_ERROR("Could not get DRM device connector properties' info. drmModeGetProperty: %s\n", strerror(ok)); - goto fail_free_props; - } - -#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 - - DRM_CONNECTOR_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { - // this is the trailing else case - LOG_DEBUG("Unknown DRM connector property: %s\n", prop_info->name); - } - -#undef CHECK_ASSIGN_PROPERTY_ID - - if (strncmp(prop_info->name, "CRTC_ID", DRM_PROP_NAME_LEN) == 0) { - crtc_id = props->prop_values[i]; - } - - drmModeFreeProperty(prop_info); - prop_info = NULL; - } - - assert((connector->modes == NULL) == (connector->count_modes == 0)); - - if (connector->modes != NULL) { - modes = memdup(connector->modes, connector->count_modes * sizeof(*connector->modes)); - if (modes == NULL) { - ok = ENOMEM; - goto fail_free_props; - } - } else { - modes = NULL; - } - - connector_out->id = connector->connector_id; - connector_out->type = connector->connector_type; - connector_out->type_id = connector->connector_type_id; - connector_out->ids = ids; - connector_out->n_encoders = connector->count_encoders; - 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: - free(connectors); - *connectors_out = NULL; - *n_connectors_out = 0; - return ok; -} - -static int free_connectors(struct drm_connector *connectors, size_t n_connectors) { - for (int i = 0; i < n_connectors; i++) { - 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; - int ok; - - encoders = calloc(drmdev->res->count_encoders, sizeof *encoders); - if (encoders == NULL) { - *encoders_out = NULL; - *n_encoders_out = 0; - return ENOMEM; - } - - n_allocated_encoders = 0; - for (int i = 0; i < drmdev->res->count_encoders; i++, n_allocated_encoders++) { - ok = fetch_encoder(drmdev->fd, drmdev->res->encoders[i], encoders + i); - if (ok != 0) { - goto fail_free_encoders; - } - } - - *encoders_out = encoders; - *n_encoders_out = drmdev->res->count_encoders; - return 0; - -fail_free_encoders: - for (int i = 0; i < n_allocated_encoders; i++) { - drmModeFreeEncoder(encoders[i].encoder); - } - - free(encoders); - - *encoders_out = NULL; - *n_encoders_out = 0; - return ok; -} - -static void free_encoders(struct drm_encoder *encoders, size_t n_encoders) { - for (int i = 0; i < n_encoders; i++) { - 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 ok; - - crtcs = calloc(drmdev->res->count_crtcs, sizeof *crtcs); - if (crtcs == NULL) { - *crtcs_out = NULL; - *n_crtcs_out = 0; - return ENOMEM; - } - - 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; - } - } - - *crtcs_out = crtcs; - *n_crtcs_out = drmdev->res->count_crtcs; - return 0; - -fail_free_crtcs: - free(crtcs); - *crtcs_out = NULL; - *n_crtcs_out = 0; - return ok; -} - -static int free_crtcs(struct drm_crtc *crtcs, size_t n_crtcs) { - for (int i = 0; i < n_crtcs; i++) { - free_crtc(crtcs + i); - } - free(crtcs); - return 0; -} - -void drm_plane_for_each_modified_format(struct drm_plane *plane, drm_plane_modified_format_callback_t callback, void *userdata) { - struct drm_format_modifier_blob *blob; - struct drm_format_modifier *modifiers; - uint32_t *formats; - - ASSERT_NOT_NULL(plane); - ASSERT_NOT_NULL(callback); - ASSERT(plane->supports_modifiers); - ASSERT_EQUALS(plane->supported_modified_formats_blob->version, FORMAT_BLOB_CURRENT); - - blob = plane->supported_modified_formats_blob; - - modifiers = (void *) (((char *) blob) + blob->modifiers_offset); - formats = (void *) (((char *) blob) + blob->formats_offset); - - int index = 0; - for (int i = 0; i < blob->count_modifiers; i++) { - for (int j = modifiers[i].offset; (j < blob->count_formats) && (j < modifiers[i].offset + 64); j++) { - bool is_format_bit_set = (modifiers[i].formats & (1ull << (j % 64))) != 0; - if (!is_format_bit_set) { - continue; - } - - if (has_pixfmt_for_drm_format(formats[j])) { - enum pixfmt format = get_pixfmt_for_drm_format(formats[j]); - - bool should_continue = callback(plane, index, format, modifiers[i].modifier, userdata); - if (!should_continue) { - goto exit; - } - - index++; - } - } - } - -exit: - return; -} - -static bool -check_modified_format_supported(UNUSED struct drm_plane *plane, UNUSED int index, enum pixfmt format, uint64_t modifier, void *userdata) { - struct { - enum pixfmt format; - uint64_t modifier; - bool found; - } *context = userdata; - - if (format == context->format && modifier == context->modifier) { - context->found = true; - return false; - } else { - return true; - } -} - -bool drm_plane_supports_modified_format(struct drm_plane *plane, enum pixfmt format, uint64_t modifier) { - if (!plane->supported_modified_formats_blob) { - // return false if we want a modified format but the plane doesn't support modified formats - return false; - } - - struct { - enum pixfmt format; - uint64_t modifier; - bool found; - } context = { - .format = format, - .modifier = modifier, - .found = false, - }; - - // Check if the requested format & modifier is supported. - drm_plane_for_each_modified_format(plane, check_modified_format_supported, &context); - - return context.found; -} - -bool drm_plane_supports_unmodified_format(struct drm_plane *plane, enum pixfmt format) { - // we don't want a modified format, return false if the format is not in the list - // of supported (unmodified) formats - return plane->supported_formats[format]; -} - -bool drm_crtc_any_plane_supports_format(struct drmdev *drmdev, struct drm_crtc *crtc, enum pixfmt pixel_format) { - struct drm_plane *plane; - - for_each_plane_in_drmdev(drmdev, plane) { - if (!(plane->possible_crtcs & crtc->bitmask)) { - // Only query planes that are possible to connect to the CRTC we're using. - continue; - } - - if (plane->type != kPrimary_DrmPlaneType && plane->type != kOverlay_DrmPlaneType) { - // We explicitly only look for primary and overlay planes. - continue; - } - - if (drm_plane_supports_unmodified_format(plane, pixel_format)) { - return true; - } - } - - return false; -} - -struct _drmModeFB2; - -struct drm_mode_fb2 { - uint32_t fb_id; - uint32_t width, height; - uint32_t pixel_format; /* fourcc code from drm_fourcc.h */ - uint64_t modifier; /* applies to all buffers */ - uint32_t flags; - - /* per-plane GEM handle; may be duplicate entries for multiple planes */ - uint32_t handles[4]; - uint32_t pitches[4]; /* bytes */ - uint32_t offsets[4]; /* bytes */ -}; - -#ifdef HAVE_FUNC_ATTRIBUTE_WEAK -extern struct _drmModeFB2 *drmModeGetFB2(int fd, uint32_t bufferId) __attribute__((weak)); -extern void drmModeFreeFB2(struct _drmModeFB2 *ptr) __attribute__((weak)); - #define HAVE_WEAK_DRM_MODE_GET_FB2 -#endif - -static int fetch_plane(int drm_fd, uint32_t plane_id, struct drm_plane *plane_out) { - struct drm_plane_prop_ids ids; - drmModeObjectProperties *props; - 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[PIXFMT_COUNT] = { 0 }; - bool has_type, has_rotation, has_zpos, has_hardcoded_zpos, has_hardcoded_rotation, has_alpha, has_blend_mode; - int ok; - - 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(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; - } - - // zero-initialize plane_out. - memset(plane_out, 0, sizeof(*plane_out)); - - has_type = false; - has_rotation = false; - has_hardcoded_rotation = false; - has_zpos = false; - has_hardcoded_zpos = false; - has_alpha = false; - has_blend_mode = false; - 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' info. drmModeGetProperty"); - goto fail_maybe_free_supported_modified_formats_blob; - } - - if (streq(info->name, "type")) { - assert(has_type == false); - has_type = true; - - type = props->prop_values[j]; - } else if (streq(info->name, "rotation")) { - assert(has_rotation == false); - has_rotation = true; - - supported_rotations = PLANE_TRANSFORM_NONE; - assert(info->flags & DRM_MODE_PROP_BITMASK); - - for (int k = 0; k < info->count_enums; k++) { - supported_rotations.u32 |= 1 << info->enums[k].value; - } - - 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 (streq(info->name, "zpos")) { - 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); - assert(min_zpos <= max_zpos); - assert(min_zpos <= committed_zpos); - assert(committed_zpos <= max_zpos); - } else if (info->flags & DRM_MODE_PROP_RANGE) { - assert(info->values[0] < (uint64_t) INT64_MAX); - assert(info->values[1] < (uint64_t) INT64_MAX); - min_zpos = info->values[0]; - max_zpos = info->values[1]; - committed_zpos = props->prop_values[j]; - assert(min_zpos <= max_zpos); - } else { - ASSERT_MSG(info->flags && false, "Invalid property type for zpos property."); - } - - if (info->flags & DRM_MODE_PROP_IMMUTABLE) { - has_hardcoded_zpos = true; - 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 (streq(info->name, "SRC_X")) { - comitted_src_x = props->prop_values[j]; - } else if (streq(info->name, "SRC_Y")) { - comitted_src_y = props->prop_values[j]; - } else if (streq(info->name, "SRC_W")) { - comitted_src_w = props->prop_values[j]; - } else if (streq(info->name, "SRC_H")) { - comitted_src_h = props->prop_values[j]; - } else if (streq(info->name, "CRTC_X")) { - comitted_crtc_x = props->prop_values[j]; - } else if (streq(info->name, "CRTC_Y")) { - comitted_crtc_y = props->prop_values[j]; - } else if (streq(info->name, "CRTC_W")) { - comitted_crtc_w = props->prop_values[j]; - } else if (streq(info->name, "CRTC_H")) { - comitted_crtc_h = props->prop_values[j]; - } else if (streq(info->name, "IN_FORMATS")) { - drmModePropertyBlobRes *blob; - - blob = drmModeGetPropertyBlob(drm_fd, props->prop_values[j]); - if (blob == NULL) { - ok = errno; - 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; - } - - plane_out->supports_modifiers = true; - plane_out->supported_modified_formats_blob = memdup(blob->data, blob->length); - ASSERT_NOT_NULL(plane_out->supported_modified_formats_blob); - - drmModeFreePropertyBlob(blob); - } else if (streq(info->name, "alpha")) { - has_alpha = true; - assert(info->flags == DRM_MODE_PROP_RANGE); - assert(info->values[0] == 0); - assert(info->values[1] == 0xFFFF); - assert(props->prop_values[j] <= 0xFFFF); - - committed_alpha = (uint16_t) props->prop_values[j]; - } else if (streq(info->name, "pixel blend mode")) { - has_blend_mode = true; - assert(info->flags == DRM_MODE_PROP_ENUM); - - for (int i = 0; i < info->count_enums; i++) { - if (streq(info->enums[i].name, "None")) { - ASSERT_EQUALS(info->enums[i].value, kNone_DrmBlendMode); - supported_blend_modes[kNone_DrmBlendMode] = true; - } else if (streq(info->enums[i].name, "Pre-multiplied")) { - ASSERT_EQUALS(info->enums[i].value, kPremultiplied_DrmBlendMode); - supported_blend_modes[kPremultiplied_DrmBlendMode] = true; - } else if (streq(info->enums[i].name, "Coverage")) { - ASSERT_EQUALS(info->enums[i].value, kCoverage_DrmBlendMode); - supported_blend_modes[kCoverage_DrmBlendMode] = true; - } else { - LOG_DEBUG( - "Unknown KMS pixel blend mode: %s (value: %" PRIu64 ")\n", - info->enums[i].name, - (uint64_t) info->enums[i].value - ); - } - } - - committed_blend_mode = props->prop_values[j]; - assert(committed_blend_mode >= 0 && committed_blend_mode <= kMax_DrmBlendMode); - assert(supported_blend_modes[committed_blend_mode]); - } - -#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ - if (strncmp(info->name, _name_str, ARRAY_SIZE(info->name)) == 0) { \ - ids._name = info->prop_id; \ - } else - - DRM_PLANE_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { - // do nothing - } - -#undef CHECK_ASSIGN_PROPERTY_ID - - drmModeFreeProperty(info); - } - - assert(has_type); - (void) has_type; - - for (int i = 0; i < plane->count_formats; i++) { - for (int j = 0; j < PIXFMT_COUNT; j++) { - if (get_pixfmt_info(j)->drm_format == plane->formats[i]) { - supported_formats[j] = true; - break; - } - } - } - - bool has_format = false; - enum pixfmt format = PIXFMT_RGB565; - - // drmModeGetFB2 might not be present. - // If __attribute__((weak)) is supported by the compiler, we redefine it as - // weak above. - // If we don't have weak, we can't check for it here. -#ifdef HAVE_WEAK_DRM_MODE_GET_FB2 - if (drmModeGetFB2 && drmModeFreeFB2) { - struct drm_mode_fb2 *fb = (struct drm_mode_fb2 *) drmModeGetFB2(drm_fd, plane->fb_id); - if (fb != NULL) { - for (int i = 0; i < PIXFMT_COUNT; i++) { - if (get_pixfmt_info(i)->drm_format == fb->pixel_format) { - has_format = true; - format = i; - break; - } - } - - drmModeFreeFB2((struct _drmModeFB2 *) fb); - } - } -#endif - - 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->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; - plane_out->committed_state.has_format = has_format; - plane_out->committed_state.format = format; - drmModeFreeObjectProperties(props); - drmModeFreePlane(plane); - return 0; - -fail_maybe_free_supported_modified_formats_blob: - if (plane_out->supported_modified_formats_blob != NULL) - free(plane_out->supported_modified_formats_blob); - -fail_free_props: - drmModeFreeObjectProperties(props); - -fail_free_plane: - drmModeFreePlane(plane); - return ok; -} - -static void free_plane(UNUSED struct drm_plane *plane) { - if (plane->supported_modified_formats_blob != NULL) { - free(plane->supported_modified_formats_blob); - } -} - -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; - } - - 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); -} - -static void assert_rotations_work() { - assert(PLANE_TRANSFORM_ROTATE_0.rotate_0 == true); - assert(PLANE_TRANSFORM_ROTATE_0.rotate_90 == false); - assert(PLANE_TRANSFORM_ROTATE_0.rotate_180 == false); - assert(PLANE_TRANSFORM_ROTATE_0.rotate_270 == false); - assert(PLANE_TRANSFORM_ROTATE_0.reflect_x == false); - assert(PLANE_TRANSFORM_ROTATE_0.reflect_y == false); - - assert(PLANE_TRANSFORM_ROTATE_90.rotate_0 == false); - assert(PLANE_TRANSFORM_ROTATE_90.rotate_90 == true); - assert(PLANE_TRANSFORM_ROTATE_90.rotate_180 == false); - assert(PLANE_TRANSFORM_ROTATE_90.rotate_270 == false); - assert(PLANE_TRANSFORM_ROTATE_90.reflect_x == false); - assert(PLANE_TRANSFORM_ROTATE_90.reflect_y == false); - - assert(PLANE_TRANSFORM_ROTATE_180.rotate_0 == false); - assert(PLANE_TRANSFORM_ROTATE_180.rotate_90 == false); - assert(PLANE_TRANSFORM_ROTATE_180.rotate_180 == true); - assert(PLANE_TRANSFORM_ROTATE_180.rotate_270 == false); - assert(PLANE_TRANSFORM_ROTATE_180.reflect_x == false); - assert(PLANE_TRANSFORM_ROTATE_180.reflect_y == false); - - assert(PLANE_TRANSFORM_ROTATE_270.rotate_0 == false); - assert(PLANE_TRANSFORM_ROTATE_270.rotate_90 == false); - assert(PLANE_TRANSFORM_ROTATE_270.rotate_180 == false); - assert(PLANE_TRANSFORM_ROTATE_270.rotate_270 == true); - assert(PLANE_TRANSFORM_ROTATE_270.reflect_x == false); - assert(PLANE_TRANSFORM_ROTATE_270.reflect_y == false); - - assert(PLANE_TRANSFORM_REFLECT_X.rotate_0 == false); - assert(PLANE_TRANSFORM_REFLECT_X.rotate_90 == false); - assert(PLANE_TRANSFORM_REFLECT_X.rotate_180 == false); - assert(PLANE_TRANSFORM_REFLECT_X.rotate_270 == false); - assert(PLANE_TRANSFORM_REFLECT_X.reflect_x == true); - assert(PLANE_TRANSFORM_REFLECT_X.reflect_y == false); - - assert(PLANE_TRANSFORM_REFLECT_Y.rotate_0 == false); - assert(PLANE_TRANSFORM_REFLECT_Y.rotate_90 == false); - assert(PLANE_TRANSFORM_REFLECT_Y.rotate_180 == false); - assert(PLANE_TRANSFORM_REFLECT_Y.rotate_270 == false); - assert(PLANE_TRANSFORM_REFLECT_Y.reflect_x == false); - assert(PLANE_TRANSFORM_REFLECT_Y.reflect_y == true); - - drm_plane_transform_t r = PLANE_TRANSFORM_NONE; - - r.rotate_0 = true; - r.reflect_x = true; - assert(r.u32 == (DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_X)); - - r.u32 = DRM_MODE_ROTATE_90 | DRM_MODE_REFLECT_Y; - assert(r.rotate_0 == false); - assert(r.rotate_90 == true); - assert(r.rotate_180 == false); - assert(r.rotate_270 == false); - assert(r.reflect_x == false); - assert(r.reflect_y == true); - (void) r; -} - -static int set_drm_client_caps(int fd, bool *supports_atomic_modesetting) { - int ok; - - ok = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not set DRM client universal planes capable. drmSetClientCap: %s\n", strerror(ok)); - return ok; - } - -#ifdef USE_LEGACY_KMS - *supports_atomic_modesetting = false; -#else - ok = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1); - if ((ok < 0) && (errno == EOPNOTSUPP)) { - if (supports_atomic_modesetting != NULL) { - *supports_atomic_modesetting = false; - } - } else if (ok < 0) { - ok = errno; - LOG_ERROR("Could not set DRM client atomic capable. drmSetClientCap: %s\n", strerror(ok)); - return ok; - } else { - if (supports_atomic_modesetting != NULL) { - *supports_atomic_modesetting = true; - } - } -#endif - - return 0; -} - -struct drmdev *drmdev_new_from_interface_fd(int fd, void *fd_metadata, const struct drmdev_interface *interface, void *userdata) { - struct gbm_device *gbm_device; - struct drmdev *drmdev; - uint64_t cap; - bool supports_atomic_modesetting; - bool supports_dumb_buffers; - int ok, master_fd, event_fd; - - assert_rotations_work(); - - drmdev = malloc(sizeof *drmdev); - if (drmdev == NULL) { - return NULL; - } - - master_fd = fd; - - ok = set_drm_client_caps(fd, &supports_atomic_modesetting); - if (ok != 0) { - goto fail_free_drmdev; - } - - cap = 0; - ok = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &cap); - if (ok < 0) { - supports_dumb_buffers = false; - } else { - supports_dumb_buffers = !!cap; - } - - drmdev->res = drmModeGetResources(fd); - if (drmdev->res == NULL) { - ok = errno; - LOG_ERROR("Could not get DRM device resources. drmModeGetResources: %s\n", strerror(ok)); - goto fail_free_drmdev; - } - - drmdev->plane_res = drmModeGetPlaneResources(fd); - if (drmdev->plane_res == NULL) { - ok = errno; - 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; - } - - ok = fetch_encoders(drmdev, &drmdev->encoders, &drmdev->n_encoders); - if (ok != 0) { - goto fail_free_connectors; - } - - ok = fetch_crtcs(drmdev, &drmdev->crtcs, &drmdev->n_crtcs); - if (ok != 0) { - goto fail_free_encoders; - } - - ok = fetch_planes(drmdev, &drmdev->planes, &drmdev->n_planes); - if (ok != 0) { - goto fail_free_crtcs; - } - - // Rockchip driver always wants the N-th primary/cursor plane to be associated with the N-th CRTC. - // If we don't respect this, commits will work but not actually show anything on screen. - int primary_plane_index = 0; - int cursor_plane_index = 0; - for (int i = 0; i < drmdev->n_planes; i++) { - if (drmdev->planes[i].type == DRM_PLANE_TYPE_PRIMARY) { - if ((drmdev->planes[i].possible_crtcs & (1 << primary_plane_index)) != 0) { - drmdev->planes[i].possible_crtcs = (1 << primary_plane_index); - } else { - LOG_DEBUG("Primary plane %d does not support CRTC %d.\n", primary_plane_index, primary_plane_index); - } - - primary_plane_index++; - } else if (drmdev->planes[i].type == DRM_PLANE_TYPE_CURSOR) { - if ((drmdev->planes[i].possible_crtcs & (1 << cursor_plane_index)) != 0) { - drmdev->planes[i].possible_crtcs = (1 << cursor_plane_index); - } else { - LOG_DEBUG("Cursor plane %d does not support CRTC %d.\n", cursor_plane_index, cursor_plane_index); - } - - cursor_plane_index++; - } - } - - gbm_device = gbm_create_device(drmdev->fd); - if (gbm_device == NULL) { - LOG_ERROR("Could not create GBM device.\n"); - goto fail_free_planes; - } - - 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; - } - - pthread_mutex_init(&drmdev->mutex, get_default_mutex_attrs()); - drmdev->n_refs = REFCOUNT_INIT_1; - drmdev->fd = fd; - drmdev->supports_atomic_modesetting = supports_atomic_modesetting; - drmdev->supports_dumb_buffers = supports_dumb_buffers; - drmdev->gbm_device = gbm_device; - drmdev->event_fd = event_fd; - memset(drmdev->per_crtc_state, 0, sizeof(drmdev->per_crtc_state)); - drmdev->master_fd = master_fd; - drmdev->master_fd_metadata = fd_metadata; - drmdev->interface = *interface; - drmdev->userdata = userdata; - list_inithead(&drmdev->fbs); - return drmdev; - -fail_close_event_fd: - close(event_fd); - -fail_destroy_gbm_device: - gbm_device_destroy(gbm_device); - -fail_free_planes: - free_planes(drmdev->planes, drmdev->n_planes); - -fail_free_crtcs: - free_crtcs(drmdev->crtcs, drmdev->n_crtcs); - -fail_free_encoders: - free_encoders(drmdev->encoders, drmdev->n_encoders); - -fail_free_connectors: - free_connectors(drmdev->connectors, drmdev->n_connectors); - -fail_free_plane_resources: - drmModeFreePlaneResources(drmdev->plane_res); - -fail_free_resources: - drmModeFreeResources(drmdev->res); - - // fail_close_master_fd: - // interface->close(master_fd, NULL, userdata); - -fail_free_drmdev: - free(drmdev); - - return NULL; -} - -struct drmdev *drmdev_new_from_path(const char *path, const struct drmdev_interface *interface, void *userdata) { - struct drmdev *drmdev; - void *fd_metadata; - int fd; - - ASSERT_NOT_NULL(path); - ASSERT_NOT_NULL(interface); - - fd = interface->open(path, O_RDWR, &fd_metadata, userdata); - if (fd < 0) { - LOG_ERROR("Could not open DRM device. interface->open: %s\n", strerror(errno)); - return NULL; - } - - drmdev = drmdev_new_from_interface_fd(fd, fd_metadata, interface, userdata); - if (drmdev == NULL) { - close(fd); - return NULL; - } - - return drmdev; -} - -static void drmdev_destroy(struct drmdev *drmdev) { - assert(refcount_is_zero(&drmdev->n_refs)); - - drmdev->interface.close(drmdev->master_fd, drmdev->master_fd_metadata, drmdev->userdata); - close(drmdev->event_fd); - gbm_device_destroy(drmdev->gbm_device); - free_planes(drmdev->planes, drmdev->n_planes); - free_crtcs(drmdev->crtcs, drmdev->n_crtcs); - free_encoders(drmdev->encoders, drmdev->n_encoders); - free_connectors(drmdev->connectors, drmdev->n_connectors); - drmModeFreePlaneResources(drmdev->plane_res); - drmModeFreeResources(drmdev->res); - free(drmdev); -} - -DEFINE_REF_OPS(drmdev, n_refs) - -int drmdev_get_fd(struct drmdev *drmdev) { - ASSERT_NOT_NULL(drmdev); - return drmdev->master_fd; -} - -int drmdev_get_event_fd(struct drmdev *drmdev) { - ASSERT_NOT_NULL(drmdev); - return drmdev->master_fd; -} - -bool drmdev_supports_dumb_buffers(struct drmdev *drmdev) { - return drmdev->supports_dumb_buffers; -} - -int drmdev_create_dumb_buffer( - struct drmdev *drmdev, - int width, - int height, - int bpp, - uint32_t *gem_handle_out, - uint32_t *pitch_out, - size_t *size_out -) { - struct drm_mode_create_dumb create_req; - int ok; - - ASSERT_NOT_NULL(drmdev); - ASSERT_NOT_NULL(gem_handle_out); - ASSERT_NOT_NULL(pitch_out); - ASSERT_NOT_NULL(size_out); - - memset(&create_req, 0, sizeof create_req); - create_req.width = width; - create_req.height = height; - create_req.bpp = bpp; - create_req.flags = 0; - - ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not create dumb buffer. ioctl: %s\n", strerror(errno)); - goto fail_return_ok; - } - - *gem_handle_out = create_req.handle; - *pitch_out = create_req.pitch; - *size_out = create_req.size; - return 0; - -fail_return_ok: - return ok; -} - -void drmdev_destroy_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle) { - struct drm_mode_destroy_dumb destroy_req; - int ok; - - ASSERT_NOT_NULL(drmdev); - - memset(&destroy_req, 0, sizeof destroy_req); - destroy_req.handle = gem_handle; - - ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); - if (ok < 0) { - LOG_ERROR("Could not destroy dumb buffer. ioctl: %s\n", strerror(errno)); - } -} - -void *drmdev_map_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle, size_t size) { - struct drm_mode_map_dumb map_req; - void *map; - int ok; - - ASSERT_NOT_NULL(drmdev); - - memset(&map_req, 0, sizeof map_req); - map_req.handle = gem_handle; - - ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req); - if (ok < 0) { - LOG_ERROR("Could not prepare dumb buffer mmap. ioctl: %s\n", strerror(errno)); - return NULL; - } - - map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, drmdev->fd, map_req.offset); - if (map == MAP_FAILED) { - LOG_ERROR("Could not mmap dumb buffer. mmap: %s\n", strerror(errno)); - return NULL; - } - - return map; -} - -void drmdev_unmap_dumb_buffer(struct drmdev *drmdev, void *map, size_t size) { - int ok; - - ASSERT_NOT_NULL(drmdev); - ASSERT_NOT_NULL(map); - (void) drmdev; - - ok = munmap(map, size); - if (ok < 0) { - LOG_ERROR("Couldn't unmap dumb buffer. munmap: %s\n", strerror(errno)); - } -} - -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 kms_req_builder *builder; - struct drm_crtc *crtc; - struct kms_req **last_flipped; - struct kms_req *req; - struct drmdev *drmdev; - - ASSERT_NOT_NULL(userdata); - builder = userdata; - req = userdata; - - (void) fd; - (void) sequence; - (void) crtc_id; - - drmdev = builder->drmdev; - for_each_crtc_in_drmdev(drmdev, crtc) { - if (crtc->id == crtc_id) { - break; - } - } - - ASSERT_NOT_NULL_MSG(crtc, "Invalid CRTC id"); - - if (drmdev->per_crtc_state[crtc->index].scanout_callback != NULL) { - uint64_t vblank_ns = tv_sec * 1000000000ull + tv_usec * 1000ull; - drmdev->per_crtc_state[crtc->index].scanout_callback(drmdev, vblank_ns, drmdev->per_crtc_state[crtc->index].userdata); - - // clear the scanout callback - drmdev->per_crtc_state[crtc->index].scanout_callback = NULL; - drmdev->per_crtc_state[crtc->index].destroy_callback = NULL; - drmdev->per_crtc_state[crtc->index].userdata = NULL; - } - - last_flipped = &drmdev->per_crtc_state[crtc->index].last_flipped; - if (*last_flipped != NULL) { - /// TODO: Remove this if we ever cache KMS reqs. - /// FIXME: This will fail if we're using blocking commits. - // assert(refcount_is_one(&((struct kms_req_builder*) *last_flipped)->n_refs)); - } - - kms_req_swap_ptrs(last_flipped, req); - kms_req_unref(req); -} - -static int drmdev_on_modesetting_fd_ready_locked(struct drmdev *drmdev) { - int ok; - - static drmEventContext ctx = { - .version = DRM_EVENT_CONTEXT_VERSION, - .vblank_handler = NULL, - .page_flip_handler = NULL, - .page_flip_handler2 = drmdev_on_page_flip_locked, - .sequence_handler = NULL, - }; - - ok = drmHandleEvent(drmdev->master_fd, &ctx); - if (ok != 0) { - return EIO; - } - - return 0; -} - -int drmdev_on_event_fd_ready(struct drmdev *drmdev) { - struct epoll_event events[16]; - int ok, n_events; - - 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; - } - } - - n_events = ok; - for (int i = 0; i < n_events; i++) { - // currently this could only be the root drmdev fd. - ASSERT_EQUALS(events[i].data.ptr, NULL); - ok = drmdev_on_modesetting_fd_ready_locked(drmdev); - if (ok != 0) { - goto fail_unlock; - } - } - - drmdev_unlock(drmdev); - - return 0; - -fail_unlock: - drmdev_unlock(drmdev); - return ok; -} - -struct gbm_device *drmdev_get_gbm_device(struct drmdev *drmdev) { - ASSERT_NOT_NULL(drmdev); - return drmdev->gbm_device; -} - -int drmdev_get_last_vblank_locked(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out) { - int ok; - - ASSERT_NOT_NULL(drmdev); - 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; -} - -int drmdev_get_last_vblank(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out) { - int ok; - - drmdev_lock(drmdev); - ok = drmdev_get_last_vblank_locked(drmdev, crtc_id, last_vblank_ns_out); - drmdev_unlock(drmdev); - - return ok; -} - -uint32_t drmdev_add_fb_multiplanar_locked( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - const uint32_t bo_handles[4], - const uint32_t pitches[4], - const uint32_t offsets[4], - bool has_modifiers, - const uint64_t modifiers[4] -) { - struct drm_fb *fb; - uint32_t fb_id; - int ok; - - /// TODO: Code in https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/drm_framebuffer.c#L257 - /// assumes handles, pitches, offsets and modifiers for unused planes are zero. Make sure that's the - /// case here. - ASSERT_NOT_NULL(drmdev); - assert(width > 0 && height > 0); - assert(bo_handles[0] != 0); - assert(pitches[0] != 0); - - fb = malloc(sizeof *fb); - if (fb == NULL) { - return 0; - } - - list_inithead(&fb->entry); - fb->id = 0; - fb->width = width; - fb->height = height; - fb->format = pixel_format; - fb->has_modifier = has_modifiers; - fb->modifier = modifiers[0]; - fb->flags = 0; - memcpy(fb->handles, bo_handles, sizeof(fb->handles)); - memcpy(fb->pitches, pitches, sizeof(fb->pitches)); - memcpy(fb->offsets, offsets, sizeof(fb->offsets)); - - fb_id = 0; - if (has_modifiers) { - ok = drmModeAddFB2WithModifiers( - drmdev->fd, - width, - height, - get_pixfmt_info(pixel_format)->drm_format, - bo_handles, - pitches, - offsets, - modifiers, - &fb_id, - DRM_MODE_FB_MODIFIERS - ); - if (ok < 0) { - LOG_ERROR("Couldn't add buffer as DRM fb. drmModeAddFB2WithModifiers: %s\n", strerror(-ok)); - goto fail_free_fb; - } - } else { - ok = drmModeAddFB2(drmdev->fd, width, height, get_pixfmt_info(pixel_format)->drm_format, bo_handles, pitches, offsets, &fb_id, 0); - if (ok < 0) { - LOG_ERROR("Couldn't add buffer as DRM fb. drmModeAddFB2: %s\n", strerror(-ok)); - goto fail_free_fb; - } - } - - fb->id = fb_id; - list_add(&fb->entry, &drmdev->fbs); - - assert(fb_id != 0); - return fb_id; - -fail_free_fb: - free(fb); - return 0; -} - -uint32_t drmdev_add_fb_multiplanar( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - const uint32_t bo_handles[4], - const uint32_t pitches[4], - const uint32_t offsets[4], - bool has_modifiers, - const uint64_t modifiers[4] -) { - uint32_t fb; - - drmdev_lock(drmdev); - - fb = drmdev_add_fb_multiplanar_locked(drmdev, width, height, pixel_format, bo_handles, pitches, offsets, has_modifiers, modifiers); - - drmdev_unlock(drmdev); - - return fb; -} - -uint32_t drmdev_add_fb_locked( - struct drmdev *drmdev, - 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 -) { - return drmdev_add_fb_multiplanar_locked( - drmdev, - width, - height, - pixel_format, - (uint32_t[4]){ bo_handle, 0 }, - (uint32_t[4]){ pitch, 0 }, - (uint32_t[4]){ offset, 0 }, - has_modifier, - (const uint64_t[4]){ modifier, 0 } - ); -} - -uint32_t drmdev_add_fb( - struct drmdev *drmdev, - 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 -) { - return drmdev_add_fb_multiplanar( - drmdev, - width, - height, - pixel_format, - (uint32_t[4]){ bo_handle, 0 }, - (uint32_t[4]){ pitch, 0 }, - (uint32_t[4]){ offset, 0 }, - has_modifier, - (const uint64_t[4]){ modifier, 0 } - ); -} - -uint32_t drmdev_add_fb_from_dmabuf_locked( - struct drmdev *drmdev, - 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 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 drmdev_add_fb_locked(drmdev, width, height, pixel_format, prime_fd, pitch, offset, has_modifier, modifier); -} - -uint32_t drmdev_add_fb_from_dmabuf( - struct drmdev *drmdev, - 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 fb; - - drmdev_lock(drmdev); - - fb = drmdev_add_fb_from_dmabuf_locked(drmdev, width, height, pixel_format, prime_fd, pitch, offset, has_modifier, modifier); - - drmdev_unlock(drmdev); - - return fb; -} - -uint32_t drmdev_add_fb_from_dmabuf_multiplanar_locked( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - const int prime_fds[4], - const uint32_t pitches[4], - const uint32_t offsets[4], - bool has_modifiers, - const uint64_t modifiers[4] -) { - uint32_t bo_handles[4] = { 0 }; - int ok; - - 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; - } - } - - return drmdev_add_fb_multiplanar_locked(drmdev, width, height, pixel_format, bo_handles, pitches, offsets, has_modifiers, modifiers); -} - -uint32_t drmdev_add_fb_from_dmabuf_multiplanar( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - const int prime_fds[4], - const uint32_t pitches[4], - const uint32_t offsets[4], - bool has_modifiers, - const uint64_t modifiers[4] -) { - uint32_t fb; - - drmdev_lock(drmdev); - - fb = drmdev_add_fb_from_dmabuf_multiplanar_locked( - drmdev, - width, - height, - pixel_format, - prime_fds, - pitches, - offsets, - has_modifiers, - modifiers - ); - - drmdev_unlock(drmdev); - - return fb; -} - -uint32_t drmdev_add_fb_from_gbm_bo_locked(struct drmdev *drmdev, struct gbm_bo *bo, bool cast_opaque) { - enum pixfmt format; - uint32_t fourcc; - int n_planes; - - n_planes = gbm_bo_get_plane_count(bo); - ASSERT(0 <= n_planes && n_planes <= 4); - - fourcc = gbm_bo_get_format(bo); - - if (!has_pixfmt_for_gbm_format(fourcc)) { - LOG_ERROR("GBM pixel format is not supported.\n"); - return 0; - } - - format = get_pixfmt_for_gbm_format(fourcc); - - if (cast_opaque) { - format = pixfmt_opaque(format); - } - - uint32_t handles[4]; - uint32_t pitches[4]; - - // Returns DRM_FORMAT_MOD_INVALID on failure, or DRM_FORMAT_MOD_LINEAR - // for dumb buffers. - uint64_t modifier = gbm_bo_get_modifier(bo); - bool has_modifiers = modifier != DRM_FORMAT_MOD_INVALID; - - for (int i = 0; i < n_planes; i++) { - // gbm_bo_get_handle_for_plane will return -1 (in gbm_bo_handle.s32) and - // set errno on failure. - errno = 0; - union gbm_bo_handle handle = gbm_bo_get_handle_for_plane(bo, i); - if (handle.s32 == -1) { - LOG_ERROR("Could not get GEM handle for plane %d: %s\n", i, strerror(errno)); - return 0; - } - - handles[i] = handle.u32; - - // gbm_bo_get_stride_for_plane will return 0 and set errno on failure. - errno = 0; - uint32_t pitch = gbm_bo_get_stride_for_plane(bo, i); - if (pitch == 0 && errno != 0) { - LOG_ERROR("Could not get framebuffer stride for plane %d: %s\n", i, strerror(errno)); - return 0; - } - - pitches[i] = pitch; - } - - for (int i = n_planes; i < 4; i++) { - handles[i] = 0; - pitches[i] = 0; - } - - return drmdev_add_fb_multiplanar_locked( - drmdev, - gbm_bo_get_width(bo), - gbm_bo_get_height(bo), - format, - handles, - pitches, - (uint32_t[4]){ - n_planes >= 1 ? gbm_bo_get_offset(bo, 0) : 0, - n_planes >= 2 ? gbm_bo_get_offset(bo, 1) : 0, - n_planes >= 3 ? gbm_bo_get_offset(bo, 2) : 0, - n_planes >= 4 ? gbm_bo_get_offset(bo, 3) : 0, - }, - has_modifiers, - (uint64_t[4]){ - n_planes >= 1 ? modifier : 0, - n_planes >= 2 ? modifier : 0, - n_planes >= 3 ? modifier : 0, - n_planes >= 4 ? modifier : 0, - } - ); -} - -uint32_t drmdev_add_fb_from_gbm_bo(struct drmdev *drmdev, struct gbm_bo *bo, bool cast_opaque) { - uint32_t fb; - - drmdev_lock(drmdev); - - fb = drmdev_add_fb_from_gbm_bo_locked(drmdev, bo, cast_opaque); - - drmdev_unlock(drmdev); - - return fb; -} - -int drmdev_rm_fb_locked(struct drmdev *drmdev, uint32_t fb_id) { - int ok; - - list_for_each_entry(struct drm_fb, fb, &drmdev->fbs, entry) { - if (fb->id == fb_id) { - list_del(&fb->entry); - free(fb); - break; - } - } - - ok = drmModeRmFB(drmdev->fd, fb_id); - if (ok < 0) { - ok = -ok; - LOG_ERROR("Could not remove DRM framebuffer. drmModeRmFB: %s\n", strerror(ok)); - return ok; - } - - return 0; -} - -int drmdev_rm_fb(struct drmdev *drmdev, uint32_t fb_id) { - int ok; - - drmdev_lock(drmdev); - - ok = drmdev_rm_fb_locked(drmdev, fb_id); - - drmdev_unlock(drmdev); - - return ok; -} - -bool drmdev_can_modeset(struct drmdev *drmdev) { - bool can_modeset; - - ASSERT_NOT_NULL(drmdev); - - drmdev_lock(drmdev); - - can_modeset = drmdev->master_fd > 0; - - drmdev_unlock(drmdev); - - return can_modeset; -} - -void drmdev_suspend(struct drmdev *drmdev) { - ASSERT_NOT_NULL(drmdev); - - drmdev_lock(drmdev); - - if (drmdev->master_fd <= 0) { - LOG_ERROR("drmdev_suspend was called, but drmdev is already suspended\n"); - drmdev_unlock(drmdev); - return; - } - - drmdev->interface.close(drmdev->master_fd, drmdev->master_fd_metadata, drmdev->userdata); - drmdev->master_fd = -1; - drmdev->master_fd_metadata = NULL; - - drmdev_unlock(drmdev); -} - -int drmdev_resume(struct drmdev *drmdev) { - drmDevicePtr device; - void *fd_metadata; - int ok, master_fd; - - ASSERT_NOT_NULL(drmdev); - - drmdev_lock(drmdev); - - if (drmdev->master_fd > 0) { - ok = EINVAL; - LOG_ERROR("drmdev_resume was called, but drmdev is already resumed\n"); - goto fail_unlock; - } - - ok = drmGetDevice(drmdev->fd, &device); - if (ok < 0) { - ok = errno; - LOG_ERROR("Couldn't query DRM device info. drmGetDevice: %s\n", strerror(ok)); - goto fail_unlock; - } - - ok = drmdev->interface.open(device->nodes[DRM_NODE_PRIMARY], O_CLOEXEC | O_NONBLOCK, &fd_metadata, drmdev->userdata); - if (ok < 0) { - ok = -ok; - LOG_ERROR("Couldn't open DRM device.\n"); - goto fail_free_device; - } - - master_fd = ok; - - drmFreeDevice(&device); - - ok = set_drm_client_caps(master_fd, NULL); - if (ok != 0) { - goto fail_close_device; - } - - drmdev->master_fd = master_fd; - drmdev->master_fd_metadata = fd_metadata; - drmdev_unlock(drmdev); - return 0; - -fail_close_device: - drmdev->interface.close(master_fd, fd_metadata, drmdev->userdata); - goto fail_unlock; - -fail_free_device: - drmFreeDevice(&device); - -fail_unlock: - drmdev_unlock(drmdev); - return ok; -} - -int drmdev_move_cursor(struct drmdev *drmdev, uint32_t crtc_id, struct vec2i pos) { - int ok = drmModeMoveCursor(drmdev->master_fd, crtc_id, pos.x, pos.y); - if (ok < 0) { - LOG_ERROR("Couldn't move mouse cursor. drmModeMoveCursor: %s\n", strerror(-ok)); - return -ok; - } - - return 0; -} - -static void drmdev_set_scanout_callback_locked( - struct drmdev *drmdev, - uint32_t crtc_id, - kms_scanout_cb_t scanout_callback, - void *userdata, - void_callback_t destroy_callback -) { - struct drm_crtc *crtc; - - ASSERT_NOT_NULL(drmdev); - - for_each_crtc_in_drmdev(drmdev, crtc) { - if (crtc->id == crtc_id) { - break; - } - } - - ASSERT_NOT_NULL_MSG(crtc, "Could not find CRTC with given id."); - - // If there's already a scanout callback configured, this is probably a state machine error. - // The scanout callback is configured in kms_req_commit and is cleared after it was called. - // So if this is called again this mean kms_req_commit is called but the previous frame wasn't committed yet. - ASSERT_EQUALS_MSG( - drmdev->per_crtc_state[crtc->index].scanout_callback, - NULL, - "There's already a scanout callback configured for this CRTC." - ); - drmdev->per_crtc_state[crtc->index].scanout_callback = scanout_callback; - drmdev->per_crtc_state[crtc->index].destroy_callback = destroy_callback; - drmdev->per_crtc_state[crtc->index].userdata = userdata; -} - -UNUSED static struct drm_plane *get_plane_by_id(struct drmdev *drmdev, uint32_t plane_id) { - struct drm_plane *plane; - - plane = NULL; - for (int i = 0; i < drmdev->n_planes; i++) { - if (drmdev->planes[i].id == plane_id) { - plane = drmdev->planes + i; - break; - } - } - - return plane; -} - -struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector) { - bool found = connector == NULL; - for (size_t i = 0; i < drmdev->n_connectors; i++) { - if (drmdev->connectors + i == connector) { - found = true; - } else if (found) { - return drmdev->connectors + i; - } - } - - 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; - } - } - - return NULL; -} - -struct drm_crtc *__next_crtc(const struct drmdev *drmdev, const struct drm_crtc *crtc) { - bool found = crtc == NULL; - for (size_t i = 0; i < drmdev->n_crtcs; i++) { - if (drmdev->crtcs + i == crtc) { - found = true; - } else if (found) { - return drmdev->crtcs + i; - } - } - - return NULL; -} - -struct drm_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; -} - -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 NULL; -} - -#ifdef DEBUG_DRM_PLANE_ALLOCATIONS - #define LOG_DRM_PLANE_ALLOCATION_DEBUG LOG_DEBUG -#else - #define LOG_DRM_PLANE_ALLOCATION_DEBUG(...) -#endif - -static bool plane_qualifies( - // clang-format off - struct drm_plane *plane, - bool allow_primary, - bool allow_overlay, - bool allow_cursor, - enum pixfmt format, - bool has_modifier, uint64_t modifier, - bool has_zpos, int64_t zpos_lower_limit, int64_t zpos_upper_limit, - bool has_rotation, drm_plane_transform_t rotation, - bool has_id_range, uint32_t id_lower_limit - // clang-format on -) { - LOG_DRM_PLANE_ALLOCATION_DEBUG(" checking if plane with id %" PRIu32 " qualifies...\n", plane->id); - - if (plane->type == kPrimary_DrmPlaneType) { - if (!allow_primary) { - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane type is primary but allow_primary is false\n"); - return false; - } - } else if (plane->type == kOverlay_DrmPlaneType) { - if (!allow_overlay) { - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane type is overlay but allow_overlay is false\n"); - return false; - } - } else if (plane->type == kCursor_DrmPlaneType) { - if (!allow_cursor) { - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane type is cursor but allow_cursor is false\n"); - return false; - } - } else { - ASSERT(false); - } - - if (has_modifier) { - if (!plane->supported_modified_formats_blob) { - // return false if we want a modified format but the plane doesn't support modified formats - LOG_DRM_PLANE_ALLOCATION_DEBUG( - " does not qualify: framebuffer has modifier %" PRIu64 " but plane does not support modified formats\n", - modifier - ); - return false; - } - - struct { - enum pixfmt format; - uint64_t modifier; - bool found; - } context = { - .format = format, - .modifier = modifier, - .found = false, - }; - - // Check if the requested format & modifier is supported. - drm_plane_for_each_modified_format(plane, check_modified_format_supported, &context); - - // Otherwise fail. - if (!context.found) { - LOG_DRM_PLANE_ALLOCATION_DEBUG( - " does not qualify: plane does not support the modified format %s, %" PRIu64 ".\n", - get_pixfmt_info(format)->name, - modifier - ); - - // not found in the supported modified format list - return false; - } - } else { - // we don't want a modified format, return false if the format is not in the list - // of supported (unmodified) formats - if (!plane->supported_formats[format]) { - LOG_DRM_PLANE_ALLOCATION_DEBUG( - " does not qualify: plane does not support the (unmodified) format %s.\n", - get_pixfmt_info(format)->name - ); - return false; - } - } - - if (has_zpos) { - if (!plane->has_zpos) { - // return false if we want a zpos but the plane doesn't support one - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: zpos constraints specified but plane doesn't have a zpos property.\n"); - 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 - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane limits cannot satisfy the specified zpos constraints.\n"); - LOG_DRM_PLANE_ALLOCATION_DEBUG( - " plane zpos range: %" PRIi64 " <= zpos <= %" PRIi64 ", given zpos constraints: %" PRIi64 " <= zpos <= %" PRIi64 ".\n", - plane->min_zpos, - plane->max_zpos, - zpos_lower_limit, - zpos_upper_limit - ); - return false; - } - } - if (has_id_range && plane->id < id_lower_limit) { - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane id does not satisfy the given plane id constrains.\n"); - LOG_DRM_PLANE_ALLOCATION_DEBUG(" plane id: %" PRIu32 ", plane id lower limit: %" PRIu32 "\n", plane->id, id_lower_limit); - return false; - } - if (has_rotation) { - if (!plane->has_rotation) { - // return false if the plane doesn't support rotation - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: explicit rotation requested but plane has no rotation property.\n"); - 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 - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane has hardcoded rotation that doesn't match the requested rotation.\n" - ); - return false; - } else if (rotation.u32 & ~plane->supported_rotations.u32) { - // return false if we can't construct the rotation using the rotation - // bits that are supported by the plane - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: requested rotation is not supported by the plane.\n"); - return false; - } - } - - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does qualify.\n"); - return true; -} - -static struct drm_plane *allocate_plane( - // clang-format off - struct kms_req_builder *builder, - bool allow_primary, - bool allow_overlay, - bool allow_cursor, - enum pixfmt format, - bool has_modifier, uint64_t modifier, - bool has_zpos, int64_t zpos_lower_limit, int64_t zpos_upper_limit, - bool has_rotation, drm_plane_transform_t rotation, - bool has_id_range, uint32_t id_lower_limit - // clang-format on -) { - for (int i = 0; i < BITSET_SIZE(builder->available_planes); i++) { - struct drm_plane *plane = builder->drmdev->planes + i; - - if (BITSET_TEST(builder->available_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 - BITSET_CLEAR(builder->available_planes, i); - return plane; - } - } - - // 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 == NULL) { - LOG_ERROR("Could not release invalid plane %" PRIu32 ".\n", plane_id); - return; - } - - assert(!BITSET_TEST(builder->available_planes, index)); - BITSET_SET(builder->available_planes, index); -} - -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; - bool supports_atomic_modesetting; - - ASSERT_NOT_NULL(drmdev); - assert(crtc_id != 0 && crtc_id != 0xFFFFFFFF); - - drmdev_lock(drmdev); - - for_each_crtc_in_drmdev(drmdev, crtc) { - if (crtc->id == crtc_id) { - break; - } - } - - if (crtc == NULL) { - LOG_ERROR("Invalid CRTC id: %" PRId32 "\n", crtc_id); - goto fail_unlock; - } - - builder = malloc(sizeof *builder); - if (builder == NULL) { - goto fail_unlock; - } - - supports_atomic_modesetting = drmdev->supports_atomic_modesetting; - - if (supports_atomic_modesetting) { - req = drmModeAtomicAlloc(); - if (req == NULL) { - goto fail_free_builder; - } - - // set the CRTC to active - drmModeAtomicAddProperty(req, crtc->id, crtc->ids.active, 1); - } else { - req = NULL; - } - - min_zpos = INT64_MAX; - BITSET_ZERO(builder->available_planes); - for (int i = 0; i < drmdev->n_planes; i++) { - struct drm_plane *plane = drmdev->planes + i; - - if (plane->possible_crtcs & crtc->bitmask) { - BITSET_SET(builder->available_planes, i); - if (plane->has_zpos && plane->min_zpos < min_zpos) { - min_zpos = plane->min_zpos; - } - } - } - - drmdev_unlock(drmdev); - - builder->n_refs = REFCOUNT_INIT_1; - builder->drmdev = drmdev_ref(drmdev); - // right now they're the same, but they might not be in the future. - builder->use_legacy = !supports_atomic_modesetting; - builder->supports_atomic = supports_atomic_modesetting; - builder->connector = NULL; - builder->crtc = crtc; - builder->req = req; - builder->next_zpos = min_zpos; - builder->n_layers = 0; - builder->has_mode = false; - builder->unset_mode = false; - return builder; - -fail_free_builder: - free(builder); - -fail_unlock: - drmdev_unlock(drmdev); - return NULL; -} - -static void kms_req_builder_destroy(struct kms_req_builder *builder) { - /// TODO: Is this complete? - for (int i = 0; i < builder->n_layers; i++) { - if (builder->layers[i].release_callback != NULL) { - builder->layers[i].release_callback(builder->layers[i].release_callback_userdata); - } - } - if (builder->req != NULL) { - drmModeAtomicFree(builder->req); - } - drmdev_unref(builder->drmdev); - free(builder); -} - -DEFINE_REF_OPS(kms_req_builder, n_refs) - -struct drmdev *kms_req_builder_get_drmdev(struct kms_req_builder *builder) { - return builder->drmdev; -} - -struct drm_crtc *kms_req_builder_get_crtc(struct kms_req_builder *builder) { - return builder->crtc; -} - -bool kms_req_builder_prefer_next_layer_opaque(struct kms_req_builder *builder) { - ASSERT_NOT_NULL(builder); - return builder->n_layers == 0; -} - -int kms_req_builder_set_mode(struct kms_req_builder *builder, const drmModeModeInfo *mode) { - ASSERT_NOT_NULL(builder); - ASSERT_NOT_NULL(mode); - builder->has_mode = true; - builder->mode = *mode; - return 0; -} - -int kms_req_builder_unset_mode(struct kms_req_builder *builder) { - ASSERT_NOT_NULL(builder); - assert(!builder->has_mode); - builder->unset_mode = true; - return 0; -} - -int kms_req_builder_set_connector(struct kms_req_builder *builder, uint32_t connector_id) { - struct drm_connector *conn; - - ASSERT_NOT_NULL(builder); - assert(DRM_ID_IS_VALID(connector_id)); - - for_each_connector_in_drmdev(builder->drmdev, conn) { - if (conn->id == connector_id) { - break; - } - } - - if (conn == NULL) { - LOG_ERROR("Could not find connector with id %" PRIu32 "\n", connector_id); - return EINVAL; - } - - builder->connector = conn; - return 0; -} - -int kms_req_builder_push_fb_layer( - struct kms_req_builder *builder, - const struct kms_fb_layer *layer, - kms_fb_release_cb_t release_callback, - kms_deferred_fb_release_cb_t deferred_release_callback, - void *userdata -) { - struct drm_plane *plane; - int64_t zpos; - bool has_zpos; - bool close_in_fence_fd_after; - int ok, index; - - ASSERT_NOT_NULL(builder); - ASSERT_NOT_NULL(layer); - ASSERT_NOT_NULL(release_callback); - ASSERT_EQUALS_MSG(deferred_release_callback, NULL, "deferred release callbacks are not supported right now."); - - if (builder->use_legacy && builder->supports_atomic && builder->n_layers > 1) { - // if we already have a first layer and we should use legacy modesetting even though the kernel driver - // supports atomic modesetting, return EINVAL. - // if the driver supports atomic modesetting, drmModeSetPlane will block for vblank, so we can't use it, - // and we can't use drmModeAtomicCommit for non-blocking multi-plane commits of course. - // For the first layer we can use drmModePageFlip though. - LOG_DEBUG("Can't do multi-plane commits when using legacy modesetting (and driver supports atomic modesetting).\n"); - return EINVAL; - } - - 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; - - // If we should prefer a cursor plane, try to find one first. - plane = NULL; - if (layer->prefer_cursor) { - plane = allocate_plane( - // clang-format off - builder, - /* allow_primary */ false, - /* allow_overlay */ false, - /* allow_cursor */ true, - /* format */ layer->format, - /* modifier */ layer->has_modifier, layer->modifier, - /* zpos */ false, 0, 0, - /* rotation */ layer->has_rotation, layer->rotation, - /* id_range */ false, 0 - // clang-format on - ); - if (plane == NULL) { - LOG_DEBUG("Couldn't find a fitting cursor plane.\n"); - } - } - - /// TODO: Not sure we can use crtc_x, crtc_y, etc with primary planes - if (plane == NULL && 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( - // clang-format off - builder, - /* allow_primary */ true, - /* allow_overlay */ false, - /* allow_cursor */ false, - /* format */ layer->format, - /* modifier */ layer->has_modifier, layer->modifier, - /* zpos */ false, 0, 0, - /* rotation */ layer->has_rotation, layer->rotation, - /* id_range */ false, 0 - // clang-format on - ); - - 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( - // clang-format off - builder, - /* allow_primary */ true, - /* allow_overlay */ false, - /* allow_cursor */ false, - /* format */ pixfmt_opaque(layer->format), - /* modifier */ layer->has_modifier, layer->modifier, - /* zpos */ false, 0, 0, - /* rotation */ layer->has_rotation, layer->rotation, - /* id_range */ false, 0 - // clang-format on - ); - } - } else if (plane == NULL) { - // First try to find an overlay plane with a higher zpos. - plane = allocate_plane( - // clang-format off - builder, - /* allow_primary */ false, - /* allow_overlay */ true, - /* allow_cursor */ false, - /* format */ layer->format, - /* modifier */ layer->has_modifier, layer->modifier, - /* zpos */ true, builder->next_zpos, INT64_MAX, - /* rotation */ layer->has_rotation, layer->rotation, - /* id_range */ false, 0 - // clang-format on - ); - - // 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( - // clang-format off - builder, - /* allow_primary */ false, - /* allow_overlay */ true, - /* allow_cursor */ false, - /* format */ layer->format, - /* modifier */ layer->has_modifier, layer->modifier, - /* zpos */ false, 0, 0, - /* rotation */ layer->has_rotation, layer->rotation, - /* id_range */ true, builder->layers[index - 1].plane_id + 1 - // clang-format on - ); - } - } - - if (plane == NULL) { - LOG_ERROR("Could not find a suitable unused DRM plane for pushing the framebuffer.\n"); - return EIO; - } - - // Now that we have a plane, use the minimum zpos - // that's both higher than the last layers zpos and - // also supported by the plane. - // This will also work for planes with hardcoded zpos. - has_zpos = plane->has_zpos; - if (has_zpos) { - zpos = builder->next_zpos; - if (plane->min_zpos > zpos) { - zpos = plane->min_zpos; - } - } else { - // just to silence an uninitialized use warning below. - zpos = 0; - } - - if (builder->use_legacy) { - } 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); - } - - if (layer->has_rotation && plane->has_rotation && !plane->has_hardcoded_rotation) { - drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.rotation, layer->rotation.u64); - } - - if (index == 0) { - if (plane->has_alpha) { - drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.alpha, DRM_BLEND_ALPHA_OPAQUE); - } - - if (plane->has_blend_mode && plane->supported_blend_modes[kNone_DrmBlendMode]) { - drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.pixel_blend_mode, kNone_DrmBlendMode); - } - } - } - - // 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; - } - } - - /// 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 (has_zpos) { - builder->next_zpos = zpos + 1; - } - builder->layers[index].layer = *layer; - builder->layers[index].plane_id = plane->id; - builder->layers[index].plane = plane; - builder->layers[index].set_zpos = has_zpos; - builder->layers[index].zpos = zpos; - builder->layers[index].set_rotation = layer->has_rotation; - builder->layers[index].rotation = layer->rotation; - builder->layers[index].release_callback = release_callback; - builder->layers[index].deferred_release_callback = deferred_release_callback; - builder->layers[index].release_callback_userdata = userdata; - return 0; - -fail_release_plane: - release_plane(builder, plane->id); - return ok; -} - -int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder, int64_t *zpos_out) { - ASSERT_NOT_NULL(builder); - ASSERT_NOT_NULL(zpos_out); - *zpos_out = builder->next_zpos++; - return 0; -} - -struct kms_req *kms_req_builder_build(struct kms_req_builder *builder) { - return (struct kms_req *) kms_req_builder_ref(builder); -} - -UNUSED struct kms_req *kms_req_ref(struct kms_req *req) { - return (struct kms_req *) kms_req_builder_ref((struct kms_req_builder *) req); -} - -UNUSED void kms_req_unref(struct kms_req *req) { - return kms_req_builder_unref((struct kms_req_builder *) req); -} - -UNUSED void kms_req_unrefp(struct kms_req **req) { - return kms_req_builder_unrefp((struct kms_req_builder **) req); -} - -UNUSED void kms_req_swap_ptrs(struct kms_req **oldp, struct kms_req *new) { - return kms_req_builder_swap_ptrs((struct kms_req_builder **) oldp, (struct kms_req_builder *) new); -} - -static bool drm_plane_is_active(struct drm_plane *plane) { - return plane->committed_state.fb_id != 0 && plane->committed_state.crtc_id != 0; -} - -static int -kms_req_commit_common(struct kms_req *req, bool blocking, kms_scanout_cb_t scanout_cb, void *userdata, void_callback_t destroy_cb) { - struct kms_req_builder *builder; - struct drm_mode_blob *mode_blob; - uint32_t flags; - bool internally_blocking; - bool update_mode; - int ok; - - internally_blocking = false; - update_mode = false; - mode_blob = NULL; - - ASSERT_NOT_NULL(req); - builder = (struct kms_req_builder *) req; - - drmdev_lock(builder->drmdev); - - if (builder->drmdev->master_fd < 0) { - LOG_ERROR("Commit requested, but drmdev doesn't have a DRM master fd right now.\n"); - ok = EBUSY; - goto fail_unlock; - } - - if (!is_drm_master(builder->drmdev->master_fd)) { - LOG_ERROR("Commit requested, but drmdev is paused right now.\n"); - ok = EBUSY; - goto fail_unlock; - } - - // only change the mode if the new mode differs from the old one - - /// TOOD: If this is not a standard mode reported by connector/CRTC, - /// is there a way to verify if it is valid? (maybe use DRM_MODE_ATOMIC_TEST) - - // this could be a single expression but this way you see a bit better what's going on. - // We need to upload the new mode blob if: - // - we have a new mode - // - and: we don't have an old mode - // - or: the old mode differs from the new mode - bool upload_mode = false; - if (builder->has_mode) { - if (!builder->crtc->committed_state.has_mode) { - upload_mode = true; - } else if (memcmp(&builder->crtc->committed_state.mode, &builder->mode, sizeof(drmModeModeInfo)) != 0) { - upload_mode = true; - } - } - - if (upload_mode) { - update_mode = true; - mode_blob = drm_mode_blob_new(builder->drmdev->fd, &builder->mode); - if (mode_blob == NULL) { - ok = EIO; - goto fail_unlock; - } - } else if (builder->unset_mode) { - update_mode = true; - mode_blob = NULL; - } - - if (builder->use_legacy) { - ASSERT_EQUALS(builder->layers[0].layer.dst_x, 0); - ASSERT_EQUALS(builder->layers[0].layer.dst_y, 0); - ASSERT_EQUALS(builder->layers[0].layer.dst_w, builder->mode.hdisplay); - ASSERT_EQUALS(builder->layers[0].layer.dst_h, builder->mode.vdisplay); - - /// TODO: Do we really need to assert this? - ASSERT_NOT_NULL(builder->connector); - - bool needs_set_crtc = update_mode; - - // check if the plane pixel format changed. - // that needs a drmModeSetCrtc for legacy KMS as well. - // get the current, committed fb for the plane, check if we have info - // for it (we can't use drmModeGetFB2 since that's not present on debian buster) - // and if we're not absolutely sure the formats match, set needs_set_crtc - // too. - if (!needs_set_crtc) { - struct kms_req_layer *layer = builder->layers + 0; - struct drm_plane *plane = layer->plane; - ASSERT_NOT_NULL(plane); - - if (plane->committed_state.has_format && plane->committed_state.format == layer->layer.format) { - needs_set_crtc = false; - } else { - needs_set_crtc = true; - } - -#ifdef DEBUG - drmModeFBPtr committed_fb = drmModeGetFB(builder->drmdev->master_fd, plane->committed_state.fb_id); - if (committed_fb == NULL) { - needs_set_crtc = true; - } else { - needs_set_crtc = true; - - list_for_each_entry(struct drm_fb, fb, &builder->drmdev->fbs, entry) { - if (fb->id == committed_fb->fb_id) { - ASSERT_EQUALS(fb->format, plane->committed_state.format); - - if (fb->format == layer->layer.format) { - needs_set_crtc = false; - } - } - - if (fb->id == layer->layer.drm_fb_id) { - ASSERT_EQUALS(fb->format, layer->layer.format); - } - } - } - - drmModeFreeFB(committed_fb); -#endif - } - - /// TODO: Handle {src,dst}_{x,y,w,h} here - /// TODO: Handle setting other properties as well - if (needs_set_crtc) { - /// TODO: Fetch new connector or current connector here since we seem to need it for drmModeSetCrtc - ok = drmModeSetCrtc( - builder->drmdev->master_fd, - builder->crtc->id, - builder->layers[0].layer.drm_fb_id, - 0, - 0, - (uint32_t[1]){ builder->connector->id }, - 1, - builder->unset_mode ? NULL : &builder->mode - ); - if (ok != 0) { - ok = errno; - LOG_ERROR("Could not commit display update. drmModeSetCrtc: %s\n", strerror(ok)); - goto fail_maybe_destroy_mode_blob; - } - - internally_blocking = true; - } else { - ok = drmModePageFlip( - builder->drmdev->master_fd, - builder->crtc->id, - builder->layers[0].layer.drm_fb_id, - DRM_MODE_PAGE_FLIP_EVENT, - kms_req_builder_ref(builder) - ); - if (ok != 0) { - ok = errno; - LOG_ERROR("Could not commit display update. drmModePageFlip: %s\n", strerror(ok)); - goto fail_unref_builder; - } - } - - // This should also be ensured by kms_req_builder_push_fb_layer - ASSERT_MSG( - !(builder->supports_atomic && builder->n_layers > 1), - "There can be at most one framebuffer layer when the KMS device supports atomic modesetting but we are " - "using legacy modesetting." - ); - - /// TODO: Call drmModeSetPlane for all other layers - /// TODO: Assert here - } else { - /// TODO: If we can do explicit fencing, don't use the page flip event. - /// TODO: Can we set OUT_FENCE_PTR even though we didn't set any IN_FENCE_FDs? - flags = DRM_MODE_PAGE_FLIP_EVENT | (blocking ? 0 : DRM_MODE_ATOMIC_NONBLOCK) | (update_mode ? DRM_MODE_ATOMIC_ALLOW_MODESET : 0); - - // All planes that are not used by us and are connected to our CRTC - // should be disabled. - { - int i; - BITSET_FOREACH_SET(i, builder->available_planes, 32) { - struct drm_plane *plane = builder->drmdev->planes + i; - - if (drm_plane_is_active(plane) && plane->committed_state.crtc_id == builder->crtc->id) { - drmModeAtomicAddProperty(builder->req, plane->id, plane->ids.crtc_id, 0); - drmModeAtomicAddProperty(builder->req, plane->id, plane->ids.fb_id, 0); - } - } - } - - if (builder->connector != NULL) { - // add the CRTC_ID property if that was explicitly set - drmModeAtomicAddProperty(builder->req, builder->connector->id, builder->connector->ids.crtc_id, builder->crtc->id); - } - - if (update_mode) { - if (mode_blob != NULL) { - drmModeAtomicAddProperty(builder->req, builder->crtc->id, builder->crtc->ids.mode_id, mode_blob->blob_id); - } else { - drmModeAtomicAddProperty(builder->req, builder->crtc->id, builder->crtc->ids.mode_id, 0); - } - } - - /// TODO: If we're on raspberry pi and only have one layer, we can do an async pageflip - /// on the primary plane to replace the next queued frame. (To do _real_ triple buffering - /// with fully decoupled framerate, potentially) - ok = drmModeAtomicCommit(builder->drmdev->master_fd, builder->req, flags, kms_req_builder_ref(builder)); - if (ok != 0) { - ok = errno; - LOG_ERROR("Could not commit display update. drmModeAtomicCommit: %s\n", strerror(ok)); - goto fail_unref_builder; - } - } - - // update struct drm_plane.committed_state for all planes - for (int i = 0; i < builder->n_layers; i++) { - struct drm_plane *plane = builder->layers[i].plane; - struct kms_req_layer *layer = builder->layers + i; - - plane->committed_state.crtc_id = builder->crtc->id; - plane->committed_state.fb_id = layer->layer.drm_fb_id; - plane->committed_state.src_x = layer->layer.src_x; - plane->committed_state.src_y = layer->layer.src_y; - plane->committed_state.src_w = layer->layer.src_w; - plane->committed_state.src_h = layer->layer.src_h; - plane->committed_state.crtc_x = layer->layer.dst_x; - plane->committed_state.crtc_y = layer->layer.dst_y; - plane->committed_state.crtc_w = layer->layer.dst_w; - plane->committed_state.crtc_h = layer->layer.dst_h; - - if (builder->layers[i].set_zpos) { - plane->committed_state.zpos = layer->zpos; - } - if (builder->layers[i].set_rotation) { - plane->committed_state.rotation = layer->rotation; - } - - plane->committed_state.has_format = true; - plane->committed_state.format = layer->layer.format; - - // builder->layers[i].plane->committed_state.alpha = layer->alpha; - // builder->layers[i].plane->committed_state.blend_mode = builder->layers[i].layer.blend_mode; - } - - // update struct drm_crtc.committed_state - 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; - } - } - - // update struct drm_connector.committed_state - builder->connector->committed_state.crtc_id = builder->crtc->id; - // builder->connector->committed_state.encoder_id = 0; - - drmdev_set_scanout_callback_locked(builder->drmdev, builder->crtc->id, scanout_cb, userdata, destroy_cb); - - if (internally_blocking) { - uint64_t sequence = 0; - uint64_t ns = 0; - int ok; - - ok = drmCrtcGetSequence(builder->drmdev->fd, builder->crtc->id, &sequence, &ns); - if (ok != 0) { - ok = errno; - LOG_ERROR("Could not get vblank timestamp. drmCrtcGetSequence: %s\n", strerror(ok)); - goto fail_unref_builder; - } - - drmdev_on_page_flip_locked( - builder->drmdev->fd, - (unsigned int) sequence, - ns / 1000000000, - ns / 1000, - builder->crtc->id, - kms_req_ref(req) - ); - } else 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_unset_scanout_callback; - } - } - - drmdev_unlock(builder->drmdev); - - return 0; - -fail_unset_scanout_callback: - builder->drmdev->per_crtc_state[builder->crtc->index].scanout_callback = NULL; - builder->drmdev->per_crtc_state[builder->crtc->index].destroy_callback = NULL; - builder->drmdev->per_crtc_state[builder->crtc->index].userdata = NULL; - goto fail_unlock; - -fail_unref_builder: - kms_req_builder_unref(builder); - -fail_maybe_destroy_mode_blob: - if (mode_blob != NULL) - drm_mode_blob_destroy(mode_blob); - -fail_unlock: - drmdev_unlock(builder->drmdev); - - return ok; -} - -void set_vblank_ns(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata) { - uint64_t *vblank_ns_out; - - ASSERT_NOT_NULL(userdata); - vblank_ns_out = userdata; - (void) drmdev; - - *vblank_ns_out = vblank_ns; -} - -int kms_req_commit_blocking(struct kms_req *req, uint64_t *vblank_ns_out) { - uint64_t vblank_ns; - int ok; - - vblank_ns = int64_to_uint64(-1); - ok = kms_req_commit_common(req, true, set_vblank_ns, &vblank_ns, NULL); - if (ok != 0) { - return ok; - } - - // make sure the vblank_ns is actually set - assert(vblank_ns != int64_to_uint64(-1)); - if (vblank_ns_out != NULL) { - *vblank_ns_out = vblank_ns; - } - - return 0; -} - -int kms_req_commit_nonblocking(struct kms_req *req, kms_scanout_cb_t scanout_cb, void *userdata, void_callback_t destroy_cb) { - return kms_req_commit_common(req, false, scanout_cb, userdata, destroy_cb); -} diff --git a/src/notifier_listener.c b/src/notifier_listener.c index d8156538..943ba3ff 100644 --- a/src/notifier_listener.c +++ b/src/notifier_listener.c @@ -48,7 +48,7 @@ int value_notifier_init(struct notifier *notifier, void *initial_value, void_cal return 0; } -struct notifier *change_notifier_new() { +struct notifier *change_notifier_new(void) { struct notifier *n; int ok; diff --git a/src/notifier_listener.h b/src/notifier_listener.h index 3861182e..cbc042b9 100644 --- a/src/notifier_listener.h +++ b/src/notifier_listener.h @@ -59,7 +59,7 @@ int value_notifier_init(struct notifier *notifier, void *initial_value, void_cal * For the behaviour of change notifiers, see @ref change_notifier_init. * */ -struct notifier *change_notifier_new(); +struct notifier *change_notifier_new(void); /** * @brief Create a new heap allocated value notifier. diff --git a/src/pixel_format.c b/src/pixel_format.c index 6b936619..a1d09b2f 100644 --- a/src/pixel_format.c +++ b/src/pixel_format.c @@ -77,7 +77,7 @@ const size_t n_pixfmt_infos = n_pixfmt_infos_constexpr; COMPILE_ASSERT(n_pixfmt_infos_constexpr == PIXFMT_MAX + 1); #ifdef DEBUG -void assert_pixfmt_list_valid() { +void assert_pixfmt_list_valid(void) { for (enum pixfmt format = 0; format < PIXFMT_COUNT; format++) { assert(pixfmt_infos[format].format == format); } diff --git a/src/pixel_format.h b/src/pixel_format.h index 3ad991ee..14119039 100644 --- a/src/pixel_format.h +++ b/src/pixel_format.h @@ -359,7 +359,7 @@ extern const struct pixfmt_info pixfmt_infos[]; extern const size_t n_pixfmt_infos; #ifdef DEBUG -void assert_pixfmt_list_valid(); +void assert_pixfmt_list_valid(void); #endif /** diff --git a/src/platformchannel.c b/src/platformchannel.c index 4bc13be1..020727c0 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -165,45 +166,48 @@ static int _readSize(const uint8_t **pbuffer, uint32_t *psize, size_t *remaining return 0; } -int platch_free_value_std(struct std_value *value) { - int ok; - +void platch_free_value_std(struct std_value *value) { switch (value->type) { + case kStdNull: + case kStdTrue: + case kStdFalse: + case kStdInt32: + case kStdInt64: + case kStdLargeInt: + case kStdFloat64: break; case kStdString: free(value->string_value); break; + case kStdUInt8Array: + case kStdInt32Array: + case kStdInt64Array: + case kStdFloat64Array: break; case kStdList: for (int i = 0; i < value->size; i++) { - ok = platch_free_value_std(&(value->list[i])); - if (ok != 0) - return ok; + platch_free_value_std(value->list + i); } free(value->list); break; case kStdMap: for (int i = 0; i < value->size; i++) { - ok = platch_free_value_std(&(value->keys[i])); - if (ok != 0) - return ok; - ok = platch_free_value_std(&(value->values[i])); - if (ok != 0) - return ok; + platch_free_value_std(value->keys + i); + platch_free_value_std(value->values + i); } free(value->keys); break; + case kStdFloat32Array: break; default: break; } - - return 0; } -int platch_free_json_value(struct json_value *value, bool shallow) { - int ok; - +void platch_free_json_value(struct json_value *value, bool shallow) { switch (value->type) { + case kJsonNull: + case kJsonTrue: + case kJsonFalse: + case kJsonNumber: + case kJsonString: break; case kJsonArray: if (!shallow) { for (int i = 0; i < value->size; i++) { - ok = platch_free_json_value(&(value->array[i]), false); - if (ok != 0) - return ok; + platch_free_json_value(&(value->array[i]), false); } } @@ -212,9 +216,7 @@ int platch_free_json_value(struct json_value *value, bool shallow) { case kJsonObject: if (!shallow) { for (int i = 0; i < value->size; i++) { - ok = platch_free_json_value(&(value->values[i]), false); - if (ok != 0) - return ok; + platch_free_json_value(&(value->values[i]), false); } } @@ -223,11 +225,11 @@ int platch_free_json_value(struct json_value *value, bool shallow) { break; default: break; } - - return 0; } -int platch_free_obj(struct platch_obj *object) { + +void platch_free_obj(struct platch_obj *object) { switch (object->codec) { + case kNotImplemented: break; case kStringCodec: free(object->string_value); break; case kBinaryCodec: break; case kJSONMessageCodec: platch_free_json_value(&(object->json_value), false); break; @@ -235,12 +237,34 @@ int platch_free_obj(struct platch_obj *object) { case kStandardMethodCall: free(object->method); platch_free_value_std(&(object->std_arg)); + break; + case kStandardMethodCallResponse: + if (object->success) { + platch_free_value_std(&(object->std_result)); + } else { + free(object->error_code); + if (object->error_msg) { + free(object->error_msg); + } + platch_free_value_std(&(object->std_error_details)); + } + break; case kJSONMethodCall: platch_free_json_value(&(object->json_arg), false); break; - default: break; - } + case kJSONMethodCallResponse: + if (object->success) { + platch_free_json_value(&(object->json_result), false); + } else { + free(object->error_code); + if (object->error_msg) { + free(object->error_msg); + } + platch_free_json_value(&(object->json_error_details), false); + } - return 0; + break; + default: UNREACHABLE(); + } } int platch_calc_value_size_std(struct std_value *value, size_t *size_out) { @@ -330,6 +354,15 @@ int platch_calc_value_size_std(struct std_value *value, size_t *size_out) { } break; + case kStdFloat32Array: + element_size = value->size; + + _advance_size_bytes(&size, element_size, NULL); + _align(&size, 4, NULL); + _advance(&size, element_size * 4, NULL); + + break; + default: return EINVAL; } @@ -342,7 +375,7 @@ int platch_write_value_to_buffer_std(struct std_value *value, uint8_t **pbuffer) size_t size; int ok; - _write_u8(pbuffer, value->type, NULL); + _write_u8(pbuffer, (uint8_t) value->type, NULL); switch (value->type) { case kStdNull: @@ -425,6 +458,16 @@ int platch_write_value_to_buffer_std(struct std_value *value, uint8_t **pbuffer) return ok; } break; + case kStdFloat32Array: + size = value->size; + + _writeSize(pbuffer, size, NULL); + _align((uintptr_t *) pbuffer, 4, NULL); + + for (int i = 0; i < size; i++) { + _write_float(pbuffer, value->float32array[i], NULL); + } + break; default: return EINVAL; } @@ -678,7 +721,7 @@ int platch_decode_value_std(const uint8_t **pbuffer, size_t *premaining, struct return ok; break; - case kStdList: + case kStdList: { ok = _readSize(pbuffer, &size, premaining); if (ok != 0) return ok; @@ -687,36 +730,80 @@ int platch_decode_value_std(const uint8_t **pbuffer, size_t *premaining, struct value_out->list = calloc(size, sizeof(struct std_value)); for (int i = 0; i < size; i++) { - ok = platch_decode_value_std(pbuffer, premaining, &value_out->list[i]); - if (ok != 0) + ok = platch_decode_value_std(pbuffer, premaining, value_out->list + i); + if (ok != 0) { + for (int j = 0; j < i; j++) { + platch_free_value_std(value_out->list + j); + } + free(value_out->list); return ok; + } } break; - case kStdMap: + } + case kStdMap: { ok = _readSize(pbuffer, &size, premaining); - if (ok != 0) + if (ok != 0) { return ok; + } value_out->size = size; value_out->keys = calloc(size * 2, sizeof(struct std_value)); - if (!value_out->keys) + if (!value_out->keys) { return ENOMEM; + } value_out->values = &value_out->keys[size]; for (int i = 0; i < size; i++) { ok = platch_decode_value_std(pbuffer, premaining, &(value_out->keys[i])); - if (ok != 0) + if (ok != 0) { + for (int j = 0; j < i; j++) { + platch_free_value_std(&(value_out->values[j])); + platch_free_value_std(&(value_out->keys[j])); + } + free(value_out->keys); return ok; + } ok = platch_decode_value_std(pbuffer, premaining, &(value_out->values[i])); - if (ok != 0) + if (ok != 0) { + platch_free_value_std(&(value_out->keys[i])); + for (int j = 0; j < i; j++) { + platch_free_value_std(&(value_out->values[j])); + platch_free_value_std(&(value_out->keys[j])); + } + free(value_out->keys); return ok; + } } break; + } + + case kStdFloat32Array: { + ok = _readSize(pbuffer, &size, premaining); + if (ok != 0) + return ok; + + ok = _align((uintptr_t *) pbuffer, 4, premaining); + if (ok != 0) + return ok; + + if (*premaining < size * 4) + return EBADMSG; + + value_out->size = size; + value_out->float32array = (float *) *pbuffer; + + ok = _advance((uintptr_t *) pbuffer, size * 4, premaining); + if (ok != 0) + return ok; + + break; + } default: return EBADMSG; } @@ -792,8 +879,10 @@ int platch_decode_value_json(char *message, size_t size, jsmntok_t **pptoken, si for (int i = 0; i < ptoken->size; i++) { ok = platch_decode_value_json(message, size, pptoken, ptokensremaining, &array[i]); - if (ok != 0) + if (ok != 0) { + free(array); return ok; + } } value_out->type = kJsonArray; @@ -801,25 +890,52 @@ int platch_decode_value_json(char *message, size_t size, jsmntok_t **pptoken, si value_out->array = array; break; - case JSMN_OBJECT:; - struct json_value key; + case JSMN_OBJECT: { char **keys = calloc(ptoken->size, sizeof(char *)); + if (!keys) { + return ENOMEM; + } + struct json_value *values = calloc(ptoken->size, sizeof(struct json_value)); - if ((!keys) || (!values)) + if (!values) { + free(keys); return ENOMEM; + } for (int i = 0; i < ptoken->size; i++) { + struct json_value key; + ok = platch_decode_value_json(message, size, pptoken, ptokensremaining, &key); - if (ok != 0) + if (ok != 0) { + for (int j = 0; j < i; j++) { + free(keys[j]); + } + free(keys); + free(values); return ok; + } - if (key.type != kJsonString) + if (key.type != kJsonString) { + platch_free_json_value(&key, true); + for (int j = 0; j < i; j++) { + free(keys[j]); + } + free(keys); + free(values); return EBADMSG; + } + keys[i] = key.string_value; ok = platch_decode_value_json(message, size, pptoken, ptokensremaining, &values[i]); - if (ok != 0) + if (ok != 0) { + for (int j = 0; j < i; j++) { + free(keys[j]); + } + free(keys); + free(values); return ok; + } } value_out->type = kJsonObject; @@ -828,6 +944,7 @@ int platch_decode_value_json(char *message, size_t size, jsmntok_t **pptoken, si value_out->values = values; break; + } default: return EBADMSG; } } @@ -840,135 +957,191 @@ int platch_decode_json(char *string, struct json_value *out) { } int platch_decode(const uint8_t *buffer, size_t size, enum platch_codec codec, struct platch_obj *object_out) { - struct json_value root_jsvalue; - const uint8_t *buffer_cursor = buffer; - size_t remaining = size; int ok; - if ((size == 0) && (buffer == NULL)) { - object_out->codec = kNotImplemented; - return 0; + if (codec != kNotImplemented && ((size == 0) || (buffer == NULL))) { + return EINVAL; } + const uint8_t *buffer_cursor = buffer; + size_t remaining = size; + object_out->codec = codec; switch (codec) { - case kStringCodec:; - /// buffer is a non-null-terminated, UTF8-encoded string. - /// it's really sad we have to allocate a new memory block for this, but we have to since string codec buffers are not null-terminated. + case kNotImplemented: { + if (size != 0) { + return EINVAL; + } + if (buffer != NULL) { + return EINVAL; + } + + break; + } - char *string; - if (!(string = malloc(size + 1))) + case kStringCodec: { + char *string = malloc(size + 1); + if (string == NULL) { return ENOMEM; - memcpy(string, buffer, size); + } + + strncpy(string, (char *) buffer, size); string[size] = '\0'; object_out->string_value = string; - break; - case kBinaryCodec: + } + case kBinaryCodec: { + if (size == 0) { + return EINVAL; + } + if (buffer == NULL) { + return EINVAL; + } + object_out->binarydata = buffer; object_out->binarydata_size = size; - break; - case kJSONMessageCodec: - ok = platch_decode_value_json((char *) buffer, size, NULL, NULL, &(object_out->json_value)); - if (ok != 0) + } + case kJSONMessageCodec: { + ok = platch_decode_value_json((char *) buffer, size, NULL, NULL, &object_out->json_value); + if (ok != 0) { return ok; + } break; - case kJSONMethodCall:; - ok = platch_decode_value_json((char *) buffer, size, NULL, NULL, &root_jsvalue); - if (ok != 0) - return ok; + } + case kJSONMethodCall: { + struct json_value root; - if (root_jsvalue.type != kJsonObject) - return EBADMSG; + ok = platch_decode_value_json((char *) buffer, size, NULL, NULL, &root); + if (ok != 0) { + return ok; + } - for (int i = 0; i < root_jsvalue.size; i++) { - if ((streq(root_jsvalue.keys[i], "method")) && (root_jsvalue.values[i].type == kJsonString)) { - object_out->method = root_jsvalue.values[i].string_value; - } else if (streq(root_jsvalue.keys[i], "args")) { - object_out->json_arg = root_jsvalue.values[i]; - } else - return EBADMSG; + if (root.type != kJsonObject) { + platch_free_json_value(&root, true); + return EINVAL; } - platch_free_json_value(&root_jsvalue, true); + for (int i = 0; i < root.size; i++) { + if ((streq(root.keys[i], "method")) && (root.values[i].type == kJsonString)) { + object_out->method = root.values[i].string_value; + } else if (streq(root.keys[i], "args")) { + object_out->json_arg = root.values[i]; + } else { + return EINVAL; + } + } + platch_free_json_value(&root, true); break; - case kJSONMethodCallResponse:; - ok = platch_decode_value_json((char *) buffer, size, NULL, NULL, &root_jsvalue); - if (ok != 0) + } + case kJSONMethodCallResponse: { + struct json_value root; + + ok = platch_decode_value_json((char *) buffer, size, NULL, NULL, &root); + if (ok != 0) { return ok; - if (root_jsvalue.type != kJsonArray) - return EBADMSG; + } + + if (root.type != kJsonArray) { + platch_free_json_value(&root, true); + return EINVAL; + } - if (root_jsvalue.size == 1) { + if (root.size == 1) { object_out->success = true; - object_out->json_result = root_jsvalue.array[0]; - return platch_free_json_value(&root_jsvalue, true); - } else if ((root_jsvalue.size == 3) && - (root_jsvalue.array[0].type == kJsonString) && - ((root_jsvalue.array[1].type == kJsonString) || (root_jsvalue.array[1].type == kJsonNull))) { + object_out->json_result = root.array[0]; + } else if ((root.size == 3) && (root.array[0].type == kJsonString) && + ((root.array[1].type == kJsonString) || (root.array[1].type == kJsonNull))) { object_out->success = false; - object_out->error_code = root_jsvalue.array[0].string_value; - object_out->error_msg = root_jsvalue.array[1].string_value; - object_out->json_error_details = root_jsvalue.array[2]; - return platch_free_json_value(&root_jsvalue, true); - } else - return EBADMSG; + object_out->error_code = root.array[0].string_value; + object_out->error_msg = root.array[1].string_value; + object_out->json_error_details = root.array[2]; + } else { + platch_free_json_value(&root, true); + return EINVAL; + } + platch_free_json_value(&root, true); break; - case kStandardMessageCodec: + } + case kStandardMessageCodec: { ok = platch_decode_value_std(&buffer_cursor, &remaining, &object_out->std_value); - if (ok != 0) + if (ok != 0) { return ok; + } + break; - case kStandardMethodCall:; + } + case kStandardMethodCall: { struct std_value methodname; ok = platch_decode_value_std(&buffer_cursor, &remaining, &methodname); - if (ok != 0) + if (ok != 0) { return ok; + } + if (methodname.type != kStdString) { platch_free_value_std(&methodname); - return EBADMSG; + return EINVAL; } + object_out->method = methodname.string_value; ok = platch_decode_value_std(&buffer_cursor, &remaining, &object_out->std_arg); - if (ok != 0) + if (ok != 0) { return ok; + } break; - case kStandardMethodCallResponse:; + } + case kStandardMethodCallResponse: { ok = _read_u8(&buffer_cursor, (uint8_t *) &object_out->success, &remaining); + if (ok != 0) { + return ok; + } if (object_out->success) { ok = platch_decode_value_std(&buffer_cursor, &remaining, &(object_out->std_result)); - if (ok != 0) + if (ok != 0) { return ok; + } } else { struct std_value error_code, error_msg; ok = platch_decode_value_std(&buffer_cursor, &remaining, &error_code); - if (ok != 0) + if (ok != 0) { return ok; + } + ok = platch_decode_value_std(&buffer_cursor, &remaining, &error_msg); - if (ok != 0) + if (ok != 0) { + platch_free_value_std(&error_code); return ok; + } + ok = platch_decode_value_std(&buffer_cursor, &remaining, &(object_out->std_error_details)); - if (ok != 0) + if (ok != 0) { + platch_free_value_std(&error_msg); + platch_free_value_std(&error_code); return ok; + } if ((error_code.type == kStdString) && ((error_msg.type == kStdString) || (error_msg.type == kStdNull))) { object_out->error_code = error_code.string_value; object_out->error_msg = (error_msg.type == kStdString) ? error_msg.string_value : NULL; } else { - return EBADMSG; + platch_free_value_std(&object_out->std_error_details); + platch_free_value_std(&error_code); + platch_free_value_std(&error_msg); + return EINVAL; } } + break; + } default: return EINVAL; } @@ -976,153 +1149,224 @@ int platch_decode(const uint8_t *buffer, size_t size, enum platch_codec codec, s } int platch_encode(struct platch_obj *object, uint8_t **buffer_out, size_t *size_out) { - struct std_value stdmethod, stderrcode, stderrmessage; - uint8_t *buffer, *buffer_cursor; - size_t size = 0; int ok = 0; - *size_out = 0; - *buffer_out = NULL; - switch (object->codec) { - case kNotImplemented: + case kNotImplemented: { *size_out = 0; *buffer_out = NULL; return 0; - case kStringCodec: size = strlen(object->string_value); break; - case kBinaryCodec: + } + case kStringCodec: { + *buffer_out = (uint8_t *) strdup(object->string_value); + if (buffer_out == NULL) { + return ENOMEM; + } + + *size_out = strlen(object->string_value); + return 0; + } + case kBinaryCodec: { /// FIXME: Copy buffer instead *buffer_out = (uint8_t *) object->binarydata; *size_out = object->binarydata_size; return 0; - case kJSONMessageCodec: - size = platch_calc_value_size_json(&(object->json_value)); + } + case kJSONMessageCodec: { + size_t size = platch_calc_value_size_json(&(object->json_value)); size += 1; // JSONMsgCodec uses sprintf, which null-terminates strings, // so lets allocate one more byte for the last null-terminator. // this is decremented again in the second switch-case, so flutter // doesn't complain about a malformed message. + + uint8_t *buffer = malloc(size); + if (buffer == NULL) { + return ENOMEM; + } + + uint8_t *buffer_cursor = buffer; + + ok = platch_write_value_to_buffer_json(&(object->json_value), &buffer_cursor); + if (ok != 0) { + free(buffer); + return ok; + } + + *buffer_out = buffer; + *size_out = size; break; - case kStandardMessageCodec: - ok = platch_calc_value_size_std(&(object->std_value), &size); - if (ok != 0) + } + case kStandardMessageCodec: { + size_t size; + + ok = platch_calc_value_size_std(&object->std_value, &size); + if (ok != 0) { return ok; + } + + uint8_t *buffer = malloc(size); + if (buffer == NULL) { + return ENOMEM; + } + + uint8_t *buffer_cursor = buffer; + + ok = platch_write_value_to_buffer_std(&object->std_value, &buffer_cursor); + if (ok != 0) { + free(buffer); + return ok; + } + + *buffer_out = buffer; + *size_out = size; break; - case kStandardMethodCall: + } + case kStandardMethodCall: { + struct std_value stdmethod; + size_t size; + stdmethod.type = kStdString; stdmethod.string_value = object->method; ok = platch_calc_value_size_std(&stdmethod, &size); - if (ok != 0) + if (ok != 0) { return ok; + } ok = platch_calc_value_size_std(&(object->std_arg), &size); - if (ok != 0) + if (ok != 0) { + return ok; + } + + uint8_t *buffer = malloc(size); + if (buffer == NULL) { + return ENOMEM; + } + + uint8_t *buffer_cursor = buffer; + ok = platch_write_value_to_buffer_std(&stdmethod, &buffer_cursor); + if (ok != 0) { + free(buffer); return ok; + } + ok = platch_write_value_to_buffer_std(&(object->std_arg), &buffer_cursor); + if (ok != 0) { + free(buffer); + return ok; + } + + *buffer_out = buffer; + *size_out = size; break; - case kStandardMethodCallResponse: - size += 1; + } + case kStandardMethodCallResponse: { + size_t size = 1; if (object->success) { ok = platch_calc_value_size_std(&(object->std_result), &size); - if (ok != 0) + if (ok != 0) { return ok; + } } else { - stderrcode = (struct std_value){ .type = kStdString, .string_value = object->error_code }; - stderrmessage = (struct std_value){ .type = kStdString, .string_value = object->error_msg }; - - ok = platch_calc_value_size_std(&stderrcode, &size); - if (ok != 0) + ok = platch_calc_value_size_std(&STDSTRING(object->error_code), &size); + if (ok != 0) { return ok; - ok = platch_calc_value_size_std(&stderrmessage, &size); - if (ok != 0) + } + + ok = platch_calc_value_size_std(&STDSTRING(object->error_msg), &size); + if (ok != 0) { return ok; + } + ok = platch_calc_value_size_std(&(object->std_error_details), &size); - if (ok != 0) + if (ok != 0) { return ok; + } } - break; - case kJSONMethodCall: - size = platch_calc_value_size_json(&JSONOBJECT2("method", JSONSTRING(object->method), "args", object->json_arg)); - size += 1; - break; - case kJSONMethodCallResponse: - if (object->success) { - size = 1 + platch_calc_value_size_json(&JSONARRAY1(object->json_result)); - } else { - size = 1 + platch_calc_value_size_json(&JSONARRAY3( - JSONSTRING(object->error_code), - (object->error_msg != NULL) ? JSONSTRING(object->error_msg) : JSONNULL, - object->json_error_details - )); - } - break; - default: return EINVAL; - } - - buffer = malloc(size); - if (buffer == NULL) { - return ENOMEM; - } - - buffer_cursor = buffer; - - switch (object->codec) { - case kStringCodec: memcpy(buffer, object->string_value, size); break; - case kStandardMessageCodec: - ok = platch_write_value_to_buffer_std(&(object->std_value), &buffer_cursor); - if (ok != 0) - goto free_buffer_and_return_ok; - break; - case kStandardMethodCall: - ok = platch_write_value_to_buffer_std(&stdmethod, &buffer_cursor); - if (ok != 0) - goto free_buffer_and_return_ok; - ok = platch_write_value_to_buffer_std(&(object->std_arg), &buffer_cursor); - if (ok != 0) - goto free_buffer_and_return_ok; + uint8_t *buffer = malloc(size); + if (buffer == NULL) { + return ENOMEM; + } - break; - case kStandardMethodCallResponse: + uint8_t *buffer_cursor = buffer; if (object->success) { _write_u8(&buffer_cursor, 0x00, NULL); - ok = platch_write_value_to_buffer_std(&(object->std_result), &buffer_cursor); - if (ok != 0) - goto free_buffer_and_return_ok; + ok = platch_write_value_to_buffer_std(&object->std_result, &buffer_cursor); + if (ok != 0) { + free(buffer); + return ok; + } } else { _write_u8(&buffer_cursor, 0x01, NULL); - ok = platch_write_value_to_buffer_std(&stderrcode, &buffer_cursor); - if (ok != 0) - goto free_buffer_and_return_ok; - ok = platch_write_value_to_buffer_std(&stderrmessage, &buffer_cursor); - if (ok != 0) - goto free_buffer_and_return_ok; + ok = platch_write_value_to_buffer_std(&STDSTRING(object->error_code), &buffer_cursor); + if (ok != 0) { + free(buffer); + return ok; + } + + ok = platch_write_value_to_buffer_std(&STDSTRING(object->error_msg), &buffer_cursor); + if (ok != 0) { + free(buffer); + return ok; + } + ok = platch_write_value_to_buffer_std(&(object->std_error_details), &buffer_cursor); - if (ok != 0) - goto free_buffer_and_return_ok; + if (ok != 0) { + free(buffer); + return ok; + } } + *buffer_out = buffer; + *size_out = size; break; - case kJSONMessageCodec: - size -= 1; - ok = platch_write_value_to_buffer_json(&(object->json_value), &buffer_cursor); - if (ok != 0) - goto free_buffer_and_return_ok; - break; - case kJSONMethodCall: - size -= 1; + } + case kJSONMethodCall: { + size_t size = platch_calc_value_size_json(&JSONOBJECT2("method", JSONSTRING(object->method), "args", object->json_arg)); + + uint8_t *buffer = malloc(size + 1); + if (buffer == NULL) { + return ENOMEM; + } + + uint8_t *buffer_cursor = buffer; + ok = platch_write_value_to_buffer_json( &JSONOBJECT2("method", JSONSTRING(object->method), "args", object->json_arg), &buffer_cursor ); if (ok != 0) { - goto free_buffer_and_return_ok; + free(buffer); + return ok; } + + *buffer_out = buffer; + *size_out = size; break; - case kJSONMethodCallResponse: + } + case kJSONMethodCallResponse: { + size_t size; + + if (object->success) { + size = platch_calc_value_size_json(&JSONARRAY1(object->json_result)); + } else { + size = platch_calc_value_size_json(&JSONARRAY3( + JSONSTRING(object->error_code), + (object->error_msg != NULL) ? JSONSTRING(object->error_msg) : JSONNULL, + object->json_error_details + )); + } + + uint8_t *buffer = malloc(size + 1); + if (buffer == NULL) { + return ENOMEM; + } + + uint8_t *buffer_cursor = buffer; if (object->success) { ok = platch_write_value_to_buffer_json(&JSONARRAY1(object->json_result), &buffer_cursor); } else { @@ -1135,21 +1379,21 @@ int platch_encode(struct platch_obj *object, uint8_t **buffer_out, size_t *size_ &buffer_cursor ); } - size -= 1; + if (ok != 0) { - goto free_buffer_and_return_ok; + free(buffer); + return ok; } + + *buffer_out = buffer; + *size_out = size; + break; + } default: return EINVAL; } - *buffer_out = buffer; - *size_out = size; return 0; - -free_buffer_and_return_ok: - free(buffer); - return ok; } void platch_on_response_internal(const uint8_t *buffer, size_t size, void *userdata) { @@ -1168,9 +1412,7 @@ void platch_on_response_internal(const uint8_t *buffer, size_t size, void *userd free(handlerdata); - ok = platch_free_obj(&object); - if (ok != 0) - return; + platch_free_obj(&object); } int platch_send( @@ -1193,7 +1435,8 @@ int platch_send( if (on_response) { handlerdata = malloc(sizeof(struct platch_msg_resp_handler_data)); if (!handlerdata) { - return ENOMEM; + ok = ENOMEM; + goto fail_free_object; } handlerdata->codec = response_codec; @@ -1232,6 +1475,11 @@ int platch_send( free(handlerdata); } +fail_free_object: + if (object->codec != kBinaryCodec) { + free(buffer); + } + return ok; } @@ -1266,7 +1514,7 @@ int platch_respond(const FlutterPlatformMessageResponseHandle *handle, struct pl free(buffer); } - return 0; + return ok; } int platch_respond_not_implemented(const FlutterPlatformMessageResponseHandle *handle) { @@ -1574,7 +1822,11 @@ bool stdvalue_equals(struct std_value *a, struct std_value *b) { ASSERT_NOT_NULL(a->string_value); ASSERT_NOT_NULL(b->string_value); return streq(a->string_value, b->string_value); - case kStdFloat64: return a->float64_value == b->float64_value; + case kStdFloat64: + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wfloat-equal") + return a->float64_value == b->float64_value; + PRAGMA_DIAGNOSTIC_POP case kStdUInt8Array: if (a->size != b->size) return false; @@ -1612,16 +1864,24 @@ bool stdvalue_equals(struct std_value *a, struct std_value *b) { return false; return true; case kStdFloat64Array: - if (a->size != b->size) - return false; if (a->float64array == b->float64array) return true; + if (a->size != b->size) + return false; + ASSERT_NOT_NULL(a->float64array); ASSERT_NOT_NULL(b->float64array); - for (int i = 0; i < a->size; i++) - if (a->float64array[i] != b->float64array[i]) + + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wfloat-equal") + for (int i = 0; i < a->size; i++) { + if (a->float64array[i] != b->float64array[i]) { return false; + } + } + PRAGMA_DIAGNOSTIC_POP + return true; case kStdList: // the order of list elements is important @@ -1680,6 +1940,25 @@ bool stdvalue_equals(struct std_value *a, struct std_value *b) { return true; } + case kStdFloat32Array: + if (a->float32array == b->float32array) + return true; + + if (a->size != b->size) + return false; + + ASSERT_NOT_NULL(a->float32array); + ASSERT_NOT_NULL(b->float32array); + + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wfloat-equal") + for (int i = 0; i < a->size; i++) { + if (a->float32array[i] != b->float32array[i]) { + return false; + } + } + PRAGMA_DIAGNOSTIC_POP + return true; default: return false; } @@ -1944,7 +2223,12 @@ ATTR_PURE bool raw_std_value_equals(const struct raw_std_value *a, const struct case kStdFalse: return true; case kStdInt32: return raw_std_value_as_int32(a) == raw_std_value_as_int32(b); case kStdInt64: return raw_std_value_as_int64(a) == raw_std_value_as_int64(b); - case kStdFloat64: return raw_std_value_as_float64(a) == raw_std_value_as_float64(b); + case kStdFloat64: + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wfloat-equal") + return raw_std_value_as_float64(a) == raw_std_value_as_float64(b); + PRAGMA_DIAGNOSTIC_POP + case kStdLargeInt: case kStdString: alignment = 0; element_size = 1; @@ -1978,11 +2262,15 @@ ATTR_PURE bool raw_std_value_equals(const struct raw_std_value *a, const struct length = raw_std_value_get_size(a); const double *a_doubles = raw_std_value_as_float64array(a); const double *b_doubles = raw_std_value_as_float64array(b); + + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wfloat-equal") for (int i = 0; i < length; i++) { if (a_doubles[i] != b_doubles[i]) { return false; } } + PRAGMA_DIAGNOSTIC_POP return true; case kStdList: @@ -2062,11 +2350,15 @@ ATTR_PURE bool raw_std_value_equals(const struct raw_std_value *a, const struct length = raw_std_value_get_size(a); const float *a_floats = raw_std_value_as_float32array(a); const float *b_floats = raw_std_value_as_float32array(b); + + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wfloat-equal") for (int i = 0; i < length; i++) { if (a_floats[i] != b_floats[i]) { return false; } } + PRAGMA_DIAGNOSTIC_POP return true; default: assert(false); return false; @@ -2277,6 +2569,7 @@ ATTR_PURE bool raw_std_value_check(const struct raw_std_value *value, size_t buf case kStdInt32: return buffer_size >= 5; case kStdInt64: return buffer_size >= 9; case kStdFloat64: return buffer_size >= 9; + case kStdLargeInt: case kStdString: alignment = 0; element_size = 1; @@ -2334,9 +2627,6 @@ ATTR_PURE bool raw_std_value_check(const struct raw_std_value *value, size_t buf return false; } - // get the value size. - size = raw_std_value_get_size(value); - for_each_element_in_raw_std_list(element, value) { int diff = (intptr_t) element - (intptr_t) value; if (buffer_size < diff) { diff --git a/src/platformchannel.h b/src/platformchannel.h index 76c03315..bad10a1a 100644 --- a/src/platformchannel.h +++ b/src/platformchannel.h @@ -89,6 +89,7 @@ struct std_value { const uint8_t *uint8array; int32_t *int32array; int64_t *int64array; + float *float32array; double *float64array; struct std_value *list; struct { @@ -1538,9 +1539,7 @@ int platch_send_error_event_json(char *channel, char *error_code, char *error_ms /// frees a ChannelObject that was decoded using PlatformChannel_decode. /// not freeing ChannelObjects may result in a memory leak. -int platch_free_obj(struct platch_obj *object); - -int platch_free_json_value(struct json_value *value, bool shallow); +void platch_free_obj(struct platch_obj *object); /// returns true if values a and b are equal. /// for JS arrays, the order of the values is relevant diff --git a/src/pluginregistry.c b/src/pluginregistry.c index 1c6ae730..4ee35b2f 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -13,6 +13,8 @@ #include #include +#include +#include #include "flutter-pi.h" #include "platformchannel.h" @@ -32,16 +34,12 @@ * or dynamically allocate memory for your plugin if you need to. */ struct plugin_instance { - struct list_head entry; - - const struct flutterpi_plugin_v2 *plugin; + struct flutterpi_plugin_v2 *plugin; void *userdata; bool initialized; }; struct platch_obj_cb_data { - struct list_head entry; - char *channel; enum platch_codec codec; platch_obj_recv_callback callback; @@ -49,184 +47,121 @@ struct platch_obj_cb_data { void *userdata; }; +KHASH_MAP_INIT_STR(static_plugins, struct flutterpi_plugin_v2 *) +KHASH_MAP_INIT_STR(plugins, struct plugin_instance) +KHASH_MAP_INIT_STR(callbacks, struct platch_obj_cb_data) + struct plugin_registry { - pthread_mutex_t lock; struct flutterpi *flutterpi; - struct list_head plugins; - struct list_head callbacks; -}; -DEFINE_STATIC_LOCK_OPS(plugin_registry, lock) + mutex_t plugins_lock; + khash_t(plugins) * plugins; -struct static_plugin_list_entry { - struct list_head entry; - const struct flutterpi_plugin_v2 *plugin; + mutex_t callbacks_lock; + khash_t(callbacks) * callbacks; }; +static khash_t(static_plugins) * static_plugins; static pthread_once_t static_plugins_init_flag = PTHREAD_ONCE_INIT; -static pthread_mutex_t static_plugins_lock; -static struct list_head static_plugins; - -static struct plugin_instance *get_plugin_by_name_locked(struct plugin_registry *registry, const char *plugin_name) { - list_for_each_entry(struct plugin_instance, instance, ®istry->plugins, entry) { - if (streq(instance->plugin->name, plugin_name)) { - return instance; - } - } - - return NULL; -} - -static struct plugin_instance *get_plugin_by_name(struct plugin_registry *registry, const char *plugin_name) { - struct plugin_instance *instance; - - plugin_registry_lock(registry); - - instance = get_plugin_by_name_locked(registry, plugin_name); - - plugin_registry_unlock(registry); - - return instance; -} - -static struct platch_obj_cb_data *get_cb_data_by_channel_locked(struct plugin_registry *registry, const char *channel) { - list_for_each_entry(struct platch_obj_cb_data, data, ®istry->callbacks, entry) { - if (streq(data->channel, channel)) { - return data; - } - } - - return NULL; -} +static mutex_t static_plugins_lock; struct plugin_registry *plugin_registry_new(struct flutterpi *flutterpi) { struct plugin_registry *reg; - ASSERTED int ok; reg = malloc(sizeof *reg); if (reg == NULL) { return NULL; } - ok = pthread_mutex_init(®->lock, get_default_mutex_attrs()); - ASSERT_ZERO(ok); - - list_inithead(®->plugins); - list_inithead(®->callbacks); - + mutex_init(®->plugins_lock); + reg->plugins = kh_init(plugins); + mutex_init(®->callbacks_lock); + reg->callbacks = kh_init(callbacks); reg->flutterpi = flutterpi; return reg; - - return NULL; } void plugin_registry_destroy(struct plugin_registry *registry) { plugin_registry_ensure_plugins_deinitialized(registry); - // remove all plugins - list_for_each_entry_safe(struct plugin_instance, instance, ®istry->plugins, entry) { - assert(instance->initialized == false); - list_del(&instance->entry); - free(instance); + { + struct plugin_instance instance; + kh_foreach_value(registry->plugins, instance, { flutterpi_plugin_v2_unrefp(&instance.plugin); }); } - assert(list_is_empty(®istry->plugins)); - assert(list_is_empty(®istry->callbacks)); free(registry); } int plugin_registry_on_platform_message(struct plugin_registry *registry, const FlutterPlatformMessage *message) { - struct platch_obj_cb_data *data; - platch_obj_recv_callback callback; - platform_message_callback_v2_t callback_v2; - struct platch_obj object; - enum platch_codec codec; - void *userdata; + struct platch_obj_cb_data data; int ok; - plugin_registry_lock(registry); + mutex_lock(®istry->callbacks_lock); - data = get_cb_data_by_channel_locked(registry, message->channel); - if (data == NULL || (data->callback == NULL && data->callback_v2 == NULL)) { + khiter_t entry = kh_get(callbacks, registry->callbacks, message->channel); + if (entry == kh_end(registry->callbacks)) { ok = platch_respond_not_implemented((FlutterPlatformMessageResponseHandle *) message->response_handle); - goto fail_unlock; + mutex_unlock(®istry->callbacks_lock); + return ok; } - codec = data->codec; - callback = data->callback; - callback_v2 = data->callback_v2; - userdata = data->userdata; + data = kh_value(registry->callbacks, entry); - plugin_registry_unlock(registry); + mutex_unlock(®istry->callbacks_lock); - if (callback_v2 != NULL) { - callback_v2(userdata, message); + if (data.callback_v2 != NULL) { + data.callback_v2(data.userdata, message); } else { - ok = platch_decode((uint8_t *) message->message, message->message_size, codec, &object); - if (ok != 0) { - platch_respond_not_implemented((FlutterPlatformMessageResponseHandle *) message->response_handle); - goto fail_return_ok; - } + struct platch_obj object; - ok = callback( - (char *) message->channel, - &object, - (FlutterPlatformMessageResponseHandle *) message->response_handle - ); //, userdata); + ok = platch_decode((uint8_t *) message->message, message->message_size, data.codec, &object); if (ok != 0) { - goto fail_free_object; + return platch_respond_not_implemented((FlutterPlatformMessageResponseHandle *) message->response_handle); } + ok = data.callback((char *) message->channel, &object, (FlutterPlatformMessageResponseHandle *) message->response_handle); + platch_free_obj(&object); + return ok; } return 0; - -fail_free_object: - platch_free_obj(&object); - -fail_unlock: - plugin_registry_unlock(registry); - -fail_return_ok: - return ok; } -void plugin_registry_add_plugin_locked(struct plugin_registry *registry, const struct flutterpi_plugin_v2 *plugin) { - struct plugin_instance *instance; - - instance = malloc(sizeof *instance); - ASSERT_NOT_NULL(instance); +void plugin_registry_add_plugin_locked(struct plugin_registry *registry, struct flutterpi_plugin_v2 *plugin) { + int ok; - instance->plugin = plugin; - instance->initialized = false; - instance->userdata = NULL; + khiter_t entry = kh_put(plugins, registry->plugins, plugin->name, &ok); + if (ok == -1) { + return; + } - list_addtail(&instance->entry, ®istry->plugins); + kh_value(registry->plugins, entry).plugin = flutterpi_plugin_v2_ref(plugin); + kh_value(registry->plugins, entry).initialized = false; + kh_value(registry->plugins, entry).userdata = NULL; } -void plugin_registry_add_plugin(struct plugin_registry *registry, const struct flutterpi_plugin_v2 *plugin) { - plugin_registry_lock(registry); +void plugin_registry_add_plugin(struct plugin_registry *registry, struct flutterpi_plugin_v2 *plugin) { + mutex_lock(®istry->plugins_lock); plugin_registry_add_plugin_locked(registry, plugin); - plugin_registry_unlock(registry); + mutex_unlock(®istry->plugins_lock); } -static void static_plugin_registry_ensure_initialized(); +static void static_plugin_registry_ensure_initialized(void); int plugin_registry_add_plugins_from_static_registry(struct plugin_registry *registry) { - ASSERTED int ok; - static_plugin_registry_ensure_initialized(); - ok = pthread_mutex_lock(&static_plugins_lock); - ASSERT_ZERO(ok); + mutex_lock(®istry->plugins_lock); + mutex_lock(&static_plugins_lock); - list_for_each_entry(struct static_plugin_list_entry, plugin, &static_plugins, entry) { - plugin_registry_add_plugin(registry, plugin->plugin); + { + struct flutterpi_plugin_v2 *plugin; + kh_foreach_value(static_plugins, plugin, { plugin_registry_add_plugin_locked(registry, plugin); }) } - ok = pthread_mutex_unlock(&static_plugins_lock); - ASSERT_ZERO(ok); + mutex_unlock(&static_plugins_lock); + mutex_unlock(®istry->plugins_lock); return 0; } @@ -234,58 +169,70 @@ int plugin_registry_add_plugins_from_static_registry(struct plugin_registry *reg int plugin_registry_ensure_plugins_initialized(struct plugin_registry *registry) { enum plugin_init_result result; - plugin_registry_lock(registry); - - list_for_each_entry(struct plugin_instance, instance, ®istry->plugins, entry) { - if (instance->initialized == false) { - result = instance->plugin->init(registry->flutterpi, &instance->userdata); - if (result == PLUGIN_INIT_RESULT_ERROR) { - LOG_ERROR("Error initializing plugin \"%s\".\n", instance->plugin->name); - goto fail_deinit_all_initialized; - } else if (result == PLUGIN_INIT_RESULT_NOT_APPLICABLE) { - // This is not an error. - LOG_DEBUG("INFO: Plugin \"%s\" is not available in this flutter-pi instance.\n", instance->plugin->name); - continue; + mutex_lock(®istry->plugins_lock); + + { + const char *plugin_name; + struct plugin_instance instance; + kh_foreach(registry->plugins, plugin_name, instance, { + if (instance.initialized == false) { + result = instance.plugin->init(registry->flutterpi, &instance.userdata); + if (result == PLUGIN_INIT_RESULT_ERROR) { + LOG_ERROR("Error initializing plugin \"%s\".\n", plugin_name); + goto fail_deinit_all_initialized; + } else if (result == PLUGIN_INIT_RESULT_NOT_APPLICABLE) { + // This is not an error. + LOG_DEBUG("INFO: Plugin \"%s\" is not available in this flutter-pi instance.\n", plugin_name); + continue; + } + + ASSERT(result == PLUGIN_INIT_RESULT_INITIALIZED); + instance.initialized = true; } - - ASSUME(result == PLUGIN_INIT_RESULT_INITIALIZED); - instance->initialized = true; - } + }); } LOG_DEBUG("Initialized plugins: "); - list_for_each_entry(struct plugin_instance, instance, ®istry->plugins, entry) { - if (instance->initialized) { - LOG_DEBUG_UNPREFIXED("%s, ", instance->plugin->name); - } + { + const char *plugin_name; + struct plugin_instance instance; + kh_foreach(registry->plugins, plugin_name, instance, { + if (instance.initialized) { + LOG_DEBUG_UNPREFIXED("%s, ", plugin_name); + } + }); } LOG_DEBUG_UNPREFIXED("\n"); - plugin_registry_unlock(registry); + mutex_unlock(®istry->plugins_lock); return 0; -fail_deinit_all_initialized: - list_for_each_entry(struct plugin_instance, instance, ®istry->plugins, entry) { - if (instance->initialized) { - instance->plugin->deinit(registry->flutterpi, instance->userdata); - instance->initialized = false; +fail_deinit_all_initialized: { + struct plugin_instance instance; + kh_foreach_value(registry->plugins, instance, { + if (instance.initialized) { + instance.plugin->deinit(registry->flutterpi, instance.userdata); + instance.userdata = NULL; + instance.initialized = false; } - } - plugin_registry_unlock(registry); + }); +} + mutex_unlock(®istry->plugins_lock); return EINVAL; } void plugin_registry_ensure_plugins_deinitialized(struct plugin_registry *registry) { - plugin_registry_lock(registry); + mutex_lock(®istry->plugins_lock); - list_for_each_entry(struct plugin_instance, instance, ®istry->plugins, entry) { - if (instance->initialized == true) { - instance->plugin->deinit(registry->flutterpi, instance->userdata); - instance->initialized = false; + struct plugin_instance instance; + kh_foreach_value(registry->plugins, instance, { + if (instance.initialized == true) { + instance.plugin->deinit(registry->flutterpi, instance.userdata); + instance.initialized = false; } - } + }); - plugin_registry_unlock(registry); + mutex_unlock(®istry->plugins_lock); } /// TODO: Move this into a separate flutter messenger API @@ -297,41 +244,33 @@ static int set_receiver_locked( platform_message_callback_v2_t callback_v2, void *userdata ) { - struct platch_obj_cb_data *data_ptr; char *channel_dup; ASSERT_MSG((!!callback) != (!!callback_v2), "Exactly one of callback or callback_v2 must be non-NULL."); - ASSERT_MUTEX_LOCKED(registry->lock); - - data_ptr = get_cb_data_by_channel_locked(registry, channel); - if (data_ptr == NULL) { - channel_dup = strdup(channel); - if (channel_dup == NULL) { - return ENOMEM; - } - - struct platch_obj_cb_data *data; - - data = malloc(sizeof *data); - if (data == NULL) { - free(channel_dup); - return ENOMEM; - } + assert_mutex_locked(®istry->callbacks_lock); - data->channel = channel_dup; - data->codec = codec; - data->callback = callback; - data->callback_v2 = callback_v2; - data->userdata = userdata; + channel_dup = strdup(channel); + if (channel_dup == NULL) { + return ENOMEM; + } - list_addtail(&data->entry, ®istry->callbacks); + int ok; + khiter_t entry = kh_put(callbacks, registry->callbacks, channel_dup, &ok); + if (ok == -1) { + free(channel_dup); + return ENOMEM; + } else if (ok == 0) { + // key was already present, + // free the channel string. + free(channel_dup); } else { - data_ptr->codec = codec; - data_ptr->callback = callback; - data_ptr->callback_v2 = callback_v2; - data_ptr->userdata = userdata; + kh_value(registry->callbacks, entry).channel = channel_dup; } + kh_value(registry->callbacks, entry).codec = codec; + kh_value(registry->callbacks, entry).callback = callback; + kh_value(registry->callbacks, entry).callback_v2 = callback_v2; + kh_value(registry->callbacks, entry).userdata = userdata; return 0; } @@ -345,9 +284,9 @@ static int set_receiver( ) { int ok; - plugin_registry_lock(registry); + mutex_lock(®istry->callbacks_lock); ok = set_receiver_locked(registry, channel, codec, callback, callback_v2, userdata); - plugin_registry_unlock(registry); + mutex_unlock(®istry->callbacks_lock); return ok; } @@ -389,40 +328,23 @@ int plugin_registry_set_receiver(const char *channel, enum platch_codec codec, p return set_receiver(registry, channel, codec, callback, NULL, NULL); } -int plugin_registry_remove_receiver_v2_locked(struct plugin_registry *registry, const char *channel) { - struct platch_obj_cb_data *data; +int plugin_registry_remove_receiver_v2(struct plugin_registry *registry, const char *channel) { + mutex_lock(®istry->callbacks_lock); - data = get_cb_data_by_channel_locked(registry, channel); - if (data == NULL) { + khiter_t entry = kh_get(callbacks, registry->callbacks, channel); + if (entry == kh_end(registry->callbacks)) { + mutex_unlock(®istry->callbacks_lock); return EINVAL; } - list_del(&data->entry); - free(data->channel); - free(data); + char *key = kh_val(registry->callbacks, entry).channel; + kh_del(callbacks, registry->callbacks, entry); + free(key); + mutex_unlock(®istry->callbacks_lock); return 0; } -int plugin_registry_remove_receiver_v2(struct plugin_registry *registry, const char *channel) { - int ok; - - plugin_registry_lock(registry); - ok = plugin_registry_remove_receiver_v2_locked(registry, channel); - plugin_registry_unlock(registry); - - return ok; -} - -int plugin_registry_remove_receiver_locked(const char *channel) { - struct plugin_registry *registry; - - registry = flutterpi_get_plugin_registry(flutterpi); - ASSUME(registry != NULL); - - return plugin_registry_remove_receiver_v2_locked(registry, channel); -} - int plugin_registry_remove_receiver(const char *channel) { struct plugin_registry *registry; @@ -433,78 +355,58 @@ int plugin_registry_remove_receiver(const char *channel) { } bool plugin_registry_is_plugin_present_locked(struct plugin_registry *registry, const char *plugin_name) { - return get_plugin_by_name_locked(registry, plugin_name) != NULL; + khiter_t plugin = kh_get(plugins, registry->plugins, plugin_name); + return plugin != kh_end(registry->plugins); } bool plugin_registry_is_plugin_present(struct plugin_registry *registry, const char *plugin_name) { - return get_plugin_by_name(registry, plugin_name) != NULL; -} + mutex_lock(®istry->plugins_lock); -void *plugin_registry_get_plugin_userdata(struct plugin_registry *registry, const char *plugin_name) { - struct plugin_instance *instance; + bool present = plugin_registry_is_plugin_present_locked(registry, plugin_name); - instance = get_plugin_by_name(registry, plugin_name); + mutex_unlock(®istry->plugins_lock); - return instance != NULL ? instance->userdata : NULL; + return present; } -void *plugin_registry_get_plugin_userdata_locked(struct plugin_registry *registry, const char *plugin_name) { - struct plugin_instance *instance; - - instance = get_plugin_by_name_locked(registry, plugin_name); +static void static_plugin_registry_initialize(void) { + static_plugins = kh_init(static_plugins); + ASSERT_NOT_NULL(static_plugins); - return instance != NULL ? instance->userdata : NULL; + mutex_init(&static_plugins_lock); } -static void static_plugin_registry_initialize() { - ASSERTED int ok; - - list_inithead(&static_plugins); - - ok = pthread_mutex_init(&static_plugins_lock, get_default_mutex_attrs()); - ASSERT_ZERO(ok); -} - -static void static_plugin_registry_ensure_initialized() { +static void static_plugin_registry_ensure_initialized(void) { pthread_once(&static_plugins_init_flag, static_plugin_registry_initialize); } -void static_plugin_registry_add_plugin(const struct flutterpi_plugin_v2 *plugin) { - struct static_plugin_list_entry *entry; - ASSERTED int ok; +void static_plugin_registry_add_plugin(struct flutterpi_plugin_v2 *plugin) { + int ok; static_plugin_registry_ensure_initialized(); - ok = pthread_mutex_lock(&static_plugins_lock); - ASSERT_ZERO(ok); + mutex_lock(&static_plugins_lock); - entry = malloc(sizeof *entry); - ASSERT_NOT_NULL(entry); - - entry->plugin = plugin; + khiter_t entry = kh_put(static_plugins, static_plugins, plugin->name, &ok); + ASSERT(ok >= 0); - list_addtail(&entry->entry, &static_plugins); + kh_value(static_plugins, entry) = flutterpi_plugin_v2_ref(plugin); - ok = pthread_mutex_unlock(&static_plugins_lock); - ASSERT_ZERO(ok); + mutex_unlock(&static_plugins_lock); } void static_plugin_registry_remove_plugin(const char *plugin_name) { - ASSERTED int ok; - static_plugin_registry_ensure_initialized(); - ok = pthread_mutex_lock(&static_plugins_lock); - ASSERT_ZERO(ok); - - list_for_each_entry(struct static_plugin_list_entry, plugin, &static_plugins, entry) { - if (streq(plugin->plugin->name, plugin_name)) { - list_del(&plugin->entry); - free(plugin); - break; - } + mutex_lock(&static_plugins_lock); + khiter_t entry = kh_get(static_plugins, static_plugins, plugin_name); + if (entry == kh_end(static_plugins)) { + mutex_unlock(&static_plugins_lock); + return; } - ok = pthread_mutex_unlock(&static_plugins_lock); - ASSERT_ZERO(ok); + flutterpi_plugin_v2_unrefp(&kh_value(static_plugins, entry)); + kh_del(static_plugins, static_plugins, entry); + + mutex_unlock(&static_plugins_lock); } diff --git a/src/pluginregistry.h b/src/pluginregistry.h index 4efe6bf8..1896c5f5 100644 --- a/src/pluginregistry.h +++ b/src/pluginregistry.h @@ -16,40 +16,65 @@ #include #include "platformchannel.h" +#include "util/refcounting.h" struct flutterpi; struct plugin_registry; +/** + * @brief The result of a plugin initialization. + */ +enum plugin_init_result { + /** + * @brief The plugin was successfully initialized. + */ + PLUGIN_INIT_RESULT_INITIALIZED, + + /** + * @brief The plugin couldn't be initialized, because it's not compatible with + * the flutter-pi instance. + * + * For example, the plugin requires OpenGL but flutter-pi is using software rendering. + * This is not an error and flutter-pi will continue initializing the other plugins. + */ + PLUGIN_INIT_RESULT_NOT_APPLICABLE, + + /** + * @brief The plugin couldn't be initialized because an unexpected error ocurred. + * + * Flutter-pi may decide to abort the startup phase of the whole flutter-pi instance at that point. + */ + PLUGIN_INIT_RESULT_ERROR +}; + typedef enum plugin_init_result (*plugin_init_t)(struct flutterpi *flutterpi, void **userdata_out); typedef void (*plugin_deinit_t)(struct flutterpi *flutterpi, void *userdata); struct flutterpi_plugin_v2 { + refcount_t n_refs; const char *name; plugin_init_t init; plugin_deinit_t deinit; }; -/// The return value of a plugin initializer function. -enum plugin_init_result { - PLUGIN_INIT_RESULT_INITIALIZED, ///< The plugin was successfully initialized. - PLUGIN_INIT_RESULT_NOT_APPLICABLE, ///< The plugin couldn't be initialized, because it's not compatible with the flutter-pi instance. - /// For example, the plugin requires OpenGL but flutter-pi is using software rendering. - /// This is not an error, and flutter-pi will continue initializing the other plugins. - PLUGIN_INIT_RESULT_ERROR ///< The plugin couldn't be initialized because an unexpected error ocurred. - /// Flutter-pi may decide to abort the startup phase of the whole flutter-pi instance at that point. -}; +static inline void flutterpi_plugin_v2_destroy(UNUSED struct flutterpi_plugin_v2 *plugin) { + // no-op +} + +DEFINE_STATIC_INLINE_REF_OPS(flutterpi_plugin_v2, n_refs) struct _FlutterPlatformMessageResponseHandle; typedef struct _FlutterPlatformMessageResponseHandle FlutterPlatformMessageResponseHandle; -/// A Callback that gets called when a platform message -/// arrives on a channel you registered it with. -/// channel is the method channel that received a platform message, -/// object is the object that is the result of automatically decoding -/// the platform message using the codec given to plugin_registry_set_receiver. -/// BE AWARE that object->type can be kNotImplemented, REGARDLESS of the codec -/// passed to plugin_registry_set_receiver. +/** + * @brief A callback that gets called when a platform message arrives on a channel you registered it with. + * + * @param channel The method channel that received a platform message. + * @param object The object that is the result of automatically decoding the platform message using the codec given to plugin_registry_set_receiver. + * @param responsehandle The response handle to respond to the platform message. + * @return int 0 if the message was handled successfully, positive errno-code if an error occurred. + */ typedef int (*platch_obj_recv_callback)(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle); typedef void (*platform_message_callback_v2_t)(void *userdata, const FlutterPlatformMessage *message); @@ -61,7 +86,7 @@ struct plugin_registry *plugin_registry_new(struct flutterpi *flutterpi); void plugin_registry_destroy(struct plugin_registry *registry); -void plugin_registry_add_plugin(struct plugin_registry *registry, const struct flutterpi_plugin_v2 *plugin); +void plugin_registry_add_plugin(struct plugin_registry *registry, struct flutterpi_plugin_v2 *plugin); int plugin_registry_add_plugins_from_static_registry(struct plugin_registry *registry); @@ -80,18 +105,6 @@ void plugin_registry_ensure_plugins_deinitialized(struct plugin_registry *regist */ int plugin_registry_on_platform_message(struct plugin_registry *registry, const FlutterPlatformMessage *message); -/** - * @brief Sets the callback that should be called when a platform message arrives on channel `channel`. - * - * The platform message will be automatically decoded using the codec `codec`. - */ -int plugin_registry_set_receiver_v2_locked( - struct plugin_registry *registry, - const char *channel, - platform_message_callback_v2_t callback, - void *userdata -); - /** * @brief Sets the callback that should be called when a platform message arrives on channel `channel`. * @@ -108,70 +121,79 @@ int plugin_registry_set_receiver_v2( * @brief Sets the callback that should be called when a platform message arrives on channel `channel`. * * The platform message will be automatically decoded using the codec `codec`. - */ -int plugin_registry_set_receiver_locked(const char *channel, enum platch_codec codec, platch_obj_recv_callback callback); - -/** - * @brief Sets the callback that should be called when a platform message arrives on channel `channel`. - * - * The platform message will be automatically decoded using the codec `codec`. + * + * @attention See @ref plugin_registry_remove_receiver_v2 for the semantics. + * + * @param registry The plugin registry to set the receiver on. + * @param channel The channel to set the receiver on. + * @param callback The callback to call when a message arrives. */ int plugin_registry_set_receiver(const char *channel, enum platch_codec codec, platch_obj_recv_callback callback); /** * @brief Removes the callback for platform channel `channel`. * - */ -int plugin_registry_remove_receiver_v2_locked(struct plugin_registry *registry, const char *channel); - -/** - * @brief Removes the callback for platform channel `channel`. - * + * @attention This function will remove the receiver from the plugin registries + * internal list of receiver callbacks. It does not wait for a currently running + * callback to finish (obviously, because if you're calling this function from + * within the callback, that would be impossible). + * + * If you're not calling this function from the platform thread, so not from within + * a receiver callback or a plugin init / deinit callback, the receiver callback + * might still be in progress, or even be called one more time after this function returns. + * + * So don't do this: + * void my_video_channel_callback(...) { + * struct my_video_data *data = userdata; + * // ... + * } + * + * // THREAD X (e.g. gstreamer video thread) + * plugin_registry_remove_receiver_v2(registry, "my_video_channel"); + * free(my_video_data); + * + * Because the callback might still be called / in progress after you've freed the data. + * + * Instead, post a message to the platform thread to remove the receiver callback: + * void remove_receiver_cb(void *userdata) { + * struct my_video_data *data = userdata; + * plugin_registry_remove_receiver_v2(registry, "my_video_channel"); + * free(data); + * } + * + * // THREAD X + * flutterpi_post_platform_task(flutterpi, remove_receiver_cb, my_video_data); + * + * @param registry The plugin registry to remove the receiver from. + * @param channel The channel to remove the receiver from. */ int plugin_registry_remove_receiver_v2(struct plugin_registry *registry, const char *channel); -/** - * @brief Removes the callback for platform channel `channel`. - * - */ -int plugin_registry_remove_receiver_locked(const char *channel); - /** * @brief Removes the callback for platform channel `channel`. * */ int plugin_registry_remove_receiver(const char *channel); -void *plugin_registry_get_plugin_userdata(struct plugin_registry *registry, const char *plugin_name); - -void *plugin_registry_get_plugin_userdata_locked(struct plugin_registry *registry, const char *plugin_name); - -/** - * @brief Returns true @ref registry has a plugin with name @ref plugin_name. - */ -bool plugin_registry_is_plugin_present(struct plugin_registry *registry, const char *plugin_name); - -/** - * @brief Returns true @ref registry has a plugin with name @ref plugin_name. - */ -bool plugin_registry_is_plugin_present_locked(struct plugin_registry *registry, const char *plugin_name); - -int plugin_registry_deinit(void); - -void static_plugin_registry_add_plugin(const struct flutterpi_plugin_v2 *plugin); +void static_plugin_registry_add_plugin(struct flutterpi_plugin_v2 *plugin); void static_plugin_registry_remove_plugin(const char *plugin_name); -#define FLUTTERPI_PLUGIN(_name, _identifier_name, _init, _deinit) \ - __attribute__((constructor)) static void __reg_plugin_##_identifier_name() { \ - static struct flutterpi_plugin_v2 plugin = { \ - .name = (_name), \ - .init = (_init), \ - .deinit = (_deinit), \ - }; \ - static_plugin_registry_add_plugin(&plugin); \ - } \ - \ - __attribute__((destructor)) static void __unreg_plugin_##_identifier_name() { static_plugin_registry_remove_plugin(_name); } +#define FLUTTERPI_PLUGIN(_name, _identifier_name, _init, _deinit) \ + static struct flutterpi_plugin_v2 _identifier_name##_plugin_struct = { \ + .n_refs = REFCOUNT_INIT_0, \ + .name = (_name), \ + .init = (_init), \ + .deinit = (_deinit), \ + }; \ + \ + __attribute__((constructor)) static void __reg_plugin_##_identifier_name(void) { \ + static_plugin_registry_add_plugin(&_identifier_name##_plugin_struct); \ + } \ + \ + __attribute__((destructor)) static void __unreg_plugin_##_identifier_name(void) { \ + static_plugin_registry_remove_plugin(_name); \ + ASSERT_MSG(refcount_is_zero(&_identifier_name##_plugin_struct.n_refs), "Plugin still in use while destructor is running."); \ + } #endif // _FLUTTERPI_SRC_PLUGINREGISTRY_H diff --git a/src/plugins/audioplayers.h b/src/plugins/audioplayers.h index 64c9e907..a65dc1b8 100644 --- a/src/plugins/audioplayers.h +++ b/src/plugins/audioplayers.h @@ -5,8 +5,9 @@ #include struct audio_player; +struct evloop; -struct audio_player *audio_player_new(char *playerId, char *channel); +struct audio_player *audio_player_new(struct evloop *platform_loop, char *playerId, char *channel); // Instance function @@ -38,7 +39,7 @@ void audio_player_set_source_url(struct audio_player *self, char *url); bool audio_player_is_id(struct audio_player *self, char *id); -const char* audio_player_subscribe_channel_name(const struct audio_player *self); +const char *audio_player_subscribe_channel_name(const struct audio_player *self); ///Asks to subscribe to channel events /// diff --git a/src/plugins/audioplayers/player.c b/src/plugins/audioplayers/player.c index 71cb91ba..3ba82f52 100644 --- a/src/plugins/audioplayers/player.c +++ b/src/plugins/audioplayers/player.c @@ -1,7 +1,7 @@ #define _GNU_SOURCE -#include #include +#include #include #include @@ -12,6 +12,7 @@ #include "platformchannel.h" #include "plugins/audioplayers.h" #include "util/asserts.h" +#include "util/event_loop.h" #include "util/logging.h" struct audio_player { @@ -35,6 +36,8 @@ struct audio_player { char *event_channel_name; _Atomic bool event_subscribed; + + struct evsrc *busfd_event_source; }; // Private Class functions @@ -49,15 +52,14 @@ static void audio_player_on_duration_update(struct audio_player *self); static void audio_player_on_seek_completed(struct audio_player *self); static void audio_player_on_playback_ended(struct audio_player *self); -static int on_bus_fd_ready(sd_event_source *src, int fd, uint32_t revents, void *userdata) { - struct audio_player *player = userdata; +static enum event_handler_return on_bus_fd_ready(int fd, uint32_t revents, void *userdata) { + struct audio_player *player; GstMessage *msg; - (void) src; (void) fd; (void) revents; - - /* DEBUG_TRACE_BEGIN(player, "on_bus_fd_ready"); */ + ASSERT_NOT_NULL(userdata); + player = userdata; msg = gst_bus_pop(player->bus); if (msg != NULL) { @@ -65,23 +67,20 @@ static int on_bus_fd_ready(sd_event_source *src, int fd, uint32_t revents, void gst_message_unref(msg); } - /* DEBUG_TRACE_END(player, "on_bus_fd_ready"); */ - - return 0; + return EVENT_HANDLER_CONTINUE; } static void audio_player_source_setup(GstElement *playbin, GstElement *source, GstElement **p_src) { - (void)(playbin); - (void)(p_src); + (void) (playbin); + (void) (p_src); if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "ssl-strict") != 0) { g_object_set(G_OBJECT(source), "ssl-strict", FALSE, NULL); } } -struct audio_player *audio_player_new(char *player_id, char *channel) { +struct audio_player *audio_player_new(struct evloop *platform_loop, char *player_id, char *channel) { GPollFD fd; - sd_event_source *busfd_event_source; struct audio_player *self = malloc(sizeof(struct audio_player)); if (self == NULL) { @@ -132,7 +131,14 @@ struct audio_player *audio_player_new(char *player_id, char *channel) { gst_bus_get_pollfd(self->bus, &fd); - flutterpi_sd_event_add_io(&busfd_event_source, fd.fd, EPOLLIN, on_bus_fd_ready, self); + uint32_t events = 0; + events |= fd.events & G_IO_IN ? EPOLLIN : 0; + events |= fd.events & G_IO_OUT ? EPOLLOUT : 0; + events |= fd.events & G_IO_PRI ? EPOLLPRI : 0; + events |= fd.events & G_IO_ERR ? EPOLLERR : 0; + events |= fd.events & G_IO_HUP ? EPOLLHUP : 0; + + self->busfd_event_source = evloop_add_io(platform_loop, fd.fd, events, on_bus_fd_ready, self); // Refresh continuously to emit recurring events g_timeout_add(1000, (GSourceFunc) audio_player_on_refresh, self); @@ -183,6 +189,9 @@ struct audio_player *audio_player_new(char *player_id, char *channel) { gboolean audio_player_on_bus_message(GstBus *bus, GstMessage *message, struct audio_player *data) { (void) bus; + + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wswitch-enum") switch (GST_MESSAGE_TYPE(message)) { case GST_MESSAGE_ERROR: { GError *err; @@ -201,12 +210,8 @@ gboolean audio_player_on_bus_message(GstBus *bus, GstMessage *message, struct au audio_player_on_media_state_change(data, message->src, &old_state, &new_state); break; } - case GST_MESSAGE_EOS: - audio_player_on_playback_ended(data); - break; - case GST_MESSAGE_DURATION_CHANGED: - audio_player_on_duration_update(data); - break; + case GST_MESSAGE_EOS: audio_player_on_playback_ended(data); break; + case GST_MESSAGE_DURATION_CHANGED: audio_player_on_duration_update(data); break; case GST_MESSAGE_ASYNC_DONE: if (!data->is_seek_completed) { audio_player_on_seek_completed(data); @@ -218,6 +223,7 @@ gboolean audio_player_on_bus_message(GstBus *bus, GstMessage *message, struct au // https://gstreamer.freedesktop.org/documentation/gstreamer/gstmessage.html?gi-language=c#enumerations break; } + PRAGMA_DIAGNOSTIC_POP // Continue watching for messages return TRUE; @@ -275,7 +281,7 @@ void audio_player_on_media_error(struct audio_player *self, GError *error, gchar return; } - char error_code[16] = {0}; + char error_code[16] = { 0 }; snprintf(error_code, sizeof(error_code), "%d", error->code); // clang-format off platch_send_error_event_std( @@ -290,7 +296,7 @@ void audio_player_on_media_error(struct audio_player *self, GError *error, gchar void audio_player_on_media_state_change(struct audio_player *self, GstObject *src, GstState *old_state, GstState *new_state) { (void) old_state; if (src == GST_OBJECT(self->playbin)) { - LOG_DEBUG("%s: on_media_state_change(old_state=%d, new_state=%d)\n", self->player_id, *old_state, *new_state); + LOG_DEBUG("%s: on_media_state_change(old_state=%u, new_state=%u)\n", self->player_id, *old_state, *new_state); if (*new_state == GST_STATE_READY) { // Need to set to pause state, in order to make player functional GstStateChangeReturn ret = gst_element_set_state(self->playbin, GST_STATE_PAUSED); @@ -486,7 +492,8 @@ void audio_player_destroy(struct audio_player *self) { if (self->event_channel_name != NULL) { free(self->event_channel_name); - self->event_channel_name = NULL;; + self->event_channel_name = NULL; + ; } free(self); @@ -524,10 +531,10 @@ void audio_player_set_balance(struct audio_player *self, double balance) { return; } - if (balance > 1.0l) { - balance = 1.0l; - } else if (balance < -1.0l) { - balance = -1.0l; + if (balance > 1.0) { + balance = 1.0; + } else if (balance < -1.0) { + balance = -1.0; } g_object_set(G_OBJECT(self->panorama), "panorama", balance, NULL); } @@ -574,7 +581,7 @@ bool audio_player_is_id(struct audio_player *self, char *player_id) { return streq(self->player_id, player_id); } -const char* audio_player_subscribe_channel_name(const struct audio_player *self) { +const char *audio_player_subscribe_channel_name(const struct audio_player *self) { return self->event_channel_name; } @@ -601,4 +608,6 @@ void audio_player_release(struct audio_player *self) { if (playbinState > GST_STATE_NULL) { gst_element_set_state(self->playbin, GST_STATE_NULL); } + + evsrc_destroy(self->busfd_event_source); } diff --git a/src/plugins/audioplayers/plugin.c b/src/plugins/audioplayers/plugin.c index e80d2d77..d8954080 100644 --- a/src/plugins/audioplayers/plugin.c +++ b/src/plugins/audioplayers/plugin.c @@ -240,12 +240,12 @@ enum plugin_init_result audioplayers_plugin_init(struct flutterpi *flutterpi, vo plugin.initialized = false; list_inithead(&plugin.players); - ok = plugin_registry_set_receiver_locked(AUDIOPLAYERS_GLOBAL_CHANNEL, kStandardMethodCall, on_global_method_call); + ok = plugin_registry_set_receiver(AUDIOPLAYERS_GLOBAL_CHANNEL, kStandardMethodCall, on_global_method_call); if (ok != 0) { return PLUGIN_INIT_RESULT_ERROR; } - ok = plugin_registry_set_receiver_locked(AUDIOPLAYERS_LOCAL_CHANNEL, kStandardMethodCall, on_local_method_call); + ok = plugin_registry_set_receiver(AUDIOPLAYERS_LOCAL_CHANNEL, kStandardMethodCall, on_local_method_call); if (ok != 0) { goto fail_remove_global_receiver; } @@ -253,7 +253,7 @@ enum plugin_init_result audioplayers_plugin_init(struct flutterpi *flutterpi, vo return PLUGIN_INIT_RESULT_INITIALIZED; fail_remove_global_receiver: - plugin_registry_remove_receiver_locked(AUDIOPLAYERS_GLOBAL_CHANNEL); + plugin_registry_remove_receiver(AUDIOPLAYERS_GLOBAL_CHANNEL); return PLUGIN_INIT_RESULT_ERROR; } @@ -262,8 +262,8 @@ void audioplayers_plugin_deinit(struct flutterpi *flutterpi, void *userdata) { (void) flutterpi; (void) userdata; - plugin_registry_remove_receiver_locked(AUDIOPLAYERS_GLOBAL_CHANNEL); - plugin_registry_remove_receiver_locked(AUDIOPLAYERS_LOCAL_CHANNEL); + plugin_registry_remove_receiver(AUDIOPLAYERS_GLOBAL_CHANNEL); + plugin_registry_remove_receiver(AUDIOPLAYERS_LOCAL_CHANNEL); list_for_each_entry_safe(struct audio_player_entry, entry, &plugin.players, entry) { audio_player_destroy(entry->player); @@ -288,7 +288,7 @@ static struct audio_player *audioplayers_linux_plugin_get_player(char *player_id ASSUME(entry != NULL); LOG_DEBUG("Create player(id=%s)\n", player_id); - player = audio_player_new(player_id, AUDIOPLAYERS_LOCAL_CHANNEL); + player = audio_player_new(flutterpi_get_platform_event_loop(plugin.flutterpi), player_id, AUDIOPLAYERS_LOCAL_CHANNEL); if (player == NULL) { LOG_ERROR("player(id=%s) cannot be created", player_id); @@ -296,13 +296,9 @@ static struct audio_player *audioplayers_linux_plugin_get_player(char *player_id return NULL; } - const char* event_channel = audio_player_subscribe_channel_name(player); + const char *event_channel = audio_player_subscribe_channel_name(player); // set a receiver on the videoEvents event channel - int ok = plugin_registry_set_receiver( - event_channel, - kStandardMethodCall, - on_receive_event_ch - ); + int ok = plugin_registry_set_receiver(event_channel, kStandardMethodCall, on_receive_event_ch); if (ok != 0) { LOG_ERROR("Cannot set player receiver for event channel: %s\n", event_channel); audio_player_destroy(player); diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index 66498ce6..8dc5bad8 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -21,11 +21,12 @@ #define MAX_N_PLANES 4 -#define GSTREAMER_VER(major, minor, patch) ((((major) &0xFF) << 16) | (((minor) &0xFF) << 8) | ((patch) &0xFF)) +#define GSTREAMER_VER(major, minor, patch) ((((major) & 0xFF) << 16) | (((minor) & 0xFF) << 8) | ((patch) & 0xFF)) #define THIS_GSTREAMER_VER GSTREAMER_VER(LIBGSTREAMER_VERSION_MAJOR, LIBGSTREAMER_VERSION_MINOR, LIBGSTREAMER_VERSION_PATCH) #define DRM_FOURCC_FORMAT "c%c%c%c" -#define DRM_FOURCC_ARGS(format) (format) & 0xFF, ((format) >> 8) & 0xFF, ((format) >> 16) & 0xFF, ((format) >> 24) & 0xFF +#define DRM_FOURCC_ARGS(format) \ + (char) ((format) & 0xFF), (char) (((format) >> 8) & 0xFF), (char) (((format) >> 16) & 0xFF), (char) (((format) >> 24) & 0xFF) struct video_frame { GstSample *sample; @@ -122,6 +123,10 @@ static bool query_formats( } } + if (n_modified_formats == 0 || max_n_modifiers == 0) { + goto fail_free_formats; + } + modified_formats = malloc(n_modified_formats * sizeof *modified_formats); if (modified_formats == NULL) { goto fail_free_formats; @@ -142,7 +147,7 @@ static bool query_formats( egl_ok = egl_query_dmabuf_modifiers(display, formats[i], max_n_modifiers, modifiers, external_only, &n_modifiers); if (egl_ok != EGL_TRUE) { LOG_ERROR("Could not query dmabuf formats supported by EGL.\n"); - goto fail_free_formats; + goto fail_free_external_only; } LOG_DEBUG_UNPREFIXED("%" DRM_FOURCC_FORMAT ", ", DRM_FOURCC_ARGS(formats[i])); @@ -164,6 +169,9 @@ static bool query_formats( *formats_out = modified_formats; return true; +fail_free_external_only: + free(external_only); + fail_free_modifiers: free(modifiers); @@ -558,6 +566,11 @@ get_plane_infos(GstBuffer *buffer, const GstVideoInfo *info, struct gbm_device * n_planes = GST_VIDEO_INFO_N_PLANES(info); + if (n_planes <= 0 || n_planes > MAX_N_PLANES) { + LOG_ERROR("Unsupported number of planes in video frame.\n"); + return EINVAL; + } + // There's so many ways to get the plane sizes. // 1. Preferably we should use the video meta. // 2. If that doesn't work, we'll use gst_video_info_align_full() with the video info. @@ -597,6 +610,8 @@ get_plane_infos(GstBuffer *buffer, const GstVideoInfo *info, struct gbm_device * has_plane_sizes = true; } + ASSERT_MSG(has_plane_sizes, "Couldn't determine video frame plane sizes.\n"); + for (int i = 0; i < n_planes; i++) { size_t offset_in_memory = 0; size_t offset_in_buffer = 0; @@ -640,7 +655,7 @@ get_plane_infos(GstBuffer *buffer, const GstVideoInfo *info, struct gbm_device * ok = dup(ok); if (ok < 0) { - ok = errno; + ok = errno ? errno : EIO; LOG_ERROR("Could not dup fd. dup: %s\n", strerror(ok)); goto fail_close_fds; } @@ -674,7 +689,7 @@ get_plane_infos(GstBuffer *buffer, const GstVideoInfo *info, struct gbm_device * fail_close_fds: for (int j = i - 1; j > 0; j--) { - close(plane_infos[i].fd); + close(plane_infos[j].fd); } return ok; } @@ -683,6 +698,8 @@ get_plane_infos(GstBuffer *buffer, const GstVideoInfo *info, struct gbm_device * } static uint32_t drm_format_from_gst_info(const GstVideoInfo *info) { + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wswitch-enum") switch (GST_VIDEO_INFO_FORMAT(info)) { case GST_VIDEO_FORMAT_YUY2: return DRM_FORMAT_YUYV; case GST_VIDEO_FORMAT_YVYU: return DRM_FORMAT_YVYU; @@ -716,6 +733,7 @@ static uint32_t drm_format_from_gst_info(const GstVideoInfo *info) { case GST_VIDEO_FORMAT_xBGR: return DRM_FORMAT_RGBX8888; default: return DRM_FORMAT_INVALID; } + PRAGMA_DIAGNOSTIC_POP } ATTR_CONST GstVideoFormat gst_video_format_from_drm_format(uint32_t drm_format) { @@ -1096,9 +1114,9 @@ struct video_frame *frame_new(struct frame_interface *interface, GstSample *samp frame->drm_format = drm_format; frame->n_dmabuf_fds = n_planes; frame->dmabuf_fds[0] = planes[0].fd; - frame->dmabuf_fds[1] = planes[1].fd; - frame->dmabuf_fds[2] = planes[2].fd; - frame->dmabuf_fds[3] = planes[3].fd; + frame->dmabuf_fds[1] = n_planes >= 2 ? planes[1].fd : -1; + frame->dmabuf_fds[2] = n_planes >= 3 ? planes[2].fd : -1; + frame->dmabuf_fds[3] = n_planes >= 4 ? planes[3].fd : -1; frame->image = egl_image; frame->gl_frame.target = target; frame->gl_frame.name = texture; diff --git a/src/plugins/gstreamer_video_player/player.c b/src/plugins/gstreamer_video_player/player.c index a6555eec..14112e66 100644 --- a/src/plugins/gstreamer_video_player/player.c +++ b/src/plugins/gstreamer_video_player/player.c @@ -22,6 +22,7 @@ #include "plugins/gstreamer_video_player.h" #include "texture_registry.h" #include "util/collection.h" +#include "util/event_loop.h" #include "util/logging.h" #ifdef DEBUG @@ -157,7 +158,7 @@ struct gstplayer { GstElement *pipeline, *sink; GstBus *bus; - sd_event_source *busfd_events; + struct evsrc *busfd_events; bool is_live; }; @@ -174,15 +175,15 @@ UNUSED static inline void unlock(struct gstplayer *player) { } UNUSED static inline void trace_instant(struct gstplayer *player, const char *name) { - return flutterpi_trace_event_instant(player->flutterpi, name); + flutterpi_trace_event_instant(player->flutterpi, name); } UNUSED static inline void trace_begin(struct gstplayer *player, const char *name) { - return flutterpi_trace_event_begin(player->flutterpi, name); + flutterpi_trace_event_begin(player->flutterpi, name); } UNUSED static inline void trace_end(struct gstplayer *player, const char *name) { - return flutterpi_trace_event_end(player->flutterpi, name); + flutterpi_trace_event_end(player->flutterpi, name); } static int maybe_send_info(struct gstplayer *player) { @@ -472,6 +473,9 @@ static void on_bus_message(struct gstplayer *player, GstMessage *msg) { gchar *debug_info; DEBUG_TRACE_BEGIN(player, "on_bus_message"); + + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wswitch-enum") switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_ERROR: gst_message_parse_error(msg, &error, &debug_info); @@ -604,15 +608,16 @@ static void on_bus_message(struct gstplayer *player, GstMessage *msg) { default: LOG_DEBUG("gstreamer message: %s, src: %s\n", GST_MESSAGE_TYPE_NAME(msg), GST_MESSAGE_SRC_NAME(msg)); break; } + PRAGMA_DIAGNOSTIC_POP + DEBUG_TRACE_END(player, "on_bus_message"); return; } -static int on_bus_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { +static enum event_handler_return on_bus_fd_ready(int fd, uint32_t revents, void *userdata) { struct gstplayer *player; GstMessage *msg; - (void) s; (void) fd; (void) revents; @@ -628,7 +633,7 @@ static int on_bus_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *u DEBUG_TRACE_END(player, "on_bus_fd_ready"); - return 0; + return EVENT_HANDLER_CONTINUE; } static GstPadProbeReturn on_query_appsink(GstPad *pad, GstPadProbeInfo *info, void *userdata) { @@ -844,7 +849,6 @@ void on_source_setup(GstElement *bin, GstElement *source, gpointer userdata) { static int init(struct gstplayer *player, bool force_sw_decoders) { GstStateChangeReturn state_change_return; - sd_event_source *busfd_event_source; GstElement *pipeline, *sink, *src; GstBus *bus; GstPad *pad; @@ -956,7 +960,15 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { gst_bus_get_pollfd(bus, &fd); - flutterpi_sd_event_add_io(&busfd_event_source, fd.fd, EPOLLIN, on_bus_fd_ready, player); + uint32_t events = 0; + events |= fd.events & G_IO_IN ? EPOLLIN : 0; + events |= fd.events & G_IO_OUT ? EPOLLOUT : 0; + events |= fd.events & G_IO_PRI ? EPOLLPRI : 0; + events |= fd.events & G_IO_ERR ? EPOLLERR : 0; + events |= fd.events & G_IO_HUP ? EPOLLHUP : 0; + + struct evsrc *busfd_event_source = + evloop_add_io(flutterpi_get_platform_event_loop(player->flutterpi), fd.fd, events, on_bus_fd_ready, player); LOG_DEBUG("Setting state to paused...\n"); state_change_return = gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PAUSED); @@ -989,7 +1001,7 @@ static int init(struct gstplayer *player, bool force_sw_decoders) { static void maybe_deinit(struct gstplayer *player) { if (player->busfd_events != NULL) { - sd_event_source_unrefp(&player->busfd_events); + evsrc_destroy(player->busfd_events); } if (player->sink != NULL) { gst_object_unref(GST_OBJECT(player->sink)); @@ -1114,6 +1126,7 @@ static struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char * fail_free_gst_headers: gst_structure_free(gst_headers); + free(pipeline_descr_owned); free(uri_owned); fail_destroy_frame_interface: @@ -1165,7 +1178,7 @@ struct gstplayer *gstplayer_new_from_pipeline(struct flutterpi *flutterpi, const } void gstplayer_destroy(struct gstplayer *player) { - LOG_DEBUG("gstplayer_destroy(%p)\n", player); + LOG_DEBUG("gstplayer_destroy(%p)\n", (void *) player); notifier_deinit(&player->video_info_notifier); notifier_deinit(&player->buffering_state_notifier); notifier_deinit(&player->error_notifier); diff --git a/src/plugins/gstreamer_video_player/plugin.c b/src/plugins/gstreamer_video_player/plugin.c index d1de5b49..e2992679 100644 --- a/src/plugins/gstreamer_video_player/plugin.c +++ b/src/plugins/gstreamer_video_player/plugin.c @@ -54,7 +54,7 @@ static struct plugin { struct list_head players; } plugin; -DEFINE_LOCK_OPS(plugin, lock); +DEFINE_LOCK_OPS(plugin, lock) /// Add a player instance to the player collection. static void add_player(struct gstplayer_meta *meta) { @@ -116,7 +116,7 @@ static void remove_player(struct gstplayer_meta *meta) { * */ static void remove_player_locked(struct gstplayer_meta *meta) { - ASSERT_MUTEX_LOCKED(plugin.lock); + assert_mutex_locked(&plugin.lock); list_del(&meta->entry); } @@ -204,7 +204,7 @@ get_player_from_map_arg(struct std_value *arg, struct gstplayer **player_out, Fl return 0; } -static int ensure_initialized() { +static int ensure_initialized(void) { GError *gst_error; gboolean success; @@ -315,7 +315,7 @@ static enum listener_return on_video_info_notify(void *arg, void *userdata) { /// on_video_info_notify is called on an internal thread, /// but send_initialized_event is (should be) mt-safe send_initialized_event(meta, !info->can_seek, info->width, info->height, info->duration_ms); - + /// FIXME: Threading /// Set this to NULL here so we don't unlisten to it twice. meta->video_info_listener = NULL; @@ -504,16 +504,12 @@ static void destroy_meta(struct gstplayer_meta *meta) { free(meta); } -static void dispose_player(struct gstplayer *player, bool plugin_registry_locked, bool plugin_locked) { +static void dispose_player(struct gstplayer *player, bool plugin_locked) { struct gstplayer_meta *meta; meta = get_meta(player); - if (plugin_registry_locked) { - plugin_registry_remove_receiver_locked(meta->event_channel_name); - } else { - plugin_registry_remove_receiver(meta->event_channel_name); - } + plugin_registry_remove_receiver(meta->event_channel_name); if (plugin_locked) { remove_player_locked(meta); @@ -690,7 +686,7 @@ static int on_dispose(char *channel, struct platch_obj *object, FlutterPlatformM return 0; } - dispose_player(player, false, false); + dispose_player(player, false); return platch_respond_success_pigeon(responsehandle, NULL); } @@ -953,11 +949,18 @@ get_player_from_texture_id_with_custom_errmsg(int64_t texture_id, FlutterPlatfor plugin_lock(&plugin); int n_texture_ids = list_length(&plugin.players); - int64_t *texture_ids = alloca(sizeof(int64_t) * n_texture_ids); - int64_t *texture_ids_cursor = texture_ids; - list_for_each_entry(struct gstplayer_meta, meta, &plugin.players, entry) { - *texture_ids_cursor++ = gstplayer_get_texture_id(meta->player); + int64_t *texture_ids; + + if (n_texture_ids == 0) { + texture_ids = NULL; + } else { + texture_ids = alloca(sizeof(int64_t) * n_texture_ids); + int64_t *texture_ids_cursor = texture_ids; + + list_for_each_entry(struct gstplayer_meta, meta, &plugin.players, entry) { + *texture_ids_cursor++ = gstplayer_get_texture_id(meta->player); + } } plugin_unlock(&plugin); @@ -1078,8 +1081,7 @@ static int on_create_v2(const struct raw_std_value *arg, FlutterPlatformMessageR } else if (raw_std_value_is_string(arg)) { asset = raw_std_string_dup(arg); if (asset == NULL) { - ok = ENOMEM; - goto fail_respond_error; + return platch_respond_native_error_std(responsehandle, ENOMEM); } } else { return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[0]` to be a String or null."); @@ -1097,11 +1099,12 @@ static int on_create_v2(const struct raw_std_value *arg, FlutterPlatformMessageR } else if (raw_std_value_is_string(arg)) { package_name = raw_std_string_dup(arg); if (package_name == NULL) { - ok = ENOMEM; - goto fail_respond_error; + ok = platch_respond_native_error_std(responsehandle, ENOMEM); + goto fail_free_asset; } } else { - return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be a String or null."); + ok = platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be a String or null."); + goto fail_free_asset; } } else { package_name = NULL; @@ -1116,11 +1119,12 @@ static int on_create_v2(const struct raw_std_value *arg, FlutterPlatformMessageR } else if (raw_std_value_is_string(arg)) { uri = raw_std_string_dup(arg); if (uri == NULL) { - ok = ENOMEM; - goto fail_respond_error; + ok = platch_respond_native_error_std(responsehandle, ENOMEM); + goto fail_free_package_name; } } else { - return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[2]` to be a String or null."); + ok = platch_respond_illegal_arg_std(responsehandle, "Expected `arg[2]` to be a String or null."); + goto fail_free_package_name; } } else { uri = NULL; @@ -1146,7 +1150,8 @@ static int on_create_v2(const struct raw_std_value *arg, FlutterPlatformMessageR } } else { invalid_format_hint: - return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[3]` to be one of 'ss', 'hls', 'dash', 'other' or null."); + ok = platch_respond_illegal_arg_std(responsehandle, "Expected `arg[3]` to be one of 'ss', 'hls', 'dash', 'other' or null."); + goto fail_free_uri; } } else { format_hint = FORMAT_HINT_NONE; @@ -1167,7 +1172,8 @@ static int on_create_v2(const struct raw_std_value *arg, FlutterPlatformMessageR headers = arg; } else { invalid_headers: - return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[4]` to be a map of strings or null."); + ok = platch_respond_illegal_arg_std(responsehandle, "Expected `arg[4]` to be a map of strings or null."); + goto fail_free_uri; } } else { headers = NULL; @@ -1182,51 +1188,64 @@ static int on_create_v2(const struct raw_std_value *arg, FlutterPlatformMessageR } else if (raw_std_value_is_string(arg)) { pipeline = raw_std_string_dup(arg); } else { - return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[5]` to be a string or null."); + ok = platch_respond_illegal_arg_std(responsehandle, "Expected `arg[5]` to be a string or null."); + goto fail_free_uri; } } else { pipeline = NULL; } if ((asset ? 1 : 0) + (uri ? 1 : 0) + (pipeline ? 1 : 0) != 1) { - return platch_respond_illegal_arg_std(responsehandle, "Expected exactly one of `arg[0]`, `arg[2]` or `arg[5]` to be non-null."); + ok = platch_respond_illegal_arg_std(responsehandle, "Expected exactly one of `arg[0]`, `arg[2]` or `arg[5]` to be non-null."); + goto fail_free_pipeline; } // Create our actual player (this doesn't initialize it) if (asset != NULL) { - player = gstplayer_new_from_asset(flutterpi, asset, package_name, NULL); - // gstplayer_new_from_network will construct a file:// URI out of the // asset path internally. - free(asset); - asset = NULL; + player = gstplayer_new_from_asset(flutterpi, asset, package_name, NULL); } else if (uri != NULL) { + // gstplayer_new_from_network will dup the uri internally. player = gstplayer_new_from_network(flutterpi, uri, format_hint, NULL); + } else if (pipeline != NULL) { + // gstplayer_new_from_network will dup the pipeline internally. + player = gstplayer_new_from_pipeline(flutterpi, pipeline, NULL); + } else { + UNREACHABLE(); + } - // gstplayer_new_from_network will dup the uri internally. + if (asset != NULL) { + free(asset); + asset = NULL; + } + + if (package_name != NULL) { + free(package_name); + package_name = NULL; + } + + if (uri != NULL) { free(uri); uri = NULL; - } else if (pipeline != NULL) { - player = gstplayer_new_from_pipeline(flutterpi, pipeline, NULL); + } - // gstplayer_new_from_network will dup the pipeline internally. + if (pipeline != NULL) { free(pipeline); pipeline = NULL; - } else { - UNREACHABLE(); } if (player == NULL) { LOG_ERROR("Couldn't create gstreamer video player.\n"); - ok = EIO; - goto fail_respond_error; + ok = platch_respond_native_error_std(responsehandle, EIO); + goto fail_destroy_player; } // create a meta object so we can store the event channel name // of a player with it meta = create_meta(gstplayer_get_texture_id(player), player); if (meta == NULL) { - ok = ENOMEM; + ok = platch_respond_native_error_std(responsehandle, ENOMEM); goto fail_destroy_player; } @@ -1251,12 +1270,14 @@ static int on_create_v2(const struct raw_std_value *arg, FlutterPlatformMessageR // Set a receiver on the videoEvents event channel ok = plugin_registry_set_receiver(meta->event_channel_name, kStandardMethodCall, on_receive_evch); if (ok != 0) { + platch_respond_native_error_std(responsehandle, ok); goto fail_remove_player; } // Finally, start initializing ok = gstplayer_initialize(player); if (ok != 0) { + platch_respond_native_error_std(responsehandle, ok); goto fail_remove_receiver; } @@ -1272,8 +1293,27 @@ static int on_create_v2(const struct raw_std_value *arg, FlutterPlatformMessageR fail_destroy_player: gstplayer_destroy(player); -fail_respond_error: - return platch_respond_native_error_std(responsehandle, ok); +fail_free_pipeline: + if (pipeline != NULL) { + free(pipeline); + } + +fail_free_uri: + if (uri != NULL) { + free(uri); + } + +fail_free_package_name: + if (package_name != NULL) { + free(package_name); + } + +fail_free_asset: + if (asset != NULL) { + free(asset); + } + + return ok; } static int on_dispose_v2(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { @@ -1284,7 +1324,7 @@ static int on_dispose_v2(const struct raw_std_value *arg, FlutterPlatformMessage return EINVAL; } - dispose_player(player, false, false); + dispose_player(player, false); return platch_respond_success_std(responsehandle, &STDNULL); } @@ -1603,70 +1643,62 @@ enum plugin_init_result gstplayer_plugin_init(struct flutterpi *flutterpi, void list_inithead(&plugin.players); - ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.initialize", kStandardMessageCodec, on_initialize); + ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.initialize", kStandardMessageCodec, on_initialize); if (ok != 0) { goto fail_destroy_lock; } - ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.create", kStandardMessageCodec, on_create); + ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.create", kStandardMessageCodec, on_create); if (ok != 0) { goto fail_remove_initialize_receiver; } - ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.dispose", kStandardMessageCodec, on_dispose); + ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.dispose", kStandardMessageCodec, on_dispose); if (ok != 0) { goto fail_remove_create_receiver; } - ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setLooping", kStandardMessageCodec, on_set_looping); + ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.setLooping", kStandardMessageCodec, on_set_looping); if (ok != 0) { goto fail_remove_dispose_receiver; } - ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setVolume", kStandardMessageCodec, on_set_volume); + ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.setVolume", kStandardMessageCodec, on_set_volume); if (ok != 0) { goto fail_remove_setLooping_receiver; } - ok = plugin_registry_set_receiver_locked( - "dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed", - kStandardMessageCodec, - on_set_playback_speed - ); + ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed", kStandardMessageCodec, on_set_playback_speed); if (ok != 0) { goto fail_remove_setVolume_receiver; } - ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.play", kStandardMessageCodec, on_play); + ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.play", kStandardMessageCodec, on_play); if (ok != 0) { goto fail_remove_setPlaybackSpeed_receiver; } - ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.position", kStandardMessageCodec, on_get_position); + ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.position", kStandardMessageCodec, on_get_position); if (ok != 0) { goto fail_remove_play_receiver; } - ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.seekTo", kStandardMessageCodec, on_seek_to); + ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.seekTo", kStandardMessageCodec, on_seek_to); if (ok != 0) { goto fail_remove_position_receiver; } - ok = plugin_registry_set_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.pause", kStandardMessageCodec, on_pause); + ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.pause", kStandardMessageCodec, on_pause); if (ok != 0) { goto fail_remove_seekTo_receiver; } - ok = plugin_registry_set_receiver_locked( - "dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers", - kStandardMessageCodec, - on_set_mix_with_others - ); + ok = plugin_registry_set_receiver("dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers", kStandardMessageCodec, on_set_mix_with_others); if (ok != 0) { goto fail_remove_pause_receiver; } - ok = plugin_registry_set_receiver_locked( + ok = plugin_registry_set_receiver( "flutter.io/videoPlayer/gstreamerVideoPlayer/advancedControls", kStandardMethodCall, on_receive_method_channel @@ -1675,7 +1707,7 @@ enum plugin_init_result gstplayer_plugin_init(struct flutterpi *flutterpi, void goto fail_remove_setMixWithOthers_receiver; } - ok = plugin_registry_set_receiver_locked("flutter-pi/gstreamerVideoPlayer", kBinaryCodec, on_receive_method_channel_v2); + ok = plugin_registry_set_receiver("flutter-pi/gstreamerVideoPlayer", kBinaryCodec, on_receive_method_channel_v2); if (ok != 0) { goto fail_remove_advancedControls_receiver; } @@ -1683,40 +1715,40 @@ enum plugin_init_result gstplayer_plugin_init(struct flutterpi *flutterpi, void return PLUGIN_INIT_RESULT_INITIALIZED; fail_remove_advancedControls_receiver: - plugin_registry_remove_receiver_locked("flutter.io/videoPlayer/gstreamerVideoPlayer/advancedControls"); + plugin_registry_remove_receiver("flutter.io/videoPlayer/gstreamerVideoPlayer/advancedControls"); fail_remove_setMixWithOthers_receiver: - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers"); fail_remove_pause_receiver: - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.pause"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.pause"); fail_remove_seekTo_receiver: - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.seekTo"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.seekTo"); fail_remove_position_receiver: - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.position"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.position"); fail_remove_play_receiver: - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.play"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.play"); fail_remove_setPlaybackSpeed_receiver: - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed"); fail_remove_setVolume_receiver: - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setVolume"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setVolume"); fail_remove_setLooping_receiver: - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setLooping"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setLooping"); fail_remove_dispose_receiver: - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.dispose"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.dispose"); fail_remove_create_receiver: - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.create"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.create"); fail_remove_initialize_receiver: - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.initialize"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.initialize"); fail_destroy_lock: pthread_mutex_destroy(&plugin.lock); @@ -1731,7 +1763,7 @@ void gstplayer_plugin_deinit(struct flutterpi *flutterpi, void *userdata) { list_for_each_entry_safe(struct gstplayer_meta, meta, &plugin.players, entry) { // will also remove the player from the player list. - dispose_player(meta->player, true, true); + dispose_player(meta->player, true); } if (plugin.initialized) { @@ -1741,19 +1773,19 @@ void gstplayer_plugin_deinit(struct flutterpi *flutterpi, void *userdata) { plugin_unlock(&plugin); - plugin_registry_remove_receiver_locked("flutter-pi/gstreamerVideoPlayer"); - plugin_registry_remove_receiver_locked("flutter.io/videoPlayer/gstreamerVideoPlayer/advancedControls"); - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers"); - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.pause"); - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.seekTo"); - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.position"); - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.play"); - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed"); - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setVolume"); - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.setLooping"); - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.dispose"); - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.create"); - plugin_registry_remove_receiver_locked("dev.flutter.pigeon.VideoPlayerApi.initialize"); + plugin_registry_remove_receiver("flutter-pi/gstreamerVideoPlayer"); + plugin_registry_remove_receiver("flutter.io/videoPlayer/gstreamerVideoPlayer/advancedControls"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.pause"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.seekTo"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.position"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.play"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setVolume"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.setLooping"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.dispose"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.create"); + plugin_registry_remove_receiver("dev.flutter.pigeon.VideoPlayerApi.initialize"); pthread_mutex_destroy(&plugin.lock); } diff --git a/src/plugins/multidisplay.c b/src/plugins/multidisplay.c new file mode 100644 index 00000000..cfa69f45 --- /dev/null +++ b/src/plugins/multidisplay.c @@ -0,0 +1,265 @@ +#define _POSIX_C_SOURCE 200809L +#include + +#include + +#include "compositor_ng.h" +#include "flutter-pi.h" +#include "notifier_listener.h" +#include "platformchannel.h" +#include "pluginregistry.h" +#include "util/logging.h" + +#define DISPLAY_MANAGER_CHANNEL "multidisplay/display_manager" +#define VIEW_CONTROLLER_CHANNEL "multidisplay/view_controller" +#define DISPLAY_SETUP_CHANNEL "multidisplay/display_setup" + +#define MULTIDISPLAY_PLUGIN_DEBUG 1 +#define LOG_MULTIDISPLAY_DEBUG(fmt, ...) \ + do { \ + if (MULTIDISPLAY_PLUGIN_DEBUG) \ + LOG_DEBUG(fmt, ##__VA_ARGS__); \ + } while (0) + +struct multidisplay_plugin { + bool has_listener; + struct listener *display_setup_listener; +}; + +static void send_display_update(struct multidisplay_plugin *plugin, struct display_setup *s) { + if (!plugin->has_listener) { + return; + } + + struct std_value connectors; + + connectors.type = kStdList; + connectors.size = display_setup_get_n_connectors(s); + connectors.list = alloca(sizeof(struct std_value) * connectors.size); + + for (size_t i = 0; i < n_ranges; i++) { + struct std_value *map = connectors.list + i; + + map->type = kStdMap; + map->size = 3; + map->keys = alloca(sizeof(struct std_value) * conn->size); + map->values = alloca(sizeof(struct std_value) * conn->size); + + map->keys[0] = STDSTRING("name"); + map->values[0] = STDSTRING(connector_get_name(connector)); + + map->keys[1] = STDSTRING("type"); + map->values[1] = STDSTRING(connector_get_type_name(connector)); + + map->keys[2] = STDSTRING("display"); + if (connector_has_display(connector)) { + struct display *display = connector_get_display(connector); + + struct std_value *display_map = alloca(sizeof(struct std_value)); + display_map->type = kStdMap; + display_map->size = 7; + + display_map->keys = alloca(sizeof(struct std_value) * display_map->size); + display_map->values = alloca(sizeof(struct std_value) * display_map->size); + + display_map->keys[0] = STDSTRING("flutterId"); + display_map->values[0] = STDINT64(display_get_fl_display_id(display)); + + display_map->keys[1] = STDSTRING("refreshRate"); + display_map->values[1] = STDFLOAT64(display_get_refresh_rate(display)); + + display_map->keys[2] = STDSTRING("width"); + display_map->values[2] = STDINT64(display_get_size(display).x); + + display_map->keys[3] = STDSTRING("height"); + display_map->values[3] = STDINT64(display_get_size(display).y); + + display_map->keys[4] = STDSTRING("widthMM"); + display_map->values[4] = STDINT64(display_get_physical_size(display).x); + + display_map->keys[5] = STDSTRING("heightMM"); + display_map->values[5] = STDINT64(display_get_physical_size(display).y); + + display_map->keys[6] = STDSTRING("devicePixelRatio"); + display_map->values[6] = STDFLOAT64(display_get_device_pixel_ratio(display)); + } else { + map->values[2] = STDNULL; + } + } + + return platch_send_success_event_std(meta->event_channel_name, &STDMAP1(STDSTRING("connectors"), connectors)); +} + +static void on_display_setup_value(void *arg, void *userdata) { + struct multidisplay_plugin *plugin; + struct display_setup *s; + + ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(value); + plugin = userdata; + s = arg; + + send_display_update(plugin, s); +} + +static void on_display_manager_method_call(void *userdata, const FlutterPlatformMessage *message) { + const FlutterPlatformMessageResponseHandle *responsehandle; + const struct raw_std_value *envelope, *method, *arg; + struct sentry_plugin *plugin; + + ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(message); + plugin = userdata; + responsehandle = message->response_handle; + envelope = (const struct raw_std_value *) (message->message); + if (!raw_std_method_call_check(envelope, message->message_size)) { + platch_respond_error_std(responsehandle, "malformed-message", "", &STDNULL); + return; + } + + method = raw_std_method_call_get_method(envelope); + arg = raw_std_method_call_get_arg(envelope); + + if (raw_std_string_equals(method, "")) { + on_init_native_sdk(plugin, arg, responsehandle); + } else { + platch_respond_error_std(responsehandle, "unknown-method", "", &STDNULL); + } +} + +static void on_view_controller_method_call(void *userdata, const FlutterPlatformMessage *message) { + const FlutterPlatformMessageResponseHandle *responsehandle; + const struct raw_std_value *envelope, *method, *arg; + struct sentry_plugin *plugin; + + ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(message); + plugin = userdata; + responsehandle = message->response_handle; + envelope = (const struct raw_std_value *) (message->message); + if (!raw_std_method_call_check(envelope, message->message_size)) { + platch_respond_error_std(responsehandle, "malformed-message", "", &STDNULL); + return; + } + + method = raw_std_method_call_get_method(envelope); + arg = raw_std_method_call_get_arg(envelope); + + if (raw_std_string_equals(method, "closeView")) { + on_close_view(plugin, arg, responsehandle); + } else { + platch_respond_error_std(responsehandle, "unknown-method", "", &STDNULL); + } +} + +static void on_event_channel_listen( + struct multidisplay_plugin *plugin, + const struct raw_std_value *arg, + const FlutterPlatformMessageResponseHandle *responsehandle +) { + if (plugin->has_listener) { + return; + } + + plugin->has_listener = true; + platch_respond_success_std(responsehandle, &STDNULL); + + struct compositor *comp = flutterpi_peek_compositor(flutterpi); + ASSERT_NOT_NULL(comp); + + plugin->display_setup_listener = notifier_listen(compositor_get_display_setup_notifier(comp), on_display_setup_value, NULL, plugin); + return; +} + +static void on_event_channel_method_call(void *userdata, const FlutterPlatformMessage *message) { + const FlutterPlatformMessageResponseHandle *responsehandle; + const struct raw_std_value *envelope, *method, *arg; + struct sentry_plugin *plugin; + + ASSERT_NOT_NULL(userdata); + ASSERT_NOT_NULL(message); + plugin = userdata; + responsehandle = message->response_handle; + envelope = (const struct raw_std_value *) (message->message); + if (!raw_std_method_call_check(envelope, message->message_size)) { + platch_respond_error_std(responsehandle, "malformed-message", "", &STDNULL); + return; + } + + method = raw_std_method_call_get_method(envelope); + arg = raw_std_method_call_get_arg(envelope); + + if (raw_std_string_equals(method, "listen")) { + on_event_channel_listen(plugin, arg, responsehandle); + } else { + platch_respond_error_std(responsehandle, "unknown-method", "", &STDNULL); + } +} + +enum plugin_init_result multidisplay_plugin_init(struct flutterpi *flutterpi, void **userdata_out) { + struct multidisplay_plugin *plugin; + int ok; + + plugin = malloc(sizeof *plugin); + if (plugin == NULL) { + return PLUGIN_INIT_RESULT_ERROR; + } + + ok = plugin_registry_set_receiver_v2_locked( + flutterpi_get_plugin_registry(flutterpi), + DISPLAY_MANAGER_CHANNEL, + on_display_manager_method_call, + plugin + ); + if (ok != 0) { + goto fail_free_plugin; + } + + ok = plugin_registry_set_receiver_v2_locked( + flutterpi_get_plugin_registry(flutterpi), + VIEW_CONTROLLER_CHANNEL, + on_view_controller_method_call, + plugin + ); + if (ok != 0) { + goto fail_remove_display_maanger_receiver; + } + + ok = plugin_registry_set_receiver_v2_locked( + flutterpi_get_plugin_registry(flutterpi), + DISPLAY_SETUP_CHANNEL, + on_event_channel_method_call, + plugin + ); + if (ok != 0) { + goto fail_remove_view_controller_receiver; + } + + *userdata_out = plugin; + + return PLUGIN_INIT_RESULT_INITIALIZED; + +fail_remove_view_controller_receiver: + plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), VIEW_CONTROLLER_CHANNEL); + +fail_remove_display_maanger_receiver: + plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), DISPLAY_MANAGER_CHANNEL); + +fail_free_plugin: + free(plugin); + return PLUGIN_INIT_RESULT_ERROR; +} + +void multidisplay_plugin_deinit(struct flutterpi *flutterpi, void *userdata) { + struct multidisplay_plugin *plugin; + + ASSERT_NOT_NULL(userdata); + plugin = userdata; + + plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), DISPLAY_SETUP_CHANNEL); + plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), VIEW_CONTROLLER_CHANNEL); + plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), DISPLAY_MANAGER_CHANNEL); + free(plugin); +} + +FLUTTERPI_PLUGIN("multidisplay", multidisplay, multidisplay_plugin_init, multidisplay_plugin_deinit); diff --git a/src/plugins/multidisplay.h b/src/plugins/multidisplay.h new file mode 100644 index 00000000..e69de29b diff --git a/src/plugins/raw_keyboard.c b/src/plugins/raw_keyboard.c index a814088a..8ace05bb 100644 --- a/src/plugins/raw_keyboard.c +++ b/src/plugins/raw_keyboard.c @@ -424,7 +424,7 @@ ATTR_CONST static uint64_t physical_key_for_evdev_keycode(uint16_t evdev_keycode ATTR_CONST static uint64_t physical_key_for_xkb_keycode(xkb_keycode_t xkb_keycode) { assert(xkb_keycode >= 8); - return physical_key_for_evdev_keycode(xkb_keycode - 8); + return physical_key_for_evdev_keycode((uint16_t) (xkb_keycode - 8)); } ATTR_CONST static char eascii_to_lower(unsigned char n) { @@ -622,7 +622,7 @@ ATTR_CONST static uint32_t logical_key_for_xkb_keysym(xkb_keysym_t keysym) { if (keysym == XKB_KEY_yen) { return apply_flutter_key_plane(0x00022); } else if (keysym < 256) { - return apply_unicode_key_plane(eascii_to_lower(keysym)); + return apply_unicode_key_plane(eascii_to_lower((int8_t) keysym)); } else if (keysym >= 0xfd06 && keysym - 0xfd06 < ARRAY_SIZE(logical_keys_1)) { logical = logical_keys_1[keysym]; } else if (keysym >= 0x1008ff02 && keysym - 0x1008ff02 < ARRAY_SIZE(logical_keys_2)) { @@ -818,6 +818,7 @@ int rawkb_on_key_event( return ok; } + // NOLINTNEXTLINE(readability-suspicious-call-argument) ok = rawkb_send_gtk_keyevent(plain_codepoint, xkb_keysym, xkb_keycode, modifiers.u32, is_down); if (ok != 0) { return ok; @@ -826,7 +827,7 @@ int rawkb_on_key_event( return 0; } -static void assert_key_modifiers_work() { +static void assert_key_modifiers_work(void) { key_modifiers_t mods; memset(&mods, 0, sizeof(mods)); diff --git a/src/plugins/sentry/sentry.c b/src/plugins/sentry/sentry.c index d18031c2..0b2aaafa 100644 --- a/src/plugins/sentry/sentry.c +++ b/src/plugins/sentry/sentry.c @@ -301,7 +301,15 @@ static sentry_value_t raw_std_value_as_sentry_value(const struct raw_std_value * case kStdInt32: return sentry_value_new_int32(raw_std_value_as_int32(arg)); case kStdInt64: return sentry_value_new_int32((int32_t) raw_std_value_as_int64(arg)); case kStdFloat64: return sentry_value_new_double(raw_std_value_as_float64(arg)); + + case kStdUInt8Array: + case kStdInt32Array: + case kStdInt64Array: + case kStdFloat64Array: return sentry_value_new_null(); + + case kStdLargeInt: case kStdString: return sentry_value_new_string_n(raw_std_string_get_nonzero_terminated(arg), raw_std_string_get_length(arg)); + case kStdMap: { sentry_value_t map = sentry_value_new_object(); for_each_entry_in_raw_std_map(key, value, arg) { @@ -328,6 +336,7 @@ static sentry_value_t raw_std_value_as_sentry_value(const struct raw_std_value * return list; } + case kStdFloat32Array: return sentry_value_new_null(); default: return sentry_value_new_null(); } } @@ -755,7 +764,7 @@ static void on_method_call(void *userdata, const FlutterPlatformMessage *message } } -enum plugin_init_result sentry_plugin_deinit(struct flutterpi *flutterpi, void **userdata_out) { +enum plugin_init_result sentry_plugin_init(struct flutterpi *flutterpi, void **userdata_out) { struct sentry_plugin *plugin; int ok; @@ -764,12 +773,7 @@ enum plugin_init_result sentry_plugin_deinit(struct flutterpi *flutterpi, void * return PLUGIN_INIT_RESULT_ERROR; } - ok = plugin_registry_set_receiver_v2_locked( - flutterpi_get_plugin_registry(flutterpi), - SENTRY_PLUGIN_METHOD_CHANNEL, - on_method_call, - plugin - ); + ok = plugin_registry_set_receiver_v2(flutterpi_get_plugin_registry(flutterpi), SENTRY_PLUGIN_METHOD_CHANNEL, on_method_call, plugin); if (ok != 0) { free(plugin); return PLUGIN_INIT_RESULT_ERROR; @@ -780,7 +784,7 @@ enum plugin_init_result sentry_plugin_deinit(struct flutterpi *flutterpi, void * return PLUGIN_INIT_RESULT_INITIALIZED; } -void sentry_plugin_init(struct flutterpi *flutterpi, void *userdata) { +void sentry_plugin_deinit(struct flutterpi *flutterpi, void *userdata) { struct sentry_plugin *plugin; ASSERT_NOT_NULL(userdata); @@ -790,8 +794,8 @@ void sentry_plugin_init(struct flutterpi *flutterpi, void *userdata) { sentry_close(); } - plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), SENTRY_PLUGIN_METHOD_CHANNEL); + plugin_registry_remove_receiver_v2(flutterpi_get_plugin_registry(flutterpi), SENTRY_PLUGIN_METHOD_CHANNEL); free(plugin); } -FLUTTERPI_PLUGIN("sentry", sentry_plugin_init, sentry_plugin_deinit, NULL); +FLUTTERPI_PLUGIN("sentry", sentry_plugin, sentry_plugin_init, sentry_plugin_deinit) diff --git a/src/plugins/services.c b/src/plugins/services.c index cbc3cdb3..1b55cbe1 100644 --- a/src/plugins/services.c +++ b/src/plugins/services.c @@ -397,25 +397,25 @@ enum plugin_init_result services_init(struct flutterpi *flutterpi, void **userda plugin->flutterpi = flutterpi; - ok = plugin_registry_set_receiver_v2_locked(registry, FLUTTER_NAVIGATION_CHANNEL, on_receive_navigation, plugin); + ok = plugin_registry_set_receiver_v2(registry, FLUTTER_NAVIGATION_CHANNEL, on_receive_navigation, plugin); if (ok != 0) { LOG_ERROR("Could not set \"" FLUTTER_NAVIGATION_CHANNEL "\" receiver. plugin_registry_set_receiver_v2_locked: %s\n", strerror(ok)); goto fail_free_plugin; } - ok = plugin_registry_set_receiver_v2_locked(registry, FLUTTER_ISOLATE_CHANNEL, on_receive_isolate, plugin); + ok = plugin_registry_set_receiver_v2(registry, FLUTTER_ISOLATE_CHANNEL, on_receive_isolate, plugin); if (ok != 0) { LOG_ERROR("Could not set \"" FLUTTER_ISOLATE_CHANNEL "\" receiver. plugin_registry_set_receiver_v2_locked: %s\n", strerror(ok)); goto fail_remove_navigation_receiver; } - ok = plugin_registry_set_receiver_v2_locked(registry, FLUTTER_PLATFORM_CHANNEL, on_receive_platform, plugin); + ok = plugin_registry_set_receiver_v2(registry, FLUTTER_PLATFORM_CHANNEL, on_receive_platform, plugin); if (ok != 0) { LOG_ERROR("Could not set \"" FLUTTER_PLATFORM_CHANNEL "\" receiver. plugin_registry_set_receiver_v2_locked: %s\n", strerror(ok)); goto fail_remove_isolate_receiver; } - ok = plugin_registry_set_receiver_v2_locked(registry, FLUTTER_ACCESSIBILITY_CHANNEL, on_receive_accessibility, plugin); + ok = plugin_registry_set_receiver_v2(registry, FLUTTER_ACCESSIBILITY_CHANNEL, on_receive_accessibility, plugin); if (ok != 0) { LOG_ERROR( "Could not set \"" FLUTTER_ACCESSIBILITY_CHANNEL "\" receiver. plugin_registry_set_receiver_v2_locked: %s\n", @@ -424,7 +424,7 @@ enum plugin_init_result services_init(struct flutterpi *flutterpi, void **userda goto fail_remove_platform_receiver; } - ok = plugin_registry_set_receiver_v2_locked(registry, FLUTTER_PLATFORM_VIEWS_CHANNEL, on_receive_platform_views, plugin); + ok = plugin_registry_set_receiver_v2(registry, FLUTTER_PLATFORM_VIEWS_CHANNEL, on_receive_platform_views, plugin); if (ok != 0) { LOG_ERROR( "Could not set \"" FLUTTER_PLATFORM_VIEWS_CHANNEL "\" receiver. plugin_registry_set_receiver_v2_locked: %s\n", @@ -433,7 +433,7 @@ enum plugin_init_result services_init(struct flutterpi *flutterpi, void **userda goto fail_remove_accessibility_receiver; } - ok = plugin_registry_set_receiver_v2_locked(registry, FLUTTER_MOUSECURSOR_CHANNEL, on_receive_mouse_cursor, plugin); + ok = plugin_registry_set_receiver_v2(registry, FLUTTER_MOUSECURSOR_CHANNEL, on_receive_mouse_cursor, plugin); if (ok != 0) { LOG_ERROR("Could not set \"" FLUTTER_MOUSECURSOR_CHANNEL "\" receiver. plugin_registry_set_receiver_v2_locked: %s\n", strerror(ok)); goto fail_remove_platform_views_receiver; @@ -444,19 +444,19 @@ enum plugin_init_result services_init(struct flutterpi *flutterpi, void **userda return 0; fail_remove_platform_views_receiver: - plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_PLATFORM_VIEWS_CHANNEL); + plugin_registry_remove_receiver_v2(registry, FLUTTER_PLATFORM_VIEWS_CHANNEL); fail_remove_accessibility_receiver: - plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_ACCESSIBILITY_CHANNEL); + plugin_registry_remove_receiver_v2(registry, FLUTTER_ACCESSIBILITY_CHANNEL); fail_remove_platform_receiver: - plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_PLATFORM_CHANNEL); + plugin_registry_remove_receiver_v2(registry, FLUTTER_PLATFORM_CHANNEL); fail_remove_isolate_receiver: - plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_ISOLATE_CHANNEL); + plugin_registry_remove_receiver_v2(registry, FLUTTER_ISOLATE_CHANNEL); fail_remove_navigation_receiver: - plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_NAVIGATION_CHANNEL); + plugin_registry_remove_receiver_v2(registry, FLUTTER_NAVIGATION_CHANNEL); fail_free_plugin: free(plugin); @@ -475,12 +475,12 @@ void services_deinit(struct flutterpi *flutterpi, void *userdata) { registry = flutterpi_get_plugin_registry(flutterpi); plugin = userdata; - plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_NAVIGATION_CHANNEL); - plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_ISOLATE_CHANNEL); - plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_PLATFORM_CHANNEL); - plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_ACCESSIBILITY_CHANNEL); - plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_PLATFORM_VIEWS_CHANNEL); - plugin_registry_remove_receiver_v2_locked(registry, FLUTTER_MOUSECURSOR_CHANNEL); + plugin_registry_remove_receiver_v2(registry, FLUTTER_NAVIGATION_CHANNEL); + plugin_registry_remove_receiver_v2(registry, FLUTTER_ISOLATE_CHANNEL); + plugin_registry_remove_receiver_v2(registry, FLUTTER_PLATFORM_CHANNEL); + plugin_registry_remove_receiver_v2(registry, FLUTTER_ACCESSIBILITY_CHANNEL); + plugin_registry_remove_receiver_v2(registry, FLUTTER_PLATFORM_VIEWS_CHANNEL); + plugin_registry_remove_receiver_v2(registry, FLUTTER_MOUSECURSOR_CHANNEL); free(plugin); } diff --git a/src/plugins/text_input.c b/src/plugins/text_input.c index c57938cf..4ca24b08 100644 --- a/src/plugins/text_input.c +++ b/src/plugins/text_input.c @@ -11,6 +11,7 @@ #include "flutter-pi.h" #include "pluginregistry.h" #include "util/asserts.h" +#include "util/logging.h" struct text_input { int64_t connection_id; @@ -33,16 +34,16 @@ struct text_input { * UTF8 utility functions */ static inline uint8_t utf8_symbol_length(uint8_t c) { - if ((c & 0b11110000) == 0b11110000) { + if ((c & 240 /* 0b11110000 */) == 240 /* 0b11110000 */) { return 4; } - if ((c & 0b11100000) == 0b11100000) { + if ((c & 224 /* 0b11100000 */) == 224 /* 0b11100000 */) { return 3; } - if ((c & 0b11000000) == 0b11000000) { + if ((c & 192 /* 0b11000000 */) == 192 /* 0b11000000 */) { return 2; } - if ((c & 0b10000000) == 0b10000000) { + if ((c & 128 /* 0b10000000 */) == 128 /* 0b10000000 */) { // XXX should we return 1 and don't care here? ASSERT_MSG(false, "Invalid UTF-8 character"); return 0; @@ -84,6 +85,23 @@ static inline int to_symbol_index(unsigned int byte_index) { return cursor < target_cursor ? -1 : symbol_index; } +static inline int count_utf8_characters(const char *str) { + int len = 0; + const char *cursor = str; + + while (*cursor) { + uint8_t utf8_len = utf8_symbol_length(*cursor); + for (uint8_t i = 0; i < utf8_len; i++) { + if (!*cursor) { + return len; + } + } + len++; + } + + return len; +} + /** * Platform message callbacks */ @@ -181,6 +199,7 @@ static int on_set_client(struct platch_obj *object, FlutterPlatformMessageRespon temp2 = jsobject_get(temp, "signed"); if (temp2 == NULL || temp2->type == kJsonNull) { has_allow_signs = false; + allow_signs = true; } else if (temp2->type == kJsonTrue || temp2->type == kJsonFalse) { has_allow_signs = true; allow_signs = temp2->type == kJsonTrue; @@ -191,6 +210,7 @@ static int on_set_client(struct platch_obj *object, FlutterPlatformMessageRespon temp2 = jsobject_get(temp, "decimal"); if (temp2 == NULL || temp2->type == kJsonNull) { has_allow_decimal = false; + allow_decimal = true; } else if (temp2->type == kJsonTrue || temp2->type == kJsonFalse) { has_allow_decimal = true; allow_decimal = temp2->type == kJsonTrue; @@ -239,14 +259,18 @@ static int on_set_client(struct platch_obj *object, FlutterPlatformMessageRespon int32_t new_id = (int32_t) object->json_arg.array[0].number_value; // everything okay, apply the new text editing config + text_input.has_allow_signs = has_allow_signs; + text_input.allow_signs = allow_signs; + text_input.has_allow_decimal = has_allow_decimal; + text_input.allow_decimal = allow_decimal; text_input.connection_id = new_id; text_input.autocorrect = autocorrect; text_input.input_action = input_action; text_input.input_type = input_type; if (autocorrect && !text_input.warned_about_autocorrect) { - printf( - "[text_input] warning: flutter requested native autocorrect, which" + LOG_ERROR( + "info: flutter requested native autocorrect, which" "is not supported by flutter-pi.\n" ); text_input.warned_about_autocorrect = true; @@ -527,7 +551,9 @@ int client_perform_action(double connection_id, enum text_input_action action) { } int client_perform_private_command(double connection_id, char *action, struct json_value *data) { - if (data != NULL && data->type != kJsonNull && data->type != kJsonObject) { + if (data == NULL) { + return EINVAL; + } else if (data->type != kJsonNull && data->type != kJsonObject) { return EINVAL; } @@ -606,33 +632,29 @@ static bool model_delete_selected(void) { return true; } -static bool model_add_utf8_char(uint8_t *c) { - size_t symbol_length; - uint8_t *to_move; - +static bool model_add_text(const char *str) { if (text_input.selection_base != text_input.selection_extent) model_delete_selected(); // find out where in our string we need to insert the utf8 symbol - symbol_length = utf8_symbol_length(*c); - to_move = symbol_at(text_input.selection_base); + size_t l = strlen(str); + uint8_t *to_move = symbol_at(text_input.selection_base); - if (!to_move || !symbol_length) + if (!to_move || !l) return false; // move the string behind the insertion position to // make place for the utf8 charactercursor - memmove(to_move + symbol_length, to_move, strlen((char *) to_move) + 1 /* null byte */); + memmove(to_move + l, to_move, strlen((char *) to_move) + 1 /* null byte */); - // after the move, to_move points to the memory - // where c should be inserted - for (int i = 0; i < symbol_length; i++) - to_move[i] = c[i]; + // We know this is not null-terminated. + // NOLINTNEXTLINE(bugprone-not-null-terminated-result) + memcpy(to_move, str, l); // move our selection to behind the inserted char - text_input.selection_extent++; + text_input.selection_extent += count_utf8_characters(str); text_input.selection_base = text_input.selection_extent; return true; @@ -731,14 +753,15 @@ static int sync_editing_state(void) { } /** - * `c` doesn't need to be NULL-terminated, the length of the char will be calculated - * using the start byte. + * @brief Called when text input was received from the keyboard. + * + * @param str The NULL-terminated UTF-8 string that was received. */ -int textin_on_utf8_char(uint8_t *c) { +int textin_on_text(const char *str) { if (text_input.connection_id == -1) return 0; - if (model_add_utf8_char(c)) + if (model_add_text(str)) return sync_editing_state(); return 0; @@ -762,7 +785,7 @@ int textin_on_xkb_keysym(xkb_keysym_t keysym) { case XKB_KEY_KP_Enter: case XKB_KEY_ISO_Enter: if (text_input.input_type == kInputTypeMultiline) - needs_sync = model_add_utf8_char((uint8_t *) "\n"); + needs_sync = model_add_text("\n"); perform_action = true; break; @@ -807,7 +830,7 @@ enum plugin_init_result textin_init(struct flutterpi *flutterpi, void **userdata return PLUGIN_INIT_RESULT_ERROR; } - ok = plugin_registry_set_receiver_locked(TEXT_INPUT_CHANNEL, kJSONMethodCall, on_receive); + ok = plugin_registry_set_receiver(TEXT_INPUT_CHANNEL, kJSONMethodCall, on_receive); if (ok != 0) { free(textin); return PLUGIN_INIT_RESULT_ERROR; @@ -834,7 +857,7 @@ enum plugin_init_result textin_init(struct flutterpi *flutterpi, void **userdata } void textin_deinit(struct flutterpi *flutterpi, void *userdata) { - plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), TEXT_INPUT_CHANNEL); + plugin_registry_remove_receiver_v2(flutterpi_get_plugin_registry(flutterpi), TEXT_INPUT_CHANNEL); free(userdata); } diff --git a/src/plugins/text_input.h b/src/plugins/text_input.h index 8c2a1198..9ac2e7b9 100644 --- a/src/plugins/text_input.h +++ b/src/plugins/text_input.h @@ -47,9 +47,18 @@ struct text_input_configuration { enum floating_cursor_drag_state { kFloatingCursorDragStateStart, kFloatingCursorDragStateUpdate, kFloatingCursorDragStateEnd }; -// parses the input string as linux terminal input and calls the TextInput model functions -// accordingly. -int textin_on_utf8_char(uint8_t *c); +/** + * @brief Should be called when text input was received from the keyboard. + * + * @param str The NULL-terminated UTF-8 string that was received. + */ +int textin_on_text(const char *text); + +/** + * @brief Should be called when a key was pressed on the keyboard. + * + * @param keysym The keysym that was pressed. + */ int textin_on_xkb_keysym(xkb_keysym_t keysym); #endif diff --git a/src/texture_registry.c b/src/texture_registry.c index a2ab95b0..2b2fd961 100644 --- a/src/texture_registry.c +++ b/src/texture_registry.c @@ -202,6 +202,7 @@ struct texture *texture_new(struct texture_registry *reg) { if (ok != 0) { pthread_mutex_destroy(&texture->lock); free(texture); + return NULL; } return texture; @@ -301,7 +302,7 @@ texture_gl_external_texture_frame_callback(struct texture *texture, size_t width if (texture->next_frame != NULL) { /// TODO: If acquiring the texture frame fails, flutter will destroy the texture frame two times. /// So we'll probably have a segfault if that happens. - frame = counted_texture_frame_ref(texture->next_frame); + frame = texture->next_frame; } else { frame = NULL; } @@ -315,14 +316,17 @@ texture_gl_external_texture_frame_callback(struct texture *texture, size_t width ok = frame->unresolved_frame.resolve(width, height, frame->unresolved_frame.userdata, &frame->frame); if (ok != 0) { LOG_ERROR("Couldn't resolve texture frame.\n"); - counted_texture_frame_unrefp(&frame); counted_texture_frame_unrefp(&texture->next_frame); + texture_unlock(texture); + return false; } frame->unresolved_frame.destroy(frame->unresolved_frame.userdata); frame->is_resolved = true; } + frame = counted_texture_frame_ref(frame); + texture_unlock(texture); // only actually fill out the frame info when we have a frame. diff --git a/src/tracer.c b/src/tracer.c index 9177fede..99623cc7 100644 --- a/src/tracer.c +++ b/src/tracer.c @@ -50,7 +50,7 @@ struct tracer *tracer_new_with_cbs( return NULL; } -struct tracer *tracer_new_with_stubs() { +struct tracer *tracer_new_with_stubs(void) { struct tracer *tracer; tracer = malloc(sizeof *tracer); diff --git a/src/tracer.h b/src/tracer.h index 896ee686..11ab967d 100644 --- a/src/tracer.h +++ b/src/tracer.h @@ -20,9 +20,9 @@ struct tracer *tracer_new_with_cbs( FlutterEngineTraceEventInstantFnPtr trace_instant ); -struct tracer *tracer_new_with_stubs(); +struct tracer *tracer_new_with_stubs(void); -DECLARE_REF_OPS(tracer); +DECLARE_REF_OPS(tracer) void __tracer_begin(struct tracer *tracer, const char *name); diff --git a/src/user_input.c b/src/user_input.c index 652d7df5..6a529447 100644 --- a/src/user_input.c +++ b/src/user_input.c @@ -10,6 +10,8 @@ #include #include +#include +#include #include #include #include @@ -24,11 +26,24 @@ #define LIBINPUT_VER(major, minor, patch) ((((major) & 0xFF) << 16) | (((minor) & 0xFF) << 8) | ((patch) & 0xFF)) #define THIS_LIBINPUT_VER LIBINPUT_VER(LIBINPUT_VERSION_MAJOR, LIBINPUT_VERSION_MINOR, LIBINPUT_VERSION_PATCH) +#define MAX_N_LISTENERS 8 + +KHASH_MAP_INIT_INT(metadata_for_fd, void *) + +struct device_listener { + int index; + void *userdata; +}; + struct input_device_data { + struct libinput_device *device; + struct keyboard_state *keyboard_state; - int64_t buttons; + uint8_t buttons; uint64_t timestamp; + int64_t device_id; + /** * @brief Whether libinput has ever emitted any pointer / mouse events * for this device. @@ -49,304 +64,460 @@ struct input_device_data { */ struct vec2f *positions; - int64_t touch_device_id_offset; - int64_t stylus_device_id; + int64_t touch_slot_id_offset; + int64_t stylus_slot_id; + + void *primary_listener_userdata; }; -struct user_input { - struct user_input_interface interface; +struct callback { + void_callback_t cb; void *userdata; +}; + +struct listener { + enum user_input_event_type filter; + bool is_primary; + + user_input_event_cb_t cb; + void *userdata; + + size_t n_events; + struct user_input_event events[64]; +}; + +struct user_input { + struct file_interface file_interface; + void *file_interface_userdata; + khash_t(metadata_for_fd) * metadata_for_fd; struct libinput *libinput; struct keyboard_config *kbdcfg; - int64_t next_unused_flutter_device_id; + int64_t next_device_id; + int64_t next_slot_id; - /// TODO: Maybe fetch the transform, display dimensions, cursor pos dynamically using a callback instead? + size_t n_cursor_devices; + size_t cursor_slot_id; - /** - * @brief transforms normalized display coordinates (0 .. display_width-1, 0 .. display_height-1) to the coordinates - * used in the flutter pointer events - */ - struct mat3f display_to_view_transform; - struct mat3f view_to_display_transform_nontranslating; - unsigned int display_width; - unsigned int display_height; + size_t n_listeners; + struct listener listeners[MAX_N_LISTENERS]; - /** - * @brief The number of devices connected that want a mouse cursor. - * libinput calls them pointer devices, flutter calls them mice. - */ - unsigned int n_cursor_devices; - /** - * @brief The flutter device id of the mouse cursor, if @ref n_cursor_devices > 0. - */ - int64_t cursor_flutter_device_id; - /** - * @brief The current mouse cursor position in floating point display coordinates (0 .. display_width-1, 0 .. display_height-1) - */ - double cursor_x, cursor_y; - - /** - * @brief Buffer of collected flutter pointer events, since we can send multiple events at once to flutter. - */ - FlutterPointerEvent collected_flutter_pointer_events[MAX_COLLECTED_FLUTTER_POINTER_EVENTS]; - /** - * @brief Number of pointer events currently contained in @ref collected_flutter_pointer_events. - */ - size_t n_collected_flutter_pointer_events; + kvec_t(struct input_device_data *) deferred_device_cleanups; }; -static inline FlutterPointerEvent make_touch_event(FlutterPointerPhase phase, size_t timestamp, struct vec2f pos, int32_t device_id) { - FlutterPointerEvent event; +static struct user_input_device *libinput_dev_to_user_input_dev(struct libinput_device *device) { + struct input_device_data *data = libinput_device_get_user_data(device); + + if (data == NULL) { + return NULL; + } + + return (struct user_input_device *) data; +} + +static struct input_device_data *user_input_device_get_device_data(struct user_input_device *device) { + return (struct input_device_data *) device; +} + +static inline struct user_input_event make_device_added_event(struct libinput_device *device, uint64_t timestamp) { + struct user_input_event event; memset(&event, 0, sizeof(event)); - event.struct_size = sizeof(event); - event.phase = phase; + event.type = USER_INPUT_DEVICE_ADDED; event.timestamp = timestamp; - event.x = pos.x; - event.y = pos.y; - event.device = device_id; - event.signal_kind = kFlutterPointerSignalKindNone; - event.scroll_delta_x = 0.0; - event.scroll_delta_y = 0.0; - event.device_kind = kFlutterPointerDeviceKindTouch; - event.buttons = 0; - event.pan_x = 0.0; - event.pan_y = 0.0; - event.scale = 0.0; - event.rotation = 0.0; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = 0; + event.slot_type = USER_INPUT_SLOT_POINTER; return event; } -UNUSED static inline FlutterPointerEvent make_touch_cancel_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_touch_event(kCancel, timestamp, pos, device_id); -} +static inline struct user_input_event make_device_removed_event(struct libinput_device *device, uint64_t timestamp) { + struct user_input_event event; + memset(&event, 0, sizeof(event)); -static inline FlutterPointerEvent make_touch_up_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_touch_event(kUp, timestamp, pos, device_id); -} + event.type = USER_INPUT_DEVICE_REMOVED; + event.timestamp = timestamp; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = 0; + event.slot_type = USER_INPUT_SLOT_POINTER; -static inline FlutterPointerEvent make_touch_down_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_touch_event(kDown, timestamp, pos, device_id); + return event; } -static inline FlutterPointerEvent make_touch_move_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_touch_event(kMove, timestamp, pos, device_id); -} +static inline struct user_input_event +make_slot_added_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, enum user_input_slot_type slot_type) { + struct user_input_event event; + memset(&event, 0, sizeof(event)); -static inline FlutterPointerEvent make_touch_add_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_touch_event(kAdd, timestamp, pos, device_id); -} + event.type = USER_INPUT_SLOT_ADDED; + event.timestamp = timestamp; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = slot_id; + event.slot_type = slot_type; -static inline FlutterPointerEvent make_touch_remove_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_touch_event(kRemove, timestamp, pos, device_id); + return event; } -static inline FlutterPointerEvent make_mouse_event( - FlutterPointerPhase phase, - size_t timestamp, - struct vec2f pos, - int32_t device_id, - FlutterPointerSignalKind signal_kind, - struct vec2f scroll_delta, - int64_t buttons -) { - FlutterPointerEvent event; +static inline struct user_input_event +make_slot_removed_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, enum user_input_slot_type slot_type) { + struct user_input_event event; memset(&event, 0, sizeof(event)); - event.struct_size = sizeof(event); - event.phase = phase; + event.type = USER_INPUT_SLOT_ADDED; event.timestamp = timestamp; - event.x = pos.x; - event.y = pos.y; - event.device = device_id; - event.signal_kind = signal_kind; - event.scroll_delta_x = scroll_delta.x; - event.scroll_delta_y = scroll_delta.y; - event.device_kind = kFlutterPointerDeviceKindMouse; - event.buttons = buttons; - event.pan_x = 0.0; - event.pan_y = 0.0; - event.scale = 0.0; - event.rotation = 0.0; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = slot_id; + event.slot_type = slot_type; return event; } -UNUSED static inline FlutterPointerEvent make_mouse_cancel_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_mouse_event(kCancel, timestamp, pos, device_id, kFlutterPointerSignalKindNone, VEC2F(0, 0), 0); -} +static inline struct user_input_event +make_pointer_motion_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, uint8_t buttons, struct vec2f delta) { + struct user_input_event event; + memset(&event, 0, sizeof(event)); -UNUSED static inline FlutterPointerEvent make_mouse_up_event(size_t timestamp, struct vec2f pos, int32_t device_id, int64_t buttons) { - return make_mouse_event(kUp, timestamp, pos, device_id, kFlutterPointerSignalKindNone, VEC2F(0, 0), buttons); -} + event.type = USER_INPUT_POINTER; + event.timestamp = timestamp; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = slot_id; + event.slot_type = USER_INPUT_SLOT_POINTER; + event.pointer.buttons = buttons; + event.pointer.changed_buttons = 0; + event.pointer.is_absolute = false; + event.pointer.delta = delta; + event.pointer.scroll_delta = VEC2F(0.0, 0.0); -UNUSED static inline FlutterPointerEvent make_mouse_down_event(size_t timestamp, struct vec2f pos, int32_t device_id, int64_t buttons) { - return make_mouse_event(kDown, timestamp, pos, device_id, kFlutterPointerSignalKindNone, VEC2F(0, 0), buttons); + return event; } -static inline FlutterPointerEvent make_mouse_move_event(size_t timestamp, struct vec2f pos, int32_t device_id, int64_t buttons) { - return make_mouse_event(kMove, timestamp, pos, device_id, kFlutterPointerSignalKindNone, VEC2F(0, 0), buttons); +static inline struct user_input_event +make_pointer_motion_absolute_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, uint8_t buttons, struct vec2f pos) { + struct user_input_event event; + memset(&event, 0, sizeof(event)); + + event.type = USER_INPUT_POINTER; + event.timestamp = timestamp; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = slot_id; + event.slot_type = USER_INPUT_SLOT_POINTER; + event.pointer.buttons = buttons; + event.pointer.changed_buttons = 0; + event.pointer.is_absolute = true; + event.pointer.position_ndc = pos; + event.pointer.scroll_delta = VEC2F(0.0, 0.0); + return event; } -static inline FlutterPointerEvent make_mouse_add_event(size_t timestamp, struct vec2f pos, int32_t device_id, int64_t buttons) { - return make_mouse_event(kAdd, timestamp, pos, device_id, kFlutterPointerSignalKindNone, VEC2F(0, 0), buttons); +static inline struct user_input_event +make_pointer_button_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, uint8_t buttons, uint8_t changed_buttons) { + struct user_input_event event; + memset(&event, 0, sizeof(event)); + + event.type = USER_INPUT_POINTER; + event.timestamp = timestamp; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = slot_id; + event.slot_type = USER_INPUT_SLOT_POINTER; + event.pointer.buttons = buttons; + event.pointer.changed_buttons = changed_buttons; + event.pointer.is_absolute = false; + event.pointer.delta = VEC2F(0.0, 0.0); + event.pointer.scroll_delta = VEC2F(0.0, 0.0); + return event; } -static inline FlutterPointerEvent make_mouse_remove_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_mouse_event(kRemove, timestamp, pos, device_id, kFlutterPointerSignalKindNone, VEC2F(0, 0), 0); +static inline struct user_input_event +make_pointer_axis_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, uint8_t buttons, struct vec2f scroll_delta) { + struct user_input_event event; + memset(&event, 0, sizeof(event)); + + event.type = USER_INPUT_POINTER; + event.timestamp = timestamp; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = slot_id; + event.slot_type = USER_INPUT_SLOT_POINTER; + event.pointer.buttons = buttons; + event.pointer.changed_buttons = 0; + event.pointer.is_absolute = false; + event.pointer.delta = VEC2F(0.0, 0.0); + event.pointer.scroll_delta = scroll_delta; + return event; } -static inline FlutterPointerEvent make_mouse_hover_event(size_t timestamp, struct vec2f pos, int32_t device_id, int64_t buttons) { - return make_mouse_event(kHover, timestamp, pos, device_id, kFlutterPointerSignalKindNone, VEC2F(0, 0), buttons); +static inline struct user_input_event +make_touch_down_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, struct vec2f pos_ndc) { + struct user_input_event event; + memset(&event, 0, sizeof(event)); + + event.type = USER_INPUT_TOUCH; + event.timestamp = timestamp; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = slot_id; + event.slot_type = USER_INPUT_SLOT_TOUCH; + event.touch.down = true; + event.touch.down_changed = true; + event.touch.position_ndc = pos_ndc; + return event; } -static inline FlutterPointerEvent make_stylus_event(FlutterPointerPhase phase, size_t timestamp, struct vec2f pos, int32_t device_id) { - FlutterPointerEvent event; +static inline struct user_input_event +make_touch_up_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, struct vec2f pos_ndc) { + struct user_input_event event; memset(&event, 0, sizeof(event)); - event.struct_size = sizeof(event); - event.phase = phase; + event.type = USER_INPUT_TOUCH; event.timestamp = timestamp; - event.x = pos.x; - event.y = pos.y; - event.device = device_id; - event.signal_kind = kFlutterPointerSignalKindNone; - event.scroll_delta_x = 0.0; - event.scroll_delta_y = 0.0; - event.device_kind = kFlutterPointerDeviceKindStylus; - event.buttons = 0; - event.pan_x = 0.0; - event.pan_y = 0.0; - event.scale = 0.0; - event.rotation = 0.0; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = slot_id; + event.slot_type = USER_INPUT_SLOT_TOUCH; + event.touch.down = false; + event.touch.down_changed = true; + event.touch.position_ndc = pos_ndc; + return event; +} +static inline struct user_input_event +make_touch_move_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, struct vec2f pos_ndc) { + struct user_input_event event; + memset(&event, 0, sizeof(event)); + + event.type = USER_INPUT_TOUCH; + event.timestamp = timestamp; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = slot_id; + event.slot_type = USER_INPUT_SLOT_TOUCH; + event.touch.down = true; + event.touch.down_changed = false; + event.touch.position_ndc = pos_ndc; return event; } -UNUSED static inline FlutterPointerEvent make_stylus_cancel_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_stylus_event(kCancel, timestamp, pos, device_id); +static inline struct user_input_event +make_stylus_down_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, struct vec2f pos_ndc) { + struct user_input_event event; + memset(&event, 0, sizeof(event)); + + event.type = USER_INPUT_TABLET_TOOL; + event.timestamp = timestamp; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = slot_id; + event.slot_type = USER_INPUT_SLOT_TABLET_TOOL; + event.tablet.tip = true; + event.tablet.tip_changed = true; + event.tablet.tool = LIBINPUT_TABLET_TOOL_TYPE_PEN; + event.tablet.position_ndc = pos_ndc; + return event; } -static inline FlutterPointerEvent make_stylus_up_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_stylus_event(kUp, timestamp, pos, device_id); +static inline struct user_input_event +make_stylus_up_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, struct vec2f pos_ndc) { + struct user_input_event event; + memset(&event, 0, sizeof(event)); + + event.type = USER_INPUT_TABLET_TOOL; + event.timestamp = timestamp; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = slot_id; + event.slot_type = USER_INPUT_SLOT_TABLET_TOOL; + event.tablet.tip = false; + event.tablet.tip_changed = true; + event.tablet.tool = LIBINPUT_TABLET_TOOL_TYPE_PEN; + event.tablet.position_ndc = pos_ndc; + return event; } -static inline FlutterPointerEvent make_stylus_down_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_stylus_event(kDown, timestamp, pos, device_id); +static inline struct user_input_event +make_stylus_move_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, bool tip, struct vec2f pos_ndc) { + struct user_input_event event; + memset(&event, 0, sizeof(event)); + + event.type = USER_INPUT_TABLET_TOOL; + event.timestamp = timestamp; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = slot_id; + event.slot_type = USER_INPUT_SLOT_TABLET_TOOL; + event.tablet.tip = tip; + event.tablet.tip_changed = false; + event.tablet.tool = LIBINPUT_TABLET_TOOL_TYPE_PEN; + event.tablet.position_ndc = pos_ndc; + return event; } -static inline FlutterPointerEvent make_stylus_move_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_stylus_event(kMove, timestamp, pos, device_id); +static inline struct user_input_event make_key_event( + struct libinput_device *device, + uint64_t timestamp, + xkb_keycode_t xkb_keycode, + xkb_keysym_t xkb_keysym, + uint32_t plain_codepoint, + key_modifiers_t modifiers, + const char text[8], + bool is_down, + bool is_repeat +) { + struct user_input_event event; + memset(&event, 0, sizeof(event)); + + event.type = USER_INPUT_KEY; + event.timestamp = timestamp; + event.device = libinput_dev_to_user_input_dev(device); + event.global_slot_id = 0; + event.slot_type = USER_INPUT_SLOT_POINTER; + event.key.xkb_keycode = xkb_keycode; + event.key.xkb_keysym = xkb_keysym; + event.key.plain_codepoint = plain_codepoint; + event.key.modifiers = modifiers; + memcpy(event.key.text, text, sizeof(event.key.text)); + event.key.is_down = is_down; + event.key.is_repeat = is_repeat; + return event; } -static inline FlutterPointerEvent make_stylus_hover_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_stylus_event(kHover, timestamp, pos, device_id); +static void flush_listener_events(struct listener *listener) { + ASSERT_NOT_NULL(listener); + + if (listener->n_events > 0) { + listener->cb(listener->userdata, listener->n_events, listener->events); + listener->n_events = 0; + } } -static inline FlutterPointerEvent make_stylus_add_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_stylus_event(kAdd, timestamp, pos, device_id); +static void flush_events(struct user_input *input) { + ASSERT_NOT_NULL(input); + + for (size_t i = 0; i < input->n_listeners; i++) { + flush_listener_events(input->listeners + i); + } } -static inline FlutterPointerEvent make_stylus_remove_event(size_t timestamp, struct vec2f pos, int32_t device_id) { - return make_stylus_event(kRemove, timestamp, pos, device_id); +static bool emit_event(struct user_input *input, const struct user_input_event event) { + assert(input != NULL); + + bool emitted = false; + + for (size_t i = 0; i < input->n_listeners; i++) { + struct listener *listener = input->listeners + i; + if (!(listener->filter & event.type)) { + continue; + } + + if (listener->n_events == ARRAY_SIZE(listener->events)) { + flush_listener_events(listener); + } + + memcpy(listener->events + listener->n_events, &event, sizeof(event)); + listener->n_events++; + + emitted = true; + } + + return emitted; } // libinput interface -static int on_open(const char *path, int flags, void *userdata) { +static int on_libinput_open(const char *path, int flags, void *userdata) { struct user_input *input; + void *fd_metadata; ASSERT_NOT_NULL(path); ASSERT_NOT_NULL(userdata); input = userdata; - return input->interface.open(path, flags | O_CLOEXEC, input->userdata); + int fd = input->file_interface.open(path, flags | O_CLOEXEC, &fd_metadata, input->file_interface_userdata); + if (fd < 0) { + LOG_DEBUG("Could not open input device. open: %s\n", strerror(errno)); + return -1; + } + + int bucket_status; + khiter_t k = kh_put(metadata_for_fd, input->metadata_for_fd, fd, &bucket_status); + if (bucket_status < 0) { + close(fd); + errno = ENOMEM; + return -1; + } + + kh_val(input->metadata_for_fd, k) = fd_metadata; + + return fd; } -static void on_close(int fd, void *userdata) { +static void on_libinput_close(int fd, void *userdata) { struct user_input *input; + void *fd_metadata; ASSERT_NOT_NULL(userdata); input = userdata; - return input->interface.close(fd, input->userdata); + khiter_t k = kh_get(metadata_for_fd, input->metadata_for_fd, fd); + if (k == kh_end(input->metadata_for_fd)) { + LOG_ERROR("Attempted to close an fd that was not previously opened.\n"); + close(fd); + } + + fd_metadata = kh_val(input->metadata_for_fd, k); + input->file_interface.close(fd, fd_metadata, input->file_interface_userdata); } -static const struct libinput_interface libinput_interface = { .open_restricted = on_open, .close_restricted = on_close }; +static const struct libinput_interface libinput_interface = { .open_restricted = on_libinput_open, .close_restricted = on_libinput_close }; -struct user_input *user_input_new( - const struct user_input_interface *interface, - void *userdata, - const struct mat3f *display_to_view_transform, - const struct mat3f *view_to_display_transform, - unsigned int display_width, - unsigned int display_height -) { - struct keyboard_config *kbdcfg; +struct user_input *user_input_new_suspended(const struct file_interface *interface, void *userdata, struct udev *udev, const char *seat) { struct user_input *input; - struct libinput *libinput; - struct udev *udev; int ok; + ASSERT_NOT_NULL(interface); + ASSERT_NOT_NULL(udev); + input = malloc(sizeof *input); if (input == NULL) { goto fail_return_null; } - udev = udev_new(); - if (udev == NULL) { - perror("[flutter-pi] Could not create udev instance. udev_new"); + input->file_interface = *interface; + input->file_interface_userdata = userdata; + input->metadata_for_fd = kh_init(metadata_for_fd); + if (input->metadata_for_fd == NULL) { goto fail_free_input; } - input->interface = *interface; - input->userdata = userdata; - - libinput = libinput_udev_create_context(&libinput_interface, input, udev); - if (libinput == NULL) { + input->libinput = libinput_udev_create_context(&libinput_interface, input, udev); + if (input->libinput == NULL) { perror("[flutter-pi] Could not create libinput instance. libinput_udev_create_context"); - goto fail_unref_udev; + goto fail_destroy_metadata; } - udev_unref(udev); + libinput_suspend(input->libinput); - ok = libinput_udev_assign_seat(libinput, "seat0"); + ok = libinput_udev_assign_seat(input->libinput, seat ? seat : "seat0"); if (ok < 0) { LOG_ERROR("Could not assign udev seat to libinput instance. libinput_udev_assign_seat: %s\n", strerror(-ok)); goto fail_unref_libinput; } #ifdef BUILD_TEXT_INPUT_PLUGIN - kbdcfg = keyboard_config_new(); - if (kbdcfg == NULL) { + input->kbdcfg = keyboard_config_new(); + if (input->kbdcfg == NULL) { LOG_ERROR("Could not initialize keyboard configuration. Flutter-pi will run without text/raw keyboard input.\n"); } #else kbdcfg = NULL; #endif - input->libinput = libinput; - input->kbdcfg = kbdcfg; - input->next_unused_flutter_device_id = 0; - - user_input_set_transform(input, display_to_view_transform, view_to_display_transform, display_width, display_height); - + input->next_slot_id = 0; + input->next_device_id = 0; input->n_cursor_devices = 0; - input->cursor_flutter_device_id = -1; - input->cursor_x = 0.0; - input->cursor_y = 0.0; - - input->n_collected_flutter_pointer_events = 0; - + input->cursor_slot_id = -1; + input->n_listeners = 0; + memset(input->listeners, 0, sizeof(input->listeners)); + kv_init(input->deferred_device_cleanups); return input; fail_unref_libinput: - libinput_unref(libinput); - goto fail_free_input; + libinput_unref(input->libinput); -fail_unref_udev: - udev_unref(udev); +fail_destroy_metadata: + kh_destroy(metadata_for_fd, input->metadata_for_fd); fail_free_input: free(input); @@ -376,6 +547,8 @@ void user_input_destroy(struct user_input *input) { event = libinput_get_event(input->libinput); event_type = libinput_event_get_type(event); + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wswitch-enum") switch (event_type) { case LIBINPUT_EVENT_DEVICE_REMOVED: ok = on_device_removed(input, event, 0, false); @@ -383,6 +556,7 @@ void user_input_destroy(struct user_input *input) { break; default: break; } + PRAGMA_DIAGNOSTIC_POP libinput_event_destroy(event); } @@ -394,25 +568,6 @@ void user_input_destroy(struct user_input *input) { free(input); } -void user_input_set_transform( - struct user_input *input, - const struct mat3f *display_to_view_transform, - const struct mat3f *view_to_display_transform, - unsigned int display_width, - unsigned int display_height -) { - assert(input != NULL); - assert(display_to_view_transform != NULL); - assert(view_to_display_transform != NULL); - - input->display_to_view_transform = *display_to_view_transform; - input->view_to_display_transform_nontranslating = *view_to_display_transform; - input->view_to_display_transform_nontranslating.transX = 0.0; - input->view_to_display_transform_nontranslating.transY = 0.0; - input->display_width = display_width; - input->display_height = display_height; -} - int user_input_get_fd(struct user_input *input) { assert(input != NULL); return libinput_get_fd(input->libinput); @@ -436,96 +591,30 @@ int user_input_resume(struct user_input *input) { return 0; } -static void flush_pointer_events(struct user_input *input) { - assert(input != NULL); - - if (input->n_collected_flutter_pointer_events > 0) { - input->interface.on_flutter_pointer_event( - input->userdata, - input->collected_flutter_pointer_events, - input->n_collected_flutter_pointer_events - ); - - input->n_collected_flutter_pointer_events = 0; - } -} - -UNUSED static void emit_pointer_events(struct user_input *input, const FlutterPointerEvent *events, size_t n_events) { - assert(input != NULL); - assert(events != NULL); - - size_t to_copy; - - while (n_events > 0) { - // if the internal buffer is full, flush it - if (input->n_collected_flutter_pointer_events == MAX_COLLECTED_FLUTTER_POINTER_EVENTS) { - flush_pointer_events(input); - } - - // how many pointer events we can copy into the internal pointer event buffer? - to_copy = MIN2(n_events, MAX_COLLECTED_FLUTTER_POINTER_EVENTS - input->n_collected_flutter_pointer_events); - - // copy into the internal pointer event buffer - memcpy( - input->collected_flutter_pointer_events + input->n_collected_flutter_pointer_events, - events, - to_copy * sizeof(FlutterPointerEvent) - ); - - // advance n_events so it's now the number of remaining unemitted events - n_events -= to_copy; - - // advance events so it points to the first remaining unemitted event - events += to_copy; - - // advance the number of stored pointer events - input->n_collected_flutter_pointer_events += to_copy; - } -} - -static void emit_pointer_event(struct user_input *input, const FlutterPointerEvent event) { - assert(input != NULL); - - // if the internal buffer is full, flush it - if (input->n_collected_flutter_pointer_events == MAX_COLLECTED_FLUTTER_POINTER_EVENTS) { - flush_pointer_events(input); - } - - memcpy(input->collected_flutter_pointer_events + input->n_collected_flutter_pointer_events, &event, sizeof(event)); - - input->n_collected_flutter_pointer_events += 1; -} - /** * @brief Called when input->n_cursor_devices was increased to maybe enable the mouse cursor * it it isn't yet enabled. */ -static void maybe_enable_mouse_cursor(struct user_input *input, uint64_t timestamp) { +static void maybe_enable_mouse_cursor(struct user_input *input, struct libinput_device *device, uint64_t timestamp) { assert(input != NULL); if (input->n_cursor_devices == 1) { - if (input->cursor_flutter_device_id == -1) { - input->cursor_flutter_device_id = input->next_unused_flutter_device_id++; + if (input->cursor_slot_id == -1) { + input->cursor_slot_id = input->next_slot_id++; } - emit_pointer_event( - input, - make_mouse_add_event(timestamp, VEC2F(input->cursor_x, input->cursor_y), input->cursor_flutter_device_id, 0) - ); + emit_event(input, make_slot_added_event(device, timestamp, input->cursor_slot_id, USER_INPUT_SLOT_POINTER)); } } /** * @brief Called when input->n_cursor_devices was decreased to maybe disable the mouse cursor. */ -static void maybe_disable_mouse_cursor(struct user_input *input, uint64_t timestamp) { +static void maybe_disable_mouse_cursor(struct user_input *input, struct libinput_device *device, uint64_t timestamp) { assert(input != NULL); if (input->n_cursor_devices == 0) { - emit_pointer_event( - input, - make_mouse_remove_event(timestamp, VEC2F(input->cursor_x, input->cursor_y), input->cursor_flutter_device_id) - ); + emit_event(input, make_slot_removed_event(device, timestamp, input->cursor_slot_id, USER_INPUT_SLOT_POINTER)); } } @@ -545,25 +634,27 @@ static int on_device_added(struct user_input *input, struct libinput_event *even return ENOMEM; } - data->touch_device_id_offset = -1; - data->stylus_device_id = -1; + data->device = libinput_device_ref(device); + data->device_id = input->next_device_id++; + data->touch_slot_id_offset = -1; + data->stylus_slot_id = -1; data->keyboard_state = NULL; data->buttons = 0; data->timestamp = timestamp; data->has_emitted_pointer_events = false; data->tip = false; data->positions = NULL; + data->primary_listener_userdata = NULL; libinput_device_set_user_data(device, data); + emit_event(input, make_device_added_event(device, timestamp)); + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)) { // no special things to do here // mouse pointer will be added as soon as the device actually sends a // mouse event, as some devices will erroneously have a LIBINPUT_DEVICE_CAP_POINTER // even though they aren't mice. (My keyboard for example is a mouse smh) - - // reserve one id for the mouse pointer - // input->next_unused_flutter_device_id++; } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { @@ -577,12 +668,10 @@ static int on_device_added(struct user_input *input, struct libinput_event *even goto fail_free_data; } - data->touch_device_id_offset = input->next_unused_flutter_device_id; + data->touch_slot_id_offset = input->next_slot_id; for (int i = 0; i < n_slots; i++) { - device_id = input->next_unused_flutter_device_id++; - - emit_pointer_event(input, make_touch_add_event(timestamp, VEC2F(0, 0), device_id)); + emit_event(input, make_slot_added_event(device, timestamp, input->next_slot_id++, USER_INPUT_SLOT_TOUCH)); } positions = malloc(n_slots * sizeof(struct vec2f)); @@ -604,11 +693,10 @@ static int on_device_added(struct user_input *input, struct libinput_event *even } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) { - device_id = input->next_unused_flutter_device_id++; - - data->stylus_device_id = device_id; + device_id = input->next_slot_id++; - emit_pointer_event(input, make_stylus_add_event(timestamp, VEC2F(0, 0), device_id)); + emit_event(input, make_slot_added_event(device, timestamp, device_id, USER_INPUT_SLOT_TABLET_TOOL)); + data->stylus_slot_id = device_id; } return 0; @@ -618,6 +706,21 @@ static int on_device_added(struct user_input *input, struct libinput_event *even return EINVAL; } +static void cleanup_device_data(struct input_device_data *data) { + libinput_device_set_user_data(data->device, NULL); + libinput_device_unref(data->device); + + if (data->keyboard_state != NULL) { + keyboard_state_destroy(data->keyboard_state); + } + + if (data->positions != NULL) { + free(data->positions); + } + + free(data); +} + static int on_device_removed(struct user_input *input, struct libinput_event *event, uint64_t timestamp, bool emit_flutter_events) { struct input_device_data *data; struct libinput_device *device; @@ -646,9 +749,7 @@ static int on_device_removed(struct user_input *input, struct libinput_event *ev if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)) { if (data->has_emitted_pointer_events) { input->n_cursor_devices--; - if (emit_flutter_events) { - maybe_disable_mouse_cursor(input, timestamp); - } + maybe_disable_mouse_cursor(input, device, timestamp); } } @@ -656,31 +757,18 @@ static int on_device_removed(struct user_input *input, struct libinput_event *ev // add all touch slots as individual touch devices to flutter if (emit_flutter_events) { for (int i = 0; i < libinput_device_touch_get_touch_count(device); i++) { - emit_pointer_event(input, make_touch_remove_event(timestamp, VEC2F(0, 0), data->touch_device_id_offset + i)); + emit_event(input, make_slot_removed_event(device, timestamp, data->touch_slot_id_offset + i, USER_INPUT_SLOT_TOUCH)); } } } - if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD)) { - // create a new keyboard state for this keyboard - if (data->keyboard_state != NULL) { - keyboard_state_destroy(data->keyboard_state); - } - } - if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) { - emit_pointer_event(input, make_stylus_remove_event(timestamp, VEC2F(0, 0), data->stylus_device_id)); - } - - if (data != NULL) { - if (data->positions != NULL) { - free(data->positions); - } - free(data); + emit_event(input, make_slot_removed_event(device, timestamp, data->stylus_slot_id, USER_INPUT_SLOT_TABLET_TOOL)); } - libinput_device_set_user_data(device, NULL); + emit_event(input, make_device_removed_event(device, timestamp)); + kv_push(input->deferred_device_cleanups, data); return 0; } @@ -732,12 +820,12 @@ static int on_key_event(struct user_input *input, struct libinput_event *event) keysym ); - if (input->interface.on_switch_vt != NULL && keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { - // "switch VT" keybind - input->interface.on_switch_vt(input->userdata, keysym - XKB_KEY_XF86Switch_VT_1 + 1); - } + // if (input->interface.on_switch_vt != NULL && keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { + // // "switch VT" keybind + // input->interface.on_switch_vt(input->userdata, keysym - XKB_KEY_XF86Switch_VT_1 + 1); + // } - uint8_t utf8_character[5] = { 0 }; + uint8_t utf8_character[8] = { 0 }; // Call the UTF8 character callback if we've got a codepoint. // Code very similiar to that of the linux kernel in drivers/tty/vt/keyboard.c, to_utf8 @@ -746,68 +834,49 @@ static int on_key_event(struct user_input *input, struct libinput_event *event) // we emit UTF8 unconditionally here, // maybe we should check if codepoint is a control character? if (isprint(codepoint)) { - utf8_character[0] = codepoint; + utf8_character[0] = (uint8_t) codepoint; } } else if (codepoint < 0x800) { - utf8_character[0] = 0xc0 | (codepoint >> 6); + utf8_character[0] = 0xc0 | (uint8_t) (codepoint >> 6); utf8_character[1] = 0x80 | (codepoint & 0x3f); } else if (codepoint < 0x10000) { // the console keyboard driver of the linux kernel checks // at this point whether `codepoint` is a UTF16 high surrogate (U+D800 to U+DFFF) // or U+FFFF and returns without emitting UTF8 in that case. // don't know whether we should do this here too - utf8_character[0] = 0xe0 | (codepoint >> 12); + utf8_character[0] = 0xe0 | (uint8_t) (codepoint >> 12); utf8_character[1] = 0x80 | ((codepoint >> 6) & 0x3f); utf8_character[2] = 0x80 | (codepoint & 0x3f); } else if (codepoint < 0x110000) { - utf8_character[0] = 0xf0 | (codepoint >> 18); + utf8_character[0] = 0xf0 | (uint8_t) (codepoint >> 18); utf8_character[1] = 0x80 | ((codepoint >> 12) & 0x3f); utf8_character[2] = 0x80 | ((codepoint >> 6) & 0x3f); utf8_character[3] = 0x80 | (codepoint & 0x3f); } } - if (input->interface.on_key_event) { - input->interface.on_key_event( - input->userdata, + emit_event( + input, + make_key_event( + device, libinput_event_keyboard_get_time_usec(key_event), evdev_keycode + 8u, keysym, plain_codepoint, - (key_modifiers_t){ .shift = keyboard_state_is_shift_active(data->keyboard_state), - .capslock = keyboard_state_is_capslock_active(data->keyboard_state), - .ctrl = keyboard_state_is_ctrl_active(data->keyboard_state), - .alt = keyboard_state_is_alt_active(data->keyboard_state), - .numlock = keyboard_state_is_numlock_active(data->keyboard_state), - .__pad = 0, - .meta = keyboard_state_is_meta_active(data->keyboard_state) }, + (key_modifiers_t){ + .shift = keyboard_state_is_shift_active(data->keyboard_state), + .capslock = keyboard_state_is_capslock_active(data->keyboard_state), + .ctrl = keyboard_state_is_ctrl_active(data->keyboard_state), + .alt = keyboard_state_is_alt_active(data->keyboard_state), + .numlock = keyboard_state_is_numlock_active(data->keyboard_state), + .__pad = 0, + .meta = keyboard_state_is_meta_active(data->keyboard_state), + }, (char *) utf8_character, key_state == LIBINPUT_KEY_STATE_PRESSED, false - ); - } else { - // call the GTK keyevent callback. - /// TODO: Simplify the meta state construction. - input->interface.on_gtk_keyevent( - input->userdata, - plain_codepoint, - (uint32_t) keysym, - evdev_keycode + (uint32_t) 8, - keyboard_state_is_shift_active(data->keyboard_state) | (keyboard_state_is_capslock_active(data->keyboard_state) << 1) | - (keyboard_state_is_ctrl_active(data->keyboard_state) << 2) | (keyboard_state_is_alt_active(data->keyboard_state) << 3) | - (keyboard_state_is_numlock_active(data->keyboard_state) << 4) | (keyboard_state_is_meta_active(data->keyboard_state) << 28), - key_state - ); - - if (utf8_character[0]) { - input->interface.on_utf8_character(input->userdata, utf8_character); - } - - // Call the XKB keysym callback if we've got a keysym. - if (keysym) { - input->interface.on_xkb_keysym(input->userdata, keysym); - } - } + ) + ); return 0; } @@ -816,7 +885,6 @@ 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 vec2f delta, pos_display, pos_view; uint64_t timestamp; assert(input != NULL); @@ -829,52 +897,15 @@ static int on_mouse_motion_event(struct user_input *input, struct libinput_event data->timestamp = timestamp; - delta = transform_point( - input->view_to_display_transform_nontranslating, - VEC2F(libinput_event_pointer_get_dx(pointer_event), libinput_event_pointer_get_dy(pointer_event)) - ); - - pos_display = VEC2F(input->cursor_x + delta.x, input->cursor_y + delta.y); - - // check if we're ran over the display boundaries. - 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 (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 = pos_display.x; - input->cursor_y = pos_display.y; - - // transform the cursor pos to view (flutter) coordinates. - pos_view = transform_point(input->display_to_view_transform, pos_display); + struct vec2f delta = VEC2F(libinput_event_pointer_get_dx(pointer_event), libinput_event_pointer_get_dy(pointer_event)); if (data->has_emitted_pointer_events == false) { data->has_emitted_pointer_events = true; input->n_cursor_devices++; - maybe_enable_mouse_cursor(input, timestamp); + maybe_enable_mouse_cursor(input, device, timestamp); } - // send the pointer event to flutter. - emit_pointer_event( - input, - data->buttons & kFlutterPointerButtonMousePrimary ? - make_mouse_move_event(timestamp, pos_view, input->cursor_flutter_device_id, data->buttons) : - make_mouse_hover_event(timestamp, pos_view, input->cursor_flutter_device_id, data->buttons) - ); - - // we don't invoke the interfaces' mouse move callback here, since we - // can have multiple mouse motion events per process_libinput_events - // and we don't want to invoke the callback each time. - // instead, we call it in user_input_on_fd_ready if the cursors - // display coordinates changed. - + emit_event(input, make_pointer_motion_event(device, timestamp, input->cursor_slot_id, data->buttons, delta)); return 0; } @@ -882,7 +913,6 @@ 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 vec2f pos_display, pos_view; uint64_t timestamp; assert(input != NULL); @@ -893,33 +923,21 @@ static int on_mouse_motion_absolute_event(struct user_input *input, struct libin data = libinput_device_get_user_data(device); timestamp = libinput_event_pointer_get_time_usec(pointer_event); - // get the new mouse position in display coordinates - pos_display = VEC2F( - libinput_event_pointer_get_absolute_x_transformed(pointer_event, input->display_width), - libinput_event_pointer_get_absolute_y_transformed(pointer_event, input->display_height) - ); - data->timestamp = timestamp; - // update the "global" cursor position - input->cursor_x = pos_display.x; - input->cursor_y = pos_display.y; - // transform x & y to view (flutter) coordinates - pos_view = transform_point(input->display_to_view_transform, pos_display); + struct vec2f pos_ndc = VEC2F( + libinput_event_pointer_get_absolute_x_transformed(pointer_event, 1.0), + libinput_event_pointer_get_absolute_y_transformed(pointer_event, 1.0) + ); if (data->has_emitted_pointer_events == false) { data->has_emitted_pointer_events = true; input->n_cursor_devices++; - maybe_enable_mouse_cursor(input, timestamp); + maybe_enable_mouse_cursor(input, device, timestamp); } - emit_pointer_event( - input, - data->buttons & kFlutterPointerButtonMousePrimary ? - make_mouse_move_event(timestamp, pos_view, input->cursor_flutter_device_id, data->buttons) : - make_mouse_hover_event(timestamp, pos_view, input->cursor_flutter_device_id, data->buttons) - ); + emit_event(input, make_pointer_motion_absolute_event(device, timestamp, input->cursor_slot_id, data->buttons, pos_ndc)); return 0; } @@ -929,12 +947,10 @@ static int on_mouse_button_event(struct user_input *input, struct libinput_event enum libinput_button_state button_state; struct input_device_data *data; struct libinput_device *device; - FlutterPointerPhase pointer_phase; - struct vec2f pos_view; uint64_t timestamp; uint16_t evdev_code; - int64_t flutter_button; - int64_t new_flutter_button_state; + uint8_t flutter_button; + uint8_t new_flutter_button_state; assert(input != NULL); assert(event != NULL); @@ -949,7 +965,7 @@ static int on_mouse_button_event(struct user_input *input, struct libinput_event if (data->has_emitted_pointer_events == false) { data->has_emitted_pointer_events = true; input->n_cursor_devices++; - maybe_enable_mouse_cursor(input, timestamp); + maybe_enable_mouse_cursor(input, device, timestamp); } // find out the flutter mouse button for this event @@ -981,33 +997,14 @@ static int on_mouse_button_event(struct user_input *input, struct libinput_event // if our button state changed, // emit a pointer event if (new_flutter_button_state != data->buttons) { - if (new_flutter_button_state == 0) { - // no buttons are pressed anymore. - pointer_phase = kUp; - } else if (data->buttons == 0) { - // previously, there were no buttons pressed. - // now, at least 1 is pressed. - pointer_phase = kDown; - } else { - // some button was pressed or released, - // but it - pointer_phase = kMove; - } - - // since the stored coords are in display, not view coordinates, - // we need to transform them again - pos_view = transform_point(input->display_to_view_transform, VEC2F(input->cursor_x, input->cursor_y)); - - emit_pointer_event( + emit_event( input, - make_mouse_event( - pointer_phase, + make_pointer_button_event( + device, timestamp, - pos_view, - input->cursor_flutter_device_id, - kFlutterPointerSignalKindNone, - VEC2F(0, 0), - new_flutter_button_state + input->cursor_slot_id, + new_flutter_button_state, + new_flutter_button_state ^ data->buttons ) ); @@ -1022,7 +1019,6 @@ 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 vec2f pos_view; uint64_t timestamp; assert(input != NULL); @@ -1035,8 +1031,6 @@ static int on_mouse_axis_event(struct user_input *input, struct libinput_event * // since the stored coords are in display, not view coordinates, // we need to transform them again - pos_view = transform_point(input->display_to_view_transform, VEC2F(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) : 0.0; @@ -1045,17 +1039,16 @@ static int on_mouse_axis_event(struct user_input *input, struct libinput_event * libinput_event_pointer_get_axis_value(pointer_event, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) : 0.0; - emit_pointer_event( + emit_event( input, - make_mouse_event( - data->buttons & kFlutterPointerButtonMousePrimary ? kMove : kHover, + make_pointer_axis_event( + device, timestamp, - pos_view, - input->cursor_flutter_device_id, - kFlutterPointerSignalKindScroll, - VEC2F(scroll_x / 15.0 * 53.0, scroll_y / 15.0 * 53.0), - data->buttons + input->cursor_slot_id, + data->buttons, + VEC2F(scroll_x / 15.0 * 53.0, scroll_y / 15.0 * 53.0) ) + ); return 0; @@ -1064,7 +1057,6 @@ 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 vec2f pos_view; uint64_t timestamp; int64_t device_id; int slot; @@ -1072,6 +1064,7 @@ static int on_touch_down(struct user_input *input, struct libinput_event *event) assert(input != NULL); assert(event != NULL); + struct libinput_device *device = libinput_event_get_device(event); data = libinput_device_get_user_data(libinput_event_get_device(event)); touch_event = libinput_event_get_touch_event(event); timestamp = libinput_event_touch_get_time_usec(touch_event); @@ -1083,22 +1076,16 @@ static int on_touch_down(struct user_input *input, struct libinput_event *event) slot = 0; } - device_id = data->touch_device_id_offset + slot; + device_id = data->touch_slot_id_offset + slot; // transform the display coordinates to view (flutter) coordinates - pos_view = transform_point( - input->display_to_view_transform, - VEC2F( - libinput_event_touch_get_x_transformed(touch_event, input->display_width), - libinput_event_touch_get_y_transformed(touch_event, input->display_height) - ) - ); + struct vec2f pos_ndc = + VEC2F(libinput_event_touch_get_x_transformed(touch_event, 1.0), libinput_event_touch_get_y_transformed(touch_event, 1.0)); - // emit the flutter pointer event - emit_pointer_event(input, make_touch_down_event(timestamp, pos_view, device_id)); + emit_event(input, make_touch_down_event(device, timestamp, device_id, pos_ndc)); // alter our device state - data->positions[slot] = pos_view; + data->positions[slot] = pos_ndc; data->timestamp = timestamp; return 0; @@ -1114,7 +1101,8 @@ static int on_touch_up(struct user_input *input, struct libinput_event *event) { assert(input != NULL); assert(event != NULL); - data = libinput_device_get_user_data(libinput_event_get_device(event)); + struct libinput_device *device = libinput_event_get_device(event); + data = libinput_device_get_user_data(device); touch_event = libinput_event_get_touch_event(event); timestamp = libinput_event_touch_get_time_usec(touch_event); @@ -1125,9 +1113,9 @@ static int on_touch_up(struct user_input *input, struct libinput_event *event) { slot = 0; } - device_id = data->touch_device_id_offset + slot; + device_id = data->touch_slot_id_offset + slot; - emit_pointer_event(input, make_touch_up_event(timestamp, data->positions[slot], device_id)); + emit_event(input, make_touch_up_event(device, timestamp, device_id, data->positions[slot])); return 0; } @@ -1135,7 +1123,6 @@ 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 vec2f pos_view; uint64_t timestamp; int64_t device_id; int slot; @@ -1143,7 +1130,8 @@ static int on_touch_motion(struct user_input *input, struct libinput_event *even assert(input != NULL); assert(event != NULL); - data = libinput_device_get_user_data(libinput_event_get_device(event)); + struct libinput_device *device = libinput_event_get_device(event); + data = libinput_device_get_user_data(device); touch_event = libinput_event_get_touch_event(event); timestamp = libinput_event_touch_get_time_usec(touch_event); @@ -1154,22 +1142,17 @@ static int on_touch_motion(struct user_input *input, struct libinput_event *even slot = 0; } - device_id = data->touch_device_id_offset + slot; + device_id = data->touch_slot_id_offset + slot; // transform the display coordinates to view (flutter) coordinates - pos_view = transform_point( - FLUTTER_TRANSFORM_AS_MAT3F(input->display_to_view_transform), - VEC2F( - libinput_event_touch_get_x_transformed(touch_event, input->display_width), - libinput_event_touch_get_y_transformed(touch_event, input->display_height) - ) - ); + struct vec2f pos_ndc = + VEC2F(libinput_event_touch_get_x_transformed(touch_event, 1.0), libinput_event_touch_get_y_transformed(touch_event, 1.0)); // emit the flutter pointer event - emit_pointer_event(input, make_touch_move_event(timestamp, pos_view, device_id)); + emit_event(input, make_touch_move_event(device, timestamp, device_id, pos_ndc)); // alter our device state - data->positions[slot] = pos_view; + data->positions[slot] = pos_ndc; data->timestamp = timestamp; return 0; @@ -1200,32 +1183,26 @@ static int on_touch_frame(struct user_input *input, struct libinput_event *event static int on_tablet_tool_axis(struct user_input *input, struct libinput_event *event) { struct libinput_event_tablet_tool *tablet_event; struct input_device_data *data; - struct vec2f pos; uint64_t timestamp; int64_t device_id; ASSERT_NOT_NULL(input); ASSERT_NOT_NULL(event); - data = libinput_device_get_user_data(libinput_event_get_device(event)); + struct libinput_device *device = libinput_event_get_device(event); + data = libinput_device_get_user_data(device); ASSERT_NOT_NULL(data); tablet_event = libinput_event_get_tablet_tool_event(event); timestamp = libinput_event_tablet_tool_get_time_usec(tablet_event); - device_id = data->stylus_device_id; - - pos.x = libinput_event_tablet_tool_get_x_transformed(tablet_event, input->display_width - 1); - pos.y = libinput_event_tablet_tool_get_y_transformed(tablet_event, input->display_height - 1); + device_id = data->stylus_slot_id; - pos = transform_point(input->display_to_view_transform, pos); - - if (data->tip) { - emit_pointer_event(input, make_stylus_move_event(timestamp, pos, device_id)); - } else { - emit_pointer_event(input, make_stylus_hover_event(timestamp, pos, device_id)); - } + struct vec2f pos_ndc; + pos_ndc.x = libinput_event_tablet_tool_get_x_transformed(tablet_event, 1); + pos_ndc.y = libinput_event_tablet_tool_get_y_transformed(tablet_event, 1); + emit_event(input, make_stylus_move_event(device, timestamp, device_id, data->tip, pos_ndc)); return 0; } @@ -1239,21 +1216,20 @@ static int on_tablet_tool_proximity(struct user_input *input, struct libinput_ev ASSERT_NOT_NULL(input); ASSERT_NOT_NULL(event); - data = libinput_device_get_user_data(libinput_event_get_device(event)); + struct libinput_device *device = libinput_event_get_device(event); + data = libinput_device_get_user_data(device); ASSERT_NOT_NULL(data); tablet_event = libinput_event_get_tablet_tool_event(event); timestamp = libinput_event_tablet_tool_get_time_usec(tablet_event); - device_id = data->stylus_device_id; - - pos.x = libinput_event_tablet_tool_get_x_transformed(tablet_event, input->display_width - 1); - pos.y = libinput_event_tablet_tool_get_y_transformed(tablet_event, input->display_height - 1); + device_id = data->stylus_slot_id; - pos = transform_point(input->display_to_view_transform, pos); + pos.x = libinput_event_tablet_tool_get_x_transformed(tablet_event, 1); + pos.y = libinput_event_tablet_tool_get_y_transformed(tablet_event, 1); if (!data->tip) { - emit_pointer_event(input, make_stylus_hover_event(timestamp, pos, device_id)); + emit_event(input, make_stylus_move_event(device, timestamp, device_id, data->tip, pos)); } return 0; @@ -1269,25 +1245,24 @@ static int on_tablet_tool_tip(struct user_input *input, struct libinput_event *e ASSERT_NOT_NULL(input); ASSERT_NOT_NULL(event); - data = libinput_device_get_user_data(libinput_event_get_device(event)); + struct libinput_device *device = libinput_event_get_device(event); + data = libinput_device_get_user_data(device); ASSERT_NOT_NULL(data); tablet_event = libinput_event_get_tablet_tool_event(event); timestamp = libinput_event_tablet_tool_get_time_usec(tablet_event); - device_id = data->stylus_device_id; + device_id = data->stylus_slot_id; - pos.x = libinput_event_tablet_tool_get_x_transformed(tablet_event, input->display_width - 1); - pos.y = libinput_event_tablet_tool_get_y_transformed(tablet_event, input->display_height - 1); - - pos = transform_point(input->display_to_view_transform, pos); + pos.x = libinput_event_tablet_tool_get_x_transformed(tablet_event, 1); + pos.y = libinput_event_tablet_tool_get_y_transformed(tablet_event, 1); if (libinput_event_tablet_tool_get_tip_state(tablet_event) == LIBINPUT_TABLET_TOOL_TIP_DOWN) { data->tip = true; - emit_pointer_event(input, make_stylus_down_event(timestamp, pos, device_id)); + emit_event(input, make_stylus_down_event(device, timestamp, device_id, pos)); } else { data->tip = false; - emit_pointer_event(input, make_stylus_up_event(timestamp, pos, device_id)); + emit_event(input, make_stylus_up_event(device, timestamp, device_id, pos)); } return 0; @@ -1356,6 +1331,11 @@ static int process_libinput_events(struct user_input *input, uint64_t timestamp) event = libinput_get_event(input->libinput); event_type = libinput_event_get_type(event); + // We explicitly don't want to handle every event type here. + // Otherwise we'd need to add a new `case` every libinput introduces + // a new event. + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wswitch-enum") switch (event_type) { case LIBINPUT_EVENT_DEVICE_ADDED: ok = on_device_added(input, event, timestamp); @@ -1481,6 +1461,7 @@ static int process_libinput_events(struct user_input *input, uint64_t timestamp) #endif default: break; } + PRAGMA_DIAGNOSTIC_POP libinput_event_destroy(event); } @@ -1493,9 +1474,7 @@ static int process_libinput_events(struct user_input *input, uint64_t timestamp) } int user_input_on_fd_ready(struct user_input *input) { - int cursor_x, cursor_y, cursor_x_before, cursor_y_before; uint64_t timestamp; - bool cursor_enabled, cursor_enabled_before; int ok; assert(input != NULL); @@ -1512,11 +1491,6 @@ int user_input_on_fd_ready(struct user_input *input) { return -ok; } - // record cursor state before handling events - cursor_enabled_before = input->n_cursor_devices > 0; - cursor_x_before = round(input->cursor_x); - cursor_y_before = round(input->cursor_y); - // handle all available libinput events ok = process_libinput_events(input, timestamp); if (ok != 0) { @@ -1524,25 +1498,87 @@ int user_input_on_fd_ready(struct user_input *input) { return ok; } - // record cursor state after handling events - cursor_enabled = input->n_cursor_devices > 0; - cursor_x = round(input->cursor_x); - cursor_y = round(input->cursor_y); - // make sure we've dispatched all the flutter pointer events - flush_pointer_events(input); + flush_events(input); - // call the interface callback if the cursor has been enabled or disabled - if (cursor_enabled && !cursor_enabled_before) { - input->interface.on_set_cursor_enabled(input->userdata, true); - } else if (!cursor_enabled && cursor_enabled_before) { - input->interface.on_set_cursor_enabled(input->userdata, false); + // now we can cleanup all devices that were removed + while (kv_size(input->deferred_device_cleanups) > 0) { + struct input_device_data *data = kv_pop(input->deferred_device_cleanups); + cleanup_device_data(data); } - // only move the pointer if the cursor is enabled now - if (cursor_enabled && ((cursor_x != cursor_x_before) || (cursor_y != cursor_y_before))) { - input->interface.on_move_cursor(input->userdata, VEC2F(cursor_x - cursor_x_before, cursor_y - cursor_y_before)); + return 0; +} + +static void +add_listener(struct user_input *input, bool is_primary, enum user_input_event_type filter, user_input_event_cb_t cb, void *userdata) { + ASSERT_NOT_NULL(input); + ASSERT_NOT_NULL(cb); + + if (input->n_listeners >= ARRAY_SIZE(input->listeners)) { + LOG_ERROR("Could not add input listener, too many listeners.\n"); + return; } - return 0; + if (is_primary) { + for (size_t i = 0; i < input->n_listeners; i++) { + if (input->listeners[i].is_primary) { + LOG_ERROR("Could not add primary input listener, another primary listener was already added.\n"); + return; + } + } + } + + input->listeners[input->n_listeners].filter = filter; + input->listeners[input->n_listeners].is_primary = is_primary; + input->listeners[input->n_listeners].cb = cb; + input->listeners[input->n_listeners].userdata = userdata; + input->n_listeners++; +} + +void user_input_add_primary_listener(struct user_input *input, enum user_input_event_type filter, user_input_event_cb_t cb, void *userdata) { + add_listener(input, true, filter, cb, userdata); +} + +void user_input_add_listener(struct user_input *input, enum user_input_event_type filter, user_input_event_cb_t cb, void *userdata) { + add_listener(input, false, filter, cb, userdata); +} + +void user_input_device_set_primary_listener_userdata(struct user_input_device *device, void *userdata) { + ASSERT_NOT_NULL(device); + + struct input_device_data *data = user_input_device_get_device_data(device); + ASSERT_NOT_NULL(data); + + data->primary_listener_userdata = userdata; +} + +void *user_input_device_get_primary_listener_userdata(struct user_input_device *device) { + ASSERT_NOT_NULL(device); + + struct input_device_data *data = user_input_device_get_device_data(device); + ASSERT_NOT_NULL(data); + + return data->primary_listener_userdata; +} + +int64_t user_input_device_get_id(struct user_input_device *device) { + ASSERT_NOT_NULL(device); + + struct input_device_data *data = user_input_device_get_device_data(device); + return data->device_id; +} + +struct libinput_device *user_input_device_get_libinput_device(struct user_input_device *device) { + ASSERT_NOT_NULL(device); + + struct input_device_data *data = user_input_device_get_device_data(device); + return data->device; +} + +struct udev_device *user_input_device_get_udev_device(struct user_input_device *device) { + ASSERT_NOT_NULL(device); + + struct input_device_data *data = user_input_device_get_device_data(device); + return libinput_device_get_udev_device(data->device); } diff --git a/src/user_input.h b/src/user_input.h index 62a98c1c..9c0deb75 100644 --- a/src/user_input.h +++ b/src/user_input.h @@ -14,18 +14,18 @@ #include #include +#include "libinput.h" #include "plugins/raw_keyboard.h" #include "util/asserts.h" #include "util/collection.h" +#include "util/file_interface.h" #include "util/geometry.h" #define MAX_COLLECTED_FLUTTER_POINTER_EVENTS 64 -typedef void (*flutter_pointer_event_callback_t)(void *userdata, const FlutterPointerEvent *events, size_t n_events); +typedef void (*utf8_character_cb_t)(void *userdata, uint8_t *character); -typedef void (*utf8_character_callback_t)(void *userdata, uint8_t *character); - -typedef void (*xkb_keysym_callback_t)(void *userdata, xkb_keysym_t keysym); +typedef void (*xkb_keysym_cb_t)(void *userdata, xkb_keysym_t keysym); // clang-format off typedef void (*gtk_keyevent_callback_t)( @@ -38,10 +38,6 @@ typedef void (*gtk_keyevent_callback_t)( ); // clang-format on -typedef void (*set_cursor_enabled_callback_t)(void *userdata, bool enabled); - -typedef void (*move_cursor_callback_t)(void *userdata, struct vec2f delta); - // clang-format off typedef void (*keyevent_callback_t)( void *userdata, @@ -56,33 +52,89 @@ typedef void (*keyevent_callback_t)( ); // clang-format on -struct user_input_interface { - flutter_pointer_event_callback_t on_flutter_pointer_event; - utf8_character_callback_t on_utf8_character; - xkb_keysym_callback_t on_xkb_keysym; - gtk_keyevent_callback_t on_gtk_keyevent; - set_cursor_enabled_callback_t on_set_cursor_enabled; - move_cursor_callback_t on_move_cursor; - int (*open)(const char *path, int flags, void *userdata); - void (*close)(int fd, void *userdata); - void (*on_switch_vt)(void *userdata, int vt); - keyevent_callback_t on_key_event; +struct user_input_device; + +void user_input_device_set_primary_listener_userdata(struct user_input_device *device, void *userdata); + +void *user_input_device_get_primary_listener_userdata(struct user_input_device *device); + +int64_t user_input_device_get_id(struct user_input_device *device); + +struct libinput_device *user_input_device_get_libinput_device(struct user_input_device *device); + +struct udev_device *user_input_device_get_udev_device(struct user_input_device *device); + +enum user_input_event_type { + USER_INPUT_DEVICE_ADDED = 1 << 0, + USER_INPUT_DEVICE_REMOVED = 1 << 1, + USER_INPUT_SLOT_ADDED = 1 << 2, + USER_INPUT_SLOT_REMOVED = 1 << 3, + USER_INPUT_POINTER = 1 << 4, + USER_INPUT_TOUCH = 1 << 5, + USER_INPUT_TABLET_TOOL = 1 << 6, + USER_INPUT_KEY = 1 << 7, }; +enum user_input_slot_type { + USER_INPUT_SLOT_POINTER, + USER_INPUT_SLOT_TOUCH, + USER_INPUT_SLOT_TABLET_TOOL, +}; + +struct user_input_event { + enum user_input_event_type type; + uint64_t timestamp; + + struct user_input_device *device; + int64_t global_slot_id; + enum user_input_slot_type slot_type; + + union { + struct { + uint8_t buttons; + uint8_t changed_buttons; + bool is_absolute; + union { + struct vec2f delta; + struct vec2f position_ndc; + }; + struct vec2f scroll_delta; + } pointer; + + struct { + bool down; + bool down_changed; + struct vec2f position_ndc; + } touch; + + struct { + bool tip; + bool tip_changed; + enum libinput_tablet_tool_type tool; + struct vec2f position_ndc; + } tablet; + + struct { + xkb_keycode_t xkb_keycode; + xkb_keysym_t xkb_keysym; + uint32_t plain_codepoint; + key_modifiers_t modifiers; + char text[8]; + bool is_down; + bool is_repeat; + } key; + }; +}; + +typedef void (*user_input_event_cb_t)(void *userdata, size_t n_events, const struct user_input_event *events); + struct user_input; /** * @brief Create a new user input instance. Will try to load the default keyboard config from /etc/default/keyboard * and create a udev-backed libinput instance. */ -struct user_input *user_input_new( - const struct user_input_interface *interface, - void *userdata, - const struct mat3f *display_to_view_transform, - const struct mat3f *view_to_display_transform, - unsigned int display_width, - unsigned int display_height -); +struct user_input *user_input_new_suspended(const struct file_interface *interface, void *userdata, struct udev *udev, const char *seat); /** * @brief Destroy this user input instance and free all allocated memory. This will not remove any input devices @@ -90,21 +142,6 @@ struct user_input *user_input_new( */ void user_input_destroy(struct user_input *input); -/** - * @brief Set a 3x3 matrix and display width / height so user_input can transform any device coordinates into - * proper flutter view coordinates. (For example to account for a rotated display) - * Will also transform absolute & relative mouse movements. - * - * @param display_to_view_transform will be copied internally. - */ -void user_input_set_transform( - struct user_input *input, - const struct mat3f *display_to_view_transform, - const struct mat3f *view_to_display_transform, - unsigned int display_width, - unsigned int display_height -); - /** * @brief Returns a filedescriptor used for input event notification. The returned * filedescriptor should be listened to with EPOLLIN | EPOLLRDHUP | EPOLLPRI or equivalent. @@ -123,4 +160,8 @@ void user_input_suspend(struct user_input *input); int user_input_resume(struct user_input *input); +void user_input_add_listener(struct user_input *input, enum user_input_event_type events, user_input_event_cb_t cb, void *userdata); + +void user_input_add_primary_listener(struct user_input *input, enum user_input_event_type events, user_input_event_cb_t cb, void *userdata); + #endif // _FLUTTERPI_SRC_USER_INPUT_H diff --git a/src/util/asserts.h b/src/util/asserts.h index b2926833..7ca0d312 100644 --- a/src/util/asserts.h +++ b/src/util/asserts.h @@ -18,18 +18,7 @@ #define ASSERT_EQUALS_MSG(__a, __b, __msg) ASSERT_MSG((__a) == (__b), __msg) #define ASSERT_EGL_TRUE(__var) assert((__var) == EGL_TRUE) #define ASSERT_EGL_TRUE_MSG(__var, __msg) ASSERT_MSG((__var) == EGL_TRUE, __msg) -#define ASSERT_MUTEX_LOCKED(__mutex) \ - assert(({ \ - bool result; \ - int r = pthread_mutex_trylock(&(__mutex)); \ - if (r == 0) { \ - pthread_mutex_unlock(&(__mutex)); \ - result = false; \ - } else { \ - result = true; \ - } \ - result; \ - })) + #define ASSERT_ZERO(__var) assert((__var) == 0) #define ASSERT_ZERO_MSG(__var, __msg) ASSERT_MSG((__var) == 0, __msg) diff --git a/src/util/bitset.h b/src/util/bitset.h index d6cfe6ce..27f7f2ad 100644 --- a/src/util/bitset.h +++ b/src/util/bitset.h @@ -38,6 +38,8 @@ * generic bitset implementation */ +#define BIT(x) (1u << (x)) + #define BITSET_WORD unsigned int #define BITSET_WORDBITS (sizeof(BITSET_WORD) * 8) diff --git a/src/util/collection.c b/src/util/collection.c deleted file mode 100644 index 323f4fb2..00000000 --- a/src/util/collection.c +++ /dev/null @@ -1,18 +0,0 @@ -#include "util/collection.h" - -static pthread_mutexattr_t default_mutex_attrs; - -static void init_default_mutex_attrs() { - pthread_mutexattr_init(&default_mutex_attrs); -#ifdef DEBUG - pthread_mutexattr_settype(&default_mutex_attrs, PTHREAD_MUTEX_ERRORCHECK); -#endif -} - -const pthread_mutexattr_t *get_default_mutex_attrs() { - static pthread_once_t init_once_ctl = PTHREAD_ONCE_INIT; - - pthread_once(&init_once_ctl, init_default_mutex_attrs); - - return &default_mutex_attrs; -} diff --git a/src/util/collection.h b/src/util/collection.h index bbc48d23..4079ec01 100644 --- a/src/util/collection.h +++ b/src/util/collection.h @@ -16,10 +16,12 @@ #include #include #include +#include #include #include "macros.h" +#include "asserts.h" static inline void *memdup(const void *restrict src, const size_t n) { void *__restrict__ dest; @@ -108,7 +110,7 @@ static inline void *uint32_to_ptr(const uint32_t v) { #define MAX_ALIGNMENT (__alignof__(max_align_t)) #define IS_MAX_ALIGNED(num) ((num) % MAX_ALIGNMENT == 0) -#define DOUBLE_TO_FP1616(v) ((uint32_t) ((v) *65536)) +#define DOUBLE_TO_FP1616(v) ((uint32_t) ((v) * 65536)) #define DOUBLE_TO_FP1616_ROUNDED(v) (((uint32_t) (v)) << 16) typedef void (*void_callback_t)(void *userdata); @@ -117,6 +119,41 @@ ATTR_PURE static inline bool streq(const char *a, const char *b) { return strcmp(a, b) == 0; } -const pthread_mutexattr_t *get_default_mutex_attrs(); +#define alloca_sprintf(fmt, ...) ({ \ + int necessary = snprintf(NULL, 0, fmt, ##__VA_ARGS__); \ + char *buf = alloca(necessary + 1); \ + snprintf(buf, necessary + 1, fmt, ##__VA_ARGS__); \ + buf; \ +}) + +static inline bool safe_string_to_int(const char *str, int *out) { + char *endptr; + + ASSERT_NOT_NULL(str); + ASSERT_NOT_NULL(out); + + long l = strtol(str, &endptr, 10); + if (endptr == str || *endptr != '\0' || l < INT_MIN || l > INT_MAX) { + return false; + } + + *out = (int) l; + return true; +} + +static inline bool safe_string_to_uint32(const char *str, uint32_t *out) { + char *endptr; + + ASSERT_NOT_NULL(str); + ASSERT_NOT_NULL(out); + + long l = strtol(str, &endptr, 10); + if (endptr == str || *endptr != '\0' || l < 0 || l > UINT32_MAX) { + return false; + } + + *out = (uint32_t) l; + return true; +} #endif // _FLUTTERPI_SRC_UTIL_COLLECTION_H diff --git a/src/event_loop.c b/src/util/event_loop.c similarity index 87% rename from src/event_loop.c rename to src/util/event_loop.c index 70213c84..fa550c7f 100644 --- a/src/event_loop.c +++ b/src/util/event_loop.c @@ -5,19 +5,21 @@ #include #include +#include #include #include #include #include "util/collection.h" +#include "util/lock_ops.h" +#include "util/logging.h" #include "util/refcounting.h" struct evloop { refcount_t n_refs; - pthread_mutex_t mutex; + mutex_t mutex; sd_event *sdloop; int wakeup_fd; - pthread_t owning_thread; }; DEFINE_STATIC_LOCK_OPS(evloop, mutex) @@ -40,7 +42,7 @@ static int on_wakeup_event_loop(sd_event_source *s, int fd, uint32_t revents, vo return 0; } -struct evloop *evloop_new() { +struct evloop *evloop_new(void) { struct evloop *loop; sd_event *sdloop; int ok, wakeup_fd; @@ -69,10 +71,13 @@ struct evloop *evloop_new() { } loop->n_refs = REFCOUNT_INIT_1; - pthread_mutex_init(&loop->mutex, get_default_mutex_attrs()); + + // PTHREAD_MUTEX_RECURSIVE_NP is a hack and was only introduced to + // make old, single-thread oriented code work with multithreading, + // but that's pretty much what we're dealing with here (with sd-event). + mutex_init_recursive(&loop->mutex); loop->sdloop = sdloop; loop->wakeup_fd = wakeup_fd; - loop->owning_thread = pthread_self(); return loop; fail_unref_sdloop: @@ -83,7 +88,7 @@ struct evloop *evloop_new() { return NULL; } -void evloop_destroy(struct evloop *loop) { +static void evloop_destroy(struct evloop *loop) { sd_event_unref(loop->sdloop); close(loop->wakeup_fd); pthread_mutex_destroy(&loop->mutex); @@ -92,9 +97,8 @@ void evloop_destroy(struct evloop *loop) { DEFINE_REF_OPS(evloop, n_refs) -int evloop_get_fd_locked(struct evloop *loop) { +static int evloop_get_fd_locked(struct evloop *loop) { ASSERT_NOT_NULL(loop); - ASSERT_MUTEX_LOCKED(loop->mutex); return sd_event_get_fd(loop->sdloop); } @@ -208,11 +212,10 @@ static int wakeup_sdloop(struct evloop *loop) { return 0; } -int evloop_schedule_exit_locked(struct evloop *loop) { +static int evloop_schedule_exit_locked(struct evloop *loop) { int ok; ASSERT_NOT_NULL(loop); - ASSERT_MUTEX_LOCKED(loop->mutex); ok = sd_event_exit(loop->sdloop, 0); if (ok != 0) { @@ -244,7 +247,6 @@ struct task { static int on_execute_task(sd_event_source *s, void *userdata) { struct task *task; - int ok; ASSERT_NOT_NULL(userdata); task = userdata; @@ -257,14 +259,13 @@ static int on_execute_task(sd_event_source *s, void *userdata) { return 0; } -int evloop_post_task_locked(struct evloop *loop, void_callback_t callback, void *userdata) { +static int evloop_post_task_locked(struct evloop *loop, void_callback_t callback, void *userdata) { sd_event_source *src; struct task *task; int ok; ASSERT_NOT_NULL(loop); ASSERT_NOT_NULL(callback); - ASSERT_MUTEX_LOCKED(loop->mutex); task = malloc(sizeof *task); if (task == NULL) { @@ -283,9 +284,6 @@ int evloop_post_task_locked(struct evloop *loop, void_callback_t callback, void return 0; -fail_remove_src: - sd_event_source_disable_unref(src); - fail_free_task: free(task); return ok; @@ -306,7 +304,7 @@ int evloop_post_task(struct evloop *loop, void_callback_t callback, void *userda return ok; } -static int on_execute_delayed_task(sd_event_source *s, uint64_t usec, void *userdata) { +static int on_run_delayed_task(sd_event_source *s, uint64_t usec, void *userdata) { struct task *task; ASSERT_NOT_NULL(userdata); @@ -320,14 +318,13 @@ static int on_execute_delayed_task(sd_event_source *s, uint64_t usec, void *user return 0; } -int evloop_post_delayed_task_locked(struct evloop *loop, void_callback_t callback, void *userdata, uint64_t target_time_usec) { +static int evloop_post_delayed_task_locked(struct evloop *loop, void_callback_t callback, void *userdata, uint64_t target_time_usec) { sd_event_source *src; struct task *task; int ok; ASSERT_NOT_NULL(loop); ASSERT_NOT_NULL(callback); - ASSERT_MUTEX_LOCKED(loop->mutex); task = malloc(sizeof *task); if (task == NULL) { @@ -337,7 +334,7 @@ int evloop_post_delayed_task_locked(struct evloop *loop, void_callback_t callbac task->callback = callback; task->userdata = userdata; - ok = sd_event_add_time(loop->sdloop, &src, CLOCK_MONOTONIC, target_time_usec, 1, on_execute_delayed_task, task); + ok = sd_event_add_time(loop->sdloop, &src, CLOCK_MONOTONIC, target_time_usec, 1, on_run_delayed_task, task); if (ok < 0) { LOG_ERROR("Error posting platform task to main loop. sd_event_add_time: %s\n", strerror(-ok)); ok = -ok; @@ -346,9 +343,6 @@ int evloop_post_delayed_task_locked(struct evloop *loop, void_callback_t callbac return 0; -fail_remove_src: - sd_event_source_disable_unref(src); - fail_free_task: free(task); return ok; @@ -377,8 +371,7 @@ struct evsrc { void *userdata; }; -void evsrc_destroy_locked(struct evsrc *src) { - ASSERT_MUTEX_LOCKED(src->loop->mutex); +static void evsrc_destroy_locked(struct evsrc *src) { sd_event_source_disable_unref(src->sdsrc); evloop_unref(src->loop); free(src); @@ -393,7 +386,7 @@ void evsrc_destroy(struct evsrc *src) { free(src); } -int on_io_src_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { +int on_io_src_ready(ASSERTED sd_event_source *s, int fd, uint32_t revents, void *userdata) { enum event_handler_return handler_return; struct evsrc *evsrc; @@ -402,7 +395,7 @@ int on_io_src_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata evsrc = userdata; handler_return = evsrc->io_callback(fd, revents, evsrc->userdata); - if (handler_return == kRemoveSrc_EventHandlerReturn) { + if (handler_return == EVENT_HANDLER_CANCEL) { evsrc_destroy_locked(evsrc); return -1; } @@ -410,7 +403,7 @@ int on_io_src_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata return 0; } -struct evsrc *evloop_add_io_locked(struct evloop *loop, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata) { +static struct evsrc *evloop_add_io_locked(struct evloop *loop, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata) { sd_event_source *src; struct evsrc *evsrc; int ok; @@ -421,7 +414,6 @@ struct evsrc *evloop_add_io_locked(struct evloop *loop, int fd, uint32_t events, } ASSERT_NOT_NULL(loop); - ASSERT_MUTEX_LOCKED(loop->mutex); evsrc->io_callback = callback; evsrc->userdata = userdata; @@ -434,7 +426,7 @@ struct evsrc *evloop_add_io_locked(struct evloop *loop, int fd, uint32_t events, } evsrc->loop = evloop_ref(loop); - evsrc->sdsrc = sd_event_source_ref(src); + evsrc->sdsrc = src; return evsrc; } @@ -459,6 +451,7 @@ struct evthread { }; struct evthread_startup_args { + struct evloop *loop; struct evthread *evthread; sem_t initialization_done; bool initialization_success; @@ -467,7 +460,6 @@ struct evthread_startup_args { static void *evthread_entry(void *userdata) { struct evthread *evthread; struct evloop *evloop; - int ok; // initialization. { @@ -480,22 +472,16 @@ static void *evthread_entry(void *userdata) { goto fail_post_semaphore; } - evloop = evloop_new(); - if (evloop == NULL) { - goto fail_free_evthread; - } + evloop = evloop_ref(args->loop); evthread->loop = evloop; evthread->thread = pthread_self(); + args->evthread = evthread; args->initialization_success = true; sem_post(&args->initialization_done); goto init_done; -// error handling -fail_free_evthread: - free(evthread); - fail_post_semaphore: args->initialization_success = false; sem_post(&args->initialization_done); @@ -504,11 +490,10 @@ static void *evthread_entry(void *userdata) { init_done: evloop_run(evloop); - sd_event_unrefp(&evthread->loop); return NULL; } -struct evthread *evthread_start() { +struct evthread *evthread_start_with_loop(struct evloop *loop) { struct evthread_startup_args *args; struct evthread *evthread; pthread_t tid; @@ -519,6 +504,7 @@ struct evthread *evthread_start() { return NULL; } + args->loop = loop; args->evthread = NULL; args->initialization_success = false; @@ -570,12 +556,14 @@ struct evthread *evthread_start() { return NULL; } -struct evloop *evthread_get_evloop(struct evthread *thread) { - return thread->loop; -} - void evthread_stop(struct evthread *thread) { evloop_schedule_exit(thread->loop); pthread_join(thread->thread, NULL); + evloop_unref(thread->loop); free(thread); } + +pthread_t evthread_get_pthread(struct evthread *thread) { + ASSERT_NOT_NULL(thread); + return thread->thread; +} diff --git a/src/util/event_loop.h b/src/util/event_loop.h new file mode 100644 index 00000000..61d0a787 --- /dev/null +++ b/src/util/event_loop.h @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MIT +/* + * Event Loop + * + * - multithreaded event loop. + * - based on sd_event by default, but with locks so tasks can be posted + * and event listeners added from any thread. + * + * Copyright (c) 2023, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_EVENT_LOOP_H +#define _FLUTTERPI_SRC_EVENT_LOOP_H + +#include + +#include + +#include "util/collection.h" +#include "util/refcounting.h" + +/** + * @brief An event loop. + * + */ +struct evloop; + +/** + * @brief Creates a new event loop. + * + * The event loop is not running yet. Any tasks added using @ref evloop_post_task, + * @ref evloop_post_delayed_task or any fd callbacks added using @ref evloop_add_io + * will not be executed yet. + * + * Unless explicitly stated otherwise, all functions in this file are thread-safe + * and can be called from any thread. + * + * @returns A new event loop or NULL on error. + */ +struct evloop *evloop_new(void); + +DECLARE_REF_OPS(evloop) + +/** + * @brief Run the event loop. + * + * This function will actually call the (delayed) task callbacks and fd callbacks, + * when they are ready. + * + * This function will run until exit is scheduled using @ref evloop_schedule_exit. + */ +int evloop_run(struct evloop *loop); + +/** + * @brief Schedule the event loop to exit. + * + */ +int evloop_schedule_exit(struct evloop *loop); + +/** + * @brief Post a task to the event loop to be executed as soon as possible. + * + * @param loop The event loop. + * @param callback The task to execute. + * @param userdata The userdata to pass to the task + */ +int evloop_post_task(struct evloop *loop, void_callback_t callback, void *userdata); + +/** + * @brief Post a task to the event loop to be executed not sooner than target_time_usec. + * + * @param loop The event loop. + * @param callback The task to execute. + * @param userdata The userdata to pass to the task + * @param target_time_usec The time in microseconds (of CLOCK_MONOTONIC) when the task should be executed. + */ +int evloop_post_delayed_task(struct evloop *loop, void_callback_t callback, void *userdata, uint64_t target_time_usec); + +/** + * @brief An event source that was added to the event loop, + * and can be disabled & destroyed using @ref evsrc_destroy. + */ +struct evsrc; + +/** + * @brief Destroy an event source. + * + * After this function returns, the callback registered for the event source + * will not be called anymore. + * + * @param src The event source to destroy. + */ +void evsrc_destroy(struct evsrc *src); + +/** + * @brief The return value of an event handler. + * + */ +enum event_handler_return { + /** + * @brief Continue watching the event source (No change basically) + */ + EVENT_HANDLER_CONTINUE, + + /** + * @brief Stop watching the event source and destroy it. + * + * This can just be used as a shorthand to @ref evsrc_destroy inside an event handler callback. + * + * NOTE: Calling @ref evsrc_destroy inside an fd callback AND returning this value + * is invalid and will result in undefined behavior. + */ + EVENT_HANDLER_CANCEL +}; + +/** + * @brief A callback that is called by the event loop when a file descriptor is ready. + * + * @param fd The file descriptor that is ready. + * @param revents The events that are ready. + * @param userdata The userdata passed to @ref evloop_add_io. + * @returns Whether the event source should be kept or destroyed. + */ +typedef enum event_handler_return (*evloop_io_handler_t)(int fd, uint32_t revents, void *userdata); + +/** + * @brief Watch a file-descriptor and call a callback when it is ready. + * + * The event loop will call the callback on the thread that it executing @ref evloop_run + * when fd is ready to read/write (depending on @ref events). + * + * To stop watching the fd, call @ref evsrc_destroy on the returned evsrc, + * or return @ref EVENT_HANDLER_CANCEL from the callback. + * + * @param loop The event loop. + * @param fd The file descriptor to watch. + * @param events The events to watch for (EPOLLIN, EPOLLOUT, etc). + * @param callback The callback to call when the fd is ready. + * @param userdata The userdata to pass to the callback. + */ +struct evsrc *evloop_add_io(struct evloop *loop, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata); + +struct evthread; + +/** + * @brief Start a new thread just running `evloop_run(loop)`. + */ +struct evthread *evthread_start_with_loop(struct evloop *loop); + +/** + * @brief Get the thread id of the event thread. + */ +pthread_t evthread_get_pthread(struct evthread *thread); + +/** + * @brief Stops the event loop that the thread is running, + * and waits for the event thread to quit. + */ +void evthread_stop(struct evthread *thread); + +#endif // _FLUTTERPI_SRC_EVENT_LOOP_H diff --git a/src/util/file_interface.h b/src/util/file_interface.h new file mode 100644 index 00000000..3d8e24cc --- /dev/null +++ b/src/util/file_interface.h @@ -0,0 +1,12 @@ +#ifndef _FLUTTERPI_SRC_UTIL_FILE_INTERFACE_H +#define _FLUTTERPI_SRC_UTIL_FILE_INTERFACE_H + +/** + * @brief Interface that will be used to open and close files. + */ +struct file_interface { + int (*open)(const char *path, int flags, void **fd_metadata_out, void *userdata); + void (*close)(int fd, void *fd_metadata, void *userdata); +}; + +#endif // _FLUTTERPI_SRC_UTIL_FILE_INTERFACE_H diff --git a/src/util/geometry.h b/src/util/geometry.h index d983d9a8..1cd3ac63 100644 --- a/src/util/geometry.h +++ b/src/util/geometry.h @@ -176,6 +176,19 @@ struct mat3f { (_t).pers2, \ }) +#define MAT3F_IDENTITY() \ + ((struct mat3f){ \ + .scaleX = 1, \ + .skewX = 0, \ + .transX = 0, \ + .skewY = 0, \ + .scaleY = 1, \ + .transY = 0, \ + .pers0 = 0, \ + .pers1 = 0, \ + .pers2 = 1, \ + }) + #define MAT3F_TRANSLATION(translate_x, translate_y) \ ((struct mat3f){ \ .scaleX = 1, \ diff --git a/src/util/khash.h b/src/util/khash.h new file mode 100644 index 00000000..26c71b72 --- /dev/null +++ b/src/util/khash.h @@ -0,0 +1,8 @@ +#ifndef _FLUTTERPI_SRC_UTIL_KHASH_H +#define _FLUTTERPI_SRC_UTIL_KHASH_H + +// NOLINTBEGIN(bugprone-suspicious-realloc-usage) +#include +// NOLINTEND(bugprone-suspicious-realloc-usage) + +#endif // _FLUTTERPI_SRC_UTIL_KHASH_H diff --git a/src/util/khash_uint32.h b/src/util/khash_uint32.h new file mode 100644 index 00000000..096dbef4 --- /dev/null +++ b/src/util/khash_uint32.h @@ -0,0 +1,15 @@ +#ifndef _FLUTTERPI_SRC_UTIL_KHASH_UINT32_H +#define _FLUTTERPI_SRC_UTIL_KHASH_UINT32_H + +#include + +#include "khash.h" + +#define kh_uint32_hash_func(key) (uint32_t)(key) +#define kh_uint32_hash_equal(a, b) ((a) == (b)) + +#define KHASH_SET_INIT_UINT32(name) KHASH_INIT(name, uint32_t, char, 0, kh_uint32_hash_func, kh_uint32_hash_equal) + +#define KHASH_MAP_INIT_UINT32(name, khval_t) KHASH_INIT(name, uint32_t, khval_t, 1, kh_uint32_hash_func, kh_uint32_hash_equal) + +#endif // _FLUTTERPI_SRC_UTIL_KHASH_UINT32_H diff --git a/src/util/lock_ops.c b/src/util/lock_ops.c new file mode 100644 index 00000000..78287c0b --- /dev/null +++ b/src/util/lock_ops.c @@ -0,0 +1,35 @@ +#include + +static pthread_mutexattr_t default_mutex_attrs; + +static void init_default_mutex_attrs(void) { + pthread_mutexattr_init(&default_mutex_attrs); +#ifdef DEBUG + pthread_mutexattr_settype(&default_mutex_attrs, PTHREAD_MUTEX_ERRORCHECK_NP); +#endif +} + +const pthread_mutexattr_t *get_default_mutex_attrs(void) { + static pthread_once_t init_once_ctl = PTHREAD_ONCE_INIT; + + pthread_once(&init_once_ctl, init_default_mutex_attrs); + + return &default_mutex_attrs; +} + +static pthread_mutexattr_t default_recursive_mutex_attrs; + +static void init_default_recursive_mutex_attrs(void) { + pthread_mutexattr_init(&default_recursive_mutex_attrs); + + // PTHREAD_MUTEX_ERRORCHECK_NP does not work with PTHREAD_MUTEX_RECURSIVE_NP. + pthread_mutexattr_settype(&default_recursive_mutex_attrs, PTHREAD_MUTEX_RECURSIVE_NP); +} + +const pthread_mutexattr_t *get_default_recursive_mutex_attrs(void) { + static pthread_once_t init_once_ctl = PTHREAD_ONCE_INIT; + + pthread_once(&init_once_ctl, init_default_recursive_mutex_attrs); + + return &default_recursive_mutex_attrs; +} diff --git a/src/util/lock_ops.h b/src/util/lock_ops.h index dc0a31a0..b465950c 100644 --- a/src/util/lock_ops.h +++ b/src/util/lock_ops.h @@ -9,9 +9,133 @@ #ifndef _FLUTTERPI_SRC_UTIL_LOCK_OPS_H #define _FLUTTERPI_SRC_UTIL_LOCK_OPS_H +#include + #include #include "asserts.h" +#include "macros.h" + +// Code based on the template from: https://clang.llvm.org/docs/ThreadSafetyAnalysis.html + +// Enable thread safety attributes only with clang. +// The attributes can be safely erased when compiling with other compilers. +#if defined(__clang__) && (!defined(SWIG)) + #define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) +#else + #define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op +#endif + +#define CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) + +#define SCOPED_CAPABILITY THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) + +#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) + +#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) + +#define ACQUIRED_BEFORE(...) THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) + +#define ACQUIRED_AFTER(...) THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) + +#define REQUIRES(...) THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) + +#define REQUIRES_SHARED(...) THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) + +#define ACQUIRE(...) THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) + +#define ACQUIRE_SHARED(...) THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) + +#define RELEASE(...) THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) + +#define RELEASE_SHARED(...) THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) + +#define RELEASE_GENERIC(...) THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__)) + +#define TRY_ACQUIRE(...) THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) + +#define TRY_ACQUIRE_SHARED(...) THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) + +#define EXCLUDES(...) THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) + +#define ASSERT_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) + +#define ASSERT_SHARED_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) + +#define RETURN_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) + +#define NO_THREAD_SAFETY_ANALYSIS THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) + +const pthread_mutexattr_t *get_default_mutex_attrs(void); + +const pthread_mutexattr_t *get_default_recursive_mutex_attrs(void); + +typedef pthread_mutex_t mutex_t CAPABILITY("mutex"); + +static inline void mutex_init(mutex_t *restrict mutex) { + ASSERTED int ok = pthread_mutex_init(mutex, get_default_mutex_attrs()); + ASSERT_ZERO_MSG(ok, "Error initializing mutex."); +} + +static inline void mutex_init_recursive(mutex_t *restrict mutex) { + ASSERTED int ok = pthread_mutex_init(mutex, get_default_recursive_mutex_attrs()); + ASSERT_ZERO_MSG(ok, "Error initializing mutex."); +} + +static inline void mutex_fini(mutex_t *restrict mutex) { + ASSERTED int ok = pthread_mutex_destroy(mutex); + ASSERT_ZERO_MSG(ok, "Error destroying mutex."); +} + +static inline void mutex_lock(mutex_t *mutex) ACQUIRE() { + ASSERTED int ok = pthread_mutex_lock(mutex); + ASSERT_ZERO_MSG(ok, "Error locking mutex."); +} + +static inline bool mutex_trylock(mutex_t *mutex) TRY_ACQUIRE(true) { + int ok = pthread_mutex_trylock(mutex); + ASSERT_MSG(ok == 0 || ok == EBUSY, "Error trying to lock mutex."); + return ok == 0; +} + +static inline void mutex_unlock(mutex_t *mutex) RELEASE() { + ASSERTED int ok = pthread_mutex_unlock(mutex); + ASSERT_ZERO_MSG(ok, "Error unlocking mutex."); +} + +typedef pthread_rwlock_t rwlock_t CAPABILITY("mutex"); + +static inline void rwlock_init(rwlock_t *restrict lock) { + ASSERTED int ok = pthread_rwlock_init(lock, NULL); + ASSERT_ZERO_MSG(ok, "Error initializing rwlock."); +} + +static inline void rwlock_lock_read(rwlock_t *rwlock) ACQUIRE_SHARED() { + ASSERTED int ok = pthread_rwlock_rdlock(rwlock); + ASSERT_ZERO_MSG(ok, "Error locking rwlock for reading."); +} + +static inline bool rwlock_try_lock_read(rwlock_t *rwlock) TRY_ACQUIRE_SHARED(true) { + int ok = pthread_rwlock_tryrdlock(rwlock); + ASSERT_MSG(ok == 0 || ok == EBUSY, "Error trying to lock rwlock for reading."); + return ok == 0; +} + +static inline void rwlock_lock_write(rwlock_t *rwlock) ACQUIRE() { + ASSERTED int ok = pthread_rwlock_wrlock(rwlock); + ASSERT_ZERO_MSG(ok, "Error locking rwlock for writing."); +} + +static inline bool rwlock_try_lock_write(rwlock_t *rwlock) TRY_ACQUIRE(true) { + int ok = pthread_rwlock_trywrlock(rwlock); + ASSERT_MSG(ok == 0 || ok == EBUSY, "Error trying to lock rwlock for writing."); + return ok == 0; +} + +static inline void rwlock_unlock(rwlock_t *rwlock) RELEASE() { + ASSERTED int ok = pthread_rwlock_unlock(rwlock); + ASSERT_ZERO_MSG(ok, "Error unlocking rwlock."); +} #define DECLARE_LOCK_OPS(obj_name) \ UNUSED void obj_name##_lock(struct obj_name *obj); \ @@ -59,4 +183,18 @@ (void) ok; \ } +#ifdef DEBUG +static inline void assert_mutex_locked(pthread_mutex_t *mutex) { + int result = pthread_mutex_trylock(mutex); + if (result == 0) { + pthread_mutex_unlock(mutex); + ASSERT_MSG(false, "Mutex is not locked."); + } +} +#else +static inline void assert_mutex_locked(pthread_mutex_t *mutex) { + (void) mutex; +} +#endif + #endif // _FLUTTERPI_SRC_UTIL_LOCK_OPS_H diff --git a/src/util/macros.h b/src/util/macros.h index e0f7933a..bbc093df 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -122,6 +122,9 @@ #if __has_attribute(noreturn) #define HAVE_FUNC_ATTRIBUTE_NORETURN #endif +#if __has_attribute(suppress) + #define HAVE_STMT_ATTRIBUTE_SUPPRESS +#endif /** * __builtin_expect macros @@ -164,6 +167,36 @@ static_assert(cond, #cond); \ } while (0) +#define DO_PRAGMA(X) _Pragma(#X) + +#if defined(__clang__) + #define PRAGMA_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define PRAGMA_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") + #define PRAGMA_DIAGNOSTIC_ERROR(X) DO_PRAGMA(clang diagnostic error X) + #define PRAGMA_DIAGNOSTIC_WARNING(X) DO_PRAGMA(clang diagnostic warning X) + #define PRAGMA_DIAGNOSTIC_IGNORED(X) DO_PRAGMA(clang diagnostic ignored X) + #define PRAGMA_GCC_DIAGNOSTIC_IGNORED(X) + #define PRAGMA_CLANG_DIAGNOSTIC_IGNORED(X) DO_PRAGMA(clang diagnostic ignored X) +#elif defined(__GNUC__) + #define PRAGMA_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define PRAGMA_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") + #define PRAGMA_DIAGNOSTIC_ERROR(X) DO_PRAGMA(GCC diagnostic error X) + #define PRAGMA_DIAGNOSTIC_WARNING(X) DO_PRAGMA(GCC diagnostic warning X) + #define PRAGMA_DIAGNOSTIC_IGNORED(X) DO_PRAGMA(GCC diagnostic ignored X) + #define PRAGMA_GCC_DIAGNOSTIC_IGNORED(X) DO_PRAGMA(GCC diagnostic ignored X) + #define PRAGMA_CLANG_DIAGNOSTIC_IGNORED(X) +#else + #define PRAGMA_DIAGNOSTIC_PUSH + #define PRAGMA_DIAGNOSTIC_POP + #define PRAGMA_DIAGNOSTIC_ERROR(X) + #define PRAGMA_DIAGNOSTIC_WARNING(X) + #define PRAGMA_DIAGNOSTIC_IGNORED(X) + #define PRAGMA_GCC_DIAGNOSTIC_IGNORED(X) + #define PRAGMA_CLANG_DIAGNOSTIC_IGNORED(X) +#endif + +PRAGMA_DIAGNOSTIC_PUSH +PRAGMA_DIAGNOSTIC_IGNORED("-Wpedantic") /** * CONTAINER_OF - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. @@ -186,6 +219,8 @@ }) #endif +PRAGMA_DIAGNOSTIC_POP + /** * Unreachable macro. Useful for suppressing "control reaches end of non-void * function" warnings. @@ -405,6 +440,12 @@ #define ATTR_NOINLINE #endif +#ifdef HAVE_STMT_ATTRIBUTE_SUPPRESS + #define ANALYZER_SUPPRESS(stmt) __attribute__((suppress)) stmt +#else + #define ANALYZER_SUPPRESS(stmt) stmt +#endif + /** * Check that STRUCT::FIELD can hold MAXVAL. We use a lot of bitfields * in Mesa/gallium. We have to be sure they're of sufficient size to @@ -421,7 +462,7 @@ } while (0) /** Compute ceiling of integer quotient of A divided by B. */ -#define DIV_ROUND_UP(A, B) (((A) + (B) -1) / (B)) +#define DIV_ROUND_UP(A, B) (((A) + (B) - 1) / (B)) /** * Clamp X to [MIN,MAX]. Turn NaN into MIN, arbitrarily. @@ -450,10 +491,10 @@ #define MAX4(A, B, C, D) ((A) > (B) ? MAX3(A, C, D) : MAX3(B, C, D)) /** Align a value to a power of two */ -#define ALIGN_POT(x, pot_align) (((x) + (pot_align) -1) & ~((pot_align) -1)) +#define ALIGN_POT(x, pot_align) (((x) + (pot_align) - 1) & ~((pot_align) - 1)) /** Checks is a value is a power of two. Does not handle zero. */ -#define IS_POT(v) (((v) & ((v) -1)) == 0) +#define IS_POT(v) (((v) & ((v) - 1)) == 0) /** Set a single bit */ #define BITFIELD_BIT(b) (1u << (b)) @@ -514,56 +555,6 @@ static inline uint64_t u_uintN_max(unsigned bit_size) { #endif #endif -/* Macros for static type-safety checking. - * - * https://clang.llvm.org/docs/ThreadSafetyAnalysis.html - */ - -#if __has_attribute(capability) -typedef int __attribute__((capability("mutex"))) lock_cap_t; - - #define GUARDED_BY(l) __attribute__((guarded_by(l))) - #define ACQUIRE_CAP(l) __attribute((acquire_capability(l), no_thread_safety_analysis)) - #define RELEASE_CAP(l) __attribute((release_capability(l), no_thread_safety_analysis)) - #define ASSERT_CAP(l) __attribute((assert_capability(l), no_thread_safety_analysis)) - #define REQUIRES_CAP(l) __attribute((requires_capability(l))) - #define DISABLE_THREAD_SAFETY_ANALYSIS __attribute((no_thread_safety_analysis)) - -#else - -typedef int lock_cap_t; - - #define GUARDED_BY(l) - #define ACQUIRE_CAP(l) - #define RELEASE_CAP(l) - #define ASSERT_CAP(l) - #define REQUIRES_CAP(l) - #define DISABLE_THREAD_SAFETY_ANALYSIS - -#endif - -#define DO_PRAGMA(X) _Pragma(#X) - -#if defined(__clang__) - #define PRAGMA_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") - #define PRAGMA_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") - #define PRAGMA_DIAGNOSTIC_ERROR(X) DO_PRAGMA(clang diagnostic error #X) - #define PRAGMA_DIAGNOSTIC_WARNING(X) DO_PRAGMA(clang diagnostic warning #X) - #define PRAGMA_DIAGNOSTIC_IGNORED(X) DO_PRAGMA(clang diagnostic ignored #X) -#elif defined(__GNUC__) - #define PRAGMA_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") - #define PRAGMA_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") - #define PRAGMA_DIAGNOSTIC_ERROR(X) DO_PRAGMA(GCC diagnostic error #X) - #define PRAGMA_DIAGNOSTIC_WARNING(X) DO_PRAGMA(GCC diagnostic warning #X) - #define PRAGMA_DIAGNOSTIC_IGNORED(X) DO_PRAGMA(GCC diagnostic ignored #X) -#else - #define PRAGMA_DIAGNOSTIC_PUSH - #define PRAGMA_DIAGNOSTIC_POP - #define PRAGMA_DIAGNOSTIC_ERROR(X) - #define PRAGMA_DIAGNOSTIC_WARNING(X) - #define PRAGMA_DIAGNOSTIC_IGNORED(X) -#endif - #define PASTE2(a, b) a##b #define PASTE3(a, b, c) a##b##c #define PASTE4(a, b, c, d) a##b##c##d @@ -588,7 +579,7 @@ typedef int lock_cap_t; #define UNIMPLEMENTED() \ do { \ - fprintf(stderr, "%s%s:%u: Unimplemented\n", __FILE__, __func__, __LINE__); \ + fprintf(stderr, "%s%s:%d: Unimplemented\n", __FILE__, __func__, __LINE__); \ TRAP(); \ } while (0) diff --git a/src/util/refcounting.h b/src/util/refcounting.h index 6f35f300..8b8ccdcb 100644 --- a/src/util/refcounting.h +++ b/src/util/refcounting.h @@ -61,54 +61,36 @@ static inline int refcount_get_for_debug(refcount_t *refcount) { UNUSED void obj_name##_swap_ptrs(struct obj_name **objp, struct obj_name *obj); \ UNUSED void obj_name##_unref_void(void *obj); -#define DEFINE_REF_OPS(obj_name, refcount_member_name) \ - UNUSED struct obj_name *obj_name##_ref(struct obj_name *obj) { \ - refcount_inc(&obj->refcount_member_name); \ - return obj; \ - } \ - UNUSED void obj_name##_unref(struct obj_name *obj) { \ - if (refcount_dec(&obj->refcount_member_name) == false) { \ - obj_name##_destroy(obj); \ - } \ - } \ - UNUSED void obj_name##_unrefp(struct obj_name **obj) { \ - obj_name##_unref(*obj); \ - *obj = NULL; \ - } \ - 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; \ - } \ - UNUSED void obj_name##_unref_void(void *obj) { obj_name##_unref((struct obj_name *) obj); } +#define DEFINE_REF_OPS_WITH_QUALIFIERS(qualifiers, obj_name, refcount_member_name) \ + qualifiers struct obj_name *obj_name##_ref(struct obj_name *obj) { \ + refcount_inc(&obj->refcount_member_name); \ + return obj; \ + } \ + qualifiers void obj_name##_unref(struct obj_name *obj) { \ + if (refcount_dec(&obj->refcount_member_name) == false) { \ + obj_name##_destroy(obj); \ + } \ + } \ + qualifiers void obj_name##_unrefp(struct obj_name **obj) { \ + obj_name##_unref(*obj); \ + *obj = NULL; \ + } \ + qualifiers 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; \ + } \ + qualifiers void obj_name##_unref_void(void *obj) { \ + obj_name##_unref((struct obj_name *) obj); \ + } -#define DEFINE_STATIC_REF_OPS(obj_name, refcount_member_name) \ - UNUSED static struct obj_name *obj_name##_ref(struct obj_name *obj) { \ - refcount_inc(&obj->refcount_member_name); \ - return obj; \ - } \ - UNUSED static void obj_name##_unref(struct obj_name *obj) { \ - if (refcount_dec(&obj->refcount_member_name) == false) { \ - obj_name##_destroy(obj); \ - } \ - } \ - UNUSED static void obj_name##_unrefp(struct obj_name **obj) { \ - obj_name##_unref(*obj); \ - *obj = NULL; \ - } \ - 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; \ - } \ - UNUSED static void obj_name##_unref_void(void *obj) { obj_name##_unref((struct obj_name *) obj); } +#define DEFINE_REF_OPS(obj_name, refcount_member_name) DEFINE_REF_OPS_WITH_QUALIFIERS(UNUSED, obj_name, refcount_member_name) +#define DEFINE_STATIC_REF_OPS(obj_name, refcount_member_name) DEFINE_REF_OPS_WITH_QUALIFIERS(UNUSED static, obj_name, refcount_member_name) +#define DEFINE_STATIC_INLINE_REF_OPS(obj_name, refcount_member_name) \ + DEFINE_REF_OPS_WITH_QUALIFIERS(UNUSED static inline, obj_name, refcount_member_name) #endif // _FLUTTERPI_SRC_UTIL_REFCOUNTING_H diff --git a/src/util/uuid.h b/src/util/uuid.h index 160d16d4..db2a679e 100644 --- a/src/util/uuid.h +++ b/src/util/uuid.h @@ -21,9 +21,9 @@ typedef struct { }) #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; diff --git a/src/vk_gbm_render_surface.c b/src/vk_gbm_render_surface.c index e42106e2..65685522 100644 --- a/src/vk_gbm_render_surface.c +++ b/src/vk_gbm_render_surface.c @@ -18,6 +18,8 @@ #include +#include "kms/drmdev.h" +#include "kms/req_builder.h" #include "render_surface.h" #include "render_surface_private.h" #include "surface.h" diff --git a/src/vk_renderer.c b/src/vk_renderer.c index 4a96b810..3c2abe5a 100644 --- a/src/vk_renderer.c +++ b/src/vk_renderer.c @@ -52,7 +52,7 @@ static VkBool32 on_debug_utils_message( UNUSED void *userdata ) { LOG_DEBUG( - "[%s] (%d, %s) %s (queues: %d, cmdbufs: %d, objects: %d)\n", + "[%s] (%d, %s) %s (queues: %u, cmdbufs: %u, objects: %u)\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" : @@ -160,7 +160,7 @@ struct vk_renderer { const char **enabled_device_extensions; }; -MUST_CHECK struct vk_renderer *vk_renderer_new() { +MUST_CHECK struct vk_renderer *vk_renderer_new(void) { PFN_vkDestroyDebugUtilsMessengerEXT destroy_debug_utils_messenger; PFN_vkCreateDebugUtilsMessengerEXT create_debug_utils_messenger; VkDebugUtilsMessengerEXT debug_utils_messenger; diff --git a/src/vk_renderer.h b/src/vk_renderer.h index 6e25b145..6ce8a61a 100644 --- a/src/vk_renderer.h +++ b/src/vk_renderer.h @@ -55,7 +55,7 @@ struct vk_renderer; * * @return New vulkan renderer instance. */ -struct vk_renderer *vk_renderer_new(); +struct vk_renderer *vk_renderer_new(void); void vk_renderer_destroy(struct vk_renderer *renderer); diff --git a/src/vulkan.c b/src/vulkan.c new file mode 100644 index 00000000..0b67b97e --- /dev/null +++ b/src/vulkan.c @@ -0,0 +1,81 @@ +#include "vulkan.h" + +#include "util/macros.h" + +const char *vk_strerror(VkResult result) { + PRAGMA_DIAGNOSTIC_PUSH + + // We'd really like to use PRAGMA_DIAGNOSTIC_WARNING for "-Wswitch-enum" here, + // but CodeChecker makes it hard to distinguish between warnings and errors + // and will always treat this an error. + // So ignore it for now. + PRAGMA_DIAGNOSTIC_IGNORED("-Wswitch-enum") + 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"; +#if VK_HEADER_VERSION >= 131 + case VK_ERROR_UNKNOWN: return "VK_ERROR_UNKNOWN"; +#endif + 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"; +#if VK_HEADER_VERSION >= 131 + case VK_ERROR_FRAGMENTATION: return "VK_ERROR_FRAGMENTATION"; + case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; +#endif +#if VK_HEADER_VERSION >= 204 + case VK_PIPELINE_COMPILE_REQUIRED: return "VK_PIPELINE_COMPILE_REQUIRED_EXT"; +#endif + 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"; +#if VK_HEADER_VERSION >= 218 && VK_ENABLE_BETA_EXTENSIONS + case VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR: return "VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR"; + case VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR"; + case VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR"; + case VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR"; + case VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR"; + case VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR"; +#endif +#if VK_HEADER_VERSION >= 89 + case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT"; +#endif +#if VK_HEADER_VERSION >= 204 + case VK_ERROR_NOT_PERMITTED_KHR: return "VK_ERROR_NOT_PERMITTED_KHR"; +#endif +#if VK_HEADER_VERSION >= 105 + case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; +#endif +#if VK_HEADER_VERSION >= 135 + 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"; +#endif +#if VK_HEADER_VERSION >= 213 + case VK_ERROR_COMPRESSION_EXHAUSTED_EXT: return "VK_ERROR_COMPRESSION_EXHAUSTED_EXT"; +#endif + case VK_RESULT_MAX_ENUM: + default: return ""; + } + PRAGMA_DIAGNOSTIC_POP +} diff --git a/src/vulkan.h b/src/vulkan.h index 3821805a..e665af4c 100644 --- a/src/vulkan.h +++ b/src/vulkan.h @@ -16,75 +16,9 @@ #include -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"; -#if VK_HEADER_VERSION >= 131 - case VK_ERROR_UNKNOWN: return "VK_ERROR_UNKNOWN"; -#endif - 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"; -#if VK_HEADER_VERSION >= 131 - case VK_ERROR_FRAGMENTATION: return "VK_ERROR_FRAGMENTATION"; - case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; -#endif -#if VK_HEADER_VERSION >= 204 - case VK_PIPELINE_COMPILE_REQUIRED: return "VK_PIPELINE_COMPILE_REQUIRED_EXT"; -#endif - 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"; -#if VK_HEADER_VERSION >= 218 && VK_ENABLE_BETA_EXTENSIONS - case VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR: return "VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR"; - case VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR"; - case VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR"; - case VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR"; - case VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR"; - case VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR"; -#endif -#if VK_HEADER_VERSION >= 89 - case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT"; -#endif -#if VK_HEADER_VERSION >= 204 - case VK_ERROR_NOT_PERMITTED_KHR: return "VK_ERROR_NOT_PERMITTED_KHR"; -#endif -#if VK_HEADER_VERSION >= 105 - case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; -#endif -#if VK_HEADER_VERSION >= 135 - 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"; -#endif -#if VK_HEADER_VERSION >= 213 - case VK_ERROR_COMPRESSION_EXHAUSTED_EXT: return "VK_ERROR_COMPRESSION_EXHAUSTED_EXT"; -#endif - default: return ""; - } -} +const char *vk_strerror(VkResult result); -#define LOG_VK_ERROR(result, fmt, ...) LOG_ERROR(fmt ": %s\n", __VA_ARGS__ vk_strerror(result)) +#define LOG_VK_ERROR_FMT(result, fmt, ...) LOG_ERROR(fmt ": %s\n", __VA_ARGS__ vk_strerror(result)) +#define LOG_VK_ERROR(result, str) LOG_ERROR(str ": %s\n", vk_strerror(result)) #endif // _FLUTTERPI_SRC_VULKAN_H diff --git a/src/window.c b/src/window.c index 687a747f..aad1626d 100644 --- a/src/window.c +++ b/src/window.c @@ -21,13 +21,16 @@ #include "cursor.h" #include "flutter-pi.h" #include "frame_scheduler.h" -#include "modesetting.h" +#include "kms/req_builder.h" +#include "kms/resources.h" #include "render_surface.h" #include "surface.h" #include "tracer.h" +#include "user_input.h" #include "util/collection.h" #include "util/logging.h" #include "util/refcounting.h" +#include "window_private.h" #include "config.h" @@ -41,208 +44,6 @@ #include "vk_renderer.h" #endif -struct window { - pthread_mutex_t lock; - refcount_t n_refs; - - /** - * @brief Event tracing interface. - * - * Used to report timing information to the dart observatory. - * - */ - struct tracer *tracer; - - /** - * @brief Manages the frame scheduling for this window. - * - */ - struct frame_scheduler *frame_scheduler; - - /** - * @brief Refresh rate of the selected video mode / display. - * - */ - double refresh_rate; - - /** - * @brief 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; - - /** - * @brief Whether we have physical screen dimensions and @ref width_mm and @ref height_mm contain usable values. - * - */ - bool has_dimensions; - - /** - * @brief Width, height of the display in millimeters. - * - */ - int width_mm, height_mm; - - /** - * @brief The size of the view, as reported to flutter, in pixels. - * - * If no rotation and scaling is applied, this probably equals the display size. - * For example, if flutter-pi should render at 1/2 the resolution of a full-hd display, this would be - * 960x540 and the display size 1920x1080. - */ - struct vec2f view_size; - - /** - * @brief The actual size of the view on the display, pixels. - * - */ - struct vec2f display_size; - - /** - * @brief The rotation we should apply to the flutter layers to present them on screen. - */ - drm_plane_transform_t rotation; - - /** - * @brief 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. Though this can also be anything else theoretically, if the user specifies weird combinations of rotation - * and orientation via cmdline arguments. - * - * @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. - * - */ - struct mat3f 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. - */ - struct mat3f view_to_display_transform; - - /** - * @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 current flutter layer composition that should be output on screen. - * - */ - struct fl_layer_composition *composition; - - /** - * @brief KMS-specific fields if this is a KMS window. - * - */ - struct { - struct drmdev *drmdev; - struct drm_connector *connector; - struct drm_encoder *encoder; - struct drm_crtc *crtc; - drmModeModeInfo *mode; - - bool should_apply_mode; - - const struct pointer_icon *pointer_icon; - struct cursor_buffer *cursor; - } kms; - - /** - * @brief The type of rendering that should be used. (gl, vk) - * - */ - enum renderer_type renderer_type; - - /** - * @brief The OpenGL renderer if OpenGL rendering should be used. - * - */ - struct gl_renderer *gl_renderer; - - /** - * @brief The Vulkan renderer if Vulkan rendering should be used. - * - */ - struct vk_renderer *vk_renderer; - - /** - * @brief Our main render surface, if we have one yet. - * - * Otherwise a new one should be created using the render surface interface. - * - */ - struct render_surface *render_surface; - -#ifdef HAVE_EGL_GLES2 - /** - * @brief The EGLSurface of this window, if any. - * - * Should be EGL_NO_SURFACE if this window is not associated with any EGL surface. - * This is really just a workaround because flutter doesn't support arbitrary EGL surfaces as render targets right now. - * (Just one global EGLSurface) - * - */ - EGLSurface egl_surface; -#endif - - /** - * @brief Whether this window currently shows a mouse cursor. - * - */ - bool cursor_enabled; - - /** - * @brief The position of the mouse cursor. - * - */ - struct vec2i cursor_pos; - - int (*push_composition)(struct window *window, struct fl_layer_composition *composition); - struct render_surface *(*get_render_surface)(struct window *window, struct vec2i size); - -#ifdef HAVE_EGL_GLES2 - bool (*has_egl_surface)(struct window *window); - EGLSurface (*get_egl_surface)(struct window *window); -#endif - - int (*set_cursor_locked)( - struct window *window, - bool has_enabled, - bool enabled, - bool has_kind, - enum pointer_kind kind, - bool has_pos, - struct vec2i pos - ); - void (*deinit)(struct window *window); -}; - void window_destroy(struct window *window); DEFINE_STATIC_LOCK_OPS(window, lock) @@ -284,9 +85,7 @@ static void fill_view_matrices( } } -static void window_deinit(struct window *window); - -static int window_init( +int window_init( // clang-format off struct window *window, struct tracer *tracer, @@ -295,12 +94,15 @@ static int window_init( bool has_orientation, enum device_orientation orientation, int width, int height, bool has_dimensions, int width_mm, int height_mm, - double refresh_rate, - bool has_forced_pixel_format, enum pixfmt forced_pixel_format + float refresh_rate, + bool has_forced_pixel_format, enum pixfmt forced_pixel_format, + enum renderer_type renderer_type, + struct gl_renderer *gl_renderer, + struct vk_renderer *vk_renderer // clang-format on ) { enum device_orientation original_orientation; - double pixel_ratio; + float pixel_ratio; ASSERT_NOT_NULL(window); ASSERT_NOT_NULL(tracer); @@ -309,6 +111,22 @@ static int window_init( assert(!has_orientation || ORIENTATION_IS_VALID(orientation)); assert(!has_dimensions || (width_mm > 0 && height_mm > 0)); +#if !defined(HAVE_VULKAN) + ASSUME(renderer_type != kVulkan_RendererType); +#endif + +#if !defined(HAVE_EGL_GLES2) + ASSUME(renderer_type != kOpenGL_RendererType); +#endif + + // if opengl --> gl_renderer != NULL && vk_renderer == NULL + assert(renderer_type != kOpenGL_RendererType || (gl_renderer != NULL && vk_renderer == NULL)); + + // if vulkan --> vk_renderer != NULL && gl_renderer == NULL + assert(renderer_type != kVulkan_RendererType || (vk_renderer != NULL && gl_renderer == NULL)); + + memset(window, 0, sizeof *window); + if (has_dimensions == false) { LOG_DEBUG( "WARNING: display didn't provide valid physical dimensions. The device-pixel ratio will default " @@ -317,7 +135,7 @@ static int window_init( ); pixel_ratio = 1.0; } else { - pixel_ratio = (10.0 * width) / (width_mm * 38.0); + pixel_ratio = (10.0f * width) / (width_mm * 38.0f); int horizontal_dpi = (int) (width / (width_mm / 25.4)); int vertical_dpi = (int) (height / (height_mm / 25.4)); @@ -406,19 +224,28 @@ static int window_init( window->vk_renderer = NULL; window->render_surface = NULL; window->cursor_enabled = false; - window->cursor_pos = VEC2I(0, 0); - window->push_composition = NULL; - window->get_render_surface = NULL; + window->cursor_pos = VEC2F(0, 0); + if (gl_renderer != NULL) { #ifdef HAVE_EGL_GLES2 - window->has_egl_surface = NULL; - window->get_egl_surface = NULL; + window->gl_renderer = gl_renderer_ref(gl_renderer); +#else + UNREACHABLE(); +#endif + } + if (vk_renderer != NULL) { +#ifdef HAVE_VULKAN + window->vk_renderer = vk_renderer_ref(vk_renderer); +#else + UNREACHABLE(); #endif - window->set_cursor_locked = NULL; - window->deinit = window_deinit; + } else { + window->vk_renderer = NULL; + } + window->ops.deinit = window_deinit; return 0; } -static void window_deinit(struct window *window) { +void window_deinit(struct window *window) { // It's possible we're destroying the window before any frame was presented. if (window->composition != NULL) { fl_layer_composition_unref(window->composition); @@ -431,17 +258,17 @@ static void window_deinit(struct window *window) { void window_destroy(struct window *window) { ASSERT_NOT_NULL(window); - ASSERT_NOT_NULL(window->deinit); + ASSERT_NOT_NULL(window->ops.deinit); - window->deinit(window); + window->ops.deinit(window); free(window); } int window_push_composition(struct window *window, struct fl_layer_composition *composition) { ASSERT_NOT_NULL(window); ASSERT_NOT_NULL(composition); - ASSERT_NOT_NULL(window->push_composition); - return window->push_composition(window, composition); + ASSERT_NOT_NULL(window->ops.push_composition); + return window->ops.push_composition(window, composition); } struct view_geometry window_get_view_geometry(struct window *window) { @@ -460,7 +287,7 @@ struct view_geometry window_get_view_geometry(struct window *window) { return geometry; } -double window_get_refresh_rate(struct window *window) { +float window_get_refresh_rate(struct window *window) { ASSERT_NOT_NULL(window); return window->refresh_rate; @@ -480,20 +307,24 @@ int window_get_next_vblank(struct window *window, uint64_t *next_vblank_ns_out) #ifdef HAVE_EGL_GLES2 bool window_has_egl_surface(struct window *window) { - return window->has_egl_surface(window); + ASSERT_NOT_NULL(window); + ASSERT_NOT_NULL(window->ops.has_egl_surface); + return window->ops.has_egl_surface(window); } EGLSurface window_get_egl_surface(struct window *window) { ASSERT_NOT_NULL(window); - ASSERT_NOT_NULL(window->get_egl_surface); - return window->get_egl_surface(window); + ASSERT_NOT_NULL(window->ops.get_egl_surface); + return window->ops.get_egl_surface(window); } #endif +/// TODO: Once we enable the backing store cache, we can actually sanely manage lifetimes and +/// rename this to window_create_render_surface. struct render_surface *window_get_render_surface(struct window *window, struct vec2i size) { ASSERT_NOT_NULL(window); - ASSERT_NOT_NULL(window->get_render_surface); - return window->get_render_surface(window, size); + ASSERT_NOT_NULL(window->ops.get_render_surface); + return window->ops.get_render_surface(window, size); } bool window_is_cursor_enabled(struct window *window) { @@ -519,1287 +350,173 @@ int window_set_cursor( int ok; ASSERT_NOT_NULL(window); + ASSERT_NOT_NULL(window->ops.set_cursor_locked); window_lock(window); - ok = window->set_cursor_locked(window, has_enabled, enabled, has_kind, kind, has_pos, pos); + ok = window->ops.set_cursor_locked(window, has_enabled, enabled, has_kind, kind, has_pos, pos); window_unlock(window); return ok; } -struct cursor_buffer { - refcount_t n_refs; - - const struct pointer_icon *icon; - enum pixfmt format; - int width, height; - drm_plane_transform_t rotation; - - struct drmdev *drmdev; - int drm_fb_id; - struct gbm_bo *bo; - - struct vec2i hotspot; -}; - -static struct vec2i get_rotated_hotspot(const struct pointer_icon *icon, drm_plane_transform_t rotation) { - struct vec2i size; - struct vec2i hotspot; - - assert(PLANE_TRANSFORM_IS_ONLY_ROTATION(rotation)); - size = pointer_icon_get_size(icon); - hotspot = pointer_icon_get_hotspot(icon); - - if (rotation.rotate_0) { - return hotspot; - } else if (rotation.rotate_90) { - return VEC2I(size.y - hotspot.y - 1, hotspot.x); - } else if (rotation.rotate_180) { - return VEC2I(size.x - hotspot.x - 1, size.y - hotspot.y - 1); - } else { - ASSUME(rotation.rotate_270); - return VEC2I(hotspot.y, size.x - hotspot.x - 1); - } -} - -static struct cursor_buffer *cursor_buffer_new(struct drmdev *drmdev, const struct pointer_icon *icon, drm_plane_transform_t rotation) { - struct cursor_buffer *b; - struct gbm_bo *bo; - uint32_t fb_id; - struct vec2i size, rotated_size; - int ok; - - ASSERT_NOT_NULL(drmdev); - assert(PLANE_TRANSFORM_IS_ONLY_ROTATION(rotation)); - - size = pointer_icon_get_size(icon); - rotated_size = size; - - b = malloc(sizeof *b); - if (b == NULL) { - return NULL; - } - - if (rotation.rotate_90 || rotation.rotate_270) { - rotated_size = vec2i_swap_xy(size); - } - - bo = gbm_bo_create( - drmdev_get_gbm_device(drmdev), - rotated_size.x, - rotated_size.y, - get_pixfmt_info(PIXFMT_ARGB8888)->gbm_format, - GBM_BO_USE_LINEAR | GBM_BO_USE_SCANOUT | GBM_BO_USE_WRITE | GBM_BO_USE_CURSOR - ); - if (bo == NULL) { - LOG_ERROR("Could not create GBM buffer for uploading mouse cursor icon. gbm_bo_create: %s\n", strerror(errno)); - goto fail_free_b; - } - - if (gbm_bo_get_stride(bo) != rotated_size.x * 4) { - LOG_ERROR("GBM BO has unsupported framebuffer stride %u, expected was: %d\n", gbm_bo_get_stride(bo), size.x * 4); - goto fail_destroy_bo; - } - - uint32_t *pixel_data = pointer_icon_dup_pixels(icon); - if (pixel_data == NULL) { - goto fail_destroy_bo; - } - - if (rotation.rotate_0) { - ok = gbm_bo_write(bo, pixel_data, gbm_bo_get_stride(bo) * size.y); - if (ok != 0) { - LOG_ERROR("Couldn't write cursor icon to GBM BO. gbm_bo_write: %s\n", strerror(errno)); - goto fail_free_duped_pixel_data; - } - } else { - ASSUME(rotation.rotate_90 || rotation.rotate_180 || rotation.rotate_270); - - uint32_t *rotated = malloc(size.x * size.y * 4); - if (rotated == NULL) { - goto fail_free_duped_pixel_data; - } - - for (int y = 0; y < size.y; y++) { - for (int x = 0; x < size.x; x++) { - int buffer_x, buffer_y; - if (rotation.rotate_90) { - buffer_x = size.y - y - 1; - buffer_y = x; - } else if (rotation.rotate_180) { - buffer_x = size.y - y - 1; - buffer_y = size.x - x - 1; - } else { - ASSUME(rotation.rotate_270); - buffer_x = y; - buffer_y = size.x - x - 1; - } - - int buffer_offset = rotated_size.x * buffer_y + buffer_x; - int cursor_offset = size.x * y + x; - - rotated[buffer_offset] = pixel_data[cursor_offset]; - } - } - - ok = gbm_bo_write(bo, rotated, gbm_bo_get_stride(bo) * rotated_size.y); - - free(rotated); - - if (ok != 0) { - LOG_ERROR("Couldn't write rotated cursor icon to GBM BO. gbm_bo_write: %s\n", strerror(errno)); - goto fail_free_duped_pixel_data; - } - } - - free(pixel_data); - - fb_id = drmdev_add_fb( - drmdev, - rotated_size.x, - rotated_size.y, - PIXFMT_ARGB8888, - gbm_bo_get_handle(bo).u32, - gbm_bo_get_stride(bo), - gbm_bo_get_offset(bo, 0), - gbm_bo_get_modifier(bo) != DRM_FORMAT_MOD_INVALID, - gbm_bo_get_modifier(bo) - ); - if (fb_id == 0) { - goto fail_destroy_bo; - } - - b->n_refs = REFCOUNT_INIT_1; - b->icon = icon; - b->format = PIXFMT_ARGB8888; - b->width = rotated_size.x; - b->height = rotated_size.y; - b->rotation = rotation; - b->drmdev = drmdev_ref(drmdev); - b->drm_fb_id = fb_id; - b->bo = bo; - b->hotspot = get_rotated_hotspot(icon, rotation); - return b; - -fail_free_duped_pixel_data: - free(pixel_data); - -fail_destroy_bo: - gbm_bo_destroy(bo); - -fail_free_b: - free(b); - return NULL; -} - -static void cursor_buffer_destroy(struct cursor_buffer *buffer) { - drmdev_rm_fb(buffer->drmdev, buffer->drm_fb_id); - gbm_bo_destroy(buffer->bo); - drmdev_unref(buffer->drmdev); - free(buffer); -} - -static void cursor_buffer_destroy_with_locked_drmdev(struct cursor_buffer *buffer) { - drmdev_rm_fb_locked(buffer->drmdev, buffer->drm_fb_id); - gbm_bo_destroy(buffer->bo); - drmdev_unref(buffer->drmdev); - free(buffer); -} - -DEFINE_STATIC_REF_OPS(cursor_buffer, n_refs) +struct vec2f window_transform_ndc_to_view(struct window *window, struct vec2f ndc) { + ASSERT_NOT_NULL(window); -static void cursor_buffer_unref_with_locked_drmdev(void *userdata) { - struct cursor_buffer *cursor; + struct vec2f view_pos; - ASSERT_NOT_NULL(userdata); - cursor = userdata; + window_lock(window); + view_pos = transform_point(window->ndc_to_view_transform, ndc); + window_unlock(window); - if (refcount_dec(&cursor->n_refs) == false) { - cursor_buffer_destroy_with_locked_drmdev(cursor); - } + return view_pos; } -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, - const char *desired_videomode +void window_on_input( + struct window *window, + size_t n_events, + const struct user_input_event *events, + const struct fl_pointer_event_interface *pointer_event_interface, + int64_t view_id ) { - struct drm_connector *connector; - struct drm_encoder *encoder; - struct drm_crtc *crtc; - drmModeModeInfo *mode, *mode_iter; - int ok; - - // 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; - } + (void) window; + (void) n_events; + (void) events; - mode = NULL; - if (desired_videomode != NULL) { - for_each_mode_in_connector(connector, mode_iter) { - char *modeline = NULL, *modeline_nohz = NULL; + size_t n_fl_events = 0; + FlutterPointerEvent fl_events[64]; - ok = asprintf(&modeline, "%" PRIu16 "x%" PRIu16 "@%" PRIu32, mode_iter->hdisplay, mode_iter->vdisplay, mode_iter->vrefresh); - if (ok < 0) { - return ENOMEM; - } + for (size_t i = 0; i < n_events; i++) { + const struct user_input_event *event = events + i; - ok = asprintf(&modeline_nohz, "%" PRIu16 "x%" PRIu16, mode_iter->hdisplay, mode_iter->vdisplay); - if (ok < 0) { - return ENOMEM; + if (n_fl_events >= ARRAY_SIZE(fl_events)) { + if (pointer_event_interface != NULL) { + pointer_event_interface->send_pointer_event(pointer_event_interface->engine, fl_events, n_fl_events); } + n_fl_events = 0; + } - if (streq(modeline, desired_videomode)) { - // Probably a bit superfluos, but the refresh rate can still vary in the decimal places. - if (mode == NULL || (mode_get_vrefresh(mode_iter) > mode_get_vrefresh(mode))) { - mode = mode_iter; - } - } else if (streq(modeline_nohz, desired_videomode)) { - if (mode == NULL || (mode_get_vrefresh(mode_iter) > mode_get_vrefresh(mode))) { - mode = mode_iter; + FlutterPointerEvent *fl_event = fl_events + n_fl_events; + + memset(fl_event, 0, sizeof *fl_event); + fl_event->struct_size = sizeof *fl_event; + fl_event->phase = kCancel; + fl_event->timestamp = event->timestamp; + fl_event->x = 0.0; + fl_event->y = 0.0; + fl_event->device = event->global_slot_id; + fl_event->signal_kind = kFlutterPointerSignalKindNone; + fl_event->scroll_delta_x = 0.0; + fl_event->scroll_delta_y = 0.0; + fl_event->device_kind = kFlutterPointerDeviceKindTouch; + fl_event->buttons = 0; + fl_event->pan_x = 0.0; + fl_event->pan_y = 0.0; + fl_event->scale = 0.0; + fl_event->rotation = 0.0; + fl_event->view_id = view_id; + switch (event->type) { + case USER_INPUT_DEVICE_ADDED: + case USER_INPUT_DEVICE_REMOVED: goto skip; + case USER_INPUT_SLOT_ADDED: + case USER_INPUT_SLOT_REMOVED: { + fl_event->phase = event->type == USER_INPUT_SLOT_ADDED ? kAdd : kRemove; + fl_event->device = event->global_slot_id; + if (event->slot_type == USER_INPUT_SLOT_POINTER) { + fl_event->device_kind = kFlutterPointerDeviceKindMouse; + } else if (event->slot_type == USER_INPUT_SLOT_TOUCH) { + fl_event->device_kind = kFlutterPointerDeviceKindTouch; + } else if (event->slot_type == USER_INPUT_SLOT_TABLET_TOOL) { + fl_event->device_kind = kFlutterPointerDeviceKindStylus; } + break; } + case USER_INPUT_POINTER: { + mutex_lock(&window->lock); - free(modeline); - free(modeline_nohz); - } - - if (mode == NULL) { - LOG_ERROR("Didn't find a videomode matching \"%s\"! Falling back to display preferred mode.\n", desired_videomode); - } - } - - // Find the preferred mode (GPU drivers _should_ always supply a preferred mode, but of course, they don't) - // Alternatively, find the mode with the highest width*height. If there are multiple modes with the same w*h, - // prefer higher refresh rates. After that, prefer progressive scanout modes. - if (mode == NULL) { - for_each_mode_in_connector(connector, mode_iter) { - if (mode_iter->type & DRM_MODE_TYPE_PREFERRED) { - mode = mode_iter; - break; - } else if (mode == NULL) { - mode = mode_iter; - } else { - int area = mode_iter->hdisplay * mode_iter->vdisplay; - int old_area = mode->hdisplay * mode->vdisplay; - - if ((area > old_area) || ((area == old_area) && (mode_iter->vrefresh > mode->vrefresh)) || - ((area == old_area) && (mode_iter->vrefresh == mode->vrefresh) && ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0))) { - mode = mode_iter; + if (event->pointer.is_absolute) { + window->cursor_pos.x = CLAMP(event->pointer.position_ndc.x * window->view_size.x, 0.0, window->view_size.x); + window->cursor_pos.y = CLAMP(event->pointer.position_ndc.y * window->view_size.y, 0.0, window->view_size.y); + } else { + window->cursor_pos.x = CLAMP(window->cursor_pos.x + event->pointer.delta.x, 0.0, window->view_size.x); + window->cursor_pos.y = CLAMP(window->cursor_pos.y + event->pointer.delta.y, 0.0, window->view_size.y); } - } - } - if (mode == NULL) { - LOG_ERROR("Could not find a preferred output mode!\n"); - return EINVAL; - } - } + fl_event->x = window->cursor_pos.x; + fl_event->y = window->cursor_pos.y; - ASSERT_NOT_NULL(mode); + mutex_unlock(&window->lock); - // 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; - } - } + if (event->pointer.changed_buttons & kFlutterPointerButtonMousePrimary) { + if (event->pointer.buttons & kFlutterPointerButtonMousePrimary) { + fl_event->phase = kDown; + } else { + fl_event->phase = kUp; + } + } else { + if (event->pointer.buttons & kFlutterPointerButtonMousePrimary) { + fl_event->phase = kMove; + } else { + fl_event->phase = kHover; + } + } - // 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 (event->pointer.scroll_delta.x != 0.0 || event->pointer.scroll_delta.y != 0.0) { + fl_event->signal_kind = kFlutterPointerSignalKindScroll; + fl_event->scroll_delta_x = event->pointer.scroll_delta.x; + fl_event->scroll_delta_y = event->pointer.scroll_delta.y; } - } - if (encoder->encoder->possible_crtcs) { - // only use this encoder if there's a crtc we can use with it + fl_event->device_kind = kFlutterPointerDeviceKindMouse; + fl_event->buttons = event->pointer.buttons; 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; - } - } + case USER_INPUT_TOUCH: { + if (event->touch.down_changed) { + fl_event->phase = event->touch.down ? kDown : kUp; + } else { + fl_event->phase = kMove; + } - // 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 + fl_event->device_kind = kFlutterPointerDeviceKindTouch; + fl_event->x = event->touch.position_ndc.x * window->view_size.x; + fl_event->y = event->touch.position_ndc.y * window->view_size.y; 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 kms_window_push_composition(struct window *window, struct fl_layer_composition *composition); -static struct render_surface *kms_window_get_render_surface(struct window *window, struct vec2i size); - -#ifdef HAVE_EGL_GLES2 -static bool kms_window_has_egl_surface(struct window *window); -static EGLSurface kms_window_get_egl_surface(struct window *window); -#endif - -static void kms_window_deinit(struct window *window); -static int kms_window_set_cursor_locked( - // clang-format off - struct window *window, - bool has_enabled, bool enabled, - bool has_kind, enum pointer_kind kind, - bool has_pos, struct vec2i pos - // clang-format on -); - -MUST_CHECK struct window *kms_window_new( - // clang-format off - struct tracer *tracer, - struct frame_scheduler *scheduler, - enum renderer_type renderer_type, - struct gl_renderer *gl_renderer, - struct vk_renderer *vk_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, - struct drmdev *drmdev, - const char *desired_videomode - // clang-format on -) { - struct window *window; - struct drm_connector *selected_connector; - struct drm_encoder *selected_encoder; - struct drm_crtc *selected_crtc; - drmModeModeInfo *selected_mode; - bool has_dimensions; - int ok; - - ASSERT_NOT_NULL(drmdev); - -#if !defined(HAVE_VULKAN) - ASSUME(renderer_type != kVulkan_RendererType); -#endif - -#if !defined(HAVE_EGL_GLES2) - ASSUME(renderer_type != kOpenGL_RendererType); -#endif - - // if opengl --> gl_renderer != NULL && vk_renderer == NULL - assert(renderer_type != kOpenGL_RendererType || (gl_renderer != NULL && vk_renderer == NULL)); - - // if vulkan --> vk_renderer != NULL && gl_renderer == NULL - assert(renderer_type != kVulkan_RendererType || (vk_renderer != NULL && gl_renderer == NULL)); - - window = malloc(sizeof *window); - if (window == NULL) { - return NULL; - } - - ok = select_mode(drmdev, &selected_connector, &selected_encoder, &selected_crtc, &selected_mode, desired_videomode); - if (ok != 0) { - goto fail_free_window; - } - - if (has_explicit_dimensions) { - has_dimensions = true; - } else if (selected_connector->variable_state.width_mm % 10 || selected_connector->variable_state.height_mm % 10) { - // as a heuristic, assume the physical dimensions are valid if they're not both multiples of 10. - // dimensions like 160x90mm, 150x100mm are often bogus. - 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) { - // assume this is the official Raspberry Pi DSI display. - has_dimensions = true; - width_mm = 155; - height_mm = 86; - } else { - has_dimensions = false; - } - - ok = window_init( - // clang-format off - window, - tracer, - scheduler, - has_rotation, rotation, - has_orientation, orientation, - selected_mode->hdisplay, selected_mode->vdisplay, - has_dimensions, width_mm, height_mm, - mode_get_vrefresh(selected_mode), - has_forced_pixel_format, forced_pixel_format - // clang-format on - ); - if (ok != 0) { - free(window); - return NULL; - } - - LOG_DEBUG_UNPREFIXED( - "display mode:\n" - " resolution: %" PRIu16 " x %" PRIu16 - "\n" - " refresh rate: %fHz\n" - " physical size: %dmm x %dmm\n" - " flutter device pixel ratio: %f\n" - " pixel format: %s\n", - selected_mode->hdisplay, - selected_mode->vdisplay, - mode_get_vrefresh(selected_mode), - width_mm, - height_mm, - window->pixel_ratio, - has_forced_pixel_format ? get_pixfmt_info(forced_pixel_format)->name : "(any)" - ); - - window->kms.drmdev = drmdev_ref(drmdev); - window->kms.connector = selected_connector; - window->kms.encoder = selected_encoder; - window->kms.crtc = selected_crtc; - window->kms.mode = selected_mode; - window->kms.should_apply_mode = true; - window->kms.cursor = NULL; - window->kms.pointer_icon = NULL; - window->renderer_type = renderer_type; - if (gl_renderer != NULL) { -#ifdef HAVE_EGL_GLES2 - window->gl_renderer = gl_renderer_ref(gl_renderer); -#else - UNREACHABLE(); -#endif - } - if (vk_renderer != NULL) { -#ifdef HAVE_VULKAN - window->vk_renderer = vk_renderer_ref(vk_renderer); -#else - UNREACHABLE(); -#endif - } else { - window->vk_renderer = NULL; - } - window->push_composition = kms_window_push_composition; - window->get_render_surface = kms_window_get_render_surface; -#ifdef HAVE_EGL_GLES2 - window->has_egl_surface = kms_window_has_egl_surface; - window->get_egl_surface = kms_window_get_egl_surface; -#endif - window->deinit = kms_window_deinit; - window->set_cursor_locked = kms_window_set_cursor_locked; - return window; - -fail_free_window: - free(window); - return NULL; -} - -void kms_window_deinit(struct window *window) { - /// TODO: Do we really need to do this? - /* - struct kms_req_builder *builder; - struct kms_req *req; - int ok; - - builder = drmdev_create_request_builder(window->kms.drmdev, window->kms.crtc->id); - ASSERT_NOT_NULL(builder); - - ok = kms_req_builder_unset_mode(builder); - ASSERT_EQUALS(ok, 0); - - req = kms_req_builder_build(builder); - ASSERT_NOT_NULL(req); - - kms_req_builder_unref(builder); - - ok = kms_req_commit_blocking(req, NULL); - ASSERT_EQUALS(ok, 0); - (void) ok; - - kms_req_unref(req); - */ - - if (window->kms.cursor != NULL) { - cursor_buffer_unref(window->kms.cursor); - } - if (window->render_surface != NULL) { - surface_unref(CAST_SURFACE(window->render_surface)); - } - if (window->gl_renderer != NULL) { -#ifdef HAVE_EGL_GLES2 - gl_renderer_unref(window->gl_renderer); -#else - UNREACHABLE(); -#endif - } - if (window->vk_renderer != NULL) { -#ifdef HAVE_VULKAN - vk_renderer_unref(window->vk_renderer); -#else - UNREACHABLE(); -#endif - } - drmdev_unref(window->kms.drmdev); - window_deinit(window); -} - -struct frame { - struct tracer *tracer; - struct kms_req *req; - bool unset_should_apply_mode_on_commit; -}; - -UNUSED static void on_scanout(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata) { - ASSERT_NOT_NULL(drmdev); - (void) drmdev; - (void) vblank_ns; - (void) userdata; - - /// TODO: What should we do here? -} - -static void on_present_frame(void *userdata) { - struct frame *frame; - int ok; - - ASSERT_NOT_NULL(userdata); - - frame = userdata; - - TRACER_BEGIN(frame->tracer, "kms_req_commit_nonblocking"); - ok = kms_req_commit_blocking(frame->req, NULL); - TRACER_END(frame->tracer, "kms_req_commit_nonblocking"); - - if (ok != 0) { - LOG_ERROR("Could not commit frame request.\n"); - } - - tracer_unref(frame->tracer); - kms_req_unref(frame->req); - free(frame); -} - -static void on_cancel_frame(void *userdata) { - struct frame *frame; - ASSERT_NOT_NULL(userdata); - - frame = userdata; - - tracer_unref(frame->tracer); - kms_req_unref(frame->req); - free(frame); -} - -static int kms_window_push_composition_locked(struct window *window, struct fl_layer_composition *composition) { - struct kms_req_builder *builder; - struct kms_req *req; - struct frame *frame; - int ok; - - ASSERT_NOT_NULL(window); - 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; - // } - // } - - /// TODO: If we don't have new revisions, we don't need to scanout anything. - fl_layer_composition_swap_ptrs(&window->composition, composition); - - builder = drmdev_create_request_builder(window->kms.drmdev, window->kms.crtc->id); - if (builder == NULL) { - ok = ENOMEM; - goto fail_unref_builder; - } - - // We only set the mode once, at the first atomic request. - if (window->kms.should_apply_mode) { - ok = kms_req_builder_set_connector(builder, window->kms.connector->id); - if (ok != 0) { - LOG_ERROR("Couldn't select connector.\n"); - goto fail_unref_builder; - } - - ok = kms_req_builder_set_mode(builder, window->kms.mode); - if (ok != 0) { - LOG_ERROR("Couldn't apply output mode.\n"); - goto fail_unref_builder; - } - } - - 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); - - 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_unref_builder; - } - } - - // add cursor infos - if (window->kms.cursor != NULL) { - ok = kms_req_builder_push_fb_layer( - builder, - &(const struct kms_fb_layer){ - .drm_fb_id = window->kms.cursor->drm_fb_id, - .format = window->kms.cursor->format, - .has_modifier = true, - .modifier = DRM_FORMAT_MOD_LINEAR, - .src_x = 0, - .src_y = 0, - .src_w = ((uint16_t) window->kms.cursor->width) << 16, - .src_h = ((uint16_t) window->kms.cursor->height) << 16, - .dst_x = window->cursor_pos.x - window->kms.cursor->hotspot.x, - .dst_y = window->cursor_pos.y - window->kms.cursor->hotspot.y, - .dst_w = window->kms.cursor->width, - .dst_h = window->kms.cursor->height, - .has_rotation = false, - .rotation = PLANE_TRANSFORM_NONE, - .has_in_fence_fd = false, - .in_fence_fd = 0, - .prefer_cursor = true, - }, - cursor_buffer_unref_with_locked_drmdev, - NULL, - window->kms.cursor - ); - if (ok != 0) { - LOG_ERROR("Couldn't present cursor.\n"); - } else { - cursor_buffer_ref(window->kms.cursor); - } - } - - req = kms_req_builder_build(builder); - if (req == NULL) { - goto fail_unref_builder; - } - - kms_req_builder_unref(builder); - builder = NULL; - - frame = malloc(sizeof *frame); - if (frame == NULL) { - goto fail_unref_req; - } - - frame->req = req; - frame->tracer = tracer_ref(window->tracer); - frame->unset_should_apply_mode_on_commit = window->kms.should_apply_mode; - - frame_scheduler_present_frame(window->frame_scheduler, on_present_frame, frame, on_cancel_frame); - - // 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 { - // 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; - // } - // } - - // 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_req: - kms_req_unref(req); - return ok; - -fail_unref_builder: - kms_req_builder_unref(builder); - return ok; -} - -static int kms_window_push_composition(struct window *window, struct fl_layer_composition *composition) { - int ok; - - window_lock(window); - - ok = kms_window_push_composition_locked(window, composition); - - window_unlock(window); - - return ok; -} - -static bool count_modifiers_for_pixel_format( - UNUSED struct drm_plane *plane, - UNUSED int index, - enum pixfmt pixel_format, - UNUSED uint64_t modifier, - void *userdata -) { - struct { - enum pixfmt format; - uint64_t *modifiers; - size_t n_modifiers; - int index; - } *context = userdata; - - if (pixel_format == context->format) { - context->n_modifiers++; - } - - return true; -} - -static bool extract_modifiers_for_pixel_format( - UNUSED struct drm_plane *plane, - UNUSED int index, - enum pixfmt pixel_format, - uint64_t modifier, - void *userdata -) { - struct { - enum pixfmt format; - uint64_t *modifiers; - size_t n_modifiers; - int index; - } *context = userdata; - - if (pixel_format == context->format) { - context->modifiers[context->index++] = modifier; - } - - return true; -} - -static struct render_surface *kms_window_get_render_surface_internal(struct window *window, bool has_size, UNUSED struct vec2i size) { - struct render_surface *render_surface; - - ASSERT_NOT_NULL(window); - - if (window->render_surface != NULL) { - return window->render_surface; - } - - if (!has_size) { - // Flutter wants a render surface, but hasn't told us the backing store dimensions yet. - // Just make a good guess about the dimensions. - LOG_DEBUG("Flutter requested render surface before supplying surface dimensions.\n"); - size = VEC2I(window->kms.mode->hdisplay, window->kms.mode->vdisplay); - } - - enum pixfmt pixel_format; - if (window->has_forced_pixel_format) { - pixel_format = window->forced_pixel_format; - } else { - // Actually, more devices support ARGB8888 might sometimes not be supported by devices, - // for example for primary planes. But we can just cast ARGB8888 to XRGB8888 if we need to, - // and ARGB8888 is still a good default choice because casting XRGB to ARGB might not work, - // and sometimes we need alpha for overlay planes. - // Also vulkan doesn't work with XRGB yet so we definitely need to use ARGB to vulkan too. - pixel_format = PIXFMT_ARGB8888; - } - - // Possibly populate this with the supported modifiers for this pixel format. - // If no plane lists modifiers for this pixel format, this will be left at NULL, - // and egl_gbm_render_surface_new... will create the GBM surface using usage flags - // (GBM_USE_SCANOUT | GBM_USE_RENDER) instead. - uint64_t *allowed_modifiers = NULL; - size_t n_allowed_modifiers = 0; - - // For now just set the supported modifiers for the first plane that supports this pixel format - // as the allowed modifiers. - /// TODO: Find a way to rank pixel formats, maybe by number of planes that support them for scanout. - { - struct drm_plane *plane; - for_each_plane_in_drmdev(window->kms.drmdev, plane) { - if (!(plane->possible_crtcs & window->kms.crtc->bitmask)) { - // Only query planes that are possible to connect to the CRTC we're using. - continue; - } - - if (plane->type != kPrimary_DrmPlaneType && plane->type != kOverlay_DrmPlaneType) { - // We explicitly only look for primary and overlay planes. - continue; - } - - if (!plane->supports_modifiers) { - // The plane does not have an IN_FORMATS property and does not support - // explicit modifiers. - // - // Calling drm_plane_for_each_modified_format below will segfault. - continue; - } - - struct { - enum pixfmt format; - uint64_t *modifiers; - size_t n_modifiers; - int index; - } context = { - .format = pixel_format, - .modifiers = NULL, - .n_modifiers = 0, - .index = 0, - }; - - // First, count the allowed modifiers for this pixel format. - drm_plane_for_each_modified_format(plane, count_modifiers_for_pixel_format, &context); - - n_allowed_modifiers = context.n_modifiers; - allowed_modifiers = calloc(n_allowed_modifiers, sizeof(*context.modifiers)); - context.modifiers = allowed_modifiers; - - // Next, fill context.modifiers with the allowed modifiers. - drm_plane_for_each_modified_format(plane, extract_modifiers_for_pixel_format, &context); - break; - } - } - - if (window->renderer_type == kOpenGL_RendererType) { - // opengl -#ifdef HAVE_EGL_GLES2 - // EGL_NO_CONFIG_KHR is defined by EGL_KHR_no_config_context. - #ifndef EGL_KHR_no_config_context - #error "EGL header definitions for extension EGL_KHR_no_config_context are required." - #endif - - struct egl_gbm_render_surface *egl_surface = egl_gbm_render_surface_new_with_egl_config( - window->tracer, - size, - gl_renderer_get_gbm_device(window->gl_renderer), - window->gl_renderer, - pixel_format, - EGL_NO_CONFIG_KHR, - allowed_modifiers, - n_allowed_modifiers - ); - if (egl_surface == NULL) { - LOG_ERROR("Couldn't create EGL GBM rendering surface.\n"); - render_surface = NULL; - } else { - render_surface = CAST_RENDER_SURFACE(egl_surface); - } - -#else - UNREACHABLE(); -#endif - } else { - ASSUME(window->renderer_type == kVulkan_RendererType); - - // vulkan -#ifdef HAVE_VULKAN - struct vk_gbm_render_surface *vk_surface = - vk_gbm_render_surface_new(window->tracer, size, drmdev_get_gbm_device(window->kms.drmdev), window->vk_renderer, pixel_format); - if (vk_surface == NULL) { - LOG_ERROR("Couldn't create Vulkan GBM rendering surface.\n"); - render_surface = NULL; - } else { - render_surface = CAST_RENDER_SURFACE(vk_surface); - } -#else - UNREACHABLE(); -#endif - } - - if (allowed_modifiers != NULL) { - free(allowed_modifiers); - } - - window->render_surface = render_surface; - return render_surface; -} - -static struct render_surface *kms_window_get_render_surface(struct window *window, struct vec2i size) { - ASSERT_NOT_NULL(window); - return kms_window_get_render_surface_internal(window, true, size); -} - -#ifdef HAVE_EGL_GLES2 -static bool kms_window_has_egl_surface(struct window *window) { - if (window->renderer_type == kOpenGL_RendererType) { - return window->render_surface != NULL; - } else { - return false; - } -} - -static EGLSurface kms_window_get_egl_surface(struct window *window) { - if (window->renderer_type == kOpenGL_RendererType) { - struct render_surface *render_surface = kms_window_get_render_surface_internal(window, false, VEC2I(0, 0)); - return egl_gbm_render_surface_get_egl_surface(CAST_EGL_GBM_RENDER_SURFACE(render_surface)); - } else { - return EGL_NO_SURFACE; - } -} -#endif - -static int kms_window_set_cursor_locked( - // clang-format off - struct window *window, - bool has_enabled, bool enabled, - bool has_kind, enum pointer_kind kind, - bool has_pos, struct vec2i pos - // clang-format on -) { - const struct pointer_icon *icon; - struct cursor_buffer *cursor; - - if (has_kind) { - if (window->kms.pointer_icon == NULL || pointer_icon_get_kind(window->kms.pointer_icon) != kind) { - window->kms.pointer_icon = pointer_icon_for_details(kind, window->pixel_ratio); - ASSERT_NOT_NULL(window->kms.pointer_icon); - } - } - - enabled = has_enabled ? enabled : window->cursor_enabled; - icon = has_kind ? pointer_icon_for_details(kind, window->pixel_ratio) : window->kms.pointer_icon; - pos = has_pos ? pos : window->cursor_pos; - cursor = window->kms.cursor; - - if (enabled && icon == NULL) { - // default to the arrow icon. - icon = pointer_icon_for_details(POINTER_KIND_BASIC, window->pixel_ratio); - ASSERT_NOT_NULL(icon); - } - - if (window->kms.pointer_icon != icon) { - window->kms.pointer_icon = icon; - } - - if (enabled) { - if (cursor == NULL || icon != cursor->icon) { - cursor = cursor_buffer_new(window->kms.drmdev, window->kms.pointer_icon, window->rotation); - if (cursor == NULL) { - return EIO; - } - - cursor_buffer_swap_ptrs(&window->kms.cursor, cursor); - - // cursor is created with refcount 1. cursor_buffer_swap_ptrs - // increases refcount by one. deref here so we don't leak a - // reference. - cursor_buffer_unrefp(&cursor); - - // apply the new cursor icon & position by scanning out a new frame. - window->cursor_pos = pos; - if (window->composition != NULL) { - kms_window_push_composition_locked(window, window->composition); + case USER_INPUT_TABLET_TOOL: { + if (event->tablet.tip_changed) { + fl_event->phase = event->tablet.tip ? kDown : kUp; + } else { + fl_event->phase = event->tablet.tip ? kMove : kHover; + } + fl_event->device_kind = kFlutterPointerDeviceKindStylus; + fl_event->x = event->tablet.position_ndc.x * window->view_size.x; + fl_event->y = event->tablet.position_ndc.y * window->view_size.y; + break; } - } else if (has_pos) { - // apply the new cursor position using drmModeMoveCursor - window->cursor_pos = pos; - drmdev_move_cursor(window->kms.drmdev, window->kms.crtc->id, vec2i_sub(pos, window->kms.cursor->hotspot)); - } - } else { - if (window->kms.cursor != NULL) { - cursor_buffer_unrefp(&window->kms.cursor); + case USER_INPUT_KEY: UNREACHABLE(); + default: break; } - } - - window->cursor_enabled = enabled; - return 0; -} - -static int dummy_window_push_composition(struct window *window, struct fl_layer_composition *composition); -static struct render_surface *dummy_window_get_render_surface_internal(struct window *window, bool has_size, UNUSED struct vec2i size); -static struct render_surface *dummy_window_get_render_surface(struct window *window, struct vec2i size); -#ifdef HAVE_EGL_GLES2 -static bool dummy_window_has_egl_surface(struct window *window); -static EGLSurface dummy_window_get_egl_surface(struct window *window); -#endif - -static void dummy_window_deinit(struct window *window); -static int dummy_window_set_cursor_locked( - // clang-format off - struct window *window, - bool has_enabled, bool enabled, - bool has_kind, enum pointer_kind kind, - bool has_pos, struct vec2i pos - // clang-format on -); - -MUST_CHECK struct window *dummy_window_new( - // clang-format off - struct tracer *tracer, - struct frame_scheduler *scheduler, - enum renderer_type renderer_type, - struct gl_renderer *gl_renderer, - struct vk_renderer *vk_renderer, - struct vec2i size, - bool has_explicit_dimensions, int width_mm, int height_mm, - double refresh_rate - // clang-format on -) { - struct window *window; + n_fl_events++; - window = malloc(sizeof *window); - if (window == NULL) { - return NULL; +skip: + continue; } - window_init( - // clang-format off - window, - tracer, - scheduler, - false, PLANE_TRANSFORM_NONE, - false, kLandscapeLeft, - size.x, size.y, - has_explicit_dimensions, width_mm, height_mm, - refresh_rate, - false, PIXFMT_RGB565 - // clang-format on - ); - - window->renderer_type = renderer_type; - if (gl_renderer != NULL) { -#ifdef HAVE_EGL_GLES2 - window->gl_renderer = gl_renderer_ref(gl_renderer); -#else - UNREACHABLE(); -#endif - } - if (vk_renderer != NULL) { -#ifdef HAVE_VULKAN - window->vk_renderer = vk_renderer_ref(vk_renderer); -#else - UNREACHABLE(); -#endif - } else { - window->vk_renderer = NULL; + if (pointer_event_interface != NULL && n_fl_events > 0) { + pointer_event_interface->send_pointer_event(pointer_event_interface->engine, fl_events, n_fl_events); } - window->push_composition = dummy_window_push_composition; - window->get_render_surface = dummy_window_get_render_surface; -#ifdef HAVE_EGL_GLES2 - window->has_egl_surface = dummy_window_has_egl_surface; - window->get_egl_surface = dummy_window_get_egl_surface; -#endif - window->deinit = dummy_window_deinit; - window->set_cursor_locked = dummy_window_set_cursor_locked; - return window; } -static int dummy_window_push_composition(struct window *window, struct fl_layer_composition *composition) { - window_lock(window); - - /// TODO: Maybe allow to export the layer composition as an image, for testing purposes. - (void) composition; - - window_unlock(window); - - return 0; -} - -static struct render_surface *dummy_window_get_render_surface_internal(struct window *window, bool has_size, UNUSED struct vec2i size) { - struct render_surface *render_surface; - +input_device_match_score_t window_match_input_device(struct window *window, struct user_input_device *device) { ASSERT_NOT_NULL(window); + ASSERT_NOT_NULL(device); - if (!has_size) { - size = vec2f_round_to_integer(window->view_size); - } - - if (window->render_surface != NULL) { - return window->render_surface; - } - - if (window->renderer_type == kOpenGL_RendererType) { - // opengl -#ifdef HAVE_EGL_GLES2 - // EGL_NO_CONFIG_KHR is defined by EGL_KHR_no_config_context. - #ifndef EGL_KHR_no_config_context - #error "EGL header definitions for extension EGL_KHR_no_config_context are required." - #endif - - struct egl_gbm_render_surface *egl_surface = egl_gbm_render_surface_new_with_egl_config( - window->tracer, - size, - gl_renderer_get_gbm_device(window->gl_renderer), - window->gl_renderer, - window->has_forced_pixel_format ? window->forced_pixel_format : PIXFMT_ARGB8888, - EGL_NO_CONFIG_KHR, - NULL, - 0 - ); - if (egl_surface == NULL) { - LOG_ERROR("Couldn't create EGL GBM rendering surface.\n"); - render_surface = NULL; - } else { - render_surface = CAST_RENDER_SURFACE(egl_surface); - } - -#else - UNREACHABLE(); -#endif + if (window->ops.match_input_device == NULL) { + return -1; } else { - ASSUME(window->renderer_type == kVulkan_RendererType); - - // vulkan -#ifdef HAVE_VULKAN - UNIMPLEMENTED(); -#else - UNREACHABLE(); -#endif + return window->ops.match_input_device(window, device); } - - window->render_surface = render_surface; - return render_surface; -} - -static struct render_surface *dummy_window_get_render_surface(struct window *window, struct vec2i size) { - ASSERT_NOT_NULL(window); - return dummy_window_get_render_surface_internal(window, true, size); -} - -#ifdef HAVE_EGL_GLES2 -static bool dummy_window_has_egl_surface(struct window *window) { - ASSERT_NOT_NULL(window); - - if (window->renderer_type == kOpenGL_RendererType) { - return window->render_surface != NULL; - } else { - return false; - } -} - -static EGLSurface dummy_window_get_egl_surface(struct window *window) { - ASSERT_NOT_NULL(window); - - if (window->renderer_type == kOpenGL_RendererType) { - struct render_surface *render_surface = dummy_window_get_render_surface_internal(window, false, VEC2I(0, 0)); - return egl_gbm_render_surface_get_egl_surface(CAST_EGL_GBM_RENDER_SURFACE(render_surface)); - } else { - return EGL_NO_SURFACE; - } -} -#endif - -static void dummy_window_deinit(struct window *window) { - ASSERT_NOT_NULL(window); - - if (window->render_surface != NULL) { - surface_unref(CAST_SURFACE(window->render_surface)); - } - - if (window->gl_renderer != NULL) { -#ifdef HAVE_EGL_GLES2 - gl_renderer_unref(window->gl_renderer); -#else - UNREACHABLE(); -#endif - } - - if (window->vk_renderer != NULL) { -#ifdef HAVE_VULKAN - vk_renderer_unref(window->vk_renderer); -#else - UNREACHABLE(); -#endif - } - - window_deinit(window); -} - -static int dummy_window_set_cursor_locked( - // clang-format off - struct window *window, - bool has_enabled, bool enabled, - bool has_kind, enum pointer_kind kind, - bool has_pos, struct vec2i pos - // clang-format on -) { - ASSERT_NOT_NULL(window); - - (void) window; - (void) has_enabled; - (void) enabled; - (void) has_kind; - (void) kind; - (void) has_pos; - (void) pos; - - return 0; } diff --git a/src/window.h b/src/window.h index 2efd0196..6db876e6 100644 --- a/src/window.h +++ b/src/window.h @@ -11,7 +11,9 @@ #define _FLUTTERPI_SRC_WINDOW_H #include "compositor_ng.h" -#include "modesetting.h" +#include "kms/drmdev.h" +#include "kms/req_builder.h" +#include "kms/resources.h" #include "pixel_format.h" #include "util/refcounting.h" @@ -27,76 +29,13 @@ struct view_geometry { struct vec2f view_size, display_size; struct mat3f display_to_view_transform; struct mat3f view_to_display_transform; - double device_pixel_ratio; + float device_pixel_ratio; }; enum renderer_type { kOpenGL_RendererType, kVulkan_RendererType }; DECLARE_REF_OPS(window) -/** - * @brief Creates a new KMS window. - * - * @param tracer - * @param scheduler - * @param render_surface_interface - * @param has_rotation - * @param rotation - * @param has_orientation - * @param orientation - * @param has_explicit_dimensions - * @param width_mm - * @param height_mm - * @param has_forced_pixel_format - * @param forced_pixel_format - * @param drmdev - * @param desired_videomode - * @return struct window* The new KMS window. - */ -struct window *kms_window_new( - // clang-format off - struct tracer *tracer, - struct frame_scheduler *scheduler, - enum renderer_type renderer_type, - struct gl_renderer *gl_renderer, - struct vk_renderer *vk_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, - struct drmdev *drmdev, - const char *desired_videomode - // clang-format on -); - -/** - * Creates a new dummy window. - * - * @param tracer The tracer object. - * @param scheduler The frame scheduler object. - * @param renderer_type The type of renderer. - * @param gl_renderer The GL renderer object. - * @param vk_renderer The Vulkan renderer object. - * @param size The size of the window. - * @param has_explicit_dimensions Indicates if the window has explicit dimensions. - * @param width_mm The width of the window in millimeters. - * @param height_mm The height of the window in millimeters. - * @param refresh_rate The refresh rate of the window. - * @return A pointer to the newly created window. - */ -MUST_CHECK struct window *dummy_window_new( - struct tracer *tracer, - struct frame_scheduler *scheduler, - enum renderer_type renderer_type, - struct gl_renderer *gl_renderer, - struct vk_renderer *vk_renderer, - struct vec2i size, - bool has_explicit_dimensions, - int width_mm, - int height_mm, - double refresh_rate -); - /** * @brief Push a new flutter composition to the window, outputting a new frame. * @@ -120,7 +59,7 @@ struct view_geometry window_get_view_geometry(struct window *window); * @param window The window instance. * @return double The refresh rate. */ -ATTR_PURE double window_get_refresh_rate(struct window *window); +ATTR_PURE float window_get_refresh_rate(struct window *window); /** * @brief Returns the timestamp of the next vblank signal in @param next_vblank_ns_out. @@ -147,6 +86,17 @@ struct render_surface *window_get_render_surface(struct window *window, struct v bool window_is_cursor_enabled(struct window *window); +/** + * @brief Set the cursor (enabled, kind, position) for this window. + * + * @param window The window to set the cursor for. + * @param has_enabled True if @param enabled contains a valid value, and the cursor should either be enabled to disabled. + * @param enabled Whether the cursor should be enabled or disabled. + * @param has_kind True if the cursor kind should be changed. + * @param kind The kind of cursor to set. + * @param has_pos True if the cursor position should be changed. + * @param pos The position of the cursor. + */ int window_set_cursor( // clang-format off struct window *window, @@ -156,4 +106,42 @@ int window_set_cursor( // clang-format on ); +struct user_input_event; + +/** + * @brief Transform a normalized device coordinate (0..1) to a view coordinate. + * + * In most cases, this just means the NDC is multiplied by the view size. + * However, for a rotated display, this might be more complex. + * + * @param window The window to transform for. + * @param ndc The normalized device coordinate. + */ +struct vec2f window_transform_ndc_to_view(struct window *window, struct vec2f ndc); + +/** + * @brief A score, describing how good an input device matches to an output window. + * + * A negative value means it doesn't match at all, and the window should under no circumstances receive + * input events from this device. + */ +typedef int input_device_match_score_t; + +struct user_input_device; + +/** + * @brief Returns how likely it is that this input device is meant for this window. + * + * Input devices that send absolute positions (e.g. touch-screen, tablet, but not a mouse) always + * need to be matched to a specific output. This input device <-> output association is unfortunately + * not always clear, so we need to guess. + * + * When a new input device is connected, the compositor will call this function to see if any window + * is a good match for this device. The window with the highest score will receive the input events. + * + * @param window The window to check. + * @param device The input device to check. + */ +input_device_match_score_t window_match_input_device(struct window *window, struct user_input_device *device); + #endif // _FLUTTERPI_SRC_WINDOW_H diff --git a/src/window_private.h b/src/window_private.h new file mode 100644 index 00000000..b491afe2 --- /dev/null +++ b/src/window_private.h @@ -0,0 +1,260 @@ +#ifndef _FLUTTERPI_SRC_WINDOW_PRIVATE_H +#define _FLUTTERPI_SRC_WINDOW_PRIVATE_H + +#define _GNU_SOURCE +#include +#include + +#include + +#include + +#include "compositor_ng.h" +#include "cursor.h" +#include "flutter-pi.h" +#include "frame_scheduler.h" +#include "kms/req_builder.h" +#include "kms/resources.h" +#include "render_surface.h" +#include "surface.h" +#include "tracer.h" +#include "util/collection.h" +#include "util/logging.h" +#include "util/refcounting.h" +#include "window.h" + +#include "config.h" + +#ifdef HAVE_EGL_GLES2 + #include "egl_gbm_render_surface.h" + #include "gl_renderer.h" +#endif + +#ifdef HAVE_VULKAN + #include "vk_gbm_render_surface.h" + #include "vk_renderer.h" +#endif + +struct user_input_device; + +struct window_ops { + void (*deinit)(struct window *window); + + int (*push_composition)(struct window *window, struct fl_layer_composition *composition); + struct render_surface *(*get_render_surface)(struct window *window, struct vec2i size); + +#ifdef HAVE_EGL_GLES2 + bool (*has_egl_surface)(struct window *window); + EGLSurface (*get_egl_surface)(struct window *window); +#endif + + int (*set_cursor_locked)( + struct window *window, + bool has_enabled, + bool enabled, + bool has_kind, + enum pointer_kind kind, + bool has_pos, + struct vec2i pos + ); + + input_device_match_score_t (*match_input_device)(struct window *window, struct user_input_device *device); +}; + +struct gl_renderer; +struct vk_renderer; + +struct window { + pthread_mutex_t lock; + refcount_t n_refs; + + /** + * @brief Event tracing interface. + * + * Used to report timing information to the dart observatory. + * + */ + struct tracer *tracer; + + /** + * @brief Manages the frame scheduling for this window. + * + */ + struct frame_scheduler *frame_scheduler; + + /** + * @brief Refresh rate of the selected video mode / display. + * + */ + float refresh_rate; + + /** + * @brief 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. + */ + float pixel_ratio; + + /** + * @brief Whether we have physical screen dimensions and @ref width_mm and @ref height_mm contain usable values. + * + */ + bool has_dimensions; + + /** + * @brief Width, height of the display in millimeters. + * + */ + int width_mm, height_mm; + + /** + * @brief The size of the view, as reported to flutter, in pixels. + * + * If no rotation and scaling is applied, this probably equals the display size. + * For example, if flutter-pi should render at 1/2 the resolution of a full-hd display, this would be + * 960x540 and the display size 1920x1080. + */ + struct vec2f view_size; + + /** + * @brief The actual size of the view on the display, pixels. + * + */ + struct vec2f display_size; + + /** + * @brief The rotation we should apply to the flutter layers to present them on screen. + */ + drm_plane_transform_t rotation; + + /** + * @brief 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. Though this can also be anything else theoretically, if the user specifies weird combinations of rotation + * and orientation via cmdline arguments. + * + * @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. + * + */ + struct mat3f 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. + */ + struct mat3f view_to_display_transform; + + /** + * @brief Matrix for transforming normalized device coordinates to view coordinates. + * + */ + struct mat3f ndc_to_view_transform; + + /** + * @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 current flutter layer composition that should be output on screen. + * + */ + struct fl_layer_composition *composition; + + /** + * @brief The type of rendering that should be used. (gl, vk) + * + */ + enum renderer_type renderer_type; + + /** + * @brief The OpenGL renderer if OpenGL rendering should be used. + * + */ + struct gl_renderer *gl_renderer; + + /** + * @brief The Vulkan renderer if Vulkan rendering should be used. + * + */ + struct vk_renderer *vk_renderer; + + /** + * @brief Our main render surface, if we have one yet. + * + * Otherwise a new one should be created using the render surface interface. + * + */ + struct render_surface *render_surface; + +#ifdef HAVE_EGL_GLES2 + /** + * @brief The EGLSurface of this window, if any. + * + * Should be EGL_NO_SURFACE if this window is not associated with any EGL surface. + * This is really just a workaround because flutter doesn't support arbitrary EGL surfaces as render targets right now. + * (Just one global EGLSurface) + * + */ + EGLSurface egl_surface; +#endif + + /** + * @brief Whether this window currently shows a mouse cursor. + * + */ + bool cursor_enabled; + + /** + * @brief The position of the mouse cursor. + * + */ + struct vec2f cursor_pos; + + struct window_ops ops; +}; + +int window_init( + // clang-format off + struct window *window, + struct tracer *tracer, + struct frame_scheduler *scheduler, + bool has_rotation, drm_plane_transform_t rotation, + bool has_orientation, enum device_orientation orientation, + int width, int height, + bool has_dimensions, int width_mm, int height_mm, + float refresh_rate, + bool has_forced_pixel_format, enum pixfmt forced_pixel_format, + enum renderer_type renderer_type, + struct gl_renderer *gl_renderer, + struct vk_renderer *vk_renderer + // clang-format on +); + +void window_deinit(struct window *window); + +#endif // _FLUTTERPI_SRC_WINDOW_PRIVATE_H diff --git a/test/flutterpi_test.c b/test/flutterpi_test.c index 96236b78..a23cbd90 100644 --- a/test/flutterpi_test.c +++ b/test/flutterpi_test.c @@ -1,10 +1,10 @@ #include #include -void setUp() { +void setUp(void) { } -void tearDown() { +void tearDown(void) { } #define TEST_ASSERT_EQUAL_BOOL(expected, actual) \ @@ -50,7 +50,7 @@ void expect_parsed_cmdline_args_matches(int argc, char **argv, bool expected_res TEST_ASSERT_EQUAL_INT(expected.dummy_display_size.y, actual.dummy_display_size.y); } -static struct flutterpi_cmdline_args get_default_args() { +static struct flutterpi_cmdline_args get_default_args(void) { static char *engine_argv[1] = { "flutter-pi" }; return (struct flutterpi_cmdline_args){ @@ -74,7 +74,7 @@ static struct flutterpi_cmdline_args get_default_args() { }; } -void test_parse_orientation_arg() { +void test_parse_orientation_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); // test --orientation @@ -132,7 +132,7 @@ void test_parse_orientation_arg() { ); } -void test_parse_rotation_arg() { +void test_parse_rotation_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.has_rotation = true; @@ -149,7 +149,7 @@ void test_parse_rotation_arg() { expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--rotation", "270", BUNDLE_PATH }, true, expected); } -void test_parse_physical_dimensions_arg() { +void test_parse_physical_dimensions_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.bundle_path = NULL; @@ -164,7 +164,7 @@ void test_parse_physical_dimensions_arg() { expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--dimensions", "10,10", BUNDLE_PATH }, true, expected); } -void test_parse_pixel_format_arg() { +void test_parse_pixel_format_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.has_pixel_format = true; @@ -176,7 +176,7 @@ void test_parse_pixel_format_arg() { expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--pixelformat", "RGBA8888", BUNDLE_PATH }, true, expected); } -void test_parse_runtime_mode_arg() { +void test_parse_runtime_mode_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); // test --debug, --profile, --release @@ -194,14 +194,14 @@ void test_parse_runtime_mode_arg() { expect_parsed_cmdline_args_matches(3, (char *[]){ "flutter-pi", "--release", BUNDLE_PATH }, true, expected); } -void test_parse_bundle_path_arg() { +void test_parse_bundle_path_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.bundle_path = "/path/to/bundle/test"; expect_parsed_cmdline_args_matches(2, (char *[]){ "flutter-pi", "/path/to/bundle/test" }, true, expected); } -void test_parse_engine_arg() { +void test_parse_engine_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.engine_argc = 2; @@ -210,14 +210,14 @@ void test_parse_engine_arg() { expect_parsed_cmdline_args_matches(3, (char *[]){ "flutter-pi", BUNDLE_PATH, "engine-arg" }, true, expected); } -void test_parse_vulkan_arg() { +void test_parse_vulkan_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.use_vulkan = true; expect_parsed_cmdline_args_matches(3, (char *[]){ "flutter-pi", "--vulkan", BUNDLE_PATH }, true, expected); } -void test_parse_desired_videomode_arg() { +void test_parse_desired_videomode_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.desired_videomode = "1920x1080"; @@ -227,7 +227,7 @@ void test_parse_desired_videomode_arg() { expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--videomode", "1920x1080@60", BUNDLE_PATH }, true, expected); } -int main() { +int main(void) { UNITY_BEGIN(); RUN_TEST(test_parse_runtime_mode_arg); diff --git a/test/platformchannel_test.c b/test/platformchannel_test.c index 5047a082..6adb3e30 100644 --- a/test/platformchannel_test.c +++ b/test/platformchannel_test.c @@ -1,6 +1,7 @@ #define _GNU_SOURCE #include "platformchannel.h" +#include #include #include #include @@ -10,34 +11,37 @@ #define RAW_STD_BUF(...) (const struct raw_std_value *) ((const uint8_t[]){ __VA_ARGS__ }) #define AS_RAW_STD_VALUE(_value) ((const struct raw_std_value *) (_value)) +#define DBL_INFINITY ((double) INFINITY) +#define DBL_NAN ((double) NAN) + // required by Unity. -void setUp() { +void setUp(void) { } -void tearDown() { +void tearDown(void) { } -void test_raw_std_value_is_null() { +void test_raw_std_value_is_null(void) { TEST_ASSERT_TRUE(raw_std_value_is_null(RAW_STD_BUF(kStdNull))); TEST_ASSERT_FALSE(raw_std_value_is_null(RAW_STD_BUF(kStdTrue))); } -void test_raw_std_value_is_true() { +void test_raw_std_value_is_true(void) { TEST_ASSERT_TRUE(raw_std_value_is_true(RAW_STD_BUF(kStdTrue))); TEST_ASSERT_FALSE(raw_std_value_is_true(RAW_STD_BUF(kStdFalse))); } -void test_raw_std_value_is_false() { +void test_raw_std_value_is_false(void) { TEST_ASSERT_TRUE(raw_std_value_is_false(RAW_STD_BUF(kStdFalse))); TEST_ASSERT_FALSE(raw_std_value_is_false(RAW_STD_BUF(kStdTrue))); } -void test_raw_std_value_is_int32() { +void test_raw_std_value_is_int32(void) { TEST_ASSERT_TRUE(raw_std_value_is_int32(RAW_STD_BUF(kStdInt32))); TEST_ASSERT_FALSE(raw_std_value_is_int32(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_int32() { +void test_raw_std_value_as_int32(void) { // clang-format off alignas(16) uint8_t buffer[5] = { kStdInt32, @@ -53,12 +57,12 @@ void test_raw_std_value_as_int32() { TEST_ASSERT_EQUAL_INT32(-2003205, raw_std_value_as_int32(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_is_int64() { +void test_raw_std_value_is_int64(void) { TEST_ASSERT_TRUE(raw_std_value_is_int64(RAW_STD_BUF(kStdInt64))); TEST_ASSERT_FALSE(raw_std_value_is_int64(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_int64() { +void test_raw_std_value_as_int64(void) { // clang-format off alignas(16) uint8_t buffer[9] = { kStdInt64, @@ -74,12 +78,12 @@ void test_raw_std_value_as_int64() { TEST_ASSERT_EQUAL_INT64(-7998090352538419200, raw_std_value_as_int64(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_is_float64() { +void test_raw_std_value_is_float64(void) { TEST_ASSERT_TRUE(raw_std_value_is_float64(RAW_STD_BUF(kStdFloat64))); TEST_ASSERT_FALSE(raw_std_value_is_float64(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_float64() { +void test_raw_std_value_as_float64(void) { // clang-format off alignas(16) uint8_t buffer[] = { kStdFloat64, @@ -93,18 +97,18 @@ void test_raw_std_value_as_float64() { TEST_ASSERT_EQUAL_DOUBLE(M_PI, raw_std_value_as_float64(AS_RAW_STD_VALUE(buffer))); - value = INFINITY; + value = DBL_INFINITY; memcpy(buffer + 8, &value, sizeof(value)); - TEST_ASSERT_EQUAL_DOUBLE(INFINITY, raw_std_value_as_float64(AS_RAW_STD_VALUE(buffer))); + TEST_ASSERT_EQUAL_DOUBLE(DBL_INFINITY, raw_std_value_as_float64(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_is_string() { +void test_raw_std_value_is_string(void) { TEST_ASSERT_TRUE(raw_std_value_is_string(RAW_STD_BUF(kStdString))); TEST_ASSERT_FALSE(raw_std_value_is_string(RAW_STD_BUF(kStdNull))); } -void test_raw_std_string_dup() { +void test_raw_std_string_dup(void) { const char *str = "The quick brown fox jumps over the lazy dog."; // clang-format off @@ -129,7 +133,7 @@ void test_raw_std_string_dup() { free(str_duped); } -void test_raw_std_string_equals() { +void test_raw_std_string_equals(void) { const char *str = "The quick brown fox jumps over the lazy dog."; alignas(16) uint8_t buffer[1 + 1 + strlen(str)]; @@ -151,12 +155,12 @@ void test_raw_std_string_equals() { TEST_ASSERT_FALSE(raw_std_string_equals(AS_RAW_STD_VALUE(buffer), "anything")); } -void test_raw_std_value_is_uint8array() { +void test_raw_std_value_is_uint8array(void) { TEST_ASSERT_TRUE(raw_std_value_is_uint8array(RAW_STD_BUF(kStdUInt8Array))); TEST_ASSERT_FALSE(raw_std_value_is_uint8array(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_uint8array() { +void test_raw_std_value_as_uint8array(void) { // clang-format off alignas(16) uint8_t buffer[] = { kStdUInt8Array, @@ -179,12 +183,12 @@ void test_raw_std_value_as_uint8array() { TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, raw_std_value_as_uint8array(AS_RAW_STD_VALUE(buffer)), 4); } -void test_raw_std_value_is_int32array() { +void test_raw_std_value_is_int32array(void) { TEST_ASSERT_TRUE(raw_std_value_is_int32array(RAW_STD_BUF(kStdInt32Array))); TEST_ASSERT_FALSE(raw_std_value_is_int32array(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_int32array() { +void test_raw_std_value_as_int32array(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -216,12 +220,12 @@ void test_raw_std_value_as_int32array() { TEST_ASSERT_EQUAL_INT32_ARRAY(expected, raw_std_value_as_int32array(AS_RAW_STD_VALUE(buffer)), 2); } -void test_raw_std_value_is_int64array() { +void test_raw_std_value_is_int64array(void) { TEST_ASSERT_TRUE(raw_std_value_is_int64array(RAW_STD_BUF(kStdInt64Array))); TEST_ASSERT_FALSE(raw_std_value_is_int64array(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_int64array() { +void test_raw_std_value_as_int64array(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -251,12 +255,12 @@ void test_raw_std_value_as_int64array() { TEST_ASSERT_EQUAL_INT64_ARRAY(expected, raw_std_value_as_int64array(AS_RAW_STD_VALUE(buffer)), 2); } -void test_raw_std_value_is_float64array() { +void test_raw_std_value_is_float64array(void) { TEST_ASSERT_TRUE(raw_std_value_is_float64array(RAW_STD_BUF(kStdFloat64Array))); TEST_ASSERT_FALSE(raw_std_value_is_float64array(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_float64array() { +void test_raw_std_value_as_float64array(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -274,7 +278,7 @@ void test_raw_std_value_as_float64array() { // clang-format off double expected[] = { M_PI, - INFINITY, + DBL_INFINITY, }; // clang-format on @@ -288,12 +292,12 @@ void test_raw_std_value_as_float64array() { TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, raw_std_value_as_float64array(AS_RAW_STD_VALUE(buffer)), 2); } -void test_raw_std_value_is_list() { +void test_raw_std_value_is_list(void) { TEST_ASSERT_TRUE(raw_std_value_is_list(RAW_STD_BUF(kStdList))); TEST_ASSERT_FALSE(raw_std_value_is_list(RAW_STD_BUF(kStdNull))); } -void test_raw_std_list_get_size() { +void test_raw_std_list_get_size(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -325,12 +329,12 @@ void test_raw_std_list_get_size() { TEST_ASSERT_EQUAL_size_t(0xDEADBEEF, raw_std_list_get_size(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_is_map() { +void test_raw_std_value_is_map(void) { TEST_ASSERT_TRUE(raw_std_value_is_map(RAW_STD_BUF(kStdMap))); TEST_ASSERT_FALSE(raw_std_value_is_map(RAW_STD_BUF(kStdNull))); } -void test_raw_std_map_get_size() { +void test_raw_std_map_get_size(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -362,12 +366,12 @@ void test_raw_std_map_get_size() { TEST_ASSERT_EQUAL_size_t(0xDEADBEEF, raw_std_map_get_size(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_is_float32array() { +void test_raw_std_value_is_float32array(void) { TEST_ASSERT_TRUE(raw_std_value_is_float32array(RAW_STD_BUF(kStdFloat32Array))); TEST_ASSERT_FALSE(raw_std_value_is_float32array(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_float32array() { +void test_raw_std_value_as_float32array(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -385,7 +389,7 @@ void test_raw_std_value_as_float32array() { // clang-format off float expected[] = { M_PI, - INFINITY, + DBL_INFINITY, }; // clang-format on @@ -399,7 +403,7 @@ void test_raw_std_value_as_float32array() { TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, raw_std_value_as_float32array(AS_RAW_STD_VALUE(buffer)), 2); } -void test_raw_std_value_equals() { +void test_raw_std_value_equals(void) { TEST_ASSERT_TRUE(raw_std_value_equals(RAW_STD_BUF(kStdNull), RAW_STD_BUF(kStdNull))); TEST_ASSERT_FALSE(raw_std_value_equals(RAW_STD_BUF(kStdNull), RAW_STD_BUF(kStdTrue))); TEST_ASSERT_FALSE(raw_std_value_equals(RAW_STD_BUF(kStdTrue), RAW_STD_BUF(kStdFalse))); @@ -479,7 +483,7 @@ void test_raw_std_value_equals() { TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); - f = NAN; + f = DBL_NAN; memcpy(rhs + 8, &f, sizeof(f)); TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); @@ -689,7 +693,7 @@ void test_raw_std_value_equals() { double array[] = { M_PI, - INFINITY, + DBL_INFINITY, }; // clang-format on @@ -705,7 +709,7 @@ void test_raw_std_value_equals() { rhs[1] = 2; double array2[] = { 0.0, - INFINITY, + DBL_INFINITY, }; memcpy(rhs + 8, array2, sizeof(array2)); @@ -783,7 +787,7 @@ void test_raw_std_value_equals() { int64_t int64 = (int64_t) INT64_MIN; float floats[] = { M_PI, - INFINITY, + DBL_INFINITY, }; // clang-format on @@ -835,7 +839,7 @@ void test_raw_std_value_equals() { float array[] = { M_PI, - INFINITY, + DBL_INFINITY, }; // clang-format on @@ -852,7 +856,7 @@ void test_raw_std_value_equals() { // clang-format off float array2[] = { 0.0, - INFINITY, + DBL_INFINITY, }; // clang-format on memcpy(rhs + 4, array2, sizeof(array2)); @@ -861,18 +865,18 @@ void test_raw_std_value_equals() { } } -void test_raw_std_value_is_bool() { +void test_raw_std_value_is_bool(void) { TEST_ASSERT_FALSE(raw_std_value_is_bool(RAW_STD_BUF(kStdNull))); TEST_ASSERT_TRUE(raw_std_value_is_bool(RAW_STD_BUF(kStdTrue))); TEST_ASSERT_TRUE(raw_std_value_is_bool(RAW_STD_BUF(kStdFalse))); } -void test_raw_std_value_as_bool() { +void test_raw_std_value_as_bool(void) { TEST_ASSERT_TRUE(raw_std_value_as_bool(RAW_STD_BUF(kStdTrue))); TEST_ASSERT_FALSE(raw_std_value_as_bool(RAW_STD_BUF(kStdFalse))); } -void test_raw_std_value_is_int() { +void test_raw_std_value_is_int(void) { TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdNull))); TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdTrue))); TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdFalse))); @@ -881,7 +885,7 @@ void test_raw_std_value_is_int() { TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdFloat64))); } -void test_raw_std_value_as_int() { +void test_raw_std_value_as_int(void) { // clang-format off alignas(16) uint8_t buffer[9] = { kStdInt32, @@ -905,7 +909,7 @@ void test_raw_std_value_as_int() { TEST_ASSERT_EQUAL_INT64(INT32_MIN, raw_std_value_as_int(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_get_size() { +void test_raw_std_value_get_size(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -938,7 +942,7 @@ void test_raw_std_value_get_size() { TEST_ASSERT_EQUAL_size_t(0xDEADBEEF, raw_std_value_get_size(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_after() { +void test_raw_std_value_after(void) { // null { // clang-format off @@ -1263,7 +1267,7 @@ void test_raw_std_value_after() { } } -void test_raw_std_list_get_first_element() { +void test_raw_std_list_get_first_element(void) { // list const char *str = "The quick brown fox jumps over the lazy dog."; @@ -1286,7 +1290,7 @@ void test_raw_std_list_get_first_element() { ); } -void test_raw_std_list_get_nth_element() { +void test_raw_std_list_get_nth_element(void) { // list const char *str = "The quick brown fox jumps over the lazy dog."; @@ -1309,7 +1313,7 @@ void test_raw_std_list_get_nth_element() { ); } -void test_raw_std_map_get_first_key() { +void test_raw_std_map_get_first_key(void) { // map // clang-format off alignas(16) uint8_t buffer[] = { @@ -1340,31 +1344,31 @@ void test_raw_std_map_get_first_key() { TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4, raw_std_map_get_first_key(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_map_find() { +void test_raw_std_map_find(void) { } -void test_raw_std_map_find_str() { +void test_raw_std_map_find_str(void) { } -void test_raw_std_value_check() { +void test_raw_std_value_check(void) { } -void test_raw_std_method_call_check() { +void test_raw_std_method_call_check(void) { } -void test_raw_std_method_call_response_check() { +void test_raw_std_method_call_response_check(void) { } -void test_raw_std_event_check() { +void test_raw_std_event_check(void) { } -void test_raw_std_method_call_get_method() { +void test_raw_std_method_call_get_method(void) { } -void test_raw_std_method_call_get_method_dup() { +void test_raw_std_method_call_get_method_dup(void) { } -void test_raw_std_method_call_get_arg() { +void test_raw_std_method_call_get_arg(void) { } int main(void) { diff --git a/third_party/flutter_embedder_header/engine.version b/third_party/flutter_embedder_header/engine.version index a7aa73a8..0def69d1 100644 --- a/third_party/flutter_embedder_header/engine.version +++ b/third_party/flutter_embedder_header/engine.version @@ -1 +1 @@ -d44b5a94c976fbb65815374f61ab5392a220b084 \ No newline at end of file +b8800d88be4866db1b15f8b954ab2573bba9960f diff --git a/third_party/flutter_embedder_header/include/flutter_embedder.h b/third_party/flutter_embedder_header/include/flutter_embedder_header/flutter_embedder.h similarity index 89% rename from third_party/flutter_embedder_header/include/flutter_embedder.h rename to third_party/flutter_embedder_header/include/flutter_embedder_header/flutter_embedder.h index 5cdba06e..069c813a 100644 --- a/third_party/flutter_embedder_header/include/flutter_embedder.h +++ b/third_party/flutter_embedder_header/include/flutter_embedder_header/flutter_embedder.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_EMBEDDER_H_ -#define FLUTTER_EMBEDDER_H_ +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_H_ #include #include @@ -25,9 +25,9 @@ // - Function signatures (names, argument counts, argument order, and argument // type) cannot change. // - The core behavior of existing functions cannot change. -// - Instead of nesting structures by value within another structure, prefer -// nesting by pointer. This ensures that adding members to the nested struct -// does not break the ABI of the parent struct. +// - Instead of nesting structures by value within another structure/union, +// prefer nesting by pointer. This ensures that adding members to the nested +// struct does not break the ABI of the parent struct/union. // - Instead of array of structures, prefer array of pointers to structures. // This ensures that array indexing does not break if members are added // to the structure. @@ -162,6 +162,8 @@ typedef enum { kFlutterSemanticsActionMoveCursorBackwardByWord = 1 << 20, /// Replace the current text in the text field. kFlutterSemanticsActionSetText = 1 << 21, + /// Request that the respective focusable widget gain input focus. + kFlutterSemanticsActionFocus = 1 << 22, } FlutterSemanticsAction; /// The set of properties that may be associated with a semantics node. @@ -236,6 +238,11 @@ typedef enum { kFlutterSemanticsFlagIsKeyboardKey = 1 << 24, /// Whether the semantics node represents a tristate checkbox in mixed state. kFlutterSemanticsFlagIsCheckStateMixed = 1 << 25, + /// The semantics node has the quality of either being "expanded" or + /// "collapsed". + kFlutterSemanticsFlagHasExpandedState = 1 << 26, + /// Whether a semantic node that hasExpandedState is currently expanded. + kFlutterSemanticsFlagIsExpanded = 1 << 27, } FlutterSemanticsFlag; typedef enum { @@ -261,6 +268,12 @@ typedef enum { typedef struct _FlutterEngine* FLUTTER_API_SYMBOL(FlutterEngine); +/// Unique identifier for views. +/// +/// View IDs are generated by the embedder and are +/// opaque to the engine; the engine does not interpret view IDs in any way. +typedef int64_t FlutterViewId; + typedef struct { /// horizontal scale factor double scaleX; @@ -676,9 +689,13 @@ typedef struct { FlutterMetalCommandQueueHandle present_command_queue; /// The callback that gets invoked when the engine requests the embedder for a /// texture to render to. + /// + /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. FlutterMetalTextureCallback get_next_drawable_callback; /// The callback presented to the embedder to present a fully populated metal /// texture to the user. + /// + /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. FlutterMetalPresentCallback present_drawable_callback; /// When the embedder specifies that a texture has a frame available, the /// engine will call this method (on an internal engine managed thread) so @@ -748,6 +765,11 @@ typedef struct { /// The queue family index of the VkQueue supplied in the next field. uint32_t queue_family_index; /// VkQueue handle. + /// The queue should not be used without protection from a mutex to make sure + /// it is not used simultaneously with other threads. That mutex should match + /// the one injected via the |get_instance_proc_address_callback|. + /// There is a proposal to remove the need for the mutex at + /// https://github.com/flutter/flutter/issues/134573. FlutterVulkanQueueHandle queue; /// The number of instance extensions available for enumerating in the next /// field. @@ -771,6 +793,12 @@ typedef struct { /// For example: VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME const char** enabled_device_extensions; /// The callback invoked when resolving Vulkan function pointers. + /// At a bare minimum this should be used to swap out any calls that operate + /// on vkQueue's for threadsafe variants that obtain locks for their duration. + /// The functions to swap out are "vkQueueSubmit" and "vkQueueWaitIdle". An + /// example of how to do that can be found in the test + /// "EmbedderTest.CanSwapOutVulkanCalls" unit-test in + /// //shell/platform/embedder/tests/embedder_vk_unittests.cc. FlutterVulkanInstanceProcAddressCallback get_instance_proc_address_callback; /// The callback invoked when the engine requests a VkImage from the embedder /// for rendering the next frame. @@ -805,6 +833,11 @@ typedef struct { }; } FlutterRendererConfig; +/// Display refers to a graphics hardware system consisting of a framebuffer, +/// typically a monitor or a screen. This ID is unique per display and is +/// stable until the Flutter application restarts. +typedef uint64_t FlutterEngineDisplayId; + typedef struct { /// The size of this struct. Must be sizeof(FlutterWindowMetricsEvent). size_t struct_size; @@ -826,8 +859,108 @@ typedef struct { double physical_view_inset_bottom; /// Left inset of window. double physical_view_inset_left; + /// The identifier of the display the view is rendering on. + FlutterEngineDisplayId display_id; + /// The view that this event is describing. + int64_t view_id; } FlutterWindowMetricsEvent; +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterAddViewResult). + size_t struct_size; + + /// True if the add view operation succeeded. + bool added; + + /// The |FlutterAddViewInfo.user_data|. + void* user_data; +} FlutterAddViewResult; + +/// The callback invoked by the engine when the engine has attempted to add a +/// view. +/// +/// The |FlutterAddViewResult| is only guaranteed to be valid during this +/// callback. +typedef void (*FlutterAddViewCallback)(const FlutterAddViewResult* result); + +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterAddViewInfo). + size_t struct_size; + + /// The identifier for the view to add. This must be unique. + FlutterViewId view_id; + + /// The view's properties. + /// + /// The metric's |view_id| must match this struct's |view_id|. + const FlutterWindowMetricsEvent* view_metrics; + + /// A baton that is not interpreted by the engine in any way. It will be given + /// back to the embedder in |add_view_callback|. Embedder resources may be + /// associated with this baton. + void* user_data; + + /// Called once the engine has attempted to add the view. This callback is + /// required. + /// + /// The embedder/app must not use the view until the callback is invoked with + /// an `added` value of `true`. + /// + /// This callback is invoked on an internal engine managed thread. Embedders + /// must re-thread if necessary. + FlutterAddViewCallback add_view_callback; +} FlutterAddViewInfo; + +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterRemoveViewResult). + size_t struct_size; + + /// True if the remove view operation succeeded. + bool removed; + + /// The |FlutterRemoveViewInfo.user_data|. + void* user_data; +} FlutterRemoveViewResult; + +/// The callback invoked by the engine when the engine has attempted to remove +/// a view. +/// +/// The |FlutterRemoveViewResult| is only guaranteed to be valid during this +/// callback. +typedef void (*FlutterRemoveViewCallback)( + const FlutterRemoveViewResult* /* result */); + +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterRemoveViewInfo). + size_t struct_size; + + /// The identifier for the view to remove. + /// + /// The implicit view cannot be removed if it is enabled. + FlutterViewId view_id; + + /// A baton that is not interpreted by the engine in any way. + /// It will be given back to the embedder in |remove_view_callback|. + /// Embedder resources may be associated with this baton. + void* user_data; + + /// Called once the engine has attempted to remove the view. + /// This callback is required. + /// + /// The embedder must not destroy the underlying surface until the callback is + /// invoked with a `removed` value of `true`. + /// + /// This callback is invoked on an internal engine managed thread. + /// Embedders must re-thread if necessary. + /// + /// The |result| argument will be deallocated when the callback returns. + FlutterRemoveViewCallback remove_view_callback; +} FlutterRemoveViewInfo; + /// The phase of the pointer event. typedef enum { kCancel, @@ -896,7 +1029,6 @@ typedef enum { kFlutterPointerSignalKindScroll, kFlutterPointerSignalKindScrollInertiaCancel, kFlutterPointerSignalKindScale, - kFlutterPointerSignalKindStylusAuxiliaryAction, } FlutterPointerSignalKind; typedef struct { @@ -935,6 +1067,8 @@ typedef struct { double scale; /// The rotation of the pan/zoom in radians, where 0.0 is the initial angle. double rotation; + /// The identifier of the view that received the pointer event. + FlutterViewId view_id; } FlutterPointerEvent; typedef enum { @@ -943,6 +1077,14 @@ typedef enum { kFlutterKeyEventTypeRepeat, } FlutterKeyEventType; +typedef enum { + kFlutterKeyEventDeviceTypeKeyboard = 1, + kFlutterKeyEventDeviceTypeDirectionalPad, + kFlutterKeyEventDeviceTypeGamepad, + kFlutterKeyEventDeviceTypeJoystick, + kFlutterKeyEventDeviceTypeHdmi, +} FlutterKeyEventDeviceType; + /// A structure to represent a key event. /// /// Sending `FlutterKeyEvent` via `FlutterEngineSendKeyEvent` results in a @@ -1006,6 +1148,8 @@ typedef struct { /// An event being synthesized means that the `timestamp` might greatly /// deviate from the actual time when the event occurs physically. bool synthesized; + /// The source device for the key event. + FlutterKeyEventDeviceType device_type; } FlutterKeyEvent; typedef void (*FlutterKeyEventCallback)(bool /* handled */, @@ -1049,6 +1193,57 @@ typedef int64_t FlutterPlatformViewIdentifier; FLUTTER_EXPORT extern const int32_t kFlutterSemanticsNodeIdBatchEnd; +// The enumeration of possible string attributes that affect how assistive +// technologies announce a string. +// +// See dart:ui's implementers of the StringAttribute abstract class. +typedef enum { + // Indicates the string should be announced character by character. + kSpellOut, + // Indicates the string should be announced using the specified locale. + kLocale, +} FlutterStringAttributeType; + +// Indicates the assistive technology should announce out the string character +// by character. +// +// See dart:ui's SpellOutStringAttribute. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterSpellOutStringAttribute). + size_t struct_size; +} FlutterSpellOutStringAttribute; + +// Indicates the assistive technology should announce the string using the +// specified locale. +// +// See dart:ui's LocaleStringAttribute. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterLocaleStringAttribute). + size_t struct_size; + // The locale of this attribute. + const char* locale; +} FlutterLocaleStringAttribute; + +// Indicates how the assistive technology should treat the string. +// +// See dart:ui's StringAttribute. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterStringAttribute). + size_t struct_size; + // The position this attribute starts. + size_t start; + // The next position after the attribute ends. + size_t end; + /// The type of the attribute described by the subsequent union. + FlutterStringAttributeType type; + union { + // Indicates the string should be announced character by character. + const FlutterSpellOutStringAttribute* spell_out; + // Indicates the string should be announced using the specified locale. + const FlutterLocaleStringAttribute* locale; + }; +} FlutterStringAttribute; + /// A node that represents some semantic data. /// /// The semantics tree is maintained during the semantics phase of the pipeline @@ -1200,6 +1395,31 @@ typedef struct { FlutterPlatformViewIdentifier platform_view_id; /// A textual tooltip attached to the node. const char* tooltip; + // The number of string attributes associated with the `label`. + size_t label_attribute_count; + // Array of string attributes associated with the `label`. + // Has length `label_attribute_count`. + const FlutterStringAttribute** label_attributes; + // The number of string attributes associated with the `hint`. + size_t hint_attribute_count; + // Array of string attributes associated with the `hint`. + // Has length `hint_attribute_count`. + const FlutterStringAttribute** hint_attributes; + // The number of string attributes associated with the `value`. + size_t value_attribute_count; + // Array of string attributes associated with the `value`. + // Has length `value_attribute_count`. + const FlutterStringAttribute** value_attributes; + // The number of string attributes associated with the `increased_value`. + size_t increased_value_attribute_count; + // Array of string attributes associated with the `increased_value`. + // Has length `increased_value_attribute_count`. + const FlutterStringAttribute** increased_value_attributes; + // The number of string attributes associated with the `decreased_value`. + size_t decreased_value_attribute_count; + // Array of string attributes associated with the `decreased_value`. + // Has length `decreased_value_attribute_count`. + const FlutterStringAttribute** decreased_value_attributes; } FlutterSemanticsNode2; /// `FlutterSemanticsCustomAction` ID used as a sentinel to signal the end of a @@ -1311,6 +1531,20 @@ typedef void (*FlutterUpdateSemanticsCallback2)( const FlutterSemanticsUpdate2* /* semantics update */, void* /* user data*/); +/// An update to whether a message channel has a listener set or not. +typedef struct { + // The size of the struct. Must be sizeof(FlutterChannelUpdate). + size_t struct_size; + /// The name of the channel. + const char* channel; + /// True if a listener has been set, false if one has been cleared. + bool listening; +} FlutterChannelUpdate; + +typedef void (*FlutterChannelUpdateCallback)( + const FlutterChannelUpdate* /* channel update */, + void* /* user data */); + typedef struct _FlutterTaskRunner* FlutterTaskRunner; typedef struct { @@ -1548,6 +1782,9 @@ typedef struct { size_t struct_size; /// The size of the render target the engine expects to render into. FlutterSize size; + /// The identifier for the view that the engine will use this backing store to + /// render into. + FlutterViewId view_id; } FlutterBackingStoreConfig; typedef enum { @@ -1558,6 +1795,27 @@ typedef enum { kFlutterLayerContentTypePlatformView, } FlutterLayerContentType; +/// A region represented by a collection of non-overlapping rectangles. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterRegion). + size_t struct_size; + /// Number of rectangles in the region. + size_t rects_count; + /// The rectangles that make up the region. + FlutterRect* rects; +} FlutterRegion; + +/// Contains additional information about the backing store provided +/// during presentation to the embedder. +typedef struct { + size_t struct_size; + + /// The area of the backing store that contains Flutter contents. Pixels + /// outside of this area are transparent and the embedder may choose not + /// to render them. Coordinates are in physical pixels. + FlutterRegion* paint_region; +} FlutterBackingStorePresentInfo; + typedef struct { /// This size of this struct. Must be sizeof(FlutterLayer). size_t struct_size; @@ -1577,8 +1835,34 @@ typedef struct { FlutterPoint offset; /// The size of the layer (in physical pixels). FlutterSize size; + + /// Extra information for the backing store that the embedder may + /// use during presentation. + FlutterBackingStorePresentInfo* backing_store_present_info; + + // Time in nanoseconds at which this frame is scheduled to be presented. 0 if + // not known. See FlutterEngineGetCurrentTime(). + uint64_t presentation_time; } FlutterLayer; +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterPresentViewInfo). + size_t struct_size; + + /// The identifier of the target view. + FlutterViewId view_id; + + /// The layers that should be composited onto the view. + const FlutterLayer** layers; + + /// The count of layers. + size_t layers_count; + + /// The |FlutterCompositor.user_data|. + void* user_data; +} FlutterPresentViewInfo; + typedef bool (*FlutterBackingStoreCreateCallback)( const FlutterBackingStoreConfig* config, FlutterBackingStore* backing_store_out, @@ -1592,13 +1876,20 @@ typedef bool (*FlutterLayersPresentCallback)(const FlutterLayer** layers, size_t layers_count, void* user_data); +/// The callback invoked when the embedder should present to a view. +/// +/// The |FlutterPresentViewInfo| will be deallocated once the callback returns. +typedef bool (*FlutterPresentViewCallback)( + const FlutterPresentViewInfo* /* present info */); + typedef struct { /// This size of this struct. Must be sizeof(FlutterCompositor). size_t struct_size; /// A baton that in not interpreted by the engine in any way. If it passed /// back to the embedder in `FlutterCompositor.create_backing_store_callback`, - /// `FlutterCompositor.collect_backing_store_callback` and - /// `FlutterCompositor.present_layers_callback` + /// `FlutterCompositor.collect_backing_store_callback`, + /// `FlutterCompositor.present_layers_callback`, and + /// `FlutterCompositor.present_view_callback`. void* user_data; /// A callback invoked by the engine to obtain a backing store for a specific /// `FlutterLayer`. @@ -1607,15 +1898,38 @@ typedef struct { /// `FlutterBackingStore::struct_size` when specifying a new backing store to /// the engine. This only matters if the embedder expects to be used with /// engines older than the version whose headers it used during compilation. + /// + /// The callback should return true if the operation was successful. FlutterBackingStoreCreateCallback create_backing_store_callback; /// A callback invoked by the engine to release the backing store. The /// embedder may collect any resources associated with the backing store. + /// + /// The callback should return true if the operation was successful. FlutterBackingStoreCollectCallback collect_backing_store_callback; /// Callback invoked by the engine to composite the contents of each layer - /// onto the screen. + /// onto the implicit view. + /// + /// DEPRECATED: Use `present_view_callback` to support multiple views. + /// If this callback is provided, `FlutterEngineAddView` and + /// `FlutterEngineRemoveView` should not be used. + /// + /// Only one of `present_layers_callback` and `present_view_callback` may be + /// provided. Providing both is an error and engine initialization will + /// terminate. + /// + /// The callback should return true if the operation was successful. FlutterLayersPresentCallback present_layers_callback; /// Avoid caching backing stores provided by this compositor. bool avoid_backing_store_cache; + /// Callback invoked by the engine to composite the contents of each layer + /// onto the specified view. + /// + /// Only one of `present_layers_callback` and `present_view_callback` may be + /// provided. Providing both is an error and engine initialization will + /// terminate. + /// + /// The callback should return true if the operation was successful. + FlutterPresentViewCallback present_view_callback; } FlutterCompositor; typedef struct { @@ -1654,11 +1968,6 @@ typedef const FlutterLocale* (*FlutterComputePlatformResolvedLocaleCallback)( const FlutterLocale** /* supported_locales*/, size_t /* Number of locales*/); -/// Display refers to a graphics hardware system consisting of a framebuffer, -/// typically a monitor or a screen. This ID is unique per display and is -/// stable until the Flutter application restarts. -typedef uint64_t FlutterEngineDisplayId; - typedef struct { /// This size of this struct. Must be sizeof(FlutterDisplay). size_t struct_size; @@ -1674,6 +1983,16 @@ typedef struct { /// This represents the refresh period in frames per second. This value may be /// zero if the device is not running or unavailable or unknown. double refresh_rate; + + /// The width of the display, in physical pixels. + size_t width; + + /// The height of the display, in physical pixels. + size_t height; + + /// The pixel ratio of the display, which is used to convert physical pixels + /// to logical pixels. + double device_pixel_ratio; } FlutterEngineDisplay; /// The update type parameter that is passed to @@ -1920,6 +2239,10 @@ typedef struct { /// `update_semantics_callback`, and /// `update_semantics_callback2` may be provided; the others /// should be set to null. + /// + /// This callback is incompatible with multiple views. If this + /// callback is provided, `FlutterEngineAddView` and + /// `FlutterEngineRemoveView` should not be used. FlutterUpdateSemanticsNodeCallback update_semantics_node_callback; /// The legacy callback invoked by the engine in order to give the embedder /// the chance to respond to updates to semantics custom actions from the Dart @@ -1936,6 +2259,10 @@ typedef struct { /// `update_semantics_callback`, and /// `update_semantics_callback2` may be provided; the others /// should be set to null. + /// + /// This callback is incompatible with multiple views. If this + /// callback is provided, `FlutterEngineAddView` and + /// `FlutterEngineRemoveView` should not be used. FlutterUpdateSemanticsCustomActionCallback update_semantics_custom_action_callback; /// Path to a directory used to store data that is cached across runs of a @@ -2085,6 +2412,10 @@ typedef struct { /// `update_semantics_callback`, and /// `update_semantics_callback2` may be provided; the others /// must be set to null. + /// + /// This callback is incompatible with multiple views. If this + /// callback is provided, `FlutterEngineAddView` and + /// `FlutterEngineRemoveView` should not be used. FlutterUpdateSemanticsCallback update_semantics_callback; /// The callback invoked by the engine in order to give the embedder the @@ -2098,10 +2429,17 @@ typedef struct { /// and `update_semantics_callback2` may be provided; the others must be set /// to null. FlutterUpdateSemanticsCallback2 update_semantics_callback2; + + /// The callback invoked by the engine in response to a channel listener + /// being registered on the framework side. The callback is invoked from + /// a task posted to the platform thread. + FlutterChannelUpdateCallback channel_update_callback; } FlutterProjectArgs; #ifndef FLUTTER_ENGINE_NO_PROTOTYPES +// NOLINTBEGIN(google-objc-function-naming) + //------------------------------------------------------------------------------ /// @brief Creates the necessary data structures to launch a Flutter Dart /// application in AOT mode. The data may only be collected after @@ -2243,6 +2581,63 @@ FLUTTER_EXPORT FlutterEngineResult FlutterEngineRunInitialized( FLUTTER_API_SYMBOL(FlutterEngine) engine); +//------------------------------------------------------------------------------ +/// @brief Adds a view. +/// +/// This is an asynchronous operation. The view should not be used +/// until the |info.add_view_callback| is invoked with an |added| +/// value of true. The embedder should prepare resources in advance +/// but be ready to clean up on failure. +/// +/// A frame is scheduled if the operation succeeds. +/// +/// The callback is invoked on a thread managed by the engine. The +/// embedder should re-thread if needed. +/// +/// Attempting to add the implicit view will fail and will return +/// kInvalidArguments. Attempting to add a view with an already +/// existing view ID will fail, and |info.add_view_callback| will be +/// invoked with an |added| value of false. +/// +/// @param[in] engine A running engine instance. +/// @param[in] info The add view arguments. This can be deallocated +/// once |FlutterEngineAddView| returns, before +/// |add_view_callback| is invoked. +/// +/// @return The result of *starting* the asynchronous operation. If +/// `kSuccess`, the |add_view_callback| will be invoked. +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineAddView(FLUTTER_API_SYMBOL(FlutterEngine) + engine, + const FlutterAddViewInfo* info); + +//------------------------------------------------------------------------------ +/// @brief Removes a view. +/// +/// This is an asynchronous operation. The view's resources must not +/// be cleaned up until |info.remove_view_callback| is invoked with +/// a |removed| value of true. +/// +/// The callback is invoked on a thread managed by the engine. The +/// embedder should re-thread if needed. +/// +/// Attempting to remove the implicit view will fail and will return +/// kInvalidArguments. Attempting to remove a view with a +/// non-existent view ID will fail, and |info.remove_view_callback| +/// will be invoked with a |removed| value of false. +/// +/// @param[in] engine A running engine instance. +/// @param[in] info The remove view arguments. This can be deallocated +/// once |FlutterEngineRemoveView| returns, before +/// |remove_view_callback| is invoked. +/// +/// @return The result of *starting* the asynchronous operation. If +/// `kSuccess`, the |remove_view_callback| will be invoked. +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineRemoveView(FLUTTER_API_SYMBOL(FlutterEngine) + engine, + const FlutterRemoveViewInfo* info); + FLUTTER_EXPORT FlutterEngineResult FlutterEngineSendWindowMetricsEvent( FLUTTER_API_SYMBOL(FlutterEngine) engine, @@ -2913,6 +3308,12 @@ typedef FlutterEngineResult (*FlutterEngineSetNextFrameCallbackFnPtr)( FLUTTER_API_SYMBOL(FlutterEngine) engine, VoidCallback callback, void* user_data); +typedef FlutterEngineResult (*FlutterEngineAddViewFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterAddViewInfo* info); +typedef FlutterEngineResult (*FlutterEngineRemoveViewFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterRemoveViewInfo* info); /// Function-pointer-based versions of the APIs above. typedef struct { @@ -2959,6 +3360,8 @@ typedef struct { FlutterEngineNotifyDisplayUpdateFnPtr NotifyDisplayUpdate; FlutterEngineScheduleFrameFnPtr ScheduleFrame; FlutterEngineSetNextFrameCallbackFnPtr SetNextFrameCallback; + FlutterEngineAddViewFnPtr AddView; + FlutterEngineRemoveViewFnPtr RemoveView; } FlutterEngineProcTable; //------------------------------------------------------------------------------ @@ -2973,8 +3376,10 @@ FLUTTER_EXPORT FlutterEngineResult FlutterEngineGetProcAddresses( FlutterEngineProcTable* table); +// NOLINTEND(google-objc-function-naming) + #if defined(__cplusplus) } // extern "C" #endif -#endif // FLUTTER_EMBEDDER_H_ +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_H_ diff --git a/third_party/klib/include/klib/khash.h b/third_party/klib/include/klib/khash.h new file mode 100644 index 00000000..1fa6b346 --- /dev/null +++ b/third_party/klib/include/klib/khash.h @@ -0,0 +1,665 @@ +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos + + 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. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, char) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2013-05-02 (0.2.8): + + * Use quadratic probing. When the capacity is power of 2, stepping function + i*(i+1)/2 guarantees to traverse each bucket. It is better than double + hashing on cache performance and is more robust than linear probing. + + In theory, double hashing should be more robust than quadratic probing. + However, my implementation is probably not for large hash tables, because + the second hash function is closely tied to the first hash function, + which reduce the effectiveness of double hashing. + + Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php + + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.8" + +#include +#include +#include + +/* compiler specific configuration */ + +#if UINT_MAX == 0xffffffffu +typedef unsigned int khint32_t; +#elif ULONG_MAX == 0xffffffffu +typedef unsigned long khint32_t; +#endif + +#if ULONG_MAX == ULLONG_MAX +typedef unsigned long khint64_t; +#else +typedef unsigned long long khint64_t; +#endif + +#ifndef kh_inline + #ifdef _MSC_VER + #define kh_inline __inline + #else + #define kh_inline inline + #endif +#endif /* kh_inline */ + +#ifndef klib_unused + #if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) + #define klib_unused __attribute__((__unused__)) + #else + #define klib_unused + #endif +#endif /* klib_unused */ + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i >> 4] >> ((i & 0xfU) << 1)) & 2) +#define __ac_isdel(flag, i) ((flag[i >> 4] >> ((i & 0xfU) << 1)) & 1) +#define __ac_iseither(flag, i) ((flag[i >> 4] >> ((i & 0xfU) << 1)) & 3) +#define __ac_set_isdel_false(flag, i) (flag[i >> 4] &= ~(1ul << ((i & 0xfU) << 1))) +#define __ac_set_isempty_false(flag, i) (flag[i >> 4] &= ~(2ul << ((i & 0xfU) << 1))) +#define __ac_set_isboth_false(flag, i) (flag[i >> 4] &= ~(3ul << ((i & 0xfU) << 1))) +#define __ac_set_isdel_true(flag, i) (flag[i >> 4] |= 1ul << ((i & 0xfU) << 1)) + +#define __ac_fsize(m) ((m) < 16 ? 1 : (m) >> 4) + +#ifndef kroundup32 + #define kroundup32(x) (--(x), (x) |= (x) >> 1, (x) |= (x) >> 2, (x) |= (x) >> 4, (x) |= (x) >> 8, (x) |= (x) >> 16, ++(x)) +#endif + +#ifndef kcalloc + #define kcalloc(N, Z) calloc(N, Z) +#endif +#ifndef kmalloc + #define kmalloc(Z) malloc(Z) +#endif +#ifndef krealloc + #define krealloc(P, Z) realloc(P, Z) +#endif +#ifndef kfree + #define kfree(P) free(P) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ + typedef struct kh_##name##_s { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + khint32_t *flags; \ + khkey_t *keys; \ + khval_t *vals; \ + } kh_##name##_t; + +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ + extern kh_##name##_t *kh_init_##name(void); \ + extern void kh_destroy_##name(kh_##name##_t *h); \ + extern void kh_clear_##name(kh_##name##_t *h); \ + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ + extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + SCOPE kh_##name##_t *kh_init_##name(void) { \ + return (kh_##name##_t *) kcalloc(1, sizeof(kh_##name##_t)); \ + } \ + SCOPE void kh_destroy_##name(kh_##name##_t *h) { \ + if (h) { \ + kfree((void *) h->keys); \ + kfree(h->flags); \ + kfree((void *) h->vals); \ + kfree(h); \ + } \ + } \ + SCOPE void kh_clear_##name(kh_##name##_t *h) { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) { \ + if (h->n_buckets) { \ + khint_t k, i, last, mask, step = 0; \ + mask = h->n_buckets - 1; \ + k = __hash_func(key); \ + i = k & mask; \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + i = (i + (++step)) & mask; \ + if (i == last) \ + return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i) ? h->n_buckets : i; \ + } else \ + return 0; \ + } \ + SCOPE int kh_resize_##name( \ + kh_##name##_t *h, \ + khint_t new_n_buckets \ + ) { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ + khint32_t *new_flags = 0; \ + khint_t j = 1; \ + { \ + kroundup32(new_n_buckets); \ + if (new_n_buckets < 4) \ + new_n_buckets = 4; \ + if (h->size >= (khint_t) (new_n_buckets * __ac_HASH_UPPER + 0.5)) \ + j = 0; /* requested size is too small */ \ + else { /* hash table size to be changed (shrink or expand); rehash */ \ + new_flags = (khint32_t *) kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (!new_flags) \ + return -1; \ + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (h->n_buckets < new_n_buckets) { /* expand */ \ + khkey_t *new_keys = (khkey_t *) krealloc((void *) h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (!new_keys) { \ + kfree(new_flags); \ + return -1; \ + } \ + h->keys = new_keys; \ + if (kh_is_map) { \ + khval_t *new_vals = (khval_t *) krealloc((void *) h->vals, new_n_buckets * sizeof(khval_t)); \ + if (!new_vals) { \ + kfree(new_flags); \ + return -1; \ + } \ + h->vals = new_vals; \ + } \ + } /* otherwise shrink */ \ + } \ + } \ + if (j) { /* rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + khint_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (kh_is_map) \ + val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ + khint_t k, i, step = 0; \ + k = __hash_func(key); \ + i = k & new_mask; \ + while (!__ac_isempty(new_flags, i)) \ + i = (i + (++step)) & new_mask; \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ + { \ + khkey_t tmp = h->keys[i]; \ + h->keys[i] = key; \ + key = tmp; \ + } \ + if (kh_is_map) { \ + khval_t tmp = h->vals[i]; \ + h->vals[i] = val; \ + val = tmp; \ + } \ + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ + } else { /* write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (kh_is_map) \ + h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ + /* NOLINTNEXTLINE(bugprone-suspicious-realloc-usage) */ \ + h->keys = (khkey_t *) krealloc((void *) h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (kh_is_map) \ + /* NOLINTNEXTLINE(bugprone-suspicious-realloc-usage) */ \ + h->vals = (khval_t *) krealloc((void *) h->vals, new_n_buckets * sizeof(khval_t)); \ + } \ + kfree(h->flags); /* free the working space */ \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t) (h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + return 0; \ + } \ + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ + if (h->n_buckets > (h->size << 1)) { \ + if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ + *ret = -1; \ + return h->n_buckets; \ + } \ + } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ + *ret = -1; \ + return h->n_buckets; \ + } \ + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ + x = site = h->n_buckets; \ + k = __hash_func(key); \ + i = k & mask; \ + if (__ac_isempty(h->flags, i)) \ + x = i; /* for speed up */ \ + else { \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) \ + site = i; \ + i = (i + (++step)) & mask; \ + if (i == last) { \ + x = site; \ + break; \ + } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) \ + x = site; \ + else \ + x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else \ + *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ + return x; \ + } \ + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_PROTOTYPES(name, khkey_t, khval_t) + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key) >> 33 ^ (key) ^ (key) << 11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static kh_inline khint_t __ac_X31_hash_string(const char *s) { + khint_t h = (khint_t) *s; + if (h) + for (++s; *s; ++s) + h = (h << 5) - h + (khint_t) *s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static kh_inline khint_t __ac_Wang_hash(khint_t key) { + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t) key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: -1 if the operation failed; + 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/*! @function + @abstract Iterate over the entries in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param kvar Variable to which key will be assigned + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach(h, kvar, vvar, code) \ + { \ + khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h, __i)) \ + continue; \ + (kvar) = kh_key(h, __i); \ + (vvar) = kh_val(h, __i); \ + code; \ + } \ + } + +/*! @function + @abstract Iterate over the values in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach_value(h, vvar, code) \ + { \ + khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h, __i)) \ + continue; \ + (vvar) = kh_val(h, __i); \ + code; \ + } \ + } + +/* More convenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash set containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ diff --git a/third_party/klib/include/klib/kvec.h b/third_party/klib/include/klib/kvec.h new file mode 100644 index 00000000..61c82c6b --- /dev/null +++ b/third_party/klib/include/klib/kvec.h @@ -0,0 +1,123 @@ +// The MIT License +// +// Copyright (c) 2008, by Attractive Chaos +// +// 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. + +// An example: +// +// #include "kvec.h" +// int main() { +// kvec_t(int) array = KV_INITIAL_VALUE; +// kv_push(array, 10); // append +// kv_a(array, 20) = 5; // dynamic +// kv_A(array, 20) = 4; // static +// kv_destroy(array); +// return 0; +// } + +#ifndef NVIM_LIB_KVEC_H +#define NVIM_LIB_KVEC_H + +#include +#include + +#define kv_roundup32(x) ((--(x)), ((x) |= (x) >> 1, (x) |= (x) >> 2, (x) |= (x) >> 4, (x) |= (x) >> 8, (x) |= (x) >> 16), (++(x))) + +#define KV_INITIAL_VALUE { .size = 0, .capacity = 0, .items = NULL } + +#define kvec_t(type) \ + struct { \ + size_t size; \ + size_t capacity; \ + type *items; \ + } + +#define kv_init(v) ((v).size = (v).capacity = 0, (v).items = 0) +#define kv_destroy(v) \ + do { \ + free((v).items); \ + kv_init(v); \ + } while (0) +#define kv_A(v, i) ((v).items[(i)]) +#define kv_pop(v) ((v).items[--(v).size]) +#define kv_size(v) ((v).size) +#define kv_max(v) ((v).capacity) +#define kv_Z(v, i) kv_A(v, kv_size(v) - (i) - 1) +#define kv_last(v) kv_Z(v, 0) + +/// Drop last n items from kvec without resizing +/// +/// Previously spelled as `(void)kv_pop(v)`, repeated n times. +/// +/// @param[out] v Kvec to drop items from. +/// @param[in] n Number of elements to drop. +#define kv_drop(v, n) ((v).size -= (n)) + +// NOLINTNEXTLINE(bugprone-suspicious-realloc-usage,bugprone-sizeof-expression) +#define kv_resize(v, s) ((v).capacity = (s), (v).items = realloc((v).items, sizeof((v).items[0]) * (v).capacity)) + +#define kv_resize_full(v) kv_resize(v, (v).capacity ? (v).capacity << 1 : 8) + +#define kv_copy(v1, v0) \ + do { \ + if ((v1).capacity < (v0).size) { \ + kv_resize(v1, (v0).size); \ + } \ + (v1).size = (v0).size; \ + memcpy((v1).items, (v0).items, sizeof((v1).items[0]) * (v0).size); \ + } while (0) + +/// fit at least "len" more items +#define kv_ensure_space(v, len) \ + do { \ + if ((v).capacity < (v).size + len) { \ + (v).capacity = (v).size + len; \ + kv_roundup32((v).capacity); \ + kv_resize((v), (v).capacity); \ + } \ + } while (0) + +#define kv_concat_len(v, data, len) \ + if (len > 0) { \ + kv_ensure_space(v, len); \ + assert((v).items); \ + memcpy((v).items + (v).size, data, sizeof((v).items[0]) * len); \ + (v).size = (v).size + len; \ + } + +#define kv_concat(v, str) kv_concat_len(v, str, strlen(str)) +#define kv_splice(v1, v0) kv_concat_len(v1, (v0).items, (v0).size) + +#define kv_pushp(v) ((((v).size == (v).capacity) ? (kv_resize_full(v), 0) : 0), ((v).items + ((v).size++))) + +#define kv_push(v, x) (*kv_pushp(v) = (x)) + +#define kv_pushp_c(v) ((v).items + ((v).size++)) +#define kv_push_c(v, x) (*kv_pushp_c(v) = (x)) + +#define kv_a(v, i) \ + (*(((v).capacity <= (size_t) (i) ? \ + ((v).capacity = (v).size = (i) + 1, kv_roundup32((v).capacity), kv_resize((v), (v).capacity), 0UL) : \ + ((v).size <= (size_t) (i) ? (v).size = (i) + 1 : 0UL)), \ + &(v).items[(i)])) + +#endif // NVIM_LIB_KVEC_H diff --git a/src/util/dynarray.h b/third_party/mesa3d/include/mesa3d/dynarray.h similarity index 99% rename from src/util/dynarray.h rename to third_party/mesa3d/include/mesa3d/dynarray.h index 2d9f2101..220ea57f 100644 --- a/src/util/dynarray.h +++ b/third_party/mesa3d/include/mesa3d/dynarray.h @@ -32,8 +32,6 @@ #include #include -#include "macros.h" - #ifdef __cplusplus extern "C" { #endif diff --git a/tools/roll_embedder_header.sh b/tools/roll_embedder_header.sh new file mode 100755 index 00000000..518f0188 --- /dev/null +++ b/tools/roll_embedder_header.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +ENGINE_VERSION="$(curl -s https://raw.githubusercontent.com/flutter/flutter/stable/bin/internal/engine.version)" + +if [ ! -f "third_party/flutter_embedder_header/include/flutter_embedder.h" ]; then + echo "Incorrect working directory. Please launch this script with the flutter-pi repo root as the working directory, using 'tools/roll_embedder_header.sh'.". + exit 1 +fi + +curl -o third_party/flutter_embedder_header/include/flutter_embedder.h "https://raw.githubusercontent.com/flutter/engine/$ENGINE_VERSION/shell/platform/embedder/embedder.h" +echo "$ENGINE_VERSION" > third_party/flutter_embedder_header/engine.version