skia2/tools/PictureRenderer.cpp
scroggo@google.com a62da2fee7 In bench_pictures --multi, maintain thread local caches.
Builds on https://codereview.appspot.com/6718046/ by mtklein.

Previously, each iteration of drawing a picture started new threads to draw the picture. Since each thread is using thread local storage for the font cache, this means that each iteration had to start with an empty font cache.

The newly added MultiCorePictureRenderer, separated from TiledPictureRenderer, now starts the drawing threads at the beginning of the test using an SkThreadPool, and keeps them alive through all iterations, so the font cache can be reused.

For now, I have removed the pipe version of the threaded renderer.

Updated bench_pictures_main and render_pictures_main to use the new
renderer, and to unref a renderer before early exit.

Review URL: https://codereview.appspot.com/6777063

git-svn-id: http://skia.googlecode.com/svn/trunk@6285 2bbb7eff-a529-9590-31e7-b0007b416f81
2012-11-02 21:28:12 +00:00

529 lines
18 KiB
C++

/*
* Copyright 2012 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "PictureRenderer.h"
#include "picture_utils.h"
#include "SamplePipeControllers.h"
#include "SkCanvas.h"
#include "SkDevice.h"
#include "SkGPipe.h"
#if SK_SUPPORT_GPU
#include "SkGpuDevice.h"
#endif
#include "SkGraphics.h"
#include "SkImageEncoder.h"
#include "SkMatrix.h"
#include "SkPicture.h"
#include "SkRTree.h"
#include "SkScalar.h"
#include "SkString.h"
#include "SkTemplates.h"
#include "SkTDArray.h"
#include "SkThreadUtils.h"
#include "SkTypes.h"
namespace sk_tools {
enum {
kDefaultTileWidth = 256,
kDefaultTileHeight = 256
};
void PictureRenderer::init(SkPicture* pict) {
SkASSERT(NULL == fPicture);
SkASSERT(NULL == fCanvas.get());
if (fPicture != NULL || NULL != fCanvas.get()) {
return;
}
SkASSERT(pict != NULL);
if (NULL == pict) {
return;
}
fPicture = pict;
fPicture->ref();
fCanvas.reset(this->setupCanvas());
}
SkCanvas* PictureRenderer::setupCanvas() {
return this->setupCanvas(fPicture->width(), fPicture->height());
}
SkCanvas* PictureRenderer::setupCanvas(int width, int height) {
switch(fDeviceType) {
case kBitmap_DeviceType: {
SkBitmap bitmap;
sk_tools::setup_bitmap(&bitmap, width, height);
return SkNEW_ARGS(SkCanvas, (bitmap));
break;
}
#if SK_SUPPORT_GPU
case kGPU_DeviceType: {
SkAutoTUnref<SkGpuDevice> device(SkNEW_ARGS(SkGpuDevice,
(fGrContext, SkBitmap::kARGB_8888_Config,
width, height)));
return SkNEW_ARGS(SkCanvas, (device.get()));
break;
}
#endif
default:
SkASSERT(0);
}
return NULL;
}
void PictureRenderer::end() {
this->resetState();
SkSafeUnref(fPicture);
fPicture = NULL;
fCanvas.reset(NULL);
}
/** Converts fPicture to a picture that uses a BBoxHierarchy.
* PictureRenderer subclasses that are used to test picture playback
* should call this method during init.
*/
void PictureRenderer::buildBBoxHierarchy() {
SkASSERT(NULL != fPicture);
if (kNone_BBoxHierarchyType != fBBoxHierarchyType && NULL != fPicture) {
SkPicture* newPicture = this->createPicture();
SkCanvas* recorder = newPicture->beginRecording(fPicture->width(), fPicture->height(),
this->recordFlags());
fPicture->draw(recorder);
newPicture->endRecording();
fPicture->unref();
fPicture = newPicture;
}
}
void PictureRenderer::resetState() {
#if SK_SUPPORT_GPU
if (this->isUsingGpuDevice()) {
SkGLContext* glContext = fGrContextFactory.getGLContext(
GrContextFactory::kNative_GLContextType);
SkASSERT(glContext != NULL);
if (NULL == glContext) {
return;
}
fGrContext->flush();
SK_GL(*glContext, Finish());
}
#endif
}
uint32_t PictureRenderer::recordFlags() {
return kNone_BBoxHierarchyType == fBBoxHierarchyType ? 0 :
SkPicture::kOptimizeForClippedPlayback_RecordingFlag;
}
/**
* Write the canvas to the specified path.
* @param canvas Must be non-null. Canvas to be written to a file.
* @param path Path for the file to be written. Should have no extension; write() will append
* an appropriate one. Passed in by value so it can be modified.
* @return bool True if the Canvas is written to a file.
*/
static bool write(SkCanvas* canvas, SkString path) {
SkASSERT(canvas != NULL);
if (NULL == canvas) {
return false;
}
SkBitmap bitmap;
SkISize size = canvas->getDeviceSize();
sk_tools::setup_bitmap(&bitmap, size.width(), size.height());
canvas->readPixels(&bitmap, 0, 0);
sk_tools::force_all_opaque(bitmap);
// Since path is passed in by value, it is okay to modify it.
path.append(".png");
return SkImageEncoder::EncodeFile(path.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
}
/**
* If path is non NULL, append number to it, and call write(SkCanvas*, SkString) to write the
* provided canvas to a file. Returns true if path is NULL or if write() succeeds.
*/
static bool writeAppendNumber(SkCanvas* canvas, const SkString* path, int number) {
if (NULL == path) {
return true;
}
SkString pathWithNumber(*path);
pathWithNumber.appendf("%i", number);
return write(canvas, pathWithNumber);
}
///////////////////////////////////////////////////////////////////////////////////////////////
bool RecordPictureRenderer::render(const SkString*) {
SkAutoTUnref<SkPicture> replayer(this->createPicture());
SkCanvas* recorder = replayer->beginRecording(fPicture->width(), fPicture->height(),
this->recordFlags());
fPicture->draw(recorder);
replayer->endRecording();
// Since this class does not actually render, return false.
return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////
bool PipePictureRenderer::render(const SkString* path) {
SkASSERT(fCanvas.get() != NULL);
SkASSERT(fPicture != NULL);
if (NULL == fCanvas.get() || NULL == fPicture) {
return false;
}
PipeController pipeController(fCanvas.get());
SkGPipeWriter writer;
SkCanvas* pipeCanvas = writer.startRecording(&pipeController);
pipeCanvas->drawPicture(*fPicture);
writer.endRecording();
fCanvas->flush();
if (NULL != path) {
return write(fCanvas, *path);
}
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////
void SimplePictureRenderer::init(SkPicture* picture) {
INHERITED::init(picture);
this->buildBBoxHierarchy();
}
bool SimplePictureRenderer::render(const SkString* path) {
SkASSERT(fCanvas.get() != NULL);
SkASSERT(fPicture != NULL);
if (NULL == fCanvas.get() || NULL == fPicture) {
return false;
}
fCanvas->drawPicture(*fPicture);
fCanvas->flush();
if (NULL != path) {
return write(fCanvas, *path);
}
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////
TiledPictureRenderer::TiledPictureRenderer()
: fTileWidth(kDefaultTileWidth)
, fTileHeight(kDefaultTileHeight)
, fTileWidthPercentage(0.0)
, fTileHeightPercentage(0.0)
, fTileMinPowerOf2Width(0) { }
void TiledPictureRenderer::init(SkPicture* pict) {
SkASSERT(pict != NULL);
SkASSERT(0 == fTileRects.count());
if (NULL == pict || fTileRects.count() != 0) {
return;
}
// Do not call INHERITED::init(), which would create a (potentially large) canvas which is not
// used by bench_pictures.
fPicture = pict;
fPicture->ref();
this->buildBBoxHierarchy();
if (fTileWidthPercentage > 0) {
fTileWidth = sk_float_ceil2int(float(fTileWidthPercentage * fPicture->width() / 100));
}
if (fTileHeightPercentage > 0) {
fTileHeight = sk_float_ceil2int(float(fTileHeightPercentage * fPicture->height() / 100));
}
if (fTileMinPowerOf2Width > 0) {
this->setupPowerOf2Tiles();
} else {
this->setupTiles();
}
}
void TiledPictureRenderer::end() {
fTileRects.reset();
this->INHERITED::end();
}
void TiledPictureRenderer::setupTiles() {
for (int tile_y_start = 0; tile_y_start < fPicture->height(); tile_y_start += fTileHeight) {
for (int tile_x_start = 0; tile_x_start < fPicture->width(); tile_x_start += fTileWidth) {
*fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start),
SkIntToScalar(tile_y_start),
SkIntToScalar(fTileWidth),
SkIntToScalar(fTileHeight));
}
}
}
// The goal of the powers of two tiles is to minimize the amount of wasted tile
// space in the width-wise direction and then minimize the number of tiles. The
// constraints are that every tile must have a pixel width that is a power of
// two and also be of some minimal width (that is also a power of two).
//
// This is solved by first taking our picture size and rounding it up to the
// multiple of the minimal width. The binary representation of this rounded
// value gives us the tiles we need: a bit of value one means we need a tile of
// that size.
void TiledPictureRenderer::setupPowerOf2Tiles() {
int rounded_value = fPicture->width();
if (fPicture->width() % fTileMinPowerOf2Width != 0) {
rounded_value = fPicture->width() - (fPicture->width() % fTileMinPowerOf2Width)
+ fTileMinPowerOf2Width;
}
int num_bits = SkScalarCeilToInt(SkScalarLog2(SkIntToScalar(fPicture->width())));
int largest_possible_tile_size = 1 << num_bits;
// The tile height is constant for a particular picture.
for (int tile_y_start = 0; tile_y_start < fPicture->height(); tile_y_start += fTileHeight) {
int tile_x_start = 0;
int current_width = largest_possible_tile_size;
// Set fTileWidth to be the width of the widest tile, so that each canvas is large enough
// to draw each tile.
fTileWidth = current_width;
while (current_width >= fTileMinPowerOf2Width) {
// It is very important this is a bitwise AND.
if (current_width & rounded_value) {
*fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start),
SkIntToScalar(tile_y_start),
SkIntToScalar(current_width),
SkIntToScalar(fTileHeight));
tile_x_start += current_width;
}
current_width >>= 1;
}
}
}
/**
* Draw the specified playback to the canvas translated to rectangle provided, so that this mini
* canvas represents the rectangle's portion of the overall picture.
* Saves and restores so that the initial clip and matrix return to their state before this function
* is called.
*/
template<class T>
static void DrawTileToCanvas(SkCanvas* canvas, const SkRect& tileRect, T* playback) {
int saveCount = canvas->save();
// Translate so that we draw the correct portion of the picture
canvas->translate(-tileRect.fLeft, -tileRect.fTop);
playback->draw(canvas);
canvas->restoreToCount(saveCount);
canvas->flush();
}
///////////////////////////////////////////////////////////////////////////////////////////////
bool TiledPictureRenderer::render(const SkString* path) {
SkASSERT(fPicture != NULL);
if (NULL == fPicture) {
return false;
}
// Reuse one canvas for all tiles.
SkCanvas* canvas = this->setupCanvas(fTileWidth, fTileHeight);
SkAutoUnref aur(canvas);
bool success = true;
for (int i = 0; i < fTileRects.count(); ++i) {
DrawTileToCanvas(canvas, fTileRects[i], fPicture);
if (NULL != path) {
success &= writeAppendNumber(canvas, path, i);
}
}
return success;
}
SkCanvas* TiledPictureRenderer::setupCanvas(int width, int height) {
SkCanvas* canvas = this->INHERITED::setupCanvas(width, height);
SkASSERT(fPicture != NULL);
// Clip the tile to an area that is completely in what the SkPicture says is the
// drawn-to area. This is mostly important for tiles on the right and bottom edges
// as they may go over this area and the picture may have some commands that
// draw outside of this area and so should not actually be written.
SkRect clip = SkRect::MakeWH(SkIntToScalar(fPicture->width()),
SkIntToScalar(fPicture->height()));
canvas->clipRect(clip);
return canvas;
}
///////////////////////////////////////////////////////////////////////////////////////////////
// Holds all of the information needed to draw a set of tiles.
class CloneData : public SkRunnable {
public:
CloneData(SkPicture* clone, SkCanvas* canvas, SkTDArray<SkRect>& rects, int start, int end,
SkRunnable* done)
: fClone(clone)
, fCanvas(canvas)
, fPath(NULL)
, fRects(rects)
, fStart(start)
, fEnd(end)
, fSuccess(NULL)
, fDone(done) {
SkASSERT(fDone != NULL);
}
virtual void run() SK_OVERRIDE {
SkGraphics::SetTLSFontCacheLimit(1024 * 1024);
for (int i = fStart; i < fEnd; i++) {
DrawTileToCanvas(fCanvas, fRects[i], fClone);
if (fPath != NULL && !writeAppendNumber(fCanvas, fPath, i)
&& fSuccess != NULL) {
*fSuccess = false;
// If one tile fails to write to a file, do not continue drawing the rest.
break;
}
}
fDone->run();
}
void setPathAndSuccess(const SkString* path, bool* success) {
fPath = path;
fSuccess = success;
}
private:
// All pointers unowned.
SkPicture* fClone; // Picture to draw from. Each CloneData has a unique one which
// is threadsafe.
SkCanvas* fCanvas; // Canvas to draw to. Reused for each tile.
const SkString* fPath; // If non-null, path to write the result to as a PNG.
SkTDArray<SkRect>& fRects; // All tiles of the picture.
const int fStart; // Range of tiles drawn by this thread.
const int fEnd;
bool* fSuccess; // Only meaningful if path is non-null. Shared by all threads,
// and only set to false upon failure to write to a PNG.
SkRunnable* fDone;
};
MultiCorePictureRenderer::MultiCorePictureRenderer(int threadCount)
: fNumThreads(threadCount)
, fThreadPool(threadCount)
, fCountdown(threadCount) {
// Only need to create fNumThreads - 1 clones, since one thread will use the base
// picture.
fPictureClones = SkNEW_ARRAY(SkPicture, fNumThreads - 1);
fCloneData = SkNEW_ARRAY(CloneData*, fNumThreads);
}
void MultiCorePictureRenderer::init(SkPicture *pict) {
// Set fPicture and the tiles.
this->INHERITED::init(pict);
for (int i = 0; i < fNumThreads; ++i) {
*fCanvasPool.append() = this->setupCanvas(this->getTileWidth(), this->getTileHeight());
}
// Only need to create fNumThreads - 1 clones, since one thread will use the base picture.
fPicture->clone(fPictureClones, fNumThreads - 1);
// Populate each thread with the appropriate data.
// Group the tiles into nearly equal size chunks, rounding up so we're sure to cover them all.
const int chunkSize = (fTileRects.count() + fNumThreads - 1) / fNumThreads;
for (int i = 0; i < fNumThreads; i++) {
SkPicture* pic;
if (i == fNumThreads-1) {
// The last set will use the original SkPicture.
pic = fPicture;
} else {
pic = &fPictureClones[i];
}
const int start = i * chunkSize;
const int end = SkMin32(start + chunkSize, fTileRects.count());
fCloneData[i] = SkNEW_ARGS(CloneData,
(pic, fCanvasPool[i], fTileRects, start, end, &fCountdown));
}
}
bool MultiCorePictureRenderer::render(const SkString *path) {
bool success = true;
if (path != NULL) {
for (int i = 0; i < fNumThreads-1; i++) {
fCloneData[i]->setPathAndSuccess(path, &success);
}
}
fCountdown.reset(fNumThreads);
for (int i = 0; i < fNumThreads; i++) {
fThreadPool.add(fCloneData[i]);
}
fCountdown.wait();
return success;
}
void MultiCorePictureRenderer::end() {
for (int i = 0; i < fNumThreads - 1; i++) {
SkDELETE(fCloneData[i]);
fCloneData[i] = NULL;
}
fCanvasPool.unrefAll();
this->INHERITED::end();
}
MultiCorePictureRenderer::~MultiCorePictureRenderer() {
// Each individual CloneData was deleted in end.
SkDELETE_ARRAY(fCloneData);
SkDELETE_ARRAY(fPictureClones);
}
///////////////////////////////////////////////////////////////////////////////////////////////
void PlaybackCreationRenderer::setup() {
fReplayer.reset(this->createPicture());
SkCanvas* recorder = fReplayer->beginRecording(fPicture->width(), fPicture->height(),
this->recordFlags());
fPicture->draw(recorder);
}
bool PlaybackCreationRenderer::render(const SkString*) {
fReplayer->endRecording();
// Since this class does not actually render, return false.
return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////
// SkPicture variants for each BBoxHierarchy type
class RTreePicture : public SkPicture {
public:
virtual SkBBoxHierarchy* createBBoxHierarchy() const SK_OVERRIDE{
static const int kRTreeMinChildren = 6;
static const int kRTreeMaxChildren = 11;
SkScalar aspectRatio = SkScalarDiv(SkIntToScalar(fWidth),
SkIntToScalar(fHeight));
return SkRTree::Create(kRTreeMinChildren, kRTreeMaxChildren,
aspectRatio);
}
};
SkPicture* PictureRenderer::createPicture() {
switch (fBBoxHierarchyType) {
case kNone_BBoxHierarchyType:
return SkNEW(SkPicture);
case kRTree_BBoxHierarchyType:
return SkNEW(RTreePicture);
}
SkASSERT(0); // invalid bbhType
return NULL;
}
} // namespace sk_tools