diff options
Diffstat (limited to 'gfx/angle/src/libANGLE/renderer/gl/BlitGL.cpp')
-rwxr-xr-x | gfx/angle/src/libANGLE/renderer/gl/BlitGL.cpp | 486 |
1 files changed, 486 insertions, 0 deletions
diff --git a/gfx/angle/src/libANGLE/renderer/gl/BlitGL.cpp b/gfx/angle/src/libANGLE/renderer/gl/BlitGL.cpp new file mode 100755 index 000000000..b181822ff --- /dev/null +++ b/gfx/angle/src/libANGLE/renderer/gl/BlitGL.cpp @@ -0,0 +1,486 @@ +// +// Copyright 2015 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// + +// BlitGL.cpp: Implements the BlitGL class, a helper for blitting textures + +#include "libANGLE/renderer/gl/BlitGL.h" + +#include "libANGLE/formatutils.h" +#include "libANGLE/Framebuffer.h" +#include "libANGLE/renderer/gl/formatutilsgl.h" +#include "libANGLE/renderer/gl/FramebufferGL.h" +#include "libANGLE/renderer/gl/FunctionsGL.h" +#include "libANGLE/renderer/gl/TextureGL.h" +#include "libANGLE/renderer/gl/StateManagerGL.h" +#include "libANGLE/renderer/gl/WorkaroundsGL.h" + +namespace +{ + +gl::Error CheckCompileStatus(const rx::FunctionsGL *functions, GLuint shader) +{ + GLint compileStatus = GL_FALSE; + functions->getShaderiv(shader, GL_COMPILE_STATUS, &compileStatus); + ASSERT(compileStatus == GL_TRUE); + if (compileStatus == GL_FALSE) + { + return gl::Error(GL_OUT_OF_MEMORY, "Failed to compile internal blit shader."); + } + + return gl::NoError(); +} + +gl::Error CheckLinkStatus(const rx::FunctionsGL *functions, GLuint program) +{ + GLint linkStatus = GL_FALSE; + functions->getProgramiv(program, GL_LINK_STATUS, &linkStatus); + ASSERT(linkStatus == GL_TRUE); + if (linkStatus == GL_FALSE) + { + return gl::Error(GL_OUT_OF_MEMORY, "Failed to link internal blit program."); + } + + return gl::NoError(); +} + +} // anonymous namespace + +namespace rx +{ + +BlitGL::BlitGL(const FunctionsGL *functions, + const WorkaroundsGL &workarounds, + StateManagerGL *stateManager) + : mFunctions(functions), + mWorkarounds(workarounds), + mStateManager(stateManager), + mBlitProgram(0), + mSourceTextureLocation(-1), + mScaleLocation(-1), + mOffsetLocation(-1), + mScratchFBO(0), + mVAO(0) +{ + for (size_t i = 0; i < ArraySize(mScratchTextures); i++) + { + mScratchTextures[i] = 0; + } + + ASSERT(mFunctions); + ASSERT(mStateManager); +} + +BlitGL::~BlitGL() +{ + if (mBlitProgram != 0) + { + mStateManager->deleteProgram(mBlitProgram); + mBlitProgram = 0; + } + + for (size_t i = 0; i < ArraySize(mScratchTextures); i++) + { + if (mScratchTextures[i] != 0) + { + mStateManager->deleteTexture(mScratchTextures[i]); + mScratchTextures[i] = 0; + } + } + + if (mScratchFBO != 0) + { + mStateManager->deleteFramebuffer(mScratchFBO); + mScratchFBO = 0; + } + + if (mVAO != 0) + { + mStateManager->deleteVertexArray(mVAO); + mVAO = 0; + } +} + +gl::Error BlitGL::copyImageToLUMAWorkaroundTexture(GLuint texture, + GLenum textureType, + GLenum target, + GLenum lumaFormat, + size_t level, + const gl::Rectangle &sourceArea, + GLenum internalFormat, + const gl::Framebuffer *source) +{ + mStateManager->bindTexture(textureType, texture); + + // Allocate the texture memory + const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat); + mFunctions->texImage2D(target, static_cast<GLint>(level), internalFormat, sourceArea.width, + sourceArea.height, 0, internalFormatInfo.format, + source->getImplementationColorReadType(), nullptr); + + return copySubImageToLUMAWorkaroundTexture(texture, textureType, target, lumaFormat, level, + gl::Offset(0, 0, 0), sourceArea, source); +} + +gl::Error BlitGL::copySubImageToLUMAWorkaroundTexture(GLuint texture, + GLenum textureType, + GLenum target, + GLenum lumaFormat, + size_t level, + const gl::Offset &destOffset, + const gl::Rectangle &sourceArea, + const gl::Framebuffer *source) +{ + ANGLE_TRY(initializeResources()); + + // Blit the framebuffer to the first scratch texture + const FramebufferGL *sourceFramebufferGL = GetImplAs<FramebufferGL>(source); + mStateManager->bindFramebuffer(GL_FRAMEBUFFER, sourceFramebufferGL->getFramebufferID()); + + nativegl::CopyTexImageImageFormat copyTexImageFormat = nativegl::GetCopyTexImageImageFormat( + mFunctions, mWorkarounds, source->getImplementationColorReadFormat(), + source->getImplementationColorReadType()); + const gl::InternalFormat &internalFormatInfo = + gl::GetInternalFormatInfo(copyTexImageFormat.internalFormat); + + mStateManager->bindTexture(GL_TEXTURE_2D, mScratchTextures[0]); + mFunctions->copyTexImage2D(GL_TEXTURE_2D, 0, copyTexImageFormat.internalFormat, sourceArea.x, + sourceArea.y, sourceArea.width, sourceArea.height, 0); + + // Set the swizzle of the scratch texture so that the channels sample into the correct emulated + // LUMA channels. + GLint swizzle[4] = { + (lumaFormat == GL_ALPHA) ? GL_ALPHA : GL_RED, + (lumaFormat == GL_LUMINANCE_ALPHA) ? GL_ALPHA : GL_ZERO, GL_ZERO, GL_ZERO, + }; + mFunctions->texParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle); + + // Make a temporary framebuffer using the second scratch texture to render the swizzled result + // to. + mStateManager->bindTexture(GL_TEXTURE_2D, mScratchTextures[1]); + mFunctions->texImage2D(GL_TEXTURE_2D, 0, copyTexImageFormat.internalFormat, sourceArea.width, + sourceArea.height, 0, internalFormatInfo.format, + source->getImplementationColorReadType(), nullptr); + + mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mScratchFBO); + mFunctions->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + mScratchTextures[1], 0); + + // Render to the destination texture, sampling from the scratch texture + mStateManager->setViewport(gl::Rectangle(0, 0, sourceArea.width, sourceArea.height)); + mStateManager->setScissorTestEnabled(false); + mStateManager->setDepthRange(0.0f, 1.0f); + mStateManager->setBlendEnabled(false); + mStateManager->setColorMask(true, true, true, true); + mStateManager->setSampleAlphaToCoverageEnabled(false); + mStateManager->setSampleCoverageEnabled(false); + mStateManager->setDepthTestEnabled(false); + mStateManager->setStencilTestEnabled(false); + mStateManager->setCullFaceEnabled(false); + mStateManager->setPolygonOffsetFillEnabled(false); + mStateManager->setRasterizerDiscardEnabled(false); + mStateManager->bindTexture(GL_TEXTURE_2D, mScratchTextures[0]); + + setScratchTextureParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); + setScratchTextureParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); + mStateManager->activeTexture(0); + mStateManager->bindTexture(GL_TEXTURE_2D, mScratchTextures[0]); + + mStateManager->useProgram(mBlitProgram); + mFunctions->uniform1i(mSourceTextureLocation, 0); + mFunctions->uniform2f(mScaleLocation, 1.0, 1.0); + mFunctions->uniform2f(mOffsetLocation, 0.0, 0.0); + + mStateManager->bindVertexArray(mVAO, 0); + + mFunctions->drawArrays(GL_TRIANGLES, 0, 3); + + // Copy the swizzled texture to the destination texture + mStateManager->bindTexture(textureType, texture); + mFunctions->copyTexSubImage2D(target, static_cast<GLint>(level), destOffset.x, destOffset.y, 0, + 0, sourceArea.width, sourceArea.height); + + // Finally orphan the scratch textures so they can be GCed by the driver. + orphanScratchTextures(); + + return gl::NoError(); +} + +gl::Error BlitGL::blitColorBufferWithShader(const gl::Framebuffer *source, + const gl::Framebuffer *dest, + const gl::Rectangle &sourceAreaIn, + const gl::Rectangle &destAreaIn, + GLenum filter) +{ + ANGLE_TRY(initializeResources()); + + // Normalize the destination area to have positive width and height because we will use + // glViewport to set it, which doesn't allow negative width or height. + gl::Rectangle sourceArea = sourceAreaIn; + gl::Rectangle destArea = destAreaIn; + if (destArea.width < 0) + { + destArea.x += destArea.width; + destArea.width = -destArea.width; + sourceArea.x += sourceArea.width; + sourceArea.width = -sourceArea.width; + } + if (destArea.height < 0) + { + destArea.y += destArea.height; + destArea.height = -destArea.height; + sourceArea.y += sourceArea.height; + sourceArea.height = -sourceArea.height; + } + + const gl::FramebufferAttachment *readAttachment = source->getReadColorbuffer(); + ASSERT(readAttachment->getSamples() <= 1); + + // Compute the part of the source that will be sampled. + gl::Rectangle inBoundsSource; + { + gl::Extents sourceSize = readAttachment->getSize(); + gl::Rectangle sourceBounds(0, 0, sourceSize.width, sourceSize.height); + gl::ClipRectangle(sourceArea, sourceBounds, &inBoundsSource); + + // Note that inBoundsSource will have lost the orientation information. + ASSERT(inBoundsSource.width >= 0 && inBoundsSource.height >= 0); + + // Early out when the sampled part is empty as the blit will be a noop, + // and it prevents a division by zero in later computations. + if (inBoundsSource.width == 0 || inBoundsSource.height == 0) + { + return gl::NoError(); + } + } + + // The blit will be emulated by getting the source of the blit in a texture and sampling it + // with CLAMP_TO_EDGE. The quad used to draw can trivially compute texture coordinates going + // from (0, 0) to (1, 1). These texture coordinates will need to be transformed to make two + // regions match: + // - The region of the texture representing the source framebuffer region that will be sampled + // - The region of the drawn quad that corresponds to non-clamped blit, this is the same as the + // region of the source rectangle that is inside the source attachment. + // + // These two regions, T (texture) and D (dest) are defined by their offset in texcoord space + // in (0, 1)^2 and their size in texcoord space in (-1, 1)^2. The size can be negative to + // represent the orientation of the blit. + // + // Then if P is the quad texcoord, Q the texcoord inside T, and R the texture texcoord: + // - Q = (P - D.offset) / D.size + // - Q = (R - T.offset) / T.size + // Hence R = (P - D.offset) / D.size * T.size - T.offset + // = P * (T.size / D.size) + (T.offset - D.offset * T.size / D.size) + + GLuint textureId; + gl::Vector2 TOffset; + gl::Vector2 TSize; + + // TODO(cwallez) once texture dirty bits are landed, reuse attached texture instead of using + // CopyTexImage2D + { + textureId = mScratchTextures[0]; + TOffset = gl::Vector2(0.0, 0.0); + TSize = gl::Vector2(1.0, 1.0); + if (sourceArea.width < 0) + { + TOffset.x = 1.0; + TSize.x = -1.0; + } + if (sourceArea.height < 0) + { + TOffset.y = 1.0; + TSize.y = -1.0; + } + + GLenum format = readAttachment->getFormat().info->internalFormat; + const FramebufferGL *sourceGL = GetImplAs<FramebufferGL>(source); + mStateManager->bindFramebuffer(GL_READ_FRAMEBUFFER, sourceGL->getFramebufferID()); + mStateManager->bindTexture(GL_TEXTURE_2D, textureId); + + mFunctions->copyTexImage2D(GL_TEXTURE_2D, 0, format, inBoundsSource.x, inBoundsSource.y, + inBoundsSource.width, inBoundsSource.height, 0); + } + + // Compute normalized sampled draw quad region + // It is the same as the region of the source rectangle that is in bounds. + gl::Vector2 DOffset; + gl::Vector2 DSize; + { + ASSERT(sourceArea.width != 0 && sourceArea.height != 0); + gl::Rectangle orientedInBounds = inBoundsSource; + if (sourceArea.width < 0) + { + orientedInBounds.x += orientedInBounds.width; + orientedInBounds.width = -orientedInBounds.width; + } + if (sourceArea.height < 0) + { + orientedInBounds.y += orientedInBounds.height; + orientedInBounds.height = -orientedInBounds.height; + } + + DOffset = + gl::Vector2(static_cast<float>(orientedInBounds.x - sourceArea.x) / sourceArea.width, + static_cast<float>(orientedInBounds.y - sourceArea.y) / sourceArea.height); + DSize = gl::Vector2(static_cast<float>(orientedInBounds.width) / sourceArea.width, + static_cast<float>(orientedInBounds.height) / sourceArea.height); + } + + ASSERT(DSize.x != 0.0 && DSize.y != 0.0); + gl::Vector2 texCoordScale = gl::Vector2(TSize.x / DSize.x, TSize.y / DSize.y); + gl::Vector2 texCoordOffset = gl::Vector2(TOffset.x - DOffset.x * texCoordScale.x, + TOffset.y - DOffset.y * texCoordScale.y); + + // Reset all the state except scissor and viewport + mStateManager->setDepthRange(0.0f, 1.0f); + mStateManager->setBlendEnabled(false); + mStateManager->setColorMask(true, true, true, true); + mStateManager->setSampleAlphaToCoverageEnabled(false); + mStateManager->setSampleCoverageEnabled(false); + mStateManager->setDepthTestEnabled(false); + mStateManager->setStencilTestEnabled(false); + mStateManager->setCullFaceEnabled(false); + mStateManager->setPolygonOffsetFillEnabled(false); + mStateManager->setRasterizerDiscardEnabled(false); + + // Use the viewport to draw exactly to the destination rectangle + mStateManager->setViewport(destArea); + + // Set uniforms + setScratchTextureParameter(GL_TEXTURE_MIN_FILTER, filter); + setScratchTextureParameter(GL_TEXTURE_MAG_FILTER, filter); + setScratchTextureParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + setScratchTextureParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + mStateManager->activeTexture(0); + mStateManager->bindTexture(GL_TEXTURE_2D, mScratchTextures[0]); + + mStateManager->useProgram(mBlitProgram); + mFunctions->uniform1i(mSourceTextureLocation, 0); + mFunctions->uniform2f(mScaleLocation, texCoordScale.x, texCoordScale.y); + mFunctions->uniform2f(mOffsetLocation, texCoordOffset.x, texCoordOffset.y); + + const FramebufferGL *destGL = GetImplAs<FramebufferGL>(dest); + mStateManager->bindFramebuffer(GL_DRAW_FRAMEBUFFER, destGL->getFramebufferID()); + + mStateManager->bindVertexArray(mVAO, 0); + mFunctions->drawArrays(GL_TRIANGLES, 0, 3); + + return gl::NoError(); +} + +gl::Error BlitGL::initializeResources() +{ + if (mBlitProgram == 0) + { + mBlitProgram = mFunctions->createProgram(); + + // Compile the fragment shader + // It uses a single, large triangle, to avoid arithmetic precision issues where fragments + // with the same Y coordinate don't get exactly the same interpolated texcoord Y. + const char *vsSource = + "#version 150\n" + "out vec2 v_texcoord;\n" + "uniform vec2 u_scale;\n" + "uniform vec2 u_offset;\n" + "\n" + "void main()\n" + "{\n" + " const vec2 quad_positions[3] = vec2[3]\n" + " (\n" + " vec2(-0.5f, 0.0f),\n" + " vec2( 1.5f, 0.0f),\n" + " vec2( 0.5f, 2.0f)\n" + " );\n" + "\n" + " gl_Position = vec4((quad_positions[gl_VertexID] * 2.0) - 1.0, 0.0, 1.0);\n" + " v_texcoord = quad_positions[gl_VertexID] * u_scale + u_offset;\n" + "}\n"; + + GLuint vs = mFunctions->createShader(GL_VERTEX_SHADER); + mFunctions->shaderSource(vs, 1, &vsSource, nullptr); + mFunctions->compileShader(vs); + ANGLE_TRY(CheckCompileStatus(mFunctions, vs)); + + mFunctions->attachShader(mBlitProgram, vs); + mFunctions->deleteShader(vs); + + // Compile the vertex shader + // It discards if the texcoord is outside (0, 1)^2 so the blitframebuffer workaround + // doesn't write when the point sampled is outside of the source framebuffer. + const char *fsSource = + "#version 150\n" + "uniform sampler2D u_source_texture;\n" + "in vec2 v_texcoord;\n" + "out vec4 output_color;\n" + "\n" + "void main()\n" + "{\n" + " if (clamp(v_texcoord, vec2(0.0), vec2(1.0)) != v_texcoord)\n" + " {\n" + " discard;\n" + " }\n" + " output_color = texture(u_source_texture, v_texcoord);\n" + "}\n"; + + GLuint fs = mFunctions->createShader(GL_FRAGMENT_SHADER); + mFunctions->shaderSource(fs, 1, &fsSource, nullptr); + mFunctions->compileShader(fs); + ANGLE_TRY(CheckCompileStatus(mFunctions, fs)); + + mFunctions->attachShader(mBlitProgram, fs); + mFunctions->deleteShader(fs); + + mFunctions->linkProgram(mBlitProgram); + ANGLE_TRY(CheckLinkStatus(mFunctions, mBlitProgram)); + + mSourceTextureLocation = mFunctions->getUniformLocation(mBlitProgram, "u_source_texture"); + mScaleLocation = mFunctions->getUniformLocation(mBlitProgram, "u_scale"); + mOffsetLocation = mFunctions->getUniformLocation(mBlitProgram, "u_offset"); + mStateManager->useProgram(mBlitProgram); + } + + for (size_t i = 0; i < ArraySize(mScratchTextures); i++) + { + if (mScratchTextures[i] == 0) + { + mFunctions->genTextures(1, &mScratchTextures[i]); + } + } + + if (mScratchFBO == 0) + { + mFunctions->genFramebuffers(1, &mScratchFBO); + } + + if (mVAO == 0) + { + mFunctions->genVertexArrays(1, &mVAO); + } + + return gl::NoError(); +} + +void BlitGL::orphanScratchTextures() +{ + for (auto texture : mScratchTextures) + { + mStateManager->bindTexture(GL_TEXTURE_2D, texture); + mFunctions->texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, + nullptr); + } +} + +void BlitGL::setScratchTextureParameter(GLenum param, GLenum value) +{ + for (auto texture : mScratchTextures) + { + mStateManager->bindTexture(GL_TEXTURE_2D, texture); + mFunctions->texParameteri(GL_TEXTURE_2D, param, value); + mFunctions->texParameteri(GL_TEXTURE_2D, param, value); + } +} + +} // namespace rx |