nsFilterInstance.cpp 33.7 KB
Newer Older
1
2
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
4
5
6
7
8
9
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Main header first:
#include "nsFilterInstance.h"

10
11
12
// MFBT headers next:
#include "mozilla/UniquePtr.h"

13
// Keep others in (case-insensitive) order:
14
#include "FilterSupport.h"
15
#include "ImgDrawResult.h"
16
#include "SVGContentUtils.h"
17
#include "gfx2DGlue.h"
18
#include "gfxContext.h"
19
#include "gfxPlatform.h"
20

21
#include "gfxUtils.h"
22
23
#include "mozilla/Unused.h"
#include "mozilla/gfx/Filters.h"
24
#include "mozilla/gfx/Helpers.h"
25
#include "mozilla/gfx/PatternHelpers.h"
26
#include "mozilla/StaticPrefs_gfx.h"
27
#include "nsCSSFilterInstance.h"
28
#include "nsSVGDisplayableFrame.h"
29
30
#include "nsSVGFilterInstance.h"
#include "nsSVGFilterPaintCallback.h"
31
#include "nsSVGIntegrationUtils.h"
32
33
34
35
36
#include "nsSVGUtils.h"

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::gfx;
37
using namespace mozilla::image;
38

39
FilterDescription nsFilterInstance::GetFilterDescription(
40
    nsIContent* aFilteredElement, Span<const StyleFilter> aFilterChain,
41
42
43
    bool aFilterInputIsTainted, const UserSpaceMetrics& aMetrics,
    const gfxRect& aBBox,
    nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) {
44
  gfxMatrix identity;
45
46
47
  nsFilterInstance instance(nullptr, aFilteredElement, aMetrics, aFilterChain,
                            aFilterInputIsTainted, nullptr, identity, nullptr,
                            nullptr, nullptr, &aBBox);
48
49
50
51
52
53
  if (!instance.IsInitialized()) {
    return FilterDescription();
  }
  return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages);
}

54
static UniquePtr<UserSpaceMetrics> UserSpaceMetricsForFrame(nsIFrame* aFrame) {
55
  if (aFrame->GetContent()->IsSVGElement()) {
56
    SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
57
58
59
60
61
    return MakeUnique<SVGElementMetrics>(element);
  }
  return MakeUnique<NonSVGFrameUserSpaceMetrics>(aFrame);
}

62
63
64
65
void nsFilterInstance::PaintFilteredFrame(
    nsIFrame* aFilteredFrame, gfxContext* aCtx,
    nsSVGFilterPaintCallback* aPaintCallback, const nsRegion* aDirtyArea,
    imgDrawingParams& aImgParams, float aOpacity) {
66
  auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
67
68
  UniquePtr<UserSpaceMetrics> metrics =
      UserSpaceMetricsForFrame(aFilteredFrame);
69
70

  gfxContextMatrixAutoSaveRestore autoSR(aCtx);
71
  gfxSize scaleFactors = aCtx->CurrentMatrixDouble().ScaleFactors(true);
72
73
74
75
  if (scaleFactors.IsEmpty()) {
    return;
  }

76
  gfxMatrix scaleMatrix(scaleFactors.width, 0.0f, 0.0f, scaleFactors.height,
77
78
79
80
81
82
83
                        0.0f, 0.0f);

  gfxMatrix reverseScaleMatrix = scaleMatrix;
  DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
  MOZ_ASSERT(invertible);
  // Pull scale vector out of aCtx's transform, put all scale factors, which
  // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits.
84
  aCtx->SetMatrixDouble(reverseScaleMatrix * aCtx->CurrentMatrixDouble());
85
86

  gfxMatrix scaleMatrixInDevUnits =
87
      scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
88

89
90
  // Hardcode InputIsTainted to true because we don't want JS to be able to
  // read the rendered contents of aFilteredFrame.
91
92
  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
                            *metrics, filterChain, /* InputIsTainted */ true,
93
94
                            aPaintCallback, scaleMatrixInDevUnits, aDirtyArea,
                            nullptr, nullptr, nullptr);
95
  if (instance.IsInitialized()) {
96
    instance.Render(aCtx, aImgParams, aOpacity);
97
98
99
  }
}

100
static mozilla::wr::ComponentTransferFuncType FuncTypeToWr(uint8_t aFuncType) {
101
  MOZ_ASSERT(aFuncType != SVG_FECOMPONENTTRANSFER_SAME_AS_R);
102
103
104
105
106
107
108
109
110
  switch (aFuncType) {
    case SVG_FECOMPONENTTRANSFER_TYPE_TABLE:
      return mozilla::wr::ComponentTransferFuncType::Table;
    case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE:
      return mozilla::wr::ComponentTransferFuncType::Discrete;
    case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR:
      return mozilla::wr::ComponentTransferFuncType::Linear;
    case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA:
      return mozilla::wr::ComponentTransferFuncType::Gamma;
111
    case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY:
112
    default:
113
      return mozilla::wr::ComponentTransferFuncType::Identity;
114
  }
115
  MOZ_ASSERT_UNREACHABLE("all func types not handled?");
116
117
118
  return mozilla::wr::ComponentTransferFuncType::Identity;
}

