Proper AA stenciling
This commit is contained in:
parent
1ef6d8df58
commit
3097585a2a
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue