skia2/tests/graphite/CommandBufferTest.cpp
Michael Ludwig b920095295 [graphite] Use RAII helper structs to configure append modes in DrawWriter
Move to RAII structs wrapping DrawWriter to control the append mode.
This allows each struct to define any extra template parameters/buffers
needed for the appended data, and expose only the appending API that
makes sense.

In follow up CLs, the RAII structs make it easy to support reserving
and returning vertex data, and add new appending modes.

Cq-Include-Trybots: luci.skia.skia.primary:Test-Mac11-Clang-MacMini9.1-GPU-AppleM1-arm64-Release-All-Graphite,Test-Mac11-Clang-MacMini9.1-GPU-AppleM1-arm64-Debug-All-ASAN_Graphite,Build-Mac-Clang-arm64-Release-iOS_Graphite,Build-Mac-Clang-arm64-Release-Graphite,Build-Mac-Clang-arm64-Debug-iOS_Graphite,Build-Mac-Clang-arm64-Debug-Graphite_NoGpu,Build-Mac-Clang-arm64-Debug-Graphite,Build-Mac-Clang-arm64-Debug-ASAN_Graphite
Bug: skia:12703
Change-Id: I5ef1bfdf3b4fa175bcfb25cc61fd0c46a62d46c4
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/498016
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
2022-01-26 21:52:45 +00:00

392 lines
16 KiB
C++

