diff --git a/12to11.c b/12to11.c index 55e796c..88094d4 100644 --- a/12to11.c +++ b/12to11.c @@ -98,7 +98,7 @@ HandleCmdline (Display *dpy, int argc, char **argv) { print_usage: fprintf (stderr, - "usage: %s [-name name] [-class class] [-xrm resourcestring]...\n", + "usage: %s [-name name] [-class class] [-xrm resourcestring...]\n", argv[0]); exit (!strcmp (argv[i], "-help") ? 0 : 1); } @@ -224,6 +224,7 @@ XLMain (int argc, char **argv) XLInitDecoration (); XLInitTextInput (); XLInitSinglePixelBuffer (); + XLInitDrmLease (); /* This has to come after the rest of the initialization. */ DetermineServerTime (); diff --git a/12to11.man b/12to11.man index 56a4375..73d6095 100644 --- a/12to11.man +++ b/12to11.man @@ -3,7 +3,7 @@ 12to11 - Wayland to X protocol translator .SH SYNOPSIS .B 12to11 -[\-\fBclass\fP \fIclass\fP] [\-\fBname\fP \fIname\fP] [\-\fBxrm\fP \fIresourcestring\fP]... +[\-\fBclass\fP \fIclass\fP] [\-\fBname\fP \fIname\fP] [\-\fBxrm\fP \fIresourcestring\fP...] .SH DESCRIPTION .I 12to11 starts a Wayland compositor on the next available socket; @@ -212,6 +212,16 @@ Protocol Version zwp_linux_explicit_synchronization_v1 2 .TE .PP +When the X server supports version 1.6 or later of the X Resize, +Rotate and Reflect Extension, the following Wayland protocol is also +supported: +.TS H +lb lb +lb n . +Protocol Version +wp_drm_lease_device_v1 1 +.TE +.PP However, Wayland clients are allowed to continue to access data from the \fBCLIPBOARD\fP and \fBPRIMARY\fP selections even when they do not have the keyboard focus, against the restrictions put out in the @@ -242,5 +252,5 @@ vast majority of clients seem not to make use of this feature, and implementing it would be a lot of trouble. .SH "SEE ALSO" X(7), Xorg(1) -.SH AUTHOR +.SH AUTHORS Various contributors. diff --git a/Imakefile b/Imakefile index 5d7d2dd..bfe3f07 100644 --- a/Imakefile +++ b/Imakefile @@ -1,4 +1,4 @@ -#include "libraries.def" +#include "12to11.conf" #ifndef HasPosixThreads #error "Posix threads are required" @@ -10,7 +10,8 @@ DEPLIBS = $(DEPXLIB) $(DEPEXTENSIONLIB) $(DEPXRANDRLIB) $(DEPXRENDERLIB) \ LOCAL_LIBRARIES = $(XLIB) $(EXTENSIONLIB) $(XCBLIB) $(XCB) $(XCB_SHM) \ $(XRANDRLIB) $(PIXMAN) $(XRENDERLIB) $(XILIB) $(XKBFILELIB) $(XFIXESLIB) \ - $(XCB_DRI3) $(XCB_SHAPE) $(WAYLAND_SERVER) + $(XCB_DRI3) $(XCB_SHAPE) $(WAYLAND_SERVER) $(XCB_RANDR) $(DRM) \ + $(XPRESENTLIB) INCLUDES := $(DRMINCLUDES) $(PIXMANINCLUDES) @@ -22,7 +23,7 @@ SRCS = 12to11.c run.c alloc.c fns.c output.c compositor.c \ icon_surface.c primary_selection.c renderer.c \ picture_renderer.c explicit_synchronization.c transform.c \ wp_viewporter.c decoration.c text_input.c \ - single_pixel_buffer.c + single_pixel_buffer.c drm_lease.c OBJS = 12to11.o run.o alloc.o fns.o output.o compositor.o \ surface.o region.o shm.o atoms.o subcompositor.o positioner.o \ @@ -32,7 +33,7 @@ OBJS = 12to11.o run.o alloc.o fns.o output.o compositor.o \ icon_surface.o primary_selection.o renderer.o \ picture_renderer.o explicit_synchronization.o transform.o \ wp_viewporter.o decoration.o text_input.o \ - single_pixel_buffer.o + single_pixel_buffer.o drm_lease.o GENHEADERS = transfer_atoms.h @@ -115,6 +116,7 @@ ScannerTarget(viewporter) ScannerTarget(xdg-decoration-unstable-v1) ScannerTarget(text-input-unstable-v3) ScannerTarget(single-pixel-buffer-v1) +ScannerTarget(drm-lease-v1) /* Make OBJS depend on scanner headers, and depend on both them and SRCS. */ $(OBJS): $(GENHEADERS) diff --git a/atoms.c b/atoms.c index 357c0f9..d7ff2f6 100644 --- a/atoms.c +++ b/atoms.c @@ -108,6 +108,7 @@ static const char *names[] = "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_MENU", "_NET_WM_WINDOW_TYPE_DND", + "CONNECTOR_ID", /* These are automatically generated from mime.txt. */ DirectTransferAtomNames @@ -129,7 +130,8 @@ Atom _NET_WM_OPAQUE_REGION, _XL_BUFFER_RELEASE, _NET_WM_SYNC_REQUEST_COUNTER, XdndActionPrivate, XdndActionList, XdndActionDescription, XdndProxy, XdndEnter, XdndPosition, XdndStatus, XdndLeave, XdndDrop, XdndFinished, _NET_WM_FRAME_TIMINGS, _NET_WM_BYPASS_COMPOSITOR, WM_STATE, - _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_DND; + _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_DND, + CONNECTOR_ID; XrmQuark resource_quark, app_quark, QString; @@ -283,9 +285,10 @@ XLInitAtoms (void) _NET_WM_WINDOW_TYPE = atoms[58]; _NET_WM_WINDOW_TYPE_MENU = atoms[59]; _NET_WM_WINDOW_TYPE_DND = atoms[60]; + CONNECTOR_ID = atoms[61]; /* This is automatically generated. */ - DirectTransferAtomInit (atoms, 61); + DirectTransferAtomInit (atoms, 62); /* Now, initialize quarks. */ resource_quark = XrmPermStringToQuark (compositor.resource_name); diff --git a/compositor.h b/compositor.h index e2a40fe..4573c18 100644 --- a/compositor.h +++ b/compositor.h @@ -121,9 +121,15 @@ typedef struct _ShmFormat ShmFormat; typedef enum _Operation Operation; +typedef void *IdleCallbackKey; +typedef void *PresentCompletionKey; + typedef void (*DmaBufSuccessFunc) (RenderBuffer, void *); typedef void (*DmaBufFailureFunc) (void *); +typedef void (*BufferIdleFunc) (RenderBuffer, void *); +typedef void (*PresentCompletionFunc) (void *); + enum _Operation { OperationOver, @@ -250,11 +256,14 @@ struct _RenderFuncs Bool (*init_render_funcs) (void); /* Create a rendering target for the given window. */ - RenderTarget (*target_from_window) (Window); + RenderTarget (*target_from_window) (Window, unsigned long); /* Create a rendering target for the given pixmap. */ RenderTarget (*target_from_pixmap) (Pixmap); + /* Set the standard event mask of the target. */ + void (*set_standard_event_mask) (RenderTarget, unsigned long); + /* Set the target width and height. This can be NULL. */ void (*note_target_size) (RenderTarget, int, int); @@ -327,6 +336,22 @@ struct _RenderFuncs called. */ int (*get_finish_fence) (Bool *); + /* Directly present the given buffer to the window, without copying, + and possibly flip the buffer contents to the screen, all while + possibly being synchronized with the vertical refresh. Call the + supplied callback when the presentation completes. May be + NULL. */ + PresentCompletionKey (*present_to_window) (RenderTarget, RenderBuffer, + pixman_region32_t *, + PresentCompletionFunc, void *); + + /* Cancel the given presentation callback. */ + void (*cancel_presentation_callback) (PresentCompletionKey); + + /* Cancel any presentation that might have happened to the window + backing the given target. */ + void (*cancel_presentation) (RenderTarget); + /* Some flags. NeverAges means targets always preserve contents that were previously drawn. */ int flags; @@ -404,6 +429,28 @@ struct _BufferFuncs by being copied to an offscreen buffer. */ Bool (*can_release_now) (RenderBuffer); + /* Run a callback once the buffer contents become idle on the given + target. NULL if flags contains ImmediateRelease. The callback + is also run when the buffer is destroyed, but not when the target + is destroyed; in that case, the callback key simply becomes + invalid. */ + IdleCallbackKey (*add_idle_callback) (RenderBuffer, RenderTarget, + BufferIdleFunc, void *); + + /* Cancel the given idle callback. */ + void (*cancel_idle_callback) (IdleCallbackKey); + + /* Return whether or not the buffer is idle. NULL if flags contains + ImmediateRelease. */ + Bool (*is_buffer_idle) (RenderBuffer, RenderTarget); + + /* Wait for a buffer to become idle on the given target. May be + NULL. */ + void (*wait_for_idle) (RenderBuffer, RenderTarget); + + /* Ensure wait_for_idle can be called. May be NULL. */ + void (*set_need_wait_for_idle) (RenderTarget); + /* Called during renderer initialization. */ void (*init_buffer_funcs) (void); }; @@ -414,8 +461,9 @@ extern void RegisterStaticRenderer (const char *, RenderFuncs *, BufferFuncs *); extern void InitRenderers (void); -extern RenderTarget RenderTargetFromWindow (Window); +extern RenderTarget RenderTargetFromWindow (Window, unsigned long); extern RenderTarget RenderTargetFromPixmap (Pixmap); +extern void RenderSetStandardEventMask (RenderTarget, unsigned long); extern void RenderNoteTargetSize (RenderTarget, int, int); extern Picture RenderPictureFromTarget (RenderTarget); extern void RenderFreePictureFromTarget (Picture); @@ -432,6 +480,12 @@ extern RenderFence RenderImportFdFence (int, Bool *); extern void RenderWaitFence (RenderFence); extern void RenderDeleteFence (RenderFence); extern int RenderGetFinishFence (Bool *); +extern PresentCompletionKey RenderPresentToWindow (RenderTarget, RenderBuffer, + pixman_region32_t *, + PresentCompletionFunc, + void *); +extern void RenderCancelPresentationCallback (PresentCompletionKey); +extern void RenderCancelPresentation (RenderTarget); extern DrmFormat *RenderGetDrmFormats (int *); extern dev_t RenderGetRenderDevice (Bool *); @@ -450,6 +504,12 @@ extern void RenderFreeSinglePixelBuffer (RenderBuffer); extern void RenderUpdateBufferForDamage (RenderBuffer, pixman_region32_t *, DrawParams *); extern Bool RenderCanReleaseNow (RenderBuffer); +extern IdleCallbackKey RenderAddIdleCallback (RenderBuffer, RenderTarget, + BufferIdleFunc, void *); +extern void RenderCancelIdleCallback (IdleCallbackKey); +extern Bool RenderIsBufferIdle (RenderBuffer, RenderTarget); +extern void RenderWaitForIdle (RenderBuffer, RenderTarget); +extern void RenderSetNeedWaitForIdle (RenderTarget); /* Defined in run.c. */ @@ -610,6 +670,15 @@ typedef struct _View View; typedef struct _List List; typedef struct _Subcompositor Subcompositor; +typedef enum _FrameMode FrameMode; + +enum _FrameMode + { + ModeStarted, + ModeComplete, + ModePresented, + }; + extern void SubcompositorInit (void); extern Subcompositor *MakeSubcompositor (void); @@ -634,6 +703,10 @@ extern void SubcompositorSetBoundsCallback (Subcompositor *, void (*) (void *, int, int, int, int), void *); +extern void SubcompositorSetNoteFrameCallback (Subcompositor *, + void (*) (FrameMode, uint64_t, + void *), + void *); extern void SubcompositorBounds (Subcompositor *, int *, int *, int *, int *); extern void SubcompositorSetProjectiveTransform (Subcompositor *, int, int); @@ -964,6 +1037,7 @@ extern void XLDefaultCommit (Surface *); extern void XLStateAttachBuffer (State *, ExtBuffer *); extern void XLStateDetachBuffer (State *); extern void XLSurfaceRunFrameCallbacks (Surface *, struct timespec); +extern void XLSurfaceRunFrameCallbacksMs (Surface *, uint32_t); extern CommitCallback *XLSurfaceRunAtCommit (Surface *, void (*) (Surface *, void *), void *); @@ -1004,6 +1078,7 @@ extern Bool XLGetOutputRectAt (int, int, int *, int *, int *, int *); extern void *XLAddScaleChangeCallback (void *, void (*) (void *, int)); extern void XLRemoveScaleChangeCallback (void *); extern void XLClearOutputs (Surface *); +extern void XLOutputSetChangeFunction (void (*) (Time)); /* Defined in atoms.c. */ @@ -1022,7 +1097,8 @@ extern Atom _NET_WM_OPAQUE_REGION, _XL_BUFFER_RELEASE, XdndActionAsk, XdndActionPrivate, XdndActionList, XdndActionDescription, XdndProxy, XdndEnter, XdndPosition, XdndStatus, XdndLeave, XdndDrop, XdndFinished, _NET_WM_FRAME_TIMINGS, _NET_WM_BYPASS_COMPOSITOR, WM_STATE, - _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_DND; + _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_DND, + CONNECTOR_ID; extern XrmQuark resource_quark, app_quark, QString; @@ -1062,6 +1138,7 @@ extern void XLFrameClockSetPredictRefresh (FrameClock *); extern void XLFrameClockDisablePredictRefresh (FrameClock *); extern void XLFrameClockSetFreezeCallback (FrameClock *, void (*) (void *), void *); +extern uint64_t XLFrameClockGetFrameTime (FrameClock *); extern void *XLAddCursorClockCallback (void (*) (void *, struct timespec), void *); extern void XLStopCursorClockCallback (void *); @@ -1477,6 +1554,10 @@ extern void XLInitDecoration (void); extern void XLInitSinglePixelBuffer (void); +/* Defined in drm_lease.h. */ + +extern void XLInitDrmLease (void); + /* Utility functions that don't belong in a specific file. */ #define ArrayElements(arr) (sizeof (arr) / sizeof (arr)[0]) diff --git a/egl.c b/egl.c index f4e8b63..813f920 100644 --- a/egl.c +++ b/egl.c @@ -1077,7 +1077,7 @@ TryPreserveOnSwap (EGLSurface *surface) } static RenderTarget -TargetFromWindow (Window window) +TargetFromWindow (Window window, unsigned long standard_event_mask) { EglTarget *target; @@ -1125,6 +1125,12 @@ TargetFromPixmap (Pixmap pixmap) return (RenderTarget) (void *) target; } +static void +SetStandardEventMask (RenderTarget target, unsigned long standard_event_mask) +{ + /* Ignored. */ +} + static void NoteTargetSize (RenderTarget target, int width, int height) { @@ -1659,6 +1665,7 @@ static RenderFuncs egl_render_funcs = .init_render_funcs = InitRenderFuncs, .target_from_window = TargetFromWindow, .target_from_pixmap = TargetFromPixmap, + .set_standard_event_mask = SetStandardEventMask, .note_target_size = NoteTargetSize, .picture_from_target = PictureFromTarget, .free_picture_from_target = FreePictureFromTarget, diff --git a/frame_clock.c b/frame_clock.c index 4f13811..d670e42 100644 --- a/frame_clock.c +++ b/frame_clock.c @@ -90,6 +90,9 @@ struct _FrameClock unfrozen until EndFrame. */ Bool need_configure, frozen, frozen_until_end_frame; + /* Whether or not EndFrame was called after StartFrame. */ + Bool end_frame_called; + /* The wanted configure value. */ uint64_t configure_id; @@ -111,6 +114,9 @@ struct _FrameClock /* Data for that callback. */ void *freeze_callback_data; + + /* Any pending frame synchronization counter value, or 0. */ + uint64_t pending_sync_value; }; struct _CursorClockCallback @@ -221,7 +227,51 @@ HandleEndFrame (Timer *timer, void *data, struct timespec time) the frame. */ RemoveTimer (timer); clock->end_frame_timer = NULL; - EndFrame (clock); + + if (clock->end_frame_called) + EndFrame (clock); +} + +/* Forward declaration. */ +static void RunFrameCallbacks (FrameClock *); + +static void +FreezeForValue (FrameClock *clock, uint64_t counter_value) +{ + /* If it took too long (1 second at 60fps) to obtain the counter + value, and said value is now out of date, don't do anything. */ + + if (clock->next_frame_id > counter_value) + return; + + /* The frame clock is now frozen, and we will have to wait for a + client to ack_configure and then commit something. */ + + if (clock->end_frame_timer) + { + /* End the frame now, and clear in_frame early. */ + RemoveTimer (clock->end_frame_timer); + clock->end_frame_timer = NULL; + + if (clock->end_frame_called) + EndFrame (clock); + } + + /* counter_value - 240 is the value seen by the compositor when the + frame contents were frozen in response to a resize. If it is + less than finished_frame_id, run frame callbacks now, or clients + like Chromium are confused and hang waiting for frame callbacks + to be called. */ + if (counter_value - 240 <= clock->finished_frame_id) + RunFrameCallbacks (clock); + + /* The reason for clearing in_frame is that otherwise a future + Commit after the configuration is acknowledged will not be able + to start a new frame and restart the frame clock. */ + clock->in_frame = False; + clock->need_configure = True; + clock->configure_id = counter_value; + clock->frozen = True; } static void @@ -320,6 +370,9 @@ StartFrame (FrameClock *clock, Bool urgent, Bool predict) if (clock->frozen_until_end_frame) return; + if (clock->in_frame) + return; + if (clock->need_configure) { clock->next_frame_id = clock->configure_id; @@ -327,6 +380,7 @@ StartFrame (FrameClock *clock, Bool urgent, Bool predict) } clock->in_frame = True; + clock->end_frame_called = False; /* Set the clock to an odd value; if we want the compositor to redraw this frame immediately (since it is running late), make it @@ -371,6 +425,10 @@ EndFrame (FrameClock *clock) clock->frozen_until_end_frame = False; + /* Signal that end_frame was called and it is now safe to finish the + frame from the timer. */ + clock->end_frame_called = True; + if (!clock->in_frame /* If the end of the frame has already been signalled, this function should just return instead of increasing the counter @@ -389,6 +447,12 @@ EndFrame (FrameClock *clock) clock->next_frame_id += 1; clock->finished_frame_id = clock->next_frame_id; + /* The frame has ended. Freeze the frame clock if there is a + pending sync value. */ + if (clock->pending_sync_value) + FreezeForValue (clock, clock->pending_sync_value); + clock->pending_sync_value = 0; + if (!frame_sync_supported) return; @@ -570,28 +634,15 @@ XLFrameClockHandleFrameEvent (FrameClock *clock, XEvent *event) if (value % 2) value += 1; - /* The frame clock is now frozen, and we will have to wait for a - client to ack_configure and then commit something. */ - - if (clock->end_frame_timer) - { - /* End the frame now, and clear in_frame early. */ - RemoveTimer (clock->end_frame_timer); - clock->end_frame_timer = NULL; - EndFrame (clock); - - /* The reason for clearing in_frame is that otherwise a - future Commit after the configuration is acknowledged - will not be able to start a new frame and restart the - frame clock. */ - clock->in_frame = False; - } - - clock->need_configure = True; - clock->configure_id = value; - clock->frozen = True; + /* If a frame is in progress, postpone this frame + synchronization message. */ + if (clock->in_frame && !clock->end_frame_called) + clock->pending_sync_value = value; + else + FreezeForValue (clock, value); if (clock->freeze_callback) + /* Call the freeze callback in any case. */ clock->freeze_callback (clock->freeze_callback_data); } } @@ -731,6 +782,16 @@ XLFrameClockSetFreezeCallback (FrameClock *clock, void (*callback) (void *), clock->freeze_callback_data = data; } +uint64_t +XLFrameClockGetFrameTime (FrameClock *clock) +{ + /* Only return the time if it is actually a valid clock time. */ + if (!compositor.server_time_monotonic) + return 0; + + return clock->last_frame_time; +} + /* Cursor animation clock-related functions. */ diff --git a/icon_surface.c b/icon_surface.c index 6884f3f..b8235fc 100644 --- a/icon_surface.c +++ b/icon_surface.c @@ -155,9 +155,14 @@ Setup (Surface *surface, Role *role) static void ReleaseBuffer (Surface *surface, Role *role, ExtBuffer *buffer) { + IconSurface *icon; + + icon = IconSurfaceFromRole (role); + /* Icon surfaces are not supposed to change much, so doing an XSync - here is okay. */ - XSync (compositor.display, False); + (or XIfEvent) here is okay. */ + RenderWaitForIdle (XLRenderBufferFromBuffer (buffer), + icon->target); /* Now really release the buffer. */ XLReleaseBuffer (buffer); @@ -213,17 +218,25 @@ NoteBounds (void *data, int min_x, int min_y, int max_x, int max_y) } static void -RunFrameCallbacks (Surface *surface) +RunFrameCallbacks (Surface *surface, FrameClock *clock) { struct timespec time; + uint64_t last_drawn_time; /* Surface can be NULL for various reasons, especially events arriving after the icon surface is detached. */ if (!surface) return; - clock_gettime (CLOCK_MONOTONIC, &time); - XLSurfaceRunFrameCallbacks (surface, time); + last_drawn_time = XLFrameClockGetFrameTime (clock); + + if (!last_drawn_time) + { + clock_gettime (CLOCK_MONOTONIC, &time); + XLSurfaceRunFrameCallbacks (surface, time); + } + else + XLSurfaceRunFrameCallbacksMs (surface, last_drawn_time / 1000); } static void @@ -246,7 +259,7 @@ AfterFrame (FrameClock *clock, void *data) return; } - RunFrameCallbacks (icon->role.surface); + RunFrameCallbacks (icon->role.surface, clock); } static void @@ -410,7 +423,11 @@ XLGetIconSurface (Surface *surface) (unsigned char *) &_NET_WM_WINDOW_TYPE_DND, 1); /* Create a target associated with the window. */ - role->target = RenderTargetFromWindow (role->window); + role->target = RenderTargetFromWindow (role->window, None); + + /* For simplicity reasons we do not handle idle notifications + asynchronously. */ + RenderSetNeedWaitForIdle (role->target); /* Create a subcompositor associated with the window. */ role->subcompositor = MakeSubcompositor (); diff --git a/output.c b/output.c index cdc5073..19349aa 100644 --- a/output.c +++ b/output.c @@ -98,6 +98,9 @@ static ScaleChangeCallback scale_callbacks; /* The scale factor currently applied on a global basis. */ int global_scale_factor; +/* Function run upon any kind of XRandR notify event. */ +static void (*change_hook) (Time); + static Bool ApplyEnvironment (const char *name, int *variable) { @@ -903,9 +906,38 @@ XLGetOutputRectAt (int x, int y, int *x_out, int *y_out, Bool XLHandleOneXEventForOutputs (XEvent *event) { + XRRNotifyEvent *notify; + XRROutputPropertyNotifyEvent *property; + XRRResourceChangeNotifyEvent *resource; + Time time; + + notify = (XRRNotifyEvent *) event; + if (event->type == compositor.rr_event_base + RRNotify) { NoticeOutputsMaybeChanged (); + + if (change_hook) + { + time = CurrentTime; + + /* See if a timestamp of some sort can be extracted. */ + switch (notify->subtype) + { + case RRNotify_OutputProperty: + property = (XRROutputPropertyNotifyEvent *) notify; + time = property->timestamp; + break; + + case RRNotify_ResourceChange: + resource = (XRRResourceChangeNotifyEvent *) notify; + time = resource->timestamp; + break; + } + + change_hook (time); + } + return True; } @@ -1053,6 +1085,12 @@ XLClearOutputs (Surface *surface) surface->n_outputs = 0; } +void +XLOutputSetChangeFunction (void (*change_func) (Time)) +{ + change_hook = change_func; +} + void XLInitRROutputs (void) { @@ -1096,11 +1134,21 @@ XLInitRROutputs (void) scale_callbacks.next = &scale_callbacks; scale_callbacks.last = &scale_callbacks; - XRRSelectInput (compositor.display, - DefaultRootWindow (compositor.display), - (RRCrtcChangeNotifyMask - | RROutputChangeNotifyMask - | RROutputPropertyNotifyMask)); + if (compositor.rr_major > 1 + && (compositor.rr_major == 1 + && compositor.rr_minor >= 4)) + XRRSelectInput (compositor.display, + DefaultRootWindow (compositor.display), + (RRCrtcChangeNotifyMask + | RROutputChangeNotifyMask + | RROutputPropertyNotifyMask + | RRResourceChangeNotifyMask)); + else + XRRSelectInput (compositor.display, + DefaultRootWindow (compositor.display), + (RRCrtcChangeNotifyMask + | RROutputChangeNotifyMask + | RROutputPropertyNotifyMask)); all_outputs = BuildOutputTree (); MakeGlobalsForOutputTree (all_outputs); diff --git a/picture_renderer.c b/picture_renderer.c index e3fdd91..8ccf3b7 100644 --- a/picture_renderer.c +++ b/picture_renderer.c @@ -31,23 +31,164 @@ along with 12to11. If not, see . */ #include #include +#include +#include +#include typedef struct _DrmFormatInfo DrmFormatInfo; typedef struct _DmaBufRecord DmaBufRecord; typedef struct _PictureBuffer PictureBuffer; +typedef struct _PictureTarget PictureTarget; +typedef struct _PresentRecord PresentRecord; + +typedef struct _BufferActivityRecord BufferActivityRecord; +typedef struct _IdleCallback IdleCallback; +typedef struct _PresentCompletionCallback PresentCompletionCallback; + +/* Structure describing an expected PresentIdleNotify from the X + server. */ + +struct _PresentRecord +{ + /* The next and last fields on the buffer. */ + PresentRecord *buffer_next, *buffer_last; + + /* The next and last fields on the target. */ + PresentRecord *target_next, *target_last; + + /* The buffer. */ + PictureBuffer *buffer; + + /* The target. */ + PictureTarget *target; + + /* The expected serial. */ + uint32_t serial; +}; + +/* Structure describing buffer activity. It is linked onto 3 (!!!) + lists. */ + +struct _BufferActivityRecord +{ + /* The buffer. */ + PictureBuffer *buffer; + + /* The target. */ + PictureTarget *target; + + /* The counter ID. */ + uint64_t id; + + /* The forward links to the three lists. */ + BufferActivityRecord *buffer_next, *target_next, *global_next; + + /* The backlinks to the three lists. */ + BufferActivityRecord *buffer_last, *target_last, *global_last; +}; + +struct _IdleCallback +{ + /* The next and last callbacks in this list, attached to the + buffer. */ + IdleCallback *buffer_next, *buffer_last; + + /* The next and last callbacks in this list, attached to the + target. */ + IdleCallback *target_next, *target_last; + + /* The associated target. */ + PictureTarget *target; + + /* The callback data. */ + void *data; + + /* The callback function. */ + BufferIdleFunc function; +}; + +enum + { + CanPresent = 1, + }; struct _PictureBuffer { /* The XID of the picture. */ Picture picture; + /* The picture's backing pixmap. */ + Pixmap pixmap; + + /* The depth of the picture's backing pixmap. */ + int depth; + + /* Flags. */ + int flags; + /* The last draw params associated with the picture. */ DrawParams params; + + /* List of release records. */ + PresentRecord pending; + + /* List of idle callbacks. */ + IdleCallback idle_callbacks; + + /* Ongoing buffer activity. */ + BufferActivityRecord activity; +}; + +enum + { + PresentationWindowMapped = 1, + NoPresentation = 2, + }; + +struct _PictureTarget +{ + /* The XID of the picture. */ + Picture picture; + + /* The backing window. */ + Window window; + + /* The presentation window. This is a window used to present + pixmaps to the source. */ + Window presentation_window; + + /* Presentation event context. */ + XID presentation_event_context; + + /* The standard event mask. */ + unsigned long standard_event_mask; + + /* The last known bounds of this render target. */ + int width, height; + + /* Flags. */ + int flags; + + /* List of release records. */ + PresentRecord pending; + + /* List of idle callbacks. */ + IdleCallback idle_callbacks; + + /* Ongoing buffer activity. */ + BufferActivityRecord activity; }; struct _DrmFormatInfo { + /* PictFormat associated with this format, or NULL if none were + found. */ + XRenderPictFormat *format; + + /* List of supported screen modifiers. */ + uint64_t *supported_modifiers; + /* The DRM format code. */ uint32_t format_code; @@ -60,13 +201,6 @@ struct _DrmFormatInfo /* The number of bits per pixel. */ int bits_per_pixel; - /* PictFormat associated with this format, or NULL if none were - found. */ - XRenderPictFormat *format; - - /* List of supported screen modifiers. */ - uint64_t *supported_modifiers; - /* Number of supported screen modifiers. */ int n_supported_modifiers; }; @@ -90,8 +224,35 @@ struct _DmaBufRecord /* The next and last pending buffers in this list. */ DmaBufRecord *next, *last; + + /* The depth of the pixmap. */ + int depth; }; +/* Structure describing presentation callback. The callback is run + upon a given presentation being completed. */ + +struct _PresentCompletionCallback +{ + /* The next and last presentation callbacks. */ + PresentCompletionCallback *next, *last; + + /* The callback itself. */ + PresentCompletionFunc callback; + + /* Data the callback will be called with. */ + void *data; + + /* The function. */ + PresentCompletionFunc function; + + /* The presentation ID. */ + uint32_t id; +}; + +/* Hash table mapping between presentation windows and targets. */ +static XLAssocTable *xid_table; + /* The identity transform. */ static XTransform identity_transform; @@ -152,6 +313,24 @@ static DrmFormatInfo all_formats[] = .alpha = 0xff, .bits_per_pixel = 32, }, + { + .format_code = DRM_FORMAT_XRGB4444, + .depth = 15, + .red = 0xf00, + .green = 0xf0, + .blue = 0xf, + .alpha = 0x0, + .bits_per_pixel = 16, + }, + { + .format_code = DRM_FORMAT_ARGB4444, + .depth = 16, + .red = 0xf00, + .green = 0xf0, + .blue = 0xf, + .alpha = 0xf000, + .bits_per_pixel = 16, + }, }; /* DRM formats reported to the caller. */ @@ -178,8 +357,277 @@ static XPixmapFormatValues *x_formats; /* Number of those formats. */ static int num_x_formats; -/* XRender and DRI3-based renderer. A RenderTarget is just a - Picture. */ +/* The serial for PresentNotify events. */ +static uint32_t present_serial; + +/* The major opcode of the presentation extension. */ +static int present_opcode; + +/* Ongoing buffer activity. */ +static BufferActivityRecord all_activity; + +/* List of all presentations that have not yet been completed. */ +static PresentCompletionCallback all_completion_callbacks; + +/* XRender, DRI3 and XPresent-based renderer. A RenderTarget is just + a Picture. Here is a rough explanation of how the buffer release + machinery works. + + Normally, upon a request to composite a buffer to a target, certain + rendering commands are issued to the X server, after which a + counter is increased. Once an event is received confirming the + increase in the counter (by which time all rendering requests + should have been processed by the server), the idle callback is run + for the pertinent target. + + If the target is destroyed, the idle callback is not run. + + However, the buffer is presented to a target instead of being + composited, that fact is noted, and the idle callback is run upon + receiving the PresentIdleNotify event instead. + + And here is an explanation of pixmaps for dummies. + + A pixmap is just a pixel buffer. The X server supports several + pixel storage formats, each of which maps between a depth, the + number of bits per pixel, and the scanline pad. The depth is the + number of significant bits inside each pixel, the bits per pixel + contains the number of bits taken up by a pixel (it is always a + multiple of 8), and the scanline pad is the value (in bits) by + which the stride must be padded. So, given a depth of 24, a bpp of + 32, and a scanline_pad of 32, the stride of a pixmap for a given + width is: + + Pad (WIDTH * (32 / 8), 32 / 8) + + Pixmaps themselves have no format information; they are simply a + collection of pixel values. Normally, the visual tells the X + server how to put the pixel onto the display server, but in the X + rendering extension a picture format does instead. The picture + format specifies the alpha, red, green, and blue channel masks that + are then used to put together the color corresponding to a pixel. + + A buffer must thus consist of a picture format, and a pixmap with a + specified depth to be useful. + + X always uses premultiplied alpha. Thankfully, Wayland does + too. */ + + + +static uint64_t +SendRoundtripMessage (void) +{ + XEvent event; + static uint64_t id; + + /* Send a message to the X server with a monotonically increasing + counter. This is necessary because the connection to the X + server does not behave synchronously, and receiving the message + tells us that the X server has finished processing all requests + that access the buffer. */ + + memset (&event, 0, sizeof event); + + id += 1; + event.xclient.type = ClientMessage; + event.xclient.window = round_trip_window; + event.xclient.message_type = _XL_BUFFER_RELEASE; + event.xclient.format = 32; + + event.xclient.data.l[0] = id >> 31 >> 1; + event.xclient.data.l[1] = id & 0xffffffff; + + XSendEvent (compositor.display, round_trip_window, False, + NoEventMask, &event); + return id; +} + +/* Find an existing buffer activity record matching the given buffer + and target. */ + +static BufferActivityRecord * +FindBufferActivityRecord (PictureBuffer *buffer, PictureTarget *target) +{ + BufferActivityRecord *record; + + /* Look through the global activity list for a record matching + the given values. */ + record = all_activity.global_next; + while (record != &all_activity) + { + if (record->buffer == buffer + && record->target == target) + return record; + + record = record->global_next; + } + + return NULL; +} + +/* Record buffer activity involving the given buffer and target. */ + +static void +RecordBufferActivity (RenderBuffer src, RenderTarget dest) +{ + BufferActivityRecord *record; + PictureBuffer *buffer; + PictureTarget *target; + + buffer = src.pointer; + target = dest.pointer; + + /* Try to find an existing record. */ + record = FindBufferActivityRecord (buffer, target); + + if (!record) + { + record = XLCalloc (1, sizeof *record); + + /* Buffer activity is actually linked on 3 different lists: + + - a global list, which is used to actually look up buffer + activity in response to events. A list is faster than a + hash table, as there is not much activity going on at any + given time. + + - a buffer list, which is used to remove buffer activity on + buffer destruction. + + - a target list, which is used to remove buffer activity on + target destruction. */ + record->buffer_next = buffer->activity.buffer_next; + record->buffer_last = &buffer->activity; + record->target_next = target->activity.target_next; + record->target_last = &target->activity; + record->global_next = all_activity.global_next; + record->global_last = &all_activity; + buffer->activity.buffer_next->buffer_last = record; + buffer->activity.buffer_next = record; + target->activity.target_next->target_last = record; + target->activity.target_next = record; + all_activity.global_next->global_last = record; + all_activity.global_next = record; + + /* Set the appropriate values. */ + record->buffer = buffer; + record->target = target; + } + + record->id = SendRoundtripMessage (); +} + +static void +RunIdleCallbacks (PictureBuffer *buffer, PictureTarget *target) +{ + IdleCallback *callback, *last; + + callback = buffer->idle_callbacks.buffer_next; + while (callback != &buffer->idle_callbacks) + { + if (callback->target == target) + { + /* Run the callback and then free it. */ + callback->function ((RenderBuffer) (void *) buffer, + callback->data); + callback->buffer_next->buffer_last = callback->buffer_last; + callback->buffer_last->buffer_next = callback->buffer_next; + callback->target_next->target_last = callback->target_last; + callback->target_last->target_next = callback->target_next; + last = callback; + callback = callback->buffer_next; + XLFree (last); + } + else + callback = callback->buffer_next; + } + + return; +} + +static void +MaybeRunIdleCallbacks (PictureBuffer *buffer, PictureTarget *target) +{ + BufferActivityRecord *record; + PresentRecord *presentation; + + /* Look through BUFFER's list of activity and presentation records. + If there is nothing left pertaining to TARGET, then run idle + callbacks. */ + record = buffer->activity.buffer_next; + while (record != &buffer->activity) + { + if (record->target == target) + /* There is still pending activity. */ + return; + + record = record->buffer_next; + } + + /* Next, loop through BUFFER's list of presentation records. If the + buffer is still busy on TARGET, then return. */ + presentation = buffer->pending.buffer_next; + while (presentation != &buffer->pending) + { + if (presentation->target == target) + /* There is still pending activity. */ + return; + + presentation = presentation->buffer_next; + } + + /* Run idle callbacks. */ + RunIdleCallbacks (buffer, target); +} + +static void +UnlinkActivityRecord (BufferActivityRecord *record) +{ + record->buffer_last->buffer_next = record->buffer_next; + record->buffer_next->buffer_last = record->buffer_last; + record->target_last->target_next = record->target_next; + record->target_next->target_last = record->target_last; + record->global_last->global_next = record->global_next; + record->global_next->global_last = record->global_last; +} + +/* Handle an event saying that the X server has completed everything + up to ID. */ + +static void +HandleActivityEvent (uint64_t counter) +{ + BufferActivityRecord *record; + + /* Look through the global activity list for a record matching + counter. */ + record = all_activity.global_next; + while (record != &all_activity) + { + if (record->id == counter) + break; + + record = record->global_next; + } + + /* If record is all_activity (meaning no matching record was found), + return. */ + if (record == &all_activity) + return; + + /* Remove the record. Then, run any callbacks pertaining to it. + This code mandates that there only be a single activity record + for each buffer-target combination on the global list at any + given time. */ + UnlinkActivityRecord (record); + MaybeRunIdleCallbacks (record->buffer, record->target); + + /* Free the record. */ + XLFree (record); +} + + static Visual * PickVisual (int *depth) @@ -212,44 +660,166 @@ PickVisual (int *depth) static Bool InitRenderFuncs (void) { + int major, minor, error_base, event_base; + XSetWindowAttributes attrs; + /* Set up the default visual. */ compositor.visual = PickVisual (&compositor.n_planes); + /* Initialize the presentation extension. */ + if (!XPresentQueryExtension (compositor.display, + &present_opcode, + &error_base, &event_base) + || !XPresentQueryVersion (compositor.display, + &major, &minor)) + { + fprintf (stderr, "the X presentation extension is not supported" + " by this X server"); + return False; + } + + /* Create an unmapped, InputOnly window, that is used to receive + roundtrip events. */ + attrs.override_redirect = True; + round_trip_window = XCreateWindow (compositor.display, + DefaultRootWindow (compositor.display), + -1, -1, 1, 1, 0, CopyFromParent, InputOnly, + CopyFromParent, CWOverrideRedirect, + &attrs); + + /* Initialize the hash table between presentation windows and + targets. */ + xid_table = XLCreateAssocTable (256); + /* Return success if the visual was found. */ return compositor.visual != NULL; } static RenderTarget -TargetFromDrawable (Drawable drawable) +TargetFromDrawable (Drawable drawable, Window window, + unsigned long standard_event_mask) { XRenderPictureAttributes picture_attrs; + PictureTarget *target; /* This is just to pacify GCC; picture_attrs is not used as mask is 0. */ memset (&picture_attrs, 0, sizeof picture_attrs); + target = XLCalloc (1, sizeof *target); + target->window = window; + target->picture = XRenderCreatePicture (compositor.display, + drawable, + compositor.argb_format, + 0, &picture_attrs); - return (RenderTarget) XRenderCreatePicture (compositor.display, - drawable, - compositor.argb_format, - 0, &picture_attrs); + /* Initialize the list of release records. */ + target->pending.target_next = &target->pending; + target->pending.target_last = &target->pending; + + /* Initialize the list of target activity. */ + target->activity.target_next = &target->activity; + target->activity.target_last = &target->activity; + + /* And idle callbacks. */ + target->idle_callbacks.target_next = &target->idle_callbacks; + target->idle_callbacks.target_last = &target->idle_callbacks; + + /* And the event mask. */ + target->standard_event_mask = standard_event_mask; + + return (RenderTarget) (void *) target; } static RenderTarget TargetFromPixmap (Pixmap pixmap) { - return TargetFromDrawable (pixmap); + return TargetFromDrawable (pixmap, None, NoEventMask); } static RenderTarget -TargetFromWindow (Window window) +TargetFromWindow (Window window, unsigned long event_mask) { - return TargetFromDrawable (window); + return TargetFromDrawable (window, window, event_mask); +} + +static void +SetStandardEventMask (RenderTarget target, unsigned long standard_event_mask) +{ + PictureTarget *pict_target; + + pict_target = target.pointer; + + /* Set the standard event mask. This is used to temporarily + suppress exposures. */ + pict_target->standard_event_mask = standard_event_mask; +} + +static void +NoteTargetSize (RenderTarget target, int width, int height) +{ + PictureTarget *pict_target; + XSetWindowAttributes attrs; + + pict_target = target.pointer; + + if (pict_target->presentation_window != None) + { + /* The presentation window already exists; resize it if the size + changed. */ + + if (pict_target->width != width + || pict_target->height != height) + { + XResizeWindow (compositor.display, + pict_target->presentation_window, + width, height); + pict_target->width = width; + pict_target->height = height; + } + + return; + } + + pict_target->width = width; + pict_target->height = height; + + if (pict_target->window == None) + /* This is a pixmap target. */ + return; + + /* Create a window that will be used to present pixmaps to the + screen. */ + pict_target->presentation_window + = XCreateWindow (compositor.display, pict_target->window, + 0, 0, width, height, 0, CopyFromParent, + InputOutput, CopyFromParent, 0, &attrs); + + /* Empty the input shape. */ + XShapeCombineRectangles (compositor.display, + pict_target->presentation_window, + ShapeInput, 0, 0, NULL, 0, ShapeSet, + Unsorted); + + /* Start selecting for presentation events from the given + window. */ + pict_target->presentation_event_context + = XPresentSelectInput (compositor.display, + pict_target->presentation_window, + PresentIdleNotifyMask + | PresentCompleteNotifyMask); + + /* Add the window to the hash table. */ + XLMakeAssoc (xid_table, pict_target->presentation_window, + pict_target); } static Picture PictureFromTarget (RenderTarget target) { - return target.xid; + PictureTarget *pict_target; + + pict_target = target.pointer; + return pict_target->picture; } static void @@ -258,10 +828,73 @@ FreePictureFromTarget (Picture picture) /* There is no need to free these pictures. */ } +static void +RemovePresentRecord (PresentRecord *record) +{ + record->target_next->target_last = record->target_last; + record->target_last->target_next = record->target_next; + record->buffer_next->buffer_last = record->buffer_last; + record->buffer_last->buffer_next = record->buffer_next; + + XLFree (record); +} + static void DestroyRenderTarget (RenderTarget target) { - XRenderFreePicture (compositor.display, target.xid); + PictureTarget *pict_target; + PresentRecord *record, *last; + BufferActivityRecord *activity_record, *activity_last; + IdleCallback *idle, *idle_last; + + pict_target = target.pointer; + + if (pict_target->presentation_window) + { + XPresentFreeInput (compositor.display, + pict_target->presentation_window, + pict_target->presentation_event_context); + XDestroyWindow (compositor.display, + pict_target->presentation_window); + XLDeleteAssoc (xid_table, pict_target->presentation_window); + + /* Free attached presentation records. */ + record = pict_target->pending.target_next; + while (record != &pict_target->pending) + { + last = record; + record = record->target_next; + + /* Free the record. */ + RemovePresentRecord (last); + } + } + + /* Free all activity associated with this target. */ + activity_record = pict_target->activity.target_next; + while (activity_record != &pict_target->activity) + { + activity_last = activity_record; + activity_record = activity_record->target_next; + + UnlinkActivityRecord (activity_last); + XLFree (activity_last); + } + + /* Free all idle callbacks on this target. */ + idle = pict_target->idle_callbacks.target_next; + while (idle != &pict_target->idle_callbacks) + { + idle_last = idle; + idle = idle->target_next; + + /* Free the callback without doing anything else with it. */ + XLFree (idle_last); + } + + XRenderFreePicture (compositor.display, + pict_target->picture); + XFree (pict_target); } static void @@ -271,6 +904,9 @@ FillBoxesWithTransparency (RenderTarget target, pixman_box32_t *boxes, XRectangle *rects; static XRenderColor color; int i; + PictureTarget *pict_target; + + pict_target = target.pointer; if (nboxes < 256) rects = alloca (sizeof *rects * nboxes); @@ -286,19 +922,56 @@ FillBoxesWithTransparency (RenderTarget target, pixman_box32_t *boxes, } XRenderFillRectangles (compositor.display, PictOpClear, - target.xid, &color, rects, nboxes); + pict_target->picture, &color, rects, + nboxes); if (nboxes >= 256) XLFree (rects); } +static XserverRegion +ServerRegionFromRegion (pixman_region32_t *region) +{ + XRectangle *rects; + int i, nboxes; + pixman_box32_t *boxes; + XserverRegion server_region; + + boxes = pixman_region32_rectangles (region, &nboxes); + + if (nboxes < 256) + rects = alloca (sizeof *rects * nboxes); + else + rects = XLMalloc (sizeof *rects * nboxes); + + for (i = 0; i < nboxes; ++i) + { + rects[i].x = BoxStartX (boxes[i]); + rects[i].y = BoxStartY (boxes[i]); + rects[i].width = BoxWidth (boxes[i]); + rects[i].height = BoxHeight (boxes[i]); + } + + server_region = XFixesCreateRegion (compositor.display, rects, + nboxes); + + if (nboxes >= 256) + XLFree (rects); + + return server_region; +} + static void ClearRectangle (RenderTarget target, int x, int y, int width, int height) { + PictureTarget *pict_target; static XRenderColor color; + pict_target = target.pointer; + XRenderFillRectangle (compositor.display, PictOpClear, - target.xid, &color, x, y, width, height); + pict_target->picture, &color, x, y, + width, height); } static int @@ -416,8 +1089,10 @@ Composite (RenderBuffer buffer, RenderTarget target, int width, int height, DrawParams *draw_params) { PictureBuffer *picture_buffer; + PictureTarget *picture_target; picture_buffer = buffer.pointer; + picture_target = target.pointer; /* Maybe set the transform if the parameters changed. (draw_params specifies a transform to apply to the buffer, not to the @@ -426,11 +1101,15 @@ Composite (RenderBuffer buffer, RenderTarget target, /* Do the compositing. */ XRenderComposite (compositor.display, ConvertOperation (op), - picture_buffer->picture, None, target.xid, + picture_buffer->picture, None, + picture_target->picture, /* src-x, src-y, mask-x, mask-y. */ src_x, src_y, 0, 0, /* dst-x, dst-y, width, height. */ x, y, width, height); + + /* Record pending buffer activity. */ + RecordBufferActivity (buffer, target); } static int @@ -471,11 +1150,191 @@ GetFinishFence (Bool *error) return -1; } +static PresentRecord * +FindPresentRecord (PictureBuffer *buffer, PictureTarget *target) +{ + PresentRecord *record; + + record = buffer->pending.buffer_next; + while (record != &buffer->pending) + { + if (record->target == target) + return record; + + record = record->buffer_next; + } + + /* No matching record was found. */ + return NULL; +} + +static PresentRecord * +AllocateRecord (PictureBuffer *buffer, PictureTarget *target) +{ + PresentRecord *record; + + /* Allocate a record and link it onto both BUFFER and TARGET. */ + record = XLCalloc (1, sizeof *record); + record->buffer_next = buffer->pending.buffer_next; + record->buffer_last = &buffer->pending; + buffer->pending.buffer_next->buffer_last = record; + buffer->pending.buffer_next = record; + record->target_next = target->pending.target_next; + record->target_last = &target->pending; + target->pending.target_next->target_last = record; + target->pending.target_next = record; + record->buffer = buffer; + record->target = target; + + return record; +} + +/* Direct presentation support. When a surface has no transform + applied, its pixmap can be presented directly onto a window + target. */ + +static PresentCompletionKey +PresentToWindow (RenderTarget target, RenderBuffer source, + pixman_region32_t *damage, + PresentCompletionFunc callback, void *data) +{ + PictureBuffer *buffer; + PictureTarget *pict_target; + XserverRegion region; + PresentRecord *record; + PresentCompletionCallback *callback_rec; + + /* Present SOURCE onto TARGET. Return False if the presentation is + not supported. */ + buffer = source.pointer; + pict_target = target.pointer; + + if (pict_target->flags & NoPresentation) + return NULL; + + /* If the depth is not the same as the window, don't present. */ + if (buffer->depth != compositor.n_planes) + return NULL; + + if (!(buffer->flags & CanPresent)) + return NULL; + + if (pict_target->presentation_window == None) + return NULL; + + /* Map the presentation window if necessary. */ + + if (!(pict_target->flags & PresentationWindowMapped)) + XMapWindow (compositor.display, + pict_target->presentation_window); + + pict_target->flags |= PresentationWindowMapped; + + /* Build the damage region. */ + if (damage) + region = ServerRegionFromRegion (damage); + else + region = None; + + /* Present the pixmap now from the damage, immediately. */ + XPresentPixmap (compositor.display, + pict_target->presentation_window, buffer->pixmap, + ++present_serial, None, region, 0, 0, None, + None, None, PresentOptionAsync, 0, 0, 0, + NULL, 0); + + if (region) + XFixesDestroyRegion (compositor.display, region); + + /* Now, we must wait for pict_target to release the buffer. See if + a presentation to this target has already been recorded. */ + record = FindPresentRecord (buffer, pict_target); + + /* If it was, just set the serial. */ + if (record) + record->serial = present_serial; + else + { + /* Otherwise, allocate and attach a record. */ + record = AllocateRecord (buffer, pict_target); + record->serial = present_serial; + } + + /* Allocate a presentation completion callback. */ + callback_rec = XLMalloc (sizeof *callback_rec); + callback_rec->id = present_serial; + callback_rec->next = all_completion_callbacks.next; + callback_rec->last = &all_completion_callbacks; + all_completion_callbacks.next->last = callback_rec; + all_completion_callbacks.next = callback_rec; + callback_rec->function = callback; + callback_rec->data = data; + callback_rec->id = present_serial; + + return callback_rec; +} + +/* Cancel the given presentation callback. */ + +static void +CancelPresentationCallback (PresentCompletionKey key) +{ + PresentCompletionCallback *callback; + + callback = key; + callback->next->last = callback->last; + callback->last->next = callback->next; + + XLFree (callback); +} + +static void +CancelPresentation (RenderTarget target) +{ + PictureTarget *pict_target; + PictureBuffer *last_buffer; + PresentRecord *record, *last; + + pict_target = target.pointer; + + if (pict_target->flags & PresentationWindowMapped) + { + /* Temporarily suppress exposures on the parent. */ + XSelectInput (compositor.display, pict_target->window, + pict_target->standard_event_mask &~ ExposureMask); + + XUnmapWindow (compositor.display, + pict_target->presentation_window); + + /* Allow exposures again. */ + XSelectInput (compositor.display, pict_target->window, + pict_target->standard_event_mask); + } + + pict_target->flags &= ~PresentationWindowMapped; + + /* Release every buffer presented up to this point. */ + record = pict_target->pending.target_next; + while (record != &pict_target->pending) + { + last = record; + record = record->target_next; + + last_buffer = last->buffer; + RemovePresentRecord (last); + + /* Run idle callbacks if this is now idle. */ + MaybeRunIdleCallbacks (last_buffer, pict_target); + } +} + static RenderFuncs picture_render_funcs = { .init_render_funcs = InitRenderFuncs, .target_from_window = TargetFromWindow, .target_from_pixmap = TargetFromPixmap, + .set_standard_event_mask = SetStandardEventMask, + .note_target_size = NoteTargetSize, .picture_from_target = PictureFromTarget, .free_picture_from_target = FreePictureFromTarget, .destroy_render_target = DestroyRenderTarget, @@ -487,6 +1346,9 @@ static RenderFuncs picture_render_funcs = .wait_fence = WaitFence, .delete_fence = DeleteFence, .get_finish_fence = GetFinishFence, + .present_to_window = PresentToWindow, + .cancel_presentation_callback = CancelPresentationCallback, + .cancel_presentation = CancelPresentation, .flags = NeverAges, }; @@ -599,7 +1461,7 @@ FindSupportedModifiers (int *pair_count_return) Window check_window; xcb_dri3_get_supported_modifiers_cookie_t *cookies; xcb_dri3_get_supported_modifiers_reply_t *reply; - int i, length, pair_count; + int i, length, pair_count, j; uint64_t *mods; cookies = alloca (sizeof *cookies * ArrayElements (all_formats)); @@ -620,8 +1482,8 @@ FindSupportedModifiers (int *pair_count_return) /* pair_count is the number of format-modifier pairs that will be returned. First, add one for each implicit - modifier. */ - pair_count += 1; + modifier, and another one for the linear modifier. */ + pair_count += 2; } } @@ -647,8 +1509,15 @@ FindSupportedModifiers (int *pair_count_return) all_formats[i].supported_modifiers = XLMalloc (sizeof *mods * length); all_formats[i].n_supported_modifiers = length; - /* Then, add length for each explicit modifier. */ - pair_count += length; + for (j = 0; j < length; ++j) + { + /* Then, add length for each explicit modifier that wasn't + already specified. */ + + if (mods[j] != DRM_FORMAT_MOD_INVALID + && mods[j] != DRM_FORMAT_MOD_LINEAR) + pair_count += length; + } memcpy (all_formats[i].supported_modifiers, mods, sizeof *mods * length); @@ -689,13 +1558,28 @@ InitDrmFormats (void) drm_formats[n].drm_modifier = DRM_FORMAT_MOD_INVALID; n++; + /* Check n < pair_count. */ + XLAssert (n < pair_count); + + /* And the linear modifier. */ + drm_formats[n].drm_format = all_formats[i].format_code; + drm_formats[n].drm_modifier = DRM_FORMAT_MOD_LINEAR; + n++; + /* Now add every supported explicit modifier. */ for (j = 0; j < all_formats[i].n_supported_modifiers; ++i) { + if ((all_formats[i].supported_modifiers[j] + == DRM_FORMAT_MOD_INVALID) + || (all_formats[i].supported_modifiers[j] + == DRM_FORMAT_MOD_LINEAR)) + /* Ignore previously specified modifiers. */ + continue; + /* Check n < pair_count. */ XLAssert (n < pair_count); - /* Add the implicit modifier. */ + /* Add the specified modifier. */ drm_formats[n].drm_format = all_formats[i].format_code; drm_formats[n].drm_modifier = all_formats[i].supported_modifiers[j]; @@ -827,6 +1711,18 @@ CloseFileDescriptors (DmaBufAttributes *attributes) close (attributes->fds[i]); } +static Bool +PictFormatIsPresentable (XRenderPictFormat *format) +{ + /* If format has the same masks as the visual format, then it is + presentable. */ + if (!memcmp (&format->direct, &compositor.argb_format->direct, + sizeof format->direct)) + return True; + + return False; +} + static RenderBuffer BufferFromDmaBuf (DmaBufAttributes *attributes, Bool *error) { @@ -881,11 +1777,28 @@ BufferFromDmaBuf (DmaBufAttributes *attributes, Bool *error) picture = XRenderCreatePicture (compositor.display, pixmap, format, 0, &picture_attrs); - XFreePixmap (compositor.display, pixmap); /* Create the wrapper object. */ buffer = XLCalloc (1, sizeof *buffer); buffer->picture = picture; + buffer->pixmap = pixmap; + buffer->depth = depth; + + /* Initialize the list of release records. */ + buffer->pending.buffer_next = &buffer->pending; + buffer->pending.buffer_last = &buffer->pending; + + /* And the list of idle funcs. */ + buffer->idle_callbacks.buffer_next = &buffer->idle_callbacks; + buffer->idle_callbacks.buffer_last = &buffer->idle_callbacks; + + /* And the list of pending activity. */ + buffer->activity.buffer_next = &buffer->activity; + buffer->activity.buffer_last = &buffer->activity; + + /* If the format is presentable, mark the buffer as presentable. */ + if (PictFormatIsPresentable (format)) + buffer->flags |= CanPresent; return (RenderBuffer) (void *) buffer; @@ -939,11 +1852,28 @@ FinishDmaBufRecord (DmaBufRecord *pending, Bool success) pending->pixmap, pending->format, 0, &picture_attrs); - XFreePixmap (compositor.display, pending->pixmap); /* Create the wrapper structure. */ buffer = XLCalloc (1, sizeof *buffer); buffer->picture = picture; + buffer->pixmap = pending->pixmap; + buffer->depth = pending->depth; + + /* Initialize the list of release records. */ + buffer->pending.buffer_next = &buffer->pending; + buffer->pending.buffer_last = &buffer->pending; + + /* And the list of idle funcs. */ + buffer->idle_callbacks.buffer_next = &buffer->idle_callbacks; + buffer->idle_callbacks.buffer_last = &buffer->idle_callbacks; + + /* And the list of pending activity. */ + buffer->activity.buffer_next = &buffer->activity; + buffer->activity.buffer_last = &buffer->activity; + + /* If the format is presentable, mark the buffer as presentable. */ + if (PictFormatIsPresentable (pending->format)) + buffer->flags |= CanPresent; /* Call the creation success function with the new picture. */ pending->success_func ((RenderBuffer) (void *) buffer, @@ -1029,6 +1959,7 @@ BufferFromDmaBufAsync (DmaBufAttributes *attributes, record->format = PictFormatForDmabufFormat (attributes->drm_format); record->pixmap = pixmap; + record->depth = depth; XLAssert (record->format != NULL); record->next = pending_success.next; @@ -1117,15 +2048,32 @@ BufferFromShm (SharedMemoryAttributes *attributes, Bool *error) depth, seg, attributes->offset); xcb_shm_detach (compositor.conn, seg); - /* Create the picture for the pixmap, and free the pixmap. */ + /* Create the picture for the pixmap. */ picture = XRenderCreatePicture (compositor.display, pixmap, PictFormatForFormat (format), 0, &picture_attrs); - XFreePixmap (compositor.display, pixmap); /* Create the wrapper object. */ buffer = XLCalloc (1, sizeof *buffer); buffer->picture = picture; + buffer->pixmap = pixmap; + buffer->depth = depth; + + /* Initialize the list of release records. */ + buffer->pending.buffer_next = &buffer->pending; + buffer->pending.buffer_last = &buffer->pending; + + /* And the list of idle funcs. */ + buffer->idle_callbacks.buffer_next = &buffer->idle_callbacks; + buffer->idle_callbacks.buffer_last = &buffer->idle_callbacks; + + /* And the list of pending activity. */ + buffer->activity.buffer_next = &buffer->activity; + buffer->activity.buffer_last = &buffer->activity; + + /* If the format is presentable, mark the buffer as presentable. */ + if (PictFormatIsPresentable (PictFormatForFormat (format))) + buffer->flags |= CanPresent; /* Return the picture. */ return (RenderBuffer) (void *) buffer; @@ -1209,9 +2157,6 @@ BufferFromSinglePixel (uint32_t red, uint32_t green, uint32_t blue, compositor.argb_format, 0, &picture_attrs); - /* Free the pixmap. */ - XFreePixmap (compositor.display, pixmap); - /* Fill the picture with the single pixel. */ color.red = red >> 16; color.green = green >> 16; @@ -1223,47 +2168,100 @@ BufferFromSinglePixel (uint32_t red, uint32_t green, uint32_t blue, /* Create the wrapper object. */ buffer = XLCalloc (1, sizeof *buffer); buffer->picture = picture; + buffer->pixmap = pixmap; + buffer->depth = compositor.n_planes; + + /* Initialize the list of release records. */ + buffer->pending.buffer_next = &buffer->pending; + buffer->pending.buffer_last = &buffer->pending; + + /* And the list of idle funcs. */ + buffer->idle_callbacks.buffer_next = &buffer->idle_callbacks; + buffer->idle_callbacks.buffer_last = &buffer->idle_callbacks; + + /* And the list of pending activity. */ + buffer->activity.buffer_next = &buffer->activity; + buffer->activity.buffer_last = &buffer->activity; /* Return the picture. */ return (RenderBuffer) (void *) buffer; } static void -FreeShmBuffer (RenderBuffer buffer) +FreeAnyBuffer (RenderBuffer buffer) { PictureBuffer *picture_buffer; + PresentRecord *record, *last; + IdleCallback *idle, *last_idle; + BufferActivityRecord *activity_record, *activity_last; picture_buffer = buffer.pointer; + XFreePixmap (compositor.display, + picture_buffer->pixmap); XRenderFreePicture (compositor.display, picture_buffer->picture); + + /* Free attached presentation records. */ + record = picture_buffer->pending.buffer_next; + while (record != &picture_buffer->pending) + { + last = record; + record = record->buffer_next; + + /* Free the record. */ + RemovePresentRecord (last); + } + + /* Free any activity involving this buffer. */ + activity_record = picture_buffer->activity.buffer_next; + while (activity_record != &picture_buffer->activity) + { + activity_last = activity_record; + activity_record = activity_record->buffer_next; + + UnlinkActivityRecord (activity_last); + XLFree (activity_last); + } + + /* Run and free all idle callbacks. */ + idle = picture_buffer->idle_callbacks.buffer_next; + while (idle != &picture_buffer->idle_callbacks) + { + last_idle = idle; + idle = idle->buffer_next; + + /* Unlink the idle callback from the target. */ + last_idle->target_next->target_last = last_idle->target_last; + last_idle->target_last->target_next = last_idle->target_next; + + /* Run it. */ + last_idle->function (buffer, last_idle->data); + + /* Free it. */ + XLFree (last_idle); + } + + /* Free the picture buffer itself. */ XLFree (picture_buffer); } +static void +FreeShmBuffer (RenderBuffer buffer) +{ + FreeAnyBuffer (buffer); +} + static void FreeDmabufBuffer (RenderBuffer buffer) { - PictureBuffer *picture_buffer; - - picture_buffer = buffer.pointer; - - /* N.B. that the picture is the only reference to the pixmap - here. */ - XRenderFreePicture (compositor.display, - picture_buffer->picture); - XLFree (picture_buffer); + FreeAnyBuffer (buffer); } static void FreeSinglePixelBuffer (RenderBuffer buffer) { - PictureBuffer *picture_buffer; - - picture_buffer = buffer.pointer; - - XRenderFreePicture (compositor.display, - picture_buffer->picture); - XLFree (picture_buffer); + FreeAnyBuffer (buffer); } static void @@ -1382,6 +2380,134 @@ CanReleaseNow (RenderBuffer buffer) return False; } +static IdleCallbackKey +AddIdleCallback (RenderBuffer buffer, RenderTarget target, + BufferIdleFunc function, void *data) +{ + PictureBuffer *pict_buffer; + PictureTarget *pict_target; + IdleCallback *key; + + pict_buffer = buffer.pointer; + pict_target = target.pointer; + + key = XLMalloc (sizeof *key); + key->function = function; + key->data = data; + key->target = target.pointer; + key->buffer_next = pict_buffer->idle_callbacks.buffer_next; + key->buffer_last = &pict_buffer->idle_callbacks; + key->target_next = pict_target->idle_callbacks.target_next; + key->target_last = &pict_target->idle_callbacks; + pict_buffer->idle_callbacks.buffer_next->buffer_last = key; + pict_buffer->idle_callbacks.buffer_next = key; + key->target->idle_callbacks.target_next->target_last = key; + key->target->idle_callbacks.target_next = key; + + return key; +} + +static void +CancelIdleCallback (IdleCallbackKey key) +{ + IdleCallback *internal_key; + + internal_key = key; + internal_key->target_next->target_last = internal_key->target_last; + internal_key->target_last->target_next = internal_key->target_next; + internal_key->buffer_next->buffer_last = internal_key->buffer_last; + internal_key->buffer_last->buffer_next = internal_key->buffer_next; + + XLFree (internal_key); +} + +static Bool +IsBufferIdle (RenderBuffer buffer, RenderTarget target) +{ + BufferActivityRecord *record; + PresentRecord *presentation; + PictureBuffer *pict_buffer; + PictureTarget *pict_target; + + pict_buffer = buffer.pointer; + pict_target = target.pointer; + + /* A buffer is idle if it has no pending activity or + presentation on the given target. */ + record = pict_buffer->activity.buffer_next; + while (record != &pict_buffer->activity) + { + if (record->target == pict_target) + /* There is still pending activity. */ + return False; + + record = record->buffer_next; + } + + /* Next, loop through BUFFER's list of presentation records. If the + buffer is still busy on TARGET, then return. */ + presentation = pict_buffer->pending.buffer_next; + while (presentation != &pict_buffer->pending) + { + if (presentation->target == pict_target) + /* There is still pending activity. */ + return False; + + presentation = presentation->buffer_next; + } + + /* The buffer is idle. */ + return True; +} + +static Bool +IdleEventPredicate (Display *display, XEvent *event, XPointer data) +{ + /* Return whether or not the event is relevant to buffer busy + tracking. */ + return ((event->type == GenericEvent + && event->xgeneric.evtype == PresentIdleNotify) + || (event->type == ClientMessage + && event->xclient.message_type == _XL_BUFFER_RELEASE)); +} + +static void +WaitForIdle (RenderBuffer buffer, RenderTarget target) +{ + XEvent event; + + while (!IsBufferIdle (buffer, target)) + { + XIfEvent (compositor.display, &event, IdleEventPredicate, + NULL); + + /* We failed to get event data for a generic event, so there's + no point in continuing. */ + if (event.type == GenericEvent + && !XGetEventData (compositor.display, &event.xcookie)) + abort (); + + /* Handle the idle event. */ + HandleOneXEventForPictureRenderer (&event); + + if (event.type == GenericEvent) + XFreeEventData (compositor.display, &event.xcookie); + } +} + +static void +SetNeedWaitForIdle (RenderTarget target) +{ + PictureTarget *pict_target; + + /* Request that WaitForIdle be valid with buffers presented to the + given target. All this normally does is disable presentation + onto the given target. */ + + pict_target = target.pointer; + pict_target->flags |= NoPresentation; +} + static BufferFuncs picture_buffer_funcs = { .get_drm_formats = GetDrmFormats, @@ -1396,6 +2522,11 @@ static BufferFuncs picture_buffer_funcs = .free_dmabuf_buffer = FreeDmabufBuffer, .free_single_pixel_buffer = FreeSinglePixelBuffer, .can_release_now = CanReleaseNow, + .add_idle_callback = AddIdleCallback, + .cancel_idle_callback = CancelIdleCallback, + .is_buffer_idle = IsBufferIdle, + .wait_for_idle = WaitForIdle, + .set_need_wait_for_idle = SetNeedWaitForIdle, .init_buffer_funcs = InitBufferFuncs, }; @@ -1431,6 +2562,95 @@ HandleErrorForPictureRenderer (XErrorEvent *error) return False; } +static Bool +HandlePresentCompleteNotify (XPresentCompleteNotifyEvent *complete) +{ + PresentCompletionCallback *callback, *last; + + callback = all_completion_callbacks.next; + while (callback != &all_completion_callbacks) + { + last = callback; + callback = callback->next; + + if (last->id == complete->serial_number) + { + /* The presentation is complete. Run and unlink the + callback. */ + last->function (last->data); + last->next->last = last->last; + last->last->next = last->next; + XLFree (last); + + return True; + } + } + + return False; +} + +static Bool +HandlePresentIdleNotify (XPresentIdleNotifyEvent *idle) +{ + PresentRecord *record; + PictureTarget *target; + PictureBuffer *buffer; + + /* Handle a single idle notify event. Find the target corresponding + to idle->window. */ + target = XLLookUpAssoc (xid_table, idle->window); + + if (target) + { + /* Now, look for a corresponding presentation record. */ + record = target->pending.target_next; + + while (record != &target->pending) + { + if (record->buffer->pixmap == idle->pixmap) + { + /* The buffer was found. Remove the presentation record + if the serial matches, and return. */ + if (record->serial == idle->serial_number) + { + /* Save away buffer. */ + buffer = record->buffer; + + /* Remove the presentation record. */ + RemovePresentRecord (record); + + /* Run idle callbacks if this is now idle. */ + MaybeRunIdleCallbacks (buffer, target); + } + + return True; + } + + record = record->target_next; + } + } + + return True; +} + +static Bool +HandlePresentationEvent (XGenericEventCookie *event) +{ + switch (event->evtype) + { + case PresentIdleNotify: + /* Find which pixmap became idle and note that it is now + idle. */ + return HandlePresentIdleNotify (event->data); + + case PresentCompleteNotify: + /* Find which presentation completed and call the callback. */ + return HandlePresentCompleteNotify (event->data); + } + + return False; +} + Bool HandleOneXEventForPictureRenderer (XEvent *event) { @@ -1454,29 +2674,42 @@ HandleOneXEventForPictureRenderer (XEvent *event) return True; } + if (event->type == ClientMessage + && event->xclient.message_type == _XL_BUFFER_RELEASE) + { + /* Values are masked against 0xffffffff, as Xlib sign-extends + those longs. */ + high = event->xclient.data.l[0] & 0xffffffff; + low = event->xclient.data.l[1] & 0xffffffff; + id = low | (high << 32); + + /* Handle the activity change. */ + HandleActivityEvent (id); + return True; + } + + if (event->type == GenericEvent + /* If present_opcode was not initialized, then it is 0, which is + not a valid extension opcode. */ + && event->xgeneric.extension == present_opcode) + return HandlePresentationEvent (&event->xcookie); + return False; } void InitPictureRenderer (void) { - XSetWindowAttributes attrs; - identity_transform.matrix[0][0] = 1; identity_transform.matrix[1][1] = 1; identity_transform.matrix[2][2] = 1; pending_success.next = &pending_success; pending_success.last = &pending_success; - - /* Create an unmapped, InputOnly window, that is used to receive - roundtrip events. */ - attrs.override_redirect = True; - round_trip_window = XCreateWindow (compositor.display, - DefaultRootWindow (compositor.display), - -1, -1, 1, 1, 0, CopyFromParent, InputOnly, - CopyFromParent, CWOverrideRedirect, - &attrs); + all_activity.global_next = &all_activity; + all_activity.global_last = &all_activity; + all_completion_callbacks.next = &all_completion_callbacks; + all_completion_callbacks.last = &all_completion_callbacks; /* Initialize the renderer with our functions. */ RegisterStaticRenderer ("picture", &picture_render_funcs, diff --git a/renderer.c b/renderer.c index 43fb6ea..cee6ad7 100644 --- a/renderer.c +++ b/renderer.c @@ -69,9 +69,9 @@ AllocateRenderer (void) } RenderTarget -RenderTargetFromWindow (Window window) +RenderTargetFromWindow (Window window, unsigned long standard_event_mask) { - return render_funcs.target_from_window (window); + return render_funcs.target_from_window (window, standard_event_mask); } RenderTarget @@ -80,6 +80,13 @@ RenderTargetFromPixmap (Pixmap pixmap) return render_funcs.target_from_pixmap (pixmap); } +void +RenderSetStandardEventMask (RenderTarget target, + unsigned long standard_event_mask) +{ + render_funcs.set_standard_event_mask (target, standard_event_mask); +} + void RenderNoteTargetSize (RenderTarget target, int width, int height) { @@ -177,6 +184,37 @@ RenderGetFinishFence (Bool *error) return render_funcs.get_finish_fence (error); } +PresentCompletionKey +RenderPresentToWindow (RenderTarget target, RenderBuffer source, + pixman_region32_t *damage, + PresentCompletionFunc callback, void *data) +{ + if (!render_funcs.present_to_window) + return NULL; + + return render_funcs.present_to_window (target, source, + damage, callback, + data); +} + +void +RenderCancelPresentationCallback (PresentCompletionKey key) +{ + if (!render_funcs.cancel_presentation_callback) + return; + + render_funcs.cancel_presentation_callback (key); +} + +void +RenderCancelPresentation (RenderTarget target) +{ + if (!render_funcs.cancel_presentation) + return; + + render_funcs.cancel_presentation (target); +} + DrmFormat * RenderGetDrmFormats (int *n_formats) { @@ -269,6 +307,43 @@ RenderCanReleaseNow (RenderBuffer buffer) return buffer_funcs.can_release_now (buffer); } +IdleCallbackKey +RenderAddIdleCallback (RenderBuffer buffer, RenderTarget target, + BufferIdleFunc function, void *data) +{ + return buffer_funcs.add_idle_callback (buffer, target, function, data); +} + +void +RenderCancelIdleCallback (IdleCallbackKey key) +{ + return buffer_funcs.cancel_idle_callback (key); +} + +Bool +RenderIsBufferIdle (RenderBuffer buffer, RenderTarget target) +{ + return buffer_funcs.is_buffer_idle (buffer, target); +} + +void +RenderWaitForIdle (RenderBuffer buffer, RenderTarget target) +{ + if (!buffer_funcs.wait_for_idle) + return; + + buffer_funcs.wait_for_idle (buffer, target); +} + +void +RenderSetNeedWaitForIdle (RenderTarget target) +{ + if (!buffer_funcs.set_need_wait_for_idle) + return; + + buffer_funcs.set_need_wait_for_idle (target); +} + void RegisterStaticRenderer (const char *name, RenderFuncs *render_funcs, diff --git a/run.c b/run.c index 27e6001..5735a30 100644 --- a/run.c +++ b/run.c @@ -139,6 +139,9 @@ HandleOneXEvent (XEvent *event) if (XLHandleXEventForXdgSurfaces (event)) return; + if (HandleOneXEventForPictureRenderer (event)) + return; + if (XLHandleXEventForXdgToplevels (event)) return; @@ -156,9 +159,6 @@ HandleOneXEvent (XEvent *event) return; #endif - if (HandleOneXEventForPictureRenderer (event)) - return; - if (XLHandleOneXEventForXData (event)) return; diff --git a/seat.c b/seat.c index ab693df..eb085a7 100644 --- a/seat.c +++ b/seat.c @@ -711,6 +711,10 @@ MaybeCreateCursor (CursorRing *ring, int index) compositor.n_planes); ring->targets[index] = RenderTargetFromPixmap (ring->pixmaps[index]); + + /* For simplicity reasons we do not handle idle notifications + asynchronously. */ + RenderSetNeedWaitForIdle (ring->targets[index]); } static int @@ -1208,9 +1212,20 @@ Setup (Surface *surface, Role *role) static void ReleaseBuffer (Surface *surface, Role *role, ExtBuffer *buffer) { + SeatCursor *cursor; + int i; + + cursor = CursorFromRole (role); + /* Cursors are generally committed only once, so syncing here is OK in terms of efficiency. */ - XSync (compositor.display, False); + for (i = 0; i < CursorRingElements; ++i) + { + if (cursor->cursor_ring->pixmaps[i]) + RenderWaitForIdle (XLRenderBufferFromBuffer (buffer), + cursor->cursor_ring->targets[i]); + } + XLReleaseBuffer (buffer); } diff --git a/subcompositor.c b/subcompositor.c index e457d1d..179fa14 100644 --- a/subcompositor.c +++ b/subcompositor.c @@ -310,9 +310,6 @@ struct _View struct _Subcompositor { - /* Various flags describing the state of this subcompositor. */ - int state; - /* List of all inferiors in compositing order. */ List *inferiors, *last; @@ -334,9 +331,29 @@ struct _Subcompositor /* Function called with the bounds before each update. */ void (*note_bounds) (void *, int, int, int, int); + /* Function called with the frame counter on each update. */ + void (*note_frame) (FrameMode, uint64_t, void *); + + /* The current frame counter, incremented with each frame. */ + uint64_t frame_counter; + /* Data for those three functions. */ void *opaque_change_data, *input_change_data, *note_bounds_data; + /* Data for the fourth. */ + void *note_frame_data; + + /* Buffers used to store that damage. */ + pixman_region32_t prior_damage[2]; + + /* The damage region of previous updates. last_damage is what the + damage region was 1 update ago, and before_damage is what the + damage region was 2 updates ago. */ + pixman_region32_t *last_damage, *before_damage; + + /* The last attached presentation callback, if any. */ + PresentCompletionKey present_key; + /* The minimum origin of any surface in this subcompositor. Used to compute the actual size of the subcompositor. */ int min_x, min_y; @@ -347,15 +364,10 @@ struct _Subcompositor /* An additional offset to apply when drawing to the target. */ int tx, ty; - - /* Buffers used to store that damage. */ - pixman_region32_t prior_damage[2]; - - /* The damage region of previous updates. last_damage is what the - damage region was 1 update ago, and before_damage is what the - damage region was 2 updates ago. */ - pixman_region32_t *last_damage, *before_damage; #endif + + /* Various flags describing the state of this subcompositor. */ + int state; }; #ifndef TEST @@ -1807,6 +1819,16 @@ SubcompositorSetBoundsCallback (Subcompositor *subcompositor, subcompositor->note_bounds_data = data; } +void +SubcompositorSetNoteFrameCallback (Subcompositor *subcompositor, + void (*note_frame) (FrameMode, uint64_t, + void *), + void *data) +{ + subcompositor->note_frame = note_frame; + subcompositor->note_frame_data = data; +} + static void FillBoxesWithTransparency (Subcompositor *subcompositor, pixman_box32_t *boxes, int nboxes) @@ -1917,6 +1939,69 @@ IntersectBoxes (pixman_box32_t *in, pixman_box32_t *other, return True; } +static Bool +NoViewsAfter (View *first_view) +{ + List *list; + View *view; + + list = first_view->link->next; + + while (list != first_view->link) + { + view = list->view; + + if (!view) + goto next_1; + + if (IsViewUnmapped (view)) + { + /* Skip the unmapped view. */ + list = view->inferior; + goto next_1; + } + + if (IsSkipped (view)) + { + /* We must skip this view, as it represents (for + instance) a subsurface that has been added, but not + committed. */ + goto next_1; + } + + if (!view->buffer) + goto next_1; + + /* There is view in front of view that potentially obscures it. + Bail out! */ + return False; + + next_1: + list = list->next; + } + + return True; +} + +static void +PresentCompletedCallback (void *data) +{ + Subcompositor *subcompositor; + + subcompositor = data; + + /* The presentation callback should still be set here. */ + XLAssert (subcompositor->present_key != NULL); + + subcompositor->present_key = NULL; + + /* Call the presentation callback if it is still set. */ + if (subcompositor->note_frame) + subcompositor->note_frame (ModePresented, + subcompositor->frame_counter, + subcompositor->note_frame_data); +} + void SubcompositorUpdate (Subcompositor *subcompositor) { @@ -1928,9 +2013,10 @@ SubcompositorUpdate (Subcompositor *subcompositor) int nboxes, i, tx, ty, view_width, view_height; Operation op; RenderBuffer buffer; - int min_x, min_y; - int age; + int min_x, min_y, age, n_seen; DrawParams draw_params; + Bool presented; + PresentCompletionKey key; /* Just return if no target was specified. */ if (!IsTargetAttached (subcompositor)) @@ -1940,6 +2026,13 @@ SubcompositorUpdate (Subcompositor *subcompositor) if (IsFrozen (subcompositor)) return; + if (subcompositor->present_key) + /* Cancel the presentation callback. The next presentation will + either be to an unmapped window, cancel the presentation, or + start a new one. */ + RenderCancelPresentationCallback (subcompositor->present_key); + subcompositor->present_key = NULL; + list = subcompositor->inferiors; min_x = subcompositor->min_x; min_y = subcompositor->min_y; @@ -1949,6 +2042,8 @@ SubcompositorUpdate (Subcompositor *subcompositor) original_start = NULL; pixman_region32_init (&temp); pixman_region32_init (&update_region); + n_seen = 0; + presented = False; start = subcompositor->inferiors->next->view; original_start = subcompositor->inferiors->next->view; @@ -2042,6 +2137,9 @@ SubcompositorUpdate (Subcompositor *subcompositor) if (!view->buffer) goto next; + /* Increase the number of views seen count. */ + n_seen++; + /* Obtain the view width and height here. */ view_width = ViewWidth (view); view_height = ViewHeight (view); @@ -2265,8 +2363,16 @@ SubcompositorUpdate (Subcompositor *subcompositor) /* If there's nothing to do, return. */ if (!start) + /* There is no starting view. Presentation is not cancelled in + this case, because the surface should now be unmapped. */ goto complete; + /* Increase the frame count and announce the new frame number. */ + if (subcompositor->note_frame) + subcompositor->note_frame (ModeStarted, + ++subcompositor->frame_counter, + subcompositor->note_frame_data); + /* Now update all views from start onwards. */ list = start->link; @@ -2311,6 +2417,7 @@ SubcompositorUpdate (Subcompositor *subcompositor) view_width = ViewWidth (view); view_height = ViewHeight (view); + /* And the buffer. */ buffer = XLRenderBufferFromBuffer (view->buffer); if (IsGarbaged (subcompositor)) @@ -2337,8 +2444,67 @@ SubcompositorUpdate (Subcompositor *subcompositor) &draw_params); } + /* Compute the transform and put it in draw_params. */ + ViewComputeTransform (view, &draw_params, True); + if (!first) { + /* See if the first mapped and visible view after start is eligible + for direct presentation. It is considered eligible if: + + - its bounds match that of the subcompositor. + - its depth and masks match that of the subcompositor. + - it is not occluded by any other view, above or below. + - it has no transform whatsoever. + + Also, presentation is done asynchronously, so we only + consider the view as eligible for presentation if + completion callbacks are attached. */ + + if (!draw_params.flags + && view->abs_x == subcompositor->min_x + && view->abs_y == subcompositor->min_y + && view_width == SubcompositorWidth (subcompositor) + && view_height == SubcompositorHeight (subcompositor) + /* N.B. that n_seen is not set (0) if the view is + garbaged. */ + && (n_seen == 1 || (!n_seen && NoViewsAfter (view))) + && subcompositor->note_frame) + { + /* Direct presentation is okay. Present the pixmap to + the drawable. */ + if (IsGarbaged (subcompositor)) + key = RenderPresentToWindow (subcompositor->target, buffer, + NULL, PresentCompletedCallback, + subcompositor); + else + key = RenderPresentToWindow (subcompositor->target, buffer, + &update_region, + PresentCompletedCallback, + subcompositor); + + /* Now set presented to whether or not key is non-NULL. */ + presented = key != NULL; + + /* And set the presentation callback. */ + subcompositor->present_key = key; + + if (presented) + { + /* If presentation succeeded, don't composite. + Instead, just continue looping to set the input + region if garbaged. */ + + if (!IsGarbaged (subcompositor) && (age >= 0 && age < 3)) + /* And if not garbaged, skip everything. */ + goto present_success; + else + goto present_success_garbaged; + } + } + else + RenderCancelPresentation (subcompositor->target); + /* The first view with an attached buffer should be drawn with PictOpSrc so that transparency is applied correctly, if it contains the entire update region. */ @@ -2388,10 +2554,11 @@ SubcompositorUpdate (Subcompositor *subcompositor) else op = OperationOver; - first = view; + if (presented && (IsGarbaged (subcompositor) + || age < 0 || age >= 3)) + goto present_success_garbaged; - /* Compute the transform and put it in draw_params. */ - ViewComputeTransform (view, &draw_params, True); + first = view; if (!IsGarbaged (subcompositor) && (age >= 0 && age < 3)) { @@ -2428,11 +2595,6 @@ SubcompositorUpdate (Subcompositor *subcompositor) } else { - /* Clear the damaged area, since it will either be drawn or - be obscured. We didn't get a chance to clear the damage - earlier, since the compositor was garbaged. */ - pixman_region32_clear (&view->damage); - /* If the subcompositor is garbaged, composite the entire view to the right location. */ RenderComposite (buffer, subcompositor->target, op, @@ -2447,6 +2609,12 @@ SubcompositorUpdate (Subcompositor *subcompositor) /* height, draw-params. */ view_height, &draw_params); + present_success_garbaged: + /* Clear the damaged area, since it will either be drawn or + be obscured. We didn't get a chance to clear the damage + earlier, since the compositor was garbaged. */ + pixman_region32_clear (&view->damage); + /* Also adjust the opaque and input regions here. */ if (pixman_region32_not_empty (&view->opaque) @@ -2501,6 +2669,8 @@ SubcompositorUpdate (Subcompositor *subcompositor) } while (list != subcompositor->inferiors); + present_success: + /* Swap changes to display. */ if (IsGarbaged (subcompositor) || age < 0 || age >= 3) @@ -2561,6 +2731,12 @@ SubcompositorUpdate (Subcompositor *subcompositor) subcompositor->state &= ~SubcompositorIsGarbaged; subcompositor->state &= ~SubcompositorIsOpaqueDirty; subcompositor->state &= ~SubcompositorIsInputDirty; + + /* Call the frame complete function if presentation did not happen. */ + if (subcompositor->note_frame && !presented) + subcompositor->note_frame (ModeComplete, + subcompositor->frame_counter, + subcompositor->note_frame_data); } void @@ -2735,6 +2911,10 @@ SubcompositorFree (Subcompositor *subcompositor) pixman_region32_fini (&subcompositor->prior_damage[0]); pixman_region32_fini (&subcompositor->prior_damage[1]); + /* Remove the presentation key. */ + if (subcompositor->present_key) + RenderCancelPresentationCallback (subcompositor->present_key); + XLFree (subcompositor); } diff --git a/surface.c b/surface.c index 223144d..5b9a4b0 100644 --- a/surface.c +++ b/surface.c @@ -1599,6 +1599,19 @@ XLSurfaceRunFrameCallbacks (Surface *surface, struct timespec time) XLSurfaceRunFrameCallbacks (list->data, time); } +void +XLSurfaceRunFrameCallbacksMs (Surface *surface, uint32_t ms_time) +{ + XLList *list; + + RunFrameCallbacks (&surface->current_state.frame_callbacks, + ms_time); + + /* Run frame callbacks for each attached subsurface as well. */ + for (list = surface->subsurfaces; list; list = list->next) + XLSurfaceRunFrameCallbacksMs (list->data, ms_time); +} + CommitCallback * XLSurfaceRunAtCommit (Surface *surface, void (*commit_func) (Surface *, void *), diff --git a/text_input.c b/text_input.c index d87a0a1..affff57 100644 --- a/text_input.c +++ b/text_input.c @@ -72,7 +72,7 @@ along with 12to11. If not, see . */ That means it is impossible to attribute forwarded events or committed text to the correct XIC, and thus it is impossible to look up which seat's TextInput resource an event is actually bound - for. If one day we move to our own implementation of the XIC + for. If one day we move to our own implementation of the XIM protocol, then it will become possible to properly support multi-seat setups, with one XIC per-client and per-seat. @@ -86,7 +86,7 @@ along with 12to11. If not, see . */ converted string before being sent to the client. This code has many inherent race conditions, just like the - zwp_text_input_v3 protocol itself. As described above, it only + zwp_text_input_v3 protocol itself. And as described above, it only supports one seat due to limitations of the Xlib XIM wrapper. */ typedef struct _TextInputClientInfo TextInputClientInfo; diff --git a/xdg-shell.xml b/xdg-shell.xml index 993c54a..d2f5d5f 100644 --- a/xdg-shell.xml +++ b/xdg-shell.xml @@ -454,6 +454,7 @@ + @@ -549,6 +550,17 @@ A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. + + Sending an ack_configure request consumes the serial number sent with + the request, as well as serial numbers sent by all configure events + sent on this xdg_surface prior to the configure event referenced by + the committed serial. + + It is an error to issue multiple ack_configure requests referencing a + serial from the same configure event, or to issue an ack_configure + request referencing a serial from a configure event issued before the + event identified by the last ack_configure request for the same + xdg_surface. Doing so will raise an invalid_serial error. diff --git a/xdg_surface.c b/xdg_surface.c index 2bdb31c..aac7a47 100644 --- a/xdg_surface.c +++ b/xdg_surface.c @@ -44,6 +44,7 @@ enum StateMaybeConfigure = (1 << 6), StateDirtyFrameExtents = (1 << 7), StateTemporaryBounds = (1 << 8), + StateFrameStarted = (1 << 9), }; typedef struct _XdgRole XdgRole; @@ -106,6 +107,9 @@ struct _XdgRole /* The implementation of this role. */ XdgRoleImplementation *impl; + /* The pending frame ID. */ + uint64_t pending_frame; + /* Various role state. */ int state; @@ -123,7 +127,7 @@ struct _XdgRole XdgState current_state; /* Configure event serial. */ - uint32_t conf_serial; + uint32_t conf_serial, last_specified_serial; /* The current bounds of the subcompositor. */ int min_x, min_y, max_x, max_y; @@ -156,13 +160,16 @@ struct _ReleaseLaterRecord /* A monotonically (overflow aside) increasing identifier. */ uint64_t id; - /* Key for the free func. */ - void *free_func_key; - /* The buffer that should be released upon receiving this message. */ ExtBuffer *buffer; + /* The idle callback. */ + IdleCallbackKey key; + + /* The XdgRole. */ + XdgRole *role; + /* The next and last records. */ ReleaseLaterRecord *next, *last; }; @@ -170,24 +177,6 @@ struct _ReleaseLaterRecord /* Event base of the XShape extension. */ int shape_base; -static void -RemoveRecord (ReleaseLaterRecord *record) -{ - /* Removing the sentinel record is invalid. */ - XLAssert (record->buffer != NULL); - - /* First, make the rest of the list skip RECORD. */ - record->last->next = record->next; - record->next->last = record->last; - - /* Next, remove the buffer listener. */ - XLBufferCancelRunOnFree (record->buffer, - record->free_func_key); - - /* Finally, free RECORD. */ - XLFree (record); -} - static void DeleteRecord (ReleaseLaterRecord *record) { @@ -217,10 +206,6 @@ FreeRecords (ReleaseLaterRecord *records) /* Release the buffer now. */ XLReleaseBuffer (last->buffer); - /* And cancel the destroy listener. */ - XLBufferCancelRunOnFree (last->buffer, - last->free_func_key); - /* Before freeing the record itself. */ XLFree (last); } @@ -311,24 +296,26 @@ FreeReconstrainCallbacks (XdgRole *role) } } -static void -ReleaseLaterExtBufferFunc (ExtBuffer *buffer, void *data) -{ - DeleteRecord (data); -} - static void RunFrameCallbacks (Surface *surface, XdgRole *role) { struct timespec time; + uint64_t last_drawn_time; /* Surface can be NULL for various reasons, especially events arriving after the shell surface is detached. */ if (!surface) return; - clock_gettime (CLOCK_MONOTONIC, &time); - XLSurfaceRunFrameCallbacks (surface, time); + last_drawn_time = XLFrameClockGetFrameTime (role->clock); + + if (!last_drawn_time) + { + clock_gettime (CLOCK_MONOTONIC, &time); + XLSurfaceRunFrameCallbacks (surface, time); + } + else + XLSurfaceRunFrameCallbacksMs (surface, last_drawn_time / 1000); } static void @@ -344,25 +331,18 @@ RunFrameCallbacksConditionally (XdgRole *role) } static void -HandleReleaseLaterMessage (XdgRole *role, uint64_t id) +BufferIdleCallback (RenderBuffer buffer, void *data) { ReleaseLaterRecord *record; + XdgRole *role; Surface *surface; - record = role->release_records->last; + record = data; + role = record->role; - if (record == role->release_records) - return; - /* Since the list of release records is a (circular) queue, ID will - either be the last element or invalid as the buffer has been - destroyed. */ - - if (record->id == id) - { - XLReleaseBuffer (record->buffer); - RemoveRecord (record); - } + XLReleaseBuffer (record->buffer); + DeleteRecord (record); surface = role->role.surface; @@ -377,70 +357,12 @@ HandleReleaseLaterMessage (XdgRole *role, uint64_t id) } } -/* It shouldn't be possible for billions of frames to pile up without - a response from the X server, so relying on wraparound to handle id - overflow should be fine. */ - -static void -ReleaseLater (XdgRole *role, ExtBuffer *buffer) -{ - ReleaseLaterRecord *record; - XEvent event; - static uint64_t id; - - /* Send a message to the X server; once it is received, release the - given buffer. This is necessary because the connection to the X - server does not behave synchronously, and receiving the message - tells us that the X server has finished processing all requests - that access the buffer. */ - - record = AddRecordAfter (role->release_records); - record->id = ++id; - record->buffer = buffer; - record->free_func_key - = XLBufferRunOnFree (buffer, ReleaseLaterExtBufferFunc, - record); - - memset (&event, 0, sizeof event); - - event.xclient.type = ClientMessage; - event.xclient.window = role->window; - event.xclient.message_type = _XL_BUFFER_RELEASE; - event.xclient.format = 32; - - event.xclient.data.l[0] = id >> 31 >> 1; - event.xclient.data.l[1] = id & 0xffffffff; - - XSendEvent (compositor.display, role->window, False, - NoEventMask, &event); -} - Bool XLHandleXEventForXdgSurfaces (XEvent *event) { XdgRole *role; - uint64_t id, low, high; Window window; - if (event->type == ClientMessage - && event->xclient.message_type == _XL_BUFFER_RELEASE) - { - role = XLLookUpAssoc (surfaces, event->xclient.window); - - if (role) - { - /* These values are masked because Xlib sign-extends 32-bit - values to fit potentially 64-bit long. */ - low = event->xclient.data.l[0] & 0xffffffff; - high = event->xclient.data.l[1] & 0xffffffff; - - id = (low << 32) | high; - HandleReleaseLaterMessage (role, id); - } - - return True; - } - if (event->type == ClientMessage && ((event->xclient.message_type == _NET_WM_FRAME_DRAWN || event->xclient.message_type == _NET_WM_FRAME_TIMINGS) @@ -611,8 +533,29 @@ AckConfigure (struct wl_client *client, struct wl_resource *resource, if (!xdg_role->role.surface) return; +#ifdef DEBUG_GEOMETRY_CALCULATION + fprintf (stderr, "ack_configure: %"PRIu32"\n", serial); +#endif + + if (serial < xdg_role->conf_serial) + { + /* The client specified an outdated serial. */ + wl_resource_post_error (resource, XDG_SURFACE_ERROR_INVALID_SERIAL, + "serial specified not monotonic"); + return; + } + + if (serial && serial == xdg_role->last_specified_serial) + { + /* The client specified the same serial twice. */ + wl_resource_post_error (resource, XDG_SURFACE_ERROR_INVALID_SERIAL, + "same serial specified twice"); + return; + } + if (serial == xdg_role->conf_serial) { + xdg_role->last_specified_serial = serial; xdg_role->state &= ~StateWaitingForAckConfigure; /* Garbage the subcompositor too, since contents could be @@ -720,6 +663,12 @@ Commit (Surface *surface, Role *role) if (!IsRoleMapped (xdg_role)) goto start_drawing; + /* If the frame clock is frozen but we are no longer waiting for the + configure event to be acknowledged by the client, unfreeze the + frame clock. */ + if (!(xdg_role->state & StateWaitingForAckConfigure)) + Unfreeze (xdg_role); + /* A frame is already in progress, so instead say that an urgent update is needed immediately after the frame completes. In any case, don't run frame callbacks upon buffer release anymore. */ @@ -727,8 +676,9 @@ Commit (Surface *surface, Role *role) { if (XLFrameClockCanBatch (xdg_role->clock)) /* But if we can squeeze the frame inside the vertical - blanking period, go ahead. */ - goto just_draw_then_return; + blanking period, or a frame is in progress but EndFrame has + not yet been called, go ahead. */ + goto start_drawing; xdg_role->state |= StateLateFrame; xdg_role->state &= ~StatePendingFrameCallback; @@ -743,38 +693,12 @@ Commit (Surface *surface, Role *role) start_drawing: - /* If the frame clock is frozen but we are no longer waiting for the - configure event to be acknowledged by the client, unfreeze the - frame clock. */ - if (!(xdg_role->state & StateWaitingForAckConfigure)) - Unfreeze (xdg_role); - - XLFrameClockStartFrame (xdg_role->clock, False); SubcompositorUpdate (xdg_role->subcompositor); - /* Also run role "commit inside frame" hook. */ - if (xdg_role->impl && xdg_role->impl->funcs.commit_inside_frame) - xdg_role->impl->funcs.commit_inside_frame (role, xdg_role->impl); - XLFrameClockEndFrame (xdg_role->clock); - - /* Clients which commit when a configure event that has not yet been - acked still expect frame callbacks to be called; however, frame - callbacks are not provided by the frame clock while it is frozen. - - If that happens, just run the frame callback immediately. */ - if (XLFrameClockIsFrozen (xdg_role->clock) - /* If the window is not mapped, then the native frame clock will - not draw frames. Some clients do commit before the initial - configure event and wait for the frame callback to be called - after or before ack_configure, leading to the mapping commit - never being performed. */ - || !IsRoleMapped (xdg_role)) - RunFrameCallbacksConditionally (xdg_role); + /* Do not end frames explicitly. Instead, wait for the + NoteFrameCallback to end the frame. */ return; - - just_draw_then_return: - SubcompositorUpdate (xdg_role->subcompositor); } static Bool @@ -887,7 +811,27 @@ Teardown (Surface *surface, Role *role) static void ReleaseBuffer (Surface *surface, Role *role, ExtBuffer *buffer) { - ReleaseLater (XdgRoleFromRole (role), buffer); + RenderBuffer render_buffer; + ReleaseLaterRecord *record; + XdgRole *xdg_role; + + render_buffer = XLRenderBufferFromBuffer (buffer); + xdg_role = XdgRoleFromRole (role); + + if (RenderIsBufferIdle (render_buffer, xdg_role->target)) + /* If the buffer is already idle, release it now. */ + XLReleaseBuffer (buffer); + else + { + /* Release the buffer once it is destroyed or becomes idle. */ + record = AddRecordAfter (xdg_role->release_records); + record->buffer = buffer; + record->key = RenderAddIdleCallback (render_buffer, + xdg_role->target, + BufferIdleCallback, + record); + record->role = xdg_role; + } } static Bool @@ -915,7 +859,8 @@ Subframe (Surface *surface, Role *role) { if (XLFrameClockCanBatch (xdg_role->clock)) /* But if we can squeeze the frame inside the vertical - blanking period, go ahead. */ + blanking period, or a frame is in progress but EndFrame has + not yet been called, go ahead. */ return True; xdg_role->state |= StateLateFrame; @@ -927,18 +872,14 @@ Subframe (Surface *surface, Role *role) return False; } - /* I guess subsurface updates don't count as urgent frames? */ - XLFrameClockStartFrame (xdg_role->clock, False); return True; } static void EndSubframe (Surface *surface, Role *role) { - XdgRole *xdg_role; - - xdg_role = XdgRoleFromRole (role); - XLFrameClockEndFrame (xdg_role->clock); + /* Don't end the frame here; instead, wait for the frame callback to + note that drawing the frame has finished. */ } static Window @@ -969,34 +910,6 @@ AfterFrame (FrameClock *clock, void *data) role = data; - if (role->state & StateLateFrame) - { - role->state &= ~StateLateFrame; - - if (role->state & StateLateFrameAcked) - XLFrameClockUnfreeze (role->clock); - - /* Since we are running late, make the compositor draw the frame - now. */ - XLFrameClockStartFrame (clock, True); - SubcompositorUpdate (role->subcompositor); - - /* Also run role "commit inside frame" hook. */ - if (role->impl - && role->impl->funcs.commit_inside_frame) - role->impl->funcs.commit_inside_frame (&role->role, - role->impl); - XLFrameClockEndFrame (clock); - - /* See the comment in Commit about frame callbacks being - attached while the frame clock is frozen. */ - if (XLFrameClockIsFrozen (role->clock) - && role->role.surface) - RunFrameCallbacksConditionally (role); - - return; - } - /* If all pending frames have been drawn, run frame callbacks. Unless some buffers have not yet been released, in which case the callbacks will be run when they are. */ @@ -1234,6 +1147,63 @@ NoteBounds (void *data, int min_x, int min_y, RunReconstrainCallbacks (role); } +static void +NoteFrame (FrameMode mode, uint64_t id, void *data) +{ + XdgRole *role; + + role = data; + + switch (mode) + { + case ModeStarted: + /* Record this frame counter as the pending frame. */ + role->pending_frame = id; + + if (!(role->state & StateFrameStarted)) + { + role->state |= StateFrameStarted; + XLFrameClockStartFrame (role->clock, False); + } + + /* Also run role "commit inside frame" hook. */ + if (role->impl && role->impl->funcs.commit_inside_frame) + role->impl->funcs.commit_inside_frame (&role->role, + role->impl); + + break; + + case ModePresented: + case ModeComplete: + /* The frame was completed. */ + if (id == role->pending_frame) + { + /* End the frame. */ + XLFrameClockEndFrame (role->clock); + + /* Clear the frame completed flag. */ + role->state &= ~StateFrameStarted; + + /* Clients which commit when a configure event that has not + yet been acked still expect frame callbacks to be called; + however, frame callbacks are not provided by the frame + clock while it is frozen. + + If that happens, just run the frame callback + immediately. */ + if (XLFrameClockIsFrozen (role->clock) + /* If the window is not mapped, then the native frame + clock will not draw frames. Some clients do commit + before the initial configure event and wait for the + frame callback to be called after or before + ack_configure, leading to the mapping commit never + being performed. */ + || !IsRoleMapped (role)) + RunFrameCallbacksConditionally (role); + } + } +} + static void ResizeForMap (XdgRole *role) { @@ -1387,6 +1357,10 @@ SelectExtraEvents (Surface *surface, Role *role, /* Select extra events for the input method. */ XSelectInput (compositor.display, xdg_role->window, DefaultEventMask | event_mask); + + /* Set the target standard event mask. */ + RenderSetStandardEventMask (xdg_role->target, + DefaultEventMask | event_mask); } void @@ -1482,7 +1456,7 @@ XLGetXdgSurface (struct wl_client *client, struct wl_resource *resource, 0, 0, 20, 20, 0, compositor.n_planes, InputOutput, compositor.visual, flags, &attrs); - role->target = RenderTargetFromWindow (role->window); + role->target = RenderTargetFromWindow (role->window, DefaultEventMask); role->subcompositor = MakeSubcompositor (); role->clock = XLMakeFrameClockForWindow (role->window); @@ -1496,6 +1470,8 @@ XLGetXdgSurface (struct wl_client *client, struct wl_resource *resource, OpaqueRegionChanged, role); SubcompositorSetBoundsCallback (role->subcompositor, NoteBounds, role); + SubcompositorSetNoteFrameCallback (role->subcompositor, + NoteFrame, role); XLSelectStandardEvents (role->window); XLMakeAssoc (surfaces, role->window, role); @@ -1581,7 +1557,8 @@ XLXdgRoleSendConfigure (Role *role, uint32_t serial) xdg_role->state &= ~StateMaybeConfigure; #ifdef DEBUG_GEOMETRY_CALCULATION - fprintf (stderr, "Waiting for ack_configure...\n"); + fprintf (stderr, "Waiting for ack_configure (%"PRIu32")...\n", + xdg_role->conf_serial); #endif xdg_surface_send_configure (role->resource, serial);