From c5216442cfb22eed7c2d4196f46c9ad8db3a5425 Mon Sep 17 00:00:00 2001
From: korei999 <ju7t1xe@gmail.com>
Date: Sun, 2 Feb 2025 13:44:41 +0200
Subject: [PATCH] implement tearing protocol

---
 Makefile     |   5 +-
 config.def.h |   8 +++
 dwl.c        | 176 +++++++++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 174 insertions(+), 15 deletions(-)

diff --git a/Makefile b/Makefile
index 578194f..7d24970 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 4816159..747e16f 100644
--- a/dwl.c
+++ b/dwl.c
@@ -51,6 +51,7 @@
 #include <wlr/types/wlr_session_lock_v1.h>
 #include <wlr/types/wlr_single_pixel_buffer_v1.h>
 #include <wlr/types/wlr_subcompositor.h>
+#include <wlr/types/wlr_tearing_control_v1.h>
 #include <wlr/types/wlr_viewporter.h>
 #include <wlr/types/wlr_virtual_keyboard_v1.h>
 #include <wlr/types/wlr_virtual_pointer_v1.h>
@@ -86,6 +87,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;
@@ -139,6 +145,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 {
@@ -239,6 +246,17 @@ 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;
+} 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);
@@ -305,6 +323,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);
@@ -319,6 +338,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);
@@ -333,6 +353,9 @@ static void spawn(const Arg *arg);
 static void startdrag(struct wl_listener *listener, void *data);
 static void tag(const Arg *arg);
 static void tagmon(const Arg *arg);
+static void tearingcontrollersethint(struct wl_listener *listener, void *data);
+static void tearingcontrollerdestroy(struct wl_listener *listener, void *data);
+static void tearingnewhint(struct wl_listener *listener, void *data);
 static void tile(Monitor *m);
 static void togglefloating(const Arg *arg);
 static void togglefullscreen(const Arg *arg);
@@ -395,6 +418,8 @@ 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;
 
+static struct wlr_tearing_control_manager_v1 *tearing_control_v1;
+
 static struct wlr_seat *seat;
 static KeyboardGroup *kb_group;
 static unsigned int cursor_mode;
@@ -435,6 +460,7 @@ static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape};
 static struct wl_listener request_start_drag = {.notify = requeststartdrag};
 static struct wl_listener start_drag = {.notify = startdrag};
 static struct wl_listener new_session_lock = {.notify = locksession};
+static struct wl_listener tearing_control_new_object = {.notify = tearingnewhint};
 
 #ifdef XWAYLAND
 static void activatex11(struct wl_listener *listener, void *data);
@@ -779,6 +805,7 @@ cleanuplisteners(void)
 	wl_list_remove(&request_start_drag.link);
 	wl_list_remove(&start_drag.link);
 	wl_list_remove(&new_session_lock.link);
+	wl_list_remove(&tearing_control_new_object.link);
 #ifdef XWAYLAND
 	wl_list_remove(&new_xwayland_surface.link);
 	wl_list_remove(&xwayland_ready.link);
@@ -1563,6 +1590,63 @@ handlesig(int signo)
 		quit(NULL);
 }
 
+void
+tearingcontrollersethint(struct wl_listener *listener, void *data)
+{
+	Client *c = NULL, *i = NULL;
+	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
+tearingcontrollerdestroy(struct wl_listener *listener, void *data)
+{
+	TearingController *controller = wl_container_of(listener, controller, destroy);
+
+	wl_list_remove(&controller->set_hint.link);
+	wl_list_remove(&controller->destroy.link);
+	free(controller);
+}
+
+void
+tearingnewhint(struct wl_listener *listener, void *data)
+{
+	struct wlr_tearing_control_v1 *tearing_control = data;
+	TearingController *controller = ecalloc(1, sizeof(*controller));
+
+	controller->tearing_control = tearing_control;
+
+	controller->set_hint.notify = tearingcontrollersethint;
+	wl_signal_add(&tearing_control->events.set_hint, &controller->set_hint);
+
+	controller->destroy.notify = tearingcontrollerdestroy;
+	wl_signal_add(&tearing_control->events.destroy, &controller->destroy);
+}
+
 void
 incnmaster(const Arg *arg)
 {
@@ -1730,6 +1814,35 @@ 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, "Forcing tearing for appid: '%s', title: '%s'\n", appid, title);
+	}
+}
+
 void
 mapnotify(struct wl_listener *listener, void *data)
 {
@@ -1739,6 +1852,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() */
@@ -1977,6 +2092,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)
 {
@@ -2147,27 +2269,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
@@ -2291,6 +2426,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)
 {
@@ -2641,6 +2786,9 @@ setup(void)
 	wl_signal_add(&output_mgr->events.apply, &output_mgr_apply);
 	wl_signal_add(&output_mgr->events.test, &output_mgr_test);
 
+	tearing_control_v1 = wlr_tearing_control_manager_v1_create(dpy, 1);
+	wl_signal_add(&tearing_control_v1->events.new_object, &tearing_control_new_object);
+
 	/* 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.49.0

