blob: ba934a980f7f641a296c651f504779ddbda2a63c [file] [log] [blame]
/*
* Copyright 2023 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/graphite/render/CoverageMaskRenderStep.h"
#include "src/gpu/graphite/ContextUtils.h"
#include "src/gpu/graphite/DrawParams.h"
#include "src/gpu/graphite/DrawWriter.h"
#include "src/gpu/graphite/PathAtlas.h"
#include "src/gpu/graphite/geom/CoverageMaskShape.h"
#include "src/gpu/graphite/render/CommonDepthStencilSettings.h"
namespace skgpu::graphite {
CoverageMaskRenderStep::CoverageMaskRenderStep()
: RenderStep("CoverageMaskRenderStep",
"",
Flags::kPerformsShading | Flags::kHasTextures | Flags::kEmitsCoverage,
/*uniforms=*/{{"atlasSizeInv", SkSLType::kFloat2},
{"isInverted", SkSLType::kInt}},
PrimitiveType::kTriangleStrip,
kDirectDepthGEqualPass,
/*vertexAttrs=*/{},
/*instanceAttrs=*/
{{"drawBounds" , VertexAttribType::kFloat4 , SkSLType::kFloat4}, // ltrb
{"deviceOrigin", VertexAttribType::kInt2, SkSLType::kInt2},
{"uvOrigin" , VertexAttribType::kUShort2, SkSLType::kUShort2},
{"maskSize" , VertexAttribType::kUShort2, SkSLType::kUShort2},
{"depth" , VertexAttribType::kFloat, SkSLType::kFloat},
{"ssboIndex" , VertexAttribType::kInt , SkSLType::kInt},
{"mat0", VertexAttribType::kFloat3, SkSLType::kFloat3},
{"mat1", VertexAttribType::kFloat3, SkSLType::kFloat3},
{"mat2", VertexAttribType::kFloat3, SkSLType::kFloat3}},
/*varyings=*/
{// `maskBounds` are the atlas-relative bounds of the coverage mask.
// `textureCoords` are the atlas-relative UV coordinates of the draw, which
// can spill beyond `maskBounds` for inverse fills.
// TODO: maskBounds is constant for all fragments for a given instance,
// could we store them in the draw's SSBO?
{"maskBounds" , SkSLType::kFloat4},
{"textureCoords", SkSLType::kFloat2}}) {}
std::string CoverageMaskRenderStep::vertexSkSL() const {
// Returns the body of a vertex function, which must define a float4 devPosition variable and
// must write to an already-defined float2 stepLocalCoords variable.
return "float4 devPosition = coverage_mask_vertex_fn("
"float2(sk_VertexID >> 1, sk_VertexID & 1), atlasSizeInv, "
"drawBounds, float2(deviceOrigin), float2(uvOrigin), "
"float2(maskSize), depth, float3x3(mat0, mat1, mat2), "
"maskBounds, textureCoords, stepLocalCoords);\n";
}
std::string CoverageMaskRenderStep::texturesAndSamplersSkSL(
const ResourceBindingRequirements& bindingReqs, int* nextBindingIndex) const {
return EmitSamplerLayout(bindingReqs, nextBindingIndex) + " sampler2D pathAtlas;";
}
const char* CoverageMaskRenderStep::fragmentCoverageSkSL() const {
return R"(
half c = sample(pathAtlas, clamp(textureCoords, maskBounds.LT, maskBounds.RB)).r;
outputCoverage = half4(isInverted == 1 ? (1 - c) : c);
)";
}
float CoverageMaskRenderStep::boundsOutset(const Transform& localToDevice, const Rect&) const {
// Always incorporate a 1-pixel wide border to the (device space) mask for AA. CoverageMasks are
// expected to be in device space but only after the clip stack has been applied to the
// CoverageMask's originating geometry. Hence `localToDevice` is not guaranteed to be identity
// `boundsOutset` needs to return a local coordinate outset for the shape which will be applied
// to its local-coordinate bounds before it gets transformed to device space.
//
// TODO(b/238770428): This won't produce an accurate result if the transform has perspective as
// the scale is not uniform across the shape. Reconsider what to do here when this RenderStep
// supports perspective.
return 1.0 / localToDevice.maxScaleFactor();
}
void CoverageMaskRenderStep::writeVertices(DrawWriter* dw,
const DrawParams& params,
int ssboIndex) const {
const CoverageMaskShape& coverageMask = params.geometry().coverageMaskShape();
// A quad is a 4-vertex instance. The coordinates are derived from the vertex IDs.
DrawWriter::Instances instances(*dw, {}, {}, 4);
skvx::half2 maskSize, uvOrigin;
skvx::int2 deviceOrigin;
if (params.clip().transformedShapeBounds().isEmptyNegativeOrNaN()) {
// If the mask shape is clipped out then this must be an inverse fill. There is no mask to
// sample but we still need to paint the fill region that excludes the mask shape. Signal
// this by setting the mask size to 0.
SkASSERT(coverageMask.inverted());
maskSize = uvOrigin = 0;
deviceOrigin = 0;
} else {
// `maskSize` and `deviceOrigin` correspond to the mask bounds (transformed shape bounds
// expanded by a 1-pixel border for AA). `uvOrigin` is positioned to include the additional
// 1-pixel border between atlas entries (which corresponds to their clip bounds and should
// contain 0).
maskSize = coverageMask.maskSize();
deviceOrigin = coverageMask.deviceOrigin();
uvOrigin = coverageMask.textureOrigin();
}
const SkM44& m = coverageMask.deviceToLocal();
instances.append(1) << params.clip().drawBounds().ltrb() // drawBounds
<< deviceOrigin << uvOrigin << maskSize
<< params.order().depthAsFloat() << ssboIndex
<< m.rc(0,0) << m.rc(1,0) << m.rc(3,0) // mat0
<< m.rc(0,1) << m.rc(1,1) << m.rc(3,1) // mat1
<< m.rc(0,3) << m.rc(1,3) << m.rc(3,3); // mat2
}
void CoverageMaskRenderStep::writeUniformsAndTextures(const DrawParams& params,
PipelineDataGatherer* gatherer) const {
SkDEBUGCODE(UniformExpectationsValidator uev(gatherer, this->uniforms());)
const CoverageMaskShape& coverageMask = params.geometry().coverageMaskShape();
const TextureProxy* proxy = coverageMask.textureProxy();
SkASSERT(proxy);
// write uniforms
SkV2 atlasSizeInv = {1.f / proxy->dimensions().width(), 1.f / proxy->dimensions().height()};
gatherer->write(atlasSizeInv);
gatherer->write(int(coverageMask.inverted()));
// write textures and samplers
const SkSamplingOptions kSamplingOptions(SkFilterMode::kNearest);
constexpr SkTileMode kTileModes[2] = {SkTileMode::kClamp, SkTileMode::kClamp};
gatherer->add(kSamplingOptions, kTileModes, sk_ref_sp(proxy));
}
} // namespace skgpu::graphite