skia2/gm/multipicturedraw.cpp
mtklein dbfd7ab108 Replace a lot of 'static const' with 'constexpr' or 'const'.
'static const' means, there must be at most one of these, and initialize it at
compile time if possible or runtime if necessary.  This leads to unexpected
code execution, and TSAN* will complain about races on the guard variables.

Generally 'constexpr' or 'const' are better choices.  Neither can cause races:
they're either intialized at compile time (constexpr) or intialized each time
independently (const).

This CL prefers constexpr where possible, and uses const where not.  It even
prefers constexpr over const where they don't make a difference... I want to have
lots of examples of constexpr for people to see and mimic.

The scoped-to-class static has nothing to do with any of this, and is not changed.

* Not yet on the bots, which use an older TSAN.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2300623005

Review-Url: https://codereview.chromium.org/2300623005
2016-09-01 11:24:54 -07:00

567 lines
19 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.h"
#include "SkColorFilter.h"
#include "SkMultiPictureDraw.h"
#include "SkPictureRecorder.h"
#include "SkSurface.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_tool_utils::color_to_565(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_tool_utils::color_to_565(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);
auto surface = canvas->makeSurface(info);
if (nullptr == surface) {
// picture canvas returns nullptr so fall back to raster
surface = SkSurface::MakeRaster(info);
}
return surface;
}
// 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(
SkColorFilter::MakeModeFilter(colors[x][y], SkXfermode::kModulate_Mode));
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;
}
}
virtual ~MultiPictureDraw() {
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_tool_utils::color_to_565(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);)
}