diff --git a/src/backends/native/meta-crtc-kms.h b/src/backends/native/meta-crtc-kms.h index bd80835f9..6a9ce3d51 100644 --- a/src/backends/native/meta-crtc-kms.h +++ b/src/backends/native/meta-crtc-kms.h @@ -35,6 +35,7 @@ #include "backends/native/meta-kms-update.h" #define META_TYPE_CRTC_KMS (meta_crtc_kms_get_type ()) +META_EXPORT_TEST G_DECLARE_FINAL_TYPE (MetaCrtcKms, meta_crtc_kms, META, CRTC_KMS, MetaCrtcNative) @@ -58,6 +59,7 @@ void meta_crtc_kms_set_mode (MetaCrtcKms *crtc_kms, void meta_crtc_kms_set_is_underscanning (MetaCrtcKms *crtc_kms, gboolean is_underscanning); +META_EXPORT_TEST MetaKmsCrtc * meta_crtc_kms_get_kms_crtc (MetaCrtcKms *crtc_kms); GArray * meta_crtc_kms_get_modifiers (MetaCrtcKms *crtc_kms, diff --git a/src/backends/native/meta-device-pool.h b/src/backends/native/meta-device-pool.h index 5ebe82d10..ee417b2e5 100644 --- a/src/backends/native/meta-device-pool.h +++ b/src/backends/native/meta-device-pool.h @@ -42,6 +42,7 @@ typedef enum _MetaDeviceFileTags typedef struct _MetaDeviceFile MetaDeviceFile; typedef struct _MetaDevicePool MetaDevicePool; +META_EXPORT_TEST int meta_device_file_get_fd (MetaDeviceFile *device_file); const char * meta_device_file_get_path (MetaDeviceFile *device_file); diff --git a/src/backends/native/meta-drm-buffer.h b/src/backends/native/meta-drm-buffer.h index 5ce6862a7..74542845e 100644 --- a/src/backends/native/meta-drm-buffer.h +++ b/src/backends/native/meta-drm-buffer.h @@ -48,6 +48,7 @@ int meta_drm_buffer_export_fd (MetaDrmBuffer *buffer, gboolean meta_drm_buffer_ensure_fb_id (MetaDrmBuffer *buffer, GError **error); +META_EXPORT_TEST uint32_t meta_drm_buffer_get_fb_id (MetaDrmBuffer *buffer); uint32_t meta_drm_buffer_get_handle (MetaDrmBuffer *buffer); diff --git a/src/backends/native/meta-onscreen-native.h b/src/backends/native/meta-onscreen-native.h index 3a85ace26..8ea7ec254 100644 --- a/src/backends/native/meta-onscreen-native.h +++ b/src/backends/native/meta-onscreen-native.h @@ -27,8 +27,10 @@ #include "backends/native/meta-backend-native-types.h" #include "clutter/clutter.h" #include "cogl/cogl.h" +#include "core/util-private.h" #define META_TYPE_ONSCREEN_NATIVE (meta_onscreen_native_get_type ()) +META_EXPORT_TEST G_DECLARE_FINAL_TYPE (MetaOnscreenNative, meta_onscreen_native, META, ONSCREEN_NATIVE, CoglOnscreenEgl) @@ -54,6 +56,7 @@ MetaOnscreenNative * meta_onscreen_native_new (MetaRendererNative *renderer_nati int width, int height); +META_EXPORT_TEST MetaCrtc * meta_onscreen_native_get_crtc (MetaOnscreenNative *onscreen_native); #endif /* META_ONSCREEN_NATIVE_H */ diff --git a/src/tests/meson.build b/src/tests/meson.build index 14d817937..2ff70832a 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -263,7 +263,10 @@ if have_native_tests { 'name': 'kms-render', 'suite': 'backends/native/kms', - 'sources': [ 'native-kms-render.c' ], + 'sources': [ + 'native-kms-render.c', + wayland_test_utils, + ], }, { 'name': 'kms-device', diff --git a/src/tests/native-kms-render.c b/src/tests/native-kms-render.c index 2f767a450..278edf4bd 100644 --- a/src/tests/native-kms-render.c +++ b/src/tests/native-kms-render.c @@ -20,13 +20,30 @@ #include "config.h" +#include + +#include "backends/native/meta-backend-native-private.h" +#include "backends/native/meta-crtc-kms.h" +#include "backends/native/meta-device-pool.h" +#include "backends/native/meta-drm-buffer.h" +#include "backends/native/meta-onscreen-native.h" +#include "backends/native/meta-kms.h" +#include "backends/native/meta-kms-device.h" +#include "core/display-private.h" #include "meta/meta-backend.h" #include "meta-test/meta-context-test.h" +#include "tests/meta-wayland-test-driver.h" +#include "tests/meta-wayland-test-utils.h" typedef struct { int number_of_frames_left; GMainLoop *loop; + + struct { + int n_paints; + uint32_t fb_id; + } scanout; } KmsRenderingTest; static MetaContext *test_context; @@ -67,11 +84,154 @@ meta_test_kms_render_basic (void) g_signal_handler_disconnect (stage, handler_id); } +static void +on_scanout_before_update (ClutterStage *stage, + ClutterStageView *stage_view, + KmsRenderingTest *test) +{ + test->scanout.n_paints = 0; + test->scanout.fb_id = 0; +} + +static void +on_scanout_before_paint (ClutterStage *stage, + ClutterStageView *stage_view, + KmsRenderingTest *test) +{ + CoglScanout *scanout; + MetaDrmBuffer *buffer; + + scanout = clutter_stage_view_peek_scanout (stage_view); + if (!scanout) + return; + + g_assert_true (META_IS_DRM_BUFFER (scanout)); + buffer = META_DRM_BUFFER (scanout); + test->scanout.fb_id = meta_drm_buffer_get_fb_id (buffer); + g_assert_cmpuint (test->scanout.fb_id, >, 0); +} + +static void +on_scanout_paint_view (ClutterStage *stage, + ClutterStageView *stage_view, + KmsRenderingTest *test) +{ + test->scanout.n_paints++; +} + +static void +on_scanout_presented (ClutterStage *stage, + ClutterStageView *stage_view, + ClutterFrameInfo *frame_info, + KmsRenderingTest *test) +{ + MetaBackend *backend = meta_context_get_backend (test_context); + MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); + MetaDevicePool *device_pool; + CoglFramebuffer *fb; + MetaCrtc *crtc; + MetaKmsCrtc *kms_crtc; + MetaKmsDevice *kms_device; + MetaDeviceFile *device_file; + GError *error = NULL; + drmModeCrtc *drm_crtc; + + if (test->scanout.n_paints > 0) + return; + + if (test->scanout.fb_id == 0) + return; + + device_pool = meta_backend_native_get_device_pool (backend_native); + + fb = clutter_stage_view_get_onscreen (stage_view); + crtc = meta_onscreen_native_get_crtc (META_ONSCREEN_NATIVE (fb)); + kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (crtc)); + kms_device = meta_kms_crtc_get_device (kms_crtc); + + device_file = meta_device_pool_open (device_pool, + meta_kms_device_get_path (kms_device), + META_DEVICE_FILE_FLAG_TAKE_CONTROL, + &error); + if (!device_file) + g_error ("Failed to open KMS device: %s", error->message); + + drm_crtc = drmModeGetCrtc (meta_device_file_get_fd (device_file), + meta_kms_crtc_get_id (kms_crtc)); + g_assert_nonnull (drm_crtc); + g_assert_cmpuint (drm_crtc->buffer_id, ==, test->scanout.fb_id); + drmModeFreeCrtc (drm_crtc); + + meta_device_file_release (device_file); + + g_main_loop_quit (test->loop); +} + +static void +meta_test_kms_render_client_scanout (void) +{ + MetaBackend *backend = meta_context_get_backend (test_context); + MetaWaylandCompositor *wayland_compositor = + meta_context_get_wayland_compositor (test_context); + ClutterActor *stage = meta_backend_get_stage (backend); + MetaKms *kms = meta_backend_native_get_kms (META_BACKEND_NATIVE (backend)); + MetaKmsDevice *kms_device = meta_kms_get_devices (kms)->data; + KmsRenderingTest test; + MetaWaylandTestClient *wayland_test_client; + g_autoptr (MetaWaylandTestDriver) test_driver = NULL; + gulong before_update_handler_id; + gulong before_paint_handler_id; + gulong paint_view_handler_id; + gulong presented_handler_id; + + test_driver = meta_wayland_test_driver_new (wayland_compositor); + meta_wayland_test_driver_set_property (test_driver, + "gpu-path", + meta_kms_device_get_path (kms_device)); + + wayland_test_client = + meta_wayland_test_client_new ("dma-buf-scanout"); + g_assert_nonnull (wayland_test_client); + + test = (KmsRenderingTest) { + .loop = g_main_loop_new (NULL, FALSE), + }; + + paint_view_handler_id = + g_signal_connect (stage, "paint-view", + G_CALLBACK (on_scanout_paint_view), &test); + before_update_handler_id = + g_signal_connect (stage, "before-update", + G_CALLBACK (on_scanout_before_update), &test); + before_paint_handler_id = + g_signal_connect (stage, "before-paint", + G_CALLBACK (on_scanout_before_paint), &test); + presented_handler_id = + g_signal_connect (stage, "presented", + G_CALLBACK (on_scanout_presented), &test); + + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); + g_main_loop_run (test.loop); + g_main_loop_unref (test.loop); + + g_assert_cmpuint (test.scanout.fb_id, >, 0); + + g_signal_handler_disconnect (stage, before_update_handler_id); + g_signal_handler_disconnect (stage, before_paint_handler_id); + g_signal_handler_disconnect (stage, paint_view_handler_id); + g_signal_handler_disconnect (stage, presented_handler_id); + + meta_wayland_test_driver_emit_sync_event (test_driver, 0); + meta_wayland_test_client_finish (wayland_test_client); +} + static void init_tests (void) { g_test_add_func ("/backends/native/kms/render/basic", meta_test_kms_render_basic); + g_test_add_func ("/backends/native/kms/render/client-scanout", + meta_test_kms_render_client_scanout); } int diff --git a/src/tests/wayland-test-clients/dma-buf-scanout.c b/src/tests/wayland-test-clients/dma-buf-scanout.c new file mode 100644 index 000000000..74b132c34 --- /dev/null +++ b/src/tests/wayland-test-clients/dma-buf-scanout.c @@ -0,0 +1,398 @@ +/* + * Copyright © 2022 Red Hat Inc. + * Copyright © 2020 Collabora, Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * + * Original license of parts of the DMA buffer parts: + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "wayland-test-client-utils.h" + +#include "linux-dmabuf-unstable-v1-client-protocol.h" + +typedef struct _Buffer +{ + struct wl_buffer *buffer; + gboolean busy; + gboolean recreate; + int dmabuf_fds[4]; + struct gbm_bo *bo; + int n_planes; + uint32_t width, height, strides[4], offsets[4]; + uint32_t format; + uint64_t modifier; +} Buffer; + +static WaylandDisplay *display; +static struct wl_registry *registry; +static struct zwp_linux_dmabuf_v1 *dmabuf; + +static struct wl_surface *surface; +static struct xdg_surface *xdg_surface; +static struct xdg_toplevel *xdg_toplevel; + +struct gbm_device *gbm_device; + +static GList *active_buffers; + +static struct +{ + uint32_t format; + uint64_t *modifiers; + int n_modifiers; +} format_state = { + .format = DRM_FORMAT_XRGB8888, +}; + +static gboolean running; + +static void +buffer_free (Buffer *buffer) +{ + int i; + + g_clear_pointer (&buffer->buffer, wl_buffer_destroy); + g_clear_pointer (&buffer->bo, gbm_bo_destroy); + + for (i = 0; i < buffer->n_planes; i++) + close (buffer->dmabuf_fds[i]); + + g_free (buffer); +} + +static void +handle_buffer_release (void *user_data, + struct wl_buffer *buffer_resource) +{ + Buffer *buffer = user_data; + + active_buffers = g_list_remove (active_buffers, buffer); + buffer_free (buffer); +} + +static const struct wl_buffer_listener buffer_listener = { + handle_buffer_release +}; + +static Buffer * +create_dma_buf_buffer (uint32_t width, + uint32_t height, + uint32_t format, + unsigned int n_modifiers, + uint64_t *modifiers) +{ + Buffer *buffer; + static uint32_t flags = 0; + struct zwp_linux_buffer_params_v1 *params; + int i; + + buffer = g_new0 (Buffer, 1); + + buffer->width = width; + buffer->height = height; + buffer->format = format; + + if (n_modifiers > 0) + { + buffer->bo = gbm_bo_create_with_modifiers2 (gbm_device, + buffer->width, buffer->height, + format, modifiers, + n_modifiers, + GBM_BO_USE_RENDERING | + GBM_BO_USE_SCANOUT); + if (buffer->bo) + buffer->modifier = gbm_bo_get_modifier (buffer->bo); + } + + if (!buffer->bo) + { + buffer->bo = gbm_bo_create (gbm_device, buffer->width, + buffer->height, buffer->format, + GBM_BO_USE_RENDERING | + GBM_BO_USE_SCANOUT); + buffer->modifier = DRM_FORMAT_MOD_INVALID; + } + + g_assert_nonnull (buffer->bo); + + buffer->n_planes = gbm_bo_get_plane_count (buffer->bo); + + params = zwp_linux_dmabuf_v1_create_params (dmabuf); + + for (i = 0; i < buffer->n_planes; i++) + { + buffer->dmabuf_fds[i] = gbm_bo_get_fd_for_plane (buffer->bo, i); + buffer->strides[i] = gbm_bo_get_stride_for_plane (buffer->bo, i); + buffer->offsets[i] = gbm_bo_get_offset (buffer->bo, i); + g_assert_cmpint (buffer->dmabuf_fds[i], >=, 0); + g_assert_cmpint (buffer->strides[i], >, 0); + + zwp_linux_buffer_params_v1_add (params, buffer->dmabuf_fds[i], i, + buffer->offsets[i], buffer->strides[i], + buffer->modifier >> 32, + buffer->modifier & 0xffffffff); + } + + buffer->buffer = + zwp_linux_buffer_params_v1_create_immed(params, + buffer->width, + buffer->height, + buffer->format, + flags); + + g_assert_nonnull (buffer->buffer); + + wl_buffer_add_listener (buffer->buffer, &buffer_listener, buffer); + + return buffer; +} + +static void +init_surface (void) +{ + xdg_toplevel_set_title (xdg_toplevel, "dma-buf-scanout-test"); + xdg_toplevel_set_fullscreen (xdg_toplevel, NULL); + wl_surface_commit (surface); +} + +static void +draw_main (int width, + int height) +{ + Buffer *buffer; + + buffer = create_dma_buf_buffer (width, height, + format_state.format, + format_state.n_modifiers, + format_state.modifiers); + + active_buffers = g_list_prepend (active_buffers, buffer); + + wl_surface_attach (surface, buffer->buffer, 0, 0); +} + +static void +handle_xdg_toplevel_configure (void *user_data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height, + struct wl_array *state) +{ + g_assert_cmpint (width, >, 0); + g_assert_cmpint (height, >, 0); + + draw_main (width, height); +} + +static void +handle_xdg_toplevel_close (void *user_data, + struct xdg_toplevel *xdg_toplevel) +{ + g_assert_not_reached (); +} + +static void +handle_xdg_toplevel_configure_bounds (void *user_data, + struct xdg_toplevel *xdg_toplevel, + int32_t bounds_width, + int32_t bounds_height) +{ +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + handle_xdg_toplevel_configure, + handle_xdg_toplevel_close, + handle_xdg_toplevel_configure_bounds, +}; + +static void +handle_frame_callback (void *user_data, + struct wl_callback *callback, + uint32_t time) +{ +} + +static const struct wl_callback_listener frame_listener = { + handle_frame_callback, +}; + +static void +handle_xdg_surface_configure (void *user_data, + struct xdg_surface *xdg_surface, + uint32_t serial) +{ + struct wl_callback *frame_callback; + + xdg_surface_ack_configure (xdg_surface, serial); + frame_callback = wl_surface_frame (surface); + wl_callback_add_listener (frame_callback, &frame_listener, NULL); + wl_surface_commit (surface); + wl_display_flush (display->display); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + handle_xdg_surface_configure, +}; + +static void +dmabuf_modifiers (void *user_data, + struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, + uint32_t format, + uint32_t modifier_hi, + uint32_t modifier_lo) +{ + uint64_t modifier; + + modifier = ((uint64_t)modifier_hi << 32) | modifier_lo; + if (format != format_state.format) + return; + + if (modifier != DRM_FORMAT_MOD_INVALID) + { + format_state.n_modifiers++; + format_state.modifiers = g_realloc_n (format_state.modifiers, + format_state.n_modifiers, + sizeof (uint64_t)); + format_state.modifiers[format_state.n_modifiers - 1] = modifier; + } +} + +static void +dmabuf_format (void *user_data, + struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, + uint32_t format) +{ +} + +static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { + dmabuf_format, + dmabuf_modifiers +}; + +static void +handle_registry_global (void *user_data, + struct wl_registry *registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + if (strcmp (interface, "zwp_linux_dmabuf_v1") == 0) + { + g_assert_cmpuint (version, >=, 3); + dmabuf = wl_registry_bind (registry, id, + &zwp_linux_dmabuf_v1_interface, 3); + zwp_linux_dmabuf_v1_add_listener (dmabuf, &dmabuf_listener, NULL); + } +} + +static void +handle_registry_global_remove (void *user_data, + struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + handle_registry_global, + handle_registry_global_remove +}; + +static void +on_sync_event (WaylandDisplay *display, + uint32_t serial) +{ + g_assert (serial == 0); + + running = FALSE; +} + +int +main (int argc, + char **argv) +{ + const char *gpu_path; + int fd; + + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + g_signal_connect (display, "sync-event", G_CALLBACK (on_sync_event), NULL); + registry = wl_display_get_registry (display->display); + wl_registry_add_listener (registry, ®istry_listener, NULL); + wl_display_roundtrip (display->display); + wl_display_roundtrip (display->display); + + g_assert_nonnull (dmabuf); + + gpu_path = lookup_property_value (display, "gpu-path"); + g_assert_nonnull (gpu_path); + + fd = open (gpu_path, O_RDWR); + if (fd < 0) + { + g_error ("Failed to open drm render node %s: %s", + gpu_path, g_strerror (errno)); + } + + gbm_device = gbm_create_device (fd); + + surface = wl_compositor_create_surface (display->compositor); + xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, surface); + xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, NULL); + xdg_toplevel = xdg_surface_get_toplevel (xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + + init_surface (); + + running = TRUE; + while (running) + { + if (wl_display_dispatch (display->display) == -1) + return EXIT_FAILURE; + } + + g_list_free_full (active_buffers, (GDestroyNotify) buffer_free); + g_object_unref (display); + + return EXIT_SUCCESS; +} diff --git a/src/tests/wayland-test-clients/meson.build b/src/tests/wayland-test-clients/meson.build index 92f85f51b..083c75057 100644 --- a/src/tests/wayland-test-clients/meson.build +++ b/src/tests/wayland-test-clients/meson.build @@ -46,29 +46,58 @@ common_sources = [ ] wayland_test_clients = [ - 'subsurface-remap-toplevel', - 'subsurface-reparenting', - 'subsurface-parent-unmapped', - 'invalid-subsurfaces', - 'invalid-xdg-shell-actions', - 'xdg-apply-limits', - 'xdg-activation', - 'xdg-toplevel-bounds', + { + 'name': 'subsurface-remap-toplevel', + }, + { + 'name': 'subsurface-reparenting', + }, + { + 'name': 'subsurface-parent-unmapped', + }, + { + 'name': 'invalid-subsurfaces', + }, + { + 'name': 'invalid-xdg-shell-actions', + }, + { + 'name': 'xdg-apply-limits', + }, + { + 'name': 'xdg-activation', + }, + { + 'name': 'xdg-toplevel-bounds', + }, + { + 'name': 'dma-buf-scanout', + 'extra_deps': [ + libdrm_dep, + libgbm_dep, + ], + }, ] foreach test : wayland_test_clients - executable(test, + test_name = test['name'] + deps = [ + glib_dep, + gobject_dep, + wayland_client_dep, + ] + if test.has_key('extra_deps') + deps += test['extra_deps'] + endif + + executable(test_name, sources: [ - '@0@.c'.format(test), + '@0@.c'.format(test_name), common_sources, ], include_directories: tests_includes, c_args: tests_c_args, - dependencies: [ - glib_dep, - gobject_dep, - wayland_client_dep, - ], + dependencies: deps, install: have_installed_tests, install_dir: wayland_test_client_installed_tests_libexecdir, )