blob: c5bbe9e5acd59533ce02b3514cd8d276d71573d7 [file] [log] [blame]
//
// 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 "common/FixedVector.h"
#include "common/utilities.h"
#include "common/vector_utils.h"
#include "image_util/copyimage.h"
#include "libANGLE/Context.h"
#include "libANGLE/Framebuffer.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/renderer/Format.h"
#include "libANGLE/renderer/gl/ContextGL.h"
#include "libANGLE/renderer/gl/FramebufferGL.h"
#include "libANGLE/renderer/gl/FunctionsGL.h"
#include "libANGLE/renderer/gl/RenderbufferGL.h"
#include "libANGLE/renderer/gl/StateManagerGL.h"
#include "libANGLE/renderer/gl/TextureGL.h"
#include "libANGLE/renderer/gl/formatutilsgl.h"
#include "libANGLE/renderer/gl/renderergl_utils.h"
#include "libANGLE/renderer/renderer_utils.h"
#include "platform/FeaturesGL.h"
using angle::Vector2;
namespace rx
{
namespace
{
angle::Result CheckCompileStatus(const gl::Context *context,
const rx::FunctionsGL *functions,
GLuint shader)
{
GLint compileStatus = GL_FALSE;
ANGLE_GL_TRY(context, functions->getShaderiv(shader, GL_COMPILE_STATUS, &compileStatus));
ASSERT(compileStatus == GL_TRUE);
ANGLE_CHECK(GetImplAs<ContextGL>(context), compileStatus == GL_TRUE,
"Failed to compile internal blit shader.", GL_OUT_OF_MEMORY);
return angle::Result::Continue;
}
angle::Result CheckLinkStatus(const gl::Context *context,
const rx::FunctionsGL *functions,
GLuint program)
{
GLint linkStatus = GL_FALSE;
ANGLE_GL_TRY(context, functions->getProgramiv(program, GL_LINK_STATUS, &linkStatus));
ASSERT(linkStatus == GL_TRUE);
ANGLE_CHECK(GetImplAs<ContextGL>(context), linkStatus == GL_TRUE,
"Failed to link internal blit program.", GL_OUT_OF_MEMORY);
return angle::Result::Continue;
}
class ScopedGLState : angle::NonCopyable
{
public:
enum
{
KEEP_SCISSOR = 1,
};
ScopedGLState() {}
~ScopedGLState() { ASSERT(mExited); }
angle::Result enter(const gl::Context *context, gl::Rectangle viewport, int keepState = 0)
{
ContextGL *contextGL = GetImplAs<ContextGL>(context);
StateManagerGL *stateManager = contextGL->getStateManager();
if (!(keepState & KEEP_SCISSOR))
{
stateManager->setScissorTestEnabled(false);
}
stateManager->setViewport(viewport);
stateManager->setDepthRange(0.0f, 1.0f);
stateManager->setBlendEnabled(false);
stateManager->setColorMask(true, true, true, true);
stateManager->setSampleAlphaToCoverageEnabled(false);
stateManager->setSampleCoverageEnabled(false);
stateManager->setDepthTestEnabled(false);
stateManager->setStencilTestEnabled(false);
stateManager->setCullFaceEnabled(false);
stateManager->setPolygonOffsetFillEnabled(false);
stateManager->setRasterizerDiscardEnabled(false);
stateManager->pauseTransformFeedback();
return stateManager->pauseAllQueries(context);
}
angle::Result exit(const gl::Context *context)
{
mExited = true;
ContextGL *contextGL = GetImplAs<ContextGL>(context);
StateManagerGL *stateManager = contextGL->getStateManager();
// XFB resuming will be done automatically
return stateManager->resumeAllQueries(context);
}
void willUseTextureUnit(const gl::Context *context, int unit)
{
ContextGL *contextGL = GetImplAs<ContextGL>(context);
if (contextGL->getFunctions()->bindSampler)
{
contextGL->getStateManager()->bindSampler(unit, 0);
}
}
private:
bool mExited = false;
};
angle::Result SetClearState(StateManagerGL *stateManager,
bool colorClear,
bool depthClear,
bool stencilClear,
GLbitfield *outClearMask)
{
*outClearMask = 0;
if (colorClear)
{
stateManager->setClearColor(gl::ColorF(0.0f, 0.0f, 0.0f, 0.0f));
stateManager->setColorMask(true, true, true, true);
*outClearMask |= GL_COLOR_BUFFER_BIT;
}
if (depthClear)
{
stateManager->setDepthMask(true);
stateManager->setClearDepth(1.0f);
*outClearMask |= GL_DEPTH_BUFFER_BIT;
}
if (stencilClear)
{
stateManager->setClearStencil(0);
*outClearMask |= GL_STENCIL_BUFFER_BIT;
}
stateManager->setScissorTestEnabled(false);
return angle::Result::Continue;
}
using ClearBindTargetVector = angle::FixedVector<GLenum, 3>;
angle::Result PrepareForClear(StateManagerGL *stateManager,
GLenum sizedInternalFormat,
ClearBindTargetVector *outBindtargets,
ClearBindTargetVector *outUnbindTargets,
GLbitfield *outClearMask)
{
const gl::InternalFormat &internalFormatInfo =
gl::GetSizedInternalFormatInfo(sizedInternalFormat);
bool bindDepth = internalFormatInfo.depthBits > 0;
bool bindStencil = internalFormatInfo.stencilBits > 0;
bool bindColor = !bindDepth && !bindStencil;
outBindtargets->clear();
if (bindColor)
{
outBindtargets->push_back(GL_COLOR_ATTACHMENT0);
}
else
{
outUnbindTargets->push_back(GL_COLOR_ATTACHMENT0);
}
if (bindDepth)
{
outBindtargets->push_back(GL_DEPTH_ATTACHMENT);
}
else
{
outUnbindTargets->push_back(GL_DEPTH_ATTACHMENT);
}
if (bindStencil)
{
outBindtargets->push_back(GL_STENCIL_ATTACHMENT);
}
else
{
outUnbindTargets->push_back(GL_STENCIL_ATTACHMENT);
}
ANGLE_TRY(SetClearState(stateManager, bindColor, bindDepth, bindStencil, outClearMask));
return angle::Result::Continue;
}
angle::Result UnbindAttachments(const gl::Context *context,
const FunctionsGL *functions,
GLenum framebufferTarget,
const ClearBindTargetVector &bindTargets)
{
for (GLenum bindTarget : bindTargets)
{
ANGLE_GL_TRY(context, functions->framebufferRenderbuffer(framebufferTarget, bindTarget,
GL_RENDERBUFFER, 0));
}
return angle::Result::Continue;
}
} // anonymous namespace
BlitGL::BlitGL(const FunctionsGL *functions,
const angle::FeaturesGL &features,
StateManagerGL *stateManager)
: mFunctions(functions),
mFeatures(features),
mStateManager(stateManager),
mScratchFBO(0),
mVAO(0),
mVertexBuffer(0)
{
for (size_t i = 0; i < ArraySize(mScratchTextures); i++)
{
mScratchTextures[i] = 0;
}
ASSERT(mFunctions);
ASSERT(mStateManager);
}
BlitGL::~BlitGL()
{
for (const auto &blitProgram : mBlitPrograms)
{
mStateManager->deleteProgram(blitProgram.second.program);
}
mBlitPrograms.clear();
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;
}
}
angle::Result BlitGL::copyImageToLUMAWorkaroundTexture(const gl::Context *context,
GLuint texture,
gl::TextureType textureType,
gl::TextureTarget target,
GLenum lumaFormat,
size_t level,
const gl::Rectangle &sourceArea,
GLenum internalFormat,
gl::Framebuffer *source)
{
mStateManager->bindTexture(textureType, texture);
// Allocate the texture memory
GLenum format = gl::GetUnsizedFormat(internalFormat);
GLenum readType = GL_NONE;
ANGLE_TRY(source->getImplementationColorReadType(context, &readType));
gl::PixelUnpackState unpack;
mStateManager->setPixelUnpackState(unpack);
mStateManager->setPixelUnpackBuffer(
context->getState().getTargetBuffer(gl::BufferBinding::PixelUnpack));
ANGLE_GL_TRY_ALWAYS_CHECK(
context,
mFunctions->texImage2D(ToGLenum(target), static_cast<GLint>(level), internalFormat,
sourceArea.width, sourceArea.height, 0, format, readType, nullptr));
return copySubImageToLUMAWorkaroundTexture(context, texture, textureType, target, lumaFormat,
level, gl::Offset(0, 0, 0), sourceArea, source);
}
angle::Result BlitGL::copySubImageToLUMAWorkaroundTexture(const gl::Context *context,
GLuint texture,
gl::TextureType textureType,
gl::TextureTarget target,
GLenum lumaFormat,
size_t level,
const gl::Offset &destOffset,
const gl::Rectangle &sourceArea,
gl::Framebuffer *source)
{
ANGLE_TRY(initializeResources(context));
BlitProgram *blitProgram = nullptr;
ANGLE_TRY(getBlitProgram(context, gl::TextureType::_2D, GL_FLOAT, GL_FLOAT, &blitProgram));
// Blit the framebuffer to the first scratch texture
const FramebufferGL *sourceFramebufferGL = GetImplAs<FramebufferGL>(source);
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, sourceFramebufferGL->getFramebufferID());
GLenum readFormat = GL_NONE;
ANGLE_TRY(source->getImplementationColorReadFormat(context, &readFormat));
GLenum readType = GL_NONE;
ANGLE_TRY(source->getImplementationColorReadType(context, &readType));
nativegl::CopyTexImageImageFormat copyTexImageFormat =
nativegl::GetCopyTexImageImageFormat(mFunctions, mFeatures, readFormat, readType);
mStateManager->bindTexture(gl::TextureType::_2D, mScratchTextures[0]);
ANGLE_GL_TRY_ALWAYS_CHECK(
context, 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,
};
ANGLE_GL_TRY(context,
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::TextureType::_2D, mScratchTextures[1]);
ANGLE_GL_TRY_ALWAYS_CHECK(
context, mFunctions->texImage2D(GL_TEXTURE_2D, 0, copyTexImageFormat.internalFormat,
sourceArea.width, sourceArea.height, 0,
gl::GetUnsizedFormat(copyTexImageFormat.internalFormat),
readType, nullptr));
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
ANGLE_GL_TRY(context, mFunctions->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, mScratchTextures[1], 0));
// Render to the destination texture, sampling from the scratch texture
ScopedGLState scopedState;
ANGLE_TRY(scopedState.enter(context, gl::Rectangle(0, 0, sourceArea.width, sourceArea.height)));
scopedState.willUseTextureUnit(context, 0);
ANGLE_TRY(setScratchTextureParameter(context, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
ANGLE_TRY(setScratchTextureParameter(context, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
mStateManager->activeTexture(0);
mStateManager->bindTexture(gl::TextureType::_2D, mScratchTextures[0]);
mStateManager->useProgram(blitProgram->program);
ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->sourceTextureLocation, 0));
ANGLE_GL_TRY(context, mFunctions->uniform2f(blitProgram->scaleLocation, 1.0, 1.0));
ANGLE_GL_TRY(context, mFunctions->uniform2f(blitProgram->offsetLocation, 0.0, 0.0));
ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->multiplyAlphaLocation, 0));
ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->unMultiplyAlphaLocation, 0));
mStateManager->bindVertexArray(mVAO, 0);
ANGLE_GL_TRY(context, mFunctions->drawArrays(GL_TRIANGLES, 0, 3));
// Copy the swizzled texture to the destination texture
mStateManager->bindTexture(textureType, texture);
if (nativegl::UseTexImage3D(textureType))
{
ANGLE_GL_TRY(context,
mFunctions->copyTexSubImage3D(ToGLenum(target), static_cast<GLint>(level),
destOffset.x, destOffset.y, destOffset.z, 0, 0,
sourceArea.width, sourceArea.height));
}
else
{
ASSERT(nativegl::UseTexImage2D(textureType));
ANGLE_GL_TRY(context, mFunctions->copyTexSubImage2D(
ToGLenum(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.
ANGLE_TRY(orphanScratchTextures(context));
ANGLE_TRY(scopedState.exit(context));
return angle::Result::Continue;
}
angle::Result BlitGL::blitColorBufferWithShader(const gl::Context *context,
const gl::Framebuffer *source,
const gl::Framebuffer *dest,
const gl::Rectangle &sourceAreaIn,
const gl::Rectangle &destAreaIn,
GLenum filter)
{
ANGLE_TRY(initializeResources(context));
BlitProgram *blitProgram = nullptr;
ANGLE_TRY(getBlitProgram(context, gl::TextureType::_2D, GL_FLOAT, GL_FLOAT, &blitProgram));
// We'll keep things simple by removing reversed coordinates from the rectangles. In the end
// we'll apply the reversal to the source texture coordinates if needed. The destination
// rectangle will be set to the gl viewport, which can't be reversed.
bool reverseX = sourceAreaIn.isReversedX() != destAreaIn.isReversedX();
bool reverseY = sourceAreaIn.isReversedY() != destAreaIn.isReversedY();
gl::Rectangle sourceArea = sourceAreaIn.removeReversal();
gl::Rectangle destArea = destAreaIn.removeReversal();
const gl::FramebufferAttachment *readAttachment = source->getReadColorAttachment();
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);
if (!gl::ClipRectangle(sourceArea, sourceBounds, &inBoundsSource))
{
// 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.
return angle::Result::Continue;
}
}
// The blit will be emulated by getting the source of the blit in a texture and sampling it
// with CLAMP_TO_EDGE.
GLuint textureId;
// TODO(cwallez) once texture dirty bits are landed, reuse attached texture instead of using
// CopyTexImage2D
{
textureId = mScratchTextures[0];
GLenum format = readAttachment->getFormat().info->internalFormat;
const FramebufferGL *sourceGL = GetImplAs<FramebufferGL>(source);
mStateManager->bindFramebuffer(GL_READ_FRAMEBUFFER, sourceGL->getFramebufferID());
mStateManager->bindTexture(gl::TextureType::_2D, textureId);
ANGLE_GL_TRY_ALWAYS_CHECK(
context,
mFunctions->copyTexImage2D(GL_TEXTURE_2D, 0, format, inBoundsSource.x, inBoundsSource.y,
inBoundsSource.width, inBoundsSource.height, 0));
// Translate sourceArea to be relative to the copied image.
sourceArea.x -= inBoundsSource.x;
sourceArea.y -= inBoundsSource.y;
ANGLE_TRY(setScratchTextureParameter(context, GL_TEXTURE_MIN_FILTER, filter));
ANGLE_TRY(setScratchTextureParameter(context, GL_TEXTURE_MAG_FILTER, filter));
ANGLE_TRY(setScratchTextureParameter(context, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
ANGLE_TRY(setScratchTextureParameter(context, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
}
// Transform the source area to the texture coordinate space (where 0.0 and 1.0 correspond to
// the edges of the texture).
Vector2 texCoordOffset(
static_cast<float>(sourceArea.x) / static_cast<float>(inBoundsSource.width),
static_cast<float>(sourceArea.y) / static_cast<float>(inBoundsSource.height));
// texCoordScale is equal to the size of the source area in texture coordinates.
Vector2 texCoordScale(
static_cast<float>(sourceArea.width) / static_cast<float>(inBoundsSource.width),
static_cast<float>(sourceArea.height) / static_cast<float>(inBoundsSource.height));
if (reverseX)
{
texCoordOffset.x() = texCoordOffset.x() + texCoordScale.x();
texCoordScale.x() = -texCoordScale.x();
}
if (reverseY)
{
texCoordOffset.y() = texCoordOffset.y() + texCoordScale.y();
texCoordScale.y() = -texCoordScale.y();
}
// Reset all the state except scissor and use the viewport to draw exactly to the destination
// rectangle
ScopedGLState scopedState;
ANGLE_TRY(scopedState.enter(context, destArea, ScopedGLState::KEEP_SCISSOR));
scopedState.willUseTextureUnit(context, 0);
// Set uniforms
mStateManager->activeTexture(0);
mStateManager->bindTexture(gl::TextureType::_2D, textureId);
mStateManager->useProgram(blitProgram->program);
ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->sourceTextureLocation, 0));
ANGLE_GL_TRY(context, mFunctions->uniform2f(blitProgram->scaleLocation, texCoordScale.x(),
texCoordScale.y()));
ANGLE_GL_TRY(context, mFunctions->uniform2f(blitProgram->offsetLocation, texCoordOffset.x(),
texCoordOffset.y()));
ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->multiplyAlphaLocation, 0));
ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->unMultiplyAlphaLocation, 0));
const FramebufferGL *destGL = GetImplAs<FramebufferGL>(dest);
mStateManager->bindFramebuffer(GL_DRAW_FRAMEBUFFER, destGL->getFramebufferID());
mStateManager->bindVertexArray(mVAO, 0);
ANGLE_GL_TRY(context, mFunctions->drawArrays(GL_TRIANGLES, 0, 3));
ANGLE_TRY(scopedState.exit(context));
return angle::Result::Continue;
}
angle::Result BlitGL::copySubTexture(const gl::Context *context,
TextureGL *source,
size_t sourceLevel,
GLenum sourceComponentType,
GLuint destID,
gl::TextureTarget destTarget,
size_t destLevel,
GLenum destComponentType,
const gl::Extents &sourceSize,
const gl::Rectangle &sourceArea,
const gl::Offset &destOffset,
bool needsLumaWorkaround,
GLenum lumaFormat,
bool unpackFlipY,
bool unpackPremultiplyAlpha,
bool unpackUnmultiplyAlpha,
bool *copySucceededOut)
{
ASSERT(source->getType() == gl::TextureType::_2D ||
source->getType() == gl::TextureType::External ||
source->getType() == gl::TextureType::Rectangle);
ANGLE_TRY(initializeResources(context));
// Make sure the destination texture can be rendered to before setting anything else up. Some
// cube maps may not be renderable until all faces have been filled.
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
ANGLE_GL_TRY(context, mFunctions->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
ToGLenum(destTarget), destID,
static_cast<GLint>(destLevel)));
GLenum status = ANGLE_GL_TRY(context, mFunctions->checkFramebufferStatus(GL_FRAMEBUFFER));
if (status != GL_FRAMEBUFFER_COMPLETE)
{
*copySucceededOut = false;
return angle::Result::Continue;
}
BlitProgram *blitProgram = nullptr;
ANGLE_TRY(getBlitProgram(context, source->getType(), sourceComponentType, destComponentType,
&blitProgram));
// Setup the source texture
if (needsLumaWorkaround)
{
GLint luminance = (lumaFormat == GL_ALPHA) ? GL_ZERO : GL_RED;
GLint alpha = GL_RED;
if (lumaFormat == GL_LUMINANCE)
{
alpha = GL_ONE;
}
else if (lumaFormat == GL_LUMINANCE_ALPHA)
{
alpha = GL_GREEN;
}
else
{
ASSERT(lumaFormat == GL_ALPHA);
}
GLint swizzle[4] = {luminance, luminance, luminance, alpha};
ANGLE_TRY(source->setSwizzle(context, swizzle));
}
ANGLE_TRY(source->setMinFilter(context, GL_NEAREST));
ANGLE_TRY(source->setMagFilter(context, GL_NEAREST));
ANGLE_TRY(source->setBaseLevel(context, static_cast<GLuint>(sourceLevel)));
// Render to the destination texture, sampling from the source texture
ScopedGLState scopedState;
ANGLE_TRY(scopedState.enter(
context, gl::Rectangle(destOffset.x, destOffset.y, sourceArea.width, sourceArea.height)));
scopedState.willUseTextureUnit(context, 0);
mStateManager->activeTexture(0);
mStateManager->bindTexture(source->getType(), source->getTextureID());
Vector2 scale(sourceArea.width, sourceArea.height);
Vector2 offset(sourceArea.x, sourceArea.y);
if (source->getType() != gl::TextureType::Rectangle)
{
scale.x() /= static_cast<float>(sourceSize.width);
scale.y() /= static_cast<float>(sourceSize.height);
offset.x() /= static_cast<float>(sourceSize.width);
offset.y() /= static_cast<float>(sourceSize.height);
}
if (unpackFlipY)
{
offset.y() += scale.y();
scale.y() = -scale.y();
}
mStateManager->useProgram(blitProgram->program);
ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->sourceTextureLocation, 0));
ANGLE_GL_TRY(context, mFunctions->uniform2f(blitProgram->scaleLocation, scale.x(), scale.y()));
ANGLE_GL_TRY(context,
mFunctions->uniform2f(blitProgram->offsetLocation, offset.x(), offset.y()));
if (unpackPremultiplyAlpha == unpackUnmultiplyAlpha)
{
ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->multiplyAlphaLocation, 0));
ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->unMultiplyAlphaLocation, 0));
}
else
{
ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->multiplyAlphaLocation,
unpackPremultiplyAlpha));
ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->unMultiplyAlphaLocation,
unpackUnmultiplyAlpha));
}
mStateManager->bindVertexArray(mVAO, 0);
ANGLE_GL_TRY(context, mFunctions->drawArrays(GL_TRIANGLES, 0, 3));
*copySucceededOut = true;
ANGLE_TRY(scopedState.exit(context));
return angle::Result::Continue;
}
angle::Result BlitGL::copySubTextureCPUReadback(const gl::Context *context,
TextureGL *source,
size_t sourceLevel,
GLenum sourceSizedInternalFormat,
TextureGL *dest,
gl::TextureTarget destTarget,
size_t destLevel,
GLenum destFormat,
GLenum destType,
const gl::Extents &sourceSize,
const gl::Rectangle &sourceArea,
const gl::Offset &destOffset,
bool needsLumaWorkaround,
GLenum lumaFormat,
bool unpackFlipY,
bool unpackPremultiplyAlpha,
bool unpackUnmultiplyAlpha)
{
ANGLE_TRY(initializeResources(context));
ContextGL *contextGL = GetImplAs<ContextGL>(context);
ASSERT(source->getType() == gl::TextureType::_2D ||
source->getType() == gl::TextureType::External ||
source->getType() == gl::TextureType::Rectangle);
const auto &destInternalFormatInfo = gl::GetInternalFormatInfo(destFormat, destType);
const gl::InternalFormat &sourceInternalFormatInfo =
gl::GetSizedInternalFormatInfo(sourceSizedInternalFormat);
gl::Rectangle readPixelsArea = sourceArea;
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
ANGLE_GL_TRY(context, mFunctions->framebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, ToGLenum(source->getType()),
source->getTextureID(), static_cast<GLint>(sourceLevel)));
GLenum status = ANGLE_GL_TRY(context, mFunctions->checkFramebufferStatus(GL_FRAMEBUFFER));
if (status != GL_FRAMEBUFFER_COMPLETE)
{
// The source texture cannot be read with glReadPixels. Copy it into another RGBA texture
// and read that back instead.
nativegl::TexImageFormat texImageFormat = nativegl::GetTexImageFormat(
mFunctions, mFeatures, sourceInternalFormatInfo.internalFormat,
sourceInternalFormatInfo.format, sourceInternalFormatInfo.type);
gl::TextureType scratchTextureType = gl::TextureType::_2D;
mStateManager->bindTexture(scratchTextureType, mScratchTextures[0]);
ANGLE_GL_TRY_ALWAYS_CHECK(
context,
mFunctions->texImage2D(ToGLenum(scratchTextureType), 0, texImageFormat.internalFormat,
sourceArea.width, sourceArea.height, 0, texImageFormat.format,
texImageFormat.type, nullptr));
bool copySucceeded = false;
ANGLE_TRY(copySubTexture(
context, source, sourceLevel, sourceInternalFormatInfo.componentType,
mScratchTextures[0], NonCubeTextureTypeToTarget(scratchTextureType), 0,
sourceInternalFormatInfo.componentType, sourceSize, sourceArea, gl::Offset(0, 0, 0),
needsLumaWorkaround, lumaFormat, false, false, false, &copySucceeded));
if (!copySucceeded)
{
// No fallback options if we can't render to the scratch texture.
return angle::Result::Stop;
}
// Bind the scratch texture as the readback texture
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
ANGLE_GL_TRY(context, mFunctions->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
ToGLenum(scratchTextureType),
mScratchTextures[0], 0));
// The scratch texture sized to sourceArea so adjust the readpixels area
readPixelsArea.x = 0;
readPixelsArea.y = 0;
// Recheck the status
status = ANGLE_GL_TRY(context, mFunctions->checkFramebufferStatus(GL_FRAMEBUFFER));
}
ASSERT(status == GL_FRAMEBUFFER_COMPLETE);
// Create a buffer for holding the source and destination memory
const size_t sourcePixelSize = 4;
size_t sourceBufferSize = readPixelsArea.width * readPixelsArea.height * sourcePixelSize;
size_t destBufferSize =
readPixelsArea.width * readPixelsArea.height * destInternalFormatInfo.pixelBytes;
angle::MemoryBuffer *buffer = nullptr;
ANGLE_CHECK_GL_ALLOC(contextGL,
context->getScratchBuffer(sourceBufferSize + destBufferSize, &buffer));
uint8_t *sourceMemory = buffer->data();
uint8_t *destMemory = buffer->data() + sourceBufferSize;
GLenum readPixelsFormat = GL_NONE;
PixelReadFunction readFunction = nullptr;
if (sourceInternalFormatInfo.componentType == GL_UNSIGNED_INT)
{
readPixelsFormat = GL_RGBA_INTEGER;
readFunction = angle::ReadColor<angle::R8G8B8A8, GLuint>;
}
else
{
ASSERT(sourceInternalFormatInfo.componentType != GL_INT);
readPixelsFormat = GL_RGBA;
readFunction = angle::ReadColor<angle::R8G8B8A8, GLfloat>;
}
gl::PixelUnpackState unpack;
unpack.alignment = 1;
mStateManager->setPixelUnpackState(unpack);
mStateManager->setPixelUnpackBuffer(nullptr);
ANGLE_GL_TRY(context, mFunctions->readPixels(readPixelsArea.x, readPixelsArea.y,
readPixelsArea.width, readPixelsArea.height,
readPixelsFormat, GL_UNSIGNED_BYTE, sourceMemory));
angle::FormatID destFormatID =
angle::Format::InternalFormatToID(destInternalFormatInfo.sizedInternalFormat);
const auto &destFormatInfo = angle::Format::Get(destFormatID);
CopyImageCHROMIUM(
sourceMemory, readPixelsArea.width * sourcePixelSize, sourcePixelSize, 0, readFunction,
destMemory, readPixelsArea.width * destInternalFormatInfo.pixelBytes,
destInternalFormatInfo.pixelBytes, 0, destFormatInfo.pixelWriteFunction,
destInternalFormatInfo.format, destInternalFormatInfo.componentType, readPixelsArea.width,
readPixelsArea.height, 1, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha);
gl::PixelPackState pack;
pack.alignment = 1;
mStateManager->setPixelPackState(pack);
mStateManager->setPixelPackBuffer(nullptr);
nativegl::TexSubImageFormat texSubImageFormat =
nativegl::GetTexSubImageFormat(mFunctions, mFeatures, destFormat, destType);
mStateManager->bindTexture(dest->getType(), dest->getTextureID());
ANGLE_GL_TRY(context, mFunctions->texSubImage2D(
ToGLenum(destTarget), static_cast<GLint>(destLevel), destOffset.x,
destOffset.y, readPixelsArea.width, readPixelsArea.height,
texSubImageFormat.format, texSubImageFormat.type, destMemory));
return angle::Result::Continue;
}
angle::Result BlitGL::copyTexSubImage(const gl::Context *context,
TextureGL *source,
size_t sourceLevel,
TextureGL *dest,
gl::TextureTarget destTarget,
size_t destLevel,
const gl::Rectangle &sourceArea,
const gl::Offset &destOffset,
bool *copySucceededOut)
{
ANGLE_TRY(initializeResources(context));
// Make sure the source texture can create a complete framebuffer before continuing.
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
ANGLE_GL_TRY(context, mFunctions->framebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, ToGLenum(source->getType()),
source->getTextureID(), static_cast<GLint>(sourceLevel)));
GLenum status = ANGLE_GL_TRY(context, mFunctions->checkFramebufferStatus(GL_FRAMEBUFFER));
if (status != GL_FRAMEBUFFER_COMPLETE)
{
*copySucceededOut = false;
return angle::Result::Continue;
}
mStateManager->bindTexture(dest->getType(), dest->getTextureID());
ANGLE_GL_TRY(context,
mFunctions->copyTexSubImage2D(ToGLenum(destTarget), static_cast<GLint>(destLevel),
destOffset.x, destOffset.y, sourceArea.x,
sourceArea.y, sourceArea.width, sourceArea.height));
*copySucceededOut = true;
return angle::Result::Continue;
}
angle::Result BlitGL::clearRenderableTexture(const gl::Context *context,
TextureGL *source,
GLenum sizedInternalFormat,
int numTextureLayers,
const gl::ImageIndex &imageIndex,
bool *clearSucceededOut)
{
ANGLE_TRY(initializeResources(context));
ClearBindTargetVector bindTargets;
ClearBindTargetVector unbindTargets;
GLbitfield clearMask = 0;
ANGLE_TRY(PrepareForClear(mStateManager, sizedInternalFormat, &bindTargets, &unbindTargets,
&clearMask));
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
ANGLE_TRY(UnbindAttachments(context, mFunctions, GL_FRAMEBUFFER, unbindTargets));
if (nativegl::UseTexImage2D(source->getType()))
{
ASSERT(numTextureLayers == 1);
for (GLenum bindTarget : bindTargets)
{
ANGLE_GL_TRY(context, mFunctions->framebufferTexture2D(
GL_FRAMEBUFFER, bindTarget, ToGLenum(imageIndex.getTarget()),
source->getTextureID(), imageIndex.getLevelIndex()));
}
GLenum status = ANGLE_GL_TRY(context, mFunctions->checkFramebufferStatus(GL_FRAMEBUFFER));
if (status == GL_FRAMEBUFFER_COMPLETE)
{
ANGLE_GL_TRY(context, mFunctions->clear(clearMask));
}
else
{
ANGLE_TRY(UnbindAttachments(context, mFunctions, GL_FRAMEBUFFER, bindTargets));
*clearSucceededOut = false;
return angle::Result::Continue;
}
}
else
{
ASSERT(nativegl::UseTexImage3D(source->getType()));
// Check if it's possible to bind all layers of the texture at once
if (mFunctions->framebufferTexture && !imageIndex.hasLayer())
{
for (GLenum bindTarget : bindTargets)
{
ANGLE_GL_TRY(context, mFunctions->framebufferTexture(GL_FRAMEBUFFER, bindTarget,
source->getTextureID(),
imageIndex.getLevelIndex()));
}
GLenum status =
ANGLE_GL_TRY(context, mFunctions->checkFramebufferStatus(GL_FRAMEBUFFER));
if (status == GL_FRAMEBUFFER_COMPLETE)
{
ANGLE_GL_TRY(context, mFunctions->clear(clearMask));
}
else
{
ANGLE_TRY(UnbindAttachments(context, mFunctions, GL_FRAMEBUFFER, bindTargets));
*clearSucceededOut = false;
return angle::Result::Continue;
}
}
else
{
GLint firstLayer = 0;
GLint layerCount = numTextureLayers;
if (imageIndex.hasLayer())
{
firstLayer = imageIndex.getLayerIndex();
layerCount = imageIndex.getLayerCount();
}
for (GLint layer = 0; layer < layerCount; layer++)
{
for (GLenum bindTarget : bindTargets)
{
ANGLE_GL_TRY(context, mFunctions->framebufferTextureLayer(
GL_FRAMEBUFFER, bindTarget, source->getTextureID(),
imageIndex.getLevelIndex(), layer + firstLayer));
}
GLenum status =
ANGLE_GL_TRY(context, mFunctions->checkFramebufferStatus(GL_FRAMEBUFFER));
if (status == GL_FRAMEBUFFER_COMPLETE)
{
ANGLE_GL_TRY(context, mFunctions->clear(clearMask));
}
else
{
ANGLE_TRY(UnbindAttachments(context, mFunctions, GL_FRAMEBUFFER, bindTargets));
*clearSucceededOut = false;
return angle::Result::Continue;
}
}
}
}
ANGLE_TRY(UnbindAttachments(context, mFunctions, GL_FRAMEBUFFER, bindTargets));
*clearSucceededOut = true;
return angle::Result::Continue;
}
angle::Result BlitGL::clearRenderbuffer(const gl::Context *context,
RenderbufferGL *source,
GLenum sizedInternalFormat)
{
ANGLE_TRY(initializeResources(context));
ClearBindTargetVector bindTargets;
ClearBindTargetVector unbindTargets;
GLbitfield clearMask = 0;
ANGLE_TRY(PrepareForClear(mStateManager, sizedInternalFormat, &bindTargets, &unbindTargets,
&clearMask));
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
ANGLE_TRY(UnbindAttachments(context, mFunctions, GL_FRAMEBUFFER, unbindTargets));
for (GLenum bindTarget : bindTargets)
{
ANGLE_GL_TRY(context,
mFunctions->framebufferRenderbuffer(
GL_FRAMEBUFFER, bindTarget, GL_RENDERBUFFER, source->getRenderbufferID()));
}
ANGLE_GL_TRY(context, mFunctions->clear(clearMask));
// Unbind
for (GLenum bindTarget : bindTargets)
{
ANGLE_GL_TRY(context, mFunctions->framebufferRenderbuffer(GL_FRAMEBUFFER, bindTarget,
GL_RENDERBUFFER, 0));
}
return angle::Result::Continue;
}
angle::Result BlitGL::clearFramebuffer(const gl::Context *context, FramebufferGL *source)
{
// initializeResources skipped because no local state is used
// Clear all attachments
GLbitfield clearMask = 0;
ANGLE_TRY(SetClearState(mStateManager, true, true, true, &clearMask));
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, source->getFramebufferID());
ANGLE_GL_TRY(context, mFunctions->clear(clearMask));
return angle::Result::Continue;
}
angle::Result BlitGL::clearRenderableTextureAlphaToOne(const gl::Context *context,
GLuint texture,
gl::TextureTarget target,
size_t level)
{
// Clearing the alpha of 3D textures is not supported/needed yet.
ASSERT(nativegl::UseTexImage2D(TextureTargetToType(target)));
ANGLE_TRY(initializeResources(context));
mStateManager->setClearColor(gl::ColorF(0.0f, 0.0f, 0.0f, 1.0f));
mStateManager->setColorMask(false, false, false, true);
mStateManager->setScissorTestEnabled(false);
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
ANGLE_GL_TRY(context, mFunctions->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
ToGLenum(target), texture,
static_cast<GLint>(level)));
ANGLE_GL_TRY(context, mFunctions->clear(GL_COLOR_BUFFER_BIT));
// Unbind the texture from the the scratch framebuffer
ANGLE_GL_TRY(context, mFunctions->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, 0));
return angle::Result::Continue;
}
angle::Result BlitGL::initializeResources(const gl::Context *context)
{
for (size_t i = 0; i < ArraySize(mScratchTextures); i++)
{
if (mScratchTextures[i] == 0)
{
ANGLE_GL_TRY(context, mFunctions->genTextures(1, &mScratchTextures[i]));
}
}
if (mScratchFBO == 0)
{
ANGLE_GL_TRY(context, mFunctions->genFramebuffers(1, &mScratchFBO));
}
if (mVertexBuffer == 0)
{
ANGLE_GL_TRY(context, mFunctions->genBuffers(1, &mVertexBuffer));
mStateManager->bindBuffer(gl::BufferBinding::Array, mVertexBuffer);
// Use 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.
float vertexData[] = {
-0.5f, 0.0f, 1.5f, 0.0f, 0.5f, 2.0f,
};
ANGLE_GL_TRY(context, mFunctions->bufferData(GL_ARRAY_BUFFER, sizeof(float) * 6, vertexData,
GL_STATIC_DRAW));
}
if (mVAO == 0)
{
ANGLE_GL_TRY(context, mFunctions->genVertexArrays(1, &mVAO));
mStateManager->bindVertexArray(mVAO, 0);
mStateManager->bindBuffer(gl::BufferBinding::Array, mVertexBuffer);
// Enable all attributes with the same buffer so that it doesn't matter what location the
// texcoord attribute is assigned
GLint maxAttributes = 0;
ANGLE_GL_TRY(context, mFunctions->getIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttributes));
for (GLint i = 0; i < maxAttributes; i++)
{
ANGLE_GL_TRY(context, mFunctions->enableVertexAttribArray(i));
ANGLE_GL_TRY(context,
mFunctions->vertexAttribPointer(i, 2, GL_FLOAT, GL_FALSE, 0, nullptr));
}
}
return angle::Result::Continue;
}
angle::Result BlitGL::orphanScratchTextures(const gl::Context *context)
{
for (auto texture : mScratchTextures)
{
mStateManager->bindTexture(gl::TextureType::_2D, texture);
gl::PixelUnpackState unpack;
mStateManager->setPixelUnpackState(unpack);
mStateManager->setPixelUnpackBuffer(nullptr);
GLint swizzle[4] = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};
ANGLE_GL_TRY(context,
mFunctions->texParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle));
ANGLE_GL_TRY(context, mFunctions->texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr));
}
return angle::Result::Continue;
}
angle::Result BlitGL::setScratchTextureParameter(const gl::Context *context,
GLenum param,
GLenum value)
{
for (auto texture : mScratchTextures)
{
mStateManager->bindTexture(gl::TextureType::_2D, texture);
ANGLE_GL_TRY(context, mFunctions->texParameteri(GL_TEXTURE_2D, param, value));
ANGLE_GL_TRY(context, mFunctions->texParameteri(GL_TEXTURE_2D, param, value));
}
return angle::Result::Continue;
}
angle::Result BlitGL::getBlitProgram(const gl::Context *context,
gl::TextureType sourceTextureType,
GLenum sourceComponentType,
GLenum destComponentType,
BlitProgram **program)
{
BlitProgramType programType(sourceTextureType, sourceComponentType, destComponentType);
BlitProgram &result = mBlitPrograms[programType];
if (result.program == 0)
{
result.program = ANGLE_GL_TRY(context, mFunctions->createProgram());
// Depending on what types need to be output by the shaders, different versions need to be
// used.
std::string version;
std::string vsInputVariableQualifier;
std::string vsOutputVariableQualifier;
std::string fsInputVariableQualifier;
std::string fsOutputVariableQualifier;
std::string sampleFunction;
if (sourceComponentType != GL_UNSIGNED_INT && destComponentType != GL_UNSIGNED_INT &&
sourceTextureType != gl::TextureType::Rectangle)
{
// Simple case, float-to-float with 2D or external textures. Only needs ESSL/GLSL 100
version = "100";
vsInputVariableQualifier = "attribute";
vsOutputVariableQualifier = "varying";
fsInputVariableQualifier = "varying";
fsOutputVariableQualifier = "";
sampleFunction = "texture2D";
}
else
{
// Need to use a higher version to support non-float output types
if (mFunctions->standard == STANDARD_GL_DESKTOP)
{
version = "330";
}
else
{
ASSERT(mFunctions->standard == STANDARD_GL_ES);
version = "300 es";
}
vsInputVariableQualifier = "in";
vsOutputVariableQualifier = "out";
fsInputVariableQualifier = "in";
fsOutputVariableQualifier = "out";
sampleFunction = "texture";
}
{
// Compile the vertex shader
std::ostringstream vsSourceStream;
vsSourceStream << "#version " << version << "\n";
vsSourceStream << vsInputVariableQualifier << " vec2 a_texcoord;\n";
vsSourceStream << "uniform vec2 u_scale;\n";
vsSourceStream << "uniform vec2 u_offset;\n";
vsSourceStream << vsOutputVariableQualifier << " vec2 v_texcoord;\n";
vsSourceStream << "\n";
vsSourceStream << "void main()\n";
vsSourceStream << "{\n";
vsSourceStream << " gl_Position = vec4((a_texcoord * 2.0) - 1.0, 0.0, 1.0);\n";
vsSourceStream << " v_texcoord = a_texcoord * u_scale + u_offset;\n";
vsSourceStream << "}\n";
std::string vsSourceStr = vsSourceStream.str();
const char *vsSourceCStr = vsSourceStr.c_str();
GLuint vs = ANGLE_GL_TRY(context, mFunctions->createShader(GL_VERTEX_SHADER));
ANGLE_GL_TRY(context, mFunctions->shaderSource(vs, 1, &vsSourceCStr, nullptr));
ANGLE_GL_TRY(context, mFunctions->compileShader(vs));
ANGLE_TRY(CheckCompileStatus(context, mFunctions, vs));
ANGLE_GL_TRY(context, mFunctions->attachShader(result.program, vs));
ANGLE_GL_TRY(context, mFunctions->deleteShader(vs));
}
{
// Sampling texture uniform changes depending on source texture type.
std::string samplerType;
switch (sourceTextureType)
{
case gl::TextureType::_2D:
switch (sourceComponentType)
{
case GL_UNSIGNED_INT:
samplerType = "usampler2D";
break;
default: // Float type
samplerType = "sampler2D";
break;
}
break;
case gl::TextureType::External:
ASSERT(sourceComponentType != GL_UNSIGNED_INT);
samplerType = "samplerExternalOES";
break;
case gl::TextureType::Rectangle:
ASSERT(sourceComponentType != GL_UNSIGNED_INT);
samplerType = "sampler2DRect";
break;
default:
UNREACHABLE();
break;
}
std::string samplerResultType;
switch (sourceComponentType)
{
case GL_UNSIGNED_INT:
samplerResultType = "uvec4";
break;
default: // Float type
samplerResultType = "vec4";
break;
}
std::string extensionRequirements;
switch (sourceTextureType)
{
case gl::TextureType::External:
extensionRequirements = "#extension GL_OES_EGL_image_external : require";
break;
case gl::TextureType::Rectangle:
if (mFunctions->hasGLExtension("GL_ARB_texture_rectangle"))
{
extensionRequirements = "#extension GL_ARB_texture_rectangle : require";
}
else
{
ASSERT(mFunctions->isAtLeastGL(gl::Version(3, 1)));
}
break;
default:
break;
}
// Output variables depend on the output type
std::string outputType;
std::string outputVariableName;
std::string outputMultiplier;
switch (destComponentType)
{
case GL_UNSIGNED_INT:
outputType = "uvec4";
outputVariableName = "outputUint";
outputMultiplier = "255.0";
break;
default: // float type
if (version == "100")
{
outputType = "";
outputVariableName = "gl_FragColor";
outputMultiplier = "1.0";
}
else
{
outputType = "vec4";
outputVariableName = "outputFloat";
outputMultiplier = "1.0";
}
break;
}
// Compile the fragment shader
std::ostringstream fsSourceStream;
fsSourceStream << "#version " << version << "\n";
fsSourceStream << extensionRequirements << "\n";
fsSourceStream << "precision highp float;\n";
fsSourceStream << "uniform " << samplerType << " u_source_texture;\n";
// Write the rest of the uniforms and varyings
fsSourceStream << "uniform bool u_multiply_alpha;\n";
fsSourceStream << "uniform bool u_unmultiply_alpha;\n";
fsSourceStream << fsInputVariableQualifier << " vec2 v_texcoord;\n";
if (!outputType.empty())
{
fsSourceStream << fsOutputVariableQualifier << " " << outputType << " "
<< outputVariableName << ";\n";
}
// Write the main body
fsSourceStream << "\n";
fsSourceStream << "void main()\n";
fsSourceStream << "{\n";
std::string maxTexcoord;
switch (sourceTextureType)
{
case gl::TextureType::Rectangle:
// Valid texcoords are within source texture size
maxTexcoord = "vec2(textureSize(u_source_texture))";
break;
default:
// Valid texcoords are in [0, 1]
maxTexcoord = "vec2(1.0)";
break;
}
// discard if the texcoord is invalid so the blitframebuffer workaround doesn't
// write when the point sampled is outside of the source framebuffer.
fsSourceStream << " if (clamp(v_texcoord, vec2(0.0), " << maxTexcoord
<< ") != v_texcoord)\n";
fsSourceStream << " {\n";
fsSourceStream << " discard;\n";
fsSourceStream << " }\n";
// Sampling code depends on the input data type
fsSourceStream << " " << samplerResultType << " color = " << sampleFunction
<< "(u_source_texture, v_texcoord);\n";
// Perform the premultiply or unmultiply alpha logic
fsSourceStream << " if (u_multiply_alpha)\n";
fsSourceStream << " {\n";
fsSourceStream << " color.xyz = color.xyz * color.a;\n";
fsSourceStream << " }\n";
fsSourceStream << " if (u_unmultiply_alpha && color.a != 0.0)\n";
fsSourceStream << " {\n";
fsSourceStream << " color.xyz = color.xyz / color.a;\n";
fsSourceStream << " }\n";
// Write the conversion to the destionation type
fsSourceStream << " color = color * " << outputMultiplier << ";\n";
// Write the output assignment code
fsSourceStream << " " << outputVariableName << " = " << outputType << "(color);\n";
fsSourceStream << "}\n";
std::string fsSourceStr = fsSourceStream.str();
const char *fsSourceCStr = fsSourceStr.c_str();
GLuint fs = ANGLE_GL_TRY(context, mFunctions->createShader(GL_FRAGMENT_SHADER));
ANGLE_GL_TRY(context, mFunctions->shaderSource(fs, 1, &fsSourceCStr, nullptr));
ANGLE_GL_TRY(context, mFunctions->compileShader(fs));
ANGLE_TRY(CheckCompileStatus(context, mFunctions, fs));
ANGLE_GL_TRY(context, mFunctions->attachShader(result.program, fs));
ANGLE_GL_TRY(context, mFunctions->deleteShader(fs));
}
ANGLE_GL_TRY(context, mFunctions->linkProgram(result.program));
ANGLE_TRY(CheckLinkStatus(context, mFunctions, result.program));
result.sourceTextureLocation = ANGLE_GL_TRY(
context, mFunctions->getUniformLocation(result.program, "u_source_texture"));
result.scaleLocation =
ANGLE_GL_TRY(context, mFunctions->getUniformLocation(result.program, "u_scale"));
result.offsetLocation =
ANGLE_GL_TRY(context, mFunctions->getUniformLocation(result.program, "u_offset"));
result.multiplyAlphaLocation = ANGLE_GL_TRY(
context, mFunctions->getUniformLocation(result.program, "u_multiply_alpha"));
result.unMultiplyAlphaLocation = ANGLE_GL_TRY(
context, mFunctions->getUniformLocation(result.program, "u_unmultiply_alpha"));
}
*program = &result;
return angle::Result::Continue;
}
} // namespace rx