diff --git a/src/gl/color_mode.js b/src/gl/color_mode.js new file mode 100644 index 00000000000..7287fb56a9e --- /dev/null +++ b/src/gl/color_mode.js @@ -0,0 +1,38 @@ +// @flow +const Color = require('../style-spec/util/color'); + +import type {BlendFuncType, ColorMaskType} from './types'; + +const ZERO = 0x0000; +const ONE = 0x0001; +const ONE_MINUS_SRC_ALPHA = 0x0303; + +class ColorMode { + blendFunction: BlendFuncType; + blendColor: Color; + mask: ColorMaskType; + + constructor(blendFunction: BlendFuncType, blendColor: Color, mask: ColorMaskType) { + this.blendFunction = blendFunction; + this.blendColor = blendColor; + this.mask = mask; + } + + static Replace: BlendFuncType; + + static disabled(): ColorMode { + return new ColorMode(ColorMode.Replace, Color.transparent, [false, false, false, false]); + } + + static unblended(): ColorMode { + return new ColorMode(ColorMode.Replace, Color.transparent, [true, true, true, true]); + } + + static alphaBlended(): ColorMode { + return new ColorMode([ONE, ONE_MINUS_SRC_ALPHA], Color.transparent, [true, true, true, true]); + } +} + +ColorMode.Replace = [ONE, ZERO]; + +module.exports = ColorMode; diff --git a/src/gl/context.js b/src/gl/context.js index 38d6515bd25..4a0d4c2487e 100644 --- a/src/gl/context.js +++ b/src/gl/context.js @@ -3,6 +3,10 @@ const IndexBuffer = require('./index_buffer'); const VertexBuffer = require('./vertex_buffer'); const Framebuffer = require('./framebuffer'); const State = require('./state'); +const DepthMode = require('./depth_mode'); +const StencilMode = require('./stencil_mode'); +const ColorMode = require('./color_mode'); +const util = require('../util/util'); const { ClearColor, ClearDepth, @@ -59,6 +63,7 @@ class Context { gl: WebGLRenderingContext; extVertexArrayObject: any; currentNumAttributes: ?number; + lineWidthRange: [number, number]; clearColor: State; clearDepth: State; @@ -95,6 +100,7 @@ class Context { constructor(gl: WebGLRenderingContext) { this.gl = gl; this.extVertexArrayObject = this.gl.getExtension('OES_vertex_array_object'); + this.lineWidthRange = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE); this.clearColor = new State(new ClearColor(this)); this.clearDepth = new State(new ClearDepth(this)); @@ -188,6 +194,44 @@ class Context { gl.clear(mask); } + + setDepthMode(depthMode: DepthMode) { + if (depthMode.func === this.gl.ALWAYS && !depthMode.mask) { + this.depthTest.set(false); + } else { + this.depthTest.set(true); + this.depthFunc.set(depthMode.func); + this.depthMask.set(depthMode.mask); + this.depthRange.set(depthMode.range); + } + } + + setStencilMode(stencilMode: StencilMode) { + if (stencilMode.func === this.gl.ALWAYS && !stencilMode.mask) { + this.stencilTest.set(false); + } else { + this.stencilTest.set(true); + this.stencilMask.set(stencilMode.mask); + this.stencilOp.set([stencilMode.fail, stencilMode.depthFail, stencilMode.pass]); + this.stencilFunc.set({ + func: stencilMode.test.func, + ref: stencilMode.ref, + mask: stencilMode.test.mask + }); + } + } + + setColorMode(colorMode: ColorMode) { + if (util.deepEqual(colorMode.blendFunction, ColorMode.Replace)) { + this.blend.set(false); + } else { + this.blend.set(true); + this.blendFunc.set(colorMode.blendFunction); + this.blendColor.set(colorMode.blendColor); + } + + this.colorMask.set(colorMode.mask); + } } module.exports = Context; diff --git a/src/gl/depth_mode.js b/src/gl/depth_mode.js new file mode 100644 index 00000000000..abc5899d7af --- /dev/null +++ b/src/gl/depth_mode.js @@ -0,0 +1,29 @@ +// @flow +import type { DepthFuncType, DepthMaskType, DepthRangeType } from './types'; + +const ALWAYS = 0x0207; + +class DepthMode { + func: DepthFuncType; + mask: DepthMaskType; + range: DepthRangeType; + + // DepthMask enums + static ReadOnly: boolean; + static ReadWrite: boolean; + + constructor(depthFunc: DepthFuncType, depthMask: DepthMaskType, depthRange: DepthRangeType) { + this.func = depthFunc; + this.mask = depthMask; + this.range = depthRange; + } + + static disabled() { + return new DepthMode(ALWAYS, DepthMode.ReadOnly, [0, 1]); + } +} + +DepthMode.ReadOnly = false; +DepthMode.ReadWrite = true; + +module.exports = DepthMode; diff --git a/src/gl/stencil_mode.js b/src/gl/stencil_mode.js new file mode 100644 index 00000000000..ff25187dae6 --- /dev/null +++ b/src/gl/stencil_mode.js @@ -0,0 +1,30 @@ +// @flow +import type { StencilOpConstant, StencilTest } from './types'; + +const ALWAYS = 0x0207; +const KEEP = 0x1E00; + +class StencilMode { + test: StencilTest; + ref: number; + mask: number; + fail: StencilOpConstant; + depthFail: StencilOpConstant; + pass: StencilOpConstant; + + constructor(test: StencilTest, ref: number, mask: number, fail: StencilOpConstant, + depthFail: StencilOpConstant, pass: StencilOpConstant) { + this.test = test; + this.ref = ref; + this.mask = mask; + this.fail = fail; + this.depthFail = depthFail; + this.pass = pass; + } + + static disabled() { + return new StencilMode({ func: ALWAYS, mask: 0 }, 0, 0, KEEP, KEEP, KEEP); + } +} + +module.exports = StencilMode; diff --git a/src/gl/types.js b/src/gl/types.js index b393825e9ee..01449372bbe 100644 --- a/src/gl/types.js +++ b/src/gl/types.js @@ -21,8 +21,6 @@ export type BlendFuncType = [BlendFuncConstant, BlendFuncConstant]; export type ColorMaskType = [boolean, boolean, boolean, boolean]; -export type DepthRangeType = [number, number]; - export type CompareFuncType = | $PropertyType | $PropertyType @@ -33,15 +31,19 @@ export type CompareFuncType = | $PropertyType | $PropertyType; +export type DepthMaskType = boolean; + +export type DepthRangeType = [number, number]; + +export type DepthFuncType = CompareFuncType; + export type StencilFuncType = { func: CompareFuncType, ref: number, mask: number }; -export type DepthFuncType = CompareFuncType; - -type StencilOpConstant = +export type StencilOpConstant = | $PropertyType | $PropertyType | $PropertyType @@ -56,3 +58,13 @@ export type StencilOpType = [StencilOpConstant, StencilOpConstant, StencilOpCons export type TextureUnitType = number; export type ViewportType = [number, number, number, number]; + +export type StencilTest = + | { func: $PropertyType, mask: 0 } + | { func: $PropertyType, mask: number } + | { func: $PropertyType, mask: number } + | { func: $PropertyType, mask: number } + | { func: $PropertyType, mask: number } + | { func: $PropertyType, mask: number } + | { func: $PropertyType, mask: number } + | { func: $PropertyType, mask: 0 }; diff --git a/src/gl/value.js b/src/gl/value.js index f3066a249d3..ab528705aa5 100644 --- a/src/gl/value.js +++ b/src/gl/value.js @@ -8,6 +8,7 @@ import type { BlendFuncType, ColorMaskType, DepthRangeType, + DepthMaskType, StencilFuncType, StencilOpType, DepthFuncType, @@ -66,10 +67,10 @@ class ColorMask extends ContextValue implements Value { } } -class DepthMask extends ContextValue implements Value { +class DepthMask extends ContextValue implements Value { static default() { return true; } - set(v: boolean): void { + set(v: DepthMaskType): void { this.context.gl.depthMask(v); } } @@ -199,7 +200,8 @@ class LineWidth extends ContextValue implements Value { static default() { return 1; } set(v: number): void { - this.context.gl.lineWidth(v); + const range = this.context.lineWidthRange; + this.context.gl.lineWidth(util.clamp(v, range[0], range[1])); } } diff --git a/src/render/draw_background.js b/src/render/draw_background.js index 3638a6779d7..4a9d99484dd 100644 --- a/src/render/draw_background.js +++ b/src/render/draw_background.js @@ -4,6 +4,8 @@ const pattern = require('./pattern'); const {ProgramConfiguration} = require('../data/program_configuration'); const {PossiblyEvaluated, PossiblyEvaluatedPropertyValue} = require('../style/properties'); const fillLayerPaintProperties = require('../style/style_layer/fill_style_layer_properties').paint; +const StencilMode = require('../gl/stencil_mode'); +const DepthMode = require('../gl/depth_mode'); import type Painter from './painter'; import type SourceCache from '../source/source_cache'; @@ -27,9 +29,9 @@ function drawBackground(painter: Painter, sourceCache: SourceCache, layer: Backg const pass = (!image && color.a === 1 && opacity === 1) ? 'opaque' : 'translucent'; if (painter.renderPass !== pass) return; - context.stencilTest.set(false); - context.depthMask.set(pass === 'opaque'); - painter.setDepthSublayer(0); + context.setStencilMode(StencilMode.disabled()); + context.setDepthMode(painter.depthModeForSublayer(0, pass === 'opaque' ? DepthMode.ReadWrite : DepthMode.ReadOnly)); + context.setColorMode(painter.colorModeForRenderPass()); const properties = new PossiblyEvaluated(fillLayerPaintProperties); diff --git a/src/render/draw_circle.js b/src/render/draw_circle.js index 3e50c03c27a..1dddfbb7cc7 100644 --- a/src/render/draw_circle.js +++ b/src/render/draw_circle.js @@ -1,6 +1,8 @@ // @flow const pixelsToTileUnits = require('../source/pixels_to_tile_units'); +const StencilMode = require('../gl/stencil_mode'); +const DepthMode = require('../gl/depth_mode'); import type Painter from './painter'; import type SourceCache from '../source/source_cache'; @@ -24,12 +26,11 @@ function drawCircles(painter: Painter, sourceCache: SourceCache, layer: CircleSt const context = painter.context; const gl = context.gl; - painter.setDepthSublayer(0); - context.depthMask.set(false); - + context.setDepthMode(painter.depthModeForSublayer(0, DepthMode.ReadOnly)); // Allow circles to be drawn across boundaries, so that // large circles are not clipped to tiles - context.stencilTest.set(false); + context.setStencilMode(StencilMode.disabled()); + context.setColorMode(painter.colorModeForRenderPass()); for (let i = 0; i < coords.length; i++) { const coord = coords[i]; diff --git a/src/render/draw_collision_debug.js b/src/render/draw_collision_debug.js index a2e0b732830..1fc2e6fb180 100644 --- a/src/render/draw_collision_debug.js +++ b/src/render/draw_collision_debug.js @@ -6,6 +6,8 @@ import type StyleLayer from '../style/style_layer'; import type {OverscaledTileID} from '../source/tile_id'; import type SymbolBucket from '../data/bucket/symbol_bucket'; const pixelsToTileUnits = require('../source/pixels_to_tile_units'); +const DepthMode = require('../gl/depth_mode'); +const StencilMode = require('../gl/stencil_mode'); module.exports = drawCollisionDebug; @@ -13,6 +15,11 @@ function drawCollisionDebugGeometry(painter: Painter, sourceCache: SourceCache, const context = painter.context; const gl = context.gl; const program = drawCircles ? painter.useProgram('collisionCircle') : painter.useProgram('collisionBox'); + + context.setDepthMode(DepthMode.disabled()); + context.setStencilMode(StencilMode.disabled()); + context.setColorMode(painter.colorModeForRenderPass()); + for (let i = 0; i < coords.length; i++) { const coord = coords[i]; const tile = sourceCache.getTile(coord); @@ -21,10 +28,11 @@ function drawCollisionDebugGeometry(painter: Painter, sourceCache: SourceCache, const buffers = drawCircles ? bucket.collisionCircle : bucket.collisionBox; if (!buffers) continue; + gl.uniformMatrix4fv(program.uniforms.u_matrix, false, coord.posMatrix); if (!drawCircles) { - painter.lineWidth(1); + context.lineWidth.set(1); } gl.uniform1f(program.uniforms.u_camera_to_center_distance, painter.transform.cameraToCenterDistance); diff --git a/src/render/draw_debug.js b/src/render/draw_debug.js index 1ee94af47e4..e6d9f6bf0a1 100644 --- a/src/render/draw_debug.js +++ b/src/render/draw_debug.js @@ -5,6 +5,8 @@ const mat4 = require('@mapbox/gl-matrix').mat4; const EXTENT = require('../data/extent'); const VertexArrayObject = require('./vertex_array_object'); const PosArray = require('../data/pos_array'); +const DepthMode = require('../gl/depth_mode'); +const StencilMode = require('../gl/stencil_mode'); import type Painter from './painter'; import type SourceCache from '../source/source_cache'; @@ -22,12 +24,15 @@ function drawDebugTile(painter, sourceCache, coord) { const context = painter.context; const gl = context.gl; - context.stencilTest.set(false); - painter.lineWidth(1 * browser.devicePixelRatio); + context.lineWidth.set(1 * browser.devicePixelRatio); const posMatrix = coord.posMatrix; const program = painter.useProgram('debug'); + context.setDepthMode(DepthMode.disabled()); + context.setStencilMode(StencilMode.disabled()); + context.setColorMode(painter.colorModeForRenderPass()); + gl.uniformMatrix4fv(program.uniforms.u_matrix, false, posMatrix); gl.uniform4f(program.uniforms.u_color, 1, 0, 0, 1); painter.debugVAO.bind(context, program, painter.debugBuffer); diff --git a/src/render/draw_fill.js b/src/render/draw_fill.js index cad148c2102..96274f04e2a 100644 --- a/src/render/draw_fill.js +++ b/src/render/draw_fill.js @@ -2,6 +2,7 @@ const pattern = require('./pattern'); const Color = require('../style-spec/util/color'); +const DepthMode = require('../gl/depth_mode'); import type Painter from './painter'; import type SourceCache from '../source/source_cache'; @@ -21,7 +22,7 @@ function drawFill(painter: Painter, sourceCache: SourceCache, layer: FillStyleLa } const context = painter.context; - context.stencilTest.set(true); + context.setColorMode(painter.colorModeForRenderPass()); const pass = (!layer.paint.get('fill-pattern') && color.constantOr(Color.transparent).a === 1 && @@ -31,15 +32,13 @@ function drawFill(painter: Painter, sourceCache: SourceCache, layer: FillStyleLa if (painter.renderPass === pass) { // Once we switch to earcut drawing we can pull most of the WebGL setup // outside of this coords loop. - painter.setDepthSublayer(1); - context.depthMask.set(painter.renderPass === 'opaque'); + context.setDepthMode(painter.depthModeForSublayer(1, painter.renderPass === 'opaque' ? DepthMode.ReadWrite : DepthMode.ReadOnly)); drawFillTiles(painter, sourceCache, layer, coords, drawFillTile); } // Draw stroke if (painter.renderPass === 'translucent' && layer.paint.get('fill-antialias')) { - painter.lineWidth(2); - context.depthMask.set(false); + context.lineWidth.set(2); // 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 @@ -49,7 +48,8 @@ function drawFill(painter: Painter, sourceCache: SourceCache, layer: FillStyleLa // 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(layer.getPaintProperty('fill-outline-color') ? 2 : 0); + context.setDepthMode(painter.depthModeForSublayer( + layer.getPaintProperty('fill-outline-color') ? 2 : 0, DepthMode.ReadOnly)); drawFillTiles(painter, sourceCache, layer, coords, drawStrokeTile); } } @@ -63,7 +63,7 @@ function drawFillTiles(painter, sourceCache, layer, coords, drawFn) { const bucket: ?FillBucket = (tile.getBucket(layer): any); if (!bucket) continue; - painter.enableTileClippingMask(coord); + painter.context.setStencilMode(painter.stencilModeForClipping(coord)); drawFn(painter, sourceCache, layer, tile, coord, bucket, firstTile); firstTile = false; } diff --git a/src/render/draw_fill_extrusion.js b/src/render/draw_fill_extrusion.js index f52c02244f7..04436f44c5d 100644 --- a/src/render/draw_fill_extrusion.js +++ b/src/render/draw_fill_extrusion.js @@ -4,9 +4,11 @@ const glMatrix = require('@mapbox/gl-matrix'); const pattern = require('./pattern'); const Texture = require('./texture'); const Color = require('../style-spec/util/color'); +const DepthMode = require('../gl/depth_mode'); const mat3 = glMatrix.mat3; const mat4 = glMatrix.mat4; const vec3 = glMatrix.vec3; +const StencilMode = require('../gl/stencil_mode'); import type Painter from './painter'; import type SourceCache from '../source/source_cache'; @@ -58,11 +60,11 @@ function drawToExtrusionFramebuffer(painter, layer) { painter.depthRboNeedsClear = false; } - context.stencilTest.set(false); - context.depthTest.set(true); - context.clear({ color: Color.transparent }); - context.depthMask.set(true); + + context.setStencilMode(StencilMode.disabled()); + context.setDepthMode(new DepthMode(gl.LEQUAL, DepthMode.ReadWrite, [0, 1])); + context.setColorMode(painter.colorModeForRenderPass()); } function drawExtrusionTexture(painter, layer) { @@ -73,8 +75,9 @@ function drawExtrusionTexture(painter, layer) { const gl = context.gl; const program = painter.useProgram('extrusionTexture'); - context.stencilTest.set(false); - context.depthTest.set(false); + context.setStencilMode(StencilMode.disabled()); + context.setDepthMode(DepthMode.disabled()); + context.setColorMode(painter.colorModeForRenderPass()); context.activeTexture.set(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, renderedTexture.colorAttachment.get()); diff --git a/src/render/draw_heatmap.js b/src/render/draw_heatmap.js index 054b7d6ae2c..9c7c0af3587 100644 --- a/src/render/draw_heatmap.js +++ b/src/render/draw_heatmap.js @@ -4,6 +4,9 @@ const mat4 = require('@mapbox/gl-matrix').mat4; const Texture = require('./texture'); const pixelsToTileUnits = require('../source/pixels_to_tile_units'); const Color = require('../style-spec/util/color'); +const DepthMode = require('../gl/depth_mode'); +const StencilMode = require('../gl/stencil_mode'); +const util = require('../util/util'); import type Painter from './painter'; import type SourceCache from '../source/source_cache'; @@ -22,19 +25,20 @@ function drawHeatmap(painter: Painter, sourceCache: SourceCache, layer: HeatmapS const context = painter.context; const gl = context.gl; - painter.setDepthSublayer(0); - context.depthMask.set(false); + context.setDepthMode(painter.depthModeForSublayer(0, DepthMode.ReadOnly)); // Allow kernels to be drawn across boundaries, so that // large kernels are not clipped to tiles - context.stencilTest.set(false); + context.setStencilMode(StencilMode.disabled()); bindFramebuffer(context, painter, layer); context.clear({ color: Color.transparent }); // Turn on additive blending for kernels, which is a key aspect of kernel density estimation formula - context.blendFunc.set([gl.ONE, gl.ONE]); + context.setColorMode(util.extend(painter.colorModeForRenderPass(), { + blendFunction: [gl.ONE, gl.ONE] + })); for (let i = 0; i < coords.length; i++) { const coord = coords[i]; @@ -70,9 +74,9 @@ function drawHeatmap(painter: Painter, sourceCache: SourceCache, layer: HeatmapS } context.viewport.set([0, 0, painter.width, painter.height]); - context.blendFunc.set(painter._showOverdrawInspector ? [gl.CONSTANT_COLOR, gl.ONE] : [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]); } else if (painter.renderPass === 'translucent') { + painter.context.setColorMode(painter.colorModeForRenderPass()); renderTextureToMap(painter, layer); } } @@ -140,7 +144,7 @@ function renderTextureToMap(painter, layer) { } colorRampTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); - context.depthTest.set(false); + context.setDepthMode(DepthMode.disabled()); const program = painter.useProgram('heatmapTexture'); @@ -158,6 +162,4 @@ function renderTextureToMap(painter, layer) { painter.viewportVAO.bind(painter.context, program, painter.viewportBuffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - - context.depthTest.set(true); } diff --git a/src/render/draw_hillshade.js b/src/render/draw_hillshade.js index 170fe45bdf4..87e197abfee 100644 --- a/src/render/draw_hillshade.js +++ b/src/render/draw_hillshade.js @@ -3,6 +3,8 @@ const Coordinate = require('../geo/coordinate'); const Texture = require('./texture'); const EXTENT = require('../data/extent'); const mat4 = require('@mapbox/gl-matrix').mat4; +const StencilMode = require('../gl/stencil_mode'); +const DepthMode = require('../gl/depth_mode'); import type Painter from './painter'; import type SourceCache from '../source/source_cache'; @@ -16,8 +18,9 @@ function drawHillshade(painter: Painter, sourceCache: SourceCache, layer: Hillsh const context = painter.context; - painter.setDepthSublayer(0); - context.stencilTest.set(false); + context.setDepthMode(painter.depthModeForSublayer(0, DepthMode.ReadOnly)); + context.setStencilMode(StencilMode.disabled()); + context.setColorMode(painter.colorModeForRenderPass()); for (const tileID of tileIDs) { const tile = sourceCache.getTile(tileID); diff --git a/src/render/draw_line.js b/src/render/draw_line.js index 537c06116cc..f7fedec2e18 100644 --- a/src/render/draw_line.js +++ b/src/render/draw_line.js @@ -2,6 +2,7 @@ const browser = require('../util/browser'); const pixelsToTileUnits = require('../source/pixels_to_tile_units'); +const DepthMode = require('../gl/depth_mode'); import type Painter from './painter'; import type SourceCache from '../source/source_cache'; @@ -17,10 +18,8 @@ module.exports = function drawLine(painter: Painter, sourceCache: SourceCache, l const context = painter.context; - painter.setDepthSublayer(0); - context.depthMask.set(false); - - context.stencilTest.set(true); + context.setDepthMode(painter.depthModeForSublayer(0, DepthMode.ReadOnly)); + context.setColorMode(painter.colorModeForRenderPass()); const programId = layer.paint.get('line-dasharray') ? 'lineSDF' : @@ -110,7 +109,7 @@ function drawLineTile(program, painter, tile, bucket, layer, coord, programConfi } } - painter.enableTileClippingMask(coord); + context.setStencilMode(painter.stencilModeForClipping(coord)); const posMatrix = painter.translatePosMatrix(coord.posMatrix, tile, layer.paint.get('line-translate'), layer.paint.get('line-translate-anchor')); gl.uniformMatrix4fv(program.uniforms.u_matrix, false, posMatrix); diff --git a/src/render/draw_raster.js b/src/render/draw_raster.js index f7fe35eb6ed..122b69c24e5 100644 --- a/src/render/draw_raster.js +++ b/src/render/draw_raster.js @@ -3,6 +3,8 @@ const util = require('../util/util'); const ImageSource = require('../source/image_source'); const browser = require('../util/browser'); +const StencilMode = require('../gl/stencil_mode'); +const DepthMode = require('../gl/depth_mode'); import type Painter from './painter'; import type SourceCache from '../source/source_cache'; @@ -20,12 +22,8 @@ function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterSty const source = sourceCache.getSource(); const program = painter.useProgram('raster'); - context.depthTest.set(true); - context.depthMask.set(layer.paint.get('raster-opacity') === 1); - // Change depth function to prevent double drawing in areas where tiles overlap. - context.depthFunc.set(gl.LESS); - - context.stencilTest.set(false); + context.setStencilMode(StencilMode.disabled()); + context.setColorMode(painter.colorModeForRenderPass()); // Constant parameters. gl.uniform1f(program.uniforms.u_brightness_low, layer.paint.get('raster-brightness-min')); @@ -40,8 +38,10 @@ function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterSty const minTileZ = coords.length && coords[0].overscaledZ; for (const coord of coords) { - // set the lower zoom level to sublayer 0, and higher zoom levels to higher sublayers - painter.setDepthSublayer(coord.overscaledZ - minTileZ); + // Set the lower zoom level to sublayer 0, and higher zoom levels to higher sublayers + // Use gl.LESS to prevent double drawing in areas where tiles overlap. + context.setDepthMode(painter.depthModeForSublayer(coord.overscaledZ - minTileZ, + layer.paint.get('raster-opacity') === 1 ? DepthMode.ReadWrite : DepthMode.ReadOnly, gl.LESS)); const tile = sourceCache.getTile(coord); const posMatrix = painter.transform.calculatePosMatrix(coord.toUnwrapped()); @@ -97,8 +97,6 @@ function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterSty gl.drawArrays(gl.TRIANGLE_STRIP, 0, buffer.length); } } - - context.depthFunc.set(gl.LEQUAL); } function spinWeights(angle) { diff --git a/src/render/draw_symbol.js b/src/render/draw_symbol.js index 2ba561da586..9672bf6a9ae 100644 --- a/src/render/draw_symbol.js +++ b/src/render/draw_symbol.js @@ -8,6 +8,8 @@ const mat4 = require('@mapbox/gl-matrix').mat4; const identityMat4 = mat4.identity(new Float32Array(16)); const symbolLayoutProperties = require('../style/style_layer/symbol_style_layer_properties').layout; const browser = require('../util/browser'); +const StencilMode = require('../gl/stencil_mode'); +const DepthMode = require('../gl/depth_mode'); import type Painter from './painter'; import type SourceCache from '../source/source_cache'; @@ -23,10 +25,8 @@ function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolSt const context = painter.context; // Disable the stencil test so that labels aren't clipped to tile boundaries. - context.stencilTest.set(false); - - painter.setDepthSublayer(0); - context.depthMask.set(false); + context.setStencilMode(StencilMode.disabled()); + context.setColorMode(painter.colorModeForRenderPass()); if (layer.paint.get('icon-opacity').constantOr(1) !== 0) { drawLayerSymbols(painter, sourceCache, layer, coords, false, @@ -70,7 +70,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate const depthOn = pitchWithMap; - context.depthTest.set(depthOn); + context.setDepthMode(depthOn ? painter.depthModeForSublayer(0, DepthMode.ReadOnly) : DepthMode.disabled()); let program; @@ -108,8 +108,6 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate gl.uniform2fv(program.uniforms.u_texsize, tile.iconAtlasTexture.size); } - painter.enableTileClippingMask(coord); - gl.uniformMatrix4fv(program.uniforms.u_matrix, false, painter.translatePosMatrix(coord.posMatrix, tile, translate, translateAnchor)); const s = pixelsToTileUnits(tile, 1, painter.transform.zoom); @@ -128,8 +126,6 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate drawTileSymbols(program, programConfiguration, painter, layer, tile, buffers, isText, isSDF, pitchWithMap); } - - if (!depthOn) context.depthTest.set(true); } function setSymbolDrawState(program, painter, layer, isText, rotateInShader, pitchWithMap, sizeData) { diff --git a/src/render/painter.js b/src/render/painter.js index ea06cc2d1ae..9deb8acbbaf 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -14,6 +14,9 @@ const CrossTileSymbolIndex = require('../symbol/cross_tile_symbol_index'); const shaders = require('../shaders'); const Program = require('./program'); const Context = require('../gl/context'); +const DepthMode = require('../gl/depth_mode'); +const StencilMode = require('../gl/stencil_mode'); +const ColorMode = require('../gl/color_mode'); const Texture = require('./texture'); const updateTileMasks = require('./tile_mask'); const Color = require('../style-spec/util/color'); @@ -40,6 +43,7 @@ import type LineAtlas from './line_atlas'; import type ImageManager from './image_manager'; import type GlyphManager from './glyph_manager'; import type VertexBuffer from '../gl/vertex_buffer'; +import type {DepthMaskType, DepthFuncType} from '../gl/types'; export type RenderPass = 'offscreen' | 'opaque' | 'translucent'; @@ -63,7 +67,6 @@ class Painter { _tileTextures: { [number]: Array }; numSublayers: number; depthEpsilon: number; - lineWidthRange: [number, number]; emptyProgramConfiguration: ProgramConfiguration; width: number; height: number; @@ -107,8 +110,6 @@ class Painter { this.depthRboNeedsClear = true; - this.lineWidthRange = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE); - this.emptyProgramConfiguration = new ProgramConfiguration(); this.crossTileSymbolIndex = new CrossTileSymbolIndex(); @@ -139,19 +140,6 @@ class Painter { setup() { const context = this.context; - const gl = this.context.gl; - - // 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. - context.blend.set(true); - context.blendFunc.set([gl.ONE, gl.ONE_MINUS_SRC_ALPHA]); - - context.stencilTest.set(true); - - context.depthTest.set(true); - context.depthFunc.set(gl.LEQUAL); - - context.depthMask.set(false); const tileExtentArray = new PosArray(); tileExtentArray.emplaceBack(0, 0); @@ -201,15 +189,9 @@ class Painter { // effectively clearing the stencil buffer: once an upstream patch lands, remove // this function in favor of context.clear({ stencil: 0x0 }) - context.colorMask.set([false, false, false, false]); - context.depthMask.set(false); - context.depthTest.set(false); - context.stencilTest.set(true); - - context.stencilMask.set(0xFF); - context.stencilOp.set([gl.ZERO, gl.ZERO, gl.ZERO]); - - context.stencilFunc.set({ func: gl.ALWAYS, ref: 0x0, mask: 0xFF }); + context.setColorMode(ColorMode.disabled()); + context.setDepthMode(DepthMode.disabled()); + context.setStencilMode(new StencilMode({ func: gl.ALWAYS, mask: 0 }, 0x0, 0xFF, gl.ZERO, gl.ZERO, gl.ZERO)); const matrix = mat4.create(); mat4.ortho(matrix, 0, this.width, this.height, 0, 0, 1); @@ -220,24 +202,14 @@ class Painter { this.viewportVAO.bind(context, program, this.viewportBuffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - - context.stencilMask.set(0x00); - context.colorMask.set([true, true, true, true]); - context.depthMask.set(true); - context.depthTest.set(true); } _renderTileClippingMasks(tileIDs: Array) { const context = this.context; const gl = context.gl; - context.colorMask.set([false, false, false, false]); - context.depthMask.set(false); - context.depthTest.set(false); - context.stencilTest.set(true); - context.stencilMask.set(0xFF); - // Tests will always pass, and ref value will be written to stencil buffer. - context.stencilOp.set([gl.KEEP, gl.KEEP, gl.REPLACE]); + context.setColorMode(ColorMode.disabled()); + context.setDepthMode(DepthMode.disabled()); let idNext = 1; this._tileClippingMaskIDs = {}; @@ -246,7 +218,8 @@ class Painter { for (const tileID of tileIDs) { const id = this._tileClippingMaskIDs[tileID.key] = idNext++; - context.stencilFunc.set({ func: gl.ALWAYS, ref: id, mask: 0xFF }); + // Tests will always pass, and ref value will be written to stencil buffer. + context.setStencilMode(new StencilMode({ func: gl.ALWAYS, mask: 0 }, id, 0xFF, gl.KEEP, gl.KEEP, gl.REPLACE)); const program = this.useProgram('fill', programConfiguration); gl.uniformMatrix4fv(program.uniforms.u_matrix, false, tileID.posMatrix); @@ -255,17 +228,31 @@ class Painter { this.tileExtentVAO.bind(this.context, program, this.tileExtentBuffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, this.tileExtentBuffer.length); } + } - context.stencilMask.set(0x00); - context.colorMask.set([true, true, true, true]); - context.depthMask.set(true); - context.depthTest.set(true); + stencilModeForClipping(tileID: OverscaledTileID): StencilMode { + const gl = this.context.gl; + return new StencilMode({ func: gl.EQUAL, mask: 0xFF }, this._tileClippingMaskIDs[tileID.key], 0x00, gl.KEEP, gl.KEEP, gl.REPLACE); } - enableTileClippingMask(tileID: OverscaledTileID) { - const context = this.context; - const gl = context.gl; - context.stencilFunc.set({ func: gl.EQUAL, ref: this._tileClippingMaskIDs[tileID.key], mask: 0xFF }); + colorModeForRenderPass(): ColorMode { + const gl = this.context.gl; + if (this._showOverdrawInspector) { + const numOverdrawSteps = 8; + const a = 1 / numOverdrawSteps; + + return new ColorMode([gl.CONSTANT_COLOR, gl.ONE], new Color(a, a, a, 0), [true, true, true, true]); + } else if (this.renderPass === 'opaque') { + return ColorMode.unblended(); + } else { + return ColorMode.alphaBlended(); + } + } + + depthModeForSublayer(n: number, mask: DepthMaskType, func: ?DepthFuncType): DepthMode { + const farDepth = 1 - ((1 + this.currentLayer) * this.numSublayers + n) * this.depthEpsilon; + const nearDepth = farDepth - 1 + this.depthRange; + return new DepthMode(func || this.context.gl.LEQUAL, mask, [nearDepth, farDepth]); } render(style: Style, options: PainterOptions) { @@ -329,9 +316,9 @@ class Painter { } // Clear buffers in preparation for drawing to the main framebuffer - this.context.clear({ color: Color.transparent, depth: 1 }); + this.context.clear({ color: options.showOverdrawInspector ? Color.black : Color.transparent, depth: 1 }); - this.showOverdrawInspector(options.showOverdrawInspector); + this._showOverdrawInspector = options.showOverdrawInspector; this.depthRange = (style._order.length + 2) * this.numSublayers * this.depthEpsilon; @@ -344,10 +331,6 @@ class Painter { this.currentLayer = layerIds.length - 1; - if (!this._showOverdrawInspector) { - this.context.blend.set(false); - } - for (this.currentLayer; this.currentLayer >= 0; this.currentLayer--) { const layer = this.style._layers[layerIds[this.currentLayer]]; @@ -375,8 +358,6 @@ class Painter { let sourceCache; let coords = []; - this.context.blend.set(true); - this.currentLayer = 0; for (this.currentLayer; this.currentLayer < layerIds.length; this.currentLayer++) { @@ -425,12 +406,6 @@ class Painter { draw[layer.type](painter, sourceCache, layer, coords); } - setDepthSublayer(n: number) { - const farDepth = 1 - ((1 + this.currentLayer) * this.numSublayers + n) * this.depthEpsilon; - const nearDepth = farDepth - 1 + this.depthRange; - this.context.depthRange.set([nearDepth, farDepth]); - } - /** * Transform a matrix to incorporate the *-translate and *-translate-anchor properties into it. * @param inViewportPixelUnitsUnits True when the units accepted by the matrix are in viewport pixels instead of tile units. @@ -477,27 +452,6 @@ class Painter { return textures && textures.length > 0 ? textures.pop() : null; } - lineWidth(width: number) { - this.context.lineWidth.set(util.clamp(width, this.lineWidthRange[0], this.lineWidthRange[1])); - } - - showOverdrawInspector(enabled: boolean) { - if (!enabled && !this._showOverdrawInspector) return; - this._showOverdrawInspector = enabled; - - const context = this.context; - const gl = context.gl; - if (enabled) { - context.blendFunc.set([gl.CONSTANT_COLOR, gl.ONE]); - const numOverdrawSteps = 8; - const a = 1 / numOverdrawSteps; - context.blendColor.set(new Color(a, a, a, 0)); - context.clear({ color: Color.black }); - } else { - context.blendFunc.set([gl.ONE, gl.ONE_MINUS_SRC_ALPHA]); - } - } - _createProgramCached(name: string, programConfiguration: ProgramConfiguration): Program { this.cache = this.cache || {}; const key = `${name}${programConfiguration.cacheKey || ''}${this._showOverdrawInspector ? '/overdraw' : ''}`;