Implement offscreen rendering using GL framebuffer objects.

Apparently pbuffers are also a deprecated and unportable
way of offscreen rendering. They're not supported by
Chromium OpenGL implementation, which is what 3D-accelerated
VirtualBox uses.

This would also help a future OS X port.
This commit is contained in:
whitequark 2015-03-20 22:35:04 +03:00
parent 622c9efadb
commit e27c6b56c3
5 changed files with 151 additions and 53 deletions

View File

@ -82,6 +82,7 @@ else()
find_package(PkgConfig REQUIRED)
pkg_check_modules(FONTCONFIG REQUIRED fontconfig)
pkg_check_modules(JSONC REQUIRED json-c)
pkg_check_modules(GLEW REQUIRED glew)
set(HAVE_GTK TRUE)
if(GUI STREQUAL "gtk3")

View File

@ -142,25 +142,30 @@ elseif(HAVE_GTK)
include_directories(
${GTKMM_INCLUDE_DIRS}
${JSONC_INCLUDE_DIRS}
${FONTCONFIG_INCLUDE_DIRS})
${FONTCONFIG_INCLUDE_DIRS}
${GLEW_INCLUDE_DIRS})
link_directories(
${GTKMM_LIBRARY_DIRS}
${JSONC_LIBRARY_DIRS}
${FONTCONFIG_LIBRARY_DIRS})
${FONTCONFIG_LIBRARY_DIRS}
${GLEW_LIBRARY_DIRS})
add_definitions(
${GTKMM_CFLAGS_OTHER}
${JSONC_CFLAGS_OTHER}
${FONTCONFIG_CFLAGS_OTHER})
${FONTCONFIG_CFLAGS_OTHER}
${GLEW_CFLAGS_OTHER})
set(platform_SOURCES
gtk/gtkmain.cpp)
gtk/gtkmain.cpp
unix/gloffscreen.cpp)
set(platform_LIBRARIES
${GTKMM_LIBRARIES}
${JSONC_LIBRARIES}
${FONTCONFIG_LIBRARIES})
${FONTCONFIG_LIBRARIES}
${GLEW_LIBRARIES})
endif()
# solvespace executable

View File

