[graphite] Tweaks to atlas path renderer selection

Previously, `Device::chooseRenderer()` fell beck to a tessellating path
renderer when a shape got rejected from the compute path atlas. In
single-sample mode (when MSAA is not available), the compute atlas
renderer could get selected shapes that it cannot efficiently render.

This has now been tweaked, such that:

1. If the compute atlas is available but it rejects a shape, and MSAA is
   supported, choose a tessellating technique and rely on hardware
   MSAA.
2. If the compute atlas is available but it rejects a shape, and MSAA is
   NOT supported, choose the raster path atlas instead. This avoids
   single-sample rendering.
3. If the compute atlas is unavailable and MSAA is supported, choose a
   tessellating technique and rely on hardware MSAA.
4. If the compute atlas is unavailable and MSAA is NOT supported, always
   use raster path atlas.
5. The path strategy options work as before with one major difference:
   when compute is available and a compute AA technique is explicitly
   requested BUT the compute atlas rejects a shape, then one of the fall
   backs in 1-2 applies. Previously we would send all shapes to the
   compute atlas regardless of whether it can be rendered. This can no
   longer be forced using path strategy option.

Bug: b/339478836

Change-Id: I8127b288f891f42155a23a65615f1a2b4ecb7b79
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/851277
Commit-Queue: Arman Uguray <armansito@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/graphite/AtlasProvider.cpp b/src/gpu/graphite/AtlasProvider.cpp
index 7789314..b50a5b0 100644
--- a/src/gpu/graphite/AtlasProvider.cpp
+++ b/src/gpu/graphite/AtlasProvider.cpp
@@ -20,8 +20,8 @@
 namespace skgpu::graphite {
 
 AtlasProvider::PathAtlasFlagsBitMask AtlasProvider::QueryPathAtlasSupport(const Caps* caps) {
-    PathAtlasFlagsBitMask flags = PathAtlasFlags::kNone;
-    flags |= PathAtlasFlags::kRaster;
+    // The raster-backend path atlas is always supported.
+    PathAtlasFlagsBitMask flags = PathAtlasFlags::kRaster;
     if (RendererProvider::IsVelloRendererSupported(caps)) {
         flags |= PathAtlasFlags::kCompute;
     }
diff --git a/src/gpu/graphite/AtlasProvider.h b/src/gpu/graphite/AtlasProvider.h
index c88ceef..117b3f1 100644
--- a/src/gpu/graphite/AtlasProvider.h
+++ b/src/gpu/graphite/AtlasProvider.h
@@ -52,7 +52,8 @@
     // glyph rendering. This TextAtlasManager is always available.
     TextAtlasManager* textAtlasManager() const { return fTextAtlasManager.get(); }
 
-    // Returns whether a particular atlas type is available
+    // Returns whether a particular atlas type is available. Currently PathAtlasFlags::kRaster is
+    // always supported.
     bool isAvailable(PathAtlasFlags atlasType) const {
         return SkToBool(fPathAtlasFlags & atlasType);
     }
diff --git a/src/gpu/graphite/ComputePathAtlas.cpp b/src/gpu/graphite/ComputePathAtlas.cpp
index cb7e169..25ba02c 100644
--- a/src/gpu/graphite/ComputePathAtlas.cpp
+++ b/src/gpu/graphite/ComputePathAtlas.cpp
@@ -25,10 +25,17 @@
 namespace skgpu::graphite {
 namespace {
 
-// TODO: This is the maximum target dimension that vello can handle today
+// TODO: This is the maximum target dimension that vello can handle today.
 constexpr uint16_t kComputeAtlasDim = 4096;
 
-// Coordinate size that is too large for vello to handle efficiently.
+// TODO: Currently we reject shapes that are smaller than a subset of a given atlas page to avoid
+// creating too many flushes in a Recording containing many large path draws. These shapes often
+// don't make efficient use of the available atlas texture space and the cost of sequential
+// dispatches to render multiple atlas pages can be prohibitive.
+constexpr size_t kBboxAreaThreshold = 1024 * 512;
+
+// Coordinate size that is too large for vello to handle efficiently. See the discussion on
+// https://github.com/linebender/vello/pull/542.
 constexpr float kCoordinateThreshold = 1e10;
 
 }  // namespace
@@ -64,16 +71,7 @@
     // For now we're allowing paths that are smaller than 1/32nd of the full 4096x4096 atlas size
     // to prevent the atlas texture from filling up too often. There are several approaches we
     // should explore to alleviate the cost of atlasing large paths.
-    //
-    // 1. Rendering multiple atlas textures requires an extra compute pass for each texture. This
-    // impairs performance because there is a fixed cost to each dispatch and all dispatches get
-    // serialized by pipeline barrier synchronization. We should explore ways to render to multiple
-    // textures by issuing more workgroups in fewer dispatches as well as removing pipeline barriers
-    // across dispatches that target different atlas pages.
-    //
-    // 2. Implement a compressed "sparse" mask rendering scheme to render paths with a large
-    // bounding box using less texture space.
-    if (width * height > 1024 * 512) {
+    if (width * height > kBboxAreaThreshold) {
         return false;
     }
 
diff --git a/src/gpu/graphite/Device.cpp b/src/gpu/graphite/Device.cpp
index 1c3e5cd..00c35fe 100644
--- a/src/gpu/graphite/Device.cpp
+++ b/src/gpu/graphite/Device.cpp
@@ -182,6 +182,13 @@
                                   SkRRectPriv::AllCornersCircular(shape.rrect()))));
 }
 
