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

Creating one mesh for the ThreeJS scene using the WASM #9

Open
vinnividivicci opened this issue Jul 24, 2023 · 6 comments
Open

Creating one mesh for the ThreeJS scene using the WASM #9

vinnividivicci opened this issue Jul 24, 2023 · 6 comments
Labels
question Further information is requested

Comments

@vinnividivicci
Copy link
Collaborator

vinnividivicci commented Jul 24, 2023

Hello @aothms,

I have a question that's been bouncing around in my head for a while: is it currently possible (or how much work would it be...) to have the IfcOpenShell WASM generate one single mesh (or multiple) without having to iterate in javascript over the entire model and create unique meshes (which is very slow)?

If I understand correctly, this would be a similar approach to how IFC.js loads the model into ThreeJs. They have the heavy lifting of generating the mesh done by the WASM which spits out one mesh that is then loaded into the ThreeJs scene and then use subsets to turn things on and off.

I think this would be an interesting direction to take the IfcOpenShell WASM in the short term since it would make it into a possible alternative IFC loader for ThreeJs. I like the work IFC.js is doing but IfcOpenShell is a more mature API in my opinion and it's always good to have options 😃

Thanks for your time!

@vinnividivicci vinnividivicci added the question Further information is requested label Jul 24, 2023
@aothms
Copy link
Member

aothms commented Jul 25, 2023

Well, I think it's important to separate some things regarding performance.

  • Initialization [downloading and unpacking of pyodide and the ifcopenshell module]
  • Loading time [parsing model, translating procedural geometry]. This won't be so much affected by your proposal. In the generation of threejs meshes there isn't so much heavy lifting. It's just some typed arrays being created.
  • Rendering performance. This is where your proposal makes sense. In gpu rendering you typically try to minimize draw calls as the latency between software and hardware rapidly adds up when you need to render many loose objects (and change state (e.g material) in between).

Instead of hacking this into three js I'd rather work with https://github.com/opensourceBIM/BIMsurfer/ While not very actively maintained, it is designed from scratch with large bim models in mind. Which isn't so hard when using some of the modern webgl2 api calls. It also has a couple of nice features such as section planes, measurement and cad-friendly orbit. You can try it online at https://view.ifcopenshell.org/ when you toggle rendering engine.

@vinnividivicci
Copy link
Collaborator Author

Hi @aothms and thanks for taking the time to reply in such detail, it has helped me make my thoughts clearer.

Of the three things you mentioned, the main one I would like to target is the second: Loading time.

Right now, with the Wasm-preview demo, it takes 45 seconds to load a 10Mb IFC file (I profiled it using the Chrome dev tools). The same model takes 15s to load in IFCjs (but with more errors...).

How do you think this parsing and geometry generation step can be sped up to similar speeds to IfcJS?

Based on the code and the profiler, it seems to me like we are doing a lot of back-and-forth calls from JS/Pyodide to Wasm and back over and over again:
image
Compared to IFCjs:
image

So, in my mind: if the IfcOpenShell WASM was able to, with one function call, output/return an entire model/object of the complete IFC file in a format compatible with ThreeJS (doing this in much faster compiled code), then we would have a "drop-in" replacement (or compatible library...) for IFC.js.

@aothms
Copy link
Member

aothms commented Jul 26, 2023

Cool insights from profiling. I'd be tempted to think the problem is not so much the per-mesh creation, but the way we go in C++ from (a) native array (b) sequence of python floats (c) sequence of javascript floats (d) javascript typed array. It looks trivial in code [0], but there is a lot of unnecessary back and forth. Also creating submeshes based on the material idxs doesn't help [1] of course.

[0] https://github.com/IfcOpenShell/wasm-preview/blob/master/index.html#L172
[1] https://github.com/IfcOpenShell/wasm-preview/blob/master/index.html#L189

So we need to look into [2] and [3] to seemlessly get the C++ buffers accessible in JS wasm as typed arrays.

[2] https://pyodide.org/en/0.16.1/type_conversions.html#typed-arrays
[3] https://www.swig.org/Doc1.3/Python.html#Python_nn75

It seems that is a prerequisite anyway. If after that it is still obviously slow, we can (optionally) concatenate these buffers on the C++ side.

Thoughts?

@vinnividivicci
Copy link
Collaborator Author

I think you may be on to something. I dug deeper into the profiling and took a 24ms long section of a pattern that seems to repeat over-and-over. Of that 24ms section, at least 50% of the time (likely higher since at one point I stopped counting) is spent on wasm-to-js and js-to-wasm functions. Here are some of them circled:
image

However, I'm not sure how to optimize it in our JS/HTML example and if it's even realistic to think that going from js -> pyiodide wasm -> js -> ifcopenshell wasm -> js ... will ever be very efficient. Wouldn't have all the heavy lifting done in the IfcOpenShell WASM and returned as geometry + data that ThreeJS or BIMSurfer can use make more sense?

@aothms
Copy link
Member

aothms commented Jul 27, 2023

To be honest I don't understand this callstack very well. IntTools_FaceFace and IntPatch_Intersection are both opencascade methods calling each other, deeply in the processing of geometric entities, why would there be this js-to-wasm in between?

It would be cool if we can annotate this profiling run with the various "events" that occur during page load, such as parsed ifc model, geometric tesselation computed, webgl representation created. So that we have a better understanding of the spacing between these events.

@vinnividivicci
Copy link
Collaborator Author

I've attached the trace created using Google Chrome Dev Tools in case you want to load it and explore:
Trace-20230731T162030.zip

The trace starts after all the WASMs are loaded and just before I click on "load" which starts loading the model:
image

I'm not familiar enough with the lower-level function calls to tell where the transition from parsing to geometry creation happens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants