Accept a callback used in MultiPictureDocument::endPage
Tested on a pixel 3, captures file, shows images. b:skia:9765 Change-Id: I96f2e854dab21a9e15ff0f6f23c4e84f7616773e Reviewed-on: https://skia-review.googlesource.com/c/skia/+/344158 Reviewed-by: Derek Sollenberger <djsollen@google.com> Reviewed-by: Alec Mouri <alecmouri@google.com> Commit-Queue: Nathaniel Nifong <nifong@google.com>
This commit is contained in:
parent
6094ed698d
commit
4a5656834e
@ -17,6 +17,7 @@
|
||||
#include "src/utils/SkMultiPictureDocumentPriv.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <functional>
|
||||
|
||||
/*
|
||||
File format:
|
||||
@ -53,9 +54,12 @@ struct MultiPictureDocument final : public SkDocument {
|
||||
SkSize fCurrentPageSize;
|
||||
SkTArray<sk_sp<SkPicture>> fPages;
|
||||
SkTArray<SkSize> fSizes;
|
||||
MultiPictureDocument(SkWStream* s, const SkSerialProcs* procs)
|
||||
std::function<void(const SkPicture*)> fOnEndPage;
|
||||
MultiPictureDocument(SkWStream* s, const SkSerialProcs* procs,
|
||||
std::function<void(const SkPicture*)> onEndPage)
|
||||
: SkDocument(s)
|
||||
, fProcs(procs ? *procs : SkSerialProcs())
|
||||
, fOnEndPage(onEndPage)
|
||||
{}
|
||||
~MultiPictureDocument() override { this->close(); }
|
||||
|
||||
@ -65,7 +69,11 @@ struct MultiPictureDocument final : public SkDocument {
|
||||
}
|
||||
void onEndPage() override {
|
||||
fSizes.push_back(fCurrentPageSize);
|
||||
fPages.push_back(fPictureRecorder.finishRecordingAsPicture());
|
||||
sk_sp<SkPicture> lastPage = fPictureRecorder.finishRecordingAsPicture();
|
||||
fPages.push_back(lastPage);
|
||||
if (fOnEndPage) {
|
||||
fOnEndPage(lastPage.get());
|
||||
}
|
||||
}
|
||||
void onClose(SkWStream* wStream) override {
|
||||
SkASSERT(wStream);
|
||||
@ -96,8 +104,9 @@ struct MultiPictureDocument final : public SkDocument {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
sk_sp<SkDocument> SkMakeMultiPictureDocument(SkWStream* wStream, const SkSerialProcs* procs) {
|
||||
return sk_make_sp<MultiPictureDocument>(wStream, procs);
|
||||
sk_sp<SkDocument> SkMakeMultiPictureDocument(SkWStream* wStream, const SkSerialProcs* procs,
|
||||
std::function<void(const SkPicture*)> onEndPage) {
|
||||
return sk_make_sp<MultiPictureDocument>(wStream, procs, onEndPage);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -12,14 +12,17 @@
|
||||
#include "include/core/SkPicture.h"
|
||||
#include "include/core/SkSize.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
struct SkDeserialProcs;
|
||||
struct SkSerialProcs;
|
||||
class SkStreamSeekable;
|
||||
|
||||
/**
|
||||
* Writes into a file format that is similar to SkPicture::serialize()
|
||||
* Accepts a callback for endPage behavior
|
||||
*/
|
||||
SK_SPI sk_sp<SkDocument> SkMakeMultiPictureDocument(SkWStream* dst, const SkSerialProcs* = nullptr);
|
||||
SK_SPI sk_sp<SkDocument> SkMakeMultiPictureDocument(SkWStream* dst, const SkSerialProcs* = nullptr,
|
||||
std::function<void(const SkPicture*)> onEndPage = nullptr);
|
||||
|
||||
struct SkDocumentPage {
|
||||
sk_sp<SkPicture> fPicture;
|
||||
|
@ -9,8 +9,10 @@
|
||||
*/
|
||||
|
||||
#include "include/core/SkCanvas.h"
|
||||
#include "include/core/SkColorSpace.h"
|
||||
#include "include/core/SkDocument.h"
|
||||
#include "include/core/SkFont.h"
|
||||
#include "include/core/SkImage.h"
|
||||
#include "include/core/SkPicture.h"
|
||||
#include "include/core/SkPictureRecorder.h"
|
||||
#include "include/core/SkString.h"
|
||||
@ -77,7 +79,7 @@ static void draw_advanced(SkCanvas* canvas, int seed, sk_sp<SkImage> image, sk_s
|
||||
}
|
||||
|
||||
// Test serialization and deserialization of multi picture document
|
||||
DEF_TEST(Serialize_and_deserialize_multi_skp, reporter) {
|
||||
DEF_TEST(SkMultiPictureDocument_Serialize_and_deserialize, reporter) {
|
||||
// Create the stream we will serialize into.
|
||||
SkDynamicMemoryWStream stream;
|
||||
|
||||
@ -107,16 +109,16 @@ DEF_TEST(Serialize_and_deserialize_multi_skp, reporter) {
|
||||
sk_sp<SkPicture> sub = pr.finishRecordingAsPicture();
|
||||
|
||||
const SkImageInfo info = SkImageInfo::MakeN32Premul(WIDTH, HEIGHT);
|
||||
std::vector<sk_sp<SkImage>> pages;
|
||||
std::vector<sk_sp<SkImage>> expectedImages;
|
||||
|
||||
for (int i=0; i<NUM_FRAMES; i++) {
|
||||
SkCanvas* pictureCanvas = multipic->beginPage(WIDTH, HEIGHT);
|
||||
draw_advanced(pictureCanvas, i, image, sub);
|
||||
multipic->endPage();
|
||||
// Also record the same commands to separate SkRecords for later comparison
|
||||
// Also draw the picture to an image for later comparison
|
||||
auto surf = SkSurface::MakeRaster(info);
|
||||
draw_advanced(surf->getCanvas(), i, image, sub);
|
||||
pages.push_back(surf->makeImageSnapshot());
|
||||
expectedImages.push_back(surf->makeImageSnapshot());
|
||||
}
|
||||
// Finalize
|
||||
multipic->close();
|
||||
@ -157,8 +159,239 @@ DEF_TEST(Serialize_and_deserialize_multi_skp, reporter) {
|
||||
auto surf = SkSurface::MakeRaster(info);
|
||||
surf->getCanvas()->drawPicture(frame.fPicture);
|
||||
auto img = surf->makeImageSnapshot();
|
||||
REPORTER_ASSERT(reporter, ToolUtils::equal_pixels(img.get(), pages[i].get()));
|
||||
REPORTER_ASSERT(reporter, ToolUtils::equal_pixels(img.get(), expectedImages[i].get()));
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if SK_SUPPORT_GPU && defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26
|
||||
|
||||
#include "include/gpu/GrDirectContext.h"
|
||||
#include "src/gpu/GrAHardwareBufferUtils.h"
|
||||
#include "src/gpu/GrDirectContextPriv.h"
|
||||
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
static const int DEV_W = 16, DEV_H = 16;
|
||||
|
||||
static SkPMColor get_src_color(int x, int y) {
|
||||
SkASSERT(x >= 0 && x < DEV_W);
|
||||
SkASSERT(y >= 0 && y < DEV_H);
|
||||
|
||||
U8CPU r = x;
|
||||
U8CPU g = y;
|
||||
U8CPU b = 0xc;
|
||||
|
||||
U8CPU a = 0xff;
|
||||
switch ((x+y) % 5) {
|
||||
case 0:
|
||||
a = 0xff;
|
||||
break;
|
||||
case 1:
|
||||
a = 0x80;
|
||||
break;
|
||||
case 2:
|
||||
a = 0xCC;
|
||||
break;
|
||||
case 4:
|
||||
a = 0x01;
|
||||
break;
|
||||
case 3:
|
||||
a = 0x00;
|
||||
break;
|
||||
}
|
||||
a = 0xff;
|
||||
return SkPremultiplyARGBInline(a, r, g, b);
|
||||
}
|
||||
|
||||
static SkBitmap make_src_bitmap() {
|
||||
static SkBitmap bmp;
|
||||
if (bmp.isNull()) {
|
||||
bmp.allocN32Pixels(DEV_W, DEV_H);
|
||||
intptr_t pixels = reinterpret_cast<intptr_t>(bmp.getPixels());
|
||||
for (int y = 0; y < DEV_H; ++y) {
|
||||
for (int x = 0; x < DEV_W; ++x) {
|
||||
SkPMColor* pixel = reinterpret_cast<SkPMColor*>(
|
||||
pixels + y * bmp.rowBytes() + x * bmp.bytesPerPixel());
|
||||
*pixel = get_src_color(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bmp;
|
||||
}
|
||||
|
||||
static void cleanup_resources(AHardwareBuffer* buffer) {
|
||||
if (buffer) {
|
||||
AHardwareBuffer_release(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static sk_sp<SkImage> makeAHardwareBufferTestImage(
|
||||
skiatest::Reporter* reporter, GrDirectContext* context, AHardwareBuffer* buffer) {
|
||||
|
||||
const SkBitmap srcBitmap = make_src_bitmap();
|
||||
|
||||
AHardwareBuffer_Desc hwbDesc;
|
||||
hwbDesc.width = DEV_W;
|
||||
hwbDesc.height = DEV_H;
|
||||
hwbDesc.layers = 1;
|
||||
hwbDesc.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
|
||||
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
|
||||
AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE;
|
||||
hwbDesc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
|
||||
// The following three are not used in the allocate
|
||||
hwbDesc.stride = 0;
|
||||
hwbDesc.rfu0= 0;
|
||||
hwbDesc.rfu1= 0;
|
||||
|
||||
if (int error = AHardwareBuffer_allocate(&hwbDesc, &buffer)) {
|
||||
ERRORF(reporter, "Failed to allocated hardware buffer, error: %d", error);
|
||||
cleanup_resources(buffer);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get actual desc for allocated buffer so we know the stride for uploading cpu data.
|
||||
AHardwareBuffer_describe(buffer, &hwbDesc);
|
||||
|
||||
void* bufferAddr;
|
||||
if (AHardwareBuffer_lock(buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, nullptr,
|
||||
&bufferAddr)) {
|
||||
ERRORF(reporter, "Failed to lock hardware buffer");
|
||||
cleanup_resources(buffer);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// fill buffer
|
||||
int bbp = srcBitmap.bytesPerPixel();
|
||||
uint32_t* src = (uint32_t*)srcBitmap.getPixels();
|
||||
int nextLineStep = DEV_W;
|
||||
uint32_t* dst = static_cast<uint32_t*>(bufferAddr);
|
||||
for (int y = 0; y < DEV_H; ++y) {
|
||||
memcpy(dst, src, DEV_W * bbp);
|
||||
src += nextLineStep;
|
||||
dst += hwbDesc.stride;
|
||||
}
|
||||
AHardwareBuffer_unlock(buffer, nullptr);
|
||||
|
||||
// Make SkImage from buffer in a way that mimics libs/hwui/AutoBackendTextureRelease
|
||||
GrBackendFormat backendFormat =
|
||||
GrAHardwareBufferUtils::GetBackendFormat(context, buffer, hwbDesc.format, false);
|
||||
GrAHardwareBufferUtils::DeleteImageProc deleteProc;
|
||||
GrAHardwareBufferUtils::UpdateImageProc updateProc;
|
||||
GrAHardwareBufferUtils::TexImageCtx imageCtx;
|
||||
GrBackendTexture texture = GrAHardwareBufferUtils::MakeBackendTexture(
|
||||
context, buffer, hwbDesc.width, hwbDesc.height,
|
||||
&deleteProc, // set by MakeBackendTexture
|
||||
&updateProc, // set by MakeBackendTexture
|
||||
&imageCtx, // set by MakeBackendTexture
|
||||
false, // don't make protected image
|
||||
backendFormat,
|
||||
false // isRenderable
|
||||
);
|
||||
SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(hwbDesc.format);
|
||||
sk_sp<SkImage> image = SkImage::MakeFromTexture(
|
||||
context, texture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType,
|
||||
SkColorSpace::MakeSRGB(),
|
||||
nullptr, // no release proc
|
||||
nullptr // context for release proc
|
||||
);
|
||||
|
||||
REPORTER_ASSERT(reporter, image);
|
||||
REPORTER_ASSERT(reporter, image->isTextureBacked());
|
||||
return image;
|
||||
}
|
||||
|
||||
// Test the onEndPage callback's intended use by processing an mskp containing AHardwareBuffer-backed SkImages
|
||||
// Expected behavior is that the callback is called while the AHardwareBuffer is still valid and the
|
||||
// images are copied so .close() can still access them.
|
||||
// Confirm deserialized file contains images with correct data.
|
||||
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkMultiPictureDocument_AHardwarebuffer,
|
||||
reporter, ctx_info) {
|
||||
auto context = ctx_info.directContext();
|
||||
if (!context->priv().caps()->supportsAHardwareBufferImages()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the stream we will serialize into.
|
||||
SkDynamicMemoryWStream stream;
|
||||
|
||||
// Create the image sharing proc.
|
||||
SkSharingSerialContext ctx;
|
||||
SkSerialProcs procs;
|
||||
procs.fImageProc = SkSharingSerialContext::serializeImage;
|
||||
procs.fImageCtx = &ctx;
|
||||
|
||||
// Create the multi picture document used for recording frames.
|
||||
// Pass a lambda as the onEndPage callback that captures our sharing context
|
||||
sk_sp<SkDocument> multipic = SkMakeMultiPictureDocument(&stream, &procs,
|
||||
[sharingCtx = &ctx](const SkPicture* pic) {
|
||||
SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
|
||||
});
|
||||
|
||||
static const int WIDTH = 256;
|
||||
static const int HEIGHT = 256;
|
||||
|
||||
// Make an image to be used in a later step.
|
||||
AHardwareBuffer* ahbuffer = nullptr;
|
||||
sk_sp<SkImage> image = makeAHardwareBufferTestImage(reporter, context, ahbuffer);
|
||||
|
||||
const SkImageInfo info = SkImageInfo::MakeN32Premul(WIDTH, HEIGHT);
|
||||
std::vector<sk_sp<SkImage>> expectedImages;
|
||||
|
||||
// Record single frame
|
||||
SkCanvas* pictureCanvas = multipic->beginPage(WIDTH, HEIGHT);
|
||||
draw_basic(pictureCanvas, 0, image);
|
||||
multipic->endPage();
|
||||
// Also draw the picture to an image for later comparison
|
||||
auto surf = SkSurface::MakeRaster(info);
|
||||
draw_basic(surf->getCanvas(), 0, image);
|
||||
expectedImages.push_back(surf->makeImageSnapshot());
|
||||
|
||||
// Release Ahardwarebuffer. If the code under test has not copied it already,
|
||||
// close() will fail.
|
||||
// Note that this only works because we're doing one frame only. If this test were recording
|
||||
// two or more frames, it would have change the buffer contents instead.
|
||||
cleanup_resources(ahbuffer);
|
||||
|
||||
// Finalize
|
||||
multipic->close();
|
||||
|
||||
// Confirm written data is at least as large as the magic word
|
||||
std::unique_ptr<SkStreamAsset> writtenStream = stream.detachAsStream();
|
||||
REPORTER_ASSERT(reporter, writtenStream->getLength() > 24,
|
||||
"Written data length too short (%zu)", writtenStream->getLength());
|
||||
|
||||
// Set up deserialization
|
||||
SkSharingDeserialContext deserialContext;
|
||||
SkDeserialProcs dprocs;
|
||||
dprocs.fImageProc = SkSharingDeserialContext::deserializeImage;
|
||||
dprocs.fImageCtx = &deserialContext;
|
||||
|
||||
// Confirm data is a MultiPictureDocument
|
||||
int frame_count = SkMultiPictureDocumentReadPageCount(writtenStream.get());
|
||||
REPORTER_ASSERT(reporter, frame_count == 1,
|
||||
"Expected 1 frame, got %d. \n 0 frames may indicate the written file was not a "
|
||||
"MultiPictureDocument.", frame_count);
|
||||
|
||||
// Deserialize
|
||||
std::vector<SkDocumentPage> frames(frame_count);
|
||||
REPORTER_ASSERT(reporter,
|
||||
SkMultiPictureDocumentRead(writtenStream.get(), frames.data(), frame_count, &dprocs),
|
||||
"Failed while reading MultiPictureDocument");
|
||||
|
||||
// Examine frame.
|
||||
SkRect bounds = frames[0].fPicture->cullRect();
|
||||
REPORTER_ASSERT(reporter, bounds.width() == WIDTH,
|
||||
"Page width: expected (%d) got (%d)", WIDTH, (int)bounds.width());
|
||||
REPORTER_ASSERT(reporter, bounds.height() == HEIGHT,
|
||||
"Page height: expected (%d) got (%d)", HEIGHT, (int)bounds.height());
|
||||
|
||||
auto surf2 = SkSurface::MakeRaster(info);
|
||||
surf2->getCanvas()->drawPicture(frames[0].fPicture);
|
||||
auto img = surf2->makeImageSnapshot();
|
||||
REPORTER_ASSERT(reporter, ToolUtils::equal_pixels(img.get(), expectedImages[0].get()));
|
||||
}
|
||||
|
||||
#endif // android compilation
|
||||
|
@ -12,6 +12,27 @@
|
||||
#include "include/core/SkImage.h"
|
||||
#include "include/core/SkSerialProcs.h"
|
||||
|
||||
namespace {
|
||||
sk_sp<SkData> collectNonTextureImagesProc(SkImage* img, void* ctx) {
|
||||
SkSharingSerialContext* context = reinterpret_cast<SkSharingSerialContext*>(ctx);
|
||||
uint32_t originalId = img->uniqueID();
|
||||
auto it = context->fNonTexMap.find(originalId);
|
||||
if (it == context->fNonTexMap.end()) {
|
||||
context->fNonTexMap[originalId] = img->makeNonTextureImage();
|
||||
}
|
||||
return SkData::MakeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
void SkSharingSerialContext::collectNonTextureImagesFromPicture(
|
||||
const SkPicture* pic, SkSharingSerialContext* sharingCtx) {
|
||||
SkSerialProcs tempProc;
|
||||
tempProc.fImageCtx = sharingCtx;
|
||||
tempProc.fImageProc = collectNonTextureImagesProc;
|
||||
auto ns = SkNullWStream();
|
||||
pic->serialize(&ns, &tempProc);
|
||||
}
|
||||
|
||||
sk_sp<SkData> SkSharingSerialContext::serializeImage(SkImage* img, void* ctx) {
|
||||
SkSharingSerialContext* context = reinterpret_cast<SkSharingSerialContext*>(ctx);
|
||||
uint32_t id = img->uniqueID(); // get this process's id for the image. these are not hashes.
|
||||
@ -19,7 +40,12 @@ sk_sp<SkData> SkSharingSerialContext::serializeImage(SkImage* img, void* ctx) {
|
||||
auto iter = context->fImageMap.find(id);
|
||||
if (iter == context->fImageMap.end()) {
|
||||
// When not present, add its id to the map and return its usual serialized form.
|
||||
context->fImageMap[id] = context->fImageMap.size();
|
||||
context->fImageMap[id] = context->fImageMap.size(); // Next in-file id
|
||||
// encode the image or it's non-texture replacement if one was collected
|
||||
auto iter2 = context->fNonTexMap.find(id);
|
||||
if (iter2 != context->fNonTexMap.end()) {
|
||||
img = iter2->second.get();
|
||||
}
|
||||
return img->encodeToData();
|
||||
}
|
||||
uint32_t fid = context->fImageMap[id];
|
||||
@ -32,7 +58,7 @@ sk_sp<SkImage> SkSharingDeserialContext::deserializeImage(
|
||||
if (!data || !length || !ctx) {
|
||||
SkDebugf("SkSharingDeserialContext::deserializeImage arguments invalid %p %d %p.\n",
|
||||
data, length, ctx);
|
||||
// Return something so the rest of the debugger can proceeed.
|
||||
// Return something so the rest of the debugger can proceed.
|
||||
SkBitmap bm;
|
||||
bm.allocPixels(SkImageInfo::MakeN32Premul(1, 1));
|
||||
return bm.asImage();
|
||||
@ -43,7 +69,7 @@ sk_sp<SkImage> SkSharingDeserialContext::deserializeImage(
|
||||
if (length == sizeof(fid)) {
|
||||
memcpy(&fid, data, sizeof(fid));
|
||||
if (fid >= context->fImages.size()) {
|
||||
SkDebugf("We do not have the data for image %d.\n", fid);
|
||||
SkDebugf("Cannot deserialize using id, We do not have the data for image %d.\n", fid);
|
||||
return nullptr;
|
||||
}
|
||||
return context->fImages[fid];
|
||||
|
@ -15,8 +15,41 @@
|
||||
#include "include/core/SkImage.h"
|
||||
#include "include/core/SkSerialProcs.h"
|
||||
|
||||
/**
|
||||
* This serial proc serializes each image it encounters only once, using their uniqueId as the
|
||||
* property for sameness.
|
||||
*
|
||||
* It's most basic usage involves setting your imageProc to SkSharingSerialContext::serializeImage
|
||||
* and creating an SkSharingSerialContext in an appropriate scope to outlive all the images that
|
||||
* will be encountered before serialization.
|
||||
*
|
||||
* Optionally, collectNonTextureImagesFromPicture can be called with an SkSharingContext and an
|
||||
* SkPicture that may reference not-yet-released texture backed images. It will make non-texture
|
||||
* copies if necessary and store them in the context. If present, they will be used in the
|
||||
* final serialization.
|
||||
*
|
||||
* This is intended to be used on Android with MultiPictureDocument's onEndPage parameter, in a
|
||||
* lambda that captures the context, because MPD cannot make assumptions about the type of proc it
|
||||
* receives and clients (Chrome) build MPD without this source file.
|
||||
*/
|
||||
|
||||
struct SkSharingSerialContext {
|
||||
// --- Data and and function for optional texture collection pass --- //
|
||||
|
||||
// A map from uniqueID of images referenced by commands to non-texture images
|
||||
// collected at the end of each frame.
|
||||
std::unordered_map<uint32_t, sk_sp<SkImage>> fNonTexMap;
|
||||
|
||||
// Collects any non-texture images referenced by the picture and stores non-texture copies
|
||||
// in the fNonTexMap of the provided SkSharingContext
|
||||
static void collectNonTextureImagesFromPicture(
|
||||
const SkPicture* pic, SkSharingSerialContext* sharingCtx);
|
||||
|
||||
|
||||
// --- Data and serialization function for regular use --- //
|
||||
|
||||
// A map from the ids from SkImage::uniqueID() to ids used within the file
|
||||
// The keys are ids of original images, not of non-texture copies
|
||||
std::unordered_map<uint32_t, uint32_t> fImageMap;
|
||||
|
||||
// A serial proc that shares images between subpictures
|
||||
|
Loading…
Reference in New Issue
Block a user