michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=8 sts=4 et sw=4 tw=80: */ 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 "GLReadTexImageHelper.h" michael@0: #include "GLContext.h" michael@0: #include "OGLShaderProgram.h" michael@0: #include "gfxTypes.h" michael@0: #include "gfxContext.h" michael@0: #include "gfxImageSurface.h" michael@0: #include "ScopedGLHelpers.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "gfx2DGlue.h" michael@0: michael@0: using namespace mozilla::gfx; michael@0: michael@0: namespace mozilla { michael@0: namespace gl { michael@0: michael@0: GLReadTexImageHelper::GLReadTexImageHelper(GLContext* gl) michael@0: : mGL(gl) michael@0: { michael@0: mPrograms[0] = 0; michael@0: mPrograms[1] = 0; michael@0: mPrograms[2] = 0; michael@0: mPrograms[3] = 0; michael@0: } michael@0: michael@0: GLReadTexImageHelper::~GLReadTexImageHelper() michael@0: { michael@0: mGL->fDeleteProgram(mPrograms[0]); michael@0: mGL->fDeleteProgram(mPrograms[1]); michael@0: mGL->fDeleteProgram(mPrograms[2]); michael@0: mGL->fDeleteProgram(mPrograms[3]); michael@0: } michael@0: michael@0: static const GLchar michael@0: readTextureImageVS[] = michael@0: "attribute vec2 aVertex;\n" michael@0: "attribute vec2 aTexCoord;\n" michael@0: "varying vec2 vTexCoord;\n" michael@0: "void main() { gl_Position = vec4(aVertex, 0, 1); vTexCoord = aTexCoord; }"; michael@0: michael@0: static const GLchar michael@0: readTextureImageFS_TEXTURE_2D[] = michael@0: "#ifdef GL_ES\n" michael@0: "precision mediump float;\n" michael@0: "#endif\n" michael@0: "varying vec2 vTexCoord;\n" michael@0: "uniform sampler2D uTexture;\n" michael@0: "void main() { gl_FragColor = texture2D(uTexture, vTexCoord); }"; michael@0: michael@0: michael@0: static const GLchar michael@0: readTextureImageFS_TEXTURE_2D_BGRA[] = michael@0: "#ifdef GL_ES\n" michael@0: "precision mediump float;\n" michael@0: "#endif\n" michael@0: "varying vec2 vTexCoord;\n" michael@0: "uniform sampler2D uTexture;\n" michael@0: "void main() { gl_FragColor = texture2D(uTexture, vTexCoord).bgra; }"; michael@0: michael@0: static const GLchar michael@0: readTextureImageFS_TEXTURE_EXTERNAL[] = michael@0: "#extension GL_OES_EGL_image_external : require\n" michael@0: "#ifdef GL_ES\n" michael@0: "precision mediump float;\n" michael@0: "#endif\n" michael@0: "varying vec2 vTexCoord;\n" michael@0: "uniform samplerExternalOES uTexture;\n" michael@0: "void main() { gl_FragColor = texture2D(uTexture, vTexCoord); }"; michael@0: michael@0: static const GLchar michael@0: readTextureImageFS_TEXTURE_RECTANGLE[] = michael@0: "#extension GL_ARB_texture_rectangle\n" michael@0: "#ifdef GL_ES\n" michael@0: "precision mediump float;\n" michael@0: "#endif\n" michael@0: "varying vec2 vTexCoord;\n" michael@0: "uniform sampler2DRect uTexture;\n" michael@0: "void main() { gl_FragColor = texture2DRect(uTexture, vTexCoord).bgra; }"; michael@0: michael@0: GLuint michael@0: GLReadTexImageHelper::TextureImageProgramFor(GLenum aTextureTarget, int aConfig) { michael@0: int variant = 0; michael@0: const GLchar* readTextureImageFS = nullptr; michael@0: if (aTextureTarget == LOCAL_GL_TEXTURE_2D) michael@0: { michael@0: if (aConfig & mozilla::layers::ENABLE_TEXTURE_RB_SWAP) michael@0: { // Need to swizzle R/B. michael@0: readTextureImageFS = readTextureImageFS_TEXTURE_2D_BGRA; michael@0: variant = 1; michael@0: } michael@0: else michael@0: { michael@0: readTextureImageFS = readTextureImageFS_TEXTURE_2D; michael@0: variant = 0; michael@0: } michael@0: } else if (aTextureTarget == LOCAL_GL_TEXTURE_EXTERNAL) { michael@0: readTextureImageFS = readTextureImageFS_TEXTURE_EXTERNAL; michael@0: variant = 2; michael@0: } else if (aTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE) { michael@0: readTextureImageFS = readTextureImageFS_TEXTURE_RECTANGLE; michael@0: variant = 3; michael@0: } michael@0: michael@0: /* This might be overkill, but assure that we don't access out-of-bounds */ michael@0: MOZ_ASSERT((size_t) variant < ArrayLength(mPrograms)); michael@0: if (!mPrograms[variant]) { michael@0: GLuint vs = mGL->fCreateShader(LOCAL_GL_VERTEX_SHADER); michael@0: const GLchar* vsSourcePtr = &readTextureImageVS[0]; michael@0: mGL->fShaderSource(vs, 1, &vsSourcePtr, nullptr); michael@0: mGL->fCompileShader(vs); michael@0: michael@0: GLuint fs = mGL->fCreateShader(LOCAL_GL_FRAGMENT_SHADER); michael@0: mGL->fShaderSource(fs, 1, &readTextureImageFS, nullptr); michael@0: mGL->fCompileShader(fs); michael@0: michael@0: GLuint program = mGL->fCreateProgram(); michael@0: mGL->fAttachShader(program, vs); michael@0: mGL->fAttachShader(program, fs); michael@0: mGL->fBindAttribLocation(program, 0, "aVertex"); michael@0: mGL->fBindAttribLocation(program, 1, "aTexCoord"); michael@0: mGL->fLinkProgram(program); michael@0: michael@0: GLint success; michael@0: mGL->fGetProgramiv(program, LOCAL_GL_LINK_STATUS, &success); michael@0: michael@0: if (!success) { michael@0: mGL->fDeleteProgram(program); michael@0: program = 0; michael@0: } michael@0: michael@0: mGL->fDeleteShader(vs); michael@0: mGL->fDeleteShader(fs); michael@0: michael@0: mPrograms[variant] = program; michael@0: } michael@0: michael@0: return mPrograms[variant]; michael@0: } michael@0: michael@0: bool michael@0: GLReadTexImageHelper::DidGLErrorOccur(const char* str) michael@0: { michael@0: GLenum error = mGL->fGetError(); michael@0: if (error != LOCAL_GL_NO_ERROR) { michael@0: printf_stderr("GL ERROR: %s (0x%04x) %s\n", michael@0: mGL->GLErrorToString(error), error, str); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: GetActualReadFormats(GLContext* gl, michael@0: GLenum destFormat, GLenum destType, michael@0: GLenum& readFormat, GLenum& readType) michael@0: { michael@0: if (destFormat == LOCAL_GL_RGBA && michael@0: destType == LOCAL_GL_UNSIGNED_BYTE) michael@0: { michael@0: readFormat = destFormat; michael@0: readType = destType; michael@0: return true; michael@0: } michael@0: michael@0: bool fallback = true; michael@0: if (gl->IsGLES()) { michael@0: GLenum auxFormat = 0; michael@0: GLenum auxType = 0; michael@0: michael@0: gl->fGetIntegerv(LOCAL_GL_IMPLEMENTATION_COLOR_READ_FORMAT, (GLint*)&auxFormat); michael@0: gl->fGetIntegerv(LOCAL_GL_IMPLEMENTATION_COLOR_READ_TYPE, (GLint*)&auxType); michael@0: michael@0: if (destFormat == auxFormat && michael@0: destType == auxType) michael@0: { michael@0: fallback = false; michael@0: } michael@0: } else { michael@0: switch (destFormat) { michael@0: case LOCAL_GL_RGB: { michael@0: if (destType == LOCAL_GL_UNSIGNED_SHORT_5_6_5_REV) michael@0: fallback = false; michael@0: break; michael@0: } michael@0: case LOCAL_GL_BGRA: { michael@0: if (destType == LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV) michael@0: fallback = false; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (fallback) { michael@0: readFormat = LOCAL_GL_RGBA; michael@0: readType = LOCAL_GL_UNSIGNED_BYTE; michael@0: return false; michael@0: } else { michael@0: readFormat = destFormat; michael@0: readType = destType; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: static void SwapRAndBComponents(DataSourceSurface* surf) michael@0: { michael@0: uint8_t *row = surf->GetData(); michael@0: if (!row) { michael@0: MOZ_ASSERT(false, "SwapRAndBComponents: Failed to get data from DataSourceSurface."); michael@0: return; michael@0: } michael@0: michael@0: size_t rowBytes = surf->GetSize().width*4; michael@0: size_t rowHole = surf->Stride() - rowBytes; michael@0: michael@0: size_t rows = surf->GetSize().height; michael@0: michael@0: while (rows) { michael@0: michael@0: const uint8_t *rowEnd = row + rowBytes; michael@0: michael@0: while (row != rowEnd) { michael@0: row[0] ^= row[2]; michael@0: row[2] ^= row[0]; michael@0: row[0] ^= row[2]; michael@0: row += 4; michael@0: } michael@0: michael@0: row += rowHole; michael@0: --rows; michael@0: } michael@0: } michael@0: michael@0: static uint16_t PackRGB565(uint8_t r, uint8_t g, uint8_t b) michael@0: { michael@0: uint16_t pixel = ((r << 11) & 0xf800) | michael@0: ((g << 5) & 0x07e0) | michael@0: ((b ) & 0x001f); michael@0: michael@0: return pixel; michael@0: } michael@0: michael@0: static void CopyDataSourceSurface(DataSourceSurface* aSource, michael@0: DataSourceSurface* aDest) michael@0: { michael@0: MOZ_ASSERT(aSource->GetSize() == aDest->GetSize()); michael@0: MOZ_ASSERT(aSource->GetFormat() == SurfaceFormat::R8G8B8A8 || michael@0: aSource->GetFormat() == SurfaceFormat::R8G8B8X8); michael@0: michael@0: uint8_t *srcRow = aSource->GetData(); michael@0: size_t srcRowBytes = aSource->GetSize().width * BytesPerPixel(aSource->GetFormat()); michael@0: size_t srcRowHole = aSource->Stride() - srcRowBytes; michael@0: michael@0: uint8_t *destRow = aDest->GetData(); michael@0: size_t destRowBytes = aDest->GetSize().width * BytesPerPixel(aDest->GetFormat()); michael@0: size_t destRowHole = aDest->Stride() - destRowBytes; michael@0: michael@0: bool needsRBSwap = false; michael@0: if (aDest->GetFormat() == SurfaceFormat::B8G8R8A8 || michael@0: aDest->GetFormat() == SurfaceFormat::B8G8R8X8 || michael@0: aDest->GetFormat() == SurfaceFormat::R5G6B5) { michael@0: needsRBSwap = true; michael@0: } michael@0: michael@0: bool needsConvertTo16Bits = false; michael@0: if (aDest->GetFormat() == SurfaceFormat::R5G6B5) { michael@0: needsConvertTo16Bits = true; michael@0: } michael@0: michael@0: size_t rows = aSource->GetSize().height; michael@0: michael@0: while (rows) { michael@0: const uint8_t *srcRowEnd = srcRow + srcRowBytes; michael@0: michael@0: while (srcRow != srcRowEnd) { michael@0: uint8_t r = needsRBSwap ? srcRow[2] : srcRow[0]; michael@0: uint8_t g = srcRow[1]; michael@0: uint8_t b = needsRBSwap ? srcRow[0] : srcRow[2]; michael@0: uint8_t a = srcRow[3]; michael@0: michael@0: if (needsConvertTo16Bits) { michael@0: *(uint16_t*)destRow = PackRGB565(r, g, b); michael@0: } else { michael@0: destRow[0] = r; michael@0: destRow[1] = g; michael@0: destRow[2] = b; michael@0: destRow[3] = a; michael@0: } michael@0: srcRow += BytesPerPixel(aSource->GetFormat()); michael@0: destRow += BytesPerPixel(aDest->GetFormat()); michael@0: } michael@0: michael@0: srcRow += srcRowHole; michael@0: destRow += destRowHole; michael@0: --rows; michael@0: } michael@0: } michael@0: michael@0: static int michael@0: CalcStride(int width, int pixelSize, int alignment) michael@0: { michael@0: MOZ_ASSERT(alignment); michael@0: michael@0: int stride = width * pixelSize; michael@0: if (stride % alignment) { // Extra at the end of the line? michael@0: int alignmentCount = stride / alignment; michael@0: stride = (alignmentCount+1) * alignment; michael@0: } michael@0: return stride; michael@0: } michael@0: michael@0: static int michael@0: GuessAlignment(int width, int pixelSize, int stride) michael@0: { michael@0: int alignment = 8; // Max GLES allows. michael@0: while (CalcStride(width, pixelSize, alignment) != stride) { michael@0: alignment /= 2; michael@0: if (!alignment) { michael@0: MOZ_ASSERT(alignment); michael@0: return 1; michael@0: } michael@0: } michael@0: return alignment; michael@0: } michael@0: michael@0: void michael@0: ReadPixelsIntoImageSurface(GLContext* gl, gfxImageSurface* dest) { michael@0: gl->MakeCurrent(); michael@0: MOZ_ASSERT(dest->GetSize() != gfxIntSize(0, 0)); michael@0: michael@0: /* gfxImageFormat::ARGB32: michael@0: * RGBA+UByte: be[RGBA], le[ABGR] michael@0: * RGBA+UInt: be[ABGR], le[RGBA] michael@0: * BGRA+UInt: be[ARGB], le[BGRA] michael@0: * BGRA+UIntRev: be[BGRA], le[ARGB] michael@0: * michael@0: * gfxImageFormat::RGB16_565: michael@0: * RGB+UShort: le[rrrrrggg,gggbbbbb] michael@0: */ michael@0: bool hasAlpha = dest->Format() == gfxImageFormat::ARGB32; michael@0: michael@0: int destPixelSize; michael@0: GLenum destFormat; michael@0: GLenum destType; michael@0: michael@0: switch (dest->Format()) { michael@0: case gfxImageFormat::RGB24: // XRGB michael@0: case gfxImageFormat::ARGB32: michael@0: destPixelSize = 4; michael@0: // Needs host (little) endian ARGB. michael@0: destFormat = LOCAL_GL_BGRA; michael@0: destType = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV; michael@0: break; michael@0: michael@0: case gfxImageFormat::RGB16_565: michael@0: destPixelSize = 2; michael@0: destFormat = LOCAL_GL_RGB; michael@0: destType = LOCAL_GL_UNSIGNED_SHORT_5_6_5_REV; michael@0: break; michael@0: michael@0: default: michael@0: MOZ_CRASH("Bad format."); michael@0: } michael@0: MOZ_ASSERT(dest->Width() * destPixelSize <= dest->Stride()); michael@0: michael@0: GLenum readFormat = destFormat; michael@0: GLenum readType = destType; michael@0: bool needsTempSurf = !GetActualReadFormats(gl, michael@0: destFormat, destType, michael@0: readFormat, readType); michael@0: michael@0: nsAutoPtr tempSurf; michael@0: gfxImageSurface* readSurf = nullptr; michael@0: int readAlignment = 0; michael@0: if (needsTempSurf) { michael@0: if (gl->DebugMode()) { michael@0: NS_WARNING("Needing intermediary surface for ReadPixels. This will be slow!"); michael@0: } michael@0: SurfaceFormat readFormatGFX; michael@0: michael@0: switch (readFormat) { michael@0: case LOCAL_GL_RGBA: michael@0: case LOCAL_GL_BGRA: { michael@0: readFormatGFX = hasAlpha ? SurfaceFormat::B8G8R8A8 michael@0: : SurfaceFormat::B8G8R8X8; michael@0: break; michael@0: } michael@0: case LOCAL_GL_RGB: { michael@0: MOZ_ASSERT(destPixelSize == 2); michael@0: MOZ_ASSERT(readType == LOCAL_GL_UNSIGNED_SHORT_5_6_5_REV); michael@0: readFormatGFX = SurfaceFormat::R5G6B5; michael@0: break; michael@0: } michael@0: default: { michael@0: MOZ_CRASH("Bad read format."); michael@0: } michael@0: } michael@0: michael@0: switch (readType) { michael@0: case LOCAL_GL_UNSIGNED_BYTE: { michael@0: MOZ_ASSERT(readFormat == LOCAL_GL_RGBA); michael@0: readAlignment = 1; michael@0: break; michael@0: } michael@0: case LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV: { michael@0: MOZ_ASSERT(readFormat == LOCAL_GL_BGRA); michael@0: readAlignment = 4; michael@0: break; michael@0: } michael@0: case LOCAL_GL_UNSIGNED_SHORT_5_6_5_REV: { michael@0: MOZ_ASSERT(readFormat == LOCAL_GL_RGB); michael@0: readAlignment = 2; michael@0: break; michael@0: } michael@0: default: { michael@0: MOZ_CRASH("Bad read type."); michael@0: } michael@0: } michael@0: michael@0: tempSurf = new gfxImageSurface(dest->GetSize(), michael@0: SurfaceFormatToImageFormat(readFormatGFX), michael@0: false); michael@0: readSurf = tempSurf; michael@0: } else { michael@0: // Figure out alignment. We don't need to know why, we just need it michael@0: // to be valid. michael@0: readAlignment = GuessAlignment(dest->Width(), michael@0: destPixelSize, michael@0: dest->Stride()); michael@0: readSurf = dest; michael@0: } michael@0: MOZ_ASSERT(readAlignment); michael@0: michael@0: GLint currentPackAlignment = 0; michael@0: gl->fGetIntegerv(LOCAL_GL_PACK_ALIGNMENT, ¤tPackAlignment); michael@0: michael@0: if (currentPackAlignment != readAlignment) michael@0: gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, readAlignment); michael@0: michael@0: GLsizei width = dest->Width(); michael@0: GLsizei height = dest->Height(); michael@0: michael@0: readSurf->Flush(); michael@0: gl->fReadPixels(0, 0, michael@0: width, height, michael@0: readFormat, readType, michael@0: readSurf->Data()); michael@0: readSurf->MarkDirty(); michael@0: michael@0: if (currentPackAlignment != readAlignment) michael@0: gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, currentPackAlignment); michael@0: michael@0: if (readSurf != dest) { michael@0: MOZ_ASSERT(readFormat == LOCAL_GL_RGBA); michael@0: MOZ_ASSERT(readType == LOCAL_GL_UNSIGNED_BYTE); michael@0: // So we just copied in RGBA in big endian, or le: 0xAABBGGRR. michael@0: // We want 0xAARRGGBB, so swap R and B: michael@0: dest->Flush(); michael@0: RefPtr readDSurf = michael@0: Factory::CreateWrappingDataSourceSurface(readSurf->Data(), michael@0: readSurf->Stride(), michael@0: ToIntSize(readSurf->GetSize()), michael@0: ImageFormatToSurfaceFormat(readSurf->Format())); michael@0: SwapRAndBComponents(readDSurf); michael@0: dest->MarkDirty(); michael@0: michael@0: gfxContext ctx(dest); michael@0: ctx.SetOperator(gfxContext::OPERATOR_SOURCE); michael@0: ctx.SetSource(readSurf); michael@0: ctx.Paint(); michael@0: } michael@0: michael@0: // Check if GL is giving back 1.0 alpha for michael@0: // RGBA reads to RGBA images from no-alpha buffers. michael@0: #ifdef XP_MACOSX michael@0: if (gl->WorkAroundDriverBugs() && michael@0: gl->Vendor() == gl::GLVendor::NVIDIA && michael@0: dest->Format() == gfxImageFormat::ARGB32 && michael@0: width && height) michael@0: { michael@0: GLint alphaBits = 0; michael@0: gl->fGetIntegerv(LOCAL_GL_ALPHA_BITS, &alphaBits); michael@0: if (!alphaBits) { michael@0: const uint32_t alphaMask = gfxPackedPixelNoPreMultiply(0xff,0,0,0); michael@0: michael@0: MOZ_ASSERT(dest->Width() * destPixelSize == dest->Stride()); michael@0: michael@0: dest->Flush(); michael@0: uint32_t* itr = (uint32_t*)dest->Data(); michael@0: uint32_t testPixel = *itr; michael@0: if ((testPixel & alphaMask) != alphaMask) { michael@0: // We need to set the alpha channel to 1.0 manually. michael@0: uint32_t* itrEnd = itr + width*height; // Stride is guaranteed to be width*4. michael@0: michael@0: for (; itr != itrEnd; itr++) { michael@0: *itr |= alphaMask; michael@0: } michael@0: } michael@0: dest->MarkDirty(); michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: ReadPixelsIntoDataSourceSurface(GLContext* gl, DataSourceSurface* dest) { michael@0: gl->MakeCurrent(); michael@0: MOZ_ASSERT(dest->GetSize().width != 0); michael@0: MOZ_ASSERT(dest->GetSize().height != 0); michael@0: michael@0: bool hasAlpha = dest->GetFormat() == SurfaceFormat::B8G8R8A8 || michael@0: dest->GetFormat() == SurfaceFormat::R8G8B8A8; michael@0: michael@0: int destPixelSize; michael@0: GLenum destFormat; michael@0: GLenum destType; michael@0: michael@0: switch (dest->GetFormat()) { michael@0: case SurfaceFormat::B8G8R8A8: michael@0: case SurfaceFormat::B8G8R8X8: michael@0: // Needs host (little) endian ARGB. michael@0: destFormat = LOCAL_GL_BGRA; michael@0: destType = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV; michael@0: break; michael@0: case SurfaceFormat::R8G8B8A8: michael@0: case SurfaceFormat::R8G8B8X8: michael@0: // Needs host (little) endian ABGR. michael@0: destFormat = LOCAL_GL_RGBA; michael@0: destType = LOCAL_GL_UNSIGNED_BYTE; michael@0: break; michael@0: case SurfaceFormat::R5G6B5: michael@0: destFormat = LOCAL_GL_RGB; michael@0: destType = LOCAL_GL_UNSIGNED_SHORT_5_6_5_REV; michael@0: break; michael@0: default: michael@0: MOZ_CRASH("Bad format."); michael@0: } michael@0: destPixelSize = BytesPerPixel(dest->GetFormat()); michael@0: MOZ_ASSERT(dest->GetSize().width * destPixelSize <= dest->Stride()); michael@0: michael@0: GLenum readFormat = destFormat; michael@0: GLenum readType = destType; michael@0: bool needsTempSurf = !GetActualReadFormats(gl, michael@0: destFormat, destType, michael@0: readFormat, readType); michael@0: michael@0: RefPtr tempSurf; michael@0: DataSourceSurface* readSurf = nullptr; michael@0: int readAlignment = 0; michael@0: if (needsTempSurf) { michael@0: if (gl->DebugMode()) { michael@0: NS_WARNING("Needing intermediary surface for ReadPixels. This will be slow!"); michael@0: } michael@0: SurfaceFormat readFormatGFX; michael@0: michael@0: // If needs temp surface, readFormat is always LOCAL_GL_RGBA michael@0: // and readType is always LOCAL_GL_UNSIGNED_BYTE michael@0: MOZ_ASSERT(readFormat == LOCAL_GL_RGBA); michael@0: MOZ_ASSERT(readType == LOCAL_GL_UNSIGNED_BYTE); michael@0: readFormatGFX = hasAlpha ? SurfaceFormat::R8G8B8A8 michael@0: : SurfaceFormat::R8G8B8X8; michael@0: readAlignment = 1; michael@0: int32_t stride = dest->GetSize().width * BytesPerPixel(readFormatGFX); michael@0: tempSurf = Factory::CreateDataSourceSurfaceWithStride(dest->GetSize(), michael@0: readFormatGFX, michael@0: stride); michael@0: readSurf = tempSurf; michael@0: } else { michael@0: // Figure out alignment. We don't need to know why, we just need it michael@0: // to be valid. michael@0: readAlignment = GuessAlignment(dest->GetSize().width, michael@0: destPixelSize, michael@0: dest->Stride()); michael@0: readSurf = dest; michael@0: } michael@0: MOZ_ASSERT(readAlignment); michael@0: MOZ_ASSERT(reinterpret_cast(readSurf->GetData()) % readAlignment == 0); michael@0: michael@0: GLint currentPackAlignment = 0; michael@0: gl->fGetIntegerv(LOCAL_GL_PACK_ALIGNMENT, ¤tPackAlignment); michael@0: michael@0: if (currentPackAlignment != readAlignment) michael@0: gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, readAlignment); michael@0: michael@0: GLsizei width = dest->GetSize().width; michael@0: GLsizei height = dest->GetSize().height; michael@0: michael@0: gl->fReadPixels(0, 0, michael@0: width, height, michael@0: readFormat, readType, michael@0: readSurf->GetData()); michael@0: michael@0: if (currentPackAlignment != readAlignment) michael@0: gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, currentPackAlignment); michael@0: michael@0: if (readSurf != dest) { michael@0: MOZ_ASSERT(readFormat == LOCAL_GL_RGBA); michael@0: MOZ_ASSERT(readType == LOCAL_GL_UNSIGNED_BYTE); michael@0: CopyDataSourceSurface(readSurf, dest); michael@0: } michael@0: michael@0: // Check if GL is giving back 1.0 alpha for michael@0: // RGBA reads to RGBA images from no-alpha buffers. michael@0: #ifdef XP_MACOSX michael@0: if (gl->WorkAroundDriverBugs() && michael@0: gl->Vendor() == gl::GLVendor::NVIDIA && michael@0: (dest->GetFormat() == SurfaceFormat::R8G8B8A8 || michael@0: dest->GetFormat() == SurfaceFormat::B8G8R8A8) && michael@0: width && height) michael@0: { michael@0: GLint alphaBits = 0; michael@0: gl->fGetIntegerv(LOCAL_GL_ALPHA_BITS, &alphaBits); michael@0: if (!alphaBits) { michael@0: const uint32_t alphaMask = gfxPackedPixelNoPreMultiply(0xff,0,0,0); michael@0: michael@0: MOZ_ASSERT(dest->GetSize().width * destPixelSize == dest->Stride()); michael@0: michael@0: uint32_t* itr = (uint32_t*)dest->GetData(); michael@0: uint32_t testPixel = *itr; michael@0: if ((testPixel & alphaMask) != alphaMask) { michael@0: // We need to set the alpha channel to 1.0 manually. michael@0: uint32_t* itrEnd = itr + width*height; // Stride is guaranteed to be width*4. michael@0: michael@0: for (; itr != itrEnd; itr++) { michael@0: *itr |= alphaMask; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: static TemporaryRef YInvertImageSurface(DataSourceSurface* aSurf) michael@0: { michael@0: RefPtr temp = michael@0: Factory::CreateDataSourceSurfaceWithStride(aSurf->GetSize(), michael@0: aSurf->GetFormat(), michael@0: aSurf->Stride()); michael@0: RefPtr dt = michael@0: Factory::CreateDrawTargetForData(BackendType::CAIRO, michael@0: temp->GetData(), michael@0: temp->GetSize(), michael@0: temp->Stride(), michael@0: temp->GetFormat()); michael@0: nsRefPtr ctx = new gfxContext(dt); michael@0: ctx->SetOperator(gfxContext::OPERATOR_SOURCE); michael@0: ctx->Scale(1.0, -1.0); michael@0: ctx->Translate(-gfxPoint(0.0, aSurf->GetSize().height)); michael@0: michael@0: nsRefPtr thebesSurf = michael@0: new gfxImageSurface(aSurf->GetData(), michael@0: ThebesIntSize(aSurf->GetSize()), michael@0: aSurf->Stride(), michael@0: SurfaceFormatToImageFormat(aSurf->GetFormat())); michael@0: ctx->SetSource(thebesSurf); michael@0: ctx->Paint(); michael@0: return temp.forget(); michael@0: } michael@0: michael@0: TemporaryRef michael@0: ReadBackSurface(GLContext* gl, GLuint aTexture, bool aYInvert, SurfaceFormat aFormat) michael@0: { michael@0: gl->MakeCurrent(); michael@0: gl->GuaranteeResolve(); michael@0: gl->fActiveTexture(LOCAL_GL_TEXTURE0); michael@0: gl->fBindTexture(LOCAL_GL_TEXTURE_2D, aTexture); michael@0: michael@0: IntSize size; michael@0: gl->fGetTexLevelParameteriv(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_TEXTURE_WIDTH, &size.width); michael@0: gl->fGetTexLevelParameteriv(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_TEXTURE_HEIGHT, &size.height); michael@0: michael@0: RefPtr surf = michael@0: Factory::CreateDataSourceSurfaceWithStride(size, SurfaceFormat::B8G8R8A8, michael@0: GetAlignedStride<4>(size.width * BytesPerPixel(SurfaceFormat::B8G8R8A8))); michael@0: michael@0: if (!surf) { michael@0: return nullptr; michael@0: } michael@0: michael@0: uint32_t currentPackAlignment = 0; michael@0: gl->fGetIntegerv(LOCAL_GL_PACK_ALIGNMENT, (GLint*)¤tPackAlignment); michael@0: if (currentPackAlignment != 4) { michael@0: gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4); michael@0: } michael@0: gl->fGetTexImage(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, surf->GetData()); michael@0: if (currentPackAlignment != 4) { michael@0: gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, currentPackAlignment); michael@0: } michael@0: michael@0: if (aFormat == SurfaceFormat::R8G8B8A8 || aFormat == SurfaceFormat::R8G8B8X8) { michael@0: SwapRAndBComponents(surf); michael@0: } michael@0: michael@0: if (aYInvert) { michael@0: surf = YInvertImageSurface(surf); michael@0: } michael@0: michael@0: return surf.forget(); michael@0: } michael@0: michael@0: void michael@0: ReadScreenIntoImageSurface(GLContext* gl, gfxImageSurface* dest) michael@0: { michael@0: ScopedBindFramebuffer autoFB(gl, 0); michael@0: ReadPixelsIntoImageSurface(gl, dest); michael@0: } michael@0: michael@0: michael@0: #define CLEANUP_IF_GLERROR_OCCURRED(x) \ michael@0: if (DidGLErrorOccur(x)) { \ michael@0: isurf = nullptr; \ michael@0: break; \ michael@0: } michael@0: michael@0: TemporaryRef michael@0: GLReadTexImageHelper::ReadTexImage(GLuint aTextureId, michael@0: GLenum aTextureTarget, michael@0: const gfx::IntSize& aSize, michael@0: /* ShaderConfigOGL.mFeature */ int aConfig, michael@0: bool aYInvert) michael@0: { michael@0: MOZ_ASSERT(aTextureTarget == LOCAL_GL_TEXTURE_2D || michael@0: aTextureTarget == LOCAL_GL_TEXTURE_EXTERNAL || michael@0: aTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB); michael@0: michael@0: mGL->MakeCurrent(); michael@0: michael@0: /* Allocate resulting image surface */ michael@0: int32_t stride = aSize.width * BytesPerPixel(SurfaceFormat::R8G8B8A8); michael@0: RefPtr isurf = michael@0: Factory::CreateDataSourceSurfaceWithStride(aSize, michael@0: SurfaceFormat::R8G8B8A8, michael@0: stride); michael@0: michael@0: GLint oldrb, oldfb, oldprog, oldTexUnit, oldTex; michael@0: GLuint rb, fb; michael@0: michael@0: do { michael@0: mGL->fGetIntegerv(LOCAL_GL_RENDERBUFFER_BINDING, &oldrb); michael@0: mGL->fGetIntegerv(LOCAL_GL_FRAMEBUFFER_BINDING, &oldfb); michael@0: mGL->fGetIntegerv(LOCAL_GL_CURRENT_PROGRAM, &oldprog); michael@0: mGL->fGetIntegerv(LOCAL_GL_ACTIVE_TEXTURE, &oldTexUnit); michael@0: mGL->fActiveTexture(LOCAL_GL_TEXTURE0); michael@0: switch (aTextureTarget) { michael@0: case LOCAL_GL_TEXTURE_2D: michael@0: mGL->fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &oldTex); michael@0: break; michael@0: case LOCAL_GL_TEXTURE_EXTERNAL: michael@0: mGL->fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_EXTERNAL, &oldTex); michael@0: break; michael@0: case LOCAL_GL_TEXTURE_RECTANGLE: michael@0: mGL->fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_RECTANGLE, &oldTex); michael@0: break; michael@0: default: /* Already checked above */ michael@0: break; michael@0: } michael@0: michael@0: ScopedGLState scopedScissorTestState(mGL, LOCAL_GL_SCISSOR_TEST, false); michael@0: ScopedGLState scopedBlendState(mGL, LOCAL_GL_BLEND, false); michael@0: ScopedViewportRect scopedViewportRect(mGL, 0, 0, aSize.width, aSize.height); michael@0: michael@0: /* Setup renderbuffer */ michael@0: mGL->fGenRenderbuffers(1, &rb); michael@0: mGL->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, rb); michael@0: michael@0: GLenum rbInternalFormat = michael@0: mGL->IsGLES() michael@0: ? (mGL->IsExtensionSupported(GLContext::OES_rgb8_rgba8) ? LOCAL_GL_RGBA8 : LOCAL_GL_RGBA4) michael@0: : LOCAL_GL_RGBA; michael@0: mGL->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, rbInternalFormat, aSize.width, aSize.height); michael@0: CLEANUP_IF_GLERROR_OCCURRED("when binding and creating renderbuffer"); michael@0: michael@0: /* Setup framebuffer */ michael@0: mGL->fGenFramebuffers(1, &fb); michael@0: mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb); michael@0: mGL->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, michael@0: LOCAL_GL_RENDERBUFFER, rb); michael@0: CLEANUP_IF_GLERROR_OCCURRED("when binding and creating framebuffer"); michael@0: michael@0: MOZ_ASSERT(mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER) == LOCAL_GL_FRAMEBUFFER_COMPLETE); michael@0: michael@0: /* Setup vertex and fragment shader */ michael@0: GLuint program = TextureImageProgramFor(aTextureTarget, aConfig); michael@0: MOZ_ASSERT(program); michael@0: michael@0: mGL->fUseProgram(program); michael@0: CLEANUP_IF_GLERROR_OCCURRED("when using program"); michael@0: mGL->fUniform1i(mGL->fGetUniformLocation(program, "uTexture"), 0); michael@0: CLEANUP_IF_GLERROR_OCCURRED("when setting uniform location"); michael@0: michael@0: /* Setup quad geometry */ michael@0: mGL->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); michael@0: mGL->fEnableVertexAttribArray(0); michael@0: mGL->fEnableVertexAttribArray(1); michael@0: michael@0: float w = (aTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE) ? (float) aSize.width : 1.0f; michael@0: float h = (aTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE) ? (float) aSize.height : 1.0f; michael@0: michael@0: michael@0: const float michael@0: vertexArray[4*2] = { michael@0: -1.0f, -1.0f, michael@0: 1.0f, -1.0f, michael@0: -1.0f, 1.0f, michael@0: 1.0f, 1.0f michael@0: }; michael@0: mGL->fVertexAttribPointer(0, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, vertexArray); michael@0: michael@0: const float u0 = 0.0f; michael@0: const float u1 = w; michael@0: const float v0 = aYInvert ? h : 0.0f; michael@0: const float v1 = aYInvert ? 0.0f : h; michael@0: const float texCoordArray[8] = { u0, v0, michael@0: u1, v0, michael@0: u0, v1, michael@0: u1, v1 }; michael@0: mGL->fVertexAttribPointer(1, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, texCoordArray); michael@0: michael@0: /* Bind the texture */ michael@0: if (aTextureId) { michael@0: mGL->fBindTexture(aTextureTarget, aTextureId); michael@0: CLEANUP_IF_GLERROR_OCCURRED("when binding texture"); michael@0: } michael@0: michael@0: /* Draw quad */ michael@0: mGL->fClearColor(1.0f, 0.0f, 1.0f, 1.0f); michael@0: mGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT); michael@0: CLEANUP_IF_GLERROR_OCCURRED("when clearing color buffer"); michael@0: michael@0: mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4); michael@0: CLEANUP_IF_GLERROR_OCCURRED("when drawing texture"); michael@0: michael@0: mGL->fDisableVertexAttribArray(1); michael@0: mGL->fDisableVertexAttribArray(0); michael@0: michael@0: /* Read-back draw results */ michael@0: ReadPixelsIntoDataSourceSurface(mGL, isurf); michael@0: CLEANUP_IF_GLERROR_OCCURRED("when reading pixels into surface"); michael@0: } while (false); michael@0: michael@0: /* Restore GL state */ michael@0: //cleanup: michael@0: mGL->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, oldrb); michael@0: mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, oldfb); michael@0: mGL->fUseProgram(oldprog); michael@0: michael@0: // note that deleting 0 has no effect in any of these calls michael@0: mGL->fDeleteRenderbuffers(1, &rb); michael@0: mGL->fDeleteFramebuffers(1, &fb); michael@0: michael@0: if (aTextureId) michael@0: mGL->fBindTexture(aTextureTarget, oldTex); michael@0: michael@0: if (oldTexUnit != LOCAL_GL_TEXTURE0) michael@0: mGL->fActiveTexture(oldTexUnit); michael@0: michael@0: return isurf.forget(); michael@0: } michael@0: michael@0: #undef CLEANUP_IF_GLERROR_OCCURRED michael@0: michael@0: michael@0: } michael@0: }