diff options
Diffstat (limited to 'cc/gl_renderer.cc')
-rw-r--r-- | cc/gl_renderer.cc | 1530 |
1 files changed, 1530 insertions, 0 deletions
diff --git a/cc/gl_renderer.cc b/cc/gl_renderer.cc new file mode 100644 index 0000000..f28cc50 --- /dev/null +++ b/cc/gl_renderer.cc @@ -0,0 +1,1530 @@ +// Copyright 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +#include "config.h" + +#if USE(ACCELERATED_COMPOSITING) +#include "CCRendererGL.h" + +#include "CCDamageTracker.h" +#include "CCLayerQuad.h" +#include "CCMathUtil.h" +#include "CCProxy.h" +#include "CCRenderPass.h" +#include "CCRenderSurfaceFilters.h" +#include "CCScopedTexture.h" +#include "CCSettings.h" +#include "CCSingleThreadProxy.h" +#include "CCVideoLayerImpl.h" +#include "Extensions3D.h" +#include "FloatQuad.h" +#include "GeometryBinding.h" +#include "GrTexture.h" +#include "NotImplemented.h" +#include "PlatformColor.h" +#include "SkBitmap.h" +#include "SkColor.h" +#include "TraceEvent.h" +#ifdef LOG +#undef LOG +#endif +#include "base/string_split.h" +#include "base/string_util.h" +#include <public/WebGraphicsContext3D.h> +#include <public/WebSharedGraphicsContext3D.h> +#include <public/WebVideoFrame.h> +#include <set> +#include <string> +#include <vector> +#include <wtf/CurrentTime.h> +#include <wtf/OwnArrayPtr.h> + +using namespace std; +using WebKit::WebGraphicsContext3D; +using WebKit::WebGraphicsMemoryAllocation; +using WebKit::WebSharedGraphicsContext3D; +using WebKit::WebTransformationMatrix; + +namespace cc { + +namespace { + +bool needsIOSurfaceReadbackWorkaround() +{ +#if OS(DARWIN) + return true; +#else + return false; +#endif +} + +} // anonymous namespace + +PassOwnPtr<CCRendererGL> CCRendererGL::create(CCRendererClient* client, CCResourceProvider* resourceProvider) +{ + OwnPtr<CCRendererGL> renderer(adoptPtr(new CCRendererGL(client, resourceProvider))); + if (!renderer->initialize()) + return nullptr; + + return renderer.release(); +} + +CCRendererGL::CCRendererGL(CCRendererClient* client, + CCResourceProvider* resourceProvider) + : CCDirectRenderer(client, resourceProvider) + , m_offscreenFramebufferId(0) + , m_sharedGeometryQuad(FloatRect(-0.5f, -0.5f, 1.0f, 1.0f)) + , m_context(resourceProvider->graphicsContext3D()) + , m_isViewportChanged(false) + , m_isFramebufferDiscarded(false) + , m_isUsingBindUniform(false) + , m_visible(true) +{ + ASSERT(m_context); +} + +bool CCRendererGL::initialize() +{ + if (!m_context->makeContextCurrent()) + return false; + + m_context->setContextLostCallback(this); + m_context->pushGroupMarkerEXT("CompositorContext"); + + std::string extensionsString = UTF16ToASCII(m_context->getString(GraphicsContext3D::EXTENSIONS)); + std::vector<std::string> extensionsList; + base::SplitString(extensionsString, ' ', &extensionsList); + std::set<string> extensions(extensionsList.begin(), extensionsList.end()); + + if (settings().acceleratePainting && extensions.count("GL_EXT_texture_format_BGRA8888") + && extensions.count("GL_EXT_read_format_bgra")) + m_capabilities.usingAcceleratedPainting = true; + else + m_capabilities.usingAcceleratedPainting = false; + + + m_capabilities.contextHasCachedFrontBuffer = extensions.count("GL_CHROMIUM_front_buffer_cached"); + + m_capabilities.usingPartialSwap = CCSettings::partialSwapEnabled() && extensions.count("GL_CHROMIUM_post_sub_buffer"); + + // Use the swapBuffers callback only with the threaded proxy. + if (CCProxy::hasImplThread()) + m_capabilities.usingSwapCompleteCallback = extensions.count("GL_CHROMIUM_swapbuffers_complete_callback"); + if (m_capabilities.usingSwapCompleteCallback) + m_context->setSwapBuffersCompleteCallbackCHROMIUM(this); + + m_capabilities.usingSetVisibility = extensions.count("GL_CHROMIUM_set_visibility"); + + if (extensions.count("GL_CHROMIUM_iosurface")) + ASSERT(extensions.count("GL_ARB_texture_rectangle")); + + m_capabilities.usingGpuMemoryManager = extensions.count("GL_CHROMIUM_gpu_memory_manager"); + if (m_capabilities.usingGpuMemoryManager) + m_context->setMemoryAllocationChangedCallbackCHROMIUM(this); + + m_capabilities.usingDiscardFramebuffer = extensions.count("GL_CHROMIUM_discard_framebuffer"); + + m_capabilities.usingEglImage = extensions.count("GL_OES_EGL_image_external"); + + GLC(m_context, m_context->getIntegerv(GraphicsContext3D::MAX_TEXTURE_SIZE, &m_capabilities.maxTextureSize)); + m_capabilities.bestTextureFormat = PlatformColor::bestTextureFormat(m_context, extensions.count("GL_EXT_texture_format_BGRA8888")); + + m_isUsingBindUniform = extensions.count("GL_CHROMIUM_bind_uniform_location"); + + if (!initializeSharedObjects()) + return false; + + // Make sure the viewport and context gets initialized, even if it is to zero. + viewportChanged(); + return true; +} + +CCRendererGL::~CCRendererGL() +{ + ASSERT(CCProxy::isImplThread()); + m_context->setSwapBuffersCompleteCallbackCHROMIUM(0); + m_context->setMemoryAllocationChangedCallbackCHROMIUM(0); + m_context->setContextLostCallback(0); + cleanupSharedObjects(); +} + +const RendererCapabilities& CCRendererGL::capabilities() const +{ + return m_capabilities; +} + +WebGraphicsContext3D* CCRendererGL::context() +{ + return m_context; +} + +void CCRendererGL::debugGLCall(WebGraphicsContext3D* context, const char* command, const char* file, int line) +{ + unsigned long error = context->getError(); + if (error != GraphicsContext3D::NO_ERROR) + LOG_ERROR("GL command failed: File: %s\n\tLine %d\n\tcommand: %s, error %x\n", file, line, command, static_cast<int>(error)); +} + +void CCRendererGL::setVisible(bool visible) +{ + if (m_visible == visible) + return; + m_visible = visible; + + // TODO: Replace setVisibilityCHROMIUM with an extension to explicitly manage front/backbuffers + // crbug.com/116049 + if (m_capabilities.usingSetVisibility) + m_context->setVisibilityCHROMIUM(visible); +} + +void CCRendererGL::releaseRenderPassTextures() +{ + m_renderPassTextures.clear(); +} + +void CCRendererGL::viewportChanged() +{ + m_isViewportChanged = true; +} + +void CCRendererGL::clearFramebuffer(DrawingFrame& frame) +{ + // On DEBUG builds, opaque render passes are cleared to blue to easily see regions that were not drawn on the screen. + if (frame.currentRenderPass->hasTransparentBackground()) + GLC(m_context, m_context->clearColor(0, 0, 0, 0)); + else + GLC(m_context, m_context->clearColor(0, 0, 1, 1)); + +#if defined(NDEBUG) + if (frame.currentRenderPass->hasTransparentBackground()) +#endif + m_context->clear(GraphicsContext3D::COLOR_BUFFER_BIT); +} + +void CCRendererGL::beginDrawingFrame(DrawingFrame& frame) +{ + // FIXME: Remove this once framebuffer is automatically recreated on first use + ensureFramebuffer(); + + if (viewportSize().isEmpty()) + return; + + TRACE_EVENT0("cc", "CCRendererGL::drawLayers"); + if (m_isViewportChanged) { + // Only reshape when we know we are going to draw. Otherwise, the reshape + // can leave the window at the wrong size if we never draw and the proper + // viewport size is never set. + m_isViewportChanged = false; + m_context->reshape(viewportWidth(), viewportHeight()); + } + + makeContextCurrent(); + // Bind the common vertex attributes used for drawing all the layers. + m_sharedGeometry->prepareForDraw(); + + GLC(m_context, m_context->disable(GraphicsContext3D::DEPTH_TEST)); + GLC(m_context, m_context->disable(GraphicsContext3D::CULL_FACE)); + GLC(m_context, m_context->colorMask(true, true, true, true)); + GLC(m_context, m_context->enable(GraphicsContext3D::BLEND)); + GLC(m_context, m_context->blendFunc(GraphicsContext3D::ONE, GraphicsContext3D::ONE_MINUS_SRC_ALPHA)); +} + +void CCRendererGL::doNoOp() +{ + GLC(m_context, m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, 0)); + GLC(m_context, m_context->flush()); +} + +void CCRendererGL::drawQuad(DrawingFrame& frame, const CCDrawQuad* quad) +{ + if (quad->needsBlending()) + GLC(m_context, m_context->enable(GraphicsContext3D::BLEND)); + else + GLC(m_context, m_context->disable(GraphicsContext3D::BLEND)); + + switch (quad->material()) { + case CCDrawQuad::Invalid: + ASSERT_NOT_REACHED(); + break; + case CCDrawQuad::Checkerboard: + drawCheckerboardQuad(frame, CCCheckerboardDrawQuad::materialCast(quad)); + break; + case CCDrawQuad::DebugBorder: + drawDebugBorderQuad(frame, CCDebugBorderDrawQuad::materialCast(quad)); + break; + case CCDrawQuad::IOSurfaceContent: + drawIOSurfaceQuad(frame, CCIOSurfaceDrawQuad::materialCast(quad)); + break; + case CCDrawQuad::RenderPass: + drawRenderPassQuad(frame, CCRenderPassDrawQuad::materialCast(quad)); + break; + case CCDrawQuad::SolidColor: + drawSolidColorQuad(frame, CCSolidColorDrawQuad::materialCast(quad)); + break; + case CCDrawQuad::StreamVideoContent: + drawStreamVideoQuad(frame, CCStreamVideoDrawQuad::materialCast(quad)); + break; + case CCDrawQuad::TextureContent: + drawTextureQuad(frame, CCTextureDrawQuad::materialCast(quad)); + break; + case CCDrawQuad::TiledContent: + drawTileQuad(frame, CCTileDrawQuad::materialCast(quad)); + break; + case CCDrawQuad::YUVVideoContent: + drawYUVVideoQuad(frame, CCYUVVideoDrawQuad::materialCast(quad)); + break; + } +} + +void CCRendererGL::drawCheckerboardQuad(const DrawingFrame& frame, const CCCheckerboardDrawQuad* quad) +{ + const TileCheckerboardProgram* program = tileCheckerboardProgram(); + ASSERT(program && program->initialized()); + GLC(context(), context()->useProgram(program->program())); + + SkColor color = quad->color(); + GLC(context(), context()->uniform4f(program->fragmentShader().colorLocation(), SkColorGetR(color) / 255.0, SkColorGetG(color) / 255.0, SkColorGetB(color) / 255.0, 1)); + + const int checkerboardWidth = 16; + float frequency = 1.0 / checkerboardWidth; + + IntRect tileRect = quad->quadRect(); + float texOffsetX = tileRect.x() % checkerboardWidth; + float texOffsetY = tileRect.y() % checkerboardWidth; + float texScaleX = tileRect.width(); + float texScaleY = tileRect.height(); + GLC(context(), context()->uniform4f(program->fragmentShader().texTransformLocation(), texOffsetX, texOffsetY, texScaleX, texScaleY)); + + GLC(context(), context()->uniform1f(program->fragmentShader().frequencyLocation(), frequency)); + + setShaderOpacity(quad->opacity(), program->fragmentShader().alphaLocation()); + drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), program->vertexShader().matrixLocation()); +} + +void CCRendererGL::drawDebugBorderQuad(const DrawingFrame& frame, const CCDebugBorderDrawQuad* quad) +{ + static float glMatrix[16]; + const SolidColorProgram* program = solidColorProgram(); + ASSERT(program && program->initialized()); + GLC(context(), context()->useProgram(program->program())); + + // Use the full quadRect for debug quads to not move the edges based on partial swaps. + const IntRect& layerRect = quad->quadRect(); + WebTransformationMatrix renderMatrix = quad->quadTransform(); + renderMatrix.translate(0.5 * layerRect.width() + layerRect.x(), 0.5 * layerRect.height() + layerRect.y()); + renderMatrix.scaleNonUniform(layerRect.width(), layerRect.height()); + CCRendererGL::toGLMatrix(&glMatrix[0], frame.projectionMatrix * renderMatrix); + GLC(context(), context()->uniformMatrix4fv(program->vertexShader().matrixLocation(), 1, false, &glMatrix[0])); + + SkColor color = quad->color(); + float alpha = SkColorGetA(color) / 255.0; + + GLC(context(), context()->uniform4f(program->fragmentShader().colorLocation(), (SkColorGetR(color) / 255.0) * alpha, (SkColorGetG(color) / 255.0) * alpha, (SkColorGetB(color) / 255.0) * alpha, alpha)); + + GLC(context(), context()->lineWidth(quad->width())); + + // The indices for the line are stored in the same array as the triangle indices. + GLC(context(), context()->drawElements(GraphicsContext3D::LINE_LOOP, 4, GraphicsContext3D::UNSIGNED_SHORT, 6 * sizeof(unsigned short))); +} + +static inline SkBitmap applyFilters(CCRendererGL* renderer, const WebKit::WebFilterOperations& filters, CCScopedTexture* sourceTexture) +{ + if (filters.isEmpty()) + return SkBitmap(); + + WebGraphicsContext3D* filterContext = CCProxy::hasImplThread() ? WebSharedGraphicsContext3D::compositorThreadContext() : WebSharedGraphicsContext3D::mainThreadContext(); + GrContext* filterGrContext = CCProxy::hasImplThread() ? WebSharedGraphicsContext3D::compositorThreadGrContext() : WebSharedGraphicsContext3D::mainThreadGrContext(); + + if (!filterContext || !filterGrContext) + return SkBitmap(); + + renderer->context()->flush(); + + CCResourceProvider::ScopedWriteLockGL lock(renderer->resourceProvider(), sourceTexture->id()); + SkBitmap source = CCRenderSurfaceFilters::apply(filters, lock.textureId(), sourceTexture->size(), filterContext, filterGrContext); + return source; +} + +PassOwnPtr<CCScopedTexture> CCRendererGL::drawBackgroundFilters(DrawingFrame& frame, const CCRenderPassDrawQuad* quad, const WebKit::WebFilterOperations& filters, const WebTransformationMatrix& contentsDeviceTransform) +{ + // This method draws a background filter, which applies a filter to any pixels behind the quad and seen through its background. + // The algorithm works as follows: + // 1. Compute a bounding box around the pixels that will be visible through the quad. + // 2. Read the pixels in the bounding box into a buffer R. + // 3. Apply the background filter to R, so that it is applied in the pixels' coordinate space. + // 4. Apply the quad's inverse transform to map the pixels in R into the quad's content space. This implicitly + // clips R by the content bounds of the quad since the destination texture has bounds matching the quad's content. + // 5. Draw the background texture for the contents using the same transform as used to draw the contents itself. This is done + // without blending to replace the current background pixels with the new filtered background. + // 6. Draw the contents of the quad over drop of the new background with blending, as per usual. The filtered background + // pixels will show through any non-opaque pixels in this draws. + // + // Pixel copies in this algorithm occur at steps 2, 3, 4, and 5. + + // FIXME: When this algorithm changes, update CCLayerTreeHost::prioritizeTextures() accordingly. + + if (filters.isEmpty()) + return nullptr; + + // FIXME: We only allow background filters on an opaque render surface because other surfaces may contain + // translucent pixels, and the contents behind those translucent pixels wouldn't have the filter applied. + if (frame.currentRenderPass->hasTransparentBackground()) + return nullptr; + ASSERT(!frame.currentTexture); + + // FIXME: Do a single readback for both the surface and replica and cache the filtered results (once filter textures are not reused). + IntRect deviceRect = enclosingIntRect(CCMathUtil::mapClippedRect(contentsDeviceTransform, sharedGeometryQuad().boundingBox())); + + int top, right, bottom, left; + filters.getOutsets(top, right, bottom, left); + deviceRect.move(-left, -top); + deviceRect.expand(left + right, top + bottom); + + deviceRect.intersect(frame.currentRenderPass->outputRect()); + + OwnPtr<CCScopedTexture> deviceBackgroundTexture = CCScopedTexture::create(m_resourceProvider); + if (!getFramebufferTexture(deviceBackgroundTexture.get(), deviceRect)) + return nullptr; + + SkBitmap filteredDeviceBackground = applyFilters(this, filters, deviceBackgroundTexture.get()); + if (!filteredDeviceBackground.getTexture()) + return nullptr; + + GrTexture* texture = reinterpret_cast<GrTexture*>(filteredDeviceBackground.getTexture()); + int filteredDeviceBackgroundTextureId = texture->getTextureHandle(); + + OwnPtr<CCScopedTexture> backgroundTexture = CCScopedTexture::create(m_resourceProvider); + if (!backgroundTexture->allocate(CCRenderer::ImplPool, quad->quadRect().size(), GraphicsContext3D::RGBA, CCResourceProvider::TextureUsageFramebuffer)) + return nullptr; + + const CCRenderPass* targetRenderPass = frame.currentRenderPass; + bool usingBackgroundTexture = useScopedTexture(frame, backgroundTexture.get(), quad->quadRect()); + + if (usingBackgroundTexture) { + // Copy the readback pixels from device to the background texture for the surface. + WebTransformationMatrix deviceToFramebufferTransform; + deviceToFramebufferTransform.translate(quad->quadRect().width() / 2.0, quad->quadRect().height() / 2.0); + deviceToFramebufferTransform.scale3d(quad->quadRect().width(), quad->quadRect().height(), 1); + deviceToFramebufferTransform.multiply(contentsDeviceTransform.inverse()); + copyTextureToFramebuffer(frame, filteredDeviceBackgroundTextureId, deviceRect, deviceToFramebufferTransform); + } + + useRenderPass(frame, targetRenderPass); + + if (!usingBackgroundTexture) + return nullptr; + return backgroundTexture.release(); +} + +void CCRendererGL::drawRenderPassQuad(DrawingFrame& frame, const CCRenderPassDrawQuad* quad) +{ + CachedTexture* contentsTexture = m_renderPassTextures.get(quad->renderPassId()); + if (!contentsTexture || !contentsTexture->id()) + return; + + const CCRenderPass* renderPass = frame.renderPassesById->get(quad->renderPassId()); + ASSERT(renderPass); + if (!renderPass) + return; + + WebTransformationMatrix renderMatrix = quad->quadTransform(); + renderMatrix.translate(0.5 * quad->quadRect().width() + quad->quadRect().x(), 0.5 * quad->quadRect().height() + quad->quadRect().y()); + WebTransformationMatrix deviceMatrix = renderMatrix; + deviceMatrix.scaleNonUniform(quad->quadRect().width(), quad->quadRect().height()); + WebTransformationMatrix contentsDeviceTransform = WebTransformationMatrix(frame.windowMatrix * frame.projectionMatrix * deviceMatrix).to2dTransform(); + + // Can only draw surface if device matrix is invertible. + if (!contentsDeviceTransform.isInvertible()) + return; + + OwnPtr<CCScopedTexture> backgroundTexture = drawBackgroundFilters(frame, quad, renderPass->backgroundFilters(), contentsDeviceTransform); + + // FIXME: Cache this value so that we don't have to do it for both the surface and its replica. + // Apply filters to the contents texture. + SkBitmap filterBitmap = applyFilters(this, renderPass->filters(), contentsTexture); + OwnPtr<CCResourceProvider::ScopedReadLockGL> contentsResourceLock; + unsigned contentsTextureId = 0; + if (filterBitmap.getTexture()) { + GrTexture* texture = reinterpret_cast<GrTexture*>(filterBitmap.getTexture()); + contentsTextureId = texture->getTextureHandle(); + } else { + contentsResourceLock = adoptPtr(new CCResourceProvider::ScopedReadLockGL(m_resourceProvider, contentsTexture->id())); + contentsTextureId = contentsResourceLock->textureId(); + } + + // Draw the background texture if there is one. + if (backgroundTexture) { + ASSERT(backgroundTexture->size() == quad->quadRect().size()); + CCResourceProvider::ScopedReadLockGL lock(m_resourceProvider, backgroundTexture->id()); + copyTextureToFramebuffer(frame, lock.textureId(), quad->quadRect(), quad->quadTransform()); + } + + bool clipped = false; + FloatQuad deviceQuad = CCMathUtil::mapQuad(contentsDeviceTransform, sharedGeometryQuad(), clipped); + ASSERT(!clipped); + CCLayerQuad deviceLayerBounds = CCLayerQuad(FloatQuad(deviceQuad.boundingBox())); + CCLayerQuad deviceLayerEdges = CCLayerQuad(deviceQuad); + + // Use anti-aliasing programs only when necessary. + bool useAA = (!deviceQuad.isRectilinear() || !deviceQuad.boundingBox().isExpressibleAsIntRect()); + if (useAA) { + deviceLayerBounds.inflateAntiAliasingDistance(); + deviceLayerEdges.inflateAntiAliasingDistance(); + } + + OwnPtr<CCResourceProvider::ScopedReadLockGL> maskResourceLock; + unsigned maskTextureId = 0; + if (quad->maskResourceId()) { + maskResourceLock = adoptPtr(new CCResourceProvider::ScopedReadLockGL(m_resourceProvider, quad->maskResourceId())); + maskTextureId = maskResourceLock->textureId(); + } + + // FIXME: use the backgroundTexture and blend the background in with this draw instead of having a separate copy of the background texture. + + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE0)); + context()->bindTexture(GraphicsContext3D::TEXTURE_2D, contentsTextureId); + + int shaderQuadLocation = -1; + int shaderEdgeLocation = -1; + int shaderMaskSamplerLocation = -1; + int shaderMaskTexCoordScaleLocation = -1; + int shaderMaskTexCoordOffsetLocation = -1; + int shaderMatrixLocation = -1; + int shaderAlphaLocation = -1; + if (useAA && maskTextureId) { + const RenderPassMaskProgramAA* program = renderPassMaskProgramAA(); + GLC(context(), context()->useProgram(program->program())); + GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); + + shaderQuadLocation = program->vertexShader().pointLocation(); + shaderEdgeLocation = program->fragmentShader().edgeLocation(); + shaderMaskSamplerLocation = program->fragmentShader().maskSamplerLocation(); + shaderMaskTexCoordScaleLocation = program->fragmentShader().maskTexCoordScaleLocation(); + shaderMaskTexCoordOffsetLocation = program->fragmentShader().maskTexCoordOffsetLocation(); + shaderMatrixLocation = program->vertexShader().matrixLocation(); + shaderAlphaLocation = program->fragmentShader().alphaLocation(); + } else if (!useAA && maskTextureId) { + const RenderPassMaskProgram* program = renderPassMaskProgram(); + GLC(context(), context()->useProgram(program->program())); + GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); + + shaderMaskSamplerLocation = program->fragmentShader().maskSamplerLocation(); + shaderMaskTexCoordScaleLocation = program->fragmentShader().maskTexCoordScaleLocation(); + shaderMaskTexCoordOffsetLocation = program->fragmentShader().maskTexCoordOffsetLocation(); + shaderMatrixLocation = program->vertexShader().matrixLocation(); + shaderAlphaLocation = program->fragmentShader().alphaLocation(); + } else if (useAA && !maskTextureId) { + const RenderPassProgramAA* program = renderPassProgramAA(); + GLC(context(), context()->useProgram(program->program())); + GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); + + shaderQuadLocation = program->vertexShader().pointLocation(); + shaderEdgeLocation = program->fragmentShader().edgeLocation(); + shaderMatrixLocation = program->vertexShader().matrixLocation(); + shaderAlphaLocation = program->fragmentShader().alphaLocation(); + } else { + const RenderPassProgram* program = renderPassProgram(); + GLC(context(), context()->useProgram(program->program())); + GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); + + shaderMatrixLocation = program->vertexShader().matrixLocation(); + shaderAlphaLocation = program->fragmentShader().alphaLocation(); + } + + if (shaderMaskSamplerLocation != -1) { + ASSERT(shaderMaskTexCoordScaleLocation != 1); + ASSERT(shaderMaskTexCoordOffsetLocation != 1); + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE1)); + GLC(context(), context()->uniform1i(shaderMaskSamplerLocation, 1)); + GLC(context(), context()->uniform2f(shaderMaskTexCoordScaleLocation, quad->maskTexCoordScaleX(), quad->maskTexCoordScaleY())); + GLC(context(), context()->uniform2f(shaderMaskTexCoordOffsetLocation, quad->maskTexCoordOffsetX(), quad->maskTexCoordOffsetY())); + context()->bindTexture(GraphicsContext3D::TEXTURE_2D, maskTextureId); + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE0)); + } + + if (shaderEdgeLocation != -1) { + float edge[24]; + deviceLayerEdges.toFloatArray(edge); + deviceLayerBounds.toFloatArray(&edge[12]); + GLC(context(), context()->uniform3fv(shaderEdgeLocation, 8, edge)); + } + + // Map device space quad to surface space. contentsDeviceTransform has no 3d component since it was generated with to2dTransform() so we don't need to project. + FloatQuad surfaceQuad = CCMathUtil::mapQuad(contentsDeviceTransform.inverse(), deviceLayerEdges.floatQuad(), clipped); + ASSERT(!clipped); + + setShaderOpacity(quad->opacity(), shaderAlphaLocation); + setShaderFloatQuad(surfaceQuad, shaderQuadLocation); + drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), shaderMatrixLocation); +} + +void CCRendererGL::drawSolidColorQuad(const DrawingFrame& frame, const CCSolidColorDrawQuad* quad) +{ + const SolidColorProgram* program = solidColorProgram(); + GLC(context(), context()->useProgram(program->program())); + + SkColor color = quad->color(); + float opacity = quad->opacity(); + float alpha = (SkColorGetA(color) / 255.0) * opacity; + + GLC(context(), context()->uniform4f(program->fragmentShader().colorLocation(), (SkColorGetR(color) / 255.0) * alpha, (SkColorGetG(color) / 255.0) * alpha, (SkColorGetB(color) / 255.0) * alpha, alpha)); + + drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), program->vertexShader().matrixLocation()); +} + +struct TileProgramUniforms { + unsigned program; + unsigned samplerLocation; + unsigned vertexTexTransformLocation; + unsigned fragmentTexTransformLocation; + unsigned edgeLocation; + unsigned matrixLocation; + unsigned alphaLocation; + unsigned pointLocation; +}; + +template<class T> +static void tileUniformLocation(T program, TileProgramUniforms& uniforms) +{ + uniforms.program = program->program(); + uniforms.vertexTexTransformLocation = program->vertexShader().vertexTexTransformLocation(); + uniforms.matrixLocation = program->vertexShader().matrixLocation(); + uniforms.pointLocation = program->vertexShader().pointLocation(); + + uniforms.samplerLocation = program->fragmentShader().samplerLocation(); + uniforms.alphaLocation = program->fragmentShader().alphaLocation(); + uniforms.fragmentTexTransformLocation = program->fragmentShader().fragmentTexTransformLocation(); + uniforms.edgeLocation = program->fragmentShader().edgeLocation(); +} + +void CCRendererGL::drawTileQuad(const DrawingFrame& frame, const CCTileDrawQuad* quad) +{ + IntRect tileRect = quad->quadVisibleRect(); + + FloatRect clampRect(tileRect); + // Clamp texture coordinates to avoid sampling outside the layer + // by deflating the tile region half a texel or half a texel + // minus epsilon for one pixel layers. The resulting clamp region + // is mapped to the unit square by the vertex shader and mapped + // back to normalized texture coordinates by the fragment shader + // after being clamped to 0-1 range. + const float epsilon = 1 / 1024.0f; + float clampX = min(0.5, clampRect.width() / 2.0 - epsilon); + float clampY = min(0.5, clampRect.height() / 2.0 - epsilon); + clampRect.inflateX(-clampX); + clampRect.inflateY(-clampY); + FloatSize clampOffset = clampRect.minXMinYCorner() - FloatRect(tileRect).minXMinYCorner(); + + FloatPoint textureOffset = quad->textureOffset() + clampOffset + + IntPoint(tileRect.location() - quad->quadRect().location()); + + // Map clamping rectangle to unit square. + float vertexTexTranslateX = -clampRect.x() / clampRect.width(); + float vertexTexTranslateY = -clampRect.y() / clampRect.height(); + float vertexTexScaleX = tileRect.width() / clampRect.width(); + float vertexTexScaleY = tileRect.height() / clampRect.height(); + + // Map to normalized texture coordinates. + const IntSize& textureSize = quad->textureSize(); + float fragmentTexTranslateX = textureOffset.x() / textureSize.width(); + float fragmentTexTranslateY = textureOffset.y() / textureSize.height(); + float fragmentTexScaleX = clampRect.width() / textureSize.width(); + float fragmentTexScaleY = clampRect.height() / textureSize.height(); + + + FloatQuad localQuad; + WebTransformationMatrix deviceTransform = WebTransformationMatrix(frame.windowMatrix * frame.projectionMatrix * quad->quadTransform()).to2dTransform(); + if (!deviceTransform.isInvertible()) + return; + + bool clipped = false; + FloatQuad deviceLayerQuad = CCMathUtil::mapQuad(deviceTransform, FloatQuad(quad->visibleContentRect()), clipped); + ASSERT(!clipped); + + TileProgramUniforms uniforms; + // For now, we simply skip anti-aliasing with the quad is clipped. This only happens + // on perspective transformed layers that go partially behind the camera. + if (quad->isAntialiased() && !clipped) { + if (quad->swizzleContents()) + tileUniformLocation(tileProgramSwizzleAA(), uniforms); + else + tileUniformLocation(tileProgramAA(), uniforms); + } else { + if (quad->needsBlending()) { + if (quad->swizzleContents()) + tileUniformLocation(tileProgramSwizzle(), uniforms); + else + tileUniformLocation(tileProgram(), uniforms); + } else { + if (quad->swizzleContents()) + tileUniformLocation(tileProgramSwizzleOpaque(), uniforms); + else + tileUniformLocation(tileProgramOpaque(), uniforms); + } + } + + GLC(context(), context()->useProgram(uniforms.program)); + GLC(context(), context()->uniform1i(uniforms.samplerLocation, 0)); + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE0)); + CCResourceProvider::ScopedReadLockGL quadResourceLock(m_resourceProvider, quad->resourceId()); + GLC(context(), context()->bindTexture(GraphicsContext3D::TEXTURE_2D, quadResourceLock.textureId())); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, quad->textureFilter())); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, quad->textureFilter())); + + bool useAA = !clipped && quad->isAntialiased(); + if (useAA) { + CCLayerQuad deviceLayerBounds = CCLayerQuad(FloatQuad(deviceLayerQuad.boundingBox())); + deviceLayerBounds.inflateAntiAliasingDistance(); + + CCLayerQuad deviceLayerEdges = CCLayerQuad(deviceLayerQuad); + deviceLayerEdges.inflateAntiAliasingDistance(); + + float edge[24]; + deviceLayerEdges.toFloatArray(edge); + deviceLayerBounds.toFloatArray(&edge[12]); + GLC(context(), context()->uniform3fv(uniforms.edgeLocation, 8, edge)); + + GLC(context(), context()->uniform4f(uniforms.vertexTexTransformLocation, vertexTexTranslateX, vertexTexTranslateY, vertexTexScaleX, vertexTexScaleY)); + GLC(context(), context()->uniform4f(uniforms.fragmentTexTransformLocation, fragmentTexTranslateX, fragmentTexTranslateY, fragmentTexScaleX, fragmentTexScaleY)); + + FloatPoint bottomRight(tileRect.maxX(), tileRect.maxY()); + FloatPoint bottomLeft(tileRect.x(), tileRect.maxY()); + FloatPoint topLeft(tileRect.x(), tileRect.y()); + FloatPoint topRight(tileRect.maxX(), tileRect.y()); + + // Map points to device space. + bottomRight = CCMathUtil::mapPoint(deviceTransform, bottomRight, clipped); + ASSERT(!clipped); + bottomLeft = CCMathUtil::mapPoint(deviceTransform, bottomLeft, clipped); + ASSERT(!clipped); + topLeft = CCMathUtil::mapPoint(deviceTransform, topLeft, clipped); + ASSERT(!clipped); + topRight = CCMathUtil::mapPoint(deviceTransform, topRight, clipped); + ASSERT(!clipped); + + CCLayerQuad::Edge bottomEdge(bottomRight, bottomLeft); + CCLayerQuad::Edge leftEdge(bottomLeft, topLeft); + CCLayerQuad::Edge topEdge(topLeft, topRight); + CCLayerQuad::Edge rightEdge(topRight, bottomRight); + + // Only apply anti-aliasing to edges not clipped by culling or scissoring. + if (quad->topEdgeAA() && tileRect.y() == quad->quadRect().y()) + topEdge = deviceLayerEdges.top(); + if (quad->leftEdgeAA() && tileRect.x() == quad->quadRect().x()) + leftEdge = deviceLayerEdges.left(); + if (quad->rightEdgeAA() && tileRect.maxX() == quad->quadRect().maxX()) + rightEdge = deviceLayerEdges.right(); + if (quad->bottomEdgeAA() && tileRect.maxY() == quad->quadRect().maxY()) + bottomEdge = deviceLayerEdges.bottom(); + + float sign = FloatQuad(tileRect).isCounterclockwise() ? -1 : 1; + bottomEdge.scale(sign); + leftEdge.scale(sign); + topEdge.scale(sign); + rightEdge.scale(sign); + + // Create device space quad. + CCLayerQuad deviceQuad(leftEdge, topEdge, rightEdge, bottomEdge); + + // Map device space quad to local space. contentsDeviceTransform has no 3d component since it was generated with to2dTransform() so we don't need to project. + WebTransformationMatrix inverseDeviceTransform = deviceTransform.inverse(); + localQuad = CCMathUtil::mapQuad(inverseDeviceTransform, deviceQuad.floatQuad(), clipped); + + // We should not ASSERT(!clipped) here, because anti-aliasing inflation may cause deviceQuad to become + // clipped. To our knowledge this scenario does not need to be handled differently than the unclipped case. + } else { + // Move fragment shader transform to vertex shader. We can do this while + // still producing correct results as fragmentTexTransformLocation + // should always be non-negative when tiles are transformed in a way + // that could result in sampling outside the layer. + vertexTexScaleX *= fragmentTexScaleX; + vertexTexScaleY *= fragmentTexScaleY; + vertexTexTranslateX *= fragmentTexScaleX; + vertexTexTranslateY *= fragmentTexScaleY; + vertexTexTranslateX += fragmentTexTranslateX; + vertexTexTranslateY += fragmentTexTranslateY; + + GLC(context(), context()->uniform4f(uniforms.vertexTexTransformLocation, vertexTexTranslateX, vertexTexTranslateY, vertexTexScaleX, vertexTexScaleY)); + + localQuad = FloatRect(tileRect); + } + + // Normalize to tileRect. + localQuad.scale(1.0f / tileRect.width(), 1.0f / tileRect.height()); + + setShaderOpacity(quad->opacity(), uniforms.alphaLocation); + setShaderFloatQuad(localQuad, uniforms.pointLocation); + + // The tile quad shader behaves differently compared to all other shaders. + // The transform and vertex data are used to figure out the extents that the + // un-antialiased quad should have and which vertex this is and the float + // quad passed in via uniform is the actual geometry that gets used to draw + // it. This is why this centered rect is used and not the original quadRect. + FloatRect centeredRect(FloatPoint(-0.5 * tileRect.width(), -0.5 * tileRect.height()), tileRect.size()); + drawQuadGeometry(frame, quad->quadTransform(), centeredRect, uniforms.matrixLocation); +} + +void CCRendererGL::drawYUVVideoQuad(const DrawingFrame& frame, const CCYUVVideoDrawQuad* quad) +{ + const VideoYUVProgram* program = videoYUVProgram(); + ASSERT(program && program->initialized()); + + const CCVideoLayerImpl::FramePlane& yPlane = quad->yPlane(); + const CCVideoLayerImpl::FramePlane& uPlane = quad->uPlane(); + const CCVideoLayerImpl::FramePlane& vPlane = quad->vPlane(); + + CCResourceProvider::ScopedReadLockGL yPlaneLock(m_resourceProvider, yPlane.resourceId); + CCResourceProvider::ScopedReadLockGL uPlaneLock(m_resourceProvider, uPlane.resourceId); + CCResourceProvider::ScopedReadLockGL vPlaneLock(m_resourceProvider, vPlane.resourceId); + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE1)); + GLC(context(), context()->bindTexture(GraphicsContext3D::TEXTURE_2D, yPlaneLock.textureId())); + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE2)); + GLC(context(), context()->bindTexture(GraphicsContext3D::TEXTURE_2D, uPlaneLock.textureId())); + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE3)); + GLC(context(), context()->bindTexture(GraphicsContext3D::TEXTURE_2D, vPlaneLock.textureId())); + + GLC(context(), context()->useProgram(program->program())); + + float yWidthScaleFactor = static_cast<float>(yPlane.visibleSize.width()) / yPlane.size.width(); + // Arbitrarily take the u sizes because u and v dimensions are identical. + float uvWidthScaleFactor = static_cast<float>(uPlane.visibleSize.width()) / uPlane.size.width(); + GLC(context(), context()->uniform1f(program->vertexShader().yWidthScaleFactorLocation(), yWidthScaleFactor)); + GLC(context(), context()->uniform1f(program->vertexShader().uvWidthScaleFactorLocation(), uvWidthScaleFactor)); + + GLC(context(), context()->uniform1i(program->fragmentShader().yTextureLocation(), 1)); + GLC(context(), context()->uniform1i(program->fragmentShader().uTextureLocation(), 2)); + GLC(context(), context()->uniform1i(program->fragmentShader().vTextureLocation(), 3)); + + // These values are magic numbers that are used in the transformation from YUV to RGB color values. + // They are taken from the following webpage: http://www.fourcc.org/fccyvrgb.php + float yuv2RGB[9] = { + 1.164f, 1.164f, 1.164f, + 0.f, -.391f, 2.018f, + 1.596f, -.813f, 0.f, + }; + GLC(context(), context()->uniformMatrix3fv(program->fragmentShader().ccMatrixLocation(), 1, 0, yuv2RGB)); + + // These values map to 16, 128, and 128 respectively, and are computed + // as a fraction over 256 (e.g. 16 / 256 = 0.0625). + // They are used in the YUV to RGBA conversion formula: + // Y - 16 : Gives 16 values of head and footroom for overshooting + // U - 128 : Turns unsigned U into signed U [-128,127] + // V - 128 : Turns unsigned V into signed V [-128,127] + float yuvAdjust[3] = { + -0.0625f, + -0.5f, + -0.5f, + }; + GLC(context(), context()->uniform3fv(program->fragmentShader().yuvAdjLocation(), 1, yuvAdjust)); + + setShaderOpacity(quad->opacity(), program->fragmentShader().alphaLocation()); + drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), program->vertexShader().matrixLocation()); + + // Reset active texture back to texture 0. + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE0)); +} + +void CCRendererGL::drawStreamVideoQuad(const DrawingFrame& frame, const CCStreamVideoDrawQuad* quad) +{ + static float glMatrix[16]; + + ASSERT(m_capabilities.usingEglImage); + + const VideoStreamTextureProgram* program = videoStreamTextureProgram(); + GLC(context(), context()->useProgram(program->program())); + + toGLMatrix(&glMatrix[0], quad->matrix()); + GLC(context(), context()->uniformMatrix4fv(program->vertexShader().texMatrixLocation(), 1, false, glMatrix)); + + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE0)); + GLC(context(), context()->bindTexture(Extensions3DChromium::GL_TEXTURE_EXTERNAL_OES, quad->textureId())); + + GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); + + setShaderOpacity(quad->opacity(), program->fragmentShader().alphaLocation()); + drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), program->vertexShader().matrixLocation()); +} + +struct TextureProgramBinding { + template<class Program> void set(Program* program) + { + ASSERT(program && program->initialized()); + programId = program->program(); + samplerLocation = program->fragmentShader().samplerLocation(); + matrixLocation = program->vertexShader().matrixLocation(); + alphaLocation = program->fragmentShader().alphaLocation(); + } + int programId; + int samplerLocation; + int matrixLocation; + int alphaLocation; +}; + +struct TexTransformTextureProgramBinding : TextureProgramBinding { + template<class Program> void set(Program* program) + { + TextureProgramBinding::set(program); + texTransformLocation = program->vertexShader().texTransformLocation(); + } + int texTransformLocation; +}; + +void CCRendererGL::drawTextureQuad(const DrawingFrame& frame, const CCTextureDrawQuad* quad) +{ + ASSERT(CCProxy::isImplThread()); + + TexTransformTextureProgramBinding binding; + if (quad->flipped()) + binding.set(textureProgramFlip()); + else + binding.set(textureProgram()); + GLC(context(), context()->useProgram(binding.programId)); + GLC(context(), context()->uniform1i(binding.samplerLocation, 0)); + const FloatRect& uvRect = quad->uvRect(); + GLC(context(), context()->uniform4f(binding.texTransformLocation, uvRect.x(), uvRect.y(), uvRect.width(), uvRect.height())); + + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE0)); + CCResourceProvider::ScopedReadLockGL quadResourceLock(m_resourceProvider, quad->resourceId()); + GLC(context(), context()->bindTexture(GraphicsContext3D::TEXTURE_2D, quadResourceLock.textureId())); + + // FIXME: setting the texture parameters every time is redundant. Move this code somewhere + // where it will only happen once per texture. + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::CLAMP_TO_EDGE)); + + if (!quad->premultipliedAlpha()) { + // As it turns out, the premultiplied alpha blending function (ONE, ONE_MINUS_SRC_ALPHA) + // will never cause the alpha channel to be set to anything less than 1.0 if it is + // initialized to that value! Therefore, premultipliedAlpha being false is the first + // situation we can generally see an alpha channel less than 1.0 coming out of the + // compositor. This is causing platform differences in some layout tests (see + // https://bugs.webkit.org/show_bug.cgi?id=82412), so in this situation, use a separate + // blend function for the alpha channel to avoid modifying it. Don't use colorMask for this + // as it has performance implications on some platforms. + GLC(context(), context()->blendFuncSeparate(GraphicsContext3D::SRC_ALPHA, GraphicsContext3D::ONE_MINUS_SRC_ALPHA, GraphicsContext3D::ZERO, GraphicsContext3D::ONE)); + } + + setShaderOpacity(quad->opacity(), binding.alphaLocation); + drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), binding.matrixLocation); + + if (!quad->premultipliedAlpha()) + GLC(m_context, m_context->blendFunc(GraphicsContext3D::ONE, GraphicsContext3D::ONE_MINUS_SRC_ALPHA)); +} + +void CCRendererGL::drawIOSurfaceQuad(const DrawingFrame& frame, const CCIOSurfaceDrawQuad* quad) +{ + ASSERT(CCProxy::isImplThread()); + TexTransformTextureProgramBinding binding; + binding.set(textureIOSurfaceProgram()); + + GLC(context(), context()->useProgram(binding.programId)); + GLC(context(), context()->uniform1i(binding.samplerLocation, 0)); + if (quad->orientation() == CCIOSurfaceDrawQuad::Flipped) + GLC(context(), context()->uniform4f(binding.texTransformLocation, 0, quad->ioSurfaceSize().height(), quad->ioSurfaceSize().width(), quad->ioSurfaceSize().height() * -1.0)); + else + GLC(context(), context()->uniform4f(binding.texTransformLocation, 0, 0, quad->ioSurfaceSize().width(), quad->ioSurfaceSize().height())); + + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE0)); + GLC(context(), context()->bindTexture(Extensions3D::TEXTURE_RECTANGLE_ARB, quad->ioSurfaceTextureId())); + + setShaderOpacity(quad->opacity(), binding.alphaLocation); + drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), binding.matrixLocation); + + GLC(context(), context()->bindTexture(Extensions3D::TEXTURE_RECTANGLE_ARB, 0)); +} + +void CCRendererGL::finishDrawingFrame(DrawingFrame& frame) +{ + m_currentFramebufferLock.clear(); + m_swapBufferRect.unite(enclosingIntRect(frame.rootDamageRect)); + + GLC(m_context, m_context->disable(GraphicsContext3D::SCISSOR_TEST)); + GLC(m_context, m_context->disable(GraphicsContext3D::BLEND)); +} + +bool CCRendererGL::flippedFramebuffer() const +{ + return true; +} + +void CCRendererGL::toGLMatrix(float* flattened, const WebTransformationMatrix& m) +{ + flattened[0] = m.m11(); + flattened[1] = m.m12(); + flattened[2] = m.m13(); + flattened[3] = m.m14(); + flattened[4] = m.m21(); + flattened[5] = m.m22(); + flattened[6] = m.m23(); + flattened[7] = m.m24(); + flattened[8] = m.m31(); + flattened[9] = m.m32(); + flattened[10] = m.m33(); + flattened[11] = m.m34(); + flattened[12] = m.m41(); + flattened[13] = m.m42(); + flattened[14] = m.m43(); + flattened[15] = m.m44(); +} + +void CCRendererGL::setShaderFloatQuad(const FloatQuad& quad, int quadLocation) +{ + if (quadLocation == -1) + return; + + float point[8]; + point[0] = quad.p1().x(); + point[1] = quad.p1().y(); + point[2] = quad.p2().x(); + point[3] = quad.p2().y(); + point[4] = quad.p3().x(); + point[5] = quad.p3().y(); + point[6] = quad.p4().x(); + point[7] = quad.p4().y(); + GLC(m_context, m_context->uniform2fv(quadLocation, 4, point)); +} + +void CCRendererGL::setShaderOpacity(float opacity, int alphaLocation) +{ + if (alphaLocation != -1) + GLC(m_context, m_context->uniform1f(alphaLocation, opacity)); +} + +void CCRendererGL::drawQuadGeometry(const DrawingFrame& frame, const WebKit::WebTransformationMatrix& drawTransform, const FloatRect& quadRect, int matrixLocation) +{ + WebTransformationMatrix quadRectMatrix; + quadRectTransform(&quadRectMatrix, drawTransform, quadRect); + static float glMatrix[16]; + toGLMatrix(&glMatrix[0], frame.projectionMatrix * quadRectMatrix); + GLC(m_context, m_context->uniformMatrix4fv(matrixLocation, 1, false, &glMatrix[0])); + + GLC(m_context, m_context->drawElements(GraphicsContext3D::TRIANGLES, 6, GraphicsContext3D::UNSIGNED_SHORT, 0)); +} + +void CCRendererGL::copyTextureToFramebuffer(const DrawingFrame& frame, int textureId, const IntRect& rect, const WebTransformationMatrix& drawMatrix) +{ + const RenderPassProgram* program = renderPassProgram(); + + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE0)); + GLC(context(), context()->bindTexture(GraphicsContext3D::TEXTURE_2D, textureId)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::CLAMP_TO_EDGE)); + + GLC(context(), context()->useProgram(program->program())); + GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); + setShaderOpacity(1, program->fragmentShader().alphaLocation()); + drawQuadGeometry(frame, drawMatrix, rect, program->vertexShader().matrixLocation()); +} + +void CCRendererGL::finish() +{ + TRACE_EVENT0("cc", "CCRendererGL::finish"); + m_context->finish(); +} + +bool CCRendererGL::swapBuffers() +{ + ASSERT(m_visible); + ASSERT(!m_isFramebufferDiscarded); + + TRACE_EVENT0("cc", "CCRendererGL::swapBuffers"); + // We're done! Time to swapbuffers! + + if (m_capabilities.usingPartialSwap) { + // If supported, we can save significant bandwidth by only swapping the damaged/scissored region (clamped to the viewport) + m_swapBufferRect.intersect(IntRect(IntPoint(), viewportSize())); + int flippedYPosOfRectBottom = viewportHeight() - m_swapBufferRect.y() - m_swapBufferRect.height(); + m_context->postSubBufferCHROMIUM(m_swapBufferRect.x(), flippedYPosOfRectBottom, m_swapBufferRect.width(), m_swapBufferRect.height()); + } else { + // Note that currently this has the same effect as swapBuffers; we should + // consider exposing a different entry point on WebGraphicsContext3D. + m_context->prepareTexture(); + } + + m_swapBufferRect = IntRect(); + + return true; +} + +void CCRendererGL::onSwapBuffersComplete() +{ + m_client->onSwapBuffersComplete(); +} + +void CCRendererGL::onMemoryAllocationChanged(WebGraphicsMemoryAllocation allocation) +{ + // FIXME: This is called on the main thread in single threaded mode, but we expect it on the impl thread. + if (!CCProxy::hasImplThread()) { + ASSERT(CCProxy::isMainThread()); + DebugScopedSetImplThread impl; + onMemoryAllocationChangedOnImplThread(allocation); + } else { + ASSERT(CCProxy::isImplThread()); + onMemoryAllocationChangedOnImplThread(allocation); + } +} + +void CCRendererGL::onMemoryAllocationChangedOnImplThread(WebKit::WebGraphicsMemoryAllocation allocation) +{ + if (m_visible && !allocation.gpuResourceSizeInBytes) + return; + + if (!allocation.suggestHaveBackbuffer && !m_visible) + discardFramebuffer(); + + if (!allocation.gpuResourceSizeInBytes) { + releaseRenderPassTextures(); + m_client->releaseContentsTextures(); + GLC(m_context, m_context->flush()); + } else + m_client->setMemoryAllocationLimitBytes(allocation.gpuResourceSizeInBytes); +} + +void CCRendererGL::discardFramebuffer() +{ + if (m_isFramebufferDiscarded) + return; + + if (!m_capabilities.usingDiscardFramebuffer) + return; + + // FIXME: Update attachments argument to appropriate values once they are no longer ignored. + m_context->discardFramebufferEXT(GraphicsContext3D::TEXTURE_2D, 0, 0); + m_isFramebufferDiscarded = true; + + // Damage tracker needs a full reset every time framebuffer is discarded. + m_client->setFullRootLayerDamage(); +} + +void CCRendererGL::ensureFramebuffer() +{ + if (!m_isFramebufferDiscarded) + return; + + if (!m_capabilities.usingDiscardFramebuffer) + return; + + m_context->ensureFramebufferCHROMIUM(); + m_isFramebufferDiscarded = false; +} + +void CCRendererGL::onContextLost() +{ + m_client->didLoseContext(); +} + + +void CCRendererGL::getFramebufferPixels(void *pixels, const IntRect& rect) +{ + ASSERT(rect.maxX() <= viewportWidth() && rect.maxY() <= viewportHeight()); + + if (!pixels) + return; + + makeContextCurrent(); + + bool doWorkaround = needsIOSurfaceReadbackWorkaround(); + + Platform3DObject temporaryTexture = 0; + Platform3DObject temporaryFBO = 0; + + if (doWorkaround) { + // On Mac OS X, calling glReadPixels against an FBO whose color attachment is an + // IOSurface-backed texture causes corruption of future glReadPixels calls, even those on + // different OpenGL contexts. It is believed that this is the root cause of top crasher + // http://crbug.com/99393. <rdar://problem/10949687> + + temporaryTexture = m_context->createTexture(); + GLC(m_context, m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, temporaryTexture)); + GLC(m_context, m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR)); + GLC(m_context, m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR)); + GLC(m_context, m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE)); + GLC(m_context, m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::CLAMP_TO_EDGE)); + // Copy the contents of the current (IOSurface-backed) framebuffer into a temporary texture. + GLC(m_context, m_context->copyTexImage2D(GraphicsContext3D::TEXTURE_2D, 0, GraphicsContext3D::RGBA, 0, 0, viewportSize().width(), viewportSize().height(), 0)); + temporaryFBO = m_context->createFramebuffer(); + // Attach this texture to an FBO, and perform the readback from that FBO. + GLC(m_context, m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, temporaryFBO)); + GLC(m_context, m_context->framebufferTexture2D(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::COLOR_ATTACHMENT0, GraphicsContext3D::TEXTURE_2D, temporaryTexture, 0)); + + ASSERT(m_context->checkFramebufferStatus(GraphicsContext3D::FRAMEBUFFER) == GraphicsContext3D::FRAMEBUFFER_COMPLETE); + } + + OwnArrayPtr<uint8_t> srcPixels = adoptArrayPtr(new uint8_t[rect.width() * rect.height() * 4]); + GLC(m_context, m_context->readPixels(rect.x(), viewportSize().height() - rect.maxY(), rect.width(), rect.height(), + GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, srcPixels.get())); + + uint8_t* destPixels = static_cast<uint8_t*>(pixels); + size_t rowBytes = rect.width() * 4; + int numRows = rect.height(); + size_t totalBytes = numRows * rowBytes; + for (size_t destY = 0; destY < totalBytes; destY += rowBytes) { + // Flip Y axis. + size_t srcY = totalBytes - destY - rowBytes; + // Swizzle BGRA -> RGBA. + for (size_t x = 0; x < rowBytes; x += 4) { + destPixels[destY + (x+0)] = srcPixels.get()[srcY + (x+2)]; + destPixels[destY + (x+1)] = srcPixels.get()[srcY + (x+1)]; + destPixels[destY + (x+2)] = srcPixels.get()[srcY + (x+0)]; + destPixels[destY + (x+3)] = srcPixels.get()[srcY + (x+3)]; + } + } + + if (doWorkaround) { + // Clean up. + GLC(m_context, m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, 0)); + GLC(m_context, m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, 0)); + GLC(m_context, m_context->deleteFramebuffer(temporaryFBO)); + GLC(m_context, m_context->deleteTexture(temporaryTexture)); + } + + if (!m_visible) { + TRACE_EVENT0("cc", "CCRendererGL::getFramebufferPixels dropping resources after readback"); + discardFramebuffer(); + releaseRenderPassTextures(); + m_client->releaseContentsTextures(); + GLC(m_context, m_context->flush()); + } +} + +bool CCRendererGL::getFramebufferTexture(CCScopedTexture* texture, const IntRect& deviceRect) +{ + ASSERT(!texture->id() || (texture->size() == deviceRect.size() && texture->format() == GraphicsContext3D::RGB)); + + if (!texture->id() && !texture->allocate(CCRenderer::ImplPool, deviceRect.size(), GraphicsContext3D::RGB, CCResourceProvider::TextureUsageAny)) + return false; + + CCResourceProvider::ScopedWriteLockGL lock(m_resourceProvider, texture->id()); + GLC(m_context, m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, lock.textureId())); + GLC(m_context, m_context->copyTexImage2D(GraphicsContext3D::TEXTURE_2D, 0, texture->format(), + deviceRect.x(), deviceRect.y(), deviceRect.width(), deviceRect.height(), 0)); + return true; +} + +bool CCRendererGL::useScopedTexture(DrawingFrame& frame, const CCScopedTexture* texture, const IntRect& viewportRect) +{ + ASSERT(texture->id()); + frame.currentRenderPass = 0; + frame.currentTexture = texture; + + return bindFramebufferToTexture(frame, texture, viewportRect); +} + +void CCRendererGL::bindFramebufferToOutputSurface(DrawingFrame& frame) +{ + m_currentFramebufferLock.clear(); + GLC(m_context, m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, 0)); +} + +bool CCRendererGL::bindFramebufferToTexture(DrawingFrame& frame, const CCScopedTexture* texture, const IntRect& framebufferRect) +{ + ASSERT(texture->id()); + + GLC(m_context, m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_offscreenFramebufferId)); + m_currentFramebufferLock = adoptPtr(new CCResourceProvider::ScopedWriteLockGL(m_resourceProvider, texture->id())); + unsigned textureId = m_currentFramebufferLock->textureId(); + GLC(m_context, m_context->framebufferTexture2D(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::COLOR_ATTACHMENT0, GraphicsContext3D::TEXTURE_2D, textureId, 0)); + +#if !defined ( NDEBUG ) + if (m_context->checkFramebufferStatus(GraphicsContext3D::FRAMEBUFFER) != GraphicsContext3D::FRAMEBUFFER_COMPLETE) { + ASSERT_NOT_REACHED(); + return false; + } +#endif + + initializeMatrices(frame, framebufferRect, false); + setDrawViewportSize(framebufferRect.size()); + + return true; +} + +void CCRendererGL::enableScissorTestRect(const IntRect& scissorRect) +{ + GLC(m_context, m_context->enable(GraphicsContext3D::SCISSOR_TEST)); + GLC(m_context, m_context->scissor(scissorRect.x(), scissorRect.y(), scissorRect.width(), scissorRect.height())); +} + +void CCRendererGL::disableScissorTest() +{ + GLC(m_context, m_context->disable(GraphicsContext3D::SCISSOR_TEST)); +} + +void CCRendererGL::setDrawViewportSize(const IntSize& viewportSize) +{ + GLC(m_context, m_context->viewport(0, 0, viewportSize.width(), viewportSize.height())); +} + +bool CCRendererGL::makeContextCurrent() +{ + return m_context->makeContextCurrent(); +} + +bool CCRendererGL::initializeSharedObjects() +{ + TRACE_EVENT0("cc", "CCRendererGL::initializeSharedObjects"); + makeContextCurrent(); + + // Create an FBO for doing offscreen rendering. + GLC(m_context, m_offscreenFramebufferId = m_context->createFramebuffer()); + + // We will always need these programs to render, so create the programs eagerly so that the shader compilation can + // start while we do other work. Other programs are created lazily on first access. + m_sharedGeometry = adoptPtr(new GeometryBinding(m_context, quadVertexRect())); + m_renderPassProgram = adoptPtr(new RenderPassProgram(m_context)); + m_tileProgram = adoptPtr(new TileProgram(m_context)); + m_tileProgramOpaque = adoptPtr(new TileProgramOpaque(m_context)); + + GLC(m_context, m_context->flush()); + + return true; +} + +const CCRendererGL::TileCheckerboardProgram* CCRendererGL::tileCheckerboardProgram() +{ + if (!m_tileCheckerboardProgram) + m_tileCheckerboardProgram = adoptPtr(new TileCheckerboardProgram(m_context)); + if (!m_tileCheckerboardProgram->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::checkerboardProgram::initalize"); + m_tileCheckerboardProgram->initialize(m_context, m_isUsingBindUniform); + } + return m_tileCheckerboardProgram.get(); +} + +const CCRendererGL::SolidColorProgram* CCRendererGL::solidColorProgram() +{ + if (!m_solidColorProgram) + m_solidColorProgram = adoptPtr(new SolidColorProgram(m_context)); + if (!m_solidColorProgram->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::solidColorProgram::initialize"); + m_solidColorProgram->initialize(m_context, m_isUsingBindUniform); + } + return m_solidColorProgram.get(); +} + +const CCRendererGL::RenderPassProgram* CCRendererGL::renderPassProgram() +{ + ASSERT(m_renderPassProgram); + if (!m_renderPassProgram->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::renderPassProgram::initialize"); + m_renderPassProgram->initialize(m_context, m_isUsingBindUniform); + } + return m_renderPassProgram.get(); +} + +const CCRendererGL::RenderPassProgramAA* CCRendererGL::renderPassProgramAA() +{ + if (!m_renderPassProgramAA) + m_renderPassProgramAA = adoptPtr(new RenderPassProgramAA(m_context)); + if (!m_renderPassProgramAA->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::renderPassProgramAA::initialize"); + m_renderPassProgramAA->initialize(m_context, m_isUsingBindUniform); + } + return m_renderPassProgramAA.get(); +} + +const CCRendererGL::RenderPassMaskProgram* CCRendererGL::renderPassMaskProgram() +{ + if (!m_renderPassMaskProgram) + m_renderPassMaskProgram = adoptPtr(new RenderPassMaskProgram(m_context)); + if (!m_renderPassMaskProgram->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::renderPassMaskProgram::initialize"); + m_renderPassMaskProgram->initialize(m_context, m_isUsingBindUniform); + } + return m_renderPassMaskProgram.get(); +} + +const CCRendererGL::RenderPassMaskProgramAA* CCRendererGL::renderPassMaskProgramAA() +{ + if (!m_renderPassMaskProgramAA) + m_renderPassMaskProgramAA = adoptPtr(new RenderPassMaskProgramAA(m_context)); + if (!m_renderPassMaskProgramAA->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::renderPassMaskProgramAA::initialize"); + m_renderPassMaskProgramAA->initialize(m_context, m_isUsingBindUniform); + } + return m_renderPassMaskProgramAA.get(); +} + +const CCRendererGL::TileProgram* CCRendererGL::tileProgram() +{ + ASSERT(m_tileProgram); + if (!m_tileProgram->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::tileProgram::initialize"); + m_tileProgram->initialize(m_context, m_isUsingBindUniform); + } + return m_tileProgram.get(); +} + +const CCRendererGL::TileProgramOpaque* CCRendererGL::tileProgramOpaque() +{ + ASSERT(m_tileProgramOpaque); + if (!m_tileProgramOpaque->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::tileProgramOpaque::initialize"); + m_tileProgramOpaque->initialize(m_context, m_isUsingBindUniform); + } + return m_tileProgramOpaque.get(); +} + +const CCRendererGL::TileProgramAA* CCRendererGL::tileProgramAA() +{ + if (!m_tileProgramAA) + m_tileProgramAA = adoptPtr(new TileProgramAA(m_context)); + if (!m_tileProgramAA->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::tileProgramAA::initialize"); + m_tileProgramAA->initialize(m_context, m_isUsingBindUniform); + } + return m_tileProgramAA.get(); +} + +const CCRendererGL::TileProgramSwizzle* CCRendererGL::tileProgramSwizzle() +{ + if (!m_tileProgramSwizzle) + m_tileProgramSwizzle = adoptPtr(new TileProgramSwizzle(m_context)); + if (!m_tileProgramSwizzle->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::tileProgramSwizzle::initialize"); + m_tileProgramSwizzle->initialize(m_context, m_isUsingBindUniform); + } + return m_tileProgramSwizzle.get(); +} + +const CCRendererGL::TileProgramSwizzleOpaque* CCRendererGL::tileProgramSwizzleOpaque() +{ + if (!m_tileProgramSwizzleOpaque) + m_tileProgramSwizzleOpaque = adoptPtr(new TileProgramSwizzleOpaque(m_context)); + if (!m_tileProgramSwizzleOpaque->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::tileProgramSwizzleOpaque::initialize"); + m_tileProgramSwizzleOpaque->initialize(m_context, m_isUsingBindUniform); + } + return m_tileProgramSwizzleOpaque.get(); +} + +const CCRendererGL::TileProgramSwizzleAA* CCRendererGL::tileProgramSwizzleAA() +{ + if (!m_tileProgramSwizzleAA) + m_tileProgramSwizzleAA = adoptPtr(new TileProgramSwizzleAA(m_context)); + if (!m_tileProgramSwizzleAA->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::tileProgramSwizzleAA::initialize"); + m_tileProgramSwizzleAA->initialize(m_context, m_isUsingBindUniform); + } + return m_tileProgramSwizzleAA.get(); +} + +const CCRendererGL::TextureProgram* CCRendererGL::textureProgram() +{ + if (!m_textureProgram) + m_textureProgram = adoptPtr(new TextureProgram(m_context)); + if (!m_textureProgram->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::textureProgram::initialize"); + m_textureProgram->initialize(m_context, m_isUsingBindUniform); + } + return m_textureProgram.get(); +} + +const CCRendererGL::TextureProgramFlip* CCRendererGL::textureProgramFlip() +{ + if (!m_textureProgramFlip) + m_textureProgramFlip = adoptPtr(new TextureProgramFlip(m_context)); + if (!m_textureProgramFlip->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::textureProgramFlip::initialize"); + m_textureProgramFlip->initialize(m_context, m_isUsingBindUniform); + } + return m_textureProgramFlip.get(); +} + +const CCRendererGL::TextureIOSurfaceProgram* CCRendererGL::textureIOSurfaceProgram() +{ + if (!m_textureIOSurfaceProgram) + m_textureIOSurfaceProgram = adoptPtr(new TextureIOSurfaceProgram(m_context)); + if (!m_textureIOSurfaceProgram->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::textureIOSurfaceProgram::initialize"); + m_textureIOSurfaceProgram->initialize(m_context, m_isUsingBindUniform); + } + return m_textureIOSurfaceProgram.get(); +} + +const CCRendererGL::VideoYUVProgram* CCRendererGL::videoYUVProgram() +{ + if (!m_videoYUVProgram) + m_videoYUVProgram = adoptPtr(new VideoYUVProgram(m_context)); + if (!m_videoYUVProgram->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::videoYUVProgram::initialize"); + m_videoYUVProgram->initialize(m_context, m_isUsingBindUniform); + } + return m_videoYUVProgram.get(); +} + +const CCRendererGL::VideoStreamTextureProgram* CCRendererGL::videoStreamTextureProgram() +{ + if (!m_videoStreamTextureProgram) + m_videoStreamTextureProgram = adoptPtr(new VideoStreamTextureProgram(m_context)); + if (!m_videoStreamTextureProgram->initialized()) { + TRACE_EVENT0("cc", "CCRendererGL::streamTextureProgram::initialize"); + m_videoStreamTextureProgram->initialize(m_context, m_isUsingBindUniform); + } + return m_videoStreamTextureProgram.get(); +} + +void CCRendererGL::cleanupSharedObjects() +{ + makeContextCurrent(); + + m_sharedGeometry.clear(); + + if (m_tileProgram) + m_tileProgram->cleanup(m_context); + if (m_tileProgramOpaque) + m_tileProgramOpaque->cleanup(m_context); + if (m_tileProgramSwizzle) + m_tileProgramSwizzle->cleanup(m_context); + if (m_tileProgramSwizzleOpaque) + m_tileProgramSwizzleOpaque->cleanup(m_context); + if (m_tileProgramAA) + m_tileProgramAA->cleanup(m_context); + if (m_tileProgramSwizzleAA) + m_tileProgramSwizzleAA->cleanup(m_context); + if (m_tileCheckerboardProgram) + m_tileCheckerboardProgram->cleanup(m_context); + + if (m_renderPassMaskProgram) + m_renderPassMaskProgram->cleanup(m_context); + if (m_renderPassProgram) + m_renderPassProgram->cleanup(m_context); + if (m_renderPassMaskProgramAA) + m_renderPassMaskProgramAA->cleanup(m_context); + if (m_renderPassProgramAA) + m_renderPassProgramAA->cleanup(m_context); + + if (m_textureProgram) + m_textureProgram->cleanup(m_context); + if (m_textureProgramFlip) + m_textureProgramFlip->cleanup(m_context); + if (m_textureIOSurfaceProgram) + m_textureIOSurfaceProgram->cleanup(m_context); + + if (m_videoYUVProgram) + m_videoYUVProgram->cleanup(m_context); + if (m_videoStreamTextureProgram) + m_videoStreamTextureProgram->cleanup(m_context); + + if (m_solidColorProgram) + m_solidColorProgram->cleanup(m_context); + + if (m_offscreenFramebufferId) + GLC(m_context, m_context->deleteFramebuffer(m_offscreenFramebufferId)); + + releaseRenderPassTextures(); +} + +bool CCRendererGL::isContextLost() +{ + return (m_context->getGraphicsResetStatusARB() != GraphicsContext3D::NO_ERROR); +} + +} // namespace cc + +#endif // USE(ACCELERATED_COMPOSITING) |