Browse Source

Eliminated most virtual function calls from getting Drawable batches.

Lasse Öörni 13 years ago
parent
commit
48190acfca

+ 35 - 33
Engine/Graphics/AnimatedModel.cpp

@@ -30,7 +30,6 @@
 #include "Context.h"
 #include "DebugRenderer.h"
 #include "DrawableEvents.h"
-#include "Geometry.h"
 #include "Graphics.h"
 #include "IndexBuffer.h"
 #include "Log.h"
@@ -226,7 +225,7 @@ void AnimatedModel::Update(const FrameInfo& frame)
     UpdateAnimation(frame);
 }
 
-void AnimatedModel::UpdateDistance(const FrameInfo& frame)
+void AnimatedModel::UpdateBatches(const FrameInfo& frame)
 {
     const Matrix3x4& worldTransform = node_->GetWorldTransform();
     distance_ = frame.camera_->GetDistance(worldTransform.Translation());
@@ -235,10 +234,16 @@ void AnimatedModel::UpdateDistance(const FrameInfo& frame)
     if (batches_.Size() > 1)
     {
         for (unsigned i = 0; i < batches_.Size(); ++i)
-            batches_[i].distance_ = frame.camera_->GetDistance(worldTransform * batches_[i].center_);
+        {
+            batches_[i].distance_ = frame.camera_->GetDistance(worldTransform * geometryCenters_[i]);
+            batches_[i].worldTransform_ = &worldTransform;
+        }
     }
     else
+    {
         batches_[0].distance_ = distance_;
+        batches_[0].worldTransform_ = &worldTransform;
+    }
     
     float scale = GetWorldBoundingBox().Size().DotProduct(DOT_SCALE);
     float newLodDistance = frame.camera_->GetLodDistance(distance_, scale, lodBias_);
@@ -278,33 +283,6 @@ UpdateGeometryType AnimatedModel::GetUpdateGeometryType()
         return UPDATE_NONE;
 }
 
-void AnimatedModel::GetBatch(Batch& batch, const FrameInfo& frame, unsigned batchIndex)
-{
-    StaticModelBatch& srcBatch = batches_[batchIndex];
-    
-    batch.distance_ = srcBatch.distance_;
-    batch.geometry_ = srcBatch.geometry_;
-    batch.geometryType_ = GEOM_SKINNED;
-    batch.worldTransform_ = &node_->GetWorldTransform();
-    batch.material_ = srcBatch.material_;
-    
-    if (skinMatrices_.Size())
-    {
-        // Check if model has per-geometry bone mappings
-        if (geometrySkinMatrices_.Size() && geometrySkinMatrices_[batchIndex].Size())
-        {
-            batch.shaderData_ = geometrySkinMatrices_[batchIndex][0].Data();
-            batch.shaderDataSize_ = geometrySkinMatrices_[batchIndex].Size() * 12;
-        }
-        // If not, use the global skin matrices
-        else
-        {
-            batch.shaderData_ = skinMatrices_[0].Data();
-            batch.shaderDataSize_ = skinMatrices_.Size() * 12;
-        }
-    }
-}
-
 void AnimatedModel::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
     if (debug)
@@ -334,7 +312,7 @@ void AnimatedModel::SetModel(Model* model, bool createBones)
     for (unsigned i = 0; i < geometries.Size(); ++i)
     {
         geometries_[i] = geometries[i];
-        batches_[i].center_ = geometryCenters[i];
+        geometryCenters_[i] = geometryCenters[i];
     }
     
     // Copy geometry bone mappings
@@ -368,6 +346,30 @@ void AnimatedModel::SetModel(Model* model, bool createBones)
     SetBoundingBox(model->GetBoundingBox());
     SetSkeleton(model->GetSkeleton(), createBones);
     ResetLodLevels();
+    
+    // Enable skinning in batches
+    for (unsigned i = 0; i < batches_.Size(); ++i)
+    {
+        if (skinMatrices_.Size())
+        {
+            batches_[i].geometryType_ = GEOM_SKINNED;
+            // Check if model has per-geometry bone mappings
+            if (geometrySkinMatrices_.Size() && geometrySkinMatrices_[i].Size())
+            {
+                batches_[i].shaderData_ = geometrySkinMatrices_[i][0].Data();
+                batches_[i].shaderDataSize_ = geometrySkinMatrices_[i].Size() * 12;
+            }
+            // If not, use the global skin matrices
+            else
+            {
+                batches_[i].shaderData_ = skinMatrices_[0].Data();
+                batches_[i].shaderDataSize_ = skinMatrices_.Size() * 12;
+            }
+        }
+        else
+            batches_[i].geometryType_ = GEOM_STATIC;
+    }
+    
     MarkNetworkUpdate();
 }
 
@@ -707,7 +709,7 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
     
     // Reserve space for skinning matrices
     skinMatrices_.Resize(skeleton_.GetNumBones());
-    RefreshGeometryBoneMappings();
+    SetGeometryBoneMappings();
     
     assignBonesPending_ = !createBones;
 }
@@ -955,7 +957,7 @@ void AnimatedModel::CloneGeometries()
     }
 }
 
-void AnimatedModel::RefreshGeometryBoneMappings()
+void AnimatedModel::SetGeometryBoneMappings()
 {
     geometrySkinMatrices_.Clear();
     geometrySkinMatrixPtrs_.Clear();

+ 4 - 6
Engine/Graphics/AnimatedModel.h

@@ -56,14 +56,12 @@ public:
     virtual void ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results);
     /// Update before octree reinsertion. Animation is updated here.
     virtual void Update(const FrameInfo& frame);
-    /// Calculate distance and LOD level for rendering. May be called from worker thread(s), possibly re-entrantly.
-    virtual void UpdateDistance(const FrameInfo& frame);
+    /// Calculate distance and update batches for rendering. May be called from worker thread(s), possibly re-entrantly.
+    virtual void UpdateBatches(const FrameInfo& frame);
     /// Prepare geometry for rendering. Called from a worker thread if possible (no GPU update.)
     virtual void UpdateGeometry(const FrameInfo& frame);
     /// Return whether a geometry update is necessary, and if it should happen in a worker thread.
     virtual UpdateGeometryType GetUpdateGeometryType();
