skia2/tests/graphite/CommandBufferTest.cpp
Michael Ludwig bdc0bad2e2 [graphite] Implement inverse fills
Renderer::StencilAndFill() chooses between two instances based on fill
type (more to come when we add stencil pass). The inverse fill uses
different stencil settings and different geometry (hence why it must be
a distinct renderer, since stencil is part of the pipeline).

Also updates the command buffer asserts and types to support float3
attributes and has the fill bounds render step pre-transform vertices.
This matches the intended plan of device-space control points to
avoid matrix transform uniforms when no other coords are needed.

Makes DepthStencilSettings constexpr so they can be declared constants.

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: I5be4151f533e4cc5c560baf96c59193162b48dab
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/484559
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
2021-12-21 20:44:51 +00:00

368 lines
14 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/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"
#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<UniformData> 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 = UniformData::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", SLType::kFloat2},
{"translate", SLType::kFloat2}},
PrimitiveType::kTriangleStrip,
kTestDepthStencilSettings,
/*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->draw(vertices, indices, 6);
}
sk_sp<UniformData> writeUniforms(Layout layout,
const SkIRect&,
const Transform&,
const Shape&) const override {
auto uniforms = UniformData::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", SLType::kFloat2},
{"translate", SLType::kFloat2}},
PrimitiveType::kTriangles,
kTestDepthStencilSettings,
/*vertexAttrs=*/{{"position", VertexAttribType::kFloat2, SLType::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;
writer->setInstanceTemplate({}, indices, 6);
auto instanceWriter = writer->appendInstances(1);
instanceWriter << shape.rect().topLeft() << shape.rect().size();
}
sk_sp<UniformData> 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, SLType::kFloat2 },
{ "dims", VertexAttribType::kFloat2, SLType::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
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);
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) {
Combination shader{ShaderCombo::ShaderType::kSolidColor};
GraphicsPipelineDesc pipelineDesc;
pipelineDesc.setProgram(step, shader);
drawWriter.newPipelineState(step->primitiveType(),
step->vertexStride(),
step->instanceStride());
auto pipeline = gpu->resourceProvider()->findOrCreateGraphicsPipeline(pipelineDesc);
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
}