| 1 | /* |
| 2 | Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) |
| 3 | Copyright (C) 2012 Igalia S.L. |
| 4 | Copyright (C) 2012 Adobe Systems Incorporated |
| 5 | |
| 6 | This library is free software; you can redistribute it and/or |
| 7 | modify it under the terms of the GNU Library General Public |
| 8 | License as published by the Free Software Foundation; either |
| 9 | version 2 of the License, or (at your option) any later version. |
| 10 | |
| 11 | This library is distributed in the hope that it will be useful, |
| 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 14 | Library General Public License for more details. |
| 15 | |
| 16 | You should have received a copy of the GNU Library General Public License |
| 17 | along with this library; see the file COPYING.LIB. If not, write to |
| 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 19 | Boston, MA 02110-1301, USA. |
| 20 | */ |
| 21 | |
| 22 | #include "config.h" |
| 23 | #include "TextureMapperGL.h" |
| 24 | |
| 25 | #if USE(TEXTURE_MAPPER_GL) |
| 26 | |
| 27 | #include "BitmapTextureGL.h" |
| 28 | #include "BitmapTexturePool.h" |
| 29 | #include "Extensions3D.h" |
| 30 | #include "FilterOperations.h" |
| 31 | #include "FloatQuad.h" |
| 32 | #include "GLContext.h" |
| 33 | #include "GraphicsContext.h" |
| 34 | #include "Image.h" |
| 35 | #include "LengthFunctions.h" |
| 36 | #include "NotImplemented.h" |
| 37 | #include "TextureMapperShaderProgram.h" |
| 38 | #include "Timer.h" |
| 39 | #include <wtf/HashMap.h> |
| 40 | #include <wtf/NeverDestroyed.h> |
| 41 | #include <wtf/Ref.h> |
| 42 | #include <wtf/RefCounted.h> |
| 43 | #include <wtf/SetForScope.h> |
| 44 | |
| 45 | #if USE(CAIRO) |
| 46 | #include "CairoUtilities.h" |
| 47 | #include "RefPtrCairo.h" |
| 48 | #include <cairo.h> |
| 49 | #include <wtf/text/CString.h> |
| 50 | #endif |
| 51 | |
| 52 | namespace WebCore { |
| 53 | |
| 54 | class TextureMapperGLData { |
| 55 | WTF_MAKE_FAST_ALLOCATED; |
| 56 | public: |
| 57 | explicit TextureMapperGLData(void*); |
| 58 | ~TextureMapperGLData(); |
| 59 | |
| 60 | void initializeStencil(); |
| 61 | GLuint getStaticVBO(GLenum target, GLsizeiptr, const void* data); |
| 62 | GLuint getVAO(); |
| 63 | Ref<TextureMapperShaderProgram> getShaderProgram(TextureMapperShaderProgram::Options); |
| 64 | |
| 65 | TransformationMatrix projectionMatrix; |
| 66 | TextureMapper::PaintFlags PaintFlags { 0 }; |
| 67 | GLint previousProgram { 0 }; |
| 68 | GLint previousVAO { 0 }; |
| 69 | GLint targetFrameBuffer { 0 }; |
| 70 | bool didModifyStencil { false }; |
| 71 | GLint previousScissorState { 0 }; |
| 72 | GLint previousDepthState { 0 }; |
| 73 | GLint viewport[4] { 0, }; |
| 74 | GLint previousScissor[4] { 0, }; |
| 75 | RefPtr<BitmapTexture> currentSurface; |
| 76 | const BitmapTextureGL::FilterInfo* filterInfo { nullptr }; |
| 77 | |
| 78 | private: |
| 79 | class SharedGLData : public RefCounted<SharedGLData> { |
| 80 | public: |
| 81 | static Ref<SharedGLData> currentSharedGLData(void* platformContext) |
| 82 | { |
| 83 | auto it = contextDataMap().find(platformContext); |
| 84 | if (it != contextDataMap().end()) |
| 85 | return *it->value; |
| 86 | |
| 87 | Ref<SharedGLData> data = adoptRef(*new SharedGLData); |
| 88 | contextDataMap().add(platformContext, data.ptr()); |
| 89 | return data; |
| 90 | } |
| 91 | |
| 92 | ~SharedGLData() |
| 93 | { |
| 94 | ASSERT(std::any_of(contextDataMap().begin(), contextDataMap().end(), |
| 95 | [this](auto& entry) { return entry.value == this; })); |
| 96 | contextDataMap().removeIf([this](auto& entry) { return entry.value == this; }); |
| 97 | } |
| 98 | |
| 99 | private: |
| 100 | friend class TextureMapperGLData; |
| 101 | |
| 102 | using GLContextDataMap = HashMap<void*, SharedGLData*>; |
| 103 | static GLContextDataMap& contextDataMap() |
| 104 | { |
| 105 | static NeverDestroyed<GLContextDataMap> map; |
| 106 | return map; |
| 107 | } |
| 108 | |
| 109 | SharedGLData() = default; |
| 110 | |
| 111 | HashMap<TextureMapperShaderProgram::Options, RefPtr<TextureMapperShaderProgram>> m_programs; |
| 112 | }; |
| 113 | |
| 114 | Ref<SharedGLData> m_sharedGLData; |
| 115 | HashMap<const void*, GLuint> m_vbos; |
| 116 | GLuint m_vao { 0 }; |
| 117 | }; |
| 118 | |
| 119 | TextureMapperGLData::TextureMapperGLData(void* platformContext) |
| 120 | : m_sharedGLData(SharedGLData::currentSharedGLData(platformContext)) |
| 121 | { |
| 122 | } |
| 123 | |
| 124 | TextureMapperGLData::~TextureMapperGLData() |
| 125 | { |
| 126 | for (auto& entry : m_vbos) |
| 127 | glDeleteBuffers(1, &entry.value); |
| 128 | |
| 129 | #if !USE(OPENGL_ES) |
| 130 | if (GLContext::current()->version() >= 320 && m_vao) |
| 131 | glDeleteVertexArrays(1, &m_vao); |
| 132 | #endif |
| 133 | } |
| 134 | |
| 135 | void TextureMapperGLData::initializeStencil() |
| 136 | { |
| 137 | if (currentSurface) { |
| 138 | static_cast<BitmapTextureGL*>(currentSurface.get())->initializeStencil(); |
| 139 | return; |
| 140 | } |
| 141 | |
| 142 | if (didModifyStencil) |
| 143 | return; |
| 144 | |
| 145 | glClearStencil(0); |
| 146 | glClear(GL_STENCIL_BUFFER_BIT); |
| 147 | didModifyStencil = true; |
| 148 | } |
| 149 | |
| 150 | GLuint TextureMapperGLData::getStaticVBO(GLenum target, GLsizeiptr size, const void* data) |
| 151 | { |
| 152 | auto addResult = m_vbos.ensure(data, |
| 153 | [this, target, size, data] { |
| 154 | GLuint vbo = 0; |
| 155 | glGenBuffers(1, &vbo); |
| 156 | glBindBuffer(target, vbo); |
| 157 | glBufferData(target, size, data, GL_STATIC_DRAW); |
| 158 | return vbo; |
| 159 | }); |
| 160 | return addResult.iterator->value; |
| 161 | } |
| 162 | |
| 163 | GLuint TextureMapperGLData::getVAO() |
| 164 | { |
| 165 | #if !USE(OPENGL_ES) |
| 166 | if (GLContext::current()->version() >= 320 && !m_vao) |
| 167 | glGenVertexArrays(1, &m_vao); |
| 168 | #endif |
| 169 | |
| 170 | return m_vao; |
| 171 | } |
| 172 | |
| 173 | Ref<TextureMapperShaderProgram> TextureMapperGLData::getShaderProgram(TextureMapperShaderProgram::Options options) |
| 174 | { |
| 175 | auto addResult = m_sharedGLData->m_programs.ensure(options, |
| 176 | [options] { return TextureMapperShaderProgram::create(options); }); |
| 177 | return *addResult.iterator->value; |
| 178 | } |
| 179 | |
| 180 | TextureMapperGL::TextureMapperGL() |
| 181 | : m_contextAttributes(TextureMapperContextAttributes::get()) |
| 182 | , m_enableEdgeDistanceAntialiasing(false) |
| 183 | { |
| 184 | void* platformContext = GLContext::current()->platformContext(); |
| 185 | ASSERT(platformContext); |
| 186 | |
| 187 | m_data = new TextureMapperGLData(platformContext); |
| 188 | #if USE(TEXTURE_MAPPER_GL) |
| 189 | m_texturePool = std::make_unique<BitmapTexturePool>(m_contextAttributes); |
| 190 | #endif |
| 191 | } |
| 192 | |
| 193 | ClipStack& TextureMapperGL::clipStack() |
| 194 | { |
| 195 | return data().currentSurface ? toBitmapTextureGL(data().currentSurface.get())->clipStack() : m_clipStack; |
| 196 | } |
| 197 | |
| 198 | void TextureMapperGL::beginPainting(PaintFlags flags) |
| 199 | { |
| 200 | glGetIntegerv(GL_CURRENT_PROGRAM, &data().previousProgram); |
| 201 | data().previousScissorState = glIsEnabled(GL_SCISSOR_TEST); |
| 202 | data().previousDepthState = glIsEnabled(GL_DEPTH_TEST); |
| 203 | glDisable(GL_DEPTH_TEST); |
| 204 | glEnable(GL_SCISSOR_TEST); |
| 205 | data().didModifyStencil = false; |
| 206 | glDepthMask(0); |
| 207 | glGetIntegerv(GL_VIEWPORT, data().viewport); |
| 208 | glGetIntegerv(GL_SCISSOR_BOX, data().previousScissor); |
| 209 | m_clipStack.reset(IntRect(0, 0, data().viewport[2], data().viewport[3]), flags & PaintingMirrored ? ClipStack::YAxisMode::Default : ClipStack::YAxisMode::Inverted); |
| 210 | glGetIntegerv(GL_FRAMEBUFFER_BINDING, &data().targetFrameBuffer); |
| 211 | data().PaintFlags = flags; |
| 212 | bindSurface(0); |
| 213 | |
| 214 | #if !USE(OPENGL_ES) |
| 215 | if (GLContext::current()->version() >= 320) { |
| 216 | glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &data().previousVAO); |
| 217 | glBindVertexArray(data().getVAO()); |
| 218 | } |
| 219 | #endif |
| 220 | } |
| 221 | |
| 222 | void TextureMapperGL::endPainting() |
| 223 | { |
| 224 | if (data().didModifyStencil) { |
| 225 | glClearStencil(1); |
| 226 | glClear(GL_STENCIL_BUFFER_BIT); |
| 227 | } |
| 228 | |
| 229 | glUseProgram(data().previousProgram); |
| 230 | |
| 231 | glScissor(data().previousScissor[0], data().previousScissor[1], data().previousScissor[2], data().previousScissor[3]); |
| 232 | if (data().previousScissorState) |
| 233 | glEnable(GL_SCISSOR_TEST); |
| 234 | else |
| 235 | glDisable(GL_SCISSOR_TEST); |
| 236 | |
| 237 | if (data().previousDepthState) |
| 238 | glEnable(GL_DEPTH_TEST); |
| 239 | else |
| 240 | glDisable(GL_DEPTH_TEST); |
| 241 | |
| 242 | #if !USE(OPENGL_ES) |
| 243 | if (GLContext::current()->version() >= 320) |
| 244 | glBindVertexArray(data().previousVAO); |
| 245 | #endif |
| 246 | } |
| 247 | |
| 248 | void TextureMapperGL::drawBorder(const Color& color, float width, const FloatRect& targetRect, const TransformationMatrix& modelViewMatrix) |
| 249 | { |
| 250 | if (clipStack().isCurrentScissorBoxEmpty()) |
| 251 | return; |
| 252 | |
| 253 | Ref<TextureMapperShaderProgram> program = data().getShaderProgram(TextureMapperShaderProgram::SolidColor); |
| 254 | glUseProgram(program->programID()); |
| 255 | |
| 256 | float r, g, b, a; |
| 257 | Color(premultipliedARGBFromColor(color)).getRGBA(r, g, b, a); |
| 258 | glUniform4f(program->colorLocation(), r, g, b, a); |
| 259 | glLineWidth(width); |
| 260 | |
| 261 | draw(targetRect, modelViewMatrix, program.get(), GL_LINE_LOOP, !color.isOpaque() ? ShouldBlend : 0); |
| 262 | } |
| 263 | |
| 264 | // FIXME: drawNumber() should save a number texture-atlas and re-use whenever possible. |
| 265 | void TextureMapperGL::drawNumber(int number, const Color& color, const FloatPoint& targetPoint, const TransformationMatrix& modelViewMatrix) |
| 266 | { |
| 267 | int pointSize = 8; |
| 268 | |
| 269 | #if USE(CAIRO) |
| 270 | CString counterString = String::number(number).ascii(); |
| 271 | // cairo_text_extents() requires a cairo_t, so dimensions need to be guesstimated. |
| 272 | int width = counterString.length() * pointSize * 1.2; |
| 273 | int height = pointSize * 1.5; |
| 274 | |
| 275 | cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); |
| 276 | cairo_t* cr = cairo_create(surface); |
| 277 | |
| 278 | // Since we won't swap R+B when uploading a texture, paint with the swapped R+B color. |
| 279 | if (color.isExtended()) |
| 280 | cairo_set_source_rgba(cr, color.asExtended().blue(), color.asExtended().green(), color.asExtended().red(), color.asExtended().alpha()); |
| 281 | else { |
| 282 | float r, g, b, a; |
| 283 | color.getRGBA(r, g, b, a); |
| 284 | cairo_set_source_rgba(cr, b, g, r, a); |
| 285 | } |
| 286 | |
| 287 | cairo_rectangle(cr, 0, 0, width, height); |
| 288 | cairo_fill(cr); |
| 289 | |
| 290 | cairo_select_font_face(cr, "Monospace" , CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); |
| 291 | cairo_set_font_size(cr, pointSize); |
| 292 | cairo_set_source_rgb(cr, 1, 1, 1); |
| 293 | cairo_move_to(cr, 2, pointSize); |
| 294 | cairo_show_text(cr, counterString.data()); |
| 295 | |
| 296 | IntSize size(width, height); |
| 297 | IntRect sourceRect(IntPoint::zero(), size); |
| 298 | IntRect targetRect(roundedIntPoint(targetPoint), size); |
| 299 | |
| 300 | RefPtr<BitmapTexture> texture = acquireTextureFromPool(size); |
| 301 | const unsigned char* bits = cairo_image_surface_get_data(surface); |
| 302 | int stride = cairo_image_surface_get_stride(surface); |
| 303 | static_cast<BitmapTextureGL*>(texture.get())->updateContents(bits, sourceRect, IntPoint::zero(), stride); |
| 304 | drawTexture(*texture, targetRect, modelViewMatrix, 1.0f, AllEdges); |
| 305 | |
| 306 | cairo_surface_destroy(surface); |
| 307 | cairo_destroy(cr); |
| 308 | |
| 309 | #else |
| 310 | UNUSED_PARAM(number); |
| 311 | UNUSED_PARAM(pointSize); |
| 312 | UNUSED_PARAM(targetPoint); |
| 313 | UNUSED_PARAM(modelViewMatrix); |
| 314 | notImplemented(); |
| 315 | #endif |
| 316 | } |
| 317 | |
| 318 | static TextureMapperShaderProgram::Options optionsForFilterType(FilterOperation::OperationType type, unsigned pass) |
| 319 | { |
| 320 | switch (type) { |
| 321 | case FilterOperation::GRAYSCALE: |
| 322 | return TextureMapperShaderProgram::Texture | TextureMapperShaderProgram::GrayscaleFilter; |
| 323 | case FilterOperation::SEPIA: |
| 324 | return TextureMapperShaderProgram::Texture | TextureMapperShaderProgram::SepiaFilter; |
| 325 | case FilterOperation::SATURATE: |
| 326 | return TextureMapperShaderProgram::Texture | TextureMapperShaderProgram::SaturateFilter; |
| 327 | case FilterOperation::HUE_ROTATE: |
| 328 | return TextureMapperShaderProgram::Texture | TextureMapperShaderProgram::HueRotateFilter; |
| 329 | case FilterOperation::INVERT: |
| 330 | return TextureMapperShaderProgram::Texture | TextureMapperShaderProgram::InvertFilter; |
| 331 | case FilterOperation::BRIGHTNESS: |
| 332 | return TextureMapperShaderProgram::Texture | TextureMapperShaderProgram::BrightnessFilter; |
| 333 | case FilterOperation::CONTRAST: |
| 334 | return TextureMapperShaderProgram::Texture | TextureMapperShaderProgram::ContrastFilter; |
| 335 | case FilterOperation::OPACITY: |
| 336 | return TextureMapperShaderProgram::Texture | TextureMapperShaderProgram::OpacityFilter; |
| 337 | case FilterOperation::BLUR: |
| 338 | return TextureMapperShaderProgram::BlurFilter; |
| 339 | case FilterOperation::DROP_SHADOW: |
| 340 | return TextureMapperShaderProgram::AlphaBlur |
| 341 | | (pass ? TextureMapperShaderProgram::ContentTexture | TextureMapperShaderProgram::SolidColor: 0); |
| 342 | default: |
| 343 | ASSERT_NOT_REACHED(); |
| 344 | return 0; |
| 345 | } |
| 346 | } |
| 347 | |
| 348 | // Create a normal distribution of 21 values between -2 and 2. |
| 349 | static const unsigned GaussianKernelHalfWidth = 11; |
| 350 | static const float GaussianKernelStep = 0.2; |
| 351 | |
| 352 | static inline float gauss(float x) |
| 353 | { |
| 354 | return exp(-(x * x) / 2.); |
| 355 | } |
| 356 | |
| 357 | static float* gaussianKernel() |
| 358 | { |
| 359 | static bool prepared = false; |
| 360 | static float kernel[GaussianKernelHalfWidth] = {0, }; |
| 361 | |
| 362 | if (prepared) |
| 363 | return kernel; |
| 364 | |
| 365 | kernel[0] = gauss(0); |
| 366 | float sum = kernel[0]; |
| 367 | for (unsigned i = 1; i < GaussianKernelHalfWidth; ++i) { |
| 368 | kernel[i] = gauss(i * GaussianKernelStep); |
| 369 | sum += 2 * kernel[i]; |
| 370 | } |
| 371 | |
| 372 | // Normalize the kernel. |
| 373 | float scale = 1 / sum; |
| 374 | for (unsigned i = 0; i < GaussianKernelHalfWidth; ++i) |
| 375 | kernel[i] *= scale; |
| 376 | |
| 377 | prepared = true; |
| 378 | return kernel; |
| 379 | } |
| 380 | |
| 381 | static void prepareFilterProgram(TextureMapperShaderProgram& program, const FilterOperation& operation, unsigned pass, const IntSize& size, GLuint contentTexture) |
| 382 | { |
| 383 | glUseProgram(program.programID()); |
| 384 | |
| 385 | switch (operation.type()) { |
| 386 | case FilterOperation::GRAYSCALE: |
| 387 | case FilterOperation::SEPIA: |
| 388 | case FilterOperation::SATURATE: |
| 389 | case FilterOperation::HUE_ROTATE: |
| 390 | glUniform1f(program.filterAmountLocation(), static_cast<const BasicColorMatrixFilterOperation&>(operation).amount()); |
| 391 | break; |
| 392 | case FilterOperation::INVERT: |
| 393 | case FilterOperation::BRIGHTNESS: |
| 394 | case FilterOperation::CONTRAST: |
| 395 | case FilterOperation::OPACITY: |
| 396 | glUniform1f(program.filterAmountLocation(), static_cast<const BasicComponentTransferFilterOperation&>(operation).amount()); |
| 397 | break; |
| 398 | case FilterOperation::BLUR: { |
| 399 | const BlurFilterOperation& blur = static_cast<const BlurFilterOperation&>(operation); |
| 400 | FloatSize radius; |
| 401 | |
| 402 | // Blur is done in two passes, first horizontally and then vertically. The same shader is used for both. |
| 403 | if (pass) |
| 404 | radius.setHeight(floatValueForLength(blur.stdDeviation(), size.height()) / size.height()); |
| 405 | else |
| 406 | radius.setWidth(floatValueForLength(blur.stdDeviation(), size.width()) / size.width()); |
| 407 | |
| 408 | glUniform2f(program.blurRadiusLocation(), radius.width(), radius.height()); |
| 409 | glUniform1fv(program.gaussianKernelLocation(), GaussianKernelHalfWidth, gaussianKernel()); |
| 410 | break; |
| 411 | } |
| 412 | case FilterOperation::DROP_SHADOW: { |
| 413 | const DropShadowFilterOperation& shadow = static_cast<const DropShadowFilterOperation&>(operation); |
| 414 | glUniform1fv(program.gaussianKernelLocation(), GaussianKernelHalfWidth, gaussianKernel()); |
| 415 | switch (pass) { |
| 416 | case 0: |
| 417 | // First pass: horizontal alpha blur. |
| 418 | glUniform2f(program.blurRadiusLocation(), shadow.stdDeviation() / float(size.width()), 0); |
| 419 | glUniform2f(program.shadowOffsetLocation(), float(shadow.location().x()) / float(size.width()), float(shadow.location().y()) / float(size.height())); |
| 420 | break; |
| 421 | case 1: |
| 422 | // Second pass: we need the shadow color and the content texture for compositing. |
| 423 | float r, g, b, a; |
| 424 | Color(premultipliedARGBFromColor(shadow.color())).getRGBA(r, g, b, a); |
| 425 | glUniform4f(program.colorLocation(), r, g, b, a); |
| 426 | glUniform2f(program.blurRadiusLocation(), 0, shadow.stdDeviation() / float(size.height())); |
| 427 | glUniform2f(program.shadowOffsetLocation(), 0, 0); |
| 428 | glActiveTexture(GL_TEXTURE1); |
| 429 | glBindTexture(GL_TEXTURE_2D, contentTexture); |
| 430 | glUniform1i(program.contentTextureLocation(), 1); |
| 431 | break; |
| 432 | } |
| 433 | break; |
| 434 | } |
| 435 | default: |
| 436 | break; |
| 437 | } |
| 438 | } |
| 439 | |
| 440 | static TransformationMatrix colorSpaceMatrixForFlags(TextureMapperGL::Flags flags) |
| 441 | { |
| 442 | // The matrix is initially the identity one, which means no color conversion. |
| 443 | TransformationMatrix matrix; |
| 444 | if (flags & TextureMapperGL::ShouldConvertTextureBGRAToRGBA) |
| 445 | 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); |
| 446 | else if (flags & TextureMapperGL::ShouldConvertTextureARGBToRGBA) |
| 447 | 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); |
| 448 | |
| 449 | return matrix; |
| 450 | } |
| 451 | |
| 452 | void TextureMapperGL::drawTexture(const BitmapTexture& texture, const FloatRect& targetRect, const TransformationMatrix& matrix, float opacity, unsigned exposedEdges) |
| 453 | { |
| 454 | if (!texture.isValid()) |
| 455 | return; |
| 456 | |
| 457 | if (clipStack().isCurrentScissorBoxEmpty()) |
| 458 | return; |
| 459 | |
| 460 | const BitmapTextureGL& textureGL = static_cast<const BitmapTextureGL&>(texture); |
| 461 | SetForScope<const BitmapTextureGL::FilterInfo*> filterInfo(data().filterInfo, textureGL.filterInfo()); |
| 462 | |
| 463 | drawTexture(textureGL.id(), textureGL.colorConvertFlags() | (textureGL.isOpaque() ? 0 : ShouldBlend), textureGL.size(), targetRect, matrix, opacity, exposedEdges); |
| 464 | } |
| 465 | |
| 466 | void TextureMapperGL::drawTexture(GLuint texture, Flags flags, const IntSize& textureSize, const FloatRect& targetRect, const TransformationMatrix& modelViewMatrix, float opacity, unsigned exposedEdges) |
| 467 | { |
| 468 | bool useRect = flags & ShouldUseARBTextureRect; |
| 469 | bool useAntialiasing = m_enableEdgeDistanceAntialiasing |
| 470 | && exposedEdges == AllEdges |
| 471 | && !modelViewMatrix.mapQuad(targetRect).isRectilinear(); |
| 472 | |
| 473 | TextureMapperShaderProgram::Options options = TextureMapperShaderProgram::Texture; |
| 474 | if (useRect) |
| 475 | options |= TextureMapperShaderProgram::Rect; |
| 476 | if (opacity < 1) |
| 477 | options |= TextureMapperShaderProgram::Opacity; |
| 478 | if (useAntialiasing) { |
| 479 | options |= TextureMapperShaderProgram::Antialiasing; |
| 480 | flags |= ShouldAntialias; |
| 481 | } |
| 482 | if (wrapMode() == RepeatWrap && !m_contextAttributes.supportsNPOTTextures) |
| 483 | options |= TextureMapperShaderProgram::ManualRepeat; |
| 484 | |
| 485 | RefPtr<FilterOperation> filter = data().filterInfo ? data().filterInfo->filter: 0; |
| 486 | GLuint filterContentTextureID = 0; |
| 487 | |
| 488 | if (filter) { |
| 489 | if (data().filterInfo->contentTexture) |
| 490 | filterContentTextureID = toBitmapTextureGL(data().filterInfo->contentTexture.get())->id(); |
| 491 | options |= optionsForFilterType(filter->type(), data().filterInfo->pass); |
| 492 | if (filter->affectsOpacity()) |
| 493 | flags |= ShouldBlend; |
| 494 | } |
| 495 | |
| 496 | if (useAntialiasing || opacity < 1) |
| 497 | flags |= ShouldBlend; |
| 498 | |
| 499 | Ref<TextureMapperShaderProgram> program = data().getShaderProgram(options); |
| 500 | |
| 501 | if (filter) |
| 502 | prepareFilterProgram(program.get(), *filter.get(), data().filterInfo->pass, textureSize, filterContentTextureID); |
| 503 | |
| 504 | drawTexturedQuadWithProgram(program.get(), texture, flags, textureSize, targetRect, modelViewMatrix, opacity); |
| 505 | } |
| 506 | |
| 507 | void TextureMapperGL::drawSolidColor(const FloatRect& rect, const TransformationMatrix& matrix, const Color& color, bool isBlendingAllowed) |
| 508 | { |
| 509 | Flags flags = 0; |
| 510 | TextureMapperShaderProgram::Options options = TextureMapperShaderProgram::SolidColor; |
| 511 | if (!matrix.mapQuad(rect).isRectilinear()) { |
| 512 | options |= TextureMapperShaderProgram::Antialiasing; |
| 513 | flags |= ShouldAntialias | (isBlendingAllowed ? ShouldBlend : 0); |
| 514 | } |
| 515 | |
| 516 | Ref<TextureMapperShaderProgram> program = data().getShaderProgram(options); |
| 517 | glUseProgram(program->programID()); |
| 518 | |
| 519 | float r, g, b, a; |
| 520 | Color(premultipliedARGBFromColor(color)).getRGBA(r, g, b, a); |
| 521 | glUniform4f(program->colorLocation(), r, g, b, a); |
| 522 | if (a < 1 && isBlendingAllowed) |
| 523 | flags |= ShouldBlend; |
| 524 | |
| 525 | draw(rect, matrix, program.get(), GL_TRIANGLE_FAN, flags); |
| 526 | } |
| 527 | |
| 528 | void TextureMapperGL::clearColor(const Color& color) |
| 529 | { |
| 530 | glClearColor(color.red() / 255.0f, color.green() / 255.0f, color.blue() / 255.0f, color.alpha() / 255.0f); |
| 531 | glClear(GL_COLOR_BUFFER_BIT); |
| 532 | } |
| 533 | |
| 534 | void TextureMapperGL::drawEdgeTriangles(TextureMapperShaderProgram& program) |
| 535 | { |
| 536 | const GLfloat left = 0; |
| 537 | const GLfloat top = 0; |
| 538 | const GLfloat right = 1; |
| 539 | const GLfloat bottom = 1; |
| 540 | const GLfloat center = 0.5; |
| 541 | |
| 542 | // Each 4d triangle consists of a center point and two edge points, where the zw coordinates |
| 543 | // of each vertex equals the nearest point to the vertex on the edge. |
| 544 | #define SIDE_TRIANGLE_DATA(x1, y1, x2, y2) \ |
| 545 | x1, y1, x1, y1, \ |
| 546 | x2, y2, x2, y2, \ |
| 547 | center, center, (x1 + x2) / 2, (y1 + y2) / 2 |
| 548 | |
| 549 | static const GLfloat unitRectSideTriangles[] = { |
| 550 | SIDE_TRIANGLE_DATA(left, top, right, top), |
| 551 | SIDE_TRIANGLE_DATA(left, top, left, bottom), |
| 552 | SIDE_TRIANGLE_DATA(right, top, right, bottom), |
| 553 | SIDE_TRIANGLE_DATA(left, bottom, right, bottom) |
| 554 | }; |
| 555 | #undef SIDE_TRIANGLE_DATA |
| 556 | |
| 557 | GLuint vbo = data().getStaticVBO(GL_ARRAY_BUFFER, sizeof(GC3Dfloat) * 48, unitRectSideTriangles); |
| 558 | glBindBuffer(GL_ARRAY_BUFFER, vbo); |
| 559 | glVertexAttribPointer(program.vertexLocation(), 4, GL_FLOAT, false, 0, 0); |
| 560 | glDrawArrays(GL_TRIANGLES, 0, 12); |
| 561 | glBindBuffer(GL_ARRAY_BUFFER, 0); |
| 562 | } |
| 563 | |
| 564 | void TextureMapperGL::drawUnitRect(TextureMapperShaderProgram& program, GLenum drawingMode) |
| 565 | { |
| 566 | static const GLfloat unitRect[] = { 0, 0, 1, 0, 1, 1, 0, 1 }; |
| 567 | GLuint vbo = data().getStaticVBO(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, unitRect); |
| 568 | glBindBuffer(GL_ARRAY_BUFFER, vbo); |
| 569 | glVertexAttribPointer(program.vertexLocation(), 2, GL_FLOAT, false, 0, 0); |
| 570 | glDrawArrays(drawingMode, 0, 4); |
| 571 | glBindBuffer(GL_ARRAY_BUFFER, 0); |
| 572 | } |
| 573 | |
| 574 | void TextureMapperGL::draw(const FloatRect& rect, const TransformationMatrix& modelViewMatrix, TextureMapperShaderProgram& program, GLenum drawingMode, Flags flags) |
| 575 | { |
| 576 | TransformationMatrix matrix(modelViewMatrix); |
| 577 | matrix.multiply(TransformationMatrix::rectToRect(FloatRect(0, 0, 1, 1), rect)); |
| 578 | |
| 579 | glEnableVertexAttribArray(program.vertexLocation()); |
| 580 | program.setMatrix(program.modelViewMatrixLocation(), matrix); |
| 581 | program.setMatrix(program.projectionMatrixLocation(), data().projectionMatrix); |
| 582 | |
| 583 | if (isInMaskMode()) { |
| 584 | glBlendFunc(GL_ZERO, GL_SRC_ALPHA); |
| 585 | glEnable(GL_BLEND); |
| 586 | } else { |
| 587 | if (flags & ShouldBlend) { |
| 588 | glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); |
| 589 | glEnable(GL_BLEND); |
| 590 | } else |
| 591 | glDisable(GL_BLEND); |
| 592 | } |
| 593 | |
| 594 | if (flags & ShouldAntialias) |
| 595 | drawEdgeTriangles(program); |
| 596 | else |
| 597 | drawUnitRect(program, drawingMode); |
| 598 | |
| 599 | glDisableVertexAttribArray(program.vertexLocation()); |
| 600 | glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); |
| 601 | glEnable(GL_BLEND); |
| 602 | } |
| 603 | |
| 604 | void TextureMapperGL::drawTexturedQuadWithProgram(TextureMapperShaderProgram& program, uint32_t texture, Flags flags, const IntSize& size, const FloatRect& rect, const TransformationMatrix& modelViewMatrix, float opacity) |
| 605 | { |
| 606 | glUseProgram(program.programID()); |
| 607 | glActiveTexture(GL_TEXTURE0); |
| 608 | GLenum target = flags & ShouldUseARBTextureRect ? GLenum(GL_TEXTURE_RECTANGLE_ARB) : GLenum(GL_TEXTURE_2D); |
| 609 | glBindTexture(target, texture); |
| 610 | glUniform1i(program.samplerLocation(), 0); |
| 611 | if (wrapMode() == RepeatWrap && m_contextAttributes.supportsNPOTTextures) { |
| 612 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); |
| 613 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); |
| 614 | } |
| 615 | |
| 616 | TransformationMatrix patternTransform = this->patternTransform(); |
| 617 | if (flags & ShouldRotateTexture90) { |
| 618 | patternTransform.rotate(-90); |
| 619 | patternTransform.translate(-1, 0); |
| 620 | } |
| 621 | if (flags & ShouldRotateTexture180) { |
| 622 | patternTransform.rotate(180); |
| 623 | patternTransform.translate(-1, -1); |
| 624 | } |
| 625 | if (flags & ShouldRotateTexture270) { |
| 626 | patternTransform.rotate(-270); |
| 627 | patternTransform.translate(0, -1); |
| 628 | } |
| 629 | if (flags & ShouldFlipTexture) |
| 630 | patternTransform.flipY(); |
| 631 | if (flags & ShouldUseARBTextureRect) |
| 632 | patternTransform.scaleNonUniform(size.width(), size.height()); |
| 633 | if (flags & ShouldFlipTexture) |
| 634 | patternTransform.translate(0, -1); |
| 635 | |
| 636 | program.setMatrix(program.textureSpaceMatrixLocation(), patternTransform); |
| 637 | program.setMatrix(program.textureColorSpaceMatrixLocation(), colorSpaceMatrixForFlags(flags)); |
| 638 | glUniform1f(program.opacityLocation(), opacity); |
| 639 | |
| 640 | if (opacity < 1) |
| 641 | flags |= ShouldBlend; |
| 642 | |
| 643 | draw(rect, modelViewMatrix, program, GL_TRIANGLE_FAN, flags); |
| 644 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| 645 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| 646 | } |
| 647 | |
| 648 | void TextureMapperGL::drawFiltered(const BitmapTexture& sampler, const BitmapTexture* contentTexture, const FilterOperation& filter, int pass) |
| 649 | { |
| 650 | // For standard filters, we always draw the whole texture without transformations. |
| 651 | TextureMapperShaderProgram::Options options = optionsForFilterType(filter.type(), pass); |
| 652 | Ref<TextureMapperShaderProgram> program = data().getShaderProgram(options); |
| 653 | |
| 654 | prepareFilterProgram(program.get(), filter, pass, sampler.contentSize(), contentTexture ? static_cast<const BitmapTextureGL*>(contentTexture)->id() : 0); |
| 655 | FloatRect targetRect(IntPoint::zero(), sampler.contentSize()); |
| 656 | drawTexturedQuadWithProgram(program.get(), static_cast<const BitmapTextureGL&>(sampler).id(), 0, IntSize(1, 1), targetRect, TransformationMatrix(), 1); |
| 657 | } |
| 658 | |
| 659 | static inline TransformationMatrix createProjectionMatrix(const IntSize& size, bool mirrored) |
| 660 | { |
| 661 | const float nearValue = 9999999; |
| 662 | const float farValue = -99999; |
| 663 | |
| 664 | return TransformationMatrix(2.0 / float(size.width()), 0, 0, 0, |
| 665 | 0, (mirrored ? 2.0 : -2.0) / float(size.height()), 0, 0, |
| 666 | 0, 0, -2.f / (farValue - nearValue), 0, |
| 667 | -1, mirrored ? -1 : 1, -(farValue + nearValue) / (farValue - nearValue), 1); |
| 668 | } |
| 669 | |
| 670 | TextureMapperGL::~TextureMapperGL() |
| 671 | { |
| 672 | delete m_data; |
| 673 | } |
| 674 | |
| 675 | void TextureMapperGL::bindDefaultSurface() |
| 676 | { |
| 677 | glBindFramebuffer(GL_FRAMEBUFFER, data().targetFrameBuffer); |
| 678 | auto& viewport = data().viewport; |
| 679 | data().projectionMatrix = createProjectionMatrix(IntSize(viewport[2], viewport[3]), data().PaintFlags & PaintingMirrored); |
| 680 | glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); |
| 681 | m_clipStack.apply(); |
| 682 | data().currentSurface = nullptr; |
| 683 | } |
| 684 | |
| 685 | void TextureMapperGL::bindSurface(BitmapTexture *surface) |
| 686 | { |
| 687 | if (!surface) { |
| 688 | bindDefaultSurface(); |
| 689 | return; |
| 690 | } |
| 691 | |
| 692 | static_cast<BitmapTextureGL*>(surface)->bindAsSurface(); |
| 693 | data().projectionMatrix = createProjectionMatrix(surface->size(), true /* mirrored */); |
| 694 | data().currentSurface = surface; |
| 695 | } |
| 696 | |
| 697 | BitmapTexture* TextureMapperGL::currentSurface() |
| 698 | { |
| 699 | return data().currentSurface.get(); |
| 700 | } |
| 701 | |
| 702 | bool TextureMapperGL::beginScissorClip(const TransformationMatrix& modelViewMatrix, const FloatRect& targetRect) |
| 703 | { |
| 704 | // 3D transforms are currently not supported in scissor clipping |
| 705 | // resulting in cropped surfaces when z>0. |
| 706 | if (!modelViewMatrix.isAffine()) |
| 707 | return false; |
| 708 | |
| 709 | FloatQuad quad = modelViewMatrix.projectQuad(targetRect); |
| 710 | IntRect rect = quad.enclosingBoundingBox(); |
| 711 | |
| 712 | // Only use scissors on rectilinear clips. |
| 713 | if (!quad.isRectilinear() || rect.isEmpty()) |
| 714 | return false; |
| 715 | |
| 716 | clipStack().intersect(rect); |
| 717 | clipStack().applyIfNeeded(); |
| 718 | return true; |
| 719 | } |
| 720 | |
| 721 | void TextureMapperGL::beginClip(const TransformationMatrix& modelViewMatrix, const FloatRect& targetRect) |
| 722 | { |
| 723 | clipStack().push(); |
| 724 | if (beginScissorClip(modelViewMatrix, targetRect)) |
| 725 | return; |
| 726 | |
| 727 | data().initializeStencil(); |
| 728 | |
| 729 | Ref<TextureMapperShaderProgram> program = data().getShaderProgram(TextureMapperShaderProgram::SolidColor); |
| 730 | |
| 731 | glUseProgram(program->programID()); |
| 732 | glEnableVertexAttribArray(program->vertexLocation()); |
| 733 | const GLfloat unitRect[] = {0, 0, 1, 0, 1, 1, 0, 1}; |
| 734 | GLuint vbo = data().getStaticVBO(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, unitRect); |
| 735 | glBindBuffer(GL_ARRAY_BUFFER, vbo); |
| 736 | glVertexAttribPointer(program->vertexLocation(), 2, GL_FLOAT, false, 0, 0); |
| 737 | |
| 738 | TransformationMatrix matrix(modelViewMatrix); |
| 739 | matrix.multiply(TransformationMatrix::rectToRect(FloatRect(0, 0, 1, 1), targetRect)); |
| 740 | |
| 741 | static const TransformationMatrix fullProjectionMatrix = TransformationMatrix::rectToRect(FloatRect(0, 0, 1, 1), FloatRect(-1, -1, 2, 2)); |
| 742 | |
| 743 | int stencilIndex = clipStack().getStencilIndex(); |
| 744 | |
| 745 | glEnable(GL_STENCIL_TEST); |
| 746 | |
| 747 | // Make sure we don't do any actual drawing. |
| 748 | glStencilFunc(GL_NEVER, stencilIndex, stencilIndex); |
| 749 | |
| 750 | // Operate only on the stencilIndex and above. |
| 751 | glStencilMask(0xff & ~(stencilIndex - 1)); |
| 752 | |
| 753 | // First clear the entire buffer at the current index. |
| 754 | program->setMatrix(program->projectionMatrixLocation(), fullProjectionMatrix); |
| 755 | program->setMatrix(program->modelViewMatrixLocation(), TransformationMatrix()); |
| 756 | glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); |
| 757 | glDrawArrays(GL_TRIANGLE_FAN, 0, 4); |
| 758 | |
| 759 | // Now apply the current index to the new quad. |
| 760 | glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); |
| 761 | program->setMatrix(program->projectionMatrixLocation(), data().projectionMatrix); |
| 762 | program->setMatrix(program->modelViewMatrixLocation(), matrix); |
| 763 | glDrawArrays(GL_TRIANGLE_FAN, 0, 4); |
| 764 | |
| 765 | // Clear the state. |
| 766 | glBindBuffer(GL_ARRAY_BUFFER, 0); |
| 767 | glDisableVertexAttribArray(program->vertexLocation()); |
| 768 | glStencilMask(0); |
| 769 | |
| 770 | // Increase stencilIndex and apply stencil testing. |
| 771 | clipStack().setStencilIndex(stencilIndex * 2); |
| 772 | clipStack().applyIfNeeded(); |
| 773 | } |
| 774 | |
| 775 | void TextureMapperGL::endClip() |
| 776 | { |
| 777 | clipStack().pop(); |
| 778 | clipStack().applyIfNeeded(); |
| 779 | } |
| 780 | |
| 781 | IntRect TextureMapperGL::clipBounds() |
| 782 | { |
| 783 | return clipStack().current().scissorBox; |
| 784 | } |
| 785 | |
| 786 | Ref<BitmapTexture> TextureMapperGL::createTexture(GLint internalFormat) |
| 787 | { |
| 788 | return BitmapTextureGL::create(m_contextAttributes, internalFormat); |
| 789 | } |
| 790 | |
| 791 | std::unique_ptr<TextureMapper> TextureMapper::platformCreateAccelerated() |
| 792 | { |
| 793 | return std::make_unique<TextureMapperGL>(); |
| 794 | } |
| 795 | |
| 796 | }; |
| 797 | |
| 798 | #endif // USE(TEXTURE_MAPPER_GL) |
| 799 | |