blob: d5681864901a3b1c9c25c313a96daeb63091bb88 [file] [log] [blame]
// 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/benchmarking/dl_benchmarks.h"
#include "flutter/display_list/dl_builder.h"
#include "flutter/display_list/dl_op_flags.h"
#include "flutter/display_list/skia/dl_sk_canvas.h"
#include "flutter/display_list/testing/dl_test_snippets.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkPoint.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/core/SkTextBlob.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"
#include "third_party/skia/include/gpu/GrRecordingContext.h"
#include "third_party/skia/include/gpu/GrTypes.h"
namespace flutter {
namespace testing {
DlPaint GetPaintForRun(unsigned attributes) {
DlPaint paint;
if (attributes & kStrokedStyle && attributes & kFilledStyle) {
// Not currently exposed by Flutter, but we can probably benchmark this in
// the future
paint.setDrawStyle(DlDrawStyle::kStrokeAndFill);
} else if (attributes & kStrokedStyle) {
paint.setDrawStyle(DlDrawStyle::kStroke);
} else if (attributes & kFilledStyle) {
paint.setDrawStyle(DlDrawStyle::kFill);
}
if (attributes & kHairlineStroke) {
paint.setStrokeWidth(0.0f);
} else {
paint.setStrokeWidth(1.0f);
}
paint.setAntiAlias(attributes & kAntiAliasing);
return paint;
}
static void FlushSubmitCpuSync(const sk_sp<SkSurface>& surface) {
if (!surface) {
return;
}
if (GrDirectContext* dContext =
GrAsDirectContext(surface->recordingContext())) {
dContext->flushAndSubmit(surface.get(), GrSyncCpu::kYes);
}
}
void AnnotateAttributes(unsigned attributes,
benchmark::State& state,
const DisplayListAttributeFlags flags) {
if (flags.always_stroked()) {
state.counters["HairlineStroke"] = attributes & kHairlineStroke ? 1 : 0;
}
if (flags.applies_style()) {
state.counters["HairlineStroke"] = attributes & kHairlineStroke ? 1 : 0;
state.counters["StrokedStyle"] = attributes & kStrokedStyle ? 1 : 0;
state.counters["FilledStyle"] = attributes & kFilledStyle ? 1 : 0;
}
if (flags.applies_anti_alias()) {
state.counters["AntiAliasing"] = attributes & kAntiAliasing ? 1 : 0;
}
}
// 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 kDRRectsToDraw = 2000;
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,
BackendType backend_type,
unsigned attributes) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawLineFlags);
size_t length = state.range(0);
surface_provider->InitializeSurface(length, length);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->getCanvas());
state.counters["DrawCallCount"] = kLinesToDraw;
for (size_t i = 0; i < kLinesToDraw; i++) {
builder.DrawLine(SkPoint::Make(i % length, 0),
SkPoint::Make(length - i % length, length), paint);
}
auto display_list = builder.Build();
// We only want to time the actual rasterization.
for ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawLine-" +
std::to_string(state.range(0)) + ".png";
surface_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,
BackendType backend_type,
unsigned attributes) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawRectFlags);
size_t length = state.range(0);
size_t canvas_size = length * 2;
surface_provider->InitializeSurface(canvas_size, canvas_size);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->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);
state.counters["DrawCallCount"] = kRectsToDraw;
for (size_t i = 0; i < kRectsToDraw; i++) {
builder.DrawRect(rect, paint);
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 ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawRect-" +
std::to_string(state.range(0)) + ".png";
surface_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,
BackendType backend_type,
unsigned attributes) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawOvalFlags);
size_t length = state.range(0);
size_t canvas_size = length * 2;
surface_provider->InitializeSurface(canvas_size, canvas_size);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->getCanvas());
SkRect rect = SkRect::MakeXYWH(0, 0, length * 1.5f, length);
const SkScalar offset = 0.5f;
state.counters["DrawCallCount"] = kOvalsToDraw;
for (size_t i = 0; i < kOvalsToDraw; i++) {
builder.DrawOval(rect, paint);
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 ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawOval-" +
std::to_string(state.range(0)) + ".png";
surface_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,
BackendType backend_type,
unsigned attributes) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawCircleFlags);
size_t length = state.range(0);
size_t canvas_size = length * 2;
surface_provider->InitializeSurface(canvas_size, canvas_size);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->getCanvas());
SkScalar radius = length / 2.0f;
const SkScalar offset = 0.5f;
SkPoint center = SkPoint::Make(radius, radius);
state.counters["DrawCallCount"] = kCirclesToDraw;
for (size_t i = 0; i < kCirclesToDraw; i++) {
builder.DrawCircle(center, radius, paint);
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 ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawCircle-" +
std::to_string(state.range(0)) + ".png";
surface_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,
BackendType backend_type,
unsigned attributes,
SkRRect::Type type) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawRRectFlags);
size_t length = state.range(0);
size_t canvas_size = length * 2;
surface_provider->InitializeSurface(canvas_size, canvas_size);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->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);
state.counters["DrawCallCount"] = kRRectsToDraw;
for (size_t i = 0; i < kRRectsToDraw; i++) {
builder.DrawRRect(rrect, paint);
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 ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawRRect-" +
std::to_string(state.range(0)) + ".png";
surface_provider->Snapshot(filename);
}
// Draws a series of "DR" rects of the requested width across
// the canvas and repeats until `kRRectsToDraw` rects have been drawn.
//
// A "DR" rect is a shape consisting of the difference between two
// rounded rects.
//
// Half the drawn DR rects will not have an integral offset.
void BM_DrawDRRect(benchmark::State& state,
BackendType backend_type,
unsigned attributes,
SkRRect::Type type) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawDRRectFlags);
size_t length = state.range(0);
size_t canvas_size = length * 2;
surface_provider->InitializeSurface(canvas_size, canvas_size);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->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, 7.0f);
radii[1] = SkVector::Make(3.0f, 7.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(8.0f, 7.0f);
break;
default:
break;
}
const SkScalar offset = 0.5f;
const SkScalar multiplier = length / 16.0f;
SkRRect rrect, rrect_2;
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);
state.counters["DrawCallCount"] = kDRRectsToDraw;
for (size_t i = 0; i < kDRRectsToDraw; i++) {
rrect.inset(0.1f * length, 0.1f * length, &rrect_2);
builder.DrawDRRect(rrect, rrect_2, paint);
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 ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawDRRect-" +
std::to_string(state.range(0)) + ".png";
surface_provider->Snapshot(filename);
}
void BM_DrawArc(benchmark::State& state,
BackendType backend_type,
unsigned attributes) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state,
DisplayListOpFlags::kDrawArcNoCenterFlags);
size_t length = state.range(0);
size_t canvas_size = length * 2;
surface_provider->InitializeSurface(canvas_size, canvas_size);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->getCanvas());
SkScalar starting_angle = 0.0f;
SkScalar offset = 0.5f;
// Just some random sweeps that will mostly circumnavigate the circle
std::vector<SkScalar> 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);
state.counters["DrawCallCount"] = kArcSweepSetsToDraw * segment_sweeps.size();
for (size_t i = 0; i < kArcSweepSetsToDraw; i++) {
for (SkScalar sweep : segment_sweeps) {
builder.DrawArc(bounds, starting_angle, sweep, false, paint);
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 ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawArc-" +
std::to_string(state.range(0)) + ".png";
surface_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<SkPoint> GetPolygonPoints(size_t n, SkPoint center, SkScalar r) {
std::vector<SkPoint> points;
SkScalar x, y;
float angle;
float full_circle = 2.0f * M_PI;
for (size_t i = 0; i < n; i++) {
angle = (full_circle / static_cast<float>(n)) * static_cast<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<SkPoint> 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<SkPoint> points = GetPolygonPoints(sides, center, radius);
std::vector<SkPoint> 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<SkPoint> points = GetPolygonPoints(sides, center, radius);
std::vector<SkPoint> 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<SkPoint> points = GetPolygonPoints(sides, center, radius);
std::vector<SkPoint> inner_control_points =
GetPolygonPoints(sides * 3, center, radius * 0.8f);
std::vector<SkPoint> 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<SkPoint> 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,
BackendType backend_type,
unsigned attributes,
SkPath::Verb type) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawPathFlags);
size_t length = kFixedCanvasSize;
surface_provider->InitializeSurface(length, length);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->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();
state.counters["DrawCallCount"] = 1;
builder.DrawPath(path, paint);
auto display_list = builder.Build();
// We only want to time the actual rasterization.
for ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawPath-" + label +
"-" + std::to_string(state.range(0)) + ".png";
surface_provider->Snapshot(filename);
}
// Returns a set of vertices that describe a circle that has a
// 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,
// and the final vertex being the center point of the disc.
//
// Each vertex colour will alternate through Red, Green, Blue and Cyan.
std::shared_ptr<DlVertices> GetTestVertices(SkPoint center,
float radius,
size_t vertex_count,
DlVertexMode mode,
size_t& final_vertex_count) {
size_t outer_vertex_count = vertex_count / 2;
std::vector<SkPoint> outer_points =
GetPolygonPoints(outer_vertex_count, center, radius);
std::vector<SkPoint> vertices;
std::vector<DlColor> colors;
switch (mode) {
case DlVertexMode::kTriangleFan:
// 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(DlColor(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(DlColor(SK_ColorRED));
} else if (i % 3 == 1) {
colors.push_back(DlColor(SK_ColorGREEN));
} else {
colors.push_back(DlColor(SK_ColorBLUE));
}
}
break;
case DlVertexMode::kTriangles:
// 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(DlColor(SK_ColorRED));
vertices.push_back(outer_points[(i + 1) % outer_points.size()]);
colors.push_back(DlColor(SK_ColorGREEN));
vertices.push_back(center);
colors.push_back(DlColor(SK_ColorBLUE));
}
break;
case DlVertexMode::kTriangleStrip:
// 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 ? DlColor(SK_ColorRED) : DlColor(SK_ColorGREEN));
if (i % 2 == 1) {
vertices.push_back(center);
colors.push_back(DlColor(SK_ColorBLUE));
}
}
break;
default:
break;
}
final_vertex_count = vertices.size();
return DlVertices::Make(mode, vertices.size(), vertices.data(), nullptr,
colors.data());
}
std::string VertexModeToString(DlVertexMode mode) {
switch (mode) {
case DlVertexMode::kTriangleStrip:
return "TriangleStrip";
case DlVertexMode::kTriangleFan:
return "TriangleFan";
case DlVertexMode::kTriangles:
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,
BackendType backend_type,
unsigned attributes,
DlVertexMode mode) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawVerticesFlags);
size_t length = kFixedCanvasSize;
surface_provider->InitializeSurface(length, length);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->getCanvas());
SkPoint center = SkPoint::Make(length / 2.0f, length / 2.0f);
float radius = length / 4.0f;
size_t vertex_count, total_vertex_count = 0;
size_t disc_count = state.range(0);
std::vector<SkPoint> center_points =
GetPolygonPoints(disc_count, center, radius / 4.0f);
state.counters["DrawCallCount"] = center_points.size();
for (SkPoint p : center_points) {
std::shared_ptr<DlVertices> vertices =
GetTestVertices(p, radius, 50, mode, vertex_count);
total_vertex_count += vertex_count;
builder.DrawVertices(vertices.get(), DlBlendMode::kSrc, paint);
}
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 ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawVertices-" +
std::to_string(disc_count) + "-" + VertexModeToString(mode) +
".png";
surface_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<SkPoint> GetTestPoints(size_t count, SkISize canvas_size) {
std::vector<SkPoint> points;
// Some arbitrary offsets to use when building the list of points
std::vector<SkScalar> delta_x = {10.0f, 6.3f, 15.0f, 3.5f, 22.6f, 4.7f};
std::vector<SkScalar> 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(DlCanvas::PointMode mode) {
switch (mode) {
case DlCanvas::PointMode::kLines:
return "Lines";
case DlCanvas::PointMode::kPolygon:
return "Polygon";
case DlCanvas::PointMode::kPoints:
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,
BackendType backend_type,
unsigned attributes,
DlCanvas::PointMode mode) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
switch (mode) {
case DlCanvas::PointMode::kPoints:
AnnotateAttributes(attributes, state,
DisplayListOpFlags::kDrawPointsAsPointsFlags);
break;
case DlCanvas::PointMode::kLines:
AnnotateAttributes(attributes, state,
DisplayListOpFlags::kDrawPointsAsLinesFlags);
break;
case DlCanvas::PointMode::kPolygon:
AnnotateAttributes(attributes, state,
DisplayListOpFlags::kDrawPointsAsPolygonFlags);
break;
}
size_t length = kFixedCanvasSize;
surface_provider->InitializeSurface(length, length);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->getCanvas());
size_t point_count = state.range(0);
state.SetComplexityN(point_count);
state.counters["PointCount"] = point_count;
state.counters["DrawCallCount"] = 1;
std::vector<SkPoint> points =
GetTestPoints(point_count, SkISize::Make(length, length));
builder.DrawPoints(mode, points.size(), points.data(), paint);
auto display_list = builder.Build();
for ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawPoints-" +
PointModeToString(mode) + "-" + std::to_string(point_count) +
".png";
surface_provider->Snapshot(filename);
}
sk_sp<SkImage> 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 SkImages::RasterFromPixmap(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,
BackendType backend_type,
unsigned attributes,
DlImageSampling options,
bool upload_bitmap) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state,
DisplayListOpFlags::kDrawImageWithPaintFlags);
size_t bitmap_size = state.range(0);
size_t canvas_size = 2 * bitmap_size;
surface_provider->InitializeSurface(canvas_size, canvas_size);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->getCanvas());
sk_sp<SkImage> image;
std::shared_ptr<DlSurfaceInstance> offscreen_instance;
sk_sp<SkSurface> 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_instance =
surface_provider->MakeOffscreenSurface(bitmap_size, bitmap_size);
offscreen = offscreen_instance->sk_surface();
offscreen->getCanvas()->clear(SK_ColorRED);
}
SkScalar offset = 0.5f;
SkPoint dst = SkPoint::Make(0, 0);
state.counters["DrawCallCount"] = kImagesToDraw;
for (size_t i = 0; i < kImagesToDraw; i++) {
image = upload_bitmap ? ImageFromBitmapWithNewID(bitmap)
: offscreen->makeImageSnapshot();
builder.DrawImage(DlImage::Make(image), dst, options, &paint);
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 ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawImage-" +
(upload_bitmap ? "Upload-" : "Texture-") +
std::to_string(bitmap_size) + ".png";
surface_provider->Snapshot(filename);
}
std::string ConstraintToString(DlCanvas::SrcRectConstraint constraint) {
switch (constraint) {
case DlCanvas::SrcRectConstraint::kStrict:
return "Strict";
case DlCanvas::SrcRectConstraint::kFast:
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,
BackendType backend_type,
unsigned attributes,
DlImageSampling options,
DlCanvas::SrcRectConstraint constraint,
bool upload_bitmap) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state,
DisplayListOpFlags::kDrawImageRectWithPaintFlags);
size_t bitmap_size = state.range(0);
size_t canvas_size = 2 * bitmap_size;
surface_provider->InitializeSurface(canvas_size, canvas_size);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->getCanvas());
sk_sp<SkImage> image;
std::shared_ptr<DlSurfaceInstance> offscreen_instance;
sk_sp<SkSurface> 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_instance =
surface_provider->MakeOffscreenSurface(bitmap_size, bitmap_size);
offscreen = offscreen_instance->sk_surface();
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);
state.counters["DrawCallCount"] = kImagesToDraw;
for (size_t i = 0; i < kImagesToDraw; i++) {
image = upload_bitmap ? ImageFromBitmapWithNewID(bitmap)
: offscreen->makeImageSnapshot();
builder.DrawImageRect(DlImage::Make(image), src, dst, options, &paint,
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 ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawImageRect-" +
(upload_bitmap ? "Upload-" : "Texture-") +
ConstraintToString(constraint) + "-" +
std::to_string(bitmap_size) + ".png";
surface_provider->Snapshot(filename);
}
std::string FilterModeToString(const DlFilterMode mode) {
switch (mode) {
case DlFilterMode::kNearest:
return "Nearest";
case DlFilterMode::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,
BackendType backend_type,
unsigned attributes,
const DlFilterMode filter,
bool upload_bitmap) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state,
DisplayListOpFlags::kDrawImageNineWithPaintFlags);
size_t bitmap_size = state.range(0);
size_t canvas_size = 2 * bitmap_size;
surface_provider->InitializeSurface(canvas_size, canvas_size);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->getCanvas());
SkIRect center = SkIRect::MakeXYWH(bitmap_size / 4, bitmap_size / 4,
bitmap_size / 2, bitmap_size / 2);
sk_sp<SkImage> image;
std::shared_ptr<DlSurfaceInstance> offscreen_instance;
sk_sp<SkSurface> 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_instance =
surface_provider->MakeOffscreenSurface(bitmap_size, bitmap_size);
offscreen = offscreen_instance->sk_surface();
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);
state.counters["DrawCallCount"] = kImagesToDraw;
for (size_t i = 0; i < kImagesToDraw; i++) {
image = upload_bitmap ? ImageFromBitmapWithNewID(bitmap)
: offscreen->makeImageSnapshot();
builder.DrawImageNine(DlImage::Make(image), center, dst, filter, &paint);
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 ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawImageNine-" +
(upload_bitmap ? "Upload-" : "Texture-") +
FilterModeToString(filter) + "-" +
std::to_string(bitmap_size) + ".png";
surface_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,
BackendType backend_type,
unsigned attributes) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawTextBlobFlags);
size_t draw_calls = state.range(0);
size_t canvas_size = kFixedCanvasSize;
surface_provider->InitializeSurface(canvas_size, canvas_size);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->getCanvas());
state.counters["DrawCallCount_Varies"] = draw_calls;
state.counters["GlyphCount"] = draw_calls;
char character[2] = {'A', '\0'};
for (size_t i = 0; i < draw_calls; i++) {
character[0] = 'A' + (i % 26);
auto blob = SkTextBlob::MakeFromString(character, CreateTestFontOfSize(20));
builder.DrawTextBlob(blob, 50.0f, 50.0f, paint);
}
auto display_list = builder.Build();
for ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawTextBlob-" +
std::to_string(draw_calls) + ".png";
surface_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,
BackendType backend_type,
unsigned attributes,
bool transparent_occluder,
SkPath::Verb type) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawShadowFlags);
size_t length = kFixedCanvasSize;
surface_provider->InitializeSurface(length, length);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->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);
state.counters["DrawCallCount"] = 1;
// 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, DlColor(SK_ColorBLUE), elevation,
transparent_occluder, 1.0f);
auto display_list = builder.Build();
// We only want to time the actual rasterization.
for ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-DrawShadow-" +
VerbToString(type) + "-" +
(transparent_occluder ? "Transparent-" : "Opaque-") +
std::to_string(elevation) + "-" + ".png";
surface_provider->Snapshot(filename);
}
// Calls saveLayer N times from the root canvas layer, and optionally calls
// saveLayer a further M times nested inside that top saveLayer call.
//
// The total number of saveLayer calls will be N * (M+1).
//
// In each saveLayer call, simply draw the colour red with no clip rect.
void BM_SaveLayer(benchmark::State& state,
BackendType backend_type,
unsigned attributes,
size_t save_depth) {
auto surface_provider = DlSurfaceProvider::Create(backend_type);
DisplayListBuilder builder;
DlPaint paint = GetPaintForRun(attributes);
AnnotateAttributes(attributes, state, DisplayListOpFlags::kSaveLayerFlags);
size_t length = kFixedCanvasSize;
surface_provider->InitializeSurface(length, length);
auto surface = surface_provider->GetPrimarySurface()->sk_surface();
auto canvas = DlSkCanvasAdapter(surface->getCanvas());
size_t save_layer_calls = state.range(0);
// Ensure we draw two overlapping rects to avoid any peephole optimisations
SkRect rect1 = SkRect::MakeLTRB(0, 0, 0.75f * length, 0.75f * length);
SkRect rect2 =
SkRect::MakeLTRB(0.25f * length, 0.25f * length, length, length);
state.counters["DrawCallCount_Varies"] = save_layer_calls * save_depth;
for (size_t i = 0; i < save_layer_calls; i++) {
for (size_t j = 0; j < save_depth; j++) {
builder.SaveLayer(nullptr, nullptr);
builder.DrawRect(rect1, paint);
builder.DrawRect(rect2, paint);
}
for (size_t j = 0; j < save_depth; j++) {
builder.Restore();
}
}
auto display_list = builder.Build();
// We only want to time the actual rasterization.
for ([[maybe_unused]] auto _ : state) {
canvas.DrawDisplayList(display_list);
FlushSubmitCpuSync(surface);
}
auto filename = surface_provider->backend_name() + "-SaveLayer-" +
std::to_string(save_depth) + "-" +
std::to_string(save_layer_calls) + ".png";
surface_provider->Snapshot(filename);
}
#ifdef ENABLE_SOFTWARE_BENCHMARKS
RUN_DISPLAYLIST_BENCHMARKS(Software)
#endif
#ifdef ENABLE_OPENGL_BENCHMARKS
RUN_DISPLAYLIST_BENCHMARKS(OpenGL)
#endif
#ifdef ENABLE_METAL_BENCHMARKS
RUN_DISPLAYLIST_BENCHMARKS(Metal)
#endif
} // namespace testing
} // namespace flutter