Skip to content

Commit

Permalink
Revise step interpolation for lines, increase precision of line progr…
Browse files Browse the repository at this point in the history
…ess as needed (#9694)

* Vastly increase precision for line gradients
- Step interpolant now uses gl.NEAREST for texture sampler: This allows
for hard transition between different line sections when using step
interpolation. Other use cases are covered by using a smooth interpolation
type along with gl.LINEAR texture sampler.
- Step interpolant now uses an increased texture resolution color ramp
based on the total line length tile coverage to limitate the potential
precision issues for high coverage
- Reuse color ramp memory placement when already available
- Precision is increased by two factors for geojson with line metrics on:
  - Tile line gradient textures for higher representation in texture space
  - Add an optional extension vertex buffer for increased line progress precision

* Review comments (Thanks @mourner)
- Eliminate Math.log2 (not available on ie11)
- Inline and compress evaluation interpolations for line clip by
specializing it for the use case

* Optimization pass
- Use single attribute for uv on x, move computation cpu side (Thanks @ansis)
- Offload fragment shader from a few operations by moving it to vertex
- More explicit addHalfVertex function block
  • Loading branch information
karimnaaji authored Jun 9, 2020
1 parent 25cc7a9 commit 58a7390
Show file tree
Hide file tree
Showing 21 changed files with 615 additions and 105 deletions.
2 changes: 2 additions & 0 deletions build/generate-struct-arrays.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ const circleAttributes = require('../src/data/bucket/circle_attributes').default
const fillAttributes = require('../src/data/bucket/fill_attributes').default;
const fillExtrusionAttributes = require('../src/data/bucket/fill_extrusion_attributes').default;
const lineAttributes = require('../src/data/bucket/line_attributes').default;
const lineAttributesExt = require('../src/data/bucket/line_attributes_ext').default;
const patternAttributes = require('../src/data/bucket/pattern_attributes').default;

// layout vertex arrays
Expand All @@ -138,6 +139,7 @@ const layoutAttributes = {
'fill-extrusion': fillExtrusionAttributes,
heatmap: circleAttributes,
line: lineAttributes,
lineExt: lineAttributesExt,
pattern: patternAttributes
};
for (const name in layoutAttributes) {
Expand Down
67 changes: 34 additions & 33 deletions src/data/array_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,38 @@ class StructArrayLayout2i4ub8 extends StructArray {
StructArrayLayout2i4ub8.prototype.bytesPerElement = 8;
register('StructArrayLayout2i4ub8', StructArrayLayout2i4ub8);

/**
* Implementation of the StructArray layout:
* [0]: Float32[2]
*
* @private
*/
class StructArrayLayout2f8 extends StructArray {
uint8: Uint8Array;
float32: Float32Array;

_refreshViews() {
this.uint8 = new Uint8Array(this.arrayBuffer);
this.float32 = new Float32Array(this.arrayBuffer);
}

emplaceBack(v0: number, v1: number) {
const i = this.length;
this.resize(i + 1);
return this.emplace(i, v0, v1);
}

emplace(i: number, v0: number, v1: number) {
const o4 = i * 2;
this.float32[o4 + 0] = v0;
this.float32[o4 + 1] = v1;
return i;
}
}

StructArrayLayout2f8.prototype.bytesPerElement = 8;
register('StructArrayLayout2f8', StructArrayLayout2f8);

/**
* Implementation of the StructArray layout:
* [0]: Uint16[10]
Expand Down Expand Up @@ -784,38 +816,6 @@ class StructArrayLayout1ui2 extends StructArray {
StructArrayLayout1ui2.prototype.bytesPerElement = 2;
register('StructArrayLayout1ui2', StructArrayLayout1ui2);

/**
* Implementation of the StructArray layout:
* [0]: Float32[2]
*
* @private
*/
class StructArrayLayout2f8 extends StructArray {
uint8: Uint8Array;
float32: Float32Array;

_refreshViews() {
this.uint8 = new Uint8Array(this.arrayBuffer);
this.float32 = new Float32Array(this.arrayBuffer);
}

emplaceBack(v0: number, v1: number) {
const i = this.length;
this.resize(i + 1);
return this.emplace(i, v0, v1);
}

emplace(i: number, v0: number, v1: number) {
const o4 = i * 2;
this.float32[o4 + 0] = v0;
this.float32[o4 + 1] = v1;
return i;
}
}

StructArrayLayout2f8.prototype.bytesPerElement = 8;
register('StructArrayLayout2f8', StructArrayLayout2f8);

/**
* Implementation of the StructArray layout:
* [0]: Float32[4]
Expand Down Expand Up @@ -1095,6 +1095,7 @@ export {
StructArrayLayout4i8,
StructArrayLayout2i4i12,
StructArrayLayout2i4ub8,
StructArrayLayout2f8,
StructArrayLayout10ui20,
StructArrayLayout4i4ui4i24,
StructArrayLayout3f12,
Expand All @@ -1111,7 +1112,6 @@ export {
StructArrayLayout1ul2ui8,
StructArrayLayout2ui4,
StructArrayLayout1ui2,
StructArrayLayout2f8,
StructArrayLayout4f16,
StructArrayLayout2i4 as PosArray,
StructArrayLayout4i8 as RasterBoundsArray,
Expand All @@ -1120,6 +1120,7 @@ export {
StructArrayLayout2i4i12 as FillExtrusionLayoutArray,
StructArrayLayout2i4 as HeatmapLayoutArray,
StructArrayLayout2i4ub8 as LineLayoutArray,
StructArrayLayout2f8 as LineExtLayoutArray,
StructArrayLayout10ui20 as PatternLayoutArray,
StructArrayLayout4i4ui4i24 as SymbolLayoutArray,
StructArrayLayout3f12 as SymbolDynamicLayoutArray,
Expand Down
10 changes: 10 additions & 0 deletions src/data/bucket/line_attributes_ext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @flow
import {createLayout} from '../../util/struct_array';

const lineLayoutAttributesExt = createLayout([
{name: 'a_uv_x', components: 1, type: 'Float32'},
{name: 'a_split_index', components: 1, type: 'Float32'},
]);

export default lineLayoutAttributesExt;
export const {members, size, alignment} = lineLayoutAttributesExt;
68 changes: 51 additions & 17 deletions src/data/bucket/line_bucket.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// @flow

import {LineLayoutArray} from '../array_types';
import {LineLayoutArray, LineExtLayoutArray} 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';
Expand All @@ -25,7 +26,9 @@ import type {
import type LineStyleLayer from '../../style/style_layer/line_style_layer';
import type Point from '@mapbox/point-geometry';
import type {Segment} from '../segment';
import {RGBAImage} from '../../util/image';
import type Context from '../../gl/context';
import type Texture from '../../render/texture';
import type IndexBuffer from '../../gl/index_buffer';
import type VertexBuffer from '../../gl/vertex_buffer';
import type {FeatureStates} from '../../source/source_state';
Expand Down Expand Up @@ -67,15 +70,20 @@ 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 = {
start: number;
end: number;
}

/**
* @private
*/
class LineBucket implements Bucket {
distance: number;
totalDistance: number;
maxLineLength: number;
scaledDistance: number;
clipStart: number;
clipEnd: number;
lineClips: ?LineClips;

e1: number;
e2: number;
Expand All @@ -88,9 +96,15 @@ class LineBucket implements Bucket {
stateDependentLayers: Array<any>;
stateDependentLayerIds: Array<string>;
patternFeatures: Array<BucketFeature>;
lineClipsArray: Array<LineClips>;

layoutVertexArray: LineLayoutArray;
layoutVertexBuffer: VertexBuffer;
layoutVertexArray2: LineExtLayoutArray;
layoutVertexBuffer2: VertexBuffer;
gradientTexture: Texture;
gradient: ?RGBAImage;
gradientVersion: number;

indexArray: TriangleIndexArray;
indexBuffer: IndexBuffer;
Expand All @@ -108,11 +122,14 @@ class LineBucket implements Bucket {
this.index = options.index;
this.hasPattern = false;
this.patternFeatures = [];
this.lineClipsArray = [];

this.layoutVertexArray = new LineLayoutArray();
this.layoutVertexArray2 = new LineExtLayoutArray();
this.indexArray = new TriangleIndexArray();
this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom);
this.segments = new SegmentVector();
this.maxLineLength = 0;

this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id);
}
Expand Down Expand Up @@ -196,6 +213,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);
}
Expand All @@ -211,12 +231,21 @@ 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 start = +feature.properties['mapbox_clip_start'];
const end = +feature.properties['mapbox_clip_end'];
return {start, end};
}
}

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 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);
Expand All @@ -230,18 +259,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) {
this.lineClipsArray.push(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);
}

