blob: 3c06ca7191aa3b9448430bad1ecb45d367cfcf3f [file] [log] [blame]
/*
* 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 "src/gpu/tessellate/PatchWriter.h"
#include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h"
#if SK_GPU_V1
#include "src/gpu/tessellate/PathTessellator.h"
#include "src/gpu/tessellate/StrokeTessellator.h"
#endif
namespace skgpu {
SK_ALWAYS_INLINE SkPoint to_skpoint(float2 p) { return skvx::bit_pun<SkPoint>(p); }
void write_triangle_stack(PatchWriter* writer,
MiddleOutPolygonTriangulator::PoppedTriangleStack&& stack) {
for (auto [p0, p1, p2] : stack) {
writer->writeTriangle(p0, p1, p2);
}
}
#if SK_GPU_V1
PatchWriter::PatchWriter(GrMeshDrawTarget* target,
PathTessellator* tessellator,
int maxTessellationSegments,
int initialPatchAllocCount)
: PatchWriter(target,
&tessellator->fVertexChunkArray,
tessellator->fAttribs,
maxTessellationSegments,
sizeof(SkPoint) * 4 + PatchAttribsStride(tessellator->fAttribs),
initialPatchAllocCount) {
}
PatchWriter::PatchWriter(GrMeshDrawTarget* target,
StrokeTessellator* tessellator,
int maxTessellationSegments,
int initialPatchAllocCount)
: PatchWriter(target,
&tessellator->fVertexChunkArray,
tessellator->fAttribs,
maxTessellationSegments,
sizeof(SkPoint) * 4 + PatchAttribsStride(tessellator->fAttribs),
initialPatchAllocCount) {
}
#endif
void PatchWriter::chopAndWriteQuads(float2 p0, float2 p1, float2 p2, int numPatches) {
// If we aren't fanning or stroking, we need to fill the space between chops with triangles.
const bool needsInnerTriangles = this->writesCurvesOnly();
MiddleOutPolygonTriangulator innerTriangulator(numPatches, to_skpoint(p0));
for (; numPatches >= 3; numPatches -= 2) {
// Chop into 3 quads.
float4 T = float4(1,1,2,2) / numPatches;
float4 ab = mix(p0.xyxy(), p1.xyxy(), T);
float4 bc = mix(p1.xyxy(), p2.xyxy(), T);
float4 abc = mix(ab, bc, T);
// p1 & p2 of the cubic representation of the middle quad.
float4 middle = mix(ab, bc, mix(T, T.zwxy(), 2/3.f));
this->writeQuadPatch(p0, ab.lo, abc.lo); // Write the 1st quad.
if (needsInnerTriangles) {
this->writeTriangle(p0, abc.lo, abc.hi);
}
this->writeCubicPatch(abc.lo, middle, abc.hi); // Write the 2nd quad (as a cubic already)
if (needsInnerTriangles) {
write_triangle_stack(this, innerTriangulator.pushVertex(to_skpoint(abc.hi)));
}
std::tie(p0, p1) = {abc.hi, bc.hi}; // Save the 3rd quad.
}
if (numPatches == 2) {
// Chop into 2 quads.
float2 ab = (p0 + p1) * .5f;
float2 bc = (p1 + p2) * .5f;
float2 abc = (ab + bc) * .5f;
this->writeQuadPatch(p0, ab, abc); // Write the 1st quad.
if (needsInnerTriangles) {
this->writeTriangle(p0, abc, p2);
}
this->writeQuadPatch(abc, bc, p2); // Write the 2nd quad.
} else {
SkASSERT(numPatches == 1);
this->writeQuadPatch(p0, p1, p2); // Write the single remaining quad.
}
if (needsInnerTriangles) {
write_triangle_stack(this, innerTriangulator.pushVertex(to_skpoint(p2)));
write_triangle_stack(this, innerTriangulator.close());
}
}
void PatchWriter::chopAndWriteConics(float2 p0, float2 p1, float2 p2, float w, int numPatches) {
// If we aren't fanning or stroking, we need to fill the space between chops with triangles.
const bool needsInnerTriangles = this->writesCurvesOnly();
MiddleOutPolygonTriangulator innerTriangulator(numPatches, to_skpoint(p0));
// Load the conic in 3d homogeneous (unprojected) space.
float4 h0 = float4(p0,1,1);
float4 h1 = float4(p1,1,1) * w;
float4 h2 = float4(p2,1,1);
for (; numPatches >= 2; --numPatches) {
// Chop in homogeneous space.
float T = 1.f/numPatches;
float4 ab = mix(h0, h1, T);
float4 bc = mix(h1, h2, T);
float4 abc = mix(ab, bc, T);
// Project and write the 1st conic.
float2 midpoint = abc.xy() / abc.w();
this->writeConicPatch(h0.xy() / h0.w(),
ab.xy() / ab.w(),
midpoint,
ab.w() / sqrtf(h0.w() * abc.w()));
if (needsInnerTriangles) {
write_triangle_stack(this, innerTriangulator.pushVertex(to_skpoint(midpoint)));
}
std::tie(h0, h1) = {abc, bc}; // Save the 2nd conic (in homogeneous space).
}
// Project and write the remaining conic.
SkASSERT(numPatches == 1);
this->writeConicPatch(h0.xy() / h0.w(),
h1.xy() / h1.w(),
h2.xy(), // h2.w == 1
h1.w() / sqrtf(h0.w()));
if (needsInnerTriangles) {
write_triangle_stack(this, innerTriangulator.pushVertex(to_skpoint(h2.xy())));
write_triangle_stack(this, innerTriangulator.close());
}
}
void PatchWriter::chopAndWriteCubics(float2 p0, float2 p1, float2 p2, float2 p3, int numPatches) {
// If we aren't fanning or stroking, we need to fill the space between chops with triangles.
const bool needsInnerTriangles = this->writesCurvesOnly();
MiddleOutPolygonTriangulator innerTriangulator(numPatches, to_skpoint(p0));
for (; numPatches >= 3; numPatches -= 2) {
// Chop into 3 cubics.
float4 T = float4(1,1,2,2) / numPatches;
float4 ab = mix(p0.xyxy(), p1.xyxy(), T);
float4 bc = mix(p1.xyxy(), p2.xyxy(), T);
float4 cd = mix(p2.xyxy(), p3.xyxy(), T);
float4 abc = mix(ab, bc, T);
float4 bcd = mix(bc, cd, T);
float4 abcd = mix(abc, bcd, T);
float4 middle = mix(abc, bcd, T.zwxy()); // p1 & p2 of the middle cubic.
this->writeCubicPatch(p0, ab.lo, abc.lo, abcd.lo); // Write the 1st cubic.
if (needsInnerTriangles) {
this->writeTriangle(p0, abcd.lo, abcd.hi);
}
this->writeCubicPatch(abcd.lo, middle, abcd.hi); // Write the 2nd cubic.
if (needsInnerTriangles) {
write_triangle_stack(this, innerTriangulator.pushVertex(to_skpoint(abcd.hi)));
}
std::tie(p0, p1, p2) = {abcd.hi, bcd.hi, cd.hi}; // Save the 3rd cubic.
}
if (numPatches == 2) {
// Chop into 2 cubics.
float2 ab = (p0 + p1) * .5f;
float2 bc = (p1 + p2) * .5f;
float2 cd = (p2 + p3) * .5f;
float2 abc = (ab + bc) * .5f;
float2 bcd = (bc + cd) * .5f;
float2 abcd = (abc + bcd) * .5f;
this->writeCubicPatch(p0, ab, abc, abcd); // Write the 1st cubic.
if (needsInnerTriangles) {
this->writeTriangle(p0, abcd, p3);
}
this->writeCubicPatch(abcd, bcd, cd, p3); // Write the 2nd cubic.
} else {
SkASSERT(numPatches == 1);
this->writeCubicPatch(p0, p1, p2, p3); // Write the single remaining cubic.
}
if (needsInnerTriangles) {
write_triangle_stack(this, innerTriangulator.pushVertex(to_skpoint(p3)));
write_triangle_stack(this, innerTriangulator.close());
}
}
} // namespace skgpu