diff --git a/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.cpp b/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.cpp index 17274e3811..31223494d9 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.cpp @@ -250,6 +250,34 @@ private: JUCE_DECLARE_NON_COPYABLE (Pimpl); }; +//============================================================================== +class OpenGLFrameBuffer::SavedState +{ +public: + SavedState (OpenGLFrameBuffer& buffer, const int w, const int h) + : width (w), height (h), + data (w * h) + { + buffer.readPixels (data, Rectangle (0, 0, w, h)); + } + + bool restore (OpenGLFrameBuffer& buffer) + { + if (buffer.initialise (width, height)) + { + buffer.writePixels (data, Rectangle (0, 0, width, height)); + return true; + } + + return false; + } + +private: + const int width, height; + HeapBlock data; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedState); +}; //============================================================================== OpenGLFrameBuffer::OpenGLFrameBuffer() {} @@ -257,7 +285,7 @@ OpenGLFrameBuffer::~OpenGLFrameBuffer() {} bool OpenGLFrameBuffer::initialise (int width, int height) { - release(); + pimpl = nullptr; pimpl = new Pimpl (width, height, true, false); if (! pimpl->ok) @@ -269,6 +297,28 @@ bool OpenGLFrameBuffer::initialise (int width, int height) void OpenGLFrameBuffer::release() { pimpl = nullptr; + savedState = nullptr; +} + +void OpenGLFrameBuffer::saveAndRelease() +{ + if (pimpl != nullptr) + { + savedState = new SavedState (*this, pimpl->width, pimpl->height); + pimpl = nullptr; + } +} + +bool OpenGLFrameBuffer::reloadSavedCopy() +{ + if (savedState != nullptr + && savedState->restore (*this)) + { + savedState = nullptr; + return true; + } + + return false; } int OpenGLFrameBuffer::getWidth() const noexcept { return pimpl != nullptr ? pimpl->width : 0; } @@ -277,6 +327,10 @@ GLuint OpenGLFrameBuffer::getTextureID() const noexcept { return pimpl != nu bool OpenGLFrameBuffer::makeCurrentTarget() { + // trying to use a framebuffer after saving it with saveAndRelease()! Be sure to call + // reloadSavedCopy() to put it back into GPU memory before using it.. + jassert (savedState == nullptr); + return pimpl != nullptr && pimpl->bind(); } @@ -295,6 +349,70 @@ void OpenGLFrameBuffer::clear (const Colour& colour) } } +bool OpenGLFrameBuffer::readPixels (void* target, const Rectangle& area) +{ + if (! makeCurrentTarget()) + return false; + + OpenGLHelpers::prepareFor2D (pimpl->width, pimpl->height); + + glPixelStorei (GL_PACK_ALIGNMENT, 4); + glReadPixels (area.getX(), area.getY(), area.getWidth(), area.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, target); + + glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0); + + return true; +} + +bool OpenGLFrameBuffer::writePixels (const void* data, const Rectangle& area) +{ + if (! makeCurrentTarget()) + return false; + + OpenGLHelpers::prepareFor2D (pimpl->width, pimpl->height); + + glDisable (GL_DEPTH_TEST); + glDisable (GL_BLEND); + + #if JUCE_OPENGL_ES + // GLES has no glDrawPixels function, so we have to create a texture and draw it.. + GLuint temporaryTexture = 0; + glGenTextures (1, &temporaryTexture); + jassert (temporaryTexture != 0); // can't create a texture! + + if (temporaryTexture != 0) + { + glEnable (GL_TEXTURE_2D); + glBindTexture (GL_TEXTURE_2D, temporaryTexture); + + glPixelStorei (GL_UNPACK_ALIGNMENT, 4); + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, area.getWidth(), area.getHeight(), 0, + GL_BGRA_EXT, GL_UNSIGNED_BYTE, data); + + glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + const int cropRect[4] = { 0, 0, area.getWidth(), area.getHeight() }; + glTexParameteriv (GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect); + + glDrawTexiOES (area.getX(), area.getY(), 1, area.getWidth(), area.getHeight()); + + glBindTexture (GL_TEXTURE_2D, 0); + glDeleteTextures (1, &temporaryTexture); + } + + #else + glRasterPos2i (area.getX(), area.getY()); + glBindTexture (GL_TEXTURE_2D, 0); + glPixelStorei (GL_UNPACK_ALIGNMENT, 4); + glDrawPixels (area.getWidth(), area.getHeight(), GL_BGRA_EXT, GL_UNSIGNED_BYTE, data); + #endif + + glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0); + + return true; +} + void OpenGLFrameBuffer::draw2D (float x1, float y1, float x2, float y2, float x3, float y3, @@ -332,9 +450,7 @@ public: : firstSlice (nullptr), windingMask (p.isUsingNonZeroWinding() ? -1 : 1) { - PathFlatteningIterator iter (p); - - while (iter.next()) + for (PathFlatteningIterator iter (p); iter.next();) addLine (floatToInt (iter.x1), floatToInt (iter.y1), floatToInt (iter.x2), floatToInt (iter.y2)); } @@ -575,7 +691,7 @@ private: enum { factor = 128 }; static inline int floatToInt (const float n) noexcept { return roundToInt (n * (float) factor); } - static inline float intToFloat (const int n) noexcept { return n * (1.0f/ (float) factor); } + static inline float intToFloat (const int n) noexcept { return n * (1.0f / (float) factor); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TrapezoidedPath); }; diff --git a/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.h b/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.h index 5211be9e0b..7dbdd6701d 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.h +++ b/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.h @@ -49,9 +49,23 @@ public: */ bool initialise (int width, int height); - /** Releases the buffer, if one has been allocated. */ + /** Releases the buffer, if one has been allocated. + Any saved state that was created with saveAndRelease() will also be freed by this call. + */ void release(); + /** If the framebuffer is active, this will save a stashed copy of its contents in main memory, + and will release the GL buffer. + After saving, the original state can be restored again by calling reloadSavedCopy(). + */ + void saveAndRelease(); + + /** Restores the framebuffer content that was previously saved using saveAndRelease(). + After saving to main memory, the original state can be restored by calling restoreToGPUMemory(). + */ + bool reloadSavedCopy(); + + //============================================================================== /** Returns true if a valid buffer has been allocated. */ bool isValid() const noexcept { return pimpl != nullptr; } @@ -88,6 +102,12 @@ public: float x4, float y4, float z4, const Colour& colour) const; + /** Reads an area of pixels from the framebuffer into a specified pixel array. */ + bool readPixels (void* target, const Rectangle& area); + + /** Writes an area of pixels into the framebuffer from a specified pixel array. */ + bool writePixels (const void* target, const Rectangle& area); + /** This will render an anti-aliased path into just the alpha channel of this framebuffer. The idea here is that you can clear a framebuffer, use this to set its alpha channel, then @@ -104,6 +124,10 @@ private: friend class ScopedPointer; ScopedPointer pimpl; + class SavedState; + friend class ScopedPointer; + ScopedPointer savedState; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLFrameBuffer); }; diff --git a/modules/juce_opengl/opengl/juce_OpenGLImage.cpp b/modules/juce_opengl/opengl/juce_OpenGLImage.cpp index e9fb160b29..7bb1a04193 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLImage.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLImage.cpp @@ -74,14 +74,7 @@ namespace OpenGLImageHelpers { static void read (OpenGLFrameBuffer& frameBuffer, Image::BitmapData& bitmapData, int x, int y) { - if (frameBuffer.makeCurrentTarget()) - { - OpenGLHelpers::prepareFor2D (frameBuffer.getWidth(), frameBuffer.getHeight()); - - glPixelStorei (GL_PACK_ALIGNMENT, 4); - glReadPixels (x, y, bitmapData.width, bitmapData.height, GL_RGBA, GL_UNSIGNED_BYTE, bitmapData.data); - frameBuffer.releaseCurrentTarget(); - } + frameBuffer.readPixels (bitmapData.data, Rectangle (x, y, bitmapData.width, bitmapData.height)); } }; @@ -93,49 +86,7 @@ namespace OpenGLImageHelpers void write (const void* const data) const noexcept { - if (frameBuffer.makeCurrentTarget()) - { - OpenGLHelpers::prepareFor2D (frameBuffer.getWidth(), frameBuffer.getHeight()); - - glDisable (GL_DEPTH_TEST); - glDisable (GL_BLEND); - - #if JUCE_OPENGL_ES - // GLES has no glDrawPixels function, so we have to create a texture and draw it.. - GLuint temporaryTexture = 0; - glGenTextures (1, &temporaryTexture); - jassert (temporaryTexture != 0); // can't create a texture! - - if (temporaryTexture != 0) - { - glEnable (GL_TEXTURE_2D); - glBindTexture (GL_TEXTURE_2D, temporaryTexture); - - glPixelStorei (GL_UNPACK_ALIGNMENT, 4); - glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, area.getWidth(), area.getHeight(), 0, - GL_BGRA_EXT, GL_UNSIGNED_BYTE, data); - - glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - const int cropRect[4] = { 0, 0, area.getWidth(), area.getHeight() }; - glTexParameteriv (GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect); - - glDrawTexiOES (area.getX(), area.getY(), 1, area.getWidth(), area.getHeight()); - - glBindTexture (GL_TEXTURE_2D, 0); - glDeleteTextures (1, &temporaryTexture); - } - - #else - glRasterPos2i (area.getX(), area.getY()); - glBindTexture (GL_TEXTURE_2D, 0); - glPixelStorei (GL_UNPACK_ALIGNMENT, 4); - glDrawPixels (area.getWidth(), area.getHeight(), GL_BGRA_EXT, GL_UNSIGNED_BYTE, data); - #endif - - frameBuffer.releaseCurrentTarget(); - } + frameBuffer.writePixels (data, area); } OpenGLFrameBuffer& frameBuffer;