UNPKG

@deck.gl/core

Version:

deck.gl core library

859 lines 35.4 kB
// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import LayerManager from "./layer-manager.js"; import ViewManager from "./view-manager.js"; import MapView from "../views/map-view.js"; import EffectManager from "./effect-manager.js"; import DeckRenderer from "./deck-renderer.js"; import DeckPicker from "./deck-picker.js"; import { WidgetManager } from "./widget-manager.js"; import { TooltipWidget } from "./tooltip-widget.js"; import log from "../utils/log.js"; import { deepEqual } from "../utils/deep-equal.js"; import typedArrayManager from "../utils/typed-array-manager.js"; import { VERSION } from "./init.js"; import { luma } from '@luma.gl/core'; import { webgl2Adapter } from '@luma.gl/webgl'; import { Timeline } from '@luma.gl/engine'; import { AnimationLoop } from '@luma.gl/engine'; import { GL } from '@luma.gl/constants'; import { Stats } from '@probe.gl/stats'; import { EventManager } from 'mjolnir.js'; import assert from "../utils/assert.js"; import { EVENT_HANDLERS, RECOGNIZERS } from "./constants.js"; /* global document */ // eslint-disable-next-line @typescript-eslint/no-empty-function function noop() { } const getCursor = ({ isDragging }) => (isDragging ? 'grabbing' : 'grab'); const defaultProps = { id: '', width: '100%', height: '100%', style: null, viewState: null, initialViewState: null, pickingRadius: 0, layerFilter: null, parameters: {}, parent: null, device: null, deviceProps: {}, gl: null, canvas: null, layers: [], effects: [], views: null, controller: null, // Rely on external controller, e.g. react-map-gl useDevicePixels: true, touchAction: 'none', eventRecognizerOptions: {}, _framebuffer: null, _animate: false, _pickable: true, _typedArrayManagerProps: {}, _customRender: null, widgets: [], onDeviceInitialized: noop, onWebGLInitialized: noop, onResize: noop, onViewStateChange: noop, onInteractionStateChange: noop, onBeforeRender: noop, onAfterRender: noop, onLoad: noop, onError: (error) => log.error(error.message, error.cause)(), onHover: null, onClick: null, onDragStart: null, onDrag: null, onDragEnd: null, _onMetrics: null, getCursor, getTooltip: null, debug: false, drawPickingColors: false }; /* eslint-disable max-statements */ class Deck { constructor(props) { this.width = 0; this.height = 0; // Allows attaching arbitrary data to the instance this.userData = {}; this.device = null; this.canvas = null; this.viewManager = null; this.layerManager = null; this.effectManager = null; this.deckRenderer = null; this.deckPicker = null; this.eventManager = null; this.widgetManager = null; this.tooltip = null; this.animationLoop = null; this.cursorState = { isHovering: false, isDragging: false }; this.stats = new Stats({ id: 'deck.gl' }); this.metrics = { fps: 0, setPropsTime: 0, updateAttributesTime: 0, framesRedrawn: 0, pickTime: 0, pickCount: 0, gpuTime: 0, gpuTimePerFrame: 0, cpuTime: 0, cpuTimePerFrame: 0, bufferMemory: 0, textureMemory: 0, renderbufferMemory: 0, gpuMemory: 0 }; this._metricsCounter = 0; this._needsRedraw = 'Initial render'; this._pickRequest = { mode: 'hover', x: -1, y: -1, radius: 0, event: null }; /** * Pick and store the object under the pointer on `pointerdown`. * This object is reused for subsequent `onClick` and `onDrag*` callbacks. */ this._lastPointerDownInfo = null; // The `pointermove` event may fire multiple times in between two animation frames, // it's a waste of time to run picking without rerender. Instead we save the last pick // request and only do it once on the next animation frame. /** Internal use only: event handler for pointerdown */ this._onPointerMove = (event) => { const { _pickRequest } = this; if (event.type === 'pointerleave') { _pickRequest.x = -1; _pickRequest.y = -1; _pickRequest.radius = 0; } else if (event.leftButton || event.rightButton) { // Do not trigger onHover callbacks if mouse button is down. return; } else { const pos = event.offsetCenter; // Do not trigger callbacks when click/hover position is invalid. Doing so will cause a // assertion error when attempting to unproject the position. if (!pos) { return; } _pickRequest.x = pos.x; _pickRequest.y = pos.y; _pickRequest.radius = this.props.pickingRadius; } if (this.layerManager) { this.layerManager.context.mousePosition = { x: _pickRequest.x, y: _pickRequest.y }; } _pickRequest.event = event; }; /** Internal use only: event handler for click & drag */ this._onEvent = (event) => { const eventHandlerProp = EVENT_HANDLERS[event.type]; const pos = event.offsetCenter; if (!eventHandlerProp || !pos || !this.layerManager) { return; } // Reuse last picked object const layers = this.layerManager.getLayers(); const info = this.deckPicker.getLastPickedObject({ x: pos.x, y: pos.y, layers, viewports: this.getViewports(pos) }, this._lastPointerDownInfo); const { layer } = info; const layerHandler = layer && (layer[eventHandlerProp] || layer.props[eventHandlerProp]); const rootHandler = this.props[eventHandlerProp]; let handled = false; if (layerHandler) { handled = layerHandler.call(layer, info, event); } if (!handled) { rootHandler?.(info, event); this.widgetManager.onEvent(info, event); } }; /** Internal use only: evnet handler for pointerdown */ this._onPointerDown = (event) => { // TODO(ibgreen) Picking not yet supported on WebGPU if (this.device?.type === 'webgpu') { return; } const pos = event.offsetCenter; const pickedInfo = this._pick('pickObject', 'pickObject Time', { x: pos.x, y: pos.y, radius: this.props.pickingRadius }); this._lastPointerDownInfo = pickedInfo.result[0] || pickedInfo.emptyInfo; }; // @ts-ignore views this.props = { ...defaultProps, ...props }; props = this.props; if (props.viewState && props.initialViewState) { log.warn('View state tracking is disabled. Use either `initialViewState` for auto update or `viewState` for manual update.')(); } this.viewState = this.props.initialViewState; // See if we already have a device if (props.device) { this.device = props.device; } let deviceOrPromise = this.device; // Attach a new luma.gl device to a WebGL2 context if supplied if (!deviceOrPromise && props.gl) { if (props.gl instanceof WebGLRenderingContext) { log.error('WebGL1 context not supported.')(); } deviceOrPromise = webgl2Adapter.attach(props.gl, this.props.deviceProps); } // Create a new device if (!deviceOrPromise) { deviceOrPromise = this._createDevice(props); } this.animationLoop = this._createAnimationLoop(deviceOrPromise, props); this.setProps(props); // UNSAFE/experimental prop: only set at initialization to avoid performance hit if (props._typedArrayManagerProps) { typedArrayManager.setOptions(props._typedArrayManagerProps); } this.animationLoop.start(); } /** Stop rendering and dispose all resources */ finalize() { this.animationLoop?.stop(); this.animationLoop?.destroy(); this.animationLoop = null; this._lastPointerDownInfo = null; this.layerManager?.finalize(); this.layerManager = null; this.viewManager?.finalize(); this.viewManager = null; this.effectManager?.finalize(); this.effectManager = null; this.deckRenderer?.finalize(); this.deckRenderer = null; this.deckPicker?.finalize(); this.deckPicker = null; this.eventManager?.destroy(); this.eventManager = null; this.widgetManager?.finalize(); this.widgetManager = null; if (!this.props.canvas && !this.props.device && !this.props.gl && this.canvas) { // remove internally created canvas this.canvas.parentElement?.removeChild(this.canvas); this.canvas = null; } } /** Partially update props */ setProps(props) { this.stats.get('setProps Time').timeStart(); if ('onLayerHover' in props) { log.removed('onLayerHover', 'onHover')(); } if ('onLayerClick' in props) { log.removed('onLayerClick', 'onClick')(); } if (props.initialViewState && // depth = 3 when comparing viewStates: viewId.position.0 !deepEqual(this.props.initialViewState, props.initialViewState, 3)) { // Overwrite internal view state this.viewState = props.initialViewState; } // Merge with existing props Object.assign(this.props, props); // Update CSS size of canvas this._setCanvasSize(this.props); // We need to overwrite CSS style width and height with actual, numeric values const resolvedProps = Object.create(this.props); Object.assign(resolvedProps, { views: this._getViews(), width: this.width, height: this.height, viewState: this._getViewState() }); if (props.device && props.device.id !== this.device?.id) { this.animationLoop?.stop(); if (this.canvas !== props.device.canvasContext?.canvas) { // remove old canvas if new one being used and de-register events // TODO (ck): We might not own this canvas depending it's source, so removing it from the // DOM here might be a bit unexpected but it should be ok for most users. this.canvas?.remove(); this.eventManager?.destroy(); // ensure we will re-attach ourselves after createDevice callbacks this.canvas = null; } log.log(`recreating animation loop for new device! id=${props.device.id}`)(); this.animationLoop = this._createAnimationLoop(props.device, props); this.animationLoop.start(); } // Update the animation loop this.animationLoop?.setProps(resolvedProps); if (props.useDevicePixels !== undefined && this.device?.canvasContext?.canvas instanceof HTMLCanvasElement) { // TODO: It would be much cleaner if CanvasContext had a setProps method this.device.canvasContext.props.useDevicePixels = props.useDevicePixels; const canvas = this.device.canvasContext.canvas; const entry = { target: canvas, contentBoxSize: [{ inlineSize: canvas.clientWidth, blockSize: canvas.clientHeight }], devicePixelContentBoxSize: [ { inlineSize: canvas.clientWidth, blockSize: canvas.clientHeight } ], borderBoxSize: [{ inlineSize: canvas.clientWidth, blockSize: canvas.clientHeight }] }; // Access the protected _handleResize method through the canvas context this.device.canvasContext._handleResize([entry]); } // If initialized, update sub manager props if (this.layerManager) { this.viewManager.setProps(resolvedProps); // Make sure that any new layer gets initialized with the current viewport this.layerManager.activateViewport(this.getViewports()[0]); this.layerManager.setProps(resolvedProps); this.effectManager.setProps(resolvedProps); this.deckRenderer.setProps(resolvedProps); this.deckPicker.setProps(resolvedProps); this.widgetManager.setProps(resolvedProps); } this.stats.get('setProps Time').timeEnd(); } // Public API /** * Check if a redraw is needed * @returns `false` or a string summarizing the redraw reason */ needsRedraw(opts = { clearRedrawFlags: false }) { if (!this.layerManager) { // Not initialized or already finalized return false; } if (this.props._animate) { return 'Deck._animate'; } let redraw = this._needsRedraw; if (opts.clearRedrawFlags) { this._needsRedraw = false; } const viewManagerNeedsRedraw = this.viewManager.needsRedraw(opts); const layerManagerNeedsRedraw = this.layerManager.needsRedraw(opts); const effectManagerNeedsRedraw = this.effectManager.needsRedraw(opts); const deckRendererNeedsRedraw = this.deckRenderer.needsRedraw(opts); redraw = redraw || viewManagerNeedsRedraw || layerManagerNeedsRedraw || effectManagerNeedsRedraw || deckRendererNeedsRedraw; return redraw; } /** * Redraw the GL context * @param reason If not provided, only redraw if deemed necessary. Otherwise redraw regardless of internal states. * @returns */ redraw(reason) { if (!this.layerManager) { // Not yet initialized return; } // Check if we need to redraw let redrawReason = this.needsRedraw({ clearRedrawFlags: true }); // User-supplied should take precedent, however the redraw flags get cleared regardless redrawReason = reason || redrawReason; if (!redrawReason) { return; } this.stats.get('Redraw Count').incrementCount(); if (this.props._customRender) { this.props._customRender(redrawReason); } else { this._drawLayers(redrawReason); } } /** Flag indicating that the Deck instance has initialized its resources and it's safe to call public methods. */ get isInitialized() { return this.viewManager !== null; } /** Get a list of views that are currently rendered */ getViews() { assert(this.viewManager); return this.viewManager.views; } /** Get a list of viewports that are currently rendered. * @param rect If provided, only returns viewports within the given bounding box. */ getViewports(rect) { assert(this.viewManager); return this.viewManager.getViewports(rect); } /** Get the current canvas element. */ getCanvas() { return this.canvas; } /** Query the object rendered on top at a given point */ pickObject(opts) { const infos = this._pick('pickObject', 'pickObject Time', opts).result; return infos.length ? infos[0] : null; } /* Query all rendered objects at a given point */ pickMultipleObjects(opts) { opts.depth = opts.depth || 10; return this._pick('pickObject', 'pickMultipleObjects Time', opts).result; } /* Query all objects rendered on top within a bounding box */ pickObjects(opts) { return this._pick('pickObjects', 'pickObjects Time', opts); } /** Experimental * Add a global resource for sharing among layers */ _addResources(resources, forceUpdate = false) { for (const id in resources) { this.layerManager.resourceManager.add({ resourceId: id, data: resources[id], forceUpdate }); } } /** Experimental * Remove a global resource */ _removeResources(resourceIds) { for (const id of resourceIds) { this.layerManager.resourceManager.remove(id); } } /** Experimental * Register a default effect. Effects will be sorted by order, those with a low order will be rendered first */ _addDefaultEffect(effect) { this.effectManager.addDefaultEffect(effect); } _addDefaultShaderModule(module) { this.layerManager.addDefaultShaderModule(module); } _removeDefaultShaderModule(module) { this.layerManager?.removeDefaultShaderModule(module); } _pick(method, statKey, opts) { assert(this.deckPicker); const { stats } = this; stats.get('Pick Count').incrementCount(); stats.get(statKey).timeStart(); const infos = this.deckPicker[method]({ // layerManager, viewManager and effectManager are always defined if deckPicker is layers: this.layerManager.getLayers(opts), views: this.viewManager.getViews(), viewports: this.getViewports(opts), onViewportActive: this.layerManager.activateViewport, effects: this.effectManager.getEffects(), ...opts }); stats.get(statKey).timeEnd(); return infos; } /** Resolve props.canvas to element */ _createCanvas(props) { let canvas = props.canvas; // TODO EventManager should accept element id if (typeof canvas === 'string') { canvas = document.getElementById(canvas); assert(canvas); } if (!canvas) { canvas = document.createElement('canvas'); canvas.id = props.id || 'deckgl-overlay'; // TODO this is a hack, investigate why these are not set for the picking // tests if (props.width && typeof props.width === 'number') { canvas.width = props.width; } if (props.height && typeof props.height === 'number') { canvas.height = props.height; } const parent = props.parent || document.body; parent.appendChild(canvas); } Object.assign(canvas.style, props.style); return canvas; } /** Updates canvas width and/or height, if provided as props */ _setCanvasSize(props) { if (!this.canvas) { return; } const { width, height } = props; // Set size ONLY if props are being provided, otherwise let canvas be layouted freely if (width || width === 0) { const cssWidth = Number.isFinite(width) ? `${width}px` : width; this.canvas.style.width = cssWidth; } if (height || height === 0) { const cssHeight = Number.isFinite(height) ? `${height}px` : height; // Note: position==='absolute' required for height 100% to work this.canvas.style.position = props.style?.position || 'absolute'; this.canvas.style.height = cssHeight; } } /** If canvas size has changed, reads out the new size and update */ _updateCanvasSize() { const { canvas } = this; if (!canvas) { return; } // Fallback to width/height when clientWidth/clientHeight are undefined (OffscreenCanvas). const newWidth = canvas.clientWidth ?? canvas.width; const newHeight = canvas.clientHeight ?? canvas.height; if (newWidth !== this.width || newHeight !== this.height) { // @ts-expect-error private assign to read-only property this.width = newWidth; // @ts-expect-error private assign to read-only property this.height = newHeight; this.viewManager?.setProps({ width: newWidth, height: newHeight }); // Make sure that any new layer gets initialized with the current viewport this.layerManager?.activateViewport(this.getViewports()[0]); this.props.onResize({ width: newWidth, height: newHeight }); } } _createAnimationLoop(deviceOrPromise, props) { const { // width, // height, gl, // debug, onError // onBeforeRender, // onAfterRender, } = props; return new AnimationLoop({ device: deviceOrPromise, // TODO v9 autoResizeDrawingBuffer: !gl, // do not auto resize external context autoResizeViewport: false, // @ts-expect-error luma.gl needs to accept Promise<void> return value onInitialize: context => this._setDevice(context.device), onRender: this._onRenderFrame.bind(this), // @ts-expect-error typing mismatch: AnimationLoop does not accept onError:null onError // onBeforeRender, // onAfterRender, }); } // Create a device from the deviceProps, assigning required defaults _createDevice(props) { const canvasContextUserProps = this.props.deviceProps?.createCanvasContext; const canvasContextProps = typeof canvasContextUserProps === 'object' ? canvasContextUserProps : undefined; // In deck.gl v9, Deck always bundles and adds a webgl2Adapter. // This behavior is expected to change in deck.gl v10 to support WebGPU only builds. const deviceProps = { adapters: [], ...props.deviceProps }; if (!deviceProps.adapters.includes(webgl2Adapter)) { deviceProps.adapters.push(webgl2Adapter); } const defaultCanvasProps = { // we must use 'premultiplied' canvas for webgpu to enable transparency and match shaders alphaMode: this.props.deviceProps?.type === 'webgpu' ? 'premultiplied' : undefined }; // Create the "best" device supported from the registered adapters return luma.createDevice({ // luma by default throws if a device is already attached // asynchronous device creation could happen after finalize() is called // TODO - createDevice should support AbortController? _reuseDevices: true, // tests can't handle WebGPU devices yet so we force WebGL2 unless overridden type: 'webgl', ...deviceProps, // In deck.gl v10 we may emphasize multi canvas support and unwind this prop wrapping createCanvasContext: { ...defaultCanvasProps, ...canvasContextProps, canvas: this._createCanvas(props), useDevicePixels: this.props.useDevicePixels, autoResize: true } }); } // Get the most relevant view state: props.viewState, if supplied, shadows internal viewState // TODO: For backwards compatibility ensure numeric width and height is added to the viewState _getViewState() { return this.props.viewState || this.viewState; } // Get the view descriptor list _getViews() { const { views } = this.props; const normalizedViews = Array.isArray(views) ? views : // If null, default to a full screen map view port views ? [views] : [new MapView({ id: 'default-view' })]; if (normalizedViews.length && this.props.controller) { // Backward compatibility: support controller prop normalizedViews[0].props.controller = this.props.controller; } return normalizedViews; } _onContextLost() { const { onError } = this.props; if (this.animationLoop && onError) { onError(new Error('WebGL context is lost')); } } /** Actually run picking */ _pickAndCallback() { if (this.device?.type === 'webgpu') { return; } const { _pickRequest } = this; if (_pickRequest.event) { // Perform picking const { result, emptyInfo } = this._pick('pickObject', 'pickObject Time', _pickRequest); this.cursorState.isHovering = result.length > 0; // There are 4 possible scenarios: // result is [outInfo, pickedInfo] (moved from one pickable layer to another) // result is [outInfo] (moved outside of a pickable layer) // result is [pickedInfo] (moved into or over a pickable layer) // result is [] (nothing is or was picked) // // `layer.props.onHover` should be called on all affected layers (out/over) // `deck.props.onHover` should be called with the picked info if any, or empty info otherwise // `deck.props.getTooltip` should be called with the picked info if any, or empty info otherwise // Execute callbacks let pickedInfo = emptyInfo; let handled = false; for (const info of result) { pickedInfo = info; handled = info.layer?.onHover(info, _pickRequest.event) || handled; } if (!handled) { this.props.onHover?.(pickedInfo, _pickRequest.event); this.widgetManager.onHover(pickedInfo, _pickRequest.event); } // Clear pending pickRequest _pickRequest.event = null; } } _updateCursor() { const container = this.props.parent || this.canvas; if (container) { container.style.cursor = this.props.getCursor(this.cursorState); } } _setDevice(device) { this.device = device; if (!this.animationLoop) { // finalize() has been called return; } // if external context... if (!this.canvas) { this.canvas = this.device.canvasContext?.canvas; // external canvas may not be in DOM if (!this.canvas.isConnected && this.props.parent) { this.props.parent.insertBefore(this.canvas, this.props.parent.firstChild); } // TODO v9 // ts-expect-error - Currently luma.gl v9 does not expose these options // All WebGLDevice contexts are instrumented, but it seems the device // should have a method to start state tracking even if not enabled? // instrumentGLContext(this.device.gl, {enable: true, copyState: true}); } if (this.device.type === 'webgl') { this.device.setParametersWebGL({ blend: true, blendFunc: [770, 771, 1, 771], polygonOffsetFill: true, depthTest: true, depthFunc: 515 }); } this.props.onDeviceInitialized(this.device); if (this.device.type === 'webgl') { // Legacy callback - warn? // @ts-expect-error gl is not visible on Device base class this.props.onWebGLInitialized(this.device.gl); } // timeline for transitions const timeline = new Timeline(); timeline.play(); this.animationLoop.attachTimeline(timeline); this.eventManager = new EventManager(this.props.parent || this.canvas, { touchAction: this.props.touchAction, recognizers: Object.keys(RECOGNIZERS).map((eventName) => { // Resolve recognizer settings const [RecognizerConstructor, defaultOptions, recognizeWith, requestFailure] = RECOGNIZERS[eventName]; const optionsOverride = this.props.eventRecognizerOptions?.[eventName]; const options = { ...defaultOptions, ...optionsOverride, event: eventName }; return { recognizer: new RecognizerConstructor(options), recognizeWith, requestFailure }; }), events: { pointerdown: this._onPointerDown, pointermove: this._onPointerMove, pointerleave: this._onPointerMove } }); for (const eventType in EVENT_HANDLERS) { this.eventManager.on(eventType, this._onEvent); } this.viewManager = new ViewManager({ timeline, eventManager: this.eventManager, onViewStateChange: this._onViewStateChange.bind(this), onInteractionStateChange: this._onInteractionStateChange.bind(this), views: this._getViews(), viewState: this._getViewState(), width: this.width, height: this.height }); // viewManager must be initialized before layerManager // layerManager depends on viewport created by viewManager. const viewport = this.viewManager.getViewports()[0]; // Note: avoid React setState due GL animation loop / setState timing issue this.layerManager = new LayerManager(this.device, { deck: this, stats: this.stats, viewport, timeline }); this.effectManager = new EffectManager({ deck: this, device: this.device }); this.deckRenderer = new DeckRenderer(this.device); this.deckPicker = new DeckPicker(this.device); this.widgetManager = new WidgetManager({ deck: this, parentElement: this.canvas?.parentElement }); this.widgetManager.addDefault(new TooltipWidget()); this.setProps(this.props); this._updateCanvasSize(); this.props.onLoad(); } /** Internal only: default render function (redraw all layers and views) */ _drawLayers(redrawReason, renderOptions) { const { device, gl } = this.layerManager.context; this.props.onBeforeRender({ device, gl }); const opts = { target: this.props._framebuffer, layers: this.layerManager.getLayers(), viewports: this.viewManager.getViewports(), onViewportActive: this.layerManager.activateViewport, views: this.viewManager.getViews(), pass: 'screen', effects: this.effectManager.getEffects(), ...renderOptions }; this.deckRenderer?.renderLayers(opts); if (opts.pass === 'screen') { // This method could be called when drawing to picking buffer, texture etc. // Only when drawing to screen, update all widgets (UI components) this.widgetManager.onRedraw({ viewports: opts.viewports, layers: opts.layers }); } this.props.onAfterRender({ device, gl }); } // Callbacks _onRenderFrame() { this._getFrameStats(); // Log perf stats every second if (this._metricsCounter++ % 60 === 0) { this._getMetrics(); this.stats.reset(); log.table(4, this.metrics)(); // Experimental: report metrics if (this.props._onMetrics) { this.props._onMetrics(this.metrics); } } this._updateCanvasSize(); this._updateCursor(); // Update layers if needed (e.g. some async prop has loaded) // Note: This can trigger a redraw this.layerManager.updateLayers(); // Perform picking request if any // TODO(ibgreen): Picking not yet supported on WebGPU if (this.device?.type !== 'webgpu') { this._pickAndCallback(); } // Redraw if necessary this.redraw(); // Update viewport transition if needed // Note: this can trigger `onViewStateChange`, and affect layers // We want to defer these changes to the next frame if (this.viewManager) { this.viewManager.updateViewStates(); } } // Callbacks _onViewStateChange(params) { // Let app know that view state is changing, and give it a chance to change it const viewState = this.props.onViewStateChange(params) || params.viewState; // If initialViewState was set on creation, auto track position if (this.viewState) { this.viewState = { ...this.viewState, [params.viewId]: viewState }; if (!this.props.viewState) { // Apply internal view state if (this.viewManager) { this.viewManager.setProps({ viewState: this.viewState }); } } } } _onInteractionStateChange(interactionState) { this.cursorState.isDragging = interactionState.isDragging || false; this.props.onInteractionStateChange(interactionState); } _getFrameStats() { const { stats } = this; stats.get('frameRate').timeEnd(); stats.get('frameRate').timeStart(); // Get individual stats from luma.gl so reset works const animationLoopStats = this.animationLoop.stats; stats.get('GPU Time').addTime(animationLoopStats.get('GPU Time').lastTiming); stats.get('CPU Time').addTime(animationLoopStats.get('CPU Time').lastTiming); } _getMetrics() { const { metrics, stats } = this; metrics.fps = stats.get('frameRate').getHz(); metrics.setPropsTime = stats.get('setProps Time').time; metrics.updateAttributesTime = stats.get('Update Attributes').time; metrics.framesRedrawn = stats.get('Redraw Count').count; metrics.pickTime = stats.get('pickObject Time').time + stats.get('pickMultipleObjects Time').time + stats.get('pickObjects Time').time; metrics.pickCount = stats.get('Pick Count').count; // Luma stats metrics.gpuTime = stats.get('GPU Time').time; metrics.cpuTime = stats.get('CPU Time').time; metrics.gpuTimePerFrame = stats.get('GPU Time').getAverageTime(); metrics.cpuTimePerFrame = stats.get('CPU Time').getAverageTime(); const memoryStats = luma.stats.get('Memory Usage'); metrics.bufferMemory = memoryStats.get('Buffer Memory').count; metrics.textureMemory = memoryStats.get('Texture Memory').count; metrics.renderbufferMemory = memoryStats.get('Renderbuffer Memory').count; metrics.gpuMemory = memoryStats.get('GPU Memory').count; } } Deck.defaultProps = defaultProps; // This is used to defeat tree shaking of init.js // https://github.com/visgl/deck.gl/issues/3213 Deck.VERSION = VERSION; export default Deck; //# sourceMappingURL=deck.js.map