Bug 1860959 [wpt PR 42048] - [webaudio] Convert test to testharness, a=testonly
[gecko.git] / gfx / gl / GLUploadHelpers.cpp
blobc6efcfd656b940b837eb722a2f7b72eff4a709e0
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/. */
6 #include "GLUploadHelpers.h"
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"
16 namespace mozilla {
18 using namespace gfx;
20 namespace gl {
22 static unsigned int DataOffset(const IntPoint& aPoint, int32_t aStride,
23 SurfaceFormat aFormat) {
24 unsigned int data = aPoint.y * aStride;
25 data += aPoint.x * BytesPerPixel(aFormat);
26 return data;
29 static bool CheckUploadBounds(const IntSize& aDst, const IntSize& aSrc,
30 const IntPoint& aOffset) {
31 if (aOffset.x < 0 || aOffset.y < 0 || aOffset.x >= aSrc.width ||
32 aOffset.y >= aSrc.height) {
33 MOZ_ASSERT_UNREACHABLE("Offset outside source bounds");
34 return false;
36 if (aDst.width > (aSrc.width - aOffset.x) ||
37 aDst.height > (aSrc.height - aOffset.y)) {
38 MOZ_ASSERT_UNREACHABLE("Source has insufficient data");
39 return false;
41 return true;
44 static GLint GetAddressAlignment(ptrdiff_t aAddress) {
45 if (!(aAddress & 0x7)) {
46 return 8;
47 } else if (!(aAddress & 0x3)) {
48 return 4;
49 } else if (!(aAddress & 0x1)) {
50 return 2;
51 } else {
52 return 1;
56 // Take texture data in a given buffer and copy it into a larger buffer,
57 // padding out the edge pixels for filtering if necessary
58 static void CopyAndPadTextureData(const GLvoid* srcBuffer, GLvoid* dstBuffer,
59 GLsizei srcWidth, GLsizei srcHeight,
60 GLsizei dstWidth, GLsizei dstHeight,
61 GLsizei stride, GLint pixelsize) {
62 unsigned char* rowDest = static_cast<unsigned char*>(dstBuffer);
63 const unsigned char* source = static_cast<const unsigned char*>(srcBuffer);
65 for (GLsizei h = 0; h < srcHeight; ++h) {
66 memcpy(rowDest, source, srcWidth * pixelsize);
67 rowDest += dstWidth * pixelsize;
68 source += stride;
71 GLsizei padHeight = srcHeight;
73 // Pad out an extra row of pixels so that edge filtering doesn't use garbage
74 // data
75 if (dstHeight > srcHeight) {
76 memcpy(rowDest, source - stride, srcWidth * pixelsize);
77 padHeight++;
80 // Pad out an extra column of pixels
81 if (dstWidth > srcWidth) {
82 rowDest = static_cast<unsigned char*>(dstBuffer) + srcWidth * pixelsize;
83 for (GLsizei h = 0; h < padHeight; ++h) {
84 memcpy(rowDest, rowDest - pixelsize, pixelsize);
85 rowDest += dstWidth * pixelsize;
90 // In both of these cases (for the Adreno at least) it is impossible
91 // to determine good or bad driver versions for POT texture uploads,
92 // so blacklist them all. Newer drivers use a different rendering
93 // string in the form "Adreno (TM) 200" and the drivers we've seen so
94 // far work fine with NPOT textures, so don't blacklist those until we
95 // have evidence of any problems with them.
96 bool ShouldUploadSubTextures(GLContext* gl) {
97 if (!gl->WorkAroundDriverBugs()) return true;
99 // There are certain GPUs that we don't want to use glTexSubImage2D on
100 // because that function can be very slow and/or buggy
101 if (gl->Renderer() == GLRenderer::Adreno200 ||
102 gl->Renderer() == GLRenderer::Adreno205) {
103 return false;
106 // On PowerVR glTexSubImage does a readback, so it will be slower
107 // than just doing a glTexImage2D() directly. i.e. 26ms vs 10ms
108 if (gl->Renderer() == GLRenderer::SGX540 ||
109 gl->Renderer() == GLRenderer::SGX530) {
110 return false;
113 return true;
116 static void TexSubImage2DWithUnpackSubimageGLES(
117 GLContext* gl, GLenum target, GLint level, GLint xoffset, GLint yoffset,
118 GLsizei width, GLsizei height, GLsizei stride, GLint pixelsize,
119 GLenum format, GLenum type, const GLvoid* pixels) {
120 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
121 std::min(GetAddressAlignment((ptrdiff_t)pixels),
122 GetAddressAlignment((ptrdiff_t)stride)));
123 // When using GL_UNPACK_ROW_LENGTH, we need to work around a Tegra
124 // driver crash where the driver apparently tries to read
125 // (stride - width * pixelsize) bytes past the end of the last input
126 // row. We only upload the first height-1 rows using GL_UNPACK_ROW_LENGTH,
127 // and then we upload the final row separately. See bug 697990.
128 int rowLength = stride / pixelsize;
129 if (gl->HasPBOState()) {
130 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
131 gl->fTexSubImage2D(target, level, xoffset, yoffset, width, height, format,
132 type, pixels);
133 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0);
134 } else {
135 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
136 gl->fTexSubImage2D(target, level, xoffset, yoffset, width, height - 1,
137 format, type, pixels);
138 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0);
139 gl->fTexSubImage2D(target, level, xoffset, yoffset + height - 1, width, 1,
140 format, type,
141 (const unsigned char*)pixels + (height - 1) * stride);
143 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
146 static void TexSubImage2DWithoutUnpackSubimage(
147 GLContext* gl, GLenum target, GLint level, GLint xoffset, GLint yoffset,
148 GLsizei width, GLsizei height, GLsizei stride, GLint pixelsize,
149 GLenum format, GLenum type, const GLvoid* pixels) {
150 // Not using the whole row of texture data and GL_UNPACK_ROW_LENGTH
151 // isn't supported. We make a copy of the texture data we're using,
152 // such that we're using the whole row of data in the copy. This turns
153 // out to be more efficient than uploading row-by-row; see bug 698197.
155 // Width and height are never more than 16384. At 16Ki*16Ki, 4bpp is 1GiB, but
156 // if we allow 8bpp (or higher) here, that's 2GiB, which would overflow on
157 // 32-bit.
158 MOZ_ASSERT(width <= 16384);
159 MOZ_ASSERT(height <= 16384);
160 MOZ_ASSERT(pixelsize < 8);
162 const auto size = CheckedInt<size_t>(width) * height * pixelsize;
163 if (!size.isValid()) {
164 // This should never happen, but we use a defensive check.
165 MOZ_ASSERT_UNREACHABLE("Unacceptable size calculated.!");
166 return;
169 unsigned char* newPixels = new (fallible) unsigned char[size.value()];
171 if (newPixels) {
172 unsigned char* rowDest = newPixels;
173 const unsigned char* rowSource = (const unsigned char*)pixels;
174 for (int h = 0; h < height; h++) {
175 memcpy(rowDest, rowSource, width * pixelsize);
176 rowDest += width * pixelsize;
177 rowSource += stride;
180 stride = width * pixelsize;
181 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
182 std::min(GetAddressAlignment((ptrdiff_t)newPixels),
183 GetAddressAlignment((ptrdiff_t)stride)));
184 gl->fTexSubImage2D(target, level, xoffset, yoffset, width, height, format,
185 type, newPixels);
186 delete[] newPixels;
187 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
189 } else {
190 // If we did not have sufficient memory for the required
191 // temporary buffer, then fall back to uploading row-by-row.
192 const unsigned char* rowSource = (const unsigned char*)pixels;
194 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
195 std::min(GetAddressAlignment((ptrdiff_t)pixels),
196 GetAddressAlignment((ptrdiff_t)stride)));
198 for (int i = 0; i < height; i++) {
199 gl->fTexSubImage2D(target, level, xoffset, yoffset + i, width, 1, format,
200 type, rowSource);
201 rowSource += stride;
204 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
207 static void TexSubImage2DHelper(GLContext* gl, GLenum target, GLint level,
208 GLint xoffset, GLint yoffset, GLsizei width,
209 GLsizei height, GLsizei stride, GLint pixelsize,
210 GLenum format, GLenum type,
211 const GLvoid* pixels) {
212 if (gl->IsGLES()) {
213 if (stride == width * pixelsize) {
214 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
215 std::min(GetAddressAlignment((ptrdiff_t)pixels),
216 GetAddressAlignment((ptrdiff_t)stride)));
217 gl->fTexSubImage2D(target, level, xoffset, yoffset, width, height, format,
218 type, pixels);
219 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
220 } else if (gl->IsExtensionSupported(GLContext::EXT_unpack_subimage) ||
221 gl->HasPBOState()) {
222 TexSubImage2DWithUnpackSubimageGLES(gl, target, level, xoffset, yoffset,
223 width, height, stride, pixelsize,
224 format, type, pixels);
226 } else {
227 TexSubImage2DWithoutUnpackSubimage(gl, target, level, xoffset, yoffset,
228 width, height, stride, pixelsize,
229 format, type, pixels);
231 } else {
232 // desktop GL (non-ES) path
233 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
234 std::min(GetAddressAlignment((ptrdiff_t)pixels),
235 GetAddressAlignment((ptrdiff_t)stride)));
236 int rowLength = stride / pixelsize;
237 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
238 gl->fTexSubImage2D(target, level, xoffset, yoffset, width, height, format,
239 type, pixels);
240 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0);
241 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
245 static void TexImage2DHelper(GLContext* gl, GLenum target, GLint level,
246 GLint internalformat, GLsizei width,
247 GLsizei height, GLsizei stride, GLint pixelsize,
248 GLint border, GLenum format, GLenum type,
249 const GLvoid* pixels) {
250 if (gl->IsGLES()) {
251 NS_ASSERTION(
252 format == (GLenum)internalformat,
253 "format and internalformat not the same for glTexImage2D on GLES2");
255 MOZ_ASSERT(width >= 0 && height >= 0);
256 if (!CanUploadNonPowerOfTwo(gl) &&
257 (stride != width * pixelsize || !IsPowerOfTwo((uint32_t)width) ||
258 !IsPowerOfTwo((uint32_t)height))) {
259 // Pad out texture width and height to the next power of two
260 // as we don't support/want non power of two texture uploads
261 GLsizei paddedWidth = RoundUpPow2((uint32_t)width);
262 GLsizei paddedHeight = RoundUpPow2((uint32_t)height);
264 // Width and height are never more than 16384. At 16Ki*16Ki, 4bpp is 1GiB,
265 // but if we allow 8bpp (or higher) here, that's 2GiB, which would
266 // overflow on 32-bit.
267 MOZ_ASSERT(width <= 16384);
268 MOZ_ASSERT(height <= 16384);
269 MOZ_ASSERT(pixelsize < 8);
271 const auto size =
272 CheckedInt<size_t>(paddedWidth) * paddedHeight * pixelsize;
273 if (!size.isValid()) {
274 // This should never happen, but we use a defensive check.
275 MOZ_ASSERT_UNREACHABLE("Unacceptable size calculated.!");
276 return;
279 GLvoid* paddedPixels = new unsigned char[size.value()];
281 // Pad out texture data to be in a POT sized buffer for uploading to
282 // a POT sized texture
283 CopyAndPadTextureData(pixels, paddedPixels, width, height, paddedWidth,
284 paddedHeight, stride, pixelsize);
286 gl->fPixelStorei(
287 LOCAL_GL_UNPACK_ALIGNMENT,
288 std::min(GetAddressAlignment((ptrdiff_t)paddedPixels),
289 GetAddressAlignment((ptrdiff_t)paddedWidth * pixelsize)));
290 gl->fTexImage2D(target, border, internalformat, paddedWidth, paddedHeight,
291 border, format, type, paddedPixels);
292 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
294 delete[] static_cast<unsigned char*>(paddedPixels);
295 return;
298 if (stride == width * pixelsize) {
299 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
300 std::min(GetAddressAlignment((ptrdiff_t)pixels),
301 GetAddressAlignment((ptrdiff_t)stride)));
302 gl->fTexImage2D(target, border, internalformat, width, height, border,
303 format, type, pixels);
304 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
305 } else {
306 // Use GLES-specific workarounds for GL_UNPACK_ROW_LENGTH; these are
307 // implemented in TexSubImage2D.
308 gl->fTexImage2D(target, border, internalformat, width, height, border,
309 format, type, nullptr);
310 TexSubImage2DHelper(gl, target, level, 0, 0, width, height, stride,
311 pixelsize, format, type, pixels);
313 } else {
314 // desktop GL (non-ES) path
316 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
317 std::min(GetAddressAlignment((ptrdiff_t)pixels),
318 GetAddressAlignment((ptrdiff_t)stride)));
319 int rowLength = stride / pixelsize;
320 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
321 gl->fTexImage2D(target, level, internalformat, width, height, border,
322 format, type, pixels);
323 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0);
324 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
328 SurfaceFormat UploadImageDataToTexture(
329 GLContext* gl, unsigned char* aData, const gfx::IntSize& aDataSize,
330 const IntPoint& aDstOffset, int32_t aStride, SurfaceFormat aFormat,
331 const nsIntRegion& aDstRegion, GLuint aTexture, const gfx::IntSize& aSize,
332 size_t* aOutUploadSize, bool aNeedInit, GLenum aTextureUnit,
333 GLenum aTextureTarget) {
334 gl->MakeCurrent();
335 gl->fActiveTexture(aTextureUnit);
336 gl->fBindTexture(aTextureTarget, aTexture);
338 GLenum format = 0;
339 GLenum internalFormat = 0;
340 GLenum type = 0;
341 int32_t pixelSize = BytesPerPixel(aFormat);
342 SurfaceFormat surfaceFormat = gfx::SurfaceFormat::UNKNOWN;
344 MOZ_ASSERT(gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA ||
345 gl->GetPreferredARGB32Format() == LOCAL_GL_RGBA);
347 switch (aFormat) {
348 case SurfaceFormat::B8G8R8A8:
349 if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) {
350 format = LOCAL_GL_BGRA;
351 surfaceFormat = SurfaceFormat::R8G8B8A8;
352 type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
353 } else {
354 format = LOCAL_GL_RGBA;
355 surfaceFormat = SurfaceFormat::B8G8R8A8;
356 type = LOCAL_GL_UNSIGNED_BYTE;
358 internalFormat = LOCAL_GL_RGBA;
359 break;
360 case SurfaceFormat::B8G8R8X8:
361 // Treat BGRX surfaces as BGRA except for the surface
362 // format used.
363 if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) {
364 format = LOCAL_GL_BGRA;
365 surfaceFormat = SurfaceFormat::R8G8B8X8;
366 type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
367 } else {
368 format = LOCAL_GL_RGBA;
369 surfaceFormat = SurfaceFormat::B8G8R8X8;
370 type = LOCAL_GL_UNSIGNED_BYTE;
372 internalFormat = LOCAL_GL_RGBA;
373 break;
374 case SurfaceFormat::R8G8B8A8:
375 if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) {
376 // Upload our RGBA as BGRA, but store that the uploaded format is
377 // BGRA. (sample from R to get B)
378 format = LOCAL_GL_BGRA;
379 type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
380 surfaceFormat = SurfaceFormat::B8G8R8A8;
381 } else {
382 format = LOCAL_GL_RGBA;
383 type = LOCAL_GL_UNSIGNED_BYTE;
384 surfaceFormat = SurfaceFormat::R8G8B8A8;
386 internalFormat = LOCAL_GL_RGBA;
387 break;
388 case SurfaceFormat::R8G8B8X8:
389 // Treat RGBX surfaces as RGBA except for the surface
390 // format used.
391 if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) {
392 format = LOCAL_GL_BGRA;
393 type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
394 surfaceFormat = SurfaceFormat::B8G8R8X8;
395 } else {
396 format = LOCAL_GL_RGBA;
397 type = LOCAL_GL_UNSIGNED_BYTE;
398 surfaceFormat = SurfaceFormat::R8G8B8X8;
400 internalFormat = LOCAL_GL_RGBA;
401 break;
402 case SurfaceFormat::R5G6B5_UINT16:
403 internalFormat = format = LOCAL_GL_RGB;
404 type = LOCAL_GL_UNSIGNED_SHORT_5_6_5;
405 surfaceFormat = SurfaceFormat::R5G6B5_UINT16;
406 break;
407 case SurfaceFormat::A8:
408 if (gl->IsGLES()) {
409 format = LOCAL_GL_LUMINANCE;
410 internalFormat = LOCAL_GL_LUMINANCE;
411 } else {
412 format = LOCAL_GL_RED;
413 internalFormat = LOCAL_GL_R8;
415 type = LOCAL_GL_UNSIGNED_BYTE;
416 // We don't have a specific luminance shader
417 surfaceFormat = SurfaceFormat::A8;
418 break;
419 case SurfaceFormat::A16:
420 if (gl->IsGLES()) {
421 format = LOCAL_GL_LUMINANCE;
422 internalFormat = LOCAL_GL_LUMINANCE16;
423 } else {
424 format = LOCAL_GL_RED;
425 internalFormat = LOCAL_GL_R16;
427 type = LOCAL_GL_UNSIGNED_SHORT;
428 // We don't have a specific luminance shader
429 surfaceFormat = SurfaceFormat::A8;
430 pixelSize = 2;
431 break;
432 default:
433 MOZ_ASSERT_UNREACHABLE("Unhandled image surface format!");
436 if (aOutUploadSize) {
437 *aOutUploadSize = 0;
440 if (surfaceFormat == gfx::SurfaceFormat::UNKNOWN) {
441 return gfx::SurfaceFormat::UNKNOWN;
444 // We can only skip SubTextures if aOffset = 0 because we need the whole
445 // buffer
446 if (aNeedInit || (!ShouldUploadSubTextures(gl) && aDstOffset == IntPoint())) {
447 if (!CheckUploadBounds(aSize, aDataSize, IntPoint())) {
448 return SurfaceFormat::UNKNOWN;
450 // If the texture needs initialized, or we are unable to
451 // upload sub textures, then initialize and upload the entire
452 // texture.
453 TexImage2DHelper(gl, aTextureTarget, 0, internalFormat, aSize.width,
454 aSize.height, aStride, pixelSize, 0, format, type, aData);
456 if (aOutUploadSize && aNeedInit) {
457 uint32_t texelSize = GetBytesPerTexel(internalFormat, type);
458 size_t numTexels = size_t(aSize.width) * size_t(aSize.height);
459 *aOutUploadSize += texelSize * numTexels;
461 } else {
462 // Upload each rect in the region to the texture
463 for (auto iter = aDstRegion.RectIter(); !iter.Done(); iter.Next()) {
464 IntRect rect = iter.Get();
465 if (!CheckUploadBounds(rect.Size(), aDataSize, rect.TopLeft())) {
466 return SurfaceFormat::UNKNOWN;
469 const unsigned char* rectData =
470 aData + DataOffset(rect.TopLeft(), aStride, aFormat);
472 rect += aDstOffset;
473 TexSubImage2DHelper(gl, aTextureTarget, 0, rect.X(), rect.Y(),
474 rect.Width(), rect.Height(), aStride, pixelSize,
475 format, type, rectData);
479 return surfaceFormat;
482 SurfaceFormat UploadSurfaceToTexture(GLContext* gl, DataSourceSurface* aSurface,
483 const nsIntRegion& aDstRegion,
484 GLuint aTexture, const gfx::IntSize& aSize,
485 size_t* aOutUploadSize, bool aNeedInit,
486 const gfx::IntPoint& aSrcOffset,
487 const gfx::IntPoint& aDstOffset,
488 GLenum aTextureUnit,
489 GLenum aTextureTarget) {
490 DataSourceSurface::ScopedMap map(aSurface, DataSourceSurface::READ);
491 int32_t stride = map.GetStride();
492 SurfaceFormat format = aSurface->GetFormat();
493 gfx::IntSize size = aSurface->GetSize();
495 // only fail if we'll need the entire surface for initialization
496 if (aNeedInit && !CheckUploadBounds(aSize, size, aSrcOffset)) {
497 return SurfaceFormat::UNKNOWN;
500 unsigned char* data = map.GetData() + DataOffset(aSrcOffset, stride, format);
501 size.width -= aSrcOffset.x;
502 size.height -= aSrcOffset.y;
504 return UploadImageDataToTexture(gl, data, size, aDstOffset, stride, format,
505 aDstRegion, aTexture, aSize, aOutUploadSize,
506 aNeedInit, aTextureUnit, aTextureTarget);
509 bool CanUploadNonPowerOfTwo(GLContext* gl) {
510 if (!gl->WorkAroundDriverBugs()) return true;
512 // Some GPUs driver crash when uploading non power of two 565 textures.
513 return gl->Renderer() != GLRenderer::Adreno200 &&
514 gl->Renderer() != GLRenderer::Adreno205;
517 } // namespace gl
518 } // namespace mozilla