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 f40e3ab8..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,122 +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: 'ubuntu-24.04' - timeout-minutes: 360 - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'cpp' ] - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - 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@v3 - 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@v3 - 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@v4 - 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 c84156cd..cac3106c 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 8fa47818..605029b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,10 +127,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/util/bitscan.c src/util/vector.c + src/util/collection.c src/cursor.c src/keyboard.c src/user_input.c @@ -162,13 +164,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 +240,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..2f2ada87 100644 --- a/src/compositor_ng.c +++ b/src/compositor_ng.c @@ -20,20 +20,22 @@ #include #include +#include #include #include "cursor.h" #include "dummy_render_surface.h" #include "flutter-pi.h" #include "frame_scheduler.h" -#include "modesetting.h" +#include "kms/drmdev.h" +#include "kms/req_builder.h" +#include "kms/resources.h" #include "notifier_listener.h" #include "pixel_format.h" #include "render_surface.h" #include "surface.h" #include "tracer.h" #include "util/collection.h" -#include "util/dynarray.h" #include "util/logging.h" #include "util/refcounting.h" #include "window.h" @@ -163,7 +165,7 @@ MUST_CHECK struct compositor *compositor_new(struct tracer *tracer, struct windo compositor->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); + COMPILE_ASSERT(sizeof(FlutterCompositor) == 28 || sizeof(FlutterCompositor) == 56); memset(&compositor->flutter_compositor, 0, sizeof(FlutterCompositor)); compositor->flutter_compositor.struct_size = sizeof(FlutterCompositor); @@ -231,7 +233,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 +264,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 +350,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 +387,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 +555,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/compositor_ng.h b/src/compositor_ng.h index d7360964..d1895f69 100644 --- a/src/compositor_ng.h +++ b/src/compositor_ng.h @@ -15,7 +15,9 @@ #include "cursor.h" #include "flutter-pi.h" #include "frame_scheduler.h" -#include "modesetting.h" +#include "kms/drmdev.h" +#include "kms/resources.h" +#include "kms/req_builder.h" #include "pixel_format.h" #include "util/collection.h" #include "util/refcounting.h" 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..aa2aeac9 100644 --- a/src/egl_gbm_render_surface.c +++ b/src/egl_gbm_render_surface.c @@ -16,7 +16,8 @@ #include "egl.h" #include "gl_renderer.h" #include "gles.h" -#include "modesetting.h" +#include "kms/req_builder.h" +#include "kms/resources.h" #include "pixel_format.h" #include "render_surface.h" #include "render_surface_private.h" @@ -146,22 +147,23 @@ 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( + gbm_surface = gbm_surface_create_with_modifiers2( gbm_device, size.x, size.y, get_pixfmt_info(pixel_format)->gbm_format, allowed_modifiers, - n_allowed_modifiers + n_allowed_modifiers, + GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING ); if (gbm_surface == NULL) { - ok = errno; - LOG_ERROR("Couldn't create GBM surface for rendering. gbm_surface_create_with_modifiers: %s\n", strerror(ok)); - LOG_ERROR("Will retry without modifiers\n"); + with_modifiers_errno = errno; } } + if (gbm_surface == NULL) { gbm_surface = gbm_surface_create( gbm_device, @@ -172,8 +174,20 @@ static int egl_gbm_render_surface_init( ); if (gbm_surface == NULL) { ok = errno; - LOG_ERROR("Couldn't create GBM surface for rendering. gbm_surface_create: %s\n", strerror(ok)); - return ok; + + if (allowed_modifiers != NULL) { + LOG_ERROR( + "Couldn't create GBM surface for rendering. gbm_surface_create_with_modifiers: %s, gbm_surface_create: %s\n", + strerror(with_modifiers_errno), + strerror(ok) + ); + } else { + LOG_ERROR("Couldn't create GBM surface for rendering. gbm_surface_create: %s\n", strerror(ok)); + } + + // Return an error != 0 in any case, so the caller doesn't think + // that the surface was created successfully. + return ok ? ok : EIO; } } @@ -383,10 +397,8 @@ static void on_release_layer(void *userdata) { static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder) { struct egl_gbm_render_surface *egl_surface; struct gbm_bo_meta *meta; - struct drmdev *drmdev; struct gbm_bo *bo; enum pixfmt pixel_format; - uint32_t fb_id, opaque_fb_id; int ok; egl_surface = CAST_THIS(s); @@ -410,16 +422,18 @@ static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl goto fail_unlock; } - drmdev = kms_req_builder_get_drmdev(builder); - ASSERT_NOT_NULL(drmdev); + meta->drmdev = kms_req_builder_get_drmdev(builder); + ASSERT_NOT_NULL(meta->drmdev); + + drmdev_ref(meta->drmdev); struct drm_crtc *crtc = kms_req_builder_get_crtc(builder); ASSERT_NOT_NULL(crtc); - if (drm_crtc_any_plane_supports_format(drmdev, crtc, egl_surface->pixel_format)) { + if (drm_resources_any_crtc_plane_supports_format(kms_req_builder_peek_resources(builder), crtc->id, egl_surface->pixel_format)) { TRACER_BEGIN(egl_surface->surface.tracer, "drmdev_add_fb (non-opaque)"); - fb_id = drmdev_add_fb_from_gbm_bo( - drmdev, + uint32_t fb_id = drmdev_add_fb_from_gbm_bo( + meta->drmdev, bo, /* cast_opaque */ false ); @@ -428,7 +442,7 @@ static int egl_gbm_render_surface_present_kms(struct surface *s, const struct fl if (fb_id == 0) { ok = EIO; LOG_ERROR("Couldn't add GBM buffer as DRM framebuffer.\n"); - goto fail_free_meta; + goto fail_unref_drmdev; } meta->has_nonopaque_fb_id = true; @@ -441,16 +455,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_resources_any_crtc_plane_supports_format(kms_req_builder_peek_resources(builder), crtc->id, 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 +477,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 +505,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 +569,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 +669,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 eacd80e9..6675c4de 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -45,7 +45,7 @@ #include "frame_scheduler.h" #include "keyboard.h" #include "locales.h" -#include "modesetting.h" +#include "kms/drmdev.h" #include "pixel_format.h" #include "platformchannel.h" #include "pluginregistry.h" @@ -262,6 +262,8 @@ struct flutterpi { bool session_active; char *desired_videomode; + + struct frame_scheduler *scheduler; }; struct device_id_and_fd { @@ -394,7 +396,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 +411,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; @@ -468,16 +472,13 @@ struct frame_req { }; static int on_deferred_begin_frame(void *userdata) { - FlutterEngineResult engine_result; - struct frame_req *req; - ASSERT_NOT_NULL(userdata); - req = userdata; + struct frame_req *req = userdata; assert(flutterpi_runs_platform_tasks_on_current_thread(req->flutterpi)); TRACER_INSTANT(req->flutterpi->tracer, "FlutterEngineOnVsync"); - engine_result = req->flutterpi->flutter.procs.OnVsync(req->flutterpi->flutter.engine, req->baton, req->vblank_ns, req->next_vblank_ns); + FlutterEngineResult engine_result = req->flutterpi->flutter.procs.OnVsync(req->flutterpi->flutter.engine, req->baton, req->vblank_ns, req->next_vblank_ns); free(req); @@ -489,88 +490,39 @@ static int on_deferred_begin_frame(void *userdata) { return 0; } -UNUSED static void on_begin_frame(void *userdata, uint64_t vblank_ns, uint64_t next_vblank_ns) { - FlutterEngineResult engine_result; - struct frame_req *req; - int ok; - +static void on_begin_frame(void *userdata, intptr_t baton, uint64_t vblank_ns, uint64_t next_vblank_ns) { ASSERT_NOT_NULL(userdata); - req = userdata; + struct flutterpi *flutterpi = userdata; - if (flutterpi_runs_platform_tasks_on_current_thread(req->flutterpi)) { - TRACER_INSTANT(req->flutterpi->tracer, "FlutterEngineOnVsync"); + if (flutterpi_runs_platform_tasks_on_current_thread(flutterpi)) { + TRACER_INSTANT(flutterpi->tracer, "FlutterEngineOnVsync"); - engine_result = req->flutterpi->flutter.procs.OnVsync(req->flutterpi->flutter.engine, req->baton, vblank_ns, next_vblank_ns); + FlutterEngineResult engine_result = flutterpi->flutter.procs.OnVsync(flutterpi->flutter.engine, baton, vblank_ns, next_vblank_ns); if (engine_result != kSuccess) { LOG_ERROR("Couldn't signal frame begin to flutter engine. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); - goto fail_free_req; } - - free(req); } else { + struct frame_req *req = malloc(sizeof(struct frame_req)); + req->flutterpi = flutterpi; + req->baton = baton; req->vblank_ns = vblank_ns; req->next_vblank_ns = next_vblank_ns; - ok = flutterpi_post_platform_task(on_deferred_begin_frame, req); + + int ok = flutterpi_post_platform_task(on_deferred_begin_frame, req); if (ok != 0) { LOG_ERROR("Couldn't defer signalling frame begin.\n"); - goto fail_free_req; + free(req); } } - - return; - -fail_free_req: - free(req); - return; } -/// Called on some flutter internal thread to request a frame, -/// and also get the vblank timestamp of the pageflip preceding that frame. -UNUSED static void on_frame_request(void *userdata, intptr_t baton) { - FlutterEngineResult engine_result; - struct flutterpi *flutterpi; - struct frame_req *req; - int ok; - +static void on_frame_timings_request(void *userdata, intptr_t baton) { ASSERT_NOT_NULL(userdata); - flutterpi = userdata; - - TRACER_INSTANT(flutterpi->tracer, "on_frame_request"); - - req = malloc(sizeof *req); - if (req == NULL) { - LOG_ERROR("Out of memory\n"); - return; - } - - req->flutterpi = flutterpi; - req->baton = baton; - req->vblank_ns = get_monotonic_time(); - req->next_vblank_ns = req->vblank_ns + (1000000000.0 / compositor_get_refresh_rate(flutterpi->compositor)); - - if (flutterpi_runs_platform_tasks_on_current_thread(req->flutterpi)) { - TRACER_INSTANT(req->flutterpi->tracer, "FlutterEngineOnVsync"); + struct flutterpi *flutterpi = userdata; - engine_result = - req->flutterpi->flutter.procs.OnVsync(req->flutterpi->flutter.engine, req->baton, req->vblank_ns, req->next_vblank_ns); - if (engine_result != kSuccess) { - LOG_ERROR("Couldn't signal frame begin to flutter engine. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); - goto fail_free_req; - } + TRACER_INSTANT(flutterpi->tracer, "on_frame_timings_request"); - free(req); - } else { - ok = flutterpi_post_platform_task(on_deferred_begin_frame, req); - if (ok != 0) { - LOG_ERROR("Couldn't defer signalling frame begin.\n"); - goto fail_free_req; - } - } - - return; - -fail_free_req: - free(req); + frame_scheduler_on_fl_vsync_request(flutterpi->scheduler, baton); } UNUSED static FlutterTransformation on_get_transformation(void *userdata) { @@ -768,7 +720,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; } @@ -1045,7 +997,7 @@ struct tracer *flutterpi_get_tracer(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) { @@ -1095,7 +1047,8 @@ static int on_drmdev_ready(sd_event_source *s, int fd, uint32_t revents, void *u ASSERT_NOT_NULL(userdata); drmdev = userdata; - return drmdev_on_event_fd_ready(drmdev); + drmdev_dispatch_modesetting(drmdev); + return 0; } static const FlutterLocale *on_compute_platform_resolved_locales(const FlutterLocale **locales, size_t n_locales) { @@ -1171,19 +1124,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) { @@ -1352,7 +1306,7 @@ static FlutterEngine create_flutter_engine( project_args.update_semantics_custom_action_callback = NULL; project_args.persistent_cache_path = paths->asset_bundle_path; project_args.is_persistent_cache_read_only = false; - project_args.vsync_callback = NULL; // on_frame_request, /* broken since 2.2, kinda * + project_args.vsync_callback = on_frame_timings_request; project_args.custom_dart_entrypoint = NULL; project_args.custom_task_runners = &custom_task_runners; project_args.shutdown_dart_vm_when_done = true; @@ -1456,9 +1410,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; @@ -1925,7 +1879,7 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin "%s", usage ); - return false; + goto fail; } break; @@ -1939,7 +1893,7 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin "%s", usage ); - return false; + goto fail; } result_out->rotation = rotation; @@ -1950,13 +1904,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; @@ -1979,7 +1933,7 @@ bool flutterpi_parse_cmdline_args(int argc, char **argv, struct flutterpi_cmdlin "%s", usage ); - return false; + goto fail; valid_format: break; @@ -1987,7 +1941,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; @@ -1997,15 +1955,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; @@ -2016,7 +1974,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]); @@ -2032,6 +1990,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) { @@ -2102,81 +2071,14 @@ static void on_drmdev_close(int fd, void *fd_metadata, void *userdata) { } } -static const struct drmdev_interface drmdev_interface = { .open = on_drmdev_open, .close = on_drmdev_close }; +static const struct drmdev_file_interface drmdev_interface = { .open = on_drmdev_open, .close = on_drmdev_close }; -static struct drmdev *find_drmdev(struct libseat *libseat) { - struct drm_connector *connector; - struct drmdev *drmdev; - drmDevicePtr devices[64]; - int ok, n_devices; - -#ifndef HAVE_LIBSEAT - ASSERT_EQUALS(libseat, NULL); -#endif - - ok = drmGetDevices2(0, devices, sizeof(devices) / sizeof(*devices)); - if (ok < 0) { - LOG_ERROR("Could not query DRM device list: %s\n", strerror(-ok)); - return NULL; - } - - n_devices = ok; - - // find a GPU that has a primary node - drmdev = NULL; - for (int i = 0; i < n_devices; i++) { - drmDevicePtr device; - - device = devices[i]; - - if (!(device->available_nodes & (1 << DRM_NODE_PRIMARY))) { - // We need a primary node. - continue; - } - - drmdev = drmdev_new_from_path(device->nodes[DRM_NODE_PRIMARY], &drmdev_interface, libseat); - if (drmdev == NULL) { - LOG_ERROR("Could not create drmdev from device at \"%s\". Continuing.\n", device->nodes[DRM_NODE_PRIMARY]); - continue; - } - - for_each_connector_in_drmdev(drmdev, connector) { - if (connector->variable_state.connection_state == kConnected_DrmConnectionState) { - goto found_connected_connector; - } - } - LOG_ERROR("Device \"%s\" doesn't have a display connected. Skipping.\n", device->nodes[DRM_NODE_PRIMARY]); - drmdev_unref(drmdev); - continue; - -found_connected_connector: - break; - } - - drmFreeDevices(devices, n_devices); - - if (drmdev == NULL) { - LOG_ERROR( - "flutter-pi couldn't find a usable DRM device.\n" - "Please make sure you've enabled the Fake-KMS driver in raspi-config.\n" - "If you're not using a Raspberry Pi, please make sure there's KMS support for your graphics chip.\n" - ); - goto fail_free_devices; - } - - return drmdev; - -fail_free_devices: - drmFreeDevices(devices, n_devices); - return NULL; -} - -static struct gbm_device *open_rendernode_as_gbm_device() { +static struct gbm_device *open_rendernode_as_gbm_device(void) { struct gbm_device *gbm; drmDevicePtr devices[64]; int ok, n_devices; - ok = drmGetDevices2(0, devices, sizeof(devices) / sizeof(*devices)); + ok = drmGetDevices2(0, devices, ARRAY_SIZE(devices)); if (ok < 0) { LOG_ERROR("Could not query DRM device list: %s\n", strerror(-ok)); return NULL; @@ -2305,7 +2207,6 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { 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; @@ -2320,14 +2221,13 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { struct flutterpi_cmdline_args cmd_args; struct libseat *libseat; struct locales *locales; - struct drmdev *drmdev; struct tracer *tracer; struct window *window; void *engine_handle; - char *bundle_path, **engine_argv, *desired_videomode; + char **engine_argv, *desired_videomode; int ok, engine_argc, wakeup_fd; - fpi = malloc(sizeof *fpi); + fpi = calloc(1, sizeof *fpi); if (fpi == NULL) { return NULL; } @@ -2344,15 +2244,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) @@ -2366,16 +2265,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)); @@ -2441,6 +2337,8 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { locales_print(locales); + struct drmdev *drmdev = NULL; + struct drm_resources *resources = NULL; if (cmd_args.dummy_display) { drmdev = NULL; @@ -2453,11 +2351,21 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { goto fail_destroy_locales; } } else { - drmdev = find_drmdev(libseat); + // TODO: Share this udev instance with the one the user input uses. + struct udev *udev = udev_new(); + + drmdev = drmdev_new_from_udev_primary(udev, "seat0", &drmdev_interface, NULL); if (drmdev == NULL) { goto fail_destroy_locales; } + resources = drmdev_query_resources(drmdev); + if (resources == NULL) { + goto fail_destroy_drmdev; + } + + udev_unref(udev); + gbm_device = drmdev_get_gbm_device(drmdev); if (gbm_device == NULL) { LOG_ERROR("Couldn't create GBM device.\n"); @@ -2471,8 +2379,8 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { goto fail_destroy_drmdev; } - scheduler = frame_scheduler_new(false, kDoubleBufferedVsync_PresentMode, NULL, NULL); - if (scheduler == NULL) { + fpi->scheduler = frame_scheduler_new(true, kDoubleBufferedVsync_PresentMode, on_begin_frame, fpi); + if (fpi->scheduler == NULL) { LOG_ERROR("Couldn't create frame scheduler.\n"); goto fail_unref_tracer; } @@ -2483,7 +2391,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 @@ -2495,7 +2402,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; } @@ -2523,7 +2429,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { if (cmd_args.dummy_display) { window = dummy_window_new( tracer, - scheduler, + fpi->scheduler, renderer_type, gl_renderer, vk_renderer, @@ -2537,7 +2443,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { window = kms_window_new( // clang-format off tracer, - scheduler, + fpi->scheduler, renderer_type, gl_renderer, vk_renderer, @@ -2551,6 +2457,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { 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, + resources, desired_videomode // clang-format on ); @@ -2560,6 +2467,8 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { } } + drm_resources_unrefp(&resources); + compositor = compositor_new(tracer, window); if (compositor == NULL) { LOG_ERROR("Couldn't create compositor.\n"); @@ -2568,7 +2477,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { /// 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); + ok = sd_event_add_io(event_loop, NULL, drmdev_get_modesetting_fd(drmdev), EPOLLIN | EPOLLHUP | EPOLLPRI, on_drmdev_ready, drmdev); if (ok < 0) { LOG_ERROR("Could not add DRM pageflip event listener. sd_event_add_io: %s\n", strerror(-ok)); goto fail_unref_compositor; @@ -2598,8 +2507,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"); @@ -2698,9 +2607,10 @@ 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; @@ -2712,7 +2622,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; @@ -2759,12 +2668,15 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { } fail_unref_scheduler: - frame_scheduler_unref(scheduler); + frame_scheduler_unref(fpi->scheduler); fail_unref_tracer: tracer_unref(tracer); fail_destroy_drmdev: + if (resources != NULL) { + drm_resources_unref(resources); + } drmdev_unref(drmdev); fail_destroy_locales: @@ -2790,6 +2702,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/frame_scheduler.c b/src/frame_scheduler.c index 0adf7b9a..d7426333 100644 --- a/src/frame_scheduler.c +++ b/src/frame_scheduler.c @@ -18,35 +18,56 @@ struct frame_scheduler { refcount_t n_refs; + pthread_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; + + intptr_t pending_frame_timings_request; }; DEFINE_REF_OPS(frame_scheduler, n_refs) DEFINE_STATIC_LOCK_OPS(frame_scheduler, mutex) -struct frame_scheduler * -frame_scheduler_new(bool uses_frame_requests, enum present_mode present_mode, fl_vsync_callback_t vsync_cb, void *userdata) { - struct frame_scheduler *scheduler; - +struct frame_scheduler *frame_scheduler_new( + bool uses_frame_requests, + enum present_mode present_mode, + fl_vsync_callback_t vsync_cb, + void *userdata +) { // uses_frame_requests? => vsync_cb != NULL assert(!uses_frame_requests || vsync_cb != NULL); - scheduler = malloc(sizeof *scheduler); + struct frame_scheduler *scheduler = calloc(1, sizeof *scheduler); if (scheduler == NULL) { return NULL; } scheduler->n_refs = REFCOUNT_INIT_1; + ASSERTED int ok = pthread_mutex_init(&scheduler->mutex, get_default_mutex_attrs()); + ASSERT_ZERO(ok); + 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; + + scheduler->pending_frame_timings_request = 0; + return scheduler; } @@ -82,12 +103,32 @@ void frame_scheduler_on_fl_vsync_request(struct frame_scheduler *scheduler, intp // as well if we draw too many frames at once. (Especially considering one framebuffer is probably busy with scanout right now) // - /// TODO: Implement - /// For now, just unconditionally reply + bool begin_now = false; + if (scheduler->present_mode == kTripleBufferedVsync_PresentMode) { - scheduler->vsync_cb(scheduler->userdata, vsync_baton, 0, 0); + /// TODO: Query actual frame interval and vblank timestamp here + begin_now = true; } else if (scheduler->present_mode == kDoubleBufferedVsync_PresentMode) { - scheduler->vsync_cb(scheduler->userdata, vsync_baton, 0, 0); + frame_scheduler_lock(scheduler); + + if (scheduler->waiting_for_scanout) { + // Reply to the frame timings request once the current frame has been scanned out. + ASSERT_ZERO(scheduler->pending_frame_timings_request); + scheduler->pending_frame_timings_request = vsync_baton; + } else { + /// TODO: Query actual frame interval and vblank timestamp here + begin_now = true; + } + + frame_scheduler_unlock(scheduler); + } else { + UNREACHABLE(); + } + + if (begin_now) { + uint64_t now = get_monotonic_time(); + uint64_t now_plus_frame_interval = now + 1000000000/60; + scheduler->vsync_cb(scheduler->userdata, vsync_baton, now, now_plus_frame_interval); } } @@ -118,14 +159,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 +206,46 @@ 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; + intptr_t pending_frame_timings_request = 0; + + 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; + scheduler->waiting_for_scanout = true; + } else if (scheduler->pending_frame_timings_request) { + // only reply to the frame timings request if we don't have another frame already + // pending. We only want to start a new frame once the previous frame has been scanned out + /// TODO: Maybe do start it before the previous one has been scanned out? + pending_frame_timings_request = scheduler->pending_frame_timings_request; + scheduler->pending_frame_timings_request = 0; + } + } + + frame_scheduler_unlock(scheduler); + + if (pending_frame_timings_request) { + if (!has_timestamp) { + timestamp_ns = get_monotonic_time(); + } + + /// TODO: Use actual frame interval here + uint64_t now_plus_frame_interval = timestamp_ns + 1000000000 / 60; + + scheduler->vsync_cb(scheduler->userdata, pending_frame_timings_request, timestamp_ns, now_plus_frame_interval); + } + + if (present_cb != NULL) { + present_cb(userdata); + } } diff --git a/src/frame_scheduler.h b/src/frame_scheduler.h index 229d9e59..4f66d8d6 100644 --- a/src/frame_scheduler.h +++ b/src/frame_scheduler.h @@ -80,4 +80,6 @@ void frame_scheduler_on_fb_released(struct frame_scheduler *scheduler, bool has_ */ void frame_scheduler_present_frame(struct frame_scheduler *scheduler, void_callback_t present_cb, void *userdata, void_callback_t cancel_cb); +void frame_scheduler_on_scanout(struct frame_scheduler *scheduler, bool has_timestamp, uint64_t timestamp_ns); + #endif // _FLUTTERPI_SRC_FRAME_SCHEDULER_H diff --git a/src/gl_renderer.c b/src/gl_renderer.c index 58b25ea2..a151310e 100644 --- a/src/gl_renderer.c +++ b/src/gl_renderer.c @@ -62,24 +62,23 @@ struct gl_renderer { #endif }; -static void *try_get_proc_address(const char *name) { - void *address; - - address = eglGetProcAddress(name); - if (address) { - return address; +static fn_ptr_t try_get_proc_address(const char *name) { + fn_ptr_t fn = eglGetProcAddress(name); + if (fn) { + return fn; } - address = dlsym(RTLD_DEFAULT, name); - if (address) { - return address; + void *void_fn = dlsym(RTLD_DEFAULT, name); + if (void_fn) { + *((void **) &fn) = void_fn; + return fn; } - return NULL; + return (fn_ptr_t) NULL; } -static void *get_proc_address(const char *name) { - void *address; +static fn_ptr_t get_proc_address(const char *name) { + fn_ptr_t address; address = try_get_proc_address(name); if (address == NULL) { @@ -177,13 +176,13 @@ struct gl_renderer *gl_renderer_new_from_gbm_device( // PFNEGLGETPLATFORMDISPLAYEXTPROC, PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC // are defined by EGL_EXT_platform_base. #ifdef EGL_EXT_platform_base - PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_ext; - PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC egl_create_platform_window_surface_ext; + PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_ext = NULL; + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC egl_create_platform_window_surface_ext = NULL; #endif if (supports_egl_ext_platform_base) { #ifdef EGL_EXT_platform_base - egl_get_platform_display_ext = try_get_proc_address("eglGetPlatformDisplayEXT"); + egl_get_platform_display_ext = (PFNEGLGETPLATFORMDISPLAYEXTPROC) try_get_proc_address("eglGetPlatformDisplayEXT"); if (egl_get_platform_display_ext == NULL) { LOG_ERROR("Couldn't resolve \"eglGetPlatformDisplayEXT\" even though \"EGL_EXT_platform_base\" was listed as supported.\n"); supports_egl_ext_platform_base = false; @@ -195,7 +194,8 @@ struct gl_renderer *gl_renderer_new_from_gbm_device( if (supports_egl_ext_platform_base) { #ifdef EGL_EXT_platform_base - egl_create_platform_window_surface_ext = try_get_proc_address("eglCreatePlatformWindowSurfaceEXT"); + egl_create_platform_window_surface_ext = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC + ) try_get_proc_address("eglCreatePlatformWindowSurfaceEXT"); if (egl_create_platform_window_surface_ext == NULL) { LOG_ERROR( "Couldn't resolve \"eglCreatePlatformWindowSurfaceEXT\" even though \"EGL_EXT_platform_base\" was listed as supported.\n" @@ -217,8 +217,9 @@ struct gl_renderer *gl_renderer_new_from_gbm_device( bool failed_before = false; #ifdef EGL_VERSION_1_5 - PFNEGLGETPLATFORMDISPLAYPROC egl_get_platform_display = try_get_proc_address("eglGetPlatformDisplay"); - PFNEGLCREATEPLATFORMWINDOWSURFACEPROC egl_create_platform_window_surface = try_get_proc_address("eglCreatePlatformWindowSurface"); + PFNEGLGETPLATFORMDISPLAYPROC egl_get_platform_display = (PFNEGLGETPLATFORMDISPLAYPROC) try_get_proc_address("eglGetPlatformDisplay"); + PFNEGLCREATEPLATFORMWINDOWSURFACEPROC egl_create_platform_window_surface = (PFNEGLCREATEPLATFORMWINDOWSURFACEPROC + ) try_get_proc_address("eglCreatePlatformWindowSurface"); if (egl_display == EGL_NO_DISPLAY && egl_get_platform_display != NULL) { egl_display = egl_get_platform_display(EGL_PLATFORM_GBM_KHR, gbm_device, NULL); @@ -550,7 +551,7 @@ int gl_renderer_clear_current(struct gl_renderer *renderer) { return 0; } -void *gl_renderer_get_proc_address(ASSERTED struct gl_renderer *renderer, const char *name) { +fn_ptr_t gl_renderer_get_proc_address(ASSERTED struct gl_renderer *renderer, const char *name) { ASSERT_NOT_NULL(renderer); ASSERT_NOT_NULL(name); return get_proc_address(name); @@ -628,7 +629,7 @@ int gl_renderer_make_this_a_render_thread(struct gl_renderer *renderer) { return 0; } -void gl_renderer_cleanup_this_render_thread() { +void gl_renderer_cleanup_this_render_thread(void) { EGLDisplay display; EGLContext context; EGLBoolean egl_ok; diff --git a/src/gl_renderer.h b/src/gl_renderer.h index d6c8160a..6afcd577 100644 --- a/src/gl_renderer.h +++ b/src/gl_renderer.h @@ -26,6 +26,8 @@ #include "egl.h" +typedef void (*fn_ptr_t)(void); + struct tracer; struct gl_renderer *gl_renderer_new_from_gbm_device( @@ -59,7 +61,7 @@ int gl_renderer_clear_current(struct gl_renderer *renderer); EGLContext gl_renderer_create_context(struct gl_renderer *renderer); -void *gl_renderer_get_proc_address(struct gl_renderer *renderer, const char *name); +fn_ptr_t gl_renderer_get_proc_address(struct gl_renderer *renderer, const char *name); EGLDisplay gl_renderer_get_egl_display(struct gl_renderer *renderer); @@ -71,7 +73,7 @@ bool gl_renderer_is_llvmpipe(struct gl_renderer *renderer); int gl_renderer_make_this_a_render_thread(struct gl_renderer *renderer); -void gl_renderer_cleanup_this_render_thread(); +void gl_renderer_cleanup_this_render_thread(void); ATTR_PURE EGLConfig gl_renderer_choose_config(struct gl_renderer *renderer, bool has_desired_pixel_format, enum pixfmt desired_pixel_format); diff --git a/src/kms/drmdev.c b/src/kms/drmdev.c new file mode 100644 index 00000000..d92f33fe --- /dev/null +++ b/src/kms/drmdev.c @@ -0,0 +1,947 @@ +#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 drmdev_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 drmdev_file_interface *interface, void *userdata) { + void *fd_metadata; + + int fd = interface->open(path, O_RDWR, &fd_metadata, userdata); + if (fd < 0) { + return false; + } + + // Ideally we'd use drmIsKMS() here, but it's not available everywhere. + + struct drm_mode_card_res res = { 0 }; + if (drmIoctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res) != 0) { + interface->close(fd, fd_metadata, userdata); + return false; + } + + interface->close(fd, fd_metadata, userdata); + return res.count_crtcs > 0 && res.count_connectors > 0 && res.count_encoders > 0; +} + +static void assert_rotations_work() { + 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 drmdev_file_interface *interface, void *interface_userdata) { + struct udev_enumerate *enumerator; + + enumerator = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(enumerator, "drm"); + udev_enumerate_add_match_sysname(enumerator, "card[0-9]*"); + + udev_enumerate_scan_devices(enumerator); + + struct udev_list_entry *entry; + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerator)) { + const char *syspath = udev_list_entry_get_name(entry); + + struct udev_device *udev_device = udev_device_new_from_syspath(udev, syspath); + if (udev_device == NULL) { + LOG_ERROR("Could not create udev device from syspath. udev_device_new_from_syspath: %s\n", strerror(errno)); + continue; + } + + // Find out if the drm card is connected to our seat. + // This could also be part of the enumerator filter, e.g.: + // + // udev_enumerate_add_match_property(enumerator, "ID_SEAT", seat), + // + // if we didn't have to handle a NULL value for ID_SEAT. + const char *device_seat = udev_device_get_property_value(udev_device, "ID_SEAT"); + if (device_seat == NULL) { + device_seat = "seat0"; + } + if (!streq(device_seat, seat)) { + udev_device_unref(udev_device); + continue; + } + + // devnode is the path to the /dev/dri/cardX device. + const char *devnode = udev_device_get_devnode(udev_device); + if (devnode == NULL) { + // likely a connector, not a card. + udev_device_unref(udev_device); + continue; + } + + if (access(devnode, R_OK | W_OK) != 0) { + LOG_ERROR("Insufficient permissions to open KMS device \"%s\" for display output. access: %s\n", devnode, strerror(errno)); + udev_device_unref(udev_device); + continue; + } + + if (!is_kms_device(devnode, interface, interface_userdata)) { + udev_device_unref(udev_device); + continue; + } + + udev_enumerate_unref(enumerator); + return udev_device; + } + + udev_enumerate_unref(enumerator); + return NULL; +} + +static void drmdev_on_page_flip( + struct drmdev *drmdev, + uint32_t crtc_id, + uint64_t vblank_ns +) { + ASSERT_NOT_NULL(drmdev); + struct pageflip_callbacks cbs_copy; + + { + ASSERTED int ok; + ok = pthread_mutex_lock(&drmdev->mutex); + ASSERT_ZERO(ok); + + 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. + ok = pthread_mutex_unlock(&drmdev->mutex); + ASSERT_ZERO(ok); + 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; + + ok = pthread_mutex_unlock(&drmdev->mutex); + ASSERT_ZERO(ok); + } + + if (cbs_copy.callbacks[cbs_copy.index].void_callback != NULL) { + cbs_copy.callbacks[cbs_copy.index].void_callback(cbs_copy.callbacks[cbs_copy.index].void_callback_userdata); + } + + if (cbs_copy.callbacks[cbs_copy.index].scanout_callback != NULL) { + cbs_copy.callbacks[cbs_copy.index].scanout_callback(vblank_ns, cbs_copy.callbacks[cbs_copy.index].scanout_callback_userdata); + } +} + +static void on_page_flip( + int fd, + unsigned int sequence, + unsigned int tv_sec, unsigned int tv_usec, + unsigned int crtc_id, + void *userdata +) { + struct drmdev *drmdev; + + ASSERT_NOT_NULL(userdata); + drmdev = userdata; + + (void) fd; + (void) sequence; + + uint64_t vblank_ns = tv_sec * 1000000000ull + tv_usec * 1000ull; + drmdev_on_page_flip(drmdev, crtc_id, vblank_ns); + + drmdev_unref(drmdev); +} + +/** + * @brief Should be called when the drmdev modesetting fd is ready. + */ +void drmdev_dispatch_modesetting(struct drmdev *drmdev) { + int ok; + + static drmEventContext ctx = { + .version = DRM_EVENT_CONTEXT_VERSION, + .vblank_handler = NULL, + .page_flip_handler = NULL, + .page_flip_handler2 = on_page_flip, + .sequence_handler = NULL, + }; + + ok = drmHandleEvent(drmdev->fd, &ctx); + if (ok != 0) { + LOG_ERROR("Could not handle DRM event. drmHandleEvent: %s\n", strerror(errno)); + } +} + +/** + * @brief Create a new drmdev from the primary drm device for the given udev & seat. + */ +struct drmdev *drmdev_new_from_udev_primary(struct udev *udev, const char *seat, const struct drmdev_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; + pthread_mutex_init(&d->mutex, get_default_mutex_attrs()); + 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) { + ok = pthread_mutex_lock(&drmdev->mutex); + ASSERT_ZERO(ok); + + khint_t cbs_it = kh_put(pageflip_callbacks, drmdev->pageflip_callbacks, crtc_id, &bucket_status); + if (bucket_status == -1) { + ok = pthread_mutex_unlock(&drmdev->mutex); + ASSERT_ZERO(ok); + return ENOMEM; + } + + ok = drmModeAtomicCommit(drmdev->fd, req, flags, pageflip_event ? drmdev_ref(drmdev) : NULL); + if (ok != 0) { + ok = -errno; + + ASSERTED int mutex_ok = pthread_mutex_unlock(&drmdev->mutex); + ASSERT_ZERO(mutex_ok); + + 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; + + ok = pthread_mutex_unlock(&drmdev->mutex); + ASSERT_ZERO(ok); + } 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..29453034 --- /dev/null +++ b/src/kms/drmdev.h @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT +/* + * KMS Modesetting + * + * - implements the interface to linux kernel modesetting + * - allows querying connected screens, crtcs, planes, etc + * - allows setting video modes, showing things on screen + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_MODESETTING_H +#define _FLUTTERPI_SRC_MODESETTING_H + +#include + +#include + +#include +#include + +#include "pixel_format.h" +#include "util/collection.h" +#include "util/geometry.h" +#include "util/refcounting.h" + +/** + * @brief Interface that will be used to open and close files. + */ +struct drmdev_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_udev_primary(struct udev *udev, const char *seat, const struct drmdev_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(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/req_builder.c b/src/kms/req_builder.c new file mode 100644 index 00000000..5a399f30 --- /dev/null +++ b/src/kms/req_builder.c @@ -0,0 +1,961 @@ +#include "req_builder.h" + +#include +#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 & (1 << 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 &= ~(1 << 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 & (1 << index))); + builder->available_planes |= (1 << 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); + builder->req = NULL; + } + drm_resources_unref(builder->res); + 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) { + 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, plane->max_alpha); + } + + if (plane->has_blend_mode && plane->supported_blend_modes[DRM_BLEND_MODE_NONE]) { + drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.pixel_blend_mode, DRM_BLEND_MODE_NONE); + } + } + } + + // This should be done when we're sure we're not failing. + // Because on failure it would be the callers job to close the fd. + if (close_in_fence_fd_after) { + ok = close(layer->in_fence_fd); + if (ok < 0) { + ok = errno; + LOG_ERROR("Could not close layer in_fence_fd. close: %s\n", strerror(ok)); + goto fail_release_plane; + } + } + + /// TODO: Right now we're adding zpos, rotation to the atomic request unconditionally + /// when specified in the fb layer. Ideally we would check for updates + /// on commit and only add to the atomic request when zpos / rotation changed. + builder->n_layers++; + if (has_zpos) { + builder->next_zpos = zpos + 1; + } + builder->layers[index].layer = *layer; + builder->layers[index].plane_id = plane->id; + builder->layers[index].plane = plane; + builder->layers[index].set_zpos = has_zpos; + builder->layers[index].zpos = zpos; + builder->layers[index].set_rotation = layer->has_rotation; + builder->layers[index].rotation = layer->rotation; + builder->layers[index].release_callback = release_callback; + builder->layers[index].deferred_release_callback = deferred_release_callback; + builder->layers[index].release_callback_userdata = userdata; + return 0; + +fail_release_plane: + release_plane(builder, plane->id); + return ok; +} + +int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder, int64_t *zpos_out) { + ASSERT_NOT_NULL(builder); + ASSERT_NOT_NULL(zpos_out); + *zpos_out = builder->next_zpos++; + return 0; +} + +struct kms_req *kms_req_builder_build(struct kms_req_builder *builder) { + return (struct kms_req *) kms_req_builder_ref(builder); +} + +UNUSED struct kms_req *kms_req_ref(struct kms_req *req) { + return (struct kms_req *) kms_req_builder_ref((struct kms_req_builder *) req); +} + +UNUSED void kms_req_unref(struct kms_req *req) { + kms_req_builder_unref((struct kms_req_builder *) req); +} + +UNUSED void kms_req_unrefp(struct kms_req **req) { + kms_req_builder_unrefp((struct kms_req_builder **) req); +} + +UNUSED void kms_req_swap_ptrs(struct kms_req **oldp, struct kms_req *new) { + kms_req_builder_swap_ptrs((struct kms_req_builder **) oldp, (struct kms_req_builder *) new); +} + +UNUSED static bool drm_plane_is_active(struct drm_plane *plane) { + return plane->committed_state.fb_id != 0 && plane->committed_state.crtc_id != 0; +} + +static void on_kms_req_scanout(uint64_t vblank_ns, void *userdata) { + struct kms_req_builder *b; + + ASSERT_NOT_NULL(userdata); + b = (struct kms_req_builder *) userdata; + + if (b->scanout_cb != NULL) { + b->scanout_cb(vblank_ns, b->scanout_cb_userdata); + } +} + +static void on_kms_req_release(void *userdata) { + struct kms_req_builder *b; + + ASSERT_NOT_NULL(userdata); + b = (struct kms_req_builder *) userdata; + + if (b->release_cb != NULL) { + b->release_cb(b->release_cb_userdata); + } + + ASSERT(refcount_is_one(&b->n_refs)); + kms_req_builder_unref(b); +} + + +static int kms_req_commit_common( + struct kms_req *req, + struct drmdev *drmdev, + bool blocking, + kmsreq_scanout_cb_t scanout_cb, + void *scanout_cb_userdata, + uint64_t *vblank_ns_out, + void_callback_t release_cb, + void *release_cb_userdata +) { + struct kms_req_builder *builder; + 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 & (1 << 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..f9548241 --- /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 release_cb); + +#endif // _FLUTTERPI_SRC_MODESETTING_REQ_BUILDER_H_ diff --git a/src/kms/resources.c b/src/kms/resources.c new file mode 100644 index 00000000..fdd908c6 --- /dev/null +++ b/src/kms/resources.c @@ -0,0 +1,1495 @@ +#include "resources.h" + +#include +#include +#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 _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_encoder_init(int drm_fd, uint32_t encoder_id, struct drm_encoder *out) { + drmModeEncoder *encoder = drmModeGetEncoder(drm_fd, encoder_id); + if (encoder == NULL) { + int ok = errno; + if (ok == 0) ok = ENOMEM; + return ok; + } + + 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) { + int ok = errno; + if (ok == 0) ok = ENOMEM; + return ok; + } + + 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) { + int ok = errno; + if (ok == 0) ok = ENOMEM; + return ok; + } + + for (int i = 0; i < props->count_props; i++) { + drmModePropertyRes *prop_info = drmModeGetProperty(drm_fd, props->props[i]); + if (prop_info == NULL) { + drmModeFreeObjectProperties(props); + int ok = errno; + if (ok == 0) ok = ENOMEM; + return ok; + } + + #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) { + int ok = errno; + if (ok == 0) ok = ENOMEM; + return ok; + } + + 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] <= 0xFFFF); + assert(info->values[1] <= 0xFFFF); + assert(info->values[0] <= info->values[1]); + out->min_alpha = (uint16_t) info->values[0]; + out->max_alpha = (uint16_t) info->values[1]; + + assert(out->min_alpha <= value); + assert(value <= out->max_alpha); + 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) { + ok = errno; + if (ok == 0) ok = ENOMEM; + return ok; + } + + 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) { + ok = errno; + if (ok == 0) ok = ENOMEM; + return ok; + } + + 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; + if (ok == 0) ok = 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; + if (ok == 0) ok = 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; + if (ok == 0) ok = 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; + if (ok == 0) ok = 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) + + +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 401e1150..02c26689 100644 --- a/src/modesetting.h +++ b/src/kms/resources.h @@ -1,29 +1,33 @@ -// 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 "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) @@ -216,12 +220,12 @@ #define DECLARE_PROP_ID_AS_UINT32(prop_name, prop_var_name) uint32_t prop_var_name; 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 { @@ -306,55 +310,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 { @@ -382,8 +383,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 { @@ -396,7 +418,7 @@ struct drm_crtc { struct { bool has_mode; drmModeModeInfo mode; - struct drm_mode_blob *mode_blob; + struct drm_blob *mode_blob; } committed_state; }; @@ -513,7 +535,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. @@ -601,7 +623,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. @@ -610,337 +672,127 @@ 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); -}; - -struct drmdev *drmdev_new_from_interface_fd(int fd, void *fd_metadata, const struct drmdev_interface *interface, void *userdata); +/** + * @brief Create a new drm_resources object + */ +struct drm_resources *drm_resources_new(int drm_fd); -struct drmdev *drmdev_new_from_path(const char *path, 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); -DECLARE_REF_OPS(drmdev) +struct drm_resources *drm_resources_dup_filtered(struct drm_resources *res, uint32_t connector_id, uint32_t encoder_id, uint32_t crtc_id, size_t n_planes, const uint32_t *plane_ids); -struct drmdev; -struct _drmModeModeInfo; +void drm_resources_destroy(struct drm_resources *r); -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 -); +DECLARE_REF_OPS(drm_resources) -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); +/** + * @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 diff --git a/src/locales.c b/src/locales.c index ff66a098..7f13b1a6 100644 --- a/src/locales.c +++ b/src/locales.c @@ -220,7 +220,7 @@ static int add_locale_variants(struct list_head *locales, const char *locale_des } // then append all possible combinations - for (int i = 0b111; i >= 0; i--) { + for (int i = 7; i >= 0; i--) { char *territory_2 = NULL, *codeset_2 = NULL, *modifier_2 = NULL; if ((i & 1) != 0) { @@ -311,7 +311,7 @@ struct locales *locales_new(void) { // Use those to create our flutter locales. n_locales = list_length(&locales->locales); - fl_locales = calloc(n_locales, sizeof *fl_locales); + fl_locales = calloc(n_locales == 0 ? 1 : n_locales, sizeof(const FlutterLocale *)); if (fl_locales == NULL) { goto fail_free_allocated_locales; } @@ -322,6 +322,18 @@ struct locales *locales_new(void) { i++; } + // If we have no locales, add a default "C" locale. + if (i == 0) { + fl_locales[0] = &(const FlutterLocale){ + .struct_size = sizeof(FlutterLocale), + .language_code = "C", + .country_code = NULL, + .script_code = NULL, + .variant_code = NULL, + }; + i++; + } + if (streq(fl_locales[0]->language_code, "C")) { LOG_LOCALES_ERROR("Warning: The system has no configured locale. The default \"C\" locale may or may not be supported by the app.\n" ); diff --git a/src/modesetting.c b/src/modesetting.c deleted file mode 100644 index 892c2973..00000000 --- a/src/modesetting.c +++ /dev/null @@ -1,2989 +0,0 @@ -#include "modesetting.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include "pixel_format.h" -#include "util/bitset.h" -#include "util/list.h" -#include "util/lock_ops.h" -#include "util/logging.h" -#include "util/macros.h" -#include "util/refcounting.h" - -struct drm_fb { - struct list_head entry; - - uint32_t id; - - uint32_t width, height; - - enum pixfmt format; - - bool has_modifier; - uint64_t modifier; - - uint32_t flags; - - uint32_t handles[4]; - uint32_t pitches[4]; - uint32_t offsets[4]; -}; - -struct kms_req_layer { - struct kms_fb_layer layer; - - uint32_t plane_id; - struct drm_plane *plane; - - bool set_zpos; - int64_t zpos; - - bool set_rotation; - drm_plane_transform_t rotation; - - kms_fb_release_cb_t release_callback; - kms_deferred_fb_release_cb_t deferred_release_callback; - void *release_callback_userdata; -}; - -struct kms_req_builder { - refcount_t n_refs; - - struct drmdev *drmdev; - bool use_legacy; - bool supports_atomic; - - struct drm_connector *connector; - struct drm_crtc *crtc; - - BITSET_DECLARE(available_planes, 32); - drmModeAtomicReq *req; - int64_t next_zpos; - - int n_layers; - struct kms_req_layer layers[32]; - - bool unset_mode; - bool has_mode; - drmModeModeInfo mode; -}; - -COMPILE_ASSERT(BITSET_SIZE(((struct kms_req_builder *) 0)->available_planes) == 32); - -struct drmdev { - int fd; - - refcount_t n_refs; - pthread_mutex_t mutex; - bool supports_atomic_modesetting; - bool supports_dumb_buffers; - - size_t n_connectors; - struct drm_connector *connectors; - - size_t n_encoders; - struct drm_encoder *encoders; - - size_t n_crtcs; - struct drm_crtc *crtcs; - - size_t n_planes; - struct drm_plane *planes; - - drmModeRes *res; - drmModePlaneRes *plane_res; - - struct gbm_device *gbm_device; - - int event_fd; - - struct { - kms_scanout_cb_t scanout_callback; - void *userdata; - void_callback_t destroy_callback; - - struct kms_req *last_flipped; - } per_crtc_state[32]; - - int master_fd; - void *master_fd_metadata; - - struct drmdev_interface interface; - void *userdata; - - struct list_head fbs; -}; - -static bool is_drm_master(int fd) { - return drmAuthMagic(fd, 0) != -EACCES; -} - -static struct drm_mode_blob *drm_mode_blob_new(int drm_fd, const drmModeModeInfo *mode) { - struct drm_mode_blob *blob; - uint32_t blob_id; - int ok; - - blob = malloc(sizeof *blob); - if (blob == NULL) { - return NULL; - } - - ok = drmModeCreatePropertyBlob(drm_fd, mode, sizeof *mode, &blob_id); - if (ok != 0) { - ok = errno; - LOG_ERROR("Couldn't upload mode to kernel. drmModeCreatePropertyBlob: %s\n", strerror(ok)); - free(blob); - return NULL; - } - - blob->drm_fd = dup(drm_fd); - blob->blob_id = blob_id; - blob->mode = *mode; - return blob; -} - -void drm_mode_blob_destroy(struct drm_mode_blob *blob) { - int ok; - - ASSERT_NOT_NULL(blob); - - ok = drmModeDestroyPropertyBlob(blob->drm_fd, blob->blob_id); - if (ok != 0) { - ok = errno; - LOG_ERROR("Couldn't destroy mode property blob. drmModeDestroyPropertyBlob: %s\n", strerror(ok)); - } - // we dup()-ed it in drm_mode_blob_new. - close(blob->drm_fd); - free(blob); -} - -DEFINE_STATIC_LOCK_OPS(drmdev, mutex) - -static int fetch_connector(int drm_fd, uint32_t connector_id, struct drm_connector *connector_out) { - struct drm_connector_prop_ids ids; - drmModeObjectProperties *props; - drmModePropertyRes *prop_info; - drmModeConnector *connector; - drmModeModeInfo *modes; - uint32_t crtc_id; - int ok; - - drm_connector_prop_ids_init(&ids); - - connector = drmModeGetConnector(drm_fd, connector_id); - if (connector == NULL) { - ok = errno; - LOG_ERROR("Could not get DRM device connector. drmModeGetConnector"); - return ok; - } - - props = drmModeObjectGetProperties(drm_fd, connector_id, DRM_MODE_OBJECT_CONNECTOR); - if (props == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device connectors properties. drmModeObjectGetProperties"); - goto fail_free_connector; - } - - crtc_id = DRM_ID_NONE; - for (int i = 0; i < props->count_props; i++) { - prop_info = drmModeGetProperty(drm_fd, props->props[i]); - if (prop_info == NULL) { - ok = errno; - LOG_ERROR("Could not get DRM device connector properties' info. drmModeGetProperty: %s\n", strerror(ok)); - goto fail_free_props; - } - -#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ - if (strncmp(prop_info->name, _name_str, DRM_PROP_NAME_LEN) == 0) { \ - ids._name = prop_info->prop_id; \ - } else - - DRM_CONNECTOR_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { - // this is the trailing else case - LOG_DEBUG("Unknown DRM connector property: %s\n", prop_info->name); - } - -#undef CHECK_ASSIGN_PROPERTY_ID - - if (strncmp(prop_info->name, "CRTC_ID", DRM_PROP_NAME_LEN) == 0) { - crtc_id = props->prop_values[i]; - } - - drmModeFreeProperty(prop_info); - prop_info = NULL; - } - - assert((connector->modes == NULL) == (connector->count_modes == 0)); - - if (connector->modes != NULL) { - modes = memdup(connector->modes, connector->count_modes * sizeof(*connector->modes)); - if (modes == NULL) { - ok = ENOMEM; - goto fail_free_props; - } - } else { - modes = NULL; - } - - connector_out->id = connector->connector_id; - connector_out->type = connector->connector_type; - connector_out->type_id = connector->connector_type_id; - connector_out->ids = ids; - connector_out->n_encoders = connector->count_encoders; - assert(connector->count_encoders <= 32); - memcpy(connector_out->encoders, connector->encoders, connector->count_encoders * sizeof(uint32_t)); - connector_out->variable_state.connection_state = (enum drm_connection_state) connector->connection; - connector_out->variable_state.subpixel_layout = (enum drm_subpixel_layout) connector->subpixel; - connector_out->variable_state.width_mm = connector->mmWidth; - connector_out->variable_state.height_mm = connector->mmHeight; - connector_out->variable_state.n_modes = connector->count_modes; - connector_out->variable_state.modes = modes; - connector_out->committed_state.crtc_id = crtc_id; - connector_out->committed_state.encoder_id = connector->encoder_id; - drmModeFreeObjectProperties(props); - drmModeFreeConnector(connector); - return 0; - -fail_free_props: - drmModeFreeObjectProperties(props); - -fail_free_connector: - drmModeFreeConnector(connector); - return ok; -} - -static void free_connector(struct drm_connector *connector) { - free(connector->variable_state.modes); -} - -static int fetch_connectors(struct drmdev *drmdev, struct drm_connector **connectors_out, size_t *n_connectors_out) { - struct drm_connector *connectors; - int ok; - - connectors = calloc(drmdev->res->count_connectors, sizeof *connectors); - if (connectors == NULL) { - *connectors_out = NULL; - return ENOMEM; - } - - for (int i = 0; i < drmdev->res->count_connectors; i++) { - ok = fetch_connector(drmdev->fd, drmdev->res->connectors[i], connectors + i); - if (ok != 0) { - for (int j = 0; j < i; j++) - free_connector(connectors + j); - goto fail_free_connectors; - } - } - - *connectors_out = connectors; - *n_connectors_out = drmdev->res->count_connectors; - return 0; - -fail_free_connectors: - free(connectors); - *connectors_out = NULL; - *n_connectors_out = 0; - return ok; -} - -static int free_connectors(struct drm_connector *connectors, size_t n_connectors) { - for (int i = 0; i < n_connectors; i++) { - free_connector(connectors + i); - } - free(connectors); - return 0; -} - -static int fetch_encoder(int drm_fd, uint32_t encoder_id, struct drm_encoder *encoder_out) { - drmModeEncoder *encoder; - int ok; - - encoder = drmModeGetEncoder(drm_fd, encoder_id); - if (encoder == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device encoder. drmModeGetEncoder"); - return ok; - } - - encoder_out->encoder = encoder; - return 0; -} - -static void free_encoder(struct drm_encoder *encoder) { - drmModeFreeEncoder(encoder->encoder); -} - -static int fetch_encoders(struct drmdev *drmdev, struct drm_encoder **encoders_out, size_t *n_encoders_out) { - struct drm_encoder *encoders; - int n_allocated_encoders; - int ok; - - encoders = calloc(drmdev->res->count_encoders, sizeof *encoders); - if (encoders == NULL) { - *encoders_out = NULL; - *n_encoders_out = 0; - return ENOMEM; - } - - n_allocated_encoders = 0; - for (int i = 0; i < drmdev->res->count_encoders; i++, n_allocated_encoders++) { - ok = fetch_encoder(drmdev->fd, drmdev->res->encoders[i], encoders + i); - if (ok != 0) { - goto fail_free_encoders; - } - } - - *encoders_out = encoders; - *n_encoders_out = drmdev->res->count_encoders; - return 0; - -fail_free_encoders: - for (int i = 0; i < n_allocated_encoders; i++) { - drmModeFreeEncoder(encoders[i].encoder); - } - - free(encoders); - - *encoders_out = NULL; - *n_encoders_out = 0; - return ok; -} - -static void free_encoders(struct drm_encoder *encoders, size_t n_encoders) { - for (int i = 0; i < n_encoders; i++) { - free_encoder(encoders + i); - } - free(encoders); -} - -static int fetch_crtc(int drm_fd, int crtc_index, uint32_t crtc_id, struct drm_crtc *crtc_out) { - struct drm_crtc_prop_ids ids; - drmModeObjectProperties *props; - drmModePropertyRes *prop_info; - drmModeCrtc *crtc; - int ok; - - drm_crtc_prop_ids_init(&ids); - - crtc = drmModeGetCrtc(drm_fd, crtc_id); - if (crtc == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device CRTC. drmModeGetCrtc"); - return ok; - } - - props = drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC); - if (props == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device CRTCs properties. drmModeObjectGetProperties"); - goto fail_free_crtc; - } - - for (int i = 0; i < props->count_props; i++) { - prop_info = drmModeGetProperty(drm_fd, props->props[i]); - if (prop_info == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device CRTCs properties' info. drmModeGetProperty"); - goto fail_free_props; - } - -#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ - if (strncmp(prop_info->name, _name_str, ARRAY_SIZE(prop_info->name)) == 0) { \ - ids._name = prop_info->prop_id; \ - } else - - DRM_CRTC_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { - // this is the trailing else case - LOG_DEBUG("Unknown DRM crtc property: %s\n", prop_info->name); - } - -#undef CHECK_ASSIGN_PROPERTY_ID - - drmModeFreeProperty(prop_info); - prop_info = NULL; - } - - crtc_out->id = crtc->crtc_id; - crtc_out->index = crtc_index; - crtc_out->bitmask = 1u << crtc_index; - crtc_out->ids = ids; - crtc_out->committed_state.has_mode = crtc->mode_valid; - crtc_out->committed_state.mode = crtc->mode; - crtc_out->committed_state.mode_blob = NULL; - drmModeFreeObjectProperties(props); - drmModeFreeCrtc(crtc); - return 0; - -fail_free_props: - drmModeFreeObjectProperties(props); - -fail_free_crtc: - drmModeFreeCrtc(crtc); - return ok; -} - -static void free_crtc(struct drm_crtc *crtc) { - /// TODO: Implement - (void) crtc; -} - -static int fetch_crtcs(struct drmdev *drmdev, struct drm_crtc **crtcs_out, size_t *n_crtcs_out) { - struct drm_crtc *crtcs; - int ok; - - crtcs = calloc(drmdev->res->count_crtcs, sizeof *crtcs); - if (crtcs == NULL) { - *crtcs_out = NULL; - *n_crtcs_out = 0; - return ENOMEM; - } - - for (int i = 0; i < drmdev->res->count_crtcs; i++) { - ok = fetch_crtc(drmdev->fd, i, drmdev->res->crtcs[i], crtcs + i); - if (ok != 0) { - for (int j = 0; j < i; j++) - free_crtc(crtcs + i); - goto fail_free_crtcs; - } - } - - *crtcs_out = crtcs; - *n_crtcs_out = drmdev->res->count_crtcs; - return 0; - -fail_free_crtcs: - free(crtcs); - *crtcs_out = NULL; - *n_crtcs_out = 0; - return ok; -} - -static int free_crtcs(struct drm_crtc *crtcs, size_t n_crtcs) { - for (int i = 0; i < n_crtcs; i++) { - free_crtc(crtcs + i); - } - free(crtcs); - return 0; -} - -void drm_plane_for_each_modified_format(struct drm_plane *plane, drm_plane_modified_format_callback_t callback, void *userdata) { - struct drm_format_modifier_blob *blob; - struct drm_format_modifier *modifiers; - uint32_t *formats; - - ASSERT_NOT_NULL(plane); - ASSERT_NOT_NULL(callback); - ASSERT(plane->supports_modifiers); - ASSERT_EQUALS(plane->supported_modified_formats_blob->version, FORMAT_BLOB_CURRENT); - - blob = plane->supported_modified_formats_blob; - - modifiers = (void *) (((char *) blob) + blob->modifiers_offset); - formats = (void *) (((char *) blob) + blob->formats_offset); - - int index = 0; - for (int i = 0; i < blob->count_modifiers; i++) { - for (int j = modifiers[i].offset; (j < blob->count_formats) && (j < modifiers[i].offset + 64); j++) { - bool is_format_bit_set = (modifiers[i].formats & (1ull << (j % 64))) != 0; - if (!is_format_bit_set) { - continue; - } - - if (has_pixfmt_for_drm_format(formats[j])) { - enum pixfmt format = get_pixfmt_for_drm_format(formats[j]); - - bool should_continue = callback(plane, index, format, modifiers[i].modifier, userdata); - if (!should_continue) { - goto exit; - } - - index++; - } - } - } - -exit: - return; -} - -static bool -check_modified_format_supported(UNUSED struct drm_plane *plane, UNUSED int index, enum pixfmt format, uint64_t modifier, void *userdata) { - struct { - enum pixfmt format; - uint64_t modifier; - bool found; - } *context = userdata; - - if (format == context->format && modifier == context->modifier) { - context->found = true; - return false; - } else { - return true; - } -} - -bool drm_plane_supports_modified_format(struct drm_plane *plane, enum pixfmt format, uint64_t modifier) { - if (!plane->supported_modified_formats_blob) { - // return false if we want a modified format but the plane doesn't support modified formats - return false; - } - - struct { - enum pixfmt format; - uint64_t modifier; - bool found; - } context = { - .format = format, - .modifier = modifier, - .found = false, - }; - - // Check if the requested format & modifier is supported. - drm_plane_for_each_modified_format(plane, check_modified_format_supported, &context); - - return context.found; -} - -bool drm_plane_supports_unmodified_format(struct drm_plane *plane, enum pixfmt format) { - // we don't want a modified format, return false if the format is not in the list - // of supported (unmodified) formats - return plane->supported_formats[format]; -} - -bool drm_crtc_any_plane_supports_format(struct drmdev *drmdev, struct drm_crtc *crtc, enum pixfmt pixel_format) { - struct drm_plane *plane; - - for_each_plane_in_drmdev(drmdev, plane) { - if (!(plane->possible_crtcs & crtc->bitmask)) { - // Only query planes that are possible to connect to the CRTC we're using. - continue; - } - - if (plane->type != kPrimary_DrmPlaneType && plane->type != kOverlay_DrmPlaneType) { - // We explicitly only look for primary and overlay planes. - continue; - } - - if (drm_plane_supports_unmodified_format(plane, pixel_format)) { - return true; - } - } - - return false; -} - -struct _drmModeFB2; - -struct drm_mode_fb2 { - uint32_t fb_id; - uint32_t width, height; - uint32_t pixel_format; /* fourcc code from drm_fourcc.h */ - uint64_t modifier; /* applies to all buffers */ - uint32_t flags; - - /* per-plane GEM handle; may be duplicate entries for multiple planes */ - uint32_t handles[4]; - uint32_t pitches[4]; /* bytes */ - uint32_t offsets[4]; /* bytes */ -}; - -#ifdef HAVE_FUNC_ATTRIBUTE_WEAK -extern struct _drmModeFB2 *drmModeGetFB2(int fd, uint32_t bufferId) __attribute__((weak)); -extern void drmModeFreeFB2(struct _drmModeFB2 *ptr) __attribute__((weak)); - #define HAVE_WEAK_DRM_MODE_GET_FB2 -#endif - -static int fetch_plane(int drm_fd, uint32_t plane_id, struct drm_plane *plane_out) { - struct drm_plane_prop_ids ids; - drmModeObjectProperties *props; - drm_plane_transform_t hardcoded_rotation, supported_rotations, committed_rotation; - enum drm_blend_mode committed_blend_mode; - enum drm_plane_type type; - drmModePropertyRes *info; - drmModePlane *plane; - uint32_t comitted_crtc_x, comitted_crtc_y, comitted_crtc_w, comitted_crtc_h; - uint32_t comitted_src_x, comitted_src_y, comitted_src_w, comitted_src_h; - uint16_t committed_alpha; - int64_t min_zpos, max_zpos, hardcoded_zpos, committed_zpos; - bool supported_blend_modes[kCount_DrmBlendMode] = { 0 }; - bool supported_formats[PIXFMT_COUNT] = { 0 }; - bool has_type, has_rotation, has_zpos, has_hardcoded_zpos, has_hardcoded_rotation, has_alpha, has_blend_mode; - int ok; - - drm_plane_prop_ids_init(&ids); - - plane = drmModeGetPlane(drm_fd, plane_id); - if (plane == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device plane. drmModeGetPlane"); - return ok; - } - - props = drmModeObjectGetProperties(drm_fd, plane_id, DRM_MODE_OBJECT_PLANE); - if (props == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device planes' properties. drmModeObjectGetProperties"); - goto fail_free_plane; - } - - // zero-initialize plane_out. - memset(plane_out, 0, sizeof(*plane_out)); - - has_type = false; - has_rotation = false; - has_hardcoded_rotation = false; - has_zpos = false; - has_hardcoded_zpos = false; - has_alpha = false; - has_blend_mode = false; - comitted_crtc_x = comitted_crtc_y = comitted_crtc_w = comitted_crtc_h = 0; - comitted_src_x = comitted_src_y = comitted_src_w = comitted_src_h = 0; - for (int j = 0; j < props->count_props; j++) { - info = drmModeGetProperty(drm_fd, props->props[j]); - if (info == NULL) { - ok = errno; - perror("[modesetting] Could not get DRM device planes' properties' info. drmModeGetProperty"); - goto fail_maybe_free_supported_modified_formats_blob; - } - - if (streq(info->name, "type")) { - assert(has_type == false); - has_type = true; - - type = props->prop_values[j]; - } else if (streq(info->name, "rotation")) { - assert(has_rotation == false); - has_rotation = true; - - supported_rotations = PLANE_TRANSFORM_NONE; - assert(info->flags & DRM_MODE_PROP_BITMASK); - - for (int k = 0; k < info->count_enums; k++) { - supported_rotations.u32 |= 1 << info->enums[k].value; - } - - assert(PLANE_TRANSFORM_IS_VALID(supported_rotations)); - - if (info->flags & DRM_MODE_PROP_IMMUTABLE) { - has_hardcoded_rotation = true; - hardcoded_rotation.u64 = props->prop_values[j]; - } - - committed_rotation.u64 = props->prop_values[j]; - } else if (streq(info->name, "zpos")) { - assert(has_zpos == false); - has_zpos = true; - - if (info->flags & DRM_MODE_PROP_SIGNED_RANGE) { - min_zpos = *(int64_t *) (info->values + 0); - max_zpos = *(int64_t *) (info->values + 1); - committed_zpos = *(int64_t *) (props->prop_values + j); - assert(min_zpos <= max_zpos); - assert(min_zpos <= committed_zpos); - assert(committed_zpos <= max_zpos); - } else if (info->flags & DRM_MODE_PROP_RANGE) { - assert(info->values[0] < (uint64_t) INT64_MAX); - assert(info->values[1] < (uint64_t) INT64_MAX); - min_zpos = info->values[0]; - max_zpos = info->values[1]; - committed_zpos = props->prop_values[j]; - assert(min_zpos <= max_zpos); - } else { - ASSERT_MSG(info->flags && false, "Invalid property type for zpos property."); - } - - if (info->flags & DRM_MODE_PROP_IMMUTABLE) { - has_hardcoded_zpos = true; - assert(props->prop_values[j] < (uint64_t) INT64_MAX); - hardcoded_zpos = committed_zpos; - if (min_zpos != max_zpos) { - LOG_DEBUG( - "DRM plane minimum supported zpos does not equal maximum supported zpos, even though zpos is " - "immutable.\n" - ); - min_zpos = max_zpos = hardcoded_zpos; - } - } - } else if (streq(info->name, "SRC_X")) { - comitted_src_x = props->prop_values[j]; - } else if (streq(info->name, "SRC_Y")) { - comitted_src_y = props->prop_values[j]; - } else if (streq(info->name, "SRC_W")) { - comitted_src_w = props->prop_values[j]; - } else if (streq(info->name, "SRC_H")) { - comitted_src_h = props->prop_values[j]; - } else if (streq(info->name, "CRTC_X")) { - comitted_crtc_x = props->prop_values[j]; - } else if (streq(info->name, "CRTC_Y")) { - comitted_crtc_y = props->prop_values[j]; - } else if (streq(info->name, "CRTC_W")) { - comitted_crtc_w = props->prop_values[j]; - } else if (streq(info->name, "CRTC_H")) { - comitted_crtc_h = props->prop_values[j]; - } else if (streq(info->name, "IN_FORMATS")) { - drmModePropertyBlobRes *blob; - - blob = drmModeGetPropertyBlob(drm_fd, props->prop_values[j]); - if (blob == NULL) { - ok = errno; - LOG_ERROR( - "Couldn't get list of supported format modifiers for plane %u. drmModeGetPropertyBlob: %s\n", - plane_id, - strerror(ok) - ); - drmModeFreeProperty(info); - goto fail_free_props; - } - - plane_out->supports_modifiers = true; - plane_out->supported_modified_formats_blob = memdup(blob->data, blob->length); - ASSERT_NOT_NULL(plane_out->supported_modified_formats_blob); - - drmModeFreePropertyBlob(blob); - } else if (streq(info->name, "alpha")) { - has_alpha = true; - assert(info->flags == DRM_MODE_PROP_RANGE); - assert(info->values[0] <= 0xFFFF); - assert(info->values[1] <= 0xFFFF); - assert(info->values[0] <= info->values[1]); - plane_out->min_alpha = (uint16_t) info->values[0]; - plane_out->max_alpha = (uint16_t) info->values[1]; - - uint64_t value = props->prop_values[j]; - assert(plane_out->min_alpha <= value); - assert(value <= plane_out->max_alpha); - committed_alpha = (uint16_t) value; - } else if (streq(info->name, "pixel blend mode")) { - has_blend_mode = true; - assert(info->flags == DRM_MODE_PROP_ENUM); - - for (int i = 0; i < info->count_enums; i++) { - if (streq(info->enums[i].name, "None")) { - ASSERT_EQUALS(info->enums[i].value, kNone_DrmBlendMode); - supported_blend_modes[kNone_DrmBlendMode] = true; - } else if (streq(info->enums[i].name, "Pre-multiplied")) { - ASSERT_EQUALS(info->enums[i].value, kPremultiplied_DrmBlendMode); - supported_blend_modes[kPremultiplied_DrmBlendMode] = true; - } else if (streq(info->enums[i].name, "Coverage")) { - ASSERT_EQUALS(info->enums[i].value, kCoverage_DrmBlendMode); - supported_blend_modes[kCoverage_DrmBlendMode] = true; - } else { - LOG_DEBUG( - "Unknown KMS pixel blend mode: %s (value: %" PRIu64 ")\n", - info->enums[i].name, - (uint64_t) info->enums[i].value - ); - } - } - - committed_blend_mode = props->prop_values[j]; - assert(committed_blend_mode >= 0 && committed_blend_mode <= kMax_DrmBlendMode); - assert(supported_blend_modes[committed_blend_mode]); - } - -#define CHECK_ASSIGN_PROPERTY_ID(_name_str, _name) \ - if (strncmp(info->name, _name_str, ARRAY_SIZE(info->name)) == 0) { \ - ids._name = info->prop_id; \ - } else - - DRM_PLANE_PROPERTIES(CHECK_ASSIGN_PROPERTY_ID) { - // do nothing - } - -#undef CHECK_ASSIGN_PROPERTY_ID - - drmModeFreeProperty(info); - } - - assert(has_type); - (void) has_type; - - for (int i = 0; i < plane->count_formats; i++) { - for (int j = 0; j < PIXFMT_COUNT; j++) { - if (get_pixfmt_info(j)->drm_format == plane->formats[i]) { - supported_formats[j] = true; - break; - } - } - } - - bool has_format = false; - enum pixfmt format = PIXFMT_RGB565; - - // drmModeGetFB2 might not be present. - // If __attribute__((weak)) is supported by the compiler, we redefine it as - // weak above. - // If we don't have weak, we can't check for it here. -#ifdef HAVE_WEAK_DRM_MODE_GET_FB2 - if (drmModeGetFB2 && drmModeFreeFB2) { - struct drm_mode_fb2 *fb = (struct drm_mode_fb2 *) drmModeGetFB2(drm_fd, plane->fb_id); - if (fb != NULL) { - for (int i = 0; i < PIXFMT_COUNT; i++) { - if (get_pixfmt_info(i)->drm_format == fb->pixel_format) { - has_format = true; - format = i; - break; - } - } - - drmModeFreeFB2((struct _drmModeFB2 *) fb); - } - } -#endif - - plane_out->id = plane->plane_id; - plane_out->possible_crtcs = plane->possible_crtcs; - plane_out->ids = ids; - plane_out->type = type; - plane_out->has_zpos = has_zpos; - plane_out->min_zpos = min_zpos; - plane_out->max_zpos = max_zpos; - plane_out->has_hardcoded_zpos = has_hardcoded_zpos; - plane_out->hardcoded_zpos = hardcoded_zpos; - plane_out->has_rotation = has_rotation; - plane_out->supported_rotations = supported_rotations; - plane_out->has_hardcoded_rotation = has_hardcoded_rotation; - plane_out->hardcoded_rotation = hardcoded_rotation; - memcpy(plane_out->supported_formats, supported_formats, sizeof supported_formats); - plane_out->has_alpha = has_alpha; - plane_out->has_blend_mode = has_blend_mode; - memcpy(plane_out->supported_blend_modes, supported_blend_modes, sizeof supported_blend_modes); - plane_out->committed_state.crtc_id = plane->crtc_id; - plane_out->committed_state.fb_id = plane->fb_id; - plane_out->committed_state.src_x = comitted_src_x; - plane_out->committed_state.src_y = comitted_src_y; - plane_out->committed_state.src_w = comitted_src_w; - plane_out->committed_state.src_h = comitted_src_h; - plane_out->committed_state.crtc_x = comitted_crtc_x; - plane_out->committed_state.crtc_y = comitted_crtc_y; - plane_out->committed_state.crtc_w = comitted_crtc_w; - plane_out->committed_state.crtc_h = comitted_crtc_h; - plane_out->committed_state.zpos = committed_zpos; - plane_out->committed_state.rotation = committed_rotation; - plane_out->committed_state.alpha = committed_alpha; - plane_out->committed_state.blend_mode = committed_blend_mode; - plane_out->committed_state.has_format = has_format; - plane_out->committed_state.format = format; - drmModeFreeObjectProperties(props); - drmModeFreePlane(plane); - return 0; - -fail_maybe_free_supported_modified_formats_blob: - if (plane_out->supported_modified_formats_blob != NULL) - free(plane_out->supported_modified_formats_blob); - -fail_free_props: - drmModeFreeObjectProperties(props); - -fail_free_plane: - drmModeFreePlane(plane); - return ok; -} - -static void free_plane(UNUSED struct drm_plane *plane) { - if (plane->supported_modified_formats_blob != NULL) { - free(plane->supported_modified_formats_blob); - } -} - -static int fetch_planes(struct drmdev *drmdev, struct drm_plane **planes_out, size_t *n_planes_out) { - struct drm_plane *planes; - int ok; - - planes = calloc(drmdev->plane_res->count_planes, sizeof *planes); - if (planes == NULL) { - *planes_out = NULL; - return ENOMEM; - } - - for (int i = 0; i < drmdev->plane_res->count_planes; i++) { - ok = fetch_plane(drmdev->fd, drmdev->plane_res->planes[i], planes + i); - if (ok != 0) { - for (int j = 0; j < i; j++) { - free_plane(planes + i); - } - free(planes); - return ENOMEM; - } - - ASSERT_MSG(planes[0].has_zpos == planes[i].has_zpos, "If one plane has a zpos property, all planes need to have one."); - } - - *planes_out = planes; - *n_planes_out = drmdev->plane_res->count_planes; - - return 0; -} - -static void free_planes(struct drm_plane *planes, size_t n_planes) { - for (int i = 0; i < n_planes; i++) { - free_plane(planes + i); - } - free(planes); -} - -static void assert_rotations_work() { - assert(PLANE_TRANSFORM_ROTATE_0.rotate_0 == true); - assert(PLANE_TRANSFORM_ROTATE_0.rotate_90 == false); - assert(PLANE_TRANSFORM_ROTATE_0.rotate_180 == false); - assert(PLANE_TRANSFORM_ROTATE_0.rotate_270 == false); - assert(PLANE_TRANSFORM_ROTATE_0.reflect_x == false); - assert(PLANE_TRANSFORM_ROTATE_0.reflect_y == false); - - assert(PLANE_TRANSFORM_ROTATE_90.rotate_0 == false); - assert(PLANE_TRANSFORM_ROTATE_90.rotate_90 == true); - assert(PLANE_TRANSFORM_ROTATE_90.rotate_180 == false); - assert(PLANE_TRANSFORM_ROTATE_90.rotate_270 == false); - assert(PLANE_TRANSFORM_ROTATE_90.reflect_x == false); - assert(PLANE_TRANSFORM_ROTATE_90.reflect_y == false); - - assert(PLANE_TRANSFORM_ROTATE_180.rotate_0 == false); - assert(PLANE_TRANSFORM_ROTATE_180.rotate_90 == false); - assert(PLANE_TRANSFORM_ROTATE_180.rotate_180 == true); - assert(PLANE_TRANSFORM_ROTATE_180.rotate_270 == false); - assert(PLANE_TRANSFORM_ROTATE_180.reflect_x == false); - assert(PLANE_TRANSFORM_ROTATE_180.reflect_y == false); - - assert(PLANE_TRANSFORM_ROTATE_270.rotate_0 == false); - assert(PLANE_TRANSFORM_ROTATE_270.rotate_90 == false); - assert(PLANE_TRANSFORM_ROTATE_270.rotate_180 == false); - assert(PLANE_TRANSFORM_ROTATE_270.rotate_270 == true); - assert(PLANE_TRANSFORM_ROTATE_270.reflect_x == false); - assert(PLANE_TRANSFORM_ROTATE_270.reflect_y == false); - - assert(PLANE_TRANSFORM_REFLECT_X.rotate_0 == false); - assert(PLANE_TRANSFORM_REFLECT_X.rotate_90 == false); - assert(PLANE_TRANSFORM_REFLECT_X.rotate_180 == false); - assert(PLANE_TRANSFORM_REFLECT_X.rotate_270 == false); - assert(PLANE_TRANSFORM_REFLECT_X.reflect_x == true); - assert(PLANE_TRANSFORM_REFLECT_X.reflect_y == false); - - assert(PLANE_TRANSFORM_REFLECT_Y.rotate_0 == false); - assert(PLANE_TRANSFORM_REFLECT_Y.rotate_90 == false); - assert(PLANE_TRANSFORM_REFLECT_Y.rotate_180 == false); - assert(PLANE_TRANSFORM_REFLECT_Y.rotate_270 == false); - assert(PLANE_TRANSFORM_REFLECT_Y.reflect_x == false); - assert(PLANE_TRANSFORM_REFLECT_Y.reflect_y == true); - - drm_plane_transform_t r = PLANE_TRANSFORM_NONE; - - r.rotate_0 = true; - r.reflect_x = true; - assert(r.u32 == (DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_X)); - - r.u32 = DRM_MODE_ROTATE_90 | DRM_MODE_REFLECT_Y; - assert(r.rotate_0 == false); - assert(r.rotate_90 == true); - assert(r.rotate_180 == false); - assert(r.rotate_270 == false); - assert(r.reflect_x == false); - assert(r.reflect_y == true); - (void) r; -} - -static int set_drm_client_caps(int fd, bool *supports_atomic_modesetting) { - int ok; - - ok = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not set DRM client universal planes capable. drmSetClientCap: %s\n", strerror(ok)); - return ok; - } - -#ifdef USE_LEGACY_KMS - *supports_atomic_modesetting = false; -#else - ok = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1); - if ((ok < 0) && (errno == EOPNOTSUPP)) { - if (supports_atomic_modesetting != NULL) { - *supports_atomic_modesetting = false; - } - } else if (ok < 0) { - ok = errno; - LOG_ERROR("Could not set DRM client atomic capable. drmSetClientCap: %s\n", strerror(ok)); - return ok; - } else { - if (supports_atomic_modesetting != NULL) { - *supports_atomic_modesetting = true; - } - } -#endif - - return 0; -} - -struct drmdev *drmdev_new_from_interface_fd(int fd, void *fd_metadata, const struct drmdev_interface *interface, void *userdata) { - struct gbm_device *gbm_device; - struct drmdev *drmdev; - uint64_t cap; - bool supports_atomic_modesetting; - bool supports_dumb_buffers; - int ok, master_fd, event_fd; - - assert_rotations_work(); - - drmdev = malloc(sizeof *drmdev); - if (drmdev == NULL) { - return NULL; - } - - master_fd = fd; - - ok = set_drm_client_caps(fd, &supports_atomic_modesetting); - if (ok != 0) { - goto fail_free_drmdev; - } - - cap = 0; - ok = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &cap); - if (ok < 0) { - supports_dumb_buffers = false; - } else { - supports_dumb_buffers = !!cap; - } - - drmdev->res = drmModeGetResources(fd); - if (drmdev->res == NULL) { - ok = errno; - LOG_ERROR("Could not get DRM device resources. drmModeGetResources: %s\n", strerror(ok)); - goto fail_free_drmdev; - } - - drmdev->plane_res = drmModeGetPlaneResources(fd); - if (drmdev->plane_res == NULL) { - ok = errno; - LOG_ERROR("Could not get DRM device planes resources. drmModeGetPlaneResources: %s\n", strerror(ok)); - goto fail_free_resources; - } - - drmdev->fd = fd; - - ok = fetch_connectors(drmdev, &drmdev->connectors, &drmdev->n_connectors); - if (ok != 0) { - goto fail_free_plane_resources; - } - - ok = fetch_encoders(drmdev, &drmdev->encoders, &drmdev->n_encoders); - if (ok != 0) { - goto fail_free_connectors; - } - - ok = fetch_crtcs(drmdev, &drmdev->crtcs, &drmdev->n_crtcs); - if (ok != 0) { - goto fail_free_encoders; - } - - ok = fetch_planes(drmdev, &drmdev->planes, &drmdev->n_planes); - if (ok != 0) { - goto fail_free_crtcs; - } - - // Rockchip driver always wants the N-th primary/cursor plane to be associated with the N-th CRTC. - // If we don't respect this, commits will work but not actually show anything on screen. - int primary_plane_index = 0; - int cursor_plane_index = 0; - for (int i = 0; i < drmdev->n_planes; i++) { - if (drmdev->planes[i].type == DRM_PLANE_TYPE_PRIMARY) { - if ((drmdev->planes[i].possible_crtcs & (1 << primary_plane_index)) != 0) { - drmdev->planes[i].possible_crtcs = (1 << primary_plane_index); - } else { - LOG_DEBUG("Primary plane %d does not support CRTC %d.\n", primary_plane_index, primary_plane_index); - } - - primary_plane_index++; - } else if (drmdev->planes[i].type == DRM_PLANE_TYPE_CURSOR) { - if ((drmdev->planes[i].possible_crtcs & (1 << cursor_plane_index)) != 0) { - drmdev->planes[i].possible_crtcs = (1 << cursor_plane_index); - } else { - LOG_DEBUG("Cursor plane %d does not support CRTC %d.\n", cursor_plane_index, cursor_plane_index); - } - - cursor_plane_index++; - } - } - - gbm_device = gbm_create_device(drmdev->fd); - if (gbm_device == NULL) { - LOG_ERROR("Could not create GBM device.\n"); - goto fail_free_planes; - } - - event_fd = epoll_create1(EPOLL_CLOEXEC); - if (event_fd < 0) { - LOG_ERROR("Could not create modesetting epoll instance.\n"); - goto fail_destroy_gbm_device; - } - - ok = epoll_ctl(event_fd, EPOLL_CTL_ADD, fd, &(struct epoll_event){ .events = EPOLLIN | EPOLLPRI, .data.ptr = NULL }); - if (ok != 0) { - LOG_ERROR("Could not add DRM file descriptor to epoll instance.\n"); - goto fail_close_event_fd; - } - - pthread_mutex_init(&drmdev->mutex, get_default_mutex_attrs()); - drmdev->n_refs = REFCOUNT_INIT_1; - drmdev->fd = fd; - drmdev->supports_atomic_modesetting = supports_atomic_modesetting; - drmdev->supports_dumb_buffers = supports_dumb_buffers; - drmdev->gbm_device = gbm_device; - drmdev->event_fd = event_fd; - memset(drmdev->per_crtc_state, 0, sizeof(drmdev->per_crtc_state)); - drmdev->master_fd = master_fd; - drmdev->master_fd_metadata = fd_metadata; - drmdev->interface = *interface; - drmdev->userdata = userdata; - list_inithead(&drmdev->fbs); - return drmdev; - -fail_close_event_fd: - close(event_fd); - -fail_destroy_gbm_device: - gbm_device_destroy(gbm_device); - -fail_free_planes: - free_planes(drmdev->planes, drmdev->n_planes); - -fail_free_crtcs: - free_crtcs(drmdev->crtcs, drmdev->n_crtcs); - -fail_free_encoders: - free_encoders(drmdev->encoders, drmdev->n_encoders); - -fail_free_connectors: - free_connectors(drmdev->connectors, drmdev->n_connectors); - -fail_free_plane_resources: - drmModeFreePlaneResources(drmdev->plane_res); - -fail_free_resources: - drmModeFreeResources(drmdev->res); - - // fail_close_master_fd: - // interface->close(master_fd, NULL, userdata); - -fail_free_drmdev: - free(drmdev); - - return NULL; -} - -struct drmdev *drmdev_new_from_path(const char *path, const struct drmdev_interface *interface, void *userdata) { - struct drmdev *drmdev; - void *fd_metadata; - int fd; - - ASSERT_NOT_NULL(path); - ASSERT_NOT_NULL(interface); - - fd = interface->open(path, O_RDWR, &fd_metadata, userdata); - if (fd < 0) { - LOG_ERROR("Could not open DRM device. interface->open: %s\n", strerror(errno)); - return NULL; - } - - drmdev = drmdev_new_from_interface_fd(fd, fd_metadata, interface, userdata); - if (drmdev == NULL) { - close(fd); - return NULL; - } - - return drmdev; -} - -static void drmdev_destroy(struct drmdev *drmdev) { - assert(refcount_is_zero(&drmdev->n_refs)); - - drmdev->interface.close(drmdev->master_fd, drmdev->master_fd_metadata, drmdev->userdata); - close(drmdev->event_fd); - gbm_device_destroy(drmdev->gbm_device); - free_planes(drmdev->planes, drmdev->n_planes); - free_crtcs(drmdev->crtcs, drmdev->n_crtcs); - free_encoders(drmdev->encoders, drmdev->n_encoders); - free_connectors(drmdev->connectors, drmdev->n_connectors); - drmModeFreePlaneResources(drmdev->plane_res); - drmModeFreeResources(drmdev->res); - free(drmdev); -} - -DEFINE_REF_OPS(drmdev, n_refs) - -int drmdev_get_fd(struct drmdev *drmdev) { - ASSERT_NOT_NULL(drmdev); - return drmdev->master_fd; -} - -int drmdev_get_event_fd(struct drmdev *drmdev) { - ASSERT_NOT_NULL(drmdev); - return drmdev->master_fd; -} - -bool drmdev_supports_dumb_buffers(struct drmdev *drmdev) { - return drmdev->supports_dumb_buffers; -} - -int drmdev_create_dumb_buffer( - struct drmdev *drmdev, - int width, - int height, - int bpp, - uint32_t *gem_handle_out, - uint32_t *pitch_out, - size_t *size_out -) { - struct drm_mode_create_dumb create_req; - int ok; - - ASSERT_NOT_NULL(drmdev); - ASSERT_NOT_NULL(gem_handle_out); - ASSERT_NOT_NULL(pitch_out); - ASSERT_NOT_NULL(size_out); - - memset(&create_req, 0, sizeof create_req); - create_req.width = width; - create_req.height = height; - create_req.bpp = bpp; - create_req.flags = 0; - - ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not create dumb buffer. ioctl: %s\n", strerror(errno)); - goto fail_return_ok; - } - - *gem_handle_out = create_req.handle; - *pitch_out = create_req.pitch; - *size_out = create_req.size; - return 0; - -fail_return_ok: - return ok; -} - -void drmdev_destroy_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle) { - struct drm_mode_destroy_dumb destroy_req; - int ok; - - ASSERT_NOT_NULL(drmdev); - - memset(&destroy_req, 0, sizeof destroy_req); - destroy_req.handle = gem_handle; - - ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); - if (ok < 0) { - LOG_ERROR("Could not destroy dumb buffer. ioctl: %s\n", strerror(errno)); - } -} - -void *drmdev_map_dumb_buffer(struct drmdev *drmdev, uint32_t gem_handle, size_t size) { - struct drm_mode_map_dumb map_req; - void *map; - int ok; - - ASSERT_NOT_NULL(drmdev); - - memset(&map_req, 0, sizeof map_req); - map_req.handle = gem_handle; - - ok = ioctl(drmdev->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req); - if (ok < 0) { - LOG_ERROR("Could not prepare dumb buffer mmap. ioctl: %s\n", strerror(errno)); - return NULL; - } - - map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, drmdev->fd, map_req.offset); - if (map == MAP_FAILED) { - LOG_ERROR("Could not mmap dumb buffer. mmap: %s\n", strerror(errno)); - return NULL; - } - - return map; -} - -void drmdev_unmap_dumb_buffer(struct drmdev *drmdev, void *map, size_t size) { - int ok; - - ASSERT_NOT_NULL(drmdev); - ASSERT_NOT_NULL(map); - (void) drmdev; - - ok = munmap(map, size); - if (ok < 0) { - LOG_ERROR("Couldn't unmap dumb buffer. munmap: %s\n", strerror(errno)); - } -} - -static void -drmdev_on_page_flip_locked(int fd, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, unsigned int crtc_id, void *userdata) { - struct kms_req_builder *builder; - struct drm_crtc *crtc; - struct kms_req **last_flipped; - struct kms_req *req; - struct drmdev *drmdev; - - ASSERT_NOT_NULL(userdata); - builder = userdata; - req = userdata; - - (void) fd; - (void) sequence; - (void) crtc_id; - - drmdev = builder->drmdev; - for_each_crtc_in_drmdev(drmdev, crtc) { - if (crtc->id == crtc_id) { - break; - } - } - - ASSERT_NOT_NULL_MSG(crtc, "Invalid CRTC id"); - - if (drmdev->per_crtc_state[crtc->index].scanout_callback != NULL) { - uint64_t vblank_ns = tv_sec * 1000000000ull + tv_usec * 1000ull; - drmdev->per_crtc_state[crtc->index].scanout_callback(drmdev, vblank_ns, drmdev->per_crtc_state[crtc->index].userdata); - - // clear the scanout callback - drmdev->per_crtc_state[crtc->index].scanout_callback = NULL; - drmdev->per_crtc_state[crtc->index].destroy_callback = NULL; - drmdev->per_crtc_state[crtc->index].userdata = NULL; - } - - last_flipped = &drmdev->per_crtc_state[crtc->index].last_flipped; - if (*last_flipped != NULL) { - /// TODO: Remove this if we ever cache KMS reqs. - /// FIXME: This will fail if we're using blocking commits. - // assert(refcount_is_one(&((struct kms_req_builder*) *last_flipped)->n_refs)); - } - - kms_req_swap_ptrs(last_flipped, req); - kms_req_unref(req); -} - -static int drmdev_on_modesetting_fd_ready_locked(struct drmdev *drmdev) { - int ok; - - static drmEventContext ctx = { - .version = DRM_EVENT_CONTEXT_VERSION, - .vblank_handler = NULL, - .page_flip_handler = NULL, - .page_flip_handler2 = drmdev_on_page_flip_locked, - .sequence_handler = NULL, - }; - - ok = drmHandleEvent(drmdev->master_fd, &ctx); - if (ok != 0) { - return EIO; - } - - return 0; -} - -int drmdev_on_event_fd_ready(struct drmdev *drmdev) { - struct epoll_event events[16]; - int ok, n_events; - - ASSERT_NOT_NULL(drmdev); - - drmdev_lock(drmdev); - - while (1) { - ok = epoll_wait(drmdev->event_fd, events, ARRAY_SIZE(events), 0); - if ((ok < 0) && (errno == EINTR)) { - // retry - continue; - } else if (ok < 0) { - ok = errno; - LOG_ERROR("Could read kernel modesetting events. epoll_wait: %s\n", strerror(ok)); - goto fail_unlock; - } else { - break; - } - } - - n_events = ok; - for (int i = 0; i < n_events; i++) { - // currently this could only be the root drmdev fd. - ASSERT_EQUALS(events[i].data.ptr, NULL); - ok = drmdev_on_modesetting_fd_ready_locked(drmdev); - if (ok != 0) { - goto fail_unlock; - } - } - - drmdev_unlock(drmdev); - - return 0; - -fail_unlock: - drmdev_unlock(drmdev); - return ok; -} - -struct gbm_device *drmdev_get_gbm_device(struct drmdev *drmdev) { - ASSERT_NOT_NULL(drmdev); - return drmdev->gbm_device; -} - -int drmdev_get_last_vblank_locked(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out) { - int ok; - - ASSERT_NOT_NULL(drmdev); - ASSERT_NOT_NULL(last_vblank_ns_out); - - ok = drmCrtcGetSequence(drmdev->fd, crtc_id, NULL, last_vblank_ns_out); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not get next vblank timestamp. drmCrtcGetSequence: %s\n", strerror(ok)); - return ok; - } - - return 0; -} - -int drmdev_get_last_vblank(struct drmdev *drmdev, uint32_t crtc_id, uint64_t *last_vblank_ns_out) { - int ok; - - drmdev_lock(drmdev); - ok = drmdev_get_last_vblank_locked(drmdev, crtc_id, last_vblank_ns_out); - drmdev_unlock(drmdev); - - return ok; -} - -uint32_t drmdev_add_fb_multiplanar_locked( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - const uint32_t bo_handles[4], - const uint32_t pitches[4], - const uint32_t offsets[4], - bool has_modifiers, - const uint64_t modifiers[4] -) { - struct drm_fb *fb; - uint32_t fb_id; - int ok; - - /// TODO: Code in https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/drm_framebuffer.c#L257 - /// assumes handles, pitches, offsets and modifiers for unused planes are zero. Make sure that's the - /// case here. - ASSERT_NOT_NULL(drmdev); - assert(width > 0 && height > 0); - assert(bo_handles[0] != 0); - assert(pitches[0] != 0); - - fb = malloc(sizeof *fb); - if (fb == NULL) { - return 0; - } - - list_inithead(&fb->entry); - fb->id = 0; - fb->width = width; - fb->height = height; - fb->format = pixel_format; - fb->has_modifier = has_modifiers; - fb->modifier = modifiers[0]; - fb->flags = 0; - memcpy(fb->handles, bo_handles, sizeof(fb->handles)); - memcpy(fb->pitches, pitches, sizeof(fb->pitches)); - memcpy(fb->offsets, offsets, sizeof(fb->offsets)); - - fb_id = 0; - if (has_modifiers) { - ok = drmModeAddFB2WithModifiers( - drmdev->fd, - width, - height, - get_pixfmt_info(pixel_format)->drm_format, - bo_handles, - pitches, - offsets, - modifiers, - &fb_id, - DRM_MODE_FB_MODIFIERS - ); - if (ok < 0) { - LOG_ERROR("Couldn't add buffer as DRM fb. drmModeAddFB2WithModifiers: %s\n", strerror(-ok)); - goto fail_free_fb; - } - } else { - ok = drmModeAddFB2(drmdev->fd, width, height, get_pixfmt_info(pixel_format)->drm_format, bo_handles, pitches, offsets, &fb_id, 0); - if (ok < 0) { - LOG_ERROR("Couldn't add buffer as DRM fb. drmModeAddFB2: %s\n", strerror(-ok)); - goto fail_free_fb; - } - } - - fb->id = fb_id; - list_add(&fb->entry, &drmdev->fbs); - - assert(fb_id != 0); - return fb_id; - -fail_free_fb: - free(fb); - return 0; -} - -uint32_t drmdev_add_fb_multiplanar( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - const uint32_t bo_handles[4], - const uint32_t pitches[4], - const uint32_t offsets[4], - bool has_modifiers, - const uint64_t modifiers[4] -) { - uint32_t fb; - - drmdev_lock(drmdev); - - fb = drmdev_add_fb_multiplanar_locked(drmdev, width, height, pixel_format, bo_handles, pitches, offsets, has_modifiers, modifiers); - - drmdev_unlock(drmdev); - - return fb; -} - -uint32_t drmdev_add_fb_locked( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - uint32_t bo_handle, - uint32_t pitch, - uint32_t offset, - bool has_modifier, - uint64_t modifier -) { - return drmdev_add_fb_multiplanar_locked( - drmdev, - width, - height, - pixel_format, - (uint32_t[4]){ bo_handle, 0 }, - (uint32_t[4]){ pitch, 0 }, - (uint32_t[4]){ offset, 0 }, - has_modifier, - (const uint64_t[4]){ modifier, 0 } - ); -} - -uint32_t drmdev_add_fb( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - uint32_t bo_handle, - uint32_t pitch, - uint32_t offset, - bool has_modifier, - uint64_t modifier -) { - return drmdev_add_fb_multiplanar( - drmdev, - width, - height, - pixel_format, - (uint32_t[4]){ bo_handle, 0 }, - (uint32_t[4]){ pitch, 0 }, - (uint32_t[4]){ offset, 0 }, - has_modifier, - (const uint64_t[4]){ modifier, 0 } - ); -} - -uint32_t drmdev_add_fb_from_dmabuf_locked( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - int prime_fd, - uint32_t pitch, - uint32_t offset, - bool has_modifier, - uint64_t modifier -) { - uint32_t bo_handle; - int ok; - - ok = drmPrimeFDToHandle(drmdev->fd, prime_fd, &bo_handle); - if (ok < 0) { - LOG_ERROR("Couldn't import DMA-buffer as GEM buffer. drmPrimeFDToHandle: %s\n", strerror(errno)); - return 0; - } - - return drmdev_add_fb_locked(drmdev, width, height, pixel_format, prime_fd, pitch, offset, has_modifier, modifier); -} - -uint32_t drmdev_add_fb_from_dmabuf( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - int prime_fd, - uint32_t pitch, - uint32_t offset, - bool has_modifier, - uint64_t modifier -) { - uint32_t fb; - - drmdev_lock(drmdev); - - fb = drmdev_add_fb_from_dmabuf_locked(drmdev, width, height, pixel_format, prime_fd, pitch, offset, has_modifier, modifier); - - drmdev_unlock(drmdev); - - return fb; -} - -uint32_t drmdev_add_fb_from_dmabuf_multiplanar_locked( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - const int prime_fds[4], - const uint32_t pitches[4], - const uint32_t offsets[4], - bool has_modifiers, - const uint64_t modifiers[4] -) { - uint32_t bo_handles[4] = { 0 }; - int ok; - - for (int i = 0; (i < 4) && (prime_fds[i] != 0); i++) { - ok = drmPrimeFDToHandle(drmdev->fd, prime_fds[i], bo_handles + i); - if (ok < 0) { - LOG_ERROR("Couldn't import DMA-buffer as GEM buffer. drmPrimeFDToHandle: %s\n", strerror(errno)); - return 0; - } - } - - return drmdev_add_fb_multiplanar_locked(drmdev, width, height, pixel_format, bo_handles, pitches, offsets, has_modifiers, modifiers); -} - -uint32_t drmdev_add_fb_from_dmabuf_multiplanar( - struct drmdev *drmdev, - uint32_t width, - uint32_t height, - enum pixfmt pixel_format, - const int prime_fds[4], - const uint32_t pitches[4], - const uint32_t offsets[4], - bool has_modifiers, - const uint64_t modifiers[4] -) { - uint32_t fb; - - drmdev_lock(drmdev); - - fb = drmdev_add_fb_from_dmabuf_multiplanar_locked( - drmdev, - width, - height, - pixel_format, - prime_fds, - pitches, - offsets, - has_modifiers, - modifiers - ); - - drmdev_unlock(drmdev); - - return fb; -} - -uint32_t drmdev_add_fb_from_gbm_bo_locked(struct drmdev *drmdev, struct gbm_bo *bo, bool cast_opaque) { - enum pixfmt format; - uint32_t fourcc; - int n_planes; - - n_planes = gbm_bo_get_plane_count(bo); - ASSERT(0 <= n_planes && n_planes <= 4); - - fourcc = gbm_bo_get_format(bo); - - if (!has_pixfmt_for_gbm_format(fourcc)) { - LOG_ERROR("GBM pixel format is not supported.\n"); - return 0; - } - - format = get_pixfmt_for_gbm_format(fourcc); - - if (cast_opaque) { - format = pixfmt_opaque(format); - } - - uint32_t handles[4]; - uint32_t pitches[4]; - - // Returns DRM_FORMAT_MOD_INVALID on failure, or DRM_FORMAT_MOD_LINEAR - // for dumb buffers. - uint64_t modifier = gbm_bo_get_modifier(bo); - bool has_modifiers = modifier != DRM_FORMAT_MOD_INVALID; - - for (int i = 0; i < n_planes; i++) { - // gbm_bo_get_handle_for_plane will return -1 (in gbm_bo_handle.s32) and - // set errno on failure. - errno = 0; - union gbm_bo_handle handle = gbm_bo_get_handle_for_plane(bo, i); - if (handle.s32 == -1) { - LOG_ERROR("Could not get GEM handle for plane %d: %s\n", i, strerror(errno)); - return 0; - } - - handles[i] = handle.u32; - - // gbm_bo_get_stride_for_plane will return 0 and set errno on failure. - errno = 0; - uint32_t pitch = gbm_bo_get_stride_for_plane(bo, i); - if (pitch == 0 && errno != 0) { - LOG_ERROR("Could not get framebuffer stride for plane %d: %s\n", i, strerror(errno)); - return 0; - } - - pitches[i] = pitch; - } - - for (int i = n_planes; i < 4; i++) { - handles[i] = 0; - pitches[i] = 0; - } - - return drmdev_add_fb_multiplanar_locked( - drmdev, - gbm_bo_get_width(bo), - gbm_bo_get_height(bo), - format, - handles, - pitches, - (uint32_t[4]){ - n_planes >= 1 ? gbm_bo_get_offset(bo, 0) : 0, - n_planes >= 2 ? gbm_bo_get_offset(bo, 1) : 0, - n_planes >= 3 ? gbm_bo_get_offset(bo, 2) : 0, - n_planes >= 4 ? gbm_bo_get_offset(bo, 3) : 0, - }, - has_modifiers, - (uint64_t[4]){ - n_planes >= 1 ? modifier : 0, - n_planes >= 2 ? modifier : 0, - n_planes >= 3 ? modifier : 0, - n_planes >= 4 ? modifier : 0, - } - ); -} - -uint32_t drmdev_add_fb_from_gbm_bo(struct drmdev *drmdev, struct gbm_bo *bo, bool cast_opaque) { - uint32_t fb; - - drmdev_lock(drmdev); - - fb = drmdev_add_fb_from_gbm_bo_locked(drmdev, bo, cast_opaque); - - drmdev_unlock(drmdev); - - return fb; -} - -int drmdev_rm_fb_locked(struct drmdev *drmdev, uint32_t fb_id) { - int ok; - - list_for_each_entry(struct drm_fb, fb, &drmdev->fbs, entry) { - if (fb->id == fb_id) { - list_del(&fb->entry); - free(fb); - break; - } - } - - ok = drmModeRmFB(drmdev->fd, fb_id); - if (ok < 0) { - ok = -ok; - LOG_ERROR("Could not remove DRM framebuffer. drmModeRmFB: %s\n", strerror(ok)); - return ok; - } - - return 0; -} - -int drmdev_rm_fb(struct drmdev *drmdev, uint32_t fb_id) { - int ok; - - drmdev_lock(drmdev); - - ok = drmdev_rm_fb_locked(drmdev, fb_id); - - drmdev_unlock(drmdev); - - return ok; -} - -bool drmdev_can_modeset(struct drmdev *drmdev) { - bool can_modeset; - - ASSERT_NOT_NULL(drmdev); - - drmdev_lock(drmdev); - - can_modeset = drmdev->master_fd > 0; - - drmdev_unlock(drmdev); - - return can_modeset; -} - -void drmdev_suspend(struct drmdev *drmdev) { - ASSERT_NOT_NULL(drmdev); - - drmdev_lock(drmdev); - - if (drmdev->master_fd <= 0) { - LOG_ERROR("drmdev_suspend was called, but drmdev is already suspended\n"); - drmdev_unlock(drmdev); - return; - } - - drmdev->interface.close(drmdev->master_fd, drmdev->master_fd_metadata, drmdev->userdata); - drmdev->master_fd = -1; - drmdev->master_fd_metadata = NULL; - - drmdev_unlock(drmdev); -} - -int drmdev_resume(struct drmdev *drmdev) { - drmDevicePtr device; - void *fd_metadata; - int ok, master_fd; - - ASSERT_NOT_NULL(drmdev); - - drmdev_lock(drmdev); - - if (drmdev->master_fd > 0) { - ok = EINVAL; - LOG_ERROR("drmdev_resume was called, but drmdev is already resumed\n"); - goto fail_unlock; - } - - ok = drmGetDevice(drmdev->fd, &device); - if (ok < 0) { - ok = errno; - LOG_ERROR("Couldn't query DRM device info. drmGetDevice: %s\n", strerror(ok)); - goto fail_unlock; - } - - ok = drmdev->interface.open(device->nodes[DRM_NODE_PRIMARY], O_CLOEXEC | O_NONBLOCK, &fd_metadata, drmdev->userdata); - if (ok < 0) { - ok = -ok; - LOG_ERROR("Couldn't open DRM device.\n"); - goto fail_free_device; - } - - master_fd = ok; - - drmFreeDevice(&device); - - ok = set_drm_client_caps(master_fd, NULL); - if (ok != 0) { - goto fail_close_device; - } - - drmdev->master_fd = master_fd; - drmdev->master_fd_metadata = fd_metadata; - drmdev_unlock(drmdev); - return 0; - -fail_close_device: - drmdev->interface.close(master_fd, fd_metadata, drmdev->userdata); - goto fail_unlock; - -fail_free_device: - drmFreeDevice(&device); - -fail_unlock: - drmdev_unlock(drmdev); - return ok; -} - -int drmdev_move_cursor(struct drmdev *drmdev, uint32_t crtc_id, struct vec2i pos) { - int ok = drmModeMoveCursor(drmdev->master_fd, crtc_id, pos.x, pos.y); - if (ok < 0) { - LOG_ERROR("Couldn't move mouse cursor. drmModeMoveCursor: %s\n", strerror(-ok)); - return -ok; - } - - return 0; -} - -static void drmdev_set_scanout_callback_locked( - struct drmdev *drmdev, - uint32_t crtc_id, - kms_scanout_cb_t scanout_callback, - void *userdata, - void_callback_t destroy_callback -) { - struct drm_crtc *crtc; - - ASSERT_NOT_NULL(drmdev); - - for_each_crtc_in_drmdev(drmdev, crtc) { - if (crtc->id == crtc_id) { - break; - } - } - - ASSERT_NOT_NULL_MSG(crtc, "Could not find CRTC with given id."); - - // If there's already a scanout callback configured, this is probably a state machine error. - // The scanout callback is configured in kms_req_commit and is cleared after it was called. - // So if this is called again this mean kms_req_commit is called but the previous frame wasn't committed yet. - ASSERT_EQUALS_MSG( - drmdev->per_crtc_state[crtc->index].scanout_callback, - NULL, - "There's already a scanout callback configured for this CRTC." - ); - drmdev->per_crtc_state[crtc->index].scanout_callback = scanout_callback; - drmdev->per_crtc_state[crtc->index].destroy_callback = destroy_callback; - drmdev->per_crtc_state[crtc->index].userdata = userdata; -} - -UNUSED static struct drm_plane *get_plane_by_id(struct drmdev *drmdev, uint32_t plane_id) { - struct drm_plane *plane; - - plane = NULL; - for (int i = 0; i < drmdev->n_planes; i++) { - if (drmdev->planes[i].id == plane_id) { - plane = drmdev->planes + i; - break; - } - } - - return plane; -} - -struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector) { - bool found = connector == NULL; - for (size_t i = 0; i < drmdev->n_connectors; i++) { - if (drmdev->connectors + i == connector) { - found = true; - } else if (found) { - return drmdev->connectors + i; - } - } - - return NULL; -} - -struct drm_encoder *__next_encoder(const struct drmdev *drmdev, const struct drm_encoder *encoder) { - bool found = encoder == NULL; - for (size_t i = 0; i < drmdev->n_encoders; i++) { - if (drmdev->encoders + i == encoder) { - found = true; - } else if (found) { - return drmdev->encoders + i; - } - } - - return NULL; -} - -struct drm_crtc *__next_crtc(const struct drmdev *drmdev, const struct drm_crtc *crtc) { - bool found = crtc == NULL; - for (size_t i = 0; i < drmdev->n_crtcs; i++) { - if (drmdev->crtcs + i == crtc) { - found = true; - } else if (found) { - return drmdev->crtcs + i; - } - } - - return NULL; -} - -struct drm_plane *__next_plane(const struct drmdev *drmdev, const struct drm_plane *plane) { - bool found = plane == NULL; - for (size_t i = 0; i < drmdev->n_planes; i++) { - if (drmdev->planes + i == plane) { - found = true; - } else if (found) { - return drmdev->planes + i; - } - } - - return NULL; -} - -drmModeModeInfo *__next_mode(const struct drm_connector *connector, const drmModeModeInfo *mode) { - bool found = mode == NULL; - for (int i = 0; i < connector->variable_state.n_modes; i++) { - if (connector->variable_state.modes + i == mode) { - found = true; - } else if (found) { - return connector->variable_state.modes + i; - } - } - - return NULL; -} - -#ifdef DEBUG_DRM_PLANE_ALLOCATIONS - #define LOG_DRM_PLANE_ALLOCATION_DEBUG LOG_DEBUG -#else - #define LOG_DRM_PLANE_ALLOCATION_DEBUG(...) -#endif - -static bool plane_qualifies( - // clang-format off - struct drm_plane *plane, - bool allow_primary, - bool allow_overlay, - bool allow_cursor, - enum pixfmt format, - bool has_modifier, uint64_t modifier, - bool has_zpos, int64_t zpos_lower_limit, int64_t zpos_upper_limit, - bool has_rotation, drm_plane_transform_t rotation, - bool has_id_range, uint32_t id_lower_limit - // clang-format on -) { - LOG_DRM_PLANE_ALLOCATION_DEBUG(" checking if plane with id %" PRIu32 " qualifies...\n", plane->id); - - if (plane->type == kPrimary_DrmPlaneType) { - if (!allow_primary) { - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane type is primary but allow_primary is false\n"); - return false; - } - } else if (plane->type == kOverlay_DrmPlaneType) { - if (!allow_overlay) { - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane type is overlay but allow_overlay is false\n"); - return false; - } - } else if (plane->type == kCursor_DrmPlaneType) { - if (!allow_cursor) { - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane type is cursor but allow_cursor is false\n"); - return false; - } - } else { - ASSERT(false); - } - - if (has_modifier) { - if (!plane->supported_modified_formats_blob) { - // return false if we want a modified format but the plane doesn't support modified formats - LOG_DRM_PLANE_ALLOCATION_DEBUG( - " does not qualify: framebuffer has modifier %" PRIu64 " but plane does not support modified formats\n", - modifier - ); - return false; - } - - struct { - enum pixfmt format; - uint64_t modifier; - bool found; - } context = { - .format = format, - .modifier = modifier, - .found = false, - }; - - // Check if the requested format & modifier is supported. - drm_plane_for_each_modified_format(plane, check_modified_format_supported, &context); - - // Otherwise fail. - if (!context.found) { - LOG_DRM_PLANE_ALLOCATION_DEBUG( - " does not qualify: plane does not support the modified format %s, %" PRIu64 ".\n", - get_pixfmt_info(format)->name, - modifier - ); - - // not found in the supported modified format list - return false; - } - } else { - // we don't want a modified format, return false if the format is not in the list - // of supported (unmodified) formats - if (!plane->supported_formats[format]) { - LOG_DRM_PLANE_ALLOCATION_DEBUG( - " does not qualify: plane does not support the (unmodified) format %s.\n", - get_pixfmt_info(format)->name - ); - return false; - } - } - - if (has_zpos) { - if (!plane->has_zpos) { - // return false if we want a zpos but the plane doesn't support one - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: zpos constraints specified but plane doesn't have a zpos property.\n"); - return false; - } else if (zpos_lower_limit > plane->max_zpos || zpos_upper_limit < plane->min_zpos) { - // return false if the zpos we want is outside the supported range of the plane - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane limits cannot satisfy the specified zpos constraints.\n"); - LOG_DRM_PLANE_ALLOCATION_DEBUG( - " plane zpos range: %" PRIi64 " <= zpos <= %" PRIi64 ", given zpos constraints: %" PRIi64 " <= zpos <= %" PRIi64 ".\n", - plane->min_zpos, - plane->max_zpos, - zpos_lower_limit, - zpos_upper_limit - ); - return false; - } - } - if (has_id_range && plane->id < id_lower_limit) { - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane id does not satisfy the given plane id constrains.\n"); - LOG_DRM_PLANE_ALLOCATION_DEBUG(" plane id: %" PRIu32 ", plane id lower limit: %" PRIu32 "\n", plane->id, id_lower_limit); - return false; - } - if (has_rotation) { - if (!plane->has_rotation) { - // return false if the plane doesn't support rotation - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: explicit rotation requested but plane has no rotation property.\n"); - return false; - } else if (plane->has_hardcoded_rotation && plane->hardcoded_rotation.u32 != rotation.u32) { - // return false if the plane has a hardcoded rotation and the rotation we want - // is not the hardcoded one - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: plane has hardcoded rotation that doesn't match the requested rotation.\n" - ); - return false; - } else if (rotation.u32 & ~plane->supported_rotations.u32) { - // return false if we can't construct the rotation using the rotation - // bits that are supported by the plane - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does not qualify: requested rotation is not supported by the plane.\n"); - return false; - } - } - - LOG_DRM_PLANE_ALLOCATION_DEBUG(" does qualify.\n"); - return true; -} - -static struct drm_plane *allocate_plane( - // clang-format off - struct kms_req_builder *builder, - bool allow_primary, - bool allow_overlay, - bool allow_cursor, - enum pixfmt format, - bool has_modifier, uint64_t modifier, - bool has_zpos, int64_t zpos_lower_limit, int64_t zpos_upper_limit, - bool has_rotation, drm_plane_transform_t rotation, - bool has_id_range, uint32_t id_lower_limit - // clang-format on -) { - for (int i = 0; i < BITSET_SIZE(builder->available_planes); i++) { - struct drm_plane *plane = builder->drmdev->planes + i; - - if (BITSET_TEST(builder->available_planes, i)) { - // find out if the plane matches our criteria - bool qualifies = plane_qualifies( - plane, - allow_primary, - allow_overlay, - allow_cursor, - format, - has_modifier, - modifier, - has_zpos, - zpos_lower_limit, - zpos_upper_limit, - has_rotation, - rotation, - has_id_range, - id_lower_limit - ); - - // if it doesn't, look for the next one - if (!qualifies) { - continue; - } - - // we found one, mark it as used and return it - BITSET_CLEAR(builder->available_planes, i); - return plane; - } - } - - // we didn't find an available plane matching our criteria - return NULL; -} - -static void release_plane(struct kms_req_builder *builder, uint32_t plane_id) { - struct drm_plane *plane; - int index; - - index = 0; - for_each_plane_in_drmdev(builder->drmdev, plane) { - if (plane->id == plane_id) { - break; - } - index++; - } - - if (plane == NULL) { - LOG_ERROR("Could not release invalid plane %" PRIu32 ".\n", plane_id); - return; - } - - assert(!BITSET_TEST(builder->available_planes, index)); - BITSET_SET(builder->available_planes, index); -} - -struct kms_req_builder *drmdev_create_request_builder(struct drmdev *drmdev, uint32_t crtc_id) { - struct kms_req_builder *builder; - drmModeAtomicReq *req; - struct drm_crtc *crtc; - int64_t min_zpos; - bool supports_atomic_modesetting; - - ASSERT_NOT_NULL(drmdev); - assert(crtc_id != 0 && crtc_id != 0xFFFFFFFF); - - drmdev_lock(drmdev); - - for_each_crtc_in_drmdev(drmdev, crtc) { - if (crtc->id == crtc_id) { - break; - } - } - - if (crtc == NULL) { - LOG_ERROR("Invalid CRTC id: %" PRId32 "\n", crtc_id); - goto fail_unlock; - } - - builder = malloc(sizeof *builder); - if (builder == NULL) { - goto fail_unlock; - } - - supports_atomic_modesetting = drmdev->supports_atomic_modesetting; - - if (supports_atomic_modesetting) { - req = drmModeAtomicAlloc(); - if (req == NULL) { - goto fail_free_builder; - } - - // set the CRTC to active - drmModeAtomicAddProperty(req, crtc->id, crtc->ids.active, 1); - } else { - req = NULL; - } - - min_zpos = INT64_MAX; - BITSET_ZERO(builder->available_planes); - for (int i = 0; i < drmdev->n_planes; i++) { - struct drm_plane *plane = drmdev->planes + i; - - if (plane->possible_crtcs & crtc->bitmask) { - BITSET_SET(builder->available_planes, i); - if (plane->has_zpos && plane->min_zpos < min_zpos) { - min_zpos = plane->min_zpos; - } - } - } - - drmdev_unlock(drmdev); - - builder->n_refs = REFCOUNT_INIT_1; - builder->drmdev = drmdev_ref(drmdev); - // right now they're the same, but they might not be in the future. - builder->use_legacy = !supports_atomic_modesetting; - builder->supports_atomic = supports_atomic_modesetting; - builder->connector = NULL; - builder->crtc = crtc; - builder->req = req; - builder->next_zpos = min_zpos; - builder->n_layers = 0; - builder->has_mode = false; - builder->unset_mode = false; - return builder; - -fail_free_builder: - free(builder); - -fail_unlock: - drmdev_unlock(drmdev); - return NULL; -} - -static void kms_req_builder_destroy(struct kms_req_builder *builder) { - /// TODO: Is this complete? - for (int i = 0; i < builder->n_layers; i++) { - if (builder->layers[i].release_callback != NULL) { - builder->layers[i].release_callback(builder->layers[i].release_callback_userdata); - } - } - if (builder->req != NULL) { - drmModeAtomicFree(builder->req); - } - drmdev_unref(builder->drmdev); - free(builder); -} - -DEFINE_REF_OPS(kms_req_builder, n_refs) - -struct drmdev *kms_req_builder_get_drmdev(struct kms_req_builder *builder) { - return builder->drmdev; -} - -struct drm_crtc *kms_req_builder_get_crtc(struct kms_req_builder *builder) { - return builder->crtc; -} - -bool kms_req_builder_prefer_next_layer_opaque(struct kms_req_builder *builder) { - ASSERT_NOT_NULL(builder); - return builder->n_layers == 0; -} - -int kms_req_builder_set_mode(struct kms_req_builder *builder, const drmModeModeInfo *mode) { - ASSERT_NOT_NULL(builder); - ASSERT_NOT_NULL(mode); - builder->has_mode = true; - builder->mode = *mode; - return 0; -} - -int kms_req_builder_unset_mode(struct kms_req_builder *builder) { - ASSERT_NOT_NULL(builder); - assert(!builder->has_mode); - builder->unset_mode = true; - return 0; -} - -int kms_req_builder_set_connector(struct kms_req_builder *builder, uint32_t connector_id) { - struct drm_connector *conn; - - ASSERT_NOT_NULL(builder); - assert(DRM_ID_IS_VALID(connector_id)); - - for_each_connector_in_drmdev(builder->drmdev, conn) { - if (conn->id == connector_id) { - break; - } - } - - if (conn == NULL) { - LOG_ERROR("Could not find connector with id %" PRIu32 "\n", connector_id); - return EINVAL; - } - - builder->connector = conn; - return 0; -} - -int kms_req_builder_push_fb_layer( - struct kms_req_builder *builder, - const struct kms_fb_layer *layer, - kms_fb_release_cb_t release_callback, - kms_deferred_fb_release_cb_t deferred_release_callback, - void *userdata -) { - struct drm_plane *plane; - int64_t zpos; - bool has_zpos; - bool close_in_fence_fd_after; - int ok, index; - - ASSERT_NOT_NULL(builder); - ASSERT_NOT_NULL(layer); - ASSERT_NOT_NULL(release_callback); - ASSERT_EQUALS_MSG(deferred_release_callback, NULL, "deferred release callbacks are not supported right now."); - - if (builder->use_legacy && builder->supports_atomic && builder->n_layers > 1) { - // if we already have a first layer and we should use legacy modesetting even though the kernel driver - // supports atomic modesetting, return EINVAL. - // if the driver supports atomic modesetting, drmModeSetPlane will block for vblank, so we can't use it, - // and we can't use drmModeAtomicCommit for non-blocking multi-plane commits of course. - // For the first layer we can use drmModePageFlip though. - LOG_DEBUG("Can't do multi-plane commits when using legacy modesetting (and driver supports atomic modesetting).\n"); - return EINVAL; - } - - close_in_fence_fd_after = false; - if (builder->use_legacy && layer->has_in_fence_fd) { - LOG_DEBUG("Explicit fencing is not supported for legacy modesetting. Implicit fencing will be used instead.\n"); - close_in_fence_fd_after = true; - } - - // Index of our layer. - index = builder->n_layers; - - // If we should prefer a cursor plane, try to find one first. - plane = NULL; - if (layer->prefer_cursor) { - plane = allocate_plane( - // clang-format off - builder, - /* allow_primary */ false, - /* allow_overlay */ false, - /* allow_cursor */ true, - /* format */ layer->format, - /* modifier */ layer->has_modifier, layer->modifier, - /* zpos */ false, 0, 0, - /* rotation */ layer->has_rotation, layer->rotation, - /* id_range */ false, 0 - // clang-format on - ); - } - - /// 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, plane->max_alpha); - } - - if (plane->has_blend_mode && plane->supported_blend_modes[kNone_DrmBlendMode]) { - drmModeAtomicAddProperty(builder->req, plane_id, plane->ids.pixel_blend_mode, kNone_DrmBlendMode); - } - } - } - - // This should be done when we're sure we're not failing. - // Because on failure it would be the callers job to close the fd. - if (close_in_fence_fd_after) { - ok = close(layer->in_fence_fd); - if (ok < 0) { - ok = errno; - LOG_ERROR("Could not close layer in_fence_fd. close: %s\n", strerror(ok)); - goto fail_release_plane; - } - } - - /// TODO: Right now we're adding zpos, rotation to the atomic request unconditionally - /// when specified in the fb layer. Ideally we would check for updates - /// on commit and only add to the atomic request when zpos / rotation changed. - builder->n_layers++; - if (has_zpos) { - builder->next_zpos = zpos + 1; - } - builder->layers[index].layer = *layer; - builder->layers[index].plane_id = plane->id; - builder->layers[index].plane = plane; - builder->layers[index].set_zpos = has_zpos; - builder->layers[index].zpos = zpos; - builder->layers[index].set_rotation = layer->has_rotation; - builder->layers[index].rotation = layer->rotation; - builder->layers[index].release_callback = release_callback; - builder->layers[index].deferred_release_callback = deferred_release_callback; - builder->layers[index].release_callback_userdata = userdata; - return 0; - -fail_release_plane: - release_plane(builder, plane->id); - return ok; -} - -int kms_req_builder_push_zpos_placeholder_layer(struct kms_req_builder *builder, int64_t *zpos_out) { - ASSERT_NOT_NULL(builder); - ASSERT_NOT_NULL(zpos_out); - *zpos_out = builder->next_zpos++; - return 0; -} - -struct kms_req *kms_req_builder_build(struct kms_req_builder *builder) { - return (struct kms_req *) kms_req_builder_ref(builder); -} - -UNUSED struct kms_req *kms_req_ref(struct kms_req *req) { - return (struct kms_req *) kms_req_builder_ref((struct kms_req_builder *) req); -} - -UNUSED void kms_req_unref(struct kms_req *req) { - return kms_req_builder_unref((struct kms_req_builder *) req); -} - -UNUSED void kms_req_unrefp(struct kms_req **req) { - return kms_req_builder_unrefp((struct kms_req_builder **) req); -} - -UNUSED void kms_req_swap_ptrs(struct kms_req **oldp, struct kms_req *new) { - return kms_req_builder_swap_ptrs((struct kms_req_builder **) oldp, (struct kms_req_builder *) new); -} - -static bool drm_plane_is_active(struct drm_plane *plane) { - return plane->committed_state.fb_id != 0 && plane->committed_state.crtc_id != 0; -} - -static int -kms_req_commit_common(struct kms_req *req, bool blocking, kms_scanout_cb_t scanout_cb, void *userdata, void_callback_t destroy_cb) { - struct kms_req_builder *builder; - struct drm_mode_blob *mode_blob; - uint32_t flags; - bool internally_blocking; - bool update_mode; - int ok; - - internally_blocking = false; - update_mode = false; - mode_blob = NULL; - - ASSERT_NOT_NULL(req); - builder = (struct kms_req_builder *) req; - - drmdev_lock(builder->drmdev); - - if (builder->drmdev->master_fd < 0) { - LOG_ERROR("Commit requested, but drmdev doesn't have a DRM master fd right now.\n"); - ok = EBUSY; - goto fail_unlock; - } - - if (!is_drm_master(builder->drmdev->master_fd)) { - LOG_ERROR("Commit requested, but drmdev is paused right now.\n"); - ok = EBUSY; - goto fail_unlock; - } - - // only change the mode if the new mode differs from the old one - - /// TOOD: If this is not a standard mode reported by connector/CRTC, - /// is there a way to verify if it is valid? (maybe use DRM_MODE_ATOMIC_TEST) - - // this could be a single expression but this way you see a bit better what's going on. - // We need to upload the new mode blob if: - // - we have a new mode - // - and: we don't have an old mode - // - or: the old mode differs from the new mode - bool upload_mode = false; - if (builder->has_mode) { - if (!builder->crtc->committed_state.has_mode) { - upload_mode = true; - } else if (memcmp(&builder->crtc->committed_state.mode, &builder->mode, sizeof(drmModeModeInfo)) != 0) { - upload_mode = true; - } - } - - if (upload_mode) { - update_mode = true; - mode_blob = drm_mode_blob_new(builder->drmdev->fd, &builder->mode); - if (mode_blob == NULL) { - ok = EIO; - goto fail_unlock; - } - } else if (builder->unset_mode) { - update_mode = true; - mode_blob = NULL; - } - - if (builder->use_legacy) { - ASSERT_EQUALS(builder->layers[0].layer.dst_x, 0); - ASSERT_EQUALS(builder->layers[0].layer.dst_y, 0); - ASSERT_EQUALS(builder->layers[0].layer.dst_w, builder->mode.hdisplay); - ASSERT_EQUALS(builder->layers[0].layer.dst_h, builder->mode.vdisplay); - - /// TODO: Do we really need to assert this? - ASSERT_NOT_NULL(builder->connector); - - bool needs_set_crtc = update_mode; - - // check if the plane pixel format changed. - // that needs a drmModeSetCrtc for legacy KMS as well. - // get the current, committed fb for the plane, check if we have info - // for it (we can't use drmModeGetFB2 since that's not present on debian buster) - // and if we're not absolutely sure the formats match, set needs_set_crtc - // too. - if (!needs_set_crtc) { - struct kms_req_layer *layer = builder->layers + 0; - struct drm_plane *plane = layer->plane; - ASSERT_NOT_NULL(plane); - - if (plane->committed_state.has_format && plane->committed_state.format == layer->layer.format) { - needs_set_crtc = false; - } else { - needs_set_crtc = true; - } - -#ifdef DEBUG - drmModeFBPtr committed_fb = drmModeGetFB(builder->drmdev->master_fd, plane->committed_state.fb_id); - if (committed_fb == NULL) { - needs_set_crtc = true; - } else { - needs_set_crtc = true; - - list_for_each_entry(struct drm_fb, fb, &builder->drmdev->fbs, entry) { - if (fb->id == committed_fb->fb_id) { - ASSERT_EQUALS(fb->format, plane->committed_state.format); - - if (fb->format == layer->layer.format) { - needs_set_crtc = false; - } - } - - if (fb->id == layer->layer.drm_fb_id) { - ASSERT_EQUALS(fb->format, layer->layer.format); - } - } - } - - drmModeFreeFB(committed_fb); -#endif - } - - /// TODO: Handle {src,dst}_{x,y,w,h} here - /// TODO: Handle setting other properties as well - if (needs_set_crtc) { - /// TODO: Fetch new connector or current connector here since we seem to need it for drmModeSetCrtc - ok = drmModeSetCrtc( - builder->drmdev->master_fd, - builder->crtc->id, - builder->layers[0].layer.drm_fb_id, - 0, - 0, - (uint32_t[1]){ builder->connector->id }, - 1, - builder->unset_mode ? NULL : &builder->mode - ); - if (ok != 0) { - ok = errno; - LOG_ERROR("Could not commit display update. drmModeSetCrtc: %s\n", strerror(ok)); - goto fail_maybe_destroy_mode_blob; - } - - internally_blocking = true; - } else { - ok = drmModePageFlip( - builder->drmdev->master_fd, - builder->crtc->id, - builder->layers[0].layer.drm_fb_id, - DRM_MODE_PAGE_FLIP_EVENT, - kms_req_builder_ref(builder) - ); - if (ok != 0) { - ok = errno; - LOG_ERROR("Could not commit display update. drmModePageFlip: %s\n", strerror(ok)); - goto fail_unref_builder; - } - } - - // This should also be ensured by kms_req_builder_push_fb_layer - ASSERT_MSG( - !(builder->supports_atomic && builder->n_layers > 1), - "There can be at most one framebuffer layer when the KMS device supports atomic modesetting but we are " - "using legacy modesetting." - ); - - /// TODO: Call drmModeSetPlane for all other layers - /// TODO: Assert here - } else { - /// TODO: If we can do explicit fencing, don't use the page flip event. - /// TODO: Can we set OUT_FENCE_PTR even though we didn't set any IN_FENCE_FDs? - flags = DRM_MODE_PAGE_FLIP_EVENT | (blocking ? 0 : DRM_MODE_ATOMIC_NONBLOCK) | (update_mode ? DRM_MODE_ATOMIC_ALLOW_MODESET : 0); - - // All planes that are not used by us and are connected to our CRTC - // should be disabled. - { - int i; - BITSET_FOREACH_SET(i, builder->available_planes, 32) { - struct drm_plane *plane = builder->drmdev->planes + i; - - if (drm_plane_is_active(plane) && plane->committed_state.crtc_id == builder->crtc->id) { - drmModeAtomicAddProperty(builder->req, plane->id, plane->ids.crtc_id, 0); - drmModeAtomicAddProperty(builder->req, plane->id, plane->ids.fb_id, 0); - } - } - } - - if (builder->connector != NULL) { - // add the CRTC_ID property if that was explicitly set - drmModeAtomicAddProperty(builder->req, builder->connector->id, builder->connector->ids.crtc_id, builder->crtc->id); - } - - if (update_mode) { - if (mode_blob != NULL) { - drmModeAtomicAddProperty(builder->req, builder->crtc->id, builder->crtc->ids.mode_id, mode_blob->blob_id); - } else { - drmModeAtomicAddProperty(builder->req, builder->crtc->id, builder->crtc->ids.mode_id, 0); - } - } - - /// TODO: If we're on raspberry pi and only have one layer, we can do an async pageflip - /// on the primary plane to replace the next queued frame. (To do _real_ triple buffering - /// with fully decoupled framerate, potentially) - ok = drmModeAtomicCommit(builder->drmdev->master_fd, builder->req, flags, kms_req_builder_ref(builder)); - if (ok != 0) { - ok = errno; - LOG_ERROR("Could not commit display update. drmModeAtomicCommit: %s\n", strerror(ok)); - goto fail_unref_builder; - } - } - - // update struct drm_plane.committed_state for all planes - for (int i = 0; i < builder->n_layers; i++) { - struct drm_plane *plane = builder->layers[i].plane; - struct kms_req_layer *layer = builder->layers + i; - - plane->committed_state.crtc_id = builder->crtc->id; - plane->committed_state.fb_id = layer->layer.drm_fb_id; - plane->committed_state.src_x = layer->layer.src_x; - plane->committed_state.src_y = layer->layer.src_y; - plane->committed_state.src_w = layer->layer.src_w; - plane->committed_state.src_h = layer->layer.src_h; - plane->committed_state.crtc_x = layer->layer.dst_x; - plane->committed_state.crtc_y = layer->layer.dst_y; - plane->committed_state.crtc_w = layer->layer.dst_w; - plane->committed_state.crtc_h = layer->layer.dst_h; - - if (builder->layers[i].set_zpos) { - plane->committed_state.zpos = layer->zpos; - } - if (builder->layers[i].set_rotation) { - plane->committed_state.rotation = layer->rotation; - } - - plane->committed_state.has_format = true; - plane->committed_state.format = layer->layer.format; - - // builder->layers[i].plane->committed_state.alpha = layer->alpha; - // builder->layers[i].plane->committed_state.blend_mode = builder->layers[i].layer.blend_mode; - } - - // update struct drm_crtc.committed_state - if (update_mode) { - // destroy the old mode blob - if (builder->crtc->committed_state.mode_blob != NULL) { - /// TODO: Should we defer this to after the pageflip? - drm_mode_blob_destroy(builder->crtc->committed_state.mode_blob); - } - - // store the new mode - if (mode_blob != NULL) { - builder->crtc->committed_state.has_mode = true; - builder->crtc->committed_state.mode = builder->mode; - builder->crtc->committed_state.mode_blob = mode_blob; - } else { - builder->crtc->committed_state.has_mode = false; - builder->crtc->committed_state.mode_blob = NULL; - } - } - - // update struct drm_connector.committed_state - builder->connector->committed_state.crtc_id = builder->crtc->id; - // builder->connector->committed_state.encoder_id = 0; - - drmdev_set_scanout_callback_locked(builder->drmdev, builder->crtc->id, scanout_cb, userdata, destroy_cb); - - if (internally_blocking) { - uint64_t sequence = 0; - uint64_t ns = 0; - int ok; - - ok = drmCrtcGetSequence(builder->drmdev->fd, builder->crtc->id, &sequence, &ns); - if (ok != 0) { - ok = errno; - LOG_ERROR("Could not get vblank timestamp. drmCrtcGetSequence: %s\n", strerror(ok)); - goto fail_unref_builder; - } - - drmdev_on_page_flip_locked( - builder->drmdev->fd, - (unsigned int) sequence, - ns / 1000000000, - ns / 1000, - builder->crtc->id, - kms_req_ref(req) - ); - } else if (blocking) { - // handle the page-flip event here, rather than via the eventfd - ok = drmdev_on_modesetting_fd_ready_locked(builder->drmdev); - if (ok != 0) { - LOG_ERROR("Couldn't synchronously handle pageflip event.\n"); - goto fail_unset_scanout_callback; - } - } - - drmdev_unlock(builder->drmdev); - - return 0; - -fail_unset_scanout_callback: - builder->drmdev->per_crtc_state[builder->crtc->index].scanout_callback = NULL; - builder->drmdev->per_crtc_state[builder->crtc->index].destroy_callback = NULL; - builder->drmdev->per_crtc_state[builder->crtc->index].userdata = NULL; - goto fail_unlock; - -fail_unref_builder: - kms_req_builder_unref(builder); - -fail_maybe_destroy_mode_blob: - if (mode_blob != NULL) - drm_mode_blob_destroy(mode_blob); - -fail_unlock: - drmdev_unlock(builder->drmdev); - - return ok; -} - -void set_vblank_ns(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata) { - uint64_t *vblank_ns_out; - - ASSERT_NOT_NULL(userdata); - vblank_ns_out = userdata; - (void) drmdev; - - *vblank_ns_out = vblank_ns; -} - -int kms_req_commit_blocking(struct kms_req *req, uint64_t *vblank_ns_out) { - uint64_t vblank_ns; - int ok; - - vblank_ns = int64_to_uint64(-1); - ok = kms_req_commit_common(req, true, set_vblank_ns, &vblank_ns, NULL); - if (ok != 0) { - return ok; - } - - // make sure the vblank_ns is actually set - assert(vblank_ns != int64_to_uint64(-1)); - if (vblank_ns_out != NULL) { - *vblank_ns_out = vblank_ns; - } - - return 0; -} - -int kms_req_commit_nonblocking(struct kms_req *req, kms_scanout_cb_t scanout_cb, void *userdata, void_callback_t destroy_cb) { - return kms_req_commit_common(req, false, scanout_cb, userdata, destroy_cb); -} diff --git a/src/notifier_listener.c b/src/notifier_listener.c index 278b9749..3cb8adea 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 5ed3cbca..05a5c03c 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) { @@ -1578,7 +1826,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; @@ -1616,16 +1868,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 @@ -1684,6 +1944,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; } @@ -1948,7 +2227,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; @@ -1982,11 +2266,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: @@ -2066,11 +2354,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; @@ -2281,6 +2573,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; @@ -2338,9 +2631,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 14ea7287..8520d2e7 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 { @@ -1540,9 +1541,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/gstplayer.c b/src/plugins/gstplayer.c index 24962145..d1e1ac97 100644 --- a/src/plugins/gstplayer.c +++ b/src/plugins/gstplayer.c @@ -899,6 +899,8 @@ static void start_async(struct gstplayer *player, struct async_completer complet } static void on_bus_message(struct gstplayer *player, GstMessage *msg) { + PRAGMA_DIAGNOSTIC_PUSH + PRAGMA_DIAGNOSTIC_IGNORED("-Wswitch-enum") switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_EOS: on_eos_message(player, msg); @@ -1027,6 +1029,7 @@ static void on_bus_message(struct gstplayer *player, GstMessage *msg) { break; } + PRAGMA_DIAGNOSTIC_POP return; } @@ -1336,7 +1339,7 @@ struct gstplayer *gstplayer_new(struct flutterpi *flutterpi, const char *uri, vo TRACER_END(p->tracer, "gstplayer_new()"); - LOG_PLAYER_DEBUG(p, "gstplayer_new(\"%s\", %s): %s\n", uri ?: "", play_audio ? "with audio" : "without audio", p->is_live ? "live" : "not live"); + LOG_PLAYER_DEBUG(p, "gstplayer_new(\"%s\", %s): %s\n", uri ? uri : "", play_audio ? "with audio" : "without audio", p->is_live ? "live" : "not live"); return p; @@ -1684,7 +1687,7 @@ int gstplayer_step_backward(struct gstplayer *player) { void gstplayer_set_audio_balance(struct gstplayer *player, float balance) { if (player->audiopanorama) { - g_object_set(player->audiopanorama, "panorama", (gfloat) balance, NULL); + g_object_set(player->audiopanorama, "panorama", (double) balance, NULL); } } diff --git a/src/plugins/gstreamer_video_player/frame.c b/src/plugins/gstreamer_video_player/frame.c index 59062e0b..a6225f90 100644 --- a/src/plugins/gstreamer_video_player/frame.c +++ b/src/plugins/gstreamer_video_player/frame.c @@ -23,7 +23,8 @@ #define MAX_N_PLANES 4 #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; @@ -119,6 +120,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; @@ -139,7 +144,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])); @@ -161,6 +166,9 @@ static bool query_formats( *formats_out = modified_formats; return true; +fail_free_external_only: + free(external_only); + fail_free_modifiers: free(modifiers); @@ -563,6 +571,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. @@ -602,6 +615,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; @@ -653,7 +668,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; } @@ -692,7 +707,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; } @@ -701,6 +716,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; @@ -734,6 +751,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) { @@ -1114,9 +1132,9 @@ static struct video_frame *frame_new_egl_imported(struct frame_interface *interf 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/plugin.c b/src/plugins/gstreamer_video_player/plugin.c index 6b8be89c..c8d23a9d 100644 --- a/src/plugins/gstreamer_video_player/plugin.c +++ b/src/plugins/gstreamer_video_player/plugin.c @@ -55,7 +55,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) { @@ -117,7 +117,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); } @@ -205,7 +205,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; @@ -936,11 +936,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); @@ -1060,8 +1067,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."); @@ -1079,11 +1085,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; @@ -1098,11 +1105,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; @@ -1128,7 +1136,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; @@ -1161,7 +1170,8 @@ static int on_create_v2(const struct raw_std_value *arg, FlutterPlatformMessageR if (headers != NULL) { gst_structure_free(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; @@ -1176,49 +1186,65 @@ 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_headers; } } 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, /* play_video */ true, /* play_audio */ false, NULL); + } else if (uri != NULL) { + player = gstplayer_new_from_network(flutterpi, uri, format_hint, /* play_video */ true, /* play_audio */ false, NULL, headers); + + // player owns the headers now, except creation failed + if (player) { + headers = NULL; + } + } else if (pipeline != NULL) { + player = gstplayer_new_from_pipeline(flutterpi, uri, NULL); + } else { + UNREACHABLE(); + } - // gstplayer_new_from_network will construct a file:// URI out of the - // asset path internally. + if (asset != NULL) { free(asset); asset = NULL; - } else if (uri != NULL) { - player = gstplayer_new_from_network(flutterpi, uri, format_hint, /* play_video */ true, /* play_audio */ false, NULL, headers); + } + + if (package_name != NULL) { + free(package_name); + package_name = NULL; + } - // gstplayer_new_from_network will dup the uri internally. + if (uri != NULL) { free(uri); uri = NULL; - } else if (pipeline != NULL) { - player = gstplayer_new_from_pipeline(flutterpi, pipeline, NULL); + } + if (pipeline != NULL) { free(pipeline); - } else { - UNREACHABLE(); + pipeline = NULL; } 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; } @@ -1242,8 +1268,37 @@ 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) { + free(pipeline); + pipeline = NULL; + } + +fail_free_headers: + if (headers != NULL) { + gst_structure_free(headers); + headers = NULL; + } + +fail_free_uri: + if (uri) { + free(uri); + uri = NULL; + } + +fail_free_package_name: + if (package_name) { + free(package_name); + package_name = NULL; + } + +fail_free_asset: + if (asset) { + free(asset); + asset = NULL; + } + + return ok; } static int on_create_with_audio(const struct raw_std_value *arg, FlutterPlatformMessageResponseHandle *responsehandle) { @@ -1273,10 +1328,7 @@ static int on_create_with_audio(const struct raw_std_value *arg, FlutterPlatform asset = NULL; } else if (raw_std_value_is_string(arg)) { asset = raw_std_string_dup(arg); - if (asset == NULL) { - ok = ENOMEM; - goto fail_respond_error; - } + ASSERT_NOT_NULL(asset); } else { return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[0]` to be a String or null."); } @@ -1292,10 +1344,7 @@ static int on_create_with_audio(const struct raw_std_value *arg, FlutterPlatform package_name = NULL; } else if (raw_std_value_is_string(arg)) { package_name = raw_std_string_dup(arg); - if (package_name == NULL) { - ok = ENOMEM; - goto fail_respond_error; - } + ASSERT_NOT_NULL(package_name); } else { return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[1]` to be a String or null."); } @@ -1312,8 +1361,7 @@ static int on_create_with_audio(const struct raw_std_value *arg, FlutterPlatform } else if (raw_std_value_is_string(arg)) { uri = raw_std_string_dup(arg); if (uri == NULL) { - ok = ENOMEM; - goto fail_respond_error; + ASSERT_NOT_NULL(uri); } } else { return platch_respond_illegal_arg_std(responsehandle, "Expected `arg[2]` to be a String or null."); @@ -1364,6 +1412,8 @@ static int on_create_with_audio(const struct raw_std_value *arg, FlutterPlatform } char *key_str = raw_std_string_dup(key); + ASSERT_NOT_NULL(key_str); + gst_structure_take_string(headers, key_str, raw_std_string_dup(value)); free(key_str); } else { @@ -1382,40 +1432,48 @@ static int on_create_with_audio(const struct raw_std_value *arg, FlutterPlatform } if ((asset ? 1 : 0) + (uri ? 1 : 0) != 1) { - return platch_respond_illegal_arg_std(responsehandle, "Expected exactly one of `arg[0]` or `arg[2]` to be non-null."); + platch_respond_illegal_arg_std(responsehandle, "Expected exactly one of `arg[0]` or `arg[2]` to be non-null."); + goto fail_free_headers; } // Create our actual player (this doesn't initialize it) if (asset != NULL) { player = gstplayer_new_from_asset(flutterpi, asset, package_name, /* play_video */ true, /* play_audio */ true, NULL); + } else if (uri != NULL) { + player = gstplayer_new_from_network(flutterpi, uri, format_hint, /* play_video */ true, /* play_audio */ true, NULL, headers); - // gstplayer_new_from_network will construct a file:// URI out of the - // asset path internally. + if (player) { + headers = NULL; + } + } else { + UNREACHABLE(); + } + + if (asset != NULL) { free(asset); asset = NULL; - } else if (uri != NULL) { - player = gstplayer_new_from_network(flutterpi, uri, format_hint, /* play_video */ true, /* play_audio */ true, NULL, headers); + } - // gstplayer_new_from_network will dup the uri internally. + if (package_name != NULL) { + free(package_name); + package_name = NULL; + } + + if (uri != NULL) { free(uri); uri = 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_free_headers; } // 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; - goto fail_destroy_player; - } + ASSERT_NOT_NULL(meta); gstplayer_set_userdata(player, meta); @@ -1425,6 +1483,7 @@ static int on_create_with_audio(const struct raw_std_value *arg, FlutterPlatform // 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; } @@ -1433,12 +1492,27 @@ static int on_create_with_audio(const struct raw_std_value *arg, FlutterPlatform fail_remove_player: remove_player(meta); destroy_meta(meta); - -fail_destroy_player: gstplayer_destroy(player); -fail_respond_error: - return platch_respond_native_error_std(responsehandle, ok); +fail_free_headers: + if (headers != NULL) { + gst_structure_free(headers); + headers = NULL; + } + + if (uri != NULL) { + free(uri); + } + + if (package_name != NULL) { + free(package_name); + } + + 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..d94be0a2 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/drmdev.h" +#include "kms/resources.h" #include "render_surface.h" #include "surface.h" #include "tracer.h" @@ -73,7 +74,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. @@ -162,6 +163,8 @@ struct window { */ struct { struct drmdev *drmdev; + struct drm_resources *resources; + struct drm_connector *connector; struct drm_encoder *encoder; struct drm_crtc *crtc; @@ -171,6 +174,7 @@ struct window { const struct pointer_icon *pointer_icon; struct cursor_buffer *cursor; + } kms; /** @@ -300,7 +304,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 +321,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)); @@ -701,43 +705,23 @@ 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, drmModeModeInfo **mode_out, const char *desired_videomode ) { - struct drm_connector *connector; - struct drm_encoder *encoder; - struct drm_crtc *crtc; - drmModeModeInfo *mode, *mode_iter; int ok; // find any connected connector - for_each_connector_in_drmdev(drmdev, connector) { - if (connector->variable_state.connection_state == kConnected_DrmConnectionState) { + struct drm_connector *connector = NULL; + drm_resources_for_each_connector(resources, connector_it) { + if (connector_it->variable_state.connection_state == DRM_CONNSTATE_CONNECTED) { + connector = connector_it; break; } } @@ -747,9 +731,9 @@ static int select_mode( return EINVAL; } - mode = NULL; + drmModeModeInfo *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 +770,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 +796,10 @@ 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) { + struct drm_encoder *encoder = NULL; + drm_resources_for_each_encoder(resources, encoder_it) { + if (encoder_it->id == connector->committed_state.encoder_id) { + encoder = encoder_it; break; } } @@ -821,13 +807,14 @@ 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]) { + encoder = encoder_it; break; } } - if (encoder->encoder->possible_crtcs) { + if (encoder && encoder->possible_crtcs) { // only use this encoder if there's a crtc we can use with it break; } @@ -840,17 +827,20 @@ 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) { + struct drm_crtc *crtc = NULL; + drm_resources_for_each_crtc(resources, crtc_it) { + if (crtc_it->id == encoder->variable_state.crtc_id) { + crtc = crtc_it; break; } } // Otherwise use any CRTC that this encoder supports linking to if (crtc == NULL) { - 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 +888,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 +901,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 +922,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; } @@ -943,9 +935,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,11 +976,12 @@ 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)" ); 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; @@ -1080,48 +1072,66 @@ void kms_window_deinit(struct window *window) { struct frame { struct tracer *tracer; + struct drmdev *drmdev; struct kms_req *req; + struct frame_scheduler *scheduler; bool unset_should_apply_mode_on_commit; }; -UNUSED static void on_scanout(struct drmdev *drmdev, uint64_t vblank_ns, void *userdata) { - ASSERT_NOT_NULL(drmdev); - (void) drmdev; - (void) vblank_ns; - (void) userdata; +UNUSED static void on_scanout(uint64_t vblank_ns, void *userdata) { + ASSERT_NOT_NULL(userdata); + struct frame *frame = userdata; + + // This potentially presents a new frame. + frame_scheduler_on_scanout(frame->scheduler, true, vblank_ns); - /// TODO: What should we do here? + frame_scheduler_unref(frame->scheduler); + tracer_unref(frame->tracer); + kms_req_unref(frame->req); + drmdev_unref(frame->drmdev); + free(frame); } static void on_present_frame(void *userdata) { - struct frame *frame; + ASSERT_NOT_NULL(userdata); + struct frame *frame = userdata; + int ok; - ASSERT_NOT_NULL(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); - frame = userdata; + // 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_BEGIN(frame->tracer, "kms_req_commit_nonblocking"); - ok = kms_req_commit_blocking(frame->req, NULL); - TRACER_END(frame->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); + drmdev_unref(frame->drmdev); + free(frame); } - - 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; + struct frame *frame = userdata; + frame_scheduler_unref(frame->scheduler); tracer_unref(frame->tracer); kms_req_unref(frame->req); + drmdev_unref(frame->drmdev); free(frame); } @@ -1152,7 +1162,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; @@ -1206,7 +1216,7 @@ 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 ); @@ -1219,6 +1229,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,67 +1238,18 @@ 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; } 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: @@ -1392,14 +1354,13 @@ static struct render_surface *kms_window_get_render_surface_internal(struct wind // 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) { + drm_resources_for_each_plane(window->kms.resources, 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) { + if (plane->type != DRM_PRIMARY_PLANE && plane->type != DRM_OVERLAY_PLANE) { // We explicitly only look for primary and overlay planes. continue; } @@ -1428,11 +1389,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 +1715,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..1ff1f400 100644 --- a/src/window.h +++ b/src/window.h @@ -11,7 +11,7 @@ #define _FLUTTERPI_SRC_WINDOW_H #include "compositor_ng.h" -#include "modesetting.h" +#include "kms/resources.h" #include "pixel_format.h" #include "util/refcounting.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 }; @@ -65,6 +65,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 ); diff --git a/test/flutterpi_test.c b/test/flutterpi_test.c index 96236b78..a23cbd90 100644 --- a/test/flutterpi_test.c +++ b/test/flutterpi_test.c @@ -1,10 +1,10 @@ #include #include -void setUp() { +void setUp(void) { } -void tearDown() { +void tearDown(void) { } #define TEST_ASSERT_EQUAL_BOOL(expected, actual) \ @@ -50,7 +50,7 @@ void expect_parsed_cmdline_args_matches(int argc, char **argv, bool expected_res TEST_ASSERT_EQUAL_INT(expected.dummy_display_size.y, actual.dummy_display_size.y); } -static struct flutterpi_cmdline_args get_default_args() { +static struct flutterpi_cmdline_args get_default_args(void) { static char *engine_argv[1] = { "flutter-pi" }; return (struct flutterpi_cmdline_args){ @@ -74,7 +74,7 @@ static struct flutterpi_cmdline_args get_default_args() { }; } -void test_parse_orientation_arg() { +void test_parse_orientation_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); // test --orientation @@ -132,7 +132,7 @@ void test_parse_orientation_arg() { ); } -void test_parse_rotation_arg() { +void test_parse_rotation_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.has_rotation = true; @@ -149,7 +149,7 @@ void test_parse_rotation_arg() { expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--rotation", "270", BUNDLE_PATH }, true, expected); } -void test_parse_physical_dimensions_arg() { +void test_parse_physical_dimensions_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.bundle_path = NULL; @@ -164,7 +164,7 @@ void test_parse_physical_dimensions_arg() { expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--dimensions", "10,10", BUNDLE_PATH }, true, expected); } -void test_parse_pixel_format_arg() { +void test_parse_pixel_format_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.has_pixel_format = true; @@ -176,7 +176,7 @@ void test_parse_pixel_format_arg() { expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--pixelformat", "RGBA8888", BUNDLE_PATH }, true, expected); } -void test_parse_runtime_mode_arg() { +void test_parse_runtime_mode_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); // test --debug, --profile, --release @@ -194,14 +194,14 @@ void test_parse_runtime_mode_arg() { expect_parsed_cmdline_args_matches(3, (char *[]){ "flutter-pi", "--release", BUNDLE_PATH }, true, expected); } -void test_parse_bundle_path_arg() { +void test_parse_bundle_path_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.bundle_path = "/path/to/bundle/test"; expect_parsed_cmdline_args_matches(2, (char *[]){ "flutter-pi", "/path/to/bundle/test" }, true, expected); } -void test_parse_engine_arg() { +void test_parse_engine_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.engine_argc = 2; @@ -210,14 +210,14 @@ void test_parse_engine_arg() { expect_parsed_cmdline_args_matches(3, (char *[]){ "flutter-pi", BUNDLE_PATH, "engine-arg" }, true, expected); } -void test_parse_vulkan_arg() { +void test_parse_vulkan_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.use_vulkan = true; expect_parsed_cmdline_args_matches(3, (char *[]){ "flutter-pi", "--vulkan", BUNDLE_PATH }, true, expected); } -void test_parse_desired_videomode_arg() { +void test_parse_desired_videomode_arg(void) { struct flutterpi_cmdline_args expected = get_default_args(); expected.desired_videomode = "1920x1080"; @@ -227,7 +227,7 @@ void test_parse_desired_videomode_arg() { expect_parsed_cmdline_args_matches(4, (char *[]){ "flutter-pi", "--videomode", "1920x1080@60", BUNDLE_PATH }, true, expected); } -int main() { +int main(void) { UNITY_BEGIN(); RUN_TEST(test_parse_runtime_mode_arg); diff --git a/test/platformchannel_test.c b/test/platformchannel_test.c index 5047a082..6adb3e30 100644 --- a/test/platformchannel_test.c +++ b/test/platformchannel_test.c @@ -1,6 +1,7 @@ #define _GNU_SOURCE #include "platformchannel.h" +#include #include #include #include @@ -10,34 +11,37 @@ #define RAW_STD_BUF(...) (const struct raw_std_value *) ((const uint8_t[]){ __VA_ARGS__ }) #define AS_RAW_STD_VALUE(_value) ((const struct raw_std_value *) (_value)) +#define DBL_INFINITY ((double) INFINITY) +#define DBL_NAN ((double) NAN) + // required by Unity. -void setUp() { +void setUp(void) { } -void tearDown() { +void tearDown(void) { } -void test_raw_std_value_is_null() { +void test_raw_std_value_is_null(void) { TEST_ASSERT_TRUE(raw_std_value_is_null(RAW_STD_BUF(kStdNull))); TEST_ASSERT_FALSE(raw_std_value_is_null(RAW_STD_BUF(kStdTrue))); } -void test_raw_std_value_is_true() { +void test_raw_std_value_is_true(void) { TEST_ASSERT_TRUE(raw_std_value_is_true(RAW_STD_BUF(kStdTrue))); TEST_ASSERT_FALSE(raw_std_value_is_true(RAW_STD_BUF(kStdFalse))); } -void test_raw_std_value_is_false() { +void test_raw_std_value_is_false(void) { TEST_ASSERT_TRUE(raw_std_value_is_false(RAW_STD_BUF(kStdFalse))); TEST_ASSERT_FALSE(raw_std_value_is_false(RAW_STD_BUF(kStdTrue))); } -void test_raw_std_value_is_int32() { +void test_raw_std_value_is_int32(void) { TEST_ASSERT_TRUE(raw_std_value_is_int32(RAW_STD_BUF(kStdInt32))); TEST_ASSERT_FALSE(raw_std_value_is_int32(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_int32() { +void test_raw_std_value_as_int32(void) { // clang-format off alignas(16) uint8_t buffer[5] = { kStdInt32, @@ -53,12 +57,12 @@ void test_raw_std_value_as_int32() { TEST_ASSERT_EQUAL_INT32(-2003205, raw_std_value_as_int32(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_is_int64() { +void test_raw_std_value_is_int64(void) { TEST_ASSERT_TRUE(raw_std_value_is_int64(RAW_STD_BUF(kStdInt64))); TEST_ASSERT_FALSE(raw_std_value_is_int64(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_int64() { +void test_raw_std_value_as_int64(void) { // clang-format off alignas(16) uint8_t buffer[9] = { kStdInt64, @@ -74,12 +78,12 @@ void test_raw_std_value_as_int64() { TEST_ASSERT_EQUAL_INT64(-7998090352538419200, raw_std_value_as_int64(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_is_float64() { +void test_raw_std_value_is_float64(void) { TEST_ASSERT_TRUE(raw_std_value_is_float64(RAW_STD_BUF(kStdFloat64))); TEST_ASSERT_FALSE(raw_std_value_is_float64(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_float64() { +void test_raw_std_value_as_float64(void) { // clang-format off alignas(16) uint8_t buffer[] = { kStdFloat64, @@ -93,18 +97,18 @@ void test_raw_std_value_as_float64() { TEST_ASSERT_EQUAL_DOUBLE(M_PI, raw_std_value_as_float64(AS_RAW_STD_VALUE(buffer))); - value = INFINITY; + value = DBL_INFINITY; memcpy(buffer + 8, &value, sizeof(value)); - TEST_ASSERT_EQUAL_DOUBLE(INFINITY, raw_std_value_as_float64(AS_RAW_STD_VALUE(buffer))); + TEST_ASSERT_EQUAL_DOUBLE(DBL_INFINITY, raw_std_value_as_float64(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_is_string() { +void test_raw_std_value_is_string(void) { TEST_ASSERT_TRUE(raw_std_value_is_string(RAW_STD_BUF(kStdString))); TEST_ASSERT_FALSE(raw_std_value_is_string(RAW_STD_BUF(kStdNull))); } -void test_raw_std_string_dup() { +void test_raw_std_string_dup(void) { const char *str = "The quick brown fox jumps over the lazy dog."; // clang-format off @@ -129,7 +133,7 @@ void test_raw_std_string_dup() { free(str_duped); } -void test_raw_std_string_equals() { +void test_raw_std_string_equals(void) { const char *str = "The quick brown fox jumps over the lazy dog."; alignas(16) uint8_t buffer[1 + 1 + strlen(str)]; @@ -151,12 +155,12 @@ void test_raw_std_string_equals() { TEST_ASSERT_FALSE(raw_std_string_equals(AS_RAW_STD_VALUE(buffer), "anything")); } -void test_raw_std_value_is_uint8array() { +void test_raw_std_value_is_uint8array(void) { TEST_ASSERT_TRUE(raw_std_value_is_uint8array(RAW_STD_BUF(kStdUInt8Array))); TEST_ASSERT_FALSE(raw_std_value_is_uint8array(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_uint8array() { +void test_raw_std_value_as_uint8array(void) { // clang-format off alignas(16) uint8_t buffer[] = { kStdUInt8Array, @@ -179,12 +183,12 @@ void test_raw_std_value_as_uint8array() { TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, raw_std_value_as_uint8array(AS_RAW_STD_VALUE(buffer)), 4); } -void test_raw_std_value_is_int32array() { +void test_raw_std_value_is_int32array(void) { TEST_ASSERT_TRUE(raw_std_value_is_int32array(RAW_STD_BUF(kStdInt32Array))); TEST_ASSERT_FALSE(raw_std_value_is_int32array(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_int32array() { +void test_raw_std_value_as_int32array(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -216,12 +220,12 @@ void test_raw_std_value_as_int32array() { TEST_ASSERT_EQUAL_INT32_ARRAY(expected, raw_std_value_as_int32array(AS_RAW_STD_VALUE(buffer)), 2); } -void test_raw_std_value_is_int64array() { +void test_raw_std_value_is_int64array(void) { TEST_ASSERT_TRUE(raw_std_value_is_int64array(RAW_STD_BUF(kStdInt64Array))); TEST_ASSERT_FALSE(raw_std_value_is_int64array(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_int64array() { +void test_raw_std_value_as_int64array(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -251,12 +255,12 @@ void test_raw_std_value_as_int64array() { TEST_ASSERT_EQUAL_INT64_ARRAY(expected, raw_std_value_as_int64array(AS_RAW_STD_VALUE(buffer)), 2); } -void test_raw_std_value_is_float64array() { +void test_raw_std_value_is_float64array(void) { TEST_ASSERT_TRUE(raw_std_value_is_float64array(RAW_STD_BUF(kStdFloat64Array))); TEST_ASSERT_FALSE(raw_std_value_is_float64array(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_float64array() { +void test_raw_std_value_as_float64array(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -274,7 +278,7 @@ void test_raw_std_value_as_float64array() { // clang-format off double expected[] = { M_PI, - INFINITY, + DBL_INFINITY, }; // clang-format on @@ -288,12 +292,12 @@ void test_raw_std_value_as_float64array() { TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, raw_std_value_as_float64array(AS_RAW_STD_VALUE(buffer)), 2); } -void test_raw_std_value_is_list() { +void test_raw_std_value_is_list(void) { TEST_ASSERT_TRUE(raw_std_value_is_list(RAW_STD_BUF(kStdList))); TEST_ASSERT_FALSE(raw_std_value_is_list(RAW_STD_BUF(kStdNull))); } -void test_raw_std_list_get_size() { +void test_raw_std_list_get_size(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -325,12 +329,12 @@ void test_raw_std_list_get_size() { TEST_ASSERT_EQUAL_size_t(0xDEADBEEF, raw_std_list_get_size(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_is_map() { +void test_raw_std_value_is_map(void) { TEST_ASSERT_TRUE(raw_std_value_is_map(RAW_STD_BUF(kStdMap))); TEST_ASSERT_FALSE(raw_std_value_is_map(RAW_STD_BUF(kStdNull))); } -void test_raw_std_map_get_size() { +void test_raw_std_map_get_size(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -362,12 +366,12 @@ void test_raw_std_map_get_size() { TEST_ASSERT_EQUAL_size_t(0xDEADBEEF, raw_std_map_get_size(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_is_float32array() { +void test_raw_std_value_is_float32array(void) { TEST_ASSERT_TRUE(raw_std_value_is_float32array(RAW_STD_BUF(kStdFloat32Array))); TEST_ASSERT_FALSE(raw_std_value_is_float32array(RAW_STD_BUF(kStdNull))); } -void test_raw_std_value_as_float32array() { +void test_raw_std_value_as_float32array(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -385,7 +389,7 @@ void test_raw_std_value_as_float32array() { // clang-format off float expected[] = { M_PI, - INFINITY, + DBL_INFINITY, }; // clang-format on @@ -399,7 +403,7 @@ void test_raw_std_value_as_float32array() { TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, raw_std_value_as_float32array(AS_RAW_STD_VALUE(buffer)), 2); } -void test_raw_std_value_equals() { +void test_raw_std_value_equals(void) { TEST_ASSERT_TRUE(raw_std_value_equals(RAW_STD_BUF(kStdNull), RAW_STD_BUF(kStdNull))); TEST_ASSERT_FALSE(raw_std_value_equals(RAW_STD_BUF(kStdNull), RAW_STD_BUF(kStdTrue))); TEST_ASSERT_FALSE(raw_std_value_equals(RAW_STD_BUF(kStdTrue), RAW_STD_BUF(kStdFalse))); @@ -479,7 +483,7 @@ void test_raw_std_value_equals() { TEST_ASSERT_TRUE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); - f = NAN; + f = DBL_NAN; memcpy(rhs + 8, &f, sizeof(f)); TEST_ASSERT_FALSE(raw_std_value_equals(AS_RAW_STD_VALUE(lhs), AS_RAW_STD_VALUE(rhs))); @@ -689,7 +693,7 @@ void test_raw_std_value_equals() { double array[] = { M_PI, - INFINITY, + DBL_INFINITY, }; // clang-format on @@ -705,7 +709,7 @@ void test_raw_std_value_equals() { rhs[1] = 2; double array2[] = { 0.0, - INFINITY, + DBL_INFINITY, }; memcpy(rhs + 8, array2, sizeof(array2)); @@ -783,7 +787,7 @@ void test_raw_std_value_equals() { int64_t int64 = (int64_t) INT64_MIN; float floats[] = { M_PI, - INFINITY, + DBL_INFINITY, }; // clang-format on @@ -835,7 +839,7 @@ void test_raw_std_value_equals() { float array[] = { M_PI, - INFINITY, + DBL_INFINITY, }; // clang-format on @@ -852,7 +856,7 @@ void test_raw_std_value_equals() { // clang-format off float array2[] = { 0.0, - INFINITY, + DBL_INFINITY, }; // clang-format on memcpy(rhs + 4, array2, sizeof(array2)); @@ -861,18 +865,18 @@ void test_raw_std_value_equals() { } } -void test_raw_std_value_is_bool() { +void test_raw_std_value_is_bool(void) { TEST_ASSERT_FALSE(raw_std_value_is_bool(RAW_STD_BUF(kStdNull))); TEST_ASSERT_TRUE(raw_std_value_is_bool(RAW_STD_BUF(kStdTrue))); TEST_ASSERT_TRUE(raw_std_value_is_bool(RAW_STD_BUF(kStdFalse))); } -void test_raw_std_value_as_bool() { +void test_raw_std_value_as_bool(void) { TEST_ASSERT_TRUE(raw_std_value_as_bool(RAW_STD_BUF(kStdTrue))); TEST_ASSERT_FALSE(raw_std_value_as_bool(RAW_STD_BUF(kStdFalse))); } -void test_raw_std_value_is_int() { +void test_raw_std_value_is_int(void) { TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdNull))); TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdTrue))); TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdFalse))); @@ -881,7 +885,7 @@ void test_raw_std_value_is_int() { TEST_ASSERT_FALSE(raw_std_value_is_int(RAW_STD_BUF(kStdFloat64))); } -void test_raw_std_value_as_int() { +void test_raw_std_value_as_int(void) { // clang-format off alignas(16) uint8_t buffer[9] = { kStdInt32, @@ -905,7 +909,7 @@ void test_raw_std_value_as_int() { TEST_ASSERT_EQUAL_INT64(INT32_MIN, raw_std_value_as_int(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_get_size() { +void test_raw_std_value_get_size(void) { // clang-format off alignas(16) uint8_t buffer[] = { // type @@ -938,7 +942,7 @@ void test_raw_std_value_get_size() { TEST_ASSERT_EQUAL_size_t(0xDEADBEEF, raw_std_value_get_size(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_value_after() { +void test_raw_std_value_after(void) { // null { // clang-format off @@ -1263,7 +1267,7 @@ void test_raw_std_value_after() { } } -void test_raw_std_list_get_first_element() { +void test_raw_std_list_get_first_element(void) { // list const char *str = "The quick brown fox jumps over the lazy dog."; @@ -1286,7 +1290,7 @@ void test_raw_std_list_get_first_element() { ); } -void test_raw_std_list_get_nth_element() { +void test_raw_std_list_get_nth_element(void) { // list const char *str = "The quick brown fox jumps over the lazy dog."; @@ -1309,7 +1313,7 @@ void test_raw_std_list_get_nth_element() { ); } -void test_raw_std_map_get_first_key() { +void test_raw_std_map_get_first_key(void) { // map // clang-format off alignas(16) uint8_t buffer[] = { @@ -1340,31 +1344,31 @@ void test_raw_std_map_get_first_key() { TEST_ASSERT_EQUAL_PTR(buffer + 1 + 1 + 4, raw_std_map_get_first_key(AS_RAW_STD_VALUE(buffer))); } -void test_raw_std_map_find() { +void test_raw_std_map_find(void) { } -void test_raw_std_map_find_str() { +void test_raw_std_map_find_str(void) { } -void test_raw_std_value_check() { +void test_raw_std_value_check(void) { } -void test_raw_std_method_call_check() { +void test_raw_std_method_call_check(void) { } -void test_raw_std_method_call_response_check() { +void test_raw_std_method_call_response_check(void) { } -void test_raw_std_event_check() { +void test_raw_std_event_check(void) { } -void test_raw_std_method_call_get_method() { +void test_raw_std_method_call_get_method(void) { } -void test_raw_std_method_call_get_method_dup() { +void test_raw_std_method_call_get_method_dup(void) { } -void test_raw_std_method_call_get_arg() { +void test_raw_std_method_call_get_arg(void) { } int main(void) { diff --git a/third_party/flutter_embedder_header/engine.version b/third_party/flutter_embedder_header/engine.version index a7aa73a8..0def69d1 100644 --- a/third_party/flutter_embedder_header/engine.version +++ b/third_party/flutter_embedder_header/engine.version @@ -1 +1 @@ -d44b5a94c976fbb65815374f61ab5392a220b084 \ No newline at end of file +b8800d88be4866db1b15f8b954ab2573bba9960f diff --git a/third_party/flutter_embedder_header/include/flutter_embedder.h b/third_party/flutter_embedder_header/include/flutter_embedder_header/flutter_embedder.h similarity index 89% rename from third_party/flutter_embedder_header/include/flutter_embedder.h rename to third_party/flutter_embedder_header/include/flutter_embedder_header/flutter_embedder.h index 5cdba06e..069c813a 100644 --- a/third_party/flutter_embedder_header/include/flutter_embedder.h +++ b/third_party/flutter_embedder_header/include/flutter_embedder_header/flutter_embedder.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_EMBEDDER_H_ -#define FLUTTER_EMBEDDER_H_ +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_H_ #include #include @@ -25,9 +25,9 @@ // - Function signatures (names, argument counts, argument order, and argument // type) cannot change. // - The core behavior of existing functions cannot change. -// - Instead of nesting structures by value within another structure, prefer -// nesting by pointer. This ensures that adding members to the nested struct -// does not break the ABI of the parent struct. +// - Instead of nesting structures by value within another structure/union, +// prefer nesting by pointer. This ensures that adding members to the nested +// struct does not break the ABI of the parent struct/union. // - Instead of array of structures, prefer array of pointers to structures. // This ensures that array indexing does not break if members are added // to the structure. @@ -162,6 +162,8 @@ typedef enum { kFlutterSemanticsActionMoveCursorBackwardByWord = 1 << 20, /// Replace the current text in the text field. kFlutterSemanticsActionSetText = 1 << 21, + /// Request that the respective focusable widget gain input focus. + kFlutterSemanticsActionFocus = 1 << 22, } FlutterSemanticsAction; /// The set of properties that may be associated with a semantics node. @@ -236,6 +238,11 @@ typedef enum { kFlutterSemanticsFlagIsKeyboardKey = 1 << 24, /// Whether the semantics node represents a tristate checkbox in mixed state. kFlutterSemanticsFlagIsCheckStateMixed = 1 << 25, + /// The semantics node has the quality of either being "expanded" or + /// "collapsed". + kFlutterSemanticsFlagHasExpandedState = 1 << 26, + /// Whether a semantic node that hasExpandedState is currently expanded. + kFlutterSemanticsFlagIsExpanded = 1 << 27, } FlutterSemanticsFlag; typedef enum { @@ -261,6 +268,12 @@ typedef enum { typedef struct _FlutterEngine* FLUTTER_API_SYMBOL(FlutterEngine); +/// Unique identifier for views. +/// +/// View IDs are generated by the embedder and are +/// opaque to the engine; the engine does not interpret view IDs in any way. +typedef int64_t FlutterViewId; + typedef struct { /// horizontal scale factor double scaleX; @@ -676,9 +689,13 @@ typedef struct { FlutterMetalCommandQueueHandle present_command_queue; /// The callback that gets invoked when the engine requests the embedder for a /// texture to render to. + /// + /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. FlutterMetalTextureCallback get_next_drawable_callback; /// The callback presented to the embedder to present a fully populated metal /// texture to the user. + /// + /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. FlutterMetalPresentCallback present_drawable_callback; /// When the embedder specifies that a texture has a frame available, the /// engine will call this method (on an internal engine managed thread) so @@ -748,6 +765,11 @@ typedef struct { /// The queue family index of the VkQueue supplied in the next field. uint32_t queue_family_index; /// VkQueue handle. + /// The queue should not be used without protection from a mutex to make sure + /// it is not used simultaneously with other threads. That mutex should match + /// the one injected via the |get_instance_proc_address_callback|. + /// There is a proposal to remove the need for the mutex at + /// https://github.com/flutter/flutter/issues/134573. FlutterVulkanQueueHandle queue; /// The number of instance extensions available for enumerating in the next /// field. @@ -771,6 +793,12 @@ typedef struct { /// For example: VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME const char** enabled_device_extensions; /// The callback invoked when resolving Vulkan function pointers. + /// At a bare minimum this should be used to swap out any calls that operate + /// on vkQueue's for threadsafe variants that obtain locks for their duration. + /// The functions to swap out are "vkQueueSubmit" and "vkQueueWaitIdle". An + /// example of how to do that can be found in the test + /// "EmbedderTest.CanSwapOutVulkanCalls" unit-test in + /// //shell/platform/embedder/tests/embedder_vk_unittests.cc. FlutterVulkanInstanceProcAddressCallback get_instance_proc_address_callback; /// The callback invoked when the engine requests a VkImage from the embedder /// for rendering the next frame. @@ -805,6 +833,11 @@ typedef struct { }; } FlutterRendererConfig; +/// Display refers to a graphics hardware system consisting of a framebuffer, +/// typically a monitor or a screen. This ID is unique per display and is +/// stable until the Flutter application restarts. +typedef uint64_t FlutterEngineDisplayId; + typedef struct { /// The size of this struct. Must be sizeof(FlutterWindowMetricsEvent). size_t struct_size; @@ -826,8 +859,108 @@ typedef struct { double physical_view_inset_bottom; /// Left inset of window. double physical_view_inset_left; + /// The identifier of the display the view is rendering on. + FlutterEngineDisplayId display_id; + /// The view that this event is describing. + int64_t view_id; } FlutterWindowMetricsEvent; +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterAddViewResult). + size_t struct_size; + + /// True if the add view operation succeeded. + bool added; + + /// The |FlutterAddViewInfo.user_data|. + void* user_data; +} FlutterAddViewResult; + +/// The callback invoked by the engine when the engine has attempted to add a +/// view. +/// +/// The |FlutterAddViewResult| is only guaranteed to be valid during this +/// callback. +typedef void (*FlutterAddViewCallback)(const FlutterAddViewResult* result); + +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterAddViewInfo). + size_t struct_size; + + /// The identifier for the view to add. This must be unique. + FlutterViewId view_id; + + /// The view's properties. + /// + /// The metric's |view_id| must match this struct's |view_id|. + const FlutterWindowMetricsEvent* view_metrics; + + /// A baton that is not interpreted by the engine in any way. It will be given + /// back to the embedder in |add_view_callback|. Embedder resources may be + /// associated with this baton. + void* user_data; + + /// Called once the engine has attempted to add the view. This callback is + /// required. + /// + /// The embedder/app must not use the view until the callback is invoked with + /// an `added` value of `true`. + /// + /// This callback is invoked on an internal engine managed thread. Embedders + /// must re-thread if necessary. + FlutterAddViewCallback add_view_callback; +} FlutterAddViewInfo; + +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterRemoveViewResult). + size_t struct_size; + + /// True if the remove view operation succeeded. + bool removed; + + /// The |FlutterRemoveViewInfo.user_data|. + void* user_data; +} FlutterRemoveViewResult; + +/// The callback invoked by the engine when the engine has attempted to remove +/// a view. +/// +/// The |FlutterRemoveViewResult| is only guaranteed to be valid during this +/// callback. +typedef void (*FlutterRemoveViewCallback)( + const FlutterRemoveViewResult* /* result */); + +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterRemoveViewInfo). + size_t struct_size; + + /// The identifier for the view to remove. + /// + /// The implicit view cannot be removed if it is enabled. + FlutterViewId view_id; + + /// A baton that is not interpreted by the engine in any way. + /// It will be given back to the embedder in |remove_view_callback|. + /// Embedder resources may be associated with this baton. + void* user_data; + + /// Called once the engine has attempted to remove the view. + /// This callback is required. + /// + /// The embedder must not destroy the underlying surface until the callback is + /// invoked with a `removed` value of `true`. + /// + /// This callback is invoked on an internal engine managed thread. + /// Embedders must re-thread if necessary. + /// + /// The |result| argument will be deallocated when the callback returns. + FlutterRemoveViewCallback remove_view_callback; +} FlutterRemoveViewInfo; + /// The phase of the pointer event. typedef enum { kCancel, @@ -896,7 +1029,6 @@ typedef enum { kFlutterPointerSignalKindScroll, kFlutterPointerSignalKindScrollInertiaCancel, kFlutterPointerSignalKindScale, - kFlutterPointerSignalKindStylusAuxiliaryAction, } FlutterPointerSignalKind; typedef struct { @@ -935,6 +1067,8 @@ typedef struct { double scale; /// The rotation of the pan/zoom in radians, where 0.0 is the initial angle. double rotation; + /// The identifier of the view that received the pointer event. + FlutterViewId view_id; } FlutterPointerEvent; typedef enum { @@ -943,6 +1077,14 @@ typedef enum { kFlutterKeyEventTypeRepeat, } FlutterKeyEventType; +typedef enum { + kFlutterKeyEventDeviceTypeKeyboard = 1, + kFlutterKeyEventDeviceTypeDirectionalPad, + kFlutterKeyEventDeviceTypeGamepad, + kFlutterKeyEventDeviceTypeJoystick, + kFlutterKeyEventDeviceTypeHdmi, +} FlutterKeyEventDeviceType; + /// A structure to represent a key event. /// /// Sending `FlutterKeyEvent` via `FlutterEngineSendKeyEvent` results in a @@ -1006,6 +1148,8 @@ typedef struct { /// An event being synthesized means that the `timestamp` might greatly /// deviate from the actual time when the event occurs physically. bool synthesized; + /// The source device for the key event. + FlutterKeyEventDeviceType device_type; } FlutterKeyEvent; typedef void (*FlutterKeyEventCallback)(bool /* handled */, @@ -1049,6 +1193,57 @@ typedef int64_t FlutterPlatformViewIdentifier; FLUTTER_EXPORT extern const int32_t kFlutterSemanticsNodeIdBatchEnd; +// The enumeration of possible string attributes that affect how assistive +// technologies announce a string. +// +// See dart:ui's implementers of the StringAttribute abstract class. +typedef enum { + // Indicates the string should be announced character by character. + kSpellOut, + // Indicates the string should be announced using the specified locale. + kLocale, +} FlutterStringAttributeType; + +// Indicates the assistive technology should announce out the string character +// by character. +// +// See dart:ui's SpellOutStringAttribute. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterSpellOutStringAttribute). + size_t struct_size; +} FlutterSpellOutStringAttribute; + +// Indicates the assistive technology should announce the string using the +// specified locale. +// +// See dart:ui's LocaleStringAttribute. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterLocaleStringAttribute). + size_t struct_size; + // The locale of this attribute. + const char* locale; +} FlutterLocaleStringAttribute; + +// Indicates how the assistive technology should treat the string. +// +// See dart:ui's StringAttribute. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterStringAttribute). + size_t struct_size; + // The position this attribute starts. + size_t start; + // The next position after the attribute ends. + size_t end; + /// The type of the attribute described by the subsequent union. + FlutterStringAttributeType type; + union { + // Indicates the string should be announced character by character. + const FlutterSpellOutStringAttribute* spell_out; + // Indicates the string should be announced using the specified locale. + const FlutterLocaleStringAttribute* locale; + }; +} FlutterStringAttribute; + /// A node that represents some semantic data. /// /// The semantics tree is maintained during the semantics phase of the pipeline @@ -1200,6 +1395,31 @@ typedef struct { FlutterPlatformViewIdentifier platform_view_id; /// A textual tooltip attached to the node. const char* tooltip; + // The number of string attributes associated with the `label`. + size_t label_attribute_count; + // Array of string attributes associated with the `label`. + // Has length `label_attribute_count`. + const FlutterStringAttribute** label_attributes; + // The number of string attributes associated with the `hint`. + size_t hint_attribute_count; + // Array of string attributes associated with the `hint`. + // Has length `hint_attribute_count`. + const FlutterStringAttribute** hint_attributes; + // The number of string attributes associated with the `value`. + size_t value_attribute_count; + // Array of string attributes associated with the `value`. + // Has length `value_attribute_count`. + const FlutterStringAttribute** value_attributes; + // The number of string attributes associated with the `increased_value`. + size_t increased_value_attribute_count; + // Array of string attributes associated with the `increased_value`. + // Has length `increased_value_attribute_count`. + const FlutterStringAttribute** increased_value_attributes; + // The number of string attributes associated with the `decreased_value`. + size_t decreased_value_attribute_count; + // Array of string attributes associated with the `decreased_value`. + // Has length `decreased_value_attribute_count`. + const FlutterStringAttribute** decreased_value_attributes; } FlutterSemanticsNode2; /// `FlutterSemanticsCustomAction` ID used as a sentinel to signal the end of a @@ -1311,6 +1531,20 @@ typedef void (*FlutterUpdateSemanticsCallback2)( const FlutterSemanticsUpdate2* /* semantics update */, void* /* user data*/); +/// An update to whether a message channel has a listener set or not. +typedef struct { + // The size of the struct. Must be sizeof(FlutterChannelUpdate). + size_t struct_size; + /// The name of the channel. + const char* channel; + /// True if a listener has been set, false if one has been cleared. + bool listening; +} FlutterChannelUpdate; + +typedef void (*FlutterChannelUpdateCallback)( + const FlutterChannelUpdate* /* channel update */, + void* /* user data */); + typedef struct _FlutterTaskRunner* FlutterTaskRunner; typedef struct { @@ -1548,6 +1782,9 @@ typedef struct { size_t struct_size; /// The size of the render target the engine expects to render into. FlutterSize size; + /// The identifier for the view that the engine will use this backing store to + /// render into. + FlutterViewId view_id; } FlutterBackingStoreConfig; typedef enum { @@ -1558,6 +1795,27 @@ typedef enum { kFlutterLayerContentTypePlatformView, } FlutterLayerContentType; +/// A region represented by a collection of non-overlapping rectangles. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterRegion). + size_t struct_size; + /// Number of rectangles in the region. + size_t rects_count; + /// The rectangles that make up the region. + FlutterRect* rects; +} FlutterRegion; + +/// Contains additional information about the backing store provided +/// during presentation to the embedder. +typedef struct { + size_t struct_size; + + /// The area of the backing store that contains Flutter contents. Pixels + /// outside of this area are transparent and the embedder may choose not + /// to render them. Coordinates are in physical pixels. + FlutterRegion* paint_region; +} FlutterBackingStorePresentInfo; + typedef struct { /// This size of this struct. Must be sizeof(FlutterLayer). size_t struct_size; @@ -1577,8 +1835,34 @@ typedef struct { FlutterPoint offset; /// The size of the layer (in physical pixels). FlutterSize size; + + /// Extra information for the backing store that the embedder may + /// use during presentation. + FlutterBackingStorePresentInfo* backing_store_present_info; + + // Time in nanoseconds at which this frame is scheduled to be presented. 0 if + // not known. See FlutterEngineGetCurrentTime(). + uint64_t presentation_time; } FlutterLayer; +typedef struct { + /// The size of this struct. + /// Must be sizeof(FlutterPresentViewInfo). + size_t struct_size; + + /// The identifier of the target view. + FlutterViewId view_id; + + /// The layers that should be composited onto the view. + const FlutterLayer** layers; + + /// The count of layers. + size_t layers_count; + + /// The |FlutterCompositor.user_data|. + void* user_data; +} FlutterPresentViewInfo; + typedef bool (*FlutterBackingStoreCreateCallback)( const FlutterBackingStoreConfig* config, FlutterBackingStore* backing_store_out, @@ -1592,13 +1876,20 @@ typedef bool (*FlutterLayersPresentCallback)(const FlutterLayer** layers, size_t layers_count, void* user_data); +/// The callback invoked when the embedder should present to a view. +/// +/// The |FlutterPresentViewInfo| will be deallocated once the callback returns. +typedef bool (*FlutterPresentViewCallback)( + const FlutterPresentViewInfo* /* present info */); + typedef struct { /// This size of this struct. Must be sizeof(FlutterCompositor). size_t struct_size; /// A baton that in not interpreted by the engine in any way. If it passed /// back to the embedder in `FlutterCompositor.create_backing_store_callback`, - /// `FlutterCompositor.collect_backing_store_callback` and - /// `FlutterCompositor.present_layers_callback` + /// `FlutterCompositor.collect_backing_store_callback`, + /// `FlutterCompositor.present_layers_callback`, and + /// `FlutterCompositor.present_view_callback`. void* user_data; /// A callback invoked by the engine to obtain a backing store for a specific /// `FlutterLayer`. @@ -1607,15 +1898,38 @@ typedef struct { /// `FlutterBackingStore::struct_size` when specifying a new backing store to /// the engine. This only matters if the embedder expects to be used with /// engines older than the version whose headers it used during compilation. + /// + /// The callback should return true if the operation was successful. FlutterBackingStoreCreateCallback create_backing_store_callback; /// A callback invoked by the engine to release the backing store. The /// embedder may collect any resources associated with the backing store. + /// + /// The callback should return true if the operation was successful. FlutterBackingStoreCollectCallback collect_backing_store_callback; /// Callback invoked by the engine to composite the contents of each layer - /// onto the screen. + /// onto the implicit view. + /// + /// DEPRECATED: Use `present_view_callback` to support multiple views. + /// If this callback is provided, `FlutterEngineAddView` and + /// `FlutterEngineRemoveView` should not be used. + /// + /// Only one of `present_layers_callback` and `present_view_callback` may be + /// provided. Providing both is an error and engine initialization will + /// terminate. + /// + /// The callback should return true if the operation was successful. FlutterLayersPresentCallback present_layers_callback; /// Avoid caching backing stores provided by this compositor. bool avoid_backing_store_cache; + /// Callback invoked by the engine to composite the contents of each layer + /// onto the specified view. + /// + /// Only one of `present_layers_callback` and `present_view_callback` may be + /// provided. Providing both is an error and engine initialization will + /// terminate. + /// + /// The callback should return true if the operation was successful. + FlutterPresentViewCallback present_view_callback; } FlutterCompositor; typedef struct { @@ -1654,11 +1968,6 @@ typedef const FlutterLocale* (*FlutterComputePlatformResolvedLocaleCallback)( const FlutterLocale** /* supported_locales*/, size_t /* Number of locales*/); -/// Display refers to a graphics hardware system consisting of a framebuffer, -/// typically a monitor or a screen. This ID is unique per display and is -/// stable until the Flutter application restarts. -typedef uint64_t FlutterEngineDisplayId; - typedef struct { /// This size of this struct. Must be sizeof(FlutterDisplay). size_t struct_size; @@ -1674,6 +1983,16 @@ typedef struct { /// This represents the refresh period in frames per second. This value may be /// zero if the device is not running or unavailable or unknown. double refresh_rate; + + /// The width of the display, in physical pixels. + size_t width; + + /// The height of the display, in physical pixels. + size_t height; + + /// The pixel ratio of the display, which is used to convert physical pixels + /// to logical pixels. + double device_pixel_ratio; } FlutterEngineDisplay; /// The update type parameter that is passed to @@ -1920,6 +2239,10 @@ typedef struct { /// `update_semantics_callback`, and /// `update_semantics_callback2` may be provided; the others /// should be set to null. + /// + /// This callback is incompatible with multiple views. If this + /// callback is provided, `FlutterEngineAddView` and + /// `FlutterEngineRemoveView` should not be used. FlutterUpdateSemanticsNodeCallback update_semantics_node_callback; /// The legacy callback invoked by the engine in order to give the embedder /// the chance to respond to updates to semantics custom actions from the Dart @@ -1936,6 +2259,10 @@ typedef struct { /// `update_semantics_callback`, and /// `update_semantics_callback2` may be provided; the others /// should be set to null. + /// + /// This callback is incompatible with multiple views. If this + /// callback is provided, `FlutterEngineAddView` and + /// `FlutterEngineRemoveView` should not be used. FlutterUpdateSemanticsCustomActionCallback update_semantics_custom_action_callback; /// Path to a directory used to store data that is cached across runs of a @@ -2085,6 +2412,10 @@ typedef struct { /// `update_semantics_callback`, and /// `update_semantics_callback2` may be provided; the others /// must be set to null. + /// + /// This callback is incompatible with multiple views. If this + /// callback is provided, `FlutterEngineAddView` and + /// `FlutterEngineRemoveView` should not be used. FlutterUpdateSemanticsCallback update_semantics_callback; /// The callback invoked by the engine in order to give the embedder the @@ -2098,10 +2429,17 @@ typedef struct { /// and `update_semantics_callback2` may be provided; the others must be set /// to null. FlutterUpdateSemanticsCallback2 update_semantics_callback2; + + /// The callback invoked by the engine in response to a channel listener + /// being registered on the framework side. The callback is invoked from + /// a task posted to the platform thread. + FlutterChannelUpdateCallback channel_update_callback; } FlutterProjectArgs; #ifndef FLUTTER_ENGINE_NO_PROTOTYPES +// NOLINTBEGIN(google-objc-function-naming) + //------------------------------------------------------------------------------ /// @brief Creates the necessary data structures to launch a Flutter Dart /// application in AOT mode. The data may only be collected after @@ -2243,6 +2581,63 @@ FLUTTER_EXPORT FlutterEngineResult FlutterEngineRunInitialized( FLUTTER_API_SYMBOL(FlutterEngine) engine); +//------------------------------------------------------------------------------ +/// @brief Adds a view. +/// +/// This is an asynchronous operation. The view should not be used +/// until the |info.add_view_callback| is invoked with an |added| +/// value of true. The embedder should prepare resources in advance +/// but be ready to clean up on failure. +/// +/// A frame is scheduled if the operation succeeds. +/// +/// The callback is invoked on a thread managed by the engine. The +/// embedder should re-thread if needed. +/// +/// Attempting to add the implicit view will fail and will return +/// kInvalidArguments. Attempting to add a view with an already +/// existing view ID will fail, and |info.add_view_callback| will be +/// invoked with an |added| value of false. +/// +/// @param[in] engine A running engine instance. +/// @param[in] info The add view arguments. This can be deallocated +/// once |FlutterEngineAddView| returns, before +/// |add_view_callback| is invoked. +/// +/// @return The result of *starting* the asynchronous operation. If +/// `kSuccess`, the |add_view_callback| will be invoked. +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineAddView(FLUTTER_API_SYMBOL(FlutterEngine) + engine, + const FlutterAddViewInfo* info); + +//------------------------------------------------------------------------------ +/// @brief Removes a view. +/// +/// This is an asynchronous operation. The view's resources must not +/// be cleaned up until |info.remove_view_callback| is invoked with +/// a |removed| value of true. +/// +/// The callback is invoked on a thread managed by the engine. The +/// embedder should re-thread if needed. +/// +/// Attempting to remove the implicit view will fail and will return +/// kInvalidArguments. Attempting to remove a view with a +/// non-existent view ID will fail, and |info.remove_view_callback| +/// will be invoked with a |removed| value of false. +/// +/// @param[in] engine A running engine instance. +/// @param[in] info The remove view arguments. This can be deallocated +/// once |FlutterEngineRemoveView| returns, before +/// |remove_view_callback| is invoked. +/// +/// @return The result of *starting* the asynchronous operation. If +/// `kSuccess`, the |remove_view_callback| will be invoked. +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineRemoveView(FLUTTER_API_SYMBOL(FlutterEngine) + engine, + const FlutterRemoveViewInfo* info); + FLUTTER_EXPORT FlutterEngineResult FlutterEngineSendWindowMetricsEvent( FLUTTER_API_SYMBOL(FlutterEngine) engine, @@ -2913,6 +3308,12 @@ typedef FlutterEngineResult (*FlutterEngineSetNextFrameCallbackFnPtr)( FLUTTER_API_SYMBOL(FlutterEngine) engine, VoidCallback callback, void* user_data); +typedef FlutterEngineResult (*FlutterEngineAddViewFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterAddViewInfo* info); +typedef FlutterEngineResult (*FlutterEngineRemoveViewFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterRemoveViewInfo* info); /// Function-pointer-based versions of the APIs above. typedef struct { @@ -2959,6 +3360,8 @@ typedef struct { FlutterEngineNotifyDisplayUpdateFnPtr NotifyDisplayUpdate; FlutterEngineScheduleFrameFnPtr ScheduleFrame; FlutterEngineSetNextFrameCallbackFnPtr SetNextFrameCallback; + FlutterEngineAddViewFnPtr AddView; + FlutterEngineRemoveViewFnPtr RemoveView; } FlutterEngineProcTable; //------------------------------------------------------------------------------ @@ -2973,8 +3376,10 @@ FLUTTER_EXPORT FlutterEngineResult FlutterEngineGetProcAddresses( FlutterEngineProcTable* table); +// NOLINTEND(google-objc-function-naming) + #if defined(__cplusplus) } // extern "C" #endif -#endif // FLUTTER_EMBEDDER_H_ +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_H_ diff --git a/src/util/dynarray.h b/third_party/mesa3d/include/mesa3d/dynarray.h similarity index 99% rename from src/util/dynarray.h rename to third_party/mesa3d/include/mesa3d/dynarray.h index 2d9f2101..220ea57f 100644 --- a/src/util/dynarray.h +++ b/third_party/mesa3d/include/mesa3d/dynarray.h @@ -32,8 +32,6 @@ #include #include -#include "macros.h" - #ifdef __cplusplus extern "C" { #endif diff --git a/tools/roll_embedder_header.sh b/tools/roll_embedder_header.sh new file mode 100755 index 00000000..518f0188 --- /dev/null +++ b/tools/roll_embedder_header.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +ENGINE_VERSION="$(curl -s https://raw.githubusercontent.com/flutter/flutter/stable/bin/internal/engine.version)" + +if [ ! -f "third_party/flutter_embedder_header/include/flutter_embedder.h" ]; then + echo "Incorrect working directory. Please launch this script with the flutter-pi repo root as the working directory, using 'tools/roll_embedder_header.sh'.". + exit 1 +fi + +curl -o third_party/flutter_embedder_header/include/flutter_embedder.h "https://raw.githubusercontent.com/flutter/engine/$ENGINE_VERSION/shell/platform/embedder/embedder.h" +echo "$ENGINE_VERSION" > third_party/flutter_embedder_header/engine.version