The Present Extension
I've finally gotten an initial implementation of the Present extension written and running and thought I should write up the current design and status.
The current Present extension consists of two requests:
- PresentRegion. Puts new bits from a pixmap in a window.
- PresentSelectInput. Asks for Present events to be delivered.
This request takes a pile of arguments:
┌─── PresentRegion window: WINDOW pixmap: PIXMAP valid-area: REGION or None update-area: REGION or None x-off, y-off: INT16 target-msc: CARD64 divisor: CARD64 remainder: CARD64 idle-fence: FENCE └─── Errors: Drawable, Pixmap, Match
Provides new content for the specified window, to be made visible at the specified time (defined by 'target-msc', 'divisor' and 'remainder').
'update-area' defines the subset of the window to be updated, or None if the whole window is to be updated.
'valid-area' defines the portion of 'pixmap' which contains valid window contents, or None if the pixmap contains valid contents for the whole window.
PresentRegion may use any region of 'pixmap' which contains 'update-area' and which is contained by 'valid-area'. In other words, areas inside 'update-area' will be presented from 'pixmap', areas outside 'valid-area' will not be presented from 'pixmap' and areas inside 'valid-area' but outside 'update-area' may or may not be presented at the discretion of the X server.
'x-off' and 'y-off' define the location in the window where the 0,0 location of the pixmap will be presented. valid-area and update-area are relative to the pixmap.
If 'target-msc' is greater than the current msc for 'window', the presentation will occur at (or after) the 'target-msc' field. Otherwise, the presentation will occur after the next field where msc % 'divisor' == 'remainder'. If 'divisor' is zero, then the presentation will occur after the current field.
'idle-fence' is triggered when 'pixmap' is no longer in use. This may be at any time following the PresentRegion request, the contents may be immediately copied to another buffer, copied just in time for the vblank interrupt or the pixmap may be used directly for display, in which case it will be busy until some future PresentRegion operation.
If 'window' is destroyed before the presentation occurs, then the presentation action will not be completed.
PresentRegion holds a reference to 'pixmap' until the presentation occurs, so 'pixmap' may be immediately freed after the request executes, even if that is before the presentation occurs.
If 'idle-fence' is destroyed before the presentation occurs, then idle-fence will not be signaled but the presentation will occur normally.
┌─── PresentSelectInput event-id: PRESENTEVENTID window: WINDOW eventMask: SETofPRESENTEVENT └─── Errors: Window, Value, Match, IDchoice, Access
Selects the set of Present events to be delivered for the specified window and event context. PresentSelectInput can create, modify or delete event contexts. An event context is associated with a specific window; using an existing event context with a different window generates a Match error.
If eventContext specifies an existing event context, then if eventMask is empty, PresentSelectInput deletes the specified context, otherwise the specified event context is changed to select a different set of events.
If eventContext is an unused XID, then if eventMask is empty no operation is performed. Otherwise, a new event context is created selecting the specified events.
Present Extension Events
There are three different events for the Present extension:
This event is moving from the DRI3 extension where it doesn't belong.
┌─── PresentConfigureNotify type: CARD8 XGE event type (35) extension: CARD8 Present extension request number length: CARD16 2 evtype: CARD16 Present_ConfigureNotify eventID: PRESENTEVENTID window: WINDOW x: INT16 y: INT16 width: CARD16 height: CARD16 off_x: INT16 off_y: INT16 pixmap_width: CARD16 pixmap_height: CARD16 pixmap_flags: CARD32 └───
PresentConfigureNotify events are sent when the window configuration changes if PresentSelectInput has requested it. PresentConfigureNotify events are XGE events and so do not have a unique event type.
'x' and 'y' are the parent-relative location of 'window'.
┌─── PresentCompleteNotify type: CARD8 XGE event type (35) extension: CARD8 Present extension request number length: CARD16 2 evtype: CARD16 Present_CompleteNotify eventID: PRESENTEVENTID window: WINDOW ust: CARD64 msc: CARD64 sbc: CARD64 └───
Notify events are delivered when a PresentRegion operation has completed and the specified contents are being displayed. sbc, msc and ust indicate the swap count, frame count and system time of the related PresentRegion request.
This one is not specified yet, but the intent is for it to contain sufficient information for the compositing manager to be able to construct a suitable screen update that includes an application window update.
At this point, Mesa only exposes the old swap_interval configuration value, it doesn't provide any of the GLX_OML_sync_control APIs. However, the Present protocol does have the bits necessary to support glXSwapBuffersMscOML in the PresentRegion request. Let's see how the remaining APIs in this GL extension will be supported.
This one is easy; it only needs to return the UST/MSC/SBC values from the most recent SwapBuffers request. Those are returned in the PresentCompleteNotify event, so Mesa just needs to capture that event and save the values away for return to the application. We don't need any new Present protocol for this.
This returns the refresh rate of the monitor associated with the specified drawable. RandR exposes all of the necessary data for each monitor, but the monitor each window is going to be synchronized against isn't exposed anywhere, and is effectively implementation-dependent. So, I think the easy thing to do here is to have a Present request which reports which RandR output a window will be synchronized with.
This is the one API which is already directly supported by the Present extension.
This is effectively the same as glXSwapBuffersMscOML, except that it doesn't actually perform a swap. For this, I think we want a new request that generates an X event when the target values are reached, and then have the client block until that event is received. This will avoid blocking other threads using the same X connection.
Like glXWaitForMscOML, this just needs to trigger an event to be delivered at the right time.
I'm focusing on finishing the above stuff before I start writing the redirection spec and code, but I've been thinking a bit about it.
What we want is for applications that are presenting a new frame within a redirected window to have that presentation delivered directly to the compositing manager instead of having the bits copied to the redirected buffer and have the compositing manager only learn about this when the damage event is received from copying the bits.
Once the compositing manager receives notification that there are new bits available for a portion of a window, it can then get those bits onto the screen in one of two ways:
Copy them to the back buffer for the whole screen and then present that back buffer to the screen.
Present them directly to the screen, bypassing the compositing manager's back buffer entirely.
The first option is what you'd do if there were more updates than just a single window; it would update the whole screen at the same time. The second option is what you'd do if there was only the one window update to act on, and this would automatically take advantage of page flipping to avoid copying data at all. No need to un-redirect windows for the page flip to work.
In both cases, we need to inform the original application both when its pixmap is idle and when the swap actually hits the screen. And we need to make sure the final swap happens when the application requested.
For the pixmap idle notification, the current PresentRegion idle_fence argument should suffice; we just need to pass the idle_fence XID along in the redirection event. Simple.
For completion notification, we need to make sure the SBC value for the original window gets incremented and that a PresentCompleteNotify event is delivered. I think that means we want to append a chunk of data to the PresentRegion request so that suitable events can be delivered when the compositing manager PresentRegion occur. I think that just needs to be the 'window' of that original window.
To make sure the swap happens at the right time, we just need to have the target_msc value provided to the compositing manager. I'm hoping that having this all event driven is that when a later PresentRegion is redirected that has an earlier target_msc, we can simply queue that as well and things should 'just work'.
I've got the protocol definitions (both X server and XCB) done, and libxcb supporting the extension.
I've got the X server bits working, but the actual updates are not synchronized to the monitor; they're using OS timers and CopyArea for now, mostly so I can test things without also hacking drivers.
Mesa is using the extension, but it only provides the swap_interval value and is not yet supporting the full GLX_OML_sync_control extension.
I've also got a simple 2D core X application using the extension which is in the 'shmfd' repository in my home directory on freedesktop.org.
As always, these bits are already published in my home directory on freedesktop.org in various git repositories.