| 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 | , fChild(child) { |
| 25 | fMBNode->unobserveInval(fChild); |
| 26 | } |
| 27 | |
| 28 | ~AutoInvalBlocker() { |
| 29 | fMBNode->observeInval(fChild); |
| 30 | } |
| 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 | 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 | |
| 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 | , fDT(dt) {} |
| 65 | |
| 66 | const sksg::RenderNode* MotionBlurEffect::onNodeAt(const SkPoint&) const { |
| 67 | return nullptr; |
| 68 | } |
| 69 | |
| 70 | SkRect 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 | |
| 78 | SkRect 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 | |
| 90 | void 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 | |
| 193 | void 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 | |