Bug 1880704 - Crop more PDF rendering and wait differently for rendering r=mboldan
[gecko.git] / gfx / src / nsRect.h
blobccf48f0d9e942554ce59b8d8ea2b959b65f74218
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef NSRECT_H
8 #define NSRECT_H
10 #include <stdint.h> // for int32_t, int64_t
11 #include <algorithm> // for min/max
12 #include "mozilla/Likely.h" // for MOZ_UNLIKELY
13 #include "mozilla/gfx/BaseRect.h"
14 #include "mozilla/gfx/Rect.h"
15 #include "nsCoord.h" // for nscoord, etc
16 #include "nsISupports.h" // for MOZ_COUNT_CTOR, etc
17 #include "nsPoint.h" // for nsIntPoint, nsPoint
18 #include "nsSize.h" // for IntSize, nsSize
19 #if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || \
20 (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
21 # if defined(_MSC_VER) && !defined(__clang__)
22 # include "smmintrin.h"
23 # else
24 # include "emmintrin.h"
25 # endif
26 #endif
28 struct nsMargin;
30 typedef mozilla::gfx::IntRect nsIntRect;
32 struct nsRect : public mozilla::gfx::BaseRect<nscoord, nsRect, nsPoint, nsSize,
33 nsMargin> {
34 typedef mozilla::gfx::BaseRect<nscoord, nsRect, nsPoint, nsSize, nsMargin>
35 Super;
37 // Constructors
38 nsRect() { MOZ_COUNT_CTOR(nsRect); }
39 nsRect(const nsRect& aRect) : Super(aRect) { MOZ_COUNT_CTOR(nsRect); }
40 nsRect(const nsPoint& aOrigin, const nsSize& aSize) : Super(aOrigin, aSize) {
41 MOZ_COUNT_CTOR(nsRect);
43 nsRect(nscoord aX, nscoord aY, nscoord aWidth, nscoord aHeight)
44 : Super(aX, aY, aWidth, aHeight) {
45 MOZ_COUNT_CTOR(nsRect);
47 nsRect& operator=(const nsRect&) = default;
49 MOZ_COUNTED_DTOR(nsRect)
51 // We have saturating versions of all the Union methods. These avoid
52 // overflowing nscoord values in the 'width' and 'height' fields by
53 // clamping the width and height values to nscoord_MAX if necessary.
55 // Returns the smallest rectangle that contains both the area of both
56 // this and aRect. Thus, empty input rectangles are ignored.
57 // Note: if both rectangles are empty, returns aRect.
58 [[nodiscard]] nsRect SaturatingUnion(const nsRect& aRect) const {
59 if (IsEmpty()) {
60 return aRect;
61 } else if (aRect.IsEmpty()) {
62 return *static_cast<const nsRect*>(this);
63 } else {
64 return SaturatingUnionEdges(aRect);
68 [[nodiscard]] nsRect SaturatingUnionEdges(const nsRect& aRect) const {
69 nscoord resultX = std::min(aRect.X(), x);
70 int64_t w =
71 std::max(int64_t(aRect.X()) + aRect.Width(), int64_t(x) + width) -
72 resultX;
73 if (MOZ_UNLIKELY(w > nscoord_MAX)) {
74 // Clamp huge negative x to nscoord_MIN / 2 and try again.
75 resultX = std::max(resultX, nscoord_MIN / 2);
76 w = std::max(int64_t(aRect.X()) + aRect.Width(), int64_t(x) + width) -
77 resultX;
78 if (MOZ_UNLIKELY(w > nscoord_MAX)) {
79 w = nscoord_MAX;
83 nscoord resultY = std::min(aRect.y, y);
84 int64_t h =
85 std::max(int64_t(aRect.Y()) + aRect.Height(), int64_t(y) + height) -
86 resultY;
87 if (MOZ_UNLIKELY(h > nscoord_MAX)) {
88 // Clamp huge negative y to nscoord_MIN / 2 and try again.
89 resultY = std::max(resultY, nscoord_MIN / 2);
90 h = std::max(int64_t(aRect.Y()) + aRect.Height(), int64_t(y) + height) -
91 resultY;
92 if (MOZ_UNLIKELY(h > nscoord_MAX)) {
93 h = nscoord_MAX;
96 return nsRect(resultX, resultY, nscoord(w), nscoord(h));
99 // Make all nsRect Union methods be saturating.
100 [[nodiscard]] nsRect UnionEdges(const nsRect& aRect) const {
101 return SaturatingUnionEdges(aRect);
103 [[nodiscard]] nsRect Union(const nsRect& aRect) const {
104 return SaturatingUnion(aRect);
106 [[nodiscard]] nsRect UnsafeUnion(const nsRect& aRect) const {
107 return Super::Union(aRect);
109 void UnionRect(const nsRect& aRect1, const nsRect& aRect2) {
110 *this = aRect1.Union(aRect2);
113 #if defined(_MSC_VER) && !defined(__clang__) && \
114 (defined(_M_X64) || defined(_M_IX86))
115 // Only MSVC supports inlining intrinsics for archs you're not compiling for.
116 [[nodiscard]] nsRect Intersect(const nsRect& aRect) const {
117 nsRect result;
118 if (mozilla::gfx::Factory::HasSSE4()) {
119 __m128i rect1 = _mm_loadu_si128((__m128i*)&aRect); // x1, y1, w1, h1
120 __m128i rect2 = _mm_loadu_si128((__m128i*)this); // x2, y2, w2, h2
122 __m128i resultRect = _mm_max_epi32(rect1, rect2); // xr, yr, zz, zz
124 // result.width = std::min<int32_t>(x - result.x + width,
125 // aRect.x - result.x + aRect.width);
126 // result.height = std::min<int32_t>(y - result.y + height,
127 // aRect.y - result.y + aRect.height);
128 __m128i widthheight = _mm_min_epi32(
129 _mm_add_epi32(_mm_sub_epi32(rect1, resultRect),
130 _mm_srli_si128(rect1, 8)),
131 _mm_add_epi32(_mm_sub_epi32(rect2, resultRect),
132 _mm_srli_si128(rect2, 8))); // w, h, zz, zz
133 widthheight = _mm_slli_si128(widthheight, 8); // 00, 00, wr, hr
135 resultRect =
136 _mm_blend_epi16(resultRect, widthheight, 0xF0); // xr, yr, wr, hr
138 if ((_mm_movemask_ps(_mm_castsi128_ps(
139 _mm_cmplt_epi32(resultRect, _mm_setzero_si128()))) &
140 0xC) != 0) {
141 // It's potentially more efficient to store all 0s. But the non SSE4
142 // code leaves x/y intact so let's do the same here.
143 resultRect = _mm_and_si128(resultRect,
144 _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF));
147 _mm_storeu_si128((__m128i*)&result, resultRect);
149 return result;
152 result.x = std::max<int32_t>(x, aRect.x);
153 result.y = std::max<int32_t>(y, aRect.y);
154 result.width = std::min<int32_t>(x - result.x + width,
155 aRect.x - result.x + aRect.width);
156 result.height = std::min<int32_t>(y - result.y + height,
157 aRect.y - result.y + aRect.height);
158 if (result.width < 0 || result.height < 0) {
159 result.SizeTo(0, 0);
161 return result;
164 bool IntersectRect(const nsRect& aRect1, const nsRect& aRect2) {
165 if (mozilla::gfx::Factory::HasSSE4()) {
166 __m128i rect1 = _mm_loadu_si128((__m128i*)&aRect1); // x1, y1, w1, h1
167 __m128i rect2 = _mm_loadu_si128((__m128i*)&aRect2); // x2, y2, w2, h2
169 __m128i resultRect = _mm_max_epi32(rect1, rect2); // xr, yr, zz, zz
170 // result.width = std::min<int32_t>(x - result.x + width,
171 // aRect.x - result.x + aRect.width);
172 // result.height = std::min<int32_t>(y - result.y + height,
173 // aRect.y - result.y + aRect.height);
174 __m128i widthheight = _mm_min_epi32(
175 _mm_add_epi32(_mm_sub_epi32(rect1, resultRect),
176 _mm_srli_si128(rect1, 8)),
177 _mm_add_epi32(_mm_sub_epi32(rect2, resultRect),
178 _mm_srli_si128(rect2, 8))); // w, h, zz, zz
179 widthheight = _mm_slli_si128(widthheight, 8); // 00, 00, wr, hr
181 resultRect =
182 _mm_blend_epi16(resultRect, widthheight, 0xF0); // xr, yr, wr, hr
184 if ((_mm_movemask_ps(_mm_castsi128_ps(
185 _mm_cmpgt_epi32(resultRect, _mm_setzero_si128()))) &
186 0xC) != 0xC) {
187 // It's potentially more efficient to store all 0s. But the non SSE4
188 // code leaves x/y intact so let's do the same here.
189 resultRect = _mm_and_si128(resultRect,
190 _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF));
191 _mm_storeu_si128((__m128i*)this, resultRect);
192 return false;
195 _mm_storeu_si128((__m128i*)this, resultRect);
197 return true;
200 int32_t newX = std::max<int32_t>(aRect1.x, aRect2.x);
201 int32_t newY = std::max<int32_t>(aRect1.y, aRect2.y);
202 width = std::min<int32_t>(aRect1.x - newX + aRect1.width,
203 aRect2.x - newX + aRect2.width);
204 height = std::min<int32_t>(aRect1.y - newY + aRect1.height,
205 aRect2.y - newY + aRect2.height);
206 x = newX;
207 y = newY;
208 if (width <= 0 || height <= 0) {
209 SizeTo(0, 0);
210 return false;
212 return true;
214 #endif
216 // Return whether this rect's right or bottom edge overflow int32.
217 bool Overflows() const;
220 * Return this rect scaled to a different appunits per pixel (APP) ratio.
221 * In the RoundOut version we make the rect the smallest rect containing the
222 * unrounded result. In the RoundIn version we make the rect the largest rect
223 * contained in the unrounded result.
224 * @param aFromAPP the APP to scale from
225 * @param aToAPP the APP to scale to
226 * @note this can turn an empty rectangle into a non-empty rectangle
228 [[nodiscard]] inline nsRect ScaleToOtherAppUnitsRoundOut(
229 int32_t aFromAPP, int32_t aToAPP) const;
230 [[nodiscard]] inline nsRect ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP,
231 int32_t aToAPP) const;
233 [[nodiscard]] inline mozilla::gfx::IntRect ScaleToNearestPixels(
234 float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const;
236 [[nodiscard]] inline mozilla::gfx::IntRect ToNearestPixels(
237 nscoord aAppUnitsPerPixel) const;
239 // Note: this can turn an empty rectangle into a non-empty rectangle
240 [[nodiscard]] inline mozilla::gfx::IntRect ScaleToOutsidePixels(
241 float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const;
243 // Note: this can turn an empty rectangle into a non-empty rectangle
244 [[nodiscard]] inline mozilla::gfx::IntRect ToOutsidePixels(
245 nscoord aAppUnitsPerPixel) const;
247 [[nodiscard]] inline mozilla::gfx::IntRect ScaleToInsidePixels(
248 float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const;
250 [[nodiscard]] inline mozilla::gfx::IntRect ToInsidePixels(
251 nscoord aAppUnitsPerPixel) const;
253 // This is here only to keep IPDL-generated code happy. DO NOT USE.
254 bool operator==(const nsRect& aRect) const { return IsEqualEdges(aRect); }
256 [[nodiscard]] inline nsRect RemoveResolution(const float aResolution) const;
258 [[nodiscard]] mozilla::Maybe<nsRect> EdgeInclusiveIntersection(
259 const nsRect& aOther) const {
260 nscoord left = std::max(x, aOther.x);
261 nscoord top = std::max(y, aOther.y);
262 nscoord right = std::min(XMost(), aOther.XMost());
263 nscoord bottom = std::min(YMost(), aOther.YMost());
264 if (left > right || top > bottom) {
265 return mozilla::Nothing();
267 return mozilla::Some(nsRect(left, top, right - left, bottom - top));
272 * App Unit/Pixel conversions
275 inline nsRect nsRect::ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP,
276 int32_t aToAPP) const {
277 if (aFromAPP == aToAPP) {
278 return *this;
281 nsRect rect;
282 rect.SetBox(NSToCoordFloor(NSCoordScale(x, aFromAPP, aToAPP)),
283 NSToCoordFloor(NSCoordScale(y, aFromAPP, aToAPP)),
284 NSToCoordCeil(NSCoordScale(XMost(), aFromAPP, aToAPP)),
285 NSToCoordCeil(NSCoordScale(YMost(), aFromAPP, aToAPP)));
286 return rect;
289 inline nsRect nsRect::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP,
290 int32_t aToAPP) const {
291 if (aFromAPP == aToAPP) {
292 return *this;
295 nsRect rect;
296 rect.SetBox(NSToCoordCeil(NSCoordScale(x, aFromAPP, aToAPP)),
297 NSToCoordCeil(NSCoordScale(y, aFromAPP, aToAPP)),
298 NSToCoordFloor(NSCoordScale(XMost(), aFromAPP, aToAPP)),
299 NSToCoordFloor(NSCoordScale(YMost(), aFromAPP, aToAPP)));
300 return rect;
303 #if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || \
304 (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
305 // Life would be so much better if we had SSE4 here.
306 static MOZ_ALWAYS_INLINE __m128i floor_ps2epi32(__m128 x) {
307 __m128 one = _mm_set_ps(1.0f, 1.0f, 1.0f, 1.0f);
309 __m128 t = _mm_cvtepi32_ps(_mm_cvttps_epi32(x));
310 __m128 r = _mm_sub_ps(t, _mm_and_ps(_mm_cmplt_ps(x, t), one));
312 return _mm_cvttps_epi32(r);
315 static MOZ_ALWAYS_INLINE __m128i ceil_ps2epi32(__m128 x) {
316 __m128 t = _mm_sub_ps(_mm_setzero_ps(), x);
317 __m128i r = _mm_sub_epi32(_mm_setzero_si128(), floor_ps2epi32(t));
319 return r;
321 #endif
323 // scale the rect but round to preserve centers
324 inline mozilla::gfx::IntRect nsRect::ScaleToNearestPixels(
325 float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const {
326 mozilla::gfx::IntRect rect;
327 // Android x86 builds have bindgen issues.
328 #if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || \
329 (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
330 __m128 appUnitsPacked = _mm_set_ps(aAppUnitsPerPixel, aAppUnitsPerPixel,
331 aAppUnitsPerPixel, aAppUnitsPerPixel);
332 __m128 scalesPacked = _mm_set_ps(aYScale, aXScale, aYScale, aXScale);
333 __m128 biasesPacked = _mm_set_ps(0.5f, 0.5f, 0.5f, 0.5f);
335 __m128i rectPacked = _mm_loadu_si128((__m128i*)this);
336 __m128i topLeft = _mm_slli_si128(rectPacked, 8);
338 rectPacked = _mm_add_epi32(rectPacked, topLeft); // X, Y, XMost(), YMost()
340 __m128 rectFloat = _mm_cvtepi32_ps(rectPacked);
342 // Scale, i.e. ([ x y xmost ymost ] / aAppUnitsPerPixel) * [ aXScale aYScale
343 // aXScale aYScale ]
344 rectFloat = _mm_mul_ps(_mm_div_ps(rectFloat, appUnitsPacked), scalesPacked);
346 // Floor
347 // Executed with bias and roundmode down, since round-nearest rounds 0.5
348 // downward half the time.
349 rectFloat = _mm_add_ps(rectFloat, biasesPacked);
350 rectPacked = floor_ps2epi32(rectFloat);
352 topLeft = _mm_slli_si128(rectPacked, 8);
353 rectPacked = _mm_sub_epi32(rectPacked, topLeft); // X, Y, Width, Height
355 // Avoid negative width/height due to overflow.
356 __m128i mask = _mm_or_si128(_mm_cmpgt_epi32(rectPacked, _mm_setzero_si128()),
357 _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF));
358 // Mask will now contain [ 0xFFFFFFFF 0xFFFFFFFF (width <= 0 ? 0 : 0xFFFFFFFF)
359 // (height <= 0 ? 0 : 0xFFFFFFFF) ]
360 rectPacked = _mm_and_si128(rectPacked, mask);
362 _mm_storeu_si128((__m128i*)&rect, rectPacked);
363 #else
364 rect.SetNonEmptyBox(
365 NSToIntRoundUp(NSAppUnitsToFloatPixels(x, aAppUnitsPerPixel) * aXScale),
366 NSToIntRoundUp(NSAppUnitsToFloatPixels(y, aAppUnitsPerPixel) * aYScale),
367 NSToIntRoundUp(NSAppUnitsToFloatPixels(XMost(), aAppUnitsPerPixel) *
368 aXScale),
369 NSToIntRoundUp(NSAppUnitsToFloatPixels(YMost(), aAppUnitsPerPixel) *
370 aYScale));
371 #endif
372 return rect;
375 // scale the rect but round to smallest containing rect
376 inline mozilla::gfx::IntRect nsRect::ScaleToOutsidePixels(
377 float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const {
378 mozilla::gfx::IntRect rect;
379 // Android x86 builds have bindgen issues.
380 #if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || \
381 (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
382 __m128 appUnitsPacked = _mm_set_ps(aAppUnitsPerPixel, aAppUnitsPerPixel,
383 aAppUnitsPerPixel, aAppUnitsPerPixel);
384 __m128 scalesPacked = _mm_set_ps(aYScale, aXScale, aYScale, aXScale);
386 __m128i rectPacked = _mm_loadu_si128((__m128i*)this); // x, y, w, h
387 __m128i topLeft = _mm_slli_si128(rectPacked, 8); // 0, 0, x, y
389 rectPacked = _mm_add_epi32(rectPacked, topLeft); // X, Y, XMost(), YMost()
391 __m128 rectFloat = _mm_cvtepi32_ps(rectPacked);
393 // Scale i.e. ([ x y xmost ymost ] / aAppUnitsPerPixel) *
394 // [ aXScale aYScale aXScale aYScale ]
395 rectFloat = _mm_mul_ps(_mm_div_ps(rectFloat, appUnitsPacked), scalesPacked);
396 rectPacked = ceil_ps2epi32(rectFloat); // xx, xx, XMost(), YMost()
397 __m128i tmp = floor_ps2epi32(rectFloat); // x, y, xx, xx
399 // _mm_move_sd is 1 cycle method of getting the blending we want.
400 rectPacked = _mm_castpd_si128(
401 _mm_move_sd(_mm_castsi128_pd(rectPacked),
402 _mm_castsi128_pd(tmp))); // x, y, XMost(), YMost()
404 topLeft = _mm_slli_si128(rectPacked, 8); // 0, 0, r.x, r.y
405 rectPacked = _mm_sub_epi32(rectPacked, topLeft); // r.x, r.y, r.w, r.h
407 // Avoid negative width/height due to overflow.
408 __m128i mask = _mm_or_si128(_mm_cmpgt_epi32(rectPacked, _mm_setzero_si128()),
409 _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF));
410 // clang-format off
411 // Mask will now contain [ 0xFFFFFFFF 0xFFFFFFFF (width <= 0 ? 0 : 0xFFFFFFFF) (height <= 0 ? 0 : 0xFFFFFFFF) ]
412 // clang-format on
413 rectPacked = _mm_and_si128(rectPacked, mask);
415 _mm_storeu_si128((__m128i*)&rect, rectPacked);
416 #else
417 rect.SetNonEmptyBox(
418 NSToIntFloor(NSAppUnitsToFloatPixels(x, float(aAppUnitsPerPixel)) *
419 aXScale),
420 NSToIntFloor(NSAppUnitsToFloatPixels(y, float(aAppUnitsPerPixel)) *
421 aYScale),
422 NSToIntCeil(NSAppUnitsToFloatPixels(XMost(), float(aAppUnitsPerPixel)) *
423 aXScale),
424 NSToIntCeil(NSAppUnitsToFloatPixels(YMost(), float(aAppUnitsPerPixel)) *
425 aYScale));
426 #endif
427 return rect;
430 // scale the rect but round to largest contained rect
431 inline mozilla::gfx::IntRect nsRect::ScaleToInsidePixels(
432 float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const {
433 mozilla::gfx::IntRect rect;
434 rect.SetNonEmptyBox(
435 NSToIntCeil(NSAppUnitsToFloatPixels(x, float(aAppUnitsPerPixel)) *
436 aXScale),
437 NSToIntCeil(NSAppUnitsToFloatPixels(y, float(aAppUnitsPerPixel)) *
438 aYScale),
439 NSToIntFloor(NSAppUnitsToFloatPixels(XMost(), float(aAppUnitsPerPixel)) *
440 aXScale),
441 NSToIntFloor(NSAppUnitsToFloatPixels(YMost(), float(aAppUnitsPerPixel)) *
442 aYScale));
443 return rect;
446 inline mozilla::gfx::IntRect nsRect::ToNearestPixels(
447 nscoord aAppUnitsPerPixel) const {
448 return ScaleToNearestPixels(1.0f, 1.0f, aAppUnitsPerPixel);
451 inline mozilla::gfx::IntRect nsRect::ToOutsidePixels(
452 nscoord aAppUnitsPerPixel) const {
453 return ScaleToOutsidePixels(1.0f, 1.0f, aAppUnitsPerPixel);
456 inline mozilla::gfx::IntRect nsRect::ToInsidePixels(
457 nscoord aAppUnitsPerPixel) const {
458 return ScaleToInsidePixels(1.0f, 1.0f, aAppUnitsPerPixel);
461 inline nsRect nsRect::RemoveResolution(const float aResolution) const {
462 MOZ_ASSERT(aResolution > 0.0f);
463 nsRect rect;
464 rect.MoveTo(NSToCoordRound(NSCoordToFloat(x) / aResolution),
465 NSToCoordRound(NSCoordToFloat(y) / aResolution));
466 // A 1x1 rect indicates we are just hit testing a point, so pass down a 1x1
467 // rect as well instead of possibly rounding the width or height to zero.
468 if (width == 1 && height == 1) {
469 rect.SizeTo(1, 1);
470 } else {
471 rect.SizeTo(NSToCoordCeil(NSCoordToFloat(width) / aResolution),
472 NSToCoordCeil(NSCoordToFloat(height) / aResolution));
475 return rect;
478 const mozilla::gfx::IntRect& GetMaxSizedIntRect();
480 // app units are integer multiples of pixels, so no rounding needed
481 template <class units>
482 nsRect ToAppUnits(const mozilla::gfx::IntRectTyped<units>& aRect,
483 nscoord aAppUnitsPerPixel) {
484 return nsRect(NSIntPixelsToAppUnits(aRect.X(), aAppUnitsPerPixel),
485 NSIntPixelsToAppUnits(aRect.Y(), aAppUnitsPerPixel),
486 NSIntPixelsToAppUnits(aRect.Width(), aAppUnitsPerPixel),
487 NSIntPixelsToAppUnits(aRect.Height(), aAppUnitsPerPixel));
490 #endif /* NSRECT_H */