/* -*- 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 gfx::IntSize& 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 gfx::IntSize& 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 gfx::IntSize& 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 gfx::IntSize& 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 gfx::IntSize& 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 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 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 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 gfx::IntSize& 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 gfx::IntSize& srcSize, const gfx::IntSize& 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 gfx::IntSize& srcSize, const gfx::IntSize& 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 gfx::IntSize& srcSize, const gfx::IntSize& 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 gfx::IntSize& srcSize, const gfx::IntSize& 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 gfx::IntSize& srcSize, const gfx::IntSize& 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); } } }