// Copyright 2012 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/heads_up_display_layer_impl.h" #include "base/stringprintf.h" #include "cc/debug_rect_history.h" #include "cc/font_atlas.h" #include "cc/frame_rate_counter.h" #include "cc/layer_tree_host_impl.h" #include "cc/quad_sink.h" #include "cc/texture_draw_quad.h" #include "skia/ext/platform_canvas.h" #include "skia/ext/platform_canvas.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/SkPaint.h" #include "third_party/skia/include/effects/SkColorMatrixFilter.h" #include "ui/gfx/point.h" #include "ui/gfx/size.h" namespace cc { static inline SkPaint createPaint() { // The SkCanvas is in RGBA but the shader is expecting BGRA, so we need to // swizzle our colors when drawing to the SkCanvas. SkColorMatrix swizzleMatrix; for (int i = 0; i < 20; ++i) swizzleMatrix.fMat[i] = 0; swizzleMatrix.fMat[0 + 5 * 2] = 1; swizzleMatrix.fMat[1 + 5 * 1] = 1; swizzleMatrix.fMat[2 + 5 * 0] = 1; swizzleMatrix.fMat[3 + 5 * 3] = 1; SkPaint paint; paint.setColorFilter(new SkColorMatrixFilter(swizzleMatrix))->unref(); return paint; } HeadsUpDisplayLayerImpl::HeadsUpDisplayLayerImpl(int id) : LayerImpl(id) , m_averageFPS(0) , m_stdDeviation(0) , m_showFPSCounter(false) { } HeadsUpDisplayLayerImpl::~HeadsUpDisplayLayerImpl() { } void HeadsUpDisplayLayerImpl::setFontAtlas(scoped_ptr fontAtlas) { m_fontAtlas = fontAtlas.Pass(); } void HeadsUpDisplayLayerImpl::setShowFPSCounter(bool show) { m_showFPSCounter = show; } void HeadsUpDisplayLayerImpl::willDraw(ResourceProvider* resourceProvider) { LayerImpl::willDraw(resourceProvider); if (!m_hudTexture) m_hudTexture = ScopedResource::create(resourceProvider); // FIXME: Scale the HUD by deviceScale to make it more friendly under high DPI. if (m_hudTexture->size() != bounds()) m_hudTexture->free(); if (!m_hudTexture->id()) m_hudTexture->allocate(Renderer::ImplPool, bounds(), GL_RGBA, ResourceProvider::TextureUsageAny); } void HeadsUpDisplayLayerImpl::appendQuads(QuadSink& quadSink, AppendQuadsData& appendQuadsData) { if (!m_hudTexture->id()) return; SharedQuadState* sharedQuadState = quadSink.useSharedQuadState(createSharedQuadState()); gfx::Rect quadRect(gfx::Point(), bounds()); bool premultipliedAlpha = true; gfx::RectF uvRect(0, 0, 1, 1); bool flipped = false; quadSink.append(TextureDrawQuad::create(sharedQuadState, quadRect, m_hudTexture->id(), premultipliedAlpha, uvRect, flipped).PassAs(), appendQuadsData); } void HeadsUpDisplayLayerImpl::updateHudTexture(ResourceProvider* resourceProvider) { if (!m_hudTexture->id()) return; SkISize canvasSize; if (m_hudCanvas) canvasSize = m_hudCanvas->getDeviceSize(); else canvasSize.set(0, 0); if (canvasSize.fWidth != bounds().width() || canvasSize.fHeight != bounds().height() || !m_hudCanvas) m_hudCanvas = make_scoped_ptr(skia::CreateBitmapCanvas(bounds().width(), bounds().height(), false /* opaque */)); m_hudCanvas->clear(SkColorSetARGB(0, 0, 0, 0)); drawHudContents(m_hudCanvas.get()); const SkBitmap* bitmap = &m_hudCanvas->getDevice()->accessBitmap(false); SkAutoLockPixels locker(*bitmap); gfx::Rect layerRect(gfx::Point(), bounds()); DCHECK(bitmap->config() == SkBitmap::kARGB_8888_Config); resourceProvider->setPixels(m_hudTexture->id(), static_cast(bitmap->getPixels()), layerRect, layerRect, gfx::Vector2d()); } void HeadsUpDisplayLayerImpl::didDraw(ResourceProvider* resourceProvider) { LayerImpl::didDraw(resourceProvider); if (!m_hudTexture->id()) return; // FIXME: the following assert will not be true when sending resources to a // parent compositor. We will probably need to hold on to m_hudTexture for // longer, and have several HUD textures in the pipeline. DCHECK(!resourceProvider->inUseByConsumer(m_hudTexture->id())); } void HeadsUpDisplayLayerImpl::didLoseContext() { m_hudTexture.reset(); } bool HeadsUpDisplayLayerImpl::layerIsAlwaysDamaged() const { return true; } void HeadsUpDisplayLayerImpl::drawHudContents(SkCanvas* canvas) { const LayerTreeSettings& settings = layerTreeHostImpl()->settings(); if (settings.showPlatformLayerTree) { SkPaint paint = createPaint(); paint.setColor(SkColorSetARGB(192, 0, 0, 0)); canvas->drawRect(SkRect::MakeXYWH(0, 0, bounds().width(), bounds().height()), paint); } int platformLayerTreeTop = 0; if (m_showFPSCounter) platformLayerTreeTop = drawFPSCounter(canvas, layerTreeHostImpl()->fpsCounter()); if (settings.showPlatformLayerTree && m_fontAtlas.get()) { std::string layerTree = layerTreeHostImpl()->layerTreeAsText(); m_fontAtlas->drawText(canvas, createPaint(), layerTree, gfx::Point(2, platformLayerTreeTop), bounds()); } if (settings.showDebugRects()) drawDebugRects(canvas, layerTreeHostImpl()->debugRectHistory()); } int HeadsUpDisplayLayerImpl::drawFPSCounter(SkCanvas* canvas, FrameRateCounter* fpsCounter) { const int left = 2; const int top = 2; const int padding = 4; const int fontHeight = m_fontAtlas.get() ? m_fontAtlas->fontHeight() : 0; const int graphWidth = fpsCounter->timeStampHistorySize() - 3; const int graphHeight = 40; const int width = graphWidth + 2 * padding; const int height = fontHeight + graphHeight + 4 * padding + 2; SkPaint paint = createPaint(); // Draw background. paint.setColor(SkColorSetARGB(215, 17, 17, 17)); canvas->drawRect(SkRect::MakeXYWH(left, top, width, height), paint); SkRect textBounds = SkRect::MakeXYWH(left + padding, top + padding, graphWidth, fontHeight); SkRect graphBounds = SkRect::MakeXYWH(left + padding, textBounds.bottom() + 2 * padding, graphWidth, graphHeight); drawFPSCounterText(canvas, paint, fpsCounter, textBounds); drawFPSCounterGraph(canvas, paint, fpsCounter, graphBounds); return top + height; } void HeadsUpDisplayLayerImpl::drawFPSCounterText(SkCanvas* canvas, SkPaint& paint, FrameRateCounter* fpsCounter, SkRect bounds) { // Update FPS text - not every frame so text is readable if (base::TimeDelta(fpsCounter->timeStampOfRecentFrame(0) - textUpdateTime).InSecondsF() > 0.25) { fpsCounter->getAverageFPSAndStandardDeviation(m_averageFPS, m_stdDeviation); textUpdateTime = fpsCounter->timeStampOfRecentFrame(0); } // Draw FPS text. if (m_fontAtlas.get()) { std::string fpsText = base::StringPrintf("FPS:%5.1f", m_averageFPS); std::string deviationText = base::StringPrintf("+/-%4.1f", m_stdDeviation); int deviationWidth = m_fontAtlas->textSize(deviationText).width(); gfx::Size textArea(bounds.width(), bounds.height()); paint.setColor(SK_ColorRED); m_fontAtlas->drawText(canvas, paint, fpsText, gfx::Point(bounds.left(), bounds.top()), textArea); m_fontAtlas->drawText(canvas, paint, deviationText, gfx::Point(bounds.right() - deviationWidth, bounds.top()), textArea); } } void HeadsUpDisplayLayerImpl::drawFPSCounterGraph(SkCanvas* canvas, SkPaint& paint, FrameRateCounter* fpsCounter, SkRect bounds) { const double loFPS = 0; const double hiFPS = 80; paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(1); // Draw top and bottom line. paint.setColor(SkColorSetRGB(150, 150, 150)); canvas->drawLine(bounds.left(), bounds.top() - 1, bounds.right(), bounds.top() - 1, paint); canvas->drawLine(bounds.left(), bounds.bottom(), bounds.right(), bounds.bottom(), paint); // Draw 60fps line. paint.setColor(SkColorSetRGB(100, 100, 100)); canvas->drawLine(bounds.left(), bounds.top() + bounds.height() / 4, bounds.right(), bounds.top() + bounds.height() / 4, paint); // Draw FPS graph. int x = 0; SkPath path; for (int i = 1; i < fpsCounter->timeStampHistorySize() - 1; ++i) { base::TimeDelta delta = fpsCounter->timeStampOfRecentFrame(i + 1) - fpsCounter->timeStampOfRecentFrame(i); // Skip plotting this particular instantaneous frame rate if it is not likely to have been valid. if (!fpsCounter->isBadFrameInterval(delta)) { double fps = 1.0 / delta.InSecondsF(); // Clamp the FPS to the range we want to plot visually. double p = 1 - ((fps - loFPS) / (hiFPS - loFPS)); if (p < 0) p = 0; if (p > 1) p = 1; // Plot this data point. SkPoint cur = SkPoint::Make(bounds.left() + x, bounds.top() + p * bounds.height()); if (path.isEmpty()) path.moveTo(cur); else path.lineTo(cur); } x += 1; } paint.setAntiAlias(true); paint.setColor(SK_ColorRED); canvas->drawPath(path, paint); } void HeadsUpDisplayLayerImpl::drawDebugRects(SkCanvas* canvas, DebugRectHistory* debugRectHistory) { const std::vector& debugRects = debugRectHistory->debugRects(); for (size_t i = 0; i < debugRects.size(); ++i) { SkColor strokeColor = 0; SkColor fillColor = 0; switch (debugRects[i].type) { case PaintRectType: // Paint rects in red strokeColor = SkColorSetARGB(255, 255, 0, 0); fillColor = SkColorSetARGB(30, 255, 0, 0); break; case PropertyChangedRectType: // Property-changed rects in blue strokeColor = SkColorSetARGB(255, 255, 0, 0); fillColor = SkColorSetARGB(30, 0, 0, 255); break; case SurfaceDamageRectType: // Surface damage rects in yellow-orange strokeColor = SkColorSetARGB(255, 200, 100, 0); fillColor = SkColorSetARGB(30, 200, 100, 0); break; case ReplicaScreenSpaceRectType: // Screen space rects in green. strokeColor = SkColorSetARGB(255, 100, 200, 0); fillColor = SkColorSetARGB(30, 100, 200, 0); break; case ScreenSpaceRectType: // Screen space rects in purple. strokeColor = SkColorSetARGB(255, 100, 0, 200); fillColor = SkColorSetARGB(10, 100, 0, 200); break; case OccludingRectType: // Occluding rects in a reddish color. strokeColor = SkColorSetARGB(255, 200, 0, 100); fillColor = SkColorSetARGB(10, 200, 0, 100); break; } const gfx::RectF& rect = debugRects[i].rect; SkRect skRect = SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()); SkPaint paint = createPaint(); paint.setColor(fillColor); canvas->drawRect(skRect, paint); paint.setColor(strokeColor); paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(2); canvas->drawRect(skRect, paint); } } const char* HeadsUpDisplayLayerImpl::layerTypeAsString() const { return "HeadsUpDisplayLayer"; } } // namespace cc