Use a lookup table to do area calculations instead of Loop-Blinn-style
distance-to-edge for stencil analytic antialiasing. This improves the rendering quality of stencil AAA significantly. Additionally, this adds an approximation of Core Graphics' (macOS') defringing filter. Closes #73.
This commit is contained in:
parent
e851afd5d9
commit
6e13fb171c
|
@ -4,6 +4,7 @@ members = [
|
|||
"partitioner",
|
||||
"path-utils",
|
||||
"demo/server",
|
||||
"utils/area-lut",
|
||||
"utils/frontend",
|
||||
"utils/gamma-lut",
|
||||
]
|
||||
|
|
|
@ -51,8 +51,18 @@
|
|||
<option value="xcaa">XCAA</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group row justify-content-between">
|
||||
{{>partials/switch.html id="pf-subpixel-aa" title="Subpixel AA"}}
|
||||
<div class="form-group">
|
||||
<label for="pf-subpixel-aa-select">Subpixel AA</label>
|
||||
<select id="pf-subpixel-aa-select"
|
||||
class="form-control custom-select">
|
||||
<option value="none">None</option>
|
||||
<option value="freetype" selected>
|
||||
FreeType-style
|
||||
</option>
|
||||
<option value="core-graphics">
|
||||
Core Graphics-style
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -67,9 +67,14 @@
|
|||
<option value="slight" selected>Slight</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group row justify-content-between">
|
||||
{{>partials/switch.html id="pf-subpixel-aa"
|
||||
title="Subpixel AA"}}
|
||||
<div class="form-group">
|
||||
<label for="pf-subpixel-aa-select">Subpixel AA</label>
|
||||
<select id="pf-subpixel-aa-select"
|
||||
class="form-control custom-select">
|
||||
<option value="none">None</option>
|
||||
<option value="freetype" selected>FreeType-style</option>
|
||||
<option value="core-graphics">Core Graphics-style</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group row justify-content-between">
|
||||
{{>partials/switch.html id="pf-gamma-correction"
|
||||
|
|
|
@ -155,11 +155,12 @@ class ThreeDController extends DemoAppController<ThreeDView> {
|
|||
this.monumentPromise.then(monument => this.layoutMonument(fileData, monument));
|
||||
}
|
||||
|
||||
protected createView(gammaLUT: HTMLImageElement,
|
||||
protected createView(areaLUT: HTMLImageElement,
|
||||
gammaLUT: HTMLImageElement,
|
||||
commonShaderSource: string,
|
||||
shaderSources: ShaderMap<ShaderProgramSource>):
|
||||
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<ShaderProgramSource>) {
|
||||
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<Float32Array> {
|
||||
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<Float32Array> {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<View extends DemoView> extends AppContro
|
|||
implements Switches {
|
||||
view!: Promise<View>;
|
||||
|
||||
subpixelAASwitchInputs: SwitchInputs | null = null;
|
||||
gammaCorrectionSwitchInputs: SwitchInputs | null = null;
|
||||
stemDarkeningSwitchInputs: SwitchInputs | null = null;
|
||||
|
||||
|
@ -120,6 +111,7 @@ export abstract class DemoAppController<View extends DemoView> 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<View extends DemoView> 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<View extends DemoView> 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<View extends DemoView> 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<View extends DemoView> extends AppContro
|
|||
// Overridden by subclasses.
|
||||
}
|
||||
|
||||
protected abstract createView(gammaLUT: HTMLImageElement,
|
||||
protected abstract createView(areaLUT: HTMLImageElement,
|
||||
gammaLUT: HTMLImageElement,
|
||||
commonShaderSource: string,
|
||||
shaderSources: ShaderMap<ShaderProgramSource>):
|
||||
View;
|
||||
|
@ -331,8 +341,8 @@ export abstract class DemoAppController<View extends DemoView> extends AppContro
|
|||
}, false);
|
||||
}
|
||||
|
||||
private loadGammaLUT(): Promise<HTMLImageElement> {
|
||||
return window.fetch(GAMMA_LUT_URI)
|
||||
private loadTexture(uri: string): Promise<HTMLImageElement> {
|
||||
return window.fetch(uri)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const imgElement = document.createElement('img');
|
||||
|
|
|
@ -188,19 +188,20 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
|||
}
|
||||
}
|
||||
|
||||
protected createView(gammaLUT: HTMLImageElement,
|
||||
protected createView(areaLUT: HTMLImageElement,
|
||||
gammaLUT: HTMLImageElement,
|
||||
commonShaderSource: string,
|
||||
shaderSources: ShaderMap<ShaderProgramSource>):
|
||||
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<ShaderProgramSource>) {
|
||||
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<Float32Array> {
|
||||
pathTransformsForObject(objectIndex: number): PathTransformBuffers<Float32Array> {
|
||||
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<void> {
|
||||
return 'directCurve';
|
||||
}
|
||||
|
|
|
@ -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<ReferenceTestView> {
|
|||
context.putImageData(imageData, 0, 0);
|
||||
}
|
||||
|
||||
protected createView(gammaLUT: HTMLImageElement,
|
||||
protected createView(areaLUT: HTMLImageElement,
|
||||
gammaLUT: HTMLImageElement,
|
||||
commonShaderSource: string,
|
||||
shaderSources: ShaderMap<ShaderProgramSource>):
|
||||
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<ReferenceTestView> {
|
|||
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<ShaderProgramSource>) {
|
||||
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<Float32Array> {
|
||||
pathTransformsForObject(objectIndex: number): PathTransformBuffers<Float32Array> {
|
||||
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<void> {
|
||||
return 'directCurve';
|
||||
}
|
||||
|
|
|
@ -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<Float32Array>;
|
||||
|
||||
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<Float32Array>;
|
||||
|
||||
protected abstract directCurveProgramName(): keyof ShaderMap<void>;
|
||||
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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -48,11 +48,12 @@ class SVGDemoController extends DemoAppController<SVGDemoView> {
|
|||
});
|
||||
}
|
||||
|
||||
protected createView(gammaLUT: HTMLImageElement,
|
||||
protected createView(areaLUT: HTMLImageElement,
|
||||
gammaLUT: HTMLImageElement,
|
||||
commonShaderSource: string,
|
||||
shaderSources: ShaderMap<ShaderProgramSource>):
|
||||
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<ShaderProgramSource>) {
|
||||
super(gammaLUT, commonShaderSource, shaderSources);
|
||||
super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
|
||||
|
||||
this.appController = appController;
|
||||
this.renderer = new SVGDemoRenderer(this, {sizeToFit: true});
|
||||
|
|
|
@ -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<Float32Array> {
|
||||
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<Float32Array> {
|
||||
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):
|
||||
|
|
|
@ -227,11 +227,12 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
|||
}
|
||||
}
|
||||
|
||||
protected createView(gammaLUT: HTMLImageElement,
|
||||
protected createView(areaLUT: HTMLImageElement,
|
||||
gammaLUT: HTMLImageElement,
|
||||
commonShaderSource: string,
|
||||
shaderSources: ShaderMap<ShaderProgramSource>):
|
||||
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<ShaderProgramSource>) {
|
||||
super(gammaLUT, commonShaderSource, shaderSources);
|
||||
super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
|
||||
|
||||
this.appController = appController;
|
||||
this.renderer = new TextDemoRenderer(this);
|
||||
|
|
|
@ -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<Float32Array> {
|
||||
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<Float32Array> {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -143,6 +143,7 @@ export abstract class DemoView extends PathfinderView implements RenderContext {
|
|||
gl!: WebGLRenderingContext;
|
||||
|
||||
shaderPrograms: ShaderMap<PathfinderShaderProgram>;
|
||||
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<ShaderProgramSource>) {
|
||||
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<PathfinderShaderProgram>;
|
||||
readonly areaLUT: HTMLImageElement;
|
||||
readonly gammaLUT: HTMLImageElement;
|
||||
|
||||
readonly quadPositionsBuffer: WebGLBuffer;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "area-lut"
|
||||
version = "0.1.0"
|
||||
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||
|
||||
[dependencies]
|
||||
clap = "2.30"
|
||||
euclid = "0.17"
|
||||
image = "0.18"
|
|
@ -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<f32>, p1: &Point2D<f32>, y: f32) -> Point2D<f32> {
|
||||
let m = (p1.y - p0.y) / (p1.x - p0.x);
|
||||
Point2D::new(p0.x - (p0.y - y) / m, y)
|
||||
}
|
||||
|
||||
fn area_tri(p0: Point2D<f32>, p1: Point2D<f32>) -> f32 {
|
||||
0.5 * (p1.x - p0.x) * (p0.y - p1.y)
|
||||
}
|
||||
|
||||
fn area_rect(p0: Point2D<f32>, p1: Point2D<f32>) -> 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();
|
||||
}
|
Loading…
Reference in New Issue