From 5879d9778dfea0391fd0bba54a2277e99df03afd Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 30 Jan 2018 09:34:34 -0800 Subject: [PATCH] Replace the MCAA shader with an extended Loop-Blinn approach and an approximation of area based on approximate first-order line distance. This enables support for full affine transforms in MCAA. It also greatly simplifies the shader and reduces the amount of work that the GPU needs to do for fragment shading. Experimental testing has shown the area approximation to be accurate to about 4%. --- demo/client/src/meshes.ts | 50 +++- demo/client/src/renderer.ts | 47 +++- demo/client/src/view.ts | 1 + demo/client/src/xcaa-strategy.ts | 137 +++++----- partitioner/src/mesh_library.rs | 280 +++++++++++++++++--- shaders/gles2/common.inc.glsl | 78 +----- shaders/gles2/conservative-interior.vs.glsl | 33 +-- shaders/gles2/mcaa.fs.glsl | 81 ++---- shaders/gles2/mcaa.vs.glsl | 136 +++------- 9 files changed, 483 insertions(+), 360 deletions(-) diff --git a/demo/client/src/meshes.ts b/demo/client/src/meshes.ts index c9ba93cc..29f65c64 100644 --- a/demo/client/src/meshes.ts +++ b/demo/client/src/meshes.ts @@ -1,6 +1,6 @@ // pathfinder/client/src/meshes.ts // -// Copyright © 2017 The Pathfinder Project Developers. +// Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -87,6 +87,8 @@ const SEGMENT_LINE_SIZE: number = 4 * 4; const SEGMENT_CURVE_SIZE: number = 4 * 6; const MESH_TYPES: Meshes = { + bBoxPathIDs: { type: 'Uint16', size: 1 }, + bBoxes: { type: 'Float32', size: 20 }, bQuadVertexInteriorIndices: { type: 'Uint32', size: 1 }, bQuadVertexPositionPathIDs: { type: 'Uint16', size: 6 }, bQuadVertexPositions: { type: 'Float32', size: 12 }, @@ -99,6 +101,8 @@ const MESH_TYPES: Meshes = { }; const BUFFER_TYPES: Meshes = { + bBoxPathIDs: 'ARRAY_BUFFER', + bBoxes: 'ARRAY_BUFFER', bQuadVertexInteriorIndices: 'ELEMENT_ARRAY_BUFFER', bQuadVertexPositionPathIDs: 'ARRAY_BUFFER', bQuadVertexPositions: 'ARRAY_BUFFER', @@ -118,6 +122,7 @@ const MESH_LIBRARY_FOURCC: string = 'PFML'; // Must match the FourCCs in `pathfinder_partitioner::mesh_library::MeshLibrary::serialize_into()`. const BUFFER_TYPE_FOURCCS: BufferTypeFourCCTable = { + bbox: 'bBoxes', bqii: 'bQuadVertexInteriorIndices', bqvp: 'bQuadVertexPositions', scur: 'segmentCurves', @@ -129,6 +134,7 @@ const BUFFER_TYPE_FOURCCS: BufferTypeFourCCTable = { // Must match the FourCCs in // `pathfinder_partitioner::mesh_library::MeshLibrary::serialize_into::write_path_ranges()`. const PATH_RANGE_TYPE_FOURCCS: PathRangeTypeFourCCTable = { + bbox: 'bBoxPathRanges', bqii: 'bQuadVertexInteriorIndexPathRanges', bqvp: 'bQuadVertexPositionPathRanges', scur: 'segmentCurveRanges', @@ -136,6 +142,7 @@ const PATH_RANGE_TYPE_FOURCCS: PathRangeTypeFourCCTable = { }; const RANGE_TO_COUNT_TABLE: RangeToCountTable = { + bBoxPathRanges: 'bBoxCount', bQuadVertexInteriorIndexPathRanges: 'bQuadVertexInteriorIndexCount', bQuadVertexPositionPathRanges: 'bQuadVertexPositionCount', segmentCurveRanges: 'segmentCurveCount', @@ -143,6 +150,7 @@ const RANGE_TO_COUNT_TABLE: RangeToCountTable = { }; const RANGE_TO_RANGE_BUFFER_TABLE: RangeToRangeBufferTable = { + bBoxPathRanges: 'bBoxPathIDs', bQuadVertexPositionPathRanges: 'bQuadVertexPositionPathIDs', segmentCurveRanges: 'segmentCurvePathIDs', segmentLineRanges: 'segmentLinePathIDs', @@ -151,6 +159,7 @@ const RANGE_TO_RANGE_BUFFER_TABLE: RangeToRangeBufferTable = { const RANGE_KEYS: Array = [ 'bQuadVertexPositionPathRanges', 'bQuadVertexInteriorIndexPathRanges', + 'bBoxPathRanges', 'segmentCurveRanges', 'segmentLineRanges', ]; @@ -160,12 +169,14 @@ type BufferType = 'ARRAY_BUFFER' | 'ELEMENT_ARRAY_BUFFER'; export interface Meshes { readonly bQuadVertexPositions: T; readonly bQuadVertexInteriorIndices: T; + readonly bBoxes: T; readonly segmentLines: T; readonly segmentCurves: T; readonly segmentLineNormals: T; readonly segmentCurveNormals: T; bQuadVertexPositionPathIDs: T; + bBoxPathIDs: T; segmentLinePathIDs: T; segmentCurvePathIDs: T; } @@ -173,6 +184,7 @@ export interface Meshes { interface MeshDataCounts { readonly bQuadVertexPositionCount: number; readonly bQuadVertexInteriorIndexCount: number; + readonly bBoxCount: number; readonly segmentLineCount: number; readonly segmentCurveCount: number; } @@ -180,6 +192,7 @@ interface MeshDataCounts { interface PathRanges { bQuadVertexPositionPathRanges: Range[]; bQuadVertexInteriorIndexPathRanges: Range[]; + bBoxPathRanges: Range[]; segmentCurveRanges: Range[]; segmentLineRanges: Range[]; } @@ -187,6 +200,9 @@ interface PathRanges { export class PathfinderMeshData implements Meshes, MeshDataCounts, PathRanges { readonly bQuadVertexPositions: ArrayBuffer; readonly bQuadVertexInteriorIndices: ArrayBuffer; + readonly bBoxes: ArrayBuffer; + readonly bBoxSigns: ArrayBuffer; + readonly bBoxIndices: ArrayBuffer; readonly segmentLines: ArrayBuffer; readonly segmentCurves: ArrayBuffer; readonly segmentLineNormals: ArrayBuffer; @@ -194,15 +210,18 @@ export class PathfinderMeshData implements Meshes, MeshDataCounts, readonly bQuadVertexPositionCount: number; readonly bQuadVertexInteriorIndexCount: number; + readonly bBoxCount: number; readonly segmentLineCount: number; readonly segmentCurveCount: number; bQuadVertexPositionPathIDs: ArrayBuffer; + bBoxPathIDs: ArrayBuffer; segmentCurvePathIDs: ArrayBuffer; segmentLinePathIDs: ArrayBuffer; bQuadVertexPositionPathRanges: Range[]; bQuadVertexInteriorIndexPathRanges: Range[]; + bBoxPathRanges: Range[]; segmentCurveRanges: Range[]; segmentLineRanges: Range[]; @@ -241,6 +260,7 @@ export class PathfinderMeshData implements Meshes, MeshDataCounts, B_QUAD_VERTEX_POSITION_SIZE; this.bQuadVertexInteriorIndexCount = this.bQuadVertexInteriorIndices.byteLength / INDEX_SIZE; + this.bBoxCount = this.bBoxes.byteLength / (FLOAT32_SIZE * 6); this.segmentCurveCount = this.segmentCurves.byteLength / SEGMENT_CURVE_SIZE; this.segmentLineCount = this.segmentLines.byteLength / SEGMENT_LINE_SIZE; @@ -292,7 +312,7 @@ export class PathfinderMeshData implements Meshes, MeshDataCounts, const firstBQuadVertexIndex = bQuadVertexCopyResult.originalStartIndex; const lastBQuadVertexIndex = bQuadVertexCopyResult.originalEndIndex; - // Copy over indices. + // Copy over B-vertex indices. copyIndices(expandedArrays.bQuadVertexInteriorIndices, expandedRanges.bQuadVertexInteriorIndexPathRanges, originalBuffers.bQuadVertexInteriorIndices as Uint32Array, @@ -301,6 +321,23 @@ export class PathfinderMeshData implements Meshes, MeshDataCounts, lastBQuadVertexIndex * 6, expandedPathID); + // Copy over B-boxes. + const bBoxVertexCopyResult = copyVertices(['bBoxes'], + 'bBoxPathRanges', + expandedArrays, + expandedRanges, + originalBuffers, + originalRanges, + expandedPathID, + originalPathID); + + if (bBoxVertexCopyResult == null) + continue; + + const firstExpandedBBoxIndex = bBoxVertexCopyResult.expandedStartIndex; + const firstBBoxIndex = bBoxVertexCopyResult.originalStartIndex; + const lastBBoxIndex = bBoxVertexCopyResult.originalEndIndex; + // Copy over segments. copySegments(['segmentLines', 'segmentLineNormals'], 'segmentLineRanges', @@ -361,9 +398,11 @@ export class PathfinderMeshData implements Meshes, MeshDataCounts, const rangeBufferKey = RANGE_TO_RANGE_BUFFER_TABLE[rangeKey]; const instanceCount = this[RANGE_TO_COUNT_TABLE[rangeKey]]; - const fieldCount = MESH_TYPES[rangeBufferKey].size; const ranges = this[rangeKey as keyof PathRanges]; + const meshType = MESH_TYPES[rangeBufferKey]; + const fieldCount = meshType.size; + const destBuffer = new Uint16Array(instanceCount * fieldCount); let destIndex = 0; for (let pathIndex = 0; pathIndex < ranges.length; pathIndex++) { @@ -385,6 +424,10 @@ export class PathfinderMeshBuffers implements Meshes, PathRanges { readonly bQuadVertexPositions: WebGLBuffer; readonly bQuadVertexPositionPathIDs: WebGLBuffer; readonly bQuadVertexInteriorIndices: WebGLBuffer; + readonly bBoxes: WebGLBuffer; + readonly bBoxSigns: WebGLBuffer; + readonly bBoxIndices: WebGLBuffer; + readonly bBoxPathIDs: WebGLBuffer; readonly segmentLines: WebGLBuffer; readonly segmentCurves: WebGLBuffer; readonly segmentLinePathIDs: WebGLBuffer; @@ -394,6 +437,7 @@ export class PathfinderMeshBuffers implements Meshes, PathRanges { readonly bQuadVertexPositionPathRanges: Range[]; readonly bQuadVertexInteriorIndexPathRanges: Range[]; + readonly bBoxPathRanges: Range[]; readonly segmentCurveRanges: Range[]; readonly segmentLineRanges: Range[]; diff --git a/demo/client/src/renderer.ts b/demo/client/src/renderer.ts index 8e40b178..e480f8dd 100644 --- a/demo/client/src/renderer.ts +++ b/demo/client/src/renderer.ts @@ -267,14 +267,7 @@ export abstract class Renderer { } setTransformUniform(uniforms: UniformMap, pass: number, objectIndex: number): void { - let transform; - if (this.antialiasingStrategy == null) - transform = glmatrix.mat4.create(); - else - transform = this.antialiasingStrategy.worldTransformForPass(this, pass); - - glmatrix.mat4.mul(transform, transform, this.worldTransform); - glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex)); + const transform = this.computeTransform(pass, objectIndex); this.renderContext.gl.uniformMatrix4fv(uniforms.uTransform, false, transform); } @@ -284,10 +277,7 @@ export abstract class Renderer { const renderContext = this.renderContext; const gl = renderContext.gl; - const transform = glmatrix.mat4.clone(this.worldTransform); - glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex)); - - const translation = glmatrix.vec4.clone([transform[12], transform[13], 0.0, 1.0]); + const transform = this.computeTransform(0, objectIndex); gl.uniform4f(uniforms.uTransformST, transform[0], @@ -296,6 +286,22 @@ export abstract class Renderer { transform[13]); } + setTransformAffineUniforms(uniforms: UniformMap, objectIndex: number): void { + // FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an affine matrix is ugly and + // fragile. Refactor. + const renderContext = this.renderContext; + const gl = renderContext.gl; + + const transform = this.computeTransform(0, objectIndex); + + gl.uniform4f(uniforms.uTransformST, + transform[0], + transform[5], + transform[12], + transform[13]); + gl.uniform2f(uniforms.uTransformExt, transform[1], transform[4]); + } + uploadPathColors(objectCount: number): void { const renderContext = this.renderContext; for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) { @@ -481,7 +487,10 @@ export abstract class Renderer { this.initImplicitCoverInteriorVAO(objectIndex, instanceRange, renderingMode); // Draw direct interior parts. - this.setTransformUniform(directInteriorProgram.uniforms, pass, objectIndex); + if (renderingMode === 'conservative') + this.setTransformAffineUniforms(directInteriorProgram.uniforms, objectIndex); + else + this.setTransformUniform(directInteriorProgram.uniforms, pass, objectIndex); this.setFramebufferSizeUniform(directInteriorProgram.uniforms); this.setHintsUniform(directInteriorProgram.uniforms); this.setPathColorsUniform(objectIndex, directInteriorProgram.uniforms, 0); @@ -737,6 +746,18 @@ export abstract class Renderer { gl.bufferData(gl.ARRAY_BUFFER, vertexIDs, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null); } + + private computeTransform(pass: number, objectIndex: number): glmatrix.mat4 { + let transform; + if (this.antialiasingStrategy == null) + transform = glmatrix.mat4.create(); + else + transform = this.antialiasingStrategy.worldTransformForPass(this, pass); + + glmatrix.mat4.mul(transform, transform, this.worldTransform); + glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex)); + return transform; + } } function getMeshIndexRange(indexRanges: Range[], pathRange: Range): Range { diff --git a/demo/client/src/view.ts b/demo/client/src/view.ts index 1595402a..02c06ecf 100644 --- a/demo/client/src/view.ts +++ b/demo/client/src/view.ts @@ -272,6 +272,7 @@ export abstract class DemoView extends PathfinderView implements RenderContext { this.vertexArrayObjectExt = this.gl.getExtension('OES_vertex_array_object'); this.gl.getExtension('EXT_frag_depth'); this.gl.getExtension('OES_element_index_uint'); + this.gl.getExtension('OES_standard_derivatives'); this.gl.getExtension('OES_texture_float'); this.gl.getExtension('WEBGL_depth_texture'); diff --git a/demo/client/src/xcaa-strategy.ts b/demo/client/src/xcaa-strategy.ts index ed4d9141..ee9aa29d 100644 --- a/demo/client/src/xcaa-strategy.ts +++ b/demo/client/src/xcaa-strategy.ts @@ -42,10 +42,12 @@ const PATCH_VERTICES: Float32Array = new Float32Array([ 1.0, 1.0, ]); -const MCAA_PATCH_INDICES: Uint8Array = new Uint8Array([0, 1, 2, 0, 2, 3, 2, 5, 3, 3, 5, 4]); +const MCAA_PATCH_INDICES: Uint8Array = new Uint8Array([0, 1, 2, 1, 3, 2]); const ECAA_CURVE_PATCH_INDICES: Uint8Array = new Uint8Array([0, 1, 2, 0, 2, 3, 2, 5, 3]); +export type TransformType = 'dilation' | 'affine' | '3d'; + export abstract class XCAAStrategy extends AntialiasingStrategy { abstract readonly directRenderingMode: DirectRenderingMode; @@ -56,7 +58,7 @@ export abstract class XCAAStrategy extends AntialiasingStrategy { return 1; } - protected abstract get usesDilationTransforms(): boolean; + protected abstract get transformType(): TransformType; protected abstract get patchIndices(): Uint8Array; @@ -255,10 +257,17 @@ export abstract class XCAAStrategy extends AntialiasingStrategy { const renderContext = renderer.renderContext; const gl = renderContext.gl; - if (this.usesDilationTransforms) + switch (this.transformType) { + case 'dilation': renderer.setTransformSTUniform(uniforms, 0); - else + break; + case 'affine': + renderer.setTransformAffineUniforms(uniforms, 0); + break; + case '3d': renderer.setTransformUniform(uniforms, 0, 0); + break; + } gl.uniform2i(uniforms.uFramebufferSize, this.supersampledFramebufferSize[0], @@ -372,8 +381,8 @@ export class MCAAStrategy extends XCAAStrategy { return MCAA_PATCH_INDICES; } - protected get usesDilationTransforms(): boolean { - return true; + protected get transformType(): TransformType { + return 'affine'; } protected get mightUseAAFramebuffer(): boolean { @@ -434,9 +443,7 @@ export class MCAAStrategy extends XCAAStrategy { gl.depthFunc(gl.GREATER); gl.depthMask(false); gl.enable(gl.DEPTH_TEST); - gl.frontFace(gl.CCW); - gl.cullFace(gl.BACK); - gl.enable(gl.CULL_FACE); + gl.disable(gl.CULL_FACE); } protected clearForResolve(renderer: Renderer): void { @@ -485,80 +492,68 @@ export class MCAAStrategy extends XCAAStrategy { const vao = this.vao; renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao); - const bQuadRanges = renderer.meshData[meshIndex].bQuadVertexPositionPathRanges; - const offset = calculateStartFromIndexRanges(pathRange, bQuadRanges); + const bBoxRanges = renderer.meshData[meshIndex].bBoxPathRanges; + const offset = calculateStartFromIndexRanges(pathRange, bBoxRanges); gl.useProgram(shaderProgram.program); - gl.bindBuffer(gl.ARRAY_BUFFER, this.patchVertexBuffer); - gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, 0, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshes[meshIndex].bQuadVertexPositions); - gl.vertexAttribPointer(attributes.aUpperLeftEndpointPosition, - 2, + gl.bindBuffer(gl.ARRAY_BUFFER, renderer.renderContext.quadPositionsBuffer); + gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, FLOAT32_SIZE * 2, 0); + gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshes[meshIndex].bBoxes); + gl.vertexAttribPointer(attributes.aRect, + 4, gl.FLOAT, false, - FLOAT32_SIZE * 12, - FLOAT32_SIZE * 12 * offset + FLOAT32_SIZE * 0); - gl.vertexAttribPointer(attributes.aUpperControlPointPosition, - 2, + FLOAT32_SIZE * 20, + FLOAT32_SIZE * 0 + offset * FLOAT32_SIZE * 20); + gl.vertexAttribPointer(attributes.aUV, + 4, gl.FLOAT, false, - FLOAT32_SIZE * 12, - FLOAT32_SIZE * 12 * offset + FLOAT32_SIZE * 2); - gl.vertexAttribPointer(attributes.aUpperRightEndpointPosition, - 2, + FLOAT32_SIZE * 20, + FLOAT32_SIZE * 4 + offset * FLOAT32_SIZE * 20); + gl.vertexAttribPointer(attributes.aDUVDX, + 4, gl.FLOAT, false, - FLOAT32_SIZE * 12, - FLOAT32_SIZE * 12 * offset + FLOAT32_SIZE * 4); - gl.vertexAttribPointer(attributes.aLowerRightEndpointPosition, - 2, + FLOAT32_SIZE * 20, + FLOAT32_SIZE * 8 + offset * FLOAT32_SIZE * 20); + gl.vertexAttribPointer(attributes.aDUVDY, + 4, gl.FLOAT, false, - FLOAT32_SIZE * 12, - FLOAT32_SIZE * 12 * offset + FLOAT32_SIZE * 6); - gl.vertexAttribPointer(attributes.aLowerControlPointPosition, - 2, + FLOAT32_SIZE * 20, + FLOAT32_SIZE * 12 + offset * FLOAT32_SIZE * 20); + gl.vertexAttribPointer(attributes.aSignMode, + 4, gl.FLOAT, false, - FLOAT32_SIZE * 12, - FLOAT32_SIZE * 12 * offset + FLOAT32_SIZE * 8); - gl.vertexAttribPointer(attributes.aLowerLeftEndpointPosition, - 2, - gl.FLOAT, - false, - FLOAT32_SIZE * 12, - FLOAT32_SIZE * 12 * offset + FLOAT32_SIZE * 10); - renderContext.instancedArraysExt - .vertexAttribDivisorANGLE(attributes.aUpperLeftEndpointPosition, 1); - renderContext.instancedArraysExt - .vertexAttribDivisorANGLE(attributes.aUpperControlPointPosition, 1); - renderContext.instancedArraysExt - .vertexAttribDivisorANGLE(attributes.aUpperRightEndpointPosition, 1); - renderContext.instancedArraysExt - .vertexAttribDivisorANGLE(attributes.aLowerRightEndpointPosition, 1); - renderContext.instancedArraysExt - .vertexAttribDivisorANGLE(attributes.aLowerControlPointPosition, 1); - renderContext.instancedArraysExt - .vertexAttribDivisorANGLE(attributes.aLowerLeftEndpointPosition, 1); + FLOAT32_SIZE * 20, + FLOAT32_SIZE * 16 + offset * FLOAT32_SIZE * 20); - gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshes[meshIndex].bQuadVertexPositionPathIDs); + gl.enableVertexAttribArray(attributes.aTessCoord); + gl.enableVertexAttribArray(attributes.aRect); + gl.enableVertexAttribArray(attributes.aUV); + gl.enableVertexAttribArray(attributes.aDUVDX); + gl.enableVertexAttribArray(attributes.aDUVDY); + gl.enableVertexAttribArray(attributes.aSignMode); + + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aRect, 1); + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aUV, 1); + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aDUVDX, 1); + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aDUVDY, 1); + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aSignMode, 1); + + gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshes[meshIndex].bBoxPathIDs); gl.vertexAttribPointer(attributes.aPathID, 1, gl.UNSIGNED_SHORT, false, - UINT16_SIZE * 6, - UINT16_SIZE * offset); + UINT16_SIZE, + offset * UINT16_SIZE); + gl.enableVertexAttribArray(attributes.aPathID); renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1); - gl.enableVertexAttribArray(attributes.aTessCoord); - gl.enableVertexAttribArray(attributes.aUpperLeftEndpointPosition); - gl.enableVertexAttribArray(attributes.aUpperControlPointPosition); - gl.enableVertexAttribArray(attributes.aUpperRightEndpointPosition); - gl.enableVertexAttribArray(attributes.aLowerRightEndpointPosition); - gl.enableVertexAttribArray(attributes.aLowerControlPointPosition); - gl.enableVertexAttribArray(attributes.aLowerLeftEndpointPosition); - gl.enableVertexAttribArray(attributes.aPathID); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.patchIndexBuffer); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderer.renderContext.quadElementsBuffer); renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); } @@ -571,7 +566,7 @@ export class MCAAStrategy extends XCAAStrategy { objectIndex: number, shaderProgram: PathfinderShaderProgram): void { - if (renderer.meshData == null) + if (renderer.meshes == null || renderer.meshData == null) return; const renderContext = renderer.renderContext; @@ -593,11 +588,13 @@ export class MCAAStrategy extends XCAAStrategy { this.setBlendModeForAA(renderer); this.setAADepthState(renderer); - const bQuadRanges = renderer.meshData[meshIndex].bQuadVertexPositionPathRanges; - const count = calculateCountFromIndexRanges(pathRange, bQuadRanges); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer); + + const bBoxRanges = renderer.meshData[meshIndex].bBoxPathRanges; + const count = calculateCountFromIndexRanges(pathRange, bBoxRanges); renderContext.instancedArraysExt - .drawElementsInstancedANGLE(gl.TRIANGLES, 12, gl.UNSIGNED_BYTE, 0, count); + .drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count); renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); gl.disable(gl.DEPTH_TEST); @@ -641,8 +638,8 @@ export class ECAAStrategy extends XCAAStrategy { private lineVAOs: Partial>; private curveVAOs: Partial>; - protected get usesDilationTransforms(): boolean { - return false; + protected get transformType(): TransformType { + return '3d'; } get directRenderingMode(): DirectRenderingMode { diff --git a/partitioner/src/mesh_library.rs b/partitioner/src/mesh_library.rs index 4e65d4b7..b65514f6 100644 --- a/partitioner/src/mesh_library.rs +++ b/partitioner/src/mesh_library.rs @@ -10,7 +10,8 @@ use bincode::{self, Infinite}; use byteorder::{LittleEndian, WriteBytesExt}; -use euclid::{Point2D, Vector2D}; +use euclid::approxeq::ApproxEq; +use euclid::{Point2D, Rect, Size2D, Vector2D}; use lyon_path::PathEvent; use lyon_path::iterator::PathIterator; use pathfinder_path_utils::normals::PathNormals; @@ -32,6 +33,7 @@ pub struct MeshLibrary { pub b_quad_vertex_interior_indices: Vec, pub b_vertex_positions: Vec>, pub b_vertex_loop_blinn_data: Vec, + pub b_boxes: Vec, pub segments: MeshLibrarySegments, pub segment_normals: MeshLibrarySegmentNormals, } @@ -46,6 +48,7 @@ impl MeshLibrary { b_quad_vertex_interior_indices: vec![], b_vertex_positions: vec![], b_vertex_loop_blinn_data: vec![], + b_boxes: vec![], segments: MeshLibrarySegments::new(), segment_normals: MeshLibrarySegmentNormals::new(), } @@ -58,6 +61,7 @@ impl MeshLibrary { self.b_quad_vertex_interior_indices.clear(); self.b_vertex_positions.clear(); self.b_vertex_loop_blinn_data.clear(); + self.b_boxes.clear(); self.segments.clear(); self.segment_normals.clear(); } @@ -78,45 +82,121 @@ impl MeshLibrary { } pub(crate) fn add_b_quad(&mut self, b_quad: &BQuad) { + let BQuadVertexPositions { + upper_left_vertex_position: ul, + upper_right_vertex_position: ur, + lower_left_vertex_position: ll, + lower_right_vertex_position: lr, + .. + } = self.get_b_quad_vertex_positions(b_quad); + + if ul.x.approx_eq(&ur.x) || ll.x.approx_eq(&lr.x) { + return + } + self.b_quads.push(*b_quad); - let upper_left_position = - self.b_vertex_positions[b_quad.upper_left_vertex_index as usize]; - let upper_right_position = - self.b_vertex_positions[b_quad.upper_right_vertex_index as usize]; - let lower_left_position = - self.b_vertex_positions[b_quad.lower_left_vertex_index as usize]; - let lower_right_position = - self.b_vertex_positions[b_quad.lower_right_vertex_index as usize]; - - let mut b_quad_vertex_positions = BQuadVertexPositions { - upper_left_vertex_position: upper_left_position, - upper_control_point_position: upper_left_position, - upper_right_vertex_position: upper_right_position, - lower_left_vertex_position: lower_left_position, - lower_control_point_position: lower_right_position, - lower_right_vertex_position: lower_right_position, - }; - - if b_quad.upper_control_point_vertex_index != u32::MAX { - let upper_control_point_position = - self.b_vertex_positions[b_quad.upper_control_point_vertex_index as usize]; - b_quad_vertex_positions.upper_control_point_position = upper_control_point_position; - } - - if b_quad.lower_control_point_vertex_index != u32::MAX { - let lower_control_point_position = - self.b_vertex_positions[b_quad.lower_control_point_vertex_index as usize]; - b_quad_vertex_positions.lower_control_point_position = lower_control_point_position; - } + self.add_b_quad_vertex_positions(b_quad); + self.add_b_box(b_quad); + } + fn add_b_quad_vertex_positions(&mut self, b_quad: &BQuad) { + let b_quad_vertex_positions = self.get_b_quad_vertex_positions(b_quad); let first_b_quad_vertex_position_index = (self.b_quad_vertex_positions.len() as u32) * 6; self.push_b_quad_vertex_position_interior_indices(first_b_quad_vertex_position_index, &b_quad_vertex_positions); - self.b_quad_vertex_positions.push(b_quad_vertex_positions); } + fn add_b_box(&mut self, b_quad: &BQuad) { + let BQuadVertexPositions { + upper_left_vertex_position: ul, + upper_control_point_position: uc, + upper_right_vertex_position: ur, + lower_left_vertex_position: ll, + lower_control_point_position: lc, + lower_right_vertex_position: lr, + } = self.get_b_quad_vertex_positions(b_quad); + + let rect = Rect::from_points([ul, uc, ur, ll, lc, lr].into_iter()); + + let (edge_ucl, edge_urc, edge_ulr) = (uc - ul, ur - uc, ul - ur); + let (edge_lcl, edge_lrc, edge_llr) = (lc - ll, lr - lc, ll - lr); + + let (edge_len_ucl, edge_len_urc) = (edge_ucl.length(), edge_urc.length()); + let (edge_len_lcl, edge_len_lrc) = (edge_lcl.length(), edge_lrc.length()); + let (edge_len_ulr, edge_len_llr) = (edge_ulr.length(), edge_llr.length()); + + let (uv_upper, uv_lower, sign_upper, sign_lower, mode_upper, mode_lower); + + if edge_len_ucl < 0.01 || edge_len_urc < 0.01 || edge_len_ulr < 0.01 || + edge_ucl.dot(-edge_ulr) > 0.9999 * edge_len_ucl * edge_len_ulr { + uv_upper = Uv::line(&rect, &ul, &ur); + sign_upper = -1.0; + mode_upper = -1.0; + } else { + uv_upper = Uv::curve(&rect, &ul, &uc, &ur); + sign_upper = (edge_ucl.cross(-edge_ulr)).signum(); + mode_upper = 1.0; + } + + if edge_len_lcl < 0.01 || edge_len_lrc < 0.01 || edge_len_llr < 0.01 || + edge_lcl.dot(-edge_llr) > 0.9999 * edge_len_lcl * edge_len_llr { + uv_lower = Uv::line(&rect, &ll, &lr); + sign_lower = 1.0; + mode_lower = -1.0; + } else { + uv_lower = Uv::curve(&rect, &ll, &lc, &lr); + sign_lower = -(edge_lcl.cross(-edge_llr)).signum(); + mode_lower = 1.0; + } + + let b_box = BBox { + upper_left_position: rect.origin, + lower_right_position: rect.bottom_right(), + upper_left_uv_upper: uv_upper.origin, + upper_left_uv_lower: uv_lower.origin, + d_upper_uv_dx: uv_upper.d_uv_dx, + d_lower_uv_dx: uv_lower.d_uv_dx, + d_upper_uv_dy: uv_upper.d_uv_dy, + d_lower_uv_dy: uv_lower.d_uv_dy, + upper_sign: sign_upper, + lower_sign: sign_lower, + upper_mode: mode_upper, + lower_mode: mode_lower, + }; + + self.b_boxes.push(b_box); + } + + fn get_b_quad_vertex_positions(&self, b_quad: &BQuad) -> BQuadVertexPositions { + let ul = self.b_vertex_positions[b_quad.upper_left_vertex_index as usize]; + let ur = self.b_vertex_positions[b_quad.upper_right_vertex_index as usize]; + let ll = self.b_vertex_positions[b_quad.lower_left_vertex_index as usize]; + let lr = self.b_vertex_positions[b_quad.lower_right_vertex_index as usize]; + + let mut b_quad_vertex_positions = BQuadVertexPositions { + upper_left_vertex_position: ul, + upper_control_point_position: ul, + upper_right_vertex_position: ur, + lower_left_vertex_position: ll, + lower_control_point_position: lr, + lower_right_vertex_position: lr, + }; + + if b_quad.upper_control_point_vertex_index != u32::MAX { + let uc = &self.b_vertex_positions[b_quad.upper_control_point_vertex_index as usize]; + b_quad_vertex_positions.upper_control_point_position = *uc; + } + + if b_quad.lower_control_point_vertex_index != u32::MAX { + let lc = &self.b_vertex_positions[b_quad.lower_control_point_vertex_index as usize]; + b_quad_vertex_positions.lower_control_point_position = *lc; + } + + b_quad_vertex_positions + } + fn push_b_quad_vertex_position_interior_indices(&mut self, first_vertex_index: u32, b_quad: &BQuadVertexPositions) { @@ -282,6 +362,7 @@ impl MeshLibrary { try!(write_simple_chunk(writer, b"bqua", &self.b_quads)); try!(write_simple_chunk(writer, b"bqvp", &self.b_quad_vertex_positions)); try!(write_simple_chunk(writer, b"bqii", &self.b_quad_vertex_interior_indices)); + try!(write_simple_chunk(writer, b"bbox", &self.b_boxes)); try!(write_simple_chunk(writer, b"slin", &self.segments.lines)); try!(write_simple_chunk(writer, b"scur", &self.segments.curves)); try!(write_simple_chunk(writer, b"snli", &self.segment_normals.line_normals)); @@ -331,6 +412,7 @@ impl MeshLibrary { path_ranges, |ranges| &ranges.b_quad_vertex_interior_indices)); try!(write_path_range(writer, b"bver", path_ranges, |ranges| &ranges.b_vertices)); + try!(write_path_range(writer, b"bbox", path_ranges, |ranges| &ranges.b_boxes)); try!(write_path_range(writer, b"slin", path_ranges, |ranges| &ranges.segment_lines)); try!(write_path_range(writer, b"scur", path_ranges, |ranges| &ranges.segment_curves)); Ok(()) @@ -359,6 +441,7 @@ impl MeshLibrary { b_quad_vertex_positions: self.b_quad_vertex_positions.len() as u32, b_quad_vertex_interior_indices: self.b_quad_vertex_interior_indices.len() as u32, b_vertices: self.b_vertex_positions.len() as u32, + b_boxes: self.b_boxes.len() as u32, } } } @@ -374,6 +457,7 @@ pub(crate) struct MeshLibraryLengths { b_quad_vertex_positions: u32, b_quad_vertex_interior_indices: u32, b_vertices: u32, + b_boxes: u32, } #[derive(Clone, Debug)] @@ -382,6 +466,7 @@ pub struct PathRanges { pub b_quad_vertex_positions: Range, pub b_quad_vertex_interior_indices: Range, pub b_vertices: Range, + pub b_boxes: Range, pub segment_lines: Range, pub segment_curves: Range, } @@ -393,6 +478,7 @@ impl PathRanges { b_quad_vertex_positions: 0..0, b_quad_vertex_interior_indices: 0..0, b_vertices: 0..0, + b_boxes: 0..0, segment_lines: 0..0, segment_curves: 0..0, } @@ -406,6 +492,7 @@ impl PathRanges { self.b_quad_vertex_interior_indices = start.b_quad_vertex_interior_indices..end.b_quad_vertex_interior_indices; self.b_vertices = start.b_vertices..end.b_vertices; + self.b_boxes = start.b_boxes..end.b_boxes; } } @@ -474,3 +561,134 @@ pub struct CurveSegmentNormals { pub control_point: f32, pub endpoint_1: f32, } + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct BBox { + pub upper_left_position: Point2D, + pub lower_right_position: Point2D, + pub upper_left_uv_upper: Point2D, + pub upper_left_uv_lower: Point2D, + pub d_upper_uv_dx: Vector2D, + pub d_lower_uv_dx: Vector2D, + pub d_upper_uv_dy: Vector2D, + pub d_lower_uv_dy: Vector2D, + pub upper_sign: f32, + pub lower_sign: f32, + pub upper_mode: f32, + pub lower_mode: f32, +} + +#[derive(Clone, Copy, Debug)] +struct CornerPositions { + upper_left: Point2D, + upper_right: Point2D, + lower_left: Point2D, + lower_right: Point2D, +} + +#[derive(Clone, Copy, Debug)] +struct CornerValues { + upper_left: Point2D, + upper_right: Point2D, + lower_left: Point2D, + lower_right: Point2D, +} + +#[derive(Clone, Copy, Debug)] +struct Uv { + origin: Point2D, + d_uv_dx: Vector2D, + d_uv_dy: Vector2D, +} + +impl Uv { + fn from_values(origin: &Point2D, origin_right: &Point2D, origin_down: &Point2D) + -> Uv { + Uv { + origin: *origin, + d_uv_dx: *origin_right - *origin, + d_uv_dy: *origin_down - *origin, + } + } + + fn curve(rect: &Rect, left: &Point2D, ctrl: &Point2D, right: &Point2D) + -> Uv { + let origin_right = rect.top_right(); + let origin_down = rect.bottom_left(); + + let (lambda_origin, denom) = to_barycentric(left, ctrl, right, &rect.origin); + let (lambda_origin_right, _) = to_barycentric(left, ctrl, right, &origin_right); + let (lambda_origin_down, _) = to_barycentric(left, ctrl, right, &origin_down); + + let uv_origin = lambda_to_uv(&lambda_origin, denom); + let uv_origin_right = lambda_to_uv(&lambda_origin_right, denom); + let uv_origin_down = lambda_to_uv(&lambda_origin_down, denom); + + return Uv::from_values(&uv_origin, &uv_origin_right, &uv_origin_down); + + // https://gamedev.stackexchange.com/a/23745 + fn to_barycentric(a: &Point2D, b: &Point2D, c: &Point2D, p: &Point2D) + -> ([f64; 2], f64) { + let (a, b, c, p) = (a.to_f64(), b.to_f64(), c.to_f64(), p.to_f64()); + let (v0, v1, v2) = (b - a, c - a, p - a); + let (d00, d01) = (v0.dot(v0), v0.dot(v1)); + let d11 = v1.dot(v1); + let (d20, d21) = (v2.dot(v0), v2.dot(v1)); + let denom = d00 * d11 - d01 * d01; + ([(d11 * d20 - d01 * d21), (d00 * d21 - d01 * d20)], denom) + } + + fn lambda_to_uv(lambda: &[f64; 2], denom: f64) -> Point2D { + (Point2D::new(lambda[0] * 0.5 + lambda[1], lambda[1]) / denom).to_f32() + } + } + + fn line(rect: &Rect, left: &Point2D, right: &Point2D) -> Uv { + let (values, line_bounds); + if f32::abs(left.y - right.y) < 0.01 { + values = CornerValues { + upper_left: Point2D::new(0.0, 0.5), + upper_right: Point2D::new(0.5, 1.0), + lower_right: Point2D::new(1.0, 0.5), + lower_left: Point2D::new(0.5, 0.0), + }; + line_bounds = Rect::new(*left + Vector2D::new(0.0, -1.0), + Size2D::new(right.x - left.x, 2.0)); + } else { + if left.y < right.y { + values = CornerValues { + upper_left: Point2D::new(1.0, 1.0), + upper_right: Point2D::new(0.0, 1.0), + lower_left: Point2D::new(1.0, 0.0), + lower_right: Point2D::new(0.0, 0.0), + }; + } else { + values = CornerValues { + upper_left: Point2D::new(0.0, 1.0), + upper_right: Point2D::new(1.0, 1.0), + lower_left: Point2D::new(0.0, 0.0), + lower_right: Point2D::new(1.0, 0.0), + }; + } + line_bounds = Rect::from_points([*left, *right].into_iter()); + } + + let origin_right = rect.top_right(); + let origin_down = rect.bottom_left(); + + let uv_origin = bilerp(&line_bounds, &values, &rect.origin); + let uv_origin_right = bilerp(&line_bounds, &values, &origin_right); + let uv_origin_down = bilerp(&line_bounds, &values, &origin_down); + + return Uv::from_values(&uv_origin, &uv_origin_right, &uv_origin_down); + + fn bilerp(rect: &Rect, values: &CornerValues, position: &Point2D) + -> Point2D { + let upper = values.upper_left.lerp(values.upper_right, + (position.x - rect.min_x()) / rect.size.width); + let lower = values.lower_left.lerp(values.lower_right, + (position.x - rect.min_x()) / rect.size.width); + upper.lerp(lower, (position.y - rect.min_y()) / rect.size.height) + } + } +} diff --git a/shaders/gles2/common.inc.glsl b/shaders/gles2/common.inc.glsl index 58278be5..116e2cdd 100644 --- a/shaders/gles2/common.inc.glsl +++ b/shaders/gles2/common.inc.glsl @@ -11,6 +11,7 @@ #version 100 #extension GL_EXT_frag_depth : require +#extension GL_OES_standard_derivatives : require #define LCD_FILTER_FACTOR_0 (86.0 / 255.0) #define LCD_FILTER_FACTOR_1 (77.0 / 255.0) @@ -104,65 +105,11 @@ float convertPathIndexToViewportDepthValue(int pathIndex) { return float(pathIndex) / float(MAX_PATHS) * 2.0 - 1.0; } -/// Packs the given path ID into a floating point value suitable for storage in the depth buffer. -/// -/// This function returns values in window space (i.e. what `gl_FragDepth`/`gl_FragDepthEXT` is -/// in). -float convertPathIndexToWindowDepthValue(int pathIndex) { - return float(pathIndex) / float(MAX_PATHS); -} - /// Displaces the given point by the given distance in the direction of the normal angle. vec2 dilatePosition(vec2 position, float normalAngle, vec2 amount) { return position + vec2(cos(normalAngle), -sin(normalAngle)) * amount; } -vec2 offsetPositionVertically(vec2 position, ivec2 framebufferSize, bool roundUp) { - position = convertClipToScreenSpace(position, framebufferSize); - position.y = roundUp ? ceil(position.y + 1.0) : floor(position.y - 1.0); - return convertScreenToClipSpace(position, framebufferSize); -} - -vec2 computeMCAAPosition(vec2 position, - vec4 hints, - vec4 localTransformST, - vec4 globalTransformST, - ivec2 framebufferSize) { - if (position == vec2(0.0)) - return position; - - position = hintPosition(position, hints); - position = transformVertexPositionST(position, localTransformST); - position = transformVertexPositionST(position, globalTransformST); - return convertClipToScreenSpace(position, framebufferSize); -} - -vec2 computeMCAASnappedPosition(vec2 position, - vec4 hints, - vec4 localTransformST, - vec4 globalTransformST, - ivec2 framebufferSize, - float slope, - bool snapToPixelGrid) { - position = hintPosition(position, hints); - position = transformVertexPositionST(position, localTransformST); - position = transformVertexPositionST(position, globalTransformST); - position = convertClipToScreenSpace(position, framebufferSize); - - float xNudge; - if (snapToPixelGrid) { - xNudge = fract(position.x); - if (xNudge < 0.5) - xNudge = -xNudge; - else - xNudge = 1.0 - xNudge; - } else { - xNudge = 0.0; - } - - return position + vec2(xNudge, xNudge * slope); -} - vec2 transformECAAPosition(vec2 position, vec4 localTransformST, vec2 localTransformExt, @@ -409,21 +356,6 @@ float computeCoverage(vec2 p0X, vec2 dPX, float pixelCenterY, float winding) { return abs(area) * winding * 0.5; } -/// Returns true if the line runs through this pixel or false otherwise. -/// -/// The line must run left-to-right and must already be clipped to the left and right sides of the -/// pixel, which implies that `dP.x` must be within the range [0.0, 1.0]. -/// -/// * `p0X` is the start point of the line. -/// * `dPX` is the vector from the start point to the endpoint of the line. -/// * `pixelCenterY` is the Y coordinate of the center of the pixel in window coordinates (i.e. -/// `gl_FragCoord.y`). -bool isPartiallyCovered(vec2 p0X, vec2 dPX, float pixelCenterY) { - float pixelTop; - vec2 dP = clipLineToPixelRow(p0X, dPX, pixelCenterY, pixelTop).zw; - return !isNearZero(dP.x) || !isNearZero(dP.y); -} - /// Solves the equation: /// /// x = p0x + t^2 * (p0x - 2*p1x + p2x) + t*(2*p1x - 2*p0x) @@ -482,3 +414,11 @@ vec4 fetchPathAffineTransform(out vec2 outPathTransformExt, pathTransformExtDimensions); return fetchFloat4Data(pathTransformSTTexture, pathID, pathTransformSTDimensions); } + +float detMat2(mat2 m) { + return m[0][0] * m[1][1] - m[0][1] * m[1][0]; +} + +mat2 invMat2(mat2 m) { + return mat2(m[1][1], -m[0][1], -m[1][0], m[0][0]) / detMat2(m); +} diff --git a/shaders/gles2/conservative-interior.vs.glsl b/shaders/gles2/conservative-interior.vs.glsl index be76e8b7..84499e39 100644 --- a/shaders/gles2/conservative-interior.vs.glsl +++ b/shaders/gles2/conservative-interior.vs.glsl @@ -1,6 +1,6 @@ -// pathfinder/shaders/gles2/direct-interior.vs.glsl +// pathfinder/shaders/gles2/conservative-interior.vs.glsl // -// Copyright (c) 2017 The Pathfinder Project Developers. +// Copyright (c) 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -17,8 +17,9 @@ precision highp float; -/// A 3D transform to be applied to all points. -uniform mat4 uTransform; +/// An affine transform to be applied to all points. +uniform vec4 uTransformST; +uniform vec2 uTransformExt; /// Vertical snapping positions. uniform vec4 uHints; /// The framebuffer size in pixels. @@ -52,21 +53,21 @@ void main() { int pathID = int(aPathID); int vertexID = int(aVertexID); - vec2 pathTransformExt; - vec4 pathTransformST = fetchPathAffineTransform(pathTransformExt, - uPathTransformST, - uPathTransformSTDimensions, - uPathTransformExt, - uPathTransformExtDimensions, - pathID); + vec4 transformST = fetchFloat4Data(uPathTransformST, pathID, uPathTransformSTDimensions); - vec2 position = hintPosition(aPosition, uHints); - position = transformVertexPositionAffine(position, pathTransformST, pathTransformExt); - position = transformVertexPosition(position, uTransform); - position = offsetPositionVertically(position, uFramebufferSize, imod(vertexID, 6) < 3); + mat2 globalTransformLinear = mat2(uTransformST.x, uTransformExt, uTransformST.y); + mat2 localTransformLinear = mat2(transformST.x, 0.0, 0.0, transformST.y); + mat2 transformLinear = globalTransformLinear * localTransformLinear; + vec2 translation = uTransformST.zw + globalTransformLinear * transformST.zw; + + float onePixel = 2.0 / float(uFramebufferSize.y); + float dilation = length(invMat2(transformLinear) * vec2(0.0, onePixel)); + + vec2 position = aPosition + vec2(0.0, imod(vertexID, 6) < 3 ? dilation : -dilation); + position = transformLinear * position + translation; float depth = convertPathIndexToViewportDepthValue(pathID); - gl_Position = vec4(position, depth, 1.0); + gl_Position = vec4(position, depth, 1.0); vColor = fetchFloat4Data(uPathColors, pathID, uPathColorsDimensions); } diff --git a/shaders/gles2/mcaa.fs.glsl b/shaders/gles2/mcaa.fs.glsl index 3ad7fae3..83b8baab 100644 --- a/shaders/gles2/mcaa.fs.glsl +++ b/shaders/gles2/mcaa.fs.glsl @@ -1,6 +1,6 @@ // pathfinder/shaders/gles2/mcaa.fs.glsl // -// Copyright (c) 2017 The Pathfinder Project Developers. +// Copyright (c) 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -12,68 +12,43 @@ //! (MCAA). This one shader handles both lines and curves. //! //! This shader expects to render to a standard RGB color buffer. -//! -//! Use this shader only when *both* of the following are true: -//! -//! 1. You are rendering multiple multicolor paths. (Otherwise, consider the -//! other MCAA shaders, which render with higher quality.) -//! -//! 2. Your transform is only a scale and/or translation, not a perspective, -//! rotation, or skew. (Otherwise, consider repartitioning the path to -//! generate a new mesh, or, alternatively, use the direct Loop-Blinn -//! shaders.) precision highp float; -/// True if multiple colors are being rendered; false otherwise. -/// -/// If this is true, then points will be snapped to the nearest pixel. -uniform bool uMulticolor; - -varying vec4 vUpperEndpoints; -varying vec4 vLowerEndpoints; -varying vec4 vControlPoints; varying vec4 vColor; +varying vec4 vUV; +varying vec4 vSignMode; -float computeCoverageForSide(vec2 p0, vec2 cp, vec2 p1, float winding) { - // Compute pixel extents. - vec2 pixelCenter = gl_FragCoord.xy; - vec2 pixelColumnBounds = pixelCenter.xx + vec2(-0.5, 0.5); +// Cubic approximation to the square area coverage, accurate to about 4%. +float estimateArea(float dist) { + if (dist >= 0.707107) + return 1.0; + // Catch NaNs here. + if (!(dist > -0.707107)) + return 0.0; + return 0.5 + 1.14191 * dist - 0.83570 * dist * dist * dist; +} - vec2 clippedP0, clippedDP; - if (cp == vec2(0.0)) { - vec4 p0DPX = clipLineToPixelColumn(p0, p1 - p0, pixelCenter.x); - clippedP0 = p0DPX.xy; - clippedDP = p0DPX.zw; - } else { - // Clip the curve to the left and right edges to create a line. - vec2 t = solveCurveT(p0.x, cp.x, p1.x, pixelColumnBounds); +float computeAlpha(vec2 uv, float curveSign, float mode) { + vec2 dUVDX = dFdx(uv), dUVDY = dFdy(uv); - // Handle endpoints properly. These tests are negated to handle NaNs. - if (!(p0.x < pixelColumnBounds.x)) - t.x = 0.0; - if (!(p1.x > pixelColumnBounds.y)) - t.y = 1.0; - - clippedP0 = mix(mix(p0, cp, t.x), mix(cp, p1, t.x), t.x); - clippedDP = mix(mix(p0, cp, t.y), mix(cp, p1, t.y), t.y) - clippedP0; + // u^2 - v for curves inside uv square; u - v otherwise. + float g = uv.x; + vec2 dG = vec2(dUVDX.x, dUVDY.x); + if (mode > 0.0 && uv.x > 0.0 && uv.x < 1.0 && uv.y > 0.0 && uv.y < 1.0) { + g *= uv.x; + dG *= 2.0 * uv.x; } + g -= uv.y; + dG -= vec2(dUVDX.y, dUVDY.y); - return computeCoverage(clippedP0, clippedDP, pixelCenter.y, winding); + float signedDistance = g / length(dG); + return estimateArea(signedDistance * curveSign); } void main() { - float alpha = computeCoverageForSide(vLowerEndpoints.xy, - vControlPoints.zw, - vLowerEndpoints.zw, - -1.0); - - alpha += computeCoverageForSide(vUpperEndpoints.xy, - vControlPoints.xy, - vUpperEndpoints.zw, - 1.0); - - // Compute area. - vec4 color = uMulticolor ? vColor : vec4(1.0); - gl_FragColor = alpha * color; + float alpha = 1.0; + alpha -= computeAlpha(vUV.xy, vSignMode.x, vSignMode.z); + alpha -= computeAlpha(vUV.zw, vSignMode.y, vSignMode.w); + gl_FragColor = alpha * vColor; } diff --git a/shaders/gles2/mcaa.vs.glsl b/shaders/gles2/mcaa.vs.glsl index 79cc0c0e..34035d88 100644 --- a/shaders/gles2/mcaa.vs.glsl +++ b/shaders/gles2/mcaa.vs.glsl @@ -34,10 +34,9 @@ precision highp float; -/// A dilation (scale and translation) to be applied to the object. +/// A scale and transform to be applied to the object. uniform vec4 uTransformST; -/// Vertical snapping positions. -uniform vec4 uHints; +uniform vec2 uTransformExt; /// The framebuffer size in pixels. uniform ivec2 uFramebufferSize; /// The size of the path transform buffer texture in texels. @@ -54,120 +53,47 @@ uniform sampler2D uPathColors; uniform bool uMulticolor; attribute vec2 aTessCoord; -attribute vec2 aUpperLeftEndpointPosition; -attribute vec2 aUpperControlPointPosition; -attribute vec2 aUpperRightEndpointPosition; -attribute vec2 aLowerRightEndpointPosition; -attribute vec2 aLowerControlPointPosition; -attribute vec2 aLowerLeftEndpointPosition; +attribute vec4 aRect; +attribute vec4 aUV; +attribute vec4 aDUVDX; +attribute vec4 aDUVDY; +attribute vec4 aSignMode; attribute float aPathID; -varying vec4 vUpperEndpoints; -varying vec4 vLowerEndpoints; -varying vec4 vControlPoints; varying vec4 vColor; +varying vec4 vUV; +varying vec4 vSignMode; void main() { - vec2 tlPosition = aUpperLeftEndpointPosition; - vec2 tcPosition = aUpperControlPointPosition; - vec2 trPosition = aUpperRightEndpointPosition; - vec2 blPosition = aLowerLeftEndpointPosition; - vec2 bcPosition = aLowerControlPointPosition; - vec2 brPosition = aLowerRightEndpointPosition; vec2 tessCoord = aTessCoord; int pathID = int(floor(aPathID)); + vec4 color; + if (uMulticolor) + color = fetchFloat4Data(uPathColors, pathID, uPathColorsDimensions); + else + color = vec4(1.0); + vec4 transformST = fetchFloat4Data(uPathTransformST, pathID, uPathTransformSTDimensions); - if (abs(transformST.x) > 0.001 && abs(transformST.y) > 0.001) { - vec4 color = fetchFloat4Data(uPathColors, pathID, uPathColorsDimensions); - vec2 topVector = trPosition - tlPosition, bottomVector = brPosition - blPosition; + mat2 globalTransformLinear = mat2(uTransformST.x, uTransformExt, uTransformST.y); + mat2 localTransformLinear = mat2(transformST.x, 0.0, 0.0, transformST.y); + mat2 rectTransformLinear = mat2(aRect.z - aRect.x, 0.0, 0.0, aRect.w - aRect.y); + mat2 transformLinear = globalTransformLinear * localTransformLinear * rectTransformLinear; - float topSlope = topVector.y / topVector.x; - float bottomSlope = bottomVector.y / bottomVector.x; - if (abs(topSlope) > MAX_SLOPE) - topSlope = sign(topSlope) * MAX_SLOPE; - if (abs(bottomSlope) > MAX_SLOPE) - bottomSlope = sign(bottomSlope) * MAX_SLOPE; + vec2 translation = transformST.zw + localTransformLinear * aRect.xy; + translation = uTransformST.zw + globalTransformLinear * translation; - // Transform the points, and compute the position of this vertex. - tlPosition = computeMCAASnappedPosition(tlPosition, - uHints, - transformST, - uTransformST, - uFramebufferSize, - topSlope, - uMulticolor); - trPosition = computeMCAASnappedPosition(trPosition, - uHints, - transformST, - uTransformST, - uFramebufferSize, - topSlope, - uMulticolor); - tcPosition = computeMCAAPosition(tcPosition, - uHints, - transformST, - uTransformST, - uFramebufferSize); - blPosition = computeMCAASnappedPosition(blPosition, - uHints, - transformST, - uTransformST, - uFramebufferSize, - bottomSlope, - uMulticolor); - brPosition = computeMCAASnappedPosition(brPosition, - uHints, - transformST, - uTransformST, - uFramebufferSize, - bottomSlope, - uMulticolor); - bcPosition = computeMCAAPosition(bcPosition, - uHints, - transformST, - uTransformST, - uFramebufferSize); + float onePixel = 2.0 / float(uFramebufferSize.y); + float dilation = length(invMat2(transformLinear) * vec2(0.0, onePixel)); + tessCoord.y += tessCoord.y < 0.5 ? -dilation : dilation; - float depth = convertPathIndexToViewportDepthValue(pathID); + vec2 position = transformLinear * tessCoord + translation; + vec4 uv = aUV + tessCoord.x * aDUVDX + tessCoord.y * aDUVDY; + float depth = convertPathIndexToViewportDepthValue(pathID); - // Use the same side--in this case, the top--or else floating point error during - // partitioning can occasionally cause inconsistent rounding, resulting in cracks. - vec2 position; - if (tessCoord.y < 0.5) { - if (tessCoord.x < 0.25) - position = tlPosition; - else if (tessCoord.x < 0.75) - position = tcPosition; - else - position = trPosition; - position.y = floor(position.y - 0.5); - } else { - if (tessCoord.x < 0.25) - position = blPosition; - else if (tessCoord.x < 0.75) - position = bcPosition; - else - position = brPosition; - position.y = ceil(position.y + 0.5); - } - - if (!uMulticolor) { - if (tessCoord.x < 0.25) - position.x = floor(position.x); - else if (tessCoord.x >= 0.75) - position.x = ceil(position.x); - } - - position = convertScreenToClipSpace(position, uFramebufferSize); - - gl_Position = vec4(position, depth, 1.0); - vUpperEndpoints = vec4(tlPosition, trPosition); - vLowerEndpoints = vec4(blPosition, brPosition); - vControlPoints = vec4(tcPosition, bcPosition); - vColor = color; - } else { - gl_Position = vec4(0.0); - } + gl_Position = vec4(position, depth, 1.0); + vColor = color; + vUV = uv; + vSignMode = aSignMode; }