119
bool nsFilterInstance::BuildWebRenderFilters(nsIFrame* aFilteredFrame,
120
                                             Span<const StyleFilter> aFilters,
121
                                             WrFiltersHolder& aWrFilters,
122
                                             Maybe<nsRect>& aPostFilterClip) {
123
124
125
  aWrFilters.filters.Clear();
  aWrFilters.filter_datas.Clear();
  aWrFilters.values.Clear();
126

127
128
  UniquePtr<UserSpaceMetrics> metrics =
      UserSpaceMetricsForFrame(aFilteredFrame);
129
130
131
132
133

  // TODO: simply using an identity matrix here, was pulling the scale from a
  // gfx context for the non-wr path.
  gfxMatrix scaleMatrix;
  gfxMatrix scaleMatrixInDevUnits =
134
      scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
135
136
137
138
139

  // Hardcode inputIsTainted to true because we don't want JS to be able to
  // read the rendered contents of aFilteredFrame.
  bool inputIsTainted = true;
  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
140
                            *metrics, aFilters, inputIsTainted, nullptr,
141
142
                            scaleMatrixInDevUnits, nullptr, nullptr, nullptr,
                            nullptr);
143
144
145
146
147

  if (!instance.IsInitialized()) {
    return false;
  }

148
149
150
  // If there are too many filters to render, then just pretend that we
  // succeeded, and don't render any of them.
  if (instance.mFilterDescription.mPrimitives.Length() >
151
      StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
152
153
154
    return true;
  }

155
  Maybe<IntRect> finalClip;
