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

View File

@ -12,9 +12,33 @@
precision highp float; precision highp float;
uniform sampler2D uAreaLUT;
in vec2 vFrom;
in vec2 vCtrl;
in vec2 vTo;
out vec4 oFragColor; out vec4 oFragColor;
void main() { void main() {
float coverage = gl_FrontFacing ? 1.0 : -1.0; // Unpack.
oFragColor = vec4(coverage); 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 vec2 aTo;
in uint aTileIndex; in uint aTileIndex;
out vec2 vFrom;
out vec2 vCtrl;
out vec2 vTo;
vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) { vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) {
uint tilesPerRow = uint(stencilTextureWidth / uTileSize.x); uint tilesPerRow = uint(stencilTextureWidth / uTileSize.x);
uvec2 tileOffset = uvec2(aTileIndex % tilesPerRow, aTileIndex / tilesPerRow); uvec2 tileOffset = uvec2(aTileIndex % tilesPerRow, aTileIndex / tilesPerRow);
@ -29,9 +33,29 @@ vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) {
void main() { void main() {
vec2 tileOrigin = computeTileOffset(aTileIndex, uFramebufferSize.x); vec2 tileOrigin = computeTileOffset(aTileIndex, uFramebufferSize.x);
vec2 offset = aTessCoord.x < 0.5 ? aFrom : aTo; vec2 from = aFrom, ctrl = mix(aFrom, aTo, 0.5), to = aTo;
if (aTessCoord.y > 0.5)
offset.y = uTileSize.y;
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);
} }