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