s2maps-gpu
Version:
S2 Maps GPU - An open source, high-performance, and GPU-accelerated map engine for rendering large-scale, interactive maps.
333 lines (332 loc) • 11.5 kB
JavaScript
import loadShader from './loadShader.js';
/** A Generic Feature that can be drawn to the GPU */
export class Feature {
workflow;
tile;
layerGuide;
featureCode;
parent;
bounds;
/**
* @param workflow - the input workflow
* @param tile - the tile that the feature is drawn on
* @param layerGuide - the layer guide
* @param featureCode - the feature code that tells the GPU how to compute it's properties
* @param parent - the parent tile if applicable
* @param bounds - the bounds of the tile if applicable
*/
constructor(workflow, tile, layerGuide, featureCode = [0], parent, bounds) {
this.workflow = workflow;
this.tile = tile;
this.layerGuide = layerGuide;
this.featureCode = featureCode;
this.parent = parent;
this.bounds = bounds;
}
/**
* Draw the feature to the GPU
* @param interactive - whether or not the feature is interactive
*/
draw(interactive = false) {
const { tile, parent, workflow, layerGuide } = this;
const { context } = workflow;
const { layerIndex, layerCode, lch } = layerGuide;
// let the context know the current workflow
workflow.context.setWorkflow(workflow);
// ensure the tile information is set
workflow.setTileUniforms(tile, parent);
// setup stencil
context.stencilFuncEqual(tile.tmpMaskID);
// set layer code
workflow.setLayerCode(layerIndex, layerCode, lch);
// set interactive if applicable
workflow.setInteractive(interactive);
}
/** Destroy the feature */
destroy() { }
}
/** Generic Workflow used by most workflows */
export default class Workflow {
vertexShader;
fragmentShader;
radii = false;
context;
gl;
type;
glProgram;
updateColorBlindMode = null;
updateMatrix = null; // pointer
updateInputs = null; // pointer
updateAspect = null; // pointer
curLayer = -1;
curMode = -1;
curTile = -1n;
LCH;
interactive;
uniforms;
/**
* @param context - the context to use that tracks the GPU state
*/
constructor(context) {
// set context
this.context = context;
// grab variables we need
const { gl, type } = context;
this.gl = gl;
this.type = type >= 2 ? 2 : 1;
// create the program
const program = gl.createProgram();
if (program === null)
throw Error('Failed to create program');
this.glProgram = program;
}
/**
* @param vertex - the vertex shader
* @param fragment - the fragment shader
* @param attributeLocations - the attribute locations
*/
buildShaders(vertex, fragment, attributeLocations) {
const { gl, glProgram } = this;
// setup attribute locations prior to building
if (attributeLocations !== undefined)
this.setupAttributes(vertex.attributes, attributeLocations);
// load vertex and fragment shaders
const vertexShader = (this.vertexShader = loadShader(gl, vertex.source, gl.VERTEX_SHADER));
const fragmentShader = (this.fragmentShader = loadShader(gl, fragment.source, gl.FRAGMENT_SHADER));
// if shaders worked, attach, link, validate, etc.
gl.attachShader(glProgram, vertexShader);
gl.attachShader(glProgram, fragmentShader);
gl.linkProgram(glProgram);
if (gl.getProgramParameter(glProgram, gl.LINK_STATUS) === false) {
throw Error(gl.getProgramInfoLog(glProgram) ?? 'Failed to link program');
}
const uniforms = { ...vertex.uniforms, ...fragment.uniforms };
this.setupUniforms(uniforms);
}
/**
* Setup the connections to uniforms in the shader
* @param uniforms - the mapping of uniform names to shader names
*/
setupUniforms(uniforms) {
const { gl, glProgram } = this;
this.uniforms = {};
for (const [uniform, code] of Object.entries(uniforms)) {
// const uniformName = uniform as keyof this
const location = gl.getUniformLocation(glProgram, code);
if (location === null) {
console.error(`failed to get uniform location for ${uniform}`);
continue;
}
this.uniforms[uniform] = location;
}
}
/**
* Setup shader attributes, their names and locations
* @param attributes - the mapping of attribute names to shader names
* @param attributeLocations - the mapping of attribute names to locations
*/
setupAttributes(attributes, attributeLocations) {
const { gl, glProgram } = this;
for (const attr in attributeLocations) {
gl.bindAttribLocation(glProgram, attributeLocations[attr], attributes[attr]);
}
}
/** Delete the workflow and it's shaders */
delete() {
const { gl, vertexShader, fragmentShader } = this;
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
}
/** Activate this workflow as the current shaders for the GPU */
use() {
const { gl, glProgram } = this;
// reset tile tracker since it impacts wether we update our matrix or not
this.curTile = -1n;
gl.useProgram(glProgram);
this.flush();
}
/**
* Inject uniforms that are common to the frame
* @param matrix - the projection matrix
* @param view - the view matrix
* @param aspect - the canvas aspect ratio
*/
injectFrameUniforms(matrix, view, aspect) {
this.updateMatrix = matrix;
this.updateInputs = view;
this.updateAspect = aspect;
}
/** Flush the uniforms to the GPU */
flush() {
if (this.updateColorBlindMode !== null)
this.setColorBlindMode(this.updateColorBlindMode);
if (this.updateMatrix !== null)
this.setMatrix(this.updateMatrix);
if (this.updateInputs !== null)
this.setInputs(this.updateInputs);
if (this.updateAspect !== null)
this.setAspect(this.updateAspect);
}
/**
* Setup the tile uniforms
* @param tile - the tile
* @param parent - the parent tile if applicable
*/
setTileUniforms(tile, parent) {
const { gl, uniforms } = this;
const { id, type, bottomTop, faceST, matrix } = parent ?? tile;
if (id === this.curTile)
return;
this.curTile = id;
this.setTilePos(bottomTop);
if (type === 'S2') {
this.setFaceST(faceST);
gl.uniform1i(uniforms.uIsS2, 1);
}
else {
this.setMatrix(matrix);
gl.uniform1i(uniforms.uIsS2, 0);
}
}
/**
* Set the device pixel ratio uniform
* @param ratio - the device pixel ratio
*/
setDevicePixelRatio(ratio) {
const { uniforms } = this;
if (uniforms.uDevicePixelRatio === undefined)
return;
this.gl.uniform1f(uniforms.uDevicePixelRatio, ratio);
}
/**
* Set the colorblind mode uniform
* @param colorMode - the colorblind mode
*/
setColorBlindMode(colorMode) {
const { gl, type, uniforms } = this;
if (uniforms.uCBlind === undefined)
return;
this.gl.uniform1f(uniforms.uCBlind, colorMode);
if (type === 1 && colorMode !== 0) {
// uCVD
if (!('uCVD' in uniforms))
return;
if (colorMode === 1)
gl.uniform1fv(uniforms.uCVD, [0, 2.02344, -2.52581, 0, 1, 0, 0, 0, 1]);
else if (colorMode === 2)
gl.uniform1fv(uniforms.uCVD, [1, 0, 0, 0.494207, 0, 1.24827, 0, 0, 1]);
else if (colorMode === 3)
gl.uniform1fv(uniforms.uCVD, [1, 0, 0, 0, 1, 0, -0.395913, 0.801109, 0]);
}
// flush update pointers
this.updateColorBlindMode = null;
}
/**
* Set the current matrix uniform
* @param matrix - the matrix
*/
setMatrix(matrix) {
const { uniforms } = this;
if (uniforms.uMatrix === undefined)
return;
this.gl.uniformMatrix4fv(uniforms.uMatrix, false, matrix);
// flush update pointers
this.updateMatrix = null;
}
/**
* Setup basic inputs uniform values
* @param inputs - the inputs
*/
setInputs(inputs) {
const { uniforms } = this;
if (uniforms.uInputs === undefined)
return;
this.gl.uniform1fv(uniforms.uInputs, inputs);
this.updateInputs = null; // ensure updateInputs is "flushed"
}
/**
* Set the canvas aspect uniform values
* @param aspect - the aspect
*/
setAspect(aspect) {
const { uniforms } = this;
if (uniforms.uAspect === undefined)
return;
this.gl.uniform2fv(uniforms.uAspect, [aspect.x, aspect.y]);
this.updateAspect = null;
}
/**
* Set the faceST uniform values
* @param faceST - the faceST
*/
setFaceST(faceST) {
const { uniforms } = this;
if (uniforms.uFaceST === undefined)
return;
this.gl.uniform1fv(uniforms.uFaceST, faceST);
}
/**
* Set the tile position uniform
* @param bottomTop - the tile position
*/
setTilePos(bottomTop) {
const { uniforms, gl } = this;
if (uniforms.uBottom === undefined || uniforms.uTop === undefined)
return;
gl.uniform4fv(uniforms.uBottom, bottomTop.subarray(0, 4));
gl.uniform4fv(uniforms.uTop, bottomTop.subarray(4, 8));
}
/**
* Set the layer code uniform data
* @param layerIndex - the layer index
* @param layerCode - the encoded layer data
* @param lch - whether or not the layer is LCH encoded or RGB
*/
setLayerCode(layerIndex, layerCode, lch = false) {
const { uniforms, gl } = this;
if (this.curLayer === layerIndex)
return;
this.curLayer = layerIndex;
if (uniforms.uLayerCode !== undefined && layerCode.length > 0)
gl.uniform1fv(uniforms.uLayerCode, layerCode);
// also set lch if we need to
if (uniforms.uLCH !== undefined && this.LCH !== lch) {
this.LCH = lch;
gl.uniform1i(uniforms.uLCH, ~~lch);
}
}
/**
* Set the interactive mode uniform
* @param interactive - the interactive mode
*/
setInteractive(interactive) {
const { uniforms, gl } = this;
if (uniforms.uInteractive !== undefined && this.interactive !== interactive) {
this.interactive = interactive;
gl.uniform1i(uniforms.uInteractive, ~~interactive);
}
}
/**
* Set the current feature code
* @param featureCode - the feature code
*/
setFeatureCode(featureCode) {
const { uniforms, gl } = this;
if (uniforms.uFeatureCode !== undefined && featureCode.length !== 0) {
gl.uniform1fv(uniforms.uFeatureCode, featureCode);
}
}
/**
* Set the curent draw mode (uniform used by the shader)
* @param mode - the draw mode
*/
setMode(mode) {
const { uniforms, gl } = this;
if (uniforms.uMode !== undefined && this.curMode !== mode) {
// update current value
this.curMode = mode;
// update gpu uniform
gl.uniform1i(uniforms.uMode, mode);
}
}
}