skia2/tools/PictureRenderer.cpp
scroggo@google.com b6e806bf17 Allow render_pictures to render using multiple threads.
Make write() a static function so it can be used by the
thread entry functions.

Add a helper function to append a number to a string and
call write to share code.

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

git-svn-id: http://skia.googlecode.com/svn/trunk@5789 2bbb7eff-a529-9590-31e7-b0007b416f81
2012-10-03 17:32:33 +00:00

526 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 "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;
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();
fPicture = NULL;
fCanvas.reset(NULL);
}
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
}
/**
* 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*) {
SkPicture replayer;
SkCanvas* recorder = replayer.beginRecording(fPicture->width(), fPicture->height());
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();
return path != NULL && write(fCanvas, *path);
}
///////////////////////////////////////////////////////////////////////////////////////////////
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();
return path != NULL && write(fCanvas, *path);
}
///////////////////////////////////////////////////////////////////////////////////////////////
TiledPictureRenderer::TiledPictureRenderer()
: fUsePipe(false)
, fTileWidth(kDefaultTileWidth)
, fTileHeight(kDefaultTileHeight)
, fTileWidthPercentage(0.0)
, fTileHeightPercentage(0.0)
, fTileMinPowerOf2Width(0)
, fTileCounter(0)
, fNumThreads(1)
, fPictureClones(NULL)
, fPipeController(NULL) { }
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;
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();
}
if (this->multiThreaded()) {
for (int i = 0; i < fNumThreads; ++i) {
*fCanvasPool.append() = this->setupCanvas(fTileWidth, fTileHeight);
}
if (!fUsePipe) {
SkASSERT(NULL == fPictureClones);
// Only need to create fNumThreads - 1 clones, since one thread will use the base
// picture.
int numberOfClones = fNumThreads - 1;
// This will be deleted in end().
fPictureClones = SkNEW_ARRAY(SkPicture, numberOfClones);
fPicture->clone(fPictureClones, numberOfClones);
}
}
}
void TiledPictureRenderer::end() {
fTileRects.reset();
SkDELETE_ARRAY(fPictureClones);
fPictureClones = NULL;
fCanvasPool.unrefAll();
if (fPipeController != NULL) {
SkASSERT(fUsePipe);
SkDELETE(fPipeController);
fPipeController = NULL;
}
this->INHERITED::end();
}
TiledPictureRenderer::~TiledPictureRenderer() {
// end() must be called to delete fPictureClones and fPipeController
SkASSERT(NULL == fPictureClones);
SkASSERT(NULL == fPipeController);
}
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();
}
///////////////////////////////////////////////////////////////////////////////////////////////
// Base class for data used both by pipe and clone picture multi threaded drawing.
struct ThreadData {
ThreadData(SkCanvas* target, int* tileCounter, SkTDArray<SkRect>* tileRects,
const SkString* path, bool* success)
: fCanvas(target)
, fTileCounter(tileCounter)
, fTileRects(tileRects)
, fPath(path)
, fSuccess(success) {
SkASSERT(target != NULL && tileCounter != NULL && tileRects != NULL);
// Success must start off true, and it will be set to false upon failure.
SkASSERT(success != NULL && *success);
}
int32_t nextTile(SkRect* rect) {
int32_t i = sk_atomic_inc(fTileCounter);
if (i < fTileRects->count()) {
SkASSERT(rect != NULL);
*rect = fTileRects->operator[](i);
return i;
}
return -1;
}
// All of these are pointers to objects owned elsewhere
SkCanvas* fCanvas;
const SkString* fPath;
bool* fSuccess;
private:
// Shared by all threads, this states which is the next tile to be drawn.
int32_t* fTileCounter;
// Points to the array of rectangles. The array is already created before any threads are
// started and then it is unmodified, so there is no danger of race conditions.
const SkTDArray<SkRect>* fTileRects;
};
///////////////////////////////////////////////////////////////////////////////////////////////
// Draw using Pipe
struct TileData : public ThreadData {
TileData(ThreadSafePipeController* controller, SkCanvas* canvas, int* tileCounter,
SkTDArray<SkRect>* tileRects, const SkString* path, bool* success)
: INHERITED(canvas, tileCounter, tileRects, path, success)
, fController(controller) {}
ThreadSafePipeController* fController;
typedef ThreadData INHERITED;
};
static void DrawTile(void* data) {
SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024);
TileData* tileData = static_cast<TileData*>(data);
SkRect tileRect;
int32_t i;
while ((i = tileData->nextTile(&tileRect)) != -1) {
DrawTileToCanvas(tileData->fCanvas, tileRect, tileData->fController);
if (!writeAppendNumber(tileData->fCanvas, tileData->fPath, i)) {
*tileData->fSuccess = false;
break;
}
}
SkDELETE(tileData);
}
///////////////////////////////////////////////////////////////////////////////////////////////
// Draw using Picture
struct CloneData : public ThreadData {
CloneData(SkPicture* clone, SkCanvas* target, int* tileCounter, SkTDArray<SkRect>* tileRects,
const SkString* path, bool* success)
: INHERITED(target, tileCounter, tileRects, path, success)
, fClone(clone) {}
SkPicture* fClone;
typedef ThreadData INHERITED;
};
static void DrawClonedTiles(void* data) {
SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024);
CloneData* cloneData = static_cast<CloneData*>(data);
SkRect tileRect;
int32_t i;
while ((i = cloneData->nextTile(&tileRect)) != -1) {
DrawTileToCanvas(cloneData->fCanvas, tileRect, cloneData->fClone);
if (!writeAppendNumber(cloneData->fCanvas, cloneData->fPath, i)) {
*cloneData->fSuccess = false;
break;
}
}
SkDELETE(cloneData);
}
///////////////////////////////////////////////////////////////////////////////////////////////
void TiledPictureRenderer::setup() {
if (this->multiThreaded()) {
// Reset to zero so we start with the first tile.
fTileCounter = 0;
if (fUsePipe) {
// Record the picture into the pipe controller. It is done here because unlike
// SkPicture, the pipe is modified (bitmaps can be removed) by drawing.
// fPipeController is deleted here after each call to render() except the last one and
// in end() for the last one.
if (fPipeController != NULL) {
SkDELETE(fPipeController);
}
fPipeController = SkNEW_ARGS(ThreadSafePipeController, (fTileRects.count()));
SkGPipeWriter writer;
SkCanvas* pipeCanvas = writer.startRecording(fPipeController,
SkGPipeWriter::kSimultaneousReaders_Flag);
SkASSERT(fPicture != NULL);
fPicture->draw(pipeCanvas);
writer.endRecording();
}
}
}
bool TiledPictureRenderer::render(const SkString* path) {
SkASSERT(fPicture != NULL);
if (NULL == fPicture) {
return false;
}
if (this->multiThreaded()) {
SkASSERT(fCanvasPool.count() == fNumThreads);
SkTDArray<SkThread*> threads;
SkThread::entryPointProc proc = fUsePipe ? DrawTile : DrawClonedTiles;
bool success = true;
for (int i = 0; i < fNumThreads; ++i) {
// data will be deleted by the entryPointProc.
ThreadData* data;
if (fUsePipe) {
data = SkNEW_ARGS(TileData, (fPipeController, fCanvasPool[i], &fTileCounter,
&fTileRects, path, &success));
} else {
SkPicture* pic = (0 == i) ? fPicture : &fPictureClones[i-1];
data = SkNEW_ARGS(CloneData, (pic, fCanvasPool[i], &fTileCounter, &fTileRects, path,
&success));
}
SkThread* thread = SkNEW_ARGS(SkThread, (proc, data));
if (!thread->start()) {
SkDebugf("Could not start %s thread %i.\n", (fUsePipe ? "pipe" : "picture"), i);
}
*threads.append() = thread;
}
SkASSERT(threads.count() == fNumThreads);
for (int i = 0; i < fNumThreads; ++i) {
SkThread* thread = threads[i];
thread->join();
SkDELETE(thread);
}
threads.reset();
return success;
} else {
// For single thread, we really only need one canvas total.
SkCanvas* canvas = this->setupCanvas(fTileWidth, fTileHeight);
SkAutoUnref aur(canvas);
for (int i = 0; i < fTileRects.count(); ++i) {
DrawTileToCanvas(canvas, fTileRects[i], fPicture);
if (!writeAppendNumber(canvas, path, i)) {
return false;
}
}
return path != NULL;
}
}
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;
}
///////////////////////////////////////////////////////////////////////////////////////////////
void PlaybackCreationRenderer::setup() {
SkCanvas* recorder = fReplayer.beginRecording(fPicture->width(), fPicture->height());
fPicture->draw(recorder);
}
bool PlaybackCreationRenderer::render(const SkString*) {
fReplayer.endRecording();
// Since this class does not actually render, return false.
return false;
}
}