Jump to content

Reading Objects from other Thread.


photo

Recommended Posts

Hello,

 

I want to read data from objects of classes Like Mesh or ObjectMeshStatic in another thread.

But when I do this the microprofiler is not working anymore.

Is it safe to use read only methods on Unigine classes from another thread?

 

Regards

Sebastian

 

Link to comment

Dear @sebastian.vesenmayer,

The question looks very dangerous situation.

How I will solve this?

UNIGINE main thread is running.

Prepare a data structure you want to access from another thread. Probably vertices data.

Write a callback on Render class for end of frame CALLBACK_END. Copy required data from Mesh or any UNIGINE object to your data structure as required.

You are free to read this data from any other thread with at least one frame latency. So you need to hook it correctly for your workflow. 

BINGO, It works. Only thing is we are creating redundancy for data. 

Rohit

Link to comment

@rohit.gonsalves you are right, that it is a dangerous situation because nobody knows what the engine does during this time and if objects are still alive.

And yes I want to avoid data redundancy, because i need the complete node hierarchy, positions and orientations.

Thanks

Link to comment

I want to access Meshes and Matrices from Node Trees to reduce draw calls when objects are not animated anymore in a separate thread. The merged data will be copied from main thread when it is ready.

Do I need own classes for matix vector multiplication or can I use Unigine classes for this in an own thread?

 

Edited by sebastian.vesenmayer
Link to comment
m_optimizerThread=std::make_unique<std::thread>([this]
			{				
				m_mergedMaterials.clear();
				m_mergedMeshes.clear();
				Unigine::Vector<Unigine::NodePtr> hierarchy;
				m_rootNode->getHierarchy(hierarchy);
				for (auto& node : hierarchy)
				{
					if (node->getType() == Unigine::Node::TYPE::OBJECT_MESH_STATIC && node->isEnabled())
					{
						//create transformation matrix for the mesh
						auto transformNode = node;
						auto meshMatrix = transformNode->getTransform();
						while (transformNode = transformNode->getParent())
						{
							if (transformNode->getParent())
							{
								meshMatrix = transformNode->getTransform() * meshMatrix;
							}
							if (m_aboutToDestroy)
							{
								return;
							}
						}

						auto objectMeshStatic = Unigine::static_ptr_cast<Unigine::ObjectMeshStatic>(node);
						
						//Check for material, add it to list of materials and get index for referencing
						if (objectMeshStatic->getNumSurfaces() < 1)
						{
							continue;
						}
						auto usedMaterial = std::string(objectMeshStatic->getMaterial(0)->getName());
						auto result = std::find(m_mergedMaterials.begin(), m_mergedMaterials.end(), usedMaterial);
						auto index = m_mergedMaterials.size();
						MergedMesh* mergedMesh = nullptr;
						if (result != m_mergedMaterials.end())
						{
							index = std::distance(m_mergedMaterials.begin(), result);
							mergedMesh = &(m_mergedMeshes[index]);
						}
						else
						{
							m_mergedMaterials.push_back(usedMaterial);
							m_mergedMeshes.emplace_back();
							mergedMesh = &(m_mergedMeshes.back());
						}
						
						//get mesh data of this node and append it transformed to the combined mesh
						Unigine::MeshPtr toCopyMesh = Unigine::Mesh::create();
						if (objectMeshStatic->getMesh(toCopyMesh))
						{
							for (int i = 0, j = toCopyMesh->getNumSurfaces(); i < j; ++i)
							{
								auto vertices = toCopyMesh->getVertices(i);
								auto texCoords = toCopyMesh->getTexCoords0(i);
								auto tangents = toCopyMesh->getTangents(i);
								auto indices = toCopyMesh->getCIndices(i);

								if (mergedMesh->surface.size() <= i)
								{
									mergedMesh->surface.emplace_back();
									mergedMesh->surface.back().minVisibleDistance= objectMeshStatic->getMinVisibleDistance(i);
									mergedMesh->surface.back().maxVisibleDistance= objectMeshStatic->getMaxVisibleDistance(i);
								}

								auto& surface = mergedMesh->surface[i];
								auto startIndex = surface.mergedVertices.size();
								for (int k = 0, l = vertices.size(); k < l; ++k)
								{
									auto vertex = meshMatrix * vertices[k];
									surface.mergedVertices.push_back(vertices[k]);
									surface.mergedTexCoords.push_back(texCoords[k]);
									surface.mergedTangents.push_back(tangents[k]);

									if (m_aboutToDestroy)
									{
										return;
									}
								}

								for (int k = 0, l = indices.size(); k < l; ++k)
								{
									surface.mergedIndices.push_back(startIndex + indices[k]);

									if (m_aboutToDestroy)
									{
										return;
									}
								}
							}
						}
					}
					if (m_aboutToDestroy)
					{
						return;
					}
				}

				//check for object destruction
				if (m_aboutToDestroy)
					return;
				m_processingOptimization = false;
				m_optimizationReady = true;
			}
			);

That is what I want to do.

Link to comment

Hello Sebastian,

It's not clear which call is problematic here. It also depends on what the other threads do.

The general recommendation is not to communicate with nodes, objects, materials etc. when the main loop is executed. Especially when Engine::swap is executed because this is the place where nodes deferred deletion occurs. (https://developer.unigine.com/en/docs/2.14.1/code/fundamentals/thread_safety/?rlang=cpp#dependent_objects  )

That's why we provide CPUShader class which is always synchornized with Engine::swap (you can try it https://developer.unigine.com/en/docs/2.14.1/api/library/rendering/class.cpushader?rlang=cpp#runAsync_int_void )

It's totally fine to work with Mesh and mat4 classes in the async thread though.

You may go to the gray zone and try to read data in the async thread for the sake of performance. But in that case every scenario is unique and we need to localize which call is problematic. The fact that microprofile doesn't work signals that something really bad happened.
A minimal reproducer can be useful.

Link to comment

When I read this correctly from the documentation, main thread waits for cpu shaders to finish and can block the main thread. Not what I want.

Looping over hierarchy may be the problem, so I guess I need to copy mesh data from the node tree first and continue in another thread with the calculation.

Unigine::Vector<Unigine::NodePtr> hierarchy;
				m_rootNode->getHierarchy(hierarchy);
				

If it is safe to use Unigine::Math classes in another thread there will be no problem I guess.

Link to comment

Some other interaction with nodes may be problematic too. Obvious example

objectMeshStatic->getMesh(toCopyMesh)

if objectMeshStatic is being deleted in the main thread at the moment

Link to comment

I have the implementation in the main thread now and I am going to implement another threaded codepath, when models get to big I am going to switch to the threaded path because of the high amount on matrix/vertex calculations.

I have another question on the tangents. I don't know exactly how to transform them, when I bake transformations into a mesh. How should I apply the matrix on the tangents quaternion?

Thanks

Link to comment
×
×
  • Create New...