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

Optimize line drawing by avoiding per-tile work #3485

Merged
merged 2 commits into from
Oct 30, 2016
Merged
Changes from all commits
Commits
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
159 changes: 82 additions & 77 deletions js/render/draw_line.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,101 +24,106 @@ module.exports = function drawLine(painter, sourceCache, layer, coords) {
// don't draw zero-width lines
if (layer.paint['line-width'] <= 0) return;

const programId =
layer.paint['line-dasharray'] ? 'lineSDF' :
layer.paint['line-pattern'] ? 'linePattern' : 'line';

let prevTileZoom;
for (let k = 0; k < coords.length; k++) {
drawLineTile(painter, sourceCache, layer, coords[k]);
const tile = sourceCache.getTile(coords[k]);
const bucket = tile.getBucket(layer);
if (!bucket) continue;

const layerData = bucket.buffers.layerData[layer.id];
const prevProgram = painter.currentProgram;
const program = painter.useProgram(programId, layerData.programConfiguration);
const programChanged = k === 0 || program !== prevProgram;
const tileRatioChanged = prevTileZoom !== tile.coord.z;

if (programChanged) {
layerData.programConfiguration.setUniforms(painter.gl, program, layer, {zoom: painter.transform.zoom});
}
drawLineTile(program, painter, tile, bucket.buffers, layer, coords[k], layerData, programChanged, tileRatioChanged);
prevTileZoom = tile.coord.z;
}
};

