DeferredDisplayList API proposal

Chrome would like to perform cpu-side preprocessing for gpu draws in parallel. 
They do not want to go through a picture (since they have their own display list format).


The general idea is that we add a new SkDeferredDisplayListRecorder class to
perform all of Ganesh's cpu-side preprocessing ahead of time and in parallel.

The SkDDLRecorder operates like SkPictureRecorder. The user can get an SkCanvas
from the SkDDLRecorder and feed it draw operations. Once finished, the user
calls 'detach' to get an SkDeferredDisplayList. All the work up to and 
including the 'detach' call can be done in parallel and will not touch
the GPU. To actually get pixels the client must call SkSurface::draw(SkDDL)
on an SkSurface that is "compatible" with the surface characterization
initially given to the SkDDLMaker.

The surface characterization contains the minimum amount of information Ganesh needs 
to know about the ultimate destination in order to perform its cpu-side work
(i.e., caps, width, height, config).



Change-Id: I75faa483ab5a6b779c8de56ea56b9d90b990f43a
Reviewed-on: https://skia-review.googlesource.com/30140
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Robert Phillips 2017-08-30 12:06:35 -04:00 committed by Skia Commit-Bot
parent f1942de288
commit ad8a43f769
14 changed files with 427 additions and 13 deletions

View File

@ -92,6 +92,8 @@ DEFINE_int32(shard, 0, "Which shard do I run?");
DEFINE_string(mskps, "", "Directory to read mskps from, or a single mskp file.");
DEFINE_bool(forceRasterPipeline, false, "sets gSkForceRasterPipelineBlitter");
DEFINE_bool(ddl, false, "If true, use DeferredDisplayLists for GPU SKP rendering.");
#if SK_SUPPORT_GPU
DEFINE_pathrenderer_flag;
#endif
@ -773,7 +775,11 @@ static bool gather_srcs() {
push_src("gm", "", new GMSrc(r->factory()));
}
gather_file_srcs<SKPSrc>(FLAGS_skps, "skp");
if (FLAGS_ddl) {
gather_file_srcs<DDLSKPSrc>(FLAGS_skps, "skp");
} else {
gather_file_srcs<SKPSrc>(FLAGS_skps, "skp");
}
gather_file_srcs<MSKPSrc>(FLAGS_mskps, "mskp");
#if defined(SK_XML)
gather_file_srcs<SVGSrc>(FLAGS_svgs, "svg");

View File

