weakref 不能用于按需释放 webgl 纹理,因其无法触发 gl.deletetexture(),且 webgltexture 不受 js 垃圾回收控制;它仅适用于缓存 image 或 imagedata 等可 gc 的 js 对象,并配合 finalizationregistry 清理缓存键。

WeakRef 不能用于按需释放 WebGL 纹理,因为 gl.deleteTexture() 必须由开发者显式调用,而 WeakRef 既不触发释放动作,也无法感知纹理是否“闲置”或“可删”。
它只能在目标对象**已被 GC 回收后**通知你“人没了”,但 WebGL 纹理对象(WebGLTexture)本身不受 JS 垃圾回收控制——只要没调用 gl.deleteTexture(),GPU 内存就一直占着,哪怕 JS 层的 texture 变量早已被设为 null。
所以,“用 WeakRef 实现按需释放”这个前提不成立。真正可行的是:用 WeakRef 辅助**自动清理缓存引用**,但释放动作必须手动、及时、成对。
为什么 WeakRef 对 WebGLTexture 没法触发释放
WebGL 资源生命周期独立于 JS 堆:
-
gl.createTexture()分配 GPU 内存,JS 层只拿到一个轻量 handle(类似指针) - 这个 handle 是普通对象,可被
WeakRef包裹,但它不是资源本体 - 即使
WeakRef.deref()返回undefined,也只说明 JS 层引用断了,gl.deleteTexture()没被调,GPU 纹理依然存在 - FinalizationRegistry 回调里拿不到原
WebGLTexture,无法调gl.deleteTexture()
WeakRef 的合理位置:管理纹理加载缓存,而非 GPU 资源本身
适合用 WeakRef 的是“纹理加载中间态”——比如你用 URL 作 key 缓存 Image 或解码后的 ImageData,这些是纯 JS 对象,GC 可控:
- 缓存
new Image()实例时,用WeakRef避免图片加载后长期驻留内存 - 缓存解压缩后的纹理像素数据(如
Uint8Array)时,WeakRef可让 GC 在内存紧张时优先回收 - 注册
FinalizationRegistry清理 Map 中失效的 key,防止缓存无限膨胀
const imageCache = new Map();
const registry = new FinalizationRegistry(key => imageCache.delete(key));
function loadImage(url) {
if (imageCache.has(url)) {
const ref = imageCache.get(url);
const img = ref?.deref();
if (img) return img;
}
const img = new Image();
img.src = url;
imageCache.set(url, new WeakRef(img));
registry.register(img, url);
return img;
}
真正的“按需释放”靠的是资源使用计数 + 显式 delete
要实现纹理的按需释放(比如页面切换、模型卸载时),必须自己维护引用关系:
- 每个
WebGLTexture对应一个引用计数器,每次绑定给某个材质/对象就 +1 - 每次该材质/对象销毁或切换纹理,就 -1;计数归零时立即调
gl.deleteTexture(texture) - 切忌依赖
WeakRef等 GC 来决定何时释放——GC 时间不可控,GPU 内存可能卡住数秒甚至更久 - 务必同步解除 JS 引用:
texture = null,否则 GC 无法回收 handle 对象(虽不影响 GPU,但浪费 JS 堆)
FinalizationRegistry 的唯一安全用途:日志与兜底告警
你可以用它记录“本该被释放却没被释放”的纹理:
- 注册时传入纹理 ID 和创建堆栈(
new Error().stack) - 回调中仅打日志:
console.warn(`Texture ${id} leaked, created at:`, stack)` - 绝不尝试在回调里调
gl.deleteTexture()—— 此时 GL 上下文可能已丢失,且回调无 gl 上下文可用 - 这属于开发期辅助手段,上线应关闭
WebGLTexture handle 都变为无效,但 JS 层变量仍非 null。任何基于 WeakRef 或引用计数的机制,都必须配合 webglcontextlost 事件做批量重置,否则后续 gl.bindTexture() 会静默失败。











