mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-30 02:50:05 +00:00
Made the OpenGL graphics context keep a cache of textures it has recently used for Image rendering, to avoid repeatedly moving data to the GPU.
This commit is contained in:
parent
95d9d489a1
commit
7a869d6528
9 changed files with 222 additions and 64 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -455,6 +455,19 @@ public:
|
|||
|
||||
typedef ReferenceCountedObjectPtr<ImagePixelData> Ptr;
|
||||
|
||||
//==============================================================================
|
||||
struct Listener
|
||||
{
|
||||
virtual ~Listener() {}
|
||||
|
||||
virtual void imageDataChanged (ImagePixelData*) = 0;
|
||||
virtual void imageDataBeingDeleted (ImagePixelData*) = 0;
|
||||
};
|
||||
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
void sendDataChangeMessage();
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImagePixelData)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<CachedImageList*> (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<CachedImageList> 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<CachedImage> 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<int>* 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();
|
||||
|
|
|
|||
|
|
@ -253,30 +253,3 @@ void OpenGLHelpers::fillRect (const Rectangle<int>& 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() {}
|
||||
|
|
|
|||
|
|
@ -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<OpenGLTexture> texture;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLTextureFromImage)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_OPENGLHELPERS_H_INCLUDED
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public:
|
|||
|
||||
LowLevelGraphicsContext* createLowLevelContext() override
|
||||
{
|
||||
sendDataChangeMessage();
|
||||
return createOpenGLGraphicsContext (context, frameBuffer);
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +74,9 @@ public:
|
|||
case Image::BitmapData::readWrite: DataReleaser<Reader, Writer>::initialise (frameBuffer, bitmapData, x, y); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
|
||||
if (mode != Image::BitmapData::readOnly)
|
||||
sendDataChangeMessage();
|
||||
}
|
||||
|
||||
OpenGLContext& context;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue