blob: 5c8b21e9b6b7d1780abf3c58e6d4303cd995d583 [file] [log] [blame]
/*
* 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());
GrContext* ctx2 = info2.grContext();
int resourceCountBefore = 0, resourceCountAfter = 0;
size_t resourceBytesBefore = 0, resourceBytesAfter = 0;
if (ctx2 && info.grContext()->caps()->crossContextTextureSupport()) {
ctx2->getResourceCacheUsage(&resourceCountBefore, &resourceBytesBefore);
}
auto image = SkImage::MakeFromCrossContextImageData(ctx2, std::move(ccid));
REPORTER_ASSERT(reporter, image != nullptr);
if (ctx2 && info.grContext()->caps()->crossContextTextureSupport()) {
// MakeFromCrossContextImageData should have imported the texture back into our
// cache, so we should see an uptick. (If we have crossContextTextureSupport,
// otherwise we're just handing around a CPU or codec-backed image, so no cache
// impact will occur).
ctx2->getResourceCacheUsage(&resourceCountAfter, &resourceBytesAfter);
REPORTER_ASSERT(reporter, resourceCountAfter == resourceCountBefore + 1);
REPORTER_ASSERT(reporter, resourceBytesAfter > resourceBytesBefore);
}
// 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*/) {
sk_sp<SkImage> testImage = create_test_image();
// Test both PNG and JPG, to exercise GPU YUV conversion
for (auto format : { SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kJPEG }) {
// Use a new factory for each batch of tests. Otherwise the shared context will still be
// current on the upload thread when we do the second iteration, and we get undefined
// behavior.
GrContextFactory factory;
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);
uploadThread.join();
}
}
}
#endif