/*
* 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 "tests/Test.h"
#include "experimental/graphite/include/Context.h"
#include "experimental/graphite/src/ContextPriv.h"
#include "experimental/graphite/include/mtl/MtlTypes.h"
#include "experimental/graphite/src/Buffer.h"
#include "experimental/graphite/src/Caps.h"
#include "experimental/graphite/src/CommandBuffer.h"
#include "experimental/graphite/src/ContextUtils.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/Renderer.h"
#include "experimental/graphite/src/ResourceProvider.h"
#include "experimental/graphite/src/Sampler.h"
#include "experimental/graphite/src/Texture.h"
#include "experimental/graphite/src/TextureProxy.h"
#include "experimental/graphite/src/UniformManager.h"
#include "experimental/graphite/src/geom/Shape.h"
#include "experimental/graphite/src/geom/Transform_graphite.h"
#include "include/private/SkShaderCodeDictionary.h"
#include "src/core/SkKeyHelpers.h"
#include "src/core/SkUniformData.h"
#if GRAPHITE_TEST_UTILS
// set to 1 if you want to do GPU capture of the commandBuffer
#define CAPTURE_COMMANDBUFFER 0
#endif
using namespace skgpu;
namespace {
const DepthStencilSettings kTestDepthStencilSettings = {
// stencil
{},
{},
0,
true,
// depth
CompareOp::kAlways,
true,
false,
};
class UniformRectDraw final : public RenderStep {
public:
~UniformRectDraw() override {}
static const RenderStep* Singleton() {
static const UniformRectDraw kSingleton;
return &kSingleton;
}
const char* name() const override { return "uniform-rect"; }
const char* vertexSkSL() const override {
return "float2 tmpPosition = float2(float(sk_VertexID >> 1), float(sk_VertexID & 1));\n"
"float4 devPosition = float4(tmpPosition * scale + translate, 0.0, 1.0);\n";
}
void writeVertices(DrawWriter* writer,
const SkIRect&,
const Transform&,
const Shape&) const override {
// The shape is upload via uniforms, so this just needs to record 4 data-less vertices
writer->draw({}, 4);
}
sk_sp<SkUniformData> writeUniforms(Layout layout,
const SkIRect&,
const Transform&,
const Shape& shape) const override {
SkASSERT(shape.isRect());
// TODO: A << API for uniforms would be nice, particularly if it could take pre-computed
// offsets for each uniform.
auto uniforms = SkUniformData::Make(this->numUniforms(), this->uniforms().data(),
sizeof(float) * 4);
float2 scale = shape.rect().size();
float2 translate = shape.rect().topLeft();
memcpy(uniforms->data(), &scale, sizeof(float2));
memcpy(uniforms->data() + sizeof(float2), &translate, sizeof(float2));
return uniforms;
}
private:
UniformRectDraw() : RenderStep(Flags::kPerformsShading,
/*uniforms=*/{{"scale", SkSLType::kFloat2},
{"translate", SkSLType::kFloat2}},
PrimitiveType::kTriangleStrip,
{{},
{},
0,
true,
CompareOp::kAlways,
false,
false},
/*vertexAttrs=*/{},
/*instanceAttrs=*/{}) {}
};
class TriangleRectDraw final : public RenderStep {
public:
~TriangleRectDraw() override {}
static const RenderStep* Singleton() {
static const TriangleRectDraw kSingleton;
return &kSingleton;
}
const char* name() const override { return "triangle-rect"; }
const char* vertexSkSL() const override {
return "float4 devPosition = float4(position * scale + translate, 0.0, 1.0);\n";
}
void writeVertices(DrawWriter* writer,
const SkIRect&,
const Transform&,
const Shape& shape) const override {
DrawBufferManager* bufferMgr = writer->bufferManager();
auto [vertexWriter, vertices] = bufferMgr->getVertexWriter(4 * this->vertexStride());
vertexWriter << 0.5f * (shape.rect().left() + 1.f) << 0.5f * (shape.rect().top() + 1.f)
<< 0.5f * (shape.rect().left() + 1.f) << 0.5f * (shape.rect().bot() + 1.f)
<< 0.5f * (shape.rect().right() + 1.f) << 0.5f * (shape.rect().top() + 1.f)
<< 0.5f * (shape.rect().right() + 1.f) << 0.5f * (shape.rect().bot() + 1.f);
// TODO: Would be nice to re-use this
auto [indexWriter, indices] = bufferMgr->getIndexWriter(6 * sizeof(uint16_t));
indexWriter << 0 << 1 << 2
<< 2 << 1 << 3;
writer->drawIndexed(vertices, indices, 6);
}
sk_sp<SkUniformData> writeUniforms(Layout layout,
const SkIRect&,
const Transform&,
const Shape&) const override {
auto uniforms = SkUniformData::Make(this->numUniforms(), this->uniforms().data(),
sizeof(float) * 4);
float data[4] = {2.f, 2.f, -1.f, -1.f};
memcpy(uniforms->data(), data, 4 * sizeof(float));
return uniforms;
}
private:
TriangleRectDraw()
: RenderStep(Flags::kPerformsShading,
/*uniforms=*/{{"scale", SkSLType::kFloat2},
{"translate", SkSLType::kFloat2}},
PrimitiveType::kTriangles,
kTestDepthStencilSettings,
/*vertexAttrs=*/{{"position",
VertexAttribType::kFloat2,
SkSLType::kFloat2}},
/*instanceAttrs=*/{}) {}
};
class InstanceRectDraw final : public RenderStep {
public:
~InstanceRectDraw() override {}
static const RenderStep* Singleton() {
static const InstanceRectDraw kSingleton;
return &kSingleton;
}
const char* name() const override { return "instance-rect"; }
const char* vertexSkSL() const override {
return "float2 tmpPosition = float2(float(sk_VertexID >> 1), float(sk_VertexID & 1));\n"
"float4 devPosition = float4(tmpPosition * dims + position, 0.0, 1.0);\n";
}
void writeVertices(DrawWriter* writer,
const SkIRect&,
const Transform&,
const Shape& shape) const override {
SkASSERT(shape.isRect());
DrawBufferManager* bufferMgr = writer->bufferManager();
// TODO: To truly test draw merging, this index buffer needs to remembered across
// writeVertices calls
auto [indexWriter, indices] = bufferMgr->getIndexWriter(6 * sizeof(uint16_t));
indexWriter << 0 << 1 << 2
<< 2 << 1 << 3;
DrawWriter::Instances instances{*writer, {}, indices, 6};
instances.append(1) << shape.rect().topLeft() << shape.rect().size();
}
sk_sp<SkUniformData> writeUniforms(Layout,
const SkIRect&,
const Transform&,
const Shape&) const override {
return nullptr;
}
private:
InstanceRectDraw()
: RenderStep(Flags::kPerformsShading,
/*uniforms=*/{},
PrimitiveType::kTriangles,
kTestDepthStencilSettings,
/*vertexAttrs=*/{},
/*instanceAttrs=*/ {
{ "position", VertexAttribType::kFloat2, SkSLType::kFloat2 },
{ "dims", VertexAttribType::kFloat2, SkSLType::kFloat2 }
}) {}
};
} // anonymous namespace
/*
* This is to test the various pieces of the CommandBuffer interface.
*/
DEF_GRAPHITE_TEST_FOR_CONTEXTS(CommandBufferTest, reporter, context) {
constexpr int kTextureWidth = 1024;
constexpr int kTextureHeight = 768;
auto gpu = context->priv().gpu();
REPORTER_ASSERT(reporter, gpu);
#if GRAPHITE_TEST_UTILS && CAPTURE_COMMANDBUFFER
gpu->testingOnly_startCapture();
#endif
auto commandBuffer = gpu->resourceProvider()->createCommandBuffer();
SkISize textureSize = { kTextureWidth, kTextureHeight };
#ifdef SK_METAL
skgpu::mtl::TextureInfo mtlTextureInfo = {
1,
1,
70, // MTLPixelFormatRGBA8Unorm
0x0005, // MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead
2, // MTLStorageModePrivate
false, // framebufferOnly
};
TextureInfo textureInfo(mtlTextureInfo);
#else
TextureInfo textureInfo;
#endif
SkPaintParamsKey key = CreateKey(SkBackend::kGraphite,
ShaderCombo::ShaderType::kSolidColor,
SkTileMode::kClamp,
SkBlendMode::kSrc);
auto entry = context->priv().shaderCodeDictionary()->findOrCreate(key);
auto target = sk_sp<TextureProxy>(new TextureProxy(textureSize, textureInfo));
REPORTER_ASSERT(reporter, target);
RenderPassDesc renderPassDesc = {};
renderPassDesc.fColorAttachment.fTextureInfo = target->textureInfo();
renderPassDesc.fColorAttachment.fLoadOp = LoadOp::kClear;
renderPassDesc.fColorAttachment.fStoreOp = StoreOp::kStore;
renderPassDesc.fClearColor = { 1, 0, 0, 1 }; // red
target->instantiate(gpu->resourceProvider());
DrawBufferManager bufferMgr(gpu->resourceProvider(), 4);
TextureInfo depthStencilInfo =
gpu->caps()->getDefaultDepthStencilTextureInfo(DepthStencilFlags::kDepthStencil,
1,
Protected::kNo);
renderPassDesc.fDepthStencilAttachment.fTextureInfo = depthStencilInfo;
renderPassDesc.fDepthStencilAttachment.fLoadOp = LoadOp::kDiscard;
renderPassDesc.fDepthStencilAttachment.fStoreOp = StoreOp::kDiscard;
sk_sp<Texture> depthStencilTexture =
gpu->resourceProvider()->findOrCreateTexture(textureSize, depthStencilInfo);
// Create Sampler -- for now, just to test creation
sk_sp<Sampler> sampler = gpu->resourceProvider()->findOrCreateCompatibleSampler(
SkSamplingOptions(SkFilterMode::kLinear), SkTileMode::kClamp, SkTileMode::kDecal);
REPORTER_ASSERT(reporter, sampler);
commandBuffer->beginRenderPass(renderPassDesc, target->refTexture(), nullptr,
depthStencilTexture);
commandBuffer->setViewport(0.f, 0.f, kTextureWidth, kTextureHeight);
DrawWriter drawWriter(commandBuffer->asDrawDispatcher(), &bufferMgr);
struct RectAndColor {
SkRect fRect;
SkColor4f fColor;
};
auto draw = [&](const RenderStep* step, std::vector<RectAndColor> draws) {
GraphicsPipelineDesc pipelineDesc;
pipelineDesc.setProgram(step, entry->uniqueID());
drawWriter.newPipelineState(step->primitiveType(),
step->vertexStride(),
step->instanceStride());
auto pipeline = gpu->resourceProvider()->findOrCreateGraphicsPipeline(context,
pipelineDesc,
renderPassDesc);
commandBuffer->bindGraphicsPipeline(std::move(pipeline));
// All of the test RenderSteps ignore the transform, so just use the identity
static const Transform kIdentity{SkM44()};
// No set scissor, so use entire render target dimensions
static const SkIRect kBounds = SkIRect::MakeWH(kTextureWidth, kTextureHeight);
for (auto d : draws) {
drawWriter.newDynamicState();
Shape shape(d.fRect);
auto renderStepUniforms =
step->writeUniforms(Layout::kMetal, kBounds, kIdentity, shape);
if (renderStepUniforms) {
auto [writer, bindInfo] =
bufferMgr.getUniformWriter(renderStepUniforms->dataSize());
writer.write(renderStepUniforms->data(), renderStepUniforms->dataSize());
commandBuffer->bindUniformBuffer(UniformSlot::kRenderStep,
sk_ref_sp(bindInfo.fBuffer),
bindInfo.fOffset);
}
// TODO: Rely on uniform writer and GetUniforms(kSolidColor).
auto [writer, bindInfo] = bufferMgr.getUniformWriter(sizeof(SkColor4f));
writer.write(&d.fColor, sizeof(SkColor4f));
commandBuffer->bindUniformBuffer(UniformSlot::kPaint,
sk_ref_sp(bindInfo.fBuffer),
bindInfo.fOffset);
step->writeVertices(&drawWriter, kBounds, kIdentity, shape);
}
};
SkRect fullRect = SkRect::MakeIWH(kTextureWidth, kTextureHeight);
// Draw blue rectangle over entire rendertarget (which was red)
draw(UniformRectDraw::Singleton(), {{fullRect, SkColors::kBlue}});
// Draw inset yellow rectangle using uniforms
draw(UniformRectDraw::Singleton(),
{{fullRect.makeInset(kTextureWidth/20.f, kTextureHeight/20.f), SkColors::kYellow}});
// Draw inset magenta rectangle with triangles in vertex buffer
draw(TriangleRectDraw::Singleton(),
{{fullRect.makeInset(kTextureWidth/4.f, kTextureHeight/4.f), SkColors::kMagenta}});
// Draw green and cyan rects using instance buffer
draw(InstanceRectDraw::Singleton(),
{ {{kTextureWidth/3.f, kTextureHeight/3.f,
kTextureWidth/2.f, kTextureHeight/2.f}, SkColors::kGreen},
{{kTextureWidth/2.f, kTextureHeight/2.f,
5.f*kTextureWidth/8.f, 5.f*kTextureHeight/8.f}, SkColors::kCyan} });
drawWriter.flush();
bufferMgr.transferToCommandBuffer(commandBuffer.get());
commandBuffer->endRenderPass();
// Do readback
// TODO: add 4-byte transfer buffer alignment for Mac to Caps
// add bpp to Caps
size_t rowBytes = 4*kTextureWidth;
size_t bufferSize = rowBytes*kTextureHeight;
sk_sp<Buffer> copyBuffer = gpu->resourceProvider()->findOrCreateBuffer(
bufferSize, BufferType::kXferGpuToCpu, PrioritizeGpuReads::kNo);
REPORTER_ASSERT(reporter, copyBuffer);
SkIRect srcRect = { 0, 0, kTextureWidth, kTextureHeight };
commandBuffer->copyTextureToBuffer(target->refTexture(), srcRect, copyBuffer, 0, rowBytes);
bool result = gpu->submit(commandBuffer);
REPORTER_ASSERT(reporter, result);
gpu->checkForFinishedWork(skgpu::SyncToCpu::kYes);
uint32_t* pixels = (uint32_t*)(copyBuffer->map());
REPORTER_ASSERT(reporter, pixels[0] == 0xffff0000);
REPORTER_ASSERT(reporter, pixels[51 + 38*kTextureWidth] == 0xff00ffff);
REPORTER_ASSERT(reporter, pixels[256 + 192*kTextureWidth] == 0xffff00ff);
copyBuffer->unmap();
#if GRAPHITE_TEST_UTILS && CAPTURE_COMMANDBUFFER
gpu->testingOnly_endCapture();
#endif
}