Three.js: DRACOLoader fails only on iOS Safari, only in production build

Created on 4 Feb 2020  路  9Comments  路  Source: mrdoob/three.js

When loading a draco gltf model on a development build, I have no issue anywhere. When loading a draco gltf model on Safari iOS in production, I have a load of errors (THREE.DracoLoader: Unexpected attribute type). It works on any other device/browser, and fails only on my production build.

In my experience, minification and optimization of code for production sometimes produces code that will fail on specific browsers, but I'm a bit clueless for this one. Did anyone ever experience that, and what would be a fix?
The build is made with webpack 4.41.2, and it fails with both wasm and js draco decoders.

Bug Loaders

All 9 comments

Would you be able to share a demo of the broken production build? Or, if not, the webpack configuration you're using? I haven't heard of this issue before, but DRACOLoader uses Web Workers, and the error is coming from inside a Worker, so perhaps minification is interfering with that somehow.

If you'd be able to modify DRACOLoader locally, changing this line:

https://github.com/mrdoob/three.js/blob/daedcccfa3d9e50f49a949abf9dc6e3124518857/examples/js/loaders/DRACOLoader.js#L511

... to ...

var attributeType = attributeTypes[ attributeName ];

... might be worth a try. That self[ ... ] reference looks like it may be incorrect.

It took me almost all my day of work, but I got it. Here's the Webpack config I used to fix the issue:

{
  optimization: {
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          keep_fnames: /Float32Array/,
        },
      }),
    ],
  }
}

I'm still not completely sure what happened. I think on iOS Safari (at least older versions, up to 11.2 according to caniuse.com), Float32Array is polyfilled, and when the code is minified, Float32Array's name gets lost somehow. The problem itself was not in DRACOLoader but in GLTFLoader, in the part of decodePrimitive that fills attributeTypeMap.
I don't know if this could be mitigated in three.js directly.

EDIT: actually I misread caniuse and Float32Array has been long available. So I have no clue :|

In any case, glad you got this working!

Maybe Float32Array did get polyfilled, just to patch some missing behavior, like .fill? Safari still doesn't have that. I don't think we use that method, but webpack wouldn't know.

Hard to guess exactly what's happening but if this comes up again, maybe we could try replacing the constants here...

https://github.com/mrdoob/three.js/blob/daedcccfa3d9e50f49a949abf9dc6e3124518857/examples/js/loaders/GLTFLoader.js#L1013-L1020

... with strings, and using a switch block to choose a constructor.

Yes that would be better. I'm guessing something similar could come up with other typed arrays, but since DRACO only uses Float32Array, I didn't have to put the rest of them in the webpack configuration

Hello, I run into the same issue in production, my project works great on Chrome but on Safari and Safari IOS the DRACO loader return the same error message THREE.DracoLoader: Unexpected attribute type. I tried @SBRK solution with the TerserPlugin but with no luck.

What would you recommend to solve this issue ?

@SBRK Had the same issue and your fix solved it for us! Thank you so much for the search 馃憤

In any case, glad you got this working!

Maybe Float32Array did get polyfilled, just to patch some missing behavior, like .fill? Safari still doesn't have that. I don't think we use that method, but webpack wouldn't know.

Hard to guess exactly what's happening but if this comes up again, maybe we could try replacing the constants here...

https://github.com/mrdoob/three.js/blob/daedcccfa3d9e50f49a949abf9dc6e3124518857/examples/js/loaders/GLTFLoader.js#L1013-L1020

... with strings, and using a switch block to choose a constructor.

Actually, doing that an only that (ie. replacing the Constructors with their names as Strings)

var WEBGL_COMPONENT_TYPES = {
  5120: "Int8Array",
  5121: "Uint8Array",
  5122: "Int16Array",
  5123: "Uint16Array",
  5125: "Uint32Array",
  5126: "Float32Array"
};

was enough for me to get the DRACOLoader worker to work on Safari (iOS and desktop).
In fact, in https://github.com/mrdoob/three.js/blob/c5560e5f94a9c40cba74fdb5f64b3794fecf0a4b/examples/jsm/loaders/DRACOLoader.js#L520

this will be evaluated to the Constructor, even if the passed in value is a String.

Thanks a lot for the pointer @donmccurdy!

(background to this is that it seems passing actual JavaScript Objects to the worker doesn't really work in Safari. The attributeTypes was becoming { position: "", normal: "", uv: "", uv2: "", ... } in the worker context for me)

--

EDIT 23.07.20
I was wrong, just changing WEBGL_COMPONENT_TYPES to contain strings breaks the non-draco loading, specifically at (for example) https://github.com/mrdoob/three.js/blob/c0039bc8fa31c7524a5ef2fd05bf96cb4c4d2a96/examples/jsm/loaders/GLTFLoader.js#L2083

which then throws TypedArray is not a constructor error down the line, since it's now a String.
To solve this, I had to do what @donmccurdy sugested in the first place (on top of change WEBGL_COMPONENT_TYPES to contain strings) and add the following utility function:

function getWebglComponentTypeConstructor(componentTypeString) {
  switch (componentTypeString) {
    case "Int8Array":
      return Int8Array;
    case "Uint8Array":
      return Uint8Array;
    case "Int16Array":
      return Int16Array;
    case "Uint16Array":
      return Uint16Array;
    case "Uint32Array":
      return Uint32Array;
    case "Float32Array":
      return Float32Array;
  }
}

as well as change a few lines in my GLTFLoader instead
https://github.com/mrdoob/three.js/blob/c0039bc8fa31c7524a5ef2fd05bf96cb4c4d2a96/examples/jsm/loaders/GLTFLoader.js#L2083 to

var TypedArray = getWebglComponentTypeConstructor(WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]);

and
https://github.com/mrdoob/three.js/blob/c0039bc8fa31c7524a5ef2fd05bf96cb4c4d2a96/examples/jsm/loaders/GLTFLoader.js#L2135 to

var TypedArrayIndices = getWebglComponentTypeConstructor(WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]);

@silvainSayduck @donmccurdy did any of this make its way intto 3js

I'm not sure what there is to fix here, it seems more like a bug in Webpack. If you understand what is wrong and want to open a PR that's okay with me.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

akshaysrin picture akshaysrin  路  3Comments

jlaquinte picture jlaquinte  路  3Comments

donmccurdy picture donmccurdy  路  3Comments

yqrashawn picture yqrashawn  路  3Comments

Horray picture Horray  路  3Comments