blob: b85c80ed313be7b27841960a3e9846e357b82676 [file] [log] [blame]
/*
Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
Copyright (C) 2012 Igalia S.L.
Copyright (C) 2012 Adobe Systems Incorporated
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "TextureMapperGL.h"
#if USE(TEXTURE_MAPPER_GL)
#include "BitmapTextureGL.h"
#include "BitmapTexturePool.h"
#include "FilterOperations.h"
#include "FloatQuad.h"
#include "FloatRoundedRect.h"
#include "GLContext.h"
#include "GraphicsContext.h"
#include "Image.h"
#include "LengthFunctions.h"
#include "NotImplemented.h"
#include "TextureMapperShaderProgram.h"
#include "Timer.h"
#include <wtf/HashMap.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/Ref.h>
#include <wtf/RefCounted.h>
#include <wtf/SetForScope.h>
#if USE(CAIRO)
#include "CairoUtilities.h"
#include "RefPtrCairo.h"
#include <cairo.h>
#include <wtf/text/CString.h>
#endif
namespace WebCore {
class TextureMapperGLData {
WTF_MAKE_FAST_ALLOCATED;
public:
explicit TextureMapperGLData(void*);
~TextureMapperGLData();
void initializeStencil();
GLuint getStaticVBO(GLenum target, GLsizeiptr, const void* data);
GLuint getVAO();
Ref<TextureMapperShaderProgram> getShaderProgram(TextureMapperShaderProgram::Options);
TransformationMatrix projectionMatrix;
TextureMapper::PaintFlags PaintFlags { 0 };
GLint previousProgram { 0 };
GLint previousVAO { 0 };
GLint targetFrameBuffer { 0 };
bool didModifyStencil { false };
GLint previousScissorState { 0 };
GLint previousDepthState { 0 };
GLint viewport[4] { 0, };
GLint previousScissor[4] { 0, };
RefPtr<BitmapTexture> currentSurface;
const BitmapTextureGL::FilterInfo* filterInfo { nullptr };
private:
class SharedGLData : public RefCounted<SharedGLData> {
public:
static Ref<SharedGLData> currentSharedGLData(void* platformContext)
{
auto it = contextDataMap().find(platformContext);
if (it != contextDataMap().end())
return *it->value;
Ref<SharedGLData> data = adoptRef(*new SharedGLData);
contextDataMap().add(platformContext, data.ptr());
return data;
}
~SharedGLData()
{
ASSERT(std::any_of(contextDataMap().begin(), contextDataMap().end(),
[this](auto& entry) { return entry.value == this; }));
contextDataMap().removeIf([this](auto& entry) { return entry.value == this; });
}
private:
friend class TextureMapperGLData;
using GLContextDataMap = HashMap<void*, SharedGLData*>;
static GLContextDataMap& contextDataMap()
{
static NeverDestroyed<GLContextDataMap> map;
return map;
}
SharedGLData() = default;
HashMap<unsigned, RefPtr<TextureMapperShaderProgram>> m_programs;
};
Ref<SharedGLData> m_sharedGLData;
HashMap<const void*, GLuint> m_vbos;
GLuint m_vao { 0 };
};
TextureMapperGLData::TextureMapperGLData(void* platformContext)
: m_sharedGLData(SharedGLData::currentSharedGLData(platformContext))
{
}
TextureMapperGLData::~TextureMapperGLData()
{
for (auto& entry : m_vbos)
glDeleteBuffers(1, &entry.value);
#if !USE(OPENGL_ES)
if (GLContext::current()->version() >= 320 && m_vao)
glDeleteVertexArrays(1, &m_vao);
#endif
}
void TextureMapperGLData::initializeStencil()
{
if (currentSurface) {
static_cast<BitmapTextureGL*>(currentSurface.get())->initializeStencil();
return;
}
if (didModifyStencil)
return;
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
didModifyStencil = true;
}
GLuint TextureMapperGLData::getStaticVBO(GLenum target, GLsizeiptr size, const void* data)
{
auto addResult = m_vbos.ensure(data,
[target, size, data] {
GLuint vbo = 0;
glGenBuffers(1, &vbo);
glBindBuffer(target, vbo);
glBufferData(target, size, data, GL_STATIC_DRAW);
return vbo;
});
return addResult.iterator->value;
}
GLuint TextureMapperGLData::getVAO()
{
#if !USE(OPENGL_ES)
if (GLContext::current()->version() >= 320 && !m_vao)
glGenVertexArrays(1, &m_vao);
#endif
return m_vao;
}
Ref<TextureMapperShaderProgram> TextureMapperGLData::getShaderProgram(TextureMapperShaderProgram::Options options)
{
ASSERT(!options.isEmpty());
auto addResult = m_sharedGLData->m_programs.ensure(options.toRaw(),
[options] { return TextureMapperShaderProgram::create(options); });
return *addResult.iterator->value;
}
TextureMapperGL::TextureMapperGL()
: m_contextAttributes(TextureMapperContextAttributes::get())
, m_enableEdgeDistanceAntialiasing(false)
{
void* platformContext = GLContext::current()->platformContext();
ASSERT(platformContext);
m_data = new TextureMapperGLData(platformContext);
#if USE(TEXTURE_MAPPER_GL)
m_texturePool = makeUnique<BitmapTexturePool>(m_contextAttributes);
#endif
}
ClipStack& TextureMapperGL::clipStack()
{
return data().currentSurface ? toBitmapTextureGL(data().currentSurface.get())->clipStack() : m_clipStack;
}
void TextureMapperGL::beginPainting(PaintFlags flags)
{
glGetIntegerv(GL_CURRENT_PROGRAM, &data().previousProgram);
data().previousScissorState = glIsEnabled(GL_SCISSOR_TEST);
data().previousDepthState = glIsEnabled(GL_DEPTH_TEST);
glDisable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_SCISSOR_TEST);
data().didModifyStencil = false;
glGetIntegerv(GL_VIEWPORT, data().viewport);
glGetIntegerv(GL_SCISSOR_BOX, data().previousScissor);
m_clipStack.reset(IntRect(0, 0, data().viewport[2], data().viewport[3]), flags & PaintingMirrored ? ClipStack::YAxisMode::Default : ClipStack::YAxisMode::Inverted);
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &data().targetFrameBuffer);
data().PaintFlags = flags;
bindSurface(0);
#if !USE(OPENGL_ES)
if (GLContext::current()->version() >= 320) {
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &data().previousVAO);
glBindVertexArray(data().getVAO());
}
#endif
}
void TextureMapperGL::endPainting()
{
if (data().didModifyStencil) {
glClearStencil(1);
glClear(GL_STENCIL_BUFFER_BIT);
}
glUseProgram(data().previousProgram);
glScissor(data().previousScissor[0], data().previousScissor[1], data().previousScissor[2], data().previousScissor[3]);
if (data().previousScissorState)
glEnable(GL_SCISSOR_TEST);
else
glDisable(GL_SCISSOR_TEST);
if (data().previousDepthState)
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
#if !USE(OPENGL_ES)
if (GLContext::current()->version() >= 320)
glBindVertexArray(data().previousVAO);
#endif
}
void TextureMapperGL::drawBorder(const Color& color, float width, const FloatRect& targetRect, const TransformationMatrix& modelViewMatrix)
{
if (clipStack().isCurrentScissorBoxEmpty())
return;
Ref<TextureMapperShaderProgram> program = data().getShaderProgram(TextureMapperShaderProgram::SolidColor);
glUseProgram(program->programID());
auto [r, g, b, a] = premultiplied(color.toColorTypeLossy<SRGBA<float>>()).resolved();
glUniform4f(program->colorLocation(), r, g, b, a);
glLineWidth(width);
draw(targetRect, modelViewMatrix, program.get(), GL_LINE_LOOP, !color.isOpaque() ? ShouldBlend : 0);
}
// FIXME: drawNumber() should save a number texture-atlas and re-use whenever possible.
void TextureMapperGL::drawNumber(int number, const Color& color, const FloatPoint& targetPoint, const TransformationMatrix& modelViewMatrix)
{
int pointSize = 8;
#if USE(CAIRO)
CString counterString = String::number(number).ascii();
// cairo_text_extents() requires a cairo_t, so dimensions need to be guesstimated.
int width = counterString.length() * pointSize * 1.2;
int height = pointSize * 1.5;
cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
cairo_t* cr = cairo_create(surface);
// Since we won't swap R+B when uploading a texture, paint with the swapped R+B color.
auto [r, g, b, a] = color.toColorTypeLossy<SRGBA<float>>().resolved();
cairo_set_source_rgba(cr, b, g, r, a);
cairo_rectangle(cr, 0, 0, width, height);
cairo_fill(cr);
cairo_select_font_face(cr, "Monospace", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, pointSize);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_move_to(cr, 2, pointSize);
cairo_show_text(cr, counterString.data());
IntSize size(width, height);
IntRect sourceRect(IntPoint::zero(), size);
IntRect targetRect(roundedIntPoint(targetPoint), size);
RefPtr<BitmapTexture> texture = acquireTextureFromPool(size);
const unsigned char* bits = cairo_image_surface_get_data(surface);
int stride = cairo_image_surface_get_stride(surface);
static_cast<BitmapTextureGL*>(texture.get())->updateContents(bits, sourceRect, IntPoint::zero(), stride);
drawTexture(*texture, targetRect, modelViewMatrix, 1.0f, AllEdges);
cairo_surface_destroy(surface);
cairo_destroy(cr);
#else
UNUSED_PARAM(number);
UNUSED_PARAM(pointSize);
UNUSED_PARAM(targetPoint);
UNUSED_PARAM(modelViewMatrix);
notImplemented();
#endif
}
static TextureMapperShaderProgram::Options optionsForFilterType(FilterOperation::OperationType type, unsigned pass)
{
switch (type) {
case FilterOperation::GRAYSCALE:
return { TextureMapperShaderProgram::TextureRGB, TextureMapperShaderProgram::GrayscaleFilter };
case FilterOperation::SEPIA:
return { TextureMapperShaderProgram::TextureRGB, TextureMapperShaderProgram::SepiaFilter };
case FilterOperation::SATURATE:
return { TextureMapperShaderProgram::TextureRGB, TextureMapperShaderProgram::SaturateFilter };
case FilterOperation::HUE_ROTATE:
return { TextureMapperShaderProgram::TextureRGB, TextureMapperShaderProgram::HueRotateFilter };
case FilterOperation::INVERT:
return { TextureMapperShaderProgram::TextureRGB, TextureMapperShaderProgram::InvertFilter };
case FilterOperation::BRIGHTNESS:
return { TextureMapperShaderProgram::TextureRGB, TextureMapperShaderProgram::BrightnessFilter };
case FilterOperation::CONTRAST:
return { TextureMapperShaderProgram::TextureRGB, TextureMapperShaderProgram::ContrastFilter };
case FilterOperation::OPACITY:
return { TextureMapperShaderProgram::TextureRGB, TextureMapperShaderProgram::OpacityFilter };
case FilterOperation::BLUR:
return { TextureMapperShaderProgram::BlurFilter };
case FilterOperation::DROP_SHADOW:
if (!pass)
return { TextureMapperShaderProgram::AlphaBlur };
return { TextureMapperShaderProgram::AlphaBlur, TextureMapperShaderProgram::ContentTexture, TextureMapperShaderProgram::SolidColor };
default:
ASSERT_NOT_REACHED();
return { };
}
}
// Create a normal distribution of 21 values between -2 and 2.
static const unsigned GaussianKernelHalfWidth = 11;
static const float GaussianKernelStep = 0.2;
static inline float gauss(float x)
{
return exp(-(x * x) / 2.);
}
static float* gaussianKernel()
{
static bool prepared = false;
static float kernel[GaussianKernelHalfWidth] = {0, };
if (prepared)
return kernel;
kernel[0] = gauss(0);
float sum = kernel[0];
for (unsigned i = 1; i < GaussianKernelHalfWidth; ++i) {
kernel[i] = gauss(i * GaussianKernelStep);
sum += 2 * kernel[i];
}
// Normalize the kernel.
float scale = 1 / sum;
for (unsigned i = 0; i < GaussianKernelHalfWidth; ++i)
kernel[i] *= scale;
prepared = true;
return kernel;
}
static void prepareFilterProgram(TextureMapperShaderProgram& program, const FilterOperation& operation, unsigned pass, const IntSize& size, GLuint contentTexture)
{
glUseProgram(program.programID());
switch (operation.type()) {
case FilterOperation::GRAYSCALE:
case FilterOperation::SEPIA:
case FilterOperation::SATURATE:
case FilterOperation::HUE_ROTATE:
glUniform1f(program.filterAmountLocation(), static_cast<const BasicColorMatrixFilterOperation&>(operation).amount());
break;
case FilterOperation::INVERT:
case FilterOperation::BRIGHTNESS:
case FilterOperation::CONTRAST:
case FilterOperation::OPACITY:
glUniform1f(program.filterAmountLocation(), static_cast<const BasicComponentTransferFilterOperation&>(operation).amount());
break;
case FilterOperation::BLUR: {
const BlurFilterOperation& blur = static_cast<const BlurFilterOperation&>(operation);
FloatSize radius;
// Blur is done in two passes, first horizontally and then vertically. The same shader is used for both.
if (pass)
radius.setHeight(floatValueForLength(blur.stdDeviation(), size.height()) / size.height());
else
radius.setWidth(floatValueForLength(blur.stdDeviation(), size.width()) / size.width());
glUniform2f(program.blurRadiusLocation(), radius.width(), radius.height());
glUniform1fv(program.gaussianKernelLocation(), GaussianKernelHalfWidth, gaussianKernel());
break;
}
case FilterOperation::DROP_SHADOW: {
const DropShadowFilterOperation& shadow = static_cast<const DropShadowFilterOperation&>(operation);
glUniform1fv(program.gaussianKernelLocation(), GaussianKernelHalfWidth, gaussianKernel());
switch (pass) {
case 0:
// First pass: horizontal alpha blur.
glUniform2f(program.blurRadiusLocation(), shadow.stdDeviation() / float(size.width()), 0);
glUniform2f(program.shadowOffsetLocation(), float(shadow.location().x()) / float(size.width()), float(shadow.location().y()) / float(size.height()));
break;
case 1:
// Second pass: we need the shadow color and the content texture for compositing.
auto [r, g, b, a] = premultiplied(shadow.color().toColorTypeLossy<SRGBA<float>>()).resolved();
glUniform4f(program.colorLocation(), r, g, b, a);
glUniform2f(program.blurRadiusLocation(), 0, shadow.stdDeviation() / float(size.height()));
glUniform2f(program.shadowOffsetLocation(), 0, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, contentTexture);
glUniform1i(program.contentTextureLocation(), 1);
break;
}
break;
}
default:
break;
}
}
static TransformationMatrix colorSpaceMatrixForFlags(TextureMapperGL::Flags flags)
{
// The matrix is initially the identity one, which means no color conversion.
TransformationMatrix matrix;
if (flags & TextureMapperGL::ShouldConvertTextureBGRAToRGBA)
matrix.setMatrix(0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
else if (flags & TextureMapperGL::ShouldConvertTextureARGBToRGBA)
matrix.setMatrix(0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0);
return matrix;
}
static void prepareRoundedRectClip(TextureMapperShaderProgram& program, const float* rects, const float* transforms, int nRects)
{
glUseProgram(program.programID());
glUniform1i(program.roundedRectNumberLocation(), nRects);
glUniform4fv(program.roundedRectLocation(), 3 * nRects, rects);
glUniformMatrix4fv(program.roundedRectInverseTransformMatrixLocation(), nRects, false, transforms);
}
void TextureMapperGL::drawTexture(const BitmapTexture& texture, const FloatRect& targetRect, const TransformationMatrix& matrix, float opacity, unsigned exposedEdges)
{
if (!texture.isValid())
return;
if (clipStack().isCurrentScissorBoxEmpty())
return;
const BitmapTextureGL& textureGL = static_cast<const BitmapTextureGL&>(texture);
SetForScope filterInfo(data().filterInfo, textureGL.filterInfo());
drawTexture(textureGL.id(), textureGL.colorConvertFlags() | (textureGL.isOpaque() ? 0 : ShouldBlend), textureGL.size(), targetRect, matrix, opacity, exposedEdges);
}
void TextureMapperGL::drawTexture(GLuint texture, Flags flags, const IntSize& textureSize, const FloatRect& targetRect, const TransformationMatrix& modelViewMatrix, float opacity, unsigned exposedEdges)
{
bool useAntialiasing = m_enableEdgeDistanceAntialiasing
&& exposedEdges == AllEdges
&& !modelViewMatrix.mapQuad(targetRect).isRectilinear();
TextureMapperShaderProgram::Options options;
if (opacity < 1)
options.add(TextureMapperShaderProgram::Opacity);
if (useAntialiasing) {
options.add(TextureMapperShaderProgram::Antialiasing);
flags |= ShouldAntialias;
}
if (wrapMode() == RepeatWrap && !m_contextAttributes.supportsNPOTTextures)
options.add(TextureMapperShaderProgram::ManualRepeat);
RefPtr<FilterOperation> filter = data().filterInfo ? data().filterInfo->filter: nullptr;
GLuint filterContentTextureID = 0;
if (filter) {
if (data().filterInfo->contentTexture)
filterContentTextureID = toBitmapTextureGL(data().filterInfo->contentTexture.get())->id();
options.add(optionsForFilterType(filter->type(), data().filterInfo->pass));
if (filter->affectsOpacity())
flags |= ShouldBlend;
} else
options.add(TextureMapperShaderProgram::TextureRGB);
if (useAntialiasing || opacity < 1)
flags |= ShouldBlend;
if (clipStack().isRoundedRectClipEnabled()) {
options.add(TextureMapperShaderProgram::RoundedRectClip);
flags |= ShouldBlend;
}
if (flags & ShouldPremultiply)
options.add(TextureMapperShaderProgram::Premultiply);
Ref<TextureMapperShaderProgram> program = data().getShaderProgram(options);
if (filter)
prepareFilterProgram(program.get(), *filter.get(), data().filterInfo->pass, textureSize, filterContentTextureID);
if (clipStack().isRoundedRectClipEnabled())
prepareRoundedRectClip(program.get(), clipStack().roundedRectComponents(), clipStack().roundedRectInverseTransformComponents(), clipStack().roundedRectCount());
drawTexturedQuadWithProgram(program.get(), texture, flags, targetRect, modelViewMatrix, opacity);
}
static void prepareTransformationMatrixWithFlags(TransformationMatrix& patternTransform, TextureMapperGL::Flags flags)
{
if (flags & TextureMapperGL::ShouldRotateTexture90) {
patternTransform.rotate(-90);
patternTransform.translate(-1, 0);
}
if (flags & TextureMapperGL::ShouldRotateTexture180) {
patternTransform.rotate(180);
patternTransform.translate(-1, -1);
}
if (flags & TextureMapperGL::ShouldRotateTexture270) {
patternTransform.rotate(-270);
patternTransform.translate(0, -1);
}
if (flags & TextureMapperGL::ShouldFlipTexture) {
patternTransform.flipY();
patternTransform.translate(0, -1);
}
}
void TextureMapperGL::drawTexturePlanarYUV(const std::array<GLuint, 3>& textures, const std::array<GLfloat, 16>& yuvToRgbMatrix, Flags flags, const IntSize& textureSize, const FloatRect& targetRect, const TransformationMatrix& modelViewMatrix, float opacity, std::optional<GLuint> alphaPlane, unsigned exposedEdges)
{
bool useAntialiasing = m_enableEdgeDistanceAntialiasing
&& exposedEdges == AllEdges
&& !modelViewMatrix.mapQuad(targetRect).isRectilinear();
TextureMapperShaderProgram::Options options = alphaPlane ? TextureMapperShaderProgram::TextureYUVA : TextureMapperShaderProgram::TextureYUV;
if (opacity < 1)
options.add(TextureMapperShaderProgram::Opacity);
if (useAntialiasing) {
options.add(TextureMapperShaderProgram::Antialiasing);
flags |= ShouldAntialias;
}
if (wrapMode() == RepeatWrap && !m_contextAttributes.supportsNPOTTextures)
options.add(TextureMapperShaderProgram::ManualRepeat);
RefPtr<FilterOperation> filter = data().filterInfo ? data().filterInfo->filter: nullptr;
GLuint filterContentTextureID = 0;
if (filter) {
if (data().filterInfo->contentTexture)
filterContentTextureID = toBitmapTextureGL(data().filterInfo->contentTexture.get())->id();
options.add(optionsForFilterType(filter->type(), data().filterInfo->pass));
if (filter->affectsOpacity())
flags |= ShouldBlend;
}
if (useAntialiasing || opacity < 1)
flags |= ShouldBlend;
if (clipStack().isRoundedRectClipEnabled()) {
options.add(TextureMapperShaderProgram::RoundedRectClip);
flags |= ShouldBlend;
}
if (flags & ShouldPremultiply)
options.add(TextureMapperShaderProgram::Premultiply);
Ref<TextureMapperShaderProgram> program = data().getShaderProgram(options);
if (filter)
prepareFilterProgram(program.get(), *filter.get(), data().filterInfo->pass, textureSize, filterContentTextureID);
if (clipStack().isRoundedRectClipEnabled())
prepareRoundedRectClip(program.get(), clipStack().roundedRectComponents(), clipStack().roundedRectInverseTransformComponents(), clipStack().roundedRectCount());
Vector<std::pair<GLuint, GLuint> > texturesAndSamplers = {
{ textures[0], program->samplerYLocation() },
{ textures[1], program->samplerULocation() },
{ textures[2], program->samplerVLocation() }
};
if (alphaPlane)
texturesAndSamplers.append({*alphaPlane, program->samplerALocation() });
glUseProgram(program->programID());
glUniformMatrix4fv(program->yuvToRgbLocation(), 1, GL_FALSE, static_cast<const GLfloat *>(&yuvToRgbMatrix[0]));
drawTexturedQuadWithProgram(program.get(), texturesAndSamplers, flags, targetRect, modelViewMatrix, opacity);
}
void TextureMapperGL::drawTextureSemiPlanarYUV(const std::array<GLuint, 2>& textures, bool uvReversed, const std::array<GLfloat, 16>& yuvToRgbMatrix, Flags flags, const IntSize& textureSize, const FloatRect& targetRect, const TransformationMatrix& modelViewMatrix, float opacity, unsigned exposedEdges)
{
bool useAntialiasing = m_enableEdgeDistanceAntialiasing
&& exposedEdges == AllEdges
&& !modelViewMatrix.mapQuad(targetRect).isRectilinear();
TextureMapperShaderProgram::Options options = uvReversed ?
TextureMapperShaderProgram::TextureNV21 : TextureMapperShaderProgram::TextureNV12;
if (opacity < 1)
options.add(TextureMapperShaderProgram::Opacity);
if (useAntialiasing) {
options.add(TextureMapperShaderProgram::Antialiasing);
flags |= ShouldAntialias;
}
if (wrapMode() == RepeatWrap && !m_contextAttributes.supportsNPOTTextures)
options.add(TextureMapperShaderProgram::ManualRepeat);
RefPtr<FilterOperation> filter = data().filterInfo ? data().filterInfo->filter: nullptr;
GLuint filterContentTextureID = 0;
if (filter) {
if (data().filterInfo->contentTexture)
filterContentTextureID = toBitmapTextureGL(data().filterInfo->contentTexture.get())->id();
options.add(optionsForFilterType(filter->type(), data().filterInfo->pass));
if (filter->affectsOpacity())
flags |= ShouldBlend;
}
if (useAntialiasing || opacity < 1)
flags |= ShouldBlend;
if (clipStack().isRoundedRectClipEnabled()) {
options.add(TextureMapperShaderProgram::RoundedRectClip);
flags |= ShouldBlend;
}
Ref<TextureMapperShaderProgram> program = data().getShaderProgram(options);
if (filter)
prepareFilterProgram(program.get(), *filter.get(), data().filterInfo->pass, textureSize, filterContentTextureID);
if (clipStack().isRoundedRectClipEnabled())
prepareRoundedRectClip(program.get(), clipStack().roundedRectComponents(), clipStack().roundedRectInverseTransformComponents(), clipStack().roundedRectCount());
Vector<std::pair<GLuint, GLuint> > texturesAndSamplers = {
{ textures[0], program->samplerYLocation() },
{ textures[1], program->samplerULocation() }
};
glUseProgram(program->programID());
glUniformMatrix4fv(program->yuvToRgbLocation(), 1, GL_FALSE, static_cast<const GLfloat *>(&yuvToRgbMatrix[0]));
drawTexturedQuadWithProgram(program.get(), texturesAndSamplers, flags, targetRect, modelViewMatrix, opacity);
}
void TextureMapperGL::drawTexturePackedYUV(GLuint texture, const std::array<GLfloat, 16>& yuvToRgbMatrix, Flags flags, const IntSize& textureSize, const FloatRect& targetRect, const TransformationMatrix& modelViewMatrix, float opacity, unsigned exposedEdges)
{
bool useAntialiasing = m_enableEdgeDistanceAntialiasing
&& exposedEdges == AllEdges
&& !modelViewMatrix.mapQuad(targetRect).isRectilinear();
TextureMapperShaderProgram::Options options = TextureMapperShaderProgram::TexturePackedYUV;
if (opacity < 1)
options.add(TextureMapperShaderProgram::Opacity);
if (useAntialiasing) {
options.add(TextureMapperShaderProgram::Antialiasing);
flags |= ShouldAntialias;
}
if (wrapMode() == RepeatWrap && !m_contextAttributes.supportsNPOTTextures)
options.add(TextureMapperShaderProgram::ManualRepeat);
RefPtr<FilterOperation> filter = data().filterInfo ? data().filterInfo->filter: nullptr;
GLuint filterContentTextureID = 0;
if (filter) {
if (data().filterInfo->contentTexture)
filterContentTextureID = toBitmapTextureGL(data().filterInfo->contentTexture.get())->id();
options.add(optionsForFilterType(filter->type(), data().filterInfo->pass));
if (filter->affectsOpacity())
flags |= ShouldBlend;
}
if (useAntialiasing || opacity < 1)
flags |= ShouldBlend;
if (clipStack().isRoundedRectClipEnabled()) {
options.add(TextureMapperShaderProgram::RoundedRectClip);
flags |= ShouldBlend;
}
Ref<TextureMapperShaderProgram> program = data().getShaderProgram(options);
if (filter)
prepareFilterProgram(program.get(), *filter.get(), data().filterInfo->pass, textureSize, filterContentTextureID);
if (clipStack().isRoundedRectClipEnabled())
prepareRoundedRectClip(program.get(), clipStack().roundedRectComponents(), clipStack().roundedRectInverseTransformComponents(), clipStack().roundedRectCount());
Vector<std::pair<GLuint, GLuint> > texturesAndSamplers = {
{ texture, program->samplerLocation() }
};
glUseProgram(program->programID());
glUniformMatrix4fv(program->yuvToRgbLocation(), 1, GL_FALSE, static_cast<const GLfloat *>(&yuvToRgbMatrix[0]));
drawTexturedQuadWithProgram(program.get(), texturesAndSamplers, flags, targetRect, modelViewMatrix, opacity);
}
void TextureMapperGL::drawSolidColor(const FloatRect& rect, const TransformationMatrix& matrix, const Color& color, bool isBlendingAllowed)
{
Flags flags = 0;
TextureMapperShaderProgram::Options options = TextureMapperShaderProgram::SolidColor;
if (!matrix.mapQuad(rect).isRectilinear()) {
options.add(TextureMapperShaderProgram::Antialiasing);
flags |= ShouldAntialias | (isBlendingAllowed ? ShouldBlend : 0);
}
if (clipStack().isRoundedRectClipEnabled()) {
options.add(TextureMapperShaderProgram::RoundedRectClip);
flags |= ShouldBlend;
}
Ref<TextureMapperShaderProgram> program = data().getShaderProgram(options);
glUseProgram(program->programID());
if (clipStack().isRoundedRectClipEnabled())
prepareRoundedRectClip(program.get(), clipStack().roundedRectComponents(), clipStack().roundedRectInverseTransformComponents(), clipStack().roundedRectCount());
auto [r, g, b, a] = premultiplied(color.toColorTypeLossy<SRGBA<float>>()).resolved();
glUniform4f(program->colorLocation(), r, g, b, a);
if (a < 1 && isBlendingAllowed)
flags |= ShouldBlend;
draw(rect, matrix, program.get(), GL_TRIANGLE_FAN, flags);
}
void TextureMapperGL::clearColor(const Color& color)
{
auto [r, g, b, a] = color.toColorTypeLossy<SRGBA<float>>().resolved();
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT);
}
void TextureMapperGL::drawEdgeTriangles(TextureMapperShaderProgram& program)
{
const GLfloat left = 0;
const GLfloat top = 0;
const GLfloat right = 1;
const GLfloat bottom = 1;
const GLfloat center = 0.5;
// Each 4d triangle consists of a center point and two edge points, where the zw coordinates
// of each vertex equals the nearest point to the vertex on the edge.
#define SIDE_TRIANGLE_DATA(x1, y1, x2, y2) \
x1, y1, x1, y1, \
x2, y2, x2, y2, \
center, center, (x1 + x2) / 2, (y1 + y2) / 2
static const GLfloat unitRectSideTriangles[] = {
SIDE_TRIANGLE_DATA(left, top, right, top),
SIDE_TRIANGLE_DATA(left, top, left, bottom),
SIDE_TRIANGLE_DATA(right, top, right, bottom),
SIDE_TRIANGLE_DATA(left, bottom, right, bottom)
};
#undef SIDE_TRIANGLE_DATA
GLuint vbo = data().getStaticVBO(GL_ARRAY_BUFFER, sizeof(GCGLfloat) * 48, unitRectSideTriangles);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(program.vertexLocation(), 4, GL_FLOAT, false, 0, 0);
glDrawArrays(GL_TRIANGLES, 0, 12);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void TextureMapperGL::drawUnitRect(TextureMapperShaderProgram& program, GLenum drawingMode)
{
static const GLfloat unitRect[] = { 0, 0, 1, 0, 1, 1, 0, 1 };
GLuint vbo = data().getStaticVBO(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, unitRect);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(program.vertexLocation(), 2, GL_FLOAT, false, 0, 0);
glDrawArrays(drawingMode, 0, 4);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void TextureMapperGL::draw(const FloatRect& rect, const TransformationMatrix& modelViewMatrix, TextureMapperShaderProgram& program, GLenum drawingMode, Flags flags)
{
TransformationMatrix matrix(modelViewMatrix);
matrix.multiply(TransformationMatrix::rectToRect(FloatRect(0, 0, 1, 1), rect));
glEnableVertexAttribArray(program.vertexLocation());
program.setMatrix(program.modelViewMatrixLocation(), matrix);
program.setMatrix(program.projectionMatrixLocation(), data().projectionMatrix);
if (isInMaskMode()) {
glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
glEnable(GL_BLEND);
} else {
if (flags & ShouldBlend) {
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
} else
glDisable(GL_BLEND);
}
if (flags & ShouldAntialias)
drawEdgeTriangles(program);
else
drawUnitRect(program, drawingMode);
glDisableVertexAttribArray(program.vertexLocation());
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
}
void TextureMapperGL::drawTexturedQuadWithProgram(TextureMapperShaderProgram& program, const Vector<std::pair<GLuint, GLuint> >& texturesAndSamplers, Flags flags, const FloatRect& rect, const TransformationMatrix& modelViewMatrix, float opacity)
{
glUseProgram(program.programID());
bool repeatWrap = wrapMode() == RepeatWrap && m_contextAttributes.supportsNPOTTextures;
GLenum target = GLenum(GL_TEXTURE_2D);
if (flags & ShouldUseExternalOESTextureRect)
target = GLenum(GL_TEXTURE_EXTERNAL_OES);
for (unsigned i = 0; i < texturesAndSamplers.size(); ++i) {
auto& textureAndSampler = texturesAndSamplers[i];
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(target, textureAndSampler.first);
glUniform1i(textureAndSampler.second, i);
if (repeatWrap) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
}
TransformationMatrix patternTransform = this->patternTransform();
prepareTransformationMatrixWithFlags(patternTransform, flags);
program.setMatrix(program.textureSpaceMatrixLocation(), patternTransform);
program.setMatrix(program.textureColorSpaceMatrixLocation(), colorSpaceMatrixForFlags(flags));
glUniform1f(program.opacityLocation(), opacity);
if (opacity < 1)
flags |= ShouldBlend;
draw(rect, modelViewMatrix, program, GL_TRIANGLE_FAN, flags);
if (repeatWrap) {
for (auto& textureAndSampler : texturesAndSamplers) {
glBindTexture(target, textureAndSampler.first);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
}
}
void TextureMapperGL::drawTexturedQuadWithProgram(TextureMapperShaderProgram& program, uint32_t texture, Flags flags, const FloatRect& rect, const TransformationMatrix& modelViewMatrix, float opacity)
{
drawTexturedQuadWithProgram(program, { { texture, program.samplerLocation() } }, flags, rect, modelViewMatrix, opacity);
}
void TextureMapperGL::drawFiltered(const BitmapTexture& sampler, const BitmapTexture* contentTexture, const FilterOperation& filter, int pass)
{
// For standard filters, we always draw the whole texture without transformations.
TextureMapperShaderProgram::Options options = optionsForFilterType(filter.type(), pass);
Ref<TextureMapperShaderProgram> program = data().getShaderProgram(options);
prepareFilterProgram(program.get(), filter, pass, sampler.contentSize(), contentTexture ? static_cast<const BitmapTextureGL*>(contentTexture)->id() : 0);
FloatRect targetRect(IntPoint::zero(), sampler.contentSize());
drawTexturedQuadWithProgram(program.get(), static_cast<const BitmapTextureGL&>(sampler).id(), 0, targetRect, TransformationMatrix(), 1);
}
static inline TransformationMatrix createProjectionMatrix(const IntSize& size, bool mirrored)
{
const float nearValue = -99999;
const float farValue = 9999999;
return TransformationMatrix(2.0 / float(size.width()), 0, 0, 0,
0, (mirrored ? 2.0 : -2.0) / float(size.height()), 0, 0,
0, 0, -2.f / (farValue - nearValue), 0,
-1, mirrored ? -1 : 1, -(farValue + nearValue) / (farValue - nearValue), 1);
}
TextureMapperGL::~TextureMapperGL()
{
delete m_data;
}
void TextureMapperGL::bindDefaultSurface()
{
glBindFramebuffer(GL_FRAMEBUFFER, data().targetFrameBuffer);
auto& viewport = data().viewport;
data().projectionMatrix = createProjectionMatrix(IntSize(viewport[2], viewport[3]), data().PaintFlags & PaintingMirrored);
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
m_clipStack.apply();
data().currentSurface = nullptr;
}
void TextureMapperGL::bindSurface(BitmapTexture *surface)
{
if (!surface) {
bindDefaultSurface();
return;
}
static_cast<BitmapTextureGL*>(surface)->bindAsSurface();
data().projectionMatrix = createProjectionMatrix(surface->size(), true /* mirrored */);
data().currentSurface = surface;
}
BitmapTexture* TextureMapperGL::currentSurface()
{
return data().currentSurface.get();
}
bool TextureMapperGL::beginScissorClip(const TransformationMatrix& modelViewMatrix, const FloatRect& targetRect)
{
// 3D transforms are currently not supported in scissor clipping
// resulting in cropped surfaces when z>0.
if (!modelViewMatrix.isAffine())
return false;
FloatQuad quad = modelViewMatrix.projectQuad(targetRect);
IntRect rect = quad.enclosingBoundingBox();
// Only use scissors on rectilinear clips.
if (!quad.isRectilinear() || rect.isEmpty())
return false;
clipStack().intersect(rect);
clipStack().applyIfNeeded();
return true;
}
bool TextureMapperGL::beginRoundedRectClip(const TransformationMatrix& modelViewMatrix, const FloatRoundedRect& targetRect)
{
// This is implemented by telling the fragment shader to check whether each pixel is inside the rounded rectangle
// before painting it.
//
// Inside the shader, the math to check whether a point is inside the rounded rectangle requires the rectangle to
// be aligned to the X and Y axis, which is not guaranteed if the transformation matrix includes rotations. In order
// to avoid this, instead of applying the transformation to the rounded rectangle, we calculate the inverse
// of the transformation and apply it to the pixels before checking whether they are inside the rounded rectangle.
// This works fine as long as the transformation matrix is invertible.
//
// There is a limit to the number of rounded rectangle clippings that can be done, that happens because the GLSL
// arrays must have a predefined size. The limit is defined inside ClipStack, and that's why we need to call
// clipStack().isRoundedRectClipAllowed() before trying to add a new clip.
if (!targetRect.isRounded() || !targetRect.isRenderable() || targetRect.isEmpty() || !modelViewMatrix.isInvertible() || !clipStack().isRoundedRectClipAllowed())
return false;
FloatQuad quad = modelViewMatrix.projectQuad(targetRect.rect());
IntRect rect = quad.enclosingBoundingBox();
clipStack().addRoundedRect(targetRect, modelViewMatrix.inverse().value());
clipStack().intersect(rect);
clipStack().applyIfNeeded();
return true;
}
void TextureMapperGL::beginClip(const TransformationMatrix& modelViewMatrix, const FloatRoundedRect& targetRect)
{
clipStack().push();
if (beginRoundedRectClip(modelViewMatrix, targetRect))
return;
if (beginScissorClip(modelViewMatrix, targetRect.rect()))
return;
data().initializeStencil();
Ref<TextureMapperShaderProgram> program = data().getShaderProgram(TextureMapperShaderProgram::SolidColor);
glUseProgram(program->programID());
glEnableVertexAttribArray(program->vertexLocation());
const GLfloat unitRect[] = {0, 0, 1, 0, 1, 1, 0, 1};
GLuint vbo = data().getStaticVBO(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, unitRect);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(program->vertexLocation(), 2, GL_FLOAT, false, 0, 0);
TransformationMatrix matrix(modelViewMatrix);
matrix.multiply(TransformationMatrix::rectToRect(FloatRect(0, 0, 1, 1), targetRect.rect()));
static const TransformationMatrix fullProjectionMatrix = TransformationMatrix::rectToRect(FloatRect(0, 0, 1, 1), FloatRect(-1, -1, 2, 2));
int stencilIndex = clipStack().getStencilIndex();
glEnable(GL_STENCIL_TEST);
// Make sure we don't do any actual drawing.
glStencilFunc(GL_NEVER, stencilIndex, stencilIndex);
// Operate only on the stencilIndex and above.
glStencilMask(0xff & ~(stencilIndex - 1));
// First clear the entire buffer at the current index.
program->setMatrix(program->projectionMatrixLocation(), fullProjectionMatrix);
program->setMatrix(program->modelViewMatrixLocation(), TransformationMatrix());
glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
// Now apply the current index to the new quad.
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
program->setMatrix(program->projectionMatrixLocation(), data().projectionMatrix);
program->setMatrix(program->modelViewMatrixLocation(), matrix);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
// Clear the state.
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(program->vertexLocation());
glStencilMask(0);
// Increase stencilIndex and apply stencil testing.
clipStack().setStencilIndex(stencilIndex * 2);
clipStack().applyIfNeeded();
}
void TextureMapperGL::endClip()
{
clipStack().pop();
clipStack().applyIfNeeded();
}
IntRect TextureMapperGL::clipBounds()
{
return clipStack().current().scissorBox;
}
void TextureMapperGL::beginPreserves3D()
{
glEnable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT);
}
void TextureMapperGL::endPreserves3D()
{
glDisable(GL_DEPTH_TEST);
}
Ref<BitmapTexture> TextureMapperGL::createTexture(GLint internalFormat)
{
return BitmapTextureGL::create(m_contextAttributes, internalFormat);
}
std::unique_ptr<TextureMapper> TextureMapper::platformCreateAccelerated()
{
return makeUnique<TextureMapperGL>();
}
void TextureMapperGL::drawTextureExternalOES(GLuint texture, Flags flags, const FloatRect& targetRect, const TransformationMatrix& modelViewMatrix, float opacity)
{
Ref<TextureMapperShaderProgram> program = data().getShaderProgram(TextureMapperShaderProgram::Option::TextureExternalOES);
drawTexturedQuadWithProgram(program.get(), { { texture, program->externalOESTextureLocation() } },
flags | TextureMapperGL::ShouldUseExternalOESTextureRect, targetRect, modelViewMatrix, opacity);
}
};
#endif // USE(TEXTURE_MAPPER_GL)