From 92d30599de469af5588dd86709193bd2c1f17d44 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 7 Apr 2021 17:13:51 +0300 Subject: [PATCH 01/29] WIP data-driven line-dasharray / line-cap (not working yet) --- src/data/bucket/line_bucket.js | 61 ++++++++++++++++++- src/data/program_configuration.js | 7 ++- src/render/line_atlas.js | 23 +++++-- src/render/program/line_program.js | 33 +++------- src/shaders/line_sdf.fragment.glsl | 10 ++- src/shaders/line_sdf.vertex.glsl | 27 ++++++-- src/source/worker_tile.js | 4 ++ src/style-spec/reference/v8.json | 10 +-- src/style-spec/types.js | 2 +- .../line_style_layer_properties.js | 9 ++- 10 files changed, 134 insertions(+), 52 deletions(-) diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 5389cbe1dd5..3082edc2c61 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -169,6 +169,8 @@ class LineBucket implements Bucket { }); } + const hasDataDrivenDashes = this.hasDataDrivenDashes(); + for (const bucketFeature of bucketFeatures) { const {geometry, index, sourceLayerIndex} = bucketFeature; @@ -177,6 +179,11 @@ class LineBucket implements Bucket { // pattern features are added only once the pattern is loaded into the image atlas // so are stored during populate until later updated with positions by tile worker in addFeatures this.patternFeatures.push(patternBucketFeature); + + } else if (hasDataDrivenDashes) { + this.addFeatureDashes(bucketFeature, options); + this.addFeature(bucketFeature, geometry, index, canonical, options.lineAtlas.positions); + } else { this.addFeature(bucketFeature, geometry, index, canonical, {}); } @@ -186,6 +193,58 @@ class LineBucket implements Bucket { } } + addFeatureDashes(feature: BucketFeature, {lineAtlas}: PopulateParameters) { + + const zoom = this.zoom; + + for (const layer of this.layers) { + const dashPropertyValue = layer.paint.get('line-dasharray').value; + const capPropertyValue = layer.layout.get('line-cap').value; + + if (dashPropertyValue.kind !== 'constant' || capPropertyValue.kind !== 'constant') { + const minDashArray = dashPropertyValue.evaluate({zoom: zoom - 1}, feature); + const midDashArray = dashPropertyValue.evaluate({zoom}, feature); + const maxDashArray = dashPropertyValue.evaluate({zoom: zoom + 1}, feature); + + let minRound, midRound, maxRound; + if (capPropertyValue.kind === 'constant') { + minRound = midRound = maxRound = capPropertyValue.value === 'round'; + } else { + minRound = capPropertyValue.evaluate({zoom: zoom - 1}, feature) === 'round'; + midRound = capPropertyValue.evaluate({zoom}, feature) === 'round'; + maxRound = capPropertyValue.evaluate({zoom: zoom + 1}, feature) === 'round'; + } + + // add to line atlas + lineAtlas.getDash(minDashArray, minRound); + lineAtlas.getDash(midDashArray, midRound); + lineAtlas.getDash(maxDashArray, maxRound); + + const min = lineAtlas.getKey(minDashArray, minRound); + const mid = lineAtlas.getKey(midDashArray, midRound); + const max = lineAtlas.getKey(maxDashArray, maxRound); + + // save positions for paint array + feature.patterns[layer.id] = {min, mid, max}; + } + } + + } + + hasDataDrivenDashes() { + for (const layer of this.layers) { + const dashProperty = layer.paint.get('line-dasharray'); + const capProperty = layer.layout.get('line-cap'); + if (!dashProperty || typeof (dashProperty.isConstant) !== 'function') continue; // temporary + + if (!dashProperty.isConstant() || !capProperty.isConstant()) { + return true; + } + } + + return false; + } + update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[_: string]: ImagePosition}) { if (!this.stateDependentLayers.length) return; this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); @@ -236,7 +295,7 @@ class LineBucket implements Bucket { addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { const layout = this.layers[0].layout; const join = layout.get('line-join').evaluate(feature, {}); - const cap = layout.get('line-cap'); + const cap = layout.get('line-cap').evaluate(feature, {}); const miterLimit = layout.get('line-miter-limit'); const roundLimit = layout.get('line-round-limit'); this.lineClips = this.lineFeatureClips(feature); diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 30638af16d1..0601d9faf9b 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -664,6 +664,7 @@ function paintAttributeNames(property, type) { 'line-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], 'fill-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], 'fill-extrusion-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], + 'line-dasharray': ['pattern_to', 'pattern_from'] }; return attributeNameExceptions[property] || [property.replace(`${type}-`, '').replace(/-/g, '_')]; @@ -671,7 +672,7 @@ function paintAttributeNames(property, type) { function getLayoutException(property) { const propertyExceptions = { - 'line-pattern':{ + 'line-pattern': { 'source': PatternLayoutArray, 'composite': PatternLayoutArray }, @@ -682,6 +683,10 @@ function getLayoutException(property) { 'fill-extrusion-pattern':{ 'source': PatternLayoutArray, 'composite': PatternLayoutArray + }, + 'line-dasharray': { // temporary layout + 'source': PatternLayoutArray, + 'composite': PatternLayoutArray } }; diff --git a/src/render/line_atlas.js b/src/render/line_atlas.js index c869141d587..9620225ea02 100644 --- a/src/render/line_atlas.js +++ b/src/render/line_atlas.js @@ -19,7 +19,7 @@ class LineAtlas { nextRow: number; bytes: number; data: Uint8Array; - dashEntry: {[_: string]: any}; + dashes: {[_: string]: any}; dirty: boolean; texture: WebGLTexture; @@ -30,7 +30,8 @@ class LineAtlas { this.data = new Uint8Array(this.width * this.height); - this.dashEntry = {}; + this.dashes = {}; + this.positions = {}; } /** @@ -42,12 +43,22 @@ class LineAtlas { * @private */ getDash(dasharray: Array, round: boolean) { - const key = dasharray.join(",") + String(round); + const key = this.getKey(dasharray, round); - if (!this.dashEntry[key]) { - this.dashEntry[key] = this.addDash(dasharray, round); + if (!this.dashes[key]) { + const dash = this.dashes[key] = this.addDash(dasharray, round); + + this.positions[key] = { + tl: [dash.y, dash.y + dash.height], + br: [dash.width, 0], + pixelRatio: 1 + }; } - return this.dashEntry[key]; + return this.dashes[key]; + } + + getKey(dasharray, round) { + return dasharray.join(',') + String(round); } getDashRanges(dasharray: Array, lineAtlasWidth: number, stretch: number) { diff --git a/src/render/program/line_program.js b/src/render/program/line_program.js index 12163a675af..638bffce6fb 100644 --- a/src/render/program/line_program.js +++ b/src/render/program/line_program.js @@ -5,6 +5,7 @@ import { Uniform1f, Uniform2f, Uniform3f, + Uniform4f, UniformMatrix4f } from '../uniform_binding.js'; import pixelsToTileUnits from '../../source/pixels_to_tile_units.js'; @@ -52,12 +53,10 @@ export type LineSDFUniformsType = {| 'u_ratio': Uniform1f, 'u_device_pixel_ratio': Uniform1f, 'u_units_to_pixels': Uniform2f, - 'u_patternscale_a': Uniform2f, - 'u_patternscale_b': Uniform2f, - 'u_sdfgamma': Uniform1f, + 'u_scale': Uniform3f, + 'u_pattern_from': Uniform4f, + 'u_pattern_to': Uniform4f, 'u_image': Uniform1i, - 'u_tex_y_a': Uniform1f, - 'u_tex_y_b': Uniform1f, 'u_mix': Uniform1f |}; @@ -93,12 +92,8 @@ const lineSDFUniforms = (context: Context, locations: UniformLocations): LineSDF 'u_ratio': new Uniform1f(context, locations.u_ratio), 'u_device_pixel_ratio': new Uniform1f(context, locations.u_device_pixel_ratio), 'u_units_to_pixels': new Uniform2f(context, locations.u_units_to_pixels), - 'u_patternscale_a': new Uniform2f(context, locations.u_patternscale_a), - 'u_patternscale_b': new Uniform2f(context, locations.u_patternscale_b), - 'u_sdfgamma': new Uniform1f(context, locations.u_sdfgamma), 'u_image': new Uniform1i(context, locations.u_image), - 'u_tex_y_a': new Uniform1f(context, locations.u_tex_y_a), - 'u_tex_y_b': new Uniform1f(context, locations.u_tex_y_b), + 'u_scale': new Uniform3f(context, locations.u_scale), 'u_mix': new Uniform1f(context, locations.u_mix) }); @@ -167,25 +162,11 @@ const lineSDFUniformValues = ( crossfade: CrossfadeParameters, matrix: ?Float32Array ): UniformValues => { - const transform = painter.transform; - const lineAtlas = painter.lineAtlas; - const tileRatio = calculateTileRatio(tile, transform); - - const round = layer.layout.get('line-cap') === 'round'; - - const posA = lineAtlas.getDash(dasharray.from, round); - const posB = lineAtlas.getDash(dasharray.to, round); - - const widthA = posA.width * crossfade.fromScale; - const widthB = posB.width * crossfade.toScale; + const tileZoomRatio = calculateTileRatio(tile, painter.transform); return extend(lineUniformValues(painter, tile, layer, matrix), { - 'u_patternscale_a': [tileRatio / widthA, -posA.height / 2], - 'u_patternscale_b': [tileRatio / widthB, -posB.height / 2], - 'u_sdfgamma': lineAtlas.width / (Math.min(widthA, widthB) * 256 * browser.devicePixelRatio) / 2, + 'u_scale': [tileZoomRatio, crossfade.fromScale, crossfade.toScale], 'u_image': 0, - 'u_tex_y_a': posA.y, - 'u_tex_y_b': posB.y, 'u_mix': crossfade.t }); }; diff --git a/src/shaders/line_sdf.fragment.glsl b/src/shaders/line_sdf.fragment.glsl index 70a74996b34..88b6500025c 100644 --- a/src/shaders/line_sdf.fragment.glsl +++ b/src/shaders/line_sdf.fragment.glsl @@ -1,8 +1,8 @@ uniform lowp float u_device_pixel_ratio; uniform sampler2D u_image; -uniform float u_sdfgamma; uniform float u_mix; +uniform vec3 u_scale; varying vec2 v_normal; varying vec2 v_width2; @@ -15,6 +15,8 @@ varying float v_gamma_scale; #pragma mapbox: define lowp float opacity #pragma mapbox: define mediump float width #pragma mapbox: define lowp float floorwidth +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to void main() { #pragma mapbox: initialize highp vec4 color @@ -22,6 +24,8 @@ void main() { #pragma mapbox: initialize lowp float opacity #pragma mapbox: initialize mediump float width #pragma mapbox: initialize lowp float floorwidth + #pragma mapbox: initialize mediump vec4 pattern_from + #pragma mapbox: initialize mediump vec4 pattern_to // Calculate the distance of the pixel from the line in pixels. float dist = length(v_normal) * v_width2.s; @@ -35,7 +39,9 @@ void main() { float sdfdist_a = texture2D(u_image, v_tex_a).a; float sdfdist_b = texture2D(u_image, v_tex_b).a; float sdfdist = mix(sdfdist_a, sdfdist_b, u_mix); - alpha *= smoothstep(0.5 - u_sdfgamma / floorwidth, 0.5 + u_sdfgamma / floorwidth, sdfdist); + float sdfwidth = min(pattern_from.z * u_scale.y, pattern_to.z * u_scale.z); // temp layout + float sdfgamma = 1.0 / (2.0 * u_device_pixel_ratio) / sdfwidth; + alpha *= smoothstep(0.5 - sdfgamma / floorwidth, 0.5 + sdfgamma / floorwidth, sdfdist); gl_FragColor = color * (alpha * opacity); diff --git a/src/shaders/line_sdf.vertex.glsl b/src/shaders/line_sdf.vertex.glsl index 7dc1d739c00..260135809f9 100644 --- a/src/shaders/line_sdf.vertex.glsl +++ b/src/shaders/line_sdf.vertex.glsl @@ -13,17 +13,17 @@ attribute float a_linesofar; uniform mat4 u_matrix; uniform mediump float u_ratio; uniform lowp float u_device_pixel_ratio; -uniform vec2 u_patternscale_a; -uniform float u_tex_y_a; -uniform vec2 u_patternscale_b; -uniform float u_tex_y_b; uniform vec2 u_units_to_pixels; +uniform mediump vec3 u_scale; + varying vec2 v_normal; varying vec2 v_width2; +varying float v_linesofar; varying vec2 v_tex_a; varying vec2 v_tex_b; varying float v_gamma_scale; +varying float v_width; #pragma mapbox: define highp vec4 color #pragma mapbox: define lowp float blur @@ -32,6 +32,8 @@ varying float v_gamma_scale; #pragma mapbox: define lowp float offset #pragma mapbox: define mediump float width #pragma mapbox: define lowp float floorwidth +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to void main() { #pragma mapbox: initialize highp vec4 color @@ -41,6 +43,8 @@ void main() { #pragma mapbox: initialize lowp float offset #pragma mapbox: initialize mediump float width #pragma mapbox: initialize lowp float floorwidth + #pragma mapbox: initialize mediump vec4 pattern_from + #pragma mapbox: initialize mediump vec4 pattern_to // the distance over which the line edge fades out. // Retina devices need a smaller distance to avoid aliasing. @@ -91,8 +95,19 @@ void main() { v_gamma_scale = 1.0; #endif - v_tex_a = vec2(a_linesofar * u_patternscale_a.x / floorwidth, normal.y * u_patternscale_a.y + u_tex_y_a); - v_tex_b = vec2(a_linesofar * u_patternscale_b.x / floorwidth, normal.y * u_patternscale_b.y + u_tex_y_b); + float tileZoomRatio = u_scale.x; + float fromScale = u_scale.y; + float toScale = u_scale.z; + + float widthA = pattern_from.z * fromScale; // temp layout + float widthB = pattern_to.z * toScale; + float heightA = pattern_from.y - pattern_from.x; + float heightB = pattern_to.y - pattern_to.x; + + v_tex_a = vec2(a_linesofar * (tileZoomRatio / widthA) / floorwidth, -normal.y * heightA / 2.0 + pattern_from.x); + v_tex_b = vec2(a_linesofar * (tileZoomRatio / widthB) / floorwidth, -normal.y * heightB / 2.0 + pattern_to.x); + v_linesofar = a_linesofar; v_width2 = vec2(outset, inset); + v_width = floorwidth; } diff --git a/src/source/worker_tile.js b/src/source/worker_tile.js index 9356cf11076..d46c7f22b6f 100644 --- a/src/source/worker_tile.js +++ b/src/source/worker_tile.js @@ -11,6 +11,7 @@ import FillBucket from '../data/bucket/fill_bucket.js'; import FillExtrusionBucket from '../data/bucket/fill_extrusion_bucket.js'; import {warnOnce, mapObject, values} from '../util/util.js'; import assert from 'assert'; +import LineAtlas from '../render/line_atlas.js'; import ImageAtlas from '../render/image_atlas.js'; import GlyphAtlas from '../render/glyph_atlas.js'; import EvaluationParameters from '../style/evaluation_parameters.js'; @@ -83,11 +84,14 @@ class WorkerTile { const buckets: {[_: string]: Bucket} = {}; + const lineAtlas = new LineAtlas(256, 256); + const options = { featureIndex, iconDependencies: {}, patternDependencies: {}, glyphDependencies: {}, + lineAtlas, availableImages }; diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index 271840a349c..bcb23cc9261 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -889,10 +889,11 @@ "expression": { "interpolated": false, "parameters": [ - "zoom" + "zoom", + "feature" ] }, - "property-type": "data-constant" + "property-type": "data-driven" }, "line-join": { "type": "enum", @@ -4528,10 +4529,11 @@ "expression": { "interpolated": false, "parameters": [ - "zoom" + "zoom", + "feature" ] }, - "property-type": "cross-faded" + "property-type": "cross-faded-data-driven" }, "line-pattern": { "type": "resolvedImage", diff --git a/src/style-spec/types.js b/src/style-spec/types.js index 1186be1c05e..ff6ef534159 100644 --- a/src/style-spec/types.js +++ b/src/style-spec/types.js @@ -197,7 +197,7 @@ export type LineLayerSpecification = {| "maxzoom"?: number, "filter"?: FilterSpecification, "layout"?: {| - "line-cap"?: PropertyValueSpecification<"butt" | "round" | "square">, + "line-cap"?: DataDrivenPropertyValueSpecification<"butt" | "round" | "square">, "line-join"?: DataDrivenPropertyValueSpecification<"bevel" | "round" | "miter">, "line-miter-limit"?: PropertyValueSpecification, "line-round-limit"?: PropertyValueSpecification, diff --git a/src/style/style_layer/line_style_layer_properties.js b/src/style/style_layer/line_style_layer_properties.js index d70e97d076e..61c3b521254 100644 --- a/src/style/style_layer/line_style_layer_properties.js +++ b/src/style/style_layer/line_style_layer_properties.js @@ -9,7 +9,6 @@ import { DataConstantProperty, DataDrivenProperty, CrossFadedDataDrivenProperty, - CrossFadedProperty, ColorRampProperty } from '../properties.js'; @@ -20,7 +19,7 @@ import type Formatted from '../../style-spec/expression/types/formatted.js'; import type ResolvedImage from '../../style-spec/expression/types/resolved_image.js'; export type LayoutProps = {| - "line-cap": DataConstantProperty<"butt" | "round" | "square">, + "line-cap": DataDrivenProperty<"butt" | "round" | "square">, "line-join": DataDrivenProperty<"bevel" | "round" | "miter">, "line-miter-limit": DataConstantProperty, "line-round-limit": DataConstantProperty, @@ -28,7 +27,7 @@ export type LayoutProps = {| |}; const layout: Properties = new Properties({ - "line-cap": new DataConstantProperty(styleSpec["layout_line"]["line-cap"]), + "line-cap": new DataDrivenProperty(styleSpec["layout_line"]["line-cap"]), "line-join": new DataDrivenProperty(styleSpec["layout_line"]["line-join"]), "line-miter-limit": new DataConstantProperty(styleSpec["layout_line"]["line-miter-limit"]), "line-round-limit": new DataConstantProperty(styleSpec["layout_line"]["line-round-limit"]), @@ -44,7 +43,7 @@ export type PaintProps = {| "line-gap-width": DataDrivenProperty, "line-offset": DataDrivenProperty, "line-blur": DataDrivenProperty, - "line-dasharray": CrossFadedProperty>, + "line-dasharray": CrossFadedDataDrivenProperty>, "line-pattern": CrossFadedDataDrivenProperty, "line-gradient": ColorRampProperty, |}; @@ -58,7 +57,7 @@ const paint: Properties = new Properties({ "line-gap-width": new DataDrivenProperty(styleSpec["paint_line"]["line-gap-width"]), "line-offset": new DataDrivenProperty(styleSpec["paint_line"]["line-offset"]), "line-blur": new DataDrivenProperty(styleSpec["paint_line"]["line-blur"]), - "line-dasharray": new CrossFadedProperty(styleSpec["paint_line"]["line-dasharray"]), + "line-dasharray": new CrossFadedDataDrivenProperty(styleSpec["paint_line"]["line-dasharray"]), "line-pattern": new CrossFadedDataDrivenProperty(styleSpec["paint_line"]["line-pattern"]), "line-gradient": new ColorRampProperty(styleSpec["paint_line"]["line-gradient"]), }); From c2855d5629c7d17f54353a31cf6ffc8f0221d240 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 9 Apr 2021 19:31:29 +0300 Subject: [PATCH 02/29] WIP pass the per-tile line atlas through --- src/data/bucket/line_bucket.js | 2 ++ src/render/draw_line.js | 16 ++++++---------- src/render/line_atlas.js | 5 ++++- src/render/program/line_program.js | 2 -- src/source/tile.js | 8 ++++++++ src/source/worker_source.js | 1 + src/source/worker_tile.js | 1 + 7 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 3082edc2c61..02b187eaa7b 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -207,8 +207,10 @@ class LineBucket implements Bucket { const maxDashArray = dashPropertyValue.evaluate({zoom: zoom + 1}, feature); let minRound, midRound, maxRound; + if (capPropertyValue.kind === 'constant') { minRound = midRound = maxRound = capPropertyValue.value === 'round'; + } else { minRound = capPropertyValue.evaluate({zoom: zoom - 1}, feature) === 'round'; midRound = capPropertyValue.evaluate({zoom}, feature) === 'round'; diff --git a/src/render/draw_line.js b/src/render/draw_line.js index 9a605ae4417..cece7b604af 100644 --- a/src/render/draw_line.js +++ b/src/render/draw_line.js @@ -29,7 +29,8 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay const depthMode = painter.depthModeForSublayer(0, DepthMode.ReadOnly); const colorMode = painter.colorModeForRenderPass(); - const dasharray = layer.paint.get('line-dasharray'); + const dasharrayProperty = layer.paint.get('line-dasharray'); + const dasharray = dasharrayProperty.constantOr((1: any)); const patternProperty = layer.paint.get('line-pattern'); const image = patternProperty.constantOr((1: any)); @@ -44,8 +45,6 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay const context = painter.context; const gl = context.gl; - let firstTile = true; - for (const coord of coords) { const tile = sourceCache.getTile(coord); if (image && !tile.patternsLoaded()) continue; @@ -57,7 +56,6 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay const programConfiguration = bucket.programConfigurations.get(layer.id); const prevProgram = painter.context.program.get(); const program = painter.useProgram(programId, programConfiguration); - const programChanged = firstTile || program.program !== prevProgram; const constantPattern = patternProperty.constantOr(null); if (constantPattern && tile.imageAtlas) { @@ -69,7 +67,7 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay const matrix = painter.terrain ? coord.posMatrix : null; const uniformValues = image ? linePatternUniformValues(painter, tile, layer, crossfade, matrix) : - dasharray ? lineSDFUniformValues(painter, tile, layer, dasharray, crossfade, matrix) : + dasharray ? lineSDFUniformValues(painter, tile, layer, crossfade, matrix) : gradient ? lineGradientUniformValues(painter, tile, layer, matrix, bucket.lineClipsArray.length) : lineUniformValues(painter, tile, layer, matrix); @@ -77,9 +75,10 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay context.activeTexture.set(gl.TEXTURE0); tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); programConfiguration.updatePaintBuffers(crossfade); - } else if (dasharray && (programChanged || painter.lineAtlas.dirty)) { + } else if (dasharray) { context.activeTexture.set(gl.TEXTURE0); - painter.lineAtlas.bind(context); + tile.lineAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); + programConfiguration.updatePaintBuffers(crossfade); } else if (gradient) { const layerGradient = bucket.gradients[layer.id]; let gradientTexture = layerGradient.texture; @@ -119,8 +118,5 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay painter.stencilModeForClipping(coord), colorMode, CullFaceMode.disabled, uniformValues, layer.id, bucket.layoutVertexBuffer, bucket.indexBuffer, bucket.segments, layer.paint, painter.transform.zoom, programConfiguration, bucket.layoutVertexBuffer2); - - firstTile = false; - // once refactored so that bound texture state is managed, we'll also be able to remove this firstTile/programChanged logic } } diff --git a/src/render/line_atlas.js b/src/render/line_atlas.js index 9620225ea02..354c892f333 100644 --- a/src/render/line_atlas.js +++ b/src/render/line_atlas.js @@ -1,6 +1,7 @@ // @flow import {warnOnce} from '../util/util.js'; +import {AlphaImage} from '../util/image.js'; import type Context from '../gl/context.js'; @@ -19,6 +20,7 @@ class LineAtlas { nextRow: number; bytes: number; data: Uint8Array; + image: AlphaImage; dashes: {[_: string]: any}; dirty: boolean; texture: WebGLTexture; @@ -28,7 +30,8 @@ class LineAtlas { this.height = height; this.nextRow = 0; - this.data = new Uint8Array(this.width * this.height); + this.data = new Uint8Array(width * height); + this.image = new AlphaImage({width, height}, this.data); this.dashes = {}; this.positions = {}; diff --git a/src/render/program/line_program.js b/src/render/program/line_program.js index 638bffce6fb..389e1cde86a 100644 --- a/src/render/program/line_program.js +++ b/src/render/program/line_program.js @@ -16,7 +16,6 @@ import type Context from '../../gl/context.js'; import type {UniformValues, UniformLocations} from '../uniform_binding.js'; import type Transform from '../../geo/transform.js'; import type Tile from '../../source/tile.js'; -import type {CrossFaded} from '../../style/properties.js'; import type LineStyleLayer from '../../style/style_layer/line_style_layer.js'; import type Painter from '../painter.js'; import type {CrossfadeParameters} from '../../style/evaluation_parameters.js'; @@ -158,7 +157,6 @@ const lineSDFUniformValues = ( painter: Painter, tile: Tile, layer: LineStyleLayer, - dasharray: CrossFaded>, crossfade: CrossfadeParameters, matrix: ?Float32Array ): UniformValues => { diff --git a/src/source/tile.js b/src/source/tile.js index eb36fb2e23a..0aa1968db9a 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -215,6 +215,9 @@ class Tile { if (data.glyphAtlasImage) { this.glyphAtlasImage = data.glyphAtlasImage; } + if (data.lineAtlasImage) { + this.lineAtlasImage = data.lineAtlasImage; + } } /** @@ -275,6 +278,11 @@ class Tile { this.glyphAtlasTexture = new Texture(context, this.glyphAtlasImage, gl.ALPHA); this.glyphAtlasImage = null; } + + if (this.lineAtlasImage) { + this.lineAtlasTexture = new Texture(context, this.lineAtlasImage, gl.ALPHA); + this.lineAtlasImage = null; + } } prepare(imageManager: ImageManager) { diff --git a/src/source/worker_source.js b/src/source/worker_source.js index d9c37b37630..8d7f79b0d47 100644 --- a/src/source/worker_source.js +++ b/src/source/worker_source.js @@ -52,6 +52,7 @@ export type WorkerTileResult = { buckets: Array, imageAtlas: ImageAtlas, glyphAtlasImage: AlphaImage, + lineAtlasImage: AlphaImage, featureIndex: FeatureIndex, collisionBoxArray: CollisionBoxArray, rawTileData?: ArrayBuffer, diff --git a/src/source/worker_tile.js b/src/source/worker_tile.js index d46c7f22b6f..45fc2aa64d1 100644 --- a/src/source/worker_tile.js +++ b/src/source/worker_tile.js @@ -243,6 +243,7 @@ class WorkerTile { featureIndex, collisionBoxArray: this.collisionBoxArray, glyphAtlasImage: glyphAtlas.image, + lineAtlasImage: lineAtlas.image, imageAtlas, // Only used for benchmarking: glyphMap: this.returnDependencies ? glyphMap : null, From 6d3f045eadb44ab3c79240ebbebfa00558439e83 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Mon, 12 Apr 2021 18:52:56 +0300 Subject: [PATCH 03/29] WIP switch to integer coords for line atlas --- src/data/bucket/line_bucket.js | 9 ++++----- src/render/line_atlas.js | 17 +++++------------ src/render/program/line_program.js | 10 +++++----- src/shaders/line_sdf.fragment.glsl | 4 ++-- src/shaders/line_sdf.vertex.glsl | 11 ++++++----- src/source/tile.js | 5 +++++ 6 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 02b187eaa7b..556189f38a9 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -180,12 +180,11 @@ class LineBucket implements Bucket { // so are stored during populate until later updated with positions by tile worker in addFeatures this.patternFeatures.push(patternBucketFeature); - } else if (hasDataDrivenDashes) { - this.addFeatureDashes(bucketFeature, options); - this.addFeature(bucketFeature, geometry, index, canonical, options.lineAtlas.positions); - } else { - this.addFeature(bucketFeature, geometry, index, canonical, {}); + if (hasDataDrivenDashes) { + this.addFeatureDashes(bucketFeature, options); + } + this.addFeature(bucketFeature, geometry, index, canonical, options.lineAtlas.dashes); } const feature = features[index].feature; diff --git a/src/render/line_atlas.js b/src/render/line_atlas.js index 354c892f333..5c06162d386 100644 --- a/src/render/line_atlas.js +++ b/src/render/line_atlas.js @@ -34,7 +34,6 @@ class LineAtlas { this.image = new AlphaImage({width, height}, this.data); this.dashes = {}; - this.positions = {}; } /** @@ -49,13 +48,7 @@ class LineAtlas { const key = this.getKey(dasharray, round); if (!this.dashes[key]) { - const dash = this.dashes[key] = this.addDash(dasharray, round); - - this.positions[key] = { - tl: [dash.y, dash.y + dash.height], - br: [dash.width, 0], - pixelRatio: 1 - }; + this.dashes[key] = this.addDash(dasharray, round); } return this.dashes[key]; } @@ -199,10 +192,10 @@ class LineAtlas { } } - const dashEntry = { - y: (this.nextRow + n + 0.5) / this.height, - height: 2 * n / this.height, - width: length + const dashEntry = { // temp layout + tl: [this.nextRow, this.nextRow + height], + br: [length, 0], + pixelRatio: 1 }; this.nextRow += height; diff --git a/src/render/program/line_program.js b/src/render/program/line_program.js index 389e1cde86a..5d7bdf545e2 100644 --- a/src/render/program/line_program.js +++ b/src/render/program/line_program.js @@ -52,9 +52,7 @@ export type LineSDFUniformsType = {| 'u_ratio': Uniform1f, 'u_device_pixel_ratio': Uniform1f, 'u_units_to_pixels': Uniform2f, - 'u_scale': Uniform3f, - 'u_pattern_from': Uniform4f, - 'u_pattern_to': Uniform4f, + 'u_scale': Uniform4f, 'u_image': Uniform1i, 'u_mix': Uniform1f |}; @@ -92,7 +90,7 @@ const lineSDFUniforms = (context: Context, locations: UniformLocations): LineSDF 'u_device_pixel_ratio': new Uniform1f(context, locations.u_device_pixel_ratio), 'u_units_to_pixels': new Uniform2f(context, locations.u_units_to_pixels), 'u_image': new Uniform1i(context, locations.u_image), - 'u_scale': new Uniform3f(context, locations.u_scale), + 'u_scale': new Uniform4f(context, locations.u_scale), 'u_mix': new Uniform1f(context, locations.u_mix) }); @@ -162,8 +160,10 @@ const lineSDFUniformValues = ( ): UniformValues => { const tileZoomRatio = calculateTileRatio(tile, painter.transform); + const atlasHeight = tile.lineAtlasTexture.size[1]; + return extend(lineUniformValues(painter, tile, layer, matrix), { - 'u_scale': [tileZoomRatio, crossfade.fromScale, crossfade.toScale], + 'u_scale': [tileZoomRatio, crossfade.fromScale, crossfade.toScale, atlasHeight], 'u_image': 0, 'u_mix': crossfade.t }); diff --git a/src/shaders/line_sdf.fragment.glsl b/src/shaders/line_sdf.fragment.glsl index 88b6500025c..f5494329b46 100644 --- a/src/shaders/line_sdf.fragment.glsl +++ b/src/shaders/line_sdf.fragment.glsl @@ -2,7 +2,7 @@ uniform lowp float u_device_pixel_ratio; uniform sampler2D u_image; uniform float u_mix; -uniform vec3 u_scale; +uniform vec4 u_scale; varying vec2 v_normal; varying vec2 v_width2; @@ -43,7 +43,7 @@ void main() { float sdfgamma = 1.0 / (2.0 * u_device_pixel_ratio) / sdfwidth; alpha *= smoothstep(0.5 - sdfgamma / floorwidth, 0.5 + sdfgamma / floorwidth, sdfdist); - gl_FragColor = color * (alpha * opacity); + gl_FragColor = vec4(v_tex_a.y, v_tex_b.y, 0.0, 1.0); //color * (alpha * opacity); #ifdef OVERDRAW_INSPECTOR gl_FragColor = vec4(1.0); diff --git a/src/shaders/line_sdf.vertex.glsl b/src/shaders/line_sdf.vertex.glsl index 260135809f9..096bb15659d 100644 --- a/src/shaders/line_sdf.vertex.glsl +++ b/src/shaders/line_sdf.vertex.glsl @@ -15,7 +15,7 @@ uniform mediump float u_ratio; uniform lowp float u_device_pixel_ratio; uniform vec2 u_units_to_pixels; -uniform mediump vec3 u_scale; +uniform mediump vec4 u_scale; varying vec2 v_normal; varying vec2 v_width2; @@ -98,14 +98,15 @@ void main() { float tileZoomRatio = u_scale.x; float fromScale = u_scale.y; float toScale = u_scale.z; + float texHeight = u_scale.w; float widthA = pattern_from.z * fromScale; // temp layout float widthB = pattern_to.z * toScale; - float heightA = pattern_from.y - pattern_from.x; - float heightB = pattern_to.y - pattern_to.x; + float heightA = (pattern_from.y - pattern_from.x) / texHeight; + float heightB = (pattern_to.y - pattern_to.x) / texHeight; - v_tex_a = vec2(a_linesofar * (tileZoomRatio / widthA) / floorwidth, -normal.y * heightA / 2.0 + pattern_from.x); - v_tex_b = vec2(a_linesofar * (tileZoomRatio / widthB) / floorwidth, -normal.y * heightB / 2.0 + pattern_to.x); + v_tex_a = vec2(a_linesofar * (tileZoomRatio / widthA) / floorwidth, -normal.y * heightA / 2.0 + (pattern_from.x + 0.5) / texHeight); + v_tex_b = vec2(a_linesofar * (tileZoomRatio / widthB) / floorwidth, -normal.y * heightB / 2.0 + (pattern_to.x + 0.5) / texHeight); v_linesofar = a_linesofar; v_width2 = vec2(outset, inset); diff --git a/src/source/tile.js b/src/source/tile.js index 0aa1968db9a..5e055046269 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -242,6 +242,11 @@ class Tile { if (this.glyphAtlasTexture) { this.glyphAtlasTexture.destroy(); } + + if (this.lineAtlasTexture) { + this.lineAtlasTexture.destroy(); + } + Debug.run(() => { if (this.queryGeometryDebugViz) { this.queryGeometryDebugViz.unload(); From dca713a222138a9637516f9d41cd5177c4c6acdd Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Mon, 12 Apr 2021 19:04:29 +0300 Subject: [PATCH 04/29] add a dds dasharray debug page (temp) --- debug/dasharray.html | 118 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 debug/dasharray.html diff --git a/debug/dasharray.html b/debug/dasharray.html new file mode 100644 index 00000000000..9df1ab4c09c --- /dev/null +++ b/debug/dasharray.html @@ -0,0 +1,118 @@ + + + + Mapbox GL JS debug page + + + + + + + +
+ + + + + + From 48a63033a1f7564160d5459f492e6ee21376c069 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Mon, 12 Apr 2021 19:05:37 +0300 Subject: [PATCH 05/29] fix debug page --- debug/dasharray.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/debug/dasharray.html b/debug/dasharray.html index 9df1ab4c09c..4708c165ae5 100644 --- a/debug/dasharray.html +++ b/debug/dasharray.html @@ -20,10 +20,9 @@ var map = window.map = new mapboxgl.Map({ container: 'map', - zoom: 12.5, - center: [-122.4194, 37.7749], - style: 'mapbox://styles/mapbox/streets-v11', - hash: true + zoom: 4, + center: [0, 0], + style: 'mapbox://styles/mapbox/streets-v11' }); map.on('load', () => { From 220386166615aff10a1eff50d093fadba9a5d2ea Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 13 Apr 2021 14:05:32 -0400 Subject: [PATCH 06/29] use different attribute names for dashes to avoid collision The attribute names used for dashes were the same as those used for patterns. This was leading to incorrectly generated shaders. Since there was no pattern set for the dash layer it was setting the defines that generate the shader without those attributes. The line atlas texture is now bound with gl.REPEAT to render more than one dash. --- build/generate-struct-arrays.js | 4 +++- debug/dasharray.html | 2 +- src/data/array_types.js | 1 + src/data/bucket/dash_attributes.js | 12 ++++++++++ src/data/program_configuration.js | 22 +++++++++++-------- src/render/draw_line.js | 2 +- src/shaders/line_sdf.fragment.glsl | 12 +++++----- src/shaders/line_sdf.vertex.glsl | 20 ++++++++--------- .../line_style_layer_properties.js | 1 + 9 files changed, 48 insertions(+), 28 deletions(-) create mode 100644 src/data/bucket/dash_attributes.js diff --git a/build/generate-struct-arrays.js b/build/generate-struct-arrays.js index 8238f98d5cb..6a3e8cf86ea 100644 --- a/build/generate-struct-arrays.js +++ b/build/generate-struct-arrays.js @@ -128,6 +128,7 @@ import fillAttributes from '../src/data/bucket/fill_attributes.js'; import lineAttributes from '../src/data/bucket/line_attributes.js'; import lineAttributesExt from '../src/data/bucket/line_attributes_ext.js'; import patternAttributes from '../src/data/bucket/pattern_attributes.js'; +import dashAttributes from '../src/data/bucket/dash_attributes.js'; import skyboxAttributes from '../src/render/skybox_attributes.js'; import {fillExtrusionAttributes, centroidAttributes} from '../src/data/bucket/fill_extrusion_attributes.js'; @@ -139,7 +140,8 @@ const layoutAttributes = { heatmap: circleAttributes, line: lineAttributes, lineExt: lineAttributesExt, - pattern: patternAttributes + pattern: patternAttributes, + dash: dashAttributes }; for (const name in layoutAttributes) { createStructArrayType(`${name.replace(/-/g, '_')}_layout`, layoutAttributes[name]); diff --git a/debug/dasharray.html b/debug/dasharray.html index 4708c165ae5..32cd4c69a7a 100644 --- a/debug/dasharray.html +++ b/debug/dasharray.html @@ -22,7 +22,7 @@ container: 'map', zoom: 4, center: [0, 0], - style: 'mapbox://styles/mapbox/streets-v11' + style: { sources: {}, version: 8, layers: [] } }); map.on('load', () => { diff --git a/src/data/array_types.js b/src/data/array_types.js index 220dae362c5..7031bf4b54d 100644 --- a/src/data/array_types.js +++ b/src/data/array_types.js @@ -1129,6 +1129,7 @@ export { StructArrayLayout2i4ub1f12 as LineLayoutArray, StructArrayLayout2f8 as LineExtLayoutArray, StructArrayLayout10ui20 as PatternLayoutArray, + StructArrayLayout10ui20 as DashLayoutArray, StructArrayLayout4i4ui4i24 as SymbolLayoutArray, StructArrayLayout3f12 as SymbolDynamicLayoutArray, StructArrayLayout1ul4 as SymbolOpacityArray, diff --git a/src/data/bucket/dash_attributes.js b/src/data/bucket/dash_attributes.js new file mode 100644 index 00000000000..86892cda7db --- /dev/null +++ b/src/data/bucket/dash_attributes.js @@ -0,0 +1,12 @@ +// @flow +import {createLayout} from '../../util/struct_array.js'; + +const dashAttributes = createLayout([ + // [tl.x, tl.y, br.x, br.y] + {name: 'a_dash_to', components: 4, type: 'Uint16'}, + {name: 'a_dash_from', components: 4, type: 'Uint16'}, + {name: 'a_pixel_ratio_to', components: 1, type: 'Uint16'}, + {name: 'a_pixel_ratio_from', components: 1, type: 'Uint16'}, +]); + +export default dashAttributes; diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 0601d9faf9b..69e03d1d229 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -5,9 +5,10 @@ import Color from '../style-spec/util/color.js'; import {supportsPropertyExpression} from '../style-spec/util/properties.js'; import {register} from '../util/web_worker_transfer.js'; import {PossiblyEvaluatedPropertyValue} from '../style/properties.js'; -import {StructArrayLayout1f4, StructArrayLayout2f8, StructArrayLayout4f16, PatternLayoutArray} from './array_types.js'; +import {StructArrayLayout1f4, StructArrayLayout2f8, StructArrayLayout4f16, PatternLayoutArray, DashLayoutArray} from './array_types.js'; import {clamp} from '../util/util.js'; import patternAttributes from './bucket/pattern_attributes.js'; +import dashAttributes from './bucket/dash_attributes.js'; import EvaluationParameters from '../style/evaluation_parameters.js'; import FeaturePositionMap from './feature_position_map.js'; import { @@ -320,8 +321,11 @@ class CrossFadedCompositeBinder implements AttributeBinder { this.zoom = zoom; this.layerId = layerId; + this.patternAttributes = type === 'array' ? + dashAttributes : + patternAttributes; for (let i = 0; i < names.length; ++i) { - assert(`a_${names[i]}` === patternAttributes.members[i].name); + assert(`a_${names[i]}` === this.patternAttributes.members[i].name); } this.zoomInPaintVertexArray = new PaintVertexArray(); @@ -369,8 +373,8 @@ class CrossFadedCompositeBinder implements AttributeBinder { upload(context: Context) { if (this.zoomInPaintVertexArray && this.zoomInPaintVertexArray.arrayBuffer && this.zoomOutPaintVertexArray && this.zoomOutPaintVertexArray.arrayBuffer) { - this.zoomInPaintVertexBuffer = context.createVertexBuffer(this.zoomInPaintVertexArray, patternAttributes.members, this.expression.isStateDependent); - this.zoomOutPaintVertexBuffer = context.createVertexBuffer(this.zoomOutPaintVertexArray, patternAttributes.members, this.expression.isStateDependent); + this.zoomInPaintVertexBuffer = context.createVertexBuffer(this.zoomInPaintVertexArray, this.patternAttributes.members, this.expression.isStateDependent); + this.zoomOutPaintVertexBuffer = context.createVertexBuffer(this.zoomOutPaintVertexArray, this.patternAttributes.members, this.expression.isStateDependent); } } @@ -512,8 +516,8 @@ export default class ProgramConfiguration { result.push(binder.paintVertexAttributes[i].name); } } else if (binder instanceof CrossFadedCompositeBinder) { - for (let i = 0; i < patternAttributes.members.length; i++) { - result.push(patternAttributes.members[i].name); + for (let i = 0; i < binder.patternAttributes.members.length; i++) { + result.push(binder.patternAttributes.members[i].name); } } } @@ -664,7 +668,7 @@ function paintAttributeNames(property, type) { 'line-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], 'fill-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], 'fill-extrusion-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], - 'line-dasharray': ['pattern_to', 'pattern_from'] + 'line-dasharray': ['dash_to', 'dash_from'] }; return attributeNameExceptions[property] || [property.replace(`${type}-`, '').replace(/-/g, '_')]; @@ -685,8 +689,8 @@ function getLayoutException(property) { 'composite': PatternLayoutArray }, 'line-dasharray': { // temporary layout - 'source': PatternLayoutArray, - 'composite': PatternLayoutArray + 'source': DashLayoutArray, + 'composite': DashLayoutArray } }; diff --git a/src/render/draw_line.js b/src/render/draw_line.js index cece7b604af..7c60a40b4a3 100644 --- a/src/render/draw_line.js +++ b/src/render/draw_line.js @@ -77,7 +77,7 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay programConfiguration.updatePaintBuffers(crossfade); } else if (dasharray) { context.activeTexture.set(gl.TEXTURE0); - tile.lineAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); + tile.lineAtlasTexture.bind(gl.LINEAR, gl.REPEAT); programConfiguration.updatePaintBuffers(crossfade); } else if (gradient) { const layerGradient = bucket.gradients[layer.id]; diff --git a/src/shaders/line_sdf.fragment.glsl b/src/shaders/line_sdf.fragment.glsl index f5494329b46..32e1ec7b3d4 100644 --- a/src/shaders/line_sdf.fragment.glsl +++ b/src/shaders/line_sdf.fragment.glsl @@ -15,8 +15,8 @@ varying float v_gamma_scale; #pragma mapbox: define lowp float opacity #pragma mapbox: define mediump float width #pragma mapbox: define lowp float floorwidth -#pragma mapbox: define lowp vec4 pattern_from -#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp vec4 dash_from +#pragma mapbox: define lowp vec4 dash_to void main() { #pragma mapbox: initialize highp vec4 color @@ -24,8 +24,8 @@ void main() { #pragma mapbox: initialize lowp float opacity #pragma mapbox: initialize mediump float width #pragma mapbox: initialize lowp float floorwidth - #pragma mapbox: initialize mediump vec4 pattern_from - #pragma mapbox: initialize mediump vec4 pattern_to + #pragma mapbox: initialize mediump vec4 dash_from + #pragma mapbox: initialize mediump vec4 dash_to // Calculate the distance of the pixel from the line in pixels. float dist = length(v_normal) * v_width2.s; @@ -39,11 +39,11 @@ void main() { float sdfdist_a = texture2D(u_image, v_tex_a).a; float sdfdist_b = texture2D(u_image, v_tex_b).a; float sdfdist = mix(sdfdist_a, sdfdist_b, u_mix); - float sdfwidth = min(pattern_from.z * u_scale.y, pattern_to.z * u_scale.z); // temp layout + float sdfwidth = min(dash_from.z * u_scale.y, dash_to.z * u_scale.z); // temp layout float sdfgamma = 1.0 / (2.0 * u_device_pixel_ratio) / sdfwidth; alpha *= smoothstep(0.5 - sdfgamma / floorwidth, 0.5 + sdfgamma / floorwidth, sdfdist); - gl_FragColor = vec4(v_tex_a.y, v_tex_b.y, 0.0, 1.0); //color * (alpha * opacity); + gl_FragColor = color * (alpha * opacity); #ifdef OVERDRAW_INSPECTOR gl_FragColor = vec4(1.0); diff --git a/src/shaders/line_sdf.vertex.glsl b/src/shaders/line_sdf.vertex.glsl index 096bb15659d..ca817893076 100644 --- a/src/shaders/line_sdf.vertex.glsl +++ b/src/shaders/line_sdf.vertex.glsl @@ -32,8 +32,8 @@ varying float v_width; #pragma mapbox: define lowp float offset #pragma mapbox: define mediump float width #pragma mapbox: define lowp float floorwidth -#pragma mapbox: define lowp vec4 pattern_from -#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp vec4 dash_from +#pragma mapbox: define lowp vec4 dash_to void main() { #pragma mapbox: initialize highp vec4 color @@ -43,8 +43,8 @@ void main() { #pragma mapbox: initialize lowp float offset #pragma mapbox: initialize mediump float width #pragma mapbox: initialize lowp float floorwidth - #pragma mapbox: initialize mediump vec4 pattern_from - #pragma mapbox: initialize mediump vec4 pattern_to + #pragma mapbox: initialize mediump vec4 dash_from + #pragma mapbox: initialize mediump vec4 dash_to // the distance over which the line edge fades out. // Retina devices need a smaller distance to avoid aliasing. @@ -100,13 +100,13 @@ void main() { float toScale = u_scale.z; float texHeight = u_scale.w; - float widthA = pattern_from.z * fromScale; // temp layout - float widthB = pattern_to.z * toScale; - float heightA = (pattern_from.y - pattern_from.x) / texHeight; - float heightB = (pattern_to.y - pattern_to.x) / texHeight; + float widthA = dash_from.z * fromScale; // temp layout + float widthB = dash_to.z * toScale; + float heightA = (dash_from.y - dash_from.x) / texHeight; + float heightB = (dash_to.y - dash_to.x) / texHeight; - v_tex_a = vec2(a_linesofar * (tileZoomRatio / widthA) / floorwidth, -normal.y * heightA / 2.0 + (pattern_from.x + 0.5) / texHeight); - v_tex_b = vec2(a_linesofar * (tileZoomRatio / widthB) / floorwidth, -normal.y * heightB / 2.0 + (pattern_to.x + 0.5) / texHeight); + v_tex_a = vec2(a_linesofar * (tileZoomRatio / widthA) / floorwidth, -normal.y * heightA / 2.0 + (dash_from.x + 0.5) / texHeight); + v_tex_b = vec2(a_linesofar * (tileZoomRatio / widthB) / floorwidth, -normal.y * heightB / 2.0 + (dash_to.x + 0.5) / texHeight); v_linesofar = a_linesofar; v_width2 = vec2(outset, inset); diff --git a/src/style/style_layer/line_style_layer_properties.js b/src/style/style_layer/line_style_layer_properties.js index 61c3b521254..729725d70ce 100644 --- a/src/style/style_layer/line_style_layer_properties.js +++ b/src/style/style_layer/line_style_layer_properties.js @@ -9,6 +9,7 @@ import { DataConstantProperty, DataDrivenProperty, CrossFadedDataDrivenProperty, + CrossFadedProperty, ColorRampProperty } from '../properties.js'; From ff328d86a41aad0d678fb533a51bd6f1eb6575da Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 14 Apr 2021 12:15:17 +0300 Subject: [PATCH 07/29] fix DDS dasharray rendering --- debug/dasharray.html | 12 ++++++------ src/render/line_atlas.js | 2 +- src/shaders/line_sdf.vertex.glsl | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/debug/dasharray.html b/debug/dasharray.html index 32cd4c69a7a..3cb4c631dee 100644 --- a/debug/dasharray.html +++ b/debug/dasharray.html @@ -100,15 +100,15 @@ "line-width": 10, "line-dasharray": [ "match", ["get", "property"], - 1, ["literal", [1, 1]], - 2, ["literal", [2, 1]], - 3, ["literal", [3, 1]], + 1, ["literal", [1, 2]], + 2, ["literal", [2, 2]], + 3, ["literal", [3, 2]], ["literal", [1, 1]] ] }, - // "layout": { - // "line-cap": "round" - // } + "layout": { + "line-cap": ["match", ["get", "property"], 2, "round", "butt"] + } }); }); diff --git a/src/render/line_atlas.js b/src/render/line_atlas.js index 5c06162d386..29abb38f057 100644 --- a/src/render/line_atlas.js +++ b/src/render/line_atlas.js @@ -193,7 +193,7 @@ class LineAtlas { } const dashEntry = { // temp layout - tl: [this.nextRow, this.nextRow + height], + tl: [this.nextRow + n, n], br: [length, 0], pixelRatio: 1 }; diff --git a/src/shaders/line_sdf.vertex.glsl b/src/shaders/line_sdf.vertex.glsl index ca817893076..169a0ed292c 100644 --- a/src/shaders/line_sdf.vertex.glsl +++ b/src/shaders/line_sdf.vertex.glsl @@ -102,11 +102,11 @@ void main() { float widthA = dash_from.z * fromScale; // temp layout float widthB = dash_to.z * toScale; - float heightA = (dash_from.y - dash_from.x) / texHeight; - float heightB = (dash_to.y - dash_to.x) / texHeight; + float heightA = dash_from.y / texHeight; + float heightB = dash_to.y / texHeight; - v_tex_a = vec2(a_linesofar * (tileZoomRatio / widthA) / floorwidth, -normal.y * heightA / 2.0 + (dash_from.x + 0.5) / texHeight); - v_tex_b = vec2(a_linesofar * (tileZoomRatio / widthB) / floorwidth, -normal.y * heightB / 2.0 + (dash_to.x + 0.5) / texHeight); + v_tex_a = vec2(a_linesofar * (tileZoomRatio / widthA) / floorwidth, -normal.y * heightA + (dash_from.x + 0.5) / texHeight); + v_tex_b = vec2(a_linesofar * (tileZoomRatio / widthB) / floorwidth, -normal.y * heightB + (dash_to.x + 0.5) / texHeight); v_linesofar = a_linesofar; v_width2 = vec2(outset, inset); From 304c290a0d89453fc5d4fdb03e5957499c35ab6e Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 16 Apr 2021 20:12:20 +0300 Subject: [PATCH 08/29] refactor to support constant patterns --- src/data/bucket/line_bucket.js | 2 +- src/data/program_configuration.js | 16 +++++----- src/render/draw_line.js | 11 ++++++- src/render/line_atlas.js | 50 +++++++------------------------ src/source/tile.js | 23 ++++++++------ src/source/worker_tile.js | 2 +- 6 files changed, 45 insertions(+), 59 deletions(-) diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 556189f38a9..6781e720f29 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -184,7 +184,7 @@ class LineBucket implements Bucket { if (hasDataDrivenDashes) { this.addFeatureDashes(bucketFeature, options); } - this.addFeature(bucketFeature, geometry, index, canonical, options.lineAtlas.dashes); + this.addFeature(bucketFeature, geometry, index, canonical, options.lineAtlas.positions); } const feature = features[index].feature; diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 69e03d1d229..0326cc02c98 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -132,23 +132,23 @@ class CrossFadedConstantBinder implements UniformBinder { setConstantPatternPositions(posTo: ImagePosition, posFrom: ImagePosition) { this.pixelRatioFrom = posFrom.pixelRatio; this.pixelRatioTo = posTo.pixelRatio; - this.patternFrom = posFrom.tlbr; - this.patternTo = posTo.tlbr; + this.patternFrom = posFrom.tl.concat(posFrom.br); + this.patternTo = posTo.tl.concat(posTo.br); } setUniform(uniform: Uniform<*>, globals: GlobalProperties, currentValue: PossiblyEvaluatedPropertyValue, uniformName: string) { const pos = - uniformName === 'u_pattern_to' ? this.patternTo : - uniformName === 'u_pattern_from' ? this.patternFrom : + uniformName === 'u_pattern_to' || uniformName === 'u_dash_to' ? this.patternTo : + uniformName === 'u_pattern_from' || uniformName === 'u_dash_from' ? this.patternFrom : uniformName === 'u_pixel_ratio_to' ? this.pixelRatioTo : uniformName === 'u_pixel_ratio_from' ? this.pixelRatioFrom : null; if (pos) uniform.set(pos); } getBinding(context: Context, location: WebGLUniformLocation, name: string): $Shape> { - return name.substr(0, 9) === 'u_pattern' ? - new Uniform4f(context, location) : - new Uniform1f(context, location); + if (name === 'u_pattern_from' || name === 'u_pattern_to') return new Uniform4f(context, location); + if (name === 'u_dash_from' || name === 'u_dash_to') return new Uniform4f(context, location); + return new Uniform1f(context, location); } } @@ -690,7 +690,7 @@ function getLayoutException(property) { }, 'line-dasharray': { // temporary layout 'source': DashLayoutArray, - 'composite': DashLayoutArray + 'composite': DashLayoutArray } }; diff --git a/src/render/draw_line.js b/src/render/draw_line.js index 7c60a40b4a3..cb1cfe975a9 100644 --- a/src/render/draw_line.js +++ b/src/render/draw_line.js @@ -31,6 +31,7 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay const dasharrayProperty = layer.paint.get('line-dasharray'); const dasharray = dasharrayProperty.constantOr((1: any)); + const capProperty = layer.layout.get('line-cap'); const patternProperty = layer.paint.get('line-pattern'); const image = patternProperty.constantOr((1: any)); @@ -54,7 +55,6 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay painter.prepareDrawTile(coord); const programConfiguration = bucket.programConfigurations.get(layer.id); - const prevProgram = painter.context.program.get(); const program = painter.useProgram(programId, programConfiguration); const constantPattern = patternProperty.constantOr(null); @@ -65,6 +65,15 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom); } + const constantDash = dasharrayProperty.constantOr(null); + const constantCap = capProperty.constantOr(null); + + if (constantDash && constantCap && tile.lineAtlas) { + const posTo = tile.lineAtlas.getDash(constantDash.to, constantCap); + const posFrom = tile.lineAtlas.getDash(constantDash.from, constantCap); + if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom); + } + const matrix = painter.terrain ? coord.posMatrix : null; const uniformValues = image ? linePatternUniformValues(painter, tile, layer, crossfade, matrix) : dasharray ? lineSDFUniformValues(painter, tile, layer, crossfade, matrix) : diff --git a/src/render/line_atlas.js b/src/render/line_atlas.js index 29abb38f057..9800fb98b90 100644 --- a/src/render/line_atlas.js +++ b/src/render/line_atlas.js @@ -2,8 +2,7 @@ import {warnOnce} from '../util/util.js'; import {AlphaImage} from '../util/image.js'; - -import type Context from '../gl/context.js'; +import {register} from '../util/web_worker_transfer.js'; /** * A LineAtlas lets us reuse rendered dashed lines @@ -18,22 +17,15 @@ class LineAtlas { width: number; height: number; nextRow: number; - bytes: number; - data: Uint8Array; image: AlphaImage; - dashes: {[_: string]: any}; - dirty: boolean; - texture: WebGLTexture; + positions: {[_: string]: any}; constructor(width: number, height: number) { this.width = width; this.height = height; this.nextRow = 0; - - this.data = new Uint8Array(width * height); - this.image = new AlphaImage({width, height}, this.data); - - this.dashes = {}; + this.image = new AlphaImage({width, height}); + this.positions = {}; } /** @@ -47,10 +39,10 @@ class LineAtlas { getDash(dasharray: Array, round: boolean) { const key = this.getKey(dasharray, round); - if (!this.dashes[key]) { - this.dashes[key] = this.addDash(dasharray, round); + if (!this.positions[key]) { + this.positions[key] = this.addDash(dasharray, round); } - return this.dashes[key]; + return this.positions[key]; } getKey(dasharray, round) { @@ -110,7 +102,7 @@ class LineAtlas { signedDistance = halfStretch - Math.sqrt(minDist * minDist + distMiddle * distMiddle); } - this.data[index + x] = Math.max(0, Math.min(255, signedDistance + 128)); + this.image.data[index + x] = Math.max(0, Math.min(255, signedDistance + 128)); } } } @@ -153,7 +145,7 @@ class LineAtlas { const minDist = Math.min(distLeft, distRight); const signedDistance = range.isDash ? minDist : -minDist; - this.data[index + x] = Math.max(0, Math.min(255, signedDistance + 128)); + this.image.data[index + x] = Math.max(0, Math.min(255, signedDistance + 128)); } } @@ -199,31 +191,11 @@ class LineAtlas { }; this.nextRow += height; - this.dirty = true; return dashEntry; } - - bind(context: Context) { - const gl = context.gl; - if (!this.texture) { - this.texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, this.width, this.height, 0, gl.ALPHA, gl.UNSIGNED_BYTE, this.data); - - } else { - gl.bindTexture(gl.TEXTURE_2D, this.texture); - - if (this.dirty) { - this.dirty = false; - gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.width, this.height, gl.ALPHA, gl.UNSIGNED_BYTE, this.data); - } - } - } } +register('LineAtlas', LineAtlas); + export default LineAtlas; diff --git a/src/source/tile.js b/src/source/tile.js index 5e055046269..9c3b03295db 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -215,8 +215,8 @@ class Tile { if (data.glyphAtlasImage) { this.glyphAtlasImage = data.glyphAtlasImage; } - if (data.lineAtlasImage) { - this.lineAtlasImage = data.lineAtlasImage; + if (data.lineAtlas) { + this.lineAtlas = data.lineAtlas; } } @@ -231,14 +231,18 @@ class Tile { } this.buckets = {}; - if (this.imageAtlasTexture) { - this.imageAtlasTexture.destroy(); - } - if (this.imageAtlas) { this.imageAtlas = null; } + if (this.lineAtlas) { + this.lineAtlas = null; + } + + if (this.imageAtlasTexture) { + this.imageAtlasTexture.destroy(); + } + if (this.glyphAtlasTexture) { this.glyphAtlasTexture.destroy(); } @@ -284,9 +288,10 @@ class Tile { this.glyphAtlasImage = null; } - if (this.lineAtlasImage) { - this.lineAtlasTexture = new Texture(context, this.lineAtlasImage, gl.ALPHA); - this.lineAtlasImage = null; + if (this.lineAtlas && !this.lineAtlas.uploaded) { + console.log(this.lineAtlas); + this.lineAtlasTexture = new Texture(context, this.lineAtlas.image, gl.ALPHA); + this.lineAtlas.uploaded = true; } } diff --git a/src/source/worker_tile.js b/src/source/worker_tile.js index 45fc2aa64d1..459f5b0dfb9 100644 --- a/src/source/worker_tile.js +++ b/src/source/worker_tile.js @@ -243,7 +243,7 @@ class WorkerTile { featureIndex, collisionBoxArray: this.collisionBoxArray, glyphAtlasImage: glyphAtlas.image, - lineAtlasImage: lineAtlas.image, + lineAtlas, imageAtlas, // Only used for benchmarking: glyphMap: this.returnDependencies ? glyphMap : null, From 2383ac5f4d18fe09a68f1a77dba045a157cd55e4 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 16 Apr 2021 20:19:33 +0300 Subject: [PATCH 09/29] fix constant dasharrays --- src/data/bucket/line_bucket.js | 22 ++++------------------ src/source/tile.js | 1 - 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 6781e720f29..c628e742c9e 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -169,8 +169,6 @@ class LineBucket implements Bucket { }); } - const hasDataDrivenDashes = this.hasDataDrivenDashes(); - for (const bucketFeature of bucketFeatures) { const {geometry, index, sourceLayerIndex} = bucketFeature; @@ -181,9 +179,7 @@ class LineBucket implements Bucket { this.patternFeatures.push(patternBucketFeature); } else { - if (hasDataDrivenDashes) { - this.addFeatureDashes(bucketFeature, options); - } + this.addFeatureDashes(bucketFeature, options); this.addFeature(bucketFeature, geometry, index, canonical, options.lineAtlas.positions); } @@ -227,23 +223,13 @@ class LineBucket implements Bucket { // save positions for paint array feature.patterns[layer.id] = {min, mid, max}; - } - } - - } - - hasDataDrivenDashes() { - for (const layer of this.layers) { - const dashProperty = layer.paint.get('line-dasharray'); - const capProperty = layer.layout.get('line-cap'); - if (!dashProperty || typeof (dashProperty.isConstant) !== 'function') continue; // temporary - if (!dashProperty.isConstant() || !capProperty.isConstant()) { - return true; + } else if (dashPropertyValue.value) { + lineAtlas.getDash(dashPropertyValue.value.from, capPropertyValue.value); + lineAtlas.getDash(dashPropertyValue.value.to, capPropertyValue.value); } } - return false; } update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[_: string]: ImagePosition}) { diff --git a/src/source/tile.js b/src/source/tile.js index 9c3b03295db..2bfac61bb9d 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -289,7 +289,6 @@ class Tile { } if (this.lineAtlas && !this.lineAtlas.uploaded) { - console.log(this.lineAtlas); this.lineAtlasTexture = new Texture(context, this.lineAtlas.image, gl.ALPHA); this.lineAtlas.uploaded = true; } From 3171140d3023163cd9495fdcff0ea2ad201cbce7 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Sat, 17 Apr 2021 11:50:31 +0300 Subject: [PATCH 10/29] dasharray fixes --- debug/dasharray.html | 115 ++++++++++----------------------- src/data/bucket/line_bucket.js | 5 +- src/render/draw_line.js | 5 +- src/style-spec/types.js | 2 +- 4 files changed, 42 insertions(+), 85 deletions(-) diff --git a/debug/dasharray.html b/debug/dasharray.html index 3cb4c631dee..be1b7d2590d 100644 --- a/debug/dasharray.html +++ b/debug/dasharray.html @@ -22,93 +22,48 @@ container: 'map', zoom: 4, center: [0, 0], - style: { sources: {}, version: 8, layers: [] } + style: {sources: {}, version: 8, layers: []} }); map.on('load', () => { map.addSource('geojson', { - "type": "geojson", - "data": { - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": { - "property": 1 - }, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -10, - -5 - ], - [ - 10, - -5 - ] - ] - } - }, - { - "type": "Feature", - "properties": { - "property": 2 - }, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -10, - 0 - ], - [ - 10, - 0 - ] - ] - } - }, - { - "type": "Feature", - "properties": { - "property": 3 - }, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -10, - 5 - ], - [ - 10, - 5 - ] - ] - } - } - ] - } + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "properties": {"property": 1}, + "geometry": {"type": "LineString", "coordinates": [[-10, -5], [10, -5]]} + }, { + "type": "Feature", + "properties": {"property": 2}, + "geometry": {"type": "LineString", "coordinates": [[-10, 0], [10, 0]]} + }, { + "type": "Feature", + "properties": {"property": 3}, + "geometry": {"type": "LineString", "coordinates": [[-10, 5], [10, 5]]} + }] + } }); map.addLayer({ - "id": "dash-dds", - "type": "line", - "source": "geojson", - "paint": { - "line-width": 10, - "line-dasharray": [ - "match", ["get", "property"], - 1, ["literal", [1, 2]], - 2, ["literal", [2, 2]], - 3, ["literal", [3, 2]], - ["literal", [1, 1]] - ] - }, - "layout": { - "line-cap": ["match", ["get", "property"], 2, "round", "butt"] - } + "id": "dash-dds", + "type": "line", + "source": "geojson", + "paint": { + "line-width": 10, + // "line-dasharray": [1, 2], + "line-dasharray": [ + "match", ["get", "property"], + 1, ["literal", [1, 2]], + 2, ["literal", [2, 2]], + 3, ["literal", [3, 2]], + ["literal", [1, 1]] + ] + }, + "layout": { + "line-cap": ["match", ["get", "property"], 2, "round", "butt"] + } }); }); diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index c628e742c9e..f68efba4e66 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -225,8 +225,9 @@ class LineBucket implements Bucket { feature.patterns[layer.id] = {min, mid, max}; } else if (dashPropertyValue.value) { - lineAtlas.getDash(dashPropertyValue.value.from, capPropertyValue.value); - lineAtlas.getDash(dashPropertyValue.value.to, capPropertyValue.value); + const round = capPropertyValue.value === 'round'; + lineAtlas.getDash(dashPropertyValue.value.from, round); + lineAtlas.getDash(dashPropertyValue.value.to, round); } } diff --git a/src/render/draw_line.js b/src/render/draw_line.js index cb1cfe975a9..da3b462da2d 100644 --- a/src/render/draw_line.js +++ b/src/render/draw_line.js @@ -69,8 +69,9 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay const constantCap = capProperty.constantOr(null); if (constantDash && constantCap && tile.lineAtlas) { - const posTo = tile.lineAtlas.getDash(constantDash.to, constantCap); - const posFrom = tile.lineAtlas.getDash(constantDash.from, constantCap); + const round = constantCap === 'round'; + const posTo = tile.lineAtlas.getDash(constantDash.to, round); + const posFrom = tile.lineAtlas.getDash(constantDash.from, round); if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom); } diff --git a/src/style-spec/types.js b/src/style-spec/types.js index ff6ef534159..0269f8c7819 100644 --- a/src/style-spec/types.js +++ b/src/style-spec/types.js @@ -213,7 +213,7 @@ export type LineLayerSpecification = {| "line-gap-width"?: DataDrivenPropertyValueSpecification, "line-offset"?: DataDrivenPropertyValueSpecification, "line-blur"?: DataDrivenPropertyValueSpecification, - "line-dasharray"?: PropertyValueSpecification>, + "line-dasharray"?: DataDrivenPropertyValueSpecification>, "line-pattern"?: DataDrivenPropertyValueSpecification, "line-gradient"?: ExpressionSpecification |} From fb5f5529575309f69a24370c697a54f06a2f2728 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Sat, 17 Apr 2021 12:53:14 +0300 Subject: [PATCH 11/29] more dasharray fixes --- src/data/bucket.js | 4 +++- src/data/bucket/line_bucket.js | 27 +++++++++++++++++++-------- src/data/program_configuration.js | 16 +++++----------- src/render/draw_line.js | 7 ++++--- src/render/line_atlas.js | 4 +++- src/source/tile.js | 3 +++ src/source/worker_source.js | 3 ++- 7 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/data/bucket.js b/src/data/bucket.js index 332f635da8d..b2c5c1a47d0 100644 --- a/src/data/bucket.js +++ b/src/data/bucket.js @@ -7,6 +7,7 @@ import type FeatureIndex from './feature_index.js'; import type Context from '../gl/context.js'; import type {FeatureStates} from '../source/source_state.js'; import type {ImagePosition} from '../render/image_atlas.js'; +import type LineAtlas from '../render/line_atlas.js'; import type {CanonicalTileID} from '../source/tile_id.js'; export type BucketParameters = { @@ -26,7 +27,8 @@ export type PopulateParameters = { iconDependencies: {}, patternDependencies: {}, glyphDependencies: {}, - availableImages: Array + availableImages: Array, + lineAtlas: LineAtlas } export type IndexedFeature = { diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index f68efba4e66..0bd47407136 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -172,6 +172,8 @@ class LineBucket implements Bucket { for (const bucketFeature of bucketFeatures) { const {geometry, index, sourceLayerIndex} = bucketFeature; + this.addFeatureDashes(bucketFeature, options); + if (this.hasPattern) { const patternBucketFeature = addPatternDependencies('line', this.layers, bucketFeature, this.zoom, options); // pattern features are added only once the pattern is loaded into the image atlas @@ -179,7 +181,6 @@ class LineBucket implements Bucket { this.patternFeatures.push(patternBucketFeature); } else { - this.addFeatureDashes(bucketFeature, options); this.addFeature(bucketFeature, geometry, index, canonical, options.lineAtlas.positions); } @@ -197,11 +198,19 @@ class LineBucket implements Bucket { const capPropertyValue = layer.layout.get('line-cap').value; if (dashPropertyValue.kind !== 'constant' || capPropertyValue.kind !== 'constant') { - const minDashArray = dashPropertyValue.evaluate({zoom: zoom - 1}, feature); - const midDashArray = dashPropertyValue.evaluate({zoom}, feature); - const maxDashArray = dashPropertyValue.evaluate({zoom: zoom + 1}, feature); + let minDashArray, midDashArray, maxDashArray, minRound, midRound, maxRound; - let minRound, midRound, maxRound; + if (dashPropertyValue.kind === 'constant') { + const constDash = dashPropertyValue.value; + if (!constDash) continue; + minDashArray = midDashArray = constDash.from; + maxDashArray = constDash.to; + + } else { + minDashArray = dashPropertyValue.evaluate({zoom: zoom - 1}, feature); + midDashArray = dashPropertyValue.evaluate({zoom}, feature); + maxDashArray = dashPropertyValue.evaluate({zoom: zoom + 1}, feature); + } if (capPropertyValue.kind === 'constant') { minRound = midRound = maxRound = capPropertyValue.value === 'round'; @@ -224,10 +233,12 @@ class LineBucket implements Bucket { // save positions for paint array feature.patterns[layer.id] = {min, mid, max}; - } else if (dashPropertyValue.value) { + } else { const round = capPropertyValue.value === 'round'; - lineAtlas.getDash(dashPropertyValue.value.from, round); - lineAtlas.getDash(dashPropertyValue.value.to, round); + const constDash = dashPropertyValue.value; + if (!constDash) continue; + lineAtlas.getDash(constDash.from, round); + lineAtlas.getDash(constDash.to, round); } } diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 0326cc02c98..13beb29d9b1 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -321,11 +321,9 @@ class CrossFadedCompositeBinder implements AttributeBinder { this.zoom = zoom; this.layerId = layerId; - this.patternAttributes = type === 'array' ? - dashAttributes : - patternAttributes; + this.paintVertexAttributes = (type === 'array' ? dashAttributes : patternAttributes).members; for (let i = 0; i < names.length; ++i) { - assert(`a_${names[i]}` === this.patternAttributes.members[i].name); + assert(`a_${names[i]}` === this.paintVertexAttributes[i].name); } this.zoomInPaintVertexArray = new PaintVertexArray(); @@ -373,8 +371,8 @@ class CrossFadedCompositeBinder implements AttributeBinder { upload(context: Context) { if (this.zoomInPaintVertexArray && this.zoomInPaintVertexArray.arrayBuffer && this.zoomOutPaintVertexArray && this.zoomOutPaintVertexArray.arrayBuffer) { - this.zoomInPaintVertexBuffer = context.createVertexBuffer(this.zoomInPaintVertexArray, this.patternAttributes.members, this.expression.isStateDependent); - this.zoomOutPaintVertexBuffer = context.createVertexBuffer(this.zoomOutPaintVertexArray, this.patternAttributes.members, this.expression.isStateDependent); + this.zoomInPaintVertexBuffer = context.createVertexBuffer(this.zoomInPaintVertexArray, this.paintVertexAttributes, this.expression.isStateDependent); + this.zoomOutPaintVertexBuffer = context.createVertexBuffer(this.zoomOutPaintVertexArray, this.paintVertexAttributes, this.expression.isStateDependent); } } @@ -511,14 +509,10 @@ export default class ProgramConfiguration { const result = []; for (const property in this.binders) { const binder = this.binders[property]; - if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder) { + if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder || binder instanceof CrossFadedCompositeBinder) { for (let i = 0; i < binder.paintVertexAttributes.length; i++) { result.push(binder.paintVertexAttributes[i].name); } - } else if (binder instanceof CrossFadedCompositeBinder) { - for (let i = 0; i < binder.patternAttributes.members.length; i++) { - result.push(binder.patternAttributes.members[i].name); - } } } return result; diff --git a/src/render/draw_line.js b/src/render/draw_line.js index da3b462da2d..c4fdb7bcdfd 100644 --- a/src/render/draw_line.js +++ b/src/render/draw_line.js @@ -66,12 +66,13 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay } const constantDash = dasharrayProperty.constantOr(null); - const constantCap = capProperty.constantOr(null); + const constantCap = capProperty.constantOr((null: any)); if (constantDash && constantCap && tile.lineAtlas) { + const atlas = tile.lineAtlas; const round = constantCap === 'round'; - const posTo = tile.lineAtlas.getDash(constantDash.to, round); - const posFrom = tile.lineAtlas.getDash(constantDash.from, round); + const posTo = atlas.getDash(constantDash.to, round); + const posFrom = atlas.getDash(constantDash.from, round); if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom); } diff --git a/src/render/line_atlas.js b/src/render/line_atlas.js index 9800fb98b90..c8d05d459e9 100644 --- a/src/render/line_atlas.js +++ b/src/render/line_atlas.js @@ -19,6 +19,7 @@ class LineAtlas { nextRow: number; image: AlphaImage; positions: {[_: string]: any}; + uploaded: boolean; constructor(width: number, height: number) { this.width = width; @@ -26,6 +27,7 @@ class LineAtlas { this.nextRow = 0; this.image = new AlphaImage({width, height}); this.positions = {}; + this.uploaded = false; } /** @@ -45,7 +47,7 @@ class LineAtlas { return this.positions[key]; } - getKey(dasharray, round) { + getKey(dasharray: Array, round: boolean): string { return dasharray.join(',') + String(round); } diff --git a/src/source/tile.js b/src/source/tile.js index 2bfac61bb9d..a29dc1629ee 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -26,6 +26,7 @@ import type Actor from '../util/actor.js'; import type DEMData from '../data/dem_data.js'; import type {AlphaImage} from '../util/image.js'; import type ImageAtlas from '../render/image_atlas.js'; +import type LineAtlas from '../render/line_atlas.js'; import type ImageManager from '../render/image_manager.js'; import type Context from '../gl/context.js'; import type {OverscaledTileID} from './tile_id.js'; @@ -62,6 +63,8 @@ class Tile { latestRawTileData: ?ArrayBuffer; imageAtlas: ?ImageAtlas; imageAtlasTexture: Texture; + lineAtlas: ?LineAtlas; + lineAtlasTexture: Texture; glyphAtlasImage: ?AlphaImage; glyphAtlasTexture: Texture; expirationTime: any; diff --git a/src/source/worker_source.js b/src/source/worker_source.js index 8d7f79b0d47..9c33bf574db 100644 --- a/src/source/worker_source.js +++ b/src/source/worker_source.js @@ -4,6 +4,7 @@ import type {RequestParameters} from '../util/ajax.js'; import type {RGBAImage, AlphaImage} from '../util/image.js'; import type {GlyphPositions} from '../render/glyph_atlas.js'; import type ImageAtlas from '../render/image_atlas.js'; +import type LineAtlas from '../render/line_atlas.js'; import type {OverscaledTileID} from './tile_id.js'; import type {Bucket} from '../data/bucket.js'; import type FeatureIndex from '../data/feature_index.js'; @@ -52,7 +53,7 @@ export type WorkerTileResult = { buckets: Array, imageAtlas: ImageAtlas, glyphAtlasImage: AlphaImage, - lineAtlasImage: AlphaImage, + lineAtlas: LineAtlas, featureIndex: FeatureIndex, collisionBoxArray: CollisionBoxArray, rawTileData?: ArrayBuffer, From 54cbbe09c36680e0e3b70e7070e4c90d908449e1 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Mon, 19 Apr 2021 16:55:35 +0300 Subject: [PATCH 12/29] fix line atlas unit tests --- test/unit/render/line_atlas.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/unit/render/line_atlas.test.js b/test/unit/render/line_atlas.test.js index ef607b93ab6..1f2f2e4abb7 100644 --- a/test/unit/render/line_atlas.test.js +++ b/test/unit/render/line_atlas.test.js @@ -5,43 +5,43 @@ test('LineAtlas', (t) => { const lineAtlas = new LineAtlas(64, 64); t.test('round [0, 0]', (t) => { const entry = lineAtlas.addDash([0, 0], true); - t.equal(entry.width, 0); + t.equal(entry.br[0], 0); t.end(); }); t.test('round [1, 0]', (t) => { const entry = lineAtlas.addDash([1, 0], true); - t.equal(entry.width, 1); + t.equal(entry.br[0], 1); t.end(); }); t.test('round [0, 1]', (t) => { const entry = lineAtlas.addDash([0, 1], true); - t.equal(entry.width, 1); + t.equal(entry.br[0], 1); t.end(); }); t.test('odd round [1, 2, 1]', (t) => { const entry = lineAtlas.addDash([1, 2, 1], true); - t.equal(entry.width, 4); + t.equal(entry.br[0], 4); t.end(); }); t.test('regular [0, 0]', (t) => { const entry = lineAtlas.addDash([0, 0], false); - t.equal(entry.width, 0); + t.equal(entry.br[0], 0); t.end(); }); t.test('regular [1, 0]', (t) => { const entry = lineAtlas.addDash([1, 0], false); - t.equal(entry.width, 1); + t.equal(entry.br[0], 1); t.end(); }); t.test('regular [0, 1]', (t) => { const entry = lineAtlas.addDash([0, 1], false); - t.equal(entry.width, 1); + t.equal(entry.br[0], 1); t.end(); }); t.test('odd regular [1, 2, 1]', (t) => { const entry = lineAtlas.addDash([1, 2, 1], false); - t.equal(entry.width, 4); + t.equal(entry.br[0], 4); t.end(); }); t.end(); From c5ed1c8f06ed17ce051578b72378ee7a6e9b11a6 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Mon, 19 Apr 2021 17:12:50 +0300 Subject: [PATCH 13/29] more unit test fixes --- src/style-spec/reference/v8.json | 7 ++++++- test/unit/style-spec/fixture/functions.output.json | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index bcb23cc9261..1ff60375e7d 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -884,6 +884,9 @@ "android": "2.0.1", "ios": "2.0.0", "macos": "0.1.0" + }, + "data-driven styling": { + "js": "2.3.0" } }, "expression": { @@ -4524,7 +4527,9 @@ "ios": "2.0.0", "macos": "0.1.0" }, - "data-driven styling": {} + "data-driven styling": { + "js": "2.3.0" + } }, "expression": { "interpolated": false, diff --git a/test/unit/style-spec/fixture/functions.output.json b/test/unit/style-spec/fixture/functions.output.json index 580f30573a8..9461099bd21 100644 --- a/test/unit/style-spec/fixture/functions.output.json +++ b/test/unit/style-spec/fixture/functions.output.json @@ -28,7 +28,7 @@ "line": 75 }, { - "message": "layers[6].paint.line-dasharray.stops[0][0]: number expected, string found", + "message": "layers[6].paint.line-dasharray.stops[0][0]: number expected, string found\nIf you intended to use a categorical function, specify `\"type\": \"categorical\"`.", "line": 89 }, { From 9756f3a187df9a9eb3b033c2669f996f7a78ad60 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Mon, 19 Apr 2021 17:14:45 +0300 Subject: [PATCH 14/29] fix remaining render test --- src/render/draw_line.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/draw_line.js b/src/render/draw_line.js index c4fdb7bcdfd..a8a95a52256 100644 --- a/src/render/draw_line.js +++ b/src/render/draw_line.js @@ -68,7 +68,7 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay const constantDash = dasharrayProperty.constantOr(null); const constantCap = capProperty.constantOr((null: any)); - if (constantDash && constantCap && tile.lineAtlas) { + if (!image && constantDash && constantCap && tile.lineAtlas) { const atlas = tile.lineAtlas; const round = constantCap === 'round'; const posTo = atlas.getDash(constantDash.to, round); From 7057bfb3dce76432543b622c779f17445334300e Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Mon, 19 Apr 2021 17:23:48 +0300 Subject: [PATCH 15/29] one more unit test fix --- .../unit/style-spec/fixture/functions.output-api-supported.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/style-spec/fixture/functions.output-api-supported.json b/test/unit/style-spec/fixture/functions.output-api-supported.json index 580f30573a8..9461099bd21 100644 --- a/test/unit/style-spec/fixture/functions.output-api-supported.json +++ b/test/unit/style-spec/fixture/functions.output-api-supported.json @@ -28,7 +28,7 @@ "line": 75 }, { - "message": "layers[6].paint.line-dasharray.stops[0][0]: number expected, string found", + "message": "layers[6].paint.line-dasharray.stops[0][0]: number expected, string found\nIf you intended to use a categorical function, specify `\"type\": \"categorical\"`.", "line": 89 }, { From 4d7ddbfb0bd7b728b15f5926365455965e3908a0 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 21 Apr 2021 16:00:23 +0300 Subject: [PATCH 16/29] optimize dash buffer layout --- src/data/array_types.js | 41 +++++++++++++++++++++++++++++- src/data/bucket/dash_attributes.js | 7 ++--- src/data/program_configuration.js | 27 +++++++++----------- src/render/line_atlas.js | 11 ++++---- 4 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/data/array_types.js b/src/data/array_types.js index 7031bf4b54d..3e673069f71 100644 --- a/src/data/array_types.js +++ b/src/data/array_types.js @@ -188,6 +188,44 @@ class StructArrayLayout10ui20 extends StructArray { StructArrayLayout10ui20.prototype.bytesPerElement = 20; register('StructArrayLayout10ui20', StructArrayLayout10ui20); +/** + * Implementation of the StructArray layout: + * [0]: Uint16[8] + * + * @private + */ +class StructArrayLayout8ui16 extends StructArray { + uint8: Uint8Array; + uint16: Uint16Array; + + _refreshViews() { + this.uint8 = new Uint8Array(this.arrayBuffer); + this.uint16 = new Uint16Array(this.arrayBuffer); + } + + emplaceBack(v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number) { + const i = this.length; + this.resize(i + 1); + return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7); + } + + emplace(i: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number) { + const o2 = i * 8; + this.uint16[o2 + 0] = v0; + this.uint16[o2 + 1] = v1; + this.uint16[o2 + 2] = v2; + this.uint16[o2 + 3] = v3; + this.uint16[o2 + 4] = v4; + this.uint16[o2 + 5] = v5; + this.uint16[o2 + 6] = v6; + this.uint16[o2 + 7] = v7; + return i; + } +} + +StructArrayLayout8ui16.prototype.bytesPerElement = 16; +register('StructArrayLayout8ui16', StructArrayLayout8ui16); + /** * Implementation of the StructArray layout: * [0]: Int16[4] @@ -1104,6 +1142,7 @@ export { StructArrayLayout2i4ub1f12, StructArrayLayout2f8, StructArrayLayout10ui20, + StructArrayLayout8ui16, StructArrayLayout4i4ui4i24, StructArrayLayout3f12, StructArrayLayout1ul4, @@ -1129,7 +1168,7 @@ export { StructArrayLayout2i4ub1f12 as LineLayoutArray, StructArrayLayout2f8 as LineExtLayoutArray, StructArrayLayout10ui20 as PatternLayoutArray, - StructArrayLayout10ui20 as DashLayoutArray, + StructArrayLayout8ui16 as DashLayoutArray, StructArrayLayout4i4ui4i24 as SymbolLayoutArray, StructArrayLayout3f12 as SymbolDynamicLayoutArray, StructArrayLayout1ul4 as SymbolOpacityArray, diff --git a/src/data/bucket/dash_attributes.js b/src/data/bucket/dash_attributes.js index 86892cda7db..1b0520cf47a 100644 --- a/src/data/bucket/dash_attributes.js +++ b/src/data/bucket/dash_attributes.js @@ -2,11 +2,8 @@ import {createLayout} from '../../util/struct_array.js'; const dashAttributes = createLayout([ - // [tl.x, tl.y, br.x, br.y] - {name: 'a_dash_to', components: 4, type: 'Uint16'}, - {name: 'a_dash_from', components: 4, type: 'Uint16'}, - {name: 'a_pixel_ratio_to', components: 1, type: 'Uint16'}, - {name: 'a_pixel_ratio_from', components: 1, type: 'Uint16'}, + {name: 'a_dash_to', components: 4, type: 'Uint16'}, // [x, y, width, unused] + {name: 'a_dash_from', components: 4, type: 'Uint16'} ]); export default dashAttributes; diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 13beb29d9b1..08756e5976b 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -146,9 +146,9 @@ class CrossFadedConstantBinder implements UniformBinder { } getBinding(context: Context, location: WebGLUniformLocation, name: string): $Shape> { - if (name === 'u_pattern_from' || name === 'u_pattern_to') return new Uniform4f(context, location); - if (name === 'u_dash_from' || name === 'u_dash_to') return new Uniform4f(context, location); - return new Uniform1f(context, location); + return name === 'u_pattern_from' || name === 'u_pattern_to' || name === 'u_dash_from' || name === 'u_dash_to' ? + new Uniform4f(context, location) : + new Uniform1f(context, location); } } @@ -354,21 +354,18 @@ class CrossFadedCompositeBinder implements AttributeBinder { // we're cross-fading to at layout time. In order to keep vertex attributes to a minimum and not pass // unnecessary vertex data to the shaders, we determine which to upload at draw time. for (let i = start; i < end; i++) { - this.zoomInPaintVertexArray.emplace(i, - imageMid.tl[0], imageMid.tl[1], imageMid.br[0], imageMid.br[1], - imageMin.tl[0], imageMin.tl[1], imageMin.br[0], imageMin.br[1], - imageMid.pixelRatio, - imageMin.pixelRatio, - ); - this.zoomOutPaintVertexArray.emplace(i, - imageMid.tl[0], imageMid.tl[1], imageMid.br[0], imageMid.br[1], - imageMax.tl[0], imageMax.tl[1], imageMax.br[0], imageMax.br[1], - imageMid.pixelRatio, - imageMax.pixelRatio, - ); + this._setPaintValue(this.zoomInPaintVertexArray, i, imageMid, imageMin); + this._setPaintValue(this.zoomOutPaintVertexArray, i, imageMid, imageMax); } } + _setPaintValue(array, i, posA, posB) { + array.emplace(i, + posA.tl[0], posA.tl[1], posA.br[0], posA.br[1], + posB.tl[0], posB.tl[1], posB.br[0], posB.br[1] + ); + } + upload(context: Context) { if (this.zoomInPaintVertexArray && this.zoomInPaintVertexArray.arrayBuffer && this.zoomOutPaintVertexArray && this.zoomOutPaintVertexArray.arrayBuffer) { this.zoomInPaintVertexBuffer = context.createVertexBuffer(this.zoomInPaintVertexArray, this.paintVertexAttributes, this.expression.isStateDependent); diff --git a/src/render/line_atlas.js b/src/render/line_atlas.js index c8d05d459e9..4f62d3188b3 100644 --- a/src/render/line_atlas.js +++ b/src/render/line_atlas.js @@ -186,15 +186,14 @@ class LineAtlas { } } - const dashEntry = { // temp layout - tl: [this.nextRow + n, n], - br: [length, 0], - pixelRatio: 1 - }; + const y = this.nextRow + n; this.nextRow += height; - return dashEntry; + return { + tl: [y, n], + br: [length, 0] + }; } } From 20632febfcf7c5f9fe66c5b125f4034c3cc602f4 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 21 Apr 2021 17:43:13 +0300 Subject: [PATCH 17/29] fixup line pattern --- src/data/program_configuration.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 08756e5976b..d56a67b55fa 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -362,7 +362,8 @@ class CrossFadedCompositeBinder implements AttributeBinder { _setPaintValue(array, i, posA, posB) { array.emplace(i, posA.tl[0], posA.tl[1], posA.br[0], posA.br[1], - posB.tl[0], posB.tl[1], posB.br[0], posB.br[1] + posB.tl[0], posB.tl[1], posB.br[0], posB.br[1], + posA.pixelRatio, posB.pixelRatio ); } From 87d47486618ca4430db1c01cc12279d76dfe52dc Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 21 Apr 2021 17:55:10 +0300 Subject: [PATCH 18/29] fix constant dash + dds line-cap --- src/data/program_configuration.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index d56a67b55fa..e422480112e 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -425,7 +425,9 @@ export default class ProgramConfiguration { const propType = value.property.specification['property-type']; const isCrossFaded = propType === 'cross-faded' || propType === 'cross-faded-data-driven'; - if (expression.kind === 'constant') { + const sourceException = property === 'line-dasharray' && layer.layout._values['line-cap'].value.kind === 'source'; + + if (expression.kind === 'constant' && !sourceException) { this.binders[property] = isCrossFaded ? new CrossFadedConstantBinder(expression.value, names) : new ConstantBinder(expression.value, names, type); From a228a36ef6d11ff904fc48e234e794cd76c44d9b Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 21 Apr 2021 18:11:57 +0300 Subject: [PATCH 19/29] fixup --- src/data/program_configuration.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index e422480112e..b4d1dc48c07 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -425,7 +425,7 @@ export default class ProgramConfiguration { const propType = value.property.specification['property-type']; const isCrossFaded = propType === 'cross-faded' || propType === 'cross-faded-data-driven'; - const sourceException = property === 'line-dasharray' && layer.layout._values['line-cap'].value.kind === 'source'; + const sourceException = String(property) === 'line-dasharray' && (layer.layout: any).get('line-cap').value.kind === 'source'; if (expression.kind === 'constant' && !sourceException) { this.binders[property] = isCrossFaded ? @@ -433,7 +433,7 @@ export default class ProgramConfiguration { new ConstantBinder(expression.value, names, type); keys.push(`/u_${property}`); - } else if (expression.kind === 'source' || isCrossFaded) { + } else if (expression.kind === 'source' || sourceException || isCrossFaded) { const StructArrayLayout = layoutType(property, type, 'source'); this.binders[property] = isCrossFaded ? new CrossFadedCompositeBinder(expression, names, type, useIntegerZoom, zoom, StructArrayLayout, layer.id) : From 955a96dba7b15553d347074d65d485c042e87304 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Thu, 22 Apr 2021 19:22:07 +0300 Subject: [PATCH 20/29] fix dasharray + composite line-cap --- src/data/program_configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index b4d1dc48c07..333306b3e2e 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -425,7 +425,7 @@ export default class ProgramConfiguration { const propType = value.property.specification['property-type']; const isCrossFaded = propType === 'cross-faded' || propType === 'cross-faded-data-driven'; - const sourceException = String(property) === 'line-dasharray' && (layer.layout: any).get('line-cap').value.kind === 'source'; + const sourceException = String(property) === 'line-dasharray' && (layer.layout: any).get('line-cap').value.kind !== 'constant'; if (expression.kind === 'constant' && !sourceException) { this.binders[property] = isCrossFaded ? From 4f10225f04ac982135da41b3ec6fde7a98023365 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Thu, 22 Apr 2021 20:25:51 +0300 Subject: [PATCH 21/29] fix dasharray flickering when crossing zoom stops --- src/data/bucket/line_bucket.js | 6 ++++-- src/style/properties.js | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 0bd47407136..714c668d807 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -203,8 +203,9 @@ class LineBucket implements Bucket { if (dashPropertyValue.kind === 'constant') { const constDash = dashPropertyValue.value; if (!constDash) continue; - minDashArray = midDashArray = constDash.from; - maxDashArray = constDash.to; + minDashArray = constDash.other || constDash.to; + midDashArray = constDash.to; + maxDashArray = constDash.from; } else { minDashArray = dashPropertyValue.evaluate({zoom: zoom - 1}, feature); @@ -239,6 +240,7 @@ class LineBucket implements Bucket { if (!constDash) continue; lineAtlas.getDash(constDash.from, round); lineAtlas.getDash(constDash.to, round); + if (constDash.other) lineAtlas.getDash(constDash.other, round); } } diff --git a/src/style/properties.js b/src/style/properties.js index 1ebf0e850d5..71084fe9de9 100644 --- a/src/style/properties.js +++ b/src/style/properties.js @@ -27,7 +27,8 @@ type TimePoint = number; export type CrossFaded = { to: T, - from: T + from: T, + other?: T }; /** @@ -634,7 +635,12 @@ export class CrossFadedDataDrivenProperty extends DataDrivenProperty { const z = parameters.zoom; - return z > parameters.zoomHistory.lastIntegerZoom ? {from: min, to: mid} : {from: max, to: mid}; + // ugly hack alert: when evaluating non-constant dashes on the worker side, + // we need all three values to pack into the atlas; the if condition is always false there; + // will be removed after removing cross-fading + return z > parameters.zoomHistory.lastIntegerZoom ? + {from: min, to: mid, other: max} : + {from: max, to: mid, other: min}; } interpolate(a: PossiblyEvaluatedPropertyValue>): PossiblyEvaluatedPropertyValue> { From 8c5b978094ad96593c78dff99f25f3f3a7c10e1c Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 23 Apr 2021 18:17:43 +0300 Subject: [PATCH 22/29] dasharray perf optimizations --- src/data/bucket/line_bucket.js | 99 ++++++++++++++++++++-------------- src/render/line_atlas.js | 8 ++- src/source/worker_tile.js | 3 ++ 3 files changed, 70 insertions(+), 40 deletions(-) diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 714c668d807..7e6296562b9 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -34,6 +34,7 @@ import type IndexBuffer from '../../gl/index_buffer.js'; import type VertexBuffer from '../../gl/vertex_buffer.js'; import type {FeatureStates} from '../../source/source_state.js'; import type {ImagePosition} from '../../render/image_atlas.js'; +import type LineAtlas from '../../render/line_atlas.js'; // NOTE ON EXTRUDE SCALE: // scale the extrusion vector so that the normal length is this value. @@ -169,10 +170,15 @@ class LineBucket implements Bucket { }); } + const {lineAtlas, featureIndex} = options; + const hasFeatureDashes = this.addConstantDashes(lineAtlas); + for (const bucketFeature of bucketFeatures) { const {geometry, index, sourceLayerIndex} = bucketFeature; - this.addFeatureDashes(bucketFeature, options); + if (hasFeatureDashes) { + this.addFeatureDashes(bucketFeature, lineAtlas); + } if (this.hasPattern) { const patternBucketFeature = addPatternDependencies('line', this.layers, bucketFeature, this.zoom, options); @@ -181,67 +187,82 @@ class LineBucket implements Bucket { this.patternFeatures.push(patternBucketFeature); } else { - this.addFeature(bucketFeature, geometry, index, canonical, options.lineAtlas.positions); + this.addFeature(bucketFeature, geometry, index, canonical, lineAtlas.positions); } const feature = features[index].feature; - options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); + featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); } } - addFeatureDashes(feature: BucketFeature, {lineAtlas}: PopulateParameters) { - - const zoom = this.zoom; + addConstantDashes(lineAtlas: LineAtlas) { + let hasFeatureDashes = false; for (const layer of this.layers) { const dashPropertyValue = layer.paint.get('line-dasharray').value; const capPropertyValue = layer.layout.get('line-cap').value; if (dashPropertyValue.kind !== 'constant' || capPropertyValue.kind !== 'constant') { - let minDashArray, midDashArray, maxDashArray, minRound, midRound, maxRound; + hasFeatureDashes = true; - if (dashPropertyValue.kind === 'constant') { - const constDash = dashPropertyValue.value; - if (!constDash) continue; - minDashArray = constDash.other || constDash.to; - midDashArray = constDash.to; - maxDashArray = constDash.from; + } else { + const round = capPropertyValue.value === 'round'; + const constDash = dashPropertyValue.value; + if (!constDash) continue; + lineAtlas.getDash(constDash.from, round); + lineAtlas.getDash(constDash.to, round); + if (constDash.other) lineAtlas.getDash(constDash.other, round); + } + } - } else { - minDashArray = dashPropertyValue.evaluate({zoom: zoom - 1}, feature); - midDashArray = dashPropertyValue.evaluate({zoom}, feature); - maxDashArray = dashPropertyValue.evaluate({zoom: zoom + 1}, feature); - } + return hasFeatureDashes; + } - if (capPropertyValue.kind === 'constant') { - minRound = midRound = maxRound = capPropertyValue.value === 'round'; + addFeatureDashes(feature: BucketFeature, lineAtlas: LineAtlas) { - } else { - minRound = capPropertyValue.evaluate({zoom: zoom - 1}, feature) === 'round'; - midRound = capPropertyValue.evaluate({zoom}, feature) === 'round'; - maxRound = capPropertyValue.evaluate({zoom: zoom + 1}, feature) === 'round'; - } + const zoom = this.zoom; - // add to line atlas - lineAtlas.getDash(minDashArray, minRound); - lineAtlas.getDash(midDashArray, midRound); - lineAtlas.getDash(maxDashArray, maxRound); + for (const layer of this.layers) { + const dashPropertyValue = layer.paint.get('line-dasharray').value; + const capPropertyValue = layer.layout.get('line-cap').value; - const min = lineAtlas.getKey(minDashArray, minRound); - const mid = lineAtlas.getKey(midDashArray, midRound); - const max = lineAtlas.getKey(maxDashArray, maxRound); + if (dashPropertyValue.kind === 'constant' && capPropertyValue.kind === 'constant') continue; - // save positions for paint array - feature.patterns[layer.id] = {min, mid, max}; + let minDashArray, midDashArray, maxDashArray, minRound, midRound, maxRound; - } else { - const round = capPropertyValue.value === 'round'; + if (dashPropertyValue.kind === 'constant') { const constDash = dashPropertyValue.value; if (!constDash) continue; - lineAtlas.getDash(constDash.from, round); - lineAtlas.getDash(constDash.to, round); - if (constDash.other) lineAtlas.getDash(constDash.other, round); + minDashArray = constDash.other || constDash.to; + midDashArray = constDash.to; + maxDashArray = constDash.from; + + } else { + minDashArray = dashPropertyValue.evaluate({zoom: zoom - 1}, feature); + midDashArray = dashPropertyValue.evaluate({zoom}, feature); + maxDashArray = dashPropertyValue.evaluate({zoom: zoom + 1}, feature); } + + if (capPropertyValue.kind === 'constant') { + minRound = midRound = maxRound = capPropertyValue.value === 'round'; + + } else { + minRound = capPropertyValue.evaluate({zoom: zoom - 1}, feature) === 'round'; + midRound = capPropertyValue.evaluate({zoom}, feature) === 'round'; + maxRound = capPropertyValue.evaluate({zoom: zoom + 1}, feature) === 'round'; + } + + // add to line atlas + lineAtlas.getDash(minDashArray, minRound); + lineAtlas.getDash(midDashArray, midRound); + lineAtlas.getDash(maxDashArray, maxRound); + + const min = lineAtlas.getKey(minDashArray, minRound); + const mid = lineAtlas.getKey(midDashArray, midRound); + const max = lineAtlas.getKey(maxDashArray, maxRound); + + // save positions for paint array + feature.patterns[layer.id] = {min, mid, max}; } } diff --git a/src/render/line_atlas.js b/src/render/line_atlas.js index 4f62d3188b3..6da6a40d93e 100644 --- a/src/render/line_atlas.js +++ b/src/render/line_atlas.js @@ -1,6 +1,6 @@ // @flow -import {warnOnce} from '../util/util.js'; +import {warnOnce, nextPowerOfTwo} from '../util/util.js'; import {AlphaImage} from '../util/image.js'; import {register} from '../util/web_worker_transfer.js'; @@ -47,6 +47,12 @@ class LineAtlas { return this.positions[key]; } + trim() { + const width = this.width; + const height = this.height = nextPowerOfTwo(this.nextRow); + this.image.resize({width, height}); + } + getKey(dasharray: Array, round: boolean): string { return dasharray.join(',') + String(round); } diff --git a/src/source/worker_tile.js b/src/source/worker_tile.js index 459f5b0dfb9..6aa2fd0312b 100644 --- a/src/source/worker_tile.js +++ b/src/source/worker_tile.js @@ -84,6 +84,7 @@ class WorkerTile { const buckets: {[_: string]: Bucket} = {}; + // we initially reserve space for a 256x256 atlas, but trim it after processing all line features const lineAtlas = new LineAtlas(256, 256); const options = { @@ -159,6 +160,8 @@ class WorkerTile { } } + lineAtlas.trim(); + let error: ?Error; let glyphMap: ?{[_: string]: {[_: number]: ?StyleGlyph}}; let iconMap: ?{[_: string]: StyleImage}; From 03873b2f41d3d052d6c68b71084878886ee62d99 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 23 Apr 2021 22:57:01 +0300 Subject: [PATCH 23/29] clean up leftovers --- src/render/image_atlas.js | 4 ---- src/shaders/line_sdf.fragment.glsl | 2 +- src/shaders/line_sdf.vertex.glsl | 14 +++++--------- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/render/image_atlas.js b/src/render/image_atlas.js index e1e2165a385..1ac51570034 100644 --- a/src/render/image_atlas.js +++ b/src/render/image_atlas.js @@ -49,10 +49,6 @@ export class ImagePosition { ]; } - get tlbr(): Array { - return this.tl.concat(this.br); - } - get displaySize(): [number, number] { return [ (this.paddedRect.w - IMAGE_PADDING * 2) / this.pixelRatio, diff --git a/src/shaders/line_sdf.fragment.glsl b/src/shaders/line_sdf.fragment.glsl index 32e1ec7b3d4..5e77316b40c 100644 --- a/src/shaders/line_sdf.fragment.glsl +++ b/src/shaders/line_sdf.fragment.glsl @@ -39,7 +39,7 @@ void main() { float sdfdist_a = texture2D(u_image, v_tex_a).a; float sdfdist_b = texture2D(u_image, v_tex_b).a; float sdfdist = mix(sdfdist_a, sdfdist_b, u_mix); - float sdfwidth = min(dash_from.z * u_scale.y, dash_to.z * u_scale.z); // temp layout + float sdfwidth = min(dash_from.z * u_scale.y, dash_to.z * u_scale.z); float sdfgamma = 1.0 / (2.0 * u_device_pixel_ratio) / sdfwidth; alpha *= smoothstep(0.5 - sdfgamma / floorwidth, 0.5 + sdfgamma / floorwidth, sdfdist); diff --git a/src/shaders/line_sdf.vertex.glsl b/src/shaders/line_sdf.vertex.glsl index 169a0ed292c..960a5518711 100644 --- a/src/shaders/line_sdf.vertex.glsl +++ b/src/shaders/line_sdf.vertex.glsl @@ -19,11 +19,9 @@ uniform mediump vec4 u_scale; varying vec2 v_normal; varying vec2 v_width2; -varying float v_linesofar; varying vec2 v_tex_a; varying vec2 v_tex_b; varying float v_gamma_scale; -varying float v_width; #pragma mapbox: define highp vec4 color #pragma mapbox: define lowp float blur @@ -100,15 +98,13 @@ void main() { float toScale = u_scale.z; float texHeight = u_scale.w; - float widthA = dash_from.z * fromScale; // temp layout + float widthA = dash_from.z * fromScale; float widthB = dash_to.z * toScale; - float heightA = dash_from.y / texHeight; - float heightB = dash_to.y / texHeight; + float heightA = dash_from.y; + float heightB = dash_to.y; - v_tex_a = vec2(a_linesofar * (tileZoomRatio / widthA) / floorwidth, -normal.y * heightA + (dash_from.x + 0.5) / texHeight); - v_tex_b = vec2(a_linesofar * (tileZoomRatio / widthB) / floorwidth, -normal.y * heightB + (dash_to.x + 0.5) / texHeight); + v_tex_a = vec2(a_linesofar * (tileZoomRatio / widthA) / floorwidth, (-normal.y * heightA + dash_from.x + 0.5) / texHeight); + v_tex_b = vec2(a_linesofar * (tileZoomRatio / widthB) / floorwidth, (-normal.y * heightB + dash_to.x + 0.5) / texHeight); - v_linesofar = a_linesofar; v_width2 = vec2(outset, inset); - v_width = floorwidth; } From 020260e5a7a2d07c4e88cd5feead9d1a03678277 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 28 Apr 2021 17:41:01 +0300 Subject: [PATCH 24/29] minor optimization in program_configuration --- src/data/program_configuration.js | 98 +++++++++++++++---------------- 1 file changed, 47 insertions(+), 51 deletions(-) diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 333306b3e2e..dc8c0602ee2 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -646,64 +646,60 @@ export class ProgramConfigurationSet { } } -function paintAttributeNames(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'], - 'line-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], - 'fill-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], - 'fill-extrusion-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], - 'line-dasharray': ['dash_to', 'dash_from'] - }; +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'], + 'line-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], + 'fill-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], + 'fill-extrusion-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], + 'line-dasharray': ['dash_to', 'dash_from'] +}; +function paintAttributeNames(property, type) { return attributeNameExceptions[property] || [property.replace(`${type}-`, '').replace(/-/g, '_')]; } -function getLayoutException(property) { - const propertyExceptions = { - 'line-pattern': { - 'source': PatternLayoutArray, - 'composite': PatternLayoutArray - }, - 'fill-pattern': { - 'source': PatternLayoutArray, - 'composite': PatternLayoutArray - }, - 'fill-extrusion-pattern':{ - 'source': PatternLayoutArray, - 'composite': PatternLayoutArray - }, - 'line-dasharray': { // temporary layout - 'source': DashLayoutArray, - 'composite': DashLayoutArray - } - }; +const propertyExceptions = { + 'line-pattern': { + 'source': PatternLayoutArray, + 'composite': PatternLayoutArray + }, + 'fill-pattern': { + 'source': PatternLayoutArray, + 'composite': PatternLayoutArray + }, + 'fill-extrusion-pattern':{ + 'source': PatternLayoutArray, + 'composite': PatternLayoutArray + }, + 'line-dasharray': { // temporary layout + 'source': DashLayoutArray, + 'composite': DashLayoutArray + } +}; - return propertyExceptions[property]; -} +const defaultLayouts = { + 'color': { + 'source': StructArrayLayout2f8, + 'composite': StructArrayLayout4f16 + }, + 'number': { + 'source': StructArrayLayout1f4, + 'composite': StructArrayLayout2f8 + } +}; function layoutType(property, type, binderType) { - const defaultLayouts = { - 'color': { - 'source': StructArrayLayout2f8, - 'composite': StructArrayLayout4f16 - }, - 'number': { - 'source': StructArrayLayout1f4, - 'composite': StructArrayLayout2f8 - } - }; - - const layoutException = getLayoutException(property); + const layoutException = propertyExceptions[property]; return layoutException && layoutException[binderType] || defaultLayouts[type][binderType]; } From d94f9cb80e80384e2728fbc29da0020af802b8f9 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Thu, 29 Apr 2021 18:49:43 +0300 Subject: [PATCH 25/29] add data-driven dash/cap render tests --- .../composite-dash-composite-cap/expected.png | Bin 0 -> 1805 bytes .../composite-dash-composite-cap/style.json | 89 ++++++++++++++++++ .../const-dash-feature-cap/expected.png | Bin 0 -> 951 bytes .../const-dash-feature-cap/style.json | 74 +++++++++++++++ .../feature-dash-const-cap/expected.png | Bin 0 -> 831 bytes .../feature-dash-const-cap/style.json | 81 ++++++++++++++++ .../feature-dash-feature-cap/expected.png | Bin 0 -> 924 bytes .../feature-dash-feature-cap/style.json | 81 ++++++++++++++++ 8 files changed, 325 insertions(+) create mode 100644 test/integration/render-tests/line-dasharray/data-driven/composite-dash-composite-cap/expected.png create mode 100644 test/integration/render-tests/line-dasharray/data-driven/composite-dash-composite-cap/style.json create mode 100644 test/integration/render-tests/line-dasharray/data-driven/const-dash-feature-cap/expected.png create mode 100644 test/integration/render-tests/line-dasharray/data-driven/const-dash-feature-cap/style.json create mode 100644 test/integration/render-tests/line-dasharray/data-driven/feature-dash-const-cap/expected.png create mode 100644 test/integration/render-tests/line-dasharray/data-driven/feature-dash-const-cap/style.json create mode 100644 test/integration/render-tests/line-dasharray/data-driven/feature-dash-feature-cap/expected.png create mode 100644 test/integration/render-tests/line-dasharray/data-driven/feature-dash-feature-cap/style.json diff --git a/test/integration/render-tests/line-dasharray/data-driven/composite-dash-composite-cap/expected.png b/test/integration/render-tests/line-dasharray/data-driven/composite-dash-composite-cap/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..ef16adf79d4a90711fe005b305ae7c25bc3896c7 GIT binary patch literal 1805 zcmV+o2lDudP)u|>FHC|RWpmAskXo@qx~1a!cL2<1N`LV z8O883fqBk_t_Dtb@{D5mV!)@)h5i9n0rYPrSpfV2EaF^fS>Q(}&nSjp00;ygx(qnk z!!V+;0>E$1wd8nxm;(R~!vz3h2Z$XY9s!OmV?;ya5zxZWaRz8{23WC$p})-?G+NBL zAXt_JmT*p_gVxVZo>2{N?FO&@+d6)d>K2E!MN~0f3%4)T>Sa zz-zIdjSm3y%%NU&0svl%^=y0qpl1&CsuKY4TC8W|0{}g9s8^j70BZwl0Zl;h7w`%2 z(U8Nyx@({i=$Z```w&oQdYJbUlw)6GAk#t((V(S6|#Db=ifJ<9WdJJ%FOWry*TWtXR-hwyBB7Jq6$wRjS za|M8df!jQsX6zBb%^mowSpRHsJ*}AQ7htI&$kYXt66d%Uj%uQYl2}R^d+87X;8fre zFK06Tq9NR)Qx;E7>_WQNu%LIQtAJxW2sMlwM~imYRpto*TLQ0kP--^tS_l3r)(;v! z&&v4U0yZClOgjM2c9?e?;DrwSRjls<8@8Btc5w#S2RH#(!7+&rG&c@uOOqIq<@&*a z%OTc(2UG28?|YaWV~+<82bvrJi)m%6n*i+~!=PmbzG!+_)gb>ksf8uzyF{Hvu$H9i0f=-f^8XfKxJaNS5z9uA|WIdNp%=4Q$qN z4JCS%>aHUtfhi1K>vK|(kKAn zGFSfw2LSr#Q>-S=F{FaE_#_*IRg@BDRff5i>-UAwHHMD+!qXJR1Y zQ%pc|SW+xN{B>QsR0|;+mfg%&tU~hZr;3y>5PwySMSY;><1YZP6Yx|9-*Vn^Kv{j; z?!coNe(_1jvSN)b8q^lZ2r`c9+AQfhw625aNM&rQH?Snniwl4PK4A>*!qIymUYTfTq&vaHqtz+E}-0b2pD zl#JWHL55LmdjOA=@GC3$c7yeg0&XZ7w{qswS+JY~oL|D(ZL49y+vQaaGGcT(Nx@Ebjq(SMckTqaCQOo=@ACGLdCFh(fVX54ON6 zKWh8ePF3|7dN5P;sWAA?HIIS&fQ@TFB)J8^s+~%}+BO41)p5RFG3H8Omkhs(f1W4< zKm+i{kUm-_v26CuCM7Ne&PwJeyB4kxa0(J7OcXXPNMYtZli#JNeYcOALa8}a zLAEeNNgySIQnKyU6l%RA8L%I4ck;U&W3!dEF5qI|w1j6P;2pEB&8ggLV)Ty#yCpnj zxdJu!O2fiun?#SQyll@P*;qiC@w)|peRb?yKr!-yWDqrODoCHq)s1ozN@?YS)PhbR zC}>mQdBYb0&2b6UyNX;Q%H0#>e?J5tG0{j_ZPQp|6>ewBNZl8m30-eg?3X*QNQ<`ih v+s&km;}B>(Gi|>H0H)33M&AIylmp;j)FC}b`;oD?00000NkvXXu0mjflN&`# literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/line-dasharray/data-driven/composite-dash-composite-cap/style.json b/test/integration/render-tests/line-dasharray/data-driven/composite-dash-composite-cap/style.json new file mode 100644 index 00000000000..bc95e002e7e --- /dev/null +++ b/test/integration/render-tests/line-dasharray/data-driven/composite-dash-composite-cap/style.json @@ -0,0 +1,89 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 128, + "height": 64 + } + }, + "zoom": 0.1, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {"property": 2}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, -18], [40, -18]] + } + }, + { + "type": "Feature", + "properties": {"property": 3}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, -10], [40, -10]] + } + }, + { + "type": "Feature", + "properties": {"property": 4}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, -2], [40, -2]] + } + }, + { + "type": "Feature", + "properties": {"property": 5}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, 7], [40, 7]] + } + }, + { + "type": "Feature", + "properties": {"property": 6}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, 18], [40, 18]] + } + } + ] + } + } + }, + "layers": [ + { + "id": "road", + "type": "line", + "source": "geojson", + "layout": { + "line-cap": [ + "step", ["zoom"], + ["case", ["==", ["%", ["get", "property"], 2], 0], "butt", "round"], + 1, "butt" + ] + }, + "paint": { + "line-width": ["get", "property"], + "line-dasharray": [ + "step", ["zoom"], + [ + "match", ["get", "property"], + 2, ["literal", [2, 2]], + 3, ["literal", [2, 3]], + 4, ["literal", [2, 4]], + 5, ["literal", [2, 5]], + ["literal", [2, 6]] + ], + 1, ["literal", [1, 1]] + ] + } + } + ] +} diff --git a/test/integration/render-tests/line-dasharray/data-driven/const-dash-feature-cap/expected.png b/test/integration/render-tests/line-dasharray/data-driven/const-dash-feature-cap/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..53b0a629d3bd5df3d95c8340e09e7afbbc26f115 GIT binary patch literal 951 zcmV;o14#UdP)98pq*`t3BLHgBPa41iKmce6 zs4}1)pkXx993VZRX?u)rJAT#>Fb!B-(|#pzMe&nrwXIJU0GYLv*|;j; z{S6$~y!5>@6%YWKLPa$b08uNOnU?^_6e_Bj0Ek-I%)A6ZrchDM1VGfvX67XTGKGq2 zCIF&VHZ!jj0P}#Iz*=C16YCrB5_k$c2EI1fuLm~tVl8*#*#jktHz?9v>-RT$3V?^e z)=Y@{X0L!#z_ps+X5cKaUGau0Fe?29zC;rMyMgPoELPwA9B=~oRq@{kTm}{v#8f<( zYyAYkgahgVU!8u*fEw|fcp zY`grM1sbJ(&r-B{01Vq1^d8s?+(`uYcDBfWbowRhEC410U;)t3OHEP_R1ClkU`wKl z{{S2Z?j_poL6Qsd?}UEqO6LF}X1Yout$;TNkaocp5CFjo?4>6FJcWX{3xMDS_Ri~A&-<};@Dto%9&kMkAeEoB!0N4mT z=snBdWMjL3&H$$YJ0f-$a2{BlIDJ(1|6dp?C7$AF)}Vc?>3 z?yu~JpS#?)J_muTxd51*?aHw|X-jHOwv73_7tGH3wQCb_60lp2vhSWgkAS1I83Mey zgW7{Apnm~D3R#+l04P;1h@SumQpnOY1VE{BLHqV2t55M`;nf#M z+N$3tm|5<$%Ys&4{HXTs>eILs+%IB~(M)NSDBukNB{AhQMhXakF)CIYUsV9Kq3RgL Z`~!8rZeNmGw$}gv002ovPDHLkV1gYKsRsZ6 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/line-dasharray/data-driven/const-dash-feature-cap/style.json b/test/integration/render-tests/line-dasharray/data-driven/const-dash-feature-cap/style.json new file mode 100644 index 00000000000..01db0779b87 --- /dev/null +++ b/test/integration/render-tests/line-dasharray/data-driven/const-dash-feature-cap/style.json @@ -0,0 +1,74 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 128, + "height": 64 + } + }, + "zoom": 0, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {"property": 2}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, -18], [40, -18]] + } + }, + { + "type": "Feature", + "properties": {"property": 3}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, -10], [40, -10]] + } + }, + { + "type": "Feature", + "properties": {"property": 4}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, -2], [40, -2]] + } + }, + { + "type": "Feature", + "properties": {"property": 5}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, 7], [40, 7]] + } + }, + { + "type": "Feature", + "properties": {"property": 6}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, 18], [40, 18]] + } + } + ] + } + } + }, + "layers": [ + { + "id": "road", + "type": "line", + "source": "geojson", + "layout": { + "line-cap": ["case", ["==", ["%", ["get", "property"], 2], 0], "butt", "round"] + }, + "paint": { + "line-width": ["get", "property"], + "line-dasharray": [2, 2] + } + } + ] +} diff --git a/test/integration/render-tests/line-dasharray/data-driven/feature-dash-const-cap/expected.png b/test/integration/render-tests/line-dasharray/data-driven/feature-dash-const-cap/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..7465f4abfab0178bdb8dfbeac9e21ae295d33a76 GIT binary patch literal 831 zcmV-F1Hk-=P)Ou;qTDKLeN%QYj5uluoJ{eRIn2)6twdXSlau6h##P$ zvUoa`JMnz)(~bc5E40!tD!y!V~1}o&fMxxN!Rdpzs8FyC(p=6)xPq04O{`-tJidTm+6APJ~Cm z_kkCV&W0bRoCA&-*Z3-51{K1F|tG7S0%oCh8_0+2cwkuSqZh~`-UtWRx?FbaV6 z^BmnF0D97>><+V1k}la5AT)t$y#7vbi4CdMs9(!%0%xq8GF74iRufpF&+=(T1q8s1 zV%BI%0MwYZ8GRN2Gm2TGDFIMp)@Jls0L&<6jixLBjsp84fqe&F0iQDi-vfl-?#uqL zfZqE2x~c`h*f8kbYC`uY69DUxs_c*dyLqZU0${@d761akz6IO`PDM)md*CwgH1ig4 z2)H5pQT4p_NvZ&uxz)B>6tJ4WTH=tOnF;N4SSk@qT{j7Uo50DQP+v+0-*5( zTj2=+OQFWs1wi8ow!#wtmO_oM3xLKGY=tKPEQK0h7XXbX*b2`Ap!>myx4;D;%m>2J z{~&|b%hhK)27(1ZcNp|~H3WLHGS+{Ft@YnSvJ&;lenm|8!;wFL@ZOs+;tWSH+Wj!2 z*2~o=%p?X6s{&ZJ5Up3PfB>jGfm7WV08%6ba4!+hWe!l%#^^oVMnXq{KkCUU(4TQ zZPcCqzpm4n!G`gh*_lbI|5yrkC;q>3_Zsu}r~4TM4#=^EFg`n?xv7bhf$_my{`39s4{$#a{lN2M*8#tLwlb#G4ZMuE?Vp@> zX}!mEo#FJ=J>dBbbn<3D%hv+6PZj=D8P|8bM;{4M%MoDIzf?oMm^ z|F(|XVrN44ADs(-(3?^20BCTh)cXZ8Om29x7i90q1g2UISK81T8>U%vQQa6@Zc zJWJL?Q+DTd+(FT6e$QIl8KuiKTiosIo2^W{_%FQvQd%CzP;!04buku)^_Q>Ttz^6q zdpIsyfMKs+eED-$hjQNPTt$|KyUY=koqCCvf*y_hWGDI|4U&Eer^1Fr6NO( zdb1rvdt>`Vh8DklC-(g>DGsk?ad^nU&A=!yk16MM!SuyP>RJ4l`<0hBY&{TqvwYR` zsQGT93P#(du8FfcbsyMwMA2SZRDfaPA>Py(3=&>y%8#*e_!+9bR=j1saoOzkS-$u^ z%=fInF}{)eHtW^uJ5sZkhyDNbk|}}xgGj`)8~Ph~7jPe_d~lsZfxSWdfLia|P&N0p zO$;(?=WTepy2QL8xFNKq$~x}u!{|qnYc|#&nK>=Ykzvi@)CB7dMJf}w@%|CoFxmXG z-^`G|+ZfXub)~%5MVT@sXuOs=`Y_PpKdVSzJA(j2t9C%ek5vpz3kq1d_~S(#7(~`N zJbbvCiGyK1(^qk~8*CR051iwQkgip1UiWg!P>x}5z~0+ a@PA(0RTl!Ra<75%A%mx@pUXO@geCxF!kHZa literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/line-dasharray/data-driven/feature-dash-feature-cap/style.json b/test/integration/render-tests/line-dasharray/data-driven/feature-dash-feature-cap/style.json new file mode 100644 index 00000000000..a356b05a3f1 --- /dev/null +++ b/test/integration/render-tests/line-dasharray/data-driven/feature-dash-feature-cap/style.json @@ -0,0 +1,81 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 128, + "height": 64 + } + }, + "zoom": 0, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {"property": 2}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, -18], [40, -18]] + } + }, + { + "type": "Feature", + "properties": {"property": 3}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, -10], [40, -10]] + } + }, + { + "type": "Feature", + "properties": {"property": 4}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, -2], [40, -2]] + } + }, + { + "type": "Feature", + "properties": {"property": 5}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, 7], [40, 7]] + } + }, + { + "type": "Feature", + "properties": {"property": 6}, + "geometry": { + "type": "LineString", + "coordinates": [[-40, 18], [40, 18]] + } + } + ] + } + } + }, + "layers": [ + { + "id": "road", + "type": "line", + "source": "geojson", + "layout": { + "line-cap": ["case", ["==", ["%", ["get", "property"], 2], 0], "butt", "round"] + }, + "paint": { + "line-width": ["get", "property"], + "line-dasharray": [ + "match", ["get", "property"], + 2, ["literal", [2, 2]], + 3, ["literal", [2, 3]], + 4, ["literal", [2, 4]], + 5, ["literal", [2, 5]], + ["literal", [2, 6]] + ] + } + } + ] +} From bc726e94697727c6cd20847a66489c8dedf8995a Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 30 Apr 2021 16:53:49 +0300 Subject: [PATCH 26/29] add a render test for solid line data-driven line-cap --- .../line-cap/data-driven/expected.png | Bin 0 -> 452 bytes .../line-cap/data-driven/style.json | 62 ++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 test/integration/render-tests/line-cap/data-driven/expected.png create mode 100644 test/integration/render-tests/line-cap/data-driven/style.json diff --git a/test/integration/render-tests/line-cap/data-driven/expected.png b/test/integration/render-tests/line-cap/data-driven/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..5d1599bbf279bf986847a5de6e0f1991cac795cc GIT binary patch literal 452 zcmV;#0XzPQP)2w>iw#YzIO7~?zy0nEFzSV;gD zW1NQ|fO&ToD+$12jPno#Fz?P{WiNma3rZkX=S%<@aS%YpoL4CcfB Date: Wed, 5 May 2021 12:29:01 +0300 Subject: [PATCH 27/29] make shaders more consistent for easier porting --- src/render/program/line_program.js | 13 ++++++------- src/shaders/line_sdf.fragment.glsl | 2 +- src/shaders/line_sdf.vertex.glsl | 8 ++++---- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/render/program/line_program.js b/src/render/program/line_program.js index 5d7bdf545e2..caa9fe3dbad 100644 --- a/src/render/program/line_program.js +++ b/src/render/program/line_program.js @@ -5,7 +5,6 @@ import { Uniform1f, Uniform2f, Uniform3f, - Uniform4f, UniformMatrix4f } from '../uniform_binding.js'; import pixelsToTileUnits from '../../source/pixels_to_tile_units.js'; @@ -49,10 +48,11 @@ export type LinePatternUniformsType = {| export type LineSDFUniformsType = {| 'u_matrix': UniformMatrix4f, + 'u_texsize': Uniform2f, 'u_ratio': Uniform1f, 'u_device_pixel_ratio': Uniform1f, 'u_units_to_pixels': Uniform2f, - 'u_scale': Uniform4f, + 'u_scale': Uniform3f, 'u_image': Uniform1i, 'u_mix': Uniform1f |}; @@ -86,11 +86,12 @@ const linePatternUniforms = (context: Context, locations: UniformLocations): Lin const lineSDFUniforms = (context: Context, locations: UniformLocations): LineSDFUniformsType => ({ 'u_matrix': new UniformMatrix4f(context, locations.u_matrix), + 'u_texsize': new Uniform2f(context, locations.u_texsize), 'u_ratio': new Uniform1f(context, locations.u_ratio), 'u_device_pixel_ratio': new Uniform1f(context, locations.u_device_pixel_ratio), 'u_units_to_pixels': new Uniform2f(context, locations.u_units_to_pixels), 'u_image': new Uniform1i(context, locations.u_image), - 'u_scale': new Uniform4f(context, locations.u_scale), + 'u_scale': new Uniform3f(context, locations.u_scale), 'u_mix': new Uniform1f(context, locations.u_mix) }); @@ -159,11 +160,9 @@ const lineSDFUniformValues = ( matrix: ?Float32Array ): UniformValues => { const tileZoomRatio = calculateTileRatio(tile, painter.transform); - - const atlasHeight = tile.lineAtlasTexture.size[1]; - return extend(lineUniformValues(painter, tile, layer, matrix), { - 'u_scale': [tileZoomRatio, crossfade.fromScale, crossfade.toScale, atlasHeight], + 'u_texsize': tile.lineAtlasTexture.size, + 'u_scale': [tileZoomRatio, crossfade.fromScale, crossfade.toScale], 'u_image': 0, 'u_mix': crossfade.t }); diff --git a/src/shaders/line_sdf.fragment.glsl b/src/shaders/line_sdf.fragment.glsl index 5e77316b40c..6abbeb3a562 100644 --- a/src/shaders/line_sdf.fragment.glsl +++ b/src/shaders/line_sdf.fragment.glsl @@ -2,7 +2,7 @@ uniform lowp float u_device_pixel_ratio; uniform sampler2D u_image; uniform float u_mix; -uniform vec4 u_scale; +uniform vec3 u_scale; varying vec2 v_normal; varying vec2 v_width2; diff --git a/src/shaders/line_sdf.vertex.glsl b/src/shaders/line_sdf.vertex.glsl index 960a5518711..20809a6fc4f 100644 --- a/src/shaders/line_sdf.vertex.glsl +++ b/src/shaders/line_sdf.vertex.glsl @@ -15,7 +15,8 @@ uniform mediump float u_ratio; uniform lowp float u_device_pixel_ratio; uniform vec2 u_units_to_pixels; -uniform mediump vec4 u_scale; +uniform vec2 u_texsize; +uniform mediump vec3 u_scale; varying vec2 v_normal; varying vec2 v_width2; @@ -96,15 +97,14 @@ void main() { float tileZoomRatio = u_scale.x; float fromScale = u_scale.y; float toScale = u_scale.z; - float texHeight = u_scale.w; float widthA = dash_from.z * fromScale; float widthB = dash_to.z * toScale; float heightA = dash_from.y; float heightB = dash_to.y; - v_tex_a = vec2(a_linesofar * (tileZoomRatio / widthA) / floorwidth, (-normal.y * heightA + dash_from.x + 0.5) / texHeight); - v_tex_b = vec2(a_linesofar * (tileZoomRatio / widthB) / floorwidth, (-normal.y * heightB + dash_to.x + 0.5) / texHeight); + v_tex_a = vec2(a_linesofar * (tileZoomRatio / widthA) / floorwidth, (-normal.y * heightA + dash_from.x + 0.5) / u_texsize.y); + v_tex_b = vec2(a_linesofar * (tileZoomRatio / widthB) / floorwidth, (-normal.y * heightB + dash_to.x + 0.5) / u_texsize.y); v_width2 = vec2(outset, inset); } From c79f577b7059555c1774f57ba1dbeba6312f031a Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 5 May 2021 14:40:20 +0300 Subject: [PATCH 28/29] rename conflicting shader constant --- src/shaders/line_sdf.vertex.glsl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shaders/line_sdf.vertex.glsl b/src/shaders/line_sdf.vertex.glsl index 20809a6fc4f..941c8111e4a 100644 --- a/src/shaders/line_sdf.vertex.glsl +++ b/src/shaders/line_sdf.vertex.glsl @@ -4,7 +4,7 @@ // there are also "special" normals that have a bigger length (of up to 126 in // this case). // #define scale 63.0 -#define scale 0.015873016 +#define EXTRUDE_SCALE 0.015873016 attribute vec2 a_pos_normal; attribute vec4 a_data; @@ -72,7 +72,7 @@ void main() { // Scale the extrusion vector down to a normal and then up by the line width // of this vertex. - mediump vec2 dist = outset * a_extrude * scale; + mediump vec2 dist = outset * a_extrude * EXTRUDE_SCALE; // Calculate the offset when drawing a line that is to the side of the actual line. // We do this by creating a vector that points towards the extrude, but rotate @@ -80,7 +80,7 @@ void main() { // extrude vector points in another direction. mediump float u = 0.5 * a_direction; mediump float t = 1.0 - abs(u); - mediump vec2 offset2 = offset * a_extrude * scale * normal.y * mat2(t, -u, u, t); + mediump vec2 offset2 = offset * a_extrude * EXTRUDE_SCALE * normal.y * mat2(t, -u, u, t); vec4 projected_extrude = u_matrix * vec4(dist / u_ratio, 0.0, 0.0); gl_Position = u_matrix * vec4(pos + offset2 / u_ratio, 0.0, 1.0) + projected_extrude; From 2a035f3c3977f9d8a56a841804deeabdff3c803c Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Thu, 6 May 2021 12:28:59 +0300 Subject: [PATCH 29/29] fix out of atlas space warning in some cases --- src/data/bucket/line_bucket.js | 13 ++++++------- src/render/line_atlas.js | 11 +++++------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 7e6296562b9..0c620c747dc 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -209,9 +209,9 @@ class LineBucket implements Bucket { const round = capPropertyValue.value === 'round'; const constDash = dashPropertyValue.value; if (!constDash) continue; - lineAtlas.getDash(constDash.from, round); - lineAtlas.getDash(constDash.to, round); - if (constDash.other) lineAtlas.getDash(constDash.other, round); + lineAtlas.addDash(constDash.from, round); + lineAtlas.addDash(constDash.to, round); + if (constDash.other) lineAtlas.addDash(constDash.other, round); } } @@ -252,10 +252,9 @@ class LineBucket implements Bucket { maxRound = capPropertyValue.evaluate({zoom: zoom + 1}, feature) === 'round'; } - // add to line atlas - lineAtlas.getDash(minDashArray, minRound); - lineAtlas.getDash(midDashArray, midRound); - lineAtlas.getDash(maxDashArray, maxRound); + lineAtlas.addDash(minDashArray, minRound); + lineAtlas.addDash(midDashArray, midRound); + lineAtlas.addDash(maxDashArray, maxRound); const min = lineAtlas.getKey(minDashArray, minRound); const mid = lineAtlas.getKey(midDashArray, midRound); diff --git a/src/render/line_atlas.js b/src/render/line_atlas.js index 6da6a40d93e..f8d0fb620e1 100644 --- a/src/render/line_atlas.js +++ b/src/render/line_atlas.js @@ -31,7 +31,7 @@ class LineAtlas { } /** - * Get or create a dash line pattern. + * Get a dash line pattern. * * @param {Array} dasharray * @param {boolean} round whether to add circle caps in between dash segments @@ -40,10 +40,6 @@ class LineAtlas { */ getDash(dasharray: Array, round: boolean) { const key = this.getKey(dasharray, round); - - if (!this.positions[key]) { - this.positions[key] = this.addDash(dasharray, round); - } return this.positions[key]; } @@ -158,6 +154,7 @@ class LineAtlas { } addDash(dasharray: Array, round: boolean) { + const key = this.getKey(dasharray, round); const n = round ? 7 : 0; const height = 2 * n + 1; @@ -196,10 +193,12 @@ class LineAtlas { this.nextRow += height; - return { + const pos = { tl: [y, n], br: [length, 0] }; + this.positions[key] = pos; + return pos; } }