156
157
158
159
160
161
162
163
164
  bool srgb = true;
  // We currently apply the clip on the stacking context after applying filters,
  // but primitive subregions imply clipping after each filter and not just the
  // end of the chain. For some types of filter it doesn't matter, but for those
  // which sample outside of the location of the destination pixel like blurs,
  // only clipping after could produce incorrect results, so we bail out in this
  // case.
  // We can lift this restriction once we have added support for primitive
  // subregions to WebRender's filters.
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
  for (uint32_t i = 0; i < instance.mFilterDescription.mPrimitives.Length();
       i++) {
    const auto& primitive = instance.mFilterDescription.mPrimitives[i];

    // WebRender only supports filters with one input.
    if (primitive.NumberOfInputs() != 1) {
      return false;
    }
    // The first primitive must have the source graphic as the input, all
    // other primitives must have the prior primitive as the input, otherwise
    // it's not supported by WebRender.
    if (i == 0) {
      if (primitive.InputPrimitiveIndex(0) !=
          FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic) {
        return false;
      }
    } else if (primitive.InputPrimitiveIndex(0) != int32_t(i - 1)) {
      return false;
    }
184

185
186
187
    bool previousSrgb = srgb;
    bool primNeedsSrgb = primitive.InputColorSpace(0) == gfx::ColorSpace::SRGB;
    if (srgb && !primNeedsSrgb) {
188
      aWrFilters.filters.AppendElement(wr::FilterOp::SrgbToLinear());
189
    } else if (!srgb && primNeedsSrgb) {
190
      aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
191
    }
192
    srgb = primitive.OutputColorSpace() == gfx::ColorSpace::SRGB;
193
194
195
196
197
198
199

    const PrimitiveAttributes& attr = primitive.Attributes();

    bool filterIsNoop = false;

    if (attr.is<OpacityAttributes>()) {
      float opacity = attr.as<OpacityAttributes>().mOpacity;
200
      aWrFilters.filters.AppendElement(wr::FilterOp::Opacity(
201
          wr::PropertyBinding<float>::Value(opacity), opacity));
202
    } else if (attr.is<ColorMatrixAttributes>()) {
203
204
      const ColorMatrixAttributes& attributes =
          attr.as<ColorMatrixAttributes>();
205
206

      float transposed[20];
207
208
209
210
211
212
213
214
215
216
      if (gfx::ComputeColorMatrix(attributes, transposed)) {
        float matrix[20] = {
            transposed[0], transposed[5], transposed[10], transposed[15],
            transposed[1], transposed[6], transposed[11], transposed[16],
            transposed[2], transposed[7], transposed[12], transposed[17],
            transposed[3], transposed[8], transposed[13], transposed[18],
            transposed[4], transposed[9], transposed[14], transposed[19]};

        aWrFilters.filters.AppendElement(wr::FilterOp::ColorMatrix(matrix));
      } else {
217
218
        filterIsNoop = true;
      }
219
    } else if (attr.is<GaussianBlurAttributes>()) {
220
      if (finalClip) {
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
        // There's a clip that needs to apply before the blur filter, but
        // WebRender only lets us apply the clip at the end of the filter
        // chain. Clipping after a blur is not equivalent to clipping before
        // a blur, so bail out.
        return false;
      }

      const GaussianBlurAttributes& blur = attr.as<GaussianBlurAttributes>();

      const Size& stdDev = blur.mStdDeviation;
      if (stdDev.width != stdDev.height) {
        return false;
      }

      float radius = stdDev.width;
      if (radius != 0.0) {
237
        aWrFilters.filters.AppendElement(wr::FilterOp::Blur(radius));
238
239
240
241
      } else {
        filterIsNoop = true;
      }
    } else if (attr.is<DropShadowAttributes>()) {
242
      if (finalClip) {
243
244
245
246
247
248
249
250
251
252
253
        // We have to bail out for the same reason we would with a blur filter.
        return false;
      }

      const DropShadowAttributes& shadow = attr.as<DropShadowAttributes>();

      const Size& stdDev = shadow.mStdDeviation;
      if (stdDev.width != stdDev.height) {
        return false;
      }

254
      sRGBColor color = shadow.mColor;
255
      if (!primNeedsSrgb) {
256
257
258
        color = sRGBColor(gsRGBToLinearRGBMap[uint8_t(color.r * 255)],
                          gsRGBToLinearRGBMap[uint8_t(color.g * 255)],
                          gsRGBToLinearRGBMap[uint8_t(color.b * 255)], color.a);
259
      }
260
      wr::Shadow wrShadow;
261
      wrShadow.offset = {shadow.mOffset.x, shadow.mOffset.y};
262
263
264
      wrShadow.color = wr::ToColorF(ToDeviceColor(color));
      wrShadow.blur_radius = stdDev.width;
      wr::FilterOp filterOp = wr::FilterOp::DropShadow(wrShadow);
265

266
267
268
269
270
      aWrFilters.filters.AppendElement(filterOp);
    } else if (attr.is<ComponentTransferAttributes>()) {
      const ComponentTransferAttributes& attributes =
          attr.as<ComponentTransferAttributes>();

271
272
273
      size_t numValues =
          attributes.mValues[0].Length() + attributes.mValues[1].Length() +
          attributes.mValues[2].Length() + attributes.mValues[3].Length();
274
275
276
277
278
279
280
281
282
      if (numValues > 1024) {
        // Depending on how the wr shaders are implemented we may need to
        // limit the total number of values.
        return false;
      }

      wr::FilterOp filterOp = {wr::FilterOp::Tag::ComponentTransfer};
      wr::WrFilterData filterData;
      aWrFilters.values.AppendElement(nsTArray<float>());
283
284
      nsTArray<float>* values =
          &aWrFilters.values[aWrFilters.values.Length() - 1];
285
286
287
288
289
290
291
      values->SetCapacity(numValues);

      filterData.funcR_type = FuncTypeToWr(attributes.mTypes[0]);
      size_t R_startindex = values->Length();
      values->AppendElements(attributes.mValues[0]);
      filterData.R_values_count = attributes.mValues[0].Length();

292
293
294
      size_t indexToUse =
          attributes.mTypes[1] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 1;
      filterData.funcG_type = FuncTypeToWr(attributes.mTypes[indexToUse]);
295
      size_t G_startindex = values->Length();
296
297
      values->AppendElements(attributes.mValues[indexToUse]);
      filterData.G_values_count = attributes.mValues[indexToUse].Length();
298

299
300
301
      indexToUse =
          attributes.mTypes[2] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 2;
      filterData.funcB_type = FuncTypeToWr(attributes.mTypes[indexToUse]);
302
      size_t B_startindex = values->Length();
303
304
      values->AppendElements(attributes.mValues[indexToUse]);
      filterData.B_values_count = attributes.mValues[indexToUse].Length();
305
306
307
308
309
310

      filterData.funcA_type = FuncTypeToWr(attributes.mTypes[3]);
      size_t A_startindex = values->Length();
      values->AppendElements(attributes.mValues[3]);
      filterData.A_values_count = attributes.mValues[3].Length();

311
312
313
314
315
316
317
318
      filterData.R_values =
          filterData.R_values_count > 0 ? &((*values)[R_startindex]) : nullptr;
      filterData.G_values =
          filterData.G_values_count > 0 ? &((*values)[G_startindex]) : nullptr;
      filterData.B_values =
          filterData.B_values_count > 0 ? &((*values)[B_startindex]) : nullptr;
      filterData.A_values =
          filterData.A_values_count > 0 ? &((*values)[A_startindex]) : nullptr;
319
320
321

      aWrFilters.filters.AppendElement(filterOp);
      aWrFilters.filter_datas.AppendElement(filterData);
322
323
324
325
    } else {
      return false;
    }

326
    if (filterIsNoop && aWrFilters.filters.Length() > 0 &&
327
328
329
330
        (aWrFilters.filters.LastElement().tag ==
             wr::FilterOp::Tag::SrgbToLinear ||
         aWrFilters.filters.LastElement().tag ==
             wr::FilterOp::Tag::LinearToSrgb)) {
331
332
333
334
335
336
337
      // We pushed a color space conversion filter in prevision of applying
      // another filter which turned out to be a no-op, so the conversion is
      // unnecessary. Remove it from the filter list.
      // This is both an optimization and a way to pass the wptest
      // css/filter-effects/filter-scale-001.html for which the needless
      // sRGB->linear->no-op->sRGB roundtrip introduces a slight error and we
      // cannot add fuzziness to the test.
338
      Unused << aWrFilters.filters.PopLastElement();
339
      srgb = previousSrgb;
340
    }
341
342
343
344
345
346
347
348
349

    if (!filterIsNoop) {
      if (finalClip.isNothing()) {
        finalClip = Some(primitive.PrimitiveSubregion());
      } else {
        finalClip =
            Some(primitive.PrimitiveSubregion().Intersect(finalClip.value()));
      }
    }
350
351
352
  }

  if (!srgb) {
353
    aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
354
355
  }

