GLTextureImage.cpp (15586B)
1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 4; -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "GLTextureImage.h" 7 #include "GLContext.h" 8 #include "gfxContext.h" 9 #include "gfxPlatform.h" 10 #include "gfxUtils.h" 11 #include "gfx2DGlue.h" 12 #include "mozilla/gfx/2D.h" 13 #include "ScopedGLHelpers.h" 14 #include "GLUploadHelpers.h" 15 #include "GfxTexturesReporter.h" 16 17 using namespace mozilla::gfx; 18 19 namespace mozilla { 20 namespace gl { 21 22 already_AddRefed<TextureImage> CreateTextureImage( 23 GLContext* gl, const gfx::IntSize& aSize, 24 TextureImage::ContentType aContentType, GLenum aWrapMode, 25 TextureImage::Flags aFlags, TextureImage::ImageFormat aImageFormat) { 26 GLint maxTextureSize; 27 gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &maxTextureSize); 28 if (aSize.width > maxTextureSize || aSize.height > maxTextureSize) { 29 NS_ASSERTION(aWrapMode == LOCAL_GL_CLAMP_TO_EDGE, 30 "Can't support wrapping with tiles!"); 31 return CreateTiledTextureImage(gl, aSize, aContentType, aFlags, 32 aImageFormat); 33 } else { 34 return CreateBasicTextureImage(gl, aSize, aContentType, aWrapMode, aFlags); 35 } 36 } 37 38 static already_AddRefed<TextureImage> TileGenFunc( 39 GLContext* gl, const IntSize& aSize, TextureImage::ContentType aContentType, 40 TextureImage::Flags aFlags, TextureImage::ImageFormat aImageFormat) { 41 return CreateBasicTextureImage(gl, aSize, aContentType, 42 LOCAL_GL_CLAMP_TO_EDGE, aFlags); 43 } 44 45 already_AddRefed<TextureImage> TextureImage::Create( 46 GLContext* gl, const gfx::IntSize& size, 47 TextureImage::ContentType contentType, GLenum wrapMode, 48 TextureImage::Flags flags) { 49 return CreateTextureImage(gl, size, contentType, wrapMode, flags); 50 } 51 52 bool TextureImage::UpdateFromDataSource(gfx::DataSourceSurface* aSurface, 53 const nsIntRegion* aDestRegion, 54 const gfx::IntPoint* aSrcOffset, 55 const gfx::IntPoint* aDstOffset) { 56 nsIntRegion destRegion = aDestRegion 57 ? *aDestRegion 58 : IntRect(0, 0, aSurface->GetSize().width, 59 aSurface->GetSize().height); 60 gfx::IntPoint srcPoint = aSrcOffset ? *aSrcOffset : gfx::IntPoint(0, 0); 61 gfx::IntPoint srcPointOut = aDstOffset ? *aDstOffset : gfx::IntPoint(0, 0); 62 return DirectUpdate(aSurface, destRegion, srcPoint, srcPointOut); 63 } 64 65 gfx::IntRect TextureImage::GetTileRect() { 66 return gfx::IntRect(gfx::IntPoint(0, 0), mSize); 67 } 68 69 gfx::IntRect TextureImage::GetSrcTileRect() { return GetTileRect(); } 70 71 void TextureImage::UpdateUploadSize(size_t amount) { 72 if (mUploadSize > 0) { 73 GfxTexturesReporter::UpdateAmount(GfxTexturesReporter::MemoryFreed, 74 mUploadSize); 75 } 76 mUploadSize = amount; 77 GfxTexturesReporter::UpdateAmount(GfxTexturesReporter::MemoryAllocated, 78 mUploadSize); 79 } 80 81 BasicTextureImage::~BasicTextureImage() { 82 GLContext* ctx = mGLContext; 83 if (ctx->IsDestroyed() || !ctx->IsValidOwningThread()) { 84 ctx = ctx->GetSharedContext(); 85 } 86 87 // If we have a context, then we need to delete the texture; 88 // if we don't have a context (either real or shared), 89 // then they went away when the contex was deleted, because it 90 // was the only one that had access to it. 91 if (ctx && ctx->MakeCurrent()) { 92 ctx->fDeleteTextures(1, &mTexture); 93 } 94 } 95 96 void BasicTextureImage::BindTexture(GLenum aTextureUnit) { 97 mGLContext->fActiveTexture(aTextureUnit); 98 mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); 99 mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); 100 } 101 102 bool BasicTextureImage::DirectUpdate( 103 gfx::DataSourceSurface* aSurf, const nsIntRegion& aRegion, 104 const gfx::IntPoint& aSrcOffset /* = gfx::IntPoint(0, 0) */, 105 const gfx::IntPoint& aDstOffset /* = gfx::IntPoint(0, 0) */) { 106 nsIntRegion region; 107 if (mTextureState == Valid) { 108 region = aRegion; 109 } else { 110 region = nsIntRegion(IntRect(0, 0, mSize.width, mSize.height)); 111 } 112 bool needInit = mTextureState == Created; 113 size_t uploadSize; 114 115 mTextureFormat = 116 UploadSurfaceToTexture(mGLContext, aSurf, region, mTexture, mSize, 117 &uploadSize, needInit, aSrcOffset, aDstOffset); 118 if (mTextureFormat == SurfaceFormat::UNKNOWN) { 119 return false; 120 } 121 122 if (uploadSize > 0) { 123 UpdateUploadSize(uploadSize); 124 } 125 mTextureState = Valid; 126 return true; 127 } 128 129 void BasicTextureImage::Resize(const gfx::IntSize& aSize) { 130 mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); 131 132 // This matches the logic in UploadImageDataToTexture so that 133 // we avoid mixing formats. 134 GLenum format; 135 GLenum type; 136 if (mGLContext->GetPreferredARGB32Format() == LOCAL_GL_BGRA) { 137 MOZ_ASSERT(!mGLContext->IsGLES()); 138 format = LOCAL_GL_BGRA; 139 type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV; 140 } else { 141 format = LOCAL_GL_RGBA; 142 type = LOCAL_GL_UNSIGNED_BYTE; 143 } 144 145 mGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, aSize.width, 146 aSize.height, 0, format, type, nullptr); 147 148 mTextureState = Allocated; 149 mSize = aSize; 150 } 151 152 gfx::IntSize TextureImage::GetSize() const { return mSize; } 153 154 TextureImage::TextureImage(const gfx::IntSize& aSize, GLenum aWrapMode, 155 ContentType aContentType, Flags aFlags) 156 : mSize(aSize), 157 mWrapMode(aWrapMode), 158 mContentType(aContentType), 159 mTextureFormat(gfx::SurfaceFormat::UNKNOWN), 160 mSamplingFilter(SamplingFilter::GOOD), 161 mFlags(aFlags), 162 mUploadSize(0) {} 163 164 BasicTextureImage::BasicTextureImage(GLuint aTexture, const gfx::IntSize& aSize, 165 GLenum aWrapMode, ContentType aContentType, 166 GLContext* aContext, 167 TextureImage::Flags aFlags) 168 : TextureImage(aSize, aWrapMode, aContentType, aFlags), 169 mTexture(aTexture), 170 mTextureState(Created), 171 mGLContext(aContext) {} 172 173 static bool WantsSmallTiles(GLContext* gl) { 174 // We can't use small tiles on the SGX 540, because of races in texture 175 // upload. 176 if (gl->WorkAroundDriverBugs() && gl->Renderer() == GLRenderer::SGX540) 177 return false; 178 179 // We should use small tiles for good performance if we can't use 180 // glTexSubImage2D() for some reason. 181 if (!ShouldUploadSubTextures(gl)) return true; 182 183 // Don't use small tiles otherwise. (If we implement incremental texture 184 // upload, then we will want to revisit this.) 185 return false; 186 } 187 188 TiledTextureImage::TiledTextureImage(GLContext* aGL, gfx::IntSize aSize, 189 TextureImage::ContentType aContentType, 190 TextureImage::Flags aFlags, 191 TextureImage::ImageFormat aImageFormat) 192 : TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aFlags), 193 mCurrentImage(0), 194 mIterationCallback(nullptr), 195 mIterationCallbackData(nullptr), 196 mTileSize(0), 197 mRows(0), 198 mColumns(0), 199 mGL(aGL), 200 mTextureState(Created), 201 mImageFormat(aImageFormat) { 202 if (!(aFlags & TextureImage::DisallowBigImage) && WantsSmallTiles(mGL)) { 203 mTileSize = 256; 204 } else { 205 mGL->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, (GLint*)&mTileSize); 206 } 207 if (aSize.width != 0 && aSize.height != 0) { 208 Resize(aSize); 209 } 210 } 211 212 TiledTextureImage::~TiledTextureImage() = default; 213 214 bool TiledTextureImage::DirectUpdate( 215 gfx::DataSourceSurface* aSurf, const nsIntRegion& aRegion, 216 const gfx::IntPoint& aSrcOffset /* = gfx::IntPoint(0, 0) */, 217 const gfx::IntPoint& aDstOffset /* = gfx::IntPoint(0, 0) */) { 218 // We don't handle non-zero aDstOffset 219 MOZ_RELEASE_ASSERT(aDstOffset == gfx::IntPoint()); 220 221 if (mSize.width == 0 || mSize.height == 0) { 222 return true; 223 } 224 225 nsIntRegion region; 226 227 if (mTextureState != Valid) { 228 IntRect bounds = IntRect(0, 0, mSize.width, mSize.height); 229 region = nsIntRegion(bounds); 230 } else { 231 region = aRegion; 232 } 233 234 bool result = true; 235 int oldCurrentImage = mCurrentImage; 236 BeginBigImageIteration(); 237 do { 238 IntRect tileRect = GetSrcTileRect(); 239 int xPos = tileRect.X(); 240 int yPos = tileRect.Y(); 241 242 nsIntRegion tileRegion; 243 tileRegion.And(region, tileRect); // intersect with tile 244 245 if (tileRegion.IsEmpty()) continue; 246 247 tileRegion.MoveBy(-xPos, -yPos); // translate into tile local space 248 249 result &= mImages[mCurrentImage]->DirectUpdate( 250 aSurf, tileRegion, aSrcOffset + gfx::IntPoint(xPos, yPos)); 251 252 if (mCurrentImage == mImages.Length() - 1) { 253 // We know we're done, but we still need to ensure that the callback 254 // gets called (e.g. to update the uploaded region). 255 NextTile(); 256 break; 257 } 258 // Override a callback cancelling iteration if the texture wasn't valid. 259 // We need to force the update in that situation, or we may end up 260 // showing invalid/out-of-date texture data. 261 } while (NextTile() || (mTextureState != Valid)); 262 mCurrentImage = oldCurrentImage; 263 264 mTextureFormat = mImages[0]->GetTextureFormat(); 265 mTextureState = Valid; 266 return result; 267 } 268 269 void TiledTextureImage::BeginBigImageIteration() { mCurrentImage = 0; } 270 271 bool TiledTextureImage::NextTile() { 272 bool continueIteration = true; 273 274 if (mIterationCallback) 275 continueIteration = 276 mIterationCallback(this, mCurrentImage, mIterationCallbackData); 277 278 if (mCurrentImage + 1 < mImages.Length()) { 279 mCurrentImage++; 280 return continueIteration; 281 } 282 return false; 283 } 284 285 void TiledTextureImage::SetIterationCallback( 286 BigImageIterationCallback aCallback, void* aCallbackData) { 287 mIterationCallback = aCallback; 288 mIterationCallbackData = aCallbackData; 289 } 290 291 gfx::IntRect TiledTextureImage::GetTileRect() { 292 if (!GetTileCount()) { 293 return gfx::IntRect(); 294 } 295 gfx::IntRect rect = mImages[mCurrentImage]->GetTileRect(); 296 unsigned int xPos = (mCurrentImage % mColumns) * mTileSize; 297 unsigned int yPos = (mCurrentImage / mColumns) * mTileSize; 298 rect.MoveBy(xPos, yPos); 299 return rect; 300 } 301 302 gfx::IntRect TiledTextureImage::GetSrcTileRect() { 303 gfx::IntRect rect = GetTileRect(); 304 const bool needsYFlip = mFlags & OriginBottomLeft; 305 unsigned int srcY = 306 needsYFlip ? mSize.height - rect.Height() - rect.Y() : rect.Y(); 307 return gfx::IntRect(rect.X(), srcY, rect.Width(), rect.Height()); 308 } 309 310 void TiledTextureImage::BindTexture(GLenum aTextureUnit) { 311 if (!GetTileCount()) { 312 return; 313 } 314 mImages[mCurrentImage]->BindTexture(aTextureUnit); 315 } 316 317 /* 318 * Resize, trying to reuse tiles. The reuse strategy is to decide on reuse per 319 * column. A tile on a column is reused if it hasn't changed size, otherwise it 320 * is discarded/replaced. Extra tiles on a column are pruned after iterating 321 * each column, and extra rows are pruned after iteration over the entire image 322 * finishes. 323 */ 324 void TiledTextureImage::Resize(const gfx::IntSize& aSize) { 325 if (mSize == aSize && mTextureState != Created) { 326 return; 327 } 328 329 // calculate rows and columns, rounding up 330 unsigned int columns = (aSize.width + mTileSize - 1) / mTileSize; 331 unsigned int rows = (aSize.height + mTileSize - 1) / mTileSize; 332 333 // Iterate over old tile-store and insert/remove tiles as necessary 334 int row; 335 unsigned int i = 0; 336 for (row = 0; row < (int)rows; row++) { 337 // If we've gone beyond how many rows there were before, set mColumns to 338 // zero so that we only create new tiles. 339 if (row >= (int)mRows) mColumns = 0; 340 341 // Similarly, if we're on the last row of old tiles and the height has 342 // changed, discard all tiles in that row. 343 // This will cause the pruning of columns not to work, but we don't need 344 // to worry about that, as no more tiles will be reused past this point 345 // anyway. 346 if ((row == (int)mRows - 1) && (aSize.height != mSize.height)) mColumns = 0; 347 348 int col; 349 for (col = 0; col < (int)columns; col++) { 350 IntSize size( // use tilesize first, then the remainder 351 (col + 1) * mTileSize > (unsigned int)aSize.width 352 ? aSize.width % mTileSize 353 : mTileSize, 354 (row + 1) * mTileSize > (unsigned int)aSize.height 355 ? aSize.height % mTileSize 356 : mTileSize); 357 358 bool replace = false; 359 360 // Check if we can re-use old tiles. 361 if (col < (int)mColumns) { 362 // Reuse an existing tile. If the tile is an end-tile and the 363 // width differs, replace it instead. 364 if (mSize.width != aSize.width) { 365 if (col == (int)mColumns - 1) { 366 // Tile at the end of the old column, replace it with 367 // a new one. 368 replace = true; 369 } else if (col == (int)columns - 1) { 370 // Tile at the end of the new column, create a new one. 371 } else { 372 // Before the last column on both the old and new sizes, 373 // reuse existing tile. 374 i++; 375 continue; 376 } 377 } else { 378 // Width hasn't changed, reuse existing tile. 379 i++; 380 continue; 381 } 382 } 383 384 // Create a new tile. 385 RefPtr<TextureImage> teximg = 386 TileGenFunc(mGL, size, mContentType, mFlags, mImageFormat); 387 if (replace) 388 mImages.ReplaceElementAt(i, teximg); 389 else 390 mImages.InsertElementAt(i, teximg); 391 i++; 392 } 393 394 // Prune any unused tiles on the end of the column. 395 if (row < (int)mRows) { 396 for (col = (int)mColumns - col; col > 0; col--) { 397 mImages.RemoveElementAt(i); 398 } 399 } 400 } 401 402 // Prune any unused tiles at the end of the store. 403 mImages.RemoveLastElements(mImages.Length() - i); 404 405 // Reset tile-store properties. 406 mRows = rows; 407 mColumns = columns; 408 mSize = aSize; 409 mTextureState = Allocated; 410 mCurrentImage = 0; 411 } 412 413 uint32_t TiledTextureImage::GetTileCount() { return mImages.Length(); } 414 415 already_AddRefed<TextureImage> CreateTiledTextureImage( 416 GLContext* aGL, const gfx::IntSize& aSize, 417 TextureImage::ContentType aContentType, TextureImage::Flags aFlags, 418 TextureImage::ImageFormat aImageFormat) { 419 RefPtr<TextureImage> texImage = 420 static_cast<TextureImage*>(new gl::TiledTextureImage( 421 aGL, aSize, aContentType, aFlags, aImageFormat)); 422 return texImage.forget(); 423 } 424 425 already_AddRefed<TextureImage> CreateBasicTextureImage( 426 GLContext* aGL, const gfx::IntSize& aSize, 427 TextureImage::ContentType aContentType, GLenum aWrapMode, 428 TextureImage::Flags aFlags) { 429 bool useNearestFilter = aFlags & TextureImage::UseNearestFilter; 430 if (!aGL->MakeCurrent()) { 431 return nullptr; 432 } 433 434 GLuint texture = 0; 435 aGL->fGenTextures(1, &texture); 436 437 ScopedBindTexture bind(aGL, texture); 438 439 GLint texfilter = useNearestFilter ? LOCAL_GL_NEAREST : LOCAL_GL_LINEAR; 440 aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, 441 texfilter); 442 aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, 443 texfilter); 444 aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, aWrapMode); 445 aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, aWrapMode); 446 447 RefPtr<BasicTextureImage> texImage = new BasicTextureImage( 448 texture, aSize, aWrapMode, aContentType, aGL, aFlags); 449 return texImage.forget(); 450 } 451 452 } // namespace gl 453 } // namespace mozilla