Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GLTFLoader: Fix incorrect accessor count caching #16989

Merged
merged 1 commit into from
Jul 24, 2019
Merged

GLTFLoader: Fix incorrect accessor count caching #16989

merged 1 commit into from
Jul 24, 2019

Conversation

zeux
Copy link
Contributor

@zeux zeux commented Jul 8, 2019

When two accessors with a different count but the same component type
share a buffer view, there are two cases in GLTF loader:

  • If accessor stride is "natural" (equal to item size in bytes), a
    TypedArray slice is created with the count matching accessor count

  • If accessor stride is "unnatural", an InterleavedBuffer is created for
    the entire buffer view; accessor count is ignored as a result.

Incorrect count is problematic because various algorithms such as
bounding box calculation would try to process elements that may not
exist in the buffer. Note that this situation arises for any unnatural
stride, whether the rest of the stride is used by another interleaved
accessor or not.

A solution is to make sure that the InterleavedBufferAttribute.count
(which is derived from InterleavedBuffer.array.length) is always
correct, by creating a TypedArray from a "slice" of the buffer view.

In an actual interleaving situation this maintains the effectiveness of
the cache - multiple accessors sharing the same interleaved data that
are used in the same mesh primitive will get the same slice (since the
byte offsets will be almost the same, and floor(byteoffset / stride)
will match). Thus this change should only fix existing cases where
.count is broken and not make anything worse.

Fixed #16802.

When two accessors with a different count but the same component type
share a buffer view, there are two cases in GLTF loader:

- If accessor stride is "natural" (equal to item size in bytes), a
TypedArray slice is created with the count matching accessor count

- If accessor stride is "unnatural", an InterleavedBuffer is created for
the entire buffer view; accessor count is ignored as a result.

Incorrect count is problematic because various algorithms such as
bounding box calculation would try to process elements that may not
exist in the buffer. Note that this situation arises for any unnatural
stride, whether the rest of the stride is used by another interleaved
accessor or not.

A solution is to make sure that the InterleavedBufferAttribute.count
(which is derived from InterleavedBuffer.array.length) is always
correct, by creating a TypedArray from a "slice" of the buffer view.

In an actual interleaving situation this maintains the effectiveness of
the cache - multiple accessors sharing the same interleaved data that
are used in the same mesh primitive will get the same slice (since the
byte offsets will be almost the same, and floor(byteoffset / stride)
will match). Thus this change should only fix existing cases where
.count is broken and not make anything worse.

Fixed #16802.
zeux added a commit to zeux/meshoptimizer that referenced this pull request Jul 12, 2019
Specify versions of three.js and Babylon.js that fully support gltfpack output files. Note that three.js version is opportunistically assuming mrdoob/three.js#16989 will get merged in time for the release.
@zeux
Copy link
Contributor Author

zeux commented Jul 17, 2019

@donmccurdy Welcome back! :) Sorry to ping you but hoping this can get into r107 and want to make sure this didn't get lost in the notification noise.

@donmccurdy
Copy link
Collaborator

Thanks! My assumption would be that this change causes the cache to work correctly, but that multiple InterleavedBuffers sharing underlying data will still end up duplicated in GPU memory. As that is still a step forward, I'm fine with merging this. However, I don't have access to a glTF model that exercises this code, or an easy way to create one – does gltfpack output something we can test here?

@zeux
Copy link
Contributor Author

zeux commented Jul 17, 2019

multiple InterleavedBuffers sharing underlying data will still end up duplicated in GPU memory

This is still the case when component types differ, yes - in the interleaved storage scenario we by necessity need to create a buffer per each component type because the existing three.js API can't represent the real data layout. As noted on the linked issue, this change resolves the correctness problem, but doesn't resolve the efficiency problem.

gltfpack hits this problem even though it doesn't technically interleave the buffers, but it e.g. stores normals as BYTE3 normalized data, and has to use byteStride=4 to align it properly. BYTE3 normalized normals are currently not valid as per spec but the same issue can occur in existing interleaving scenarios - it's easy for me to upload an example of the former, but harder to make an example of the latter because I don't have code on my hands to do that.

@donmccurdy
Copy link
Collaborator

donmccurdy commented Jul 17, 2019

it's easy for me to upload an example of the former, but harder to make an example of the latter because I don't have code on my hands to do that.

If the former is enough to test this change, and you could test that, I'm satisfied. The latter does seem like a situation we should add to glTF-Asset-Generator if it isn't covered yet, but that may not happen right away.

@zeux
Copy link
Contributor Author

zeux commented Jul 17, 2019

@donmccurdy Yeah this fix does resolve issues with gltfpack-generated meshes.

https://github.com/KhronosGroup/glTF-Asset-Generator/blob/master/Output/Positive/Buffer_Interleaved/README.md only has basic single-mesh cases, so it's not hitting that - the problem in this case would have been if a second mesh was added that had an accessor into the same buffer view with a different offset. Interleaved buffers in general tend to be rare in glTF world as far as I know which is probably why nobody has hit this before...

@zeux
Copy link
Contributor Author

zeux commented Jul 18, 2019

FWIW here's an example of a .glb file packed by gltfpack; it can be opened by Babylon.JS without issues, but trying to open in three.JS doesn't render the mesh, and produces the following errors in the output:

THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.

The error happens because of incorrectly propagated .count, as described in the related issue. This change fixes that. and after applying it the .glb file loads correctly. You can see this here https://timvanscherpenzeel.github.io/three-gltf-viewer/ (this viewer fork pulls GLTFLoader.js from meshoptimizer repository which has the fix applied)

BrainStem.glb.zip

@mrdoob mrdoob requested review from takahirox and donmccurdy July 18, 2019 07:43
@zeux
Copy link
Contributor Author

zeux commented Jul 24, 2019

@mrdoob let me know if I need to do anything else to help get this merged

@Mugen87 Mugen87 added this to the r107 milestone Jul 24, 2019
@mrdoob
Copy link
Owner

mrdoob commented Jul 24, 2019

All good!

@mrdoob mrdoob merged commit bc8720f into mrdoob:dev Jul 24, 2019
@mrdoob
Copy link
Owner

mrdoob commented Jul 24, 2019

Thanks!

@zeux zeux deleted the gltf-fixibcache branch July 25, 2019 02:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

GLTFLoader: Invalid interleaved buffer setup when multiple attribute accessors share the same buffer
4 participants