356
357
  if (finalClip) {
    aPostFilterClip = Some(instance.FilterSpaceToFrameSpace(finalClip.value()));
358
359
360
361
  }
  return true;
}

362
363
nsRegion nsFilterInstance::GetPostFilterDirtyArea(
    nsIFrame* aFilteredFrame, const nsRegion& aPreFilterDirtyRegion) {
364
365
  if (aPreFilterDirtyRegion.IsEmpty()) {
    return nsRegion();
366
367
  }

368
  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
369
  auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
370
371
  UniquePtr<UserSpaceMetrics> metrics =
      UserSpaceMetricsForFrame(aFilteredFrame);
372
373
  // Hardcode InputIsTainted to true because we don't want JS to be able to
  // read the rendered contents of aFilteredFrame.
374
375
376
  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
                            *metrics, filterChain, /* InputIsTainted */ true,
                            nullptr, tm, nullptr, &aPreFilterDirtyRegion);
377
  if (!instance.IsInitialized()) {
378
    return nsRegion();
379
  }
380

381
382
383
  // We've passed in the source's dirty area so the instance knows about it.
  // Now we can ask the instance to compute the area of the filter output
  // that's dirty.
384
  return instance.ComputePostFilterDirtyRegion();
385
386
}

387
388
nsRegion nsFilterInstance::GetPreFilterNeededArea(
    nsIFrame* aFilteredFrame, const nsRegion& aPostFilterDirtyRegion) {
389
  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
390
  auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
391
392
  UniquePtr<UserSpaceMetrics> metrics =
      UserSpaceMetricsForFrame(aFilteredFrame);
393
394
  // Hardcode InputIsTainted to true because we don't want JS to be able to
  // read the rendered contents of aFilteredFrame.
395
396
397
  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
                            *metrics, filterChain, /* InputIsTainted */ true,
                            nullptr, tm, &aPostFilterDirtyRegion);
398
399
400
  if (!instance.IsInitialized()) {
    return nsRect();
  }
401

402
403
  // Now we can ask the instance to compute the area of the source
  // that's needed.
404
  return instance.ComputeSourceNeededRect();
405
406
}

407
408
409
nsRect nsFilterInstance::GetPostFilterBounds(nsIFrame* aFilteredFrame,
                                             const gfxRect* aOverrideBBox,
                                             const nsRect* aPreFilterBounds) {
410
  MOZ_ASSERT(!(aFilteredFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
411
                 !(aFilteredFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
412
413
             "Non-display SVG do not maintain visual overflow rects");

414
415
416
417
418
419
  nsRegion preFilterRegion;
  nsRegion* preFilterRegionPtr = nullptr;
  if (aPreFilterBounds) {
    preFilterRegion = *aPreFilterBounds;
    preFilterRegionPtr = &preFilterRegion;
  }
420

421
  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
422
  auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
423
424
  UniquePtr<UserSpaceMetrics> metrics =
      UserSpaceMetricsForFrame(aFilteredFrame);
425
426
  // Hardcode InputIsTainted to true because we don't want JS to be able to
  // read the rendered contents of aFilteredFrame.
427
428
429
430
  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
                            *metrics, filterChain, /* InputIsTainted */ true,
                            nullptr, tm, nullptr, preFilterRegionPtr,
                            aPreFilterBounds, aOverrideBBox);
431
432
433
  if (!instance.IsInitialized()) {
    return nsRect();
  }
434
435

  return instance.ComputePostFilterExtents();
436
437
}

438
439
nsFilterInstance::nsFilterInstance(
    nsIFrame* aTargetFrame, nsIContent* aTargetContent,
440
441
442
    const UserSpaceMetrics& aMetrics, Span<const StyleFilter> aFilterChain,
    bool aFilterInputIsTainted, nsSVGFilterPaintCallback* aPaintCallback,
    const gfxMatrix& aPaintTransform, const nsRegion* aPostFilterDirtyRegion,
443
444
445
446
447
448
449
450
451
    const nsRegion* aPreFilterDirtyRegion,
    const nsRect* aPreFilterVisualOverflowRectOverride,
    const gfxRect* aOverrideBBox)
    : mTargetFrame(aTargetFrame),
      mTargetContent(aTargetContent),
      mMetrics(aMetrics),
      mPaintCallback(aPaintCallback),
      mPaintTransform(aPaintTransform),
      mInitialized(false) {
452
453
454
  if (aOverrideBBox) {
    mTargetBBox = *aOverrideBBox;
  } else {
455
456
    MOZ_ASSERT(mTargetFrame,
               "Need to supply a frame when there's no aOverrideBBox");
457
458
    mTargetBBox = nsSVGUtils::GetBBox(mTargetFrame,
                                      nsSVGUtils::eUseFrameBoundsForOuterSVG |
459
                                          nsSVGUtils::eBBoxIncludeFillGeometry);
460
  }
461

462
  // Compute user space to filter space transforms.
463
  if (!ComputeUserSpaceToFilterSpaceScale()) {
464
465
466
    return;
  }

467
  if (!ComputeTargetBBoxInFilterSpace()) {
468
469
470
471
    return;
  }

  // Get various transforms:
472
473
  gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.width, 0.0f, 0.0f,
                              mFilterSpaceToUserSpaceScale.height, 0.0f, 0.0f);
474
475

  mFilterSpaceToFrameSpaceInCSSPxTransform =
476
      filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
477
478
  // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
  mFrameSpaceInCSSPxToFilterSpaceTransform =
479
      mFilterSpaceToFrameSpaceInCSSPxTransform;
480
481
  mFrameSpaceInCSSPxToFilterSpaceTransform.Invert();

482
483
484
  nsIntRect targetBounds;
  if (aPreFilterVisualOverflowRectOverride) {
    targetBounds =
485
        FrameSpaceToFilterSpace(aPreFilterVisualOverflowRectOverride);
486
487
488
489
490
491
  } else if (mTargetFrame) {
    nsRect preFilterVOR = mTargetFrame->GetPreEffectsVisualOverflowRect();
    targetBounds = FrameSpaceToFilterSpace(&preFilterVOR);
  }
  mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds);

