Use the LUT to gamma correct text, and fix stem darkening math

This commit is contained in:
Patrick Walton 2017-11-07 17:24:19 -08:00
parent 82fd214a76
commit e5b76726d9
12 changed files with 180 additions and 67 deletions

View File

@ -70,6 +70,10 @@
{{>partials/switch.html id="pf-subpixel-aa" {{>partials/switch.html id="pf-subpixel-aa"
title="Subpixel AA"}} title="Subpixel AA"}}
</div> </div>
<div class="form-group row justify-content-between">
{{>partials/switch.html id="pf-gamma-correction"
title="Gamma Correction"}}
</div>
<div class="form-group row justify-content-between"> <div class="form-group row justify-content-between">
{{>partials/switch.html id="pf-stem-darkening" {{>partials/switch.html id="pf-stem-darkening"
title="Stem Darkening"}} title="Stem Darkening"}}

View File

@ -22,6 +22,8 @@ export type DirectRenderingMode = 'none' | 'color' | 'color-depth';
export type SubpixelAAType = 'none' | 'medium'; export type SubpixelAAType = 'none' | 'medium';
export type GammaCorrectionMode = 'off' | 'on';
export type StemDarkeningMode = 'none' | 'dark'; export type StemDarkeningMode = 'none' | 'dark';
export abstract class AntialiasingStrategy { export abstract class AntialiasingStrategy {

View File

@ -8,7 +8,8 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
import {AntialiasingStrategyName, StemDarkeningMode, SubpixelAAType} from "./aa-strategy"; import {AntialiasingStrategyName, GammaCorrectionMode, StemDarkeningMode} from "./aa-strategy";
import {SubpixelAAType} from "./aa-strategy";
import {FilePickerView} from "./file-picker"; import {FilePickerView} from "./file-picker";
import {ShaderLoader, ShaderMap, ShaderProgramSource} from './shader-loader'; import {ShaderLoader, ShaderMap, ShaderProgramSource} from './shader-loader';
import {expectNotNull, unwrapNull, unwrapUndef} from './utils'; import {expectNotNull, unwrapNull, unwrapUndef} from './utils';
@ -16,6 +17,52 @@ import {DemoView, Timings, TIMINGS} from "./view";
const GAMMA_LUT_URI: string = "/textures/gamma-lut.png"; const GAMMA_LUT_URI: string = "/textures/gamma-lut.png";
const SWITCHES: SwitchMap = {
gammaCorrection: {
id: 'pf-gamma-correction',
offValue: 'off',
onValue: 'on',
radioButtonName: 'gammaCorrectionRadioButton',
},
stemDarkening: {
id: 'pf-stem-darkening',
offValue: 'none',
onValue: 'dark',
radioButtonName: 'stemDarkeningRadioButton',
},
subpixelAA: {
id: 'pf-subpixel-aa',
offValue: 'none',
onValue: 'medium',
radioButtonName: 'subpixelAARadioButton',
},
};
interface SwitchDescriptor {
id: string;
radioButtonName: keyof Switches;
onValue: string;
offValue: string;
}
interface SwitchMap {
gammaCorrection: SwitchDescriptor;
stemDarkening: SwitchDescriptor;
subpixelAA: SwitchDescriptor;
}
export interface AAOptions {
gammaCorrection: GammaCorrectionMode;
stemDarkening: StemDarkeningMode;
subpixelAA: SubpixelAAType;
}
interface Switches {
subpixelAARadioButton: HTMLInputElement | null;
gammaCorrectionRadioButton: HTMLInputElement | null;
stemDarkeningRadioButton: HTMLInputElement | null;
}
export abstract class AppController { export abstract class AppController {
protected canvas: HTMLCanvasElement; protected canvas: HTMLCanvasElement;
@ -47,9 +94,14 @@ export abstract class AppController {
protected abstract get defaultFile(): string; protected abstract get defaultFile(): string;
} }
export abstract class DemoAppController<View extends DemoView> extends AppController { export abstract class DemoAppController<View extends DemoView> extends AppController
implements Switches {
view: Promise<View>; view: Promise<View>;
subpixelAARadioButton: HTMLInputElement | null;
gammaCorrectionRadioButton: HTMLInputElement | null;
stemDarkeningRadioButton: HTMLInputElement | null;
protected abstract readonly builtinFileURI: string; protected abstract readonly builtinFileURI: string;
protected filePickerView: FilePickerView | null; protected filePickerView: FilePickerView | null;
@ -59,8 +111,6 @@ export abstract class DemoAppController<View extends DemoView> extends AppContro
protected gammaLUT: HTMLImageElement; protected gammaLUT: HTMLImageElement;
private aaLevelSelect: HTMLSelectElement | null; private aaLevelSelect: HTMLSelectElement | null;
private subpixelAARadioButton: HTMLInputElement | null;
private stemDarkeningRadioButton: HTMLInputElement | null;
private fpsLabel: HTMLElement | null; private fpsLabel: HTMLElement | null;
constructor() { constructor() {
@ -169,22 +219,15 @@ export abstract class DemoAppController<View extends DemoView> extends AppContro
// click listener that Bootstrap sets up until the event bubbles up to the document. This // 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 // click listener is what toggles the `checked` attribute, so we have to wait until it
// fires before updating the antialiasing settings. // fires before updating the antialiasing settings.
this.subpixelAARadioButton = for (const switchName of Object.keys(SWITCHES) as Array<keyof SwitchMap>) {
document.getElementById('pf-subpixel-aa-select-on') as HTMLInputElement | null; const radioButtonName = SWITCHES[switchName].radioButtonName;
const subpixelAAButtons = const switchID = SWITCHES[switchName].id;
document.getElementById('pf-subpixel-aa-buttons') as HTMLElement | null; this[radioButtonName] = document.getElementById(`${switchID}-select-on`) as
if (subpixelAAButtons != null) { HTMLInputElement | null;
subpixelAAButtons.addEventListener('click', () => { const buttons = document.getElementById(`${switchID}-buttons`) as HTMLElement | null;
window.setTimeout(() => this.updateAALevel(), 0); if (buttons == null)
}, false); continue;
} buttons.addEventListener('click', () => {
this.stemDarkeningRadioButton =
document.getElementById('pf-stem-darkening-select-on') as HTMLInputElement | null;
const stemDarkeningButtons =
document.getElementById('pf-stem-darkening-buttons') as HTMLElement | null;
if (stemDarkeningButtons != null) {
stemDarkeningButtons.addEventListener('click', () => {
window.setTimeout(() => this.updateAALevel(), 0); window.setTimeout(() => this.updateAALevel(), 0);
}, false); }, false);
} }
@ -242,20 +285,19 @@ export abstract class DemoAppController<View extends DemoView> extends AppContro
aaLevel = 0; aaLevel = 0;
} }
let subpixelAA: SubpixelAAType; const aaOptions: Partial<AAOptions> = {};
if (this.subpixelAARadioButton != null && this.subpixelAARadioButton.checked) for (const switchName of Object.keys(SWITCHES) as Array<keyof SwitchMap>) {
subpixelAA = 'medium'; const switchDescriptor = SWITCHES[switchName];
const radioButtonName = switchDescriptor.radioButtonName;
const radioButton = this[radioButtonName];
if (radioButton != null && radioButton.checked)
aaOptions[switchName] = switchDescriptor.onValue as any;
else else
subpixelAA = 'none'; aaOptions[switchName] = switchDescriptor.offValue as any;
}
let stemDarkening: StemDarkeningMode;
if (this.stemDarkeningRadioButton != null && this.stemDarkeningRadioButton.checked)
stemDarkening = 'dark';
else
stemDarkening = 'none';
this.view.then(view => { this.view.then(view => {
view.setAntialiasingOptions(aaType, aaLevel, subpixelAA, stemDarkening); view.setAntialiasingOptions(aaType, aaLevel, aaOptions as AAOptions);
}); });
} }

View File

@ -11,8 +11,9 @@
import * as glmatrix from 'gl-matrix'; import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash'; import * as _ from 'lodash';
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy'; import {AntialiasingStrategy, AntialiasingStrategyName, GammaCorrectionMode} from './aa-strategy';
import {StemDarkeningMode, SubpixelAAType} from './aa-strategy'; import {NoAAStrategy, StemDarkeningMode, SubpixelAAType} from './aa-strategy';
import {AAOptions} from './app-controller';
import PathfinderBufferTexture from "./buffer-texture"; import PathfinderBufferTexture from "./buffer-texture";
import {UniformMap} from './gl-utils'; import {UniformMap} from './gl-utils';
import {PathfinderMeshBuffers, PathfinderMeshData} from "./meshes"; import {PathfinderMeshBuffers, PathfinderMeshData} from "./meshes";
@ -42,8 +43,8 @@ export abstract class Renderer {
return glmatrix.vec2.create(); return glmatrix.vec2.create();
} }
get bgColor(): glmatrix.vec4 | null { get bgColor(): glmatrix.vec4 {
return null; return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
} }
get fgColor(): glmatrix.vec4 | null { get fgColor(): glmatrix.vec4 | null {
@ -66,6 +67,8 @@ export abstract class Renderer {
protected lastTimings: Timings; protected lastTimings: Timings;
protected pathColorsBufferTextures: PathfinderBufferTexture[]; protected pathColorsBufferTextures: PathfinderBufferTexture[];
protected gammaCorrectionMode: GammaCorrectionMode;
protected get pathIDsAreInstanced(): boolean { protected get pathIDsAreInstanced(): boolean {
return false; return false;
} }
@ -87,6 +90,8 @@ export abstract class Renderer {
this.lastTimings = { rendering: 0, compositing: 0 }; this.lastTimings = { rendering: 0, compositing: 0 };
this.gammaCorrectionMode = 'on';
this.pathTransformBufferTextures = []; this.pathTransformBufferTextures = [];
this.pathColorsBufferTextures = []; this.pathColorsBufferTextures = [];
@ -154,12 +159,14 @@ export abstract class Renderer {
setAntialiasingOptions(aaType: AntialiasingStrategyName, setAntialiasingOptions(aaType: AntialiasingStrategyName,
aaLevel: number, aaLevel: number,
subpixelAA: SubpixelAAType, aaOptions: AAOptions):
stemDarkening: StemDarkeningMode) { void {
this.gammaCorrectionMode = aaOptions.gammaCorrection;
this.antialiasingStrategy = this.createAAStrategy(aaType, this.antialiasingStrategy = this.createAAStrategy(aaType,
aaLevel, aaLevel,
subpixelAA, aaOptions.subpixelAA,
stemDarkening); aaOptions.stemDarkening);
this.antialiasingStrategy.init(this); this.antialiasingStrategy.init(this);
if (this.meshData != null) if (this.meshData != null)
@ -168,12 +175,12 @@ export abstract class Renderer {
this.renderContext.setDirty(); this.renderContext.setDirty();
} }
canvasResized() { canvasResized(): void {
if (this.antialiasingStrategy != null) if (this.antialiasingStrategy != null)
this.antialiasingStrategy.init(this); this.antialiasingStrategy.init(this);
} }
setFramebufferSizeUniform(uniforms: UniformMap) { setFramebufferSizeUniform(uniforms: UniformMap): void {
const gl = this.renderContext.gl; const gl = this.renderContext.gl;
gl.uniform2i(uniforms.uFramebufferSize, gl.uniform2i(uniforms.uFramebufferSize,
this.destAllocatedSize[0], this.destAllocatedSize[0],
@ -201,7 +208,7 @@ export abstract class Renderer {
gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]); gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
} }
setTransformSTUniform(uniforms: UniformMap, objectIndex: number) { setTransformSTUniform(uniforms: UniformMap, objectIndex: number): void {
// FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an ST matrix is ugly and fragile. // FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an ST matrix is ugly and fragile.
// Refactor. // Refactor.
const renderContext = this.renderContext; const renderContext = this.renderContext;
@ -219,7 +226,7 @@ export abstract class Renderer {
transform[13]); transform[13]);
} }
uploadPathColors(objectCount: number) { uploadPathColors(objectCount: number): void {
const renderContext = this.renderContext; const renderContext = this.renderContext;
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) { for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
const pathColors = this.pathColorsForObject(objectIndex); const pathColors = this.pathColorsForObject(objectIndex);
@ -237,7 +244,7 @@ export abstract class Renderer {
} }
} }
uploadPathTransforms(objectCount: number) { uploadPathTransforms(objectCount: number): void {
const renderContext = this.renderContext; const renderContext = this.renderContext;
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) { for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
const pathTransforms = this.pathTransformsForObject(objectIndex); const pathTransforms = this.pathTransformsForObject(objectIndex);
@ -273,6 +280,18 @@ export abstract class Renderer {
return glmatrix.vec4.create(); return glmatrix.vec4.create();
} }
protected bindGammaLUT(bgColor: glmatrix.vec3, textureUnit: number, uniforms: UniformMap):
void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
gl.activeTexture(gl.TEXTURE0 + textureUnit);
gl.bindTexture(gl.TEXTURE_2D, this.gammaLUTTexture);
gl.uniform1i(uniforms.uGammaLUT, textureUnit);
gl.uniform3f(uniforms.uBGColor, bgColor[0], bgColor[1], bgColor[2]);
}
protected abstract createAAStrategy(aaType: AntialiasingStrategyName, protected abstract createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number, aaLevel: number,
subpixelAA: SubpixelAAType, subpixelAA: SubpixelAAType,
@ -515,6 +534,12 @@ export abstract class Renderer {
const texture = unwrapNull(gl.createTexture()); const texture = unwrapNull(gl.createTexture());
gl.bindTexture(gl.TEXTURE_2D, texture); gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, gl.LUMINANCE, gl.UNSIGNED_BYTE, gammaLUT); 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.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;
} }
private initImplicitCoverCurveVAO(objectIndex: number, instanceRange: Range): void { private initImplicitCoverCurveVAO(objectIndex: number, instanceRange: Range): void {

View File

@ -12,7 +12,8 @@ import {AttributeMap, UniformMap} from './gl-utils';
import {expectNotNull, PathfinderError, unwrapNull} from './utils'; import {expectNotNull, PathfinderError, unwrapNull} from './utils';
export interface ShaderMap<T> { export interface ShaderMap<T> {
blit: T; blitLinear: T;
blitGamma: T;
compositeAlphaMask: T; compositeAlphaMask: T;
demo3DDistantGlyph: T; demo3DDistantGlyph: T;
demo3DMonument: T; demo3DMonument: T;
@ -43,7 +44,8 @@ export interface UnlinkedShaderProgram {
const COMMON_SHADER_URL: string = '/glsl/gles2/common.inc.glsl'; const COMMON_SHADER_URL: string = '/glsl/gles2/common.inc.glsl';
export const SHADER_NAMES: Array<keyof ShaderMap<void>> = [ export const SHADER_NAMES: Array<keyof ShaderMap<void>> = [
'blit', 'blitLinear',
'blitGamma',
'compositeAlphaMask', 'compositeAlphaMask',
'directCurve', 'directCurve',
'directInterior', 'directInterior',
@ -67,8 +69,12 @@ export const SHADER_NAMES: Array<keyof ShaderMap<void>> = [
]; ];
const SHADER_URLS: ShaderMap<ShaderProgramURLs> = { const SHADER_URLS: ShaderMap<ShaderProgramURLs> = {
blit: { blitGamma: {
fragment: "/glsl/gles2/blit.fs.glsl", fragment: "/glsl/gles2/blit-gamma.fs.glsl",
vertex: "/glsl/gles2/blit.vs.glsl",
},
blitLinear: {
fragment: "/glsl/gles2/blit-linear.fs.glsl",
vertex: "/glsl/gles2/blit.vs.glsl", vertex: "/glsl/gles2/blit.vs.glsl",
}, },
compositeAlphaMask: { compositeAlphaMask: {

View File

@ -176,7 +176,7 @@ export default class SSAAStrategy extends AntialiasingStrategy {
if (this.subpixelAA !== 'none') if (this.subpixelAA !== 'none')
resolveProgram = renderContext.shaderPrograms.ssaaSubpixelResolve; resolveProgram = renderContext.shaderPrograms.ssaaSubpixelResolve;
else else
resolveProgram = renderContext.shaderPrograms.blit; resolveProgram = renderContext.shaderPrograms.blitLinear;
gl.useProgram(resolveProgram.program); gl.useProgram(resolveProgram.program);
renderContext.initQuadVAO(resolveProgram.attributes); renderContext.initQuadVAO(resolveProgram.attributes);

View File

@ -16,7 +16,7 @@ import * as opentype from 'opentype.js';
import {Metrics} from 'opentype.js'; import {Metrics} from 'opentype.js';
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
import {StemDarkeningMode, SubpixelAAType} from './aa-strategy'; import {StemDarkeningMode, SubpixelAAType} from './aa-strategy';
import {DemoAppController} from './app-controller'; import {AAOptions, DemoAppController} from './app-controller';
import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey, SUBPIXEL_GRANULARITY} from './atlas'; import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey, SUBPIXEL_GRANULARITY} from './atlas';
import PathfinderBufferTexture from './buffer-texture'; import PathfinderBufferTexture from './buffer-texture';
import {CameraView, OrthographicCamera} from "./camera"; import {CameraView, OrthographicCamera} from "./camera";
@ -379,10 +379,9 @@ class TextDemoRenderer extends TextRenderer {
setAntialiasingOptions(aaType: AntialiasingStrategyName, setAntialiasingOptions(aaType: AntialiasingStrategyName,
aaLevel: number, aaLevel: number,
subpixelAA: SubpixelAAType, aaOptions: AAOptions):
stemDarkening: StemDarkeningMode):
void { void {
super.setAntialiasingOptions(aaType, aaLevel, subpixelAA, stemDarkening); super.setAntialiasingOptions(aaType, aaLevel, aaOptions);
// Need to relayout because changing AA options can cause font dilation to change... // Need to relayout because changing AA options can cause font dilation to change...
this.layoutText(); this.layoutText();
@ -425,8 +424,11 @@ class TextDemoRenderer extends TextRenderer {
gl.clearColor(1.0, 1.0, 1.0, 1.0); gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT);
// Set the appropriate program.
const programName = this.gammaCorrectionMode === 'off' ? 'blitLinear' : 'blitGamma';
const blitProgram = this.renderContext.shaderPrograms[programName];
// Set up the composite VAO. // Set up the composite VAO.
const blitProgram = this.renderContext.shaderPrograms.blit;
const attributes = blitProgram.attributes; const attributes = blitProgram.attributes;
gl.useProgram(blitProgram.program); gl.useProgram(blitProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
@ -458,6 +460,7 @@ class TextDemoRenderer extends TextRenderer {
gl.bindTexture(gl.TEXTURE_2D, destTexture); gl.bindTexture(gl.TEXTURE_2D, destTexture);
gl.uniform1i(blitProgram.uniforms.uSource, 0); gl.uniform1i(blitProgram.uniforms.uSource, 0);
this.setIdentityTexScaleUniform(blitProgram.uniforms); this.setIdentityTexScaleUniform(blitProgram.uniforms);
this.bindGammaLUT(glmatrix.vec3.clone([1.0, 1.0, 1.0]), 1, blitProgram.uniforms);
const totalGlyphCount = this.layout.textFrame.totalGlyphCount; const totalGlyphCount = this.layout.textFrame.totalGlyphCount;
gl.drawElements(gl.TRIANGLES, totalGlyphCount * 6, gl.UNSIGNED_INT, 0); gl.drawElements(gl.TRIANGLES, totalGlyphCount * 6, gl.UNSIGNED_INT, 0);
} }

View File

@ -386,10 +386,7 @@ export function computeStemDarkeningAmount(pixelsPerEm: number, pixelsPerUnit: n
return amount; return amount;
glmatrix.vec2.scale(amount, STEM_DARKENING_FACTORS, pixelsPerEm); glmatrix.vec2.scale(amount, STEM_DARKENING_FACTORS, pixelsPerEm);
glmatrix.vec2.min(amount, amount, MIN_STEM_DARKENING_AMOUNT); glmatrix.vec2.max(amount, amount, MIN_STEM_DARKENING_AMOUNT);
// Convert diameter to radius.
glmatrix.vec2.scale(amount, amount, 0.5);
glmatrix.vec2.scale(amount, amount, 1.0 / pixelsPerUnit); glmatrix.vec2.scale(amount, amount, 1.0 / pixelsPerUnit);
return amount; return amount;
} }

View File

@ -12,6 +12,7 @@ import * as glmatrix from 'gl-matrix';
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
import {StemDarkeningMode, SubpixelAAType} from "./aa-strategy"; import {StemDarkeningMode, SubpixelAAType} from "./aa-strategy";
import {AAOptions} from './app-controller';
import PathfinderBufferTexture from './buffer-texture'; import PathfinderBufferTexture from './buffer-texture';
import {Camera} from "./camera"; import {Camera} from "./camera";
import {EXTDisjointTimerQuery, QUAD_ELEMENTS, UniformMap} from './gl-utils'; import {EXTDisjointTimerQuery, QUAD_ELEMENTS, UniformMap} from './gl-utils';
@ -153,15 +154,13 @@ export abstract class DemoView extends PathfinderView implements RenderContext {
meshData: PathfinderMeshData[]; meshData: PathfinderMeshData[];
get colorAlphaFormat(): GLenum { get colorAlphaFormat(): GLenum {
return this.sRGBExt == null ? this.gl.RGBA : this.sRGBExt.SRGB_ALPHA_EXT; return this.gl.RGBA;
} }
get renderContext(): RenderContext { get renderContext(): RenderContext {
return this; return this;
} }
protected sRGBExt: EXTsRGB;
protected colorBufferHalfFloatExt: any; protected colorBufferHalfFloatExt: any;
private wantsScreenshot: boolean; private wantsScreenshot: boolean;
@ -204,9 +203,9 @@ export abstract class DemoView extends PathfinderView implements RenderContext {
setAntialiasingOptions(aaType: AntialiasingStrategyName, setAntialiasingOptions(aaType: AntialiasingStrategyName,
aaLevel: number, aaLevel: number,
subpixelAA: SubpixelAAType, aaOptions: AAOptions):
stemDarkening: StemDarkeningMode) { void {
this.renderer.setAntialiasingOptions(aaType, aaLevel, subpixelAA, stemDarkening); this.renderer.setAntialiasingOptions(aaType, aaLevel, aaOptions);
} }
protected resized(): void { protected resized(): void {
@ -220,7 +219,6 @@ export abstract class DemoView extends PathfinderView implements RenderContext {
"Failed to initialize WebGL! Check that your browser supports it."); "Failed to initialize WebGL! Check that your browser supports it.");
this.colorBufferHalfFloatExt = this.gl.getExtension('EXT_color_buffer_half_float'); this.colorBufferHalfFloatExt = this.gl.getExtension('EXT_color_buffer_half_float');
this.instancedArraysExt = this.gl.getExtension('ANGLE_instanced_arrays'); this.instancedArraysExt = this.gl.getExtension('ANGLE_instanced_arrays');
this.sRGBExt = this.gl.getExtension('EXT_sRGB');
this.textureHalfFloatExt = this.gl.getExtension('OES_texture_half_float'); this.textureHalfFloatExt = this.gl.getExtension('OES_texture_half_float');
this.timerQueryExt = this.gl.getExtension('EXT_disjoint_timer_query'); this.timerQueryExt = this.gl.getExtension('EXT_disjoint_timer_query');
this.vertexArrayObjectExt = this.gl.getExtension('OES_vertex_array_object'); this.vertexArrayObjectExt = this.gl.getExtension('OES_vertex_array_object');

View File

@ -0,0 +1,25 @@
// pathfinder/shaders/gles2/blit-gamma.fs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
/// Blits a texture, applying gamma correction.
precision mediump float;
uniform sampler2D uSource;
uniform vec3 uBGColor;
uniform sampler2D uGammaLUT;
varying vec2 vTexCoord;
void main() {
vec4 source = texture2D(uSource, vTexCoord);
gl_FragColor = vec4(gammaCorrect(source.rgb, uBGColor, uGammaLUT), source.a);
}

View File

@ -1,4 +1,4 @@
// pathfinder/shaders/gles2/blit.fs.glsl // pathfinder/shaders/gles2/blit-linear.fs.glsl
// //
// Copyright (c) 2017 The Pathfinder Project Developers. // Copyright (c) 2017 The Pathfinder Project Developers.
// //

View File

@ -306,6 +306,17 @@ float lcdFilter(float shadeL2, float shadeL1, float shade0, float shadeR1, float
LCD_FILTER_FACTOR_2 * shadeR2; LCD_FILTER_FACTOR_2 * shadeR2;
} }
float gammaCorrectChannel(float fgColor, float bgColor, sampler2D gammaLUT) {
return texture2D(gammaLUT, vec2(fgColor, 1.0 - bgColor)).r;
}
// `fgColor` is in linear space.
vec3 gammaCorrect(vec3 fgColor, vec3 bgColor, sampler2D gammaLUT) {
return vec3(gammaCorrectChannel(fgColor.r, bgColor.r, gammaLUT),
gammaCorrectChannel(fgColor.g, bgColor.g, gammaLUT),
gammaCorrectChannel(fgColor.b, bgColor.b, gammaLUT));
}
int unpackUInt16(vec2 packedValue) { int unpackUInt16(vec2 packedValue) {
ivec2 valueBytes = ivec2(floor(packedValue * 255.0)); ivec2 valueBytes = ivec2(floor(packedValue * 255.0));
return valueBytes.y * 256 + valueBytes.x; return valueBytes.y * 256 + valueBytes.x;