From c5f154c6de89a5a15b2dee4268fd2438f0bcdfbe Mon Sep 17 00:00:00 2001 From: Molly Lloyd Date: Wed, 30 Mar 2016 18:49:57 -0700 Subject: [PATCH] fix anti-aliasing for fill-patterns update package.json with new test-suite sha, rename imageOffset -> setPattern include distance interpolation for opacity fix linting tests refactor image offset calculations use distance from line to calculate alpha, change to multiplication for combining u_opacity and alpha add u_world to use_program for outline pattern, refactor using new imageOffset function refactor draw_fill, fix conditionals --- js/render/draw_fill.js | 152 ++++++++++++++++----------- js/render/painter/use_program.js | 6 ++ package.json | 2 +- shaders/outlinepattern.fragment.glsl | 32 ++++++ shaders/outlinepattern.vertex.glsl | 23 ++++ 5 files changed, 154 insertions(+), 61 deletions(-) create mode 100644 shaders/outlinepattern.fragment.glsl create mode 100644 shaders/outlinepattern.vertex.glsl diff --git a/js/render/draw_fill.js b/js/render/draw_fill.js index 2a6f792002a..68ed54f6bcb 100644 --- a/js/render/draw_fill.js +++ b/js/render/draw_fill.js @@ -23,32 +23,48 @@ function draw(painter, source, layer, coords) { } // Draw stroke - if (!painter.isOpaquePass && layer.paint['fill-antialias'] && !(layer.paint['fill-pattern'] && !strokeColor)) { - var outlineProgram = painter.useProgram('outline'); - painter.lineWidth(2); - painter.depthMask(false); - - 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. - painter.setDepthSublayer(2); - + if (!painter.isOpaquePass && layer.paint['fill-antialias']) { + if (strokeColor || !layer.paint['fill-pattern']) { + var outlineProgram = painter.useProgram('outline'); + painter.lineWidth(2); + painter.depthMask(false); + + 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. + painter.setDepthSublayer(2); + } else { + // Otherwise, we only want to drawFill 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. + painter.setDepthSublayer(0); + } + gl.uniform2f(outlineProgram.u_world, gl.drawingBufferWidth, gl.drawingBufferHeight); + gl.uniform4fv(outlineProgram.u_color, strokeColor ? strokeColor : color); + + for (var j = 0; j < coords.length; j++) { + drawStroke(painter, source, layer, coords[j]); + } } else { + var outlinePatternProgram = painter.useProgram('outlinepattern'); + painter.lineWidth(2); + painter.depthMask(false); // Otherwise, we only want to drawFill 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. painter.setDepthSublayer(0); - } - - gl.uniform2f(outlineProgram.u_world, gl.drawingBufferWidth, gl.drawingBufferHeight); - gl.uniform4fv(outlineProgram.u_color, strokeColor ? strokeColor : color); + gl.uniform2f(outlinePatternProgram.u_world, gl.drawingBufferWidth, gl.drawingBufferHeight); - for (var j = 0; j < coords.length; j++) { - drawStroke(painter, source, layer, coords[j]); + for (var k = 0; k < coords.length; k++) { + drawStroke(painter, source, layer, coords[k]); + } } + } } @@ -125,49 +141,8 @@ function drawFill(painter, source, layer, coord) { 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; - program = painter.useProgram('pattern', posMatrix); - gl.uniform1i(program.u_image, 0); - 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_opacity, opacity); - gl.uniform1f(program.u_mix, image.t); - - var imageSizeScaledA = [ - (imagePosA.size[0] * image.fromScale), - (imagePosA.size[1] * image.fromScale) - ]; - var imageSizeScaledB = [ - (imagePosB.size[0] * image.toScale), - (imagePosB.size[1] * image.toScale) - ]; - - gl.uniform2fv(program.u_patternscale_a, [ - 1 / pixelsToTileUnits(tile, imageSizeScaledA[0], painter.transform.tileZoom), - 1 / pixelsToTileUnits(tile, imageSizeScaledA[1], painter.transform.tileZoom) - ]); - - gl.uniform2fv(program.u_patternscale_b, [ - 1 / pixelsToTileUnits(tile, imageSizeScaledB[0], painter.transform.tileZoom), - 1 / pixelsToTileUnits(tile, imageSizeScaledB[1], painter.transform.tileZoom) - ]); - - var tileSizeAtNearestZoom = tile.tileSize * Math.pow(2, painter.transform.tileZoom - tile.coord.z); - - // shift images to match at tile boundaries - var offsetAx = ((tileSizeAtNearestZoom / imageSizeScaledA[0]) % 1) * (tile.coord.x + coord.w * Math.pow(2, tile.coord.z)); - var offsetAy = ((tileSizeAtNearestZoom / imageSizeScaledA[1]) % 1) * tile.coord.y; - - var offsetBx = ((tileSizeAtNearestZoom / imageSizeScaledB[0]) % 1) * (tile.coord.x + coord.w * Math.pow(2, tile.coord.z)); - var offsetBy = ((tileSizeAtNearestZoom / imageSizeScaledB[1]) % 1) * tile.coord.y; - - gl.uniform2fv(program.u_offset_a, [offsetAx, offsetAy]); - gl.uniform2fv(program.u_offset_b, [offsetBx, offsetBy]); + setPattern(image, opacity, tile, coord, painter, program); gl.activeTexture(gl.TEXTURE0); painter.spriteAtlas.bind(gl, true); @@ -194,7 +169,10 @@ function drawStroke(painter, source, layer, coord) { var gl = painter.gl; var elementGroups = bucket.elementGroups.fill; - var program = painter.useProgram('outline'); + + var image = layer.paint['fill-pattern']; + var opacity = layer.paint['fill-opacity']; + var program = image ? painter.useProgram('outlinepattern') : painter.useProgram('outline'); painter.setPosMatrix(painter.translatePosMatrix( painter.calculatePosMatrix(coord, source.maxzoom), @@ -203,6 +181,8 @@ function drawStroke(painter, source, layer, coord) { layer.paint['fill-translate-anchor'] )); + if (image) { setPattern(image, opacity, tile, coord, painter, program); } + // Draw all buffers var vertex = bucket.buffers.fillVertex; var elements = bucket.buffers.fillSecondElement; @@ -221,3 +201,55 @@ function drawStroke(painter, source, layer, coord) { gl.drawElements(gl.LINES, count, gl.UNSIGNED_SHORT, elementOffset); } } + + +function setPattern(image, opacity, tile, coord, painter, program) { + var gl = painter.gl; + + var imagePosA = painter.spriteAtlas.getPosition(image.from, true); + var imagePosB = painter.spriteAtlas.getPosition(image.to, true); + if (!imagePosA || !imagePosB) return; + + + gl.uniform1i(program.u_image, 0); + 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_opacity, opacity); + gl.uniform1f(program.u_mix, image.t); + + var imageSizeScaledA = [ + (imagePosA.size[0] * image.fromScale), + (imagePosA.size[1] * image.fromScale) + ]; + var imageSizeScaledB = [ + (imagePosB.size[0] * image.toScale), + (imagePosB.size[1] * image.toScale) + ]; + + gl.uniform2fv(program.u_patternscale_a, [ + 1 / pixelsToTileUnits(tile, imageSizeScaledA[0], painter.transform.tileZoom), + 1 / pixelsToTileUnits(tile, imageSizeScaledA[1], painter.transform.tileZoom) + ]); + + gl.uniform2fv(program.u_patternscale_b, [ + 1 / pixelsToTileUnits(tile, imageSizeScaledB[0], painter.transform.tileZoom), + 1 / pixelsToTileUnits(tile, imageSizeScaledB[1], painter.transform.tileZoom) + ]); + + var tileSizeAtNearestZoom = tile.tileSize * Math.pow(2, painter.transform.tileZoom - tile.coord.z); + + // shift images to match at tile boundaries + var offsetAx = ((tileSizeAtNearestZoom / imageSizeScaledA[0]) % 1) * (tile.coord.x + coord.w * Math.pow(2, tile.coord.z)); + var offsetAy = ((tileSizeAtNearestZoom / imageSizeScaledA[1]) % 1) * tile.coord.y; + + var offsetBx = ((tileSizeAtNearestZoom / imageSizeScaledB[0]) % 1) * (tile.coord.x + coord.w * Math.pow(2, tile.coord.z)); + var offsetBy = ((tileSizeAtNearestZoom / imageSizeScaledB[1]) % 1) * tile.coord.y; + + gl.uniform2fv(program.u_offset_a, [offsetAx, offsetAy]); + gl.uniform2fv(program.u_offset_b, [offsetBx, offsetBy]); + + gl.activeTexture(gl.TEXTURE0); + painter.spriteAtlas.bind(gl, true); +} diff --git a/js/render/painter/use_program.js b/js/render/painter/use_program.js index ef75ee5721d..8fe5457413d 100644 --- a/js/render/painter/use_program.js +++ b/js/render/painter/use_program.js @@ -49,6 +49,12 @@ var definitions = { attributeNames: ['a_pos'], uniformNames: ['u_matrix', 'u_color', 'u_world'] }, + outlinepattern: { + fragmentSource: fs.readFileSync(path.join(__dirname, '../../../shaders/outlinepattern.fragment.glsl'), 'utf8'), + vertexSource: fs.readFileSync(path.join(__dirname, '../../../shaders/outlinepattern.vertex.glsl'), 'utf8'), + attributeNames: ['a_pos'], + uniformNames: ['u_matrix', 'u_world', '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', 'u_offset_a', 'u_offset_b'] + }, pattern: { fragmentSource: fs.readFileSync(path.join(__dirname, '../../../shaders/pattern.fragment.glsl'), 'utf8'), vertexSource: fs.readFileSync(path.join(__dirname, '../../../shaders/pattern.vertex.glsl'), 'utf8'), diff --git a/package.json b/package.json index 2e2733d99bb..9def37b0d17 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "express": "^4.13.4", "gl": "^2.1.5", "istanbul": "^0.4.2", - "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#a42cd0cb818356d91fc6b61e2b64801e57486ac0", + "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#d974ec6b3748a258f8ddd7528e049493390177b4", "nyc": "^6.1.1", "sinon": "^1.15.4", "st": "^1.0.0", diff --git a/shaders/outlinepattern.fragment.glsl b/shaders/outlinepattern.fragment.glsl new file mode 100644 index 00000000000..70c174f237e --- /dev/null +++ b/shaders/outlinepattern.fragment.glsl @@ -0,0 +1,32 @@ +precision mediump float; + +uniform float u_opacity; +uniform vec2 u_pattern_tl_a; +uniform vec2 u_pattern_br_a; +uniform vec2 u_pattern_tl_b; +uniform vec2 u_pattern_br_b; +uniform float u_mix; + +uniform sampler2D u_image; + +varying vec2 v_pos_a; +varying vec2 v_pos_b; +varying vec2 v_pos; + +void main() { + vec2 imagecoord = mod(v_pos_a, 1.0); + vec2 pos = mix(u_pattern_tl_a, u_pattern_br_a, imagecoord); + vec4 color1 = texture2D(u_image, pos); + + vec2 imagecoord_b = mod(v_pos_b, 1.0); + vec2 pos2 = mix(u_pattern_tl_b, u_pattern_br_b, imagecoord_b); + vec4 color2 = texture2D(u_image, pos2); + + // find distance to outline for alpha interpolation + + float dist = length(v_pos - gl_FragCoord.xy); + float alpha = smoothstep(1.0, 0.0, dist); + + + gl_FragColor = mix(color1, color2, u_mix) * alpha * u_opacity; +} diff --git a/shaders/outlinepattern.vertex.glsl b/shaders/outlinepattern.vertex.glsl new file mode 100644 index 00000000000..1a3973cfc5e --- /dev/null +++ b/shaders/outlinepattern.vertex.glsl @@ -0,0 +1,23 @@ +precision highp float; + +uniform vec2 u_patternscale_a; +uniform vec2 u_patternscale_b; +uniform vec2 u_offset_a; +uniform vec2 u_offset_b; + +attribute vec2 a_pos; + +uniform mat4 u_matrix; +uniform vec2 u_world; + +varying vec2 v_pos_a; +varying vec2 v_pos_b; +varying vec2 v_pos; + + +void main() { + gl_Position = u_matrix * vec4(a_pos, 0, 1); + v_pos_a = u_patternscale_a * a_pos + u_offset_a; + v_pos_b = u_patternscale_b * a_pos + u_offset_b; + v_pos = (gl_Position.xy/gl_Position.w + 1.0) / 2.0 * u_world; +}