Browse Source

Combine octree update & reinsertion queuing.
Queue only one large work item per thread for threaded Octree & View operations.
Protect the network replication dirty sets with mutex if nodes/components are inserted during the threaded update phase.

Lasse Öörni 12 years ago
parent
commit
c1e03c48db

+ 2 - 1
Bin/Data/LuaScripts/20_HugeObjectCount.lua

@@ -145,8 +145,9 @@ end
 function AnimateObjects(timeStep)
     local ROTATE_SPEED = 15.0
     local delta = ROTATE_SPEED * timeStep
+    local rotateQuat = Quaternion(delta, Vector3(0.0, 0.0, 1.0))
     for i, v in ipairs(boxNodes) do
-        v:Roll(delta)
+        v:Rotate(rotateQuat)
     end
 end
 

+ 3 - 1
Bin/Data/Scripts/20_HugeObjectCount.as

@@ -139,9 +139,11 @@ void MoveCamera(float timeStep)
 void AnimateObjects(float timeStep)
 {
     const float ROTATE_SPEED = 15.0f;
+    // Rotate about the Z axis (roll)
+    Quaternion rotateQuat(ROTATE_SPEED * timeStep, Vector3(0.0f, 0.0f, 1.0f));
 
     for (uint i = 0; i < boxNodes.length; ++i)
-        boxNodes[i].Roll(ROTATE_SPEED * timeStep);
+        boxNodes[i].Rotate(rotateQuat);
 }
 
 void HandleUpdate(StringHash eventType, VariantMap& eventData)

+ 1 - 4
Source/Engine/Graphics/AnimatedModel.cpp

@@ -932,7 +932,6 @@ void AnimatedModel::MarkAnimationDirty()
     if (isMaster_)
     {
         animationDirty_ = true;
-        // Mark for pre-octree reinsertion update (threaded)
         MarkForUpdate();
     }
 }
@@ -942,7 +941,6 @@ void AnimatedModel::MarkAnimationOrderDirty()
     if (isMaster_)
     {
         animationOrderDirty_ = true;
-        // Mark for pre-octree reinsertion update (threaded)
         MarkForUpdate();
     }
 }
@@ -1128,9 +1126,8 @@ void AnimatedModel::UpdateAnimation(const FrameInfo& frame)
     for (Vector<SharedPtr<AnimationState> >::Iterator i = animationStates_.Begin(); i != animationStates_.End(); ++i)
         (*i)->Apply();
 
-    // Calculate new bone bounding box, then mark for octree reinsertion
+    // Calculate new bone bounding box
     UpdateBoneBoundingBox();
-    Drawable::OnMarkedDirty(node_);
     
     animationDirty_ = false;
 }

+ 1 - 1
Source/Engine/Graphics/AnimatedModel.h

@@ -55,7 +55,7 @@ public:
     virtual void ApplyAttributes();
     /// Process octree raycast. May be called from a worker thread.
     virtual void ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results);
-    /// Update before octree reinsertion. Is called from a worker thread. Needs to be requested with MarkForUpdate().
+    /// Update before octree reinsertion. Is called from a worker thread.
     virtual void Update(const FrameInfo& frame);
     /// Calculate distance and prepare batches for rendering. May be called from worker thread(s), possibly re-entrantly.
     virtual void UpdateBatches(const FrameInfo& frame);

+ 4 - 7
Source/Engine/Graphics/Drawable.cpp

@@ -62,7 +62,6 @@ Drawable::Drawable(Context* context, unsigned char drawableFlags) :
     occluder_(false),
     occludee_(true),
     updateQueued_(false),
-    reinsertionQueued_(false),
     viewMask_(DEFAULT_VIEWMASK),
     lightMask_(DEFAULT_LIGHTMASK),
     shadowMask_(DEFAULT_SHADOWMASK),
@@ -228,8 +227,8 @@ void Drawable::SetOccludee(bool enable)
     {
         occludee_ = enable;
         // Reinsert to octree to make sure octant occlusion does not erroneously hide this drawable
-        if (octant_ && !reinsertionQueued_)
-            octant_->GetRoot()->QueueReinsertion(this);
+        if (octant_ && !updateQueued_)
+            octant_->GetRoot()->QueueUpdate(this);
         MarkNetworkUpdate();
     }
 }
