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