maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
186 lines (159 loc) • 8.17 kB
text/typescript
import {Color} from '@maplibre/maplibre-gl-style-spec';
import {DepthMode} from '../depth_mode.ts';
import {CullFaceMode} from '../cull_face_mode.ts';
import {
fillUniformValues,
fillPatternUniformValues,
fillOutlineUniformValues,
fillOutlinePatternUniformValues
} from '../program/fill_program.ts';
import {updatePatternPositionsInProgram} from '../../render/update_pattern_positions_in_program.ts';
import {translatePosition} from '../../util/util.ts';
import {drawLayerOpacity, prepareDrawLayerOpacity} from './draw_layer_opacity.ts';
import type {ColorMode} from '../color_mode.ts';
import type {Painter, RenderOptions} from '../../render/painter.ts';
import type {TileManager} from '../../tile/tile_manager.ts';
import type {FillStyleLayer} from '../../style/style_layer/fill_style_layer.ts';
import type {FillBucket} from '../../data/bucket/fill_bucket.ts';
import type {OverscaledTileID} from '../../tile/tile_id.ts';
export function drawFill(painter: Painter, tileManager: TileManager, layer: FillStyleLayer, coords: OverscaledTileID[], renderOptions: RenderOptions): void {
const color = layer.paint.get('fill-color');
const opacity = layer.paint.get('fill-opacity');
const layerOpacity = layer.paint.get('fill-layer-opacity');
if (opacity.constantOr(1) === 0 || layerOpacity === 0) return;
if (layerOpacity < 1) {
if (painter.renderPass !== 'translucent') return;
const useTerrain = !!painter.style.map.terrain;
const results = prepareDrawLayerOpacity(painter,layer, coords, useTerrain);
drawFillAndOutline(painter, tileManager, layer, coords, renderOptions);
drawLayerOpacity(painter, layerOpacity, results, layer);
return;
}
const pattern = layer.paint.get('fill-pattern');
const fillEligibleForOpaque = painter.opaquePassEnabledForLayer() &&
!pattern.constantOr(1 as any) &&
color.constantOr(Color.transparent).a === 1 &&
opacity.constantOr(0) === 1;
if (fillEligibleForOpaque && painter.renderPass === 'opaque') {
// Opaque-eligible fill draws standalone in the opaque pass with ReadWrite depth;
// its outline (always translucent) runs in the translucent pass below.
const {isRenderingToTexture} = renderOptions;
const colorMode = painter.colorModeForRenderPass();
const depthMode = painter.getDepthModeForSublayer(1, DepthMode.ReadWrite);
drawFillTiles(painter, tileManager, layer, coords, depthMode, colorMode, false, isRenderingToTexture);
return;
}
if (fillEligibleForOpaque && painter.renderPass === 'translucent') {
// Fill already drew in the opaque pass; just draw the outline here.
drawOutline(painter, tileManager, layer, coords, renderOptions);
return;
}
if (painter.renderPass === 'translucent') {
drawFillAndOutline(painter, tileManager, layer, coords, renderOptions);
}
}
/**
* Draw fill + outline in a single translucent pass with ReadOnly depth.
* Shared by the layer-opacity subpass (always) and the normal translucent path
* (when the fill is not opaque-pass-eligible).
*/
function drawFillAndOutline(
painter: Painter,
tileManager: TileManager,
layer: FillStyleLayer,
coords: OverscaledTileID[],
renderOptions: RenderOptions
) {
const {isRenderingToTexture} = renderOptions;
const colorMode = painter.colorModeForRenderPass();
const fillDepthMode = painter.getDepthModeForSublayer(1, DepthMode.ReadOnly);
drawFillTiles(painter, tileManager, layer, coords, fillDepthMode, colorMode, false, isRenderingToTexture);
drawOutline(painter, tileManager, layer, coords, renderOptions);
}
function drawOutline(
painter: Painter,
tileManager: TileManager,
layer: FillStyleLayer,
coords: OverscaledTileID[],
renderOptions: RenderOptions
) {
if (!layer.paint.get('fill-antialias')) return;
// 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
// clipping mask.
// Otherwise, we only want to drawFill the antialiased parts that are
// *outside* the current shape. This is important in case the fill
// 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.
const {isRenderingToTexture} = renderOptions;
const colorMode = painter.colorModeForRenderPass();
const depthMode = painter.getDepthModeForSublayer(
layer.getPaintProperty('fill-outline-color') ? 2 : 0, DepthMode.ReadOnly);
drawFillTiles(painter, tileManager, layer, coords, depthMode, colorMode, true, isRenderingToTexture);
}
function drawFillTiles(
painter: Painter,
tileManager: TileManager,
layer: FillStyleLayer,
coords: OverscaledTileID[],
depthMode: Readonly<DepthMode>,
colorMode: Readonly<ColorMode>,
isOutline: boolean,
isRenderingToTexture: boolean) {
const gl = painter.context.gl;
const fillPropertyName = 'fill-pattern';
const patternProperty = layer.paint.get(fillPropertyName);
const image = patternProperty?.constantOr(1 as any);
const crossfade = layer.getCrossfadeParameters();
let drawMode, programName, uniformValues, indexBuffer, segments;
const transform = painter.transform;
const propertyFillTranslate = layer.paint.get('fill-translate');
const propertyFillTranslateAnchor = layer.paint.get('fill-translate-anchor');
if (!isOutline) {
programName = image ? 'fillPattern' : 'fill';
drawMode = gl.TRIANGLES;
} else {
programName = image && !layer.getPaintProperty('fill-outline-color') ? 'fillOutlinePattern' : 'fillOutline';
drawMode = gl.LINES;
}
const constantPattern = patternProperty.constantOr(null);
for (const coord of coords) {
const tile = tileManager.getTile(coord);
if (image && !tile.patternsLoaded()) continue;
const bucket: FillBucket = (tile.getBucket(layer) as any);
if (!bucket) continue;
const programConfiguration = bucket.programConfigurations.get(layer.id);
const program = painter.useProgram(programName, programConfiguration);
const terrainData = painter.getTerrainDataForTile(coord, isRenderingToTexture);
if (image) {
painter.context.activeTexture.set(gl.TEXTURE0);
tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
programConfiguration.updatePaintBuffers(crossfade);
}
updatePatternPositionsInProgram(programConfiguration, fillPropertyName, constantPattern, tile, layer);
const projectionData = transform.getProjectionData({
overscaledTileID: coord,
applyGlobeMatrix: !isRenderingToTexture,
applyTerrainMatrix: true
});
const translateForUniforms = translatePosition(transform, tile, propertyFillTranslate, propertyFillTranslateAnchor);
if (!isOutline) {
indexBuffer = bucket.indexBuffer;
segments = bucket.segments;
uniformValues = image ? fillPatternUniformValues(painter, crossfade, tile, translateForUniforms) : fillUniformValues(translateForUniforms);
} else {
indexBuffer = bucket.indexBuffer2;
segments = bucket.segments2;
const drawingBufferSize = [gl.drawingBufferWidth, gl.drawingBufferHeight] as [number, number];
uniformValues = (programName === 'fillOutlinePattern' && image) ?
fillOutlinePatternUniformValues(painter, crossfade, tile, drawingBufferSize, translateForUniforms) :
fillOutlineUniformValues(drawingBufferSize, translateForUniforms);
}
const stencil = painter.stencilModeForClipping(coord);
program.draw(painter.context, drawMode, depthMode,
stencil, colorMode, CullFaceMode.backCCW, uniformValues, terrainData, projectionData,
layer.id, bucket.layoutVertexBuffer, indexBuffer, segments,
layer.paint, painter.transform.zoom, programConfiguration);
}
}