gecko/gfx/gl/GLBlitHelper.cpp
Wes Kocher 8f849d9d65 Backed out 6 changesets (bug 877115) for mochitest-1 and reftest orange on this CLOSED TREE
Backed out changeset 65ad9d8860d6 (bug 877115)
Backed out changeset bf8095c168fb (bug 877115)
Backed out changeset 290ad5863615 (bug 877115)
Backed out changeset 4488ec28910e (bug 877115)
Backed out changeset 45f8859c6fd6 (bug 877115)
Backed out changeset 111cc426fa9e (bug 877115)
2013-12-16 16:33:07 -08:00

609 lines
21 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "GLBlitHelper.h"
#include "GLContext.h"
#include "ScopedGLHelpers.h"
#include "mozilla/Preferences.h"
namespace mozilla {
namespace gl {
static void
RenderbufferStorageBySamples(GLContext* aGL, GLsizei aSamples,
GLenum aInternalFormat, const gfxIntSize& aSize)
{
if (aSamples) {
aGL->fRenderbufferStorageMultisample(LOCAL_GL_RENDERBUFFER,
aSamples,
aInternalFormat,
aSize.width, aSize.height);
} else {
aGL->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER,
aInternalFormat,
aSize.width, aSize.height);
}
}
GLuint
CreateTexture(GLContext* aGL, GLenum aInternalFormat, GLenum aFormat,
GLenum aType, const gfxIntSize& aSize)
{
GLuint tex = 0;
aGL->fGenTextures(1, &tex);
ScopedBindTexture autoTex(aGL, tex);
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
aGL->fTexImage2D(LOCAL_GL_TEXTURE_2D,
0,
aInternalFormat,
aSize.width, aSize.height,
0,
aFormat,
aType,
nullptr);
return tex;
}
GLuint
CreateTextureForOffscreen(GLContext* aGL, const GLFormats& aFormats,
const gfxIntSize& aSize)
{
MOZ_ASSERT(aFormats.color_texInternalFormat);
MOZ_ASSERT(aFormats.color_texFormat);
MOZ_ASSERT(aFormats.color_texType);
return CreateTexture(aGL,
aFormats.color_texInternalFormat,
aFormats.color_texFormat,
aFormats.color_texType,
aSize);
}
GLuint
CreateRenderbuffer(GLContext* aGL, GLenum aFormat, GLsizei aSamples,
const gfxIntSize& aSize)
{
GLuint rb = 0;
aGL->fGenRenderbuffers(1, &rb);
ScopedBindRenderbuffer autoRB(aGL, rb);
RenderbufferStorageBySamples(aGL, aSamples, aFormat, aSize);
return rb;
}
void
CreateRenderbuffersForOffscreen(GLContext* aGL, const GLFormats& aFormats,
const gfxIntSize& aSize, bool aMultisample,
GLuint* aColorMSRB, GLuint* aDepthRB,
GLuint* aStencilRB)
{
GLsizei samples = aMultisample ? aFormats.samples : 0;
if (aColorMSRB) {
MOZ_ASSERT(aFormats.samples > 0);
MOZ_ASSERT(aFormats.color_rbFormat);
*aColorMSRB = CreateRenderbuffer(aGL, aFormats.color_rbFormat, samples, aSize);
}
if (aDepthRB &&
aStencilRB &&
aFormats.depthStencil)
{
*aDepthRB = CreateRenderbuffer(aGL, aFormats.depthStencil, samples, aSize);
*aStencilRB = *aDepthRB;
} else {
if (aDepthRB) {
MOZ_ASSERT(aFormats.depth);
*aDepthRB = CreateRenderbuffer(aGL, aFormats.depth, samples, aSize);
}
if (aStencilRB) {
MOZ_ASSERT(aFormats.stencil);
*aStencilRB = CreateRenderbuffer(aGL, aFormats.stencil, samples, aSize);
}
}
}
GLBlitHelper::GLBlitHelper(GLContext* gl)
: mGL(gl)
, mTexBlit_Buffer(0)
, mTexBlit_VertShader(0)
, mTex2DBlit_FragShader(0)
, mTex2DRectBlit_FragShader(0)
, mTex2DBlit_Program(0)
, mTex2DRectBlit_Program(0)
{
}
GLBlitHelper::~GLBlitHelper()
{
DeleteTexBlitProgram();
}
// Allowed to be destructive of state we restore in functions below.
bool
GLBlitHelper::InitTexQuadProgram(GLenum target)
{
const char kTexBlit_VertShaderSource[] = "\
attribute vec2 aPosition; \n\
\n\
varying vec2 vTexCoord; \n\
\n\
void main(void) { \n\
vTexCoord = aPosition; \n\
vec2 vertPos = aPosition * 2.0 - 1.0; \n\
gl_Position = vec4(vertPos, 0.0, 1.0); \n\
} \n\
";
const char kTex2DBlit_FragShaderSource[] = "\
#ifdef GL_FRAGMENT_PRECISION_HIGH \n\
precision highp float; \n\
#else \n\
precision mediump float; \n\
#endif \n\
\n\
uniform sampler2D uTexUnit; \n\
\n\
varying vec2 vTexCoord; \n\
\n\
void main(void) { \n\
gl_FragColor = texture2D(uTexUnit, vTexCoord); \n\
} \n\
";
const char kTex2DRectBlit_FragShaderSource[] = "\
#ifdef GL_FRAGMENT_PRECISION_HIGH \n\
precision highp float; \n\
#else \n\
precision mediump float; \n\
#endif \n\
\n\
uniform sampler2D uTexUnit; \n\
uniform vec2 uTexCoordMult; \n\
\n\
varying vec2 vTexCoord; \n\
\n\
void main(void) { \n\
gl_FragColor = texture2DRect(uTexUnit, \n\
vTexCoord * uTexCoordMult); \n\
} \n\
";
MOZ_ASSERT(target == LOCAL_GL_TEXTURE_2D ||
target == LOCAL_GL_TEXTURE_RECTANGLE_ARB);
bool success = false;
GLuint *programPtr;
GLuint *fragShaderPtr;
const char* fragShaderSource;
if (target == LOCAL_GL_TEXTURE_2D) {
programPtr = &mTex2DBlit_Program;
fragShaderPtr = &mTex2DBlit_FragShader;
fragShaderSource = kTex2DBlit_FragShaderSource;
} else {
programPtr = &mTex2DRectBlit_Program;
fragShaderPtr = &mTex2DRectBlit_FragShader;
fragShaderSource = kTex2DRectBlit_FragShaderSource;
}
GLuint& program = *programPtr;
GLuint& fragShader = *fragShaderPtr;
// Use do-while(false) to let us break on failure
do {
if (program) {
// Already have it...
success = true;
break;
}
if (!mTexBlit_Buffer) {
/* CCW tri-strip:
* 2---3
* | \ |
* 0---1
*/
GLfloat verts[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
};
MOZ_ASSERT(!mTexBlit_Buffer);
mGL->fGenBuffers(1, &mTexBlit_Buffer);
mGL->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mTexBlit_Buffer);
const size_t vertsSize = sizeof(verts);
// Make sure we have a sane size.
MOZ_ASSERT(vertsSize >= 3 * sizeof(GLfloat));
mGL->fBufferData(LOCAL_GL_ARRAY_BUFFER, vertsSize, verts, LOCAL_GL_STATIC_DRAW);
}
if (!mTexBlit_VertShader) {
const char* vertShaderSource = kTexBlit_VertShaderSource;
mTexBlit_VertShader = mGL->fCreateShader(LOCAL_GL_VERTEX_SHADER);
mGL->fShaderSource(mTexBlit_VertShader, 1, &vertShaderSource, nullptr);
mGL->fCompileShader(mTexBlit_VertShader);
}
MOZ_ASSERT(!fragShader);
fragShader = mGL->fCreateShader(LOCAL_GL_FRAGMENT_SHADER);
mGL->fShaderSource(fragShader, 1, &fragShaderSource, nullptr);
mGL->fCompileShader(fragShader);
program = mGL->fCreateProgram();
mGL->fAttachShader(program, mTexBlit_VertShader);
mGL->fAttachShader(program, fragShader);
mGL->fBindAttribLocation(program, 0, "aPosition");
mGL->fLinkProgram(program);
if (mGL->DebugMode()) {
GLint status = 0;
mGL->fGetShaderiv(mTexBlit_VertShader, LOCAL_GL_COMPILE_STATUS, &status);
if (status != LOCAL_GL_TRUE) {
NS_ERROR("Vert shader compilation failed.");
GLint length = 0;
mGL->fGetShaderiv(mTexBlit_VertShader, LOCAL_GL_INFO_LOG_LENGTH, &length);
if (!length) {
printf_stderr("No shader info log available.\n");
break;
}
nsAutoArrayPtr<char> buffer(new char[length]);
mGL->fGetShaderInfoLog(mTexBlit_VertShader, length, nullptr, buffer);
printf_stderr("Shader info log (%d bytes): %s\n", length, buffer.get());
break;
}
status = 0;
mGL->fGetShaderiv(fragShader, LOCAL_GL_COMPILE_STATUS, &status);
if (status != LOCAL_GL_TRUE) {
NS_ERROR("Frag shader compilation failed.");
GLint length = 0;
mGL->fGetShaderiv(fragShader, LOCAL_GL_INFO_LOG_LENGTH, &length);
if (!length) {
printf_stderr("No shader info log available.\n");
break;
}
nsAutoArrayPtr<char> buffer(new char[length]);
mGL->fGetShaderInfoLog(fragShader, length, nullptr, buffer);
printf_stderr("Shader info log (%d bytes): %s\n", length, buffer.get());
break;
}
}
GLint status = 0;
mGL->fGetProgramiv(program, LOCAL_GL_LINK_STATUS, &status);
if (status != LOCAL_GL_TRUE) {
if (mGL->DebugMode()) {
NS_ERROR("Linking blit program failed.");
GLint length = 0;
mGL->fGetProgramiv(program, LOCAL_GL_INFO_LOG_LENGTH, &length);
if (!length) {
printf_stderr("No program info log available.\n");
break;
}
nsAutoArrayPtr<char> buffer(new char[length]);
mGL->fGetProgramInfoLog(program, length, nullptr, buffer);
printf_stderr("Program info log (%d bytes): %s\n", length, buffer.get());
}
break;
}
MOZ_ASSERT(mGL->fGetAttribLocation(program, "aPosition") == 0);
GLint texUnitLoc = mGL->fGetUniformLocation(program, "uTexUnit");
MOZ_ASSERT(texUnitLoc != -1, "uniform not found");
// Set uniforms here:
mGL->fUseProgram(program);
mGL->fUniform1i(texUnitLoc, 0);
success = true;
} while (false);
if (!success) {
NS_ERROR("Creating program for texture blit failed!");
// Clean up:
DeleteTexBlitProgram();
return false;
}
mGL->fUseProgram(program);
mGL->fEnableVertexAttribArray(0);
mGL->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mTexBlit_Buffer);
mGL->fVertexAttribPointer(0,
2,
LOCAL_GL_FLOAT,
false,
0,
nullptr);
return true;
}
bool
GLBlitHelper::UseTexQuadProgram(GLenum target, const gfxIntSize& srcSize)
{
if (!InitTexQuadProgram(target)) {
return false;
}
if (target == LOCAL_GL_TEXTURE_RECTANGLE_ARB) {
GLint texCoordMultLoc = mGL->fGetUniformLocation(mTex2DRectBlit_Program, "uTexCoordMult");
MOZ_ASSERT(texCoordMultLoc != -1, "uniform not found");
mGL->fUniform2f(texCoordMultLoc, srcSize.width, srcSize.height);
}
return true;
}
void
GLBlitHelper::DeleteTexBlitProgram()
{
if (mTexBlit_Buffer) {
mGL->fDeleteBuffers(1, &mTexBlit_Buffer);
mTexBlit_Buffer = 0;
}
if (mTexBlit_VertShader) {
mGL->fDeleteShader(mTexBlit_VertShader);
mTexBlit_VertShader = 0;
}
if (mTex2DBlit_FragShader) {
mGL->fDeleteShader(mTex2DBlit_FragShader);
mTex2DBlit_FragShader = 0;
}
if (mTex2DRectBlit_FragShader) {
mGL->fDeleteShader(mTex2DRectBlit_FragShader);
mTex2DRectBlit_FragShader = 0;
}
if (mTex2DBlit_Program) {
mGL->fDeleteProgram(mTex2DBlit_Program);
mTex2DBlit_Program = 0;
}
if (mTex2DRectBlit_Program) {
mGL->fDeleteProgram(mTex2DRectBlit_Program);
mTex2DRectBlit_Program = 0;
}
}
void
GLBlitHelper::BlitFramebufferToFramebuffer(GLuint srcFB, GLuint destFB,
const gfxIntSize& srcSize,
const gfxIntSize& destSize)
{
MOZ_ASSERT(!srcFB || mGL->fIsFramebuffer(srcFB));
MOZ_ASSERT(!destFB || mGL->fIsFramebuffer(destFB));
MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit));
ScopedBindFramebuffer boundFB(mGL);
ScopedGLState scissor(mGL, LOCAL_GL_SCISSOR_TEST, false);
mGL->BindReadFB(srcFB);
mGL->BindDrawFB(destFB);
mGL->fBlitFramebuffer(0, 0, srcSize.width, srcSize.height,
0, 0, destSize.width, destSize.height,
LOCAL_GL_COLOR_BUFFER_BIT,
LOCAL_GL_NEAREST);
}
void
GLBlitHelper::BlitFramebufferToFramebuffer(GLuint srcFB, GLuint destFB,
const gfxIntSize& srcSize,
const gfxIntSize& destSize,
const GLFormats& srcFormats)
{
MOZ_ASSERT(!srcFB || mGL->fIsFramebuffer(srcFB));
MOZ_ASSERT(!destFB || mGL->fIsFramebuffer(destFB));
if (mGL->IsSupported(GLFeature::framebuffer_blit)) {
BlitFramebufferToFramebuffer(srcFB, destFB,
srcSize, destSize);
return;
}
GLuint tex = CreateTextureForOffscreen(mGL, srcFormats, srcSize);
MOZ_ASSERT(tex);
BlitFramebufferToTexture(srcFB, tex, srcSize, srcSize);
BlitTextureToFramebuffer(tex, destFB, srcSize, destSize);
mGL->fDeleteTextures(1, &tex);
}
void
GLBlitHelper::BlitTextureToFramebuffer(GLuint srcTex, GLuint destFB,
const gfxIntSize& srcSize,
const gfxIntSize& destSize,
GLenum srcTarget)
{
MOZ_ASSERT(mGL->fIsTexture(srcTex));
MOZ_ASSERT(!destFB || mGL->fIsFramebuffer(destFB));
if (mGL->IsSupported(GLFeature::framebuffer_blit)) {
ScopedFramebufferForTexture srcWrapper(mGL, srcTex, srcTarget);
MOZ_ASSERT(srcWrapper.IsComplete());
BlitFramebufferToFramebuffer(srcWrapper.FB(), destFB,
srcSize, destSize);
return;
}
ScopedBindFramebuffer boundFB(mGL, destFB);
// UseTexQuadProgram initializes a shader that reads
// from texture unit 0.
ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);
ScopedBindTexture boundTex(mGL, srcTex, srcTarget);
GLuint boundProgram = 0;
mGL->GetUIntegerv(LOCAL_GL_CURRENT_PROGRAM, &boundProgram);
GLuint boundBuffer = 0;
mGL->GetUIntegerv(LOCAL_GL_ARRAY_BUFFER_BINDING, &boundBuffer);
/*
* mGL->fGetVertexAttribiv takes:
* VERTEX_ATTRIB_ARRAY_ENABLED
* VERTEX_ATTRIB_ARRAY_SIZE,
* VERTEX_ATTRIB_ARRAY_STRIDE,
* VERTEX_ATTRIB_ARRAY_TYPE,
* VERTEX_ATTRIB_ARRAY_NORMALIZED,
* VERTEX_ATTRIB_ARRAY_BUFFER_BINDING,
* CURRENT_VERTEX_ATTRIB
*
* CURRENT_VERTEX_ATTRIB is vertex shader state. \o/
* Others appear to be vertex array state,
* or alternatively in the internal vertex array state
* for a buffer object.
*/
GLint attrib0_enabled = 0;
GLint attrib0_size = 0;
GLint attrib0_stride = 0;
GLint attrib0_type = 0;
GLint attrib0_normalized = 0;
GLint attrib0_bufferBinding = 0;
void* attrib0_pointer = nullptr;
mGL->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_ENABLED, &attrib0_enabled);
mGL->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_SIZE, &attrib0_size);
mGL->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_STRIDE, &attrib0_stride);
mGL->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_TYPE, &attrib0_type);
mGL->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &attrib0_normalized);
mGL->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &attrib0_bufferBinding);
mGL->fGetVertexAttribPointerv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER, &attrib0_pointer);
// Note that uniform values are program state, so we don't need to rebind those.
ScopedGLState blend (mGL, LOCAL_GL_BLEND, false);
ScopedGLState cullFace (mGL, LOCAL_GL_CULL_FACE, false);
ScopedGLState depthTest (mGL, LOCAL_GL_DEPTH_TEST, false);
ScopedGLState dither (mGL, LOCAL_GL_DITHER, false);
ScopedGLState polyOffsFill(mGL, LOCAL_GL_POLYGON_OFFSET_FILL, false);
ScopedGLState sampleAToC (mGL, LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE, false);
ScopedGLState sampleCover (mGL, LOCAL_GL_SAMPLE_COVERAGE, false);
ScopedGLState scissor (mGL, LOCAL_GL_SCISSOR_TEST, false);
ScopedGLState stencil (mGL, LOCAL_GL_STENCIL_TEST, false);
realGLboolean colorMask[4];
mGL->fGetBooleanv(LOCAL_GL_COLOR_WRITEMASK, colorMask);
mGL->fColorMask(LOCAL_GL_TRUE,
LOCAL_GL_TRUE,
LOCAL_GL_TRUE,
LOCAL_GL_TRUE);
GLint viewport[4];
mGL->fGetIntegerv(LOCAL_GL_VIEWPORT, viewport);
mGL->fViewport(0, 0, destSize.width, destSize.height);
// Does destructive things to (only!) what we just saved above.
bool good = UseTexQuadProgram(srcTarget, srcSize);
if (!good) {
// We're up against the wall, so bail.
// This should really be MOZ_CRASH(why) or MOZ_RUNTIME_ASSERT(good).
printf_stderr("[%s:%d] Fatal Error: Failed to prepare to blit texture->framebuffer.\n",
__FILE__, __LINE__);
MOZ_CRASH();
}
mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
mGL->fViewport(viewport[0], viewport[1],
viewport[2], viewport[3]);
mGL->fColorMask(colorMask[0],
colorMask[1],
colorMask[2],
colorMask[3]);
if (attrib0_enabled)
mGL->fEnableVertexAttribArray(0);
mGL->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, attrib0_bufferBinding);
mGL->fVertexAttribPointer(0,
attrib0_size,
attrib0_type,
attrib0_normalized,
attrib0_stride,
attrib0_pointer);
mGL->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, boundBuffer);
mGL->fUseProgram(boundProgram);
}
void
GLBlitHelper::BlitFramebufferToTexture(GLuint srcFB, GLuint destTex,
const gfxIntSize& srcSize,
const gfxIntSize& destSize,
GLenum destTarget)
{
MOZ_ASSERT(!srcFB || mGL->fIsFramebuffer(srcFB));
MOZ_ASSERT(mGL->fIsTexture(destTex));
if (mGL->IsSupported(GLFeature::framebuffer_blit)) {
ScopedFramebufferForTexture destWrapper(mGL, destTex, destTarget);
BlitFramebufferToFramebuffer(srcFB, destWrapper.FB(),
srcSize, destSize);
return;
}
ScopedBindTexture autoTex(mGL, destTex, destTarget);
ScopedBindFramebuffer boundFB(mGL, srcFB);
ScopedGLState scissor(mGL, LOCAL_GL_SCISSOR_TEST, false);
mGL->fCopyTexSubImage2D(destTarget, 0,
0, 0,
0, 0,
srcSize.width, srcSize.height);
}
void
GLBlitHelper::BlitTextureToTexture(GLuint srcTex, GLuint destTex,
const gfxIntSize& srcSize,
const gfxIntSize& destSize,
GLenum srcTarget, GLenum destTarget)
{
MOZ_ASSERT(mGL->fIsTexture(srcTex));
MOZ_ASSERT(mGL->fIsTexture(destTex));
// Generally, just use the CopyTexSubImage path
ScopedFramebufferForTexture srcWrapper(mGL, srcTex, srcTarget);
BlitFramebufferToTexture(srcWrapper.FB(), destTex,
srcSize, destSize, destTarget);
}
}
}