/src/skia/modules/skottie/src/effects/MotionBlurEffect.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
17 | | namespace skottie { |
18 | | namespace internal { |
19 | | |
20 | | class MotionBlurEffect::AutoInvalBlocker { |
21 | | public: |
22 | | AutoInvalBlocker(const MotionBlurEffect* mb, const sk_sp<RenderNode>& child) |
23 | | : fMBNode(const_cast<MotionBlurEffect*>(mb)) |
24 | 0 | , fChild(child) { |
25 | 0 | fMBNode->unobserveInval(fChild); |
26 | 0 | } |
27 | | |
28 | 0 | ~AutoInvalBlocker() { |
29 | 0 | fMBNode->observeInval(fChild); |
30 | 0 | } |
31 | | |
32 | | private: |
33 | | MotionBlurEffect* fMBNode; |
34 | | const sk_sp<RenderNode>& fChild; |
35 | | }; |
36 | | |
37 | | sk_sp<MotionBlurEffect> MotionBlurEffect::Make(sk_sp<Animator> animator, |
38 | | sk_sp<sksg::RenderNode> child, |
39 | | size_t samples_per_frame, |
40 | 0 | float shutter_angle, float shutter_phase) { |
41 | 0 | if (!samples_per_frame || shutter_angle <= 0) { |
42 | 0 | return nullptr; |
43 | 0 | } |
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 | 0 | const auto samples_duration = shutter_angle / 360, |
48 | 0 | phase = shutter_phase / 360, |
49 | 0 | dt = samples_duration / (samples_per_frame - 1); |
50 | |
|
51 | 0 | return sk_sp<MotionBlurEffect>(new MotionBlurEffect(std::move(animator), |
52 | 0 | std::move(child), |
53 | 0 | samples_per_frame, |
54 | 0 | phase, dt)); |
55 | 0 | } |
56 | | |
57 | | MotionBlurEffect::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 | 0 | , fDT(dt) {} |
65 | | |
66 | 0 | const sksg::RenderNode* MotionBlurEffect::onNodeAt(const SkPoint&) const { |
67 | 0 | return nullptr; |
68 | 0 | } |
69 | | |
70 | 0 | SkRect MotionBlurEffect::seekToSample(size_t sample_idx, const SkMatrix& ctm) const { |
71 | 0 | SkASSERT(sample_idx < fSampleCount); |
72 | 0 | fAnimator->seek(fT + fPhase + fDT * sample_idx); |
73 | |
|
74 | 0 | SkASSERT(this->children().size() == 1ul); |
75 | 0 | return this->children()[0]->revalidate(nullptr, ctm); |
76 | 0 | } |
77 | | |
78 | 0 | SkRect MotionBlurEffect::onRevalidate(sksg::InvalidationController*, const SkMatrix& ctm) { |
79 | 0 | SkRect bounds = SkRect::MakeEmpty(); |
80 | 0 | fVisibleSampleCount = 0; |
81 | |
|
82 | 0 | for (size_t i = 0; i < fSampleCount; ++i) { |
83 | 0 | bounds.join(this->seekToSample(i, ctm)); |
84 | 0 | fVisibleSampleCount += SkToSizeT(this->children()[0]->isVisible()); |
85 | 0 | } |
86 | |
|
87 | 0 | return bounds; |
88 | 0 | } |
89 | | |
90 | | void MotionBlurEffect::renderToRaster8888Pow2Samples(SkCanvas* canvas, |
91 | 0 | 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 | 0 | const int shift = SkNextLog2(fVisibleSampleCount); |
97 | 0 | SkASSERT((size_t(1)<<shift) == fVisibleSampleCount); |
98 | |
|
99 | 0 | SkASSERT(this->children().size() == 1ul); |
100 | 0 | const sk_sp<RenderNode>& child = this->children()[0]; |
101 | |
|
102 | 0 | SkAutoCanvasRestore acr(canvas, false); |
103 | 0 | canvas->saveLayer(this->bounds(), nullptr); |
104 | |
|
105 | 0 | SkImageInfo info; |
106 | 0 | size_t rowBytes; |
107 | 0 | auto layer = (uint32_t*)canvas->accessTopLayerPixels(&info, &rowBytes); |
108 | 0 | SkASSERT(layer); |
109 | 0 | SkASSERT(info.colorType() == kRGBA_8888_SkColorType || |
110 | 0 | info.colorType() == kBGRA_8888_SkColorType); |
111 | |
|
112 | 0 | SkASSERT(!info.isEmpty()); |
113 | 0 | std::vector<uint64_t> accum(info.width() * info.height()); |
114 | |
|
115 | 0 | SkDEBUGCODE(size_t frames_rendered = 0;) |
116 | 0 | bool needs_clear = false; // Cleared initially by saveLayer(). |
117 | 0 | for (size_t i = 0; i < fSampleCount; ++i) { |
118 | 0 | this->seekToSample(i, canvas->getTotalMatrix()); |
119 | |
|
120 | 0 | if (!child->isVisible()) { |
121 | 0 | continue; |
122 | 0 | } |
123 | | |
124 | | // Draw this subframe. |
125 | 0 | if (needs_clear) { |
126 | 0 | canvas->clear(0); |
127 | 0 | } |
128 | 0 | needs_clear = true; |
129 | 0 | child->render(canvas, ctx); |
130 | 0 | SkDEBUGCODE(frames_rendered++;) |
131 | | |
132 | | // Pluck out the pixels we've drawn in the layer. |
133 | 0 | const uint32_t* src = layer; |
134 | 0 | uint64_t* dst = accum.data(); |
135 | |
|
136 | 0 | for (int y = 0; y < info.height(); y++) { |
137 | | // Expand 8-bit to 16-bit and accumulate. |
138 | 0 | int n = info.width(); |
139 | 0 | const auto row = src; |
140 | 0 | while (n >= 4) { |
141 | 0 | auto s = skvx::Vec<16, uint8_t >::Load(src); |
142 | 0 | auto d = skvx::Vec<16, uint16_t>::Load(dst); |
143 | |
|
144 | 0 | (d + skvx::cast<uint16_t>(s)).store(dst); |
145 | |
|
146 | 0 | src += 4; |
147 | 0 | dst += 4; |
148 | 0 | n -= 4; |
149 | 0 | } |
150 | 0 | while (n) { |
151 | 0 | auto s = skvx::Vec<4, uint8_t >::Load(src); |
152 | 0 | auto d = skvx::Vec<4, uint16_t>::Load(dst); |
153 | |
|
154 | 0 | (d + skvx::cast<uint16_t>(s)).store(dst); |
155 | |
|
156 | 0 | src += 1; |
157 | 0 | dst += 1; |
158 | 0 | n -= 1; |
159 | 0 | } |
160 | 0 | src = (const uint32_t*)( (const char*)row + rowBytes ); |
161 | 0 | } |
162 | 0 | } |
163 | 0 | SkASSERT(frames_rendered == fVisibleSampleCount); |
164 | | |
165 | | // Actually draw the frame using the accumulated subframes. |
166 | 0 | const uint64_t* src = accum.data(); |
167 | 0 | uint32_t* dst = layer; |
168 | 0 | for (int y = 0; y < info.height(); y++) { |
169 | | // Divide accumulated subframes through by sample count. |
170 | 0 | int n = info.width(); |
171 | 0 | const auto row = dst; |
172 | 0 | while (n >= 4) { |
173 | 0 | auto s = skvx::Vec<16, uint16_t>::Load(src); |
174 | 0 | skvx::cast<uint8_t>(s >> shift).store(dst); |
175 | |
|
176 | 0 | src += 4; |
177 | 0 | dst += 4; |
178 | 0 | n -= 4; |
179 | 0 | } |
180 | 0 | while (n) { |
181 | 0 | auto s = skvx::Vec<4, uint16_t>::Load(src); |
182 | 0 | skvx::cast<uint8_t>(s >> shift).store(dst); |
183 | |
|
184 | 0 | src += 1; |
185 | 0 | dst += 1; |
186 | 0 | n -= 1; |
187 | 0 | } |
188 | |
|
189 | 0 | dst = (uint32_t*)( (char*)row + rowBytes ); |
190 | 0 | } |
191 | 0 | } |
192 | | |
193 | 0 | void MotionBlurEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const { |
194 | 0 | if (!fVisibleSampleCount) { |
195 | 0 | return; |
196 | 0 | } |
197 | | |
198 | 0 | SkASSERT(this->children().size() == 1ul); |
199 | 0 | 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 | 0 | AutoInvalBlocker aib(this, child); |
204 | |
|
205 | 0 | SkPixmap pm; |
206 | 0 | if (canvas->peekPixels(&pm) && (canvas->imageInfo().colorType() == kRGBA_8888_SkColorType || |
207 | 0 | canvas->imageInfo().colorType() == kBGRA_8888_SkColorType ) |
208 | 0 | && SkIsPow2(fVisibleSampleCount)) { |
209 | 0 | this->renderToRaster8888Pow2Samples(canvas, ctx); |
210 | 0 | return; |
211 | 0 | } |
212 | | |
213 | 0 | SkAutoCanvasRestore acr1(canvas, false); |
214 | | |
215 | | // Accumulate in F16 for more precision. |
216 | 0 | canvas->saveLayer(SkCanvas::SaveLayerRec(&this->bounds(), nullptr, SkCanvas::kF16ColorType)); |
217 | |
|
218 | 0 | 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 | 0 | ScopedRenderContext frame_ctx(canvas, ctx); |
223 | 0 | SkPaint frame_paint; |
224 | |
|
225 | 0 | const bool isolate_frames = frame_ctx->fBlendMode != SkBlendMode::kSrcOver; |
226 | 0 | if (isolate_frames) { |
227 | 0 | frame_paint.setAlphaf(frame_alpha); |
228 | 0 | frame_paint.setBlendMode(SkBlendMode::kPlus); |
229 | 0 | } else { |
230 | 0 | frame_ctx = frame_ctx.modulateOpacity(frame_alpha) |
231 | 0 | .modulateBlendMode(SkBlendMode::kPlus); |
232 | 0 | } |
233 | |
|
234 | 0 | SkDEBUGCODE(size_t frames_rendered = 0;) |
235 | 0 | for (size_t i = 0; i < fSampleCount; ++i) { |
236 | 0 | this->seekToSample(i, canvas->getTotalMatrix()); |
237 | |
|
238 | 0 | if (!child->isVisible()) { |
239 | 0 | continue; |
240 | 0 | } |
241 | | |
242 | 0 | SkAutoCanvasRestore acr2(canvas, false); |
243 | 0 | if (isolate_frames) { |
244 | 0 | canvas->saveLayer(nullptr, &frame_paint); |
245 | 0 | } |
246 | |
|
247 | 0 | child->render(canvas, frame_ctx); |
248 | 0 | SkDEBUGCODE(frames_rendered++;) |
249 | 0 | } |
250 | |
|
251 | 0 | SkASSERT(frames_rendered == fVisibleSampleCount); |
252 | 0 | } |
253 | | |
254 | | } // namespace internal |
255 | | } // namespace skottie |