Skip to content

Commit

Permalink
Add debug toggle for terrain wireframe (mapbox#10406)
Browse files Browse the repository at this point in the history
* Add terrain wireframe for debugging

* Add docstring for terrain line grid

* Lazily instantiate wireframe gl data

* Swap overdraw and wireframe preprocessor order

* Remove unused property from terrain

* Clean up wireframe function naming

* Fix linter errors

* Fix linter and flow errors

* Simplifiy rendering logic

* Add a render test for wireframe

* Clean up docstring wording

* Increase pixelmatch allowance in wireframe test
  • Loading branch information
rreusser authored Feb 24, 2021
1 parent b66fb77 commit c359d1e
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 29 deletions.
5 changes: 5 additions & 0 deletions debug/terrain-debug.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<label><input id='show-overdraw-checkbox' type='checkbox'> overdraw debug</label><br />
<label><input id='freeze-tile-coverage-checkbox' type='checkbox'> freeze tile coverage </label><br />
<label><input id='terrain-checkbox' type='checkbox' checked> terrain</label><br />
<label><input id='show-terrain-wireframe-checkbox' type='checkbox'> terrain wireframe</label><br />
<label><input id='satellite-checkbox' type='checkbox'> satellite</label><br />
<label><input id='buildings-checkbox' type='checkbox'> buildings</label><br />
<label><input id='road-label-checkbox' type='checkbox'> road-label-*</label><br />
Expand Down Expand Up @@ -265,6 +266,10 @@
map.showTileBoundaries = !!this.checked;
};

document.getElementById('show-terrain-wireframe-checkbox').onclick = function() {
map.showTerrainWireframe = !!this.checked;
};

