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 May 29, 2020
1 parent 2c7ad46 commit 9d73d1a
Show file tree
Hide file tree
Showing 16 changed files with 208 additions and 109 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
71 changes: 36 additions & 35 deletions src/data/array_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,40 @@ class StructArrayLayout2i4ub8 extends StructArray {
StructArrayLayout2i4ub8.prototype.bytesPerElement = 8;
register('StructArrayLayout2i4ub8', StructArrayLayout2i4ub8);

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

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

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

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

StructArrayLayout4f16.prototype.bytesPerElement = 16;
register('StructArrayLayout4f16', StructArrayLayout4f16);

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

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

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

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

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

StructArrayLayout4f16.prototype.bytesPerElement = 16;
register('StructArrayLayout4f16', StructArrayLayout4f16);

class CollisionBoxStruct extends Struct {
_structArray: CollisionBoxArray;
anchorPointX: number;
Expand Down Expand Up @@ -1095,6 +1095,7 @@ export {
StructArrayLayout4i8,
StructArrayLayout2i4i12,
StructArrayLayout2i4ub8,
StructArrayLayout4f16,
StructArrayLayout10ui20,
StructArrayLayout4i4ui4i24,
StructArrayLayout3f12,
Expand All @@ -1112,14 +1113,14 @@ export {
StructArrayLayout2ui4,
StructArrayLayout1ui2,
StructArrayLayout2f8,
StructArrayLayout4f16,
StructArrayLayout2i4 as PosArray,
StructArrayLayout4i8 as RasterBoundsArray,
StructArrayLayout2i4 as CircleLayoutArray,
StructArrayLayout2i4 as FillLayoutArray,
StructArrayLayout2i4i12 as FillExtrusionLayoutArray,
StructArrayLayout2i4 as HeatmapLayoutArray,
StructArrayLayout2i4ub8 as LineLayoutArray,
StructArrayLayout4f16 as LineExtLayoutArray,
StructArrayLayout10ui20 as PatternLayoutArray,
StructArrayLayout4i4ui4i24 as SymbolLayoutArray,
StructArrayLayout3f12 as SymbolDynamicLayoutArray,
Expand Down
6 changes: 4 additions & 2 deletions src/data/bucket/line_attributes_ext.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import {createLayout} from '../../util/struct_array';

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

export default lineLayoutAttributesExt;
export const {members, size, alignment} = lineLayoutAttributesExt;
37 changes: 24 additions & 13 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,11 +96,14 @@ 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;

indexArray: TriangleIndexArray;
indexBuffer: IndexBuffer;
Expand All @@ -118,9 +123,10 @@ class LineBucket implements Bucket {
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 @@ -229,9 +235,9 @@ 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};
}
}

Expand All @@ -246,7 +252,7 @@ class LineBucket implements Bucket {
totalFeatureDistance += line[i].dist(line[i + 1]);
}
}
const clipDiff = lineClips.clipEnd - lineClips.clipStart;
const clipDiff = lineClips.end - lineClips.start;
if (totalFeatureDistance / clipDiff > MAX_LINE_DISTANCE) {
return true;
}
Expand Down Expand Up @@ -280,8 +286,9 @@ class LineBucket implements Bucket {
this.totalDistance += vertices[i].dist(vertices[i + 1]);
}
this.updateScaledDistance();
this.maxLineLength = Math.max(this.maxLineLength, this.totalDistance);
//$FlowFixMe
this.maxLineLength = Math.max(this.maxLineLength, this.totalDistance / (this.lineClips.clipEnd - this.lineClips.clipStart));
this.lineClipsArray.push(this.lineClips);
}

const isPolygon = vectorTileFeatureTypes[feature.type] === 'Polygon';
Expand Down Expand Up @@ -555,8 +562,12 @@ 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.lineClipsArray.length);
}

const e = segment.vertexLength++;
Expand All @@ -577,7 +588,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
29 changes: 21 additions & 8 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, type ColorRampParams} 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,25 @@ 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) {
} else if (gradient) {
let gradientTexture = bucket.gradientTexture;
if (!gradientTexture || layer.gradientTextureInvalidated) {
const sourceMaxZoom = sourceCache.getSource().maxzoom;
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 lineTileCoverageAtMaxZoom = Math.ceil((1 << (sourceMaxZoom - coord.canonical.z)) * lineLength);

layer.renderGradientFromExpression(lineTileCoverageAtMaxZoom, context.maxTextureSize);
gradientTexture = layer.gradientTexture = new Texture(context, layer.gradient, gl.RGBA);
const maxTextureCoverage = lineLength * maxTilePixelSize;
const textureResolution = nextPowerOfTwo(clamp(maxTextureCoverage, 256, context.maxTextureSize));
const renderGradientParams = {
expression: layer.gradientExpression(),
evaluationKey: 'lineProgress',
resolution: layer.stepInterpolant ? textureResolution : 256,
image: bucket.gradient || undefined,
clips: bucket.lineClipsArray
};
bucket.gradient = renderColorRamp(renderGradientParams);
gradientTexture = bucket.gradientTexture = new Texture(context, bucket.gradient, gl.RGBA);
}
context.activeTexture.set(gl.TEXTURE0);
gradientTexture.bind(layer.stepInterpolant ? gl.NEAREST : gl.LINEAR, gl.CLAMP_TO_EDGE);
Expand All @@ -100,4 +111,6 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay
firstTile = false;
// once refactored so that bound texture state is managed, we'll also be able to remove this firstTile/programChanged logic
}

layer.gradientTextureInvalidated = false;
}
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 vec2 v_line_clips;
varying float v_split_index;

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

float map(float value, float start, float end, float new_start, 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));
float texel_height = 1.0 / u_image_height;
float half_texel_height = 0.5 * texel_height;
vec2 uv = vec2(
map(v_lineprogress, v_line_clips.x, v_line_clips.y, 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 9d73d1a

Please sign in to comment.