function parse(loader, frameState) {
  const gltf = loader.gltfJson;
  const extensions = gltf.extensions ?? Frozen.EMPTY_OBJECT;
  const structuralMetadataExtension = extensions.EXT_structural_metadata;
  const featureMetadataExtensionLegacy = extensions.EXT_feature_metadata;
  const cesiumRtcExtension = extensions.CESIUM_RTC;

  if (defined(featureMetadataExtensionLegacy)) {
    // If the old EXT_feature_metadata extension is present, sort the IDs of the
    // feature tables and feature textures so we don't have to do this once
    // per primitive.
    //
    // This must run before loadNodes so these IDs are available when
    // attributes are processed.
    const featureTables = featureMetadataExtensionLegacy.featureTables;
    const featureTextures = featureMetadataExtensionLegacy.featureTextures;
    const allPropertyTableIds = defined(featureTables) ? featureTables : [];
    const allFeatureTextureIds = defined(featureTextures)
      ? featureTextures
      : [];
    loader._sortedPropertyTableIds = Object.keys(allPropertyTableIds).sort();
    loader._sortedFeatureTextureIds = Object.keys(allFeatureTextureIds).sort();
  }

  const nodes = loadNodes(loader, frameState);
  const skins = loadSkins(loader, nodes);
  const animations = loadAnimations(loader, nodes);
  const articulations = loadArticulations(gltf);
  const scene = loadScene(gltf, nodes);

  const components = new Components();
  const asset = new Asset();
  const copyright = gltf.asset.copyright;
  if (defined(copyright)) {
    const credits = copyright.split(";").map(function (string) {
      return new Credit(string.trim());
    });
    asset.credits = credits;
  }

  components.asset = asset;
  components.scene = scene;
  components.nodes = nodes;
  components.skins = skins;
  components.animations = animations;
  components.articulations = articulations;
  components.upAxis = loader._upAxis;
  components.forwardAxis = loader._forwardAxis;

  if (defined(cesiumRtcExtension)) {
    // CESIUM_RTC is almost always WGS84 coordinates so no axis conversion needed
    const center = Cartesian3.fromArray(
      cesiumRtcExtension.center,
      0,
      scratchCenter,
    );
    components.transform = Matrix4.fromTranslation(
      center,
      components.transform,
    );
  }

  loader._components = components;

  // Load structural metadata (property tables and property textures)
  if (
    defined(structuralMetadataExtension) ||
    defined(featureMetadataExtensionLegacy)
  ) {
    const promise = loadStructuralMetadata(
      loader,
      structuralMetadataExtension,
      featureMetadataExtensionLegacy,
      frameState,
    );
    loader._loaderPromises.push(promise);
  }

  // Load NGA_gpm_local from root object
  const gpmExtension = extensions.NGA_gpm_local;
  if (defined(gpmExtension)) {
    const gltfGpmLocal = GltfGpmLoader.load(gpmExtension);
    loader._components.extensions["NGA_gpm_local"] = gltfGpmLocal;
  }

  // Load NGA_gpm_local from mesh primitives
  const meshes = gltf.meshes;
  if (defined(meshes)) {
    for (const mesh of meshes) {
      const primitives = mesh.primitives;
      if (defined(primitives)) {
        for (const primitive of primitives) {
          const primitiveExtensions = primitive.extensions;
          if (defined(primitiveExtensions)) {
            const meshPrimitiveGpmExtension = primitiveExtensions.NGA_gpm_local;
            if (defined(meshPrimitiveGpmExtension)) {
              const promise = loadMeshPrimitiveGpm(
                loader,
                gltf,
                meshPrimitiveGpmExtension,
                frameState,
              );
              loader._loaderPromises.push(promise);
            }
          }
        }
      }
    }
  }

  // Gather promises and handle any errors
  const readyPromises = [];
  readyPromises.push.apply(readyPromises, loader._loaderPromises);

  // When incrementallyLoadTextures is true, the errors are caught and thrown individually
  // since it doesn't affect the overall loader state
  if (!loader._incrementallyLoadTextures) {
    readyPromises.push.apply(readyPromises, loader._texturesPromises);
  }

  return Promise.all(readyPromises);
}
Parse the glTF which populates the loaders arrays. Loading promises will be created, and will
resolve once the loaders are ready (i.e. all external resources
have been fetched and all GPU resources have been created). Loaders that
create GPU resources need to be processed every frame until they become
ready since the JobScheduler is not able to execute all jobs in a single
frame. Any promise failures are collected, and will be handled synchronously in process().
Also note that it's fine to call process before a loader is ready to process or
after it has failed; nothing will happen.

@param {GltfLoader} loader
@param {FrameState} frameState
@returns {Promise} A Promise that resolves when all loaders are ready
@private