Skip to content

Commit

Permalink
draw extrusions and custom layers into main framebuffer
Browse files Browse the repository at this point in the history
Previously, both fill-extrusions and custom layers were drawn into a
offscreen framebuffer before being copied over to the main framebuffer.

Drawing directly into the main frame buffer is better because:
- it avoids fragment-expensive copies
- it unblocks using msaa antialiasing (which only works in the main

Drawing opaque extrusions and custom layers involved few changes.
Transparent extrusions needed to be implemented differently. They now
use a two pass approach:
- the first pass only draws depth
- the second pass only colors a sufrace if it's depth matches

Some tweaks were made to how we draw stencil masks so that we could use
the stencil buffer in the second pass to prevent double-shading. The
changes should reduce the amount of stencil clears that happen.

The way fill-extrusion-opacity works across layers was already kind of
weird. This pr makes no attempt to change any of that behaviour. It
should be completely backwards compatible.
  • Loading branch information
ansis committed Feb 25, 2019
1 parent d8544ac commit 4bf07df
Show file tree
Hide file tree
Showing 33 changed files with 249 additions and 225 deletions.
35 changes: 11 additions & 24 deletions src/render/draw_custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export default drawCustom;

import DepthMode from '../gl/depth_mode';
import StencilMode from '../gl/stencil_mode';
import {prepareOffscreenFramebuffer, drawOffscreenTexture} from './offscreen';

import type Painter from './painter';
import type SourceCache from '../source/source_cache';
Expand All @@ -27,35 +26,23 @@ function drawCustom(painter: Painter, sourceCache: SourceCache, layer: CustomSty
painter.setBaseState();
}

if (implementation.renderingMode === '3d') {
painter.setCustomLayerDefaults();

prepareOffscreenFramebuffer(painter, layer);
implementation.render(context.gl, painter.transform.customLayerMatrix());

context.setDirty();
painter.setBaseState();
}

} else if (painter.renderPass === 'translucent') {

if (implementation.renderingMode === '3d') {
drawOffscreenTexture(painter, layer, 1);
painter.setCustomLayerDefaults();

} else {
painter.setCustomLayerDefaults();
context.setColorMode(painter.colorModeForRenderPass());
context.setStencilMode(StencilMode.disabled);

context.setColorMode(painter.colorModeForRenderPass());
context.setStencilMode(StencilMode.disabled);
const depthMode = implementation.renderingMode === '3d' ?
new DepthMode(painter.context.gl.LEQUAL, DepthMode.ReadWrite, painter.depthRangeFor3D) :
painter.depthModeForSublayer(0, DepthMode.ReadOnly);

const depthMode = painter.depthModeForSublayer(0, DepthMode.ReadOnly);
context.setDepthMode(depthMode);
context.setDepthMode(depthMode);

implementation.render(context.gl, painter.transform.customLayerMatrix());
implementation.render(context.gl, painter.transform.customLayerMatrix());

context.setDirty();
painter.setBaseState();
context.bindFramebuffer.set(null);
}
context.setDirty();
painter.setBaseState();
context.bindFramebuffer.set(null);
}
}
3 changes: 2 additions & 1 deletion src/render/draw_fill.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ function drawFill(painter: Painter, sourceCache: SourceCache, layer: FillStyleLa
const colorMode = painter.colorModeForRenderPass();

const pattern = layer.paint.get('fill-pattern');
const pass = (!pattern.constantOr((1: any)) &&
const pass = painter.opaquePassEnabledForLayer() &&
(!pattern.constantOr((1: any)) &&
color.constantOr(Color.transparent).a === 1 &&
opacity.constantOr(0) === 1) ? 'opaque' : 'translucent';

Expand Down
42 changes: 27 additions & 15 deletions src/render/draw_fill_extrusion.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import DepthMode from '../gl/depth_mode';
import StencilMode from '../gl/stencil_mode';
import ColorMode from '../gl/color_mode';
import CullFaceMode from '../gl/cull_face_mode';
import {
fillExtrusionUniformValues,
fillExtrusionPatternUniformValues,
} from './program/fill_extrusion_program';
import {prepareOffscreenFramebuffer, drawOffscreenTexture} from './offscreen';

import type Painter from './painter';
import type SourceCache from '../source/source_cache';
Expand All @@ -18,21 +18,32 @@ import type {OverscaledTileID} from '../source/tile_id';
export default draw;

function draw(painter: Painter, source: SourceCache, layer: FillExtrusionStyleLayer, coords: Array<OverscaledTileID>) {
if (layer.paint.get('fill-extrusion-opacity') === 0) {
const opacity = layer.paint.get('fill-extrusion-opacity');
if (opacity === 0) {
return;
}

if (painter.renderPass === 'offscreen') {
prepareOffscreenFramebuffer(painter, layer);

const depthMode = new DepthMode(painter.context.gl.LEQUAL, DepthMode.ReadWrite, [0, 1]),
stencilMode = StencilMode.disabled,
colorMode = painter.colorModeForRenderPass();

drawExtrusionTiles(painter, source, layer, coords, depthMode, stencilMode, colorMode);

} else if (painter.renderPass === 'translucent') {
drawOffscreenTexture(painter, layer, layer.paint.get('fill-extrusion-opacity'));
if (painter.renderPass === 'translucent') {
const depthMode = new DepthMode(painter.context.gl.LEQUAL, DepthMode.ReadWrite, painter.depthRangeFor3D);

if (opacity === 1 && !layer.paint.get('fill-extrusion-pattern').constantOr((1: any))) {
const colorMode = painter.colorModeForRenderPass();
drawExtrusionTiles(painter, source, layer, coords, depthMode, StencilMode.disabled, colorMode);

} else {
// Draw transparent buildings in two passes so that only the closest surface is drawn.
// First draw all the extrusions into only the depth buffer. No colors are drawn.
drawExtrusionTiles(painter, source, layer, coords, depthMode,
StencilMode.disabled,
ColorMode.disabled);

// Then draw all the extrusions a second type, only coloring fragments if they have the
// same depth value as the closest fragment in the previous pass. Use the stencil buffer
// to prevent the second draw in cases where we have coincident polygons.
drawExtrusionTiles(painter, source, layer, coords, depthMode,
painter.stencilModeFor3D(),
painter.colorModeForRenderPass());
}
}
}

Expand All @@ -42,6 +53,7 @@ function drawExtrusionTiles(painter, source, layer, coords, depthMode, stencilMo
const patternProperty = layer.paint.get('fill-extrusion-pattern');
const image = patternProperty.constantOr((1: any));
const crossfade = layer.getCrossfadeParameters();
const opacity = layer.paint.get('fill-extrusion-opacity');

for (const coord of coords) {
const tile = source.getTile(coord);
Expand Down Expand Up @@ -72,8 +84,8 @@ function drawExtrusionTiles(painter, source, layer, coords, depthMode, stencilMo

const shouldUseVerticalGradient = layer.paint.get('fill-extrusion-vertical-gradient');
const uniformValues = image ?
fillExtrusionPatternUniformValues(matrix, painter, shouldUseVerticalGradient, coord, crossfade, tile) :
fillExtrusionUniformValues(matrix, painter, shouldUseVerticalGradient);
fillExtrusionPatternUniformValues(matrix, painter, shouldUseVerticalGradient, opacity, coord, crossfade, tile) :
fillExtrusionUniformValues(matrix, painter, shouldUseVerticalGradient, opacity);


program.draw(context, context.gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.backCCW,
Expand Down
64 changes: 0 additions & 64 deletions src/render/offscreen.js

This file was deleted.

81 changes: 56 additions & 25 deletions src/render/painter.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import type ImageManager from './image_manager';
import type GlyphManager from './glyph_manager';
import type VertexBuffer from '../gl/vertex_buffer';
import type IndexBuffer from '../gl/index_buffer';
import type {DepthMaskType, DepthFuncType} from '../gl/types';
import type {DepthRangeType, DepthMaskType, DepthFuncType} from '../gl/types';

export type RenderPass = 'offscreen' | 'opaque' | 'translucent';

Expand Down Expand Up @@ -108,9 +108,12 @@ class Painter {
lineAtlas: LineAtlas;
imageManager: ImageManager;
glyphManager: GlyphManager;
depthRange: number;
depthRangeFor3D: DepthRangeType;
opaquePassCutoff: number;
renderPass: RenderPass;
currentLayer: number;
currentStencilSource: ?string;
nextStencilID: number;
id: string;
_showOverdrawInspector: boolean;
cache: { [string]: Program<*> };
Expand Down Expand Up @@ -219,6 +222,9 @@ class Painter {
const context = this.context;
const gl = context.gl;

this.nextStencilID = 1;
this.currentStencilSource = undefined;

// As a temporary workaround for https://github.com/mapbox/mapbox-gl-js/issues/5490,
// pending an upstream fix, we draw a fullscreen stencil=0 clipping mask here,
// effectively clearing the stencil buffer: once an upstream patch lands, remove
Expand All @@ -235,20 +241,28 @@ class Painter {
this.quadTriangleIndexBuffer, this.viewportSegments);
}

_renderTileClippingMasks(tileIDs: Array<OverscaledTileID>) {
_renderTileClippingMasks(layer: StyleLayer, tileIDs: Array<OverscaledTileID>) {
if (this.currentStencilSource === layer.source || !layer.isTileClipped() || !tileIDs || !tileIDs.length) return;

this.currentStencilSource = layer.source;

const context = this.context;
const gl = context.gl;

if (this.nextStencilID + tileIDs.length > 256) {
// we'll run out of fresh IDs so we need to clear and start from scratch
this.clearStencil();
}

context.setColorMode(ColorMode.disabled);
context.setDepthMode(DepthMode.disabled);

const program = this.useProgram('clippingMask');

let idNext = 1;
this._tileClippingMaskIDs = {};

for (const tileID of tileIDs) {
const id = this._tileClippingMaskIDs[tileID.key] = idNext++;
const id = this._tileClippingMaskIDs[tileID.key] = this.nextStencilID++;

program.draw(context, gl.TRIANGLES, DepthMode.disabled,
// Tests will always pass, and ref value will be written to stencil buffer.
Expand All @@ -259,6 +273,16 @@ class Painter {
}
}

stencilModeFor3D(): StencilMode {
if (this.nextStencilID + 1 > 256) {
this.clearStencil();
}

const id = this.nextStencilID++;
const gl = this.context.gl;
return new StencilMode({ func: gl.NOTEQUAL, mask: 0xFF }, id, 0xFF, gl.KEEP, gl.KEEP, gl.REPLACE);
}

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);
Expand All @@ -278,11 +302,23 @@ class Painter {
}
}

depthModeForSublayer(n: number, mask: DepthMaskType, func: ?DepthFuncType): DepthMode {
depthModeForSublayer(n: number, mask: DepthMaskType, func: ?DepthFuncType): $ReadOnly<DepthMode> {
if (!this.opaquePassEnabledForLayer()) return DepthMode.disabled;
const depth = 1 - ((1 + this.currentLayer) * this.numSublayers + n) * this.depthEpsilon;
return new DepthMode(func || this.context.gl.LEQUAL, mask, [depth, depth]);
}

/*
* The opaque pass and 3D layers both use the depth buffer.
* Layers drawn above 3D layers need to be drawn using the
* painter's algorithm so that they appear above 3D features.
* This returns true for layers that can be drawn using the
* opaque pass.
*/
opaquePassEnabledForLayer() {
return this.currentLayer < this.opaquePassCutoff;
}

render(style: Style, options: PainterOptions) {
this.style = style;
this.options = options;
Expand Down Expand Up @@ -323,6 +359,15 @@ class Painter {
updateTileMasks(visibleTiles, this.context);
}

this.opaquePassCutoff = Infinity;
for (let i = 0; i < layerIds.length; i++) {
const layerId = layerIds[i];
if (this.style._layers[layerId].is3D()) {
this.opaquePassCutoff = i;
break;
}
}

// Offscreen pass ===============================================
// We first do all rendering that requires rendering to a separate
// framebuffer, and then save those for rendering back to the map
Expand All @@ -345,36 +390,29 @@ class Painter {

// Clear buffers in preparation for drawing to the main framebuffer
this.context.clear({ color: options.showOverdrawInspector ? Color.black : Color.transparent, depth: 1 });
this.clearStencil();

this._showOverdrawInspector = options.showOverdrawInspector;
this.depthRange = (style._order.length + 2) * this.numSublayers * this.depthEpsilon;
this.depthRangeFor3D = [0, 1 - ((style._order.length + 2) * this.numSublayers * this.depthEpsilon)];

// Opaque pass ===============================================
// Draw opaque layers top-to-bottom first.
this.renderPass = 'opaque';
let prevSourceId;

for (this.currentLayer = layerIds.length - 1; this.currentLayer >= 0; this.currentLayer--) {
const layer = this.style._layers[layerIds[this.currentLayer]];
const sourceCache = sourceCaches[layer.source];
const coords = coordsAscending[layer.source];

if (layer.source !== prevSourceId && sourceCache) {
this.clearStencil();
if (sourceCache.getSource().isTileClipped) {
this._renderTileClippingMasks(coords);
}
}

this._renderTileClippingMasks(layer, coords);
this.renderLayer(this, sourceCache, layer, coords);
prevSourceId = layer.source;
}

// Translucent pass ===============================================
// Draw all other layers bottom-to-top.
this.renderPass = 'translucent';

for (this.currentLayer = 0, prevSourceId = null; this.currentLayer < layerIds.length; this.currentLayer++) {
for (this.currentLayer = 0; this.currentLayer < layerIds.length; this.currentLayer++) {
const layer = this.style._layers[layerIds[this.currentLayer]];
const sourceCache = sourceCaches[layer.source];

Expand All @@ -383,15 +421,8 @@ class Painter {
// separate clipping masks
const coords = (layer.type === 'symbol' ? coordsDescendingSymbol : coordsDescending)[layer.source];

if (layer.source !== prevSourceId && sourceCache) {
this.clearStencil();
if (sourceCache.getSource().isTileClipped) {
this._renderTileClippingMasks(coordsAscending[layer.source]);
}
}

this._renderTileClippingMasks(layer, coordsAscending[layer.source]);
this.renderLayer(this, sourceCache, layer, coords);
prevSourceId = layer.source;
}

if (this.options.showTileBoundaries) {
Expand Down
Loading

0 comments on commit 4bf07df

Please sign in to comment.