492
  // Build the filter graph.
493
494
  if (NS_FAILED(
          BuildPrimitives(aFilterChain, aTargetFrame, aFilterInputIsTainted))) {
495
496
497
498
    return;
  }

  // Convert the passed in rects from frame space to filter space:
499
500
  mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion);
  mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion);
501

502
503
504
  mInitialized = true;
}

505
bool nsFilterInstance::ComputeTargetBBoxInFilterSpace() {
506
507
508
  gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox);
  targetBBoxInFilterSpace.RoundOut();

509
510
  return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace,
                                    &mTargetBBoxInFilterSpace);
511
512
}

513
bool nsFilterInstance::ComputeUserSpaceToFilterSpaceScale() {
514
  if (mTargetFrame) {
515
516
517
    mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors(true);
    if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
        mUserSpaceToFilterSpaceScale.height <= 0.0f) {
518
      // Nothing should be rendered.
519
      return false;
520
    }
521
522
  } else {
    mUserSpaceToFilterSpaceScale = gfxSize(1.0, 1.0);
523
524
  }

525
  mFilterSpaceToUserSpaceScale =
526
527
      gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
              1.0f / mUserSpaceToFilterSpaceScale.height);
528

529
  return true;
530
531
}

532
533
gfxRect nsFilterInstance::UserSpaceToFilterSpace(
    const gfxRect& aUserSpaceRect) const {
534
535
536
537
  gfxRect filterSpaceRect = aUserSpaceRect;
  filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width,
                        mUserSpaceToFilterSpaceScale.height);
  return filterSpaceRect;
538
539
}

540
541
gfxRect nsFilterInstance::FilterSpaceToUserSpace(
    const gfxRect& aFilterSpaceRect) const {
542
543
544
545
  gfxRect userSpaceRect = aFilterSpaceRect;
  userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width,
                      mFilterSpaceToUserSpaceScale.height);
  return userSpaceRect;
546
547
}

548
549
550
nsresult nsFilterInstance::BuildPrimitives(Span<const StyleFilter> aFilterChain,
                                           nsIFrame* aTargetFrame,
                                           bool aFilterInputIsTainted) {
551
  nsTArray<FilterPrimitiveDescription> primitiveDescriptions;
552

553
  for (uint32_t i = 0; i < aFilterChain.Length(); i++) {
554
555
556
557
558
    bool inputIsTainted = primitiveDescriptions.IsEmpty()
                              ? aFilterInputIsTainted
                              : primitiveDescriptions.LastElement().IsTainted();
    nsresult rv = BuildPrimitivesForFilter(
        aFilterChain[i], aTargetFrame, inputIsTainted, primitiveDescriptions);
559
560
561
562
    if (NS_FAILED(rv)) {
      return rv;
    }
  }
563

564
  mFilterDescription = FilterDescription(std::move(primitiveDescriptions));
565

566
567
568
  return NS_OK;
}

