PageSourceSearch

https://deck.gl/assets/js/0112794a.ec67aa21.js

js deck.gl collected 2026-10-01 10:15:28 UTC 46,882 bytes, 2 lines download raw bytes

1"use strict";(self.webpackChunkproject_website=self.webpackChunkproject_website||[]).push([["411"],{48191(e,n,a){a.r(n),a.d(n,{metadata:()=>t,default:()=>h,frontMatter:()=>l,contentTitle:()=>c,toc:()=>p,assets:()=>d});var t=JSON.parse('{"id":"developer-guide/performance","title":"Performance Optimization","description":"General Performance Expectations","source":"@site/../docs/developer-guide/performance.md","sourceDirName":"developer-guide","slug":"/developer-guide/performance","permalink":"/docs/developer-guide/performance","draft":false,"unlisted":false,"editUrl":"https://github.com/visgl/deck.gl/tree/master/website/../docs/developer-guide/performance.md","tags":[],"version":"current","frontMatter":{},"sidebar":"tutorialSidebar","previous":{"title":"Using Effects","permalink":"/docs/developer-guide/using-effects"},"next":{"title":"Tips and Tricks","permalink":"/docs/developer-guide/tips-and-tricks"}}'),r=a(74848),i=a(28453),s=a(50773),o=a(57250);let l={},c="Performance Optimization",d={},p=[{value:"General Performance Expectations",id:"general-performance-expectations",level:2},{value:"Layer Update Performance",id:"layer-update-performance",level:2},{value:"Minimize data changes",id:"minimize-data-changes",level:3},{value:"Avoid unnecessary shallow change in data prop",id:"avoid-unnecessary-shallow-change-in-data-prop",level:4},{value:"Use updateTriggers",id:"use-updatetriggers",level:4},{value:"Handle incremental data loading",id:"handle-incremental-data-loading",level:4},{value:"Favor layer visibility over addition and removal",id:"favor-layer-visibility-over-addition-and-removal",level:4},{value:"Optimize Accessors",id:"optimize-accessors",level:3},{value:"Favor constants over callback functions",id:"favor-constants-over-callback-functions",level:4},{value:"Use trivial functions as accessors",id:"use-trivial-functions-as-accessors",level:4},{value:"Use Binary Data",id:"use-binary-data",level:3},{value:"Supply binary blobs to the data prop",id:"supply-binary-blobs-to-the-data-prop",level:4},{value:"Supply attributes directly",id:"supply-attributes-directly",level:4},{value:"Layer Rendering Performance",id:"layer-rendering-performance",level:2},{value:"Layer Picking Performance",id:"layer-picking-performance",level:2},{value:"Number of Layers",id:"number-of-layers",level:2},{value:"Common Issues",id:"common-issues",level:2}];function u(e){let n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"performance-optimization",children:"Performance Optimization"})}),"\n",(0,r.jsx)(n.h2,{id:"general-performance-expectations",children:"General Performance Expectations"}),"\n",(0,r.jsx)(n.p,{children:"There are mainly two aspects that developers usually consider regarding the\nperformance of any computer programs: the time and the memory consumption, both of which obviously depends on the specs of the hardware deck.gl is ultimately running on."}),"\n",(0,r.jsxs)(n.p,{children:["On 2015 MacBook Pros with dual graphics cards, most basic layers\n(like ",(0,r.jsx)(n.code,{children:"ScatterplotLayer"}),") renders fluidly at 60 FPS during pan and zoom\noperations up to about 1M (one million) data items, with framerates dropping into low double digits (10-20FPS) when the data sets approach 10M items."]}),"\n",(0,r.jsx)(n.p,{children:"Even if interactivity is not an issue, browser limitations on how big chunks of contiguous memory can be allocated (e.g. Chrome caps individual allocations at 1GB) will cause most layers to crash during GPU buffer generation somewhere between 10M and 100M items. You would need to break up your data into chunks and use multiple deck.gl layers to get past this limit."}),"\n",(0,r.jsx)(n.p,{children:"Modern phones (recent iPhones and higher-end Android phones) are surprisingly capable in terms of rendering performance, but are considerably more sensitive to memory pressure than laptops, resulting in browser restarts or page reloads. They also tend to load data significantly slower than desktop computers, so some tuning is usually needed to ensure a good overall user experience on mobile."}),"\n",(0,r.jsx)(n.h2,{id:"layer-update-performance",children:"Layer Update Performance"}),"\n",(0,r.jsxs)(n.p,{children:["Layer update happens when the layer is first created, or when some layer props change. During an update, deck.gl may load necessary resources (e.g. image textures), generate GPU buffers, and upload them to the GPU, all of which may take some time to complete, depending on the number of items in your ",(0,r.jsx)(n.code,{children:"data"})," prop. Therefore, the key to performant deck.gl applications is to minimize layer updates wherever possible."]}),"\n",(0,r.jsx)(n.h3,{id:"minimize-data-changes",children:"Minimize data changes"}),"\n",(0,r.jsxs)(n.p,{children:["When the ",(0,r.jsx)(n.code,{children:"data"})," prop changes, the layer will recalculate all of its GPU buffers. The time required for this is proportional to the number of items in your\n",(0,r.jsx)(n.code,{children:"data"})," prop.\nThis step is the most expensive operation that a layer does - also on CPU - potentially affecting the responsiveness of the application. It may take\nmultiple seconds for multi-million item layers, and if your ",(0,r.jsx)(n.code,{children:"data"}),' prop is updated\nfrequently (e.g. animations), "stutter" can be visible even for layers with just a few thousand items.']}),"\n",(0,r.jsx)(n.p,{children:"Some good places to check for performance improvements are:"}),"\n",(0,r.jsx)(n.h4,{id:"avoid-unnecessary-shallow-change-in-data-prop",children:"Avoid unnecessary shallow change in data prop"}),"\n",(0,r.jsxs)(n.p,{children:["The layer does a shallow comparison between renders to determine if it needs to regenerate buffers. If nothing has changed, make sure you supply the ",(0,r.jsx)(n.em,{children:"same"})," data object every time you render. If the data object has to change shallowly for some reason, consider using the ",(0,r.jsx)(n.code,{children:"dataComparator"})," prop to supply a custom comparison logic."]}),"\n","\n",(0,r.jsxs)(s.A,{groupId:"language",children:[(0,r.jsxs)(o.A,{value:"ts",label:"TypeScript",children:[(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:"import {Deck} from '@deck.gl/core';\nimport {ScatterplotLayer} from '@deck.gl/layers';
1\n\ntype DataType = {\n  position: [x: number, y: number];\n  time: number;\n};\ntype Settings = {\n  radius: number;\n  minTime: number;\n  maxTime: number;\n}\nconst DATA: DataType[] = [...];\n\nconst deckInstance = new Deck({...});\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Bad practice"',children:"function render(settings: Settings) {\n  const layers = [\n    new ScatterplotLayer<DataType>({\n      // `filter` creates a new array every time `render` is called, even if minTime/maxTime have not changed\n      data: DATA.filter(d => d.time >= settings.minTime && d.time <= settings.maxTime),\n      getPosition: (d: DataType) => d.position,\n      getRadius: settings.radius\n    })\n  ];\n\n  deckInstance.setProps({layers});\n}\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Good practice"',children:"let filteredData: DataType[];\nlet lastSettings: Settings;\n\nfunction render(settings: Settings) {\n  if (!lastSettings ||\n      settings.minTime !== lastSettings.minTime ||\n      settings.maxTime !== lastSettings.maxTime) {\n    filteredData = DATA.filter(d => d.time >= settings.minTime && d.time <= settings.maxTime);\n  }\n  lastSettings = settings;\n\n  const layers = [\n    new ScatterplotLayer<DataType>({\n      data: filteredData,\n      getPosition: (d: DataType) => d.position,\n      getRadius: settings.radius\n    })\n  ];\n\n  deckInstance.setProps({layers});\n}\n"})})]}),(0,r.jsxs)(o.A,{value:"react",label:"React",children:[(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",children:"import React from 'react';\nimport {DeckGL} from '@deck.gl/react';\nimport {ScatterplotLayer} from '@deck.gl/layers';\n\ntype DataType = {\n  position: [x: number, y: number];\n  time: number;\n};\ntype Settings = {\n  radius: number;\n  minTime: number;\n  maxTime: number;\n}\nconst DATA: DataType[] = [...];\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",metastring:'title="Bad practice"',children:"function App({settings}: {\n  settings: Settings;\n}) {\n  const layers = [\n    new ScatterplotLayer<DataType>({\n      // `filter` creates a new array every time `render` is called, even if minTime/maxTime have not changed\n      data: DATA.filter(d => d.time >= settings.minTime && d.time <= settings.maxTime),\n      getPosition: (d: DataType) => d.position,\n      getRadius: settings.radius\n    })\n  ];\n\n  return <DeckGL\n    // ...\n    layers={layers}\n  />;\n}\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",metastring:'title="Good practice"',children:"function App({settings}: {\n  settings: Settings;\n}) {\n  const filteredData = React.useMemo(() => {\n    return DATA.filter(d => d.time >= settings.minTime && d.time <= settings.maxTime);\n  }, [settings.minTime, settings.maxTime]);\n\n  const layers = [\n    new ScatterplotLayer<DataType>({\n      data: filteredData,\n      getPosition: (d: DataType) => d.position,\n      getRadius: settings.radius\n    })\n  ];\n\n  return <DeckGL\n    // ...\n    layers={layers}\n  />;\n}\n"})})]})]}),"\n",(0,r.jsx)(n.h4,{id:"use-updatetriggers",children:"Use updateTriggers"}),"\n",(0,r.jsxs)(n.p,{children:["So ",(0,r.jsx)(n.code,{children:"data"})," has indeed changed. Do we have an entirely new collection of objects? Or did just certain fields changed in each row? Remember that changing ",(0,r.jsx)(n.code,{children:"data"})," will update ",(0,r.jsx)(n.em,{children:"all"})," buffers, so if, for example, object positions have not changed, it will be a waste of time to recalculate them."]}),"\n",(0,r.jsxs)(s.A,{groupId:"language",children:[(0,r.jsxs)(o.A,{value:"ts",label:"TypeScript",children:[(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:"import {Deck} from '@deck.gl/core';\nimport {ScatterplotLayer} from '@deck.gl/layers';\n\ntype CensusTract = {\n  centroid: [longitude: number, latitude: number];\n  populationsByYear: {[year: number]: number};\n};\nconst DATA: CensusTract[] = [...];\n\nconst deckInstance = new Deck({...});\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Bad practice"',children:"type CensusTractCurrentYear = {\n  centroid: [longitude: number, latitude: number];\n  population: number;\n};\n\nfunction render(year: number) {\n  const layers = [\n    new ScatterplotLayer<CensusTractCurrentYear>({\n      // `data` changes every time year changed, but positions don't need to update\n      data: DATA.map(d => ({\n        centroid: d.centroid,\n        population: d.populationsByYear[year]\n      })),\n      getPosition: (d: CensusTractCurrentYear) => d.centroid,\n      getRadius: (d: CensusTractCurrentYear) => Math.sqrt(d.population)\n    })\n  ];\n\n  deckInstance.setProps({layers});\n}\n"})}),(0,r.jsxs)(n.p,{children:["In this case, it is more efficient to use ",(0,r.jsx)(n.code,{children:"updateTriggers"}
1)," to invalidate only the selected attributes:"]}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Good practice"',children:"function render(year: number) {\n  const layers = [\n    new ScatterplotLayer<CensusTract>({\n      // `data` never changes\n      data: DATA,\n      getPosition: (d: CensusTract) => d.centroid,\n      // radius depends on `year`\n      getRadius: (d: CensusTract) => Math.sqrt(d.populationsByYear[year]),\n      updateTriggers: {\n        // This tells deck.gl to recalculate radius when `year` changes\n        getRadius: year\n      }\n    })\n  ];\n\n  deckInstance.setProps({layers});\n}\n"})})]}),(0,r.jsxs)(o.A,{value:"react",label:"React",children:[(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",children:"import React from 'react';\nimport {DeckGL} from '@deck.gl/react';\nimport {ScatterplotLayer} from '@deck.gl/layers';\n\ntype CensusTract = {\n  centroid: [longitude: number, latitude: number];\n  populationsByYear: {[year: number]: number};\n};\nconst DATA: CensusTract[] = [...];\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",metastring:'title="Bad practice"',children:"type CensusTractCurrentYear = {\n  centroid: [longitude: number, latitude: number];\n  population: number;\n};\n\nfunction App({year}: {\n  year: number;\n}) {\n  // `data` changes every time year changed, but positions don't need to update\n  const data: CensusTractCurrentYear[] = React.useMemo(() => {\n    return DATA.map(d => ({\n      centroid: d.centroid,\n      population: d.populationsByYear[year]\n    }));\n  }, [year]);\n\n  const layers = [\n    new ScatterplotLayer<CensusTractCurrentYear>({\n      data,\n      getPosition: (d: CensusTractCurrentYear) => d.centroid,\n      getRadius: (d: CensusTractCurrentYear) => Math.sqrt(d.population)\n    })\n  ];\n\n  return <DeckGL\n    // ...\n    layers={layers}\n  />;\n}\n"})}),(0,r.jsxs)(n.p,{children:["In this case, it is more efficient to use ",(0,r.jsx)(n.code,{children:"updateTriggers"})," to invalidate only the selected attributes:"]}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",metastring:'title="Good practice"',children:"function App({year}: {\n  year: number;\n}) {\n  const layers = [\n    new ScatterplotLayer<CensusTract>({\n      // `data` never changes\n      data: DATA,\n      getPosition: (d: CensusTract) => d.centroid,\n      // radius depends on `year`\n      getRadius: (d: CensusTract) => Math.sqrt(d.populationsByYear[year]),\n      updateTriggers: {\n        // This tells deck.gl to recalculate radius when `year` changes\n        getRadius: year\n      }\n    })\n  ];\n\n  return <DeckGL\n    // ...\n    layers={layers}\n  />;\n}\n"})})]})]}),"\n",(0,r.jsx)(n.h4,{id:"handle-incremental-data-loading",children:"Handle incremental data loading"}),"\n",(0,r.jsx)(n.p,{children:"A common technique for handling big datasets on the client side is to load data in chunks. We want to update the visualization whenever a new chunk comes in. If we append the new chunk to an existing data array, deck.gl will recalculate the whole buffers, even for the previously loaded chunks where nothing have changed:"}),"\n",(0,r.jsxs)(s.A,{groupId:"language",children:[(0,r.jsxs)(o.A,{value:"ts",label:"TypeScript",children:[(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:"import {Deck} from '@deck.gl/core';\nimport {ScatterplotLayer} from '@deck.gl/layers';\n\ntype DataType = {\n  position: [longitude: number, latitude: number];\n};\n\nconst deckInstance = new Deck({...});\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Bad practice"',children:"let loadedData: DataType[] = [];\nlet chunk: DataType[];\nwhile (chunk = await fetchNextChunk()) {\n  loadedData = loadedData.concat(chunk);\n  render();\n}\n\nfunction render() {\n  const layers = [\n    new ScatterplotLayer<DataType>({\n      id: 'points',\n      // If we have 1 million rows loaded and 100,000 new rows arrive,\n      // we end up recalculating the buffers for all 1,100,000 rows\n      data: loadedData,\n      getPosition: (d: DataType) => d.position\n    })\n  ];\n\n  deckInstance.setProps({layers});\n}\n"})}),(0,r.jsx)(n.p,{children:"To avoid doing this, we instead generate one layer for each chunk:"}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Good practice"',children:"let dataChunks: DataType[][] = [];\nlet chunk: DataType[];\nwhile (chunk = await fetchNextChunk()) {\n  dataChunks.push(chunk);\n  render();\n}\n\nfunction render() {\n  const layers = dataChunks.map((chunk: DataType[], chunkIndex: number) =>\n    new ScatterplotLayer<DataType>({\n      // Important: each layer must have a consistent & unique id\n      id: `points-${chunkIndex}`,\n      // If we have 10 100,000-row chunks already loaded and a new one arrive,\n      // the first 10 layers will see no prop change\n      // only the 11th layer's buffers need to be generated\n      data: chunk,\n      getPosition: (d: DataType) => d.position\n    });\n\n  deckInstance.setProps({layers});\n}\n"})}),(0,r.jsx)(n.p,{children:"Starting v7.2.0, support for async iterables is added to efficie
1ntly update layers with incrementally loaded data:"}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Good alternative"',children:"// Create an async iterable\nasync function* getData() {\n  let chunk: DataType[];\n  while (chunk = await fetchNextChunk()) {\n    yield chunk;\n  }\n}\n\nfunction render() {\n  const layers = [\n    new ScatterplotLayer<DataType>({\n      id: 'points',\n      // When a new chunk arrives, deck.gl only updates the sub buffers for the new rows\n      data: getData(),\n      getPosition: (d: DataType) => d.position\n    })\n  ];\n\n  deckInstance.setProps({layers});\n}\n"})})]}),(0,r.jsxs)(o.A,{value:"react",label:"React",children:[(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",children:"import React, {useState, useEffect} from 'react';\nimport {DeckGL} from '@deck.gl/react';\nimport {ScatterplotLayer} from '@deck.gl/layers';\n\ntype DataType = {\n  position: [longitude: number, latitude: number];\n};\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",metastring:'title="Bad practice"',children:"function App() {\n  const [loadedData, setLoadedData] = useState<DataType[]>([]);\n\n  useEffect(() => {\n    (async () => {\n      let chunk: DataType[];\n      while (chunk = await fetchNextChunk()) {\n        setLoadedData(current => current.concat(chunk));\n      }\n    })();\n  }, []);\n  \n  const layers = [\n    new ScatterplotLayer<DataType>({\n      id: 'points',\n      // If we have 1 million rows loaded and 100,000 new rows arrive,\n      // we end up recalculating the buffers for all 1,100,000 rows\n      data: loadedData,\n      getPosition: (d: DataType) => d.position\n    })\n  ];\n\n  return <DeckGL\n    // ...\n    layers={layers}\n  />;\n}\n"})}),(0,r.jsx)(n.p,{children:"To avoid doing this, we instead generate one layer for each chunk:"}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",metastring:'title="Good practice"',children:"function App() {\n  const [dataChunks, setDataChunks] = useState<DataType[][]>([]);\n\n  useEffect(() => {\n    (async () => {\n      let chunk: DataType[];\n      while (chunk = await fetchNextChunk()) {\n        setDataChunks(current => current.concat([chunk]));\n      }\n    })();\n  }, []);\n  \n  const layers = dataChunks.map((chunk: DataType[], chunkIndex: number) =>\n    new ScatterplotLayer<DataType>({\n      // Important: each layer must have a consistent & unique id\n      id: `points-${chunkIndex}`,\n      // If we have 10 100,000-row chunks already loaded and a new one arrive,\n      // the first 10 layers will see no prop change\n      // only the 11th layer's buffers need to be generated\n      data: chunk,\n      getPosition: (d: DataType) => d.position\n    });\n\n  return <DeckGL\n    // ...\n    layers={layers}\n  />;\n}\n"})}),(0,r.jsx)(n.p,{children:"Starting v7.2.0, support for async iterables is added to efficiently update layers with incrementally loaded data:"}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",metastring:'title="Good alternative"',children:"// Create an async iterable\nasync function* getData() {\n  let chunk: DataType[];\n  while (chunk = await fetchNextChunk()) {\n    yield chunk;\n  }\n}\n\nfunction App() {  \n  const layers = [\n    new ScatterplotLayer<DataType>({\n      id: 'points';\n      // When a new chunk arrives, deck.gl only updates the sub buffers for the new rows\n      data: getData(),\n      getPosition: (d: DataType) => d.position\n    })\n  ];\n\n  return <DeckGL\n    // ...\n    layers={layers}\n  />;\n}\n"})})]})]}),"\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsx)(n.a,{href:"/docs/api-reference/core/layer#data",children:"Layer properties"})," for details."]}),"\n",(0,r.jsx)(n.h4,{id:"favor-layer-visibility-over-addition-and-removal",children:"Favor layer visibility over addition and removal"}),"\n",(0,r.jsx)(n.p,{children:"Removing a layer will lose all of its internal states, including generated buffers. If the layer is added back later, all the GPU resources need to be regenerated again. In the use cases where layers need to be toggled frequently (e.g. via a control panel), there might be a significant perf penalty:"}),"\n",(0,r.jsxs)(s.A,{groupId:"language",children:[(0,r.jsxs)(o.A,{value:"ts",label:"TypeScript",children:[(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:"import {Deck} from '@deck.gl/core';\nimport {ScatterplotLayer, TextLayer} from '@deck.gl/layers';
1\n\nconst deckInstance = new Deck({...});\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Bad practice"',children:"function render(layerVisibility: {\n  circles: boolean;\n  labels: boolean;\n}) {\n  const layers = [\n    // when visibility goes from on to off to on, this layer will be completely removed and then regenerated\n    layerVisibility.circles && new ScatterplotLayer({\n      id: 'circles'\n      // ...\n    }),\n    layerVisibility.labels && new TextLayer({\n      id: 'labels',\n      //...\n    })\n  ];\n\n  deckInstance.setProps({layers});\n}\n"})}),(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.a,{href:"/docs/api-reference/core/layer#visible",children:(0,r.jsx)(n.code,{children:"visible"})})," prop is a cheap way to temporarily hide a layer:"]}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Good practice"',children:"function render(layerVisibility: {\n  circles: boolean;\n  labels: boolean;\n}) {\n  const layers = [\n    // when visibility is off, this layer's internal states will be retained in memory, making turning it back on instant\n    new ScatterplotLayer({\n      id: 'circles',\n      visible: layerVisibility.circles,\n      // ...\n    }),\n    new TextLayer({\n      id: 'labels',\n      visible: layerVisibility.labels,\n      // ...\n    })\n  ];\n\n  deckInstance.setProps({layers});\n}\n"})})]}),(0,r.jsxs)(o.A,{value:"react",label:"React",children:[(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",children:"import React from 'react';\nimport {DeckGL} from '@deck.gl/react';\nimport {ScatterplotLayer, TextLayer} from '@deck.gl/layers';\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",metastring:'title="Bad practice"',children:"function App({layerVisibility}: {\n  layerVisibility: {\n    circles: boolean;\n    labels: boolean;\n  }\n}) {\n  const layers = [\n    // when visibility goes from on to off to on, this layer will be completely removed and then regenerated\n    layerVisibility.circles && new ScatterplotLayer({\n      id: 'circles'\n      // ...\n    }),\n    layerVisibility.labels && new TextLayer({\n      id: 'labels',\n      //...\n    })\n  ];\n\n  return <DeckGL\n    // ...\n    layers={layers}\n  />;\n}\n"})}),(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.a,{href:"/docs/api-reference/core/layer#visible",children:(0,r.jsx)(n.code,{children:"visible"})})," prop is a cheap way to temporarily hide a layer:"]}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",metastring:'title="Good practice"',children:"function App({layerVisibility}: {\n  layerVisibility: {\n    circles: boolean;\n    labels: boolean;\n  }\n}) {\n  const layers = [\n    // when visibility is off, this layer's internal states will be retained in memory, making turning it back on instant\n    new ScatterplotLayer({\n      id: 'circles',\n      visible: layerVisibility.circles,\n      // ...\n    }),\n    new TextLayer({\n      id: 'labels',\n      visible: layerVisibility.labels,\n      // ...\n    })\n  ];\n\n  return <DeckGL\n    // ...\n    layers={layers}\n  />;\n}\n"})})]})]}),"\n",(0,r.jsx)(n.h3,{id:"optimize-accessors",children:"Optimize Accessors"}),"\n",(0,r.jsx)(n.p,{children:"99% of the CPU time that deck.gl spends in updating buffers is calling the accessors you supply to the layer. Since they are called on every data object, any performance issue in the accessors is amplified by the size of your data."}),"\n",(0,r.jsx)(n.h4,{id:"favor-constants-over-callback-functions",children:"Favor constants over callback functions"}),"\n",(0,r.jsxs)(n.p,{children:["Most accessors accept constant values as well as functions. Constant props are extremely cheap to update in comparison. Use ",(0,r.jsx)(n.code,{children:"ScatterplotLayer"})," as an example, the following two prop settings yield exactly the same visual outcome:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"getFillColor: [255, 0, 0, 128]"})," - deck.gl uploads 4 numbers to the GPU."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"getFillColor: d =>
1 [255, 0, 0, 128]"})," - deck.gl first builds a typed array of ",(0,r.jsx)(n.code,{children:"4 * data.length"})," elements, call the accessor ",(0,r.jsx)(n.code,{children:"data.length"})," times to fill it, then upload it to the GPU."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Aside from accessors, most layers also offer one or more ",(0,r.jsx)(n.code,{children:"*Scale"})," props that are uniform multipliers on top of the per-object value. Always consider using them before invoking the accessors:"]}),"\n",(0,r.jsxs)(s.A,{groupId:"language",children:[(0,r.jsxs)(o.A,{value:"ts",label:"TypeScript",children:[(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:"import {Deck} from '@deck.gl/core';\nimport {ScatterplotLayer} from '@deck.gl/layers';\nimport {animate} from 'popmotion';\n\ntype DataType = {\n  position: [x: number, y: number];\n  size: number;\n};\nconst data: DataType[] = [...];\nconst radiusAnimation = animate({\n  from: 10, // start radius\n  to: 50, // end radius\n  duration: 1000,\n  repeat: Infinity,\n  repeatType: 'mirror', // alternate to and fro\n  onUpdate: updateLayers\n});\n\nconst deckInstance = new Deck({...});\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Bad practice"',children:"function updateLayers(radius: number) {\n  const layers = [\n    new ScatterplotLayer<DataType>({\n      data,\n      getPosition: (d: DataType) => d.position,\n      // deck.gl will call `getRadius` for ALL data objects every animation frame, which will likely choke the app\n      getRadius: (d: DataType) => d.size * radius,\n      updateTriggers: {\n        getRadius: radius\n      }\n    })\n  ];\n\n  deckInstance.setProps({layers});\n}\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Good practice"',children:"function updateLayers(radius: number) {\n  const layers = [\n    new ScatterplotLayer<DataType>({\n      data,\n      getPosition: (d: DataType) => d.position,\n      getRadius: (d: DataType) => d.size,\n      // This has virtually no cost to update, easily getting 60fps animation\n      radiusScale: radius\n    })\n  ];\n\n  deckInstance.setProps({layers});\n}\n"})})]}),(0,r.jsxs)(o.A,{value:"react",label:"React",children:[(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",children:"import React, {useState, useEffect} from 'react';\nimport {DeckGL} from '@deck.gl/react';\nimport {ScatterplotLayer} from '@deck.gl/layers';\nimport {animate} from 'popmotion';\n\ntype DataType = {\n  position: [x: number, y: number];\n  size: number;\n};\nconst data: DataType[] = [...];\n\nfunction App() {\n  const [radius, setRadius] = useState<number>(0);\n\n  useEffect(() => {\n    const radiusAnimation = animate({\n      from: 10, // start radius\n      to: 50, // end radius\n      duration: 1000,\n      repeat: Infinity,\n      repeatType: 'mirror', // alternate to and fro\n      onUpdate: setRadius\n    });\n\n    return () => radiusAnimation.stop();\n  }, []);\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",metastring:'title="Bad practice"',children:"  const layers = [\n    new ScatterplotLayer<DataType>({\n      data,\n      getPosition: (d: DataType) => d.position,\n      // deck.gl will call `getRadius` for ALL data objects every animation frame, which will likely choke the app\n      getRadius: (d: DataType) => d.size * radius,\n      updateTriggers: {\n        getRadius: radius\n      }\n    })\n  ];\n\n  return <DeckGL\n    // ...\n    layers={layers}\n  />;\n}\n"})}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-tsx",metastring:'title="Good practice"',children:"  const layers = [\n    new ScatterplotLayer<DataType>({\n      data,\n      getPosition: (d: DataType) => d.position,\n      getRadius: (d: DataType) => d.size,\n      // This has virtually no cost to update, easily getting 60fps animation\n      radiusScale: radius\n    })\n  ];\n\n  return <DeckGL\n    // ...\n    layers={layers}\n  />;\n}\n"})})]})]}),"\n",(0,r.jsx)(n.h4,{id:"use-trivial-functions-as-accessors",children:"Use trivial functions as accessors"}),"\n",(0,r.jsx)(n.p,{children:"Whenever possible, make the accessors trivial functions and utilize pre-defined and/or pre-computed data."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:"type CensusTract = {\n  centroid: [longitude: number, latitude: number];\n  populationsByYear: {[year: number]: number};\n};\n\nconst DATA: CensusTract[] = [...];\n"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Bad practice"',children:"const layer = new ScatterplotLayer<CensusTract>({\n  data: DATA,\n  getPosition: (d: CensusTract) => d.centroid,\n  getFillColor: (d: CensusTract) => {\n    // This line creates a new values array from each object\n    // which can incur significant cost in garbage collection\n    const maxPopulation = Math.max.apply(null, Object.values(d.populationsByYear));\n    // This switch case creates a new color array for each object\n    // which can also incur significant cost in garbage collection\n    if (maxPopulation > 1000000) {\n      return [255, 0, 0];\n    } else if (maxPopulation > 100000) {\n      return [0, 255, 0];\n    } else {\n      return [0, 0, 255];\n    }\n  },\n  getRadius: (d: CensusTract) => {\n    // This line duplicates what's done in `getFillColor` and doubles the cost\n    const maxPopulation = Math.max.apply(null, Object.values(d.populationsByYear));\n    return Math.sqrt(maxPopulation);\n  }\n});\n"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Good practice"',children:"import {Color} from '@deck.gl/core';\n\n// Calculate max population once and cache it\nconst maxPopulations: number[] = Data.map((d: CensusTract) => {\n  // Use a for loop to avoid creating new objects\n  let maxPopulation = 0;\n  for (const year in populationsByYear) {\n    const population = populationsByYear[year];\n    if (population > maxPopulation) {\n      maxPopulation = population;\n    }\n  }\n  return maxPopulation;\n});\n\n// Use constant color values to avoid generating new arrays\nconst COLORS: {[n
1ame: string]: Color} = {\n  ONE_MILLION: [255, 0, 0],\n  HUNDRED_THOUSAND: [0, 255, 0],\n  OTHER: [0, 0, 255]\n};\n\nconst layer = new ScatterplotLayer<CensusTract>({\n  data: DATA,\n  getPosition: (d: CensusTract) => d.centroid,\n  getFillColor: (d: CensusTract, {index}) => {\n    const maxPopulation = maxPopulations[index];\n    if (maxPopulation > 1000000) {\n      return COLORS.ONE_MILLION;\n    } else if (maxPopulation > 100000) {\n      return COLORS.HUNDRED_THOUSAND;\n    } else {\n      return COLORS.OTHER;\n    }\n  },\n  getRadius: (d: CensusTract, {index}) => Math.sqrt(maxPopulations[index]),\n});\n"})}),"\n",(0,r.jsx)(n.h3,{id:"use-binary-data",children:"Use Binary Data"}),"\n",(0,r.jsx)(n.p,{children:"When creating data-intensive applications, it is often desirable to offload client-side data processing to the server or web workers."}),"\n",(0,r.jsxs)(n.p,{children:["The server can send data to the client more efficiently using binary formats, e.g. ",(0,r.jsx)(n.a,{href:"https://developers.google.com/protocol-buffers",children:"protobuf"}),", ",(0,r.jsx)(n.a,{href:"https://arrow.apache.org/",children:"Arrow"})," or simply a custom binary blob."]}),"\n",(0,r.jsxs)(n.p,{children:["Some deck.gl applications use web workers to load data and generate attributes to get the processing off the main thread. Modern worker implementations allow ownership of typed arrays to be ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage#Parameters",children:"transferred directly"})," between threads at virtually no cost, bypassing serialization and deserialization of JSON objects."]}),"\n",(0,r.jsx)(n.h4,{id:"supply-binary-blobs-to-the-data-prop",children:"Supply binary blobs to the data prop"}),"\n",(0,r.jsx)(n.p,{children:"Assume we have the data source encoded in the following format:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:"// lon1, lat1, radius1, red1, green1, blue1, lon2, lat2, ...\nconst binaryData = new Float32Array([-122.4, 37.78, 1000, 255, 200, 0, -122.41, 37.775, 500, 200, 0, 0, -122.39, 37.8, 500, 0, 40, 200]);\n"})}),"\n",(0,r.jsx)(n.p,{children:"Upon receiving the typed arrays, the application can of course re-construct a classic JavaScript array:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Bad practice"',children:"type DataType = {\n  position: [lon: number, lat: number];\n  radius: number;\n  color: [r: number, g: numer, b: number];\n}\nconst data: DataType[] = [];\nfor (let i = 0; i < binaryData.length; i += 6) {\n  data.push({\n    position: [binaryData[i], binaryData[i + 1]],\n    radius: binaryData[i + 2],\n    color: [binaryData[i + 3], binaryData[i + 4], binaryData[i + 5]]\n  });\n}\n\nconst data = new ScatterplotLayer<DataType>({\n  data,\n  getPosition: (d: DataType) => d.position,\n  getRadius: (d: DataType) => d.radius,\n  getFillColor: (d: DataType) => d.color\n});\n"})}),"\n",(0,r.jsx)(n.p,{children:"However, in addition to requiring custom repacking code, this array will take valuable CPU time to create, and significantly more memory to store than its binary form. In performance-sensitive applications that constantly push a large volume of data (e.g. animations), this method will not be efficient enough."}),"\n",(0,r.jsxs)(n.p,{children:["Alternatively, one may supply a non-iterable object (not Array or TypedArray) to the ",(0,r.jsx)(n.code,{children:"data"})," object. In this case, it must contain a ",(0,r.jsx)(n.code,{children:"length"})," field that specifies the total number of objects. Since ",(0,r.jsx)(n.code,{children:"data"})," is not iterable, each accessor will not receive a valid ",(0,r.jsx)(n.code,{children:"object"})," argument, and therefore responsible of interpreting the input data's buffer layout:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Good practice"',children:"// Note: binaryData.length does not equal the number of items,\n// which is why we need to wrap it in an object that contains a custom `length` field\nconst DATA = {src: binaryData, length: binaryData.length / 6}\n\nconst layer = new ScatterplotLayer({\n  data: DATA,\n  getPosition: (_, {index, data}) => {\n    return data.src.subarray(index * 6, index * 6 + 2);\n  },\n  getRadius: (_, {index, data}) => {\n    return data.src[index * 6 + 2];\n  },\n  getFillColor: (_, {index, data, target}) =>
1 {\n    return data.src.subarray(index * 6 + 3, index * 6 + 6);\n  }\n})\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Optionally, the accessors can utilize the pre-allocated ",(0,r.jsx)(n.code,{children:"target"})," array in the second argument to further avoid creating new objects:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Good alternative"',children:"const DATA = {src: binaryData, length: binaryData.length / 6}\n\nconst layer = new ScatterplotLayer({\n  data: DATA,\n  getPosition: (_, {index, data, target}) => {\n    target[0] = data.src[index * 6];\n    target[1] = data.src[index * 6 + 1];\n    target[2] = 0;\n    return target;\n  },\n  getRadius: (_, {index, data}) => {\n    return data.src[index * 6 + 2];\n  },\n  getFillColor: (_, {index, data, target}) => {\n    target[0] = data.src[index * 6 + 3];\n    target[1] = data.src[index * 6 + 4];\n    target[2] = data.src[index * 6 + 5];\n    target[3] = 255;\n    return target;\n  }\n})\n"})}),"\n",(0,r.jsx)(n.h4,{id:"supply-attributes-directly",children:"Supply attributes directly"}),"\n",(0,r.jsxs)(n.p,{children:["While the built-in attribute generation functionality is a major part of a ",(0,r.jsx)(n.code,{children:"Layer"}),"s functionality, it can become a major bottleneck in performance since it is done on CPU in the main thread. If the application needs to push many data changes frequently, for example to render animations, data updates can block rendering and user interaction. In this case, the application should consider precalculated attributes on the back end or in web workers."]}),"\n",(0,r.jsx)(n.p,{children:"Deck.gl layers accepts external attributes as either a typed array or a luma.gl Buffers. Such attributes, if prepared carefully, can be directly utilized by the GPU, thus bypassing the CPU-bound attribute generation completely."}),"\n",(0,r.jsx)(n.p,{children:"This technique offers the maximum performance possible in terms of data throughput, and is commonly used in heavy-duty, performance-sensitive applications."}),"\n",(0,r.jsxs)(n.p,{children:["To generate an attribute buffer for a layer, take the results returned from each object by the ",(0,r.jsx)(n.code,{children:"get*"})," accessors and flatten them into a typed array. For example, consider the following layers:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:"type DataType = {\n  position: [x: number, y: number, z: number];\n  color: [r: number, g: number, b: number];\n};\nconst POINT_CLOUD_DATA: DataType[] = [...];\n\n// Calculate attributes on the main thread\nconst layer = new PointCloudLayer<DataType>({\n  data: POINT_CLOUD_DATA,\n  getPosition: (d: DataType) => d.position,\n  getColor: (d: DataType) => d.color,\n  getNormal: [0, 0, 1]\n})\n"})}),"\n",(0,r.jsx)(n.p,{children:"Should we move the attribute generation to a web worker:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Worker"',children:"// positions can be sent as either float32 or float64, depending on precision requirements\n// point[0].x, point[0].y, point[0].z, point[1].x, point[1].y, point[1].z, ...\nconst positions = new Float64Array(POINT_CLOUD_DATA.flatMap((d: DataType) => d.position));\n// point[0].r, point[0].g, point[0].b, point[1].r, point[1].g, point[1].b, ...\nconst colors = new Uint8Array(POINT_CLOUD_DATA.flatMap((d: DataType) => d.color));\n\n// send back to main thread\npostMessage({pointCount: POINT_CLOUD_DATA.length, positions, colors}, [positions.buffer, colors.buffer]);\n"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Main thread"',children:"// `data` is received from the worker\nconst layer = new PointCloudLayer({\n  data: {\n    // this is required so that the layer knows how many points to draw\n    length: data.pointCount,\n    attributes: {\n      getPosition: {value: data.positions, size: 3},\n      getColor: {value: data.colors, size: 3},\n    }\n  },\n  // constant accessor works without raw data\n  getNormal: [0, 0, 1]\n});\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Note that instead of ",(0,r.jsx)(n.code,{children:"getPosition"}),", we supply a ",(0,r.jsx)(n.code,{children:"data.attributes.getPosition"})," object. This object defines the buffer from which ",(0,r.jsx)(n.code,{children:"PointCloudLayer"})," should access its positions data. See the base ",(0,r.jsx)(n.code,{children:"Layer"})," class' ",(0,r.jsx)(n.a,{href:"/docs/api-reference/core/layer#basic-properties",children:"data prop"})," for details."]}),"\n",(0,r.jsx)(n.p,{children:"It is also possible to use interleaved or custom layout external buffers:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Worker"',children:"// x0,y0,z0, r0,g0,b0, x1,y1,z1, r1,g1,b1, ...\nconst positionsAndColors = new Float32Array(POINT_CLOUD_DATA.flatMap(d => [\n  d.position[0],\n  d.position[1],\n  d.position[2],\n  // colors must be normalized if sent as floats\n  d.color[0] / 25
15,\n  d.color[1] / 255,\n  d.color[2] / 255\n]));\n\n// send back to main thread\npostMessage({pointCount: POINT_CLOUD_DATA.length, positionsAndColors}, [positionsAndColors.buffer]);\n"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",metastring:'title="Main thread"',children:"const buffer = deckInstance.device.createBuffer({data: data.positionsAndColors});\n\nconst layer = new PointCloudLayer({\n  data: {\n    length : data.pointCount,\n    attributes: {\n      getPosition: {buffer, size: 3, offset: 0, stride: 24},\n      getColor: {buffer, size: 3, offset: 12, stride: 24},\n    }\n  },\n  // constant accessor works without raw data\n  getNormal: [0, 0, 1]\n});\n"})}),"\n",(0,r.jsxs)(n.p,{children:["See full example in ",(0,r.jsx)(n.a,{href:"https://github.com/visgl/deck.gl/tree/9.4-release/examples/experimental/interleaved-buffer",children:"examples/experimental/interleaved-buffer"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Note that external attributes only work with primitive layers, not composite layers, because composite layers often need to preprocess the data before passing it to the sub layers. Some layers that deal with variable-width data, such as ",(0,r.jsx)(n.code,{children:"PathLayer"}),", ",(0,r.jsx)(n.code,{children:"SolidPolygonLayer"}),", require additional information passed along with ",(0,r.jsx)(n.code,{children:"data.attributes"}),". Consult each layer's documentation before use."]}),"\n",(0,r.jsx)(n.h2,{id:"layer-rendering-performance",children:"Layer Rendering Performance"}),"\n",(0,r.jsx)(n.p,{children:"Layer rendering time (for large data sets) is essentially proportional to:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["The number of vertex shader invocations,\nwhich corresponds to the number of items in the layer's ",(0,r.jsx)(n.code,{children:"data"})," prop"]}),"\n",(0,r.jsx)(n.li,{children:"The number of fragment shader invocations, which corresponds to the total\nnumber of pixels drawn."}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Thus it is possible to render a scatterplot layer with 10M items with reasonable\nframe rates on recent GPUs, provided that the radius (number of pixels) of each\npoint is small."}),"\n",(0,r.jsxs)(n.p,{children:["It is good to be aware that excessive overdraw (drawing many objects/pixels on top of each other) can generate very high fragment counts and thus hurt performance. As an example, a ",(0,r.jsx)(n.code,{children:"Scatterplot"})," radius of 5 pixels generates ~ 100 pixels per point. If you have a ",(0,r.jsx)(n.code,{children:"Scatterplot"})," layer with 10 million points, this can result in up to 1 billion fragment shader invocations per frame. While dependent on zoom levels (clipping will improve performance to some extent) this many fragments will certainly strain even a recent MacBook Pro GPU."]}),"\n",(0,r.jsx)(n.h2,{id:"layer-picking-performance",children:"Layer Picking Performance"}),"\n",(0,r.jsx)(n.p,{children:"deck.gl performs picking by drawing the layer into an off screen picking buffer. This essentially means that every layer that supports picking will be drawn off screen when panning and hovering. The picking is performed using the same GPU code that does the visual rendering, so the performance should be easy to predict."}),"\n",(0,r.jsx)(n.p,{children:"Picking limitations:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"The picking system can only distinguish between 16M items per layer."}),"\n",(0,r.jsx)(n.li,{children:"The picking system can only handle 256 layers with the pickable flag set to true."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"number-of-layers",children:"Number of Layers"}),"\n",(0,r.jsx)(n.p,{children:"The layer count of an advanced deck.gl application tends to gradually increase, especially when using composite layers. We have built and optimized a highly complex application using close to 100 deck.gl layers (this includes hierarchies of sublayers rendered by custom composite layers rendering other composite layers) without seeing any performance issues related to the number of layers. If you really need to, it is probably possible to go a little higher (a few hu
1ndred layers). Just keep in mind that deck.gl was not designed to be used with thousands of layers."}),"\n",(0,r.jsx)(n.h2,{id:"common-issues",children:"Common Issues"}),"\n",(0,r.jsx)(n.p,{children:"A couple of particular things to watch out for that tend to have a big impact on performance:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["If not needed disable Retina/High DPI rendering. It generates 4x the number of pixels (fragments) and can have a big performance impact that depends on which computer or monitor is being used. This feature can be controlled using ",(0,r.jsx)(n.code,{children:"useDevicePixels"})," prop of ",(0,r.jsx)(n.code,{children:"DeckGL"})," component and it is on by default."]}),"\n",(0,r.jsx)(n.li,{children:"Avoid using luma.gl debug mode in production. It queries the GPU error status after each operation which has a big impact on performance."}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Smaller considerations:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Enabling picking can have a small performance penalty so make sure the ",(0,r.jsx)(n.code,{children:"pickable"})," property is ",(0,r.jsx)(n.code,{children:"false"})," in layers that do not need picking (this is the default value)."]}),"\n"]})]})}function h(e={}){let{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(u,{...e})}):u(e)}}}]);
2//# sourceMappingURL=0112794a.ec67aa21.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.