Skip to content

Commit

Permalink
CMCL-1583: fix camera update logic, brain update modes sample (#981)
Browse files Browse the repository at this point in the history
* fix camera update logic, brain update modes sample

* cleanup
  • Loading branch information
glabute authored May 9, 2024
1 parent 1153313 commit 5a53a47
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 200 deletions.
1 change: 1 addition & 0 deletions com.unity.cinemachine/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Fixed
- Bugfix: InputAxis.TriggerRecentering() function caused the axis to immediately snap to its recenter value.
- Bugfix: When multiple CM Brains were present, FixedUpdte cameras were sometimes being updated too frequently, resulting in jittery motion.
- SimplePlayerController no longer uses PlayerController.isGrounded because it's not reliable outside of FixedUpdate.

### Changed
Expand Down
30 changes: 15 additions & 15 deletions com.unity.cinemachine/Runtime/Behaviours/CinemachineBrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ void OnDisable()

m_BlendManager.OnDisable();
StopCoroutine(m_PhysicsCoroutine);
UpdateTracker.ForgetContext(this);
CameraUpdateManager.ForgetContext(this);
}

void OnSceneLoaded(Scene scene, LoadSceneMode mode)
Expand Down Expand Up @@ -518,34 +520,32 @@ public void ManualUpdate()
m_LastFrameUpdated = Time.frameCount;

float deltaTime = GetEffectiveDeltaTime(false);
if (!Application.isPlaying || BlendUpdateMethod != BrainUpdateMethods.FixedUpdate)
m_BlendManager.UpdateRootFrame(this, TopCameraFromPriorityQueue(), DefaultWorldUp, deltaTime);

m_BlendManager.ComputeCurrentBlend();

if (Application.isPlaying && UpdateMethod == UpdateMethods.FixedUpdate)
if (Application.isPlaying && (UpdateMethod == UpdateMethods.FixedUpdate || Time.inFixedTimeStep))
{
CameraUpdateManager.s_CurrentUpdateFilter = CameraUpdateManager.UpdateFilter.Fixed;

// Special handling for fixed update: cameras that have been enabled
// since the last physics frame must be updated now
if (BlendUpdateMethod != BrainUpdateMethods.FixedUpdate)
{
CameraUpdateManager.s_CurrentUpdateFilter = CameraUpdateManager.UpdateFilter.Fixed;
if (CinemachineCore.SoloCamera == null)
m_BlendManager.RefreshCurrentCameraState(DefaultWorldUp, GetEffectiveDeltaTime(true));
}
if (BlendUpdateMethod != BrainUpdateMethods.FixedUpdate && CinemachineCore.SoloCamera == null)
m_BlendManager.RefreshCurrentCameraState(DefaultWorldUp, GetEffectiveDeltaTime(true));
}
else
{
var filter = CameraUpdateManager.UpdateFilter.Late;
if (UpdateMethod == UpdateMethods.SmartUpdate)
{
// Track the targets
UpdateTracker.OnUpdate(UpdateTracker.UpdateClock.Late);
UpdateTracker.OnUpdate(UpdateTracker.UpdateClock.Late, this);
filter = CameraUpdateManager.UpdateFilter.SmartLate;
}
UpdateVirtualCameras(filter, deltaTime);
}

if (!Application.isPlaying || BlendUpdateMethod != BrainUpdateMethods.FixedUpdate)
m_BlendManager.UpdateRootFrame(this, TopCameraFromPriorityQueue(), DefaultWorldUp, deltaTime);

m_BlendManager.ComputeCurrentBlend();

