UnloadTilesPlugin: revisited tiles remain evictable in the GPU cache · Issue #1767 · NASA-AMMOS/3DTilesRendererJS · GitHub
Skip to content

UnloadTilesPlugin: revisited tiles remain evictable in the GPU cache #1767

Open

Description

@hoyleb

Description

A tile retained while hidden by a nonzero GPU byte target remains marked unused in the plugin cache when shown again. A later cleanup can remove its cache entry while the visibility guard skips GPU disposal. This understates estimatedGpuBytes and prevents that tile from being unloaded on a later hide.

The visibility callback updates the renderer cache through tiles.markTileUsed(tile), but not the plugin cache. lruCache.add(tile, ...) does not mark an existing item used again. Proposed fix: also call the plugin cache's lruCache.markUsed(tile) on every visible transition, keeping the renderer cache marking unchanged.

I have two regression tests and an AI-assisted fix prepared as a draft for discussion. The first regression fails against master and passes with the additional mark.

Reproduction steps

  1. Register UnloadTilesPlugin({ bytesTarget: 100 }) and use two tile scenes each estimated at 100 bytes. Set an equal-priority comparator for deterministic eviction order.
  2. Show tile A, hide it, and let the scheduled cleanup run. The budget retains A as unused.
  3. Show A again, then show and hide B. Let cleanup run.
  4. On master, A is removed from the plugin cache even though it is visible; its unload callback skips actual disposal and B remains resident.
  5. Set bytesTarget to zero and hide A again. A cannot be unloaded because it is no longer tracked. With the fix, B unloads in step 3, A remains counted, and A unloads in step 5.

Code

import { Group } from 'three';
import { TilesRenderer } from '3d-tiles-renderer/three';
import { UnloadTilesPlugin } from '3d-tiles-renderer/three/plugins';

const tiles = new TilesRenderer();
tiles.lruCache.unloadPriorityCallback = () => 0;
const plugin = new UnloadTilesPlugin({ bytesTarget: 100 });
tiles.registerPlugin(plugin);
tiles.dispatchEvent({ type: 'update-before' });
// Controlled byte estimates isolate residency tracking from mesh size.
tiles.calculateBytesUsed = () => 100;
const a = { engineData: { scene: new Group() } };
const b = { engineData: { scene: new Group() } };

tiles.setTileVisible(a, true);
tiles.setTileVisible(a, false);
await Promise.resolve();
tiles.setTileVisible(a, true);
tiles.setTileVisible(b, true);
tiles.setTileVisible(b, false);
await Promise.resolve();

console.log(plugin.lruCache.has(a)); // expected true; master: false
console.log(plugin.lruCache.has(b)); // expected false; master: true

Live example

No hosted browser sandbox. Runnable regression tests and the proposed fix are available in the fork draft: hoyleb#1

In that checkout: npm ci && npx vitest run test/three/UnloadTilesPlugin.test.js. The first regression was verified failing on unmodified master and passing after the fix.

Screenshots & Tile Set

No dataset is needed: the reproduction uses two synthetic Group scenes and controlled byte estimates to isolate the cache lifecycle. No browser GPU benchmark was performed.

Library Version

0.5.3; master at 075b123

Three.js Version

0.185.1

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions