diff mbox

[Branch,~glmark2-dev/glmark2/trunk] Rev 202: Canvas*: Add support for rendering to an off-screen surface.

Message ID 20120322104018.25386.9065.launchpad@ackee.canonical.com
State Accepted
Headers show

Commit Message

alexandros.frantzis@linaro.org March 22, 2012, 10:40 a.m. UTC
Merge authors:
  Alexandros Frantzis (afrantzis)
------------------------------------------------------------
revno: 202 [merge]
committer: Alexandros Frantzis <alexandros.frantzis@linaro.org>
branch nick: trunk
timestamp: Thu 2012-03-22 12:37:04 +0200
message:
  Canvas*: Add support for rendering to an off-screen surface.
modified:
  doc/glmark2.1.in
  src/canvas-x11-egl.cpp
  src/canvas-x11-egl.h
  src/canvas-x11-glx.cpp
  src/canvas-x11-glx.h
  src/canvas-x11.cpp
  src/canvas-x11.h
  src/canvas.h
  src/gl-headers.cpp
  src/gl-headers.h
  src/main.cpp
  src/options.cpp
  src/options.h
  src/scene-desktop.cpp


--
lp:glmark2
https://code.launchpad.net/~glmark2-dev/glmark2/trunk

You are subscribed to branch lp:glmark2.
To unsubscribe from this branch go to https://code.launchpad.net/~glmark2-dev/glmark2/trunk/+edit-subscription
diff mbox

Patch

=== modified file 'doc/glmark2.1.in'
--- doc/glmark2.1.in	2012-02-16 11:45:12 +0000
+++ doc/glmark2.1.in	2012-03-22 09:40:12 +0000
@@ -21,15 +21,17 @@ 
 Run a quick output validation test instead of
 running the benchmarks
 .TP
-\fB\-\-no\-swap\-buffers\fR
-Don't update the screen by swapping the front and
-back buffer, use glFinish() instead
+\fB\-\-frame-end\fR METHOD
+How to end a frame [default,none,swap,finish,readpixels]
+.TP
+\fB\-\-off-screen\fR
+Render to an off-screen surface
 .TP
 \fB\-\-reuse\-context\fR
 Use a single context for all scenes
 (by default, each scene gets its own context)
 .TP
-\fB\-s\fR, \fB\-\-size WxH\fR
+\fB\-s\fR, \fB\-\-size\fR WxH
 Size of the output window (default: 800x600)
 .TP
 \fB\-l\fR, \fB\-\-list\-scenes\fR

=== modified file 'src/canvas-x11-egl.cpp'
--- src/canvas-x11-egl.cpp	2012-01-04 16:51:20 +0000
+++ src/canvas-x11-egl.cpp	2012-03-20 11:16:03 +0000
@@ -86,6 +86,20 @@ 
     return true;
 }
 
+void
+CanvasX11EGL::get_glvisualinfo(GLVisualInfo &gl_visinfo)
+{
+    if (!ensure_egl_config())
+        return;
+
+    eglGetConfigAttrib(egl_display_, egl_config_, EGL_BUFFER_SIZE, &gl_visinfo.buffer_size);
+    eglGetConfigAttrib(egl_display_, egl_config_, EGL_RED_SIZE, &gl_visinfo.red_size);
+    eglGetConfigAttrib(egl_display_, egl_config_, EGL_GREEN_SIZE, &gl_visinfo.green_size);
+    eglGetConfigAttrib(egl_display_, egl_config_, EGL_BLUE_SIZE, &gl_visinfo.blue_size);
+    eglGetConfigAttrib(egl_display_, egl_config_, EGL_ALPHA_SIZE, &gl_visinfo.alpha_size);
+    eglGetConfigAttrib(egl_display_, egl_config_, EGL_DEPTH_SIZE, &gl_visinfo.depth_size);
+}
+
 /*******************
  * Private methods *
  *******************/
