Jump to content

Creating big meshes performance


photo

Recommended Posts

Hello,

We have our own streaming of data and in the end we need to create the Unigine meshes. While we can load the actual data in background threads, it seems that there is no reliable way to create Unigine meshes but in the main thread (please correct me if I am wrong). So in the main thread, we need to call Unigine::ObjectMeshStatic::create in small steps and bail out in the current frame if create takes too much time. So basically in each frame, in the main thread, we create as many meshes as we can as long as we are under a certain number of milliseconds (so we don't create stalls). The problem is that we have some meshes that are big (e.g. heightmap meshes 1024x1024 vertices) and this take too much time to create (Unigine::ObjectMeshStatic::create takes up to half second).

So is there any way we can do this on steps or reliable on a background thread? I would rather avoid to break the big meshes into smaller ones (complicates the code and introduce unnecessary rendering calls).

Kind Regards,

Adrian

VSTEP BV.

Link to comment

Hi,

you can use AsyncQueue class for load mesh in other thread then main.

When you finish loading, then in main thread call tis function:

mech.get()->flushMesh();
mech.get()->setEnabeld(false);
mech.get()->setEnabeld(true);
Unigine::Editor::get()->addNode(mesh.get()->getNode(), true);

It works nice for background loading/modify mesh.

Honya

Edited by honya
Link to comment

Hi Honya and thank you for your suggestion. Unfortunately we are using Unigine 2.5 (and don't see us updating soon due to other dependencies). AsyncQueue was added starting with 2.6.

Any other ideas how to speed this up?

Link to comment

Hi Adrian,

AsyncQueue is just async stuff gathered from FileSystem / World / Terrain classes to a single place. All of them are available in earlier versions as well, try to use FileSystem::loadMesh.

Another reason of big spikes might be collision spatial tree generation during first intersection / collision test. This can be solved by creating ObjectMeshStatic in another thread and checking test intersection which will generate the tree.

What is prohibited to do in another thread is upload geometry to the GPU, so make sure you call ObjectMeshStatic::flushMesh explicitly or implicitly in main thread. Also notice that all render related operations (render api calls, material shader fetch) might implicitly call ObjectMeshStatic::flushMesh so avoid those operations in another threads.

Link to comment

Hi UncleBob and thank you for the response.

So basically as long as I don't upload video mem data (flush meshes) I should be fine?

Also, I did noticed that if I use my own mutex and call lock; create mesh; unlock (so I don't try to create two meshes at the same time), even if I am using multiple threads, all seems to be fine. Can you confirm this (before I go and commit changes to the entire code)? Or should I restrict it to a single background thread?

For example:

lock();

Unigine::ObjectMeshStatic::create(engineMesh)

unlock();

... set material etc...

The same seems to be true for clusters too:

lock();

engCluster = Unigine::ObjectMeshCluster::create(nullptr);

...

engCluster->createMeshes(insts);

unlock();

Do I also need to include createMeshes into the lock (this was not yet fully tested)?

Also, can I call SetParent on nodes from background thread? Or does this need to be made in the main thread?

Kind Regards,

Adrian

 

Link to comment

Adrian,

Quote

So basically as long as I don't upload video mem data (flush meshes) I should be fine?

It depends, but in theory it should be fine. We can give you more accurate advices if you can provide a small test example that can be build on our test stand.

 

Quote

Also, can I call SetParent on nodes from background thread? Or does this need to be made in the main thread?

That can be done only in main thread.

 

Quote

Also, I did noticed that if I use my own mutex and call lock; create mesh; unlock (so I don't try to create two meshes at the same time), even if I am using multiple threads, all seems to be fine. Can you confirm this

Unfortunately, without a test scene it's not possible to say 100% sure. More likely it would work in some cases, but we can't guarantee that.

Thanks!

How to submit a good bug report
---
FTP server for test scenes and user uploads:

Link to comment

Hi and thank you for the feedback!

For now I cannot provide some sample test (very big data, confidential issues etc).

Currently I am testing what I can move in the background threads. Seems that I can also call SetParent in a background thread as long as the parent is not yet visible. Can you confirm this? Basically I have big parent nodes that have their data async deployed (in background threads): static meshes and clusters. Seems that I am able to assign these subdata as nodes of the parent as long as this is inactive. Later I activate the parent node in the main thread, once every subdata is loaded (and the working threads are not working on it anymore).

Kind Regards,

Adrian

Link to comment

Hi Silent,

Yes, noticed that in the end. So now I gather all the setParent calls in a list and apply it in the main thread once the meshes are created.

I also noticed that setWorldTransform called on a background thread (when setParent is not yet set) creates problems (seems that somehow I have nodes ending up on wrong positions in world?). Can you confirm this?

Currently I no longer know what is possible and what it is not (to be done on a background thread). Code is not yet stable, errors depend on very big data being loaded so very hard to trace down. I will probably have more conclusive tests in few days.

Regards,

Adrian

Link to comment

Ok, maybe you can help me to avoid lots of tests and debugging:

So can you indicate what of the following are thread safe (any of them can be called from one or multiple background threads, not in a certain order)? And if they are not, can they still be inserted between a common mutex lock/unlock and made thread safe (called from multiple background threads)? 

Unigine::Mesh::create
Unigine::Mesh::addSurface
Unigine::Mesh::setNumIndices
Unigine::Mesh::setNumVertex
Unigine::Mesh::setNumNormals
Unigine::Mesh::setNumTexCoords0
Unigine::Mesh::setVertex
Unigine::Mesh::setNormal
Unigine::Mesh::setTexCoord0
Unigine::Mesh::setIndex
Unigine::Mesh::setSurfaceTransform
Unigine::Mesh::setBoundBox
Unigine::Mesh::setBoundSphere

Unigine::ObjectMeshStatic::create
ObjectMeshStatic::getNode
ObjectMeshStatic::setEnable
ObjectMeshStatic::setMaterial
ObjectMeshStatic::setName
ObjectMeshStatic::setViewportMask
ObjectMeshStatic::setCastWorldShadow
ObjectMeshStatic::setCastShadow
ObjectMeshStatic::getMaterialInherit

Unigine::Material::findTexture
Unigine::Material::setImageTexture

Unigine::ObjectMeshCluster::create
ObjectMeshCluster::setMesh
ObjectMeshCluster::createMeshes
ObjectMeshCluster::getNumSurfaces
ObjectMeshCluster::setMaterial
ObjectMeshCluster::setCastWorldShadow
ObjectMeshCluster::setCastShadow
ObjectMeshCluster::getMaterialInherit
ObjectMeshCluster::setViewportMask


Also:
is setWorldTransform not thread safe even if I call it right after create for a static mesh object or cluster, without the node having a parent?

Plus, one thing that I've observed is if I call Mesh::create (or maybe some other mesh content functions, like addSurface, or maybe ObjectMeshStatic::create) in a background thread (even if do a lock/unlock), I consistently end up with d3d11_surfaces having no surfaces in D3D11MeshStatic for existent object mesh clusters (only for clusters not simple static meshes) for which I previously had surfaces. Seems that creation overwrites some clusters data. Is this possible?
 

I am using Unigine 2.5

Kind Regards,

Adrian

Link to comment

Hi Adrian,

In theory Unigine::Mesh::* methods can be used in a separate thread, but not the ObjectMeshStatic / Material / ObjectMeshCluster. Also, it's not possible to render an object on screen and simultaneously call some threaded functions that somehow affects this object.

Quote

is setWorldTransform not thread safe even if I call it right after create for a static mesh object or cluster, without the node having a parent?

Yep, it's not recommended to use this function in threads other than main.

Quote

I call Mesh::create (or maybe some other mesh content functions, like addSurface, or maybe ObjectMeshStatic::create) in a background thread (even if do a lock/unlock), I consistently end up with d3d11_surfaces having no surfaces in D3D11MeshStatic for existent object mesh clusters (only for clusters not simple static meshes) for which I previously had surfaces. Seems that creation overwrites some clusters data. Is this possible?

A small test sample will surely help us to get to the root of this issue much faster. Right now it's hard to say what is causing this behavior, sorry.

How to submit a good bug report
---
FTP server for test scenes and user uploads:

Link to comment
Quote

In theory Unigine::Mesh::* methods can be used in a separate thread,

So, if I don't create an object (or use the mesh into any other thread) until the mesh is fully created by my background thread, should I be safe to Mesh functions in a background thread?

I am asking, since based on my latest tests it seems that Mesh functions in a background thread, even for unrelated meshes, messes up the D3D11MeshStatic::d3d11_surfaces for object clusters already created (this vector becomes blank).

Quote

but not the ObjectMeshStatic / Material / ObjectMeshCluster.

How about the suggestion from UncleBob that I can create the objects in the background thread? Can I at least call ObjectMeshStatic::create and Unigine::ObjectMeshCluster::create on a background thread? Maybe I can lock/unlock them so no other threads are calling those?

Many thanks,
Adrian

Link to comment

Adrian,

It's hard to suggest anything without seeing the actual code and use-case. Almost all API methods (except maybe FileSystem are not thread-safe even with locks / unlocks and other stuff) and trying to make them work is pretty hard.

If we can see the actual test scene we can try to at least a) reproduce these issues that you mentioned earlier and b) make things a little better on API side (or maybe suggest completely different approach that might work in your case) c) fix some bugs (if we can find any) that potentially can lead to these unwanted behavior.

Thanks!

How to submit a good bug report
---
FTP server for test scenes and user uploads:

Link to comment

Hi Silent,

Sharing a repro case is not an option for me right now (bound to very stringent confidentiality terms, including used data). If I am not able to figure this out by myself, I may be able to share code snippets but without any data or full exe.

One good news though: about D3D11MeshStatic::d3d11_surfaces becoming blank, after debugging, break by value change etc, finally tracked this down to this code from RenderStreamMeshStatic::update:

        if (isLoaded() && engine.frame - frame > RENDER_MANAGER_MESH_FRAMES && memory_usage > memory_limit)
        {
            memory_usage -= getVideoMemoryUsage();
            clear();
        }

Basically I am going over render_manager_meshes_memory (default 15%) and the engine starts to remove my data until is under this limit. I find it strange, at least I should have an warning on the console. Anyway, probably, this is fine when using Unigine meshes that are streamed from disk (restreamed when needed?), but for my case, in which I have many objects clusters created, I would rather end up without video memory and have a crash (our hardware solution is very custom, we can just slot in more video memory). Also, what happens inside D3D11MeshStatic::renderInstancedSurface? It seems that in this case it is just crashing since it tries to render surfaces that are removed from memory. Do I need to mark the meshes in use somehow, so Unigine doesn't try to unload them?

Regards,

Adrian

Edited by adrian.licuriceanu
Link to comment

Just my personal feeling: trying to implement your own graphics-related multi-threading by tricking the Unigine engine is a guaranty for trouble...even if you would get a stable configuration with current version, there are excellent chances that everything breaks down with future upgrades or even with just other client hardware showing different threading performance....you should better rethink the general approach and try to find a different solution

Link to comment

Hello Ulf, 

I wish I could just use stock Unigine and be done with it. I cannot give you more specific details, but for this project alone we are required to read from a specific database format, somewhere on a server, with lots of node caches along the way, while Unigine rendering clients are among many others using the same central repository. The specs asked us to read directly from this format, without any intermediary caches, so we cannot use Unigine formats at all, but to fill its data in background threads. The hardware is very specific and done for this project alone. Update to Unigine is out of the question, at least for short term. 

And this is only one of multiple projects using Unigine. For others, we have heavily modified Unigine, where only the deferred shading stage, shadowing and postprocessing are being used. Tools, mesh formats, ocean etc, are custom made. For this project, at least in the first stage, I would rather avoid to go on custom formats, and just fill Unigine meshes with data in background threads.

Kind Regards,

Adrian

Link to comment

Sounds like such kind of a project...:-)