const isPolygon = vectorTileFeatureTypes[feature.type] === 'Polygon';
Expand Down Expand Up @@ -504,8 +529,9 @@ class LineBucket implements Bucket {
}

addHalfVertex({x, y}: Point, extrudeX: number, extrudeY: number, round: boolean, up: boolean, dir: number, segment: Segment) {
const totalDistance = this.lineClips ? this.scaledDistance * (MAX_LINE_DISTANCE - 1) : this.scaledDistance;
// scale down so that we can store longer distances while sacrificing precision.
const linesofar = this.scaledDistance * LINE_DISTANCE_SCALE;
const linesofarScaled = totalDistance * LINE_DISTANCE_SCALE;

this.layoutVertexArray.emplaceBack(
// a_pos_normal
Expand All @@ -517,11 +543,19 @@ 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.lineClips) {
const progressRealigned = this.scaledDistance - this.lineClips.start;
const endClipRealigned = this.lineClips.end - this.lineClips.start;
const uvX = progressRealigned / endClipRealigned;
this.layoutVertexArray2.emplaceBack(uvX, this.lineClipsArray.length);
}

const e = segment.vertexLength++;
if (this.e1 >= 0 && this.e2 >= 0) {
Expand All @@ -540,8 +574,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.start + (this.lineClips.end - this.lineClips.start) * this.distance / this.totalDistance :
this.distance;
}

Expand Down
2 changes: 2 additions & 0 deletions src/gl/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Context {
gl: WebGLRenderingContext;
extVertexArrayObject: any;
currentNumAttributes: ?number;
maxTextureSize: number;

clearColor: ClearColor;
clearDepth: ClearDepth;
Expand Down Expand Up @@ -118,6 +119,7 @@ class Context {
}

this.extTimerQuery = gl.getExtension('EXT_disjoint_timer_query');
this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
}

setDefault() {
Expand Down
Loading

0 comments on commit 58a7390

Please sign in to comment.