blob: bb50c570e88e9fd454ec821666a7caf739d2c3e5 [file] [log] [blame]
/*
* 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);
}
}
}
}