/* -*- Mode: c++; c-basic-offset: 4; 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" #include "TextureImageEGL.h" using namespace mozilla::gfx; namespace mozilla { namespace gl { already_AddRefed CreateTextureImage( GLContext* gl, const gfx::IntSize& aSize, TextureImage::ContentType aContentType, GLenum aWrapMode, TextureImage::Flags aFlags, TextureImage::ImageFormat aImageFormat) { switch (gl->GetContextType()) { case GLContextType::EGL: return CreateTextureImageEGL(gl, aSize, aContentType, aWrapMode, aFlags, aImageFormat); default: { 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 TileGenFunc( GLContext* gl, const IntSize& aSize, TextureImage::ContentType aContentType, TextureImage::Flags aFlags, TextureImage::ImageFormat aImageFormat) { switch (gl->GetContextType()) { case GLContextType::EGL: return TileGenFuncEGL(gl, aSize, aContentType, aFlags, aImageFormat); default: return CreateBasicTextureImage(gl, aSize, aContentType, LOCAL_GL_CLAMP_TO_EDGE, aFlags); } } already_AddRefed 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* aSrcPoint) { nsIntRegion destRegion = aDestRegion ? *aDestRegion : IntRect(0, 0, aSurface->GetSize().width, aSurface->GetSize().height); gfx::IntPoint srcPoint = aSrcPoint ? *aSrcPoint : gfx::IntPoint(0, 0); return DirectUpdate(aSurface, destRegion, srcPoint); } 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->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->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& aFrom /* = 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, aFrom); 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 (!CanUploadSubTextures(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() {} bool TiledTextureImage::DirectUpdate( gfx::DataSourceSurface* aSurf, const nsIntRegion& aRegion, const gfx::IntPoint& aFrom /* = gfx::IntPoint(0, 0) */) { 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, aFrom + 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 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. unsigned int length = mImages.Length(); for (; i < length; i++) mImages.RemoveLastElement(); // Reset tile-store properties. mRows = rows; mColumns = columns; mSize = aSize; mTextureState = Allocated; mCurrentImage = 0; } uint32_t TiledTextureImage::GetTileCount() { return mImages.Length(); } already_AddRefed CreateTiledTextureImage( GLContext* aGL, const gfx::IntSize& aSize, TextureImage::ContentType aContentType, TextureImage::Flags aFlags, TextureImage::ImageFormat aImageFormat) { RefPtr texImage = static_cast(new gl::TiledTextureImage( aGL, aSize, aContentType, aFlags, aImageFormat)); return texImage.forget(); } already_AddRefed 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 texImage = new BasicTextureImage( texture, aSize, aWrapMode, aContentType, aGL, aFlags); return texImage.forget(); } } // namespace gl } // namespace mozilla