@ -49,11 +49,11 @@
#undef HAVE_STDINT_H /* no thanks, we have our own config.h */
#define GLX_GLXEXT_PROTOTYPES
#include <GL/glx.h>
#include "solvespace.h"
#include <config.h>
#include "solvespace.h"
#include "../unix/gloffscreen.h"
#ifdef HAVE_SPACEWARE
# include <spnav.h>
@ -67,7 +67,8 @@ char RecentFile[MAX_RECENT][MAX_PATH];
#define GL_CHECK() \
do { \
int err = (int)glGetError(); \
if(err) dbp("%s:%d: glGetError() == 0x%X\n", __FILE__, __LINE__, err); \
if(err) dbp("%s:%d: glGetError() == 0x%X %s", \
__FILE__, __LINE__, err, gluErrorString(err)); \
} while (0)
/* Settings */
@ -254,9 +255,7 @@ void ScheduleLater() {
namespace SolveSpacePlatf {
class GlWidget : public Gtk::DrawingArea {
public:
GlWidget() : _pixels(NULL), _glpbuffer(0) {
set_double_buffered(false);
GlWidget() : _offscreen(NULL) {
_xdisplay = gdk_x11_get_default_xdisplay();
int glxmajor, glxminor;
@ -287,7 +286,10 @@ public:
/* prefer FBConfigs with depth of 32;
* Mesa software rasterizer explodes with a BadMatch without this;
* without this, Intel on Mesa flickers horribly for some reason.
this does not seem to affect other rasterizers (ie NVidia). */
this does not seem to affect other rasterizers (ie NVidia).
see this Mesa bug:
http://lists.freedesktop.org/archives/mesa-dev/2015-January/074693.html */
GLXFBConfig fbconfig = fbconfigs[0];
for(int i = 0; i < fbconfig_num; i++) {
XVisualInfo *visual_info = glXGetVisualFromFBConfig(_xdisplay, fbconfigs[i]);
@ -307,63 +309,43 @@ public:
oops();
}
int buffer_size = 2048;
_pixels = new uint32_t[buffer_size * buffer_size];
_pixels_inv = new uint32_t[buffer_size * buffer_size];
int pbuffer_attrs[] = {
GLX_PBUFFER_WIDTH, 2048,
GLX_PBUFFER_HEIGHT, 2048,
None
};
_glpbuffer = glXCreatePbuffer(_xdisplay, fbconfig, pbuffer_attrs);
if(!_glpbuffer)
oops();
XFree(fbconfigs);
}
~GlWidget() {
delete[] _pixels;
delete[] _pixels_inv;
delete _offscreen;
glXDestroyPbuffer(_xdisplay, _glpbuffer);
glXDestroyContext(_xdisplay, _glcontext);
}
protected:
/* Draw on a GLX pbuffer, then read pixels out and draw them on
/* Draw on a GLX framebuffer object, then read pixels out and draw them on
the Cairo context. Slower, but you get to overlay nice widgets. */
virtual bool on_draw(const Cairo::RefPtr<Cairo::Context> &cr) {
if(!glXMakeContextCurrent(_xdisplay, _glpbuffer, _glpbuffer, _glcontext))
/* We activate a GL context for the X window, because we need /something/,
but we never draw directly onto the window. */
#ifdef HAVE_GTK3
::Window _xwindow = gdk_x11_window_get_xid(get_window()->gobj());
#else
::Window _xwindow = gdk_x11_drawable_get_xid(get_window()->gobj());
#endif
if(!glXMakeCurrent(_xdisplay, _xwindow, _glcontext))
oops();
if(!_offscreen)
_offscreen = new GLOffscreen;
Gdk::Rectangle allocation = get_allocation();
if(!_offscreen->begin(allocation.get_width(), allocation.get_height()))
oops();
on_gl_draw();
glFlush();
GL_CHECK();
Gdk::Rectangle allocation = get_allocation();
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
glReadPixels(0, 0, allocation.get_width(), allocation.get_height(),
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, _pixels);
#else
glReadPixels(0, 0, allocation.get_width(), allocation.get_height(),
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, _pixels);
#endif
/* in OpenGL coordinates, bottom is zero Y */
int row_size = allocation.get_width() * 4;
for(int i = 0; i < allocation.get_height(); i++) {
memcpy(&_pixels_inv[allocation.get_width() * i],
&_pixels[allocation.get_width() * (allocation.get_height() - i - 1)],
row_size);
}
Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create(
(uint8_t*) _pixels_inv, Cairo::FORMAT_RGB24,
allocation.get_width(), allocation.get_height(), row_size);
_offscreen->end(), Cairo::FORMAT_RGB24,
allocation.get_width(), allocation.get_height(), allocation.get_width() * 4);
cr->set_source(surface, 0, 0);
cr->paint();
surface->finish();
@ -382,8 +364,7 @@ protected:
private:
Display *_xdisplay;
GLXContext _glcontext;
GLXPbuffer _glpbuffer;
uint32_t *_pixels, *_pixels_inv;
GLOffscreen *_offscreen;
};
};

76
src/unix/gloffscreen.cpp Normal file
View File

@ -0,0 +1,76 @@
//-----------------------------------------------------------------------------
// Offscreen rendering in OpenGL using framebuffer objects.
//
// Copyright 2015 <whitequark@whitequark.org>
//-----------------------------------------------------------------------------
#include "gloffscreen.h"
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <GL/glew.h>
GLOffscreen::GLOffscreen() : _pixels(NULL), _pixels_inv(NULL) {
if(glewInit() != GLEW_OK)
abort();
glGenFramebuffers(1, &_framebuffer);
glGenRenderbuffers(1, &_color_renderbuffer);
glGenRenderbuffers(1, &_depth_renderbuffer);
}
GLOffscreen::~GLOffscreen() {
glDeleteRenderbuffers(1, &_depth_renderbuffer);
glDeleteRenderbuffers(1, &_color_renderbuffer);
glDeleteFramebuffers(1, &_framebuffer);
}
bool GLOffscreen::begin(int width, int height) {
if(_width != width || _height != height) {
delete[] _pixels;
delete[] _pixels_inv;
_pixels = new uint32_t[width * height * 4];
_pixels_inv = new uint32_t[width * height * 4];
_width = width;
_height = height;
}
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _color_renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, _width, _height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, _color_renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _depth_renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, _width, _height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, _depth_renderbuffer);
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
return true;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return false;
}
uint8_t *GLOffscreen::end() {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
glReadPixels(0, 0, _width, _height,
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, _pixels_inv);
#else
glReadPixels(0, 0, _width, _height,
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, _pixels_inv);
#endif
/* in OpenGL coordinates, bottom is zero Y */
for(int i = 0; i < _height; i++)
memcpy(&_pixels[_width * i], &_pixels_inv[_width * (_height - i - 1)], _width * 4);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return (uint8_t*) _pixels;
}

35
src/unix/gloffscreen.h Normal file
View File

@ -0,0 +1,35 @@
//-----------------------------------------------------------------------------
// Offscreen rendering in OpenGL using framebuffer objects.
//
// Copyright 2015 <whitequark@whitequark.org>
//-----------------------------------------------------------------------------
#ifndef __GLOFFSCREEN_H
#define __GLOFFSCREEN_H
#include <stdint.h>
class GLOffscreen {
public:
/* these allocate and deallocate OpenGL resources.
an OpenGL context /must/ be current. */
GLOffscreen();
~GLOffscreen();
/* prepare for drawing a frame of specified size.
returns true if OpenGL likes our configuration, false
otherwise. if it returns false, the OpenGL state is restored. */
bool begin(int width, int height);
/* get pixels out of the frame and restore OpenGL state.
the pixel format is ARGB32 with top row at index 0.
the returned array is valid until the next call to begin() */
uint8_t *end();
private:
unsigned int _framebuffer;
unsigned int _color_renderbuffer, _depth_renderbuffer;
uint32_t *_pixels, *_pixels_inv;
int _width, _height;
};
#endif