Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

tests: first work regarding a working offscreen window rendering impl…

…ementation
  • Loading branch information...
commit 33b1d293846b74e3fa2a2f30d2959ad1b1ef01b3 1 parent fad08c0
@heeen heeen authored Carsten Munk committed
View
1  configure.ac
@@ -14,6 +14,7 @@ m4_define([hybris_lt_revision], [0])
m4_define([hybris_lt_age], [0])
AC_PROG_CC
+AC_PROG_CXX
AM_PROG_CC_C_O
AC_GNU_SOURCE
AC_DISABLE_STATIC
View
17 support.h
@@ -0,0 +1,17 @@
+#ifndef SUPPORT_H_
+#define SUPPORT_H_
+
+#include <stddef.h>
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+#endif
View
17 tests/Makefile.am
@@ -1,6 +1,7 @@
bin_PROGRAMS = \
test_egl \
- test_glesv2
+ test_glesv2 \
+ test_offscreen_rendering
test_egl_SOURCES = test_egl.c
test_egl_CFLAGS = \
@@ -17,3 +18,17 @@ test_glesv2_LDADD = \
$(top_builddir)/common/libhybris-common.la \
$(top_builddir)/egl/libEGL.la \
$(top_builddir)/glesv2/libGLESv2.la
+
+test_offscreen_rendering_SOURCES = \
+ test_offscreen_rendering.cpp \
+ fbdev_window.cpp \
+ nativewindowbase.cpp \
+ offscreen_window.cpp
+test_offscreen_rendering_CXXFLAGS = \
+ -I$(top_srcdir)/include
+test_offscreen_rendering_LDADD = \
+ -lm \
+ $(top_builddir)/common/libhybris-common.la \
+ $(top_builddir)/hardware/libhardware.la \
+ $(top_builddir)/egl/libEGL.la \
+ $(top_builddir)/glesv2/libGLESv2.la
View
135 tests/fbdev_window.cpp
@@ -0,0 +1,135 @@
+#include "fbdev_window.h"
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <linux/matroxfb.h> // for FBIO_WAITFORVSYNC
+#include <sys/mman.h> //mmap, munmap
+#include <errno.h>
+
+
+FbDevNativeWindow::FbDevNativeWindow()
+{
+ hw_module_t const* pmodule = NULL;
+ hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &pmodule);
+ int err = framebuffer_open(pmodule, &m_fbDev);
+ printf("open framebuffer HAL (%s) format %i", strerror(-err), m_fbDev->format);
+
+ err = gralloc_open(pmodule, &m_gralloc);
+ printf("got gralloc %p err:%s\n", m_gralloc, strerror(-err));
+
+ for(unsigned int i = 0; i < FRAMEBUFFER_PARTITIONS; i++) {
+ m_buffers[i] = new FbDevNativeWindowBuffer(width(), height(), m_fbDev->format, GRALLOC_USAGE_HW_FB);
+
+ int err = m_gralloc->alloc(m_gralloc,
+ width(), height(), m_fbDev->format,
+ GRALLOC_USAGE_HW_FB,
+ &m_buffers[i]->handle,
+ &m_buffers[i]->stride);
+ printf("buffer %i is at %p (native %p) err=%s handle=%i stride=%i\n",
+ i, m_buffers[i], (ANativeWindowBuffer*) m_buffers[i],
+ strerror(-err), m_buffers[i]->handle, m_buffers[i]->stride);
+
+
+ }
+ m_frontbuffer = 0;
+ m_tailbuffer = 1;
+
+
+}
+
+FbDevNativeWindow::~FbDevNativeWindow() {
+ printf("%s\n",__PRETTY_FUNCTION__);
+}
+
+// overloads from BaseNativeWindow
+int FbDevNativeWindow::setSwapInterval(int interval) {
+ printf("%s\n",__PRETTY_FUNCTION__);
+ return 0;
+}
+
+int FbDevNativeWindow::dequeueBuffer(BaseNativeWindowBuffer **buffer){
+ printf("%s\n",__PRETTY_FUNCTION__);
+ *buffer = m_buffers[m_tailbuffer];
+ printf("dequeueing buffer %i %p",m_tailbuffer, m_buffers[m_tailbuffer]);
+ m_tailbuffer++;
+ if(m_tailbuffer == FRAMEBUFFER_PARTITIONS)
+ m_tailbuffer = 0;
+ return NO_ERROR;
+}
+
+int FbDevNativeWindow::lockBuffer(BaseNativeWindowBuffer* buffer){
+ printf("%s\n",__PRETTY_FUNCTION__);
+ return NO_ERROR;
+}
+
+int FbDevNativeWindow::queueBuffer(BaseNativeWindowBuffer* buffer){
+ FbDevNativeWindowBuffer* buf = static_cast<FbDevNativeWindowBuffer*>(buffer);
+ m_frontbuffer++;
+ if(m_frontbuffer == FRAMEBUFFER_PARTITIONS)
+ m_frontbuffer = 0;
+ int res = m_fbDev->post(m_fbDev, buffer->handle);
+ printf("%s %s\n",__PRETTY_FUNCTION__,strerror(res));
+ return NO_ERROR;
+}
+
+int FbDevNativeWindow::cancelBuffer(BaseNativeWindowBuffer* buffer){
+ printf("%s\n",__PRETTY_FUNCTION__);
+ return 0;
+}
+
+unsigned int FbDevNativeWindow::width() const {
+ unsigned int val = m_fbDev->width;
+ printf("%s value: %i\n",__PRETTY_FUNCTION__, val);
+ return val;
+}
+
+unsigned int FbDevNativeWindow::height() const {
+ unsigned int val = m_fbDev->height;
+ printf("%s value: %i\n",__PRETTY_FUNCTION__, val);
+ return val;
+}
+
+unsigned int FbDevNativeWindow::format() const {
+ unsigned int val = m_fbDev->format;
+ printf("%s value: %i\n",__PRETTY_FUNCTION__, val);
+ return val;
+}
+
+unsigned int FbDevNativeWindow::defaultWidth() const {
+ printf("%s\n",__PRETTY_FUNCTION__);
+ return m_fbDev->width;
+}
+
+unsigned int FbDevNativeWindow::defaultHeight() const {
+ printf("%s\n",__PRETTY_FUNCTION__);
+ return m_fbDev->height;
+}
+
+unsigned int FbDevNativeWindow::queueLength() const {
+ printf("%s\n",__PRETTY_FUNCTION__);
+ return 0;
+}
+
+unsigned int FbDevNativeWindow::type() const {
+ printf("%s\n",__PRETTY_FUNCTION__);
+ return NATIVE_WINDOW_FRAMEBUFFER;
+}
+
+unsigned int FbDevNativeWindow::transformHint() const {
+ printf("%s\n",__PRETTY_FUNCTION__);
+ return 0;
+}
+
+int FbDevNativeWindow::setUsage(int usage) {
+ printf("%s usage %i\n",__PRETTY_FUNCTION__, usage);
+ return NO_ERROR;
+}
+
+int FbDevNativeWindow::setBuffersFormat(int format) {
+ printf("%s format %i\n",__PRETTY_FUNCTION__, format);
+ return NO_ERROR;
+}
+
+int FbDevNativeWindow::setBuffersDimensions(int width, int height) {
+ printf("%s size %ix%i\n",__PRETTY_FUNCTION__, width, height);
+ return NO_ERROR;
+}
View
56 tests/fbdev_window.h
@@ -0,0 +1,56 @@
+#ifndef FBDEV_WINDOW_H
+#define FBDEV_WINDOW_H
+#include "nativewindowbase.h"
+#include <linux/fb.h>
+#include <hardware/gralloc.h>
+#define FRAMEBUFFER_PARTITIONS 2
+
+class FbDevNativeWindowBuffer : public BaseNativeWindowBuffer
+{
+ friend class FbDevNativeWindow;
+ protected:
+ FbDevNativeWindowBuffer(unsigned int width,
+ unsigned int height,
+ unsigned int format,
+ unsigned int usage) {
+ // Base members
+ ANativeWindowBuffer::width = width;
+ ANativeWindowBuffer::height = height;
+ ANativeWindowBuffer::format = format;
+ ANativeWindowBuffer::usage = usage;
+ };
+};
+
+class FbDevNativeWindow : public BaseNativeWindow
+{
+public:
+ FbDevNativeWindow();
+ ~FbDevNativeWindow();
+protected:
+ // overloads from BaseNativeWindow
+ virtual int setSwapInterval(int interval);
+ virtual int dequeueBuffer(BaseNativeWindowBuffer **buffer);
+ virtual int lockBuffer(BaseNativeWindowBuffer* buffer);
+ virtual int queueBuffer(BaseNativeWindowBuffer* buffer);
+ virtual int cancelBuffer(BaseNativeWindowBuffer* buffer);
+ 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;
+ // perform calls
+ virtual int setUsage(int usage);
+ virtual int setBuffersFormat(int format);
+ virtual int setBuffersDimensions(int width, int height);
+private:
+ unsigned int m_frontbuffer;
+ unsigned int m_tailbuffer;
+ FbDevNativeWindowBuffer* m_buffers[FRAMEBUFFER_PARTITIONS];
+ alloc_device_t* m_gralloc;
+ framebuffer_device_t* m_fbDev;
+};
+
+#endif
View
2  tests/nativewindowbase.cpp
@@ -0,0 +1,2 @@
+#include "nativewindowbase.h"
+
View
275 tests/nativewindowbase.h
@@ -0,0 +1,275 @@
+#ifndef NATIVEWINDOWBASE_H
+#define NATIVEWINDOWBASE_H
+
+#include <system/window.h>
+#include <hardware/gralloc.h>
+#include <EGL/egl.h>
+#include "support.h"
+#include <stdarg.h>
+
+#define NO_ERROR 0L
+#define BAD_VALUE -1
+
+//#define TRACE(...)
+#define TRACE printf
+/*
+ * A Class to do common ANativeBuffer initialization and thunk c-style
+ * callbacks into C++ method calls.
+ * */
+
+
+class BaseNativeWindowBuffer : public ANativeWindowBuffer {
+public:
+
+protected:
+ BaseNativeWindowBuffer() {
+ // done in ANativeWindowBuffer::ANativeWindowBuffer
+ // common.magic = ANDROID_NATIVE_WINDOW_MAGIC;
+ // common.version = sizeof(ANativeWindow);
+ // memset(common.reserved, 0, sizeof(window->native.common.reserved));
+
+ ANativeWindowBuffer::common.decRef = &_decRef;
+ ANativeWindowBuffer::common.incRef = &_incRef;
+ ANativeWindowBuffer::width = 0;
+ ANativeWindowBuffer::height = 0;
+ ANativeWindowBuffer::stride = 0;
+ ANativeWindowBuffer::format = 0;
+ ANativeWindowBuffer::usage = 0;
+ ANativeWindowBuffer::handle = 0;
+ }
+private:
+ // does this require more magic?
+ unsigned int refcount;
+ static void _decRef(struct android_native_base_t* base) {
+ ANativeWindowBuffer* self = container_of(base, ANativeWindowBuffer, common);
+ static_cast<BaseNativeWindowBuffer*>(self)->refcount--;
+ };
+ static void _incRef(struct android_native_base_t* base) {
+ ANativeWindowBuffer* self = container_of(base, ANativeWindowBuffer, common);
+ static_cast<BaseNativeWindowBuffer*>(self)->refcount++;
+ };
+};
+
+/*
+ * A Class to do common ANativeWindow initialization and thunk c-style
+ * callbacks into C++ method calls.
+ * */
+class BaseNativeWindow : public ANativeWindow {
+public:
+ operator EGLNativeWindowType()
+ {
+ EGLNativeWindowType ret = reinterpret_cast<EGLNativeWindowType>((ANativeWindow*)this);
+ TRACE("casting %p to %p\n", this, ret);
+ return ret;
+ }
+protected:
+ BaseNativeWindow()
+ {
+ TRACE("%s this=%p or A %p\n",__PRETTY_FUNCTION__, this, (ANativeWindow*)this);
+ // done in ANativeWindow
+ // common.magic = ANDROID_NATIVE_WINDOW_MAGIC;
+ // common.version = sizeof(ANativeWindow);
+ // memset(common.reserved, 0, sizeof(window->native.common.reserved));
+ common.decRef = &_decRef;
+ common.incRef = &_incRef;
+
+ ANativeWindow::flags = 0;
+ ANativeWindow::minSwapInterval = 0;
+ ANativeWindow::maxSwapInterval = 0;
+ ANativeWindow::xdpi = 0;
+ ANativeWindow::ydpi = 0;
+
+ ANativeWindow::setSwapInterval = _setSwapInterval;
+ ANativeWindow::lockBuffer = &_lockBuffer;
+ ANativeWindow::dequeueBuffer = &_dequeueBuffer;
+ ANativeWindow::queueBuffer = &_queueBuffer;
+ ANativeWindow::query = &_query;
+ ANativeWindow::perform = &_perform;
+ ANativeWindow::cancelBuffer = &_cancelBuffer;
+ refcount = 0;
+ };
+
+ ~BaseNativeWindow()
+ {
+ TRACE("%s\n",__PRETTY_FUNCTION__);
+ };
+
+ // does this require more magic?
+ unsigned int refcount;
+ static void _decRef(struct android_native_base_t* base) {
+ ANativeWindow* self = container_of(base, ANativeWindow, common);
+ static_cast<BaseNativeWindow*>(self)->refcount--;
+ };
+ static void _incRef(struct android_native_base_t* base) {
+ ANativeWindow* self = container_of(base, ANativeWindow, common);
+ static_cast<BaseNativeWindow*>(self)->refcount++;
+ };
+
+ // these have to be implemented in the concrete implementation, eg. FBDEV or offscreen window
+ virtual int setSwapInterval(int interval) = 0;
+ virtual int dequeueBuffer(BaseNativeWindowBuffer **buffer) = 0;
+ virtual int lockBuffer(BaseNativeWindowBuffer* buffer) = 0;
+ virtual int queueBuffer(BaseNativeWindowBuffer* buffer) = 0;
+ virtual int cancelBuffer(BaseNativeWindowBuffer* buffer) = 0;
+
+ virtual unsigned int type() const = 0;
+ virtual unsigned int width() const = 0;
+ virtual unsigned int height() const = 0;
+ virtual unsigned int format() const = 0;
+ virtual unsigned int defaultWidth() const = 0;
+ virtual unsigned int defaultHeight() const = 0;
+ virtual unsigned int queueLength() const = 0;
+ virtual unsigned int transformHint() const = 0;
+ //perform interfaces
+ virtual int setBuffersFormat(int format) = 0;
+ virtual int setBuffersDimensions(int width, int height) = 0;
+ virtual int setUsage(int usage) = 0;
+private:
+ static int _setSwapInterval(struct ANativeWindow* window, int interval)
+ {
+ TRACE("%s interval=%i\n",__PRETTY_FUNCTION__, interval);
+ return static_cast<BaseNativeWindow*>(window)->setSwapInterval(interval);
+ }
+
+ static int _dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer)
+ {
+ TRACE("%s pointer dest %p, contains %p\n",__PRETTY_FUNCTION__, buffer, *buffer);
+ BaseNativeWindowBuffer* temp = static_cast<BaseNativeWindowBuffer*>(*buffer);
+ TRACE("temp %p\n", temp);
+ int ret = static_cast<BaseNativeWindow*>(window)->dequeueBuffer(&temp);
+ TRACE("temp now %p\n", temp);
+ *buffer = static_cast<ANativeWindowBuffer*>(temp);
+ TRACE("now pointer dest %p, contains %p\n", buffer, *buffer);
+ return ret;
+ }
+
+ static int _lockBuffer(struct ANativeWindow* window, ANativeWindowBuffer* buffer)
+ {
+ TRACE("%s buffer=%p\n",__PRETTY_FUNCTION__, buffer);
+ return static_cast<BaseNativeWindow*>(window)->lockBuffer(static_cast<BaseNativeWindowBuffer*>(buffer));
+ }
+
+ static int _queueBuffer(struct ANativeWindow* window, ANativeWindowBuffer* buffer)
+ {
+ TRACE("%s buffer=%p\n",__PRETTY_FUNCTION__, buffer);
+ return static_cast<BaseNativeWindow*>(window)->queueBuffer(static_cast<BaseNativeWindowBuffer*>(buffer));
+ return 0;
+ }
+
+ static int _query(const struct ANativeWindow* window, int what, int* value)
+ {
+ printf("_query window %p %i %p\n", window, what, value);
+ const BaseNativeWindow* self=static_cast<const BaseNativeWindow*>(window);
+ switch (what) {
+ case NATIVE_WINDOW_WIDTH:
+ *value = self->width();
+ return NO_ERROR;
+ case NATIVE_WINDOW_HEIGHT:
+ *value = self->height();
+ return NO_ERROR;
+ case NATIVE_WINDOW_FORMAT:
+ *value = self->format();
+ printf("done\n");
+ return NO_ERROR;
+ case NATIVE_WINDOW_CONCRETE_TYPE:
+ *value = self->type();
+ return NO_ERROR;
+ case NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER:
+ *value = self->queueLength();
+ return NO_ERROR;
+ case NATIVE_WINDOW_DEFAULT_WIDTH:
+ *value = self->defaultWidth();
+ return NO_ERROR;
+ case NATIVE_WINDOW_DEFAULT_HEIGHT:
+ *value = self->defaultHeight();
+ return NO_ERROR;
+ case NATIVE_WINDOW_TRANSFORM_HINT:
+ *value = self->transformHint();
+ return NO_ERROR;
+ }
+ printf("huh?\n");
+ TRACE("EGL error: unkown window attribute!\n");
+ *value = 0;
+ return BAD_VALUE;
+ }
+
+ static int _perform(struct ANativeWindow* window, int operation, ... )
+ {
+ BaseNativeWindow* self = static_cast<BaseNativeWindow*>(window);
+ va_list args;
+ va_start(args, operation);
+
+ // FIXME
+ TRACE("%s operation = %i\n", __PRETTY_FUNCTION__, operation);
+ switch(operation) {
+ case NATIVE_WINDOW_SET_USAGE : // 0,
+ {
+ int usage = va_arg(args, int);
+ return self->setUsage(usage);
+ }
+ case NATIVE_WINDOW_CONNECT : // 1, /* deprecated */
+ TRACE("connect\n");
+ break;
+ case NATIVE_WINDOW_DISCONNECT : // 2, /* deprecated */
+ TRACE("disconnect\n");
+ break;
+ case NATIVE_WINDOW_SET_CROP : // 3, /* private */
+ TRACE("set crop\n");
+ break;
+ case NATIVE_WINDOW_SET_BUFFER_COUNT : // 4,
+ TRACE("set buffer count\n");
+ break;
+ case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY : // 5, /* deprecated */
+ TRACE("set buffers geometry\n");
+ break;
+ case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM : // 6,
+ TRACE("set buffers transform\n");
+ break;
+ case NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP : // 7,
+ TRACE("set buffers timestamp\n");
+ break;
+ case NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS : // 8,
+ {
+ int width = va_arg(args, int);
+ int height = va_arg(args, int);
+ return self->setBuffersDimensions(width, height);
+ }
+ case NATIVE_WINDOW_SET_BUFFERS_FORMAT : // 9,
+ {
+ int format = va_arg(args, int);
+ return self->setBuffersFormat(format);
+ }
+ case NATIVE_WINDOW_SET_SCALING_MODE : // 10, /* private */
+ TRACE("set scaling mode\n");
+ break;
+ case NATIVE_WINDOW_LOCK : // 11, /* private */
+ TRACE("window lock\n");
+ break;
+ case NATIVE_WINDOW_UNLOCK_AND_POST : // 12, /* private */
+ TRACE("unlock and post\n");
+ break;
+ case NATIVE_WINDOW_API_CONNECT : // 13, /* private */
+ TRACE("api connect\n");
+ break;
+ case NATIVE_WINDOW_API_DISCONNECT : // 14, /* private */
+ TRACE("api disconnect\n");
+ break;
+ case NATIVE_WINDOW_SET_BUFFERS_USER_DIMENSIONS : // 15, /* private */
+ TRACE("set buffers user dimensions\n");
+ break;
+ case NATIVE_WINDOW_SET_POST_TRANSFORM_CROP : // 16,
+ TRACE("set post transform crop\n");
+ break;
+ }
+ return NO_ERROR;
+ }
+
+ static int _cancelBuffer(struct ANativeWindow* window, ANativeWindowBuffer* buffer)
+ {
+ TRACE("%s buffer = %p\n",__PRETTY_FUNCTION__, buffer);
+ return static_cast<BaseNativeWindow*>(window)->cancelBuffer(static_cast<BaseNativeWindowBuffer*>(buffer));
+ }
+
+};
+
+#endif
View
251 tests/offscreen_window.cpp
@@ -0,0 +1,251 @@
+#include "offscreen_window.h"
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <linux/matroxfb.h> // for FBIO_WAITFORVSYNC
+#include <sys/mman.h> //mmap, munmap
+#include <errno.h>
+
+void printUsage(int usage) {
+ if(usage & GRALLOC_USAGE_SW_READ_NEVER) {
+ TRACE("GRALLOC_USAGE_SW_READ_NEVER | ");
+ }
+ /* buffer is rarely read in software */
+ if(usage & GRALLOC_USAGE_SW_READ_RARELY) {
+ TRACE("GRALLOC_USAGE_SW_READ_RARELY | ");
+ }
+ /* buffer is often read in software */
+ if(usage & GRALLOC_USAGE_SW_READ_OFTEN) {
+ TRACE("GRALLOC_USAGE_SW_READ_OFTEN | ");
+ }
+ /* mask for the software read values */
+ if(usage & GRALLOC_USAGE_SW_READ_MASK) {
+ TRACE("GRALLOC_USAGE_SW_READ_MASK | ");
+ }
+
+ /* buffer is never written in software */
+ if(usage & GRALLOC_USAGE_SW_WRITE_NEVER) {
+ TRACE("GRALLOC_USAGE_SW_WRITE_NEVER | ");
+ }
+ /* buffer is never written in software */
+ if(usage & GRALLOC_USAGE_SW_WRITE_RARELY) {
+ TRACE("GRALLOC_USAGE_SW_WRITE_RARELY | ");
+ }
+ /* buffer is never written in software */
+ if(usage & GRALLOC_USAGE_SW_WRITE_OFTEN) {
+ TRACE("GRALLOC_USAGE_SW_WRITE_OFTEN | ");
+ }
+ /* mask for the software write values */
+ if(usage & GRALLOC_USAGE_SW_WRITE_MASK) {
+ TRACE("GRALLOC_USAGE_SW_WRITE_MASK | ");
+ }
+
+ /* buffer will be used as an OpenGL ES texture */
+ if(usage & GRALLOC_USAGE_HW_TEXTURE) {
+ TRACE("GRALLOC_USAGE_HW_TEXTURE | ");
+ }
+ /* buffer will be used as an OpenGL ES render target */
+ if(usage & GRALLOC_USAGE_HW_RENDER) {
+ TRACE("GRALLOC_USAGE_HW_RENDER | ");
+ }
+ /* buffer will be used by the 2D hardware blitter */
+ if(usage & GRALLOC_USAGE_HW_2D) {
+ TRACE("GRALLOC_USAGE_HW_2D | ");
+ }
+ /* buffer will be used by the HWComposer HAL module */
+ if(usage & GRALLOC_USAGE_HW_COMPOSER) {
+ TRACE("GRALLOC_USAGE_HW_COMPOSER | ");
+ }
+ /* buffer will be used with the framebuffer device */
+ if(usage & GRALLOC_USAGE_HW_FB) {
+ TRACE("GRALLOC_USAGE_HW_FB | ");
+ }
+ /* buffer will be used with the HW video encoder */
+ if(usage & GRALLOC_USAGE_HW_VIDEO_ENCODER) {
+ TRACE("GRALLOC_USAGE_HW_VIDEO_ENCODER | ");
+ }
+ /* mask for the software usage bit-mask */
+ if(usage & GRALLOC_USAGE_HW_MASK) {
+ TRACE("GRALLOC_USAGE_HW_MASK | ");
+ }
+
+ /* buffer should be displayed full-screen on an external display when
+ * possible
+ */
+ if(usage & GRALLOC_USAGE_EXTERNAL_DISP) {
+ TRACE("GRALLOC_USAGE_EXTERNAL_DISP | ");
+ }
+
+ /* Must have a hardware-protected path to external display sink for
+ * this buffer. If a hardware-protected path is not available, then
+ * either don't composite only this buffer (preferred) to the
+ * external sink, or (less desirable) do not route the entire
+ * composition to the external sink.
+ */
+ if(usage & GRALLOC_USAGE_PROTECTED) {
+ TRACE("GRALLOC_USAGE_PROTECTED | ");
+ }
+
+ /* implementation-specific private usage flags */
+ if(usage & GRALLOC_USAGE_PRIVATE_0) {
+ TRACE("GRALLOC_USAGE_PRIVATE_0 | ");
+ }
+ if(usage & GRALLOC_USAGE_PRIVATE_1) {
+ TRACE("GRALLOC_USAGE_PRIVATE_1 | ");
+ }
+ if(usage & GRALLOC_USAGE_PRIVATE_2) {
+ TRACE("GRALLOC_USAGE_PRIVATE_2 |");
+ }
+ if(usage & GRALLOC_USAGE_PRIVATE_3) {
+ TRACE("GRALLOC_USAGE_PRIVATE_3 |");
+ }
+ if(usage & GRALLOC_USAGE_PRIVATE_MASK) {
+ TRACE("GRALLOC_USAGE_PRIVATE_MASK |");
+ }
+ printf("\n");
+}
+
+OffscreenNativeWindow::OffscreenNativeWindow(unsigned int aWidth, unsigned int aHeight, unsigned int aFormat)
+ : m_width(aWidth)
+ , m_height(aHeight)
+ , m_defaultWidth(aWidth)
+ , m_defaultHeight(aHeight)
+ , m_format(aFormat)
+{
+ hw_get_module(GRALLOC_HARDWARE_MODULE_ID, (const hw_module_t**)&m_gralloc);
+ m_usage=GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_2D | GRALLOC_USAGE_SW_READ_RARELY;
+ int err = gralloc_open((hw_module_t*)m_gralloc, &m_alloc);
+ TRACE("got alloc %p err:%s\n", m_alloc, strerror(-err));
+
+ for(unsigned int i = 0; i < NUM_BUFFERS; i++) {
+ m_buffers[i] = 0;
+ }
+ m_frontbuffer = 0;
+ m_tailbuffer = 0;
+}
+
+OffscreenNativeWindow::~OffscreenNativeWindow() {
+ TRACE("%s\n",__PRETTY_FUNCTION__);
+}
+OffscreenNativeWindowBuffer* OffscreenNativeWindow::getFrontBuffer() {
+ return m_buffers[m_frontbuffer];
+}
+
+// overloads from BaseNativeWindow
+int OffscreenNativeWindow::setSwapInterval(int interval) {
+ TRACE("%s\n",__PRETTY_FUNCTION__);
+ return 0;
+}
+
+int OffscreenNativeWindow::dequeueBuffer(BaseNativeWindowBuffer **buffer){
+ TRACE("%s ===================================\n",__PRETTY_FUNCTION__);
+ if(m_buffers[m_tailbuffer] == 0) {
+ m_buffers[m_tailbuffer] = new OffscreenNativeWindowBuffer(width(), height(), m_format, m_usage);
+ int usage = m_buffers[m_tailbuffer]->usage;
+ printf("alloc usage: ");
+ printUsage(usage);
+ int err = m_alloc->alloc(m_alloc,
+ width(), height(), m_format,
+ usage,
+ &m_buffers[m_tailbuffer]->handle,
+ &m_buffers[m_tailbuffer]->stride);
+ TRACE("buffer %i is at %p (native %p) err=%s handle=%i stride=%i\n",
+ m_tailbuffer, m_buffers[m_tailbuffer], (ANativeWindowBuffer*) m_buffers[m_tailbuffer],
+ strerror(-err), m_buffers[m_tailbuffer]->handle, m_buffers[m_tailbuffer]->stride);
+ }
+ *buffer = m_buffers[m_tailbuffer];
+ TRACE("dequeueing buffer %i %p\n",m_tailbuffer, m_buffers[m_tailbuffer]);
+ m_tailbuffer++;
+ if(m_tailbuffer == NUM_BUFFERS)
+ m_tailbuffer = 0;
+ return NO_ERROR;
+}
+
+int OffscreenNativeWindow::lockBuffer(BaseNativeWindowBuffer* buffer){
+ TRACE("%s ===================\n",__PRETTY_FUNCTION__);
+/* OffscreenNativeWindowBuffer *buf = static_cast<OffscreenNativeWindowBuffer*>(buffer);
+ int usage = buf->usage | GRALLOC_USAGE_SW_READ_RARELY;
+ printf("lock usage: ");
+ printUsage(usage);
+ int err = m_gralloc->lock(m_gralloc,
+ buf->handle,
+ usage,
+ 0,0, m_width, m_height,
+ &buf->vaddr
+ );
+ TRACE("lock %s vaddr %p\n", strerror(-err), buf->vaddr);
+ return err;*/
+ return NO_ERROR;
+}
+
+int OffscreenNativeWindow::queueBuffer(BaseNativeWindowBuffer* buffer){
+ OffscreenNativeWindowBuffer* buf = static_cast<OffscreenNativeWindowBuffer*>(buffer);
+ m_frontbuffer++;
+ if(m_frontbuffer == NUM_BUFFERS)
+ m_frontbuffer = 0;
+ int res = 0;
+ //fixme
+ TRACE("%s %s =+++++=====================================\n",__PRETTY_FUNCTION__,strerror(-res));
+ return NO_ERROR;
+}
+
+int OffscreenNativeWindow::cancelBuffer(BaseNativeWindowBuffer* buffer){
+ TRACE("%s\n",__PRETTY_FUNCTION__);
+ return 0;
+}
+
+unsigned int OffscreenNativeWindow::width() const {
+ TRACE("%s value: %i\n",__PRETTY_FUNCTION__, m_width);
+ return m_width;
+}
+
+unsigned int OffscreenNativeWindow::height() const {
+ TRACE("%s value: %i\n",__PRETTY_FUNCTION__, m_height);
+ return m_height;
+}
+
+unsigned int OffscreenNativeWindow::format() const {
+ TRACE("%s value: %i\n",__PRETTY_FUNCTION__, m_format);
+ return m_format;
+}
+
+unsigned int OffscreenNativeWindow::defaultWidth() const {
+ TRACE("%s value: %i\n",__PRETTY_FUNCTION__, m_defaultWidth);
+ return m_defaultWidth;
+}
+
+unsigned int OffscreenNativeWindow::defaultHeight() const {
+ TRACE("%s value: %i\n",__PRETTY_FUNCTION__, m_defaultHeight);
+ return m_defaultHeight;
+}
+
+unsigned int OffscreenNativeWindow::queueLength() const {
+ TRACE("%s\n",__PRETTY_FUNCTION__);
+ return 1;
+}
+
+unsigned int OffscreenNativeWindow::type() const {
+ TRACE("%s\n",__PRETTY_FUNCTION__);
+ return NATIVE_WINDOW_SURFACE_TEXTURE_CLIENT;
+}
+
+unsigned int OffscreenNativeWindow::transformHint() const {
+ TRACE("%s\n",__PRETTY_FUNCTION__);
+ return 0;
+}
+
+int OffscreenNativeWindow::setBuffersFormat(int format) {
+ TRACE("%s format %i\n",__PRETTY_FUNCTION__, format);
+ return NO_ERROR;
+}
+
+int OffscreenNativeWindow::setBuffersDimensions(int width, int height) {
+ TRACE("%s size %ix%i\n",__PRETTY_FUNCTION__, width, height);
+ return NO_ERROR;
+}
+
+int OffscreenNativeWindow::setUsage(int usage) {
+ TRACE("%s\n",__PRETTY_FUNCTION__);
+ printUsage(usage);
+ m_usage = usage;
+ return NO_ERROR;
+}
View
65 tests/offscreen_window.h
@@ -0,0 +1,65 @@
+#ifndef Offscreen_WINDOW_H
+#define Offscreen_WINDOW_H
+#include "nativewindowbase.h"
+#include <linux/fb.h>
+#include <hardware/gralloc.h>
+#define NUM_BUFFERS 3
+
+class OffscreenNativeWindowBuffer : public BaseNativeWindowBuffer
+{
+ friend class OffscreenNativeWindow;
+ protected:
+ OffscreenNativeWindowBuffer(unsigned int width,
+ unsigned int height,
+ unsigned int format,
+ unsigned int usage) {
+ // Base members
+ ANativeWindowBuffer::width = width;
+ ANativeWindowBuffer::height = height;
+ ANativeWindowBuffer::format = format;
+ ANativeWindowBuffer::usage = usage;
+ };
+ void* vaddr;
+};
+
+class OffscreenNativeWindow : public BaseNativeWindow
+{
+public:
+ OffscreenNativeWindow(unsigned int width, unsigned int height, unsigned int format = 5);
+ ~OffscreenNativeWindow();
+ OffscreenNativeWindowBuffer* getFrontBuffer();
+protected:
+ // overloads from BaseNativeWindow
+ virtual int setSwapInterval(int interval);
+ virtual int dequeueBuffer(BaseNativeWindowBuffer **buffer);
+ virtual int lockBuffer(BaseNativeWindowBuffer* buffer);
+ virtual int queueBuffer(BaseNativeWindowBuffer* buffer);
+ virtual int cancelBuffer(BaseNativeWindowBuffer* buffer);
+ 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;
+ // perform calls
+ virtual int setUsage(int usage);
+ virtual int setBuffersFormat(int format);
+ virtual int setBuffersDimensions(int width, int height);
+private:
+ unsigned int m_frontbuffer;
+ unsigned int m_tailbuffer;
+ unsigned int m_width;
+ unsigned int m_height;
+ unsigned int m_format;
+ unsigned int m_defaultWidth;
+ unsigned int m_defaultHeight;
+ unsigned int m_usage;
+ OffscreenNativeWindowBuffer* m_buffers[NUM_BUFFERS];
+ alloc_device_t* m_alloc;
+ const gralloc_module_t* m_gralloc;
+
+};
+
+#endif
View
17 tests/support.h
@@ -0,0 +1,17 @@
+#ifndef SUPPORT_H_
+#define SUPPORT_H_
+
+#include <stddef.h>
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+#endif
View
322 tests/test_offscreen_rendering.cpp
@@ -0,0 +1,322 @@
+#define MESA_EGL_NO_X11_HEADERS
+//#define EGL_EGLEXT_PROTOTYPES
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <assert.h>
+#include <stdio.h>
+#include <malloc.h>
+#include <stdlib.h>
+#include "fbdev_window.h"
+#include "offscreen_window.h"
+PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR=0;
+PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR=0;
+PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES=0;
+
+static void checkGlError(const char* op) {
+ for (GLint error = glGetError(); error; error
+ = glGetError()) {
+ fprintf(stderr, "after %s() glError (0x%x)\n", op, error);
+ }
+}
+
+static const char gVertexShader[] =
+ "attribute vec4 vPosition;\n"
+ "varying vec2 yuvTexCoords;\n"
+ "void main() {\n"
+ " yuvTexCoords = vPosition.xy + vec2(0.5, 0.5);\n"
+ " gl_Position = vPosition;\n"
+ "}\n";
+
+static const char gFragmentShader[] =
+ "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES yuvTexSampler;\n"
+ "varying vec2 yuvTexCoords;\n"
+ "void main() {\n"
+ " gl_FragColor = texture2D(yuvTexSampler, yuvTexCoords);\n"
+ "}\n";
+
+GLuint loadShader(GLenum shaderType, const char* pSource) {
+ GLuint shader = glCreateShader(shaderType);
+ if (shader) {
+ glShaderSource(shader, 1, &pSource, NULL);
+ glCompileShader(shader);
+ GLint compiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+ if (!compiled) {
+ GLint infoLen = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+ if (infoLen) {
+ char* buf = (char*) malloc(infoLen);
+ if (buf) {
+ glGetShaderInfoLog(shader, infoLen, NULL, buf);
+ fprintf(stderr, "Could not compile shader %d:\n%s\n",
+ shaderType, buf);
+ free(buf);
+ }
+ } else {
+ fprintf(stderr, "Guessing at GL_INFO_LOG_LENGTH size\n");
+ char* buf = (char*) malloc(0x1000);
+ if (buf) {
+ glGetShaderInfoLog(shader, 0x1000, NULL, buf);
+ fprintf(stderr, "Could not compile shader %d:\n%s\n",
+ shaderType, buf);
+ free(buf);
+ }
+ }
+ glDeleteShader(shader);
+ shader = 0;
+ }
+ }
+ return shader;
+}
+
+GLuint createProgram(const char* pVertexSource, const char* pFragmentSource) {
+ GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource);
+ if (!vertexShader) {
+ return 0;
+ }
+
+ GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource);
+ if (!pixelShader) {
+ return 0;
+ }
+
+ GLuint program = glCreateProgram();
+ if (program) {
+ glAttachShader(program, vertexShader);
+ checkGlError("glAttachShader");
+ glAttachShader(program, pixelShader);
+ checkGlError("glAttachShader");
+ glLinkProgram(program);
+ GLint linkStatus = GL_FALSE;
+ glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+ if (linkStatus != GL_TRUE) {
+ GLint bufLength = 0;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
+ if (bufLength) {
+ char* buf = (char*) malloc(bufLength);
+ if (buf) {
+ glGetProgramInfoLog(program, bufLength, NULL, buf);
+ fprintf(stderr, "Could not link program:\n%s\n", buf);
+ free(buf);
+ }
+ }
+ glDeleteProgram(program);
+ program = 0;
+ }
+ }
+ return program;
+}
+
+GLuint gProgram;
+GLint gvPositionHandle;
+GLint gYuvTexSamplerHandle;
+const GLfloat gTriangleVertices[] = {
+ -0.5f, 0.5f,
+ -0.5f, -0.5f,
+ 0.5f, -0.5f,
+ 0.5f, 0.5f,
+};
+
+
+class EGLClient {
+public:
+ EGLClient() {
+ EGLConfig ecfg;
+ EGLint num_config;
+ EGLint attr[] = { // some attributes to set up our egl-interface
+ EGL_BUFFER_SIZE, 32,
+ EGL_RENDERABLE_TYPE,
+ EGL_OPENGL_ES2_BIT,
+ EGL_NONE
+ };
+ EGLint ctxattr[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+ display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (display == EGL_NO_DISPLAY) {
+ printf("ERROR: Could not get default display\n");
+ return;
+ }
+
+ printf("INFO: Successfully retrieved default display!\n");
+
+ eglInitialize(display, 0, 0);
+ eglChooseConfig((EGLDisplay) display, attr, &ecfg, 1, &num_config);
+
+ printf("INFO: Initialized display with default configuration\n");
+
+ window = new OffscreenNativeWindow(720, 1280);
+ printf("INFO: Created native window %p\n", window);
+ printf("creating window surface...\n");
+ surface = eglCreateWindowSurface((EGLDisplay) display, ecfg, *window, NULL);
+ assert(surface != EGL_NO_SURFACE);
+ printf("INFO: Created our main window surface %p\n", surface);
+ context = eglCreateContext((EGLDisplay) display, ecfg, EGL_NO_CONTEXT, ctxattr);
+ assert(surface != EGL_NO_CONTEXT);
+ printf("INFO: Created context for display\n");
+ frame=0;
+ };
+ void render() {
+ assert(eglMakeCurrent((EGLDisplay) display, surface, surface, context) == EGL_TRUE);
+ printf("INFO: Made context and surface current for display\n");
+ glViewport ( 0 , 0 , 1280, 720);
+ printf("client frame %i\n", frame++);
+ glClearColor ( 1.00 , (frame & 1) * 1.0f , ((float)(frame % 255))/255.0f, 1.); // background color
+ glClear(GL_COLOR_BUFFER_BIT);
+ eglSwapBuffers(display, surface);
+ printf("client swapped\n");
+ }
+ int frame;
+ EGLDisplay display;
+ EGLContext context;
+ EGLSurface surface;
+ OffscreenNativeWindow* window;
+};
+
+class EGLCompositor {
+public:
+ EGLCompositor() {
+ EGLConfig ecfg;
+ EGLint num_config;
+ EGLint attr[] = { // some attributes to set up our egl-interface
+ EGL_BUFFER_SIZE, 32,
+ EGL_RENDERABLE_TYPE,
+ EGL_OPENGL_ES2_BIT,
+ EGL_NONE
+ };
+ EGLint ctxattr[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+ display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (display == EGL_NO_DISPLAY) {
+ printf("ERROR: Could not get default display\n");
+ return;
+ }
+
+ printf("INFO: Successfully retrieved default display!\n");
+
+ eglInitialize(display, 0, 0);
+ eglChooseConfig((EGLDisplay) display, attr, &ecfg, 1, &num_config);
+
+ printf("INFO: Initialized display with default configuration\n");
+
+ window = new FbDevNativeWindow();
+ printf("INFO: Created native window %p\n", window);
+ printf("creating window surface...\n");
+ surface = eglCreateWindowSurface((EGLDisplay) display, ecfg, *window, NULL);
+ assert(surface != EGL_NO_SURFACE);
+ printf("INFO: Created our main window surface %p\n", surface);
+ context = eglCreateContext((EGLDisplay) display, ecfg, EGL_NO_CONTEXT, ctxattr);
+ assert(surface != EGL_NO_CONTEXT);
+ printf("INFO: Created context for display\n");
+ assert(eglMakeCurrent((EGLDisplay) display, surface, surface, context) == EGL_TRUE);
+ printf("INFO: Made context and surface current for display\n");
+ frame=0;
+ glGenTextures(1, &texture);
+
+ gProgram = createProgram(gVertexShader, gFragmentShader);
+ if (!gProgram) {
+ printf("no program\n");
+ abort();
+ }
+ gvPositionHandle = glGetAttribLocation(gProgram, "vPosition");
+ checkGlError("glGetAttribLocation");
+ fprintf(stderr, "glGetAttribLocation(\"vPosition\") = %d\n",
+ gvPositionHandle);
+ gYuvTexSamplerHandle = glGetUniformLocation(gProgram, "yuvTexSampler");
+ checkGlError("glGetUniformLocation");
+ fprintf(stderr, "glGetUniformLocation(\"yuvTexSampler\") = %d\n",
+ gYuvTexSamplerHandle);
+
+ glViewport(0, 0, 1280, 720);
+ checkGlError("glViewport");
+
+ };
+ void render(OffscreenNativeWindow* window) {
+ assert(eglMakeCurrent((EGLDisplay) display, surface, surface, context) == EGL_TRUE);
+ printf("INFO: Made context and surface current for display\n");
+
+
+ EGLClientBuffer cbuf = (EGLClientBuffer) window->getFrontBuffer();
+ EGLint attrs[] = {
+ EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
+ EGL_NONE,
+ };
+ EGLImageKHR image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, cbuf, attrs);
+ if (image == EGL_NO_IMAGE_KHR) {
+ EGLint error = eglGetError();
+ printf("error creating EGLImage: %#x", error);
+ }
+ printf("got egl image %p\n", image);
+
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture);
+ int err;
+ if(err = glGetError()) printf("%i gl error %x\n", __LINE__, err);
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, (GLeglImageOES)image);
+ if(err = glGetError()) printf("%i gl error %x\n", __LINE__, err);
+ glEnable(GL_TEXTURE_EXTERNAL_OES);
+ if(err = glGetError()) printf("%i gl error %x\n", __LINE__, err);
+
+
+
+ glViewport ( 0 , 0 , 1280, 720);
+ printf("compositor frame %i\n", frame++);
+ float c = (frame % 64) / 64.0f;
+ glClearColor ( c , c , c, 1.); // background color
+ checkGlError("glClearColor");
+
+
+ glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+ checkGlError("glClear");
+
+ glUseProgram(gProgram);
+ checkGlError("glUseProgram");
+
+ glVertexAttribPointer(gvPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, gTriangleVertices);
+ checkGlError("glVertexAttribPointer");
+ glEnableVertexAttribArray(gvPositionHandle);
+ checkGlError("glEnableVertexAttribArray");
+
+ glUniform1i(gYuvTexSamplerHandle, 0);
+ checkGlError("glUniform1i");
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture);
+ checkGlError("glBindTexture");
+
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+ checkGlError("glDrawArrays");
+
+
+
+ eglSwapBuffers(display, surface);
+ eglDestroyImageKHR(display, image);
+ printf("compositor swapped\n");
+ }
+ int frame;
+ EGLDisplay display;
+ EGLContext context;
+ EGLSurface surface;
+ FbDevNativeWindow* window;
+ GLuint texture;
+ EGLImageKHR image;
+};
+
+int main(int argc, char **argv)
+{
+ EGLCompositor compositor;
+ eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR");
+ eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC) eglGetProcAddress("eglDestroyImageKHR");
+ glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) eglGetProcAddress("glEGLImageTargetTexture2DOES");
+
+
+ EGLClient client;
+ while(1) {
+ client.render();
+ compositor.render(client.window);
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.