@@ -256,18 +270,7 @@ 
 CanvasX11EGL::init_gl_extensions()
 {
 #if USE_GLESv2
-    /*
-     * Parse the extensions we care about from the extension string.
-     * Don't even bother to get function pointers until we know the
-     * extension is present.
-     */
-    std::string extString;
-    const char* exts = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
-    if (exts) {
-        extString = exts;
-    }
-
-    if (extString.find("GL_OES_mapbuffer") != std::string::npos) {
+    if (GLExtensions::support("GL_OES_mapbuffer")) {
         GLExtensions::MapBuffer =
             reinterpret_cast<PFNGLMAPBUFFEROESPROC>(eglGetProcAddress("glMapBufferOES"));
         GLExtensions::UnmapBuffer =

=== modified file 'src/canvas-x11-egl.h'
--- src/canvas-x11-egl.h	2012-01-04 00:37:12 +0000
+++ src/canvas-x11-egl.h	2012-03-19 16:55:27 +0000
@@ -43,6 +43,7 @@ 
     bool make_current();
     bool reset_context();
     void swap_buffers() { eglSwapBuffers(egl_display_, egl_surface_); }
+    void get_glvisualinfo(GLVisualInfo &gl_visinfo);
 
 private:
     bool ensure_egl_display();

=== modified file 'src/canvas-x11-glx.cpp'
--- src/canvas-x11-glx.cpp	2012-01-04 00:37:12 +0000
+++ src/canvas-x11-glx.cpp	2012-03-19 16:55:27 +0000
@@ -68,6 +68,19 @@ 
     return true;
 }
 
+void
+CanvasX11GLX::get_glvisualinfo(GLVisualInfo &gl_visinfo)
+{
+    if (!ensure_glx_fbconfig())
+        return;
+
+    glXGetFBConfigAttrib(xdpy_, glx_fbconfig_, GLX_BUFFER_SIZE, &gl_visinfo.buffer_size);
+    glXGetFBConfigAttrib(xdpy_, glx_fbconfig_, GLX_RED_SIZE, &gl_visinfo.red_size);
+    glXGetFBConfigAttrib(xdpy_, glx_fbconfig_, GLX_GREEN_SIZE, &gl_visinfo.green_size);
+    glXGetFBConfigAttrib(xdpy_, glx_fbconfig_, GLX_BLUE_SIZE, &gl_visinfo.blue_size);
+    glXGetFBConfigAttrib(xdpy_, glx_fbconfig_, GLX_ALPHA_SIZE, &gl_visinfo.alpha_size);
+    glXGetFBConfigAttrib(xdpy_, glx_fbconfig_, GLX_DEPTH_SIZE, &gl_visinfo.depth_size);
+}
 
 /*******************
  * Private methods *

=== modified file 'src/canvas-x11-glx.h'
--- src/canvas-x11-glx.h	2012-01-04 00:37:12 +0000
+++ src/canvas-x11-glx.h	2012-03-19 16:55:27 +0000
@@ -43,6 +43,7 @@ 
     bool make_current();
     bool reset_context();
     void swap_buffers() { glXSwapBuffers(xdpy_, xwin_); }
+    void get_glvisualinfo(GLVisualInfo &gl_visinfo);
 
 private:
     bool check_glx_version();

=== modified file 'src/canvas-x11.cpp'
--- src/canvas-x11.cpp	2012-01-04 00:37:12 +0000
+++ src/canvas-x11.cpp	2012-03-20 12:55:49 +0000
@@ -35,10 +35,12 @@ 
 bool
 CanvasX11::reset()
 {
+    release_fbo();
+
     if (!reset_context())
         return false;
 
-    if (!make_current())
+    if (!do_make_current())
         return false;
 
     if (!supports_gl2()) {
@@ -78,7 +80,7 @@ 
 void
 CanvasX11::visible(bool visible)
 {
-    if (visible)
+    if (visible && !offscreen_)
         XMapWindow(xdpy_, xwin_);
 }
 
@@ -97,16 +99,35 @@ 
 void
 CanvasX11::update()
 {
-    if (Options::swap_buffers)
-        swap_buffers();
-    else
-        glFinish();
+    Options::FrameEnd m = Options::frame_end;
+
+    if (m == Options::FrameEndDefault) {
+        if (offscreen_)
+            m = Options::FrameEndFinish;
+        else
+            m = Options::FrameEndSwap;
+    }
+
+    switch(m) {
+        case Options::FrameEndSwap:
+            swap_buffers();
+            break;
+        case Options::FrameEndFinish:
+            glFinish();
+            break;
+        case Options::FrameEndReadPixels:
+            read_pixel(width_ / 2, height_ / 2);
+            break;
+        case Options::FrameEndNone:
+        default:
+            break;
+    }
 }
 
 void
 CanvasX11::print_info()
 {
-    make_current();
+    do_make_current();
 
     std::stringstream ss;
 
@@ -173,6 +194,12 @@ 
     glViewport(0, 0, width_, height_);
 }
 
+unsigned int
+CanvasX11::fbo()
+{
+    return fbo_;
+}
+
 bool
 CanvasX11::supports_gl2()
 {
@@ -285,7 +312,183 @@ 
     if (!ensure_x_window())
         Log::error("Error: Couldn't create X Window!\n");
 
+    if (color_renderbuffer_) {
+        glBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer_);
+        glRenderbufferStorage(GL_RENDERBUFFER, gl_color_format_,
+                              width_, height_);
+    }
+
+    if (depth_renderbuffer_) {
+        glBindRenderbuffer(GL_RENDERBUFFER, depth_renderbuffer_);
+        glRenderbufferStorage(GL_RENDERBUFFER, gl_depth_format_,
+                              width_, height_);
+    }
+
     projection_ = LibMatrix::Mat4::perspective(60.0, width_ / static_cast<float>(height_),
                                                1.0, 1024.0);
 }
 
+bool
+CanvasX11::do_make_current()
+{
+    if (!make_current())
+        return false;
+
+    if (offscreen_) {
+        if (!ensure_fbo())
+            return false;
+
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
+    }
+
+    return true;
+}
+
+bool
+CanvasX11::ensure_gl_formats()
+{
+    if (gl_color_format_ && gl_depth_format_)
+        return true;
+
+    GLVisualInfo gl_visinfo;
+    get_glvisualinfo(gl_visinfo);
+
+    gl_color_format_ = 0;
+    gl_depth_format_ = 0;
+
+    bool supports_rgba8(false);
+    bool supports_rgb8(false);
+    bool supports_depth24(false);
+    bool supports_depth32(false);
+
+#if USE_GLESv2
+    if (GLExtensions::support("GL_ARM_rgba8"))
+        supports_rgba8 = true;
+
+    if (GLExtensions::support("GL_OES_rgb8_rgba8")) {
+        supports_rgba8 = true;
+        supports_rgb8 = true;
+    }
+
+    if (GLExtensions::support("GL_OES_depth24"))
+        supports_depth24 = true;
+
+    if (GLExtensions::support("GL_OES_depth32"))
+        supports_depth32 = true;
+#elif USE_GL
+    supports_rgba8 = true;
+    supports_rgb8 = true;
+    supports_depth24 = true;
+    supports_depth32 = true;
+#endif
+
+    if (gl_visinfo.buffer_size == 32) {
+        if (supports_rgba8)
+            gl_color_format_ = GL_RGBA8;
+        else
+            gl_color_format_ = GL_RGBA4;
+    }
+    else if (gl_visinfo.buffer_size == 24) {
+        if (supports_rgb8)
+            gl_color_format_ = GL_RGB8;
+        else
+            gl_color_format_ = GL_RGB565;
+    }
+    else if (gl_visinfo.buffer_size == 16) {
+        if (gl_visinfo.red_size == 4 && gl_visinfo.green_size == 4 &&
+            gl_visinfo.blue_size == 4 && gl_visinfo.alpha_size == 4)
+        {
+            gl_color_format_ = GL_RGBA4;
+        }
+        else if (gl_visinfo.red_size == 5 && gl_visinfo.green_size == 5 &&
+                 gl_visinfo.blue_size == 5 && gl_visinfo.alpha_size == 1)
+        {
+            gl_color_format_ = GL_RGB5_A1;
+        }
+        else if (gl_visinfo.red_size == 5 && gl_visinfo.green_size == 6 &&
+                 gl_visinfo.blue_size == 5 && gl_visinfo.alpha_size == 0)
+        {
+            gl_color_format_ = GL_RGB565;
+        }
+    }
+
+    if (gl_visinfo.depth_size == 32 && supports_depth32)
+        gl_depth_format_ = GL_DEPTH_COMPONENT32;
+    else if (gl_visinfo.depth_size >= 24 && supports_depth24)
+        gl_depth_format_ = GL_DEPTH_COMPONENT24;
+    else if (gl_visinfo.depth_size == 16)
+        gl_depth_format_ = GL_DEPTH_COMPONENT16;
+
+    Log::debug("Selected Renderbuffer ColorFormat: %s DepthFormat: %s\n",
+               get_gl_format_str(gl_color_format_),
+               get_gl_format_str(gl_depth_format_));
+
+    return (gl_color_format_ && gl_depth_format_);
+}
+
+bool
+CanvasX11::ensure_fbo()
+{
+    if (!fbo_) {
+        if (!ensure_gl_formats())
+            return false;
+
+        /* Create a texture for the color attachment  */
+        glGenRenderbuffers(1, &color_renderbuffer_);
+        glBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer_);
+        glRenderbufferStorage(GL_RENDERBUFFER, gl_color_format_,
+                              width_, height_);
+
+        /* Create a renderbuffer for the depth attachment */
+        glGenRenderbuffers(1, &depth_renderbuffer_);
+        glBindRenderbuffer(GL_RENDERBUFFER, depth_renderbuffer_);
+        glRenderbufferStorage(GL_RENDERBUFFER, gl_depth_format_,
+                              width_, height_);
+
+        /* Create a FBO and set it up */
+        glGenFramebuffers(1, &fbo_);
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                  GL_RENDERBUFFER, color_renderbuffer_);
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+                                  GL_RENDERBUFFER, depth_renderbuffer_);
+    }
+
+    return true;
+}
+
+void
+CanvasX11::release_fbo()
+{
+    glDeleteFramebuffers(1, &fbo_);
+    glDeleteRenderbuffers(1, &color_renderbuffer_);
+    glDeleteRenderbuffers(1, &depth_renderbuffer_);
+    fbo_ = 0;
+    color_renderbuffer_ = 0;
+    depth_renderbuffer_ = 0;
+
+    gl_color_format_ = 0;
+    gl_depth_format_ = 0;
+}
+
+const char *
+CanvasX11::get_gl_format_str(GLenum f)
+{
+    const char *str;
+
+    switch(f) {
+        case GL_RGBA8: str = "GL_RGBA8"; break;
+        case GL_RGB8: str = "GL_RGB8"; break;
+        case GL_RGBA4: str = "GL_RGBA4"; break;
+        case GL_RGB5_A1: str = "GL_RGB5_A1"; break;
+        case GL_RGB565: str = "GL_RGB565"; break;
+        case GL_DEPTH_COMPONENT16: str = "GL_DEPTH_COMPONENT16"; break;
+        case GL_DEPTH_COMPONENT24: str = "GL_DEPTH_COMPONENT24"; break;
+        case GL_DEPTH_COMPONENT32: str = "GL_DEPTH_COMPONENT32"; break;
+        case GL_NONE: str = "GL_NONE"; break;
+        default: str = "Unknown"; break;
+    }
+
+    return str;
+}
+

