diff --git a/src/render/draw_color_relief.ts b/src/render/draw_color_relief.ts new file mode 100644 index 00000000000..4df7bf01952 --- /dev/null +++ b/src/render/draw_color_relief.ts @@ -0,0 +1,110 @@ +import {Texture} from './texture'; +import type {StencilMode} from '../gl/stencil_mode'; +import {DepthMode} from '../gl/depth_mode'; +import {CullFaceMode} from '../gl/cull_face_mode'; +import {type ColorMode} from '../gl/color_mode'; +import { + colorReliefUniformValues +} from './program/color_relief_program'; + +import type {Painter, RenderOptions} from './painter'; +import type {SourceCache} from '../source/source_cache'; +import type {ColorReliefStyleLayer} from '../style/style_layer/color_relief_style_layer'; +import type {OverscaledTileID} from '../source/tile_id'; +import type {Context} from '../gl/context'; + +export function drawColorRelief(painter: Painter, sourceCache: SourceCache, layer: ColorReliefStyleLayer, tileIDs: Array, renderOptions: RenderOptions) { + if (painter.renderPass !== 'translucent') return; + if (!tileIDs.length) return; + + const {isRenderingToTexture} = renderOptions; + const projection = painter.style.projection; + const useSubdivision = projection.useSubdivision; + + const depthMode = painter.getDepthModeForSublayer(0, DepthMode.ReadOnly); + const colorMode = painter.colorModeForRenderPass(); + + // Globe (or any projection with subdivision) needs two-pass rendering to avoid artifacts when rendering texture tiles. + // See comments in draw_raster.ts for more details. + if (useSubdivision) { + // Two-pass rendering + const [stencilBorderless, stencilBorders, coords] = painter.stencilConfigForOverlapTwoPass(tileIDs); + renderColorRelief(painter, sourceCache, layer, coords, stencilBorderless, depthMode, colorMode, false, isRenderingToTexture); // draw without borders + renderColorRelief(painter, sourceCache, layer, coords, stencilBorders, depthMode, colorMode, true, isRenderingToTexture); // draw with borders + } else { + // Simple rendering + const [stencil, coords] = painter.getStencilConfigForOverlapAndUpdateStencilID(tileIDs); + renderColorRelief(painter, sourceCache, layer, coords, stencil, depthMode, colorMode, false, isRenderingToTexture); + } +} + +function renderColorRelief( + painter: Painter, + sourceCache: SourceCache, + layer: ColorReliefStyleLayer, + coords: Array, + stencilModes: {[_: number]: Readonly}, + depthMode: Readonly, + colorMode: Readonly, + useBorder: boolean, + isRenderingToTexture: boolean +) { + const projection = painter.style.projection; + const context = painter.context; + const transform = painter.transform; + const gl = context.gl; + const program = painter.useProgram('colorRelief'); + const align = !painter.options.moving; + + if(layer.colorRamp) { + const colorRampTexture = getColorRampTexture(context, layer); + context.activeTexture.set(gl.TEXTURE5); + colorRampTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); + } + + for (const coord of coords) { + const tile = sourceCache.getTile(coord); + const dem = tile.dem; + + if (!dem || !dem.data) { + continue; + } + + const textureStride = dem.stride; + + const pixelData = dem.getPixels(); + context.activeTexture.set(gl.TEXTURE0); + + context.pixelStoreUnpackPremultiplyAlpha.set(false); + tile.demTexture = tile.demTexture || painter.getTileTexture(textureStride); + if (tile.demTexture) { + const demTexture = tile.demTexture; + demTexture.update(pixelData, {premultiply: false}); + demTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); + } else { + tile.demTexture = new Texture(context, pixelData, gl.RGBA, {premultiply: false}); + tile.demTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); + } + + const mesh = projection.getMeshFromTileID(context, coord.canonical, useBorder, true, 'raster'); + + const terrainData = painter.style.map.terrain?.getTerrainData(coord); + + const projectionData = transform.getProjectionData({ + overscaledTileID: coord, + aligned: align, + applyGlobeMatrix: !isRenderingToTexture, + applyTerrainMatrix: true + }); + + program.draw(context, gl.TRIANGLES, depthMode, stencilModes[coord.overscaledZ], colorMode, CullFaceMode.backCCW, + colorReliefUniformValues(layer, tile.dem, layer.elevationRange), terrainData, projectionData, layer.id, mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); + } +} + +function getColorRampTexture(context: Context, layer: ColorReliefStyleLayer): Texture { + if (!layer.colorRampTexture) { + layer.colorRampTexture = new Texture(context, layer.colorRamp, context.gl.RGBA); + } + return layer.colorRampTexture; +} diff --git a/src/render/painter.ts b/src/render/painter.ts index c6deab985b0..bb65802f2fe 100644 --- a/src/render/painter.ts +++ b/src/render/painter.ts @@ -25,6 +25,7 @@ import {drawLine} from './draw_line'; import {drawFill} from './draw_fill'; import {drawFillExtrusion} from './draw_fill_extrusion'; import {drawHillshade} from './draw_hillshade'; +import {drawColorRelief} from './draw_color_relief'; import {drawRaster} from './draw_raster'; import {drawBackground} from './draw_background'; import {drawDebug, drawDebugPadding, selectDebugSource} from './draw_debug'; @@ -56,6 +57,7 @@ import {isLineStyleLayer} from '../style/style_layer/line_style_layer'; import {isFillStyleLayer} from '../style/style_layer/fill_style_layer'; import {isFillExtrusionStyleLayer} from '../style/style_layer/fill_extrusion_style_layer'; import {isHillshadeStyleLayer} from '../style/style_layer/hillshade_style_layer'; +import {isColorReliefStyleLayer} from '../style/style_layer/color_relief_style_layer'; import {isRasterStyleLayer} from '../style/style_layer/raster_style_layer'; import {isBackgroundStyleLayer} from '../style/style_layer/background_style_layer'; import {isCustomStyleLayer} from '../style/style_layer/custom_style_layer'; @@ -671,6 +673,8 @@ export class Painter { drawFillExtrusion(painter, sourceCache, layer, coords, renderOptions); } else if (isHillshadeStyleLayer(layer)) { drawHillshade(painter, sourceCache, layer, coords, renderOptions); + } else if (isColorReliefStyleLayer(layer)) { + drawColorRelief(painter, sourceCache, layer, coords, renderOptions); } else if (isRasterStyleLayer(layer)) { drawRaster(painter, sourceCache, layer, coords, renderOptions); } else if (isBackgroundStyleLayer(layer)) { diff --git a/src/render/program/color_relief_program.ts b/src/render/program/color_relief_program.ts new file mode 100644 index 00000000000..bd756064a23 --- /dev/null +++ b/src/render/program/color_relief_program.ts @@ -0,0 +1,61 @@ +import { + Uniform1i, + Uniform1f, + Uniform2f, + Uniform4f, + UniformFloatArray, + UniformColorArray +} from '../uniform_binding'; + +import type {Context} from '../../gl/context'; +import type {UniformValues, UniformLocations} from '../uniform_binding'; +import type {ColorReliefStyleLayer} from '../../style/style_layer/color_relief_style_layer'; +import type {DEMData} from '../../data/dem_data'; + +export type ColorReliefUniformsType = { + 'u_image': Uniform1i; + 'u_unpack': Uniform4f; + 'u_colormap': Uniform1i; + 'u_colormap_scale': Uniform1f; + 'u_elevation_start': Uniform1f; + 'u_dimension': Uniform2f; + 'u_elevation_stops': UniformFloatArray; + 'u_color_stops': UniformColorArray; + 'u_colormap_length': Uniform1i; +}; + +const colorReliefUniforms = (context: Context, locations: UniformLocations): ColorReliefUniformsType => ({ + 'u_image': new Uniform1i(context, locations.u_image), + 'u_unpack': new Uniform4f(context, locations.u_unpack), + 'u_colormap': new Uniform1i(context, locations.u_colormap), + 'u_colormap_scale': new Uniform1f(context, locations.u_colormap_scale), + 'u_elevation_start': new Uniform1f(context, locations.u_elevation_start), + 'u_dimension': new Uniform2f(context, locations.u_dimension), + 'u_elevation_stops': new UniformFloatArray(context, locations.u_elevation_stops), + 'u_color_stops': new UniformColorArray(context, locations.u_color_stops), + 'u_colormap_length': new Uniform1i(context, locations.u_colormap_length) +}); + +const colorReliefUniformValues = ( + layer: ColorReliefStyleLayer, + dem: DEMData, + elevationRange: {start: number; end: number} +): UniformValues => { + + return { + 'u_image': 0, + 'u_unpack': dem.getUnpackVector(), + 'u_colormap': 5, + 'u_colormap_scale': 1.0 / (elevationRange.end - elevationRange.start), + 'u_elevation_start': elevationRange.start, + 'u_dimension': [dem.stride, dem.stride], + 'u_elevation_stops': layer.elevationStops, + 'u_color_stops': layer.colorStops, + 'u_colormap_length': layer.elevationStops.length + }; +}; + +export { + colorReliefUniforms, + colorReliefUniformValues, +}; diff --git a/src/render/program/program_uniforms.ts b/src/render/program/program_uniforms.ts index d84722ebb4c..2584857580d 100644 --- a/src/render/program/program_uniforms.ts +++ b/src/render/program/program_uniforms.ts @@ -5,6 +5,7 @@ import {collisionUniforms, collisionCircleUniforms} from './collision_program'; import {debugUniforms} from './debug_program'; import {heatmapUniforms, heatmapTextureUniforms} from './heatmap_program'; import {hillshadeUniforms, hillshadePrepareUniforms} from './hillshade_program'; +import {colorReliefUniforms} from './color_relief_program'; import {lineUniforms, lineGradientUniforms, linePatternUniforms, lineSDFUniforms} from './line_program'; import {rasterUniforms} from './raster_program'; import {symbolIconUniforms, symbolSDFUniforms, symbolTextAndIconUniforms} from './symbol_program'; @@ -33,6 +34,7 @@ export const programUniforms = { heatmapTexture: heatmapTextureUniforms, hillshade: hillshadeUniforms, hillshadePrepare: hillshadePrepareUniforms, + colorRelief: colorReliefUniforms, line: lineUniforms, lineGradient: lineGradientUniforms, linePattern: linePatternUniforms, diff --git a/src/render/render_to_texture.ts b/src/render/render_to_texture.ts index b5b95a002ca..62c729cc572 100644 --- a/src/render/render_to_texture.ts +++ b/src/render/render_to_texture.ts @@ -18,7 +18,8 @@ const LAYERS: { [keyof in StyleLayer['type']]?: boolean } = { fill: true, line: true, raster: true, - hillshade: true + hillshade: true, + 'color-relief': true }; /** diff --git a/src/render/uniform_binding.ts b/src/render/uniform_binding.ts index f44e28a6a91..ddf03b3a4e4 100644 --- a/src/render/uniform_binding.ts +++ b/src/render/uniform_binding.ts @@ -174,6 +174,41 @@ class UniformMatrix4f extends Uniform { } } } +class UniformColorArray extends Uniform> { + constructor(context: Context, location: WebGLUniformLocation) { + super(context, location); + this.current = new Array(); + } + + set(v: Array): void { + if (v != this.current) { + this.current = v; + const values = new Float32Array(v.length*4); + for( let i = 0; i < v.length; i++) { + values[4*i] = v[i].r; + values[4*i+1] = v[i].g; + values[4*i+2] = v[i].b; + values[4*i+3] = v[i].a; + } + this.gl.uniform4fv(this.location, values); + } + } +} + +class UniformFloatArray extends Uniform> { + constructor(context: Context, location: WebGLUniformLocation) { + super(context, location); + this.current = new Array(); + } + + set(v: Array): void { + if (v != this.current) { + this.current = v; + const values = new Float32Array(v); + this.gl.uniform1fv(this.location, values); + } + } +} export { Uniform, diff --git a/src/shaders/color_relief.fragment.glsl b/src/shaders/color_relief.fragment.glsl new file mode 100644 index 00000000000..142e1c91b49 --- /dev/null +++ b/src/shaders/color_relief.fragment.glsl @@ -0,0 +1,61 @@ +uniform sampler2D u_image; +uniform vec4 u_unpack; +uniform float u_colormap_scale; +uniform float u_elevation_start; +uniform sampler2D u_colormap; +uniform float u_elevation_stops[17]; +uniform vec4 u_color_stops[17]; +uniform int u_colormap_length; + +in vec2 v_pos; + +float getElevation(vec2 coord) { + // Convert encoded elevation value to meters + vec4 data = texture(u_image, coord) * 255.0; + data.a = -1.0; + return dot(data, u_unpack); +} + +void main() { + float el = getElevation(v_pos); + + // Naive lookup table + /*fragColor = u_color_stops[0]; + for(int i = 0; i < u_colormap_length - 1; i++) + { + if(el >= u_elevation_stops[i] && el < u_elevation_stops[i+1]) + { + fragColor = mix(u_color_stops[i], + u_color_stops[i+1], + (el - u_elevation_stops[i])/(u_elevation_stops[i+1]-u_elevation_stops[i])); + } + }*/ + + // Binary search + int r = (u_colormap_length - 1); + int l = 0; + while(r - l > 1) + { + int m = (r + l) / 2; + if(el < u_elevation_stops[m]) + { + r = m; + } + else + { + l = m; + } + } + fragColor = mix(u_color_stops[l], + u_color_stops[r], + clamp((el - u_elevation_stops[l])/(u_elevation_stops[r]-u_elevation_stops[l]), 0.0, 1.0)); + + + // Texture interpolation + //float x = (el - u_elevation_start)*u_colormap_scale; + //fragColor = texture(u_colormap, vec2(x, 0)); + +#ifdef OVERDRAW_INSPECTOR + fragColor = vec4(1.0); +#endif +} diff --git a/src/shaders/color_relief.vertex.glsl b/src/shaders/color_relief.vertex.glsl new file mode 100644 index 00000000000..83ffc659b85 --- /dev/null +++ b/src/shaders/color_relief.vertex.glsl @@ -0,0 +1,20 @@ +uniform vec2 u_dimension; + +in vec2 a_pos; + +out vec2 v_pos; + +void main() { + gl_Position = projectTile(a_pos, a_pos); + highp vec2 epsilon = 1.0 / u_dimension; + float scale = (u_dimension.x - 2.0) / u_dimension.x; + v_pos = (a_pos / 8192.0) * scale + epsilon; + // North pole + if (a_pos.y < -32767.5) { + v_pos.y = 0.0; + } + // South pole + if (a_pos.y > 32766.5) { + v_pos.y = 1.0; + } +} diff --git a/src/shaders/shaders.ts b/src/shaders/shaders.ts index d8ad62f0d3c..053cf057b8b 100644 --- a/src/shaders/shaders.ts +++ b/src/shaders/shaders.ts @@ -18,6 +18,8 @@ import collisionBoxFrag from './collision_box.fragment.glsl.g'; import collisionBoxVert from './collision_box.vertex.glsl.g'; import collisionCircleFrag from './collision_circle.fragment.glsl.g'; import collisionCircleVert from './collision_circle.vertex.glsl.g'; +import colorReliefFrag from './color_relief.fragment.glsl.g'; +import colorReliefVert from './color_relief.vertex.glsl.g'; import debugFrag from './debug.fragment.glsl.g'; import debugVert from './debug.vertex.glsl.g'; import depthVert from './depth.vertex.glsl.g'; @@ -87,6 +89,7 @@ export const shaders = { heatmapTexture: prepare(heatmapTextureFrag, heatmapTextureVert), collisionBox: prepare(collisionBoxFrag, collisionBoxVert), collisionCircle: prepare(collisionCircleFrag, collisionCircleVert), + colorRelief: prepare(colorReliefFrag, colorReliefVert), debug: prepare(debugFrag, debugVert), depth: prepare(clippingMaskFrag, depthVert), fill: prepare(fillFrag, fillVert), diff --git a/src/style/create_style_layer.ts b/src/style/create_style_layer.ts index 25905cad631..fb523213404 100644 --- a/src/style/create_style_layer.ts +++ b/src/style/create_style_layer.ts @@ -1,6 +1,7 @@ import {CircleStyleLayer} from './style_layer/circle_style_layer'; import {HeatmapStyleLayer} from './style_layer/heatmap_style_layer'; import {HillshadeStyleLayer} from './style_layer/hillshade_style_layer'; +import {ColorReliefStyleLayer} from './style_layer/color_relief_style_layer'; import {FillStyleLayer} from './style_layer/fill_style_layer'; import {FillExtrusionStyleLayer} from './style_layer/fill_extrusion_style_layer'; import {LineStyleLayer} from './style_layer/line_style_layer'; @@ -20,6 +21,8 @@ export function createStyleLayer(layer: LayerSpecification | CustomLayerInterfac return new BackgroundStyleLayer(layer); case 'circle': return new CircleStyleLayer(layer); + case 'color-relief': + return new ColorReliefStyleLayer(layer); case 'fill': return new FillStyleLayer(layer); case 'fill-extrusion': diff --git a/src/style/style_layer/color_relief_style_layer.ts b/src/style/style_layer/color_relief_style_layer.ts new file mode 100644 index 00000000000..20cedd5f2df --- /dev/null +++ b/src/style/style_layer/color_relief_style_layer.ts @@ -0,0 +1,61 @@ +import {StyleLayer} from '../style_layer'; + +import properties, {type ColorReliefPaintPropsPossiblyEvaluated} from './color_relief_style_layer_properties.g'; +import {type Transitionable, type Transitioning, type PossiblyEvaluated} from '../properties'; + +import type {ColorReliefPaintProps} from './color_relief_style_layer_properties.g'; +import {Color, Interpolate, ZoomConstantExpression, type LayerSpecification} from '@maplibre/maplibre-gl-style-spec'; +import type {Texture} from '../../render/texture'; +import type {RGBAImage} from '../../util/image'; +import {renderColorRamp} from '../../util/color_ramp'; +import {nextPowerOfTwo} from '../../util/util'; + +export const isColorReliefStyleLayer = (layer: StyleLayer): layer is ColorReliefStyleLayer => layer.type === 'color-relief'; + +export class ColorReliefStyleLayer extends StyleLayer { + colorRamp: RGBAImage; + colorRampTexture: Texture; + elevationRange: {start: number; end: number}; + elevationStops: Array; + colorStops: Array; + _transitionablePaint: Transitionable; + _transitioningPaint: Transitioning; + paint: PossiblyEvaluated; + + constructor(layer: LayerSpecification) { + super(layer, properties); this._updateColorRamp(); + } + + _updateColorRamp() { + const expression = this._transitionablePaint._values['color-relief-color'].value.expression; + if (expression instanceof ZoomConstantExpression && expression._styleExpression.expression instanceof Interpolate) { + const interpolater = expression._styleExpression.expression; + this.elevationRange = {start: interpolater.labels[0], end: interpolater.labels[interpolater.labels.length-1]}; + this.elevationStops = []; + this.colorStops = []; + for (const label of interpolater.labels) { + this.elevationStops.push(label); + this.colorStops.push(interpolater.evaluate({globals: { elevation: label }} as any)) + } + const colormapLength = nextPowerOfTwo(this.elevationStops.length - 1) + 1; + while (this.elevationStops.length < colormapLength) { + this.elevationStops.push(this.elevationStops.at(-1)); + this.colorStops.push(this.colorStops.at(-1)); + } + this.colorRamp = renderColorRamp({ + expression, + evaluationKey: 'elevation', + image: this.colorRamp, + clips: [this.elevationRange] + }); + } else{ + this.elevationRange = {start: 0, end: 1}; + this.colorRamp = null; + } + this.colorRampTexture = null; + } + + hasOffscreenPass() { + return this.visibility !== 'none' && !!this.colorRamp; + } +} diff --git a/src/style/style_layer/typed_style_layer.ts b/src/style/style_layer/typed_style_layer.ts index a340484e7e6..dc45b20dfd3 100644 --- a/src/style/style_layer/typed_style_layer.ts +++ b/src/style/style_layer/typed_style_layer.ts @@ -3,7 +3,8 @@ import type {FillStyleLayer} from './fill_style_layer'; import type {FillExtrusionStyleLayer} from './fill_extrusion_style_layer'; import type {HeatmapStyleLayer} from './heatmap_style_layer'; import type {HillshadeStyleLayer} from './hillshade_style_layer'; +import type {ColorReliefStyleLayer} from './color_relief_style_layer'; import type {LineStyleLayer} from './line_style_layer'; import type {SymbolStyleLayer} from './symbol_style_layer'; -export type TypedStyleLayer = CircleStyleLayer | FillStyleLayer | FillExtrusionStyleLayer | HeatmapStyleLayer | HillshadeStyleLayer | LineStyleLayer | SymbolStyleLayer; +export type TypedStyleLayer = CircleStyleLayer | FillStyleLayer | FillExtrusionStyleLayer | HeatmapStyleLayer | HillshadeStyleLayer | ColorReliefStyleLayer | LineStyleLayer | SymbolStyleLayer; diff --git a/src/ui/map.ts b/src/ui/map.ts index 1300eb3e8e1..31bc22bfba6 100644 --- a/src/ui/map.ts +++ b/src/ui/map.ts @@ -2066,12 +2066,15 @@ export class Map extends Camera { if (!sourceCache) throw new Error(`cannot load terrain, because there exists no source with ID: ${options.source}`); // Update terrain tiles when adding new terrain if (this.terrain === null) sourceCache.reload(); - // Warn once if user is using the same source for hillshade and terrain + // Warn once if user is using the same source for hillshade/color-relief and terrain for (const index in this.style._layers) { const thisLayer = this.style._layers[index]; if (thisLayer.type === 'hillshade' && thisLayer.source === options.source) { warnOnce('You are using the same source for a hillshade layer and for 3D terrain. Please consider using two separate sources to improve rendering quality.'); } + if (thisLayer.type === 'color-relief' && thisLayer.source === options.source) { + warnOnce('You are using the same source for a color-relief layer and for 3D terrain. Please consider using two separate sources to improve rendering quality.'); + } } this.terrain = new Terrain(this.painter, sourceCache, options); this.painter.renderToTexture = new RenderToTexture(this.painter, this.terrain); diff --git a/test/bench/benchmarks/color_relief_load.ts b/test/bench/benchmarks/color_relief_load.ts new file mode 100644 index 00000000000..354b517fae6 --- /dev/null +++ b/test/bench/benchmarks/color_relief_load.ts @@ -0,0 +1,56 @@ +import Benchmark from '../lib/benchmark'; +import createMap from '../lib/create_map'; +import type {StyleSpecification} from '@maplibre/maplibre-gl-style-spec'; + +/** + * Measures how long it takes the map to reach the idle state when only using color-relief tiles. + */ +export default class ColorReliefLoad extends Benchmark { + style: StyleSpecification; + + constructor() { + super(); + + // This is a longer running test and the duration will vary by device and network. + // To keep the test time more reasonable, lower the minimum number of measurements. + // 55 measurements => 10 observations for regression. + this.minimumMeasurements = 55; + + this.style = { + 'version': 8, + 'name': 'Color-relief-only', + 'center': [-112.81596278901452, 37.251160384573595], + 'zoom': 11.560975632435424, + 'bearing': 0, + 'pitch': 0, + 'sources': { + 'terrain-rgb': { + 'url': 'https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=get_your_own_OpIi9ZULNHzrESv6T2vL', + 'type': 'raster-dem', + 'tileSize': 256 + } + }, + 'layers': [ + { + 'id': 'maplibre-terrain-rgb', + 'type': 'color-relief', + 'source': 'terrain-rgb', + 'layout': {}, + 'paint': {} + } + ] + }; + } + + async bench() { + const map = await createMap({ + width: 1024, + height: 1024, + style: this.style, + stubRender: false, + showMap: true, + idle: true + }); + map.remove(); + } +} diff --git a/test/bench/versions/index.ts b/test/bench/versions/index.ts index 63261f46f7e..fbf09d3f3d6 100644 --- a/test/bench/versions/index.ts +++ b/test/bench/versions/index.ts @@ -10,6 +10,7 @@ import {PropertyLevelRemove, FeatureLevelRemove, SourceLevelRemove} from '../ben import {LayerBackground, LayerCircle, LayerFill, LayerFillExtrusion, LayerHeatmap, LayerHillshade, LayerLine, LayerRaster, LayerSymbol, LayerSymbolWithIcons, LayerTextWithVariableAnchor, LayerSymbolWithSortKey} from '../benchmarks/layers'; import Load from '../benchmarks/map_load'; import HillshadeLoad from '../benchmarks/hillshade_load'; +import ColorReliefLoad from '../benchmarks/color_relief_load'; import Validate from '../benchmarks/style_validate'; import StyleLayerCreate from '../benchmarks/style_layer_create'; import QueryPoint from '../benchmarks/query_point'; @@ -76,6 +77,7 @@ register('SymbolLayout', new SymbolLayout(style, styleLocations.map(location => register('FilterCreate', new FilterCreate()); register('FilterEvaluate', new FilterEvaluate()); register('HillshadeLoad', new HillshadeLoad()); +register('ColorReliefLoad', new ColorReliefLoad()); register('CustomLayer', new CustomLayer()); register('MapIdle', new MapIdle()); register('SymbolCollisionBox', new SymbolCollisionBox(false));