Add support for GPU-backed buffers to SkCustomMesh.

The client passes a GrDirectContext* when creating a vertex or index
buffer. The data is copied to a GPU accessible buffer object
and the client gets a Skia object that may only be used with the GrDirectContext. The GPU backend draws directly from the buffer, thereby
avoiding the per-draw copy cost.

The underlying Ganesh object is freed in a thread-safe manner using an
existing message bus to perform a delayed unref in GrResourceCache.

Bug: skia:12720
Change-Id: If2578fbbf094874967a294a095b3bc5d7616d73a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/527918
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Brian Salomon 2022-05-05 15:31:44 -04:00 committed by SkCQ
parent e706ba895c
commit 4cf577899c
7 changed files with 264 additions and 64 deletions

View File

@ -3238,7 +3238,9 @@ generated_cc_atom(
"//include/core:SkData_hdr",
"//include/core:SkSurface_hdr",
"//include/effects:SkGradientShader_hdr",
"//include/gpu:GrDirectContext_hdr",
"//src/core:SkCanvasPriv_hdr",
"//src/core:SkCustomMeshPriv_hdr",
],
)

View File

@ -13,7 +13,9 @@
#include "include/core/SkData.h"
#include "include/core/SkSurface.h"
#include "include/effects/SkGradientShader.h"
#include "include/gpu/GrDirectContext.h"
#include "src/core/SkCanvasPriv.h"
#include "src/core/SkCustomMeshPriv.h"
#include <memory>
@ -102,31 +104,29 @@ protected:
nullptr,
2,
SkTileMode::kMirror);
fColorVB = SkCustomMesh::MakeVertexBuffer(
/*GrDirectContext*=*/nullptr,
SkData::MakeWithoutCopy(kColorQuad, sizeof(kColorQuad)));
}
// Make this one such that the data is offset into the buffer.
auto data = SkData::MakeUninitialized(sizeof(kNoColorQuad) + kNoColorOffset);
std::memcpy(SkTAddOffset<void>(data->writable_data(), kNoColorOffset),
kNoColorQuad,
sizeof(kNoColorQuad));
fNoColorVB = SkCustomMesh::MakeVertexBuffer(/*GrDirectContext*=*/nullptr, std::move(data));
DrawResult onGpuSetup(GrDirectContext* context, SkString* string) override {
this->ensureBuffers();
if (!context || context->abandoned()) {
return DrawResult::kOk;
}
fColorIndexedVB = SkCustomMesh::MakeVertexBuffer(
/*GrDirectContext*=*/nullptr,
SkData::MakeWithoutCopy(kColorIndexedQuad, sizeof(kColorIndexedQuad)));
fColorVB = SkCustomMesh::MakeVertexBuffer(context, CpuVBAsData(fColorVB));
fColorIndexedVB = SkCustomMesh::MakeVertexBuffer(context, CpuVBAsData(fColorIndexedVB));
fIB[1] = SkCustomMesh::MakeIndexBuffer (context, CpuIBAsData(fIB[0]));
if (!fColorVB || !fColorIndexedVB || !fIB[1]) {
return DrawResult::kFail;
}
return DrawResult::kOk;
}
fNoColorIndexedVB = SkCustomMesh::MakeVertexBuffer(
/*GrDirectContext*=*/nullptr,
SkData::MakeWithoutCopy(kNoColorIndexedQuad, sizeof(kNoColorIndexedQuad)));
// Also make index buffer with an offset
data = SkData::MakeUninitialized(sizeof(kIndices) + kIndexOffset);
std::memcpy(SkTAddOffset<void>(data->writable_data(), kIndexOffset),
kIndices,
sizeof(kIndices));
fIB = SkCustomMesh::MakeIndexBuffer(/*GrDirectContext*=*/nullptr, std::move(data));
void onGpuTeardown() override {
// Destroy the GPU buffers and recreate on CPU
fColorVB = nullptr;
fColorIndexedVB = nullptr;
fIB[1] = nullptr;
this->ensureBuffers();
}
SkString onShortName() override { return SkString("custommesh"); }
@ -159,30 +159,30 @@ protected:
kRect);
}
} else {
// Alternate between CPU and GPU-backend index buffers.
auto ib = (i%4 == 0) ? fIB[0] : fIB[1];
if (colors) {
cm = SkCustomMesh::MakeIndexed(fSpecWithColor,
SkCustomMesh::Mode::kTriangles,
fColorIndexedVB,
/*vertexCount=*/ 6,
/*vertexOffset=*/0,
fIB,
/*vertexCount=*/6,
kColorIndexedOffset,
std::move(ib),
/*indexCount=*/6,
kIndexOffset,
kRect);
} else {
cm = SkCustomMesh::MakeIndexed(fSpecWithNoColor,
SkCustomMesh::Mode::kTriangles,
fNoColorIndexedVB,
/*vertexCount=*/ 6,
/*vertexOffset=*/0,
fIB,
std::move(ib),
/*indexCount=*/6,
kIndexOffset,
kRect);
}
}
SkPaint paint;
paint.setColor(SK_ColorGREEN);
paint.setShader(shader ? fShader : nullptr);
@ -200,6 +200,66 @@ protected:
}
private:
static sk_sp<const SkData> CpuVBAsData(sk_sp<SkCustomMesh::VertexBuffer> buffer) {
auto vb = static_cast<SkCustomMeshPriv::VB*>(buffer.get());
SkASSERT(vb->asData());
return vb->asData();
}
static sk_sp<const SkData> CpuIBAsData(sk_sp<SkCustomMesh::IndexBuffer> buffer) {
auto ib = static_cast<SkCustomMeshPriv::IB*>(buffer.get());
SkASSERT(ib->asData());
return ib->asData();
}
void ensureBuffers() {
if (!fColorVB) {
fColorVB = SkCustomMesh::MakeVertexBuffer(
/*GrDirectContext*=*/nullptr,
SkData::MakeWithoutCopy(kColorQuad, sizeof(kColorQuad)));
}
if (!fNoColorVB) {
// Make this one such that the data is offset into the buffer.
auto data = SkData::MakeUninitialized(sizeof(kNoColorQuad) + kNoColorOffset);
std::memcpy(SkTAddOffset<void>(data->writable_data(), kNoColorOffset),
kNoColorQuad,
sizeof(kNoColorQuad));
fNoColorVB = SkCustomMesh::MakeVertexBuffer(/*GrDirectContext*=*/nullptr,
std::move(data));
}
if (!fColorIndexedVB) {
// This buffer also has an offset.
auto data = SkData::MakeUninitialized(sizeof(kColorIndexedQuad) + kColorIndexedOffset);
std::memcpy(SkTAddOffset<void>(data->writable_data(), kColorIndexedOffset),
kColorIndexedQuad,
sizeof(kColorIndexedQuad));
fColorIndexedVB = SkCustomMesh::MakeVertexBuffer(/*GrDirectContext*=*/nullptr,
std::move(data));
}
if (!fNoColorIndexedVB) {
fNoColorIndexedVB = SkCustomMesh::MakeVertexBuffer(
/*GrDirectContext*=*/nullptr,
SkData::MakeWithoutCopy(kNoColorIndexedQuad, sizeof(kNoColorIndexedQuad)));
}
if (!fIB[0]) {
// The index buffer has an offset.
auto data = SkData::MakeUninitialized(sizeof(kIndices) + kIndexOffset);
std::memcpy(SkTAddOffset<void>(data->writable_data(), kIndexOffset),
kIndices,
sizeof(kIndices));
fIB[0] = SkCustomMesh::MakeIndexBuffer(/*GrDirectContext*=*/nullptr, std::move(data));
}
if (!fIB[1]) {
// On CPU we always use the same CPU-backed index buffer.
fIB[1] = fIB[0];
}
}
struct ColorVertex {
uint32_t pad;
uint32_t brag;
@ -249,15 +309,18 @@ private:
static constexpr uint16_t kIndices[]{0, 2, 4, 2, 5, 4};
static constexpr size_t kNoColorOffset = sizeof(NoColorVertex);
static constexpr size_t kIndexOffset = 6;
// For some buffers we add an offset to ensure we're exercising drawing from mid-buffer.
static constexpr size_t kNoColorOffset = sizeof(NoColorVertex);
static constexpr size_t kColorIndexedOffset = 2*sizeof(ColorVertex);
static constexpr size_t kIndexOffset = 6;
sk_sp<SkShader> fShader;
sk_sp<SkCustomMeshSpecification> fSpecWithColor;
sk_sp<SkCustomMeshSpecification> fSpecWithNoColor;
sk_sp<SkCustomMesh::IndexBuffer> fIB;
// On GPU the first IB is a CPU buffer and the second is a GPU buffer.
sk_sp<SkCustomMesh::IndexBuffer> fIB[2];
sk_sp<SkCustomMesh::VertexBuffer> fColorVB;
sk_sp<SkCustomMesh::VertexBuffer> fNoColorVB;

View File

@ -217,18 +217,26 @@ public:
/**
* Makes an index buffer to be used with SkCustomMeshes. The SkData is used to determine the
* size and contents of the buffer.
* size and contents of the buffer. The buffer may be CPU- or GPU-backed depending on whether
* GrDirectContext* is nullptr.
*
* @param GrDirectContext* currently ignored. May be nullptr.
* @param GrDirectContext* If nullptr a CPU-backed object is returned that owns the SkData.
* Otherwise, the data is uploaded to the GPU and a GPU-backed buffer
* is returned. It may only be used to draw into SkSurfaces that
* are backed by the passed GrDirectContext.
* @param sk_sp<SkData> required. The data used to populate the buffer.
*/
static sk_sp<IndexBuffer> MakeIndexBuffer(GrDirectContext*, sk_sp<const SkData>);
/**
* Makes a vertex buffer to be used with SkCustomMeshes. The SkData is used to determine the
* size and contents of the buffer.
* size and contents of the buffer.The buffer may be CPU- or GPU-backed depending on whether
* GrDirectContext* is nullptr.
*
* @param GrDirectContext* currently ignored. May be nullptr.
* @param GrDirectContext* If nullptr a CPU-backed object is returned that owns the SkData.
* Otherwise, the data is uploaded to the GPU and a GPU-backed buffer
* is returned. It may only be used to draw into SkSurfaces that
* are backed by the passed GrDirectContext.
* @param sk_sp<SkData> required. The data used to populate the buffer.
*/
static sk_sp<VertexBuffer> MakeVertexBuffer(GrDirectContext*, sk_sp<const SkData>);

View File

@ -5566,7 +5566,12 @@ generated_cc_atom(
":SkSLTypeShared_hdr",
"//include/core:SkCustomMesh_hdr",
"//include/core:SkData_hdr",
"//include/gpu:GrDirectContext_hdr",
"//include/private/gpu/ganesh:GrTypesPriv_hdr",
"//src/gpu/ganesh:GrDirectContextPriv_hdr",
"//src/gpu/ganesh:GrGpuBuffer_hdr",
"//src/gpu/ganesh:GrResourceCache_hdr",
"//src/gpu/ganesh:GrResourceProvider_hdr",
],
)