All simulators can be used individually, or in combination for joint mission exercises. The bridge simulators will leverage the Open Geospatial Consortium’s Common Database (OGC CDB) for high-fidelity synthetic environments, as well as data interfaces to industry-standard networking protocols such as the High-Level Architecture (HLA) for integration with other training devices, 

Link to comment

ok, just kidding...without any in-depth knowledge of your specific requirements and just brainstorming: have you evaluated using WorldLayer nodes for getting things done in the background ?

https://developer.unigine.com/en/docs/2.7.1/objects/worlds/world_layer/

Maybe its possible to somehow convert your mesh data on the fly to some sort of WorldLayer object, so the engine takes care of proper interaction between the background data loading and main thread graphics resource preparation and uploading...just an idea

 

Link to comment

Hi Ulf, thanks for world layer, will have a look. But from what understand this just loads a hierarchy of nodes that point to data already in Unigine format. I don't know if we can afford two types of streaming: one to read original data, convert and place it (on the fly) to Unigine formats, and then another streaming using World Layer. But this is just my initial impression, I will look over it in more detail.

Link to comment

Hi Adrian, the unqualified first-thought idea for trying to use WorldLayer is the fact that here the critical coordination between background and main thread is already handled by the Unigine engine internally including some per-frame time-budget resource preparation limits.

Nevertheless this approach of course might also have all kind of issues and no-go's, but it's at least some idea to test 

Link to comment
×
×
  • Create New...