From a38165f96a021953b6fe1899761ea5a540ef1eb6 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 21 Apr 2015 16:11:52 -0700 Subject: [PATCH 01/13] fix background in perspective view fixes #1156 --- js/render/draw_background.js | 72 +++++++++++++++--------------------- js/render/draw_fill.js | 10 +---- js/render/painter.js | 2 +- shaders/pattern.vertex.glsl | 8 ++-- 4 files changed, 37 insertions(+), 55 deletions(-) diff --git a/js/render/draw_background.js b/js/render/draw_background.js index a52012e5954..8ecaacfce01 100644 --- a/js/render/draw_background.js +++ b/js/render/draw_background.js @@ -1,11 +1,15 @@ 'use strict'; -var mat3 = require('gl-matrix').mat3; +var TilePyramid = require('../source/tile_pyramid'); +var Tile = require('../source/tile'); + +var pyramid = new TilePyramid({ tileSize: 512 }); module.exports = drawBackground; function drawBackground(painter, layer, posMatrix) { var gl = painter.gl; + var transform = painter.transform; var color = layer.paint['background-color']; var image = layer.paint['background-image']; var opacity = layer.paint['background-opacity']; @@ -25,46 +29,19 @@ function drawBackground(painter, layer, posMatrix) { gl.uniform2fv(shader.u_pattern_br_b, imagePosB.br); gl.uniform1f(shader.u_opacity, opacity); - var transform = painter.transform; - var sizeA = imagePosA.size; - var sizeB = imagePosB.size; - var center = transform.locationCoordinate(transform.center); - var scale = 1 / Math.pow(2, transform.zoomFraction); - gl.uniform1f(shader.u_mix, image.t); - var matrixA = mat3.create(); - mat3.scale(matrixA, matrixA, [ - 1 / (sizeA[0] * image.fromScale), - 1 / (sizeA[1] * image.fromScale) - ]); - mat3.translate(matrixA, matrixA, [ - (center.column * transform.tileSize) % (sizeA[0] * image.fromScale), - (center.row * transform.tileSize) % (sizeA[1] * image.fromScale) - ]); - mat3.rotate(matrixA, matrixA, -transform.angle); - mat3.scale(matrixA, matrixA, [ - scale * transform.width / 2, - -scale * transform.height / 2 - ]); - - var matrixB = mat3.create(); - mat3.scale(matrixB, matrixB, [ - 1 / (sizeB[0] * image.toScale), - 1 / (sizeB[1] * image.toScale) - ]); - mat3.translate(matrixB, matrixB, [ - (center.column * transform.tileSize) % (sizeB[0] * image.toScale), - (center.row * transform.tileSize) % (sizeB[1] * image.toScale) - ]); - mat3.rotate(matrixB, matrixB, -transform.angle); - mat3.scale(matrixB, matrixB, [ - scale * transform.width / 2, - -scale * transform.height / 2 - ]); - - gl.uniformMatrix3fv(shader.u_patternmatrix_a, false, matrixA); - gl.uniformMatrix3fv(shader.u_patternmatrix_b, false, matrixB); + var factor = (4096 / transform.tileSize) / Math.pow(2, 0); + + gl.uniform2fv(shader.u_patternscale_a, [ + 1 / (imagePosA.size[0] * factor * image.fromScale), + 1 / (imagePosA.size[1] * factor * image.fromScale) + ]); + + gl.uniform2fv(shader.u_patternscale_b, [ + 1 / (imagePosB.size[0] * factor * image.toScale), + 1 / (imagePosB.size[1] * factor * image.toScale) + ]); painter.spriteAtlas.bind(gl, true); @@ -76,9 +53,20 @@ function drawBackground(painter, layer, posMatrix) { } gl.disable(gl.STENCIL_TEST); - gl.bindBuffer(gl.ARRAY_BUFFER, painter.backgroundBuffer); - gl.vertexAttribPointer(shader.a_pos, painter.backgroundBuffer.itemSize, gl.SHORT, false, 0, 0); - gl.drawArrays(gl.TRIANGLE_STRIP, 0, painter.backgroundBuffer.itemCount); + gl.bindBuffer(gl.ARRAY_BUFFER, painter.tileExtentBuffer); + gl.vertexAttribPointer(shader.a_pos, painter.tileExtentBuffer.itemSize, gl.SHORT, false, 0, 0); + + var coveringTiles = pyramid.coveringTiles(transform); + for (var c = 0; c < coveringTiles.length; c++) { + var coord = coveringTiles[c]; + + var tile = new Tile(coord, transform.tileSize); + tile.calculateMatrices(coord.z, coord.x, coord.y, transform); + + gl.uniformMatrix4fv(shader.u_matrix, false, tile.posMatrix); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, painter.tileExtentBuffer.itemCount); + } + gl.enable(gl.STENCIL_TEST); gl.stencilMask(0x00); diff --git a/js/render/draw_fill.js b/js/render/draw_fill.js index 0a314ab5c3b..c22c422be43 100644 --- a/js/render/draw_fill.js +++ b/js/render/draw_fill.js @@ -1,7 +1,6 @@ 'use strict'; var browser = require('../util/browser'); -var mat3 = require('gl-matrix').mat3; module.exports = drawFill; @@ -133,21 +132,16 @@ function drawFill(painter, layer, posMatrix, tile) { var factor = (4096 / tile.tileSize) / Math.pow(2, painter.transform.tileZoom - tile.coord.z); - var matrixA = mat3.create(); - mat3.scale(matrixA, matrixA, [ + gl.uniform2fv(shader.u_patternscale_a, [ 1 / (imagePosA.size[0] * factor * image.fromScale), 1 / (imagePosA.size[1] * factor * image.fromScale) ]); - var matrixB = mat3.create(); - mat3.scale(matrixB, matrixB, [ + gl.uniform2fv(shader.u_patternscale_b, [ 1 / (imagePosB.size[0] * factor * image.toScale), 1 / (imagePosB.size[1] * factor * image.toScale) ]); - gl.uniformMatrix3fv(shader.u_patternmatrix_a, false, matrixA); - gl.uniformMatrix3fv(shader.u_patternmatrix_b, false, matrixB); - painter.spriteAtlas.bind(gl, true); } else { diff --git a/js/render/painter.js b/js/render/painter.js index 3c1f64cdd2a..e516920ff19 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -94,7 +94,7 @@ GLPainter.prototype.setup = function() { this.patternShader = gl.initializeShader('pattern', ['a_pos'], - ['u_matrix', 'u_pattern_tl_a', 'u_pattern_br_a', 'u_pattern_tl_b', 'u_pattern_br_b', 'u_mix', 'u_patternmatrix_a', 'u_patternmatrix_b', 'u_opacity', 'u_image'] + ['u_matrix', 'u_pattern_tl_a', 'u_pattern_br_a', 'u_pattern_tl_b', 'u_pattern_br_b', 'u_mix', 'u_patternscale_a', 'u_patternscale_b', 'u_opacity', 'u_image'] ); this.fillShader = gl.initializeShader('fill', diff --git a/shaders/pattern.vertex.glsl b/shaders/pattern.vertex.glsl index dff4469d2b6..9164a466f5e 100644 --- a/shaders/pattern.vertex.glsl +++ b/shaders/pattern.vertex.glsl @@ -1,6 +1,6 @@ uniform mat4 u_matrix; -uniform mat3 u_patternmatrix_a; -uniform mat3 u_patternmatrix_b; +uniform vec2 u_patternscale_a; +uniform vec2 u_patternscale_b; attribute vec2 a_pos; @@ -9,6 +9,6 @@ varying vec2 v_pos_b; void main() { gl_Position = u_matrix * vec4(a_pos, 0, 1); - v_pos_a = (u_patternmatrix_a * vec3(a_pos, 1)).xy; - v_pos_b = (u_patternmatrix_b * vec3(a_pos, 1)).xy; + v_pos_a = u_patternscale_a * a_pos; + v_pos_b = u_patternscale_b * a_pos; } From d0a519ac78f49edae2e880a974dd1039e1942985 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 27 Mar 2015 17:46:55 -0700 Subject: [PATCH 02/13] draw clipping IDs to stencil and use them later Instead of drawing the current tile's clipping area before starting a triangle, draw clipping masks for all tiles at the beginning. This will let us to switch to layer-by-layer rendering. --- js/render/draw_fill.js | 12 +++++------ js/render/painter.js | 48 ++++++++++++++++++++++++------------------ js/source/source.js | 9 +++++++- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/js/render/draw_fill.js b/js/render/draw_fill.js index c22c422be43..509e2fb62ec 100644 --- a/js/render/draw_fill.js +++ b/js/render/draw_fill.js @@ -20,15 +20,15 @@ function drawFill(painter, layer, posMatrix, tile) { // Draw the stencil mask. // We're only drawing to the first seven bits (== support a maximum of - // 127 overlapping polygons in one place before we get rendering errors). - gl.stencilMask(0x3F); + // 8 overlapping polygons in one place before we get rendering errors). + gl.stencilMask(0x07); gl.clear(gl.STENCIL_BUFFER_BIT); // Draw front facing triangles. Wherever the 0x80 bit is 1, we are // increasing the lower 7 bits by one if the triangle is a front-facing // triangle. This means that all visible polygons should be in CCW // orientation, while all holes (see below) are in CW orientation. - gl.stencilFunc(gl.NOTEQUAL, 0x80, 0x80); + gl.stencilFunc(gl.NOTEQUAL, tile.clipID, 0xF8); // When we do a nonzero fill, we count the number of times a pixel is // covered by a counterclockwise polygon, and subtract the number of @@ -79,7 +79,7 @@ function drawFill(painter, layer, posMatrix, tile) { if (strokeColor) { // If we defined a different color for the fill outline, we are - // going to ignore the bits in 0x3F and just care about the global + // going to ignore the bits in 0x07 and just care about the global // clipping mask. gl.stencilFunc(gl.EQUAL, 0x80, 0x80); } else { @@ -152,11 +152,11 @@ function drawFill(painter, layer, posMatrix, tile) { } // Only draw regions that we marked - gl.stencilFunc(gl.NOTEQUAL, 0x0, 0x3F); + gl.stencilFunc(gl.NOTEQUAL, 0x0, 0x07); gl.bindBuffer(gl.ARRAY_BUFFER, painter.tileExtentBuffer); gl.vertexAttribPointer(shader.a_pos, painter.tileExtentBuffer.itemSize, gl.SHORT, false, 0, 0); gl.drawArrays(gl.TRIANGLE_STRIP, 0, painter.tileExtentBuffer.itemCount); gl.stencilMask(0x00); - gl.stencilFunc(gl.EQUAL, 0x80, 0x80); + gl.stencilFunc(gl.EQUAL, tile.clipID, 0xF8); } diff --git a/js/render/painter.js b/js/render/painter.js index e516920ff19..d6b7bf656d6 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -161,35 +161,43 @@ GLPainter.prototype.clearStencil = function() { gl.clear(gl.STENCIL_BUFFER_BIT); }; -GLPainter.prototype.drawClippingMask = function(tile) { +GLPainter.prototype.drawClippingMasks = function(tiles) { var gl = this.gl; - gl.switchShader(this.fillShader, tile.posMatrix); gl.colorMask(false, false, false, false); - // Clear the entire stencil buffer, except for the 7th bit, which stores - // the global clipping mask that allows us to avoid drawing in regions of - // tiles we've already painted in. - gl.clearStencil(0x0); - gl.stencilMask(0xBF); - gl.clear(gl.STENCIL_BUFFER_BIT); + // Only write clipping IDs to the last 5 bits. The first three are used for drawing fills. + gl.stencilMask(0xF8); + // Tests will always pass, and ref value will be written to stencil buffer. + gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); + + var clipID = 1; + for (var i = 0; i < tiles.length; i++) { + var tile = tiles[i]; + tile.clipID = clipID << 3; + this._drawClippingMask(tile); + clipID++; + + } + + gl.stencilMask(0x00); + gl.colorMask(true, true, true, true); +}; - // The stencil test will fail always, meaning we set all pixels covered - // by this geometry to 0x80. We use the highest bit 0x80 to mark the regions - // we want to draw in. All pixels that have this bit *not* set will never be - // drawn in. - gl.stencilFunc(gl.EQUAL, 0xC0, 0x40); - gl.stencilMask(0xC0); - gl.stencilOp(gl.REPLACE, gl.KEEP, gl.KEEP); +GLPainter.prototype._drawClippingMask = function(tile) { + var gl = this.gl; + gl.stencilFunc(gl.ALWAYS, tile.clipID, 0xF8); + + gl.switchShader(this.fillShader, tile.posMatrix); // Draw the clipping mask gl.bindBuffer(gl.ARRAY_BUFFER, this.tileExtentBuffer); gl.vertexAttribPointer(this.fillShader.a_pos, this.tileExtentBuffer.itemSize, gl.SHORT, false, 8, 0); gl.drawArrays(gl.TRIANGLE_STRIP, 0, this.tileExtentBuffer.itemCount); +}; - gl.stencilFunc(gl.EQUAL, 0x80, 0x80); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); - gl.stencilMask(0x00); - gl.colorMask(true, true, true, true); +GLPainter.prototype._setClippingMask = function(tile) { + var gl = this.gl; + gl.stencilFunc(gl.EQUAL, tile.clipID, 0xF8); }; // Overridden by headless tests. @@ -241,7 +249,7 @@ GLPainter.prototype.render = function(style, options) { }; GLPainter.prototype.drawTile = function(tile, layers) { - this.drawClippingMask(tile); + this._setClippingMask(tile); this.drawLayers(layers, tile.posMatrix, tile); if (this.options.debug) { diff --git a/js/source/source.js b/js/source/source.js index 2488541fe24..6864c5ed2f2 100644 --- a/js/source/source.js +++ b/js/source/source.js @@ -45,6 +45,7 @@ exports._renderTiles = function(layers, painter) { if (!this._pyramid) return; + var tiles = []; var ids = this._pyramid.renderedIDs(); for (var i = 0; i < ids.length; i++) { var tile = this._pyramid.getTile(ids[i]), @@ -63,7 +64,13 @@ exports._renderTiles = function(layers, painter) { x += w * (1 << z); tile.calculateMatrices(z, x, y, painter.transform, painter); - painter.drawTile(tile, layers); + tiles.push(tile); + } + + painter.drawClippingMasks(tiles); + + for (var t = 0; t < tiles.length; t++) { + painter.drawTile(tiles[t], layers); } }; From 38f5a8168db894bc5a321c1f3d88ddf7e4217643 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 6 Apr 2015 19:58:43 -0700 Subject: [PATCH 03/13] draw layer-by-layer --- js/render/painter.js | 62 +++++++++++++++++++++++++-------- js/source/geojson_source.js | 3 +- js/source/raster_tile_source.js | 2 +- js/source/source.js | 31 ++++------------- js/source/tile.js | 4 ++- js/source/tile_pyramid.js | 2 +- js/source/vector_tile_source.js | 3 +- 7 files changed, 63 insertions(+), 44 deletions(-) diff --git a/js/render/painter.js b/js/render/painter.js index d6b7bf656d6..36f580b4742 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -4,6 +4,7 @@ var glutil = require('./gl_util'); var browser = require('../util/browser'); var mat4 = require('gl-matrix').mat4; var FrameHistory = require('./frame_history'); +var TileCoord = require('../source/tile_coord'); /* * Initialize a new painter object. @@ -161,7 +162,7 @@ GLPainter.prototype.clearStencil = function() { gl.clear(gl.STENCIL_BUFFER_BIT); }; -GLPainter.prototype.drawClippingMasks = function(tiles) { +GLPainter.prototype._drawClippingMasks = function(tiles) { var gl = this.gl; gl.colorMask(false, false, false, false); @@ -200,6 +201,21 @@ GLPainter.prototype._setClippingMask = function(tile) { gl.stencilFunc(gl.EQUAL, tile.clipID, 0xF8); }; +GLPainter.prototype._prepareTile = function(tile) { + var pos = TileCoord.fromID(tile.id), + z = pos.z, + x = pos.x, + y = pos.y, + w = pos.w; + + // if z > maxzoom then the tile is actually a overscaled maxzoom tile, + // so calculate the matrix the maxzoom tile would use. + z = Math.min(z, tile.sourceMaxZoom); + + x += w * (1 << z); + tile.calculateMatrices(z, x, y, this.transform, this); +}; + // Overridden by headless tests. GLPainter.prototype.prepareBuffers = function() {}; GLPainter.prototype.bindDefaultFramebuffer = function() { @@ -240,35 +256,53 @@ GLPainter.prototype.render = function(style, options) { if (source) { this.clearStencil(); - source.render(group, this); + var tiles = source.renderedTiles(); + + for (var t = 0; t < tiles.length; t++) { + this._prepareTile(tiles[t]); + } + + this._drawClippingMasks(tiles); + this.drawLayers(group, tiles); } else if (group.source === undefined) { - this.drawLayers(group, this.identityMatrix); + + for (var l = 0; l < group.length; l++) { + var layer = group[l]; + if (layer.hidden) + continue; + + draw.background(this, layer, this.identityMatrix); + } } } }; -GLPainter.prototype.drawTile = function(tile, layers) { - this._setClippingMask(tile); - this.drawLayers(layers, tile.posMatrix, tile); +GLPainter.prototype.drawLayer = function(layer, tiles) { + for (var t = 0; t < tiles.length; t++) { + var tile = tiles[t]; - if (this.options.debug) { - draw.debug(this, tile); + this._setClippingMask(tile); + draw[layer.type](this, layer, tile.posMatrix, tile); + + if (this.options.vertices) { + draw.vertices(this, layer, tile.posMatrix, tile); + } + + if (this.options.debug) { + draw.debug(this, tile); + } } }; -GLPainter.prototype.drawLayers = function(layers, matrix, tile) { +GLPainter.prototype.drawLayers = function(layers, tiles) { for (var i = layers.length - 1; i >= 0; i--) { var layer = layers[i]; if (layer.hidden) continue; - draw[layer.type](this, layer, matrix, tile); - - if (this.options.vertices) { - draw.vertices(this, layer, matrix, tile); - } + this.drawLayer(layer, tiles); } }; diff --git a/js/source/geojson_source.js b/js/source/geojson_source.js index 364df215dca..d2b265463d5 100644 --- a/js/source/geojson_source.js +++ b/js/source/geojson_source.js @@ -97,7 +97,8 @@ GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototy this._pyramid.reload(); }, - render: Source._renderTiles, + renderedTiles: Source._renderedTiles, + featuresAt: Source._vectorFeaturesAt, _updateData: function() { diff --git a/js/source/raster_tile_source.js b/js/source/raster_tile_source.js index d3c886360c3..23a547599d6 100644 --- a/js/source/raster_tile_source.js +++ b/js/source/raster_tile_source.js @@ -34,7 +34,7 @@ RasterTileSource.prototype = util.inherit(Evented, { } }, - render: Source._renderTiles, + renderedTiles: Source._renderedTiles, _loadTile: function(tile) { ajax.getImage(normalizeURL(tile.coord.url(this.tiles), this.url), function(err, img) { diff --git a/js/source/source.js b/js/source/source.js index 6864c5ed2f2..d44d761237f 100644 --- a/js/source/source.js +++ b/js/source/source.js @@ -41,37 +41,18 @@ exports._loadTileJSON = function(options) { } }; -exports._renderTiles = function(layers, painter) { +exports._renderedTiles = function() { + var tiles = []; + if (!this._pyramid) - return; + return tiles; - var tiles = []; var ids = this._pyramid.renderedIDs(); for (var i = 0; i < ids.length; i++) { - var tile = this._pyramid.getTile(ids[i]), - // coord is different than tile.coord for wrapped tiles since the actual - // tile object is shared between all the visible copies of that tile. - coord = TileCoord.fromID(ids[i]), - z = coord.z, - x = coord.x, - y = coord.y, - w = coord.w; - - // if z > maxzoom then the tile is actually a overscaled maxzoom tile, - // so calculate the matrix the maxzoom tile would use. - z = Math.min(z, this.maxzoom); - - x += w * (1 << z); - tile.calculateMatrices(z, x, y, painter.transform, painter); - - tiles.push(tile); + tiles.push(this._pyramid.getTile(ids[i])); } - painter.drawClippingMasks(tiles); - - for (var t = 0; t < tiles.length; t++) { - painter.drawTile(tiles[t], layers); - } + return tiles; }; exports._vectorFeaturesAt = function(coord, params, callback) { diff --git a/js/source/tile.js b/js/source/tile.js index 95ca8cb0024..57a44b5f91f 100644 --- a/js/source/tile.js +++ b/js/source/tile.js @@ -14,13 +14,15 @@ module.exports = Tile; * * @param {Coordinate} coord * @param {number} size + * @param {sourceMaxZoom} the tile's source's maximum zoom level */ -function Tile(coord, size) { +function Tile(coord, size, sourceMaxZoom) { this.coord = coord; this.uid = util.uniqueId(); this.loaded = false; this.uses = 0; this.tileSize = size; + this.sourceMaxZoom = sourceMaxZoom; } Tile.prototype = { diff --git a/js/source/tile_pyramid.js b/js/source/tile_pyramid.js index 617d3ddb49e..dce403f117a 100644 --- a/js/source/tile_pyramid.js +++ b/js/source/tile_pyramid.js @@ -263,7 +263,7 @@ TilePyramid.prototype = { if (!tile) { var zoom = coord.z; var overscaling = zoom > this.maxzoom ? Math.pow(2, zoom - this.maxzoom) : 1; - tile = new Tile(wrapped, this.tileSize * overscaling); + tile = new Tile(wrapped, this.tileSize * overscaling, this.maxzoom); this._load(tile); } diff --git a/js/source/vector_tile_source.js b/js/source/vector_tile_source.js index b1f36fb4c9d..ee4d256ec26 100644 --- a/js/source/vector_tile_source.js +++ b/js/source/vector_tile_source.js @@ -53,7 +53,8 @@ VectorTileSource.prototype = util.inherit(Evented, { } }, - render: Source._renderTiles, + renderedTiles: Source._renderedTiles, + featuresAt: Source._vectorFeaturesAt, _loadTile: function(tile) { From 86ac40549901c7fff2a53ab9159e2f4014d9e01f Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 14 Apr 2015 13:50:45 -0700 Subject: [PATCH 04/13] switch to two passes and use depth buffer The first pass draws opaque objects top-down without blending. The second pass draws translucent objects bottom-up with blending. The depth buffer is used to drop fragments below opaque fills. --- js/render/draw_background.js | 8 ++ js/render/draw_fill.js | 15 +++- js/render/draw_line.js | 4 + js/render/draw_symbol.js | 32 ++++---- js/render/painter.js | 143 ++++++++++++++++++++++------------- js/util/browser/canvas.js | 2 +- 6 files changed, 135 insertions(+), 69 deletions(-) diff --git a/js/render/draw_background.js b/js/render/draw_background.js index 8ecaacfce01..7cfc4eb72c0 100644 --- a/js/render/draw_background.js +++ b/js/render/draw_background.js @@ -8,6 +8,7 @@ var pyramid = new TilePyramid({ tileSize: 512 }); module.exports = drawBackground; function drawBackground(painter, layer, posMatrix) { + return; var gl = painter.gl; var transform = painter.transform; var color = layer.paint['background-color']; @@ -18,7 +19,11 @@ function drawBackground(painter, layer, posMatrix) { var imagePosA = image ? painter.spriteAtlas.getPosition(image.from, true) : null; var imagePosB = image ? painter.spriteAtlas.getPosition(image.to, true) : null; + painter.setSublayer(0); if (imagePosA && imagePosB) { + + if (painter.opaquePass) return; + // Draw texture fill shader = painter.patternShader; gl.switchShader(shader, posMatrix); @@ -47,6 +52,9 @@ function drawBackground(painter, layer, posMatrix) { } else { // Draw filling rectangle. + + if (painter.opaquePass !== (color[3] === 1)) return; + shader = painter.fillShader; gl.switchShader(shader, posMatrix); gl.uniform4fv(shader.u_color, color); diff --git a/js/render/draw_fill.js b/js/render/draw_fill.js index 509e2fb62ec..7b094f89e83 100644 --- a/js/render/draw_fill.js +++ b/js/render/draw_fill.js @@ -10,11 +10,17 @@ function drawFill(painter, layer, posMatrix, tile) { var elementGroups = tile.elementGroups[layer.ref || layer.id]; if (!elementGroups) return; + var color = layer.paint['fill-color']; + var image = layer.paint['fill-image']; + + if (image && painter.opaquePass) return; + if (!image && painter.opaquePass !== (color[3] === 1)) return; + + painter.setSublayer(0); + var gl = painter.gl; var translatedPosMatrix = painter.translateMatrix(posMatrix, tile, layer.paint['fill-translate'], layer.paint['fill-translate-anchor']); - var color = layer.paint['fill-color']; - var vertex, elements, group, count; // Draw the stencil mask. @@ -39,6 +45,8 @@ function drawFill(painter, layer, posMatrix, tile) { // When drawing a shape, we first draw all shapes to the stencil buffer // and incrementing all areas where polygons are gl.colorMask(false, false, false, false); + gl.disable(gl.DEPTH_TEST); + gl.depthMask(false); // Draw the actual triangle fan into the stencil buffer. gl.switchShader(painter.fillShader, translatedPosMatrix); @@ -64,6 +72,8 @@ function drawFill(painter, layer, posMatrix, tile) { // Now that we have the stencil mask in the stencil buffer, we can start // writing to the color buffer. gl.colorMask(true, true, true, true); + gl.depthMask(true); + gl.enable(gl.DEPTH_TEST); // From now on, we don't want to update the stencil buffer anymore. gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); @@ -110,7 +120,6 @@ function drawFill(painter, layer, posMatrix, tile) { } } - var image = layer.paint['fill-image']; var opacity = layer.paint['fill-opacity'] || 1; var shader; diff --git a/js/render/draw_line.js b/js/render/draw_line.js index 9f7f3b4ffd2..520a604195d 100644 --- a/js/render/draw_line.js +++ b/js/render/draw_line.js @@ -13,6 +13,10 @@ var mat2 = require('gl-matrix').mat2; * @returns {undefined} draws with the painter */ module.exports = function drawLine(painter, layer, posMatrix, tile) { + + if (painter.opaquePass) return; + painter.setSublayer(0); + // No data if (!tile.buffers) return; var elementGroups = tile.elementGroups[layer.ref || layer.id]; diff --git a/js/render/draw_symbol.js b/js/render/draw_symbol.js index 4d8ea2e2d40..8d1c002ad24 100644 --- a/js/render/draw_symbol.js +++ b/js/render/draw_symbol.js @@ -8,6 +8,9 @@ var drawCollisionDebug = require('./draw_collision_debug'); module.exports = drawSymbols; function drawSymbols(painter, layer, posMatrix, tile) { + + if (painter.opaquePass) return; + // No data if (!tile.buffers) return; var elementGroups = tile.elementGroups[layer.ref || layer.id]; @@ -132,20 +135,6 @@ function drawSymbol(painter, layer, posMatrix, tile, elementGroups, prefix, sdf) var haloOffset = 6; var gamma = 0.105 * defaultSizes[prefix] / fontSize / browser.devicePixelRatio; - gl.uniform1f(shader.u_gamma, gamma * gammaScale); - gl.uniform4fv(shader.u_color, layer.paint[prefix + '-color']); - gl.uniform1f(shader.u_buffer, (256 - 64) / 256); - - for (var i = 0; i < elementGroups.groups.length; i++) { - group = elementGroups.groups[i]; - offset = group.vertexStartIndex * vertex.itemSize; - vertex.bind(gl, shader, offset); - - count = group.elementLength * 3; - elementOffset = group.elementStartIndex * elements.itemSize; - gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, elementOffset); - } - if (layer.paint[prefix + '-halo-color']) { // Draw halo underneath the text. gl.uniform1f(shader.u_gamma, (layer.paint[prefix + '-halo-blur'] * blurOffset / fontScale / sdfPx + gamma) * gammaScale); @@ -162,6 +151,21 @@ function drawSymbol(painter, layer, posMatrix, tile, elementGroups, prefix, sdf) gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, elementOffset); } } + + gl.uniform1f(shader.u_gamma, gamma * gammaScale); + gl.uniform4fv(shader.u_color, layer.paint[prefix + '-color']); + gl.uniform1f(shader.u_buffer, (256 - 64) / 256); + + for (var i = 0; i < elementGroups.groups.length; i++) { + group = elementGroups.groups[i]; + offset = group.vertexStartIndex * vertex.itemSize; + vertex.bind(gl, shader, offset); + + count = group.elementLength * 3; + elementOffset = group.elementStartIndex * elements.itemSize; + gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, elementOffset); + } + } else { gl.uniform1f(shader.u_opacity, layer.paint['icon-opacity']); for (var k = 0; k < elementGroups.groups.length; k++) { diff --git a/js/render/painter.js b/js/render/painter.js index 36f580b4742..f8ee97b3203 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -4,7 +4,6 @@ var glutil = require('./gl_util'); var browser = require('../util/browser'); var mat4 = require('gl-matrix').mat4; var FrameHistory = require('./frame_history'); -var TileCoord = require('../source/tile_coord'); /* * Initialize a new painter object. @@ -23,6 +22,8 @@ function GLPainter(gl, transform) { this.frameHistory = new FrameHistory(); this.setup(); + + this.depthEpsilon = 1 / Math.pow(2, 16); } /* @@ -47,10 +48,13 @@ GLPainter.prototype.setup = function() { // We are blending the new pixels *behind* the existing pixels. That way we can // draw front-to-back and use then stencil buffer to cull opaque pixels early. gl.enable(gl.BLEND); - gl.blendFunc(gl.ONE_MINUS_DST_ALPHA, gl.ONE); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); gl.enable(gl.STENCIL_TEST); + gl.enable(gl.DEPTH_TEST); + gl.depthFunc(gl.LEQUAL); + // Initialize shaders this.debugShader = gl.initializeShader('debug', ['a_pos'], @@ -162,9 +166,18 @@ GLPainter.prototype.clearStencil = function() { gl.clear(gl.STENCIL_BUFFER_BIT); }; +GLPainter.prototype.clearDepth = function() { + var gl = this.gl; + gl.clearDepth(1); + gl.depthMask(true); + gl.clear(gl.DEPTH_BUFFER_BIT); +}; + GLPainter.prototype._drawClippingMasks = function(tiles) { var gl = this.gl; gl.colorMask(false, false, false, false); + gl.depthMask(false); + gl.disable(gl.DEPTH_TEST); // Only write clipping IDs to the last 5 bits. The first three are used for drawing fills. gl.stencilMask(0xF8); @@ -182,6 +195,8 @@ GLPainter.prototype._drawClippingMasks = function(tiles) { gl.stencilMask(0x00); gl.colorMask(true, true, true, true); + gl.depthMask(true); + gl.enable(gl.DEPTH_TEST); }; GLPainter.prototype._drawClippingMask = function(tile) { @@ -202,11 +217,11 @@ GLPainter.prototype._setClippingMask = function(tile) { }; GLPainter.prototype._prepareTile = function(tile) { - var pos = TileCoord.fromID(tile.id), - z = pos.z, - x = pos.x, - y = pos.y, - w = pos.w; + var coord = tile.coord, + z = coord.z, + x = coord.x, + y = coord.y, + w = coord.w; // if z > maxzoom then the tile is actually a overscaled maxzoom tile, // so calculate the matrix the maxzoom tile would use. @@ -216,6 +231,21 @@ GLPainter.prototype._prepareTile = function(tile) { tile.calculateMatrices(z, x, y, this.transform, this); }; +GLPainter.prototype._prepareSource = function(source) { + if (source) { + this.clearStencil(); + var tiles = source.renderedTiles(); + + for (var t = 0; t < tiles.length; t++) { + this._prepareTile(tiles[t]); + } + + this._drawClippingMasks(tiles); + } + + return tiles; +}; + // Overridden by headless tests. GLPainter.prototype.prepareBuffers = function() {}; GLPainter.prototype.bindDefaultFramebuffer = function() { @@ -249,35 +279,70 @@ GLPainter.prototype.render = function(style, options) { this.prepareBuffers(); this.clearColor(); + this.clearDepth(); + var numLayers = style._order.length; + this.depthRangeSize = 1 - numLayers * 3 * this.depthEpsilon; + this.currentLayer = numLayers; + + var group, layer, tiles; for (var i = style._groups.length - 1; i >= 0; i--) { - var group = style._groups[i]; - var source = style.sources[group.source]; + group = style._groups[i]; + tiles = this._prepareSource(style.sources[group.source]); + + this.setOpaque(); - if (source) { - this.clearStencil(); - var tiles = source.renderedTiles(); + for (var l = group.length - 1; l >= 0; l--) { + layer = group[l]; + this.currentLayer--; - for (var t = 0; t < tiles.length; t++) { - this._prepareTile(tiles[t]); + if (layer.hidden) + continue; + + if (group.source === undefined) { + draw.background(this, layer, this.identityMatrix, undefined); + } else { + this.drawLayer(layer, tiles); } + } + } + + this.currentLayer = 0; - this._drawClippingMasks(tiles); - this.drawLayers(group, tiles); + for (var m = 0; m < style._groups.length; m++) { + group = style._groups[m]; + tiles = this._prepareSource(style.sources[group.source]); - } else if (group.source === undefined) { + this.setTranslucent(); - for (var l = 0; l < group.length; l++) { - var layer = group[l]; - if (layer.hidden) - continue; + for (var k = 0; k < group.length; k++) { + layer = group[k]; + this.currentLayer++; - draw.background(this, layer, this.identityMatrix); + if (layer.hidden) + continue; + + if (group.source === undefined) { + draw.background(this, layer, this.identityMatrix, undefined); + } else { + this.drawLayer(layer, tiles); } + } } }; +GLPainter.prototype.setOpaque = function() { + this.gl.disable(this.gl.BLEND); + this.gl.depthMask(true); + this.opaquePass = true; +}; + +GLPainter.prototype.setTranslucent = function() { + this.gl.enable(this.gl.BLEND); + this.opaquePass = false; +}; + GLPainter.prototype.drawLayer = function(layer, tiles) { for (var t = 0; t < tiles.length; t++) { var tile = tiles[t]; @@ -295,35 +360,11 @@ GLPainter.prototype.drawLayer = function(layer, tiles) { } }; -GLPainter.prototype.drawLayers = function(layers, tiles) { - for (var i = layers.length - 1; i >= 0; i--) { - var layer = layers[i]; - - if (layer.hidden) - continue; - - this.drawLayer(layer, tiles); - } -}; - -// Draws non-opaque areas. This is for debugging purposes. -GLPainter.prototype.drawStencilBuffer = function() { - var gl = this.gl; - gl.switchShader(this.fillShader, this.identityMatrix); - - // Blend to the front, not the back. - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - gl.stencilMask(0x00); - gl.stencilFunc(gl.EQUAL, 0x80, 0x80); - - // Drw the filling quad where the stencil buffer isn't set. - gl.bindBuffer(gl.ARRAY_BUFFER, this.backgroundBuffer); - gl.vertexAttribPointer(this.fillShader.a_pos, this.backgroundBuffer.itemSize, gl.SHORT, false, 0, 0); - gl.uniform4fv(this.fillShader.u_color, [0, 0, 0, 0.5]); - gl.drawArrays(gl.TRIANGLE_STRIP, 0, this.backgroundBuffer.itemCount); - - // Revert blending mode to blend to the back. - gl.blendFunc(gl.ONE_MINUS_DST_ALPHA, gl.ONE); +GLPainter.prototype.setSublayer = function(n) { + var maxSublayers = 3; + var farDepth = 1 - ((1 + this.currentLayer) * maxSublayers + n) * this.depthEpsilon; + var nearDepth = farDepth - this.depthRangeSize; + this.gl.depthRange(nearDepth, farDepth); }; GLPainter.prototype.translateMatrix = function(matrix, tile, translate, anchor) { diff --git a/js/util/browser/canvas.js b/js/util/browser/canvas.js index d7a4048f16c..ba15c831253 100644 --- a/js/util/browser/canvas.js +++ b/js/util/browser/canvas.js @@ -36,7 +36,7 @@ Canvas.prototype._contextAttributes = { antialias: false, alpha: true, stencil: true, - depth: false + depth: true }; Canvas.prototype.getWebGLContext = function(failIfMajorPerformanceCaveat) { From 54ff03941c6124a57cc4cc87ffa39c6eccd924d9 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 14 Apr 2015 17:47:11 -0700 Subject: [PATCH 05/13] don't write lines and symbols to depth buffer --- js/render/draw_fill.js | 6 +++--- js/render/draw_line.js | 1 + js/render/draw_symbol.js | 5 +++++ js/render/painter.js | 17 +++++++++++++---- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/js/render/draw_fill.js b/js/render/draw_fill.js index 7b094f89e83..81a6873718d 100644 --- a/js/render/draw_fill.js +++ b/js/render/draw_fill.js @@ -46,7 +46,7 @@ function drawFill(painter, layer, posMatrix, tile) { // and incrementing all areas where polygons are gl.colorMask(false, false, false, false); gl.disable(gl.DEPTH_TEST); - gl.depthMask(false); + painter.depthMask(false); // Draw the actual triangle fan into the stencil buffer. gl.switchShader(painter.fillShader, translatedPosMatrix); @@ -72,7 +72,7 @@ function drawFill(painter, layer, posMatrix, tile) { // Now that we have the stencil mask in the stencil buffer, we can start // writing to the color buffer. gl.colorMask(true, true, true, true); - gl.depthMask(true); + painter.depthMask(true); gl.enable(gl.DEPTH_TEST); // From now on, we don't want to update the stencil buffer anymore. @@ -83,7 +83,7 @@ function drawFill(painter, layer, posMatrix, tile) { // Because we're drawing top-to-bottom, and we update the stencil mask // below, we have to draw the outline first (!) - if (layer.paint['fill-antialias'] === true && !(layer.paint['fill-image'] && !strokeColor)) { + if (false && layer.paint['fill-antialias'] === true && !(layer.paint['fill-image'] && !strokeColor)) { gl.switchShader(painter.outlineShader, translatedPosMatrix); gl.lineWidth(2 * browser.devicePixelRatio); diff --git a/js/render/draw_line.js b/js/render/draw_line.js index 520a604195d..1f74626bb67 100644 --- a/js/render/draw_line.js +++ b/js/render/draw_line.js @@ -16,6 +16,7 @@ module.exports = function drawLine(painter, layer, posMatrix, tile) { if (painter.opaquePass) return; painter.setSublayer(0); + painter.depthMask(false); // No data if (!tile.buffers) return; diff --git a/js/render/draw_symbol.js b/js/render/draw_symbol.js index 8d1c002ad24..f0fabbd49fa 100644 --- a/js/render/draw_symbol.js +++ b/js/render/draw_symbol.js @@ -30,6 +30,10 @@ function drawSymbols(painter, layer, posMatrix, tile) { gl.disable(gl.STENCIL_TEST); } + painter.setSublayer(0); + painter.depthMask(false); + gl.disable(gl.DEPTH_TEST); + if (elementGroups.text.groups.length) { drawSymbol(painter, layer, posMatrix, tile, elementGroups.text, 'text', true); } @@ -42,6 +46,7 @@ function drawSymbols(painter, layer, posMatrix, tile) { if (drawAcrossEdges) { gl.enable(gl.STENCIL_TEST); } + gl.enable(gl.DEPTH_TEST); } var defaultSizes = { diff --git a/js/render/painter.js b/js/render/painter.js index f8ee97b3203..db6328d02e4 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -55,6 +55,9 @@ GLPainter.prototype.setup = function() { gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); + this._depthMask = false; + gl.depthMask(false); + // Initialize shaders this.debugShader = gl.initializeShader('debug', ['a_pos'], @@ -169,14 +172,14 @@ GLPainter.prototype.clearStencil = function() { GLPainter.prototype.clearDepth = function() { var gl = this.gl; gl.clearDepth(1); - gl.depthMask(true); + this.depthMask(true); gl.clear(gl.DEPTH_BUFFER_BIT); }; GLPainter.prototype._drawClippingMasks = function(tiles) { var gl = this.gl; gl.colorMask(false, false, false, false); - gl.depthMask(false); + this.depthMask(false); gl.disable(gl.DEPTH_TEST); // Only write clipping IDs to the last 5 bits. The first three are used for drawing fills. @@ -195,7 +198,7 @@ GLPainter.prototype._drawClippingMasks = function(tiles) { gl.stencilMask(0x00); gl.colorMask(true, true, true, true); - gl.depthMask(true); + this.depthMask(true); gl.enable(gl.DEPTH_TEST); }; @@ -334,7 +337,6 @@ GLPainter.prototype.render = function(style, options) { GLPainter.prototype.setOpaque = function() { this.gl.disable(this.gl.BLEND); - this.gl.depthMask(true); this.opaquePass = true; }; @@ -343,6 +345,13 @@ GLPainter.prototype.setTranslucent = function() { this.opaquePass = false; }; +GLPainter.prototype.depthMask = function(mask) { + if (mask !== this._depthMask) { + this._depthMask = mask; + this.gl.depthMask(mask); + } +}; + GLPainter.prototype.drawLayer = function(layer, tiles) { for (var t = 0; t < tiles.length; t++) { var tile = tiles[t]; From 9fd120bcbbff282dd831c51020a8c4147bf4422e Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 21 Apr 2015 16:23:33 -0700 Subject: [PATCH 06/13] fix background and icons --- js/render/draw_background.js | 1 - js/render/draw_symbol.js | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/js/render/draw_background.js b/js/render/draw_background.js index 7cfc4eb72c0..044ac3b311f 100644 --- a/js/render/draw_background.js +++ b/js/render/draw_background.js @@ -8,7 +8,6 @@ var pyramid = new TilePyramid({ tileSize: 512 }); module.exports = drawBackground; function drawBackground(painter, layer, posMatrix) { - return; var gl = painter.gl; var transform = painter.transform; var color = layer.paint['background-color']; diff --git a/js/render/draw_symbol.js b/js/render/draw_symbol.js index f0fabbd49fa..fdec0765f85 100644 --- a/js/render/draw_symbol.js +++ b/js/render/draw_symbol.js @@ -34,12 +34,12 @@ function drawSymbols(painter, layer, posMatrix, tile) { painter.depthMask(false); gl.disable(gl.DEPTH_TEST); - if (elementGroups.text.groups.length) { - drawSymbol(painter, layer, posMatrix, tile, elementGroups.text, 'text', true); - } if (elementGroups.icon.groups.length) { drawSymbol(painter, layer, posMatrix, tile, elementGroups.icon, 'icon', elementGroups.sdfIcons); } + if (elementGroups.text.groups.length) { + drawSymbol(painter, layer, posMatrix, tile, elementGroups.text, 'text', true); + } drawCollisionDebug(painter, layer, posMatrix, tile); From 3de8bf1e198565f1b352e0418e01f15bb08af8b7 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 23 Apr 2015 10:58:53 -0700 Subject: [PATCH 07/13] move tile loop to inside of drawLine/drawFill/etc --- js/render/draw_collision_debug.js | 4 +- js/render/draw_fill.js | 30 ++++--- js/render/draw_line.js | 125 +++++++++++++++++------------- js/render/draw_symbol.js | 46 ++++++++--- js/render/gl_util.js | 17 +--- js/render/painter.js | 22 ++---- js/source/tile.js | 9 +++ 7 files changed, 144 insertions(+), 109 deletions(-) diff --git a/js/render/draw_collision_debug.js b/js/render/draw_collision_debug.js index b3574c865dd..cd613c8ccfd 100644 --- a/js/render/draw_collision_debug.js +++ b/js/render/draw_collision_debug.js @@ -12,8 +12,10 @@ function drawPlacementDebug(painter, layer, posMatrix, tile) { var shader = painter.collisionBoxShader; gl.enable(gl.STENCIL_TEST); + painter.setClippingMask(tile); - gl.switchShader(shader, posMatrix); + gl.switchShader(shader); + gl.uniformMatrix4fv(shader.u_matrix, false, posMatrix); buffer.bind(gl, shader); gl.lineWidth(3); diff --git a/js/render/draw_fill.js b/js/render/draw_fill.js index 81a6873718d..ca40cdda21e 100644 --- a/js/render/draw_fill.js +++ b/js/render/draw_fill.js @@ -4,7 +4,15 @@ var browser = require('../util/browser'); module.exports = drawFill; -function drawFill(painter, layer, posMatrix, tile) { +function drawFill(painter, layer, tiles) { + for (var n = 0; n < tiles.length; n++) { + var tile = tiles[n]; + drawFillTile(painter, layer, tile.posMatrix, tile); + } +} + +function drawFillTile(painter, layer, posMatrix, tile) { + // No data if (!tile.buffers) return; var elementGroups = tile.elementGroups[layer.ref || layer.id]; @@ -34,22 +42,22 @@ function drawFill(painter, layer, posMatrix, tile) { // increasing the lower 7 bits by one if the triangle is a front-facing // triangle. This means that all visible polygons should be in CCW // orientation, while all holes (see below) are in CW orientation. - gl.stencilFunc(gl.NOTEQUAL, tile.clipID, 0xF8); + gl.stencilFunc(gl.EQUAL, tile.clipID, 0xF8); // When we do a nonzero fill, we count the number of times a pixel is // covered by a counterclockwise polygon, and subtract the number of // times it is "uncovered" by a clockwise polygon. - gl.stencilOpSeparate(gl.FRONT, gl.INCR_WRAP, gl.KEEP, gl.KEEP); - gl.stencilOpSeparate(gl.BACK, gl.DECR_WRAP, gl.KEEP, gl.KEEP); + gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP); + gl.stencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP); // When drawing a shape, we first draw all shapes to the stencil buffer // and incrementing all areas where polygons are gl.colorMask(false, false, false, false); - gl.disable(gl.DEPTH_TEST); painter.depthMask(false); // Draw the actual triangle fan into the stencil buffer. - gl.switchShader(painter.fillShader, translatedPosMatrix); + gl.switchShader(painter.fillShader); + gl.uniformMatrix4fv(painter.fillShader.u_matrix, false, translatedPosMatrix); // Draw all buffers vertex = tile.buffers.fillVertex; @@ -73,7 +81,6 @@ function drawFill(painter, layer, posMatrix, tile) { // writing to the color buffer. gl.colorMask(true, true, true, true); painter.depthMask(true); - gl.enable(gl.DEPTH_TEST); // From now on, we don't want to update the stencil buffer anymore. gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); @@ -84,7 +91,8 @@ function drawFill(painter, layer, posMatrix, tile) { // Because we're drawing top-to-bottom, and we update the stencil mask // below, we have to draw the outline first (!) if (false && layer.paint['fill-antialias'] === true && !(layer.paint['fill-image'] && !strokeColor)) { - gl.switchShader(painter.outlineShader, translatedPosMatrix); + gl.switchShader(painter.outlineShader); + gl.uniformMatrix4fv(painter.outlineShader.u_matrix, false, translatedPosMatrix); gl.lineWidth(2 * browser.devicePixelRatio); if (strokeColor) { @@ -130,7 +138,8 @@ function drawFill(painter, layer, posMatrix, tile) { if (!imagePosA || !imagePosB) return; shader = painter.patternShader; - gl.switchShader(shader, posMatrix); + gl.switchShader(shader); + gl.uniformMatrix4fv(shader.u_matrix, false, posMatrix); gl.uniform1i(shader.u_image, 0); gl.uniform2fv(shader.u_pattern_tl_a, imagePosA.tl); gl.uniform2fv(shader.u_pattern_br_a, imagePosA.br); @@ -156,7 +165,8 @@ function drawFill(painter, layer, posMatrix, tile) { } else { // Draw filling rectangle. shader = painter.fillShader; - gl.switchShader(shader, posMatrix); + gl.switchShader(shader); + gl.uniformMatrix4fv(shader.u_matrix, false, posMatrix); gl.uniform4fv(shader.u_color, color); } diff --git a/js/render/draw_line.js b/js/render/draw_line.js index 1f74626bb67..7c644b0a5fb 100644 --- a/js/render/draw_line.js +++ b/js/render/draw_line.js @@ -12,16 +12,20 @@ var mat2 = require('gl-matrix').mat2; * @param {Tile} tile * @returns {undefined} draws with the painter */ -module.exports = function drawLine(painter, layer, posMatrix, tile) { +module.exports = function drawLine(painter, layer, tiles) { if (painter.opaquePass) return; painter.setSublayer(0); painter.depthMask(false); - // No data - if (!tile.buffers) return; - var elementGroups = tile.elementGroups[layer.ref || layer.id]; - if (!elementGroups) return; + var hasData = false; + for (var t = 0; t < tiles.length; t++) { + if (tiles[t].hasLayerData(layer)) { + hasData = true; + break; + } + } + if (!hasData) return; var gl = painter.gl; @@ -47,10 +51,7 @@ module.exports = function drawLine(painter, layer, posMatrix, tile) { } var outset = offset + edgeWidth + antialiasing / 2 + shift; - var color = layer.paint['line-color']; - var ratio = painter.transform.scale / (1 << tile.coord.z) / (4096 / tile.tileSize); - var vtxMatrix = painter.translateMatrix(posMatrix, tile, layer.paint['line-translate'], layer.paint['line-translate-anchor']); var tr = painter.transform; @@ -65,61 +66,39 @@ module.exports = function drawLine(painter, layer, posMatrix, tile) { var x = tr.height / 2 * Math.tan(tr._pitch); var extra = (topedgelength + x) / topedgelength - 1; - // how much the tile is overscaled by - var overscaling = tile.tileSize / painter.transform.tileSize; - - var shader; - - var dasharray = layer.paint['line-dasharray']; var image = layer.paint['line-image']; + var shader, posA, posB, imagePosA, imagePosB; if (dasharray) { - shader = painter.linesdfpatternShader; - gl.switchShader(shader, vtxMatrix, tile.exMatrix); + gl.switchShader(shader); gl.uniform2fv(shader.u_linewidth, [ outset, inset ]); - gl.uniform1f(shader.u_ratio, ratio); gl.uniform1f(shader.u_blur, blur); gl.uniform4fv(shader.u_color, color); - var posA = painter.lineAtlas.getDash(dasharray.from, layer.layout['line-cap'] === 'round'); - var posB = painter.lineAtlas.getDash(dasharray.to, layer.layout['line-cap'] === 'round'); + posA = painter.lineAtlas.getDash(dasharray.from, layer.layout['line-cap'] === 'round'); + posB = painter.lineAtlas.getDash(dasharray.to, layer.layout['line-cap'] === 'round'); painter.lineAtlas.bind(gl); - var patternratio = Math.pow(2, Math.floor(Math.log(painter.transform.scale) / Math.LN2) - tile.coord.z) / 8 * overscaling; - var scaleA = [patternratio / posA.width / dasharray.fromScale, -posA.height / 2]; - var gammaA = painter.lineAtlas.width / (dasharray.fromScale * posA.width * 256 * browser.devicePixelRatio) / 2; - var scaleB = [patternratio / posB.width / dasharray.toScale, -posB.height / 2]; - var gammaB = painter.lineAtlas.width / (dasharray.toScale * posB.width * 256 * browser.devicePixelRatio) / 2; - - gl.uniform2fv(shader.u_patternscale_a, scaleA); gl.uniform1f(shader.u_tex_y_a, posA.y); - gl.uniform2fv(shader.u_patternscale_b, scaleB); gl.uniform1f(shader.u_tex_y_b, posB.y); - gl.uniform1i(shader.u_image, 0); - gl.uniform1f(shader.u_sdfgamma, Math.max(gammaA, gammaB)); gl.uniform1f(shader.u_mix, dasharray.t); } else if (image) { - var imagePosA = painter.spriteAtlas.getPosition(image.from, true); - var imagePosB = painter.spriteAtlas.getPosition(image.to, true); + imagePosA = painter.spriteAtlas.getPosition(image.from, true); + imagePosB = painter.spriteAtlas.getPosition(image.to, true); if (!imagePosA || !imagePosB) return; - var factor = 4096 / tile.tileSize / Math.pow(2, painter.transform.tileZoom - tile.coord.z) * overscaling; painter.spriteAtlas.bind(gl, true); shader = painter.linepatternShader; - gl.switchShader(shader, vtxMatrix, tile.exMatrix); + gl.switchShader(shader); gl.uniform2fv(shader.u_linewidth, [ outset, inset ]); - gl.uniform1f(shader.u_ratio, ratio); gl.uniform1f(shader.u_blur, blur); - - gl.uniform2fv(shader.u_pattern_size_a, [imagePosA.size[0] * factor * image.fromScale, imagePosB.size[1] ]); - gl.uniform2fv(shader.u_pattern_size_b, [imagePosB.size[0] * factor * image.toScale, imagePosB.size[1] ]); gl.uniform2fv(shader.u_pattern_tl_a, imagePosA.tl); gl.uniform2fv(shader.u_pattern_br_a, imagePosA.br); gl.uniform2fv(shader.u_pattern_tl_b, imagePosB.tl); @@ -127,33 +106,71 @@ module.exports = function drawLine(painter, layer, posMatrix, tile) { gl.uniform1f(shader.u_fade, image.t); gl.uniform1f(shader.u_opacity, layer.paint['line-opacity']); + } else { shader = painter.lineShader; - gl.switchShader(shader, vtxMatrix, tile.exMatrix); + gl.switchShader(shader); gl.uniform2fv(shader.u_linewidth, [ outset, inset ]); - gl.uniform1f(shader.u_ratio, ratio); gl.uniform1f(shader.u_blur, blur); gl.uniform1f(shader.u_extra, extra); gl.uniformMatrix2fv(shader.u_antialiasingmatrix, false, antialiasingMatrix); - gl.uniform4fv(shader.u_color, color); } - var vertex = tile.buffers.lineVertex; - vertex.bind(gl); - var element = tile.buffers.lineElement; - element.bind(gl); - - for (var i = 0; i < elementGroups.groups.length; i++) { - var group = elementGroups.groups[i]; - var vtxOffset = group.vertexStartIndex * vertex.itemSize; - gl.vertexAttribPointer(shader.a_pos, 2, gl.SHORT, false, 8, vtxOffset + 0); - gl.vertexAttribPointer(shader.a_data, 4, gl.BYTE, false, 8, vtxOffset + 4); - - var count = group.elementLength * 3; - var elementOffset = group.elementStartIndex * element.itemSize; - gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, elementOffset); + for (var k = 0; k < tiles.length; k++) { + var tile = tiles[k]; + var elementGroups = tile.buffers && tile.elementGroups[layer.ref || layer.id]; + if (!elementGroups) continue; + + painter.setClippingMask(tile); + + // set uniforms that are different for each tile + var posMatrix = painter.translateMatrix(tile.posMatrix, tile, layer.paint['line-translate'], layer.paint['line-translate-anchor']); + + gl.uniformMatrix4fv(shader.u_matrix, false, posMatrix); + gl.uniformMatrix4fv(shader.u_exmatrix, false, tile.exMatrix); + var ratio = painter.transform.scale / (1 << tile.coord.z) / (4096 / tile.tileSize); + + // how much the tile is overscaled by + var overscaling = tile.tileSize / painter.transform.tileSize; + + if (dasharray) { + var patternratio = Math.pow(2, Math.floor(Math.log(painter.transform.scale) / Math.LN2) - tile.coord.z) / 8 * overscaling; + var scaleA = [patternratio / posA.width / dasharray.fromScale, -posA.height / 2]; + var gammaA = painter.lineAtlas.width / (dasharray.fromScale * posA.width * 256 * browser.devicePixelRatio) / 2; + var scaleB = [patternratio / posB.width / dasharray.toScale, -posB.height / 2]; + var gammaB = painter.lineAtlas.width / (dasharray.toScale * posB.width * 256 * browser.devicePixelRatio) / 2; + gl.uniform1f(shader.u_ratio, ratio); + gl.uniform2fv(shader.u_patternscale_a, scaleA); + gl.uniform2fv(shader.u_patternscale_b, scaleB); + gl.uniform1f(shader.u_sdfgamma, Math.max(gammaA, gammaB)); + + } else if (image) { + var factor = 4096 / tile.tileSize / Math.pow(2, painter.transform.tileZoom - tile.coord.z) * overscaling; + gl.uniform1f(shader.u_ratio, ratio); + gl.uniform2fv(shader.u_pattern_size_a, [imagePosA.size[0] * factor * image.fromScale, imagePosB.size[1] ]); + gl.uniform2fv(shader.u_pattern_size_b, [imagePosB.size[0] * factor * image.toScale, imagePosB.size[1] ]); + + } else { + gl.uniform1f(shader.u_ratio, ratio); + } + + var vertex = tile.buffers.lineVertex; + vertex.bind(gl); + var element = tile.buffers.lineElement; + element.bind(gl); + + for (var i = 0; i < elementGroups.groups.length; i++) { + var group = elementGroups.groups[i]; + var vtxOffset = group.vertexStartIndex * vertex.itemSize; + gl.vertexAttribPointer(shader.a_pos, 2, gl.SHORT, false, 8, vtxOffset + 0); + gl.vertexAttribPointer(shader.a_data, 4, gl.BYTE, false, 8, vtxOffset + 4); + + var count = group.elementLength * 3; + var elementOffset = group.elementStartIndex * element.itemSize; + gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, elementOffset); + } } }; diff --git a/js/render/draw_symbol.js b/js/render/draw_symbol.js index fdec0765f85..37dee616cc3 100644 --- a/js/render/draw_symbol.js +++ b/js/render/draw_symbol.js @@ -7,15 +7,10 @@ var drawCollisionDebug = require('./draw_collision_debug'); module.exports = drawSymbols; -function drawSymbols(painter, layer, posMatrix, tile) { +function drawSymbols(painter, layer, tiles) { if (painter.opaquePass) return; - // No data - if (!tile.buffers) return; - var elementGroups = tile.elementGroups[layer.ref || layer.id]; - if (!elementGroups) return; - var drawAcrossEdges = !(layer.layout['text-allow-overlap'] || layer.layout['icon-allow-overlap'] || layer.layout['text-ignore-placement'] || layer.layout['icon-ignore-placement']); @@ -34,14 +29,39 @@ function drawSymbols(painter, layer, posMatrix, tile) { painter.depthMask(false); gl.disable(gl.DEPTH_TEST); - if (elementGroups.icon.groups.length) { - drawSymbol(painter, layer, posMatrix, tile, elementGroups.icon, 'icon', elementGroups.sdfIcons); + var tile, elementGroups; + + for (var t = 0; t < tiles.length; t++) { + tile = tiles[t]; + + if (!tile.buffers) continue; + elementGroups = tile.elementGroups[layer.ref || layer.id]; + if (!elementGroups) continue; + + if (elementGroups.icon.groups.length) { + drawSymbol(painter, layer, tile.posMatrix, tile, elementGroups.icon, 'icon', elementGroups.sdfIcons); + } } - if (elementGroups.text.groups.length) { - drawSymbol(painter, layer, posMatrix, tile, elementGroups.text, 'text', true); + + for (var k = 0; k < tiles.length; k++) { + tile = tiles[k]; + + if (!tile.buffers) continue; + elementGroups = tile.elementGroups[layer.ref || layer.id]; + if (!elementGroups) continue; + + if (elementGroups.text.groups.length) { + drawSymbol(painter, layer, tile.posMatrix, tile, elementGroups.text, 'text', true); + } } - drawCollisionDebug(painter, layer, posMatrix, tile); + for (var n = 0; n < tiles.length; n++) { + tile = tiles[n]; + if (!tile.buffers) continue; + elementGroups = tile.elementGroups[layer.ref || layer.id]; + if (!elementGroups) continue; + drawCollisionDebug(painter, layer, tile.posMatrix, tile); + } if (drawAcrossEdges) { gl.enable(gl.STENCIL_TEST); @@ -113,7 +133,9 @@ function drawSymbol(painter, layer, posMatrix, tile, elementGroups, prefix, sdf) texsize = [painter.spriteAtlas.width / 4, painter.spriteAtlas.height / 4]; } - gl.switchShader(shader, posMatrix, exMatrix); + gl.switchShader(shader); + gl.uniformMatrix4fv(shader.u_matrix, false, posMatrix); + gl.uniformMatrix4fv(shader.u_exmatrix, false, exMatrix); gl.uniform1i(shader.u_texture, 0); gl.uniform2fv(shader.u_texsize, texsize); gl.uniform1i(shader.u_skewed, skewed); diff --git a/js/render/gl_util.js b/js/render/gl_util.js index e920ce295c4..6bea70dc387 100644 --- a/js/render/gl_util.js +++ b/js/render/gl_util.js @@ -53,10 +53,7 @@ exports.extend = function(context) { }; // Switches to a different shader program. - context.switchShader = function(shader, posMatrix, exMatrix) { - if (!posMatrix) { - console.trace('posMatrix does not have required argument'); - } + context.switchShader = function(shader) { if (this.currentShader !== shader) { this.useProgram(shader.program); @@ -81,18 +78,6 @@ exports.extend = function(context) { this.currentShader = shader; } - - // Update the matrices if necessary. Note: This relies on object identity! - // This means changing the matrix values without the actual matrix object - // will FAIL to update the matrix properly. - if (shader.posMatrix !== posMatrix) { - this.uniformMatrix4fv(shader.u_matrix, false, posMatrix); - shader.posMatrix = posMatrix; - } - if (exMatrix && shader.exMatrix !== exMatrix && shader.u_exmatrix) { - this.uniformMatrix4fv(shader.u_exmatrix, false, exMatrix); - shader.exMatrix = exMatrix; - } }; return context; diff --git a/js/render/painter.js b/js/render/painter.js index db6328d02e4..f56834fd6d9 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -206,7 +206,8 @@ GLPainter.prototype._drawClippingMask = function(tile) { var gl = this.gl; gl.stencilFunc(gl.ALWAYS, tile.clipID, 0xF8); - gl.switchShader(this.fillShader, tile.posMatrix); + gl.switchShader(this.fillShader); + gl.uniformMatrix4fv(this.fillShader.u_matrix, false, tile.posMatrix); // Draw the clipping mask gl.bindBuffer(gl.ARRAY_BUFFER, this.tileExtentBuffer); @@ -214,7 +215,7 @@ GLPainter.prototype._drawClippingMask = function(tile) { gl.drawArrays(gl.TRIANGLE_STRIP, 0, this.tileExtentBuffer.itemCount); }; -GLPainter.prototype._setClippingMask = function(tile) { +GLPainter.prototype.setClippingMask = function(tile) { var gl = this.gl; gl.stencilFunc(gl.EQUAL, tile.clipID, 0xF8); }; @@ -267,6 +268,7 @@ var draw = { }; GLPainter.prototype.render = function(style, options) { + this.style = style; this.options = options; @@ -353,20 +355,8 @@ GLPainter.prototype.depthMask = function(mask) { }; GLPainter.prototype.drawLayer = function(layer, tiles) { - for (var t = 0; t < tiles.length; t++) { - var tile = tiles[t]; - - this._setClippingMask(tile); - draw[layer.type](this, layer, tile.posMatrix, tile); - - if (this.options.vertices) { - draw.vertices(this, layer, tile.posMatrix, tile); - } - - if (this.options.debug) { - draw.debug(this, tile); - } - } + if (!tiles.length) return; + draw[layer.type](this, layer, tiles); }; GLPainter.prototype.setSublayer = function(n) { diff --git a/js/source/tile.js b/js/source/tile.js index 57a44b5f91f..c59f869e471 100644 --- a/js/source/tile.js +++ b/js/source/tile.js @@ -144,5 +144,14 @@ Tile.prototype = { this.buffers[b].destroy(painter.gl); } this.buffers = null; + }, + + /** + * Return whether this tile has any data for the given layer. + * @param {Object} style layer object + * @returns {boolean} + */ + hasLayerData: function(layer) { + return Boolean(this.buffers && this.elementGroups[layer.ref || layer.id]); } }; From 4df7d58d1d73fe7f14a1e95251dafef8adc2fb61 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 1 May 2015 13:55:01 -0700 Subject: [PATCH 08/13] fix fill antialiasing --- js/render/draw_fill.js | 222 +++++++++++++++++++++-------------------- js/render/painter.js | 2 +- 2 files changed, 115 insertions(+), 109 deletions(-) diff --git a/js/render/draw_fill.js b/js/render/draw_fill.js index ca40cdda21e..13bb24ccaf4 100644 --- a/js/render/draw_fill.js +++ b/js/render/draw_fill.js @@ -21,92 +21,146 @@ function drawFillTile(painter, layer, posMatrix, tile) { var color = layer.paint['fill-color']; var image = layer.paint['fill-image']; - if (image && painter.opaquePass) return; - if (!image && painter.opaquePass !== (color[3] === 1)) return; + var drawFillThisPass = image ? + !painter.opaquePass : + painter.opaquePass === (color[3] === 1); - painter.setSublayer(0); var gl = painter.gl; var translatedPosMatrix = painter.translateMatrix(posMatrix, tile, layer.paint['fill-translate'], layer.paint['fill-translate-anchor']); var vertex, elements, group, count; - // Draw the stencil mask. - - // We're only drawing to the first seven bits (== support a maximum of - // 8 overlapping polygons in one place before we get rendering errors). - gl.stencilMask(0x07); - gl.clear(gl.STENCIL_BUFFER_BIT); - - // Draw front facing triangles. Wherever the 0x80 bit is 1, we are - // increasing the lower 7 bits by one if the triangle is a front-facing - // triangle. This means that all visible polygons should be in CCW - // orientation, while all holes (see below) are in CW orientation. - gl.stencilFunc(gl.EQUAL, tile.clipID, 0xF8); - - // When we do a nonzero fill, we count the number of times a pixel is - // covered by a counterclockwise polygon, and subtract the number of - // times it is "uncovered" by a clockwise polygon. - gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP); - gl.stencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP); - - // When drawing a shape, we first draw all shapes to the stencil buffer - // and incrementing all areas where polygons are - gl.colorMask(false, false, false, false); - painter.depthMask(false); - - // Draw the actual triangle fan into the stencil buffer. - gl.switchShader(painter.fillShader); - gl.uniformMatrix4fv(painter.fillShader.u_matrix, false, translatedPosMatrix); - - // Draw all buffers - vertex = tile.buffers.fillVertex; - vertex.bind(gl); - elements = tile.buffers.fillElement; - elements.bind(gl); - - var offset, elementOffset; - - for (var i = 0; i < elementGroups.groups.length; i++) { - group = elementGroups.groups[i]; - offset = group.vertexStartIndex * vertex.itemSize; - gl.vertexAttribPointer(painter.fillShader.a_pos, 2, gl.SHORT, false, 4, offset + 0); - - count = group.elementLength * 3; - elementOffset = group.elementStartIndex * elements.itemSize; - gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, elementOffset); - } + if (drawFillThisPass) { + // Draw the stencil mask. + painter.setSublayer(1); + + // We're only drawing to the first seven bits (== support a maximum of + // 8 overlapping polygons in one place before we get rendering errors). + gl.stencilMask(0x07); + gl.clear(gl.STENCIL_BUFFER_BIT); + + // Draw front facing triangles. Wherever the 0x80 bit is 1, we are + // increasing the lower 7 bits by one if the triangle is a front-facing + // triangle. This means that all visible polygons should be in CCW + // orientation, while all holes (see below) are in CW orientation. + gl.stencilFunc(gl.EQUAL, tile.clipID, 0xF8); + + // When we do a nonzero fill, we count the number of times a pixel is + // covered by a counterclockwise polygon, and subtract the number of + // times it is "uncovered" by a clockwise polygon. + gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP); + gl.stencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP); + + // When drawing a shape, we first draw all shapes to the stencil buffer + // and incrementing all areas where polygons are + gl.colorMask(false, false, false, false); + painter.depthMask(false); + + // Draw the actual triangle fan into the stencil buffer. + gl.switchShader(painter.fillShader); + gl.uniformMatrix4fv(painter.fillShader.u_matrix, false, translatedPosMatrix); - // Now that we have the stencil mask in the stencil buffer, we can start - // writing to the color buffer. - gl.colorMask(true, true, true, true); - painter.depthMask(true); + // Draw all buffers + vertex = tile.buffers.fillVertex; + vertex.bind(gl); + elements = tile.buffers.fillElement; + elements.bind(gl); - // From now on, we don't want to update the stencil buffer anymore. - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - gl.stencilMask(0x0); + var offset, elementOffset; + + for (var i = 0; i < elementGroups.groups.length; i++) { + group = elementGroups.groups[i]; + offset = group.vertexStartIndex * vertex.itemSize; + gl.vertexAttribPointer(painter.fillShader.a_pos, 2, gl.SHORT, false, 4, offset + 0); + + count = group.elementLength * 3; + elementOffset = group.elementStartIndex * elements.itemSize; + gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, elementOffset); + } + + // Now that we have the stencil mask in the stencil buffer, we can start + // writing to the color buffer. + gl.colorMask(true, true, true, true); + painter.depthMask(true); + + // From now on, we don't want to update the stencil buffer anymore. + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + gl.stencilMask(0x0); + var opacity = layer.paint['fill-opacity'] || 1; + var shader; + + if (image) { + // Draw texture fill + var imagePosA = painter.spriteAtlas.getPosition(image.from, true); + var imagePosB = painter.spriteAtlas.getPosition(image.to, true); + if (!imagePosA || !imagePosB) return; + + shader = painter.patternShader; + gl.switchShader(shader); + gl.uniformMatrix4fv(shader.u_matrix, false, posMatrix); + gl.uniform1i(shader.u_image, 0); + gl.uniform2fv(shader.u_pattern_tl_a, imagePosA.tl); + gl.uniform2fv(shader.u_pattern_br_a, imagePosA.br); + gl.uniform2fv(shader.u_pattern_tl_b, imagePosB.tl); + gl.uniform2fv(shader.u_pattern_br_b, imagePosB.br); + gl.uniform1f(shader.u_opacity, opacity); + gl.uniform1f(shader.u_mix, image.t); + + var factor = (4096 / tile.tileSize) / Math.pow(2, painter.transform.tileZoom - tile.coord.z); + + gl.uniform2fv(shader.u_patternscale_a, [ + 1 / (imagePosA.size[0] * factor * image.fromScale), + 1 / (imagePosA.size[1] * factor * image.fromScale) + ]); + + gl.uniform2fv(shader.u_patternscale_b, [ + 1 / (imagePosB.size[0] * factor * image.toScale), + 1 / (imagePosB.size[1] * factor * image.toScale) + ]); + + painter.spriteAtlas.bind(gl, true); + + } else { + // Draw filling rectangle. + shader = painter.fillShader; + gl.switchShader(shader); + gl.uniformMatrix4fv(shader.u_matrix, false, posMatrix); + gl.uniform4fv(shader.u_color, color); + } + + // Only draw regions that we marked + gl.stencilFunc(gl.NOTEQUAL, 0x0, 0x07); + gl.bindBuffer(gl.ARRAY_BUFFER, painter.tileExtentBuffer); + gl.vertexAttribPointer(shader.a_pos, painter.tileExtentBuffer.itemSize, gl.SHORT, false, 0, 0); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, painter.tileExtentBuffer.itemCount); + + gl.stencilMask(0x00); + gl.stencilFunc(gl.EQUAL, tile.clipID, 0xF8); + } var strokeColor = layer.paint['fill-outline-color']; // Because we're drawing top-to-bottom, and we update the stencil mask // below, we have to draw the outline first (!) - if (false && layer.paint['fill-antialias'] === true && !(layer.paint['fill-image'] && !strokeColor)) { + if (!painter.opaquePass && layer.paint['fill-antialias'] === true && !(layer.paint['fill-image'] && !strokeColor)) { gl.switchShader(painter.outlineShader); gl.uniformMatrix4fv(painter.outlineShader.u_matrix, false, translatedPosMatrix); - gl.lineWidth(2 * browser.devicePixelRatio); + gl.lineWidth(2 * browser.devicePixelRatio * 10); if (strokeColor) { // If we defined a different color for the fill outline, we are // going to ignore the bits in 0x07 and just care about the global // clipping mask. - gl.stencilFunc(gl.EQUAL, 0x80, 0x80); + painter.setSublayer(2); + } else { // Otherwise, we only want to draw the antialiased parts that are // *outside* the current shape. This is important in case the fill // or stroke color is translucent. If we wouldn't clip to outside // the current shape, some pixels from the outline stroke overlapped // the (non-antialiased) fill. - gl.stencilFunc(gl.EQUAL, 0x80, 0xBF); + painter.setSublayer(0); } gl.uniform2f(painter.outlineShader.u_world, gl.drawingBufferWidth, gl.drawingBufferHeight); @@ -114,6 +168,7 @@ function drawFillTile(painter, layer, posMatrix, tile) { // Draw all buffers vertex = tile.buffers.fillVertex; + vertex.bind(gl); elements = tile.buffers.outlineElement; elements.bind(gl); @@ -128,54 +183,5 @@ function drawFillTile(painter, layer, posMatrix, tile) { } } - var opacity = layer.paint['fill-opacity'] || 1; - var shader; - - if (image) { - // Draw texture fill - var imagePosA = painter.spriteAtlas.getPosition(image.from, true); - var imagePosB = painter.spriteAtlas.getPosition(image.to, true); - if (!imagePosA || !imagePosB) return; - - shader = painter.patternShader; - gl.switchShader(shader); - gl.uniformMatrix4fv(shader.u_matrix, false, posMatrix); - gl.uniform1i(shader.u_image, 0); - gl.uniform2fv(shader.u_pattern_tl_a, imagePosA.tl); - gl.uniform2fv(shader.u_pattern_br_a, imagePosA.br); - gl.uniform2fv(shader.u_pattern_tl_b, imagePosB.tl); - gl.uniform2fv(shader.u_pattern_br_b, imagePosB.br); - gl.uniform1f(shader.u_opacity, opacity); - gl.uniform1f(shader.u_mix, image.t); - - var factor = (4096 / tile.tileSize) / Math.pow(2, painter.transform.tileZoom - tile.coord.z); - - gl.uniform2fv(shader.u_patternscale_a, [ - 1 / (imagePosA.size[0] * factor * image.fromScale), - 1 / (imagePosA.size[1] * factor * image.fromScale) - ]); - - gl.uniform2fv(shader.u_patternscale_b, [ - 1 / (imagePosB.size[0] * factor * image.toScale), - 1 / (imagePosB.size[1] * factor * image.toScale) - ]); - - painter.spriteAtlas.bind(gl, true); - - } else { - // Draw filling rectangle. - shader = painter.fillShader; - gl.switchShader(shader); - gl.uniformMatrix4fv(shader.u_matrix, false, posMatrix); - gl.uniform4fv(shader.u_color, color); - } - - // Only draw regions that we marked - gl.stencilFunc(gl.NOTEQUAL, 0x0, 0x07); - gl.bindBuffer(gl.ARRAY_BUFFER, painter.tileExtentBuffer); - gl.vertexAttribPointer(shader.a_pos, painter.tileExtentBuffer.itemSize, gl.SHORT, false, 0, 0); - gl.drawArrays(gl.TRIANGLE_STRIP, 0, painter.tileExtentBuffer.itemCount); - gl.stencilMask(0x00); - gl.stencilFunc(gl.EQUAL, tile.clipID, 0xF8); } diff --git a/js/render/painter.js b/js/render/painter.js index f56834fd6d9..91e74986bec 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -312,7 +312,7 @@ GLPainter.prototype.render = function(style, options) { } } - this.currentLayer = 0; + this.currentLayer = -1; for (var m = 0; m < style._groups.length; m++) { group = style._groups[m]; From 485056d38e3ed5cfafdc2205536fb25de9426fe6 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 1 May 2015 14:55:35 -0700 Subject: [PATCH 09/13] add missing uniform --- js/render/painter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/render/painter.js b/js/render/painter.js index 91e74986bec..56a08bc1310 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -73,7 +73,7 @@ GLPainter.prototype.setup = function() { this.lineShader = gl.initializeShader('line', ['a_pos', 'a_data'], - ['u_matrix', 'u_linewidth', 'u_color', 'u_ratio', 'u_blur', 'u_extra', 'u_antialiasingmatrix']); + ['u_matrix', 'u_exmatrix', 'u_linewidth', 'u_color', 'u_ratio', 'u_blur', 'u_extra', 'u_antialiasingmatrix']); this.linepatternShader = gl.initializeShader('linepattern', ['a_pos', 'a_data'], From 5b42eac3286f72de814841097bb2b703b3d591c4 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 1 May 2015 15:00:34 -0700 Subject: [PATCH 10/13] fix drawRaster --- js/render/draw_raster.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/js/render/draw_raster.js b/js/render/draw_raster.js index fa086fe4656..9bdc7327340 100644 --- a/js/render/draw_raster.js +++ b/js/render/draw_raster.js @@ -4,13 +4,20 @@ var util = require('../util/util'); module.exports = drawRaster; -function drawRaster(painter, layer, posMatrix, tile) { +function drawRaster(painter, layer, tiles) { + for (var t = 0; t < tiles.length; t++) { + drawRasterTile(painter, layer, tiles[t].posMatrix, tiles[t]); + } +} + +function drawRasterTile(painter, layer, posMatrix, tile) { var gl = painter.gl; gl.disable(gl.STENCIL_TEST); var shader = painter.rasterShader; - gl.switchShader(shader, posMatrix); + gl.switchShader(shader); + gl.uniformMatrix4fv(shader.u_matrix, false, posMatrix); // color parameters gl.uniform1f(shader.u_brightness_low, layer.paint['raster-brightness-min']); From b799cbbf59395e20e5ded373405c44f9e2f8b626 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 1 May 2015 16:46:16 -0700 Subject: [PATCH 11/13] fix VideoSource --- js/render/draw_raster.js | 4 ++++ js/render/painter.js | 14 ++++++++------ js/source/geojson_source.js | 1 + js/source/source.js | 1 - js/source/vector_tile_source.js | 1 + js/source/video_source.js | 22 ++++++++++------------ 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/js/render/draw_raster.js b/js/render/draw_raster.js index 9bdc7327340..bd4d9fa98aa 100644 --- a/js/render/draw_raster.js +++ b/js/render/draw_raster.js @@ -11,6 +11,10 @@ function drawRaster(painter, layer, tiles) { } function drawRasterTile(painter, layer, posMatrix, tile) { + if (painter.opaquePass) return; + + painter.setSublayer(0); + var gl = painter.gl; gl.disable(gl.STENCIL_TEST); diff --git a/js/render/painter.js b/js/render/painter.js index 56a08bc1310..5ccb4da2fc8 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -236,14 +236,16 @@ GLPainter.prototype._prepareTile = function(tile) { }; GLPainter.prototype._prepareSource = function(source) { - if (source) { - this.clearStencil(); - var tiles = source.renderedTiles(); + if (!source) return []; - for (var t = 0; t < tiles.length; t++) { - this._prepareTile(tiles[t]); - } + var tiles = source.renderedTiles(); + for (var t = 0; t < tiles.length; t++) { + this._prepareTile(tiles[t]); + } + + if (source.useStencilClipping) { + this.clearStencil(); this._drawClippingMasks(tiles); } diff --git a/js/source/geojson_source.js b/js/source/geojson_source.js index d2b265463d5..ad150e860e6 100644 --- a/js/source/geojson_source.js +++ b/js/source/geojson_source.js @@ -56,6 +56,7 @@ GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototy minzoom: 0, maxzoom: 14, _dirty: true, + useStencilClipping: true, /** * Update source geojson data and rerender map diff --git a/js/source/source.js b/js/source/source.js index d44d761237f..277c6d8158a 100644 --- a/js/source/source.js +++ b/js/source/source.js @@ -4,7 +4,6 @@ var util = require('../util/util'); var ajax = require('../util/ajax'); var browser = require('../util/browser'); var TilePyramid = require('./tile_pyramid'); -var TileCoord = require('./tile_coord'); var normalizeURL = require('../util/mapbox').normalizeSourceURL; exports._loadTileJSON = function(options) { diff --git a/js/source/vector_tile_source.js b/js/source/vector_tile_source.js index ee4d256ec26..65681b16b0e 100644 --- a/js/source/vector_tile_source.js +++ b/js/source/vector_tile_source.js @@ -22,6 +22,7 @@ VectorTileSource.prototype = util.inherit(Evented, { tileSize: 512, reparseOverscaled: true, _loaded: false, + useStencilClipping: true, onAdd: function(map) { this.map = map; diff --git a/js/source/video_source.js b/js/source/video_source.js index 71188b537f3..894de7b1110 100644 --- a/js/source/video_source.js +++ b/js/source/video_source.js @@ -6,6 +6,7 @@ var LatLng = require('../geo/lat_lng'); var Point = require('point-geometry'); var Evented = require('../util/evented'); var Coordinate = require('../geo/coordinate'); +var TileCoord = require('./tile_coord'); var ajax = require('../util/ajax'); module.exports = VideoSource; @@ -130,14 +131,12 @@ VideoSource.prototype = util.inherit(Evented, { tileCoords[2].x, tileCoords[2].y, maxInt16, maxInt16 ]); - this.tile = new Tile(); + this.tile = new Tile(new TileCoord(center.zoom, center.column, center.row), 512, Infinity); this.tile.buckets = {}; this.tile.boundsBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.tile.boundsBuffer); gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW); - - this.center = center; }, loaded: function() { @@ -148,14 +147,11 @@ VideoSource.prototype = util.inherit(Evented, { // noop }, - render: function(layers, painter) { - if (!this._loaded) return; - if (this.video.readyState < 2) return; // not enough data for current position - - var c = this.center; - this.tile.calculateMatrices(c.zoom, c.column, c.row, this.map.transform, painter); + renderedTiles: function() { + // not enough data for current position + if (!this._loaded || this.video.readyState < 2) return []; - var gl = painter.gl; + var gl = this.map.painter.gl; if (!this.tile.texture) { this.tile.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.tile.texture); @@ -164,12 +160,14 @@ VideoSource.prototype = util.inherit(Evented, { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.video); - } else { + } else if (this._currentTime !== this.video.currentTime) { gl.bindTexture(gl.TEXTURE_2D, this.tile.texture); gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, this.video); } - painter.drawLayers(layers, this.tile.posMatrix, this.tile); + this._currentTime = this.video.currentTime; + + return [this.tile]; }, featuresAt: function(point, params, callback) { From f15277cf445be6576d66de505c7446c7e69db185 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 5 May 2015 16:52:57 -0700 Subject: [PATCH 12/13] Use gl.DEPTH_STENCIL and gl.DEPTH_STENCIL_ATTACHMENT https://www.khronos.org/registry/webgl/specs/1.0/#6.6 --- test/render.test.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/render.test.js b/test/render.test.js index 0dcd5fcc5eb..2156f095964 100644 --- a/test/render.test.js +++ b/test/render.test.js @@ -62,6 +62,10 @@ function renderTest(style, info, base, key) { var gl = map.painter.gl; + // Add missing constants (https://github.com/stackgl/headless-gl/issues/12) + gl.DEPTH_STENCIL = 0x84F9; + gl.DEPTH_STENCIL_ATTACHMENT = 0x821A; + map.painter.prepareBuffers = function() { var gl = this.gl; @@ -72,11 +76,11 @@ function renderTest(style, info, base, key) { gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA, gl.drawingBufferWidth, gl.drawingBufferHeight); } - if (!gl.stencilbuffer) { + if (!gl.depthStencilBuffer) { // Create default stencilbuffer - gl.stencilbuffer = gl.createRenderbuffer(); - gl.bindRenderbuffer(gl.RENDERBUFFER, gl.stencilbuffer); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, gl.drawingBufferWidth, gl.drawingBufferHeight); + gl.depthStencilBuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, gl.depthStencilBuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, gl.drawingBufferWidth, gl.drawingBufferHeight); } if (!gl.framebuffer) { @@ -86,7 +90,7 @@ function renderTest(style, info, base, key) { gl.bindFramebuffer(gl.FRAMEBUFFER, gl.framebuffer); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, gl.renderbuffer); - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, gl.stencilbuffer); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, gl.depthStencilBuffer); this.clearColor(); }; From 8fd9e8b9fa4fc2c43e95312b12d1722b7e80ccc0 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 11 May 2015 16:02:17 -0400 Subject: [PATCH 13/13] fix negative near z plane values --- js/render/painter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/render/painter.js b/js/render/painter.js index 5ccb4da2fc8..ef6631d97eb 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -24,6 +24,7 @@ function GLPainter(gl, transform) { this.setup(); this.depthEpsilon = 1 / Math.pow(2, 16); + this.numSublayers = 3; } /* @@ -289,7 +290,7 @@ GLPainter.prototype.render = function(style, options) { this.clearDepth(); var numLayers = style._order.length; - this.depthRangeSize = 1 - numLayers * 3 * this.depthEpsilon; + this.depthRangeSize = 1 - (numLayers + 2) * this.numSublayers * this.depthEpsilon; this.currentLayer = numLayers; var group, layer, tiles; @@ -362,8 +363,7 @@ GLPainter.prototype.drawLayer = function(layer, tiles) { }; GLPainter.prototype.setSublayer = function(n) { - var maxSublayers = 3; - var farDepth = 1 - ((1 + this.currentLayer) * maxSublayers + n) * this.depthEpsilon; + var farDepth = 1 - ((1 + this.currentLayer) * this.numSublayers + n) * this.depthEpsilon; var nearDepth = farDepth - this.depthRangeSize; this.gl.depthRange(nearDepth, farDepth); };