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