=== modified file 'src/canvas-x11.h'
--- src/canvas-x11.h	2012-01-04 00:37:12 +0000
+++ src/canvas-x11.h	2012-03-20 11:29:09 +0000
@@ -44,10 +44,25 @@ 
     virtual void write_to_file(std::string &filename);
     virtual bool should_quit();
     virtual void resize(int width, int height);
+    virtual unsigned int fbo();
 
 protected:
     CanvasX11(int width, int height) :
-        Canvas(width, height), xwin_(0), xdpy_(0) {}
+        Canvas(width, height), xwin_(0), xdpy_(0),
+        gl_color_format_(0), gl_depth_format_(0),
+        color_renderbuffer_(0), depth_renderbuffer_(0), fbo_(0) {}
+
+    /**
+     * Information about a GL visual.
+     */
+    struct GLVisualInfo {
+        int buffer_size;
+        int red_size;
+        int green_size;
+        int blue_size;
+        int alpha_size;
+        int depth_size;
+    };
 
     /**
      * Gets the XVisualInfo to use for creating the X window with.
@@ -88,6 +103,13 @@ 
     virtual void swap_buffers() = 0;
 
     /**
+     * Gets information about the GL visual used for this canvas.
+     *
+     * This method should be implemented in derived classes.
+     */
+    virtual void get_glvisualinfo(GLVisualInfo &gl_visinfo) = 0;
+
+    /**
      * Whether the current implementation supports GL(ES) 2.0.
      *
      * @return true if it supports GL(ES) 2.0, false otherwise
@@ -103,6 +125,18 @@ 
 private:
     void resize_no_viewport(int width, int height);
     bool ensure_x_window();
+    bool do_make_current();
+    bool ensure_gl_formats();
+    bool ensure_fbo();
+    void release_fbo();
+
+    const char *get_gl_format_str(GLenum f);
+
+    GLenum gl_color_format_;
+    GLenum gl_depth_format_;
+    GLuint color_renderbuffer_;
+    GLuint depth_renderbuffer_;
+    GLuint fbo_;
 };
 
 #endif

=== modified file 'src/canvas.h'
--- src/canvas.h	2012-01-04 00:37:12 +0000
+++ src/canvas.h	2012-03-15 12:17:22 +0000
@@ -191,6 +191,13 @@ 
     virtual void resize(int width, int height) { static_cast<void>(width); static_cast<void>(height); }
 
     /**
+     * Gets the FBO associated with the canvas.
+     *
+     * @return the FBO
+     */
+    virtual unsigned int fbo() { return 0; }
+
+    /**
      * Gets a dummy canvas object.
      *
      * @return the dummy canvas
@@ -224,12 +231,21 @@ 
      */
     const LibMatrix::mat4 &projection() { return projection_; }
 
