diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 5ee0f81f072..db970059194 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -1,7 +1,7 @@ // @flow const {LineLayoutArray} = require('../array_types'); -const {lineLayoutAttributes, dynamicLineAttributes} = require('./line_attributes'); +const {lineLayoutAttributes} = require('./line_attributes'); const layoutAttributes = lineLayoutAttributes.members; const {SegmentVector} = require('../segment'); const {ProgramConfigurationSet} = require('../program_configuration'); @@ -10,6 +10,8 @@ const loadGeometry = require('../load_geometry'); const EXTENT = require('../extent'); const vectorTileFeatureTypes = require('@mapbox/vector-tile').VectorTileFeature.types; const {register} = require('../../util/web_worker_transfer'); +const packUint8ToFloat = require('../../shaders/encode_attribute').packUint8ToFloat; + import type { Bucket, @@ -23,6 +25,18 @@ import type {Segment} from '../segment'; import type Context from '../../gl/context'; import type IndexBuffer from '../../gl/index_buffer'; import type VertexBuffer from '../../gl/vertex_buffer'; +import type {CrossFaded} from '../../style/cross_faded'; + + +export type LineFeature = {| + image: ?CrossFaded, + index: number, + sourceLayerIndex: number, + geometry: Array>, + properties: Object, + type: number, + id?: any +|}; // NOTE ON EXTRUDE SCALE: // scale the extrusion vector so that the normal length is this value. @@ -92,6 +106,8 @@ class LineBucket implements Bucket { overscaling: number; layers: Array; layerIds: Array; + features: Array; + dataDrivenPattern: boolean; layoutVertexArray: LineLayoutArray; layoutVertexBuffer: VertexBuffer; @@ -109,16 +125,57 @@ class LineBucket implements Bucket { this.layers = options.layers; this.layerIds = this.layers.map(layer => layer.id); this.index = options.index; + this.features = []; this.layoutVertexArray = new LineLayoutArray(); this.indexArray = new TriangleIndexArray(); this.programConfigurations = new ProgramConfigurationSet(layoutAttributes, options.layers, options.zoom); this.segments = new SegmentVector(); + + this.dataDrivenPattern = false; + + for (const key in this.programConfigurations) { + const programConfiguration = this.programConfigurations[key]; + for (const layer in programConfiguration) { + const binders = programConfiguration[layer].binders; + if (binders && binders['line-pattern'] && binders['line-pattern'].paintVertexArray) { + this.dataDrivenPattern = true; + break; + } + } + } } populate(features: Array, options: PopulateParameters) { + const icons = options.iconDependencies; + this.features = []; for (const {feature, index, sourceLayerIndex} of features) { - if (this.layers[0]._featureFilter({zoom: this.zoom}, feature)) { + if (!this.layers[0]._featureFilter({zoom: this.zoom}, feature)) continue; + if (this.dataDrivenPattern) { + const layer = this.layers[0]; + const linePattern = layer.paint.get('line-pattern'); + const image = linePattern.evaluate(feature); + if (image) { + icons[image.from] = true; + icons[image.to] = true; + } + const geometry = loadGeometry(feature) + const lineFeature: LineFeature = { + image: image, + sourceLayerIndex: sourceLayerIndex, + index: index, + geometry: geometry, + properties: feature.properties, + type: feature.type + }; + + if (typeof feature.id !== 'undefined') { + lineFeature.id = feature.id; + } + + this.features.push(lineFeature); + options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); + } else { const geometry = loadGeometry(feature); this.addFeature(feature, geometry); options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); @@ -126,6 +183,17 @@ class LineBucket implements Bucket { } } + // used if line-pattern is data-driven + addPatternFeatures(options, imageMap, imagePositions) { + this.imageMap = imageMap; + this.imagePositions = imagePositions; + for (const feature of this.features) { + const {geometry, index, sourceLayerIndex} = feature; + this.addFeature(feature, geometry); + // options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); + } + } + isEmpty() { return this.layoutVertexArray.length === 0; } @@ -420,8 +488,41 @@ class LineBucket implements Bucket { } this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature); + if (this.dataDrivenPattern) this.populatePatternPaintArray(this.layoutVertexArray.length, feature); } + populatePatternPaintArray(length, feature) { + for (const key in this.programConfigurations) { + const programConfig = this.programConfigurations[key]; + for (const layer in programConfig) { + if (programConfig[layer].binders && programConfig[layer].binders['line-pattern'] && programConfig[layer].binders['line-pattern'].paintVertexArray) { + const paintArray = programConfig[layer].binders['line-pattern'].paintVertexArray; + const start = paintArray.length; + + paintArray.reserve(length); + const image = feature.image; + const imagePosA = this.imagePositions[image.from]; + const imagePosB = this.imagePositions[image.to]; + + if (!imagePosA || !imagePosB) return; + + const aTL = packUint8ToFloat(imagePosA.tl[0], imagePosA.tl[1]); + const aBR = packUint8ToFloat(imagePosA.br[0], imagePosA.br[1]); + const bTL = packUint8ToFloat(imagePosB.tl[0], imagePosB.tl[1]); + const bBR = packUint8ToFloat(imagePosB.br[0], imagePosB.br[1]); + + for (let i = start; i < length; i++) { + paintArray.emplaceBack( + // u_pattern_tl_a, u_pattern_br_a + aTL, aBR, + // u_pattern_tl_b, u_pattern_br_b + bTL, bBR + ); + } + } + } + } + } /** * Add two vertices to the buffers. * @@ -509,6 +610,6 @@ class LineBucket implements Bucket { } } -register('LineBucket', LineBucket, {omit: ['layers']}); +register('LineBucket', LineBucket, {omit: ['layers', 'features']}); module.exports = LineBucket; diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 1d974ad25ba..757068ecfe2 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -9,7 +9,9 @@ const {PossiblyEvaluatedPropertyValue} = require('../style/properties'); const { StructArrayLayout1f4, StructArrayLayout2f8, - StructArrayLayout4f16 + StructArrayLayout4f16, + LinePatternSourceExpressionLayoutArray, + LinePatternCompositeExpressionLayoutArray } = require('./array_types'); import type Context from '../gl/context'; @@ -97,7 +99,7 @@ class ConstantBinder implements Binder { const value: any = currentValue.constantOr(this.value); const gl = context.gl; for (let i = 0; i < this.names.length; i++) { - const name = this.names[i] + const name = this.names[i]; if (this.type === 'color') { gl.uniform4f(program.uniforms[`u_${name}`], value.r, value.g, value.b, value.a); } else { @@ -117,18 +119,18 @@ class SourceExpressionBinder implements Binder { paintVertexAttributes: Array; paintVertexBuffer: ?VertexBuffer; - constructor(expression: SourceExpression, names: Array, type: string, layout: () => StructArray) { + constructor(expression: SourceExpression, names: Array, type: string, layout: Class) { this.expression = expression; this.names = names; this.type = type; this.statistics = { max: -Infinity }; const PaintVertexArray = layout; - this.paintVertexAttributes = names.map( name => + this.paintVertexAttributes = names.map((name, i) => ({ name: `a_${name}`, type: 'Float32', - components: type === 'color' ? 2 : 1, - offset: 0 + components: type === 'color' || name.match(/pattern/) ? 2 : 1, + offset: name.match(/pattern/) ? i * 8 : 0 }) ); this.paintVertexArray = new PaintVertexArray(); @@ -145,6 +147,7 @@ class SourceExpressionBinder implements Binder { paintArray.reserve(length); const value = this.expression.evaluate({zoom: 0}, feature); + // figure out how to design this for atypical paint properties with multiple attributes if (this.type === 'color') { const color = packColor(value); @@ -189,7 +192,7 @@ class CompositeExpressionBinder implements Binder { paintVertexAttributes: Array; paintVertexBuffer: ?VertexBuffer; - constructor(expression: CompositeExpression, names: Array, type: string, useIntegerZoom: boolean, zoom: number, layout: () => StructArray) { + constructor(expression: CompositeExpression, names: Array, type: string, useIntegerZoom: boolean, zoom: number, layout: Class) { this.expression = expression; this.names = names; this.type = type; @@ -330,6 +333,9 @@ class ProgramConfiguration { populatePaintArrays(length: number, feature: Feature) { for (const property in this.binders) { + // binders for properties with layout exceptions populate their paint arrays in the + // bucket because they have multiple attributes and property-specific population code + if (getLayoutException(property)) continue; this.binders[property].populatePaintArray(length, feature); } } @@ -420,25 +426,35 @@ class ProgramConfigurationSet { // paint property arrays function paintAttributeName(property, type) { const attributeNameExceptions = { - 'text-opacity': 'opacity', - 'icon-opacity': 'opacity', - 'text-color': 'fill_color', - 'icon-color': 'fill_color', - 'text-halo-color': 'halo_color', - 'icon-halo-color': 'halo_color', - 'text-halo-blur': 'halo_blur', - 'icon-halo-blur': 'halo_blur', - 'text-halo-width': 'halo_width', - 'icon-halo-width': 'halo_width', - 'line-gap-width': 'gapwidth', + 'text-opacity': ['opacity'], + 'icon-opacity': ['opacity'], + 'text-color': ['fill_color'], + 'icon-color': ['fill_color'], + 'text-halo-color': ['halo_color'], + 'icon-halo-color': ['halo_color'], + 'text-halo-blur': ['halo_blur'], + 'icon-halo-blur': ['halo_blur'], + 'text-halo-width': ['halo_width'], + 'icon-halo-width': ['halo_width'], + 'line-gap-width': ['gapwidth'], 'line-pattern': ['pattern_a', 'pattern_b', 'pattern_size'] }; - return [attributeNameExceptions[property] || - property.replace(`${type}-`, '').replace(/-/g, '_')]; + return attributeNameExceptions[property] || + [property.replace(`${type}-`, '').replace(/-/g, '_')]; +} + +function getLayoutException(property) { + const propertyExceptions = { + 'line-pattern':{ + 'source': LinePatternSourceExpressionLayoutArray, + 'composite': LinePatternCompositeExpressionLayoutArray + } + }; + + return propertyExceptions[property]; } function layoutType(property, type, binderType) { - const propertyExceptions = {}; const defaultLayouts = { 'color': { 'source': StructArrayLayout2f8, @@ -450,7 +466,8 @@ function layoutType(property, type, binderType) { } }; - return propertyExceptions[property] && propertyExceptions[property][binderType] || + const layoutException = getLayoutException(property, binderType); + return layoutException && layoutException[binderType] || defaultLayouts[type][binderType]; } diff --git a/src/render/draw_line.js b/src/render/draw_line.js index c3979eeb9fe..711a06567c0 100644 --- a/src/render/draw_line.js +++ b/src/render/draw_line.js @@ -53,12 +53,12 @@ function drawLineTile(program, painter, tile, bucket, layer, coord, programConfi const gl = context.gl; const dasharray = layer.paint.get('line-dasharray'); const linePattern = layer.paint.get('line-pattern'); - const image = linePattern && linePattern.evaluate(); + const image = linePattern && (linePattern.value.kind === "constant" || linePattern.value.kind === "camera" ) ? linePattern.value.value : undefined; let posA, posB, imagePosA, imagePosB; + const tileRatio = 1 / pixelsToTileUnits(tile, 1, painter.transform.tileZoom); if (programChanged || tileRatioChanged) { - const tileRatio = 1 / pixelsToTileUnits(tile, 1, painter.transform.tileZoom); if (dasharray) { posA = painter.lineAtlas.getDash(dasharray.from, layer.layout.get('line-cap') === 'round'); @@ -71,22 +71,27 @@ function drawLineTile(program, painter, tile, bucket, layer, coord, programConfi gl.uniform2f(program.uniforms.u_patternscale_b, tileRatio / widthB, -posB.height / 2); gl.uniform1f(program.uniforms.u_sdfgamma, painter.lineAtlas.width / (Math.min(widthA, widthB) * 256 * browser.devicePixelRatio) / 2); + } else if (bucket.dataDrivenPattern) { + const size = tile.iconAtlasTexture.size; + gl.uniform2fv(program.uniforms.u_texsize, size); } else if (image) { imagePosA = painter.imageManager.getPattern(image.from); imagePosB = painter.imageManager.getPattern(image.to); if (!imagePosA || !imagePosB) return; - gl.uniform4f(program.uniforms.u_pattern_size, imagePosA.displaySize[0] * image.fromScale / tileRatio, imagePosA.displaySize[1], imagePosB.displaySize[0] * image.toScale / tileRatio, imagePosB.displaySize[1]); + gl.uniform4f(program.uniforms.u_scale, imagePosA.pixelRatio, tileRatio, image.fromScale, image.toScale); const {width, height} = painter.imageManager.getPixelSize(); gl.uniform2fv(program.uniforms.u_texsize, [width, height]); + } else if (bucket.dataDrivenPattern) { + const size = tile.iconAtlasTexture.size; + gl.uniform2fv(program.uniforms.u_texsize, size); } gl.uniform2f(program.uniforms.u_gl_units_to_pixels, 1 / painter.transform.pixelsToGLUnits[0], 1 / painter.transform.pixelsToGLUnits[1]); } if (programChanged) { - if (dasharray) { gl.uniform1i(program.uniforms.u_image, 0); context.activeTexture.set(gl.TEXTURE0); @@ -95,7 +100,14 @@ function drawLineTile(program, painter, tile, bucket, layer, coord, programConfi gl.uniform1f(program.uniforms.u_tex_y_a, (posA: any).y); gl.uniform1f(program.uniforms.u_tex_y_b, (posB: any).y); gl.uniform1f(program.uniforms.u_mix, dasharray.t); + } else if (bucket.dataDrivenPattern) { + gl.uniform1i(program.uniforms.u_image, 0); + context.activeTexture.set(gl.TEXTURE0); + tile.iconAtlasTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE); + const evaluated = linePattern.evaluate(); + gl.uniform1f(program.uniforms.u_fade, evaluated.t); + gl.uniform4f(program.uniforms.u_scale, 1, tileRatio, evaluated.fromScale, evaluated.toScale); } else if (image) { gl.uniform1i(program.uniforms.u_image, 0); context.activeTexture.set(gl.TEXTURE0); @@ -103,6 +115,12 @@ function drawLineTile(program, painter, tile, bucket, layer, coord, programConfi gl.uniform4fv(program.uniforms.u_pattern_a, (imagePosA: any).tl.concat((imagePosA: any).br)); gl.uniform4fv(program.uniforms.u_pattern_b, (imagePosB: any).tl.concat((imagePosB: any).br)); gl.uniform1f(program.uniforms.u_fade, image.t); + } else if (bucket.dataDrivenPattern) { + gl.uniform1i(program.uniforms.u_image, 0); + context.activeTexture.set(gl.TEXTURE0); + tile.iconAtlasTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE); + gl.uniform1f(program.uniforms.u_fade, 1); + } } @@ -112,7 +130,6 @@ function drawLineTile(program, painter, tile, bucket, layer, coord, programConfi gl.uniformMatrix4fv(program.uniforms.u_matrix, false, posMatrix); gl.uniform1f(program.uniforms.u_ratio, 1 / pixelsToTileUnits(tile, 1, painter.transform.zoom)); - program.draw( context, gl.TRIANGLES, @@ -120,5 +137,6 @@ function drawLineTile(program, painter, tile, bucket, layer, coord, programConfi bucket.layoutVertexBuffer, bucket.indexBuffer, bucket.segments, - programConfiguration); + programConfiguration, + bucket.dynamicLineAttributeBuffer); } diff --git a/src/source/worker_tile.js b/src/source/worker_tile.js index 4be271261b4..e70df1101fe 100644 --- a/src/source/worker_tile.js +++ b/src/source/worker_tile.js @@ -5,6 +5,7 @@ const {performSymbolLayout} = require('../symbol/symbol_layout'); const {CollisionBoxArray} = require('../data/array_types'); const DictionaryCoder = require('../util/dictionary_coder'); const SymbolBucket = require('../data/bucket/symbol_bucket'); +const LineBucket = require('../data/bucket/line_bucket'); const util = require('../util/util'); const assert = require('assert'); const {makeImageAtlas} = require('../render/image_atlas'); @@ -159,6 +160,9 @@ class WorkerTile { if (bucket instanceof SymbolBucket) { recalculateLayers(bucket.layers, this.zoom); performSymbolLayout(bucket, glyphMap, glyphAtlas.positions, imageMap, imageAtlas.positions, this.showCollisionBoxes); + } else if (bucket instanceof LineBucket && bucket.dataDrivenPattern) { + recalculateLayers(bucket.layers, this.zoom); + bucket.addPatternFeatures(options, imageMap, imageAtlas.positions); } }