Prechádzať zdrojové kódy

Maximum amount of sorted instances can be configured in Renderer. By default 1000.
Moved the NetworkState pointer back to Serializable, as there was not much practical benefit.

Lasse Öörni 13 rokov pred
rodič
commit
31e027b354

+ 1 - 0
Docs/ScriptAPI.dox

@@ -2340,6 +2340,7 @@ Properties:<br>
 - bool reuseShadowMaps
 - bool dynamicInstancing
 - int maxInstanceTriangles
+- int maxSortedInstances
 - int maxOccluderTriangles
 - int occlusionBufferSize
 - float occluderSizeThreshold

+ 2 - 0
Engine/Engine/GraphicsAPI.cpp

@@ -845,6 +845,8 @@ static void RegisterRenderer(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Renderer", "bool get_dynamicInstancing() const", asMETHOD(Renderer, GetDynamicInstancing), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "void set_maxInstanceTriangles(int)", asMETHOD(Renderer, SetMaxInstanceTriangles), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "int get_maxInstanceTriangles() const", asMETHOD(Renderer, GetMaxInstanceTriangles), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Renderer", "void set_maxSortedInstances(int)", asMETHOD(Renderer, SetMaxSortedInstances), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Renderer", "int get_maxSortedInstances() const", asMETHOD(Renderer, GetMaxSortedInstances), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "void set_maxOccluderTriangles(int)", asMETHOD(Renderer, SetMaxOccluderTriangles), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "int get_maxOccluderTriangles() const", asMETHOD(Renderer, GetMaxOccluderTriangles), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "void set_occlusionBufferSize(int)", asMETHOD(Renderer, SetOcclusionBufferSize), asCALL_THISCALL);

+ 34 - 4
Engine/Graphics/Batch.cpp

@@ -62,7 +62,7 @@ inline bool CompareInstancesFrontToBack(const InstanceData& lhs, const InstanceD
 
 inline bool CompareBatchGroupsFrontToBack(BatchGroup* lhs, BatchGroup* rhs)
 {
-    return lhs->instances_[0].distance_ < rhs->instances_[0].distance_;
+    return lhs->distance_ < rhs->distance_;
 }
 
 void CalculateShadowMatrix(Matrix4& dest, LightBatchQueue* queue, unsigned split, Renderer* renderer, const Vector3& translation)
@@ -741,13 +741,14 @@ void BatchGroup::Draw(Graphics* graphics, Renderer* renderer) const
     }
 }
 
-void BatchQueue::Clear()
+void BatchQueue::Clear(int maxSortedInstances)
 {
     batches_.Clear();
     sortedBaseBatches_.Clear();
     sortedBatches_.Clear();
     baseBatchGroups_.Clear();
     batchGroups_.Clear();
+    maxSortedInstances_ = maxSortedInstances;
 }
 
 void BatchQueue::SortBackToFront()
@@ -792,9 +793,38 @@ void BatchQueue::SortFrontToBack()
     
     // Sort each group front to back
     for (HashMap<BatchGroupKey, BatchGroup>::Iterator i = baseBatchGroups_.Begin(); i != baseBatchGroups_.End(); ++i)
-        Sort(i->second_.instances_.Begin(), i->second_.instances_.End(), CompareInstancesFrontToBack);
+    {
+        if (i->second_.instances_.Size() <= maxSortedInstances_)
+        {
+            Sort(i->second_.instances_.Begin(), i->second_.instances_.End(), CompareInstancesFrontToBack);
+            if (i->second_.instances_.Size())
+                i->second_.distance_ = i->second_.instances_[0].distance_;
+        }
+        else
+        {
+            float minDistance = M_INFINITY;
+            for (PODVector<InstanceData>::ConstIterator j = i->second_.instances_.Begin(); j != i->second_.instances_.End(); ++j)
+                minDistance = Min(minDistance, j->distance_);
+            i->second_.distance_ = minDistance;
+        }
+    }
+    
     for (HashMap<BatchGroupKey, BatchGroup>::Iterator i = batchGroups_.Begin(); i != batchGroups_.End(); ++i)