@@ -353,8 +352,8 @@ void Drawable::OnNodeSet(Node* node)
 void Drawable::OnMarkedDirty(Node* node)
 {
     worldBoundingBoxDirty_ = true;
-    if (!reinsertionQueued_ && octant_)
-        octant_->GetRoot()->QueueReinsertion(this);
+    if (!updateQueued_ && octant_)
+        octant_->GetRoot()->QueueUpdate(this);
 
     // Mark zone assignment dirty
     if (node == node_)
@@ -390,8 +389,6 @@ void Drawable::RemoveFromOctree()
         Octree* octree = octant_->GetRoot();
         if (updateQueued_)
             octree->CancelUpdate(this);
-        if (reinsertionQueued_)
-            octree->CancelReinsertion(this);
         
         octant_->RemoveDrawable(this);
     }

+ 2 - 5
Source/Engine/Graphics/Drawable.h

@@ -37,7 +37,6 @@ static const unsigned DEFAULT_VIEWMASK = M_MAX_UNSIGNED;
 static const unsigned DEFAULT_LIGHTMASK = M_MAX_UNSIGNED;
 static const unsigned DEFAULT_SHADOWMASK = M_MAX_UNSIGNED;
 static const unsigned DEFAULT_ZONEMASK = M_MAX_UNSIGNED;
-static const int DRAWABLES_PER_WORK_ITEM = 16;
 static const int MAX_VERTEX_LIGHTS = 4;
 static const float ANIMATION_LOD_BASESCALE = 2500.0f;
 
@@ -120,7 +119,7 @@ public:
     virtual void OnSetEnabled();
     /// Process octree raycast. May be called from a worker thread.
     virtual void ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results);
-    /// Update before octree reinsertion. Is called from a worker thread. Needs to be requested with MarkForUpdate().
+    /// Update before octree reinsertion. Is called from a worker thread.
     virtual void Update(const FrameInfo& frame) {}
     /// Calculate distance and prepare batches for rendering. May be called from worker thread(s), possibly re-entrantly.
     virtual void UpdateBatches(const FrameInfo& frame);
@@ -159,7 +158,7 @@ public:
     void SetOccluder(bool enable);
     /// Set occludee flag.
     void SetOccludee(bool enable);
-    /// Mark for update before octree reinsertion.
+    /// Mark for update and octree reinsertion. Update is automatically queued when the drawable's scene node moves or changes scale.
     void MarkForUpdate();
     
     /// Return local space bounding box. May not be applicable or properly updated on all drawables.
@@ -276,8 +275,6 @@ protected:
     bool occludee_;
     /// Octree update queued flag.
     bool updateQueued_;
-    /// Octree reinsertion queued flag.
-    bool reinsertionQueued_;
     /// View mask.
     unsigned viewMask_;
     /// Light mask.

+ 78 - 94
Source/Engine/Graphics/Octree.cpp

@@ -75,7 +75,8 @@ void UpdateDrawablesWork(const WorkItem* item, unsigned threadIndex)
         if (drawable)
         {
             drawable->Update(frame);
-            drawable->updateQueued_ = false;
+            // Ask for an updated world bounding box from the drawable already here to calculate them multithreaded
+            drawable->GetWorldBoundingBox();
         }
         ++start;
     }
@@ -108,7 +109,7 @@ Octant::~Octant()
         {
             (*i)->SetOctant(root_);
             root_->drawables_.Push(*i);
-            root_->QueueReinsertion(*i);
+            root_->QueueUpdate(*i);
         }
         drawables_.Clear();
         numDrawables_ = 0;