View File

@ -384,12 +384,30 @@ SkCustomMesh::SkCustomMesh(SkCustomMesh&&) = default;
SkCustomMesh& SkCustomMesh::operator=(const SkCustomMesh&) = default;
SkCustomMesh& SkCustomMesh::operator=(SkCustomMesh&&) = default;
sk_sp<IndexBuffer> SkCustomMesh::MakeIndexBuffer(GrDirectContext*, sk_sp<const SkData> data) {
return SkCustomMeshPriv::CpuIndexBuffer::Make(std::move(data));
sk_sp<IndexBuffer> SkCustomMesh::MakeIndexBuffer(GrDirectContext* dc, sk_sp<const SkData> data) {
if (!data) {
return nullptr;
}
if (!dc) {
return SkCustomMeshPriv::CpuIndexBuffer::Make(std::move(data));
}
#if SK_SUPPORT_GPU
return SkCustomMeshPriv::GpuIndexBuffer::Make(dc, std::move(data));
#endif
return nullptr;
}
sk_sp<VertexBuffer> SkCustomMesh::MakeVertexBuffer(GrDirectContext*, sk_sp<const SkData> data) {
return SkCustomMeshPriv::CpuVertexBuffer::Make(std::move(data));
sk_sp<VertexBuffer> SkCustomMesh::MakeVertexBuffer(GrDirectContext* dc, sk_sp<const SkData> data) {
if (!data) {
return nullptr;
}
if (!dc) {
return SkCustomMeshPriv::CpuVertexBuffer::Make(std::move(data));
}
#if SK_SUPPORT_GPU
return SkCustomMeshPriv::GpuVertexBuffer::Make(dc, std::move(data));
#endif
return nullptr;
}
SkCustomMesh SkCustomMesh::Make(sk_sp<SkCustomMeshSpecification> spec,

View File

@ -15,6 +15,14 @@
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "src/core/SkSLTypeShared.h"
#if SK_SUPPORT_GPU
#include "include/gpu/GrDirectContext.h"
#include "src/gpu/ganesh/GrDirectContextPriv.h"
#include "src/gpu/ganesh/GrGpuBuffer.h"
#include "src/gpu/ganesh/GrResourceCache.h"
#include "src/gpu/ganesh/GrResourceProvider.h"
#endif
struct SkCustomMeshSpecificationPriv {
using Varying = SkCustomMeshSpecification::Varying;
using Attribute = SkCustomMeshSpecification::Attribute;
@ -91,7 +99,11 @@ struct SkCustomMeshPriv {
Buffer& operator=(const Buffer&) = delete;
virtual sk_sp<const SkData> asData() const = 0;
virtual sk_sp<const SkData> asData() const { return nullptr; }
#if SK_SUPPORT_GPU
virtual sk_sp<const GrGpuBuffer> asGpuBuffer() const { return nullptr; }
#endif
virtual size_t size() const = 0;
};
@ -116,16 +128,69 @@ struct SkCustomMeshPriv {
using CpuIndexBuffer = CpuBuffer<IB>;
using CpuVertexBuffer = CpuBuffer<VB>;
#if SK_SUPPORT_GPU
template <typename Base, GrGpuBufferType> class GpuBuffer final : public Base {
public:
GpuBuffer() = default;
~GpuBuffer() override;
static sk_sp<Base> Make(GrDirectContext*, sk_sp<const SkData>);
sk_sp<const GrGpuBuffer> asGpuBuffer() const override { return fBuffer; }
size_t size() const override { return fBuffer->size(); }
private:
sk_sp<GrGpuBuffer> fBuffer;
GrDirectContext::DirectContextID fContextID;
};
using GpuIndexBuffer = GpuBuffer<IB, GrGpuBufferType::kIndex >;
using GpuVertexBuffer = GpuBuffer<VB, GrGpuBufferType::kVertex>;
#endif // SK_SUPPORT_GPU
};
inline SkCustomMeshPriv::Buffer::~Buffer() = default;
template <typename Base>
sk_sp<Base> SkCustomMeshPriv::CpuBuffer<Base>::Make(sk_sp<const SkData> data) {
SkASSERT(data);
auto result = new CpuBuffer<Base>;
result->fData = std::move(data);
return sk_sp<Base>(result);
}
#if SK_SUPPORT_GPU
template <typename Base, GrGpuBufferType Type>
SkCustomMeshPriv::GpuBuffer<Base, Type>::~GpuBuffer() {
GrResourceCache::ReturnResourceFromThread(std::move(fBuffer), fContextID);
}
template <typename Base, GrGpuBufferType Type>
sk_sp<Base> SkCustomMeshPriv::GpuBuffer<Base, Type>::Make(GrDirectContext* dc,
sk_sp<const SkData> data) {
SkASSERT(dc);
SkASSERT(data);
sk_sp<GrGpuBuffer> buffer = dc->priv().resourceProvider()->createBuffer(
data->size(),
Type,
kStatic_GrAccessPattern,
data->data());
if (!buffer) {
return nullptr;
}
auto result = new GpuBuffer;
result->fBuffer = std::move(buffer);
result->fContextID = dc->directContextID();
return sk_sp<Base>(result);
}
#endif // SK_SUPPORT_GPU
#endif // SK_ENABLE_SKSL

View File

@ -396,6 +396,20 @@ private:
bool isFromVertices() const { return SkToBool(fVertices); }
std::tuple<sk_sp<const GrGpuBuffer>, size_t> gpuVB() const {
if (this->isFromVertices()) {
return {};
}
return {fCMData.vb->asGpuBuffer(), fCMData.voffset};
}
std::tuple<sk_sp<const GrGpuBuffer>, size_t> gpuIB() const {
if (this->isFromVertices() || !fCMData.ib) {
return {};
}
return {fCMData.ib->asGpuBuffer(), fCMData.ioffset};
}
void writeVertices(skgpu::VertexWriter& writer,
const SkCustomMeshSpecification& spec,
bool transform) const;
@ -405,9 +419,17 @@ private:
}
const uint16_t* indices() const {
return this->isFromVertices()
? fVertices->priv().indices()
: SkTAddOffset<const uint16_t>(fCMData.ib->data(), fCMData.ioffset);
if (this->isFromVertices()) {
return fVertices->priv().indices();
}
if (!fCMData.ib) {
return nullptr;
}
auto data = fCMData.ib->asData();
if (!data) {
return nullptr;
}
return SkTAddOffset<const uint16_t>(data->data(), fCMData.ioffset);
}
int indexCount() const {
@ -416,8 +438,8 @@ private:
private:
struct CMData {
sk_sp<const SkData> vb;
sk_sp<const SkData> ib;
sk_sp<const SkCustomMeshPriv::VB> vb;
sk_sp<const SkCustomMeshPriv::IB> ib;
size_t vcount = 0;
size_t icount = 0;
@ -452,9 +474,9 @@ private:
CustomMeshOp::Mesh::Mesh(const SkCustomMesh& cm) {
new (&fCMData) CMData();
fCMData.vb = static_cast<SkCustomMeshPriv::VB*>(cm.vertexBuffer().get())->asData();
fCMData.vb = sk_ref_sp(static_cast<SkCustomMeshPriv::VB*>(cm.vertexBuffer().get()));
if (cm.indexBuffer()) {
fCMData.ib = static_cast<SkCustomMeshPriv::IB*>(cm.indexBuffer().get())->asData();
fCMData.ib = sk_ref_sp(static_cast<SkCustomMeshPriv::IB*>(cm.indexBuffer().get()));
}
fCMData.vcount = cm.vertexCount();
fCMData.voffset = cm.vertexOffset();
@ -507,8 +529,11 @@ void CustomMeshOp::Mesh::writeVertices(skgpu::VertexWriter& writer,
}
}
} else {
auto vb = static_cast<const char*>(fCMData.vb->data()) + fCMData.voffset;
writer << skgpu::VertexWriter::Array(vb, spec.stride()*fCMData.vcount);
sk_sp<const SkData> data = fCMData.vb->asData();
if (data) {
auto vdata = static_cast<const char*>(data->data()) + fCMData.voffset;
writer << skgpu::VertexWriter::Array(vdata, spec.stride() * fCMData.vcount);
}
}
}
@ -706,25 +731,35 @@ void CustomMeshOp::onCreateProgramInfo(const GrCaps* caps,
void CustomMeshOp::onPrepareDraws(GrMeshDrawTarget* target) {
size_t vertexStride = fSpecification->stride();
sk_sp<const GrBuffer> vertexBuffer;
int firstVertex = 0;
skgpu::VertexWriter verts = target->makeVertexWriter(vertexStride,
fVertexCount,
&vertexBuffer,
&firstVertex);
if (!verts) {
SkDebugf("Could not allocate vertices.\n");
return;
}
int firstVertex;
std::tie(vertexBuffer, firstVertex) = fMeshes[0].gpuVB();
bool transform = fViewMatrix == SkMatrix::InvalidMatrix();
for (const auto& m : fMeshes) {
m.writeVertices(verts, *fSpecification, transform);
if (!vertexBuffer) {
skgpu::VertexWriter verts = target->makeVertexWriter(vertexStride,
fVertexCount,
&vertexBuffer,
&firstVertex);
if (!verts) {
SkDebugf("Could not allocate vertices.\n");
return;
}
bool transform = fViewMatrix == SkMatrix::InvalidMatrix();
for (const auto& m : fMeshes) {
m.writeVertices(verts, *fSpecification, transform);
}
} else {
SkASSERT(fMeshes.count() == 1);
SkASSERT(firstVertex % fSpecification->stride() == 0);
firstVertex /= fSpecification->stride();
}
sk_sp<const GrBuffer> indexBuffer;
int firstIndex = 0;
uint16_t* indices = nullptr;
if (fIndexCount) {
std::tie(indexBuffer, firstIndex) = fMeshes[0].gpuIB();
if (fIndexCount && !indexBuffer) {
uint16_t* indices = nullptr;
indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
if (!indices) {
SkDebugf("Could not allocate indices.\n");
@ -742,12 +777,16 @@ void CustomMeshOp::onPrepareDraws(GrMeshDrawTarget* target) {
}
SkASSERT(voffset == fVertexCount);
SkASSERT(ioffset == fIndexCount);
} else if (indexBuffer) {
SkASSERT(fMeshes.count() == 1);
SkASSERT(firstIndex % sizeof(uint16_t) == 0);
firstIndex /= sizeof(uint16_t);
}
SkASSERT(!fMesh);
fMesh = target->allocMesh();
if (indices) {
if (indexBuffer) {
fMesh->setIndexed(std::move(indexBuffer),
fIndexCount,
firstIndex,