-        Sort(i->second_.instances_.Begin(), i->second_.instances_.End(), CompareInstancesFrontToBack);
+    {
+        if (i->second_.instances_.Size() <= maxSortedInstances_)
+        {
+            Sort(i->second_.instances_.Begin(), i->second_.instances_.End(), CompareInstancesFrontToBack);
+            if (i->second_.instances_.Size())
+                i->second_.distance_ = i->second_.instances_[0].distance_;
+        }
+        else
+        {
+            float minDistance = M_INFINITY;
+            for (PODVector<InstanceData>::ConstIterator j = i->second_.instances_.Begin(); j != i->second_.instances_.End(); ++j)
+                minDistance = Min(minDistance, j->distance_);
+            i->second_.distance_ = minDistance;
+        }
+    }
     
     // Now sort batch groups by the distance of the first batch
     sortedBaseBatchGroups_.Resize(baseBatchGroups_.Size());

+ 6 - 4
Engine/Graphics/Batch.h

@@ -197,8 +197,8 @@ struct BatchGroupKey
 struct BatchQueue
 {
 public:
-    /// Clear everything.
-    void Clear();
+    /// Clear for new frame by clearing all groups and batches.
+    void Clear(int maxSortedInstances);
     /// Sort non-instanced draw calls back to front.
     void SortBackToFront();
     /// Sort instanced and non-instanced draw calls front to back.
@@ -228,6 +228,8 @@ public:
     PODVector<BatchGroup*> sortedBaseBatchGroups_;
     /// Sorted instanced draw calls.
     PODVector<BatchGroup*> sortedBatchGroups_;
+    /// Maximum sorted instances.
+    unsigned maxSortedInstances_;
 };
 
 /// Queue for shadow map draw calls
@@ -250,10 +252,10 @@ struct LightBatchQueue
 {
     /// Per-pixel light.
     Light* light_;
-    /// Lit geometry draw calls.
-    BatchQueue litBatches_;
     /// Shadow map depth texture.
     Texture2D* shadowMap_;
+    /// Lit geometry draw calls.
+    BatchQueue litBatches_;
     /// Shadow map split queues.
     Vector<ShadowBatchQueue> shadowSplits_;
     /// Per-vertex lights.

+ 6 - 0
Engine/Graphics/Renderer.cpp

@@ -274,6 +274,7 @@ Renderer::Renderer(Context* context) :
     maxShadowMaps_(1),
     maxShadowCascades_(MAX_CASCADE_SPLITS),
     maxInstanceTriangles_(500),
+    maxSortedInstances_(1000),
     maxOccluderTriangles_(5000),
     occlusionBufferSize_(256),
     occluderSizeThreshold_(0.025f),
@@ -478,6 +479,11 @@ void Renderer::SetMaxInstanceTriangles(int triangles)
     maxInstanceTriangles_ = Max(triangles, 0);
 }
 