-    /// Fill rendering batch with distance, geometry, material and world transform.
-    virtual void GetBatch(Batch& batch, const FrameInfo& frame, unsigned batchIndex);
     /// Add debug geometry to the debug renderer.
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
     
@@ -163,8 +161,8 @@ private:
     void MarkMorphsDirty();
     /// %Set skeleton.
     void SetSkeleton(const Skeleton& skeleton, bool createBones);
-    /// Refresh mapping of subgeometry bone indices.
-    void RefreshGeometryBoneMappings();
+    /// %Set mapping of subgeometry bone indices.
+    void SetGeometryBoneMappings();
     /// Clone geometries as required.
     void CloneGeometries();
     /// Recalculate animations. Called from UpdateNode().

+ 0 - 2
Engine/Graphics/Batch.cpp

@@ -23,11 +23,9 @@
 
 #include "Precompiled.h"
 #include "Camera.h"
-#include "Geometry.h"
 #include "Graphics.h"
 #include "GraphicsImpl.h"
 #include "Light.h"
-#include "Material.h"
 #include "Renderer.h"
 #include "Profiler.h"
 #include "ShaderVariation.h"

+ 18 - 9
Engine/Graphics/Batch.h

@@ -23,7 +23,7 @@
 
 #pragma once
 
-#include "GraphicsDefs.h"
+#include "Drawable.h"
 #include "HashMap.h"
 #include "MathDefs.h"
 #include "Ptr.h"
@@ -45,20 +45,29 @@ class VertexBuffer;
 class Zone;
 struct LightBatchQueue;
 
-/// Description of a 3D geometry draw call.
+/// Queued 3D geometry draw call.
 struct Batch
 {
     /// Construct with defaults.
     Batch() :
         lightQueue_(0),
-        shaderData_(0),
-        shaderDataSize_(0),
-        geometryType_(GEOM_STATIC),
-        overrideView_(false),
         isBase_(false)
     {
     }
     
+    /// Assign from a drawable's source data.
+    void CopyFrom(const SourceBatch& rhs)
+    {
+        distance_ = rhs.distance_;
+        geometry_ = rhs.geometry_;
+        material_ = rhs.material_;
+        worldTransform_  = rhs.worldTransform_;
+        shaderData_ = rhs.shaderData_;
+        shaderDataSize_ = rhs.shaderDataSize_;
+        geometryType_ = rhs.geometryType_;
+        overrideView_ = rhs.overrideView_;
+    }
+    
     /// Calculate state sorting key, which consists of base pass flag, light, pass and geometry.
     void CalculateSortKey();
     /// Prepare for rendering.
@@ -72,6 +81,8 @@ struct Batch
     float distance_;
     /// Geometry.
     Geometry* geometry_;
+    /// Material.
+    Material* material_;
     /// Model world transform.
     const Matrix3x4* worldTransform_;
     /// Camera.
@@ -80,8 +91,6 @@ struct Batch
     Zone* zone_;
     /// Light properties.
     LightBatchQueue* lightQueue_;
-    /// Material.
-    Material* material_;
     /// Material pass.
     Pass* pass_;
     /// Vertex shader.
@@ -247,7 +256,7 @@ struct ShadowBatchQueue
     float farSplit_;
 };
 
-/// Queue for light related draw calls
+/// Queue for light related draw calls.
 struct LightBatchQueue
 {
     /// Per-pixel light.

+ 22 - 25
Engine/Graphics/BillboardSet.cpp

@@ -26,11 +26,9 @@
 #include "BillboardSet.h"
 #include "Camera.h"
 #include "Context.h"
-#include "Geometry.h"
 #include "Graphics.h"
 #include "GraphicsImpl.h"
 #include "IndexBuffer.h"
-#include "Material.h"
 #include "MemoryBuffer.h"
 #include "Profiler.h"
 #include "ResourceCache.h"
@@ -52,7 +50,6 @@ OBJECTTYPESTATIC(BillboardSet);
 
 BillboardSet::BillboardSet(Context* context) :
     Drawable(context),
-    geometry_(new Geometry(context_)),
     animationLodBias_(1.0f),
     animationLodTimer_(0.0f),
     relative_(true),
@@ -65,6 +62,10 @@ BillboardSet::BillboardSet(Context* context) :
     sortFrameNumber_(0)
 {
     drawableFlags_ = DRAWABLE_GEOMETRY;
+    
+    batches_.Resize(1);
+    batches_[0].geometry_ = new Geometry(context_);
+    batches_[0].geometryType_ = GEOM_BILLBOARD;
 }
 
 BillboardSet::~BillboardSet()
@@ -90,10 +91,11 @@ void BillboardSet::RegisterObject(Context* context)
     REF_ACCESSOR_ATTRIBUTE(BillboardSet, VAR_BUFFER, "Network Billboards", GetNetBillboardsAttr, SetNetBillboardsAttr, PODVector<unsigned char>, PODVector<unsigned char>(), AM_NET | AM_NOEDIT);
 }
 
-void BillboardSet::UpdateDistance(const FrameInfo& frame)
+void BillboardSet::UpdateBatches(const FrameInfo& frame)
 {
     // Check if position relative to camera has changed, and re-sort in that case
-    const Vector3& worldPos = node_->GetWorldPosition();
+    const Matrix3x4& worldTransform = node_->GetWorldTransform();
+    Vector3 worldPos = worldTransform.Translation();
     Vector3 offset = (worldPos - frame.camera_->GetNode()->GetWorldPosition());
     if (offset != previousOffset_)
     {
@@ -119,6 +121,9 @@ void BillboardSet::UpdateDistance(const FrameInfo& frame)
         lodDistance_ = frame.camera_->GetLodDistance(distance_, scale, lodBias_);
     else
         lodDistance_ = 0.0f;
+    
+    batches_[0].distance_ = distance_;
+    batches_[0].worldTransform_ = &worldTransform;
 }
 
 void BillboardSet::UpdateGeometry(const FrameInfo& frame)
@@ -128,6 +133,7 @@ void BillboardSet::UpdateGeometry(const FrameInfo& frame)
         UpdateBufferSize();
         forceUpdate_ = true;
     }
