| /* | |
| * Copyright 2025 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/SkColorFilter.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkPoint.h" | |
| #include "include/core/SkRRect.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/effects/SkColorMatrix.h" | |
| #include "include/effects/SkImageFilters.h" | |
| #include "src/cpu/Recorder.h" | |
| #include "tools/DecodeUtils.h" | |
| #if defined(SK_GANESH) | |
| #include "include/gpu/ganesh/SkImageGanesh.h" | |
| #endif | |
| #if defined(SK_GRAPHITE) | |
| #include "include/gpu/graphite/Image.h" | |
| #include "include/gpu/graphite/Recorder.h" | |
| #endif | |
| // Emulates rendering operations and color space conversions on Android running in HDR mode, | |
| // mixing SDR background content, an HDR PiP "video", and an overall blur shade from a swipe. | |
| class HDRPiPBlurGM : public skiagm::GM { | |
| // This GM is fragment bound, so when benchmarking, use a large texture size. Use a smaller | |
| // size for DM to reduce device load. | |
| static constexpr SkISize kFullSize{2560, 1440}; | |
| static constexpr SkISize kNonBenchSize{640, 360}; | |
| public: | |
| HDRPiPBlurGM() {} | |
| protected: | |
| SkString getName() const override { return SkString("hdr-pip-blur"); } | |
| SkISize getISize() override { | |
| if (this->getMode() == kBench_Mode) { | |
| return kFullSize; | |
| } else { | |
| return kNonBenchSize; | |
| } | |
| } | |
| bool runAsBench() const override { return true; } | |
| DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override { | |
| // When running in DM, all offscreen passes and the final rendering will be small. | |
| SkIRect screenBounds = SkIRect::MakeSize(this->getMode() == kGM_Mode ? kNonBenchSize | |
| : kFullSize); | |
| sk_sp<SkImage> input; | |
| { | |
| // The main surface is RGBA8 but with a wider gamut sRGB colorspace. | |
| sk_sp<SkSurface> content = canvas->makeSurface( | |
| canvas->imageInfo().makeWH(screenBounds.width(), screenBounds.height()) | |
| .makeColorType(kRGBA_8888_SkColorType) | |
| .makeColorSpace( | |
| SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, | |
| SkNamedGamut::kDisplayP3))); | |
| if (!content) { | |
| // This occurs in DDL dm configs | |
| *errorMsg = "Could not create offscreen surface"; | |
| return DrawResult::kSkip; | |
| } | |
| SkCanvas* c = content->getCanvas(); | |
| c->clear(SkColors::kDkGray); | |
| SkMatrix toScreenBounds = SkMatrix::RectToRectOrIdentity(SkRect::Make(kFullSize), | |
| SkRect::Make(screenBounds)); | |
| c->concat(toScreenBounds); | |
| // Now render everything to `c` as if it were a kFullSize image. | |
| c->drawImageRect(fBackgroundImage.get(), | |
| SkRect::Make(fBackgroundImage->bounds()), | |
| SkRect::Make(kFullSize), | |
| SkFilterMode::kLinear, | |
| &fPaint, | |
| SkCanvas::kFast_SrcRectConstraint); | |
| SkRRect pip = SkRRect::MakeRectXY(SkRect::MakeXYWH(1500.f, 700.f, 800.f, 600.f), | |
| 20.f, 20.f); | |
| c->drawRRect(pip, fPaint); | |
| c->save(); | |
| c->clipRRect(pip, true); | |
| c->drawImageRect(fPiPImage.get(), | |
| SkRect::MakeIWH(fPiPImage->width(), fPiPImage->height()), | |
| pip.rect(), | |
| SkFilterMode::kLinear, | |
| &fPaint, | |
| SkCanvas::kFast_SrcRectConstraint); | |
| c->restore(); | |
| input = content->makeTemporaryImage(); | |
| } | |
| canvas->save(); | |
| if (this->getMode() == kSample_Mode) { | |
| // For viewer, the offscreen passes operate at full resolution, but we draw smaller to | |
| // fit into the window. This lets overall frame times match nanobench, but it looks like | |
| // what dm produces. | |
| SkMatrix toViewBounds = SkMatrix::RectToRectOrIdentity(SkRect::Make(screenBounds), | |
| SkRect::Make(kNonBenchSize)); | |
| canvas->concat(toViewBounds); | |
| } | |
| canvas->clipIRect(screenBounds); | |
| canvas->drawImage(input.get(), 0.f, 0.f, SkFilterMode::kLinear, &fPaint); | |
| // Explicitly blur the input image | |
| // NOTE: Skip calling MakeWithFilter and comment out the last `drawImageRect` call to | |
| // disable the shade blur. | |
| SkIRect outSubset; | |
| SkIPoint outOffset; | |
| sk_sp<SkImage> blur; | |
| #if defined(SK_GRAPHITE) | |
| if (canvas->recorder()) { | |
| blur = SkImages::MakeWithFilter(canvas->recorder(), | |
| input, | |
| fShadeBlur.get(), | |
| screenBounds, screenBounds, | |
| &outSubset, &outOffset); | |
| } else | |
| #endif | |
| #if defined(SK_GANESH) | |
| if (canvas->recordingContext()) { | |
| blur = SkImages::MakeWithFilter(canvas->recordingContext(), | |
| input, | |
| fShadeBlur.get(), | |
| screenBounds, screenBounds, | |
| &outSubset, &outOffset); | |
| } else | |
| #endif | |
| { | |
| blur = SkImages::MakeWithFilter(input, | |
| fShadeBlur.get(), | |
| screenBounds, screenBounds, | |
| &outSubset, &outOffset); | |
| } | |
| SkPaint fadedBlur; | |
| fadedBlur.setAlphaf(0.9f); | |
| canvas->drawImageRect(blur.get(), | |
| SkRect::Make(outSubset), | |
| SkRect::MakeXYWH(outOffset.x(), outOffset.y(), | |
| outSubset.width(), outSubset.height()), | |
| SkFilterMode::kLinear, | |
| &fadedBlur, | |
| SkCanvas::kFast_SrcRectConstraint); | |
| canvas->restore(); | |
| return DrawResult::kOk; | |
| } | |
| void onOnceBeforeDraw() override { | |
| fPaint.setColor4f({ 0.0f, 0.0f, 0.0f, 1.f }); | |
| SkColorMatrix cm; | |
| cm.setSaturation(1.5f); | |
| fPaint.setColorFilter(SkColorFilters::Matrix(cm)); | |
| auto recorder = skcpu::Recorder::TODO(); | |
| // The background image is standard sRGB | |
| fBackgroundImage = ToolUtils::GetResourceAsImage("images/yellow_rose.png") | |
| ->makeRasterImage(nullptr) | |
| ->makeColorSpace(recorder, SkColorSpace::MakeSRGB(), | |
| {}); | |
| // The pip will be PQ-ish HDR | |
| fPiPImage = ToolUtils::GetResourceAsImage("images/mandrill_512.png") | |
| ->makeRasterImage(nullptr) | |
| ->makeColorSpace(recorder, | |
| SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, | |
| SkNamedGamut::kRec2020), | |
| {}); | |
| const float sigma = 32.f; | |
| const bool scaleSigma = this->getMode() == kGM_Mode; | |
| const float sigmaX = sigma * (scaleSigma ? kNonBenchSize.width() : kFullSize.width()) / | |
| (float) kFullSize.width(); | |
| const float sigmaY = sigma * (scaleSigma ? kNonBenchSize.height() : kFullSize.height()) / | |
| (float) kFullSize.height(); | |
| fShadeBlur = SkImageFilters::Blur(sigmaX, sigmaY, SkTileMode::kClamp, nullptr); | |
| } | |
| private: | |
| sk_sp<SkImage> fBackgroundImage; | |
| sk_sp<SkImage> fPiPImage; | |
| sk_sp<SkImageFilter> fShadeBlur; | |
| SkPaint fPaint; | |
| }; | |
| DEF_GM( return new HDRPiPBlurGM; ) |