// Choose the active vcam and apply it to the Unity camera
if (!Application.isPlaying || BlendUpdateMethod != BrainUpdateMethods.FixedUpdate)
ProcessActiveCamera(deltaTime);
Expand All @@ -561,7 +561,7 @@ void DoFixedUpdate()
if (UpdateMethod == UpdateMethods.SmartUpdate)
{
// Track the targets
UpdateTracker.OnUpdate(UpdateTracker.UpdateClock.Fixed);
UpdateTracker.OnUpdate(UpdateTracker.UpdateClock.Fixed, this);
filter = CameraUpdateManager.UpdateFilter.SmartFixed;
}
UpdateVirtualCameras(filter, GetEffectiveDeltaTime(true));
Expand Down Expand Up @@ -596,7 +596,7 @@ void UpdateVirtualCameras(CameraUpdateManager.UpdateFilter updateFilter, float d
{
// We always update all active virtual cameras
CameraUpdateManager.s_CurrentUpdateFilter = updateFilter;
CameraUpdateManager.UpdateAllActiveVirtualCameras((uint)ChannelMask, DefaultWorldUp, deltaTime);
CameraUpdateManager.UpdateAllActiveVirtualCameras((uint)ChannelMask, DefaultWorldUp, deltaTime, this);

// Make sure all live cameras get updated, in case some of them are deactivated
if (CinemachineCore.SoloCamera != null)
Expand Down
106 changes: 53 additions & 53 deletions com.unity.cinemachine/Runtime/Core/CameraUpdateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,36 @@ namespace Unity.Cinemachine
/// <summary>Owns Camera Registry.
/// Provides services to update cinemachine cameras and keep track of
/// whether and how they have been updated each frame.</summary>
static class CameraUpdateManager
internal static class CameraUpdateManager
{
static readonly VirtualCameraRegistry s_CameraRegistry = new ();
static CinemachineVirtualCameraBase s_RoundRobinVcamLastFrame = null;
static float s_LastUpdateTime;
static int s_FixedFrameCount; // Current fixed frame count
static int s_RoundRobinIndex = 0;
static int s_RoundRobinSubIndex = 0;
static object s_LastFixedUpdateContext;
static float s_LastUpdateTime = 0;
static int s_FixedFrameCount = 0; // Current fixed frame count

class UpdateStatus
{
public int lastUpdateFrame;
public int lastUpdateFixedFrame;
public UpdateTracker.UpdateClock lastUpdateMode;
public float lastUpdateDeltaTime;
}
static Dictionary<CinemachineVirtualCameraBase, UpdateStatus> s_UpdateStatus;

[RuntimeInitializeOnLoadMethod]
static void InitializeModule() => s_UpdateStatus = new ();

/// <summary>Internal use only</summary>
internal enum UpdateFilter
public enum UpdateFilter
{
Fixed = UpdateTracker.UpdateClock.Fixed,
Late = UpdateTracker.UpdateClock.Late,
Smart = 8, // meant to be or'ed with the others
SmartFixed = Smart | Fixed,
SmartLate = Smart | Late
}
internal static UpdateFilter s_CurrentUpdateFilter;
public static UpdateFilter s_CurrentUpdateFilter;

/// <summary>
/// List of all active CinemachineCameras for all brains.
Expand All @@ -50,102 +51,104 @@ public static CinemachineVirtualCameraBase GetVirtualCamera(int index)
=> s_CameraRegistry.GetActiveCamera(index);

/// <summary>Called when a CinemachineCamera is enabled.</summary>
internal static void AddActiveCamera(CinemachineVirtualCameraBase vcam)
public static void AddActiveCamera(CinemachineVirtualCameraBase vcam)
=> s_CameraRegistry.AddActiveCamera(vcam);

/// <summary>Called when a CinemachineCamera is disabled.</summary>
internal static void RemoveActiveCamera(CinemachineVirtualCameraBase vcam)
public static void RemoveActiveCamera(CinemachineVirtualCameraBase vcam)
=> s_CameraRegistry.RemoveActiveCamera(vcam);

/// <summary>Called when a CinemachineCamera is destroyed.</summary>
internal static void CameraDestroyed(CinemachineVirtualCameraBase vcam)
public static void CameraDestroyed(CinemachineVirtualCameraBase vcam)
{
s_CameraRegistry.CameraDestroyed(vcam);
if (s_UpdateStatus != null && s_UpdateStatus.ContainsKey(vcam))
s_UpdateStatus.Remove(vcam);
}

/// <summary>Called when a vcam is enabled.</summary>
internal static void CameraEnabled(CinemachineVirtualCameraBase vcam)
public static void CameraEnabled(CinemachineVirtualCameraBase vcam)
=> s_CameraRegistry.CameraEnabled(vcam);

/// <summary>Called when a vcam is disabled.</summary>
internal static void CameraDisabled(CinemachineVirtualCameraBase vcam)
public static void CameraDisabled(CinemachineVirtualCameraBase vcam)
=> s_CameraRegistry.CameraDisabled(vcam);

public static void ForgetContext(object context)
{
s_CameraRegistry.CameraDisabled(vcam);
if (s_RoundRobinVcamLastFrame == vcam)
s_RoundRobinVcamLastFrame = null;
if (s_LastFixedUpdateContext == context)
s_LastFixedUpdateContext = null;
}

/// <summary>Update all the active vcams in the scene, in the correct dependency order.</summary>
internal static void UpdateAllActiveVirtualCameras(uint channelMask, Vector3 worldUp, float deltaTime)
public static void UpdateAllActiveVirtualCameras(uint channelMask, Vector3 worldUp, float deltaTime, object context)
{
// Setup for roundRobin standby updating
var filter = s_CurrentUpdateFilter;
bool canUpdateStandby = (filter != UpdateFilter.SmartFixed); // never in smart fixed
var currentRoundRobin = s_RoundRobinVcamLastFrame;
// Update the fixed frame count - do it only once per fixed frmae
if ((s_CurrentUpdateFilter & ~UpdateFilter.Smart) == UpdateFilter.Fixed
&& (s_LastFixedUpdateContext == null || s_LastFixedUpdateContext == context))
{
++s_FixedFrameCount;
s_LastFixedUpdateContext = context;
}

// Update the fixed frame count
// Advance the round-robin index once per rendered frame
var allCameras = s_CameraRegistry.AllCamerasSortedByNestingLevel;
float now = CinemachineCore.CurrentTime;
if (now != s_LastUpdateTime)
{
s_LastUpdateTime = now;
if ((filter & ~UpdateFilter.Smart) == UpdateFilter.Fixed)
++s_FixedFrameCount;
if (allCameras.Count > 0)
{
if (s_RoundRobinIndex >= allCameras.Count)
s_RoundRobinIndex = 0;
if (++s_RoundRobinSubIndex >= allCameras[s_RoundRobinIndex].Count)
{
s_RoundRobinSubIndex = 0;
if (++s_RoundRobinIndex >= allCameras.Count)
s_RoundRobinIndex = 0;
}
}
}

// Update the leaf-most cameras first
var allCameras = s_CameraRegistry.AllCamerasSortedByNestingLevel;
for (int i = allCameras.Count-1; i >= 0; --i)
{
var sublist = allCameras[i];
for (int j = sublist.Count - 1; j >= 0; --j)
{
var vcam = sublist[j];
if (canUpdateStandby && vcam == s_RoundRobinVcamLastFrame)
currentRoundRobin = null; // update the next roundrobin candidate
if (vcam == null)
{
sublist.RemoveAt(j);
continue; // deleted
}
if (vcam.StandbyUpdate == CinemachineVirtualCameraBase.StandbyUpdateMode.Always
|| CinemachineCore.IsLive(vcam))

// Skip this vcam if it's not on the channel mask
if (((uint)vcam.OutputChannel & channelMask) == 0)
continue;

if (CinemachineCore.IsLive(vcam)
|| vcam.StandbyUpdate == CinemachineVirtualCameraBase.StandbyUpdateMode.Always)
{
// Skip this vcam if it's not on the channel mask
if (((uint)vcam.OutputChannel & channelMask) != 0)
UpdateVirtualCamera(vcam, worldUp, deltaTime);
UpdateVirtualCamera(vcam, worldUp, deltaTime);
}
else if (currentRoundRobin == null
&& s_RoundRobinVcamLastFrame != vcam
&& canUpdateStandby
&& vcam.StandbyUpdate != CinemachineVirtualCameraBase.StandbyUpdateMode.Never
// Do round-robin update
else if (vcam.StandbyUpdate == CinemachineVirtualCameraBase.StandbyUpdateMode.RoundRobin
&& s_RoundRobinIndex == i && s_RoundRobinSubIndex == j
&& vcam.isActiveAndEnabled)
{
// Do the round-robin update
s_CurrentUpdateFilter &= ~UpdateFilter.Smart; // force it
UpdateVirtualCamera(vcam, worldUp, deltaTime);
s_CurrentUpdateFilter = filter;
currentRoundRobin = vcam;
}
}
}

// Did we manage to update a roundrobin?
if (canUpdateStandby)
{
if (currentRoundRobin == s_RoundRobinVcamLastFrame)
currentRoundRobin = null; // take the first candidate
s_RoundRobinVcamLastFrame = currentRoundRobin;
}
}

/// <summary>
/// Update a single CinemachineCamera if and only if it
/// hasn't already been updated this frame. Always update vcams via this method.
/// Calling this more than once per frame for the same camera will have no effect.
/// </summary>
internal static void UpdateVirtualCamera(
public static void UpdateVirtualCamera(
CinemachineVirtualCameraBase vcam, Vector3 worldUp, float deltaTime)
{
if (vcam == null)
Expand All @@ -172,7 +175,6 @@ internal static void UpdateVirtualCamera(
{
status = new UpdateStatus
{
lastUpdateDeltaTime = -2,
lastUpdateMode = UpdateTracker.UpdateClock.Late,
lastUpdateFrame = Time.frameCount + 2, // so that frameDelta ends up negative
lastUpdateFixedFrame = s_FixedFrameCount + 2
Expand All @@ -186,8 +188,7 @@ internal static void UpdateVirtualCamera(

if (deltaTime >= 0)
{
if (frameDelta == 0 && status.lastUpdateMode == updateClock
&& status.lastUpdateDeltaTime == deltaTime)
if (frameDelta == 0 && status.lastUpdateMode == updateClock)
return; // already updated
if (!CinemachineCore.UnitTestMode && frameDelta > 0)
deltaTime *= frameDelta; // try to catch up if multiple frames
Expand All @@ -198,7 +199,6 @@ internal static void UpdateVirtualCamera(
status.lastUpdateFrame = Time.frameCount;
status.lastUpdateFixedFrame = s_FixedFrameCount;
status.lastUpdateMode = updateClock;
status.lastUpdateDeltaTime = deltaTime;
}

static Transform GetUpdateTarget(CinemachineVirtualCameraBase vcam)
Expand All @@ -216,7 +216,7 @@ static Transform GetUpdateTarget(CinemachineVirtualCameraBase vcam)
}

/// <summary>Internal use only - inspector</summary>
internal static UpdateTracker.UpdateClock GetVcamUpdateStatus(CinemachineVirtualCameraBase vcam)
public static UpdateTracker.UpdateClock GetVcamUpdateStatus(CinemachineVirtualCameraBase vcam)
{
if (s_UpdateStatus == null || !s_UpdateStatus.TryGetValue(vcam, out UpdateStatus status))
return UpdateTracker.UpdateClock.Late;
Expand Down
17 changes: 11 additions & 6 deletions com.unity.cinemachine/Runtime/Core/UpdateTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Unity.Cinemachine
/// </summary>
internal class UpdateTracker
{
public enum UpdateClock { Fixed, Late }
public enum UpdateClock { Fixed = 1, Late = 2}

class UpdateStatus
{
Expand Down Expand Up @@ -113,16 +113,21 @@ public static UpdateClock GetPreferredUpdate(Transform target)
return UpdateClock.Late;
}

static float s_LastUpdateTime;
public static void OnUpdate(UpdateClock currentClock)
static object s_LastUpdateContext;
public static void OnUpdate(UpdateClock currentClock, object context)
{
// Do something only if we are the first controller processing this frame
float now = CinemachineCore.CurrentTime;
if (now != s_LastUpdateTime)
if (s_LastUpdateContext == null || s_LastUpdateContext == context)
{
s_LastUpdateTime = now;
s_LastUpdateContext = context;
UpdateTargets(currentClock);
}
}

public static void ForgetContext(object context)
{
if (s_LastUpdateContext == context)
s_LastUpdateContext = null;
}
}
}
Loading

0 comments on commit 5a53a47

Please sign in to comment.