diff --git a/CMakeLists.txt b/CMakeLists.txt index 45974ac..803a67c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4631d92..1be505a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/gtk/gtkmain.cpp b/src/gtk/gtkmain.cpp index a35402c..6682852 100644 --- a/src/gtk/gtkmain.cpp +++ b/src/gtk/gtkmain.cpp @@ -49,11 +49,11 @@ #undef HAVE_STDINT_H /* no thanks, we have our own config.h */ -#define GLX_GLXEXT_PROTOTYPES #include -#include "solvespace.h" #include +#include "solvespace.h" +#include "../unix/gloffscreen.h" #ifdef HAVE_SPACEWARE # include @@ -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 &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 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; }; }; diff --git a/src/unix/gloffscreen.cpp b/src/unix/gloffscreen.cpp new file mode 100644 index 0000000..2a734e5 --- /dev/null +++ b/src/unix/gloffscreen.cpp @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// Offscreen rendering in OpenGL using framebuffer objects. +// +// Copyright 2015 +//----------------------------------------------------------------------------- +#include "gloffscreen.h" + +#include +#include +#include + +#include + +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; +} diff --git a/src/unix/gloffscreen.h b/src/unix/gloffscreen.h new file mode 100644 index 0000000..5f325b9 --- /dev/null +++ b/src/unix/gloffscreen.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// Offscreen rendering in OpenGL using framebuffer objects. +// +// Copyright 2015 +//----------------------------------------------------------------------------- +#ifndef __GLOFFSCREEN_H +#define __GLOFFSCREEN_H + +#include + +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