2019-06-08 10:54:50 +00:00
|
|
|
From d3fece21813f0a0e00e751703a8998b40f8dad73 Mon Sep 17 00:00:00 2001
|
|
|
|
From: Danct12 <danct12@disroot.org>
|
|
|
|
Date: Sat, 8 Jun 2019 16:56:10 +0700
|
|
|
|
Subject: [PATCH] [PATCH v2] Implement X11 EGL platform based on wayland code.
|
|
|
|
|
|
|
|
Original patch by NeKit <nekit1000@gmail.com>
|
|
|
|
Patched up for compatibility with libhybris gralloc support by Danct12 <danct12@disroot.org>
|
2018-04-10 21:13:42 +00:00
|
|
|
|
2018-10-02 05:09:28 +00:00
|
|
|
* Allow window system to hook eglGetConfigAttrib (needed for X11 EGL_NATIVE_VISUAL_ID)
|
2018-04-10 21:13:42 +00:00
|
|
|
|
2018-10-02 05:09:28 +00:00
|
|
|
* Use custom DRIHYBRIS Xorg extension for buffer sharing to Xorg/glamor when possible
|
|
|
|
Gives huge speedup over XShmPutImage, but requires patched Glamor and DDX driver to
|
|
|
|
utilize it
|
2018-04-10 21:13:42 +00:00
|
|
|
|
2018-10-02 05:09:28 +00:00
|
|
|
* Check for window resizes using Present extension when possible
|
2018-04-10 21:13:42 +00:00
|
|
|
|
2018-10-02 05:09:28 +00:00
|
|
|
* x11nativewindow: use same depth as target window for pixmap.
|
|
|
|
Call xcb_present_pixmap instead of xcb_copy_area to present pixmap
|
2019-06-08 10:54:50 +00:00
|
|
|
|
|
|
|
Signed-off-by: Danct12 <danct12@disroot.org>
|
2018-04-10 21:13:42 +00:00
|
|
|
---
|
2019-06-08 10:54:50 +00:00
|
|
|
hybris/configure.ac | 1 +
|
|
|
|
hybris/egl/egl.c | 13 +-
|
|
|
|
hybris/egl/platforms/Makefile.am | 2 +-
|
|
|
|
hybris/egl/platforms/x11/Makefile.am | 43 ++
|
|
|
|
hybris/egl/platforms/x11/eglplatform_x11.cpp | 229 ++++++
|
|
|
|
hybris/egl/platforms/x11/x11_window.cpp | 739 +++++++++++++++++++
|
|
|
|
hybris/egl/platforms/x11/x11_window.h | 195 +++++
|
|
|
|
hybris/egl/platforms/x11/xcb_drihybris.c | 167 +++++
|
|
|
|
hybris/egl/platforms/x11/xcb_drihybris.h | 122 +++
|
|
|
|
hybris/egl/ws.c | 9 +
|
|
|
|
hybris/egl/ws.h | 2 +
|
|
|
|
11 files changed, 1520 insertions(+), 2 deletions(-)
|
2018-04-10 21:13:42 +00:00
|
|
|
create mode 100644 hybris/egl/platforms/x11/Makefile.am
|
|
|
|
create mode 100644 hybris/egl/platforms/x11/eglplatform_x11.cpp
|
|
|
|
create mode 100644 hybris/egl/platforms/x11/x11_window.cpp
|
|
|
|
create mode 100644 hybris/egl/platforms/x11/x11_window.h
|
|
|
|
create mode 100644 hybris/egl/platforms/x11/xcb_drihybris.c
|
|
|
|
create mode 100644 hybris/egl/platforms/x11/xcb_drihybris.h
|
|
|
|
|
|
|
|
diff --git a/hybris/configure.ac b/hybris/configure.ac
|
2019-06-08 10:54:50 +00:00
|
|
|
index b3ddc50..7d20c34 100644
|
2018-04-10 21:13:42 +00:00
|
|
|
--- a/hybris/configure.ac
|
|
|
|
+++ b/hybris/configure.ac
|
2019-06-08 10:54:50 +00:00
|
|
|
@@ -264,6 +264,7 @@ AC_CONFIG_FILES([
|
2018-04-10 21:13:42 +00:00
|
|
|
egl/platforms/null/Makefile
|
|
|
|
egl/platforms/fbdev/Makefile
|
|
|
|
egl/platforms/wayland/Makefile
|
|
|
|
+ egl/platforms/x11/Makefile
|
|
|
|
egl/platforms/hwcomposer/Makefile
|
|
|
|
egl/platforms/hwcomposer/hwcomposer-egl.pc
|
|
|
|
glesv1/glesv1_cm.pc
|
|
|
|
diff --git a/hybris/egl/egl.c b/hybris/egl/egl.c
|
|
|
|
index f6dcd09..4c02ebf 100644
|
|
|
|
--- a/hybris/egl/egl.c
|
|
|
|
+++ b/hybris/egl/egl.c
|
|
|
|
@@ -228,7 +228,6 @@ const char * eglQueryString(EGLDisplay dpy, EGLint name)
|
|
|
|
|
|
|
|
HYBRIS_IMPLEMENT_FUNCTION4(egl, EGLBoolean, eglGetConfigs, EGLDisplay, EGLConfig *, EGLint, EGLint *);
|
|
|
|
HYBRIS_IMPLEMENT_FUNCTION5(egl, EGLBoolean, eglChooseConfig, EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *);
|
|
|
|
-HYBRIS_IMPLEMENT_FUNCTION4(egl, EGLBoolean, eglGetConfigAttrib, EGLDisplay, EGLConfig, EGLint, EGLint *);
|
|
|
|
|
|
|
|
EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config,
|
|
|
|
EGLNativeWindowType win,
|
|
|
|
@@ -461,4 +460,16 @@ EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR image)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
+EGLBoolean eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value)
|
|
|
|
+{
|
|
|
|
+ HYBRIS_DLSYSM(egl, &_eglGetConfigAttrib, "eglGetConfigAttrib");
|
|
|
|
+ struct _EGLDisplay *display = hybris_egl_display_get_mapping(dpy);
|
|
|
|
+
|
|
|
|
+ EGLBoolean ret = ws_eglGetConfigAttrib(display, config, attribute, value);
|
|
|
|
+ if (ret == EGL_FALSE) {
|
|
|
|
+ return (*_eglGetConfigAttrib)(dpy, config, attribute, value);
|
|
|
|
+ }
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
// vim:ts=4:sw=4:noexpandtab
|
|
|
|
diff --git a/hybris/egl/platforms/Makefile.am b/hybris/egl/platforms/Makefile.am
|
|
|
|
index 4126752..c52de15 100644
|
|
|
|
--- a/hybris/egl/platforms/Makefile.am
|
|
|
|
+++ b/hybris/egl/platforms/Makefile.am
|
|
|
|
@@ -11,4 +11,4 @@ if WANT_WAYLAND
|
|
|
|
SUBDIRS += wayland
|
|
|
|
endif
|
|
|
|
|
|
|
|
-
|
|
|
|
+SUBDIRS += x11
|
|
|
|
diff --git a/hybris/egl/platforms/x11/Makefile.am b/hybris/egl/platforms/x11/Makefile.am
|
|
|
|
new file mode 100644
|
2019-06-08 10:54:50 +00:00
|
|
|
index 0000000..4ab00f2
|
2018-04-10 21:13:42 +00:00
|
|
|
--- /dev/null
|
|
|
|
+++ b/hybris/egl/platforms/x11/Makefile.am
|
|
|
|
@@ -0,0 +1,43 @@
|
|
|
|
+pkglib_LTLIBRARIES = eglplatform_x11.la
|
|
|
|
+
|
|
|
|
+eglplatform_x11_la_SOURCES = eglplatform_x11.cpp x11_window.cpp xcb_drihybris.c
|
|
|
|
+eglplatform_x11_la_CXXFLAGS = \
|
|
|
|
+ -I$(top_srcdir)/common \
|
|
|
|
+ -I$(top_srcdir)/include \
|
|
|
|
+ -I$(top_srcdir)/egl \
|
|
|
|
+ -I$(top_srcdir)/egl/platforms/common \
|
|
|
|
+ $(ANDROID_HEADERS_CFLAGS) \
|
|
|
|
+ $(WAYLAND_CLIENT_CFLAGS)
|
|
|
|
+
|
|
|
|
+if WANT_DEBUG
|
|
|
|
+eglplatform_x11_la_CXXFLAGS += -I$(top_srcdir)/common
|
|
|
|
+endif
|
|
|
|
+
|
|
|
|
+if WANT_TRACE
|
|
|
|
+eglplatform_x11_la_CXXFLAGS += -DDEBUG
|
|
|
|
+endif
|
|
|
|
+
|
|
|
|
+if WANT_DEBUG
|
|
|
|
+eglplatform_x11_la_CXXFLAGS += -ggdb -O0
|
|
|
|
+endif
|
|
|
|
+
|
|
|
|
+if !WANT_WL_SERVERSIDE_BUFFERS
|
|
|
|
+eglplatform_x11_la_CXXFLAGS += -DHYBRIS_NO_SERVER_SIDE_BUFFERS
|
|
|
|
+endif
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+eglplatform_x11_la_LDFLAGS = \
|
|
|
|
+ -avoid-version -module -shared -export-dynamic \
|
|
|
|
+ $(top_builddir)/egl/platforms/common/libhybris-eglplatformcommon.la \
|
2019-06-08 10:54:50 +00:00
|
|
|
+ $(top_builddir)/gralloc/libgralloc.la \
|
2018-04-10 21:13:42 +00:00
|
|
|
+ -lXext -lxcb -lX11-xcb -lxcb-present
|
|
|
|
+
|
|
|
|
+if HAS_ANDROID_4_2_0
|
|
|
|
+eglplatform_x11_la_LDFLAGS += $(top_builddir)/libsync/libsync.la
|
|
|
|
+endif
|
|
|
|
+
|
|
|
|
+if HAS_ANDROID_5_0_0
|
|
|
|
+eglplatform_x11_la_LDFLAGS += $(top_builddir)/libsync/libsync.la
|
|
|
|
+endif
|
|
|
|
+
|
|
|
|
diff --git a/hybris/egl/platforms/x11/eglplatform_x11.cpp b/hybris/egl/platforms/x11/eglplatform_x11.cpp
|
|
|
|
new file mode 100644
|
2019-06-08 10:54:50 +00:00
|
|
|
index 0000000..3a1ac4f
|
2018-04-10 21:13:42 +00:00
|
|
|
--- /dev/null
|
|
|
|
+++ b/hybris/egl/platforms/x11/eglplatform_x11.cpp
|
2019-06-08 10:54:50 +00:00
|
|
|
@@ -0,0 +1,229 @@
|
2018-04-10 21:13:42 +00:00
|
|
|
+/****************************************************************************************
|
|
|
|
+**
|
|
|
|
+** Copyright (C) 2013 Jolla Ltd.
|
|
|
|
+** Contact: Carsten Munk <carsten.munk@jollamobile.com>
|
|
|
|
+** All rights reserved.
|
|
|
|
+**
|
|
|
|
+** This file is part of X11 enablement for libhybris
|
|
|
|
+**
|
|
|
|
+** You may use this file under the terms of the GNU Lesser General
|
|
|
|
+** Public License version 2.1 as published by the Free Software Foundation
|
|
|
|
+** and appearing in the file license.lgpl included in the packaging
|
|
|
|
+** of this file.
|
|
|
|
+**
|
|
|
|
+** This library is free software; you can redistribute it and/or
|
|
|
|
+** modify it under the terms of the GNU Lesser General Public
|
|
|
|
+** License version 2.1 as published by the Free Software Foundation
|
|
|
|
+** and appearing in the file license.lgpl included in the packaging
|
|
|
|
+** of this file.
|
|
|
|
+**
|
|
|
|
+** This library 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
|
|
|
|
+** Lesser General Public License for more details.
|
|
|
|
+**
|
|
|
|
+****************************************************************************************/
|
|
|
|
+
|
|
|
|
+#include <android-config.h>
|
|
|
|
+#include <ws.h>
|
|
|
|
+#include <malloc.h>
|
|
|
|
+#include <assert.h>
|
|
|
|
+#include <fcntl.h>
|
|
|
|
+#include <stdio.h>
|
|
|
|
+#include <stdlib.h>
|
|
|
|
+#include <string.h>
|
|
|
|
+#include <sys/stat.h>
|
|
|
|
+#include <unistd.h>
|
|
|
|
+#include <assert.h>
|
|
|
|
+#include <stdlib.h>
|
|
|
|
+extern "C" {
|
|
|
|
+#include <eglplatformcommon.h>
|
|
|
|
+};
|
|
|
|
+#include <eglhybris.h>
|
|
|
|
+
|
|
|
|
+#include <EGL/eglext.h>
|
|
|
|
+
|
|
|
|
+extern "C" {
|
|
|
|
+#include <wayland-client.h>
|
|
|
|
+#include <wayland-egl.h>
|
|
|
|
+}
|
|
|
|
+
|
2019-06-08 10:54:50 +00:00
|
|
|
+#include <hybris/gralloc/gralloc.h>
|
2018-04-10 21:13:42 +00:00
|
|
|
+#include "x11_window.h"
|
|
|
|
+#include "logging.h"
|
|
|
|
+
|
|
|
|
+#include <X11/Xlib.h>
|
|
|
|
+#include <X11/Xutil.h>
|
|
|
|
+
|
|
|
|
+static const char * (*_eglQueryString)(EGLDisplay dpy, EGLint name) = NULL;
|
|
|
|
+static __eglMustCastToProperFunctionPointerType (*_eglGetProcAddress)(const char *procname) = NULL;
|
|
|
|
+static EGLSyncKHR (*_eglCreateSyncKHR)(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list) = NULL;
|
|
|
|
+static EGLBoolean (*_eglDestroySyncKHR)(EGLDisplay dpy, EGLSyncKHR sync) = NULL;
|
|
|
|
+static EGLint (*_eglClientWaitSyncKHR)(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout) = NULL;
|
|
|
|
+
|
|
|
|
+struct X11Display {
|
|
|
|
+ _EGLDisplay base;
|
|
|
|
+ Display *xl_display;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+extern "C" void x11ws_init_module(struct ws_egl_interface *egl_iface)
|
|
|
|
+{
|
2019-06-08 10:54:50 +00:00
|
|
|
+ hybris_gralloc_initialize(0);
|
2019-06-07 17:06:26 +00:00
|
|
|
+ eglplatformcommon_init(egl_iface);
|
2018-04-10 21:13:42 +00:00
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void _init_egl_funcs(EGLDisplay display)
|
|
|
|
+{
|
|
|
|
+ if (_eglQueryString != NULL)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ _eglQueryString = (const char * (*)(void*, int))
|
|
|
|
+ hybris_android_egl_dlsym("eglQueryString");
|
|
|
|
+ assert(_eglQueryString);
|
|
|
|
+ _eglGetProcAddress = (__eglMustCastToProperFunctionPointerType (*)(const char *))
|
|
|
|
+ hybris_android_egl_dlsym("eglGetProcAddress");
|
|
|
|
+ assert(_eglGetProcAddress);
|
|
|
|
+
|
|
|
|
+ const char *extensions = (*_eglQueryString)(display, EGL_EXTENSIONS);
|
|
|
|
+
|
|
|
|
+ if (strstr(extensions, "EGL_KHR_fence_sync")) {
|
|
|
|
+ _eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)
|
|
|
|
+ (*_eglGetProcAddress)("eglCreateSyncKHR");
|
|
|
|
+ assert(_eglCreateSyncKHR);
|
|
|
|
+ _eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC)
|
|
|
|
+ (*_eglGetProcAddress)("eglDestroySyncKHR");
|
|
|
|
+ assert(_eglDestroySyncKHR);
|
|
|
|
+ _eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)
|
|
|
|
+ (*_eglGetProcAddress)("eglClientWaitSyncKHR");
|
|
|
|
+ assert(_eglClientWaitSyncKHR);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extern "C" _EGLDisplay *x11ws_GetDisplay(EGLNativeDisplayType display)
|
|
|
|
+{
|
|
|
|
+ X11Display *xdpy = new X11Display;
|
|
|
|
+ xdpy->xl_display = (Display *)display;
|
|
|
|
+
|
|
|
|
+ return &xdpy->base;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extern "C" void x11ws_Terminate(_EGLDisplay *dpy)
|
|
|
|
+{
|
|
|
|
+ X11Display *xdpy = (X11Display *)dpy;
|
|
|
|
+ int ret = 0;
|
|
|
|
+ delete xdpy;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extern "C" EGLNativeWindowType x11ws_CreateWindow(EGLNativeWindowType win, _EGLDisplay *display)
|
|
|
|
+{
|
|
|
|
+ Window xlib_window = (Window) win;
|
|
|
|
+ X11Display *xdpy = (X11Display *)display;
|
|
|
|
+
|
|
|
|
+ if (win == 0 || xdpy->xl_display == 0) {
|
|
|
|
+ HYBRIS_ERROR("Running with EGL_PLATFORM=x11 without X server is not possible");
|
|
|
|
+ HYBRIS_ERROR("If you want to run a standlone EGL client do it like this:");
|
|
|
|
+ HYBRIS_ERROR(" $ export EGL_PLATFORM=null");
|
|
|
|
+ HYBRIS_ERROR(" $ test_glevs2");
|
|
|
|
+ abort();
|
|
|
|
+ }
|
|
|
|
+
|
2019-06-08 10:54:50 +00:00
|
|
|
+ X11NativeWindow *window = new X11NativeWindow(xdpy->xl_display, xlib_window);
|
2018-04-10 21:13:42 +00:00
|
|
|
+ window->common.incRef(&window->common);
|
|
|
|
+ return (EGLNativeWindowType) static_cast<struct ANativeWindow *>(window);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extern "C" void x11ws_DestroyWindow(EGLNativeWindowType win)
|
|
|
|
+{
|
|
|
|
+ X11NativeWindow *window = static_cast<X11NativeWindow *>((struct ANativeWindow *)win);
|
|
|
|
+ window->common.decRef(&window->common);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extern "C" __eglMustCastToProperFunctionPointerType x11ws_eglGetProcAddress(const char *procname)
|
|
|
|
+{
|
|
|
|
+ return eglplatformcommon_eglGetProcAddress(procname);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extern "C" void x11ws_passthroughImageKHR(EGLContext *ctx, EGLenum *target, EGLClientBuffer *buffer, const EGLint **attrib_list)
|
|
|
|
+{
|
|
|
|
+ eglplatformcommon_passthroughImageKHR(ctx, target, buffer, attrib_list);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extern "C" const char *x11ws_eglQueryString(EGLDisplay dpy, EGLint name, const char *(*real_eglQueryString)(EGLDisplay dpy, EGLint name))
|
|
|
|
+{
|
|
|
|
+ const char *ret = eglplatformcommon_eglQueryString(dpy, name, real_eglQueryString);
|
|
|
|
+ if (ret && name == EGL_EXTENSIONS)
|
|
|
|
+ {
|
|
|
|
+ static char eglextensionsbuf[1024];
|
|
|
|
+ snprintf(eglextensionsbuf, 1022, "%s %s", ret,
|
|
|
|
+ "EGL_EXT_swap_buffers_with_damage EGL_WL_create_x11_buffer_from_image"
|
|
|
|
+ );
|
|
|
|
+ ret = eglextensionsbuf;
|
|
|
|
+ }
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extern "C" void x11ws_prepareSwap(EGLDisplay dpy, EGLNativeWindowType win, EGLint *damage_rects, EGLint damage_n_rects)
|
|
|
|
+{
|
|
|
|
+ X11NativeWindow *window = static_cast<X11NativeWindow *>((struct ANativeWindow *)win);
|
|
|
|
+ window->prepareSwap(damage_rects, damage_n_rects);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extern "C" void x11ws_finishSwap(EGLDisplay dpy, EGLNativeWindowType win)
|
|
|
|
+{
|
|
|
|
+ _init_egl_funcs(dpy);
|
|
|
|
+ X11NativeWindow *window = static_cast<X11NativeWindow *>((struct ANativeWindow *)win);
|
|
|
|
+ if (_eglCreateSyncKHR) {
|
|
|
|
+ EGLSyncKHR sync = (*_eglCreateSyncKHR)(dpy, EGL_SYNC_FENCE_KHR, NULL);
|
|
|
|
+ (*_eglClientWaitSyncKHR)(dpy, sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR);
|
|
|
|
+ (*_eglDestroySyncKHR)(dpy, sync);
|
|
|
|
+ }
|
|
|
|
+ window->finishSwap();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extern "C" void x11ws_setSwapInterval(EGLDisplay dpy, EGLNativeWindowType win, EGLint interval)
|
|
|
|
+{
|
|
|
|
+ X11NativeWindow *window = static_cast<X11NativeWindow *>((struct ANativeWindow *)win);
|
|
|
|
+ window->setSwapInterval(interval);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extern "C" EGLBoolean x11ws_eglGetConfigAttrib(struct _EGLDisplay *display, EGLConfig config, EGLint attribute, EGLint *value)
|
|
|
|
+{
|
|
|
|
+ TRACE("attribute:%i", attribute);
|
|
|
|
+ if (attribute == EGL_NATIVE_VISUAL_ID)
|
|
|
|
+ {
|
|
|
|
+ X11Display *xdpy = (X11Display *)display;
|
|
|
|
+ XVisualInfo visinfo_template;
|
|
|
|
+ XVisualInfo *visinfo = NULL;
|
|
|
|
+ int visinfos_count = 0;
|
|
|
|
+
|
|
|
|
+ visinfo_template.depth = 32;
|
|
|
|
+ visinfo = XGetVisualInfo (xdpy->xl_display,
|
|
|
|
+ VisualDepthMask,
|
|
|
|
+ &visinfo_template,
|
|
|
|
+ &visinfos_count);
|
|
|
|
+
|
|
|
|
+ if (visinfos_count)
|
|
|
|
+ {
|
|
|
|
+ TRACE("visinfo.visualid:%i", attribute);
|
|
|
|
+ *value = visinfo->visualid;
|
|
|
|
+ return EGL_TRUE;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ return EGL_FALSE;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+struct ws_module ws_module_info = {
|
|
|
|
+ x11ws_init_module,
|
|
|
|
+ x11ws_GetDisplay,
|
|
|
|
+ x11ws_Terminate,
|
|
|
|
+ x11ws_CreateWindow,
|
|
|
|
+ x11ws_DestroyWindow,
|
|
|
|
+ x11ws_eglGetProcAddress,
|
|
|
|
+ x11ws_passthroughImageKHR,
|
|
|
|
+ x11ws_eglQueryString,
|
|
|
|
+ x11ws_prepareSwap,
|
|
|
|
+ x11ws_finishSwap,
|
|
|
|
+ x11ws_setSwapInterval,
|
|
|
|
+ x11ws_eglGetConfigAttrib
|
|
|
|
+};
|
|
|
|
diff --git a/hybris/egl/platforms/x11/x11_window.cpp b/hybris/egl/platforms/x11/x11_window.cpp
|
|
|
|
new file mode 100644
|
2019-06-08 10:54:50 +00:00
|
|
|
index 0000000..6eea75b
|
2018-04-10 21:13:42 +00:00
|
|
|
--- /dev/null
|
|
|
|
+++ b/hybris/egl/platforms/x11/x11_window.cpp
|
2019-06-08 10:54:50 +00:00
|
|
|
@@ -0,0 +1,739 @@
|
2018-04-10 21:13:42 +00:00
|
|
|
+/****************************************************************************************
|
|
|
|
+ **
|
|
|
|
+ ** Copyright (C) 2013 Jolla Ltd.
|
|
|
|
+ ** Contact: Carsten Munk <carsten.munk@jollamobile.com>
|
|
|
|
+ ** All rights reserved.
|
|
|
|
+ **
|
|
|
|
+ ** This file is part of Wayland enablement for libhybris
|
|
|
|
+ **
|
|
|
|
+ ** You may use this file under the terms of the GNU Lesser General
|
|
|
|
+ ** Public License version 2.1 as published by the Free Software Foundation
|
|
|
|
+ ** and appearing in the file license.lgpl included in the packaging
|
|
|
|
+ ** of this file.
|
|
|
|
+ **
|
|
|
|
+ ** This library is free software; you can redistribute it and/or
|
|
|
|
+ ** modify it under the terms of the GNU Lesser General Public
|
|
|
|
+ ** License version 2.1 as published by the Free Software Foundation
|
|
|
|
+ ** and appearing in the file license.lgpl included in the packaging
|
|
|
|
+ ** of this file.
|
|
|
|
+ **
|
|
|
|
+ ** This library 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
|
|
|
|
+ ** Lesser General Public License for more details.
|
|
|
|
+ **
|
|
|
|
+ ****************************************************************************************/
|
|
|
|
+
|
|
|
|
+#include <android-config.h>
|
|
|
|
+#include "x11_window.h"
|
|
|
|
+#include <assert.h>
|
|
|
|
+#include <stdlib.h>
|
|
|
|
+#include <string.h>
|
|
|
|
+#include <errno.h>
|
|
|
|
+
|
|
|
|
+#include "logging.h"
|
|
|
|
+#include <eglhybris.h>
|
|
|
|
+#include "xcb_drihybris.h"
|
|
|
|
+
|
|
|
|
+#if ANDROID_VERSION_MAJOR>=4 && ANDROID_VERSION_MINOR>=2 || ANDROID_VERSION_MAJOR>=5
|
|
|
|
+extern "C" {
|
|
|
|
+#include <sync/sync.h>
|
|
|
|
+}
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+void X11NativeWindow::resize(unsigned int width, unsigned int height)
|
|
|
|
+{
|
|
|
|
+ lock();
|
2018-10-02 05:09:28 +00:00
|
|
|
+ this->m_defaultWidth = this->m_width = width;
|
|
|
|
+ this->m_defaultHeight = this->m_height = height;
|
2018-04-10 21:13:42 +00:00
|
|
|
+ unlock();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void X11NativeWindow::lock()
|
|
|
|
+{
|
|
|
|
+ pthread_mutex_lock(&this->mutex);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void X11NativeWindow::unlock()
|
|
|
|
+{
|
|
|
|
+ pthread_mutex_unlock(&this->mutex);
|
|
|
|
+}
|
|
|
|
+
|
2019-06-08 10:54:50 +00:00
|
|
|
+X11NativeWindow::X11NativeWindow(Display* xl_display, Window xl_window)
|
2018-04-10 21:13:42 +00:00
|
|
|
+{
|
|
|
|
+ int wayland_ok;
|
|
|
|
+
|
|
|
|
+ HYBRIS_TRACE_BEGIN("x11-platform", "create_window", "");
|
|
|
|
+ this->m_window = xl_window;
|
|
|
|
+ this->m_display = xl_display;
|
|
|
|
+ this->m_connection = XGetXCBConnection(xl_display);
|
|
|
|
+ this->m_image = 0;
|
|
|
|
+ this->m_useShm = true;
|
|
|
|
+ this->m_format = HAL_PIXEL_FORMAT_RGBA_8888;
|
|
|
|
+
|
|
|
|
+ const_cast<int&>(ANativeWindow::minSwapInterval) = 0;
|
|
|
|
+ const_cast<int&>(ANativeWindow::maxSwapInterval) = 1;
|
|
|
|
+ // This is the default as per the EGL documentation
|
|
|
|
+ this->m_swap_interval = 1;
|
|
|
|
+
|
|
|
|
+ TRACE("getting X11 window information");
|
|
|
|
+
|
|
|
|
+ XWindowAttributes window_attributes;
|
|
|
|
+ XGetWindowAttributes(m_display, m_window, &window_attributes);
|
|
|
|
+
|
|
|
|
+ TRACE("window x=%d y=%d width=%d height=%d depth=%d",
|
|
|
|
+ window_attributes.x,
|
|
|
|
+ window_attributes.y,
|
|
|
|
+ window_attributes.width,
|
|
|
|
+ window_attributes.height,
|
|
|
|
+ window_attributes.depth);
|
|
|
|
+
|
|
|
|
+ m_width = window_attributes.width;
|
|
|
|
+ m_height = window_attributes.height;
|
2018-10-02 05:09:28 +00:00
|
|
|
+ m_depth = window_attributes.depth;
|
2018-04-10 21:13:42 +00:00
|
|
|
+
|
|
|
|
+ const char *env = getenv("HYBRIS_X11_FORCE_WIDTH");
|
|
|
|
+ if (env != NULL)
|
|
|
|
+ {
|
|
|
|
+ m_width = atoi(env);
|
|
|
|
+ TRACE("forced width=%d", m_width);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ env = getenv("HYBRIS_X11_FORCE_HEIGHT");
|
|
|
|
+ if (env != NULL)
|
|
|
|
+ {
|
|
|
|
+ m_height = atoi(env);
|
|
|
|
+ TRACE("forced height=%d", m_height);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ m_defaultWidth = m_width;
|
|
|
|
+ m_defaultHeight = m_height;
|
|
|
|
+
|
|
|
|
+ env = getenv("HYBRIS_X11_DISABLE_SHM");
|
|
|
|
+ if (env != NULL)
|
|
|
|
+ {
|
|
|
|
+ m_useShm = false;
|
|
|
|
+ TRACE("won't use MIT-SHM");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ XGCValues gcvalues;
|
|
|
|
+ m_gc = XCreateGC(m_display, m_window, 0, &gcvalues);
|
|
|
|
+
|
|
|
|
+ m_xcb_gc = xcb_generate_id(m_connection);
|
|
|
|
+ xcb_create_gc(m_connection, m_xcb_gc, m_window, 0, 0);
|
|
|
|
+
|
|
|
|
+ m_specialEvent = 0;
|
|
|
|
+ m_haveDRIHybris = false;
|
|
|
|
+ tryEnableDRIHybris();
|
|
|
|
+
|
|
|
|
+ m_usage=GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_READ_OFTEN;
|
|
|
|
+ pthread_mutex_init(&mutex, NULL);
|
|
|
|
+ pthread_cond_init(&cond, NULL);
|
|
|
|
+ m_queueReads = 0;
|
|
|
|
+ m_freeBufs = 0;
|
|
|
|
+ m_damage_rects = NULL;
|
|
|
|
+ m_damage_n_rects = 0;
|
|
|
|
+ m_lastBuffer = 0;
|
|
|
|
+ setBufferCount(3);
|
|
|
|
+ HYBRIS_TRACE_END("x11-platform", "create_window", "");
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+X11NativeWindow::~X11NativeWindow()
|
|
|
|
+{
|
|
|
|
+ std::list<X11NativeWindowBuffer *>::iterator it = m_bufList.begin();
|
|
|
|
+ destroyBuffers();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// overloads from BaseNativeWindow
|
|
|
|
+int X11NativeWindow::setSwapInterval(int interval) {
|
|
|
|
+ TRACE("interval:%i", interval);
|
|
|
|
+
|
|
|
|
+ if (interval < 0)
|
|
|
|
+ interval = 0;
|
|
|
|
+ if (interval > 1)
|
|
|
|
+ interval = 1;
|
|
|
|
+
|
|
|
|
+ HYBRIS_TRACE_BEGIN("x11-platform", "swap_interval", "=%d", interval);
|
|
|
|
+
|
|
|
|
+ lock();
|
|
|
|
+ m_swap_interval = interval;
|
|
|
|
+ unlock();
|
|
|
|
+
|
|
|
|
+ HYBRIS_TRACE_END("x11-platform", "swap_interval", "");
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int X11NativeWindow::dequeueBuffer(BaseNativeWindowBuffer **buffer, int *fenceFd){
|
|
|
|
+ HYBRIS_TRACE_BEGIN("x11-platform", "dequeueBuffer", "");
|
|
|
|
+
|
|
|
|
+ X11NativeWindowBuffer *wnb=NULL;
|
|
|
|
+ TRACE("%p", buffer);
|
|
|
|
+
|
|
|
|
+ readQueue(false);
|
|
|
|
+
|
|
|
|
+ HYBRIS_TRACE_BEGIN("x11-platform", "dequeueBuffer_wait_for_buffer", "");
|
|
|
|
+
|
|
|
|
+ HYBRIS_TRACE_COUNTER("x11-platform", "m_freeBufs", "%i", m_freeBufs);
|
|
|
|
+
|
|
|
|
+ while (m_freeBufs == 0) {
|
|
|
|
+ HYBRIS_TRACE_COUNTER("x11-platform", "m_freeBufs", "%i", m_freeBufs);
|
|
|
|
+ readQueue(true);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ lock();
|
|
|
|
+
|
|
|
|
+ std::list<X11NativeWindowBuffer *>::iterator it = m_bufList.begin();
|
|
|
|
+ for (; it != m_bufList.end(); it++)
|
|
|
|
+ {
|
|
|
|
+ if ((*it)->busy)
|
|
|
|
+ continue;
|
|
|
|
+ if ((*it)->youngest == 1)
|
|
|
|
+ continue;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (it==m_bufList.end()) {
|
|
|
|
+ HYBRIS_TRACE_BEGIN("x11-platform", "dequeueBuffer_worst_case_scenario", "");
|
|
|
|
+ HYBRIS_TRACE_END("x11-platform", "dequeueBuffer_worst_case_scenario", "");
|
|
|
|
+
|
|
|
|
+ it = m_bufList.begin();
|
|
|
|
+ for (; it != m_bufList.end() && (*it)->busy; it++)
|
|
|
|
+ {}
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ if (it==m_bufList.end()) {
|
|
|
|
+ unlock();
|
|
|
|
+ HYBRIS_TRACE_BEGIN("x11-platform", "dequeueBuffer_no_free_buffers", "");
|
|
|
|
+ HYBRIS_TRACE_END("x11-platform", "dequeueBuffer_no_free_buffers", "");
|
|
|
|
+ TRACE("%p: no free buffers", buffer);
|
|
|
|
+ return NO_ERROR;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ wnb = *it;
|
|
|
|
+ assert(wnb!=NULL);
|
|
|
|
+ HYBRIS_TRACE_END("x11-platform", "dequeueBuffer_wait_for_buffer", "");
|
|
|
|
+
|
|
|
|
+ /* If the buffer doesn't match the window anymore, re-allocate */
|
|
|
|
+ if (wnb->width != m_width || wnb->height != m_height
|
|
|
|
+ || wnb->format != m_format || wnb->usage != m_usage)
|
|
|
|
+ {
|
|
|
|
+ TRACE("wnb:%p,win:%p %i,%i %i,%i x%x,x%x x%x,x%x",
|
|
|
|
+ wnb,m_window,
|
|
|
|
+ wnb->width,m_width, wnb->height,m_height,
|
|
|
|
+ wnb->format,m_format, wnb->usage,m_usage);
|
|
|
|
+ destroyBuffer(wnb);
|
|
|
|
+ m_bufList.erase(it);
|
|
|
|
+ wnb = addBuffer();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ wnb->busy = 1;
|
|
|
|
+ *buffer = wnb;
|
|
|
|
+ queue.push_back(wnb);
|
|
|
|
+ --m_freeBufs;
|
|
|
|
+
|
|
|
|
+ HYBRIS_TRACE_COUNTER("x11-platform", "m_freeBufs", "%i", m_freeBufs);
|
|
|
|
+ HYBRIS_TRACE_BEGIN("x11-platform", "dequeueBuffer_gotBuffer", "-%p", wnb);
|
|
|
|
+ HYBRIS_TRACE_END("x11-platform", "dequeueBuffer_gotBuffer", "-%p", wnb);
|
|
|
|
+ HYBRIS_TRACE_END("x11-platform", "dequeueBuffer_wait_for_buffer", "");
|
|
|
|
+
|
|
|
|
+ unlock();
|
|
|
|
+ return NO_ERROR;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int X11NativeWindow::lockBuffer(BaseNativeWindowBuffer* buffer){
|
|
|
|
+ X11NativeWindowBuffer *wnb = (X11NativeWindowBuffer*) buffer;
|
|
|
|
+ HYBRIS_TRACE_BEGIN("x11-platform", "lockBuffer", "-%p", wnb);
|
|
|
|
+ HYBRIS_TRACE_END("x11-platform", "lockBuffer", "-%p", wnb);
|
|
|
|
+ return NO_ERROR;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int X11NativeWindow::readQueue(bool block)
|
|
|
|
+{
|
|
|
|
+ int ret = 0;
|
|
|
|
+
|
|
|
|
+ if (++m_queueReads == 1) {
|
|
|
|
+ if (m_specialEvent) {
|
|
|
|
+ xcb_generic_event_t *ev;
|
|
|
|
+
|
|
|
|
+ if (!block)
|
|
|
|
+ {
|
|
|
|
+ while ((ev = xcb_poll_for_special_event(m_connection,
|
|
|
|
+ m_specialEvent)) != NULL) {
|
|
|
|
+ xcb_present_generic_event_t *ge = (xcb_present_generic_event_t *) ev;
|
|
|
|
+ handlePresentEvent(ge);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // all threads waiting on the false branch will wake and return now, so we
|
|
|
|
+ // can safely set m_queueReads to 0 here instead of relying on every thread
|
|
|
|
+ // to decrement it. This prevents a race condition when a thread enters readQueue()
|
|
|
|
+ // before the one in this thread returns.
|
|
|
|
+ // The new thread would go in the false branch, and there would be no thread in the
|
|
|
|
+ // true branch, blocking the new thread and any other that will call readQueue in
|
|
|
|
+ // the future.
|
|
|
|
+ m_queueReads = 0;
|
|
|
|
+
|
|
|
|
+ pthread_cond_broadcast(&cond);
|
|
|
|
+
|
|
|
|
+ } else if (block) {
|
|
|
|
+ while (m_queueReads > 0) {
|
|
|
|
+ pthread_cond_wait(&cond, &mutex);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void X11NativeWindow::prepareSwap(EGLint *damage_rects, EGLint damage_n_rects)
|
|
|
|
+{
|
|
|
|
+ lock();
|
|
|
|
+ m_damage_rects = damage_rects;
|
|
|
|
+ m_damage_n_rects = damage_n_rects;
|
|
|
|
+ unlock();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void X11NativeWindow::finishSwap()
|
|
|
|
+{
|
2018-10-02 05:09:28 +00:00
|
|
|
+ static int serial = 0;
|
2018-04-10 21:13:42 +00:00
|
|
|
+ int ret = 0;
|
|
|
|
+ lock();
|
|
|
|
+
|
|
|
|
+ X11NativeWindowBuffer *wnb = queue.front();
|
|
|
|
+ if (!wnb) {
|
|
|
|
+ wnb = m_lastBuffer;
|
|
|
|
+ } else {
|
|
|
|
+ queue.pop_front();
|
|
|
|
+ }
|
|
|
|
+ assert(wnb);
|
|
|
|
+ m_lastBuffer = wnb;
|
|
|
|
+ wnb->busy = 1;
|
|
|
|
+
|
2018-10-02 05:09:28 +00:00
|
|
|
+ // fronted.push_back(wnb);
|
2018-04-10 21:13:42 +00:00
|
|
|
+
|
|
|
|
+ m_damage_rects = NULL;
|
|
|
|
+ m_damage_n_rects = 0;
|
|
|
|
+ unlock();
|
|
|
|
+
|
|
|
|
+ if (m_haveDRIHybris) {
|
|
|
|
+ if (wnb->pixmap == 0)
|
|
|
|
+ wnb->pixmap_from_buffer(m_connection, m_window);
|
|
|
|
+
|
2018-10-02 05:09:28 +00:00
|
|
|
+ xcb_present_pixmap(m_connection,
|
|
|
|
+ m_window,
|
|
|
|
+ wnb->pixmap,
|
|
|
|
+ (uint32_t) serial++,
|
|
|
|
+ 0, /* valid */
|
|
|
|
+ 0, /* update */
|
|
|
|
+ 0, /* x_off */
|
|
|
|
+ 0, /* y_off */
|
|
|
|
+ None, /* target_crtc */
|
|
|
|
+ None,
|
|
|
|
+ NULL,
|
|
|
|
+ XCB_PRESENT_OPTION_NONE,
|
|
|
|
+ 0,
|
|
|
|
+ 0,
|
|
|
|
+ 0, 0, NULL);
|
2018-04-10 21:13:42 +00:00
|
|
|
+ xcb_flush(m_connection);
|
|
|
|
+
|
|
|
|
+ lock();
|
|
|
|
+
|
|
|
|
+ ++m_freeBufs;
|
|
|
|
+ HYBRIS_TRACE_COUNTER("x11-platform", "m_freeBufs", "%i", m_freeBufs);
|
|
|
|
+
|
|
|
|
+ std::list<X11NativeWindowBuffer *>::iterator it;
|
|
|
|
+ for (it = m_bufList.begin(); it != m_bufList.end(); it++)
|
|
|
|
+ {
|
|
|
|
+ (*it)->youngest = 0;
|
|
|
|
+ }
|
|
|
|
+ wnb->youngest = 1;
|
|
|
|
+ wnb->busy = 0;
|
|
|
|
+
|
|
|
|
+ unlock();
|
|
|
|
+ } else {
|
|
|
|
+ copyToX11(wnb);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int debugenvchecked = 0;
|
|
|
|
+
|
|
|
|
+int X11NativeWindow::queueBuffer(BaseNativeWindowBuffer* buffer, int fenceFd)
|
|
|
|
+{
|
|
|
|
+ X11NativeWindowBuffer *wnb = (X11NativeWindowBuffer*) buffer;
|
|
|
|
+ int ret = 0;
|
|
|
|
+
|
|
|
|
+ HYBRIS_TRACE_BEGIN("x11-platform", "queueBuffer", "-%p", wnb);
|
|
|
|
+ lock();
|
|
|
|
+
|
|
|
|
+ if (debugenvchecked == 0)
|
|
|
|
+ {
|
|
|
|
+ if (getenv("HYBRIS_WAYLAND_DUMP_BUFFERS") != NULL)
|
|
|
|
+ debugenvchecked = 2;
|
|
|
|
+ else
|
|
|
|
+ debugenvchecked = 1;
|
|
|
|
+ }
|
|
|
|
+ if (debugenvchecked == 2)
|
|
|
|
+ {
|
|
|
|
+ HYBRIS_TRACE_BEGIN("x11-platform", "queueBuffer_dumping_buffer", "-%p", wnb);
|
|
|
|
+ hybris_dump_buffer_to_file(wnb->getNativeBuffer());
|
|
|
|
+ HYBRIS_TRACE_END("x11-platform", "queueBuffer_dumping_buffer", "-%p", wnb);
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+#if ANDROID_VERSION_MAJOR>=4 && ANDROID_VERSION_MINOR>=2 || ANDROID_VERSION_MAJOR>=5
|
|
|
|
+ HYBRIS_TRACE_BEGIN("x11-platform", "queueBuffer_waiting_for_fence", "-%p", wnb);
|
|
|
|
+ if (fenceFd >= 0)
|
|
|
|
+ {
|
|
|
|
+ sync_wait(fenceFd, -1);
|
|
|
|
+ close(fenceFd);
|
|
|
|
+ }
|
|
|
|
+ HYBRIS_TRACE_END("x11-platform", "queueBuffer_waiting_for_fence", "-%p", wnb);
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+ HYBRIS_TRACE_COUNTER("x11-platform", "fronted.size", "%i", fronted.size());
|
|
|
|
+ HYBRIS_TRACE_END("x11-platform", "queueBuffer", "-%p", wnb);
|
|
|
|
+
|
|
|
|
+ unlock();
|
|
|
|
+
|
|
|
|
+ return NO_ERROR;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int X11NativeWindow::cancelBuffer(BaseNativeWindowBuffer* buffer, int fenceFd){
|
|
|
|
+ std::list<X11NativeWindowBuffer *>::iterator it;
|
|
|
|
+ X11NativeWindowBuffer *wnb = (X11NativeWindowBuffer*) buffer;
|
|
|
|
+
|
|
|
|
+ lock();
|
|
|
|
+ HYBRIS_TRACE_BEGIN("x11-platform", "cancelBuffer", "-%p", wnb);
|
|
|
|
+
|
|
|
|
+ /* Check first that it really is our buffer */
|
|
|
|
+ for (it = m_bufList.begin(); it != m_bufList.end(); it++)
|
|
|
|
+ {
|
|
|
|
+ if ((*it) == wnb)
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ assert(it != m_bufList.end());
|
|
|
|
+
|
|
|
|
+ wnb->busy = 0;
|
|
|
|
+ ++m_freeBufs;
|
|
|
|
+ HYBRIS_TRACE_COUNTER("x11-platform", "m_freeBufs", "%i", m_freeBufs);
|
|
|
|
+
|
|
|
|
+ for (it = m_bufList.begin(); it != m_bufList.end(); it++)
|
|
|
|
+ {
|
|
|
|
+ (*it)->youngest = 0;
|
|
|
|
+ }
|
|
|
|
+ wnb->youngest = 1;
|
|
|
|
+
|
|
|
|
+ if (m_queueReads != 0) {
|
|
|
|
+ // Some thread is waiting on wl_display_dispatch_queue(), possibly waiting for a wl_buffer.release
|
|
|
|
+ // event. Since we have now cancelled a buffer push an artificial event so that the dispatch returns
|
|
|
|
+ // and the thread can notice the cancelled buffer. This means there is a delay of one roundtrip,
|
|
|
|
+ // but I don't see other solution except having one dedicated thread for calling wl_display_dispatch_queue().
|
|
|
|
+ //wl_callback_destroy(wl_display_sync(m_display));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ HYBRIS_TRACE_END("x11-platform", "cancelBuffer", "-%p", wnb);
|
|
|
|
+ unlock();
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+unsigned int X11NativeWindow::width() const {
|
|
|
|
+ TRACE("value:%i", m_width);
|
|
|
|
+ return m_width;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+unsigned int X11NativeWindow::height() const {
|
|
|
|
+ TRACE("value:%i", m_height);
|
|
|
|
+ return m_height;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+unsigned int X11NativeWindow::format() const {
|
|
|
|
+ TRACE("value:%i", m_format);
|
|
|
|
+ return m_format;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+unsigned int X11NativeWindow::defaultWidth() const {
|
|
|
|
+ TRACE("value:%i", m_defaultWidth);
|
|
|
|
+ return m_defaultWidth;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+unsigned int X11NativeWindow::defaultHeight() const {
|
|
|
|
+ TRACE("value:%i", m_defaultHeight);
|
|
|
|
+ return m_defaultHeight;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+unsigned int X11NativeWindow::queueLength() const {
|
|
|
|
+ TRACE("WARN: stub");
|
|
|
|
+ return 1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+unsigned int X11NativeWindow::type() const {
|
|
|
|
+ TRACE("");
|
|
|
|
+#if ANDROID_VERSION_MAJOR>=4 && ANDROID_VERSION_MINOR>=3 || ANDROID_VERSION_MAJOR>=5
|
|
|
|
+ /* https://android.googlesource.com/platform/system/core/+/bcfa910611b42018db580b3459101c564f802552%5E!/ */
|
|
|
|
+ return NATIVE_WINDOW_SURFACE;
|
|
|
|
+#else
|
|
|
|
+ return NATIVE_WINDOW_SURFACE_TEXTURE_CLIENT;
|
|
|
|
+#endif
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+unsigned int X11NativeWindow::transformHint() const {
|
|
|
|
+ TRACE("WARN: stub");
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * returns the current usage of this window
|
|
|
|
+ */
|
|
|
|
+unsigned int X11NativeWindow::getUsage() const {
|
|
|
|
+ return m_usage;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int X11NativeWindow::setBuffersFormat(int format) {
|
|
|
|
+ if (format != m_format)
|
|
|
|
+ {
|
|
|
|
+ TRACE("old-format:x%x new-format:x%x", m_format, format);
|
|
|
|
+ m_format = format;
|
|
|
|
+ /* Buffers will be re-allocated when dequeued */
|
|
|
|
+ } else {
|
|
|
|
+ TRACE("format:x%x", format);
|
|
|
|
+ }
|
|
|
|
+ return NO_ERROR;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void X11NativeWindow::destroyBuffer(X11NativeWindowBuffer* wnb)
|
|
|
|
+{
|
|
|
|
+ TRACE("wnb:%p", wnb);
|
|
|
|
+
|
|
|
|
+ assert(wnb != NULL);
|
|
|
|
+
|
|
|
|
+ int ret = 0;
|
|
|
|
+
|
|
|
|
+ wnb->common.decRef(&wnb->common);
|
|
|
|
+ m_freeBufs--;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void X11NativeWindow::destroyBuffers()
|
|
|
|
+{
|
|
|
|
+ TRACE("");
|
|
|
|
+
|
|
|
|
+ std::list<X11NativeWindowBuffer*>::iterator it = m_bufList.begin();
|
|
|
|
+ for (; it!=m_bufList.end(); ++it)
|
|
|
|
+ {
|
|
|
|
+ destroyBuffer(*it);
|
|
|
|
+ it = m_bufList.erase(it);
|
|
|
|
+ }
|
|
|
|
+ m_bufList.clear();
|
|
|
|
+ m_freeBufs = 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+X11NativeWindowBuffer *X11NativeWindow::addBuffer() {
|
|
|
|
+
|
|
|
|
+ X11NativeWindowBuffer *wnb;
|
|
|
|
+
|
2019-06-08 10:54:50 +00:00
|
|
|
+ wnb = new ClientX11Buffer(m_width, m_height, m_format, m_usage, m_depth);
|
2018-04-10 21:13:42 +00:00
|
|
|
+ m_bufList.push_back(wnb);
|
|
|
|
+ ++m_freeBufs;
|
|
|
|
+
|
|
|
|
+ TRACE("wnb:%p width:%i height:%i format:x%x usage:x%x",
|
|
|
|
+ wnb, wnb->width, wnb->height, wnb->format, wnb->usage);
|
|
|
|
+
|
|
|
|
+ return wnb;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int X11NativeWindow::setBufferCount(int cnt) {
|
|
|
|
+ int start = 0;
|
|
|
|
+
|
|
|
|
+ TRACE("cnt:%d", cnt);
|
|
|
|
+
|
|
|
|
+ if (m_bufList.size() == cnt)
|
|
|
|
+ return NO_ERROR;
|
|
|
|
+
|
|
|
|
+ lock();
|
|
|
|
+
|
|
|
|
+ if (m_bufList.size() > cnt) {
|
|
|
|
+ /* Decreasing buffer count, remove from beginning */
|
|
|
|
+ std::list<X11NativeWindowBuffer*>::iterator it = m_bufList.begin();
|
|
|
|
+ for (int i = 0; i <= m_bufList.size() - cnt; i++ )
|
|
|
|
+ {
|
|
|
|
+ destroyBuffer(*it);
|
|
|
|
+ ++it;
|
|
|
|
+ m_bufList.pop_front();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ /* Increasing buffer count, start from current size */
|
|
|
|
+ for (int i = m_bufList.size(); i < cnt; i++)
|
|
|
|
+ X11NativeWindowBuffer *unused = addBuffer();
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ unlock();
|
|
|
|
+
|
|
|
|
+ return NO_ERROR;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int X11NativeWindow::setBuffersDimensions(int width, int height) {
|
|
|
|
+ if (m_width != width || m_height != height)
|
|
|
|
+ {
|
|
|
|
+ TRACE("old-size:%ix%i new-size:%ix%i", m_width, m_height, width, height);
|
|
|
|
+ m_width = width;
|
|
|
|
+ m_height = height;
|
|
|
|
+ /* Buffers will be re-allocated when dequeued */
|
|
|
|
+ } else {
|
|
|
|
+ TRACE("size:%ix%i", width, height);
|
|
|
|
+ }
|
|
|
|
+ return NO_ERROR;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int X11NativeWindow::setUsage(int usage) {
|
|
|
|
+// if ((usage | GRALLOC_USAGE_HW_TEXTURE) != m_usage)
|
|
|
|
+// {
|
|
|
|
+// TRACE("old-usage:x%x new-usage:x%x", m_usage, usage);
|
|
|
|
+// m_usage = usage | GRALLOC_USAGE_HW_TEXTURE;
|
|
|
|
+// /* Buffers will be re-allocated when dequeued */
|
|
|
|
+// } else {
|
|
|
|
+// TRACE("usage:x%x", usage);
|
|
|
|
+// }
|
|
|
|
+ return NO_ERROR;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void X11NativeWindow::copyToX11(X11NativeWindowBuffer *wnb) {
|
|
|
|
+ int ret;
|
|
|
|
+ void *vaddr;
|
|
|
|
+ std::list<X11NativeWindowBuffer *>::iterator it;
|
|
|
|
+
|
2019-06-08 10:54:50 +00:00
|
|
|
+ ret = hybris_gralloc_lock(wnb->handle, wnb->usage, 0, 0, wnb->width, wnb->height, &vaddr);
|
2018-04-10 21:13:42 +00:00
|
|
|
+ TRACE("wnb:%p gralloc lock returns %i", wnb, ret);
|
|
|
|
+ TRACE("wnb:%p lock to vaddr %p", wnb, vaddr);
|
|
|
|
+ TRACE("wnb:%p width=%d stride=%d height=%d format=%d", wnb, wnb->width, wnb->stride, wnb->height, wnb->format);
|
|
|
|
+
|
|
|
|
+ if (!m_image)
|
|
|
|
+ {
|
|
|
|
+ if (m_useShm)
|
|
|
|
+ {
|
|
|
|
+ m_image = XShmCreateImage(m_display,
|
|
|
|
+ CopyFromParent,
|
|
|
|
+ 32,
|
|
|
|
+ ZPixmap, 0, &m_shminfo, wnb->stride, wnb->height);
|
|
|
|
+
|
|
|
|
+ m_shminfo.shmid = shmget(IPC_PRIVATE,
|
|
|
|
+ m_image->bytes_per_line * m_image->height,
|
|
|
|
+ IPC_CREAT|0777);
|
|
|
|
+
|
|
|
|
+ m_shminfo.shmaddr = m_image->data = (char *)shmat(m_shminfo.shmid, 0, 0);
|
|
|
|
+ m_shminfo.readOnly = 0;
|
|
|
|
+
|
|
|
|
+ TRACE("m_shminfo.shmaddr %p", m_shminfo.shmaddr);
|
|
|
|
+
|
|
|
|
+ XShmAttach(m_display, &m_shminfo);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ m_image = XCreateImage(m_display,
|
|
|
|
+ CopyFromParent,
|
|
|
|
+ 32,
|
|
|
|
+ ZPixmap, 0, (char *)vaddr, wnb->stride, wnb->height, 32, 0);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (m_useShm)
|
|
|
|
+ {
|
|
|
|
+ memcpy(m_image->data, vaddr, m_image->bytes_per_line * m_image->height);
|
2019-06-08 10:54:50 +00:00
|
|
|
+ hybris_gralloc_unlock(wnb->handle);
|
2018-04-10 21:13:42 +00:00
|
|
|
+ XShmPutImage(m_display, m_window, m_gc, m_image, 0, 0, 0, 0, m_width, m_height, 0);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ m_image->data = (char *)vaddr;
|
|
|
|
+ XPutImage(m_display, m_window, m_gc, m_image, 0, 0, 0, 0, m_width, m_height);
|
2019-06-08 10:54:50 +00:00
|
|
|
+ hybris_gralloc_unlock(wnb->handle);
|
2018-04-10 21:13:42 +00:00
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ lock();
|
|
|
|
+
|
|
|
|
+ ++m_freeBufs;
|
|
|
|
+ HYBRIS_TRACE_COUNTER("x11-platform", "m_freeBufs", "%i", m_freeBufs);
|
|
|
|
+ for (it = m_bufList.begin(); it != m_bufList.end(); it++)
|
|
|
|
+ {
|
|
|
|
+ (*it)->youngest = 0;
|
|
|
|
+ }
|
|
|
|
+ wnb->youngest = 1;
|
|
|
|
+ wnb->busy = 0;
|
|
|
|
+
|
|
|
|
+ unlock();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void X11NativeWindow::tryEnableDRIHybris()
|
|
|
|
+{
|
|
|
|
+ const xcb_query_extension_reply_t *extension;
|
|
|
|
+ xcb_void_cookie_t cookie;
|
|
|
|
+ xcb_generic_error_t *error;
|
|
|
|
+
|
|
|
|
+ xcb_prefetch_extension_data (m_connection, &xcb_drihybris_id);
|
|
|
|
+ xcb_prefetch_extension_data (m_connection, &xcb_present_id);
|
|
|
|
+
|
|
|
|
+ extension = xcb_get_extension_data(m_connection, &xcb_drihybris_id);
|
|
|
|
+ if (!(extension && extension->present))
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ extension = xcb_get_extension_data(m_connection, &xcb_present_id);
|
|
|
|
+ if (!(extension && extension->present))
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ m_specialEventId = xcb_generate_id(m_connection);
|
|
|
|
+ m_specialEvent = xcb_register_for_special_xge(m_connection,
|
|
|
|
+ &xcb_present_id, m_specialEventId, NULL);
|
|
|
|
+
|
|
|
|
+ cookie = xcb_present_select_input_checked(m_connection,
|
|
|
|
+ m_specialEventId, m_window,
|
|
|
|
+ XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY |
|
|
|
|
+ XCB_PRESENT_EVENT_MASK_CONFIGURE_NOTIFY);
|
|
|
|
+
|
|
|
|
+ error = xcb_request_check(m_connection, cookie);
|
|
|
|
+ if (error) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ m_haveDRIHybris = true;
|
|
|
|
+ // HYBRIS_PIXEL_FORMAT_RGBA_8888 is used in glamor for buffer import
|
|
|
|
+ m_format = HAL_PIXEL_FORMAT_RGBA_8888;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void X11NativeWindow::handlePresentEvent(xcb_present_generic_event_t *ge)
|
|
|
|
+{
|
|
|
|
+ switch (ge->evtype) {
|
|
|
|
+ case XCB_PRESENT_CONFIGURE_NOTIFY: {
|
|
|
|
+ xcb_present_configure_notify_event_t *ce = (xcb_present_configure_notify_event_t *) ge;
|
|
|
|
+ printf("XCB_PRESENT_CONFIGURE_NOTIFY: %dx%d\n", ce->width, ce->height);
|
|
|
|
+ resize(ce->width, ce->height);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void X11NativeWindowBuffer::pixmap_from_buffer(xcb_connection_t *connection, xcb_drawable_t drawable)
|
|
|
|
+{
|
|
|
|
+ int32_t * fds;
|
|
|
|
+ fds = (int32_t *)calloc(handle->numFds, sizeof(int));
|
|
|
|
+ for (int i = 0; i < handle->numFds; i++) {
|
|
|
|
+ fds[i] = dup(handle->data[i]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ xcb_drihybris_pixmap_from_buffer_checked(connection,
|
|
|
|
+ (pixmap = xcb_generate_id(connection)),
|
|
|
|
+ drawable,
|
|
|
|
+ stride * height * 4,
|
|
|
|
+ this->width, height, stride,
|
2018-10-02 05:09:28 +00:00
|
|
|
+ windowDepth, 32,
|
2018-04-10 21:13:42 +00:00
|
|
|
+ handle->numInts,
|
|
|
|
+ handle->numFds,
|
|
|
|
+ (const uint32_t *)(handle->data + handle->numFds),
|
|
|
|
+ (const int32_t *)fds);
|
|
|
|
+ xcb_flush(connection);
|
|
|
|
+ free(fds);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// vim: noai:ts=4:sw=4:ss=4:expandtab
|
|
|
|
diff --git a/hybris/egl/platforms/x11/x11_window.h b/hybris/egl/platforms/x11/x11_window.h
|
|
|
|
new file mode 100644
|
2019-06-08 10:54:50 +00:00
|
|
|
index 0000000..e4464db
|
2018-04-10 21:13:42 +00:00
|
|
|
--- /dev/null
|
|
|
|
+++ b/hybris/egl/platforms/x11/x11_window.h
|
2019-06-08 10:54:50 +00:00
|
|
|
@@ -0,0 +1,195 @@
|
2018-04-10 21:13:42 +00:00
|
|
|
+/****************************************************************************************
|
|
|
|
+ **
|
|
|
|
+ ** Copyright (C) 2013 Jolla Ltd.
|
|
|
|
+ ** Contact: Carsten Munk <carsten.munk@jollamobile.com>
|
|
|
|
+ ** All rights reserved.
|
|
|
|
+ **
|
|
|
|
+ ** This file is part of Wayland enablement for libhybris
|
|
|
|
+ **
|
|
|
|
+ ** You may use this file under the terms of the GNU Lesser General
|
|
|
|
+ ** Public License version 2.1 as published by the Free Software Foundation
|
|
|
|
+ ** and appearing in the file license.lgpl included in the packaging
|
|
|
|
+ ** of this file.
|
|
|
|
+ **
|
|
|
|
+ ** This library is free software; you can redistribute it and/or
|
|
|
|
+ ** modify it under the terms of the GNU Lesser General Public
|
|
|
|
+ ** License version 2.1 as published by the Free Software Foundation
|
|
|
|
+ ** and appearing in the file license.lgpl included in the packaging
|
|
|
|
+ ** of this file.
|
|
|
|
+ **
|
|
|
|
+ ** This library 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
|
|
|
|
+ ** Lesser General Public License for more details.
|
|
|
|
+ **
|
|
|
|
+ ****************************************************************************************/
|
|
|
|
+
|
|
|
|
+#ifndef X11_WINDOW_H
|
|
|
|
+#define X11_WINDOW_H
|
|
|
|
+#include "nativewindowbase.h"
|
|
|
|
+#include <linux/fb.h>
|
2019-06-08 10:54:50 +00:00
|
|
|
+#include <hybris/gralloc/gralloc.h>
|
2018-04-10 21:13:42 +00:00
|
|
|
+extern "C" {
|
|
|
|
+#include <X11/Xlib-xcb.h>
|
|
|
|
+#include <xcb/present.h>
|
|
|
|
+#include <sys/shm.h>
|
|
|
|
+#include <X11/extensions/XShm.h>
|
|
|
|
+#include <pthread.h>
|
|
|
|
+}
|
|
|
|
+#include <list>
|
|
|
|
+#include <deque>
|
|
|
|
+
|
|
|
|
+class X11NativeWindowBuffer : public BaseNativeWindowBuffer
|
|
|
|
+{
|
|
|
|
+public:
|
|
|
|
+ X11NativeWindowBuffer() : busy(0), youngest(0), other(0) {}
|
|
|
|
+ X11NativeWindowBuffer(ANativeWindowBuffer *other)
|
|
|
|
+ {
|
|
|
|
+ ANativeWindowBuffer::width = other->width;
|
|
|
|
+ ANativeWindowBuffer::height = other->height;
|
|
|
|
+ ANativeWindowBuffer::format = other->format;
|
|
|
|
+ ANativeWindowBuffer::usage = other->usage;
|
|
|
|
+ ANativeWindowBuffer::handle = other->handle;
|
|
|
|
+ ANativeWindowBuffer::stride = other->stride;
|
|
|
|
+
|
|
|
|
+ this->busy = 0;
|
|
|
|
+ this->other = other;
|
|
|
|
+ this->youngest = 0;
|
|
|
|
+ this->pixmap = 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ int busy;
|
|
|
|
+ int youngest;
|
|
|
|
+ ANativeWindowBuffer *other;
|
2018-10-02 05:09:28 +00:00
|
|
|
+ int windowDepth;
|
2018-04-10 21:13:42 +00:00
|
|
|
+ xcb_pixmap_t pixmap;
|
|
|
|
+
|
|
|
|
+ void pixmap_from_buffer(xcb_connection_t *connection, xcb_drawable_t drawable);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+class ClientX11Buffer : public X11NativeWindowBuffer
|
|
|
|
+{
|
|
|
|
+friend class X11NativeWindow;
|
|
|
|
+protected:
|
2019-06-08 10:54:50 +00:00
|
|
|
+ ClientX11Buffer( unsigned int width,
|
2018-04-10 21:13:42 +00:00
|
|
|
+ unsigned int height,
|
|
|
|
+ unsigned int format,
|
2018-10-02 05:09:28 +00:00
|
|
|
+ unsigned int usage,
|
|
|
|
+ unsigned int windowDepth)
|
2018-04-10 21:13:42 +00:00
|
|
|
+ {
|
|
|
|
+ // Base members
|
|
|
|
+ ANativeWindowBuffer::width = width;
|
|
|
|
+ ANativeWindowBuffer::height = height;
|
|
|
|
+ ANativeWindowBuffer::format = format;
|
|
|
|
+ ANativeWindowBuffer::usage = usage;
|
|
|
|
+
|
|
|
|
+ this->busy = 0;
|
|
|
|
+ this->other = NULL;
|
2019-06-08 10:54:50 +00:00
|
|
|
+ int alloc_ok = hybris_gralloc_allocate(this->width ? this->width : 1, this->height ? this->height : 1,
|
2018-04-10 21:13:42 +00:00
|
|
|
+ this->format, this->usage,
|
2019-06-08 10:54:50 +00:00
|
|
|
+ &this->handle, (uint32_t*)&this->stride);
|
2018-04-10 21:13:42 +00:00
|
|
|
+ assert(alloc_ok == 0);
|
|
|
|
+ this->youngest = 0;
|
|
|
|
+ this->common.incRef(&this->common);
|
2018-10-02 05:09:28 +00:00
|
|
|
+
|
|
|
|
+ this->windowDepth = windowDepth;
|
2018-04-10 21:13:42 +00:00
|
|
|
+ this->pixmap = 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ~ClientX11Buffer()
|
|
|
|
+ {
|
2019-06-08 10:54:50 +00:00
|
|
|
+ hybris_gralloc_release(this->handle, 1);
|
2018-04-10 21:13:42 +00:00
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+protected:
|
|
|
|
+ void* vaddr;
|
|
|
|
+
|
|
|
|
+public:
|
|
|
|
+
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+class X11NativeWindow : public BaseNativeWindow {
|
|
|
|
+public:
|
2019-06-08 10:54:50 +00:00
|
|
|
+ X11NativeWindow(Display* xl_display, Window xl_window);
|
2018-04-10 21:13:42 +00:00
|
|
|
+ ~X11NativeWindow();
|
|
|
|
+
|
|
|
|
+ void lock();
|
|
|
|
+ void unlock();
|
|
|
|
+ void frame();
|
|
|
|
+ void resize(unsigned int width, unsigned int height);
|
|
|
|
+ void releaseBuffer(struct wl_buffer *buffer);
|
|
|
|
+
|
|
|
|
+ virtual int setSwapInterval(int interval);
|
|
|
|
+ void prepareSwap(EGLint *damage_rects, EGLint damage_n_rects);
|
|
|
|
+ void finishSwap();
|
|
|
|
+
|
|
|
|
+protected:
|
|
|
|
+ // overloads from BaseNativeWindow
|
|
|
|
+ virtual int dequeueBuffer(BaseNativeWindowBuffer **buffer, int *fenceFd);
|
|
|
|
+ virtual int lockBuffer(BaseNativeWindowBuffer* buffer);
|
|
|
|
+ virtual int queueBuffer(BaseNativeWindowBuffer* buffer, int fenceFd);
|
|
|
|
+ virtual int cancelBuffer(BaseNativeWindowBuffer* buffer, int fenceFd);
|
|
|
|
+ virtual unsigned int type() const;
|
|
|
|
+ virtual unsigned int width() const;
|
|
|
|
+ virtual unsigned int height() const;
|
|
|
|
+ virtual unsigned int format() const;
|
|
|
|
+ virtual unsigned int defaultWidth() const;
|
|
|
|
+ virtual unsigned int defaultHeight() const;
|
|
|
|
+ virtual unsigned int queueLength() const;
|
|
|
|
+ virtual unsigned int transformHint() const;
|
|
|
|
+ virtual unsigned int getUsage() const;
|
|
|
|
+ // perform calls
|
|
|
|
+ virtual int setUsage(int usage);
|
|
|
|
+ virtual int setBuffersFormat(int format);
|
|
|
|
+ virtual int setBuffersDimensions(int width, int height);
|
|
|
|
+ virtual int setBufferCount(int cnt);
|
|
|
|
+
|
|
|
|
+private:
|
|
|
|
+ X11NativeWindowBuffer *addBuffer();
|
|
|
|
+ void destroyBuffer(X11NativeWindowBuffer *);
|
|
|
|
+ void destroyBuffers();
|
|
|
|
+ int readQueue(bool block);
|
|
|
|
+
|
|
|
|
+ void copyToX11(X11NativeWindowBuffer *wnb);
|
|
|
|
+ void tryEnableDRIHybris();
|
|
|
|
+ void handlePresentEvent(xcb_present_generic_event_t *ge);
|
|
|
|
+
|
|
|
|
+ std::list<X11NativeWindowBuffer *> m_bufList;
|
|
|
|
+ std::list<X11NativeWindowBuffer *> fronted;
|
|
|
|
+ std::list<X11NativeWindowBuffer *> posted;
|
|
|
|
+ std::list<X11NativeWindowBuffer *> post_registered;
|
|
|
|
+ std::deque<X11NativeWindowBuffer *> queue;
|
|
|
|
+
|
|
|
|
+ Display* m_display;
|
|
|
|
+ Window m_window;
|
|
|
|
+ XImage *m_image;
|
|
|
|
+ XShmSegmentInfo m_shminfo;
|
|
|
|
+ GC m_gc;
|
|
|
|
+
|
|
|
|
+ xcb_connection_t *m_connection;
|
|
|
|
+ xcb_gcontext_t m_xcb_gc;
|
|
|
|
+ xcb_present_event_t m_specialEventId;
|
|
|
|
+ xcb_special_event_t *m_specialEvent;
|
|
|
|
+
|
|
|
|
+ bool m_useShm;
|
|
|
|
+ bool m_haveDRIHybris;
|
|
|
|
+
|
|
|
|
+ X11NativeWindowBuffer *m_lastBuffer;
|
|
|
|
+ unsigned int m_width;
|
|
|
|
+ unsigned int m_height;
|
2018-10-02 05:09:28 +00:00
|
|
|
+ unsigned int m_depth;
|
2018-04-10 21:13:42 +00:00
|
|
|
+ unsigned int m_format;
|
|
|
|
+ unsigned int m_defaultWidth;
|
|
|
|
+ unsigned int m_defaultHeight;
|
|
|
|
+ unsigned int m_usage;
|
|
|
|
+
|
|
|
|
+ pthread_mutex_t mutex;
|
|
|
|
+ pthread_cond_t cond;
|
|
|
|
+ int m_queueReads;
|
|
|
|
+ int m_freeBufs;
|
|
|
|
+ EGLint *m_damage_rects, m_damage_n_rects;
|
|
|
|
+ int m_swap_interval;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+#endif
|
|
|
|
+// vim: noai:ts=4:sw=4:ss=4:expandtab
|
|
|
|
diff --git a/hybris/egl/platforms/x11/xcb_drihybris.c b/hybris/egl/platforms/x11/xcb_drihybris.c
|
|
|
|
new file mode 100644
|
|
|
|
index 0000000..bec3722
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/hybris/egl/platforms/x11/xcb_drihybris.c
|
|
|
|
@@ -0,0 +1,167 @@
|
|
|
|
+/*
|
|
|
|
+ * This file generated automatically from drihybris.xml by c_client.py.
|
|
|
|
+ * Edit at your peril.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include "xcb_drihybris.h"
|
|
|
|
+#include <stddef.h> /* for offsetof() */
|
|
|
|
+
|
|
|
|
+xcb_extension_t xcb_drihybris_id = { "DRIHYBRIS", 0 };
|
|
|
|
+
|
|
|
|
+#define ALIGNOF(type) offsetof(struct { char dummy; type member; }, member)
|
|
|
|
+
|
|
|
|
+int
|
|
|
|
+xcb_drihybris_pixmap_from_buffer_sizeof (const void *_buffer,
|
|
|
|
+ int32_t pixmap_fd)
|
|
|
|
+{
|
|
|
|
+ char *xcb_tmp = (char *)_buffer;
|
|
|
|
+ const xcb_drihybris_pixmap_from_buffer_request_t *_aux = (xcb_drihybris_pixmap_from_buffer_request_t *)_buffer;
|
|
|
|
+ unsigned int xcb_buffer_len = 0;
|
|
|
|
+ unsigned int xcb_block_len = 0;
|
|
|
|
+ unsigned int xcb_pad = 0;
|
|
|
|
+ unsigned int xcb_align_to = 0;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ xcb_block_len += sizeof(xcb_drihybris_pixmap_from_buffer_request_t);
|
|
|
|
+ xcb_tmp += xcb_block_len;
|
|
|
|
+ xcb_buffer_len += xcb_block_len;
|
|
|
|
+ xcb_block_len = 0;
|
|
|
|
+ /* ints */
|
|
|
|
+ xcb_block_len += _aux->num_ints * sizeof(uint32_t);
|
|
|
|
+ xcb_tmp += xcb_block_len;
|
|
|
|
+ xcb_align_to = ALIGNOF(uint32_t);
|
|
|
|
+ /* insert padding */
|
|
|
|
+ xcb_pad = -xcb_block_len & (xcb_align_to - 1);
|
|
|
|
+ xcb_buffer_len += xcb_block_len + xcb_pad;
|
|
|
|
+ if (0 != xcb_pad) {
|
|
|
|
+ xcb_tmp += xcb_pad;
|
|
|
|
+ xcb_pad = 0;
|
|
|
|
+ }
|
|
|
|
+ xcb_block_len = 0;
|
|
|
|
+
|
|
|
|
+ return xcb_buffer_len;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+xcb_void_cookie_t
|
|
|
|
+xcb_drihybris_pixmap_from_buffer_checked (xcb_connection_t *c,
|
|
|
|
+ xcb_pixmap_t pixmap,
|
|
|
|
+ xcb_drawable_t drawable,
|
|
|
|
+ uint32_t size,
|
|
|
|
+ uint16_t width,
|
|
|
|
+ uint16_t height,
|
|
|
|
+ uint16_t stride,
|
|
|
|
+ uint8_t depth,
|
|
|
|
+ uint8_t bpp,
|
|
|
|
+ uint16_t num_ints,
|
|
|
|
+ uint16_t num_fds,
|
|
|
|
+ const uint32_t *ints,
|
|
|
|
+ const int32_t *fds)
|
|
|
|
+{
|
|
|
|
+ static const xcb_protocol_request_t xcb_req = {
|
|
|
|
+ .count = 4,
|
|
|
|
+ .ext = &xcb_drihybris_id,
|
|
|
|
+ .opcode = XCB_DRIHYBRIS_PIXMAP_FROM_BUFFER,
|
|
|
|
+ .isvoid = 1
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ struct iovec xcb_parts[6];
|
|
|
|
+ xcb_void_cookie_t xcb_ret;
|
|
|
|
+ xcb_drihybris_pixmap_from_buffer_request_t xcb_out;
|
|
|
|
+
|
|
|
|
+ xcb_out.pixmap = pixmap;
|
|
|
|
+ xcb_out.drawable = drawable;
|
|
|
|
+ xcb_out.size = size;
|
|
|
|
+ xcb_out.width = width;
|
|
|
|
+ xcb_out.height = height;
|
|
|
|
+ xcb_out.stride = stride;
|
|
|
|
+ xcb_out.depth = depth;
|
|
|
|
+ xcb_out.bpp = bpp;
|
|
|
|
+ xcb_out.num_ints = num_ints;
|
|
|
|
+ xcb_out.num_fds = num_fds;
|
|
|
|
+
|
|
|
|
+ xcb_parts[2].iov_base = (char *) &xcb_out;
|
|
|
|
+ xcb_parts[2].iov_len = sizeof(xcb_out);
|
|
|
|
+ xcb_parts[3].iov_base = 0;
|
|
|
|
+ xcb_parts[3].iov_len = -xcb_parts[2].iov_len & 3;
|
|
|
|
+ /* uint32_t ints */
|
|
|
|
+ xcb_parts[4].iov_base = (char *) ints;
|
|
|
|
+ xcb_parts[4].iov_len = num_ints * sizeof(uint32_t);
|
|
|
|
+ xcb_parts[5].iov_base = 0;
|
|
|
|
+ xcb_parts[5].iov_len = -xcb_parts[4].iov_len & 3;
|
|
|
|
+
|
|
|
|
+ xcb_ret.sequence = xcb_send_request_with_fds(c, XCB_REQUEST_CHECKED, xcb_parts + 2, &xcb_req, num_fds, fds);
|
|
|
|
+ return xcb_ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+xcb_void_cookie_t
|
|
|
|
+xcb_drihybris_pixmap_from_buffer (xcb_connection_t *c,
|
|
|
|
+ xcb_pixmap_t pixmap,
|
|
|
|
+ xcb_drawable_t drawable,
|
|
|
|
+ uint32_t size,
|
|
|
|
+ uint16_t width,
|
|
|
|
+ uint16_t height,
|
|
|
|
+ uint16_t stride,
|
|
|
|
+ uint8_t depth,
|
|
|
|
+ uint8_t bpp,
|
|
|
|
+ uint16_t num_ints,
|
|
|
|
+ uint16_t num_fds,
|
|
|
|
+ const uint32_t *ints,
|
|
|
|
+ const int32_t *fds)
|
|
|
|
+{
|
|
|
|
+ static const xcb_protocol_request_t xcb_req = {
|
|
|
|
+ .count = 4,
|
|
|
|
+ .ext = &xcb_drihybris_id,
|
|
|
|
+ .opcode = XCB_DRIHYBRIS_PIXMAP_FROM_BUFFER,
|
|
|
|
+ .isvoid = 1
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ struct iovec xcb_parts[6];
|
|
|
|
+ xcb_void_cookie_t xcb_ret;
|
|
|
|
+ xcb_drihybris_pixmap_from_buffer_request_t xcb_out;
|
|
|
|
+
|
|
|
|
+ xcb_out.pixmap = pixmap;
|
|
|
|
+ xcb_out.drawable = drawable;
|
|
|
|
+ xcb_out.size = size;
|
|
|
|
+ xcb_out.width = width;
|
|
|
|
+ xcb_out.height = height;
|
|
|
|
+ xcb_out.stride = stride;
|
|
|
|
+ xcb_out.depth = depth;
|
|
|
|
+ xcb_out.bpp = bpp;
|
|
|
|
+ xcb_out.num_ints = num_ints;
|
|
|
|
+ xcb_out.num_fds = num_fds;
|
|
|
|
+
|
|
|
|
+ xcb_parts[2].iov_base = (char *) &xcb_out;
|
|
|
|
+ xcb_parts[2].iov_len = sizeof(xcb_out);
|
|
|
|
+ xcb_parts[3].iov_base = 0;
|
|
|
|
+ xcb_parts[3].iov_len = -xcb_parts[2].iov_len & 3;
|
|
|
|
+ /* uint32_t ints */
|
|
|
|
+ xcb_parts[4].iov_base = (char *) ints;
|
|
|
|
+ xcb_parts[4].iov_len = num_ints * sizeof(uint32_t);
|
|
|
|
+ xcb_parts[5].iov_base = 0;
|
|
|
|
+ xcb_parts[5].iov_len = -xcb_parts[4].iov_len & 3;
|
|
|
|
+
|
|
|
|
+ xcb_ret.sequence = xcb_send_request_with_fds(c, 0, xcb_parts + 2, &xcb_req, num_fds, fds);
|
|
|
|
+ return xcb_ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+uint32_t *
|
|
|
|
+xcb_drihybris_pixmap_from_buffer_ints (const xcb_drihybris_pixmap_from_buffer_request_t *R)
|
|
|
|
+{
|
|
|
|
+ return (uint32_t *) (R + 1);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int
|
|
|
|
+xcb_drihybris_pixmap_from_buffer_ints_length (const xcb_drihybris_pixmap_from_buffer_request_t *R)
|
|
|
|
+{
|
|
|
|
+ return R->num_ints;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+xcb_generic_iterator_t
|
|
|
|
+xcb_drihybris_pixmap_from_buffer_ints_end (const xcb_drihybris_pixmap_from_buffer_request_t *R)
|
|
|
|
+{
|
|
|
|
+ xcb_generic_iterator_t i;
|
|
|
|
+ i.data = ((uint32_t *) (R + 1)) + (R->num_ints);
|
|
|
|
+ i.rem = 0;
|
|
|
|
+ i.index = (char *) i.data - (char *) R;
|
|
|
|
+ return i;
|
|
|
|
+}
|
|
|
|
diff --git a/hybris/egl/platforms/x11/xcb_drihybris.h b/hybris/egl/platforms/x11/xcb_drihybris.h
|
|
|
|
new file mode 100644
|
|
|
|
index 0000000..974828d
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/hybris/egl/platforms/x11/xcb_drihybris.h
|
|
|
|
@@ -0,0 +1,122 @@
|
|
|
|
+/*
|
|
|
|
+ * This file generated automatically from drihybris.xml by c_client.py.
|
|
|
|
+ * Edit at your peril.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * @defgroup XCB_DRIHYBRIS_API XCB DRIHYBRIS API
|
|
|
|
+ * @brief DRIHYBRIS XCB Protocol Implementation.
|
|
|
|
+ * @{
|
|
|
|
+ **/
|
|
|
|
+
|
|
|
|
+#ifndef DRIHYBRIS_PROTO_H
|
|
|
|
+#define DRIHYBRIS_PROTO_H
|
|
|
|
+
|
|
|
|
+#include <xcb/xcb.h>
|
|
|
|
+#include <xcb/xcbext.h>
|
|
|
|
+
|
|
|
|
+#ifdef __cplusplus
|
|
|
|
+extern "C" {
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+#define XCB_DRIHYBRIS_MAJOR_VERSION 1
|
|
|
|
+#define XCB_DRIHYBRIS_MINOR_VERSION 0
|
|
|
|
+
|
|
|
|
+extern xcb_extension_t xcb_drihybris_id;
|
|
|
|
+
|
|
|
|
+/** Opcode for xcb_drihybris_pixmap_from_buffer. */
|
|
|
|
+#define XCB_DRIHYBRIS_PIXMAP_FROM_BUFFER 1
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * @brief xcb_drihybris_pixmap_from_buffer_request_t
|
|
|
|
+ **/
|
|
|
|
+typedef struct xcb_drihybris_pixmap_from_buffer_request_t {
|
|
|
|
+ uint8_t major_opcode;
|
|
|
|
+ uint8_t minor_opcode;
|
|
|
|
+ uint16_t length;
|
|
|
|
+ xcb_pixmap_t pixmap;
|
|
|
|
+ xcb_drawable_t drawable;
|
|
|
|
+ uint32_t size;
|
|
|
|
+ uint16_t width;
|
|
|
|
+ uint16_t height;
|
|
|
|
+ uint16_t stride;
|
|
|
|
+ uint8_t depth;
|
|
|
|
+ uint8_t bpp;
|
|
|
|
+ uint16_t num_ints;
|
|
|
|
+ uint16_t num_fds;
|
|
|
|
+} xcb_drihybris_pixmap_from_buffer_request_t;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * @brief xcb_drihybris_buffer_from_pixmap_cookie_t
|
|
|
|
+ **/
|
|
|
|
+typedef struct xcb_drihybris_buffer_from_pixmap_cookie_t {
|
|
|
|
+ unsigned int sequence;
|
|
|
|
+} xcb_drihybris_buffer_from_pixmap_cookie_t;
|
|
|
|
+
|
|
|
|
+int
|
|
|
|
+xcb_drihybris_pixmap_from_buffer_sizeof (const void *_buffer,
|
|
|
|
+ int32_t pixmap_fd);
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ *
|
|
|
|
+ * @param c The connection
|
|
|
|
+ * @return A cookie
|
|
|
|
+ *
|
|
|
|
+ * Delivers a request to the X server.
|
|
|
|
+ *
|
|
|
|
+ * This form can be used only if the request will not cause
|
|
|
|
+ * a reply to be generated. Any returned error will be
|
|
|
|
+ * saved for handling by xcb_request_check().
|
|
|
|
+ */
|
|
|
|
+xcb_void_cookie_t
|
|
|
|
+xcb_drihybris_pixmap_from_buffer_checked (xcb_connection_t *c,
|
|
|
|
+ xcb_pixmap_t pixmap,
|
|
|
|
+ xcb_drawable_t drawable,
|
|
|
|
+ uint32_t size,
|
|
|
|
+ uint16_t width,
|
|
|
|
+ uint16_t height,
|
|
|
|
+ uint16_t stride,
|
|
|
|
+ uint8_t depth,
|
|
|
|
+ uint8_t bpp,
|
|
|
|
+ uint16_t num_ints,
|
|
|
|
+ uint16_t num_fds,
|
|
|
|
+ const uint32_t *ints,
|
|
|
|
+ const int32_t *fds);
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ *
|
|
|
|
+ * @param c The connection
|
|
|
|
+ * @return A cookie
|
|
|
|
+ *
|
|
|
|
+ * Delivers a request to the X server.
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+xcb_void_cookie_t
|
|
|
|
+xcb_drihybris_pixmap_from_buffer (xcb_connection_t *c,
|
|
|
|
+ xcb_pixmap_t pixmap,
|
|
|
|
+ xcb_drawable_t drawable,
|
|
|
|
+ uint32_t size,
|
|
|
|
+ uint16_t width,
|
|
|
|
+ uint16_t height,
|
|
|
|
+ uint16_t stride,
|
|
|
|
+ uint8_t depth,
|
|
|
|
+ uint8_t bpp,
|
|
|
|
+ uint16_t num_ints,
|
|
|
|
+ uint16_t num_fds,
|
|
|
|
+ const uint32_t *ints,
|
|
|
|
+ const int32_t *fds);
|
|
|
|
+
|
|
|
|
+uint32_t *
|
|
|
|
+xcb_drihybris_pixmap_from_buffer_ints (const xcb_drihybris_pixmap_from_buffer_request_t *R);
|
|
|
|
+
|
|
|
|
+int
|
|
|
|
+xcb_drihybris_pixmap_from_buffer_ints_length (const xcb_drihybris_pixmap_from_buffer_request_t *R);
|
|
|
|
+
|
|
|
|
+xcb_generic_iterator_t
|
|
|
|
+xcb_drihybris_pixmap_from_buffer_ints_end (const xcb_drihybris_pixmap_from_buffer_request_t *R);
|
|
|
|
+
|
|
|
|
+#ifdef __cplusplus
|
|
|
|
+}
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+#endif //DRIHYBRIS_PROTO_H
|
|
|
|
diff --git a/hybris/egl/ws.c b/hybris/egl/ws.c
|
|
|
|
index b10c704..36bc180 100644
|
|
|
|
--- a/hybris/egl/ws.c
|
|
|
|
+++ b/hybris/egl/ws.c
|
|
|
|
@@ -118,4 +118,13 @@ void ws_setSwapInterval(EGLDisplay dpy, EGLNativeWindowType win, EGLint interval
|
|
|
|
ws->setSwapInterval(dpy, win, interval);
|
|
|
|
}
|
|
|
|
|
|
|
|
+EGLBoolean ws_eglGetConfigAttrib(struct _EGLDisplay *display, EGLConfig config, EGLint attribute, EGLint *value)
|
|
|
|
+{
|
|
|
|
+ _init_ws();
|
|
|
|
+ if (ws->eglGetConfigAttrib)
|
|
|
|
+ return ws->eglGetConfigAttrib(display, config, attribute, value);
|
|
|
|
+ else
|
|
|
|
+ return EGL_FALSE;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
// vim:ts=4:sw=4:noexpandtab
|
|
|
|
diff --git a/hybris/egl/ws.h b/hybris/egl/ws.h
|
|
|
|
index c4811c0..92b221a 100644
|
|
|
|
--- a/hybris/egl/ws.h
|
|
|
|
+++ b/hybris/egl/ws.h
|
|
|
|
@@ -37,6 +37,7 @@ struct ws_module {
|
|
|
|
void (*prepareSwap)(EGLDisplay dpy, EGLNativeWindowType win, EGLint *damage_rects, EGLint damage_n_rects);
|
|
|
|
void (*finishSwap)(EGLDisplay dpy, EGLNativeWindowType win);
|
|
|
|
void (*setSwapInterval)(EGLDisplay dpy, EGLNativeWindowType win, EGLint interval);
|
|
|
|
+ EGLBoolean (*eglGetConfigAttrib)(struct _EGLDisplay *display, EGLConfig config, EGLint attribute, EGLint *value);
|
|
|
|
};
|
|
|
|
|
|
|
|
struct _EGLDisplay *ws_GetDisplay(EGLNativeDisplayType native);
|
|
|
|
@@ -49,5 +50,6 @@ const char *ws_eglQueryString(EGLDisplay dpy, EGLint name, const char *(*real_eg
|
|
|
|
void ws_prepareSwap(EGLDisplay dpy, EGLNativeWindowType win, EGLint *damage_rects, EGLint damage_n_rects);
|
|
|
|
void ws_finishSwap(EGLDisplay dpy, EGLNativeWindowType win);
|
|
|
|
void ws_setSwapInterval(EGLDisplay dpy, EGLNativeWindowType win, EGLint interval);
|
|
|
|
+EGLBoolean ws_eglGetConfigAttrib(struct _EGLDisplay *display, EGLConfig config, EGLint attribute, EGLint *value);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
--
|
2019-06-08 10:54:50 +00:00
|
|
|
2.21.0
|
2018-04-10 21:13:42 +00:00
|
|
|
|