Skip to content

Commit

Permalink
Enable property functions for text-size
Browse files Browse the repository at this point in the history
  • Loading branch information
Anand Thakker committed Mar 23, 2017
1 parent 752c39a commit 23e2b51
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 47 deletions.
120 changes: 120 additions & 0 deletions debug/textsize.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel='stylesheet' href='/dist/mapbox-gl.css' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
</style>
</head>

<body>
<div id='map'></div>

<script src='/dist/mapbox-gl-dev.js'></script>
<script src='/debug/access_token_generated.js'></script>
<script>
var stops = [
[ 0, 6 ],
[ 1, 6 ],
[ 2, 6 ],
[ 2.1, 3 ],
[ 2.2, 3 ],
[ 2.3, 3 ],
[ 2.4, 3 ],
[ 3, 12 ],
[ 10, 12 ]
];

var layer = (id, x, textSize, color) => ({
"id": id,
"type": "symbol",
"source": "geojson",
"filter": ["==", "x", x],
"layout": {
"symbol-placement": "line",
"symbol-spacing": 1,
"text-anchor": "left",
"text-allow-overlap": true,
"text-ignore-placement": true,
"text-max-angle": 180,
"text-field": "abcdefghijklmnopqrstuvwxyz",
"text-font": [
"Open Sans Semibold",
"Arial Unicode MS Bold"
],
"text-size": textSize
},
"paint": { "text-color": color }
});

var style = {
"version": 8,
"metadata": {
"test": {
"width": 64,
"height": 64
}
},
"sources": {
"geojson": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": { "x": 0 },
"geometry": {
"type": "LineString",
"coordinates": [
[ -10, -1 ],
[ -20, -10 ],
[ -30, -1 ],
[ -40, -10 ]
]
}
}
]
}
}
},

"glyphs": "mapbox://fonts/mapbox/{fontstack}/{range}.pbf",
"layers": [
layer('symbol-camera', 0, {
"stops": stops
}, 'red'),

layer('symbol-composite', 0, {
"property": "x",
"stops": stops.map(s => [{value: 0, zoom: s[0]}, s[1]])
}, 'blue'),

{
"id": "line",
"type": "line",
"source": "geojson"
}
]
};

var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 12.5,
center: [-77.01866, 38.888],
style: style,
hash: true
});

map.showTileBoundaries = true;

setTimeout(() => console.log(JSON.stringify(map.getStyle())));