@ -19,6 +19,7 @@
#include "SkData.h"
#include "SkDebugCanvas.h"
#include "SkDeferredCanvas.h"
#include "SkDeferredDisplayListRecorder.h"
#include "SkDocument.h"
#include "SkExecutor.h"
#include "SkImageGenerator.h"
@ -39,9 +40,11 @@
#include "SkRandom.h"
#include "SkRecordDraw.h"
#include "SkRecorder.h"
#include "SkSurfaceCharacterization.h"
#include "SkSVGCanvas.h"
#include "SkStream.h"
#include "SkSwizzler.h"
#include "SkTaskGroup.h"
#include "SkTLogic.h"
#include <cmath>
#include <functional>
@ -1140,37 +1143,51 @@ Name ColorCodecSrc::name() const {
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
static const SkRect kSKPViewport = {0,0, 1000,1000};
static const SkRect kSKPViewport = {0, 0, 1000, 1000};
SKPSrc::SKPSrc(Path path) : fPath(path) {}
SKPSrc::SKPSrc(Path path) : fPath(path) { }
Error SKPSrc::draw(SkCanvas* canvas) const {
std::unique_ptr<SkStream> stream = SkStream::MakeFromFile(fPath.c_str());
static sk_sp<SkPicture> read_skp(const char* path) {
std::unique_ptr<SkStream> stream = SkStream::MakeFromFile(path);
if (!stream) {
return SkStringPrintf("Couldn't read %s.", fPath.c_str());
return nullptr;
}
sk_sp<SkPicture> pic(SkPicture::MakeFromStream(stream.get()));
if (!pic) {
return SkStringPrintf("Couldn't decode %s as a picture.", fPath.c_str());
return nullptr;
}
stream = nullptr; // Might as well drop this when we're done with it.
return pic;
}
Error SKPSrc::draw(SkCanvas* canvas) const {
sk_sp<SkPicture> pic = read_skp(fPath.c_str());
if (!pic) {
return SkStringPrintf("Couldn't read %s.", fPath.c_str());
}
canvas->clipRect(kSKPViewport);
canvas->drawPicture(pic);
return "";
}
SkISize SKPSrc::size() const {
std::unique_ptr<SkStream> stream = SkStream::MakeFromFile(fPath.c_str());
static SkRect get_cull_rect_for_skp(const char* path) {
std::unique_ptr<SkStream> stream = SkStream::MakeFromFile(path);
if (!stream) {
return {0, 0};
return SkRect::MakeEmpty();
}
SkPictInfo info;
if (!SkPicture::InternalOnly_StreamIsSKP(stream.get(), &info)) {
return {0, 0};
return SkRect::MakeEmpty();
}
SkRect viewport = kSKPViewport;
if (!viewport.intersect(info.fCullRect)) {
return info.fCullRect;
}
SkISize SKPSrc::size() const {
SkRect viewport = get_cull_rect_for_skp(fPath.c_str());
if (!viewport.intersect(kSKPViewport)) {
return {0, 0};
}
return viewport.roundOut().size();
@ -1178,6 +1195,123 @@ SkISize SKPSrc::size() const {
Name SKPSrc::name() const { return SkOSPath::Basename(fPath.c_str()); }
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
static const int kNumDDLXTiles = 4;
static const int kNumDDLYTiles = 4;
static const int kDDLTileSize = 1024;
static const SkRect kDDLSKPViewport = { 0, 0,
kNumDDLXTiles * kDDLTileSize,
kNumDDLYTiles * kDDLTileSize };
DDLSKPSrc::DDLSKPSrc(Path path) : fPath(path) { }
Error DDLSKPSrc::draw(SkCanvas* canvas) const {
class TileData {
public:
// Note: we could just pass in surface characterization
TileData(sk_sp<SkSurface> surf, const SkIRect& clip)
: fSurface(std::move(surf))
, fClip(clip) {
SkAssertResult(fSurface->characterize(&fCharacterization));
}
// This method operates in parallel
void preprocess(SkPicture* pic) {
SkDeferredDisplayListRecorder recorder(fCharacterization);
SkCanvas* subCanvas = recorder.getCanvas();
subCanvas->clipRect(SkRect::MakeWH(fClip.width(), fClip.height()));
subCanvas->translate(-fClip.fLeft, -fClip.fTop);
// Note: in this use case we only render a picture to the deferred canvas
// but, more generally, clients will use arbitrary draw calls.
subCanvas->drawPicture(pic);
fDisplayList = recorder.detach();
}
// This method operates serially
void draw() {
fSurface->draw(fDisplayList.get());
}
// This method also operates serially
void compose(SkCanvas* dst) {
sk_sp<SkImage> img = fSurface->makeImageSnapshot();
dst->save();
dst->clipRect(SkRect::Make(fClip));
dst->drawImage(std::move(img), fClip.fLeft, fClip.fTop);
dst->restore();
}
private:
sk_sp<SkSurface> fSurface;
SkIRect fClip; // in the device space of the destination canvas
std::unique_ptr<SkDeferredDisplayList> fDisplayList;
SkSurfaceCharacterization fCharacterization;
};
SkTArray<TileData> tileData;
tileData.reserve(16);
sk_sp<SkPicture> pic = read_skp(fPath.c_str());
if (!pic) {
return SkStringPrintf("Couldn't read %s.", fPath.c_str());
}
const SkRect cullRect = pic->cullRect();
// All the destination tiles are the same size
const SkImageInfo tileII = SkImageInfo::MakeN32Premul(kDDLTileSize, kDDLTileSize);
// First, create the destination tiles
for (int y = 0; y < kNumDDLYTiles; ++y) {
for (int x = 0; x < kNumDDLXTiles; ++x) {
SkRect clip = SkRect::MakeXYWH(x * kDDLTileSize, y * kDDLTileSize,
kDDLTileSize, kDDLTileSize);
if (!clip.intersect(cullRect)) {
continue;
}
tileData.push_back(TileData(canvas->makeSurface(tileII), clip.roundOut()));
}
}
// Second, run the cpu pre-processing in threads
SkTaskGroup().batch(tileData.count(), [&](int i) {
tileData[i].preprocess(pic.get());
});
// Third, synchronously render the display lists into the dest tiles
// TODO: it would be cool to not wait until all the tiles are drawn to begin
// drawing to the GPU
for (int i = 0; i < tileData.count(); ++i) {
tileData[i].draw();
}
// Finally, compose the drawn tiles into the result
// Note: the separation between the tiles and the final composition better
// matches Chrome but costs us a copy
for (int i = 0; i < tileData.count(); ++i) {
tileData[i].compose(canvas);
}
return "";
}
SkISize DDLSKPSrc::size() const {
SkRect viewport = get_cull_rect_for_skp(fPath.c_str());
if (!viewport.intersect(kDDLSKPViewport)) {
return {0, 0};
}
return viewport.roundOut().size();
}
Name DDLSKPSrc::name() const { return SkOSPath::Basename(fPath.c_str()); }
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
#if defined(SK_XML)
// Used when the image doesn't have an intrinsic size.

View File

@ -248,6 +248,18 @@ private:
Path fPath;
};
// DeferredDisplayList flavor
class DDLSKPSrc : public Src {
public:
explicit DDLSKPSrc(Path path);
Error draw(SkCanvas*) const override;
SkISize size() const override;
Name name() const override;
private:
Path fPath;
};
#if defined(SK_XML)
} // namespace DM

View File

@ -96,6 +96,7 @@ skia_core_sources = [
"$_src/core/SkData.cpp",
"$_src/core/SkDataTable.cpp",
"$_src/core/SkDebug.cpp",
"$_src/core/SkDeferredDisplayListRecorder.cpp",
"$_src/core/SkDeque.cpp",
"$_src/core/SkDescriptor.h",
"$_src/core/SkDevice.cpp",
@ -371,6 +372,7 @@ skia_core_sources = [
"$_include/core/SkColorFilter.h",
"$_include/core/SkColorPriv.h",
"$_include/core/SkData.h",
"$_include/core/SkDeferredDisplayListRecorder.h",
"$_include/core/SkDeque.h",
"$_include/core/SkDrawable.h",
"$_include/core/SkDrawFilter.h",
@ -429,6 +431,7 @@ skia_core_sources = [
# private
"$_include/private/SkAtomics.h",
"$_include/private/SkChecksum.h",
"$_include/private/SkDeferredDisplayList.h",
"$_include/private/SkFixed.h",
"$_include/private/SkFloatBits.h",
"$_include/private/SkFloatingPoint.h",
@ -440,6 +443,7 @@ skia_core_sources = [
"$_include/private/SkSemaphore.h",
"$_include/private/SkShadowFlags.h",
"$_include/private/SkSpinlock.h",
"$_include/private/SkSurfaceCharacterization.h",
"$_include/private/SkTemplates.h",
"$_include/private/SkTArray.h",
"$_include/private/SkTDArray.h",

View File

@ -0,0 +1,52 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkDeferredDisplayListMaker_DEFINED
#define SkDeferredDisplayListMaker_DEFINED
#include "SkRefCnt.h"
#include "../private/SkDeferredDisplayList.h"
#include "../private/SkSurfaceCharacterization.h"
class SkCanvas;
class SkSurface; // TODO: remove
/*
* This class is intended to be used as:
* Get an SkSurfaceCharacterization from the ultimate intended gpu-backed destination SkSurface
* Create one of these (an SkDDLMaker) on the stack
* Get the canvas and render into it
* Snap off and hold on to an SkDeferredDisplayList
* Once your app actually needs the pixels, call SkSurface::draw(SkDeferredDisplayList*)
*
* This class never accesses the GPU but performs all the cpu work it can. It
* is thread-safe (i.e., one can break a scene into tiles and perform their cpu-side
* work in parallel ahead of time).
*/
class SkDeferredDisplayListRecorder {
public:
SkDeferredDisplayListRecorder(const SkSurfaceCharacterization&);
const SkSurfaceCharacterization& characterization() const {
return fCharacterization;
}
// The backing canvas will become invalid (and this entry point will return
// null) once 'detach' is called.
// Note: ownership of the SkCanvas is not transfered via this call.
SkCanvas* getCanvas();
std::unique_ptr<SkDeferredDisplayList> detach();
private:
SkSurfaceCharacterization fCharacterization;
sk_sp<SkSurface> fSurface; // temporary until we have a real implementation
};
#endif

View File

@ -15,7 +15,9 @@
#include "GrTypes.h"
class SkCanvas;
class SkDeferredDisplayList;
class SkPaint;
class SkSurfaceCharacterization;
class GrBackendRenderTarget;
class GrBackendSemaphore;
class GrContext;
@ -335,6 +337,21 @@ public:
*/
bool wait(int numSemaphores, const GrBackendSemaphore* waitSemaphores);
/**
* This creates a characterization of this SkSurface's properties that can
* be used to perform gpu-backend preprocessing in a separate thread (via
* the SkDeferredDisplayListRecorder).
* It will return false on failure (e.g., if the SkSurface is cpu-backed).
*/
bool characterize(SkSurfaceCharacterization* characterization) const;
/**
* Draw a deferred display list (created via SkDeferredDisplayListRecorder).
* The draw will be skipped if the characterization stored in the display list
* isn't compatible with this surface.
*/
void draw(SkDeferredDisplayList* deferredDisplayList);
protected:
SkSurface(int width, int height, const SkSurfaceProps*);
SkSurface(const SkImageInfo&, const SkSurfaceProps*);

View File

@ -0,0 +1,43 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkDeferredDisplayList_DEFINED
#define SkDeferredDisplayList_DEFINED
#include "SkSurfaceCharacterization.h"
class SkImage; // TODO: rm this
/*
* This class contains pre-processed gpu operations that can be replayed into
* an SkSurface via draw(SkDeferredDisplayList*).
*
* TODO: we probably need to expose this class so users can query it for memory usage.
*/
class SkDeferredDisplayList {
public:
SkDeferredDisplayList(const SkSurfaceCharacterization& characterization,
sk_sp<SkImage> image) // TODO rm this parameter
: fCharacterization(characterization)
, fImage(std::move(image)) {
}
const SkSurfaceCharacterization& characterization() const {
return fCharacterization;
}
// TODO: remove this. It is just scaffolding to get something up & running
void draw(SkSurface*);
private:
SkSurfaceCharacterization fCharacterization;
// TODO: actually store the GPU opLists
sk_sp<SkImage> fImage;
};
#endif

View File

@ -0,0 +1,54 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkSurfaceCharacterization_DEFINED
#define SkSurfaceCharacterization_DEFINED
#include "GrTypes.h"
class SkSurface;
// This class captures all the pertinent data about an SkSurface required
// to perform cpu-preprocessing for gpu-rendering.
class SkSurfaceCharacterization {
public:
SkSurfaceCharacterization()
: fOrigin(kBottomLeft_GrSurfaceOrigin)
, fWidth(0)
, fHeight(0)
, fConfig(kRGBA_8888_GrPixelConfig)
, fSampleCnt(0) {
}
void set(GrSurfaceOrigin origin,
int width, int height,
GrPixelConfig config,
int sampleCnt) {
fOrigin = origin;
fWidth = width;
fHeight = height;
fConfig = config;
fSampleCnt = sampleCnt;
}
GrSurfaceOrigin origin() const { return fOrigin; }
int width() const { return fWidth; }
int height() const { return fHeight; }
GrPixelConfig config() const { return fConfig; }
int sampleCount() const { return fSampleCnt; }
private:
GrSurfaceOrigin fOrigin;
int fWidth;
int fHeight;
GrPixelConfig fConfig;
int fSampleCnt;
// TODO: need to include caps!
// Maybe use GrContextThreadSafeProxy (it has the caps & the unique Context ID already)
};
#endif

View File

@ -0,0 +1,46 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkDeferredDisplayListRecorder.h"
#include "SkCanvas.h" // TODO: remove
#include "SkDeferredDisplayList.h"
#include "SkSurface.h" // TODO: remove
SkDeferredDisplayListRecorder::SkDeferredDisplayListRecorder(
const SkSurfaceCharacterization& characterization)
: fCharacterization(characterization) {
}
SkCanvas* SkDeferredDisplayListRecorder::getCanvas() {
if (!fSurface) {
SkImageInfo ii = SkImageInfo::MakeN32(fCharacterization.width(),
fCharacterization.height(),
kOpaque_SkAlphaType);
// Use raster right now to allow threading
fSurface = SkSurface::MakeRaster(ii, nullptr);
}
return fSurface->getCanvas();
}
std::unique_ptr<SkDeferredDisplayList> SkDeferredDisplayListRecorder::detach() {
sk_sp<SkImage> img = fSurface->makeImageSnapshot();
fSurface.reset();
// TODO: need to wrap the opLists associated with the deferred draws
// in the SkDeferredDisplayList.
return std::unique_ptr<SkDeferredDisplayList>(
new SkDeferredDisplayList(fCharacterization, std::move(img)));
}
// Placeholder. Ultimately, the SkSurface_Gpu will pass the wrapped opLists to its
// renderTargetContext.
void SkDeferredDisplayList::draw(SkSurface* surface) {
surface->getCanvas()->drawImage(fImage.get(), 0, 0);
}

View File

@ -209,6 +209,14 @@ bool SkSurface::wait(int numSemaphores, const GrBackendSemaphore* waitSemaphores
return asSB(this)->onWait(numSemaphores, waitSemaphores);
}
bool SkSurface::characterize(SkSurfaceCharacterization* characterization) const {
return asSB(const_cast<SkSurface*>(this))->onCharacterize(characterization);
}
void SkSurface::draw(SkDeferredDisplayList* ddl) {
return asSB(this)->onDraw(ddl);
}
//////////////////////////////////////////////////////////////////////////////////////
#include "SkNoDrawCanvas.h"

View File

@ -94,6 +94,9 @@ public:
return false;
}
virtual bool onCharacterize(SkSurfaceCharacterization*) const { return false; }
virtual void onDraw(SkDeferredDisplayList*) { }
inline SkCanvas* getCachedCanvas();
inline sk_sp<SkImage> refCachedImage();

View File

@ -15,11 +15,13 @@
#include "SkCanvas.h"
#include "SkColorSpace_Base.h"
#include "SkDeferredDisplayList.h"
#include "SkGpuDevice.h"
#include "SkImage_Base.h"
#include "SkImage_Gpu.h"
#include "SkImagePriv.h"
#include "SkSurface_Base.h"
#include "SkSurfaceCharacterization.h"
#if SK_SUPPORT_GPU
@ -158,6 +160,35 @@ bool SkSurface_Gpu::onWait(int numSemaphores, const GrBackendSemaphore* waitSema
return fDevice->wait(numSemaphores, waitSemaphores);
}
bool SkSurface_Gpu::onCharacterize(SkSurfaceCharacterization* data) const {
GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext();
data->set(rtc->origin(), rtc->width(), rtc->height(),
rtc->config(), rtc->numColorSamples());
return true;
}
bool SkSurface_Gpu::isCompatible(const SkSurfaceCharacterization& data) const {
GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext();
return data.origin() == rtc->origin() &&
data.width() == rtc->width() &&
data.height() == rtc->height() &&
data.config() == rtc->config() &&
data.sampleCount() == rtc->numColorSamples();
}
void SkSurface_Gpu::onDraw(SkDeferredDisplayList* dl) {
if (!this->isCompatible(dl->characterization())) {
return;
}
// Ultimately need to pass opLists from the DeferredDisplayList on to the
// SkGpuDevice's renderTargetContext.
dl->draw(this);
}
///////////////////////////////////////////////////////////////////////////////
bool SkSurface_Gpu::Valid(const SkImageInfo& info) {

View File

@ -29,6 +29,9 @@ public:
GrSemaphoresSubmitted onFlush(int numSemaphores,
GrBackendSemaphore signalSemaphores[]) override;
bool onWait(int numSemaphores, const GrBackendSemaphore* waitSemaphores) override;
bool onCharacterize(SkSurfaceCharacterization*) const override;
bool isCompatible(const SkSurfaceCharacterization&) const;
void onDraw(SkDeferredDisplayList*) override;
SkGpuDevice* getDevice() { return fDevice.get(); }

View File

@ -25,6 +25,7 @@ DECLARE_bool(preAbandonGpuContext);
DECLARE_bool(abandonGpuContext);
DECLARE_bool(releaseAndAbandonGpuContext);
DECLARE_string(skps);
DECLARE_bool(ddl);
DECLARE_string(svgs);
DECLARE_int32(threads);
DECLARE_string(resourcePath);