Line data Source code
1 : /* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */
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 "GLUploadHelpers.h"
7 :
8 : #include "GLContext.h"
9 : #include "mozilla/gfx/2D.h"
10 : #include "gfxUtils.h"
11 : #include "mozilla/gfx/Tools.h" // For BytesPerPixel
12 : #include "nsRegion.h"
13 : #include "GfxTexturesReporter.h"
14 : #include "mozilla/gfx/Logging.h"
15 :
16 : namespace mozilla {
17 :
18 : using namespace gfx;
19 :
20 : namespace gl {
21 :
22 : static unsigned int
23 0 : DataOffset(const IntPoint& aPoint, int32_t aStride, SurfaceFormat aFormat)
24 : {
25 0 : unsigned int data = aPoint.y * aStride;
26 0 : data += aPoint.x * BytesPerPixel(aFormat);
27 0 : return data;
28 : }
29 :
30 0 : static GLint GetAddressAlignment(ptrdiff_t aAddress)
31 : {
32 0 : if (!(aAddress & 0x7)) {
33 0 : return 8;
34 0 : } else if (!(aAddress & 0x3)) {
35 0 : return 4;
36 0 : } else if (!(aAddress & 0x1)) {
37 0 : return 2;
38 : } else {
39 0 : return 1;
40 : }
41 : }
42 :
43 : // Take texture data in a given buffer and copy it into a larger buffer,
44 : // padding out the edge pixels for filtering if necessary
45 : static void
46 0 : CopyAndPadTextureData(const GLvoid* srcBuffer,
47 : GLvoid* dstBuffer,
48 : GLsizei srcWidth, GLsizei srcHeight,
49 : GLsizei dstWidth, GLsizei dstHeight,
50 : GLsizei stride, GLint pixelsize)
51 : {
52 0 : unsigned char* rowDest = static_cast<unsigned char*>(dstBuffer);
53 0 : const unsigned char* source = static_cast<const unsigned char*>(srcBuffer);
54 :
55 0 : for (GLsizei h = 0; h < srcHeight; ++h) {
56 0 : memcpy(rowDest, source, srcWidth * pixelsize);
57 0 : rowDest += dstWidth * pixelsize;
58 0 : source += stride;
59 : }
60 :
61 0 : GLsizei padHeight = srcHeight;
62 :
63 : // Pad out an extra row of pixels so that edge filtering doesn't use garbage data
64 0 : if (dstHeight > srcHeight) {
65 0 : memcpy(rowDest, source - stride, srcWidth * pixelsize);
66 0 : padHeight++;
67 : }
68 :
69 : // Pad out an extra column of pixels
70 0 : if (dstWidth > srcWidth) {
71 0 : rowDest = static_cast<unsigned char*>(dstBuffer) + srcWidth * pixelsize;
72 0 : for (GLsizei h = 0; h < padHeight; ++h) {
73 0 : memcpy(rowDest, rowDest - pixelsize, pixelsize);
74 0 : rowDest += dstWidth * pixelsize;
75 : }
76 : }
77 0 : }
78 :
79 : // In both of these cases (for the Adreno at least) it is impossible
80 : // to determine good or bad driver versions for POT texture uploads,
81 : // so blacklist them all. Newer drivers use a different rendering
82 : // string in the form "Adreno (TM) 200" and the drivers we've seen so
83 : // far work fine with NPOT textures, so don't blacklist those until we
84 : // have evidence of any problems with them.
85 : bool
86 0 : CanUploadSubTextures(GLContext* gl)
87 : {
88 0 : if (!gl->WorkAroundDriverBugs())
89 0 : return true;
90 :
91 : // There are certain GPUs that we don't want to use glTexSubImage2D on
92 : // because that function can be very slow and/or buggy
93 0 : if (gl->Renderer() == GLRenderer::Adreno200 ||
94 0 : gl->Renderer() == GLRenderer::Adreno205)
95 : {
96 0 : return false;
97 : }
98 :
99 : // On PowerVR glTexSubImage does a readback, so it will be slower
100 : // than just doing a glTexImage2D() directly. i.e. 26ms vs 10ms
101 0 : if (gl->Renderer() == GLRenderer::SGX540 ||
102 0 : gl->Renderer() == GLRenderer::SGX530)
103 : {
104 0 : return false;
105 : }
106 :
107 0 : return true;
108 : }
109 :
110 : static void
111 0 : TexSubImage2DWithUnpackSubimageGLES(GLContext* gl,
112 : GLenum target, GLint level,
113 : GLint xoffset, GLint yoffset,
114 : GLsizei width, GLsizei height,
115 : GLsizei stride, GLint pixelsize,
116 : GLenum format, GLenum type,
117 : const GLvoid* pixels)
118 : {
119 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
120 0 : std::min(GetAddressAlignment((ptrdiff_t)pixels),
121 0 : GetAddressAlignment((ptrdiff_t)stride)));
122 : // When using GL_UNPACK_ROW_LENGTH, we need to work around a Tegra
123 : // driver crash where the driver apparently tries to read
124 : // (stride - width * pixelsize) bytes past the end of the last input
125 : // row. We only upload the first height-1 rows using GL_UNPACK_ROW_LENGTH,
126 : // and then we upload the final row separately. See bug 697990.
127 0 : int rowLength = stride/pixelsize;
128 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
129 0 : gl->fTexSubImage2D(target,
130 : level,
131 : xoffset,
132 : yoffset,
133 : width,
134 : height-1,
135 : format,
136 : type,
137 0 : pixels);
138 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0);
139 0 : gl->fTexSubImage2D(target,
140 : level,
141 : xoffset,
142 0 : yoffset+height-1,
143 : width,
144 : 1,
145 : format,
146 : type,
147 0 : (const unsigned char*)pixels+(height-1)*stride);
148 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
149 0 : }
150 :
151 : static void
152 0 : TexSubImage2DWithoutUnpackSubimage(GLContext* gl,
153 : GLenum target, GLint level,
154 : GLint xoffset, GLint yoffset,
155 : GLsizei width, GLsizei height,
156 : GLsizei stride, GLint pixelsize,
157 : GLenum format, GLenum type,
158 : const GLvoid* pixels)
159 : {
160 : // Not using the whole row of texture data and GL_UNPACK_ROW_LENGTH
161 : // isn't supported. We make a copy of the texture data we're using,
162 : // such that we're using the whole row of data in the copy. This turns
163 : // out to be more efficient than uploading row-by-row; see bug 698197.
164 0 : unsigned char* newPixels = new (fallible) unsigned char[width*height*pixelsize];
165 :
166 0 : if (newPixels) {
167 0 : unsigned char* rowDest = newPixels;
168 0 : const unsigned char* rowSource = (const unsigned char*)pixels;
169 0 : for (int h = 0; h < height; h++) {
170 0 : memcpy(rowDest, rowSource, width*pixelsize);
171 0 : rowDest += width*pixelsize;
172 0 : rowSource += stride;
173 : }
174 :
175 0 : stride = width*pixelsize;
176 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
177 0 : std::min(GetAddressAlignment((ptrdiff_t)newPixels),
178 0 : GetAddressAlignment((ptrdiff_t)stride)));
179 : gl->fTexSubImage2D(target,
180 : level,
181 : xoffset,
182 : yoffset,
183 : width,
184 : height,
185 : format,
186 : type,
187 0 : newPixels);
188 0 : delete [] newPixels;
189 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
190 :
191 : } else {
192 : // If we did not have sufficient memory for the required
193 : // temporary buffer, then fall back to uploading row-by-row.
194 0 : const unsigned char* rowSource = (const unsigned char*)pixels;
195 :
196 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
197 0 : std::min(GetAddressAlignment((ptrdiff_t)pixels),
198 0 : GetAddressAlignment((ptrdiff_t)stride)));
199 :
200 0 : for (int i = 0; i < height; i++) {
201 0 : gl->fTexSubImage2D(target, level,
202 : xoffset, yoffset + i,
203 : width, 1,
204 0 : format, type, rowSource);
205 0 : rowSource += stride;
206 : }
207 :
208 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
209 : }
210 0 : }
211 :
212 : static void
213 0 : TexSubImage2DHelper(GLContext* gl,
214 : GLenum target, GLint level,
215 : GLint xoffset, GLint yoffset,
216 : GLsizei width, GLsizei height, GLsizei stride,
217 : GLint pixelsize, GLenum format,
218 : GLenum type, const GLvoid* pixels)
219 : {
220 0 : if (gl->IsGLES()) {
221 0 : if (stride == width * pixelsize) {
222 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
223 0 : std::min(GetAddressAlignment((ptrdiff_t)pixels),
224 0 : GetAddressAlignment((ptrdiff_t)stride)));
225 : gl->fTexSubImage2D(target,
226 : level,
227 : xoffset,
228 : yoffset,
229 : width,
230 : height,
231 : format,
232 : type,
233 0 : pixels);
234 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
235 0 : } else if (gl->IsExtensionSupported(GLContext::EXT_unpack_subimage)) {
236 : TexSubImage2DWithUnpackSubimageGLES(gl, target, level, xoffset, yoffset,
237 : width, height, stride,
238 0 : pixelsize, format, type, pixels);
239 :
240 : } else {
241 : TexSubImage2DWithoutUnpackSubimage(gl, target, level, xoffset, yoffset,
242 : width, height, stride,
243 0 : pixelsize, format, type, pixels);
244 : }
245 : } else {
246 : // desktop GL (non-ES) path
247 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
248 0 : std::min(GetAddressAlignment((ptrdiff_t)pixels),
249 0 : GetAddressAlignment((ptrdiff_t)stride)));
250 0 : int rowLength = stride/pixelsize;
251 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
252 : gl->fTexSubImage2D(target,
253 : level,
254 : xoffset,
255 : yoffset,
256 : width,
257 : height,
258 : format,
259 : type,
260 0 : pixels);
261 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0);
262 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
263 : }
264 0 : }
265 :
266 : static void
267 0 : TexImage2DHelper(GLContext* gl,
268 : GLenum target, GLint level, GLint internalformat,
269 : GLsizei width, GLsizei height, GLsizei stride,
270 : GLint pixelsize, GLint border, GLenum format,
271 : GLenum type, const GLvoid* pixels)
272 : {
273 0 : if (gl->IsGLES()) {
274 :
275 0 : NS_ASSERTION(format == (GLenum)internalformat,
276 : "format and internalformat not the same for glTexImage2D on GLES2");
277 :
278 0 : MOZ_ASSERT(width >= 0 && height >= 0);
279 0 : if (!CanUploadNonPowerOfTwo(gl)
280 0 : && (stride != width * pixelsize
281 0 : || !IsPowerOfTwo((uint32_t)width)
282 0 : || !IsPowerOfTwo((uint32_t)height))) {
283 :
284 : // Pad out texture width and height to the next power of two
285 : // as we don't support/want non power of two texture uploads
286 0 : GLsizei paddedWidth = RoundUpPow2((uint32_t)width);
287 0 : GLsizei paddedHeight = RoundUpPow2((uint32_t)height);
288 :
289 0 : GLvoid* paddedPixels = new unsigned char[paddedWidth * paddedHeight * pixelsize];
290 :
291 : // Pad out texture data to be in a POT sized buffer for uploading to
292 : // a POT sized texture
293 : CopyAndPadTextureData(pixels, paddedPixels, width, height,
294 0 : paddedWidth, paddedHeight, stride, pixelsize);
295 :
296 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
297 0 : std::min(GetAddressAlignment((ptrdiff_t)paddedPixels),
298 0 : GetAddressAlignment((ptrdiff_t)paddedWidth * pixelsize)));
299 : gl->fTexImage2D(target,
300 : border,
301 : internalformat,
302 : paddedWidth,
303 : paddedHeight,
304 : border,
305 : format,
306 : type,
307 0 : paddedPixels);
308 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
309 :
310 0 : delete[] static_cast<unsigned char*>(paddedPixels);
311 0 : return;
312 : }
313 :
314 0 : if (stride == width * pixelsize) {
315 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
316 0 : std::min(GetAddressAlignment((ptrdiff_t)pixels),
317 0 : GetAddressAlignment((ptrdiff_t)stride)));
318 : gl->fTexImage2D(target,
319 : border,
320 : internalformat,
321 : width,
322 : height,
323 : border,
324 : format,
325 : type,
326 0 : pixels);
327 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
328 : } else {
329 : // Use GLES-specific workarounds for GL_UNPACK_ROW_LENGTH; these are
330 : // implemented in TexSubImage2D.
331 : gl->fTexImage2D(target,
332 : border,
333 : internalformat,
334 : width,
335 : height,
336 : border,
337 : format,
338 : type,
339 0 : nullptr);
340 : TexSubImage2DHelper(gl,
341 : target,
342 : level,
343 : 0,
344 : 0,
345 : width,
346 : height,
347 : stride,
348 : pixelsize,
349 : format,
350 : type,
351 0 : pixels);
352 : }
353 : } else {
354 : // desktop GL (non-ES) path
355 :
356 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
357 0 : std::min(GetAddressAlignment((ptrdiff_t)pixels),
358 0 : GetAddressAlignment((ptrdiff_t)stride)));
359 0 : int rowLength = stride/pixelsize;
360 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
361 : gl->fTexImage2D(target,
362 : level,
363 : internalformat,
364 : width,
365 : height,
366 : border,
367 : format,
368 : type,
369 0 : pixels);
370 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0);
371 0 : gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
372 : }
373 : }
374 :
375 : SurfaceFormat
376 0 : UploadImageDataToTexture(GLContext* gl,
377 : unsigned char* aData,
378 : int32_t aStride,
379 : SurfaceFormat aFormat,
380 : const nsIntRegion& aDstRegion,
381 : GLuint aTexture,
382 : const gfx::IntSize& aSize,
383 : size_t* aOutUploadSize,
384 : bool aNeedInit,
385 : GLenum aTextureUnit,
386 : GLenum aTextureTarget)
387 : {
388 0 : gl->MakeCurrent();
389 0 : gl->fActiveTexture(aTextureUnit);
390 0 : gl->fBindTexture(aTextureTarget, aTexture);
391 :
392 0 : GLenum format = 0;
393 0 : GLenum internalFormat = 0;
394 0 : GLenum type = 0;
395 0 : int32_t pixelSize = BytesPerPixel(aFormat);
396 0 : SurfaceFormat surfaceFormat = gfx::SurfaceFormat::UNKNOWN;
397 :
398 0 : MOZ_ASSERT(gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA ||
399 : gl->GetPreferredARGB32Format() == LOCAL_GL_RGBA);
400 :
401 0 : switch (aFormat) {
402 : case SurfaceFormat::B8G8R8A8:
403 0 : if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) {
404 0 : format = LOCAL_GL_BGRA;
405 0 : surfaceFormat = SurfaceFormat::R8G8B8A8;
406 0 : type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
407 : } else {
408 0 : format = LOCAL_GL_RGBA;
409 0 : surfaceFormat = SurfaceFormat::B8G8R8A8;
410 0 : type = LOCAL_GL_UNSIGNED_BYTE;
411 : }
412 0 : internalFormat = LOCAL_GL_RGBA;
413 0 : break;
414 : case SurfaceFormat::B8G8R8X8:
415 : // Treat BGRX surfaces as BGRA except for the surface
416 : // format used.
417 0 : if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) {
418 0 : format = LOCAL_GL_BGRA;
419 0 : surfaceFormat = SurfaceFormat::R8G8B8X8;
420 0 : type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
421 : } else {
422 0 : format = LOCAL_GL_RGBA;
423 0 : surfaceFormat = SurfaceFormat::B8G8R8X8;
424 0 : type = LOCAL_GL_UNSIGNED_BYTE;
425 : }
426 0 : internalFormat = LOCAL_GL_RGBA;
427 0 : break;
428 : case SurfaceFormat::R8G8B8A8:
429 0 : if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) {
430 : // Upload our RGBA as BGRA, but store that the uploaded format is
431 : // BGRA. (sample from R to get B)
432 0 : format = LOCAL_GL_BGRA;
433 0 : type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
434 0 : surfaceFormat = SurfaceFormat::B8G8R8A8;
435 : } else {
436 0 : format = LOCAL_GL_RGBA;
437 0 : type = LOCAL_GL_UNSIGNED_BYTE;
438 0 : surfaceFormat = SurfaceFormat::R8G8B8A8;
439 : }
440 0 : internalFormat = LOCAL_GL_RGBA;
441 0 : break;
442 : case SurfaceFormat::R8G8B8X8:
443 : // Treat RGBX surfaces as RGBA except for the surface
444 : // format used.
445 0 : if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) {
446 0 : format = LOCAL_GL_BGRA;
447 0 : type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
448 0 : surfaceFormat = SurfaceFormat::B8G8R8X8;
449 : } else {
450 0 : format = LOCAL_GL_RGBA;
451 0 : type = LOCAL_GL_UNSIGNED_BYTE;
452 0 : surfaceFormat = SurfaceFormat::R8G8B8X8;
453 : }
454 0 : internalFormat = LOCAL_GL_RGBA;
455 0 : break;
456 : case SurfaceFormat::R5G6B5_UINT16:
457 0 : internalFormat = format = LOCAL_GL_RGB;
458 0 : type = LOCAL_GL_UNSIGNED_SHORT_5_6_5;
459 0 : surfaceFormat = SurfaceFormat::R5G6B5_UINT16;
460 0 : break;
461 : case SurfaceFormat::A8:
462 0 : internalFormat = format = LOCAL_GL_LUMINANCE;
463 0 : type = LOCAL_GL_UNSIGNED_BYTE;
464 : // We don't have a specific luminance shader
465 0 : surfaceFormat = SurfaceFormat::A8;
466 0 : break;
467 : default:
468 0 : NS_ASSERTION(false, "Unhandled image surface format!");
469 : }
470 :
471 0 : if (aOutUploadSize) {
472 0 : *aOutUploadSize = 0;
473 : }
474 :
475 0 : if (aNeedInit || !CanUploadSubTextures(gl)) {
476 : // If the texture needs initialized, or we are unable to
477 : // upload sub textures, then initialize and upload the entire
478 : // texture.
479 0 : TexImage2DHelper(gl,
480 : aTextureTarget,
481 : 0,
482 : internalFormat,
483 0 : aSize.width,
484 0 : aSize.height,
485 : aStride,
486 : pixelSize,
487 : 0,
488 : format,
489 : type,
490 0 : aData);
491 :
492 0 : if (aOutUploadSize && aNeedInit) {
493 0 : uint32_t texelSize = GetBytesPerTexel(internalFormat, type);
494 0 : size_t numTexels = size_t(aSize.width) * size_t(aSize.height);
495 0 : *aOutUploadSize += texelSize * numTexels;
496 : }
497 : } else {
498 : // Upload each rect in the region to the texture
499 0 : for (auto iter = aDstRegion.RectIter(); !iter.Done(); iter.Next()) {
500 0 : const IntRect& rect = iter.Get();
501 : const unsigned char* rectData =
502 0 : aData + DataOffset(rect.TopLeft(), aStride, aFormat);
503 :
504 : TexSubImage2DHelper(gl,
505 : aTextureTarget,
506 : 0,
507 0 : rect.x,
508 0 : rect.y,
509 0 : rect.width,
510 0 : rect.height,
511 : aStride,
512 : pixelSize,
513 : format,
514 : type,
515 0 : rectData);
516 : }
517 : }
518 :
519 0 : return surfaceFormat;
520 : }
521 :
522 : SurfaceFormat
523 0 : UploadSurfaceToTexture(GLContext* gl,
524 : DataSourceSurface* aSurface,
525 : const nsIntRegion& aDstRegion,
526 : GLuint aTexture,
527 : const gfx::IntSize& aSize,
528 : size_t* aOutUploadSize,
529 : bool aNeedInit,
530 : const gfx::IntPoint& aSrcPoint,
531 : GLenum aTextureUnit,
532 : GLenum aTextureTarget)
533 : {
534 :
535 0 : int32_t stride = aSurface->Stride();
536 0 : SurfaceFormat format = aSurface->GetFormat();
537 0 : unsigned char* data = aSurface->GetData() +
538 0 : DataOffset(aSrcPoint, stride, format);
539 :
540 0 : return UploadImageDataToTexture(gl, data, stride, format,
541 : aDstRegion, aTexture, aSize,
542 : aOutUploadSize, aNeedInit,
543 0 : aTextureUnit, aTextureTarget);
544 : }
545 :
546 : bool
547 0 : CanUploadNonPowerOfTwo(GLContext* gl)
548 : {
549 0 : if (!gl->WorkAroundDriverBugs())
550 0 : return true;
551 :
552 : // Some GPUs driver crash when uploading non power of two 565 textures.
553 0 : return gl->Renderer() != GLRenderer::Adreno200 &&
554 0 : gl->Renderer() != GLRenderer::Adreno205;
555 : }
556 :
557 : } // namespace gl
558 : } // namespace mozilla
|