xserver-multidpi/hw/xwayland/xwayland-glamor-eglstream.c
Olivier Fourdan 7d509b6f34 xwayland/eglstream: Remove stream validity
To avoid an EGL stream in the wrong state, if the window pixmap changed
before the stream was connected, we would still keep the pending stream
but mark it as invalid. Once the callback is received, the pending would
be simply discarded.

But all of this is actually to avoid a bug in egl-wayland, there should
not be any problem with Xwayland destroying an EGL stream while the
compositor is still using it.

With that bug now fixed in egl-wayland 1.1.7, we can safely drop all
that logic from Xwayland EGLstream backend.

Signed-off-by: Olivier Fourdan <ofourdan@redhat.com>
Reviewed-by: Michel Dänzer <mdaenzer@redhat.com>
Closes: https://gitlab.freedesktop.org/xorg/xserver/-/issues/1189
2021-06-30 09:06:37 +02:00

1192 lines
38 KiB
C

/*
* Copyright © 2017 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including
* the next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Authors:
* Lyude Paul <lyude@redhat.com>
*
*/
#include <xwayland-config.h>
#define MESA_EGL_NO_X11_HEADERS
#define EGL_NO_X11
#include <glamor_egl.h>
#include <glamor.h>
#include <glamor_priv.h>
#include <glamor_transform.h>
#include <glamor_transfer.h>
#include <xf86drm.h>
#include <dri3.h>
#include <drm_fourcc.h>
#include <epoxy/egl.h>
#include "xwayland-glamor.h"
#include "xwayland-pixmap.h"
#include "xwayland-screen.h"
#include "xwayland-window.h"
#include "wayland-eglstream-client-protocol.h"
#include "wayland-eglstream-controller-client-protocol.h"
#include "linux-dmabuf-unstable-v1-client-protocol.h"
struct xwl_eglstream_private {
EGLDeviceEXT egl_device;
struct wl_eglstream_display *display;
struct wl_eglstream_controller *controller;
uint32_t display_caps;
EGLConfig config;
SetWindowPixmapProcPtr SetWindowPixmap;
Bool have_egl_damage;
Bool have_egl_stream_flush;
GLint blit_prog;
GLuint blit_vao;
GLuint blit_vbo;
GLuint blit_is_rgba_pos;
};
enum xwl_pixmap_type {
XWL_PIXMAP_EGLSTREAM, /* Pixmaps created by glamor. */
XWL_PIXMAP_DMA_BUF, /* Pixmaps allocated through DRI3. */
};
struct xwl_pixmap {
enum xwl_pixmap_type type;
/* add any new <= 4-byte member here to avoid holes on 64-bit */
struct xwl_screen *xwl_screen;
struct wl_buffer *buffer;
struct wl_callback *pending_cb;
Bool wait_for_buffer_release;
/* XWL_PIXMAP_EGLSTREAM. */
EGLStreamKHR stream;
EGLSurface surface;
/* XWL_PIXMAP_DMA_BUF. */
EGLImage image;
};
static DevPrivateKeyRec xwl_eglstream_private_key;
static inline struct xwl_eglstream_private *
xwl_eglstream_get(struct xwl_screen *xwl_screen)
{
return dixLookupPrivate(&xwl_screen->screen->devPrivates,
&xwl_eglstream_private_key);
}
static GLint
xwl_eglstream_compile_glsl_prog(GLenum type, const char *source)
{
GLint ok;
GLint prog;
prog = glCreateShader(type);
glShaderSource(prog, 1, (const GLchar **) &source, NULL);
glCompileShader(prog);
glGetShaderiv(prog, GL_COMPILE_STATUS, &ok);
if (!ok) {
GLchar *info;
GLint size;
glGetShaderiv(prog, GL_INFO_LOG_LENGTH, &size);
info = malloc(size);
if (info) {
glGetShaderInfoLog(prog, size, NULL, info);
ErrorF("Failed to compile %s: %s\n",
type == GL_FRAGMENT_SHADER ? "FS" : "VS", info);
ErrorF("Program source:\n%s", source);
free(info);
}
else
ErrorF("Failed to get shader compilation info.\n");
FatalError("GLSL compile failure\n");
}
return prog;
}
static GLuint
xwl_eglstream_build_glsl_prog(GLuint vs, GLuint fs)
{
GLint ok;
GLuint prog;
prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
if (!ok) {
GLchar *info;
GLint size;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &size);
info = malloc(size);
glGetProgramInfoLog(prog, size, NULL, info);
ErrorF("Failed to link: %s\n", info);
FatalError("GLSL link failure\n");
}
return prog;
}
static void
xwl_eglstream_cleanup(struct xwl_screen *xwl_screen)
{
struct xwl_eglstream_private *xwl_eglstream =
xwl_eglstream_get(xwl_screen);
if (xwl_eglstream->display)
wl_eglstream_display_destroy(xwl_eglstream->display);
if (xwl_eglstream->controller)
wl_eglstream_controller_destroy(xwl_eglstream->controller);
if (xwl_eglstream->blit_prog) {
glDeleteProgram(xwl_eglstream->blit_prog);
glDeleteBuffers(1, &xwl_eglstream->blit_vbo);
}
free(xwl_eglstream);
}
static Bool
xwl_glamor_egl_supports_device_probing(void)
{
return epoxy_has_egl_extension(NULL, "EGL_EXT_device_base");
}
static void **
xwl_glamor_egl_get_devices(int *num_devices)
{
EGLDeviceEXT *devices, *tmp;
Bool ret;
int drm_dev_count = 0;
int i;
if (!xwl_glamor_egl_supports_device_probing())
return NULL;
/* Get the number of devices */
ret = eglQueryDevicesEXT(0, NULL, num_devices);
if (!ret || *num_devices < 1)
return NULL;
devices = calloc(*num_devices, sizeof(EGLDeviceEXT));
if (!devices)
return NULL;
ret = eglQueryDevicesEXT(*num_devices, devices, num_devices);
if (!ret)
goto error;
/* We're only ever going to care about devices that support
* EGL_EXT_device_drm, so filter out the ones that don't
*/
for (i = 0; i < *num_devices; i++) {
const char *extension_str =
eglQueryDeviceStringEXT(devices[i], EGL_EXTENSIONS);
if (!epoxy_extension_in_string(extension_str, "EGL_EXT_device_drm"))
continue;
devices[drm_dev_count++] = devices[i];
}
if (!drm_dev_count)
goto error;
*num_devices = drm_dev_count;
tmp = realloc(devices, sizeof(EGLDeviceEXT) * drm_dev_count);
if (!tmp)
goto error;
devices = tmp;
return devices;
error:
free(devices);
return NULL;
}
static Bool
xwl_glamor_egl_device_has_egl_extensions(void *device,
const char **ext_list, size_t size)
{
EGLDisplay egl_display;
int i;
Bool has_exts = TRUE;
egl_display = glamor_egl_get_display(EGL_PLATFORM_DEVICE_EXT, device);
if (!egl_display || !eglInitialize(egl_display, NULL, NULL))
return FALSE;
for (i = 0; i < size; i++) {
if (!epoxy_has_egl_extension(egl_display, ext_list[i])) {
has_exts = FALSE;
break;
}
}
eglTerminate(egl_display);
return has_exts;
}
static void
xwl_eglstream_destroy_pixmap_stream(struct xwl_pixmap *xwl_pixmap)
{
struct xwl_screen *xwl_screen = xwl_pixmap->xwl_screen;
/* If we're using this stream in the current egl context, unbind it so the
* driver doesn't keep it around until the next eglMakeCurrent()
* don't have to keep it around until something else changes the surface
*/
xwl_glamor_egl_make_current(xwl_screen);
if (eglGetCurrentSurface(EGL_READ) == xwl_pixmap->surface ||
eglGetCurrentSurface(EGL_DRAW) == xwl_pixmap->surface) {
eglMakeCurrent(xwl_screen->egl_display,
EGL_NO_SURFACE, EGL_NO_SURFACE,
xwl_screen->egl_context);
}
if (xwl_pixmap->surface != EGL_NO_SURFACE)
eglDestroySurface(xwl_screen->egl_display, xwl_pixmap->surface);
if (xwl_pixmap->stream != EGL_NO_STREAM_KHR)
eglDestroyStreamKHR(xwl_screen->egl_display, xwl_pixmap->stream);
if (xwl_pixmap->buffer)
wl_buffer_destroy(xwl_pixmap->buffer);
if (xwl_pixmap->image != EGL_NO_IMAGE_KHR)
eglDestroyImageKHR(xwl_screen->egl_display, xwl_pixmap->image);
free(xwl_pixmap);
}
static void
xwl_glamor_eglstream_remove_pending_stream(struct xwl_pixmap *xwl_pixmap)
{
if (xwl_pixmap->pending_cb) {
wl_callback_destroy(xwl_pixmap->pending_cb);
xwl_pixmap->pending_cb = NULL;
}
}
static Bool
xwl_glamor_eglstream_destroy_pixmap(PixmapPtr pixmap)
{
struct xwl_pixmap *xwl_pixmap = xwl_pixmap_get(pixmap);
if (xwl_pixmap && pixmap->refcnt == 1) {
xwl_glamor_eglstream_remove_pending_stream(xwl_pixmap);
xwl_eglstream_destroy_pixmap_stream(xwl_pixmap);
xwl_pixmap_del_buffer_release_cb(pixmap);
}
return glamor_destroy_pixmap(pixmap);
}
static struct wl_buffer *
xwl_glamor_eglstream_get_wl_buffer_for_pixmap(PixmapPtr pixmap)
{
struct xwl_pixmap *xwl_pixmap = xwl_pixmap_get(pixmap);
if (!xwl_pixmap)
return NULL;
return xwl_pixmap->buffer;
}
static void
xwl_eglstream_cancel_pending_stream(PixmapPtr pixmap)
{
struct xwl_pixmap *xwl_pixmap;
xwl_pixmap = xwl_pixmap_get(pixmap);
if (xwl_pixmap)
xwl_glamor_eglstream_remove_pending_stream(xwl_pixmap);
}
static void
xwl_eglstream_set_window_pixmap(WindowPtr window, PixmapPtr pixmap)
{
ScreenPtr screen = window->drawable.pScreen;
struct xwl_screen *xwl_screen = xwl_screen_get(screen);
struct xwl_eglstream_private *xwl_eglstream =
xwl_eglstream_get(xwl_screen);
PixmapPtr old_pixmap;
/* The pixmap for this window has changed.
* If that occurs while there is a stream pending, i.e. before the
* compositor has finished attaching the consumer for the window's
* pixmap's original eglstream, then a producer could no longer be
* attached, so the stream would be useless.
*/
old_pixmap = (*screen->GetWindowPixmap) (window);
if (old_pixmap && old_pixmap != pixmap)
xwl_eglstream_cancel_pending_stream(old_pixmap);
xwl_screen->screen->SetWindowPixmap = xwl_eglstream->SetWindowPixmap;
(*xwl_screen->screen->SetWindowPixmap)(window, pixmap);
xwl_eglstream->SetWindowPixmap = xwl_screen->screen->SetWindowPixmap;
xwl_screen->screen->SetWindowPixmap = xwl_eglstream_set_window_pixmap;
}
static const char *
xwl_eglstream_get_error_str(EGLint error)
{
switch (error) {
case EGL_BAD_PARAMETER:
return "EGL_BAD_PARAMETER";
case EGL_BAD_ATTRIBUTE:
return "EGL_BAD_ATTRIBUTE";
case EGL_BAD_MATCH:
return "EGL_BAD_MATCH";
case EGL_BAD_ACCESS:
return "EGL_BAD_ACCESS";
case EGL_BAD_STATE_KHR:
return "EGL_BAD_STATE_KHR";
case EGL_BAD_STREAM_KHR:
return "EGL_BAD_STREAM_KHR";
case EGL_BAD_DISPLAY:
return "EGL_BAD_DISPLAY";
case EGL_NOT_INITIALIZED:
return "EGL_NOT_INITIALIZED";
default:
return "Unknown error";
}
}
static const char *
xwl_eglstream_get_stream_state_str(EGLint state)
{
switch (state) {
case EGL_STREAM_STATE_CREATED_KHR:
return "EGL_STREAM_STATE_CREATED_KHR";
case EGL_STREAM_STATE_CONNECTING_KHR:
return "EGL_STREAM_STATE_CONNECTING_KHR";
case EGL_STREAM_STATE_EMPTY_KHR:
return "EGL_STREAM_STATE_EMPTY_KHR";
case EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR:
return "EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR";
case EGL_STREAM_STATE_OLD_FRAME_AVAILABLE_KHR:
return "EGL_STREAM_STATE_OLD_FRAME_AVAILABLE_KHR";
case EGL_STREAM_STATE_DISCONNECTED_KHR:
return "EGL_STREAM_STATE_DISCONNECTED_KHR";
default:
return "Unknown state";
}
}
static EGLint
xwl_eglstream_get_state(EGLDisplay egl_display, EGLStreamKHR egl_stream)
{
EGLint state;
eglQueryStreamKHR(egl_display, egl_stream, EGL_STREAM_STATE_KHR, &state);
if (!eglQueryStreamKHR(egl_display, egl_stream,
EGL_STREAM_STATE_KHR, &state)) {
EGLint state_error = eglGetError();
ErrorF("eglstream: Failed to query state - error 0x%X: %s\n",
state_error, xwl_eglstream_get_error_str(state_error));
return EGL_FALSE;
}
return state;
}
static void
xwl_eglstream_print_error(EGLDisplay egl_display,
EGLStreamKHR egl_stream, EGLint error)
{
ErrorF("eglstream: error 0x%X: %s\n", error,
xwl_eglstream_get_error_str(error));
if (error == EGL_BAD_STATE_KHR) {
EGLint state = xwl_eglstream_get_state(egl_display, egl_stream);
ErrorF("eglstream: stream state 0x%X: %s\n", state,
xwl_eglstream_get_stream_state_str(state));
}
}
static void
xwl_eglstream_consumer_ready_callback(void *data,
struct wl_callback *callback,
uint32_t time)
{
PixmapPtr pixmap = data;
struct xwl_pixmap *xwl_pixmap = xwl_pixmap_get(pixmap);
struct xwl_screen *xwl_screen = xwl_pixmap->xwl_screen;
struct xwl_eglstream_private *xwl_eglstream =
xwl_eglstream_get(xwl_screen);
wl_callback_destroy(callback);
xwl_pixmap->pending_cb = NULL;
xwl_glamor_egl_make_current(xwl_screen);
xwl_pixmap->surface = eglCreateStreamProducerSurfaceKHR(
xwl_screen->egl_display, xwl_eglstream->config,
xwl_pixmap->stream, (int[]) {
EGL_WIDTH, pixmap->drawable.width,
EGL_HEIGHT, pixmap->drawable.height,
EGL_NONE
});
if (xwl_pixmap->surface == EGL_NO_SURFACE) {
ErrorF("eglstream: Failed to create EGLSurface for pixmap\n");
xwl_eglstream_print_error(xwl_screen->egl_display,
xwl_pixmap->stream, eglGetError());
} else {
DebugF("eglstream: completes eglstream for pixmap %p, congrats!\n",
pixmap);
}
}
static const struct wl_callback_listener consumer_ready_listener = {
xwl_eglstream_consumer_ready_callback
};
static void
xwl_eglstream_buffer_release_callback(void *data)
{
PixmapPtr pixmap = data;
struct xwl_pixmap *xwl_pixmap = xwl_pixmap_get(pixmap);
assert(xwl_pixmap);
if (xwl_pixmap->wait_for_buffer_release) {
xwl_pixmap->wait_for_buffer_release = FALSE;
/* drop the reference we took in the ready callback, freeing if necessary */
dixDestroyPixmap(pixmap, 0);
}
}
static const struct wl_buffer_listener xwl_eglstream_buffer_release_listener = {
xwl_pixmap_buffer_release_cb,
};
static void
xwl_eglstream_create_pixmap_and_stream(struct xwl_screen *xwl_screen,
WindowPtr window, PixmapPtr pixmap)
{
struct xwl_eglstream_private *xwl_eglstream =
xwl_eglstream_get(xwl_screen);
struct xwl_pixmap *xwl_pixmap;
struct xwl_window *xwl_window = xwl_window_from_window(window);
struct wl_array stream_attribs;
int stream_fd = -1;
xwl_pixmap = calloc(1, sizeof(*xwl_pixmap));
if (!xwl_pixmap)
FatalError("Not enough memory to create pixmap\n");
xwl_pixmap_set_private(pixmap, xwl_pixmap);
xwl_pixmap->type = XWL_PIXMAP_EGLSTREAM;
xwl_pixmap->image = EGL_NO_IMAGE;
xwl_glamor_egl_make_current(xwl_screen);
xwl_pixmap->wait_for_buffer_release = FALSE;
xwl_pixmap->xwl_screen = xwl_screen;
xwl_pixmap->surface = EGL_NO_SURFACE;
xwl_pixmap->stream = eglCreateStreamKHR(xwl_screen->egl_display, NULL);
if (xwl_pixmap->stream == EGL_NO_STREAM_KHR) {
ErrorF("eglstream: Couldn't create EGL stream.\n");
goto fail;
}
stream_fd = eglGetStreamFileDescriptorKHR(xwl_screen->egl_display,
xwl_pixmap->stream);
if (stream_fd == EGL_NO_FILE_DESCRIPTOR_KHR) {
ErrorF("eglstream: Couldn't get EGL stream file descriptor.\n");
goto fail;
}
wl_array_init(&stream_attribs);
xwl_pixmap->buffer =
wl_eglstream_display_create_stream(xwl_eglstream->display,
pixmap->drawable.width,
pixmap->drawable.height,
stream_fd,
WL_EGLSTREAM_HANDLE_TYPE_FD,
&stream_attribs);
if (!xwl_pixmap->buffer) {
ErrorF("eglstream: Failed to create buffer\n");
goto fail;
}
wl_buffer_add_listener(xwl_pixmap->buffer,
&xwl_eglstream_buffer_release_listener,
pixmap);
xwl_pixmap_set_buffer_release_cb(pixmap,
xwl_eglstream_buffer_release_callback,
pixmap);
wl_eglstream_controller_attach_eglstream_consumer(
xwl_eglstream->controller, xwl_window->surface, xwl_pixmap->buffer);
xwl_pixmap->pending_cb = wl_display_sync(xwl_screen->display);
wl_callback_add_listener(xwl_pixmap->pending_cb, &consumer_ready_listener,
pixmap);
fail:
if (stream_fd >= 0)
close(stream_fd);
}
static Bool
xwl_glamor_eglstream_allow_commits(struct xwl_window *xwl_window)
{
struct xwl_screen *xwl_screen = xwl_window->xwl_screen;
PixmapPtr pixmap =
(*xwl_screen->screen->GetWindowPixmap)(xwl_window->window);
struct xwl_pixmap *xwl_pixmap = xwl_pixmap_get(pixmap);
if (xwl_pixmap) {
if (xwl_pixmap->pending_cb) {
/* Wait for the compositor to finish connecting the consumer for
* this eglstream */
return FALSE;
}
if (xwl_pixmap->surface != EGL_NO_SURFACE ||
xwl_pixmap->type == XWL_PIXMAP_DMA_BUF) {
return TRUE;
}
/* The pending stream got removed, we have a xwl_pixmap and
* yet we do not have a surface.
* So something went wrong with the surface creation, retry.
*/
xwl_eglstream_destroy_pixmap_stream(xwl_pixmap);
}
/* Glamor pixmap has no backing stream yet; begin making one and disallow
* commits until then
*/
xwl_eglstream_create_pixmap_and_stream(xwl_screen, xwl_window->window,
pixmap);
return FALSE;
}
static Bool
xwl_glamor_eglstream_post_damage(struct xwl_window *xwl_window,
PixmapPtr pixmap, RegionPtr region)
{
struct xwl_screen *xwl_screen = xwl_window->xwl_screen;
struct xwl_eglstream_private *xwl_eglstream =
xwl_eglstream_get(xwl_screen);
struct xwl_pixmap *xwl_pixmap = xwl_pixmap_get(pixmap);
BoxPtr box = RegionExtents(region);
EGLint egl_damage[] = {
box->x1, box->y1,
box->x2 - box->x1, box->y2 - box->y1
};
GLint saved_vao;
int status;
if (xwl_pixmap->type != XWL_PIXMAP_EGLSTREAM)
/* This can happen if a client does X11 rendering on a
* flipping OpenGL or Vulkan window. In that case, we don't
* need to do the copy below.
*/
return TRUE;
/* Unbind the framebuffer BEFORE binding the EGLSurface, otherwise we
* won't actually draw to it
*/
xwl_glamor_egl_make_current(xwl_screen);
glamor_set_alu(xwl_screen->screen, GXcopy);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (eglGetCurrentSurface(EGL_READ) != xwl_pixmap->surface ||
eglGetCurrentSurface(EGL_DRAW) != xwl_pixmap->surface)
eglMakeCurrent(xwl_screen->egl_display,
xwl_pixmap->surface, xwl_pixmap->surface,
xwl_screen->egl_context);
/* Save current GL state */
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &saved_vao);
/* Setup our GL state */
glUseProgram(xwl_eglstream->blit_prog);
glViewport(0, 0, pixmap->drawable.width, pixmap->drawable.height);
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(xwl_eglstream->blit_vao);
glBindTexture(GL_TEXTURE_2D, glamor_get_pixmap_texture(pixmap));
glUniform1i(xwl_eglstream->blit_is_rgba_pos,
pixmap->drawable.depth >= 32);
status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
ErrorF("eglstream: Framebuffer incomplete 0x%X, not posting damage\n", status);
status = FALSE;
goto out;
}
/* Blit rendered image into EGLStream surface */
glDrawBuffer(GL_BACK);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
if (xwl_eglstream->have_egl_damage)
status = eglSwapBuffersWithDamageKHR(xwl_screen->egl_display,
xwl_pixmap->surface,
egl_damage, 1);
else
status = eglSwapBuffers(xwl_screen->egl_display,
xwl_pixmap->surface);
if (!status) {
ErrorF("eglstream: buffer swap failed, not posting damage\n");
goto out;
}
#ifdef EGL_NV_stream_flush
if (xwl_eglstream->have_egl_stream_flush)
/* block until stream state is updated on the compositor's side */
eglStreamFlushNV(xwl_screen->egl_display,
xwl_pixmap->stream);
#endif
if (!xwl_pixmap->wait_for_buffer_release) {
/* hang onto the pixmap until the compositor has released it */
pixmap->refcnt++;
xwl_pixmap->wait_for_buffer_release = TRUE;
}
out:
/* Restore previous state */
glBindVertexArray(saved_vao);
glBindTexture(GL_TEXTURE_2D, 0);
return status;
}
static Bool
xwl_glamor_eglstream_check_flip(PixmapPtr pixmap)
{
return xwl_pixmap_get(pixmap)->type == XWL_PIXMAP_DMA_BUF;
}
static void
xwl_eglstream_display_handle_caps(void *data,
struct wl_eglstream_display *disp,
int32_t caps)
{
xwl_eglstream_get(data)->display_caps = caps;
}
static void
xwl_eglstream_display_handle_swapinterval_override(void *data,
struct wl_eglstream_display *disp,
int32_t swapinterval,
struct wl_buffer *stream)
{
}
const struct wl_eglstream_display_listener eglstream_display_listener = {
.caps = xwl_eglstream_display_handle_caps,
.swapinterval_override = xwl_eglstream_display_handle_swapinterval_override,
};
static Bool
xwl_glamor_eglstream_init_wl_registry(struct xwl_screen *xwl_screen,
struct wl_registry *wl_registry,
uint32_t id, const char *name,
uint32_t version)
{
struct xwl_eglstream_private *xwl_eglstream =
xwl_eglstream_get(xwl_screen);
if (strcmp(name, "wl_eglstream_display") == 0) {
xwl_eglstream->display = wl_registry_bind(
wl_registry, id, &wl_eglstream_display_interface, version);
wl_eglstream_display_add_listener(xwl_eglstream->display,
&eglstream_display_listener,
xwl_screen);
return TRUE;
} else if (strcmp(name, "wl_eglstream_controller") == 0) {
xwl_eglstream->controller = wl_registry_bind(
wl_registry, id, &wl_eglstream_controller_interface, version);
return TRUE;
} else if (strcmp(name, "zwp_linux_dmabuf_v1") == 0) {
xwl_screen_set_dmabuf_interface(xwl_screen, id, version);
return TRUE;
}
/* no match */
return FALSE;
}
static Bool
xwl_glamor_eglstream_has_wl_interfaces(struct xwl_screen *xwl_screen)
{
struct xwl_eglstream_private *xwl_eglstream =
xwl_eglstream_get(xwl_screen);
if (xwl_eglstream->display == NULL) {
ErrorF("glamor: 'wl_eglstream_display' not supported\n");
return FALSE;
}
if (xwl_eglstream->controller == NULL) {
ErrorF("glamor: 'wl_eglstream_controller' not supported\n");
return FALSE;
}
return TRUE;
}
static inline void
xwl_eglstream_init_shaders(struct xwl_screen *xwl_screen)
{
struct xwl_eglstream_private *xwl_eglstream =
xwl_eglstream_get(xwl_screen);
GLint fs, vs, attrib;
GLuint vbo;
const char *blit_vs_src =
"attribute vec2 texcoord;\n"
"attribute vec2 position;\n"
"varying vec2 t;\n"
"void main() {\n"
" t = texcoord;\n"
" gl_Position = vec4(position, 0, 1);\n"
"}";
const char *blit_fs_src =
"varying vec2 t;\n"
"uniform sampler2D s;\n"
"uniform bool is_rgba;\n"
"void main() {\n"
" if (is_rgba)\n"
" gl_FragColor = texture2D(s, t);\n"
" else\n"
" gl_FragColor = vec4(texture2D(s, t).rgb, 1.0);\n"
"}";
static const float position[] = {
/* position */
-1, -1,
1, -1,
1, 1,
-1, 1,
/* texcoord */
0, 1,
1, 1,
1, 0,
0, 0,
};
vs = xwl_eglstream_compile_glsl_prog(GL_VERTEX_SHADER, blit_vs_src);
fs = xwl_eglstream_compile_glsl_prog(GL_FRAGMENT_SHADER, blit_fs_src);
xwl_eglstream->blit_prog = xwl_eglstream_build_glsl_prog(vs, fs);
glDeleteShader(vs);
glDeleteShader(fs);
/* Create the blitter's vao */
glGenVertexArrays(1, &xwl_eglstream->blit_vao);
glBindVertexArray(xwl_eglstream->blit_vao);
/* Set the data for both position and texcoord in the vbo */
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);
xwl_eglstream->blit_vbo = vbo;
/* Define each shader attribute's data location in our vbo */
attrib = glGetAttribLocation(xwl_eglstream->blit_prog, "position");
glVertexAttribPointer(attrib, 2, GL_FLOAT, TRUE, 0, NULL);
glEnableVertexAttribArray(attrib);
attrib = glGetAttribLocation(xwl_eglstream->blit_prog, "texcoord");
glVertexAttribPointer(attrib, 2, GL_FLOAT, TRUE, 0,
(void*)(sizeof(float) * 8));
glEnableVertexAttribArray(attrib);
/* Save the location of uniforms we'll set later */
xwl_eglstream->blit_is_rgba_pos =
glGetUniformLocation(xwl_eglstream->blit_prog, "is_rgba");
}
static int
xwl_dri3_open_client(ClientPtr client,
ScreenPtr screen,
RRProviderPtr provider,
int *pfd)
{
/* Not supported with this backend. */
return BadImplementation;
}
static PixmapPtr
xwl_dri3_pixmap_from_fds(ScreenPtr screen,
CARD8 num_fds, const int *fds,
CARD16 width, CARD16 height,
const CARD32 *strides, const CARD32 *offsets,
CARD8 depth, CARD8 bpp,
uint64_t modifier)
{
PixmapPtr pixmap;
struct xwl_screen *xwl_screen = xwl_screen_get(screen);
struct xwl_pixmap *xwl_pixmap;
unsigned int texture;
EGLint image_attribs[48];
uint32_t mod_hi = modifier >> 32, mod_lo = modifier & 0xffffffff, format;
int attrib = 0, i;
struct zwp_linux_buffer_params_v1 *params;
format = wl_drm_format_for_depth(depth);
if (!xwl_glamor_is_modifier_supported(xwl_screen, format, modifier)) {
ErrorF("glamor: unsupported format modifier\n");
return NULL;
}
xwl_pixmap = calloc(1, sizeof (*xwl_pixmap));
if (!xwl_pixmap)
return NULL;
xwl_pixmap->type = XWL_PIXMAP_DMA_BUF;
xwl_pixmap->xwl_screen = xwl_screen;
xwl_pixmap->buffer = NULL;
xwl_pixmap->stream = EGL_NO_STREAM_KHR;
xwl_pixmap->surface = EGL_NO_SURFACE;
params = zwp_linux_dmabuf_v1_create_params(xwl_screen->dmabuf);
for (i = 0; i < num_fds; i++) {
zwp_linux_buffer_params_v1_add(params, fds[i], i,
offsets[i], strides[i],
mod_hi, mod_lo);
}
xwl_pixmap->buffer =
zwp_linux_buffer_params_v1_create_immed(params, width, height,
format, 0);
zwp_linux_buffer_params_v1_destroy(params);
image_attribs[attrib++] = EGL_WIDTH;
image_attribs[attrib++] = width;
image_attribs[attrib++] = EGL_HEIGHT;
image_attribs[attrib++] = height;
image_attribs[attrib++] = EGL_LINUX_DRM_FOURCC_EXT;
image_attribs[attrib++] = drm_format_for_depth(depth, bpp);
if (num_fds > 0) {
image_attribs[attrib++] = EGL_DMA_BUF_PLANE0_FD_EXT;
image_attribs[attrib++] = fds[0];
image_attribs[attrib++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
image_attribs[attrib++] = offsets[0];
image_attribs[attrib++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
image_attribs[attrib++] = strides[0];
image_attribs[attrib++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
image_attribs[attrib++] = mod_hi;
image_attribs[attrib++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
image_attribs[attrib++] = mod_lo;
}
if (num_fds > 1) {
image_attribs[attrib++] = EGL_DMA_BUF_PLANE1_FD_EXT;
image_attribs[attrib++] = fds[1];
image_attribs[attrib++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT;
image_attribs[attrib++] = offsets[1];
image_attribs[attrib++] = EGL_DMA_BUF_PLANE1_PITCH_EXT;
image_attribs[attrib++] = strides[1];
image_attribs[attrib++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT;
image_attribs[attrib++] = mod_hi;
image_attribs[attrib++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT;
image_attribs[attrib++] = mod_lo;
}
if (num_fds > 2) {
image_attribs[attrib++] = EGL_DMA_BUF_PLANE2_FD_EXT;
image_attribs[attrib++] = fds[2];
image_attribs[attrib++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT;
image_attribs[attrib++] = offsets[2];
image_attribs[attrib++] = EGL_DMA_BUF_PLANE2_PITCH_EXT;
image_attribs[attrib++] = strides[2];
image_attribs[attrib++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT;
image_attribs[attrib++] = mod_hi;
image_attribs[attrib++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT;
image_attribs[attrib++] = mod_lo;
}
if (num_fds > 3) {
image_attribs[attrib++] = EGL_DMA_BUF_PLANE3_FD_EXT;
image_attribs[attrib++] = fds[3];
image_attribs[attrib++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT;
image_attribs[attrib++] = offsets[3];
image_attribs[attrib++] = EGL_DMA_BUF_PLANE3_PITCH_EXT;
image_attribs[attrib++] = strides[3];
image_attribs[attrib++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT;
image_attribs[attrib++] = mod_hi;
image_attribs[attrib++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT;
image_attribs[attrib++] = mod_lo;
}
image_attribs[attrib++] = EGL_NONE;
xwl_glamor_egl_make_current(xwl_screen);
/* eglCreateImageKHR will close fds */
xwl_pixmap->image = eglCreateImageKHR(xwl_screen->egl_display,
EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT,
NULL, image_attribs);
if (xwl_pixmap->image == EGL_NO_IMAGE_KHR) {
ErrorF("eglCreateImageKHR failed!\n");
if (xwl_pixmap->buffer)
wl_buffer_destroy(xwl_pixmap->buffer);
free(xwl_pixmap);
return NULL;
}
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, xwl_pixmap->image);
glBindTexture(GL_TEXTURE_2D, 0);
pixmap = glamor_create_pixmap(screen, width, height, depth,
GLAMOR_CREATE_PIXMAP_NO_TEXTURE);
glamor_set_pixmap_texture(pixmap, texture);
glamor_set_pixmap_type(pixmap, GLAMOR_TEXTURE_DRM);
wl_buffer_add_listener(xwl_pixmap->buffer,
&xwl_eglstream_buffer_release_listener,
pixmap);
xwl_pixmap_set_private(pixmap, xwl_pixmap);
return pixmap;
}
static const dri3_screen_info_rec xwl_dri3_info = {
.version = 2,
.open = NULL,
.pixmap_from_fds = xwl_dri3_pixmap_from_fds,
.fds_from_pixmap = NULL,
.open_client = xwl_dri3_open_client,
.get_formats = xwl_glamor_get_formats,
.get_modifiers = xwl_glamor_get_modifiers,
.get_drawable_modifiers = glamor_get_drawable_modifiers,
};
static Bool
xwl_glamor_eglstream_init_egl(struct xwl_screen *xwl_screen)
{
struct xwl_eglstream_private *xwl_eglstream =
xwl_eglstream_get(xwl_screen);
EGLConfig config;
const EGLint attrib_list[] = {
EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR,
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
EGL_CONTEXT_MAJOR_VERSION_KHR,
GLAMOR_GL_CORE_VER_MAJOR,
EGL_CONTEXT_MINOR_VERSION_KHR,
GLAMOR_GL_CORE_VER_MINOR,
EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG,
EGL_NONE
};
const EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_STREAM_BIT_KHR,
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_NONE,
};
int n;
xwl_screen->egl_display = glamor_egl_get_display(
EGL_PLATFORM_DEVICE_EXT, xwl_eglstream->egl_device);
if (!xwl_screen->egl_display)
goto error;
if (!eglInitialize(xwl_screen->egl_display, NULL, NULL)) {
xwl_screen->egl_display = NULL;
goto error;
}
if (!epoxy_has_egl_extension(xwl_screen->egl_display,
"EGL_IMG_context_priority")) {
ErrorF("EGL_IMG_context_priority not available\n");
goto error;
}
eglChooseConfig(xwl_screen->egl_display, config_attribs, &config, 1, &n);
if (!n) {
ErrorF("No acceptable EGL configs found\n");
goto error;
}
xwl_eglstream->config = config;
#if 0
xwl_screen->formats =
XWL_FORMAT_RGB565 | XWL_FORMAT_XRGB8888 | XWL_FORMAT_ARGB8888;
#endif
eglBindAPI(EGL_OPENGL_API);
xwl_screen->egl_context = eglCreateContext(
xwl_screen->egl_display, config, EGL_NO_CONTEXT, attrib_list);
if (xwl_screen->egl_context == EGL_NO_CONTEXT) {
ErrorF("Failed to create main EGL context: 0x%x\n", eglGetError());
goto error;
}
if (!eglMakeCurrent(xwl_screen->egl_display,
EGL_NO_SURFACE, EGL_NO_SURFACE,
xwl_screen->egl_context)) {
ErrorF("Failed to make EGL context current\n");
goto error;
}
xwl_eglstream->have_egl_damage =
epoxy_has_egl_extension(xwl_screen->egl_display,
"EGL_KHR_swap_buffers_with_damage");
if (!xwl_eglstream->have_egl_damage)
ErrorF("Driver lacks EGL_KHR_swap_buffers_with_damage, performance "
"will be affected\n");
#ifdef EGL_NV_stream_flush
xwl_eglstream->have_egl_stream_flush =
epoxy_has_egl_extension(xwl_screen->egl_display,
"EGL_NV_stream_flush");
#else
xwl_eglstream->have_egl_stream_flush = FALSE;
#endif /* EGL_NV_stream_flush */
if (!xwl_eglstream->have_egl_stream_flush)
ErrorF("EGL_NV_stream_flush not available, "
"this may cause visible corruption.\n");
xwl_eglstream_init_shaders(xwl_screen);
if (epoxy_has_gl_extension("GL_OES_EGL_image")) {
if (dri3_screen_init(xwl_screen->screen, &xwl_dri3_info))
xwl_screen->glvnd_vendor = "nvidia";
else
ErrorF("DRI3 initialization failed. Performance will be affected.\n");
} else {
ErrorF("Driver lacks GL_OES_EGL_image, performance will be affected.\n");
}
return TRUE;
error:
xwl_eglstream_cleanup(xwl_screen);
return FALSE;
}
static Bool
xwl_glamor_eglstream_init_screen(struct xwl_screen *xwl_screen)
{
struct xwl_eglstream_private *xwl_eglstream =
xwl_eglstream_get(xwl_screen);
ScreenPtr screen = xwl_screen->screen;
/* We can just let glamor handle CreatePixmap */
screen->DestroyPixmap = xwl_glamor_eglstream_destroy_pixmap;
xwl_eglstream->SetWindowPixmap = screen->SetWindowPixmap;
screen->SetWindowPixmap = xwl_eglstream_set_window_pixmap;
return TRUE;
}
static EGLDeviceEXT
xwl_eglstream_get_device(struct xwl_screen *xwl_screen)
{
void **devices = NULL;
const char *exts[] = {
"EGL_KHR_stream",
"EGL_KHR_stream_producer_eglsurface",
};
int num_devices, i;
EGLDeviceEXT device = EGL_NO_DEVICE_EXT;
/* No device specified by the user, so find one ourselves */
devices = xwl_glamor_egl_get_devices(&num_devices);
if (!devices)
goto out;
for (i = 0; i < num_devices; i++) {
if (xwl_glamor_egl_device_has_egl_extensions(devices[i], exts,
ARRAY_SIZE(exts))) {
device = devices[i];
break;
}
}
free(devices);
out:
if (!device)
ErrorF("glamor: No eglstream capable devices found\n");
return device;
}
void
xwl_glamor_init_eglstream(struct xwl_screen *xwl_screen)
{
struct xwl_eglstream_private *xwl_eglstream;
EGLDeviceEXT egl_device;
xwl_screen->eglstream_backend.is_available = FALSE;
egl_device = xwl_eglstream_get_device(xwl_screen);
if (egl_device == EGL_NO_DEVICE_EXT)
return;
if (!dixRegisterPrivateKey(&xwl_eglstream_private_key, PRIVATE_SCREEN, 0))
return;
xwl_eglstream = calloc(1, sizeof(*xwl_eglstream));
if (!xwl_eglstream) {
ErrorF("Failed to allocate memory required to init EGLStream support\n");
return;
}
dixSetPrivate(&xwl_screen->screen->devPrivates,
&xwl_eglstream_private_key, xwl_eglstream);
xwl_eglstream->egl_device = egl_device;
xwl_screen->eglstream_backend.init_egl = xwl_glamor_eglstream_init_egl;
xwl_screen->eglstream_backend.init_wl_registry = xwl_glamor_eglstream_init_wl_registry;
xwl_screen->eglstream_backend.has_wl_interfaces = xwl_glamor_eglstream_has_wl_interfaces;
xwl_screen->eglstream_backend.init_screen = xwl_glamor_eglstream_init_screen;
xwl_screen->eglstream_backend.get_wl_buffer_for_pixmap = xwl_glamor_eglstream_get_wl_buffer_for_pixmap;
xwl_screen->eglstream_backend.post_damage = xwl_glamor_eglstream_post_damage;
xwl_screen->eglstream_backend.allow_commits = xwl_glamor_eglstream_allow_commits;
xwl_screen->eglstream_backend.check_flip = xwl_glamor_eglstream_check_flip;
xwl_screen->eglstream_backend.is_available = TRUE;
xwl_screen->eglstream_backend.backend_flags = XWL_EGL_BACKEND_NO_FLAG;
}