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/. */
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"
24 # include "emmintrin.h"
30 typedef mozilla::gfx::IntRect nsIntRect
;
32 struct nsRect
: public mozilla::gfx::BaseRect
<nscoord
, nsRect
, nsPoint
, nsSize
,
34 typedef mozilla::gfx::BaseRect
<nscoord
, nsRect
, nsPoint
, nsSize
, nsMargin
>
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 {
61 } else if (aRect
.IsEmpty()) {
62 return *static_cast<const nsRect
*>(this);
64 return SaturatingUnionEdges(aRect
);
68 [[nodiscard
]] nsRect
SaturatingUnionEdges(const nsRect
& aRect
) const {
69 nscoord resultX
= std::min(aRect
.X(), x
);
71 std::max(int64_t(aRect
.X()) + aRect
.Width(), int64_t(x
) + width
) -
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
) -
78 if (MOZ_UNLIKELY(w
> nscoord_MAX
)) {
83 nscoord resultY
= std::min(aRect
.y
, y
);
85 std::max(int64_t(aRect
.Y()) + aRect
.Height(), int64_t(y
) + height
) -
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
) -
92 if (MOZ_UNLIKELY(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 {
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
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()))) &
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
);
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) {
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
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()))) &
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
);
195 _mm_storeu_si128((__m128i
*)this, resultRect
);
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
);
208 if (width
<= 0 || height
<= 0) {
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
) {
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
)));
289 inline nsRect
nsRect::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP
,
290 int32_t aToAPP
) const {
291 if (aFromAPP
== aToAPP
) {
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
)));
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
));
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
344 rectFloat
= _mm_mul_ps(_mm_div_ps(rectFloat
, appUnitsPacked
), scalesPacked
);
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
);
365 NSToIntRoundUp(NSAppUnitsToFloatPixels(x
, aAppUnitsPerPixel
) * aXScale
),
366 NSToIntRoundUp(NSAppUnitsToFloatPixels(y
, aAppUnitsPerPixel
) * aYScale
),
367 NSToIntRoundUp(NSAppUnitsToFloatPixels(XMost(), aAppUnitsPerPixel
) *
369 NSToIntRoundUp(NSAppUnitsToFloatPixels(YMost(), aAppUnitsPerPixel
) *
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));
411 // Mask will now contain [ 0xFFFFFFFF 0xFFFFFFFF (width <= 0 ? 0 : 0xFFFFFFFF) (height <= 0 ? 0 : 0xFFFFFFFF) ]
413 rectPacked
= _mm_and_si128(rectPacked
, mask
);
415 _mm_storeu_si128((__m128i
*)&rect
, rectPacked
);
418 NSToIntFloor(NSAppUnitsToFloatPixels(x
, float(aAppUnitsPerPixel
)) *
420 NSToIntFloor(NSAppUnitsToFloatPixels(y
, float(aAppUnitsPerPixel
)) *
422 NSToIntCeil(NSAppUnitsToFloatPixels(XMost(), float(aAppUnitsPerPixel
)) *
424 NSToIntCeil(NSAppUnitsToFloatPixels(YMost(), float(aAppUnitsPerPixel
)) *
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
;
435 NSToIntCeil(NSAppUnitsToFloatPixels(x
, float(aAppUnitsPerPixel
)) *
437 NSToIntCeil(NSAppUnitsToFloatPixels(y
, float(aAppUnitsPerPixel
)) *
439 NSToIntFloor(NSAppUnitsToFloatPixels(XMost(), float(aAppUnitsPerPixel
)) *
441 NSToIntFloor(NSAppUnitsToFloatPixels(YMost(), float(aAppUnitsPerPixel
)) *
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
);
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) {
471 rect
.SizeTo(NSToCoordCeil(NSCoordToFloat(width
) / aResolution
),
472 NSToCoordCeil(NSCoordToFloat(height
) / aResolution
));
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 */