1/*
2 * Copyright 2019 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "modules/skottie/src/effects/MotionBlurEffect.h"
9
10#include "include/core/SkCanvas.h"
11#include "include/core/SkMath.h"
12#include "include/core/SkPixmap.h"
13#include "include/private/SkVx.h"
14#include "modules/skottie/src/animator/Animator.h"
15#include "src/core/SkMathPriv.h"
16
17namespace skottie {
18namespace internal {
19
20class MotionBlurEffect::AutoInvalBlocker {
21public:
22 AutoInvalBlocker(const MotionBlurEffect* mb, const sk_sp<RenderNode>& child)
23 : fMBNode(const_cast<MotionBlurEffect*>(mb))
24 , fChild(child) {
25 fMBNode->unobserveInval(fChild);
26 }
27
28 ~AutoInvalBlocker() {
29 fMBNode->observeInval(fChild);
30 }
31
32private:
33 MotionBlurEffect* fMBNode;
34 const sk_sp<RenderNode>& fChild;
35};
36
37sk_sp<MotionBlurEffect> MotionBlurEffect::Make(sk_sp<Animator> animator,
38 sk_sp<sksg::RenderNode> child,
39 size_t samples_per_frame,
40 float shutter_angle, float shutter_phase) {
41 if (!samples_per_frame || shutter_angle <= 0) {
42 return nullptr;
43 }
44
45 // shutter_angle is [ 0 .. 720], mapped to [ 0 .. 2] (frame space)
46 // shutter_phase is [-360 .. 360], mapped to [-1 .. 1] (frame space)
47 const auto samples_duration = shutter_angle / 360,
48 phase = shutter_phase / 360,
49 dt = samples_duration / (samples_per_frame - 1);
50
51 return sk_sp<MotionBlurEffect>(new MotionBlurEffect(std::move(animator),
52 std::move(child),
53 samples_per_frame,
54 phase, dt));
55}
56
57MotionBlurEffect::MotionBlurEffect(sk_sp<Animator> animator,
58 sk_sp<sksg::RenderNode> child,
59 size_t samples, float phase, float dt)
60 : INHERITED({std::move(child)})
61 , fAnimator(std::move(animator))
62 , fSampleCount(samples)
63 , fPhase(phase)
64 , fDT(dt) {}
65
66const sksg::RenderNode* MotionBlurEffect::onNodeAt(const SkPoint&) const {
67 return nullptr;
68}
69
70SkRect MotionBlurEffect::seekToSample(size_t sample_idx, const SkMatrix& ctm) const {
71 SkASSERT(sample_idx < fSampleCount);
72 fAnimator->seek(fT + fPhase + fDT * sample_idx);
73
74 SkASSERT(this->children().size() == 1ul);
75 return this->children()[0]->revalidate(nullptr, ctm);
76}
77
78SkRect MotionBlurEffect::onRevalidate(sksg::InvalidationController*, const SkMatrix& ctm) {
79 SkRect bounds = SkRect::MakeEmpty();
80 fVisibleSampleCount = 0;
81
82 for (size_t i = 0; i < fSampleCount; ++i) {
83 bounds.join(this->seekToSample(i, ctm));
84 fVisibleSampleCount += SkToSizeT(this->children()[0]->isVisible());
85 }
86
87 return bounds;
88}
89
90void MotionBlurEffect::renderToRaster8888Pow2Samples(SkCanvas* canvas,
91 const RenderContext* ctx) const {
92 // canvas is raster backed and RGBA 8888 or BGRA 8888, and fSamples is a power of 2.
93 // We can play dirty tricks.
94
95 // Don't worry about "Next"... this is exact.
96 const int shift = SkNextLog2(fVisibleSampleCount);
97 SkASSERT((size_t(1)<<shift) == fVisibleSampleCount);
98
99 SkASSERT(this->children().size() == 1ul);
100 const sk_sp<RenderNode>& child = this->children()[0];
101
102 SkAutoCanvasRestore acr(canvas, false);
103 canvas->saveLayer(this->bounds(), nullptr);
104
105 SkImageInfo info;
106 size_t rowBytes;
107 auto layer = (uint32_t*)canvas->accessTopLayerPixels(&info, &rowBytes);
108 SkASSERT(layer);
109 SkASSERT(info.colorType() == kRGBA_8888_SkColorType ||
110 info.colorType() == kBGRA_8888_SkColorType);
111
112 SkASSERT(!info.isEmpty());
113 std::vector<uint64_t> accum(info.width() * info.height());
114
115 SkDEBUGCODE(size_t frames_rendered = 0;)
116 bool needs_clear = false; // Cleared initially by saveLayer().
117 for (size_t i = 0; i < fSampleCount; ++i) {
118 this->seekToSample(i, canvas->getTotalMatrix());
119
120 if (!child->isVisible()) {
121 continue;
122 }
123
124 // Draw this subframe.
125 if (needs_clear) {
126 canvas->clear(0);
127 }
128 needs_clear = true;
129 child->render(canvas, ctx);
130 SkDEBUGCODE(frames_rendered++;)
131
132 // Pluck out the pixels we've drawn in the layer.
133 const uint32_t* src = layer;
134 uint64_t* dst = accum.data();
135
136 for (int y = 0; y < info.height(); y++) {
137 // Expand 8-bit to 16-bit and accumulate.
138 int n = info.width();
139 const auto row = src;
140 while (n >= 4) {
141 auto s = skvx::Vec<16, uint8_t >::Load(src);
142 auto d = skvx::Vec<16, uint16_t>::Load(dst);
143
144 (d + skvx::cast<uint16_t>(s)).store(dst);
145
146 src += 4;
147 dst += 4;
148 n -= 4;
149 }
150 while (n) {
151 auto s = skvx::Vec<4, uint8_t >::Load(src);
152 auto d = skvx::Vec<4, uint16_t>::Load(dst);
153
154 (d + skvx::cast<uint16_t>(s)).store(dst);
155
156 src += 1;
157 dst += 1;
158 n -= 1;
159 }
160 src = (const uint32_t*)( (const char*)row + rowBytes );
161 }
162 }
163 SkASSERT(frames_rendered == fVisibleSampleCount);
164
165 // Actually draw the frame using the accumulated subframes.
166 const uint64_t* src = accum.data();
167 uint32_t* dst = layer;
168 for (int y = 0; y < info.height(); y++) {
169 // Divide accumulated subframes through by sample count.
170 int n = info.width();
171 const auto row = dst;
172 while (n >= 4) {
173 auto s = skvx::Vec<16, uint16_t>::Load(src);
174 skvx::cast<uint8_t>(s >> shift).store(dst);
175
176 src += 4;
177 dst += 4;
178 n -= 4;
179 }
180 while (n) {
181 auto s = skvx::Vec<4, uint16_t>::Load(src);
182 skvx::cast<uint8_t>(s >> shift).store(dst);
183
184 src += 1;
185 dst += 1;
186 n -= 1;
187 }
188
189 dst = (uint32_t*)( (char*)row + rowBytes );
190 }
191}
192
193void MotionBlurEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
194 if (!fVisibleSampleCount) {
195 return;
196 }
197
198 SkASSERT(this->children().size() == 1ul);
199 const auto& child = this->children()[0];
200
201 // We're about to mutate/revalidate the subtree for sampling. Capture the invalidation
202 // at this scope, to prevent dirtying ancestor SG nodes (no way to revalidate the global scene).
203 AutoInvalBlocker aib(this, child);
204
205 SkPixmap pm;
206 if (canvas->peekPixels(&pm) && (canvas->imageInfo().colorType() == kRGBA_8888_SkColorType ||
207 canvas->imageInfo().colorType() == kBGRA_8888_SkColorType )
208 && SkIsPow2(fVisibleSampleCount)) {
209 this->renderToRaster8888Pow2Samples(canvas, ctx);
210 return;
211 }
212
213 SkAutoCanvasRestore acr(canvas, false);
214
215 // Accumulate in F16 for more precision.
216 canvas->saveLayer(SkCanvas::SaveLayerRec(&this->bounds(), nullptr, SkCanvas::kF16ColorType));
217
218 const float frame_alpha = 1.0f / fVisibleSampleCount;
219
220 // Depending on whether we can defer frame blending,
221 // use a local (deferred) RenderContext or an explicit layer for frame/content rendering.
222 ScopedRenderContext frame_ctx(canvas, ctx);
223 SkPaint frame_paint;
224
225 const bool isolate_frames = frame_ctx->fBlendMode != SkBlendMode::kSrcOver;
226 if (isolate_frames) {
227 frame_paint.setAlphaf(frame_alpha);
228 frame_paint.setBlendMode(SkBlendMode::kPlus);
229 } else {
230 frame_ctx = frame_ctx.modulateOpacity(frame_alpha)
231 .modulateBlendMode(SkBlendMode::kPlus);
232 }
233
234 SkDEBUGCODE(size_t frames_rendered = 0;)
235 for (size_t i = 0; i < fSampleCount; ++i) {
236 this->seekToSample(i, canvas->getTotalMatrix());
237
238 if (!child->isVisible()) {
239 continue;
240 }
241
242 SkAutoCanvasRestore acr(canvas, false);
243 if (isolate_frames) {
244 canvas->saveLayer(nullptr, &frame_paint);
245 }
246
247 child->render(canvas, frame_ctx);
248 SkDEBUGCODE(frames_rendered++;)
249 }
250
251 SkASSERT(frames_rendered == fVisibleSampleCount);
252}
253
254} // namespace internal
255} // namespace skottie
256