@@ -392,8 +393,43 @@ void Octree::SetSize(const BoundingBox& box, unsigned numLevels)
 
 void Octree::Update(const FrameInfo& frame)
 {
-    UpdateDrawables(frame);
+    // Let drawables update themselves before reinsertion. This can be used for animation
+    if (!drawableUpdates_.Empty())
+    {
+        PROFILE(UpdateDrawables);
+
+        // Perform updates in worker threads. Notify the scene that a threaded update is going on and components
+        // (for example physics objects) should not perform non-threadsafe work when marked dirty
+        Scene* scene = GetScene();
+        WorkQueue* queue = GetSubsystem<WorkQueue>();
+        scene->BeginThreadedUpdate();
+        
+        int numWorkItems = queue->GetNumThreads() + 1; // Worker threads + main thread
+        int drawablesPerItem = drawableUpdates_.Size() / numWorkItems;
+        
+        WorkItem item;
+        item.workFunction_ = UpdateDrawablesWork;
+        item.aux_ = const_cast<FrameInfo*>(&frame);
+
+        PODVector<Drawable*>::Iterator start = drawableUpdates_.Begin();
+        // Create a work item for each thread
+        for (int i = 0; i < numWorkItems; ++i)
+        {
+            PODVector<Drawable*>::Iterator end = drawableUpdates_.End();
+            if (i < numWorkItems - 1 && end - start > drawablesPerItem)
+                end = start + drawablesPerItem;
 
+            item.start_ = &(*start);
+            item.end_ = &(*end);
+            queue->AddWorkItem(item);
+
+            start = end;
+        }
+
+        queue->Complete(M_MAX_UNSIGNED);
+        scene->EndThreadedUpdate();
+    }
+    
     // Notify drawable update being finished. Custom animation (eg. IK) can be done at this point
     Scene* scene = GetScene();
     if (scene)
@@ -405,8 +441,42 @@ void Octree::Update(const FrameInfo& frame)
         eventData[P_TIMESTEP] = frame.timeStep_;
         scene->SendEvent(E_SCENEDRAWABLEUPDATEFINISHED, eventData);
     }
+    
+    // Reinsert drawables that have been moved or resized, or that have been newly added to the octree and do not sit inside
+    // the proper octant yet
+    if (!drawableUpdates_.Empty())
+    {
+        PROFILE(ReinsertToOctree);
 
-    ReinsertDrawables(frame);
+        for (PODVector<Drawable*>::Iterator i = drawableUpdates_.Begin(); i != drawableUpdates_.End(); ++i)
+        {
+            Drawable* drawable = *i;
+            drawable->updateQueued_ = false;
+            Octant* octant = drawable->GetOctant();
+            const BoundingBox& box = drawable->GetWorldBoundingBox();
+
+            // Skip if no octant or does not belong to this octree anymore
+            if (!octant || octant->GetRoot() != this)
+                continue;
+            // Skip if still fits the current octant
+            if (drawable->IsOccludee() && octant->GetCullingBox().IsInside(box) == INSIDE && octant->CheckDrawableFit(box))
+                continue;
+
+            InsertDrawable(drawable);
+
+            #ifdef _DEBUG
+            // Verify that the drawable will be culled correctly
+            octant = drawable->GetOctant();
+            if (octant != this && octant->GetCullingBox().IsInside(box) != INSIDE)
+            {
+                LOGERROR("Drawable is not fully inside its octant's culling bounds: drawable box " + box.ToString() +
+                    " octant box " + octant->GetCullingBox().ToString());
+            }
+            #endif
+        }
+    }
+    
+    drawableUpdates_.Clear();
 }
 
 void Octree::AddManualDrawable(Drawable* drawable)
@@ -531,23 +601,17 @@ void Octree::RaycastSingle(RayOctreeQuery& query) const
 }
 
 void Octree::QueueUpdate(Drawable* drawable)
-{
-    drawableUpdates_.Push(drawable);
-    drawable->updateQueued_ = true;
-}
-
-void Octree::QueueReinsertion(Drawable* drawable)
 {
     Scene* scene = GetScene();
     if (scene && scene->IsThreadedUpdate())
     {
         MutexLock lock(octreeMutex_);
-        drawableReinsertions_.Push(drawable);
+        drawableUpdates_.Push(drawable);
     }
     else
-        drawableReinsertions_.Push(drawable);
-
-    drawable->reinsertionQueued_ = true;
+        drawableUpdates_.Push(drawable);
+    
+    drawable->updateQueued_ = true;
 }
 
 void Octree::CancelUpdate(Drawable* drawable)
@@ -556,90 +620,10 @@ void Octree::CancelUpdate(Drawable* drawable)
     drawable->updateQueued_ = false;
 }
 