569
nsresult nsFilterInstance::BuildPrimitivesForFilter(
570
    const StyleFilter& aFilter, nsIFrame* aTargetFrame, bool aInputIsTainted,
571
    nsTArray<FilterPrimitiveDescription>& aPrimitiveDescriptions) {
572
  NS_ASSERTION(mUserSpaceToFilterSpaceScale.width > 0.0f &&
573
                   mFilterSpaceToUserSpaceScale.height > 0.0f,
574
575
               "scale factors between spaces should be positive values");

576
  if (aFilter.IsUrl()) {
577
    // Build primitives for an SVG filter.
578
    nsSVGFilterInstance svgFilterInstance(aFilter, aTargetFrame, mTargetContent,
579
                                          mMetrics, mTargetBBox,
580
                                          mUserSpaceToFilterSpaceScale);
581
582
583
584
    if (!svgFilterInstance.IsInitialized()) {
      return NS_ERROR_FAILURE;
    }

585
586
    return svgFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
                                             mInputImages, aInputIsTainted);
587
588
  }

589
  // Build primitives for a CSS filter.
590
591
592
593

  // If we don't have a frame, use opaque black for shadows with unspecified
  // shadow colors.
  nscolor shadowFallbackColor =
594
      mTargetFrame ? mTargetFrame->StyleText()->mColor.ToColor()
595
                   : NS_RGB(0, 0, 0);
596

597
598
599
600
601
  nsCSSFilterInstance cssFilterInstance(
      aFilter, shadowFallbackColor, mTargetBounds,
      mFrameSpaceInCSSPxToFilterSpaceTransform);
  return cssFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
                                           aInputIsTainted);
602
603
}

604
static void UpdateNeededBounds(const nsIntRegion& aRegion, nsIntRect& aBounds) {
605
606
607
608
  aBounds = aRegion.GetBounds();

  bool overflow;
  IntSize surfaceSize =
609
      nsSVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds.Size()), &overflow);
610
611
612
613
614
  if (overflow) {
    aBounds.SizeTo(surfaceSize);
  }
}

615
void nsFilterInstance::ComputeNeededBoxes() {
616
  if (mFilterDescription.mPrimitives.IsEmpty()) {
617
    return;
618
  }
619
620
621
622
623
624

  nsIntRegion sourceGraphicNeededRegion;
  nsIntRegion fillPaintNeededRegion;
  nsIntRegion strokePaintNeededRegion;

  FilterSupport::ComputeSourceNeededRegions(
625
626
      mFilterDescription, mPostFilterDirtyRegion, sourceGraphicNeededRegion,
      fillPaintNeededRegion, strokePaintNeededRegion);
627

628
  sourceGraphicNeededRegion.And(sourceGraphicNeededRegion, mTargetBounds);
629

630
631
632
  UpdateNeededBounds(sourceGraphicNeededRegion, mSourceGraphic.mNeededBounds);
  UpdateNeededBounds(fillPaintNeededRegion, mFillPaint.mNeededBounds);
  UpdateNeededBounds(strokePaintNeededRegion, mStrokePaint.mNeededBounds);
633
634
}

635
636
void nsFilterInstance::BuildSourcePaint(SourceInfo* aSource,
                                        imgDrawingParams& aImgParams) {
637
  MOZ_ASSERT(mTargetFrame);
638
  nsIntRect neededRect = aSource->mNeededBounds;
639
  if (neededRect.IsEmpty()) {
640
    return;
641
  }
642

643
  RefPtr<DrawTarget> offscreenDT =
644
645
      gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
          neededRect.Size(), SurfaceFormat::B8G8R8A8);
646
  if (!offscreenDT || !offscreenDT->IsValid()) {
647
    return;
648
649
  }

650
  RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
651
  MOZ_ASSERT(ctx);  // already checked the draw target above
652
653
  gfxContextAutoSaveRestore saver(ctx);

654
655
  ctx->SetMatrixDouble(mPaintTransform *
                       gfxMatrix::Translation(-neededRect.TopLeft()));
656
657
  GeneralPattern pattern;
  if (aSource == &mFillPaint) {
658
    nsSVGUtils::MakeFillPatternFor(mTargetFrame, ctx, &pattern, aImgParams);
659
  } else if (aSource == &mStrokePaint) {
660
    nsSVGUtils::MakeStrokePatternFor(mTargetFrame, ctx, &pattern, aImgParams);
661
662
663
  }

  if (pattern.GetPattern()) {
664
665
    offscreenDT->FillRect(
        ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))), pattern);
666
667
  }

668
  aSource->mSourceSurface = offscreenDT->Snapshot();
669
  aSource->mSurfaceRect = neededRect;
670
671
}

672
void nsFilterInstance::BuildSourcePaints(imgDrawingParams& aImgParams) {
673
  if (!mFillPaint.mNeededBounds.IsEmpty()) {
674
    BuildSourcePaint(&mFillPaint, aImgParams);
675
676
677
  }

  if (!mStrokePaint.mNeededBounds.IsEmpty()) {
678
    BuildSourcePaint(&mStrokePaint, aImgParams);
679
680
681
  }
}

