[graphite] Add DrawWriter type for auto merging consecutive vertex data into draws

Change-Id: I94ded218e822fe50687e1efff7d46bcc4c1f1293
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/471780
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
This commit is contained in:
Michael Ludwig 2021-11-22 15:39:21 -05:00 committed by SkCQ
parent d7ebf8604e
commit b8e36741e0
14 changed files with 569 additions and 159 deletions

View File

@ -71,6 +71,14 @@ void CommandBuffer::bindIndexBuffer(sk_sp<Buffer> indexBuffer, size_t bufferOffs
fHasWork = true;
}
void CommandBuffer::bindDrawBuffers(BindBufferInfo vertices,
BindBufferInfo instances,
BindBufferInfo indices) {
this->bindVertexBuffers(sk_ref_sp(vertices.fBuffer), vertices.fOffset,
sk_ref_sp(instances.fBuffer), instances.fOffset);
this->bindIndexBuffer(sk_ref_sp(indices.fBuffer), indices.fOffset);
}
static bool check_max_blit_width(int widthInPixels) {
if (widthInPixels > 32767) {
SkASSERT(false); // surfaces should not be this wide anyway

View File

@ -9,6 +9,7 @@
#define skgpu_CommandBuffer_DEFINED
#include "experimental/graphite/src/DrawTypes.h"
#include "experimental/graphite/src/DrawWriter.h"
#include "include/core/SkColor.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
@ -53,7 +54,7 @@ struct RenderPassDesc {
// * input attachments
};
class CommandBuffer : public SkRefCnt {
class CommandBuffer : public SkRefCnt, private DrawDispatcher {
public:
~CommandBuffer() override {
this->releaseResources();
@ -73,9 +74,10 @@ public:
//---------------------------------------------------------------
void bindGraphicsPipeline(sk_sp<GraphicsPipeline> graphicsPipeline);
void bindUniformBuffer(UniformSlot, sk_sp<Buffer>, size_t bufferOffset);
void bindVertexBuffers(sk_sp<Buffer> vertexBuffer, size_t vertexOffset,
sk_sp<Buffer> instanceBuffer, size_t instanceOffset);
void bindIndexBuffer(sk_sp<Buffer> indexBuffer, size_t bufferOffset);
void bindDrawBuffers(BindBufferInfo vertices,
BindBufferInfo instances,
BindBufferInfo indices) final;
// TODO: do we want to handle multiple scissor rects and viewports?
void setScissor(unsigned int left, unsigned int top, unsigned int width, unsigned int height) {
@ -100,28 +102,33 @@ public:
fHasWork = true;
}
void draw(PrimitiveType type, unsigned int baseVertex, unsigned int vertexCount) {
void draw(PrimitiveType type, unsigned int baseVertex, unsigned int vertexCount) final {
this->onDraw(type, baseVertex, vertexCount);
fHasWork = true;
}
void drawIndexed(PrimitiveType type, unsigned int baseIndex, unsigned int indexCount,
unsigned int baseVertex) {
unsigned int baseVertex) final {
this->onDrawIndexed(type, baseIndex, indexCount, baseVertex);
fHasWork = true;
}
void drawInstanced(PrimitiveType type, unsigned int baseVertex, unsigned int vertexCount,
unsigned int baseInstance, unsigned int instanceCount) {
unsigned int baseInstance, unsigned int instanceCount) final {
this->onDrawInstanced(type, baseVertex, vertexCount, baseInstance, instanceCount);
fHasWork = true;
}
void drawIndexedInstanced(PrimitiveType type, unsigned int baseIndex, unsigned int indexCount,
unsigned int baseVertex, unsigned int baseInstance,
unsigned int instanceCount) {
unsigned int instanceCount) final {
this->onDrawIndexedInstanced(type, baseIndex, indexCount, baseVertex, baseInstance,
instanceCount);
fHasWork = true;
}
// When using a DrawWriter dispatching directly to a CommandBuffer, binding of pipelines and
// uniforms must be coordinated with forNewPipeline() and forDynamicStateChange(). The direct
// draw calls and vertex buffer binding calls on CB should not be intermingled with the writer.
DrawDispatcher* asDrawDispatcher() { return this; }
//---------------------------------------------------------------
// Can only be used outside renderpasses
//---------------------------------------------------------------
@ -137,6 +144,13 @@ protected:
private:
void releaseResources();
// TODO: Once all buffer use goes through the DrawBufferManager, we likely do not need to track
// refs every time a buffer is bound, since the DBM will transfer ownership for any used buffer
// to the CommandBuffer.
void bindVertexBuffers(sk_sp<Buffer> vertexBuffer, size_t vertexOffset,
sk_sp<Buffer> instanceBuffer, size_t instanceOffset);
void bindIndexBuffer(sk_sp<Buffer> indexBuffer, size_t bufferOffset);
virtual void onBeginRenderPass(const RenderPassDesc&) = 0;
virtual void onBindGraphicsPipeline(const GraphicsPipeline*) = 0;

View File

@ -84,23 +84,4 @@ void DrawList::strokePath(const Transform& localToDevice,
// fRenderStepCount += Renderer::StrokePath().numRenderSteps();
}
size_t DrawList::Draw::requiredVertexSpace(int renderStep) const {
SkASSERT(renderStep < fRenderer.numRenderSteps());
return fRenderer.steps()[renderStep]->requiredVertexSpace(fShape);
}
size_t DrawList::Draw::requiredIndexSpace(int renderStep) const {
SkASSERT(renderStep < fRenderer.numRenderSteps());
return fRenderer.steps()[renderStep]->requiredIndexSpace(fShape);
}
void DrawList::Draw::writeVertices(VertexWriter vertexWriter,
IndexWriter indexWriter,
int renderStep) const {
SkASSERT(renderStep < fRenderer.numRenderSteps());
fRenderer.steps()[renderStep]->writeVertices(std::move(vertexWriter),
std::move(indexWriter),
fShape);
}
} // namespace skgpu

View File

@ -25,9 +25,7 @@ struct SkIRect;
namespace skgpu {
struct IndexWriter;
class Renderer;
struct VertexWriter;
// TBD: If occlusion culling is eliminated as a phase, we can easily move the paint conversion
// back to Device when the command is recorded (similar to SkPaint -> GrPaint), and then
@ -216,11 +214,6 @@ private:
, fOrder(order)
, fPaintParams(paint ? skstd::optional<PaintParams>(*paint) : skstd::nullopt)
, fStrokeParams(stroke ? skstd::optional<StrokeParams>(*stroke) : skstd::nullopt) {}
size_t requiredVertexSpace(int renderStep) const;
size_t requiredIndexSpace(int renderStep) const;
void writeVertices(VertexWriter, IndexWriter, int renderStep) const;
};
// The returned Transform reference remains valid for the lifetime of the DrawList.

View File

@ -9,10 +9,12 @@
#include "experimental/graphite/include/GraphiteTypes.h"
#include "experimental/graphite/src/Buffer.h"
#include "experimental/graphite/src/ContextPriv.h"
#include "experimental/graphite/src/ContextUtils.h"
#include "experimental/graphite/src/DrawBufferManager.h"
#include "experimental/graphite/src/DrawContext.h"
#include "experimental/graphite/src/DrawList.h"
#include "experimental/graphite/src/DrawWriter.h"
#include "experimental/graphite/src/ProgramCache.h"
#include "experimental/graphite/src/Recorder.h"
#include "experimental/graphite/src/Renderer.h"
@ -78,6 +80,7 @@ public:
pipelineIndex}
, fUniformKey{geomUniformIndex, shadingUniformIndex}
, fDraw(draw) {
SkASSERT(renderStep <= draw->fRenderer.numRenderSteps());
}
bool operator<(const SortKey& k) const {
@ -88,7 +91,9 @@ public:
const DrawList::Draw* draw() const { return fDraw; }
uint32_t pipeline() const { return fPipelineKey.fPipeline; }
int renderStep() const { return static_cast<int>(fPipelineKey.fRenderStep); }
const RenderStep& renderStep() const {
return *fDraw->fRenderer.steps()[fPipelineKey.fRenderStep];
}
uint32_t geometryUniforms() const { return fUniformKey.fGeometryIndex; }
uint32_t shadingUniforms() const { return fUniformKey.fShadingIndex; }
@ -120,6 +125,40 @@ private:
static_assert(30 >= SkNextLog2_portable(Renderer::kMaxRenderSteps * DrawList::kMaxDraws));
};
class DrawPass::Drawer final : public DrawDispatcher {
public:
Drawer() {}
~Drawer() override = default;
void bindDrawBuffers(BindBufferInfo vertexAttribs,
BindBufferInfo instanceAttribs,
BindBufferInfo indices) override {
// TODO: Actually record bind vertex buffers struct into DrawPass' command list
}
void draw(PrimitiveType type, unsigned int baseVertex, unsigned int vertexCount) override {
// TODO: Actually record draw struct into DrawPass' command list
}
void drawIndexed(PrimitiveType type, unsigned int baseIndex,
unsigned int indexCount, unsigned int baseVertex) override {
// TODO: Actually record draw struct into DrawPass' command list
}
void drawInstanced(PrimitiveType type,
unsigned int baseVertex, unsigned int vertexCount,
unsigned int baseInstance, unsigned int instanceCount) override {
// TODO: Actually record draw struct into DrawPass' command list
}
void drawIndexedInstanced(PrimitiveType type,
unsigned int baseIndex, unsigned int indexCount,
unsigned int baseVertex, unsigned int baseInstance,
unsigned int instanceCount) override {
// TODO: Actually record draw struct into DrawPass' command list
}
};
///////////////////////////////////////////////////////////////////////////////////////////////////
namespace {
@ -230,79 +269,65 @@ std::unique_ptr<DrawPass> DrawPass::Make(Recorder* recorder,
DrawBufferManager* bufferMgr = recorder->drawBufferManager();
// Used to record vertex/instance data, buffer binds, and draw calls
Drawer drawer;
DrawWriter drawWriter(&drawer, bufferMgr);
// Used to track when a new pipeline or dynamic state needs recording between draw steps
uint32_t lastPipeline = 0;
uint32_t lastShadingUniforms = UniformData::kInvalidUniformID;
uint32_t lastGeometryUniforms = 0;
SkIRect lastScissor = SkIRect::MakeSize(target->dimensions());
Buffer* lastBoundVertexBuffer = nullptr;
Buffer* lastBoundIndexBuffer = nullptr;
for (const SortKey& key : keys) {
const DrawList::Draw& draw = *key.draw();
int renderStep = key.renderStep();
const RenderStep& renderStep = key.renderStep();
size_t vertexSize = draw.requiredVertexSpace(renderStep);
size_t indexSize = draw.requiredIndexSpace(renderStep);
auto [vertexWriter, vertexInfo] = bufferMgr->getVertexWriter(vertexSize);
auto [indexWriter, indexInfo] = bufferMgr->getIndexWriter(indexSize);
// TODO: handle the case where we fail to get a vertex or index writer besides asserting
SkASSERT(!vertexSize || (vertexWriter && vertexInfo.fBuffer));
SkASSERT(!indexSize || (indexWriter && indexInfo.fBuffer));
draw.writeVertices(std::move(vertexWriter), std::move(indexWriter), renderStep);
const bool pipelineChange = key.pipeline() != lastPipeline;
const bool stateChange = key.geometryUniforms() != lastGeometryUniforms ||
key.shadingUniforms() != lastShadingUniforms ||
draw.fClip.scissor() != lastScissor;
if (vertexSize) {
if (lastBoundVertexBuffer != vertexInfo.fBuffer) {
// TODO: Record a vertex bind call that stores the vertexInfo.fBuffer.
}
// TODO: Store the vertexInfo.fOffset so the draw will know its vertex offset when it
// executes.
}
if (indexSize) {
if (lastBoundIndexBuffer != indexInfo.fBuffer) {
// TODO: Record a vertex bind call that stores the vertexInfo.fBuffer.
}
// TODO: Store the vertexInfo.fOffset so the draw will know its vertex offset when it
// executes.
// Update DrawWriter *before* we actually change any state so that accumulated draws from
// the previous state use the proper state.
if (pipelineChange) {
drawWriter.newPipelineState(renderStep.primitiveType(),
renderStep.vertexStride(),
renderStep.instanceStride());
} else if (stateChange) {
drawWriter.newDynamicState();
}
// TODO: Have the render step write out vertices and figure out what draw call function and
// primitive type it uses. The vertex buffer binding/offset and draw params will be examined
// to determine if the active draw can be updated to include the new vertices, or if it has
// to be ended and a new one begun for this step. In addition to checking this state, must
// also check if pipeline, uniform, scissor etc. would require the active draw to end.
//
// const RenderStep* const step = draw.fRenderer.steps()[key.renderStep()];
if (key.pipeline() != lastPipeline) {
// Make state changes before accumulating new draw data
if (pipelineChange) {
// TODO: Look up pipeline description from key's index and record binding it
lastPipeline = key.pipeline();
lastShadingUniforms = UniformData::kInvalidUniformID;
lastGeometryUniforms = 0;
}
if (key.geometryUniforms() != lastGeometryUniforms) {
// TODO: Look up uniform buffer binding info corresponding to key's index and record it
lastGeometryUniforms = key.geometryUniforms();
}
if (key.shadingUniforms() != lastShadingUniforms) {
auto ud = lookup(recorder, key.shadingUniforms());
if (stateChange) {
if (key.geometryUniforms() != lastGeometryUniforms) {
// TODO: Look up uniform buffer binding info corresponding to key's index and record
lastGeometryUniforms = key.geometryUniforms();
}
if (key.shadingUniforms() != lastShadingUniforms) {
auto ud = lookup(recorder, key.shadingUniforms());
auto [writer, bufferInfo] = bufferMgr->getUniformWriter(ud->dataSize());
writer.write(ud->data(), ud->dataSize());
// TODO: recording 'bufferInfo' somewhere to allow a later uniform bind call
auto [writer, bufferInfo] = bufferMgr->getUniformWriter(ud->dataSize());
writer.write(ud->data(), ud->dataSize());
// TODO: recording 'bufferInfo' somewhere to allow a later uniform bind call
lastShadingUniforms = key.shadingUniforms();
lastShadingUniforms = key.shadingUniforms();
}
if (draw.fClip.scissor() != lastScissor) {
// TODO: Record new scissor rectangle
}
}
if (draw.fClip.scissor() != lastScissor) {
// TODO: Record new scissor rectangle
}
// TODO: Write vertex and index data for the draw step
renderStep.writeVertices(&drawWriter, draw.fShape);
}
// if (currentDraw) {
// TODO: End the current draw if it has pending vertices
// }
// Finish recording draw calls for any collected data at the end of the loop
drawWriter.flush();
passBounds.roundOut();
SkIRect pxPassBounds = SkIRect::MakeLTRB((int) passBounds.left(), (int) passBounds.top(),

View File

@ -74,6 +74,7 @@ public:
private:
class SortKey;
class Drawer;
DrawPass(sk_sp<TextureProxy> target,
const SkIRect& bounds,

View File

@ -215,8 +215,10 @@ static constexpr inline size_t VertexAttribTypeSize(VertexAttribType type) {
* CommandBuffer.
*/
struct BindBufferInfo {
Buffer* fBuffer = nullptr;
const Buffer* fBuffer = nullptr;
size_t fOffset = 0;
operator bool() const { return SkToBool(fBuffer); }
};
}; // namespace skgpu

View File

@ -0,0 +1,164 @@
/*
* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "experimental/graphite/src/DrawWriter.h"
#include "experimental/graphite/src/DrawBufferManager.h"
#include "src/gpu/BufferWriter.h"
namespace skgpu {
DrawWriter::DrawWriter(DrawDispatcher* dispatcher, DrawBufferManager* bufferManager)
: DrawWriter(dispatcher, bufferManager, PrimitiveType::kTriangles, 0, 0) {}
DrawWriter::DrawWriter(DrawDispatcher* dispatcher,
DrawBufferManager* bufferManager,
PrimitiveType primitiveType,
size_t vertexStride,
size_t instanceStride)
: fDispatcher(dispatcher)
, fManager(bufferManager)
, fPrimitiveType(primitiveType)
, fVertexStride(vertexStride)
, fInstanceStride(instanceStride) {
SkASSERT(dispatcher && bufferManager);
}
void DrawWriter::setTemplateInternal(BindBufferInfo vertices,
BindBufferInfo indices,
unsigned int count,
bool drawPendingVertices) {
SkASSERT(!vertices || fVertexStride > 0);
if (vertices != fFixedVertexBuffer ||
indices != fFixedIndexBuffer ||
count != fFixedVertexCount) {
// Issue any accumulated data that referred to the old template.
if (drawPendingVertices) {
this->drawPendingVertices();
}
fFixedBuffersDirty = true;
fFixedVertexBuffer = vertices;
fFixedIndexBuffer = indices;
fFixedVertexCount = count;
}
}
void DrawWriter::drawInternal(BindBufferInfo instances,
unsigned int base,
unsigned int instanceCount) {
// Draw calls that are only 1 instance and have no extra instance data get routed to
// the simpler draw APIs.
// TODO: Is there any benefit to this? Does it help hint to drivers? Avoid more bugs?
// Or should we always call drawInstanced and drawIndexedInstanced?
const bool useNonInstancedDraw =
!SkToBool(instances) && base == 0 && instanceCount == 1;
SkASSERT(!useNonInstancedDraw || fInstanceStride == 0);
// Issue new buffer binds only as necessary
// TODO: Should this instead be the responsibility of the CB or DrawDispatcher to remember
// what was last bound?
if (fFixedBuffersDirty || instances != fLastInstanceBuffer) {
fDispatcher->bindDrawBuffers(fFixedVertexBuffer, instances, fFixedIndexBuffer);
fFixedBuffersDirty = false;
fLastInstanceBuffer = instances;
}
if (useNonInstancedDraw) {
if (fFixedIndexBuffer) {
// Should only get here from a direct draw, in which case base should be 0 and any
// offset needs to be embedded in the BindBufferInfo by caller.
SkASSERT(base == 0);
fDispatcher->drawIndexed(fPrimitiveType, 0, fFixedVertexCount, 0);
} else {
// 'base' offsets accumulated vertex data from another DrawWriter across a state change.
fDispatcher->draw(fPrimitiveType, base, fFixedVertexCount);
}
} else {
// 'base' offsets accumulated instance data (or is 0 for a direct instanced draw). It is
// assumed that any base vertex and index have been folded into the BindBufferInfos already.
if (fFixedIndexBuffer) {
fDispatcher->drawIndexedInstanced(fPrimitiveType, 0, fFixedVertexCount, 0,
base, instanceCount);
} else {
fDispatcher->drawInstanced(fPrimitiveType, 0, fFixedVertexCount, base, instanceCount);
}
}
}
void DrawWriter::drawPendingVertices() {
if (fPendingCount > 0) {
if (fPendingMode == VertexMode::kInstances) {
// This uses instanced draws, so 'base' will be interpreted in instance units.
this->drawInternal(fPendingAttrs, fPendingBaseVertex, fPendingCount);
} else {
// This triggers a non-instanced draw call so 'base' passed to drawInternal is
// interpreted in vertex units.
this->setTemplateInternal(fPendingAttrs, {}, fPendingCount, /*drawPending=*/false);
this->drawInternal({}, fPendingBaseVertex, 1);
}
fPendingCount = 0;
fPendingBaseVertex = 0;
fPendingAttrs = {};
}
}
VertexWriter DrawWriter::appendData(VertexMode mode, size_t stride, unsigned int count) {
if (fPendingMode != mode) {
// Switched between accumulating vertices and instances, so issue draws for the old data.
this->drawPendingVertices();
fPendingMode = mode;
}
auto [writer, nextChunk] = fManager->getVertexWriter(count * stride);
// Check if next chunk's data is contiguous with what's previously been appended
if (nextChunk.fBuffer == fPendingAttrs.fBuffer &&
fPendingAttrs.fOffset + (fPendingBaseVertex + fPendingCount) * stride
== nextChunk.fOffset) {
// It is, so the next chunk's vertices that will be written can be folded into the next draw
fPendingCount += count;
} else {
// Alignment mismatch, or the old buffer filled up
this->drawPendingVertices();
fPendingCount = count;
fPendingBaseVertex = 0;
fPendingAttrs = nextChunk;
}
return std::move(writer);
}
void DrawWriter::newDynamicState() {
// Remember where we left off after we draw, since drawPendingVertices() resets all pending data
BindBufferInfo base = fPendingAttrs;
unsigned int baseVertex = fPendingBaseVertex + fPendingCount;
// Draw anything that used the previous dynamic state
this->drawPendingVertices();
fPendingAttrs = base;
fPendingBaseVertex = baseVertex;
}
void DrawWriter::newPipelineState(PrimitiveType type,
size_t vertexStride,
size_t instanceStride) {
// Draw anything that used the previous pipeline
this->drawPendingVertices();
// For simplicity, if there's a new pipeline, just forget about any previous buffer bindings,
// in which case the new writer only needs to use the dispatcher and buffer manager.
this->setTemplateInternal({}, {}, 0, false);
fLastInstanceBuffer = {};
fPrimitiveType = type;
fVertexStride = vertexStride;
fInstanceStride = instanceStride;
}
} // namespace skgpu

View File

@ -0,0 +1,219 @@
/*
* 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

View File

@ -8,6 +8,8 @@
#ifndef skgpu_Renderer_DEFINED
#define skgpu_Renderer_DEFINED
#include "experimental/graphite/src/DrawTypes.h"
#include "include/core/SkSpan.h"
#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
@ -16,9 +18,10 @@
namespace skgpu {
struct IndexWriter;
class DrawWriter;
class ResourceProvider;
class Shape;
struct VertexWriter;
class Uniform;
class RenderStep {
public:
@ -29,22 +32,21 @@ public:
virtual bool requiresMSAA() const = 0;
virtual bool performsShading() const = 0;
virtual size_t requiredVertexSpace(const Shape&) const = 0;
virtual size_t requiredIndexSpace(const Shape&) const = 0;
virtual void writeVertices(VertexWriter, IndexWriter, const Shape&) const = 0;
// The DrawWriter is configured with the vertex and instance strides of the RenderStep, and its
// primitive type. The recorded draws will be executed with a graphics pipeline compatible with
// this RenderStep.
virtual void writeVertices(DrawWriter*, const Shape&) const = 0;
// While RenderStep does not define the full program that's run for a draw, it defines the
// entire vertex layout of the pipeline.
virtual size_t vertexStride() const = 0;
virtual size_t instanceStride() const = 0;
virtual PrimitiveType primitiveType() const = 0;
// TODO: Actual API to do things
// 1. Provide stencil settings
// 2. Provide shader key or MSL(?) for the vertex stage
// 3. Write vertex data given a Shape/Transform/Stroke info
// 4. Write uniform data given a Shape/Transform/Stroke info
// 5. Somehow specify the draw call that needs to be made, although this can't just be "record"
// the draw call, since we want multiple draws to accumulate into the same vertex buffer,
// and the final draw totals aren't known until we have done #3 for another draw and it
// requires binding a new vertex buffer/offset.
// - maybe if it just says what it's primitive type is and instanced/indexed/etc. and then
// the DrawPass building is able to track the total number of vertices/indices written for
// the draws in the batch and can handle recording the draw command itself.
// 6. Some Renderers benefit from being able to share vertices between RenderSteps. Must find a
// way to support that. It may mean that RenderSteps get state per draw.
// - Does Renderer make RenderStepFactories that create steps for each DrawList::Draw?

View File

@ -7,6 +7,7 @@
#include "experimental/graphite/src/Renderer.h"
#include "experimental/graphite/src/DrawWriter.h"
#include "experimental/graphite/src/geom/Shape.h"
#include "src/gpu/BufferWriter.h"
@ -52,7 +53,7 @@ private:
// TODO: Hand off to csmartdalton, this should roughly correspond to the fCoverBBoxProgram stage
// of skgpu::v1::PathStencilCoverOp.
class FillBoundsRenderStep : public RenderStep {
class FillBoundsRenderStep final : public RenderStep {
public:
FillBoundsRenderStep() {}
@ -64,21 +65,24 @@ public:
bool requiresMSAA() const override { return false; }
bool performsShading() const override { return true; }
size_t requiredVertexSpace(const Shape&) const override {
return 8 * sizeof(float);
PrimitiveType primitiveType() const override { return PrimitiveType::kTriangleStrip; }
size_t instanceStride() const override { return 0; }
size_t vertexStride() const override {
return VertexAttribTypeSize(VertexAttribType::kFloat2);
}
size_t requiredIndexSpace(const Shape&) const override {
return 0;
void writeVertices(DrawWriter* writer, const Shape& shape) const override {
// TODO: Need to account for the transform eventually, but that requires more plumbing
writer->appendVertices(4)
.writeQuad(VertexWriter::TriStripFromRect(shape.bounds().asSkRect()));
// TODO: triangle strip/fan is actually tricky to merge correctly, since we want strips
// for everything that was appended by the RenderStep, but no connection across RenderSteps,
// so either we need a way to end it here, or switch to instance rendering w/o instance
// data so that vertices are still clustered appripriately. But that would require updating
// the DrawWriter to support appending both vertex and instance data simultaneously, which
// would need to return 2 vertex writers?
}
void writeVertices(VertexWriter vertexWriter,
IndexWriter indexWriter,
const Shape& shape) const override {
vertexWriter.writeQuad(VertexWriter::TriStripFromRect(shape.bounds().asSkRect()));
}
private:
};

View File

@ -43,6 +43,8 @@ skia_graphite_sources = [
"$_src/DrawPass.cpp",
"$_src/DrawPass.h",
"$_src/DrawTypes.h",
"$_src/DrawWriter.cpp",
"$_src/DrawWriter.h",
"$_src/EnumBitMask.h",
"$_src/Gpu.cpp",
"$_src/Gpu.h",

View File

@ -325,6 +325,8 @@ inline IndexWriter& operator<<(IndexWriter& w, uint16_t val) {
return w;
}
inline IndexWriter& operator<<(IndexWriter& w, int val) { return (w << SkTo<uint16_t>(val)); }
///////////////////////////////////////////////////////////////////////////////////////////////////
struct UniformWriter : public BufferWriter {

View File

@ -13,6 +13,8 @@
#include "experimental/graphite/include/mtl/MtlTypes.h"
#include "experimental/graphite/src/Buffer.h"
#include "experimental/graphite/src/CommandBuffer.h"
#include "experimental/graphite/src/DrawBufferManager.h"
#include "experimental/graphite/src/DrawWriter.h"
#include "experimental/graphite/src/Gpu.h"
#include "experimental/graphite/src/GraphicsPipeline.h"
#include "experimental/graphite/src/ResourceProvider.h"
@ -66,8 +68,13 @@ DEF_GRAPHITE_TEST_FOR_CONTEXTS(CommandBufferTest, reporter, context) {
renderPassDesc.fClearColor = { 1, 0, 0, 1 }; // red
target->instantiate(gpu->resourceProvider());
DrawBufferManager bufferMgr(gpu->resourceProvider(), 4);
commandBuffer->beginRenderPass(renderPassDesc);
DrawWriter drawWriter(commandBuffer->asDrawDispatcher(), &bufferMgr);
// Shared uniform buffer
struct UniformData {
SkPoint fScale;
@ -82,19 +89,25 @@ DEF_GRAPHITE_TEST_FOR_CONTEXTS(CommandBufferTest, reporter, context) {
GraphicsPipelineDesc pipelineDesc;
pipelineDesc.setTestingOnlyShaderIndex(0);
auto graphicsPipeline = gpu->resourceProvider()->findOrCreateGraphicsPipeline(pipelineDesc);
drawWriter.newPipelineState(PrimitiveType::kTriangleStrip,
pipelineDesc.vertexStride(),
pipelineDesc.instanceStride());
commandBuffer->bindGraphicsPipeline(std::move(graphicsPipeline));
commandBuffer->draw(PrimitiveType::kTriangleStrip, 0, 4);
drawWriter.draw({}, {}, 4);
// Draw inset yellow rectangle using uniforms
pipelineDesc.setTestingOnlyShaderIndex(1);
graphicsPipeline = gpu->resourceProvider()->findOrCreateGraphicsPipeline(pipelineDesc);
drawWriter.newPipelineState(PrimitiveType::kTriangleStrip,
pipelineDesc.vertexStride(),
pipelineDesc.instanceStride());
commandBuffer->bindGraphicsPipeline(std::move(graphicsPipeline));
UniformData* uniforms = (UniformData*)uniformBuffer->map();
uniforms->fScale = SkPoint({1.8, 1.8});
uniforms->fTranslate = SkPoint({-0.9, -0.9});
uniforms->fColor = SkColors::kYellow;
commandBuffer->bindUniformBuffer(UniformSlot::kRenderStep, uniformBuffer, uniformOffset);
commandBuffer->draw(PrimitiveType::kTriangleStrip, 0, 4);
drawWriter.draw({}, {}, 4);
uniformOffset += sizeof(UniformData);
++uniforms;
@ -105,36 +118,25 @@ DEF_GRAPHITE_TEST_FOR_CONTEXTS(CommandBufferTest, reporter, context) {
};
pipelineDesc.setVertexAttributes(vertexAttributes, 1);
graphicsPipeline = gpu->resourceProvider()->findOrCreateGraphicsPipeline(pipelineDesc);
drawWriter.newPipelineState(PrimitiveType::kTriangles,
pipelineDesc.vertexStride(),
pipelineDesc.instanceStride());
commandBuffer->bindGraphicsPipeline(std::move(graphicsPipeline));
struct VertexData {
SkPoint fPosition;
};
sk_sp<Buffer> vertexBuffer = gpu->resourceProvider()->findOrCreateBuffer(
4*sizeof(VertexData), BufferType::kVertex, PrioritizeGpuReads::kNo);
sk_sp<Buffer> indexBuffer = gpu->resourceProvider()->findOrCreateBuffer(
6*sizeof(uint16_t), BufferType::kIndex, PrioritizeGpuReads::kNo);
auto vertices = (VertexData*)vertexBuffer->map();
vertices[0].fPosition = SkPoint({0.25f, 0.25f});
vertices[1].fPosition = SkPoint({0.25f, 0.75f});
vertices[2].fPosition = SkPoint({0.75f, 0.25f});
vertices[3].fPosition = SkPoint({0.75f, 0.75f});
vertexBuffer->unmap();
auto indices = (uint16_t*)indexBuffer->map();
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
indices[3] = 2;
indices[4] = 1;
indices[5] = 3;
indexBuffer->unmap();
commandBuffer->bindVertexBuffers(vertexBuffer, 0, nullptr, 0);
commandBuffer->bindIndexBuffer(indexBuffer, 0);
auto [vertexWriter, vertices] = bufferMgr.getVertexWriter(4 * pipelineDesc.vertexStride());
vertexWriter << SkPoint{0.25f, 0.25f}
<< SkPoint{0.25f, 0.75f}
<< SkPoint{0.75f, 0.25f}
<< SkPoint{0.75f, 0.75f};
auto [indexWriter, indices] = bufferMgr.getIndexWriter(6 * sizeof(uint16_t));
indexWriter << 0 << 1 << 2
<< 2 << 1 << 3;
uniforms->fScale = SkPoint({2, 2});
uniforms->fTranslate = SkPoint({-1, -1});
uniforms->fColor = SkColors::kMagenta;
commandBuffer->bindUniformBuffer(UniformSlot::kRenderStep, uniformBuffer, uniformOffset);
commandBuffer->drawIndexed(PrimitiveType::kTriangles, 0, 6, 0);
drawWriter.draw(vertices, indices, 6);
// draw rects using instance buffer
pipelineDesc.setTestingOnlyShaderIndex(3);
@ -146,31 +148,22 @@ DEF_GRAPHITE_TEST_FOR_CONTEXTS(CommandBufferTest, reporter, context) {
pipelineDesc.setVertexAttributes(nullptr, 0);
pipelineDesc.setInstanceAttributes(instanceAttributes, 3);
graphicsPipeline = gpu->resourceProvider()->findOrCreateGraphicsPipeline(pipelineDesc);
drawWriter.newPipelineState(PrimitiveType::kTriangles,
pipelineDesc.vertexStride(),
pipelineDesc.instanceStride());
commandBuffer->bindGraphicsPipeline(std::move(graphicsPipeline));
struct InstanceData {
SkPoint fPosition;
SkPoint fDims;
SkColor4f fColor;
};
sk_sp<Buffer> instanceBuffer = gpu->resourceProvider()->findOrCreateBuffer(
2*sizeof(InstanceData), BufferType::kVertex, PrioritizeGpuReads::kNo);
auto instances = (InstanceData*)instanceBuffer->map();
instances[0].fPosition = SkPoint({-0.4, -0.4});
instances[0].fDims = SkPoint({0.4, 0.4});
instances[0].fColor = SkColors::kGreen;
instances[1].fPosition = SkPoint({0, 0});
instances[1].fDims = SkPoint({0.25, 0.25});
instances[1].fColor = SkColors::kCyan;
instanceBuffer->unmap();
commandBuffer->bindVertexBuffers(nullptr, 0, instanceBuffer, 0);
// commandBuffer->drawInstanced(PrimitiveType::kTriangleStrip, 0, 4, 0, 2);
commandBuffer->drawIndexedInstanced(PrimitiveType::kTriangles, 0, 6, 0, 0, 2);
commandBuffer->endRenderPass();
drawWriter.setInstanceTemplate({}, indices, 6);
auto instanceWriter = drawWriter.appendInstances(2);
instanceWriter << SkPoint{-0.4f, -0.4f} << SkPoint{0.4f, 0.4f} << SkColors::kGreen
<< SkPoint{0.f, 0.f} << SkPoint{0.25f, 0.25f} << SkColors::kCyan;
drawWriter.flush();
uniformBuffer->unmap();
bufferMgr.transferToCommandBuffer(commandBuffer.get());
commandBuffer->endRenderPass();
// Do readback
// TODO: add 4-byte transfer buffer alignment for Mac to Caps