xref: /aosp_15_r20/external/skia/src/gpu/graphite/PathAtlas.h (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2023 Google LLC
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #ifndef skgpu_graphite_PathAtlas_DEFINED
9 #define skgpu_graphite_PathAtlas_DEFINED
10 
11 #include "include/core/SkStrokeRec.h"
12 #include "src/base/SkTInternalLList.h"
13 #include "src/core/SkTHash.h"
14 #include "src/gpu/AtlasTypes.h"
15 #include "src/gpu/ResourceKey.h"
16 #include "src/gpu/graphite/DrawAtlas.h"
17 #include "src/gpu/graphite/geom/CoverageMaskShape.h"
18 
19 namespace skgpu::graphite {
20 
21 class Caps;
22 class DrawContext;
23 class Recorder;
24 class Rect;
25 class Renderer;
26 class Shape;
27 class TextureProxy;
28 class Transform;
29 
30 /**
31  * PathAtlas manages one or more atlas textures that store coverage masks for path rendering.
32  *
33  * The contents of a PathAtlas are intended to be transient: atlas regions are considered valid only
34  * for the scope of the render passes that sample them. Unlike DrawAtlas, PathAtlas does not
35  * necessarily support partial eviction and reuse of subregions. In most subclasses, once an atlas
36  * texture is filled up all of its sub-allocations must be invalidated before it can be reused.
37  *
38  * PathAtlas does not prescribe how atlas contents get uploaded to the GPU. The specific task
39  * mechanism is defined by subclasses.
40  */
41 class PathAtlas {
42 public:
43     /**
44      * The PathAtlas will use textures of the requested size or the system's maximum texture size,
45      * whichever is smaller.
46      */
47     PathAtlas(Recorder* recorder, uint32_t requestedWidth, uint32_t requestedHeight);
48     virtual ~PathAtlas();
49 
50     using MaskAndOrigin = std::pair<CoverageMaskShape, SkIPoint>;
51 
52     // Subclasses should ensure that the recorded masks have this much padding around each entry.
53     // PathAtlas passes in un-padded sizes to onAddShape and assumes that padding has been included
54     // in the outPos value.
55     static constexpr int kEntryPadding = 1;
56 
57     /**
58      * Searches the atlas for a slot that can fit a coverage mask for a clipped shape with the given
59      * bounds in device coordinates and submits the mask to be drawn into the found atlas region.
60      * For atlases that cache coverage masks, will first search the cache before adding.
61      *
62      * Returns an empty result if a the shape cannot fit in the atlas. Otherwise, returns the
63      * CoverageMaskShape (including the texture proxy) for sampling the eventually-rendered coverage
64      * mask and the device-space origin the mask should be drawn at (e.g. its recorded draw should
65      * be an integer translation matrix), and the Renderer that should be used to draw that shape.
66      * The Renderer should have single-channel coverage, require AA bounds outsetting, and have a
67      * single renderStep.
68      *
69      * The bounds of the atlas entry is laid out with a 1 pixel outset from the given dimensions.
70      * The returned shape's UV origin accounts for the padding, and its mask size does not include
71      * the padding. This allows the mask to be sampled safely with linear filtering without worrying
72      * about HW filtering accessing pixels from other entries.
73      *
74      * `shape` will be drawn after applying the linear components (scale, rotation, skew) of the
75      * provided `localToDevice` transform. This is done by  translating the shape by the inverse of
76      * the rounded out `transformedShapeBounds` offset. For an unclipped shape this amounts to
77      * translating it back to its origin while preserving any sub-pixel translation. For a clipped
78      * shape, this ensures that the visible portions of the mask are centered in the atlas slot
79      * while invisible portions that would lie outside the atlas slot get clipped out.
80      *
81      * `addShape()` schedules the shape to be drawn but when and how the rendering happens is
82      * specified by the subclass implementation.
83      *
84      * The stroke-and-fill style is drawn as a single combined coverage mask containing the stroke
85      * and the fill.
86      */
87     std::pair<const Renderer*, std::optional<MaskAndOrigin>> addShape(
88             const Rect& transformedShapeBounds,
89             const Shape& shape,
90             const Transform& localToDevice,
91             const SkStrokeRec& style);
92 
93     /**
94      * Returns true if a path coverage mask with the given device-space bounds is sufficiently
95      * small to benefit from atlasing without causing too many atlas renders.
96      *
97      * `transformedShapeBounds` represents the device-space bounds of the coverage mask shape
98      * unrestricted by clip and viewport bounds.
99      *
100      * `clipBounds` represents the conservative bounding box of the union of the clip stack that
101      * should apply to the shape.
102      */
isSuitableForAtlasing(const Rect & transformedShapeBounds,const Rect & clipBounds)103     virtual bool isSuitableForAtlasing(const Rect& transformedShapeBounds,
104                                        const Rect& clipBounds) const {
105         return true;
106     }
107 
width()108     uint32_t width() const { return fWidth; }
height()109     uint32_t height() const { return fHeight; }
110 
111 protected:
112     // The 'transform' has been adjusted to draw the Shape into a logical image from (0,0) to
113     // 'maskSize'. The actual rendering into the returned TextureProxy will need to be further
114     // translated by the value written to 'outPos', which is the responsibility of subclasses.
115     virtual const TextureProxy* onAddShape(const Shape&,
116                                            const Transform& transform,
117                                            const SkStrokeRec&,
118                                            skvx::half2 maskSize,
119                                            skvx::half2* outPos) = 0;
120 
121     // Wrapper class to manage DrawAtlas and associated caching operations
122     class DrawAtlasMgr : public AtlasGenerationCounter, public PlotEvictionCallback {
123     public:
124         const TextureProxy* findOrCreateEntry(Recorder* recorder,
125                                               const Shape& shape,
126                                               const Transform& transform,
127                                               const SkStrokeRec& strokeRec,
128                                               skvx::half2 maskSize,
129                                               skvx::half2* outPos);
130         // Adds to DrawAtlas but not the cache
131         const TextureProxy* addToAtlas(Recorder* recorder,
132                                        const Shape& shape,
133                                        const Transform& transform,
134                                        const SkStrokeRec& strokeRec,
135                                        skvx::half2 maskSize,
136                                        skvx::half2* outPos,
137                                        AtlasLocator* locator);
138         bool recordUploads(DrawContext*, Recorder*);
139         void evict(PlotLocator) override;
140         void compact(Recorder*, bool forceCompact);
141 
142         void evictAll();
143 
144     protected:
145         DrawAtlasMgr(size_t width, size_t height,
146                      size_t plotWidth, size_t plotHeight,
147                      DrawAtlas::UseStorageTextures useStorageTextures,
148                      std::string_view label, const Caps*);
149 
150         bool virtual onAddToAtlas(const Shape&,
151                                   const Transform& transform,
152                                   const SkStrokeRec&,
153                                   SkIRect shapeBounds,
154                                   const AtlasLocator&) = 0;
155 
156         std::unique_ptr<DrawAtlas> fDrawAtlas;
157 
158     private:
159         // Tracks whether a shape is already in the DrawAtlas, and its location in the atlas
160         struct UniqueKeyHash {
operatorUniqueKeyHash161             uint32_t operator()(const skgpu::UniqueKey& key) const { return key.hash(); }
162         };
163         using ShapeCache = skia_private::THashMap<skgpu::UniqueKey, AtlasLocator, UniqueKeyHash>;
164         ShapeCache fShapeCache;
165 
166         // List of stored keys per Plot, used to invalidate cache entries.
167         // When a Plot is invalidated via evict(), we'll get its index and Page index from the
168         // PlotLocator, index into the fKeyLists array to get the ShapeKeyList for that Plot,
169         // then iterate through the list and remove entries matching those keys from the ShapeCache.
170         struct ShapeKeyEntry {
171             skgpu::UniqueKey fKey;
172             SK_DECLARE_INTERNAL_LLIST_INTERFACE(ShapeKeyEntry);
173         };
174         using ShapeKeyList = SkTInternalLList<ShapeKeyEntry>;
175         SkTDArray<ShapeKeyList> fKeyLists;
176     };
177 
178     // The Recorder that created and owns this Atlas.
179     Recorder* fRecorder;
180 
181     uint32_t fWidth = 0;
182     uint32_t fHeight = 0;
183 };
184 
185 }  // namespace skgpu::graphite
186 
187 #endif  // skgpu_graphite_PathAtlas_DEFINED
188