diff --git a/tearing-control-v1.xml b/tearing-control-v1.xml
new file mode 100644
index 0000000..e38056d
--- /dev/null
+++ b/tearing-control-v1.xml
@@ -0,0 +1,120 @@
+
+
+
+ Copyright © 2021 Xaver Hugl
+
+ 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.
+
+
+
+
+ For some use cases like games or drawing tablets it can make sense to
+ reduce latency by accepting tearing with the use of asynchronous page
+ flips. This global is a factory interface, allowing clients to inform
+ which type of presentation the content of their surfaces is suitable for.
+
+ Graphics APIs like EGL or Vulkan, that manage the buffer queue and commits
+ of a wl_surface themselves, are likely to be using this extension
+ internally. If a client is using such an API for a wl_surface, it should
+ not directly use this extension on that surface, to avoid raising a
+ tearing_control_exists protocol error.
+
+ Warning! The protocol described in this file is currently in the testing
+ phase. Backward compatible changes may be added together with the
+ corresponding interface version bump. Backward incompatible changes can
+ only be done by creating a new major version of the extension.
+
+
+
+
+ Destroy this tearing control factory object. Other objects, including
+ wp_tearing_control_v1 objects created by this factory, are not affected
+ by this request.
+
+
+
+
+
+
+
+
+
+ Instantiate an interface extension for the given wl_surface to request
+ asynchronous page flips for presentation.
+
+ If the given wl_surface already has a wp_tearing_control_v1 object
+ associated, the tearing_control_exists protocol error is raised.
+
+
+
+
+
+
+
+
+ An additional interface to a wl_surface object, which allows the client
+ to hint to the compositor if the content on the surface is suitable for
+ presentation with tearing.
+ The default presentation hint is vsync. See presentation_hint for more
+ details.
+
+
+
+
+ This enum provides information for if submitted frames from the client
+ may be presented with tearing.
+
+
+
+ The content of this surface is meant to be synchronized to the
+ vertical blanking period. This should not result in visible tearing
+ and may result in a delay before a surface commit is presented.
+
+
+
+
+ The content of this surface is meant to be presented with minimal
+ latency and tearing is acceptable.
+
+
+
+
+
+
+ Set the presentation hint for the associated wl_surface. This state is
+ double-buffered and is applied on the next wl_surface.commit.
+
+ The compositor is free to dynamically respect or ignore this hint based
+ on various conditions like hardware capabilities, surface state and
+ user preferences.
+
+
+
+
+
+
+ Destroy this surface tearing object and revert the presentation hint to
+ vsync. The change will be applied on the next wl_surface.commit.
+
+
+
+
+
diff --git a/tearing_control.c b/tearing_control.c
new file mode 100644
index 0000000..48e6cc3
--- /dev/null
+++ b/tearing_control.c
@@ -0,0 +1,220 @@
+/* Wayland compositor running on top of an X server.
+
+Copyright (C) 2022 to various contributors.
+
+This file is part of 12to11.
+
+12to11 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 3 of the License, or (at your
+option) any later version.
+
+12to11 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 12to11. If not, see . */
+
+#include
+
+#include "compositor.h"
+#include "tearing-control-v1.h"
+
+typedef struct _TearingControl TearingControl;
+
+struct _TearingControl
+{
+ /* The associated surface. NULL when detached. */
+ Surface *surface;
+
+ /* The associated resource. */
+ struct wl_resource *resource;
+};
+
+/* The tearing control manager. */
+static struct wl_global *tearing_control_manager_global;
+
+
+
+static void
+DestroyTearingControl (struct wl_client *client, struct wl_resource *resource)
+{
+ TearingControl *control;
+
+ control = wl_resource_get_user_data (resource);
+
+ if (control->surface)
+ {
+ /* Reset the presentation hint. */
+ control->surface->pending_state.presentation_hint
+ = PresentationHintVsync;
+ control->surface->pending_state.pending
+ |= PendingPresentationHint;
+ }
+
+ wl_resource_destroy (resource);
+}
+
+static void
+SetPresentationHint (struct wl_client *client, struct wl_resource *resource,
+ uint32_t hint)
+{
+ TearingControl *control;
+
+ control = wl_resource_get_user_data (resource);
+
+ if (control->surface)
+ {
+ switch (hint)
+ {
+ case WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC:
+ control->surface->pending_state.presentation_hint
+ = PresentationHintAsync;
+ break;
+
+ default:
+ control->surface->pending_state.presentation_hint
+ = PresentationHintVsync;
+ break;
+ }
+
+ control->surface->pending_state.pending |= PendingPresentationHint;
+ }
+}
+
+static const struct wp_tearing_control_v1_interface control_impl =
+ {
+ .destroy = DestroyTearingControl,
+ .set_presentation_hint = SetPresentationHint,
+ };
+
+static void
+HandleResourceDestroy (struct wl_resource *resource)
+{
+ TearingControl *control, **reference;
+
+ control = wl_resource_get_user_data (resource);
+
+ /* If the surface is still attached to the tearing control, remove
+ it from the surface. */
+
+ if (control->surface)
+ {
+ reference
+ = XLSurfaceFindClientData (control->surface,
+ TearingControlData);
+ XLAssert (reference != NULL);
+
+ *reference = NULL;
+ }
+
+ XLFree (control);
+}
+
+
+
+static void
+FreeTearingControlData (void *data)
+{
+ TearingControl **control;
+
+ control = data;
+
+ if (!*control)
+ return;
+
+ /* Detach the surface from the tearing control. */
+ (*control)->surface = NULL;
+}
+
+static void
+Destroy (struct wl_client *client, struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static void
+GetTearingControl (struct wl_client *client, struct wl_resource *resource,
+ uint32_t id, struct wl_resource *surface_resource)
+{
+ Surface *surface;
+ TearingControl **control;
+
+ surface = wl_resource_get_user_data (surface_resource);
+ control = XLSurfaceGetClientData (surface, TearingControlData,
+ sizeof *control,
+ FreeTearingControlData);
+
+#define ControlExists \
+ WP_TEARING_CONTROL_MANAGER_V1_ERROR_TEARING_CONTROL_EXISTS
+
+ if (*control)
+ {
+ /* A tearing control resource already exists for this
+ surface. */
+ wl_resource_post_error (resource, ControlExists,
+ "a wp_tearing_control_v1 resource already exists"
+ " for the specified surface");
+ return;
+ }
+
+#undef ControlExists
+
+ (*control) = XLCalloc (1, sizeof **control);
+ (*control)->resource
+ = wl_resource_create (client,
+ &wp_tearing_control_v1_interface,
+ wl_resource_get_version (resource), id);
+
+ if (!(*control)->resource)
+ {
+ XLFree (*control);
+ (*control) = NULL;
+
+ wl_resource_post_no_memory (resource);
+ return;
+ }
+
+ (*control)->surface = surface;
+ wl_resource_set_implementation ((*control)->resource, &control_impl,
+ (*control), HandleResourceDestroy);
+}
+
+static const struct wp_tearing_control_manager_v1_interface manager_impl =
+ {
+ .destroy = Destroy,
+ .get_tearing_control = GetTearingControl,
+ };
+
+
+
+static void
+HandleBind (struct wl_client *client, void *data,
+ uint32_t version, uint32_t id)
+{
+ struct wl_resource *resource;
+
+ resource = wl_resource_create (client,
+ &wp_tearing_control_manager_v1_interface,
+ version, id);
+
+ if (!resource)
+ {
+ wl_client_post_no_memory (client);
+ return;
+ }
+
+ wl_resource_set_implementation (resource, &manager_impl,
+ NULL, NULL);
+}
+
+void
+XLInitTearingControl (void)
+{
+ tearing_control_manager_global
+ = wl_global_create (compositor.wl_display,
+ &wp_tearing_control_manager_v1_interface,
+ 1, NULL, HandleBind);
+}
diff --git a/tests/tearing_control_test.c b/tests/tearing_control_test.c
new file mode 100644
index 0000000..e897d8f
--- /dev/null
+++ b/tests/tearing_control_test.c
@@ -0,0 +1,241 @@
+/* Tests for the Wayland compositor running on the X server.
+
+Copyright (C) 2022 to various contributors.
+
+This file is part of 12to11.
+
+12to11 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 3 of the License, or (at your
+option) any later version.
+
+12to11 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 12to11. If not, see . */
+
+#include "test_harness.h"
+#include "tearing-control-v1.h"
+
+/* Tests for buffer release. */
+
+enum test_kind
+ {
+ TEARING_CONTROL_KIND,
+ TEARING_DESTROY_KIND,
+ };
+
+static const char *test_names[] =
+ {
+ "tearing_control",
+ "tearing_destroy",
+ };
+
+#define LAST_TEST TEARING_CONTROL_KIND
+
+/* The display. */
+static struct test_display *display;
+
+/* The tearing control manager. */
+static struct wp_tearing_control_manager_v1 *manager;
+
+/* Test interfaces. */
+static struct test_interface test_interfaces[] =
+ {
+ { "wp_tearing_control_manager_v1", &manager,
+ &wp_tearing_control_manager_v1_interface, 1, },
+ };
+
+/* The test surface and Wayland surface. */
+static struct test_surface *test_surface;
+static struct wl_surface *wayland_surface;
+
+/* The tearing control. */
+static struct wp_tearing_control_v1 *tearing_control;
+
+/* The presentation hint used. 1 means async, and 0 means vsync. */
+static int used_presentation_mode;
+
+
+
+/* Forward declarations. */
+static void verify_async_used (void);
+static void verify_vsync_used (void);
+
+
+
+static struct test_buffer *
+make_test_buffer (void)
+{
+ struct wl_buffer *buffer;
+ struct test_buffer *test_buffer;
+ char *empty_data;
+ size_t stride;
+
+ stride = get_image_stride (display, 24, 1);
+
+ if (!stride)
+ report_test_failure ("unknown stride");
+
+ empty_data = calloc (1, stride);
+
+ if (!empty_data)
+ report_test_failure ("failed to allocate buffer data");
+
+ buffer = upload_image_data (display, empty_data, 1, 1, 24);
+ free (empty_data);
+
+ if (!buffer)
+ report_test_failure ("failed to create single pixel buffer");
+
+ test_buffer = get_test_buffer (display, buffer);
+
+ if (!test_buffer)
+ report_test_failure ("failed to create test buffer");
+
+ return test_buffer;
+}
+
+static void
+test_single_step (enum test_kind kind)
+{
+ struct test_buffer *buffer;
+
+ again:
+
+ test_log ("running test step: %s", test_names[kind]);
+
+ switch (kind)
+ {
+ case TEARING_CONTROL_KIND:
+#define VSYNC WP_TEARING_CONTROL_V1_PRESENTATION_HINT_VSYNC
+ wp_tearing_control_v1_set_presentation_hint (tearing_control,
+ VSYNC);
+#undef VSYNC
+ buffer = make_test_buffer ();
+
+ /* Attach the buffer. */
+ wl_surface_attach (wayland_surface, buffer->buffer, 0, 0);
+ wl_surface_commit (wayland_surface);
+
+ /* Now see what kind of presentation was used. */
+ verify_vsync_used ();
+
+#define ASYNC WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC
+ wp_tearing_control_v1_set_presentation_hint (tearing_control,
+ ASYNC);
+#undef ASYNC
+ wl_surface_commit (wayland_surface);
+
+ /* Now verify that async presentation was used. */
+ verify_async_used ();
+
+ kind = TEARING_DESTROY_KIND;
+ goto again;
+
+ case TEARING_DESTROY_KIND:
+ /* Destroy the tearing control resource. */
+ wp_tearing_control_v1_destroy (tearing_control);
+ wl_surface_commit (wayland_surface);
+
+ /* Verify that the tearing hint reverted to vsync. */
+ verify_vsync_used ();
+ break;
+ }
+
+ if (kind == LAST_TEST)
+ test_complete ();
+}
+
+
+
+static void
+handle_test_surface_mapped (void *data, struct test_surface *test_surface,
+ uint32_t xid, const char *display_string)
+{
+
+}
+
+static void
+handle_test_surface_activated (void *data, struct test_surface *test_surface,
+ uint32_t months, uint32_t milliseconds,
+ struct wl_surface *activator_surface)
+{
+
+}
+
+static void
+handle_test_surface_committed (void *data, struct test_surface *test_surface,
+ uint32_t presentation_hint)
+{
+ used_presentation_mode = presentation_hint;
+}
+
+static const struct test_surface_listener test_surface_listener =
+ {
+ handle_test_surface_mapped,
+ handle_test_surface_activated,
+ handle_test_surface_committed,
+ };
+
+static void
+verify_async_used (void)
+{
+ wl_display_roundtrip (display->display);
+
+ if (used_presentation_mode != 1)
+ report_test_failure ("async presentation not used where expected!");
+}
+
+static void
+verify_vsync_used (void)
+{
+ wl_display_roundtrip (display->display);
+
+ if (used_presentation_mode == 1)
+ report_test_failure ("vsync presentation not used where expected!");
+}
+
+
+
+static void
+run_test (void)
+{
+ if (!make_test_surface (display, &wayland_surface,
+ &test_surface))
+ report_test_failure ("failed to create test surface");
+
+ test_surface_add_listener (test_surface, &test_surface_listener,
+ NULL);
+
+ tearing_control
+ = wp_tearing_control_manager_v1_get_tearing_control (manager,
+ wayland_surface);
+
+ if (!tearing_control)
+ report_test_failure ("failed to create tearing control");
+
+ test_single_step (TEARING_CONTROL_KIND);
+
+ while (true)
+ {
+ if (wl_display_dispatch (display->display) == -1)
+ die ("wl_display_dispatch");
+ }
+}
+
+int
+main (void)
+{
+ test_init ();
+ display = open_test_display (test_interfaces,
+ ARRAYELTS (test_interfaces));
+
+ if (!display)
+ report_test_failure ("failed to open display");
+
+ run_test ();
+}