s2maps-gpu
Version:
S2 Maps GPU - An open source, high-performance, and GPU-accelerated map engine for rendering large-scale, interactive maps.
232 lines (231 loc) • 8.57 kB
JavaScript
import encodeLayerAttribute from 'style/encodeLayerAttribute.js';
import Workflow, { Feature } from './workflow.js';
// WEBGL1
import frag1 from '../shaders/raster1.fragment.glsl';
import vert1 from '../shaders/raster1.vertex.glsl';
// WEBGL2
import frag2 from '../shaders/raster2.fragment.glsl';
import vert2 from '../shaders/raster2.vertex.glsl';
/** Raster Feature is a standalone raster render storage unit that can be drawn to the GPU */
export class RasterFeature extends Feature {
layerGuide;
workflow;
source;
featureCode;
tile;
fadeStartTime;
parent;
type = 'raster';
opacity; // webgl1
saturation; // webgl1
contrast; // webgl1
/**
* @param layerGuide - layer guide for this feature
* @param workflow - the raster workflow
* @param source - the raster source
* @param featureCode - the encoded feature code that tells the GPU how to compute it's properties
* @param tile - the tile that the feature is drawn on
* @param fadeStartTime - the start time of the "fade" to be applied
* @param parent - the parent tile
*/
constructor(layerGuide, workflow, source, featureCode, tile, fadeStartTime = Date.now(), parent) {
super(workflow, tile, layerGuide, featureCode, parent);
this.layerGuide = layerGuide;
this.workflow = workflow;
this.source = source;
this.featureCode = featureCode;
this.tile = tile;
this.fadeStartTime = fadeStartTime;
this.parent = parent;
}
/**
* Draw this feature to the GPU
* @param interactive - whether or not the feature is interactive for compute or render
*/
draw(interactive = false) {
super.draw(interactive);
this.workflow.draw(this, interactive);
}
/**
* Duplicate this feature
* @param tile - the tile that the feature is drawn on
* @param parent - the parent tile if applicable
* @returns the duplicated feature
*/
duplicate(tile, parent) {
const { layerGuide, workflow, source, featureCode, fadeStartTime, opacity, saturation, contrast, } = this;
const newFeature = new RasterFeature(layerGuide, workflow, source, featureCode, tile, fadeStartTime, parent);
newFeature.setWebGL1Attributes(opacity, saturation, contrast);
return newFeature;
}
/**
* Set the webgl1 attributes if the context is webgl1
* @param opacity - the opacity
* @param saturation - the saturation
* @param contrast - the contrast
*/
setWebGL1Attributes(opacity, saturation, contrast) {
this.opacity = opacity;
this.saturation = saturation;
this.contrast = contrast;
}
}
/** Raster Workflow */
export default class RasterWorkflow extends Workflow {
label = 'raster';
curSample = 'none';
layerGuides = new Map();
/** @param context - the WebGL(1|2) context */
constructor(context) {
// get gl from context
const { type } = context;
// inject Program
super(context);
// build shaders
if (type === 1)
this.buildShaders(vert1, frag1, { aPos: 0 });
else
this.buildShaders(vert2, frag2);
}
/**
* Set the sample type
* @param type - the sample type
*/
#setSampleType(type) {
const { curSample, gl } = this;
if (curSample === type)
return;
this.curSample = type;
if (type === 'nearest') {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
}
else {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
}
}
/**
* Build a layer definition for this workflow given the user input layer
* @param layerBase - the common layer attributes
* @param layer - the user defined layer attributes
* @returns a built layer definition that's ready to describe how to render a raster feature
*/
buildLayerDefinition(layerBase, layer) {
const { type } = this;
const { source, layerIndex, lch, visible, opaque } = layerBase;
// PRE) get layer base
const { resampling, fadeDuration } = layer;
let { opacity, saturation, contrast } = layer;
opacity = opacity ?? 1;
saturation = saturation ?? 0;
contrast = contrast ?? 0;
// 1) build definition
const layerDefinition = {
...layerBase,
type: 'raster',
opacity: opacity ?? 1,
saturation: saturation ?? 0,
contrast: contrast ?? 0,
};
// 2) Store layer workflow, building code if webgl2
const layerCode = [];
if (type === 2) {
for (const paint of [opacity, saturation, contrast]) {
layerCode.push(...encodeLayerAttribute(paint, lch));
}
}
this.layerGuides.set(layerIndex, {
sourceName: source,
layerIndex,
layerCode,
lch,
fadeDuration: fadeDuration ?? 300,
resampling: resampling ?? 'linear',
visible,
interactive: false,
opaque: opaque ?? false,
});
return layerDefinition;
}
/**
* Build the source raster data into raster features
* @param rasterData - the input raster data
* @param tile - the tile we are building the features for
*/
buildSource(rasterData, tile) {
const { context } = this;
const { image, size } = rasterData;
// setup texture params
const texture = context.buildTexture(image, size);
// Extend mask
const rasterSource = { type: 'raster', texture, size };
this.#buildFeatures(rasterSource, rasterData, tile);
}
/**
* Build features for this source
* @param source - the raster source
* @param rasterData - the input raster data
* @param tile - the tile we are building the features for
*/
#buildFeatures(source, rasterData, tile) {
const { featureGuides } = rasterData;
// for each layer that maches the source, build the feature
const features = [];
for (const { code, layerIndex } of featureGuides) {
const layerGuide = this.layerGuides.get(layerIndex);
if (layerGuide === undefined)
continue;
const feature = new RasterFeature(layerGuide, this, source, code, tile);
if (this.type === 1)
feature.setWebGL1Attributes(code[0], code[1], code[2]);
features.push(feature);
}
tile.addFeatures(features);
}
/** Use this workflow as the current shaders for the GPU */
use() {
super.use();
const { context } = this;
context.defaultBlend();
context.enableDepthTest();
context.enableCullFace();
context.enableStencilTest();
context.lessDepth();
}
/**
* Draw a raster feature
* @param feature - the feature to draw
* @param _interactive - whether or not the feature is interactive
*/
draw(feature, _interactive = false) {
// grab gl from the context
const { type, gl, context, uniforms } = this;
const { uFade, uOpacity, uContrast, uSaturation } = uniforms;
// get current source data
const { tile, parent, source, featureCode, opacity, contrast, saturation, layerGuide: { layerIndex, visible, resampling }, } = feature;
if (!visible)
return;
const { texture } = source;
const { vao, count, offset } = (parent ?? tile).mask;
context.setDepthRange(layerIndex);
// set fade
gl.uniform1f(uFade, 1);
// set feature code (webgl 1 we store the opacity, webgl 2 we store layerCode lookups)
if (type === 1) {
gl.uniform1f(uOpacity, opacity ?? 1);
gl.uniform1f(uContrast, contrast ?? 0);
gl.uniform1f(uSaturation, saturation ?? 0);
}
else {
this.setFeatureCode(featureCode);
}
// bind vao
gl.bindVertexArray(vao);
// setup the texture
gl.bindTexture(gl.TEXTURE_2D, texture);
this.#setSampleType(resampling);
// draw elements
gl.drawElements(gl.TRIANGLE_STRIP, count, gl.UNSIGNED_INT, offset);
}
}