Browse Source

Initial work queue & multithreading support.

Lasse Öörni 14 years ago
parent
commit
c8089b1687

+ 12 - 8
Engine/Audio/Audio.cpp

@@ -215,12 +215,15 @@ public:
                 {
                 {
                     // Mix sound to locked positions
                     // Mix sound to locked positions
                     {
                     {
-                        MutexLock Lock(owner_->GetMutex());
+                        SpinLock& lock = owner_->GetLock();
+                        lock.Acquire();
                         
                         
                         if (bytes1)
                         if (bytes1)
                             owner_->MixOutput(ptr1, bytes1 / sampleSize_);
                             owner_->MixOutput(ptr1, bytes1 / sampleSize_);
                         if (bytes2)
                         if (bytes2)
                             owner_->MixOutput(ptr2, bytes2 / sampleSize_);
                             owner_->MixOutput(ptr2, bytes2 / sampleSize_);
+                        
+                        lock.Release();
                     }
                     }
                     
                     
                     // Unlock buffer and update write cursor
                     // Unlock buffer and update write cursor
@@ -361,8 +364,6 @@ void Audio::Update(float timeStep)
 {
 {
     PROFILE(UpdateAudio);
     PROFILE(UpdateAudio);
     
     
-    MutexLock Lock(audioMutex_);
-    
     // Update in reverse order, because sound sources might remove themselves
     // Update in reverse order, because sound sources might remove themselves
     for (unsigned i = soundSources_.Size() - 1; i < soundSources_.Size(); --i)
     for (unsigned i = soundSources_.Size() - 1; i < soundSources_.Size(); --i)
         soundSources_[i]->Update(timeStep);
         soundSources_[i]->Update(timeStep);
@@ -454,18 +455,20 @@ float Audio::GetMasterGain(SoundType type) const
 
 
 void Audio::AddSoundSource(SoundSource* channel)
 void Audio::AddSoundSource(SoundSource* channel)
 {
 {
-    MutexLock Lock(audioMutex_);
-    
+    audioLock_.Acquire();
     soundSources_.Push(channel);
     soundSources_.Push(channel);
+    audioLock_.Release();
 }
 }
 
 
 void Audio::RemoveSoundSource(SoundSource* channel)
 void Audio::RemoveSoundSource(SoundSource* channel)
 {
 {
-    MutexLock Lock(audioMutex_);
-    
     PODVector<SoundSource*>::Iterator i = soundSources_.Find(channel);
     PODVector<SoundSource*>::Iterator i = soundSources_.Find(channel);
     if (i != soundSources_.End())
     if (i != soundSources_.End())
+    {
+        audioLock_.Acquire();
         soundSources_.Erase(i);
         soundSources_.Erase(i);
+        audioLock_.Release();
+    }
 }
 }
 
 
 #ifdef USE_OPENGL
 #ifdef USE_OPENGL
@@ -475,8 +478,9 @@ int AudioCallback(const void *inputBuffer, void *outputBuffer, unsigned long fra
     Audio* audio = static_cast<Audio*>(userData);
     Audio* audio = static_cast<Audio*>(userData);
     
     
     {
     {
-        MutexLock Lock(audio->GetMutex());
+        audioLock_.Acquire();
         audio->MixOutput(outputBuffer, framesPerBuffer);
         audio->MixOutput(outputBuffer, framesPerBuffer);
+        audioLock_.Release();
     }
     }
     
     
     return 0;
     return 0;

+ 5 - 5
Engine/Audio/Audio.h

@@ -25,9 +25,9 @@
 
 
 #include "ArrayPtr.h"
 #include "ArrayPtr.h"
 #include "AudioDefs.h"
 #include "AudioDefs.h"
-#include "Mutex.h"
 #include "Object.h"
 #include "Object.h"
 #include "Quaternion.h"
 #include "Quaternion.h"
+#include "SpinLock.h"
 #include "Thread.h"
 #include "Thread.h"
 
 
 class AudioImpl;
 class AudioImpl;
@@ -89,8 +89,8 @@ public:
     void AddSoundSource(SoundSource* soundSource);
     void AddSoundSource(SoundSource* soundSource);
     /// Remove a sound source. Called by SoundSource.
     /// Remove a sound source. Called by SoundSource.
     void RemoveSoundSource(SoundSource* soundSource);
     void RemoveSoundSource(SoundSource* soundSource);
-    /// Return audio thread mutex.
-    Mutex& GetMutex() { return audioMutex_; }
+    /// Return audio thread spinlock. Note: it is not re-entrant.
+    SpinLock& GetLock() { return audioLock_; }
     /// Return sound type specific gain multiplied by master gain.
     /// Return sound type specific gain multiplied by master gain.
     float GetSoundSourceMasterGain(SoundType type) const { return masterGain_[SOUND_MASTER] * masterGain_[type]; }
     float GetSoundSourceMasterGain(SoundType type) const { return masterGain_[SOUND_MASTER] * masterGain_[type]; }
     
     
@@ -107,8 +107,8 @@ private:
     void* stream_;
     void* stream_;
     /// Clipping buffer for mixing.
     /// Clipping buffer for mixing.
     SharedArrayPtr<int> clipBuffer_;
     SharedArrayPtr<int> clipBuffer_;
-    /// Audio thread mutex.
-    Mutex audioMutex_;
+    /// Audio thread spinlock.
+    SpinLock audioLock_;
     /// Sample size.
     /// Sample size.
     unsigned sampleSize_;
     unsigned sampleSize_;
     /// Clip buffer size in samples.
     /// Clip buffer size in samples.

+ 9 - 3
Engine/Audio/SoundSource.cpp

@@ -156,8 +156,10 @@ void SoundSource::Play(Sound* sound)
     // If sound source is currently playing, have to lock the audio mutex
     // If sound source is currently playing, have to lock the audio mutex
     if (position_)
     if (position_)
     {
     {
-        MutexLock Lock(audio_->GetMutex());
+        SpinLock& lock = audio_->GetLock();
+        lock.Acquire();
         PlayLockless(sound);
         PlayLockless(sound);
+        lock.Release();
     }
     }
     else
     else
         PlayLockless(sound);
         PlayLockless(sound);
@@ -192,8 +194,10 @@ void SoundSource::Stop()
     // If sound source is currently playing, have to lock the audio mutex
     // If sound source is currently playing, have to lock the audio mutex
     if (position_)
     if (position_)
     {
     {
-        MutexLock Lock(audio_->GetMutex());
+        SpinLock& lock = audio_->GetLock();
+        lock.Acquire();
         StopLockless();
         StopLockless();
+        lock.Release();
     }
     }
     
     
     // Free the compressed sound decoder now if any
     // Free the compressed sound decoder now if any
@@ -244,8 +248,10 @@ void SoundSource::SetPlayPosition(signed char* pos)
     if (!audio_ || !sound_)
     if (!audio_ || !sound_)
         return;
         return;
     
     
-    MutexLock Lock(audio_->GetMutex());
+    SpinLock& lock = audio_->GetLock();
+    lock.Acquire();
     SetPlayPositionLockless(pos);
     SetPlayPositionLockless(pos);
+    lock.Release();
 }
 }
 
 
 void SoundSource::PlayLockless(Sound* sound)
 void SoundSource::PlayLockless(Sound* sound)

+ 4 - 2
Engine/Audio/SoundSource3D.cpp

@@ -95,8 +95,10 @@ void SoundSource3D::CalculateAttenuation()
     {
     {
         Vector3 relativePos(audio_->GetListenerRotation().Inverse() * (GetWorldPosition() - audio_->GetListenerPosition()));
         Vector3 relativePos(audio_->GetListenerRotation().Inverse() * (GetWorldPosition() - audio_->GetListenerPosition()));
         float distance = Clamp(relativePos.Length() - nearDistance_, 0.0f, interval);
         float distance = Clamp(relativePos.Length() - nearDistance_, 0.0f, interval);
+        float attenuation = powf(1.0f - distance / interval, rolloffFactor_);
+        float panning = relativePos.Normalized().x_;
         
         
-        attenuation_ = powf(1.0f - distance / interval, rolloffFactor_);
-        panning_ = relativePos.Normalized().x_;
+        attenuation_ = attenuation;
+        panning_ = panning;
     }
     }
 }
 }

+ 6 - 0
Engine/Container/HashBase.h

@@ -51,6 +51,12 @@ class HashIteratorBase
 {
 {
 public:
 public:
     /// Construct.
     /// Construct.
+    HashIteratorBase() :
+        ptr_(0)
+    {
+    }
+    
+    /// Construct with a node pointer.
     explicit HashIteratorBase(HashNodeBase* ptr) :
     explicit HashIteratorBase(HashNodeBase* ptr) :
         ptr_(ptr)
         ptr_(ptr)
     {
     {

+ 10 - 0
Engine/Container/HashMap.h

@@ -88,6 +88,11 @@ public:
     {
     {
     public:
     public:
         /// Construct.
         /// Construct.
+        Iterator()
+        {
+        }
+        
+        /// Construct with a node pointer.
         Iterator(Node* ptr) :
         Iterator(Node* ptr) :
             HashIteratorBase(ptr)
             HashIteratorBase(ptr)
         {
         {
@@ -113,6 +118,11 @@ public:
     {
     {
     public:
     public:
         /// Construct.
         /// Construct.
+        ConstIterator()
+        {
+        }
+        
+        /// Construct with a node pointer.
         ConstIterator(Node* ptr) :
         ConstIterator(Node* ptr) :
             HashIteratorBase(ptr)
             HashIteratorBase(ptr)
         {
         {

+ 10 - 0
Engine/Container/HashSet.h

@@ -59,6 +59,11 @@ public:
     {
     {
     public:
     public:
         /// Construct.
         /// Construct.
+        Iterator()
+        {
+        }
+        
+        /// Construct with a node pointer.
         Iterator(Node* ptr) :
         Iterator(Node* ptr) :
             HashIteratorBase(ptr)
             HashIteratorBase(ptr)
         {
         {
@@ -84,6 +89,11 @@ public:
     {
     {
     public:
     public:
         /// Construct.
         /// Construct.
+        ConstIterator()
+        {
+        }
+        
+        /// Construct with a node pointer.
         ConstIterator(Node* ptr) :
         ConstIterator(Node* ptr) :
             HashIteratorBase(ptr)
             HashIteratorBase(ptr)
         {
         {

+ 10 - 0
Engine/Container/List.h

@@ -57,6 +57,11 @@ public:
     {
     {
     public:
     public:
         /// Construct.
         /// Construct.
+        Iterator()
+        {
+        }
+        
+        /// Construct with a node pointer.
         explicit Iterator(Node* ptr) :
         explicit Iterator(Node* ptr) :
             ListIteratorBase(ptr)
             ListIteratorBase(ptr)
         {
         {
@@ -82,6 +87,11 @@ public:
     {
     {
     public:
     public:
         /// Construct.
         /// Construct.
+        ConstIterator()
+        {
+        }
+        
+        /// Construct with a node pointer.
         explicit ConstIterator(Node* ptr) :
         explicit ConstIterator(Node* ptr) :
             ListIteratorBase(ptr)
             ListIteratorBase(ptr)
         {
         {

+ 6 - 0
Engine/Container/ListBase.h

@@ -47,6 +47,12 @@ class ListIteratorBase
 {
 {
 public:
 public:
     /// Construct.
     /// Construct.
+    ListIteratorBase() :
+        ptr_(0)
+    {
+    }
+    
+    /// Construct with a node pointer.
     explicit ListIteratorBase(ListNodeBase* ptr) :
     explicit ListIteratorBase(ListNodeBase* ptr) :
         ptr_(ptr)
         ptr_(ptr)
     {
     {

+ 10 - 0
Engine/Container/Map.h

@@ -89,6 +89,11 @@ public:
     {
     {
     public:
     public:
         /// Construct.
         /// Construct.
+        Iterator()
+        {
+        }
+        
+        /// Construct with a node pointer.
         Iterator(Node* ptr) :
         Iterator(Node* ptr) :
             TreeIteratorBase(ptr)
             TreeIteratorBase(ptr)
         {
         {
@@ -114,6 +119,11 @@ public:
     {
     {
     public:
     public:
         /// Construct.
         /// Construct.
+        ConstIterator()
+        {
+        }
+        
+        /// Construct with a node pointer.
         ConstIterator(Node* ptr) :
         ConstIterator(Node* ptr) :
             TreeIteratorBase(ptr)
             TreeIteratorBase(ptr)
         {
         {

+ 11 - 1
Engine/Container/Set.h

@@ -59,7 +59,12 @@ public:
     class Iterator : public TreeIteratorBase
     class Iterator : public TreeIteratorBase
     {
     {
     public:
     public:
-        // Construct.
+        /// Construct.
+        Iterator()
+        {
+        }
+        
+        /// Construct with a node pointer.
         Iterator(Node* ptr) :
         Iterator(Node* ptr) :
             TreeIteratorBase(ptr)
             TreeIteratorBase(ptr)
         {
         {
@@ -85,6 +90,11 @@ public:
     {
     {
     public:
     public:
         /// Construct.
         /// Construct.
+        ConstIterator()
+        {
+        }
+        
+        /// Construct with a node pointer.
         ConstIterator(Node* ptr) :
         ConstIterator(Node* ptr) :
             TreeIteratorBase(ptr)
             TreeIteratorBase(ptr)
         {
         {

+ 1 - 1
Engine/Container/TreeBase.h

@@ -69,7 +69,7 @@ public:
     }
     }
     
     
     /// Construct with a node pointer.
     /// Construct with a node pointer.
-    TreeIteratorBase(TreeNodeBase* ptr) :
+    explicit TreeIteratorBase(TreeNodeBase* ptr) :
         ptr_(ptr),
         ptr_(ptr),
         prev_(0)
         prev_(0)
     {
     {

+ 12 - 0
Engine/Container/VectorBase.h

@@ -30,6 +30,12 @@ template <class T> class RandomAccessIterator
 {
 {
 public:
 public:
     /// Construct.
     /// Construct.
+    RandomAccessIterator() :
+        ptr_(0)
+    {
+    }
+    
+    /// Construct with an object pointer.
     explicit RandomAccessIterator(T* ptr) :
     explicit RandomAccessIterator(T* ptr) :
         ptr_(ptr)
         ptr_(ptr)
     {
     {
@@ -79,6 +85,12 @@ template <class T> class RandomAccessConstIterator
 {
 {
 public:
 public:
     /// Construct.
     /// Construct.
+    RandomAccessConstIterator() :
+        ptr_(0)
+    {
+    }
+    
+    /// Construct with an object pointer.
     explicit RandomAccessConstIterator(T* ptr) :
     explicit RandomAccessConstIterator(T* ptr) :
         ptr_(ptr)
         ptr_(ptr)
     {
     {

+ 12 - 12
Engine/Core/Mutex.cpp

@@ -34,33 +34,33 @@
 
 
 #ifdef WIN32
 #ifdef WIN32
 Mutex::Mutex() :
 Mutex::Mutex() :
-    criticalSection_(new CRITICAL_SECTION)
+    handle_(new CRITICAL_SECTION)
 {
 {
-    InitializeCriticalSection((CRITICAL_SECTION*)criticalSection_);
+    InitializeCriticalSection((CRITICAL_SECTION*)handle_);
 }
 }
 
 
 Mutex::~Mutex()
 Mutex::~Mutex()
 {
 {
-    CRITICAL_SECTION* cs = (CRITICAL_SECTION*)criticalSection_;
+    CRITICAL_SECTION* cs = (CRITICAL_SECTION*)handle_;
     DeleteCriticalSection(cs);
     DeleteCriticalSection(cs);
     delete cs;
     delete cs;
-    criticalSection_ = 0;
+    handle_ = 0;
 }
 }
 
 
 void Mutex::Acquire()
 void Mutex::Acquire()
 {
 {
-    EnterCriticalSection((CRITICAL_SECTION*)criticalSection_);
+    EnterCriticalSection((CRITICAL_SECTION*)handle_);
 }
 }
 
 
 void Mutex::Release()
 void Mutex::Release()
 {
 {
-    LeaveCriticalSection((CRITICAL_SECTION*)criticalSection_);
+    LeaveCriticalSection((CRITICAL_SECTION*)handle_);
 }
 }
 #else
 #else
 Mutex::Mutex() :
 Mutex::Mutex() :
-    criticalSection_(new pthread_mutex_t)
+    handle_(new pthread_mutex_t)
 {
 {
-    pthread_mutex_t* mutex = (pthread_mutex_t*)criticalSection_;
+    pthread_mutex_t* mutex = (pthread_mutex_t*)handle_;
     pthread_mutexattr_t attr;
     pthread_mutexattr_t attr;
     pthread_mutexattr_init(&attr);
     pthread_mutexattr_init(&attr);
     pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
     pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
@@ -69,20 +69,20 @@ Mutex::Mutex() :
 
 
 Mutex::~Mutex()
 Mutex::~Mutex()
 {
 {
-    pthread_mutex_t* mutex = (pthread_mutex_t*)criticalSection_;
+    pthread_mutex_t* mutex = (pthread_mutex_t*)handle_;
     pthread_mutex_destroy(mutex);
     pthread_mutex_destroy(mutex);
     delete mutex;
     delete mutex;
-    criticalSection_ = 0;
+    handle_ = 0;
 }
 }
 
 
 void Mutex::Acquire()
 void Mutex::Acquire()
 {
 {
-    pthread_mutex_lock((pthread_mutex_t*)criticalSection_);
+    pthread_mutex_lock((pthread_mutex_t*)handle_);
 }
 }
 
 
 void Mutex::Release()
 void Mutex::Release()
 {
 {
-    pthread_mutex_unlock((pthread_mutex_t*)criticalSection_);
+    pthread_mutex_unlock((pthread_mutex_t*)handle_);
 }
 }
 #endif
 #endif
 
 

+ 2 - 2
Engine/Core/Mutex.h

@@ -38,8 +38,8 @@ public:
     void Release();
     void Release();
     
     
 private:
 private:
-    /// Critical section.
-    void* criticalSection_;
+    /// Mutex handle.
+    void* handle_;
 };
 };
 
 
 /// Lock that automatically acquires and releases a mutex.
 /// Lock that automatically acquires and releases a mutex.

+ 1 - 1
Engine/Core/SpinLock.h

@@ -52,7 +52,7 @@ public:
     /// Release the lock.
     /// Release the lock.
     void Release()
     void Release()
     {
     {
-        TestAndSet(&locked_, 0);
+        locked_ = 0;
     }
     }
     
     
 private:
 private:

+ 32 - 0
Engine/Core/WorkItem.h

@@ -0,0 +1,32 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+/// Work queue item base class.
+class WorkItem
+{
+public:
+    /// Do the work. Main thread has index 0, while worker threads have indices 1 - n.
+    virtual void Process(unsigned threadIndex) = 0;
+};

+ 245 - 0
Engine/Core/WorkQueue.cpp

@@ -0,0 +1,245 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "ProcessUtils.h"
+#include "WorkerThread.h"
+#include "WorkItem.h"
+#include "WorkQueue.h"
+
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <pthread.h>
+#endif
+
+/// Work queue implementation.
+class WorkQueueImpl
+{
+public:
+    /// Construct.
+    WorkQueueImpl()
+    {
+        #ifdef WIN32
+        eventHandle_ = CreateEvent(0, FALSE, FALSE, 0);
+        #else
+        pthread_cond_init(&condition_, 0);
+        pthread_mutex_init(&mutex_, 0);
+        #endif
+    }
+    
+    /// Destruct.
+    ~WorkQueueImpl()
+    {
+        #ifdef WIN32
+        CloseHandle(eventHandle_);
+        #else
+        pthread_cond_destroy(&condition_);
+        pthread_mutex_destroy(&mutex_);
+        #endif
+    }
+    
+    /// Signal one worker thread for available work.
+    void Signal()
+    {
+        #ifdef WIN32
+        SetEvent(eventHandle_);
+        #else
+        pthread_cond_signal(&condition_);
+        #endif
+    }
+    
+    /// Wait for available work.
+    void Wait()
+    {
+        #ifdef WIN32
+        WaitForSingleObject(eventHandle_, INFINITE);
+        #else
+        pthread_cond_wait(&condition_, &mutex_);
+        #endif
+    }
+    
+private:
+    #ifdef WIN32
+    /// Event for signaling the worker threads.
+    HANDLE eventHandle_;
+    #else
+    /// Condition variable for signaling the worker threads.
+    pthread_cond_t condition_;
+    /// Mutex for the condition variable.
+    pthread_mutex_t mutex_;
+    #endif
+};
+
+OBJECTTYPESTATIC(WorkQueue);
+
+WorkQueue::WorkQueue(Context* context) :
+    Object(context),
+    impl_(new WorkQueueImpl()),
+    started_(false),
+    shutDown_(false)
+{
+    // Create worker threads and start them
+    unsigned numCores = GetNumCPUCores();
+    for (unsigned i = 1; i < numCores; ++i)
+    {
+        SharedPtr<WorkerThread> thread(new WorkerThread(this, i));
+        thread->Start();
+        threads_.Push(thread);
+    }
+}
+
+WorkQueue::~WorkQueue()
+{
+    // Stop the worker threads. First make sure they are not waiting for work items.
+    if (!threads_.Empty())
+    {
+        shutDown_ = true;
+        started_ = false;
+        
+        for (unsigned i = 0; i < threads_.Size(); ++i)
+            impl_->Signal();
+        for (unsigned i = 0; i < threads_.Size(); ++i)
+            threads_[i]->Stop();
+    }
+    
+    delete impl_;
+    impl_ = 0;
+}
+
+void WorkQueue::AddWorkItem(WorkItem* item)
+{
+    if (!started_)
+        queue_.Push(item);
+    else
+    {
+        queueLock_.Acquire();
+        queue_.Push(item);
+        queueLock_.Release();
+        impl_->Signal();
+    }
+}
+
+void WorkQueue::Start()
+{
+    if (!threads_.Empty() && !started_)
+    {
+        started_ = true;
+        for (unsigned i = 0; i < threads_.Size(); ++i)
+            impl_->Signal();
+    }
+}
+
+void WorkQueue::Finish()
+{
+    if (threads_.Empty())
+    {
+        // If no worker threads, do all work now in the main thread
+        for (List<WorkItem*>::Iterator i = queue_.Begin(); i != queue_.End(); ++i)
+            (*i)->Process(0);
+        queue_.Clear();
+    }
+    else
+    {
+        // Wait for work to finish while also taking work items in the main thread
+        for (;;)
+        {
+            WorkItem* item = 0;
+            bool allIdle = false;
+            
+            queueLock_.Acquire();
+            if (!queue_.Empty())
+            {
+                item = queue_.Front();
+                queue_.PopFront();
+            }
+            else
+                allIdle = CheckIdle();
+            queueLock_.Release();
+            
+            if (item)
+                item->Process(0);
+            else if (allIdle)
+                break;
+        }
+    }
+}
+
+void WorkQueue::FinishAndStop()
+{
+    Finish();
+    started_ = false;
+}
+
+bool WorkQueue::IsCompleted()
+{
+    queueLock_.Acquire();
+    bool queueEmpty = queue_.Empty();
+    bool allIdle = false;
+    if (queueEmpty)
+        allIdle = CheckIdle();
+    queueLock_.Release();
+    
+    return queueEmpty && allIdle;
+}
+
+WorkItem* WorkQueue::GetNextWorkItem(WorkerThread* thread)
+{
+    for (;;)
+    {
+        if (shutDown_)
+            return 0;
+        
+        WorkItem* item = 0;
+        queueLock_.Acquire();
+        if (started_ && !queue_.Empty())
+        {
+            item = queue_.Front();
+            queue_.PopFront();
+            thread->SetWorking(true);
+        }
+        queueLock_.Release();
+        
+        if (item)
+            return item;
+        else
+        {
+            thread->SetWorking(false);
+            impl_->Wait();
+        }
+    }
+}
+
+bool WorkQueue::CheckIdle()
+{
+    bool allIdle = true;
+    for (unsigned i = 0; i < threads_.Size(); ++i)
+    {
+        if (threads_[i]->IsWorking())
+        {
+            allIdle = false;
+            break;
+        }
+    }
+    return allIdle;
+}

+ 81 - 0
Engine/Core/WorkQueue.h

@@ -0,0 +1,81 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "List.h"
+#include "Object.h"
+#include "SpinLock.h"
+
+class WorkerThread;
+class WorkItem;
+class WorkQueueImpl;
+
+/// Work queue subsystem.
+class WorkQueue : public Object
+{
+    OBJECT(WorkQueue);
+    
+    friend class WorkerThread;
+    
+public:
+    /// Construct.
+    WorkQueue(Context* context);
+    /// Destruct.
+    ~WorkQueue();
+    
+    /// Add a work item. If work has been started, this incurs synchronization overhead.
+    void AddWorkItem(WorkItem* item);
+    /// Start working. No-op on single-core systems, which have no worker threads.
+    void Start();
+    /// Finish all current work items without stopping further work. On single-core systems all work will be done here.
+    void Finish();
+    /// Finish all work items and stop further work. On single-core systems all work will be done here.
+    void FinishAndStop();
+    
+    /// Return number of worker threads.
+    unsigned GetNumThreads() const { return threads_.Size(); }
+    /// Return whether work has been started. Always false on single-core systems.
+    bool IsStarted() const { return started_; }
+    /// Return whether all work is completed.
+    bool IsCompleted();
+    
+private:
+    /// Block until a work item is available and return it. Item may be null. Called by the worker threads.
+    WorkItem* GetNextWorkItem(WorkerThread* thread);
+    /// Check if all worker threads are idle.
+    bool CheckIdle();
+    
+    /// Work queue implementation. Contains the operating system-specific signaling mechanism.
+    WorkQueueImpl* impl_;
+    /// Worker threads.
+    Vector<SharedPtr<WorkerThread> > threads_;
+    /// Work item queue.
+    List<WorkItem*> queue_;
+    /// Queue spinlock.
+    SpinLock queueLock_;
+    /// Started flag.
+    volatile bool started_;
+    /// Shutting down flag.
+    volatile bool shutDown_;
+};

+ 44 - 0
Engine/Core/WorkerThread.cpp

@@ -0,0 +1,44 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "WorkerThread.h"
+#include "WorkItem.h"
+#include "WorkQueue.h"
+
+WorkerThread::WorkerThread(WorkQueue* owner, unsigned index) :
+    owner_(owner),
+    index_(index),
+    working_(false)
+{
+}
+
+void WorkerThread::ThreadFunction()
+{
+    while (shouldRun_)
+    {
+        WorkItem* item = owner_->GetNextWorkItem(this);
+        if (item)
+            item->Process(index_);
+    }
+}

+ 55 - 0
Engine/Core/WorkerThread.h

@@ -0,0 +1,55 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "RefCounted.h"
+#include "Thread.h"
+
+class WorkQueue;
+
+/// Worker thread managed by the work queue.
+class WorkerThread : public Thread, public RefCounted
+{
+public:
+    /// Construct.
+    WorkerThread(WorkQueue* owner, unsigned index);
+    
+    /// Process work items until stopped.
+    virtual void ThreadFunction();
+    
+    /// Return thread index.
+    unsigned GetIndex() const { return index_; }
+    /// Set working flag.
+    void SetWorking(bool enable) { working_ = enable; }
+    /// Return whether is working.
+    bool IsWorking() const { return working_; }
+    
+private:
+    /// Work queue.
+    WorkQueue* owner_;
+    /// Thread index.
+    unsigned index_;
+    /// Working flag.
+    volatile bool working_;
+};

+ 2 - 0
Engine/Engine/Engine.cpp

@@ -45,6 +45,7 @@
 #include "ScriptAPI.h"
 #include "ScriptAPI.h"
 #include "StringUtils.h"
 #include "StringUtils.h"
 #include "UI.h"
 #include "UI.h"
+#include "WorkQueue.h"
 
 
 #include "DebugNew.h"
 #include "DebugNew.h"
 
 
@@ -419,6 +420,7 @@ void Engine::RegisterSubsystems()
     
     
     // Create and register the rest of the subsystems
     // Create and register the rest of the subsystems
     context_->RegisterSubsystem(new Time(context_));
     context_->RegisterSubsystem(new Time(context_));
+    context_->RegisterSubsystem(new WorkQueue(context_));
     #ifdef ENABLE_PROFILING
     #ifdef ENABLE_PROFILING
     context_->RegisterSubsystem(new Profiler(context_));
     context_->RegisterSubsystem(new Profiler(context_));
     #endif
     #endif

+ 23 - 20
Engine/Graphics/AnimatedModel.cpp

@@ -63,8 +63,8 @@ AnimatedModel::AnimatedModel(Context* context) :
     animationLodTimer_(-1.0f),
     animationLodTimer_(-1.0f),
     animationLodDistance_(0.0f),
     animationLodDistance_(0.0f),
     invisibleLodFactor_(0.0f),
     invisibleLodFactor_(0.0f),
-    animationDirty_(true),
-    animationOrderDirty_(true),
+    animationDirty_(false),
+    animationOrderDirty_(false),
     morphsDirty_(true),
     morphsDirty_(true),
     skinningDirty_(true),
     skinningDirty_(true),
     isMaster_(true),
     isMaster_(true),
@@ -231,6 +231,11 @@ void AnimatedModel::UpdateDistance(const FrameInfo& frame)
     }
     }
 }
 }
 
 
+bool AnimatedModel::GetUpdateOnGPU()
+{
+    return morphsDirty_ && morphs_.Size();
+}
+
 void AnimatedModel::UpdateGeometry(const FrameInfo& frame)
 void AnimatedModel::UpdateGeometry(const FrameInfo& frame)
 {
 {
     if (morphsDirty_ && morphs_.Size())
     if (morphsDirty_ && morphs_.Size())
@@ -616,6 +621,8 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
             }
             }
         }
         }
         
         
+        MarkAnimationDirty();
+        
         using namespace BoneHierarchyCreated;
         using namespace BoneHierarchyCreated;
         
         
         VariantMap eventData;
         VariantMap eventData;
@@ -778,26 +785,28 @@ void AnimatedModel::AssignBoneNodes()
         AnimationState* state = *i;
         AnimationState* state = *i;
         state->SetStartBone(state->GetStartBone());
         state->SetStartBone(state->GetStartBone());
     }
     }
+    
+    MarkAnimationDirty();
 }
 }
 
 
 void AnimatedModel::MarkAnimationDirty()
 void AnimatedModel::MarkAnimationDirty()
 {
 {
-    if (!isMaster_)
-        return;
-    
-    animationDirty_ = true;
-    // Mark for octree update, as animation is updated before octree reinsertion
-    MarkForUpdate();
+    if (isMaster_)
+    {
+        animationDirty_ = true;
+        // Mark for pre-octree reinsertion update (threaded)
+        MarkForUpdate();
+    }
 }
 }
 
 
 void AnimatedModel::MarkAnimationOrderDirty()
 void AnimatedModel::MarkAnimationOrderDirty()
 {
 {
-    if (!isMaster_)
-        return;
-    
-    animationOrderDirty_ = true;
-    // Mark for octree update, as animation is updated before octree reinsertion
-    MarkForUpdate();
+    if (isMaster_)
+    {
+        animationOrderDirty_ = true;
+        // Mark for pre-octree reinsertion update (threaded)
+        MarkForUpdate();
+    }
 }
 }
 
 
 void AnimatedModel::MarkMorphsDirty()
 void AnimatedModel::MarkMorphsDirty()
@@ -915,8 +924,6 @@ void AnimatedModel::UpdateAnimation(const FrameInfo& frame)
             animationLodTimer_ = 0.0f;
             animationLodTimer_ = 0.0f;
     }
     }
     
     
-    PROFILE(UpdateAnimation);
-    
     // Make sure animations are in ascending priority order
     // Make sure animations are in ascending priority order
     if (animationOrderDirty_)
     if (animationOrderDirty_)
     {
     {
@@ -936,8 +943,6 @@ void AnimatedModel::UpdateAnimation(const FrameInfo& frame)
 
 
 void AnimatedModel::UpdateSkinning()
 void AnimatedModel::UpdateSkinning()
 {
 {
-    PROFILE(UpdateSkinning);
-    
     // Note: the model's world transform will be baked in the skin matrices
     // Note: the model's world transform will be baked in the skin matrices
     const Vector<Bone>& bones = skeleton_.GetBones();
     const Vector<Bone>& bones = skeleton_.GetBones();
     // Use model's world transform in case a bone is missing
     // Use model's world transform in case a bone is missing
@@ -979,8 +984,6 @@ void AnimatedModel::UpdateMorphs()
 {
 {
     if (morphs_.Size())
     if (morphs_.Size())
     {
     {
-        PROFILE(UpdateMorphs);
-        
         // Reset the morph data range from all morphable vertex buffers, then apply morphs
         // Reset the morph data range from all morphable vertex buffers, then apply morphs
         for (unsigned i = 0; i < morphVertexBuffers_.Size(); ++i)
         for (unsigned i = 0; i < morphVertexBuffers_.Size(); ++i)
         {
         {

+ 3 - 1
Engine/Graphics/AnimatedModel.h

@@ -54,7 +54,9 @@ public:
     virtual void Update(const FrameInfo& frame);
     virtual void Update(const FrameInfo& frame);
     /// Calculate distance and LOD level for rendering.
     /// Calculate distance and LOD level for rendering.
     virtual void UpdateDistance(const FrameInfo& frame);
     virtual void UpdateDistance(const FrameInfo& frame);
-    /// Prepare GPU geometry for rendering. Called on the main thread.
+    /// Return whether the next geometry update will touch actual GPU resources.
+    virtual bool GetUpdateOnGPU();
+    /// Prepare geometry for rendering. Called from a worker thread if possible (no GPU update.)
     virtual void UpdateGeometry(const FrameInfo& frame);
     virtual void UpdateGeometry(const FrameInfo& frame);
     /// Fill rendering batch with distance, geometry, material and world transform.
     /// Fill rendering batch with distance, geometry, material and world transform.
     virtual void GetBatch(Batch& batch, const FrameInfo& frame, unsigned batchIndex);
     virtual void GetBatch(Batch& batch, const FrameInfo& frame, unsigned batchIndex);

+ 0 - 2
Engine/Graphics/BillboardSet.cpp

@@ -375,8 +375,6 @@ void BillboardSet::UpdateVertexBuffer(const FrameInfo& frame)
         }
         }
     }
     }
     
     
-    PROFILE(UpdateBillboardSet);
-    
     unsigned numBillboards = billboards_.Size();
     unsigned numBillboards = billboards_.Size();
     unsigned enabledBillboards = 0;
     unsigned enabledBillboards = 0;
     const Matrix3x4& worldTransform = GetWorldTransform();
     const Matrix3x4& worldTransform = GetWorldTransform();

+ 3 - 1
Engine/Graphics/BillboardSet.h

@@ -65,7 +65,9 @@ public:
     
     
     /// Calculate distance and LOD level for rendering.
     /// Calculate distance and LOD level for rendering.
     virtual void UpdateDistance(const FrameInfo& frame);
     virtual void UpdateDistance(const FrameInfo& frame);
-    /// Prepare GPU geometry for rendering. Called on the main thread.
+    /// Return whether the next geometry update will touch actual GPU resources.
+    virtual bool GetUpdateOnGPU() { return true; }
+    /// Prepare geometry for rendering. Called from a worker thread if possible (no GPU update.)
     virtual void UpdateGeometry(const FrameInfo& frame);
     virtual void UpdateGeometry(const FrameInfo& frame);
     /// Return number of batches.
     /// Return number of batches.
     virtual unsigned GetNumBatches();
     virtual unsigned GetNumBatches();

+ 4 - 2
Engine/Graphics/Drawable.h

@@ -77,11 +77,13 @@ public:
     
     
     /// Process octree raycast.
     /// Process octree raycast.
     virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
     virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
-    /// Update before octree reinsertion. Needs to be requested with MarkForUpdate().
+    /// Update before octree reinsertion. Is called from a worker thread. Needs to be requested with MarkForUpdate().
     virtual void Update(const FrameInfo& frame) {}
     virtual void Update(const FrameInfo& frame) {}
     /// Calculate distance and LOD level for rendering.
     /// Calculate distance and LOD level for rendering.
     virtual void UpdateDistance(const FrameInfo& frame);
     virtual void UpdateDistance(const FrameInfo& frame);
-    /// Prepare GPU geometry for rendering. Called on the main thread.
+    /// Return whether the next geometry update will touch actual GPU resources. If not, it can be threaded.
+    virtual bool GetUpdateOnGPU() { return true; }
+    /// Prepare geometry for rendering.
     virtual void UpdateGeometry(const FrameInfo& frame) {}
     virtual void UpdateGeometry(const FrameInfo& frame) {}
     /// Return number of rendering batches.
     /// Return number of rendering batches.
     virtual unsigned GetNumBatches() { return 0; }
     virtual unsigned GetNumBatches() { return 0; }

+ 115 - 55
Engine/Graphics/Octree.cpp

@@ -27,7 +27,9 @@
 #include "Profiler.h"
 #include "Profiler.h"
 #include "Octree.h"
 #include "Octree.h"
 #include "OctreeQuery.h"
 #include "OctreeQuery.h"
+#include "Scene.h"
 #include "Sort.h"
 #include "Sort.h"
+#include "WorkQueue.h"
 
 
 #include "DebugNew.h"
 #include "DebugNew.h"
 
 
@@ -37,6 +39,7 @@
 
 
 static const float DEFAULT_OCTREE_SIZE = 1000.0f;
 static const float DEFAULT_OCTREE_SIZE = 1000.0f;
 static const int DEFAULT_OCTREE_LEVELS = 8;
 static const int DEFAULT_OCTREE_LEVELS = 8;
+static const int DRAWABLES_PER_WORKITEM = 4;
 
 
 inline bool CompareRayQueryResults(const RayQueryResult& lhs, const RayQueryResult& rhs)
 inline bool CompareRayQueryResults(const RayQueryResult& lhs, const RayQueryResult& rhs)
 {
 {
@@ -262,6 +265,7 @@ OBJECTTYPESTATIC(Octree);
 Octree::Octree(Context* context) :
 Octree::Octree(Context* context) :
     Component(context),
     Component(context),
     Octant(BoundingBox(-DEFAULT_OCTREE_SIZE, DEFAULT_OCTREE_SIZE), 0, 0, this),
     Octant(BoundingBox(-DEFAULT_OCTREE_SIZE, DEFAULT_OCTREE_SIZE), 0, 0, this),
+    scene_(0),
     numLevels_(DEFAULT_OCTREE_LEVELS)
     numLevels_(DEFAULT_OCTREE_LEVELS)
 {
 {
 }
 }
@@ -310,60 +314,8 @@ void Octree::Resize(const BoundingBox& box, unsigned numLevels)
 
 
 void Octree::Update(const FrameInfo& frame)
 void Octree::Update(const FrameInfo& frame)
 {
 {
-    {
-        PROFILE(UpdateDrawables);
-        
-        // Let drawables update themselves before reinsertion
-        for (HashSet<Drawable*>::Iterator i = drawableUpdates_.Begin(); i != drawableUpdates_.End(); ++i)
-            (*i)->Update(frame);
-        
-        for (unsigned i = unculledDrawables_.Size() - 1; i < unculledDrawables_.Size(); --i)
-        {
-            // Remove expired unculled drawables at this point
-            if (!unculledDrawables_[i])
-                unculledDrawables_.Erase(i, 1);
-            else
-                unculledDrawables_[i]->Update(frame);
-        }
-    }
-    
-    {
-        PROFILE(ReinsertDrawables);
-        
-        // Reinsert drawables into the octree
-        for (HashSet<Drawable*>::Iterator i = drawableReinsertions_.Begin(); i != drawableReinsertions_.End(); ++i)
-        {
-            Drawable* drawable = *i;
-            Octant* octant = drawable->GetOctant();
-            
-            if (octant)
-            {
-                bool reinsert = false;
-                
-                if (octant == this)
-                {
-                    // Handle root octant as special case: if outside the root, do not reinsert
-                    if (GetCullingBox().IsInside(drawable->GetWorldBoundingBox()) == INSIDE && !CheckDrawableSize(drawable))
-                        reinsert = true;
-                }
-                else
-                {
-                    // Otherwise reinsert if outside current octant or if size does not fit octant size
-                    if (octant->GetCullingBox().IsInside(drawable->GetWorldBoundingBox()) != INSIDE ||
-                        !octant->CheckDrawableSize(drawable))
-                        reinsert = true;
-                }
-                
-                if (reinsert)
-                    InsertDrawable(drawable);
-            }
-            else
-                InsertDrawable(drawable);
-        }
-    }
-    
-    drawableUpdates_.Clear();
-    drawableReinsertions_.Clear();
+    UpdateDrawables(frame);
+    ReinsertDrawables(frame);
 }
 }
 
 
 void Octree::AddManualDrawable(Drawable* drawable, bool culling)
 void Octree::AddManualDrawable(Drawable* drawable, bool culling)
@@ -443,7 +395,14 @@ void Octree::QueueUpdate(Drawable* drawable)
 
 
 void Octree::QueueReinsertion(Drawable* drawable)
 void Octree::QueueReinsertion(Drawable* drawable)
 {
 {
-    drawableReinsertions_.Insert(drawable);
+    if (scene_ && scene_->IsThreadedUpdate())
+    {
+        reinsertionLock_.Acquire();
+        drawableReinsertions_.Insert(drawable);
+        reinsertionLock_.Release();
+    }
+    else
+        drawableReinsertions_.Insert(drawable);
 }
 }
 
 
 void Octree::CancelUpdate(Drawable* drawable)
 void Octree::CancelUpdate(Drawable* drawable)
@@ -466,3 +425,104 @@ void Octree::DrawDebugGeometry(bool depthTest)
     
     
     Octant::DrawDebugGeometry(debug, depthTest);
     Octant::DrawDebugGeometry(debug, depthTest);
 }
 }
+
+void Octree::OnNodeSet(Node* node)
+{
+    scene_ = node ? node->GetScene() : 0;
+}
+
+void Octree::UpdateDrawables(const FrameInfo& frame)
+{
+    // Let drawables update themselves before reinsertion
+    if (drawableUpdates_.Empty())
+        return;
+    
+    PROFILE(UpdateDrawables);
+    
+    Scene* scene = node_->GetScene();
+    scene->BeginThreadedUpdate();
+    
+    WorkQueue* queue = GetSubsystem<WorkQueue>();
+    List<DrawableUpdate>::Iterator item = drawableUpdateItems_.Begin();
+    HashSet<Drawable*>::Iterator start = drawableUpdates_.Begin();
+    
+    while (start != drawableUpdates_.End())
+    {
+        // Create new item to the pool if necessary
+        if (item == drawableUpdateItems_.End())
+        {
+            drawableUpdateItems_.Push(DrawableUpdate());
+            item = --drawableUpdateItems_.End();
+        }
+        
+        HashSet<Drawable*>::Iterator end = start;
+        int count = 0;
+        while (count < DRAWABLES_PER_WORKITEM && end != drawableUpdates_.End())
+        {
+            ++count;
+            ++end;
+        }
+        
+        item->frame_ = &frame;
+        item->start_ = start;
+        item->end_ = end;
+        queue->AddWorkItem(&(*item));
+        
+        ++item;
+        start = end;
+    }
+    
+    queue->Start();
+    queue->FinishAndStop();
+    drawableUpdates_.Clear();
+    
+    scene->EndThreadedUpdate();
+    
+    for (unsigned i = unculledDrawables_.Size() - 1; i < unculledDrawables_.Size(); --i)
+    {
+        // Remove expired unculled drawables at this point
+        if (!unculledDrawables_[i])
+            unculledDrawables_.Erase(i, 1);
+    }
+}
+
+void Octree::ReinsertDrawables(const FrameInfo& frame)
+{
+    if (drawableReinsertions_.Empty())
+        return;
+    
+    PROFILE(ReinsertDrawables);
+    
+    // Reinsert drawables into the octree
+    for (HashSet<Drawable*>::Iterator i = drawableReinsertions_.Begin(); i != drawableReinsertions_.End(); ++i)
+    {
+        Drawable* drawable = *i;
+        Octant* octant = drawable->GetOctant();
+        
+        if (octant)
+        {
+            bool reinsert = false;
+            
+            if (octant == this)
+            {
+                // Handle root octant as special case: if outside the root, do not reinsert
+                if (GetCullingBox().IsInside(drawable->GetWorldBoundingBox()) == INSIDE && !CheckDrawableSize(drawable))
+                    reinsert = true;
+            }
+            else
+            {
+                // Otherwise reinsert if outside current octant or if size does not fit octant size
+                if (octant->GetCullingBox().IsInside(drawable->GetWorldBoundingBox()) != INSIDE ||
+                    !octant->CheckDrawableSize(drawable))
+                    reinsert = true;
+            }
+            
+            if (reinsert)
+                InsertDrawable(drawable);
+        }
+        else
+            InsertDrawable(drawable);
+    }
+    
+    drawableReinsertions_.Clear();
+}

+ 38 - 1
Engine/Graphics/Octree.h

@@ -25,6 +25,9 @@
 
 
 #include "Drawable.h"
 #include "Drawable.h"
 #include "HashSet.h"
 #include "HashSet.h"
+#include "List.h"
+#include "SpinLock.h"
+#include "WorkItem.h"
 
 
 class Drawable;
 class Drawable;
 class Octree;
 class Octree;
@@ -33,6 +36,25 @@ class RayOctreeQuery;
 
 
 static const int NUM_OCTANTS = 8;
 static const int NUM_OCTANTS = 8;
 
 
+/// Drawable update work item.
+class DrawableUpdate : public WorkItem
+{
+public:
+    /// Do the work.
+    virtual void Process(unsigned threadIndex)
+    {
+        for (HashSet<Drawable*>::Iterator i = start_; i != end_; ++i)
+            (*i)->Update(*frame_);
+    }
+    
+    /// Frame info.
+    const FrameInfo* frame_;
+    /// Start iterator.
+    HashSet<Drawable*>::Iterator start_;
+    /// End iterator.
+    HashSet<Drawable*>::Iterator end_;
+};
+
 /// %Octree octant
 /// %Octree octant
 class Octant
 class Octant
 {
 {
@@ -178,7 +200,7 @@ public:
     
     
     /// Mark drawable object as requiring an update.
     /// Mark drawable object as requiring an update.
     void QueueUpdate(Drawable* drawable);
     void QueueUpdate(Drawable* drawable);
-    /// Mark drawable object as requiring a reinsertion.
+    /// Mark drawable object as requiring a reinsertion. Is thread-safe.
     void QueueReinsertion(Drawable* drawable);
     void QueueReinsertion(Drawable* drawable);
     /// Remove drawable object from update list.
     /// Remove drawable object from update list.
     void CancelUpdate(Drawable* drawable);
     void CancelUpdate(Drawable* drawable);
@@ -187,11 +209,26 @@ public:
     /// Add debug geometry to the debug graphics.
     /// Add debug geometry to the debug graphics.
     void DrawDebugGeometry(bool depthTest);
     void DrawDebugGeometry(bool depthTest);
     
     
+protected:
+    /// Handle node being assigned.
+    virtual void OnNodeSet(Node* node);
+    
 private:
 private:
+    /// Update drawable objects marked for update. Updates are executed in worker threads.
+    void UpdateDrawables(const FrameInfo& frame);
+    /// Reinsert moved drawable objects into the octree.
+    void ReinsertDrawables(const FrameInfo& frame);
+    
+    /// Scene.
+    Scene* scene_;
     /// %Set of drawable objects that require update.
     /// %Set of drawable objects that require update.
     HashSet<Drawable*> drawableUpdates_;
     HashSet<Drawable*> drawableUpdates_;
     /// %Set of drawable objects that require reinsertion.
     /// %Set of drawable objects that require reinsertion.
     HashSet<Drawable*> drawableReinsertions_;
     HashSet<Drawable*> drawableReinsertions_;
+    /// Pool for drawable update work items.
+    List<DrawableUpdate> drawableUpdateItems_;
+    /// Lock for octree reinsertions.
+    SpinLock reinsertionLock_;
     /// Unculled drawables.
     /// Unculled drawables.
     Vector<WeakPtr<Drawable> > unculledDrawables_;
     Vector<WeakPtr<Drawable> > unculledDrawables_;
     /// Subdivision level.
     /// Subdivision level.

+ 8 - 13
Engine/Graphics/ParticleEmitter.cpp

@@ -90,17 +90,9 @@ void ParticleEmitter::RegisterObject(Context* context)
     ACCESSOR_ATTRIBUTE(ParticleEmitter, VAR_VARIANTVECTOR, "Billboards", GetBillboardsAttr, SetBillboardsAttr, VariantVector, VariantVector(), AM_FILE | AM_NOEDIT);
     ACCESSOR_ATTRIBUTE(ParticleEmitter, VAR_VARIANTVECTOR, "Billboards", GetBillboardsAttr, SetBillboardsAttr, VariantVector, VariantVector(), AM_FILE | AM_NOEDIT);
 }
 }
 
 
-void ParticleEmitter::Update(float timeStep)
+void ParticleEmitter::Update(const FrameInfo& frame)
 {
 {
-    // If no invisible update, check that the billboardset is in view (framenumber has changed)
-    if (!updateInvisible_)
-    {
-        if (viewFrameNumber_ == lastUpdateFrameNumber_)
-            return;
-    }
-    lastUpdateFrameNumber_ = viewFrameNumber_;
-    
-    PROFILE(UpdateParticleEmitter);
+    float timeStep = frame.timeStep_;
     
     
     // If there is an amount mismatch between particles and billboards, correct it
     // If there is an amount mismatch between particles and billboards, correct it
     if (particles_.Size() != billboards_.Size())
     if (particles_.Size() != billboards_.Size())
@@ -570,7 +562,10 @@ void ParticleEmitter::GetVector3MinMax(const XMLElement& element, Vector3& minVa
 
 
 void ParticleEmitter::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
 void ParticleEmitter::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
-    using namespace ScenePostUpdate;
-    
-    Update(eventData[P_TIMESTEP].GetFloat());
+    // If no invisible update, check that the billboardset is in view (framenumber has changed)
+    if (updateInvisible_ || viewFrameNumber_ != lastUpdateFrameNumber_)
+    {
+        lastUpdateFrameNumber_ = viewFrameNumber_;
+        MarkForUpdate();
+    }
 }
 }

+ 3 - 2
Engine/Graphics/ParticleEmitter.h

@@ -80,8 +80,9 @@ public:
     /// Register object factory.
     /// Register object factory.
     static void RegisterObject(Context* context);
     static void RegisterObject(Context* context);
     
     
-    /// Update the particle system. Is called from HandleScenePostUpdate().
-    void Update(float timeStep);
+    /// Update before octree reinsertion. Is called from a worker thread. Needs to be requested with MarkForUpdate().
+    virtual void Update(const FrameInfo& frame);
+    
     /// Load emitter parameters from an XML file. Return true if successful.
     /// Load emitter parameters from an XML file. Return true if successful.
     bool LoadParameters(XMLFile* file);
     bool LoadParameters(XMLFile* file);
     /// %Set emitter active/inactive state and optionally reset active/inactive timer.
     /// %Set emitter active/inactive state and optionally reset active/inactive timer.

+ 1 - 1
Engine/Graphics/Renderer.cpp

@@ -573,7 +573,7 @@ void Renderer::Update(float timeStep)
                 debug->SetView(viewport.camera_);
                 debug->SetView(viewport.camera_);
         }
         }
         
         
-        // Update the viewport's main view and any auxiliary views it Creates
+        // Update the viewport's main view and any auxiliary views it creates
         for (unsigned i = mainView; i < numViews_; ++i)
         for (unsigned i = mainView; i < numViews_; ++i)
             views_[i]->Update(frame_);
             views_[i]->Update(frame_);
     }
     }

+ 54 - 1
Engine/Graphics/View.cpp

@@ -42,6 +42,7 @@
 #include "TextureCube.h"
 #include "TextureCube.h"
 #include "VertexBuffer.h"
 #include "VertexBuffer.h"
 #include "View.h"
 #include "View.h"
+#include "WorkQueue.h"
 #include "Zone.h"
 #include "Zone.h"
 
 
 #include "DebugNew.h"
 #include "DebugNew.h"
@@ -56,6 +57,8 @@ static const Vector3 directions[] =
     Vector3(0.0f, 0.0f, -1.0f)
     Vector3(0.0f, 0.0f, -1.0f)
 };
 };
 
 
+static const int DRAWABLES_PER_WORKITEM = 4;
+
 OBJECTTYPESTATIC(View);
 OBJECTTYPESTATIC(View);
 
 
 View::View(Context* context) :
 View::View(Context* context) :
@@ -590,8 +593,58 @@ void View::UpdateGeometries()
 {
 {
     PROFILE(UpdateGeometries);
     PROFILE(UpdateGeometries);
     
     
+    // Split into threaded and non-threaded geometries.
+    threadedGeometries_.Clear();
     for (PODVector<Drawable*>::ConstIterator i = allGeometries_.Begin(); i != allGeometries_.End(); ++i)
     for (PODVector<Drawable*>::ConstIterator i = allGeometries_.Begin(); i != allGeometries_.End(); ++i)
-        (*i)->UpdateGeometry(frame_);
+    {
+        if (!(*i)->GetUpdateOnGPU())
+            threadedGeometries_.Push(*i);
+    }
+    
+    WorkQueue* queue = GetSubsystem<WorkQueue>();
+    
+    if (threadedGeometries_.Size())
+    {
+        List<DrawableGeometryUpdate>::Iterator item = drawableGeometryUpdateItems_.Begin();
+        PODVector<Drawable*>::Iterator start = threadedGeometries_.Begin();
+        
+        while (start != threadedGeometries_.End())
+        {
+            // Create new item to the pool if necessary
+            if (item == drawableGeometryUpdateItems_.End())
+            {
+                drawableGeometryUpdateItems_.Push(DrawableGeometryUpdate());
+                item = --drawableGeometryUpdateItems_.End();
+            }
+            
+            PODVector<Drawable*>::Iterator end = start;
+            int count = 0;
+            while (count < DRAWABLES_PER_WORKITEM && end != threadedGeometries_.End())
+            {
+                ++count;
+                ++end;
+            }
+            
+            item->frame_ = &frame_;
+            item->start_ = start;
+            item->end_ = end;
+            queue->AddWorkItem(&(*item));
+            
+            ++item;
+            start = end;
+        }
+        
+        queue->Start();
+    }
+    
+    // While the work queue is processed, update non-threaded geometries
+    for (PODVector<Drawable*>::ConstIterator i = allGeometries_.Begin(); i != allGeometries_.End(); ++i)
+    {
+        if ((*i)->GetUpdateOnGPU())
+            (*i)->UpdateGeometry(frame_);
+    }
+    
+    queue->FinishAndStop();
 }
 }
 
 
 void View::GetLitBatches(Drawable* drawable, LightBatchQueue& lightQueue)
 void View::GetLitBatches(Drawable* drawable, LightBatchQueue& lightQueue)

+ 26 - 0
Engine/Graphics/View.h

@@ -24,9 +24,12 @@
 #pragma once
 #pragma once
 
 
 #include "Batch.h"
 #include "Batch.h"
+#include "Drawable.h"
 #include "HashSet.h"
 #include "HashSet.h"
+#include "List.h"
 #include "Object.h"
 #include "Object.h"
 #include "Polyhedron.h"
 #include "Polyhedron.h"
+#include "WorkItem.h"
 
 
 class Camera;
 class Camera;
 class DebugRenderer;
 class DebugRenderer;
@@ -49,6 +52,25 @@ struct GeometryDepthBounds
     float max_;
     float max_;
 };
 };
 
 
+/// Drawable geometry update work item.
+class DrawableGeometryUpdate : public WorkItem
+{
+public:
+    /// Do the work.
+    virtual void Process(unsigned threadIndex)
+    {
+        for (PODVector<Drawable*>::Iterator i = start_; i != end_; ++i)
+            (*i)->UpdateGeometry(*frame_);
+    }
+    
+    /// Frame info.
+    const FrameInfo* frame_;
+    /// Start iterator.
+    PODVector<Drawable*>::Iterator start_;
+    /// End iterator.
+    PODVector<Drawable*>::Iterator end_;
+};
+
 /// 3D rendering view. Includes the main view(s) and any auxiliary views, but not shadow cameras.
 /// 3D rendering view. Includes the main view(s) and any auxiliary views, but not shadow cameras.
 class View : public Object
 class View : public Object
 {
 {
@@ -208,6 +230,8 @@ private:
     PODVector<Drawable*> geometries_;
     PODVector<Drawable*> geometries_;
     /// All geometry objects, including shadow casters not visible in the main view.
     /// All geometry objects, including shadow casters not visible in the main view.
     PODVector<Drawable*> allGeometries_;
     PODVector<Drawable*> allGeometries_;
+    /// Geometry objects that will be updated in worker threads.
+    PODVector<Drawable*> threadedGeometries_;
     /// Occluder objects.
     /// Occluder objects.
     PODVector<Drawable*> occluders_;
     PODVector<Drawable*> occluders_;
     /// Directional light shadow rendering occluders.
     /// Directional light shadow rendering occluders.
@@ -222,6 +246,8 @@ private:
     Map<Light*, unsigned> lightQueueIndex_;
     Map<Light*, unsigned> lightQueueIndex_;
     /// Cache for light scissor queries.
     /// Cache for light scissor queries.
     HashMap<Light*, Rect> lightScissorCache_;
     HashMap<Light*, Rect> lightScissorCache_;
+    /// Pool for drawable geometry update work items.
+    List<DrawableGeometryUpdate> drawableGeometryUpdateItems_;
     /// Base pass batches.
     /// Base pass batches.
     BatchQueue baseQueue_;
     BatchQueue baseQueue_;
     /// Pre-transparent pass batches.
     /// Pre-transparent pass batches.

+ 8 - 0
Engine/Physics/CollisionShape.cpp

@@ -801,6 +801,14 @@ void CollisionShape::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 
 
 void CollisionShape::OnMarkedDirty(Node* node)
 void CollisionShape::OnMarkedDirty(Node* node)
 {
 {
+    // Physics operations are not safe from worker threads
+    Scene* scene = node->GetScene();
+    if (scene && scene->IsThreadedUpdate())
+    {
+        scene->DelayedMarkedDirty(this);
+        return;
+    }
+    
     // If scale has changed, must recreate the geometry
     // If scale has changed, must recreate the geometry
     if (node->GetWorldScale() != geometryScale_)
     if (node->GetWorldScale() != geometryScale_)
         CreateGeometry();
         CreateGeometry();

+ 8 - 0
Engine/Physics/RigidBody.cpp

@@ -351,6 +351,14 @@ const PODVector<unsigned char>& RigidBody::GetNetAngularVelocityAttr() const
 
 
 void RigidBody::OnMarkedDirty(Node* node)
 void RigidBody::OnMarkedDirty(Node* node)
 {
 {
+    // Physics operations are not safe from worker threads
+    Scene* scene = node->GetScene();
+    if (scene && scene->IsThreadedUpdate())
+    {
+        scene->DelayedMarkedDirty(this);
+        return;
+    }
+    
     // If the node is smoothed, do not use the dirty callback, but rather update manually during prestep
     // If the node is smoothed, do not use the dirty callback, but rather update manually during prestep
     if (node_->IsSmoothed())
     if (node_->IsSmoothed())
         return;
         return;

+ 34 - 1
Engine/Scene/Scene.cpp

@@ -31,6 +31,7 @@
 #include "Profiler.h"
 #include "Profiler.h"
 #include "Scene.h"
 #include "Scene.h"
 #include "SceneEvents.h"
 #include "SceneEvents.h"
+#include "WorkQueue.h"
 #include "XMLFile.h"
 #include "XMLFile.h"
 
 
 static const int ASYNC_LOAD_MIN_FPS = 50;
 static const int ASYNC_LOAD_MIN_FPS = 50;
@@ -51,7 +52,8 @@ Scene::Scene(Context* context) :
     snapThreshold_(DEFAULT_SNAP_THRESHOLD),
     snapThreshold_(DEFAULT_SNAP_THRESHOLD),
     checksum_(0),
     checksum_(0),
     active_(true),
     active_(true),
-    asyncLoading_(false)
+    asyncLoading_(false),
+    threadedUpdate_(false)
 {
 {
     // Assign an ID to self so that nodes can refer to this node as a parent
     // Assign an ID to self so that nodes can refer to this node as a parent
     SetID(GetFreeNodeID(REPLICATED));
     SetID(GetFreeNodeID(REPLICATED));
@@ -393,6 +395,37 @@ void Scene::Update(float timeStep)
     SendEvent(E_SCENEPOSTUPDATE, eventData);
     SendEvent(E_SCENEPOSTUPDATE, eventData);
 }
 }
 
 
+void Scene::BeginThreadedUpdate()
+{
+    // Check the work queue subsystem whether it actually has created worker threads. If not, do not enter threaded mode.
+    if (GetSubsystem<WorkQueue>()->GetNumThreads())
+        threadedUpdate_ = true;
+}
+
+void Scene::EndThreadedUpdate()
+{
+    if (!threadedUpdate_)
+        return;
+    
+    threadedUpdate_ = false;
+    
+    if (!delayedDirtyComponents_.Empty())
+    {
+        PROFILE(EndThreadedUpdate);
+        
+        for (PODVector<Component*>::ConstIterator i = delayedDirtyComponents_.Begin(); i != delayedDirtyComponents_.End(); ++i)
+            (*i)->OnMarkedDirty((*i)->GetNode());
+        delayedDirtyComponents_.Clear();
+    }
+}
+
+void Scene::DelayedMarkedDirty(Component* component)
+{
+    delayedDirtyLock_.Acquire();
+    delayedDirtyComponents_.Push(component);
+    delayedDirtyLock_.Release();
+}
+
 unsigned Scene::GetFreeNodeID(CreateMode mode)
 unsigned Scene::GetFreeNodeID(CreateMode mode)
 {
 {
     if (mode == REPLICATED)
     if (mode == REPLICATED)

+ 15 - 0
Engine/Scene/Scene.h

@@ -24,6 +24,7 @@
 #pragma once
 #pragma once
 
 
 #include "Node.h"
 #include "Node.h"
+#include "SpinLock.h"
 #include "XMLElement.h"
 #include "XMLElement.h"
 
 
 class File;
 class File;
@@ -132,6 +133,14 @@ public:
     
     
     /// Update scene. Called by HandleUpdate.
     /// Update scene. Called by HandleUpdate.
     void Update(float timeStep);
     void Update(float timeStep);
+    /// Begin a threaded update. During threaded update components can choose to delay dirty processing.
+    void BeginThreadedUpdate();
+    /// End a threaded update. Notify components that marked themselves for delayed dirty processing.
+    void EndThreadedUpdate();
+    /// Add a component to the delayed dirty notify queue. Is thread-safe.
+    void DelayedMarkedDirty(Component* component);
+    /// Return threaded update flag.
+    bool IsThreadedUpdate() const { return threadedUpdate_; }
     /// Get free node ID, either non-local or local.
     /// Get free node ID, either non-local or local.
     unsigned GetFreeNodeID(CreateMode mode);
     unsigned GetFreeNodeID(CreateMode mode);
     /// Get free component ID, either non-local or local.
     /// Get free component ID, either non-local or local.
@@ -171,6 +180,10 @@ private:
     Vector<SharedPtr<PackageFile> > requiredPackageFiles_;
     Vector<SharedPtr<PackageFile> > requiredPackageFiles_;
     /// Registered node user variable reverse mappings.
     /// Registered node user variable reverse mappings.
     Map<ShortStringHash, String> varNames_;
     Map<ShortStringHash, String> varNames_;
+    /// Delayed dirty notification queue for components.
+    PODVector<Component*> delayedDirtyComponents_;
+    /// Lock for the delayed dirty notification queue.
+    SpinLock delayedDirtyLock_;
     /// Next free non-local node ID.
     /// Next free non-local node ID.
     unsigned replicatedNodeID_;
     unsigned replicatedNodeID_;
     /// Next free local node ID.
     /// Next free local node ID.
@@ -189,6 +202,8 @@ private:
     bool active_;
     bool active_;
     /// Asynchronous loading flag.
     /// Asynchronous loading flag.
     bool asyncLoading_;
     bool asyncLoading_;
+    /// Threaded update flag.
+    bool threadedUpdate_;
 };
 };
 
 
 /// Register Scene library objects.
 /// Register Scene library objects.