+    
     if (vertexBuffer_->IsDataLost())
     {
         vertexBuffer_->ClearDataLost();
@@ -147,23 +153,9 @@ UpdateGeometryType BillboardSet::GetUpdateGeometryType()
         return UPDATE_NONE;
 }
 
-unsigned BillboardSet::GetNumBatches()
-{
-    return 1;
-}
-
-void BillboardSet::GetBatch(Batch& batch, const FrameInfo& frame, unsigned batchIndex)
-{
-    batch.distance_ = distance_;
-    batch.geometry_ = geometry_;
-    batch.geometryType_ = GEOM_BILLBOARD;
-    batch.worldTransform_ = &node_->GetWorldTransform();
-    batch.material_ = material_;
-}
-
 void BillboardSet::SetMaterial(Material* material)
 {
-    material_ = material;
+    batches_[0].material_ = material;
     MarkNetworkUpdate();
 }
 
@@ -222,6 +214,11 @@ void BillboardSet::Updated()
     MarkNetworkUpdate();
 }
 
+Material* BillboardSet::GetMaterial() const
+{
+    return batches_[0].material_;
+}
+
 Billboard* BillboardSet::GetBillboard(unsigned index)
 {
     return index < billboards_.Size() ? &billboards_[index] : (Billboard*)0;
@@ -269,7 +266,7 @@ void BillboardSet::SetNetBillboardsAttr(const PODVector<unsigned char>& value)
 
 ResourceRef BillboardSet::GetMaterialAttr() const
 {
-    return GetResourceRef(material_, Material::GetTypeStatic());
+    return GetResourceRef(batches_[0].material_, Material::GetTypeStatic());
 }
 
 VariantVector BillboardSet::GetBillboardsAttr() const
@@ -346,8 +343,8 @@ void BillboardSet::UpdateBufferSize()
         vertexBuffer_ = new VertexBuffer(context_);
         indexBuffer_ = new IndexBuffer(context_);
         
-        geometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1 | MASK_TEXCOORD2);
-        geometry_->SetIndexBuffer(indexBuffer_);
+        batches_[0].geometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1 | MASK_TEXCOORD2);
+        batches_[0].geometry_->SetIndexBuffer(indexBuffer_);
     }
     
     unsigned numBillboards = billboards_.Size();
@@ -419,8 +416,8 @@ void BillboardSet::UpdateVertexBuffer(const FrameInfo& frame)
         }
     }
     
-    geometry_->SetDrawRange(TRIANGLE_LIST, 0, enabledBillboards * 6, false);
-
+    batches_[0].geometry_->SetDrawRange(TRIANGLE_LIST, 0, enabledBillboards * 6, false);
+    
     bufferDirty_ = false;
     forceUpdate_ = false;
     if (!enabledBillboards)

+ 3 - 11
Engine/Graphics/BillboardSet.h

@@ -63,16 +63,12 @@ public:
     /// Register object factory.
     static void RegisterObject(Context* context);
     
-    /// Calculate distance and LOD level for rendering.  May be called from worker thread(s), possibly re-entrantly.
-    virtual void UpdateDistance(const FrameInfo& frame);
+    /// Calculate distance and update batches for rendering. May be called from worker thread(s), possibly re-entrantly.
+    virtual void UpdateBatches(const FrameInfo& frame);
     /// Prepare geometry for rendering. Called from a worker thread if possible (no GPU update.)
     virtual void UpdateGeometry(const FrameInfo& frame);
     /// Return whether a geometry update is necessary, and if it should happen in a worker thread.
     virtual UpdateGeometryType GetUpdateGeometryType();
-    /// Return number of batches.
-    virtual unsigned GetNumBatches();
-    /// Fill rendering batch with distance, geometry, material and world transform.
-    virtual void GetBatch(Batch& batch, const FrameInfo& frame, unsigned batchIndex);
     
     /// %Set material.
     void SetMaterial(Material* material);
@@ -90,7 +86,7 @@ public:
     void Updated();
     
     /// Return material.
-    Material* GetMaterial() const { return material_; }
+    Material* GetMaterial() const;
     /// Return number of billboards.
     unsigned GetNumBillboards() const { return billboards_.Size(); }
     /// Return all billboards.
@@ -146,10 +142,6 @@ private:
     /// Rewrite billboard vertex buffer.
     void UpdateVertexBuffer(const FrameInfo& frame);
     
-    /// Geometry.
-    SharedPtr<Geometry> geometry_;
-    /// Material.
-    SharedPtr<Material> material_;
     /// Vertex buffer.
     SharedPtr<VertexBuffer> vertexBuffer_;
     /// Index buffer.

+ 0 - 2
Engine/Graphics/Direct3D9/D3D9Graphics.cpp

@@ -28,14 +28,12 @@
 #include "Camera.h"
 #include "Context.h"
 #include "DebugRenderer.h"
-#include "Geometry.h"
 #include "Graphics.h"
 #include "GraphicsEvents.h"
 #include "GraphicsImpl.h"
 #include "IndexBuffer.h"
 #include "Light.h"
 #include "Log.h"
-#include "Material.h"
 #include "Octree.h"
 #include "ParticleEmitter.h"
 #include "Profiler.h"

+ 1 - 1
Engine/Graphics/Drawable.cpp

@@ -98,7 +98,7 @@ void Drawable::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryRe
     }
 }
 
-void Drawable::UpdateDistance(const FrameInfo& frame)
+void Drawable::UpdateBatches(const FrameInfo& frame)
 {
     distance_ = frame.camera_->GetDistance(node_->GetWorldPosition());
     

+ 40 - 7
Engine/Graphics/Drawable.h

@@ -25,7 +25,9 @@
 
 #include "BoundingBox.h"
 #include "Component.h"
+#include "Geometry.h"
 #include "GraphicsDefs.h"
+#include "Material.h"
 #include "Node.h"
 
 static const unsigned DRAWABLE_GEOMETRY = 0x1;
@@ -39,7 +41,6 @@ static const unsigned DEFAULT_ZONEMASK = M_MAX_UNSIGNED;
 static const int DRAWABLES_PER_WORK_ITEM = 16;
 static const int MAX_VERTEX_LIGHTS = 6;
 
-struct Batch;
 class Camera;
 class DebugRenderer;
 class Geometry;
@@ -73,6 +74,38 @@ struct FrameInfo
     Camera* camera_;
 };
 
