// 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 "cc/gl_renderer.h" #include "base/debug/trace_event.h" #include "base/logging.h" #include "base/string_split.h" #include "base/string_util.h" #include "build/build_config.h" #include "cc/damage_tracker.h" #include "cc/geometry_binding.h" #include "cc/layer_quad.h" #include "cc/math_util.h" #include "cc/platform_color.h" #include "cc/priority_calculator.h" #include "cc/proxy.h" #include "cc/render_pass.h" #include "cc/render_surface_filters.h" #include "cc/scoped_resource.h" #include "cc/single_thread_proxy.h" #include "cc/stream_video_draw_quad.h" #include "cc/texture_draw_quad.h" #include "cc/video_layer_impl.h" #include "third_party/khronos/GLES2/gl2.h" #include "third_party/khronos/GLES2/gl2ext.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/gpu/GrContext.h" #include "third_party/skia/include/gpu/GrTexture.h" #include "third_party/skia/include/gpu/SkGpuDevice.h" #include "third_party/skia/include/gpu/SkGrTexturePixelRef.h" #include "ui/gfx/quad_f.h" #include "ui/gfx/rect_conversions.h" #include #include #include #include #include using namespace std; using WebKit::WebGraphicsContext3D; using WebKit::WebGraphicsMemoryAllocation; using WebKit::WebSharedGraphicsContext3D; using WebKit::WebTransformationMatrix; namespace cc { namespace { bool needsIOSurfaceReadbackWorkaround() { #if defined(OS_MACOSX) return true; #else return false; #endif } } // anonymous namespace scoped_ptr GLRenderer::create(RendererClient* client, ResourceProvider* resourceProvider) { scoped_ptr renderer(make_scoped_ptr(new GLRenderer(client, resourceProvider))); if (!renderer->initialize()) return scoped_ptr(); return renderer.Pass(); } GLRenderer::GLRenderer(RendererClient* client, ResourceProvider* resourceProvider) : DirectRenderer(client, resourceProvider) , m_offscreenFramebufferId(0) , m_sharedGeometryQuad(gfx::RectF(-0.5f, -0.5f, 1.0f, 1.0f)) , m_context(resourceProvider->graphicsContext3D()) , m_isViewportChanged(false) , m_isFramebufferDiscarded(false) , m_discardFramebufferWhenNotVisible(false) , m_isUsingBindUniform(false) , m_visible(true) , m_isScissorEnabled(false) { DCHECK(m_context); } bool GLRenderer::initialize() { if (!m_context->makeContextCurrent()) return false; m_context->setContextLostCallback(this); m_context->pushGroupMarkerEXT("CompositorContext"); std::string extensionsString = UTF16ToASCII(m_context->getString(GL_EXTENSIONS)); std::vector extensionsList; base::SplitString(extensionsString, ' ', &extensionsList); std::set 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 = settings().partialSwapEnabled && extensions.count("GL_CHROMIUM_post_sub_buffer"); // Use the swapBuffers callback only with the threaded proxy. if (m_client->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")) DCHECK(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(GL_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"); // Make sure scissoring starts as disabled. GLC(m_context, m_context->disable(GL_SCISSOR_TEST)); DCHECK(!m_isScissorEnabled); if (!initializeSharedObjects()) return false; // Make sure the viewport and context gets initialized, even if it is to zero. viewportChanged(); return true; } GLRenderer::~GLRenderer() { m_context->setSwapBuffersCompleteCallbackCHROMIUM(0); m_context->setMemoryAllocationChangedCallbackCHROMIUM(0); m_context->setContextLostCallback(0); cleanupSharedObjects(); } const RendererCapabilities& GLRenderer::capabilities() const { return m_capabilities; } WebGraphicsContext3D* GLRenderer::context() { return m_context; } void GLRenderer::debugGLCall(WebGraphicsContext3D* context, const char* command, const char* file, int line) { unsigned long error = context->getError(); if (error != GL_NO_ERROR) LOG(ERROR) << "GL command failed: File: " << file << "\n\tLine " << line << "\n\tcommand: " << command << ", error " << static_cast(error) << "\n"; } void GLRenderer::setVisible(bool visible) { if (m_visible == visible) return; m_visible = visible; enforceMemoryPolicy(); // TODO: Replace setVisibilityCHROMIUM with an extension to explicitly manage front/backbuffers // crbug.com/116049 if (m_capabilities.usingSetVisibility) m_context->setVisibilityCHROMIUM(visible); } void GLRenderer::sendManagedMemoryStats(size_t bytesVisible, size_t bytesVisibleAndNearby, size_t bytesAllocated) { WebKit::WebGraphicsManagedMemoryStats stats; stats.bytesVisible = bytesVisible; stats.bytesVisibleAndNearby = bytesVisibleAndNearby; stats.bytesAllocated = bytesAllocated; stats.backbufferRequested = !m_isFramebufferDiscarded; m_context->sendManagedMemoryStatsCHROMIUM(&stats); } void GLRenderer::releaseRenderPassTextures() { m_renderPassTextures.clear(); } void GLRenderer::viewportChanged() { m_isViewportChanged = true; } void GLRenderer::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->has_transparent_background) GLC(m_context, m_context->clearColor(0, 0, 0, 0)); else GLC(m_context, m_context->clearColor(0, 0, 1, 1)); #ifdef NDEBUG if (frame.currentRenderPass->has_transparent_background) #endif m_context->clear(GL_COLOR_BUFFER_BIT); } void GLRenderer::beginDrawingFrame(DrawingFrame& frame) { // FIXME: Remove this once framebuffer is automatically recreated on first use ensureFramebuffer(); if (viewportSize().IsEmpty()) return; TRACE_EVENT0("cc", "GLRenderer::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(GL_DEPTH_TEST)); GLC(m_context, m_context->disable(GL_CULL_FACE)); GLC(m_context, m_context->colorMask(true, true, true, true)); GLC(m_context, m_context->enable(GL_BLEND)); GLC(m_context, m_context->blendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); GLC(context(), context()->activeTexture(GL_TEXTURE0)); } void GLRenderer::doNoOp() { GLC(m_context, m_context->bindFramebuffer(GL_FRAMEBUFFER, 0)); GLC(m_context, m_context->flush()); } void GLRenderer::drawQuad(DrawingFrame& frame, const DrawQuad* quad) { DCHECK(quad->rect.Contains(quad->visible_rect)); if (quad->ShouldDrawWithBlending()) GLC(m_context, m_context->enable(GL_BLEND)); else GLC(m_context, m_context->disable(GL_BLEND)); switch (quad->material) { case DrawQuad::INVALID: NOTREACHED(); break; case DrawQuad::CHECKERBOARD: drawCheckerboardQuad(frame, CheckerboardDrawQuad::MaterialCast(quad)); break; case DrawQuad::DEBUG_BORDER: drawDebugBorderQuad(frame, DebugBorderDrawQuad::MaterialCast(quad)); break; case DrawQuad::IO_SURFACE_CONTENT: drawIOSurfaceQuad(frame, IOSurfaceDrawQuad::MaterialCast(quad)); break; case DrawQuad::RENDER_PASS: drawRenderPassQuad(frame, RenderPassDrawQuad::MaterialCast(quad)); break; case DrawQuad::SOLID_COLOR: drawSolidColorQuad(frame, SolidColorDrawQuad::MaterialCast(quad)); break; case DrawQuad::STREAM_VIDEO_CONTENT: drawStreamVideoQuad(frame, StreamVideoDrawQuad::MaterialCast(quad)); break; case DrawQuad::TEXTURE_CONTENT: drawTextureQuad(frame, TextureDrawQuad::MaterialCast(quad)); break; case DrawQuad::TILED_CONTENT: drawTileQuad(frame, TileDrawQuad::MaterialCast(quad)); break; case DrawQuad::YUV_VIDEO_CONTENT: drawYUVVideoQuad(frame, YUVVideoDrawQuad::MaterialCast(quad)); break; } } void GLRenderer::drawCheckerboardQuad(const DrawingFrame& frame, const CheckerboardDrawQuad* quad) { const TileCheckerboardProgram* program = tileCheckerboardProgram(); DCHECK(program && (program->initialized() || isContextLost())); 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; gfx::Rect tileRect = quad->rect; 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->rect, program->vertexShader().matrixLocation()); } void GLRenderer::drawDebugBorderQuad(const DrawingFrame& frame, const DebugBorderDrawQuad* quad) { static float glMatrix[16]; const SolidColorProgram* program = solidColorProgram(); DCHECK(program && (program->initialized() || isContextLost())); GLC(context(), context()->useProgram(program->program())); // Use the full quadRect for debug quads to not move the edges based on partial swaps. const gfx::Rect& layerRect = quad->rect; WebTransformationMatrix renderMatrix = quad->quadTransform(); renderMatrix.translate(0.5 * layerRect.width() + layerRect.x(), 0.5 * layerRect.height() + layerRect.y()); renderMatrix.scaleNonUniform(layerRect.width(), layerRect.height()); GLRenderer::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(GL_LINE_LOOP, 4, GL_UNSIGNED_SHORT, 6 * sizeof(unsigned short))); } static WebGraphicsContext3D* getFilterContext(bool hasImplThread) { if (hasImplThread) return WebSharedGraphicsContext3D::compositorThreadContext(); else return WebSharedGraphicsContext3D::mainThreadContext(); } static GrContext* getFilterGrContext(bool hasImplThread) { if (hasImplThread) return WebSharedGraphicsContext3D::compositorThreadGrContext(); else return WebSharedGraphicsContext3D::mainThreadGrContext(); } static inline SkBitmap applyFilters(GLRenderer* renderer, const WebKit::WebFilterOperations& filters, ScopedResource* sourceTexture, bool hasImplThread) { if (filters.isEmpty()) return SkBitmap(); WebGraphicsContext3D* filterContext = getFilterContext(hasImplThread); GrContext* filterGrContext = getFilterGrContext(hasImplThread); if (!filterContext || !filterGrContext) return SkBitmap(); renderer->context()->flush(); ResourceProvider::ScopedWriteLockGL lock(renderer->resourceProvider(), sourceTexture->id()); SkBitmap source = RenderSurfaceFilters::apply(filters, lock.textureId(), sourceTexture->size(), filterContext, filterGrContext); return source; } static SkBitmap applyImageFilter(GLRenderer* renderer, SkImageFilter* filter, ScopedResource* sourceTexture, bool hasImplThread) { if (!filter) return SkBitmap(); WebGraphicsContext3D* context3d = getFilterContext(hasImplThread); GrContext* grContext = getFilterGrContext(hasImplThread); if (!context3d || !grContext) return SkBitmap(); renderer->context()->flush(); ResourceProvider::ScopedWriteLockGL lock(renderer->resourceProvider(), sourceTexture->id()); // Wrap the source texture in a Ganesh platform texture. GrPlatformTextureDesc platformTextureDescription; platformTextureDescription.fWidth = sourceTexture->size().width(); platformTextureDescription.fHeight = sourceTexture->size().height(); platformTextureDescription.fConfig = kSkia8888_GrPixelConfig; platformTextureDescription.fTextureHandle = lock.textureId(); SkAutoTUnref texture(grContext->createPlatformTexture(platformTextureDescription)); // Place the platform texture inside an SkBitmap. SkBitmap source; source.setConfig(SkBitmap::kARGB_8888_Config, sourceTexture->size().width(), sourceTexture->size().height()); source.setPixelRef(new SkGrPixelRef(texture.get()))->unref(); // Create a scratch texture for backing store. GrTextureDesc desc; desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit; desc.fSampleCnt = 0; desc.fWidth = source.width(); desc.fHeight = source.height(); desc.fConfig = kSkia8888_GrPixelConfig; GrAutoScratchTexture scratchTexture(grContext, desc, GrContext::kExact_ScratchTexMatch); SkAutoTUnref backingStore(scratchTexture.detach()); // Create a device and canvas using that backing store. SkGpuDevice device(grContext, backingStore.get()); SkCanvas canvas(&device); // Draw the source bitmap through the filter to the canvas. SkPaint paint; paint.setImageFilter(filter); canvas.clear(0x0); canvas.drawSprite(source, 0, 0, &paint); canvas.flush(); context3d->flush(); return device.accessBitmap(false); } scoped_ptr GLRenderer::drawBackgroundFilters( DrawingFrame& frame, const RenderPassDrawQuad* quad, const WebKit::WebFilterOperations& filters, const WebTransformationMatrix& contentsDeviceTransform, const WebTransformationMatrix& contentsDeviceTransformInverse) { // 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 LayerTreeHost::prioritizeTextures() accordingly. if (filters.isEmpty()) return scoped_ptr(); // 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->has_transparent_background) return scoped_ptr(); DCHECK(!frame.currentTexture); // FIXME: Do a single readback for both the surface and replica and cache the filtered results (once filter textures are not reused). gfx::Rect deviceRect = gfx::ToEnclosingRect(MathUtil::mapClippedRect(contentsDeviceTransform, sharedGeometryQuad().BoundingBox())); int top, right, bottom, left; filters.getOutsets(top, right, bottom, left); deviceRect.Inset(-left, -top, -right, -bottom); deviceRect.Intersect(frame.currentRenderPass->output_rect); scoped_ptr deviceBackgroundTexture = ScopedResource::create(m_resourceProvider); if (!getFramebufferTexture(deviceBackgroundTexture.get(), deviceRect)) return scoped_ptr(); SkBitmap filteredDeviceBackground = applyFilters(this, filters, deviceBackgroundTexture.get(), m_client->hasImplThread()); if (!filteredDeviceBackground.getTexture()) return scoped_ptr(); GrTexture* texture = reinterpret_cast(filteredDeviceBackground.getTexture()); int filteredDeviceBackgroundTextureId = texture->getTextureHandle(); scoped_ptr backgroundTexture = ScopedResource::create(m_resourceProvider); if (!backgroundTexture->Allocate(Renderer::ImplPool, quad->rect.size(), GL_RGBA, ResourceProvider::TextureUsageFramebuffer)) return scoped_ptr(); const RenderPass* targetRenderPass = frame.currentRenderPass; bool usingBackgroundTexture = useScopedTexture(frame, backgroundTexture.get(), quad->rect); if (usingBackgroundTexture) { // Copy the readback pixels from device to the background texture for the surface. WebTransformationMatrix deviceToFramebufferTransform; deviceToFramebufferTransform.translate(quad->rect.width() / 2.0, quad->rect.height() / 2.0); deviceToFramebufferTransform.scale3d(quad->rect.width(), quad->rect.height(), 1); deviceToFramebufferTransform.multiply(contentsDeviceTransformInverse); copyTextureToFramebuffer(frame, filteredDeviceBackgroundTextureId, deviceRect, deviceToFramebufferTransform); } useRenderPass(frame, targetRenderPass); if (!usingBackgroundTexture) return scoped_ptr(); return backgroundTexture.Pass(); } void GLRenderer::drawRenderPassQuad(DrawingFrame& frame, const RenderPassDrawQuad* quad) { CachedResource* contentsTexture = m_renderPassTextures.get(quad->render_pass_id); if (!contentsTexture || !contentsTexture->id()) return; const RenderPass* renderPass = frame.renderPassesById->get(quad->render_pass_id); DCHECK(renderPass); if (!renderPass) return; WebTransformationMatrix quadRectMatrix; quadRectTransform(&quadRectMatrix, quad->quadTransform(), quad->rect); WebTransformationMatrix contentsDeviceTransform = (frame.windowMatrix * frame.projectionMatrix * quadRectMatrix).to2dTransform(); // Can only draw surface if device matrix is invertible. if (!contentsDeviceTransform.isInvertible()) return; WebTransformationMatrix contentsDeviceTransformInverse = contentsDeviceTransform.inverse(); scoped_ptr backgroundTexture = drawBackgroundFilters( frame, quad, renderPass->background_filters, contentsDeviceTransform, contentsDeviceTransformInverse); // 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; if (renderPass->filter) { filterBitmap = applyImageFilter(this, renderPass->filter, contentsTexture, m_client->hasImplThread()); } else { filterBitmap = applyFilters(this, renderPass->filters, contentsTexture, m_client->hasImplThread()); } scoped_ptr contentsResourceLock; unsigned contentsTextureId = 0; if (filterBitmap.getTexture()) { GrTexture* texture = reinterpret_cast(filterBitmap.getTexture()); contentsTextureId = texture->getTextureHandle(); } else { contentsResourceLock = make_scoped_ptr(new ResourceProvider::ScopedReadLockGL(m_resourceProvider, contentsTexture->id())); contentsTextureId = contentsResourceLock->textureId(); } // Draw the background texture if there is one. if (backgroundTexture) { DCHECK(backgroundTexture->size() == quad->rect.size()); ResourceProvider::ScopedReadLockGL lock(m_resourceProvider, backgroundTexture->id()); copyTextureToFramebuffer(frame, lock.textureId(), quad->rect, quad->quadTransform()); } bool clipped = false; gfx::QuadF deviceQuad = MathUtil::mapQuad(contentsDeviceTransform, sharedGeometryQuad(), clipped); DCHECK(!clipped); LayerQuad deviceLayerBounds = LayerQuad(gfx::QuadF(deviceQuad.BoundingBox())); LayerQuad deviceLayerEdges = LayerQuad(deviceQuad); // Use anti-aliasing programs only when necessary. bool useAA = (!deviceQuad.IsRectilinear() || !deviceQuad.BoundingBox().IsExpressibleAsRect()); if (useAA) { deviceLayerBounds.inflateAntiAliasingDistance(); deviceLayerEdges.inflateAntiAliasingDistance(); } scoped_ptr maskResourceLock; unsigned maskTextureId = 0; if (quad->mask_resource_id) { maskResourceLock.reset(new ResourceProvider::ScopedReadLockGL(m_resourceProvider, quad->mask_resource_id)); 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. context()->bindTexture(GL_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) { DCHECK(shaderMaskTexCoordScaleLocation != 1); DCHECK(shaderMaskTexCoordOffsetLocation != 1); GLC(context(), context()->activeTexture(GL_TEXTURE1)); GLC(context(), context()->uniform1i(shaderMaskSamplerLocation, 1)); GLC(context(), context()->uniform2f(shaderMaskTexCoordScaleLocation, quad->mask_tex_coord_scale_x, quad->mask_tex_coord_scale_y)); GLC(context(), context()->uniform2f(shaderMaskTexCoordOffsetLocation, quad->mask_tex_coord_offset_x, quad->mask_tex_coord_offset_y)); context()->bindTexture(GL_TEXTURE_2D, maskTextureId); GLC(context(), context()->activeTexture(GL_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. gfx::QuadF surfaceQuad = MathUtil::mapQuad(contentsDeviceTransformInverse, deviceLayerEdges.ToQuadF(), clipped); DCHECK(!clipped); setShaderOpacity(quad->opacity(), shaderAlphaLocation); setShaderQuadF(surfaceQuad, shaderQuadLocation); drawQuadGeometry(frame, quad->quadTransform(), quad->rect, shaderMatrixLocation); // Flush the compositor context before the filter bitmap goes out of // scope, so the draw gets processed before the filter texture gets deleted. if (filterBitmap.getTexture()) m_context->flush(); } void GLRenderer::drawSolidColorQuad(const DrawingFrame& frame, const SolidColorDrawQuad* 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->rect, program->vertexShader().matrixLocation()); } struct TileProgramUniforms { unsigned program; unsigned samplerLocation; unsigned vertexTexTransformLocation; unsigned fragmentTexTransformLocation; unsigned edgeLocation; unsigned matrixLocation; unsigned alphaLocation; unsigned pointLocation; }; template 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 GLRenderer::drawTileQuad(const DrawingFrame& frame, const TileDrawQuad* quad) { gfx::Rect tileRect = quad->visible_rect; gfx::RectF texCoordRect = quad->tex_coord_rect; float texToGeomScaleX = quad->rect.width() / texCoordRect.width(); float texToGeomScaleY = quad->rect.height() / texCoordRect.height(); // texCoordRect corresponds to quadRect, but quadVisibleRect may be // smaller than quadRect due to occlusion or clipping. Adjust // texCoordRect to match. gfx::Vector2d topLeftDiff = tileRect.origin() - quad->rect.origin(); gfx::Vector2d bottomRightDiff = tileRect.bottom_right() - quad->rect.bottom_right(); texCoordRect.Inset(topLeftDiff.x() / texToGeomScaleX, topLeftDiff.y() / texToGeomScaleY, -bottomRightDiff.x() / texToGeomScaleX, -bottomRightDiff.y() / texToGeomScaleY); gfx::RectF clampGeomRect(tileRect); gfx::RectF clampTexRect(texCoordRect); // 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 texClampX = std::min(0.5f, 0.5f * clampTexRect.width() - epsilon); float texClampY = std::min(0.5f, 0.5f * clampTexRect.height() - epsilon); float geomClampX = std::min(texClampX * texToGeomScaleX, 0.5f * clampGeomRect.width() - epsilon); float geomClampY = std::min(texClampY * texToGeomScaleY, 0.5f * clampGeomRect.height() - epsilon); clampGeomRect.Inset(geomClampX, geomClampY, geomClampX, geomClampY); clampTexRect.Inset(texClampX, texClampY, texClampX, texClampY); // Map clamping rectangle to unit square. float vertexTexTranslateX = -clampGeomRect.x() / clampGeomRect.width(); float vertexTexTranslateY = -clampGeomRect.y() / clampGeomRect.height(); float vertexTexScaleX = tileRect.width() / clampGeomRect.width(); float vertexTexScaleY = tileRect.height() / clampGeomRect.height(); // Map to normalized texture coordinates. const gfx::Size& textureSize = quad->texture_size; float fragmentTexTranslateX = clampTexRect.x() / textureSize.width(); float fragmentTexTranslateY = clampTexRect.y() / textureSize.height(); float fragmentTexScaleX = clampTexRect.width() / textureSize.width(); float fragmentTexScaleY = clampTexRect.height() / textureSize.height(); gfx::QuadF localQuad; WebTransformationMatrix deviceTransform = WebTransformationMatrix(frame.windowMatrix * frame.projectionMatrix * quad->quadTransform()).to2dTransform(); if (!deviceTransform.isInvertible()) return; bool clipped = false; gfx::QuadF deviceLayerQuad = MathUtil::mapQuad(deviceTransform, gfx::QuadF(quad->visibleContentRect()), clipped); DCHECK(!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->swizzle_contents) tileUniformLocation(tileProgramSwizzleAA(), uniforms); else tileUniformLocation(tileProgramAA(), uniforms); } else { if (quad->ShouldDrawWithBlending()) { if (quad->swizzle_contents) tileUniformLocation(tileProgramSwizzle(), uniforms); else tileUniformLocation(tileProgram(), uniforms); } else { if (quad->swizzle_contents) tileUniformLocation(tileProgramSwizzleOpaque(), uniforms); else tileUniformLocation(tileProgramOpaque(), uniforms); } } GLC(context(), context()->useProgram(uniforms.program)); GLC(context(), context()->uniform1i(uniforms.samplerLocation, 0)); ResourceProvider::ScopedReadLockGL quadResourceLock(m_resourceProvider, quad->resource_id); GLC(context(), context()->bindTexture(GL_TEXTURE_2D, quadResourceLock.textureId())); bool useAA = !clipped && quad->IsAntialiased(); if (useAA) { LayerQuad deviceLayerBounds = LayerQuad(gfx::QuadF(deviceLayerQuad.BoundingBox())); deviceLayerBounds.inflateAntiAliasingDistance(); LayerQuad deviceLayerEdges = LayerQuad(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)); gfx::PointF bottomRight = tileRect.bottom_right(); gfx::PointF bottomLeft = tileRect.bottom_left(); gfx::PointF topLeft = tileRect.origin(); gfx::PointF topRight = tileRect.top_right(); // Map points to device space. bottomRight = MathUtil::mapPoint(deviceTransform, bottomRight, clipped); DCHECK(!clipped); bottomLeft = MathUtil::mapPoint(deviceTransform, bottomLeft, clipped); DCHECK(!clipped); topLeft = MathUtil::mapPoint(deviceTransform, topLeft, clipped); DCHECK(!clipped); topRight = MathUtil::mapPoint(deviceTransform, topRight, clipped); DCHECK(!clipped); LayerQuad::Edge bottomEdge(bottomRight, bottomLeft); LayerQuad::Edge leftEdge(bottomLeft, topLeft); LayerQuad::Edge topEdge(topLeft, topRight); LayerQuad::Edge rightEdge(topRight, bottomRight); // Only apply anti-aliasing to edges not clipped by culling or scissoring. if (quad->top_edge_aa && tileRect.y() == quad->rect.y()) topEdge = deviceLayerEdges.top(); if (quad->left_edge_aa && tileRect.x() == quad->rect.x()) leftEdge = deviceLayerEdges.left(); if (quad->right_edge_aa && tileRect.right() == quad->rect.right()) rightEdge = deviceLayerEdges.right(); if (quad->bottom_edge_aa && tileRect.bottom() == quad->rect.bottom()) bottomEdge = deviceLayerEdges.bottom(); float sign = gfx::QuadF(tileRect).IsCounterClockwise() ? -1 : 1; bottomEdge.scale(sign); leftEdge.scale(sign); topEdge.scale(sign); rightEdge.scale(sign); // Create device space quad. LayerQuad deviceQuad(leftEdge, topEdge, rightEdge, bottomEdge); // Map device space quad to local space. deviceTransform has no 3d component since it was generated with to2dTransform() so we don't need to project. WebTransformationMatrix deviceTransformInverse = deviceTransform.inverse(); localQuad = MathUtil::mapQuad(deviceTransformInverse, deviceQuad.ToQuadF(), clipped); // We should not DCHECK(!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 = gfx::RectF(tileRect); } // Normalize to tileRect. localQuad.Scale(1.0f / tileRect.width(), 1.0f / tileRect.height()); setShaderOpacity(quad->opacity(), uniforms.alphaLocation); setShaderQuadF(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. gfx::RectF centeredRect(gfx::PointF(-0.5 * tileRect.width(), -0.5 * tileRect.height()), tileRect.size()); drawQuadGeometry(frame, quad->quadTransform(), centeredRect, uniforms.matrixLocation); } void GLRenderer::drawYUVVideoQuad(const DrawingFrame& frame, const YUVVideoDrawQuad* quad) { const VideoYUVProgram* program = videoYUVProgram(); DCHECK(program && (program->initialized() || isContextLost())); const VideoLayerImpl::FramePlane& yPlane = quad->y_plane; const VideoLayerImpl::FramePlane& uPlane = quad->u_plane; const VideoLayerImpl::FramePlane& vPlane = quad->v_plane; ResourceProvider::ScopedReadLockGL yPlaneLock(m_resourceProvider, yPlane.resourceId); ResourceProvider::ScopedReadLockGL uPlaneLock(m_resourceProvider, uPlane.resourceId); ResourceProvider::ScopedReadLockGL vPlaneLock(m_resourceProvider, vPlane.resourceId); GLC(context(), context()->activeTexture(GL_TEXTURE1)); GLC(context(), context()->bindTexture(GL_TEXTURE_2D, yPlaneLock.textureId())); GLC(context(), context()->activeTexture(GL_TEXTURE2)); GLC(context(), context()->bindTexture(GL_TEXTURE_2D, uPlaneLock.textureId())); GLC(context(), context()->activeTexture(GL_TEXTURE3)); GLC(context(), context()->bindTexture(GL_TEXTURE_2D, vPlaneLock.textureId())); GLC(context(), context()->useProgram(program->program())); GLC(context(), context()->uniform2f(program->vertexShader().texScaleLocation(), quad->tex_scale.width(), quad->tex_scale.height())); 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().yuvMatrixLocation(), 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->rect, program->vertexShader().matrixLocation()); // Reset active texture back to texture 0. GLC(context(), context()->activeTexture(GL_TEXTURE0)); } void GLRenderer::drawStreamVideoQuad(const DrawingFrame& frame, const StreamVideoDrawQuad* quad) { static float glMatrix[16]; DCHECK(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()->bindTexture(GL_TEXTURE_EXTERNAL_OES, quad->texture_id)); GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); setShaderOpacity(quad->opacity(), program->fragmentShader().alphaLocation()); drawQuadGeometry(frame, quad->quadTransform(), quad->rect, program->vertexShader().matrixLocation()); } struct TextureProgramBinding { template void set( Program* program, WebKit::WebGraphicsContext3D* context) { DCHECK(program && (program->initialized() || context->isContextLost())); 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 void set( Program* program, WebKit::WebGraphicsContext3D* context) { TextureProgramBinding::set(program, context); texTransformLocation = program->vertexShader().texTransformLocation(); } int texTransformLocation; }; void GLRenderer::drawTextureQuad(const DrawingFrame& frame, const TextureDrawQuad* quad) { TexTransformTextureProgramBinding binding; if (quad->flipped) binding.set(textureProgramFlip(), context()); else binding.set(textureProgram(), context()); GLC(context(), context()->useProgram(binding.programId)); GLC(context(), context()->uniform1i(binding.samplerLocation, 0)); const gfx::RectF& uvRect = quad->uv_rect; GLC(context(), context()->uniform4f(binding.texTransformLocation, uvRect.x(), uvRect.y(), uvRect.width(), uvRect.height())); ResourceProvider::ScopedReadLockGL quadResourceLock(m_resourceProvider, quad->resource_id); GLC(context(), context()->bindTexture(GL_TEXTURE_2D, quadResourceLock.textureId())); if (!quad->premultiplied_alpha) { // 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(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE)); } setShaderOpacity(quad->opacity(), binding.alphaLocation); drawQuadGeometry(frame, quad->quadTransform(), quad->rect, binding.matrixLocation); if (!quad->premultiplied_alpha) GLC(m_context, m_context->blendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); } void GLRenderer::drawIOSurfaceQuad(const DrawingFrame& frame, const IOSurfaceDrawQuad* quad) { TexTransformTextureProgramBinding binding; binding.set(textureIOSurfaceProgram(), context()); GLC(context(), context()->useProgram(binding.programId)); GLC(context(), context()->uniform1i(binding.samplerLocation, 0)); if (quad->orientation == IOSurfaceDrawQuad::FLIPPED) GLC(context(), context()->uniform4f(binding.texTransformLocation, 0, quad->io_surface_size.height(), quad->io_surface_size.width(), quad->io_surface_size.height() * -1.0)); else GLC(context(), context()->uniform4f(binding.texTransformLocation, 0, 0, quad->io_surface_size.width(), quad->io_surface_size.height())); GLC(context(), context()->bindTexture(GL_TEXTURE_RECTANGLE_ARB, quad->io_surface_texture_id)); setShaderOpacity(quad->opacity(), binding.alphaLocation); drawQuadGeometry(frame, quad->quadTransform(), quad->rect, binding.matrixLocation); GLC(context(), context()->bindTexture(GL_TEXTURE_RECTANGLE_ARB, 0)); } void GLRenderer::finishDrawingFrame(DrawingFrame& frame) { m_currentFramebufferLock.reset(); m_swapBufferRect.Union(gfx::ToEnclosingRect(frame.rootDamageRect)); GLC(m_context, m_context->disable(GL_BLEND)); } bool GLRenderer::flippedFramebuffer() const { return true; } void GLRenderer::ensureScissorTestEnabled() { if (m_isScissorEnabled) return; GLC(m_context, m_context->enable(GL_SCISSOR_TEST)); m_isScissorEnabled = true; } void GLRenderer::ensureScissorTestDisabled() { if (!m_isScissorEnabled) return; GLC(m_context, m_context->disable(GL_SCISSOR_TEST)); m_isScissorEnabled = false; } void GLRenderer::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 GLRenderer::setShaderQuadF(const gfx::QuadF& 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 GLRenderer::setShaderOpacity(float opacity, int alphaLocation) { if (alphaLocation != -1) GLC(m_context, m_context->uniform1f(alphaLocation, opacity)); } void GLRenderer::drawQuadGeometry(const DrawingFrame& frame, const WebKit::WebTransformationMatrix& drawTransform, const gfx::RectF& 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(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0)); } void GLRenderer::copyTextureToFramebuffer(const DrawingFrame& frame, int textureId, const gfx::Rect& rect, const WebTransformationMatrix& drawMatrix) { const RenderPassProgram* program = renderPassProgram(); GLC(context(), context()->bindTexture(GL_TEXTURE_2D, textureId)); 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 GLRenderer::finish() { TRACE_EVENT0("cc", "GLRenderer::finish"); m_context->finish(); } bool GLRenderer::swapBuffers() { DCHECK(m_visible); DCHECK(!m_isFramebufferDiscarded); TRACE_EVENT0("cc", "GLRenderer::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(gfx::Rect(gfx::Point(), 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 = gfx::Rect(); return true; } void GLRenderer::onSwapBuffersComplete() { m_client->onSwapBuffersComplete(); } void GLRenderer::onMemoryAllocationChanged(WebGraphicsMemoryAllocation allocation) { // Just ignore the memory manager when it says to set the limit to zero // bytes. This will happen when the memory manager thinks that the renderer // is not visible (which the renderer knows better). if (allocation.bytesLimitWhenVisible) { ManagedMemoryPolicy policy( allocation.bytesLimitWhenVisible, priorityCutoffValue(allocation.priorityCutoffWhenVisible), allocation.bytesLimitWhenNotVisible, priorityCutoffValue(allocation.priorityCutoffWhenNotVisible)); if (allocation.enforceButDoNotKeepAsPolicy) m_client->enforceManagedMemoryPolicy(policy); else m_client->setManagedMemoryPolicy(policy); } bool oldDiscardFramebufferWhenNotVisible = m_discardFramebufferWhenNotVisible; m_discardFramebufferWhenNotVisible = !allocation.suggestHaveBackbuffer; enforceMemoryPolicy(); if (allocation.enforceButDoNotKeepAsPolicy) m_discardFramebufferWhenNotVisible = oldDiscardFramebufferWhenNotVisible; } int GLRenderer::priorityCutoffValue(WebKit::WebGraphicsMemoryAllocation::PriorityCutoff priorityCutoff) { switch (priorityCutoff) { case WebKit::WebGraphicsMemoryAllocation::PriorityCutoffAllowNothing: return PriorityCalculator::allowNothingCutoff(); case WebKit::WebGraphicsMemoryAllocation::PriorityCutoffAllowVisibleOnly: return PriorityCalculator::allowVisibleOnlyCutoff(); case WebKit::WebGraphicsMemoryAllocation::PriorityCutoffAllowVisibleAndNearby: return PriorityCalculator::allowVisibleAndNearbyCutoff(); case WebKit::WebGraphicsMemoryAllocation::PriorityCutoffAllowEverything: return PriorityCalculator::allowEverythingCutoff(); } NOTREACHED(); return 0; } void GLRenderer::enforceMemoryPolicy() { if (!m_visible) { TRACE_EVENT0("cc", "GLRenderer::enforceMemoryPolicy dropping resources"); releaseRenderPassTextures(); if (m_discardFramebufferWhenNotVisible) discardFramebuffer(); GLC(m_context, m_context->flush()); } } void GLRenderer::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(GL_TEXTURE_2D, 0, 0); m_isFramebufferDiscarded = true; // Damage tracker needs a full reset every time framebuffer is discarded. m_client->setFullRootLayerDamage(); } void GLRenderer::ensureFramebuffer() { if (!m_isFramebufferDiscarded) return; if (!m_capabilities.usingDiscardFramebuffer) return; m_context->ensureFramebufferCHROMIUM(); m_isFramebufferDiscarded = false; } void GLRenderer::onContextLost() { m_client->didLoseContext(); } void GLRenderer::getFramebufferPixels(void *pixels, const gfx::Rect& rect) { DCHECK(rect.right() <= viewportWidth()); DCHECK(rect.bottom() <= viewportHeight()); if (!pixels) return; makeContextCurrent(); bool doWorkaround = needsIOSurfaceReadbackWorkaround(); GLuint temporaryTexture = 0; GLuint 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. temporaryTexture = m_context->createTexture(); GLC(m_context, m_context->bindTexture(GL_TEXTURE_2D, temporaryTexture)); GLC(m_context, m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GLC(m_context, m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GLC(m_context, m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GLC(m_context, m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); // Copy the contents of the current (IOSurface-backed) framebuffer into a temporary texture. GLC(m_context, m_context->copyTexImage2D(GL_TEXTURE_2D, 0, GL_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(GL_FRAMEBUFFER, temporaryFBO)); GLC(m_context, m_context->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, temporaryTexture, 0)); DCHECK(m_context->checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); } scoped_array srcPixels(new uint8_t[rect.width() * rect.height() * 4]); GLC(m_context, m_context->readPixels(rect.x(), viewportSize().height() - rect.bottom(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE, srcPixels.get())); uint8_t* destPixels = static_cast(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(GL_FRAMEBUFFER, 0)); GLC(m_context, m_context->bindTexture(GL_TEXTURE_2D, 0)); GLC(m_context, m_context->deleteFramebuffer(temporaryFBO)); GLC(m_context, m_context->deleteTexture(temporaryTexture)); } enforceMemoryPolicy(); } bool GLRenderer::getFramebufferTexture(ScopedResource* texture, const gfx::Rect& deviceRect) { DCHECK(!texture->id() || (texture->size() == deviceRect.size() && texture->format() == GL_RGB)); if (!texture->id() && !texture->Allocate(Renderer::ImplPool, deviceRect.size(), GL_RGB, ResourceProvider::TextureUsageAny)) return false; ResourceProvider::ScopedWriteLockGL lock(m_resourceProvider, texture->id()); GLC(m_context, m_context->bindTexture(GL_TEXTURE_2D, lock.textureId())); GLC(m_context, m_context->copyTexImage2D(GL_TEXTURE_2D, 0, texture->format(), deviceRect.x(), deviceRect.y(), deviceRect.width(), deviceRect.height(), 0)); return true; } bool GLRenderer::useScopedTexture(DrawingFrame& frame, const ScopedResource* texture, const gfx::Rect& viewportRect) { DCHECK(texture->id()); frame.currentRenderPass = 0; frame.currentTexture = texture; return bindFramebufferToTexture(frame, texture, viewportRect); } void GLRenderer::bindFramebufferToOutputSurface(DrawingFrame& frame) { m_currentFramebufferLock.reset(); GLC(m_context, m_context->bindFramebuffer(GL_FRAMEBUFFER, 0)); } bool GLRenderer::bindFramebufferToTexture(DrawingFrame& frame, const ScopedResource* texture, const gfx::Rect& framebufferRect) { DCHECK(texture->id()); GLC(m_context, m_context->bindFramebuffer(GL_FRAMEBUFFER, m_offscreenFramebufferId)); m_currentFramebufferLock = make_scoped_ptr(new ResourceProvider::ScopedWriteLockGL(m_resourceProvider, texture->id())); unsigned textureId = m_currentFramebufferLock->textureId(); GLC(m_context, m_context->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId, 0)); DCHECK(m_context->checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); initializeMatrices(frame, framebufferRect, false); setDrawViewportSize(framebufferRect.size()); return true; } void GLRenderer::setScissorTestRect(const gfx::Rect& scissorRect) { ensureScissorTestEnabled(); // Don't unnecessarily ask the context to change the scissor, because it // may cause undesired GPU pipeline flushes. if (scissorRect == m_scissorRect) return; m_scissorRect = scissorRect; GLC(m_context, m_context->scissor(scissorRect.x(), scissorRect.y(), scissorRect.width(), scissorRect.height())); } void GLRenderer::setDrawViewportSize(const gfx::Size& viewportSize) { GLC(m_context, m_context->viewport(0, 0, viewportSize.width(), viewportSize.height())); } bool GLRenderer::makeContextCurrent() { return m_context->makeContextCurrent(); } bool GLRenderer::initializeSharedObjects() { TRACE_EVENT0("cc", "GLRenderer::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 = make_scoped_ptr(new GeometryBinding(m_context, quadVertexRect())); m_renderPassProgram = make_scoped_ptr(new RenderPassProgram(m_context)); m_tileProgram = make_scoped_ptr(new TileProgram(m_context)); m_tileProgramOpaque = make_scoped_ptr(new TileProgramOpaque(m_context)); GLC(m_context, m_context->flush()); return true; } const GLRenderer::TileCheckerboardProgram* GLRenderer::tileCheckerboardProgram() { if (!m_tileCheckerboardProgram) m_tileCheckerboardProgram = make_scoped_ptr(new TileCheckerboardProgram(m_context)); if (!m_tileCheckerboardProgram->initialized()) { TRACE_EVENT0("cc", "GLRenderer::checkerboardProgram::initalize"); m_tileCheckerboardProgram->initialize(m_context, m_isUsingBindUniform); } return m_tileCheckerboardProgram.get(); } const GLRenderer::SolidColorProgram* GLRenderer::solidColorProgram() { if (!m_solidColorProgram) m_solidColorProgram = make_scoped_ptr(new SolidColorProgram(m_context)); if (!m_solidColorProgram->initialized()) { TRACE_EVENT0("cc", "GLRenderer::solidColorProgram::initialize"); m_solidColorProgram->initialize(m_context, m_isUsingBindUniform); } return m_solidColorProgram.get(); } const GLRenderer::RenderPassProgram* GLRenderer::renderPassProgram() { DCHECK(m_renderPassProgram); if (!m_renderPassProgram->initialized()) { TRACE_EVENT0("cc", "GLRenderer::renderPassProgram::initialize"); m_renderPassProgram->initialize(m_context, m_isUsingBindUniform); } return m_renderPassProgram.get(); } const GLRenderer::RenderPassProgramAA* GLRenderer::renderPassProgramAA() { if (!m_renderPassProgramAA) m_renderPassProgramAA = make_scoped_ptr(new RenderPassProgramAA(m_context)); if (!m_renderPassProgramAA->initialized()) { TRACE_EVENT0("cc", "GLRenderer::renderPassProgramAA::initialize"); m_renderPassProgramAA->initialize(m_context, m_isUsingBindUniform); } return m_renderPassProgramAA.get(); } const GLRenderer::RenderPassMaskProgram* GLRenderer::renderPassMaskProgram() { if (!m_renderPassMaskProgram) m_renderPassMaskProgram = make_scoped_ptr(new RenderPassMaskProgram(m_context)); if (!m_renderPassMaskProgram->initialized()) { TRACE_EVENT0("cc", "GLRenderer::renderPassMaskProgram::initialize"); m_renderPassMaskProgram->initialize(m_context, m_isUsingBindUniform); } return m_renderPassMaskProgram.get(); } const GLRenderer::RenderPassMaskProgramAA* GLRenderer::renderPassMaskProgramAA() { if (!m_renderPassMaskProgramAA) m_renderPassMaskProgramAA = make_scoped_ptr(new RenderPassMaskProgramAA(m_context)); if (!m_renderPassMaskProgramAA->initialized()) { TRACE_EVENT0("cc", "GLRenderer::renderPassMaskProgramAA::initialize"); m_renderPassMaskProgramAA->initialize(m_context, m_isUsingBindUniform); } return m_renderPassMaskProgramAA.get(); } const GLRenderer::TileProgram* GLRenderer::tileProgram() { DCHECK(m_tileProgram); if (!m_tileProgram->initialized()) { TRACE_EVENT0("cc", "GLRenderer::tileProgram::initialize"); m_tileProgram->initialize(m_context, m_isUsingBindUniform); } return m_tileProgram.get(); } const GLRenderer::TileProgramOpaque* GLRenderer::tileProgramOpaque() { DCHECK(m_tileProgramOpaque); if (!m_tileProgramOpaque->initialized()) { TRACE_EVENT0("cc", "GLRenderer::tileProgramOpaque::initialize"); m_tileProgramOpaque->initialize(m_context, m_isUsingBindUniform); } return m_tileProgramOpaque.get(); } const GLRenderer::TileProgramAA* GLRenderer::tileProgramAA() { if (!m_tileProgramAA) m_tileProgramAA = make_scoped_ptr(new TileProgramAA(m_context)); if (!m_tileProgramAA->initialized()) { TRACE_EVENT0("cc", "GLRenderer::tileProgramAA::initialize"); m_tileProgramAA->initialize(m_context, m_isUsingBindUniform); } return m_tileProgramAA.get(); } const GLRenderer::TileProgramSwizzle* GLRenderer::tileProgramSwizzle() { if (!m_tileProgramSwizzle) m_tileProgramSwizzle = make_scoped_ptr(new TileProgramSwizzle(m_context)); if (!m_tileProgramSwizzle->initialized()) { TRACE_EVENT0("cc", "GLRenderer::tileProgramSwizzle::initialize"); m_tileProgramSwizzle->initialize(m_context, m_isUsingBindUniform); } return m_tileProgramSwizzle.get(); } const GLRenderer::TileProgramSwizzleOpaque* GLRenderer::tileProgramSwizzleOpaque() { if (!m_tileProgramSwizzleOpaque) m_tileProgramSwizzleOpaque = make_scoped_ptr(new TileProgramSwizzleOpaque(m_context)); if (!m_tileProgramSwizzleOpaque->initialized()) { TRACE_EVENT0("cc", "GLRenderer::tileProgramSwizzleOpaque::initialize"); m_tileProgramSwizzleOpaque->initialize(m_context, m_isUsingBindUniform); } return m_tileProgramSwizzleOpaque.get(); } const GLRenderer::TileProgramSwizzleAA* GLRenderer::tileProgramSwizzleAA() { if (!m_tileProgramSwizzleAA) m_tileProgramSwizzleAA = make_scoped_ptr(new TileProgramSwizzleAA(m_context)); if (!m_tileProgramSwizzleAA->initialized()) { TRACE_EVENT0("cc", "GLRenderer::tileProgramSwizzleAA::initialize"); m_tileProgramSwizzleAA->initialize(m_context, m_isUsingBindUniform); } return m_tileProgramSwizzleAA.get(); } const GLRenderer::TextureProgram* GLRenderer::textureProgram() { if (!m_textureProgram) m_textureProgram = make_scoped_ptr(new TextureProgram(m_context)); if (!m_textureProgram->initialized()) { TRACE_EVENT0("cc", "GLRenderer::textureProgram::initialize"); m_textureProgram->initialize(m_context, m_isUsingBindUniform); } return m_textureProgram.get(); } const GLRenderer::TextureProgramFlip* GLRenderer::textureProgramFlip() { if (!m_textureProgramFlip) m_textureProgramFlip = make_scoped_ptr(new TextureProgramFlip(m_context)); if (!m_textureProgramFlip->initialized()) { TRACE_EVENT0("cc", "GLRenderer::textureProgramFlip::initialize"); m_textureProgramFlip->initialize(m_context, m_isUsingBindUniform); } return m_textureProgramFlip.get(); } const GLRenderer::TextureIOSurfaceProgram* GLRenderer::textureIOSurfaceProgram() { if (!m_textureIOSurfaceProgram) m_textureIOSurfaceProgram = make_scoped_ptr(new TextureIOSurfaceProgram(m_context)); if (!m_textureIOSurfaceProgram->initialized()) { TRACE_EVENT0("cc", "GLRenderer::textureIOSurfaceProgram::initialize"); m_textureIOSurfaceProgram->initialize(m_context, m_isUsingBindUniform); } return m_textureIOSurfaceProgram.get(); } const GLRenderer::VideoYUVProgram* GLRenderer::videoYUVProgram() { if (!m_videoYUVProgram) m_videoYUVProgram = make_scoped_ptr(new VideoYUVProgram(m_context)); if (!m_videoYUVProgram->initialized()) { TRACE_EVENT0("cc", "GLRenderer::videoYUVProgram::initialize"); m_videoYUVProgram->initialize(m_context, m_isUsingBindUniform); } return m_videoYUVProgram.get(); } const GLRenderer::VideoStreamTextureProgram* GLRenderer::videoStreamTextureProgram() { if (!m_videoStreamTextureProgram) m_videoStreamTextureProgram = make_scoped_ptr(new VideoStreamTextureProgram(m_context)); if (!m_videoStreamTextureProgram->initialized()) { TRACE_EVENT0("cc", "GLRenderer::streamTextureProgram::initialize"); m_videoStreamTextureProgram->initialize(m_context, m_isUsingBindUniform); } return m_videoStreamTextureProgram.get(); } void GLRenderer::cleanupSharedObjects() { makeContextCurrent(); m_sharedGeometry.reset(); 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 GLRenderer::isContextLost() { return (m_context->getGraphicsResetStatusARB() != GL_NO_ERROR); } } // namespace cc