GLUploadHelpers.cpp (20463B)
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 "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 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; 27 } 28 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; 35 } 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; 40 } 41 return true; 42 } 43 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; 53 } 54 } 55 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); 64 65 for (GLsizei h = 0; h < srcHeight; ++h) { 66 memcpy(rowDest, source, srcWidth * pixelsize); 67 rowDest += dstWidth * pixelsize; 68 source += stride; 69 } 70 71 GLsizei padHeight = srcHeight; 72 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++; 78 } 79 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; 86 } 87 } 88 } 89 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; 98 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; 104 } 105 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; 111 } 112 113 return true; 114 } 115 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); 142 } 143 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); 144 } 145 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. 154 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); 161 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; 167 } 168 169 unsigned char* newPixels = new (fallible) unsigned char[size.value()]; 170 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; 178 } 179 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); 188 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; 193 194 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 195 std::min(GetAddressAlignment((ptrdiff_t)pixels), 196 GetAddressAlignment((ptrdiff_t)stride))); 197 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; 202 } 203 204 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); 205 } 206 } 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); 225 226 } else { 227 TexSubImage2DWithoutUnpackSubimage(gl, target, level, xoffset, yoffset, 228 width, height, stride, pixelsize, 229 format, type, pixels); 230 } 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); 242 } 243 } 244 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"); 254 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); 263 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); 270 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; 277 } 278 279 GLvoid* paddedPixels = new unsigned char[size.value()]; 280 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); 285 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); 293 294 delete[] static_cast<unsigned char*>(paddedPixels); 295 return; 296 } 297 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); 312 } 313 } else { 314 // desktop GL (non-ES) path 315 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); 325 } 326 } 327 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); 337 338 GLenum format = 0; 339 GLenum internalFormat = 0; 340 GLenum type = 0; 341 int32_t pixelSize = BytesPerPixel(aFormat); 342 SurfaceFormat surfaceFormat = gfx::SurfaceFormat::UNKNOWN; 343 344 MOZ_ASSERT(gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA || 345 gl->GetPreferredARGB32Format() == LOCAL_GL_RGBA); 346 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; 357 } 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; 371 } 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; 385 } 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; 399 } 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; 414 } 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; 426 } 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!"); 434 } 435 436 if (aOutUploadSize) { 437 *aOutUploadSize = 0; 438 } 439 440 if (surfaceFormat == gfx::SurfaceFormat::UNKNOWN) { 441 return gfx::SurfaceFormat::UNKNOWN; 442 } 443 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; 449 } 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); 455 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; 460 } 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; 467 } 468 469 const unsigned char* rectData = 470 aData + DataOffset(rect.TopLeft(), aStride, aFormat); 471 472 rect += aDstOffset; 473 TexSubImage2DHelper(gl, aTextureTarget, 0, rect.X(), rect.Y(), 474 rect.Width(), rect.Height(), aStride, pixelSize, 475 format, type, rectData); 476 } 477 } 478 479 return surfaceFormat; 480 } 481 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(); 494 495 // only fail if we'll need the entire surface for initialization 496 if (aNeedInit && !CheckUploadBounds(aSize, size, aSrcOffset)) { 497 return SurfaceFormat::UNKNOWN; 498 } 499 500 unsigned char* data = map.GetData() + DataOffset(aSrcOffset, stride, format); 501 size.width -= aSrcOffset.x; 502 size.height -= aSrcOffset.y; 503 504 return UploadImageDataToTexture(gl, data, size, aDstOffset, stride, format, 505 aDstRegion, aTexture, aSize, aOutUploadSize, 506 aNeedInit, aTextureUnit, aTextureTarget); 507 } 508 509 bool CanUploadNonPowerOfTwo(GLContext* gl) { 510 if (!gl->WorkAroundDriverBugs()) return true; 511 512 // Some GPUs driver crash when uploading non power of two 565 textures. 513 return gl->Renderer() != GLRenderer::Adreno200 && 514 gl->Renderer() != GLRenderer::Adreno205; 515 } 516 517 } // namespace gl 518 } // namespace mozilla