///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: MultithreadingExample.cpp
// Language: C++
//
// Copyright (c) 2019 Microsoft. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////
//
// #Description#
//
// This example demonstrates how to use Simplygon in a multithreaded
// environment, to utilize all cores of a multicore system. The example
// spawns 100 threads, but keeps the number of concurrent threads at or
// below 12.
//
///////////////////////////////////////////////////////////////////////////
#include "../Common/Example.h"
/////////////////////////////////////////////////////////////////////
// Main settings
// total number of threads to spawn
const int num_threads = 100;
// number of threads to run concurrently
const int num_concurrent_threads = 12;
/////////////////////////////////////////////////////////////////////
// Thread-handling declarations
rhandle ReCreateCriticalSection();
void ReEnterCriticalSection( rhandle cs );
void ReLeaveCriticalSection( rhandle cs );
void ReDeleteCriticalSection( rhandle cs );
rhandle ReCreateThread( void( *start_address )(void *), void * arg );
void ReSleep( int milliseconds );
/////////////////////////////////////////////////////////////////////
// Example main code
int spawned_threads = 0;
rid threadids[num_threads];
bool threaddoneflags[num_threads];
IGeometryDataCollection *src_geoms;
rhandle common_lock;
void log_string( int threadid, const char *str )
{
printf( "Thread (%d): %s\n", threadid, str );
}
// This function copies from the source geometries list a
// geometry that will be reduced within the thread processing.
// To keep the system thread safe, the function must lock the source
// geometries list using a critical section, during the copy procedure.
// Apart from this lock, no other thread collision has
// to be accounted for, as all other data is thread-local.
spGeometryData ThreadSafeCopyGeometryFromSourceGeometriesList( int threadid )
{
// shared object: src_geoms, lock the common lock
ReEnterCriticalSection( common_lock );
// the geometry copied is based on the index of the geometry
int index = (threadid % src_geoms->GetItemCount());
// get the source geometry, copy to new object
spGeometryData threadgeom = sg->CreateGeometryData();
threadgeom->DeepCopy( src_geoms->GetGeometryData( src_geoms->GetItem( index ) ), true );
// Remove material ids. For this example the materials are not interesting,
// but if they were important, the materials from the material table of the
// original scene would have to be inserted into the material table of the
// scene created for export below. This because material ids in a scene
// geometry must have a corresponding material in the scene material table.
threadgeom->RemoveMaterialIds();
// done with the shared object
ReLeaveCriticalSection( common_lock );
return threadgeom;
}
// This function runs the Simplygon reduction processing on the thread-local
// geometry data object.
void RunReductionProcessing( spGeometryData geom, float triangle_ratio )
{
// Create the reduction processor. Set the geometry that is to be processed
spReductionProcessor reducer = sg->CreateReductionProcessor();
spScene tempScene = sg->CreateScene();
tempScene->GetRootNode()->CreateChildMesh( geom );
reducer->SetScene( tempScene );
///////////////////////////////////////////////////
//
// Set the Repair Settings. Current settings will mean that all visual gaps will remain in the geometry and thus
// hinder the reduction on geometries that contains gaps, holes and tjunctions.
spRepairSettings repair_settings = reducer->GetRepairSettings();
// Only vertices that actually share the same position will be welded together
repair_settings->SetWeldDist( 0.0f );
// Only t-junctions with no actual visual distance will be fixed.
repair_settings->SetTjuncDist( 0.0f );
///////////////////////////////////////////////////
//
// Set the Reduction Settings.
spReductionSettings reduction_settings = reducer->GetReductionSettings();
// Will reduce to 1/4 of the original trianglecount.
reduction_settings->SetTriangleRatio( triangle_ratio );
///////////////////////////////////////////////////
//
// Set the Normal Calculation Settings.
spNormalCalculationSettings normal_settings = reducer->GetNormalCalculationSettings();
// Will completely recalculate the normals.
normal_settings->SetReplaceNormals( true );
normal_settings->SetHardEdgeAngleInRadians( 3.14159f * 90.f / 180.0f );
// Run the process
reducer->RunProcessing();
}
// This is the per-thread processing function. It copies
// a geometry from the source geometry list, processes it, and
// stores the result into a wavefront .obj file.
void ThreadProc( void *arg )
{
int threadid = *((int*)arg);
//log_string(threadid,"Thread started...\n");
// Copy the geometry, lock the common lock, as the src_geoms object is shared
spGeometryData threadgeom = ThreadSafeCopyGeometryFromSourceGeometriesList( threadid );
//log_string(threadid,"Run reduction...\n");
// Run reduction.
// Reduce the geometry to 25% of the original number of triangles.
RunReductionProcessing( threadgeom, 0.25f );
//log_string(threadid,"Reduction done...\n");
// Create scene for export
spScene scene = sg->CreateScene();
spSceneMesh sceneMesh = sg->CreateSceneMesh();
sceneMesh->SetGeometry( threadgeom );
scene->GetRootNode()->AddChild( sceneMesh );
// Store in wavefront file
spWavefrontExporter exp = sg->CreateWavefrontExporter();
std::string path = GetExecutablePath() + "output_thread_" + std::to_string( threadid ) + ".obj";
exp->SetExportFilePath( path.c_str() );
exp->SetScene( scene );
exp->RunExport();
exp = NULL;
//log_string(threadid,"Thread done...\n");
threaddoneflags[threadid] = true;
}
// This function spawns a new thread using the ReCreateThread function.
void SpawnNewThread()
{
printf( "Main thread: Spawning thread #%d.\n", spawned_threads );
threaddoneflags[spawned_threads] = false;
threadids[spawned_threads] = spawned_threads;
ReCreateThread( &ThreadProc, &threadids[spawned_threads] );
++spawned_threads;
}
// This function keeps track of the number of running threads.
int CountRunningThreads()
{
int RunningThreads = 0;
for( int i = 0; i < spawned_threads; ++i )
{
if( !threaddoneflags[i] )
{
++RunningThreads;
}
}
return RunningThreads;
}
// This function checks if all threads have been spawned and
// have also completed.
bool AreAllThreadsDone()
{
// if not all have been started, we are not done
if( spawned_threads < num_threads )
{
return false;
}
// if none is running, we are done
if( CountRunningThreads() == 0 )
return true;
return false;
}
// This is the example main thread function, that only spawns new threads
// as long as there are still threads to be run, and the number of
// concurrent threads are less than "num_concurrent_threads", by default 4.
void RunExample()
{
common_lock = ReCreateCriticalSection();
printf( "Loading Wavefront OBJ...\n" );
spWavefrontImporter is = sg->CreateWavefrontImporter();
std::string assetPath = GetAssetPath();
is->SetImportFilePath( (assetPath + "testmodel.obj").c_str() );
is->SetExtractGroups( true );
if( !is->RunImport() )
{
ExitWithError( "Could not open input test file" );
}
spScene scene = is->GetScene();
int selectionSetAllSceneMeshesID = scene->SelectNodes( "ISceneMesh" );
spSelectionSet selectionSetAllSceneMeshes = scene->GetSelectionSetTable()->GetSelectionSet( selectionSetAllSceneMeshesID );
spGeometryDataCollection geoms = sg->CreateGeometryDataCollection();
for( uint i = 0; i < selectionSetAllSceneMeshes->GetItemCount(); ++i )
{
// Get the node from the selection set and cast it to an ISceneMesh
spSceneMesh sceneMesh = Cast<ISceneMesh>( scene->GetNodeByGUID( selectionSetAllSceneMeshes->GetItem( i ) ) );
// Get the geometry from the scene mesh and insert into geometry list for processing
spGeometryData geom = sceneMesh->GetGeometry();
geoms->AddGeometryData( geom );
}
src_geoms = geoms;
while( !AreAllThreadsDone() )
{
while( (spawned_threads < num_threads) &&
(CountRunningThreads() < num_concurrent_threads) )
{
SpawnNewThread();
}
ReSleep( 10 );
}
printf( "All threads done.\n" );
ReDeleteCriticalSection( common_lock );
}
/////////////////////////////////////////////////////////////////////
// Main function with startup and shutdown code
int main( int argc, char* argv[] )
{
try
{
// Initiate
InitExample();
// Set per-process core limit, so the processes aren't competing for internal multithreading
sg->SetGlobalSetting("LogicalCoreLimit", 1);
// Run the example code
RunExample();
// Deinitialize the SDK
DeinitExample();
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl;
return -1;
}
return 0;
}
/////////////////////////////////////////////////////////////////////
// Platform-specific thread handling
#ifdef _WIN32
// Windows implementation
rhandle ReCreateCriticalSection()
{
CRITICAL_SECTION *p = new CRITICAL_SECTION;
InitializeCriticalSection( p );
return (rhandle)p;
}
void ReEnterCriticalSection( rhandle cs )
{
CRITICAL_SECTION *p = (CRITICAL_SECTION*)cs;
EnterCriticalSection( p );
}
void ReLeaveCriticalSection( rhandle cs )
{
CRITICAL_SECTION *p = (CRITICAL_SECTION*)cs;
LeaveCriticalSection( p );
}
void ReDeleteCriticalSection( rhandle cs )
{
CRITICAL_SECTION *p = (CRITICAL_SECTION*)cs;
DeleteCriticalSection( p );
delete p;
}
rhandle ReCreateThread( void( *start_address )(void *), void * arg )
{
return (rhandle)_beginthread( start_address, 0, arg );
}
void ReSleep( int milliseconds )
{
Sleep( milliseconds );
}
#elif defined(__linux__) || defined(__APPLE__)
// Linux implementation
rhandle ReCreateCriticalSection()
{
pthread_mutex_t *mutex = new pthread_mutex_t(); // Mutex
pthread_mutexattr_t mutexattr; // Mutex attribute variable
pthread_mutexattr_init( &mutexattr );
// Set the mutex as a recursive mutex
#if defined(__linux__)
pthread_mutexattr_settype( &mutexattr, PTHREAD_MUTEX_RECURSIVE_NP );
#elif defined(__APPLE__)
pthread_mutexattr_settype( &mutexattr, PTHREAD_MUTEX_RECURSIVE );
#endif
// create the mutex with the attributes set
pthread_mutex_init( mutex, &mutexattr );
// After initializing the mutex, the thread attribute can be destroyed
pthread_mutexattr_destroy( &mutexattr );
return (rhandle)mutex;
}
void ReEnterCriticalSection( rhandle cs )
{
pthread_mutex_t *mutex = (pthread_mutex_t*)cs;
pthread_mutex_lock( mutex );
}
void ReLeaveCriticalSection( rhandle cs )
{
pthread_mutex_t *mutex = (pthread_mutex_t*)cs;
pthread_mutex_unlock( mutex );
}
void ReDeleteCriticalSection( rhandle cs )
{
pthread_mutex_t *mutex = (pthread_mutex_t*)cs;
pthread_mutex_destroy( mutex );
delete mutex;
}
typedef void( *p_thread_start )(void*);
struct thread_info
{
p_thread_start start_address;
void *arg;
};
void *thread_entry_func( void *args )
{
thread_info *info = (thread_info*)args;
(*info->start_address)(info->arg);
delete info;
return NULL;
}
rhandle ReCreateThread( void( *start_address )(void *), void * arg )
{
thread_info *info = new thread_info();
info->start_address = start_address;
info->arg = arg;
pthread_t threadid;
pthread_attr_t threadAttr;
// initialize the thread attribute
// Set the stack size of the thread
// Set thread to detached state.
pthread_attr_init( &threadAttr );
pthread_attr_setstacksize( &threadAttr, 120 * 1024 );
pthread_attr_setdetachstate( &threadAttr, PTHREAD_CREATE_DETACHED );
// Create the threads
pthread_create( &threadid, &threadAttr, &thread_entry_func, info );
// Destroy the thread attributes
pthread_attr_destroy( &threadAttr );
return (rhandle)threadid;
}
void ReSleep( int milliseconds )
{
usleep( milliseconds * 1000 );
}
#endif