diff --git a/Cargo.toml b/Cargo.toml
index 789129ac..e90d691d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,6 +4,7 @@ members = [
"partitioner",
"path-utils",
"demo/server",
+ "utils/area-lut",
"utils/frontend",
"utils/gamma-lut",
]
diff --git a/demo/client/html/benchmark.html.hbs b/demo/client/html/benchmark.html.hbs
index a0e83cd6..bd5dab81 100644
--- a/demo/client/html/benchmark.html.hbs
+++ b/demo/client/html/benchmark.html.hbs
@@ -51,8 +51,18 @@
-
- {{>partials/switch.html id="pf-subpixel-aa" title="Subpixel AA"}}
+
+
+
diff --git a/demo/client/html/text-demo.html.hbs b/demo/client/html/text-demo.html.hbs
index 2e39ddc2..e1ce6155 100644
--- a/demo/client/html/text-demo.html.hbs
+++ b/demo/client/html/text-demo.html.hbs
@@ -67,9 +67,14 @@
-
- {{>partials/switch.html id="pf-subpixel-aa"
- title="Subpixel AA"}}
+
+
+
{{>partials/switch.html id="pf-gamma-correction"
diff --git a/demo/client/src/3d-demo.ts b/demo/client/src/3d-demo.ts
index 396cc25d..42bdcb1b 100644
--- a/demo/client/src/3d-demo.ts
+++ b/demo/client/src/3d-demo.ts
@@ -155,11 +155,12 @@ class ThreeDController extends DemoAppController
{
this.monumentPromise.then(monument => this.layoutMonument(fileData, monument));
}
- protected createView(gammaLUT: HTMLImageElement,
+ protected createView(areaLUT: HTMLImageElement,
+ gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap):
ThreeDView {
- return new ThreeDView(this, gammaLUT, commonShaderSource, shaderSources);
+ return new ThreeDView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
}
protected get builtinFileURI(): string {
@@ -343,10 +344,11 @@ class ThreeDView extends DemoView implements TextRenderContext {
}
constructor(appController: ThreeDController,
+ areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap) {
- super(gammaLUT, commonShaderSource, shaderSources);
+ super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
this.cameraView = new ThreeDAtlasCameraView;
@@ -385,6 +387,10 @@ class ThreeDRenderer extends Renderer {
return this.destAllocatedSize;
}
+ get allowSubpixelAA(): boolean {
+ return false;
+ }
+
get backgroundColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
}
@@ -466,6 +472,17 @@ class ThreeDRenderer extends Renderer {
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
}
+ pathTransformsForObject(objectIndex: number): PathTransformBuffers {
+ const meshDescriptor = this.renderContext.appController.meshDescriptors[objectIndex];
+ const pathCount = this.pathCountForObject(objectIndex);
+ const pathTransforms = this.createPathTransformBuffers(pathCount);
+ for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
+ const glyphOrigin = meshDescriptor.positions[pathIndex];
+ pathTransforms.st.set([1, 1, glyphOrigin[0], glyphOrigin[1]], (pathIndex + 1) * 4);
+ }
+ return pathTransforms;
+ }
+
protected clearColorForObject(objectIndex: number): glmatrix.vec4 | null {
return null;
}
@@ -496,17 +513,6 @@ class ThreeDRenderer extends Renderer {
return TEXT_COLOR;
}
- protected pathTransformsForObject(objectIndex: number): PathTransformBuffers {
- const meshDescriptor = this.renderContext.appController.meshDescriptors[objectIndex];
- const pathCount = this.pathCountForObject(objectIndex);
- const pathTransforms = this.createPathTransformBuffers(pathCount);
- for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
- const glyphOrigin = meshDescriptor.positions[pathIndex];
- pathTransforms.st.set([1, 1, glyphOrigin[0], glyphOrigin[1]], (pathIndex + 1) * 4);
- }
- return pathTransforms;
- }
-
protected meshInstanceCountForObject(objectIndex: number): number {
return this.renderContext.appController.meshDescriptors[objectIndex].positions.length;
}
diff --git a/demo/client/src/aa-strategy.ts b/demo/client/src/aa-strategy.ts
index 804ad3d9..b329b6d0 100644
--- a/demo/client/src/aa-strategy.ts
+++ b/demo/client/src/aa-strategy.ts
@@ -10,17 +10,25 @@
import * as glmatrix from 'gl-matrix';
-import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
+import {createFramebuffer, createFramebufferColorTexture, UniformMap} from './gl-utils';
import {createFramebufferDepthTexture} from './gl-utils';
import {Renderer} from './renderer';
import {unwrapNull} from './utils';
import {DemoView} from './view';
+const SUBPIXEL_AA_KERNELS: {readonly [kind in SubpixelAAType]: glmatrix.vec4} = {
+ // These intentionally do not precisely match what Core Graphics does (a Lanczos function),
+ // because we don't want any ringing artefacts.
+ 'core-graphics': glmatrix.vec4.clone([0.033165660, 0.102074051, 0.221434336, 0.286651906]),
+ 'freetype': glmatrix.vec4.clone([0.0, 0.031372549, 0.301960784, 0.337254902]),
+ 'none': glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]),
+};
+
export type AntialiasingStrategyName = 'none' | 'ssaa' | 'xcaa';
export type DirectRenderingMode = 'none' | 'conservative' | 'color';
-export type SubpixelAAType = 'none' | 'medium';
+export type SubpixelAAType = 'none' | 'freetype' | 'core-graphics';
export type GammaCorrectionMode = 'off' | 'on';
@@ -38,6 +46,12 @@ export abstract class AntialiasingStrategy {
// How many rendering passes this AA strategy requires.
abstract readonly passCount: number;
+ protected subpixelAA: SubpixelAAType;
+
+ constructor(subpixelAA: SubpixelAAType) {
+ this.subpixelAA = subpixelAA;
+ }
+
// Prepares any OpenGL data. This is only called on startup and canvas resize.
init(renderer: Renderer): void {
this.setFramebufferSize(renderer);
@@ -83,6 +97,14 @@ export abstract class AntialiasingStrategy {
// This usually blits to the real framebuffer.
abstract resolve(pass: number, renderer: Renderer): void;
+ setSubpixelAAKernelUniform(renderer: Renderer, uniforms: UniformMap): void {
+ const renderContext = renderer.renderContext;
+ const gl = renderContext.gl;
+
+ const kernel = SUBPIXEL_AA_KERNELS[this.subpixelAA];
+ gl.uniform4f(uniforms.uKernel, kernel[0], kernel[1], kernel[2], kernel[3]);
+ }
+
worldTransformForPass(renderer: Renderer, pass: number): glmatrix.mat4 {
return glmatrix.mat4.create();
}
@@ -96,7 +118,7 @@ export class NoAAStrategy extends AntialiasingStrategy {
}
constructor(level: number, subpixelAA: SubpixelAAType) {
- super();
+ super(subpixelAA);
this.framebufferSize = glmatrix.vec2.create();
}
diff --git a/demo/client/src/app-controller.ts b/demo/client/src/app-controller.ts
index b3c50107..9df5b686 100644
--- a/demo/client/src/app-controller.ts
+++ b/demo/client/src/app-controller.ts
@@ -15,6 +15,7 @@ import {ShaderLoader, ShaderMap, ShaderProgramSource} from './shader-loader';
import {expectNotNull, unwrapNull, unwrapUndef} from './utils';
import {DemoView, Timings, TIMINGS} from "./view";
+const AREA_LUT_URI: string = "/textures/area-lut.png";
const GAMMA_LUT_URI: string = "/textures/gamma-lut.png";
const SWITCHES: SwitchMap = {
@@ -32,13 +33,6 @@ const SWITCHES: SwitchMap = {
onValue: 'dark',
switchInputsName: 'stemDarkeningSwitchInputs',
},
- subpixelAA: {
- defaultValue: 'none',
- id: 'pf-subpixel-aa',
- offValue: 'none',
- onValue: 'medium',
- switchInputsName: 'subpixelAASwitchInputs',
- },
};
interface SwitchDescriptor {
@@ -52,7 +46,6 @@ interface SwitchDescriptor {
interface SwitchMap {
gammaCorrection: SwitchDescriptor;
stemDarkening: SwitchDescriptor;
- subpixelAA: SwitchDescriptor;
}
export interface AAOptions {
@@ -67,7 +60,6 @@ export interface SwitchInputs {
}
interface Switches {
- subpixelAASwitchInputs: SwitchInputs | null;
gammaCorrectionSwitchInputs: SwitchInputs | null;
stemDarkeningSwitchInputs: SwitchInputs | null;
}
@@ -111,7 +103,6 @@ export abstract class DemoAppController extends AppContro
implements Switches {
view!: Promise;
- subpixelAASwitchInputs: SwitchInputs | null = null;
gammaCorrectionSwitchInputs: SwitchInputs | null = null;
stemDarkeningSwitchInputs: SwitchInputs | null = null;
@@ -120,6 +111,7 @@ export abstract class DemoAppController extends AppContro
protected filePickerView: FilePickerView | null = null;
protected aaLevelSelect: HTMLSelectElement | null = null;
+ protected subpixelAASelect: HTMLSelectElement | null = null;
private fpsLabel: HTMLElement | null = null;
@@ -190,11 +182,17 @@ export abstract class DemoAppController extends AppContro
const shaderLoader = new ShaderLoader;
shaderLoader.load();
- const gammaLUTPromise = this.loadGammaLUT();
+ const areaLUTPromise = this.loadTexture(AREA_LUT_URI);
+ const gammaLUTPromise = this.loadTexture(GAMMA_LUT_URI);
- const promises: any[] = [gammaLUTPromise, shaderLoader.common, shaderLoader.shaders];
+ const promises: any[] = [
+ areaLUTPromise,
+ gammaLUTPromise,
+ shaderLoader.common,
+ shaderLoader.shaders,
+ ];
this.view = Promise.all(promises).then(assets => {
- return this.createView(assets[0], assets[1], assets[2]);
+ return this.createView(assets[0], assets[1], assets[2], assets[3]);
});
this.aaLevelSelect = document.getElementById('pf-aa-level-select') as
@@ -202,6 +200,11 @@ export abstract class DemoAppController extends AppContro
if (this.aaLevelSelect != null)
this.aaLevelSelect.addEventListener('change', () => this.updateAALevel(), false);
+ this.subpixelAASelect = document.getElementById('pf-subpixel-aa-select') as
+ (HTMLSelectElement | null);
+ if (this.subpixelAASelect != null)
+ this.subpixelAASelect.addEventListener('change', () => this.updateAALevel(), false);
+
// The event listeners here use `window.setTimeout()` because jQuery won't fire the "live"
// click listener that Bootstrap sets up until the event bubbles up to the document. This
// click listener is what toggles the `checked` attribute, so we have to wait until it
@@ -284,6 +287,12 @@ export abstract class DemoAppController extends AppContro
else
aaOptions[switchName] = switchDescriptor.offValue as any;
}
+ if (this.subpixelAASelect != null) {
+ const selectedOption = this.subpixelAASelect.selectedOptions[0];
+ aaOptions.subpixelAA = selectedOption.value as SubpixelAAType;
+ } else {
+ aaOptions.subpixelAA = 'none';
+ }
return this.view.then(view => {
view.setAntialiasingOptions(aaType, aaLevel, aaOptions as AAOptions);
@@ -294,7 +303,8 @@ export abstract class DemoAppController extends AppContro
// Overridden by subclasses.
}
- protected abstract createView(gammaLUT: HTMLImageElement,
+ protected abstract createView(areaLUT: HTMLImageElement,
+ gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap):
View;
@@ -331,8 +341,8 @@ export abstract class DemoAppController extends AppContro
}, false);
}
- private loadGammaLUT(): Promise {
- return window.fetch(GAMMA_LUT_URI)
+ private loadTexture(uri: string): Promise {
+ return window.fetch(uri)
.then(response => response.blob())
.then(blob => {
const imgElement = document.createElement('img');
diff --git a/demo/client/src/benchmark.ts b/demo/client/src/benchmark.ts
index bb168819..6a1b9347 100644
--- a/demo/client/src/benchmark.ts
+++ b/demo/client/src/benchmark.ts
@@ -188,19 +188,20 @@ class BenchmarkAppController extends DemoAppController {
}
}
- protected createView(gammaLUT: HTMLImageElement,
+ protected createView(areaLUT: HTMLImageElement,
+ gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap):
BenchmarkTestView {
- return new BenchmarkTestView(this, gammaLUT, commonShaderSource, shaderSources);
+ return new BenchmarkTestView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
}
private modeChanged(): void {
this.loadInitialFile(this.builtinFileURI);
if (this.aaLevelSelect != null)
this.aaLevelSelect.selectedIndex = 0;
- if (this.subpixelAASwitchInputs != null)
- setSwitchInputsValue(this.subpixelAASwitchInputs, false);
+ if (this.subpixelAASelect != null)
+ this.subpixelAASelect.selectedIndex = 0;
this.updateAALevel();
}
@@ -381,10 +382,11 @@ class BenchmarkTestView extends DemoView {
}
constructor(appController: BenchmarkAppController,
+ areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap) {
- super(gammaLUT, commonShaderSource, shaderSources);
+ super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
this.appController = appController;
this.recreateRenderer();
this.resizeToFit(true);
@@ -447,6 +449,10 @@ class BenchmarkTextRenderer extends Renderer {
return this.destAllocatedSize;
}
+ get allowSubpixelAA(): boolean {
+ return true;
+ }
+
get emboldenAmount(): glmatrix.vec2 {
return this.stemDarkeningAmount;
}
@@ -541,27 +547,7 @@ class BenchmarkTextRenderer extends Renderer {
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
}
- protected createAAStrategy(aaType: AntialiasingStrategyName,
- aaLevel: number,
- subpixelAA: SubpixelAAType):
- AntialiasingStrategy {
- return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
- }
-
- protected compositeIfNecessary(): void {}
-
- protected updateTimings(timings: Timings): void {
- // TODO(pcwalton)
- }
-
- protected pathColorsForObject(objectIndex: number): Uint8Array {
- const pathColors = new Uint8Array(4 * (STRING.length + 1));
- for (let pathIndex = 0; pathIndex < STRING.length; pathIndex++)
- pathColors.set(TEXT_COLOR, (pathIndex + 1) * 4);
- return pathColors;
- }
-
- protected pathTransformsForObject(objectIndex: number): PathTransformBuffers {
+ pathTransformsForObject(objectIndex: number): PathTransformBuffers {
const appController = this.renderContext.appController;
const canvas = this.renderContext.canvas;
const font = unwrapNull(appController.font);
@@ -586,6 +572,26 @@ class BenchmarkTextRenderer extends Renderer {
return pathTransforms;
}
+ protected createAAStrategy(aaType: AntialiasingStrategyName,
+ aaLevel: number,
+ subpixelAA: SubpixelAAType):
+ AntialiasingStrategy {
+ return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
+ }
+
+ protected compositeIfNecessary(): void {}
+
+ protected updateTimings(timings: Timings): void {
+ // TODO(pcwalton)
+ }
+
+ protected pathColorsForObject(objectIndex: number): Uint8Array {
+ const pathColors = new Uint8Array(4 * (STRING.length + 1));
+ for (let pathIndex = 0; pathIndex < STRING.length; pathIndex++)
+ pathColors.set(TEXT_COLOR, (pathIndex + 1) * 4);
+ return pathColors;
+ }
+
protected directCurveProgramName(): keyof ShaderMap {
return 'directCurve';
}
diff --git a/demo/client/src/reference-test.ts b/demo/client/src/reference-test.ts
index c8687454..7782946a 100644
--- a/demo/client/src/reference-test.ts
+++ b/demo/client/src/reference-test.ts
@@ -19,7 +19,8 @@ import {DemoAppController, setSwitchInputsValue} from "./app-controller";
import {SUBPIXEL_GRANULARITY} from './atlas';
import {OrthographicCamera} from './camera';
import {UniformMap} from './gl-utils';
-import {PathfinderMeshPack, PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from './meshes';
+import {B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET, PathfinderMeshPack} from './meshes';
+import {PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from './meshes';
import {PathTransformBuffers, Renderer} from "./renderer";
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
import SSAAStrategy from './ssaa-strategy';
@@ -28,6 +29,7 @@ import {SVGRenderer} from './svg-renderer';
import {BUILTIN_FONT_URI, computeStemDarkeningAmount, ExpandedMeshData, GlyphStore} from "./text";
import {Hint} from "./text";
import {PathfinderFont, TextFrame, TextRun} from "./text";
+import {MAX_SUBPIXEL_AA_FONT_SIZE} from './text-renderer';
import {unwrapNull} from "./utils";
import {DemoView} from "./view";
import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy';
@@ -314,11 +316,12 @@ class ReferenceTestAppController extends DemoAppController {
context.putImageData(imageData, 0, 0);
}
- protected createView(gammaLUT: HTMLImageElement,
+ protected createView(areaLUT: HTMLImageElement,
+ gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap):
ReferenceTestView {
- return new ReferenceTestView(this, gammaLUT, commonShaderSource, shaderSources);
+ return new ReferenceTestView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
}
protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
@@ -468,7 +471,8 @@ class ReferenceTestAppController extends DemoAppController {
return option.value.startsWith(currentTestCase.aaMode);
});
- setSwitchInputsValue(unwrapNull(this.subpixelAASwitchInputs), currentTestCase.subpixel);
+ const subpixelAASelect = unwrapNull(this.subpixelAASelect);
+ subpixelAASelect.selectedIndex = currentTestCase.subpixel ? 1 : 0;
return this.updateAALevel();
}
@@ -591,10 +595,11 @@ class ReferenceTestView extends DemoView {
}
constructor(appController: ReferenceTestAppController,
+ areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap) {
- super(gammaLUT, commonShaderSource, shaderSources);
+ super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
this.appController = appController;
this.recreateRenderer();
@@ -687,6 +692,11 @@ class ReferenceTestTextRenderer extends Renderer {
return this.stemDarkeningAmount;
}
+ get allowSubpixelAA(): boolean {
+ const appController = this.renderContext.appController;
+ return appController.currentFontSize <= MAX_SUBPIXEL_AA_FONT_SIZE;
+ }
+
protected get objectCount(): number {
return this.meshBuffers == null ? 0 : this.meshBuffers.length;
}
@@ -768,22 +778,7 @@ class ReferenceTestTextRenderer extends Renderer {
return textRun.pixelRectForGlyphAt(glyphIndex);
}
- protected createAAStrategy(aaType: AntialiasingStrategyName,
- aaLevel: number,
- subpixelAA: SubpixelAAType):
- AntialiasingStrategy {
- return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
- }
-
- protected compositeIfNecessary(): void {}
-
- protected pathColorsForObject(objectIndex: number): Uint8Array {
- const pathColors = new Uint8Array(4 * 2);
- pathColors.set(TEXT_COLOR, 1 * 4);
- return pathColors;
- }
-
- protected pathTransformsForObject(objectIndex: number): PathTransformBuffers {
+ pathTransformsForObject(objectIndex: number): PathTransformBuffers {
const appController = this.renderContext.appController;
const canvas = this.renderContext.canvas;
const font = unwrapNull(appController.font);
@@ -809,6 +804,21 @@ class ReferenceTestTextRenderer extends Renderer {
return pathTransforms;
}
+ protected createAAStrategy(aaType: AntialiasingStrategyName,
+ aaLevel: number,
+ subpixelAA: SubpixelAAType):
+ AntialiasingStrategy {
+ return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
+ }
+
+ protected compositeIfNecessary(): void {}
+
+ protected pathColorsForObject(objectIndex: number): Uint8Array {
+ const pathColors = new Uint8Array(4 * 2);
+ pathColors.set(TEXT_COLOR, 1 * 4);
+ return pathColors;
+ }
+
protected directCurveProgramName(): keyof ShaderMap {
return 'directCurve';
}
diff --git a/demo/client/src/renderer.ts b/demo/client/src/renderer.ts
index 93891236..ad4620b8 100644
--- a/demo/client/src/renderer.ts
+++ b/demo/client/src/renderer.ts
@@ -70,6 +70,7 @@ export abstract class Renderer {
abstract get isMulticolor(): boolean;
abstract get needsStencil(): boolean;
+ abstract get allowSubpixelAA(): boolean;
abstract get destFramebuffer(): WebGLFramebuffer | null;
abstract get destAllocatedSize(): glmatrix.vec2;
@@ -92,6 +93,7 @@ export abstract class Renderer {
private implicitCoverCurveVAO: WebGLVertexArrayObjectOES | null = null;
private gammaLUTTexture: WebGLTexture | null = null;
+ private areaLUTTexture: WebGLTexture | null = null;
private instancedPathIDVBO: WebGLBuffer | null = null;
private vertexIDVBO: WebGLBuffer | null = null;
@@ -114,7 +116,8 @@ export abstract class Renderer {
this.initInstancedPathIDVBO();
this.initVertexIDVBO();
- this.initGammaLUTTexture();
+ this.initLUTTexture('gammaLUT', 'gammaLUTTexture');
+ this.initLUTTexture('areaLUT', 'areaLUTTexture');
this.antialiasingStrategy = new NoAAStrategy(0, 'none');
this.antialiasingStrategy.init(this);
@@ -132,6 +135,7 @@ export abstract class Renderer {
abstract pathBoundingRects(objectIndex: number): Float32Array;
abstract setHintsUniform(uniforms: UniformMap): void;
+ abstract pathTransformsForObject(objectIndex: number): PathTransformBuffers;
redraw(): void {
const renderContext = this.renderContext;
@@ -287,20 +291,27 @@ export abstract class Renderer {
transform[13]);
}
- setTransformAffineUniforms(uniforms: UniformMap, objectIndex: number): void {
+ affineTransform(objectIndex: number): glmatrix.mat2d {
// FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an affine matrix is ugly and
// fragile. Refactor.
+ const transform = this.computeTransform(0, objectIndex);
+ return glmatrix.mat2d.fromValues(transform[0], transform[1],
+ transform[4], transform[5],
+ transform[12], transform[13]);
+
+ }
+
+ setTransformAffineUniforms(uniforms: UniformMap, objectIndex: number): void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
- const transform = this.computeTransform(0, objectIndex);
-
+ const transform = this.affineTransform(objectIndex);
gl.uniform4f(uniforms.uTransformST,
transform[0],
- transform[5],
- transform[12],
- transform[13]);
- gl.uniform2f(uniforms.uTransformExt, transform[1], transform[4]);
+ transform[3],
+ transform[4],
+ transform[5]);
+ gl.uniform2f(uniforms.uTransformExt, transform[1], transform[2]);
}
uploadPathColors(objectCount: number): void {
@@ -367,6 +378,15 @@ export abstract class Renderer {
return new Range(1, bVertexPathRanges.length + 1);
}
+ bindAreaLUT(textureUnit: number, uniforms: UniformMap): void {
+ const renderContext = this.renderContext;
+ const gl = renderContext.gl;
+
+ gl.activeTexture(gl.TEXTURE0 + textureUnit);
+ gl.bindTexture(gl.TEXTURE_2D, this.areaLUTTexture);
+ gl.uniform1i(uniforms.uAreaLUT, textureUnit);
+ }
+
protected clearColorForObject(objectIndex: number): glmatrix.vec4 | null {
return null;
}
@@ -390,8 +410,6 @@ export abstract class Renderer {
AntialiasingStrategy;
protected abstract compositeIfNecessary(): void;
protected abstract pathColorsForObject(objectIndex: number): Uint8Array;
- protected abstract pathTransformsForObject(objectIndex: number):
- PathTransformBuffers;
protected abstract directCurveProgramName(): keyof ShaderMap;
protected abstract directInteriorProgramName(renderingMode: DirectRenderingMode):
@@ -609,20 +627,23 @@ export abstract class Renderer {
}, TIME_INTERVAL_DELAY);
}
- private initGammaLUTTexture(): void {
+ private initLUTTexture(imageName: 'gammaLUT' | 'areaLUT',
+ textureName: 'gammaLUTTexture' | 'areaLUTTexture'):
+ void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
- const gammaLUT = renderContext.gammaLUT;
+ const image = renderContext[imageName];
const texture = unwrapNull(gl.createTexture());
gl.bindTexture(gl.TEXTURE_2D, texture);
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, gl.LUMINANCE, gl.UNSIGNED_BYTE, gammaLUT);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, gl.LUMINANCE, gl.UNSIGNED_BYTE, image);
+ const filter = imageName === 'gammaLUT' ? gl.NEAREST : gl.LINEAR;
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
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.gammaLUTTexture = texture;
+ this[textureName] = texture;
}
private initImplicitCoverCurveVAO(objectIndex: number, instanceRange: Range): void {
diff --git a/demo/client/src/ssaa-strategy.ts b/demo/client/src/ssaa-strategy.ts
index 01ecdec0..395f6d3c 100644
--- a/demo/client/src/ssaa-strategy.ts
+++ b/demo/client/src/ssaa-strategy.ts
@@ -29,7 +29,6 @@ export default class SSAAStrategy extends AntialiasingStrategy {
}
private level: number;
- private subpixelAA: SubpixelAAType;
private destFramebufferSize: glmatrix.vec2;
private supersampledFramebufferSize: glmatrix.vec2;
@@ -38,7 +37,7 @@ export default class SSAAStrategy extends AntialiasingStrategy {
private supersampledFramebuffer!: WebGLFramebuffer;
constructor(level: number, subpixelAA: SubpixelAAType) {
- super();
+ super(subpixelAA);
this.level = level;
this.subpixelAA = subpixelAA;
diff --git a/demo/client/src/svg-demo.ts b/demo/client/src/svg-demo.ts
index dd76cf7c..e62d6c6e 100644
--- a/demo/client/src/svg-demo.ts
+++ b/demo/client/src/svg-demo.ts
@@ -48,11 +48,12 @@ class SVGDemoController extends DemoAppController {
});
}
- protected createView(gammaLUT: HTMLImageElement,
+ protected createView(areaLUT: HTMLImageElement,
+ gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap):
SVGDemoView {
- return new SVGDemoView(this, gammaLUT, commonShaderSource, shaderSources);
+ return new SVGDemoView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
}
protected get defaultFile(): string {
@@ -76,10 +77,11 @@ class SVGDemoView extends DemoView {
}
constructor(appController: SVGDemoController,
+ areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap) {
- super(gammaLUT, commonShaderSource, shaderSources);
+ super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
this.appController = appController;
this.renderer = new SVGDemoRenderer(this, {sizeToFit: true});
diff --git a/demo/client/src/svg-renderer.ts b/demo/client/src/svg-renderer.ts
index 7bf409be..1947e080 100644
--- a/demo/client/src/svg-renderer.ts
+++ b/demo/client/src/svg-renderer.ts
@@ -79,6 +79,10 @@ export abstract class SVGRenderer extends Renderer {
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
}
+ get allowSubpixelAA(): boolean {
+ return false;
+ }
+
protected get objectCount(): number {
return 1;
}
@@ -136,6 +140,19 @@ export abstract class SVGRenderer extends Renderer {
return new Range(1, this.loader.pathInstances.length + 1);
}
+ pathTransformsForObject(objectIndex: number): PathTransformBuffers {
+ const instances = this.loader.pathInstances;
+ const pathTransforms = this.createPathTransformBuffers(instances.length);
+
+ for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
+ // TODO(pcwalton): Set transform.
+ const startOffset = (pathIndex + 1) * 4;
+ pathTransforms.st.set([1, 1, 0, 0], startOffset);
+ }
+
+ return pathTransforms;
+ }
+
protected get usedSizeFactor(): glmatrix.vec2 {
return glmatrix.vec2.clone([1.0, 1.0]);
}
@@ -189,19 +206,6 @@ export abstract class SVGRenderer extends Renderer {
return pathColors;
}
- protected pathTransformsForObject(objectIndex: number): PathTransformBuffers {
- const instances = this.loader.pathInstances;
- const pathTransforms = this.createPathTransformBuffers(instances.length);
-
- for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
- // TODO(pcwalton): Set transform.
- const startOffset = (pathIndex + 1) * 4;
- pathTransforms.st.set([1, 1, 0, 0], startOffset);
- }
-
- return pathTransforms;
- }
-
protected createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType):
diff --git a/demo/client/src/text-demo.ts b/demo/client/src/text-demo.ts
index d18a92c1..46b00953 100644
--- a/demo/client/src/text-demo.ts
+++ b/demo/client/src/text-demo.ts
@@ -227,11 +227,12 @@ class TextDemoController extends DemoAppController {
}
}
- protected createView(gammaLUT: HTMLImageElement,
+ protected createView(areaLUT: HTMLImageElement,
+ gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap):
TextDemoView {
- return new TextDemoView(this, gammaLUT, commonShaderSource, shaderSources);
+ return new TextDemoView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
}
protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null) {
@@ -386,10 +387,11 @@ class TextDemoView extends DemoView implements TextRenderContext {
}
constructor(appController: TextDemoController,
+ areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap) {
- super(gammaLUT, commonShaderSource, shaderSources);
+ super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
this.appController = appController;
this.renderer = new TextDemoRenderer(this);
diff --git a/demo/client/src/text-renderer.ts b/demo/client/src/text-renderer.ts
index 833b115b..4f8a968a 100644
--- a/demo/client/src/text-renderer.ts
+++ b/demo/client/src/text-renderer.ts
@@ -25,12 +25,12 @@ import {MAX_STEM_DARKENING_PIXELS_PER_EM, PathfinderFont, SimpleTextLayout} from
import {UnitMetrics} from "./text";
import {unwrapNull} from './utils';
import {RenderContext, Timings} from "./view";
-import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy';
+import {StencilAAAStrategy} from './xcaa-strategy';
interface AntialiasingStrategyTable {
none: typeof NoAAStrategy;
ssaa: typeof SSAAStrategy;
- xcaa: typeof AdaptiveStencilMeshAAAStrategy;
+ xcaa: typeof StencilAAAStrategy;
}
const SQRT_1_2: number = 1.0 / Math.sqrt(2.0);
@@ -38,10 +38,12 @@ const SQRT_1_2: number = 1.0 / Math.sqrt(2.0);
const MIN_SCALE: number = 0.0025;
const MAX_SCALE: number = 0.5;
+export const MAX_SUBPIXEL_AA_FONT_SIZE: number = 48.0;
+
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
none: NoAAStrategy,
ssaa: SSAAStrategy,
- xcaa: AdaptiveStencilMeshAAAStrategy,
+ xcaa: StencilAAAStrategy,
};
export interface TextRenderContext extends RenderContext {
@@ -103,6 +105,10 @@ export abstract class TextRenderer extends Renderer {
return 0.0;
}
+ get allowSubpixelAA(): boolean {
+ return this.renderContext.fontSize <= MAX_SUBPIXEL_AA_FONT_SIZE;
+ }
+
protected get pixelsPerUnit(): number {
return this.renderContext.fontSize / this.renderContext.font.opentypeFont.unitsPerEm;
}
@@ -188,6 +194,54 @@ export abstract class TextRenderer extends Renderer {
return boundingRects;
}
+ pathTransformsForObject(objectIndex: number): PathTransformBuffers {
+ const pathCount = this.pathCount;
+ const atlasGlyphs = this.renderContext.atlasGlyphs;
+ const pixelsPerUnit = this.pixelsPerUnit;
+ const rotationAngle = this.rotationAngle;
+
+ // FIXME(pcwalton): This is a hack that tries to preserve the vertical extents of the glyph
+ // after stem darkening. It's better than nothing, but we should really do better.
+ //
+ // This hack seems to produce *better* results than what macOS does on sans-serif fonts;
+ // the ascenders and x-heights of the glyphs are pixel snapped, while they aren't on macOS.
+ // But we should really figure out what macOS does…
+ const ascender = this.renderContext.font.opentypeFont.ascender;
+ const emboldenAmount = this.emboldenAmount;
+ const stemDarkeningYScale = (ascender + emboldenAmount[1]) / ascender;
+
+ const stemDarkeningOffset = glmatrix.vec2.clone(emboldenAmount);
+ glmatrix.vec2.scale(stemDarkeningOffset, stemDarkeningOffset, pixelsPerUnit);
+ glmatrix.vec2.scale(stemDarkeningOffset, stemDarkeningOffset, SQRT_1_2);
+ glmatrix.vec2.mul(stemDarkeningOffset, stemDarkeningOffset, [1, stemDarkeningYScale]);
+
+ const transform = glmatrix.mat2d.create();
+ const transforms = this.createPathTransformBuffers(pathCount);
+
+ for (const glyph of atlasGlyphs) {
+ const pathID = glyph.pathID;
+ const atlasOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
+
+ glmatrix.mat2d.identity(transform);
+ glmatrix.mat2d.translate(transform, transform, atlasOrigin);
+ glmatrix.mat2d.translate(transform, transform, stemDarkeningOffset);
+ glmatrix.mat2d.rotate(transform, transform, rotationAngle);
+ glmatrix.mat2d.scale(transform,
+ transform,
+ [pixelsPerUnit, pixelsPerUnit * stemDarkeningYScale]);
+
+ transforms.st[pathID * 4 + 0] = transform[0];
+ transforms.st[pathID * 4 + 1] = transform[3];
+ transforms.st[pathID * 4 + 2] = transform[4];
+ transforms.st[pathID * 4 + 3] = transform[5];
+
+ transforms.ext[pathID * 2 + 0] = transform[1];
+ transforms.ext[pathID * 2 + 1] = transform[2];
+ }
+
+ return transforms;
+ }
+
protected createAtlasFramebuffer(): void {
const atlasColorTexture = this.renderContext.atlas.ensureTexture(this.renderContext);
this.atlasDepthTexture = createFramebufferDepthTexture(this.renderContext.gl, ATLAS_SIZE);
@@ -250,54 +304,6 @@ export abstract class TextRenderer extends Renderer {
return pathColors;
}
- protected pathTransformsForObject(objectIndex: number): PathTransformBuffers {
- const pathCount = this.pathCount;
- const atlasGlyphs = this.renderContext.atlasGlyphs;
- const pixelsPerUnit = this.pixelsPerUnit;
- const rotationAngle = this.rotationAngle;
-
- // FIXME(pcwalton): This is a hack that tries to preserve the vertical extents of the glyph
- // after stem darkening. It's better than nothing, but we should really do better.
- //
- // This hack seems to produce *better* results than what macOS does on sans-serif fonts;
- // the ascenders and x-heights of the glyphs are pixel snapped, while they aren't on macOS.
- // But we should really figure out what macOS does…
- const ascender = this.renderContext.font.opentypeFont.ascender;
- const emboldenAmount = this.emboldenAmount;
- const stemDarkeningYScale = (ascender + emboldenAmount[1]) / ascender;
-
- const stemDarkeningOffset = glmatrix.vec2.clone(emboldenAmount);
- glmatrix.vec2.scale(stemDarkeningOffset, stemDarkeningOffset, pixelsPerUnit);
- glmatrix.vec2.scale(stemDarkeningOffset, stemDarkeningOffset, SQRT_1_2);
- glmatrix.vec2.mul(stemDarkeningOffset, stemDarkeningOffset, [1, stemDarkeningYScale]);
-
- const transform = glmatrix.mat2d.create();
- const transforms = this.createPathTransformBuffers(pathCount);
-
- for (const glyph of atlasGlyphs) {
- const pathID = glyph.pathID;
- const atlasOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
-
- glmatrix.mat2d.identity(transform);
- glmatrix.mat2d.translate(transform, transform, atlasOrigin);
- glmatrix.mat2d.translate(transform, transform, stemDarkeningOffset);
- glmatrix.mat2d.rotate(transform, transform, rotationAngle);
- glmatrix.mat2d.scale(transform,
- transform,
- [pixelsPerUnit, pixelsPerUnit * stemDarkeningYScale]);
-
- transforms.st[pathID * 4 + 0] = transform[0];
- transforms.st[pathID * 4 + 1] = transform[3];
- transforms.st[pathID * 4 + 2] = transform[4];
- transforms.st[pathID * 4 + 3] = transform[5];
-
- transforms.ext[pathID * 2 + 0] = transform[1];
- transforms.ext[pathID * 2 + 1] = transform[2];
- }
-
- return transforms;
- }
-
protected newTimingsReceived(): void {
this.renderContext.newTimingsReceived(this.lastTimings);
}
diff --git a/demo/client/src/text.ts b/demo/client/src/text.ts
index 68982b4c..268a9556 100644
--- a/demo/client/src/text.ts
+++ b/demo/client/src/text.ts
@@ -334,6 +334,7 @@ export class Hint {
private useHinting: boolean;
constructor(font: PathfinderFont, pixelsPerUnit: number, useHinting: boolean) {
+ useHinting = false;
this.useHinting = useHinting;
const os2Table = font.opentypeFont.tables.os2;
diff --git a/demo/client/src/view.ts b/demo/client/src/view.ts
index 8267671e..509ce024 100644
--- a/demo/client/src/view.ts
+++ b/demo/client/src/view.ts
@@ -143,6 +143,7 @@ export abstract class DemoView extends PathfinderView implements RenderContext {
gl!: WebGLRenderingContext;
shaderPrograms: ShaderMap;
+ areaLUT: HTMLImageElement;
gammaLUT: HTMLImageElement;
instancedArraysExt!: ANGLE_instanced_arrays;
@@ -175,7 +176,8 @@ export abstract class DemoView extends PathfinderView implements RenderContext {
private wantsScreenshot: boolean;
/// NB: All subclasses are responsible for creating a renderer in their constructors.
- constructor(gammaLUT: HTMLImageElement,
+ constructor(areaLUT: HTMLImageElement,
+ gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap) {
super();
@@ -188,6 +190,7 @@ export abstract class DemoView extends PathfinderView implements RenderContext {
const shaderSource = this.compileShaders(commonShaderSource, shaderSources);
this.shaderPrograms = this.linkShaders(shaderSource);
+ this.areaLUT = areaLUT;
this.gammaLUT = gammaLUT;
this.wantsScreenshot = false;
@@ -372,6 +375,7 @@ export interface RenderContext {
readonly colorAlphaFormat: ColorAlphaFormat;
readonly shaderPrograms: ShaderMap;
+ readonly areaLUT: HTMLImageElement;
readonly gammaLUT: HTMLImageElement;
readonly quadPositionsBuffer: WebGLBuffer;
diff --git a/demo/client/src/xcaa-strategy.ts b/demo/client/src/xcaa-strategy.ts
index e06575aa..64da4dfb 100644
--- a/demo/client/src/xcaa-strategy.ts
+++ b/demo/client/src/xcaa-strategy.ts
@@ -65,8 +65,6 @@ export abstract class XCAAStrategy extends AntialiasingStrategy {
protected supersampledFramebufferSize: glmatrix.vec2;
protected destFramebufferSize: glmatrix.vec2;
- protected subpixelAA: SubpixelAAType;
-
protected resolveVAO: WebGLVertexArrayObject | null;
protected aaAlphaTexture: WebGLTexture | null = null;
@@ -76,9 +74,7 @@ export abstract class XCAAStrategy extends AntialiasingStrategy {
protected abstract get mightUseAAFramebuffer(): boolean;
constructor(level: number, subpixelAA: SubpixelAAType) {
- super();
-
- this.subpixelAA = subpixelAA;
+ super(subpixelAA);
this.supersampledFramebufferSize = glmatrix.vec2.create();
this.destFramebufferSize = glmatrix.vec2.create();
@@ -201,6 +197,7 @@ export abstract class XCAAStrategy extends AntialiasingStrategy {
if (renderer.fgColor != null)
gl.uniform4fv(resolveProgram.uniforms.uFGColor, renderer.fgColor);
renderer.setTransformSTAndTexScaleUniformsForDest(resolveProgram.uniforms);
+ this.setSubpixelAAKernelUniform(renderer, resolveProgram.uniforms);
this.setAdditionalStateForResolveIfNecessary(renderer, resolveProgram, 1);
gl.drawElements(renderContext.gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
@@ -273,6 +270,7 @@ export abstract class XCAAStrategy extends AntialiasingStrategy {
renderer.pathTransformBufferTextures[0].st.bind(gl, uniforms, 1);
this.pathBoundsBufferTextures[objectIndex].bind(gl, uniforms, 2);
renderer.setHintsUniform(uniforms);
+ renderer.bindAreaLUT(4, uniforms);
}
protected setDepthAndBlendModeForResolve(renderContext: RenderContext): void {
@@ -410,7 +408,7 @@ export class MCAAStrategy extends XCAAStrategy {
const renderContext = renderer.renderContext;
if (renderer.isMulticolor)
return null;
- if (this.subpixelAA !== 'none')
+ if (this.subpixelAA !== 'none' && renderer.allowSubpixelAA)
return renderContext.shaderPrograms.xcaaMonoSubpixelResolve;
return renderContext.shaderPrograms.xcaaMonoResolve;
}
@@ -651,8 +649,11 @@ export class StencilAAAStrategy extends XCAAStrategy {
// FIXME(pcwalton): Only render the appropriate instances.
const count = renderer.meshes[0].count('stencilSegments');
- renderContext.instancedArraysExt
- .drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count);
+ for (let side = 0; side < 2; side++) {
+ gl.uniform1i(uniforms.uSide, side);
+ renderContext.instancedArraysExt
+ .drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count);
+ }
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
}
@@ -673,7 +674,7 @@ export class StencilAAAStrategy extends XCAAStrategy {
protected getResolveProgram(renderer: Renderer): PathfinderShaderProgram | null {
const renderContext = renderer.renderContext;
- if (this.subpixelAA !== 'none')
+ if (this.subpixelAA !== 'none' && renderer.allowSubpixelAA)
return renderContext.shaderPrograms.xcaaMonoSubpixelResolve;
return renderContext.shaderPrograms.xcaaMonoResolve;
}
@@ -768,8 +769,6 @@ export class StencilAAAStrategy extends XCAAStrategy {
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aToNormal, 1);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1);
- // TODO(pcwalton): Normals.
-
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer);
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
@@ -789,7 +788,7 @@ export class StencilAAAStrategy extends XCAAStrategy {
/// darkening is enabled.
///
/// FIXME(pcwalton): Share textures and FBOs between the two strategies.
-export class AdaptiveStencilMeshAAAStrategy implements AntialiasingStrategy {
+export class AdaptiveStencilMeshAAAStrategy extends AntialiasingStrategy {
private meshStrategy: MCAAStrategy;
private stencilStrategy: StencilAAAStrategy;
@@ -802,6 +801,7 @@ export class AdaptiveStencilMeshAAAStrategy implements AntialiasingStrategy {
}
constructor(level: number, subpixelAA: SubpixelAAType) {
+ super(subpixelAA);
this.meshStrategy = new MCAAStrategy(level, subpixelAA);
this.stencilStrategy = new StencilAAAStrategy(level, subpixelAA);
}
diff --git a/resources/textures/area-lut.png b/resources/textures/area-lut.png
new file mode 100644
index 00000000..b54d669f
Binary files /dev/null and b/resources/textures/area-lut.png differ
diff --git a/shaders/gles2/common.inc.glsl b/shaders/gles2/common.inc.glsl
index 983ea7d9..8016f25a 100644
--- a/shaders/gles2/common.inc.glsl
+++ b/shaders/gles2/common.inc.glsl
@@ -13,9 +13,16 @@
#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)
-#define LCD_FILTER_FACTOR_2 (8.0 / 255.0)
+#define FREETYPE_LCD_FILTER_FACTOR_0 0.337254902
+#define FREETYPE_LCD_FILTER_FACTOR_1 0.301960784
+#define FREETYPE_LCD_FILTER_FACTOR_2 0.031372549
+
+// These intentionally do not precisely match what Core Graphics does (a Lanczos function), because
+// we don't want any ringing artefacts.
+#define CG_LCD_FILTER_FACTOR_0 0.286651906
+#define CG_LCD_FILTER_FACTOR_1 0.221434336
+#define CG_LCD_FILTER_FACTOR_2 0.102074051
+#define CG_LCD_FILTER_FACTOR_3 0.033165660
#define MAX_PATHS 65536
@@ -226,12 +233,38 @@ vec2 solveCurveT(float p0x, float p1x, float p2x, vec2 x) {
///
/// The algorithm should be identical to that of FreeType:
/// https://www.freetype.org/freetype2/docs/reference/ft2-lcd_filtering.html
-float lcdFilter(float shadeL2, float shadeL1, float shade0, float shadeR1, float shadeR2) {
- return LCD_FILTER_FACTOR_2 * shadeL2 +
- LCD_FILTER_FACTOR_1 * shadeL1 +
- LCD_FILTER_FACTOR_0 * shade0 +
- LCD_FILTER_FACTOR_1 * shadeR1 +
- LCD_FILTER_FACTOR_2 * shadeR2;
+float freetypeLCDFilter(float shadeL2, float shadeL1, float shade0, float shadeR1, float shadeR2) {
+ return FREETYPE_LCD_FILTER_FACTOR_2 * shadeL2 +
+ FREETYPE_LCD_FILTER_FACTOR_1 * shadeL1 +
+ FREETYPE_LCD_FILTER_FACTOR_0 * shade0 +
+ FREETYPE_LCD_FILTER_FACTOR_1 * shadeR1 +
+ FREETYPE_LCD_FILTER_FACTOR_2 * shadeR2;
+}
+
+float sample1Tap(sampler2D source, vec2 center, float offset) {
+ return texture2D(source, vec2(center.x + offset, center.y)).r;
+}
+
+void sample9Tap(out vec4 outShadesL,
+ out float outShadeC,
+ out vec4 outShadesR,
+ sampler2D source,
+ vec2 center,
+ float onePixel,
+ vec4 kernel) {
+ outShadesL = vec4(kernel.x > 0.0 ? sample1Tap(source, center, -4.0 * onePixel) : 0.0,
+ sample1Tap(source, center, -3.0 * onePixel),
+ sample1Tap(source, center, -2.0 * onePixel),
+ sample1Tap(source, center, -1.0 * onePixel));
+ outShadeC = sample1Tap(source, center, 0.0);
+ outShadesR = vec4(sample1Tap(source, center, 1.0 * onePixel),
+ sample1Tap(source, center, 2.0 * onePixel),
+ sample1Tap(source, center, 3.0 * onePixel),
+ kernel.x > 0.0 ? sample1Tap(source, center, 4.0 * onePixel) : 0.0);
+}
+
+float convolve7Tap(vec4 shades0, vec3 shades1, vec4 kernel) {
+ return dot(shades0, kernel) + dot(shades1, kernel.zyx);
}
float gammaCorrectChannel(float fgColor, float bgColor, sampler2D gammaLUT) {
diff --git a/shaders/gles2/ssaa-subpixel-resolve.fs.glsl b/shaders/gles2/ssaa-subpixel-resolve.fs.glsl
index bbe3386b..e0e31bf4 100644
--- a/shaders/gles2/ssaa-subpixel-resolve.fs.glsl
+++ b/shaders/gles2/ssaa-subpixel-resolve.fs.glsl
@@ -18,27 +18,19 @@ precision mediump float;
uniform sampler2D uSource;
/// The dimensions of the alpha coverage texture, in texels.
uniform ivec2 uSourceDimensions;
+uniform vec4 uKernel;
varying vec2 vTexCoord;
-float sampleSource(float deltaX) {
- return texture2D(uSource, vec2(vTexCoord.x + deltaX, vTexCoord.y)).r;
-}
-
void main() {
float onePixel = 1.0 / float(uSourceDimensions.x);
+ vec4 shadesL, shadesR;
+ float shadeC;
+ sample9Tap(shadesL, shadeC, shadesR, uSource, vTexCoord, onePixel, uKernel);
- float shade0 = sampleSource(0.0);
- vec3 shadeL = vec3(sampleSource(-1.0 * onePixel),
- sampleSource(-2.0 * onePixel),
- sampleSource(-3.0 * onePixel));
- vec3 shadeR = vec3(sampleSource(1.0 * onePixel),
- sampleSource(2.0 * onePixel),
- sampleSource(3.0 * onePixel));
+ vec3 shades = vec3(convolve7Tap(shadesL, vec3(shadeC, shadesR.xy), uKernel),
+ convolve7Tap(vec4(shadesL.yzw, shadeC), shadesR.xyz, uKernel),
+ convolve7Tap(vec4(shadesL.zw, shadeC, shadesR.x), shadesR.yzw, uKernel));
- vec3 color = vec3(lcdFilter(shadeL.z, shadeL.y, shadeL.x, shade0, shadeR.x),
- lcdFilter(shadeL.y, shadeL.x, shade0, shadeR.x, shadeR.y),
- lcdFilter(shadeL.x, shade0, shadeR.x, shadeR.y, shadeR.z));
-
- gl_FragColor = vec4(color, 1.0);
+ gl_FragColor = vec4(shades, 1.0);
}
diff --git a/shaders/gles2/stencil-aaa.fs.glsl b/shaders/gles2/stencil-aaa.fs.glsl
index ba3a5a72..f0097163 100644
--- a/shaders/gles2/stencil-aaa.fs.glsl
+++ b/shaders/gles2/stencil-aaa.fs.glsl
@@ -8,42 +8,33 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
-varying vec3 vUV;
-varying vec3 vXDist;
+precision mediump float;
+
+uniform sampler2D uAreaLUT;
+
+varying vec2 vFrom;
+varying vec2 vCtrl;
+varying vec2 vTo;
void main() {
// Unpack.
- vec3 uv = vUV;
- vec2 dUVDX = dFdx(uv.xy), dUVDY = dFdy(uv.xy);
- vec3 xDist = vXDist;
- vec2 dXDistDX = dFdx(xDist.xz);
+ vec2 from = vFrom, ctrl = vCtrl, to = vTo;
- // Calculate X distances between endpoints (x02, x10, and x21 respectively).
- vec3 vDist = xDist - xDist.zxy;
+ // 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;
- // Compute winding number and convexity.
- bool inCurve = insideCurve(uv);
- float openWinding = fastSign(-vDist.x);
- float convex = uv.z != 0.0 ? uv.z : fastSign(vDist.x * dUVDY.y);
+ // 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 open rect area.
- vec2 areas = clamp(xDist.xz / dXDistDX, -0.5, 0.5);
- float openRectArea = openWinding * (areas.y - areas.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);
- // Compute closed rect area and winding, if necessary.
- float closedRectArea = 0.0, closedWinding = 0.0;
- if (inCurve && vDist.y * vDist.z < 0.0) {
- closedRectArea = 0.5 - fastSign(vDist.y) * (vDist.x * vDist.y < 0.0 ? areas.y : areas.x);
- closedWinding = fastSign(vDist.y * dUVDY.y);
- }
-
- // Calculate approximate area of the curve covering this pixel square.
- float curveArea = estimateArea(signedDistanceToCurve(uv.xy, dUVDX, dUVDY, inCurve));
-
- // Calculate alpha.
- vec2 alpha = vec2(openWinding, closedWinding) * 0.5 + convex * curveArea;
- alpha *= vec2(openRectArea, closedRectArea);
-
- // Finish up.
- gl_FragColor = vec4(alpha.x + alpha.y);
+ // Look up area under that line, and scale horizontally to the window size.
+ float dX = window.x - window.y;
+ gl_FragColor = vec4(texture2D(uAreaLUT, vec2(y + 8.0, abs(d * dX)) / 16.0).r * dX);
}
diff --git a/shaders/gles2/stencil-aaa.vs.glsl b/shaders/gles2/stencil-aaa.vs.glsl
index 69046af7..b9ad03bb 100644
--- a/shaders/gles2/stencil-aaa.vs.glsl
+++ b/shaders/gles2/stencil-aaa.vs.glsl
@@ -20,6 +20,7 @@ uniform ivec2 uPathTransformSTDimensions;
uniform sampler2D uPathTransformST;
uniform ivec2 uPathTransformExtDimensions;
uniform sampler2D uPathTransformExt;
+uniform int uSide;
attribute vec2 aTessCoord;
attribute vec2 aFromPosition;
@@ -30,8 +31,9 @@ attribute vec2 aCtrlNormal;
attribute vec2 aToNormal;
attribute float aPathID;
-varying vec3 vUV;
-varying vec3 vXDist;
+varying vec2 vFrom;
+varying vec2 vCtrl;
+varying vec2 vTo;
void main() {
// Unpack.
@@ -39,14 +41,14 @@ void main() {
int pathID = int(aPathID);
// Hint positions.
- vec2 fromPosition = hintPosition(aFromPosition, uHints);
- vec2 ctrlPosition = hintPosition(aCtrlPosition, uHints);
- vec2 toPosition = hintPosition(aToPosition, uHints);
+ vec2 from = hintPosition(aFromPosition, uHints);
+ vec2 ctrl = hintPosition(aCtrlPosition, uHints);
+ vec2 to = hintPosition(aToPosition, uHints);
// Embolden as necessary.
- fromPosition -= aFromNormal * emboldenAmount;
- ctrlPosition -= aCtrlNormal * emboldenAmount;
- toPosition -= aToNormal * emboldenAmount;
+ from -= aFromNormal * emboldenAmount;
+ ctrl -= aCtrlNormal * emboldenAmount;
+ to -= aToNormal * emboldenAmount;
// Fetch transform.
vec2 transformExt;
@@ -63,9 +65,9 @@ void main() {
mat2 transformLinear = globalTransformLinear * localTransformLinear;
// Perform the linear component of the transform (everything but translation).
- fromPosition = quantize(transformLinear * fromPosition);
- ctrlPosition = quantize(transformLinear * ctrlPosition);
- toPosition = quantize(transformLinear * toPosition);
+ from = transformLinear * from;
+ ctrl = transformLinear * ctrl;
+ to = transformLinear * to;
// Choose correct quadrant for rotation.
vec4 bounds = fetchFloat4Data(uPathBounds, pathID, uPathBoundsDimensions);
@@ -73,53 +75,48 @@ void main() {
vec2 corner = transformLinear * vec2(fillVector.x < 0.0 ? bounds.z : bounds.x,
fillVector.y < 0.0 ? bounds.y : bounds.w);
- // Compute edge vectors.
- vec2 v02 = toPosition - fromPosition;
- vec2 v01 = ctrlPosition - fromPosition, v21 = ctrlPosition - toPosition;
-
- // Compute area of convex hull (w). Change from curve to line if appropriate.
- float w = det2(mat2(v01, v02));
- float sqLen01 = dot(v01, v01), sqLen02 = dot(v02, v02), sqLen21 = dot(v21, v21);
- float hullHeight = abs(w * inversesqrt(sqLen02));
- float minCtrlSqLen = sqLen02 * 0.01;
- if (sqLen01 < minCtrlSqLen || sqLen21 < minCtrlSqLen || hullHeight < 0.0001) {
- w = 0.0;
- v01 = vec2(0.5, abs(v02.y) >= 0.01 ? 0.0 : 0.5) * v02.xx;
+ // Compute edge vectors. De Casteljau subdivide if necessary.
+ vec2 v01 = ctrl - from, v12 = to - ctrl;
+ float t = clamp(v01.x / (v01.x - v12.x), 0.0, 1.0);
+ vec2 ctrl0 = mix(from, ctrl, t), ctrl1 = mix(ctrl, to, t);
+ vec2 mid = mix(ctrl0, ctrl1, t);
+ if (uSide == 0) {
+ from = mid;
+ ctrl = ctrl1;
+ } else {
+ ctrl = ctrl0;
+ to = mid;
}
// Compute position and dilate. If too thin, discard to avoid artefacts.
- vec2 dilation = vec2(0.0), position;
+ vec2 dilation, position;
+ bool zeroArea = abs(from.x - to.x) < 0.00001;
if (aTessCoord.x < 0.5) {
- position.x = min(min(fromPosition.x, toPosition.x), ctrlPosition.x);
- dilation.x = -1.0;
+ position.x = min(min(from.x, to.x), ctrl.x);
+ dilation.x = zeroArea ? 0.0 : -1.0;
} else {
- position.x = max(max(fromPosition.x, toPosition.x), ctrlPosition.x);
- dilation.x = 1.0;
+ 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(fromPosition.y, toPosition.y), ctrlPosition.y);
- dilation.y = -1.0;
+ position.y = min(min(from.y, to.y), ctrl.y);
+ dilation.y = zeroArea ? 0.0 : -1.0;
} else {
position.y = corner.y;
+ dilation.y = 0.0;
}
- position += 2.0 * dilation / vec2(uFramebufferSize);
-
- // Compute UV using Cramer's rule.
- // https://gamedev.stackexchange.com/a/63203
- vec2 v03 = position - fromPosition;
- vec3 uv = vec3(0.0, det2(mat2(v01, v03)), sign(w));
- uv.x = uv.y + 0.5 * det2(mat2(v03, v02));
- uv.xy /= det2(mat2(v01, v02));
-
- // Compute X distances.
- vec3 xDist = position.x - vec3(fromPosition.x, ctrlPosition.x, toPosition.x);
+ position += dilation * 2.0 / vec2(uFramebufferSize);
// Compute final position and depth.
- position += uTransformST.zw + globalTransformLinear * transformST.zw;
+ vec2 offsetPosition = position + uTransformST.zw + globalTransformLinear * transformST.zw;
float depth = convertPathIndexToViewportDepthValue(pathID);
+ // Compute transformed framebuffer size.
+ vec2 framebufferSizeVector = 0.5 * vec2(uFramebufferSize);
+
// Finish up.
- gl_Position = vec4(position, depth, 1.0);
- vUV = uv;
- vXDist = xDist;
+ gl_Position = vec4(offsetPosition, depth, 1.0);
+ vFrom = (from - position) * framebufferSizeVector;
+ vCtrl = (ctrl - position) * framebufferSizeVector;
+ vTo = (to - position) * framebufferSizeVector;
}
diff --git a/shaders/gles2/xcaa-mono-resolve.fs.glsl b/shaders/gles2/xcaa-mono-resolve.fs.glsl
index 59f65bfa..41fe70c3 100644
--- a/shaders/gles2/xcaa-mono-resolve.fs.glsl
+++ b/shaders/gles2/xcaa-mono-resolve.fs.glsl
@@ -22,6 +22,6 @@ uniform sampler2D uAAAlpha;
varying vec2 vTexCoord;
void main() {
- float alpha = clamp(texture2D(uAAAlpha, vTexCoord).r, 0.0, 1.0);
+ float alpha = abs(texture2D(uAAAlpha, vTexCoord).r);
gl_FragColor = mix(uBGColor, uFGColor, alpha);
}
diff --git a/shaders/gles2/xcaa-mono-subpixel-resolve.fs.glsl b/shaders/gles2/xcaa-mono-subpixel-resolve.fs.glsl
index 70973020..8eed946f 100644
--- a/shaders/gles2/xcaa-mono-subpixel-resolve.fs.glsl
+++ b/shaders/gles2/xcaa-mono-subpixel-resolve.fs.glsl
@@ -22,27 +22,19 @@ uniform vec4 uFGColor;
uniform sampler2D uAAAlpha;
/// The dimensions of the alpha coverage texture, in texels.
uniform ivec2 uAAAlphaDimensions;
+uniform vec4 uKernel;
varying vec2 vTexCoord;
-float sampleSource(float deltaX) {
- return abs(texture2D(uAAAlpha, vec2(vTexCoord.s + deltaX, vTexCoord.y)).r);
-}
-
void main() {
float onePixel = 1.0 / float(uAAAlphaDimensions.x);
+ vec4 shadesL, shadesR;
+ float shadeC;
+ sample9Tap(shadesL, shadeC, shadesR, uAAAlpha, vTexCoord, onePixel, uKernel);
- float shade0 = sampleSource(0.0);
- vec3 shadeL = vec3(sampleSource(-1.0 * onePixel),
- sampleSource(-2.0 * onePixel),
- sampleSource(-3.0 * onePixel));
- vec3 shadeR = vec3(sampleSource(1.0 * onePixel),
- sampleSource(2.0 * onePixel),
- sampleSource(3.0 * onePixel));
-
- vec3 shades = vec3(lcdFilter(shadeL.z, shadeL.y, shadeL.x, shade0, shadeR.x),
- lcdFilter(shadeL.y, shadeL.x, shade0, shadeR.x, shadeR.y),
- lcdFilter(shadeL.x, shade0, shadeR.x, shadeR.y, shadeR.z));
+ vec3 shades = vec3(convolve7Tap(shadesL, vec3(shadeC, shadesR.xy), uKernel),
+ convolve7Tap(vec4(shadesL.yzw, shadeC), shadesR.xyz, uKernel),
+ convolve7Tap(vec4(shadesL.zw, shadeC, shadesR.x), shadesR.yzw, uKernel));
vec3 color = mix(uBGColor.rgb, uFGColor.rgb, shades);
float alpha = any(greaterThan(shades, vec3(0.0))) ? uFGColor.a : uBGColor.a;
diff --git a/utils/area-lut/Cargo.toml b/utils/area-lut/Cargo.toml
new file mode 100644
index 00000000..03094d96
--- /dev/null
+++ b/utils/area-lut/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "area-lut"
+version = "0.1.0"
+authors = ["Patrick Walton "]
+
+[dependencies]
+clap = "2.30"
+euclid = "0.17"
+image = "0.18"
diff --git a/utils/area-lut/src/main.rs b/utils/area-lut/src/main.rs
new file mode 100644
index 00000000..7e258c98
--- /dev/null
+++ b/utils/area-lut/src/main.rs
@@ -0,0 +1,91 @@
+// pathfinder/area-lut/src/main.rs
+
+extern crate clap;
+extern crate euclid;
+extern crate image;
+
+use clap::{App, Arg};
+use euclid::Point2D;
+use image::{ImageBuffer, Luma};
+use std::f32;
+use std::path::Path;
+
+const WIDTH: u32 = 256;
+const HEIGHT: u32 = 256;
+
+fn solve_line_y(p0: &Point2D, p1: &Point2D, y: f32) -> Point2D {
+ let m = (p1.y - p0.y) / (p1.x - p0.x);
+ Point2D::new(p0.x - (p0.y - y) / m, y)
+}
+
+fn area_tri(p0: Point2D, p1: Point2D) -> f32 {
+ 0.5 * (p1.x - p0.x) * (p0.y - p1.y)
+}
+
+fn area_rect(p0: Point2D, p1: Point2D) -> f32 {
+ (p1.x - p0.x) * (p0.y - p1.y)
+}
+
+fn main() {
+ let app = App::new("Pathfinder Area LUT Generator")
+ .version("0.1")
+ .author("The Pathfinder Project Developers")
+ .about("Generates area lookup tables for use with Pathfinder")
+ .arg(Arg::with_name("OUTPUT-PATH").help("The `.png` image to produce")
+ .required(true)
+ .index(1));
+
+ let matches = app.get_matches();
+ let image = ImageBuffer::from_fn(WIDTH, HEIGHT, |u, v| {
+ if u == 0 {
+ return Luma([255])
+ }
+ if u == WIDTH - 1 {
+ return Luma([0])
+ }
+
+ let y = ((u as f32) - (WIDTH / 2) as f32) / 16.0;
+ let dydx = -(v as f32) / 16.0;
+
+ let (x_left, x_right) = (-0.5, 0.5);
+ let (y_left, y_right) = (dydx * x_left + y, dydx * x_right + y);
+
+ let (p0, p1) = (Point2D::new(x_left, y_left), Point2D::new(x_right, y_right));
+ let p2 = solve_line_y(&p0, &p1, -0.5);
+ let p3 = Point2D::new(p1.x, -0.5);
+ let p4 = solve_line_y(&p0, &p1, 0.5);
+ let p7 = Point2D::new(p1.x, 0.5);
+
+ let alpha;
+ if p0.y > 0.5 {
+ if p1.y < -0.5 {
+ // Case 0
+ alpha = area_tri(p0, p1) - area_tri(p2, p1) - area_rect(p0, p7) + area_tri(p0, p4);
+ } else if p1.y < 0.5 {
+ // Case 6
+ alpha = area_tri(p0, p1) - area_rect(p0, p7) + area_tri(p0, p4);
+ } else {
+ // Case 3
+ alpha = 0.0;
+ }
+ } else if p0.y > -0.5 {
+ if p1.y < -0.5 {
+ // Case 1
+ alpha = area_tri(p0, p1) - area_tri(p2, p1) - area_rect(p0, p7);
+ } else {
+ // Case 4
+ alpha = area_tri(p0, p1) - area_rect(p0, p7);
+ }
+ } else {
+ // Case 2
+ alpha = -area_rect(p0, p7) + area_rect(p0, p3);
+ }
+
+ Luma([f32::round(alpha * 255.0) as u8])
+ });
+
+ let output_path = matches.value_of("OUTPUT-PATH").unwrap();
+ let output_path = Path::new(output_path);
+
+ image.save(&output_path).unwrap();
+}