+    /**
+     * Sets whether the canvas should be backed by an off-screen surface.
+     *
+     * This takes effect after the next init()/reset().
+     */
+    void offscreen(bool offscreen) { offscreen_ = offscreen; }
+
 protected:
-    Canvas(int width, int height) : width_(width), height_(height) {}
+    Canvas(int width, int height) :
+        width_(width), height_(height), offscreen_(false) {}
 
     int width_;
     int height_;
     LibMatrix::mat4 projection_;
+    bool offscreen_;
 };
 
 #endif

=== modified file 'src/gl-headers.cpp'
--- src/gl-headers.cpp	2011-10-10 11:00:36 +0000
+++ src/gl-headers.cpp	2012-03-20 11:15:27 +0000
@@ -24,3 +24,23 @@ 
 void* (*GLExtensions::MapBuffer) (GLenum target, GLenum access) = 0;
 GLboolean (*GLExtensions::UnmapBuffer) (GLenum target) = 0;
 
+bool
+GLExtensions::support(const std::string &ext)
+{
+    std::string ext_string;
+    const char* exts = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
+    if (exts) {
+        ext_string = exts;
+    }
+
+    const size_t ext_size = ext.size();
+    size_t pos = 0;
+
+    while ((pos = ext_string.find(ext, pos)) != std::string::npos) {
+        char c = ext_string[pos + ext_size];
+        if (c == ' ' || c == '\0')
+            break;
+    }
+
+    return pos != std::string::npos;
+}

