forked from 12to11/12to11

* 12to11.man: Document new protocol. * compositor.h (enum _RenderMode): Add RenderModeVsyncAsync. * picture_renderer.c (SwapBackBuffers, PresentToWindow) (NotifyMsc): Handle new render mode. * sync_source.c (GetWantedSynchronizationType): Use presentation if the mode is VsyncAsync. (NoteFrame): Set the vsync presentation mode to RenderModeVsyncAsync.
3568 lines
90 KiB
C
3568 lines
90 KiB
C
/* Wayland compositor running on top of an X server.
|
||
|
||
Copyright (C) 2022 to various contributors.
|
||
|
||
This file is part of 12to11.
|
||
|
||
12to11 is free software: you can redistribute it and/or modify it
|
||
under the terms of the GNU General Public License as published by the
|
||
Free Software Foundation, either version 3 of the License, or (at your
|
||
option) any later version.
|
||
|
||
12to11 is distributed in the hope that it will be useful, but WITHOUT
|
||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||
for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with 12to11. If not, see <https://www.gnu.org/licenses/>. */
|
||
|
||
#include <alloca.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdio.h>
|
||
|
||
#include <sys/fcntl.h>
|
||
#include <drm_fourcc.h>
|
||
|
||
#include "compositor.h"
|
||
#include "drm_modifiers.h"
|
||
|
||
#include <xcb/dri3.h>
|
||
#include <xcb/randr.h>
|
||
|
||
#include <X11/Xmd.h>
|
||
#include <X11/extensions/dri3proto.h>
|
||
#include <X11/extensions/Xpresent.h>
|
||
#include <X11/extensions/Xfixes.h>
|
||
#include <X11/extensions/shape.h>
|
||
|
||
typedef struct _DrmFormatInfo DrmFormatInfo;
|
||
typedef struct _DmaBufRecord DmaBufRecord;
|
||
typedef struct _DrmModifierName DrmModifierName;
|
||
|
||
typedef struct _BackBuffer BackBuffer;
|
||
|
||
typedef struct _PictureBuffer PictureBuffer;
|
||
typedef struct _PictureTarget PictureTarget;
|
||
typedef struct _PresentRecord PresentRecord;
|
||
|
||
typedef struct _BufferActivityRecord BufferActivityRecord;
|
||
typedef struct _IdleCallback IdleCallback;
|
||
typedef struct _PresentCompletionCallback PresentCompletionCallback;
|
||
|
||
struct _DrmModifierName
|
||
{
|
||
/* The modifier name. */
|
||
const char *name;
|
||
|
||
/* The modifier code. */
|
||
uint64_t modifier;
|
||
};
|
||
|
||
/* 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,
|
||
IsOpaque = (1 << 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 width and height of the buffer. */
|
||
short width, height;
|
||
|
||
/* 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
|
||
{
|
||
JustPresented = 1,
|
||
NoPresentation = 2,
|
||
};
|
||
|
||
/* 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;
|
||
|
||
/* Data the callback will be called with. */
|
||
void *data;
|
||
|
||
/* The function. */
|
||
PresentCompletionFunc function;
|
||
|
||
/* The presentation ID. */
|
||
uint32_t id;
|
||
};
|
||
|
||
struct _BackBuffer
|
||
{
|
||
/* How many pixels were allocated. */
|
||
uint64_t n_pixels;
|
||
|
||
/* The picture of this back buffer. High bit means the back buffer
|
||
is busy. */
|
||
Picture picture;
|
||
|
||
/* The pixmap of this back buffer. */
|
||
Pixmap pixmap;
|
||
|
||
/* The idle fence of this back buffer. */
|
||
Fence *idle_fence;
|
||
|
||
/* The serial of the last presentation, or 0. */
|
||
uint32_t present_serial;
|
||
|
||
/* The age of this back buffer. 0 means it is fresh. */
|
||
unsigned int age;
|
||
};
|
||
|
||
enum
|
||
{
|
||
/* The buffer is currently busy. */
|
||
BufferBusy = (1U << 31),
|
||
/* The idle notification has (or will) arrive, but the fence has
|
||
not yet been waited upon. If this is set on the pixmap as well
|
||
as the picture, then calling XFlush is not necessary. */
|
||
BufferSync = (1U << 30),
|
||
};
|
||
|
||
#define IsBufferBusy(buffer) ((buffer)->picture & BufferBusy)
|
||
#define SetBufferBusy(buffer) ((buffer)->picture |= BufferBusy)
|
||
#define ClearBufferBusy(buffer) ((buffer)->picture &= ~BufferBusy)
|
||
|
||
struct _PictureTarget
|
||
{
|
||
/* The next frame number. */
|
||
uint64_t next_msc;
|
||
|
||
/* The XID of the picture. */
|
||
Picture picture;
|
||
|
||
/* The backing window. */
|
||
Window window;
|
||
|
||
/* The GC used to swap back buffers */
|
||
GC gc;
|
||
|
||
/* Two back buffers. */
|
||
BackBuffer *back_buffers[2];
|
||
|
||
/* Structure used to allocate the amount of pixmap allocated on
|
||
behalf of a client. */
|
||
ClientErrorData *client;
|
||
|
||
/* 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;
|
||
|
||
/* The index of the current back buffer. */
|
||
int current_back_buffer;
|
||
|
||
/* List of release records. */
|
||
PresentRecord pending;
|
||
|
||
/* List of idle callbacks. */
|
||
IdleCallback idle_callbacks;
|
||
|
||
/* Ongoing buffer activity. */
|
||
BufferActivityRecord activity;
|
||
|
||
/* List of present completion callbacks. */
|
||
PresentCompletionCallback completion_callbacks;
|
||
|
||
/* List of buffers that were used in the course of an update. */
|
||
XLList *buffers_used;
|
||
|
||
/* What rendering mode should be used. */
|
||
RenderMode render_mode;
|
||
};
|
||
|
||
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;
|
||
|
||
/* The X Windows depth. */
|
||
int depth;
|
||
|
||
/* The X Windows green, red, blue, and alpha masks. */
|
||
int red, green, blue, alpha;
|
||
|
||
/* The number of bits per pixel. */
|
||
int bits_per_pixel;
|
||
|
||
/* Number of supported screen modifiers. */
|
||
int n_supported_modifiers;
|
||
};
|
||
|
||
struct _DmaBufRecord
|
||
{
|
||
/* The XID of the pixmap. */
|
||
Pixmap pixmap;
|
||
|
||
/* The success callback. */
|
||
DmaBufSuccessFunc success_func;
|
||
|
||
/* The failure callback. */
|
||
DmaBufFailureFunc failure_func;
|
||
|
||
/* The callback data. */
|
||
void *data;
|
||
|
||
/* The picture format that will be used. */
|
||
XRenderPictFormat *format;
|
||
|
||
/* The next and last pending buffers in this list. */
|
||
DmaBufRecord *next, *last;
|
||
|
||
/* The depth of the pixmap. */
|
||
int depth;
|
||
|
||
/* The width and height. */
|
||
short width, height;
|
||
};
|
||
|
||
/* Number of format modifiers specified by the user. */
|
||
static int num_specified_modifiers;
|
||
|
||
/* Array of user-specified format modifiers. */
|
||
static uint64_t *user_specified_modifiers;
|
||
|
||
/* Hash table mapping between presentation windows and targets. */
|
||
static XLAssocTable *xid_table;
|
||
|
||
/* The identity transform. */
|
||
|
||
static XTransform identity_transform;
|
||
|
||
/* The default SHM formats. */
|
||
|
||
static ShmFormat default_formats[] =
|
||
{
|
||
{ WL_SHM_FORMAT_ARGB8888 },
|
||
{ WL_SHM_FORMAT_XRGB8888 },
|
||
};
|
||
|
||
/* List of all supported DRM formats. */
|
||
static DrmFormatInfo all_formats[] =
|
||
{
|
||
{
|
||
.format_code = DRM_FORMAT_ARGB8888,
|
||
.depth = 32,
|
||
.red = 0xff0000,
|
||
.green = 0xff00,
|
||
.blue = 0xff,
|
||
.alpha = 0xff000000,
|
||
.bits_per_pixel = 32,
|
||
},
|
||
{
|
||
.format_code = DRM_FORMAT_XRGB8888,
|
||
.depth = 24,
|
||
.red = 0xff0000,
|
||
.green = 0xff00,
|
||
.blue = 0xff,
|
||
.alpha = 0,
|
||
.bits_per_pixel = 32,
|
||
},
|
||
{
|
||
.format_code = DRM_FORMAT_XBGR8888,
|
||
.depth = 24,
|
||
.blue = 0xff0000,
|
||
.green = 0xff00,
|
||
.red = 0xff,
|
||
.alpha = 0,
|
||
.bits_per_pixel = 32,
|
||
},
|
||
{
|
||
.format_code = DRM_FORMAT_ABGR8888,
|
||
.depth = 32,
|
||
.blue = 0xff0000,
|
||
.green = 0xff00,
|
||
.red = 0xff,
|
||
.alpha = 0xff000000,
|
||
.bits_per_pixel = 32,
|
||
},
|
||
{
|
||
.format_code = DRM_FORMAT_BGRA8888,
|
||
.depth = 32,
|
||
.blue = 0xff000000,
|
||
.green = 0xff0000,
|
||
.red = 0xff00,
|
||
.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,
|
||
},
|
||
};
|
||
|
||
/* Array of all known DRM modifier names. */
|
||
static DrmModifierName known_modifiers[] =
|
||
{
|
||
/* Generated from drm_fourcc.h. */
|
||
DrmModifiersList
|
||
};
|
||
|
||
/* DRM formats reported to the caller. */
|
||
static DrmFormat *drm_formats;
|
||
|
||
/* Number of formats available. */
|
||
static int n_drm_formats;
|
||
|
||
/* List of buffers that are still pending asynchronous creation. */
|
||
static DmaBufRecord pending_success;
|
||
|
||
/* The id of the next round trip event. */
|
||
static uint64_t next_roundtrip_id;
|
||
|
||
/* A window used to receive round trip events. */
|
||
static Window round_trip_window;
|
||
|
||
/* The opcode of the DRI3 extension. */
|
||
static int dri3_opcode;
|
||
|
||
/* List of pixmap format values supported by the X server. */
|
||
static XPixmapFormatValues *x_formats;
|
||
|
||
/* Number of those formats. */
|
||
static int num_x_formats;
|
||
|
||
/* 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;
|
||
|
||
/* The device nodes of each provider. */
|
||
static dev_t *render_devices;
|
||
|
||
/* The number of device nodes. */
|
||
static int num_render_devices;
|
||
|
||
/* 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 (PictureBuffer *buffer, PictureTarget *target,
|
||
uint64_t roundtrip_id)
|
||
{
|
||
BufferActivityRecord *record;
|
||
|
||
/* Try to find an existing record. */
|
||
record = FindBufferActivityRecord (buffer, target);
|
||
|
||
if (!record)
|
||
{
|
||
record = XLMalloc (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 = roundtrip_id;
|
||
}
|
||
|
||
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, *last;
|
||
|
||
/* Look through the global activity list for a record matching
|
||
counter. */
|
||
record = all_activity.global_next;
|
||
while (record != &all_activity)
|
||
{
|
||
last = record;
|
||
record = record->global_next;
|
||
|
||
if (last->id == counter)
|
||
{
|
||
/* 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 (last);
|
||
MaybeRunIdleCallbacks (last->buffer, last->target);
|
||
|
||
/* Free the record. */
|
||
XLFree (last);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
static void
|
||
FreeBackBuffer (PictureTarget *target, BackBuffer *buffer)
|
||
{
|
||
XRenderFreePicture (compositor.display, (buffer->picture
|
||
& ~BufferSync
|
||
& ~BufferBusy));
|
||
XFreePixmap (compositor.display, (buffer->pixmap
|
||
& ~BufferSync));
|
||
FenceRelease (buffer->idle_fence);
|
||
|
||
/* Subtract the amount of pixels allocated from the target. */
|
||
if (target->client
|
||
&& IntSubtractWrapv (target->client->n_pixels,
|
||
buffer->n_pixels,
|
||
&target->client->n_pixels))
|
||
/* Handle overflow by just setting n_pixels to 0. */
|
||
target->client->n_pixels = 0;
|
||
|
||
XLFree (buffer);
|
||
}
|
||
|
||
static void
|
||
FreeBackBuffers (PictureTarget *target)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < ArrayElements (target->back_buffers); ++i)
|
||
{
|
||
if (target->back_buffers[i])
|
||
FreeBackBuffer (target, target->back_buffers[i]);
|
||
}
|
||
|
||
/* Also clear target->picture if it is a window target. */
|
||
if (target->window)
|
||
target->picture = None;
|
||
|
||
target->back_buffers[0] = NULL;
|
||
target->back_buffers[1] = NULL;
|
||
target->current_back_buffer = -1;
|
||
}
|
||
|
||
static BackBuffer *
|
||
CreateBackBuffer (PictureTarget *target)
|
||
{
|
||
BackBuffer *buffer;
|
||
XRenderPictureAttributes attrs;
|
||
Window root_window;
|
||
|
||
/* Create a single back buffer. */
|
||
root_window = DefaultRootWindow (compositor.display);
|
||
buffer = XLMalloc (sizeof *buffer);
|
||
|
||
buffer->pixmap
|
||
= XCreatePixmap (compositor.display, root_window,
|
||
target->width, target->height,
|
||
compositor.n_planes);
|
||
buffer->picture
|
||
= XRenderCreatePicture (compositor.display, buffer->pixmap,
|
||
compositor.argb_format, 0, &attrs);
|
||
buffer->idle_fence = GetFence ();
|
||
|
||
/* The target is no longer freshly presented. */
|
||
target->flags &= ~JustPresented;
|
||
|
||
/* Calculate how many pixels would be allocated and add it to the
|
||
target data. */
|
||
|
||
if (IntMultiplyWrapv (target->width, target->height,
|
||
&buffer->n_pixels))
|
||
buffer->n_pixels = UINT64_MAX;
|
||
|
||
if (target->client
|
||
&& IntAddWrapv (target->client->n_pixels, buffer->n_pixels,
|
||
&target->client->n_pixels))
|
||
target->client->n_pixels = UINT64_MAX;
|
||
|
||
return buffer;
|
||
}
|
||
|
||
/* Forward declaration. */
|
||
static XserverRegion ServerRegionFromRegion (pixman_region32_t *);
|
||
|
||
static PresentCompletionCallback *
|
||
MakePresentationCallback (void)
|
||
{
|
||
PresentCompletionCallback *callback_rec;
|
||
|
||
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;
|
||
|
||
return callback_rec;
|
||
}
|
||
|
||
static PresentCompletionCallback *
|
||
SwapBackBuffers (PictureTarget *target, pixman_region32_t *damage)
|
||
{
|
||
XserverRegion region;
|
||
XSyncFence fence;
|
||
BackBuffer *back_buffer;
|
||
int other;
|
||
PresentCompletionCallback *callback;
|
||
|
||
/* Swap back buffers according to the damage in region using the
|
||
Present extension. Return a present completion callback. */
|
||
|
||
if (damage)
|
||
region = ServerRegionFromRegion (damage);
|
||
else
|
||
region = None;
|
||
|
||
/* Find the current back buffer. */
|
||
back_buffer = target->back_buffers[target->current_back_buffer];
|
||
|
||
/* Get the idle fence. */
|
||
fence = FenceToXFence (back_buffer->idle_fence);
|
||
|
||
/* Present the current pixmap. */
|
||
present_serial++;
|
||
|
||
/* 0 is not a valid serial here, so don't let it reach that. */
|
||
if (!present_serial)
|
||
present_serial++;
|
||
|
||
if (target->render_mode == RenderModeAsync)
|
||
XPresentPixmap (compositor.display, target->window,
|
||
back_buffer->pixmap, present_serial,
|
||
None, region, 0, 0, None, None, fence,
|
||
PresentOptionAsync, 0, 0, 0, NULL, 0);
|
||
else if (target->render_mode == RenderModeVsyncAsync)
|
||
/* Present the pixmap asynchronously at the next frame. */
|
||
XPresentPixmap (compositor.display, target->window,
|
||
back_buffer->pixmap, present_serial,
|
||
None, region, 0, 0, None, None, fence,
|
||
PresentOptionAsync, target->next_msc,
|
||
1, 0, NULL, 0);
|
||
else
|
||
/* Present the pixmap synchronously at the next frame. */
|
||
XPresentPixmap (compositor.display, target->window,
|
||
back_buffer->pixmap, present_serial,
|
||
None, region, 0, 0, None, None, fence,
|
||
PresentOptionNone, target->next_msc,
|
||
1, 0, NULL, 0);
|
||
|
||
/* Mark the back buffer as busy, and the other back buffer as having
|
||
been released. */
|
||
SetBufferBusy (back_buffer);
|
||
back_buffer->present_serial = present_serial;
|
||
|
||
/* Set the current back buffer's age to 1, meaning that it reflects
|
||
the contents of the buffer 1 swap ago. */
|
||
back_buffer->age = 1;
|
||
|
||
/* Find the other back buffer and clear its busy flag if set. */
|
||
other = (target->current_back_buffer ? 0 : 1);
|
||
|
||
if (target->back_buffers[other])
|
||
{
|
||
target->back_buffers[other]->picture |= BufferSync;
|
||
ClearBufferBusy (target->back_buffers[other]);
|
||
|
||
/* Age the other buffer as well, given that it is not currently
|
||
garbaged. */
|
||
if (target->back_buffers[other]->age)
|
||
target->back_buffers[other]->age++;
|
||
}
|
||
|
||
if (region)
|
||
XFixesDestroyRegion (compositor.display, region);
|
||
|
||
target->current_back_buffer = -1;
|
||
target->picture = None;
|
||
|
||
callback = MakePresentationCallback ();
|
||
callback->id = present_serial;
|
||
|
||
return callback;
|
||
}
|
||
|
||
static void
|
||
SwapBackBuffersWithCopy (PictureTarget *target, pixman_region32_t *damage)
|
||
{
|
||
pixman_box32_t *boxes;
|
||
int nboxes, i, other;
|
||
BackBuffer *back_buffer;
|
||
|
||
boxes = pixman_region32_rectangles (damage, &nboxes);
|
||
|
||
if (nboxes > 20)
|
||
/* Damage is too large; simplify it by using the extents
|
||
instead. */
|
||
boxes = &damage->extents, nboxes = 1;
|
||
|
||
/* Find the current back buffer. */
|
||
back_buffer = target->back_buffers[target->current_back_buffer];
|
||
|
||
for (i = 0; i < nboxes; ++i)
|
||
XCopyArea (compositor.display,
|
||
back_buffer->pixmap,
|
||
target->window, target->gc,
|
||
boxes[i].x1, boxes[i].y1,
|
||
boxes[i].x2 - boxes[i].x1,
|
||
boxes[i].y1 - boxes[i].y2,
|
||
boxes[i].x1, boxes[i].y1);
|
||
|
||
/* Age and the other back buffer. N.B. that presenting and then
|
||
copying is not handled at all, so be sure to only call one or the
|
||
other for any given target. */
|
||
|
||
other = (target->current_back_buffer ? 0 : 1);
|
||
back_buffer->age = 1;
|
||
|
||
if (target->back_buffers[other])
|
||
target->back_buffers[other]++;
|
||
}
|
||
|
||
static void
|
||
MaybeAwaitBuffer (BackBuffer *buffer)
|
||
{
|
||
if (!(buffer->picture & BufferSync))
|
||
return;
|
||
|
||
/* Flush any pending presentation requests. */
|
||
if (!(buffer->pixmap & BufferSync))
|
||
XFlush (compositor.display);
|
||
|
||
/* Start waiting on the buffer's idle fence. */
|
||
FenceAwait (buffer->idle_fence);
|
||
buffer->picture &= ~BufferSync;
|
||
buffer->pixmap &= ~BufferSync;
|
||
|
||
/* Set the present serial to 0 so BufferSync is not set again
|
||
afterwards. */
|
||
buffer->present_serial = 0;
|
||
}
|
||
|
||
static BackBuffer *
|
||
GetNextBackBuffer (PictureTarget *target)
|
||
{
|
||
/* Return the next back buffer that will be used, but do not create
|
||
any if none exists. */
|
||
|
||
if (target->back_buffers[0]
|
||
&& !IsBufferBusy (target->back_buffers[0]))
|
||
return target->back_buffers[0];
|
||
|
||
return target->back_buffers[1];
|
||
}
|
||
|
||
static void
|
||
EnsurePicture (PictureTarget *target)
|
||
{
|
||
BackBuffer *buffer;
|
||
|
||
if (target->picture)
|
||
return;
|
||
|
||
/* Find a back buffer that isn't busy. */
|
||
if (!target->back_buffers[0]
|
||
|| !IsBufferBusy (target->back_buffers[0]))
|
||
{
|
||
buffer = target->back_buffers[0];
|
||
target->current_back_buffer = 0;
|
||
|
||
if (!buffer)
|
||
{
|
||
/* Create the first back buffer. */
|
||
buffer = CreateBackBuffer (target);
|
||
target->back_buffers[0] = buffer;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
buffer = target->back_buffers[1];
|
||
target->current_back_buffer = 1;
|
||
|
||
if (!buffer)
|
||
{
|
||
/* Create the second back buffer. */
|
||
buffer = CreateBackBuffer (target);
|
||
target->back_buffers[1] = buffer;
|
||
}
|
||
}
|
||
|
||
/* The selected buffer must not be busy. */
|
||
XLAssert (!IsBufferBusy (buffer));
|
||
|
||
/* If the fence will be triggered, wait on it now. */
|
||
MaybeAwaitBuffer (buffer);
|
||
|
||
/* Set target->picture. */
|
||
target->picture = buffer->picture;
|
||
}
|
||
|
||
|
||
|
||
static Visual *
|
||
PickVisual (int *depth)
|
||
{
|
||
int n_visuals;
|
||
XVisualInfo vinfo, *visuals;
|
||
Visual *selection;
|
||
|
||
vinfo.screen = DefaultScreen (compositor.display);
|
||
vinfo.class = TrueColor;
|
||
vinfo.depth = 32;
|
||
|
||
visuals = XGetVisualInfo (compositor.display, (VisualScreenMask
|
||
| VisualClassMask
|
||
| VisualDepthMask),
|
||
&vinfo, &n_visuals);
|
||
|
||
if (n_visuals)
|
||
{
|
||
selection = visuals[0].visual;
|
||
*depth = visuals[0].depth;
|
||
XFree (visuals);
|
||
|
||
return selection;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
AddAdditionalModifier (const char *name)
|
||
{
|
||
int i, j;
|
||
|
||
for (i = 0; i < ArrayElements (known_modifiers) - 1; ++i)
|
||
{
|
||
if (!strcmp (known_modifiers[i].name, name))
|
||
{
|
||
/* The modifier was found. See if it already exists. */
|
||
for (j = 0; j < num_specified_modifiers; ++j)
|
||
{
|
||
if (user_specified_modifiers[j]
|
||
== known_modifiers[i].modifier)
|
||
/* The modifier was already specified. */
|
||
return;
|
||
}
|
||
|
||
/* Otherwise, increment num_specified_modifiers. */
|
||
num_specified_modifiers++;
|
||
|
||
/* Make user_specified_modifiers big enough. */
|
||
user_specified_modifiers
|
||
= XLRealloc (user_specified_modifiers,
|
||
num_specified_modifiers
|
||
* sizeof *user_specified_modifiers);
|
||
|
||
/* And add the modifier. */
|
||
user_specified_modifiers[num_specified_modifiers - 1]
|
||
= known_modifiers[i].modifier;
|
||
return;
|
||
}
|
||
}
|
||
|
||
fprintf (stderr, "Unknown buffer format modifier: %s\n", name);
|
||
}
|
||
|
||
static void
|
||
ParseAdditionalModifiers (const char *string)
|
||
{
|
||
const char *end, *sep;
|
||
char *buffer;
|
||
|
||
end = string + strlen (string);
|
||
|
||
while (string < end)
|
||
{
|
||
/* Find the next comma. */
|
||
sep = strchr (string, ',');
|
||
|
||
if (!sep)
|
||
sep = end;
|
||
|
||
/* Copy the text between string and sep into buffer. */
|
||
buffer = alloca (sep - string + 1);
|
||
memcpy (buffer, string, sep - string);
|
||
buffer[sep - string] = '\0';
|
||
|
||
/* Add this modifier. */
|
||
AddAdditionalModifier (buffer);
|
||
|
||
string = sep + 1;
|
||
}
|
||
}
|
||
|
||
static void
|
||
InitAdditionalModifiers (void)
|
||
{
|
||
XrmDatabase rdb;
|
||
XrmName namelist[3];
|
||
XrmClass classlist[3];
|
||
XrmValue value;
|
||
XrmRepresentation type;
|
||
char *name;
|
||
|
||
rdb = XrmGetDatabase (compositor.display);
|
||
|
||
if (!rdb)
|
||
return;
|
||
|
||
if (!asprintf (&name, "additionalModifiersOfScreen%d",
|
||
DefaultScreen (compositor.display)))
|
||
return;
|
||
|
||
namelist[1] = XrmStringToQuark (name);
|
||
free (name);
|
||
|
||
namelist[0] = app_quark;
|
||
namelist[2] = NULLQUARK;
|
||
|
||
classlist[1] = XrmStringToQuark ("AdditionalModifiers");
|
||
classlist[0] = resource_quark;
|
||
classlist[2] = NULLQUARK;
|
||
|
||
if (XrmQGetResource (rdb, namelist, classlist,
|
||
&type, &value)
|
||
&& type == QString)
|
||
ParseAdditionalModifiers ((const char *) value.addr);
|
||
}
|
||
|
||
/* Forward declaration. */
|
||
static void AddRenderFlag (int);
|
||
|
||
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\n");
|
||
return False;
|
||
}
|
||
|
||
/* Find out what additional modifiers the user wants. */
|
||
InitAdditionalModifiers ();
|
||
|
||
/* Add the direct presentation support flag. */
|
||
AddRenderFlag (SupportsDirectPresent);
|
||
|
||
/* 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, Window window,
|
||
unsigned long standard_event_mask)
|
||
{
|
||
XRenderPictureAttributes picture_attrs;
|
||
PictureTarget *target;
|
||
XGCValues gcvalues;
|
||
|
||
/* 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;
|
||
|
||
if (window != None)
|
||
/* Start selecting for presentation events from the given
|
||
window. */
|
||
target->presentation_event_context
|
||
= XPresentSelectInput (compositor.display, window,
|
||
PresentIdleNotifyMask
|
||
| PresentCompleteNotifyMask);
|
||
else
|
||
/* Create the picture corresponding to this drawable. */
|
||
target->picture = XRenderCreatePicture (compositor.display,
|
||
drawable,
|
||
compositor.argb_format,
|
||
0, &picture_attrs);
|
||
|
||
/* Initialize the current back buffer. */
|
||
target->current_back_buffer = -1;
|
||
|
||
/* 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;
|
||
|
||
if (window)
|
||
{
|
||
/* Add the window to the assoc table. */
|
||
XLMakeAssoc (xid_table, window, target);
|
||
|
||
/* Create the GC used to swap back buffers. */
|
||
target->gc = XCreateGC (compositor.display,
|
||
window, 0, &gcvalues);
|
||
}
|
||
|
||
return (RenderTarget) (void *) target;
|
||
}
|
||
|
||
static RenderTarget
|
||
TargetFromWindow (Window window, unsigned long event_mask)
|
||
{
|
||
return TargetFromDrawable (window, window, event_mask);
|
||
}
|
||
|
||
static RenderTarget
|
||
TargetFromPixmap (Pixmap pixmap)
|
||
{
|
||
return TargetFromDrawable (pixmap, None, NoEventMask);
|
||
}
|
||
|
||
static Bool
|
||
SetRenderMode (RenderTarget target, RenderMode mode,
|
||
uint64_t target_msc)
|
||
{
|
||
PictureTarget *pict_target;
|
||
|
||
pict_target = target.pointer;
|
||
|
||
/* Set the rendering mode to use with target. */
|
||
pict_target->render_mode = mode;
|
||
|
||
/* And set the target msc. */
|
||
pict_target->next_msc = target_msc;
|
||
return True;
|
||
}
|
||
|
||
static void
|
||
SetClient (RenderTarget target, struct wl_client *client)
|
||
{
|
||
PictureTarget *picture_target;
|
||
ClientErrorData *data;
|
||
uint64_t pixels;
|
||
|
||
picture_target = target.pointer;
|
||
|
||
/* Release the client data if some is already attached. */
|
||
if (picture_target->client)
|
||
{
|
||
if (picture_target->client
|
||
&& IntMultiplyWrapv (picture_target->width,
|
||
picture_target->height,
|
||
&pixels)
|
||
&& IntSubtractWrapv (picture_target->client->n_pixels,
|
||
pixels,
|
||
&picture_target->client->n_pixels))
|
||
picture_target->client->n_pixels = 0;
|
||
|
||
ReleaseClientData (picture_target->client);
|
||
}
|
||
picture_target->client = NULL;
|
||
|
||
if (!client)
|
||
return;
|
||
|
||
/* Retain the client data. */
|
||
data = ErrorDataForClient (client);
|
||
picture_target->client = data;
|
||
data->refcount++;
|
||
}
|
||
|
||
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;
|
||
uint64_t pixels;
|
||
|
||
pict_target = target.pointer;
|
||
|
||
if (width != pict_target->width
|
||
|| height != pict_target->height)
|
||
{
|
||
/* Recreate all the back buffers for the new target size. */
|
||
FreeBackBuffers (pict_target);
|
||
|
||
/* First, remove existing pixels from the client. */
|
||
if (pict_target->client
|
||
&& IntMultiplyWrapv (pict_target->width,
|
||
pict_target->height,
|
||
&pixels)
|
||
&& IntSubtractWrapv (pict_target->client->n_pixels,
|
||
pixels,
|
||
&pict_target->client->n_pixels))
|
||
pict_target->client->n_pixels = 0;
|
||
|
||
/* Next, add the new width and height to the client. */
|
||
if (pict_target->client
|
||
&& IntMultiplyWrapv (width, height, &pixels)
|
||
&& IntAddWrapv (pict_target->client->n_pixels,
|
||
pixels,
|
||
&pict_target->client->n_pixels))
|
||
pict_target->client->n_pixels = UINT64_MAX;
|
||
}
|
||
|
||
pict_target->width = width;
|
||
pict_target->height = height;
|
||
}
|
||
|
||
static Picture
|
||
PictureFromTarget (RenderTarget target)
|
||
{
|
||
PictureTarget *pict_target;
|
||
|
||
pict_target = target.pointer;
|
||
return pict_target->picture;
|
||
}
|
||
|
||
static void
|
||
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)
|
||
{
|
||
PictureTarget *pict_target;
|
||
PresentRecord *record, *last;
|
||
BufferActivityRecord *activity_record, *activity_last;
|
||
IdleCallback *idle, *idle_last;
|
||
uint64_t pixels;
|
||
|
||
pict_target = target.pointer;
|
||
|
||
/* Assert that there are no more buffers left in the active buffer
|
||
list. */
|
||
XLAssert (pict_target->buffers_used == NULL);
|
||
|
||
/* Destroy all back buffers. */
|
||
FreeBackBuffers (pict_target);
|
||
|
||
if (pict_target->window)
|
||
{
|
||
/* Delete the window from the assoc table. */
|
||
XLDeleteAssoc (xid_table, pict_target->window);
|
||
|
||
/* Free the GC. */
|
||
XFreeGC (compositor.display, pict_target->gc);
|
||
}
|
||
|
||
/* 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);
|
||
}
|
||
|
||
if (pict_target->picture)
|
||
XRenderFreePicture (compositor.display,
|
||
pict_target->picture);
|
||
|
||
/* Dereference the client data if it is set. Also, remove the
|
||
pixels recorded. */
|
||
if (pict_target->client)
|
||
{
|
||
if (pict_target->client
|
||
&& IntMultiplyWrapv (pict_target->width,
|
||
pict_target->height,
|
||
&pixels)
|
||
&& IntSubtractWrapv (pict_target->client->n_pixels,
|
||
pixels,
|
||
&pict_target->client->n_pixels))
|
||
pict_target->client->n_pixels = 0;
|
||
|
||
ReleaseClientData (pict_target->client);
|
||
}
|
||
|
||
XFree (pict_target);
|
||
}
|
||
|
||
static void
|
||
FillBoxesWithTransparency (RenderTarget target, pixman_box32_t *boxes,
|
||
int nboxes, int min_x, int min_y)
|
||
{
|
||
XRectangle *rects;
|
||
static XRenderColor color;
|
||
int i;
|
||
PictureTarget *pict_target;
|
||
|
||
pict_target = target.pointer;
|
||
|
||
/* Ensure a back buffer is created or used. */
|
||
EnsurePicture (pict_target);
|
||
|
||
if (nboxes < 256)
|
||
rects = alloca (sizeof *rects * nboxes);
|
||
else
|
||
rects = XLMalloc (sizeof *rects * nboxes);
|
||
|
||
/* Pacify GCC. */
|
||
memset (rects, 0, sizeof *rects * nboxes);
|
||
|
||
for (i = 0; i < nboxes; ++i)
|
||
{
|
||
rects[i].x = BoxStartX (boxes[i]) - min_x;
|
||
rects[i].y = BoxStartY (boxes[i]) - min_y;
|
||
rects[i].width = BoxWidth (boxes[i]);
|
||
rects[i].height = BoxHeight (boxes[i]);
|
||
}
|
||
|
||
XRenderFillRectangles (compositor.display, PictOpClear,
|
||
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,
|
||
pict_target->picture, &color, x, y,
|
||
width, height);
|
||
}
|
||
|
||
static int
|
||
ConvertOperation (Operation op)
|
||
{
|
||
switch (op)
|
||
{
|
||
case OperationOver:
|
||
return PictOpOver;
|
||
|
||
case OperationSource:
|
||
return PictOpSrc;
|
||
}
|
||
|
||
abort ();
|
||
}
|
||
|
||
static double
|
||
GetScale (DrawParams *params)
|
||
{
|
||
if (params->flags & ScaleSet)
|
||
return params->scale;
|
||
|
||
return 1.0;
|
||
}
|
||
|
||
static double
|
||
GetSourceX (DrawParams *params)
|
||
{
|
||
if (params->flags & OffsetSet)
|
||
return params->off_x;
|
||
|
||
return 0.0;
|
||
}
|
||
|
||
static double
|
||
GetSourceY (DrawParams *params)
|
||
{
|
||
if (params->flags & OffsetSet)
|
||
return params->off_y;
|
||
|
||
return 0.0;
|
||
}
|
||
|
||
static BufferTransform
|
||
GetBufferTransform (DrawParams *params)
|
||
{
|
||
if (params->flags & TransformSet)
|
||
return params->transform;
|
||
|
||
return Normal;
|
||
}
|
||
|
||
static Bool
|
||
CompareStretch (DrawParams *params, DrawParams *other)
|
||
{
|
||
if ((params->flags & StretchSet)
|
||
!= (other->flags & StretchSet))
|
||
return False;
|
||
|
||
if (params->flags & StretchSet)
|
||
return (other->crop_width == params->crop_width
|
||
&& other->crop_height == params->crop_height
|
||
&& other->stretch_width == params->stretch_width
|
||
&& other->stretch_height == params->stretch_height);
|
||
|
||
return True;
|
||
}
|
||
|
||
static void
|
||
MaybeApplyTransform (PictureBuffer *buffer, DrawParams *params)
|
||
{
|
||
XTransform transform;
|
||
Matrix ftransform;
|
||
|
||
if (GetScale (params) == GetScale (&buffer->params)
|
||
&& GetSourceX (params) == GetSourceX (&buffer->params)
|
||
&& GetSourceY (params) == GetSourceY (&buffer->params)
|
||
&& (GetBufferTransform (params)
|
||
== GetBufferTransform (&buffer->params))
|
||
&& CompareStretch (params, &buffer->params))
|
||
/* Nothing changed. */
|
||
return;
|
||
|
||
/* Otherwise, compute and apply the new transform. */
|
||
if (!params->flags)
|
||
/* No transform of any kind is set, use the identity matrix. */
|
||
XRenderSetPictureTransform (compositor.display,
|
||
buffer->picture,
|
||
&identity_transform);
|
||
else
|
||
{
|
||
MatrixIdentity (&ftransform);
|
||
|
||
/* The buffer transform must always be applied first. */
|
||
|
||
if (params->flags & TransformSet)
|
||
ApplyInverseTransform (buffer->width, buffer->height,
|
||
&ftransform, params->transform);
|
||
|
||
/* Note that these must be applied in the right order. First,
|
||
the scale is applied. Then, the offset, and finally the
|
||
stretch. */
|
||
|
||
if (params->flags & ScaleSet)
|
||
MatrixScale (&ftransform, 1.0 / GetScale (params),
|
||
1.0 / GetScale (params));
|
||
|
||
if (params->flags & OffsetSet)
|
||
MatrixTranslate (&ftransform, params->off_x, params->off_y);
|
||
|
||
if (params->flags & StretchSet)
|
||
MatrixScale (&ftransform,
|
||
params->crop_width / params->stretch_width,
|
||
params->crop_height / params->stretch_height);
|
||
|
||
/* Export the matrix to an XTransform. */
|
||
MatrixExport (&ftransform, &transform);
|
||
|
||
/* Set the transform. The transform maps from dest coords to
|
||
picture coords, so that [X Y 1] = TRANSFORM * [DX DY 1]. */
|
||
XRenderSetPictureTransform (compositor.display, buffer->picture,
|
||
&transform);
|
||
}
|
||
|
||
/* Save the parameters into buffer. */
|
||
buffer->params = *params;
|
||
}
|
||
|
||
static void
|
||
Composite (RenderBuffer buffer, RenderTarget target,
|
||
Operation op, int src_x, int src_y, int x, int y,
|
||
int width, int height, DrawParams *draw_params)
|
||
{
|
||
PictureBuffer *picture_buffer;
|
||
PictureTarget *picture_target;
|
||
XLList *tem;
|
||
|
||
picture_buffer = buffer.pointer;
|
||
picture_target = target.pointer;
|
||
|
||
/* Ensure a back buffer is created. */
|
||
EnsurePicture (picture_target);
|
||
|
||
/* Maybe set the transform if the parameters changed. (draw_params
|
||
specifies a transform to apply to the buffer, not to the
|
||
target.) */
|
||
MaybeApplyTransform (picture_buffer, draw_params);
|
||
|
||
/* Do the compositing. */
|
||
XRenderComposite (compositor.display, ConvertOperation (op),
|
||
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);
|
||
|
||
for (tem = picture_target->buffers_used; tem; tem = tem->next)
|
||
{
|
||
/* Return if the buffer is already in the buffers_used list. */
|
||
|
||
if (tem->data == picture_buffer)
|
||
return;
|
||
}
|
||
|
||
/* Record pending buffer activity; the roundtrip message is then
|
||
sent later. */
|
||
|
||
picture_target->buffers_used
|
||
= XLListPrepend (picture_target->buffers_used, picture_buffer);
|
||
}
|
||
|
||
static RenderCompletionKey
|
||
FinishRender (RenderTarget target, pixman_region32_t *damage,
|
||
RenderCompletionFunc function, void *data)
|
||
{
|
||
XLList *tem, *last;
|
||
PictureTarget *pict_target;
|
||
uint64_t roundtrip_id;
|
||
PresentCompletionCallback *callback_rec;
|
||
|
||
pict_target = target.pointer;
|
||
|
||
if (!pict_target->buffers_used)
|
||
/* No buffers were used. */
|
||
return NULL;
|
||
|
||
/* Finish rendering. This function then sends a single roundtrip
|
||
message and records buffer activity for each buffer involved in
|
||
the update based on that. */
|
||
|
||
roundtrip_id = SendRoundtripMessage ();
|
||
tem = pict_target->buffers_used;
|
||
|
||
while (tem)
|
||
{
|
||
last = tem;
|
||
tem = tem->next;
|
||
|
||
/* Record buffer activity on this one buffer. */
|
||
RecordBufferActivity (last->data, pict_target,
|
||
roundtrip_id);
|
||
|
||
/* Free the list element. */
|
||
XLFree (last);
|
||
}
|
||
|
||
/* Clear buffers_used. */
|
||
pict_target->buffers_used = NULL;
|
||
|
||
/* Swap the back buffer to the screen if it was used. */
|
||
|
||
if (pict_target->current_back_buffer != -1)
|
||
{
|
||
/* If a callback was specified, then use the Present extension,
|
||
and return a completion callback. */
|
||
|
||
if (function)
|
||
{
|
||
callback_rec = SwapBackBuffers (pict_target, damage);
|
||
callback_rec->function = function;
|
||
callback_rec->data = data;
|
||
|
||
return (RenderCompletionKey) callback_rec;
|
||
}
|
||
|
||
/* Otherwise, swap buffers using XCopyArea. */
|
||
SwapBackBuffersWithCopy (pict_target, damage);
|
||
return NULL;
|
||
}
|
||
|
||
/* Otherwise, there is nothing to do. */
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
CancelCompletionCallback (RenderCompletionKey key)
|
||
{
|
||
PresentCompletionCallback *callback;
|
||
|
||
callback = key;
|
||
callback->next->last = callback->last;
|
||
callback->last->next = callback->next;
|
||
|
||
XLFree (callback);
|
||
}
|
||
|
||
static int
|
||
TargetAge (RenderTarget target)
|
||
{
|
||
BackBuffer *buffer;
|
||
PictureTarget *pict_target;
|
||
|
||
pict_target = target.pointer;
|
||
|
||
if (pict_target->flags & JustPresented)
|
||
return -2;
|
||
|
||
buffer = GetNextBackBuffer (pict_target);
|
||
|
||
if (!buffer)
|
||
return -1;
|
||
|
||
if (buffer->age > INT_MAX)
|
||
return -1;
|
||
|
||
return (int) buffer->age - 1;
|
||
}
|
||
|
||
/* At first glance, it seems like this should be easy to support using
|
||
DRI3 and synchronization extension fences. Unfortunately, the
|
||
"fences" used by DRI3 are userspace fences implemented by the
|
||
xshmfence library, and not Android dma-fences, so there is no
|
||
straightforward implementation. */
|
||
|
||
static RenderFence
|
||
ImportFdFence (int fd, Bool *error)
|
||
{
|
||
*error = True;
|
||
return (RenderFence) (XID) None;
|
||
}
|
||
|
||
static void
|
||
WaitFence (RenderFence fence)
|
||
{
|
||
/* Unsupported. */
|
||
}
|
||
|
||
static void
|
||
DeleteFence (RenderFence fence)
|
||
{
|
||
/* Unsupported. */
|
||
}
|
||
|
||
static int
|
||
GetFinishFence (Bool *error)
|
||
{
|
||
*error = True;
|
||
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;
|
||
|
||
/* Since presentation has happened, free all the back buffers. */
|
||
FreeBackBuffers (pict_target);
|
||
|
||
/* Build the damage region. */
|
||
if (damage)
|
||
region = ServerRegionFromRegion (damage);
|
||
else
|
||
region = None;
|
||
|
||
if (pict_target->render_mode == RenderModeAsync)
|
||
/* Present the pixmap asynchronously at an msc of 0. */
|
||
XPresentPixmap (compositor.display, pict_target->window, buffer->pixmap,
|
||
++present_serial, None, region, 0, 0, None, None, None,
|
||
PresentOptionAsync, 0, 0, 0, NULL, 0);
|
||
else if (pict_target->render_mode == RenderModeVsyncAsync)
|
||
/* Present the pixmap at the next msc if possible. Otherwise,
|
||
present the pixmap asynchronously. */
|
||
XPresentPixmap (compositor.display, pict_target->window, buffer->pixmap,
|
||
++present_serial, None, region, 0, 0, None, None, None,
|
||
PresentOptionAsync, pict_target->next_msc, 1, 0, NULL, 0);
|
||
else
|
||
/* Present the pixmap at the next msc. */
|
||
XPresentPixmap (compositor.display, pict_target->window, buffer->pixmap,
|
||
++present_serial, None, region, 0, 0, None, None, None,
|
||
PresentOptionNone, pict_target->next_msc, 1, 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 = MakePresentationCallback ();
|
||
callback_rec->function = callback;
|
||
callback_rec->data = data;
|
||
callback_rec->id = present_serial;
|
||
|
||
/* Mark the target as just having been presented. The flag is
|
||
cleared the next time a back buffer is created. */
|
||
pict_target->flags |= JustPresented;
|
||
|
||
return callback_rec;
|
||
}
|
||
|
||
static RenderCompletionKey
|
||
NotifyMsc (RenderTarget target, RenderCompletionFunc callback,
|
||
void *data)
|
||
{
|
||
PictureTarget *pict_target;
|
||
PresentCompletionCallback *callback_rec;
|
||
|
||
pict_target = target.pointer;
|
||
|
||
if (pict_target->render_mode == RenderModeAsync)
|
||
return NULL;
|
||
|
||
/* Allocate a presentation completion callback. */
|
||
callback_rec = MakePresentationCallback ();
|
||
callback_rec->function = callback;
|
||
callback_rec->data = data;
|
||
callback_rec->id = ++present_serial;
|
||
|
||
/* Ask for a notification. */
|
||
if (pict_target->render_mode == RenderModeVsync)
|
||
XPresentNotifyMSC (compositor.display, pict_target->window,
|
||
present_serial, 0, 1, 0);
|
||
else
|
||
XPresentNotifyMSC (compositor.display, pict_target->window,
|
||
present_serial, pict_target->next_msc,
|
||
1, 0);
|
||
|
||
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 RenderFuncs picture_render_funcs =
|
||
{
|
||
.init_render_funcs = InitRenderFuncs,
|
||
.target_from_window = TargetFromWindow,
|
||
.target_from_pixmap = TargetFromPixmap,
|
||
.set_render_mode = SetRenderMode,
|
||
.set_client = SetClient,
|
||
.set_standard_event_mask = SetStandardEventMask,
|
||
.note_target_size = NoteTargetSize,
|
||
.picture_from_target = PictureFromTarget,
|
||
.free_picture_from_target = FreePictureFromTarget,
|
||
.destroy_render_target = DestroyRenderTarget,
|
||
.fill_boxes_with_transparency = FillBoxesWithTransparency,
|
||
.clear_rectangle = ClearRectangle,
|
||
.composite = Composite,
|
||
.finish_render = FinishRender,
|
||
.cancel_completion_callback = CancelCompletionCallback,
|
||
.target_age = TargetAge,
|
||
.import_fd_fence = ImportFdFence,
|
||
.wait_fence = WaitFence,
|
||
.delete_fence = DeleteFence,
|
||
.get_finish_fence = GetFinishFence,
|
||
.present_to_window = PresentToWindow,
|
||
.notify_msc = NotifyMsc,
|
||
.cancel_presentation_callback = CancelPresentationCallback,
|
||
};
|
||
|
||
static void
|
||
AddRenderFlag (int flag)
|
||
{
|
||
picture_render_funcs.flags |= flag;
|
||
}
|
||
|
||
static DrmFormatInfo *
|
||
FindFormatMatching (XRenderPictFormat *format)
|
||
{
|
||
unsigned long alpha, red, green, blue;
|
||
int i;
|
||
|
||
if (format->type != PictTypeDirect)
|
||
/* No DRM formats are colormapped. */
|
||
return NULL;
|
||
|
||
alpha = format->direct.alphaMask << format->direct.alpha;
|
||
red = format->direct.redMask << format->direct.red;
|
||
green = format->direct.greenMask << format->direct.green;
|
||
blue = format->direct.blueMask << format->direct.blue;
|
||
|
||
for (i = 0; i < ArrayElements (all_formats); ++i)
|
||
{
|
||
if (all_formats[i].depth == format->depth
|
||
&& all_formats[i].red == red
|
||
&& all_formats[i].green == green
|
||
&& all_formats[i].blue == blue
|
||
&& all_formats[i].alpha == alpha)
|
||
return &all_formats[i];
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static Bool
|
||
HavePixmapFormat (int depth, int bpp)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < num_x_formats; ++i)
|
||
{
|
||
if (x_formats[i].depth == depth
|
||
&& x_formats[i].bits_per_pixel == bpp)
|
||
return True;
|
||
}
|
||
|
||
return False;
|
||
}
|
||
|
||
static Bool
|
||
FindSupportedFormats (void)
|
||
{
|
||
int count;
|
||
XRenderPictFormat *format;
|
||
DrmFormatInfo *info;
|
||
Bool supported;
|
||
|
||
count = 0;
|
||
supported = False;
|
||
|
||
do
|
||
{
|
||
format = XRenderFindFormat (compositor.display, 0,
|
||
NULL, count);
|
||
count++;
|
||
|
||
if (!format)
|
||
break;
|
||
|
||
info = FindFormatMatching (format);
|
||
|
||
/* See if the info's depth and bpp are supported. */
|
||
if (info
|
||
&& !HavePixmapFormat (info->depth,
|
||
info->bits_per_pixel))
|
||
continue;
|
||
|
||
if (info && !info->format)
|
||
info->format = format;
|
||
|
||
if (info)
|
||
supported = True;
|
||
}
|
||
while (format);
|
||
|
||
return supported;
|
||
}
|
||
|
||
static Window
|
||
MakeCheckWindow (void)
|
||
{
|
||
XSetWindowAttributes attrs;
|
||
unsigned long flags;
|
||
|
||
/* Make an override-redirect window to use as the icon surface. */
|
||
flags = (CWColormap | CWBorderPixel | CWEventMask
|
||
| CWOverrideRedirect);
|
||
attrs.colormap = compositor.colormap;
|
||
attrs.border_pixel = border_pixel;
|
||
attrs.event_mask = (ExposureMask | StructureNotifyMask);
|
||
attrs.override_redirect = 1;
|
||
|
||
return XCreateWindow (compositor.display,
|
||
DefaultRootWindow (compositor.display),
|
||
0, 0, 1, 1, 0, compositor.n_planes,
|
||
InputOutput, compositor.visual, flags,
|
||
&attrs);
|
||
}
|
||
|
||
static void
|
||
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, j;
|
||
uint64_t *mods;
|
||
|
||
cookies = alloca (sizeof *cookies * ArrayElements (all_formats));
|
||
|
||
/* Create a temporary window similar to ones surfaces will use to
|
||
determine which modifiers are supported. */
|
||
check_window = MakeCheckWindow ();
|
||
pair_count = 0;
|
||
|
||
for (i = 0; i < ArrayElements (all_formats); ++i)
|
||
{
|
||
if (all_formats[i].format)
|
||
{
|
||
cookies[i]
|
||
= xcb_dri3_get_supported_modifiers (compositor.conn,
|
||
check_window, all_formats[i].depth,
|
||
all_formats[i].bits_per_pixel);
|
||
|
||
/* pair_count is the number of format-modifier pairs that
|
||
will be returned. First, add one for each implicit
|
||
modifier, and another one for each manually specified
|
||
modifier. */
|
||
pair_count += 1 + num_specified_modifiers;
|
||
}
|
||
}
|
||
|
||
/* Delete the temporary window used to query for modifiers. */
|
||
XDestroyWindow (compositor.display, check_window);
|
||
|
||
for (i = 0; i < ArrayElements (all_formats); ++i)
|
||
{
|
||
if (!all_formats[i].format)
|
||
continue;
|
||
|
||
reply = xcb_dri3_get_supported_modifiers_reply (compositor.conn,
|
||
cookies[i], NULL);
|
||
|
||
if (!reply)
|
||
continue;
|
||
|
||
mods
|
||
= xcb_dri3_get_supported_modifiers_screen_modifiers (reply);
|
||
length
|
||
= xcb_dri3_get_supported_modifiers_screen_modifiers_length (reply);
|
||
|
||
all_formats[i].supported_modifiers = XLMalloc (sizeof *mods * length);
|
||
all_formats[i].n_supported_modifiers = 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);
|
||
free (reply);
|
||
}
|
||
|
||
*pair_count_return = pair_count;
|
||
}
|
||
|
||
static void
|
||
InitDrmFormats (void)
|
||
{
|
||
int pair_count, i, j, n, k;
|
||
|
||
/* First, look up which formats are supported. */
|
||
if (!FindSupportedFormats ())
|
||
return;
|
||
|
||
/* Then, look up modifiers. */
|
||
FindSupportedModifiers (&pair_count);
|
||
|
||
/* Allocate the amount of memory we need to store the DRM format
|
||
list. */
|
||
drm_formats = XLCalloc (pair_count, sizeof *drm_formats);
|
||
n = 0;
|
||
|
||
/* Populate the format list. */
|
||
for (i = 0; i < ArrayElements (all_formats); ++i)
|
||
{
|
||
if (!all_formats[i].format)
|
||
continue;
|
||
|
||
/* Check n < pair_count. */
|
||
XLAssert (n < pair_count);
|
||
|
||
/* Add the implicit modifier. */
|
||
drm_formats[n].drm_format = all_formats[i].format_code;
|
||
drm_formats[n].drm_modifier = DRM_FORMAT_MOD_INVALID;
|
||
n++;
|
||
|
||
/* And add all of the user-specified modifiers. */
|
||
for (j = 0; j < num_specified_modifiers; ++j)
|
||
{
|
||
/* Assert that n < pair_count. */
|
||
XLAssert (n < pair_count);
|
||
|
||
drm_formats[n].drm_format = all_formats[i].format_code;
|
||
drm_formats[n].drm_modifier = user_specified_modifiers[j];
|
||
n++;
|
||
}
|
||
|
||
/* Now add every supported explicit modifier. */
|
||
for (j = 0; j < all_formats[i].n_supported_modifiers; ++i)
|
||
{
|
||
/* Ignore previously specified modifiers. */
|
||
|
||
if ((all_formats[i].supported_modifiers[j]
|
||
== DRM_FORMAT_MOD_INVALID))
|
||
continue;
|
||
|
||
/* Ignore user-specified modifiers. */
|
||
|
||
for (k = 0; k < num_specified_modifiers; ++k)
|
||
{
|
||
if (user_specified_modifiers[k]
|
||
== all_formats[i].supported_modifiers[j])
|
||
continue;
|
||
}
|
||
|
||
/* Check n < pair_count. */
|
||
XLAssert (n < pair_count);
|
||
|
||
/* 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];
|
||
n++;
|
||
}
|
||
}
|
||
|
||
/* Set the number of supported formats to the pair count. */
|
||
n_drm_formats = pair_count;
|
||
|
||
/* Return. */
|
||
return;
|
||
}
|
||
|
||
static DrmFormat *
|
||
GetDrmFormats (int *num_formats)
|
||
{
|
||
*num_formats = n_drm_formats;
|
||
return drm_formats;
|
||
}
|
||
|
||
static dev_t
|
||
GetRenderDevice (xcb_dri3_open_reply_t *reply, Bool *error)
|
||
{
|
||
int *fds, fd;
|
||
struct stat dev_stat;
|
||
|
||
fds = xcb_dri3_open_reply_fds (compositor.conn, reply);
|
||
|
||
if (!fds)
|
||
{
|
||
*error = True;
|
||
return (dev_t) 0;
|
||
}
|
||
|
||
fd = fds[0];
|
||
|
||
if (fstat (fd, &dev_stat) != 0)
|
||
{
|
||
close (fd);
|
||
*error = True;
|
||
return (dev_t) 0;
|
||
}
|
||
|
||
close (fd);
|
||
return dev_stat.st_rdev;
|
||
}
|
||
|
||
static dev_t *
|
||
GetRenderDevices (int *num_devices)
|
||
{
|
||
Window root;
|
||
xcb_randr_get_providers_cookie_t cookie;
|
||
xcb_randr_get_providers_reply_t *reply;
|
||
xcb_randr_provider_t *providers;
|
||
int nproviders;
|
||
xcb_dri3_open_cookie_t *open_cookies;
|
||
xcb_dri3_open_reply_t *open_reply;
|
||
xcb_generic_error_t *error;
|
||
int ndevices, i;
|
||
dev_t *devices;
|
||
Bool error_experienced;
|
||
|
||
if (render_devices)
|
||
{
|
||
*num_devices = num_render_devices;
|
||
return render_devices;
|
||
}
|
||
|
||
root = DefaultRootWindow (compositor.display);
|
||
|
||
/* Get a list of all providers on the default screen. */
|
||
cookie = xcb_randr_get_providers (compositor.conn,
|
||
root);
|
||
reply = xcb_randr_get_providers_reply (compositor.conn,
|
||
cookie, NULL);
|
||
|
||
if (!reply)
|
||
return NULL;
|
||
|
||
providers = xcb_randr_get_providers_providers (reply);
|
||
nproviders = xcb_randr_get_providers_providers_length (reply);
|
||
|
||
/* Now, open each and every provider. */
|
||
open_cookies = alloca (nproviders * sizeof *open_cookies);
|
||
|
||
for (i = 0; i < nproviders; ++i)
|
||
open_cookies[i] = xcb_dri3_open (compositor.conn, root,
|
||
providers[i]);
|
||
|
||
/* Free the provider list and wait for replies from the X server.
|
||
Also, allocate an array large enough to hold each device. */
|
||
|
||
free (reply);
|
||
|
||
/* Allocate 1 extra provider so that render_devices is not set to
|
||
NULL if there are no providers. */
|
||
devices = XLCalloc (nproviders + 1, sizeof *devices);
|
||
ndevices = 0;
|
||
|
||
for (i = 0; i < nproviders; ++i)
|
||
{
|
||
open_reply = xcb_dri3_open_reply (compositor.conn, open_cookies[i],
|
||
&error);
|
||
|
||
if (error || !open_reply)
|
||
{
|
||
if (error)
|
||
free (error);
|
||
|
||
continue;
|
||
}
|
||
|
||
/* Now obtain the device node associated with the opened DRM
|
||
node. */
|
||
error_experienced = False;
|
||
devices[ndevices] = GetRenderDevice (open_reply, &error_experienced);
|
||
free (open_reply);
|
||
|
||
if (error_experienced)
|
||
/* An error occured. */
|
||
continue;
|
||
|
||
/* Otherwise, increment ndevices. */
|
||
ndevices++;
|
||
}
|
||
|
||
num_render_devices = ndevices;
|
||
render_devices = devices;
|
||
|
||
/* Return the device list. */
|
||
*num_devices = ndevices;
|
||
return devices;
|
||
}
|
||
|
||
static ShmFormat *
|
||
GetShmFormats (int *num_formats)
|
||
{
|
||
/* Return the two mandatory shm formats. */
|
||
*num_formats = ArrayElements (default_formats);
|
||
return default_formats;
|
||
}
|
||
|
||
static int
|
||
DepthForDmabufFormat (uint32_t drm_format, int *bits_per_pixel)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < ArrayElements (all_formats); ++i)
|
||
{
|
||
if (all_formats[i].format_code == drm_format
|
||
&& all_formats[i].format)
|
||
{
|
||
*bits_per_pixel = all_formats[i].bits_per_pixel;
|
||
|
||
return all_formats[i].depth;
|
||
}
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
static XRenderPictFormat *
|
||
PictFormatForDmabufFormat (uint32_t drm_format)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < ArrayElements (all_formats); ++i)
|
||
{
|
||
if (all_formats[i].format_code == drm_format
|
||
&& all_formats[i].format)
|
||
return all_formats[i].format;
|
||
}
|
||
|
||
/* This shouldn't happen, since the format was already verified in
|
||
Create. */
|
||
abort ();
|
||
}
|
||
|
||
static void
|
||
CloseFileDescriptors (DmaBufAttributes *attributes)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < attributes->n_planes; ++i)
|
||
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)
|
||
{
|
||
int depth, bpp;
|
||
Pixmap pixmap;
|
||
Picture picture;
|
||
xcb_void_cookie_t check_cookie;
|
||
xcb_generic_error_t *xerror;
|
||
XRenderPictFormat *format;
|
||
XRenderPictureAttributes picture_attrs;
|
||
PictureBuffer *buffer;
|
||
|
||
/* Find the depth and bpp corresponding to the format. */
|
||
depth = DepthForDmabufFormat (attributes->drm_format, &bpp);
|
||
|
||
/* Flags are not supported. */
|
||
if (attributes->flags || depth == -1)
|
||
goto error;
|
||
|
||
/* Create the pixmap. */
|
||
pixmap = xcb_generate_id (compositor.conn);
|
||
check_cookie
|
||
= xcb_dri3_pixmap_from_buffers (compositor.conn, pixmap,
|
||
DefaultRootWindow (compositor.display),
|
||
attributes->n_planes,
|
||
attributes->width,
|
||
attributes->height,
|
||
attributes->offsets[0],
|
||
attributes->strides[0],
|
||
attributes->offsets[1],
|
||
attributes->strides[1],
|
||
attributes->offsets[2],
|
||
attributes->strides[2],
|
||
attributes->offsets[3],
|
||
attributes->strides[3],
|
||
depth, bpp,
|
||
attributes->modifier,
|
||
attributes->fds);
|
||
xerror = xcb_request_check (compositor.conn, check_cookie);
|
||
|
||
/* A platform specific error occured creating this buffer. Signal
|
||
failure. */
|
||
if (xerror)
|
||
{
|
||
free (xerror);
|
||
goto error;
|
||
}
|
||
|
||
/* Otherwise, create the picture and free the pixmap. */
|
||
format = PictFormatForDmabufFormat (attributes->drm_format);
|
||
XLAssert (format != NULL);
|
||
|
||
picture = XRenderCreatePicture (compositor.display, pixmap,
|
||
format, 0, &picture_attrs);
|
||
|
||
/* Create the wrapper object. */
|
||
buffer = XLCalloc (1, sizeof *buffer);
|
||
buffer->picture = picture;
|
||
buffer->pixmap = pixmap;
|
||
buffer->depth = depth;
|
||
buffer->width = attributes->width;
|
||
buffer->height = attributes->height;
|
||
|
||
/* 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;
|
||
|
||
/* And mark it as opaque if it is. */
|
||
if (!format->direct.alphaMask)
|
||
buffer->flags |= IsOpaque;
|
||
|
||
return (RenderBuffer) (void *) buffer;
|
||
|
||
error:
|
||
CloseFileDescriptors (attributes);
|
||
*error = True;
|
||
return (RenderBuffer) NULL;
|
||
}
|
||
|
||
static void
|
||
ForceRoundTrip (void)
|
||
{
|
||
uint64_t id;
|
||
XEvent event;
|
||
|
||
/* Send an event with a monotonically increasing identifier to
|
||
ourselves.
|
||
|
||
Once the last event is received, create the actual buffers for
|
||
each buffer resource for which error handlers have not run. */
|
||
|
||
id = next_roundtrip_id++;
|
||
|
||
memset (&event, 0, sizeof event);
|
||
|
||
event.xclient.type = ClientMessage;
|
||
event.xclient.window = round_trip_window;
|
||
event.xclient.message_type = _XL_DMA_BUF_CREATED;
|
||
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);
|
||
}
|
||
|
||
static void
|
||
FinishDmaBufRecord (DmaBufRecord *pending, Bool success)
|
||
{
|
||
Picture picture;
|
||
XRenderPictureAttributes picture_attrs;
|
||
PictureBuffer *buffer;
|
||
|
||
if (success)
|
||
{
|
||
/* This is just to pacify GCC. */
|
||
memset (&picture_attrs, 0, sizeof picture_attrs);
|
||
|
||
picture = XRenderCreatePicture (compositor.display,
|
||
pending->pixmap,
|
||
pending->format, 0,
|
||
&picture_attrs);
|
||
|
||
/* Create the wrapper structure. */
|
||
buffer = XLCalloc (1, sizeof *buffer);
|
||
buffer->picture = picture;
|
||
buffer->pixmap = pending->pixmap;
|
||
buffer->depth = pending->depth;
|
||
buffer->width = pending->width;
|
||
buffer->height = pending->height;
|
||
|
||
/* 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;
|
||
|
||
/* And mark it as opaque if it is. */
|
||
if (!pending->format->direct.alphaMask)
|
||
buffer->flags |= IsOpaque;
|
||
|
||
/* Call the creation success function with the new picture. */
|
||
pending->success_func ((RenderBuffer) (void *) buffer,
|
||
pending->data);
|
||
}
|
||
else
|
||
/* Call the failure function with the data. */
|
||
pending->failure_func (pending->data);
|
||
|
||
/* Unlink the creation record. */
|
||
pending->last->next = pending->next;
|
||
pending->next->last = pending->last;
|
||
|
||
XLFree (pending);
|
||
}
|
||
|
||
static void
|
||
FinishBufferCreation (void)
|
||
{
|
||
DmaBufRecord *next, *last;
|
||
|
||
/* It is now known that all records in pending_success have been
|
||
created. Create pictures and call the success function for
|
||
each. */
|
||
next = pending_success.next;
|
||
while (next != &pending_success)
|
||
{
|
||
last = next;
|
||
next = next->next;
|
||
|
||
FinishDmaBufRecord (last, True);
|
||
}
|
||
}
|
||
|
||
/* N.B. that the caller is supposed to keep callback_data around until
|
||
one of success_func or failure_func is called. */
|
||
|
||
static void
|
||
BufferFromDmaBufAsync (DmaBufAttributes *attributes,
|
||
DmaBufSuccessFunc success_func,
|
||
DmaBufFailureFunc failure_func,
|
||
void *callback_data)
|
||
{
|
||
DmaBufRecord *record;
|
||
int depth, bpp;
|
||
Pixmap pixmap;
|
||
|
||
/* Find the depth and bpp corresponding to the format. */
|
||
depth = DepthForDmabufFormat (attributes->drm_format, &bpp);
|
||
|
||
/* Flags are not supported. */
|
||
if (attributes->flags || depth == -1)
|
||
goto error;
|
||
|
||
/* Create the pixmap. */
|
||
pixmap = xcb_generate_id (compositor.conn);
|
||
xcb_dri3_pixmap_from_buffers (compositor.conn, pixmap,
|
||
DefaultRootWindow (compositor.display),
|
||
attributes->n_planes, attributes->width,
|
||
attributes->height,
|
||
attributes->offsets[0],
|
||
attributes->strides[0],
|
||
attributes->offsets[1],
|
||
attributes->strides[1],
|
||
attributes->offsets[2],
|
||
attributes->strides[2],
|
||
attributes->offsets[3],
|
||
attributes->strides[3],
|
||
depth, bpp,
|
||
attributes->modifier, attributes->fds);
|
||
|
||
/* Then, link the resulting pixmap and callbacks onto the list of
|
||
pending buffers. Right now, we do not know if the creation will
|
||
be rejected by the X server, so we first arrange to catch all
|
||
errors from DRI3PixmapFromBuffers, and send the create event the
|
||
next time we know that a roundtrip has happened without any
|
||
errors being raised. */
|
||
|
||
record = XLMalloc (sizeof *record);
|
||
record->success_func = success_func;
|
||
record->failure_func = failure_func;
|
||
record->data = callback_data;
|
||
record->format
|
||
= PictFormatForDmabufFormat (attributes->drm_format);
|
||
record->pixmap = pixmap;
|
||
record->depth = depth;
|
||
record->width = attributes->width;
|
||
record->height = attributes->height;
|
||
|
||
XLAssert (record->format != NULL);
|
||
|
||
record->next = pending_success.next;
|
||
record->last = &pending_success;
|
||
pending_success.next->last = record;
|
||
pending_success.next = record;
|
||
|
||
ForceRoundTrip ();
|
||
|
||
return;
|
||
|
||
error:
|
||
/* Just call the failure func to signal failure this early on. */
|
||
failure_func (callback_data);
|
||
|
||
/* Then, close the file descriptors. */
|
||
CloseFileDescriptors (attributes);
|
||
}
|
||
|
||
static int
|
||
DepthForFormat (uint32_t format, int *bpp)
|
||
{
|
||
switch (format)
|
||
{
|
||
case WL_SHM_FORMAT_ARGB8888:
|
||
*bpp = 32;
|
||
return 32;
|
||
|
||
case WL_SHM_FORMAT_XRGB8888:
|
||
*bpp = 32;
|
||
return 24;
|
||
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
static XRenderPictFormat *
|
||
PictFormatForFormat (uint32_t format)
|
||
{
|
||
switch (format)
|
||
{
|
||
case WL_SHM_FORMAT_ARGB8888:
|
||
return compositor.argb_format;
|
||
|
||
case WL_SHM_FORMAT_XRGB8888:
|
||
return compositor.xrgb_format;
|
||
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
static RenderBuffer
|
||
BufferFromShm (SharedMemoryAttributes *attributes, Bool *error)
|
||
{
|
||
XRenderPictureAttributes picture_attrs;
|
||
xcb_shm_seg_t seg;
|
||
Pixmap pixmap;
|
||
Picture picture;
|
||
int fd, depth, format, bpp;
|
||
PictureBuffer *buffer;
|
||
XRenderPictFormat *pict_format;
|
||
|
||
depth = DepthForFormat (attributes->format, &bpp);
|
||
format = attributes->format;
|
||
|
||
if (!depth)
|
||
{
|
||
*error = True;
|
||
return (RenderBuffer) NULL;
|
||
}
|
||
|
||
/* Duplicate the fd, since XCB closes file descriptors after sending
|
||
them. */
|
||
fd = fcntl (attributes->fd, F_DUPFD_CLOEXEC, 0);
|
||
|
||
if (fd < 0)
|
||
{
|
||
*error = True;
|
||
return (RenderBuffer) NULL;
|
||
}
|
||
|
||
/* Obtain the picture format. */
|
||
pict_format = PictFormatForFormat (format);
|
||
XLAssert (pict_format != NULL);
|
||
|
||
/* Now, allocate the XIDs for the shm segment and pixmap. */
|
||
seg = xcb_generate_id (compositor.conn);
|
||
pixmap = xcb_generate_id (compositor.conn);
|
||
|
||
/* Create the segment and attach the pixmap to it. */
|
||
xcb_shm_attach_fd (compositor.conn, seg, fd, false);
|
||
xcb_shm_create_pixmap (compositor.conn, pixmap,
|
||
DefaultRootWindow (compositor.display),
|
||
attributes->width, attributes->height,
|
||
depth, seg, attributes->offset);
|
||
xcb_shm_detach (compositor.conn, seg);
|
||
|
||
/* Create the picture for the pixmap. */
|
||
picture = XRenderCreatePicture (compositor.display, pixmap,
|
||
pict_format, 0, &picture_attrs);
|
||
|
||
/* Create the wrapper object. */
|
||
buffer = XLCalloc (1, sizeof *buffer);
|
||
buffer->picture = picture;
|
||
buffer->pixmap = pixmap;
|
||
buffer->depth = depth;
|
||
buffer->width = attributes->width;
|
||
buffer->height = attributes->height;
|
||
|
||
/* 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 (pict_format))
|
||
buffer->flags |= CanPresent;
|
||
|
||
/* And mark it as opaque if it is. */
|
||
if (!pict_format->direct.alphaMask)
|
||
buffer->flags |= IsOpaque;
|
||
|
||
/* Return the picture. */
|
||
return (RenderBuffer) (void *) buffer;
|
||
}
|
||
|
||
static int
|
||
GetScanlinePad (int depth)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < num_x_formats; ++i)
|
||
{
|
||
if (x_formats[i].depth == depth)
|
||
return x_formats[i].scanline_pad;
|
||
}
|
||
|
||
/* This should never happen in practice. */
|
||
return -1;
|
||
}
|
||
|
||
/* Roundup rounds up NBYTES to PAD. PAD is a value that can appear as
|
||
the scanline pad. Macro borrowed from Xlib, as usual for everyone
|
||
working with such images. */
|
||
#define Roundup(nbytes, pad) ((((nbytes) + ((pad) - 1)) / (pad)) * ((pad) >> 3))
|
||
|
||
static Bool
|
||
ValidateShmParams (uint32_t format, uint32_t width, uint32_t height,
|
||
int32_t offset, int32_t stride, size_t pool_size)
|
||
{
|
||
int bpp, depth;
|
||
long wanted_stride;
|
||
size_t total_size;
|
||
|
||
/* Obtain the depth and bpp. */
|
||
depth = DepthForFormat (format, &bpp);
|
||
XLAssert (depth != 0);
|
||
|
||
/* If any signed values are negative, return. */
|
||
if (offset < 0 || stride < 0)
|
||
return False;
|
||
|
||
/* Obtain width * bpp padded to the scanline pad. Xlib or the X
|
||
server do not try to handle overflow here... */
|
||
wanted_stride = Roundup (width * (long) bpp,
|
||
GetScanlinePad (depth));
|
||
|
||
/* Assume that size_t can hold int32_t. */
|
||
total_size = offset;
|
||
|
||
/* Calculate the total data size. */
|
||
if (IntMultiplyWrapv (stride, height, &total_size))
|
||
return False;
|
||
|
||
if (IntAddWrapv (offset, total_size, &total_size))
|
||
return False;
|
||
|
||
/* Verify that the stride is correct and the image fits. */
|
||
if (stride != wanted_stride || total_size > pool_size)
|
||
return False;
|
||
|
||
return True;
|
||
}
|
||
|
||
static RenderBuffer
|
||
BufferFromSinglePixel (uint32_t red, uint32_t green, uint32_t blue,
|
||
uint32_t alpha, Bool *error)
|
||
{
|
||
Picture picture;
|
||
Pixmap pixmap;
|
||
XRenderPictureAttributes picture_attrs;
|
||
XRenderColor color;
|
||
PictureBuffer *buffer;
|
||
|
||
/* Create the pixmap. */
|
||
pixmap = XCreatePixmap (compositor.display,
|
||
DefaultRootWindow (compositor.display),
|
||
1, 1, compositor.n_planes);
|
||
|
||
/* Create the picture. */
|
||
picture = XRenderCreatePicture (compositor.display, pixmap,
|
||
compositor.argb_format, 0,
|
||
&picture_attrs);
|
||
|
||
/* Fill the picture with the single pixel. */
|
||
color.red = red >> 16;
|
||
color.green = green >> 16;
|
||
color.blue = blue >> 16;
|
||
color.alpha = alpha >> 16;
|
||
XRenderFillRectangle (compositor.display, PictOpSrc,
|
||
picture, &color, 0, 0, 1, 1);
|
||
|
||
/* 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
|
||
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)
|
||
{
|
||
FreeAnyBuffer (buffer);
|
||
}
|
||
|
||
static void
|
||
FreeSinglePixelBuffer (RenderBuffer buffer)
|
||
{
|
||
FreeAnyBuffer (buffer);
|
||
}
|
||
|
||
static void
|
||
SetupMitShm (void)
|
||
{
|
||
xcb_shm_query_version_reply_t *reply;
|
||
xcb_shm_query_version_cookie_t cookie;
|
||
|
||
/* This shouldn't be freed. */
|
||
const xcb_query_extension_reply_t *ext;
|
||
|
||
ext = xcb_get_extension_data (compositor.conn, &xcb_shm_id);
|
||
|
||
if (!ext || !ext->present)
|
||
{
|
||
fprintf (stderr, "The MIT-SHM extension is not supported by this X server.\n");
|
||
exit (1);
|
||
}
|
||
|
||
cookie = xcb_shm_query_version (compositor.conn);
|
||
reply = xcb_shm_query_version_reply (compositor.conn,
|
||
cookie, NULL);
|
||
|
||
if (!reply)
|
||
{
|
||
fprintf (stderr, "The MIT-SHM extension on this X server is too old.\n");
|
||
exit (1);
|
||
}
|
||
else if (reply->major_version < 1
|
||
|| (reply->major_version == 1
|
||
&& reply->minor_version < 2))
|
||
{
|
||
fprintf (stderr, "The MIT-SHM extension on this X server is too old"
|
||
" to support POSIX shared memory.\n");
|
||
exit (1);
|
||
}
|
||
|
||
free (reply);
|
||
|
||
/* Now check that the mandatory image formats are supported. */
|
||
|
||
if (!HavePixmapFormat (24, 32))
|
||
{
|
||
fprintf (stderr, "X server does not support pixmap format"
|
||
" of depth 24 with 32 bits per pixel\n");
|
||
exit (1);
|
||
}
|
||
|
||
if (!HavePixmapFormat (32, 32))
|
||
{
|
||
fprintf (stderr, "X server does not support pixmap format"
|
||
" of depth 32 with 32 bits per pixel\n");
|
||
exit (1);
|
||
}
|
||
}
|
||
|
||
static void
|
||
InitBufferFuncs (void)
|
||
{
|
||
xcb_dri3_query_version_cookie_t cookie;
|
||
xcb_dri3_query_version_reply_t *reply;
|
||
const xcb_query_extension_reply_t *ext;
|
||
|
||
/* Obtain the list of supported pixmap formats from the X
|
||
server. */
|
||
x_formats = XListPixmapFormats (compositor.display, &num_x_formats);
|
||
|
||
if (!x_formats)
|
||
{
|
||
fprintf (stderr, "No pixmap formats could be retrieved from"
|
||
" the X server\n");
|
||
return;
|
||
}
|
||
|
||
/* Set up the MIT shared memory extension. It is required to
|
||
work. */
|
||
SetupMitShm ();
|
||
|
||
/* XRender should already have been set up; it is used for things
|
||
other than rendering as well. */
|
||
|
||
ext = xcb_get_extension_data (compositor.conn, &xcb_dri3_id);
|
||
reply = NULL;
|
||
|
||
if (ext && ext->present)
|
||
{
|
||
cookie = xcb_dri3_query_version (compositor.conn, 1, 2);
|
||
reply = xcb_dri3_query_version_reply (compositor.conn, cookie,
|
||
NULL);
|
||
|
||
if (!reply)
|
||
goto error;
|
||
|
||
if (reply->major_version < 1
|
||
|| (reply->major_version == 1
|
||
&& reply->minor_version < 2))
|
||
goto error;
|
||
|
||
dri3_opcode = ext->major_opcode;
|
||
|
||
/* Initialize DRM formats. */
|
||
InitDrmFormats ();
|
||
}
|
||
else
|
||
error:
|
||
fprintf (stderr, "Warning: the X server does not support a new enough version of"
|
||
" the DRI3 extension.\nHardware acceleration will not be available.\n");
|
||
|
||
if (reply)
|
||
free (reply);
|
||
}
|
||
|
||
static Bool
|
||
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 Bool
|
||
IsBufferOpaque (RenderBuffer buffer)
|
||
{
|
||
PictureBuffer *pict_buffer;
|
||
|
||
/* Return whether or not the buffer format is opaque, which lets us
|
||
optimize some things out. */
|
||
pict_buffer = buffer.pointer;
|
||
|
||
if (pict_buffer->flags & IsOpaque)
|
||
return True;
|
||
|
||
return False;
|
||
}
|
||
|
||
static BufferFuncs picture_buffer_funcs =
|
||
{
|
||
.get_drm_formats = GetDrmFormats,
|
||
.get_render_devices = GetRenderDevices,
|
||
.get_shm_formats = GetShmFormats,
|
||
.buffer_from_dma_buf = BufferFromDmaBuf,
|
||
.buffer_from_dma_buf_async = BufferFromDmaBufAsync,
|
||
.buffer_from_shm = BufferFromShm,
|
||
.validate_shm_params = ValidateShmParams,
|
||
.buffer_from_single_pixel = BufferFromSinglePixel,
|
||
.free_shm_buffer = FreeShmBuffer,
|
||
.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,
|
||
.is_buffer_opaque = IsBufferOpaque,
|
||
.init_buffer_funcs = InitBufferFuncs,
|
||
};
|
||
|
||
Bool
|
||
HandleErrorForPictureRenderer (XErrorEvent *error)
|
||
{
|
||
DmaBufRecord *record, *next;
|
||
|
||
if (error->request_code == dri3_opcode
|
||
&& error->minor_code == xDRI3BuffersFromPixmap)
|
||
{
|
||
/* Something chouldn't be created. Find what failed and unlink
|
||
it. */
|
||
|
||
next = pending_success.next;
|
||
|
||
while (next != &pending_success)
|
||
{
|
||
record = next;
|
||
next = next->next;
|
||
|
||
if (record->pixmap == error->resourceid)
|
||
{
|
||
/* Call record's failure callback and unlink it. */
|
||
FinishDmaBufRecord (record, False);
|
||
break;
|
||
}
|
||
}
|
||
|
||
return True;
|
||
}
|
||
|
||
return False;
|
||
}
|
||
|
||
static Bool
|
||
HandlePresentCompleteNotify (XPresentCompleteNotifyEvent *complete)
|
||
{
|
||
PresentCompletionCallback *callback, *last;
|
||
#ifdef DEBUG_PRESENT_TIME
|
||
static uint64_t last_ust;
|
||
#endif
|
||
Bool rc;
|
||
|
||
callback = all_completion_callbacks.next;
|
||
rc = False;
|
||
|
||
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, complete->msc, complete->ust);
|
||
last->next->last = last->last;
|
||
last->last->next = last->next;
|
||
|
||
#ifdef DEBUG_PRESENT_TIME
|
||
fprintf (stderr, "Time taken: %lu us (%g ms) (= 1/%g s)\n",
|
||
complete->ust - last_ust,
|
||
(complete->ust - last_ust) / 1000.0,
|
||
1000.0 / ((complete->ust - last_ust) / 1000.0));
|
||
last_ust = complete->ust;
|
||
#endif
|
||
|
||
XLFree (last);
|
||
rc = True;
|
||
}
|
||
}
|
||
|
||
return rc;
|
||
}
|
||
|
||
static Bool
|
||
HandlePresentIdleNotify (XPresentIdleNotifyEvent *idle)
|
||
{
|
||
PresentRecord *record;
|
||
PictureTarget *target;
|
||
PictureBuffer *buffer;
|
||
int i;
|
||
|
||
/* Handle a single idle notify event. Find the target corresponding
|
||
to idle->window. */
|
||
target = XLLookUpAssoc (xid_table, idle->window);
|
||
|
||
if (target)
|
||
{
|
||
/* See if the idle record corresponds to any of target's back
|
||
buffers, and set the BufferSync flag if that is the case. */
|
||
for (i = 0; i < ArrayElements (target->back_buffers); ++i)
|
||
{
|
||
if (target->back_buffers[i]
|
||
&& (target->back_buffers[i]->present_serial
|
||
== idle->serial_number))
|
||
{
|
||
/* Now say that the target idle fence must be waited
|
||
for. */
|
||
target->back_buffers[i]->present_serial = 0;
|
||
target->back_buffers[i]->picture |= BufferSync;
|
||
target->back_buffers[i]->pixmap |= BufferSync;
|
||
ClearBufferBusy (target->back_buffers[i]);
|
||
|
||
return True;
|
||
}
|
||
}
|
||
|
||
/* 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)
|
||
{
|
||
uint64_t id, low, high;
|
||
|
||
if (event->type == ClientMessage
|
||
&& event->xclient.message_type == _XL_DMA_BUF_CREATED)
|
||
{
|
||
/* 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);
|
||
|
||
/* Ignore the message if the id is too old. */
|
||
if (id < next_roundtrip_id)
|
||
/* Otherwise, it means buffer creation was successful.
|
||
Complete all pending buffer creation. */
|
||
FinishBufferCreation ();
|
||
|
||
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)
|
||
{
|
||
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;
|
||
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,
|
||
&picture_buffer_funcs);
|
||
}
|