import {Texture} from '../texture'; import {StencilMode} from '../stencil_mode'; import {DepthMode} from '../depth_mode'; import {CullFaceMode} from '../cull_face_mode'; import {type ColorMode} from '../color_mode'; import { hillshadeUniformValues, hillshadeUniformPrepareValues } from '../program/hillshade_program'; import type {Painter, RenderOptions} from '../../render/painter'; import type {TileManager} from '../../tile/tile_manager'; import type {HillshadeStyleLayer} from '../../style/style_layer/hillshade_style_layer'; import type {OverscaledTileID} from '../../tile/tile_id'; export function drawHillshade(painter: Painter, tileManager: TileManager, layer: HillshadeStyleLayer, tileIDs: OverscaledTileID[], renderOptions: RenderOptions) { if (painter.renderPass !== 'offscreen' && painter.renderPass !== 'translucent') return; const {isRenderingToTexture} = renderOptions; const context = painter.context; const projection = painter.style.projection; const useSubdivision = projection.useSubdivision; const depthMode = painter.getDepthModeForSublayer(0, DepthMode.ReadOnly); const colorMode = painter.colorModeForRenderPass(); if (painter.renderPass === 'offscreen') { // Prepare tiles prepareHillshade(painter, tileManager, tileIDs, layer, depthMode, StencilMode.disabled, colorMode); context.viewport.set([0, 0, painter.width, painter.height]); } else if (painter.renderPass === 'translucent') { // 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); renderHillshade(painter, tileManager, layer, coords, stencilBorderless, depthMode, colorMode, false, isRenderingToTexture); // draw without borders renderHillshade(painter, tileManager, layer, coords, stencilBorders, depthMode, colorMode, true, isRenderingToTexture); // draw with borders } else { // Simple rendering const [stencil, coords] = painter.getStencilConfigForOverlapAndUpdateStencilID(tileIDs); renderHillshade(painter, tileManager, layer, coords, stencil, depthMode, colorMode, false, isRenderingToTexture); } } } function renderHillshade( painter: Painter, tileManager: TileManager, layer: HillshadeStyleLayer, coords: OverscaledTileID[], 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 defines = [`#define NUM_ILLUMINATION_SOURCES ${layer.paint.get('hillshade-highlight-color').values.length}`]; const program = painter.useProgram('hillshade', null, false, defines); const align = !painter.options.moving; for (const coord of coords) { const tile = tileManager.getTile(coord); const fbo = tile.fbo; if (!fbo) { continue; } const mesh = projection.getMeshFromTileID(context, coord.canonical, useBorder, true, 'raster'); const terrainData = painter.style.map.terrain?.getTerrainData(coord); context.activeTexture.set(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); const projectionData = transform.getProjectionData({ overscaledTileID: coord, aligned: align, applyGlobeMatrix: !isRenderingToTexture, applyTerrainMatrix: true }); program.draw(context, gl.TRIANGLES, depthMode, stencilModes[coord.overscaledZ], colorMode, CullFaceMode.backCCW, hillshadeUniformValues(painter, tile, layer), terrainData, projectionData, layer.id, mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); } } // hillshade rendering is done in two steps. the prepare step first calculates the slope of the terrain in the x and y // directions for each pixel, and saves those values to a framebuffer texture in the r and g channels. function prepareHillshade( painter: Painter, tileManager: TileManager, tileIDs: OverscaledTileID[], layer: HillshadeStyleLayer, depthMode: Readonly, stencilMode: Readonly, colorMode: Readonly) { const context = painter.context; const gl = context.gl; const textureFilter = layer.paint.get('resampling') === 'nearest' ? gl.NEAREST : gl.LINEAR; for (const coord of tileIDs) { const tile = tileManager.getTile(coord); const dem = tile.dem; if (!dem?.data) { continue; } if (!tile.needsHillshadePrepare) { continue; } const tileSize = dem.dim; const textureStride = dem.stride; const pixelData = dem.getPixels(); context.activeTexture.set(gl.TEXTURE1); 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.NEAREST, gl.CLAMP_TO_EDGE); } else { tile.demTexture = new Texture(context, pixelData, gl.RGBA, {premultiply: false}); tile.demTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE); } context.activeTexture.set(gl.TEXTURE0); let fbo = tile.fbo; if (!fbo) { const renderTexture = new Texture(context, {width: tileSize, height: tileSize, data: null}, gl.RGBA); renderTexture.bind(textureFilter, gl.CLAMP_TO_EDGE); fbo = tile.fbo = context.createFramebuffer(tileSize, tileSize, true, false); fbo.colorAttachment.set(renderTexture.texture); } context.bindFramebuffer.set(fbo.framebuffer); context.viewport.set([0, 0, tileSize, tileSize]); painter.useProgram('hillshadePrepare').draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, hillshadeUniformPrepareValues(tile.tileID, dem), null, null, layer.id, painter.rasterBoundsBuffer, painter.quadTriangleIndexBuffer, painter.rasterBoundsSegments); tile.needsHillshadePrepare = false; } }