Flutter Engine
The Flutter Engine
DrawWriter.h
Go to the documentation of this file.
1/*
2 * Copyright 2021 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_DrawWriter_DEFINED
9#define skgpu_graphite_DrawWriter_DEFINED
10
15
16namespace skgpu::graphite {
17
18namespace DrawPassCommands {
19class List;
20}
21
22/**
23 * DrawWriter is a helper around recording draws (to a temporary buffer or directly to a
24 * CommandBuffer), particularly when the number of draws is not known ahead of time, or the vertex
25 * and instance data is computed at record time and does not have a known size.
26 *
27 * To use, construct the DrawWriter with the current pipeline layout or call newPipelineState() on
28 * an existing DrawWriter and then bind that matching pipeline. When other dynamic state needs to
29 * change between draw calls, notify the DrawWriter using newDynamicState() before recording the
30 * modifications. See the listing below for how to append dynamic data or draw with existing buffers
31 *
32 * CommandBuffer::draw(vertices)
33 * - dynamic vertex data -> DrawWriter::Vertices(writer) verts;
34 * verts.append(n) << ...;
35 * - fixed vertex data -> writer.draw(vertices, {}, vertexCount)
36 *
37 * CommandBuffer::drawIndexed(vertices, indices)
38 * - dynamic vertex data -> unsupported
39 * - fixed vertex,index data -> writer.drawIndexed(vertices, indices, indexCount)
40 *
41 * CommandBuffer::drawInstances(vertices, instances)
42 * - dynamic instance data + fixed vertex data ->
43 * DrawWriter::Instances instances(writer, vertices, {}, vertexCount);
44 * instances.append(n) << ...;
45 * - fixed vertex and instance data ->
46 * writer.drawInstanced(vertices, vertexCount, instances, instanceCount)
47 *
48 * CommandBuffer::drawIndexedInstanced(vertices, indices, instances)
49 * - dynamic instance data + fixed vertex, index data ->
50 * DrawWriter::Instances instances(writer, vertices, indices, indexCount);
51 * instances.append(n) << ...;
52 * - fixed vertex, index, and instance data ->
53 * writer.drawIndexedInstanced(vertices, indices, indexCount, instances, instanceCount)
54 *
55 * NOTE: DrawWriter automatically handles failures to find or create a GPU buffer or map it to
56 * be writable. All returned VertexWriters will have a non-null pointer to write to, even if it will
57 * be discarded due to GPU failure at Recorder::snap() time.
58 */
60public:
61 // NOTE: This constructor creates a writer that defaults 0 vertex and instance stride, so
62 // 'newPipelineState()' must be called once the pipeline properties are known before it's used.
64
65 // Cannot move or copy
66 DrawWriter(const DrawWriter&) = delete;
68
69 // flush() should be called before the writer is destroyed
70 ~DrawWriter() { SkASSERT(fPendingCount == 0); }
71
72 DrawBufferManager* bufferManager() { return fManager; }
73
74 // Issue draw calls for any pending vertex and instance data collected by the writer.
75 // Use either flush() or newDynamicState() based on context and readability.
76 void flush();
77 void newDynamicState() { this->flush(); }
78
79 // Notify the DrawWriter that a new pipeline needs to be bound, providing the primitive type and
80 // attribute strides of that pipeline. This issues draw calls for pending data that relied on
81 // the old pipeline, so this must be called *before* binding the new pipeline.
82 void newPipelineState(PrimitiveType type, size_t vertexStride, size_t instanceStride) {
83 this->flush();
84 fPrimitiveType = type;
85 fVertexStride = vertexStride;
86 fInstanceStride = instanceStride;
87
88 // NOTE: resetting pending base is sufficient to redo bindings for vertex/instance data that
89 // is later appended but doesn't invalidate bindings for fixed buffers that might not need
90 // to change between pipelines.
91 fPendingBase = 0;
92 SkASSERT(fPendingCount == 0);
93 }
94
95#ifdef SK_DEBUG
96 // Query current pipeline state for validation
97 size_t instanceStride() const { return fInstanceStride; }
98 size_t vertexStride() const { return fVertexStride; }
99 PrimitiveType primitiveType() const { return fPrimitiveType; }
100#endif
101
102 // Collects new vertex data for a call to CommandBuffer::draw(). Automatically accumulates
103 // vertex data into a buffer, issuing draw and bind calls as needed when a new buffer is
104 // required, so that it is seamless to the caller. The draws do not use instances or indices.
105 //
106 // Usage (assuming writer has already had 'newPipelineState()' called with correct strides):
107 // DrawWriter::Vertices verts{writer};
108 // verts.append(n) << x << y << ...;
109 //
110 // This should not be used when the vertex stride is 0.
111 class Vertices;
112
113 // Collects new instance data for a call to CommandBuffer::drawInstanced() or
114 // drawIndexedInstanced(). The specific draw call that's issued depends on if a non-null index
115 // buffer is provided for the template. Like DrawWriter::Vertices, this automatically merges
116 // the appended data into as few buffer binds and draw calls as possible, while remaining
117 // seamless to the caller.
118 //
119 // Usage for drawInstanced (assuming writer has correct strides):
120 // DrawWriter::Instances instances{writer, fixedVerts, {}, fixedVertexCount};
121 // instances.append(n) << foo << bar << ...;
122 //
123 // Usage for drawIndexedInstanced:
124 // DrawWriter::Instances instances{writer, fixedVerts, fixedIndices, fixedIndexCount};
125 // instances.append(n) << foo << bar << ...;
126 //
127 // This should not be used when the instance stride is 0. However, the fixed vertex buffer can
128 // be null (or have a stride of 0) if the vertex shader only relies on the vertex ID and no
129 // other per-vertex data.
130 class Instances;
131
132 // Collects new instance data for a call to CommandBuffer::drawInstanced() or
133 // drawIndexedInstanced() (depending on presence of index data in the template). Unlike the
134 // Instances mode, the template's index or vertex count is not provided at the time of creation.
135 // Instead, DynamicInstances can be used with pipeline programs that can have a flexible number
136 // of vertices per instance. Appended instances specify a proxy object that can be converted
137 // to the minimum index/vertex count they must be drawn with; but if they are later batched with
138 // instances that would use more, the pipeline's vertex shader knows how to handle it.
139 //
140 // The proxy object serves as a useful point of indirection when the actual index count is
141 // expensive to compute, but can be derived from correlated geometric properties. The proxy
142 // can store those properties and accumulate a "worst-case" and then calculate the index count
143 // when DrawWriter has to flush.
144 //
145 // The VertexCountProxy type must provide:
146 // - a default constructor and copy assignment, where the initial value represents the minimum
147 // supported vertex count.
148 // - an 'unsigned int' operator that converts the proxy to the actual index count that is
149 // needed in order to dispatch a draw call.
150 // - operator <<(const V&) where V is any type the caller wants to pass to append() that
151 // represents the proxy for the about-to-be-written instances. This operator then updates its
152 // internal state to represent the worst case between what had previously been recorded and
153 // the latest V value.
154 //
155 // Usage for drawInstanced (fixedIndices == {}) or drawIndexedInstanced:
156 // DrawWriter::DynamicInstances<ProxyType> instances(writer, fixedVerts, fixedIndices);
157 // instances.append(minIndexProxy1, n1) << ...;
158 // instances.append(minIndexProxy2, n2) << ...;
159 //
160 // In this example, if the two sets of instances were contiguous, a single draw call with
161 // (n1 + n2) instances would still be made using max(minIndexCount1, minIndexCount2) as the
162 // index/vertex count, 'minIndexCountX' was derived from 'minIndexProxyX'. If the available
163 // vertex data from the DrawBufferManager forced a flush after the first, then the second would
164 // use minIndexCount2 unless a subsequent compatible DynamicInstances template appended more
165 // contiguous data.
166 template <typename VertexCountProxy>
167 class DynamicInstances;
168
169 // Issues a draws with fully specified data. This can be used when all instance data has already
170 // been written to known buffers, or when the vertex shader only depends on the vertex or
171 // instance IDs. To keep things simple, these helpers do not accept parameters for base vertices
172 // or instances; if needed, this can be accounted for in the BindBufferInfos provided.
173 //
174 // This will not merge with any already appended instance or vertex data, pending data is issued
175 // in its own draw call first.
176 void draw(BindBufferInfo vertices, unsigned int vertexCount) {
177 this->bindAndFlush(vertices, {}, {}, 0, vertexCount);
178 }
179 void drawIndexed(BindBufferInfo vertices, BindBufferInfo indices, unsigned int indexCount) {
180 this->bindAndFlush(vertices, indices, {}, 0, indexCount);
181 }
182 void drawInstanced(BindBufferInfo vertices, unsigned int vertexCount,
183 BindBufferInfo instances, unsigned int instanceCount) {
184 SkASSERT(vertexCount > 0);
185 this->bindAndFlush(vertices, {}, instances, vertexCount, instanceCount);
186 }
188 unsigned int indexCount, BindBufferInfo instances,
189 unsigned int instanceCount) {
190 SkASSERT(indexCount > 0);
191 this->bindAndFlush(vertices, indices, instances, indexCount, instanceCount);
192 }
193
194private:
195 // Both of these pointers must outlive the DrawWriter.
196 DrawPassCommands::List* fCommandList;
197 DrawBufferManager* fManager;
198
199 SkAutoMalloc fFailureStorage; // storage address for VertexWriter when GPU buffer mapping fails
200
201 // Pipeline state matching currently bound pipeline
202 PrimitiveType fPrimitiveType;
203 size_t fVertexStride;
204 size_t fInstanceStride;
205
206 /// Draw buffer binding state for pending draws
207 BindBufferInfo fVertices;
208 BindBufferInfo fIndices;
209 BindBufferInfo fInstances;
210 // Vertex/index count for [pseudo]-instanced rendering:
211 // == 0 is vertex-only drawing; > 0 is regular instanced drawing; < 0 is dynamic index count
212 // instanced drawing, where real index count = max(-fTemplateCount-1)
213 int fTemplateCount;
214
215 unsigned int fPendingCount; // # of vertices or instances (depending on mode) to be drawn
216 unsigned int fPendingBase; // vertex/instance offset (depending on mode) applied to buffer
217 bool fPendingBufferBinds; // true if {fVertices,fIndices,fInstances} has changed since last draw
218
219 void setTemplate(BindBufferInfo vertices, BindBufferInfo indices, BindBufferInfo instances,
220 int templateCount);
221 // NOTE: bindAndFlush's templateCount is unsigned because dynamic index count instancing
222 // isn't applicable.
223 void bindAndFlush(BindBufferInfo vertices, BindBufferInfo indices, BindBufferInfo instances,
224 unsigned int templateCount, unsigned int drawCount) {
225 SkASSERT(drawCount > 0);
226 SkASSERT(!fAppender); // shouldn't be appending and manually drawing at the same time.
227 this->setTemplate(vertices, indices, instances, SkTo<int>(templateCount));
228 fPendingBase = 0;
229 fPendingCount = drawCount;
230 this->flush();
231 }
232
233 // RAII - Sets the DrawWriter's template and marks the writer in append mode (disabling direct
234 // draws until the Appender is destructed).
235 class Appender;
236 SkDEBUGCODE(const Appender* fAppender = nullptr;)
237};
238
239// Appender implementations for DrawWriter that set the template on creation and provide a
240// template-specific API to accumulate vertex/instance data.
242public:
243 enum class Target { kVertices, kInstances };
244
246 : fDrawer(w)
247 , fTarget(target == Target::kVertices ? w.fVertices : w.fInstances)
248 , fStride(target == Target::kVertices ? w.fVertexStride : w.fInstanceStride)
249 , fReservedCount(0)
250 , fNextWriter() {
251 SkASSERT(fStride > 0);
252 SkASSERT(!w.fAppender);
253 SkDEBUGCODE(w.fAppender = this;)
254 }
255
256 virtual ~Appender() {
257 if (fReservedCount > 0) {
259 }
260 SkASSERT(fDrawer.fAppender == this);
261 SkDEBUGCODE(fDrawer.fAppender = nullptr;)
262 }
263
264protected:
267 size_t fStride;
268
269 unsigned int fReservedCount; // in target stride units
270 VertexWriter fNextWriter; // writing to the target buffer binding
271
272 virtual void onFlush() {}
273
274 void reserve(unsigned int count) {
275 if (fReservedCount >= count) {
276 return;
277 } else if (fReservedCount > 0) {
278 // Have contiguous bytes that can't satisfy request, so return them in the event the
279 // DBM has additional contiguous bytes after the prior reserved range.
281 }
282
284 // NOTE: Cannot bind tuple directly to fNextWriter, compilers don't produce the right
285 // move assignment.
286 auto [writer, reservedChunk] = fDrawer.fManager->getVertexWriter(count * fStride);
287 if (reservedChunk.fBuffer != fTarget.fBuffer ||
288 reservedChunk.fOffset !=
289 (fTarget.fOffset + (fDrawer.fPendingBase + fDrawer.fPendingCount) * fStride)) {
290 // Not contiguous, so flush and update binding to 'reservedChunk'
291 this->onFlush();
292 fDrawer.flush();
293
294 fTarget = reservedChunk;
295 fDrawer.fPendingBase = 0;
296 fDrawer.fPendingBufferBinds = true;
297 }
298 fNextWriter = std::move(writer);
299 }
300
301 VertexWriter append(unsigned int count) {
302 SkASSERT(count > 0);
303 this->reserve(count);
304
305 const size_t size = count * fStride;
307 // If the GPU mapped buffer failed, ensure we have a sufficiently large CPU address to
308 // write to so that RenderSteps don't have to worry about error handling. The Recording
309 // will fail since the map failure is tracked by BufferManager.
311 size);
312 }
313
316 fDrawer.fPendingCount += count;
317 return std::exchange(fNextWriter, fNextWriter.makeOffset(size));
318 }
319};
320
322public:
324 w.setTemplate(w.fVertices, {}, {}, 0);
325 }
326
327 using Appender::reserve;
328 using Appender::append;
329};
330
332public:
334 BindBufferInfo vertices,
335 BindBufferInfo indices,
336 unsigned int vertexCount)
337 : Appender(w, Target::kInstances) {
338 SkASSERT(vertexCount > 0);
339 w.setTemplate(vertices, indices, w.fInstances, SkTo<int>(vertexCount));
340 }
341
342 using Appender::reserve;
343 using Appender::append;
344};
345
346template <typename VertexCountProxy>
348public:
350 BindBufferInfo vertices,
351 BindBufferInfo indices)
352 : Appender(w, Target::kInstances) {
353 w.setTemplate(vertices, indices, w.fInstances, -1);
354 }
355
356 ~DynamicInstances() override {
357 // Persist the template count since the DrawWriter could continue batching if a new
358 // compatible DynamicInstances object is created for the next draw.
359 this->updateTemplateCount();
360 }
361
363
364 template <typename V>
365 VertexWriter append(const V& vertexCount, unsigned int instanceCount) {
366 VertexWriter w = this->Appender::append(instanceCount);
367 // Record index count after appending instance data in case the append triggered a flush
368 // and the max index count is reset. However, the contents of 'w' will not have been flushed
369 // so 'fProxy' will account for 'vertexCount' when it is actually drawn.
370 fProxy << vertexCount;
371 return w;
372 }
373
374private:
375 void updateTemplateCount() {
376 const unsigned int count = static_cast<unsigned int>(fProxy);
377 fDrawer.fTemplateCount = std::min(fDrawer.fTemplateCount, -SkTo<int>(count) - 1);
378 // By resetting the proxy after updating the template count, the next batch will start over
379 // with the minimum required vertex count and grow from there.
380 fProxy = {};
381 }
382
383 void onFlush() override {
384 // Update the DrawWriter's template count before its flush() is invoked and the appender
385 // starts recording to a new buffer, which ensures the flush's draw call uses the most
386 // up-to-date vertex count derived from fProxy.
387 this->updateTemplateCount();
388 }
389
390 VertexCountProxy fProxy = {};
391};
392
393} // namespace skgpu::graphite
394
395#endif // skgpu_graphite_DrawWriter_DEFINED
int count
Definition: FontMgrTest.cpp:50
#define SkASSERT(cond)
Definition: SkAssert.h:116
#define SK_UNLIKELY
Definition: SkAssert.h:28
SkDEBUGCODE(SK_SPI) SkThreadID SkGetThreadID()
GLenum type
void * reset(size_t size=0, OnShrink shrink=kAlloc_OnShrink)
Definition: SkAutoMalloc.h:53
std::pair< VertexWriter, BindBufferInfo > getVertexWriter(size_t requiredBytes)
void returnVertexBytes(size_t unusedBytes)
void reserve(unsigned int count)
Definition: DrawWriter.h:274
Appender(DrawWriter &w, Target target)
Definition: DrawWriter.h:245
VertexWriter append(unsigned int count)
Definition: DrawWriter.h:301
DynamicInstances(DrawWriter &w, BindBufferInfo vertices, BindBufferInfo indices)
Definition: DrawWriter.h:349
VertexWriter append(const V &vertexCount, unsigned int instanceCount)
Definition: DrawWriter.h:365
Instances(DrawWriter &w, BindBufferInfo vertices, BindBufferInfo indices, unsigned int vertexCount)
Definition: DrawWriter.h:333
void drawIndexed(BindBufferInfo vertices, BindBufferInfo indices, unsigned int indexCount)
Definition: DrawWriter.h:179
void drawInstanced(BindBufferInfo vertices, unsigned int vertexCount, BindBufferInfo instances, unsigned int instanceCount)
Definition: DrawWriter.h:182
DrawWriter(DrawWriter &&)=delete
DrawWriter(const DrawWriter &)=delete
void draw(BindBufferInfo vertices, unsigned int vertexCount)
Definition: DrawWriter.h:176
void drawIndexedInstanced(BindBufferInfo vertices, BindBufferInfo indices, unsigned int indexCount, BindBufferInfo instances, unsigned int instanceCount)
Definition: DrawWriter.h:187
DrawWriter(DrawPassCommands::List *, DrawBufferManager *)
Definition: DrawWriter.cpp:15
void newPipelineState(PrimitiveType type, size_t vertexStride, size_t instanceStride)
Definition: DrawWriter.h:82
DrawBufferManager * bufferManager()
Definition: DrawWriter.h:72
uint32_t * target
static float min(float r, float g, float b)
Definition: hsl.cpp:48
T __attribute__((ext_vector_type(N))) V
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
SkScalar w
VertexWriter makeOffset(size_t offsetInBytes) const
Definition: BufferWriter.h:142