</script>
</body>
</html>
29 changes: 13 additions & 16 deletions src/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const symbolInterfaces = {
layoutVertexArrayType: layoutVertexArrayType,
elementArrayType: elementArrayType,
paintAttributes: [
{name: 'a_size', property: 'text-size', type: 'Uint16', multiplier: 10},
{name: 'a_fill_color', property: 'text-color', type: 'Uint8'},
{name: 'a_halo_color', property: 'text-halo-color', type: 'Uint8'},
{name: 'a_halo_width', property: 'text-halo-width', type: 'Uint16', multiplier: 10},
Expand All @@ -53,6 +54,7 @@ const symbolInterfaces = {
layoutVertexArrayType: layoutVertexArrayType,
elementArrayType: elementArrayType,
paintAttributes: [
{name: 'a_size', property: 'icon-size', type: 'Uint16', multiplier: 10},
{name: 'a_fill_color', property: 'icon-color', type: 'Uint8'},
{name: 'a_halo_color', property: 'icon-halo-color', type: 'Uint8'},
{name: 'a_halo_width', property: 'icon-halo-width', type: 'Uint16', multiplier: 10},
Expand Down Expand Up @@ -138,8 +140,6 @@ class SymbolBucket {
this.index = options.index;
this.sdfIcons = options.sdfIcons;
this.iconsNeedLinear = options.iconsNeedLinear;
this.adjustedTextSize = options.adjustedTextSize;
this.adjustedIconSize = options.adjustedIconSize;
this.fontstack = options.fontstack;

if (options.arrays) {
Expand Down Expand Up @@ -245,8 +245,6 @@ class SymbolBucket {
layerIds: this.layers.map((l) => l.id),
sdfIcons: this.sdfIcons,
iconsNeedLinear: this.iconsNeedLinear,
adjustedTextSize: this.adjustedTextSize,
adjustedIconSize: this.adjustedIconSize,
fontstack: this.fontstack,
arrays: util.mapObject(this.arrays, (a) => a.isEmpty() ? null : a.serialize(transferables))
};
Expand All @@ -270,15 +268,6 @@ class SymbolBucket {
prepare(stacks, icons) {
this.symbolInstances = [];

// To reduce the number of labels that jump around when zooming we need
// to use a text-size value that is the same for all zoom levels.
// This calculates text-size at a high zoom level so that all tiles can
// use the same value when calculating anchor positions.
this.adjustedTextMaxSize = this.layers[0].getLayoutValue('text-size', {zoom: 18});
this.adjustedTextSize = this.layers[0].getLayoutValue('text-size', {zoom: this.zoom + 1});
this.adjustedIconMaxSize = this.layers[0].getLayoutValue('icon-size', {zoom: 18});
this.adjustedIconSize = this.layers[0].getLayoutValue('icon-size', {zoom: this.zoom + 1});

const tileSize = 512 * this.overscaling;
this.tilePixelRatio = EXTENT / tileSize;
this.compareText = {};
Expand Down Expand Up @@ -368,13 +357,21 @@ class SymbolBucket {
}

addFeature(feature, shapedTextOrientations, shapedIcon) {
// To reduce the number of labels that jump around when zooming we need
// to use a text-size value that is the same for all zoom levels.
// This calculates text-size at a high zoom level so that all tiles can
// use the same value when calculating anchor positions.
const adjustedTextSize = this.layers[0].getLayoutValue('text-size', {zoom: this.zoom + 1}, feature.properties);
const adjustedIconSize = this.layers[0].getLayoutValue('icon-size', {zoom: this.zoom + 1}, feature.properties);
const adjustedTextMaxSize = this.layers[0].getLayoutValue('text-size', {zoom: 18}, feature.properties);

const layout = this.layers[0].layout,
glyphSize = 24,
fontScale = this.adjustedTextSize / glyphSize,
textMaxSize = this.adjustedTextMaxSize !== undefined ? this.adjustedTextMaxSize : this.adjustedTextSize,
fontScale = adjustedTextSize / glyphSize,
textMaxSize = adjustedTextMaxSize !== undefined ? adjustedTextMaxSize : adjustedTextSize,
textBoxScale = this.tilePixelRatio * fontScale,
textMaxBoxScale = this.tilePixelRatio * textMaxSize / glyphSize,
iconBoxScale = this.tilePixelRatio * this.adjustedIconSize,
iconBoxScale = this.tilePixelRatio * adjustedIconSize,
symbolMinDistance = this.tilePixelRatio * layout['symbol-spacing'],
avoidEdges = layout['symbol-avoid-edges'],
textPadding = layout['text-padding'] * this.tilePixelRatio,
Expand Down
72 changes: 50 additions & 22 deletions src/render/draw_symbol.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const assert = require('assert');
const browser = require('../util/browser');
const drawCollisionDebug = require('./draw_collision_debug');
const pixelsToTileUnits = require('../source/pixels_to_tile_units');
Expand Down Expand Up @@ -37,16 +38,14 @@ function drawSymbols(painter, sourceCache, layer, coords) {
layer.layout['icon-rotation-alignment'],
// icon-pitch-alignment is not yet implemented
// and we simply inherit the rotation alignment
layer.layout['icon-rotation-alignment'],
layer.layout['icon-size']
layer.layout['icon-rotation-alignment']
);

drawLayerSymbols(painter, sourceCache, layer, coords, true,
layer.paint['text-translate'],
layer.paint['text-translate-anchor'],
layer.layout['text-rotation-alignment'],
layer.layout['text-pitch-alignment'],
layer.layout['text-size']
layer.layout['text-pitch-alignment']
);

if (sourceCache.map.showCollisionBoxes) {
Expand All @@ -55,7 +54,7 @@ function drawSymbols(painter, sourceCache, layer, coords) {
}

function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate, translateAnchor,
rotationAlignment, pitchAlignment, size) {
rotationAlignment, pitchAlignment) {

if (!isText && painter.style.sprite && !painter.style.sprite.loaded())
return;
Expand Down Expand Up @@ -90,26 +89,24 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
program = painter.useProgram(isSDF ? 'symbolSDF' : 'symbolIcon', programConfiguration);
programConfiguration.setUniforms(gl, program, layer, {zoom: painter.transform.zoom});

setSymbolDrawState(program, painter, isText, isSDF, rotateWithMap, pitchWithMap, bucket.fontstack, size,
bucket.iconsNeedLinear, isText ? bucket.adjustedTextSize : bucket.adjustedIconSize);
setSymbolDrawState(program, painter, layer, coord.z, isText, isSDF, rotateWithMap, pitchWithMap, bucket.fontstack, bucket.iconsNeedLinear);
}

painter.enableTileClippingMask(coord);

gl.uniformMatrix4fv(program.u_matrix, false,
painter.translatePosMatrix(coord.posMatrix, tile, translate, translateAnchor));

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

prevFontstack = bucket.fontstack;
}

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

function setSymbolDrawState(program, painter, isText, isSDF, rotateWithMap, pitchWithMap, fontstack, size,
iconsNeedLinear, adjustedSize) {
function setSymbolDrawState(program, painter, layer, tileZoom, isText, isSDF, rotateWithMap, pitchWithMap, fontstack, iconsNeedLinear) {

const gl = painter.gl;
const tr = painter.transform;
Expand All @@ -120,6 +117,8 @@ function setSymbolDrawState(program, painter, isText, isSDF, rotateWithMap, pitc
gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(program.u_texture, 0);

gl.uniform1f(program.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']).
Expand All @@ -130,7 +129,10 @@ function setSymbolDrawState(program, painter, isText, isSDF, rotateWithMap, pitc
gl.uniform2f(program.u_texsize, glyphAtlas.width / 4, glyphAtlas.height / 4);
} else {
const mapMoving = painter.options.rotating || painter.options.zooming;
const iconScaled = size !== 1 || browser.devicePixelRatio !== painter.spriteAtlas.pixelRatio || iconsNeedLinear;
const iconSizeScaled = !layer.isLayoutValueFeatureConstant('text-size') ||
!layer.isLayoutValueZoomConstant('text-size') ||
layer.getLayoutValue('text-size', { zoom: tr.zoom }, {}) !== 1;
const iconScaled = iconSizeScaled || browser.devicePixelRatio !== painter.spriteAtlas.pixelRatio || iconsNeedLinear;
const iconTransformed = pitchWithMap || tr.pitch;
painter.spriteAtlas.bind(gl, isSDF || mapMoving || iconScaled || iconTransformed);
gl.uniform2f(program.u_texsize, painter.spriteAtlas.width / 4, painter.spriteAtlas.height / 4);
Expand All @@ -141,35 +143,61 @@ function setSymbolDrawState(program, painter, isText, isSDF, rotateWithMap, pitc
gl.uniform1i(program.u_fadetexture, 1);

// adjust min/max zooms for variable font sizes
const zoomAdjust = Math.log(size / adjustedSize) / Math.LN2 || 0;
gl.uniform1f(program.u_zoom, (tr.zoom - zoomAdjust) * 10); // current zoom level
gl.uniform1f(program.u_zoom, tr.zoom);

gl.uniform1f(program.u_pitch, tr.pitch / 360 * 2 * Math.PI);
gl.uniform1f(program.u_bearing, tr.bearing / 360 * 2 * Math.PI);
gl.uniform1f(program.u_aspect_ratio, tr.width / tr.height);
}

function drawTileSymbols(program, painter, layer, tile, buffers, isText, isSDF,
pitchWithMap, size) {
function drawTileSymbols(program, programConfiguration, painter, layer, tile, buffers, isText, isSDF,
pitchWithMap) {

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

const fontScale = size / (isText ? 24 : 1);
// If {text,icon}-size is a composite function, the shader needs to
// evaluate it at the zoom level that was used at *layout* time, which
// is distinct the current *rendered* zoom level.
const sizeProperty = isText ? 'text-size' : 'icon-size';
const isSizeCompositeFunction =
!layer.isLayoutValueZoomConstant(sizeProperty) &&
!layer.isLayoutValueFeatureConstant(sizeProperty);
gl.uniform1f(program.u_is_size_composite, isSizeCompositeFunction ? 1 : 0);

const layoutZoom = tile.coord.z + 1;
if (isSizeCompositeFunction) {
// Reproduce the ProgramConfiguration logic to provide the shader with
// an interpolation "t" value corresponding to the layout zoom level.
// (see ProgramConfiguration#addZoomAndPropertyAttribute.)
let stopOffset;
for (const uniform of programConfiguration.interpolationUniforms) {
if (uniform.property === sizeProperty) stopOffset = uniform.stopOffset;
}
assert(typeof stopOffset === 'number');
const stopInterp = layer.getLayoutInterpolationT(sizeProperty, { zoom: layoutZoom });
const interpolationT = Math.max(0, Math.min(3, stopInterp - stopOffset));
gl.uniform1f(program.u_adjusted_size_t, interpolationT);
} else {
gl.uniform1f(program.u_adjusted_size,
layer.getLayoutValue(sizeProperty, { zoom: layoutZoom })
);
}

if (pitchWithMap) {
const s = pixelsToTileUnits(tile, fontScale, tr.zoom);
const s = pixelsToTileUnits(tile, 1, tr.zoom);
gl.uniform2f(program.u_extrude_scale, s, s);
} else {
const s = tr.cameraToCenterDistance * fontScale;
gl.uniform2f(program.u_extrude_scale, tr.pixelsToGLUnits[0] * s, tr.pixelsToGLUnits[1] * s);
const s = tr.cameraToCenterDistance;
gl.uniform2f(program.u_extrude_scale,
tr.pixelsToGLUnits[0] * s,
tr.pixelsToGLUnits[1] * s);
}

if (isSDF) {
const haloWidthProperty = `${isText ? 'text' : 'icon'}-halo-width`;
const hasHalo = !layer.isPaintValueFeatureConstant(haloWidthProperty) || layer.paint[haloWidthProperty];
const gammaScale = fontScale * (pitchWithMap ? Math.cos(tr._pitch) : 1) * tr.cameraToCenterDistance;
gl.uniform1f(program.u_font_scale, fontScale);
const gammaScale = (isText ? 1 / 24 : 1) * (pitchWithMap ? Math.cos(tr._pitch) : 1) * tr.cameraToCenterDistance;
gl.uniform1f(program.u_gamma_scale, gammaScale);

if (hasHalo) { // Draw halo underneath the text.
Expand Down
12 changes: 8 additions & 4 deletions src/shaders/symbol_sdf.fragment.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define EDGE_GAMMA 0.105/DEVICE_PIXEL_RATIO

uniform bool u_is_halo;
#pragma mapbox: define mediump float size
#pragma mapbox: define lowp vec4 fill_color
#pragma mapbox: define lowp vec4 halo_color
#pragma mapbox: define lowp float opacity
Expand All @@ -10,27 +11,30 @@ uniform bool u_is_halo;

uniform sampler2D u_texture;
uniform sampler2D u_fadetexture;
uniform lowp float u_font_scale;
uniform highp float u_gamma_scale;
uniform bool u_is_text;

varying vec2 v_tex;
varying vec2 v_fade_tex;
varying float v_gamma_scale;

void main() {
#pragma mapbox: initialize mediump float size
#pragma mapbox: initialize lowp vec4 fill_color
#pragma mapbox: initialize lowp vec4 halo_color
#pragma mapbox: initialize lowp float opacity
#pragma mapbox: initialize lowp float halo_width
#pragma mapbox: initialize lowp float halo_blur

float fontScale = u_is_text ? size / 24.0 : size;

lowp vec4 color = fill_color;
highp float gamma = EDGE_GAMMA / u_gamma_scale;
highp float gamma = EDGE_GAMMA / (size * u_gamma_scale);
lowp float buff = (256.0 - 64.0) / 256.0;
if (u_is_halo) {
color = halo_color;
gamma = (halo_blur * 1.19 / SDF_PX + EDGE_GAMMA) / u_gamma_scale;
buff = (6.0 - halo_width / u_font_scale) / SDF_PX;
gamma = (halo_blur * 1.19 / SDF_PX + EDGE_GAMMA) / (size * u_gamma_scale);
buff = (6.0 - halo_width / fontScale) / SDF_PX;
}

lowp float dist = texture2D(u_texture, v_tex).a;
Expand Down
Loading

0 comments on commit 23e2b51

Please sign in to comment.