+/// Source data for a 3D geometry draw call.
+struct SourceBatch
+{
+    /// Construct with defaults.
+    SourceBatch() :
+        distance_(0.0f),
+        worldTransform_(&Matrix3x4::IDENTITY),
+        shaderData_(0),
+        shaderDataSize_(0),
+        geometryType_(GEOM_STATIC),
+        overrideView_(false)
+    {
+    }
+    
+    /// Distance from camera.
+    float distance_;
+    /// Geometry.
+    SharedPtr<Geometry> geometry_;
+    /// Material.
+    SharedPtr<Material> material_;
+    /// Model world transform.
+    const Matrix3x4* worldTransform_;
+    /// Vertex shader data.
+    const float* shaderData_;
+    /// Vertex shader data size in floats.
+    unsigned shaderDataSize_;
+    /// Geometry type.
+    GeometryType geometryType_;
+    /// Override view transform flag.
+    bool overrideView_;
+};
+
 /// Base class for visible components.
 class Drawable : public Component
 {
@@ -94,16 +127,12 @@ public:
     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().
     virtual void Update(const FrameInfo& frame) {}
-    /// Calculate distance and LOD level for rendering.May be called from worker thread(s), possibly re-entrantly.
-    virtual void UpdateDistance(const FrameInfo& frame);
+    /// Calculate distance and update batches for rendering. May be called from worker thread(s), possibly re-entrantly.
+    virtual void UpdateBatches(const FrameInfo& frame);
     /// Prepare geometry for rendering.
     virtual void UpdateGeometry(const FrameInfo& frame) {}
     /// Return whether a geometry update is necessary, and if it should happen in a worker thread.
     virtual UpdateGeometryType GetUpdateGeometryType() { return UPDATE_NONE; }
-    /// Return number of rendering batches.
-    virtual unsigned GetNumBatches() { return 0; }
-    /// Fill rendering batch with distance, geometry, material and world transform.
-    virtual void GetBatch(Batch& batch, const FrameInfo& frame, unsigned batchIndex) {}
     /// Return number of occlusion geometry triangles.
     virtual unsigned GetNumOccluderTriangles() { return 0; }
     /// Draw to occlusion buffer. Return true if did not run out of triangles.
@@ -166,6 +195,8 @@ public:
     bool IsOccluder() const { return occluder_; }
     /// Return occludee flag.
     bool IsOccludee() const { return occludee_; }
+    /// Return draw call source data.
+    const Vector<SourceBatch>& GetBatches() const { return batches_; }
     
     /// %Set new zone.
     void SetZone(Zone* zone, bool temporary = false);
@@ -232,6 +263,8 @@ protected:
     
     /// World bounding box.
     BoundingBox worldBoundingBox_;
+    /// Draw call source data.
+    Vector<SourceBatch> batches_;
     /// Drawable flags.
     unsigned char drawableFlags_;
     /// Bounding box dirty flag.

+ 1 - 1
Engine/Graphics/Light.cpp

@@ -230,7 +230,7 @@ void Light::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResul
     }
 }
 
-void Light::UpdateDistance(const FrameInfo& frame)
+void Light::UpdateBatches(const FrameInfo& frame)
 {
     switch (lightType_)
     {

+ 2 - 2
Engine/Graphics/Light.h

@@ -161,8 +161,8 @@ public:
     virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src);
     /// Process octree raycast. May be called from a worker thread.
     virtual void ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results);
-    /// Calculate distance for rendering. May be called from worker thread(s), possibly re-entrantly.
-    virtual void UpdateDistance(const FrameInfo& frame);
+    /// Calculate distance and update batches for rendering. May be called from worker thread(s), possibly re-entrantly.
+    virtual void UpdateBatches(const FrameInfo& frame);
     /// Add debug geometry to the debug renderer.
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
     

+ 0 - 1
Engine/Graphics/OpenGL/OGLGraphics.cpp

@@ -35,7 +35,6 @@
 #include "IndexBuffer.h"
 #include "Light.h"
 #include "Log.h"
-#include "Material.h"
 #include "Mutex.h"
 #include "Octree.h"
 #include "ParticleEmitter.h"

+ 7 - 13
Engine/Graphics/Skybox.cpp

@@ -52,24 +52,18 @@ void Skybox::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResu
     // Return no ray hits, as camera rays practically always originate within the bounding box, blocking any other results
 }
 
-void Skybox::UpdateDistance(const FrameInfo& frame)
+void Skybox::UpdateBatches(const FrameInfo& frame)
 {
-    distance_ = 0.0f;
-}
-
-void Skybox::GetBatch(Batch& batch, const FrameInfo& frame, unsigned batchIndex)
-{
-    StaticModelBatch& srcBatch = batches_[batchIndex];
-    
     // Follow only the camera rotation, not position
     Matrix3x4 customView(Vector3::ZERO, frame.camera_->GetNode()->GetWorldRotation().Inverse(), Vector3::ONE);
     customWorldTransform_ = customView * node_->GetWorldTransform();
     
-    batch.distance_ = 0.0f;
-    batch.geometry_ = srcBatch.geometry_;
-    batch.worldTransform_ = &customWorldTransform_;
-    batch.overrideView_ = true;
-    batch.material_ = srcBatch.material_;
+    for (unsigned i = 0; i < batches_.Size(); ++i)
+    {
+        batches_[i].worldTransform_ = &customWorldTransform_;
+        batches_[i].distance_ = 0.0f;
+        batches_[i].overrideView_ = true;
+    }
 }
 
 void Skybox::OnWorldBoundingBoxUpdate()

+ 2 - 4
Engine/Graphics/Skybox.h

@@ -40,10 +40,8 @@ public:
     
     /// Process octree raycast. May be called from a worker thread.
     virtual void ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results);
-    /// Calculate distance and LOD level for rendering. May be called from worker thread(s), possibly re-entrantly.
-    virtual void UpdateDistance(const FrameInfo& frame);
-    /// Fill rendering batch with distance, geometry, material and world transform.
-    virtual void GetBatch(Batch& batch, const FrameInfo& frame, unsigned batchIndex);
+    /// Calculate distance and update batches for rendering. May be called from worker thread(s), possibly re-entrantly.
+    virtual void UpdateBatches(const FrameInfo& frame);
     
 protected:
     /// Recalculate the world-space bounding box.

