summaryrefslogtreecommitdiffstats
path: root/gfx/gl/GLTextureImage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/gl/GLTextureImage.cpp')
-rw-r--r--gfx/gl/GLTextureImage.cpp521
1 files changed, 521 insertions, 0 deletions
diff --git a/gfx/gl/GLTextureImage.cpp b/gfx/gl/GLTextureImage.cpp
new file mode 100644
index 000000000..c91d558af
--- /dev/null
+++ b/gfx/gl/GLTextureImage.cpp
@@ -0,0 +1,521 @@
+/* -*- 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"
+#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<TextureImage>
+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<TextureImage>
+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>
+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 (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)
+ , 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<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.
+ 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();
+}
+
+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