| @@ -47,6 +47,7 @@ Graphics::Graphics (const Image& imageToDrawOnto) | |||
| contextToDelete (&context), | |||
| saveStatePending (false) | |||
| { | |||
| jassert (imageToDrawOnto.isValid()); // Can't draw into a null image! | |||
| } | |||
| Graphics::Graphics (LowLevelGraphicsContext* const internalContext) noexcept | |||
| @@ -159,6 +159,28 @@ static void clearGLError() | |||
| while (glGetError() != GL_NO_ERROR) {} | |||
| } | |||
| struct OpenGLTargetSaver | |||
| { | |||
| OpenGLTargetSaver (const OpenGLContext& c) | |||
| : context (c), oldFramebuffer (OpenGLFrameBuffer::getCurrentFrameBufferTarget()) | |||
| { | |||
| glGetIntegerv (GL_VIEWPORT, oldViewport); | |||
| } | |||
| ~OpenGLTargetSaver() | |||
| { | |||
| context.extensions.glBindFramebuffer (GL_FRAMEBUFFER, oldFramebuffer); | |||
| glViewport (oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]); | |||
| } | |||
| private: | |||
| const OpenGLContext& context; | |||
| GLuint oldFramebuffer; | |||
| GLint oldViewport[4]; | |||
| OpenGLTargetSaver& operator= (const OpenGLTargetSaver&); | |||
| }; | |||
| //============================================================================== | |||
| #include "opengl/juce_OpenGLFrameBuffer.cpp" | |||
| #include "opengl/juce_OpenGLGraphicsContext.cpp" | |||
| @@ -244,7 +244,7 @@ public: | |||
| glBindTexture (GL_TEXTURE_2D, cachedImageFrameBuffer.getTextureID()); | |||
| const Rectangle<int> cacheBounds (cachedImageFrameBuffer.getWidth(), cachedImageFrameBuffer.getHeight()); | |||
| context.copyTexture (cacheBounds, cacheBounds, cacheBounds.getWidth(), cacheBounds.getHeight()); | |||
| context.copyTexture (cacheBounds, cacheBounds, cacheBounds.getWidth(), cacheBounds.getHeight(), false); | |||
| glBindTexture (GL_TEXTURE_2D, 0); | |||
| JUCE_CHECK_OPENGL_ERROR | |||
| } | |||
| @@ -672,7 +672,8 @@ void OpenGLContext::setAssociatedObject (const char* name, ReferenceCountedObjec | |||
| void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea, | |||
| const Rectangle<int>& anchorPosAndTextureSize, | |||
| const int contextWidth, const int contextHeight) | |||
| const int contextWidth, const int contextHeight, | |||
| bool flippedVertically) | |||
| { | |||
| if (contextWidth <= 0 || contextHeight <= 0) | |||
| return; | |||
| @@ -722,12 +723,13 @@ void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea, | |||
| prog.addShader ("uniform sampler2D imageTexture;" | |||
| "uniform " JUCE_HIGHP " float textureBounds[4];" | |||
| "uniform " JUCE_HIGHP " vec2 vOffsetAndScale;" | |||
| "varying " JUCE_HIGHP " vec2 pixelPos;" | |||
| "void main()" | |||
| "{" | |||
| JUCE_HIGHP " vec2 texturePos = (pixelPos - vec2 (textureBounds[0], textureBounds[1]))" | |||
| "/ vec2 (textureBounds[2], textureBounds[3]);" | |||
| "gl_FragColor = texture2D (imageTexture, vec2 (texturePos.x, 1.0 - texturePos.y));" | |||
| "gl_FragColor = texture2D (imageTexture, vec2 (texturePos.x, vOffsetAndScale.x + vOffsetAndScale.y * texturePos.y));" | |||
| "}", | |||
| GL_FRAGMENT_SHADER); | |||
| prog.link(); | |||
| @@ -740,19 +742,23 @@ void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea, | |||
| : positionAttribute (prog, "position"), | |||
| screenSize (prog, "screenSize"), | |||
| imageTexture (prog, "imageTexture"), | |||
| textureBounds (prog, "textureBounds") | |||
| textureBounds (prog, "textureBounds"), | |||
| vOffsetAndScale (prog, "vOffsetAndScale") | |||
| {} | |||
| void set (const float targetWidth, const float targetHeight, const Rectangle<float>& bounds) const | |||
| void set (const float targetWidth, const float targetHeight, const Rectangle<float>& bounds, bool flippedVertically) const | |||
| { | |||
| const GLfloat m[] = { bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() }; | |||
| textureBounds.set (m, 4); | |||
| imageTexture.set (0); | |||
| screenSize.set (targetWidth, targetHeight); | |||
| vOffsetAndScale.set (flippedVertically ? 0.0f : 1.0f, | |||
| flippedVertically ? 1.0f : -1.0f); | |||
| } | |||
| OpenGLShaderProgram::Attribute positionAttribute; | |||
| OpenGLShaderProgram::Uniform screenSize, imageTexture, textureBounds; | |||
| OpenGLShaderProgram::Uniform screenSize, imageTexture, textureBounds, vOffsetAndScale; | |||
| }; | |||
| OpenGLShaderProgram program; | |||
| @@ -767,7 +773,7 @@ void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea, | |||
| const GLshort vertices[] = { left, bottom, right, bottom, left, top, right, top }; | |||
| const OverlayShaderProgram& program = OverlayShaderProgram::select (*this); | |||
| program.params.set ((float) contextWidth, (float) contextHeight, anchorPosAndTextureSize.toFloat()); | |||
| program.params.set ((float) contextWidth, (float) contextHeight, anchorPosAndTextureSize.toFloat(), flippedVertically); | |||
| extensions.glVertexAttribPointer (program.params.positionAttribute.attributeID, 2, GL_SHORT, GL_FALSE, 4, vertices); | |||
| extensions.glEnableVertexAttribArray (program.params.positionAttribute.attributeID); | |||
| @@ -219,10 +219,13 @@ public: | |||
| used for scaling of the coordinates. | |||
| @param contextHeight the height of the context or framebuffer that is being drawn into, | |||
| used for vertical flipping of the y coordinates. | |||
| @param textureOriginIsBottomLeft if true, the texture's origin is treated as being at | |||
| (0, 0). If false, it is assumed to be (0, 1) | |||
| */ | |||
| void copyTexture (const Rectangle<int>& targetClipArea, | |||
| const Rectangle<int>& anchorPosAndTextureSize, | |||
| int contextWidth, int contextHeight); | |||
| int contextWidth, int contextHeight, | |||
| bool textureOriginIsBottomLeft); | |||
| //============================================================================== | |||
| @@ -222,7 +222,7 @@ bool OpenGLFrameBuffer::initialise (OpenGLFrameBuffer& other) | |||
| clearGLError(); | |||
| #endif | |||
| glBindTexture (GL_TEXTURE_2D, p->textureID); | |||
| pimpl->context.copyTexture (area, area, area.getWidth(), area.getHeight()); | |||
| pimpl->context.copyTexture (area, area, area.getWidth(), area.getHeight(), false); | |||
| glBindTexture (GL_TEXTURE_2D, 0); | |||
| JUCE_CHECK_OPENGL_ERROR | |||
| @@ -325,13 +325,14 @@ bool OpenGLFrameBuffer::readPixels (PixelARGB* target, const Rectangle<int>& are | |||
| glReadPixels (area.getX(), area.getY(), area.getWidth(), area.getHeight(), | |||
| JUCE_RGBA_FORMAT, GL_UNSIGNED_BYTE, target); | |||
| pimpl->context.extensions.glBindFramebuffer (GL_FRAMEBUFFER, 0); | |||
| glPixelStorei (GL_PACK_ALIGNMENT, 0); | |||
| JUCE_CHECK_OPENGL_ERROR | |||
| return true; | |||
| } | |||
| bool OpenGLFrameBuffer::writePixels (const PixelARGB* data, const Rectangle<int>& area) | |||
| { | |||
| OpenGLTargetSaver ts (pimpl->context); | |||
| if (! makeCurrentRenderingTarget()) | |||
| return false; | |||
| @@ -339,10 +340,10 @@ bool OpenGLFrameBuffer::writePixels (const PixelARGB* data, const Rectangle<int> | |||
| glDisable (GL_BLEND); | |||
| JUCE_CHECK_OPENGL_ERROR | |||
| #if JUCE_OPENGL_ES && JUCE_USE_OPENGL_FIXED_FUNCTION | |||
| OpenGLTexture tex; | |||
| tex.loadARGBFlipped (data, area.getWidth(), area.getHeight()); | |||
| #if JUCE_OPENGL_ES && JUCE_USE_OPENGL_FIXED_FUNCTION | |||
| const int texH = tex.getHeight(); | |||
| tex.bind(); | |||
| const GLint cropRect[4] = { 0, texH - area.getHeight(), area.getWidth(), area.getHeight() }; | |||
| @@ -353,10 +354,15 @@ bool OpenGLFrameBuffer::writePixels (const PixelARGB* data, const Rectangle<int> | |||
| glDrawTexiOES (area.getX(), area.getY(), 1, area.getWidth(), area.getHeight()); | |||
| glBindTexture (GL_TEXTURE_2D, 0); | |||
| #else | |||
| pimpl->context.copyTexture (area, area, pimpl->width, pimpl->height); | |||
| OpenGLTexture tex; | |||
| tex.loadARGB (data, area.getWidth(), area.getHeight()); | |||
| glViewport (0, 0, pimpl->width, pimpl->height); | |||
| pimpl->context.copyTexture (area, Rectangle<int> (area.getX(), area.getY(), | |||
| tex.getWidth(), tex.getHeight()), | |||
| pimpl->width, pimpl->height, true); | |||
| #endif | |||
| pimpl->context.extensions.glBindFramebuffer (GL_FRAMEBUFFER, 0); | |||
| JUCE_CHECK_OPENGL_ERROR | |||
| return true; | |||
| } | |||
| @@ -1364,7 +1364,7 @@ public: | |||
| clip (other.clip), | |||
| maskArea (other.clip) | |||
| { | |||
| TargetSaver ts (state.target.context); | |||
| OpenGLTargetSaver ts (state.target.context); | |||
| state.currentShader.clearShader (state.shaderQuadQueue); | |||
| state.shaderQuadQueue.flush(); | |||
| state.activeTextures.setSingleTextureMode (state.shaderQuadQueue); | |||
| @@ -1393,7 +1393,7 @@ public: | |||
| clip (r.getBounds()), | |||
| maskArea (clip) | |||
| { | |||
| TargetSaver ts (state.target.context); | |||
| OpenGLTargetSaver ts (state.target.context); | |||
| state.currentShader.clearShader (state.shaderQuadQueue); | |||
| state.shaderQuadQueue.flush(); | |||
| state.activeTextures.clear(); | |||
| @@ -1429,7 +1429,7 @@ public: | |||
| if (excluded.getNumRectangles() == 1) | |||
| return excludeClipRectangle (excluded.getRectangle (0)); | |||
| TargetSaver ts (state.target.context); | |||
| OpenGLTargetSaver ts (state.target.context); | |||
| makeActive(); | |||
| state.blendMode.setBlendMode (state.shaderQuadQueue, true); | |||
| state.currentShader.setShader (maskArea, state.shaderQuadQueue, state.currentShader.programs->solidColourProgram); | |||
| @@ -1445,7 +1445,7 @@ public: | |||
| if (r.contains (clip)) | |||
| return Ptr(); | |||
| TargetSaver ts (state.target.context); | |||
| OpenGLTargetSaver ts (state.target.context); | |||
| makeActive(); | |||
| state.blendMode.setBlendMode (state.shaderQuadQueue, true); | |||
| state.currentShader.setShader (maskArea, state.shaderQuadQueue, state.currentShader.programs->solidColourProgram); | |||
| @@ -1460,7 +1460,7 @@ public: | |||
| if (! et.isEmpty()) | |||
| { | |||
| TargetSaver ts (state.target.context); | |||
| OpenGLTargetSaver ts (state.target.context); | |||
| state.currentShader.clearShader (state.shaderQuadQueue); | |||
| state.shaderQuadQueue.flush(); | |||
| state.activeTextures.clear(); | |||
| @@ -1480,7 +1480,7 @@ public: | |||
| if (clip.isEmpty()) | |||
| return Ptr(); | |||
| TargetSaver ts (state.target.context); | |||
| OpenGLTargetSaver ts (state.target.context); | |||
| makeActive(); | |||
| state.activeTextures.setSingleTextureMode (state.shaderQuadQueue); | |||
| @@ -1501,7 +1501,7 @@ public: | |||
| Ptr clipToImageAlpha (const OpenGLTextureFromImage& image, const AffineTransform& transform) | |||
| { | |||
| TargetSaver ts (state.target.context); | |||
| OpenGLTargetSaver ts (state.target.context); | |||
| makeActive(); | |||
| state.activeTextures.setSingleTextureMode (state.shaderQuadQueue); | |||
| state.activeTextures.bindTexture (image.textureID); | |||
| @@ -1617,28 +1617,6 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE (ShaderFillOperation) | |||
| }; | |||
| struct TargetSaver | |||
| { | |||
| TargetSaver (const OpenGLContext& c) | |||
| : context (c), oldFramebuffer (OpenGLFrameBuffer::getCurrentFrameBufferTarget()) | |||
| { | |||
| glGetIntegerv (GL_VIEWPORT, oldViewport); | |||
| } | |||
| ~TargetSaver() | |||
| { | |||
| context.extensions.glBindFramebuffer (GL_FRAMEBUFFER, oldFramebuffer); | |||
| glViewport (oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]); | |||
| } | |||
| private: | |||
| const OpenGLContext& context; | |||
| GLuint oldFramebuffer; | |||
| GLint oldViewport[4]; | |||
| TargetSaver& operator= (const TargetSaver&); | |||
| }; | |||
| void makeActive() | |||
| { | |||
| state.shaderQuadQueue.flush(); | |||
| @@ -2209,7 +2187,8 @@ public: | |||
| target.makeActive(); | |||
| target.context.copyTexture (target.bounds, Rectangle<int> (texture.getWidth(), | |||
| texture.getHeight()), | |||
| target.bounds.getWidth(), target.bounds.getHeight()); | |||
| target.bounds.getWidth(), target.bounds.getHeight(), | |||
| false); | |||
| glBindTexture (GL_TEXTURE_2D, 0); | |||
| #if JUCE_WINDOWS | |||
| @@ -38,7 +38,7 @@ bool OpenGLTexture::isValidSize (int width, int height) | |||
| return isPowerOfTwo (width) && isPowerOfTwo (height); | |||
| } | |||
| void OpenGLTexture::create (const int w, const int h, const void* pixels, GLenum type) | |||
| void OpenGLTexture::create (const int w, const int h, const void* pixels, GLenum type, bool topLeft) | |||
| { | |||
| ownerContext = OpenGLContext::getCurrentContext(); | |||
| @@ -46,11 +46,6 @@ void OpenGLTexture::create (const int w, const int h, const void* pixels, GLenum | |||
| // context. You'll need to create this object in one of the OpenGLContext's callbacks. | |||
| jassert (ownerContext != nullptr); | |||
| jassert (isValidSize (w, h)); // Perhaps these dimensions must be a power-of-two? | |||
| width = w; | |||
| height = h; | |||
| if (textureID == 0) | |||
| { | |||
| JUCE_CHECK_OPENGL_ERROR | |||
| @@ -70,8 +65,26 @@ void OpenGLTexture::create (const int w, const int h, const void* pixels, GLenum | |||
| glPixelStorei (GL_UNPACK_ALIGNMENT, 1); | |||
| JUCE_CHECK_OPENGL_ERROR | |||
| glTexImage2D (GL_TEXTURE_2D, 0, type == GL_ALPHA ? GL_ALPHA : GL_RGBA, | |||
| w, h, 0, type, GL_UNSIGNED_BYTE, pixels); | |||
| width = nextPowerOfTwo (w); | |||
| height = nextPowerOfTwo (h); | |||
| const GLint internalformat = type == GL_ALPHA ? GL_ALPHA : GL_RGBA; | |||
| if (width != w || height != h) | |||
| { | |||
| glTexImage2D (GL_TEXTURE_2D, 0, internalformat, | |||
| width, height, 0, type, GL_UNSIGNED_BYTE, nullptr); | |||
| glTexSubImage2D (GL_TEXTURE_2D, 0, 0, topLeft ? (height - h) : 0, w, h, | |||
| type, GL_UNSIGNED_BYTE, pixels); | |||
| } | |||
| else | |||
| { | |||
| glTexImage2D (GL_TEXTURE_2D, 0, internalformat, | |||
| w, h, 0, type, GL_UNSIGNED_BYTE, pixels); | |||
| } | |||
| JUCE_CHECK_OPENGL_ERROR | |||
| } | |||
| @@ -79,29 +92,20 @@ template <class PixelType> | |||
| struct Flipper | |||
| { | |||
| static void flip (HeapBlock<PixelARGB>& dataCopy, const uint8* srcData, const int lineStride, | |||
| const int w, const int h, const int textureW, const int textureH) | |||
| const int w, const int h) | |||
| { | |||
| dataCopy.malloc ((size_t) (textureW * textureH)); | |||
| dataCopy.malloc ((size_t) (w * h)); | |||
| for (int y = 0; y < h; ++y) | |||
| { | |||
| const PixelType* src = (const PixelType*) srcData; | |||
| PixelARGB* const dst = (PixelARGB*) (dataCopy + textureW * (textureH - 1 - y)); | |||
| PixelARGB* const dst = (PixelARGB*) (dataCopy + w * (h - 1 - y)); | |||
| for (int x = 0; x < w; ++x) | |||
| dst[x].set (src[x]); | |||
| if (textureW > w) | |||
| dst[w].set (PixelARGB (0)); | |||
| srcData += lineStride; | |||
| } | |||
| // for textures which are larger than the area of interest, clear the pixels that lie | |||
| // just outside the actual image, so that the texture interpolation doesn't read junk. | |||
| if (textureH > h) | |||
| zeromem (dataCopy + textureW * (textureH - 1 - h), | |||
| sizeof (PixelARGB) * jmin (textureW, w + 1)); | |||
| } | |||
| }; | |||
| @@ -109,44 +113,37 @@ void OpenGLTexture::loadImage (const Image& image) | |||
| { | |||
| const int imageW = image.getWidth(); | |||
| const int imageH = image.getHeight(); | |||
| const int textureW = nextPowerOfTwo (imageW); | |||
| const int textureH = nextPowerOfTwo (imageH); | |||
| HeapBlock<PixelARGB> dataCopy; | |||
| Image::BitmapData srcData (image, Image::BitmapData::readOnly); | |||
| switch (srcData.pixelFormat) | |||
| { | |||
| case Image::ARGB: Flipper<PixelARGB> ::flip (dataCopy, srcData.data, srcData.lineStride, imageW, imageH, textureW, textureH); break; | |||
| case Image::RGB: Flipper<PixelRGB> ::flip (dataCopy, srcData.data, srcData.lineStride, imageW, imageH, textureW, textureH); break; | |||
| case Image::SingleChannel: Flipper<PixelAlpha>::flip (dataCopy, srcData.data, srcData.lineStride, imageW, imageH, textureW, textureH); break; | |||
| case Image::ARGB: Flipper<PixelARGB> ::flip (dataCopy, srcData.data, srcData.lineStride, imageW, imageH); break; | |||
| case Image::RGB: Flipper<PixelRGB> ::flip (dataCopy, srcData.data, srcData.lineStride, imageW, imageH); break; | |||
| case Image::SingleChannel: Flipper<PixelAlpha>::flip (dataCopy, srcData.data, srcData.lineStride, imageW, imageH); break; | |||
| default: break; | |||
| } | |||
| create (textureW, textureH, dataCopy, JUCE_RGBA_FORMAT); | |||
| create (imageW, imageH, dataCopy, JUCE_RGBA_FORMAT, true); | |||
| } | |||
| void OpenGLTexture::loadARGB (const PixelARGB* pixels, const int w, const int h) | |||
| { | |||
| jassert (isValidSize (w, h)); | |||
| create (w, h, pixels, JUCE_RGBA_FORMAT); | |||
| create (w, h, pixels, JUCE_RGBA_FORMAT, false); | |||
| } | |||
| void OpenGLTexture::loadAlpha (const uint8* pixels, int w, int h) | |||
| { | |||
| jassert (isValidSize (w, h)); | |||
| create (w, h, pixels, GL_ALPHA); | |||
| create (w, h, pixels, GL_ALPHA, false); | |||
| } | |||
| void OpenGLTexture::loadARGBFlipped (const PixelARGB* pixels, int w, int h) | |||
| { | |||
| const int textureW = nextPowerOfTwo (w); | |||
| const int textureH = nextPowerOfTwo (h); | |||
| HeapBlock<PixelARGB> flippedCopy; | |||
| Flipper<PixelARGB>::flip (flippedCopy, (const uint8*) pixels, 4 * w, w, h, textureW, textureH); | |||
| Flipper<PixelARGB>::flip (flippedCopy, (const uint8*) pixels, 4 * w, w, h); | |||
| loadARGB (flippedCopy, textureW, textureH); | |||
| create (w, h, flippedCopy, JUCE_RGBA_FORMAT, true); | |||
| } | |||
| void OpenGLTexture::release() | |||
| @@ -47,11 +47,10 @@ public: | |||
| void loadImage (const Image& image); | |||
| /** Creates a texture from a raw array of pixels. | |||
| The width and height provided must be valid - i.e. power-of-two unless | |||
| the underlying GL system allows otherwise. | |||
| If width and height are not powers-of-two, the texture will be created with a | |||
| larger size, and only the subsection (0, 0, width, height) will be initialised. | |||
| The data is sent directly to the OpenGL driver without being flipped vertically, | |||
| so the first pixel will be mapped onto texture coordinate (0, 0). | |||
| bottom-left corner of the texture | |||
| */ | |||
| void loadARGB (const PixelARGB* pixels, int width, int height); | |||
| @@ -63,11 +62,10 @@ public: | |||
| void loadARGBFlipped (const PixelARGB* pixels, int width, int height); | |||
| /** Creates an alpha-channel texture from an array of alpha values. | |||
| The width and height provided must be valid - i.e. power-of-two unless | |||
| the underlying GL system allows otherwise. | |||
| If width and height are not powers-of-two, the texture will be created with a | |||
| larger size, and only the subsection (0, 0, width, height) will be initialised. | |||
| The data is sent directly to the OpenGL driver without being flipped vertically, | |||
| so the first pixel will be mapped onto texture coordinate (0, 0). | |||
| bottom-left corner of the texture | |||
| */ | |||
| void loadAlpha (const uint8* pixels, int width, int height); | |||
| @@ -112,7 +110,7 @@ private: | |||
| int width, height; | |||
| OpenGLContext* ownerContext; | |||
| void create (int w, int h, const void*, GLenum type); | |||
| void create (int w, int h, const void*, GLenum, bool topLeft); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLTexture) | |||
| }; | |||