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