The 'null' platform (WAFFLE_PLATFORM_NULL) works with EGL_PLATFORM_NULL,
KHR_surfaceless_context and EXT_image_dma_buf_import to hide surfaceless
operation behind a waffle window.
In this platform waffle windows contain gbm buffers which GLES renders
into via dmabuf -> EGL image -> GL framebuffer.
The gbm buffers can be displayed via DRM/KMS.

Signed-off-by: Frank Henigman <fjhenig...@google.com>
---
 LICENSE.txt                                        |   5 +
 Options.cmake                                      |   8 +
 cmake/Modules/WaffleDefineCompilerFlags.cmake      |   4 +
 cmake/Modules/WaffleFindDependencies.cmake         |   3 +
 .../Modules/WafflePrintConfigurationSummary.cmake  |   3 +
 include/CMakeLists.txt                             |   1 +
 include/waffle/waffle.h                            |  13 +
 include/waffle/waffle_null.h                       |  65 +++
 src/waffle/CMakeLists.txt                          |  19 +
 src/waffle/api/waffle_init.c                       |  11 +
 src/waffle/core/wcore_util.c                       |   1 +
 src/waffle/null/wnull_context.c                    |  87 +++
 src/waffle/null/wnull_context.h                    |  62 ++
 src/waffle/null/wnull_display.c                    | 631 +++++++++++++++++++++
 src/waffle/null/wnull_display.h                    |  93 +++
 src/waffle/null/wnull_platform.c                   | 103 ++++
 src/waffle/null/wnull_platform.h                   |  10 +
 src/waffle/null/wnull_window.c                     | 495 ++++++++++++++++
 src/waffle/null/wnull_window.h                     |  44 ++
 19 files changed, 1658 insertions(+)
 create mode 100644 include/waffle/waffle_null.h
 create mode 100644 src/waffle/null/wnull_context.c
 create mode 100644 src/waffle/null/wnull_context.h
 create mode 100644 src/waffle/null/wnull_display.c
 create mode 100644 src/waffle/null/wnull_display.h
 create mode 100644 src/waffle/null/wnull_platform.c
 create mode 100644 src/waffle/null/wnull_platform.h
 create mode 100644 src/waffle/null/wnull_window.c
 create mode 100644 src/waffle/null/wnull_window.h

diff --git a/LICENSE.txt b/LICENSE.txt
index c6993ac..55e257b 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,5 @@
 Copyright 2012 Intel Corporation
+Copyright (c) 2015, Google Inc.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
@@ -11,6 +12,10 @@ modification, are permitted provided that the following 
conditions are met:
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 
+- Neither the name of Google Inc. nor the names of its contributors may be used
+  to endorse or promote products derived from this software without specific
+  prior written permission.
+
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
diff --git a/Options.cmake b/Options.cmake
index 4f097a0..d6edf21 100644
--- a/Options.cmake
+++ b/Options.cmake
@@ -23,6 +23,12 @@ if(waffle_on_linux)
         set(gbm_default OFF)
     endif()
 
+    if(gbm_FOUND AND libudev_FOUND AND egl_FOUND AND libdrm_FOUND)
+        set(null_default ON)
+    else()
+        set(null_default OFF)
+    endif()
+
     # On Linux, you must enable at least one of the below options.
     option(waffle_has_glx "Build support for GLX" ${glx_default})
     option(waffle_has_wayland "Build support for Wayland" ${wayland_default})
@@ -33,6 +39,8 @@ if(waffle_on_linux)
     # NaCl specific settings.
     set(nacl_sdk_path "" CACHE STRING "Set nacl_sdk path here")
     set(nacl_version "pepper_39" CACHE STRING "Set NaCl bundle here")
+
+    option(waffle_has_null "Build support for Null" ${null_default})
 endif()
 
 option(waffle_build_tests "Build tests" ON)
diff --git a/cmake/Modules/WaffleDefineCompilerFlags.cmake 
b/cmake/Modules/WaffleDefineCompilerFlags.cmake
index 679d09c..00bbeea 100644
--- a/cmake/Modules/WaffleDefineCompilerFlags.cmake
+++ b/cmake/Modules/WaffleDefineCompilerFlags.cmake
@@ -118,6 +118,10 @@ if(waffle_on_linux)
         add_definitions(-DWAFFLE_HAS_GBM)
     endif()
 
+    if(waffle_has_null)
+        add_definitions(-DWAFFLE_HAS_NULL)
+    endif()
+
     if(waffle_has_tls)
         add_definitions(-DWAFFLE_HAS_TLS)
     endif()
diff --git a/cmake/Modules/WaffleFindDependencies.cmake 
b/cmake/Modules/WaffleFindDependencies.cmake
index 3fd7338..110c2b4 100644
--- a/cmake/Modules/WaffleFindDependencies.cmake
+++ b/cmake/Modules/WaffleFindDependencies.cmake
@@ -79,6 +79,9 @@ if(waffle_on_linux)
     # waffle_has_gbm
     waffle_pkg_config(gbm gbm)
     waffle_pkg_config(libudev libudev)
+
+    # waffle_has_null
+    waffle_pkg_config(libdrm libdrm)
 endif()
 
 
diff --git a/cmake/Modules/WafflePrintConfigurationSummary.cmake 
b/cmake/Modules/WafflePrintConfigurationSummary.cmake
index 1199ea3..13d0dbe 100644
--- a/cmake/Modules/WafflePrintConfigurationSummary.cmake
+++ b/cmake/Modules/WafflePrintConfigurationSummary.cmake
@@ -47,6 +47,9 @@ endif()
 if(waffle_has_gbm)
     message("    gbm")
 endif()
