Răsfoiți Sursa

Threaded light queries.

Lasse Öörni 14 ani în urmă
părinte
comite
cccb072a3d

+ 1 - 1
Engine/Engine/DebugHud.cpp

@@ -119,7 +119,7 @@ void DebugHud::Update(float timeStep)
             "\nViews " + String(renderer->GetNumViews()) + 
             "\nViews " + String(renderer->GetNumViews()) + 
             "\nLights " + String(renderer->GetNumLights(true)) +
             "\nLights " + String(renderer->GetNumLights(true)) +
             "\nShadowmaps " + String(renderer->GetNumShadowMaps(true)) +
             "\nShadowmaps " + String(renderer->GetNumShadowMaps(true)) +
-            "\nOccluders " + String(renderer->GetNumOccluders(true) + renderer->GetNumShadowOccluders(true));
+            "\nOccluders " + String(renderer->GetNumOccluders(true));
         
         
         statsText_->SetText(stats);
         statsText_->SetText(stats);
     }
     }

+ 0 - 1
Engine/Engine/GraphicsAPI.cpp

@@ -798,7 +798,6 @@ static void RegisterRenderer(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Renderer", "uint get_numLights(bool) const", asMETHOD(Renderer, GetNumLights), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "uint get_numLights(bool) const", asMETHOD(Renderer, GetNumLights), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "uint get_numShadowMaps(bool) const", asMETHOD(Renderer, GetNumShadowMaps), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "uint get_numShadowMaps(bool) const", asMETHOD(Renderer, GetNumShadowMaps), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "uint get_numOccluders(bool) const", asMETHOD(Renderer, GetNumOccluders), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "uint get_numOccluders(bool) const", asMETHOD(Renderer, GetNumOccluders), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Renderer", "uint get_numShadowOccluders(bool) const", asMETHOD(Renderer, GetNumShadowOccluders), asCALL_THISCALL);
     engine->RegisterGlobalFunction("Renderer@+ get_renderer()", asFUNCTION(GetRenderer), asCALL_CDECL);
     engine->RegisterGlobalFunction("Renderer@+ get_renderer()", asFUNCTION(GetRenderer), asCALL_CDECL);
 }
 }
 
 

+ 1 - 1
Engine/Graphics/AnimatedModel.h

@@ -52,7 +52,7 @@ public:
     virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
     virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
     /// Update before octree reinsertion. Animation is updated here.
     /// Update before octree reinsertion. Animation is updated here.
     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. May be called from worker thread(s), possibly re-entrantly.
     virtual void UpdateDistance(const FrameInfo& frame);
     virtual void UpdateDistance(const FrameInfo& frame);
     /// Prepare geometry for rendering. Called from a worker thread if possible (no GPU update.)
     /// 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);

+ 1 - 1
Engine/Graphics/BillboardSet.h

@@ -63,7 +63,7 @@ public:
     /// Register object factory.
     /// Register object factory.
     static void RegisterObject(Context* context);
     static void RegisterObject(Context* context);
     
     
-    /// Calculate distance and LOD level for rendering.
+    /// Calculate distance and LOD level for rendering.  May be called from worker thread(s), possibly re-entrantly.
     virtual void UpdateDistance(const FrameInfo& frame);
     virtual void UpdateDistance(const FrameInfo& frame);
     /// Return whether the next geometry update will touch actual GPU resources.
     /// Return whether the next geometry update will touch actual GPU resources.
     virtual bool GetUpdateOnGPU() { return true; }
     virtual bool GetUpdateOnGPU() { return true; }

+ 1 - 1
Engine/Graphics/Drawable.h

@@ -91,7 +91,7 @@ public:
     virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
     virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
     /// 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. 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.  May be called from worker thread(s), possibly re-entrantly.
     virtual void UpdateDistance(const FrameInfo& frame);
     virtual void UpdateDistance(const FrameInfo& frame);
     /// Prepare geometry for rendering.
     /// Prepare geometry for rendering.
     virtual void UpdateGeometry(const FrameInfo& frame) {}
     virtual void UpdateGeometry(const FrameInfo& frame) {}

+ 0 - 2
Engine/Graphics/Light.cpp

@@ -26,7 +26,6 @@
 #include "Context.h"
 #include "Context.h"
 #include "DebugRenderer.h"
 #include "DebugRenderer.h"
 #include "Light.h"
 #include "Light.h"
-#include "Log.h"
 #include "OctreeQuery.h"
 #include "OctreeQuery.h"
 #include "Profiler.h"
 #include "Profiler.h"
 #include "ResourceCache.h"
 #include "ResourceCache.h"
@@ -192,7 +191,6 @@ void Light::ProcessRayQuery(RayOctreeQuery& query, float initialDistance)
             float distance = query.ray_.HitDistance(GetFrustum());
             float distance = query.ray_.HitDistance(GetFrustum());
             if (distance < query.maxDistance_)
             if (distance < query.maxDistance_)
             {
             {
-                LOGINFO("Frustum hitdistance: " + String(distance));
                 RayQueryResult result;
                 RayQueryResult result;
                 result.drawable_ = this;
                 result.drawable_ = this;
                 result.node_ = GetNode();
                 result.node_ = GetNode();

+ 1 - 1
Engine/Graphics/Light.h

@@ -160,7 +160,7 @@ public:
     virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src);
     virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src);
     /// Process octree raycast.
     /// Process octree raycast.
     virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
     virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
-    /// Calculate distance for rendering.
+    /// Calculate distance for rendering. May be called from worker thread(s), possibly re-entrantly.
     virtual void UpdateDistance(const FrameInfo& frame);
     virtual void UpdateDistance(const FrameInfo& frame);
     /// Add debug geometry to the debug graphics.
     /// Add debug geometry to the debug graphics.
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);

+ 0 - 2
Engine/Graphics/Octree.cpp