=== modified file 'src/gl-headers.h'
--- src/gl-headers.h	2011-10-26 11:12:19 +0000
+++ src/gl-headers.h	2012-03-20 11:29:09 +0000
@@ -27,19 +27,43 @@ 
 #if USE_GL
 #include <GL/gl.h>
 #include <GL/glext.h>
+#ifndef GL_RGB565
+#define GL_RGB565 0x8D62
+#endif
 #elif USE_GLESv2
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 #ifndef GL_WRITE_ONLY
 #define GL_WRITE_ONLY GL_WRITE_ONLY_OES
 #endif
-#endif
+#ifndef GL_DEPTH_COMPONENT24
+#define GL_DEPTH_COMPONENT24 GL_DEPTH_COMPONENT24_OES
+#endif
+#ifndef GL_DEPTH_COMPONENT32
+#define GL_DEPTH_COMPONENT32 GL_DEPTH_COMPONENT32_OES
+#endif
+#ifndef GL_RGBA8
+#define GL_RGBA8 GL_RGBA8_OES
+#endif
+#ifndef GL_RGB8
+#define GL_RGB8 GL_RGB8_OES
+#endif
+#endif
+
+#include <string>
 
 /**
  * Struct that holds pointers to functions that belong to extensions
  * in either GL2.0 or GLES2.0.
  */
 struct GLExtensions {
+    /**
+     * Whether the current context has support for a GL extension.
+     *
+     * @return true if the extension is supported
+     */
+    static bool support(const std::string &ext);
+
     static void* (*MapBuffer) (GLenum target, GLenum access);
     static GLboolean (*UnmapBuffer) (GLenum target);
 };

