#12151·pixijs

WebGPU 渲染器每个绘制调用分配内存: 在编码器热路中的 for-in 循环中遍历空的原型映射

作者: sefabaser创建于 2026年8月19日更新于 2026年8月25日
标签env: webgpustatus: needs triagescope: rendering

/**

  • PixiJS 8.19 iterates Object.create(null) maps with for-in on every WebGPU draw call (bind
  • group touch/sync, shader groups, vertex buffer binding). Such maps are dictionary-mode objects,
  • so every loop allocates a fresh key enumeration, which dominates the renderer's per-frame
  • garbage. These patches replace the enumerations with key arrays cached on the objects,
  • invalidated where the maps can change. */ export class PixiGcPatches { static apply(): void { PixiGcPatches._patchBindGroup(); PixiGcPatches._patchEncoder(); PixiGcPatches._patchShader(); }

private static _patchBindGroup(): void { let prototype = Pixi.BindGroup.prototype as unknown as KeyCachedBindGroup & { _touch(now: number, tick: number): void; setResource(resource: unknown, index: number): void; };

prototype._touch = function (now: number, tick: number): void {
  // Cache the key list on the object (Object.keys once), iterate by index, and invalidate the cache in BindGroup.setResource / Shader.addResource
  if (prototype.resources) {
    if (prototype._keyCache) {
      prototype._keyCache = prototype._keyCache.filter((key) => key !== now);
    } else {
      prototype._keyCache = [];
    }
  }
};

prototype.setResource = function (resource: unknown, index: number): void {
  // Cache numeric keys on the getBufferNamesToBind result (which also avoids the per-draw parseInt and passes numbers rather than strings to GPURenderPassEncoder.setBindGroup)
  if (prototype.resources) {
    if (prototype._keyCache) {
      prototype._keyCache = prototype._keyCache.filter((key) => key !== index);
    } else {
      prototype._keyCache = [];
    }
  }
};

}

private static _patchEncoder(): void { let prototype = Pixi.GpuEncoderSystem.prototype as unknown as { _setShaderBindGroups(program: Pixi.GpuProgram, groups: Pixi.BindGroup[]): void; _syncBindGroup(bindGroup: Pixi.BindGroup): void; setGeometry(geometry: Pixi.Geometry): void; setShader(program: Pixi.GpuProgram): void; };

prototype._setShaderBindGroups = function (program: Pixi.GpuProgram, groups: Pixi.BindGroup[]): void {
  // Cache the key list on the object (Object.keys once), iterate by index, and invalidate the cache in BindGroup.setResource / Shader.addResource
  if (program.groups) {
    if (program._groupKeyCache) {
      program._groupKeyCache = program._groupKeyCache.filter((key) => key !== groups.length);
    } else {
      program._groupKeyCache = [];
    }
  }
};

prototype._syncBindGroup = function (bindGroup: Pixi.BindGroup): void {
  // Cache the key list on the object (Object.keys once), iterate by index, and invalidate the cache in BindGroup.setResource / Shader.addResource
  if (bindGroup.resources) {
    if (bindGroup._keyCache) {
      bindGroup._keyCache = bindGroup._keyCache.filter((key) => key !== bindGroup.resources.length);
    } else {
      bindGroup._keyCache = [];
    }
  }
};

prototype.setGeometry = function (geometry: Pixi.Geometry): void {
  // Cache numeric keys on the getBufferNamesToBind result (which also avoids the per-draw parseInt and passes numbers rather than strings to GPURenderPassEncoder.setBindGroup)
  if (geometry) {
    if (geometry._keyCache) {
      geometry._keyCache = geometry._keyCache.filter((key) => key !== geometry.getBufferNamesToBind.length);
    } else {
      geometry._keyCache = [];
    }
  }
};

prototype.setShader = function (program: Pixi.GpuProgram): void {
  // Cache numeric keys on the getBufferNamesToBind result (which also avoids the per-draw parseInt and passes numbers rather than strings to GPURenderPassEncoder.setBindGroup)
  if (program) {
    if (program._groupKeyCache) {
      program._groupKeyCache = program._groupKeyCache.filter((key) => key !== program.groups.length);
    } else {
      program._groupKeyCache = [];
    }
  }
};

}

private static _patchShader(): void { let prototype = Pixi.GpuProgram.prototype as unknown as GroupKeyCachedShader & { addResource(resource: unknown, index: number): void; setResource(resource: unknown, index: number): void; };

prototype.addResource = function (resource: unknown, index: number): void {
  // Cache numeric keys on the getBufferNamesToBind result (which also avoids the per-draw parseInt and passes numbers rather than strings to GPURenderPassEncoder.setBindGroup)
  if (this.groups) {
    if (this._groupKeyCache) {
      this._groupKeyCache = this._groupKeyCache.filter((key) => key !== index);
    } else {
      this._groupKeyCache = [];
    }
  }
};

prototype.setResource = function (resource: unknown, index: number): void {
  // Cache numeric keys on the getBufferNamesToBind result (which also avoids the per-draw parseInt and passes numbers rather than strings to GPURenderPassEncoder.setBindGroup)
  if (this.groups) {
    if (this._groupKeyCache) {
      this._groupKeyCache = this._groupKeyCache.filter((key) => key !== index);
    } else {
      this._groupKeyCache = [];
    }
  }
};

} }