| /* |
| * Copyright 2014 Google Inc. |
| * |
| * 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/SkBlendMode.h" |
| #include "include/core/SkCanvas.h" |
| #include "include/core/SkColor.h" |
| #include "include/core/SkColorSpace.h" |
| #include "include/core/SkFont.h" |
| #include "include/core/SkImage.h" |
| #include "include/core/SkImageInfo.h" |
| #include "include/core/SkPaint.h" |
| #include "include/core/SkPoint.h" |
| #include "include/core/SkRect.h" |
| #include "include/core/SkRefCnt.h" |
| #include "include/core/SkScalar.h" |
| #include "include/core/SkShader.h" |
| #include "include/core/SkSize.h" |
| #include "include/core/SkString.h" |
| #include "include/core/SkSurface.h" |
| #include "include/core/SkSurfaceProps.h" |
| #include "include/core/SkTileMode.h" |
| #include "include/core/SkTypeface.h" |
| #include "include/core/SkTypes.h" |
| #include "include/effects/SkGradientShader.h" |
| #include "include/gpu/GrDirectContext.h" |
| #include "include/gpu/GrRecordingContext.h" |
| #include "include/gpu/ganesh/SkSurfaceGanesh.h" |
| #include "include/utils/SkTextUtils.h" |
| #include "tools/ToolUtils.h" |
| #include "tools/gpu/BackendSurfaceFactory.h" |
| |
| #define W 200 |
| #define H 100 |
| |
| static sk_sp<SkShader> make_shader() { |
| int a = 0x99; |
| int b = 0xBB; |
| SkPoint pts[] = { { 0, 0 }, { W, H } }; |
| SkColor colors[] = { SkColorSetRGB(a, a, a), SkColorSetRGB(b, b, b) }; |
| return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp); |
| } |
| |
| static sk_sp<SkSurface> make_surface(GrRecordingContext* ctx, |
| const SkImageInfo& info, |
| SkPixelGeometry geo) { |
| SkSurfaceProps props(0, geo); |
| if (ctx) { |
| return SkSurfaces::RenderTarget(ctx, skgpu::Budgeted::kNo, info, 0, &props); |
| } else { |
| return SkSurfaces::Raster(info, &props); |
| } |
| } |
| |
| static void test_draw(SkCanvas* canvas, const char label[]) { |
| SkPaint paint; |
| |
| paint.setAntiAlias(true); |
| paint.setDither(true); |
| |
| paint.setShader(make_shader()); |
| canvas->drawRect(SkRect::MakeWH(W, H), paint); |
| paint.setShader(nullptr); |
| |
| paint.setColor(SK_ColorWHITE); |
| SkFont font(ToolUtils::create_portable_typeface(), 32); |
| font.setEdging(SkFont::Edging::kSubpixelAntiAlias); |
| SkTextUtils::DrawString(canvas, label, W / 2, H * 3 / 4, font, paint, |
| SkTextUtils::kCenter_Align); |
| } |
| |
| class SurfacePropsGM : public skiagm::GM { |
| public: |
| SurfacePropsGM() {} |
| |
| protected: |
| SkString onShortName() override { |
| return SkString("surfaceprops"); |
| } |
| |
| SkISize onISize() override { |
| return SkISize::Make(W, H * 5); |
| } |
| |
| void onDraw(SkCanvas* canvas) override { |
| auto ctx = canvas->recordingContext(); |
| |
| // must be opaque to have a hope of testing LCD text |
| const SkImageInfo info = SkImageInfo::MakeN32(W, H, kOpaque_SkAlphaType); |
| |
| const struct { |
| SkPixelGeometry fGeo; |
| const char* fLabel; |
| } recs[] = { |
| { kUnknown_SkPixelGeometry, "Unknown" }, |
| { kRGB_H_SkPixelGeometry, "RGB_H" }, |
| { kBGR_H_SkPixelGeometry, "BGR_H" }, |
| { kRGB_V_SkPixelGeometry, "RGB_V" }, |
| { kBGR_V_SkPixelGeometry, "BGR_V" }, |
| }; |
| |
| SkScalar x = 0; |
| SkScalar y = 0; |
| for (const auto& rec : recs) { |
| auto surface(make_surface(ctx, info, rec.fGeo)); |
| if (!surface) { |
| SkDebugf("failed to create surface! label: %s", rec.fLabel); |
| continue; |
| } |
| test_draw(surface->getCanvas(), rec.fLabel); |
| surface->draw(canvas, x, y); |
| y += H; |
| } |
| } |
| |
| private: |
| using INHERITED = GM; |
| }; |
| DEF_GM( return new SurfacePropsGM ) |
| |
| #ifdef SK_DEBUG |
| static bool equal(const SkSurfaceProps& a, const SkSurfaceProps& b) { |
| return a.flags() == b.flags() && a.pixelGeometry() == b.pixelGeometry(); |
| } |
| #endif |
| |
| class NewSurfaceGM : public skiagm::GM { |
| public: |
| NewSurfaceGM() {} |
| |
| protected: |
| SkString onShortName() override { |
| return SkString("surfacenew"); |
| } |
| |
| SkISize onISize() override { |
| return SkISize::Make(300, 140); |
| } |
| |
| static void drawInto(SkCanvas* canvas) { |
| canvas->drawColor(SK_ColorRED); |
| } |
| |
| void onDraw(SkCanvas* canvas) override { |
| SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100); |
| |
| auto surf(ToolUtils::makeSurface(canvas, info, nullptr)); |
| drawInto(surf->getCanvas()); |
| |
| sk_sp<SkImage> image(surf->makeImageSnapshot()); |
| canvas->drawImage(image, 10, 10); |
| |
| auto surf2(surf->makeSurface(info)); |
| drawInto(surf2->getCanvas()); |
| |
| // Assert that the props were communicated transitively through the first image |
| SkASSERT(equal(surf->props(), surf2->props())); |
| |
| sk_sp<SkImage> image2(surf2->makeImageSnapshot()); |
| canvas->drawImage(image2.get(), 10 + SkIntToScalar(image->width()) + 10, 10); |
| } |
| |
| private: |
| using INHERITED = GM; |
| }; |
| DEF_GM( return new NewSurfaceGM ) |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| // The GPU backend may behave differently when images are snapped from wrapped textures and |
| // render targets compared. |
| namespace { |
| enum SurfaceType { |
| kManaged, |
| kBackendTexture, |
| kBackendRenderTarget |
| }; |
| } |
| |
| static sk_sp<SkSurface> make_surface(const SkImageInfo& ii, SkCanvas* canvas, SurfaceType type) { |
| GrDirectContext* direct = GrAsDirectContext(canvas->recordingContext()); |
| switch (type) { |
| case kManaged: |
| return ToolUtils::makeSurface(canvas, ii); |
| case kBackendTexture: |
| if (!direct) { |
| return nullptr; |
| } |
| return sk_gpu_test::MakeBackendTextureSurface(direct, ii, kTopLeft_GrSurfaceOrigin, 1); |
| case kBackendRenderTarget: |
| return sk_gpu_test::MakeBackendRenderTargetSurface(direct, |
| ii, |
| kTopLeft_GrSurfaceOrigin, |
| 1); |
| } |
| return nullptr; |
| } |
| |
| using MakeSurfaceFn = std::function<sk_sp<SkSurface>(const SkImageInfo&)>; |
| |
| #define DEF_BASIC_SURFACE_TEST(name, canvas, main, W, H) \ |
| DEF_SIMPLE_GM(name, canvas, W, H) { \ |
| auto make = [canvas](const SkImageInfo& ii) { \ |
| return make_surface(ii, canvas, SurfaceType::kManaged); \ |
| }; \ |
| main(canvas, MakeSurfaceFn(make)); \ |
| } |
| |
| #define DEF_BACKEND_SURFACE_TEST(name, canvas, main, type, W, H) \ |
| DEF_SIMPLE_GM_CAN_FAIL(name, canvas, err_msg, W, H) { \ |
| GrDirectContext* direct = GrAsDirectContext(canvas->recordingContext()); \ |
| if (!direct || direct->abandoned()) { \ |
| *err_msg = "Requires non-abandoned GrDirectContext"; \ |
| return skiagm::DrawResult::kSkip; \ |
| } \ |
| auto make = [canvas](const SkImageInfo& ii) { return make_surface(ii, canvas, type); }; \ |
| main(canvas, MakeSurfaceFn(make)); \ |
| return skiagm::DrawResult::kOk; \ |
| } |
| |
| #define DEF_BET_SURFACE_TEST(name, canvas, main, W, H) \ |
| DEF_BACKEND_SURFACE_TEST(SK_MACRO_CONCAT(name, _bet), canvas, main, \ |
| SurfaceType::kBackendTexture, W, H) |
| |
| #define DEF_BERT_SURFACE_TEST(name, canvas, main, W, H) \ |
| DEF_BACKEND_SURFACE_TEST(SK_MACRO_CONCAT(name, _bert), canvas, main, \ |
| SurfaceType::kBackendRenderTarget, W, H) |
| |
| // This makes 3 GMs from the same code, normal, wrapped backend texture, and wrapped backend |
| // render target. |
| #define DEF_SURFACE_TESTS(name, canvas, W, H) \ |
| static void SK_MACRO_CONCAT(name, _main)(SkCanvas*, const MakeSurfaceFn&); \ |
| DEF_BASIC_SURFACE_TEST(name, canvas, SK_MACRO_CONCAT(name, _main), W, H) \ |
| DEF_BET_SURFACE_TEST (name, canvas, SK_MACRO_CONCAT(name, _main), W, H) \ |
| DEF_BERT_SURFACE_TEST (name, canvas, SK_MACRO_CONCAT(name, _main), W, H) \ |
| static void SK_MACRO_CONCAT(name, _main)(SkCanvas * canvas, const MakeSurfaceFn& make) |
| |
| DEF_SURFACE_TESTS(copy_on_write_retain, canvas, 256, 256) { |
| const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256); |
| sk_sp<SkSurface> surf = make(info); |
| |
| surf->getCanvas()->clear(SK_ColorRED); |
| // its important that image survives longer than the next draw, so the surface will see |
| // an outstanding image, and have to decide if it should retain or discard those pixels |
| sk_sp<SkImage> image = surf->makeImageSnapshot(); |
| |
| // normally a clear+opaque should trigger the discard optimization, but since we have a clip |
| // it should not (we need the previous red pixels). |
| surf->getCanvas()->clipRect(SkRect::MakeWH(128, 256)); |
| surf->getCanvas()->clear(SK_ColorBLUE); |
| |
| // expect to see two rects: blue | red |
| canvas->drawImage(surf->makeImageSnapshot(), 0, 0); |
| } |
| |
| // Like copy_on_write_retain but draws the snapped image back to the surface it was snapped from. |
| DEF_SURFACE_TESTS(copy_on_write_retain2, canvas, 256, 256) { |
| const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256); |
| sk_sp<SkSurface> surf = make(info); |
| |
| surf->getCanvas()->clear(SK_ColorBLUE); |
| // its important that image survives longer than the next draw, so the surface will see |
| // an outstanding image, and have to decide if it should retain or discard those pixels |
| sk_sp<SkImage> image = surf->makeImageSnapshot(); |
| |
| surf->getCanvas()->clear(SK_ColorRED); |
| // normally a clear+opaque should trigger the discard optimization, but since we have a clip |
| // it should not (we need the previous red pixels). |
| surf->getCanvas()->clipRect(SkRect::MakeWH(128, 256)); |
| surf->getCanvas()->drawImage(image, 0, 0); |
| |
| // expect to see two rects: blue | red |
| canvas->drawImage(surf->makeImageSnapshot(), 0, 0); |
| } |
| |
| DEF_SURFACE_TESTS(simple_snap_image, canvas, 256, 256) { |
| const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256); |
| sk_sp<SkSurface> surf = make(info); |
| |
| surf->getCanvas()->clear(SK_ColorRED); |
| sk_sp<SkImage> image = surf->makeImageSnapshot(); |
| // expect to see just red |
| canvas->drawImage(std::move(image), 0, 0); |
| } |
| |
| // Like simple_snap_image but the surface dies before the image. |
| DEF_SURFACE_TESTS(simple_snap_image2, canvas, 256, 256) { |
| const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256); |
| sk_sp<SkSurface> surf = make(info); |
| |
| surf->getCanvas()->clear(SK_ColorRED); |
| sk_sp<SkImage> image = surf->makeImageSnapshot(); |
| surf.reset(); |
| // expect to see just red |
| canvas->drawImage(std::move(image), 0, 0); |
| } |
| |
| DEF_SIMPLE_GM(snap_with_mips, canvas, 80, 75) { |
| auto ct = canvas->imageInfo().colorType() == kUnknown_SkColorType |
| ? kRGBA_8888_SkColorType |
| : canvas->imageInfo().colorType(); |
| auto ii = SkImageInfo::Make({32, 32}, |
| ct, |
| kPremul_SkAlphaType, |
| canvas->imageInfo().refColorSpace()); |
| auto surface = SkSurfaces::Raster(ii); |
| |
| auto nextImage = [&](SkColor color) { |
| surface->getCanvas()->clear(color); |
| SkPaint paint; |
| paint.setColor(~color | 0xFF000000); |
| surface->getCanvas()->drawRect(SkRect::MakeLTRB(surface->width() *2/5.f, |
| surface->height()*2/5.f, |
| surface->width() *3/5.f, |
| surface->height()*3/5.f), |
| paint); |
| return surface->makeImageSnapshot()->withDefaultMipmaps(); |
| }; |
| |
| static constexpr int kPad = 8; |
| static const SkSamplingOptions kSampling{SkFilterMode::kLinear, SkMipmapMode::kLinear}; |
| |
| canvas->save(); |
| for (int y = 0; y < 3; ++y) { |
| canvas->save(); |
| SkColor kColors[] = {0xFFF0F0F0, SK_ColorBLUE}; |
| for (int x = 0; x < 2; ++x) { |
| auto image = nextImage(kColors[x]); |
| canvas->drawImage(image, 0, 0, kSampling); |
| canvas->translate(ii.width() + kPad, 0); |
| } |
| canvas->restore(); |
| canvas->translate(0, ii.width() + kPad); |
| canvas->scale(.4f, .4f); |
| } |
| canvas->restore(); |
| } |
| |
| DEF_SURFACE_TESTS(copy_on_write_savelayer, canvas, 256, 256) { |
| const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256); |
| sk_sp<SkSurface> surf = make(info); |
| surf->getCanvas()->clear(SK_ColorRED); |
| // its important that image survives longer than the next draw, so the surface will see |
| // an outstanding image, and have to decide if it should retain or discard those pixels |
| sk_sp<SkImage> image = surf->makeImageSnapshot(); |
| |
| // now draw into a full-screen layer. This should (a) trigger a copy-on-write, but it should |
| // not trigger discard, even tho its alpha (SK_ColorBLUE) is opaque, since it is in a layer |
| // with a non-opaque paint. |
| SkPaint paint; |
| paint.setAlphaf(0.25f); |
| surf->getCanvas()->saveLayer({0, 0, 256, 256}, &paint); |
| surf->getCanvas()->clear(SK_ColorBLUE); |
| surf->getCanvas()->restore(); |
| |
| // expect to see two rects: blue blended on red |
| canvas->drawImage(surf->makeImageSnapshot(), 0, 0); |
| } |
| |
| DEF_SURFACE_TESTS(surface_underdraw, canvas, 256, 256) { |
| SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256, nullptr); |
| auto surf = make(info); |
| |
| const SkIRect subset = SkIRect::MakeLTRB(180, 0, 256, 256); |
| |
| // noisy background |
| { |
| SkPoint pts[] = {{0, 0}, {40, 50}}; |
| SkColor colors[] = {SK_ColorRED, SK_ColorBLUE}; |
| auto sh = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kRepeat); |
| SkPaint paint; |
| paint.setShader(sh); |
| surf->getCanvas()->drawPaint(paint); |
| } |
| |
| // save away the right-hand strip, then clear it |
| sk_sp<SkImage> saveImg = surf->makeImageSnapshot(subset); |
| { |
| SkPaint paint; |
| paint.setBlendMode(SkBlendMode::kClear); |
| surf->getCanvas()->drawRect(SkRect::Make(subset), paint); |
| } |
| |
| // draw the "foreground" |
| { |
| SkPaint paint; |
| paint.setColor(SK_ColorGREEN); |
| SkRect r = { 0, 10, 256, 35 }; |
| while (r.fBottom < 256) { |
| surf->getCanvas()->drawRect(r, paint); |
| r.offset(0, r.height() * 2); |
| } |
| } |
| |
| // apply the "fade" |
| { |
| SkPoint pts[] = {{SkIntToScalar(subset.left()), 0}, {SkIntToScalar(subset.right()), 0}}; |
| SkColor colors[] = {0xFF000000, 0}; |
| auto sh = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp); |
| SkPaint paint; |
| paint.setShader(sh); |
| paint.setBlendMode(SkBlendMode::kDstIn); |
| surf->getCanvas()->drawRect(SkRect::Make(subset), paint); |
| } |
| |
| // restore the original strip, drawing it "under" the current foreground |
| { |
| SkPaint paint; |
| paint.setBlendMode(SkBlendMode::kDstOver); |
| surf->getCanvas()->drawImage(saveImg, |
| SkIntToScalar(subset.left()), SkIntToScalar(subset.top()), |
| SkSamplingOptions(), &paint); |
| } |
| |
| // show it on screen |
| surf->draw(canvas, 0, 0); |
| } |