Reland "Add support for explicit attribute offsets and strides."
This is a reland of 6927ab9311
Original change's description:
> Add support for explicit attribute offsets and strides.
>
> Previously attribute offsets were always computed based on their
> position in an attribute array and the stride was determined
> by the offset and size of the last attribute.
>
> Now a GP has the option to create attributes with explicit offsets
> and specify an explicit vertex stride. All attributes must either
> be implicit or explicit (enforced by assert).
>
> GrGeometryProcessor::AttributeSet is now responsible for handling
> implicitly determined attribute offsets and strides. The backends
> no longer compute them.
>
> Bug: skia:12720
> Change-Id: I0211673dc70d4797c2d66b2555d8f5fb430be056
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/484736
> Reviewed-by: Greg Daniel <egdaniel@google.com>
> Reviewed-by: Michael Ludwig <michaelludwig@google.com>
> Commit-Queue: Brian Salomon <bsalomon@google.com>
Bug: skia:12720
Change-Id: Ic4975b5b2e52f2d8213154da0e585eca6dfdd78d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/486098
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
This commit is contained in:
parent
f70746701c
commit
dd9ef457d9
@ -117,7 +117,7 @@ private:
|
||||
fInColor = {"inColor", kHalf4_GrVertexAttribType, kHalf4_GrSLType};
|
||||
break;
|
||||
}
|
||||
this->setVertexAttributes(&fInPosition, 2);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 2);
|
||||
}
|
||||
|
||||
Mode fMode;
|
||||
|
300
gm/attributes.cpp
Normal file
300
gm/attributes.cpp
Normal file
@ -0,0 +1,300 @@
|
||||
/*
|
||||
* 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 "gm/gm.h"
|
||||
#include "include/core/SkPoint.h"
|
||||
#include "include/core/SkRect.h"
|
||||
#include "include/gpu/GrRecordingContext.h"
|
||||
#include "src/core/SkCanvasPriv.h"
|
||||
#include "src/gpu/GrBuffer.h"
|
||||
#include "src/gpu/GrGeometryProcessor.h"
|
||||
#include "src/gpu/GrGpuBuffer.h"
|
||||
#include "src/gpu/GrOpFlushState.h"
|
||||
#include "src/gpu/GrProcessor.h"
|
||||
#include "src/gpu/GrProcessorSet.h"
|
||||
#include "src/gpu/GrProgramInfo.h"
|
||||
#include "src/gpu/GrResourceProvider.h"
|
||||
#include "src/gpu/GrShaderVar.h"
|
||||
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
|
||||
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
|
||||
#include "src/gpu/ops/GrDrawOp.h"
|
||||
#include "src/gpu/ops/GrOp.h"
|
||||
#include "src/gpu/v1/SurfaceDrawContext_v1.h"
|
||||
#include "tools/gpu/ProxyUtils.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class GrAppliedClip;
|
||||
class GrGLSLProgramDataManager;
|
||||
|
||||
namespace {
|
||||
|
||||
enum class AttrMode {
|
||||
kAuto,
|
||||
kManual,
|
||||
kWacky
|
||||
};
|
||||
|
||||
class AttributeTestProcessor : public GrGeometryProcessor {
|
||||
public:
|
||||
static GrGeometryProcessor* Make(SkArenaAlloc* arena, AttrMode mode) {
|
||||
return arena->make([&](void* ptr) { return new (ptr) AttributeTestProcessor(mode); });
|
||||
}
|
||||
|
||||
const char* name() const final { return "AttributeTestProcessor"; }
|
||||
|
||||
void addToKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {
|
||||
b->add32(static_cast<uint32_t>(fMode));
|
||||
}
|
||||
|
||||
std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const final;
|
||||
|
||||
private:
|
||||
AttributeTestProcessor(AttrMode mode)
|
||||
: GrGeometryProcessor(kAttributeTestProcessor_ClassID), fMode(mode) {
|
||||
switch (fMode) {
|
||||
case AttrMode::kAuto:
|
||||
fAttributes.emplace_back("pos", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
|
||||
fAttributes.emplace_back("color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType);
|
||||
this->setVertexAttributesWithImplicitOffsets(fAttributes.data(),
|
||||
fAttributes.size());
|
||||
break;
|
||||
case AttrMode::kManual:
|
||||
// Same result as kAuto but with explicitly specified offsets and stride.
|
||||
fAttributes.emplace_back("pos", kFloat2_GrVertexAttribType, kFloat2_GrSLType, 0);
|
||||
fAttributes.emplace_back(
|
||||
"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType, 8);
|
||||
this->setVertexAttributes(fAttributes.data(), fAttributes.size(), 12);
|
||||
break;
|
||||
case AttrMode::kWacky:
|
||||
// 0 thru 7 : float2 aliased to "pos0" and "pos1"
|
||||
// 8 thru 11: pad
|
||||
// 12 thru 15: unorm4 "color"
|
||||
// 16 thru 19: pad
|
||||
fAttributes.emplace_back("pos0", kFloat2_GrVertexAttribType, kFloat2_GrSLType, 0);
|
||||
fAttributes.emplace_back("pos1", kFloat2_GrVertexAttribType, kFloat2_GrSLType, 0);
|
||||
fAttributes.emplace_back(
|
||||
"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType, 12);
|
||||
this->setVertexAttributes(fAttributes.data(), fAttributes.size(), 20);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const AttrMode fMode;
|
||||
|
||||
std::vector<Attribute> fAttributes;
|
||||
|
||||
using INHERITED = GrGeometryProcessor;
|
||||
};
|
||||
|
||||
std::unique_ptr<GrGeometryProcessor::ProgramImpl> AttributeTestProcessor::makeProgramImpl(
|
||||
const GrShaderCaps&) const {
|
||||
class Impl : public ProgramImpl {
|
||||
public:
|
||||
void setData(const GrGLSLProgramDataManager&,
|
||||
const GrShaderCaps&,
|
||||
const GrGeometryProcessor&) override {}
|
||||
|
||||
private:
|
||||
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
|
||||
const AttributeTestProcessor& proc = args.fGeomProc.cast<AttributeTestProcessor>();
|
||||
args.fVaryingHandler->emitAttributes(proc);
|
||||
if (proc.fMode == AttrMode::kWacky) {
|
||||
args.fVertBuilder->codeAppend("float2 pos = pos0 + pos1;");
|
||||
}
|
||||
args.fFragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
|
||||
args.fVaryingHandler->addPassThroughAttribute(GrShaderVar("color", kHalf4_GrSLType),
|
||||
args.fOutputColor);
|
||||
gpArgs->fPositionVar.set(kFloat2_GrSLType, "pos");
|
||||
args.fFragBuilder->codeAppendf("const half4 %s = half4(1);", args.fOutputCoverage);
|
||||
}
|
||||
};
|
||||
|
||||
return std::make_unique<Impl>();
|
||||
}
|
||||
|
||||
class AttributeTestOp : public GrDrawOp {
|
||||
public:
|
||||
DEFINE_OP_CLASS_ID
|
||||
|
||||
static GrOp::Owner Make(GrRecordingContext* context, AttrMode mode, const SkRect& r) {
|
||||
return GrOp::Make<AttributeTestOp>(context, mode, r);
|
||||
}
|
||||
|
||||
private:
|
||||
AttributeTestOp(AttrMode mode, SkRect rect) : GrDrawOp(ClassID()), fMode(mode), fRect(rect) {
|
||||
this->setBounds(fRect, HasAABloat::kNo, IsHairline::kNo);
|
||||
}
|
||||
|
||||
const char* name() const override { return "AttributeTestOp"; }
|
||||
FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
|
||||
GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override {
|
||||
return GrProcessorSet::EmptySetAnalysis();
|
||||
}
|
||||
|
||||
GrProgramInfo* createProgramInfo(const GrCaps* caps,
|
||||
SkArenaAlloc* arena,
|
||||
const GrSurfaceProxyView& writeView,
|
||||
bool usesMSAASurface,
|
||||
GrAppliedClip&& appliedClip,
|
||||
const GrDstProxyView& dstProxyView,
|
||||
GrXferBarrierFlags renderPassXferBarriers,
|
||||
GrLoadOp colorLoadOp) const {
|
||||
GrGeometryProcessor* geomProc = AttributeTestProcessor::Make(arena, fMode);
|
||||
|
||||
return sk_gpu_test::CreateProgramInfo(caps,
|
||||
arena,
|
||||
writeView,
|
||||
usesMSAASurface,
|
||||
std::move(appliedClip),
|
||||
dstProxyView,
|
||||
geomProc,
|
||||
SkBlendMode::kSrcOver,
|
||||
GrPrimitiveType::kTriangleStrip,
|
||||
renderPassXferBarriers,
|
||||
colorLoadOp);
|
||||
}
|
||||
|
||||
GrProgramInfo* createProgramInfo(GrOpFlushState* flushState) const {
|
||||
return this->createProgramInfo(&flushState->caps(),
|
||||
flushState->allocator(),
|
||||
flushState->writeView(),
|
||||
flushState->usesMSAASurface(),
|
||||
flushState->detachAppliedClip(),
|
||||
flushState->dstProxyView(),
|
||||
flushState->renderPassBarriers(),
|
||||
flushState->colorLoadOp());
|
||||
}
|
||||
|
||||
void onPrePrepare(GrRecordingContext* context,
|
||||
const GrSurfaceProxyView& writeView,
|
||||
GrAppliedClip* clip,
|
||||
const GrDstProxyView& dstProxyView,
|
||||
GrXferBarrierFlags renderPassXferBarriers,
|
||||
GrLoadOp colorLoadOp) final {
|
||||
SkArenaAlloc* arena = context->priv().recordTimeAllocator();
|
||||
|
||||
// DMSAA is not supported on DDL.
|
||||
bool usesMSAASurface = writeView.asRenderTargetProxy()->numSamples() > 1;
|
||||
|
||||
// This is equivalent to a GrOpFlushState::detachAppliedClip
|
||||
GrAppliedClip appliedClip = clip ? std::move(*clip) : GrAppliedClip::Disabled();
|
||||
|
||||
fProgramInfo = this->createProgramInfo(context->priv().caps(),
|
||||
arena,
|
||||
writeView,
|
||||
usesMSAASurface,
|
||||
std::move(appliedClip),
|
||||
dstProxyView,
|
||||
renderPassXferBarriers,
|
||||
colorLoadOp);
|
||||
|
||||
context->priv().recordProgramInfo(fProgramInfo);
|
||||
}
|
||||
|
||||
template <typename V> void makeVB(GrOpFlushState* flushState, const SkRect rect) {
|
||||
V v[4];
|
||||
v[0].p = {rect.left() , rect.top() };
|
||||
v[1].p = {rect.right(), rect.top() };
|
||||
v[2].p = {rect.left() , rect.bottom()};
|
||||
v[3].p = {rect.right(), rect.bottom()};
|
||||
v[0].color = SK_ColorRED;
|
||||
v[1].color = SK_ColorGREEN;
|
||||
v[2].color = SK_ColorYELLOW;
|
||||
v[3].color = SK_ColorMAGENTA;
|
||||
fVertexBuffer = flushState->resourceProvider()->createBuffer(
|
||||
sizeof(v), GrGpuBufferType::kVertex, kStatic_GrAccessPattern, v);
|
||||
}
|
||||
|
||||
void onPrepare(GrOpFlushState* flushState) override {
|
||||
if (fMode == AttrMode::kWacky) {
|
||||
struct V {
|
||||
SkPoint p;
|
||||
uint32_t pad0;
|
||||
uint32_t color;
|
||||
uint32_t pad1;
|
||||
};
|
||||
SkRect rect {fRect.fLeft/2.f, fRect.fTop/2.f, fRect.fRight/2.f, fRect.fBottom/2.f};
|
||||
this->makeVB<V>(flushState, rect);
|
||||
} else {
|
||||
struct V {
|
||||
SkPoint p;
|
||||
uint32_t color;
|
||||
};
|
||||
this->makeVB<V>(flushState, fRect);
|
||||
}
|
||||
}
|
||||
|
||||
void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
|
||||
if (!fVertexBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fProgramInfo) {
|
||||
fProgramInfo = this->createProgramInfo(flushState);
|
||||
}
|
||||
|
||||
flushState->bindPipeline(*fProgramInfo, fRect);
|
||||
flushState->bindBuffers(nullptr, nullptr, std::move(fVertexBuffer));
|
||||
flushState->draw(4, 0);
|
||||
}
|
||||
|
||||
sk_sp<GrBuffer> fVertexBuffer;
|
||||
const AttrMode fMode;
|
||||
const SkRect fRect;
|
||||
|
||||
// The program info (and both the GrPipeline and GrGeometryProcessor it relies on), when
|
||||
// allocated, are allocated in either the ddl-record-time or flush-time arena. It is the
|
||||
// arena's job to free up their memory so we just have a bare programInfo pointer here. We
|
||||
// don't even store the GrPipeline and GrGeometryProcessor pointers here bc they are
|
||||
// guaranteed to have the same lifetime as the program info.
|
||||
GrProgramInfo* fProgramInfo = nullptr;
|
||||
|
||||
friend class ::GrOp; // for ctor
|
||||
|
||||
using INHERITED = GrDrawOp;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace skiagm {
|
||||
|
||||
/**
|
||||
* This is a GPU-backend specific test that exercises explicit and implicit attribute offsets and
|
||||
* strides.
|
||||
*/
|
||||
class AttributesGM : public GpuGM {
|
||||
SkString onShortName() override { return SkString("attributes"); }
|
||||
SkISize onISize() override { return {120, 340}; }
|
||||
DrawResult onDraw(GrRecordingContext*, SkCanvas*, SkString* errorMsg) override;
|
||||
};
|
||||
|
||||
DrawResult AttributesGM::onDraw(GrRecordingContext* rc, SkCanvas* canvas, SkString* errorMsg) {
|
||||
auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
|
||||
if (!sdc) {
|
||||
*errorMsg = kErrorMsg_DrawSkippedGpuOnly;
|
||||
return DrawResult::kSkip;
|
||||
}
|
||||
|
||||
sdc->clear(SK_PMColor4fBLACK);
|
||||
|
||||
// Draw the test directly to the frame buffer.
|
||||
auto r = SkRect::MakeXYWH(10, 10, 100, 100);
|
||||
for (AttrMode m : {AttrMode::kAuto, AttrMode::kManual, AttrMode::kWacky}) {
|
||||
sdc->addDrawOp(AttributeTestOp::Make(rc, m, r));
|
||||
r.offset(0, 110);
|
||||
}
|
||||
|
||||
return DrawResult::kOk;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
DEF_GM( return new AttributesGM(); )
|
||||
|
||||
} // namespace skiagm
|
@ -84,7 +84,7 @@ private:
|
||||
ClockwiseTestProcessor(bool readSkFragCoord)
|
||||
: GrGeometryProcessor(kClockwiseTestProcessor_ClassID)
|
||||
, fReadSkFragCoord(readSkFragCoord) {
|
||||
this->setVertexAttributes(&gVertex, 1);
|
||||
this->setVertexAttributesWithImplicitOffsets(&gVertex, 1);
|
||||
}
|
||||
|
||||
const bool fReadSkFragCoord;
|
||||
|
@ -77,7 +77,7 @@ private:
|
||||
FwidthSquircleTestProcessor(const SkMatrix& viewMatrix)
|
||||
: GrGeometryProcessor(kFwidthSquircleTestProcessor_ClassID)
|
||||
, fViewMatrix(viewMatrix) {
|
||||
this->setVertexAttributes(&gVertex, 1);
|
||||
this->setVertexAttributesWithImplicitOffsets(&gVertex, 1);
|
||||
}
|
||||
|
||||
const SkMatrix fViewMatrix;
|
||||
|
@ -55,7 +55,7 @@ class TessellationTestTriShader : public GrGeometryProcessor {
|
||||
public:
|
||||
TessellationTestTriShader(const SkMatrix& viewMatrix)
|
||||
: GrGeometryProcessor(kTessellationTestTriShader_ClassID), fViewMatrix(viewMatrix) {
|
||||
this->setVertexAttributes(&kPositionAttrib, 1);
|
||||
this->setVertexAttributesWithImplicitOffsets(&kPositionAttrib, 1);
|
||||
this->setWillUseTessellationShaders();
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ gm_sources = [
|
||||
"$_gm/arcto.cpp",
|
||||
"$_gm/arithmode.cpp",
|
||||
"$_gm/asyncrescaleandread.cpp",
|
||||
"$_gm/attributes.cpp",
|
||||
"$_gm/b_119394958.cpp",
|
||||
"$_gm/backdrop.cpp",
|
||||
"$_gm/backdrop_imagefilter_croprect.cpp",
|
||||
|
@ -223,7 +223,7 @@ private:
|
||||
if (fFlags & kCoverageAttribute_GPFlag) {
|
||||
fInCoverage = {"inCoverage", kFloat_GrVertexAttribType, kHalf_GrSLType};
|
||||
}
|
||||
this->setVertexAttributes(&fInPosition, 4);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 4);
|
||||
}
|
||||
|
||||
Attribute fInPosition;
|
||||
|
@ -486,3 +486,89 @@ void ProgramImpl::WriteLocalCoord(GrGLSLVertexBuilder* vertBuilder,
|
||||
&gpArgs->fLocalCoordVar,
|
||||
localMatrixUniform);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using Attribute = GrGeometryProcessor::Attribute;
|
||||
using AttributeSet = GrGeometryProcessor::AttributeSet;
|
||||
|
||||
GrGeometryProcessor::Attribute AttributeSet::Iter::operator*() const {
|
||||
if (fCurr->offset().has_value()) {
|
||||
return *fCurr;
|
||||
}
|
||||
return Attribute(fCurr->name(), fCurr->cpuType(), fCurr->gpuType(), fImplicitOffset);
|
||||
}
|
||||
|
||||
void AttributeSet::Iter::operator++() {
|
||||
if (fRemaining) {
|
||||
fRemaining--;
|
||||
fImplicitOffset += Attribute::AlignOffset(fCurr->size());
|
||||
fCurr++;
|
||||
this->skipUninitialized();
|
||||
}
|
||||
}
|
||||
|
||||
void AttributeSet::Iter::skipUninitialized() {
|
||||
if (!fRemaining) {
|
||||
fCurr = nullptr;
|
||||
} else {
|
||||
while (!fCurr->isInitialized()) {
|
||||
++fCurr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AttributeSet::initImplicit(const Attribute* attrs, int count) {
|
||||
fAttributes = attrs;
|
||||
fRawCount = count;
|
||||
fCount = 0;
|
||||
fStride = 0;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (attrs[i].isInitialized()) {
|
||||
fCount++;
|
||||
fStride += Attribute::AlignOffset(attrs[i].size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AttributeSet::initExplicit(const Attribute* attrs, int count, size_t stride) {
|
||||
fAttributes = attrs;
|
||||
fRawCount = count;
|
||||
fCount = count;
|
||||
fStride = stride;
|
||||
SkASSERT(Attribute::AlignOffset(fStride) == fStride);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
SkASSERT(attrs[i].isInitialized());
|
||||
SkASSERT(attrs[i].offset().has_value());
|
||||
SkASSERT(Attribute::AlignOffset(*attrs[i].offset()) == *attrs[i].offset());
|
||||
SkASSERT(*attrs[i].offset() + attrs[i].size() <= fStride);
|
||||
}
|
||||
}
|
||||
|
||||
void AttributeSet::addToKey(GrProcessorKeyBuilder* b) const {
|
||||
int rawCount = SkAbs32(fRawCount);
|
||||
b->addBits(16, SkToU16(this->stride()), "stride");
|
||||
b->addBits(16, rawCount, "attribute count");
|
||||
size_t implicitOffset = 0;
|
||||
for (int i = 0; i < rawCount; ++i) {
|
||||
const Attribute& attr = fAttributes[i];
|
||||
b->appendComment(attr.isInitialized() ? attr.name() : "unusedAttr");
|
||||
static_assert(kGrVertexAttribTypeCount < (1 << 8), "");
|
||||
static_assert(kGrSLTypeCount < (1 << 8), "");
|
||||
b->addBits(8, attr.isInitialized() ? attr.cpuType() : 0xff, "attrType");
|
||||
b->addBits(8 , attr.isInitialized() ? attr.gpuType() : 0xff, "attrGpuType");
|
||||
int16_t offset = -1;
|
||||
if (attr.isInitialized()) {
|
||||
if (attr.offset().has_value()) {
|
||||
offset = *attr.offset();
|
||||
} else {
|
||||
offset = implicitOffset;
|
||||
implicitOffset += Attribute::AlignOffset(attr.size());
|
||||
}
|
||||
}
|
||||
b->addBits(16, static_cast<uint16_t>(offset), "attrOffset");
|
||||
}
|
||||
}
|
||||
|
||||
AttributeSet::Iter AttributeSet::begin() const { return Iter(fAttributes, fCount); }
|
||||
AttributeSet::Iter AttributeSet::end() const { return Iter(); }
|
||||
|
@ -63,98 +63,111 @@ public:
|
||||
/** Describes a vertex or instance attribute. */
|
||||
class Attribute {
|
||||
public:
|
||||
static constexpr size_t AlignOffset(size_t offset) { return SkAlign4(offset); }
|
||||
|
||||
constexpr Attribute() = default;
|
||||
/**
|
||||
* Makes an attribute whose offset will be implicitly determined by the types and ordering
|
||||
* of an array attributes.
|
||||
*/
|
||||
constexpr Attribute(const char* name,
|
||||
GrVertexAttribType cpuType,
|
||||
GrSLType gpuType)
|
||||
: fName(name), fCPUType(cpuType), fGPUType(gpuType) {
|
||||
SkASSERT(name && gpuType != kVoid_GrSLType);
|
||||
}
|
||||
/**
|
||||
* Makes an attribute with an explicit offset.
|
||||
*/
|
||||
constexpr Attribute(const char* name,
|
||||
GrVertexAttribType cpuType,
|
||||
GrSLType gpuType,
|
||||
size_t offset)
|
||||
: fName(name), fCPUType(cpuType), fGPUType(gpuType), fOffset(SkToU32(offset)) {
|
||||
SkASSERT(AlignOffset(offset) == offset);
|
||||
SkASSERT(name && gpuType != kVoid_GrSLType);
|
||||
}
|
||||
constexpr Attribute(const Attribute&) = default;
|
||||
|
||||
Attribute& operator=(const Attribute&) = default;
|
||||
|
||||
constexpr bool isInitialized() const { return fGPUType != kVoid_GrSLType; }
|
||||
|
||||
constexpr const char* name() const { return fName; }
|
||||
constexpr const char* name() const { return fName; }
|
||||
constexpr GrVertexAttribType cpuType() const { return fCPUType; }
|
||||
constexpr GrSLType gpuType() const { return fGPUType; }
|
||||
/**
|
||||
* Returns the offset if attributes were specified with explicit offsets. Otherwise,
|
||||
* offsets (and total vertex stride) are implicitly determined from attribute order and
|
||||
* types.
|
||||
*/
|
||||
skstd::optional<size_t> offset() const {
|
||||
if (fOffset != kImplicitOffset) {
|
||||
SkASSERT(AlignOffset(fOffset) == fOffset);
|
||||
return {fOffset};
|
||||
}
|
||||
return skstd::nullopt;
|
||||
}
|
||||
|
||||
inline constexpr size_t size() const;
|
||||
constexpr size_t sizeAlign4() const { return SkAlign4(this->size()); }
|
||||
|
||||
GrShaderVar asShaderVar() const {
|
||||
return {fName, fGPUType, GrShaderVar::TypeModifier::In};
|
||||
}
|
||||
|
||||
private:
|
||||
const char* fName = nullptr;
|
||||
static constexpr uint32_t kImplicitOffset = 1; // 1 is not valid because it isn't aligned.
|
||||
|
||||
const char* fName = nullptr;
|
||||
GrVertexAttribType fCPUType = kFloat_GrVertexAttribType;
|
||||
GrSLType fGPUType = kVoid_GrSLType;
|
||||
GrSLType fGPUType = kVoid_GrSLType;
|
||||
uint32_t fOffset = kImplicitOffset;
|
||||
};
|
||||
|
||||
class Iter {
|
||||
public:
|
||||
Iter() : fCurr(nullptr), fRemaining(0) {}
|
||||
Iter(const Iter& iter) : fCurr(iter.fCurr), fRemaining(iter.fRemaining) {}
|
||||
Iter& operator= (const Iter& iter) {
|
||||
fCurr = iter.fCurr;
|
||||
fRemaining = iter.fRemaining;
|
||||
return *this;
|
||||
}
|
||||
Iter(const Attribute* attrs, int count) : fCurr(attrs), fRemaining(count) {
|
||||
this->skipUninitialized();
|
||||
}
|
||||
/**
|
||||
* A set of attributes that can iterated. The iterator handles hides two pieces of complexity:
|
||||
* 1) It skips uninitialized attributes.
|
||||
* 2) It always returns an attribute with a known offset.
|
||||
*/
|
||||
class AttributeSet {
|
||||
class Iter {
|
||||
public:
|
||||
Iter() = default;
|
||||
Iter(const Iter& iter) = default;
|
||||
Iter& operator=(const Iter& iter) = default;
|
||||
|
||||
bool operator!=(const Iter& that) const { return fCurr != that.fCurr; }
|
||||
const Attribute& operator*() const { return *fCurr; }
|
||||
void operator++() {
|
||||
if (fRemaining) {
|
||||
fRemaining--;
|
||||
fCurr++;
|
||||
Iter(const Attribute* attrs, int count) : fCurr(attrs), fRemaining(count) {
|
||||
this->skipUninitialized();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void skipUninitialized() {
|
||||
if (!fRemaining) {
|
||||
fCurr = nullptr;
|
||||
} else {
|
||||
while (!fCurr->isInitialized()) {
|
||||
++fCurr;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool operator!=(const Iter& that) const { return fCurr != that.fCurr; }
|
||||
Attribute operator*() const;
|
||||
void operator++();
|
||||
|
||||
const Attribute* fCurr;
|
||||
int fRemaining;
|
||||
};
|
||||
private:
|
||||
void skipUninitialized();
|
||||
|
||||
const Attribute* fCurr = nullptr;
|
||||
int fRemaining = 0;
|
||||
size_t fImplicitOffset = 0;
|
||||
};
|
||||
|
||||
class AttributeSet {
|
||||
public:
|
||||
Iter begin() const { return Iter(fAttributes, fCount); }
|
||||
Iter end() const { return Iter(); }
|
||||
Iter begin() const;
|
||||
Iter end() const;
|
||||
|
||||
int count() const { return fCount; }
|
||||
size_t stride() const { return fStride; }
|
||||
|
||||
// Init with implicit offsets and stride. No attributes can have a predetermined stride.
|
||||
void initImplicit(const Attribute* attrs, int count);
|
||||
// Init with explicit offsets and stride. All attributes must be initialized and have
|
||||
// an explicit offset aligned to 4 bytes and with no attribute crossing stride boundaries.
|
||||
void initExplicit(const Attribute* attrs, int count, size_t stride);
|
||||
|
||||
void addToKey(GrProcessorKeyBuilder* b) const;
|
||||
|
||||
private:
|
||||
friend class GrGeometryProcessor;
|
||||
|
||||
void init(const Attribute* attrs, int count) {
|
||||
fAttributes = attrs;
|
||||
fRawCount = count;
|
||||
fCount = 0;
|
||||
fStride = 0;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (attrs[i].isInitialized()) {
|
||||
fCount++;
|
||||
fStride += attrs[i].sizeAlign4();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Attribute* fAttributes = nullptr;
|
||||
int fRawCount = 0;
|
||||
int fCount = 0;
|
||||
@ -165,21 +178,21 @@ public:
|
||||
|
||||
int numTextureSamplers() const { return fTextureSamplerCnt; }
|
||||
const TextureSampler& textureSampler(int index) const;
|
||||
int numVertexAttributes() const { return fVertexAttributes.fCount; }
|
||||
int numVertexAttributes() const { return fVertexAttributes.count(); }
|
||||
const AttributeSet& vertexAttributes() const { return fVertexAttributes; }
|
||||
int numInstanceAttributes() const { return fInstanceAttributes.fCount; }
|
||||
int numInstanceAttributes() const { return fInstanceAttributes.count(); }
|
||||
const AttributeSet& instanceAttributes() const { return fInstanceAttributes; }
|
||||
|
||||
bool hasVertexAttributes() const { return SkToBool(fVertexAttributes.fCount); }
|
||||
bool hasInstanceAttributes() const { return SkToBool(fInstanceAttributes.fCount); }
|
||||
bool hasVertexAttributes() const { return SkToBool(fVertexAttributes.count()); }
|
||||
bool hasInstanceAttributes() const { return SkToBool(fInstanceAttributes.count()); }
|
||||
|
||||
/**
|
||||
* A common practice is to populate the the vertex/instance's memory using an implicit array of
|
||||
* structs. In this case, it is best to assert that:
|
||||
* stride == sizeof(struct)
|
||||
*/
|
||||
size_t vertexStride() const { return fVertexAttributes.fStride; }
|
||||
size_t instanceStride() const { return fInstanceAttributes.fStride; }
|
||||
size_t vertexStride() const { return fVertexAttributes.stride(); }
|
||||
size_t instanceStride() const { return fInstanceAttributes.stride(); }
|
||||
|
||||
bool willUseTessellationShaders() const {
|
||||
return fShaders & (kTessControl_GrShaderFlag | kTessEvaluation_GrShaderFlag);
|
||||
@ -200,23 +213,10 @@ public:
|
||||
virtual void addToKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const = 0;
|
||||
|
||||
void getAttributeKey(GrProcessorKeyBuilder* b) const {
|
||||
// Ensure that our CPU and GPU type fields fit together in a 32-bit value, and we never
|
||||
// collide with the "uninitialized" value.
|
||||
static_assert(kGrVertexAttribTypeCount < (1 << 8), "");
|
||||
static_assert(kGrSLTypeCount < (1 << 8), "");
|
||||
|
||||
auto add_attributes = [=](const Attribute* attrs, int attrCount) {
|
||||
for (int i = 0; i < attrCount; ++i) {
|
||||
const Attribute& attr = attrs[i];
|
||||
b->appendComment(attr.isInitialized() ? attr.name() : "unusedAttr");
|
||||
b->addBits(8, attr.isInitialized() ? attr.cpuType() : 0xff, "attrType");
|
||||
b->addBits(8, attr.isInitialized() ? attr.gpuType() : 0xff, "attrGpuType");
|
||||
}
|
||||
};
|
||||
b->add32(fVertexAttributes.fRawCount, "numVertexAttributes");
|
||||
add_attributes(fVertexAttributes.fAttributes, fVertexAttributes.fRawCount);
|
||||
b->add32(fInstanceAttributes.fRawCount, "numInstanceAttributes");
|
||||
add_attributes(fInstanceAttributes.fAttributes, fInstanceAttributes.fRawCount);
|
||||
b->appendComment("vertex attributes");
|
||||
fVertexAttributes.addToKey(b);
|
||||
b->appendComment("instance attributes");
|
||||
fInstanceAttributes.addToKey(b);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,13 +233,20 @@ protected:
|
||||
wideColor ? kFloat4_GrVertexAttribType : kUByte4_norm_GrVertexAttribType,
|
||||
kHalf4_GrSLType };
|
||||
}
|
||||
|
||||
void setVertexAttributes(const Attribute* attrs, int attrCount) {
|
||||
fVertexAttributes.init(attrs, attrCount);
|
||||
void setVertexAttributes(const Attribute* attrs, int attrCount, size_t stride) {
|
||||
fVertexAttributes.initExplicit(attrs, attrCount, stride);
|
||||
}
|
||||
void setInstanceAttributes(const Attribute* attrs, int attrCount) {
|
||||
void setInstanceAttributes(const Attribute* attrs, int attrCount, size_t stride) {
|
||||
SkASSERT(attrCount >= 0);
|
||||
fInstanceAttributes.init(attrs, attrCount);
|
||||
fInstanceAttributes.initExplicit(attrs, attrCount, stride);
|
||||
}
|
||||
|
||||
void setVertexAttributesWithImplicitOffsets(const Attribute* attrs, int attrCount) {
|
||||
fVertexAttributes.initImplicit(attrs, attrCount);
|
||||
}
|
||||
void setInstanceAttributesWithImplicitOffsets(const Attribute* attrs, int attrCount) {
|
||||
SkASSERT(attrCount >= 0);
|
||||
fInstanceAttributes.initImplicit(attrs, attrCount);
|
||||
}
|
||||
void setWillUseTessellationShaders() {
|
||||
fShaders |= kTessControl_GrShaderFlag | kTessEvaluation_GrShaderFlag;
|
||||
|
@ -32,6 +32,7 @@ public:
|
||||
enum ClassID {
|
||||
kNull_ClassID, // Reserved ID for missing (null) processors
|
||||
|
||||
kAttributeTestProcessor_ClassID,
|
||||
kBigKeyProcessor_ClassID,
|
||||
kBlendFragmentProcessor_ClassID,
|
||||
kBlockInputFragmentProcessor_ClassID,
|
||||
|
@ -245,32 +245,26 @@ static void setup_vertex_input_layout(const GrGeometryProcessor& geomProc,
|
||||
}
|
||||
|
||||
unsigned int currentAttrib = 0;
|
||||
unsigned int vertexAttributeOffset = 0;
|
||||
|
||||
for (const auto& attrib : geomProc.vertexAttributes()) {
|
||||
for (auto attrib : geomProc.vertexAttributes()) {
|
||||
// When using SPIRV-Cross it converts the location modifier in SPIRV to be
|
||||
// TEXCOORD<N> where N is the location value for eveery vertext attribute
|
||||
inputElements[currentAttrib] = { "TEXCOORD", currentAttrib,
|
||||
attrib_type_to_format(attrib.cpuType()),
|
||||
vertexSlot, vertexAttributeOffset,
|
||||
vertexSlot, SkToU32(*attrib.offset()),
|
||||
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 };
|
||||
vertexAttributeOffset += attrib.sizeAlign4();
|
||||
currentAttrib++;
|
||||
}
|
||||
SkASSERT(vertexAttributeOffset == geomProc.vertexStride());
|
||||
|
||||
unsigned int instanceAttributeOffset = 0;
|
||||
for (const auto& attrib : geomProc.instanceAttributes()) {
|
||||
for (auto attrib : geomProc.instanceAttributes()) {
|
||||
// When using SPIRV-Cross it converts the location modifier in SPIRV to be
|
||||
// TEXCOORD<N> where N is the location value for eveery vertext attribute
|
||||
inputElements[currentAttrib] = { "TEXCOORD", currentAttrib,
|
||||
attrib_type_to_format(attrib.cpuType()),
|
||||
instanceSlot, instanceAttributeOffset,
|
||||
instanceSlot, SkToU32(*attrib.offset()),
|
||||
D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 };
|
||||
instanceAttributeOffset += attrib.sizeAlign4();
|
||||
currentAttrib++;
|
||||
}
|
||||
SkASSERT(instanceAttributeOffset == geomProc.instanceStride());
|
||||
}
|
||||
|
||||
static D3D12_BLEND blend_coeff_to_d3d_blend(GrBlendCoeff coeff) {
|
||||
|
@ -338,18 +338,16 @@ sk_sp<GrDawnProgram> GrDawnProgramBuilder::Build(GrDawnGpu* gpu,
|
||||
const GrGeometryProcessor& geomProc = programInfo.geomProc();
|
||||
int i = 0;
|
||||
if (geomProc.numVertexAttributes() > 0) {
|
||||
size_t offset = 0;
|
||||
for (const auto& attrib : geomProc.vertexAttributes()) {
|
||||
for (auto attrib : geomProc.vertexAttributes()) {
|
||||
wgpu::VertexAttribute attribute;
|
||||
attribute.shaderLocation = i;
|
||||
attribute.offset = offset;
|
||||
attribute.offset = *attrib.offset();
|
||||
attribute.format = to_dawn_vertex_format(attrib.cpuType());
|
||||
vertexAttributes.push_back(attribute);
|
||||
offset += attrib.sizeAlign4();
|
||||
i++;
|
||||
}
|
||||
wgpu::VertexBufferLayout input;
|
||||
input.arrayStride = offset;
|
||||
input.arrayStride = geomProc.vertexStride();
|
||||
input.stepMode = wgpu::VertexStepMode::Vertex;
|
||||
input.attributeCount = vertexAttributes.size();
|
||||
input.attributes = &vertexAttributes.front();
|
||||
@ -357,18 +355,16 @@ sk_sp<GrDawnProgram> GrDawnProgramBuilder::Build(GrDawnGpu* gpu,
|
||||
}
|
||||
std::vector<wgpu::VertexAttribute> instanceAttributes;
|
||||
if (geomProc.numInstanceAttributes() > 0) {
|
||||
size_t offset = 0;
|
||||
for (const auto& attrib : geomProc.instanceAttributes()) {
|
||||
for (auto attrib : geomProc.instanceAttributes()) {
|
||||
wgpu::VertexAttribute attribute;
|
||||
attribute.shaderLocation = i;
|
||||
attribute.offset = offset;
|
||||
attribute.offset = *attrib.offset();
|
||||
attribute.format = to_dawn_vertex_format(attrib.cpuType());
|
||||
instanceAttributes.push_back(attribute);
|
||||
offset += attrib.sizeAlign4();
|
||||
i++;
|
||||
}
|
||||
wgpu::VertexBufferLayout input;
|
||||
input.arrayStride = offset;
|
||||
input.arrayStride = geomProc.instanceStride();
|
||||
input.stepMode = wgpu::VertexStepMode::Instance;
|
||||
input.attributeCount = instanceAttributes.size();
|
||||
input.attributes = &instanceAttributes.front();
|
||||
|
@ -175,7 +175,7 @@ GrConicEffect::GrConicEffect(const SkPMColor4f& color, const SkMatrix& viewMatri
|
||||
, fLocalMatrix(viewMatrix)
|
||||
, fUsesLocalCoords(usesLocalCoords)
|
||||
, fCoverageScale(coverage) {
|
||||
this->setVertexAttributes(kAttributes, SK_ARRAY_COUNT(kAttributes));
|
||||
this->setVertexAttributesWithImplicitOffsets(kAttributes, SK_ARRAY_COUNT(kAttributes));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@ -328,7 +328,7 @@ GrQuadEffect::GrQuadEffect(const SkPMColor4f& color, const SkMatrix& viewMatrix,
|
||||
, fLocalMatrix(localMatrix)
|
||||
, fUsesLocalCoords(usesLocalCoords)
|
||||
, fCoverageScale(coverage) {
|
||||
this->setVertexAttributes(kAttributes, SK_ARRAY_COUNT(kAttributes));
|
||||
this->setVertexAttributesWithImplicitOffsets(kAttributes, SK_ARRAY_COUNT(kAttributes));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -140,7 +140,7 @@ GrBitmapTextGeoProc::GrBitmapTextGeoProc(const GrShaderCaps& caps,
|
||||
|
||||
fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
|
||||
caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&fInPosition, 3);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
|
||||
|
||||
if (numActiveViews) {
|
||||
fAtlasDimensions = views[0].proxy()->dimensions();
|
||||
|
@ -228,7 +228,7 @@ GrDistanceFieldA8TextGeoProc::GrDistanceFieldA8TextGeoProc(const GrShaderCaps& c
|
||||
fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType };
|
||||
fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
|
||||
caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&fInPosition, 3);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
|
||||
|
||||
if (numViews) {
|
||||
fAtlasDimensions = views[0].proxy()->dimensions();
|
||||
@ -494,7 +494,7 @@ GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(const GrShaderCaps& caps,
|
||||
fInColor = MakeColorAttribute("inColor", wideColor);
|
||||
fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
|
||||
caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&fInPosition, 3);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
|
||||
|
||||
if (numViews) {
|
||||
fAtlasDimensions = views[0].proxy()->dimensions();
|
||||
@ -807,7 +807,7 @@ GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(const GrShaderCaps&
|
||||
fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
|
||||
fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
|
||||
caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&fInPosition, 3);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
|
||||
|
||||
if (numViews) {
|
||||
fAtlasDimensions = views[0].proxy()->dimensions();
|
||||
|
@ -56,7 +56,7 @@ GrRRectShadowGeoProc::GrRRectShadowGeoProc(const GrSurfaceProxyView& lutView)
|
||||
fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
|
||||
fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
|
||||
fInShadowParams = {"inShadowParams", kFloat3_GrVertexAttribType, kHalf3_GrSLType};
|
||||
this->setVertexAttributes(&fInPosition, 3);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
|
||||
|
||||
SkASSERT(lutView.proxy());
|
||||
fLUTTextureSampler.reset(GrSamplerState::Filter::kLinear, lutView.proxy()->backendFormat(),
|
||||
|
@ -123,25 +123,23 @@ void GrGLProgramBuilder::computeCountsAndStrides(GrGLuint programID,
|
||||
fInstanceAttributeCnt = geomProc.numInstanceAttributes();
|
||||
fAttributes = std::make_unique<GrGLProgram::Attribute[]>(
|
||||
fVertexAttributeCnt + fInstanceAttributeCnt);
|
||||
auto addAttr = [&](int i, const auto& a, size_t* stride) {
|
||||
auto addAttr = [&](int i, const auto& a) {
|
||||
fAttributes[i].fCPUType = a.cpuType();
|
||||
fAttributes[i].fGPUType = a.gpuType();
|
||||
fAttributes[i].fOffset = *stride;
|
||||
*stride += a.sizeAlign4();
|
||||
fAttributes[i].fOffset = *a.offset();
|
||||
fAttributes[i].fLocation = i;
|
||||
if (bindAttribLocations) {
|
||||
GL_CALL(BindAttribLocation(programID, i, a.name()));
|
||||
}
|
||||
};
|
||||
fVertexStride = 0;
|
||||
fVertexStride = geomProc.vertexStride();
|
||||
int i = 0;
|
||||
for (const auto& attr : geomProc.vertexAttributes()) {
|
||||
addAttr(i++, attr, &fVertexStride);
|
||||
for (auto attr : geomProc.vertexAttributes()) {
|
||||
addAttr(i++, attr);
|
||||
}
|
||||
SkASSERT(fVertexStride == geomProc.vertexStride());
|
||||
fInstanceStride = 0;
|
||||
for (const auto& attr : geomProc.instanceAttributes()) {
|
||||
addAttr(i++, attr, &fInstanceStride);
|
||||
fInstanceStride = geomProc.instanceStride();
|
||||
for (auto attr : geomProc.instanceAttributes()) {
|
||||
addAttr(i++, attr);
|
||||
}
|
||||
SkASSERT(fInstanceStride == geomProc.instanceStride());
|
||||
}
|
||||
@ -192,10 +190,10 @@ void GrGLProgramBuilder::storeShaderInCache(const SkSL::Program::Inputs& inputs,
|
||||
meta.fSettings = settings;
|
||||
meta.fHasCustomColorOutput = fFS.hasCustomColorOutput();
|
||||
meta.fHasSecondaryColorOutput = fFS.hasSecondaryOutput();
|
||||
for (const auto& attr : this->geometryProcessor().vertexAttributes()) {
|
||||
for (auto attr : this->geometryProcessor().vertexAttributes()) {
|
||||
meta.fAttributeNames.emplace_back(attr.name());
|
||||
}
|
||||
for (const auto& attr : this->geometryProcessor().instanceAttributes()) {
|
||||
for (auto attr : this->geometryProcessor().instanceAttributes()) {
|
||||
meta.fAttributeNames.emplace_back(attr.name());
|
||||
}
|
||||
|
||||
|
@ -58,10 +58,10 @@ void GrGLSLVaryingHandler::addVarying(const char* name, GrGLSLVarying* varying,
|
||||
}
|
||||
|
||||
void GrGLSLVaryingHandler::emitAttributes(const GrGeometryProcessor& gp) {
|
||||
for (const auto& attr : gp.vertexAttributes()) {
|
||||
for (auto attr : gp.vertexAttributes()) {
|
||||
this->addAttribute(attr.asShaderVar());
|
||||
}
|
||||
for (const auto& attr : gp.instanceAttributes()) {
|
||||
for (auto attr : gp.instanceAttributes()) {
|
||||
this->addAttribute(attr.asShaderVar());
|
||||
}
|
||||
}
|
||||
|
@ -199,33 +199,30 @@ static MTLVertexDescriptor* create_vertex_descriptor(const GrGeometryProcessor&
|
||||
if (writer) {
|
||||
writer->writeInt(vertexAttributeCount);
|
||||
}
|
||||
size_t vertexAttributeOffset = 0;
|
||||
for (const auto& attribute : geomProc.vertexAttributes()) {
|
||||
for (auto attribute : geomProc.vertexAttributes()) {
|
||||
MTLVertexAttributeDescriptor* mtlAttribute = vertexDescriptor.attributes[attributeIndex];
|
||||
MTLVertexFormat format = attribute_type_to_mtlformat(attribute.cpuType());
|
||||
SkASSERT(MTLVertexFormatInvalid != format);
|
||||
mtlAttribute.format = format;
|
||||
mtlAttribute.offset = vertexAttributeOffset;
|
||||
mtlAttribute.offset = *attribute.offset();
|
||||
mtlAttribute.bufferIndex = vertexBinding;
|
||||
if (writer) {
|
||||
writer->writeInt(format);
|
||||
writer->writeUInt(vertexAttributeOffset);
|
||||
writer->writeUInt(*attribute.offset());
|
||||
writer->writeUInt(vertexBinding);
|
||||
}
|
||||
|
||||
vertexAttributeOffset += attribute.sizeAlign4();
|
||||
attributeIndex++;
|
||||
}
|
||||
SkASSERT(vertexAttributeOffset == geomProc.vertexStride());
|
||||
|
||||
if (vertexAttributeCount) {
|
||||
MTLVertexBufferLayoutDescriptor* vertexBufferLayout =
|
||||
vertexDescriptor.layouts[vertexBinding];
|
||||
vertexBufferLayout.stepFunction = MTLVertexStepFunctionPerVertex;
|
||||
vertexBufferLayout.stepRate = 1;
|
||||
vertexBufferLayout.stride = vertexAttributeOffset;
|
||||
vertexBufferLayout.stride = geomProc.vertexStride();
|
||||
if (writer) {
|
||||
writer->writeUInt(vertexAttributeOffset);
|
||||
writer->writeUInt(geomProc.vertexStride());
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,33 +230,30 @@ static MTLVertexDescriptor* create_vertex_descriptor(const GrGeometryProcessor&
|
||||
if (writer) {
|
||||
writer->writeInt(instanceAttributeCount);
|
||||
}
|
||||
size_t instanceAttributeOffset = 0;
|
||||
for (const auto& attribute : geomProc.instanceAttributes()) {
|
||||
for (auto attribute : geomProc.instanceAttributes()) {
|
||||
MTLVertexAttributeDescriptor* mtlAttribute = vertexDescriptor.attributes[attributeIndex];
|
||||
MTLVertexFormat format = attribute_type_to_mtlformat(attribute.cpuType());
|
||||
SkASSERT(MTLVertexFormatInvalid != format);
|
||||
mtlAttribute.format = format;
|
||||
mtlAttribute.offset = instanceAttributeOffset;
|
||||
mtlAttribute.offset = *attribute.offset();
|
||||
mtlAttribute.bufferIndex = instanceBinding;
|
||||
if (writer) {
|
||||
writer->writeInt(format);
|
||||
writer->writeUInt(instanceAttributeOffset);
|
||||
writer->writeUInt(*attribute.offset());
|
||||
writer->writeUInt(instanceBinding);
|
||||
}
|
||||
|
||||
instanceAttributeOffset += attribute.sizeAlign4();
|
||||
attributeIndex++;
|
||||
}
|
||||
SkASSERT(instanceAttributeOffset == geomProc.instanceStride());
|
||||
|
||||
if (instanceAttributeCount) {
|
||||
MTLVertexBufferLayoutDescriptor* instanceBufferLayout =
|
||||
vertexDescriptor.layouts[instanceBinding];
|
||||
instanceBufferLayout.stepFunction = MTLVertexStepFunctionPerInstance;
|
||||
instanceBufferLayout.stepRate = 1;
|
||||
instanceBufferLayout.stride = instanceAttributeOffset;
|
||||
instanceBufferLayout.stride = geomProc.instanceStride();
|
||||
if (writer) {
|
||||
writer->writeUInt(instanceAttributeOffset);
|
||||
writer->writeUInt(geomProc.instanceStride());
|
||||
}
|
||||
}
|
||||
return vertexDescriptor;
|
||||
|
@ -577,7 +577,7 @@ private:
|
||||
fInColor = MakeColorAttribute("inColor", wideColor);
|
||||
// GL on iOS 14 needs more precision for the quadedge attributes
|
||||
fInQuadEdge = {"inQuadEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
|
||||
this->setVertexAttributes(&fInPosition, 3);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
|
||||
}
|
||||
|
||||
Attribute fInPosition;
|
||||
|
@ -867,7 +867,7 @@ DashingCircleEffect::DashingCircleEffect(const SkPMColor4f& color,
|
||||
fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
|
||||
fInDashParams = {"inDashParams", kFloat3_GrVertexAttribType, kHalf3_GrSLType};
|
||||
fInCircleParams = {"inCircleParams", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
|
||||
this->setVertexAttributes(&fInPosition, 3);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
|
||||
}
|
||||
|
||||
GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DashingCircleEffect);
|
||||
@ -1081,7 +1081,7 @@ DashingLineEffect::DashingLineEffect(const SkPMColor4f& color,
|
||||
fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
|
||||
fInDashParams = {"inDashParams", kFloat3_GrVertexAttribType, kHalf3_GrSLType};
|
||||
fInRect = {"inRect", kFloat4_GrVertexAttribType, kHalf4_GrSLType};
|
||||
this->setVertexAttributes(&fInPosition, 3);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
|
||||
}
|
||||
|
||||
GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DashingLineEffect);
|
||||
|
@ -33,7 +33,7 @@ public:
|
||||
if (!shaderCaps.vertexIDSupport()) {
|
||||
constexpr static Attribute kUnitCoordAttrib(
|
||||
"unitCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
|
||||
this->setVertexAttributes(&kUnitCoordAttrib, 1);
|
||||
this->setVertexAttributesWithImplicitOffsets(&kUnitCoordAttrib, 1);
|
||||
}
|
||||
fAttribs.emplace_back("fillBounds", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
|
||||
if (fUsesLocalCoords) {
|
||||
@ -44,7 +44,7 @@ public:
|
||||
fAttribs.emplace_back("color", kFloat4_GrVertexAttribType, kHalf4_GrSLType);
|
||||
fAtlasHelper->appendInstanceAttribs(&fAttribs);
|
||||
SkASSERT(fAttribs.count() <= kMaxInstanceAttribs);
|
||||
this->setInstanceAttributes(fAttribs.data(), fAttribs.count());
|
||||
this->setInstanceAttributesWithImplicitOffsets(fAttribs.data(), fAttribs.count());
|
||||
this->setTextureSamplerCnt(1);
|
||||
}
|
||||
|
||||
|
@ -174,7 +174,7 @@ private:
|
||||
? Attribute{"inLocalCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType}
|
||||
: missingAttr);
|
||||
|
||||
this->setVertexAttributes(fAttributes.data(), fAttributes.size());
|
||||
this->setVertexAttributesWithImplicitOffsets(fAttributes.data(), fAttributes.size());
|
||||
}
|
||||
|
||||
enum {
|
||||
|
@ -353,7 +353,8 @@ private:
|
||||
Processor(GrAAType aaType, ProcessorFlags flags)
|
||||
: GrGeometryProcessor(kGrFillRRectOp_Processor_ClassID)
|
||||
, fFlags(flags) {
|
||||
this->setVertexAttributes(kVertexAttribs, SK_ARRAY_COUNT(kVertexAttribs));
|
||||
this->setVertexAttributesWithImplicitOffsets(kVertexAttribs,
|
||||
SK_ARRAY_COUNT(kVertexAttribs));
|
||||
|
||||
fInstanceAttribs.emplace_back("skew", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
|
||||
fInstanceAttribs.emplace_back("translate", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
|
||||
@ -366,7 +367,8 @@ private:
|
||||
"local_rect", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
|
||||
}
|
||||
SkASSERT(fInstanceAttribs.count() <= kMaxInstanceAttribs);
|
||||
this->setInstanceAttributes(fInstanceAttribs.begin(), fInstanceAttribs.count());
|
||||
this->setInstanceAttributesWithImplicitOffsets(fInstanceAttribs.begin(),
|
||||
fInstanceAttribs.count());
|
||||
}
|
||||
|
||||
inline static constexpr Attribute kVertexAttribs[] = {
|
||||
|
@ -118,7 +118,7 @@ private:
|
||||
fInRoundCapCenters =
|
||||
{"inRoundCapCenters", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
|
||||
}
|
||||
this->setVertexAttributes(&fInPosition, 7);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 7);
|
||||
}
|
||||
|
||||
class Impl : public ProgramImpl {
|
||||
@ -298,7 +298,7 @@ private:
|
||||
fInColor = MakeColorAttribute("inColor", wideColor);
|
||||
fInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
|
||||
fInDashParams = {"inDashParams", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
|
||||
this->setVertexAttributes(&fInPosition, 4);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 4);
|
||||
}
|
||||
|
||||
class Impl : public ProgramImpl {
|
||||
@ -562,7 +562,7 @@ private:
|
||||
fInEllipseOffset = {"inEllipseOffset", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
|
||||
}
|
||||
fInEllipseRadii = {"inEllipseRadii", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
|
||||
this->setVertexAttributes(&fInPosition, 4);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 4);
|
||||
}
|
||||
|
||||
class Impl : public ProgramImpl {
|
||||
@ -761,7 +761,7 @@ private:
|
||||
kFloat2_GrSLType};
|
||||
}
|
||||
fInEllipseOffsets1 = {"inEllipseOffsets1", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&fInPosition, 4);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 4);
|
||||
}
|
||||
|
||||
class Impl : public ProgramImpl {
|
||||
|
@ -111,7 +111,7 @@ private:
|
||||
fInTextureCoords = {"textureCoords", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
|
||||
fInTextureDomain = {"textureDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
|
||||
fInColor = MakeColorAttribute("color", wideColor);
|
||||
this->setVertexAttributes(&fInPosition, 4);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 4);
|
||||
}
|
||||
|
||||
const TextureSampler& onTextureSampler(int) const override { return fSampler; }
|
||||
|
@ -36,13 +36,14 @@ public:
|
||||
// each patch that explicitly tells the shader what type of curve it is.
|
||||
fInstanceAttribs.emplace_back("curveType", kFloat_GrVertexAttribType, kFloat_GrSLType);
|
||||
}
|
||||
this->setInstanceAttributes(fInstanceAttribs.data(), fInstanceAttribs.count());
|
||||
this->setInstanceAttributesWithImplicitOffsets(fInstanceAttribs.data(),
|
||||
fInstanceAttribs.count());
|
||||
SkASSERT(fInstanceAttribs.count() <= kMaxInstanceAttribCount);
|
||||
|
||||
if (!shaderCaps.vertexIDSupport()) {
|
||||
constexpr static Attribute kVertexIdxAttrib("vertexidx", kFloat_GrVertexAttribType,
|
||||
kFloat_GrSLType);
|
||||
this->setVertexAttributes(&kVertexIdxAttrib, 1);
|
||||
this->setVertexAttributesWithImplicitOffsets(&kVertexIdxAttrib, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,14 +36,15 @@ public:
|
||||
if (!shaderCaps.vertexIDSupport()) {
|
||||
constexpr static Attribute kUnitCoordAttrib("unitCoord", kFloat2_GrVertexAttribType,
|
||||
kFloat2_GrSLType);
|
||||
this->setVertexAttributes(&kUnitCoordAttrib, 1);
|
||||
this->setVertexAttributesWithImplicitOffsets(&kUnitCoordAttrib, 1);
|
||||
}
|
||||
constexpr static Attribute kInstanceAttribs[] = {
|
||||
{"matrix2d", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
|
||||
{"translate", kFloat2_GrVertexAttribType, kFloat2_GrSLType},
|
||||
{"pathBounds", kFloat4_GrVertexAttribType, kFloat4_GrSLType}
|
||||
};
|
||||
this->setInstanceAttributes(kInstanceAttribs, SK_ARRAY_COUNT(kInstanceAttribs));
|
||||
this->setInstanceAttributesWithImplicitOffsets(kInstanceAttribs,
|
||||
SK_ARRAY_COUNT(kInstanceAttribs));
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -894,7 +894,7 @@ private:
|
||||
fTexSubset = {"texSubset", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
|
||||
}
|
||||
|
||||
this->setVertexAttributes(&fPosition, 6);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fPosition, 6);
|
||||
}
|
||||
|
||||
const TextureSampler& onTextureSampler(int) const override { return fSampler; }
|
||||
|
@ -25,7 +25,7 @@ public:
|
||||
PatchAttribs::kNone) {
|
||||
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
|
||||
kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&kInputPointAttrib, 1);
|
||||
this->setVertexAttributesWithImplicitOffsets(&kInputPointAttrib, 1);
|
||||
}
|
||||
|
||||
int maxTessellationSegments(const GrShaderCaps&) const override { SkUNREACHABLE; }
|
||||
|
@ -39,7 +39,7 @@ public:
|
||||
GrPrimitiveType::kPatches, 5, viewMatrix, color, attribs) {
|
||||
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
|
||||
kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&kInputPointAttrib, 1);
|
||||
this->setVertexAttributesWithImplicitOffsets(&kInputPointAttrib, 1);
|
||||
SkASSERT(this->vertexStride() * 5 ==
|
||||
sizeof(SkPoint) * 4 + skgpu::PatchAttribsStride(fAttribs));
|
||||
}
|
||||
@ -180,7 +180,7 @@ public:
|
||||
attribs) {
|
||||
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
|
||||
kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&kInputPointAttrib, 1);
|
||||
this->setVertexAttributesWithImplicitOffsets(&kInputPointAttrib, 1);
|
||||
SkASSERT(this->vertexStride() * 4 ==
|
||||
sizeof(SkPoint) * 4 + skgpu::PatchAttribsStride(fAttribs));
|
||||
}
|
||||
|
@ -56,14 +56,15 @@ public:
|
||||
// each patch that explicitly tells the shader what type of curve it is.
|
||||
fInstanceAttribs.emplace_back("curveType", kFloat_GrVertexAttribType, kFloat_GrSLType);
|
||||
}
|
||||
this->setInstanceAttributes(fInstanceAttribs.data(), fInstanceAttribs.count());
|
||||
this->setInstanceAttributesWithImplicitOffsets(fInstanceAttribs.data(),
|
||||
fInstanceAttribs.count());
|
||||
SkASSERT(fInstanceAttribs.count() <= kMaxInstanceAttribCount);
|
||||
SkASSERT(this->instanceStride() ==
|
||||
sizeof(SkPoint) * 4 + skgpu::PatchAttribsStride(fAttribs));
|
||||
|
||||
constexpr static Attribute kVertexAttrib("resolveLevel_and_idx", kFloat2_GrVertexAttribType,
|
||||
kFloat2_GrSLType);
|
||||
this->setVertexAttributes(&kVertexAttrib, 1);
|
||||
this->setVertexAttributesWithImplicitOffsets(&kVertexAttrib, 1);
|
||||
}
|
||||
|
||||
int maxTessellationSegments(const GrShaderCaps&) const override {
|
||||
|
@ -91,15 +91,15 @@ GrStrokeTessellationShader::GrStrokeTessellationShader(const GrShaderCaps& shade
|
||||
fAttribs.emplace_back("curveTypeAttr", kFloat_GrVertexAttribType, kFloat_GrSLType);
|
||||
}
|
||||
if (fMode == Mode::kHardwareTessellation) {
|
||||
this->setVertexAttributes(fAttribs.data(), fAttribs.count());
|
||||
this->setVertexAttributesWithImplicitOffsets(fAttribs.data(), fAttribs.count());
|
||||
SkASSERT(this->vertexStride() == sizeof(SkPoint) * 5 + PatchAttribsStride(fPatchAttribs));
|
||||
} else {
|
||||
this->setInstanceAttributes(fAttribs.data(), fAttribs.count());
|
||||
this->setInstanceAttributesWithImplicitOffsets(fAttribs.data(), fAttribs.count());
|
||||
SkASSERT(this->instanceStride() == sizeof(SkPoint) * 5 + PatchAttribsStride(fPatchAttribs));
|
||||
if (!shaderCaps.vertexIDSupport()) {
|
||||
constexpr static Attribute kVertexAttrib("edgeID", kFloat_GrVertexAttribType,
|
||||
kFloat_GrSLType);
|
||||
this->setVertexAttributes(&kVertexAttrib, 1);
|
||||
this->setVertexAttributesWithImplicitOffsets(&kVertexAttrib, 1);
|
||||
}
|
||||
}
|
||||
SkASSERT(fAttribs.count() <= kMaxAttribCount);
|
||||
|
@ -100,39 +100,33 @@ static void setup_vertex_input_state(
|
||||
|
||||
// setup attribute descriptions
|
||||
int attribIndex = 0;
|
||||
size_t vertexAttributeOffset = 0;
|
||||
for (const auto& attrib : vertexAttribs) {
|
||||
for (auto attrib : vertexAttribs) {
|
||||
VkVertexInputAttributeDescription& vkAttrib = attributeDesc[attribIndex];
|
||||
vkAttrib.location = attribIndex++; // for now assume location = attribIndex
|
||||
vkAttrib.binding = vertexBinding;
|
||||
vkAttrib.format = attrib_type_to_vkformat(attrib.cpuType());
|
||||
vkAttrib.offset = vertexAttributeOffset;
|
||||
vertexAttributeOffset += attrib.sizeAlign4();
|
||||
vkAttrib.offset = *attrib.offset();
|
||||
}
|
||||
SkASSERT(vertexAttributeOffset == vertexAttribs.stride());
|
||||
|
||||
size_t instanceAttributeOffset = 0;
|
||||
for (const auto& attrib : instanceAttribs) {
|
||||
for (auto attrib : instanceAttribs) {
|
||||
VkVertexInputAttributeDescription& vkAttrib = attributeDesc[attribIndex];
|
||||
vkAttrib.location = attribIndex++; // for now assume location = attribIndex
|
||||
vkAttrib.binding = instanceBinding;
|
||||
vkAttrib.format = attrib_type_to_vkformat(attrib.cpuType());
|
||||
vkAttrib.offset = instanceAttributeOffset;
|
||||
instanceAttributeOffset += attrib.sizeAlign4();
|
||||
vkAttrib.offset = *attrib.offset();
|
||||
}
|
||||
SkASSERT(instanceAttributeOffset == instanceAttribs.stride());
|
||||
|
||||
if (vaCount) {
|
||||
bindingDescs->push_back() = {
|
||||
vertexBinding,
|
||||
(uint32_t) vertexAttributeOffset,
|
||||
(uint32_t) vertexAttribs.stride(),
|
||||
VK_VERTEX_INPUT_RATE_VERTEX
|
||||
};
|
||||
}
|
||||
if (iaCount) {
|
||||
bindingDescs->push_back() = {
|
||||
instanceBinding,
|
||||
(uint32_t) instanceAttributeOffset,
|
||||
(uint32_t) instanceAttribs.stride(),
|
||||
VK_VERTEX_INPUT_RATE_INSTANCE
|
||||
};
|
||||
}
|
||||
|
@ -469,15 +469,15 @@ private:
|
||||
if (instanced) {
|
||||
fInstanceLocation = {"location", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
|
||||
fInstanceColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
|
||||
this->setInstanceAttributes(&fInstanceLocation, 2);
|
||||
this->setInstanceAttributesWithImplicitOffsets(&fInstanceLocation, 2);
|
||||
if (hasVertexBuffer) {
|
||||
fVertexPosition = {"vertex", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
|
||||
this->setVertexAttributes(&fVertexPosition, 1);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fVertexPosition, 1);
|
||||
}
|
||||
} else {
|
||||
fVertexPosition = {"vertex", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
|
||||
fVertexColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
|
||||
this->setVertexAttributes(&fVertexPosition, 2);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fVertexPosition, 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ public:
|
||||
|
||||
private:
|
||||
PipelineDynamicStateTestProcessor() : INHERITED(kGrPipelineDynamicStateTestProcessor_ClassID) {
|
||||
this->setVertexAttributes(kAttributes, SK_ARRAY_COUNT(kAttributes));
|
||||
this->setVertexAttributesWithImplicitOffsets(kAttributes, SK_ARRAY_COUNT(kAttributes));
|
||||
}
|
||||
|
||||
const Attribute& inVertex() const { return kAttributes[0]; }
|
||||
|
@ -115,7 +115,7 @@ private:
|
||||
kFloat2_GrSLType};
|
||||
}
|
||||
}
|
||||
this->setVertexAttributes(fAttributes.get(), numAttribs);
|
||||
this->setVertexAttributesWithImplicitOffsets(fAttributes.get(), numAttribs);
|
||||
}
|
||||
|
||||
int fNumAttribs;
|
||||
|
@ -26,7 +26,7 @@ public:
|
||||
GP(const SkMatrix& localMatrix, bool wideColor)
|
||||
: GrGeometryProcessor(kTestRectOp_ClassID), fLocalMatrix(localMatrix) {
|
||||
fInColor = MakeColorAttribute("color", wideColor);
|
||||
this->setVertexAttributes(&fInPosition, 3);
|
||||
this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
|
||||
}
|
||||
|
||||
const char* name() const override { return "TestRectOp::GP"; }
|
||||
|
Loading…
Reference in New Issue
Block a user