PLS external framebuffer optimizations

1) When rendering to an external framebuffer, rather than blitting the
   entire framebuffer to and from the offscreen target, only blit the
   bounds being updated.

2) Coalesce the atomic "resolve" operation and the blit to the target
   framebuffer into a single operation. i.e., render directly to the
   target framebuffer and fetch from the offscreen framebuffer.

Diffs=
5cfc226f2 PLS external framebuffer optimizations (#6516)

Co-authored-by: Chris Dalton <99840794+csmartdalton@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head
index f218173..bdc326a 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-2018358a6b7dc1dc1d6ca5ded14f11d4b3e072ca
+5cfc226f2444c9bef5f8ce06ef4d8cacde53f41c
diff --git a/include/rive/math/aabb.hpp b/include/rive/math/aabb.hpp
index d6efcd6..4c79aed 100644
--- a/include/rive/math/aabb.hpp
+++ b/include/rive/math/aabb.hpp
@@ -3,7 +3,6 @@
 
 #include "rive/span.hpp"
 #include "rive/math/vec2d.hpp"
-#include <cstddef>
 #include <limits>
 
 namespace rive
@@ -14,10 +13,30 @@
 
     constexpr int width() const { return right - left; }
     constexpr int height() const { return bottom - top; }
-    constexpr bool empty() const { return width() <= 0 || height() <= 0; }
+    constexpr bool empty() const { return left >= right || top >= bottom; }
 
     IAABB inset(int dx, int dy) const { return {left + dx, top + dy, right - dx, bottom - dy}; }
     IAABB offset(int dx, int dy) const { return {left + dx, top + dy, right + dx, bottom + dy}; }
+    IAABB join(IAABB b) const
+    {
+        return {std::min(left, b.left),
+                std::min(top, b.top),
+                std::max(right, b.right),
+                std::max(bottom, b.bottom)};
+    }
+    IAABB intersect(IAABB b) const
+    {
+        return {std::max(left, b.left),
+                std::max(top, b.top),
+                std::min(right, b.right),
+                std::min(bottom, b.bottom)};
+    }
+
+    bool operator==(const IAABB& o) const
+    {
+        return left == o.left && top == o.top && right == o.right && bottom == o.bottom;
+    }
+    bool operator!=(const IAABB& o) const { return !(*this == o); }
 };
 
 class AABB
diff --git a/test/aabb_test.cpp b/test/aabb_test.cpp
new file mode 100644
index 0000000..4adafea
--- /dev/null
+++ b/test/aabb_test.cpp
@@ -0,0 +1,32 @@
+#include <catch.hpp>
+#include "rive/math/mat2d.hpp"
+#include "rive/math/math_types.hpp"
+
+namespace rive
+{
+TEST_CASE("IAABB_join", "[IAABB]")
+{
+    CHECK(IAABB{1, -2, 99, 101}.join({0, 0, 100, 100}) == IAABB{0, -2, 100, 101});
+    CHECK(IAABB{1, -2, 99, 101}.join({2, -3, 98, 103}) == IAABB{1, -3, 99, 103});
+}
+
+TEST_CASE("IAABB_intersect", "[IAABB]")
+{
+    CHECK(IAABB{1, -2, 99, 101}.intersect({0, 0, 100, 100}) == IAABB{1, 0, 99, 100});
+    CHECK(IAABB{1, -2, 99, 101}.intersect({2, -3, 98, 103}) == IAABB{2, -2, 98, 101});
+}
+
+TEST_CASE("IAABB_empty", "[IAABB]")
+{
+    CHECK(IAABB{0, 0, 0, 0}.empty());
+    CHECK(IAABB{0, 0, 0, 1}.empty());
+    CHECK(IAABB{0, 0, 1, 0}.empty());
+    CHECK(!IAABB{0, 0, 1, 1}.empty());
+    CHECK(IAABB{0, 0, -1, -1}.empty());
+    CHECK(IAABB{std::numeric_limits<int32_t>::max(),
+                std::numeric_limits<int32_t>::max(),
+                std::numeric_limits<int32_t>::min(),
+                std::numeric_limits<int32_t>::min()}
+              .empty());
+}
+} // namespace rive