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:
parent
622c9efadb
commit
e27c6b56c3
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
76
src/unix/gloffscreen.cpp
Normal 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
35
src/unix/gloffscreen.h
Normal 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
|
Loading…
Reference in New Issue
Block a user