diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c index b8cf3bd2a..75283c5c2 100644 --- a/clutter/clutter/clutter-frame-clock.c +++ b/clutter/clutter/clutter-frame-clock.c @@ -977,8 +977,7 @@ clutter_frame_clock_uninhibit (ClutterFrameClock *frame_clock) static gboolean want_triple_buffering (ClutterFrameClock *frame_clock) { - /* disabled until test cases are updated */ - return FALSE; + return TRUE; } void diff --git a/src/tests/native-kms-render.c b/src/tests/native-kms-render.c index aea22603d..784fac518 100644 --- a/src/tests/native-kms-render.c +++ b/src/tests/native-kms-render.c @@ -39,22 +39,29 @@ #include "tests/meta-wayland-test-driver.h" #include "tests/meta-wayland-test-utils.h" +#define N_FRAMES_PER_TEST 30 + typedef struct { int number_of_frames_left; GMainLoop *loop; struct { - int n_paints; - uint32_t fb_id; + int n_frames_started; + int n_presentations; + int n_direct_scanouts; + GList *fb_ids; + gboolean wait_for_scanout; + gboolean expect_double_buffering; } scanout; - gboolean wait_for_scanout; - struct { - gboolean scanout_sabotaged; - gboolean fallback_painted; - guint repaint_guard_id; + int last_frame_started; + int last_frame_presented; + int frame_sabotaged; + int first_scanout; + int fallbacks_painted; + gboolean first_scanout_presented; ClutterStageView *scanout_failed_view; } scanout_fallback; } KmsRenderingTest; @@ -101,7 +108,7 @@ meta_test_kms_render_basic (void) gulong handler_id; test = (KmsRenderingTest) { - .number_of_frames_left = 10, + .number_of_frames_left = N_FRAMES_PER_TEST, .loop = g_main_loop_new (NULL, FALSE), }; handler_id = g_signal_connect (stage, "after-update", @@ -116,16 +123,6 @@ meta_test_kms_render_basic (void) g_signal_handler_disconnect (stage, handler_id); } -static void -on_scanout_before_update (ClutterStage *stage, - ClutterStageView *stage_view, - ClutterFrame *frame, - KmsRenderingTest *test) -{ - test->scanout.n_paints = 0; - test->scanout.fb_id = 0; -} - static void on_scanout_before_paint (ClutterStage *stage, ClutterStageView *stage_view, @@ -135,6 +132,9 @@ on_scanout_before_paint (ClutterStage *stage, CoglScanout *scanout; CoglScanoutBuffer *scanout_buffer; MetaDrmBuffer *buffer; + uint32_t fb_id; + + test->scanout.n_frames_started++; scanout = clutter_stage_view_peek_scanout (stage_view); if (!scanout) @@ -143,18 +143,13 @@ on_scanout_before_paint (ClutterStage *stage, scanout_buffer = cogl_scanout_get_buffer (scanout); g_assert_true (META_IS_DRM_BUFFER (scanout_buffer)); buffer = META_DRM_BUFFER (scanout_buffer); - test->scanout.fb_id = meta_drm_buffer_get_fb_id (buffer); - g_assert_cmpuint (test->scanout.fb_id, >, 0); -} + fb_id = meta_drm_buffer_get_fb_id (buffer); + g_assert_cmpuint (fb_id, >, 0); + test->scanout.fb_ids = g_list_append (test->scanout.fb_ids, + GUINT_TO_POINTER (fb_id)); -static void -on_scanout_paint_view (ClutterStage *stage, - ClutterStageView *stage_view, - MtkRegion *region, - ClutterFrame *frame, - KmsRenderingTest *test) -{ - test->scanout.n_paints++; + /* Triple buffering, but no higher */ + g_assert_cmpuint (g_list_length (test->scanout.fb_ids), <=, 2); } static void @@ -173,13 +168,17 @@ on_scanout_presented (ClutterStage *stage, MetaDeviceFile *device_file; GError *error = NULL; drmModeCrtc *drm_crtc; + uint32_t first_fb_id_expected; - if (test->wait_for_scanout && test->scanout.n_paints > 0) + /* Ignore frames from previous sub-tests */ + if (test->scanout.n_frames_started <= 0) return; - if (test->wait_for_scanout && test->scanout.fb_id == 0) + if (test->scanout.wait_for_scanout && test->scanout.fb_ids == NULL) return; + test->scanout.n_presentations++; + device_pool = meta_backend_native_get_device_pool (backend_native); fb = clutter_stage_view_get_onscreen (stage_view); @@ -197,15 +196,37 @@ on_scanout_presented (ClutterStage *stage, drm_crtc = drmModeGetCrtc (meta_device_file_get_fd (device_file), meta_kms_crtc_get_id (kms_crtc)); g_assert_nonnull (drm_crtc); - if (test->scanout.fb_id == 0) - g_assert_cmpuint (drm_crtc->buffer_id, !=, test->scanout.fb_id); + + /* Triple buffering remains in effect even when transitioning to + * direct scanout. So we expect the first presentation after + * wait_for_scanout will still be composited and won't match the head of + * fb_ids yet... + */ + if (test->scanout.fb_ids && + (test->scanout.expect_double_buffering || + test->scanout.n_presentations > 1)) + { + test->scanout.n_direct_scanouts++; + first_fb_id_expected = GPOINTER_TO_UINT (test->scanout.fb_ids->data); + test->scanout.fb_ids = g_list_delete_link (test->scanout.fb_ids, + test->scanout.fb_ids); + g_assert_cmpuint (drm_crtc->buffer_id, ==, first_fb_id_expected); + } else - g_assert_cmpuint (drm_crtc->buffer_id, ==, test->scanout.fb_id); + { + first_fb_id_expected = 0; + g_assert_cmpuint (drm_crtc->buffer_id, !=, first_fb_id_expected); + } + drmModeFreeCrtc (drm_crtc); meta_device_file_release (device_file); - g_main_loop_quit (test->loop); + test->number_of_frames_left--; + if (test->number_of_frames_left <= 0) + g_main_loop_quit (test->loop); + else + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); } typedef enum @@ -226,9 +247,7 @@ meta_test_kms_render_client_scanout (void) 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; MetaWindow *window; MtkRectangle view_rect; @@ -244,9 +263,11 @@ meta_test_kms_render_client_scanout (void) g_assert_nonnull (wayland_test_client); test = (KmsRenderingTest) { + .number_of_frames_left = N_FRAMES_PER_TEST, .loop = g_main_loop_new (NULL, FALSE), - .wait_for_scanout = TRUE, + .scanout = {0}, }; + test.scanout.wait_for_scanout = TRUE; g_assert_cmpuint (g_list_length (clutter_stage_peek_stage_views (stage)), ==, @@ -254,12 +275,6 @@ meta_test_kms_render_client_scanout (void) clutter_stage_view_get_layout (clutter_stage_peek_stage_views (stage)->data, &view_rect); - 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); @@ -270,7 +285,8 @@ meta_test_kms_render_client_scanout (void) clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); g_main_loop_run (test.loop); - g_assert_cmpuint (test.scanout.fb_id, >, 0); + g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST); + g_assert_cmpint (test.scanout.n_direct_scanouts, ==, N_FRAMES_PER_TEST - 1); g_debug ("Unmake fullscreen"); window = meta_find_window_from_title (test_context, "dma-buf-scanout-test"); @@ -291,11 +307,18 @@ meta_test_kms_render_client_scanout (void) g_assert_cmpint (buffer_rect.x, ==, 10); g_assert_cmpint (buffer_rect.y, ==, 10); - test.wait_for_scanout = FALSE; + test.number_of_frames_left = N_FRAMES_PER_TEST; + test.scanout.wait_for_scanout = FALSE; + test.scanout.expect_double_buffering = TRUE; /* because wait_for_sync_point */ + test.scanout.n_frames_started = 0; + test.scanout.n_presentations = 0; + test.scanout.n_direct_scanouts = 0; + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); g_main_loop_run (test.loop); - g_assert_cmpuint (test.scanout.fb_id, ==, 0); + g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST); + g_assert_cmpint (test.scanout.n_direct_scanouts, ==, 1); g_debug ("Moving back to 0, 0"); meta_window_move_frame (window, TRUE, 0, 0); @@ -306,15 +329,20 @@ meta_test_kms_render_client_scanout (void) g_assert_cmpint (buffer_rect.x, ==, 0); g_assert_cmpint (buffer_rect.y, ==, 0); - test.wait_for_scanout = TRUE; + test.number_of_frames_left = N_FRAMES_PER_TEST; + test.scanout.wait_for_scanout = TRUE; + test.scanout.expect_double_buffering = FALSE; + test.scanout.n_frames_started = 0; + test.scanout.n_presentations = 0; + test.scanout.n_direct_scanouts = 0; + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); g_main_loop_run (test.loop); - g_assert_cmpuint (test.scanout.fb_id, >, 0); + g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST); + g_assert_cmpint (test.scanout.n_direct_scanouts, ==, N_FRAMES_PER_TEST - 1); - 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); @@ -322,30 +350,6 @@ meta_test_kms_render_client_scanout (void) g_main_loop_unref (test.loop); } -static gboolean -needs_repainted_guard (gpointer user_data) -{ - g_assert_not_reached (); - return G_SOURCE_REMOVE; -} - -static void -scanout_fallback_result_feedback (const MetaKmsFeedback *kms_feedback, - gpointer user_data) -{ - KmsRenderingTest *test = user_data; - - g_assert_cmpuint (test->scanout_fallback.repaint_guard_id, ==, 0); - g_assert_nonnull (test->scanout_fallback.scanout_failed_view); - - test->scanout_fallback.repaint_guard_id = - g_idle_add_full (G_PRIORITY_LOW, needs_repainted_guard, test, NULL); -} - -static const MetaKmsResultListenerVtable scanout_fallback_result_listener_vtable = { - .feedback = scanout_fallback_result_feedback, -}; - static void on_scanout_fallback_before_paint (ClutterStage *stage, ClutterStageView *stage_view, @@ -356,15 +360,43 @@ on_scanout_fallback_before_paint (ClutterStage *stage, MetaCrtc *crtc = meta_renderer_view_get_crtc (view); MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (crtc)); MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); - MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); CoglScanout *scanout; - MetaKmsUpdate *kms_update; + int this_frame; + + /* We don't know exactly how many frames the test will take due to: + * 1. Client scanouts taking a while to get started. + * 2. Triple buffering being asynchronous so one can't infer which DRM + * calls have completed from just the painting state. + * 3. Atomic commits now live in a separate thread! + * + * So ensure there's always a reason to start the next frame and + * the test never hangs; + */ + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); + + this_frame = ++test->scanout_fallback.last_frame_started; scanout = clutter_stage_view_peek_scanout (stage_view); if (!scanout) return; - g_assert_false (test->scanout_fallback.scanout_sabotaged); + if (!test->scanout_fallback.first_scanout) + { + test->scanout_fallback.first_scanout = this_frame; + return; + } + + /* Keep the test simple: Only one frame is ever sabotaged and it is + * definitely a direct scanout. But we can't rely on the value of 'scanout' + * alone because that may be non-NULL even when the next commit is going + * to be composited (triple buffering). So wait until first_scanout_presented + * before doing the sabotage. + */ + if (test->scanout_fallback.frame_sabotaged || + !test->scanout_fallback.first_scanout_presented) + return; + + test->scanout_fallback.frame_sabotaged = this_frame; if (is_atomic_mode_setting (kms_device)) { @@ -375,17 +407,6 @@ on_scanout_fallback_before_paint (ClutterStage *stage, drm_mock_queue_error (DRM_MOCK_CALL_PAGE_FLIP, EINVAL); drm_mock_queue_error (DRM_MOCK_CALL_SET_CRTC, EINVAL); } - - test->scanout_fallback.scanout_sabotaged = TRUE; - - kms_update = meta_frame_native_ensure_kms_update (frame_native, kms_device); - meta_kms_update_add_result_listener (kms_update, - &scanout_fallback_result_listener_vtable, - NULL, - test, - NULL); - - test->scanout_fallback.scanout_failed_view = stage_view; } static void @@ -395,13 +416,14 @@ on_scanout_fallback_paint_view (ClutterStage *stage, ClutterFrame *frame, KmsRenderingTest *test) { - if (test->scanout_fallback.scanout_sabotaged) - { - g_assert_cmpuint (test->scanout_fallback.repaint_guard_id, !=, 0); - g_clear_handle_id (&test->scanout_fallback.repaint_guard_id, - g_source_remove); - test->scanout_fallback.fallback_painted = TRUE; - } + /* With triple buffering, usable fallback paints may occur even before the + * failing commit they are needed to replace. So it would be too racy to + * check if the a notification of the failed commit has been emitted yet. + * Just make sure there has been at least one repaint after the sabotage AND + * that at the end of the test g_test_assert_expected_messages passes. + */ + if (test->scanout_fallback.frame_sabotaged) + test->scanout_fallback.fallbacks_painted++; } static void @@ -410,11 +432,21 @@ on_scanout_fallback_presented (ClutterStage *stage, ClutterFrameInfo *frame_info, KmsRenderingTest *test) { - if (!test->scanout_fallback.scanout_sabotaged) - return; + int this_frame; - g_assert_true (test->scanout_fallback.fallback_painted); - g_main_loop_quit (test->loop); + if (test->scanout_fallback.last_frame_started <= 0) + return; /* Leftovers from previous tests. Ignore. */ + + this_frame = ++test->scanout_fallback.last_frame_presented; + if (this_frame >= test->scanout_fallback.first_scanout) + test->scanout_fallback.first_scanout_presented = TRUE; + + if (test->scanout_fallback.fallbacks_painted > 0) + g_main_loop_quit (test->loop); + + test->number_of_frames_left--; + g_assert_cmpint (test->number_of_frames_left, >, 0); + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); } static void @@ -443,6 +475,7 @@ meta_test_kms_render_client_scanout_fallback (void) g_assert_nonnull (wayland_test_client); test = (KmsRenderingTest) { + .number_of_frames_left = N_FRAMES_PER_TEST, .loop = g_main_loop_new (NULL, FALSE), }; @@ -464,6 +497,16 @@ meta_test_kms_render_client_scanout_fallback (void) g_main_loop_run (test.loop); g_main_loop_unref (test.loop); + g_test_message ("Test ending with:\n" + "\tfallbacks_painted: %d\n" + "\tlast_frame_started: %d\n" + "\tlast_frame_presented: %d\n" + "\tframe_sabotaged: %d", + test.scanout_fallback.fallbacks_painted, + test.scanout_fallback.last_frame_started, + test.scanout_fallback.last_frame_presented, + test.scanout_fallback.frame_sabotaged); + g_test_assert_expected_messages (); g_signal_handler_disconnect (stage, before_paint_handler_id);