adrian.licuriceanu Posted July 25, 2018 Share Posted July 25, 2018 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
honya Posted July 25, 2018 Share Posted July 25, 2018 (edited) 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 July 25, 2018 by honya Link to comment
adrian.licuriceanu Posted July 25, 2018 Author Share Posted July 25, 2018 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
unclebob Posted July 27, 2018 Share Posted July 27, 2018 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
adrian.licuriceanu Posted July 27, 2018 Author Share Posted July 27, 2018 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
silent Posted July 30, 2018 Share Posted July 30, 2018 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: ftp://files.unigine.com user: upload password: 6xYkd6vLYWjpW6SN Link to comment
adrian.licuriceanu Posted July 30, 2018 Author Share Posted July 30, 2018 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
silent Posted July 31, 2018 Share Posted July 31, 2018 Hi Adrian, setParent is not thead-safe (even if parent is not yet visible), so use it on your own risk :) How to submit a good bug report --- FTP server for test scenes and user uploads: ftp://files.unigine.com user: upload password: 6xYkd6vLYWjpW6SN Link to comment
adrian.licuriceanu Posted July 31, 2018 Author Share Posted July 31, 2018 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
silent Posted August 1, 2018 Share Posted August 1, 2018 AFAIK, setWorldTransform is also not a thead-safe function. How to submit a good bug report --- FTP server for test scenes and user uploads: ftp://files.unigine.com user: upload password: 6xYkd6vLYWjpW6SN Link to comment
adrian.licuriceanu Posted August 1, 2018 Author Share Posted August 1, 2018 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
silent Posted August 2, 2018 Share Posted August 2, 2018 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: ftp://files.unigine.com user: upload password: 6xYkd6vLYWjpW6SN Link to comment
adrian.licuriceanu Posted August 3, 2018 Author Share Posted August 3, 2018 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
silent Posted August 3, 2018 Share Posted August 3, 2018 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: ftp://files.unigine.com user: upload password: 6xYkd6vLYWjpW6SN Link to comment
adrian.licuriceanu Posted August 3, 2018 Author Share Posted August 3, 2018 (edited) 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 August 3, 2018 by adrian.licuriceanu Link to comment
ulf.schroeter Posted August 4, 2018 Share Posted August 4, 2018 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
adrian.licuriceanu Posted August 6, 2018 Author Share Posted August 6, 2018 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
ulf.schroeter Posted August 6, 2018 Share Posted August 6, 2018 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
ulf.schroeter Posted August 6, 2018 Share Posted August 6, 2018 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
ulf.schroeter Posted August 6, 2018 Share Posted August 6, 2018 https://developer.unigine.com/forum/topic/2240-unigine-sdk-2013-10-01-improved-background-loading-updated-documentation-site/ 1 Link to comment
adrian.licuriceanu Posted August 7, 2018 Author Share Posted August 7, 2018 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
ulf.schroeter Posted August 7, 2018 Share Posted August 7, 2018 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
Recommended Posts