682
void nsFilterInstance::BuildSourceImage(DrawTarget* aDest,
683
684
685
686
                                        imgDrawingParams& aImgParams,
                                        FilterNode* aFilter,
                                        FilterNode* aSource,
                                        const Rect& aSourceRect) {
687
688
  MOZ_ASSERT(mTargetFrame);

689
690
  nsIntRect neededRect = mSourceGraphic.mNeededBounds;
  if (neededRect.IsEmpty()) {
691
    return;
692
693
  }

694
695
696
697
  RefPtr<DrawTarget> offscreenDT;
  SurfaceFormat format = SurfaceFormat::B8G8R8A8;
  if (aDest->CanCreateSimilarDrawTarget(neededRect.Size(), format)) {
    offscreenDT = aDest->CreateSimilarDrawTargetForFilter(
698
        neededRect.Size(), format, aFilter, aSource, aSourceRect, Point(0, 0));
699
  }
700
  if (!offscreenDT || !offscreenDT->IsValid()) {
701
    return;
702
703
  }

704
  gfxRect r = FilterSpaceToUserSpace(ThebesRect(neededRect));
705
706
  r.RoundOut();
  nsIntRect dirty;
707
  if (!gfxUtils::GfxRectToIntRect(r, &dirty)) {
708
    return;
709
  }
710
711
712
713
714
715
716
717
718
719
720
721

  // SVG graphics paint to device space, so we need to set an initial device
  // space to filter space transform on the gfxContext that SourceGraphic
  // and SourceAlpha will paint to.
  //
  // (In theory it would be better to minimize error by having filtered SVG
  // graphics temporarily paint to user space when painting the sources and
  // only set a user space to filter space transform on the gfxContext
  // (since that would eliminate the transform multiplications from user
  // space to device space and back again). However, that would make the
  // code more complex while being hard to get right without introducing
  // subtle bugs, and in practice it probably makes no real difference.)
722
  RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
723
  MOZ_ASSERT(ctx);  // already checked the draw target above
724
725
726
  gfxMatrix devPxToCssPxTM = nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
  DebugOnly<bool> invertible = devPxToCssPxTM.Invert();
  MOZ_ASSERT(invertible);
727
728
  ctx->SetMatrixDouble(devPxToCssPxTM * mPaintTransform *
                       gfxMatrix::Translation(-neededRect.TopLeft()));
729

730
731
  mPaintCallback->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty,
                        aImgParams);
732

733
  mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
734
  mSourceGraphic.mSurfaceRect = neededRect;
735
736
}

737
738
void nsFilterInstance::Render(gfxContext* aCtx, imgDrawingParams& aImgParams,
                              float aOpacity) {
739
740
  MOZ_ASSERT(mTargetFrame, "Need a frame for rendering");

741
  if (mFilterDescription.mPrimitives.IsEmpty()) {
742
    // An filter without any primitive. Treat it as success and paint nothing.
743
    return;
744
745
  }

746
  nsIntRect filterRect =
747
      mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
748
  if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
749
    return;
750
751
  }

752
  gfxContextMatrixAutoSaveRestore autoSR(aCtx);
753
754
  aCtx->SetMatrix(
      aCtx->CurrentMatrix().PreTranslate(filterRect.x, filterRect.y));
755
756

  ComputeNeededBoxes();
757

758
759
760
  Rect renderRect = IntRectToRect(filterRect);
  RefPtr<DrawTarget> dt = aCtx->GetDrawTarget();

761
762
763
764
765
  MOZ_ASSERT(dt);
  if (!dt->IsValid()) {
    return;
  }

766
  BuildSourcePaints(aImgParams);
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
  RefPtr<FilterNode> sourceGraphic, fillPaint, strokePaint;
  if (mFillPaint.mSourceSurface) {
    fillPaint = FilterWrappers::ForSurface(dt, mFillPaint.mSourceSurface,
                                           mFillPaint.mSurfaceRect.TopLeft());
  }
  if (mStrokePaint.mSourceSurface) {
    strokePaint = FilterWrappers::ForSurface(
        dt, mStrokePaint.mSourceSurface, mStrokePaint.mSurfaceRect.TopLeft());
  }

  // We make the sourceGraphic filter but don't set its inputs until after so
  // that we can make the sourceGraphic size depend on the filter chain
  sourceGraphic = dt->CreateFilter(FilterType::TRANSFORM);
  if (sourceGraphic) {
    // Make sure we set the translation before calling BuildSourceImage
    // so that CreateSimilarDrawTargetForFilter works properly
    IntPoint offset = mSourceGraphic.mNeededBounds.TopLeft();
    sourceGraphic->SetAttribute(ATT_TRANSFORM_MATRIX,
                                Matrix::Translation(offset.x, offset.y));
  }

  RefPtr<FilterNode> resultFilter = FilterNodeGraphFromDescription(
789
      dt, mFilterDescription, renderRect, sourceGraphic,
790
791
792
793
794
795
796
      mSourceGraphic.mSurfaceRect, fillPaint, strokePaint, mInputImages);

  if (!resultFilter) {
    gfxWarning() << "Filter is NULL.";
    return;
  }

