diff --git a/modules/juce_graphics/contexts/juce_GraphicsContext.cpp b/modules/juce_graphics/contexts/juce_GraphicsContext.cpp index 8e1a783aad..9ff39ad3c4 100644 --- a/modules/juce_graphics/contexts/juce_GraphicsContext.cpp +++ b/modules/juce_graphics/contexts/juce_GraphicsContext.cpp @@ -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 diff --git a/modules/juce_opengl/juce_opengl.cpp b/modules/juce_opengl/juce_opengl.cpp index 034d244d60..b32bd1805a 100644 --- a/modules/juce_opengl/juce_opengl.cpp +++ b/modules/juce_opengl/juce_opengl.cpp @@ -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" diff --git a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp index d8bf0b17f1..425714e1b3 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp @@ -244,7 +244,7 @@ public: glBindTexture (GL_TEXTURE_2D, cachedImageFrameBuffer.getTextureID()); const Rectangle 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& targetClipArea, const Rectangle& 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& 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& 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& bounds) const + void set (const float targetWidth, const float targetHeight, const Rectangle& 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& 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); diff --git a/modules/juce_opengl/opengl/juce_OpenGLContext.h b/modules/juce_opengl/opengl/juce_OpenGLContext.h index 4335477568..14ddd0bf9c 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLContext.h +++ b/modules/juce_opengl/opengl/juce_OpenGLContext.h @@ -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& targetClipArea, const Rectangle& anchorPosAndTextureSize, - int contextWidth, int contextHeight); + int contextWidth, int contextHeight, + bool textureOriginIsBottomLeft); //============================================================================== diff --git a/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.cpp b/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.cpp index 742e70bb9c..2bf1e90ffe 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.cpp @@ -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& 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& area) { + OpenGLTargetSaver ts (pimpl->context); + if (! makeCurrentRenderingTarget()) return false; @@ -339,10 +340,10 @@ bool OpenGLFrameBuffer::writePixels (const PixelARGB* data, const Rectangle 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 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 (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; } diff --git a/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp b/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp index ccd5863158..c0c940b927 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp @@ -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 (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 diff --git a/modules/juce_opengl/opengl/juce_OpenGLTexture.cpp b/modules/juce_opengl/opengl/juce_OpenGLTexture.cpp index 550c353ad3..9cca33a054 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLTexture.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLTexture.cpp @@ -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 struct Flipper { static void flip (HeapBlock& 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 dataCopy; Image::BitmapData srcData (image, Image::BitmapData::readOnly); switch (srcData.pixelFormat) { - case Image::ARGB: Flipper ::flip (dataCopy, srcData.data, srcData.lineStride, imageW, imageH, textureW, textureH); break; - case Image::RGB: Flipper ::flip (dataCopy, srcData.data, srcData.lineStride, imageW, imageH, textureW, textureH); break; - case Image::SingleChannel: Flipper::flip (dataCopy, srcData.data, srcData.lineStride, imageW, imageH, textureW, textureH); break; + case Image::ARGB: Flipper ::flip (dataCopy, srcData.data, srcData.lineStride, imageW, imageH); break; + case Image::RGB: Flipper ::flip (dataCopy, srcData.data, srcData.lineStride, imageW, imageH); break; + case Image::SingleChannel: Flipper::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 flippedCopy; - Flipper::flip (flippedCopy, (const uint8*) pixels, 4 * w, w, h, textureW, textureH); + Flipper::flip (flippedCopy, (const uint8*) pixels, 4 * w, w, h); - loadARGB (flippedCopy, textureW, textureH); + create (w, h, flippedCopy, JUCE_RGBA_FORMAT, true); } void OpenGLTexture::release() diff --git a/modules/juce_opengl/opengl/juce_OpenGLTexture.h b/modules/juce_opengl/opengl/juce_OpenGLTexture.h index 617056853f..bd59cccc77 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLTexture.h +++ b/modules/juce_opengl/opengl/juce_OpenGLTexture.h @@ -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) };