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