From 4aaf6cec5d5f8fb84f4ed8e9314bdf2e427044c3 Mon Sep 17 00:00:00 2001 From: George Wright Date: Thu, 4 Nov 2021 17:02:36 -0700 Subject: [PATCH 1/5] Add a new display_list_benchmarks test suite that will allow us to microbenchmark all the rasterops defined in our DisplayList format on both CPU and GPU canvases. --- BUILD.gn | 1 + display_list/BUILD.gn | 37 + display_list/display_list_benchmarks.cc | 1036 +++++++++++++++++ display_list/display_list_benchmarks.h | 323 +++++ display_list/display_list_benchmarks_gl.cc | 60 + display_list/display_list_benchmarks_metal.cc | 48 + 6 files changed, 1505 insertions(+) create mode 100644 display_list/display_list_benchmarks.cc create mode 100644 display_list/display_list_benchmarks.h create mode 100644 display_list/display_list_benchmarks_gl.cc create mode 100644 display_list/display_list_benchmarks_metal.cc diff --git a/BUILD.gn b/BUILD.gn index 1c01315f9cb46..87e1b075267f8 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -116,6 +116,7 @@ group("flutter") { # Compile all benchmark targets if enabled. if (enable_unittests && !is_win) { public_deps += [ + "//flutter/display_list:display_list_benchmarks", "//flutter/fml:fml_benchmarks", "//flutter/lib/ui:ui_benchmarks", "//flutter/shell/common:shell_benchmarks", diff --git a/display_list/BUILD.gn b/display_list/BUILD.gn index cb2722b631fff..d31ccc06966b5 100644 --- a/display_list/BUILD.gn +++ b/display_list/BUILD.gn @@ -2,6 +2,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//flutter/testing/testing.gni") + source_set("display_list") { sources = [ "display_list.cc", @@ -46,3 +48,38 @@ source_set("unittests") { public_deps = [ ":display_list" ] } + +fixtures_location("display_list_benchmarks_fixtures") { + assets_dir = "$target_gen_dir/" +} + +executable("display_list_benchmarks") { + testonly = true + + sources = [ + "display_list_benchmarks.cc", + "display_list_benchmarks.h", + ] + + deps = [ + ":display_list", + ":display_list_benchmarks_fixtures", + "//flutter/benchmarking", + "//flutter/common/graphics", + "//flutter/fml", + "//flutter/testing:skia", + "//flutter/testing:testing_lib", + "//third_party/dart/runtime:libdart_jit", # for tracing + "//third_party/skia", + ] + + if (!is_fuchsia) { + sources += [ "display_list_benchmarks_gl.cc" ] + deps += [ "//flutter/testing:opengl" ] + } + + if (is_mac) { + sources += [ "display_list_benchmarks_metal.cc" ] + deps += [ "//flutter/testing:metal" ] + } +} diff --git a/display_list/display_list_benchmarks.cc b/display_list/display_list_benchmarks.cc new file mode 100644 index 0000000000000..af4602bce729c --- /dev/null +++ b/display_list/display_list_benchmarks.cc @@ -0,0 +1,1036 @@ +// Copyright 2013 The Flutter 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 "flutter/display_list/display_list_benchmarks.h" +#include "flutter/display_list/display_list.h" + +#include "third_party/skia/include/core/SkPoint.h" +#include "third_party/skia/include/core/SkTextBlob.h" + +namespace flutter { +namespace testing { + +class SoftwareCanvasProvider : public CanvasProvider { + public: + virtual ~SoftwareCanvasProvider() = default; + void InitializeSurface(const size_t width, const size_t height) override { + surface_ = SkSurface::MakeRasterN32Premul(width, height); + surface_->getCanvas()->clear(SK_ColorTRANSPARENT); + } + + sk_sp GetSurface() override { return surface_; } + + sk_sp MakeOffscreenSurface(const size_t width, + const size_t height) override { + auto surface = SkSurface::MakeRasterN32Premul(width, height); + surface->getCanvas()->clear(SK_ColorTRANSPARENT); + return surface; + } + + const std::string BackendName() override { return "Software"; } + + private: + sk_sp surface_; +}; + +// Constants chosen to produce benchmark results in the region of 1-50ms +constexpr size_t kLinesToDraw = 10000; +constexpr size_t kRectsToDraw = 5000; +constexpr size_t kOvalsToDraw = 1000; +constexpr size_t kCirclesToDraw = 5000; +constexpr size_t kRRectsToDraw = 5000; +constexpr size_t kArcSweepSetsToDraw = 1000; +constexpr size_t kImagesToDraw = 500; +constexpr size_t kFixedCanvasSize = 1024; + +// Draw a series of diagonal lines across a square canvas of width/height of +// the length requested. The lines will start from the top left corner to the +// bottom right corner, and move from left to right (at the top) and from right +// to left (at the bottom) until 10,000 lines are drawn. +// +// The resulting image will be an hourglass shape. +void BM_DrawLine(benchmark::State& state, + std::unique_ptr canvas_provider) { + DisplayListBuilder builder; + size_t length = state.range(0); + + canvas_provider->InitializeSurface(length, length); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + for (size_t i = 0; i < kLinesToDraw; i++) { + builder.drawLine(SkPoint::Make(i % length, 0), + SkPoint::Make(length - i % length, length)); + } + + auto display_list = builder.Build(); + + // We only want to time the actual rasterization. + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawLine-" + + std::to_string(state.range(0)) + ".png"; + canvas_provider->Snapshot(filename); +} + +// Draws a series of square rects of the requested width across +// the canvas and repeats until `kRectsToDraw` rects have been drawn. +// +// Half the drawn rects will not have an integral offset. +void BM_DrawRect(benchmark::State& state, + std::unique_ptr canvas_provider) { + DisplayListBuilder builder; + size_t length = state.range(0); + size_t canvas_size = length * 2; + canvas_provider->InitializeSurface(canvas_size, canvas_size); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + // As rects have SkScalar dimensions, we want to ensure that we also + // draw rects with non-integer position and size + const SkScalar offset = 0.5f; + SkRect rect = SkRect::MakeLTRB(0, 0, length, length); + + for (size_t i = 0; i < kRectsToDraw; i++) { + builder.drawRect(rect); + rect.offset(offset, offset); + if (rect.right() > canvas_size) { + rect.offset(-canvas_size, 0); + } + if (rect.bottom() > canvas_size) { + rect.offset(0, -canvas_size); + } + } + + auto display_list = builder.Build(); + + // We only want to time the actual rasterization. + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawRect-" + + std::to_string(state.range(0)) + ".png"; + canvas_provider->Snapshot(filename); +} + +// Draws a series of ovals of the requested height with aspect ratio 3:2 across +// the canvas and repeats until `kOvalsToDraw` ovals have been drawn. +// +// Half the drawn ovals will not have an integral offset. +void BM_DrawOval(benchmark::State& state, + std::unique_ptr canvas_provider) { + DisplayListBuilder builder; + size_t length = state.range(0); + size_t canvas_size = length * 2; + canvas_provider->InitializeSurface(canvas_size, canvas_size); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + SkRect rect = SkRect::MakeXYWH(0, 0, length * 1.5f, length); + const SkScalar offset = 0.5f; + + for (size_t i = 0; i < kOvalsToDraw; i++) { + builder.drawOval(rect); + rect.offset(offset, offset); + if (rect.right() > canvas_size) { + rect.offset(-canvas_size, 0); + } + if (rect.bottom() > canvas_size) { + rect.offset(0, -canvas_size); + } + } + auto display_list = builder.Build(); + + // We only want to time the actual rasterization. + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawOval-" + + std::to_string(state.range(0)) + ".png"; + canvas_provider->Snapshot(filename); +} + +// Draws a series of circles of the requested radius across +// the canvas and repeats until `kCirclesToDraw` circles have been drawn. +// +// Half the drawn circles will not have an integral center point. +void BM_DrawCircle(benchmark::State& state, + std::unique_ptr canvas_provider) { + DisplayListBuilder builder; + size_t length = state.range(0); + size_t canvas_size = length * 2; + canvas_provider->InitializeSurface(canvas_size, canvas_size); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + SkScalar radius = length / 2.0f; + const SkScalar offset = 0.5f; + + SkPoint center = SkPoint::Make(radius, radius); + + for (size_t i = 0; i < kCirclesToDraw; i++) { + builder.drawCircle(center, radius); + center.offset(offset, offset); + if (center.x() + radius > canvas_size) { + center.set(radius, center.y()); + } + if (center.y() + radius > canvas_size) { + center.set(center.x(), radius); + } + } + auto display_list = builder.Build(); + + // We only want to time the actual rasterization. + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawCircle-" + + std::to_string(state.range(0)) + ".png"; + canvas_provider->Snapshot(filename); +} + +// Draws a series of rounded rects of the requested width across +// the canvas and repeats until `kRRectsToDraw` rects have been drawn. +// +// Half the drawn rounded rects will not have an integral offset. +void BM_DrawRRect(benchmark::State& state, + std::unique_ptr canvas_provider, + SkRRect::Type type) { + DisplayListBuilder builder; + size_t length = state.range(0); + size_t canvas_size = length * 2; + canvas_provider->InitializeSurface(canvas_size, canvas_size); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + SkVector radii[4]; + switch (type) { + case SkRRect::Type::kSimple_Type: + radii[0] = SkVector::Make(5.0f, 5.0f); + radii[1] = SkVector::Make(5.0f, 5.0f); + radii[2] = SkVector::Make(5.0f, 5.0f); + radii[3] = SkVector::Make(5.0f, 5.0f); + break; + case SkRRect::Type::kNinePatch_Type: + radii[0] = SkVector::Make(5.0f, 2.0f); + radii[1] = SkVector::Make(3.0f, 2.0f); + radii[2] = SkVector::Make(3.0f, 4.0f); + radii[3] = SkVector::Make(5.0f, 4.0f); + break; + case SkRRect::Type::kComplex_Type: + radii[0] = SkVector::Make(5.0f, 4.0f); + radii[1] = SkVector::Make(4.0f, 5.0f); + radii[2] = SkVector::Make(3.0f, 6.0f); + radii[3] = SkVector::Make(2.0f, 7.0f); + break; + default: + break; + } + + const SkScalar offset = 0.5f; + const SkScalar multiplier = length / 16.0f; + SkRRect rrect; + + SkVector set_radii[4]; + for (size_t i = 0; i < 4; i++) { + set_radii[i] = radii[i] * multiplier; + } + rrect.setRectRadii(SkRect::MakeLTRB(0, 0, length, length), set_radii); + + for (size_t i = 0; i < kRRectsToDraw; i++) { + builder.drawRRect(rrect); + rrect.offset(offset, offset); + if (rrect.rect().right() > canvas_size) { + rrect.offset(-canvas_size, 0); + } + if (rrect.rect().bottom() > canvas_size) { + rrect.offset(0, -canvas_size); + } + } + auto display_list = builder.Build(); + + // We only want to time the actual rasterization. + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawRRect-" + + std::to_string(state.range(0)) + ".png"; + canvas_provider->Snapshot(filename); +} + +void BM_DrawArc(benchmark::State& state, + std::unique_ptr canvas_provider) { + DisplayListBuilder builder; + size_t length = state.range(0); + size_t canvas_size = length * 2; + canvas_provider->InitializeSurface(canvas_size, canvas_size); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + SkScalar starting_angle = 0.0f; + SkScalar offset = 0.5f; + + // Just some random sweeps that will mostly circumnavigate the circle + std::vector segment_sweeps = {5.5f, -10.0f, 42.0f, 71.7f, 90.0f, + 37.5f, 17.9f, 32.0f, 379.4f}; + + SkRect bounds = SkRect::MakeLTRB(0, 0, length, length); + + for (size_t i = 0; i < kArcSweepSetsToDraw; i++) { + for (SkScalar sweep : segment_sweeps) { + builder.drawArc(bounds, starting_angle, sweep, false); + starting_angle += sweep + 5.0f; + } + bounds.offset(offset, offset); + if (bounds.right() > canvas_size) { + bounds.offset(-canvas_size, 0); + } + if (bounds.bottom() > canvas_size) { + bounds.offset(0, -canvas_size); + } + } + + auto display_list = builder.Build(); + + // We only want to time the actual rasterization. + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawArc-" + + std::to_string(state.range(0)) + ".png"; + canvas_provider->Snapshot(filename); +} + +// Returns a list of SkPoints that represent `n` points equally spaced out +// along the circumference of a circle with radius `r` and centered on `center`. +std::vector GetPolygonPoints(size_t n, SkPoint center, SkScalar r) { + std::vector points; + SkScalar x, y; + float angle; + float full_circle = 2.0f * M_PI; + for (size_t i = 0; i < n; i++) { + angle = (full_circle / (float)n) * (float)i; + x = center.x() + r * std::cosf(angle); + y = center.y() + r * std::sinf(angle); + points.push_back(SkPoint::Make(x, y)); + } + return points; +} + +// Creates a path that represents a regular polygon with `sides` sides, +// centered on `center` with a radius of `radius`. The control points are +// equally spaced out along the circumference of the circle described by +// `radius` and `center`. +// +// The path segment connecting each control point is a line segment. +void GetLinesPath(SkPath& path, size_t sides, SkPoint center, float radius) { + std::vector points = GetPolygonPoints(sides, center, radius); + path.moveTo(points[0]); + for (size_t i = 1; i < sides; i++) { + path.lineTo(points[i]); + } + path.lineTo(points[0]); + path.close(); +} + +// Creates a path that represents a regular polygon with `sides` sides, +// centered on `center` with a radius of `radius`. The control points are +// equally spaced out along the circumference of the circle described by +// `radius` and `center`. +// +// The path segment connecting each control point is a quad bezier, with the +// bezier control point being on a circle with 80% of `radius` and with the +// control point angle half way between the start and end point angles for the +// polygon segment. +void GetQuadsPath(SkPath& path, size_t sides, SkPoint center, float radius) { + std::vector points = GetPolygonPoints(sides, center, radius); + std::vector control_points = + GetPolygonPoints(sides * 2, center, radius * 0.8f); + + path.moveTo(points[0]); + for (size_t i = 1; i < sides; i++) { + path.quadTo(control_points[2 * i - 1], points[i]); + } + path.quadTo(control_points[2 * sides - 1], points[0]); + path.close(); +} + +// Creates a path that represents a regular polygon with `sides` sides, +// centered on `center` with a radius of `radius`. The control points are +// equally spaced out along the circumference of the circle described by +// `radius` and `center`. +// +// The path segment connecting each control point is a conic, with the +// control point being on a circle with 80% of `radius` and with the +// control point angle half way between the start and end point angles for the +// polygon segment, and the conic weight set to 3.7f. +void GetConicsPath(SkPath& path, size_t sides, SkPoint center, float radius) { + std::vector points = GetPolygonPoints(sides, center, radius); + std::vector control_points = + GetPolygonPoints(sides * 2, center, radius * 0.8f); + + path.moveTo(points[0]); + for (size_t i = 1; i < sides; i++) { + path.conicTo(control_points[2 * i - 1], points[i], 3.7f); + } + path.conicTo(control_points[2 * sides - 1], points[0], 3.7f); + path.close(); +} + +// Creates a path that represents a regular polygon with `sides` sides, +// centered on `center` with a radius of `radius`. The control points are +// equally spaced out along the circumference of the circle described by +// `radius` and `center`. +// +// The path segment connecting each control point is a cubic, with the first +// control point being on a circle with 80% of `radius` and with the second +// control point being on a circle with 120% of `radius`. The first +// control point is 1/3, and the second control point is 2/3, of the angle +// between the start and end point angles for the polygon segment. +void GetCubicsPath(SkPath& path, size_t sides, SkPoint center, float radius) { + std::vector points = GetPolygonPoints(sides, center, radius); + std::vector inner_control_points = + GetPolygonPoints(sides * 3, center, radius * 0.8f); + std::vector outer_control_points = + GetPolygonPoints(sides * 3, center, radius * 1.2f); + + path.moveTo(points[0]); + for (size_t i = 1; i < sides; i++) { + path.cubicTo(inner_control_points[3 * i - 2], + outer_control_points[3 * i - 1], points[i]); + } + path.cubicTo(inner_control_points[3 * sides - 2], + outer_control_points[3 * sides - 1], points[0]); + path.close(); +} + +// Returns a path generated by one of the above path generators +// which is multiplied `number` times centered on each of the `number` control +// points along the circumference of a circle centered on `center` with radius +// `radius`. +// +// Each of the polygons will have `sides` sides, and the resulting path will be +// bounded by a circle with radius of 150% of `radius` (or another 20% on top of +// that for cubics) +void MultiplyPath(SkPath& path, + SkPath::Verb type, + SkPoint center, + size_t sides, + size_t number, + float radius) { + std::vector center_points = + GetPolygonPoints(number, center, radius / 2.0f); + + for (SkPoint p : center_points) { + switch (type) { + case SkPath::Verb::kLine_Verb: + GetLinesPath(path, sides, p, radius); + break; + case SkPath::Verb::kQuad_Verb: + GetQuadsPath(path, sides, p, radius); + break; + case SkPath::Verb::kConic_Verb: + GetConicsPath(path, sides, p, radius); + break; + case SkPath::Verb::kCubic_Verb: + GetCubicsPath(path, sides, p, radius); + break; + default: + break; + } + } +} + +std::string VerbToString(SkPath::Verb type) { + switch (type) { + case SkPath::Verb::kLine_Verb: + return "Lines"; + case SkPath::Verb::kQuad_Verb: + return "Quads"; + case SkPath::Verb::kConic_Verb: + return "Conics"; + case SkPath::Verb::kCubic_Verb: + return "Cubics"; + default: + return "Unknown"; + } +} + +// Draws a series of overlapping 20-sided polygons where the path segment +// between each point is one of the verb types defined in SkPath. +// +// The number of polygons drawn will be varied to get an overall path +// with approximately 20*N verbs, so we can get an idea of the fixed +// cost of using drawPath as well as an idea of how the cost varies according +// to the verb count. +void BM_DrawPath(benchmark::State& state, + std::unique_ptr canvas_provider, + SkPath::Verb type) { + DisplayListBuilder builder; + size_t length = kFixedCanvasSize; + canvas_provider->InitializeSurface(length, length); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + SkPath path; + + std::string label = VerbToString(type); + SkPoint center = SkPoint::Make(length / 2.0f, length / 2.0f); + float radius = length * 0.25f; + state.SetComplexityN(state.range(0)); + + MultiplyPath(path, type, center, 20, state.range(0), radius); + + state.counters["VerbCount"] = path.countVerbs(); + + builder.drawPath(path); + auto display_list = builder.Build(); + + // We only want to time the actual rasterization. + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawPath-" + label + "-" + + std::to_string(state.range(0)) + ".png"; + canvas_provider->Snapshot(filename); +} + +// Returns a set of vertices that describe a circle that has a +// radius of `radius` and total vertex count of approximately +// `vertex_count`. The final number of vertices may differ as +// we always return an even number of vertices from this method. +// +// The resulting vertices will describe a disc consisting of a series +// of triangles with two vertices on the circumference of the disc, +// and the final vertex being the center point of the disc. +// +// Each vertex colour will alternate through Red, Green, Blue and Cyan. +sk_sp GetTestVertices(SkPoint center, + float radius, + size_t vertex_count, + SkVertices::VertexMode mode, + size_t& final_vertex_count) { + size_t outer_vertex_count = vertex_count / 2; + std::vector outer_points = + GetPolygonPoints(outer_vertex_count, center, radius); + + std::vector vertices; + std::vector colors; + + switch (mode) { + case SkVertices::VertexMode::kTriangleFan_VertexMode: + vertices.push_back(center); + colors.push_back(SK_ColorCYAN); + for (size_t i = 0; i <= outer_points.size(); i++) { + vertices.push_back(outer_points[i % outer_points.size()]); + if (i % 3 == 0) + colors.push_back(SK_ColorRED); + else if (i % 3 == 1) + colors.push_back(SK_ColorGREEN); + else + colors.push_back(SK_ColorBLUE); + } + break; + case SkVertices::VertexMode::kTriangles_VertexMode: + case SkVertices::VertexMode::kTriangleStrip_VertexMode: + for (size_t i = 0; i <= outer_vertex_count; i++) { + vertices.push_back(outer_points[i % outer_points.size()]); + colors.push_back(i % 2 ? SK_ColorRED : SK_ColorGREEN); + vertices.push_back(center); + colors.push_back(i % 2 ? SK_ColorBLUE : SK_ColorCYAN); + } + break; + default: + break; + } + + final_vertex_count = vertices.size(); + return SkVertices::MakeCopy(mode, vertices.size(), vertices.data(), nullptr, + colors.data()); +} + +std::string VertexModeToString(SkVertices::VertexMode mode) { + switch (mode) { + case SkVertices::VertexMode::kTriangleStrip_VertexMode: + return "TriangleStrip"; + case SkVertices::VertexMode::kTriangleFan_VertexMode: + return "TriangleFan"; + case SkVertices::VertexMode::kTriangles_VertexMode: + return "Triangles"; + } + return "Unknown"; +} + +// Draws a series of discs generated by `GetTestVertices()` with +// 50 vertices in each disc. The number of discs drawn will vary according +// to the benchmark input, and the benchmark will automatically calculate +// the Big-O complexity of `DrawVertices` with N being the number of vertices +// being drawn. +// +// The discs drawn will be centered on points along a circle with radius of 25% +// of the canvas width/height, with each point being equally spaced out. +void BM_DrawVertices(benchmark::State& state, + std::unique_ptr canvas_provider, + SkVertices::VertexMode mode) { + DisplayListBuilder builder; + size_t length = kFixedCanvasSize; + canvas_provider->InitializeSurface(length, length); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + SkPoint center = SkPoint::Make(length / 2.0f, length / 2.0f); + + float radius = length / 4.0f; + + size_t vertex_count, total_vertex_count = 0; + + std::vector center_points = + GetPolygonPoints(state.range(0), center, radius / 4.0f); + + for (SkPoint p : center_points) { + sk_sp vertices = + GetTestVertices(p, radius, 50, mode, vertex_count); + total_vertex_count += vertex_count; + builder.drawVertices(vertices, SkBlendMode::kSrc); + } + + state.counters["VertexCount"] = total_vertex_count; + state.SetComplexityN(total_vertex_count); + + auto display_list = builder.Build(); + + // We only want to time the actual rasterization. + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawVertices-" + + VertexModeToString(mode) + ".png"; + canvas_provider->Snapshot(filename); +} + +// Generate `count` test points. +// +// The points are distributed using some fixed constant offsets that were +// chosen to appear somewhat random. +// +// The points generated will wrap in x and y for the bounds of `canvas_size`. +std::vector GetTestPoints(size_t count, SkISize canvas_size) { + std::vector points; + + // Some arbitrary offsets to use when building the list of points + std::vector delta_x = {10.0f, 6.3f, 15.0f, 3.5f, 22.6f, 4.7f}; + std::vector delta_y = {9.3f, -5.4f, 8.5f, -12.0f, 19.2f, -19.6f}; + + SkPoint current = SkPoint::Make(0.0f, 0.0f); + for (size_t i = 0; i < count; i++) { + points.push_back(current); + current.offset(delta_x[i % delta_x.size()], delta_y[i % delta_y.size()]); + if (current.x() > canvas_size.width()) { + current.offset(-canvas_size.width(), 25.0f); + } + if (current.y() > canvas_size.height()) { + current.offset(0.0f, -canvas_size.height()); + } + } + + return points; +} + +std::string PointModeToString(SkCanvas::PointMode mode) { + switch (mode) { + case SkCanvas::kLines_PointMode: + return "Lines"; + case SkCanvas::kPolygon_PointMode: + return "Polygon"; + case SkCanvas::kPoints_PointMode: + default: + return "Points"; + } +} + +// Draws a series of points generated by `GetTestPoints()` above to +// a fixed-size canvas. The benchmark will vary the number of points drawn, +// and they can be drawn in one of three modes - Lines, Polygon or Points mode. +// +// This benchmark will automatically calculate the Big-O complexity of +// `DrawPoints` with N being the number of points being drawn. +void BM_DrawPoints(benchmark::State& state, + std::unique_ptr canvas_provider, + SkCanvas::PointMode mode) { + DisplayListBuilder builder; + size_t length = kFixedCanvasSize; + canvas_provider->InitializeSurface(length, length); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + size_t point_count = state.range(0); + state.SetComplexityN(point_count); + state.counters["PointCount"] = point_count; + + std::vector points = + GetTestPoints(point_count, SkISize::Make(length, length)); + builder.drawPoints(mode, points.size(), points.data()); + + auto display_list = builder.Build(); + + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawPoints-" + + PointModeToString(mode) + "-" + std::to_string(point_count) + + ".png"; + canvas_provider->Snapshot(filename); +} + +sk_sp ImageFromBitmapWithNewID(const SkBitmap& bitmap) { + // If we create an SkPixmap with a ref to the SkBitmap's pixel data, + // then create an SkImage from that, we always get a new generation ID, + // so we will avoid hitting the cache. + SkPixmap pixmap; + bitmap.peekPixels(&pixmap); + return SkImage::MakeFromRaster(pixmap, nullptr, nullptr); +} + +// Draws `kImagesToDraw` bitmaps to a canvas, either with texture-backed +// bitmaps or bitmaps that need to be uploaded to the GPU first. +void BM_DrawImage(benchmark::State& state, + std::unique_ptr canvas_provider, + const SkSamplingOptions& options, + bool upload_bitmap) { + DisplayListBuilder builder; + size_t bitmap_size = state.range(0); + size_t canvas_size = 2 * bitmap_size; + canvas_provider->InitializeSurface(canvas_size, canvas_size); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + sk_sp image; + sk_sp offscreen; + SkBitmap bitmap; + + if (upload_bitmap) { + SkImageInfo info = SkImageInfo::Make(bitmap_size, bitmap_size, + SkColorType::kRGBA_8888_SkColorType, + SkAlphaType::kPremul_SkAlphaType); + bitmap.allocPixels(info, 0); + bitmap.eraseColor(SK_ColorBLUE); + } else { + offscreen = canvas_provider->MakeOffscreenSurface(bitmap_size, bitmap_size); + offscreen->getCanvas()->clear(SK_ColorRED); + } + + SkScalar offset = 0.5f; + SkPoint dst = SkPoint::Make(0, 0); + + for (size_t i = 0; i < kImagesToDraw; i++) { + image = upload_bitmap ? ImageFromBitmapWithNewID(bitmap) + : offscreen->makeImageSnapshot(); + builder.drawImage(image, dst, options, true); + + dst.offset(offset, offset); + if (dst.x() + bitmap_size > canvas_size) { + dst.set(0, dst.y()); + } + if (dst.y() + bitmap_size > canvas_size) { + dst.set(dst.x(), 0); + } + } + + auto display_list = builder.Build(); + + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawImage-" + + (upload_bitmap ? "Upload-" : "Texture-") + + std::to_string(bitmap_size) + ".png"; + canvas_provider->Snapshot(filename); +} + +std::string ConstraintToString(SkCanvas::SrcRectConstraint constraint) { + switch (constraint) { + case SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint: + return "Strict"; + case SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint: + return "Fast"; + default: + return "Unknown"; + } +} + +// Draws `kImagesToDraw` bitmaps to a canvas, either with texture-backed +// bitmaps or bitmaps that need to be uploaded to the GPU first. +// +// The bitmaps are shrunk down to 75% of their size when rendered to the canvas. +void BM_DrawImageRect(benchmark::State& state, + std::unique_ptr canvas_provider, + const SkSamplingOptions& options, + SkCanvas::SrcRectConstraint constraint, + bool upload_bitmap) { + DisplayListBuilder builder; + size_t bitmap_size = state.range(0); + size_t canvas_size = 2 * bitmap_size; + canvas_provider->InitializeSurface(canvas_size, canvas_size); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + sk_sp image; + sk_sp offscreen; + SkBitmap bitmap; + + if (upload_bitmap) { + SkImageInfo info = SkImageInfo::Make(bitmap_size, bitmap_size, + SkColorType::kRGBA_8888_SkColorType, + SkAlphaType::kPremul_SkAlphaType); + bitmap.allocPixels(info, 0); + bitmap.eraseColor(SK_ColorBLUE); + } else { + offscreen = canvas_provider->MakeOffscreenSurface(bitmap_size, bitmap_size); + offscreen->getCanvas()->clear(SK_ColorRED); + } + + SkScalar offset = 0.5f; + SkRect src = SkRect::MakeXYWH(bitmap_size / 4.0f, bitmap_size / 4.0f, + bitmap_size / 2.0f, bitmap_size / 2.0f); + SkRect dst = + SkRect::MakeXYWH(0.0f, 0.0f, bitmap_size * 0.75f, bitmap_size * 0.75f); + + for (size_t i = 0; i < kImagesToDraw; i++) { + image = upload_bitmap ? ImageFromBitmapWithNewID(bitmap) + : offscreen->makeImageSnapshot(); + builder.drawImageRect(image, src, dst, options, true, constraint); + dst.offset(offset, offset); + if (dst.right() > canvas_size) { + dst.offsetTo(0, dst.y()); + } + if (dst.bottom() > canvas_size) { + dst.offsetTo(dst.x(), 0); + } + } + + auto display_list = builder.Build(); + + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawImageRect-" + + (upload_bitmap ? "Upload-" : "Texture-") + + ConstraintToString(constraint) + "-" + + std::to_string(bitmap_size) + ".png"; + canvas_provider->Snapshot(filename); +} + +std::string FilterModeToString(const SkFilterMode mode) { + switch (mode) { + case SkFilterMode::kNearest: + return "Nearest"; + case SkFilterMode::kLinear: + return "Linear"; + default: + return "Unknown"; + } +} + +// Draws `kImagesToDraw` bitmaps to a canvas, either with texture-backed +// bitmaps or bitmaps that need to be uploaded to the GPU first. +// +// The image is split into 9 sub-rects and stretched proportionally for final +// rendering. +void BM_DrawImageNine(benchmark::State& state, + std::unique_ptr canvas_provider, + const SkFilterMode filter, + bool upload_bitmap) { + DisplayListBuilder builder; + size_t bitmap_size = state.range(0); + size_t canvas_size = 2 * bitmap_size; + canvas_provider->InitializeSurface(canvas_size, canvas_size); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + SkIRect center = SkIRect::MakeXYWH(bitmap_size / 4, bitmap_size / 4, + bitmap_size / 2, bitmap_size / 2); + + sk_sp image; + sk_sp offscreen; + SkBitmap bitmap; + + if (upload_bitmap) { + SkImageInfo info = SkImageInfo::Make(bitmap_size, bitmap_size, + SkColorType::kRGBA_8888_SkColorType, + SkAlphaType::kPremul_SkAlphaType); + bitmap.allocPixels(info, 0); + bitmap.eraseColor(SK_ColorBLUE); + } else { + offscreen = canvas_provider->MakeOffscreenSurface(bitmap_size, bitmap_size); + offscreen->getCanvas()->clear(SK_ColorRED); + } + + SkScalar offset = 0.5f; + SkRect dst = + SkRect::MakeXYWH(0.0f, 0.0f, bitmap_size * 0.75f, bitmap_size * 0.75f); + + for (size_t i = 0; i < kImagesToDraw; i++) { + image = upload_bitmap ? ImageFromBitmapWithNewID(bitmap) + : offscreen->makeImageSnapshot(); + builder.drawImageNine(image, center, dst, filter, true); + dst.offset(offset, offset); + if (dst.right() > canvas_size) { + dst.offsetTo(0, dst.y()); + } + if (dst.bottom() > canvas_size) { + dst.offsetTo(dst.x(), 0); + } + } + + auto display_list = builder.Build(); + + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawImageNine-" + + (upload_bitmap ? "Upload-" : "Texture-") + + FilterModeToString(filter) + "-" + + std::to_string(bitmap_size) + ".png"; + canvas_provider->Snapshot(filename); +} + +// Draws a series of glyph runs with 32 glyphs in each run. The number of runs +// may vary according to the benchmark parameters. The text will start in the +// upper left corner of the canvas and advance from left to right and wrap at +// the canvas boundaries in both x and y. +// +// This benchmark will automatically calculate the Big-O complexity of +// `DrawTextBlob` with N being the number of glyphs being drawn. +void BM_DrawTextBlob(benchmark::State& state, + std::unique_ptr canvas_provider) { + DisplayListBuilder builder; + size_t glyph_runs = state.range(0); + size_t canvas_size = kFixedCanvasSize; + canvas_provider->InitializeSurface(canvas_size, canvas_size); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + // We're just using plain Latin-1 where glyph count == character count + const char* string_fragment = "This text has exactly 32 glyphs."; + size_t fragment_length = strlen(string_fragment); + state.SetComplexityN(glyph_runs * fragment_length); + + // TODO(gw280): different fonts + SkFont font; + + auto blob_fragment = SkTextBlob::MakeFromString(string_fragment, font); + auto bounds = blob_fragment->bounds(); + + // Calculate the approximate number of these glyph runs we can fit on a single + // canvas. + size_t x_count_max = canvas_size / bounds.width(); + size_t y_count_max = canvas_size / bounds.height(); + size_t remaining_runs = glyph_runs; + + SkTextBlobBuilder blob_builder; + size_t current_y = 0; + while (remaining_runs > 0) { + size_t runs_this_pass = std::min(x_count_max, remaining_runs); + auto buffer = blob_builder.allocRun( + font, runs_this_pass * fragment_length, 0, + ((current_y % y_count_max) + 1) * bounds.height()); + for (size_t i = 0; i < runs_this_pass; i++) { + font.textToGlyphs(string_fragment, fragment_length, SkTextEncoding::kUTF8, + buffer.glyphs + (i * fragment_length), fragment_length); + } + remaining_runs -= runs_this_pass; + current_y++; + } + + auto blob = blob_builder.make(); + + builder.drawTextBlob(blob, 0.0f, 0.0f); + + auto display_list = builder.Build(); + + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawTextBlob-" + + std::to_string(glyph_runs * fragment_length) + ".png"; + canvas_provider->Snapshot(filename); +} + +// Draw the shadow for a 10-sided regular polygon where the polygon's +// sides are denoted by one of a Line, Quad, Conic or Cubic path segment. +// +// The elevation of the light source will vary according to the benchmark +// paremeters. +// +// The benchmark can be run with either a transparent occluder or an opaque +// occluder. +void BM_DrawShadow(benchmark::State& state, + std::unique_ptr canvas_provider, + bool transparent_occluder, + SkPath::Verb type) { + DisplayListBuilder builder; + size_t length = kFixedCanvasSize; + canvas_provider->InitializeSurface(length, length); + auto canvas = canvas_provider->GetSurface()->getCanvas(); + + SkPath path; + + SkPoint center = SkPoint::Make(length / 2.0f, length / 2.0f); + float radius = length * 0.25f; + + switch (type) { + case SkPath::Verb::kLine_Verb: + GetLinesPath(path, 10, center, radius); + break; + case SkPath::Verb::kQuad_Verb: + GetQuadsPath(path, 10, center, radius); + break; + case SkPath::Verb::kConic_Verb: + GetConicsPath(path, 10, center, radius); + break; + case SkPath::Verb::kCubic_Verb: + GetCubicsPath(path, 10, center, radius); + break; + default: + break; + } + + float elevation = state.range(0); + + // We can hardcode dpr to 1.0f as we're varying elevation, and dpr is only + // ever used in conjunction with elevation. + builder.drawShadow(path, SK_ColorBLUE, elevation, transparent_occluder, 1.0f); + auto display_list = builder.Build(); + + // We only want to time the actual rasterization. + for (auto _ : state) { + display_list->RenderTo(canvas); + canvas_provider->GetSurface()->flushAndSubmit(true); + } + + auto filename = canvas_provider->BackendName() + "-DrawShadow-" + + VerbToString(type) + "-" + + (transparent_occluder ? "Transparent-" : "Opaque-") + + std::to_string(elevation) + "-" + ".png"; + canvas_provider->Snapshot(filename); +} + +RUN_DISPLAYLIST_BENCHMARKS(Software) + +} // namespace testing +} // namespace flutter diff --git a/display_list/display_list_benchmarks.h b/display_list/display_list_benchmarks.h new file mode 100644 index 0000000000000..148f858c6145b --- /dev/null +++ b/display_list/display_list_benchmarks.h @@ -0,0 +1,323 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FLOW_DISPLAY_LIST_BENCHMARKS_H_ +#define FLUTTER_FLOW_DISPLAY_LIST_BENCHMARKS_H_ + +#include "flutter/benchmarking/benchmarking.h" +#include "flutter/fml/mapping.h" +#include "flutter/testing/testing.h" + +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/core/SkVertices.h" + +namespace flutter { + +namespace testing { + +class CanvasProvider { + public: + virtual ~CanvasProvider() = default; + virtual const std::string BackendName() = 0; + virtual void InitializeSurface(const size_t width, const size_t height) = 0; + virtual sk_sp GetSurface() = 0; + virtual sk_sp MakeOffscreenSurface(const size_t width, + const size_t height) = 0; + + virtual bool Snapshot(std::string filename) { + auto image = GetSurface()->makeImageSnapshot(); + if (!image) { + return false; + } + auto raster = image->makeRasterImage(); + if (!raster) { + return false; + } + auto data = raster->encodeToData(); + if (!data) { + return false; + } + fml::NonOwnedMapping mapping(static_cast(data->data()), + data->size()); + return WriteAtomically(OpenFixturesDirectory(), filename.c_str(), mapping); + } +}; + +// Benchmarks + +void BM_DrawLine(benchmark::State& state, + std::unique_ptr canvas_provider); +void BM_DrawRect(benchmark::State& state, + std::unique_ptr canvas_provider); +void BM_DrawCircle(benchmark::State& state, + std::unique_ptr canvas_provider); +void BM_DrawOval(benchmark::State& state, + std::unique_ptr canvas_provider); +void BM_DrawArc(benchmark::State& state, + std::unique_ptr canvas_provider); +void BM_DrawRRect(benchmark::State& state, + std::unique_ptr canvas_provider, + SkRRect::Type type); +void BM_DrawPath(benchmark::State& state, + std::unique_ptr canvas_provider, + SkPath::Verb type); +void BM_DrawPoints(benchmark::State& state, + std::unique_ptr canvas_provider, + SkCanvas::PointMode mode); +void BM_DrawVertices(benchmark::State& state, + std::unique_ptr canvas_provider, + SkVertices::VertexMode mode); +void BM_DrawImage(benchmark::State& state, + std::unique_ptr canvas_provider, + const SkSamplingOptions& options, + bool upload_bitmap); +void BM_DrawImageRect(benchmark::State& state, + std::unique_ptr canvas_provider, + const SkSamplingOptions& options, + SkCanvas::SrcRectConstraint constraint, + bool upload_bitmap); +void BM_DrawImageNine(benchmark::State& state, + std::unique_ptr canvas_provider, + const SkFilterMode filter, + bool upload_bitmap); +void BM_DrawTextBlob(benchmark::State& state, + std::unique_ptr canvas_provider); +void BM_DrawShadow(benchmark::State& state, + std::unique_ptr canvas_provider, + bool transparent_occluder, + SkPath::Verb type); + +// clang-format off + +#define RUN_DISPLAYLIST_BENCHMARKS(BACKEND) \ + \ + BENCHMARK_CAPTURE(BM_DrawLine, BACKEND, \ + std::make_unique()) \ + ->RangeMultiplier(2) \ + ->Range(16, 2048) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawRect, BACKEND, \ + std::make_unique()) \ + ->RangeMultiplier(2) \ + ->Range(16, 2048) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawOval, BACKEND, \ + std::make_unique()) \ + ->RangeMultiplier(2) \ + ->Range(16, 2048) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawCircle, BACKEND, \ + std::make_unique()) \ + ->RangeMultiplier(2) \ + ->Range(16, 2048) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawArc, BACKEND, \ + std::make_unique()) \ + ->RangeMultiplier(2) \ + ->Range(128, 2048) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawPoints, Points/BACKEND, \ + std::make_unique(), \ + SkCanvas::kPoints_PointMode) \ + ->RangeMultiplier(2) \ + ->Range(1024, 32768) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawPoints, Lines/BACKEND, \ + std::make_unique(), \ + SkCanvas::kLines_PointMode) \ + ->RangeMultiplier(2) \ + ->Range(1024, 32768) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawPoints, Polygon/BACKEND, \ + std::make_unique(), \ + SkCanvas::kPolygon_PointMode) \ + ->RangeMultiplier(2) \ + ->Range(1024, 32768) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawRRect, Symmetric/BACKEND, \ + std::make_unique(), \ + SkRRect::Type::kSimple_Type) \ + ->RangeMultiplier(2) \ + ->Range(16, 2048) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawRRect, NinePatch/BACKEND, \ + std::make_unique(), \ + SkRRect::Type::kNinePatch_Type) \ + ->RangeMultiplier(2) \ + ->Range(16, 2048) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawRRect, Complex/BACKEND, \ + std::make_unique(), \ + SkRRect::Type::kComplex_Type) \ + ->RangeMultiplier(2) \ + ->Range(16, 2048) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawImage, Texture/BACKEND, \ + std::make_unique(), \ + SkSamplingOptions(), false) \ + ->RangeMultiplier(2) \ + ->Range(128, 1024) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawImage, Upload/BACKEND, \ + std::make_unique(), \ + SkSamplingOptions(), true) \ + ->RangeMultiplier(2) \ + ->Range(128, 1024) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE( \ + BM_DrawImageRect, Texture/Strict/BACKEND, \ + std::make_unique(), SkSamplingOptions(), \ + SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint, false) \ + ->RangeMultiplier(2) \ + ->Range(32, 256) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE( \ + BM_DrawImageRect, Upload/Fast/BACKEND, \ + std::make_unique(), SkSamplingOptions(), \ + SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint, true) \ + ->RangeMultiplier(2) \ + ->Range(32, 256) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawImageNine, Texture/Nearest/BACKEND, \ + std::make_unique(), \ + SkFilterMode::kNearest, false) \ + ->RangeMultiplier(2) \ + ->Range(32, 256) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawImageNine, Upload/Nearest/BACKEND, \ + std::make_unique(), \ + SkFilterMode::kNearest, true) \ + ->RangeMultiplier(2) \ + ->Range(32, 256) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawImageNine, Texture/Linear/BACKEND, \ + std::make_unique(), \ + SkFilterMode::kLinear, false) \ + ->RangeMultiplier(2) \ + ->Range(32, 256) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawImageNine, Upload/Linear/BACKEND, \ + std::make_unique(), \ + SkFilterMode::kLinear, true) \ + ->RangeMultiplier(2) \ + ->Range(32, 256) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawTextBlob, BACKEND, \ + std::make_unique()) \ + ->RangeMultiplier(2) \ + ->Range(1, 256) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond) \ + ->Complexity(); \ + \ + BENCHMARK_CAPTURE(BM_DrawShadow, Lines/Transparent/BACKEND, \ + std::make_unique(), true, \ + SkPath::Verb::kLine_Verb) \ + ->RangeMultiplier(2) \ + ->Range(1, 32) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawShadow, Quads/Transparent/BACKEND, \ + std::make_unique(), true, \ + SkPath::Verb::kQuad_Verb) \ + ->RangeMultiplier(2) \ + ->Range(1, 32) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawShadow, Conics/Transparent/BACKEND, \ + std::make_unique(), true, \ + SkPath::Verb::kConic_Verb) \ + ->RangeMultiplier(2) \ + ->Range(1, 32) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawShadow, Cubics/Transparent/BACKEND, \ + std::make_unique(), true, \ + SkPath::Verb::kCubic_Verb) \ + ->RangeMultiplier(2) \ + ->Range(1, 32) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawShadow, Lines/Opaque/BACKEND, \ + std::make_unique(), false, \ + SkPath::Verb::kLine_Verb) \ + ->RangeMultiplier(2) \ + ->Range(1, 32) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawShadow, Quads/Opaque/BACKEND, \ + std::make_unique(), false, \ + SkPath::Verb::kQuad_Verb) \ + ->RangeMultiplier(2) \ + ->Range(1, 32) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawShadow, Conics/Opaque/BACKEND, \ + std::make_unique(), false, \ + SkPath::Verb::kConic_Verb) \ + ->RangeMultiplier(2) \ + ->Range(1, 32) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE(BM_DrawShadow, Cubics/Opaque/BACKEND, \ + std::make_unique(), false, \ + SkPath::Verb::kCubic_Verb) \ + ->RangeMultiplier(2) \ + ->Range(1, 32) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); + +// clang-format on + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_FLOW_DISPLAY_LIST_BENCHMARKS_H_ diff --git a/display_list/display_list_benchmarks_gl.cc b/display_list/display_list_benchmarks_gl.cc new file mode 100644 index 0000000000000..21afbc42f0b31 --- /dev/null +++ b/display_list/display_list_benchmarks_gl.cc @@ -0,0 +1,60 @@ +// Copyright 2013 The Flutter 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 "flutter/display_list/display_list_benchmarks.h" +#include "flutter/testing/test_gl_surface.h" + +#include "third_party/skia/include/core/SkCanvas.h" + +namespace flutter { +namespace testing { + +class OpenGLCanvasProvider : public CanvasProvider { + public: + virtual ~OpenGLCanvasProvider() = default; + void InitializeSurface(const size_t width, const size_t height) override { + surface_size_ = SkISize::Make(width, height); + + gl_surface_ = std::make_unique(surface_size_); + gl_surface_->MakeCurrent(); + + const auto image_info = SkImageInfo::MakeN32Premul(surface_size_); + surface_ = SkSurface::MakeRenderTarget( + gl_surface_->GetGrContext().get(), SkBudgeted::kNo, image_info, 1, + kTopLeft_GrSurfaceOrigin, nullptr, false); + surface_->getCanvas()->clear(SK_ColorTRANSPARENT); + } + + sk_sp GetSurface() override { + if (!gl_surface_->MakeCurrent()) { + return nullptr; + } + return surface_; + } + + sk_sp MakeOffscreenSurface(const size_t width, + const size_t height) override { + surface_size_ = SkISize::Make(width, height); + const auto image_info = SkImageInfo::MakeN32Premul(surface_size_); + + auto offscreen_surface = SkSurface::MakeRenderTarget( + gl_surface_->GetGrContext().get(), SkBudgeted::kNo, image_info, 1, + kTopLeft_GrSurfaceOrigin, nullptr, false); + + offscreen_surface->getCanvas()->clear(SK_ColorTRANSPARENT); + return offscreen_surface; + } + + const std::string BackendName() override { return "OpenGL"; } + + private: + SkISize surface_size_; + sk_sp surface_; + std::unique_ptr gl_surface_; +}; + +RUN_DISPLAYLIST_BENCHMARKS(OpenGL) + +} // namespace testing +} // namespace flutter diff --git a/display_list/display_list_benchmarks_metal.cc b/display_list/display_list_benchmarks_metal.cc new file mode 100644 index 0000000000000..7e4b10adcb14e --- /dev/null +++ b/display_list/display_list_benchmarks_metal.cc @@ -0,0 +1,48 @@ +// Copyright 2013 The Flutter 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 "flutter/display_list/display_list_benchmarks.h" +#include "flutter/testing/test_metal_surface.h" + +#include "third_party/skia/include/core/SkCanvas.h" + +namespace flutter { +namespace testing { + +class MetalCanvasProvider : public CanvasProvider { + public: + virtual ~MetalCanvasProvider() = default; + void InitializeSurface(const size_t width, const size_t height) override { + metal_context_ = std::make_unique(); + metal_surface_ = + TestMetalSurface::Create(*metal_context_, SkISize::Make(width, height)); + metal_surface_->GetSurface()->getCanvas()->clear(SK_ColorTRANSPARENT); + } + + sk_sp GetSurface() override { + if (!metal_surface_) { + return nullptr; + } + return metal_surface_->GetSurface(); + } + + sk_sp MakeOffscreenSurface(const size_t width, + const size_t height) override { + metal_offscreen_surface_ = + TestMetalSurface::Create(*metal_context_, SkISize::Make(width, height)); + return metal_offscreen_surface_->GetSurface(); + } + + const std::string BackendName() override { return "Metal"; } + + private: + std::unique_ptr metal_context_; + std::unique_ptr metal_surface_; + std::unique_ptr metal_offscreen_surface_; +}; + +RUN_DISPLAYLIST_BENCHMARKS(Metal) + +} // namespace testing +} // namespace flutter From d764d36dcf8dd7f126b80840cb9eef0d23c0c2cb Mon Sep 17 00:00:00 2001 From: George Wright Date: Thu, 23 Dec 2021 11:03:48 -0800 Subject: [PATCH 2/5] Only enable the display_list_benchmarks target if enable_unittests is on --- display_list/BUILD.gn | 52 ++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/display_list/BUILD.gn b/display_list/BUILD.gn index d31ccc06966b5..267d1ba8b7e56 100644 --- a/display_list/BUILD.gn +++ b/display_list/BUILD.gn @@ -53,33 +53,35 @@ fixtures_location("display_list_benchmarks_fixtures") { assets_dir = "$target_gen_dir/" } -executable("display_list_benchmarks") { - testonly = true +if (enable_unittests) { + executable("display_list_benchmarks") { + testonly = true - sources = [ - "display_list_benchmarks.cc", - "display_list_benchmarks.h", - ] + sources = [ + "display_list_benchmarks.cc", + "display_list_benchmarks.h", + ] - deps = [ - ":display_list", - ":display_list_benchmarks_fixtures", - "//flutter/benchmarking", - "//flutter/common/graphics", - "//flutter/fml", - "//flutter/testing:skia", - "//flutter/testing:testing_lib", - "//third_party/dart/runtime:libdart_jit", # for tracing - "//third_party/skia", - ] + deps = [ + ":display_list", + ":display_list_benchmarks_fixtures", + "//flutter/benchmarking", + "//flutter/common/graphics", + "//flutter/fml", + "//flutter/testing:skia", + "//flutter/testing:testing_lib", + "//third_party/dart/runtime:libdart_jit", # for tracing + "//third_party/skia", + ] - if (!is_fuchsia) { - sources += [ "display_list_benchmarks_gl.cc" ] - deps += [ "//flutter/testing:opengl" ] - } + if (!is_fuchsia) { + sources += [ "display_list_benchmarks_gl.cc" ] + deps += [ "//flutter/testing:opengl" ] + } - if (is_mac) { - sources += [ "display_list_benchmarks_metal.cc" ] - deps += [ "//flutter/testing:metal" ] + if (is_mac) { + sources += [ "display_list_benchmarks_metal.cc" ] + deps += [ "//flutter/testing:metal" ] + } } -} +} \ No newline at end of file From 9f2a3ddd57c27423bfbaf859f6e87ce1b2565a9b Mon Sep 17 00:00:00 2001 From: George Wright Date: Thu, 23 Dec 2021 11:28:01 -0800 Subject: [PATCH 3/5] Review updates for DrawVertices Clean up test definitions, add missing ones Add labels to test definitions --- display_list/BUILD.gn | 2 +- display_list/display_list_benchmarks.cc | 38 +++++-- display_list/display_list_benchmarks.h | 130 ++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 8 deletions(-) diff --git a/display_list/BUILD.gn b/display_list/BUILD.gn index 267d1ba8b7e56..13aa1bb3b5c4f 100644 --- a/display_list/BUILD.gn +++ b/display_list/BUILD.gn @@ -84,4 +84,4 @@ if (enable_unittests) { deps += [ "//flutter/testing:metal" ] } } -} \ No newline at end of file +} diff --git a/display_list/display_list_benchmarks.cc b/display_list/display_list_benchmarks.cc index af4602bce729c..031ecbc8b9ab6 100644 --- a/display_list/display_list_benchmarks.cc +++ b/display_list/display_list_benchmarks.cc @@ -505,9 +505,11 @@ void BM_DrawPath(benchmark::State& state, } // Returns a set of vertices that describe a circle that has a -// radius of `radius` and total vertex count of approximately -// `vertex_count`. The final number of vertices may differ as -// we always return an even number of vertices from this method. +// radius of `radius` and outer vertex count of approximately +// `vertex_count`. The final number of vertices will differ as we +// need to ensure the correct usage of vertices to ensure we do not +// request degenerate triangles be drawn. This final count is output +// through `final_vertex_count`. // // The resulting vertices will describe a disc consisting of a series // of triangles with two vertices on the circumference of the disc, @@ -528,6 +530,9 @@ sk_sp GetTestVertices(SkPoint center, switch (mode) { case SkVertices::VertexMode::kTriangleFan_VertexMode: + // Calling the points on the outer circle O_0, O_1, O_2, ..., and + // the center point C, this should create a triangle fan with vertices + // C, O_0, O_1, O_2, O_3, ... vertices.push_back(center); colors.push_back(SK_ColorCYAN); for (size_t i = 0; i <= outer_points.size(); i++) { @@ -541,12 +546,29 @@ sk_sp GetTestVertices(SkPoint center, } break; case SkVertices::VertexMode::kTriangles_VertexMode: + // Calling the points on the outer circle O_0, O_1, O_2, ..., and + // the center point C, this should create a series of triangles with + // vertices O_0, O_1, C, O_1, O_2, C, O_2, O_3, C, ... + for (size_t i = 0; i < outer_vertex_count; i++) { + vertices.push_back(outer_points[i % outer_points.size()]); + colors.push_back(SK_ColorRED); + vertices.push_back(outer_points[(i + 1) % outer_points.size()]); + colors.push_back(SK_ColorGREEN); + vertices.push_back(center); + colors.push_back(SK_ColorBLUE); + } + break; case SkVertices::VertexMode::kTriangleStrip_VertexMode: + // Calling the points on the outer circle O_0, O_1, O_2, ..., and + // the center point C, this should create a strip with vertices + // O_0, O_1, C, O_2, O_3, C, O_4, O_5, C, ... for (size_t i = 0; i <= outer_vertex_count; i++) { vertices.push_back(outer_points[i % outer_points.size()]); colors.push_back(i % 2 ? SK_ColorRED : SK_ColorGREEN); - vertices.push_back(center); - colors.push_back(i % 2 ? SK_ColorBLUE : SK_ColorCYAN); + if (i % 2 == 1) { + vertices.push_back(center); + colors.push_back(SK_ColorBLUE); + } } break; default: @@ -591,9 +613,10 @@ void BM_DrawVertices(benchmark::State& state, float radius = length / 4.0f; size_t vertex_count, total_vertex_count = 0; + size_t disc_count = state.range(0); std::vector center_points = - GetPolygonPoints(state.range(0), center, radius / 4.0f); + GetPolygonPoints(disc_count, center, radius / 4.0f); for (SkPoint p : center_points) { sk_sp vertices = @@ -614,7 +637,8 @@ void BM_DrawVertices(benchmark::State& state, } auto filename = canvas_provider->BackendName() + "-DrawVertices-" + - VertexModeToString(mode) + ".png"; + std::to_string(disc_count) + "-" + VertexModeToString(mode) + + ".png"; canvas_provider->Snapshot(filename); } diff --git a/display_list/display_list_benchmarks.h b/display_list/display_list_benchmarks.h index 148f858c6145b..8cf6af3a48699 100644 --- a/display_list/display_list_benchmarks.h +++ b/display_list/display_list_benchmarks.h @@ -94,6 +94,9 @@ void BM_DrawShadow(benchmark::State& state, #define RUN_DISPLAYLIST_BENCHMARKS(BACKEND) \ \ + /* \ + * DrawLine \ + */ \ BENCHMARK_CAPTURE(BM_DrawLine, BACKEND, \ std::make_unique()) \ ->RangeMultiplier(2) \ @@ -101,6 +104,9 @@ void BM_DrawShadow(benchmark::State& state, ->UseRealTime() \ ->Unit(benchmark::kMillisecond); \ \ + /* \ + * DrawRect \ + */ \ BENCHMARK_CAPTURE(BM_DrawRect, BACKEND, \ std::make_unique()) \ ->RangeMultiplier(2) \ @@ -108,6 +114,9 @@ void BM_DrawShadow(benchmark::State& state, ->UseRealTime() \ ->Unit(benchmark::kMillisecond); \ \ + /* \ + * DrawOval \ + */ \ BENCHMARK_CAPTURE(BM_DrawOval, BACKEND, \ std::make_unique()) \ ->RangeMultiplier(2) \ @@ -115,6 +124,9 @@ void BM_DrawShadow(benchmark::State& state, ->UseRealTime() \ ->Unit(benchmark::kMillisecond); \ \ + /* \ + * DrawCircle \ + */ \ BENCHMARK_CAPTURE(BM_DrawCircle, BACKEND, \ std::make_unique()) \ ->RangeMultiplier(2) \ @@ -122,6 +134,9 @@ void BM_DrawShadow(benchmark::State& state, ->UseRealTime() \ ->Unit(benchmark::kMillisecond); \ \ + /* \ + * DrawArc \ + */ \ BENCHMARK_CAPTURE(BM_DrawArc, BACKEND, \ std::make_unique()) \ ->RangeMultiplier(2) \ @@ -129,6 +144,52 @@ void BM_DrawShadow(benchmark::State& state, ->UseRealTime() \ ->Unit(benchmark::kMillisecond); \ \ + /* \ + * DrawPath \ + */ \ + BENCHMARK_CAPTURE(BM_DrawPath, \ + Lines/BACKEND, \ + std::make_unique(), \ + SkPath::Verb::kLine_Verb) \ + ->RangeMultiplier(2) \ + ->Range(8, 1024) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond) \ + ->Complexity(); \ + \ + BENCHMARK_CAPTURE(BM_DrawPath, \ + Quads/BACKEND, \ + std::make_unique(), \ + SkPath::Verb::kQuad_Verb) \ + ->RangeMultiplier(2) \ + ->Range(8, 1024) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond) \ + ->Complexity(); \ + \ + BENCHMARK_CAPTURE(BM_DrawPath, \ + Conics/BACKEND, \ + std::make_unique(), \ + SkPath::Verb::kConic_Verb) \ + ->RangeMultiplier(2) \ + ->Range(8, 1024) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond) \ + ->Complexity(); \ + \ + BENCHMARK_CAPTURE(BM_DrawPath, \ + Cubics/BACKEND, \ + std::make_unique(), \ + SkPath::Verb::kCubic_Verb) \ + ->RangeMultiplier(2) \ + ->Range(8, 1024) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond) \ + ->Complexity(); \ + \ + /* \ + * DrawPoints \ + */ \ BENCHMARK_CAPTURE(BM_DrawPoints, Points/BACKEND, \ std::make_unique(), \ SkCanvas::kPoints_PointMode) \ @@ -153,6 +214,42 @@ void BM_DrawShadow(benchmark::State& state, ->UseRealTime() \ ->Unit(benchmark::kMillisecond); \ \ + /* \ + * DrawVertices \ + */ \ + BENCHMARK_CAPTURE(BM_DrawVertices, \ + TriangleStrip/BACKEND, \ + std::make_unique(), \ + SkVertices::VertexMode::kTriangleStrip_VertexMode) \ + ->RangeMultiplier(2) \ + ->Range(16, 2048) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond) \ + ->Complexity(); \ + \ + BENCHMARK_CAPTURE(BM_DrawVertices, \ + TriangleFan/BACKEND, \ + std::make_unique(), \ + SkVertices::VertexMode::kTriangleFan_VertexMode) \ + ->RangeMultiplier(2) \ + ->Range(16, 2048) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond) \ + ->Complexity(); \ + \ + BENCHMARK_CAPTURE(BM_DrawVertices, \ + Triangles/BACKEND, \ + std::make_unique(), \ + SkVertices::VertexMode::kTriangles_VertexMode) \ + ->RangeMultiplier(2) \ + ->Range(16, 2048) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond) \ + ->Complexity(); \ + \ + /* \ + * DrawRRect \ + */ \ BENCHMARK_CAPTURE(BM_DrawRRect, Symmetric/BACKEND, \ std::make_unique(), \ SkRRect::Type::kSimple_Type) \ @@ -177,6 +274,9 @@ void BM_DrawShadow(benchmark::State& state, ->UseRealTime() \ ->Unit(benchmark::kMillisecond); \ \ + /* \ + * DrawImage \ + */ \ BENCHMARK_CAPTURE(BM_DrawImage, Texture/BACKEND, \ std::make_unique(), \ SkSamplingOptions(), false) \ @@ -193,6 +293,9 @@ void BM_DrawShadow(benchmark::State& state, ->UseRealTime() \ ->Unit(benchmark::kMillisecond); \ \ + /* \ + * DrawImageRect \ + */ \ BENCHMARK_CAPTURE( \ BM_DrawImageRect, Texture/Strict/BACKEND, \ std::make_unique(), SkSamplingOptions(), \ @@ -202,6 +305,24 @@ void BM_DrawShadow(benchmark::State& state, ->UseRealTime() \ ->Unit(benchmark::kMillisecond); \ \ + BENCHMARK_CAPTURE( \ + BM_DrawImageRect, Texture/Fast/BACKEND, \ + std::make_unique(), SkSamplingOptions(), \ + SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint, false) \ + ->RangeMultiplier(2) \ + ->Range(32, 256) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ + BENCHMARK_CAPTURE( \ + BM_DrawImageRect, Upload/Strict/BACKEND, \ + std::make_unique(), SkSamplingOptions(), \ + SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint, true) \ + ->RangeMultiplier(2) \ + ->Range(32, 256) \ + ->UseRealTime() \ + ->Unit(benchmark::kMillisecond); \ + \ BENCHMARK_CAPTURE( \ BM_DrawImageRect, Upload/Fast/BACKEND, \ std::make_unique(), SkSamplingOptions(), \ @@ -211,6 +332,9 @@ void BM_DrawShadow(benchmark::State& state, ->UseRealTime() \ ->Unit(benchmark::kMillisecond); \ \ + /* \ + * DrawImageNine \ + */ \ BENCHMARK_CAPTURE(BM_DrawImageNine, Texture/Nearest/BACKEND, \ std::make_unique(), \ SkFilterMode::kNearest, false) \ @@ -243,6 +367,9 @@ void BM_DrawShadow(benchmark::State& state, ->UseRealTime() \ ->Unit(benchmark::kMillisecond); \ \ + /* \ + * DrawTextBlob \ + */ \ BENCHMARK_CAPTURE(BM_DrawTextBlob, BACKEND, \ std::make_unique()) \ ->RangeMultiplier(2) \ @@ -251,6 +378,9 @@ void BM_DrawShadow(benchmark::State& state, ->Unit(benchmark::kMillisecond) \ ->Complexity(); \ \ + /* \ + * DrawShadow \ + */ \ BENCHMARK_CAPTURE(BM_DrawShadow, Lines/Transparent/BACKEND, \ std::make_unique(), true, \ SkPath::Verb::kLine_Verb) \ From c231e63ecdc3fd2e8c01df6950c5a18e40f37b43 Mon Sep 17 00:00:00 2001 From: George Wright Date: Thu, 23 Dec 2021 12:44:19 -0800 Subject: [PATCH 4/5] Licences --- ci/licenses_golden/licenses_flutter | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index e6a3c0997b1d6..471fd44324ffd 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -34,6 +34,10 @@ FILE: ../../../flutter/common/task_runners.cc FILE: ../../../flutter/common/task_runners.h FILE: ../../../flutter/display_list/display_list.cc FILE: ../../../flutter/display_list/display_list.h +FILE: ../../../flutter/display_list/display_list_benchmarks.cc +FILE: ../../../flutter/display_list/display_list_benchmarks.h +FILE: ../../../flutter/display_list/display_list_benchmarks_gl.cc +FILE: ../../../flutter/display_list/display_list_benchmarks_metal.cc FILE: ../../../flutter/display_list/display_list_builder.cc FILE: ../../../flutter/display_list/display_list_builder.h FILE: ../../../flutter/display_list/display_list_canvas_dispatcher.cc From 953d56243963eb23739f8164dcab04ee055430c0 Mon Sep 17 00:00:00 2001 From: George Wright Date: Tue, 4 Jan 2022 16:02:44 -0800 Subject: [PATCH 5/5] header name update --- display_list/display_list_benchmarks.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/display_list/display_list_benchmarks.cc b/display_list/display_list_benchmarks.cc index 031ecbc8b9ab6..6fd39773ee015 100644 --- a/display_list/display_list_benchmarks.cc +++ b/display_list/display_list_benchmarks.cc @@ -3,7 +3,7 @@ // found in the LICENSE file. #include "flutter/display_list/display_list_benchmarks.h" -#include "flutter/display_list/display_list.h" +#include "flutter/display_list/display_list_builder.h" #include "third_party/skia/include/core/SkPoint.h" #include "third_party/skia/include/core/SkTextBlob.h"