diff --git a/demo2/declaration.d.ts b/demo2/declaration.d.ts index 130a7f98..4a93eef5 100644 --- a/demo2/declaration.d.ts +++ b/demo2/declaration.d.ts @@ -1,5 +1,6 @@ declare module "*.glsl"; declare module "*.jpg"; +declare module "*.png"; declare module "*.svg"; declare function require(s: string): any; diff --git a/demo2/pathfinder.ts b/demo2/pathfinder.ts index 663f9f85..80fb5b0a 100644 --- a/demo2/pathfinder.ts +++ b/demo2/pathfinder.ts @@ -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 { 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 { 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); }); }); } diff --git a/demo2/stencil.fs.glsl b/demo2/stencil.fs.glsl index a554bc46..a3033661 100644 --- a/demo2/stencil.fs.glsl +++ b/demo2/stencil.fs.glsl @@ -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); } diff --git a/demo2/stencil.vs.glsl b/demo2/stencil.vs.glsl index 78bbb007..ea6d402f 100644 --- a/demo2/stencil.vs.glsl +++ b/demo2/stencil.vs.glsl @@ -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); }