797
  BuildSourceImage(dt, aImgParams, resultFilter, sourceGraphic, renderRect);
798
799
800
801
802
803
804
805
  if (sourceGraphic) {
    if (mSourceGraphic.mSourceSurface) {
      sourceGraphic->SetInput(IN_TRANSFORM_IN, mSourceGraphic.mSourceSurface);
    } else {
      RefPtr<FilterNode> clear = FilterWrappers::Clear(aCtx->GetDrawTarget());
      sourceGraphic->SetInput(IN_TRANSFORM_IN, clear);
    }
  }
806

807
  dt->DrawFilter(resultFilter, renderRect, Point(0, 0), DrawOptions(aOpacity));
808
809
}

810
811
812
nsRegion nsFilterInstance::ComputePostFilterDirtyRegion() {
  if (mPreFilterDirtyRegion.IsEmpty() ||
      mFilterDescription.mPrimitives.IsEmpty()) {
813
    return nsRegion();
814
815
  }

816
817
  nsIntRegion resultChangeRegion = FilterSupport::ComputeResultChangeRegion(
      mFilterDescription, mPreFilterDirtyRegion, nsIntRegion(), nsIntRegion());
818
  return FilterSpaceToFrameSpace(resultChangeRegion);
819
820
}

821
nsRect nsFilterInstance::ComputePostFilterExtents() {
822
  if (mFilterDescription.mPrimitives.IsEmpty()) {
823
824
825
    return nsRect();
  }

826
827
  nsIntRegion postFilterExtents = FilterSupport::ComputePostFilterExtents(
      mFilterDescription, mTargetBounds);
828
  return FilterSpaceToFrameSpace(postFilterExtents.GetBounds());
829
830
}

831
nsRect nsFilterInstance::ComputeSourceNeededRect() {
832
  ComputeNeededBoxes();
833
  return FilterSpaceToFrameSpace(mSourceGraphic.mNeededBounds);
834
835
}

836
nsIntRect nsFilterInstance::OutputFilterSpaceBounds() const {
837
  uint32_t numPrimitives = mFilterDescription.mPrimitives.Length();
838
839
840
  if (numPrimitives <= 0) {
    return nsIntRect();
  }
841

842
  return mFilterDescription.mPrimitives[numPrimitives - 1].PrimitiveSubregion();
843
844
}

845
nsIntRect nsFilterInstance::FrameSpaceToFilterSpace(const nsRect* aRect) const {
846
  nsIntRect rect = OutputFilterSpaceBounds();
847
848
849
850
851
  if (aRect) {
    if (aRect->IsEmpty()) {
      return nsIntRect();
    }
    gfxRect rectInCSSPx =
852
        nsLayoutUtils::RectToGfxRect(*aRect, AppUnitsPerCSSPixel());
853
    gfxRect rectInFilterSpace =
854
        mFrameSpaceInCSSPxToFilterSpaceTransform.TransformBounds(rectInCSSPx);
855
856
857
858
859
860
861
862
863
    rectInFilterSpace.RoundOut();
    nsIntRect intRect;
    if (gfxUtils::GfxRectToIntRect(rectInFilterSpace, &intRect)) {
      rect = intRect;
    }
  }
  return rect;
}

864
nsRect nsFilterInstance::FilterSpaceToFrameSpace(const nsIntRect& aRect) const {
865
866
867
868
  if (aRect.IsEmpty()) {
    return nsRect();
  }
  gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
869
  r = mFilterSpaceToFrameSpaceInCSSPxTransform.TransformBounds(r);
870
  // nsLayoutUtils::RoundGfxRectToAppRect rounds out.
871
  return nsLayoutUtils::RoundGfxRectToAppRect(r, AppUnitsPerCSSPixel());
872
873
}

874
875
nsIntRegion nsFilterInstance::FrameSpaceToFilterSpace(
    const nsRegion* aRegion) const {
876
  if (!aRegion) {
877
    return OutputFilterSpaceBounds();
878
879
  }
  nsIntRegion result;
880
  for (auto iter = aRegion->RectIter(); !iter.Done(); iter.Next()) {
881
    // FrameSpaceToFilterSpace rounds out, so this works.
882
883
    nsRect rect = iter.Get();
    result.Or(result, FrameSpaceToFilterSpace(&rect));
884
885
886
887
  }
  return result;
}

888
889
nsRegion nsFilterInstance::FilterSpaceToFrameSpace(
    const nsIntRegion& aRegion) const {
890
  nsRegion result;
891
  for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
892
    // FilterSpaceToFrameSpace rounds out, so this works.
893
    result.Or(result, FilterSpaceToFrameSpace(iter.Get()));
894
895
896
897
  }
  return result;
}

898
gfxMatrix nsFilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const {
899
900
901
  if (!mTargetFrame) {
    return gfxMatrix();
  }
902
903
  return gfxMatrix::Translation(
      -nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame));
904
}