Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
ComputePathAtlas.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2024 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
9
19
20#ifdef SK_ENABLE_VELLO_SHADERS
22#endif
23
24namespace skgpu::graphite {
25namespace {
26
27// TODO: This is the maximum target dimension that vello can handle today
28constexpr uint16_t kComputeAtlasDim = 4096;
29
30} // namespace
31
33 : PathAtlas(recorder, kComputeAtlasDim, kComputeAtlasDim)
34 , fRectanizer(this->width(), this->height()) {}
35
36bool ComputePathAtlas::initializeTextureIfNeeded() {
37 if (!fTexture) {
40 this->width(),
41 this->height(),
42 targetCT,
43 /*identifier=*/0,
44 /*requireStorageUsage=*/true);
45 }
46 return fTexture != nullptr;
47}
48
49bool ComputePathAtlas::isSuitableForAtlasing(const Rect& transformedShapeBounds) const {
50 Rect maskBounds = transformedShapeBounds.makeRoundOut();
51 skvx::float2 maskSize = maskBounds.size();
52 float width = maskSize.x(), height = maskSize.y();
53
54 if (width > this->width() || height > this->height()) {
55 return false;
56 }
57
58 // For now we're allowing paths that are smaller than 1/32nd of the full 4096x4096 atlas size
59 // to prevent the atlas texture from filling up too often. There are several approaches we
60 // should explore to alleviate the cost of atlasing large paths.
61 //
62 // 1. Rendering multiple atlas textures requires an extra compute pass for each texture. This
63 // impairs performance because there is a fixed cost to each dispatch and all dispatches get
64 // serialized by pipeline barrier synchronization. We should explore ways to render to multiple
65 // textures by issuing more workgroups in fewer dispatches as well as removing pipeline barriers
66 // across dispatches that target different atlas pages.
67 //
68 // 2. Implement a compressed "sparse" mask rendering scheme to render paths with a large
69 // bounding box using less texture space.
70 return (width * height) <= (1024 * 512);
71}
72
74 SkIPoint16* outPos) {
75 if (!this->initializeTextureIfNeeded()) {
76 SKGPU_LOG_E("Failed to instantiate an atlas texture");
77 return nullptr;
78 }
79
80 // An empty mask always fits, so just return the texture.
81 // TODO: This may not be needed if we can handle clipped out bounds with inverse fills
82 // another way. See PathAtlas::addShape().
83 if (!all(maskSize)) {
84 *outPos = {0, 0};
85 return fTexture.get();
86 }
87
88 if (!fRectanizer.addPaddedRect(maskSize.x(), maskSize.y(), kEntryPadding, outPos)) {
89 return nullptr;
90 }
91
92 return fTexture.get();
93}
94
96 fRectanizer.reset();
97
98 this->onReset();
99}
100
101#ifdef SK_ENABLE_VELLO_SHADERS
102
103/**
104 * ComputePathAtlas that uses a VelloRenderer.
105 */
106class VelloComputePathAtlas final : public ComputePathAtlas {
107public:
108 explicit VelloComputePathAtlas(Recorder* recorder) : ComputePathAtlas(recorder) {}
109 // Record the compute dispatches that will draw the atlas contents.
110 std::unique_ptr<DispatchGroup> recordDispatches(Recorder* recorder) const override;
111
112private:
113 const TextureProxy* onAddShape(const Shape&,
114 const Transform& transform,
115 const SkStrokeRec&,
116 skvx::half2 maskSize,
117 skvx::half2* outPos) override;
118 void onReset() override {
119 fScene.reset();
120 fOccuppiedWidth = fOccuppiedHeight = 0;
121 }
122
123 // Contains the encoded scene buffer data that serves as the input to a vello compute pass.
124 VelloScene fScene;
125
126 // Occuppied bounds of the atlas
127 uint32_t fOccuppiedWidth = 0;
128 uint32_t fOccuppiedHeight = 0;
129};
130
131std::unique_ptr<DispatchGroup> VelloComputePathAtlas::recordDispatches(Recorder* recorder) const {
132 if (!this->texture()) {
133 return nullptr;
134 }
135
136 SkASSERT(recorder && recorder == fRecorder);
137 // Unless the analytic area AA mode unless caps say otherwise.
139#if defined(GRAPHITE_TEST_UTILS)
140 PathRendererStrategy strategy = recorder->priv().caps()->requestedPathRendererStrategy();
141 if (strategy == PathRendererStrategy::kComputeMSAA16) {
142 config = VelloAaConfig::kMSAA16;
143 } else if (strategy == PathRendererStrategy::kComputeMSAA8) {
144 config = VelloAaConfig::kMSAA8;
145 }
146#endif
147 return recorder->priv().rendererProvider()->velloRenderer()->renderScene(
148 {fOccuppiedWidth, fOccuppiedHeight, SkColors::kBlack, config},
149 fScene,
150 sk_ref_sp(this->texture()),
151 recorder);
152}
153
154const TextureProxy* VelloComputePathAtlas::onAddShape(
155 const Shape& shape,
156 const Transform& transform,
157 const SkStrokeRec& style,
158 skvx::half2 maskSize,
159 skvx::half2* outPos) {
160 SkIPoint16 iPos;
161 const TextureProxy* texProxy = this->addRect(maskSize, &iPos);
162 if (!texProxy) {
163 return nullptr;
164 }
165 *outPos = skvx::half2(iPos.x(), iPos.y());
166 // If the mask is empty, just return.
167 // TODO: This may not be needed if we can handle clipped out bounds with inverse fills
168 // another way. See PathAtlas::addShape().
169 if (!all(maskSize)) {
170 return texProxy;
171 }
172
173 // TODO: The compute renderer doesn't support perspective yet. We assume that the path has been
174 // appropriately transformed in that case.
176
177 // Restrict the render to the occupied area of the atlas, including entry padding so that the
178 // padded row/column is cleared when Vello renders.
179 Rect atlasBounds = Rect::XYWH(skvx::float2(iPos.x(), iPos.y()), skvx::cast<float>(maskSize));
180 fOccuppiedWidth = std::max(fOccuppiedWidth, (uint32_t)atlasBounds.right() + kEntryPadding);
181 fOccuppiedHeight = std::max(fOccuppiedHeight, (uint32_t)atlasBounds.bot() + kEntryPadding);
182
183 // TODO(b/283876964): Apply clips here. Initially we'll need to encode the clip stack repeatedly
184 // for each shape since the full vello renderer treats clips and their affected draws as a
185 // single shape hierarchy in the same scene coordinate space. For coverage masks we want each
186 // mask to be transformed to its atlas allocation coordinates and for the clip to be applied
187 // with a translation relative to the atlas slot.
188 //
189 // Repeatedly encoding the clip stack should be relatively cheap (depending on how deep the
190 // clips get) however it is wasteful both in terms of time and memory. If this proves to hurt
191 // performance, future work will explore building an atlas-oriented element processing stage
192 // that applies the atlas-relative translation while evaluating the stack monoid on the GPU.
193
194 // Clip the mask to the bounds of the atlas slot, which are already inset by 1px relative to
195 // the bounds that the Rectanizer assigned.
196 SkPath clipRect = SkPath::Rect(atlasBounds.asSkRect());
197 fScene.pushClipLayer(clipRect, Transform::Identity());
198
199 // The atlas transform of the shape is the linear-components (scale, rotation, skew) of
200 // `localToDevice` translated by the top-left offset of `atlasBounds`.
201 Transform atlasTransform = transform.postTranslate(atlasBounds.x(), atlasBounds.y());
202 SkPath devicePath = shape.asPath();
203
204 // For stroke-and-fill, draw two masks into the same atlas slot: one for the stroke and one for
205 // the fill.
206 SkStrokeRec::Style styleType = style.getStyle();
207 if (styleType == SkStrokeRec::kStroke_Style ||
208 styleType == SkStrokeRec::kHairline_Style ||
210 // We need to special-case hairline strokes and strokes with sub-pixel width as Vello
211 // draws these with aliasing and the results are barely visible. Draw the stroke with a
212 // device-space width of 1 pixel and scale down the alpha by the true width to approximate
213 // the sampled area.
214 float width = style.getWidth();
215 float deviceWidth = width * atlasTransform.maxScaleFactor();
216 if (style.isHairlineStyle() || deviceWidth <= 1.0) {
217 // Both strokes get 1/2 weight scaled by the theoretical area (1 for hairlines,
218 // `deviceWidth` otherwise).
220 color.fR *= style.isHairlineStyle() ? 1.0 : deviceWidth;
221
222 // Transform the stroke's width to its local coordinate space since it'll get drawn with
223 // `atlasTransform`.
224 float transformedWidth = 1.0f / atlasTransform.maxScaleFactor();
225 SkStrokeRec adjustedStyle(style);
226 adjustedStyle.setStrokeStyle(transformedWidth);
227 fScene.solidStroke(devicePath, color, adjustedStyle, atlasTransform);
228 } else {
229 fScene.solidStroke(devicePath, SkColors::kRed, style, atlasTransform);
230 }
231 }
232 if (styleType == SkStrokeRec::kFill_Style || styleType == SkStrokeRec::kStrokeAndFill_Style) {
233 fScene.solidFill(devicePath, SkColors::kRed, shape.fillType(), atlasTransform);
234 }
235
236 fScene.popClipLayer();
237
238 return texProxy;
239}
240
241#endif // SK_ENABLE_VELLO_SHADERS
242
243std::unique_ptr<ComputePathAtlas> ComputePathAtlas::CreateDefault(Recorder* recorder) {
244#ifdef SK_ENABLE_VELLO_SHADERS
245 return std::make_unique<VelloComputePathAtlas>(recorder);
246#else
247 return nullptr;
248#endif
249}
250
251} // namespace skgpu::graphite
SkColor4f color
#define SKGPU_LOG_E(fmt,...)
Definition Log.h:38
#define SkASSERT(cond)
Definition SkAssert.h:116
SkColorType
Definition SkColorType.h:19
sk_sp< T > sk_ref_sp(T *obj)
Definition SkRefCnt.h:381
Shape
static SkPath Rect(const SkRect &, SkPathDirection=SkPathDirection::kCW, unsigned startIndex=0)
Definition SkPath.cpp:3518
Style getStyle() const
@ kStrokeAndFill_Style
Definition SkStrokeRec.h:36
bool isHairlineStyle() const
Definition SkStrokeRec.h:47
SkScalar getWidth() const
Definition SkStrokeRec.h:42
bool addPaddedRect(int width, int height, int16_t padding, SkIPoint16 *loc)
Definition Rectanizer.h:34
sk_sp< TextureProxy > getAtlasTexture(Recorder *, uint16_t width, uint16_t height, SkColorType, uint16_t identifier, bool requireStorageUsage)
bool isSuitableForAtlasing(const Rect &transformedShapeBounds) const override
static std::unique_ptr< ComputePathAtlas > CreateDefault(Recorder *)
const TextureProxy * addRect(skvx::half2 maskSize, SkIPoint16 *outPos)
uint32_t height() const
Definition PathAtlas.h:95
static constexpr int kEntryPadding
Definition PathAtlas.h:101
uint32_t width() const
Definition PathAtlas.h:94
AtlasProvider * atlasProvider()
const Caps * caps() const
const RendererProvider * rendererProvider() const
static AI Rect XYWH(float x, float y, float w, float h)
Definition Rect.h:40
AI float2 size() const
Definition Rect.h:107
AI Rect makeRoundOut() const
Definition Rect.h:133
static constexpr Transform Identity()
FlTexture * texture
constexpr SkColor4f kRed
Definition SkColor.h:440
constexpr SkColor4f kBlack
Definition SkColor.h:435
clipRect(r.rect, r.opAA.op(), r.opAA.aa())) template<> void Draw
TRect< Scalar > Rect
Definition rect.h:746
SkColorType ComputeShaderCoverageMaskTargetFormat(const Caps *caps)
skgpu::graphite::Transform Transform
Vec< 2, uint16_t > half2
Definition SkVx.h:1175
SIT bool all(const Vec< 1, T > &x)
Definition SkVx.h:582
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
Definition p3.cpp:47
int32_t height
int32_t width
int16_t y() const
Definition SkIPoint16.h:43
int16_t x() const
Definition SkIPoint16.h:37