+bool use_compute_atlas_when_available(PathRendererStrategy strategy) {
+    return strategy == PathRendererStrategy::kComputeAnalyticAA ||
+           strategy == PathRendererStrategy::kComputeMSAA16 ||
+           strategy == PathRendererStrategy::kComputeMSAA8 ||
+           strategy == PathRendererStrategy::kDefault;
+}
+
 } // anonymous namespace
 
 /**
@@ -1328,9 +1335,11 @@
     // Path rendering options. For now the strategy is very simple and not optimal:
     // I. Use tessellation if MSAA is required for an effect.
     // II: otherwise:
-    //    1. Always use compute AA if supported unless it was excluded by ContextOptions.
-    //    2. Use CPU raster AA if hardware MSAA is disabled or it was explicitly requested by
-    //       ContextOptions.
+    //    1. Always use compute AA if supported unless it was excluded by ContextOptions or the
+    //       compute renderer cannot render the shape efficiently yet (based on the result of
+    //       `isSuitableForAtlasing`).
+    //    2. Fall back to CPU raster AA if hardware MSAA is disabled or it was explicitly requested
+    //       via ContextOptions.
     //    3. Otherwise use tessellation.
 #if defined(GRAPHITE_TEST_UTILS)
     PathRendererStrategy strategy = fRecorder->priv().caps()->requestedPathRendererStrategy();
@@ -1339,46 +1348,46 @@
 #endif
 
     PathAtlas* pathAtlas = nullptr;
+    AtlasProvider* atlasProvider = fRecorder->priv().atlasProvider();
 
     // Prefer compute atlas draws if supported. This currently implicitly filters out clip draws as
     // they require MSAA. Eventually we may want to route clip shapes to the atlas as well but not
     // if hardware MSAA is required.
-    AtlasProvider* atlasProvider = fRecorder->priv().atlasProvider();
+    Rect drawBounds = localToDevice.mapRect(shape.bounds());
     if (atlasProvider->isAvailable(AtlasProvider::PathAtlasFlags::kCompute) &&
-        (strategy == PathRendererStrategy::kComputeAnalyticAA ||
-         strategy == PathRendererStrategy::kComputeMSAA16 ||
-         strategy == PathRendererStrategy::kComputeMSAA8 ||
-         strategy == PathRendererStrategy::kDefault)) {
-        pathAtlas = fDC->getComputePathAtlas(fRecorder);
-    // Only use CPU rendered paths when multisampling is disabled
-    // TODO: enable other uses of the software path renderer
-    } else if (atlasProvider->isAvailable(AtlasProvider::PathAtlasFlags::kRaster) &&
-               (strategy == PathRendererStrategy::kRasterAA ||
-                (strategy == PathRendererStrategy::kDefault && !fMSAASupported))) {
-        pathAtlas = atlasProvider->getRasterPathAtlas();
-    }
+        use_compute_atlas_when_available(strategy)) {
+        PathAtlas* atlas = fDC->getComputePathAtlas(fRecorder);
+        SkASSERT(atlas);
 
-    // Use an atlas only if an MSAA technique isn't required.
-    if (!requireMSAA && pathAtlas) {
-        // Don't use a coverage mask renderer if the shape is too large for the atlas such that it
-        // cannot be efficiently rasterized. The only exception is if hardware MSAA is not supported
-        // as a fallback or one of the atlas strategies was explicitly requested.
+        // Don't use the compute renderer if it can't handle the shape efficiently.
         //
-        // If the hardware doesn't support MSAA and anti-aliasing is required, then we always render
-        // paths with atlasing.
-        if (!fMSAASupported || strategy != PathRendererStrategy::kDefault) {
-            return {nullptr, pathAtlas};
-        }
-
         // Use the conservative clip bounds for a rough estimate of the mask size (this avoids
         // having to evaluate the entire clip stack before choosing the renderer as it will have to
         // get evaluated again if we fall back to a different renderer).
-        Rect drawBounds = localToDevice.mapRect(shape.bounds());
-        if (pathAtlas->isSuitableForAtlasing(drawBounds, fClip.conservativeBounds())) {
-            return {nullptr, pathAtlas};
+        if (atlas->isSuitableForAtlasing(drawBounds, fClip.conservativeBounds())) {
+            pathAtlas = atlas;
         }
     }
 
+    // Fall back to CPU rendered paths when multisampling is disabled and the compute atlas is not
+    // available.
+    // TODO: enable other uses of the software path renderer
+    if (!pathAtlas && atlasProvider->isAvailable(AtlasProvider::PathAtlasFlags::kRaster) &&
+        (strategy == PathRendererStrategy::kRasterAA ||
+         (strategy == PathRendererStrategy::kDefault && !fMSAASupported))) {
+        PathAtlas* atlas = atlasProvider->getRasterPathAtlas();
+        SkASSERT(atlas);
+        if (atlas->isSuitableForAtlasing(drawBounds, fClip.conservativeBounds())) {
+            pathAtlas = atlas;
+        }
+    }
+
+    if (!requireMSAA && pathAtlas) {
+        // If we got here it means that we should draw with an atlas renderer if we can and avoid
+        // resorting to one of the tessellating techniques.
+        return {nullptr, pathAtlas};
+    }
+
     // If we got here, it requires tessellated path rendering or an MSAA technique applied to a
     // simple shape (so we interpret them as paths to reduce the number of pipelines we need).
 
@@ -1406,7 +1415,6 @@
         // would be pretty trivial to spin up.
         return {renderers->convexTessellatedWedges(), nullptr};
     } else {
-        Rect drawBounds = localToDevice.mapRect(shape.bounds());
         drawBounds.intersect(fClip.conservativeBounds());
         const bool preferWedges =
                 // If the draw bounds don't intersect with the clip stack's conservative bounds,