+if(waffle_has_null)
+    message("    null")
+endif()
 if(waffle_on_windows)
     message("    wgl")
 endif()
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index e190a76..988d7f6 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -7,6 +7,7 @@ install(
         waffle/waffle.h
         waffle/waffle_gbm.h
         waffle/waffle_glx.h
+        waffle/waffle_null.h
         waffle/waffle_version.h
         waffle/waffle_wayland.h
         waffle/waffle_x11_egl.h
diff --git a/include/waffle/waffle.h b/include/waffle/waffle.h
index df0218e..aae35ee 100644
--- a/include/waffle/waffle.h
+++ b/include/waffle/waffle.h
@@ -119,6 +119,7 @@ enum waffle_enum {
         WAFFLE_PLATFORM_GBM                                     = 0x0016,
         WAFFLE_PLATFORM_WGL                                     = 0x0017,
         WAFFLE_PLATFORM_NACL                                    = 0x0018,
+        WAFFLE_PLATFORM_NULL                                    = 0x0019,
 
     // ------------------------------------------------------------------
     // For waffle_config_choose()
@@ -173,6 +174,10 @@ enum waffle_enum {
     WAFFLE_WINDOW_WIDTH                                         = 0x0310,
     WAFFLE_WINDOW_HEIGHT                                        = 0x0311,
     WAFFLE_WINDOW_FULLSCREEN                                    = 0x0312,
+
+    WAFFLE_WINDOW_NULL_SHOW_METHOD                              = 0x0320,
+        WAFFLE_WINDOW_NULL_SHOW_METHOD_FLIP                     = 0x0321,
+        WAFFLE_WINDOW_NULL_SHOW_METHOD_COPY                     = 0x0322,
 };
 
 const char*
@@ -312,12 +317,17 @@ struct waffle_x11_egl_config;
 struct waffle_x11_egl_context;
 struct waffle_x11_egl_display;
 struct waffle_x11_egl_window;
+struct waffle_null_config;
+struct waffle_null_context;
+struct waffle_null_display;
+struct waffle_null_window;
 
 union waffle_native_display {
     struct waffle_gbm_display *gbm;
     struct waffle_glx_display *glx;
     struct waffle_x11_egl_display *x11_egl;
     struct waffle_wayland_display *wayland;
+    struct waffle_null_display *null;
 };
 
 union waffle_native_config {
@@ -325,6 +335,7 @@ union waffle_native_config {
     struct waffle_glx_config *glx;
     struct waffle_x11_egl_config *x11_egl;
     struct waffle_wayland_config *wayland;
+    struct waffle_null_config *null;
 };
 
 union waffle_native_context {
@@ -332,6 +343,7 @@ union waffle_native_context {
     struct waffle_glx_context *glx;
     struct waffle_x11_egl_context *x11_egl;
     struct waffle_wayland_context *wayland;
+    struct waffle_null_context *null;
 };
 
 union waffle_native_window {
@@ -339,6 +351,7 @@ union waffle_native_window {
     struct waffle_glx_window *glx;
     struct waffle_x11_egl_window *x11_egl;
     struct waffle_wayland_window *wayland;
+    struct waffle_null_window *null;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/include/waffle/waffle_null.h b/include/waffle/waffle_null.h
new file mode 100644
index 0000000..170f2ad
--- /dev/null
+++ b/include/waffle/waffle_null.h
@@ -0,0 +1,65 @@
+// Copyright 2015 Google
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// - Redistributions of source code must retain the above copyright notice, 
this
+//   list of conditions and the following disclaimer.
+//
+// - Redistributions in binary form must reproduce the above copyright notice,
+//   this list of conditions and the following disclaimer in the documentation
+//   and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#define __GBM__ 1
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <EGL/egl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct gbm_device;
+
+struct waffle_null_display {
+    struct gbm_device *gbm_device;
+    EGLDisplay egl_display;
+};
+
+struct waffle_null_config {
+    struct waffle_null_display display;
+    EGLConfig egl_config;
+};
+
+struct waffle_null_context {
+    struct waffle_null_display display;
+    EGLContext egl_context;
+};
+
+struct waffle_null_window {
+    struct waffle_null_display display;
+    int width;
+    int height;
+    //XXX expose native buffers here, like gbm_bo, EGLImage, or GL framebuffer?
+};
+
+#ifdef __cplusplus
+} // end extern "C"
+#endif
diff --git a/src/waffle/CMakeLists.txt b/src/waffle/CMakeLists.txt
index 4f1d5c7..f5e99a3 100644
--- a/src/waffle/CMakeLists.txt
+++ b/src/waffle/CMakeLists.txt
@@ -13,9 +13,11 @@ include_directories(
     cgl
     core
     egl
+    gbm
     glx
     linux
     nacl
+    null
     wayland
     wgl
     x11
@@ -25,8 +27,10 @@ include_directories(
     ${gbm_INCLUDE_DIRS}
     ${gl_INCLUDE_DIRS}
     ${GLEXT_INCLUDE_DIR}
+    ${libdrm_INCLUDE_DIRS}
     ${libudev_INCLUDE_DIRS}
     ${nacl_INCLUDE_DIRS}
+    ${null_INCLUDE_DIRS}
     ${wayland-client_INCLUDE_DIRS}
     ${wayland-egl_INCLUDE_DIRS}
     ${x11-xcb_INCLUDE_DIRS}
@@ -51,6 +55,12 @@ if(waffle_on_linux)
             ${libudev_LDFLAGS}
             )
     endif()
+    if(waffle_has_null)
+        list(APPEND waffle_libdeps
+            ${libdrm_LDFLAGS}
+            ${libudev_LDFLAGS}
+            )
+    endif()
 endif()
 
 if(waffle_has_nacl)
@@ -189,6 +199,15 @@ if(waffle_has_nacl)
         )
 endif()
 
+if(waffle_has_null)
+    list(APPEND waffle_sources
+        null/wnull_context.c
+        null/wnull_display.c
+        null/wnull_platform.c
+        null/wnull_window.c
+    )
+endif()
+
 # CMake will pass to the C compiler only C sources. CMake does not recognize 
the
 # .m extension and ignores any such files in the source lists. To coerce CMake
 # to pass .m files to the compiler, we must lie and claim that they are
diff --git a/src/waffle/api/waffle_init.c b/src/waffle/api/waffle_init.c
index 60091d1..a09fb11 100644
--- a/src/waffle/api/waffle_init.c
+++ b/src/waffle/api/waffle_init.c
@@ -36,6 +36,7 @@ struct wcore_platform* xegl_platform_create(void);
 struct wcore_platform* wgbm_platform_create(void);
 struct wcore_platform* wgl_platform_create(void);
 struct wcore_platform* nacl_platform_create(void);
+struct wcore_platform* wnull_platform_create(void);
 
 static bool
 waffle_init_parse_attrib_list(
@@ -99,6 +100,12 @@ waffle_init_parse_attrib_list(
                     CASE_UNDEFINED_PLATFORM(GBM)
 #endif
 
+#ifdef WAFFLE_HAS_NULL
+                    CASE_DEFINED_PLATFORM(NULL)
+#else
+                    CASE_UNDEFINED_PLATFORM(NULL)
+#endif
+
 #ifdef WAFFLE_HAS_WGL
                     CASE_DEFINED_PLATFORM(WGL)
 #else
@@ -167,6 +174,10 @@ waffle_init_create_platform(int32_t waffle_platform)
         case WAFFLE_PLATFORM_GBM:
             return wgbm_platform_create();
 #endif
+#ifdef WAFFLE_HAS_NULL
+        case WAFFLE_PLATFORM_NULL:
+            return wnull_platform_create();
+#endif
 #ifdef WAFFLE_HAS_WGL
         case WAFFLE_PLATFORM_WGL:
             return wgl_platform_create();
diff --git a/src/waffle/core/wcore_util.c b/src/waffle/core/wcore_util.c
index b7809a3..7d90489 100644
--- a/src/waffle/core/wcore_util.c
+++ b/src/waffle/core/wcore_util.c
@@ -83,6 +83,7 @@ wcore_enum_to_string(int32_t e)
         CASE(WAFFLE_PLATFORM_WAYLAND);
         CASE(WAFFLE_PLATFORM_X11_EGL);
         CASE(WAFFLE_PLATFORM_GBM);
+        CASE(WAFFLE_PLATFORM_NULL);
         CASE(WAFFLE_PLATFORM_WGL);
         CASE(WAFFLE_PLATFORM_NACL);
         CASE(WAFFLE_CONTEXT_API);
diff --git a/src/waffle/null/wnull_context.c b/src/waffle/null/wnull_context.c
new file mode 100644
index 0000000..896213a
--- /dev/null
+++ b/src/waffle/null/wnull_context.c
@@ -0,0 +1,87 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.txt file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+#include <dlfcn.h>
+
+#include "wcore_error.h"
+
+#include "wnull_context.h"
+#include "wnull_display.h"
+
+#if 0
+#include <stdio.h>
+#define prt(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define prt(...)
+#endif
+
+struct wcore_context*
+wnull_context_create(struct wcore_platform *wc_plat,
+                     struct wcore_config *wc_config,
+                     struct wcore_context *wc_share_ctx)
+{
+    struct wnull_context *ctx = wcore_calloc(sizeof(*ctx));
+    if (!ctx)
+        return NULL;
+
+    if (wc_config->attrs.samples > 0) {
+        wcore_errorf(WAFFLE_ERROR_BAD_ATTRIBUTE,
+                     "WAFFLE_PLATFORM_NULL does not support samples");
+        goto fail;
+    }
+
+    if (wc_config->attrs.sample_buffers) {
+        wcore_errorf(WAFFLE_ERROR_BAD_ATTRIBUTE,
+                     "WAFFLE_PLATFORM_NULL does not support sample buffers");
+        goto fail;
+    }
+
+    int32_t dl;
+    switch (wc_config->attrs.context_api) {
+        //XXX could some other APIs work?
+        case WAFFLE_CONTEXT_OPENGL_ES2: dl = WAFFLE_DL_OPENGL_ES2;  break;
+        default:
+            wcore_errorf(WAFFLE_ERROR_BAD_ATTRIBUTE,
+                         "WAFFLE_PLATFORM_NULL api must be GLES2");
+            goto fail;
+    }
+
+    bool ok = true;
+#define LOOKUP(type, name, args) \
+    ctx->name = waffle_dl_sym(dl, #name); \
+    ok &= ctx->name != NULL;
+    WNULL_GL_FUNCTIONS(LOOKUP)
+#undef LOOKUP
+
+    ok &= wegl_context_init(&ctx->wegl, wc_config, wc_share_ctx);
+    if (!ok)
+        goto fail;
+
+    prt("create context %p\n", ctx);
+    return &ctx->wegl.wcore;
+
+fail:
+    wnull_context_destroy(&ctx->wegl.wcore);
+    return NULL;
+}
+
+bool
+wnull_context_destroy(struct wcore_context *wc_ctx)
+{
+    bool result = true;
+
+    if (wc_ctx) {
+        struct wnull_context *self = wnull_context(wc_ctx);
+        prt("destroy context %p\n", self);
+        result = wegl_context_teardown(&self->wegl);
+
+        // tell the display this context is gone
+        wnull_display_clean(wnull_display(wc_ctx->display), self, NULL);
+
+        free(self);
+    }
+    return result;
+}
diff --git a/src/waffle/null/wnull_context.h b/src/waffle/null/wnull_context.h
new file mode 100644
index 0000000..e1b1773
--- /dev/null
+++ b/src/waffle/null/wnull_context.h
@@ -0,0 +1,62 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.txt file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+#pragma once
+
+#include <stdbool.h>
+
+#include "wcore_util.h"
+#include "wegl_context.h"
+
+typedef void *EGLImageKHR;
+typedef void *GLeglImageOES;
+typedef unsigned int GLenum;
+typedef int GLint;
+typedef int GLsizei;
+typedef unsigned int GLuint;
+
+#define WNULL_GL_FUNCTIONS(f) \
+    f(void  , glBindFramebuffer                     , (GLenum target, GLuint 
framebuffer)) \
+    f(void  , glBindRenderbuffer                    , (GLenum target, GLuint 
renderbuffer)) \
+    f(GLenum, glCheckFramebufferStatus              , (GLenum target)) \
+    f(void  , glDeleteFramebuffers                  , (GLsizei n, const GLuint 
*framebuffers)) \
+    f(void  , glDeleteRenderbuffers                 , (GLsizei n, const GLuint 
*framebuffers)) \
+    f(void  , glEGLImageTargetRenderbufferStorageOES, (GLenum target, 
GLeglImageOES image)) \
+    f(void  , glFinish                              , ()) \
+    f(void  , glFramebufferRenderbuffer             , (GLenum target, GLenum 
attachment, GLenum renderbuffertarget, GLuint renderbuffer)) \
+    f(void  , glGenFramebuffers                     , (GLsizei n, GLuint 
*framebuffers)) \
+    f(void  , glGenRenderbuffers                    , (GLsizei n, GLuint 
*renderbuffers)) \
+    f(GLenum, glGetError                            , ()) \
+    f(void  , glGetIntegerv                         , (GLenum pname, GLint 
*data)) \
+    f(void  , glRenderbufferStorage                 , (GLenum target, GLenum 
internalformat, GLsizei width, GLsizei height)) \
+    f(void  , glScissor                             , (GLint x, GLint y, 
GLsizei width, GLsizei height)) \
+    f(void  , glViewport                            , (GLint x, GLint y, 
GLsizei width, GLsizei height))
+
+struct wnull_context {
+    struct wegl_context wegl;
+#define DECLARE(type, name, args) type (*name) args;
+    WNULL_GL_FUNCTIONS(DECLARE)
+#undef DECLARE
+};
+
+static inline struct wnull_context*
+wnull_context(struct wcore_context *wc_self)
+{
+    if (wc_self) {
+        struct wegl_context *wegl_self = container_of(wc_self, struct 
wegl_context, wcore);
+        return container_of(wegl_self, struct wnull_context, wegl);
+    } else {
+        return NULL;
+    }
+}
+
+struct wcore_context*
+wnull_context_create(struct wcore_platform *wc_plat,
+                     struct wcore_config *wc_config,
+                     struct wcore_context *wc_share_ctx);
+
+bool
+wnull_context_destroy(struct wcore_context *wc_ctx);
diff --git a/src/waffle/null/wnull_display.c b/src/waffle/null/wnull_display.c
new file mode 100644
index 0000000..e81961c
--- /dev/null
+++ b/src/waffle/null/wnull_display.c
@@ -0,0 +1,631 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.txt file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+#define _GNU_SOURCE
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <libudev.h>
+
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <i915_drm.h>
+
+#include "wcore_error.h"
+
+#include "wgbm_display.h"
+#include "wgbm_platform.h"
+
+#include "wnull_display.h"
+
+#define ARRAY_END(a) ((a)+sizeof(a)/sizeof((a)[0]))
+#define MIN(a,b) ((a)<(b)?(a):(b))
+
+#if 0
+#include <stdio.h>
+#define prt(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define prt(...)
+#endif
+
+struct wnull_display_buffer {
+    struct wgbm_platform *plat;
+    struct gbm_bo *bo;
+    uint32_t drm_fb;
+    int drm_fd;
+    int dmabuf_fd;
+    void (*finish)();
+};
+
+struct drm_display {
+    struct gbm_device *gbm_device;
+    drmModeConnectorPtr conn;
+    drmModeModeInfoPtr mode;
+    drmModeCrtcPtr crtc;
+    int32_t width;
+    int32_t height;
+    bool setcrtc_done;
+    bool flip_pending;
+    struct wnull_display_buffer scanout[2];
+    struct wnull_display_buffer *current;
+};
+
+
+static void
+wnull_display_buffer_teardown(struct wnull_display_buffer *buf)
+{
+    if (!buf || !buf->bo)
+        return;
+    if (buf->dmabuf_fd >= 0) {
+        close(buf->dmabuf_fd);
+        buf->dmabuf_fd = -1;
+    }
+    if (buf->drm_fd >= 0) {
+        drmModeRmFB(buf->drm_fd, buf->drm_fb);
+        buf->drm_fd = -1;
+    }
+    buf->plat->gbm_bo_destroy(buf->bo);
+    buf->bo = NULL;
+    prt("tore down display buffer %p\n",buf);
+}
+
+void
+wnull_display_buffer_destroy(struct wnull_display_buffer *buf)
+{
+    wnull_display_buffer_teardown(buf);
+    free(buf);
+    prt("destroyed display buffer %p\n",buf);
+}
+
+static bool
+wnull_display_buffer_init(struct wnull_display_buffer *self,
+                          struct wnull_display *dpy,
+                          int width, int height,
+                          uint32_t format, uint32_t flags,
+                          void (*finish)())
+{
+    if (self->bo)
+        return true;
+
+    self->plat = wgbm_platform(wegl_platform(dpy->wegl.wcore.platform));
+    self->drm_fd = -1;
+    self->dmabuf_fd = -1;
+    self->finish = finish;
+
+    if (width == -1 && height == -1) {
+        width = dpy->drm->width;
+        height = dpy->drm->height;
+    }
+
+    self->bo = self->plat->gbm_bo_create(dpy->drm->gbm_device,
+                                         width, height,
+                                         format, flags);
+    if (self->bo) {
+        prt("init-ed display buffer %p\n",self);
+        return true;
+    }
+
+    wnull_display_buffer_teardown(self);
+    return false;
+}
+
+struct wnull_display_buffer *
+wnull_display_buffer_create(struct wnull_display *dpy,
+                            int width, int height,
+                            uint32_t format, uint32_t flags,
+                            void (*finish)())
+{
+    struct wnull_display_buffer *buf = wcore_calloc(sizeof(*buf));
+    if (!buf)
+        return NULL;
+
+    if (wnull_display_buffer_init(buf, dpy, width, height, format, flags, 
finish)) {
+        prt("created display buffer %p\n",buf);
+        return buf;
+    }
+
+    wnull_display_buffer_destroy(buf);
+    return NULL;
+}
+
+bool
+wnull_display_buffer_dmabuf(struct wnull_display_buffer *buf,
+                            int *fd,
+                            uint32_t *stride)
+{
+    if (!buf->bo)
+        return false;
+    if (buf->dmabuf_fd < 0)
+        buf->dmabuf_fd = buf->plat->gbm_bo_get_fd(buf->bo);
+    if (buf->dmabuf_fd < 0)
+        return false;
+    if (fd)
+        *fd = buf->dmabuf_fd;
+    if (stride)
+        *stride = buf->plat->gbm_bo_get_stride(buf->bo);
+    return true;
+}
+
+static void
+page_flip_handler(int fd,
+                  unsigned int sequence,
+                  unsigned int tv_sec,
+                  unsigned int tv_usec,
+                  void *user_data)
+{
+    struct drm_display *drm = (struct drm_display *) user_data;
+    assert(drm->flip_pending);
+    drm->flip_pending = false;
+}
+
+bool
+wnull_display_show_buffer(struct wnull_display *dpy,
+                          struct wnull_display_buffer *buf)
+{
+    struct drm_display *drm = dpy->drm;
+    struct wgbm_platform *plat = buf->plat;
+    int fd = plat->gbm_device_get_fd(drm->gbm_device);
+
+    prt("showing %p\n", buf);
+    if (!drm->crtc)
+        return true;
+
+    assert(buf->bo);
+    if (buf->drm_fd < 0) {
+        if (drmModeAddFB(fd,
+                         plat->gbm_bo_get_width(buf->bo),
+                         plat->gbm_bo_get_height(buf->bo),
+                         24, 32,
+                         plat->gbm_bo_get_stride(buf->bo),
+                         plat->gbm_bo_get_handle(buf->bo).u32,
+                         &buf->drm_fb)) {
+            wcore_errorf(WAFFLE_ERROR_UNKNOWN,
+                         "drm addfb failed: errno=%d",
+                         errno);
+            return false;
+        }
+        buf->drm_fd = fd;
+    }
+
+    if (!drm->setcrtc_done) {
+        if (drmModeSetCrtc(fd, drm->crtc->crtc_id, buf->drm_fb, 0, 0,
+                           &drm->conn->connector_id, 1, drm->mode)) {
+            wcore_errorf(WAFFLE_ERROR_UNKNOWN,
+                         "drm setcrtc failed: errno=%d",
+                         errno);
+            return false;
+        }
+        drm->setcrtc_done = true;
+    }
+
+    // wait for pending flip, if any
+    if (drm->flip_pending) {
+        drmEventContext event = {
+            .version = DRM_EVENT_CONTEXT_VERSION,
+            .page_flip_handler = page_flip_handler,
+        };
+        drmHandleEvent(fd, &event);
+    }
+    assert(!drm->flip_pending);
+
+    // do this after waiting for flip because during that wait
+    // rendering could proceed, making this wait shorter
+    if (buf->finish)
+        buf->finish();
+
+    drm->flip_pending = true;
+    if (drmModePageFlip(fd, drm->crtc->crtc_id, buf->drm_fb,
+                        DRM_MODE_PAGE_FLIP_EVENT, drm)) {
+        wcore_errorf(WAFFLE_ERROR_UNKNOWN,
+                     "drm page flip failed: errno=%d",
+                     errno);
+        return false;
+    }
+
+    return true;
+}
+
+//XXX i915-specific
+static bool
+buffer_copy(struct wnull_display_buffer *dst,
+            struct wnull_display_buffer *src)
+{
+    assert(dst->bo);
+    assert(src->bo);
+
+    struct drm_i915_gem_get_tiling dst_tiling = {
+        .handle = dst->plat->gbm_bo_get_handle(dst->bo).u32,
+    };
+    struct drm_i915_gem_get_tiling src_tiling = {
+        .handle = src->plat->gbm_bo_get_handle(src->bo).u32,
+    };
+    int dst_fd = 
dst->plat->gbm_device_get_fd(dst->plat->gbm_bo_get_device(dst->bo));
+    int src_fd = 
src->plat->gbm_device_get_fd(src->plat->gbm_bo_get_device(src->bo));
+
+    if (drmIoctl(dst_fd, DRM_IOCTL_I915_GEM_GET_TILING, &dst_tiling) ||
+        drmIoctl(src_fd, DRM_IOCTL_I915_GEM_GET_TILING, &src_tiling))
+        return false;
+
+    if (dst_tiling.tiling_mode != src_tiling.tiling_mode)
+        return false;
+
+    unsigned rows;
+    switch (dst_tiling.tiling_mode) {
+        case I915_TILING_NONE:
+            rows = 1;
+            break;
+        case I915_TILING_X:
+            rows = 8;
+            break;
+        default:
+            return false;
+    }
+
+    unsigned dst_step = dst->plat->gbm_bo_get_stride(dst->bo) * rows;
+    unsigned src_step = src->plat->gbm_bo_get_stride(src->bo) * rows;
+    unsigned copy_size = MIN(src_step, dst_step);
+    // round up, not down, or we miss the last partly filled tile
+    unsigned num_copy = (MIN(src->plat->gbm_bo_get_height(src->bo),
+                             dst->plat->gbm_bo_get_height(dst->bo)) + rows - 1)
+                             / rows;
+
+    void *tmp = malloc(copy_size);
+    if (!tmp)
+        return false;
+
+    struct drm_i915_gem_pread pread = {
+        .handle = src->plat->gbm_bo_get_handle(src->bo).u32,
+        .size = copy_size,
+        .offset = 0,
+        .data_ptr = (uint64_t) (uintptr_t) tmp,
+    };
+
+    struct drm_i915_gem_pwrite pwrite = {
+        .handle = dst->plat->gbm_bo_get_handle(dst->bo).u32,
+        .size = copy_size,
+        .offset = 0,
+        .data_ptr = (uint64_t) (uintptr_t) tmp,
+    };
+
+    // blit on gpu must be faster than this, but seems complicated to do
+    bool ok = true;
+    for (int i = 0; ok && i < num_copy; ++i) {
+        ok = !(drmIoctl(src_fd, DRM_IOCTL_I915_GEM_PREAD, &pread) ||
+               drmIoctl(dst_fd, DRM_IOCTL_I915_GEM_PWRITE, &pwrite));
+        pread.offset += src_step;
+        pwrite.offset += dst_step;
+    }
+    free(tmp);
+    return ok;
+}
+
+bool
+wnull_display_copy_buffer(struct wnull_display *dpy,
+                          struct wnull_display_buffer *buf)
+{
+    struct drm_display *drm = dpy->drm;
+
+    prt("copying %p\n", buf);
+    if (!drm->current)
+        drm->current = drm->scanout;
+
+    if (!wnull_display_buffer_init(drm->current,
+                                   dpy,
+                                   -1, -1,
+                                   buf->plat->gbm_bo_get_format(buf->bo),
+                                   GBM_BO_USE_SCANOUT,
+                                   NULL))
+        return false;
+
+    assert(buf->finish);
+    buf->finish();
+
+    if (!buffer_copy(drm->current, buf)) {
+        prt("copy failed %p\n", buf);
+        return false;
+    }
+
+    if ( !wnull_display_show_buffer(dpy, drm->current)) {
+        prt("show failed %p\n", buf);
+        return false;
+    }
+
+    ++drm->current;
+    if (drm->current == ARRAY_END(drm->scanout))
+        drm->current = drm->scanout;
+    return true;
+}
+
+static drmModeModeInfoPtr
+choose_mode(drmModeConnectorPtr conn)
+{
+    drmModeModeInfoPtr mode = NULL;
+    assert(conn);
+    assert(conn->connection == DRM_MODE_CONNECTED);
+    // use first preferred mode if any, else end up with last mode in list
+    for (int i = 0; i < conn->count_modes; ++i) {
+        mode = conn->modes + i;
+        if (mode->type & DRM_MODE_TYPE_PREFERRED)
+            break;
+    }
+    return mode;
+}
+
+static int
+choose_crtc(int fd, unsigned count_crtcs, drmModeConnectorPtr conn)
+{
+    drmModeEncoderPtr enc = 0;
+    for (int i = 0; i < conn->count_encoders; ++i) {
+        drmModeFreeEncoder(enc);
+        enc = drmModeGetEncoder(fd, conn->encoders[i]);
+        unsigned b = enc->possible_crtcs;
+        drmModeFreeEncoder(enc);
+        for (int j = 0; b && j < count_crtcs; b >>= 1, ++j) {
+            if (b & 1)
+                return j;
+        }
+    }
+    return -1;
+}
+
+static void
+drm_display_destroy(struct drm_display *self, struct wgbm_platform *plat)
+{
+    struct wnull_display_buffer *buf;
+    for (buf = self->scanout; buf < ARRAY_END(self->scanout); ++buf)
+        wnull_display_buffer_teardown(buf);
+
+    if (self->gbm_device) {
+        int fd = plat->gbm_device_get_fd(self->gbm_device);
+        plat->gbm_device_destroy(self->gbm_device);
+        close(fd);
+    }
+
+    drmModeFreeConnector(self->conn);
+    free(self);
+}
+
+static struct drm_display *
+drm_display_create(int fd, struct wgbm_platform *plat)
+{
+    struct drm_display *drm = wcore_calloc(sizeof(*drm));
+    if (!drm)
+        return NULL;
+
+    dlopen("libglapi.so.0", RTLD_LAZY | RTLD_GLOBAL);
+    drm->gbm_device = plat->gbm_create_device(fd);
+    if (!drm->gbm_device) {
+        wcore_errorf(WAFFLE_ERROR_UNKNOWN, "gbm_create_device failed");
+        goto error;
+    }
+
+    drm->conn = NULL;
+    drmModeResPtr mr = drmModeGetResources(fd);
+    if (!mr) {
+        wcore_errorf(WAFFLE_ERROR_UNKNOWN,
+                     "no display on device (is it a render node?");
+        goto error;
+    }
+
+    bool monitor_connected = false;
+    for (int i = 0; i < mr->count_connectors; ++i) {
+        drmModeFreeConnector(drm->conn);
+        drm->conn = drmModeGetConnector(fd, mr->connectors[i]);
+        if (!drm->conn || drm->conn->connection != DRM_MODE_CONNECTED)
+            continue;
+        monitor_connected = true;
+        drm->mode = choose_mode(drm->conn);
+        if (!drm->mode)
+            continue;
+        int n = choose_crtc(fd, mr->count_crtcs, drm->conn);
+        if (n < 0)
+            continue;
+        drm->crtc = drmModeGetCrtc(fd, mr->crtcs[n]);
+        if (drm->crtc) {
+            drm->width = drm->mode->hdisplay;
+            drm->height = drm->mode->vdisplay;
+            return drm;
+        }
+    }
+
+    if (!monitor_connected) {
+        prt("headless\n");
+        assert(!drm->crtc);
+        // arbitrary
+        drm->width = 1280;
+        drm->height = 1024;
+        return drm;
+    }
+
+error:
+    drm_display_destroy(drm, plat);
+    return NULL;
+}
+
+bool
+wnull_display_destroy(struct wcore_display *wc_self)
+{
+    struct wnull_display *self = wnull_display(wc_self);
+    if (!self)
+        return true;
+
+    if (self->drm)
+        drm_display_destroy(self->drm,
+                            wgbm_platform(wegl_platform(wc_self->platform)));
+
+    bool ok = wegl_display_teardown(&self->wegl);
+
+    free(self->cur);
+    free(self);
+    prt("destroy display %p\n", self);
+    return ok;
+}
+
+struct wcore_display*
+wnull_display_connect(struct wcore_platform *wc_plat,
+                      const char *name)
+{
+    struct wgbm_platform *plat = wgbm_platform(wegl_platform(wc_plat));
+
+    struct wnull_display *self = wcore_calloc(sizeof(*self));
+    if (!self)
+        return NULL;
+
+    int fd;
+    if (name != NULL)
+        fd = open(name, O_RDWR | O_CLOEXEC);
+    else
+        fd = wgbm_get_default_fd_for_pattern("card[0-9]*");
+
+    if (fd < 0) {
+        wcore_errorf(WAFFLE_ERROR_UNKNOWN, "open drm file for gbm failed");
+        goto error;
+    }
+
+    self->drm = drm_display_create(fd, plat);
+    if (!self->drm)
+        goto error;
+
+    if (!wegl_display_init(&self->wegl, wc_plat,
+                           (intptr_t) self->drm->gbm_device))
+        goto error;
+
+    prt("create display %p\n",self);
+    return &self->wegl.wcore;
+
+error:
+    wnull_display_destroy(&self->wegl.wcore);
+    return NULL;
+}
+
+void
+wnull_display_get_size(struct wnull_display *self,
+                       int32_t *width, int32_t *height)
+{
+    *width = self->drm->width;
+    *height = self->drm->height;
+}
+
+void
+wnull_display_fill_native(struct wnull_display *self,
+                          struct waffle_null_display *n_dpy)
+{
+    n_dpy->gbm_device = self->drm->gbm_device;
+    n_dpy->egl_display = self->wegl.egl;
+}
+
+union waffle_native_display*
+wnull_display_get_native(struct wcore_display *wc_self)
+{
+    struct wnull_display *self = wnull_display(wc_self);
+    union waffle_native_display *n_dpy;
+
+    WCORE_CREATE_NATIVE_UNION(n_dpy, null);
+    if (n_dpy == NULL)
+        return NULL;
+
+    wnull_display_fill_native(self, n_dpy->null);
+
+    return n_dpy;
+}
+
+struct ctx_win {
+    struct wnull_context *ctx;
+    struct wnull_window *win;
+};
+
+// Keep track of which context is current and maintain a list of which
+// context/window pairs have been current.
+// This lets us answer two questions:
+// 1) Is it the first time the given pair will be current together?
+// 2) Which windows have been current with the current context?
+//
+// The pair (ctx,win) is added to the list, if not already there.
+// *first is set to true if it wasn't already there.
+// *old_win_ptr is pointed at a NULL-terminated array of windows which were
+// ever current with the current context.
+// Finally the current context is set to 'ctx.'
+//
+// Caller responsible for freeing *old_win_ptr.
+// Return false for failure and do not modify the output parameters.
+bool
+wnull_display_make_current(struct wnull_display *self,
+                           struct wnull_context *ctx,
+                           struct wnull_window *win,
+                           bool *first,
+                           struct wnull_window ***old_win_ptr)
+{
+    assert(self->num_cur <= self->len_cur);
+    prt("make_current dpy %p ctx %p win %p\n", self, ctx, win);
+    prt("ctx/win list before:\n"); for (int i = 0; i < self->num_cur; ++i) 
prt("  %p/%p\n", self->cur[i].ctx, self->cur[i].win);
+
+    struct wnull_window **old_win;
+    if (self->current_context) {
+        // allocate the most we might use
+        // i.e. length of our list plus one for the terminator
+        old_win = wcore_calloc((self->num_cur + 1) * sizeof(old_win[0]));
+        if (!old_win)
+            return false;
+        *old_win_ptr = old_win;
+    }
+
+    // search for given pair; build list of windows found with current context
+    *first = true;
+    for (int i = 0; i < self->num_cur; ++i) {
+        assert(self->cur[i].ctx);
+        assert(self->cur[i].win);
+        if (self->cur[i].ctx == ctx && self->cur[i].win == win)
+            *first = false;
+        if (self->cur[i].ctx == self->current_context) {
+            *old_win++ = self->cur[i].win;
+            *old_win = NULL;
+        }
+    }
+
+    if (ctx && *first) {
+        assert(win);
+        // add to list
+        if (self->num_cur == self->len_cur) {
+            // grow list
+            self->len_cur += 5;
+            self->cur = realloc(self->cur,
+                                self->len_cur * sizeof(self->cur[0]));
+        }
+        // add at end
+        self->cur[self->num_cur].ctx = ctx;
+        self->cur[self->num_cur].win = win;
+        ++self->num_cur;
+    }
+
+    prt("ctx/win list after:\n"); for (int i = 0; i < self->num_cur; ++i) 
prt("  %p/%p\n", self->cur[i].ctx, self->cur[i].win);
+    self->current_context = ctx;
+
+    assert(self->num_cur <= self->len_cur);
+    return true;
+}
+
+// Remove entries from the list of context/window pairs whose
+// context == 'ctx' or whose window == 'win.'
+void
+wnull_display_clean(struct wnull_display *self,
+                    struct wnull_context *ctx,
+                    struct wnull_window *win)
+{
+    prt("cleaning dpy %p ctx %p win %p\n", self, ctx, win);
+    prt("ctx/win list before:\n"); for (int i = 0; i < self->num_cur; ++i) 
prt("  %p/%p\n", self->cur[i].ctx, self->cur[i].win);
+    for (int i = 0; i < self->num_cur;) {
+        assert(self->cur[i].ctx);
+        assert(self->cur[i].win);
+        if (self->cur[i].ctx == ctx || self->cur[i].win == win)
+            self->cur[i] = self->cur[--self->num_cur];
+        else
+            ++i;
+    }
+    prt("ctx/win list after:\n"); for (int i = 0; i < self->num_cur; ++i) 
prt("  %p/%p\n", self->cur[i].ctx, self->cur[i].win);
+}
diff --git a/src/waffle/null/wnull_display.h b/src/waffle/null/wnull_display.h
new file mode 100644
index 0000000..b92364b
--- /dev/null
+++ b/src/waffle/null/wnull_display.h
@@ -0,0 +1,93 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.txt file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "waffle_null.h"
+
+#include "wegl_display.h"
+
+struct wcore_platform;
+struct wgbm_platform;
+struct wnull_window;
+struct wnull_display_buffer;
+
+struct wnull_display {
+    struct wnull_context *current_context;
+    struct wegl_display wegl;
+
+    struct drm_display *drm;
+
+    struct ctx_win *cur; // list of context/window pairs which have been 
current
+    int num_cur; // number of items in list
+    int len_cur; // length of array
+};
+
+static inline struct wnull_display*
+wnull_display(struct wcore_display *wc_self)
+{
+    if (wc_self) {
+        struct wegl_display *wegl_self = container_of(wc_self, struct 
wegl_display, wcore);
+        return container_of(wegl_self, struct wnull_display, wegl);
+    } else {
+        return NULL;
+    }
+}
+
+struct wcore_display*
+wnull_display_connect(struct wcore_platform *wc_plat,
+                      const char *name);
+
+bool
+wnull_display_destroy(struct wcore_display *wc_self);
+
+void
+wnull_display_get_size(struct wnull_display *self,
+                       int32_t *width, int32_t *height);
+
+union waffle_native_display*
+wnull_display_get_native(struct wcore_display *wc_self);
+
+void
+wnull_display_fill_native(struct wnull_display *self,
+                          struct waffle_null_display *n_dpy);
+
+bool
+wnull_display_make_current(struct wnull_display *self,
+                           struct wnull_context *ctx,
+                           struct wnull_window *win,
+                           bool *first,
+                           struct wnull_window ***old_win);
+
+void
+wnull_display_clean(struct wnull_display *self,
+                    struct wnull_context *ctx,
+                    struct wnull_window *win);
+
+struct wnull_display_buffer *
+wnull_display_buffer_create(struct wnull_display *dpy,
+                            int width, int height,
+                            uint32_t format, uint32_t flags,
+                            void (*finish)());
+
+void
+wnull_display_buffer_destroy(struct wnull_display_buffer *self);
+
+bool
+wnull_display_buffer_dmabuf(struct wnull_display_buffer *self,
+                            int *fd,
+                            uint32_t *stride);
+
+bool
+wnull_display_show_buffer(struct wnull_display *self,
+                          struct wnull_display_buffer *buf);
+
+bool
+wnull_display_copy_buffer(struct wnull_display *self,
+                          struct wnull_display_buffer *buf);
diff --git a/src/waffle/null/wnull_platform.c b/src/waffle/null/wnull_platform.c
new file mode 100644
index 0000000..1dc3e7e
--- /dev/null
+++ b/src/waffle/null/wnull_platform.c
@@ -0,0 +1,103 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.txt file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+#define _POSIX_C_SOURCE 200112 // glib feature macro for unsetenv()
+
+#include <stdlib.h>
+#include <dlfcn.h>
+
+#include "wcore_error.h"
+
+#include "linux_platform.h"
+
+#include "wegl_config.h"
+#include "wegl_context.h"
+#include "wegl_platform.h"
+#include "wegl_util.h"
+
+#include "wgbm_config.h"
+#include "wgbm_platform.h"
+
+#include "wnull_context.h"
+#include "wnull_display.h"
+#include "wnull_platform.h"
+#include "wnull_window.h"
+
+static const struct wcore_platform_vtbl wnull_platform_vtbl;
+
+struct wcore_platform*
+wnull_platform_create(void)
+{
+    struct wgbm_platform *self = wcore_calloc(sizeof(*self));
+    if (self == NULL)
+        return NULL;
+
+    if (!wgbm_platform_init(self) ||
+        !self->wegl.eglCreateImageKHR ||
+        !self->wegl.eglDestroyImageKHR) {
+        wgbm_platform_destroy(&self->wegl.wcore);
+        return NULL;
+    }
+
+    setenv("EGL_PLATFORM", "null", true);
+
+    self->wegl.wcore.vtbl = &wnull_platform_vtbl;
+    return &self->wegl.wcore;
+}
+
+static union waffle_native_context*
+wnull_context_get_native(struct wcore_context *wc_ctx)
+{
+    struct wnull_display *dpy = wnull_display(wc_ctx->display);
+    struct wegl_context *ctx = wegl_context(wc_ctx);
+    union waffle_native_context *n_ctx;
+
+    WCORE_CREATE_NATIVE_UNION(n_ctx, null);
+    if (!n_ctx)
+        return NULL;
+
+    wnull_display_fill_native(dpy, &n_ctx->null->display);
+    n_ctx->null->egl_context = ctx->egl;
+
+    return n_ctx;
+}
+
+
+static const struct wcore_platform_vtbl wnull_platform_vtbl = {
+    .destroy = wgbm_platform_destroy,
+
+    .make_current = wnull_make_current,
+    .get_proc_address = wegl_get_proc_address,
+    .dl_can_open = wgbm_dl_can_open,
+    .dl_sym = wgbm_dl_sym,
+
+    .display = {
+        .connect = wnull_display_connect,
+        .destroy = wnull_display_destroy,
+        .supports_context_api = wegl_display_supports_context_api,
+        .get_native = wnull_display_get_native,
+    },
+
+    .config = {
+        .choose = wegl_config_choose,
+        .destroy = wegl_config_destroy,
+        .get_native = wgbm_config_get_native,
+    },
+
+    .context = {
+        .create = wnull_context_create,
+        .destroy = wnull_context_destroy,
+        .get_native = wnull_context_get_native,
+    },
+
+    .window = {
+        .create = wnull_window_create,
+        .destroy = wnull_window_destroy,
+        .show = wnull_window_show,
+        .swap_buffers = wnull_window_swap_buffers,
+        .get_native = wnull_window_get_native,
+    },
+};
diff --git a/src/waffle/null/wnull_platform.h b/src/waffle/null/wnull_platform.h
new file mode 100644
index 0000000..b707d69
--- /dev/null
+++ b/src/waffle/null/wnull_platform.h
@@ -0,0 +1,10 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.txt file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+#pragma once
+
+struct wcore_platform*
+wnull_platform_create(void);
diff --git a/src/waffle/null/wnull_window.c b/src/waffle/null/wnull_window.c
new file mode 100644
index 0000000..053dcb1
--- /dev/null
+++ b/src/waffle/null/wnull_window.c
@@ -0,0 +1,495 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.txt file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <drm_fourcc.h>
+
+#include "wcore_attrib_list.h"
+#include "wcore_error.h"
+#include "wcore_window.h"
+
+#include "wegl_platform.h"
+#include "wegl_util.h"
+
+#include "wgbm_config.h"
+
+#include "wnull_context.h"
+#include "wnull_display.h"
+#include "wnull_window.h"
+
+#define ARRAY_END(a) ((a)+sizeof(a)/sizeof((a)[0]))
+
+#if 0
+#include <stdio.h>
+#define prt(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define prt(...)
+#endif
+
+enum window_part {
+    WINDOW_PART_COLOR   = 0x01,
+    WINDOW_PART_DEPTH   = 0x02,
+    WINDOW_PART_STENCIL = 0x04,
+};
+
+struct window_buffer {
+    struct wnull_display_buffer *dpy_buf;
+    EGLImageKHR image;
+    GLuint fb;
+    GLuint color;
+    GLuint depth_stencil;
+};
+
+struct wnull_window {
+    uint32_t width;
+    uint32_t height;
+    unsigned parts;
+    bool show;
+    intptr_t show_method;
+    uint32_t gbm_format;
+    uint32_t drm_format;
+    GLenum depth_stencil_format;
+    struct window_buffer buffer[3];
+    struct window_buffer *current;
+    struct wcore_window wcore;
+};
+
+struct wnull_window*
+wnull_window(struct wcore_window *wcore_self)
+{
+    if (wcore_self)
+        return container_of(wcore_self, struct wnull_window, wcore);
+    else
+        return 0;
+}
+
+static void
+window_buffer_destroy_fb(struct window_buffer *self,
+                         struct wnull_context *ctx)
+{
+    prt("destroy fb %u\n",self->fb);
+    ctx->glDeleteRenderbuffers(1, &self->color);
+    ctx->glDeleteRenderbuffers(1, &self->depth_stencil);
+    ctx->glDeleteFramebuffers(1, &self->fb);
+    self->fb = 0;
+}
+
+// If there is a GL FB, 'ctx' is the context used to create it, otherwise
+// 'ctx' is NULL.
+static void
+window_buffer_teardown(struct window_buffer *self,
+                       struct wnull_display *dpy,
+                       struct wnull_context *ctx)
+{
+    struct wegl_platform *plat = wegl_platform(dpy->wegl.wcore.platform);
+
+    if (self->fb) {
+        assert(ctx);
+        window_buffer_destroy_fb(self, ctx);
+    }
+    if (self->image != EGL_NO_IMAGE_KHR) {
+        plat->eglDestroyImageKHR(dpy->wegl.egl, self->image);
+        self->image = EGL_NO_IMAGE_KHR;
+    }
+    wnull_display_buffer_destroy(self->dpy_buf);
+    self->dpy_buf = 0;
+}
+
+#define GLCHECK { \
+    GLenum e = ctx->glGetError(); \
+    if (e != GL_NO_ERROR) { \
+        prt(stderr, "gl error %x @ line %d\n", (int)e, __LINE__); \
+        wcore_errorf(WAFFLE_ERROR_UNKNOWN, "gl error %x @ line %d\n", (int)e, 
__LINE__); \
+        goto gl_error; \
+    } \
+}
+
+static bool
+wnull_window_prepare_current_buffer(struct wnull_window *self,
+                                    struct wnull_display *dpy)
+{
+    if (!self->parts)
+        return true;
+
+    struct wegl_platform *plat = wegl_platform(dpy->wegl.wcore.platform);
+    struct wnull_context *ctx = dpy->current_context;
+    assert(ctx);
+    GLint save_fb = -1;
+    GLint save_rb = -1;
+
+    if (!self->current)
+        self->current = self->buffer;
+
+    if (!self->current->dpy_buf)
+        self->current->image = EGL_NO_IMAGE_KHR;
+
+    if (!self->current->dpy_buf && (self->parts & WINDOW_PART_COLOR)) {
+
+        uint32_t flags = GBM_BO_USE_RENDERING;
+        // This flag should only be set when self->show == true and
+        // self->show_method == FLIP, but set it unconditionally because:
+        // 1) When show_method == COPY the buffer copy function
+        // may not handle the case of copying non-scanout to scanout
+        // due to different tiling.
+        // 2) If show == false it may be that waffle_window_show()
+        // hasn't been called yet.  The waffle api seems to allow calling
+        // it after waffle_make_current() but that is too late for us so
+        // set this now just in case we need it.
+        flags |= GBM_BO_USE_SCANOUT;
+
+        self->current->dpy_buf = wnull_display_buffer_create(dpy,
+                                                             self->width,
+                                                             self->height,
+                                                             self->gbm_format,
+                                                             flags,
+                                                             ctx->glFinish);
+        if (!self->current->dpy_buf) {
+            wcore_errorf(WAFFLE_ERROR_UNKNOWN, "display buffer create failed");
+            goto buf_error;
+        }
+
+        int dmabuf_fd;
+        uint32_t stride;
+        if (!wnull_display_buffer_dmabuf(self->current->dpy_buf,
+                                         &dmabuf_fd,
+                                         &stride)) {
+            wcore_errorf(WAFFLE_ERROR_UNKNOWN, "dmabuf failed");
+            goto buf_error;
+        }
+
+        EGLint attr[] = {
+            EGL_WIDTH, self->width,
+            EGL_HEIGHT, self->height,
+            EGL_LINUX_DRM_FOURCC_EXT, self->drm_format,
+            EGL_DMA_BUF_PLANE0_FD_EXT, dmabuf_fd,
+            EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
+            EGL_DMA_BUF_PLANE0_PITCH_EXT, stride,
+            EGL_NONE,
+        };
+        self->current->image = plat->eglCreateImageKHR(dpy->wegl.egl,
+                                                       EGL_NO_CONTEXT,
+                                                       EGL_LINUX_DMA_BUF_EXT,
+                                                       NULL,
+                                                       attr);
+        if (self->current->image == EGL_NO_IMAGE_KHR) {
+            wcore_errorf(WAFFLE_ERROR_UNKNOWN, "eglCreateImageKHR failed");
+            goto buf_error;
+        }
+    }
+
+    ctx->glGetIntegerv(GL_FRAMEBUFFER_BINDING, &save_fb);
+    GLCHECK
+    ctx->glGetIntegerv(GL_RENDERBUFFER_BINDING, &save_rb);
+    GLCHECK
+
+    if (self->current->fb) {
+        prt("use fb %u color %u depth/stencil %u\n", self->current->fb, 
self->current->color, self->current->depth_stencil);
+        ctx->glBindFramebuffer(GL_FRAMEBUFFER, self->current->fb);
+        GLCHECK
+    } else {
+        ctx->glGenFramebuffers(1, &self->current->fb);
+        GLCHECK
+        ctx->glBindFramebuffer(GL_FRAMEBUFFER, self->current->fb);
+        GLCHECK
+
+        if (self->parts & WINDOW_PART_COLOR) {
+            ctx->glGenRenderbuffers(1, &self->current->color);
+            GLCHECK
+            ctx->glBindRenderbuffer(GL_RENDERBUFFER, self->current->color);
+            GLCHECK
+            ctx->glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER,
+                                                        self->current->image);
+            GLCHECK
+            ctx->glFramebufferRenderbuffer(GL_FRAMEBUFFER,
+                                           GL_COLOR_ATTACHMENT0_EXT,
+                                           GL_RENDERBUFFER,
+                                           self->current->color);
+            GLCHECK
+        }
+
+        if (self->parts & (WINDOW_PART_DEPTH | WINDOW_PART_STENCIL)) {
+            ctx->glGenRenderbuffers(1, &self->current->depth_stencil);
+            GLCHECK
+            ctx->glBindRenderbuffer(GL_RENDERBUFFER, 
self->current->depth_stencil);
+            GLCHECK
+            ctx->glRenderbufferStorage(GL_RENDERBUFFER,
+                                       self->depth_stencil_format,
+                                       self->width,
+                                       self->height);
+            GLCHECK
+        }
+
+        if (self->parts & WINDOW_PART_DEPTH) {
+            ctx->glFramebufferRenderbuffer(GL_FRAMEBUFFER,
+                                           GL_DEPTH_ATTACHMENT,
+                                           GL_RENDERBUFFER,
+                                           self->current->depth_stencil);
+            GLCHECK
+        }
+
+        if (self->parts & WINDOW_PART_STENCIL) {
+            ctx->glFramebufferRenderbuffer(GL_FRAMEBUFFER,
+                                           GL_STENCIL_ATTACHMENT,
+                                           GL_RENDERBUFFER,
+                                           self->current->depth_stencil);
+            GLCHECK
+        }
+
+        prt("set up fb %u color %u depth/stencil %u\n", self->current->fb, 
self->current->color, self->current->depth_stencil);
+        GLenum s = ctx->glCheckFramebufferStatus(GL_FRAMEBUFFER);
+        GLCHECK
+        if (s != GL_FRAMEBUFFER_COMPLETE) {
+            prt(stderr, "incomplete fb\n");
+            goto gl_error;
+        }
+    }
+
+    return true;
+
+buf_error:
+    window_buffer_teardown(self->current, dpy, ctx);
+
+gl_error:
+    if (save_fb >= 0)
+        ctx->glBindFramebuffer(GL_FRAMEBUFFER, save_fb);
+    if (save_rb >= 0)
+        ctx->glBindFramebuffer(GL_RENDERBUFFER, save_rb);
+    return false;
+}
+
+bool
+wnull_window_destroy(struct wcore_window *wc_self)
+{
+    if (!wc_self)
+        return true;
+
+    struct wnull_window *self = wnull_window(wc_self);
+    prt("window destroy %p\n", self);
+    if (wc_self->display) {
+        struct wnull_display *dpy = wnull_display(wc_self->display);
+        struct window_buffer *buf;
+        for (buf = self->buffer; buf != ARRAY_END(self->buffer); ++buf) {
+            // If the buffer has a GL FB, that FB must exist in the current
+            // context, because when contexts cease to be current we destroy
+            // all their GL FBs.
+            window_buffer_teardown(buf,
+                                   dpy,
+                                   buf->fb ? dpy->current_context : NULL);
+        }
+
+        // tell the display this window is gone
+        wnull_display_clean(dpy, NULL, self);
+    }
+
+    free(self);
+    return true;
+}
+
+static void
+wnull_window_destroy_fbs(struct wnull_window *self,
+                         struct wnull_context *ctx)
+{
+    struct window_buffer *buf;
+    for (buf = self->buffer; buf != ARRAY_END(self->buffer); ++buf)
+        window_buffer_destroy_fb(buf, ctx);
+}
+
+struct wcore_window*
+wnull_window_create(struct wcore_platform *wc_plat,
+                    struct wcore_config *wc_config,
+                    int32_t width,
+                    int32_t height,
+                    const intptr_t attrib_list[])
+{
+
+    struct wnull_window *window = wcore_calloc(sizeof(*window));
+    if (!window)
+        return NULL;
+
+#if 0
+    //XXX EGL_PLATFORM_NULL is not providing EGL_NATIVE_VISUAL_ID
+    // so we can't use this yet.
+    window->gbm_format = wgbm_config_get_gbm_format(wc_plat,
+                                                    wc_config->display,
+                                                    wc_config);
+#else
+    if (wc_config->attrs.alpha_size <= 0)
+        window->gbm_format = GBM_FORMAT_XRGB8888;
+    else if (wc_config->attrs.alpha_size <= 8)
+        window->gbm_format = GBM_FORMAT_ARGB8888;
+    else {
+        wcore_errorf(WAFFLE_ERROR_UNKNOWN, "unexpected alpha size");
+        goto error;
+    }
+#endif
+
+    switch(window->gbm_format) {
+        case GBM_FORMAT_XRGB8888:
+            window->drm_format = DRM_FORMAT_XRGB8888;
+            break;
+        case GBM_FORMAT_ARGB8888:
+            window->drm_format = DRM_FORMAT_ARGB8888;
+            break;
+        case GBM_FORMAT_XRGB2101010:
+            window->drm_format = DRM_FORMAT_XRGB2101010;
+            break;
+        case GBM_FORMAT_ARGB2101010:
+            window->drm_format = DRM_FORMAT_ARGB2101010;
+            break;
+        case GBM_FORMAT_RGB565:
+            window->drm_format = DRM_FORMAT_RGB565;
+            break;
+        default:
+            wcore_errorf(WAFFLE_ERROR_UNKNOWN,
+                         "unexpected gbm format %x",
+                         window->gbm_format);
+            goto error;
+    }
+
+    intptr_t unused;
+    if (wcore_attrib_list_get(attrib_list,
+                              WAFFLE_WINDOW_FULLSCREEN,
+                              &unused))
+        wnull_display_get_size(wnull_display(wc_config->display), &width, 
&height);
+    window->width = width;
+    window->height = height;
+
+    if (!wcore_attrib_list_get(attrib_list,
+                               WAFFLE_WINDOW_NULL_SHOW_METHOD,
+                               &window->show_method))
+        window->show_method = WAFFLE_WINDOW_NULL_SHOW_METHOD_COPY;
+
+    window->parts = 0;
+    if (wc_config->attrs.rgba_size > 0)
+        window->parts |= WINDOW_PART_COLOR;
+    if (wc_config->attrs.depth_size > 0)
+        window->parts |= WINDOW_PART_DEPTH;
+    if (wc_config->attrs.stencil_size > 0)
+        window->parts |= WINDOW_PART_STENCIL;
+
+    if (wc_config->attrs.stencil_size)
+        window->depth_stencil_format = GL_DEPTH24_STENCIL8_OES;
+    else if (wc_config->attrs.depth_size <= 16)
+        window->depth_stencil_format = GL_DEPTH_COMPONENT16;
+    else if (wc_config->attrs.depth_size <= 24)
+        window->depth_stencil_format = GL_DEPTH_COMPONENT24_OES;
+    else
+        window->depth_stencil_format = GL_DEPTH_COMPONENT32_OES;
+
+    if (wcore_window_init(&window->wcore, wc_config))
+        return &window->wcore;
+
+error:
+    wnull_window_destroy(&window->wcore);
+    return NULL;
+}
+
+bool
+wnull_window_show(struct wcore_window *wc_self)
+{
+    struct wnull_window *self = wnull_window(wc_self);
+    self->show = true;
+    return true;
+}
+
+bool
+wnull_window_swap_buffers(struct wcore_window *wc_self)
+{
+    struct wnull_window *self = wnull_window(wc_self);
+    struct wnull_display *dpy = wnull_display(wc_self->display);
+    int ok = true;
+
+    assert(self->current);
+    assert(self->current->fb);
+    GLint cur_fb;
+    dpy->current_context->glGetIntegerv(GL_FRAMEBUFFER_BINDING, &cur_fb);
+    // if the user has bound their own framebuffer, bail out
+    if (cur_fb && cur_fb != self->current->fb)
+        return true;
+
+    if (self->show && (self->parts & WINDOW_PART_COLOR)) {
+        assert(self->current->dpy_buf);
+        if (self->show_method == WAFFLE_WINDOW_NULL_SHOW_METHOD_FLIP)
+            ok &= wnull_display_show_buffer(dpy, self->current->dpy_buf);
+        else if (self->show_method == WAFFLE_WINDOW_NULL_SHOW_METHOD_COPY)
+            ok &= wnull_display_copy_buffer(dpy, self->current->dpy_buf);
+    }
+
+    if (ok) {
+        ++self->current;
+        if (self->current == ARRAY_END(self->buffer))
+            self->current = self->buffer;
+        ok &= wnull_window_prepare_current_buffer(self, dpy);
+    }
+
+    return ok;
+}
+
+bool
+wnull_make_current(struct wcore_platform *wc_plat,
+                   struct wcore_display *wc_dpy,
+                   struct wcore_window *wc_window,
+                   struct wcore_context *wc_ctx)
+{
+    struct wegl_platform *plat = wegl_platform(wc_plat);
+    struct wnull_display *dpy = wnull_display(wc_dpy);
+    struct wnull_context *ctx = wnull_context(wc_ctx);
+    struct wnull_context *old_ctx = dpy->current_context;
+    struct wnull_window *win = wnull_window(wc_window);
+
+    bool first; // first time the context/window pair will be current?
+    struct wnull_window **old_win = NULL; // list of windows in old context
+    if (!wnull_display_make_current(dpy, ctx, win, &first, &old_win))
+        return false;
+
+    // When the current context is changed to a different one we must
+    // delete any framebuffers created in the first context as it may
+    // not be seen again.
+    if (old_ctx && old_ctx != ctx && old_win)
+        for (struct wnull_window **w = old_win; *w; ++w)
+            wnull_window_destroy_fbs(*w, old_ctx);
+    free(old_win);
+
+    if (!plat->eglMakeCurrent(dpy->wegl.egl,
+                              EGL_NO_SURFACE,
+                              EGL_NO_SURFACE,
+                              ctx ? ctx->wegl.egl : EGL_NO_CONTEXT)) {
+        wegl_emit_error(plat, "eglMakeCurrent");
+        return false;
+    }
+
+    bool ok = true;
+    if (ctx && win) {
+        ok = wnull_window_prepare_current_buffer(win, dpy);
+        if (ok && first) {
+            prt("setting viewport\n");
+            ctx->glViewport(0, 0, win->width, win->height);
+            ctx->glScissor(0, 0, win->width, win->height);
+        }
+    }
+
+    return ok;
+}
+
+union waffle_native_window*
+wnull_window_get_native(struct wcore_window *wc_self)
+{
+    struct wnull_window *self = wnull_window(wc_self);
+    struct wnull_display *dpy = wnull_display(wc_self->display);
+    union waffle_native_window *n_window;
+
+    WCORE_CREATE_NATIVE_UNION(n_window, null);
+    if (n_window == NULL)
+        return NULL;
+
+    wnull_display_fill_native(dpy, &n_window->null->display);
+    n_window->null->width = self->width;
+    n_window->null->height = self->height;
+
+    return n_window;
+}
diff --git a/src/waffle/null/wnull_window.h b/src/waffle/null/wnull_window.h
new file mode 100644
index 0000000..fce16e9
--- /dev/null
+++ b/src/waffle/null/wnull_window.h
@@ -0,0 +1,44 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.txt file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+#pragma once
+
+#include <stdbool.h>
+
+struct wcore_config;
+struct wcore_display;
+struct wcore_platform;
+struct wcore_window;
+
+struct wnull_window;
+
+struct wnull_window*
+wnull_window(struct wcore_window *wc_self);
+
+struct wcore_window*
+wnull_window_create(struct wcore_platform *wc_plat,
+                   struct wcore_config *wc_config,
+                   int32_t width,
+                   int32_t height,
+                   const intptr_t attrib_list[]);
+
+bool
+wnull_window_destroy(struct wcore_window *wc_self);
+
+bool
+wnull_window_show(struct wcore_window *wc_self);
+
+bool
+wnull_window_swap_buffers(struct wcore_window *wc_self);
+
+union waffle_native_window*
+wnull_window_get_native(struct wcore_window *wc_self);
+
+bool
+wnull_make_current(struct wcore_platform *wc_plat,
+                   struct wcore_display *wc_dpy,
+                   struct wcore_window *wc_window,
+                   struct wcore_context *wc_ctx);
-- 
2.2.0.rc0.207.ga3a616c

_______________________________________________
waffle mailing list
waffle@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/waffle

Reply via email to