Proper AA stenciling

This commit is contained in:
Patrick Walton 2018-11-16 20:43:27 -08:00
parent 1ef6d8df58
commit 3097585a2a
4 changed files with 102 additions and 26 deletions

View File

@ -1,5 +1,6 @@
declare module "*.glsl";
declare module "*.jpg";
declare module "*.png";
declare module "*.svg";
declare function require(s: string): any;

View File

@ -13,6 +13,7 @@ import COVER_FRAGMENT_SHADER_SOURCE from "./cover.fs.glsl";
import STENCIL_VERTEX_SHADER_SOURCE from "./stencil.vs.glsl";
import STENCIL_FRAGMENT_SHADER_SOURCE from "./stencil.fs.glsl";
import SVG from "../resources/svg/Ghostscript_Tiger.svg";
import AREA_LUT from "../resources/textures/area-lut.png";
const SVGPath: (path: string) => SVGPath = require('svgpath');
const parseColor: (color: string) => any = require('parse-color');
@ -95,16 +96,18 @@ type Edge = 'left' | 'top' | 'right' | 'bottom';
class App {
private canvas: HTMLCanvasElement;
private svg: XMLDocument;
private areaLUT: HTMLImageElement;
private gl: WebGL2RenderingContext;
private disjointTimerQueryExt: any;
private areaLUTTexture: WebGLTexture;
private stencilTexture: WebGLTexture;
private stencilFramebuffer: WebGLFramebuffer;
private stencilProgram: Program<'FramebufferSize' | 'TileSize' | 'AreaLUT',
'TessCoord' | 'From' | 'To' | 'TileIndex'>;
private coverProgram:
Program<'FramebufferSize' | 'TileSize' | 'StencilTexture' | 'StencilTextureSize',
'TessCoord' | 'TileOrigin' | 'TileIndex' | 'Color'>;
private stencilProgram: Program<'FramebufferSize' | 'TileSize',
'TessCoord' | 'From' | 'To' | 'TileIndex'>;
private quadVertexBuffer: WebGLBuffer;
private stencilVertexPositionsBuffer: WebGLBuffer;
private stencilVertexTileIndicesBuffer: WebGLBuffer;
@ -115,10 +118,11 @@ class App {
private scene: Scene | null;
private primitiveCount: number | null;
constructor(svg: XMLDocument) {
constructor(svg: XMLDocument, areaLUT: HTMLImageElement) {
const canvas = staticCast(document.getElementById('canvas'), HTMLCanvasElement);
this.canvas = canvas;
this.svg = svg;
this.areaLUT = areaLUT;
const devicePixelRatio = window.devicePixelRatio;
canvas.width = window.innerWidth * devicePixelRatio;
@ -131,6 +135,14 @@ class App {
gl.getExtension('EXT_color_buffer_float');
this.disjointTimerQueryExt = gl.getExtension('EXT_disjoint_timer_query');
this.areaLUTTexture = unwrapNull(gl.createTexture());
gl.bindTexture(gl.TEXTURE_2D, this.areaLUTTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, areaLUT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
this.stencilTexture = unwrapNull(gl.createTexture());
gl.bindTexture(gl.TEXTURE_2D, this.stencilTexture);
gl.texImage2D(gl.TEXTURE_2D,
@ -172,7 +184,7 @@ class App {
const stencilProgram = new Program(gl,
STENCIL_VERTEX_SHADER_SOURCE,
STENCIL_FRAGMENT_SHADER_SOURCE,
['FramebufferSize', 'TileSize'],
['FramebufferSize', 'TileSize', 'AreaLUT'],
['TessCoord', 'From', 'To', 'TileIndex']);
this.stencilProgram = stencilProgram;
@ -268,6 +280,9 @@ class App {
STENCIL_FRAMEBUFFER_SIZE,
STENCIL_FRAMEBUFFER_SIZE);
gl.uniform2f(this.stencilProgram.uniforms.TileSize, TILE_SIZE, TILE_SIZE);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.areaLUTTexture);
gl.uniform1i(this.stencilProgram.uniforms.AreaLUT, 0);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE);
gl.enable(gl.BLEND);
@ -317,10 +332,15 @@ class App {
// Construct stencil VBOs.
let primitiveCount = 0;
const stencilVertexPositions: number[] = [], stencilVertexTileIndices: number[] = [];
const primitiveCountHistogram: number[] = [];
for (let tileIndex = 0; tileIndex < scene.tiles.length; tileIndex++) {
const tile = scene.tiles[tileIndex];
let firstPoint = {x: 0.0, y: 0.0}, lastPoint = {x: 0.0, y: 0.0};
let primitiveCountForThisTile = 0;
tile.path.iterate(segment => {
/*if (primitiveCountForThisTile > 0)
return;*/
let point;
if (segment[0] === 'Z') {
point = firstPoint;
@ -348,11 +368,17 @@ class App {
stencilVertexPositions.push(lastPoint.x, lastPoint.y, point.x, point.y);
stencilVertexTileIndices.push(tileIndex);
primitiveCount++;
primitiveCountForThisTile++;
}
lastPoint = point;
});
if (primitiveCountHistogram[primitiveCountForThisTile] == null)
primitiveCountHistogram[primitiveCountForThisTile] = 0;
primitiveCountHistogram[primitiveCountForThisTile]++;
}
console.log(stencilVertexPositions);
console.log("histogram", primitiveCountHistogram);
// Populate the stencil VBOs.
gl.bindBuffer(gl.ARRAY_BUFFER, this.stencilVertexPositionsBuffer);
@ -435,7 +461,6 @@ class Scene {
boundingRect.size.width,
boundingRect.size.height);*/
let y = boundingRect.origin.y - boundingRect.origin.y % TILE_SIZE;
while (true) {
let x = boundingRect.origin.x - boundingRect.origin.x % TILE_SIZE;
@ -626,7 +651,7 @@ class Tile {
class Program<U extends string, A extends string> {
program: WebGLProgram;
uniforms: {[key in U]: WebGLUniformLocation};
uniforms: {[key in U]: WebGLUniformLocation | null};
attributes: {[key in A]: number};
private vertexShader: WebGLShader;
@ -662,12 +687,10 @@ class Program<U extends string, A extends string> {
throw new Error("Program linking failed!");
}
const uniforms: {[key in U]?: WebGLUniformLocation} = {};
for (const uniformName of uniformNames) {
uniforms[uniformName] = unwrapNull(gl.getUniformLocation(this.program,
"u" + uniformName));
}
this.uniforms = uniforms as {[key in U]: WebGLUniformLocation};
const uniforms: {[key in U]?: WebGLUniformLocation | null} = {};
for (const uniformName of uniformNames)
uniforms[uniformName] = gl.getUniformLocation(this.program, "u" + uniformName);
this.uniforms = uniforms as {[key in U]: WebGLUniformLocation | null};
const attributes: {[key in A]?: number} = {};
for (const attributeName of attributeNames) {
@ -765,14 +788,18 @@ function main(): void {
svg.text().then(svgText => {
const svg = staticCast((new DOMParser).parseFromString(svgText, 'image/svg+xml'),
XMLDocument);
try {
const app = new App(svg);
app.buildScene();
app.prepare();
app.redraw();
} catch (e) {
console.error("error", e, e.stack);
}
const image = new Image;
image.src = AREA_LUT;
image.addEventListener('load', event => {
try {
const app = new App(svg, image);
app.buildScene();
app.prepare();
app.redraw();
} catch (e) {
console.error("error", e, e.stack);
}
}, false);
});
});
}

View File

@ -12,9 +12,33 @@
precision highp float;
uniform sampler2D uAreaLUT;
in vec2 vFrom;
in vec2 vCtrl;
in vec2 vTo;
out vec4 oFragColor;
void main() {
float coverage = gl_FrontFacing ? 1.0 : -1.0;
oFragColor = vec4(coverage);
// Unpack.
vec2 from = vFrom, ctrl = vCtrl, to = vTo;
// Determine winding, and sort into a consistent order so we only need to find one root below.
bool winding = from.x < to.x;
vec2 left = winding ? from : to, right = winding ? to : from;
vec2 v0 = ctrl - left, v1 = right - ctrl;
// Shoot a vertical ray toward the curve.
vec2 window = clamp(vec2(from.x, to.x), -0.5, 0.5);
float offset = mix(window.x, window.y, 0.5) - left.x;
float t = offset / (v0.x + sqrt(v1.x * offset - v0.x * (offset - v0.x)));
// Compute position and derivative to form a line approximation.
float y = mix(mix(left.y, ctrl.y, t), mix(ctrl.y, right.y, t), t);
float d = mix(v0.y, v1.y, t) / mix(v0.x, v1.x, t);
// Look up area under that line, and scale horizontally to the window size.
float dX = window.x - window.y;
oFragColor = vec4(texture(uAreaLUT, vec2(y + 8.0, abs(d * dX)) / 16.0).r * dX);
}

View File

@ -20,6 +20,10 @@ in vec2 aFrom;
in vec2 aTo;
in uint aTileIndex;
out vec2 vFrom;
out vec2 vCtrl;
out vec2 vTo;
vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) {
uint tilesPerRow = uint(stencilTextureWidth / uTileSize.x);
uvec2 tileOffset = uvec2(aTileIndex % tilesPerRow, aTileIndex / tilesPerRow);
@ -29,9 +33,29 @@ vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) {
void main() {
vec2 tileOrigin = computeTileOffset(aTileIndex, uFramebufferSize.x);
vec2 offset = aTessCoord.x < 0.5 ? aFrom : aTo;
if (aTessCoord.y > 0.5)
offset.y = uTileSize.y;
vec2 from = aFrom, ctrl = mix(aFrom, aTo, 0.5), to = aTo;
gl_Position = vec4((tileOrigin + offset) / uFramebufferSize * 2.0 - 1.0, 0.0, 1.0);
vec2 dilation, position;
bool zeroArea = abs(from.x - to.x) < 0.01;
if (aTessCoord.x < 0.5) {
position.x = min(min(from.x, to.x), ctrl.x);
dilation.x = zeroArea ? 0.0 : -1.0;
} else {
position.x = max(max(from.x, to.x), ctrl.x);
dilation.x = zeroArea ? 0.0 : 1.0;
}
if (aTessCoord.y < 0.5) {
position.y = min(min(from.y, to.y), ctrl.y);
dilation.y = zeroArea ? 0.0 : -1.0;
} else {
position.y = uTileSize.y;
dilation.y = 0.0;
}
position += dilation;
vFrom = aFrom - position;
vCtrl = mix(aFrom, aTo, 0.5) - position;
vTo = aTo - position;
gl_Position = vec4((tileOrigin + position) / uFramebufferSize * 2.0 - 1.0, 0.0, 1.0);
}