<< Click to Display Table of Contents >> Navigation: Simplygon 7.1 examples > MultithreadingExample.cpp |
///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: MultithreadingExample.cpp
// Language: C++
//
// Copyright (c) 2015 Donya Labs AB. 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[] )
{
// Initiate
InitExample();
// Run the example code
RunExample();
// Deinitialize the SDK
DeinitExample();
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