From 5f7804bd6b22bbcaba5e0bd1e21381031a659813 Mon Sep 17 00:00:00 2001 From: korei999 Date: Wed, 18 Sep 2024 20:11:22 +0300 Subject: implement tearing protocol --- Makefile | 5 +- config.def.h | 8 ++ dwl.c | 184 ++++++++++++++++++++++++-- patches/tearing.patch | 356 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 538 insertions(+), 15 deletions(-) create mode 100644 patches/tearing.patch diff --git a/Makefile b/Makefile index 520059c..11d478c 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ dwl: dwl.o util.o dwl-ipc-unstable-v2-protocol.o dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ - dwl-ipc-unstable-v2-protocol.h + dwl-ipc-unstable-v2-protocol.h tearing-control-v1-protocol.h util.o: util.c util.h dwl-ipc-unstable-v2-protocol.o: dwl-ipc-unstable-v2-protocol.c dwl-ipc-unstable-v2-protocol.h @@ -53,6 +53,9 @@ dwl-ipc-unstable-v2-protocol.h: dwl-ipc-unstable-v2-protocol.c: $(WAYLAND_SCANNER) private-code \ protocols/dwl-ipc-unstable-v2.xml $@ +tearing-control-v1-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/staging/tearing-control/tearing-control-v1.xml $@ config.h: cp config.def.h $@ diff --git a/config.def.h b/config.def.h index a92e06d..4ca65a9 100644 --- a/config.def.h +++ b/config.def.h @@ -33,6 +33,14 @@ static const Rule rules[] = { { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ }; +/* tearing */ +static int tearing_allowed = 1; +static const ForceTearingRule force_tearing[] = { + {.title = "", .appid = "hl_linux"}, + {.title = "Warcraft III", .appid = ""}, + {.title = "", .appid = "gamescope"}, +}; + /* layout(s) */ static const Layout layouts[] = { /* symbol arrange function */ diff --git a/dwl.c b/dwl.c index fd6cf35..9bb4702 100644 --- a/dwl.c +++ b/dwl.c @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,11 @@ enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ enum { XDGShell, LayerShell, X11 }; /* client types */ enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +typedef struct ForceTearingRule { + const char* title; + const char* appid; +} ForceTearingRule; + typedef union { int i; uint32_t ui; @@ -141,6 +147,7 @@ typedef struct { uint32_t tags; int isfloating, isurgent, isfullscreen; uint32_t resize; /* configure serial of a pending resize */ + enum wp_tearing_control_v1_presentation_hint tearing_hint; } Client; typedef struct { @@ -250,6 +257,19 @@ typedef struct { struct wl_listener destroy; } SessionLock; +typedef struct TearingController { + struct wlr_tearing_control_v1 *tearing_control; + struct wl_listener set_hint; + struct wl_listener destroy; + + struct wl_list link; /* tearing_controllers */ +} TearingController; + +typedef struct SendFrameDoneData { + struct timespec when; + struct Monitor *mon; +} SendFrameDoneData; + /* function declarations */ static void applybounds(Client *c, struct wlr_box *bbox); static void applyrules(Client *c); @@ -311,6 +331,9 @@ static Client *focustop(Monitor *m); static void fullscreennotify(struct wl_listener *listener, void *data); static void gpureset(struct wl_listener *listener, void *data); static void handlesig(int signo); +static void handletearingcontrollersethint(struct wl_listener *listener, void *data); +static void handletearingcontrollerdestroy(struct wl_listener *listener, void *data); +static void handlenewtearinghint(struct wl_listener *listener, void *data); static void incnmaster(const Arg *arg); static void inputdevice(struct wl_listener *listener, void *data); static int keybinding(uint32_t mods, xkb_keysym_t sym); @@ -327,6 +350,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double double sy, double sx_unaccel, double sy_unaccel); static void motionrelative(struct wl_listener *listener, void *data); static void moveresize(const Arg *arg); +static int moncantear(Monitor* m); static void outputmgrapply(struct wl_listener *listener, void *data); static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); static void outputmgrtest(struct wl_listener *listener, void *data); @@ -341,6 +365,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); static void requestmonstate(struct wl_listener *listener, void *data); static void resize(Client *c, struct wlr_box geo, int interact); static void run(char *startup_cmd); +static void sendframedoneiterator(struct wlr_scene_buffer *buffer, int x, int y, void *user_data); static void setcursor(struct wl_listener *listener, void *data); static void setcursorshape(struct wl_listener *listener, void *data); static void setfloating(Client *c, int floating); @@ -419,6 +444,10 @@ static struct wlr_session_lock_manager_v1 *session_lock_mgr; static struct wlr_scene_rect *locked_bg; static struct wlr_session_lock_v1 *cur_lock; +struct wlr_tearing_control_manager_v1 *tearing_control_v1; +struct wl_listener tearing_control_new_object; +struct wl_list tearing_controllers; + static struct wlr_seat *seat; static KeyboardGroup *kb_group; static unsigned int cursor_mode; @@ -1798,6 +1827,69 @@ handlesig(int signo) quit(NULL); } +void +handletearingcontrollersethint(struct wl_listener *listener, void *data) +{ + Client *c = NULL, *i = NULL; + struct TearingController *controller = wl_container_of(listener, controller, set_hint); + + struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(controller->tearing_control->surface); +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface = wlr_xwayland_surface_try_from_wlr_surface(controller->tearing_control->surface); +#endif + + wl_list_for_each(i, &fstack, flink) { + if (i->surface.xdg == surface +#ifdef XWAYLAND + || i->surface.xwayland == xsurface +#endif + ) { + c = i; + break; + } + } + + if (c) { + enum wp_tearing_control_v1_presentation_hint hint = controller->tearing_control->current; + fprintf( + stderr, "TEARING: found surface: %p(appid: '%s', title: '%s'), hint: %d(%s)\n", + (void*)c, client_get_appid(c), client_get_title(c), hint, hint ? "ASYNC" : "VSYNC" + ); + c->tearing_hint = controller->tearing_control->current; + } +} + +void +handletearingcontrollerdestroy(struct wl_listener *listener, void *data) +{ + struct TearingController *controller = wl_container_of(listener, controller, destroy); + + wl_list_remove(&controller->set_hint.link); + wl_list_remove(&controller->destroy.link); + wl_list_remove(&controller->link); + free(controller); +} + +void +handlenewtearinghint(struct wl_listener *listener, void *data) +{ + struct wlr_tearing_control_v1 *tearing_control = data; + struct TearingController *controller = calloc(1, sizeof(struct TearingController)); + + if (!controller) + return; + + controller->tearing_control = tearing_control; + controller->set_hint.notify = handletearingcontrollersethint; + wl_signal_add(&tearing_control->events.set_hint, &controller->set_hint); + + controller->destroy.notify = handletearingcontrollerdestroy; + wl_signal_add(&tearing_control->events.destroy, &controller->destroy); + + wl_list_init(&controller->link); + wl_list_insert(&tearing_controllers, &controller->link); +} + void incnmaster(const Arg *arg) { @@ -1982,6 +2074,33 @@ locksession(struct wl_listener *listener, void *data) wlr_session_lock_v1_send_locked(session_lock); } +static inline void +forcetearingrule(Client *c) +{ + int success = 0; + const char* appid = client_get_appid(c); + const char* title = client_get_title(c); + + for (unsigned i = 0; i < LENGTH(force_tearing); i++) { + if (appid) + if (strcmp(force_tearing[i].appid, appid) == 0) { + success = 1; + break; + } + + if (title) + if (strcmp(force_tearing[i].title, title) == 0) { + success = 1; + break; + } + } + + if (success) { + c->tearing_hint = WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC; + fprintf(stderr, "tearing forced for: appid: '%s', title: '%s'\n", appid, title); + } +} + void mapnotify(struct wl_listener *listener, void *data) { @@ -1991,6 +2110,8 @@ mapnotify(struct wl_listener *listener, void *data) Monitor *m; int i; + forcetearingrule(c); + /* Create scene tree for this client and its border */ c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); /* Enabled later by a call to arrange() */ @@ -2234,6 +2355,13 @@ moveresize(const Arg *arg) } } +int +moncantear(Monitor* m) +{ + Client *c = focustop(m); + return (c && c->isfullscreen && c->tearing_hint); /* 1 == ASYNC */ +} + void outputmgrapply(struct wl_listener *listener, void *data) { @@ -2375,27 +2503,40 @@ quit(const Arg *arg) void rendermon(struct wl_listener *listener, void *data) { - /* This function is called every time an output is ready to display a frame, - * generally at the output's refresh rate (e.g. 60Hz). */ Monitor *m = wl_container_of(listener, m, frame); - Client *c; + struct wlr_scene_output *scene_output = m->scene_output; struct wlr_output_state pending = {0}; - struct timespec now; + SendFrameDoneData frame_done_data = {0}; - /* Render if no XDG clients have an outstanding resize and are visible on - * this monitor. */ - wl_list_for_each(c, &clients, link) { - if (c->resize && !c->isfloating && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) - goto skip; + m->wlr_output->frame_pending = false; + + if (!wlr_scene_output_needs_frame(scene_output)) { + goto skip; } - wlr_scene_output_commit(m->scene_output, NULL); + wlr_output_state_init(&pending); + if (!wlr_scene_output_build_state(m->scene_output, &pending, NULL)) { + goto skip; + } + + if (tearing_allowed && moncantear(m)) { + pending.tearing_page_flip = true; + + if (!wlr_output_test_state(m->wlr_output, &pending)) { + fprintf(stderr, "Output test failed on '%s', retrying without tearing page-flip\n", m->wlr_output->name); + pending.tearing_page_flip = false; + } + } + + if (!wlr_output_commit_state(m->wlr_output, &pending)) + fprintf(stderr, "Page-flip failed on output %s", m->wlr_output->name); -skip: - /* Let clients know a frame has been rendered */ - clock_gettime(CLOCK_MONOTONIC, &now); - wlr_scene_output_send_frame_done(m->scene_output, &now); wlr_output_state_finish(&pending); + +skip: + clock_gettime(CLOCK_MONOTONIC, &frame_done_data.when); + frame_done_data.mon = m; + wlr_scene_output_for_each_buffer(m->scene_output, sendframedoneiterator, &frame_done_data); } void @@ -2519,6 +2660,16 @@ run(char *startup_cmd) wl_display_run(dpy); } +void +sendframedoneiterator(struct wlr_scene_buffer *buffer, int x, int y, void *user_data) +{ + SendFrameDoneData *data = user_data; + if (buffer->primary_output != data->mon->scene_output) + return; + + wlr_scene_buffer_send_frame_done(buffer, &data->when); +} + void setcursor(struct wl_listener *listener, void *data) { @@ -2886,6 +3037,11 @@ setup(void) wl_global_create(dpy, &zdwl_ipc_manager_v2_interface, 2, NULL, dwl_ipc_manager_bind); + tearing_control_v1 = wlr_tearing_control_manager_v1_create(dpy, 1); + tearing_control_new_object.notify = handlenewtearinghint; + wl_signal_add(&tearing_control_v1->events.new_object, &tearing_control_new_object); + wl_list_init(&tearing_controllers); + /* Make sure XWayland clients don't connect to the parent X server, * e.g when running in the x11 backend or the wayland backend and the * compositor has Xwayland support */ diff --git a/patches/tearing.patch b/patches/tearing.patch new file mode 100644 index 0000000..951d25c --- /dev/null +++ b/patches/tearing.patch @@ -0,0 +1,356 @@ +From 66b2e1646bee8502a3715403c165015bd019438c Mon Sep 17 00:00:00 2001 +From: korei999 +Date: Wed, 18 Sep 2024 20:11:22 +0300 +Subject: [PATCH] implement tearing protocol + +--- + Makefile | 5 +- + config.def.h | 8 +++ + dwl.c | 184 +++++++++++++++++++++++++++++++++++++++++++++++---- + 3 files changed, 182 insertions(+), 15 deletions(-) + +diff --git a/Makefile b/Makefile +index 8db7409..6edc7d7 100644 +--- a/Makefile ++++ b/Makefile +@@ -21,7 +21,7 @@ dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ + dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h tearing-control-v1-protocol.h + util.o: util.c util.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +@@ -45,6 +45,9 @@ wlr-output-power-management-unstable-v1-protocol.h: + xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ ++tearing-control-v1-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ $(WAYLAND_PROTOCOLS)/staging/tearing-control/tearing-control-v1.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/config.def.h b/config.def.h +index 22d2171..52d38d3 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -28,6 +28,14 @@ static const Rule rules[] = { + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + }; + ++/* tearing */ ++static int tearing_allowed = 1; ++static const ForceTearingRule force_tearing[] = { ++ {.title = "", .appid = "hl_linux"}, ++ {.title = "Warcraft III", .appid = ""}, ++ {.title = "", .appid = "gamescope"}, ++}; ++ + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ +diff --git a/dwl.c b/dwl.c +index dc0c861..44be1bf 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -51,6 +51,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -90,6 +91,11 @@ enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ + #endif + ++typedef struct ForceTearingRule { ++ const char* title; ++ const char* appid; ++} ForceTearingRule; ++ + typedef union { + int i; + uint32_t ui; +@@ -143,6 +149,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ enum wp_tearing_control_v1_presentation_hint tearing_hint; + } Client; + + typedef struct { +@@ -243,6 +250,19 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++typedef struct TearingController { ++ struct wlr_tearing_control_v1 *tearing_control; ++ struct wl_listener set_hint; ++ struct wl_listener destroy; ++ ++ struct wl_list link; /* tearing_controllers */ ++} TearingController; ++ ++typedef struct SendFrameDoneData { ++ struct timespec when; ++ struct Monitor *mon; ++} SendFrameDoneData; ++ + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -293,6 +313,9 @@ static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); ++static void handletearingcontrollersethint(struct wl_listener *listener, void *data); ++static void handletearingcontrollerdestroy(struct wl_listener *listener, void *data); ++static void handlenewtearinghint(struct wl_listener *listener, void *data); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); +@@ -309,6 +332,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double + double sy, double sx_unaccel, double sy_unaccel); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++static int moncantear(Monitor* m); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); +@@ -323,6 +347,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void sendframedoneiterator(struct wlr_scene_buffer *buffer, int x, int y, void *user_data); + static void setcursor(struct wl_listener *listener, void *data); + static void setcursorshape(struct wl_listener *listener, void *data); + static void setfloating(Client *c, int floating); +@@ -400,6 +425,10 @@ static struct wlr_scene_rect *locked_bg; + static struct wlr_session_lock_v1 *cur_lock; + static struct wl_listener lock_listener = {.notify = locksession}; + ++struct wlr_tearing_control_manager_v1 *tearing_control_v1; ++struct wl_listener tearing_control_new_object; ++struct wl_list tearing_controllers; ++ + static struct wlr_seat *seat; + static KeyboardGroup *kb_group; + static unsigned int cursor_mode; +@@ -1510,6 +1539,69 @@ handlesig(int signo) + } + } + ++void ++handletearingcontrollersethint(struct wl_listener *listener, void *data) ++{ ++ Client *c = NULL, *i = NULL; ++ struct TearingController *controller = wl_container_of(listener, controller, set_hint); ++ ++ struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(controller->tearing_control->surface); ++#ifdef XWAYLAND ++ struct wlr_xwayland_surface *xsurface = wlr_xwayland_surface_try_from_wlr_surface(controller->tearing_control->surface); ++#endif ++ ++ wl_list_for_each(i, &fstack, flink) { ++ if (i->surface.xdg == surface ++#ifdef XWAYLAND ++ || i->surface.xwayland == xsurface ++#endif ++ ) { ++ c = i; ++ break; ++ } ++ } ++ ++ if (c) { ++ enum wp_tearing_control_v1_presentation_hint hint = controller->tearing_control->current; ++ fprintf( ++ stderr, "TEARING: found surface: %p(appid: '%s', title: '%s'), hint: %d(%s)\n", ++ (void*)c, client_get_appid(c), client_get_title(c), hint, hint ? "ASYNC" : "VSYNC" ++ ); ++ c->tearing_hint = controller->tearing_control->current; ++ } ++} ++ ++void ++handletearingcontrollerdestroy(struct wl_listener *listener, void *data) ++{ ++ struct TearingController *controller = wl_container_of(listener, controller, destroy); ++ ++ wl_list_remove(&controller->set_hint.link); ++ wl_list_remove(&controller->destroy.link); ++ wl_list_remove(&controller->link); ++ free(controller); ++} ++ ++void ++handlenewtearinghint(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tearing_control_v1 *tearing_control = data; ++ struct TearingController *controller = calloc(1, sizeof(struct TearingController)); ++ ++ if (!controller) ++ return; ++ ++ controller->tearing_control = tearing_control; ++ controller->set_hint.notify = handletearingcontrollersethint; ++ wl_signal_add(&tearing_control->events.set_hint, &controller->set_hint); ++ ++ controller->destroy.notify = handletearingcontrollerdestroy; ++ wl_signal_add(&tearing_control->events.destroy, &controller->destroy); ++ ++ wl_list_init(&controller->link); ++ wl_list_insert(&tearing_controllers, &controller->link); ++} ++ + void + incnmaster(const Arg *arg) + { +@@ -1677,6 +1769,33 @@ locksession(struct wl_listener *listener, void *data) + wlr_session_lock_v1_send_locked(session_lock); + } + ++static inline void ++forcetearingrule(Client *c) ++{ ++ int success = 0; ++ const char* appid = client_get_appid(c); ++ const char* title = client_get_title(c); ++ ++ for (unsigned i = 0; i < LENGTH(force_tearing); i++) { ++ if (appid) ++ if (strcmp(force_tearing[i].appid, appid) == 0) { ++ success = 1; ++ break; ++ } ++ ++ if (title) ++ if (strcmp(force_tearing[i].title, title) == 0) { ++ success = 1; ++ break; ++ } ++ } ++ ++ if (success) { ++ c->tearing_hint = WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC; ++ fprintf(stderr, "tearing forced for: appid: '%s', title: '%s'\n", appid, title); ++ } ++} ++ + void + mapnotify(struct wl_listener *listener, void *data) + { +@@ -1686,6 +1805,8 @@ mapnotify(struct wl_listener *listener, void *data) + Monitor *m; + int i; + ++ forcetearingrule(c); ++ + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); + /* Enabled later by a call to arrange() */ +@@ -1924,6 +2045,13 @@ moveresize(const Arg *arg) + } + } + ++int ++moncantear(Monitor* m) ++{ ++ Client *c = focustop(m); ++ return (c && c->isfullscreen && c->tearing_hint); /* 1 == ASYNC */ ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +@@ -2093,27 +2221,40 @@ quit(const Arg *arg) + void + rendermon(struct wl_listener *listener, void *data) + { +- /* This function is called every time an output is ready to display a frame, +- * generally at the output's refresh rate (e.g. 60Hz). */ + Monitor *m = wl_container_of(listener, m, frame); +- Client *c; ++ struct wlr_scene_output *scene_output = m->scene_output; + struct wlr_output_state pending = {0}; +- struct timespec now; ++ SendFrameDoneData frame_done_data = {0}; + +- /* Render if no XDG clients have an outstanding resize and are visible on +- * this monitor. */ +- wl_list_for_each(c, &clients, link) { +- if (c->resize && !c->isfloating && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) +- goto skip; ++ m->wlr_output->frame_pending = false; ++ ++ if (!wlr_scene_output_needs_frame(scene_output)) { ++ goto skip; + } + +- wlr_scene_output_commit(m->scene_output, NULL); ++ wlr_output_state_init(&pending); ++ if (!wlr_scene_output_build_state(m->scene_output, &pending, NULL)) { ++ goto skip; ++ } ++ ++ if (tearing_allowed && moncantear(m)) { ++ pending.tearing_page_flip = true; ++ ++ if (!wlr_output_test_state(m->wlr_output, &pending)) { ++ fprintf(stderr, "Output test failed on '%s', retrying without tearing page-flip\n", m->wlr_output->name); ++ pending.tearing_page_flip = false; ++ } ++ } ++ ++ if (!wlr_output_commit_state(m->wlr_output, &pending)) ++ fprintf(stderr, "Page-flip failed on output %s", m->wlr_output->name); + +-skip: +- /* Let clients know a frame has been rendered */ +- clock_gettime(CLOCK_MONOTONIC, &now); +- wlr_scene_output_send_frame_done(m->scene_output, &now); + wlr_output_state_finish(&pending); ++ ++skip: ++ clock_gettime(CLOCK_MONOTONIC, &frame_done_data.when); ++ frame_done_data.mon = m; ++ wlr_scene_output_for_each_buffer(m->scene_output, sendframedoneiterator, &frame_done_data); + } + + void +@@ -2237,6 +2378,16 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++sendframedoneiterator(struct wlr_scene_buffer *buffer, int x, int y, void *user_data) ++{ ++ SendFrameDoneData *data = user_data; ++ if (buffer->primary_output != data->mon->scene_output) ++ return; ++ ++ wlr_scene_buffer_send_frame_done(buffer, &data->when); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2584,6 +2735,11 @@ setup(void) + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + ++ tearing_control_v1 = wlr_tearing_control_manager_v1_create(dpy, 1); ++ tearing_control_new_object.notify = handlenewtearinghint; ++ wl_signal_add(&tearing_control_v1->events.new_object, &tearing_control_new_object); ++ wl_list_init(&tearing_controllers); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +-- +2.46.0 + -- cgit v1.2.1