diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 8f183eae5afb..17197535c857 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -23,6 +23,8 @@ #include "mozilla/Preferences.h" #include "mozilla/Util.h" // for DebugOnly +#include "GLTextureImage.h" + using namespace mozilla::gfx; namespace mozilla { @@ -738,6 +740,19 @@ GLContext::CreateTextureImage(const nsIntSize& aSize, return CreateBasicTextureImage(texture, aSize, aWrapMode, aContentType, this, aFlags); } +already_AddRefed +GLContext::CreateBasicTextureImage(GLuint aTexture, + const nsIntSize& aSize, + GLenum aWrapMode, + TextureImage::ContentType aContentType, + GLContext* aContext, + TextureImage::Flags aFlags) +{ + nsRefPtr teximage( + new BasicTextureImage(aTexture, aSize, aWrapMode, aContentType, aContext, aFlags)); + return teximage.forget(); +} + void GLContext::ApplyFilterToBoundTexture(gfxPattern::GraphicsFilter aFilter) { ApplyFilterToBoundTexture(LOCAL_GL_TEXTURE_2D, aFilter); @@ -755,546 +770,6 @@ void GLContext::ApplyFilterToBoundTexture(GLuint aTarget, } } -BasicTextureImage::~BasicTextureImage() -{ - GLContext *ctx = mGLContext; - if (ctx->IsDestroyed() || !ctx->IsOwningThreadCurrent()) { - ctx = ctx->GetSharedContext(); - } - - // If we have a context, then we need to delete the texture; - // if we don't have a context (either real or shared), - // then they went away when the contex was deleted, because it - // was the only one that had access to it. - if (ctx && !ctx->IsDestroyed()) { - mGLContext->MakeCurrent(); - mGLContext->fDeleteTextures(1, &mTexture); - } -} - -gfxASurface* -BasicTextureImage::BeginUpdate(nsIntRegion& aRegion) -{ - NS_ASSERTION(!mUpdateSurface, "BeginUpdate() without EndUpdate()?"); - - // determine the region the client will need to repaint - if (mGLContext->CanUploadSubTextures()) { - GetUpdateRegion(aRegion); - } else { - aRegion = nsIntRect(nsIntPoint(0, 0), mSize); - } - - mUpdateRegion = aRegion; - - nsIntRect rgnSize = mUpdateRegion.GetBounds(); - if (!nsIntRect(nsIntPoint(0, 0), mSize).Contains(rgnSize)) { - NS_ERROR("update outside of image"); - return NULL; - } - - ImageFormat format = - (GetContentType() == gfxASurface::CONTENT_COLOR) ? - gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; - mUpdateSurface = - GetSurfaceForUpdate(gfxIntSize(rgnSize.width, rgnSize.height), format); - - if (!mUpdateSurface || mUpdateSurface->CairoStatus()) { - mUpdateSurface = NULL; - return NULL; - } - - mUpdateSurface->SetDeviceOffset(gfxPoint(-rgnSize.x, -rgnSize.y)); - - return mUpdateSurface; -} - -void -BasicTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) -{ - // if the texture hasn't been initialized yet, or something important - // changed, we need to recreate our backing surface and force the - // client to paint everything - if (mTextureState != Valid) - aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); -} - -void -BasicTextureImage::EndUpdate() -{ - NS_ASSERTION(!!mUpdateSurface, "EndUpdate() without BeginUpdate()?"); - - // FIXME: this is the slow boat. Make me fast (with GLXPixmap?). - - // Undo the device offset that BeginUpdate set; doesn't much matter for us here, - // but important if we ever do anything directly with the surface. - mUpdateSurface->SetDeviceOffset(gfxPoint(0, 0)); - - bool relative = FinishedSurfaceUpdate(); - - mShaderType = - mGLContext->UploadSurfaceToTexture(mUpdateSurface, - mUpdateRegion, - mTexture, - mTextureState == Created, - mUpdateOffset, - relative); - FinishedSurfaceUpload(); - - mUpdateSurface = nullptr; - mTextureState = Valid; -} - -void -BasicTextureImage::BindTexture(GLenum aTextureUnit) -{ - mGLContext->fActiveTexture(aTextureUnit); - mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); - mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); -} - -void -BasicTextureImage::ApplyFilter() -{ - mGLContext->ApplyFilterToBoundTexture(mFilter); -} - - -already_AddRefed -BasicTextureImage::GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt) -{ - return gfxPlatform::GetPlatform()-> - CreateOffscreenSurface(aSize, gfxASurface::ContentFromFormat(aFmt)); -} - -bool -BasicTextureImage::FinishedSurfaceUpdate() -{ - return false; -} - -void -BasicTextureImage::FinishedSurfaceUpload() -{ -} - -bool -BasicTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) -{ - nsIntRect bounds = aRegion.GetBounds(); - nsIntRegion region; - if (mTextureState != Valid) { - bounds = nsIntRect(0, 0, mSize.width, mSize.height); - region = nsIntRegion(bounds); - } else { - region = aRegion; - } - - mShaderType = - mGLContext->UploadSurfaceToTexture(aSurf, - region, - mTexture, - mTextureState == Created, - bounds.TopLeft() + aFrom, - false); - mTextureState = Valid; - return true; -} - -void -BasicTextureImage::Resize(const nsIntSize& aSize) -{ - NS_ASSERTION(!mUpdateSurface, "Resize() while in update?"); - - mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); - - mGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D, - 0, - LOCAL_GL_RGBA, - aSize.width, - aSize.height, - 0, - LOCAL_GL_RGBA, - LOCAL_GL_UNSIGNED_BYTE, - NULL); - - mTextureState = Allocated; - mSize = aSize; -} - -TiledTextureImage::TiledTextureImage(GLContext* aGL, - nsIntSize aSize, - TextureImage::ContentType aContentType, - TextureImage::Flags aFlags) - : TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aFlags) - , mCurrentImage(0) - , mIterationCallback(nullptr) - , mInUpdate(false) - , mRows(0) - , mColumns(0) - , mGL(aGL) - , mTextureState(Created) -{ - mTileSize = (!(aFlags & TextureImage::ForceSingleTile) && mGL->WantsSmallTiles()) - ? 256 : mGL->GetMaxTextureSize(); - if (aSize != nsIntSize(0,0)) { - Resize(aSize); - } -} - -TiledTextureImage::~TiledTextureImage() -{ -} - -bool -TiledTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) -{ - nsIntRegion region; - - if (mTextureState != Valid) { - nsIntRect bounds = nsIntRect(0, 0, mSize.width, mSize.height); - region = nsIntRegion(bounds); - } else { - region = aRegion; - } - - bool result = true; - int oldCurrentImage = mCurrentImage; - BeginTileIteration(); - do { - nsIntRect tileRect = GetSrcTileRect(); - int xPos = tileRect.x; - int yPos = tileRect.y; - - nsIntRegion tileRegion; - tileRegion.And(region, tileRect); // intersect with tile - - if (tileRegion.IsEmpty()) - continue; - - if (mGL->CanUploadSubTextures()) { - tileRegion.MoveBy(-xPos, -yPos); // translate into tile local space - } else { - // If sub-textures are unsupported, expand to tile boundaries - tileRect.x = tileRect.y = 0; - tileRegion = nsIntRegion(tileRect); - } - - result &= mImages[mCurrentImage]-> - DirectUpdate(aSurf, tileRegion, aFrom + nsIntPoint(xPos, yPos)); - - if (mCurrentImage == mImages.Length() - 1) { - // We know we're done, but we still need to ensure that the callback - // gets called (e.g. to update the uploaded region). - NextTile(); - break; - } - // Override a callback cancelling iteration if the texture wasn't valid. - // We need to force the update in that situation, or we may end up - // showing invalid/out-of-date texture data. - } while (NextTile() || (mTextureState != Valid)); - mCurrentImage = oldCurrentImage; - - mShaderType = mImages[0]->GetShaderProgramType(); - mTextureState = Valid; - return result; -} - -void -TiledTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) -{ - if (mTextureState != Valid) { - // if the texture hasn't been initialized yet, or something important - // changed, we need to recreate our backing surface and force the - // client to paint everything - aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); - return; - } - - nsIntRegion newRegion; - - // We need to query each texture with the region it will be drawing and - // set aForRegion to be the combination of all of these regions - for (unsigned i = 0; i < mImages.Length(); i++) { - int xPos = (i % mColumns) * mTileSize; - int yPos = (i / mColumns) * mTileSize; - nsIntRect imageRect = nsIntRect(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); - - if (aForRegion.Intersects(imageRect)) { - // Make a copy of the region - nsIntRegion subRegion; - subRegion.And(aForRegion, imageRect); - // Translate it into tile-space - subRegion.MoveBy(-xPos, -yPos); - // Query region - mImages[i]->GetUpdateRegion(subRegion); - // Translate back - subRegion.MoveBy(xPos, yPos); - // Add to the accumulated region - newRegion.Or(newRegion, subRegion); - } - } - - aForRegion = newRegion; -} - -gfxASurface* -TiledTextureImage::BeginUpdate(nsIntRegion& aRegion) -{ - NS_ASSERTION(!mInUpdate, "nested update"); - mInUpdate = true; - - // Note, we don't call GetUpdateRegion here as if the updated region is - // fully contained in a single tile, we get to avoid iterating through - // the tiles again (and a little copying). - if (mTextureState != Valid) - { - // if the texture hasn't been initialized yet, or something important - // changed, we need to recreate our backing surface and force the - // client to paint everything - aRegion = nsIntRect(nsIntPoint(0, 0), mSize); - } - - nsIntRect bounds = aRegion.GetBounds(); - - for (unsigned i = 0; i < mImages.Length(); i++) { - int xPos = (i % mColumns) * mTileSize; - int yPos = (i / mColumns) * mTileSize; - nsIntRegion imageRegion = nsIntRegion(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); - - // a single Image can handle this update request - if (imageRegion.Contains(aRegion)) { - // adjust for tile offset - aRegion.MoveBy(-xPos, -yPos); - // forward the actual call - nsRefPtr surface = mImages[i]->BeginUpdate(aRegion); - // caller expects container space - aRegion.MoveBy(xPos, yPos); - // Correct the device offset - gfxPoint offset = surface->GetDeviceOffset(); - surface->SetDeviceOffset(gfxPoint(offset.x - xPos, - offset.y - yPos)); - // we don't have a temp surface - mUpdateSurface = nullptr; - // remember which image to EndUpdate - mCurrentImage = i; - return surface.get(); - } - } - - // Get the real updated region, taking into account the capabilities of - // each TextureImage tile - GetUpdateRegion(aRegion); - mUpdateRegion = aRegion; - bounds = aRegion.GetBounds(); - - // update covers multiple Images - create a temp surface to paint in - gfxASurface::gfxImageFormat format = - (GetContentType() == gfxASurface::CONTENT_COLOR) ? - gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; - mUpdateSurface = gfxPlatform::GetPlatform()-> - CreateOffscreenSurface(gfxIntSize(bounds.width, bounds.height), gfxASurface::ContentFromFormat(format)); - mUpdateSurface->SetDeviceOffset(gfxPoint(-bounds.x, -bounds.y)); - - return mUpdateSurface; -} - -void -TiledTextureImage::EndUpdate() -{ - NS_ASSERTION(mInUpdate, "EndUpdate not in update"); - if (!mUpdateSurface) { // update was to a single TextureImage - mImages[mCurrentImage]->EndUpdate(); - mInUpdate = false; - mTextureState = Valid; - mShaderType = mImages[mCurrentImage]->GetShaderProgramType(); - return; - } - - // upload tiles from temp surface - for (unsigned i = 0; i < mImages.Length(); i++) { - int xPos = (i % mColumns) * mTileSize; - int yPos = (i / mColumns) * mTileSize; - nsIntRect imageRect = nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize()); - - nsIntRegion subregion; - subregion.And(mUpdateRegion, imageRect); - if (subregion.IsEmpty()) - continue; - subregion.MoveBy(-xPos, -yPos); // Tile-local space - // copy tile from temp surface - gfxASurface* surf = mImages[i]->BeginUpdate(subregion); - nsRefPtr ctx = new gfxContext(surf); - gfxUtils::ClipToRegion(ctx, subregion); - ctx->SetOperator(gfxContext::OPERATOR_SOURCE); - ctx->SetSource(mUpdateSurface, gfxPoint(-xPos, -yPos)); - ctx->Paint(); - mImages[i]->EndUpdate(); - } - - mUpdateSurface = nullptr; - mInUpdate = false; - mShaderType = mImages[0]->GetShaderProgramType(); - mTextureState = Valid; -} - -void TiledTextureImage::BeginTileIteration() -{ - mCurrentImage = 0; -} - -bool TiledTextureImage::NextTile() -{ - bool continueIteration = true; - - if (mIterationCallback) - continueIteration = mIterationCallback(this, mCurrentImage, - mIterationCallbackData); - - if (mCurrentImage + 1 < mImages.Length()) { - mCurrentImage++; - return continueIteration; - } - return false; -} - -void TiledTextureImage::SetIterationCallback(TileIterationCallback aCallback, - void* aCallbackData) -{ - mIterationCallback = aCallback; - mIterationCallbackData = aCallbackData; -} - -nsIntRect TiledTextureImage::GetTileRect() -{ - nsIntRect rect = mImages[mCurrentImage]->GetTileRect(); - unsigned int xPos = (mCurrentImage % mColumns) * mTileSize; - unsigned int yPos = (mCurrentImage / mColumns) * mTileSize; - rect.MoveBy(xPos, yPos); - return rect; -} - -nsIntRect TiledTextureImage::GetSrcTileRect() -{ - nsIntRect rect = GetTileRect(); - unsigned int srcY = mFlags & NeedsYFlip - ? mSize.height - rect.height - rect.y - : rect.y; - return nsIntRect(rect.x, srcY, rect.width, rect.height); -} - -void -TiledTextureImage::BindTexture(GLenum aTextureUnit) -{ - mImages[mCurrentImage]->BindTexture(aTextureUnit); -} - -void -TiledTextureImage::ApplyFilter() -{ - mGL->ApplyFilterToBoundTexture(mFilter); -} - -/* - * Resize, trying to reuse tiles. The reuse strategy is to decide on reuse per - * column. A tile on a column is reused if it hasn't changed size, otherwise it - * is discarded/replaced. Extra tiles on a column are pruned after iterating - * each column, and extra rows are pruned after iteration over the entire image - * finishes. - */ -void TiledTextureImage::Resize(const nsIntSize& aSize) -{ - if (mSize == aSize && mTextureState != Created) { - return; - } - - // calculate rows and columns, rounding up - unsigned int columns = (aSize.width + mTileSize - 1) / mTileSize; - unsigned int rows = (aSize.height + mTileSize - 1) / mTileSize; - - // Iterate over old tile-store and insert/remove tiles as necessary - int row; - unsigned int i = 0; - for (row = 0; row < (int)rows; row++) { - // If we've gone beyond how many rows there were before, set mColumns to - // zero so that we only create new tiles. - if (row >= (int)mRows) - mColumns = 0; - - // Similarly, if we're on the last row of old tiles and the height has - // changed, discard all tiles in that row. - // This will cause the pruning of columns not to work, but we don't need - // to worry about that, as no more tiles will be reused past this point - // anyway. - if ((row == (int)mRows - 1) && (aSize.height != mSize.height)) - mColumns = 0; - - int col; - for (col = 0; col < (int)columns; col++) { - nsIntSize size( // use tilesize first, then the remainder - (col+1) * mTileSize > (unsigned int)aSize.width ? aSize.width % mTileSize : mTileSize, - (row+1) * mTileSize > (unsigned int)aSize.height ? aSize.height % mTileSize : mTileSize); - - bool replace = false; - - // Check if we can re-use old tiles. - if (col < (int)mColumns) { - // Reuse an existing tile. If the tile is an end-tile and the - // width differs, replace it instead. - if (mSize.width != aSize.width) { - if (col == (int)mColumns - 1) { - // Tile at the end of the old column, replace it with - // a new one. - replace = true; - } else if (col == (int)columns - 1) { - // Tile at the end of the new column, create a new one. - } else { - // Before the last column on both the old and new sizes, - // reuse existing tile. - i++; - continue; - } - } else { - // Width hasn't changed, reuse existing tile. - i++; - continue; - } - } - - // Create a new tile. - nsRefPtr teximg = - mGL->TileGenFunc(size, mContentType, mFlags); - if (replace) - mImages.ReplaceElementAt(i, teximg.forget()); - else - mImages.InsertElementAt(i, teximg.forget()); - i++; - } - - // Prune any unused tiles on the end of the column. - if (row < (int)mRows) { - for (col = (int)mColumns - col; col > 0; col--) { - mImages.RemoveElementAt(i); - } - } - } - - // Prune any unused tiles at the end of the store. - unsigned int length = mImages.Length(); - for (; i < length; i++) - mImages.RemoveElementAt(mImages.Length()-1); - - // Reset tile-store properties. - mRows = rows; - mColumns = columns; - mSize = aSize; - mTextureState = Allocated; - mCurrentImage = 0; -} - -uint32_t TiledTextureImage::GetTileCount() -{ - return mImages.Length(); -} GLContext::GLFormats GLContext::ChooseGLFormats(ContextFormat& aCF, ColorByteOrder aByteOrder) diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index 5188d4411157..d8a327b26d41 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -33,6 +33,7 @@ #include "nsAutoPtr.h" #include "nsThreadUtils.h" #include "GLContextTypes.h" +#include "GLTextureImage.h" typedef char realGLboolean; @@ -56,368 +57,6 @@ class GLContext; typedef uintptr_t SharedTextureHandle; -/** - * A TextureImage encapsulates a surface that can be drawn to by a - * Thebes gfxContext and (hopefully efficiently!) synchronized to a - * texture in the server. TextureImages are associated with one and - * only one GLContext. - * - * Implementation note: TextureImages attempt to unify two categories - * of backends - * - * (1) proxy to server-side object that can be bound to a texture; - * e.g. Pixmap on X11. - * - * (2) efficient manager of texture memory; e.g. by having clients draw - * into a scratch buffer which is then uploaded with - * glTexSubImage2D(). - */ -class TextureImage -{ - NS_INLINE_DECL_REFCOUNTING(TextureImage) -public: - enum TextureState - { - Created, // Texture created, but has not had glTexImage called to initialize it. - Allocated, // Texture memory exists, but contents are invalid. - Valid // Texture fully ready to use. - }; - - enum Flags { - NoFlags = 0x0, - UseNearestFilter = 0x1, - NeedsYFlip = 0x2, - ForceSingleTile = 0x4 - }; - - typedef gfxASurface::gfxContentType ContentType; - - virtual ~TextureImage() {} - - /** - * Returns a gfxASurface for updating |aRegion| of the client's - * image if successul, NULL if not. |aRegion|'s bounds must fit - * within Size(); its coordinate space (if any) is ignored. If - * the update begins successfully, the returned gfxASurface is - * owned by this. Otherwise, NULL is returned. - * - * |aRegion| is an inout param: the returned region is what the - * client must repaint. Category (1) regions above can - * efficiently handle repaints to "scattered" regions, while (2) - * can only efficiently handle repaints to rects. - * - * Painting the returned surface outside of |aRegion| results - * in undefined behavior. - * - * BeginUpdate() calls cannot be "nested", and each successful - * BeginUpdate() must be followed by exactly one EndUpdate() (see - * below). Failure to do so can leave this in a possibly - * inconsistent state. Unsuccessful BeginUpdate()s must not be - * followed by EndUpdate(). - */ - virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion) = 0; - /** - * Retrieves the region that will require updating, given a - * region that needs to be updated. This can be used for - * making decisions about updating before calling BeginUpdate(). - * - * |aRegion| is an inout param. - */ - virtual void GetUpdateRegion(nsIntRegion& aForRegion) { - } - /** - * Finish the active update and synchronize with the server, if - * necessary. - * - * BeginUpdate() must have been called exactly once before - * EndUpdate(). - */ - virtual void EndUpdate() = 0; - - /** - * The Image may contain several textures for different regions (tiles). - * These functions iterate over each sub texture image tile. - */ - virtual void BeginTileIteration() { - } - - virtual bool NextTile() { - return false; - } - - // Function prototype for a tile iteration callback. Returning false will - // cause iteration to be interrupted (i.e. the corresponding NextTile call - // will return false). - typedef bool (* TileIterationCallback)(TextureImage* aImage, - int aTileNumber, - void* aCallbackData); - - // Sets a callback to be called every time NextTile is called. - virtual void SetIterationCallback(TileIterationCallback aCallback, - void* aCallbackData) { - } - - virtual nsIntRect GetTileRect() { - return nsIntRect(nsIntPoint(0,0), mSize); - } - - virtual GLuint GetTextureID() = 0; - - virtual uint32_t GetTileCount() { - return 1; - } - - /** - * Set this TextureImage's size, and ensure a texture has been - * allocated. Must not be called between BeginUpdate and EndUpdate. - * After a resize, the contents are undefined. - * - * If this isn't implemented by a subclass, it will just perform - * a dummy BeginUpdate/EndUpdate pair. - */ - virtual void Resize(const nsIntSize& aSize) { - mSize = aSize; - nsIntRegion r(nsIntRect(0, 0, aSize.width, aSize.height)); - BeginUpdate(r); - EndUpdate(); - } - - /** - * Mark this texture as having valid contents. Call this after modifying - * the texture contents externally. - */ - virtual void MarkValid() {} - - /** - * aSurf - the source surface to update from - * aRegion - the region in this image to update - * aFrom - offset in the source to update from - */ - virtual bool DirectUpdate(gfxASurface *aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)) = 0; - - virtual void BindTexture(GLenum aTextureUnit) = 0; - virtual void ReleaseTexture() {} - - void BindTextureAndApplyFilter(GLenum aTextureUnit) { - BindTexture(aTextureUnit); - ApplyFilter(); - } - - class ScopedBindTexture - { - public: - ScopedBindTexture(TextureImage *aTexture, GLenum aTextureUnit) : - mTexture(aTexture) - { - if (mTexture) { - mTexture->BindTexture(aTextureUnit); - } - } - - ~ScopedBindTexture() - { - if (mTexture) { - mTexture->ReleaseTexture(); - } - } - - protected: - TextureImage *mTexture; - }; - - class ScopedBindTextureAndApplyFilter - : public ScopedBindTexture - { - public: - ScopedBindTextureAndApplyFilter(TextureImage *aTexture, GLenum aTextureUnit) : - ScopedBindTexture(aTexture, aTextureUnit) - { - if (mTexture) { - mTexture->ApplyFilter(); - } - } - }; - - /** - * Returns the shader program type that should be used to render - * this texture. Only valid after a matching BeginUpdate/EndUpdate - * pair have been called. - */ - virtual ShaderProgramType GetShaderProgramType() - { - return mShaderType; - } - - /** Can be called safely at any time. */ - - /** - * If this TextureImage has a permanent gfxASurface backing, - * return it. Otherwise return NULL. - */ - virtual already_AddRefed GetBackingSurface() - { return NULL; } - - const nsIntSize& GetSize() const { return mSize; } - ContentType GetContentType() const { return mContentType; } - virtual bool InUpdate() const = 0; - GLenum GetWrapMode() const { return mWrapMode; } - - void SetFilter(gfxPattern::GraphicsFilter aFilter) { mFilter = aFilter; } - - /** - * Applies this TextureImage's filter, assuming that its texture is - * the currently bound texture. - */ - virtual void ApplyFilter() = 0; - -protected: - friend class GLContext; - - /** - * After the ctor, the TextureImage is invalid. Implementations - * must allocate resources successfully before returning the new - * TextureImage from GLContext::CreateTextureImage(). That is, - * clients must not be given partially-constructed TextureImages. - */ - TextureImage(const nsIntSize& aSize, - GLenum aWrapMode, ContentType aContentType, - Flags aFlags = NoFlags) - : mSize(aSize) - , mWrapMode(aWrapMode) - , mContentType(aContentType) - , mFilter(gfxPattern::FILTER_GOOD) - , mFlags(aFlags) - {} - - virtual nsIntRect GetSrcTileRect() { - return nsIntRect(nsIntPoint(0,0), mSize); - } - - nsIntSize mSize; - GLenum mWrapMode; - ContentType mContentType; - ShaderProgramType mShaderType; - gfxPattern::GraphicsFilter mFilter; - Flags mFlags; -}; - -/** - * BasicTextureImage is the baseline TextureImage implementation --- - * it updates its texture by allocating a scratch buffer for the - * client to draw into, then using glTexSubImage2D() to upload the new - * pixels. Platforms must provide the code to create a new surface - * into which the updated pixels will be drawn, and the code to - * convert the update surface's pixels into an image on which we can - * glTexSubImage2D(). - */ -class BasicTextureImage - : public TextureImage -{ -public: - typedef gfxASurface::gfxImageFormat ImageFormat; - virtual ~BasicTextureImage(); - - BasicTextureImage(GLuint aTexture, - const nsIntSize& aSize, - GLenum aWrapMode, - ContentType aContentType, - GLContext* aContext, - TextureImage::Flags aFlags = TextureImage::NoFlags) - : TextureImage(aSize, aWrapMode, aContentType, aFlags) - , mTexture(aTexture) - , mTextureState(Created) - , mGLContext(aContext) - , mUpdateOffset(0, 0) - {} - - virtual void BindTexture(GLenum aTextureUnit); - - virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); - virtual void GetUpdateRegion(nsIntRegion& aForRegion); - virtual void EndUpdate(); - virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); - virtual GLuint GetTextureID() { return mTexture; } - // Returns a surface to draw into - virtual already_AddRefed - GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt); - - virtual void MarkValid() { mTextureState = Valid; } - - // Call when drawing into the update surface is complete. - // Returns true if textures should be upload with a relative - // offset - See UploadSurfaceToTexture. - virtual bool FinishedSurfaceUpdate(); - - // Call after surface data has been uploaded to a texture. - virtual void FinishedSurfaceUpload(); - - virtual bool InUpdate() const { return !!mUpdateSurface; } - - virtual void Resize(const nsIntSize& aSize); - - virtual void ApplyFilter(); -protected: - - GLuint mTexture; - TextureState mTextureState; - GLContext* mGLContext; - nsRefPtr mUpdateSurface; - nsIntRegion mUpdateRegion; - - // The offset into the update surface at which the update rect is located. - nsIntPoint mUpdateOffset; -}; - -/** - * A container class that complements many sub TextureImages into a big TextureImage. - * Aims to behave just like the real thing. - */ - -class TiledTextureImage - : public TextureImage -{ -public: - TiledTextureImage(GLContext* aGL, nsIntSize aSize, - TextureImage::ContentType, TextureImage::Flags aFlags = TextureImage::NoFlags); - ~TiledTextureImage(); - void DumpDiv(); - virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); - virtual void GetUpdateRegion(nsIntRegion& aForRegion); - virtual void EndUpdate(); - virtual void Resize(const nsIntSize& aSize); - virtual uint32_t GetTileCount(); - virtual void BeginTileIteration(); - virtual bool NextTile(); - virtual void SetIterationCallback(TileIterationCallback aCallback, - void* aCallbackData); - virtual nsIntRect GetTileRect(); - virtual GLuint GetTextureID() { - return mImages[mCurrentImage]->GetTextureID(); - } - virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); - virtual bool InUpdate() const { return mInUpdate; } - virtual void BindTexture(GLenum); - virtual void ApplyFilter(); - -protected: - virtual nsIntRect GetSrcTileRect(); - - unsigned int mCurrentImage; - TileIterationCallback mIterationCallback; - void* mIterationCallbackData; - nsTArray< nsRefPtr > mImages; - bool mInUpdate; - nsIntSize mSize; - unsigned int mTileSize; - unsigned int mRows, mColumns; - GLContext* mGL; - // A temporary surface to faciliate cross-tile updates. - nsRefPtr mUpdateSurface; - // The region of update requested - nsIntRegion mUpdateRegion; - TextureState mTextureState; -}; - struct THEBES_API ContextFormat { static const ContextFormat BasicRGBA32Format; @@ -1927,12 +1566,7 @@ protected: GLenum aWrapMode, TextureImage::ContentType aContentType, GLContext* aContext, - TextureImage::Flags aFlags = TextureImage::NoFlags) - { - nsRefPtr teximage( - new BasicTextureImage(aTexture, aSize, aWrapMode, aContentType, aContext, aFlags)); - return teximage.forget(); - } + TextureImage::Flags aFlags = TextureImage::NoFlags); bool IsOffscreenSizeAllowed(const gfxIntSize& aSize) const { int32_t biggerDimension = NS_MAX(aSize.width, aSize.height); diff --git a/gfx/gl/GLContextProviderEGL.cpp b/gfx/gl/GLContextProviderEGL.cpp index 85bb1c6aad10..0f7b7cbc1efd 100644 --- a/gfx/gl/GLContextProviderEGL.cpp +++ b/gfx/gl/GLContextProviderEGL.cpp @@ -1072,6 +1072,7 @@ bool GLContextEGL::AttachSharedHandle(SharedTextureShareType shareType, return true; } + bool GLContextEGL::BindTex2DOffscreen(GLContext *aOffscreen) { diff --git a/gfx/gl/GLTextureImage.cpp b/gfx/gl/GLTextureImage.cpp new file mode 100644 index 000000000000..fbfef04fa4e6 --- /dev/null +++ b/gfx/gl/GLTextureImage.cpp @@ -0,0 +1,573 @@ +/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GLTextureImage.h" +#include "GLContext.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" + +using namespace mozilla::gl; + +already_AddRefed +TextureImage::Create(GLContext* gl, + const nsIntSize& size, + TextureImage::ContentType contentType, + GLenum wrapMode, + TextureImage::Flags flags) +{ + return gl->CreateTextureImage(size, contentType, wrapMode, flags); +} + +BasicTextureImage::~BasicTextureImage() +{ + GLContext *ctx = mGLContext; + if (ctx->IsDestroyed() || !ctx->IsOwningThreadCurrent()) { + ctx = ctx->GetSharedContext(); + } + + // If we have a context, then we need to delete the texture; + // if we don't have a context (either real or shared), + // then they went away when the contex was deleted, because it + // was the only one that had access to it. + if (ctx && !ctx->IsDestroyed()) { + mGLContext->MakeCurrent(); + mGLContext->fDeleteTextures(1, &mTexture); + } +} + +gfxASurface* +BasicTextureImage::BeginUpdate(nsIntRegion& aRegion) +{ + NS_ASSERTION(!mUpdateSurface, "BeginUpdate() without EndUpdate()?"); + + // determine the region the client will need to repaint + if (mGLContext->CanUploadSubTextures()) { + GetUpdateRegion(aRegion); + } else { + aRegion = nsIntRect(nsIntPoint(0, 0), mSize); + } + + mUpdateRegion = aRegion; + + nsIntRect rgnSize = mUpdateRegion.GetBounds(); + if (!nsIntRect(nsIntPoint(0, 0), mSize).Contains(rgnSize)) { + NS_ERROR("update outside of image"); + return NULL; + } + + ImageFormat format = + (GetContentType() == gfxASurface::CONTENT_COLOR) ? + gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; + mUpdateSurface = + GetSurfaceForUpdate(gfxIntSize(rgnSize.width, rgnSize.height), format); + + if (!mUpdateSurface || mUpdateSurface->CairoStatus()) { + mUpdateSurface = NULL; + return NULL; + } + + mUpdateSurface->SetDeviceOffset(gfxPoint(-rgnSize.x, -rgnSize.y)); + + return mUpdateSurface; +} + +void +BasicTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) +{ + // if the texture hasn't been initialized yet, or something important + // changed, we need to recreate our backing surface and force the + // client to paint everything + if (mTextureState != Valid) + aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); +} + +void +BasicTextureImage::EndUpdate() +{ + NS_ASSERTION(!!mUpdateSurface, "EndUpdate() without BeginUpdate()?"); + + // FIXME: this is the slow boat. Make me fast (with GLXPixmap?). + + // Undo the device offset that BeginUpdate set; doesn't much matter for us here, + // but important if we ever do anything directly with the surface. + mUpdateSurface->SetDeviceOffset(gfxPoint(0, 0)); + + bool relative = FinishedSurfaceUpdate(); + + mShaderType = + mGLContext->UploadSurfaceToTexture(mUpdateSurface, + mUpdateRegion, + mTexture, + mTextureState == Created, + mUpdateOffset, + relative); + FinishedSurfaceUpload(); + + mUpdateSurface = nullptr; + mTextureState = Valid; +} + +void +BasicTextureImage::BindTexture(GLenum aTextureUnit) +{ + mGLContext->fActiveTexture(aTextureUnit); + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); + mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); +} + +void +BasicTextureImage::ApplyFilter() +{ + mGLContext->ApplyFilterToBoundTexture(mFilter); +} + + +already_AddRefed +BasicTextureImage::GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt) +{ + return gfxPlatform::GetPlatform()-> + CreateOffscreenSurface(aSize, gfxASurface::ContentFromFormat(aFmt)); +} + +bool +BasicTextureImage::FinishedSurfaceUpdate() +{ + return false; +} + +void +BasicTextureImage::FinishedSurfaceUpload() +{ +} + +bool +BasicTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) +{ + nsIntRect bounds = aRegion.GetBounds(); + nsIntRegion region; + if (mTextureState != Valid) { + bounds = nsIntRect(0, 0, mSize.width, mSize.height); + region = nsIntRegion(bounds); + } else { + region = aRegion; + } + + mShaderType = + mGLContext->UploadSurfaceToTexture(aSurf, + region, + mTexture, + mTextureState == Created, + bounds.TopLeft() + aFrom, + false); + mTextureState = Valid; + return true; +} + +void +BasicTextureImage::Resize(const nsIntSize& aSize) +{ + NS_ASSERTION(!mUpdateSurface, "Resize() while in update?"); + + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); + + mGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D, + 0, + LOCAL_GL_RGBA, + aSize.width, + aSize.height, + 0, + LOCAL_GL_RGBA, + LOCAL_GL_UNSIGNED_BYTE, + NULL); + + mTextureState = Allocated; + mSize = aSize; +} + +TiledTextureImage::TiledTextureImage(GLContext* aGL, + nsIntSize aSize, + TextureImage::ContentType aContentType, + TextureImage::Flags aFlags) + : TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aFlags) + , mCurrentImage(0) + , mIterationCallback(nullptr) + , mInUpdate(false) + , mRows(0) + , mColumns(0) + , mGL(aGL) + , mTextureState(Created) +{ + mTileSize = (!(aFlags & TextureImage::ForceSingleTile) && mGL->WantsSmallTiles()) + ? 256 : mGL->GetMaxTextureSize(); + if (aSize != nsIntSize(0,0)) { + Resize(aSize); + } +} + +TiledTextureImage::~TiledTextureImage() +{ +} + +bool +TiledTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) +{ + nsIntRegion region; + + if (mTextureState != Valid) { + nsIntRect bounds = nsIntRect(0, 0, mSize.width, mSize.height); + region = nsIntRegion(bounds); + } else { + region = aRegion; + } + + bool result = true; + int oldCurrentImage = mCurrentImage; + BeginTileIteration(); + do { + nsIntRect tileRect = GetSrcTileRect(); + int xPos = tileRect.x; + int yPos = tileRect.y; + + nsIntRegion tileRegion; + tileRegion.And(region, tileRect); // intersect with tile + + if (tileRegion.IsEmpty()) + continue; + + if (mGL->CanUploadSubTextures()) { + tileRegion.MoveBy(-xPos, -yPos); // translate into tile local space + } else { + // If sub-textures are unsupported, expand to tile boundaries + tileRect.x = tileRect.y = 0; + tileRegion = nsIntRegion(tileRect); + } + + result &= mImages[mCurrentImage]-> + DirectUpdate(aSurf, tileRegion, aFrom + nsIntPoint(xPos, yPos)); + + if (mCurrentImage == mImages.Length() - 1) { + // We know we're done, but we still need to ensure that the callback + // gets called (e.g. to update the uploaded region). + NextTile(); + break; + } + // Override a callback cancelling iteration if the texture wasn't valid. + // We need to force the update in that situation, or we may end up + // showing invalid/out-of-date texture data. + } while (NextTile() || (mTextureState != Valid)); + mCurrentImage = oldCurrentImage; + + mShaderType = mImages[0]->GetShaderProgramType(); + mTextureState = Valid; + return result; +} + +void +TiledTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) +{ + if (mTextureState != Valid) { + // if the texture hasn't been initialized yet, or something important + // changed, we need to recreate our backing surface and force the + // client to paint everything + aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); + return; + } + + nsIntRegion newRegion; + + // We need to query each texture with the region it will be drawing and + // set aForRegion to be the combination of all of these regions + for (unsigned i = 0; i < mImages.Length(); i++) { + int xPos = (i % mColumns) * mTileSize; + int yPos = (i / mColumns) * mTileSize; + nsIntRect imageRect = nsIntRect(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); + + if (aForRegion.Intersects(imageRect)) { + // Make a copy of the region + nsIntRegion subRegion; + subRegion.And(aForRegion, imageRect); + // Translate it into tile-space + subRegion.MoveBy(-xPos, -yPos); + // Query region + mImages[i]->GetUpdateRegion(subRegion); + // Translate back + subRegion.MoveBy(xPos, yPos); + // Add to the accumulated region + newRegion.Or(newRegion, subRegion); + } + } + + aForRegion = newRegion; +} + +gfxASurface* +TiledTextureImage::BeginUpdate(nsIntRegion& aRegion) +{ + NS_ASSERTION(!mInUpdate, "nested update"); + mInUpdate = true; + + // Note, we don't call GetUpdateRegion here as if the updated region is + // fully contained in a single tile, we get to avoid iterating through + // the tiles again (and a little copying). + if (mTextureState != Valid) + { + // if the texture hasn't been initialized yet, or something important + // changed, we need to recreate our backing surface and force the + // client to paint everything + aRegion = nsIntRect(nsIntPoint(0, 0), mSize); + } + + nsIntRect bounds = aRegion.GetBounds(); + + for (unsigned i = 0; i < mImages.Length(); i++) { + int xPos = (i % mColumns) * mTileSize; + int yPos = (i / mColumns) * mTileSize; + nsIntRegion imageRegion = nsIntRegion(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); + + // a single Image can handle this update request + if (imageRegion.Contains(aRegion)) { + // adjust for tile offset + aRegion.MoveBy(-xPos, -yPos); + // forward the actual call + nsRefPtr surface = mImages[i]->BeginUpdate(aRegion); + // caller expects container space + aRegion.MoveBy(xPos, yPos); + // Correct the device offset + gfxPoint offset = surface->GetDeviceOffset(); + surface->SetDeviceOffset(gfxPoint(offset.x - xPos, + offset.y - yPos)); + // we don't have a temp surface + mUpdateSurface = nullptr; + // remember which image to EndUpdate + mCurrentImage = i; + return surface.get(); + } + } + + // Get the real updated region, taking into account the capabilities of + // each TextureImage tile + GetUpdateRegion(aRegion); + mUpdateRegion = aRegion; + bounds = aRegion.GetBounds(); + + // update covers multiple Images - create a temp surface to paint in + gfxASurface::gfxImageFormat format = + (GetContentType() == gfxASurface::CONTENT_COLOR) ? + gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; + mUpdateSurface = gfxPlatform::GetPlatform()-> + CreateOffscreenSurface(gfxIntSize(bounds.width, bounds.height), gfxASurface::ContentFromFormat(format)); + mUpdateSurface->SetDeviceOffset(gfxPoint(-bounds.x, -bounds.y)); + + return mUpdateSurface; +} + +void +TiledTextureImage::EndUpdate() +{ + NS_ASSERTION(mInUpdate, "EndUpdate not in update"); + if (!mUpdateSurface) { // update was to a single TextureImage + mImages[mCurrentImage]->EndUpdate(); + mInUpdate = false; + mTextureState = Valid; + mShaderType = mImages[mCurrentImage]->GetShaderProgramType(); + return; + } + + // upload tiles from temp surface + for (unsigned i = 0; i < mImages.Length(); i++) { + int xPos = (i % mColumns) * mTileSize; + int yPos = (i / mColumns) * mTileSize; + nsIntRect imageRect = nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize()); + + nsIntRegion subregion; + subregion.And(mUpdateRegion, imageRect); + if (subregion.IsEmpty()) + continue; + subregion.MoveBy(-xPos, -yPos); // Tile-local space + // copy tile from temp surface + gfxASurface* surf = mImages[i]->BeginUpdate(subregion); + nsRefPtr ctx = new gfxContext(surf); + gfxUtils::ClipToRegion(ctx, subregion); + ctx->SetOperator(gfxContext::OPERATOR_SOURCE); + ctx->SetSource(mUpdateSurface, gfxPoint(-xPos, -yPos)); + ctx->Paint(); + mImages[i]->EndUpdate(); + } + + mUpdateSurface = nullptr; + mInUpdate = false; + mShaderType = mImages[0]->GetShaderProgramType(); + mTextureState = Valid; +} + +void TiledTextureImage::BeginTileIteration() +{ + mCurrentImage = 0; +} + +bool TiledTextureImage::NextTile() +{ + bool continueIteration = true; + + if (mIterationCallback) + continueIteration = mIterationCallback(this, mCurrentImage, + mIterationCallbackData); + + if (mCurrentImage + 1 < mImages.Length()) { + mCurrentImage++; + return continueIteration; + } + return false; +} + +void TiledTextureImage::SetIterationCallback(TileIterationCallback aCallback, + void* aCallbackData) +{ + mIterationCallback = aCallback; + mIterationCallbackData = aCallbackData; +} + +nsIntRect TiledTextureImage::GetTileRect() +{ + nsIntRect rect = mImages[mCurrentImage]->GetTileRect(); + unsigned int xPos = (mCurrentImage % mColumns) * mTileSize; + unsigned int yPos = (mCurrentImage / mColumns) * mTileSize; + rect.MoveBy(xPos, yPos); + return rect; +} + +nsIntRect TiledTextureImage::GetSrcTileRect() +{ + nsIntRect rect = GetTileRect(); + unsigned int srcY = mFlags & NeedsYFlip + ? mSize.height - rect.height - rect.y + : rect.y; + return nsIntRect(rect.x, srcY, rect.width, rect.height); +} + +void +TiledTextureImage::BindTexture(GLenum aTextureUnit) +{ + mImages[mCurrentImage]->BindTexture(aTextureUnit); +} + +void +TiledTextureImage::ApplyFilter() +{ + mGL->ApplyFilterToBoundTexture(mFilter); +} + +/* + * Resize, trying to reuse tiles. The reuse strategy is to decide on reuse per + * column. A tile on a column is reused if it hasn't changed size, otherwise it + * is discarded/replaced. Extra tiles on a column are pruned after iterating + * each column, and extra rows are pruned after iteration over the entire image + * finishes. + */ +void TiledTextureImage::Resize(const nsIntSize& aSize) +{ + if (mSize == aSize && mTextureState != Created) { + return; + } + + // calculate rows and columns, rounding up + unsigned int columns = (aSize.width + mTileSize - 1) / mTileSize; + unsigned int rows = (aSize.height + mTileSize - 1) / mTileSize; + + // Iterate over old tile-store and insert/remove tiles as necessary + int row; + unsigned int i = 0; + for (row = 0; row < (int)rows; row++) { + // If we've gone beyond how many rows there were before, set mColumns to + // zero so that we only create new tiles. + if (row >= (int)mRows) + mColumns = 0; + + // Similarly, if we're on the last row of old tiles and the height has + // changed, discard all tiles in that row. + // This will cause the pruning of columns not to work, but we don't need + // to worry about that, as no more tiles will be reused past this point + // anyway. + if ((row == (int)mRows - 1) && (aSize.height != mSize.height)) + mColumns = 0; + + int col; + for (col = 0; col < (int)columns; col++) { + nsIntSize size( // use tilesize first, then the remainder + (col+1) * mTileSize > (unsigned int)aSize.width ? aSize.width % mTileSize : mTileSize, + (row+1) * mTileSize > (unsigned int)aSize.height ? aSize.height % mTileSize : mTileSize); + + bool replace = false; + + // Check if we can re-use old tiles. + if (col < (int)mColumns) { + // Reuse an existing tile. If the tile is an end-tile and the + // width differs, replace it instead. + if (mSize.width != aSize.width) { + if (col == (int)mColumns - 1) { + // Tile at the end of the old column, replace it with + // a new one. + replace = true; + } else if (col == (int)columns - 1) { + // Tile at the end of the new column, create a new one. + } else { + // Before the last column on both the old and new sizes, + // reuse existing tile. + i++; + continue; + } + } else { + // Width hasn't changed, reuse existing tile. + i++; + continue; + } + } + + // Create a new tile. + nsRefPtr teximg = + mGL->TileGenFunc(size, mContentType, mFlags); + if (replace) + mImages.ReplaceElementAt(i, teximg.forget()); + else + mImages.InsertElementAt(i, teximg.forget()); + i++; + } + + // Prune any unused tiles on the end of the column. + if (row < (int)mRows) { + for (col = (int)mColumns - col; col > 0; col--) { + mImages.RemoveElementAt(i); + } + } + } + + // Prune any unused tiles at the end of the store. + unsigned int length = mImages.Length(); + for (; i < length; i++) + mImages.RemoveElementAt(mImages.Length()-1); + + // Reset tile-store properties. + mRows = rows; + mColumns = columns; + mSize = aSize; + mTextureState = Allocated; + mCurrentImage = 0; +} + +uint32_t TiledTextureImage::GetTileCount() +{ + return mImages.Length(); +} + +TextureImage::ScopedBindTexture::ScopedBindTexture(TextureImage* aTexture, + GLenum aTextureUnit) + : mTexture(aTexture) +{ + if (mTexture) { + MOZ_ASSERT(aTextureUnit >= LOCAL_GL_TEXTURE0); + mTexture->BindTexture(aTextureUnit); + } +} diff --git a/gfx/gl/GLTextureImage.h b/gfx/gl/GLTextureImage.h new file mode 100644 index 000000000000..4217778fb0ef --- /dev/null +++ b/gfx/gl/GLTextureImage.h @@ -0,0 +1,385 @@ +/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GLTEXTUREIMAGE_H_ +#define GLTEXTUREIMAGE_H_ + +#include "nsAutoPtr.h" +#include "nsRegion.h" +#include "gfxASurface.h" +#include "GLContextTypes.h" +#include "gfxPattern.h" + +namespace mozilla { +namespace gl { +class GLContext; + +/** + * A TextureImage encapsulates a surface that can be drawn to by a + * Thebes gfxContext and (hopefully efficiently!) synchronized to a + * texture in the server. TextureImages are associated with one and + * only one GLContext. + * + * Implementation note: TextureImages attempt to unify two categories + * of backends + * + * (1) proxy to server-side object that can be bound to a texture; + * e.g. Pixmap on X11. + * + * (2) efficient manager of texture memory; e.g. by having clients draw + * into a scratch buffer which is then uploaded with + * glTexSubImage2D(). + */ +class TextureImage +{ + NS_INLINE_DECL_REFCOUNTING(TextureImage) +public: + enum TextureState + { + Created, // Texture created, but has not had glTexImage called to initialize it. + Allocated, // Texture memory exists, but contents are invalid. + Valid // Texture fully ready to use. + }; + + enum Flags { + NoFlags = 0x0, + UseNearestFilter = 0x1, + NeedsYFlip = 0x2, + ForceSingleTile = 0x4 + }; + + typedef gfxASurface::gfxContentType ContentType; + + static already_AddRefed Create( + GLContext* gl, + const nsIntSize& aSize, + TextureImage::ContentType aContentType, + GLenum aWrapMode, + TextureImage::Flags aFlags = TextureImage::NoFlags); + + virtual ~TextureImage() {} + + /** + * Returns a gfxASurface for updating |aRegion| of the client's + * image if successul, NULL if not. |aRegion|'s bounds must fit + * within Size(); its coordinate space (if any) is ignored. If + * the update begins successfully, the returned gfxASurface is + * owned by this. Otherwise, NULL is returned. + * + * |aRegion| is an inout param: the returned region is what the + * client must repaint. Category (1) regions above can + * efficiently handle repaints to "scattered" regions, while (2) + * can only efficiently handle repaints to rects. + * + * Painting the returned surface outside of |aRegion| results + * in undefined behavior. + * + * BeginUpdate() calls cannot be "nested", and each successful + * BeginUpdate() must be followed by exactly one EndUpdate() (see + * below). Failure to do so can leave this in a possibly + * inconsistent state. Unsuccessful BeginUpdate()s must not be + * followed by EndUpdate(). + */ + virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion) = 0; + /** + * Retrieves the region that will require updating, given a + * region that needs to be updated. This can be used for + * making decisions about updating before calling BeginUpdate(). + * + * |aRegion| is an inout param. + */ + virtual void GetUpdateRegion(nsIntRegion& aForRegion) { + } + /** + * Finish the active update and synchronize with the server, if + * necessary. + * + * BeginUpdate() must have been called exactly once before + * EndUpdate(). + */ + virtual void EndUpdate() = 0; + + /** + * The Image may contain several textures for different regions (tiles). + * These functions iterate over each sub texture image tile. + */ + virtual void BeginTileIteration() { + } + + virtual bool NextTile() { + return false; + } + + // Function prototype for a tile iteration callback. Returning false will + // cause iteration to be interrupted (i.e. the corresponding NextTile call + // will return false). + typedef bool (* TileIterationCallback)(TextureImage* aImage, + int aTileNumber, + void* aCallbackData); + + // Sets a callback to be called every time NextTile is called. + virtual void SetIterationCallback(TileIterationCallback aCallback, + void* aCallbackData) { + } + + virtual nsIntRect GetTileRect() { + return nsIntRect(nsIntPoint(0,0), mSize); + } + + virtual GLuint GetTextureID() = 0; + + virtual uint32_t GetTileCount() { + return 1; + } + + /** + * Set this TextureImage's size, and ensure a texture has been + * allocated. Must not be called between BeginUpdate and EndUpdate. + * After a resize, the contents are undefined. + * + * If this isn't implemented by a subclass, it will just perform + * a dummy BeginUpdate/EndUpdate pair. + */ + virtual void Resize(const nsIntSize& aSize) { + mSize = aSize; + nsIntRegion r(nsIntRect(0, 0, aSize.width, aSize.height)); + BeginUpdate(r); + EndUpdate(); + } + + /** + * Mark this texture as having valid contents. Call this after modifying + * the texture contents externally. + */ + virtual void MarkValid() {} + + /** + * aSurf - the source surface to update from + * aRegion - the region in this image to update + * aFrom - offset in the source to update from + */ + virtual bool DirectUpdate(gfxASurface *aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)) = 0; + + virtual void BindTexture(GLenum aTextureUnit) = 0; + virtual void ReleaseTexture() {} + + void BindTextureAndApplyFilter(GLenum aTextureUnit) { + BindTexture(aTextureUnit); + ApplyFilter(); + } + + class ScopedBindTexture + { + public: + ScopedBindTexture(TextureImage *aTexture, GLenum aTextureUnit); + + ~ScopedBindTexture() + { + if (mTexture) { + mTexture->ReleaseTexture(); + } + } + + protected: + TextureImage *mTexture; + }; + + class ScopedBindTextureAndApplyFilter + : public ScopedBindTexture + { + public: + ScopedBindTextureAndApplyFilter(TextureImage *aTexture, GLenum aTextureUnit) : + ScopedBindTexture(aTexture, aTextureUnit) + { + if (mTexture) { + mTexture->ApplyFilter(); + } + } + }; + + /** + * Returns the shader program type that should be used to render + * this texture. Only valid after a matching BeginUpdate/EndUpdate + * pair have been called. + */ + virtual ShaderProgramType GetShaderProgramType() + { + return mShaderType; + } + + /** Can be called safely at any time. */ + + /** + * If this TextureImage has a permanent gfxASurface backing, + * return it. Otherwise return NULL. + */ + virtual already_AddRefed GetBackingSurface() + { return NULL; } + + const nsIntSize& GetSize() const { return mSize; } + ContentType GetContentType() const { return mContentType; } + virtual bool InUpdate() const = 0; + GLenum GetWrapMode() const { return mWrapMode; } + + void SetFilter(gfxPattern::GraphicsFilter aFilter) { mFilter = aFilter; } + + /** + * Applies this TextureImage's filter, assuming that its texture is + * the currently bound texture. + */ + virtual void ApplyFilter() = 0; + +protected: + friend class GLContext; + + /** + * After the ctor, the TextureImage is invalid. Implementations + * must allocate resources successfully before returning the new + * TextureImage from GLContext::CreateTextureImage(). That is, + * clients must not be given partially-constructed TextureImages. + */ + TextureImage(const nsIntSize& aSize, + GLenum aWrapMode, ContentType aContentType, + Flags aFlags = NoFlags) + : mSize(aSize) + , mWrapMode(aWrapMode) + , mContentType(aContentType) + , mFilter(gfxPattern::FILTER_GOOD) + , mFlags(aFlags) + {} + + virtual nsIntRect GetSrcTileRect() { + return nsIntRect(nsIntPoint(0,0), mSize); + } + + nsIntSize mSize; + GLenum mWrapMode; + ContentType mContentType; + ShaderProgramType mShaderType; + gfxPattern::GraphicsFilter mFilter; + Flags mFlags; +}; + +/** + * BasicTextureImage is the baseline TextureImage implementation --- + * it updates its texture by allocating a scratch buffer for the + * client to draw into, then using glTexSubImage2D() to upload the new + * pixels. Platforms must provide the code to create a new surface + * into which the updated pixels will be drawn, and the code to + * convert the update surface's pixels into an image on which we can + * glTexSubImage2D(). + */ +class BasicTextureImage + : public TextureImage +{ +public: + typedef gfxASurface::gfxImageFormat ImageFormat; + virtual ~BasicTextureImage(); + + BasicTextureImage(GLuint aTexture, + const nsIntSize& aSize, + GLenum aWrapMode, + ContentType aContentType, + GLContext* aContext, + TextureImage::Flags aFlags = TextureImage::NoFlags) + : TextureImage(aSize, aWrapMode, aContentType, aFlags) + , mTexture(aTexture) + , mTextureState(Created) + , mGLContext(aContext) + , mUpdateOffset(0, 0) + {} + + virtual void BindTexture(GLenum aTextureUnit); + + virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); + virtual void GetUpdateRegion(nsIntRegion& aForRegion); + virtual void EndUpdate(); + virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); + virtual GLuint GetTextureID() { return mTexture; } + // Returns a surface to draw into + virtual already_AddRefed + GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt); + + virtual void MarkValid() { mTextureState = Valid; } + + // Call when drawing into the update surface is complete. + // Returns true if textures should be upload with a relative + // offset - See UploadSurfaceToTexture. + virtual bool FinishedSurfaceUpdate(); + + // Call after surface data has been uploaded to a texture. + virtual void FinishedSurfaceUpload(); + + virtual bool InUpdate() const { return !!mUpdateSurface; } + + virtual void Resize(const nsIntSize& aSize); + + virtual void ApplyFilter(); +protected: + + GLuint mTexture; + TextureState mTextureState; + GLContext* mGLContext; + nsRefPtr mUpdateSurface; + nsIntRegion mUpdateRegion; + + // The offset into the update surface at which the update rect is located. + nsIntPoint mUpdateOffset; +}; + +/** + * A container class that complements many sub TextureImages into a big TextureImage. + * Aims to behave just like the real thing. + */ + +class TiledTextureImage + : public TextureImage +{ +public: + TiledTextureImage(GLContext* aGL, nsIntSize aSize, + TextureImage::ContentType, TextureImage::Flags aFlags = TextureImage::NoFlags); + ~TiledTextureImage(); + void DumpDiv(); + virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); + virtual void GetUpdateRegion(nsIntRegion& aForRegion); + virtual void EndUpdate(); + virtual void Resize(const nsIntSize& aSize); + virtual uint32_t GetTileCount(); + virtual void BeginTileIteration(); + virtual bool NextTile(); + virtual void SetIterationCallback(TileIterationCallback aCallback, + void* aCallbackData); + virtual nsIntRect GetTileRect(); + virtual GLuint GetTextureID() { + return mImages[mCurrentImage]->GetTextureID(); + } + virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); + virtual bool InUpdate() const { return mInUpdate; } + virtual void BindTexture(GLenum); + virtual void ApplyFilter(); + +protected: + virtual nsIntRect GetSrcTileRect(); + + unsigned int mCurrentImage; + TileIterationCallback mIterationCallback; + void* mIterationCallbackData; + nsTArray< nsRefPtr > mImages; + bool mInUpdate; + nsIntSize mSize; + unsigned int mTileSize; + unsigned int mRows, mColumns; + GLContext* mGL; + // A temporary surface to faciliate cross-tile updates. + nsRefPtr mUpdateSurface; + // The region of update requested + nsIntRegion mUpdateRegion; + TextureState mTextureState; +}; + +} // namespace gl +} // namespace mozilla + +#endif /* GLTEXTUREIMAGE_H_ */ diff --git a/gfx/gl/Makefile.in b/gfx/gl/Makefile.in index e3729a278007..8f1cde8fe9de 100644 --- a/gfx/gl/Makefile.in +++ b/gfx/gl/Makefile.in @@ -24,6 +24,7 @@ EXPORTS = \ GLContextProviderImpl.h \ GLLibraryLoader.h \ ForceDiscreteGPUHelperCGL.h \ + GLTextureImage.h \ $(NULL) ifdef MOZ_X11 @@ -48,6 +49,7 @@ CPPSRCS = \ GLContext.cpp \ GLContextUtils.cpp \ GLLibraryLoader.cpp \ + GLTextureImage.cpp \ $(NULL) GL_PROVIDER = Null diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index f8c005534d8d..403183554a59 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -51,6 +51,7 @@ #include "nsRegion.h" #include "Layers.h" #include "LayerManagerOGL.h" +#include "GLTextureImage.h" #include "mozilla/layers/CompositorCocoaWidgetHelper.h" #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" @@ -1758,10 +1759,11 @@ nsChildView::DrawWindowOverlay(LayerManager* aManager, nsIntRect aRect) } if (!mResizerImage) { - mResizerImage = manager->gl()->CreateTextureImage(nsIntSize(15, 15), - gfxASurface::CONTENT_COLOR_ALPHA, - LOCAL_GL_CLAMP_TO_EDGE, - TextureImage::UseNearestFilter); + mResizerImage = TextureImage::Create(manager->gl(), + nsIntSize(15, 15), + gfxASurface::CONTENT_COLOR_ALPHA, + LOCAL_GL_CLAMP_TO_EDGE, + TextureImage::UseNearestFilter); // Creation of texture images can fail. if (!mResizerImage)