From a0ecd68f4602305e1238b529bcd0b39a07978072 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 6 Dec 2018 17:43:52 +0200 Subject: [PATCH 1/4] add `symbol-sort-key` style property This property allows users to specify a sorting order for symbols. Symbols are sorted in ascending order based on the key. Symbols with lower keys will appear below symbols with higher keys when they are rendered with overlap. Symbols with lower keys will be placed before symbols with higher keys. This also fixes #7111 by defining a sort order across tile boundaries. --- src/data/bucket/symbol_bucket.js | 26 +++- src/data/segment.js | 11 +- src/render/draw_symbol.js | 118 +++++++++++++++--- src/style-spec/reference/v8.json | 20 ++- src/style-spec/types.js | 3 +- .../symbol_style_layer_properties.js | 4 +- 6 files changed, 155 insertions(+), 27 deletions(-) diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index d03e8302fb2..420703acf9c 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -56,6 +56,7 @@ export type CollisionArrays = { }; export type SymbolFeature = {| + sortKey: number | void, text: Formatted | void, icon: string | void, index: number, @@ -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; @@ -261,6 +262,7 @@ class SymbolBucket implements Bucket { tilePixelRatio: number; compareText: {[string]: Array}; fadeStartTime: number; + sortFeaturesByKey: boolean; sortFeaturesByY: boolean; sortedAngle: number; featureSortOrder: Array; @@ -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')); @@ -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 = []; @@ -370,6 +376,10 @@ class SymbolBucket implements Bucket { continue; } + const sortKey = this.sortFeaturesByKey ? + symbolSortKey.evaluate(feature, {}) : + undefined; + const symbolFeature: SymbolFeature = { text, icon, @@ -377,7 +387,8 @@ class SymbolBucket implements Bucket { 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; @@ -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}) { @@ -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; diff --git a/src/data/segment.js b/src/data/segment.js index ff20b529ef3..1d984460402 100644 --- a/src/data/segment.js +++ b/src/data/segment.js @@ -8,6 +8,7 @@ import type VertexArrayObject from '../render/vertex_array_object'; import type {StructArray} from '../util/struct_array'; export type Segment = { + sortKey: number, vertexOffset: number, primitiveOffset: number, vertexLength: number, @@ -23,15 +24,16 @@ class SegmentVector { this.segments = segments; } - prepareSegment(numVertices: number, layoutVertexArray: StructArray, indexArray: StructArray): Segment { + prepareSegment(numVertices: number, layoutVertexArray: StructArray, indexArray: StructArray, sortKey: number = 0): 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 + primitiveLength: 0, + sortKey }: any); this.segments.push(segment); } @@ -56,7 +58,8 @@ class SegmentVector { primitiveOffset, vertexLength, primitiveLength, - vaos: {} + vaos: {}, + sortKey: 0 }]); } } diff --git a/src/render/draw_symbol.js b/src/render/draw_symbol.js index 2d96707db69..16dc2f3ae79 100644 --- a/src/render/draw_symbol.js +++ b/src/render/draw_symbol.js @@ -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'; @@ -20,11 +21,25 @@ 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 = { + buffers: SymbolBuffers, + program: any, + depthMode: DepthMode, + uniformValues: any, + atlasTexture: Texture, + atlasInterpolation: any, + isSDF: boolean, + hasHalo: boolean +}; + function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolStyleLayer, coords: Array) { if (painter.renderPass !== 'translucent') return; @@ -32,26 +47,36 @@ function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolSt const stencilMode = StencilMode.disabled; const colorMode = painter.colorModeForRenderPass(); + const sortFeaturesByKey = layer.layout.get('symbol-sort-key').constantOr(1) !== undefined; + if (layer.paint.get('icon-opacity').constantOr(1) !== 0) { + const tileRenderState = sortFeaturesByKey ? [] : undefined; drawLayerSymbols(painter, sourceCache, layer, coords, false, layer.paint.get('icon-translate'), layer.paint.get('icon-translate-anchor'), layer.layout.get('icon-rotation-alignment'), layer.layout.get('icon-pitch-alignment'), layer.layout.get('icon-keep-upright'), - stencilMode, colorMode + stencilMode, colorMode, tileRenderState ); + if (sortFeaturesByKey) { + drawSymbolsSorted(painter, ((tileRenderState: any): Array), layer, colorMode, stencilMode); + } } if (layer.paint.get('text-opacity').constantOr(1) !== 0) { + const tileRenderState = sortFeaturesByKey ? [] : undefined; drawLayerSymbols(painter, sourceCache, layer, coords, true, layer.paint.get('text-translate'), layer.paint.get('text-translate-anchor'), layer.layout.get('text-rotation-alignment'), layer.layout.get('text-pitch-alignment'), layer.layout.get('text-keep-upright'), - stencilMode, colorMode + stencilMode, colorMode, tileRenderState ); + if (sortFeaturesByKey) { + drawSymbolsSorted(painter, ((tileRenderState: any): Array), layer, colorMode, stencilMode); + } } if (sourceCache.map.showCollisionBoxes) { @@ -60,12 +85,13 @@ function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolSt } function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate, translateAnchor, - rotationAlignment, pitchAlignment, keepUpright, stencilMode, colorMode) { + rotationAlignment, pitchAlignment, keepUpright, stencilMode, colorMode, tileRenderState) { const context = painter.context; const gl = context.gl; const tr = painter.transform; + const sortByFeature = Boolean(tileRenderState); const rotateWithMap = rotationAlignment === 'map'; const pitchWithMap = pitchAlignment === 'map'; const alongLine = rotateWithMap && layer.layout.get('symbol-placement') !== 'point'; @@ -99,19 +125,28 @@ 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; } + if (!sortByFeature) { + atlasTexture.bind(atlasInterpolation, gl.CLAMP_TO_EDGE); + } + const s = pixelsToTileUnits(tile, 1, painter.transform.zoom); const labelPlaneMatrix = symbolProjection.getLabelPlaneMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, s); const glCoordMatrix = symbolProjection.getGlCoordMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, s); @@ -124,36 +159,87 @@ 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); + if (!sortByFeature) { + if (hasHalo) { + drawSymbolElements(buffers, buffers.segments, layer, painter, program, depthMode, stencilMode, colorMode, uniformValues); + } + uniformValues['u_is_halo'] = 0; } - 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); + if (sortByFeature) { + ((tileRenderState: any): Array).push({ + buffers, + program, + depthMode, + uniformValues, + atlasTexture, + atlasInterpolation, + isSDF, + hasHalo + }); + } else { + atlasTexture.bind(atlasInterpolation, gl.CLAMP_TO_EDGE); + drawSymbolElements(buffers, buffers.segments, layer, painter, program, depthMode, stencilMode, colorMode, 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); } + +function drawSymbolsSorted(painter: Painter, renderData: Array, layer, colorMode, stencilMode) { + const symbols = []; + + for (const data of renderData) { + + const segments = data.buffers.segments.get(); + for (const segment of segments) { + symbols.push({ + data, + segment + }); + } + } + + symbols.sort((a, b) => a.segment.sortKey - b.segment.sortKey); + + for (const symbol of symbols) { + const data = symbol.data; + const segments = new SegmentVector([symbol.segment]); + + const gl = painter.context.gl; + data.atlasTexture.bind(data.atlasInterpolation, gl.CLAMP_TO_EDGE); + + if (data.isSDF) { + const uniformValues = ((data.uniformValues: any): UniformValues); + if (data.hasHalo) { + uniformValues['u_is_halo'] = 1; + drawSymbolElements(data.buffers, segments, layer, painter, data.program, data.depthMode, stencilMode, colorMode, uniformValues); + } + data.uniformValues['u_is_halo'] = 0; + } + drawSymbolElements(data.buffers, segments, layer, painter, data.program, data.depthMode, stencilMode, colorMode, data.uniformValues); + } +} diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index cc82a18e8b3..0eadbdfb20a 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -958,9 +958,27 @@ }, "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." }, @@ -968,7 +986,7 @@ "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": { diff --git a/src/style-spec/types.js b/src/style-spec/types.js index 5690824235e..100fddeae67 100644 --- a/src/style-spec/types.js +++ b/src/style-spec/types.js @@ -212,7 +212,8 @@ export type SymbolLayerSpecification = {| "symbol-placement"?: PropertyValueSpecification<"point" | "line" | "line-center">, "symbol-spacing"?: PropertyValueSpecification, "symbol-avoid-edges"?: PropertyValueSpecification, - "symbol-z-order"?: PropertyValueSpecification<"viewport-y" | "source">, + "symbol-sort-key"?: DataDrivenPropertyValueSpecification, + "symbol-z-order"?: PropertyValueSpecification<"auto" | "viewport-y" | "source">, "icon-allow-overlap"?: PropertyValueSpecification, "icon-ignore-placement"?: PropertyValueSpecification, "icon-optional"?: PropertyValueSpecification, diff --git a/src/style/style_layer/symbol_style_layer_properties.js b/src/style/style_layer/symbol_style_layer_properties.js index 7c77ff40fe7..90ced3407a3 100644 --- a/src/style/style_layer/symbol_style_layer_properties.js +++ b/src/style/style_layer/symbol_style_layer_properties.js @@ -21,7 +21,8 @@ export type LayoutProps = {| "symbol-placement": DataConstantProperty<"point" | "line" | "line-center">, "symbol-spacing": DataConstantProperty, "symbol-avoid-edges": DataConstantProperty, - "symbol-z-order": DataConstantProperty<"viewport-y" | "source">, + "symbol-sort-key": DataDrivenProperty, + "symbol-z-order": DataConstantProperty<"auto" | "viewport-y" | "source">, "icon-allow-overlap": DataConstantProperty, "icon-ignore-placement": DataConstantProperty, "icon-optional": DataConstantProperty, @@ -61,6 +62,7 @@ const layout: Properties = 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"]), From c4431e4c061c9c1b3799c2fb364e7ec027c4378d Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 6 Dec 2018 18:30:31 +0200 Subject: [PATCH 2/4] fixup --- src/data/segment.js | 8 ++++---- src/render/draw_symbol.js | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/data/segment.js b/src/data/segment.js index 1d984460402..9d3335923a7 100644 --- a/src/data/segment.js +++ b/src/data/segment.js @@ -8,7 +8,7 @@ import type VertexArrayObject from '../render/vertex_array_object'; import type {StructArray} from '../util/struct_array'; export type Segment = { - sortKey: number, + sortKey: number | void, vertexOffset: number, primitiveOffset: number, vertexLength: number, @@ -24,7 +24,7 @@ class SegmentVector { this.segments = segments; } - prepareSegment(numVertices: number, layoutVertexArray: StructArray, indexArray: StructArray, sortKey: number = 0): 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 || segment.sortKey !== sortKey) { @@ -32,9 +32,9 @@ class SegmentVector { vertexOffset: layoutVertexArray.length, primitiveOffset: indexArray.length, vertexLength: 0, - primitiveLength: 0, - sortKey + primitiveLength: 0 }: any); + if (sortKey !== undefined) segment.sortKey = sortKey; this.segments.push(segment); } return segment; diff --git a/src/render/draw_symbol.js b/src/render/draw_symbol.js index 16dc2f3ae79..78884f34e47 100644 --- a/src/render/draw_symbol.js +++ b/src/render/draw_symbol.js @@ -223,7 +223,9 @@ function drawSymbolsSorted(painter: Painter, renderData: Array a.segment.sortKey - b.segment.sortKey); + symbols.sort((a, b) => { + return ((a.segment.sortKey: any): number) - ((b.segment.sortKey: any): number); + }); for (const symbol of symbols) { const data = symbol.data; From 2ce21181d524cbbd5908098871489d80e853296d Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 10 Dec 2018 18:50:49 -0500 Subject: [PATCH 3/4] have one render path instead of two --- src/render/draw_symbol.js | 145 ++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 83 deletions(-) diff --git a/src/render/draw_symbol.js b/src/render/draw_symbol.js index 78884f34e47..26b9fca165b 100644 --- a/src/render/draw_symbol.js +++ b/src/render/draw_symbol.js @@ -30,14 +30,17 @@ import type {SymbolSDFUniformsType} from '../render/program/symbol_program'; export default drawSymbols; type SymbolTileRenderState = { - buffers: SymbolBuffers, - program: any, - depthMode: DepthMode, - uniformValues: any, - atlasTexture: Texture, - atlasInterpolation: any, - isSDF: boolean, - hasHalo: boolean + 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) { @@ -47,36 +50,26 @@ function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolSt const stencilMode = StencilMode.disabled; const colorMode = painter.colorModeForRenderPass(); - const sortFeaturesByKey = layer.layout.get('symbol-sort-key').constantOr(1) !== undefined; - if (layer.paint.get('icon-opacity').constantOr(1) !== 0) { - const tileRenderState = sortFeaturesByKey ? [] : undefined; drawLayerSymbols(painter, sourceCache, layer, coords, false, layer.paint.get('icon-translate'), layer.paint.get('icon-translate-anchor'), layer.layout.get('icon-rotation-alignment'), layer.layout.get('icon-pitch-alignment'), layer.layout.get('icon-keep-upright'), - stencilMode, colorMode, tileRenderState + stencilMode, colorMode ); - if (sortFeaturesByKey) { - drawSymbolsSorted(painter, ((tileRenderState: any): Array), layer, colorMode, stencilMode); - } } if (layer.paint.get('text-opacity').constantOr(1) !== 0) { - const tileRenderState = sortFeaturesByKey ? [] : undefined; drawLayerSymbols(painter, sourceCache, layer, coords, true, layer.paint.get('text-translate'), layer.paint.get('text-translate-anchor'), layer.layout.get('text-rotation-alignment'), layer.layout.get('text-pitch-alignment'), layer.layout.get('text-keep-upright'), - stencilMode, colorMode, tileRenderState + stencilMode, colorMode ); - if (sortFeaturesByKey) { - drawSymbolsSorted(painter, ((tileRenderState: any): Array), layer, colorMode, stencilMode); - } } if (sourceCache.map.showCollisionBoxes) { @@ -85,13 +78,12 @@ function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolSt } function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate, translateAnchor, - rotationAlignment, pitchAlignment, keepUpright, stencilMode, colorMode, tileRenderState) { + rotationAlignment, pitchAlignment, keepUpright, stencilMode, colorMode) { const context = painter.context; const gl = context.gl; const tr = painter.transform; - const sortByFeature = Boolean(tileRenderState); const rotateWithMap = rotationAlignment === 'map'; const pitchWithMap = pitchAlignment === 'map'; const alongLine = rotateWithMap && layer.layout.get('symbol-placement') !== 'point'; @@ -100,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 = []; + for (const coord of coords) { const tile = sourceCache.getTile(coord); const bucket: SymbolBucket = (tile.getBucket(layer): any); @@ -143,10 +139,6 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate texSize = tile.imageAtlasTexture.size; } - if (!sortByFeature) { - atlasTexture.bind(atlasInterpolation, gl.CLAMP_TO_EDGE); - } - const s = pixelsToTileUnits(tile, 1, painter.transform.zoom); const labelPlaneMatrix = symbolProjection.getLabelPlaneMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, s); const glCoordMatrix = symbolProjection.getGlCoordMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, s); @@ -163,40 +155,63 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate let uniformValues; if (isSDF) { - uniformValues = symbolSDFUniformValues(sizeData.functionType, size, rotateInShader, pitchWithMap, painter, matrix, uLabelPlaneMatrix, uglCoordMatrix, isText, texSize, true); - if (!sortByFeature) { - if (hasHalo) { - drawSymbolElements(buffers, buffers.segments, 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); } - if (sortByFeature) { - ((tileRenderState: any): Array).push({ - buffers, - program, - depthMode, - uniformValues, - atlasTexture, - atlasInterpolation, - isSDF, - hasHalo - }); + 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 { - atlasTexture.bind(atlasInterpolation, gl.CLAMP_TO_EDGE); - drawSymbolElements(buffers, buffers.segments, layer, painter, program, depthMode, stencilMode, colorMode, uniformValues); + 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); + 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, segments, layer, painter, program, depthMode, stencilMode, colorMode, uniformValues) { @@ -209,39 +224,3 @@ function drawSymbolElements(buffers, segments, layer, painter, program, depthMod buffers.dynamicLayoutVertexBuffer, buffers.opacityVertexBuffer); } -function drawSymbolsSorted(painter: Painter, renderData: Array, layer, colorMode, stencilMode) { - const symbols = []; - - for (const data of renderData) { - - const segments = data.buffers.segments.get(); - for (const segment of segments) { - symbols.push({ - data, - segment - }); - } - } - - symbols.sort((a, b) => { - return ((a.segment.sortKey: any): number) - ((b.segment.sortKey: any): number); - }); - - for (const symbol of symbols) { - const data = symbol.data; - const segments = new SegmentVector([symbol.segment]); - - const gl = painter.context.gl; - data.atlasTexture.bind(data.atlasInterpolation, gl.CLAMP_TO_EDGE); - - if (data.isSDF) { - const uniformValues = ((data.uniformValues: any): UniformValues); - if (data.hasHalo) { - uniformValues['u_is_halo'] = 1; - drawSymbolElements(data.buffers, segments, layer, painter, data.program, data.depthMode, stencilMode, colorMode, uniformValues); - } - data.uniformValues['u_is_halo'] = 0; - } - drawSymbolElements(data.buffers, segments, layer, painter, data.program, data.depthMode, stencilMode, colorMode, data.uniformValues); - } -} From 71fd325d5b92e084b3592ab84d59f8d1164f1998 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 11 Dec 2018 14:45:03 -0500 Subject: [PATCH 4/4] add render tests --- .../icon-expression/expected.png | Bin 0 -> 1606 bytes .../icon-expression/style.json | 76 +++++++++++++++ .../text-expression/expected.png | Bin 0 -> 567 bytes .../text-expression/style.json | 87 ++++++++++++++++++ .../text-placement/expected.png | Bin 0 -> 356 bytes .../symbol-sort-key/text-placement/style.json | 71 ++++++++++++++ 6 files changed, 234 insertions(+) create mode 100644 test/integration/render-tests/symbol-sort-key/icon-expression/expected.png create mode 100644 test/integration/render-tests/symbol-sort-key/icon-expression/style.json create mode 100644 test/integration/render-tests/symbol-sort-key/text-expression/expected.png create mode 100644 test/integration/render-tests/symbol-sort-key/text-expression/style.json create mode 100644 test/integration/render-tests/symbol-sort-key/text-placement/expected.png create mode 100644 test/integration/render-tests/symbol-sort-key/text-placement/style.json diff --git a/test/integration/render-tests/symbol-sort-key/icon-expression/expected.png b/test/integration/render-tests/symbol-sort-key/icon-expression/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..6b17890462d21ffd56598e7fe268cdc4ee48e884 GIT binary patch literal 1606 zcmY*a2UHST7&fi+nVFhenP{4u#0FY8OHtHJg$HG(qITSS?$> z&D6|oxpxvc(h|eX z&smE+0wy`YYX-f94BN9Pg&pGI*|*ExEj443$8Q06R>#?!^|NphQE_u#(p~w=7O%n^ zDPiu~;2n`2U=CBx?x>SXm3#7+=O+?^i+3C)8L48mh(Yt7d=jN$P>l~{tq|<5+ZIlm z2C&1U1o=8BeYiEf^}H2E!PVkXPqOXOE@}o$tXxdJ8G&Q>sAsCXtBR*rQSXe2lwQf< zlc6&tJ1K`0V9MGExC~!kJ zS%k_w(jYiv-d%G=KX#7CX2_m}wMr#3wbrBoJ_}|2k%f%2nAZef>I>Ek>k6e^$ARc9P;F7f(MR&4zLNxadH zw%9fptjSbCUNpfp9rXkm-OzKQaqNfAn#K3EQBK{UA0_||SXp}(4nhc*=c62$8E&HHyl~AUIz=i@odzR44fwvw^z_}30?S6wbCjZSWEKS&P0BY~spu_DX7bzutm?b+8rlsv;LMIwDy>BC+y zx@U5bL-%9pxAnb4Xh3yERd196W&zwr%Rz^rnCB2-TkWH@rKk!eRF|-0iN3er{ML1} zHJGSwzZ-)G940jU-n8=4ZWv+~rGk=Ml;pblh#|16t4-lAdp>HEP4V`YHTjwYPNcYL z<)#CQkkrQxjz?i)6ElnM`G^VZjd>{JK8>%`O5 zhwMt=1SOO^J8m#UH95#`-l-7HM8(2VLVKnhFb_;E>Aq7l6QiNpej@^yaF-|?P;x47 zy$5coa?XHDXr}G6ETE2n(u~whDbC_;o zmlTwted=l>`SR@mtvM9-N%cSzUbgukQA^tzKOq7sqcx4;W*fL|;rHR;Eh~}NW)Q2Z zxrj_623`)}mRy!igZyI!uE4^S1LH_(91&BcAfF&}oe7}$^)xQ)GP<&8ZSdWZ}_mW8`=`U#?BW+TE;9r;%8BOOEe^(X zsX(s91ounLil;0m5$7F%%&JB5*p=nGEZF1BMgUg9|33ZoFwFkav=uAv

