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 uint32_t fVertexStride; 204 uint32_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 uint32_t fPendingCount; // # of vertices or instances (depending on mode) to be drawn 216 uint32_t 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 uint32_t fStride; 268 269 uint32_t 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. The byte count 280 // multiply should be safe here: if it would have overflowed, the original allocation 281 // should have failed and not increased fReservedCount. 282 SkASSERT(SkTFitsIn<uint32_t>((uint64_t)fReservedCount*(uint64_t)fStride)); 283 const uint32_t returnedBytes = fReservedCount * fStride; 284 SkASSERT(fTarget.fSize >= returnedBytes); 285 fDrawer.fManager->returnVertexBytes(returnedBytes); 286 fTarget.fSize -= returnedBytes; 287 fReservedCount = 0; 288 } 289 290 // NOTE: Cannot bind tuple directly to fNextWriter, compilers don't produce the right 291 // move assignment. 292 auto [writer, reservedChunk] = fDrawer.fManager->getVertexWriter(count, fStride); 293 if (writer) { 294 fReservedCount = count; 295 296 if (reservedChunk.fBuffer != fTarget.fBuffer || 297 reservedChunk.fOffset != 298 (fTarget.fOffset + (fDrawer.fPendingBase+fDrawer.fPendingCount)*fStride)) { 299 // Not contiguous, so flush and update binding to 'reservedChunk' 300 this->onFlush(); 301 fDrawer.flush(); 302 303 fTarget = reservedChunk; 304 fDrawer.fPendingBase = 0; 305 fDrawer.fPendingBufferBinds = true; 306 } else { 307 fTarget.fSize += reservedChunk.fSize; 308 } 309 } 310 fNextWriter = std::move(writer); 311 } 312 append(unsigned int count)313 VertexWriter append(unsigned int count) { 314 SkASSERT(count > 0); 315 this->reserve(count); 316 317 if (!fNextWriter) SK_UNLIKELY { 318 // If the GPU mapped buffer failed, ensure we have a sufficiently large CPU address to 319 // write to so that RenderSteps don't have to worry about error handling. The Recording 320 // will fail since the map failure is tracked by BufferManager. 321 // Since one of the reasons for GPU mapping failure is that count*stride does not fit 322 // in 32-bits, we calculate the CPU-side size carefully. 323 uint64_t size = (uint64_t)count * (uint64_t)fStride; 324 if (!SkTFitsIn<size_t>(size)) { 325 sk_report_container_overflow_and_die(); 326 } 327 return VertexWriter(fDrawer.fFailureStorage.reset(size, SkAutoMalloc::kReuse_OnShrink), 328 SkTo<size_t>(size)); 329 } 330 331 SkASSERT(fReservedCount >= count); 332 fReservedCount -= count; 333 fDrawer.fPendingCount += count; 334 // Since we have a writer, we know count*stride is valid. 335 return std::exchange(fNextWriter, fNextWriter.makeOffset(count * fStride)); 336 } 337 }; 338 339 class DrawWriter::Vertices : private DrawWriter::Appender { 340 public: Vertices(DrawWriter & w)341 Vertices(DrawWriter& w) : Appender(w, Target::kVertices) { 342 w.setTemplate(w.fVertices, {}, {}, 0); 343 } 344 345 using Appender::reserve; 346 using Appender::append; 347 }; 348 349 class DrawWriter::Instances : private DrawWriter::Appender { 350 public: Instances(DrawWriter & w,BindBufferInfo vertices,BindBufferInfo indices,unsigned int vertexCount)351 Instances(DrawWriter& w, 352 BindBufferInfo vertices, 353 BindBufferInfo indices, 354 unsigned int vertexCount) 355 : Appender(w, Target::kInstances) { 356 SkASSERT(vertexCount > 0); 357 w.setTemplate(vertices, indices, w.fInstances, SkTo<int>(vertexCount)); 358 } 359 360 using Appender::reserve; 361 using Appender::append; 362 }; 363 364 template <typename VertexCountProxy> 365 class DrawWriter::DynamicInstances : private DrawWriter::Appender { 366 public: DynamicInstances(DrawWriter & w,BindBufferInfo vertices,BindBufferInfo indices)367 DynamicInstances(DrawWriter& w, 368 BindBufferInfo vertices, 369 BindBufferInfo indices) 370 : Appender(w, Target::kInstances) { 371 w.setTemplate(vertices, indices, w.fInstances, -1); 372 } 373 ~DynamicInstances()374 ~DynamicInstances() override { 375 // Persist the template count since the DrawWriter could continue batching if a new 376 // compatible DynamicInstances object is created for the next draw. 377 this->updateTemplateCount(); 378 } 379 380 using Appender::reserve; 381 382 template <typename V> append(const V & vertexCount,unsigned int instanceCount)383 VertexWriter append(const V& vertexCount, unsigned int instanceCount) { 384 VertexWriter w = this->Appender::append(instanceCount); 385 // Record index count after appending instance data in case the append triggered a flush 386 // and the max index count is reset. However, the contents of 'w' will not have been flushed 387 // so 'fProxy' will account for 'vertexCount' when it is actually drawn. 388 fProxy << vertexCount; 389 return w; 390 } 391 392 private: updateTemplateCount()393 void updateTemplateCount() { 394 const unsigned int count = static_cast<unsigned int>(fProxy); 395 fDrawer.fTemplateCount = std::min(fDrawer.fTemplateCount, -SkTo<int>(count) - 1); 396 // By resetting the proxy after updating the template count, the next batch will start over 397 // with the minimum required vertex count and grow from there. 398 fProxy = {}; 399 } 400 onFlush()401 void onFlush() override { 402 // Update the DrawWriter's template count before its flush() is invoked and the appender 403 // starts recording to a new buffer, which ensures the flush's draw call uses the most 404 // up-to-date vertex count derived from fProxy. 405 this->updateTemplateCount(); 406 } 407 408 VertexCountProxy fProxy = {}; 409 }; 410 411 } // namespace skgpu::graphite 412 413 #endif // skgpu_graphite_DrawWriter_DEFINED 414