Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add symbol-sort-key style property #7678

Merged
merged 4 commits into from
Dec 12, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions src/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export type CollisionArrays = {
};

export type SymbolFeature = {|
sortKey: number | void,
text: Formatted | void,
icon: string | void,
index: number,
Expand Down Expand Up @@ -101,7 +102,7 @@ function addDynamicAttributes(dynamicLayoutVertexArray: StructArray, p: Point, a
dynamicLayoutVertexArray.emplaceBack(p.x, p.y, angle);
}

class SymbolBuffers {
export class SymbolBuffers {
layoutVertexArray: SymbolLayoutArray;
layoutVertexBuffer: VertexBuffer;

Expand Down Expand Up @@ -261,6 +262,7 @@ class SymbolBucket implements Bucket {
tilePixelRatio: number;
compareText: {[string]: Array<Point>};
fadeStartTime: number;
sortFeaturesByKey: boolean;
sortFeaturesByY: boolean;
sortedAngle: number;
featureSortOrder: Array<number>;
Expand Down Expand Up @@ -291,7 +293,10 @@ class SymbolBucket implements Bucket {
this.iconSizeData = getSizeData(this.zoom, unevaluatedLayoutValues['icon-size']);

const layout = this.layers[0].layout;
const zOrderByViewportY = layout.get('symbol-z-order') === 'viewport-y';
const sortKey = layout.get('symbol-sort-key');
const zOrder = layout.get('symbol-z-order');
this.sortFeaturesByKey = zOrder !== 'viewport-y' && sortKey.constantOr(1) !== undefined;
const zOrderByViewportY = zOrder === 'viewport-y' || (zOrder === 'auto' && !this.sortFeaturesByKey);
this.sortFeaturesByY = zOrderByViewportY && (layout.get('text-allow-overlap') || layout.get('icon-allow-overlap') ||
layout.get('text-ignore-placement') || layout.get('icon-ignore-placement'));

Expand Down Expand Up @@ -333,6 +338,7 @@ class SymbolBucket implements Bucket {
(textField.value.kind !== 'constant' || textField.value.value.toString().length > 0) &&
(textFont.value.kind !== 'constant' || textFont.value.value.length > 0);
const hasIcon = iconImage.value.kind !== 'constant' || iconImage.value.value && iconImage.value.value.length > 0;
const symbolSortKey = layout.get('symbol-sort-key');

this.features = [];

Expand Down Expand Up @@ -370,14 +376,19 @@ class SymbolBucket implements Bucket {
continue;
}

const sortKey = this.sortFeaturesByKey ?
symbolSortKey.evaluate(feature, {}) :
undefined;

const symbolFeature: SymbolFeature = {
text,
icon,
index,
sourceLayerIndex,
geometry: loadGeometry(feature),
properties: feature.properties,
type: vectorTileFeatureTypes[feature.type]
type: vectorTileFeatureTypes[feature.type],
sortKey
};
if (typeof feature.id !== 'undefined') {
symbolFeature.id = feature.id;
Expand Down Expand Up @@ -405,6 +416,13 @@ class SymbolBucket implements Bucket {
// It's better to place labels on one long line than on many short segments.
this.features = mergeLines(this.features);
}

if (this.sortFeaturesByKey) {
this.features.sort((a, b) => {
// a.sortKey is always a number when sortFeaturesByKey is true
return ((a.sortKey: any): number) - ((b.sortKey: any): number);
});
}
}

update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[string]: ImagePosition}) {
Expand Down Expand Up @@ -481,7 +499,7 @@ class SymbolBucket implements Bucket {
const layoutVertexArray = arrays.layoutVertexArray;
const dynamicLayoutVertexArray = arrays.dynamicLayoutVertexArray;

const segment = arrays.segments.prepareSegment(4 * quads.length, arrays.layoutVertexArray, arrays.indexArray);
const segment = arrays.segments.prepareSegment(4 * quads.length, arrays.layoutVertexArray, arrays.indexArray, feature.sortKey);
const glyphOffsetArrayStart = this.glyphOffsetArray.length;
const vertexStartIndex = segment.vertexLength;

Expand Down
9 changes: 6 additions & 3 deletions src/data/segment.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type VertexArrayObject from '../render/vertex_array_object';
import type {StructArray} from '../util/struct_array';

export type Segment = {
sortKey: number | void,
vertexOffset: number,
primitiveOffset: number,
vertexLength: number,
Expand All @@ -23,16 +24,17 @@ class SegmentVector {
this.segments = segments;
}

prepareSegment(numVertices: number, layoutVertexArray: StructArray, indexArray: StructArray): Segment {
prepareSegment(numVertices: number, layoutVertexArray: StructArray, indexArray: StructArray, sortKey?: number): Segment {
let segment: Segment = this.segments[this.segments.length - 1];
if (numVertices > SegmentVector.MAX_VERTEX_ARRAY_LENGTH) warnOnce(`Max vertices per segment is ${SegmentVector.MAX_VERTEX_ARRAY_LENGTH}: bucket requested ${numVertices}`);
if (!segment || segment.vertexLength + numVertices > SegmentVector.MAX_VERTEX_ARRAY_LENGTH) {
if (!segment || segment.vertexLength + numVertices > SegmentVector.MAX_VERTEX_ARRAY_LENGTH || segment.sortKey !== sortKey) {
segment = ({
vertexOffset: layoutVertexArray.length,
primitiveOffset: indexArray.length,
vertexLength: 0,
primitiveLength: 0
}: any);
if (sortKey !== undefined) segment.sortKey = sortKey;
this.segments.push(segment);
}
return segment;
Expand All @@ -56,7 +58,8 @@ class SegmentVector {
primitiveOffset,
vertexLength,
primitiveLength,
vaos: {}
vaos: {},
sortKey: 0
}]);
}
}
Expand Down
99 changes: 83 additions & 16 deletions src/render/draw_symbol.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import drawCollisionDebug from './draw_collision_debug';

import SegmentVector from '../data/segment';
import pixelsToTileUnits from '../source/pixels_to_tile_units';
import * as symbolProjection from '../symbol/projection';
import * as symbolSize from '../symbol/symbol_size';
Expand All @@ -20,11 +21,28 @@ import {
import type Painter from './painter';
import type SourceCache from '../source/source_cache';
import type SymbolStyleLayer from '../style/style_layer/symbol_style_layer';
import type SymbolBucket from '../data/bucket/symbol_bucket';
import type SymbolBucket, {SymbolBuffers} from '../data/bucket/symbol_bucket';
import type Texture from '../render/texture';
import type {OverscaledTileID} from '../source/tile_id';
import type {UniformValues} from './uniform_binding';
import type {SymbolSDFUniformsType} from '../render/program/symbol_program';

export default drawSymbols;

type SymbolTileRenderState = {
segments: SegmentVector,
sortKey: number,
state: {
program: any,
buffers: SymbolBuffers,
uniformValues: any,
atlasTexture: Texture,
atlasInterpolation: any,
isSDF: boolean,
hasHalo: boolean
}
};

function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolStyleLayer, coords: Array<OverscaledTileID>) {
if (painter.renderPass !== 'translucent') return;

Expand Down Expand Up @@ -74,11 +92,15 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
// Unpitched point labels need to have their rotation applied after projection
const rotateInShader = rotateWithMap && !pitchWithMap && !alongLine;

const sortFeaturesByKey = layer.layout.get('symbol-sort-key').constantOr(1) !== undefined;

const depthMode = painter.depthModeForSublayer(0, DepthMode.ReadOnly);

let program;
let size;

const tileRenderState: Array<SymbolTileRenderState> = [];

for (const coord of coords) {
const tile = sourceCache.getTile(coord);
const bucket: SymbolBucket = (tile.getBucket(layer): any);
Expand All @@ -99,16 +121,21 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
context.activeTexture.set(gl.TEXTURE0);

let texSize: [number, number];
let atlasTexture;
let atlasInterpolation;
if (isText) {
tile.glyphAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
atlasTexture = tile.glyphAtlasTexture;
atlasInterpolation = gl.LINEAR;
texSize = tile.glyphAtlasTexture.size;

} else {
const iconScaled = layer.layout.get('icon-size').constantOr(0) !== 1 || bucket.iconsNeedLinear;
const iconTransformed = pitchWithMap || tr.pitch !== 0;

tile.imageAtlasTexture.bind(isSDF || painter.options.rotating || painter.options.zooming || iconScaled || iconTransformed ?
gl.LINEAR : gl.NEAREST, gl.CLAMP_TO_EDGE);

atlasTexture = tile.imageAtlasTexture;
atlasInterpolation = isSDF || painter.options.rotating || painter.options.zooming || iconScaled || iconTransformed ?
gl.LINEAR :
gl.NEAREST;
texSize = tile.imageAtlasTexture.size;
}

Expand All @@ -124,36 +151,76 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
uLabelPlaneMatrix = alongLine ? identityMat4 : labelPlaneMatrix,
uglCoordMatrix = painter.translatePosMatrix(glCoordMatrix, tile, translate, translateAnchor, true);

const hasHalo = isSDF && layer.paint.get(isText ? 'text-halo-width' : 'icon-halo-width').constantOr(1) !== 0;

let uniformValues;
if (isSDF) {
const hasHalo = layer.paint.get(isText ? 'text-halo-width' : 'icon-halo-width').constantOr(1) !== 0;

uniformValues = symbolSDFUniformValues(sizeData.functionType,
size, rotateInShader, pitchWithMap, painter, matrix,
uLabelPlaneMatrix, uglCoordMatrix, isText, texSize, true);

if (hasHalo) {
drawSymbolElements(buffers, layer, painter, program, depthMode, stencilMode, colorMode, uniformValues);
}

uniformValues['u_is_halo'] = 0;

} else {
uniformValues = symbolIconUniformValues(sizeData.functionType,
size, rotateInShader, pitchWithMap, painter, matrix,
uLabelPlaneMatrix, uglCoordMatrix, isText, texSize);
}

drawSymbolElements(buffers, layer, painter, program, depthMode, stencilMode, colorMode, uniformValues);
const state = {
program,
buffers,
uniformValues,
atlasTexture,
atlasInterpolation,
isSDF,
hasHalo
};

if (sortFeaturesByKey) {
const oldSegments = buffers.segments.get();
for (const segment of oldSegments) {
tileRenderState.push({
segments: new SegmentVector([segment]),
sortKey: ((segment.sortKey: any): number),
state
});
}
} else {
tileRenderState.push({
segments: buffers.segments,
sortKey: 0,
state
});
}
}

if (sortFeaturesByKey) {
tileRenderState.sort((a, b) => a.sortKey - b.sortKey);
}

for (const segmentState of tileRenderState) {
const state = segmentState.state;

state.atlasTexture.bind(state.atlasInterpolation, gl.CLAMP_TO_EDGE);

if (state.isSDF) {
const uniformValues = ((state.uniformValues: any): UniformValues<SymbolSDFUniformsType>);
if (state.hasHalo) {
uniformValues['u_is_halo'] = 1;
drawSymbolElements(state.buffers, segmentState.segments, layer, painter, state.program, depthMode, stencilMode, colorMode, uniformValues);
}
uniformValues['u_is_halo'] = 0;
}
drawSymbolElements(state.buffers, segmentState.segments, layer, painter, state.program, depthMode, stencilMode, colorMode, state.uniformValues);
}
}

function drawSymbolElements(buffers, layer, painter, program, depthMode, stencilMode, colorMode, uniformValues) {
function drawSymbolElements(buffers, segments, layer, painter, program, depthMode, stencilMode, colorMode, uniformValues) {
const context = painter.context;
const gl = context.gl;
program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled,
uniformValues, layer.id, buffers.layoutVertexBuffer,
buffers.indexBuffer, buffers.segments, layer.paint,
buffers.indexBuffer, segments, layer.paint,
painter.transform.zoom, buffers.programConfigurations.get(layer.id),
buffers.dynamicLayoutVertexBuffer, buffers.opacityVertexBuffer);
}

20 changes: 19 additions & 1 deletion src/style-spec/reference/v8.json
Original file line number Diff line number Diff line change
Expand Up @@ -958,17 +958,35 @@
},
"property-type": "data-constant"
},
"symbol-sort-key": {
"type": "number",
"doc": "Sorts features in ascending order based on this value. Features with a higher sort key will appear above features with a lower sort key wehn they overlap. Features with a lower sort key will have priority over other features when doing placement.",
"sdk-support": {
"js": "0.53.0"
},
"expression": {
"interpolated": false,
"parameters": [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"symbol-z-order": {
"type": "enum",
"values": {
"auto": {
"doc": "If `symbol-sort-key` is set, sort based on that. Otherwise sort symbols by their position relative to the viewport."
},
"viewport-y": {
"doc": "Symbols will be sorted by their y-position relative to the viewport."
},
"source": {
"doc": "Symbols will be rendered in the same order as the source data with no sorting applied."
}
},
"default": "viewport-y",
"default": "auto",
"doc": "Controls the order in which overlapping symbols in the same layer are rendered",
"sdk-support": {
"basic functionality": {
Expand Down
3 changes: 2 additions & 1 deletion src/style-spec/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ export type SymbolLayerSpecification = {|
"symbol-placement"?: PropertyValueSpecification<"point" | "line" | "line-center">,
"symbol-spacing"?: PropertyValueSpecification<number>,
"symbol-avoid-edges"?: PropertyValueSpecification<boolean>,
"symbol-z-order"?: PropertyValueSpecification<"viewport-y" | "source">,
"symbol-sort-key"?: DataDrivenPropertyValueSpecification<number>,
"symbol-z-order"?: PropertyValueSpecification<"auto" | "viewport-y" | "source">,
"icon-allow-overlap"?: PropertyValueSpecification<boolean>,
"icon-ignore-placement"?: PropertyValueSpecification<boolean>,
"icon-optional"?: PropertyValueSpecification<boolean>,
Expand Down
4 changes: 3 additions & 1 deletion src/style/style_layer/symbol_style_layer_properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export type LayoutProps = {|
"symbol-placement": DataConstantProperty<"point" | "line" | "line-center">,
"symbol-spacing": DataConstantProperty<number>,
"symbol-avoid-edges": DataConstantProperty<boolean>,
"symbol-z-order": DataConstantProperty<"viewport-y" | "source">,
"symbol-sort-key": DataDrivenProperty<number>,
"symbol-z-order": DataConstantProperty<"auto" | "viewport-y" | "source">,
"icon-allow-overlap": DataConstantProperty<boolean>,
"icon-ignore-placement": DataConstantProperty<boolean>,
"icon-optional": DataConstantProperty<boolean>,
Expand Down Expand Up @@ -61,6 +62,7 @@ const layout: Properties<LayoutProps> = new Properties({
"symbol-placement": new DataConstantProperty(styleSpec["layout_symbol"]["symbol-placement"]),
"symbol-spacing": new DataConstantProperty(styleSpec["layout_symbol"]["symbol-spacing"]),
"symbol-avoid-edges": new DataConstantProperty(styleSpec["layout_symbol"]["symbol-avoid-edges"]),
"symbol-sort-key": new DataDrivenProperty(styleSpec["layout_symbol"]["symbol-sort-key"]),
"symbol-z-order": new DataConstantProperty(styleSpec["layout_symbol"]["symbol-z-order"]),
"icon-allow-overlap": new DataConstantProperty(styleSpec["layout_symbol"]["icon-allow-overlap"]),
"icon-ignore-placement": new DataConstantProperty(styleSpec["layout_symbol"]["icon-ignore-placement"]),
Expand Down