=== modified file 'src/main.cpp'
--- src/main.cpp	2012-03-09 09:40:49 +0000
+++ src/main.cpp	2012-03-22 10:37:04 +0000
@@ -165,6 +165,8 @@ 
     CanvasX11EGL canvas(Options::size.first, Options::size.second);
 #endif
 
+    canvas.offscreen(Options::offscreen);
+
     vector<Scene*> scenes;
 
     // Register the scenes, so they can be looked up by name

=== modified file 'src/options.cpp'
--- src/options.cpp	2012-02-16 09:03:42 +0000
+++ src/options.cpp	2012-03-20 12:55:49 +0000
@@ -33,7 +33,7 @@ 
 std::vector<std::string> Options::benchmarks;
 std::vector<std::string> Options::benchmark_files;
 bool Options::validate = false;
-bool Options::swap_buffers = true;
+Options::FrameEnd Options::frame_end = Options::FrameEndDefault;
 std::pair<int,int> Options::size(800, 600);
 bool Options::list_scenes = false;
 bool Options::show_all_options = false;
@@ -42,13 +42,15 @@ 
 bool Options::reuse_context = false;
 bool Options::run_forever = false;
 bool Options::annotate = false;
+bool Options::offscreen = false;
 
 static struct option long_options[] = {
     {"annotate", 0, 0, 0},
     {"benchmark", 1, 0, 0},
     {"benchmark-file", 1, 0, 0},
     {"validate", 0, 0, 0},
-    {"no-swap-buffers", 0, 0, 0},
+    {"frame-end", 1, 0, 0},
+    {"off-screen", 0, 0, 0},
     {"reuse-context", 0, 0, 0},
     {"run-forever", 0, 0, 0},
     {"size", 1, 0, 0},
@@ -83,6 +85,31 @@ 
         size.second = size.first;
 }
 