-void Octree::CancelReinsertion(Drawable* drawable)
-{
-    drawableReinsertions_.Remove(drawable);
-    drawable->reinsertionQueued_ = false;
-}
-
 void Octree::DrawDebugGeometry(bool depthTest)
 {
     DebugRenderer* debug = GetComponent<DebugRenderer>();
     DrawDebugGeometry(debug, depthTest);
 }
 
-void Octree::UpdateDrawables(const FrameInfo& frame)
-{
-    // Let drawables update themselves before reinsertion. This can be used for animation
-    if (drawableUpdates_.Empty())
-        return;
-
-    PROFILE(UpdateDrawables);
-
-    // Perform updates in worker threads. Notify the scene that a threaded update is going on and components 
-    // (for example physics objects) should not perform non-threadsafe work when marked dirty
-    Scene* scene = GetScene();
-    WorkQueue* queue = GetSubsystem<WorkQueue>();
-    scene->BeginThreadedUpdate();
-
-    WorkItem item;
-    item.workFunction_ = UpdateDrawablesWork;
-    item.aux_ = const_cast<FrameInfo*>(&frame);
-
-    PODVector<Drawable*>::Iterator start = drawableUpdates_.Begin();
-    while (start != drawableUpdates_.End())
-    {
-        PODVector<Drawable*>::Iterator end = drawableUpdates_.End();
-        if (end - start > DRAWABLES_PER_WORK_ITEM)
-            end = start + DRAWABLES_PER_WORK_ITEM;
-
-        item.start_ = &(*start);
-        item.end_ = &(*end);
-        queue->AddWorkItem(item);
-
-        start = end;
-    }
-
-    queue->Complete(M_MAX_UNSIGNED);
-    scene->EndThreadedUpdate();
-    drawableUpdates_.Clear();
-}
-
-void Octree::ReinsertDrawables(const FrameInfo& frame)
-{
-    // Reinsert drawables that have been moved or resized, or that have been newly added to the octree and do not sit inside
-    // the proper octant yet
-    if (drawableReinsertions_.Empty())
-        return;
-
-    PROFILE(ReinsertToOctree);
-
-    for (PODVector<Drawable*>::Iterator i = drawableReinsertions_.Begin(); i != drawableReinsertions_.End(); ++i)
-    {
-        Drawable* drawable = *i;
-        drawable->reinsertionQueued_ = false;
-        Octant* octant = drawable->GetOctant();
-        const BoundingBox& box = drawable->GetWorldBoundingBox();
-
-        // Skip if no octant or does not belong to this octree anymore
-        if (!octant || octant->GetRoot() != this)
-            continue;
-        // Skip if still fits the current octant
-        if (drawable->IsOccludee() && octant->GetCullingBox().IsInside(box) == INSIDE && octant->CheckDrawableFit(box))
-            continue;
-
-        InsertDrawable(drawable);
-
-        #ifdef _DEBUG
-        // Verify that the drawable will be culled correctly
-        octant = drawable->GetOctant();
-        if (octant != this && octant->GetCullingBox().IsInside(box) != INSIDE)
-            LOGERROR("Drawable is not fully inside its octant's culling bounds: drawable box " + box.ToString() + " octant box " +
-                octant->GetCullingBox().ToString());
-        #endif
-    }
-
-    drawableReinsertions_.Clear();
-}
-
 }

+ 1 - 10
Source/Engine/Graphics/Octree.h

@@ -188,23 +188,14 @@ public:
     /// Return subdivision levels.
     unsigned GetNumLevels() const { return numLevels_; }
     
-    /// Mark drawable object as requiring an update.
+    /// Mark drawable object as requiring an update and a reinsertion.
     void QueueUpdate(Drawable* drawable);
-    /// Mark drawable object as requiring a reinsertion. Is thread-safe.
-    void QueueReinsertion(Drawable* drawable);
     /// Cancel drawable object's update.
     void CancelUpdate(Drawable* drawable);
-    /// Cancel drawable object's reinsertion.
-    void CancelReinsertion(Drawable* drawable);
     /// Visualize the component as debug geometry.
     void DrawDebugGeometry(bool depthTest);
     
 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);
