diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/gl/GLTextureImage.cpp | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/gfx/gl/GLTextureImage.cpp b/gfx/gl/GLTextureImage.cpp new file mode 100644 index 0000000000..e2295753fc --- /dev/null +++ b/gfx/gl/GLTextureImage.cpp @@ -0,0 +1,453 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 4; -*- */ +/* 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" +#include "gfx2DGlue.h" +#include "mozilla/gfx/2D.h" +#include "ScopedGLHelpers.h" +#include "GLUploadHelpers.h" +#include "GfxTexturesReporter.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace gl { + +already_AddRefed<TextureImage> CreateTextureImage( + GLContext* gl, const gfx::IntSize& aSize, + TextureImage::ContentType aContentType, GLenum aWrapMode, + TextureImage::Flags aFlags, TextureImage::ImageFormat aImageFormat) { + GLint maxTextureSize; + gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &maxTextureSize); + if (aSize.width > maxTextureSize || aSize.height > maxTextureSize) { + NS_ASSERTION(aWrapMode == LOCAL_GL_CLAMP_TO_EDGE, + "Can't support wrapping with tiles!"); + return CreateTiledTextureImage(gl, aSize, aContentType, aFlags, + aImageFormat); + } else { + return CreateBasicTextureImage(gl, aSize, aContentType, aWrapMode, aFlags); + } +} + +static already_AddRefed<TextureImage> TileGenFunc( + GLContext* gl, const IntSize& aSize, TextureImage::ContentType aContentType, + TextureImage::Flags aFlags, TextureImage::ImageFormat aImageFormat) { + return CreateBasicTextureImage(gl, aSize, aContentType, + LOCAL_GL_CLAMP_TO_EDGE, aFlags); +} + +already_AddRefed<TextureImage> TextureImage::Create( + GLContext* gl, const gfx::IntSize& size, + TextureImage::ContentType contentType, GLenum wrapMode, + TextureImage::Flags flags) { + return CreateTextureImage(gl, size, contentType, wrapMode, flags); +} + +bool TextureImage::UpdateFromDataSource(gfx::DataSourceSurface* aSurface, + const nsIntRegion* aDestRegion, + const gfx::IntPoint* aSrcOffset, + const gfx::IntPoint* aDstOffset) { + nsIntRegion destRegion = aDestRegion + ? *aDestRegion + : IntRect(0, 0, aSurface->GetSize().width, + aSurface->GetSize().height); + gfx::IntPoint srcPoint = aSrcOffset ? *aSrcOffset : gfx::IntPoint(0, 0); + gfx::IntPoint srcPointOut = aDstOffset ? *aDstOffset : gfx::IntPoint(0, 0); + return DirectUpdate(aSurface, destRegion, srcPoint, srcPointOut); +} + +gfx::IntRect TextureImage::GetTileRect() { + return gfx::IntRect(gfx::IntPoint(0, 0), mSize); +} + +gfx::IntRect TextureImage::GetSrcTileRect() { return GetTileRect(); } + +void TextureImage::UpdateUploadSize(size_t amount) { + if (mUploadSize > 0) { + GfxTexturesReporter::UpdateAmount(GfxTexturesReporter::MemoryFreed, + mUploadSize); + } + mUploadSize = amount; + GfxTexturesReporter::UpdateAmount(GfxTexturesReporter::MemoryAllocated, + mUploadSize); +} + +BasicTextureImage::~BasicTextureImage() { + GLContext* ctx = mGLContext; + if (ctx->IsDestroyed() || !ctx->IsValidOwningThread()) { + 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->MakeCurrent()) { + ctx->fDeleteTextures(1, &mTexture); + } +} + +void BasicTextureImage::BindTexture(GLenum aTextureUnit) { + mGLContext->fActiveTexture(aTextureUnit); + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); + mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); +} + +bool BasicTextureImage::DirectUpdate( + gfx::DataSourceSurface* aSurf, const nsIntRegion& aRegion, + const gfx::IntPoint& aSrcOffset /* = gfx::IntPoint(0, 0) */, + const gfx::IntPoint& aDstOffset /* = gfx::IntPoint(0, 0) */) { + nsIntRegion region; + if (mTextureState == Valid) { + region = aRegion; + } else { + region = nsIntRegion(IntRect(0, 0, mSize.width, mSize.height)); + } + bool needInit = mTextureState == Created; + size_t uploadSize; + + mTextureFormat = + UploadSurfaceToTexture(mGLContext, aSurf, region, mTexture, mSize, + &uploadSize, needInit, aSrcOffset, aDstOffset); + if (mTextureFormat == SurfaceFormat::UNKNOWN) { + return false; + } + + if (uploadSize > 0) { + UpdateUploadSize(uploadSize); + } + mTextureState = Valid; + return true; +} + +void BasicTextureImage::Resize(const gfx::IntSize& aSize) { + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); + + // This matches the logic in UploadImageDataToTexture so that + // we avoid mixing formats. + GLenum format; + GLenum type; + if (mGLContext->GetPreferredARGB32Format() == LOCAL_GL_BGRA) { + MOZ_ASSERT(!mGLContext->IsGLES()); + format = LOCAL_GL_BGRA; + type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV; + } else { + format = LOCAL_GL_RGBA; + type = LOCAL_GL_UNSIGNED_BYTE; + } + + mGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, aSize.width, + aSize.height, 0, format, type, nullptr); + + mTextureState = Allocated; + mSize = aSize; +} + +gfx::IntSize TextureImage::GetSize() const { return mSize; } + +TextureImage::TextureImage(const gfx::IntSize& aSize, GLenum aWrapMode, + ContentType aContentType, Flags aFlags) + : mSize(aSize), + mWrapMode(aWrapMode), + mContentType(aContentType), + mTextureFormat(gfx::SurfaceFormat::UNKNOWN), + mSamplingFilter(SamplingFilter::GOOD), + mFlags(aFlags), + mUploadSize(0) {} + +BasicTextureImage::BasicTextureImage(GLuint aTexture, const gfx::IntSize& aSize, + GLenum aWrapMode, ContentType aContentType, + GLContext* aContext, + TextureImage::Flags aFlags) + : TextureImage(aSize, aWrapMode, aContentType, aFlags), + mTexture(aTexture), + mTextureState(Created), + mGLContext(aContext) {} + +static bool WantsSmallTiles(GLContext* gl) { + // We can't use small tiles on the SGX 540, because of races in texture + // upload. + if (gl->WorkAroundDriverBugs() && gl->Renderer() == GLRenderer::SGX540) + return false; + + // We should use small tiles for good performance if we can't use + // glTexSubImage2D() for some reason. + if (!ShouldUploadSubTextures(gl)) return true; + + // Don't use small tiles otherwise. (If we implement incremental texture + // upload, then we will want to revisit this.) + return false; +} + +TiledTextureImage::TiledTextureImage(GLContext* aGL, gfx::IntSize aSize, + TextureImage::ContentType aContentType, + TextureImage::Flags aFlags, + TextureImage::ImageFormat aImageFormat) + : TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aFlags), + mCurrentImage(0), + mIterationCallback(nullptr), + mIterationCallbackData(nullptr), + mTileSize(0), + mRows(0), + mColumns(0), + mGL(aGL), + mTextureState(Created), + mImageFormat(aImageFormat) { + if (!(aFlags & TextureImage::DisallowBigImage) && WantsSmallTiles(mGL)) { + mTileSize = 256; + } else { + mGL->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, (GLint*)&mTileSize); + } + if (aSize.width != 0 && aSize.height != 0) { + Resize(aSize); + } +} + +TiledTextureImage::~TiledTextureImage() = default; + +bool TiledTextureImage::DirectUpdate( + gfx::DataSourceSurface* aSurf, const nsIntRegion& aRegion, + const gfx::IntPoint& aSrcOffset /* = gfx::IntPoint(0, 0) */, + const gfx::IntPoint& aDstOffset /* = gfx::IntPoint(0, 0) */) { + // We don't handle non-zero aDstOffset + MOZ_RELEASE_ASSERT(aDstOffset == gfx::IntPoint()); + + if (mSize.width == 0 || mSize.height == 0) { + return true; + } + + nsIntRegion region; + + if (mTextureState != Valid) { + IntRect bounds = IntRect(0, 0, mSize.width, mSize.height); + region = nsIntRegion(bounds); + } else { + region = aRegion; + } + + bool result = true; + int oldCurrentImage = mCurrentImage; + BeginBigImageIteration(); + do { + IntRect tileRect = GetSrcTileRect(); + int xPos = tileRect.X(); + int yPos = tileRect.Y(); + + nsIntRegion tileRegion; + tileRegion.And(region, tileRect); // intersect with tile + + if (tileRegion.IsEmpty()) continue; + + tileRegion.MoveBy(-xPos, -yPos); // translate into tile local space + + result &= mImages[mCurrentImage]->DirectUpdate( + aSurf, tileRegion, aSrcOffset + gfx::IntPoint(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; + + mTextureFormat = mImages[0]->GetTextureFormat(); + mTextureState = Valid; + return result; +} + +void TiledTextureImage::BeginBigImageIteration() { 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( + BigImageIterationCallback aCallback, void* aCallbackData) { + mIterationCallback = aCallback; + mIterationCallbackData = aCallbackData; +} + +gfx::IntRect TiledTextureImage::GetTileRect() { + if (!GetTileCount()) { + return gfx::IntRect(); + } + gfx::IntRect rect = mImages[mCurrentImage]->GetTileRect(); + unsigned int xPos = (mCurrentImage % mColumns) * mTileSize; + unsigned int yPos = (mCurrentImage / mColumns) * mTileSize; + rect.MoveBy(xPos, yPos); + return rect; +} + +gfx::IntRect TiledTextureImage::GetSrcTileRect() { + gfx::IntRect rect = GetTileRect(); + const bool needsYFlip = mFlags & OriginBottomLeft; + unsigned int srcY = + needsYFlip ? mSize.height - rect.Height() - rect.Y() : rect.Y(); + return gfx::IntRect(rect.X(), srcY, rect.Width(), rect.Height()); +} + +void TiledTextureImage::BindTexture(GLenum aTextureUnit) { + if (!GetTileCount()) { + return; + } + mImages[mCurrentImage]->BindTexture(aTextureUnit); +} + +/* + * 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 gfx::IntSize& 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++) { + IntSize 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. + RefPtr<TextureImage> teximg = + TileGenFunc(mGL, size, mContentType, mFlags, mImageFormat); + if (replace) + mImages.ReplaceElementAt(i, teximg); + else + mImages.InsertElementAt(i, teximg); + 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. + mImages.RemoveLastElements(mImages.Length() - i); + + // Reset tile-store properties. + mRows = rows; + mColumns = columns; + mSize = aSize; + mTextureState = Allocated; + mCurrentImage = 0; +} + +uint32_t TiledTextureImage::GetTileCount() { return mImages.Length(); } + +already_AddRefed<TextureImage> CreateTiledTextureImage( + GLContext* aGL, const gfx::IntSize& aSize, + TextureImage::ContentType aContentType, TextureImage::Flags aFlags, + TextureImage::ImageFormat aImageFormat) { + RefPtr<TextureImage> texImage = + static_cast<TextureImage*>(new gl::TiledTextureImage( + aGL, aSize, aContentType, aFlags, aImageFormat)); + return texImage.forget(); +} + +already_AddRefed<TextureImage> CreateBasicTextureImage( + GLContext* aGL, const gfx::IntSize& aSize, + TextureImage::ContentType aContentType, GLenum aWrapMode, + TextureImage::Flags aFlags) { + bool useNearestFilter = aFlags & TextureImage::UseNearestFilter; + if (!aGL->MakeCurrent()) { + return nullptr; + } + + GLuint texture = 0; + aGL->fGenTextures(1, &texture); + + ScopedBindTexture bind(aGL, texture); + + GLint texfilter = useNearestFilter ? LOCAL_GL_NEAREST : LOCAL_GL_LINEAR; + aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, + texfilter); + aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, + texfilter); + aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, aWrapMode); + aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, aWrapMode); + + RefPtr<BasicTextureImage> texImage = new BasicTextureImage( + texture, aSize, aWrapMode, aContentType, aGL, aFlags); + return texImage.forget(); +} + +} // namespace gl +} // namespace mozilla |