visgl / deck.gl

WebGL2 powered visualization framework
https://deck.gl
MIT License
12.25k stars 2.08k forks source link

[Bug] IconLayer not correctly disposing of Texture #9164

Open felixpalmer opened 1 month ago

felixpalmer commented 1 month ago

Description

IconLayer generates WebGL: INVALID_OPERATION errors when a layer is repeatedly added & removed. The use case is adding a custom layer on hover.

Visually the icon texture data is corrupted across all layers (not just the one rendering the hover), suggesting that we are holding onto memory we shouldn't be.

https://github.com/user-attachments/assets/84eada31-a99c-4eb5-9d2d-16f3eb8c224a

The following errors are repeatedly logged:

WebGL: INVALID_OPERATION: bindTexture: attempt to use a deleted object
WebGL: INVALID_OPERATION: generateMipmap: no texture bound to target

Flavors

Expected Behavior

No response

Steps to Reproduce

Modify the examples/website/icon/app.tsx:

import React, {useState} from 'react';
import {createRoot} from 'react-dom/client';

import DeckGL from '@deck.gl/react';
import {IconLayer} from '@deck.gl/layers';
import type {PickingInfo} from '@deck.gl/core';

type BartStation = {
  name: string;
  entries: number;
  exits: number;
  coordinates: [longitude: number, latitude: number];
};

const baseUrl = 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/'

function App() {
  const [hovered, setHovered] = useState(null);
  const layer = new IconLayer<BartStation>({
    id: 'IconLayer',
    data: baseUrl + 'bart-stations.json',
    getColor: (d: BartStation) => [Math.sqrt(d.exits), 140, 0],
    getPosition: (d: BartStation) => d.coordinates,
    getSize: 40,

    // Atlas (always works)
    // iconAtlas: baseUrl + 'icon-atlas.png',
    // iconMapping: baseUrl + 'icon-atlas.json',
    // getIcon: (d: BartStation, info) => info.index % 2 ? 'marker' : 'marker-warning',

    // Packing
    getIcon: (d: BartStation, info) => {
      const icon = d.name.length % 2 ? 'icon-marker.png' : 'icon-warning.png';
      return {url: baseUrl + icon, width: 64, height: 64}
    },

    pickable: true,
        onHover: event => {
      setHovered(event.object);
    }
  });

  const hoverLayer = layer.clone({
    id: 'hover',
    data: hovered ? [hovered] : [],
    pickable: false,
    sizeScale: 1.5,
    visible: Boolean(hovered)
  });

  // Variant A: works
  // const layers = [layer, hoverLayer];

  // Variant B: get Texture errors & glitches
  const layers = hovered ? [layer, hoverLayer] : [layer];

  return <DeckGL
    initialViewState={{
      longitude: -122.4,
      latitude: 37.74,
      zoom: 11
    } as any}
    controller
    getTooltip={({object}: PickingInfo<BartStation>) => object && object.name}
    layers={layers}
  />;
}

export function renderToDOM(container: HTMLDivElement) {
  createRoot(container).render(<App />);
}

Environment

Logs

No response

ibgreen commented 1 month ago

A possible reason is that textures are now immutable so resizing (via any method such as framebuffer.resize() etc) creates new textures. This means that if we hold on to textures separately we may end up referencing deleted textures.