+ 19 - 29
Engine/Graphics/StaticModel.cpp

@@ -25,9 +25,7 @@
 #include "Batch.h"
 #include "Camera.h"
 #include "Context.h"
-#include "Geometry.h"
 #include "Log.h"
-#include "Material.h"
 #include "Model.h"
 #include "OcclusionBuffer.h"
 #include "OctreeQuery.h"
@@ -109,12 +107,12 @@ void StaticModel::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQuer
             if (distance <= query.maxDistance_)
             {
                 // Then the actual test using triangle geometry
-                for (unsigned i = 0; i < batches_.Size(); ++i)
+                for (unsigned i = 0; i < geometries_.Size(); ++i)
                 {
                     unsigned lodLevel;
                     // Check whether to use same LOD as visible, or a specific LOD
                     if (softwareLodLevel_ == M_MAX_UNSIGNED)
-                        lodLevel = batches_[i].lodLevel_;
+                        lodLevel = lodLevels_[i];
                     else
                         lodLevel = Clamp(softwareLodLevel_, 0, geometries_[i].Size());
                     
@@ -140,7 +138,7 @@ void StaticModel::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQuer
     }
 }
 
-void StaticModel::UpdateDistance(const FrameInfo& frame)
+void StaticModel::UpdateBatches(const FrameInfo& frame)
 {
     const Matrix3x4& worldTransform = node_->GetWorldTransform();
     distance_ = frame.camera_->GetDistance(worldTransform.Translation());
@@ -148,10 +146,16 @@ void StaticModel::UpdateDistance(const FrameInfo& frame)
     if (batches_.Size() > 1)
     {
         for (unsigned i = 0; i < batches_.Size(); ++i)
-            batches_[i].distance_ = frame.camera_->GetDistance(worldTransform * batches_[i].center_);
+        {
+            batches_[i].distance_ = frame.camera_->GetDistance(worldTransform * geometryCenters_[i]);
+            batches_[i].worldTransform_ = &worldTransform;
+        }
     }
     else
+    {
         batches_[0].distance_ = distance_;
+        batches_[0].worldTransform_ = &worldTransform;
+    }
     
     float scale = GetWorldBoundingBox().Size().DotProduct(DOT_SCALE);
     float newLodDistance = frame.camera_->GetLodDistance(distance_, scale, lodBias_);
@@ -163,21 +167,6 @@ void StaticModel::UpdateDistance(const FrameInfo& frame)
     }
 }
 
-unsigned StaticModel::GetNumBatches()
-{
-    return batches_.Size();
-}
-
-void StaticModel::GetBatch(Batch& batch, const FrameInfo& frame, unsigned batchIndex)
-{
-    StaticModelBatch& srcBatch = batches_[batchIndex];
-    
-    batch.distance_ = srcBatch.distance_;
-    batch.geometry_ = srcBatch.geometry_;
-    batch.worldTransform_ = &node_->GetWorldTransform();
-    batch.material_ = srcBatch.material_;
-}
-
 unsigned StaticModel::GetNumOccluderTriangles()
 {
     unsigned triangles = 0;
@@ -187,7 +176,7 @@ unsigned StaticModel::GetNumOccluderTriangles()
         unsigned lodLevel;
         // Check whether to use same LOD as visible, or a specific LOD
         if (softwareLodLevel_ == M_MAX_UNSIGNED)
-            lodLevel = batches_[i].lodLevel_;
+            lodLevel = lodLevels_[i];
         else
             lodLevel = Clamp(softwareLodLevel_, 0, geometries_[i].Size());
         
@@ -215,7 +204,7 @@ bool StaticModel::DrawOcclusion(OcclusionBuffer* buffer)
         unsigned lodLevel;
         // Check whether to use same LOD as visible, or a specific LOD
         if (softwareLodLevel_ == M_MAX_UNSIGNED)
-            lodLevel = batches_[i].lodLevel_;
+            lodLevel = lodLevels_[i];
         else
             lodLevel = Clamp(softwareLodLevel_, 0, geometries_[i].Size());
         
@@ -223,8 +212,7 @@ bool StaticModel::DrawOcclusion(OcclusionBuffer* buffer)
         if (!geom)
             continue;
         
-        // Check that the material is suitable for occlusion (default material always is)
-        // and set culling mode
+        // Check that the material is suitable for occlusion (default material always is) and set culling mode
         Material* mat = batches_[i].material_;
         if (mat)
         {
@@ -279,7 +267,7 @@ void StaticModel::SetModel(Model* model)
     for (unsigned i = 0; i < geometries.Size(); ++i)
     {
         geometries_[i] = geometries[i];
-        batches_[i].center_ = geometryCenters[i];
+        geometryCenters_[i] = geometryCenters[i];
     }
     
     SetBoundingBox(model->GetBoundingBox());
@@ -329,6 +317,8 @@ void StaticModel::SetNumGeometries(unsigned num)
 {
     batches_.Resize(num);
     geometries_.Resize(num);
+    geometryCenters_.Resize(num);
+    lodLevels_.Resize(num);
     ResetLodLevels();
 }
 
@@ -372,7 +362,7 @@ void StaticModel::ResetLodLevels()
         if (!geometries_[i].Size())
             geometries_[i].Resize(1);
         batches_[i].geometry_ = geometries_[i][0];
-        batches_[i].lodLevel_ = 0;
+        lodLevels_[i] = 0;
     }
     
     // Find out the real LOD levels on next geometry update
@@ -393,9 +383,9 @@ void StaticModel::CalculateLodLevels()
         }
         
         unsigned newLodLevel = j - 1;
-        if (batches_[i].lodLevel_ != newLodLevel)
+        if (lodLevels_[i] != newLodLevel)
         {
-            batches_[i].lodLevel_ = newLodLevel;
+            lodLevels_[i] = newLodLevel;
             batches_[i].geometry_ = batchGeometries[newLodLevel];
         }
     }

+ 6 - 23
Engine/Graphics/StaticModel.h

@@ -27,21 +27,6 @@
 
 class Model;
 
