blob: 3cfe57e112ca3d8d2354698c44fed81532dfe17c [file] [log] [blame]
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/skottie/src/effects/MotionBlurEffect.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkMath.h"
#include "include/core/SkPixmap.h"
#include "include/private/SkVx.h"
#include "src/core/SkMathPriv.h"
namespace skottie {
namespace internal {
class MotionBlurEffect::AutoInvalBlocker {
public:
AutoInvalBlocker(const MotionBlurEffect* mb, const sk_sp<RenderNode>& child)
: fMBNode(const_cast<MotionBlurEffect*>(mb))
, fChild(child) {
fMBNode->unobserveInval(fChild);
}
~AutoInvalBlocker() {
fMBNode->observeInval(fChild);
}
private:
MotionBlurEffect* fMBNode;
const sk_sp<RenderNode>& fChild;
};
sk_sp<MotionBlurEffect> MotionBlurEffect::Make(sk_sp<sksg::Animator> animator,
sk_sp<sksg::RenderNode> child,
size_t samples_per_frame,
float shutter_angle, float shutter_phase) {
if (!samples_per_frame || shutter_angle <= 0) {
return nullptr;
}
// shutter_angle is [ 0 .. 720], mapped to [ 0 .. 2] (frame space)
// shutter_phase is [-360 .. 360], mapped to [-1 .. 1] (frame space)
const auto samples_duration = shutter_angle / 360,
phase = shutter_phase / 360,
dt = samples_duration / (samples_per_frame - 1);
return sk_sp<MotionBlurEffect>(new MotionBlurEffect(std::move(animator),
std::move(child),
samples_per_frame,
phase, dt));
}
MotionBlurEffect::MotionBlurEffect(sk_sp<sksg::Animator> animator,
sk_sp<sksg::RenderNode> child,
size_t samples, float phase, float dt)
: INHERITED({std::move(child)})
, fAnimator(std::move(animator))
, fSampleCount(samples)
, fPhase(phase)
, fDT(dt) {}
const sksg::RenderNode* MotionBlurEffect::onNodeAt(const SkPoint&) const {
return nullptr;
}
SkRect MotionBlurEffect::seekToSample(size_t sample_idx, const SkMatrix& ctm) const {
SkASSERT(sample_idx < fSampleCount);
fAnimator->tick(fT + fPhase + fDT * sample_idx);
SkASSERT(this->children().size() == 1ul);
return this->children()[0]->revalidate(nullptr, ctm);
}
SkRect MotionBlurEffect::onRevalidate(sksg::InvalidationController*, const SkMatrix& ctm) {
SkRect bounds = SkRect::MakeEmpty();
fVisibleSampleCount = 0;
for (size_t i = 0; i < fSampleCount; ++i) {
bounds.join(this->seekToSample(i, ctm));
fVisibleSampleCount += SkToSizeT(this->children()[0]->isVisible());
}
return bounds;
}
void MotionBlurEffect::renderToRaster8888Pow2Samples(SkCanvas* canvas,
const RenderContext* ctx) const {
// canvas is raster backed and RGBA 8888 or BGRA 8888, and fSamples is a power of 2.
// We can play dirty tricks.
// Don't worry about "Next"... this is exact.
const int shift = SkNextLog2(fVisibleSampleCount);
SkASSERT((size_t(1)<<shift) == fVisibleSampleCount);
SkASSERT(this->children().size() == 1ul);
const sk_sp<RenderNode>& child = this->children()[0];
SkAutoCanvasRestore acr(canvas, false);
canvas->saveLayer(this->bounds(), nullptr);
SkImageInfo info;
size_t rowBytes;
auto layer = (uint32_t*)canvas->accessTopLayerPixels(&info, &rowBytes);
SkASSERT(layer);
SkASSERT(info.colorType() == kRGBA_8888_SkColorType ||
info.colorType() == kBGRA_8888_SkColorType);
SkASSERT(!info.isEmpty());
std::vector<uint64_t> accum(info.width() * info.height());
SkDEBUGCODE(size_t frames_rendered = 0;)
bool needs_clear = false; // Cleared initially by saveLayer().
for (size_t i = 0; i < fSampleCount; ++i) {
this->seekToSample(i, canvas->getTotalMatrix());
if (!child->isVisible()) {
continue;
}
// Draw this subframe.
if (needs_clear) {
canvas->clear(0);
}
needs_clear = true;
child->render(canvas, ctx);
SkDEBUGCODE(frames_rendered++;)
// Pluck out the pixels we've drawn in the layer.
const uint32_t* src = layer;
uint64_t* dst = accum.data();
for (int y = 0; y < info.height(); y++) {
// Expand 8-bit to 16-bit and accumulate.
int n = info.width();
const auto row = src;
while (n >= 4) {
auto s = skvx::Vec<16, uint8_t >::Load(src);
auto d = skvx::Vec<16, uint16_t>::Load(dst);
(d + skvx::cast<uint16_t>(s)).store(dst);
src += 4;
dst += 4;
n -= 4;
}
while (n) {
auto s = skvx::Vec<4, uint8_t >::Load(src);
auto d = skvx::Vec<4, uint16_t>::Load(dst);
(d + skvx::cast<uint16_t>(s)).store(dst);
src += 1;
dst += 1;
n -= 1;
}
src = (const uint32_t*)( (const char*)row + rowBytes );
}
}
SkASSERT(frames_rendered == fVisibleSampleCount);
// Actually draw the frame using the accumulated subframes.
const uint64_t* src = accum.data();
uint32_t* dst = layer;
for (int y = 0; y < info.height(); y++) {
// Divide accumulated subframes through by sample count.
int n = info.width();
const auto row = dst;
while (n >= 4) {
auto s = skvx::Vec<16, uint16_t>::Load(src);
skvx::cast<uint8_t>(s >> shift).store(dst);
src += 4;
dst += 4;
n -= 4;
}
while (n) {
auto s = skvx::Vec<4, uint16_t>::Load(src);
skvx::cast<uint8_t>(s >> shift).store(dst);
src += 1;
dst += 1;
n -= 1;
}
dst = (uint32_t*)( (char*)row + rowBytes );
}
}
void MotionBlurEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
if (!fVisibleSampleCount) {
return;
}
SkASSERT(this->children().size() == 1ul);
const auto& child = this->children()[0];
// We're about to mutate/revalidate the subtree for sampling. Capture the invalidation
// at this scope, to prevent dirtying ancestor SG nodes (no way to revalidate the global scene).
AutoInvalBlocker aib(this, child);
SkPixmap pm;
if (canvas->peekPixels(&pm) && (canvas->imageInfo().colorType() == kRGBA_8888_SkColorType ||
canvas->imageInfo().colorType() == kBGRA_8888_SkColorType )
&& SkIsPow2(fVisibleSampleCount)) {
this->renderToRaster8888Pow2Samples(canvas, ctx);
return;
}
SkAutoCanvasRestore acr(canvas, false);
// Accumulate in F16 for more precision.
canvas->saveLayer(SkCanvas::SaveLayerRec(&this->bounds(), nullptr, SkCanvas::kF16ColorType));
const float frame_alpha = 1.0f / fVisibleSampleCount;
// Depending on whether we can defer frame blending,
// use a local (deferred) RenderContext or an explicit layer for frame/content rendering.
ScopedRenderContext frame_ctx(canvas, ctx);
SkPaint frame_paint;
const bool isolate_frames = frame_ctx->fBlendMode != SkBlendMode::kSrcOver;
if (isolate_frames) {
frame_paint.setAlphaf(frame_alpha);
frame_paint.setBlendMode(SkBlendMode::kPlus);
} else {
frame_ctx = frame_ctx.modulateOpacity(frame_alpha)
.modulateBlendMode(SkBlendMode::kPlus);
}
SkDEBUGCODE(size_t frames_rendered = 0;)
for (size_t i = 0; i < fSampleCount; ++i) {
this->seekToSample(i, canvas->getTotalMatrix());
if (!child->isVisible()) {
continue;
}
SkAutoCanvasRestore acr(canvas, false);
if (isolate_frames) {
canvas->saveLayer(nullptr, &frame_paint);
}
child->render(canvas, frame_ctx);
SkDEBUGCODE(frames_rendered++;)
}
SkASSERT(frames_rendered == fVisibleSampleCount);
}
} // namespace internal
} // namespace skottie