blob: 4d6a4e81907ef2e3f24ef46e4a7b2641ec3cde16 [file] [log] [blame]
/*
* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef skgpu_DrawWriter_DEFINED
#define skgpu_DrawWriter_DEFINED
#include "experimental/graphite/src/DrawTypes.h"
#include "src/gpu/BufferWriter.h"
namespace skgpu {
class DrawBufferManager;
class DrawDispatcher; // Forward declaration, handles virtual dispatch of binds/draws
/**
* DrawWriter is a helper around recording draws (to a temporary buffer or directly to a
* CommandBuffer), particularly when the number of draws is not known ahead of time, or the vertex
* and instance data is computed at record time and does not have a known size.
*
* To use, construct the DrawWriter with the current pipeline layout or call newPipelineState() on
* an existing DrawWriter and then bind that matching pipeline. When other dynamic state needs to
* change between draw calls, notify the DrawWriter using newDynamicState() before recording the
* modifications. See the listing below for how to append dynamic data or draw with existing buffers
*
* CommandBuffer::draw(vertices)
* - dynamic vertex data -> DrawWriter::appendVertices(n)
* - fixed vertex data -> DrawWriter::draw(vertices, {}, vertexCount)
*
* CommandBuffer::drawIndexed(vertices, indices)
* - dynamic vertex data -> unsupported
* - fixed vertex,index data -> DrawWriter::draw(vertices, indices, indexCount)
*
* CommandBuffer::drawInstances(vertices, instances)
* - dynamic instance data + fixed vertex data ->
* DrawWriter::setInstanceTemplate(vertices, {}, vertexCount) then
* DrawWriter::appendInstances(n)
* - fixed vertex and instance data ->
* DrawWriter::setInstanceTemplate(vertices, {}, vertexCount) then
* DrawWriter::drawInstanced(instances, instanceCount)
*
* CommandBuffer::drawIndexedInstanced(vertices, indices, instances)
* - dynamic instance data + fixed vertex, index data ->
* DrawWriter::setInstanceTemplate(vertices, indices, indexCount) then
* DrawWriter::appendInstances(n)
* - fixed vertex, index, and instance data ->
* DrawWriter::setInstanceTemplate(vertices, indices, indexCount) then
* DrawWriter::drawInstanced(instances, instanceCount)
*/
class DrawWriter {
public:
// NOTE: This constructor creates a writer that has 0 vertex and instance stride, so can only
// be used to draw triangles with pipelines that rely solely on the vertex and instance ID.
DrawWriter(DrawDispatcher*, DrawBufferManager*);
DrawWriter(DrawDispatcher*, DrawBufferManager*,
PrimitiveType type, size_t vertexStride, size_t instanceStride);
// Cannot move or copy
DrawWriter(const DrawWriter&) = delete;
DrawWriter(DrawWriter&&) = delete;
// flush() should be called before the writer is destroyed
~DrawWriter() { SkASSERT(fPendingCount == 0); }
DrawBufferManager* bufferManager() { return fManager; }
// Notify the DrawWriter that dynamic state that does not affect the pipeline needs to be
// changed. This issues draw calls for pending vertex/instance data that referred to the old
// state, so this must be called *before* changing the dynamic state.
//
// This preserves the last bound buffers and accounts for any offsets using the base vertex or
// base instance passed to draw calls to avoid re-binding buffers unnecessarily.
void newDynamicState();
// Notify the DrawWriter that a new pipeline needs to be bound, providing the primitive type and
// attribute strides of that pipeline. This issues draw calls for pending data that relied on
// the old pipeline, so this must be called *before* binding the new pipeline.
void newPipelineState(PrimitiveType type, size_t vertexStride, size_t instanceStride);
// Issue draw calls for any pending vertex and instance data collected by the writer.
void flush() { this->drawPendingVertices(); }
// Collects new vertex data for a call to CommandBuffer::draw(). Automatically accumulates
// vertex data into a buffer, issuing draw and bind calls as needed when a new buffer is
// required, so that it is seamless to the caller.
//
// Since this accumulates vertex data (and does not use instances or indices), this overrides
// the instance template when finally drawn.
//
// This should not be used when the vertex stride is 0.
VertexWriter appendVertices(unsigned int numVertices) {
SkASSERT(fVertexStride > 0);
return this->appendData(VertexMode::kVertices, fVertexStride, numVertices);
}
// Collects new instance data for a call to CommandBuffer::drawInstanced() or
// drawIndexedInstanced(). The specific draw call that's issued depends on the buffers passed to
// setInstanceTemplate(). If the template has a non-null index buffer, the eventual draw calls
// correspond to drawindexedInstanced(), otherwise to drawInstanced().
//
// Like appendVertices(), this automatically manages an internal instance buffer and merges
// the appended data into as few buffer binds and draw calls as possible, while remaining
// seamless to the caller.
//
// This requires that an instance template be specified before appending instance data. However,
// the fixed vertex buffer can be null (or have a stride of 0) if the vertex shader only relies
// on the vertex ID and no other per-vertex data.
//
// This should not be used when the instance stride is 0.
VertexWriter appendInstances(unsigned int numInstances) {
SkASSERT(fInstanceStride > 0);
return this->appendData(VertexMode::kInstances, fInstanceStride, numInstances);
}
// Set the fixed vertex and index buffers referenced when appending instance data or calling
// drawIndexed(). 'count' is the number of vertices in the template, which is either the
// vertex count (when 'indices' has a null buffer), or the index count when 'indices' are
// provided.
void setInstanceTemplate(BindBufferInfo vertices, BindBufferInfo indices, unsigned int count) {
this->setTemplateInternal(vertices, indices, count, /*drawPending=*/true);
}
// Issues a draw with fully specified data. This can be used when all instance data has already
// been written to known buffers, or when the vertex shader only depends on the vertex or
// instance IDs.
//
// The specific draw call issued depends on the buffers set via 'setInstanceTemplate' and the
// 'instances' parameter. If the template has a non-null index buffer, it will use
// drawIndexedInstanced(), otherwise it will use drawInstanced().
//
// This will not merge with any already appended instance or vertex data, pending data is issued
// in its own draw call first.
void drawInstanced(BindBufferInfo instances, unsigned int count) {
this->drawPendingVertices();
this->drawInternal(instances, 0, count);
}
// Issues a non-instanced draw call with existing, fully specified data. The specific draw call
// depends on the buffers passed to this function. If a non-null index buffer is specified, it
// will use drawIndexed(), otherwise it will use the vertex-only draw().
//
// This will not merge with any existing appended instance or vertex data, which will issue it
// own draw call. This overrides what was last set for the instance template.
void draw(BindBufferInfo vertices, BindBufferInfo indices, unsigned int count) {
this->setInstanceTemplate(vertices, indices, count); // will draw pending if needed
this->drawInstanced({}, 1);
}
private:
enum class VertexMode : unsigned {
kVertices, kInstances
};
// Both of these pointers must outlive the DrawWriter.
DrawDispatcher* fDispatcher;
DrawBufferManager* fManager;
// Must be constructed to match the pipeline that's bound
PrimitiveType fPrimitiveType;
size_t fVertexStride;
size_t fInstanceStride;
// State tracking appended vertices or instances
VertexMode fPendingMode = VertexMode::kVertices;
unsigned int fPendingCount = 0; // vertex or instance count depending on mode
unsigned int fPendingBaseVertex = 0; // or instance
BindBufferInfo fPendingAttrs = {};
// State to track the instance template that is re-used across drawn instances. These are not
// yet bound if fFixedBuffersDirty is true. Non-instanced draw buffers (e.g. draw() and
// drawIndexed()) are treated as drawing one instance, with no extra instance attributes.
BindBufferInfo fFixedVertexBuffer = {};
BindBufferInfo fFixedIndexBuffer = {};
unsigned int fFixedVertexCount = 0; // or index count if fFixedIndexBuffer is non-null
bool fFixedBuffersDirty = true;
// Will either be 'fPendingAttrData' or the arg last passed to drawInstanced(), since it may
// change even if the fixed vertex and index buffers have not.
BindBufferInfo fLastInstanceBuffer = {};
VertexWriter appendData(VertexMode mode, size_t stride, unsigned int count);
void setTemplateInternal(BindBufferInfo vertices, BindBufferInfo indices,
unsigned int count, bool drawPending);
void drawInternal(BindBufferInfo instances, unsigned int base, unsigned int instanceCount);
void drawPendingVertices();
};
// Mirrors the CommandBuffer API, since a DrawWriter is meant to aggregate and then map onto
// CommandBuffer commands, although these are virtual to allow for recording to intermediate
// storage before a CommandBuffer is available.
class DrawDispatcher {
public:
virtual ~DrawDispatcher() = default;
virtual void bindDrawBuffers(BindBufferInfo vertexAttribs,
BindBufferInfo instanceAttribs,
BindBufferInfo indices) = 0;
virtual void draw(PrimitiveType type, unsigned int baseVertex, unsigned int vertexCount) = 0;
virtual void drawIndexed(PrimitiveType type, unsigned int baseIndex,
unsigned int indexCount, unsigned int baseVertex) = 0;
virtual void drawInstanced(PrimitiveType type,
unsigned int baseVertex, unsigned int vertexCount,
unsigned int baseInstance, unsigned int instanceCount) = 0;
virtual void drawIndexedInstanced(PrimitiveType type,
unsigned int baseIndex, unsigned int indexCount,
unsigned int baseVertex, unsigned int baseInstance,
unsigned int instanceCount) = 0;
};
} // namespace skgpu
#endif // skgpu_DrawWriter_DEFINED