-/// Static model per-batch data.
-struct StaticModelBatch
-{
-    /// Current distance.
-    float distance_;
-    /// Current LOD geometry.
-    Geometry* geometry_;
-    /// Material.
-    SharedPtr<Material> material_;
-    /// Current LOD level.
-    unsigned lodLevel_;
-    /// Geometry center.
-    Vector3 center_;
-};
-
 /// Static model component.
 class StaticModel : public Drawable
 {
@@ -57,12 +42,8 @@ public:
     
     /// Process octree raycast. May be called from a worker thread.
     virtual void ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results);
-    /// Calculate distance and LOD level for rendering. May be called from worker thread(s), possibly re-entrantly.
-    virtual void UpdateDistance(const FrameInfo& frame);
-    /// Return number of batches.
-    virtual unsigned GetNumBatches();
-    /// Fill rendering batch with distance, geometry, material and world transform.
-    virtual void GetBatch(Batch& batch, const FrameInfo& frame, unsigned batchIndex);
+    /// Calculate distance and update batches for rendering. May be called from worker thread(s), possibly re-entrantly.
+    virtual void UpdateBatches(const FrameInfo& frame);
     /// Return number of occlusion geometry triangles.
     virtual unsigned GetNumOccluderTriangles();
     /// Draw to occlusion buffer. Return true if did not run out of triangles.
@@ -109,10 +90,12 @@ protected:
     /// Choose LOD levels based on distance.
     void CalculateLodLevels();
     
-    /// Per-batch data.
-    Vector<StaticModelBatch> batches_;
     /// Bounding box.
     BoundingBox boundingBox_;
+    /// Current LOD levels.
+    PODVector<unsigned> lodLevels_;
+    /// Geometry centers.
+    PODVector<Vector3> geometryCenters_;
     /// All geometries.
     Vector<Vector<SharedPtr<Geometry> > > geometries_;
     /// Model.

+ 82 - 73
Engine/Graphics/View.cpp

@@ -24,11 +24,9 @@
 #include "Precompiled.h"
 #include "Camera.h"
 #include "DebugRenderer.h"
-#include "Geometry.h"
 #include "Graphics.h"
 #include "Light.h"
 #include "Log.h"
-#include "Material.h"
 #include "OcclusionBuffer.h"
 #include "Octree.h"
 #include "Renderer.h"
@@ -211,7 +209,7 @@ void CheckVisibilityWork(const WorkItem* item, unsigned threadIndex)
     while (start != end)
     {
         Drawable* drawable = *start++;
-        drawable->UpdateDistance(view->frame_);
+        drawable->UpdateBatches(view->frame_);
         
         // If draw distance non-zero, check it
         float maxDistance = drawable->GetDrawDistance();
@@ -773,28 +771,29 @@ void View::GetBatches()
                         }
                         
                         Zone* zone = GetZone(drawable);
-                        unsigned numBatches = drawable->GetNumBatches();
+                        const Vector<SourceBatch>& batches = drawable->GetBatches();
                         
-                        for (unsigned l = 0; l < numBatches; ++l)
+                        for (unsigned l = 0; l < batches.Size(); ++l)
                         {
-                            Batch shadowBatch;
-                            drawable->GetBatch(shadowBatch, frame_, l);
+                            const SourceBatch& srcBatch = batches[l];
                             
-                            Technique* tech = GetTechnique(drawable, shadowBatch.material_);
-                            if (!shadowBatch.geometry_ || !tech)
+                            Technique* tech = GetTechnique(drawable, srcBatch.material_);
+                            if (!srcBatch.geometry_ || !tech)
                                 continue;
                             
-                            shadowBatch.pass_ = tech->GetPass(PASS_SHADOW);
+                            Batch destBatch;
+                            destBatch.pass_ = tech->GetPass(PASS_SHADOW);
                             // Skip if material has no shadow pass
-                            if (!shadowBatch.pass_)
+                            if (!destBatch.pass_)
                                 continue;
                             
-                            // Fill the rest of the batch
-                            shadowBatch.camera_ = shadowCamera;
-                            shadowBatch.zone_ = zone;
-                            shadowBatch.lightQueue_ = &lightQueue;
+                            // Copy the rest of the batch
+                            destBatch.CopyFrom(srcBatch);
+                            destBatch.camera_ = shadowCamera;
+                            destBatch.zone_ = zone;
+                            destBatch.lightQueue_ = &lightQueue;
                             
-                            AddBatchToQueue(shadowQueue.shadowBatches_, shadowBatch, tech);
+                            AddBatchToQueue(shadowQueue.shadowBatches_, destBatch, tech);
                         }
                     }
                 }
