michael@0: /* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "GLUploadHelpers.h" michael@0: michael@0: #include "GLContext.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "mozilla/gfx/Tools.h" // For BytesPerPixel michael@0: #include "nsRegion.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: using namespace gfx; michael@0: michael@0: namespace gl { michael@0: michael@0: /* These two techniques are suggested by "Bit Twiddling Hacks" michael@0: */ michael@0: michael@0: /** michael@0: * Returns true if |aNumber| is a power of two michael@0: * 0 is incorreclty considered a power of two michael@0: */ michael@0: static bool michael@0: IsPowerOfTwo(int aNumber) michael@0: { michael@0: return (aNumber & (aNumber - 1)) == 0; michael@0: } michael@0: michael@0: /** michael@0: * Returns the first integer greater than |aNumber| which is a power of two michael@0: * Undefined for |aNumber| < 0 michael@0: */ michael@0: static int michael@0: NextPowerOfTwo(int aNumber) michael@0: { michael@0: #if defined(__arm__) michael@0: return 1 << (32 - __builtin_clz(aNumber - 1)); michael@0: #else michael@0: --aNumber; michael@0: aNumber |= aNumber >> 1; michael@0: aNumber |= aNumber >> 2; michael@0: aNumber |= aNumber >> 4; michael@0: aNumber |= aNumber >> 8; michael@0: aNumber |= aNumber >> 16; michael@0: return ++aNumber; michael@0: #endif michael@0: } michael@0: michael@0: static unsigned int michael@0: DataOffset(const nsIntPoint &aPoint, int32_t aStride, SurfaceFormat aFormat) michael@0: { michael@0: unsigned int data = aPoint.y * aStride; michael@0: data += aPoint.x * BytesPerPixel(aFormat); michael@0: return data; michael@0: } michael@0: michael@0: static GLint GetAddressAlignment(ptrdiff_t aAddress) michael@0: { michael@0: if (!(aAddress & 0x7)) { michael@0: return 8; michael@0: } else if (!(aAddress & 0x3)) { michael@0: return 4; michael@0: } else if (!(aAddress & 0x1)) { michael@0: return 2; michael@0: } else { michael@0: return 1; michael@0: } michael@0: } michael@0: michael@0: // Take texture data in a given buffer and copy it into a larger buffer, michael@0: // padding out the edge pixels for filtering if necessary michael@0: static void michael@0: CopyAndPadTextureData(const GLvoid* srcBuffer, michael@0: GLvoid* dstBuffer, michael@0: GLsizei srcWidth, GLsizei srcHeight, michael@0: GLsizei dstWidth, GLsizei dstHeight, michael@0: GLsizei stride, GLint pixelsize) michael@0: { michael@0: unsigned char *rowDest = static_cast(dstBuffer); michael@0: const unsigned char *source = static_cast(srcBuffer); michael@0: michael@0: for (GLsizei h = 0; h < srcHeight; ++h) { michael@0: memcpy(rowDest, source, srcWidth * pixelsize); michael@0: rowDest += dstWidth * pixelsize; michael@0: source += stride; michael@0: } michael@0: michael@0: GLsizei padHeight = srcHeight; michael@0: michael@0: // Pad out an extra row of pixels so that edge filtering doesn't use garbage data michael@0: if (dstHeight > srcHeight) { michael@0: memcpy(rowDest, source - stride, srcWidth * pixelsize); michael@0: padHeight++; michael@0: } michael@0: michael@0: // Pad out an extra column of pixels michael@0: if (dstWidth > srcWidth) { michael@0: rowDest = static_cast(dstBuffer) + srcWidth * pixelsize; michael@0: for (GLsizei h = 0; h < padHeight; ++h) { michael@0: memcpy(rowDest, rowDest - pixelsize, pixelsize); michael@0: rowDest += dstWidth * pixelsize; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // In both of these cases (for the Adreno at least) it is impossible michael@0: // to determine good or bad driver versions for POT texture uploads, michael@0: // so blacklist them all. Newer drivers use a different rendering michael@0: // string in the form "Adreno (TM) 200" and the drivers we've seen so michael@0: // far work fine with NPOT textures, so don't blacklist those until we michael@0: // have evidence of any problems with them. michael@0: bool michael@0: CanUploadSubTextures(GLContext* gl) michael@0: { michael@0: if (!gl->WorkAroundDriverBugs()) michael@0: return true; michael@0: michael@0: // There are certain GPUs that we don't want to use glTexSubImage2D on michael@0: // because that function can be very slow and/or buggy michael@0: if (gl->Renderer() == GLRenderer::Adreno200 || michael@0: gl->Renderer() == GLRenderer::Adreno205) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: // On PowerVR glTexSubImage does a readback, so it will be slower michael@0: // than just doing a glTexImage2D() directly. i.e. 26ms vs 10ms michael@0: if (gl->Renderer() == GLRenderer::SGX540 || michael@0: gl->Renderer() == GLRenderer::SGX530) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: TexSubImage2DWithUnpackSubimageGLES(GLContext* gl, michael@0: GLenum target, GLint level, michael@0: GLint xoffset, GLint yoffset, michael@0: GLsizei width, GLsizei height, michael@0: GLsizei stride, GLint pixelsize, michael@0: GLenum format, GLenum type, michael@0: const GLvoid* pixels) michael@0: { michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, michael@0: std::min(GetAddressAlignment((ptrdiff_t)pixels), michael@0: GetAddressAlignment((ptrdiff_t)stride))); michael@0: // When using GL_UNPACK_ROW_LENGTH, we need to work around a Tegra michael@0: // driver crash where the driver apparently tries to read michael@0: // (stride - width * pixelsize) bytes past the end of the last input michael@0: // row. We only upload the first height-1 rows using GL_UNPACK_ROW_LENGTH, michael@0: // and then we upload the final row separately. See bug 697990. michael@0: int rowLength = stride/pixelsize; michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength); michael@0: gl->fTexSubImage2D(target, michael@0: level, michael@0: xoffset, michael@0: yoffset, michael@0: width, michael@0: height-1, michael@0: format, michael@0: type, michael@0: pixels); michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0); michael@0: gl->fTexSubImage2D(target, michael@0: level, michael@0: xoffset, michael@0: yoffset+height-1, michael@0: width, michael@0: 1, michael@0: format, michael@0: type, michael@0: (const unsigned char *)pixels+(height-1)*stride); michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); michael@0: } michael@0: michael@0: static void michael@0: TexSubImage2DWithoutUnpackSubimage(GLContext* gl, michael@0: GLenum target, GLint level, michael@0: GLint xoffset, GLint yoffset, michael@0: GLsizei width, GLsizei height, michael@0: GLsizei stride, GLint pixelsize, michael@0: GLenum format, GLenum type, michael@0: const GLvoid* pixels) michael@0: { michael@0: // Not using the whole row of texture data and GL_UNPACK_ROW_LENGTH michael@0: // isn't supported. We make a copy of the texture data we're using, michael@0: // such that we're using the whole row of data in the copy. This turns michael@0: // out to be more efficient than uploading row-by-row; see bug 698197. michael@0: unsigned char *newPixels = new unsigned char[width*height*pixelsize]; michael@0: unsigned char *rowDest = newPixels; michael@0: const unsigned char *rowSource = (const unsigned char *)pixels; michael@0: for (int h = 0; h < height; h++) { michael@0: memcpy(rowDest, rowSource, width*pixelsize); michael@0: rowDest += width*pixelsize; michael@0: rowSource += stride; michael@0: } michael@0: michael@0: stride = width*pixelsize; michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, michael@0: std::min(GetAddressAlignment((ptrdiff_t)newPixels), michael@0: GetAddressAlignment((ptrdiff_t)stride))); michael@0: gl->fTexSubImage2D(target, michael@0: level, michael@0: xoffset, michael@0: yoffset, michael@0: width, michael@0: height, michael@0: format, michael@0: type, michael@0: newPixels); michael@0: delete [] newPixels; michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); michael@0: } michael@0: michael@0: static void michael@0: TexSubImage2DHelper(GLContext *gl, michael@0: GLenum target, GLint level, michael@0: GLint xoffset, GLint yoffset, michael@0: GLsizei width, GLsizei height, GLsizei stride, michael@0: GLint pixelsize, GLenum format, michael@0: GLenum type, const GLvoid* pixels) michael@0: { michael@0: if (gl->IsGLES()) { michael@0: if (stride == width * pixelsize) { michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, michael@0: std::min(GetAddressAlignment((ptrdiff_t)pixels), michael@0: GetAddressAlignment((ptrdiff_t)stride))); michael@0: gl->fTexSubImage2D(target, michael@0: level, michael@0: xoffset, michael@0: yoffset, michael@0: width, michael@0: height, michael@0: format, michael@0: type, michael@0: pixels); michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); michael@0: } else if (gl->IsExtensionSupported(GLContext::EXT_unpack_subimage)) { michael@0: TexSubImage2DWithUnpackSubimageGLES(gl, target, level, xoffset, yoffset, michael@0: width, height, stride, michael@0: pixelsize, format, type, pixels); michael@0: michael@0: } else { michael@0: TexSubImage2DWithoutUnpackSubimage(gl, target, level, xoffset, yoffset, michael@0: width, height, stride, michael@0: pixelsize, format, type, pixels); michael@0: } michael@0: } else { michael@0: // desktop GL (non-ES) path michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, michael@0: std::min(GetAddressAlignment((ptrdiff_t)pixels), michael@0: GetAddressAlignment((ptrdiff_t)stride))); michael@0: int rowLength = stride/pixelsize; michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength); michael@0: gl->fTexSubImage2D(target, michael@0: level, michael@0: xoffset, michael@0: yoffset, michael@0: width, michael@0: height, michael@0: format, michael@0: type, michael@0: pixels); michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0); michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: TexImage2DHelper(GLContext *gl, michael@0: GLenum target, GLint level, GLint internalformat, michael@0: GLsizei width, GLsizei height, GLsizei stride, michael@0: GLint pixelsize, GLint border, GLenum format, michael@0: GLenum type, const GLvoid *pixels) michael@0: { michael@0: if (gl->IsGLES()) { michael@0: michael@0: NS_ASSERTION(format == (GLenum)internalformat, michael@0: "format and internalformat not the same for glTexImage2D on GLES2"); michael@0: michael@0: if (!CanUploadNonPowerOfTwo(gl) michael@0: && (stride != width * pixelsize michael@0: || !IsPowerOfTwo(width) michael@0: || !IsPowerOfTwo(height))) { michael@0: michael@0: // Pad out texture width and height to the next power of two michael@0: // as we don't support/want non power of two texture uploads michael@0: GLsizei paddedWidth = NextPowerOfTwo(width); michael@0: GLsizei paddedHeight = NextPowerOfTwo(height); michael@0: michael@0: GLvoid* paddedPixels = new unsigned char[paddedWidth * paddedHeight * pixelsize]; michael@0: michael@0: // Pad out texture data to be in a POT sized buffer for uploading to michael@0: // a POT sized texture michael@0: CopyAndPadTextureData(pixels, paddedPixels, width, height, michael@0: paddedWidth, paddedHeight, stride, pixelsize); michael@0: michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, michael@0: std::min(GetAddressAlignment((ptrdiff_t)paddedPixels), michael@0: GetAddressAlignment((ptrdiff_t)paddedWidth * pixelsize))); michael@0: gl->fTexImage2D(target, michael@0: border, michael@0: internalformat, michael@0: paddedWidth, michael@0: paddedHeight, michael@0: border, michael@0: format, michael@0: type, michael@0: paddedPixels); michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); michael@0: michael@0: delete[] static_cast(paddedPixels); michael@0: return; michael@0: } michael@0: michael@0: if (stride == width * pixelsize) { michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, michael@0: std::min(GetAddressAlignment((ptrdiff_t)pixels), michael@0: GetAddressAlignment((ptrdiff_t)stride))); michael@0: gl->fTexImage2D(target, michael@0: border, michael@0: internalformat, michael@0: width, michael@0: height, michael@0: border, michael@0: format, michael@0: type, michael@0: pixels); michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); michael@0: } else { michael@0: // Use GLES-specific workarounds for GL_UNPACK_ROW_LENGTH; these are michael@0: // implemented in TexSubImage2D. michael@0: gl->fTexImage2D(target, michael@0: border, michael@0: internalformat, michael@0: width, michael@0: height, michael@0: border, michael@0: format, michael@0: type, michael@0: nullptr); michael@0: TexSubImage2DHelper(gl, michael@0: target, michael@0: level, michael@0: 0, michael@0: 0, michael@0: width, michael@0: height, michael@0: stride, michael@0: pixelsize, michael@0: format, michael@0: type, michael@0: pixels); michael@0: } michael@0: } else { michael@0: // desktop GL (non-ES) path michael@0: michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, michael@0: std::min(GetAddressAlignment((ptrdiff_t)pixels), michael@0: GetAddressAlignment((ptrdiff_t)stride))); michael@0: int rowLength = stride/pixelsize; michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength); michael@0: gl->fTexImage2D(target, michael@0: level, michael@0: internalformat, michael@0: width, michael@0: height, michael@0: border, michael@0: format, michael@0: type, michael@0: pixels); michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0); michael@0: gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); michael@0: } michael@0: } michael@0: michael@0: SurfaceFormat michael@0: UploadImageDataToTexture(GLContext* gl, michael@0: unsigned char* aData, michael@0: int32_t aStride, michael@0: SurfaceFormat aFormat, michael@0: const nsIntRegion& aDstRegion, michael@0: GLuint& aTexture, michael@0: bool aOverwrite, michael@0: bool aPixelBuffer, michael@0: GLenum aTextureUnit, michael@0: GLenum aTextureTarget) michael@0: { michael@0: bool textureInited = aOverwrite ? false : true; michael@0: gl->MakeCurrent(); michael@0: gl->fActiveTexture(aTextureUnit); michael@0: michael@0: if (!aTexture) { michael@0: gl->fGenTextures(1, &aTexture); michael@0: gl->fBindTexture(aTextureTarget, aTexture); michael@0: gl->fTexParameteri(aTextureTarget, michael@0: LOCAL_GL_TEXTURE_MIN_FILTER, michael@0: LOCAL_GL_LINEAR); michael@0: gl->fTexParameteri(aTextureTarget, michael@0: LOCAL_GL_TEXTURE_MAG_FILTER, michael@0: LOCAL_GL_LINEAR); michael@0: gl->fTexParameteri(aTextureTarget, michael@0: LOCAL_GL_TEXTURE_WRAP_S, michael@0: LOCAL_GL_CLAMP_TO_EDGE); michael@0: gl->fTexParameteri(aTextureTarget, michael@0: LOCAL_GL_TEXTURE_WRAP_T, michael@0: LOCAL_GL_CLAMP_TO_EDGE); michael@0: textureInited = false; michael@0: } else { michael@0: gl->fBindTexture(aTextureTarget, aTexture); michael@0: } michael@0: michael@0: nsIntRegion paintRegion; michael@0: if (!textureInited) { michael@0: paintRegion = nsIntRegion(aDstRegion.GetBounds()); michael@0: } else { michael@0: paintRegion = aDstRegion; michael@0: } michael@0: michael@0: GLenum format = 0; michael@0: GLenum internalFormat = 0; michael@0: GLenum type = 0; michael@0: int32_t pixelSize = BytesPerPixel(aFormat); michael@0: SurfaceFormat surfaceFormat = gfx::SurfaceFormat::UNKNOWN; michael@0: michael@0: MOZ_ASSERT(gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA || michael@0: gl->GetPreferredARGB32Format() == LOCAL_GL_RGBA); michael@0: switch (aFormat) { michael@0: case SurfaceFormat::B8G8R8A8: michael@0: if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) { michael@0: format = LOCAL_GL_BGRA; michael@0: surfaceFormat = SurfaceFormat::R8G8B8A8; michael@0: type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV; michael@0: } else { michael@0: format = LOCAL_GL_RGBA; michael@0: surfaceFormat = SurfaceFormat::B8G8R8A8; michael@0: type = LOCAL_GL_UNSIGNED_BYTE; michael@0: } michael@0: internalFormat = LOCAL_GL_RGBA; michael@0: break; michael@0: case SurfaceFormat::B8G8R8X8: michael@0: // Treat BGRX surfaces as BGRA except for the surface michael@0: // format used. michael@0: if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) { michael@0: format = LOCAL_GL_BGRA; michael@0: surfaceFormat = SurfaceFormat::R8G8B8X8; michael@0: type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV; michael@0: } else { michael@0: format = LOCAL_GL_RGBA; michael@0: surfaceFormat = SurfaceFormat::B8G8R8X8; michael@0: type = LOCAL_GL_UNSIGNED_BYTE; michael@0: } michael@0: internalFormat = LOCAL_GL_RGBA; michael@0: break; michael@0: case SurfaceFormat::R5G6B5: michael@0: internalFormat = format = LOCAL_GL_RGB; michael@0: type = LOCAL_GL_UNSIGNED_SHORT_5_6_5; michael@0: surfaceFormat = SurfaceFormat::R5G6B5; michael@0: break; michael@0: case SurfaceFormat::A8: michael@0: internalFormat = format = LOCAL_GL_LUMINANCE; michael@0: type = LOCAL_GL_UNSIGNED_BYTE; michael@0: // We don't have a specific luminance shader michael@0: surfaceFormat = SurfaceFormat::A8; michael@0: break; michael@0: default: michael@0: NS_ASSERTION(false, "Unhandled image surface format!"); michael@0: } michael@0: michael@0: nsIntRegionRectIterator iter(paintRegion); michael@0: const nsIntRect *iterRect; michael@0: michael@0: // Top left point of the region's bounding rectangle. michael@0: nsIntPoint topLeft = paintRegion.GetBounds().TopLeft(); michael@0: michael@0: while ((iterRect = iter.Next())) { michael@0: // The inital data pointer is at the top left point of the region's michael@0: // bounding rectangle. We need to find the offset of this rect michael@0: // within the region and adjust the data pointer accordingly. michael@0: unsigned char *rectData = michael@0: aData + DataOffset(iterRect->TopLeft() - topLeft, aStride, aFormat); michael@0: michael@0: NS_ASSERTION(textureInited || (iterRect->x == 0 && iterRect->y == 0), michael@0: "Must be uploading to the origin when we don't have an existing texture"); michael@0: michael@0: if (textureInited && CanUploadSubTextures(gl)) { michael@0: TexSubImage2DHelper(gl, michael@0: aTextureTarget, michael@0: 0, michael@0: iterRect->x, michael@0: iterRect->y, michael@0: iterRect->width, michael@0: iterRect->height, michael@0: aStride, michael@0: pixelSize, michael@0: format, michael@0: type, michael@0: rectData); michael@0: } else { michael@0: TexImage2DHelper(gl, michael@0: aTextureTarget, michael@0: 0, michael@0: internalFormat, michael@0: iterRect->width, michael@0: iterRect->height, michael@0: aStride, michael@0: pixelSize, michael@0: 0, michael@0: format, michael@0: type, michael@0: rectData); michael@0: } michael@0: michael@0: } michael@0: michael@0: return surfaceFormat; michael@0: } michael@0: michael@0: SurfaceFormat michael@0: UploadSurfaceToTexture(GLContext* gl, michael@0: DataSourceSurface *aSurface, michael@0: const nsIntRegion& aDstRegion, michael@0: GLuint& aTexture, michael@0: bool aOverwrite, michael@0: const nsIntPoint& aSrcPoint, michael@0: bool aPixelBuffer, michael@0: GLenum aTextureUnit, michael@0: GLenum aTextureTarget) michael@0: { michael@0: unsigned char* data = aPixelBuffer ? nullptr : aSurface->GetData(); michael@0: int32_t stride = aSurface->Stride(); michael@0: SurfaceFormat format = aSurface->GetFormat(); michael@0: data += DataOffset(aSrcPoint, stride, format); michael@0: return UploadImageDataToTexture(gl, data, stride, format, michael@0: aDstRegion, aTexture, aOverwrite, michael@0: aPixelBuffer, aTextureUnit, michael@0: aTextureTarget); michael@0: } michael@0: michael@0: bool michael@0: CanUploadNonPowerOfTwo(GLContext* gl) michael@0: { michael@0: if (!gl->WorkAroundDriverBugs()) michael@0: return true; michael@0: michael@0: // Some GPUs driver crash when uploading non power of two 565 textures. michael@0: return gl->Renderer() != GLRenderer::Adreno200 && michael@0: gl->Renderer() != GLRenderer::Adreno205; michael@0: } michael@0: michael@0: } michael@0: }