diff --git a/modules/juce_graphics/images/juce_Image.cpp b/modules/juce_graphics/images/juce_Image.cpp index e3ab8d594f..d9aa0c4e55 100644 --- a/modules/juce_graphics/images/juce_Image.cpp +++ b/modules/juce_graphics/images/juce_Image.cpp @@ -31,6 +31,12 @@ ImagePixelData::ImagePixelData (const Image::PixelFormat format, const int w, co ImagePixelData::~ImagePixelData() { + listeners.call (&Listener::imageDataBeingDeleted, this); +} + +void ImagePixelData::sendDataChangeMessage() +{ + listeners.call (&Listener::imageDataChanged, this); } //============================================================================== @@ -69,15 +75,19 @@ public: LowLevelGraphicsContext* createLowLevelContext() override { + sendDataChangeMessage(); return new LowLevelGraphicsSoftwareRenderer (Image (this)); } - void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode) override + void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override { bitmap.data = imageData + x * pixelStride + y * lineStride; bitmap.pixelFormat = pixelFormat; bitmap.lineStride = lineStride; bitmap.pixelStride = pixelStride; + + if (mode != Image::BitmapData::readOnly) + sendDataChangeMessage(); } ImagePixelData* clone() override @@ -146,6 +156,9 @@ public: void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override { image->initialiseBitmapData (bitmap, x + area.getX(), y + area.getY(), mode); + + if (mode != Image::BitmapData::readOnly) + sendDataChangeMessage(); } ImagePixelData* clone() override diff --git a/modules/juce_graphics/images/juce_Image.h b/modules/juce_graphics/images/juce_Image.h index 0edab3dc90..3580f9f2a4 100644 --- a/modules/juce_graphics/images/juce_Image.h +++ b/modules/juce_graphics/images/juce_Image.h @@ -455,6 +455,19 @@ public: typedef ReferenceCountedObjectPtr Ptr; + //============================================================================== + struct Listener + { + virtual ~Listener() {} + + virtual void imageDataChanged (ImagePixelData*) = 0; + virtual void imageDataBeingDeleted (ImagePixelData*) = 0; + }; + + ListenerList listeners; + + void sendDataChangeMessage(); + private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImagePixelData) }; diff --git a/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm index 7ae0114aa6..03e47fff40 100644 --- a/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm +++ b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm @@ -52,15 +52,19 @@ public: LowLevelGraphicsContext* createLowLevelContext() override { + sendDataChangeMessage(); return new CoreGraphicsContext (context, height, 1.0f); } - void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode) override + void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override { bitmap.data = imageData + x * pixelStride + y * lineStride; bitmap.pixelFormat = pixelFormat; bitmap.lineStride = lineStride; bitmap.pixelStride = pixelStride; + + if (mode != Image::BitmapData::readOnly) + sendDataChangeMessage(); } ImagePixelData* clone() override diff --git a/modules/juce_gui_basics/native/juce_linux_Windowing.cpp b/modules/juce_gui_basics/native/juce_linux_Windowing.cpp index 0d314db582..3a8ddfa0cc 100644 --- a/modules/juce_gui_basics/native/juce_linux_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_linux_Windowing.cpp @@ -617,15 +617,19 @@ public: LowLevelGraphicsContext* createLowLevelContext() override { + sendDataChangeMessage(); return new LowLevelGraphicsSoftwareRenderer (Image (this)); } - void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode) override + void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override { bitmap.data = imageData + x * pixelStride + y * lineStride; bitmap.pixelFormat = pixelFormat; bitmap.lineStride = lineStride; bitmap.pixelStride = pixelStride; + + if (mode != Image::BitmapData::readOnly) + sendDataChangeMessage(); } ImagePixelData* clone() override diff --git a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index ab14b16fcc..6d62c85976 100644 --- a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -328,15 +328,19 @@ public: LowLevelGraphicsContext* createLowLevelContext() override { + sendDataChangeMessage(); return new LowLevelGraphicsSoftwareRenderer (Image (this)); } - void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode) override + void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override { bitmap.data = imageData + x * pixelStride + y * lineStride; bitmap.pixelFormat = pixelFormat; bitmap.lineStride = lineStride; bitmap.pixelStride = pixelStride; + + if (mode != Image::BitmapData::readOnly) + sendDataChangeMessage(); } ImagePixelData* clone() override diff --git a/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp b/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp index a62b6f3de3..4bc31533f2 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp @@ -25,6 +25,171 @@ namespace OpenGLRendering { +struct TextureInfo +{ + GLuint textureID; + int imageWidth, imageHeight; + float fullWidthProportion, fullHeightProportion; +}; + +//============================================================================== +// This list persists in the OpenGLContext, and will re-use cached textures which +// are created from Images. +struct CachedImageList : public ReferenceCountedObject, + private ImagePixelData::Listener +{ + CachedImageList (size_t totalCacheSizeInPixels = 8 * 1024 * 1024) noexcept + : totalSize (0), maxCacheSize (totalCacheSizeInPixels) {} + + static CachedImageList* get (OpenGLContext& context) + { + const char cacheValueID[] = "CachedImages"; + CachedImageList* list = static_cast (context.getAssociatedObject (cacheValueID)); + + if (list == nullptr) + { + list = new CachedImageList(); + context.setAssociatedObject (cacheValueID, list); + } + + return list; + } + + TextureInfo getTextureFor (const Image& image) + { + ImagePixelData* const pixelData = image.getPixelData(); + + CachedImage* c = findCachedImage (pixelData); + + if (c == nullptr) + { + if (OpenGLFrameBuffer* const fb = OpenGLImageType::getFrameBufferFrom (image)) + { + TextureInfo t; + t.textureID = fb->getTextureID(); + t.imageWidth = image.getWidth(); + t.imageHeight = image.getHeight(); + t.fullWidthProportion = 1.0f; + t.fullHeightProportion = 1.0f; + + return t; + } + + c = images.add (new CachedImage (*this, pixelData)); + totalSize += c->imageSize; + + while (totalSize > maxCacheSize && images.size() > 1 && totalSize > 0) + removeOldestItem(); + } + + return c->getTextureInfo(); + } + + typedef ReferenceCountedObjectPtr Ptr; + +private: + void imageDataChanged (ImagePixelData* im) override + { + if (CachedImage* c = findCachedImage (im)) + c->texture.release(); + } + + void imageDataBeingDeleted (ImagePixelData* im) override + { + for (int i = images.size(); --i >= 0;) + { + if (images.getUnchecked(i)->pixelData == im) + { + totalSize -= images.getUnchecked(i)->imageSize; + images.remove (i); + break; + } + } + } + + struct CachedImage + { + CachedImage (CachedImageList& list, ImagePixelData* im) + : owner (list), pixelData (im), + lastUsed (Time::getCurrentTime()), + imageSize (im->width * im->height) + { + pixelData->listeners.add (&owner); + } + + ~CachedImage() + { + if (pixelData != nullptr) + pixelData->listeners.remove (&owner); + } + + TextureInfo getTextureInfo() + { + TextureInfo t; + + if (texture.getTextureID() == 0) + texture.loadImage (Image (pixelData)); + + t.textureID = texture.getTextureID(); + t.imageWidth = pixelData->width; + t.imageHeight = pixelData->height; + t.fullWidthProportion = t.imageWidth / (float) texture.getWidth(); + t.fullHeightProportion = t.imageHeight / (float) texture.getHeight(); + + lastUsed = Time::getCurrentTime(); + + return t; + } + + CachedImageList& owner; + ImagePixelData* pixelData; + OpenGLTexture texture; + Time lastUsed; + const size_t imageSize; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedImage) + }; + + OwnedArray images; + size_t totalSize, maxCacheSize; + + CachedImage* findCachedImage (ImagePixelData* const pixelData) const + { + for (int i = 0; i < images.size(); ++i) + { + CachedImage* c = images.getUnchecked(i); + + if (c->pixelData == pixelData) + return c; + } + + return nullptr; + } + + void removeOldestItem() + { + CachedImage* oldest = nullptr; + + for (int i = 0; i < images.size(); ++i) + { + CachedImage* c = images.getUnchecked(i); + + if (oldest == nullptr || c->lastUsed < oldest->lastUsed) + oldest = c; + } + + if (oldest != nullptr) + { + totalSize -= oldest->imageSize; + images.removeObject (oldest); + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedImageList) +}; + + +//============================================================================== struct Target { Target (OpenGLContext& c, GLuint fbID, int width, int height) noexcept @@ -466,13 +631,13 @@ public: imageLimits.set (fullWidthProportion, fullHeightProportion); } - void setMatrix (const AffineTransform& trans, const OpenGLTextureFromImage& im, + void setMatrix (const AffineTransform& trans, const TextureInfo& textureInfo, const float targetX, const float targetY, bool isForTiling) const { setMatrix (trans, - im.imageWidth, im.imageHeight, - im.fullWidthProportion, im.fullHeightProportion, + textureInfo.imageWidth, textureInfo.imageHeight, + textureInfo.fullWidthProportion, textureInfo.fullHeightProportion, targetX, targetY, isForTiling); } @@ -1134,6 +1299,7 @@ public: JUCE_CHECK_OPENGL_ERROR activeTextures.clear(); shaderQuadQueue.initialise(); + cachedImageList = CachedImageList::get (t.context); JUCE_CHECK_OPENGL_ERROR } @@ -1257,7 +1423,7 @@ public: JUCE_CHECK_OPENGL_ERROR } - void setShaderForTiledImageFill (const OpenGLTextureFromImage& image, const AffineTransform& transform, + void setShaderForTiledImageFill (const TextureInfo& textureInfo, const AffineTransform& transform, const int maskTextureID, const Rectangle* const maskArea, bool isTiledFill) { blendMode.setPremultipliedBlendingMode (shaderQuadQueue); @@ -1269,7 +1435,7 @@ public: if (maskArea != nullptr) { - activeTextures.setTwoTextureMode (shaderQuadQueue, image.textureID, (GLuint) maskTextureID); + activeTextures.setTwoTextureMode (shaderQuadQueue, textureInfo.textureID, (GLuint) maskTextureID); if (isTiledFill) { @@ -1287,7 +1453,7 @@ public: else { activeTextures.setSingleTextureMode (shaderQuadQueue); - activeTextures.bindTexture (image.textureID); + activeTextures.bindTexture (textureInfo.textureID); if (isTiledFill) { @@ -1301,7 +1467,7 @@ public: } } - imageParams->setMatrix (transform, image, (float) target.bounds.getX(), (float) target.bounds.getY(), isTiledFill); + imageParams->setMatrix (transform, textureInfo, (float) target.bounds.getX(), (float) target.bounds.getY(), isTiledFill); if (maskParams != nullptr) maskParams->setBounds (*maskArea, target, 1); @@ -1315,6 +1481,8 @@ public: StateHelpers::CurrentShader currentShader; StateHelpers::ShaderQuadQueue shaderQuadQueue; + CachedImageList::Ptr cachedImageList; + private: GLuint previousFrameBufferTarget; }; @@ -1434,8 +1602,7 @@ public: const AffineTransform& trans, Graphics::ResamplingQuality, bool tiledFill) const { state->shaderQuadQueue.flush(); - OpenGLTextureFromImage image (src); - state->setShaderForTiledImageFill (image, trans, 0, nullptr, tiledFill); + state->setShaderForTiledImageFill (state->cachedImageList->getTextureFor (src), trans, 0, nullptr, tiledFill); state->shaderQuadQueue.add (iter, PixelARGB ((uint8) alpha, (uint8) alpha, (uint8) alpha, (uint8) alpha)); state->shaderQuadQueue.flush(); diff --git a/modules/juce_opengl/opengl/juce_OpenGLHelpers.cpp b/modules/juce_opengl/opengl/juce_OpenGLHelpers.cpp index f24f432f50..ba43e9cbc5 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLHelpers.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLHelpers.cpp @@ -253,30 +253,3 @@ void OpenGLHelpers::fillRect (const Rectangle& rect) glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); } #endif - -//============================================================================== -OpenGLTextureFromImage::OpenGLTextureFromImage (const Image& image) - : imageWidth (image.getWidth()), - imageHeight (image.getHeight()) -{ - JUCE_CHECK_OPENGL_ERROR - if (OpenGLFrameBuffer* const fb = OpenGLImageType::getFrameBufferFrom (image)) - { - textureID = fb->getTextureID(); - fullWidthProportion = 1.0f; - fullHeightProportion = 1.0f; - } - else - { - texture = new OpenGLTexture(); - texture->loadImage (image); - textureID = texture->getTextureID(); - - fullWidthProportion = imageWidth / (float) texture->getWidth(); - fullHeightProportion = imageHeight / (float) texture->getHeight(); - } - - JUCE_CHECK_OPENGL_ERROR -} - -OpenGLTextureFromImage::~OpenGLTextureFromImage() {} diff --git a/modules/juce_opengl/opengl/juce_OpenGLHelpers.h b/modules/juce_opengl/opengl/juce_OpenGLHelpers.h index 694c2c9304..dec06971cf 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLHelpers.h +++ b/modules/juce_opengl/opengl/juce_OpenGLHelpers.h @@ -97,29 +97,5 @@ public: #endif }; -//============================================================================== -/** - Used as a local object while rendering, this will create a temporary texture ID - from an image in the quickest way possible. - - If the image is backed by an OpenGL framebuffer, it will use that directly; otherwise, - this object will create a temporary texture or framebuffer and copy the image. -*/ -class JUCE_API OpenGLTextureFromImage -{ -public: - OpenGLTextureFromImage (const Image& image); - ~OpenGLTextureFromImage(); - - GLuint textureID; - const int imageWidth, imageHeight; - float fullWidthProportion, fullHeightProportion; - -private: - ScopedPointer texture; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLTextureFromImage) -}; - #endif // JUCE_OPENGLHELPERS_H_INCLUDED diff --git a/modules/juce_opengl/opengl/juce_OpenGLImage.cpp b/modules/juce_opengl/opengl/juce_OpenGLImage.cpp index b8f0a0d34c..0e57852f9c 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLImage.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLImage.cpp @@ -40,6 +40,7 @@ public: LowLevelGraphicsContext* createLowLevelContext() override { + sendDataChangeMessage(); return createOpenGLGraphicsContext (context, frameBuffer); } @@ -73,6 +74,9 @@ public: case Image::BitmapData::readWrite: DataReleaser::initialise (frameBuffer, bitmapData, x, y); break; default: jassertfalse; break; } + + if (mode != Image::BitmapData::readOnly) + sendDataChangeMessage(); } OpenGLContext& context;