/* * Copyright 2019 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "gm/gm.h" #include "include/core/SkCanvas.h" #include "include/core/SkColor.h" #include "include/core/SkPaint.h" #include "include/core/SkRect.h" #include "include/core/SkSurface.h" #include "include/core/SkYUVAInfo.h" #include "include/core/SkYUVAPixmaps.h" #include "include/effects/SkGradientShader.h" #include "include/gpu/ganesh/GrDirectContext.h" #include "include/gpu/ganesh/GrRecordingContext.h" #include "include/gpu/ganesh/SkImageGanesh.h" #include "src/base/SkScopeExit.h" #include "src/core/SkAutoPixmapStorage.h" #include "tools/DecodeUtils.h" #include "tools/Resources.h" #include "tools/ToolUtils.h" #include "tools/gpu/YUVUtils.h" #if defined(SK_GRAPHITE) #include "include/gpu/graphite/Context.h" #include "include/gpu/graphite/Image.h" #include "src/gpu/graphite/RecorderPriv.h" #include "tools/graphite/GraphiteTestContext.h" #endif #include namespace { /// We test reading from images and surfaces enum class ReadSource { kImage, kSurface, }; // We test reading to RGBA, YUV, and YUVA enum class Type { kRGBA, kYUV, kYUVA }; template struct SourceS; template <> struct SourceS { using Type = SkImage; }; template <> struct SourceS { using Type = SkSurface; }; template using Source = typename SourceS::Type; // Converts a source image to either an SkImage or SkSurface, backed by GPU if canvas is. Returns // kSkip or kFail if the image cannot be converted. template std::variant>, skiagm::DrawResult> convert_image_to_source(SkCanvas* canvas, sk_sp image, SkString* errorMsg); template <> std::variant, skiagm::DrawResult> convert_image_to_source( SkCanvas* canvas, sk_sp image, SkString* errorMsg) { #if defined(SK_GRAPHITE) if (auto recorder = canvas->recorder()) { image = SkImages::TextureFromImage(recorder, image); if (image) { return image; } *errorMsg = "Could not create Graphite image"; return skiagm::DrawResult::kFail; } #endif auto dContext = GrAsDirectContext(canvas->recordingContext()); if (!dContext && canvas->recordingContext()) { *errorMsg = "Not supported in DDL mode"; return skiagm::DrawResult::kSkip; } if (dContext) { image = SkImages::TextureFromImage(dContext, image); if (image) { return image; } // When testing abandoned GrContext we expect surface creation to fail. if (dContext && dContext->abandoned()) { return skiagm::DrawResult::kSkip; } *errorMsg = "Could not create Ganesh image"; return skiagm::DrawResult::kFail; } return image; } template <> std::variant, skiagm::DrawResult> convert_image_to_source( SkCanvas* canvas, sk_sp image, SkString* errorMsg) { // Turn the image into a surface in order to call the read and rescale API auto surfInfo = image->imageInfo().makeDimensions(image->dimensions()); auto surface = canvas->makeSurface(surfInfo); if (!surface && surfInfo.colorType() == kBGRA_8888_SkColorType) { surfInfo = surfInfo.makeColorType(kRGBA_8888_SkColorType); surface = canvas->makeSurface(surfInfo); } if (!surface) { *errorMsg = "Could not create surface for image."; // When testing abandoned GrContext we expect surface creation to fail. if (canvas->recordingContext() && canvas->recordingContext()->abandoned()) { return skiagm::DrawResult::kSkip; } return skiagm::DrawResult::kFail; } SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); surface->getCanvas()->drawImage(image, 0, 0, SkSamplingOptions(), &paint); return surface; } class AsyncReadGMBase : public skiagm::GM { public: AsyncReadGMBase(const char* name) : fName(name) {} SkString getName() const override { return fName; } protected: // Does a rescale and read using Graphite, Ganesh, or CPU and returns the result as a pixmap // image. template sk_sp readAndScaleRGBA(Source* src, SkIRect srcRect, GrDirectContext* direct, skgpu::graphite::Recorder* recorder, const SkImageInfo& ii, SkImage::RescaleGamma rescaleGamma, SkImage::RescaleMode rescaleMode) { auto* asyncContext = new AsyncContext(); if (recorder) { #if defined(SK_GRAPHITE) skgpu::graphite::Context* graphiteContext = recorder->priv().context(); if (!graphiteContext) { return nullptr; } // We need to flush the existing drawing commands before we try to read std::unique_ptr recording = recorder->snap(); if (!recording) { return nullptr; } skgpu::graphite::InsertRecordingInfo recordingInfo; recordingInfo.fRecording = recording.get(); if (!graphiteContext->insertRecording(recordingInfo)) { return nullptr; } graphiteContext->asyncRescaleAndReadPixels(src, ii, srcRect, rescaleGamma, rescaleMode, AsyncCallback, asyncContext); graphiteContext->submit(); while (!asyncContext->fCalled) { graphiteContext->checkAsyncWorkCompletion(); if (this->graphiteTestContext()) { this->graphiteTestContext()->tick(); } } #endif } else { src->asyncRescaleAndReadPixels(ii, srcRect, rescaleGamma, rescaleMode, AsyncCallback, asyncContext); if (direct) { direct->submit(); } while (!asyncContext->fCalled) { // Only GPU should actually be asynchronous. SkASSERT(direct); direct->checkAsyncWorkCompletion(); } } if (!asyncContext->fResult) { return nullptr; } SkPixmap pixmap(ii, asyncContext->fResult->data(0), asyncContext->fResult->rowBytes(0)); auto releasePixels = [](const void*, void* c) { delete static_cast(c); }; return SkImages::RasterFromPixmap(pixmap, releasePixels, asyncContext); } // Does a YUV[A] rescale and read using Graphite or Ganesh (no CPU support) and returns the // result as a YUVA planar texture image. template sk_sp readAndScaleYUVA(Source* src, SkIRect srcRect, SkISize resultSize, bool readAlpha, GrDirectContext* direct, skgpu::graphite::Recorder* recorder, SkYUVColorSpace yuvCS, SkImage::RescaleGamma rescaleGamma, SkImage::RescaleMode rescaleMode, SkScopeExit* cleanup) { SkASSERT(!(resultSize.width() & 0b1) && !(resultSize.height() & 0b1)); SkISize uvSize = {resultSize.width() / 2, resultSize.height() / 2}; SkImageInfo yaII = SkImageInfo::Make(resultSize, kGray_8_SkColorType, kPremul_SkAlphaType); SkImageInfo uvII = SkImageInfo::Make(uvSize, kGray_8_SkColorType, kPremul_SkAlphaType); AsyncContext asyncContext; if (recorder) { #if defined(SK_GRAPHITE) skgpu::graphite::Context* graphiteContext = recorder->priv().context(); if (!graphiteContext) { return nullptr; } // We need to flush the existing drawing commands before we try to read std::unique_ptr recording = recorder->snap(); if (!recording) { return nullptr; } skgpu::graphite::InsertRecordingInfo recordingInfo; recordingInfo.fRecording = recording.get(); if (!graphiteContext->insertRecording(recordingInfo)) { return nullptr; } if (readAlpha) { graphiteContext->asyncRescaleAndReadPixelsYUVA420(src, yuvCS, SkColorSpace::MakeSRGB(), srcRect, resultSize, rescaleGamma, rescaleMode, AsyncCallback, &asyncContext); } else { graphiteContext->asyncRescaleAndReadPixelsYUV420(src, yuvCS, SkColorSpace::MakeSRGB(), srcRect, resultSize, rescaleGamma, rescaleMode, AsyncCallback, &asyncContext); } graphiteContext->submit(); while (!asyncContext.fCalled) { graphiteContext->checkAsyncWorkCompletion(); if (this->graphiteTestContext()) { this->graphiteTestContext()->tick(); } } #endif } else { if (readAlpha) { src->asyncRescaleAndReadPixelsYUVA420(yuvCS, SkColorSpace::MakeSRGB(), srcRect, resultSize, rescaleGamma, rescaleMode, AsyncCallback, &asyncContext); } else { src->asyncRescaleAndReadPixelsYUV420(yuvCS, SkColorSpace::MakeSRGB(), srcRect, resultSize, rescaleGamma, rescaleMode, AsyncCallback, &asyncContext); } if (direct) { direct->submit(); } while (!asyncContext.fCalled) { // Only GPU should actually be asynchronous. SkASSERT(direct); direct->checkAsyncWorkCompletion(); } } if (!asyncContext.fResult) { return nullptr; } auto planeConfig = readAlpha ? SkYUVAInfo::PlaneConfig::kY_U_V_A : SkYUVAInfo::PlaneConfig::kY_U_V; SkYUVAInfo yuvaInfo(resultSize, planeConfig, SkYUVAInfo::Subsampling::k420, yuvCS); SkPixmap yuvPMs[4] = { {yaII, asyncContext.fResult->data(0), asyncContext.fResult->rowBytes(0)}, {uvII, asyncContext.fResult->data(1), asyncContext.fResult->rowBytes(1)}, {uvII, asyncContext.fResult->data(2), asyncContext.fResult->rowBytes(2)}, {}, }; if (readAlpha) { yuvPMs[3] = {yaII, asyncContext.fResult->data(3), asyncContext.fResult->rowBytes(3)}; } auto pixmaps = SkYUVAPixmaps::FromExternalPixmaps(yuvaInfo, yuvPMs); SkASSERT(pixmaps.isValid()); auto lazyYUVImage = sk_gpu_test::LazyYUVImage::Make(pixmaps); SkASSERT(lazyYUVImage); #if defined(SK_GRAPHITE) if (recorder) { return lazyYUVImage->refImage(recorder, sk_gpu_test::LazyYUVImage::Type::kFromTextures); } else #endif { return lazyYUVImage->refImage(direct, sk_gpu_test::LazyYUVImage::Type::kFromTextures); } } // Draws a 3x2 grid of rescales. The columns are none, low, and high filter quality. The rows // are rescale in src gamma and rescale in linear gamma. template skiagm::DrawResult drawRescaleGrid(SkCanvas* canvas, Source* src, SkIRect srcRect, SkISize readSize, Type type, SkString* errorMsg, int pad = 0) { SkASSERT(canvas->imageInfo().colorType() != kUnknown_SkColorType); auto direct = GrAsDirectContext(canvas->recordingContext()); auto recorder = canvas->recorder(); SkASSERT(direct || !canvas->recordingContext()); SkYUVColorSpace yuvColorSpace = kRec601_SkYUVColorSpace; canvas->save(); for (auto gamma : {SkImage::RescaleGamma::kSrc, SkImage::RescaleGamma::kLinear}) { canvas->save(); for (auto mode : {SkImage::RescaleMode::kNearest, SkImage::RescaleMode::kRepeatedLinear, SkImage::RescaleMode::kRepeatedCubic}) { SkScopeExit cleanup; sk_sp result; switch (type) { case Type::kRGBA: { const auto ii = canvas->imageInfo().makeDimensions(readSize); result = readAndScaleRGBA(src, srcRect, direct, recorder, ii, gamma, mode); if (!result) { errorMsg->printf("async read call failed."); return skiagm::DrawResult::kFail; } break; } case Type::kYUV: case Type::kYUVA: result = readAndScaleYUVA(src, srcRect, readSize, /*readAlpha=*/type == Type::kYUVA, direct, recorder, yuvColorSpace, gamma, mode, &cleanup); if (!result) { errorMsg->printf("YUV[A]420 async call failed. Allowed for now."); return skiagm::DrawResult::kSkip; } int nextCS = static_cast(yuvColorSpace + 1) % (kLastEnum_SkYUVColorSpace + 1); yuvColorSpace = static_cast(nextCS); break; } canvas->drawImage(result, 0, 0); canvas->translate(readSize.width() + pad, 0); } canvas->restore(); canvas->translate(0, readSize.height() + pad); } canvas->restore(); return skiagm::DrawResult::kOk; } private: struct AsyncContext { bool fCalled = false; std::unique_ptr fResult; }; // Making this a lambda in the test functions caused: // "error: cannot compile this forwarded non-trivially copyable parameter yet" // on x86/Win/Clang bot, referring to 'result'. static void AsyncCallback(void* c, std::unique_ptr result) { auto context = static_cast(c); context->fResult = std::move(result); context->fCalled = true; } SkString fName; }; template class AsyncRescaleAndReadGridGM : public AsyncReadGMBase { public: AsyncRescaleAndReadGridGM(const char* name, const char* imageFile, SkIRect srcRect, SkISize readSize) : AsyncReadGMBase(name) , fImageFile(imageFile) , fSrcRect(srcRect) , fReadSize(readSize) {} DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override { ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25); auto image = ToolUtils::GetResourceAsImage(fImageFile.c_str()); if (!image) { errorMsg->printf("Could not load image file %s.", fImageFile.c_str()); return skiagm::DrawResult::kFail; } if (canvas->imageInfo().colorType() == kUnknown_SkColorType) { *errorMsg = "Not supported on recording/vector backends."; return skiagm::DrawResult::kSkip; } auto sourceOrResult = convert_image_to_source(canvas, image, errorMsg); if (auto dr = std::get_if(&sourceOrResult)) { return *dr; } using Src = sk_sp>; return drawRescaleGrid(canvas, std::get(sourceOrResult).get(), fSrcRect, fReadSize, Type, errorMsg); } SkISize getISize() override { return {3 * fReadSize.width(), 2 * fReadSize.height()}; } private: SkString fImageFile; SkIRect fSrcRect; SkISize fReadSize; }; } // anonymous namespace #define DEF_RESCALE_AND_READ_GRID_GM(IMAGE_FILE, TAG, SRC_RECT, W, H, SOURCE, TYPE) \ DEF_GM(return new (AsyncRescaleAndReadGridGM)( \ "async_rescale_and_read_" #TAG, #IMAGE_FILE, SRC_RECT, SkISize{W, H});) DEF_RESCALE_AND_READ_GRID_GM(images/yellow_rose.webp, yuv420_rose, SkIRect::MakeXYWH(50, 5, 200, 150), 410, 376, ReadSource::kSurface, Type::kYUVA) DEF_RESCALE_AND_READ_GRID_GM(images/yellow_rose.webp, yuv420_rose_down, SkIRect::MakeXYWH(50, 5, 200, 150), 106, 60, ReadSource::kImage, Type::kYUV) DEF_RESCALE_AND_READ_GRID_GM(images/yellow_rose.webp, rose, SkIRect::MakeXYWH(100, 20, 100, 100), 410, 410, ReadSource::kSurface, Type::kRGBA) DEF_RESCALE_AND_READ_GRID_GM(images/dog.jpg, dog_down, SkIRect::MakeXYWH(0, 10, 180, 150), 45, 45, ReadSource::kSurface, Type::kRGBA) DEF_RESCALE_AND_READ_GRID_GM(images/dog.jpg, dog_up, SkIRect::MakeWH(180, 180), 800, 400, ReadSource::kImage, Type::kRGBA) DEF_RESCALE_AND_READ_GRID_GM(images/text.png, text_down, SkIRect::MakeWH(637, 105), (int)(0.7 * 637), (int)(0.7 * 105), ReadSource::kImage, Type::kRGBA) DEF_RESCALE_AND_READ_GRID_GM(images/text.png, text_up, SkIRect::MakeWH(637, 105), (int)(1.2 * 637), (int)(1.2 * 105), ReadSource::kSurface, Type::kRGBA) DEF_RESCALE_AND_READ_GRID_GM(images/text.png, text_up_large, SkIRect::MakeXYWH(300, 0, 300, 105), (int)(2.4 * 300), (int)(2.4 * 105), ReadSource::kImage, Type::kRGBA) namespace { class AyncYUVNoScaleGM : public AsyncReadGMBase { public: AyncYUVNoScaleGM() : AsyncReadGMBase("async_yuv_no_scale") {} DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override { auto surface = canvas->getSurface(); if (!surface) { *errorMsg = "Not supported on recording/vector backends."; return skiagm::DrawResult::kSkip; } auto dContext = GrAsDirectContext(surface->recordingContext()); if (!dContext && surface->recordingContext()) { *errorMsg = "Not supported in DDL mode"; return skiagm::DrawResult::kSkip; } auto image = ToolUtils::GetResourceAsImage("images/yellow_rose.webp"); if (!image) { return skiagm::DrawResult::kFail; } static constexpr SkIPoint kOffset = {15, 12}; SkISize evenSz = {image->width() & ~0b1, image->height() & ~0b1}; canvas->drawImage(image.get(), kOffset.fX, kOffset.fY); skgpu::graphite::Recorder* recorder = canvas->recorder(); SkScopeExit scopeExit; auto yuvImage = readAndScaleYUVA(surface, SkIRect::MakePtSize(kOffset, evenSz), evenSz, /*readAlpha=*/false, dContext, recorder, kRec601_SkYUVColorSpace, SkImage::RescaleGamma::kSrc, SkImage::RescaleMode::kNearest, &scopeExit); canvas->clear(SK_ColorWHITE); canvas->drawImage(yuvImage.get(), 0, 0); return skiagm::DrawResult::kOk; } SkISize getISize() override { return {400, 300}; } }; } // namespace DEF_GM(return new AyncYUVNoScaleGM();) namespace { class AsyncRescaleAndReadNoBleedGM : public AsyncReadGMBase { public: AsyncRescaleAndReadNoBleedGM() : AsyncReadGMBase("async_rescale_and_read_no_bleed") {} SkISize getISize() override { return {60, 60}; } DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override { if (canvas->imageInfo().colorType() == kUnknown_SkColorType) { *errorMsg = "Not supported on recording/vector backends."; return skiagm::DrawResult::kSkip; } auto dContext = GrAsDirectContext(canvas->recordingContext()); if (!dContext && canvas->recordingContext()) { *errorMsg = "Not supported in DDL mode"; return skiagm::DrawResult::kSkip; } static constexpr int kBorder = 5; static constexpr int kInner = 5; const auto srcRect = SkIRect::MakeXYWH(kBorder, kBorder, kInner, kInner); auto surfaceII = SkImageInfo::Make(kInner + 2 * kBorder, kInner + 2 * kBorder, kRGBA_8888_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGB()); auto surface = canvas->makeSurface(surfaceII); if (!surface) { *errorMsg = "Could not create surface for image."; // When testing abandoned GrContext we expect surface creation to fail. if (canvas->recordingContext() && canvas->recordingContext()->abandoned()) { return skiagm::DrawResult::kSkip; } return skiagm::DrawResult::kFail; } surface->getCanvas()->clear(SK_ColorRED); surface->getCanvas()->save(); surface->getCanvas()->clipRect(SkRect::Make(srcRect), SkClipOp::kIntersect, false); surface->getCanvas()->clear(SK_ColorBLUE); surface->getCanvas()->restore(); static constexpr int kPad = 2; canvas->translate(kPad, kPad); skiagm::DrawResult result; SkISize downSize = {static_cast(kInner / 2), static_cast(kInner / 2)}; result = drawRescaleGrid(canvas, surface.get(), srcRect, downSize, Type::kRGBA, errorMsg, kPad); if (result != skiagm::DrawResult::kOk) { return result; } canvas->translate(0, 4 * downSize.height()); SkISize upSize = {static_cast(kInner * 3.5), static_cast(kInner * 4.6)}; result = drawRescaleGrid(canvas, surface.get(), srcRect, upSize, Type::kRGBA, errorMsg, kPad); if (result != skiagm::DrawResult::kOk) { return result; } return skiagm::DrawResult::kOk; } }; } // namespace DEF_GM(return new AsyncRescaleAndReadNoBleedGM();) namespace { class AsyncRescaleAndReadAlphaTypeGM : public AsyncReadGMBase { public: AsyncRescaleAndReadAlphaTypeGM() : AsyncReadGMBase("async_rescale_and_read_alpha_type") {} SkISize getISize() override { return {512, 512}; } DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override { auto dContext = GrAsDirectContext(canvas->recordingContext()); if (!dContext && canvas->recordingContext()) { *errorMsg = "Not supported in DDL mode"; return skiagm::DrawResult::kSkip; } if (canvas->recorder()) { *errorMsg = "Reading to unpremul not supported in Graphite."; return skiagm::DrawResult::kSkip; } auto upmII = SkImageInfo::Make(200, 200, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType); auto pmII = upmII.makeAlphaType(kPremul_SkAlphaType); auto upmSurf = SkSurfaces::Raster(upmII); auto pmSurf = SkSurfaces::Raster(pmII); SkColor4f colors[] = { {.3f, .3f, .3f, .3f}, {1.f, .2f, .6f, .9f}, {0.f, .1f, 1.f, .1f}, {.7f, .8f, .2f, .7f}, }; auto shader = SkGradientShader::MakeRadial({100, 100}, 230, colors, nullptr, nullptr, std::size(colors), SkTileMode::kRepeat); SkPaint paint; paint.setShader(std::move(shader)); upmSurf->getCanvas()->drawPaint(paint); pmSurf ->getCanvas()->drawPaint(paint); auto pmImg = pmSurf->makeImageSnapshot(); auto upmImg = upmSurf->makeImageSnapshot(); auto imageOrResult = convert_image_to_source(canvas, std::move(pmImg), errorMsg); if (const auto* dr = std::get_if(&imageOrResult)) { return *dr; } pmImg = std::move(std::get>(imageOrResult)); imageOrResult = convert_image_to_source(canvas, std::move(upmImg), errorMsg); if (const auto* dr = std::get_if(&imageOrResult)) { return *dr; } upmImg = std::move(std::get>(imageOrResult)); int size = 256; ToolUtils::draw_checkerboard(canvas, SK_ColorWHITE, SK_ColorBLACK, 32); for (const auto& img : {pmImg, upmImg}) { canvas->save(); for (auto readAT : {kPremul_SkAlphaType, kUnpremul_SkAlphaType}) { auto readInfo = img->imageInfo().makeAlphaType(readAT).makeWH(size, size); auto result = readAndScaleRGBA(img.get(), SkIRect::MakeSize(img->dimensions()), dContext, canvas->recorder(), readInfo, SkImage::RescaleGamma::kSrc, SkImage::RescaleMode::kRepeatedCubic); if (!result) { *errorMsg = "async readback failed"; return skiagm::DrawResult::kFail; } canvas->drawImage(result, 0, 0); canvas->translate(size, 0); } canvas->restore(); canvas->translate(0, size); } return skiagm::DrawResult::kOk; } }; } // namespace DEF_GM(return new AsyncRescaleAndReadAlphaTypeGM();)