| /* | |
| * 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/SkGradient.h" | |
| #include "include/utils/SkTextUtils.h" | |
| #include "tools/ToolUtils.h" | |
| #include "tools/fonts/FontToolUtils.h" | |
| #include <array> | |
| #if defined(SK_GANESH) | |
| #include "include/gpu/ganesh/GrDirectContext.h" | |
| #include "include/gpu/ganesh/GrRecordingContext.h" | |
| #include "include/gpu/ganesh/SkSurfaceGanesh.h" | |
| #include "tools/gpu/BackendSurfaceFactory.h" | |
| #endif | |
| #if defined(SK_GRAPHITE) | |
| #include "include/gpu/graphite/Surface.h" | |
| #include "tools/gpu/BackendSurfaceFactory.h" | |
| #endif | |
| #define W 800 | |
| #define H 100 | |
| static sk_sp<SkShader> make_shader() { | |
| constexpr float a = 0x99/255.f; | |
| constexpr float b = 0xBB/255.f; | |
| SkPoint pts[] = { { 0, 0 }, { W, H } }; | |
| const SkColor4f colors[] = { {a, a, a, 1}, {b, b, b, 1} }; | |
| return SkShaders::LinearGradient(pts, {{colors, {}, SkTileMode::kClamp}, {}}); | |
| } | |
| static sk_sp<SkSurface> make_surface(GrRecordingContext* ctx, | |
| skgpu::graphite::Recorder* recorder, | |
| const SkImageInfo& info, | |
| uint32_t flags, | |
| SkPixelGeometry geo, | |
| SkScalar contrast, | |
| SkScalar gamma) { | |
| SkSurfaceProps props(flags, geo, contrast, gamma); | |
| #if defined(SK_GRAPHITE) | |
| if (recorder) { | |
| return SkSurfaces::RenderTarget(recorder, info, skgpu::Mipmapped::kNo, &props); | |
| } | |
| #endif | |
| #if defined(SK_GANESH) | |
| if (ctx) { | |
| return SkSurfaces::RenderTarget(ctx, skgpu::Budgeted::kNo, info, 0, &props); | |
| } | |
| #endif | |
| 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::DefaultPortableTypeface(), 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(uint32_t flags) : fFlags(flags) { | |
| recs = { | |
| {kUnknown_SkPixelGeometry, | |
| "Unknown geometry, default contrast/gamma", | |
| SK_GAMMA_CONTRAST, | |
| SK_GAMMA_EXPONENT}, | |
| {kRGB_H_SkPixelGeometry, | |
| "RGB_H, default contrast/gamma", | |
| SK_GAMMA_CONTRAST, | |
| SK_GAMMA_EXPONENT}, | |
| {kBGR_H_SkPixelGeometry, | |
| "BGR_H, default contrast/gamma", | |
| SK_GAMMA_CONTRAST, | |
| SK_GAMMA_EXPONENT}, | |
| {kRGB_V_SkPixelGeometry, | |
| "RGB_V, default contrast/gamma", | |
| SK_GAMMA_CONTRAST, | |
| SK_GAMMA_EXPONENT}, | |
| {kBGR_V_SkPixelGeometry, | |
| "BGR_V, default contrast/gamma", | |
| SK_GAMMA_CONTRAST, | |
| SK_GAMMA_EXPONENT}, | |
| {kRGB_H_SkPixelGeometry, "RGB_H contrast : 0 gamma: 0", 0, 0}, | |
| {kRGB_H_SkPixelGeometry, "RGB_H contrast : 1 gamma: 0", 1, 0}, | |
| {kRGB_H_SkPixelGeometry, "RGB_H contrast : 0 gamma: 3.9", 0, 3.9f}, | |
| {kRGB_H_SkPixelGeometry, "RGB_H contrast : 1 gamma: 3.9", 1, 3.9f}, | |
| }; | |
| } | |
| protected: | |
| SkString getName() const override { | |
| return SkStringPrintf("surfaceprops%s", | |
| fFlags != 0 ? "_df" : ""); | |
| } | |
| SkISize getISize() override { return SkISize::Make(W, H * recs.size()); } | |
| void onDraw(SkCanvas* canvas) override { | |
| auto ctx = canvas->recordingContext(); | |
| auto recorder = canvas->recorder(); | |
| // must be opaque to have a hope of testing LCD text | |
| const SkImageInfo info = SkImageInfo::MakeN32(W, H, kOpaque_SkAlphaType); | |
| SkScalar x = 0; | |
| SkScalar y = 0; | |
| for (const auto& rec : recs) { | |
| auto surface(make_surface(ctx, recorder, info, fFlags, rec.fGeo, rec.fContrast, | |
| rec.fGamma)); | |
| 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: | |
| struct SurfacePropsInput { | |
| SkPixelGeometry fGeo; | |
| const char* fLabel; | |
| SkScalar fContrast; | |
| SkScalar fGamma; | |
| }; | |
| std::vector<SurfacePropsInput> recs; | |
| uint32_t fFlags; | |
| using INHERITED = GM; | |
| }; | |
| DEF_GM( return new SurfacePropsGM(0); ) | |
| DEF_GM( return new SurfacePropsGM(SkSurfaceProps::kUseDeviceIndependentFonts_Flag); ) | |
| #ifdef SK_DEBUG | |
| static bool equal(const SkSurfaceProps& a, const SkSurfaceProps& b) { | |
| return a == b; | |
| } | |
| #endif | |
| class NewSurfaceGM : public skiagm::GM { | |
| public: | |
| NewSurfaceGM() {} | |
| protected: | |
| SkString getName() const override { return SkString("surfacenew"); } | |
| SkISize getISize() 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) { | |
| #if defined(SK_GANESH) | |
| GrDirectContext* direct = GrAsDirectContext(canvas->recordingContext()); | |
| #endif | |
| #if defined(SK_GRAPHITE) | |
| skgpu::graphite::Recorder* recorder = canvas->recorder(); | |
| #endif | |
| switch (type) { | |
| case kManaged: | |
| return ToolUtils::makeSurface(canvas, ii); | |
| case kBackendTexture: | |
| #if defined(SK_GRAPHITE) | |
| if (recorder) { | |
| return sk_gpu_test::MakeBackendTextureSurface(recorder, ii); | |
| } | |
| #endif | |
| #if defined(SK_GANESH) | |
| if (direct) { | |
| return sk_gpu_test::MakeBackendTextureSurface(direct, | |
| ii, | |
| kTopLeft_GrSurfaceOrigin, | |
| 1); | |
| } | |
| #endif | |
| return nullptr; | |
| case kBackendRenderTarget: | |
| #if defined(SK_GRAPHITE) | |
| if (recorder) { | |
| return SkSurfaces::RenderTarget(recorder, | |
| ii, | |
| skgpu::Mipmapped::kNo, | |
| /*surfaceProps=*/nullptr); | |
| } | |
| #endif | |
| #if defined(SK_GANESH) | |
| if (direct) { | |
| return sk_gpu_test::MakeBackendRenderTargetSurface(direct, | |
| ii, | |
| kTopLeft_GrSurfaceOrigin, | |
| 1); | |
| } | |
| #endif | |
| return nullptr; | |
| } | |
| 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)); \ | |
| } | |
| #if defined(SK_GANESH) | |
| #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()); \ | |
| skgpu::graphite::Recorder* recorder = canvas->recorder(); \ | |
| if ((!direct || direct->abandoned()) && !recorder) { \ | |
| *err_msg = "Requires non-abandoned GrDirectContext or Recorder"; \ | |
| return skiagm::DrawResult::kSkip; \ | |
| } \ | |
| auto make = [canvas](const SkImageInfo& ii) { return make_surface(ii, canvas, type); }; \ | |
| main(canvas, MakeSurfaceFn(make)); \ | |
| return skiagm::DrawResult::kOk; \ | |
| } | |
| #else | |
| #define DEF_BACKEND_SURFACE_TEST(...) | |
| #endif | |
| #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(); | |
| static constexpr auto kColors = std::to_array<SkColor>({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}}; | |
| SkColor4f colors[] = {SkColors::kRed, SkColors::kBlue}; | |
| auto sh = SkShaders::LinearGradient(pts, {{colors, {}, 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}}; | |
| const SkColor4f colors[] = {{0,0,0,1}, {0,0,0,0}}; | |
| auto sh = SkShaders::LinearGradient(pts, {{colors, {}, 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); | |
| } |