+void Renderer::SetMaxSortedInstances(int instances)
+{
+    maxSortedInstances_ = Max(instances, 0);
+}
+
 void Renderer::SetMaxOccluderTriangles(int triangles)
 {
     maxOccluderTriangles_ = Max(triangles, 0);

+ 7 - 1
Engine/Graphics/Renderer.h

@@ -197,6 +197,8 @@ public:
     void SetDynamicInstancing(bool enable);
     /// %Set maximum number of triangles per object for instancing.
     void SetMaxInstanceTriangles(int triangles);
+    /// %Set maximum number of sorted instances per batch group. If exceeded, instances are rendered unsorted.
+    void SetMaxSortedInstances(int instances);
     /// %Set maximum number of occluder trianges.
     void SetMaxOccluderTriangles(int triangles);
     /// %Set occluder buffer width.
@@ -234,7 +236,9 @@ public:
     /// Return whether dynamic instancing is in use.
     bool GetDynamicInstancing() const { return dynamicInstancing_; }
     /// Return maximum number of triangles per object for instancing.
-    int GetMaxInstanceTriangles() { return maxInstanceTriangles_; }
+    int GetMaxInstanceTriangles() const { return maxInstanceTriangles_; }
+    /// Return maximum number of sorted instances per batch group.
+    int GetMaxSortedInstances() const { return maxSortedInstances_; }
     /// Return maximum number of occluder triangles.
     int GetMaxOccluderTriangles() const { return maxOccluderTriangles_; }
     /// Return occlusion buffer width.
@@ -447,6 +451,8 @@ private:
     int maxShadowCascades_;
     /// Maximum triangles per object for instancing.
     int maxInstanceTriangles_;
+    /// Maximum sorted instances per batch group.
+    int maxSortedInstances_;
     /// Maximum occluder triangles.
     int maxOccluderTriangles_;
     /// Occlusion buffer width.

+ 13 - 11
Engine/Graphics/View.cpp

@@ -395,6 +395,8 @@ void View::Update(const FrameInfo& frame)
     frame_.frameNumber_ = frame.frameNumber_;
     frame_.viewSize_ = viewSize_;
     
+    int maxSortedInstances = renderer_->GetMaxSortedInstances();
+    
     // Clear screen buffers, geometry, light, occluder & batch lists
     screenBuffers_.Clear();
     geometries_.Clear();
@@ -402,12 +404,11 @@ void View::Update(const FrameInfo& frame)
     lights_.Clear();
     zones_.Clear();
     occluders_.Clear();
-    baseQueue_.Clear();
-    preAlphaQueue_.Clear();
-    gbufferQueue_.Clear();
-    alphaQueue_.Clear();
-    postAlphaQueue_.Clear();
-    lightQueues_.Clear();
+    baseQueue_.Clear(maxSortedInstances);
+    preAlphaQueue_.Clear(maxSortedInstances);
+    gbufferQueue_.Clear(maxSortedInstances);
+    alphaQueue_.Clear(maxSortedInstances);
+    postAlphaQueue_.Clear(maxSortedInstances);
     vertexLightQueues_.Clear();
     
     // Do not update if camera projection is illegal
@@ -700,8 +701,6 @@ void View::GetBatches()
     {
         PROFILE(GetLightBatches);
         
-        maxLightsDrawables_.Clear();
-        
         // Preallocate light queues: per-pixel lights which have lit geometries
         unsigned numLightQueues = 0;
         unsigned usedLightQueues = 0;
@@ -712,6 +711,8 @@ void View::GetBatches()
         }
         
         lightQueues_.Resize(numLightQueues);
+        maxLightsDrawables_.Clear();
+        unsigned maxSortedInstances = renderer_->GetMaxSortedInstances();
         
         for (Vector<LightQueryResult>::Iterator i = lightQueryResults_.Begin(); i != lightQueryResults_.End(); ++i)
         {
@@ -728,15 +729,15 @@ void View::GetBatches()
             {
                 unsigned shadowSplits = query.numSplits_;
                 
-                // Initialize light queue. Store light-to-queue mapping so that the queue can be found later
+                // Initialize light queue and store it to the light so that it can be found later
                 LightBatchQueue& lightQueue = lightQueues_[usedLightQueues++];
                 light->SetLightQueue(&lightQueue);
                 lightQueue.light_ = light;
-                lightQueue.litBatches_.Clear();
+                lightQueue.shadowMap_ = 0;
+                lightQueue.litBatches_.Clear(maxSortedInstances);
                 lightQueue.volumeBatches_.Clear();
                 
                 // Allocate shadow map now
-                lightQueue.shadowMap_ = 0;
                 if (shadowSplits > 0)
                 {
                     lightQueue.shadowMap_ = renderer_->GetShadowMap(light, camera_, viewSize_.x_, viewSize_.y_);
@@ -754,6 +755,7 @@ void View::GetBatches()
                     shadowQueue.shadowCamera_ = shadowCamera;
                     shadowQueue.nearSplit_ = query.shadowNearSplits_[j];
                     shadowQueue.farSplit_ = query.shadowFarSplits_[j];
+                    shadowQueue.shadowBatches_.Clear(maxSortedInstances);
                     
                     // Setup the shadow split viewport and finalize shadow camera parameters
                     shadowQueue.shadowViewport_ = GetShadowMapViewport(light, j, lightQueue.shadowMap_);

+ 7 - 7
Engine/Network/Connection.cpp

@@ -1049,7 +1049,7 @@ void Connection::ProcessNewNode(Node* node)
     node->AddReplicationState(&nodeState);
     
     // Write node's attributes
-    node->WriteInitialDeltaUpdate(msg_, node->GetNetworkState());
+    node->WriteInitialDeltaUpdate(msg_);
     
     // Write node's user variables
     const VariantMap& vars = node->GetVars();
@@ -1078,7 +1078,7 @@ void Connection::ProcessNewNode(Node* node)
         
         msg_.WriteShortStringHash(component->GetType());
         msg_.WriteNetID(component->GetID());
-        component->WriteInitialDeltaUpdate(msg_, component->GetNetworkState());
+        component->WriteInitialDeltaUpdate(msg_);
     }
     
     SendMessage(MSG_CREATENODE, true, true, msg_);
@@ -1129,7 +1129,7 @@ void Connection::ProcessExistingNode(Node* node, NodeReplicationState& nodeState
         {
             msg_.Clear();
             msg_.WriteNetID(node->GetID());
-            node->WriteLatestDataUpdate(msg_, node->GetNetworkState());
+            node->WriteLatestDataUpdate(msg_);
             
             SendMessage(MSG_NODELATESTDATA, true, false, msg_, node->GetID());
         }
@@ -1139,7 +1139,7 @@ void Connection::ProcessExistingNode(Node* node, NodeReplicationState& nodeState
         {
             msg_.Clear();
             msg_.WriteNetID(node->GetID());
-            node->WriteDeltaUpdate(msg_, node->GetNetworkState(), nodeState.dirtyAttributes_);
+            node->WriteDeltaUpdate(msg_, nodeState.dirtyAttributes_);
             
             // Write changed variables
             msg_.WriteVLE(nodeState.dirtyVars_.Size());
@@ -1207,7 +1207,7 @@ void Connection::ProcessExistingNode(Node* node, NodeReplicationState& nodeState
                 {
                     msg_.Clear();
                     msg_.WriteNetID(component->GetID());
-                    component->WriteLatestDataUpdate(msg_, component->GetNetworkState());
+                    component->WriteLatestDataUpdate(msg_);
                     
                     SendMessage(MSG_COMPONENTLATESTDATA, true, false, msg_, component->GetID());
                 }
@@ -1217,7 +1217,7 @@ void Connection::ProcessExistingNode(Node* node, NodeReplicationState& nodeState
                 {
                     msg_.Clear();
                     msg_.WriteNetID(component->GetID());
-                    component->WriteDeltaUpdate(msg_, component->GetNetworkState(), componentState.dirtyAttributes_);
+                    component->WriteDeltaUpdate(msg_, componentState.dirtyAttributes_);
                     
                     SendMessage(MSG_COMPONENTDELTAUPDATE, true, true, msg_);
                     
@@ -1252,7 +1252,7 @@ void Connection::ProcessExistingNode(Node* node, NodeReplicationState& nodeState
                 msg_.WriteNetID(node->GetID());
                 msg_.WriteShortStringHash(component->GetType());
                 msg_.WriteNetID(component->GetID());
-                component->WriteInitialDeltaUpdate(msg_, component->GetNetworkState());
+                component->WriteInitialDeltaUpdate(msg_);
                 
                 SendMessage(MSG_CREATECOMPONENT, true, true, msg_);
             }

+ 2 - 11
Engine/Scene/Component.cpp

@@ -36,15 +36,12 @@ Component::Component(Context* context) :
     Serializable(context),
     node_(0),
     id_(0),
-    networkState_(0),
     networkUpdate_(false)
 {
 }
 
 Component::~Component()
 {
-    delete networkState_;
-    networkState_ = 0;
 }
 
 void Component::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
@@ -91,10 +88,7 @@ Scene* Component::GetScene() const
 void Component::AddReplicationState(ComponentReplicationState* state)
 {
     if (!networkState_)
-    {
-        networkState_ = new NetworkState();
-        networkState_->attributes_ = GetNetworkAttributes();
-    }
+        AllocateNetworkState();
     
     networkState_->replicationStates_.Push(state);
 }
@@ -102,10 +96,7 @@ void Component::AddReplicationState(ComponentReplicationState* state)
 void Component::PrepareNetworkUpdate()
 {
     if (!networkState_)
-    {
-        networkState_ = new NetworkState();
-        networkState_->attributes_ = GetNetworkAttributes();
-    }
+        AllocateNetworkState();
     
     const Vector<AttributeInfo>* attributes = networkState_->attributes_;
     if (!attributes)

+ 0 - 4
Engine/Scene/Component.h

@@ -80,8 +80,6 @@ public:
     void CleanupConnection(Connection* connection);
     /// Mark for attribute check on the next network update.
     void MarkNetworkUpdate();
-    /// Return the network attribute state.
-    NetworkState* GetNetworkState() const { return networkState_; }
     
 protected:
     /// Handle scene node being assigned at creation.
@@ -97,8 +95,6 @@ protected:
     Node* node_;
     /// Unique ID within the scene.
     unsigned id_;
-    /// Network attribute state.
-    NetworkState* networkState_;
     /// Network update queued flag.
     bool networkUpdate_;
 };

+ 3 - 13
Engine/Scene/Node.cpp

@@ -51,8 +51,7 @@ Node::Node(Context* context) :
     position_(Vector3::ZERO),
     rotation_(Quaternion::IDENTITY),
     scale_(Vector3::ONE),
-    owner_(0),
-    networkState_(0)
+    owner_(0)
 {
 }
 
@@ -64,9 +63,6 @@ Node::~Node()
     // Remove from the scene
     if (scene_)
         scene_->NodeRemoved(this);
-    
-    delete networkState_;
-    networkState_ = 0;
 }
 
 void Node::RegisterObject(Context* context)
@@ -225,10 +221,7 @@ void Node::ApplyAttributes()
 void Node::AddReplicationState(NodeReplicationState* state)
 {
     if (!networkState_)
-    {
-        networkState_ = new NetworkState();
-        networkState_->attributes_ = GetNetworkAttributes();
-    }
+        AllocateNetworkState();
     
     networkState_->replicationStates_.Push(state);
 }
@@ -865,10 +858,7 @@ void Node::PrepareNetworkUpdate()
     
     // Then check for node attribute changes
     if (!networkState_)
-    {
-        networkState_ = new NetworkState();
-        networkState_->attributes_ = GetNetworkAttributes();
-    }
+        AllocateNetworkState();
     
     const Vector<AttributeInfo>* attributes = networkState_->attributes_;
     unsigned numAttributes = attributes->Size();

+ 0 - 4
Engine/Scene/Node.h

@@ -317,8 +317,6 @@ public:
     void MarkNetworkUpdate();
     /// Mark node dirty in scene replication states.
     void MarkReplicationDirty();
-    /// Return the network attribute state.
-    NetworkState* GetNetworkState() const { return networkState_; }
     
 protected:
     /// Create a component with specific ID.
@@ -376,8 +374,6 @@ private:
     mutable VectorBuffer attrBuffer_;
     
 protected:
-    /// Network attribute state.
-    NetworkState* networkState_;
     /// User variables.
     VariantMap vars_;
 };

+ 29 - 16
Engine/Scene/Serializable.cpp

@@ -35,12 +35,15 @@
 OBJECTTYPESTATIC(Serializable);
 
 Serializable::Serializable(Context* context) :
-    Object(context)
+    Object(context),
+    networkState_(0)
 {
 }
 
 Serializable::~Serializable()
 {
+    delete networkState_;
+    networkState_ = 0;
 }
 
 void Serializable::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
@@ -412,15 +415,24 @@ bool Serializable::SetAttribute(const String& name, const Variant& value)
     return false;
 }
 
-void Serializable::WriteInitialDeltaUpdate(Serializer& dest, NetworkState* state)
+void Serializable::AllocateNetworkState()
 {
-    if (!state)
+    if (!networkState_)
+    {
+        networkState_ = new NetworkState();
+        networkState_->attributes_ = context_->GetNetworkAttributes(GetType());
+    }
+}
+
+void Serializable::WriteInitialDeltaUpdate(Serializer& dest)
+{
+    if (!networkState_)
     {
         LOGERROR("WriteInitialDeltaUpdate called without allocated NetworkState");
         return;
     }
     
-    const Vector<AttributeInfo>* attributes = state->attributes_;
+    const Vector<AttributeInfo>* attributes = networkState_->attributes_;
     if (!attributes)
         return;
     
@@ -431,7 +443,7 @@ void Serializable::WriteInitialDeltaUpdate(Serializer& dest, NetworkState* state
     for (unsigned i = 0; i < numAttributes; ++i)
     {
         const AttributeInfo& attr = attributes->At(i);
-        if (state->currentValues_[i] != attr.defaultValue_)
+        if (networkState_->currentValues_[i] != attr.defaultValue_)
             attributeBits.Set(i);
     }
     
@@ -441,19 +453,19 @@ void Serializable::WriteInitialDeltaUpdate(Serializer& dest, NetworkState* state
     for (unsigned i = 0; i < numAttributes; ++i)
     {
         if (attributeBits.IsSet(i))
-            dest.WriteVariantData(state->currentValues_[i]);
+            dest.WriteVariantData(networkState_->currentValues_[i]);
     }
 }
 
-void Serializable::WriteDeltaUpdate(Serializer& dest, NetworkState* state, const DirtyBits& attributeBits)
+void Serializable::WriteDeltaUpdate(Serializer& dest, const DirtyBits& attributeBits)
 {
-    if (!state)
+    if (!networkState_)
     {
         LOGERROR("WriteDeltaUpdate called without allocated NetworkState");
         return;
     }
     
-    const Vector<AttributeInfo>* attributes = state->attributes_;
+    const Vector<AttributeInfo>* attributes = networkState_->attributes_;
     if (!attributes)
         return;
     
@@ -466,19 +478,19 @@ void Serializable::WriteDeltaUpdate(Serializer& dest, NetworkState* state, const
     for (unsigned i = 0; i < numAttributes; ++i)
     {
         if (attributeBits.IsSet(i))
-            dest.WriteVariantData(state->currentValues_[i]);
+            dest.WriteVariantData(networkState_->currentValues_[i]);
     }
 }
 
-void Serializable::WriteLatestDataUpdate(Serializer& dest, NetworkState* state)
+void Serializable::WriteLatestDataUpdate(Serializer& dest)
 {
-    if (!state)
+    if (!networkState_)
     {
         LOGERROR("WriteLatestDataUpdate called without allocated NetworkState");
         return;
     }
     
-    const Vector<AttributeInfo>* attributes = state->attributes_;
+    const Vector<AttributeInfo>* attributes = networkState_->attributes_;
     if (!attributes)
         return;
     
@@ -487,7 +499,7 @@ void Serializable::WriteLatestDataUpdate(Serializer& dest, NetworkState* state)
     for (unsigned i = 0; i < numAttributes; ++i)
     {
         if (attributes->At(i).mode_ & AM_LATESTDATA)
-            dest.WriteVariantData(state->currentValues_[i]);
+            dest.WriteVariantData(networkState_->currentValues_[i]);
     }
 }
 
@@ -572,7 +584,8 @@ unsigned Serializable::GetNumAttributes() const
 
 unsigned Serializable::GetNumNetworkAttributes() const
 {
-    const Vector<AttributeInfo>* attributes = context_->GetNetworkAttributes(GetType());
+    const Vector<AttributeInfo>* attributes = networkState_ ? networkState_->attributes_ :
+        context_->GetNetworkAttributes(GetType());
     return attributes ? attributes->Size() : 0;
 }
 
@@ -583,5 +596,5 @@ const Vector<AttributeInfo>* Serializable::GetAttributes() const
 
 const Vector<AttributeInfo>* Serializable::GetNetworkAttributes() const
 {
-    return context_->GetNetworkAttributes(GetType());
+    return networkState_ ? networkState_->attributes_ : context_->GetNetworkAttributes(GetType());
 }

+ 9 - 3
Engine/Scene/Serializable.h

@@ -67,12 +67,14 @@ public:
     bool SetAttribute(unsigned index, const Variant& value);
     /// %Set attribute by name. Return true if successfully set.
     bool SetAttribute(const String& name, const Variant& value);
+    /// Allocate network attribute state.
+    void AllocateNetworkState();
     /// Write initial delta network update.
-    void WriteInitialDeltaUpdate(Serializer& dest, NetworkState* state);
+    void WriteInitialDeltaUpdate(Serializer& dest);
     /// Write a delta network update according to dirty attribute bits.
-    void WriteDeltaUpdate(Serializer& dest, NetworkState* state, const DirtyBits& attributeBits);
+    void WriteDeltaUpdate(Serializer& dest, const DirtyBits& attributeBits);
     /// Write a latest data network update.
-    void WriteLatestDataUpdate(Serializer& dest, NetworkState* state);
+    void WriteLatestDataUpdate(Serializer& dest);
     /// Read and apply a network delta update.
     void ReadDeltaUpdate(Deserializer& source);
     /// Read and apply a network latest data update.
@@ -90,6 +92,10 @@ public:
     const Vector<AttributeInfo>* GetAttributes() const;
     /// Return network replication attribute descriptions, or null if none defined.
     const Vector<AttributeInfo>* GetNetworkAttributes() const;
+    
+protected:
+    /// Network attribute state.
+    NetworkState* networkState_;
 };
 
 /// Template implementation of the attribute accessor invoke helper class.