@@ -874,68 +873,70 @@ void View::GetBatches()
         {
             Drawable* drawable = *i;
             Zone* zone = GetZone(drawable);
-            unsigned numBatches = drawable->GetNumBatches();
+            const Vector<SourceBatch>& batches = drawable->GetBatches();
             
             const PODVector<Light*>& drawableVertexLights = drawable->GetVertexLights();
             if (!drawableVertexLights.Empty())
                 drawable->LimitVertexLights();
             
-            for (unsigned j = 0; j < numBatches; ++j)
+            for (unsigned j = 0; j < batches.Size(); ++j)
             {
-                Batch baseBatch;
-                drawable->GetBatch(baseBatch, frame_, j);
+                const SourceBatch& srcBatch = batches[j];
                 
                 // Check here if the material refers to a rendertarget texture with camera(s) attached
                 // Only check this for the main view (null rendertarget)
-                if (baseBatch.material_ && baseBatch.material_->GetAuxViewFrameNumber() != frame_.frameNumber_ && !renderTarget_)
-                    CheckMaterialForAuxView(baseBatch.material_);
+                if (srcBatch.material_ && srcBatch.material_->GetAuxViewFrameNumber() != frame_.frameNumber_ && !renderTarget_)
+                    CheckMaterialForAuxView(srcBatch.material_);
                 
                 // If already has a lit base pass, skip (forward rendering only)
                 if (j < 32 && drawable->HasBasePass(j))
                     continue;
                 
-                Technique* tech = GetTechnique(drawable, baseBatch.material_);
-                if (!baseBatch.geometry_ || !tech)
+                Technique* tech = GetTechnique(drawable, srcBatch.material_);
+                if (!srcBatch.geometry_ || !tech)
                     continue;
                 
-                // Fill the rest of the batch
-                baseBatch.camera_ = camera_;
-                baseBatch.zone_ = zone;
-                baseBatch.isBase_ = true;
-                baseBatch.pass_ = 0;
-                baseBatch.lightMask_ = GetLightMask(drawable);
+                Batch destBatch;
+                
+                // Copy the rest of the batch
+                destBatch.CopyFrom(srcBatch);
+                destBatch.camera_ = camera_;
+                destBatch.zone_ = zone;
+                destBatch.isBase_ = true;
+                destBatch.pass_ = 0;
+                destBatch.lightMask_ = GetLightMask(drawable);
                 
                 // In deferred modes check for G-buffer and material passes first
                 if (renderMode_ == RENDER_PREPASS)
                 {
-                    baseBatch.pass_ = tech->GetPass(PASS_PREPASS);
-                    if (baseBatch.pass_)
+                    destBatch.pass_ = tech->GetPass(PASS_PREPASS);
+                    if (destBatch.pass_)
                     {
                         // If the opaque object has a zero lightmask, have to skip light buffer optimization
                         if (!hasZeroLightMask_ && (!(GetLightMask(drawable) & 0xff)))
                             hasZeroLightMask_ = true;
                         
                         // Allow G-buffer pass instancing only if lightmask matches zone lightmask
-                        AddBatchToQueue(gbufferQueue_, baseBatch, tech, baseBatch.lightMask_ == (zone->GetLightMask() & 0xff));
-                        baseBatch.pass_ = tech->GetPass(PASS_MATERIAL);
+                        AddBatchToQueue(gbufferQueue_, destBatch, tech, destBatch.lightMask_ == (zone->GetLightMask() & 0xff));
+                        destBatch.pass_ = tech->GetPass(PASS_MATERIAL);
                     }
                 }
                 
                 if (renderMode_ == RENDER_DEFERRED)
-                    baseBatch.pass_ = tech->GetPass(PASS_DEFERRED);
+                    destBatch.pass_ = tech->GetPass(PASS_DEFERRED);
                 
                 // Next check for forward unlit base pass
-                if (!baseBatch.pass_)
-                    baseBatch.pass_ = tech->GetPass(PASS_BASE);
+                if (!destBatch.pass_)
+                    destBatch.pass_ = tech->GetPass(PASS_BASE);
                 
-                if (baseBatch.pass_)
+                if (destBatch.pass_)
                 {
                     // Check for vertex lights (both forward unlit, light pre-pass material pass, and deferred G-buffer)
                     if (!drawableVertexLights.Empty())
                     {
                         // For a deferred opaque batch, check if the vertex lights include converted per-pixel lights, and remove
                         // them to prevent double-lighting
-                        if (renderMode_ != RENDER_FORWARD && baseBatch.pass_->GetBlendMode() == BLEND_REPLACE)
+                        if (renderMode_ != RENDER_FORWARD && destBatch.pass_->GetBlendMode() == BLEND_REPLACE)
                         {
                             vertexLights.Clear();
                             for (unsigned i = 0; i < drawableVertexLights.Size(); ++i)
@@ -960,42 +961,42 @@ void View::GetBatches()
                                 i->second_.vertexLights_ = vertexLights;
                             }
                             
-                            baseBatch.lightQueue_ = &(i->second_);
+                            destBatch.lightQueue_ = &(i->second_);
                         }
                     }
                     
                     // Check whether batch is opaque or transparent
-                    if (baseBatch.pass_->GetBlendMode() == BLEND_REPLACE)
+                    if (destBatch.pass_->GetBlendMode() == BLEND_REPLACE)
                     {
-                        if (baseBatch.pass_->GetType() != PASS_DEFERRED)
-                            AddBatchToQueue(baseQueue_, baseBatch, tech);
+                        if (destBatch.pass_->GetType() != PASS_DEFERRED)
+                            AddBatchToQueue(baseQueue_, destBatch, tech);
                         else
                         {
                             // Allow G-buffer pass instancing only if lightmask matches zone lightmask
-                            AddBatchToQueue(gbufferQueue_, baseBatch, tech, baseBatch.lightMask_ == (baseBatch.zone_->GetLightMask() & 0xff));
+                            AddBatchToQueue(gbufferQueue_, destBatch, tech, destBatch.lightMask_ == (destBatch.zone_->GetLightMask() & 0xff));
                         }
                     }
                     else
                     {
                         // Transparent batches can not be instanced
-                        AddBatchToQueue(alphaQueue_, baseBatch, tech, false);
+                        AddBatchToQueue(alphaQueue_, destBatch, tech, false);
                     }
                     continue;
                 }
                 
                 // If no pass found so far, finally check for pre-alpha / post-alpha custom passes
-                baseBatch.pass_ = tech->GetPass(PASS_PREALPHA);
-                if (baseBatch.pass_)
+                destBatch.pass_ = tech->GetPass(PASS_PREALPHA);
+                if (destBatch.pass_)
                 {
-                    AddBatchToQueue(preAlphaQueue_, baseBatch, tech);
+                    AddBatchToQueue(preAlphaQueue_, destBatch, tech);
                     continue;
                 }
                 
-                baseBatch.pass_ = tech->GetPass(PASS_POSTALPHA);
-                if (baseBatch.pass_)
+                destBatch.pass_ = tech->GetPass(PASS_POSTALPHA);
+                if (destBatch.pass_)
                 {
                     // Post-alpha pass is treated similarly as alpha, and is not instanced
-                    AddBatchToQueue(postAlphaQueue_, baseBatch, tech, false);
+                    AddBatchToQueue(postAlphaQueue_, destBatch, tech, false);
                     continue;
                 }
             }
