Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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.
310 return VertexWriter(fDrawer.fFailureStorage.reset(size, SkAutoMalloc::kReuse_OnShrink),
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)
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)
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
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SK_UNLIKELY
Definition SkAssert.h:28
#define SkDEBUGCODE(...)
Definition SkDebug.h:23
void * reset(size_t size=0, OnShrink shrink=kAlloc_OnShrink)
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
void newPipelineState(PrimitiveType type, size_t vertexStride, size_t instanceStride)
Definition DrawWriter.h:82
DrawBufferManager * bufferManager()
Definition DrawWriter.h:72
uint32_t * target
T __attribute__((ext_vector_type(N))) V
SkScalar w
VertexWriter makeOffset(size_t offsetInBytes) const