Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Data-driven line-dasharray and line-cap #10591

Merged
merged 29 commits into from
May 6, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
92d3059
WIP data-driven line-dasharray / line-cap (not working yet)
mourner Apr 7, 2021
c2855d5
WIP pass the per-tile line atlas through
mourner Apr 9, 2021
6d3f045
WIP switch to integer coords for line atlas
mourner Apr 12, 2021
dca713a
add a dds dasharray debug page (temp)
mourner Apr 12, 2021
48a6303
fix debug page
mourner Apr 12, 2021
2203861
use different attribute names for dashes to avoid collision
ansis Apr 13, 2021
ff328d8
fix DDS dasharray rendering
mourner Apr 14, 2021
304c290
refactor to support constant patterns
mourner Apr 16, 2021
2383ac5
fix constant dasharrays
mourner Apr 16, 2021
3171140
dasharray fixes
mourner Apr 17, 2021
fb5f552
more dasharray fixes
mourner Apr 17, 2021
54cbbe0
fix line atlas unit tests
mourner Apr 19, 2021
c5ed1c8
more unit test fixes
mourner Apr 19, 2021
9756f3a
fix remaining render test
mourner Apr 19, 2021
7057bfb
one more unit test fix
mourner Apr 19, 2021
4d7ddbf
optimize dash buffer layout
mourner Apr 21, 2021
20632fe
fixup line pattern
mourner Apr 21, 2021
87d4748
fix constant dash + dds line-cap
mourner Apr 21, 2021
a228a36
fixup
mourner Apr 21, 2021
955a96d
fix dasharray + composite line-cap
mourner Apr 22, 2021
4f10225
fix dasharray flickering when crossing zoom stops
mourner Apr 22, 2021
8c5b978
dasharray perf optimizations
mourner Apr 23, 2021
03873b2
clean up leftovers
mourner Apr 23, 2021
020260e
minor optimization in program_configuration
mourner Apr 28, 2021
d94f9cb
add data-driven dash/cap render tests
mourner Apr 29, 2021
bc726e9
add a render test for solid line data-driven line-cap
mourner Apr 30, 2021
bf73eea
make shaders more consistent for easier porting
mourner May 5, 2021
c79f577
rename conflicting shader constant
mourner May 5, 2021
2a035f3
fix out of atlas space warning in some cases
mourner May 6, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build/generate-struct-arrays.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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]);
Expand Down
72 changes: 72 additions & 0 deletions debug/dasharray.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel='stylesheet' href='../dist/mapbox-gl.css' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
</style>
</head>

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

<script src='../dist/mapbox-gl-dev.js'></script>
<script src='../debug/access_token_generated.js'></script>
<script>

var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 4,
center: [0, 0],
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]]}
}]
}
});

map.addLayer({
"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"]
}
});
});

</script>
</body>
</html>
1 change: 1 addition & 0 deletions src/data/array_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion src/data/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<Layer: TypedStyleLayer> = {
Expand All @@ -26,7 +27,8 @@ export type PopulateParameters = {
iconDependencies: {},
patternDependencies: {},
glyphDependencies: {},
availableImages: Array<string>
availableImages: Array<string>,
lineAtlas: LineAtlas
}

export type IndexedFeature = {
Expand Down
12 changes: 12 additions & 0 deletions src/data/bucket/dash_attributes.js
Original file line number Diff line number Diff line change
@@ -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'},
mourner marked this conversation as resolved.
Show resolved Hide resolved
]);

export default dashAttributes;
62 changes: 60 additions & 2 deletions src/data/bucket/line_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,20 +172,78 @@ 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
// so are stored during populate until later updated with positions by tile worker in addFeatures
this.patternFeatures.push(patternBucketFeature);

} else {
this.addFeature(bucketFeature, geometry, index, canonical, {});
this.addFeature(bucketFeature, geometry, index, canonical, options.lineAtlas.positions);
}

const feature = features[index].feature;
options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index);
}
}

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') {
let minDashArray, midDashArray, maxDashArray, 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';

} 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};

} else {
const round = capPropertyValue.value === 'round';
const constDash = dashPropertyValue.value;
if (!constDash) continue;
lineAtlas.getDash(constDash.from, round);
lineAtlas.getDash(constDash.to, round);
}
}

}

update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[_: string]: ImagePosition}) {
if (!this.stateDependentLayers.length) return;
this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions);
Expand Down Expand Up @@ -236,7 +294,7 @@ class LineBucket implements Bucket {
addFeature(feature: BucketFeature, geometry: Array<Array<Point>>, 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);
Expand Down
37 changes: 20 additions & 17 deletions src/data/program_configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -131,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<mixed>, 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<Uniform<any>> {
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);
}
}

Expand Down Expand Up @@ -320,8 +321,9 @@ class CrossFadedCompositeBinder implements AttributeBinder {
this.zoom = zoom;
this.layerId = layerId;

this.paintVertexAttributes = (type === 'array' ? dashAttributes : patternAttributes).members;
for (let i = 0; i < names.length; ++i) {
assert(`a_${names[i]}` === patternAttributes.members[i].name);
assert(`a_${names[i]}` === this.paintVertexAttributes[i].name);
}

this.zoomInPaintVertexArray = new PaintVertexArray();
Expand Down Expand Up @@ -369,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, patternAttributes.members, this.expression.isStateDependent);
this.zoomOutPaintVertexBuffer = context.createVertexBuffer(this.zoomOutPaintVertexArray, 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);
}
}

Expand Down Expand Up @@ -507,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 < patternAttributes.members.length; i++) {
result.push(patternAttributes.members[i].name);
}
}
}
return result;
Expand Down Expand Up @@ -664,14 +662,15 @@ 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': ['dash_to', 'dash_from']
};

return attributeNameExceptions[property] || [property.replace(`${type}-`, '').replace(/-/g, '_')];
}

function getLayoutException(property) {
const propertyExceptions = {
'line-pattern':{
'line-pattern': {
'source': PatternLayoutArray,
'composite': PatternLayoutArray
},
Expand All @@ -682,6 +681,10 @@ function getLayoutException(property) {
'fill-extrusion-pattern':{
'source': PatternLayoutArray,
'composite': PatternLayoutArray
},
'line-dasharray': { // temporary layout
'source': DashLayoutArray,
'composite': DashLayoutArray
}
};

Expand Down
Loading