Skip to content

SogBundleParser keeps raw file data allocated #8080

@fimbox

Description

@fimbox

The downloaded arrayBuffer in SogBundleParser.load stays in memory, hence duplicating the heap memory footprint of a SOG entity.
Since the texture cleanup is done by the GSplatSogsData._destroyGpuResources there is no need to keep the textures in asset registry.

I batched the SogBundleParser.load in order to free the array after parsing and everything seems to work, but not ready for PR

  async load(url, callback, asset) {
      try {
          let arrayBuffer = await downloadArrayBuffer(url, asset);

          const files = parseZipArchive(arrayBuffer);

          // deflate
          for (const file of files) {
              if (file.compression === 'deflate') {
                  file.data = await inflate(file.data);
              }
          }

          // access bundled meta.json
          const metaFile = files.find(f => f.filename === 'meta.json');
          if (!metaFile) {
              callback('Error: meta.json not found');
              return;
          }

          // parse json
          let meta;
          try {
              meta = JSON.parse(new TextDecoder().decode(metaFile.data));
          } catch (err) {
              callback(`Error parsing meta.json: ${err}`);
              return;
          }

          // extract filenames from meta.json
          const filenames = ['means', 'scales', 'quats', 'sh0', 'shN'].map(key => meta[key]?.files ?? []).flat();

          arrayBuffer = null;

          // load referenced textures
          const textures = {};
          const promises = [];
          for (const filename of filenames) {
              const file = files.find(f => f.filename === filename);
              let texture;
              if (file) {
                  // file is embedded
                  texture = new Asset(filename, 'texture', {
                      url: `${url.load}/${filename}`,
                      filename,
                      contents: file.data
                  }, {
                      mipmaps: false
                  });
              } else {
                  // file doesn't exist in bundle, treat it as a url
                  const url = (new URL(filename, new URL(filename, window.location.href).toString())).toString();
                  texture = new Asset(filename, 'texture', {
                      url,
                      filename
                  }, {
                      mipmaps: false
                  });
              }

              const promise = new Promise((resolve, reject) => {

                  const onLoad = () => {
                      texture.off('load', onLoad);
                      texture.off('error', onError);
                      resolve(null);
                  };
                  const onError = (err) => {
                      texture.off('load', onLoad);
                      texture.off('error', onError);
                      reject(err);
                  };
                  texture.on('load', onLoad);
                  texture.on('error', onError);
              });

              //this.app.assets.add(texture);
              textures[filename] = texture;
              promises.push(promise);
          }

          Object.values(textures).forEach(t => this.app.assets.load(t));

          await Promise.allSettled(promises);

          // Release CPU-side texture data after GPU upload
          Object.values(textures).forEach((t) => {
              if (t.file) {
                  t.file.contents = null;
              }
          });
          
          
          // construct the gsplat resource
          const data = new GSplatSogsData();
          data.meta = meta;
          data.numSplats = meta.count;
          data.means_l = textures[meta.means.files[0]].resource;
          data.means_u = textures[meta.means.files[1]].resource;
          data.quats = textures[meta.quats.files[0]].resource;
          data.scales = textures[meta.scales.files[0]].resource;
          data.sh0 = textures[meta.sh0.files[0]].resource;
          data.sh_centroids = textures[meta.shN?.files[0]]?.resource;
          data.sh_labels = textures[meta.shN?.files[1]]?.resource;

          const decompress = asset.data?.decompress;

          if (!decompress) {
              // no need to prepare gpu data if decompressing
              await data.prepareGpuData();
          }

          const resource = decompress ?
              new GSplatResource(this.app.graphicsDevice, await data.decompress()) :
              new GSplatSogsResource(this.app.graphicsDevice, data);

          callback(null, resource);
      } catch (err) {
          callback(err);
      }
  }

}

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions