skia2/tests/CrossContextImageTest.cpp
Brian Osman 9ad1f92e2f Add GrExternalTextureData and SkCrossContextImageData
GrExternalTextureData is an API for exporting the backend-specific
information about a texture in a type-safe way, and without pointing
into the GrTexture. The new detachBackendTexture API lets us release
ownership of a texture to the client.

SkCrossContextImageData is the public API that lets clients upload
textures on one thread/GrContext, then safely transfer ownership to
another thread and GrContext for rendering.

Only GL is implemented/supported right now. Vulkan support requires
that we add thread-safe memory pools, or otherwise transfer the
actual memory block containing the texture to the new context.

BUG=skia:

Change-Id: I784a3a74be69807df038c7d192eaed002c7e45ca
Reviewed-on: https://skia-review.googlesource.com/8529
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
2017-02-24 16:09:33 +00:00

219 lines
7.8 KiB
C++

/*
* 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 "SkTypes.h"
#if SK_SUPPORT_GPU
#include "GrContextFactory.h"
#include "Resources.h"
#include "SkAutoPixmapStorage.h"
#include "SkBitmap.h"
#include "SkCanvas.h"
#include "SkCrossContextImageData.h"
#include "SkSemaphore.h"
#include "SkSurface.h"
#include "SkThreadUtils.h"
#include "Test.h"
using namespace sk_gpu_test;
static SkImageInfo read_pixels_info(SkImage* image) {
return SkImageInfo::MakeN32(image->width(), image->height(), image->alphaType());
}
static bool colors_are_close(SkColor a, SkColor b, int error) {
return SkTAbs((int)SkColorGetR(a) - (int)SkColorGetR(b)) <= error &&
SkTAbs((int)SkColorGetG(a) - (int)SkColorGetG(b)) <= error &&
SkTAbs((int)SkColorGetB(a) - (int)SkColorGetB(b)) <= error;
}
static void assert_equal(skiatest::Reporter* reporter, SkImage* a, SkImage* b, int error) {
REPORTER_ASSERT(reporter, a->width() == b->width());
REPORTER_ASSERT(reporter, a->height() == b->height());
SkAutoPixmapStorage pmapA, pmapB;
pmapA.alloc(read_pixels_info(a));
pmapB.alloc(read_pixels_info(b));
REPORTER_ASSERT(reporter, a->readPixels(pmapA, 0, 0));
REPORTER_ASSERT(reporter, b->readPixels(pmapB, 0, 0));
for (int y = 0; y < a->height(); ++y) {
for (int x = 0; x < a->width(); ++x) {
SkColor ca = pmapA.getColor(x, y);
SkColor cb = pmapB.getColor(x, y);
if (!error) {
if (ca != cb) {
ERRORF(reporter, "Expected 0x%08x but got 0x%08x at (%d, %d)", ca, cb, x, y);
return;
}
} else {
if (!colors_are_close(ca, cb, error)) {
ERRORF(reporter, "Expected 0x%08x +-%d but got 0x%08x at (%d, %d)",
ca, error, cb, x, y);
return;
}
}
}
}
}
static void draw_image_test_pattern(SkCanvas* canvas) {
canvas->clear(SK_ColorWHITE);
SkPaint paint;
paint.setColor(SK_ColorBLACK);
canvas->drawRect(SkRect::MakeXYWH(5, 5, 10, 10), paint);
}
static sk_sp<SkImage> create_test_image() {
SkBitmap bm;
bm.allocN32Pixels(20, 20, true);
SkCanvas canvas(bm);
draw_image_test_pattern(&canvas);
return SkImage::MakeFromBitmap(bm);
}
static sk_sp<SkData> create_test_data(SkEncodedImageFormat format) {
auto image = create_test_image();
return sk_sp<SkData>(image->encode(format, 100));
}
DEF_GPUTEST(CrossContextImage_SameContext, reporter, /*factory*/) {
GrContextFactory factory;
sk_sp<SkImage> testImage = create_test_image();
// Test both PNG and JPG, to exercise GPU YUV conversion
for (auto format : { SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kJPEG }) {
sk_sp<SkData> encoded = create_test_data(format);
for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) {
GrContextFactory::ContextType ctxType = static_cast<GrContextFactory::ContextType>(i);
if (!sk_gpu_test::GrContextFactory::IsRenderingContext(ctxType)) {
continue;
}
ContextInfo info = factory.getContextInfo(ctxType);
if (!info.grContext()) {
continue;
}
auto ccid = SkCrossContextImageData::MakeFromEncoded(info.grContext(), encoded,
nullptr);
REPORTER_ASSERT(reporter, ccid != nullptr);
auto image = SkImage::MakeFromCrossContextImageData(info.grContext(), std::move(ccid));
REPORTER_ASSERT(reporter, image != nullptr);
// JPEG encode -> decode won't round trip the image perfectly
assert_equal(reporter, testImage.get(), image.get(),
SkEncodedImageFormat::kJPEG == format ? 2 : 0);
}
}
}
DEF_GPUTEST(CrossContextImage_SharedContextSameThread, reporter, /*factory*/) {
GrContextFactory factory;
sk_sp<SkImage> testImage = create_test_image();
// Test both PNG and JPG, to exercise GPU YUV conversion
for (auto format : { SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kJPEG }) {
sk_sp<SkData> encoded = create_test_data(format);
for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) {
GrContextFactory::ContextType ctxType = static_cast<GrContextFactory::ContextType>(i);
if (!sk_gpu_test::GrContextFactory::IsRenderingContext(ctxType)) {
continue;
}
ContextInfo info = factory.getContextInfo(ctxType);
if (!info.grContext()) {
continue;
}
auto ccid = SkCrossContextImageData::MakeFromEncoded(info.grContext(), encoded,
nullptr);
REPORTER_ASSERT(reporter, ccid != nullptr);
ContextInfo info2 = factory.getSharedContextInfo(info.grContext());
auto image = SkImage::MakeFromCrossContextImageData(info2.grContext(), std::move(ccid));
REPORTER_ASSERT(reporter, image != nullptr);
// JPEG encode -> decode won't round trip the image perfectly
assert_equal(reporter, testImage.get(), image.get(),
SkEncodedImageFormat::kJPEG == format ? 2 : 0);
}
}
}
namespace {
struct CrossContextImage_ThreadContext {
GrContext* fGrContext;
sk_gpu_test::TestContext* fTestContext;
SkSemaphore fSemaphore;
std::unique_ptr<SkCrossContextImageData> fCCID;
sk_sp<SkData> fEncoded;
};
}
static void upload_image_thread_proc(void* data) {
CrossContextImage_ThreadContext* ctx = static_cast<CrossContextImage_ThreadContext*>(data);
ctx->fTestContext->makeCurrent();
ctx->fCCID = SkCrossContextImageData::MakeFromEncoded(ctx->fGrContext, ctx->fEncoded, nullptr);
ctx->fSemaphore.signal();
}
DEF_GPUTEST(CrossContextImage_SharedContextOtherThread, reporter, /*factory*/) {
GrContextFactory factory;
sk_sp<SkImage> testImage = create_test_image();
// Test both PNG and JPG, to exercise GPU YUV conversion
for (auto format : { SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kJPEG }) {
sk_sp<SkData> encoded = create_test_data(format);
for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) {
GrContextFactory::ContextType ctxType = static_cast<GrContextFactory::ContextType>(i);
if (!sk_gpu_test::GrContextFactory::IsRenderingContext(ctxType)) {
continue;
}
// Create two GrContexts in a share group
ContextInfo info = factory.getContextInfo(ctxType);
if (!info.grContext()) {
continue;
}
ContextInfo info2 = factory.getSharedContextInfo(info.grContext());
if (!info2.grContext()) {
continue;
}
// Make the first one current (on this thread) again
info.testContext()->makeCurrent();
// Bundle up data for the worker thread
CrossContextImage_ThreadContext ctx;
ctx.fGrContext = info2.grContext();
ctx.fTestContext = info2.testContext();
ctx.fEncoded = encoded;
SkThread uploadThread(upload_image_thread_proc, &ctx);
SkAssertResult(uploadThread.start());
ctx.fSemaphore.wait();
auto image = SkImage::MakeFromCrossContextImageData(info.grContext(),
std::move(ctx.fCCID));
REPORTER_ASSERT(reporter, image != nullptr);
// JPEG encode -> decode won't round trip the image perfectly
assert_equal(reporter, testImage.get(), image.get(),
SkEncodedImageFormat::kJPEG == format ? 2 : 0);
}
}
}
#endif