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