| // 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 <limits> |
| |
| #include "base/stringprintf.h" |
| #include "cc/debug_colors.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_minFPS(0) |
| , m_maxFPS(0) |
| , m_showFPSCounter(false) |
| { |
| } |
| |
| HeadsUpDisplayLayerImpl::~HeadsUpDisplayLayerImpl() |
| { |
| } |
| |
| void HeadsUpDisplayLayerImpl::setFontAtlas(scoped_ptr<FontAtlas> 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()); |
| gfx::Rect opaqueRect(contentsOpaque() ? quadRect : gfx::Rect()); |
| bool premultipliedAlpha = true; |
| gfx::RectF uvRect(0, 0, 1, 1); |
| bool flipped = false; |
| scoped_ptr<TextureDrawQuad> quad = TextureDrawQuad::Create(); |
| quad->SetNew(sharedQuadState, quadRect, opaqueRect, m_hudTexture->id(), premultipliedAlpha, uvRect, flipped); |
| quadSink.append(quad.PassAs<DrawQuad>(), 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<const uint8_t*>(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 padding = 4; |
| const int gap = 6; |
| |
| const int fontHeight = m_fontAtlas.get() ? m_fontAtlas->fontHeight() : 0; |
| |
| const int graphWidth = 120; |
| const int graphHeight = 40; |
| |
| const int histogramWidth = 37; |
| |
| const int width = graphWidth + histogramWidth + 4 * padding; |
| const int height = fontHeight + graphHeight + 4 * padding + 2; |
| |
| const int left = bounds().width() - width - 2; |
| const int top = 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 + histogramWidth + gap + 2, fontHeight); |
| SkRect graphBounds = SkRect::MakeXYWH(left + padding, textBounds.bottom() + 2 * padding, graphWidth, graphHeight); |
| SkRect histogramBounds = SkRect::MakeXYWH(graphBounds.right() + gap, graphBounds.top(), histogramWidth, graphHeight); |
| |
| drawFPSCounterText(canvas, paint, fpsCounter, textBounds); |
| drawFPSCounterGraphAndHistogram(canvas, paint, fpsCounter, graphBounds, histogramBounds); |
| |
| 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) { |
| m_averageFPS = fpsCounter->getAverageFPS(); |
| textUpdateTime = fpsCounter->timeStampOfRecentFrame(0); |
| } |
| |
| // Draw FPS text. |
| if (m_fontAtlas.get()) { |
| std::string fpsText = base::StringPrintf("FPS:%5.1f", m_averageFPS); |
| std::string minMaxText = base::StringPrintf("%.0f-%.0f", std::min( m_minFPS, m_maxFPS), m_maxFPS); |
| |
| int minMaxWidth = m_fontAtlas->textSize(minMaxText).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, minMaxText, gfx::Point(bounds.right() - minMaxWidth, bounds.top()), textArea); |
| } |
| } |
| |
| void HeadsUpDisplayLayerImpl::drawFPSCounterGraphAndHistogram(SkCanvas* canvas, SkPaint& paint, FrameRateCounter* fpsCounter, SkRect graphBounds, SkRect histogramBounds) |
| { |
| const double loFPS = 0; |
| const double hiFPS = std::max(m_maxFPS + 10.0, 80.0); |
| |
| // Draw top and bottom line. |
| paint.setColor(SkColorSetRGB(130, 130, 130)); |
| canvas->drawLine(graphBounds.left(), graphBounds.top() - 1, graphBounds.right(), graphBounds.top() - 1, paint); |
| canvas->drawLine(graphBounds.left(), graphBounds.bottom(), graphBounds.right(), graphBounds.bottom(), paint); |
| |
| // Draw 60fps line. |
| const double top60 = graphBounds.top() + graphBounds.height() * (1 - ((60 - loFPS) / (hiFPS - loFPS))) - 1; |
| paint.setColor(SkColorSetRGB(100, 100, 100)); |
| canvas->drawLine(graphBounds.left(), top60, graphBounds.right(), top60, paint); |
| |
| // Collect graph and histogram data. |
| double x = 0; |
| const double timeScale = 60; // in pixels/second |
| SkPath path; |
| |
| m_minFPS = std::numeric_limits<double>::max(); |
| m_maxFPS = 0; |
| |
| const int histogramSize = 20; |
| double histogram[histogramSize] = {0}; |
| double maxBucketValue = 0; |
| |
| for (int i = fpsCounter->timeStampHistorySize() - 2; i > 0 && x <= graphBounds.width(); --i) { |
| base::TimeDelta delta = fpsCounter->timeStampOfRecentFrame(i + 1) - fpsCounter->timeStampOfRecentFrame(i); |
| |
| // Skip this particular instantaneous frame rate if it is not likely to have been valid. |
| if (!fpsCounter->isBadFrameInterval(delta)) { |
| |
| double fps = 1.0 / delta.InSecondsF(); |
| |
| m_minFPS = std::min(fps, m_minFPS); |
| m_maxFPS = std::max(fps, m_maxFPS); |
| |
| // Clamp the FPS to the range we want to plot visually. |
| double p = (fps - loFPS) / (hiFPS - loFPS); |
| if (p < 0) |
| p = 0; |
| if (p > 1) |
| p = 1; |
| |
| // Plot this data point. |
| SkPoint cur = SkPoint::Make(graphBounds.right() - x, graphBounds.bottom() - p * graphBounds.height()); |
| if (path.isEmpty()) |
| path.moveTo(cur); |
| else |
| path.lineTo(cur); |
| |
| // Use the fps value to find the right bucket in the histogram. |
| int bucketIndex = floor(p * (histogramSize - 1)); |
| |
| // Add the delta time to take the time spent at that fps rate into account. |
| histogram[bucketIndex] += delta.InSecondsF(); |
| maxBucketValue = std::max(histogram[bucketIndex], maxBucketValue); |
| } |
| |
| x += delta.InSecondsF() * timeScale; |
| } |
| |
| // Draw FPS histogram. |
| paint.setColor(SkColorSetRGB(130, 130, 130)); |
| canvas->drawLine(histogramBounds.left() - 1, histogramBounds.top() - 1, histogramBounds.left() - 1, histogramBounds.bottom() + 1, paint); |
| canvas->drawLine(histogramBounds.right() + 1, histogramBounds.top() - 1, histogramBounds.right() + 1, histogramBounds.bottom() + 1, paint); |
| |
| paint.setColor(SK_ColorRED); |
| const double barHeight = histogramBounds.height() / histogramSize; |
| |
| for (int i = histogramSize - 1; i >= 0; --i) { |
| if (histogram[i] > 0) { |
| double barWidth = histogram[i] / maxBucketValue * histogramBounds.width(); |
| canvas->drawRect(SkRect::MakeXYWH(histogramBounds.left(), histogramBounds.bottom() - (i + 1) * barHeight, barWidth, 1), paint); |
| } |
| } |
| |
| // Draw FPS graph. |
| paint.setAntiAlias(true); |
| paint.setStyle(SkPaint::kStroke_Style); |
| paint.setStrokeWidth(1); |
| |
| canvas->drawPath(path, paint); |
| } |
| |
| void HeadsUpDisplayLayerImpl::drawDebugRects(SkCanvas* canvas, DebugRectHistory* debugRectHistory) |
| { |
| const std::vector<DebugRect>& debugRects = debugRectHistory->debugRects(); |
| |
| for (size_t i = 0; i < debugRects.size(); ++i) { |
| SkColor strokeColor = 0; |
| SkColor fillColor = 0; |
| float strokeWidth = 0; |
| |
| switch (debugRects[i].type) { |
| case PaintRectType: |
| strokeColor = DebugColors::PaintRectBorderColor(); |
| fillColor = DebugColors::PaintRectFillColor(); |
| strokeWidth = DebugColors::PaintRectBorderWidth(layerTreeHostImpl()); |
| break; |
| case PropertyChangedRectType: |
| strokeColor = DebugColors::PropertyChangedRectBorderColor(); |
| fillColor = DebugColors::PropertyChangedRectFillColor(); |
| strokeWidth = DebugColors::PropertyChangedRectBorderWidth(layerTreeHostImpl()); |
| break; |
| case SurfaceDamageRectType: |
| strokeColor = DebugColors::SurfaceDamageRectBorderColor(); |
| fillColor = DebugColors::SurfaceDamageRectFillColor(); |
| strokeWidth = DebugColors::SurfaceDamageRectBorderWidth(layerTreeHostImpl()); |
| break; |
| case ReplicaScreenSpaceRectType: |
| strokeColor = DebugColors::ScreenSpaceSurfaceReplicaRectBorderColor(); |
| fillColor = DebugColors::ScreenSpaceSurfaceReplicaRectFillColor(); |
| strokeWidth = DebugColors::ScreenSpaceSurfaceReplicaRectBorderWidth(layerTreeHostImpl()); |
| break; |
| case ScreenSpaceRectType: |
| strokeColor = DebugColors::ScreenSpaceLayerRectBorderColor(); |
| fillColor = DebugColors::ScreenSpaceLayerRectFillColor(); |
| strokeWidth = DebugColors::ScreenSpaceLayerRectBorderWidth(layerTreeHostImpl()); |
| break; |
| case OccludingRectType: |
| strokeColor = DebugColors::OccludingRectBorderColor(); |
| fillColor = DebugColors::OccludingRectFillColor(); |
| strokeWidth = DebugColors::OccludingRectBorderWidth(layerTreeHostImpl()); |
| break; |
| case NonOccludingRectType: |
| strokeColor = DebugColors::NonOccludingRectBorderColor(); |
| fillColor = DebugColors::NonOccludingRectFillColor(); |
| strokeWidth = DebugColors::NonOccludingRectBorderWidth(layerTreeHostImpl()); |
| 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(SkFloatToScalar(strokeWidth)); |
| canvas->drawRect(skRect, paint); |
| } |
| } |
| |
| const char* HeadsUpDisplayLayerImpl::layerTypeAsString() const |
| { |
| return "HeadsUpDisplayLayer"; |
| } |
| |
| } // namespace cc |