@@ -1099,20 +1100,19 @@ void View::GetLitBatches(Drawable* drawable, LightBatchQueue& lightQueue)
 {
     Light* light = lightQueue.light_;
     Zone* zone = GetZone(drawable);
+    const Vector<SourceBatch>& batches = drawable->GetBatches();
     
     bool hasAmbientGradient = zone->GetAmbientGradient() && zone->GetAmbientStartColor() != zone->GetAmbientEndColor();
     // Shadows on transparencies can only be rendered if shadow maps are not reused
     bool allowTransparentShadows = !renderer_->GetReuseShadowMaps();
     bool allowLitBase = light == drawable->GetFirstLight() && drawable->GetVertexLights().Empty() && !hasAmbientGradient;
-    unsigned numBatches = drawable->GetNumBatches();
     
-    for (unsigned i = 0; i < numBatches; ++i)
+    for (unsigned i = 0; i < batches.Size(); ++i)
     {
-        Batch litBatch;
-        drawable->GetBatch(litBatch, frame_, i);
+        const SourceBatch& srcBatch = batches[i];
         
-        Technique* tech = GetTechnique(drawable, litBatch.material_);
-        if (!litBatch.geometry_ || !tech)
+        Technique* tech = GetTechnique(drawable, srcBatch.material_);
+        if (!srcBatch.geometry_ || !tech)
             continue;
         
         // Do not create pixel lit forward passes for materials that render into the G-buffer
@@ -1120,39 +1120,42 @@ void View::GetLitBatches(Drawable* drawable, LightBatchQueue& lightQueue)
             tech->HasPass(PASS_DEFERRED)))
             continue;
         
+        Batch destBatch;
+        
         // Check for lit base pass. Because it uses the replace blend mode, it must be ensured to be the first light
         // Also vertex lighting or ambient gradient require the non-lit base pass, so skip in those cases
         if (i < 32 && allowLitBase)
         {
-            litBatch.pass_ = tech->GetPass(PASS_LITBASE);
-            if (litBatch.pass_)
+            destBatch.pass_ = tech->GetPass(PASS_LITBASE);
+            if (destBatch.pass_)
             {
-                litBatch.isBase_ = true;
+                destBatch.isBase_ = true;
                 drawable->SetBasePass(i);
             }
             else
-                litBatch.pass_ = tech->GetPass(PASS_LIGHT);
+                destBatch.pass_ = tech->GetPass(PASS_LIGHT);
         }
         else
-            litBatch.pass_ = tech->GetPass(PASS_LIGHT);
+            destBatch.pass_ = tech->GetPass(PASS_LIGHT);
         
         // Skip if material does not receive light at all
-        if (!litBatch.pass_)
+        if (!destBatch.pass_)
             continue;
         
         // Fill the rest of the batch
-        litBatch.camera_ = camera_;
-        litBatch.lightQueue_ = &lightQueue;
-        litBatch.zone_ = zone;
+        destBatch.CopyFrom(srcBatch);
+        destBatch.camera_ = camera_;
+        destBatch.lightQueue_ = &lightQueue;
+        destBatch.zone_ = zone;
         
         // Check from the ambient pass whether the object is opaque or transparent
         Pass* ambientPass = tech->GetPass(PASS_BASE);
         if (!ambientPass || ambientPass->GetBlendMode() == BLEND_REPLACE)
-            AddBatchToQueue(lightQueue.litBatches_, litBatch, tech);
+            AddBatchToQueue(lightQueue.litBatches_, destBatch, tech);
         else
         {
             // Transparent batches can not be instanced
-            AddBatchToQueue(alphaQueue_, litBatch, tech, false, allowTransparentShadows);
+            AddBatchToQueue(alphaQueue_, destBatch, tech, false, allowTransparentShadows);
         }
     }
 }
@@ -1644,7 +1647,7 @@ void View::UpdateOccluders(PODVector<Drawable*>& occluders, Camera* camera)
         bool erase = false;
         
         if (!occluder->IsInView(frame_, false))
-            occluder->UpdateDistance(frame_);
+            occluder->UpdateBatches(frame_);
         
         // Check occluder's draw distance (in main camera view)
         float maxDistance = occluder->GetDrawDistance();
@@ -1848,10 +1851,10 @@ void View::ProcessShadowCasters(LightQueryResult& query, const PODVector<Drawabl
         if (type == LIGHT_POINT && shadowCameraFrustum.IsInsideFast(drawable->GetWorldBoundingBox()) == OUTSIDE)
             continue;
         
-        // Note: as lights are processed threaded, it is possible a drawable's UpdateDistance() function is called several
+        // Note: as lights are processed threaded, it is possible a drawable's UpdateBatches() function is called several
         // times. However, this should not cause problems as no scene modification happens at this point.
         if (!drawable->IsInView(frame_, false))
-            drawable->UpdateDistance(frame_);
+            drawable->UpdateBatches(frame_);
         
         // Check shadow distance
         float maxShadowDistance = drawable->GetShadowDistance();
@@ -2259,10 +2262,13 @@ unsigned long long View::GetVertexLightQueueHash(const PODVector<Light*>& vertex
     return hash;
 }
 
-Technique* View::GetTechnique(Drawable* drawable, Material*& material)
+Technique* View::GetTechnique(Drawable* drawable, Material* material)
 {
     if (!material)
-        material = renderer_->GetDefaultMaterial();
+    {
+        const Vector<TechniqueEntry>& techniques = renderer_->GetDefaultMaterial()->GetTechniques();
+        return techniques.Size() ? techniques[0].technique_ : (Technique*)0;
+    }
     
     const Vector<TechniqueEntry>& techniques = material->GetTechniques();
     // If only one technique, no choice
@@ -2336,6 +2342,9 @@ void View::CheckMaterialForAuxView(Material* material)
 
 void View::AddBatchToQueue(BatchQueue& batchQueue, Batch& batch, Technique* tech, bool allowInstancing, bool allowShadows)
 {
+    if (!batch.material_)
+        batch.material_ = renderer_->GetDefaultMaterial();
+    
     // Convert to instanced if possible
     if (allowInstancing && batch.geometryType_ == GEOM_STATIC && !batch.shaderData_ && !batch.overrideView_)
         batch.geometryType_ = GEOM_INSTANCED;

+ 1 - 1
Engine/Graphics/View.h

@@ -161,7 +161,7 @@ private:
     /// Return hash code for a vertex light queue.
     unsigned long long GetVertexLightQueueHash(const PODVector<Light*>& vertexLights);
     /// Return material technique, considering the drawable's LOD distance.
-    Technique* GetTechnique(Drawable* drawable, Material*& material);
+    Technique* GetTechnique(Drawable* drawable, Material* material);
     /// Check if material should render an auxiliary view (if it has a camera attached.)
     void CheckMaterialForAuxView(Material* material);
     /// Choose shaders for a batch and add it to queue.