-    
     /// Drawable objects that require update.
     PODVector<Drawable*> drawableUpdates_;
     /// Drawable objects that require reinsertion.

+ 7 - 0
Source/Engine/Graphics/ParticleEmitter.cpp

@@ -163,6 +163,10 @@ void ParticleEmitter::OnSetEnabled()
 
 void ParticleEmitter::Update(const FrameInfo& frame)
 {
+    // Cancel update if has only moved but does not actually need to animate the particles
+    if (!needUpdate_)
+        return;
+    
     // If there is an amount mismatch between particles and billboards, correct it
     if (particles_.Size() != billboards_.Size())
         SetNumBillboards(particles_.Size());
@@ -299,6 +303,8 @@ void ParticleEmitter::Update(const FrameInfo& frame)
     
     if (needCommit)
         Commit();
+    
+    needUpdate_ = false;
 }
 
 bool ParticleEmitter::Load(XMLFile* file)
@@ -919,6 +925,7 @@ void ParticleEmitter::HandleScenePostUpdate(StringHash eventType, VariantMap& ev
     if (updateInvisible_ || viewFrameNumber_ != lastUpdateFrameNumber_)
     {
         lastUpdateFrameNumber_ = viewFrameNumber_;
+        needUpdate_ = true;
         MarkForUpdate();
     }
 }

+ 7 - 5
Source/Engine/Graphics/ParticleEmitter.h

@@ -131,7 +131,7 @@ public:
     
     /// Handle enabled/disabled state change.
     virtual void OnSetEnabled();
-    /// Update before octree reinsertion. Is called from a worker thread. Needs to be requested with MarkForUpdate().
+    /// Update before octree reinsertion. Is called from a worker thread.
     virtual void Update(const FrameInfo& frame);
     
     /// Load emitter parameters from an XML file.
@@ -358,14 +358,16 @@ private:
     float sizeAdd_;
     /// Particle size multiplicative parameter.
     float sizeMul_;
-    /// Currently emitting flag.
-    bool emitting_;
-    /// Update when invisible flag.
-    bool updateInvisible_;
     /// Last scene timestep.
     float lastTimeStep_;
     /// Rendering framenumber on which was last updated.
     unsigned lastUpdateFrameNumber_;
+    /// Currently emitting flag.
+    bool emitting_;
+    /// Update when invisible flag.
+    bool updateInvisible_;
+    /// Need update flag.
+    bool needUpdate_;
 };
 
 }

+ 14 - 8
Source/Engine/Graphics/View.cpp

@@ -60,7 +60,6 @@ static const Vector3* directions[] =
     &Vector3::BACK
 };
 
-static const int CHECK_DRAWABLES_PER_WORK_ITEM = 128;
 static const float LIGHT_INTENSITY_THRESHOLD = 0.003f;
 
 /// %Frustum octree query for shadowcasters.
@@ -288,7 +287,7 @@ View::View(Context* context) :
     renderTarget_(0)
 {
     // Create octree query and scene results vector for each thread
-    unsigned numThreads = GetSubsystem<WorkQueue>()->GetNumThreads() + 1;
+    unsigned numThreads = GetSubsystem<WorkQueue>()->GetNumThreads() + 1; // Worker threads + main thread
     tempDrawables_.Resize(numThreads);
     sceneResults_.Resize(numThreads);
     frame_.camera_ = 0;
@@ -679,16 +678,20 @@ void View::GetDrawables()
             result.maxZ_ = 0.0f;
         }
         
+        int numWorkItems = queue->GetNumThreads() + 1; // Worker threads + main thread
+        int drawablesPerItem = tempDrawables.Size() / numWorkItems;
+        
         WorkItem item;
         item.workFunction_ = CheckVisibilityWork;
         item.aux_ = this;
         
         PODVector<Drawable*>::Iterator start = tempDrawables.Begin();
