• 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/base/SkAutoMalloc.h"
12 #include "src/gpu/BufferWriter.h"
13 #include "src/gpu/graphite/BufferManager.h"
14 #include "src/gpu/graphite/DrawTypes.h"
15 
16 namespace skgpu::graphite {
17 
18 namespace DrawPassCommands {
19 class 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  */
59 class DrawWriter {
60 public:
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.
63     DrawWriter(DrawPassCommands::List*, DrawBufferManager*);
64 
65     // Cannot move or copy
66     DrawWriter(const DrawWriter&) = delete;
67     DrawWriter(DrawWriter&&) = delete;
68 
69     // flush() should be called before the writer is destroyed
~DrawWriter()70     ~DrawWriter() { SkASSERT(fPendingCount == 0); }
71 
bufferManager()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();
newDynamicState()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.
newPipelineState(PrimitiveType type,size_t vertexStride,size_t instanceStride)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
instanceStride()97     size_t        instanceStride() const { return fInstanceStride; }
vertexStride()98     size_t        vertexStride()   const { return fVertexStride;   }
primitiveType()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.
draw(BindBufferInfo vertices,unsigned int vertexCount)176     void draw(BindBufferInfo vertices, unsigned int vertexCount) {
177         this->bindAndFlush(vertices, {}, {}, 0, vertexCount);
178     }
drawIndexed(BindBufferInfo vertices,BindBufferInfo indices,unsigned int indexCount)179     void drawIndexed(BindBufferInfo vertices, BindBufferInfo indices, unsigned int indexCount) {
180         this->bindAndFlush(vertices, indices, {}, 0, indexCount);
181     }
drawInstanced(BindBufferInfo vertices,unsigned int vertexCount,BindBufferInfo instances,unsigned int instanceCount)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     }
drawIndexedInstanced(BindBufferInfo vertices,BindBufferInfo indices,unsigned int indexCount,BindBufferInfo instances,unsigned int instanceCount)187     void drawIndexedInstanced(BindBufferInfo vertices, BindBufferInfo indices,
188                               unsigned int indexCount, BindBufferInfo instances,
189                               unsigned int instanceCount) {
190         SkASSERT(indexCount > 0);
191         this->bindAndFlush(vertices, indices, instances, indexCount, instanceCount);
192     }
193 
194 private:
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.
bindAndFlush(BindBufferInfo vertices,BindBufferInfo indices,BindBufferInfo instances,unsigned int templateCount,unsigned int drawCount)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.
241 class DrawWriter::Appender {
242 public:
243     enum class Target { kVertices, kInstances };
244 
Appender(DrawWriter & w,Target target)245     Appender(DrawWriter& w, Target target)
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 
~Appender()256     virtual ~Appender() {
257         if (fReservedCount > 0) {
258             fDrawer.fManager->returnVertexBytes(fReservedCount * fStride);
259         }
260         SkASSERT(fDrawer.fAppender == this);
261         SkDEBUGCODE(fDrawer.fAppender = nullptr;)
262     }
263 
264 protected:
265     DrawWriter&     fDrawer;
266     BindBufferInfo& fTarget;
267     size_t          fStride;
268 
269     unsigned int fReservedCount; // in target stride units
270     VertexWriter fNextWriter;    // writing to the target buffer binding
271 
onFlush()272     virtual void onFlush() {}
273 
reserve(unsigned int count)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.
280             fDrawer.fManager->returnVertexBytes(fReservedCount * fStride);
281         }
282 
283         fReservedCount = count;
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 
append(unsigned int count)301     VertexWriter append(unsigned int count) {
302         SkASSERT(count > 0);
303         this->reserve(count);
304 
305         const size_t size = count * fStride;
306         if (!fNextWriter) SK_UNLIKELY {
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 
314         SkASSERT(fReservedCount >= count);
315         fReservedCount -= count;
316         fDrawer.fPendingCount += count;
317         return std::exchange(fNextWriter, fNextWriter.makeOffset(size));
318     }
319 };
320 
321 class DrawWriter::Vertices : private DrawWriter::Appender {
322 public:
Vertices(DrawWriter & w)323     Vertices(DrawWriter& w) : Appender(w, Target::kVertices) {
324         w.setTemplate(w.fVertices, {}, {}, 0);
325     }
326 
327     using Appender::reserve;
328     using Appender::append;
329 };
330 
331 class DrawWriter::Instances : private DrawWriter::Appender {
332 public:
Instances(DrawWriter & w,BindBufferInfo vertices,BindBufferInfo indices,unsigned int vertexCount)333     Instances(DrawWriter& w,
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 
346 template <typename VertexCountProxy>
347 class DrawWriter::DynamicInstances : private DrawWriter::Appender {
348 public:
DynamicInstances(DrawWriter & w,BindBufferInfo vertices,BindBufferInfo indices)349     DynamicInstances(DrawWriter& w,
350                      BindBufferInfo vertices,
351                      BindBufferInfo indices)
352             : Appender(w, Target::kInstances) {
353         w.setTemplate(vertices, indices, w.fInstances, -1);
354     }
355 
~DynamicInstances()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 
362     using Appender::reserve;
363 
364     template <typename V>
append(const V & vertexCount,unsigned int instanceCount)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 
374 private:
updateTemplateCount()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 
onFlush()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
396