skia2/gm/multipicturedraw.cpp
Ben Wagner 7fde8e1728 IWYU for gms.
This almost gets gms to be iwyu clean. The last bit is around gm.cpp
and the tracing framework and its use of atomic. Will also need a way
of keeping things from regressing, which is difficult due to needing to
do this outside-in.

Change-Id: I1393531e99da8b0f1a29f55c53c86d53f459af7d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/211593
Reviewed-by: Herb Derby <herb@google.com>
Commit-Queue: Ben Wagner <bungeman@google.com>
2019-05-02 17:48:53 +00:00

579 lines
20 KiB
C++

/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "gm/gm.h"
#include "include/core/SkBBHFactory.h"
#include "include/core/SkBlendMode.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkMultiPictureDraw.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTypes.h"
#include "include/private/SkTArray.h"
#include "tools/ToolUtils.h"
constexpr SkScalar kRoot3Over2 = 0.86602545f; // sin(60)
constexpr SkScalar kRoot3 = 1.73205081f;
constexpr int kHexSide = 30;
constexpr int kNumHexX = 6;
constexpr int kNumHexY = 6;
constexpr int kPicWidth = kNumHexX * kHexSide;
constexpr int kPicHeight = (int)((kNumHexY - 0.5f) * 2 * kHexSide * kRoot3Over2 + 0.5f);
constexpr SkScalar kInset = 20.0f;
constexpr int kNumPictures = 4;
constexpr int kTriSide = 40;
// Create a hexagon centered at (originX, originY)
static SkPath make_hex_path(SkScalar originX, SkScalar originY) {
SkPath hex;
hex.moveTo(originX-kHexSide, originY);
hex.rLineTo(SkScalarHalf(kHexSide), kRoot3Over2 * kHexSide);
hex.rLineTo(SkIntToScalar(kHexSide), 0);
hex.rLineTo(SkScalarHalf(kHexSide), -kHexSide * kRoot3Over2);
hex.rLineTo(-SkScalarHalf(kHexSide), -kHexSide * kRoot3Over2);
hex.rLineTo(-SkIntToScalar(kHexSide), 0);
hex.close();
return hex;
}
// Make a picture that is a tiling of the plane with stroked hexagons where
// each hexagon is in its own layer. The layers are to exercise Ganesh's
// layer hoisting.
static sk_sp<SkPicture> make_hex_plane_picture(SkColor fillColor) {
// Create a hexagon with its center at the origin
SkPath hex = make_hex_path(0, 0);
SkPaint fill;
fill.setStyle(SkPaint::kFill_Style);
fill.setColor(fillColor);
SkPaint stroke;
stroke.setStyle(SkPaint::kStroke_Style);
stroke.setStrokeWidth(3);
SkPictureRecorder recorder;
SkRTreeFactory bbhFactory;
SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
SkIntToScalar(kPicHeight),
&bbhFactory);
SkScalar xPos, yPos = 0;
for (int y = 0; y < kNumHexY; ++y) {
xPos = 0;
for (int x = 0; x < kNumHexX; ++x) {
canvas->saveLayer(nullptr, nullptr);
canvas->translate(xPos, yPos + ((x % 2) ? kRoot3Over2 * kHexSide : 0));
canvas->drawPath(hex, fill);
canvas->drawPath(hex, stroke);
canvas->restore();
xPos += 1.5f * kHexSide;
}
yPos += 2 * kHexSide * kRoot3Over2;
}
return recorder.finishRecordingAsPicture();
}
// Create a picture that consists of a single large layer that is tiled
// with hexagons.
// This is intended to exercise the layer hoisting code's clip handling (in
// tile mode).
static sk_sp<SkPicture> make_single_layer_hex_plane_picture() {
// Create a hexagon with its center at the origin
SkPath hex = make_hex_path(0, 0);
SkPaint whiteFill;
whiteFill.setStyle(SkPaint::kFill_Style);
whiteFill.setColor(SK_ColorWHITE);
SkPaint greyFill;
greyFill.setStyle(SkPaint::kFill_Style);
greyFill.setColor(SK_ColorLTGRAY);
SkPaint stroke;
stroke.setStyle(SkPaint::kStroke_Style);
stroke.setStrokeWidth(3);
SkPictureRecorder recorder;
SkRTreeFactory bbhFactory;
constexpr SkScalar kBig = 10000.0f;
SkCanvas* canvas = recorder.beginRecording(kBig, kBig, &bbhFactory);
canvas->saveLayer(nullptr, nullptr);
SkScalar xPos = 0.0f, yPos = 0.0f;
for (int y = 0; yPos < kBig; ++y) {
xPos = 0;
for (int x = 0; xPos < kBig; ++x) {
canvas->save();
canvas->translate(xPos, yPos + ((x % 2) ? kRoot3Over2 * kHexSide : 0));
// The color of the filled hex is swapped to yield a different
// pattern in each tile. This allows an error in layer hoisting (e.g.,
// the clip isn't blocking cache reuse) to cause a visual discrepancy.
canvas->drawPath(hex, ((x+y) % 3) ? whiteFill : greyFill);
canvas->drawPath(hex, stroke);
canvas->restore();
xPos += 1.5f * kHexSide;
}
yPos += 2 * kHexSide * kRoot3Over2;
}
canvas->restore();
return recorder.finishRecordingAsPicture();
}
// Make an equilateral triangle path with its top corner at (originX, originY)
static SkPath make_tri_path(SkScalar originX, SkScalar originY) {
SkPath tri;
tri.moveTo(originX, originY);
tri.rLineTo(SkScalarHalf(kTriSide), 1.5f * kTriSide / kRoot3);
tri.rLineTo(-kTriSide, 0);
tri.close();
return tri;
}
static sk_sp<SkPicture> make_tri_picture() {
SkPath tri = make_tri_path(SkScalarHalf(kTriSide), 0);
SkPaint fill;
fill.setStyle(SkPaint::kFill_Style);
fill.setColor(SK_ColorLTGRAY);
SkPaint stroke;
stroke.setStyle(SkPaint::kStroke_Style);
stroke.setStrokeWidth(3);
SkPictureRecorder recorder;
SkRTreeFactory bbhFactory;
SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
SkIntToScalar(kPicHeight),
&bbhFactory);
SkRect r = tri.getBounds();
r.outset(2.0f, 2.0f); // outset for stroke
canvas->clipRect(r);
// The saveLayer/restore block is to exercise layer hoisting
canvas->saveLayer(nullptr, nullptr);
canvas->drawPath(tri, fill);
canvas->drawPath(tri, stroke);
canvas->restore();
return recorder.finishRecordingAsPicture();
}
static sk_sp<SkPicture> make_sub_picture(const SkPicture* tri) {
SkPictureRecorder recorder;
SkRTreeFactory bbhFactory;
SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
SkIntToScalar(kPicHeight),
&bbhFactory);
canvas->scale(1.0f/2.0f, 1.0f/2.0f);
canvas->save();
canvas->translate(SkScalarHalf(kTriSide), 0);
canvas->drawPicture(tri);
canvas->restore();
canvas->save();
canvas->translate(SkIntToScalar(kTriSide), 1.5f * kTriSide / kRoot3);
canvas->drawPicture(tri);
canvas->restore();
canvas->save();
canvas->translate(0, 1.5f * kTriSide / kRoot3);
canvas->drawPicture(tri);
canvas->restore();
return recorder.finishRecordingAsPicture();
}
// Create a Sierpinkski-like picture that starts with a top row with a picture
// that just contains a triangle. Subsequent rows take the prior row's picture,
// shrinks it and replicates it 3 times then draws and appropriate number of
// copies of it.
static sk_sp<SkPicture> make_sierpinski_picture() {
sk_sp<SkPicture> pic(make_tri_picture());
SkPictureRecorder recorder;
SkRTreeFactory bbhFactory;
SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
SkIntToScalar(kPicHeight),
&bbhFactory);
constexpr int kNumLevels = 4;
for (int i = 0; i < kNumLevels; ++i) {
canvas->save();
canvas->translate(kPicWidth/2 - (i+1) * (kTriSide/2.0f), 0.0f);
for (int j = 0; j < i+1; ++j) {
canvas->drawPicture(pic);
canvas->translate(SkIntToScalar(kTriSide), 0);
}
canvas->restore();
pic = make_sub_picture(pic.get());
canvas->translate(0, 1.5f * kTriSide / kRoot3);
}
return recorder.finishRecordingAsPicture();
}
static sk_sp<SkSurface> create_compat_surface(SkCanvas* canvas, int width, int height) {
SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
return ToolUtils::makeSurface(canvas, info);
}
// This class stores the information required to compose all the result
// fragments potentially generated by the MultiPictureDraw object
class ComposeStep {
public:
ComposeStep() : fX(0.0f), fY(0.0f), fPaint(nullptr) { }
~ComposeStep() {
delete fPaint;
}
sk_sp<SkSurface> fSurf;
SkScalar fX;
SkScalar fY;
SkPaint* fPaint;
};
typedef void (*PFContentMtd)(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]);
// Just a single picture with no clip
static void no_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
canvas->drawPicture(pictures[0]);
}
// Two pictures with a rect clip on the second one
static void rect_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
canvas->drawPicture(pictures[0]);
SkRect rect = pictures[0]->cullRect();
rect.inset(kInset, kInset);
canvas->clipRect(rect);
canvas->drawPicture(pictures[1]);
}
// Two pictures with a round rect clip on the second one
static void rrect_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
canvas->drawPicture(pictures[0]);
SkRect rect = pictures[0]->cullRect();
rect.inset(kInset, kInset);
SkRRect rrect;
rrect.setRectXY(rect, kInset, kInset);
canvas->clipRRect(rrect);
canvas->drawPicture(pictures[1]);
}
// Two pictures with a clip path on the second one
static void path_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
canvas->drawPicture(pictures[0]);
// Create a hexagon centered on the middle of the hex grid
SkPath hex = make_hex_path((kNumHexX / 2.0f) * kHexSide, kNumHexY * kHexSide * kRoot3Over2);
canvas->clipPath(hex);
canvas->drawPicture(pictures[1]);
}
// Two pictures with an inverse clip path on the second one
static void invpath_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
canvas->drawPicture(pictures[0]);
// Create a hexagon centered on the middle of the hex grid
SkPath hex = make_hex_path((kNumHexX / 2.0f) * kHexSide, kNumHexY * kHexSide * kRoot3Over2);
hex.setFillType(SkPath::kInverseEvenOdd_FillType);
canvas->clipPath(hex);
canvas->drawPicture(pictures[1]);
}
// Reuse a single base (triangular) picture a _lot_ (rotated, scaled and translated).
static void sierpinski(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
canvas->save();
canvas->drawPicture(pictures[2]);
canvas->rotate(180.0f);
canvas->translate(-SkIntToScalar(kPicWidth), -SkIntToScalar(kPicHeight));
canvas->drawPicture(pictures[2]);
canvas->restore();
}
static void big_layer(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
canvas->drawPicture(pictures[3]);
}
constexpr PFContentMtd gContentMthds[] = {
no_clip,
rect_clip,
rrect_clip,
path_clip,
invpath_clip,
sierpinski,
big_layer,
};
static void create_content(SkMultiPictureDraw* mpd, PFContentMtd pfGen,
const SkPicture* pictures[kNumPictures],
SkCanvas* dest, const SkMatrix& xform) {
sk_sp<SkPicture> composite;
{
SkPictureRecorder recorder;
SkRTreeFactory bbhFactory;
SkCanvas* pictureCanvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
SkIntToScalar(kPicHeight),
&bbhFactory);
(*pfGen)(pictureCanvas, pictures);
composite = recorder.finishRecordingAsPicture();
}
mpd->add(dest, composite.get(), &xform);
}
typedef void(*PFLayoutMtd)(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd,
PFContentMtd pfGen, const SkPicture* pictures[kNumPictures],
SkTArray<ComposeStep>* composeSteps);
// Draw the content into a single canvas
static void simple(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd,
PFContentMtd pfGen,
const SkPicture* pictures[kNumPictures],
SkTArray<ComposeStep> *composeSteps) {
ComposeStep& step = composeSteps->push_back();
step.fSurf = create_compat_surface(finalCanvas, kPicWidth, kPicHeight);
SkCanvas* subCanvas = step.fSurf->getCanvas();
create_content(mpd, pfGen, pictures, subCanvas, SkMatrix::I());
}
// Draw the content into multiple canvases/tiles
static void tiled(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd,
PFContentMtd pfGen,
const SkPicture* pictures[kNumPictures],
SkTArray<ComposeStep> *composeSteps) {
const int kNumTilesX = 2;
const int kNumTilesY = 2;
const int kTileWidth = kPicWidth / kNumTilesX;
const int kTileHeight = kPicHeight / kNumTilesY;
SkASSERT(kPicWidth == kNumTilesX * kTileWidth);
SkASSERT(kPicHeight == kNumTilesY * kTileHeight);
const SkColor colors[kNumTilesX][kNumTilesY] = {
{ SK_ColorCYAN, SK_ColorMAGENTA },
{ SK_ColorYELLOW, SK_ColorGREEN }
};
for (int y = 0; y < kNumTilesY; ++y) {
for (int x = 0; x < kNumTilesX; ++x) {
ComposeStep& step = composeSteps->push_back();
step.fX = SkIntToScalar(x*kTileWidth);
step.fY = SkIntToScalar(y*kTileHeight);
step.fPaint = new SkPaint;
step.fPaint->setColorFilter(
SkColorFilters::Blend(colors[x][y], SkBlendMode::kModulate));
step.fSurf = create_compat_surface(finalCanvas, kTileWidth, kTileHeight);
SkCanvas* subCanvas = step.fSurf->getCanvas();
const SkMatrix trans = SkMatrix::MakeTrans(-SkIntToScalar(x*kTileWidth),
-SkIntToScalar(y*kTileHeight));
create_content(mpd, pfGen, pictures, subCanvas, trans);
}
}
}
constexpr PFLayoutMtd gLayoutMthds[] = { simple, tiled };
namespace skiagm {
/**
* This GM exercises the SkMultiPictureDraw object. It tests the
* cross product of:
* tiled vs. all-at-once rendering (e.g., into many or just 1 canvas)
* different clips (e.g., none, rect, rrect)
* single vs. multiple pictures (e.g., normal vs. picture-pile-style content)
*/
class MultiPictureDraw : public GM {
public:
enum Content {
kNoClipSingle_Content,
kRectClipMulti_Content,
kRRectClipMulti_Content,
kPathClipMulti_Content,
kInvPathClipMulti_Content,
kSierpinski_Content,
kBigLayer_Content,
kLast_Content = kBigLayer_Content
};
const int kContentCnt = kLast_Content + 1;
enum Layout {
kSimple_Layout,
kTiled_Layout,
kLast_Layout = kTiled_Layout
};
const int kLayoutCnt = kLast_Layout + 1;
MultiPictureDraw(Content content, Layout layout) : fContent(content), fLayout(layout) {
SkASSERT(SK_ARRAY_COUNT(gLayoutMthds) == kLayoutCnt);
SkASSERT(SK_ARRAY_COUNT(gContentMthds) == kContentCnt);
for (int i = 0; i < kNumPictures; ++i) {
fPictures[i] = nullptr;
}
}
~MultiPictureDraw() override {
for (int i = 0; i < kNumPictures; ++i) {
SkSafeUnref(fPictures[i]);
}
}
protected:
Content fContent;
Layout fLayout;
const SkPicture* fPictures[kNumPictures];
void onOnceBeforeDraw() override {
fPictures[0] = make_hex_plane_picture(SK_ColorWHITE).release();
fPictures[1] = make_hex_plane_picture(SK_ColorGRAY).release();
fPictures[2] = make_sierpinski_picture().release();
fPictures[3] = make_single_layer_hex_plane_picture().release();
}
void onDraw(SkCanvas* canvas) override {
SkMultiPictureDraw mpd;
SkTArray<ComposeStep> composeSteps;
// Fill up the MultiPictureDraw
(*gLayoutMthds[fLayout])(canvas, &mpd,
gContentMthds[fContent],
fPictures, &composeSteps);
mpd.draw();
// Compose all the drawn canvases into the final canvas
for (int i = 0; i < composeSteps.count(); ++i) {
const ComposeStep& step = composeSteps[i];
canvas->drawImage(step.fSurf->makeImageSnapshot().get(),
step.fX, step.fY, step.fPaint);
}
}
SkISize onISize() override { return SkISize::Make(kPicWidth, kPicHeight); }
SkString onShortName() override {
const char* gContentNames[] = {
"noclip", "rectclip", "rrectclip", "pathclip",
"invpathclip", "sierpinski", "biglayer"
};
const char* gLayoutNames[] = { "simple", "tiled" };
SkASSERT(SK_ARRAY_COUNT(gLayoutNames) == kLayoutCnt);
SkASSERT(SK_ARRAY_COUNT(gContentNames) == kContentCnt);
SkString name("multipicturedraw_");
name.append(gContentNames[fContent]);
name.append("_");
name.append(gLayoutNames[fLayout]);
return name;
}
bool runAsBench() const override { return true; }
private:
typedef GM INHERITED;
};
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kNoClipSingle_Content,
MultiPictureDraw::kSimple_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kRectClipMulti_Content,
MultiPictureDraw::kSimple_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kRRectClipMulti_Content,
MultiPictureDraw::kSimple_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kPathClipMulti_Content,
MultiPictureDraw::kSimple_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kInvPathClipMulti_Content,
MultiPictureDraw::kSimple_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kSierpinski_Content,
MultiPictureDraw::kSimple_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kBigLayer_Content,
MultiPictureDraw::kSimple_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kNoClipSingle_Content,
MultiPictureDraw::kTiled_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kRectClipMulti_Content,
MultiPictureDraw::kTiled_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kRRectClipMulti_Content,
MultiPictureDraw::kTiled_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kPathClipMulti_Content,
MultiPictureDraw::kTiled_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kInvPathClipMulti_Content,
MultiPictureDraw::kTiled_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kSierpinski_Content,
MultiPictureDraw::kTiled_Layout);)
DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kBigLayer_Content,
MultiPictureDraw::kTiled_Layout);)
}