UNPKG

s2maps-gpu

Version:

S2 Maps GPU - An open source, high-performance, and GPU-accelerated map engine for rendering large-scale, interactive maps.

206 lines (205 loc) 8.37 kB
import FillWorker from './fill.js'; import GlyphWorker from './glyph/index.js'; import { IDGen } from './process.spec.js'; import ImageStore from './imageStore.js'; import LineWorker from './line.js'; import PointWorker from './point.js'; import RasterWorker from './raster.js'; /** * # Process Manager * * A managment class for all input vector/raster work for the Tile Worker thread. * Handles all input data cases and handles shipping the resultant render data back to the main thread */ export default class ProcessManager { id; gpuType; idGen; experimental = false; messagePort; sourceWorker; textDecoder = new TextDecoder(); layers = {}; workers = {}; imageStore = new ImageStore(); mapStyles = {}; /** * Internal function to build the id generator * @param totalWorkers - the total number of tile workers */ _buildIDGen(totalWorkers) { this.idGen = new IDGen(this.id, totalWorkers); } /** * Setup a map style * @param mapID - the id of the map to setup the style for * @param style - the style to setup */ setupStyle(mapID, style) { this.mapStyles[mapID] = style; const { layers, gpuType, experimental } = style; this.gpuType = gpuType; this.experimental = experimental; const workerTypes = new Set(); // first we need to build the workers for (const layer of layers) workerTypes.add(layer.type); this.#buildWorkers(workerTypes, style); // Convert LayerDefinition to WorkerLayer and store in layers const workerLayers = layers .map((layer) => this.setupLayer(layer)) .filter((layer) => layer !== undefined); this.layers[mapID] = workerLayers; // setup imageStore this.imageStore.setupMap(mapID); } /** * Setup a style layer into a "worker layer" that can process input data into renderable data * @param layer - the layer to setup * @returns the worker layer */ setupLayer(layer) { if (layer.type === 'shade') return; return this.workers[layer.type]?.setupLayer(layer); } /** * Build the workers needed for the style * @param names - the names of the workers * @param style - the style that has layers describing the workers it needs */ #buildWorkers(names, style) { const { idGen, gpuType, workers, sourceWorker, imageStore } = this; const { tileSize } = style; // setup imageStore imageStore.setup(idGen, sourceWorker); for (const name of names) { if (name === 'fill') { workers.fill = new FillWorker(idGen, gpuType, imageStore); } else if (name === 'line') { workers.line = new LineWorker(idGen, gpuType); } else if (name === 'point' || name === 'heatmap') { workers.point = workers.heatmap = new PointWorker(idGen, gpuType); } else if (name === 'glyph') { workers.glyph = new GlyphWorker(idGen, gpuType, sourceWorker, imageStore, tileSize); } else if ((name === 'raster' || name === 'sensor' || name === 'hillshade') && this.workers.raster === undefined) { workers.hillshade = workers.sensor = workers.raster = new RasterWorker(gpuType); } } } /** * Process the input vector data * @param mapID - the map that made the request * @param tile - the tile request * @param sourceName - the name of the source the data belongs to * @param vectorTile - the input vector tile to parse */ async processVector(mapID, tile, sourceName, vectorTile) { const { workers } = this; const { zoom, parent } = tile; const { layerIndexes } = parent ?? tile; // filter layers to those that source metadata explains exists in this tile const sourceLayers = this.layers[mapID].filter((layer) => layerIndexes === undefined ? true : layerIndexes.includes(layer.layerIndex)); // prep a layerIndex tracker for an eventual generic flush. // Some layerIndexes will never be updated, so it's good to know const layers = {}; sourceLayers.forEach((l) => { layers[l.layerIndex] = 0; }); // TODO: features is repeated through too many times. Simplify this down. for (const sourceLayer of sourceLayers) { if (!('filter' in sourceLayer)) continue; const { type, filter, minzoom, maxzoom, layerIndex, layer } = sourceLayer; if (minzoom > zoom || maxzoom < zoom) continue; // grab the layer of interest from the vectorTile and it's extent const vectorLayer = vectorTile.layers[layer]; if (vectorLayer === undefined) continue; // iterate over the vector features, filter as necessary for (let f = 0; f < vectorLayer.length; f++) { const feature = vectorLayer.feature?.(f); if (feature === undefined) continue; const { properties } = feature; // filter out features that are not applicable, otherwise tell the vectorWorker to build if (filter(properties)) { const wasBuilt = await workers[type]?.buildFeature(tile, vectorLayer.extent, feature, sourceLayer, mapID, sourceName); if (wasBuilt === true && layers[layerIndex] !== undefined) layers[layerIndex]++; } } } // now flush the workers this.flush(mapID, tile, sourceName, layers); } /** * Flush all data produced from a tile input. * @param mapID - the map that made the request * @param tile - the tile request * @param sourceName - the name of the source the data belongs to * @param layers - the layers that were built */ flush(mapID, tile, sourceName, layers) { const { imageStore } = this; const tileID = tile.id; // first see if any data was missing. If so, we may need to wait for it to be processed const wait = imageStore.processMissingData(mapID, tileID, sourceName); // flush each worker for (const worker of Object.values(this.workers)) { void worker.flush(mapID, tile, sourceName, wait); } const deadLayers = []; for (const [id, count] of Object.entries(layers)) if (count === 0) deadLayers.push(Number(id)); const msg = { type: 'flush', from: 'tile', tileID, mapID, sourceName, deadLayers, }; postMessage(msg); } /** * Process RGBA based data * @param mapID - the map that made the request * @param tile - the tile request * @param sourceName - the name of the source the data belongs to * @param data - the input data * @param size - the size of the input data */ processRaster(mapID, tile, sourceName, data, size) { const subSourceName = sourceName.split(':')[0]; // filter layers to source const sourceLayers = this.layers[mapID].filter((layer) => layer.source === subSourceName); void this.workers.raster?.buildTile(mapID, sourceName, sourceLayers, tile, data, size); } /** * Process glyph/icon/sprite/image metadata * @param mapID - the map that made the request * @param glyphMetadata - the glyph/icon metadatas * @param imageMetadata - the sprite/image metadatas */ processMetadata(mapID, glyphMetadata, imageMetadata) { this.imageStore.processMetadata(mapID, glyphMetadata, imageMetadata); } /** * Process glyph/icon response from the source worker * @param mapID - the map that made the request * @param reqID - the id of the request * @param glyphMetadata - the glyph metadata * @param familyName - the name of the family */ processGlyphResponse(mapID, reqID, glyphMetadata, familyName) { this.imageStore.processGlyphResponse(mapID, reqID, glyphMetadata, familyName); } }