/* * 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 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 create_test_data(SkEncodedImageFormat format) { auto image = create_test_image(); return sk_sp(image->encode(format, 100)); } DEF_GPUTEST(CrossContextImage_SameContext, reporter, /*factory*/) { GrContextFactory factory; sk_sp testImage = create_test_image(); // Test both PNG and JPG, to exercise GPU YUV conversion for (auto format : { SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kJPEG }) { sk_sp encoded = create_test_data(format); for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) { GrContextFactory::ContextType ctxType = static_cast(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 testImage = create_test_image(); // Test both PNG and JPG, to exercise GPU YUV conversion for (auto format : { SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kJPEG }) { sk_sp encoded = create_test_data(format); for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) { GrContextFactory::ContextType ctxType = static_cast(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 fCCID; sk_sp fEncoded; }; } static void upload_image_thread_proc(void* data) { CrossContextImage_ThreadContext* ctx = static_cast(data); ctx->fTestContext->makeCurrent(); ctx->fCCID = SkCrossContextImageData::MakeFromEncoded(ctx->fGrContext, ctx->fEncoded, nullptr); ctx->fSemaphore.signal(); } DEF_GPUTEST(CrossContextImage_SharedContextOtherThread, reporter, /*factory*/) { sk_sp 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 encoded = create_test_data(format); for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) { GrContextFactory::ContextType ctxType = static_cast(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