+/**
+ * Parses a frame-end method string
+ *
+ * @param str the string to parse
+ *
+ * @return the parsed frame end method
+ */
+static Options::FrameEnd 
+frame_end_from_str(const std::string &str)
+{
+    Options::FrameEnd m = Options::FrameEndDefault;
+
+    if (str == "swap")
+        m = Options::FrameEndSwap;
+    else if (str == "finish")
+        m = Options::FrameEndFinish;
+    else if (str == "readpixels")
+        m = Options::FrameEndReadPixels;
+    else if (str == "none")
+        m = Options::FrameEndNone;
+
+    return m;
+}
+
+
 void
 Options::print_help()
 {
@@ -96,8 +123,8 @@ 
            "                         (the option can be used multiple times)\n"
            "      --validate         Run a quick output validation test instead of \n"
            "                         running the benchmarks\n"
-           "      --no-swap-buffers  Don't update the canvas by swapping the front and\n"
-           "                         back buffer, use glFinish() instead\n"
+           "      --frame-end METHOD How to end a frame [default,none,swap,finish,readpixels]\n"
+           "      --off-screen       Render to an off-screen surface\n"
            "      --reuse-context    Use a single context for all scenes\n"
            "                         (by default, each scene gets its own context)\n"
            "  -s, --size WxH         Size of the output window (default: 800x600)\n"
@@ -139,8 +166,10 @@ 
             Options::benchmark_files.push_back(optarg);
         else if (!strcmp(optname, "validate"))
             Options::validate = true;
-        else if (!strcmp(optname, "no-swap-buffers"))
-            Options::swap_buffers = false;
+        else if (!strcmp(optname, "frame-end"))
+            Options::frame_end = frame_end_from_str(optarg);
+        else if (!strcmp(optname, "off-screen"))
+            Options::offscreen = true;
         else if (!strcmp(optname, "reuse-context"))
             Options::reuse_context = true;
         else if (c == 's' || !strcmp(optname, "size"))

=== modified file 'src/options.h'
--- src/options.h	2012-02-16 09:03:42 +0000
+++ src/options.h	2012-03-20 12:55:49 +0000
@@ -28,13 +28,21 @@ 
 #include <vector>
 
 struct Options {
+    enum FrameEnd {
+        FrameEndDefault,
+        FrameEndNone,
+        FrameEndSwap,
+        FrameEndFinish,
+        FrameEndReadPixels
+    };
+
     static bool parse_args(int argc, char **argv);
     static void print_help();
 
     static std::vector<std::string> benchmarks;
     static std::vector<std::string> benchmark_files;
     static bool validate;
-    static bool swap_buffers;
+    static FrameEnd frame_end;
     static std::pair<int,int> size;
     static bool list_scenes;
     static bool show_all_options;
@@ -43,6 +51,7 @@ 
     static bool reuse_context;
     static bool run_forever;
     static bool annotate;
+    static bool offscreen;
 };
 
 #endif /* OPTIONS_H_ */

=== modified file 'src/scene-desktop.cpp'
--- src/scene-desktop.cpp	2012-02-13 09:43:54 +0000
+++ src/scene-desktop.cpp	2012-03-12 15:32:07 +0000
@@ -356,6 +356,7 @@ 
 class RenderScreen : public RenderObject
 {
 public:
+    RenderScreen(Canvas &canvas) { fbo_ = canvas.fbo(); }
     virtual void init() {}
     virtual void release() {}
 };
@@ -736,8 +737,8 @@ 
     RenderClearImage desktop;
     std::vector<RenderObject *> windows;
 
-    SceneDesktopPrivate() :
-        desktop(GLMARK_DATA_PATH"/textures/effect-2d.png") {}
+    SceneDesktopPrivate(Canvas &canvas) :
+        screen(canvas), desktop(GLMARK_DATA_PATH"/textures/effect-2d.png") {}
 
     ~SceneDesktopPrivate() { Util::dispose_pointer_vector(windows); }
 
@@ -747,7 +748,7 @@ 
 SceneDesktop::SceneDesktop(Canvas &canvas) :
     Scene(canvas, "desktop")
 {
-    priv_ = new SceneDesktopPrivate();
+    priv_ = new SceneDesktopPrivate(canvas);
     options_["effect"] = Scene::Option("effect", "blur",
                                        "the effect to use [blur]");
     options_["windows"] = Scene::Option("windows", "4",