/* * Copyright (c) 2010, Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "GLES2Canvas.h" #include "DrawingBuffer.h" #include "FloatRect.h" #include "FloatSize.h" #include "GraphicsContext3D.h" #include "internal_glu.h" #include "IntRect.h" #include "LoopBlinnMathUtils.h" #include "LoopBlinnPathProcessor.h" #include "LoopBlinnSolidFillShader.h" #include "Path.h" #include "PlatformString.h" #include "SharedGraphicsContext3D.h" #if USE(SKIA) #include "SkPath.h" #endif #include "Texture.h" #define _USE_MATH_DEFINES #include #include #include namespace WebCore { // Number of line segments used to approximate bezier curves. const int pathTesselation = 30; typedef void (GLAPIENTRY *TESSCB)(); typedef WTF::Vector FloatVector; typedef WTF::Vector FloatPointVector; struct PathAndTransform { PathAndTransform(const Path& p, const AffineTransform& t) : path(p) , transform(t) { } Path path; AffineTransform transform; }; struct GLES2Canvas::State { State() : m_fillColor(0, 0, 0, 255) , m_shadowColor(0, 0, 0, 0) , m_alpha(1.0f) , m_compositeOp(CompositeSourceOver) , m_numClippingPaths(0) , m_shadowOffset(0, 0) , m_shadowBlur(0) , m_shadowsIgnoreTransforms(false) { } State(const State& other) : m_fillColor(other.m_fillColor) , m_shadowColor(other.m_shadowColor) , m_alpha(other.m_alpha) , m_compositeOp(other.m_compositeOp) , m_ctm(other.m_ctm) , m_clippingPaths() // Don't copy; clipping paths are tracked per-state. , m_numClippingPaths(other.m_numClippingPaths) , m_shadowOffset(other.m_shadowOffset) , m_shadowBlur(other.m_shadowBlur) , m_shadowsIgnoreTransforms(other.m_shadowsIgnoreTransforms) { } Color m_fillColor; Color m_shadowColor; float m_alpha; CompositeOperator m_compositeOp; AffineTransform m_ctm; WTF::Vector m_clippingPaths; int m_numClippingPaths; FloatSize m_shadowOffset; float m_shadowBlur; bool m_shadowsIgnoreTransforms; // Helper function for applying the state's alpha value to the given input // color to produce a new output color. The logic is the same as // PlatformContextSkia::State::applyAlpha(), but the type is different. Color applyAlpha(const Color& c) { int s = roundf(m_alpha * 256); if (s >= 256) return c; if (s < 0) return Color(); int a = (c.alpha() * s) >> 8; return Color(c.red(), c.green(), c.blue(), a); } bool shadowActive() const { return m_shadowColor.alpha() > 0 && (m_shadowBlur || m_shadowOffset.width() || m_shadowOffset.height()); } bool clippingEnabled() { return m_numClippingPaths > 0; } }; static inline FloatPoint operator*(const FloatPoint& f, float scale) { return FloatPoint(f.x() * scale, f.y() * scale); } static inline FloatPoint operator*(float scale, const FloatPoint& f) { return FloatPoint(f.x() * scale, f.y() * scale); } static inline FloatSize operator*(const FloatSize& f, float scale) { return FloatSize(f.width() * scale, f.height() * scale); } static inline FloatSize operator*(float scale, const FloatSize& f) { return FloatSize(f.width() * scale, f.height() * scale); } class Quadratic { public: Quadratic(FloatPoint a, FloatPoint b, FloatPoint c) : m_a(a), m_b(b), m_c(c) { } static Quadratic fromBezier(FloatPoint p0, FloatPoint p1, FloatPoint p2) { FloatSize p1s(p1.x(), p1.y()); FloatSize p2s(p2.x(), p2.y()); FloatPoint b = -2.0f * p0 + 2.0f * p1s; FloatPoint c = p0 - 2.0f * p1s + p2s; return Quadratic(p0, b, c); } inline FloatPoint evaluate(float t) { return m_a + t * (m_b + t * m_c); } FloatPoint m_a, m_b, m_c, m_d; }; class Cubic { public: Cubic(FloatPoint a, FloatPoint b, FloatPoint c, FloatPoint d) : m_a(a), m_b(b), m_c(c), m_d(d) { } static Cubic fromBezier(FloatPoint p0, FloatPoint p1, FloatPoint p2, FloatPoint p3) { FloatSize p1s(p1.x(), p1.y()); FloatSize p2s(p2.x(), p2.y()); FloatSize p3s(p3.x(), p3.y()); FloatPoint b = -3.0f * p0 + 3.0f * p1s; FloatPoint c = 3.0f * p0 - 6.0f * p1s + 3.0f * p2s; FloatPoint d = -1.0f * p0 + 3.0f * p1s - 3.0f * p2s + p3s; return Cubic(p0, b, c, d); } inline FloatPoint evaluate(float t) { return m_a + t * (m_b + t * (m_c + t * m_d)); } FloatPoint m_a, m_b, m_c, m_d; }; GLES2Canvas::GLES2Canvas(SharedGraphicsContext3D* context, DrawingBuffer* drawingBuffer, const IntSize& size) : m_size(size) , m_context(context) , m_drawingBuffer(drawingBuffer) , m_state(0) , m_pathIndexBuffer(0) , m_pathVertexBuffer(0) { m_flipMatrix.translate(-1.0f, 1.0f); m_flipMatrix.scale(2.0f / size.width(), -2.0f / size.height()); m_stateStack.append(State()); m_state = &m_stateStack.last(); } GLES2Canvas::~GLES2Canvas() { if (m_pathIndexBuffer) m_context->graphicsContext3D()->deleteBuffer(m_pathIndexBuffer); if (m_pathVertexBuffer) m_context->graphicsContext3D()->deleteBuffer(m_pathVertexBuffer); } void GLES2Canvas::bindFramebuffer() { m_drawingBuffer->bind(); } void GLES2Canvas::clearRect(const FloatRect& rect) { bindFramebuffer(); if (m_state->m_ctm.isIdentity() && !m_state->clippingEnabled()) { scissorClear(rect.x(), rect.y(), rect.width(), rect.height()); } else { save(); setCompositeOperation(CompositeClear); fillRect(rect, Color(RGBA32(0)), ColorSpaceDeviceRGB); restore(); } } void GLES2Canvas::applyState() { bindFramebuffer(); m_context->applyCompositeOperator(m_state->m_compositeOp); applyClipping(m_state->clippingEnabled()); } void GLES2Canvas::scissorClear(float x, float y, float width, float height) { int intX = static_cast(x + 0.5f); int intY = static_cast(y + 0.5f); int intWidth = static_cast(x + width + 0.5f) - intX; int intHeight = static_cast(y + height + 0.5f) - intY; m_context->scissor(intX, m_size.height() - intHeight - intY, intWidth, intHeight); m_context->enable(GraphicsContext3D::SCISSOR_TEST); m_context->clearColor(Color(RGBA32(0))); m_context->clear(GraphicsContext3D::COLOR_BUFFER_BIT); m_context->disable(GraphicsContext3D::SCISSOR_TEST); } void GLES2Canvas::fillPath(const Path& path) { if (m_state->shadowActive()) { beginShadowDraw(); fillPathInternal(path, m_state->m_shadowColor); endShadowDraw(path.boundingRect()); } applyState(); fillPathInternal(path, m_state->applyAlpha(m_state->m_fillColor)); } void GLES2Canvas::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace) { if (m_state->shadowActive()) { beginShadowDraw(); fillRectInternal(rect, m_state->m_shadowColor); endShadowDraw(rect); } applyState(); fillRectInternal(rect, color); } void GLES2Canvas::fillRect(const FloatRect& rect) { fillRect(rect, m_state->applyAlpha(m_state->m_fillColor), ColorSpaceDeviceRGB); } void GLES2Canvas::fillRectInternal(const FloatRect& rect, const Color& color) { AffineTransform matrix(m_flipMatrix); matrix *= m_state->m_ctm; matrix.translate(rect.x(), rect.y()); matrix.scale(rect.width(), rect.height()); m_context->useQuadVertices(); m_context->useFillSolidProgram(matrix, color); m_context->drawArrays(GraphicsContext3D::TRIANGLE_STRIP, 0, 4); } void GLES2Canvas::setFillColor(const Color& color, ColorSpace colorSpace) { m_state->m_fillColor = color; } void GLES2Canvas::setAlpha(float alpha) { m_state->m_alpha = alpha; } void GLES2Canvas::setShadowColor(const Color& color, ColorSpace) { m_state->m_shadowColor = color; } void GLES2Canvas::setShadowOffset(const FloatSize& offset) { m_state->m_shadowOffset = offset; } void GLES2Canvas::setShadowBlur(float shadowBlur) { m_state->m_shadowBlur = shadowBlur; } void GLES2Canvas::setShadowsIgnoreTransforms(bool shadowsIgnoreTransforms) { m_state->m_shadowsIgnoreTransforms = shadowsIgnoreTransforms; } void GLES2Canvas::translate(float x, float y) { m_state->m_ctm.translate(x, y); } void GLES2Canvas::rotate(float angleInRadians) { m_state->m_ctm.rotate(angleInRadians * (180.0f / M_PI)); } void GLES2Canvas::scale(const FloatSize& size) { m_state->m_ctm.scale(size.width(), size.height()); } void GLES2Canvas::concatCTM(const AffineTransform& affine) { m_state->m_ctm *= affine; } void GLES2Canvas::setCTM(const AffineTransform& affine) { m_state->m_ctm = affine; } void GLES2Canvas::clipPath(const Path& path) { bindFramebuffer(); checkGLError("bindFramebuffer"); beginStencilDraw(GraphicsContext3D::INCR); // Red is used so we can see it if it ends up in the color buffer. Color red(255, 0, 0, 255); fillPathInternal(path, red); m_state->m_clippingPaths.append(PathAndTransform(path, m_state->m_ctm)); m_state->m_numClippingPaths++; } void GLES2Canvas::clipOut(const Path& path) { ASSERT(!"clipOut is unsupported in GLES2Canvas.\n"); } void GLES2Canvas::save() { m_stateStack.append(State(m_stateStack.last())); m_state = &m_stateStack.last(); } void GLES2Canvas::restore() { ASSERT(!m_stateStack.isEmpty()); const Vector& clippingPaths = m_state->m_clippingPaths; if (!clippingPaths.isEmpty()) { beginStencilDraw(GraphicsContext3D::DECR); WTF::Vector::const_iterator pathIter; for (pathIter = clippingPaths.begin(); pathIter < clippingPaths.end(); ++pathIter) { m_state->m_ctm = pathIter->transform; // Red is used so we can see it if it ends up in the color buffer. Color red(255, 0, 0, 255); fillPathInternal(pathIter->path, red); } } m_stateStack.removeLast(); m_state = &m_stateStack.last(); } void GLES2Canvas::drawTexturedRect(unsigned texture, const IntSize& textureSize, const FloatRect& srcRect, const FloatRect& dstRect, ColorSpace colorSpace, CompositeOperator compositeOp) { bindFramebuffer(); m_context->applyCompositeOperator(compositeOp); applyClipping(false); m_context->setActiveTexture(GraphicsContext3D::TEXTURE0); m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, texture); drawTexturedQuad(textureSize, srcRect, dstRect, m_state->m_ctm, m_state->m_alpha); } void GLES2Canvas::drawTexturedRect(Texture* texture, const FloatRect& srcRect, const FloatRect& dstRect, ColorSpace colorSpace, CompositeOperator compositeOp) { drawTexturedRect(texture, srcRect, dstRect, m_state->m_ctm, m_state->m_alpha, colorSpace, compositeOp, m_state->clippingEnabled()); } void GLES2Canvas::drawTexturedRect(Texture* texture, const FloatRect& srcRect, const FloatRect& dstRect, const AffineTransform& transform, float alpha, ColorSpace colorSpace, CompositeOperator compositeOp, bool clip) { bindFramebuffer(); m_context->applyCompositeOperator(compositeOp); applyClipping(clip); const TilingData& tiles = texture->tiles(); IntRect tileIdxRect = tiles.overlappedTileIndices(srcRect); m_context->setActiveTexture(GraphicsContext3D::TEXTURE0); for (int y = tileIdxRect.y(); y <= tileIdxRect.maxY(); y++) { for (int x = tileIdxRect.x(); x <= tileIdxRect.maxX(); x++) drawTexturedRectTile(texture, tiles.tileIndex(x, y), srcRect, dstRect, transform, alpha); } } void GLES2Canvas::drawTexturedRectTile(Texture* texture, int tile, const FloatRect& srcRect, const FloatRect& dstRect, const AffineTransform& transform, float alpha) { if (dstRect.isEmpty()) return; const TilingData& tiles = texture->tiles(); texture->bindTile(tile); FloatRect srcRectClippedInTileSpace; FloatRect dstRectIntersected; tiles.intersectDrawQuad(srcRect, dstRect, tile, &srcRectClippedInTileSpace, &dstRectIntersected); IntRect tileBoundsWithBorder = tiles.tileBoundsWithBorder(tile); drawTexturedQuad(tileBoundsWithBorder.size(), srcRectClippedInTileSpace, dstRectIntersected, transform, alpha); } void GLES2Canvas::convolveRect(unsigned texture, const IntSize& textureSize, const FloatRect& srcRect, const FloatRect& dstRect, float imageIncrement[2], const float* kernel, int kernelWidth) { m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, texture); m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE); m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::CLAMP_TO_EDGE); m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::NEAREST); m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::NEAREST); AffineTransform matrix(m_flipMatrix); matrix.translate(dstRect.x(), dstRect.y()); matrix.scale(dstRect.width(), dstRect.height()); AffineTransform texMatrix; texMatrix.scale(1.0f / textureSize.width(), 1.0f / textureSize.height()); texMatrix.translate(srcRect.x(), srcRect.y()); texMatrix.scale(srcRect.width(), srcRect.height()); m_context->useQuadVertices(); m_context->useConvolutionProgram(matrix, texMatrix, kernel, kernelWidth, imageIncrement); m_context->drawArrays(GraphicsContext3D::TRIANGLE_STRIP, 0, 4); checkGLError("glDrawArrays"); } static float gauss(float x, float sigma) { return exp(- (x * x) / (2.0f * sigma * sigma)); } static void buildKernel(float sigma, float* kernel, int kernelWidth) { float halfWidth = (kernelWidth - 1.0f) / 2.0f; float sum = 0.0f; for (int i = 0; i < kernelWidth; ++i) { kernel[i] = gauss(i - halfWidth, sigma); sum += kernel[i]; } // Normalize the kernel float scale = 1.0f / sum; for (int i = 0; i < kernelWidth; ++i) kernel[i] *= scale; } void GLES2Canvas::drawTexturedQuad(const IntSize& textureSize, const FloatRect& srcRect, const FloatRect& dstRect, const AffineTransform& transform, float alpha) { AffineTransform matrix(m_flipMatrix); matrix *= transform; matrix.translate(dstRect.x(), dstRect.y()); matrix.scale(dstRect.width(), dstRect.height()); AffineTransform texMatrix; texMatrix.scale(1.0f / textureSize.width(), 1.0f / textureSize.height()); texMatrix.translate(srcRect.x(), srcRect.y()); texMatrix.scale(srcRect.width(), srcRect.height()); m_context->useQuadVertices(); m_context->useTextureProgram(matrix, texMatrix, alpha); m_context->drawArrays(GraphicsContext3D::TRIANGLE_STRIP, 0, 4); checkGLError("glDrawArrays"); } void GLES2Canvas::drawTexturedQuadMitchell(const IntSize& textureSize, const FloatRect& srcRect, const FloatRect& dstRect, const AffineTransform& transform, float alpha) { static const float mitchellCoefficients[16] = { 0.0f / 18.0f, 1.0f / 18.0f, 16.0f / 18.0f, 1.0f / 18.0f, 0.0f / 18.0f, 9.0f / 18.0f, 0.0f / 18.0f, -9.0f / 18.0f, -6.0f / 18.0f, 27.0f / 18.0f, -36.0f / 18.0f, 15.0f / 18.0f, 7.0f / 18.0f, -21.0f / 18.0f, 21.0f / 18.0f, -7.0f / 18.0f, }; AffineTransform matrix(m_flipMatrix); matrix *= transform; matrix.translate(dstRect.x(), dstRect.y()); matrix.scale(dstRect.width(), dstRect.height()); float imageIncrement[2] = { 1.0f / textureSize.width(), 1.0f / textureSize.height() }; AffineTransform texMatrix; texMatrix.scale(imageIncrement[0], imageIncrement[1]); texMatrix.translate(srcRect.x(), srcRect.y()); texMatrix.scale(srcRect.width(), srcRect.height()); m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::NEAREST); m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::NEAREST); m_context->useQuadVertices(); m_context->useBicubicProgram(matrix, texMatrix, mitchellCoefficients, imageIncrement, alpha); m_context->drawArrays(GraphicsContext3D::TRIANGLE_STRIP, 0, 4); checkGLError("glDrawArrays"); } void GLES2Canvas::setCompositeOperation(CompositeOperator op) { m_state->m_compositeOp = op; } Texture* GLES2Canvas::createTexture(NativeImagePtr ptr, Texture::Format format, int width, int height) { return m_context->createTexture(ptr, format, width, height); } Texture* GLES2Canvas::getTexture(NativeImagePtr ptr) { return m_context->getTexture(ptr); } #if USE(SKIA) // This is actually cross-platform code, but since its only caller is inside a // USE(SKIA), it will cause a warning-as-error on Chrome/Mac. static void interpolateQuadratic(FloatPointVector* vertices, const FloatPoint& p0, const FloatPoint& p1, const FloatPoint& p2) { float tIncrement = 1.0f / pathTesselation, t = tIncrement; Quadratic c = Quadratic::fromBezier(p0, p1, p2); for (int i = 0; i < pathTesselation; ++i, t += tIncrement) vertices->append(c.evaluate(t)); } static void interpolateCubic(FloatPointVector* vertices, const FloatPoint& p0, const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& p3) { float tIncrement = 1.0f / pathTesselation, t = tIncrement; Cubic c = Cubic::fromBezier(p0, p1, p2, p3); for (int i = 0; i < pathTesselation; ++i, t += tIncrement) vertices->append(c.evaluate(t)); } #endif struct PolygonData { PolygonData(FloatPointVector* vertices, WTF::Vector* indices) : m_vertices(vertices) , m_indices(indices) { } FloatPointVector* m_vertices; WTF::Vector* m_indices; }; static void beginData(GLenum type, void* data) { ASSERT(type == GL_TRIANGLES); } static void edgeFlagData(GLboolean flag, void* data) { } static void vertexData(void* vertexData, void* data) { static_cast(data)->m_indices->append(reinterpret_cast(vertexData)); } static void endData(void* data) { } static void combineData(GLdouble coords[3], void* vertexData[4], GLfloat weight[4], void **outData, void* data) { PolygonData* polygonData = static_cast(data); int index = polygonData->m_vertices->size(); polygonData->m_vertices->append(FloatPoint(static_cast(coords[0]), static_cast(coords[1]))); *outData = reinterpret_cast(index); } typedef void (*TESSCB)(); void GLES2Canvas::tesselateAndFillPath(const Path& path, const Color& color) { if (!m_pathVertexBuffer) m_pathVertexBuffer = m_context->graphicsContext3D()->createBuffer(); if (!m_pathIndexBuffer) m_pathIndexBuffer = m_context->graphicsContext3D()->createBuffer(); AffineTransform matrix(m_flipMatrix); matrix *= m_state->m_ctm; FloatPointVector inVertices; WTF::Vector contours; #if USE(SKIA) const SkPath* skPath = path.platformPath(); SkPoint pts[4]; SkPath::Iter iter(*skPath, true); SkPath::Verb verb; while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { switch (verb) { case SkPath::kMove_Verb: inVertices.append(pts[0]); break; case SkPath::kLine_Verb: inVertices.append(pts[1]); break; case SkPath::kQuad_Verb: interpolateQuadratic(&inVertices, pts[0], pts[1], pts[2]); break; case SkPath::kCubic_Verb: interpolateCubic(&inVertices, pts[0], pts[1], pts[2], pts[3]); break; case SkPath::kClose_Verb: contours.append(inVertices.size()); break; case SkPath::kDone_Verb: break; } } #else ASSERT(!"Path extraction not implemented on this platform."); #endif if (contours.size() == 1 && LoopBlinnMathUtils::isConvex(inVertices.begin(), inVertices.size())) { m_context->graphicsContext3D()->bindBuffer(GraphicsContext3D::ARRAY_BUFFER, m_pathVertexBuffer); m_context->graphicsContext3D()->bufferData(GraphicsContext3D::ARRAY_BUFFER, inVertices.size() * 2 * sizeof(float), inVertices.data(), GraphicsContext3D::STREAM_DRAW); m_context->useFillSolidProgram(matrix, color); m_context->graphicsContext3D()->drawArrays(GraphicsContext3D::TRIANGLE_FAN, 0, inVertices.size()); return; } OwnArrayPtr inVerticesDouble = adoptArrayPtr(new double[inVertices.size() * 3]); for (size_t i = 0; i < inVertices.size(); ++i) { inVerticesDouble[i * 3 ] = inVertices[i].x(); inVerticesDouble[i * 3 + 1] = inVertices[i].y(); inVerticesDouble[i * 3 + 2] = 1.0; } GLUtesselator* tess = internal_gluNewTess(); internal_gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO); internal_gluTessCallback(tess, GLU_TESS_BEGIN_DATA, (TESSCB) &beginData); internal_gluTessCallback(tess, GLU_TESS_VERTEX_DATA, (TESSCB) &vertexData); internal_gluTessCallback(tess, GLU_TESS_END_DATA, (TESSCB) &endData); internal_gluTessCallback(tess, GLU_TESS_EDGE_FLAG_DATA, (TESSCB) &edgeFlagData); internal_gluTessCallback(tess, GLU_TESS_COMBINE_DATA, (TESSCB) &combineData); WTF::Vector indices; FloatPointVector vertices; vertices.reserveInitialCapacity(inVertices.size()); PolygonData data(&vertices, &indices); internal_gluTessBeginPolygon(tess, &data); WTF::Vector::const_iterator contour; size_t i = 0; for (contour = contours.begin(); contour != contours.end(); ++contour) { internal_gluTessBeginContour(tess); for (; i < *contour; ++i) { double* inVertex = &inVerticesDouble[i * 3]; vertices.append(FloatPoint(inVertex[0], inVertex[1])); internal_gluTessVertex(tess, inVertex, reinterpret_cast(i)); } internal_gluTessEndContour(tess); } internal_gluTessEndPolygon(tess); internal_gluDeleteTess(tess); m_context->graphicsContext3D()->bindBuffer(GraphicsContext3D::ARRAY_BUFFER, m_pathVertexBuffer); checkGLError("createVertexBufferFromPath, bindBuffer ARRAY_BUFFER"); m_context->graphicsContext3D()->bufferData(GraphicsContext3D::ARRAY_BUFFER, vertices.size() * 2 * sizeof(float), vertices.data(), GraphicsContext3D::STREAM_DRAW); checkGLError("createVertexBufferFromPath, bufferData ARRAY_BUFFER"); m_context->graphicsContext3D()->bindBuffer(GraphicsContext3D::ELEMENT_ARRAY_BUFFER, m_pathIndexBuffer); checkGLError("createVertexBufferFromPath, bindBuffer ELEMENT_ARRAY_BUFFER"); m_context->graphicsContext3D()->bufferData(GraphicsContext3D::ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(short), indices.data(), GraphicsContext3D::STREAM_DRAW); checkGLError("createVertexBufferFromPath, bufferData ELEMENT_ARRAY_BUFFER"); m_context->useFillSolidProgram(matrix, color); m_context->graphicsContext3D()->drawElements(GraphicsContext3D::TRIANGLES, indices.size(), GraphicsContext3D::UNSIGNED_SHORT, 0); } void GLES2Canvas::fillPathInternal(const Path& path, const Color& color) { if (SharedGraphicsContext3D::useLoopBlinnForPathRendering()) { m_pathCache.clear(); LoopBlinnPathProcessor processor; processor.process(path, m_pathCache); if (!m_pathVertexBuffer) m_pathVertexBuffer = m_context->createBuffer(); m_context->bindBuffer(GraphicsContext3D::ARRAY_BUFFER, m_pathVertexBuffer); int byteSizeOfVertices = 2 * m_pathCache.numberOfVertices() * sizeof(float); int byteSizeOfTexCoords = 3 * m_pathCache.numberOfVertices() * sizeof(float); int byteSizeOfInteriorVertices = 2 * m_pathCache.numberOfInteriorVertices() * sizeof(float); m_context->bufferData(GraphicsContext3D::ARRAY_BUFFER, byteSizeOfVertices + byteSizeOfTexCoords + byteSizeOfInteriorVertices, GraphicsContext3D::STATIC_DRAW); m_context->bufferSubData(GraphicsContext3D::ARRAY_BUFFER, 0, byteSizeOfVertices, m_pathCache.vertices()); m_context->bufferSubData(GraphicsContext3D::ARRAY_BUFFER, byteSizeOfVertices, byteSizeOfTexCoords, m_pathCache.texcoords()); m_context->bufferSubData(GraphicsContext3D::ARRAY_BUFFER, byteSizeOfVertices + byteSizeOfTexCoords, byteSizeOfInteriorVertices, m_pathCache.interiorVertices()); AffineTransform matrix(m_flipMatrix); matrix *= m_state->m_ctm; // Draw the exterior m_context->useLoopBlinnExteriorProgram(0, byteSizeOfVertices, matrix, color); m_context->drawArrays(GraphicsContext3D::TRIANGLES, 0, m_pathCache.numberOfVertices()); // Draw the interior m_context->useLoopBlinnInteriorProgram(byteSizeOfVertices + byteSizeOfTexCoords, matrix, color); m_context->drawArrays(GraphicsContext3D::TRIANGLES, 0, m_pathCache.numberOfInteriorVertices()); } else { tesselateAndFillPath(path, color); } } FloatRect GLES2Canvas::flipRect(const FloatRect& rect) { FloatRect flippedRect(rect); flippedRect.setY(m_size.height() - rect.y()); flippedRect.setHeight(-rect.height()); return flippedRect; } void GLES2Canvas::clearBorders(const FloatRect& rect, int width) { scissorClear(rect.x(), rect.y() - width, rect.width() + width, width); scissorClear(rect.maxX(), rect.y(), width, rect.height() + width); scissorClear(rect.x() - width, rect.maxY(), rect.width() + width, width); scissorClear(rect.x() - width, rect.y() - width, width, rect.height() + width); } void GLES2Canvas::beginShadowDraw() { float offsetX = m_state->m_shadowOffset.width(); float offsetY = m_state->m_shadowOffset.height(); save(); if (m_state->m_shadowsIgnoreTransforms) { AffineTransform newCTM; newCTM.translate(offsetX, -offsetY); newCTM *= m_state->m_ctm; m_state->m_ctm = newCTM; } else m_state->m_ctm.translate(offsetX, offsetY); if (m_state->m_shadowBlur > 0) { // Draw hard shadow to offscreen buffer 0. DrawingBuffer* dstBuffer = m_context->getOffscreenBuffer(0, m_size); dstBuffer->bind(); m_context->applyCompositeOperator(CompositeCopy); applyClipping(false); m_context->clearColor(Color(RGBA32(0))); m_context->clear(GraphicsContext3D::COLOR_BUFFER_BIT); } else { applyState(); } } void GLES2Canvas::endShadowDraw(const FloatRect& boundingBox) { if (m_state->m_shadowBlur > 0) { // Buffer 0 contains the primitive drawn with a hard shadow. DrawingBuffer* srcBuffer = m_context->getOffscreenBuffer(0, m_size); DrawingBuffer* dstBuffer = m_context->getOffscreenBuffer(1, m_size); float sigma = m_state->m_shadowBlur * 0.333333f; FloatRect shadowBoundingBox(m_state->m_ctm.mapRect(boundingBox)); FloatRect rect(FloatPoint(0, 0), m_size); FloatRect srcRect(shadowBoundingBox); int scaleFactor = 1; while (sigma > cMaxSigma) { srcRect.scale(0.5f); scaleFactor *= 2; sigma *= 0.5f; } srcRect = enclosingIntRect(srcRect); srcRect.scale(scaleFactor); for (int i = 1; i < scaleFactor; i *= 2) { dstBuffer->bind(); m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, srcBuffer->colorBuffer()); m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE); m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::CLAMP_TO_EDGE); m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR); m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR); FloatRect dstRect(srcRect); dstRect.scale(0.5f); // Clear out 1 pixel border for linear filtering. clearBorders(dstRect, 1); drawTexturedQuad(srcBuffer->size(), flipRect(srcRect), dstRect, AffineTransform(), 1.0); srcRect = dstRect; std::swap(srcBuffer, dstBuffer); } int halfWidth = static_cast(sigma * 3.0f); int kernelWidth = halfWidth * 2 + 1; OwnArrayPtr kernel = adoptArrayPtr(new float[kernelWidth]); buildKernel(sigma, kernel.get(), kernelWidth); if (scaleFactor > 1) { scissorClear(srcRect.maxX(), srcRect.y(), kernelWidth, srcRect.height()); scissorClear(srcRect.x() - kernelWidth, srcRect.y(), kernelWidth, srcRect.height()); } // Blur in X offscreen. dstBuffer->bind(); srcRect.inflateX(halfWidth); srcRect.intersect(rect); float imageIncrementX[2] = {1.0f / srcBuffer->size().width(), 0.0f}; convolveRect(srcBuffer->colorBuffer(), srcBuffer->size(), flipRect(srcRect), srcRect, imageIncrementX, kernel.get(), kernelWidth); if (scaleFactor > 1) { scissorClear(srcRect.x(), srcRect.maxY(), srcRect.width(), kernelWidth); scissorClear(srcRect.x(), srcRect.y() - kernelWidth, srcRect.width(), kernelWidth); } srcRect.inflateY(halfWidth); srcRect.intersect(rect); std::swap(srcBuffer, dstBuffer); float imageIncrementY[2] = {0.0f, 1.0f / srcBuffer->size().height()}; if (scaleFactor > 1) { // Blur in Y offscreen. dstBuffer->bind(); convolveRect(srcBuffer->colorBuffer(), srcBuffer->size(), flipRect(srcRect), srcRect, imageIncrementY, kernel.get(), kernelWidth); // Clear out 2 pixel border for bicubic filtering. clearBorders(srcRect, 2); std::swap(srcBuffer, dstBuffer); // Upsample srcBuffer -> main framebuffer using bicubic filtering. applyState(); m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, srcBuffer->colorBuffer()); FloatRect dstRect = srcRect; dstRect.scale(scaleFactor); drawTexturedQuadMitchell(srcBuffer->size(), flipRect(srcRect), dstRect, AffineTransform(), 1.0); } else { // Blur in Y directly to framebuffer. applyState(); convolveRect(srcBuffer->colorBuffer(), srcBuffer->size(), flipRect(srcRect), srcRect, imageIncrementY, kernel.get(), kernelWidth); } } restore(); } void GLES2Canvas::beginStencilDraw(unsigned op) { // Turn on stencil test. m_context->enableStencil(true); checkGLError("enable STENCIL_TEST"); // Stencil test never passes, so colorbuffer is not drawn. m_context->graphicsContext3D()->stencilFunc(GraphicsContext3D::NEVER, 1, 1); checkGLError("stencilFunc"); // All writes incremement the stencil buffer. m_context->graphicsContext3D()->stencilOp(op, op, op); checkGLError("stencilOp"); } void GLES2Canvas::applyClipping(bool enable) { m_context->enableStencil(enable); if (enable) { // Enable drawing only where stencil is non-zero. m_context->graphicsContext3D()->stencilFunc(GraphicsContext3D::EQUAL, m_state->m_numClippingPaths, -1); checkGLError("stencilFunc"); // Keep all stencil values the same. m_context->graphicsContext3D()->stencilOp(GraphicsContext3D::KEEP, GraphicsContext3D::KEEP, GraphicsContext3D::KEEP); checkGLError("stencilOp"); } } void GLES2Canvas::checkGLError(const char* header) { #ifndef NDEBUG unsigned err; while ((err = m_context->getError()) != GraphicsContext3D::NO_ERROR) { const char* errorStr = "*** UNKNOWN ERROR ***"; switch (err) { case GraphicsContext3D::INVALID_ENUM: errorStr = "GraphicsContext3D::INVALID_ENUM"; break; case GraphicsContext3D::INVALID_VALUE: errorStr = "GraphicsContext3D::INVALID_VALUE"; break; case GraphicsContext3D::INVALID_OPERATION: errorStr = "GraphicsContext3D::INVALID_OPERATION"; break; case GraphicsContext3D::INVALID_FRAMEBUFFER_OPERATION: errorStr = "GraphicsContext3D::INVALID_FRAMEBUFFER_OPERATION"; break; case GraphicsContext3D::OUT_OF_MEMORY: errorStr = "GraphicsContext3D::OUT_OF_MEMORY"; break; } if (header) LOG_ERROR("%s: %s", header, errorStr); else LOG_ERROR("%s", errorStr); } #endif } }