blob: 94f8d83124f945b5118af3f5b1d07a7bbcdc5fba [file] [log] [blame]
// 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 <public/WebGraphicsContext3D.h>
#include <public/WebSharedGraphicsContext3D.h>
#include <set>
#include <string>
#include <vector>
using namespace std;
using WebKit::WebGraphicsContext3D;
using WebKit::WebGraphicsMemoryAllocation;
using WebKit::WebSharedGraphicsContext3D;
namespace cc {
namespace {
bool needsIOSurfaceReadbackWorkaround()
{
#if defined(OS_MACOSX)
return true;
#else
return false;
#endif
}
} // anonymous namespace
scoped_ptr<GLRenderer> GLRenderer::create(RendererClient* client, ResourceProvider* resourceProvider)
{
scoped_ptr<GLRenderer> renderer(make_scoped_ptr(new GLRenderer(client, resourceProvider)));
if (!renderer->initialize())
return scoped_ptr<GLRenderer>();
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<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 = 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<int>(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;
gfx::Transform renderMatrix = quad->quadTransform();
renderMatrix.Translate(0.5 * layerRect.width() + layerRect.x(), 0.5 * layerRect.height() + layerRect.y());
renderMatrix.Scale(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<GrTexture> 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<GrTexture> 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<ScopedResource> GLRenderer::drawBackgroundFilters(
DrawingFrame& frame, const RenderPassDrawQuad* quad,
const WebKit::WebFilterOperations& filters,
const gfx::Transform& contentsDeviceTransform,
const gfx::Transform& 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<ScopedResource>();
// 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<ScopedResource>();
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<ScopedResource> deviceBackgroundTexture = ScopedResource::create(m_resourceProvider);
if (!getFramebufferTexture(deviceBackgroundTexture.get(), deviceRect))
return scoped_ptr<ScopedResource>();
SkBitmap filteredDeviceBackground = applyFilters(this, filters, deviceBackgroundTexture.get(), m_client->hasImplThread());
if (!filteredDeviceBackground.getTexture())
return scoped_ptr<ScopedResource>();
GrTexture* texture = reinterpret_cast<GrTexture*>(filteredDeviceBackground.getTexture());
int filteredDeviceBackgroundTextureId = texture->getTextureHandle();
scoped_ptr<ScopedResource> backgroundTexture = ScopedResource::create(m_resourceProvider);
if (!backgroundTexture->Allocate(Renderer::ImplPool, quad->rect.size(), GL_RGBA, ResourceProvider::TextureUsageFramebuffer))
return scoped_ptr<ScopedResource>();
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.
gfx::Transform deviceToFramebufferTransform;
deviceToFramebufferTransform.Translate(quad->rect.width() / 2.0, quad->rect.height() / 2.0);
deviceToFramebufferTransform.Scale3d(quad->rect.width(), quad->rect.height(), 1);
deviceToFramebufferTransform.PreconcatTransform(contentsDeviceTransformInverse);
copyTextureToFramebuffer(frame, filteredDeviceBackgroundTextureId, deviceRect, deviceToFramebufferTransform);
}
useRenderPass(frame, targetRenderPass);
if (!usingBackgroundTexture)
return scoped_ptr<ScopedResource>();
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;
gfx::Transform quadRectMatrix;
quadRectTransform(&quadRectMatrix, quad->quadTransform(), quad->rect);
gfx::Transform contentsDeviceTransform = MathUtil::to2dTransform(frame.windowMatrix * frame.projectionMatrix * quadRectMatrix);
// Can only draw surface if device matrix is invertible.
if (!contentsDeviceTransform.IsInvertible())
return;
gfx::Transform contentsDeviceTransformInverse = MathUtil::inverse(contentsDeviceTransform);
scoped_ptr<ScopedResource> 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<ResourceProvider::ScopedReadLockGL> contentsResourceLock;
unsigned contentsTextureId = 0;
if (filterBitmap.getTexture()) {
GrTexture* texture = reinterpret_cast<GrTexture*>(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<ResourceProvider::ScopedReadLockGL> 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<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 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;
gfx::Transform deviceTransform = MathUtil::to2dTransform(frame.windowMatrix * frame.projectionMatrix * quad->quadTransform());
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.
gfx::Transform deviceTransformInverse = MathUtil::inverse(deviceTransform);
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<class Program> 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<class Program> 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* glMatrix, const gfx::Transform& transform)
{
transform.matrix().asColMajorf(glMatrix);
}
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 gfx::Transform& drawTransform, const gfx::RectF& quadRect, int matrixLocation)
{
gfx::Transform 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 gfx::Transform& 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. <rdar://problem/10949687>
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<uint8_t> 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<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(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