diff --git a/.travis/install-debian.sh b/.travis/install-debian.sh index 6c11310..be6f9ba 100755 --- a/.travis/install-debian.sh +++ b/.travis/install-debian.sh @@ -4,5 +4,5 @@ sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test sudo apt-get update -qq sudo apt-get install -q -y \ cmake cmake-data libpng12-dev zlib1g-dev libjson0-dev libfontconfig1-dev \ - libgtkmm-2.4-dev libpangomm-1.4-dev libgl1-mesa-dev libglu-dev libglew-dev \ + libgtkmm-2.4-dev libpangomm-1.4-dev libgl1-mesa-dev libglu-dev \ libfreetype6-dev dpkg-dev gcc-5 g++-5 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5dd1844..709a4d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -196,7 +196,6 @@ else() # Linux and compatible systems pkg_check_modules(PNG REQUIRED libpng) pkg_check_modules(FONTCONFIG REQUIRED fontconfig) pkg_check_modules(JSONC REQUIRED json-c) - pkg_check_modules(GLEW REQUIRED glew) pkg_check_modules(FREETYPE REQUIRED freetype2) set(HAVE_GTK TRUE) diff --git a/README.md b/README.md index f06ca8e..42a1b12 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ On a Debian derivative (e.g. Ubuntu) these can be installed with: apt-get install libpng-dev libjson-c-dev libfreetype6-dev \ libfontconfig1-dev libgtkmm-2.4-dev libpangomm-1.4-dev \ - libgl-dev libglu-dev libglew-dev cmake + libgl-dev libglu-dev cmake Before building, check out the necessary submodules: diff --git a/debian/control b/debian/control index faa62d9..4e03aae 100644 --- a/debian/control +++ b/debian/control @@ -4,7 +4,7 @@ Priority: optional Maintainer: whitequark Build-Depends: debhelper (>= 9), cmake, libpng-dev, zlib1g-dev, libjson-c-dev, libfontconfig1-dev, libgtkmm-2.4-dev, libpangomm-1.4-dev, - libgl-dev, libglu-dev, libglew-dev + libgl-dev, libglu-dev Standards-Version: 3.9.5 Homepage: http://solvespace.com Vcs-Git: git://github.com/whitequark/solvespace diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9e794e2..250c105 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -89,7 +89,7 @@ elseif(APPLE) set(platform_SOURCES platform/cocoamain.mm - platform/gloffscreen.cpp) + render/rendergl.cpp) set(platform_BUNDLED_LIBS ${PNG_LIBRARIES} @@ -101,30 +101,26 @@ elseif(HAVE_GTK) include_directories( ${GTKMM_INCLUDE_DIRS} ${JSONC_INCLUDE_DIRS} - ${FONTCONFIG_INCLUDE_DIRS} - ${GLEW_INCLUDE_DIRS}) + ${FONTCONFIG_INCLUDE_DIRS}) link_directories( ${GTKMM_LIBRARY_DIRS} ${JSONC_LIBRARY_DIRS} - ${FONTCONFIG_LIBRARY_DIRS} - ${GLEW_LIBRARY_DIRS}) + ${FONTCONFIG_LIBRARY_DIRS}) add_definitions( ${GTKMM_CFLAGS_OTHER} ${JSONC_CFLAGS_OTHER} - ${FONTCONFIG_CFLAGS_OTHER} - ${GLEW_CFLAGS_OTHER}) + ${FONTCONFIG_CFLAGS_OTHER}) set(platform_SOURCES platform/gtkmain.cpp - platform/gloffscreen.cpp) + render/rendergl.cpp) set(platform_LIBRARIES ${GTKMM_LIBRARIES} ${JSONC_LIBRARIES} - ${FONTCONFIG_LIBRARIES} - ${GLEW_LIBRARIES}) + ${FONTCONFIG_LIBRARIES}) endif() # solvespace executable diff --git a/src/export.cpp b/src/export.cpp index db4791f..d435a8f 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -7,9 +7,6 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" -#ifndef WIN32 -#include -#endif void SolveSpaceUI::ExportSectionTo(const std::string &filename) { Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); @@ -1084,10 +1081,11 @@ void SolveSpaceUI::ExportAsPngTo(const std::string &filename) { bool prevShowToolbar = SS.showToolbar; SS.showToolbar = false; #ifndef WIN32 - std::unique_ptr gloffscreen(new GLOffscreen); - gloffscreen->begin((int)SS.GW.width, (int)SS.GW.height); + GlOffscreen offscreen; + offscreen.Render((int)SS.GW.width, (int)SS.GW.height, [&] { + SS.GW.Paint(); + }); #endif - SS.GW.Paint(); SS.showToolbar = prevShowToolbar; // Somewhat hacky way to invoke glReadPixels without dragging in all OpenGL headers. @@ -1100,6 +1098,11 @@ void SolveSpaceUI::ExportAsPngTo(const std::string &filename) { Error("Couldn't write to '%s'", filename.c_str()); } if(f) fclose(f); + +#ifndef WIN32 + offscreen.Clear(); +#endif + return; } diff --git a/src/platform/cocoamain.mm b/src/platform/cocoamain.mm index 21208ef..f448441 100644 --- a/src/platform/cocoamain.mm +++ b/src/platform/cocoamain.mm @@ -14,7 +14,6 @@ #include #include "solvespace.h" -#include "gloffscreen.h" #include using SolveSpace::dbp; @@ -126,7 +125,7 @@ const bool SolveSpace::FLIP_FRAMEBUFFER = false; @implementation GLViewWithEditor { - GLOffscreen *offscreen; + SolveSpace::GlOffscreen offscreen; NSOpenGLContext *glContext; @protected NSTextField *editor; @@ -156,7 +155,7 @@ const bool SolveSpace::FLIP_FRAMEBUFFER = false; } - (void)dealloc { - delete offscreen; + offscreen.Clear(); } #define CONVERT1(name, to_from) \ @@ -187,19 +186,13 @@ CONVERT(Rect) - (void)drawRect:(NSRect)aRect { [glContext makeCurrentContext]; - if(!offscreen) - offscreen = new GLOffscreen; - NSSize size = [self convertSizeToBacking:[self bounds].size]; int width = (int)size.width, height = (int)size.height; - offscreen->begin(width, height); + offscreen.Render(width, height, [&] { [self drawGL]; }); - [self drawGL]; - - uint8_t *pixels = offscreen->end(); CGDataProviderRef provider = CGDataProviderCreateWithData( - NULL, pixels, width * height * 4, NULL); + NULL, &offscreen.data[0], width * height * 4, NULL); CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); CGImageRef image = CGImageCreate(width, height, 8, 32, width * 4, colorspace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, diff --git a/src/platform/gloffscreen.cpp b/src/platform/gloffscreen.cpp deleted file mode 100644 index b073893..0000000 --- a/src/platform/gloffscreen.cpp +++ /dev/null @@ -1,74 +0,0 @@ -//----------------------------------------------------------------------------- -// Offscreen rendering in OpenGL using framebuffer objects. -// -// Copyright 2015 -//----------------------------------------------------------------------------- -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "gloffscreen.h" -#include "solvespace.h" - -GLOffscreen::GLOffscreen() : _pixels(NULL), _width(0), _height(0) { -#ifndef __APPLE__ - ssassert(glewInit() == GLEW_OK, "Unexpected GLEW failure"); -#endif - - ssassert(GL_EXT_framebuffer_object, "Expected an available FBO extension"); - - glGenFramebuffersEXT(1, &_framebuffer); - glGenRenderbuffersEXT(1, &_color_renderbuffer); - glGenRenderbuffersEXT(1, &_depth_renderbuffer); -} - -GLOffscreen::~GLOffscreen() { - delete[] _pixels; - glDeleteRenderbuffersEXT(1, &_depth_renderbuffer); - glDeleteRenderbuffersEXT(1, &_color_renderbuffer); - glDeleteFramebuffersEXT(1, &_framebuffer); -} - -bool GLOffscreen::begin(int width, int height) { - if(_width != width || _height != height) { - delete[] _pixels; - _pixels = new uint8_t[width * height * 4]; - - _width = width; - _height = height; - } - - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _framebuffer); - - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, _color_renderbuffer); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, _width, _height); - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, - GL_RENDERBUFFER_EXT, _color_renderbuffer); - - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, _depth_renderbuffer); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, _width, _height); - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, - GL_RENDERBUFFER_EXT, _depth_renderbuffer); - - if(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) - return true; - - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 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); -#else - glReadPixels(0, 0, _width, _height, - GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, _pixels); -#endif - - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); - - return _pixels; -} diff --git a/src/platform/gloffscreen.h b/src/platform/gloffscreen.h deleted file mode 100644 index 378e044..0000000 --- a/src/platform/gloffscreen.h +++ /dev/null @@ -1,36 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 if - flip is true and bottom row at index 0 if flip is false. - the returned array is valid until the next call to begin() */ - uint8_t *end(); - -private: - unsigned int _framebuffer; - unsigned int _color_renderbuffer, _depth_renderbuffer; - uint8_t *_pixels; - int _width, _height; -}; - -#endif diff --git a/src/platform/gtkmain.cpp b/src/platform/gtkmain.cpp index 474fea2..4f20f50 100644 --- a/src/platform/gtkmain.cpp +++ b/src/platform/gtkmain.cpp @@ -52,7 +52,6 @@ #include "solvespace.h" #include "config.h" -#include "gloffscreen.h" #ifdef HAVE_SPACEWARE # include @@ -228,7 +227,7 @@ const bool FLIP_FRAMEBUFFER = true; class GlWidget : public Gtk::DrawingArea { public: - GlWidget() : _offscreen(NULL) { + GlWidget() { _xdisplay = gdk_x11_get_default_xdisplay(); int glxmajor, glxminor; @@ -297,7 +296,7 @@ public: XDestroyWindow(_xdisplay, _xwindow); - delete _offscreen; + _offscreen.Clear(); glXDestroyContext(_xdisplay, _glcontext); } @@ -314,18 +313,16 @@ protected: ssassert(glXMakeCurrent(_xdisplay, _xwindow, _glcontext), "Cannot make OpenGL context current"); - if(!_offscreen) - _offscreen = new GLOffscreen; - Gdk::Rectangle allocation = get_allocation(); - ssassert(_offscreen->begin(allocation.get_width(), allocation.get_height()), - "Cannot allocate offscreen rendering buffer"); + bool success = _offscreen.Render( + allocation.get_width(), allocation.get_height(), + sigc::mem_fun(this, &GlWidget::on_gl_draw)); + ssassert(success, "Cannot allocate offscreen rendering buffer"); - on_gl_draw(); - - Cairo::RefPtr surface = Cairo::ImageSurface::create( - _offscreen->end(), Cairo::FORMAT_RGB24, - allocation.get_width(), allocation.get_height(), allocation.get_width() * 4); + Cairo::RefPtr surface = + Cairo::ImageSurface::create(&_offscreen.data[0], Cairo::FORMAT_RGB24, + allocation.get_width(), allocation.get_height(), + allocation.get_width() * 4); cr->set_source(surface, 0, 0); cr->paint(); surface->finish(); @@ -338,7 +335,7 @@ protected: private: Display *_xdisplay; GLXContext _glcontext; - GLOffscreen *_offscreen; + GlOffscreen _offscreen; ::Window _xwindow; }; diff --git a/src/render/render.h b/src/render/render.h index 0212dbd..b0b4b24 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -204,6 +204,17 @@ public: bool Pick(std::function drawFn); }; +// An offscreen renderer based on OpenGL framebuffers. +class GlOffscreen { +public: + unsigned int framebuffer; + unsigned int colorRenderbuffer, depthRenderbuffer; + std::vector data; + + bool Render(int width, int height, std::function renderFn); + void Clear(); +}; + // A canvas that uses the core OpenGL profile, for desktop systems. class OpenGl1Renderer : public Canvas { public: diff --git a/src/render/rendergl.cpp b/src/render/rendergl.cpp new file mode 100644 index 0000000..1e65e59 --- /dev/null +++ b/src/render/rendergl.cpp @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// Offscreen rendering in OpenGL using EGL and framebuffer objects. +// +// Copyright 2015-2016 whitequark +//----------------------------------------------------------------------------- +#ifdef __APPLE__ +#include +#else +#define GL_GLEXT_PROTOTYPES +#include +#endif + +#include "solvespace.h" + +void GlOffscreen::Clear() { + glDeleteRenderbuffersEXT(1, &depthRenderbuffer); + glDeleteRenderbuffersEXT(1, &colorRenderbuffer); + glDeleteFramebuffersEXT(1, &framebuffer); + *this = {}; +} + +bool GlOffscreen::Render(int width, int height, std::function renderFn) { + data.resize(width * height * 4); + + if(framebuffer == 0) { + glGenFramebuffersEXT(1, &framebuffer); + glGenRenderbuffersEXT(1, &colorRenderbuffer); + glGenRenderbuffersEXT(1, &depthRenderbuffer); + } + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer); + + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, colorRenderbuffer); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, width, height); + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_RENDERBUFFER_EXT, colorRenderbuffer); + + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthRenderbuffer); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, width, height); + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, + GL_RENDERBUFFER_EXT, depthRenderbuffer); + + bool framebufferComplete = + glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT; + if(framebufferComplete) { + renderFn(); +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, &data[0]); +#else + glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, &data[0]); +#endif + } + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + + return framebufferComplete; +}