/* * 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/include/Recorder.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/ContextPriv.h" #include "experimental/graphite/src/DrawBufferManager.h" #include "experimental/graphite/src/DrawWriter.h" #include "experimental/graphite/src/GlobalCache.h" #include "experimental/graphite/src/Gpu.h" #include "experimental/graphite/src/GraphicsPipeline.h" #include "experimental/graphite/src/RecorderPriv.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 "src/core/SkKeyHelpers.h" #include "src/core/SkShaderCodeDictionary.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 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->uniforms(), 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 writeUniforms(Layout layout, const SkIRect&, const Transform&, const Shape&) const override { auto uniforms = SkUniformData::Make(this->uniforms(), 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 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 recorder = context->makeRecorder(); auto resourceProvider = recorder->priv().resourceProvider(); auto dict = resourceProvider->shaderCodeDictionary(); auto commandBuffer = 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 std::unique_ptr key = CreateKey(dict, SkBackend::kGraphite, ShaderCombo::ShaderType::kSolidColor, SkTileMode::kClamp, SkBlendMode::kSrc); auto entry = dict->findOrCreate(std::move(key)); auto target = sk_sp(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(resourceProvider); DrawBufferManager bufferMgr(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 depthStencilTexture = resourceProvider->findOrCreateTexture(textureSize, depthStencilInfo); // Create Sampler -- for now, just to test creation sk_sp sampler = 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 draws) { GraphicsPipelineDesc pipelineDesc; pipelineDesc.setProgram(step, entry->uniqueID()); drawWriter.newPipelineState(step->primitiveType(), step->vertexStride(), step->instanceStride()); auto pipeline = resourceProvider->findOrCreateGraphicsPipeline(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 copyBuffer = 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 }