| /* |
| * Copyright 2022 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/core/SkBlendMode.h" |
| #include "include/core/SkColor.h" |
| #include "include/core/SkColorSpace.h" |
| #include "include/core/SkPoint.h" |
| #include "include/core/SkRect.h" |
| #include "include/core/SkRefCnt.h" |
| #include "include/core/SkSurfaceProps.h" |
| #include "include/core/SkTypes.h" |
| #include "include/gpu/GrDirectContext.h" |
| #include "include/private/SkColorData.h" |
| #include "include/private/base/SkAlign.h" |
| #include "include/private/base/SkTemplates.h" |
| #include "include/private/gpu/ganesh/GrTypesPriv.h" |
| #include "src/core/SkSLTypeShared.h" |
| #include "src/gpu/SkBackingFit.h" |
| #include "src/gpu/ganesh/GrAppliedClip.h" |
| #include "src/gpu/ganesh/GrBuffer.h" |
| #include "src/gpu/ganesh/GrCaps.h" |
| #include "src/gpu/ganesh/GrDirectContextPriv.h" |
| #include "src/gpu/ganesh/GrDrawingManager.h" |
| #include "src/gpu/ganesh/GrGeometryProcessor.h" |
| #include "src/gpu/ganesh/GrGpu.h" |
| #include "src/gpu/ganesh/GrGpuBuffer.h" |
| #include "src/gpu/ganesh/GrImageInfo.h" |
| #include "src/gpu/ganesh/GrMeshDrawTarget.h" |
| #include "src/gpu/ganesh/GrOpFlushState.h" |
| #include "src/gpu/ganesh/GrPipeline.h" |
| #include "src/gpu/ganesh/GrPixmap.h" |
| #include "src/gpu/ganesh/GrProcessorAnalysis.h" |
| #include "src/gpu/ganesh/GrProcessorSet.h" |
| #include "src/gpu/ganesh/GrProgramInfo.h" |
| #include "src/gpu/ganesh/GrResourceProvider.h" |
| #include "src/gpu/ganesh/GrSimpleMesh.h" |
| #include "src/gpu/ganesh/GrUserStencilSettings.h" |
| #include "src/gpu/ganesh/SurfaceDrawContext.h" |
| #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h" |
| #include "src/gpu/ganesh/glsl/GrGLSLVarying.h" |
| #include "src/gpu/ganesh/ops/GrMeshDrawOp.h" |
| #include "src/gpu/ganesh/ops/GrOp.h" |
| #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelper.h" |
| #include "tests/CtsEnforcement.h" |
| #include "tests/Test.h" |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <initializer_list> |
| #include <memory> |
| #include <string_view> |
| #include <utility> |
| |
| class GrDstProxyView; |
| class GrGLSLProgramDataManager; |
| class GrRecordingContext; |
| class GrSurfaceProxyView; |
| class SkArenaAlloc; |
| enum class GrXferBarrierFlags; |
| namespace skgpu { class KeyBuilder; } |
| struct GrContextOptions; |
| struct GrShaderCaps; |
| |
| // Simple op that draws a vertex buffer with float2 positions as green triangles. We use this to |
| // draw GrGpuBuffers to test that the buffer contains the expected values as not all contexts will |
| // support buffer mapping. |
| class TestVertexOp final : public GrMeshDrawOp { |
| public: |
| static GrOp::Owner Make(GrRecordingContext* context, |
| sk_sp<GrGpuBuffer> buffer, |
| int baseVertex, |
| int vertexCount, |
| const SkRect& bounds) { |
| return GrOp::Make<TestVertexOp>(context, |
| std::move(buffer), |
| baseVertex, |
| vertexCount, |
| bounds); |
| } |
| |
| const char* name() const override { return "TestVertexOp"; } |
| |
| FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; } |
| |
| GrProcessorSet::Analysis finalize(const GrCaps& caps, |
| const GrAppliedClip* clip, |
| GrClampType clampType) override { |
| static constexpr SkPMColor4f kGreen{0, 1, 0, 1}; |
| SkPMColor4f color = kGreen; |
| auto analysis = fProcessorSet.finalize(GrProcessorAnalysisColor::Opaque::kYes, |
| GrProcessorAnalysisCoverage::kNone, |
| clip, |
| &GrUserStencilSettings::kUnused, |
| caps, |
| clampType, |
| &color); |
| SkASSERT(color == kGreen); |
| return analysis; |
| } |
| |
| void visitProxies(const GrVisitProxyFunc& func) const override { |
| if (fProgramInfo) { |
| fProgramInfo->visitFPProxies(func); |
| } |
| } |
| |
| private: |
| DEFINE_OP_CLASS_ID |
| |
| TestVertexOp(sk_sp<GrGpuBuffer> buffer, |
| int baseVertex, |
| int vertexCount, |
| const SkRect& bounds) |
| : GrMeshDrawOp(ClassID()) |
| , fBuffer(std::move(buffer)) |
| , fProcessorSet(SkBlendMode::kSrc) |
| , fBaseVertex(baseVertex) |
| , fVertexCount(vertexCount) { |
| this->setBounds(bounds, HasAABloat::kNo, GrOp::IsHairline::kNo); |
| } |
| |
| GrProgramInfo* programInfo() override { return fProgramInfo; } |
| |
| void onCreateProgramInfo(const GrCaps* caps, |
| SkArenaAlloc* arena, |
| const GrSurfaceProxyView& writeView, |
| bool usesMSAASurface, |
| GrAppliedClip&& appliedClip, |
| const GrDstProxyView& dstProxyView, |
| GrXferBarrierFlags renderPassXferBarriers, |
| GrLoadOp colorLoadOp) override { |
| fProgramInfo = GrSimpleMeshDrawOpHelper::CreateProgramInfo( |
| caps, |
| arena, |
| writeView, |
| usesMSAASurface, |
| std::move(appliedClip), |
| dstProxyView, |
| &fGP, |
| std::move(fProcessorSet), |
| GrPrimitiveType::kTriangles, |
| renderPassXferBarriers, |
| colorLoadOp, |
| GrPipeline::InputFlags::kNone); |
| } |
| |
| class GP : public GrGeometryProcessor { |
| public: |
| GP() : GrGeometryProcessor(kTestFP_ClassID) { |
| this->setVertexAttributesWithImplicitOffsets(&kPos, 1); |
| } |
| |
| const char* name() const override { return "TestVertexOp::GP"; } |
| |
| std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override { |
| class Impl : public ProgramImpl { |
| public: |
| void setData(const GrGLSLProgramDataManager&, |
| const GrShaderCaps&, |
| const GrGeometryProcessor&) override {} |
| |
| private: |
| void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { |
| const auto& gp = args.fGeomProc.cast<GP>(); |
| args.fVaryingHandler->emitAttributes(gp); |
| args.fFragBuilder->codeAppendf("half4 %s = half4(0, 1, 0, 1);", |
| args.fOutputColor); |
| args.fFragBuilder->codeAppendf("const half4 %s = half4(1);", |
| args.fOutputCoverage); |
| WriteOutputPosition(args.fVertBuilder, gpArgs, kPos.name()); |
| } |
| |
| UniformHandle fLocalMatrixUni; |
| }; |
| |
| return std::make_unique<Impl>(); |
| } |
| |
| void addToKey(const GrShaderCaps &caps, skgpu::KeyBuilder *builder) const override {} |
| |
| private: |
| static constexpr Attribute kPos = {"pos", kFloat2_GrVertexAttribType, SkSLType::kFloat2}; |
| }; |
| |
| void onPrepareDraws(GrMeshDrawTarget* target) override { |
| fMesh = target->allocMesh(); |
| fMesh->set(fBuffer, fVertexCount, fBaseVertex); |
| } |
| |
| void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { |
| if (!fProgramInfo) { |
| this->createProgramInfo(flushState); |
| } |
| |
| flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); |
| flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline()); |
| flushState->drawMesh(*fMesh); |
| } |
| |
| sk_sp<GrGpuBuffer> fBuffer; |
| |
| GP fGP; |
| |
| GrProcessorSet fProcessorSet; |
| |
| int fBaseVertex; |
| int fVertexCount; |
| |
| GrProgramInfo* fProgramInfo = nullptr; |
| GrSimpleMesh* fMesh = nullptr; |
| |
| friend class ::GrOp; |
| }; |
| |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrGpuBufferTransferTest, |
| reporter, |
| ctxInfo, |
| CtsEnforcement::kApiLevel_T) { |
| if (!ctxInfo.directContext()->priv().caps()->transferFromBufferToBufferSupport()) { |
| return; |
| } |
| |
| GrDirectContext* dc = ctxInfo.directContext(); |
| |
| GrDrawingManager* dm = dc->priv().drawingManager(); |
| |
| GrResourceProvider* rp = ctxInfo.directContext()->priv().resourceProvider(); |
| |
| GrGpu* gpu = ctxInfo.directContext()->priv().getGpu(); |
| |
| auto create_cpu_to_gpu_buffer = [&](int baseVertex) { |
| // Ensure any extra vertices are offscreen |
| int totalVertices = baseVertex + 6; |
| auto points = std::make_unique<SkPoint[]>(totalVertices); |
| SkPoint offscreenPt{-10000, -10000}; |
| std::fill_n(points.get(), totalVertices, offscreenPt); |
| |
| // set the quad at the desired base vertex |
| static constexpr SkPoint kUnitQuad[] {{0, 0}, {0, 1}, {1, 0}, |
| {1, 0}, {0, 1}, {1, 1}}; |
| std::copy_n(kUnitQuad, 6, points.get() + baseVertex); |
| |
| return rp->createBuffer(points.get(), |
| totalVertices*sizeof(SkPoint), |
| GrGpuBufferType::kXferCpuToGpu, |
| kDynamic_GrAccessPattern); |
| }; |
| |
| auto create_vertex_buffer = [&](sk_sp<GrGpuBuffer> srcBuffer, |
| int srcBaseVertex, |
| int vbBaseVertex, |
| bool useTask, |
| bool minSizedTransfers) { |
| // make initialization data of offscreen points. |
| int dstVertexCount = vbBaseVertex + 6; |
| auto points = std::make_unique<SkPoint[]>(dstVertexCount); |
| SkPoint offscreenPt{-10000, -10000}; |
| std::fill_n(points.get(), dstVertexCount, offscreenPt); |
| |
| sk_sp<GrGpuBuffer> vb = rp->createBuffer(points.get(), |
| dstVertexCount*sizeof(SkPoint), |
| GrGpuBufferType::kVertex, |
| kDynamic_GrAccessPattern); |
| |
| // copy actual quad data from the source buffer to our new vb. |
| |
| static constexpr size_t kTotalSize = 6*sizeof(SkPoint); |
| |
| size_t srcOffset = srcBaseVertex*sizeof(SkPoint); |
| size_t vbOffset = vbBaseVertex*sizeof(SkPoint); |
| |
| size_t alignment = gpu->caps()->transferFromBufferToBufferAlignment(); |
| SkASSERT(kTotalSize % alignment == 0); |
| SkASSERT(sizeof(SkPoint) % alignment == 0); |
| |
| if (minSizedTransfers) { |
| for (size_t n = kTotalSize/alignment, i = 0; i < n; ++i) { |
| if (useTask) { |
| dm->newBufferTransferTask(srcBuffer, |
| srcOffset + i*alignment, |
| vb, |
| vbOffset + i*alignment, |
| alignment); |
| } else { |
| gpu->transferFromBufferToBuffer(srcBuffer, |
| srcOffset + i*alignment, |
| vb, |
| vbOffset + i*alignment, |
| alignment); |
| } |
| } |
| } else if (useTask) { |
| dm->newBufferTransferTask(srcBuffer, srcOffset, vb, vbOffset, kTotalSize); |
| } else { |
| gpu->transferFromBufferToBuffer(srcBuffer, srcOffset, vb, vbOffset, kTotalSize); |
| } |
| return vb; |
| }; |
| |
| auto sdc = skgpu::v1::SurfaceDrawContext::Make(dc, |
| GrColorType::kRGBA_8888, |
| nullptr, |
| SkBackingFit::kExact, |
| {1, 1}, |
| SkSurfaceProps{}, |
| std::string_view{}); |
| if (!sdc) { |
| ERRORF(reporter, "Could not create draw context"); |
| return; |
| } |
| |
| auto pm = GrPixmap::Allocate(sdc->imageInfo().makeColorType(GrColorType::kRGBA_F32)); |
| |
| for (bool useTask : {false, true}) { |
| for (bool minSizedTransfers : {false, true}) { |
| for (int srcBaseVertex : {0, 5}) { |
| auto src = create_cpu_to_gpu_buffer(srcBaseVertex); |
| if (!src) { |
| ERRORF(reporter, "Could not create src buffer"); |
| return; |
| } |
| for (int vbBaseVertex : {0, 2}) { |
| auto vb = create_vertex_buffer(src, |
| srcBaseVertex, |
| vbBaseVertex, |
| useTask, |
| minSizedTransfers); |
| if (!vb) { |
| ERRORF(reporter, "Could not create vertex buffer"); |
| return; |
| } |
| |
| static constexpr SkColor4f kRed{1, 0, 0, 1}; |
| |
| static constexpr SkRect kBounds{0, 0, 1, 1}; |
| |
| sdc->clear(kRed); |
| |
| sdc->addDrawOp(nullptr, TestVertexOp::Make(dc, |
| vb, |
| vbBaseVertex, |
| /*vertexCount=*/6, |
| kBounds)); |
| |
| auto color = static_cast<SkPMColor4f*>(pm.addr()); |
| *color = kRed.premul(); |
| if (!sdc->readPixels(dc, pm, {0, 0})) { |
| ERRORF(reporter, "Read back failed."); |
| return; |
| } |
| |
| static constexpr SkPMColor4f kGreen{0, 1, 0, 1}; |
| |
| REPORTER_ASSERT(reporter, *color == kGreen, "src base vertex: %d, " |
| "vb base vertex: %d, " |
| "use task: %d, " |
| "minSizedTransfers: %d", |
| srcBaseVertex, |
| vbBaseVertex, |
| useTask, |
| minSizedTransfers); |
| } |
| } |
| } |
| } |
| } |
| |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrGpuBufferUpdateDataTest, |
| reporter, |
| ctxInfo, |
| CtsEnforcement::kApiLevel_T) { |
| GrDirectContext* dc = ctxInfo.directContext(); |
| |
| GrGpu* gpu = ctxInfo.directContext()->priv().getGpu(); |
| |
| static constexpr SkPoint kUnitQuad[] {{0, 0}, {0, 1}, {1, 0}, |
| {1, 0}, {0, 1}, {1, 1}}; |
| |
| auto sdc = skgpu::v1::SurfaceDrawContext::Make(dc, |
| GrColorType::kRGBA_8888, |
| nullptr, |
| SkBackingFit::kExact, |
| {1, 1}, |
| SkSurfaceProps{}, |
| std::string_view{}); |
| if (!sdc) { |
| ERRORF(reporter, "Could not create draw context"); |
| return; |
| } |
| |
| auto pm = GrPixmap::Allocate(sdc->imageInfo().makeColorType(GrColorType::kRGBA_F32)); |
| |
| for (bool piecewise : {false, true}) { |
| size_t alignment = piecewise ? gpu->caps()->bufferUpdateDataPreserveAlignment() : 1; |
| for (size_t offset : {size_t{0}, 4*sizeof(SkPoint), size_t{1}, size_t{27}}) { |
| // For non-discarding updates we may not be able to actually put the data at an |
| // arbitrary offset. |
| if (alignment > 1) { |
| offset = SkAlignTo(offset, alignment); |
| } |
| for (auto accessPattern : {kStatic_GrAccessPattern, |
| // kStream_GrAccessPattern, GrVkGpu asserts on this for VBs. |
| kDynamic_GrAccessPattern}) { |
| // Go direct to GrGpu to avoid caching/size adjustments at GrResourceProvider level. |
| // We add an extra size(SkPoint) to ensure that everything fits when we align the |
| // first point's location in the vb below. |
| auto vb = gpu->createBuffer(sizeof(kUnitQuad) + offset + sizeof(SkPoint), |
| GrGpuBufferType::kVertex, |
| accessPattern); |
| if (!vb) { |
| ERRORF(reporter, "Could not create vertex buffer"); |
| return; |
| } |
| |
| const void* src = kUnitQuad; |
| size_t updateSize = sizeof(kUnitQuad); |
| // The vertices in the VB must be aligned to the size of a vertex (because our draw |
| // call takes a base vertex index rather than a byte offset). So if we want our |
| // upload to begin at a non-aligned byte we shift the data in the src buffer so that |
| // it falls at a vertex alignment in the vb. |
| std::unique_ptr<char[]> tempSrc; |
| size_t baseVertex = offset/sizeof(SkPoint); |
| if (size_t r = offset%sizeof(SkPoint); r != 0) { |
| size_t pad = sizeof(SkPoint) - r; |
| updateSize += pad; |
| if (alignment > 1) { |
| updateSize = SkAlignTo(updateSize, alignment); |
| } |
| ++baseVertex; |
| tempSrc.reset(new char[updateSize]); |
| std::memcpy(tempSrc.get() + pad, kUnitQuad, sizeof(kUnitQuad)); |
| src = tempSrc.get(); |
| } |
| if (piecewise) { |
| // This is the minimum size we can transfer at once. |
| size_t pieceSize = alignment; |
| |
| // Upload each piece from a buffer where the byte before and after the uploaded |
| // bytes are not the same values as want adjacent to the piece in the buffer. |
| // Thus, if updateData() transfers extra bytes around the source we should get a |
| // bad buffer. |
| auto piece = std::make_unique<unsigned char[]>(pieceSize + 2); |
| piece[0] = piece[pieceSize + 1] = 0xFF; |
| |
| for (size_t o = 0; o < updateSize; o += pieceSize) { |
| memcpy(&piece[1], SkTAddOffset<const void>(src, o), pieceSize); |
| if (!vb->updateData(&piece[1], offset + o, pieceSize, /*preserve=*/true)) { |
| ERRORF(reporter, "GrGpuBuffer::updateData returned false."); |
| return; |
| } |
| } |
| } else if (!vb->updateData(src, offset, updateSize, /*preserve=*/false)) { |
| ERRORF(reporter, "GrGpuBuffer::updateData returned false."); |
| return; |
| } |
| |
| static constexpr SkColor4f kRed{1, 0, 0, 1}; |
| |
| static constexpr SkRect kBounds{0, 0, 1, 1}; |
| |
| sdc->clear(kRed); |
| |
| sdc->addDrawOp(nullptr, TestVertexOp::Make(dc, |
| vb, |
| baseVertex, |
| std::size(kUnitQuad), |
| kBounds)); |
| |
| auto color = static_cast<SkPMColor4f*>(pm.addr()); |
| *color = kRed.premul(); |
| if (!sdc->readPixels(dc, pm, {0, 0})) { |
| ERRORF(reporter, "Read back failed."); |
| return; |
| } |
| |
| static constexpr SkPMColor4f kGreen{0, 1, 0, 1}; |
| |
| REPORTER_ASSERT(reporter, *color == kGreen, "piecewise: %d, offset: %zu", |
| piecewise, offset); |
| } |
| } |
| } |
| } |