diff --git a/src/data/bucket/line_attributes_ext.js b/src/data/bucket/line_attributes_ext.js new file mode 100644 index 00000000000..aa754c4c00e --- /dev/null +++ b/src/data/bucket/line_attributes_ext.js @@ -0,0 +1,9 @@ +// @flow +import {createLayout} from '../../util/struct_array'; + +const lineLayoutAttributesExt = createLayout([ + {name: 'a_line_progress', components: 1, type: 'Float32'} +], 4); + +export default lineLayoutAttributesExt; +export const {members, size, alignment} = lineLayoutAttributesExt; diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 280fbcb35d2..e2d7534c06b 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -1,8 +1,9 @@ // @flow -import {LineLayoutArray} from '../array_types'; +import {LineLayoutArray, StructArrayLayout1f4} from '../array_types'; import {members as layoutAttributes} from './line_attributes'; +import {members as layoutAttributesExt} from './line_attributes_ext'; import SegmentVector from '../segment'; import {ProgramConfigurationSet} from '../program_configuration'; import {TriangleIndexArray} from '../index_array_type'; @@ -67,6 +68,11 @@ const LINE_DISTANCE_SCALE = 1 / 2; // The maximum line distance, in tile units, that fits in the buffer. const MAX_LINE_DISTANCE = Math.pow(2, LINE_DISTANCE_BUFFER_BITS - 1) / LINE_DISTANCE_SCALE; +type LineClips = { + clipStart: number; + clipEnd: number; +} + /** * @private */ @@ -75,8 +81,7 @@ class LineBucket implements Bucket { totalDistance: number; maxLineLength: number; scaledDistance: number; - clipStart: number; - clipEnd: number; + lineClips: ?LineClips; e1: number; e2: number; @@ -92,11 +97,14 @@ class LineBucket implements Bucket { layoutVertexArray: LineLayoutArray; layoutVertexBuffer: VertexBuffer; + layoutVertexArray2: StructArrayLayout1f4; + layoutVertexBuffer2: VertexBuffer; indexArray: TriangleIndexArray; indexBuffer: IndexBuffer; hasPattern: boolean; + requiresHighPrecisionLineProgress: boolean; programConfigurations: ProgramConfigurationSet; segments: SegmentVector; uploaded: boolean; @@ -108,9 +116,11 @@ class LineBucket implements Bucket { this.layerIds = this.layers.map(layer => layer.id); this.index = options.index; this.hasPattern = false; + this.requiresHighPrecisionLineProgress = false; this.patternFeatures = []; this.layoutVertexArray = new LineLayoutArray(); + this.layoutVertexArray2 = new StructArrayLayout1f4(); this.indexArray = new TriangleIndexArray(); this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.segments = new SegmentVector(); @@ -160,6 +170,7 @@ class LineBucket implements Bucket { }); } + this.requiresHighPrecisionLineProgress = this._requiresHighPrecisionLineProgress(bucketFeatures); for (const bucketFeature of bucketFeatures) { const {geometry, index, sourceLayerIndex} = bucketFeature; @@ -198,6 +209,9 @@ class LineBucket implements Bucket { upload(context: Context) { if (!this.uploaded) { + if (this.layoutVertexArray2.length !== 0) { + this.layoutVertexBuffer2 = context.createVertexBuffer(this.layoutVertexArray2, layoutAttributesExt); + } this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray, layoutAttributes); this.indexBuffer = context.createIndexBuffer(this.indexArray); } @@ -213,12 +227,40 @@ class LineBucket implements Bucket { this.segments.destroy(); } + lineFeatureClips(feature: BucketFeature): ?LineClips { + if (!!feature.properties && feature.properties.hasOwnProperty('mapbox_clip_start') && feature.properties.hasOwnProperty('mapbox_clip_end')) { + const clipStart = +feature.properties['mapbox_clip_start']; + const clipEnd = +feature.properties['mapbox_clip_end']; + return {clipStart, clipEnd}; + } + } + + _requiresHighPrecisionLineProgress(features: Array) { + for (const bucketFeature of features) { + const lineClips = this.lineFeatureClips(bucketFeature); + if (!lineClips) continue; + + let totalFeatureDistance = 0; + for (const line of bucketFeature.geometry) { + for (let i = 0; i < line.length - 1; i++) { + totalFeatureDistance += line[i].dist(line[i + 1]); + } + } + const clipDiff = lineClips.clipEnd - lineClips.clipStart; + if (totalFeatureDistance / clipDiff > MAX_LINE_DISTANCE) { + return true; + } + } + return false; + } + 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 miterLimit = layout.get('line-miter-limit'); const roundLimit = layout.get('line-round-limit'); + this.lineClips = this.lineFeatureClips(feature); for (const line of geometry) { this.addLine(line, feature, join, cap, miterLimit, roundLimit); @@ -232,19 +274,14 @@ class LineBucket implements Bucket { this.scaledDistance = 0; this.totalDistance = 0; - if (!!feature.properties && - feature.properties.hasOwnProperty('mapbox_clip_start') && - feature.properties.hasOwnProperty('mapbox_clip_end')) { - - this.clipStart = +feature.properties['mapbox_clip_start']; - this.clipEnd = +feature.properties['mapbox_clip_end']; - + if (this.lineClips) { // Calculate the total distance, in tile units, of this tiled line feature for (let i = 0; i < vertices.length - 1; i++) { this.totalDistance += vertices[i].dist(vertices[i + 1]); } this.updateScaledDistance(); - this.maxLineLength = Math.max(this.maxLineLength, this.totalDistance / (this.clipEnd - this.clipStart)); + //$FlowFixMe + this.maxLineLength = Math.max(this.maxLineLength, this.totalDistance / (this.lineClips.clipEnd - this.lineClips.clipStart)); } const isPolygon = vectorTileFeatureTypes[feature.type] === 'Polygon'; @@ -495,20 +532,11 @@ class LineBucket implements Bucket { this.addHalfVertex(p, leftX, leftY, round, false, endLeft, segment); this.addHalfVertex(p, rightX, rightY, round, true, -endRight, segment); - - // There is a maximum "distance along the line" that we can store in the buffers. - // When we get close to the distance, reset it to zero and add the vertex again with - // a distance of zero. The max distance is determined by the number of bits we allocate - // to `linesofar`. - if (this.distance > MAX_LINE_DISTANCE / 2 && this.totalDistance === 0) { - this.distance = 0; - this.addCurrentVertex(p, normal, endLeft, endRight, segment, round); - } } addHalfVertex({x, y}: Point, extrudeX: number, extrudeY: number, round: boolean, up: boolean, dir: number, segment: Segment) { // scale down so that we can store longer distances while sacrificing precision. - const linesofar = this.scaledDistance * LINE_DISTANCE_SCALE; + const linesofarScaled = (this.totalDistance > 0 ? this.scaledDistance * (MAX_LINE_DISTANCE - 1) : this.scaledDistance) * LINE_DISTANCE_SCALE; this.layoutVertexArray.emplaceBack( // a_pos_normal @@ -520,11 +548,16 @@ class LineBucket implements Bucket { Math.round(EXTRUDE_SCALE * extrudeX) + 128, Math.round(EXTRUDE_SCALE * extrudeY) + 128, // Encode the -1/0/1 direction value into the first two bits of .z of a_data. - // Combine it with the lower 6 bits of `linesofar` (shifted by 2 bites to make - // room for the direction value). The upper 8 bits of `linesofar` are placed in + // Combine it with the lower 6 bits of `linesofarScaled` (shifted by 2 bits to make + // room for the direction value). The upper 8 bits of `linesofarScaled` are placed in // the `w` component. - ((dir === 0 ? 0 : (dir < 0 ? -1 : 1)) + 1) | ((linesofar & 0x3F) << 2), - linesofar >> 6); + ((dir === 0 ? 0 : (dir < 0 ? -1 : 1)) + 1) | ((linesofarScaled & 0x3F) << 2), + linesofarScaled >> 6); + + // Constructs a second vertex buffer with higher precision line progress + if (this.requiresHighPrecisionLineProgress) { + this.layoutVertexArray2.emplaceBack(this.scaledDistance); + } const e = segment.vertexLength++; if (this.e1 >= 0 && this.e2 >= 0) { @@ -543,8 +576,8 @@ class LineBucket implements Bucket { // as the total distance (in tile units) of this tiled feature, and the distance // (in tile units) of the current vertex, we can determine the relative distance // of this vertex along the full linestring feature and scale it to [0, 2^15) - this.scaledDistance = this.totalDistance > 0 ? - (this.clipStart + (this.clipEnd - this.clipStart) * this.distance / this.totalDistance) * (MAX_LINE_DISTANCE - 1) : + this.scaledDistance = this.lineClips ? + this.lineClips.clipStart + (this.lineClips.clipEnd - this.lineClips.clipStart) * this.distance / this.totalDistance : this.distance; } diff --git a/src/render/draw_line.js b/src/render/draw_line.js index f9fc1bad36c..3345ab6d248 100644 --- a/src/render/draw_line.js +++ b/src/render/draw_line.js @@ -95,7 +95,7 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay program.draw(context, gl.TRIANGLES, depthMode, painter.stencilModeForClipping(coord), colorMode, CullFaceMode.disabled, uniformValues, layer.id, bucket.layoutVertexBuffer, bucket.indexBuffer, bucket.segments, - layer.paint, painter.transform.zoom, programConfiguration); + 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/shaders/line_gradient.vertex.glsl b/src/shaders/line_gradient.vertex.glsl index 98281e2843c..6398d238061 100644 --- a/src/shaders/line_gradient.vertex.glsl +++ b/src/shaders/line_gradient.vertex.glsl @@ -12,6 +12,7 @@ attribute vec2 a_pos_normal; attribute vec4 a_data; +attribute float a_line_progress; uniform mat4 u_matrix; uniform mediump float u_ratio; @@ -43,7 +44,7 @@ void main() { vec2 a_extrude = a_data.xy - 128.0; float a_direction = mod(a_data.z, 4.0) - 1.0; - v_lineprogress = (floor(a_data.z / 4.0) + a_data.w * 64.0) * 2.0 / MAX_LINE_DISTANCE; + v_lineprogress = a_line_progress != 0.0 ? a_line_progress : (floor(a_data.z / 4.0) + a_data.w * 64.0) * 2.0 / MAX_LINE_DISTANCE; vec2 pos = floor(a_pos_normal * 0.5);