| /* | |
| * 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<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->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<SkUniformData> 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<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 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<SkPaintParamsKey> key = CreateKey(dict, | |
| SkBackend::kGraphite, | |
| ShaderCombo::ShaderType::kSolidColor, | |
| SkTileMode::kClamp, | |
| SkBlendMode::kSrc); | |
| auto entry = dict->findOrCreate(std::move(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(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<Texture> depthStencilTexture = | |
| resourceProvider->findOrCreateTexture(textureSize, depthStencilInfo); | |
| // Create Sampler -- for now, just to test creation | |
| sk_sp<Sampler> 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<RectAndColor> 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<Buffer> 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 | |
| } |