Skip to content

Commit

Permalink
Per-bucket glyph and icon atlases
Browse files Browse the repository at this point in the history
  • Loading branch information
jfirebaugh committed Aug 24, 2017
1 parent 5a56d6f commit f6cdaf5
Show file tree
Hide file tree
Showing 46 changed files with 3,257 additions and 3,102 deletions.
3 changes: 3 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
"no-useless-escape": "off",
"indent": ["error", 4, {
"flatTernaryExpressions": true,
"CallExpression": {
"arguments": "off"
},
"FunctionDeclaration": {
"parameters": "off"
},
Expand Down
20 changes: 19 additions & 1 deletion flow-typed/pbf.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
declare module "pbf" {
declare type ReadFunction<T> = (tag: number, result: T, pbf: Pbf) => void;

declare class Pbf {
constructor(ArrayBuffer | $ArrayBufferView): Pbf;
constructor(buf?: ArrayBuffer | Uint8Array): Pbf;

readFields<T>(readField: ReadFunction<T>, result: T, end?: number): T;
readMessage<T>(readField: ReadFunction<T>, result: T): T;

readFixed32(): number;
readSFixed32(): number;
readFixed64(): number;
readSFixed64(): number;
readFloat(): number;
readDouble(): number;
readVarint(): number;
readVarint64(): number;
readSVarint(): number;
readBoolean(): boolean;
readString(): string;
readBytes(): Uint8Array;
}

declare module.exports: typeof Pbf
Expand Down
24 changes: 24 additions & 0 deletions flow-typed/shelf-pack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
declare module "@mapbox/shelf-pack" {
declare type Bin = {
id: number|string,
x: number,
y: number,
w: number,
h: number
};

declare class ShelfPack {
w: number;
h: number;

constructor(w: number, h: number, options?: {autoResize: boolean}): ShelfPack;

pack(bins: Array<{w: number, h: number}>, options?: {inPlace: boolean}): Array<Bin>;
packOne(w: number, h: number, id?: number|string): Bin;

ref(bin: Bin): number;
unref(bin: Bin): number;
}

declare module.exports: typeof ShelfPack;
}
1 change: 1 addition & 0 deletions src/data/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type BucketParameters = {
index: number,
layers: Array<StyleLayer>,
zoom: number,
pixelRatio: number,
overscaling: number,
collisionBoxArray: CollisionBoxArray
}
Expand Down
76 changes: 38 additions & 38 deletions src/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ import type {
import type StyleLayer from '../../style/style_layer';
import type {Shaping, PositionedIcon} from '../../symbol/shaping';
import type {SymbolQuad} from '../../symbol/quads';
import type {StyleImage} from '../../style/style_image';
import type {StyleGlyph} from '../../style/style_glyph';
import type {ImagePosition} from '../../render/image_atlas';
import type {GlyphPosition} from '../../render/glyph_atlas';

type SymbolBucketParameters = BucketParameters & {
sdfIcons: boolean,
Expand Down Expand Up @@ -320,7 +324,6 @@ class SymbolBucket implements Bucket {
index: number;
sdfIcons: boolean;
iconsNeedLinear: boolean;
fontstack: string;
textSizeData: any;
iconSizeData: any;
placedGlyphArray: StructArray;
Expand All @@ -329,6 +332,7 @@ class SymbolBucket implements Bucket {
lineVertexArray: StructArray;
features: Array<SymbolFeature>;
symbolInstances: Array<SymbolInstance>;
pixelRatio: number;
tilePixelRatio: number;
compareText: {[string]: Array<Point>};

Expand All @@ -345,7 +349,7 @@ class SymbolBucket implements Bucket {
this.index = options.index;
this.sdfIcons = options.sdfIcons;
this.iconsNeedLinear = options.iconsNeedLinear;
this.fontstack = options.fontstack;
this.pixelRatio = options.pixelRatio;

// deserializing a bucket created on a worker thread
if (options.text) {
Expand Down Expand Up @@ -463,7 +467,6 @@ class SymbolBucket implements Bucket {
iconsNeedLinear: this.iconsNeedLinear,
textSizeData: this.textSizeData,
iconSizeData: this.iconSizeData,
fontstack: this.fontstack,
placedGlyphArray: this.placedGlyphArray.serialize(transferables),
placedIconArray: this.placedIconArray.serialize(transferables),
glyphOffsetArray: this.glyphOffsetArray.serialize(transferables),
Expand All @@ -486,7 +489,10 @@ class SymbolBucket implements Bucket {
this.collisionBox.destroy();
}

prepare(stacks: any, icons: any) {
prepare(glyphMap: {[string]: {[number]: ?StyleGlyph}},
glyphPositions: {[string]: {[number]: GlyphPosition}},
imageMap: {[string]: StyleImage},
imagePositions: {[string]: ImagePosition}) {
this.symbolInstances = [];

const tileSize = 512 * this.overscaling;
Expand All @@ -498,15 +504,17 @@ class SymbolBucket implements Bucket {

const oneEm = 24;
const lineHeight = layout['text-line-height'] * oneEm;
const fontstack = this.fontstack = layout['text-font'].join(',');

const fontstack = layout['text-font'].join(',');
const textAlongLine = layout['text-rotation-alignment'] === 'map' && layout['symbol-placement'] === 'line';
const glyphs = glyphMap[fontstack] || {};
const glyphPositionMap = glyphPositions[fontstack] || {};

for (const feature of this.features) {

let shapedTextOrientations;
const shapedTextOrientations = {};
const text = feature.text;
if (text) {
const allowsVerticalWritingMode = scriptDetection.allowsVerticalWritingMode(text);
const textOffset = this.layers[0].getLayoutValue('text-offset', {zoom: this.zoom}, feature.properties).map((t)=> t * oneEm);
const spacing = this.layers[0].getLayoutValue('text-letter-spacing', {zoom: this.zoom}, feature.properties) * oneEm;
const spacingIfAllowed = scriptDetection.allowsLetterSpacing(text) ? spacing : 0;
Expand All @@ -516,44 +524,32 @@ class SymbolBucket implements Bucket {
this.layers[0].getLayoutValue('text-max-width', {zoom: this.zoom}, feature.properties) * oneEm :
0;

shapedTextOrientations = {
[WritingMode.horizontal]: shapeText(text,
stacks[fontstack],
maxWidth,
lineHeight,
textAnchor,
textJustify,
spacingIfAllowed,
textOffset,
oneEm,
WritingMode.horizontal),
[WritingMode.vertical]: allowsVerticalWritingMode && textAlongLine && shapeText(text,
stacks[fontstack],
maxWidth,
lineHeight,
textAnchor,
textJustify,
spacingIfAllowed,
textOffset,
oneEm,
WritingMode.vertical)
const applyShaping = (text: string, writingMode: 1 | 2) => {
return shapeText(
text, glyphs, maxWidth, lineHeight, textAnchor, textJustify,
spacingIfAllowed, textOffset, oneEm, writingMode);
};
} else {
shapedTextOrientations = {};

shapedTextOrientations[WritingMode.horizontal] = applyShaping(text, WritingMode.horizontal);

if (scriptDetection.allowsVerticalWritingMode(text) && textAlongLine) {
shapedTextOrientations[WritingMode.vertical] = applyShaping(text, WritingMode.vertical);
}
}

let shapedIcon;
if (feature.icon) {
const image = icons[feature.icon];
const image = imageMap[feature.icon];
if (image) {
shapedIcon = shapeIcon(image,
shapedIcon = shapeIcon(
imagePositions[feature.icon],
this.layers[0].getLayoutValue('icon-offset', {zoom: this.zoom}, feature.properties));
if (this.sdfIcons === undefined) {
this.sdfIcons = image.sdf;
} else if (this.sdfIcons !== image.sdf) {
util.warnOnce('Style sheet warning: Cannot mix SDF and non-SDF icons in one buffer');
}
if (!image.isNativePixelRatio) {
if (image.pixelRatio !== this.pixelRatio) {
this.iconsNeedLinear = true;
} else if (layout['icon-rotate'] !== 0 || !this.layers[0].isLayoutValueFeatureConstant('icon-rotate')) {
this.iconsNeedLinear = true;
Expand All @@ -562,7 +558,7 @@ class SymbolBucket implements Bucket {
}

if (shapedTextOrientations[WritingMode.horizontal] || shapedIcon) {
this.addFeature(feature, shapedTextOrientations, shapedIcon);
this.addFeature(feature, shapedTextOrientations, shapedIcon, glyphPositionMap);
}
}
}
Expand All @@ -575,7 +571,10 @@ class SymbolBucket implements Bucket {
* source.)
* @private
*/
addFeature(feature: SymbolFeature, shapedTextOrientations: ShapedTextOrientations, shapedIcon: PositionedIcon | void) {
addFeature(feature: SymbolFeature,
shapedTextOrientations: ShapedTextOrientations,
shapedIcon: PositionedIcon | void,
glyphPositionMap: {[number]: GlyphPosition}) {
const layoutTextSize = this.layers[0].getLayoutValue('text-size', {zoom: this.zoom + 1}, feature.properties);
const layoutIconSize = this.layers[0].getLayoutValue('icon-size', {zoom: this.zoom + 1}, feature.properties);

Expand Down Expand Up @@ -628,7 +627,7 @@ class SymbolBucket implements Bucket {
addToBuffers, this.collisionBoxArray, feature.index, feature.sourceLayerIndex, this.index,
textBoxScale, textPadding, textAlongLine, textOffset,
iconBoxScale, iconPadding, iconAlongLine, iconOffset,
{zoom: this.zoom}, feature.properties);
{zoom: this.zoom}, feature.properties, glyphPositionMap);
};

if (symbolPlacement === 'line') {
Expand Down Expand Up @@ -1004,7 +1003,8 @@ class SymbolBucket implements Bucket {
iconAlongLine: boolean,
iconOffset: [number, number],
globalProperties: Object,
featureProperties: Object) {
featureProperties: Object,
glyphPositionMap: {[number]: GlyphPosition}) {

let textCollisionFeature, iconCollisionFeature;
let iconQuads = [];
Expand All @@ -1014,7 +1014,7 @@ class SymbolBucket implements Bucket {
if (!shapedTextOrientations[writingMode]) continue;
glyphQuads = glyphQuads.concat(addToBuffers ?
getGlyphQuads(anchor, shapedTextOrientations[writingMode],
layer, textAlongLine, globalProperties, featureProperties) :
layer, textAlongLine, globalProperties, featureProperties, glyphPositionMap) :
[]);
textCollisionFeature = new CollisionFeature(collisionBoxArray,
line,
Expand Down
10 changes: 6 additions & 4 deletions src/render/draw_line.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,15 @@ function drawLineTile(program, painter, tile, bucket, layer, coord, programConfi
gl.uniform1f(program.uniforms.u_sdfgamma, painter.lineAtlas.width / (Math.min(widthA, widthB) * 256 * browser.devicePixelRatio) / 2);

} else if (image) {
imagePosA = painter.spriteAtlas.getPattern(image.from);
imagePosB = painter.spriteAtlas.getPattern(image.to);
imagePosA = painter.imageManager.getPattern(image.from);
imagePosB = painter.imageManager.getPattern(image.to);
if (!imagePosA || !imagePosB) return;

gl.uniform2f(program.uniforms.u_pattern_size_a, imagePosA.displaySize[0] * image.fromScale / tileRatio, imagePosB.displaySize[1]);
gl.uniform2f(program.uniforms.u_pattern_size_b, imagePosB.displaySize[0] * image.toScale / tileRatio, imagePosB.displaySize[1]);
gl.uniform2fv(program.uniforms.u_texsize, painter.spriteAtlas.getPixelSize());

const {width, height} = painter.imageManager.getPixelSize();
gl.uniform2fv(program.uniforms.u_texsize, [width, height]);
}

gl.uniform2f(program.uniforms.u_gl_units_to_pixels, 1 / painter.transform.pixelsToGLUnits[0], 1 / painter.transform.pixelsToGLUnits[1]);
Expand All @@ -95,7 +97,7 @@ function drawLineTile(program, painter, tile, bucket, layer, coord, programConfi
} else if (image) {
gl.uniform1i(program.uniforms.u_image, 0);
gl.activeTexture(gl.TEXTURE0);
painter.spriteAtlas.bind(gl, true);
painter.imageManager.bind(gl);

gl.uniform2fv(program.uniforms.u_pattern_tl_a, (imagePosA: any).tl);
gl.uniform2fv(program.uniforms.u_pattern_br_a, (imagePosA: any).br);
Expand Down
54 changes: 23 additions & 31 deletions src/render/draw_symbol.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,8 @@ function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolSt
function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate, translateAnchor,
rotationAlignment, pitchAlignment, keepUpright) {

if (!isText && painter.style.sprite && !painter.style.sprite.loaded())
return;

const gl = painter.gl;
const tr = painter.transform;

const rotateWithMap = rotationAlignment === 'map';
const pitchWithMap = pitchAlignment === 'map';
Expand All @@ -85,7 +83,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
gl.disable(gl.DEPTH_TEST);
}

let program, prevFontstack;
let program;

for (const coord of coords) {
const tile = sourceCache.getTile(coord);
Expand All @@ -99,11 +97,29 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate

const sizeData = isText ? bucket.textSizeData : bucket.iconSizeData;

if (!program || bucket.fontstack !== prevFontstack) {
if (!program) {
program = painter.useProgram(isSDF ? 'symbolSDF' : 'symbolIcon', programConfiguration);
programConfiguration.setUniforms(gl, program, layer, {zoom: painter.transform.zoom});

setSymbolDrawState(program, painter, layer, coord.z, isText, isSDF, rotateInShader, pitchWithMap, bucket.fontstack, bucket.iconsNeedLinear, sizeData);
setSymbolDrawState(program, painter, layer, isText, rotateInShader, pitchWithMap, sizeData);
}

gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(program.uniforms.u_texture, 0);

if (isText) {
tile.glyphAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
gl.uniform2fv(program.uniforms.u_texsize, tile.glyphAtlasTexture.size);
} else {
const iconScaled = !layer.isLayoutValueFeatureConstant('icon-size') ||
!layer.isLayoutValueZoomConstant('icon-size') ||
layer.getLayoutValue('icon-size', { zoom: tr.zoom }) !== 1 ||
bucket.iconsNeedLinear;
const iconTransformed = pitchWithMap || tr.pitch !== 0;

tile.iconAtlasTexture.bind(isSDF || painter.options.rotating || painter.options.zooming || iconScaled || iconTransformed ?
gl.LINEAR : gl.NEAREST, gl.CLAMP_TO_EDGE);
gl.uniform2fv(program.uniforms.u_texsize, tile.iconAtlasTexture.size);
}

painter.enableTileClippingMask(coord);
Expand All @@ -125,44 +141,20 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
gl.uniform1f(program.uniforms.u_collision_y_stretch, (tile.collisionTile: any).yStretch);

drawTileSymbols(program, programConfiguration, painter, layer, tile, buffers, isText, isSDF, pitchWithMap);

prevFontstack = bucket.fontstack;
}

if (!depthOn) gl.enable(gl.DEPTH_TEST);
}

function setSymbolDrawState(program, painter, layer, tileZoom, isText, isSDF, rotateInShader, pitchWithMap, fontstack, iconsNeedLinear, sizeData) {
function setSymbolDrawState(program, painter, layer, isText, rotateInShader, pitchWithMap, sizeData) {

const gl = painter.gl;
const tr = painter.transform;

gl.uniform1i(program.uniforms.u_pitch_with_map, pitchWithMap ? 1 : 0);

gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(program.uniforms.u_texture, 0);

gl.uniform1f(program.uniforms.u_is_text, isText ? 1 : 0);

if (isText) {
// use the fonstack used when parsing the tile, not the fontstack
// at the current zoom level (layout['text-font']).
const glyphAtlas = fontstack && painter.glyphSource.getGlyphAtlas(fontstack);
if (!glyphAtlas) return;

glyphAtlas.updateTexture(gl);
gl.uniform2f(program.uniforms.u_texsize, glyphAtlas.width, glyphAtlas.height);
} else {
const mapMoving = painter.options.rotating || painter.options.zooming;
const iconSizeScaled = !layer.isLayoutValueFeatureConstant('icon-size') ||
!layer.isLayoutValueZoomConstant('icon-size') ||
layer.getLayoutValue('icon-size', { zoom: tr.zoom }) !== 1;
const iconScaled = iconSizeScaled || iconsNeedLinear;
const iconTransformed = pitchWithMap || tr.pitch !== 0;
painter.spriteAtlas.bind(gl, isSDF || mapMoving || iconScaled || iconTransformed);
gl.uniform2fv(program.uniforms.u_texsize, painter.spriteAtlas.getPixelSize());
}

gl.activeTexture(gl.TEXTURE1);
painter.frameHistory.bind(gl);
gl.uniform1i(program.uniforms.u_fadetexture, 1);
Expand Down
Loading

0 comments on commit f6cdaf5

Please sign in to comment.