UNPKG

@deck.gl/extensions

Version:

Plug-and-play functionalities for deck.gl layers

206 lines 8.35 kB
// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { equals } from '@math.gl/core'; import { _deepEqual as deepEqual } from '@deck.gl/core'; import CollisionFilterPass from "./collision-filter-pass.js"; // Factor by which to downscale Collision FBO relative to canvas const DOWNSCALE = 2; export default class CollisionFilterEffect { constructor() { this.id = 'collision-filter-effect'; this.props = null; this.useInPicking = true; this.order = 1; this.channels = {}; this.collisionFBOs = {}; } setup(context) { this.context = context; const { device } = context; this.dummyCollisionMap = device.createTexture({ width: 1, height: 1 }); this.collisionFilterPass = new CollisionFilterPass(device, { id: 'default-collision-filter' }); } preRender({ effects: allEffects, layers, layerFilter, viewports, onViewportActive, views, isPicking, preRenderStats = {} }) { // This can only be called in preRender() after setup() where context is populated const { device } = this.context; if (isPicking) { // Do not update on picking pass return; } const collisionLayers = layers.filter( // @ts-ignore ({ props: { visible, collisionEnabled } }) => visible && collisionEnabled); if (collisionLayers.length === 0) { this.channels = {}; return; } // Detect if mask has rendered. TODO: better dependency system for Effects const effects = allEffects?.filter(e => e.useInPicking && preRenderStats[e.id]); const maskEffectRendered = preRenderStats['mask-effect']?.didRender; // Collect layers to render const channels = this._groupByCollisionGroup(device, collisionLayers); const viewport = viewports[0]; const viewportChanged = !this.lastViewport || !this.lastViewport.equals(viewport) || maskEffectRendered; // Resize framebuffers to match canvas for (const collisionGroup in channels) { const collisionFBO = this.collisionFBOs[collisionGroup]; const renderInfo = channels[collisionGroup]; // @ts-expect-error TODO - assuming WebGL context const [width, height] = device.canvasContext.getPixelSize(); collisionFBO.resize({ width: width / DOWNSCALE, height: height / DOWNSCALE }); this._render(renderInfo, { effects, layerFilter, onViewportActive, views, viewport, viewportChanged }); } // debugFBO(this.collisionFBOs[Object.keys(channels)[0]], {minimap: true}); } _render(renderInfo, { effects, layerFilter, onViewportActive, views, viewport, viewportChanged }) { const { collisionGroup } = renderInfo; const oldRenderInfo = this.channels[collisionGroup]; if (!oldRenderInfo) { return; } const needsRender = viewportChanged || // If render info is new renderInfo === oldRenderInfo || // If sublayers have changed !deepEqual(oldRenderInfo.layers, renderInfo.layers, 1) || // If a sublayer's bounds have been updated renderInfo.layerBounds.some((b, i) => !equals(b, oldRenderInfo.layerBounds[i])) || // If a sublayer's isLoaded state has been updated renderInfo.allLayersLoaded !== oldRenderInfo.allLayersLoaded || // Some prop is in transition renderInfo.layers.some(layer => layer.props.transitions); this.channels[collisionGroup] = renderInfo; if (needsRender) { this.lastViewport = viewport; const collisionFBO = this.collisionFBOs[collisionGroup]; // Rerender collision FBO this.collisionFilterPass.renderCollisionMap(collisionFBO, { pass: 'collision-filter', isPicking: true, layers: renderInfo.layers, effects, layerFilter, viewports: viewport ? [viewport] : [], onViewportActive, views, shaderModuleProps: { collision: { enabled: true, // To avoid feedback loop forming between Framebuffer and active Texture. dummyCollisionMap: this.dummyCollisionMap }, project: { // @ts-expect-error TODO - assuming WebGL context devicePixelRatio: collisionFBO.device.canvasContext.getDevicePixelRatio() / DOWNSCALE } } }); } } /** * Group layers by collisionGroup * Returns a map from collisionGroup to render info */ _groupByCollisionGroup(device, collisionLayers) { const channelMap = {}; for (const layer of collisionLayers) { const collisionGroup = layer.props.collisionGroup; let channelInfo = channelMap[collisionGroup]; if (!channelInfo) { channelInfo = { collisionGroup, layers: [], layerBounds: [], allLayersLoaded: true }; channelMap[collisionGroup] = channelInfo; } channelInfo.layers.push(layer); channelInfo.layerBounds.push(layer.getBounds()); if (!layer.isLoaded) { channelInfo.allLayersLoaded = false; } } // Create any new passes and remove any old ones for (const collisionGroup of Object.keys(channelMap)) { if (!this.collisionFBOs[collisionGroup]) { this.createFBO(device, collisionGroup); } if (!this.channels[collisionGroup]) { this.channels[collisionGroup] = channelMap[collisionGroup]; } } for (const collisionGroup of Object.keys(this.collisionFBOs)) { if (!channelMap[collisionGroup]) { this.destroyFBO(collisionGroup); } } return channelMap; } getShaderModuleProps(layer) { const { collisionGroup, collisionEnabled } = layer .props; const { collisionFBOs, dummyCollisionMap } = this; const collisionFBO = collisionFBOs[collisionGroup]; const enabled = collisionEnabled && Boolean(collisionFBO); return { collision: { enabled, collisionFBO, dummyCollisionMap: dummyCollisionMap } }; } cleanup() { if (this.dummyCollisionMap) { this.dummyCollisionMap.delete(); this.dummyCollisionMap = undefined; } this.channels = {}; for (const collisionGroup of Object.keys(this.collisionFBOs)) { this.destroyFBO(collisionGroup); } this.collisionFBOs = {}; this.lastViewport = undefined; } createFBO(device, collisionGroup) { const { width, height } = device.getDefaultCanvasContext().canvas; const collisionMap = device.createTexture({ format: 'rgba8unorm', width, height, sampler: { minFilter: 'nearest', magFilter: 'nearest', addressModeU: 'clamp-to-edge', addressModeV: 'clamp-to-edge' } }); const depthStencilAttachment = device.createTexture({ format: 'depth16unorm', width, height }); this.collisionFBOs[collisionGroup] = device.createFramebuffer({ id: `collision-${collisionGroup}`, width, height, colorAttachments: [collisionMap], depthStencilAttachment }); } destroyFBO(collisionGroup) { const fbo = this.collisionFBOs[collisionGroup]; fbo.colorAttachments[0]?.destroy(); fbo.depthStencilAttachment?.destroy(); fbo.destroy(); delete this.collisionFBOs[collisionGroup]; } } //# sourceMappingURL=collision-filter-effect.js.map