blob: 9ec6b06f3647db354b14d215252e81dafd98aa82 [file] [log] [blame]
/*
* 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/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"
#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 {
class FullscreenRectDraw final : public RenderStep {
public:
~FullscreenRectDraw() override {}
static const RenderStep* Singleton() {
static const FullscreenRectDraw kSingleton;
return &kSingleton;
}
const char* name() const override { return "fullscreen-rect"; }
const char* vertexMSL() const override {
return "float2 position = float2(float(vertexID >> 1), float(vertexID & 1));\n"
"out.position.xy = position * 2 - 1;\n"
"out.position.zw = float2(0.0, 1.0);\n";
}
void writeVertices(DrawWriter* writer, const Shape&) const override {
// This RenderStep ignores shape and just needs to draw 4 data-less vertices
writer->draw({}, {}, 4);
}
sk_sp<UniformData> writeUniforms(Layout, const Shape&) const override {
return nullptr;
}
private:
FullscreenRectDraw() : RenderStep(Flags::kPerformsShading,
/*uniforms=*/{},
PrimitiveType::kTriangleStrip,
/*vertexAttrs=*/{},
/*instanceAttrs=*/{}) {}
};
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* vertexMSL() const override {
return "float2 position = float2(float(vertexID >> 1), float(vertexID & 1));\n"
"out.position.xy = position * uniforms.scale + uniforms.translate;\n"
"out.position.zw = float2(0.0, 1.0);\n";
}
void writeVertices(DrawWriter* writer, 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 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,
/*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* vertexMSL() const override {
return "float2 position = vtx.position;\n"
"out.position.xy = position * uniforms.scale + uniforms.translate;\n"
"out.position.zw = float2(0.0, 1.0);\n";
}
void writeVertices(DrawWriter* writer, 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 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,
/*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* vertexMSL() const override {
return "float2 position = float2(float(vertexID >> 1), float(vertexID & 1));\n"
"out.position.xy = position * vtx.dims + vtx.position;\n"
"out.position.zw = float2(0.0, 1.0);\n";
}
void writeVertices(DrawWriter* writer, 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 Shape&) const override {
return nullptr;
}
private:
InstanceRectDraw()
: RenderStep(Flags::kPerformsShading,
/*uniforms=*/{},
PrimitiveType::kTriangles,
/*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.fTextureProxy = target;
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);
commandBuffer->beginRenderPass(renderPassDesc);
DrawWriter drawWriter(commandBuffer->asDrawDispatcher(), &bufferMgr);
struct RectAndColor {
SkRect fRect;
SkColor4f fColor;
};
auto draw = [&](const RenderStep* step, std::vector<RectAndColor> draws) {
GraphicsPipelineDesc pipelineDesc;
pipelineDesc.setRenderStep(step);
drawWriter.newPipelineState(step->primitiveType(),
step->vertexStride(),
step->instanceStride());
auto pipeline = gpu->resourceProvider()->findOrCreateGraphicsPipeline(pipelineDesc);
commandBuffer->bindGraphicsPipeline(std::move(pipeline));
for (auto d : draws) {
drawWriter.newDynamicState();
Shape shape(d.fRect);
auto renderStepUniforms = step->writeUniforms(Layout::kMetal, 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: Hard-coded solid color uniform for the fragment shader is always combined
// with the RenderStep's vertex shader.
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, shape);
}
};
// Draw blue rectangle over entire rendertarget (which was red)
draw(FullscreenRectDraw::Singleton(), {{SkRect::MakeEmpty(), SkColors::kBlue}});
// Draw inset yellow rectangle using uniforms
draw(UniformRectDraw::Singleton(), {{{-0.9f, -0.9f, 0.9f, 0.9f}, SkColors::kYellow}});
// Draw inset magenta rectangle with triangles in vertex buffer
draw(TriangleRectDraw::Singleton(), {{{-.5f, -.5f, .5f, .5f}, SkColors::kMagenta}});
// Draw green and cyan rects using instance buffer
draw(InstanceRectDraw::Singleton(), { {{0.4f, -0.4f, 0.4f, 0.4f}, SkColors::kGreen},
{{0.f, 0.f, 0.25f, 0.25f}, 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
}