pathfinder/shaders/d3d11/dice.cs.glsl

218 lines
7.7 KiB
Plaintext
Raw Normal View History

2020-06-23 15:35:57 -04:00
#version 430
// pathfinder/shaders/dice.cs.glsl
//
// Copyright © 2020 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Chops lines and curves into microlines.
#extension GL_GOOGLE_include_directive : enable
#define BIN_WORKGROUP_SIZE 64
#define MAX_CURVE_STACK_SIZE 32
#define FLAGS_PATH_INDEX_CURVE_IS_QUADRATIC 0x80000000u
#define FLAGS_PATH_INDEX_CURVE_IS_CUBIC 0x40000000u
#define BIN_INDIRECT_DRAW_PARAMS_MICROLINE_COUNT_INDEX 3
#define TOLERANCE 0.25
#define MICROLINE_LENGTH 16.0
precision highp float;
#ifdef GL_ES
precision highp sampler2D;
#endif
layout(local_size_x = 64) in;
uniform mat2 uTransform;
uniform vec2 uTranslation;
uniform int uPathCount;
uniform int uLastBatchSegmentIndex;
uniform int uMaxMicrolineCount;
layout(std430, binding = 0) buffer bComputeIndirectParams {
// [0]: number of x workgroups
// [1]: number of y workgroups (always 1)
// [2]: number of z workgroups (always 1)
// [3]: number of output microlines
restrict uint iComputeIndirectParams[];
};
// Indexed by batch path index.
layout(std430, binding = 1) buffer bDiceMetadata {
// x: global path ID
// y: first global segment index
// z: first batch segment index
// w: unused
restrict readonly uvec4 iDiceMetadata[];
};
layout(std430, binding = 2) buffer bPoints {
restrict readonly vec2 iPoints[];
};
layout(std430, binding = 3) buffer bInputIndices {
restrict readonly uvec2 iInputIndices[];
};
layout(std430, binding = 4) buffer bMicrolines {
// x: from (X, Y) whole pixels, packed signed 16-bit
// y: to (X, Y) whole pixels, packed signed 16-bit
// z: (from X, from Y, to X, to Y) fractional pixels, packed unsigned 8-bit (0.8 fixed point)
// w: path ID
restrict uvec4 iMicrolines[];
};
void emitMicroline(vec4 microlineSegment, uint pathIndex, uint outputMicrolineIndex) {
if (outputMicrolineIndex >= uMaxMicrolineCount)
return;
ivec4 microlineSubpixels = ivec4(round(clamp(microlineSegment, -32768.0, 32767.0) * 256.0));
ivec4 microlinePixels = ivec4(floor(vec4(microlineSubpixels) / 256.0));
ivec4 microlineFractPixels = microlineSubpixels - microlinePixels * 256;
iMicrolines[outputMicrolineIndex] =
uvec4((uint(microlinePixels.x) & 0xffff) | (uint(microlinePixels.y) << 16),
(uint(microlinePixels.z) & 0xffff) | (uint(microlinePixels.w) << 16),
uint(microlineFractPixels.x) | (uint(microlineFractPixels.y) << 8) |
(uint(microlineFractPixels.z) << 16) | (uint(microlineFractPixels.w) << 24),
pathIndex);
}
// See Kaspar Fischer, "Piecewise Linear Approximation of Bézier Curves", 2000.
bool curveIsFlat(vec4 baseline, vec4 ctrl) {
vec4 uv = vec4(3.0) * ctrl - vec4(2.0) * baseline - baseline.zwxy;
uv *= uv;
uv = max(uv, uv.zwxy);
return uv.x + uv.y <= 16.0 * TOLERANCE * TOLERANCE;
}
void subdivideCurve(vec4 baseline,
vec4 ctrl,
float t,
out vec4 prevBaseline,
out vec4 prevCtrl,
out vec4 nextBaseline,
out vec4 nextCtrl) {
vec2 p0 = baseline.xy, p1 = ctrl.xy, p2 = ctrl.zw, p3 = baseline.zw;
vec2 p0p1 = mix(p0, p1, t), p1p2 = mix(p1, p2, t), p2p3 = mix(p2, p3, t);
vec2 p0p1p2 = mix(p0p1, p1p2, t), p1p2p3 = mix(p1p2, p2p3, t);
vec2 p0p1p2p3 = mix(p0p1p2, p1p2p3, t);
prevBaseline = vec4(p0, p0p1p2p3);
prevCtrl = vec4(p0p1, p0p1p2);
nextBaseline = vec4(p0p1p2p3, p3);
nextCtrl = vec4(p1p2p3, p2p3);
}
vec2 sampleCurve(vec4 baseline, vec4 ctrl, float t) {
vec2 p0 = baseline.xy, p1 = ctrl.xy, p2 = ctrl.zw, p3 = baseline.zw;
vec2 p0p1 = mix(p0, p1, t), p1p2 = mix(p1, p2, t), p2p3 = mix(p2, p3, t);
vec2 p0p1p2 = mix(p0p1, p1p2, t), p1p2p3 = mix(p1p2, p2p3, t);
return mix(p0p1p2, p1p2p3, t);
}
vec2 sampleLine(vec4 line, float t) {
return mix(line.xy, line.zw, t);
}
vec2 getPoint(uint pointIndex) {
return uTransform * iPoints[pointIndex] + uTranslation;
}
void main() {
uint batchSegmentIndex = gl_GlobalInvocationID.x;
if (batchSegmentIndex >= uLastBatchSegmentIndex)
return;
// Find the path index.
uint lowPathIndex = 0, highPathIndex = uint(uPathCount);
int iteration = 0;
while (iteration < 1024 && lowPathIndex + 1 < highPathIndex) {
uint midPathIndex = lowPathIndex + (highPathIndex - lowPathIndex) / 2;
uint midBatchSegmentIndex = iDiceMetadata[midPathIndex].z;
if (batchSegmentIndex < midBatchSegmentIndex) {
highPathIndex = midPathIndex;
} else {
lowPathIndex = midPathIndex;
if (batchSegmentIndex == midBatchSegmentIndex)
break;
}
iteration++;
}
uint batchPathIndex = lowPathIndex;
uvec4 diceMetadata = iDiceMetadata[batchPathIndex];
uint firstGlobalSegmentIndexInPath = diceMetadata.y;
uint firstBatchSegmentIndexInPath = diceMetadata.z;
uint globalSegmentIndex = batchSegmentIndex - firstBatchSegmentIndexInPath +
firstGlobalSegmentIndexInPath;
uvec2 inputIndices = iInputIndices[globalSegmentIndex];
uint fromPointIndex = inputIndices.x, flagsPathIndex = inputIndices.y;
uint toPointIndex = fromPointIndex;
if ((flagsPathIndex & FLAGS_PATH_INDEX_CURVE_IS_CUBIC) != 0u)
toPointIndex += 3;
else if ((flagsPathIndex & FLAGS_PATH_INDEX_CURVE_IS_QUADRATIC) != 0u)
toPointIndex += 2;
else
toPointIndex += 1;
vec4 baseline = vec4(getPoint(fromPointIndex), getPoint(toPointIndex));
// Read control points if applicable, and calculate number of segments.
//
// The technique is from Thomas Sederberg, "Computer-Aided Geometric Design" notes, section
// 10.6 "Error Bounds".
vec4 ctrl = vec4(0.0);
float segmentCountF;
bool isCurve = (flagsPathIndex & (FLAGS_PATH_INDEX_CURVE_IS_CUBIC |
FLAGS_PATH_INDEX_CURVE_IS_QUADRATIC)) != 0;
if (isCurve) {
vec2 ctrl0 = getPoint(fromPointIndex + 1);
if ((flagsPathIndex & FLAGS_PATH_INDEX_CURVE_IS_QUADRATIC) != 0) {
vec2 ctrl0_2 = ctrl0 * vec2(2.0);
ctrl = (baseline + (ctrl0 * vec2(2.0)).xyxy) * vec4(1.0 / 3.0);
} else {
ctrl = vec4(ctrl0, getPoint(fromPointIndex + 2));
}
vec2 bound = vec2(6.0) * max(abs(ctrl.zw - 2.0 * ctrl.xy + baseline.xy),
abs(baseline.zw - 2.0 * ctrl.zw + ctrl.xy));
segmentCountF = sqrt(length(bound) / (8.0 * TOLERANCE));
} else {
segmentCountF = length(baseline.zw - baseline.xy) / MICROLINE_LENGTH;
}
// Allocate space.
int segmentCount = max(int(ceil(segmentCountF)), 1);
uint firstOutputMicrolineIndex =
atomicAdd(iComputeIndirectParams[BIN_INDIRECT_DRAW_PARAMS_MICROLINE_COUNT_INDEX],
segmentCount);
float prevT = 0.0;
vec2 prevPoint = baseline.xy;
for (int segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++) {
float nextT = float(segmentIndex + 1) / float(segmentCount);
vec2 nextPoint;
if (isCurve)
nextPoint = sampleCurve(baseline, ctrl, nextT);
else
nextPoint = sampleLine(baseline, nextT);
emitMicroline(vec4(prevPoint, nextPoint),
batchPathIndex,
firstOutputMicrolineIndex + segmentIndex);
prevT = nextT;
prevPoint = nextPoint;
}
}