@@ -373,8 +373,6 @@ void Octree::RemoveManualDrawable(Drawable* drawable)
 
 
 void Octree::GetDrawables(OctreeQuery& query) const
 void Octree::GetDrawables(OctreeQuery& query) const
 {
 {
-    PROFILE(OctreeQuery);
-    
     query.result_.Clear();
     query.result_.Clear();
     GetDrawablesInternal(query, false);
     GetDrawablesInternal(query, false);
 }
 }

+ 2 - 30
Engine/Graphics/Renderer.cpp

@@ -493,36 +493,6 @@ unsigned Renderer::GetNumOccluders(bool allViews) const
     return numOccluders;
     return numOccluders;
 }
 }
 
 
-unsigned Renderer::GetNumShadowOccluders(bool allViews) const
-{
-    unsigned numShadowOccluders = 0;
-    unsigned lastView = allViews ? numViews_ : 1;
-    
-    for (unsigned i = 0; i < lastView; ++i)
-        numShadowOccluders += views_[i]->GetShadowOccluders().Size();
-    
-    return numShadowOccluders;
-}
-
-const OcclusionBuffer* Renderer::GetOcclusionBuffer(float aspectRatio, bool halfResolution)
-{
-    // Return an occlusion buffer for debug output purposes. Do not allocate new
-    int width = occlusionBufferSize_;
-    int height = (int)(occlusionBufferSize_ / aspectRatio);
-    if (halfResolution)
-    {
-        width >>= 1;
-        height >>= 1;
-    }
-    int searchKey = (width << 16) | height;
-    
-    HashMap<int, SharedPtr<OcclusionBuffer> >::Iterator i = occlusionBuffers_.Find(searchKey);
-    if (i != occlusionBuffers_.End())
-        return i->second_;
-    else
-        return 0;
-}
-
 void Renderer::Update(float timeStep)
 void Renderer::Update(float timeStep)
 {
 {
     PROFILE(UpdateViews);
     PROFILE(UpdateViews);
@@ -1023,6 +993,8 @@ void Renderer::SetBatchShaders(Batch& batch, Technique* technique, Pass* pass, b
 
 
 Camera* Renderer::CreateShadowCamera()
 Camera* Renderer::CreateShadowCamera()
 {
 {
+    MutexLock lock(rendererMutex_);
+    
     if (numShadowCameras_ >= shadowCameraNodes_.Size())
     if (numShadowCameras_ >= shadowCameraNodes_.Size())
     {
     {
         SharedPtr<Node> newNode(new Node(context_));
         SharedPtr<Node> newNode(new Node(context_));

+ 3 - 4
Engine/Graphics/Renderer.h

@@ -28,6 +28,7 @@
 #include "Drawable.h"
 #include "Drawable.h"
 #include "HashMap.h"
 #include "HashMap.h"
 #include "HashSet.h"
 #include "HashSet.h"
+#include "Mutex.h"
 #include "RenderSurface.h"
 #include "RenderSurface.h"
 
 
 class DebugRenderer;
 class DebugRenderer;
@@ -193,10 +194,6 @@ public:
     unsigned GetNumShadowMaps(bool allViews = false) const;
     unsigned GetNumShadowMaps(bool allViews = false) const;
     /// Return number of occluders rendered.
     /// Return number of occluders rendered.
     unsigned GetNumOccluders(bool allViews = false) const;
     unsigned GetNumOccluders(bool allViews = false) const;
-    /// Return number of directional light shadow occluders rendered.
-    unsigned GetNumShadowOccluders(bool allViews = false) const;
-    /// Return an occlusion buffer for inspection.
-    const OcclusionBuffer* GetOcclusionBuffer(float aspectRatio, bool halfResolution = false);
     /// Return the default zone.
     /// Return the default zone.
     Zone* GetDefaultZone() const { return defaultZone_; }
     Zone* GetDefaultZone() const { return defaultZone_; }
     /// Return the default material.
     /// Return the default material.
@@ -321,6 +318,8 @@ private:
     HashSet<Octree*> updateOctrees_;
     HashSet<Octree*> updateOctrees_;
     /// Techniques for which missing shader error has been displayed.
     /// Techniques for which missing shader error has been displayed.
     HashSet<Technique*> shaderErrorDisplayed_;
     HashSet<Technique*> shaderErrorDisplayed_;
+    /// Mutex for creating shadow camers.
+    Mutex rendererMutex_;
     /// Vertex shader format.
     /// Vertex shader format.
     String vsFormat_;
     String vsFormat_;
     /// Pixel shader format.
     /// Pixel shader format.

+ 1 - 1
Engine/Graphics/Skybox.h

@@ -40,7 +40,7 @@ public:
     
     
     /// Process octree raycast.
     /// Process octree raycast.
     void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
     void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
-    /// Calculate distance and LOD level for rendering.
+    /// Calculate distance and LOD level for rendering. May be called from worker thread(s), possibly re-entrantly.
     virtual void UpdateDistance(const FrameInfo& frame);
     virtual void UpdateDistance(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);

+ 1 - 1
Engine/Graphics/StaticModel.h

@@ -42,7 +42,7 @@ public:
     
     
     /// Process octree raycast.
     /// Process octree raycast.
     virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
     virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
-    /// Calculate distance and LOD level for rendering.
+    /// Calculate distance and LOD level for rendering. May be called from worker thread(s), possibly re-entrantly.
     virtual void UpdateDistance(const FrameInfo& frame);
     virtual void UpdateDistance(const FrameInfo& frame);
     /// Return number of batches.
     /// Return number of batches.
     virtual unsigned GetNumBatches();
     virtual unsigned GetNumBatches();

+ 141 - 90
Engine/Graphics/View.cpp

@@ -57,6 +57,14 @@ static const Vector3 directions[] =
     Vector3(0.0f, 0.0f, -1.0f)
     Vector3(0.0f, 0.0f, -1.0f)
 };
 };
 
 
+void ProcessLightWork(const WorkItem* item, unsigned threadIndex)
+{
+    View* view = reinterpret_cast<View*>(item->aux_);
+    LightQueryResult* query = reinterpret_cast<LightQueryResult*>(item->start_);
+    
+    view->ProcessLight(*query);
+}
+
 void UpdateDrawableGeometriesWork(const WorkItem* item, unsigned threadIndex)
 void UpdateDrawableGeometriesWork(const WorkItem* item, unsigned threadIndex)
 {
 {
     const FrameInfo& frame = *(reinterpret_cast<FrameInfo*>(item->aux_));
     const FrameInfo& frame = *(reinterpret_cast<FrameInfo*>(item->aux_));
@@ -189,7 +197,6 @@ void View::Update(const FrameInfo& frame)
     geometryDepthBounds_.Clear();
     geometryDepthBounds_.Clear();
     lights_.Clear();
     lights_.Clear();
     occluders_.Clear();
     occluders_.Clear();
-    shadowOccluders_.Clear();
     baseQueue_.Clear();
     baseQueue_.Clear();
     preAlphaQueue_.Clear();
     preAlphaQueue_.Clear();
     alphaQueue_.Clear();
     alphaQueue_.Clear();
@@ -430,27 +437,65 @@ void View::GetDrawables()
 
 
 void View::GetBatches()
 void View::GetBatches()
 {
 {
-    maxLightsDrawables_.Clear();
-    lightQueueIndex_.Clear();
-    
-    bool fallback = graphics_->GetFallback();
+
+    WorkQueue* queue = GetSubsystem<WorkQueue>();
     
     
-    // Go through lights
+    // Process lit geometries and shadow casters for each light
     {
     {
-        PROFILE_MULTIPLE(GetLightBatches, lights_.Size());
+        PROFILE_MULTIPLE(ProcessLights, lights_.Size());
+        
+        lightQueryResults_.Resize(lights_.Size());
+        for (unsigned i = 0; i < lightQueryResults_.Size(); ++i)
+        {
+            LightQueryResult& query = lightQueryResults_[i];
+            query.light_ = lights_[i];
+            
+            // Do not include shadowed directional lights in the work queue, as occlusion can not yet be threaded
+            query.threaded_ = query.light_->GetLightType() != LIGHT_DIRECTIONAL || !query.light_->GetCastShadows();
+            if (query.threaded_)
+            {
+                WorkItem item;
+                item.workFunction_ = ProcessLightWork;
+                item.start_ = &query;
+                item.aux_ = this;
+                queue->AddWorkItem(item);
+            }
+        }
         
         
+        // Process shadowed directional lights in the main thread
+        for (unsigned i = 0; i < lightQueryResults_.Size(); ++i)
+        {
+            LightQueryResult& query = lightQueryResults_[i];
+            if (!query.threaded_)
+                ProcessLight(query);
+        }
+        
+        // Ensure all lights have been processed before proceeding
+        queue->Complete();
+    }
+    
+    // Build light queues and lit batches
+    {
         // Preallocate enough light queues so that we can store pointers to them without having to worry about the
         // Preallocate enough light queues so that we can store pointers to them without having to worry about the
         // vector reallocating itself
         // vector reallocating itself
         lightQueues_.Resize(lights_.Size());
         lightQueues_.Resize(lights_.Size());
-        
         unsigned lightQueueCount = 0;
         unsigned lightQueueCount = 0;
-        for (unsigned i = 0; i < lights_.Size(); ++i)
+        bool fallback = graphics_->GetFallback();
+        
+        maxLightsDrawables_.Clear();
+        lightQueueIndex_.Clear();
+        
+        for (unsigned i = 0; i < lightQueryResults_.Size(); ++i)
         {
         {
-            Light* light = lights_[i];
-            unsigned shadowSplits = ProcessLight(light);
-            if (litGeometries_.Empty())
+            LightQueryResult& query = lightQueryResults_[i];
+            if (query.litGeometries_.Empty())
                 continue;
                 continue;
             
             
+            PROFILE(GetLightBatches);
+            
+            Light* light = query.light_;
+            unsigned shadowSplits = query.numSplits_;
+            
             // Initialize light queue. Store pointer-to-index mapping so that the queue can be found later
             // Initialize light queue. Store pointer-to-index mapping so that the queue can be found later
             LightBatchQueue& lightQueue = lightQueues_[lightQueueCount];
             LightBatchQueue& lightQueue = lightQueues_[lightQueueCount];
             lightQueueIndex_[light] = lightQueueCount;
             lightQueueIndex_[light] = lightQueueCount;
@@ -472,19 +517,25 @@ void View::GetBatches()
             for (unsigned j = 0; j < shadowSplits; ++j)
             for (unsigned j = 0; j < shadowSplits; ++j)
             {
             {
                 ShadowBatchQueue& shadowQueue = lightQueue.shadowSplits_[j];
                 ShadowBatchQueue& shadowQueue = lightQueue.shadowSplits_[j];
-                Camera* shadowCamera = shadowCameras_[j];
-                shadowQueue.shadowCamera_ = shadowCameras_[j];
-                shadowQueue.nearSplit_ = shadowNearSplits_[j];
-                shadowQueue.farSplit_ = shadowFarSplits_[j];
+                Camera* shadowCamera = query.shadowCameras_[j];
+                shadowQueue.shadowCamera_ = shadowCamera;
+                shadowQueue.nearSplit_ = query.shadowNearSplits_[j];
+                shadowQueue.farSplit_ = query.shadowFarSplits_[j];
                 
                 
                 // Setup the shadow split viewport and finalize shadow camera parameters
                 // Setup the shadow split viewport and finalize shadow camera parameters
                 shadowQueue.shadowViewport_ = GetShadowMapViewport(light, j, lightQueue.shadowMap_);
                 shadowQueue.shadowViewport_ = GetShadowMapViewport(light, j, lightQueue.shadowMap_);
-                FinalizeShadowCamera(shadowCamera, light, shadowQueue.shadowViewport_, shadowCasterBox_[j]);
+                FinalizeShadowCamera(shadowCamera, light, shadowQueue.shadowViewport_, query.shadowCasterBox_[j]);
                 
                 
                 // Loop through shadow casters
                 // Loop through shadow casters
-                for (unsigned k = 0; k < shadowCasters_[j].Size(); ++k)
+                for (unsigned k = 0; k < query.shadowCasters_[j].Size(); ++k)
                 {
                 {
-                    Drawable* drawable = shadowCasters_[j][k];
+                    Drawable* drawable = query.shadowCasters_[j][k];
+                    if (!drawable->IsInView(frame_, false))
+                    {
+                        drawable->MarkInView(frame_, false);
+                        allGeometries_.Push(drawable);
+                    }
+                    
                     unsigned numBatches = drawable->GetNumBatches();
                     unsigned numBatches = drawable->GetNumBatches();
                     
                     
                     for (unsigned l = 0; l < numBatches; ++l)
                     for (unsigned l = 0; l < numBatches; ++l)
@@ -512,9 +563,9 @@ void View::GetBatches()
             }
             }
             
             
             // Loop through lit geometries
             // Loop through lit geometries
-            for (unsigned j = 0; j < litGeometries_.Size(); ++j)
+            for (unsigned j = 0; j < query.litGeometries_.Size(); ++j)
             {
             {
-                Drawable* drawable = litGeometries_[j];
+                Drawable* drawable = query.litGeometries_[j];
                 drawable->AddLight(light);
                 drawable->AddLight(light);
                 
                 
                 // If drawable limits maximum lights, only record the light, and check maximum count / build batches later
                 // If drawable limits maximum lights, only record the light, and check maximum count / build batches later
@@ -553,7 +604,7 @@ void View::GetBatches()
         }
         }
     }
     }
     
     
-    // Go through geometries for base pass batches
+    // Build base pass batches
     {
     {
         PROFILE(GetBaseBatches);
         PROFILE(GetBaseBatches);
         for (unsigned i = 0; i < geometries_.Size(); ++i)
         for (unsigned i = 0; i < geometries_.Size(); ++i)
@@ -909,11 +960,12 @@ void View::DrawOccluders(OcclusionBuffer* buffer, const PODVector<Drawable*>& oc
     }
     }
 }
 }
 
 
-unsigned View::ProcessLight(Light* light)
+void View::ProcessLight(LightQueryResult& query)
 {
 {
+    Light* light = query.light_;
+    
     // Check if light should be shadowed
     // Check if light should be shadowed
     bool isShadowed = drawShadows_ && light->GetCastShadows() && light->GetShadowIntensity() < 1.0f;
     bool isShadowed = drawShadows_ && light->GetCastShadows() && light->GetShadowIntensity() < 1.0f;
-    unsigned shadowSplits = 0;
     // If shadow distance non-zero, check it
     // If shadow distance non-zero, check it
     if (isShadowed && light->GetShadowDistance() > 0.0f && light->GetDistance() > light->GetShadowDistance())
     if (isShadowed && light->GetShadowDistance() > 0.0f && light->GetDistance() > light->GetShadowDistance())
         isShadowed = false;
         isShadowed = false;
@@ -921,7 +973,7 @@ unsigned View::ProcessLight(Light* light)
     LightType type = light->GetLightType();
     LightType type = light->GetLightType();
     
     
     // Get lit geometries. They must match the light mask and be inside the main camera frustum to be considered
     // Get lit geometries. They must match the light mask and be inside the main camera frustum to be considered
-    litGeometries_.Clear();
+    query.litGeometries_.Clear();
     
     
     switch (type)
     switch (type)
     {
     {
@@ -929,44 +981,48 @@ unsigned View::ProcessLight(Light* light)
         for (unsigned i = 0; i < geometries_.Size(); ++i)
         for (unsigned i = 0; i < geometries_.Size(); ++i)
         {
         {
             if (GetLightMask(geometries_[i]) & light->GetLightMask())
             if (GetLightMask(geometries_[i]) & light->GetLightMask())
-                litGeometries_.Push(geometries_[i]);
+                query.litGeometries_.Push(geometries_[i]);
         }
         }
         break;
         break;
         
         
     case LIGHT_SPOT:
     case LIGHT_SPOT:
         {
         {
-            FrustumOctreeQuery query(tempDrawables_, light->GetFrustum(), DRAWABLE_GEOMETRY, camera_->GetViewMask());
-            octree_->GetDrawables(query);
-            for (unsigned i = 0; i < tempDrawables_.Size(); ++i)
+            FrustumOctreeQuery octreeQuery(query.tempDrawables_, light->GetFrustum(), DRAWABLE_GEOMETRY, camera_->GetViewMask());
+            octree_->GetDrawables(octreeQuery);
+            for (unsigned i = 0; i < query.tempDrawables_.Size(); ++i)
             {
             {
-                if (tempDrawables_[i]->IsInView(frame_) && (GetLightMask(tempDrawables_[i]) & light->GetLightMask()))
-                    litGeometries_.Push(tempDrawables_[i]);
+                if (query.tempDrawables_[i]->IsInView(frame_) && (GetLightMask(query.tempDrawables_[i]) & light->GetLightMask()))
+                    query.litGeometries_.Push(query.tempDrawables_[i]);
             }
             }
         }
         }
         break;
         break;
         
         
     case LIGHT_POINT:
     case LIGHT_POINT:
         {
         {
-            SphereOctreeQuery query(tempDrawables_, Sphere(light->GetWorldPosition(), light->GetRange()), DRAWABLE_GEOMETRY,
-                camera_->GetViewMask());
-            octree_->GetDrawables(query);
-            for (unsigned i = 0; i < tempDrawables_.Size(); ++i)
+            SphereOctreeQuery octreeQuery(query.tempDrawables_, Sphere(light->GetWorldPosition(), light->GetRange()),
+                DRAWABLE_GEOMETRY, camera_->GetViewMask());
+            octree_->GetDrawables(octreeQuery);
+            for (unsigned i = 0; i < query.tempDrawables_.Size(); ++i)
             {
             {
-                if (tempDrawables_[i]->IsInView(frame_) && (GetLightMask(tempDrawables_[i]) & light->GetLightMask()))
-                    litGeometries_.Push(tempDrawables_[i]);
+                if (query.tempDrawables_[i]->IsInView(frame_) && (GetLightMask(query.tempDrawables_[i]) & light->GetLightMask()))
+                    query.litGeometries_.Push(query.tempDrawables_[i]);
             }
             }
         }
         }
         break;
         break;
     }
     }
     
     
     // If no lit geometries or not shadowed, no need to process shadow cameras
     // If no lit geometries or not shadowed, no need to process shadow cameras
-    if (litGeometries_.Empty() || !isShadowed)
-        return 0;
+    if (query.litGeometries_.Empty() || !isShadowed)
+    {
+        query.numSplits_ = 0;
+        return;
+    }
     
     
     // Determine number of shadow cameras and setup their initial positions
     // Determine number of shadow cameras and setup their initial positions
-    shadowSplits = SetupShadowCameras(light);
+    SetupShadowCameras(query);
     
     
     // For a shadowed directional light, get occluders once using the whole (non-split) light frustum
     // For a shadowed directional light, get occluders once using the whole (non-split) light frustum
+    // Note: directional light query can not be threaded due to the occlusion
     bool useOcclusion = false;
     bool useOcclusion = false;
     OcclusionBuffer* buffer = 0;
     OcclusionBuffer* buffer = 0;
     
     
@@ -974,13 +1030,13 @@ unsigned View::ProcessLight(Light* light)
     {
     {
         // This shadow camera is never used for actually querying shadow casters, just occluders
         // This shadow camera is never used for actually querying shadow casters, just occluders
         Camera* shadowCamera = renderer_->CreateShadowCamera();
         Camera* shadowCamera = renderer_->CreateShadowCamera();
-        SetupDirLightShadowCamera(shadowCamera, light, 0.0f, Min(light->GetShadowCascade().GetShadowRange(), camera_->GetFarClip()),
-            true);
+        SetupDirLightShadowCamera(shadowCamera, light, 0.0f, Min(light->GetShadowCascade().GetShadowRange(),
+            camera_->GetFarClip()), true);
         
         
         // Get occluders, which must be shadow-casting themselves
         // Get occluders, which must be shadow-casting themselves
-        FrustumOctreeQuery query(shadowOccluders_, shadowCamera->GetFrustum(), DRAWABLE_GEOMETRY, camera_->GetViewMask(),
+        FrustumOctreeQuery octreeQuery(shadowOccluders_, shadowCamera->GetFrustum(), DRAWABLE_GEOMETRY, camera_->GetViewMask(),
             true, true);
             true, true);
-        octree_->GetDrawables(query);
+        octree_->GetDrawables(octreeQuery);
         
         
         UpdateOccluders(shadowOccluders_, shadowCamera);
         UpdateOccluders(shadowOccluders_, shadowCamera);
         
         
@@ -997,11 +1053,9 @@ unsigned View::ProcessLight(Light* light)
     
     
     // Process each split for shadow casters
     // Process each split for shadow casters
     bool hasShadowCasters = false;
     bool hasShadowCasters = false;
-    for (unsigned i = 0; i < shadowSplits; ++i)
+    for (unsigned i = 0; i < query.numSplits_; ++i)
     {
     {
-        shadowCasters_[i].Clear();
-        shadowCasterBox_[i].defined_ = false;
-        Camera* shadowCamera = shadowCameras_[i];
+        Camera* shadowCamera = query.shadowCameras_[i];
         Frustum shadowCameraFrustum = shadowCamera->GetFrustum();
         Frustum shadowCameraFrustum = shadowCamera->GetFrustum();
         
         
         // For point light check that the face is visible: if not, can skip the split
         // For point light check that the face is visible: if not, can skip the split
@@ -1015,54 +1069,56 @@ unsigned View::ProcessLight(Light* light)
         // For directional light check that the split is inside the visible scene: if not, can skip the split
         // For directional light check that the split is inside the visible scene: if not, can skip the split
         if (type == LIGHT_DIRECTIONAL)
         if (type == LIGHT_DIRECTIONAL)
         {
         {
-            if (sceneViewBox_.min_.z_ > shadowFarSplits_[i])
+            if (sceneViewBox_.min_.z_ > query.shadowFarSplits_[i])
                 continue;
                 continue;
-            if (sceneViewBox_.max_.z_ < shadowNearSplits_[i])
+            if (sceneViewBox_.max_.z_ < query.shadowNearSplits_[i])
                 continue;
                 continue;
         }
         }
         
         
         if (!useOcclusion)
         if (!useOcclusion)
         {
         {
             // For spot light (which has only one shadow split) we can optimize by reusing the query for
             // For spot light (which has only one shadow split) we can optimize by reusing the query for
-            // lit geometries, whose result still exists in tempDrawables_
+            // lit geometries, whose result still exists in query.tempDrawables_
             if (type != LIGHT_SPOT)
             if (type != LIGHT_SPOT)
             {
             {
-                FrustumOctreeQuery query(tempDrawables_, shadowCameraFrustum, DRAWABLE_GEOMETRY, camera_->GetViewMask(),
-                    false, true);
-                octree_->GetDrawables(query);
+                FrustumOctreeQuery octreeQuery(query.tempDrawables_, shadowCameraFrustum, DRAWABLE_GEOMETRY,
+                    camera_->GetViewMask(), false, true);
+                octree_->GetDrawables(octreeQuery);
             }
             }
         }
         }
         else
         else
         {
         {
-            OccludedFrustumOctreeQuery query(tempDrawables_, shadowCamera->GetFrustum(), buffer,
+            OccludedFrustumOctreeQuery octreeQuery(query.tempDrawables_, shadowCamera->GetFrustum(), buffer,
                 DRAWABLE_GEOMETRY, camera_->GetViewMask(), false, true);
                 DRAWABLE_GEOMETRY, camera_->GetViewMask(), false, true);
-            octree_->GetDrawables(query);
+            octree_->GetDrawables(octreeQuery);
         }
         }
         
         
         // Check which shadow casters actually contribute to the shadowing
         // Check which shadow casters actually contribute to the shadowing
-        ProcessShadowCasters(light, i, tempDrawables_, shadowCasterBox_[i]);
-        if (shadowCasters_[i].Size())
+        ProcessShadowCasters(query, i);
+        if (query.shadowCasters_[i].Size())
             hasShadowCasters = true;
             hasShadowCasters = true;
     }
     }
     
     
     // If no shadow casters, the light can be rendered unshadowed. At this point we have not allocated a shadow map yet, so the
     // If no shadow casters, the light can be rendered unshadowed. At this point we have not allocated a shadow map yet, so the
     // only cost has been the shadow camera setup & queries
     // only cost has been the shadow camera setup & queries
     if (!hasShadowCasters)
     if (!hasShadowCasters)
-        shadowSplits = 0;
-    
-    return shadowSplits;
+        query.numSplits_ = 0;
 }
 }
 
 
-void View::ProcessShadowCasters(Light* light, unsigned splitIndex, const PODVector<Drawable*>& result, BoundingBox& shadowCasterBox)
+void View::ProcessShadowCasters(LightQueryResult& query, unsigned splitIndex)
 {
 {
+    Light* light = query.light_;
     Matrix3x4 lightView;
     Matrix3x4 lightView;
     Matrix4 lightProj;
     Matrix4 lightProj;
     
     
-    Camera* shadowCamera = shadowCameras_[splitIndex];
+    Camera* shadowCamera = query.shadowCameras_[splitIndex];
     lightView = shadowCamera->GetInverseWorldTransform();
     lightView = shadowCamera->GetInverseWorldTransform();
     lightProj = shadowCamera->GetProjection();
     lightProj = shadowCamera->GetProjection();
     bool dirLight = shadowCamera->IsOrthographic();
     bool dirLight = shadowCamera->IsOrthographic();
     
     
+    query.shadowCasters_[splitIndex].Clear();
+    query.shadowCasterBox_[splitIndex].defined_ = false;
+    
     // Transform scene frustum into shadow camera's view space for shadow caster visibility check. For point & spot lights,
     // Transform scene frustum into shadow camera's view space for shadow caster visibility check. For point & spot lights,
     // we can use the whole scene frustum. For directional lights, use the intersection of the scene frustum and the split
     // we can use the whole scene frustum. For directional lights, use the intersection of the scene frustum and the split
     // frustum, so that shadow casters do not get rendered into unnecessary splits
     // frustum, so that shadow casters do not get rendered into unnecessary splits
@@ -1070,8 +1126,8 @@ void View::ProcessShadowCasters(Light* light, unsigned splitIndex, const PODVect
     if (!dirLight)
     if (!dirLight)
         lightViewFrustum = camera_->GetSplitFrustum(sceneViewBox_.min_.z_, sceneViewBox_.max_.z_).Transformed(lightView);
         lightViewFrustum = camera_->GetSplitFrustum(sceneViewBox_.min_.z_, sceneViewBox_.max_.z_).Transformed(lightView);
     else
     else
-        lightViewFrustum = camera_->GetSplitFrustum(Max(sceneViewBox_.min_.z_, shadowNearSplits_[splitIndex]),
-            Min(sceneViewBox_.max_.z_, shadowFarSplits_[splitIndex])).Transformed(lightView);
+        lightViewFrustum = camera_->GetSplitFrustum(Max(sceneViewBox_.min_.z_, query.shadowNearSplits_[splitIndex]),
+            Min(sceneViewBox_.max_.z_, query.shadowFarSplits_[splitIndex])).Transformed(lightView);
     
     
     BoundingBox lightViewFrustumBox(lightViewFrustum);
     BoundingBox lightViewFrustumBox(lightViewFrustum);
      
      
@@ -1082,14 +1138,16 @@ void View::ProcessShadowCasters(Light* light, unsigned splitIndex, const PODVect
     BoundingBox lightViewBox;
     BoundingBox lightViewBox;
     BoundingBox lightProjBox;
     BoundingBox lightProjBox;
     
     
-    for (unsigned i = 0; i < result.Size(); ++i)
+    for (unsigned i = 0; i < query.tempDrawables_.Size(); ++i)
     {
     {
-        Drawable* drawable = static_cast<Drawable*>(result[i]);
+        Drawable* drawable = static_cast<Drawable*>(query.tempDrawables_[i]);
         // In case this is a spot light query result reused for optimization, we may have non-shadowcasters included.
         // In case this is a spot light query result reused for optimization, we may have non-shadowcasters included.
         // Check for that first
         // Check for that first
         if (!drawable->GetCastShadows())
         if (!drawable->GetCastShadows())
             continue;
             continue;
         
         
+        // Note: as lights are processed threaded, it is possible a drawable's UpdateDistance() function is called several
+        // times. However, this should not cause problems as no scene modification happens at this point.
         if (!drawable->IsInView(frame_, false))
         if (!drawable->IsInView(frame_, false))
             drawable->UpdateDistance(frame_);
             drawable->UpdateDistance(frame_);
         
         
@@ -1107,21 +1165,15 @@ void View::ProcessShadowCasters(Light* light, unsigned splitIndex, const PODVect
         
         
         if (IsShadowCasterVisible(drawable, lightViewBox, shadowCamera, lightView, lightViewFrustum, lightViewFrustumBox))
         if (IsShadowCasterVisible(drawable, lightViewBox, shadowCamera, lightView, lightViewFrustum, lightViewFrustumBox))
         {
         {
-            if (!drawable->IsInView(frame_, false))
-            {
-                drawable->MarkInView(frame_, false);
-                allGeometries_.Push(drawable);
-            }
-            
             // Merge to shadow caster bounding box and add to the list
             // Merge to shadow caster bounding box and add to the list
             if (dirLight)
             if (dirLight)
-                shadowCasterBox.Merge(lightViewBox);
+                query.shadowCasterBox_[splitIndex].Merge(lightViewBox);
             else
             else
             {
             {
                 lightProjBox = lightViewBox.Projected(lightProj);
                 lightProjBox = lightViewBox.Projected(lightProj);
-                shadowCasterBox.Merge(lightProjBox);
+                query.shadowCasterBox_[splitIndex].Merge(lightProjBox);
             }
             }
-            shadowCasters_[splitIndex].Push(drawable);
+            query.shadowCasters_[splitIndex].Push(drawable);
         }
         }
     }
     }
 }
 }
@@ -1299,13 +1351,16 @@ const Rect& View::GetLightScissor(Light* light)
     }
     }
 }
 }
 
 
-unsigned View::SetupShadowCameras(Light* light)
+void View::SetupShadowCameras(LightQueryResult& query)
 {
 {
+    Light* light = query.light_;
+    
     LightType type = light->GetLightType();
     LightType type = light->GetLightType();
+    int splits = 0;
+    
     if (type == LIGHT_DIRECTIONAL)
     if (type == LIGHT_DIRECTIONAL)
     {
     {
         const CascadeParameters& cascade = light->GetShadowCascade();
         const CascadeParameters& cascade = light->GetShadowCascade();
-        int splits = 0;
         
         
         float nearSplit = camera_->GetNearClip();
         float nearSplit = camera_->GetNearClip();
         float farSplit;
         float farSplit;
@@ -1322,22 +1377,20 @@ unsigned View::SetupShadowCameras(Light* light)
             
             
             // Setup the shadow camera for the split
             // Setup the shadow camera for the split
             Camera* shadowCamera = renderer_->CreateShadowCamera();
             Camera* shadowCamera = renderer_->CreateShadowCamera();
-            shadowCameras_[splits] = shadowCamera;
-            shadowNearSplits_[splits] = nearSplit;
-            shadowFarSplits_[splits] = farSplit;
+            query.shadowCameras_[splits] = shadowCamera;
+            query.shadowNearSplits_[splits] = nearSplit;
+            query.shadowFarSplits_[splits] = farSplit;
             SetupDirLightShadowCamera(shadowCamera, light, nearSplit, farSplit, false);
             SetupDirLightShadowCamera(shadowCamera, light, nearSplit, farSplit, false);
             
             
             nearSplit = farSplit;
             nearSplit = farSplit;
             ++splits;
             ++splits;
         }
         }
-        
-        return splits;
     }
     }
     
     
     if (type == LIGHT_SPOT)
     if (type == LIGHT_SPOT)
     {
     {
         Camera* shadowCamera = renderer_->CreateShadowCamera();
         Camera* shadowCamera = renderer_->CreateShadowCamera();
-        shadowCameras_[0] = shadowCamera;
+        query.shadowCameras_[0] = shadowCamera;
         Node* cameraNode = shadowCamera->GetNode();
         Node* cameraNode = shadowCamera->GetNode();
         
         
         cameraNode->SetTransform(light->GetWorldPosition(), light->GetWorldRotation());
         cameraNode->SetTransform(light->GetWorldPosition(), light->GetWorldRotation());
@@ -1346,7 +1399,7 @@ unsigned View::SetupShadowCameras(Light* light)
         shadowCamera->SetFov(light->GetFov());
         shadowCamera->SetFov(light->GetFov());
         shadowCamera->SetAspectRatio(light->GetAspectRatio());
         shadowCamera->SetAspectRatio(light->GetAspectRatio());
         
         
-        return 1;
+        splits = 1;
     }
     }
     
     
     if (type == LIGHT_POINT)
     if (type == LIGHT_POINT)
@@ -1354,7 +1407,7 @@ unsigned View::SetupShadowCameras(Light* light)
         for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
         for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
         {
         {
             Camera* shadowCamera = renderer_->CreateShadowCamera();
             Camera* shadowCamera = renderer_->CreateShadowCamera();
-            shadowCameras_[i] = shadowCamera;
+            query.shadowCameras_[i] = shadowCamera;
             Node* cameraNode = shadowCamera->GetNode();
             Node* cameraNode = shadowCamera->GetNode();
             
             
             // When making a shadowed point light, align the splits along X, Y and Z axes regardless of light rotation
             // When making a shadowed point light, align the splits along X, Y and Z axes regardless of light rotation
@@ -1366,10 +1419,10 @@ unsigned View::SetupShadowCameras(Light* light)
             shadowCamera->SetAspectRatio(1.0f);
             shadowCamera->SetAspectRatio(1.0f);
         }
         }
         
         
-        return MAX_CUBEMAP_FACES;
+        splits = MAX_CUBEMAP_FACES;
     }
     }
     
     
-    return 0;
+    query.numSplits_ = splits;
 }
 }
 
 
 void View::SetupDirLightShadowCamera(Camera* shadowCamera, Light* light, float nearSplit, float farSplit, bool shadowOcclusion)
 void View::SetupDirLightShadowCamera(Camera* shadowCamera, Light* light, float nearSplit, float farSplit, bool shadowOcclusion)
@@ -1397,8 +1450,6 @@ void View::SetupDirLightShadowCamera(Camera* shadowCamera, Light* light, float n
     // If focusing enabled, clip the frustum volume by the combined bounding box of the lit geometries within the frustum
     // If focusing enabled, clip the frustum volume by the combined bounding box of the lit geometries within the frustum
     if (!shadowOcclusion && parameters.focus_)
     if (!shadowOcclusion && parameters.focus_)
     {
     {
-        PROFILE(ClipFrustumVolume);
-        
         BoundingBox litGeometriesBox;
         BoundingBox litGeometriesBox;
         for (unsigned i = 0; i < geometries_.Size(); ++i)
         for (unsigned i = 0; i < geometries_.Size(); ++i)
         {
         {

+ 37 - 21
Engine/Graphics/View.h

@@ -40,6 +40,7 @@ class Technique;
 class Texture2D;
 class Texture2D;
 class Zone;
 class Zone;
 struct Viewport;
 struct Viewport;
+struct WorkItem;
 
 
 /// %Geometry view space depth minimum and maximum values.
 /// %Geometry view space depth minimum and maximum values.
 struct GeometryDepthBounds
 struct GeometryDepthBounds
@@ -50,9 +51,36 @@ struct GeometryDepthBounds
     float max_;
     float max_;
 };
 };
 
 
+/// Intermediate light processing result.
+struct LightQueryResult
+{
+    /// Light.
+    Light* light_;
+    /// Threaded processing flag.
+    bool threaded_;
+    /// Octree query result.
+    PODVector<Drawable*> tempDrawables_;
+    /// Lit geometries.
+    PODVector<Drawable*> litGeometries_;
+    /// Shadow casters.
+    PODVector<Drawable*> shadowCasters_[MAX_LIGHT_SPLITS];
+    /// Shadow cameras.
+    Camera* shadowCameras_[MAX_LIGHT_SPLITS];
+    /// Combined bounding box of shadow casters in light view or projection space.
+    BoundingBox shadowCasterBox_[MAX_LIGHT_SPLITS];
+    /// Shadow camera near splits (directional lights only.)
+    float shadowNearSplits_[MAX_LIGHT_SPLITS];
+    /// Shadow camera far splits (directional lights only.)
+    float shadowFarSplits_[MAX_LIGHT_SPLITS];
+    /// Shadow map split count.
+    unsigned numSplits_;
+};
+
 /// 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
 {
 {
+    friend void ProcessLightWork(const WorkItem* item, unsigned threadIndex);
+    
     OBJECT(View);
     OBJECT(View);
     
     
 public:
 public:
@@ -80,8 +108,6 @@ public:
     const PODVector<Drawable*>& GetGeometries() const { return geometries_; }
     const PODVector<Drawable*>& GetGeometries() const { return geometries_; }
     /// Return occluder objects.
     /// Return occluder objects.
     const PODVector<Drawable*>& GetOccluders() const { return occluders_; }
     const PODVector<Drawable*>& GetOccluders() const { return occluders_; }
-    /// Return directional light shadow rendering occluders.
-    const PODVector<Drawable*>& GetShadowOccluders() const { return shadowOccluders_; }
     /// Return lights.
     /// Return lights.
     const PODVector<Light*>& GetLights() const { return lights_; }
     const PODVector<Light*>& GetLights() const { return lights_; }
     /// Return light batch queues.
     /// Return light batch queues.
@@ -103,11 +129,11 @@ private:
     /// Draw occluders to occlusion buffer.
     /// Draw occluders to occlusion buffer.
     void DrawOccluders(OcclusionBuffer* buffer, const PODVector<Drawable*>& occluders);
     void DrawOccluders(OcclusionBuffer* buffer, const PODVector<Drawable*>& occluders);
     /// Query for lit geometries and shadow casters for a light.
     /// Query for lit geometries and shadow casters for a light.
-    unsigned ProcessLight(Light* light);
+    void ProcessLight(LightQueryResult& query);
     /// Process shadow casters' visibilities and build their combined view- or projection-space bounding box.
     /// Process shadow casters' visibilities and build their combined view- or projection-space bounding box.
-    void ProcessShadowCasters(Light* light, unsigned splitIndex, const PODVector<Drawable*>& drawables, BoundingBox& shadowCasterBox);
-    /// %Set up initial shadow camera view(s). Returns the number of splits used.
-    unsigned SetupShadowCameras(Light* light);
+    void ProcessShadowCasters(LightQueryResult& query, unsigned splitIndex);
+    /// %Set up initial shadow camera view(s).
+    void SetupShadowCameras(LightQueryResult& query);
     /// %Set up a directional light shadow camera
     /// %Set up a directional light shadow camera
     void SetupDirLightShadowCamera(Camera* shadowCamera, Light* light, float nearSplit, float farSplit, bool shadowOcclusion);
     void SetupDirLightShadowCamera(Camera* shadowCamera, Light* light, float nearSplit, float farSplit, bool shadowOcclusion);
     /// Finalize shadow camera view after shadow casters and the shadow map are known.
     /// Finalize shadow camera view after shadow casters and the shadow map are known.
@@ -185,23 +211,11 @@ private:
     BoundingBox sceneViewBox_;
     BoundingBox sceneViewBox_;
     /// Volume for frustum clipping.
     /// Volume for frustum clipping.
     Polyhedron frustumVolume_;
     Polyhedron frustumVolume_;
-    /// Current shadow cameras being processed.
-    Camera* shadowCameras_[MAX_LIGHT_SPLITS];
-    /// Current shadow casters being processed.
-    PODVector<Drawable*> shadowCasters_[MAX_LIGHT_SPLITS];
-    /// Combined bounding box of shadow casters in light view or projection space.
-    BoundingBox shadowCasterBox_[MAX_LIGHT_SPLITS];
-    /// Shadow camera near splits (directional lights only.)
-    float shadowNearSplits_[MAX_LIGHT_SPLITS];
-    /// Shadow camera far splits (directional lights only.)
-    float shadowFarSplits_[MAX_LIGHT_SPLITS];
-    /// Current lit geometries being processed.
-    PODVector<Drawable*> litGeometries_;
-    /// Temporary drawable query result.
+    /// Octree query result.
     PODVector<Drawable*> tempDrawables_;
     PODVector<Drawable*> tempDrawables_;
-    /// Temporary zone query result.
+    /// Octree zone query result.
     PODVector<Zone*> tempZones_;
     PODVector<Zone*> tempZones_;
-    /// Visible zones query result.
+    /// Visible zones.
     PODVector<Zone*> zones_;
     PODVector<Zone*> zones_;
     /// Visible geometry objects.
     /// Visible geometry objects.
     PODVector<Drawable*> geometries_;
     PODVector<Drawable*> geometries_;
@@ -233,6 +247,8 @@ private:
     BatchQueue alphaQueue_;
     BatchQueue alphaQueue_;
     /// Post-transparent pass batches.
     /// Post-transparent pass batches.
     BatchQueue postAlphaQueue_;
     BatchQueue postAlphaQueue_;
+    /// Intermediate light processing results.
+    Vector<LightQueryResult> lightQueryResults_;
     /// Light queues.
     /// Light queues.
     Vector<LightBatchQueue> lightQueues_;
     Vector<LightBatchQueue> lightQueues_;
     /// Current stencil value for light optimization.
     /// Current stencil value for light optimization.