-        while (start != tempDrawables.End())
+        // Create a work item for each thread
+        for (int i = 0; i < numWorkItems; ++i)
         {
             PODVector<Drawable*>::Iterator end = tempDrawables.End();
-            if (end - start > CHECK_DRAWABLES_PER_WORK_ITEM)
-                end = start + CHECK_DRAWABLES_PER_WORK_ITEM;
+            if (i < numWorkItems - 1 && end - start > drawablesPerItem)
+                end = start + drawablesPerItem;
             
             item.start_ = &(*start);
             item.end_ = &(*end);
@@ -1100,16 +1103,19 @@ void View::UpdateGeometries()
         
         if (threadedGeometries_.Size())
         {
+            int numWorkItems = queue->GetNumThreads() + 1; // Worker threads + main thread
+            int drawablesPerItem = threadedGeometries_.Size() / numWorkItems;
+            
             WorkItem item;
             item.workFunction_ = UpdateDrawableGeometriesWork;
             item.aux_ = const_cast<FrameInfo*>(&frame_);
             
             PODVector<Drawable*>::Iterator start = threadedGeometries_.Begin();
-            while (start != threadedGeometries_.End())
+            for (int i = 0; i < numWorkItems; ++i)
             {
                 PODVector<Drawable*>::Iterator end = threadedGeometries_.End();
-                if (end - start > DRAWABLES_PER_WORK_ITEM)
-                    end = start + DRAWABLES_PER_WORK_ITEM;
+                if (i < numWorkItems - 1 && end - start > drawablesPerItem)
+                    end = start + drawablesPerItem;
                 
                 item.start_ = &(*start);
                 item.end_ = &(*end);

+ 18 - 2
Source/Engine/Scene/Scene.cpp

@@ -820,13 +820,29 @@ void Scene::CleanupConnection(Connection* connection)
 void Scene::MarkNetworkUpdate(Node* node)
 {
     if (node)
-        networkUpdateNodes_.Insert(node->GetID());
+    {
+        if (!threadedUpdate_)
+            networkUpdateNodes_.Insert(node->GetID());
+        else
+        {
+            MutexLock lock(sceneMutex_);
+            networkUpdateNodes_.Insert(node->GetID());
+        }
+    }
 }
 
 void Scene::MarkNetworkUpdate(Component* component)
 {
     if (component)
-        networkUpdateComponents_.Insert(component->GetID());
+    {
+        if (!threadedUpdate_)
+            networkUpdateComponents_.Insert(component->GetID());
+        else
+        {
+            MutexLock lock(sceneMutex_);
+            networkUpdateComponents_.Insert(component->GetID());
+        }
+    }
 }
 
 void Scene::MarkReplicationDirty(Node* node)

+ 0 - 1
Source/Extras/LuaScript/pkgs/Graphics/Drawable.pkg

@@ -8,7 +8,6 @@ static const unsigned DEFAULT_VIEWMASK;
 static const unsigned DEFAULT_LIGHTMASK;
 static const unsigned DEFAULT_SHADOWMASK;
 static const unsigned DEFAULT_ZONEMASK;
-static const int DRAWABLES_PER_WORK_ITEM;
 static const int MAX_VERTEX_LIGHTS;
 static const float ANIMATION_LOD_BASESCALE;
 

+ 1 - 2
Source/Extras/LuaScript/pkgs/Graphics/Octree.pkg

@@ -15,9 +15,8 @@ class Octree : public Component
     unsigned GetNumLevels() const;
     
     void QueueUpdate(Drawable* drawable);
-    void QueueReinsertion(Drawable* drawable);
     void DrawDebugGeometry(bool depthTest);
-    
+
     tolua_readonly tolua_property__get_set unsigned numLevels;
 };
 

+ 3 - 1
Source/Samples/20_HugeObjectCount/HugeObjectCount.cpp

@@ -190,9 +190,11 @@ void HugeObjectCount::AnimateObjects(float timeStep)
     PROFILE(AnimateObjects);
     
     const float ROTATE_SPEED = 15.0f;
+    // Rotate about the Z axis (roll)
+    Quaternion rotateQuat(ROTATE_SPEED * timeStep, Vector3::FORWARD);
 
     for (unsigned i = 0; i < boxNodes_.Size(); ++i)
-        boxNodes_[i]->Roll(ROTATE_SPEED * timeStep);
+        boxNodes_[i]->Rotate(rotateQuat);
 }
 
 void HugeObjectCount::HandleUpdate(StringHash eventType, VariantMap& eventData)