document.getElementById('show-symbol-collision-boxes-checkbox').onclick = function() {
map.showCollisionBoxes = !!this.checked;
};
Expand Down
1 change: 1 addition & 0 deletions src/render/painter.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export type CanvasCopyInstances = {
type PainterOptions = {
showOverdrawInspector: boolean,
showTileBoundaries: boolean,
showTerrainWireframe: boolean,
showQueryGeometry: boolean,
showPadding: boolean,
rotating: boolean,
Expand Down
3 changes: 3 additions & 0 deletions src/shaders/terrain_raster.fragment.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ varying vec2 v_pos0;

void main() {
gl_FragColor = texture2D(u_image0, v_pos0);
#ifdef TERRAIN_WIREFRAME
gl_FragColor = vec4(1.0, 0.0, 0.0, 0.8);
#endif
#ifdef OVERDRAW_INSPECTOR
gl_FragColor = vec4(1.0);
#endif
Expand Down
4 changes: 4 additions & 0 deletions src/shaders/terrain_raster.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ attribute vec2 a_texture_pos;
varying vec2 v_pos0;

const float skirtOffset = 24575.0;
const float wireframeOffset = 0.00015;

void main() {
v_pos0 = a_texture_pos / 8192.0;
float skirt = float(a_pos.x >= skirtOffset);
float elevation = elevation(a_texture_pos) - skirt * u_skirt_height;
#ifdef TERRAIN_WIREFRAME
elevation += u_skirt_height * u_skirt_height * wireframeOffset;
#endif
vec2 decodedPos = a_pos - vec2(skirt * skirtOffset, 0.0);
gl_Position = u_matrix * vec4(decodedPos, elevation, 1.0);
}
74 changes: 46 additions & 28 deletions src/terrain/draw_terrain_raster.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,24 +115,28 @@ function demTileChanged(prev: ?Tile, next: ?Tile): boolean {
const vertexMorphing = new VertexMorphing();
const SHADER_DEFAULT = 0;
const SHADER_MORPHING = 1;
const SHADER_TERRAIN_WIREFRAME = 2;
const defaultDuration = 250;

const shaderDefines = {
"0": null,
"1": 'TERRAIN_VERTEX_MORPHING'
"1": 'TERRAIN_VERTEX_MORPHING',
"2": 'TERRAIN_WIREFRAME'
};

function drawTerrainRaster(painter: Painter, terrain: Terrain, sourceCache: SourceCache, tileIDs: Array<OverscaledTileID>, now: number) {
const context = painter.context;
const gl = context.gl;

let program = painter.useProgram('terrainRaster');
let programMode = SHADER_DEFAULT;
let program, programMode;
const showWireframe = painter.options.showTerrainWireframe ? SHADER_TERRAIN_WIREFRAME : SHADER_DEFAULT;

const setShaderMode = (mode) => {
const setShaderMode = (mode, isWireframe) => {
if (programMode === mode)
return;
program = painter.useProgram('terrainRaster', null, shaderDefines[mode]);
const modes = [shaderDefines[mode]];
if (isWireframe) modes.push(shaderDefines[showWireframe]);
program = painter.useProgram('terrainRaster', null, modes);
programMode = mode;
};

Expand All @@ -142,35 +146,49 @@ function drawTerrainRaster(painter: Painter, terrain: Terrain, sourceCache: Sour
const tr = painter.transform;
const skirt = skirtHeight(tr.zoom) * terrain.exaggeration();

for (const coord of tileIDs) {
const tile = sourceCache.getTile(coord);
const stencilMode = StencilMode.disabled;
const batches = showWireframe ? [false, true] : [false];

const prevDemTile = terrain.prevTerrainTileForTile[coord.key];
const nextDemTile = terrain.terrainTileForTile[coord.key];
batches.forEach(isWireframe => {
// This code assumes the rendering is batched into mesh terrain and then wireframe
// terrain (if applicable) so that this is enough to ensure the correct program is
// set when we switch from one to the other.
programMode = -1;

if (demTileChanged(prevDemTile, nextDemTile)) {
vertexMorphing.newMorphing(coord.key, prevDemTile, nextDemTile, now, defaultDuration);
}
const primitive = isWireframe ? gl.LINES : gl.TRIANGLES;
const [buffer, segments] = isWireframe ? terrain.getWirefameBuffer() : [terrain.gridIndexBuffer, terrain.gridSegments];

// Bind the main draped texture
context.activeTexture.set(gl.TEXTURE0);
tile.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST);
for (const coord of tileIDs) {
const tile = sourceCache.getTile(coord);
const stencilMode = StencilMode.disabled;

const morph = vertexMorphing.getMorphValuesForProxy(coord.key);
const shaderMode = morph ? SHADER_MORPHING : SHADER_DEFAULT;
let elevationOptions;
const prevDemTile = terrain.prevTerrainTileForTile[coord.key];
const nextDemTile = terrain.terrainTileForTile[coord.key];

if (morph) {
elevationOptions = {morphing: {srcDemTile: morph.from, dstDemTile: morph.to, phase: easeCubicInOut(morph.phase)}};
}
const uniformValues = terrainRasterUniformValues(coord.posMatrix, isEdgeTile(coord.canonical, tr.renderWorldCopies) ? skirt / 10 : skirt);
if (demTileChanged(prevDemTile, nextDemTile)) {
vertexMorphing.newMorphing(coord.key, prevDemTile, nextDemTile, now, defaultDuration);
}

setShaderMode(shaderMode);
terrain.setupElevationDraw(tile, program, elevationOptions);
program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.backCCW,
uniformValues, "terrain_raster", terrain.gridBuffer, terrain.gridIndexBuffer, terrain.gridSegments);
}
// Bind the main draped texture
context.activeTexture.set(gl.TEXTURE0);
tile.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST);

const morph = vertexMorphing.getMorphValuesForProxy(coord.key);
const shaderMode = morph ? SHADER_MORPHING : SHADER_DEFAULT;
let elevationOptions;

if (morph) {
elevationOptions = {morphing: {srcDemTile: morph.from, dstDemTile: morph.to, phase: easeCubicInOut(morph.phase)}};
}

const uniformValues = terrainRasterUniformValues(coord.posMatrix, isEdgeTile(coord.canonical, tr.renderWorldCopies) ? skirt / 10 : skirt);

setShaderMode(shaderMode, isWireframe);

terrain.setupElevationDraw(tile, program, elevationOptions);
program.draw(context, primitive, depthMode, stencilMode, colorMode, CullFaceMode.backCCW,
uniformValues, "terrain_raster", terrain.gridBuffer, buffer, segments);
}
});
}

function drawTerrainDepth(painter: Painter, terrain: Terrain, sourceCache: SourceCache, tileIDs: Array<OverscaledTileID>) {
Expand Down
56 changes: 55 additions & 1 deletion src/terrain/terrain.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SourceCache from '../source/source_cache.js';
import {OverscaledTileID} from '../source/tile_id.js';
import Tile from '../source/tile.js';
import rasterBoundsAttributes from '../data/raster_bounds_attributes.js';
import {RasterBoundsArray, TriangleIndexArray} from '../data/array_types.js';
import {RasterBoundsArray, TriangleIndexArray, LineIndexArray} from '../data/array_types.js';
import SegmentVector from '../data/segment.js';
import Texture from '../render/texture.js';
import Program from '../render/program.js';
Expand Down Expand Up @@ -173,6 +173,8 @@ export class Terrain extends Elevation {
gridIndexBuffer: IndexBuffer;
gridSegments: SegmentVector;
gridNoSkirtSegments: SegmentVector;
wireframeSegments: SegmentVector;
wireframeIndexBuffer: IndexBuffer;
proxiedCoords: {[string]: Array<ProxiedTileID>};
proxyCoords: Array<OverscaledTileID>;
proxyToSource: {[number]: {[string]: Array<ProxiedTileID>}};
Expand Down Expand Up @@ -1317,6 +1319,20 @@ export class Terrain extends Elevation {
if (!sourceTiles) sourceTiles = this._tilesDirty[source] = {};
sourceTiles[coord.key] = true;
}

/*
* Lazily instantiate the wireframe index buffer and segment vector so that we don't
* allocate the geometry for rendering a debug wireframe until it's needed.
*/
getWirefameBuffer(): [IndexBuffer, SegmentVector] {
if (!this.wireframeSegments) {
const wireframeGridIndices = createWireframeGrid(GRID_DIM + 1);
this.wireframeIndexBuffer = this.painter.context.createIndexBuffer(wireframeGridIndices);
this.wireframeSegments = SegmentVector.simpleSegment(0, 0, this.gridBuffer.length, wireframeGridIndices.length);
}
return [this.wireframeIndexBuffer, this.wireframeSegments];
}

}

function sortByDistanceToCamera(tileIDs, painter) {
Expand Down Expand Up @@ -1396,6 +1412,44 @@ function createGrid(count: number): [RasterBoundsArray, TriangleIndexArray, numb
return [boundsArray, indexArray, skirtIndicesOffset];
}

/**
* Creates a grid of indices corresponding to the grid constructed by createGrid
* in order to render that grid as a wireframe rather than a solid mesh. It does
* not create a skirt and so only goes from 1 to count + 1, e.g. for count of 2:
* -------------
* | /| /|
* | / | / |
* |/ |/ |
* -------------
* | /| /|
* | / | / |
* |/ |/ |
* -------------
* @param {number} count Count of rows and columns
* @private
*/
function createWireframeGrid(count: number): LineIndexArray {
let i, j, index;
const indexArray = new LineIndexArray();
const size = count + 2;
// Draw two edges of a quad and its diagonal. The very last row and column have
// an additional line to close off the grid.
for (j = 1; j < count; j++) {
for (i = 1; i < count; i++) {
index = j * size + i;
indexArray.emplaceBack(index, index + 1);
indexArray.emplaceBack(index, index + size);
indexArray.emplaceBack(index + 1, index + size);

// Place an extra line at the end of each row
if (j === count - 1) indexArray.emplaceBack(index + size, index + size + 1);
}
// Place an extra line at the end of each col
indexArray.emplaceBack(index + 1, index + 1 + size);
}
return indexArray;
}

export type TerrainUniformsType = {|
'u_dem': Uniform1i,
'u_dem_prev': Uniform1i,
Expand Down
22 changes: 22 additions & 0 deletions src/ui/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ class Map extends Camera {
_controlPositions: {[_: string]: HTMLElement};
_interactive: ?boolean;
_showTileBoundaries: ?boolean;
_showTerrainWireframe: ?boolean;
_showQueryGeometry: ?boolean;
_showCollisionBoxes: ?boolean;
_showPadding: ?boolean;
Expand Down Expand Up @@ -2551,6 +2552,7 @@ class Map extends Camera {
// Actually draw
this.painter.render(this.style, {
showTileBoundaries: this.showTileBoundaries,
showTerrainWireframe: this.showTerrainWireframe,
showOverdrawInspector: this._showOverdrawInspector,
showQueryGeometry: !!this._showQueryGeometry,
rotating: this.isRotating(),
Expand Down Expand Up @@ -2829,6 +2831,26 @@ class Map extends Camera {
this._update();
}

/**
* Gets and sets a Boolean indicating whether the map will render a wireframe
* on top of the displayed terrain. Useful for debugging.
*
* The wireframe is always red and is drawn only when terrain is active.
*
* @name showTerrainWireframe
* @type {boolean}
* @instance
* @memberof Map
* @example
* map.showTerrainWireframe = true;
*/
get showTerrainWireframe(): boolean { return !!this._showTerrainWireframe; }
set showTerrainWireframe(value: boolean) {
if (this._showTerrainWireframe === value) return;
this._showTerrainWireframe = value;
this._update();
}

/**
* Gets and sets a Boolean indicating whether the speedindex metric calculation is on or off
*
Expand Down
1 change: 1 addition & 0 deletions test/integration/lib/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ async function runTest(t) {

if (options.debug) map.showTileBoundaries = true;
if (options.showOverdrawInspector) map.showOverdrawInspector = true;
if (options.showTerrainWireframe) map.showTerrainWireframe = true;
if (options.showPadding) map.showPadding = true;
if (options.collisionDebug) map.showCollisionBoxes = true;
if (options.fadeDuration) map._isInitialLoad = false;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions test/integration/render-tests/terrain/wireframe/style.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"version": 8,
"metadata": {
"test": {
"height": 256,
"width": 256,
"description": "In addition to terrain raster, verifies also toggling terrain off / on",
"showTerrainWireframe": true,
"allowed": 0.002,
"operations": [
["wait"],
["setTerrain", null],
["wait"],
["setTerrain", {
"source": "rgbterrain"
}],
["wait"]
]
}
},
"center": [-113.26903, 35.9654],
"zoom": 11,
"pitch": 35,
"terrain": {
"source": "rgbterrain"
},
"sources": {
"rgbterrain": {
"type": "raster-dem",
"tiles": [
"local:https://tiles/{z}-{x}-{y}.terrain.png"
],
"maxzoom": 15,
"tileSize": 256
},
"satellite": {
"type": "raster",
"tiles": [
"local:https://tiles/{z}-{x}-{y}.satellite.png"
],
"maxzoom": 17,
"tileSize": 256
}
},
"layers": [
{
"id": "raster",
"type": "raster",
"source": "satellite",
"paint": {
"raster-fade-duration": 0
}
}
]
}

0 comments on commit c359d1e

Please sign in to comment.