Refactor SkImageFilter usage to cache results.

If an SkImageFilter is used, create an image
snapshot with the filter applied to avoid
re-computing it on each draw invocation

Bug: 188450217
Test: Re-ran CTS tests

Change-Id: Ib790669e14ada9d4ebbfac958d699e2b5242f2d7
This commit is contained in:
Nader Jawad
2021-06-09 10:14:43 -07:00
parent 466698f9af
commit 6aff4814a7
6 changed files with 116 additions and 22 deletions

View File

@@ -306,11 +306,17 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
info.damageAccumulator->popTransform();
syncProperties();
const StretchEffect& stagingStretch =
mProperties.layerProperties().getStretchEffect();
auto& layerProperties = mProperties.layerProperties();
const StretchEffect& stagingStretch = layerProperties.getStretchEffect();
if (stagingStretch.isEmpty()) {
mStretchMask.clear();
}
if (layerProperties.getImageFilter() == nullptr) {
mSnapshotResult.snapshot = nullptr;
mTargetImageFilter = nullptr;
}
// We could try to be clever and only re-damage if the matrix changed.
// However, we don't need to worry about that. The cost of over-damaging
// here is only going to be a single additional map rect of this node
@@ -321,6 +327,44 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
}
}
std::optional<RenderNode::SnapshotResult> RenderNode::updateSnapshotIfRequired(
GrRecordingContext* context,
const SkImageFilter* imageFilter,
const SkIRect& clipBounds
) {
auto* layerSurface = getLayerSurface();
if (layerSurface == nullptr) {
return std::nullopt;
}
sk_sp<SkImage> snapshot = layerSurface->makeImageSnapshot();
const auto subset = SkIRect::MakeWH(properties().getWidth(),
properties().getHeight());
// If we don't have an ImageFilter just return the snapshot
if (imageFilter == nullptr) {
mSnapshotResult.snapshot = snapshot;
mSnapshotResult.outSubset = subset;
mSnapshotResult.outOffset = SkIPoint::Make(0.0f, 0.0f);
mImageFilterClipBounds = clipBounds;
mTargetImageFilter = nullptr;
} else if (mSnapshotResult.snapshot == nullptr ||
imageFilter != mTargetImageFilter.get() ||
mImageFilterClipBounds != clipBounds) {
// Otherwise create a new snapshot with the given filter and snapshot
mSnapshotResult.snapshot =
snapshot->makeWithFilter(context,
imageFilter,
subset,
clipBounds,
&mSnapshotResult.outSubset,
&mSnapshotResult.outOffset);
mTargetImageFilter = sk_ref_sp(imageFilter);
mImageFilterClipBounds = clipBounds;
}
return mSnapshotResult;
}
void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
// Make sure we inc first so that we don't fluctuate between 0 and 1,
// which would thrash the layer cache
@@ -411,6 +455,8 @@ void RenderNode::destroyLayers() {
if (hasLayer()) {
this->setLayerSurface(nullptr);
}
mSnapshotResult.snapshot = nullptr;
mTargetImageFilter = nullptr;
if (mDisplayList) {
mDisplayList.updateChildren([](RenderNode* child) { child->destroyLayers(); });
}

View File

@@ -345,6 +345,16 @@ public:
return mSkiaLayer.get() ? mSkiaLayer->layerSurface.get() : nullptr;
}
struct SnapshotResult {
sk_sp<SkImage> snapshot;
SkIRect outSubset;
SkIPoint outOffset;
};
std::optional<SnapshotResult> updateSnapshotIfRequired(GrRecordingContext* context,
const SkImageFilter* imageFilter,
const SkIRect& clipBounds);
skiapipeline::SkiaLayer* getSkiaLayer() const { return mSkiaLayer.get(); }
/**
@@ -375,6 +385,22 @@ private:
*/
std::unique_ptr<skiapipeline::SkiaLayer> mSkiaLayer;
/**
* SkImageFilter used to create the mSnapshotResult
*/
sk_sp<SkImageFilter> mTargetImageFilter;
/**
* Clip bounds used to create the mSnapshotResult
*/
SkIRect mImageFilterClipBounds;
/**
* Result of the most recent snapshot with additional metadata used to
* determine how to draw the contents
*/
SnapshotResult mSnapshotResult;
struct ClippedOutlineCache {
// keys
uint32_t outlineID = 0;

View File

@@ -188,7 +188,8 @@ static const float ZERO = 0.f;
static const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
sk_sp<SkShader> StretchEffect::getShader(float width, float height,
const sk_sp<SkImage>& snapshotImage) const {
const sk_sp<SkImage>& snapshotImage,
const SkMatrix* matrix) const {
if (isEmpty()) {
return nullptr;
}
@@ -206,8 +207,9 @@ sk_sp<SkShader> StretchEffect::getShader(float width, float height,
mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
}
mBuilder->child("uContentTexture") = snapshotImage->makeShader(
SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions(SkFilterMode::kLinear));
mBuilder->child("uContentTexture") =
snapshotImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
SkSamplingOptions(SkFilterMode::kLinear), matrix);
mBuilder->uniform("uInterpolationStrength").set(&INTERPOLATION_STRENGTH_VALUE, 1);
mBuilder->uniform("uStretchAffectedDistX").set(&width, 1);
mBuilder->uniform("uStretchAffectedDistY").set(&height, 1);

View File

@@ -93,8 +93,8 @@ public:
*/
float computeStretchedPositionY(float normalizedY) const;
sk_sp<SkShader> getShader(float width, float height,
const sk_sp<SkImage>& snapshotImage) const;
sk_sp<SkShader> getShader(float width, float height, const sk_sp<SkImage>& snapshotImage,
const SkMatrix* matrix) const;
float maxStretchAmountX = 0;
float maxStretchAmountY = 0;

View File

@@ -171,17 +171,14 @@ void RenderNodeDrawable::forceDraw(SkCanvas* canvas) const {
displayList->mProjectedOutline = nullptr;
}
static bool layerNeedsPaint(const sk_sp<SkImage>& snapshotImage, const LayerProperties& properties,
float alphaMultiplier, SkPaint* paint) {
static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultiplier,
SkPaint* paint) {
if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr ||
properties.getImageFilter() != nullptr || properties.getStretchEffect().requiresLayer()) {
properties.getStretchEffect().requiresLayer()) {
paint->setAlpha(properties.alpha() * alphaMultiplier);
paint->setBlendMode(properties.xferMode());
paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
sk_sp<SkImageFilter> imageFilter = sk_ref_sp(properties.getImageFilter());
paint->setImageFilter(std::move(imageFilter));
return true;
}
return false;
@@ -223,6 +220,9 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
// TODO should we let the bound of the drawable do this for us?
const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
bool quickRejected = properties.getClipToBounds() && canvas->quickReject(bounds);
auto clipBounds = canvas->getLocalClipBounds();
SkIRect srcBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
SkIPoint offset = SkIPoint::Make(0.0f, 0.0f);
if (!quickRejected) {
SkiaDisplayList* displayList = renderNode->getDisplayList().asSkiaDl();
const LayerProperties& layerProperties = properties.layerProperties();
@@ -230,8 +230,19 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
if (renderNode->getLayerSurface() && mComposeLayer) {
SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer);
SkPaint paint;
sk_sp<SkImage> snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot();
layerNeedsPaint(snapshotImage, layerProperties, alphaMultiplier, &paint);
layerNeedsPaint(layerProperties, alphaMultiplier, &paint);
const auto snapshotResult = renderNode->updateSnapshotIfRequired(
canvas->recordingContext(),
layerProperties.getImageFilter(),
clipBounds.roundOut()
);
sk_sp<SkImage> snapshotImage = snapshotResult->snapshot;
srcBounds = snapshotResult->outSubset;
offset = snapshotResult->outOffset;
const auto dstBounds = SkIRect::MakeXYWH(offset.x(),
offset.y(),
srcBounds.width(),
srcBounds.height());
SkSamplingOptions sampling(SkFilterMode::kLinear);
// surfaces for layers are created on LAYER_SIZE boundaries (which are >= layer size) so
@@ -257,7 +268,8 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
TransformCanvas transformCanvas(canvas, SkBlendMode::kClear);
displayList->draw(&transformCanvas);
}
canvas->drawImageRect(snapshotImage, bounds, bounds, sampling, &paint,
canvas->drawImageRect(snapshotImage, SkRect::Make(srcBounds),
SkRect::Make(dstBounds), sampling, &paint,
SkCanvas::kStrict_SrcRectConstraint);
} else {
// If we do have stretch effects and have hole punches,
@@ -265,6 +277,16 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
// get the corresponding hole punches.
// Then apply the stretch to the mask and draw the mask to
// the destination
// Also if the stretchy container has an ImageFilter applied
// to it (i.e. blur) we need to take into account the offset
// that will be generated with this result. Ex blurs will "grow"
// the source image by the blur radius so we need to translate
// the shader by the same amount to render in the same location
SkMatrix matrix;
matrix.setTranslate(
offset.x() - srcBounds.left(),
offset.y() - srcBounds.top()
);
if (renderNode->hasHolePunches()) {
GrRecordingContext* context = canvas->recordingContext();
StretchMask& stretchMask = renderNode->getStretchMask();
@@ -275,11 +297,10 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
canvas);
}
sk_sp<SkShader> stretchShader = stretch.getShader(bounds.width(),
bounds.height(),
snapshotImage);
sk_sp<SkShader> stretchShader =
stretch.getShader(bounds.width(), bounds.height(), snapshotImage, &matrix);
paint.setShader(stretchShader);
canvas->drawRect(bounds, paint);
canvas->drawRect(SkRect::Make(dstBounds), paint);
}
if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) {

View File

@@ -59,8 +59,7 @@ void StretchMask::draw(GrRecordingContext* context,
}
sk_sp<SkImage> maskImage = mMaskSurface->makeImageSnapshot();
sk_sp<SkShader> maskStretchShader = stretch.getShader(
width, height, maskImage);
sk_sp<SkShader> maskStretchShader = stretch.getShader(width, height, maskImage, nullptr);
SkPaint maskPaint;
maskPaint.setShader(maskStretchShader);