/* * 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 "modules/skottie/src/animator/Animator.h" #include "src/core/SkMathPriv.h" namespace skottie { namespace internal { class MotionBlurEffect::AutoInvalBlocker { public: AutoInvalBlocker(const MotionBlurEffect* mb, const sk_sp& child) : fMBNode(const_cast(mb)) , fChild(child) { fMBNode->unobserveInval(fChild); } ~AutoInvalBlocker() { fMBNode->observeInval(fChild); } private: MotionBlurEffect* fMBNode; const sk_sp& fChild; }; sk_sp MotionBlurEffect::Make(sk_sp animator, sk_sp 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(new MotionBlurEffect(std::move(animator), std::move(child), samples_per_frame, phase, dt)); } MotionBlurEffect::MotionBlurEffect(sk_sp animator, sk_sp 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->seek(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)<children().size() == 1ul); const sk_sp& 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 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(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(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(s >> shift).store(dst); src += 4; dst += 4; n -= 4; } while (n) { auto s = skvx::Vec<4, uint16_t>::Load(src); skvx::cast(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 acr1(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 acr2(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