Skip to content

Commit

Permalink
Tile line gradient textures
Browse files Browse the repository at this point in the history
  • Loading branch information
karimnaaji committed Jun 3, 2020
1 parent 34effd3 commit 3af34e2
Show file tree
Hide file tree
Showing 18 changed files with 331 additions and 102 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
39 changes: 39 additions & 0 deletions src/data/array_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,43 @@ class StructArrayLayout2i4ub8 extends StructArray {
StructArrayLayout2i4ub8.prototype.bytesPerElement = 8;
register('StructArrayLayout2i4ub8', StructArrayLayout2i4ub8);

/**
* Implementation of the StructArray layout:
* [0]: Float32[1]
* [4]: Float32[1]
* [8]: Uint32[1]
*
* @private
*/
class StructArrayLayout1f1f1ul12 extends StructArray {
uint8: Uint8Array;
float32: Float32Array;
uint32: Uint32Array;

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

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

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

StructArrayLayout1f1f1ul12.prototype.bytesPerElement = 12;
register('StructArrayLayout1f1f1ul12', StructArrayLayout1f1f1ul12);

/**
* Implementation of the StructArray layout:
* [0]: Uint16[10]
Expand Down Expand Up @@ -1095,6 +1132,7 @@ export {
StructArrayLayout4i8,
StructArrayLayout2i4i12,
StructArrayLayout2i4ub8,
StructArrayLayout1f1f1ul12,
StructArrayLayout10ui20,
StructArrayLayout4i4ui4i24,
StructArrayLayout3f12,
Expand All @@ -1120,6 +1158,7 @@ export {
StructArrayLayout2i4i12 as FillExtrusionLayoutArray,
StructArrayLayout2i4 as HeatmapLayoutArray,
StructArrayLayout2i4ub8 as LineLayoutArray,
StructArrayLayout1f1f1ul12 as LineExtLayoutArray,
StructArrayLayout10ui20 as PatternLayoutArray,
StructArrayLayout4i4ui4i24 as SymbolLayoutArray,
StructArrayLayout3f12 as SymbolDynamicLayoutArray,
Expand Down
4 changes: 3 additions & 1 deletion src/data/bucket/line_attributes_ext.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import {createLayout} from '../../util/struct_array';

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

export default lineLayoutAttributesExt;
Expand Down
67 changes: 32 additions & 35 deletions src/data/bucket/line_bucket.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow

import {LineLayoutArray, StructArrayLayout1f4} from '../array_types';
import {LineLayoutArray, LineExtLayoutArray} from '../array_types';

import {members as layoutAttributes} from './line_attributes';
import {members as layoutAttributesExt} from './line_attributes_ext';
Expand All @@ -26,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 @@ -69,8 +71,8 @@ const LINE_DISTANCE_SCALE = 1 / 2;
const MAX_LINE_DISTANCE = Math.pow(2, LINE_DISTANCE_BUFFER_BITS - 1) / LINE_DISTANCE_SCALE;

type LineClips = {
clipStart: number;
clipEnd: number;
start: number;
end: number;
}

/**
Expand All @@ -94,17 +96,20 @@ class LineBucket implements Bucket {
stateDependentLayers: Array<any>;
stateDependentLayerIds: Array<string>;
patternFeatures: Array<BucketFeature>;
lineClipsArray: Array<LineClips>;

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

indexArray: TriangleIndexArray;
indexBuffer: IndexBuffer;

hasPattern: boolean;
requiresHighPrecisionLineProgress: boolean;
programConfigurations: ProgramConfigurationSet<LineStyleLayer>;
segments: SegmentVector;
uploaded: boolean;
Expand All @@ -116,11 +121,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.lineClipsArray = [];

this.layoutVertexArray = new LineLayoutArray();
this.layoutVertexArray2 = new StructArrayLayout1f4();
this.layoutVertexArray2 = new LineExtLayoutArray();
this.indexArray = new TriangleIndexArray();
this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom);
this.segments = new SegmentVector();
Expand Down Expand Up @@ -170,7 +175,6 @@ class LineBucket implements Bucket {
});
}

this.requiresHighPrecisionLineProgress = this._requiresHighPrecisionLineProgress(bucketFeatures);
for (const bucketFeature of bucketFeatures) {
const {geometry, index, sourceLayerIndex} = bucketFeature;

Expand Down Expand Up @@ -229,31 +233,12 @@ class LineBucket implements Bucket {

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};
const start = +feature.properties['mapbox_clip_start'];
const end = +feature.properties['mapbox_clip_end'];
return {start, end};
}
}

_requiresHighPrecisionLineProgress(features: Array<BucketFeature>) {
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<Array<Point>>, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) {
const layout = this.layers[0].layout;
const join = layout.get('line-join').evaluate(feature, {});
Expand All @@ -275,13 +260,13 @@ class LineBucket implements Bucket {
this.totalDistance = 0;

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();
//$FlowFixMe
this.maxLineLength = Math.max(this.maxLineLength, this.totalDistance / (this.lineClips.clipEnd - this.lineClips.clipStart));
this.maxLineLength = Math.max(this.maxLineLength, this.totalDistance);
}

const isPolygon = vectorTileFeatureTypes[feature.type] === 'Polygon';
Expand Down Expand Up @@ -532,6 +517,15 @@ 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) {
Expand All @@ -555,8 +549,11 @@ class LineBucket implements Bucket {
linesofarScaled >> 6);

// Constructs a second vertex buffer with higher precision line progress
if (this.requiresHighPrecisionLineProgress) {
this.layoutVertexArray2.emplaceBack(this.scaledDistance);
if (this.lineClips) {
this.layoutVertexArray2.emplaceBack(
this.scaledDistance - this.lineClips.start,
this.lineClips.end - this.lineClips.start,
this.lineClipsArray.length);
}

const e = segment.vertexLength++;
Expand All @@ -577,7 +574,7 @@ class LineBucket implements Bucket {
// (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.lineClips ?
this.lineClips.clipStart + (this.lineClips.clipEnd - this.lineClips.clipStart) * this.distance / this.totalDistance :
this.lineClips.start + (this.lineClips.end - this.lineClips.start) * this.distance / this.totalDistance :
this.distance;
}

Expand Down
43 changes: 32 additions & 11 deletions src/render/draw_line.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import type SourceCache from '../source/source_cache';
import type LineStyleLayer from '../style/style_layer/line_style_layer';
import type LineBucket from '../data/bucket/line_bucket';
import type {OverscaledTileID} from '../source/tile_id';
import {clamp, nextPowerOfTwo} from '../util/util';
import {renderColorRamp} from '../util/color_ramp';
import EXTENT from '../data/extent';

export default function drawLine(painter: Painter, sourceCache: SourceCache, layer: LineStyleLayer, coords: Array<OverscaledTileID>) {
Expand Down Expand Up @@ -67,7 +69,7 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay

const uniformValues = image ? linePatternUniformValues(painter, tile, layer, crossfade) :
dasharray ? lineSDFUniformValues(painter, tile, layer, dasharray, crossfade) :
gradient ? lineGradientUniformValues(painter, tile, layer) :
gradient ? lineGradientUniformValues(painter, tile, layer, bucket.lineClipsArray.length) :
lineUniformValues(painter, tile, layer);

if (image) {
Expand All @@ -77,16 +79,35 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay
} else if (dasharray && (programChanged || painter.lineAtlas.dirty)) {
context.activeTexture.set(gl.TEXTURE0);
painter.lineAtlas.bind(context);
} else if (gradient && firstTile) {
let gradientTexture = layer.gradientTexture;
if (!gradientTexture) {
const sourceMaxZoom = sourceCache.getSource().maxzoom;
const lineLength = bucket.maxLineLength / EXTENT;
// Maximum possible texture coverage heuristic, bound by hardware max texture size
const lineTileCoverageAtMaxZoom = Math.ceil((1 << (sourceMaxZoom - coord.canonical.z)) * lineLength);

layer.renderGradientFromExpression(lineTileCoverageAtMaxZoom, context.maxTextureSize);
gradientTexture = layer.gradientTexture = new Texture(context, layer.gradient, gl.RGBA);
} else if (gradient) {
let gradientTexture = bucket.gradientTexture;
if (!gradientTexture || (gradientTexture && layer.gradientVersion !== bucket.gradientVersion)) {
let textureResolution = 256;
if (layer.stepInterpolant) {
const sourceMaxZoom = sourceCache.getSource().maxzoom;
const potentialOverzoom = coord.canonical.z === sourceMaxZoom ?
Math.ceil(1 << (painter.transform.maxZoom - coord.canonical.z)) : 1;
const lineLength = bucket.maxLineLength / EXTENT;
// Logical pixel tile size is 512px, and 1024px right before current zoom + 1
const maxTilePixelSize = 1024;
// Maximum possible texture coverage heuristic, bound by hardware max texture size
const maxTextureCoverage = lineLength * maxTilePixelSize * potentialOverzoom;
textureResolution = clamp(nextPowerOfTwo(maxTextureCoverage), 256, context.maxTextureSize);
}
bucket.gradient = renderColorRamp({
expression: layer.gradientExpression(),
evaluationKey: 'lineProgress',
resolution: textureResolution,
image: bucket.gradient || undefined,
clips: bucket.lineClipsArray
});
if (bucket.gradientTexture) {
bucket.gradientTexture.update(bucket.gradient);
} else {
bucket.gradientTexture = new Texture(context, bucket.gradient, gl.RGBA);
}
bucket.gradientVersion = layer.gradientVersion;
gradientTexture = bucket.gradientTexture;
}
context.activeTexture.set(gl.TEXTURE0);
gradientTexture.bind(layer.stepInterpolant ? gl.NEAREST : gl.LINEAR, gl.CLAMP_TO_EDGE);
Expand Down
12 changes: 8 additions & 4 deletions src/render/program/line_program.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export type LineGradientUniformsType = {|
'u_ratio': Uniform1f,
'u_device_pixel_ratio': Uniform1f,
'u_units_to_pixels': Uniform2f,
'u_image': Uniform1i
'u_image': Uniform1i,
'u_image_height': Uniform1f,
|};

export type LinePatternUniformsType = {|
Expand Down Expand Up @@ -72,7 +73,8 @@ const lineGradientUniforms = (context: Context, locations: UniformLocations): Li
'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_image': new Uniform1i(context, locations.u_image),
'u_image_height': new Uniform1f(context, locations.u_image_height),
});

const linePatternUniforms = (context: Context, locations: UniformLocations): LinePatternUniformsType => ({
Expand Down Expand Up @@ -121,10 +123,12 @@ const lineUniformValues = (
const lineGradientUniformValues = (
painter: Painter,
tile: Tile,
layer: LineStyleLayer
layer: LineStyleLayer,
imageHeight: number
): UniformValues<LineGradientUniformsType> => {
return extend(lineUniformValues(painter, tile, layer), {
'u_image': 0
'u_image': 0,
'u_image_height': imageHeight,
});
};

Expand Down
19 changes: 16 additions & 3 deletions src/shaders/line_gradient.fragment.glsl
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
uniform lowp float u_device_pixel_ratio;
uniform sampler2D u_image;
uniform float u_image_height;

varying vec2 v_width2;
varying vec2 v_normal;
varying float v_gamma_scale;
varying highp float v_lineprogress;
varying highp float v_line_clip;
varying lowp float v_split_index;

#pragma mapbox: define lowp float blur
#pragma mapbox: define lowp float opacity

highp float map(highp float value, highp float start, highp float end, highp float new_start, highp float new_end) {
return ((value - start) * (new_end - new_start)) / (end - start) + new_start;
}

void main() {
#pragma mapbox: initialize lowp float blur
#pragma mapbox: initialize lowp float opacity
Expand All @@ -22,9 +29,15 @@ void main() {
float blur2 = (blur + 1.0 / u_device_pixel_ratio) * v_gamma_scale;
float alpha = clamp(min(dist - (v_width2.t - blur2), v_width2.s - dist) / blur2, 0.0, 1.0);

// For gradient lines, v_lineprogress is the ratio along the entire line,
// scaled to [0, 2^15), and the gradient ramp is stored in a texture.
vec4 color = texture2D(u_image, vec2(v_lineprogress, 0.5));
highp float texel_height = 1.0 / u_image_height;
highp float half_texel_height = 0.5 * texel_height;
highp vec2 uv = vec2(
map(v_lineprogress, 0.0, v_line_clip, 0.0, 1.0),
v_split_index * texel_height - half_texel_height);

// For gradient lines, v_lineprogress is the ratio along the
// entire line, the gradient ramp is stored in a texture.
vec4 color = texture2D(u_image, uv);

gl_FragColor = color * (alpha * opacity);

Expand Down
Loading

0 comments on commit 3af34e2

Please sign in to comment.