From 4e9a4428565e4001ac0f947a3cd471704ce07fd6 Mon Sep 17 00:00:00 2001 From: hujianwei Date: Fri, 4 Nov 2022 02:25:44 +0000 Subject: [PATCH] Check in C and XML files for tests * 12to11-test.xml: * buffer_release.c: * test.c: New files. --- 12to11-test.xml | 94 +++++++++ buffer_release.c | 146 +++++++++++++ test.c | 534 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 774 insertions(+) create mode 100644 12to11-test.xml create mode 100644 buffer_release.c create mode 100644 test.c diff --git a/12to11-test.xml b/12to11-test.xml new file mode 100644 index 0000000..1b0946d --- /dev/null +++ b/12to11-test.xml @@ -0,0 +1,94 @@ + + + + Copyright (C) 2022 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 https://www.gnu.org/licenses/. + + + + + This protocol is used by the 12to11 protocol translator to + support various tests. The test_manager global allows creating + a surface whose bounds and contents can be inspected, and + connecting to the X server used by the compositor. + + Upon binding to the test_manager, a display_string event is sent + containing the name of the X display. + + + + + + + + + Get a test_surface object for a particular surface. If a role + was already attached to this surface, or a role of a different + type was previously attached, a role_present error is issued. + + The window is created immediately after get_test_surface is + called. It is mapped once a commit request with a non-nil + buffer is made. + + Once the window associated with the test_surface object is + mapped, a mapped event is sent. + + + + + + + + The display_string event sends the name of the X display to + clients. It is sent immediately after binding to the + test_manager object. + + + + + + + + This role provides a test surface. Various buffers and + subsurfaces can be attached, and the resulting display contents + validated. + + + + + This request destroys the test_surface role. Subsequently, + get_test_surface can be called again with its surface. + + + + + + The map event is sent once the window is mapped and its + contents can be retrieved. The two arguments are the XID of + the window and the name of the display it is on. + + If the surface is mapped, then unmapped (by having a nil + buffer attached) and then mapped again, without waiting for + the first mapped event, the delivery of subsequent mapped + events becomes undefined. + + + + + + diff --git a/buffer_release.c b/buffer_release.c new file mode 100644 index 0000000..16927bc --- /dev/null +++ b/buffer_release.c @@ -0,0 +1,146 @@ +/* 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 "compositor.h" + +/* Simple helper code for managing buffer release in surfaces. */ + +typedef struct _ReleaseLaterRecord ReleaseLaterRecord; + +struct _ReleaseLaterRecord +{ + /* A monotonically (overflow aside) increasing identifier. */ + uint64_t id; + + /* The buffer that should be released upon receiving this + message. */ + ExtBuffer *buffer; + + /* The idle callback, if any. */ + IdleCallbackKey key; + + /* The buffer release helper. */ + BufferReleaseHelper *helper; + + /* The next and last records. */ + ReleaseLaterRecord *next, *last; +}; + +struct _BufferReleaseHelper +{ + /* Queue of buffers pending release. */ + ReleaseLaterRecord records; + + /* Callback run upon all buffers being released. */ + AllReleasedCallback callback; + + /* Data for that callback. */ + void *callback_data; +}; + +BufferReleaseHelper * +MakeBufferReleaseHelper (AllReleasedCallback callback, + void *callback_data) +{ + BufferReleaseHelper *helper; + + helper = XLCalloc (1, sizeof *helper); + helper->records.next = &helper->records; + helper->records.last = &helper->records; + helper->callback = callback; + helper->callback_data = callback_data; + + return helper; +} + +void +FreeBufferReleaseHelper (BufferReleaseHelper *helper) +{ + ReleaseLaterRecord *next, *last; + + /* Do an XSync, and then release all the records. */ + XSync (compositor.display, False); + + next = helper->records.next; + while (next != &helper->records) + { + last = next; + next = next->next; + + /* Cancel the idle callback if it already exists. */ + if (last->key) + RenderCancelIdleCallback (last->key); + + /* Release the buffer now. */ + XLReleaseBuffer (last->buffer); + + /* Before freeing the record itself. */ + XLFree (last); + } + + /* Free the helper. */ + XLFree (helper); +} + +static void +BufferIdleCallback (RenderBuffer buffer, void *data) +{ + ReleaseLaterRecord *record; + BufferReleaseHelper *helper; + + record = data; + helper = record->helper; + + /* Release the buffer. */ + XLReleaseBuffer (record->buffer); + + /* Unlink and free the record. */ + record->next->last = record->last; + record->last->next = record->next; + XLFree (record); + + /* If there are no more records in the helper, run its + all-released-callback. */ + if (helper->records.next == &helper->records) + helper->callback (helper->callback_data); +} + +void +ReleaseBufferWithHelper (BufferReleaseHelper *helper, ExtBuffer *buffer, + RenderTarget target) +{ + ReleaseLaterRecord *record; + RenderBuffer render_buffer; + + render_buffer = XLRenderBufferFromBuffer (buffer); + + record = XLCalloc (1, sizeof *record); + record->next = helper->records.next; + record->last = &helper->records; + helper->records.next->last = record; + helper->records.next = record; + + /* Now, the record is linked into the list. Record the buffer and + add an idle callback. */ + record->buffer = buffer; + record->key = RenderAddIdleCallback (render_buffer, target, + BufferIdleCallback, + record); + record->helper = helper; +} diff --git a/test.c b/test.c new file mode 100644 index 0000000..4a99854 --- /dev/null +++ b/test.c @@ -0,0 +1,534 @@ +/* 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 "12to11-test.h" + +#define TestSurfaceFromRole(role) ((TestSurface *) (role)) + +#define DefaultEventMask \ + (ExposureMask | StructureNotifyMask | PropertyChangeMask) + +enum + { + IsSurfaceMapped = 1, + PendingBufferRelease = 1 << 1, + PendingFrameCallback = 1 << 2, + }; + +typedef struct _TestSurface TestSurface; + +struct _TestSurface +{ + /* The associated role. */ + Role role; + + /* The associated subcompositor. */ + Subcompositor *subcompositor; + + /* The associated buffer release helper. */ + BufferReleaseHelper *release_helper; + + /* The associated window. */ + Window window; + + /* The associated rendering target. */ + RenderTarget target; + + /* The number of references to this test surface, and flags. */ + int refcount, flags; + + /* The last known width and height. */ + int bounds_width, bounds_height; +}; + +/* The test surface manager global. */ +static struct wl_global *test_manager_global; + +/* Hash table of all surfaces. */ +static XLAssocTable *surfaces; + +static void +DestroyBacking (TestSurface *test) +{ + if (--test->refcount) + return; + + /* Release all allocated resources. */ + RenderDestroyRenderTarget (test->target); + XDestroyWindow (compositor.display, test->window); + + /* And the buffer release helper. */ + FreeBufferReleaseHelper (test->release_helper); + + /* Delete the association. */ + XLDeleteAssoc (surfaces, test->window); + + /* Free the subcompositor. */ + SubcompositorFree (test->subcompositor); + + /* And since there are no C level references to the role anymore, it + can be freed. */ + XLFree (test); +} + +static void +RunFrameCallbacks (TestSurface *test) +{ + struct timespec time; + + clock_gettime (CLOCK_MONOTONIC, &time); + XLSurfaceRunFrameCallbacks (test->role.surface, time); + + test->flags &= ~PendingFrameCallback; +} + +static void +RunFrameCallbacksConditionally (TestSurface *test) +{ + if (!test->role.surface) + return; + + if (test->flags & PendingBufferRelease) + /* Wait for all buffers to be released first. */ + test->flags |= PendingFrameCallback; + else + RunFrameCallbacks (test); +} + +static void +AllBuffersReleased (void *data) +{ + TestSurface *test; + + test = data; + + if (!test->role.surface) + return; + + test->flags &= ~PendingBufferRelease; + + /* Run pending frame callbacks. */ + if (test->flags & PendingFrameCallback) + RunFrameCallbacks (test); +} + +static void +NoteBounds (void *data, int min_x, int min_y, int max_x, int max_y) +{ + TestSurface *test; + int bounds_width, bounds_height; + + test = data; + + /* Avoid resizing the window should its actual size not have + changed. */ + + bounds_width = max_x - min_x + 1; + bounds_height = max_y - min_y + 1; + + if (test->bounds_width != bounds_width + || test->bounds_height != bounds_height) + { + /* Resize the window to fit. */ + XResizeWindow (compositor.display, test->window, + bounds_width, bounds_height); + test->bounds_width = bounds_width; + test->bounds_height = bounds_height; + } +} + +static void +NoteFrame (FrameMode mode, uint64_t id, void *data) +{ + if (mode != ModeComplete && mode != ModePresented) + return; + + /* Run the frame callbacks. With the test surface, this also serves + to mean that painting has completed. */ + RunFrameCallbacksConditionally (data); +} + +static void +MapTestSurface (TestSurface *test) +{ + /* Set the bounds width and height. */ + test->bounds_width = SubcompositorWidth (test->subcompositor); + test->bounds_height = SubcompositorHeight (test->subcompositor); + + /* First, resize the window to the current bounds. */ + XResizeWindow (compositor.display, test->window, + test->bounds_width, test->bounds_height); + + /* Next, map the window and raise it. Wait for a subsequent + MapNotify before sending the map event. */ + XMapRaised (compositor.display, test->window); + + /* And say that the window is now mapped. */ + test->flags |= IsSurfaceMapped; +} + +static void +UnmapTestSurface (TestSurface *test) +{ + if (test->flags & IsSurfaceMapped) + /* Unmap the surface. */ + XUnmapWindow (compositor.display, test->window); +} + +static void +Commit (Surface *surface, Role *role) +{ + TestSurface *test; + + test = TestSurfaceFromRole (role); + + if (surface->current_state.buffer + && !(test->flags & IsSurfaceMapped)) + /* Map the surface now. */ + MapTestSurface (test); + else if (!surface->current_state.buffer) + /* Unmap the surface now. */ + UnmapTestSurface (test); + + /* Finally, do a subcompositor update if the surface is now + mapped. */ + if (test->flags & IsSurfaceMapped) + SubcompositorUpdate (test->subcompositor); +} + +static Bool +Setup (Surface *surface, Role *role) +{ + TestSurface *test; + + test = TestSurfaceFromRole (role); + + /* Set role->surface here, since this is where the refcounting is + done as well. */ + role->surface = surface; + + /* Prevent the surface from ever holding another kind of role. */ + surface->role_type = TestSurfaceType; + + /* Attach the views to the subcompositor. */ + ViewSetSubcompositor (surface->view, test->subcompositor); + ViewSetSubcompositor (surface->under, test->subcompositor); + + /* Make sure the under view ends up beneath surface->view. */ + SubcompositorInsert (test->subcompositor, surface->under); + SubcompositorInsert (test->subcompositor, surface->view); + + /* Retain the backing data. */ + test->refcount++; + return True; +} + +static void +Teardown (Surface *surface, Role *role) +{ + TestSurface *test; + + /* Clear role->surface here, since this is where the refcounting is + done as well. */ + role->surface = NULL; + + test = TestSurfaceFromRole (role); + + /* Unparent the surface's views as well. */ + ViewUnparent (surface->view); + ViewUnparent (surface->under); + + /* Detach the surface's views from the subcompositor. */ + ViewSetSubcompositor (surface->view, NULL); + ViewSetSubcompositor (surface->under, NULL); + + /* Release the backing data. */ + DestroyBacking (test); +} + +static void +Destroy (struct wl_client *client, struct wl_resource *resource) +{ + TestSurface *test; + + test = wl_resource_get_user_data (resource); + + /* Now detach the role from its surface, which can be reused in the + future. */ + if (test->role.surface) + XLSurfaceReleaseRole (test->role.surface, &test->role); + + /* And destroy the resource. */ + wl_resource_destroy (resource); +} + +static void +ReleaseBuffer (Surface *surface, Role *role, ExtBuffer *buffer) +{ + TestSurface *test; + RenderBuffer render_buffer; + + test = TestSurfaceFromRole (role); + render_buffer = XLRenderBufferFromBuffer (buffer); + + if (RenderIsBufferIdle (render_buffer, test->target)) + /* Release the buffer now -- it is already idle. */ + XLReleaseBuffer (buffer); + else + { + /* Release the buffer once it becomes idle, or is destroyed. */ + ReleaseBufferWithHelper (test->release_helper, buffer, + test->target); + + /* Mark the surface as pending buffer release, so frame + callbacks can be deferred until all buffers are released. */ + test->flags |= PendingBufferRelease; + } +} + +static void +SubsurfaceUpdate (Surface *surface, Role *role) +{ + TestSurface *test; + + test = TestSurfaceFromRole (role); + SubcompositorUpdate (test->subcompositor); +} + +static Window +GetWindow (Surface *surface, Role *role) +{ + TestSurface *test; + + test = TestSurfaceFromRole (role); + return test->window; +} + +static const struct test_surface_interface test_surface_impl = + { + .destroy = Destroy, + }; + +static void +HandleResourceDestroy (struct wl_resource *resource) +{ + TestSurface *test; + + test = wl_resource_get_user_data (resource); + test->role.resource = NULL; + + /* Dereference the backing data. */ + DestroyBacking (test); +} + +static void +GetTestSurface (struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *surface_resource) +{ + Surface *surface; + TestSurface *test; + XSetWindowAttributes attrs; + unsigned long flags; + + surface = wl_resource_get_user_data (surface_resource); + + if (surface->role_type != AnythingType + && surface->role_type != TestSurfaceType) + { + /* The client is trying to create a test surface for a surface + that has or had some other role. */ + wl_resource_post_error (resource, TEST_MANAGER_ERROR_ROLE_PRESENT, + "a role is/was already present on the given surface"); + return; + } + + test = XLSafeMalloc (sizeof *test); + + if (!test) + { + wl_resource_post_no_memory (resource); + return; + } + + memset (test, 0, sizeof *test); + + /* Now create the associated resource. */ + test->role.resource + = wl_resource_create (client, &test_surface_interface, + wl_resource_get_version (resource), + id); + if (!test->role.resource) + { + wl_resource_post_no_memory (resource); + XLFree (test); + + return; + } + + /* Create the window. */ + attrs.colormap = compositor.colormap; + attrs.border_pixel = border_pixel; + attrs.event_mask = DefaultEventMask; + attrs.cursor = InitDefaultCursor (); + flags = CWColormap | CWBorderPixel | CWEventMask | CWCursor; + + test->window = XCreateWindow (compositor.display, + DefaultRootWindow (compositor.display), + 0, 0, 20, 20, 0, compositor.n_planes, + InputOutput, compositor.visual, flags, + &attrs); + + /* And the subcompositor and rendering target. */ + test->subcompositor = MakeSubcompositor (); + test->target = RenderTargetFromWindow (test->window, DefaultEventMask); + + /* And a buffer release helper. */ + test->release_helper = MakeBufferReleaseHelper (AllBuffersReleased, + test); + + /* Set the subcompositor target. */ + SubcompositorSetTarget (test->subcompositor, &test->target); + + /* Set some callbacks. The note frame callback is not useful as + test surfaces have no frame clock. */ + SubcompositorSetBoundsCallback (test->subcompositor, NoteBounds, test); + SubcompositorSetNoteFrameCallback (test->subcompositor, NoteFrame, + test); + + /* Create the hash table used to look up test surfaces if + necessary. */ + + if (!surfaces) + surfaces = XLCreateAssocTable (16); + + /* Associate the window with the role. */ + XLMakeAssoc (surfaces, test->window, test); + + /* Set the role implementation. */ + test->role.funcs.commit = Commit; + test->role.funcs.teardown = Teardown; + test->role.funcs.setup = Setup; + test->role.funcs.release_buffer = ReleaseBuffer; + test->role.funcs.subsurface_update = SubsurfaceUpdate; + test->role.funcs.get_window = GetWindow; + + /* Add the resource implementation. */ + wl_resource_set_implementation (test->role.resource, &test_surface_impl, + test, HandleResourceDestroy); + test->refcount++; + + /* Attach the role. */ + if (!XLSurfaceAttachRole (surface, &test->role)) + abort (); +} + +static const struct test_manager_interface test_manager_impl = + { + .get_test_surface = GetTestSurface, + }; + + + +static void +HandleBind (struct wl_client *client, void *data, uint32_t version, + uint32_t id) +{ + struct wl_resource *resource; + char *name; + + resource = wl_resource_create (client, &test_manager_interface, + version, id); + + if (!resource) + { + wl_client_post_no_memory (client); + return; + } + + wl_resource_set_implementation (resource, &test_manager_impl, + NULL, NULL); + + /* Send the display name to the client. */ + name = DisplayString (compositor.display); + test_manager_send_display_string (resource, name); +} + +void +XLInitTest (void) +{ + test_manager_global + = wl_global_create (compositor.wl_display, &test_manager_interface, + 1, NULL, HandleBind); +} + +static Bool +DispatchMapNotify (XEvent *event) +{ + TestSurface *test; + + /* Try to look up the surface. */ + test = XLLookUpAssoc (surfaces, event->xmap.window); + + if (!test) + return False; + + /* The surface is now mapped. Dispatch the mapped event. */ + if (test->flags & IsSurfaceMapped && test->role.resource) + test_surface_send_mapped (test->role.resource, test->window, + DisplayString (compositor.display)); + return True; +} + +static Bool +DispatchExpose (XEvent *event) +{ + TestSurface *test; + + /* Try to look up the surface. */ + test = XLLookUpAssoc (surfaces, event->xexpose.window); + + if (!test) + return False; + + /* Expose the subcompositor. */ + SubcompositorExpose (test->subcompositor, event); + + return True; +} + +Bool +XLHandleOneXEventForTest (XEvent *event) +{ + if (!surfaces) + return False; + + switch (event->type) + { + case MapNotify: + return DispatchMapNotify (event); + + case Expose: + return DispatchExpose (event); + } + + return False; +}