From 80321d69d708083a239329c9a26cedc2f26c1e00 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 15 Sep 2024 15:22:44 +0000 Subject: [PATCH 01/27] Add CodeChecker static analysis workflow - add codechecker workflow - enable `-pedantic` - fix a lot of warnings - only report error from `gbm_surface_create_with_modifiers` if `gbm_surface_create` fails too --- .codechecker.json | 22 + .codechecker.skipfile | 2 + .github/workflows/codeql-buildscript.sh | 25 +- .github/workflows/codeql.yml | 126 --- .../{fail_on_error.py => fail_on_warning.py} | 15 +- .github/workflows/static-analysis.yml | 58 ++ .gitignore | 1 + CMakeLists.txt | 6 +- CMakePresets.json | 45 +- src/compositor_ng.c | 25 +- src/cursor.c | 16 +- src/cursor.h | 2 +- src/dmabuf_surface.c | 10 +- src/egl.h | 3 +- src/egl_gbm_render_surface.c | 74 +- src/filesystem_layout.c | 9 +- src/flutter-pi.c | 98 ++- src/flutter_embedder.h | 11 + src/gl_renderer.c | 41 +- src/gl_renderer.h | 6 +- src/locales.c | 16 +- src/modesetting.c | 60 +- src/modesetting.h | 4 +- src/notifier_listener.c | 2 +- src/notifier_listener.h | 2 +- src/pixel_format.c | 2 +- src/pixel_format.h | 2 +- src/platformchannel.c | 728 ++++++++++++------ src/platformchannel.h | 5 +- src/pluginregistry.c | 22 +- src/pluginregistry.h | 44 +- src/plugins/audioplayers/player.c | 35 +- src/plugins/audioplayers/plugin.c | 8 +- src/plugins/gstreamer_video_player/frame.c | 34 +- src/plugins/gstreamer_video_player/player.c | 14 +- src/plugins/gstreamer_video_player/plugin.c | 114 ++- src/plugins/raw_keyboard.c | 7 +- src/plugins/sentry/sentry.c | 15 +- src/plugins/text_input.c | 23 +- src/texture_registry.c | 8 +- src/tracer.c | 2 +- src/tracer.h | 4 +- src/user_input.c | 27 +- src/util/asserts.h | 13 +- src/util/collection.c | 4 +- src/util/collection.h | 4 +- src/util/lock_ops.h | 14 + src/util/macros.h | 35 +- src/util/uuid.h | 4 +- src/vk_renderer.c | 4 +- src/vk_renderer.h | 2 +- src/vulkan.c | 81 ++ src/vulkan.h | 72 +- src/window.c | 31 +- src/window.h | 2 +- test/flutterpi_test.c | 26 +- test/platformchannel_test.c | 118 +-- .../flutter_embedder.h | 0 .../mesa3d/include/mesa3d}/dynarray.h | 2 - 59 files changed, 1365 insertions(+), 820 deletions(-) create mode 100644 .codechecker.json create mode 100644 .codechecker.skipfile mode change 100644 => 100755 .github/workflows/codeql-buildscript.sh delete mode 100644 .github/workflows/codeql.yml rename .github/workflows/{fail_on_error.py => fail_on_warning.py} (68%) create mode 100644 .github/workflows/static-analysis.yml create mode 100644 src/flutter_embedder.h create mode 100644 src/vulkan.c rename third_party/flutter_embedder_header/include/{ => flutter_embedder_header}/flutter_embedder.h (100%) rename {src/util => third_party/mesa3d/include/mesa3d}/dynarray.h (99%) 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/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..70c90d9d --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,58 @@ +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 }} + EOF + + 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..e507dca1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,13 +162,14 @@ 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/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 +238,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 diff --git a/CMakePresets.json b/CMakePresets.json index 866cc51f..31106e29 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -7,15 +7,58 @@ "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, + "TRY_ENABLE_VULKAN": false, + "ENABLE_VULKAN": 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 } }, + { + "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++" + } + }, { "name": "cross-aarch64-default", "displayName": "OpenGL AArch64 cross-build", @@ -41,4 +84,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/compositor_ng.c b/src/compositor_ng.c index d6f6718b..dcbf6b9c 100644 --- a/src/compositor_ng.c +++ b/src/compositor_ng.c @@ -20,6 +20,7 @@ #include #include +#include #include #include "cursor.h" @@ -33,7 +34,6 @@ #include "surface.h" #include "tracer.h" #include "util/collection.h" -#include "util/dynarray.h" #include "util/logging.h" #include "util/refcounting.h" #include "window.h" @@ -231,7 +231,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 +262,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. @@ -348,8 +348,9 @@ static int compositor_push_fl_layers(struct compositor *compositor, size_t n_fl_ /// TODO: Implement layer->surface = compositor_get_view_by_id_locked(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))); + layer->surface = CAST_SURFACE( + dummy_render_surface_new(compositor->tracer, VEC2I((int) fl_layer->size.width, (int) fl_layer->size.height)) + ); } #else // in release mode, we just assume the id is valid. @@ -384,10 +385,6 @@ static int compositor_push_fl_layers(struct compositor *compositor, size_t n_fl_ fl_layer_composition_unref(composition); - return 0; - - //fail_free_composition: - //fl_layer_composition_unref(composition); return ok; } @@ -556,14 +553,14 @@ void compositor_set_cursor( struct view_geometry viewgeo = window_get_view_geometry(compositor->main_window); - if (compositor->cursor_pos.x < 0.0f) { - compositor->cursor_pos.x = 0.0f; + if (compositor->cursor_pos.x < 0.0) { + compositor->cursor_pos.x = 0.0; } else if (compositor->cursor_pos.x > viewgeo.view_size.x) { compositor->cursor_pos.x = viewgeo.view_size.x; } - if (compositor->cursor_pos.y < 0.0f) { - compositor->cursor_pos.y = 0.0f; + if (compositor->cursor_pos.y < 0.0) { + compositor->cursor_pos.y = 0.0; } else if (compositor->cursor_pos.y > viewgeo.view_size.y) { compositor->cursor_pos.y = viewgeo.view_size.y; } 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..fa4990a8 100644 --- a/src/dmabuf_surface.c +++ b/src/dmabuf_surface.c @@ -68,7 +68,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 +298,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/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..74b0ae01 100644 --- a/src/egl_gbm_render_surface.c +++ b/src/egl_gbm_render_surface.c @@ -146,6 +146,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 +158,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 +172,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 +395,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 +420,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_crtc_any_plane_supports_format(meta->drmdev, crtc, 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 +440,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,16 +453,16 @@ 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_crtc_any_plane_supports_format(meta->drmdev, crtc, pixfmt_opaque(egl_surface->pixel_format))) { + uint32_t opaque_fb_id = drmdev_add_fb_from_gbm_bo( + meta->drmdev, bo, /* cast_opaque */ true ); if (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; @@ -463,11 +475,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 +503,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 +567,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 +667,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 ); 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..c9a87468 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -394,7 +394,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 +409,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; @@ -546,7 +548,7 @@ 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.0 / compositor_get_refresh_rate(flutterpi->compositor)); if (flutterpi_runs_platform_tasks_on_current_thread(req->flutterpi)) { TRACER_INSTANT(req->flutterpi->tracer, "FlutterEngineOnVsync"); @@ -768,7 +770,7 @@ static int on_execute_flutter_task(void *userdata) { result = flutterpi->flutter.procs.RunTask(flutterpi->flutter.engine, task); if (result != kSuccess) { - LOG_ERROR("Error running platform task. FlutterEngineRunTask: %d\n", result); + LOG_ERROR("Error running platform task. FlutterEngineRunTask: %u\n", result); free(task); return EINVAL; } @@ -1039,7 +1041,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, false, VEC2F(0, 0)); } void flutterpi_trace_event_instant(struct flutterpi *flutterpi, const char *name) { @@ -1165,19 +1167,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) { @@ -1450,9 +1453,9 @@ static int flutterpi_run(struct flutterpi *flutterpi) { 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.width = (size_t) geometry.view_size.x; + window_metrics_event.height = (size_t) geometry.view_size.y; + window_metrics_event.pixel_ratio = (double) geometry.device_pixel_ratio; window_metrics_event.left = 0; window_metrics_event.top = 0; window_metrics_event.physical_view_inset_top = 0; @@ -1919,7 +1922,7 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin "%s", usage ); - return false; + goto fail; } break; @@ -1933,7 +1936,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 +1947,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 +1976,7 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin "%s", usage ); - return false; + goto fail; valid_format: break; @@ -1981,7 +1984,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 +1998,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,7 +2017,7 @@ 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]); @@ -2026,6 +2033,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) { @@ -2108,7 +2126,7 @@ static struct drmdev *find_drmdev(struct libseat *libseat) { ASSERT_EQUALS(libseat, NULL); #endif - 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; @@ -2165,12 +2183,12 @@ static struct drmdev *find_drmdev(struct libseat *libseat) { 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; @@ -2318,7 +2336,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { struct tracer *tracer; struct window *window; void *engine_handle; - char *bundle_path, **engine_argv, *desired_videomode; + char **engine_argv, *desired_videomode; int ok, engine_argc, wakeup_fd; fpi = malloc(sizeof *fpi); @@ -2338,15 +2356,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,16 +2377,13 @@ 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; } + fpi->flutter.bundle_path = realpath(cmd_args.bundle_path, NULL); + 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)); @@ -2477,7 +2491,6 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { vk_renderer = vk_renderer_new(); if (vk_renderer == NULL) { LOG_ERROR("Couldn't create vulkan renderer.\n"); - ok = EIO; goto fail_unref_scheduler; } #else @@ -2489,7 +2502,6 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { gl_renderer = gl_renderer_new_from_gbm_device(tracer, gbm_device, cmd_args.has_pixel_format, cmd_args.pixel_format); if (gl_renderer == NULL) { LOG_ERROR("Couldn't create EGL/OpenGL renderer.\n"); - ok = EIO; goto fail_unref_scheduler; } @@ -2592,8 +2604,8 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { fpi, &geometry.display_to_view_transform, &geometry.view_to_display_transform, - geometry.display_size.x, - geometry.display_size.y + (unsigned int) geometry.display_size.x, + (unsigned int) geometry.display_size.y ); if (input == NULL) { LOG_ERROR("Couldn't initialize user input. flutter-pi will run without user input.\n"); @@ -2695,6 +2707,8 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { frame_scheduler_unref(scheduler); window_unref(window); + free(cmd_args.bundle_path); + pthread_mutex_init(&fpi->event_loop_mutex, get_default_mutex_attrs()); fpi->event_loop_thread = pthread_self(); fpi->wakeup_event_loop_fd = wakeup_fd; @@ -2706,7 +2720,6 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { fpi->vk_renderer = vk_renderer; fpi->user_input = input; 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; @@ -2784,6 +2797,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { fail_free_cmd_args: free(cmd_args.bundle_path); + free(cmd_args.desired_videomode); fail_free_fpi: free(fpi); 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/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/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 index d72ff5d3..29a4024f 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -417,8 +417,10 @@ static int fetch_crtc(int drm_fd, int crtc_index, uint32_t crtc_id, struct drm_c prop_info = NULL; } + ASSUME(0 <= crtc_index && crtc_index < 32); + crtc_out->id = crtc->crtc_id; - crtc_out->index = crtc_index; + crtc_out->index = (uint8_t) crtc_index; crtc_out->bitmask = 1u << crtc_index; crtc_out->ids = ids; crtc_out->committed_state.has_mode = crtc->mode_valid; @@ -610,15 +612,16 @@ extern void drmModeFreeFB2(struct _drmModeFB2 *ptr) __attribute__((weak)); 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; + drm_plane_transform_t hardcoded_rotation = PLANE_TRANSFORM_NONE, supported_rotations = PLANE_TRANSFORM_NONE, + committed_rotation = PLANE_TRANSFORM_NONE; + enum drm_blend_mode committed_blend_mode = kNone_DrmBlendMode; + enum drm_plane_type type = kPrimary_DrmPlaneType; 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; + uint16_t committed_alpha = 0; + int64_t min_zpos = 0, max_zpos = 0, hardcoded_zpos = 0, committed_zpos = 0; 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; @@ -843,12 +846,12 @@ static int fetch_plane(int drm_fd, uint32_t plane_id, struct drm_plane *plane_ou plane_out->id = plane->plane_id; plane_out->possible_crtcs = plane->possible_crtcs; plane_out->ids = ids; - plane_out->type = type; + plane_out->type = has_type ? type : kPrimary_DrmPlaneType; plane_out->has_zpos = has_zpos; - plane_out->min_zpos = min_zpos; - plane_out->max_zpos = max_zpos; + plane_out->min_zpos = has_zpos ? min_zpos : 0; + plane_out->max_zpos = has_zpos ? max_zpos : 0; plane_out->has_hardcoded_zpos = has_hardcoded_zpos; - plane_out->hardcoded_zpos = hardcoded_zpos; + plane_out->hardcoded_zpos = has_hardcoded_zpos ? hardcoded_zpos : 0; plane_out->has_rotation = has_rotation; plane_out->supported_rotations = supported_rotations; plane_out->has_hardcoded_rotation = has_hardcoded_rotation; @@ -909,7 +912,7 @@ static int fetch_planes(struct drmdev *drmdev, struct drm_plane **planes_out, si 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_plane(planes + j); } free(planes); return ENOMEM; @@ -931,7 +934,7 @@ static void free_planes(struct drm_plane *planes, size_t n_planes) { free(planes); } -static void assert_rotations_work() { +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); @@ -2265,7 +2268,7 @@ struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uin } if (crtc == NULL) { - LOG_ERROR("Invalid CRTC id: %" PRId32 "\n", crtc_id); + LOG_ERROR("Invalid CRTC id: %" PRIu32 "\n", crtc_id); goto fail_unlock; } @@ -2628,15 +2631,15 @@ UNUSED struct kms_req *kms_req_ref(struct kms_req *req) { } UNUSED void kms_req_unref(struct kms_req *req) { - return kms_req_builder_unref((struct kms_req_builder *) req); + 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); + 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); + 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) { @@ -2726,13 +2729,13 @@ kms_req_commit_common(struct kms_req *req, bool blocking, kms_scanout_cb_t scano struct drm_plane *plane = layer->plane; ASSERT_NOT_NULL(plane); +#ifndef DEBUG if (plane->committed_state.has_format && plane->committed_state.format == layer->layer.format) { needs_set_crtc = false; } else { needs_set_crtc = true; } - -#ifdef DEBUG +#else drmModeFBPtr committed_fb = drmModeGetFB(builder->drmdev->master_fd, plane->committed_state.fb_id); if (committed_fb == NULL) { needs_set_crtc = true; @@ -2913,6 +2916,8 @@ kms_req_commit_common(struct kms_req *req, bool blocking, kms_scanout_cb_t scano goto fail_unref_builder; } + struct drmdev *drmdev = builder->drmdev; + drmdev_on_page_flip_locked( builder->drmdev->fd, (unsigned int) sequence, @@ -2921,16 +2926,22 @@ kms_req_commit_common(struct kms_req *req, bool blocking, kms_scanout_cb_t scano builder->crtc->id, kms_req_ref(req) ); + + drmdev_unlock(drmdev); } else if (blocking) { + struct drmdev *drmdev = builder->drmdev; + // 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); + drmdev_unlock(drmdev); + } else { + drmdev_unlock(builder->drmdev); + } return 0; @@ -2940,8 +2951,15 @@ kms_req_commit_common(struct kms_req *req, bool blocking, kms_scanout_cb_t scano builder->drmdev->per_crtc_state[builder->crtc->index].userdata = NULL; goto fail_unlock; -fail_unref_builder: +fail_unref_builder: { + struct drmdev *drmdev = builder->drmdev; kms_req_builder_unref(builder); + if (mode_blob != NULL) { + drm_mode_blob_destroy(mode_blob); + } + drmdev_unlock(drmdev); + return ok; +} fail_maybe_destroy_mode_blob: if (mode_blob != NULL) diff --git a/src/modesetting.h b/src/modesetting.h index 7e5a9562..9222d79d 100644 --- a/src/modesetting.h +++ b/src/modesetting.h @@ -746,7 +746,7 @@ struct kms_req_builder; struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uint32_t crtc_id); -DECLARE_REF_OPS(kms_req_builder); +DECLARE_REF_OPS(kms_req_builder) /** * @brief Gets the @ref drmdev associated with this KMS request builder. @@ -895,7 +895,7 @@ int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder, */ struct kms_req; -DECLARE_REF_OPS(kms_req); +DECLARE_REF_OPS(kms_req) /** * @brief Build the KMS request builder into an actual, immutable KMS request 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..f78b917b 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -89,6 +89,7 @@ static struct plugin_instance *get_plugin_by_name(struct plugin_registry *regist return instance; } +// clang-format off 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)) { @@ -98,6 +99,7 @@ static struct platch_obj_cb_data *get_cb_data_by_channel_locked(struct plugin_re return NULL; } +// clang-format on struct plugin_registry *plugin_registry_new(struct flutterpi *flutterpi) { struct plugin_registry *reg; @@ -211,7 +213,7 @@ void plugin_registry_add_plugin(struct plugin_registry *registry, const struct f plugin_registry_unlock(registry); } -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; @@ -301,7 +303,7 @@ static int set_receiver_locked( char *channel_dup; ASSERT_MSG((!!callback) != (!!callback_v2), "Exactly one of callback or callback_v2 must be non-NULL."); - ASSERT_MUTEX_LOCKED(registry->lock); + assert_mutex_locked(®istry->lock); data_ptr = get_cb_data_by_channel_locked(registry, channel); if (data_ptr == NULL) { @@ -398,6 +400,16 @@ int plugin_registry_remove_receiver_v2_locked(struct plugin_registry *registry, } list_del(&data->entry); + + // Analyzer thinks get_cb_data_by_channel might still return our data + // after list_del and emits a "use-after-free" warning. + // assert()s can change the assumptions of the analyzer, so we use them here. +#ifdef DEBUG + list_for_each_entry(struct platch_obj_cb_data, data_iter, ®istry->callbacks, entry) { + ASSUME(data_iter != data); + } +#endif + free(data->channel); free(data); @@ -456,7 +468,7 @@ void *plugin_registry_get_plugin_userdata_locked(struct plugin_registry *registr return instance != NULL ? instance->userdata : NULL; } -static void static_plugin_registry_initialize() { +static void static_plugin_registry_initialize(void) { ASSERTED int ok; list_inithead(&static_plugins); @@ -465,7 +477,7 @@ static void static_plugin_registry_initialize() { 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); } @@ -480,7 +492,7 @@ void static_plugin_registry_add_plugin(const struct flutterpi_plugin_v2 *plugin) entry = malloc(sizeof *entry); ASSERT_NOT_NULL(entry); - + entry->plugin = plugin; list_addtail(&entry->entry, &static_plugins); diff --git a/src/pluginregistry.h b/src/pluginregistry.h index 4efe6bf8..b629e720 100644 --- a/src/pluginregistry.h +++ b/src/pluginregistry.h @@ -20,16 +20,6 @@ struct flutterpi; struct plugin_registry; -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 { - 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. @@ -40,6 +30,16 @@ enum plugin_init_result { /// Flutter-pi may decide to abort the startup phase of the whole flutter-pi instance at that point. }; +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 { + const char *name; + plugin_init_t init; + plugin_deinit_t deinit; +}; + struct _FlutterPlatformMessageResponseHandle; typedef struct _FlutterPlatformMessageResponseHandle FlutterPlatformMessageResponseHandle; @@ -162,16 +162,18 @@ void static_plugin_registry_add_plugin(const 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) \ + __attribute__((constructor)) static void __reg_plugin_##_identifier_name(void) { \ + 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(void) { \ + static_plugin_registry_remove_plugin(_name); \ + } #endif // _FLUTTERPI_SRC_PLUGINREGISTRY_H diff --git a/src/plugins/audioplayers/player.c b/src/plugins/audioplayers/player.c index 71cb91ba..87f259a3 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 @@ -71,8 +71,8 @@ static int on_bus_fd_ready(sd_event_source *src, int fd, uint32_t revents, void } 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); @@ -183,6 +183,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 +204,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 +217,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 +275,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 +290,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 +486,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 +525,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 +575,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; } diff --git a/src/plugins/audioplayers/plugin.c b/src/plugins/audioplayers/plugin.c index e80d2d77..98883c34 100644 --- a/src/plugins/audioplayers/plugin.c +++ b/src/plugins/audioplayers/plugin.c @@ -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..71d1bf2a 100644 --- a/src/plugins/gstreamer_video_player/player.c +++ b/src/plugins/gstreamer_video_player/player.c @@ -174,15 +174,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 +472,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,6 +607,8 @@ 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; } @@ -1114,6 +1119,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 +1171,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..4d03812a 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; @@ -953,11 +953,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 +1085,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 +1103,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 +1123,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 +1154,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 +1176,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 +1192,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 +1274,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 +1297,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) { 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..0c28a0c5 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; @@ -780,7 +789,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_fini(struct flutterpi *flutterpi, void *userdata) { struct sentry_plugin *plugin; ASSERT_NOT_NULL(userdata); @@ -794,4 +803,4 @@ void sentry_plugin_init(struct flutterpi *flutterpi, void *userdata) { free(plugin); } -FLUTTERPI_PLUGIN("sentry", sentry_plugin_init, sentry_plugin_deinit, NULL); +FLUTTERPI_PLUGIN("sentry", sentry, sentry_plugin_init, sentry_plugin_fini) diff --git a/src/plugins/text_input.c b/src/plugins/text_input.c index c57938cf..15674cd4 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; @@ -181,6 +182,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 +193,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 +242,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 +534,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; } 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..17531743 100644 --- a/src/user_input.c +++ b/src/user_input.c @@ -270,7 +270,7 @@ static void on_close(int fd, void *userdata) { ASSERT_NOT_NULL(userdata); input = userdata; - return input->interface.close(fd, input->userdata); + input->interface.close(fd, input->userdata); } static const struct libinput_interface libinput_interface = { .open_restricted = on_open, .close_restricted = on_close }; @@ -376,6 +376,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 +385,7 @@ void user_input_destroy(struct user_input *input) { break; default: break; } + PRAGMA_DIAGNOSTIC_POP libinput_event_destroy(event); } @@ -746,21 +749,21 @@ 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); @@ -1356,6 +1359,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 +1489,7 @@ static int process_libinput_events(struct user_input *input, uint64_t timestamp) #endif default: break; } + PRAGMA_DIAGNOSTIC_POP libinput_event_destroy(event); } @@ -1514,8 +1523,8 @@ int user_input_on_fd_ready(struct user_input *input) { // 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); + cursor_x_before = (int) round(input->cursor_x); + cursor_y_before = (int) round(input->cursor_y); // handle all available libinput events ok = process_libinput_events(input, timestamp); @@ -1526,8 +1535,8 @@ int user_input_on_fd_ready(struct user_input *input) { // 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); + cursor_x = (int) round(input->cursor_x); + cursor_y = (int) round(input->cursor_y); // make sure we've dispatched all the flutter pointer events flush_pointer_events(input); 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/collection.c b/src/util/collection.c index 323f4fb2..dcc50394 100644 --- a/src/util/collection.c +++ b/src/util/collection.c @@ -2,14 +2,14 @@ static pthread_mutexattr_t default_mutex_attrs; -static void init_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); #endif } -const pthread_mutexattr_t *get_default_mutex_attrs() { +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); diff --git a/src/util/collection.h b/src/util/collection.h index bbc48d23..d466ae35 100644 --- a/src/util/collection.h +++ b/src/util/collection.h @@ -108,7 +108,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 +117,6 @@ 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(); +const pthread_mutexattr_t *get_default_mutex_attrs(void); #endif // _FLUTTERPI_SRC_UTIL_COLLECTION_H diff --git a/src/util/lock_ops.h b/src/util/lock_ops.h index dc0a31a0..648c9791 100644 --- a/src/util/lock_ops.h +++ b/src/util/lock_ops.h @@ -59,4 +59,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..8be7fc43 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 @@ -405,6 +408,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 +430,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 +459,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)) @@ -547,21 +556,27 @@ typedef int lock_cap_t; #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_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_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 #define PASTE2(a, b) a##b @@ -588,7 +603,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/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_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..920878ee 100644 --- a/src/window.c +++ b/src/window.c @@ -73,7 +73,7 @@ struct window { * 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; + float pixel_ratio; /** * @brief Whether we have physical screen dimensions and @ref width_mm and @ref height_mm contain usable values. @@ -300,7 +300,7 @@ static int window_init( // clang-format on ) { enum device_orientation original_orientation; - double pixel_ratio; + float pixel_ratio; ASSERT_NOT_NULL(window); ASSERT_NOT_NULL(tracer); @@ -317,7 +317,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)); @@ -943,9 +943,8 @@ MUST_CHECK struct window *kms_window_new( 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) { + } 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; @@ -985,7 +984,7 @@ MUST_CHECK struct window *kms_window_new( mode_get_vrefresh(selected_mode), width_mm, height_mm, - window->pixel_ratio, + (double) (window->pixel_ratio), has_forced_pixel_format ? get_pixfmt_info(forced_pixel_format)->name : "(any)" ); @@ -1219,6 +1218,7 @@ static int kms_window_push_composition_locked(struct window *window, struct fl_l req = kms_req_builder_build(builder); if (req == NULL) { + ok = EIO; goto fail_unref_builder; } @@ -1227,6 +1227,7 @@ static int kms_window_push_composition_locked(struct window *window, struct fl_l frame = malloc(sizeof *frame); if (frame == NULL) { + ok = ENOMEM; goto fail_unref_req; } @@ -1428,11 +1429,15 @@ static struct render_surface *kms_window_get_render_surface_internal(struct wind 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; + if (n_allowed_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); + // Next, fill context.modifiers with the allowed modifiers. + drm_plane_for_each_modified_format(plane, extract_modifiers_for_pixel_format, &context); + } else { + allowed_modifiers = NULL; + } break; } } @@ -1750,6 +1755,10 @@ static EGLSurface dummy_window_get_egl_surface(struct window *window) { if (window->renderer_type == kOpenGL_RendererType) { struct render_surface *render_surface = dummy_window_get_render_surface_internal(window, false, VEC2I(0, 0)); + if (render_surface == NULL) { + return EGL_NO_SURFACE; + } + return egl_gbm_render_surface_get_egl_surface(CAST_EGL_GBM_RENDER_SURFACE(render_surface)); } else { return EGL_NO_SURFACE; diff --git a/src/window.h b/src/window.h index 2efd0196..7fe89d9b 100644 --- a/src/window.h +++ b/src/window.h @@ -27,7 +27,7 @@ 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 }; 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/include/flutter_embedder.h b/third_party/flutter_embedder_header/include/flutter_embedder_header/flutter_embedder.h similarity index 100% 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 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 From 2c6521e2a1fa352028a722563d0b65555e293b01 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 10 Aug 2024 16:22:16 +0200 Subject: [PATCH 02/27] add script for rolling `flutter_embedder.h` --- tools/roll_embedder_header.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100755 tools/roll_embedder_header.sh 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 From 9deaf96d22a5d0a4aeb8146cddc5b88a34dd49ad Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 10 Aug 2024 16:22:34 +0200 Subject: [PATCH 03/27] Roll `flutter_embedder.h` --- .../flutter_embedder_header/engine.version | 2 +- .../flutter_embedder.h | 435 +++++++++++++++++- 2 files changed, 421 insertions(+), 16 deletions(-) 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_header/flutter_embedder.h b/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_header/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_ From 9d56e4848c1b2a82e650ea27587d13a8a8ec46d7 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 20 Aug 2024 10:50:06 +0200 Subject: [PATCH 04/27] initial multidisplay work - add multidisplay plugin stub - make compositor multiview capable - use klib (khash) to implement the view id -> window mapping - split drmdev into drm_resources and drmdev - drmdev: (mostly) non-stateful part. Basically a better wrapper around a drm fd. mt-safe - drm_resources: the DRM state / resources. is stateful, but does not update itself. To keep it in sync with kernel state, one needs to listen to kernel events with drm_monitor and call drm_resources_update. - drm_resources is not mt-safe and only supposed to be used on a single thread. - add a bunch of QoL stuff to drm_resources - use evloop as platform and raster thread event loop (mt-safe wrapper around sd-event) - add own mutex type & fns with thread safety annotations - generally refactor flutter-pi.c and drmdev - fix function mixup in sentry plugin --- CMakeLists.txt | 86 +- CMakePresets.json | 39 +- src/compositor_ng.c | 595 ++++- src/compositor_ng.h | 221 +- src/dmabuf_surface.c | 3 + src/egl_gbm_render_surface.c | 13 +- src/flutter-pi.c | 1007 +++----- src/flutter-pi.h | 11 +- src/kms/drmdev.c | 932 ++++++++ src/kms/drmdev.h | 171 ++ src/kms/monitor.c | 128 + src/kms/monitor.h | 35 + src/kms/req_builder.c | 957 ++++++++ src/kms/req_builder.h | 206 ++ src/kms/resources.c | 1543 ++++++++++++ src/{modesetting.h => kms/resources.h} | 549 ++--- src/modesetting.c | 3005 ------------------------ src/plugins/multidisplay.c | 172 ++ src/plugins/multidisplay.h | 0 src/plugins/sentry/sentry.c | 4 +- src/util/bitset.h | 2 + src/util/collection.c | 18 - src/util/collection.h | 39 +- src/{ => util}/event_loop.c | 78 +- src/{ => util}/event_loop.h | 29 +- src/util/khash.h | 627 +++++ src/util/lock_ops.c | 35 + src/util/lock_ops.h | 142 ++ src/util/macros.h | 28 - src/window.c | 173 +- src/window.h | 6 +- 31 files changed, 6394 insertions(+), 4460 deletions(-) create mode 100644 src/kms/drmdev.c create mode 100644 src/kms/drmdev.h create mode 100644 src/kms/monitor.c create mode 100644 src/kms/monitor.h create mode 100644 src/kms/req_builder.c create mode 100644 src/kms/req_builder.h create mode 100644 src/kms/resources.c rename src/{modesetting.h => kms/resources.h} (61%) delete mode 100644 src/modesetting.c create mode 100644 src/plugins/multidisplay.c create mode 100644 src/plugins/multidisplay.h delete mode 100644 src/util/collection.c rename src/{ => util}/event_loop.c (87%) rename src/{ => util}/event_loop.h (56%) create mode 100644 src/util/khash.h create mode 100644 src/util/lock_ops.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e507dca1..bf6c74bf 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,6 +89,7 @@ message(STATUS "Generator .............. ${CMAKE_GENERATOR}") message(STATUS "Build Type ............. ${CMAKE_BUILD_TYPE}") include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) # Those libraries we definitely need. include(FindPkgConfig) @@ -127,8 +128,12 @@ 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/util/event_loop.c + src/util/lock_ops.c src/util/bitscan.c src/util/vector.c src/cursor.c @@ -425,6 +430,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), @@ -435,8 +441,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() @@ -464,84 +471,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 31106e29..fc0871c0 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -2,8 +2,8 @@ "version": 2, "configurePresets": [ { - "name": "default", - "displayName": "Default OpenGL host build", + "name": "default-debug", + "displayName": "Default OpenGL host build (Debug)", "description": "Sets Ninja generator, build and install directory", "generator": "Ninja", "binaryDir": "${sourceDir}/out/build/${presetName}", @@ -20,7 +20,40 @@ "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN": true, "TRY_BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN": false, "BUILD_SENTRY_PLUGIN": true, - "ENABLE_TESTS": true + "ENABLE_TESTS": true, + "ENABLE_ASAN": true + } + }, + { + "name": "default-relwithdebinfo", + "displayName": "Default OpenGL host build (Release with Debug Info)", + "description": "Sets Ninja generator, build and install directory", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "ENABLE_OPENGL": true, + "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN": true, + "BUILD_SENTRY_PLUGIN": true, + "ENABLE_TESTS": false, + "ENABLE_ASAN": false + } + }, + { + "name": "default-release", + "displayName": "Default OpenGL host build (Release)", + "description": "Sets Ninja generator, build and install directory", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "ENABLE_OPENGL": true, + "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN": true, + "BUILD_SENTRY_PLUGIN": true, + "ENABLE_TESTS": false, + "ENABLE_ASAN": false } }, { diff --git a/src/compositor_ng.c b/src/compositor_ng.c index dcbf6b9c..45dc596b 100644 --- a/src/compositor_ng.c +++ b/src/compositor_ng.c @@ -27,7 +27,10 @@ #include "dummy_render_surface.h" #include "flutter-pi.h" #include "frame_scheduler.h" -#include "modesetting.h" +#include "kms/drmdev.h" +#include "kms/req_builder.h" +#include "kms/resources.h" +#include "kms/monitor.h" #include "notifier_listener.h" #include "pixel_format.h" #include "render_surface.h" @@ -36,6 +39,8 @@ #include "util/collection.h" #include "util/logging.h" #include "util/refcounting.h" +#include "util/khash.h" +#include "util/event_loop.h" #include "window.h" #include "config.h" @@ -59,7 +64,7 @@ * @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 +116,9 @@ void fl_layer_composition_destroy(struct fl_layer_composition *composition) { DEFINE_REF_OPS(fl_layer_composition, n_refs) +KHASH_MAP_INIT_INT64(view, struct window *) +KHASH_MAP_INIT_INT64(platform_view, struct surface *) + /** * @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. @@ -124,16 +132,23 @@ struct compositor { struct tracer *tracer; struct window *main_window; - struct util_dynarray views; + + 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 platform_view_with_id { - int64_t id; - struct surface *surface; + struct drmdev *drmdev; + struct drm_monitor *monitor; + struct drm_resources *resources; + + struct evloop *raster_loop; + struct evsrc *drm_monitor_evsrc; }; static bool on_flutter_present_layers(const FlutterLayer **layers, size_t layers_count, void *userdata); @@ -143,52 +158,147 @@ 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 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); + + /// TODO: Check for added displays + UNIMPLEMENTED(); +} + +static const struct drm_uevent_listener uevent_listener = { + .on_uevent = on_drm_uevent, +}; + +MUST_CHECK struct compositor *compositor_new(struct tracer *tracer, struct evloop *raster_loop, struct window *main_window, struct udev *udev, struct drmdev *drmdev, struct drm_resources *resources) { + struct compositor *c; + int bucket_status; + + ASSERT_NOT_NULL(tracer); + ASSERT_NOT_NULL(raster_loop); + ASSERT_NOT_NULL(main_window); + ASSERT_NOT_NULL(drmdev); + + c = calloc(1, sizeof *c); + if (c == NULL) { + return NULL; } - ok = pthread_mutex_init(&compositor->mutex, NULL); - if (ok != 0) { - goto fail_free_compositor; + mutex_init(&c->mutex); + c->views = kh_init(view); + c->platform_views = kh_init(platform_view); + + khiter_t entry = kh_put(view, c->views, 0, &bucket_status); + if (bucket_status == -1) { + goto fail_return_null; } - util_dynarray_init(&compositor->views); + kh_value(c->views, entry) = window_ref(main_window); - compositor->n_refs = REFCOUNT_INIT_1; - compositor->main_window = window_ref(main_window); + c->n_refs = REFCOUNT_INIT_1; + c->main_window = window_ref(main_window); // 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); + c->cursor_pos = VEC2F(0, 0); + + c->next_view_id = 1; + c->next_platform_view_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); + return c; fail_return_null: return NULL; } 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); } - util_dynarray_fini(&compositor->views); + if (compositor->resources != NULL) { + drm_resources_unref(compositor->resources); + } + if (compositor->drmdev != NULL) { + drmdev_unref(compositor->drmdev); + } + evloop_unref(compositor->raster_loop); + tracer_unref(compositor->tracer); window_unref(compositor->main_window); pthread_mutex_destroy(&compositor->mutex); @@ -199,6 +309,42 @@ DEFINE_REF_OPS(compositor, n_refs) DEFINE_STATIC_LOCK_OPS(compositor, mutex) +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 = window_ref(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); + compositor_unlock(compositor); + + return window; +} + void compositor_get_view_geometry(struct compositor *compositor, struct view_geometry *view_geometry_out) { *view_geometry_out = window_get_view_geometry(compositor->main_window); } @@ -213,13 +359,26 @@ int compositor_get_next_vblank(struct compositor *compositor, uint64_t *next_vbl return window_get_next_vblank(compositor->main_window, next_vblank_ns_out); } -static int compositor_push_composition(struct compositor *compositor, struct fl_layer_composition *composition) { +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 ok; + 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->main_window); + } + TRACER_BEGIN(compositor->tracer, "window_push_composition"); - ok = window_push_composition(compositor->main_window, composition); + ok = window_push_composition(window, composition); TRACER_END(compositor->tracer, "window_push_composition"); + window_unref(window); + return ok; } @@ -308,17 +467,27 @@ 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; + struct view_geometry geometry; + struct window *window; int ok; + window = has_view_id ? compositor_get_view_by_id(compositor, view_id) : window_ref(compositor->main_window); + 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,28 +508,16 @@ 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((int) fl_layer->size.width, (int) 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(fl_layer->size.width, 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. @@ -377,18 +534,16 @@ 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); + ok = compositor_push_composition(compositor, has_view_id, view_id, composition); TRACER_END(compositor->tracer, "compositor_push_composition"); fl_layer_composition_unref(composition); - return ok; } -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; @@ -398,7 +553,7 @@ 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); + ok = compositor_push_fl_layers(compositor, false, -1, layers_count, layers); TRACER_END(compositor->tracer, "compositor_push_fl_layers"); if (ok != 0) { @@ -408,59 +563,61 @@ static bool on_flutter_present_layers(const FlutterLayer **layers, size_t layers 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 ok; + + ASSERT_NOT_NULL(present_info); + compositor = present_info->user_data; + + TRACER_BEGIN(compositor->tracer, "compositor_push_fl_layers"); + ok = 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 (ok != 0) { + return false; + } + + return true; } -int compositor_set_platform_view(struct compositor *compositor, int64_t id, struct surface *surface) { - struct platform_view_with_id *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; - } - } + int64_t id = compositor->next_platform_view_id++; - if (view == NULL) { - if (surface != NULL) { - struct platform_view_with_id v = { - .id = id, - .surface = surface_ref(surface), - }; - - 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 @@ -477,6 +634,7 @@ static bool on_flutter_create_backing_store(const FlutterBackingStoreConfig *config, FlutterBackingStore *backing_store_out, void *userdata) { struct render_surface *s; struct compositor *compositor; + struct window *window; int ok; ASSERT_NOT_NULL(config); @@ -484,11 +642,17 @@ on_flutter_create_backing_store(const FlutterBackingStoreConfig *config, Flutter 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,13 +668,17 @@ on_flutter_create_backing_store(const FlutterBackingStoreConfig *config, Flutter ok = render_surface_fill(s, backing_store_out); if (ok != 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) { @@ -578,3 +746,222 @@ void compositor_set_cursor( compositor_unlock(compositor); } + +static bool call_display_callback_with_drm_connector(display_callback_t callback, const struct drm_connector *connector, void *userdata) { + (void) callback; + (void) connector; + (void) userdata; + + /// TODO: Implement + UNIMPLEMENTED(); +} + +static bool call_connector_callback_with_drm_connector(connector_callback_t callback, const struct drm_connector *connector, void *userdata) { + struct connector conn = { + .id = NULL, + .type = CONNECTOR_TYPE_OTHER, + .other_type_name = NULL, + }; + + switch (connector->type) { + case DRM_MODE_CONNECTOR_Unknown: + conn.id = alloca_sprintf("Unknown-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_OTHER; + conn.other_type_name = "Unknown"; + break; + case DRM_MODE_CONNECTOR_VGA: + conn.id = alloca_sprintf("VGA-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_VGA; + break; + case DRM_MODE_CONNECTOR_DVII: + conn.id = alloca_sprintf("DVI-I-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_DVI; + break; + case DRM_MODE_CONNECTOR_DVID: + conn.id = alloca_sprintf("DVI-D-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_DVI; + break; + case DRM_MODE_CONNECTOR_DVIA: + conn.id = alloca_sprintf("DVI-A-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_DVI; + break; + case DRM_MODE_CONNECTOR_Composite: + conn.id = alloca_sprintf("Composite-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_OTHER; + conn.other_type_name = "Composite"; + break; + case DRM_MODE_CONNECTOR_SVIDEO: + conn.id = alloca_sprintf("SVIDEO-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_OTHER; + conn.other_type_name = "SVIDEO"; + break; + case DRM_MODE_CONNECTOR_LVDS: + conn.id = alloca_sprintf("LVDS-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_LVDS; + break; + case DRM_MODE_CONNECTOR_Component: + conn.id = alloca_sprintf("Component-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_OTHER; + conn.other_type_name = "Component"; + break; + case DRM_MODE_CONNECTOR_9PinDIN: + conn.id = alloca_sprintf("DIN-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_OTHER; + conn.other_type_name = "DIN"; + break; + case DRM_MODE_CONNECTOR_DisplayPort: + conn.id = alloca_sprintf("DP-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_DISPLAY_PORT; + break; + case DRM_MODE_CONNECTOR_HDMIA: + conn.id = alloca_sprintf("HDMI-A-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_HDMI; + break; + case DRM_MODE_CONNECTOR_HDMIB: + conn.id = alloca_sprintf("HDMI-B-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_HDMI; + break; + case DRM_MODE_CONNECTOR_TV: + conn.id = alloca_sprintf("TV-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_TV; + break; + case DRM_MODE_CONNECTOR_eDP: + conn.id = alloca_sprintf("eDP-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_EDP; + break; + case DRM_MODE_CONNECTOR_VIRTUAL: + conn.id = alloca_sprintf("Virtual-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_OTHER; + conn.other_type_name = "Virtual"; + break; + case DRM_MODE_CONNECTOR_DSI: + conn.id = alloca_sprintf("DSI-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_DSI; + break; + case DRM_MODE_CONNECTOR_DPI: + conn.id = alloca_sprintf("DPI-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_DPI; + break; + case DRM_MODE_CONNECTOR_WRITEBACK: + return true; + case DRM_MODE_CONNECTOR_SPI: + conn.id = alloca_sprintf("SPI-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_OTHER; + conn.other_type_name = "SPI"; + break; + case DRM_MODE_CONNECTOR_USB: + conn.id = alloca_sprintf("USB-%" PRIu32, connector->type_id); + conn.type = CONNECTOR_TYPE_OTHER; + conn.other_type_name = "USB"; + break; + default: + return true; + } + + return callback(&conn, userdata); +} + +int64_t compositor_add_view( + struct compositor *compositor, + struct window *window +) { + ASSERT_NOT_NULL(compositor); + ASSERT_NOT_NULL(window); + int64_t view_id; + + compositor_lock(compositor); + + view_id = compositor->next_view_id++; + + khiter_t entry = kh_put(view, compositor->views, view_id, NULL); + kh_val(compositor->views, entry) = window_ref(window); + + 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)) { + window_unref(kh_val(compositor->views, entry)); + kh_del(view, compositor->views, entry); + } + + compositor_unlock(compositor); +} + +int compositor_put_implicit_view( + struct compositor *compositor, + struct window *window +) { + int bucket_status; + + ASSERT_NOT_NULL(compositor); + ASSERT_NOT_NULL(window); + + compositor_lock(compositor); + + khiter_t entry = kh_put(view, compositor->views, 0, &bucket_status); + if (bucket_status == -1) { + compositor_unlock(compositor); + return ENOMEM; + } + + if (bucket_status == 0) { + window_swap_ptrs(&kh_val(compositor->views, entry), window); + } else { + kh_val(compositor->views, entry) = window_ref(window); + } + + compositor_unlock(compositor); + + return 0; +} + +void compositor_for_each_display( + struct compositor *compositor, + display_callback_t callback, + void *userdata +) { + struct drm_connector *connector; + + (void) compositor; + (void) callback; + (void) userdata; + (void) connector; + + /// TODO: Implement + UNIMPLEMENTED(); + + /// TODO: drm_resources is not mt-safe, but compositor_for_each_connector might not be called on the raster thread + /// (which uses drm_resources) + + drm_resources_for_each_connector(compositor->resources, connector) { + if (!call_display_callback_with_drm_connector(callback, connector, userdata)) { + break; + } + } +} + +void compositor_for_each_connector( + struct compositor *compositor, + connector_callback_t callback, + void *userdata +) { + /// TODO: drm_resources is not mt-safe, but compositor_for_each_connector might not be called on the raster thread + /// (which uses drm_resources) + drm_resources_for_each_connector(compositor->resources, connector) { + if (!call_connector_callback_with_drm_connector(callback, connector, userdata)) { + break; + } + } +} diff --git a/src/compositor_ng.h b/src/compositor_ng.h index d7360964..8f37e9ac 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,70 +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 { /** @@ -151,10 +86,11 @@ 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 compositor *compositor_new(struct tracer *tracer, struct evloop *raster_loop, struct window *main_window, struct udev *udev, struct drmdev *drmdev, struct drm_resources *resources); void compositor_destroy(struct compositor *compositor); @@ -166,9 +102,40 @@ ATTR_PURE double compositor_get_refresh_rate(struct compositor *compositor); int compositor_get_next_vblank(struct compositor *compositor, uint64_t *next_vblank_ns_out); -int compositor_set_platform_view(struct compositor *compositor, int64_t id, struct surface *surface); +/** + * @brief Adds a (non-implicit) view to the compositor, returning the view id. + */ +int64_t compositor_add_view( + struct compositor *compositor, + struct window *window +); + +/** + * @brief Removes a view from the compositor. + */ +void compositor_remove_view( + struct compositor *compositor, + int64_t view_id +); + +/** + * @brief Sets the implicit view (view with id 0) to the given window. + */ +int compositor_put_implicit_view( + struct compositor *compositor, + struct window *window +); + +/** + * @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); -struct surface *compositor_get_view_by_id_locked(struct compositor *compositor, int64_t view_id); const FlutterCompositor *compositor_get_flutter_compositor(struct compositor *compositor); @@ -194,6 +161,124 @@ void compositor_set_cursor( struct vec2f delta ); +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 connector { + /** + * @brief The ID of the connector. + * + * e.g. `HDMI-A-1`, `DP-1`, `LVDS-1`, etc. + * + * This string will only live till the end of the iteration, so make sure to copy it if you need it later. + */ + const char *id; + + /** + * @brief The type of the connector. + */ + enum connector_type type; + + /** + * @brief The name of the connector type, if @ref type is @ref CONNECTOR_TYPE_OTHER. + * + * e.g. `Virtual`, `Composite`, etc. + * + * This string will only live till the end of the iteration, so make sure to copy it if you need it later. + */ + const char *other_type_name; +}; + +/** + * @brief Callback that will be called on each iteration of + * @ref compositor_for_each_connector. + * + * Should return true if looping should continue. False if iterating should be + * stopped. + * + * @param display The current iteration value. + * @param userdata Userdata that was passed to @ref compositor_for_each_connector. + */ +typedef bool (*connector_callback_t)(const struct connector *connector, void *userdata); + +/** + * @brief Iterates over every present connector. + * + * See @ref connector_callback_t for documentation on the callback. + */ +void compositor_for_each_connector( + struct compositor *compositor, + connector_callback_t callback, + void *userdata +); + +struct display { + /** + * @brief The ID of the display, as reported to flutter. + */ + uint64_t fl_display_id; + + /** + * @brief The refresh rate of the display. + */ + double refresh_rate; + + /** + * @brief The width of the display in the selected mode, in physical pixels. + */ + size_t width; + + /** + * @brief The height of the display in the selected mode, in physical pixels. + */ + size_t height; + + /** + * @brief The device pixel ratio of the display, in the selected mode. + */ + double device_pixel_ratio; + + /** + * @brief The identifier of the connector this display is connected to. + * + * This string will only live till the end of the iteration, so make sure to copy it if you need it later. + */ + const char *connector_id; +}; + +/** + * @brief Callback that will be called on each iteration of + * @ref compositor_for_each_display. + * + * Should return true if looping should continue. False if iterating should be + * stopped. + * + * @param display The current iteration value. + * @param userdata Userdata that was passed to @ref compositor_for_each_display. + */ +typedef bool (*display_callback_t)(const struct display *display, void *userdata); + +/** + * @brief Iterates over every connected display. + * + * See @ref display_callback_t for documentation on the callback. + */ +void compositor_for_each_display( + struct compositor *compositor, + display_callback_t callback, + void *userdata +); + struct fl_layer_composition; struct fl_layer_composition *fl_layer_composition_new(size_t n_layers); diff --git a/src/dmabuf_surface.c b/src/dmabuf_surface.c index fa4990a8..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" diff --git a/src/egl_gbm_render_surface.c b/src/egl_gbm_render_surface.c index 74b0ae01..f741d426 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" @@ -428,7 +430,7 @@ static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl struct drm_crtc *crtc = kms_req_builder_get_crtc(builder); ASSERT_NOT_NULL(crtc); - if (drm_crtc_any_plane_supports_format(meta->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)"); uint32_t fb_id = drmdev_add_fb_from_gbm_bo( meta->drmdev, @@ -453,9 +455,10 @@ 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(meta->drmdev, crtc, pixfmt_opaque(egl_surface->pixel_format))) { - uint32_t opaque_fb_id = drmdev_add_fb_from_gbm_bo( - meta->drmdev, + drm_resources_any_crtc_plane_supports_format(kms_req_builder_peek_resources(builder), crtc->id, pixfmt_opaque(egl_surface->pixel_format))) { + + opaque_fb_id = drmdev_add_fb_from_gbm_bo( + drmdev, bo, /* cast_opaque */ true ); diff --git a/src/flutter-pi.c b/src/flutter-pi.c index c9a87468..4e51aaa5 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -45,7 +45,9 @@ #include "frame_scheduler.h" #include "keyboard.h" #include "locales.h" -#include "modesetting.h" +#include "kms/drmdev.h" +#include "kms/req_builder.h" +#include "kms/resources.h" #include "pixel_format.h" #include "platformchannel.h" #include "pluginregistry.h" @@ -57,6 +59,7 @@ #include "util/list.h" #include "util/logging.h" #include "window.h" +#include "util/event_loop.h" #include "config.h" @@ -185,26 +188,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 +217,21 @@ 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 evloop *evloop; + struct evsrc *drmdev_evrsc; + + /** + * @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 +249,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 { @@ -460,16 +455,23 @@ 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 { struct flutterpi *flutterpi; intptr_t baton; 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; @@ -485,10 +487,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) { @@ -512,7 +511,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; @@ -550,11 +549,11 @@ UNUSED static void on_frame_request(void *userdata, intptr_t baton) { req->vblank_ns = get_monotonic_time(); req->next_vblank_ns = req->vblank_ns + (uint64_t) (1000000000.0 / 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); + 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; @@ -562,7 +561,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; @@ -589,218 +588,92 @@ UNUSED static FlutterTransformation on_get_transformation(void *userdata) { 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; +/// TODO: Remove +struct evsrc *flutterpi_add_io_source(struct flutterpi *flutterpi, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata) { + return evloop_add_io(flutterpi->platform_loop, fd, events, callback, userdata); } -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: %d\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->fl_task = fl_task; + task->fl_run_task = flutterpi->flutter.procs.RunTask; + task->fl_engine = flutterpi->flutter.engine; - task = userdata; - - result = flutterpi->flutter.procs.RunTask(flutterpi->flutter.engine, task); - if (result != kSuccess) { - LOG_ERROR("Error running platform task. FlutterEngineRunTask: %u\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; @@ -834,8 +707,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( @@ -892,7 +763,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); @@ -944,7 +815,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); @@ -1060,38 +931,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) { @@ -1261,6 +1118,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; @@ -1316,14 +1174,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 @@ -1379,8 +1247,7 @@ 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; @@ -1401,29 +1268,35 @@ 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; } @@ -1436,7 +1309,7 @@ static int flutterpi_run(struct flutterpi *flutterpi) { display.single_display = true; display.refresh_rate = compositor_get_refresh_rate(flutterpi->compositor); - engine_result = procs->NotifyDisplayUpdate(engine, kFlutterEngineDisplaysUpdateTypeStartup, &display, 1); + engine_result = procs->NotifyDisplayUpdate(flutterpi->flutter.engine, kFlutterEngineDisplaysUpdateTypeStartup, &display, 1); if (engine_result != kSuccess) { ok = EINVAL; LOG_ERROR( @@ -1464,7 +1337,7 @@ static int flutterpi_run(struct flutterpi *flutterpi) { window_metrics_event.physical_view_inset_left = 0; // update window size - engine_result = procs->SendWindowMetricsEvent(engine, &window_metrics_event); + engine_result = procs->SendWindowMetricsEvent(flutterpi->flutter.engine, &window_metrics_event); if (engine_result != kSuccess) { LOG_ERROR( "Could not send window metrics to flutter engine. FlutterEngineSendWindowMetricsEvent: %s\n", @@ -1473,87 +1346,7 @@ static int flutterpi_run(struct flutterpi *flutterpi) { 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; - - { - 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; - } - - 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; - } - - 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; - } - - break; - case SD_EVENT_FINISHED: break; - default: UNREACHABLE(); - } - } while (state != SD_EVENT_FINISHED); - - pthread_mutex_unlock(&flutterpi->event_loop_mutex); - } + evloop_run(flutterpi->platform_loop); // We deinitialize the plugins here so plugins don't attempt to use the // flutter engine anymore. @@ -1561,26 +1354,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 @@ -1597,18 +1391,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; } @@ -1821,16 +1604,17 @@ static void on_user_input_close(int fd, void *userdata) { } } -static int on_user_input_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { +static enum event_handler_return on_user_input_fd_ready(int fd, uint32_t revents, void *userdata) { struct user_input *input; - (void) s; + (void) fd; (void) fd; (void) revents; input = userdata; - return user_input_on_fd_ready(input); + user_input_on_fd_ready(input); + return EVENT_HANDLER_CONTINUE; } static struct flutter_paths *setup_paths(enum flutter_runtime_mode runtime_mode, const char *app_bundle_path) { @@ -2020,7 +1804,7 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin 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; @@ -2114,74 +1898,7 @@ 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, ARRAY_SIZE(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; - } - - 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 const struct file_interface file_interface = { .open = on_drmdev_open, .close = on_drmdev_close }; static struct gbm_device *open_rendernode_as_gbm_device(void) { struct gbm_device *gbm; @@ -2315,31 +2032,17 @@ static int on_libseat_fd_ready(sd_event_source *s, int fd, uint32_t revents, voi 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 **engine_argv, *desired_videomode; - int ok, engine_argc, wakeup_fd; + char *bundle_path, **engine_argv, *desired_videomode; + int ok, engine_argc; - fpi = malloc(sizeof *fpi); + fpi = calloc(1, sizeof *fpi); if (fpi == NULL) { return NULL; } @@ -2382,76 +2085,70 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { goto fail_free_cmd_args; } - fpi->flutter.bundle_path = realpath(cmd_args.bundle_path, NULL); - - 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 }; + { + 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)); - } + 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)); + } - 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", - strerror(errno) - ); - libseat_close_seat(libseat); - libseat = NULL; + int fd; + 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) { - 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; + 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 (libseat != NULL) { - libseat_set_log_level(LIBSEAT_LOG_LEVEL_DEBUG); + 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); + + struct udev *udev = NULL; + 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 @@ -2461,30 +2158,36 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { goto fail_destroy_locales; } } else { - drmdev = find_drmdev(libseat); - if (drmdev == NULL) { + udev = udev_new(); + if (udev == NULL) { + LOG_ERROR("Couldn't create udev context.\n"); goto fail_destroy_locales; } - gbm_device = drmdev_get_gbm_device(drmdev); + fpi->drmdev = drmdev_new_from_udev_primary(udev, "seat0", &file_interface, fpi->libseat); + if (fpi->drmdev == NULL) { + goto fail_destroy_locales; + } + + 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; - } - if (renderer_type == kVulkan_RendererType) { #ifdef HAVE_VULKAN gl_renderer = NULL; @@ -2498,18 +2201,19 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { #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"); - goto fail_unref_scheduler; + ok = EIO; + 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" @@ -2523,65 +2227,88 @@ 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; + { + struct window *window; + + { + 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 = compositor_new(tracer, window); - if (compositor == NULL) { - LOG_ERROR("Couldn't create compositor.\n"); - goto fail_unref_window; + fpi->compositor = compositor_new(fpi->tracer, fpi->raster_evloop, window, NULL, fpi->drmdev, drm_resources); + + window_unref(window); + + if (fpi->compositor == NULL) { + LOG_ERROR("Couldn't create compositor.\n"); + goto fail_unref_tracer; + } } /// 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)); + 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; } } - compositor_get_view_geometry(compositor, &geometry); + compositor_get_view_geometry(fpi->compositor, &geometry); static const struct user_input_interface user_input_interface = { .on_flutter_pointer_event = on_flutter_pointer_event, @@ -2596,10 +2323,9 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { .on_key_event = NULL, }; - fpi->libseat = libseat; list_inithead(&fpi->fd_for_device_id); - input = user_input_new( + fpi->user_input = user_input_new( &user_input_interface, fpi, &geometry.display_to_view_transform, @@ -2607,55 +2333,41 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { (unsigned int) geometry.display_size.x, (unsigned int) geometry.display_size.y ); - if (input == NULL) { + if (fpi->user_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; + 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"); + user_input_destroy(fpi->user_input); + fpi->user_input = NULL; } - - 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); } - 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; @@ -2667,8 +2379,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; } @@ -2703,82 +2415,67 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { } } - // We don't need these anymore. - frame_scheduler_unref(scheduler); - window_unref(window); - - free(cmd_args.bundle_path); - - 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.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); + 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); + } fail_unref_compositor: - compositor_unref(compositor); - -fail_unref_window: - window_unref(window); + 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_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); #else @@ -2786,11 +2483,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { #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); @@ -2805,45 +2498,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(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..d3af8f36 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,9 +140,11 @@ 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_raster_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); diff --git a/src/kms/drmdev.c b/src/kms/drmdev.c new file mode 100644 index 00000000..026984b2 --- /dev/null +++ b/src/kms/drmdev.c @@ -0,0 +1,932 @@ +#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/list.h" +#include "util/lock_ops.h" +#include "util/logging.h" +#include "util/macros.h" +#include "util/refcounting.h" +#include "util/khash.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; + } + + if (!drmIsKMS(fd)) { + interface->close(fd, fd_metadata, userdata); + return false; + } + + interface->close(fd, fd_metadata, userdata); + return true; +} + +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; +} + + +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") ?: "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].scanout_callback != NULL) { + cbs_copy.callbacks[cbs_copy.index].scanout_callback(vblank_ns, cbs_copy.callbacks[cbs_copy.index].scanout_callback_userdata); + } + + 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); + } +} + +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..ae292728 --- /dev/null +++ b/src/kms/drmdev.h @@ -0,0 +1,171 @@ +// 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/geometry.h" +#include "util/refcounting.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); +}; + +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/monitor.c b/src/kms/monitor.c new file mode 100644 index 00000000..3220c67b --- /dev/null +++ b/src/kms/monitor.c @@ -0,0 +1,128 @@ +#include +#include +#include + +#include + +#include "resources.h" +#include "monitor.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"); + 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"); + have_connector = str != NULL && safe_string_to_uint32(str, &connector_id); + + 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..3e3c491b --- /dev/null +++ b/src/kms/monitor.h @@ -0,0 +1,35 @@ +#ifndef _FLUTTERPI_SRC_MODESETTING_RESOURCE_MONITOR_H +#define _FLUTTERPI_SRC_MODESETTING_RESOURCE_MONITOR_H + +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..443854be --- /dev/null +++ b/src/kms/req_builder.c @@ -0,0 +1,957 @@ +#include "req_builder.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "drmdev.h" +#include "resources.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" + +#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: %" PRId32 "\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: %" PRId32 "\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); + } + 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) { + 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); +} + +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); + } + + 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; + struct drm_blob *mode_blob; + bool update_mode; + int ok; + + update_mode = false; + mode_blob = NULL; + + 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; + } + } + + 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 + 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); + +// fail_maybe_destroy_mode_blob: +// 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..01f0840e --- /dev/null +++ b/src/kms/req_builder.h @@ -0,0 +1,206 @@ +#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 destroy_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..031e6e5f --- /dev/null +++ b/src/kms/resources.c @@ -0,0 +1,1543 @@ +#include "resources.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "pixel_format.h" +#include "monitor.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 ?: 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 ?: ENOMEM; + } + + out->id = crtc->crtc_id; + out->index = 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 ?: 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 ?: 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 ?: 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 ?: 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 ?: 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 ?: 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 ?: EINVAL; + LOG_ERROR("Could not get DRM device resources. drmModeGetResources: %s\n", strerror(ok)); + return NULL; + } + + 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) { + ok = ENOMEM; + 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) { + ok = ENOMEM; + 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) { + ok = ENOMEM; + 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 ?: 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) { + ok = ENOMEM; + 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); + } + + 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 ?: 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) { + ok = ENOMEM; + 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) { + ok = ENOMEM; + 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) { + ok = ENOMEM; + 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) { + ok = ENOMEM; + 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) { + ok = ENOMEM; + 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) { + ok = ENOMEM; + 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) { + ok = ENOMEM; + 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) { + ok = ENOMEM; + 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 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; +} + +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; + } + + 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 61% rename from src/modesetting.h rename to src/kms/resources.h index 9222d79d..5d2d3bc0 100644 --- a/src/modesetting.h +++ b/src/kms/resources.h @@ -1,29 +1,34 @@ -// 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 - +#ifndef _FLUTTERPI_MODESETTING_RESOURCES_H +#define _FLUTTERPI_MODESETTING_RESOURCES_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include #include +#include +#include #include #include +#include #include "pixel_format.h" -#include "util/collection.h" -#include "util/geometry.h" +#include "monitor.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) #define DRM_ID_IS_VALID(id) ((id) != 0 && (id) != DRM_ID_NONE) @@ -218,12 +223,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 +313,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 +386,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 +421,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 +531,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,7 +619,47 @@ typedef bool (*drm_plane_modified_format_callback_t)( void *userdata ); -struct drmdev; +/** + * @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. + * + */ +struct drm_resources { + refcount_t n_refs; + + bool have_filter; + struct { + uint32_t connector_id; + uint32_t encoder_id; + uint32_t crtc_id; + + size_t n_planes; + uint32_t plane_ids[32]; + } filter; + + uint32_t min_width, max_width; + uint32_t min_height, max_height; + + 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; +}; /** * @brief Iterates over every supported pixel-format & modifier pair. @@ -605,337 +668,137 @@ struct drmdev; */ void drm_plane_for_each_modified_format(struct drm_plane *plane, drm_plane_modified_format_callback_t callback, void *userdata); +bool drm_plane_supports_modified_formats(struct drm_plane *plane); + bool drm_plane_supports_modified_format(struct drm_plane *plane, enum pixfmt format, uint64_t modifier); bool drm_plane_supports_unmodified_format(struct drm_plane *plane, enum pixfmt format); -bool drm_crtc_any_plane_supports_format(struct drmdev *drmdev, struct drm_crtc *crtc, enum pixfmt pixel_format); +bool drm_resources_any_crtc_plane_supports_format(struct drm_resources *res, uint32_t crtc_id, enum pixfmt pixel_format); -struct _drmModeModeInfo; -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); -}; +/** + * @brief Create a new drm_resources object + */ +struct drm_resources *drm_resources_new(int drm_fd); -struct drmdev *drmdev_new_from_interface_fd(int fd, void *fd_metadata, const struct drmdev_interface *interface, void *userdata); +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 drmdev *drmdev_new_from_path(const char *path, const struct drmdev_interface *interface, void *userdata); +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); -DECLARE_REF_OPS(drmdev) +void drm_resources_destroy(struct drm_resources *r); -struct drmdev; -struct _drmModeModeInfo; +DECLARE_REF_OPS(drm_resources) -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 -); - -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] -); +/** + * @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); -uint32_t drmdev_add_fb_from_gbm_bo(struct drmdev *drmdev, struct gbm_bo *bo, bool cast_opaque); +/** + * @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); -int drmdev_rm_fb_locked(struct drmdev *drmdev, uint32_t fb_id); -int drmdev_rm_fb(struct drmdev *drmdev, uint32_t fb_id); +bool drm_resources_has_connector(struct drm_resources *r, uint32_t connector_id); -int drmdev_get_last_vblank(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out); +struct drm_connector *drm_resources_get_connector(struct drm_resources *r, uint32_t connector_id); -bool drmdev_can_modeset(struct drmdev *drmdev); +bool drm_resources_has_encoder(struct drm_resources *r, uint32_t encoder_id); -void drmdev_suspend(struct drmdev *drmdev); +struct drm_encoder *drm_resources_get_encoder(struct drm_resources *r, uint32_t encoder_id); -int drmdev_resume(struct drmdev *drmdev); +bool drm_resources_has_crtc(struct drm_resources *r, uint32_t crtc_id); -int drmdev_move_cursor(struct drmdev *drmdev, uint32_t crtc_id, struct vec2i pos); +struct drm_crtc *drm_resources_get_crtc(struct drm_resources *r, uint32_t crtc_id); -static inline double mode_get_vrefresh(const drmModeModeInfo *mode) { - return mode->clock * 1000.0 / (mode->htotal * mode->vtotal); -} +int64_t drm_resources_get_min_zpos_for_crtc(struct drm_resources *r, uint32_t crtc_id); -typedef void (*kms_scanout_cb_t)(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata); +uint32_t drm_resources_get_possible_planes_for_crtc(struct drm_resources *r, uint32_t crtc_id); -struct kms_fb_layer { - uint32_t drm_fb_id; - enum pixfmt format; - bool has_modifier; - uint64_t modifier; +bool drm_resources_has_plane(struct drm_resources *r, uint32_t plane_id); - int32_t src_x, src_y, src_w, src_h; - int32_t dst_x, dst_y, dst_w, dst_h; +struct drm_plane *drm_resources_get_plane(struct drm_resources *r, uint32_t plane_id); - bool has_rotation; - drm_plane_transform_t rotation; +unsigned int drm_resources_get_plane_index(struct drm_resources *r, uint32_t plane_id); - bool has_in_fence_fd; - int in_fence_fd; - bool prefer_cursor; -}; +struct drm_connector *drm_resources_connector_first(struct drm_resources *r); -typedef void (*kms_fb_release_cb_t)(void *userdata); +struct drm_connector *drm_resources_connector_end(struct drm_resources *r); -typedef void (*kms_deferred_fb_release_cb_t)(void *userdata, int syncfile_fd); +struct drm_connector *drm_resources_connector_next(struct drm_resources *r, struct drm_connector *current); -struct kms_req_builder; -struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uint32_t crtc_id); +drmModeModeInfo *drm_connector_mode_first(struct drm_connector *c); -DECLARE_REF_OPS(kms_req_builder) +drmModeModeInfo *drm_connector_mode_end(struct drm_connector *c); -/** - * @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); +drmModeModeInfo *drm_connector_mode_next(struct drm_connector *c, drmModeModeInfo *current); -/** - * @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); +struct drm_encoder *drm_resources_encoder_first(struct drm_resources *r); -/** - * @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); +struct drm_encoder *drm_resources_encoder_end(struct drm_resources *r); -/** - * @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); +struct drm_encoder *drm_resources_encoder_next(struct drm_resources *r, struct drm_encoder *current); -/** - * @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, - kms_fb_release_cb_t release_callback, - kms_deferred_fb_release_cb_t deferred_release_callback, - void *userdata -); +struct drm_crtc *drm_resources_crtc_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_crtc *drm_resources_crtc_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_crtc *drm_resources_crtc_next(struct drm_resources *r, struct drm_crtc *current); -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); +struct drm_plane *drm_resources_plane_first(struct drm_resources *r); -int kms_req_commit_blocking(struct kms_req *req, uint64_t *vblank_ns_out); +struct drm_plane *drm_resources_plane_end(struct drm_resources *r); -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_next(struct drm_resources *r, struct drm_plane *current); -struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector); -struct drm_encoder *__next_encoder(const struct drmdev *drmdev, const struct drm_encoder *encoder); +#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_crtc *__next_crtc(const struct drmdev *drmdev, const struct drm_crtc *crtc); +#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))) -struct drm_plane *__next_plane(const struct drmdev *drmdev, const struct drm_plane *plane); +#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))) -drmModeModeInfo *__next_mode(const struct drm_connector *connector, const drmModeModeInfo *mode); +#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_connector_in_drmdev(drmdev, connector) \ - for (connector = __next_connector(drmdev, NULL); connector != NULL; connector = __next_connector(drmdev, connector)) +#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_encoder_in_drmdev(drmdev, encoder) \ - for (encoder = __next_encoder(drmdev, NULL); encoder != NULL; encoder = __next_encoder(drmdev, encoder)) -#define for_each_crtc_in_drmdev(drmdev, crtc) for (crtc = __next_crtc(drmdev, NULL); crtc != NULL; crtc = __next_crtc(drmdev, crtc)) +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 double mode_get_vrefresh(const drmModeModeInfo *mode) { + return mode->clock * 1000.0 / (mode->htotal * mode->vtotal); +} -#endif // _FLUTTERPI_SRC_MODESETTING_H +#endif \ No newline at end of file diff --git a/src/modesetting.c b/src/modesetting.c deleted file mode 100644 index 29a4024f..00000000 --- a/src/modesetting.c +++ /dev/null @@ -1,3005 +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; - } - - ASSUME(0 <= crtc_index && crtc_index < 32); - - crtc_out->id = crtc->crtc_id; - crtc_out->index = (uint8_t) 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 = PLANE_TRANSFORM_NONE, supported_rotations = PLANE_TRANSFORM_NONE, - committed_rotation = PLANE_TRANSFORM_NONE; - enum drm_blend_mode committed_blend_mode = kNone_DrmBlendMode; - enum drm_plane_type type = kPrimary_DrmPlaneType; - 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 = 0; - int64_t min_zpos = 0, max_zpos = 0, hardcoded_zpos = 0, committed_zpos = 0; - 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 = has_type ? type : kPrimary_DrmPlaneType; - plane_out->has_zpos = has_zpos; - plane_out->min_zpos = has_zpos ? min_zpos : 0; - plane_out->max_zpos = has_zpos ? max_zpos : 0; - plane_out->has_hardcoded_zpos = has_hardcoded_zpos; - plane_out->hardcoded_zpos = has_hardcoded_zpos ? hardcoded_zpos : 0; - 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 + j); - } - 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(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; -} - -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: %" PRIu32 "\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) { - 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); -} - -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); - -#ifndef DEBUG - if (plane->committed_state.has_format && plane->committed_state.format == layer->layer.format) { - needs_set_crtc = false; - } else { - needs_set_crtc = true; - } -#else - 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; - } - - struct drmdev *drmdev = builder->drmdev; - - drmdev_on_page_flip_locked( - builder->drmdev->fd, - (unsigned int) sequence, - ns / 1000000000, - ns / 1000, - builder->crtc->id, - kms_req_ref(req) - ); - - drmdev_unlock(drmdev); - } else if (blocking) { - struct drmdev *drmdev = builder->drmdev; - - // 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(drmdev); - } else { - 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: { - struct drmdev *drmdev = builder->drmdev; - kms_req_builder_unref(builder); - if (mode_blob != NULL) { - drm_mode_blob_destroy(mode_blob); - } - drmdev_unlock(drmdev); - return ok; -} - -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/plugins/multidisplay.c b/src/plugins/multidisplay.c new file mode 100644 index 00000000..2fe35144 --- /dev/null +++ b/src/plugins/multidisplay.c @@ -0,0 +1,172 @@ +#define _POSIX_C_SOURCE 200809L +#include + +#include + +#include "flutter-pi.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 EVENTS_CHANNEL "multidisplay/events" + +#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; +}; + +static void send_display_update(struct multidisplay_plugin *plugin) { + +} + +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_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_deinit(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), + EVENTS_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_init(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), EVENTS_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_plugin_init, multidisplay_plugin_deinit, NULL); diff --git a/src/plugins/multidisplay.h b/src/plugins/multidisplay.h new file mode 100644 index 00000000..e69de29b diff --git a/src/plugins/sentry/sentry.c b/src/plugins/sentry/sentry.c index 0c28a0c5..44ad72eb 100644 --- a/src/plugins/sentry/sentry.c +++ b/src/plugins/sentry/sentry.c @@ -789,7 +789,7 @@ enum plugin_init_result sentry_plugin_init(struct flutterpi *flutterpi, void **u return PLUGIN_INIT_RESULT_INITIALIZED; } -void sentry_plugin_fini(struct flutterpi *flutterpi, void *userdata) { +void sentry_plugin_deinit(struct flutterpi *flutterpi, void *userdata) { struct sentry_plugin *plugin; ASSERT_NOT_NULL(userdata); @@ -803,4 +803,4 @@ void sentry_plugin_fini(struct flutterpi *flutterpi, void *userdata) { free(plugin); } -FLUTTERPI_PLUGIN("sentry", sentry, sentry_plugin_init, sentry_plugin_fini) +FLUTTERPI_PLUGIN("sentry", sentry_plugin, sentry_plugin_init, sentry_plugin_deinit); 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 dcc50394..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(void) { - 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(void) { - 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 d466ae35..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; @@ -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(void); +#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..390555c0 100644 --- a/src/event_loop.c +++ b/src/util/event_loop.c @@ -9,15 +9,18 @@ #include #include +#include + +#include "util/lock_ops.h" #include "util/collection.h" #include "util/refcounting.h" +#include "util/logging.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) @@ -69,10 +72,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 +89,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 +98,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 +213,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 +248,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 +260,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 +285,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 +305,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 +319,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 +335,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,8 +344,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); @@ -377,8 +373,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 +388,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 +397,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 +405,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 +416,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 +428,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 +453,7 @@ struct evthread { }; struct evthread_startup_args { + struct evloop *loop; struct evthread *evthread; sem_t initialization_done; bool initialization_success; @@ -467,7 +462,6 @@ struct evthread_startup_args { static void *evthread_entry(void *userdata) { struct evthread *evthread; struct evloop *evloop; - int ok; // initialization. { @@ -480,22 +474,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 +492,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 +506,7 @@ struct evthread *evthread_start() { return NULL; } + args->loop = loop; args->evthread = NULL; args->initialization_success = false; @@ -570,12 +558,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/event_loop.h b/src/util/event_loop.h similarity index 56% rename from src/event_loop.h rename to src/util/event_loop.h index 62edabe7..14ae3348 100644 --- a/src/event_loop.h +++ b/src/util/event_loop.h @@ -10,52 +10,47 @@ #ifndef _FLUTTERPI_SRC_EVENT_LOOP_H #define _FLUTTERPI_SRC_EVENT_LOOP_H +#include + +#include + #include "util/refcounting.h" +#include "util/collection.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 }; +enum event_handler_return { EVENT_HANDLER_CONTINUE, EVENT_HANDLER_CANCEL }; 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 evthread *evthread_start_with_loop(struct evloop *loop); -struct evloop *evthread_get_evloop(struct evthread *thread); +pthread_t evthread_get_pthread(struct evthread *thread); -void evthread_join(struct evthread *thread); +void evthread_stop(struct evthread *thread); #endif // _FLUTTERPI_SRC_EVENT_LOOP_H + diff --git a/src/util/khash.h b/src/util/khash.h new file mode 100644 index 00000000..f75f3474 --- /dev/null +++ b/src/util/khash.h @@ -0,0 +1,627 @@ +/* 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 */ \ + h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (kh_is_map) 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/src/util/lock_ops.c b/src/util/lock_ops.c new file mode 100644 index 00000000..56070514 --- /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() { + 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() { + 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() { + 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() { + 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 648c9791..8e858587 100644 --- a/src/util/lock_ops.h +++ b/src/util/lock_ops.h @@ -10,9 +10,151 @@ #define _FLUTTERPI_SRC_UTIL_LOCK_OPS_H #include +#include +#include "macros.h" #include "asserts.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(); + +const pthread_mutexattr_t *get_default_recursive_mutex_attrs(); + +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_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); \ UNUSED void obj_name##_unlock(struct obj_name *obj); diff --git a/src/util/macros.h b/src/util/macros.h index 8be7fc43..777d2d94 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -523,34 +523,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__) diff --git a/src/window.c b/src/window.c index 920878ee..9ad5ae40 100644 --- a/src/window.c +++ b/src/window.c @@ -21,7 +21,8 @@ #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" @@ -162,6 +163,7 @@ struct window { */ struct { struct drmdev *drmdev; + struct drm_resources *resources; struct drm_connector *connector; struct drm_encoder *encoder; struct drm_crtc *crtc; @@ -171,6 +173,8 @@ struct window { const struct pointer_icon *pointer_icon; struct cursor_buffer *cursor; + + bool cursor_works; } kms; /** @@ -490,6 +494,8 @@ EGLSurface window_get_egl_surface(struct window *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); @@ -701,28 +707,10 @@ static void cursor_buffer_destroy(struct cursor_buffer *buffer) { 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) -static void cursor_buffer_unref_with_locked_drmdev(void *userdata) { - struct cursor_buffer *cursor; - - ASSERT_NOT_NULL(userdata); - cursor = userdata; - - if (refcount_dec(&cursor->n_refs) == false) { - cursor_buffer_destroy_with_locked_drmdev(cursor); - } -} - static int select_mode( - struct drmdev *drmdev, + struct drm_resources *resources, struct drm_connector **connector_out, struct drm_encoder **encoder_out, struct drm_crtc **crtc_out, @@ -732,12 +720,13 @@ static int select_mode( struct drm_connector *connector; struct drm_encoder *encoder; struct drm_crtc *crtc; - drmModeModeInfo *mode, *mode_iter; + drmModeModeInfo *mode; int ok; // find any connected connector - for_each_connector_in_drmdev(drmdev, connector) { - if (connector->variable_state.connection_state == kConnected_DrmConnectionState) { + drm_resources_for_each_connector(resources, connector_it) { + if (connector_it->variable_state.connection_state == DRM_CONNSTATE_CONNECTED) { + connector = connector_it; break; } } @@ -749,7 +738,7 @@ static int select_mode( mode = NULL; if (desired_videomode != NULL) { - for_each_mode_in_connector(connector, mode_iter) { + 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); @@ -786,7 +775,7 @@ static int select_mode( // 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) { + drm_connector_for_each_mode(connector, mode_iter) { if (mode_iter->type & DRM_MODE_TYPE_PREFERRED) { mode = mode_iter; break; @@ -812,8 +801,9 @@ static int select_mode( ASSERT_NOT_NULL(mode); // 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) { + drm_resources_for_each_encoder(resources, encoder_it) { + if (encoder_it->id == connector->committed_state.encoder_id) { + encoder = encoder_it; break; } } @@ -821,13 +811,13 @@ static int select_mode( // 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]) { + drm_resources_for_each_encoder(resources, encoder_it) { + if (encoder_it->id == connector->encoders[i]) { break; } } - if (encoder->encoder->possible_crtcs) { + if (encoder->possible_crtcs) { // only use this encoder if there's a crtc we can use with it break; } @@ -840,17 +830,19 @@ static int select_mode( } // Find the CRTC that's currently linked to this encoder - for_each_crtc_in_drmdev(drmdev, crtc) { - if (crtc->id == encoder->encoder->crtc_id) { + 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) { - for_each_crtc_in_drmdev(drmdev, crtc) { - if (encoder->encoder->possible_crtcs & crtc->bitmask) { + 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; } } @@ -898,6 +890,7 @@ MUST_CHECK struct window *kms_window_new( 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 ) { @@ -910,6 +903,7 @@ MUST_CHECK struct window *kms_window_new( int ok; ASSERT_NOT_NULL(drmdev); + ASSERT_NOT_NULL(resources); #if !defined(HAVE_VULKAN) ASSUME(renderer_type != kVulkan_RendererType); @@ -930,7 +924,7 @@ MUST_CHECK struct window *kms_window_new( return NULL; } - ok = select_mode(drmdev, &selected_connector, &selected_encoder, &selected_crtc, &selected_mode, desired_videomode); + ok = select_mode(resources, &selected_connector, &selected_encoder, &selected_crtc, &selected_mode, desired_videomode); if (ok != 0) { goto fail_free_window; } @@ -989,6 +983,7 @@ MUST_CHECK struct window *kms_window_new( ); window->kms.drmdev = drmdev_ref(drmdev); + window->kms.resources = drm_resources_ref(resources); window->kms.connector = selected_connector; window->kms.encoder = selected_encoder; window->kms.crtc = selected_crtc; @@ -996,6 +991,7 @@ MUST_CHECK struct window *kms_window_new( window->kms.should_apply_mode = true; window->kms.cursor = NULL; window->kms.pointer_icon = NULL; + window->kms.cursor_works = true; window->renderer_type = renderer_type; if (gl_renderer != NULL) { #ifdef HAVE_EGL_GLES2 @@ -1080,12 +1076,11 @@ void kms_window_deinit(struct window *window) { struct frame { struct tracer *tracer; struct kms_req *req; + struct drmdev *drmdev; 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; +UNUSED static void on_scanout(uint64_t vblank_ns, void *userdata) { (void) vblank_ns; (void) userdata; @@ -1101,7 +1096,7 @@ static void on_present_frame(void *userdata) { frame = userdata; TRACER_BEGIN(frame->tracer, "kms_req_commit_nonblocking"); - ok = kms_req_commit_blocking(frame->req, NULL); + ok = kms_req_commit_nonblocking(frame->req, frame->drmdev, on_scanout, NULL, NULL); TRACER_END(frame->tracer, "kms_req_commit_nonblocking"); if (ok != 0) { @@ -1121,6 +1116,7 @@ static void on_cancel_frame(void *userdata) { tracer_unref(frame->tracer); kms_req_unref(frame->req); + drmdev_unref(frame->drmdev); free(frame); } @@ -1151,7 +1147,7 @@ static int kms_window_push_composition_locked(struct window *window, struct fl_l /// 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); + builder = kms_req_builder_new_atomic(window->kms.drmdev, window->kms.resources, window->kms.crtc->id); if (builder == NULL) { ok = ENOMEM; goto fail_unref_builder; @@ -1205,12 +1201,16 @@ static int kms_window_push_composition_locked(struct window *window, struct fl_l .in_fence_fd = 0, .prefer_cursor = true, }, - cursor_buffer_unref_with_locked_drmdev, + cursor_buffer_unref_void, NULL, window->kms.cursor ); if (ok != 0) { - LOG_ERROR("Couldn't present cursor.\n"); + LOG_ERROR("Couldn't present cursor. Hardware cursor will be disabled.\n"); + + window->kms.cursor_works = false; + window->cursor_enabled = false; + cursor_buffer_unrefp(&window->kms.cursor); } else { cursor_buffer_ref(window->kms.cursor); } @@ -1233,6 +1233,7 @@ static int kms_window_push_composition_locked(struct window *window, struct fl_l frame->req = req; frame->tracer = tracer_ref(window->tracer); + frame->drmdev = drmdev_ref(window->kms.drmdev); 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); @@ -1392,54 +1393,47 @@ static struct render_surface *kms_window_get_render_surface_internal(struct wind // 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; - } + drm_resources_for_each_plane(window->kms.resources, plane_it) { + if (!(plane_it->possible_crtcs & window->kms.crtc->bitmask)) { + // Only query planes that are possible to connect to the CRTC we're using. + 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; - } + if (plane_it->type != DRM_PRIMARY_PLANE && plane_it->type != DRM_OVERLAY_PLANE) { + // We explicitly only look for primary and overlay planes. + 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; - if (n_allowed_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); - } else { - allowed_modifiers = NULL; - } - break; + 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); + + 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); + break; } if (window->renderer_type == kOpenGL_RendererType) { @@ -1543,6 +1537,11 @@ static int kms_window_set_cursor_locked( pos = has_pos ? pos : window->cursor_pos; cursor = window->kms.cursor; + if (enabled && !window->kms.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, window->pixel_ratio); diff --git a/src/window.h b/src/window.h index 7fe89d9b..ced64918 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" @@ -50,6 +52,7 @@ DECLARE_REF_OPS(window) * @param has_forced_pixel_format * @param forced_pixel_format * @param drmdev + * @param resources * @param desired_videomode * @return struct window* The new KMS window. */ @@ -65,6 +68,7 @@ struct window *kms_window_new( 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 ); From fd0bcb1a75f416ab901e8fd313c70871e49b5e81 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 4 Sep 2024 09:03:11 +0000 Subject: [PATCH 05/27] implement proper frame scheduling --- src/frame_scheduler.c | 80 ++++++++++++++++++++++++++++++++++++++----- src/frame_scheduler.h | 2 ++ src/window.c | 42 +++++++++++++++++------ 3 files changed, 106 insertions(+), 18 deletions(-) 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/window.c b/src/window.c index 9ad5ae40..04aa9992 100644 --- a/src/window.c +++ b/src/window.c @@ -1077,14 +1077,23 @@ 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) { - (void) vblank_ns; - (void) userdata; + struct frame *frame; + + ASSERT_NOT_NULL(userdata); + frame = userdata; - /// TODO: What should we do here? + // 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) { @@ -1095,17 +1104,28 @@ static void on_present_frame(void *userdata) { frame = userdata; - TRACER_BEGIN(frame->tracer, "kms_req_commit_nonblocking"); - ok = kms_req_commit_nonblocking(frame->req, frame->drmdev, on_scanout, NULL, NULL); - TRACER_END(frame->tracer, "kms_req_commit_nonblocking"); + { + // 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); + tracer_unref(frame->tracer); + kms_req_unref(frame->req); + free(frame); } - - tracer_unref(frame->tracer); - kms_req_unref(frame->req); - free(frame); } static void on_cancel_frame(void *userdata) { @@ -1114,6 +1134,7 @@ static void on_cancel_frame(void *userdata) { frame = userdata; + frame_scheduler_unref(frame->scheduler); tracer_unref(frame->tracer); kms_req_unref(frame->req); drmdev_unref(frame->drmdev); @@ -1234,6 +1255,7 @@ static int kms_window_push_composition_locked(struct window *window, struct fl_l frame->req = req; frame->tracer = tracer_ref(window->tracer); frame->drmdev = drmdev_ref(window->kms.drmdev); + frame->scheduler = frame_scheduler_ref(window->frame_scheduler); 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); From e59683091752be5a3b713f4b34f21c505316a157 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 4 Sep 2024 09:06:16 +0000 Subject: [PATCH 06/27] drmdev: call release callback before scanout callback Call the release callback for the commit that is no longer visible on screen before calling the scanout callback. The scanout callback might already start a new frame, and the release callback releases some resources that could be useful for building the new frame, so this helps avoid resource exhaustion. --- src/kms/drmdev.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/kms/drmdev.c b/src/kms/drmdev.c index 026984b2..83eb891a 100644 --- a/src/kms/drmdev.c +++ b/src/kms/drmdev.c @@ -299,13 +299,13 @@ static void drmdev_on_page_flip( mutex_unlock(&drmdev->mutex); } - 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); - } - 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( From 16a5be730e008e297e1597a86435b2602dfa2237 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 4 Sep 2024 09:06:54 +0000 Subject: [PATCH 07/27] kms req builder: rename destroy_cb -> release_cb --- src/kms/req_builder.c | 2 ++ src/kms/req_builder.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/kms/req_builder.c b/src/kms/req_builder.c index 443854be..83d17cc5 100644 --- a/src/kms/req_builder.c +++ b/src/kms/req_builder.c @@ -344,6 +344,7 @@ static void kms_req_builder_destroy(struct kms_req_builder *builder) { } if (builder->req != NULL) { drmModeAtomicFree(builder->req); + builder->req = NULL; } drm_resources_unref(builder->res); free(builder); @@ -683,6 +684,7 @@ static void on_kms_req_release(void *userdata) { b->release_cb(b->release_cb_userdata); } + ASSERT(refcount_is_one(&b->n_refs)); kms_req_builder_unref(b); } diff --git a/src/kms/req_builder.h b/src/kms/req_builder.h index 01f0840e..c2c79102 100644 --- a/src/kms/req_builder.h +++ b/src/kms/req_builder.h @@ -201,6 +201,6 @@ 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 destroy_cb); +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_ From 71ece41058a84f4f3bc4b5a0ef16e9998d13d7db Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 4 Sep 2024 09:07:53 +0000 Subject: [PATCH 08/27] document event_loop --- src/util/event_loop.h | 119 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 6 deletions(-) diff --git a/src/util/event_loop.h b/src/util/event_loop.h index 14ae3348..5c5a0eef 100644 --- a/src/util/event_loop.h +++ b/src/util/event_loop.h @@ -2,7 +2,9 @@ /* * Event Loop * - * - multithreaded 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 */ @@ -17,40 +19,145 @@ #include "util/refcounting.h" #include "util/collection.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(); DECLARE_REF_OPS(evloop) -int evloop_get_fd(struct evloop *loop); - +/** + * @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); -enum event_handler_return { EVENT_HANDLER_CONTINUE, EVENT_HANDLER_CANCEL }; - +/** + * @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 - From efacb05d558f8e4ac4333c6059959a925cbd132b Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 4 Sep 2024 09:08:29 +0000 Subject: [PATCH 09/27] egl_gbm_render_surface: check we're using at most 3 fbs in debug mode --- src/egl_gbm_render_surface.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/egl_gbm_render_surface.c b/src/egl_gbm_render_surface.c index f741d426..8973a92c 100644 --- a/src/egl_gbm_render_surface.c +++ b/src/egl_gbm_render_surface.c @@ -759,8 +759,9 @@ 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; From d4ba01f621fa949ce82b334c1e28350a3a00d174 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 4 Sep 2024 09:08:50 +0000 Subject: [PATCH 10/27] start work on compositor display infra --- src/compositor_ng.c | 122 ++++++++++++++++++++++++++++++++++++++++++-- src/compositor_ng.h | 38 +++++++++++++- 2 files changed, 156 insertions(+), 4 deletions(-) diff --git a/src/compositor_ng.c b/src/compositor_ng.c index 45dc596b..1034d0e1 100644 --- a/src/compositor_ng.c +++ b/src/compositor_ng.c @@ -149,6 +149,10 @@ struct compositor { struct evloop *raster_loop; struct evsrc *drm_monitor_evsrc; + + struct fl_display_interface display_interface; + struct display_setup *display_setup; + struct notifier display_setup_notifier; }; static bool on_flutter_present_layers(const FlutterLayer **layers, size_t layers_count, void *userdata); @@ -193,7 +197,105 @@ static const struct drm_uevent_listener uevent_listener = { .on_uevent = on_drm_uevent, }; -MUST_CHECK struct compositor *compositor_new(struct tracer *tracer, struct evloop *raster_loop, struct window *main_window, struct udev *udev, struct drmdev *drmdev, struct drm_resources *resources) { +static void connector_init(const struct drm_connector *connector, struct connector *out) { + 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, + [DRM_MODE_CONNECTOR_SPI] = CONNECTOR_TYPE_OTHER, + [DRM_MODE_CONNECTOR_USB] = CONNECTOR_TYPE_OTHER, + }; + + enum connector_type type = connector_types[connector->type]; + const char *type_name = drmModeGetConnectorTypeName(connector->type) ?: "Unknown"; + + out->id = asprintf("%s-%"PRIu32, type_name, connector->id); + out->type = type; + if (type == CONNECTOR_TYPE_OTHER) { + out->other_type_name = type_name; + } else { + out->other_type_name = NULL; + } +} + +static void connector_fini(struct connector *connector) { + free(connector->id); +} + +static void display_init(struct display *display, struct drm_crtc *crtc, struct drm_encoder *encoder, struct drm_connector *connector) { + +} + +static void display_fini(struct display *display) { + +} + +struct display_setup *display_setup_new(struct drm_resources *resources) { + struct display_setup *setup = malloc(sizeof *setup); + if (setup == NULL) { + return NULL; + } + + setup->n_connectors = resources->n_connectors; + setup->connectors = malloc(setup->n_connectors * sizeof *setup->connectors); + if (setup->connectors == NULL) { + goto fail_free_setup; + } + + for (int i = 0; i < setup->n_connectors; i++) { + connector_init(resources->connectors + i, setup->connectors + i); + } + + setup->n_displays = 0; + drm_resources_for_each_connector(resources, connector) { + if (connector->variable_state.connection_state == DRM_CONNSTATE_CONNECTED) { + setup->n_displays++; + } + } + + setup->displays = malloc(setup->n_displays * sizeof *setup->displays); + if (setup->displays == NULL) { + goto fail_fini_connectors; + } + + return setup; + +fail_fini_connectors: + for (int i = 0; i < setup->n_connectors; i++) { + connector_fini(setup->connectors + i); + } + free(setup->connectors); + +fail_free_setup: + free(setup->connectors); + return NULL; +} + +MUST_CHECK struct compositor *compositor_new_multiview( + struct tracer *tracer, + struct evloop *raster_loop, + struct window *main_window, + struct udev *udev, + struct drmdev *drmdev, + struct drm_resources *resources, + const struct fl_display_interface *display_interface +) { struct compositor *c; int bucket_status; @@ -239,7 +341,6 @@ MUST_CHECK struct compositor *compositor_new(struct tracer *tracer, struct evloo c->next_view_id = 1; c->next_platform_view_id = 1; - c->raster_loop = evloop_ref(raster_loop); c->drmdev = drmdev_ref(drmdev); @@ -262,14 +363,24 @@ MUST_CHECK struct compositor *compositor_new(struct tracer *tracer, struct evloo } 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); + + value_notifier_init(&c->display_setup_notifier, ); return c; fail_return_null: return NULL; } +struct compositor *compositor_new_singleview( + struct tracer *tracer, + struct evloop *raster_loop, + struct window *window, + const struct fl_display_interface *display_interface +) { + return compositor_new_multiview(tracer, raster_loop, window, NULL, NULL, NULL, display_interface); +} + void compositor_destroy(struct compositor *compositor) { struct window *window; kh_foreach_value(compositor->views, window, @@ -965,3 +1076,8 @@ void compositor_for_each_connector( } } } + +struct notifier *compositor_get_display_setup_notifier(struct compositor *compositor) { + ASSERT_NOT_NULL(compositor); + return &compositor->display_setup_notifier; +} diff --git a/src/compositor_ng.h b/src/compositor_ng.h index 8f37e9ac..7dd3e1fd 100644 --- a/src/compositor_ng.h +++ b/src/compositor_ng.h @@ -90,7 +90,26 @@ 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 evloop *raster_loop, struct window *main_window, struct udev *udev, struct drmdev *drmdev, struct drm_resources *resources); +struct fl_display_interface { + FlutterEngineNotifyDisplayUpdateFnPtr notify_display_update; + FlutterEngine engine; +}; + +struct compositor *compositor_new_multiview( + struct tracer *tracer, + struct evloop *raster_loop, + struct udev *udev, + struct drmdev *drmdev, + struct drm_resources *resources, + const struct fl_display_interface *display_interface +); + +struct compositor *compositor_new_singleview( + struct tracer *tracer, + struct evloop *raster_loop, + struct window *window, + const struct fl_display_interface *display_interface +); void compositor_destroy(struct compositor *compositor); @@ -279,6 +298,23 @@ void compositor_for_each_display( void *userdata ); + +struct display_setup { + size_t n_connectors; + struct connector *connectors; + + size_t n_displays; + struct display *displays; +}; + +/** + * @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; struct fl_layer_composition *fl_layer_composition_new(size_t n_layers); From 6f9bfa0fbeeea212c830beaab9292e648acb22a0 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 11 Sep 2024 11:58:13 +0000 Subject: [PATCH 11/27] add more multi-display infra --- src/compositor_ng.c | 679 +++++++++++++++++++++---------------- src/compositor_ng.h | 185 ++++------ src/flutter-pi.c | 109 +++--- src/flutter-pi.h | 4 + src/plugins/multidisplay.c | 125 ++++++- src/util/khash_uint32.h | 15 + src/util/lock_ops.h | 5 + 7 files changed, 631 insertions(+), 491 deletions(-) create mode 100644 src/util/khash_uint32.h diff --git a/src/compositor_ng.c b/src/compositor_ng.c index 1034d0e1..620ae2f5 100644 --- a/src/compositor_ng.c +++ b/src/compositor_ng.c @@ -28,19 +28,20 @@ #include "flutter-pi.h" #include "frame_scheduler.h" #include "kms/drmdev.h" +#include "kms/monitor.h" #include "kms/req_builder.h" #include "kms/resources.h" -#include "kms/monitor.h" #include "notifier_listener.h" #include "pixel_format.h" #include "render_surface.h" #include "surface.h" #include "tracer.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 "util/khash.h" -#include "util/event_loop.h" #include "window.h" #include "config.h" @@ -60,6 +61,12 @@ #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. * @@ -116,8 +123,190 @@ void fl_layer_composition_destroy(struct fl_layer_composition *composition) { DEFINE_REF_OPS(fl_layer_composition, n_refs) -KHASH_MAP_INIT_INT64(view, struct window *) -KHASH_MAP_INIT_INT64(platform_view, struct surface *) +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[]; +}; + +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) { + const char *type_name = drmModeGetConnectorTypeName(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; + /// TODO: Implement flutter display id, current mode + UNIMPLEMENTED(); + + 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; + } + + for (s->n_connectors = 0; s->n_connectors < resources->n_connectors;) { + size_t i = s->n_connectors; + + khiter_t entry = kh_get(connector_display_ids, connectors, resources->connectors[i].id); + if (entry == kh_end(connectors)) { + continue; + } + + int64_t fl_display_id = kh_value(connectors, entry); + + bool ok = connector_init(resources->connectors + i, fl_display_id, s->connectors + i); + if (!ok) { + 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) { + return CONTAINER_OF(display, struct connector, display)->name; +} /** * @brief The flutter compositor. Responsible for taking the FlutterLayers, processing them into a struct fl_layer_composition*, then passing @@ -133,11 +322,14 @@ struct compositor { struct tracer *tracer; struct window *main_window; + int64_t next_display_id; + khash_t(connector_display_ids) * connectors; + int64_t next_view_id; - khash_t(view) *views; + khash_t(view) * views; int64_t next_platform_view_id; - khash_t(platform_view) *platform_views; + khash_t(platform_view) * platform_views; FlutterCompositor flutter_compositor; @@ -150,9 +342,11 @@ struct compositor { struct evloop *raster_loop; struct evsrc *drm_monitor_evsrc; - struct fl_display_interface display_interface; - struct display_setup *display_setup; struct notifier display_setup_notifier; + + bool is_startup; + bool has_display_interface; + struct fl_display_interface display_interface; }; static bool on_flutter_present_layers(const FlutterLayer **layers, size_t layers_count, void *userdata); @@ -164,13 +358,90 @@ static bool on_flutter_collect_backing_store(const FlutterBackingStore *fl_store static bool on_flutter_present_view(const FlutterPresentViewInfo *present_info); +static int update_flutter_displays(struct compositor *c) { + int ok; + + // 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, &ok); + if (ok == -1) { + return ENOMEM; + } else if (ok == 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, + c->is_startup ? kFlutterEngineDisplaysUpdateTypeStartup : kFlutterEngineDisplaysUpdateTypeCount, + 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 enum event_handler_return on_drm_monitor_ready(int fd, uint32_t events, void *userdata) { struct compositor *compositor; ASSERT_NOT_NULL(userdata); (void) fd; (void) events; - + compositor = userdata; // This will in turn probobly call on_drm_uevent. @@ -189,119 +460,33 @@ static void on_drm_uevent(const struct drm_uevent *event, void *userdata) { drm_resources_update(compositor->resources, drmdev_get_modesetting_fd(compositor->drmdev), event); drm_resources_apply_rockchip_workaround(compositor->resources); - /// TODO: Check for added displays - UNIMPLEMENTED(); -} + update_flutter_displays(compositor); -static const struct drm_uevent_listener uevent_listener = { - .on_uevent = on_drm_uevent, -}; - -static void connector_init(const struct drm_connector *connector, struct connector *out) { - 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, - [DRM_MODE_CONNECTOR_SPI] = CONNECTOR_TYPE_OTHER, - [DRM_MODE_CONNECTOR_USB] = CONNECTOR_TYPE_OTHER, - }; - - enum connector_type type = connector_types[connector->type]; - const char *type_name = drmModeGetConnectorTypeName(connector->type) ?: "Unknown"; - - out->id = asprintf("%s-%"PRIu32, type_name, connector->id); - out->type = type; - if (type == CONNECTOR_TYPE_OTHER) { - out->other_type_name = type_name; - } else { - out->other_type_name = NULL; + 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; } -} - -static void connector_fini(struct connector *connector) { - free(connector->id); -} -static void display_init(struct display *display, struct drm_crtc *crtc, struct drm_encoder *encoder, struct drm_connector *connector) { - + notifier_notify(&compositor->display_setup_notifier, display_setup); } -static void display_fini(struct display *display) { - -} - -struct display_setup *display_setup_new(struct drm_resources *resources) { - struct display_setup *setup = malloc(sizeof *setup); - if (setup == NULL) { - return NULL; - } - - setup->n_connectors = resources->n_connectors; - setup->connectors = malloc(setup->n_connectors * sizeof *setup->connectors); - if (setup->connectors == NULL) { - goto fail_free_setup; - } - - for (int i = 0; i < setup->n_connectors; i++) { - connector_init(resources->connectors + i, setup->connectors + i); - } - - setup->n_displays = 0; - drm_resources_for_each_connector(resources, connector) { - if (connector->variable_state.connection_state == DRM_CONNSTATE_CONNECTED) { - setup->n_displays++; - } - } - - setup->displays = malloc(setup->n_displays * sizeof *setup->displays); - if (setup->displays == NULL) { - goto fail_fini_connectors; - } - - return setup; - -fail_fini_connectors: - for (int i = 0; i < setup->n_connectors; i++) { - connector_fini(setup->connectors + i); - } - free(setup->connectors); - -fail_free_setup: - free(setup->connectors); - return NULL; -} +static const struct drm_uevent_listener uevent_listener = { + .on_uevent = on_drm_uevent, +}; MUST_CHECK struct compositor *compositor_new_multiview( struct tracer *tracer, struct evloop *raster_loop, - struct window *main_window, struct udev *udev, struct drmdev *drmdev, - struct drm_resources *resources, - const struct fl_display_interface *display_interface + struct drm_resources *resources ) { struct compositor *c; - int bucket_status; + int ok; ASSERT_NOT_NULL(tracer); ASSERT_NOT_NULL(raster_loop); - ASSERT_NOT_NULL(main_window); ASSERT_NOT_NULL(drmdev); c = calloc(1, sizeof *c); @@ -312,16 +497,10 @@ MUST_CHECK struct compositor *compositor_new_multiview( mutex_init(&c->mutex); c->views = kh_init(view); c->platform_views = kh_init(platform_view); - - khiter_t entry = kh_put(view, c->views, 0, &bucket_status); - if (bucket_status == -1) { - goto fail_return_null; - } - - kh_value(c->views, entry) = window_ref(main_window); + c->connectors = kh_init(connector_display_ids); c->n_refs = REFCOUNT_INIT_1; - c->main_window = window_ref(main_window); + c->main_window = NULL; // just so we get an error if the FlutterCompositor struct was updated COMPILE_ASSERT(sizeof(FlutterCompositor) == 28 || sizeof(FlutterCompositor) == 56); @@ -337,9 +516,10 @@ MUST_CHECK struct compositor *compositor_new_multiview( c->tracer = tracer_ref(tracer); c->cursor_pos = VEC2F(0, 0); - + c->next_view_id = 1; c->next_platform_view_id = 1; + c->next_display_id = 1; c->raster_loop = evloop_ref(raster_loop); c->drmdev = drmdev_ref(drmdev); @@ -365,36 +545,47 @@ MUST_CHECK struct compositor *compositor_new_multiview( 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); - value_notifier_init(&c->display_setup_notifier, ); + c->is_startup = false; + c->has_display_interface = false; + + ok = update_flutter_displays(c); + if (ok != 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); return c; fail_return_null: return NULL; } -struct compositor *compositor_new_singleview( - struct tracer *tracer, - struct evloop *raster_loop, - struct window *window, - const struct fl_display_interface *display_interface -) { - return compositor_new_multiview(tracer, raster_loop, window, NULL, NULL, NULL, display_interface); +struct compositor *compositor_new_singleview(struct tracer *tracer, struct evloop *raster_loop, struct window *window) { + struct compositor *c = compositor_new_multiview(tracer, raster_loop, NULL, NULL, NULL); + if (c == NULL) { + return NULL; + } + + compositor_add_view(c, window); + return c; } void compositor_destroy(struct compositor *compositor) { struct window *window; - kh_foreach_value(compositor->views, window, - window_unref(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_foreach_value(compositor->platform_views, surface, surface_unref(surface);) - kh_destroy(platform_view, compositor->platform_views); + kh_destroy(platform_view, compositor->platform_views); if (compositor->drm_monitor_evsrc != NULL) { evsrc_destroy(compositor->drm_monitor_evsrc); @@ -452,10 +643,55 @@ static struct window *compositor_get_view_by_id(struct compositor *compositor, i compositor_lock(compositor); struct window *window = compositor_get_view_by_id_locked(compositor, view_id); compositor_unlock(compositor); - + return 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); + + compositor->has_display_interface = true; + compositor->display_interface = *display_interface; + + // register flutter displays + update_flutter_displays(compositor); + + // send window metrics event + 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) == 96); + + FlutterWindowMetricsEvent event; + memset(&event, 0, sizeof event); + + event.struct_size = sizeof(FlutterWindowMetricsEvent); + event.width = geo.view_size.x; + event.height = geo.view_size.y; + event.pixel_ratio = 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) + ); + } + }); +} + void compositor_get_view_geometry(struct compositor *compositor, struct view_geometry *view_geometry_out) { *view_geometry_out = window_get_view_geometry(compositor->main_window); } @@ -470,7 +706,8 @@ int compositor_get_next_vblank(struct compositor *compositor, uint64_t *next_vbl return window_get_next_vblank(compositor->main_window, next_vblank_ns_out); } -static int compositor_push_composition(struct compositor *compositor, bool has_view_id, int64_t view_id, struct fl_layer_composition *composition) { +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 ok; @@ -578,7 +815,13 @@ static void fill_platform_view_layer_props( props_out->clip_rects = NULL; } -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) { +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; struct view_geometry geometry; struct window *window; @@ -858,124 +1101,7 @@ void compositor_set_cursor( compositor_unlock(compositor); } -static bool call_display_callback_with_drm_connector(display_callback_t callback, const struct drm_connector *connector, void *userdata) { - (void) callback; - (void) connector; - (void) userdata; - - /// TODO: Implement - UNIMPLEMENTED(); -} - -static bool call_connector_callback_with_drm_connector(connector_callback_t callback, const struct drm_connector *connector, void *userdata) { - struct connector conn = { - .id = NULL, - .type = CONNECTOR_TYPE_OTHER, - .other_type_name = NULL, - }; - - switch (connector->type) { - case DRM_MODE_CONNECTOR_Unknown: - conn.id = alloca_sprintf("Unknown-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_OTHER; - conn.other_type_name = "Unknown"; - break; - case DRM_MODE_CONNECTOR_VGA: - conn.id = alloca_sprintf("VGA-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_VGA; - break; - case DRM_MODE_CONNECTOR_DVII: - conn.id = alloca_sprintf("DVI-I-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_DVI; - break; - case DRM_MODE_CONNECTOR_DVID: - conn.id = alloca_sprintf("DVI-D-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_DVI; - break; - case DRM_MODE_CONNECTOR_DVIA: - conn.id = alloca_sprintf("DVI-A-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_DVI; - break; - case DRM_MODE_CONNECTOR_Composite: - conn.id = alloca_sprintf("Composite-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_OTHER; - conn.other_type_name = "Composite"; - break; - case DRM_MODE_CONNECTOR_SVIDEO: - conn.id = alloca_sprintf("SVIDEO-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_OTHER; - conn.other_type_name = "SVIDEO"; - break; - case DRM_MODE_CONNECTOR_LVDS: - conn.id = alloca_sprintf("LVDS-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_LVDS; - break; - case DRM_MODE_CONNECTOR_Component: - conn.id = alloca_sprintf("Component-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_OTHER; - conn.other_type_name = "Component"; - break; - case DRM_MODE_CONNECTOR_9PinDIN: - conn.id = alloca_sprintf("DIN-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_OTHER; - conn.other_type_name = "DIN"; - break; - case DRM_MODE_CONNECTOR_DisplayPort: - conn.id = alloca_sprintf("DP-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_DISPLAY_PORT; - break; - case DRM_MODE_CONNECTOR_HDMIA: - conn.id = alloca_sprintf("HDMI-A-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_HDMI; - break; - case DRM_MODE_CONNECTOR_HDMIB: - conn.id = alloca_sprintf("HDMI-B-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_HDMI; - break; - case DRM_MODE_CONNECTOR_TV: - conn.id = alloca_sprintf("TV-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_TV; - break; - case DRM_MODE_CONNECTOR_eDP: - conn.id = alloca_sprintf("eDP-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_EDP; - break; - case DRM_MODE_CONNECTOR_VIRTUAL: - conn.id = alloca_sprintf("Virtual-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_OTHER; - conn.other_type_name = "Virtual"; - break; - case DRM_MODE_CONNECTOR_DSI: - conn.id = alloca_sprintf("DSI-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_DSI; - break; - case DRM_MODE_CONNECTOR_DPI: - conn.id = alloca_sprintf("DPI-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_DPI; - break; - case DRM_MODE_CONNECTOR_WRITEBACK: - return true; - case DRM_MODE_CONNECTOR_SPI: - conn.id = alloca_sprintf("SPI-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_OTHER; - conn.other_type_name = "SPI"; - break; - case DRM_MODE_CONNECTOR_USB: - conn.id = alloca_sprintf("USB-%" PRIu32, connector->type_id); - conn.type = CONNECTOR_TYPE_OTHER; - conn.other_type_name = "USB"; - break; - default: - return true; - } - - return callback(&conn, userdata); -} - -int64_t compositor_add_view( - struct compositor *compositor, - struct window *window -) { +int64_t compositor_add_view(struct compositor *compositor, struct window *window) { ASSERT_NOT_NULL(compositor); ASSERT_NOT_NULL(window); int64_t view_id; @@ -992,10 +1118,7 @@ int64_t compositor_add_view( return view_id; } -void compositor_remove_view( - struct compositor *compositor, - int64_t view_id -) { +void compositor_remove_view(struct compositor *compositor, int64_t view_id) { ASSERT_NOT_NULL(compositor); ASSERT(view_id != 0); @@ -1010,10 +1133,7 @@ void compositor_remove_view( compositor_unlock(compositor); } -int compositor_put_implicit_view( - struct compositor *compositor, - struct window *window -) { +int compositor_put_implicit_view(struct compositor *compositor, struct window *window) { int bucket_status; ASSERT_NOT_NULL(compositor); @@ -1038,45 +1158,6 @@ int compositor_put_implicit_view( return 0; } -void compositor_for_each_display( - struct compositor *compositor, - display_callback_t callback, - void *userdata -) { - struct drm_connector *connector; - - (void) compositor; - (void) callback; - (void) userdata; - (void) connector; - - /// TODO: Implement - UNIMPLEMENTED(); - - /// TODO: drm_resources is not mt-safe, but compositor_for_each_connector might not be called on the raster thread - /// (which uses drm_resources) - - drm_resources_for_each_connector(compositor->resources, connector) { - if (!call_display_callback_with_drm_connector(callback, connector, userdata)) { - break; - } - } -} - -void compositor_for_each_connector( - struct compositor *compositor, - connector_callback_t callback, - void *userdata -) { - /// TODO: drm_resources is not mt-safe, but compositor_for_each_connector might not be called on the raster thread - /// (which uses drm_resources) - drm_resources_for_each_connector(compositor->resources, connector) { - if (!call_connector_callback_with_drm_connector(callback, connector, userdata)) { - break; - } - } -} - struct notifier *compositor_get_display_setup_notifier(struct compositor *compositor) { ASSERT_NOT_NULL(compositor); return &compositor->display_setup_notifier; diff --git a/src/compositor_ng.h b/src/compositor_ng.h index 7dd3e1fd..1f69ca9b 100644 --- a/src/compositor_ng.h +++ b/src/compositor_ng.h @@ -27,7 +27,6 @@ struct compositor; - struct fl_layer_props { /** * @brief True if the presentation quadrangle (the quadrangle on the target window into which the @@ -92,6 +91,7 @@ typedef void (*compositor_frame_begin_cb_t)(void *userdata, uint64_t vblank_ns, struct fl_display_interface { FlutterEngineNotifyDisplayUpdateFnPtr notify_display_update; + FlutterEngineSendWindowMetricsEventFnPtr send_window_metrics_event; FlutterEngine engine; }; @@ -100,21 +100,26 @@ struct compositor *compositor_new_multiview( struct evloop *raster_loop, struct udev *udev, struct drmdev *drmdev, - struct drm_resources *resources, - const struct fl_display_interface *display_interface + struct drm_resources *resources ); -struct compositor *compositor_new_singleview( - struct tracer *tracer, - struct evloop *raster_loop, - struct window *window, - const struct fl_display_interface *display_interface -); +struct compositor *compositor_new_singleview(struct tracer *tracer, struct evloop *raster_loop, struct window *window); 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); + void compositor_get_view_geometry(struct compositor *compositor, struct view_geometry *view_geometry_out); ATTR_PURE double compositor_get_refresh_rate(struct compositor *compositor); @@ -124,26 +129,17 @@ int compositor_get_next_vblank(struct compositor *compositor, uint64_t *next_vbl /** * @brief Adds a (non-implicit) view to the compositor, returning the view id. */ -int64_t compositor_add_view( - struct compositor *compositor, - struct window *window -); +int64_t compositor_add_view(struct compositor *compositor, struct window *window); /** * @brief Removes a view from the compositor. */ -void compositor_remove_view( - struct compositor *compositor, - int64_t view_id -); +void compositor_remove_view(struct compositor *compositor, int64_t view_id); /** * @brief Sets the implicit view (view with id 0) to the given window. */ -int compositor_put_implicit_view( - struct compositor *compositor, - struct window *window -); +int compositor_put_implicit_view(struct compositor *compositor, struct window *window); /** * @brief Adds a platform view to the compositor, returning the platform view id. @@ -155,7 +151,6 @@ int compositor_add_platform_view(struct compositor *compositor, struct surface * */ void compositor_remove_platform_view(struct compositor *compositor, int64_t view_id); - const FlutterCompositor *compositor_get_flutter_compositor(struct compositor *compositor); int compositor_request_frame(struct compositor *compositor, compositor_frame_begin_cb_t cb, void *userdata); @@ -193,119 +188,82 @@ enum connector_type { CONNECTOR_TYPE_OTHER, }; -struct connector { - /** - * @brief The ID of the connector. - * - * e.g. `HDMI-A-1`, `DP-1`, `LVDS-1`, etc. - * - * This string will only live till the end of the iteration, so make sure to copy it if you need it later. - */ - const char *id; +struct display_setup; +struct connector; +struct display; - /** - * @brief The type of the connector. - */ - enum connector_type type; - - /** - * @brief The name of the connector type, if @ref type is @ref CONNECTOR_TYPE_OTHER. - * - * e.g. `Virtual`, `Composite`, etc. - * - * This string will only live till the end of the iteration, so make sure to copy it if you need it later. - */ - const char *other_type_name; -}; +DECLARE_REF_OPS(display_setup) /** - * @brief Callback that will be called on each iteration of - * @ref compositor_for_each_connector. - * - * Should return true if looping should continue. False if iterating should be - * stopped. - * - * @param display The current iteration value. - * @param userdata Userdata that was passed to @ref compositor_for_each_connector. + * @brief Gets the number of connectors present in this display setup. */ -typedef bool (*connector_callback_t)(const struct connector *connector, void *userdata); +size_t display_setup_get_n_connectors(struct display_setup *s); /** - * @brief Iterates over every present connector. - * - * See @ref connector_callback_t for documentation on the callback. + * @brief Gets the connector at the given index. */ -void compositor_for_each_connector( - struct compositor *compositor, - connector_callback_t callback, - void *userdata -); +const struct connector *display_setup_get_connector(struct display_setup *s, size_t i); -struct display { - /** - * @brief The ID of the display, as reported to flutter. - */ - uint64_t fl_display_id; +/** + * @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 The refresh rate of the display. - */ - double refresh_rate; +/** + * @brief Gets the type of the connector. + */ +enum connector_type connector_get_type(const struct connector *connector); - /** - * @brief The width of the display in the selected mode, in physical pixels. - */ - size_t width; +/** + * @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 The height of the display in the selected mode, in physical pixels. - */ - size_t height; +/** + * @brief Checks if the connector has a display attached. + */ +bool connector_has_display(const struct connector *connector); - /** - * @brief The device pixel ratio of the display, in the selected mode. - */ - double device_pixel_ratio; +/** + * @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 The identifier of the connector this display is connected to. - * - * This string will only live till the end of the iteration, so make sure to copy it if you need it later. - */ - const char *connector_id; -}; +/** + * @brief Gets the ID of the display as reported to flutter. + */ +size_t display_get_fl_display_id(const struct display *display); /** - * @brief Callback that will be called on each iteration of - * @ref compositor_for_each_display. - * - * Should return true if looping should continue. False if iterating should be - * stopped. - * - * @param display The current iteration value. - * @param userdata Userdata that was passed to @ref compositor_for_each_display. + * @brief Gets the refresh rate of the display in the current mode. */ -typedef bool (*display_callback_t)(const struct display *display, void *userdata); +double display_get_refresh_rate(const struct display *display); /** - * @brief Iterates over every connected display. - * - * See @ref display_callback_t for documentation on the callback. + * @brief Gets the width of the display in the current mode, in physical pixels. */ -void compositor_for_each_display( - struct compositor *compositor, - display_callback_t callback, - void *userdata -); +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); -struct display_setup { - size_t n_connectors; - struct connector *connectors; +/** + * @brief Gets the device pixel ratio of the display in the current mode. + */ +double display_get_device_pixel_ratio(const struct display *display); - size_t n_displays; - struct display *displays; -}; +/** + * @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. @@ -314,7 +272,6 @@ struct display_setup { */ struct notifier *compositor_get_display_setup_notifier(struct compositor *compositor); - struct fl_layer_composition; struct fl_layer_composition *fl_layer_composition_new(size_t n_layers); diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 4e51aaa5..296fa2cc 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -44,10 +44,10 @@ #include "filesystem_layout.h" #include "frame_scheduler.h" #include "keyboard.h" -#include "locales.h" #include "kms/drmdev.h" #include "kms/req_builder.h" #include "kms/resources.h" +#include "locales.h" #include "pixel_format.h" #include "platformchannel.h" #include "pluginregistry.h" @@ -56,10 +56,10 @@ #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" -#include "util/event_loop.h" #include "config.h" @@ -224,7 +224,7 @@ struct flutterpi { pthread_t platform_thread; struct evsrc *drmdev_evrsc; - + /** * @brief The user input instance. * @@ -464,7 +464,6 @@ static bool flutterpi_runs_raster_tasks_on_current_thread(struct flutterpi *flut return pthread_equal(pthread_self(), evthread_get_pthread(flutterpi->raster_thread)) != 0; } - struct frame_req { struct flutterpi *flutterpi; intptr_t baton; @@ -552,8 +551,7 @@ UNUSED static void on_frame_request(void *userdata, intptr_t baton) { if (flutterpi_runs_platform_tasks_on_current_thread(flutterpi)) { TRACER_INSTANT(req->flutterpi->tracer, "FlutterEngineOnVsync"); - engine_result = - req->flutterpi->flutter.procs.OnVsync(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; @@ -901,6 +899,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; @@ -1245,7 +1251,6 @@ static FlutterEngine create_flutter_engine( static int flutterpi_run(struct flutterpi *flutterpi) { FlutterEngineProcTable *procs; - struct view_geometry geometry; FlutterEngineResult engine_result; int ok; @@ -1301,50 +1306,16 @@ static int flutterpi_run(struct flutterpi *flutterpi) { 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(flutterpi->flutter.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 = (size_t) geometry.view_size.x; - window_metrics_event.height = (size_t) geometry.view_size.y; - window_metrics_event.pixel_ratio = (double) 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(flutterpi->flutter.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; - } + // 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, + } + ); evloop_run(flutterpi->platform_loop); @@ -2009,14 +1980,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; @@ -2093,11 +2062,15 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { #ifdef HAVE_LIBSEAT { - static const struct libseat_seat_listener libseat_interface = { .enable_seat = on_session_enable, .disable_seat = on_session_disable }; + static const struct libseat_seat_listener libseat_interface = { .enable_seat = on_session_enable, + .disable_seat = on_session_disable }; 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)); + LOG_DEBUG( + "Couldn't open libseat. Flutter-pi will run without session switching support. libseat_open_seat: %s\n", + strerror(errno) + ); } int fd; @@ -2174,7 +2147,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { 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"); @@ -2290,7 +2263,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { frame_scheduler_unref(scheduler); } - fpi->compositor = compositor_new(fpi->tracer, fpi->raster_evloop, window, NULL, fpi->drmdev, drm_resources); + fpi->compositor = compositor_new_multiview(fpi->tracer, fpi->raster_evloop, NULL, fpi->drmdev, drm_resources); window_unref(window); @@ -2302,7 +2275,13 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { /// 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); + 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; } @@ -2336,7 +2315,13 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { if (fpi->user_input == NULL) { LOG_ERROR("Couldn't initialize user input. flutter-pi will run without user input.\n"); } else { - 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); + 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"); user_input_destroy(fpi->user_input); @@ -2477,7 +2462,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { fail_destroy_libseat: if (fpi->libseat != NULL) { #ifdef HAVE_LIBSEAT - libseat_close_seat(libseat); + libseat_close_seat(fpi->libseat); #else UNREACHABLE(); #endif @@ -2532,7 +2517,7 @@ void flutterpi_destroy(struct flutterpi *fpi) { evloop_unref(fpi->raster_evloop); if (fpi->libseat != NULL) { #ifdef HAVE_LIBSEAT - libseat_close_seat(libseat); + libseat_close_seat(fpi->libseat); #else UNREACHABLE(); #endif diff --git a/src/flutter-pi.h b/src/flutter-pi.h index d3af8f36..79f7d37e 100644 --- a/src/flutter-pi.h +++ b/src/flutter-pi.h @@ -181,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/plugins/multidisplay.c b/src/plugins/multidisplay.c index 2fe35144..cfa69f45 100644 --- a/src/plugins/multidisplay.c +++ b/src/plugins/multidisplay.c @@ -3,28 +3,103 @@ #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 EVENTS_CHANNEL "multidisplay/events" +#define DISPLAY_SETUP_CHANNEL "multidisplay/display_setup" #define MULTIDISPLAY_PLUGIN_DEBUG 1 -#define LOG_MULTIDISPLAY_DEBUG(fmt, ...) \ +#define LOG_MULTIDISPLAY_DEBUG(fmt, ...) \ do { \ - if (MULTIDISPLAY_PLUGIN_DEBUG) \ + 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) { - +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) { @@ -77,6 +152,25 @@ static void on_view_controller_method_call(void *userdata, const FlutterPlatform } } +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; @@ -102,7 +196,7 @@ static void on_event_channel_method_call(void *userdata, const FlutterPlatformMe } } -enum plugin_init_result multidisplay_plugin_deinit(struct flutterpi *flutterpi, void **userdata_out) { +enum plugin_init_result multidisplay_plugin_init(struct flutterpi *flutterpi, void **userdata_out) { struct multidisplay_plugin *plugin; int ok; @@ -133,7 +227,7 @@ enum plugin_init_result multidisplay_plugin_deinit(struct flutterpi *flutterpi, ok = plugin_registry_set_receiver_v2_locked( flutterpi_get_plugin_registry(flutterpi), - EVENTS_CHANNEL, + DISPLAY_SETUP_CHANNEL, on_event_channel_method_call, plugin ); @@ -145,28 +239,27 @@ enum plugin_init_result multidisplay_plugin_deinit(struct flutterpi *flutterpi, return PLUGIN_INIT_RESULT_INITIALIZED; - - fail_remove_view_controller_receiver: +fail_remove_view_controller_receiver: plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), VIEW_CONTROLLER_CHANNEL); - fail_remove_display_maanger_receiver: +fail_remove_display_maanger_receiver: plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), DISPLAY_MANAGER_CHANNEL); - fail_free_plugin: +fail_free_plugin: free(plugin); - return PLUGIN_INIT_RESULT_ERROR; + return PLUGIN_INIT_RESULT_ERROR; } -void multidisplay_plugin_init(struct flutterpi *flutterpi, void *userdata) { +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), EVENTS_CHANNEL); + 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); + free(plugin); } -FLUTTERPI_PLUGIN("multidisplay", multidisplay_plugin_init, multidisplay_plugin_deinit, NULL); +FLUTTERPI_PLUGIN("multidisplay", multidisplay, multidisplay_plugin_init, multidisplay_plugin_deinit); 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.h b/src/util/lock_ops.h index 8e858587..05ae6f57 100644 --- a/src/util/lock_ops.h +++ b/src/util/lock_ops.h @@ -103,6 +103,11 @@ static inline void mutex_init_recursive(mutex_t *restrict mutex) { 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."); From f975b48006f1baba713e08f4eac667f666fdee58 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 11 Sep 2024 11:58:31 +0000 Subject: [PATCH 12/27] format lock_ops.h --- src/util/lock_ops.h | 77 ++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 50 deletions(-) diff --git a/src/util/lock_ops.h b/src/util/lock_ops.h index 05ae6f57..9dd70ee7 100644 --- a/src/util/lock_ops.h +++ b/src/util/lock_ops.h @@ -9,83 +9,62 @@ #ifndef _FLUTTERPI_SRC_UTIL_LOCK_OPS_H #define _FLUTTERPI_SRC_UTIL_LOCK_OPS_H -#include #include -#include "macros.h" +#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)) + #define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) #else -#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op + #define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op #endif -#define CAPABILITY(x) \ - THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) +#define CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) -#define SCOPED_CAPABILITY \ - THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) +#define SCOPED_CAPABILITY THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) -#define GUARDED_BY(x) \ - THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) +#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) -#define PT_GUARDED_BY(x) \ - THREAD_ANNOTATION_ATTRIBUTE__(pt_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_BEFORE(...) THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) -#define ACQUIRED_AFTER(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) +#define ACQUIRED_AFTER(...) THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) -#define REQUIRES(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) +#define REQUIRES(...) THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) -#define REQUIRES_SHARED(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_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(...) THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) -#define ACQUIRE_SHARED(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_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(...) THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) -#define RELEASE_SHARED(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(release_shared_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 RELEASE_GENERIC(...) THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__)) -#define TRY_ACQUIRE(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_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 TRY_ACQUIRE_SHARED(...) THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) -#define EXCLUDES(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) +#define EXCLUDES(...) THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) -#define ASSERT_CAPABILITY(x) \ - THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) +#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) +#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(); @@ -124,12 +103,11 @@ static inline void mutex_unlock(mutex_t *mutex) RELEASE() { 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."); + 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() { @@ -159,7 +137,6 @@ static inline void rwlock_unlock(rwlock_t *rwlock) RELEASE() { ASSERT_ZERO_MSG(ok, "Error unlocking rwlock."); } - #define DECLARE_LOCK_OPS(obj_name) \ UNUSED void obj_name##_lock(struct obj_name *obj); \ UNUSED void obj_name##_unlock(struct obj_name *obj); From 53758b0f65cfe2e2081dae0a89178a8e84ffc7c1 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 11 Sep 2024 11:58:39 +0000 Subject: [PATCH 13/27] rework cmake presets --- CMakePresets.json | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index fc0871c0..c4105a8a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -2,24 +2,30 @@ "version": 2, "configurePresets": [ { - "name": "default-debug", - "displayName": "Default OpenGL host build (Debug)", + "name": "default", + "displayName": "Default OpenGL host build", "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, - "TRY_ENABLE_VULKAN": false, - "ENABLE_VULKAN": 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_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 } @@ -27,33 +33,17 @@ { "name": "default-relwithdebinfo", "displayName": "Default OpenGL host build (Release with Debug Info)", - "description": "Sets Ninja generator, build and install directory", - "generator": "Ninja", - "binaryDir": "${sourceDir}/out/build/${presetName}", + "inherits": "default", "cacheVariables": { - "CMAKE_BUILD_TYPE": "RelWithDebInfo", - "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", - "ENABLE_OPENGL": true, - "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN": true, - "BUILD_SENTRY_PLUGIN": true, - "ENABLE_TESTS": false, - "ENABLE_ASAN": false + "CMAKE_BUILD_TYPE": "RelWithDebInfo" } }, { "name": "default-release", "displayName": "Default OpenGL host build (Release)", - "description": "Sets Ninja generator, build and install directory", - "generator": "Ninja", - "binaryDir": "${sourceDir}/out/build/${presetName}", + "inherits": "default", "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", - "ENABLE_OPENGL": true, - "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN": true, - "BUILD_SENTRY_PLUGIN": true, - "ENABLE_TESTS": false, - "ENABLE_ASAN": false + "CMAKE_BUILD_TYPE": "Release" } }, { From 5b93934a0f88173ae70b0b01c465900f45ccf8d3 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 11 Sep 2024 12:20:54 +0000 Subject: [PATCH 14/27] fix gstreamer plugins --- CMakePresets.json | 1 + src/flutter-pi.c | 5 ++-- src/flutter-pi.h | 4 +-- src/plugins/audioplayers.h | 5 ++-- src/plugins/audioplayers/player.c | 30 +++++++++++++-------- src/plugins/audioplayers/plugin.c | 2 +- src/plugins/gstreamer_video_player/player.c | 21 ++++++++++----- 7 files changed, 42 insertions(+), 26 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index c4105a8a..5ce46cb1 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -13,6 +13,7 @@ "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, diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 296fa2cc..b94d53de 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -599,9 +599,8 @@ int flutterpi_post_delayed_platform_task(struct flutterpi *flutterpi, void_callb return evloop_post_delayed_task(flutterpi->platform_loop, callback, userdata, target_time_usec); } -/// TODO: Remove -struct evsrc *flutterpi_add_io_source(struct flutterpi *flutterpi, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata) { - return evloop_add_io(flutterpi->platform_loop, fd, events, callback, userdata); +struct evloop *flutterpi_get_platform_event_loop(struct flutterpi *flutterpi) { + return flutterpi->platform_loop; } struct fl_task { diff --git a/src/flutter-pi.h b/src/flutter-pi.h index 79f7d37e..881cfa7e 100644 --- a/src/flutter-pi.h +++ b/src/flutter-pi.h @@ -144,9 +144,9 @@ int flutterpi_post_platform_task(struct flutterpi *flutterpi, void_callback_t ca int flutterpi_post_delayed_platform_task(struct flutterpi *flutterpi, void_callback_t callback, void *userdata, uint64_t target_time_usec); -struct evloop *flutterpi_get_raster_event_loop(struct flutterpi *flutterpi); +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, 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 87f259a3..3ba82f52 100644 --- a/src/plugins/audioplayers/player.c +++ b/src/plugins/audioplayers/player.c @@ -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,9 +67,7 @@ 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) { @@ -79,9 +79,8 @@ static void audio_player_source_setup(GstElement *playbin, GstElement *source, G } } -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); @@ -602,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 98883c34..1e7e7c84 100644 --- a/src/plugins/audioplayers/plugin.c +++ b/src/plugins/audioplayers/plugin.c @@ -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); diff --git a/src/plugins/gstreamer_video_player/player.c b/src/plugins/gstreamer_video_player/player.c index 71d1bf2a..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; }; @@ -613,11 +614,10 @@ static void on_bus_message(struct gstplayer *player, GstMessage *msg) { 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; @@ -633,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) { @@ -849,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; @@ -961,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); @@ -994,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)); From 0af7fca0426c852dfdb7b097e888a3bf6cb99278 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 15 Sep 2024 15:20:08 +0000 Subject: [PATCH 15/27] WIP --- CMakeLists.txt | 2 + src/compositor_ng.c | 848 ++++++++++++++++--- src/compositor_ng.h | 39 +- src/dummy_window.c | 212 +++++ src/dummy_window.h | 25 + src/flutter-pi.c | 398 ++++----- src/kms/drmdev.h | 12 +- src/kms/kms_window.c | 1046 +++++++++++++++++++++++ src/kms/kms_window.h | 33 + src/plugins/text_input.c | 49 +- src/plugins/text_input.h | 15 +- src/user_input.c | 1071 ++++++++++++------------ src/user_input.h | 125 ++- src/util/file_interface.h | 12 + src/util/geometry.h | 13 + src/util/kvec.h | 122 +++ src/window.c | 1658 ++++--------------------------------- src/window.h | 114 ++- src/window_private.h | 260 ++++++ 19 files changed, 3551 insertions(+), 2503 deletions(-) create mode 100644 src/dummy_window.c create mode 100644 src/dummy_window.h create mode 100644 src/kms/kms_window.c create mode 100644 src/kms/kms_window.h create mode 100644 src/util/file_interface.h create mode 100644 src/util/kvec.h create mode 100644 src/window_private.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bf6c74bf..8647f076 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,7 @@ add_library( 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 @@ -151,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 ) diff --git a/src/compositor_ng.c b/src/compositor_ng.c index 620ae2f5..0b1d86a9 100644 --- a/src/compositor_ng.c +++ b/src/compositor_ng.c @@ -36,10 +36,12 @@ #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/kvec.h" #include "util/logging.h" #include "util/refcounting.h" #include "window.h" @@ -198,8 +200,6 @@ bool connector_init(const struct drm_connector *connector, int64_t fl_display_id if (connector->variable_state.connection_state == DRM_CONNSTATE_CONNECTED) { out->has_display = true; - /// TODO: Implement flutter display id, current mode - UNIMPLEMENTED(); out->display.fl_display_id = fl_display_id; out->display.refresh_rate = 60.0; @@ -226,18 +226,21 @@ struct display_setup *display_setup_new(struct drm_resources *resources, khash_t return NULL; } - for (s->n_connectors = 0; s->n_connectors < resources->n_connectors;) { - size_t i = s->n_connectors; + 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[i].id); + 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 ok = connector_init(resources->connectors + i, fl_display_id, s->connectors + i); - if (!ok) { + bool bucket_status = connector_init(resources->connectors + i, fl_display_id, s->connectors + s->n_connectors); + if (!bucket_status) { continue; } @@ -320,7 +323,7 @@ struct compositor { pthread_mutex_t mutex; struct tracer *tracer; - struct window *main_window; + struct window *implicit_view_fallback; int64_t next_display_id; khash_t(connector_display_ids) * connectors; @@ -333,8 +336,6 @@ struct compositor { FlutterCompositor flutter_compositor; - struct vec2f cursor_pos; - struct drmdev *drmdev; struct drm_monitor *monitor; struct drm_resources *resources; @@ -347,6 +348,18 @@ struct compositor { 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); @@ -359,7 +372,7 @@ static bool on_flutter_collect_backing_store(const FlutterBackingStore *fl_store static bool on_flutter_present_view(const FlutterPresentViewInfo *present_info); static int update_flutter_displays(struct compositor *c) { - int ok; + int bucket_status; // Allocate the display list on the stack. FlutterEngineDisplay *displays = NULL; @@ -377,10 +390,10 @@ static int update_flutter_displays(struct compositor *c) { if (connector->variable_state.connection_state == DRM_CONNSTATE_CONNECTED) { int64_t id; - khiter_t entry = kh_put(connector_display_ids, c->connectors, connector->id, &ok); - if (ok == -1) { + khiter_t entry = kh_put(connector_display_ids, c->connectors, connector->id, &bucket_status); + if (bucket_status == -1) { return ENOMEM; - } else if (ok == 0) { + } else if (bucket_status == 0) { // We already know this display. id = kh_value(c->connectors, entry); } else { @@ -417,7 +430,7 @@ static int update_flutter_displays(struct compositor *c) { if (displays != NULL) { FlutterEngineResult engine_result = c->display_interface.notify_display_update( c->display_interface.engine, - c->is_startup ? kFlutterEngineDisplaysUpdateTypeStartup : kFlutterEngineDisplaysUpdateTypeCount, + kFlutterEngineDisplaysUpdateTypeStartup, displays, n_displays ); @@ -435,6 +448,41 @@ static int update_flutter_displays(struct compositor *c) { 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) == 96); + + FlutterWindowMetricsEvent event; + memset(&event, 0, sizeof event); + + event.struct_size = sizeof(FlutterWindowMetricsEvent); + event.width = geo.view_size.x; + event.height = geo.view_size.y; + event.pixel_ratio = 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; @@ -475,15 +523,18 @@ 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 drm_resources *resources, + struct user_input *input ) { struct compositor *c; - int ok; + int bucket_status; ASSERT_NOT_NULL(tracer); ASSERT_NOT_NULL(raster_loop); @@ -500,7 +551,7 @@ MUST_CHECK struct compositor *compositor_new_multiview( c->connectors = kh_init(connector_display_ids); c->n_refs = REFCOUNT_INIT_1; - c->main_window = NULL; + c->implicit_view_fallback = NULL; // just so we get an error if the FlutterCompositor struct was updated COMPILE_ASSERT(sizeof(FlutterCompositor) == 28 || sizeof(FlutterCompositor) == 56); @@ -515,9 +566,14 @@ MUST_CHECK struct compositor *compositor_new_multiview( c->flutter_compositor.present_view_callback = on_flutter_present_view; c->tracer = tracer_ref(tracer); - c->cursor_pos = VEC2F(0, 0); - c->next_view_id = 1; + 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; @@ -545,11 +601,11 @@ MUST_CHECK struct compositor *compositor_new_multiview( 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 = false; + c->is_startup = true; c->has_display_interface = false; - ok = update_flutter_displays(c); - if (ok != 0) { + bucket_status = update_flutter_displays(c); + if (bucket_status != 0) { goto fail_return_null; } @@ -560,14 +616,24 @@ MUST_CHECK struct compositor *compositor_new_multiview( } 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 compositor *c = compositor_new_multiview(tracer, raster_loop, NULL, NULL, 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; } @@ -602,7 +668,7 @@ void compositor_destroy(struct compositor *compositor) { 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); } @@ -633,7 +699,7 @@ static struct window *compositor_get_view_by_id_locked(struct compositor *compos khiter_t entry = kh_get(view, compositor->views, view_id); if (entry != kh_end(compositor->views)) { - window = window_ref(kh_value(compositor->views, entry)); + window = kh_value(compositor->views, entry); } return window; @@ -642,15 +708,40 @@ static struct window *compositor_get_view_by_id_locked(struct compositor *compos 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; +} + 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; @@ -658,58 +749,39 @@ void compositor_set_fl_display_interface(struct compositor *compositor, const st update_flutter_displays(compositor); // send window metrics event - 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) == 96); - - FlutterWindowMetricsEvent event; - memset(&event, 0, sizeof event); - - event.struct_size = sizeof(FlutterWindowMetricsEvent); - event.width = geo.view_size.x; - event.height = geo.view_size.y; - event.pixel_ratio = 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) - ); - } - }); + send_window_metrics_events(compositor); } -void compositor_get_view_geometry(struct compositor *compositor, struct view_geometry *view_geometry_out) { - *view_geometry_out = window_get_view_geometry(compositor->main_window); +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 double compositor_get_refresh_rate(struct compositor *compositor) { - return window_get_refresh_rate(compositor->main_window); + 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, bool has_view_id, int64_t view_id, struct fl_layer_composition *composition) { struct window *window; - int ok; + int bucket_status; if (has_view_id) { window = compositor_get_view_by_id(compositor, view_id); @@ -718,16 +790,16 @@ compositor_push_composition(struct compositor *compositor, bool has_view_id, int return EINVAL; } } else { - window = window_ref(compositor->main_window); + window = window_ref(compositor->implicit_view_fallback); } TRACER_BEGIN(compositor->tracer, "window_push_composition"); - ok = window_push_composition(window, composition); + bucket_status = window_push_composition(window, composition); TRACER_END(compositor->tracer, "window_push_composition"); window_unref(window); - return ok; + return bucket_status; } static void fill_platform_view_layer_props( @@ -825,9 +897,9 @@ static int compositor_push_fl_layers( struct fl_layer_composition *composition; struct view_geometry geometry; struct window *window; - int ok; + int bucket_status; - window = has_view_id ? compositor_get_view_by_id(compositor, view_id) : window_ref(compositor->main_window); + 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; @@ -889,17 +961,17 @@ static int compositor_push_fl_layers( } TRACER_BEGIN(compositor->tracer, "compositor_push_composition"); - ok = compositor_push_composition(compositor, has_view_id, view_id, 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 ok; + return bucket_status; } /// 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); @@ -907,10 +979,10 @@ UNUSED static bool on_flutter_present_layers(const FlutterLayer **layers, size_t compositor = userdata; TRACER_BEGIN(compositor->tracer, "compositor_push_fl_layers"); - ok = compositor_push_fl_layers(compositor, false, -1, 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; } @@ -919,16 +991,16 @@ UNUSED static bool on_flutter_present_layers(const FlutterLayer **layers, size_t static bool on_flutter_present_view(const FlutterPresentViewInfo *present_info) { struct compositor *compositor; - int ok; + int bucket_status; ASSERT_NOT_NULL(present_info); compositor = present_info->user_data; TRACER_BEGIN(compositor->tracer, "compositor_push_fl_layers"); - ok = compositor_push_fl_layers(compositor, true, present_info->view_id, present_info->layers_count, present_info->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 (ok != 0) { + if (bucket_status != 0) { return false; } @@ -976,11 +1048,11 @@ void compositor_remove_platform_view(struct compositor *compositor, int64_t id) #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 @@ -989,7 +1061,7 @@ on_flutter_create_backing_store(const FlutterBackingStoreConfig *config, Flutter struct render_surface *s; struct compositor *compositor; struct window *window; - int ok; + int bucket_status; ASSERT_NOT_NULL(config); ASSERT_NOT_NULL(backing_store_out); @@ -1019,8 +1091,8 @@ 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"); goto fail_unref_window; } @@ -1073,7 +1145,7 @@ void compositor_set_cursor( // move cursor compositor->cursor_pos = vec2f_add(compositor->cursor_pos, delta); - struct view_geometry viewgeo = window_get_view_geometry(compositor->main_window); + struct view_geometry viewgeo = window_get_view_geometry(compositor->implicit_view_fallback); if (compositor->cursor_pos.x < 0.0) { compositor->cursor_pos.x = 0.0; @@ -1089,7 +1161,7 @@ void compositor_set_cursor( } window_set_cursor( - compositor->main_window, + compositor->implicit_view_fallback, has_enabled, enabled, has_kind, @@ -1105,14 +1177,26 @@ 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, NULL); + 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; @@ -1126,39 +1210,599 @@ void compositor_remove_view(struct compositor *compositor, int64_t view_id) { 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); } -int compositor_put_implicit_view(struct compositor *compositor, struct window *window) { - int bucket_status; - +struct notifier *compositor_get_display_setup_notifier(struct compositor *compositor) { ASSERT_NOT_NULL(compositor); - ASSERT_NOT_NULL(window); + return &compositor->display_setup_notifier; +} - compositor_lock(compositor); +static int64_t determine_window_for_input_device(struct compositor *compositor, struct user_input_device *device) { + input_device_match_score_t best_score; + int64_t best_window; - khiter_t entry = kh_put(view, compositor->views, 0, &bucket_status); - if (bucket_status == -1) { - compositor_unlock(compositor); - return ENOMEM; + 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; } - if (bucket_status == 0) { - window_swap_ptrs(&kh_val(compositor->views, entry), window); + 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 = kFlutterPointerDeviceKindMouse; + 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 = kFlutterPointerDeviceKindMouse; + 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 = 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 = 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 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 = kRemove; + 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, 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, 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, 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, + 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 (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 { - kh_val(compositor->views, entry) = window_ref(window); + compositor->cursor.enabled = true; + compositor->cursor.fl_view_id = new_view; + compositor->cursor.window = window_ref(new_window); + compositor->cursor.pos_view = new_pos; } +} - compositor_unlock(compositor); +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); - return 0; + 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); + } + } } -struct notifier *compositor_get_display_setup_notifier(struct compositor *compositor) { - ASSERT_NOT_NULL(compositor); - return &compositor->display_setup_notifier; +static void +on_relative_pointer_event_locked(struct compositor *compositor, struct fl_event_buffer *buffer, 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; + } + + 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; + } + } + } + + 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); + } + + // 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 (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, struct user_input_event *event) { +} + +static void on_pointer_button_event(struct compositor *compositor, struct fl_event_buffer *buffer, struct user_input_event *event) { +} + +static void on_pointer_event(struct compositor *compositor, struct fl_event_buffer *buffer, 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, 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; + } + + emit_fl_event(buffer, fl_event); +} + +static void on_tablet_tool_event(struct compositor *compositor, struct fl_event_buffer *buffer, 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 = event->device; + 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 fl_event_buffer buffer; + struct compositor *compositor; + size_t i; + + ASSERT_NOT_NULL(userdata); + compositor = userdata; + + 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; + default: LOG_DEBUG("Unhandled enum user_input_event: %d\n", event->type); break; + } + } + + flush_fl_events(&buffer); } diff --git a/src/compositor_ng.h b/src/compositor_ng.h index 1f69ca9b..fedb8668 100644 --- a/src/compositor_ng.h +++ b/src/compositor_ng.h @@ -95,15 +95,24 @@ struct fl_display_interface { FlutterEngine engine; }; -struct compositor *compositor_new_multiview( +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 drm_resources *resources, + struct user_input *input ); -struct compositor *compositor_new_singleview(struct tracer *tracer, struct evloop *raster_loop, struct window *window); +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); @@ -120,6 +129,17 @@ DECLARE_REF_OPS(compositor) */ 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); @@ -137,9 +157,18 @@ int64_t compositor_add_view(struct compositor *compositor, struct window *window void compositor_remove_view(struct compositor *compositor, int64_t view_id); /** - * @brief Sets the implicit view (view with id 0) to the given window. + * @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. */ -int compositor_put_implicit_view(struct compositor *compositor, struct window *window); +MUST_CHECK struct window *compositor_ref_implicit_view(struct compositor *compositor); /** * @brief Adds a platform view to the compositor, returning the platform view id. diff --git a/src/dummy_window.c b/src/dummy_window.c new file mode 100644 index 00000000..d3c41c43 --- /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, + double 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..36f42f00 --- /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, + double refresh_rate + // clang-format on +); + +#endif // _FLUTTERPI_SRC_DUMMY_WINDOW_H \ No newline at end of file diff --git a/src/flutter-pi.c b/src/flutter-pi.c index b94d53de..3aa09b67 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -41,10 +41,12 @@ #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" @@ -225,6 +227,8 @@ struct flutterpi { struct evsrc *drmdev_evrsc; + struct udev *udev; + /** * @brief The user input instance. * @@ -579,7 +583,13 @@ 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); } @@ -1316,8 +1326,23 @@ static int flutterpi_run(struct flutterpi *flutterpi) { } ); + compositor_set_fl_pointer_event_interface( + flutterpi->compositor, + &(const struct fl_pointer_event_interface){ + .send_pointer_event = procs->SendPointerEvent, + .engine = flutterpi->flutter.engine, + } + ); + + user_input_resume(flutterpi->user_input); + evloop_run(flutterpi->platform_loop); + compositor_set_fl_pointer_event_interface(flutterpi->compositor, NULL); + user_input_suspend(flutterpi->user_input); + + compositor_set_fl_display_interface(flutterpi->compositor, NULL); + // We deinitialize the plugins here so plugins don't attempt to use the // flutter engine anymore. // For example, otherwise the gstreamer video player might call @@ -1369,208 +1394,19 @@ 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; - - flutterpi = userdata; - - (void) flutterpi; - -#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 -} - -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); - } -#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)); - } } } @@ -1997,11 +1833,116 @@ static enum event_handler_return on_libseat_fd_ready(int fd, uint32_t revents, v } #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 flutter_paths *paths; - struct view_geometry geometry; FlutterEngineAOTData aot_data; FlutterEngineResult engine_result; struct gbm_device *gbm_device; @@ -2077,7 +2018,8 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { 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", + "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); @@ -2117,7 +2059,11 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { locales_print(fpi->locales); - struct udev *udev = NULL; + 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) { @@ -2130,13 +2076,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { goto fail_destroy_locales; } } else { - udev = udev_new(); - if (udev == NULL) { - LOG_ERROR("Couldn't create udev context.\n"); - goto fail_destroy_locales; - } - - fpi->drmdev = drmdev_new_from_udev_primary(udev, "seat0", &file_interface, fpi->libseat); + fpi->drmdev = drmdev_new_from_udev_primary(fpi->udev, "seat0", &file_interface, fpi->libseat); if (fpi->drmdev == NULL) { goto fail_destroy_locales; } @@ -2202,6 +2142,8 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { goto fail_unref_tracer; } + init_input(fpi); + { struct window *window; @@ -2262,7 +2204,9 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { frame_scheduler_unref(scheduler); } - fpi->compositor = compositor_new_multiview(fpi->tracer, fpi->raster_evloop, NULL, fpi->drmdev, drm_resources); + fpi->compositor = compositor_new_multiview(fpi->tracer, fpi->raster_evloop, NULL, fpi->drmdev, drm_resources, fpi->user_input); + + compositor_add_view(fpi->compositor, window); window_unref(window); @@ -2286,48 +2230,6 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { } } - compositor_get_view_geometry(fpi->compositor, &geometry); - - 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, - }; - - list_inithead(&fpi->fd_for_device_id); - - fpi->user_input = user_input_new( - &user_input_interface, - fpi, - &geometry.display_to_view_transform, - &geometry.view_to_display_transform, - (unsigned int) geometry.display_size.x, - (unsigned int) geometry.display_size.y - ); - if (fpi->user_input == NULL) { - LOG_ERROR("Couldn't initialize user input. flutter-pi will run without user input.\n"); - } else { - 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"); - user_input_destroy(fpi->user_input); - fpi->user_input = NULL; - } - } - fpi->flutter.engine_handle = load_flutter_engine_lib(paths); if (fpi->flutter.engine_handle == NULL) { goto fail_destroy_user_input; @@ -2417,12 +2319,8 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { unload_flutter_engine_lib(fpi->flutter.engine_handle); fail_destroy_user_input: - if (fpi->user_input_evsrc) { - evsrc_destroy(fpi->user_input_evsrc); - } - if (fpi->user_input) { - user_input_destroy(fpi->user_input); - } + fini_input(fpi); + if (fpi->drmdev_evrsc) { evsrc_destroy(fpi->drmdev_evrsc); } diff --git a/src/kms/drmdev.h b/src/kms/drmdev.h index ae292728..1a804a9e 100644 --- a/src/kms/drmdev.h +++ b/src/kms/drmdev.h @@ -21,17 +21,10 @@ #include "pixel_format.h" #include "util/collection.h" +#include "util/file_interface.h" #include "util/geometry.h" #include "util/refcounting.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); -}; - struct drmdev; typedef void (*drmdev_scanout_cb_t)(uint64_t vblank_ns, void *userdata); @@ -43,7 +36,8 @@ struct drmdev *drmdev_new_from_interface_fd(int fd, void *fd_metadata, const str 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); +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) diff --git a/src/kms/kms_window.c b/src/kms/kms_window.c new file mode 100644 index 00000000..d5676628 --- /dev/null +++ b/src/kms/kms_window.c @@ -0,0 +1,1046 @@ +#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 +) { + struct drm_connector *connector; + struct drm_encoder *encoder; + struct drm_crtc *crtc; + drmModeModeInfo *mode; + int ok; + + // find any connected connector + 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; + } + + 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 + 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]) { + break; + } + } + + if (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 + 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, + mode_get_vrefresh(selected_mode), + width_mm, + height_mm, + 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); + 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"); + goto fail_unref_builder; + } + + ok = kms_req_builder_set_mode(builder, w->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 (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 = w->base.cursor_pos.x - w->cursor->hotspot.x, + .dst_y = 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) { + 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(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); + + 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); + 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->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->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..3072eadf --- /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 \ No newline at end of file diff --git a/src/plugins/text_input.c b/src/plugins/text_input.c index 15674cd4..0162b7ac 100644 --- a/src/plugins/text_input.c +++ b/src/plugins/text_input.c @@ -85,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 */ @@ -615,33 +632,26 @@ 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 */); - - // 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]; + memmove(to_move + l, to_move, strlen((char *) to_move) + 1 /* null byte */); + 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; @@ -740,14 +750,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; @@ -771,7 +782,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; 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/user_input.c b/src/user_input.c index 17531743..04e71124 100644 --- a/src/user_input.c +++ b/src/user_input.c @@ -19,16 +19,31 @@ #include "flutter-pi.h" #include "keyboard.h" #include "util/collection.h" +#include "util/khash.h" +#include "util/kvec.h" #include "util/logging.h" #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; 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, int64_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, int64_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, int64_t buttons, int64_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, int64_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; - 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 ?: "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); @@ -397,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); @@ -439,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)); } } @@ -548,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)) { @@ -580,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)); @@ -607,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; @@ -621,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; @@ -649,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); } } @@ -659,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; } @@ -735,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 @@ -770,47 +855,28 @@ static int on_key_event(struct user_input *input, struct libinput_event *event) } } - 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; } @@ -819,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); @@ -832,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; } @@ -885,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); @@ -896,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; } @@ -932,8 +947,6 @@ 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; @@ -952,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 @@ -984,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 ) ); @@ -1025,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); @@ -1038,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; @@ -1048,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; @@ -1067,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; @@ -1075,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); @@ -1086,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; @@ -1117,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); @@ -1128,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; } @@ -1138,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; @@ -1146,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); @@ -1157,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; @@ -1203,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; } @@ -1242,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; @@ -1272,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; @@ -1502,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); @@ -1521,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 = (int) round(input->cursor_x); - cursor_y_before = (int) round(input->cursor_y); - // handle all available libinput events ok = process_libinput_events(input, timestamp); if (ok != 0) { @@ -1533,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 = (int) round(input->cursor_x); - cursor_y = (int) 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) { + return 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) { + return 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/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/kvec.h b/src/util/kvec.h new file mode 100644 index 00000000..a15455dc --- /dev/null +++ b/src/util/kvec.h @@ -0,0 +1,122 @@ +// 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)) + +#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 \ No newline at end of file diff --git a/src/window.c b/src/window.c index 04aa9992..23e84aa2 100644 --- a/src/window.c +++ b/src/window.c @@ -26,9 +26,11 @@ #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" @@ -42,211 +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. - */ - 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 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_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; - } 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) @@ -288,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, @@ -300,7 +95,10 @@ static int window_init( 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 + 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; @@ -313,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 " @@ -410,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); @@ -435,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) { @@ -484,13 +307,15 @@ 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 @@ -498,8 +323,8 @@ EGLSurface window_get_egl_surface(struct window *window) { /// 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) { @@ -525,1311 +350,172 @@ 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; - } - } +struct vec2f window_transform_ndc_to_view(struct window *window, struct vec2f ndc) { + ASSERT_NOT_NULL(window); - 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; - } + struct vec2f view_pos; - 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; -} + window_lock(window); + view_pos = transform_point(window->ndc_to_view_transform, ndc); + window_unlock(window); -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); + return view_pos; } -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 +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; - int ok; - - // find any connected connector - 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; - } + (void) window; + (void) n_events; + (void) events; - mode = NULL; - if (desired_videomode != NULL) { - drm_connector_for_each_mode(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); } - - 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; + n_fl_events = 0; + } + + 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) { - 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 (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 - drm_resources_for_each_encoder(resources, encoder_it) { - if (encoder_it->id == connector->committed_state.encoder_id) { - encoder = encoder_it; - 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) { - drm_resources_for_each_encoder(resources, encoder_it) { - if (encoder_it->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->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 - drm_resources_for_each_crtc(resources, crtc_it) { - if (crtc_it->id == encoder->variable_state.crtc_id) { - crtc = crtc_it; - 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) { - 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; + 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, - struct drm_resources *resources, - 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); - ASSERT_NOT_NULL(resources); - -#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(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, - 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, - (double) (window->pixel_ratio), - has_forced_pixel_format ? get_pixfmt_info(forced_pixel_format)->name : "(any)" - ); - - window->kms.drmdev = drmdev_ref(drmdev); - window->kms.resources = drm_resources_ref(resources); - 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->kms.cursor_works = true; - 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; - 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); - 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 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 = kms_req_builder_new_atomic(window->kms.drmdev, window->kms.resources, 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_void, - NULL, - window->kms.cursor - ); - if (ok != 0) { - LOG_ERROR("Couldn't present cursor. Hardware cursor will be disabled.\n"); - - window->kms.cursor_works = false; - window->cursor_enabled = false; - cursor_buffer_unrefp(&window->kms.cursor); - } else { - cursor_buffer_ref(window->kms.cursor); - } - } - - req = kms_req_builder_build(builder); - if (req == NULL) { - ok = EIO; - 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(window->tracer); - frame->drmdev = drmdev_ref(window->kms.drmdev); - frame->scheduler = frame_scheduler_ref(window->frame_scheduler); - 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. - drm_resources_for_each_plane(window->kms.resources, plane_it) { - if (!(plane_it->possible_crtcs & window->kms.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); - - 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); - 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 && !window->kms.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, 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); - } - } - - 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; - - 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 - // 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->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; - - 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); + default: break; } -#else - UNREACHABLE(); -#endif - } else { - ASSUME(window->renderer_type == kVulkan_RendererType); + n_fl_events++; - // vulkan -#ifdef HAVE_VULKAN - UNIMPLEMENTED(); -#else - UNREACHABLE(); -#endif +skip: + continue; } - 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; + if (pointer_event_interface != NULL && n_fl_events > 0) { + pointer_event_interface->send_pointer_event(pointer_event_interface->engine, fl_events, n_fl_events); } } -static EGLSurface dummy_window_get_egl_surface(struct window *window) { +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 (window->renderer_type == kOpenGL_RendererType) { - struct render_surface *render_surface = dummy_window_get_render_surface_internal(window, false, VEC2I(0, 0)); - if (render_surface == NULL) { - return EGL_NO_SURFACE; - } - - return egl_gbm_render_surface_get_egl_surface(CAST_EGL_GBM_RENDER_SURFACE(render_surface)); + if (window->ops.match_input_device == NULL) { + return -1; } 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 + return window->ops.match_input_device(window, device); } - - 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 ced64918..e4ed2570 100644 --- a/src/window.h +++ b/src/window.h @@ -36,71 +36,6 @@ 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 resources - * @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, - struct drm_resources *resources, - 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. * @@ -151,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, @@ -160,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..913cdccd --- /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. + * + */ + 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 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, + double 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 \ No newline at end of file From 379afbcd1ea0bd55abd548259639a2268a0aa3e1 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 16 Sep 2024 23:00:52 +0000 Subject: [PATCH 16/27] fix analysis issues --- CMakeLists.txt | 1 + CMakePresets.json | 2 + src/compositor_ng.c | 138 ++-- src/compositor_ng.h | 12 +- src/egl_gbm_render_surface.c | 19 +- src/flutter-pi.c | 17 +- src/kms/drmdev.c | 54 +- src/kms/kms_window.c | 31 +- src/kms/monitor.c | 34 +- src/kms/monitor.h | 11 +- src/kms/req_builder.c | 51 +- src/kms/req_builder.h | 15 +- src/kms/resources.c | 157 ++--- src/plugins/sentry/sentry.c | 2 +- src/user_input.c | 10 +- src/util/khash.h | 631 +---------------- src/util/macros.h | 60 +- src/vk_gbm_render_surface.c | 2 + src/window.c | 5 +- src/window.h | 2 +- src/window_private.h | 6 +- third_party/klib/include/klib/khash.h | 665 ++++++++++++++++++ .../klib/include/klib}/kvec.h | 1 + 23 files changed, 995 insertions(+), 931 deletions(-) create mode 100644 third_party/klib/include/klib/khash.h rename {src/util => third_party/klib/include/klib}/kvec.h (98%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8647f076..1127541d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,7 @@ target_include_directories(flutterpi_module PUBLIC ${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 ) diff --git a/CMakePresets.json b/CMakePresets.json index 5ce46cb1..8ae87641 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -17,6 +17,8 @@ "BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN": true, "TRY_BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN": false, "BUILD_SENTRY_PLUGIN": true, + "ENABLE_VULKAN": true, + "TRY_ENABLE_VULKAN": false, "ENABLE_SESSION_SWITCHING": true, "ENABLE_TESTS": false } diff --git a/src/compositor_ng.c b/src/compositor_ng.c index 0b1d86a9..71b2e5b5 100644 --- a/src/compositor_ng.c +++ b/src/compositor_ng.c @@ -20,6 +20,8 @@ #include #include +#include +#include #include #include @@ -38,10 +40,8 @@ #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/kvec.h" #include "util/logging.h" #include "util/refcounting.h" #include "window.h" @@ -308,7 +308,12 @@ double display_get_device_pixel_ratio(const struct display *display) { } 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 } /** @@ -460,9 +465,9 @@ static void send_window_metrics_events(struct compositor *compositor) { memset(&event, 0, sizeof event); event.struct_size = sizeof(FlutterWindowMetricsEvent); - event.width = geo.view_size.x; - event.height = geo.view_size.y; - event.pixel_ratio = geo.device_pixel_ratio; + 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; @@ -768,7 +773,7 @@ void compositor_set_fl_pointer_event_interface( } } -ATTR_PURE double compositor_get_refresh_rate(struct compositor *compositor) { +ATTR_PURE float compositor_get_refresh_rate(struct compositor *compositor) { return window_get_refresh_rate(compositor->implicit_view_fallback); } @@ -941,8 +946,9 @@ static int compositor_push_fl_layers( /// 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); - layer->surface = - CAST_SURFACE(dummy_render_surface_new(compositor->tracer, VEC2I(fl_layer->size.width, fl_layer->size.height))); + 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 @@ -1126,50 +1132,17 @@ const FlutterCompositor *compositor_get_flutter_compositor(struct compositor *co return &compositor->flutter_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 -) { - if (!has_enabled && !has_kind && !has_delta) { +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 (has_delta) { - // move cursor - compositor->cursor_pos = vec2f_add(compositor->cursor_pos, delta); - - struct view_geometry viewgeo = window_get_view_geometry(compositor->implicit_view_fallback); - - if (compositor->cursor_pos.x < 0.0) { - compositor->cursor_pos.x = 0.0; - } else if (compositor->cursor_pos.x > viewgeo.view_size.x) { - compositor->cursor_pos.x = viewgeo.view_size.x; - } - - if (compositor->cursor_pos.y < 0.0) { - compositor->cursor_pos.y = 0.0; - } else if (compositor->cursor_pos.y > viewgeo.view_size.y) { - compositor->cursor_pos.y = viewgeo.view_size.y; - } + if (compositor->cursor.window) { + window_set_cursor(compositor->cursor.window, has_enabled, enabled, has_kind, kind, false, VEC2I(0, 0)); } - window_set_cursor( - compositor->implicit_view_fallback, - has_enabled, - enabled, - has_kind, - kind, - has_delta, - VEC2I((int) round(compositor->cursor_pos.x), (int) round(compositor->cursor_pos.y)) - ); - compositor_unlock(compositor); } @@ -1243,18 +1216,20 @@ struct notifier *compositor_get_display_setup_notifier(struct compositor *compos } static int64_t determine_window_for_input_device(struct compositor *compositor, struct user_input_device *device) { - input_device_match_score_t best_score; - int64_t best_window; + 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; - } - }); + { + 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; @@ -1323,7 +1298,7 @@ static FlutterPointerEvent make_fl_pointer_add_event( event.signal_kind = kFlutterPointerSignalKindNone; event.scroll_delta_x = 0; event.scroll_delta_y = 0; - event.device_kind = kFlutterPointerDeviceKindMouse; + event.device_kind = device_kind; event.buttons = 0; event.pan_x = 0.0; event.pan_y = 0.0; @@ -1353,7 +1328,7 @@ static FlutterPointerEvent make_fl_pointer_remove_event( event.signal_kind = kFlutterPointerSignalKindNone; event.scroll_delta_x = 0; event.scroll_delta_y = 0; - event.device_kind = kFlutterPointerDeviceKindMouse; + event.device_kind = device_kind; event.buttons = 0; event.pan_x = 0.0; event.pan_y = 0.0; @@ -1376,7 +1351,7 @@ static FlutterPointerEvent make_fl_mouse_event( memset(&event, 0, sizeof event); event.struct_size = sizeof(FlutterPointerEvent); - event.phase = kRemove; + event.phase = phase; event.timestamp = timestamp; event.x = pos_view.x; event.y = pos_view.y; @@ -1395,7 +1370,7 @@ static FlutterPointerEvent make_fl_mouse_event( return event; } -static FlutterPointerEvent make_fl_mouse_scroll_event( +UNUSED static FlutterPointerEvent make_fl_mouse_scroll_event( FlutterPointerPhase phase, uint64_t timestamp, struct vec2f pos_view, @@ -1408,7 +1383,7 @@ static FlutterPointerEvent make_fl_mouse_scroll_event( memset(&event, 0, sizeof event); event.struct_size = sizeof(FlutterPointerEvent); - event.phase = kRemove; + event.phase = phase; event.timestamp = timestamp; event.x = pos_view.x; event.y = pos_view.y; @@ -1454,7 +1429,7 @@ static void on_device_removed(struct compositor *compositor, struct user_input_d user_input_device_set_primary_listener_userdata(device, NULL); } -static void on_slot_added(struct compositor *compositor, struct fl_event_buffer *buffer, struct user_input_event *event) { +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; @@ -1473,7 +1448,7 @@ static void on_slot_added(struct compositor *compositor, struct fl_event_buffer emit_fl_event(buffer, fl_event); } -static void on_slot_removed(struct compositor *compositor, struct fl_event_buffer *buffer, struct user_input_event *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; @@ -1492,7 +1467,7 @@ static void on_slot_removed(struct compositor *compositor, struct fl_event_buffe emit_fl_event(buffer, fl_event); } -static void on_absolute_pointer_event(struct compositor *compositor, struct fl_event_buffer *buffer, struct user_input_event *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); @@ -1622,7 +1597,7 @@ static void maybe_enable_cursor_locked(struct compositor *compositor) { } static void -on_relative_pointer_event_locked(struct compositor *compositor, struct fl_event_buffer *buffer, struct user_input_event *event) { +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. @@ -1718,13 +1693,23 @@ on_relative_pointer_event_locked(struct compositor *compositor, struct fl_event_ window_unrefp(¤t_window); } -static void on_pointer_scroll_event(struct compositor *compositor, struct fl_event_buffer *buffer, struct user_input_event *event) { +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, struct user_input_event *event) { +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(); } -static void on_pointer_event(struct compositor *compositor, struct fl_event_buffer *buffer, struct user_input_event *event) { +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) { @@ -1736,7 +1721,7 @@ static void on_pointer_event(struct compositor *compositor, struct fl_event_buff } } -static void on_touch_event(struct compositor *compositor, struct fl_event_buffer *buffer, struct user_input_event *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; @@ -1761,7 +1746,7 @@ static void on_touch_event(struct compositor *compositor, struct fl_event_buffer emit_fl_event(buffer, fl_event); } -static void on_tablet_tool_event(struct compositor *compositor, struct fl_event_buffer *buffer, struct user_input_event *event) { +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; @@ -1775,20 +1760,28 @@ static void on_tablet_tool_event(struct compositor *compositor, struct fl_event_ fl_event.phase = kMove; fl_event.x = event->tablet.position_ndc.x; fl_event.y = event->tablet.position_ndc.y; - fl_event.device = event->device; + 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 fl_event_buffer buffer; 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; @@ -1800,6 +1793,7 @@ static void on_input(void *userdata, size_t n_events, const struct user_input_ev 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: %d\n", event->type); break; } } diff --git a/src/compositor_ng.h b/src/compositor_ng.h index fedb8668..3d6b4778 100644 --- a/src/compositor_ng.h +++ b/src/compositor_ng.h @@ -142,7 +142,7 @@ void compositor_set_fl_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); @@ -194,15 +194,7 @@ 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, diff --git a/src/egl_gbm_render_surface.c b/src/egl_gbm_render_surface.c index 8973a92c..fcae0f8e 100644 --- a/src/egl_gbm_render_surface.c +++ b/src/egl_gbm_render_surface.c @@ -455,21 +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_resources_any_crtc_plane_supports_format(kms_req_builder_peek_resources(builder), crtc->id, 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_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; @@ -760,7 +762,10 @@ static int egl_gbm_render_surface_queue_present(struct render_surface *s, const locked: #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."); + 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))); diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 3aa09b67..1a4e9472 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -550,7 +550,7 @@ 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 + (uint64_t) (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(flutterpi)) { TRACER_INSTANT(req->flutterpi->tracer, "FlutterEngineOnVsync"); @@ -927,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) { - 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) { @@ -1948,7 +1948,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { struct gbm_device *gbm_device; struct flutterpi *fpi; struct flutterpi_cmdline_args cmd_args; - char *bundle_path, **engine_argv, *desired_videomode; + char **engine_argv, *desired_videomode; int ok, engine_argc; fpi = calloc(1, sizeof *fpi); @@ -2013,7 +2013,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { ); } - int fd; + int fd = -1; if (fpi->libseat != NULL) { fd = libseat_get_fd(fpi->libseat); if (fd < 0) { @@ -2102,11 +2102,11 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { 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"); - goto fail_unref_scheduler; + goto fail_unref_tracer; } #else UNREACHABLE(); @@ -2117,7 +2117,6 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { 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_tracer; } diff --git a/src/kms/drmdev.c b/src/kms/drmdev.c index 83eb891a..7d7c49ab 100644 --- a/src/kms/drmdev.c +++ b/src/kms/drmdev.c @@ -11,21 +11,21 @@ #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" -#include "util/khash.h" struct pageflip_callbacks { uint32_t crtc_id; @@ -85,7 +85,7 @@ struct drmdev { void *interface_userdata; struct list_head fbs; - khash_t(pageflip_callbacks) *pageflip_callbacks; + khash_t(pageflip_callbacks) * pageflip_callbacks; struct udev *udev; struct udev_device *kms_udev; @@ -210,10 +210,10 @@ static int set_drm_client_caps(int fd, bool *supports_atomic_modesetting) { return 0; } - -static struct udev_device *find_udev_kms_device(struct udev *udev, const char *seat, const struct file_interface *interface, void *interface_userdata) { +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]*"); @@ -229,14 +229,18 @@ static struct udev_device *find_udev_kms_device(struct udev *udev, const char *s 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") ?: "seat0"; + 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; @@ -269,11 +273,7 @@ static struct udev_device *find_udev_kms_device(struct udev *udev, const char *s return NULL; } -static void drmdev_on_page_flip( - struct drmdev *drmdev, - uint32_t crtc_id, - uint64_t vblank_ns -) { +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; @@ -308,13 +308,7 @@ static void drmdev_on_page_flip( } } -static void on_page_flip( - int fd, - unsigned int sequence, - unsigned int tv_sec, unsigned int tv_usec, - unsigned int crtc_id, - void *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); @@ -352,7 +346,8 @@ void drmdev_dispatch_modesetting(struct drmdev *drmdev) { /** * @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 * +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; @@ -405,7 +400,6 @@ struct drmdev *drmdev_new_from_udev_primary(struct udev *udev, const char *seat, return d; - fail_close_fd: interface->close(d->fd, d->fd_metadata, interface_userdata); @@ -621,7 +615,7 @@ uint32_t drmdev_add_fb_multiplanar( } fb->id = fb_id; - + pthread_mutex_lock(&drmdev->mutex); list_add(&fb->entry, &drmdev->fbs); @@ -891,7 +885,7 @@ static int commit_atomic_common( 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. @@ -916,7 +910,17 @@ int drmdev_commit_atomic_sync( 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); + 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( diff --git a/src/kms/kms_window.c b/src/kms/kms_window.c index d5676628..8b0c3531 100644 --- a/src/kms/kms_window.c +++ b/src/kms/kms_window.c @@ -186,13 +186,10 @@ static int select_mode( drmModeModeInfo **mode_out, const char *desired_videomode ) { - struct drm_connector *connector; - struct drm_encoder *encoder; - struct drm_crtc *crtc; - drmModeModeInfo *mode; 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; @@ -205,7 +202,7 @@ static int select_mode( return EINVAL; } - mode = NULL; + drmModeModeInfo *mode = NULL; if (desired_videomode != NULL) { drm_connector_for_each_mode(connector, mode_iter) { char *modeline = NULL, *modeline_nohz = NULL; @@ -270,6 +267,7 @@ static int select_mode( 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; @@ -282,11 +280,12 @@ static int select_mode( 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->possible_crtcs) { + if (encoder && encoder->possible_crtcs) { // only use this encoder if there's a crtc we can use with it break; } @@ -299,6 +298,7 @@ static int select_mode( } // 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; @@ -437,7 +437,7 @@ MUST_CHECK struct window *kms_window_new( mode_get_vrefresh(selected_mode), width_mm, height_mm, - window->base.pixel_ratio, + (double) window->base.pixel_ratio, has_forced_pixel_format ? get_pixfmt_info(forced_pixel_format)->name : "(any)" ); @@ -600,12 +600,14 @@ static int kms_window_push_composition_locked(struct kms_window *w, struct fl_la 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; } } @@ -633,8 +635,8 @@ static int kms_window_push_composition_locked(struct kms_window *w, struct fl_la .src_y = 0, .src_w = ((uint16_t) w->cursor->width) << 16, .src_h = ((uint16_t) w->cursor->height) << 16, - .dst_x = w->base.cursor_pos.x - w->cursor->hotspot.x, - .dst_y = w->base.cursor_pos.y - w->cursor->hotspot.y, + .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, @@ -660,6 +662,7 @@ static int kms_window_push_composition_locked(struct kms_window *w, struct fl_la req = kms_req_builder_build(builder); if (req == NULL) { + ok = ENOMEM; goto fail_unref_builder; } @@ -668,6 +671,7 @@ static int kms_window_push_composition_locked(struct kms_window *w, struct fl_la frame = malloc(sizeof *frame); if (frame == NULL) { + ok = ENOMEM; goto fail_unref_req; } @@ -908,8 +912,13 @@ static struct render_surface *kms_window_get_render_surface_internal(struct kms_ // 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); + 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; diff --git a/src/kms/monitor.c b/src/kms/monitor.c index 3220c67b..45ae0fb9 100644 --- a/src/kms/monitor.c +++ b/src/kms/monitor.c @@ -1,11 +1,12 @@ -#include +#include "monitor.h" + #include #include +#include #include #include "resources.h" -#include "monitor.h" struct drm_monitor { struct udev *udev; @@ -16,12 +17,8 @@ struct drm_monitor { 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 * +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); @@ -35,6 +32,7 @@ struct drm_monitor *drm_monitor_new( 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; } @@ -100,23 +98,23 @@ void drm_monitor_dispatch(struct drm_monitor *m) { // 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 - }; + 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); diff --git a/src/kms/monitor.h b/src/kms/monitor.h index 3e3c491b..7da2f470 100644 --- a/src/kms/monitor.h +++ b/src/kms/monitor.h @@ -1,6 +1,9 @@ #ifndef _FLUTTERPI_SRC_MODESETTING_RESOURCE_MONITOR_H #define _FLUTTERPI_SRC_MODESETTING_RESOURCE_MONITOR_H +#include +#include + struct drm_resources; struct udev; struct drm_monitor; @@ -19,12 +22,8 @@ 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 -); +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); diff --git a/src/kms/req_builder.c b/src/kms/req_builder.c index 83d17cc5..c5565790 100644 --- a/src/kms/req_builder.c +++ b/src/kms/req_builder.c @@ -11,15 +11,15 @@ #include #include +#include #include #include #include #include -#include #include "drmdev.h" -#include "resources.h" #include "pixel_format.h" +#include "resources.h" #include "util/bitset.h" #include "util/list.h" #include "util/lock_ops.h" @@ -58,15 +58,15 @@ struct kms_req_builder { 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]; @@ -648,15 +648,15 @@ UNUSED struct kms_req *kms_req_ref(struct kms_req *req) { } UNUSED void kms_req_unref(struct kms_req *req) { - return kms_req_builder_unref((struct kms_req_builder *) req); + 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); + 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); + 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) { @@ -688,7 +688,6 @@ static void on_kms_req_release(void *userdata) { kms_req_builder_unref(b); } - static int kms_req_commit_common( struct kms_req *req, struct drmdev *drmdev, @@ -782,9 +781,25 @@ static int kms_req_commit_common( 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); + 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)); + 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) { @@ -927,9 +942,9 @@ static int kms_req_commit_common( fail_unref_builder: kms_req_builder_unref(builder); -// fail_maybe_destroy_mode_blob: -// if (mode_blob != NULL) -// drm_blob_destroy(mode_blob); + // fail_maybe_destroy_mode_blob: + // if (mode_blob != NULL) + // drm_blob_destroy(mode_blob); return ok; } @@ -954,6 +969,12 @@ int kms_req_commit_blocking(struct kms_req *req, struct drmdev *drmdev, uint64_t 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) { +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 index c2c79102..2610a4df 100644 --- a/src/kms/req_builder.h +++ b/src/kms/req_builder.h @@ -28,7 +28,6 @@ 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; @@ -36,7 +35,7 @@ struct kms_req_builder *kms_req_builder_new_atomic(struct drmdev *drmdev, struct 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); +DECLARE_REF_OPS(kms_req_builder) struct drmdev *kms_req_builder_get_drmdev(struct kms_req_builder *builder); @@ -185,7 +184,7 @@ int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder, */ struct kms_req; -DECLARE_REF_OPS(kms_req); +DECLARE_REF_OPS(kms_req) /** * @brief Build the KMS request builder into an actual, immutable KMS request @@ -201,6 +200,12 @@ 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); +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_ +#endif // _FLUTTERPI_SRC_MODESETTING_REQ_BUILDER_H_ diff --git a/src/kms/resources.c b/src/kms/resources.c index 031e6e5f..a0b41edc 100644 --- a/src/kms/resources.c +++ b/src/kms/resources.c @@ -11,14 +11,14 @@ #include #include +#include #include #include #include #include -#include -#include "pixel_format.h" #include "monitor.h" +#include "pixel_format.h" #include "util/bitset.h" #include "util/list.h" #include "util/lock_ops.h" @@ -42,8 +42,8 @@ struct drm_mode_fb2 { }; #ifdef HAVE_FUNC_ATTRIBUTE_WEAK - extern struct _drmModeFB2 *drmModeGetFB2(int fd, uint32_t bufferId) __attribute__((weak)); - extern void drmModeFreeFB2(struct _drmModeFB2 *ptr) __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 @@ -56,7 +56,7 @@ static bool drm_fb_get_format(int drm_fd, uint32_t fb_id, enum pixfmt *format_ou // 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); + fb = (struct drm_mode_fb2 *) drmModeGetFB2(drm_fd, fb_id); if (fb == NULL) { return false; } @@ -92,7 +92,6 @@ static size_t sizeof_drm_format_modifier_blob(struct drm_format_modifier_blob *b ); } - static int drm_connector_init(int drm_fd, uint32_t connector_id, struct drm_connector *out) { memset(out, 0, sizeof(*out)); @@ -148,17 +147,17 @@ static int drm_connector_init(int drm_fd, uint32_t connector_id, struct drm_conn 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 +#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 +#undef CHECK_ASSIGN_PROPERTY_ID if (id == out->ids.crtc_id) { out->committed_state.crtc_id = props->prop_values[i]; @@ -195,19 +194,16 @@ static int drm_connector_update(struct drm_connector *connector, int drm_fd, boo (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 ?: ENOMEM; + return errno ? errno : ENOMEM; } out->id = encoder->encoder_id; @@ -234,20 +230,21 @@ 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 ?: ENOMEM; + return errno ? errno : ENOMEM; } + ASSERT(0 <= crtc_index && crtc_index < 32); + out->id = crtc->crtc_id; - out->index = crtc_index; + 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; @@ -259,28 +256,27 @@ static int drm_crtc_init(int drm_fd, int crtc_index, uint32_t crtc_id, struct dr { drmModeObjectProperties *props = drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC); if (props == NULL) { - return errno ?: ENOMEM; + 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 ?: ENOMEM; + 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 +#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 - +#undef CHECK_ASSIGN_PROPERTY_ID drmModeFreeProperty(prop_info); } @@ -325,7 +321,6 @@ bool drm_resources_any_crtc_plane_supports_format(struct drm_resources *r, uint3 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; @@ -373,7 +368,7 @@ static void drm_plane_init_zpos(drmModePropertyRes *info, uint64_t value, struct 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( @@ -387,12 +382,12 @@ static void drm_plane_init_zpos(drmModePropertyRes *info, uint64_t value, struct 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 ?: ENOMEM; + return errno ? errno : ENOMEM; } out->supports_modifiers = true; @@ -431,11 +426,7 @@ static void drm_plane_init_blend_mode(drmModePropertyRes *info, uint64_t value, 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 - ); + LOG_DEBUG("Unknown KMS pixel blend mode: %s (value: %" PRIu64 ")\n", info->enums[i].name, (uint64_t) info->enums[i].value); } } @@ -455,7 +446,7 @@ static int drm_plane_init(int drm_fd, uint32_t plane_id, struct drm_plane *out) { drmModePlane *plane = drmModeGetPlane(drm_fd, plane_id); if (plane == NULL) { - return errno ?: ENOMEM; + return errno ? errno : ENOMEM; } out->id = plane->plane_id; @@ -477,23 +468,23 @@ static int drm_plane_init(int drm_fd, uint32_t plane_id, struct drm_plane *out) drmModeObjectProperties *props = drmModeObjectGetProperties(drm_fd, plane_id, DRM_MODE_OBJECT_PLANE); if (props == NULL) { - return errno ?: ENOMEM; + 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 ?: ENOMEM; + 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; \ + out->ids._name = info->prop_id; \ } else DRM_PLANE_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { @@ -502,7 +493,6 @@ static int drm_plane_init(int drm_fd, uint32_t plane_id, struct drm_plane *out) #undef CHECK_ASSIGN_PROPERTY_ID - if (id == out->ids.type) { assert(has_type == false); has_type = true; @@ -540,7 +530,6 @@ static int drm_plane_init(int drm_fd, uint32_t plane_id, struct drm_plane *out) drm_plane_init_blend_mode(info, value, out); } - drmModeFreeProperty(info); } @@ -565,7 +554,8 @@ static int drm_plane_copy(struct drm_plane *dst, const struct drm_plane *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)); + 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; } @@ -668,7 +658,6 @@ bool drm_plane_supports_unmodified_format(struct drm_plane *plane, enum pixfmt f return plane->supported_formats[format]; } - struct drm_resources *drm_resources_new(int drm_fd) { struct drm_resources *r; int ok; @@ -679,14 +668,14 @@ struct drm_resources *drm_resources_new(int drm_fd) { } r->n_refs = REFCOUNT_INIT_1; - + r->have_filter = false; drmModeRes *res = drmModeGetResources(drm_fd); if (res == NULL) { - ok = errno ?: EINVAL; + ok = errno ? errno : EINVAL; LOG_ERROR("Could not get DRM device resources. drmModeGetResources: %s\n", strerror(ok)); - return NULL; + goto fail_free_r; } r->min_width = res->min_width; @@ -696,7 +685,6 @@ struct drm_resources *drm_resources_new(int drm_fd) { r->connectors = calloc(res->count_connectors, sizeof *(r->connectors)); if (r->connectors == NULL) { - ok = ENOMEM; goto fail_free_res; } @@ -712,7 +700,6 @@ struct drm_resources *drm_resources_new(int drm_fd) { r->encoders = calloc(res->count_encoders, sizeof *(r->encoders)); if (r->encoders == NULL) { - ok = ENOMEM; goto fail_free_connectors; } @@ -728,7 +715,6 @@ struct drm_resources *drm_resources_new(int drm_fd) { r->crtcs = calloc(res->count_crtcs, sizeof *(r->crtcs)); if (r->crtcs == NULL) { - ok = ENOMEM; goto fail_free_encoders; } @@ -747,14 +733,13 @@ struct drm_resources *drm_resources_new(int drm_fd) { drmModePlaneRes *plane_res = drmModeGetPlaneResources(drm_fd); if (plane_res == NULL) { - ok = errno ?: EINVAL; + 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) { - ok = ENOMEM; goto fail_free_plane_res; } @@ -801,10 +786,19 @@ struct drm_resources *drm_resources_new(int drm_fd) { 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 *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; @@ -824,7 +818,7 @@ struct drm_resources *drm_resources_new_filtered(int drm_fd, uint32_t connector_ { drmModeRes *res = drmModeGetResources(drm_fd); if (res == NULL) { - ok = errno ?: EINVAL; + ok = errno ? errno : EINVAL; LOG_ERROR("Could not get DRM device resources. drmModeGetResources: %s\n", strerror(ok)); goto fail_free_r; } @@ -839,7 +833,6 @@ struct drm_resources *drm_resources_new_filtered(int drm_fd, uint32_t connector_ r->connectors = calloc(1, sizeof *(r->connectors)); if (r->connectors == NULL) { - ok = ENOMEM; goto fail_free_r; } @@ -857,7 +850,6 @@ struct drm_resources *drm_resources_new_filtered(int drm_fd, uint32_t connector_ r->encoders = calloc(1, sizeof *(r->encoders)); if (r->encoders == NULL) { - ok = ENOMEM; goto fail_free_connectors; } @@ -875,7 +867,6 @@ struct drm_resources *drm_resources_new_filtered(int drm_fd, uint32_t connector_ r->crtcs = calloc(1, sizeof *(r->crtcs)); if (r->crtcs == NULL) { - ok = ENOMEM; goto fail_free_encoders; } @@ -896,7 +887,6 @@ struct drm_resources *drm_resources_new_filtered(int drm_fd, uint32_t connector_ r->planes = calloc(r->filter.n_planes, sizeof *(r->planes)); if (r->planes == NULL) { - ok = ENOMEM; goto fail_free_crtcs; } @@ -922,7 +912,6 @@ struct drm_resources *drm_resources_new_filtered(int drm_fd, uint32_t connector_ return 0; - fail_free_crtcs: for (int i = 0; i < r->n_crtcs; i++) { drm_crtc_fini(r->crtcs + i); @@ -936,11 +925,11 @@ struct drm_resources *drm_resources_new_filtered(int drm_fd, uint32_t connector_ 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); @@ -955,7 +944,14 @@ struct drm_resources *drm_resources_new_filtered(int drm_fd, uint32_t connector_ 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 *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; @@ -983,7 +979,6 @@ struct drm_resources *drm_resources_dup_filtered(struct drm_resources *res, uint { r->connectors = calloc(1, sizeof(struct drm_connector)); if (r->connectors == NULL) { - ok = ENOMEM; goto fail_free_r; } @@ -1004,7 +999,6 @@ struct drm_resources *drm_resources_dup_filtered(struct drm_resources *res, uint { r->encoders = calloc(1, sizeof(struct drm_encoder)); if (r->encoders == NULL) { - ok = ENOMEM; goto fail_free_connectors; } @@ -1025,7 +1019,6 @@ struct drm_resources *drm_resources_dup_filtered(struct drm_resources *res, uint { r->crtcs = calloc(1, sizeof(struct drm_crtc)); if (r->crtcs == NULL) { - ok = ENOMEM; goto fail_free_encoders; } @@ -1045,7 +1038,6 @@ struct drm_resources *drm_resources_dup_filtered(struct drm_resources *res, uint r->planes = calloc(r->filter.n_planes, sizeof *(r->planes)); if (r->planes == NULL) { - ok = ENOMEM; goto fail_free_crtcs; } @@ -1078,8 +1070,7 @@ struct drm_resources *drm_resources_dup_filtered(struct drm_resources *res, uint r->planes = NULL; } - return 0; - + return r; fail_free_crtcs: for (int i = 0; i < r->n_crtcs; i++) { @@ -1094,11 +1085,11 @@ struct drm_resources *drm_resources_dup_filtered(struct drm_resources *res, uint 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); @@ -1138,7 +1129,7 @@ void drm_resources_destroy(struct drm_resources *r) { for (int i = 0; i < r->n_connectors; i++) { drm_connector_fini(r->connectors + i); } - + if (r->connectors != NULL) { free(r->connectors); } @@ -1148,10 +1139,9 @@ void drm_resources_destroy(struct drm_resources *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, @@ -1202,7 +1192,8 @@ int drm_resources_update(struct drm_resources *r, int drm_fd, const struct drm_u UNIMPLEMENTED(); } break; - } + default: UNREACHABLE(); + } return 0; } @@ -1233,7 +1224,6 @@ void drm_resources_apply_rockchip_workaround(struct drm_resources *r) { } } - bool drm_resources_has_connector(struct drm_resources *r, uint32_t connector_id) { ASSERT_NOT_NULL(r); @@ -1272,7 +1262,7 @@ struct drm_encoder *drm_resources_get_encoder(struct drm_resources *r, uint32_t 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; } @@ -1362,7 +1352,6 @@ unsigned int drm_resources_get_plane_index(struct drm_resources *r, uint32_t pla return UINT_MAX; } - struct drm_connector *drm_resources_connector_first(struct drm_resources *r) { ASSERT_NOT_NULL(r); @@ -1382,7 +1371,6 @@ struct drm_connector *drm_resources_connector_next(struct drm_resources *r, stru return current + 1; } - drmModeModeInfo *drm_connector_mode_first(struct drm_connector *c) { ASSERT_NOT_NULL(c); @@ -1402,7 +1390,6 @@ drmModeModeInfo *drm_connector_mode_next(struct drm_connector *c, drmModeModeInf return current + 1; } - struct drm_encoder *drm_resources_encoder_first(struct drm_resources *r) { ASSERT_NOT_NULL(r); @@ -1422,7 +1409,6 @@ struct drm_encoder *drm_resources_encoder_next(struct drm_resources *r, struct d return current + 1; } - struct drm_crtc *drm_resources_crtc_first(struct drm_resources *r) { ASSERT_NOT_NULL(r); @@ -1442,7 +1428,6 @@ struct drm_crtc *drm_resources_crtc_next(struct drm_resources *r, struct drm_crt return current + 1; } - struct drm_plane *drm_resources_plane_first(struct drm_resources *r) { ASSERT_NOT_NULL(r); @@ -1462,7 +1447,6 @@ struct drm_plane *drm_resources_plane_next(struct drm_resources *r, struct drm_p return current + 1; } - struct drm_blob { int drm_fd; bool close_fd; @@ -1505,7 +1489,6 @@ struct drm_blob *drm_blob_new_mode(int drm_fd, const drmModeModeInfo *mode, bool blob->mode = *mode; return blob; - fail_maybe_close_fd: if (blob->close_fd) { close(blob->drm_fd); @@ -1531,7 +1514,7 @@ void drm_blob_destroy(struct drm_blob *blob) { if (blob->close_fd) { close(blob->drm_fd); } - + free(blob); } @@ -1539,5 +1522,3 @@ int drm_blob_get_id(struct drm_blob *blob) { ASSERT_NOT_NULL(blob); return blob->blob_id; } - - diff --git a/src/plugins/sentry/sentry.c b/src/plugins/sentry/sentry.c index 44ad72eb..367928fd 100644 --- a/src/plugins/sentry/sentry.c +++ b/src/plugins/sentry/sentry.c @@ -803,4 +803,4 @@ void sentry_plugin_deinit(struct flutterpi *flutterpi, void *userdata) { free(plugin); } -FLUTTERPI_PLUGIN("sentry", sentry_plugin, sentry_plugin_init, sentry_plugin_deinit); +FLUTTERPI_PLUGIN("sentry", sentry_plugin, sentry_plugin_init, sentry_plugin_deinit) diff --git a/src/user_input.c b/src/user_input.c index 04e71124..c812c42a 100644 --- a/src/user_input.c +++ b/src/user_input.c @@ -10,6 +10,8 @@ #include #include +#include +#include #include #include #include @@ -19,8 +21,6 @@ #include "flutter-pi.h" #include "keyboard.h" #include "util/collection.h" -#include "util/khash.h" -#include "util/kvec.h" #include "util/logging.h" #define LIBINPUT_VER(major, minor, patch) ((((major) & 0xFF) << 16) | (((minor) & 0xFF) << 8) | ((patch) & 0xFF)) @@ -489,7 +489,7 @@ struct user_input *user_input_new_suspended(const struct file_interface *interfa libinput_suspend(input->libinput); - ok = libinput_udev_assign_seat(input->libinput, seat ?: "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; @@ -1537,11 +1537,11 @@ add_listener(struct user_input *input, bool is_primary, enum user_input_event_ty } void user_input_add_primary_listener(struct user_input *input, enum user_input_event_type filter, user_input_event_cb_t cb, void *userdata) { - return add_listener(input, true, filter, cb, 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) { - return add_listener(input, false, filter, cb, userdata); + add_listener(input, false, filter, cb, userdata); } void user_input_device_set_primary_listener_userdata(struct user_input_device *device, void *userdata) { diff --git a/src/util/khash.h b/src/util/khash.h index f75f3474..586a653a 100644 --- a/src/util/khash.h +++ b/src/util/khash.h @@ -1,627 +1,8 @@ -/* The MIT License +#ifndef _FLUTTERPI_SRC_UTIL_KHASH_H +#define _FLUTTERPI_SRC_UTIL_KHASH_H - Copyright (c) 2008, 2009, 2011 by Attractive Chaos +// NOLINTBEGIN(bugprone-suspicious-realloc-usage) +#include +// NOLINTEND(bugprone-suspicious-realloc-usage) - 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 */ \ - h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ - if (kh_is_map) 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 */ +#endif // _FLUTTERPI_SRC_UTIL_KHASH_H \ No newline at end of file diff --git a/src/util/macros.h b/src/util/macros.h index 777d2d94..bbc093df 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -167,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. @@ -189,6 +219,8 @@ }) #endif +PRAGMA_DIAGNOSTIC_POP + /** * Unreachable macro. Useful for suppressing "control reaches end of non-void * function" warnings. @@ -523,34 +555,6 @@ static inline uint64_t u_uintN_max(unsigned bit_size) { #endif #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) - #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 - #define PASTE2(a, b) a##b #define PASTE3(a, b, c) a##b##c #define PASTE4(a, b, c, d) a##b##c##d 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/window.c b/src/window.c index 23e84aa2..aad1626d 100644 --- a/src/window.c +++ b/src/window.c @@ -94,7 +94,7 @@ 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, + float refresh_rate, bool has_forced_pixel_format, enum pixfmt forced_pixel_format, enum renderer_type renderer_type, struct gl_renderer *gl_renderer, @@ -287,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; @@ -495,6 +495,7 @@ void window_on_input( fl_event->y = event->tablet.position_ndc.y * window->view_size.y; break; } + case USER_INPUT_KEY: UNREACHABLE(); default: break; } diff --git a/src/window.h b/src/window.h index e4ed2570..6db876e6 100644 --- a/src/window.h +++ b/src/window.h @@ -59,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. diff --git a/src/window_private.h b/src/window_private.h index 913cdccd..c30b8a30 100644 --- a/src/window_private.h +++ b/src/window_private.h @@ -86,7 +86,7 @@ struct window { * @brief Refresh rate of the selected video mode / display. * */ - double refresh_rate; + float refresh_rate; /** * @brief Flutter device pixel ratio (in the horizontal axis). Number of physical pixels per logical pixel. @@ -96,7 +96,7 @@ struct window { * 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; + float pixel_ratio; /** * @brief Whether we have physical screen dimensions and @ref width_mm and @ref height_mm contain usable values. @@ -247,7 +247,7 @@ 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, + float refresh_rate, bool has_forced_pixel_format, enum pixfmt forced_pixel_format, enum renderer_type renderer_type, struct gl_renderer *gl_renderer, 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/src/util/kvec.h b/third_party/klib/include/klib/kvec.h similarity index 98% rename from src/util/kvec.h rename to third_party/klib/include/klib/kvec.h index a15455dc..d9d5a278 100644 --- a/src/util/kvec.h +++ b/third_party/klib/include/klib/kvec.h @@ -72,6 +72,7 @@ /// @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) From eac03dd559bb98712a830f58cfc84a7b4f8e8eec Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 17 Sep 2024 10:26:38 +0000 Subject: [PATCH 17/27] fixes for codechecker --- src/dummy_window.c | 2 +- src/dummy_window.h | 2 +- src/kms/kms_window.c | 2 +- src/kms/req_builder.c | 1 + src/kms/resources.h | 73 ++-- src/pluginregistry.c | 424 ++++++++------------ src/pluginregistry.h | 170 ++++---- src/plugins/audioplayers/plugin.c | 10 +- src/plugins/gstreamer_video_player/plugin.c | 98 ++--- src/plugins/sentry/sentry.c | 9 +- src/plugins/services.c | 34 +- src/plugins/text_input.c | 7 +- src/user_input.c | 14 +- src/util/refcounting.h | 78 ++-- 14 files changed, 406 insertions(+), 518 deletions(-) diff --git a/src/dummy_window.c b/src/dummy_window.c index d3c41c43..81177945 100644 --- a/src/dummy_window.c +++ b/src/dummy_window.c @@ -14,7 +14,7 @@ MUST_CHECK struct window *dummy_window_new( struct vk_renderer *vk_renderer, struct vec2i size, bool has_explicit_dimensions, int width_mm, int height_mm, - double refresh_rate + float refresh_rate // clang-format on ) { struct window *window; diff --git a/src/dummy_window.h b/src/dummy_window.h index 36f42f00..93fa2093 100644 --- a/src/dummy_window.h +++ b/src/dummy_window.h @@ -18,7 +18,7 @@ MUST_CHECK struct window *dummy_window_new( struct vk_renderer *vk_renderer, struct vec2i size, bool has_explicit_dimensions, int width_mm, int height_mm, - double refresh_rate + float refresh_rate // clang-format on ); diff --git a/src/kms/kms_window.c b/src/kms/kms_window.c index 8b0c3531..61605e75 100644 --- a/src/kms/kms_window.c +++ b/src/kms/kms_window.c @@ -434,7 +434,7 @@ MUST_CHECK struct window *kms_window_new( " pixel format: %s\n", selected_mode->hdisplay, selected_mode->vdisplay, - mode_get_vrefresh(selected_mode), + (double) mode_get_vrefresh(selected_mode), width_mm, height_mm, (double) window->base.pixel_ratio, diff --git a/src/kms/req_builder.c b/src/kms/req_builder.c index c5565790..b657d633 100644 --- a/src/kms/req_builder.c +++ b/src/kms/req_builder.c @@ -839,6 +839,7 @@ static int kms_req_commit_common( /// TODO: Handle setting other properties as well /// TODO: Implement + (void) needs_set_crtc; UNIMPLEMENTED(); // if (needs_set_crtc) { diff --git a/src/kms/resources.h b/src/kms/resources.h index 5d2d3bc0..428902f2 100644 --- a/src/kms/resources.h +++ b/src/kms/resources.h @@ -9,18 +9,18 @@ #include #include +#include #include #include -#include +#include #include #include #include #include -#include -#include "pixel_format.h" #include "monitor.h" +#include "pixel_format.h" #include "util/bitset.h" #include "util/list.h" #include "util/lock_ops.h" @@ -28,7 +28,6 @@ #include "util/macros.h" #include "util/refcounting.h" - #define DRM_ID_NONE ((uint32_t) 0xFFFFFFFF) #define DRM_ID_IS_VALID(id) ((id) != 0 && (id) != DRM_ID_NONE) @@ -401,10 +400,10 @@ enum drm_encoder_type { struct drm_encoder { uint32_t id; - enum drm_encoder_type type; - - uint32_t possible_crtcs; - uint32_t possible_clones; + enum drm_encoder_type type; + + uint32_t possible_crtcs; + uint32_t possible_clones; struct { uint32_t crtc_id; @@ -646,7 +645,7 @@ struct drm_resources { } filter; uint32_t min_width, max_width; - uint32_t min_height, max_height; + uint32_t min_height, max_height; size_t n_connectors; struct drm_connector *connectors; @@ -676,15 +675,28 @@ bool drm_plane_supports_unmodified_format(struct drm_plane *plane, enum pixfmt f bool drm_resources_any_crtc_plane_supports_format(struct drm_resources *res, uint32_t crtc_id, enum pixfmt pixel_format); - /** * @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); +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 *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 *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 drm_resources_destroy(struct drm_resources *r); @@ -713,7 +725,6 @@ int drm_resources_update(struct drm_resources *r, int drm_fd, const struct drm_u */ void drm_resources_apply_rockchip_workaround(struct drm_resources *r); - bool drm_resources_has_connector(struct drm_resources *r, uint32_t connector_id); struct drm_connector *drm_resources_get_connector(struct drm_resources *r, uint32_t connector_id); @@ -736,57 +747,55 @@ struct drm_plane *drm_resources_get_plane(struct drm_resources *r, uint32_t plan unsigned int drm_resources_get_plane_index(struct drm_resources *r, uint32_t plane_id); - struct drm_connector *drm_resources_connector_first(struct drm_resources *r); struct drm_connector *drm_resources_connector_end(struct drm_resources *r); struct drm_connector *drm_resources_connector_next(struct drm_resources *r, struct drm_connector *current); - drmModeModeInfo *drm_connector_mode_first(struct drm_connector *c); drmModeModeInfo *drm_connector_mode_end(struct drm_connector *c); drmModeModeInfo *drm_connector_mode_next(struct drm_connector *c, drmModeModeInfo *current); - struct drm_encoder *drm_resources_encoder_first(struct drm_resources *r); struct drm_encoder *drm_resources_encoder_end(struct drm_resources *r); struct drm_encoder *drm_resources_encoder_next(struct drm_resources *r, struct drm_encoder *current); - struct drm_crtc *drm_resources_crtc_first(struct drm_resources *r); struct drm_crtc *drm_resources_crtc_end(struct drm_resources *r); struct drm_crtc *drm_resources_crtc_next(struct drm_resources *r, struct drm_crtc *current); - struct drm_plane *drm_resources_plane_first(struct drm_resources *r); struct drm_plane *drm_resources_plane_end(struct drm_resources *r); struct drm_plane *drm_resources_plane_next(struct drm_resources *r, struct drm_plane *current); +#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))) -#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))) - -#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))) - -#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 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))) -#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 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 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 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 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))) struct drm_blob *drm_blob_new_mode(int drm_fd, const drmModeModeInfo *mode, bool dup_fd); @@ -797,8 +806,8 @@ int drm_blob_get_id(struct drm_blob *blob); /** * @brief Get the precise refresh rate of a video mode. */ -static inline double mode_get_vrefresh(const drmModeModeInfo *mode) { - return mode->clock * 1000.0 / (mode->htotal * mode->vtotal); +static inline float mode_get_vrefresh(const drmModeModeInfo *mode) { + return mode->clock * 1000.0f / (mode->htotal * mode->vtotal); } #endif \ No newline at end of file diff --git a/src/pluginregistry.c b/src/pluginregistry.c index f78b917b..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,186 +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; -} - -// clang-format off -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; -} -// clang-format on +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(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; } @@ -236,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 @@ -299,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(®istry->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; + assert_mutex_locked(®istry->callbacks_lock); - data = malloc(sizeof *data); - if (data == NULL) { - free(channel_dup); - return ENOMEM; - } - - 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; } @@ -347,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; } @@ -391,50 +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); - - // Analyzer thinks get_cb_data_by_channel might still return our data - // after list_del and emits a "use-after-free" warning. - // assert()s can change the assumptions of the analyzer, so we use them here. -#ifdef DEBUG - list_for_each_entry(struct platch_obj_cb_data, data_iter, ®istry->callbacks, entry) { - ASSUME(data_iter != data); - } -#endif - - 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; @@ -445,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; -} - -void *plugin_registry_get_plugin_userdata(struct plugin_registry *registry, const char *plugin_name) { - struct plugin_instance *instance; + mutex_lock(®istry->plugins_lock); - instance = get_plugin_by_name(registry, plugin_name); + bool present = plugin_registry_is_plugin_present_locked(registry, plugin_name); - return instance != NULL ? instance->userdata : NULL; -} - -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); + mutex_unlock(®istry->plugins_lock); - return instance != NULL ? instance->userdata : NULL; + return present; } static void static_plugin_registry_initialize(void) { - ASSERTED int ok; + static_plugins = kh_init(static_plugins); + ASSERT_NOT_NULL(static_plugins); - list_inithead(&static_plugins); - - ok = pthread_mutex_init(&static_plugins_lock, get_default_mutex_attrs()); - ASSERT_ZERO(ok); + mutex_init(&static_plugins_lock); } 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); + khiter_t entry = kh_put(static_plugins, static_plugins, plugin->name, &ok); + ASSERT(ok >= 0); - entry->plugin = plugin; + kh_value(static_plugins, entry) = flutterpi_plugin_v2_ref(plugin); - list_addtail(&entry->entry, &static_plugins); - - 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 b629e720..1896c5f5 100644 --- a/src/pluginregistry.h +++ b/src/pluginregistry.h @@ -16,18 +16,35 @@ #include #include "platformchannel.h" +#include "util/refcounting.h" struct flutterpi; struct plugin_registry; -/// The return value of a plugin initializer function. +/** + * @brief The result of a plugin initialization. + */ 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. + /** + * @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); @@ -35,21 +52,29 @@ typedef enum plugin_init_result (*plugin_init_t)(struct flutterpi *flutterpi, vo 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; }; +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,72 +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(void) { \ - 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(void) { \ - 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/plugin.c b/src/plugins/audioplayers/plugin.c index 1e7e7c84..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); diff --git a/src/plugins/gstreamer_video_player/plugin.c b/src/plugins/gstreamer_video_player/plugin.c index 4d03812a..e2992679 100644 --- a/src/plugins/gstreamer_video_player/plugin.c +++ b/src/plugins/gstreamer_video_player/plugin.c @@ -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); } @@ -1328,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); } @@ -1647,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 @@ -1719,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; } @@ -1727,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); @@ -1775,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) { @@ -1785,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/sentry/sentry.c b/src/plugins/sentry/sentry.c index 367928fd..0b2aaafa 100644 --- a/src/plugins/sentry/sentry.c +++ b/src/plugins/sentry/sentry.c @@ -773,12 +773,7 @@ enum plugin_init_result sentry_plugin_init(struct flutterpi *flutterpi, void **u 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; @@ -799,7 +794,7 @@ void sentry_plugin_deinit(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); } 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 0162b7ac..4ca24b08 100644 --- a/src/plugins/text_input.c +++ b/src/plugins/text_input.c @@ -648,6 +648,9 @@ static bool model_add_text(const char *str) { // make place for the utf8 charactercursor memmove(to_move + l, to_move, strlen((char *) to_move) + 1 /* null byte */); + + // 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 @@ -827,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; @@ -854,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/user_input.c b/src/user_input.c index c812c42a..6a529447 100644 --- a/src/user_input.c +++ b/src/user_input.c @@ -39,7 +39,7 @@ 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; @@ -174,7 +174,7 @@ make_slot_removed_event(struct libinput_device *device, uint64_t timestamp, int6 } static inline struct user_input_event -make_pointer_motion_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, int64_t buttons, struct vec2f delta) { +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)); @@ -193,7 +193,7 @@ make_pointer_motion_event(struct libinput_device *device, uint64_t timestamp, in } static inline struct user_input_event -make_pointer_motion_absolute_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, int64_t buttons, struct vec2f pos) { +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)); @@ -211,7 +211,7 @@ make_pointer_motion_absolute_event(struct libinput_device *device, uint64_t time } static inline struct user_input_event -make_pointer_button_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, int64_t buttons, int64_t changed_buttons) { +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)); @@ -229,7 +229,7 @@ make_pointer_button_event(struct libinput_device *device, uint64_t timestamp, in } static inline struct user_input_event -make_pointer_axis_event(struct libinput_device *device, uint64_t timestamp, int64_t slot_id, int64_t buttons, struct vec2f scroll_delta) { +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)); @@ -949,8 +949,8 @@ static int on_mouse_button_event(struct user_input *input, struct libinput_event struct libinput_device *device; 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); 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 From 408767aabbe3e94a796cd283955d9651c20329f7 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 17 Sep 2024 10:58:21 +0000 Subject: [PATCH 18/27] clang pedantic warning fixes --- CMakePresets.json | 18 ++++++++++++++++++ src/compositor_ng.c | 2 +- src/dummy_window.h | 2 +- src/flutter-pi.c | 7 +++++-- src/kms/drmdev.c | 2 +- src/kms/kms_window.c | 15 ++++++++++----- src/kms/kms_window.h | 2 +- src/kms/req_builder.c | 16 ++++++---------- src/kms/resources.h | 2 +- src/util/event_loop.c | 12 +++++------- src/util/event_loop.h | 6 ++---- src/util/khash.h | 2 +- src/util/lock_ops.c | 10 +++++----- src/util/lock_ops.h | 4 ++-- src/window_private.h | 2 +- third_party/klib/include/klib/kvec.h | 2 +- 16 files changed, 61 insertions(+), 43 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 8ae87641..a7fdfdd2 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -33,6 +33,24 @@ "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)", diff --git a/src/compositor_ng.c b/src/compositor_ng.c index 71b2e5b5..e47cd352 100644 --- a/src/compositor_ng.c +++ b/src/compositor_ng.c @@ -1794,7 +1794,7 @@ static void on_input(void *userdata, size_t n_events, const struct user_input_ev 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: %d\n", event->type); break; + default: LOG_DEBUG("Unhandled enum user_input_event: %u\n", event->type); break; } } diff --git a/src/dummy_window.h b/src/dummy_window.h index 93fa2093..2062b34c 100644 --- a/src/dummy_window.h +++ b/src/dummy_window.h @@ -22,4 +22,4 @@ MUST_CHECK struct window *dummy_window_new( // clang-format on ); -#endif // _FLUTTERPI_SRC_DUMMY_WINDOW_H \ No newline at end of file +#endif // _FLUTTERPI_SRC_DUMMY_WINDOW_H diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 1a4e9472..412fb57f 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -628,7 +628,7 @@ static void on_execute_fl_task(void *userdata) { result = task->fl_run_task(task->fl_engine, &task->fl_task); if (result != kSuccess) { - LOG_ERROR("Error running platform task. FlutterEngineRunTask: %d\n", result); + LOG_ERROR("Error running platform task. FlutterEngineRunTask: %u\n", result); } free(task); @@ -2097,7 +2097,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { fpi->tracer = tracer_new_with_stubs(); if (fpi->tracer == NULL) { LOG_ERROR("Couldn't create event tracer.\n"); - goto fail_destroy_drmdev; + goto fail_destroy_drm_resources; } if (renderer_type == kVulkan_RendererType) { @@ -2346,6 +2346,9 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { fail_unref_tracer: tracer_unref(fpi->tracer); +fail_destroy_drm_resources: + drm_resources_unref(drm_resources); + fail_destroy_drmdev: drmdev_unref(fpi->drmdev); diff --git a/src/kms/drmdev.c b/src/kms/drmdev.c index 7d7c49ab..c9347af4 100644 --- a/src/kms/drmdev.c +++ b/src/kms/drmdev.c @@ -119,7 +119,7 @@ static bool is_kms_device(const char *path, const struct file_interface *interfa return true; } -static void assert_rotations_work() { +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); diff --git a/src/kms/kms_window.c b/src/kms/kms_window.c index 61605e75..96c3a18d 100644 --- a/src/kms/kms_window.c +++ b/src/kms/kms_window.c @@ -870,12 +870,17 @@ static struct render_surface *kms_window_get_render_surface_internal(struct kms_ // First, count the allowed modifiers for this pixel format. drm_plane_for_each_modified_format(plane_it, 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; + 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); + // 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; } diff --git a/src/kms/kms_window.h b/src/kms/kms_window.h index 3072eadf..437c5fc3 100644 --- a/src/kms/kms_window.h +++ b/src/kms/kms_window.h @@ -30,4 +30,4 @@ MUST_CHECK struct window *kms_window_new( // clang-format on ); -#endif // _FLUTTERPI_SRC_KMS_KMS_WINDOW_H \ No newline at end of file +#endif // _FLUTTERPI_SRC_KMS_KMS_WINDOW_H diff --git a/src/kms/req_builder.c b/src/kms/req_builder.c index b657d633..dce8a442 100644 --- a/src/kms/req_builder.c +++ b/src/kms/req_builder.c @@ -274,7 +274,7 @@ struct kms_req_builder *kms_req_builder_new_atomic(struct drmdev *drmdev, struct builder->crtc = drm_resources_get_crtc(resources, crtc_id); if (builder->crtc == NULL) { - LOG_ERROR("Invalid CRTC: %" PRId32 "\n", crtc_id); + LOG_ERROR("Invalid CRTC: %" PRIu32 "\n", crtc_id); goto fail_unref_drmdev; } @@ -319,7 +319,7 @@ struct kms_req_builder *kms_req_builder_new_legacy(struct drmdev *drmdev, struct builder->crtc = drm_resources_get_crtc(resources, crtc_id); if (builder->crtc == NULL) { - LOG_ERROR("Invalid CRTC: %" PRId32 "\n", crtc_id); + LOG_ERROR("Invalid CRTC: %" PRIu32 "\n", crtc_id); goto fail_unref_drmdev; } @@ -699,13 +699,8 @@ static int kms_req_commit_common( void *release_cb_userdata ) { struct kms_req_builder *builder; - struct drm_blob *mode_blob; - bool update_mode; int ok; - update_mode = false; - mode_blob = NULL; - ASSERT_NOT_NULL(req); builder = (struct kms_req_builder *) req; @@ -733,6 +728,8 @@ static int kms_req_commit_common( } } + 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); @@ -943,9 +940,8 @@ static int kms_req_commit_common( fail_unref_builder: kms_req_builder_unref(builder); - // fail_maybe_destroy_mode_blob: - // if (mode_blob != NULL) - // drm_blob_destroy(mode_blob); + if (mode_blob != NULL) + drm_blob_destroy(mode_blob); return ok; } diff --git a/src/kms/resources.h b/src/kms/resources.h index 428902f2..ed93fbb1 100644 --- a/src/kms/resources.h +++ b/src/kms/resources.h @@ -810,4 +810,4 @@ static inline float mode_get_vrefresh(const drmModeModeInfo *mode) { return mode->clock * 1000.0f / (mode->htotal * mode->vtotal); } -#endif \ No newline at end of file +#endif // _FLUTTERPI_MODESETTING_RESOURCES_H diff --git a/src/util/event_loop.c b/src/util/event_loop.c index 390555c0..fa550c7f 100644 --- a/src/util/event_loop.c +++ b/src/util/event_loop.c @@ -5,16 +5,15 @@ #include #include +#include #include #include #include -#include - -#include "util/lock_ops.h" #include "util/collection.h" -#include "util/refcounting.h" +#include "util/lock_ops.h" #include "util/logging.h" +#include "util/refcounting.h" struct evloop { refcount_t n_refs; @@ -43,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; @@ -344,7 +343,6 @@ static int evloop_post_delayed_task_locked(struct evloop *loop, void_callback_t return 0; - fail_free_task: free(task); return ok; @@ -478,7 +476,7 @@ static void *evthread_entry(void *userdata) { evthread->loop = evloop; evthread->thread = pthread_self(); - + args->evthread = evthread; args->initialization_success = true; sem_post(&args->initialization_done); diff --git a/src/util/event_loop.h b/src/util/event_loop.h index 5c5a0eef..61d0a787 100644 --- a/src/util/event_loop.h +++ b/src/util/event_loop.h @@ -16,8 +16,8 @@ #include -#include "util/refcounting.h" #include "util/collection.h" +#include "util/refcounting.h" /** * @brief An event loop. @@ -37,7 +37,7 @@ struct evloop; * * @returns A new event loop or NULL on error. */ -struct evloop *evloop_new(); +struct evloop *evloop_new(void); DECLARE_REF_OPS(evloop) @@ -76,7 +76,6 @@ int evloop_post_task(struct evloop *loop, void_callback_t callback, void *userda */ 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. @@ -141,7 +140,6 @@ typedef enum event_handler_return (*evloop_io_handler_t)(int fd, uint32_t revent */ struct evsrc *evloop_add_io(struct evloop *loop, int fd, uint32_t events, evloop_io_handler_t callback, void *userdata); - struct evthread; /** diff --git a/src/util/khash.h b/src/util/khash.h index 586a653a..26c71b72 100644 --- a/src/util/khash.h +++ b/src/util/khash.h @@ -5,4 +5,4 @@ #include // NOLINTEND(bugprone-suspicious-realloc-usage) -#endif // _FLUTTERPI_SRC_UTIL_KHASH_H \ No newline at end of file +#endif // _FLUTTERPI_SRC_UTIL_KHASH_H diff --git a/src/util/lock_ops.c b/src/util/lock_ops.c index 56070514..78287c0b 100644 --- a/src/util/lock_ops.c +++ b/src/util/lock_ops.c @@ -2,14 +2,14 @@ static pthread_mutexattr_t default_mutex_attrs; -static void init_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() { +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); @@ -19,14 +19,14 @@ const pthread_mutexattr_t *get_default_mutex_attrs() { static pthread_mutexattr_t default_recursive_mutex_attrs; -static void init_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); + pthread_mutexattr_settype(&default_recursive_mutex_attrs, PTHREAD_MUTEX_RECURSIVE_NP); } -const pthread_mutexattr_t *get_default_recursive_mutex_attrs() { +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); diff --git a/src/util/lock_ops.h b/src/util/lock_ops.h index 9dd70ee7..b465950c 100644 --- a/src/util/lock_ops.h +++ b/src/util/lock_ops.h @@ -66,9 +66,9 @@ #define NO_THREAD_SAFETY_ANALYSIS THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) -const pthread_mutexattr_t *get_default_mutex_attrs(); +const pthread_mutexattr_t *get_default_mutex_attrs(void); -const pthread_mutexattr_t *get_default_recursive_mutex_attrs(); +const pthread_mutexattr_t *get_default_recursive_mutex_attrs(void); typedef pthread_mutex_t mutex_t CAPABILITY("mutex"); diff --git a/src/window_private.h b/src/window_private.h index c30b8a30..b491afe2 100644 --- a/src/window_private.h +++ b/src/window_private.h @@ -257,4 +257,4 @@ int window_init( void window_deinit(struct window *window); -#endif // _FLUTTERPI_SRC_WINDOW_PRIVATE_H \ No newline at end of file +#endif // _FLUTTERPI_SRC_WINDOW_PRIVATE_H diff --git a/third_party/klib/include/klib/kvec.h b/third_party/klib/include/klib/kvec.h index d9d5a278..61c82c6b 100644 --- a/third_party/klib/include/klib/kvec.h +++ b/third_party/klib/include/klib/kvec.h @@ -120,4 +120,4 @@ ((v).size <= (size_t) (i) ? (v).size = (i) + 1 : 0UL)), \ &(v).items[(i)])) -#endif // NVIM_LIB_KVEC_H \ No newline at end of file +#endif // NVIM_LIB_KVEC_H From 93876b99c95c25254f9353ed96c10d921d822300 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 17 Sep 2024 11:03:24 +0000 Subject: [PATCH 19/27] paste `CodeChecker parse` output in job summary --- .github/workflows/static-analysis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 70c90d9d..4eca696a 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -53,6 +53,12 @@ jobs: ## ⚠ī¸ 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 From 4b3227812dfac00ca055cbe21fdf10e61b98c389 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 17 Sep 2024 11:07:03 +0000 Subject: [PATCH 20/27] don't use drmIsKMS not available everywhere (debian bullseye) --- src/kms/drmdev.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/kms/drmdev.c b/src/kms/drmdev.c index c9347af4..191b7941 100644 --- a/src/kms/drmdev.c +++ b/src/kms/drmdev.c @@ -110,13 +110,16 @@ static bool is_kms_device(const char *path, const struct file_interface *interfa return false; } - if (!drmIsKMS(fd)) { + // 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 true; + return res.count_crtcs > 0 && res.count_connectors > 0 && res.count_encoders > 0; } static void assert_rotations_work(void) { From ae163c5938ed958748985e9a35beb265ad695c40 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 17 Sep 2024 11:11:02 +0000 Subject: [PATCH 21/27] define _GNU_SOURCE in kms_window for asprintf --- src/kms/kms_window.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/kms/kms_window.c b/src/kms/kms_window.c index 96c3a18d..6ff83868 100644 --- a/src/kms/kms_window.c +++ b/src/kms/kms_window.c @@ -1,3 +1,5 @@ +#define _GNU_SOURCE /* for asprintf */ + #include "kms_window.h" #include "util/refcounting.h" From 54e08852f829cd9841bc75a38a4ed5362737fb59 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 17 Sep 2024 11:17:08 +0000 Subject: [PATCH 22/27] workaround analyzer issue in on_present_frame --- src/kms/kms_window.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/kms/kms_window.c b/src/kms/kms_window.c index 6ff83868..9ac4de3c 100644 --- a/src/kms/kms_window.c +++ b/src/kms/kms_window.c @@ -545,7 +545,11 @@ static void on_present_frame(void *userdata) { if (ok != 0) { LOG_ERROR("Could not commit frame request.\n"); frame_scheduler_unref(frame->scheduler); - tracer_unref(frame->tracer); + + // 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); } From d8d37c3199d1dee2a5c8408a48561abb15d25fc0 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 17 Sep 2024 11:18:07 +0000 Subject: [PATCH 23/27] fix formatting of codechecker parse output in step summary --- .github/workflows/static-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 4eca696a..c263ac61 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -54,7 +54,7 @@ jobs: Please see the 'CodeChecker Bug Reports' artifact for more details: - ${{ steps.upload.outputs.artifact-url }} - `CodeChecker parse`: + **\`CodeChecker parse\`:** EOF echo '```' >>$GITHUB_STEP_SUMMARY From 378832b04e848f995969a1e47a5d7ca51f99824d Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 17 Sep 2024 11:26:40 +0000 Subject: [PATCH 24/27] don't use drmModeGetConnectorTypeName too new --- src/compositor_ng.c | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/compositor_ng.c b/src/compositor_ng.c index e47cd352..0fd30577 100644 --- a/src/compositor_ng.c +++ b/src/compositor_ng.c @@ -152,6 +152,42 @@ struct display_setup { 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, @@ -181,8 +217,9 @@ static const enum connector_type connector_types[] = { }; bool connector_init(const struct drm_connector *connector, int64_t fl_display_id, struct connector *out) { - const char *type_name = drmModeGetConnectorTypeName(connector->type); - + // 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; From 7112fc2b7557498a511112ebf1d3031e770bacb2 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 17 Sep 2024 11:34:24 +0000 Subject: [PATCH 25/27] fix `FlutterWindowMetricsEvent` size check for armv7 --- src/compositor_ng.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compositor_ng.c b/src/compositor_ng.c index 0fd30577..bf59e647 100644 --- a/src/compositor_ng.c +++ b/src/compositor_ng.c @@ -496,7 +496,7 @@ static void send_window_metrics_events(struct compositor *compositor) { kh_foreach(compositor->views, view_id, window, { struct view_geometry geo = window_get_view_geometry(window); - COMPILE_ASSERT(sizeof(FlutterWindowMetricsEvent) == 96); + COMPILE_ASSERT(sizeof(FlutterWindowMetricsEvent) == 80 || sizeof(FlutterWindowMetricsEvent) == 96); FlutterWindowMetricsEvent event; memset(&event, 0, sizeof event); From e84902cd3c07ca16547a4d34d7a5b3662b87e3dd Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 17 Sep 2024 11:51:18 +0000 Subject: [PATCH 26/27] require `libsystemd` v243 at minimum for sd_event_source_disable_unref --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1127541d..6eb4544b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,7 +95,7 @@ include(CheckCXXCompilerFlag) 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) From bac52329e5928be3798458c9c2629fa5933d82d1 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 17 Sep 2024 11:53:23 +0000 Subject: [PATCH 27/27] ci: remove debian:buster container, add debian:bookworm container --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 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