skia2/gm/surface.cpp
Brian Salomon d63638bb7b Copy on write for wrapped backend texture surfaces.
Makes SkImage_Gpu backed by two proxies, an original and a copy. The
image uses the original until a new render task is bound to it at which
point further uses of the image will use the copy. If the image is ever
used off a GrDirectContext we fall over to the copy. If the copy is
never used and never can be used by the next flush then the render
task that populates it is marked "skipped" and we don't perform the
copy.

Bug: skia:11208

Change-Id: Id255f4a733acc608c8a53c1a5633207aeafc404b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/366282
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
2021-03-05 19:50:05 +00:00

380 lines
14 KiB
C++

/*
* 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/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 SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info, 0, &props);
} else {
return SkSurface::MakeRaster(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_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);
}