Switch DDL rendering to be a Via in DM (take 2)
This will let us also render the GMs via DDLs. TBR=mtklein@google.com Change-Id: If7c2460d964822a6decc33cf5e8e685e67923127 Reviewed-on: https://skia-review.googlesource.com/116463 Reviewed-by: Mike Klein <mtklein@google.com> Commit-Queue: Robert Phillips <robertphillips@google.com>
This commit is contained in:
parent
2ef4525daf
commit
33f02edb14
@ -957,6 +957,8 @@ static Sink* create_via(const SkString& tag, Sink* wrapped) {
|
||||
VIA("tiles", ViaTiles, 256, 256, nullptr, wrapped);
|
||||
VIA("tiles_rt", ViaTiles, 256, 256, new SkRTreeFactory, wrapped);
|
||||
|
||||
VIA("ddl", ViaDDL, 3, wrapped);
|
||||
|
||||
if (FLAGS_matrix.count() == 4) {
|
||||
SkMatrix m;
|
||||
m.reset();
|
||||
|
371
dm/DMSrcSink.cpp
371
dm/DMSrcSink.cpp
@ -1153,8 +1153,6 @@ Name ColorCodecSrc::name() const {
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
static const SkRect kSKPViewport = {0, 0, 1000, 1000};
|
||||
|
||||
SKPSrc::SKPSrc(Path path) : fPath(path) { }
|
||||
|
||||
static sk_sp<SkPicture> read_skp(const char* path, const SkDeserialProcs* procs = nullptr) {
|
||||
@ -1177,7 +1175,7 @@ Error SKPSrc::draw(SkCanvas* canvas) const {
|
||||
return SkStringPrintf("Couldn't read %s.", fPath.c_str());
|
||||
}
|
||||
|
||||
canvas->clipRect(kSKPViewport);
|
||||
canvas->clipRect(SkRect::MakeWH(FLAGS_skpViewportSize, FLAGS_skpViewportSize));
|
||||
canvas->drawPicture(pic);
|
||||
return "";
|
||||
}
|
||||
@ -1197,7 +1195,7 @@ static SkRect get_cull_rect_for_skp(const char* path) {
|
||||
|
||||
SkISize SKPSrc::size() const {
|
||||
SkRect viewport = get_cull_rect_for_skp(fPath.c_str());
|
||||
if (!viewport.intersect(kSKPViewport)) {
|
||||
if (!viewport.intersect((SkRect::MakeWH(FLAGS_skpViewportSize, FLAGS_skpViewportSize)))) {
|
||||
return {0, 0};
|
||||
}
|
||||
return viewport.roundOut().size();
|
||||
@ -2325,6 +2323,371 @@ Error ViaTiles::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkStri
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
#if SK_SUPPORT_GPU
|
||||
|
||||
ViaDDL::ViaDDL(int numDivisions, Sink* sink)
|
||||
: Via(sink)
|
||||
, fNumDivisions(numDivisions) {
|
||||
}
|
||||
|
||||
// This class consolidates tracking & extraction of the original image data from the sources,
|
||||
// the upload of said data to the GPU and the fulfillment of promise images.
|
||||
class ViaDDL::PromiseImageHelper {
|
||||
public:
|
||||
class PromiseImageInfo {
|
||||
public:
|
||||
int fIndex; // index in the 'fImageInfo' array
|
||||
uint32_t fOriginalUniqueID; // original ID for deduping
|
||||
SkBitmap fBitmap; // CPU-side cache of the contents
|
||||
GrBackendTexture fBackendTexture; // GPU-side version
|
||||
};
|
||||
|
||||
PromiseImageHelper() : fLocked(false) { }
|
||||
|
||||
// This class will hand out pointers to its PromiseImageInfo. This is just some insurance
|
||||
// we won't be moving them around.
|
||||
void lock() { fLocked = true; }
|
||||
|
||||
bool isValidID(int id) const {
|
||||
return id >= 0 && id < fImageInfo.count();
|
||||
}
|
||||
|
||||
const PromiseImageInfo* getInfo(int id) const {
|
||||
SkASSERT(fLocked);
|
||||
return &fImageInfo[id];
|
||||
}
|
||||
|
||||
// returns -1 on failure
|
||||
int findOrDefineImage(SkImage* image) {
|
||||
int preExistingID = this->findImage(image);
|
||||
if (preExistingID >= 0) {
|
||||
SkASSERT(this->isValidID(preExistingID));
|
||||
return preExistingID;
|
||||
}
|
||||
|
||||
int newID = this->addImage(image);
|
||||
SkASSERT(this->isValidID(newID));
|
||||
return newID;
|
||||
}
|
||||
|
||||
void uploadAllToGPU(GrContext* context) {
|
||||
GrGpu* gpu = context->contextPriv().getGpu();
|
||||
SkASSERT(gpu);
|
||||
|
||||
for (int i = 0; i < fImageInfo.count(); ++i) {
|
||||
// DDL TODO: how can we tell if we need mipmapping!
|
||||
fImageInfo[i].fBackendTexture = gpu->createTestingOnlyBackendTexture(
|
||||
fImageInfo[i].fBitmap.getPixels(),
|
||||
fImageInfo[i].fBitmap.width(),
|
||||
fImageInfo[i].fBitmap.height(),
|
||||
fImageInfo[i].fBitmap.colorType(),
|
||||
false, GrMipMapped::kNo);
|
||||
SkAssertResult(fImageInfo[i].fBackendTexture.isValid());
|
||||
}
|
||||
}
|
||||
|
||||
void cleanUpVRAM(GrContext* context) {
|
||||
GrGpu* gpu = context->contextPriv().getGpu();
|
||||
SkASSERT(gpu);
|
||||
|
||||
for (int i = 0; i < fImageInfo.count(); ++i) {
|
||||
gpu->deleteTestingOnlyBackendTexture(fImageInfo[i].fBackendTexture);
|
||||
}
|
||||
}
|
||||
|
||||
static void PromiseImageFulfillProc(void* textureContext, GrBackendTexture* outTexture) {
|
||||
auto imgInfo = static_cast<const PromiseImageInfo*>(textureContext);
|
||||
|
||||
SkASSERT(imgInfo->fBackendTexture.isValid());
|
||||
*outTexture = imgInfo->fBackendTexture;
|
||||
}
|
||||
|
||||
static void PromiseImageReleaseProc(void* textureContext) {
|
||||
// Do nothing. We free all the backend textures at the end in cleanUpVRAM.
|
||||
}
|
||||
|
||||
static void PromiseImageDoneProc(void* textureContext) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private:
|
||||
// returns -1 if not found
|
||||
int findImage(SkImage* image) const {
|
||||
for (int i = 0; i < fImageInfo.count(); ++i) {
|
||||
if (fImageInfo[i].fOriginalUniqueID == image->uniqueID()) {
|
||||
SkASSERT(fImageInfo[i].fIndex == i);
|
||||
SkASSERT(this->isValidID(i) && this->isValidID(fImageInfo[i].fIndex));
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// returns -1 on failure
|
||||
int addImage(SkImage* image) {
|
||||
SkASSERT(!fLocked);
|
||||
|
||||
sk_sp<SkImage> rasterImage = image->makeRasterImage(); // force decoding of lazy images
|
||||
|
||||
SkImageInfo ii = SkImageInfo::Make(rasterImage->width(), rasterImage->height(),
|
||||
rasterImage->colorType(), rasterImage->alphaType(),
|
||||
rasterImage->refColorSpace());
|
||||
|
||||
SkBitmap bm;
|
||||
bm.allocPixels(ii);
|
||||
|
||||
if (!rasterImage->readPixels(bm.pixmap(), 0, 0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bm.setImmutable();
|
||||
|
||||
PromiseImageInfo newImageInfo;
|
||||
newImageInfo.fIndex = fImageInfo.count();
|
||||
newImageInfo.fOriginalUniqueID = image->uniqueID();
|
||||
newImageInfo.fBitmap = bm;
|
||||
/* fBackendTexture is filled in by uploadAllToGPU */
|
||||
|
||||
fImageInfo.push_back(newImageInfo);
|
||||
SkASSERT(newImageInfo.fIndex == fImageInfo.count()-1);
|
||||
return fImageInfo.count()-1;
|
||||
}
|
||||
|
||||
SkTArray<PromiseImageInfo> fImageInfo;
|
||||
bool fLocked; // are additions still allowed
|
||||
};
|
||||
|
||||
// TileData class encapsulates the information and behavior for a single tile/thread in
|
||||
// a DDL rendering.
|
||||
class ViaDDL::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
|
||||
// In each thread we will reconvert the compressedPictureData into an SkPicture
|
||||
// replacing each image-index with a promise image.
|
||||
void preprocess(SkData* compressedPictureData, const PromiseImageHelper& helper) {
|
||||
SkDeferredDisplayListRecorder recorder(fCharacterization);
|
||||
|
||||
// DDL TODO: the DDLRecorder's GrContext isn't initialized until getCanvas is called.
|
||||
// Maybe set it up in the ctor?
|
||||
SkCanvas* subCanvas = recorder.getCanvas();
|
||||
|
||||
sk_sp<SkPicture> reconstitutedPicture;
|
||||
|
||||
{
|
||||
PromiseImageCallbackContext callbackCtx = { &helper, &recorder };
|
||||
|
||||
SkDeserialProcs procs;
|
||||
procs.fImageCtx = &callbackCtx;
|
||||
procs.fImageProc = PromiseImageCreator;
|
||||
|
||||
reconstitutedPicture = SkPicture::MakeFromData(compressedPictureData, &procs);
|
||||
if (!reconstitutedPicture) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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(reconstitutedPicture);
|
||||
|
||||
fDisplayList = recorder.detach();
|
||||
}
|
||||
|
||||
// This method operates serially and replays the recorded DDL into the tile surface.
|
||||
void draw() {
|
||||
fSurface->draw(fDisplayList.get());
|
||||
}
|
||||
|
||||
// This method also operates serially and composes the results of replaying the DDL into
|
||||
// the final destination surface.
|
||||
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:
|
||||
// This class lets us pass the collected image information and the DDLRecorder to the
|
||||
// promise_image_creator callback when reconstituting a deflated SKP for a particular tile
|
||||
// (i.e., in a thread).
|
||||
class PromiseImageCallbackContext {
|
||||
public:
|
||||
const PromiseImageHelper* fHelper;
|
||||
SkDeferredDisplayListRecorder* fRecorder;
|
||||
};
|
||||
|
||||
// This generates promise images to replace the indices in the compressed picture. This
|
||||
// reconstitution is performed separately in each thread so we end of with multiple
|
||||
// promise image referring to the same GrBackendTexture.
|
||||
// DDL TODO: Having multiple promise images using the same GrBackendTexture won't work in
|
||||
// Vulkan! Move creation of the promise images to the main thread & SkImage.
|
||||
static sk_sp<SkImage> PromiseImageCreator(const void* rawData, size_t length, void* ctxIn) {
|
||||
PromiseImageCallbackContext* ctx = static_cast<PromiseImageCallbackContext*>(ctxIn);
|
||||
const PromiseImageHelper* helper = ctx->fHelper;
|
||||
SkDeferredDisplayListRecorder* recorder = ctx->fRecorder;
|
||||
|
||||
SkASSERT(length == sizeof(int));
|
||||
|
||||
const int* indexPtr = static_cast<const int*>(rawData);
|
||||
SkASSERT(helper->isValidID(*indexPtr));
|
||||
|
||||
const PromiseImageHelper::PromiseImageInfo* curImage = helper->getInfo(*indexPtr);
|
||||
SkASSERT(curImage->fIndex == *indexPtr);
|
||||
|
||||
GrBackendFormat backendFormat = curImage->fBackendTexture.format();
|
||||
|
||||
// DDL TODO: sort out mipmapping
|
||||
sk_sp<SkImage> image = recorder->makePromiseTexture(
|
||||
backendFormat,
|
||||
curImage->fBitmap.width(),
|
||||
curImage->fBitmap.height(),
|
||||
GrMipMapped::kNo,
|
||||
GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
|
||||
curImage->fBitmap.colorType(),
|
||||
curImage->fBitmap.alphaType(),
|
||||
curImage->fBitmap.refColorSpace(),
|
||||
PromiseImageHelper::PromiseImageFulfillProc,
|
||||
PromiseImageHelper::PromiseImageReleaseProc,
|
||||
PromiseImageHelper::PromiseImageDoneProc,
|
||||
(void*) curImage);
|
||||
SkASSERT(image);
|
||||
return image;
|
||||
}
|
||||
|
||||
sk_sp<SkSurface> fSurface;
|
||||
SkIRect fClip; // in the device space of the dest canvas
|
||||
std::unique_ptr<SkDeferredDisplayList> fDisplayList;
|
||||
SkSurfaceCharacterization fCharacterization;
|
||||
};
|
||||
|
||||
Error ViaDDL::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
|
||||
auto size = src.size();
|
||||
SkPictureRecorder recorder;
|
||||
Error err = src.draw(recorder.beginRecording(SkIntToScalar(size.width()),
|
||||
SkIntToScalar(size.height())));
|
||||
if (!err.isEmpty()) {
|
||||
return err;
|
||||
}
|
||||
sk_sp<SkPicture> inputPicture(recorder.finishRecordingAsPicture());
|
||||
|
||||
// this is our ultimate final drawing area/rect
|
||||
SkIRect viewport = SkIRect::MakeWH(size.fWidth, size.fHeight);
|
||||
|
||||
PromiseImageHelper helper;
|
||||
sk_sp<SkData> compressedPictureData;
|
||||
|
||||
// Convert the SkPicture into SkData replacing all the SkImages with an index.
|
||||
{
|
||||
SkSerialProcs procs;
|
||||
|
||||
procs.fImageCtx = &helper;
|
||||
procs.fImageProc = [](SkImage* image, void* ctx) -> sk_sp<SkData> {
|
||||
auto helper = static_cast<PromiseImageHelper*>(ctx);
|
||||
|
||||
int id = helper->findOrDefineImage(image);
|
||||
if (id >= 0) {
|
||||
SkASSERT(helper->isValidID(id));
|
||||
return SkData::MakeWithCopy(&id, sizeof(id));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
compressedPictureData = inputPicture->serialize(&procs);
|
||||
if (!compressedPictureData) {
|
||||
return SkStringPrintf("ViaDDL: Couldn't deflate SkPicture");
|
||||
}
|
||||
}
|
||||
|
||||
helper.lock(); // after this point no more images should be added to the helper
|
||||
|
||||
return draw_to_canvas(fSink.get(), bitmap, stream, log, size,
|
||||
[&](SkCanvas* canvas) -> Error {
|
||||
GrContext* context = canvas->getGrContext();
|
||||
if (!context || !context->contextPriv().getGpu()) {
|
||||
return SkStringPrintf("DDLs are GPU only");
|
||||
}
|
||||
|
||||
helper.uploadAllToGPU(context);
|
||||
|
||||
int xTileSize = viewport.width()/fNumDivisions;
|
||||
int yTileSize = viewport.height()/fNumDivisions;
|
||||
|
||||
SkTArray<TileData> tileData;
|
||||
tileData.reserve(fNumDivisions*fNumDivisions);
|
||||
|
||||
// First, create the destination tiles
|
||||
for (int y = 0, yOff = 0; y < fNumDivisions; ++y, yOff += yTileSize) {
|
||||
int ySize = (y < fNumDivisions-1) ? yTileSize : viewport.height()-yOff;
|
||||
|
||||
for (int x = 0, xOff = 0; x < fNumDivisions; ++x, xOff += xTileSize) {
|
||||
int xSize = (x < fNumDivisions-1) ? xTileSize : viewport.width()-xOff;
|
||||
|
||||
SkIRect clip = SkIRect::MakeXYWH(xOff, yOff, xSize, ySize);
|
||||
|
||||
SkASSERT(viewport.contains(clip));
|
||||
|
||||
SkImageInfo tileII = SkImageInfo::MakeN32Premul(xSize, ySize);
|
||||
|
||||
tileData.push_back(TileData(canvas->makeSurface(tileII), clip));
|
||||
}
|
||||
}
|
||||
|
||||
// Second, run the cpu pre-processing in threads
|
||||
SkTaskGroup().batch(tileData.count(), [&](int i) {
|
||||
tileData[i].preprocess(compressedPictureData.get(), helper);
|
||||
});
|
||||
|
||||
// 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 and composing to the final surface
|
||||
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);
|
||||
}
|
||||
|
||||
// All promise images need to be fulfilled before leaving this method since we
|
||||
// are about to delete their backing GrBackendTextures
|
||||
// DDL TODO: remove the cleanUpVRAM method and use the release & done
|
||||
// callbacks.
|
||||
GrGpu* gpu = context->contextPriv().getGpu();
|
||||
gpu->testingOnly_flushGpuAndSync();
|
||||
|
||||
helper.cleanUpVRAM(context);
|
||||
return "";
|
||||
});
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
ViaDDL::ViaDDL(int numDivisions, Sink* sink) : Via(sink) { }
|
||||
|
||||
Error ViaDDL::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
|
||||
return "ViaDDL is GPU only";
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
Error ViaPicture::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
|
||||
auto size = src.size();
|
||||
return draw_to_canvas(fSink.get(), bitmap, stream, log, size, [&](SkCanvas* canvas) -> Error {
|
||||
|
@ -537,6 +537,19 @@ private:
|
||||
std::unique_ptr<SkBBHFactory> fFactory;
|
||||
};
|
||||
|
||||
class ViaDDL : public Via {
|
||||
public:
|
||||
ViaDDL(int numDivisions, Sink* sink);
|
||||
Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
|
||||
private:
|
||||
#if SK_SUPPORT_GPU
|
||||
class PromiseImageHelper;
|
||||
class TileData;
|
||||
|
||||
const int fNumDivisions;
|
||||
#endif
|
||||
};
|
||||
|
||||
class ViaSVG : public Via {
|
||||
public:
|
||||
explicit ViaSVG(Sink* sink) : Via(sink) {}
|
||||
|
@ -63,6 +63,8 @@ DEFINE_string(jpgs, "jpgs", "Directory to read jpgs from.");
|
||||
DEFINE_string(jsons, "jsons", "Directory to read (Bodymovin) jsons from.");
|
||||
#endif
|
||||
|
||||
DEFINE_int32(skpViewportSize, 1000, "Width & height of the viewport used to crop skp rendering.");
|
||||
|
||||
DEFINE_int32(ddl, 0, "If > 0, the # of x & y divisions used for DeferredDisplayList-based "
|
||||
"GPU SKP rendering.");
|
||||
|
||||
|
@ -24,6 +24,7 @@ DECLARE_bool(preAbandonGpuContext);
|
||||
DECLARE_bool(abandonGpuContext);
|
||||
DECLARE_bool(releaseAndAbandonGpuContext);
|
||||
DECLARE_string(skps);
|
||||
DECLARE_int32(skpViewportSize);
|
||||
DECLARE_int32(ddl);
|
||||
DECLARE_string(jpgs);
|
||||
DECLARE_string(jsons);
|
||||
|
Loading…
Reference in New Issue
Block a user