blob: 00ac12a233ef0db006717b99b480513e786defd6 [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/AtlasShapeRenderStep.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/AtlasShape.h"
#include "src/gpu/graphite/render/CommonDepthStencilSettings.h"
namespace skgpu::graphite {
AtlasShapeRenderStep::AtlasShapeRenderStep()
: RenderStep("AtlasShapeRenderStep",
"",
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::kFloat2 , SkSLType::kFloat2},
{"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 AtlasShapeRenderStep::vertexSkSL() const {
// An atlas shape is an axis-aligned rectangle tessellated as a triangle strip.
//
// The bounds coordinates that we use here have already been transformed to device space and
// match the desired vertex coordinates of the draw (taking clipping into account), so a
// localToDevice transform is always the identity matrix.
//
// AtlasShape is always defined based on a regular Shape geometry and we can derive the local
// coordinates from the bounds by simply applying the inverse of the shape's localToDevice
// transform.
return R"(
float3x3 deviceToLocal = float3x3(mat0, mat1, mat2);
float2 quadCoords = float2(float(sk_VertexID >> 1), float(sk_VertexID & 1));
// Vertex coordinates.
float2 maskDims = float2(maskSize);
float2 drawCoords =
drawBounds.xy + quadCoords * max(drawBounds.zw - drawBounds.xy, maskDims);
// Local coordinates used for shading.
float3 localCoords = deviceToLocal * drawCoords.xy1;
stepLocalCoords = localCoords.xy / localCoords.z;
// Adjust the `maskBounds` to span the full atlas entry with a 2-pixel outset (-1 since the
// clamp we apply in the fragment shader is inclusive). `textureCoords` get set with a 1
// pixel inset and its dimensions should exactly match the draw coords.
//
// For an inverse fill, `textureCoords` will get clamped to `maskBounds` and the edge pixels
// will always land on a 0-coverage border pixel.
float2 uvPos = float2(uvOrigin);
if (maskDims.x > 0 && maskDims.y > 0) {
maskBounds = float4(uvPos, uvPos + maskDims + float2(1)) * atlasSizeInv.xyxy;
textureCoords = (uvPos + float2(1) + drawCoords - deviceOrigin) * atlasSizeInv;
} else {
// The mask is clipped out so send the texture coordinates to 0. This pixel should
// always be empty.
maskBounds = float4(0);
textureCoords = float2(0);
}
float4 devPosition = float4(drawCoords.xy, depth, 1);
)";
}
std::string AtlasShapeRenderStep::texturesAndSamplersSkSL(
const ResourceBindingRequirements& bindingReqs, int* nextBindingIndex) const {
return EmitSamplerLayout(bindingReqs, nextBindingIndex) + " uniform sampler2D pathAtlas;";
}
const char* AtlasShapeRenderStep::fragmentCoverageSkSL() const {
return R"(
half c = sample(pathAtlas, clamp(textureCoords, maskBounds.xy, maskBounds.zw)).r;
outputCoverage = half4(isInverted == 1 ? (1 - c) : c);
)";
}
void AtlasShapeRenderStep::writeVertices(DrawWriter* dw,
const DrawParams& params,
int ssboIndex) const {
const AtlasShape& atlasShape = params.geometry().atlasShape();
// A quad is a 4-vertex instance. The coordinates are derived from the vertex IDs.
DrawWriter::Instances instances(*dw, {}, {}, 4);
skvx::float2 maskSize, deviceOrigin, uvOrigin;
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(atlasShape.inverted());
maskSize = deviceOrigin = uvOrigin = 0;
} else {
// Adjust the mask size and device origin for the 1-pixel atlas 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 = atlasShape.maskSize() + 2;
deviceOrigin = atlasShape.deviceOrigin() - 1;
uvOrigin = atlasShape.atlasOrigin();
}
const SkM44& m = atlasShape.deviceToLocal();
instances.append(1) << params.clip().drawBounds().ltrb() // drawBounds
<< deviceOrigin // deviceOrigin
<< uint16_t(uvOrigin.x()) << uint16_t(uvOrigin.y()) // uvOrigin
<< uint16_t(maskSize.x()) << uint16_t(maskSize.y()) // 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 AtlasShapeRenderStep::writeUniformsAndTextures(const DrawParams& params,
PipelineDataGatherer* gatherer) const {
SkDEBUGCODE(UniformExpectationsValidator uev(gatherer, this->uniforms());)
const AtlasShape& atlasShape = params.geometry().atlasShape();
const TextureProxy* proxy = atlasShape.atlas()->texture();
SkASSERT(proxy);
// write uniforms
SkV2 atlasSizeInv = {1.f / proxy->dimensions().width(), 1.f / proxy->dimensions().height()};
gatherer->write(atlasSizeInv);
gatherer->write(int(atlasShape.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