function drawLineTile(painter, sourceCache, layer, coord) {
const tile = sourceCache.getTile(coord);
const bucket = tile.getBucket(layer);
if (!bucket) return;

const buffers = bucket.buffers;
const layerData = buffers.layerData[layer.id];
function drawLineTile(program, painter, tile, buffers, layer, coord, layerData, programChanged, tileRatioChanged) {
const gl = painter.gl;

const dasharray = layer.paint['line-dasharray'];
const image = layer.paint['line-pattern'];

const programConfiguration = layerData.programConfiguration;
const program = painter.useProgram(dasharray ? 'lineSDF' : image ? 'linePattern' : 'line', programConfiguration);
programConfiguration.setUniforms(gl, program, layer, {zoom: painter.transform.zoom});
let posA, posB, imagePosA, imagePosB;

if (!image) {
gl.uniform4fv(program.u_color, layer.paint['line-color']);
}
if (programChanged || tileRatioChanged) {
const tileRatio = 1 / pixelsToTileUnits(tile, 1, painter.transform.tileZoom);

let posA, posB, imagePosA, imagePosB;
if (dasharray) {
posA = painter.lineAtlas.getDash(dasharray.from, layer.layout['line-cap'] === 'round');
posB = painter.lineAtlas.getDash(dasharray.to, layer.layout['line-cap'] === 'round');

gl.uniform1i(program.u_image, 0);
gl.activeTexture(gl.TEXTURE0);
painter.lineAtlas.bind(gl);

gl.uniform1f(program.u_tex_y_a, posA.y);
gl.uniform1f(program.u_tex_y_b, posB.y);
gl.uniform1f(program.u_mix, dasharray.t);

} else if (image) {
imagePosA = painter.spriteAtlas.getPosition(image.from, true);
imagePosB = painter.spriteAtlas.getPosition(image.to, true);
if (!imagePosA || !imagePosB) return;

gl.uniform1i(program.u_image, 0);
gl.activeTexture(gl.TEXTURE0);
painter.spriteAtlas.bind(gl, true);

gl.uniform2fv(program.u_pattern_tl_a, imagePosA.tl);
gl.uniform2fv(program.u_pattern_br_a, imagePosA.br);
gl.uniform2fv(program.u_pattern_tl_b, imagePosB.tl);
gl.uniform2fv(program.u_pattern_br_b, imagePosB.br);
gl.uniform1f(program.u_fade, image.t);
}
if (dasharray) {
posA = painter.lineAtlas.getDash(dasharray.from, layer.layout['line-cap'] === 'round');
posB = painter.lineAtlas.getDash(dasharray.to, layer.layout['line-cap'] === 'round');

// the distance over which the line edge fades out.
// Retina devices need a smaller distance to avoid aliasing.
const antialiasing = 1 / browser.devicePixelRatio;
const widthA = posA.width * dasharray.fromScale;
const widthB = posB.width * dasharray.toScale;

gl.uniform1f(program.u_linewidth, layer.paint['line-width'] / 2);
gl.uniform1f(program.u_gapwidth, layer.paint['line-gap-width'] / 2);
gl.uniform1f(program.u_antialiasing, antialiasing / 2);
gl.uniform1f(program.u_blur, layer.paint['line-blur'] + antialiasing);
gl.uniform1f(program.u_opacity, layer.paint['line-opacity']);
gl.uniformMatrix2fv(program.u_antialiasingmatrix, false, painter.transform.lineAntialiasingMatrix);
gl.uniform1f(program.u_offset, -layer.paint['line-offset']);
gl.uniform1f(program.u_extra, painter.transform.lineStretch);
gl.uniform2f(program.u_patternscale_a, tileRatio / widthA, -posA.height / 2);
gl.uniform2f(program.u_patternscale_b, tileRatio / widthB, -posB.height / 2);
gl.uniform1f(program.u_sdfgamma, painter.lineAtlas.width / (Math.min(widthA, widthB) * 256 * browser.devicePixelRatio) / 2);

} else if (image) {
imagePosA = painter.spriteAtlas.getPosition(image.from, true);
imagePosB = painter.spriteAtlas.getPosition(image.to, true);
if (!imagePosA || !imagePosB) return;

gl.uniform2f(program.u_pattern_size_a, imagePosA.size[0] * image.fromScale / tileRatio, imagePosB.size[1]);
gl.uniform2f(program.u_pattern_size_b, imagePosB.size[0] * image.toScale / tileRatio, imagePosB.size[1]);
}
}

if (programChanged) {
if (!image) {
gl.uniform4fv(program.u_color, layer.paint['line-color']);
}

if (dasharray) {
gl.uniform1i(program.u_image, 0);
gl.activeTexture(gl.TEXTURE0);
painter.lineAtlas.bind(gl);

gl.uniform1f(program.u_tex_y_a, posA.y);
gl.uniform1f(program.u_tex_y_b, posB.y);
gl.uniform1f(program.u_mix, dasharray.t);

} else if (image) {
gl.uniform1i(program.u_image, 0);
gl.activeTexture(gl.TEXTURE0);
painter.spriteAtlas.bind(gl, true);

gl.uniform2fv(program.u_pattern_tl_a, imagePosA.tl);
gl.uniform2fv(program.u_pattern_br_a, imagePosA.br);
gl.uniform2fv(program.u_pattern_tl_b, imagePosB.tl);
gl.uniform2fv(program.u_pattern_br_b, imagePosB.br);
gl.uniform1f(program.u_fade, image.t);
}

// the distance over which the line edge fades out.
// Retina devices need a smaller distance to avoid aliasing.
const antialiasing = 1 / browser.devicePixelRatio;

gl.uniform1f(program.u_linewidth, layer.paint['line-width'] / 2);
gl.uniform1f(program.u_gapwidth, layer.paint['line-gap-width'] / 2);
gl.uniform1f(program.u_antialiasing, antialiasing / 2);
gl.uniform1f(program.u_blur, layer.paint['line-blur'] + antialiasing);
gl.uniform1f(program.u_opacity, layer.paint['line-opacity']);
gl.uniformMatrix2fv(program.u_antialiasingmatrix, false, painter.transform.lineAntialiasingMatrix);
gl.uniform1f(program.u_offset, -layer.paint['line-offset']);
gl.uniform1f(program.u_extra, painter.transform.lineStretch);
}

painter.enableTileClippingMask(coord);

// set uniforms that are different for each tile
const posMatrix = painter.translatePosMatrix(coord.posMatrix, tile, layer.paint['line-translate'], layer.paint['line-translate-anchor']);
gl.uniformMatrix4fv(program.u_matrix, false, posMatrix);

if (dasharray) {
const widthA = posA.width * dasharray.fromScale;
const widthB = posB.width * dasharray.toScale;
const scaleA = [1 / pixelsToTileUnits(tile, widthA, painter.transform.tileZoom), -posA.height / 2];
const scaleB = [1 / pixelsToTileUnits(tile, widthB, painter.transform.tileZoom), -posB.height / 2];
const gamma = painter.lineAtlas.width / (Math.min(widthA, widthB) * 256 * browser.devicePixelRatio) / 2;

gl.uniform2fv(program.u_patternscale_a, scaleA);
gl.uniform2fv(program.u_patternscale_b, scaleB);
gl.uniform1f(program.u_sdfgamma, gamma);

} else if (image) {
gl.uniform2fv(program.u_pattern_size_a, [
pixelsToTileUnits(tile, imagePosA.size[0] * image.fromScale, painter.transform.tileZoom),
imagePosB.size[1]
]);
gl.uniform2fv(program.u_pattern_size_b, [
pixelsToTileUnits(tile, imagePosB.size[0] * image.toScale, painter.transform.tileZoom),
imagePosB.size[1]
]);
}

gl.uniform1f(program.u_ratio, 1 / pixelsToTileUnits(tile, 1, painter.transform.zoom));

for (const segment of buffers.segments) {
Expand Down