t(SbeKk8Mhq%;~bw00x9p2yf>Z(_W=m+XJh8>v21>D0O^r&7TqCMD!PJbN2J-iV{F0xMZ zjhnMh7`fn9_?P#%iS^hgj9hS;W+u>!59q}vVdNHpujt1^9K{jg6e^Wvnk)VG6=(1O zJvf70IJt&OW+eYk5V#?Ux+~%w-mb z-(9d&yXHXho}+x{3L2MJEZntdsrQ`+qGA;t*Cjq4)#;j6mvE%UkmITa1!A z`J&aNZyXV-2lBQ!wI^L#c)TO9)Ky#7Y{Q|oC%Lx2xVU;(-E5u0-s&46u@^5f z$?Ey;do0!dBGf$Nr6k`6PTR7Ng1aj+#hRyWD{LvQIo2V&=inh7wzakgIqM$s@o&gA nYyN+*!F`s75&|T}{$c%5uJ_{DDMedgurYYL`njxgN@xNAr81SS literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/symbol-sort-key/text-placement/style.json b/test/integration/render-tests/symbol-sort-key/text-placement/style.json new file mode 100644 index 00000000000..60a2136dfe1 --- /dev/null +++ b/test/integration/render-tests/symbol-sort-key/text-placement/style.json @@ -0,0 +1,71 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 64, + "width": 64 + } + }, + "center": [0, 30], + "zoom": 1, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "color": "red", + "sort-key": 1, + "image": "bank-12" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 1, + 34 + ] + } + }, + { + "type": "Feature", + "properties": { + "color": "green", + "sort-key": 0, + "image": "bank-12" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 1, + 30 + ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "icon", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-sort-key": ["get", "sort-key"], + "text-field": ["get", "sort-key"], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + }, + "paint": { + "text-halo-width": 5, + "text-halo-color": ["get", "color"] + } + } + ] +}