• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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& localToDevice,
117                                            const SkStrokeRec&,
118                                            skvx::half2 maskOrigin,
119                                            skvx::half2 maskSize,
120                                            SkIVector transformedMaskOffset,
121                                            skvx::half2* outPos) = 0;
122 
123     // Wrapper class to manage DrawAtlas and associated caching operations
124     class DrawAtlasMgr : public AtlasGenerationCounter, public PlotEvictionCallback {
125     public:
126         const TextureProxy* findOrCreateEntry(Recorder* recorder,
127                                               const Shape&,
128                                               const Transform& localToDevice,
129                                               const SkStrokeRec&,
130                                               skvx::half2 maskOrigin,
131                                               skvx::half2 maskSize,
132                                               SkIVector transformedMaskOffset,
133                                               skvx::half2* outPos);
134         // Adds to DrawAtlas but not the cache
135         const TextureProxy* addToAtlas(Recorder* recorder,
136                                        const Shape&,
137                                        const Transform& localToDevice,
138                                        const SkStrokeRec&,
139                                        skvx::half2 maskSize,
140                                        SkIVector transformedMaskOffset,
141                                        skvx::half2* outPos,
142                                        AtlasLocator* locator);
143         bool recordUploads(DrawContext*, Recorder*);
144         void evict(PlotLocator) override;
145         void compact(Recorder*);
146         void freeGpuResources(Recorder*);
147 
148         void evictAll();
149 
150     protected:
151         DrawAtlasMgr(size_t width, size_t height,
152                      size_t plotWidth, size_t plotHeight,
153                      DrawAtlas::UseStorageTextures useStorageTextures,
154                      std::string_view label, const Caps*);
155 
156         bool virtual onAddToAtlas(const Shape&,
157                                   const Transform& localToDevice,
158                                   const SkStrokeRec&,
159                                   SkIRect shapeBounds,
160                                   SkIVector transformedMaskOffset,
161                                   const AtlasLocator&) = 0;
162 
163         std::unique_ptr<DrawAtlas> fDrawAtlas;
164 
165     private:
166         // Tracks whether a shape is already in the DrawAtlas, and its location in the atlas
167         struct UniqueKeyHash {
operatorUniqueKeyHash168             uint32_t operator()(const skgpu::UniqueKey& key) const { return key.hash(); }
169         };
170         using ShapeCache = skia_private::THashMap<skgpu::UniqueKey, AtlasLocator, UniqueKeyHash>;
171         ShapeCache fShapeCache;
172 
173         // List of stored keys per Plot, used to invalidate cache entries.
174         // When a Plot is invalidated via evict(), we'll get its index and Page index from the
175         // PlotLocator, index into the fKeyLists array to get the ShapeKeyList for that Plot,
176         // then iterate through the list and remove entries matching those keys from the ShapeCache.
177         struct ShapeKeyEntry {
178             skgpu::UniqueKey fKey;
179             SK_DECLARE_INTERNAL_LLIST_INTERFACE(ShapeKeyEntry);
180         };
181         using ShapeKeyList = SkTInternalLList<ShapeKeyEntry>;
182         SkTDArray<ShapeKeyList> fKeyLists;
183     };
184 
185     // The Recorder that created and owns this Atlas.
186     Recorder* fRecorder;
187 
188     uint32_t fWidth = 0;
189     uint32_t fHeight = 0;
190 };
191 
192 }  // namespace skgpu::graphite
193 
194 #endif  // skgpu_graphite_PathAtlas_DEFINED
195