-
Notifications
You must be signed in to change notification settings - Fork 219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initializing Deforms before Rendering #24
Comments
tldr: Deformables have a single-frame delay to increase performance. Paste the code at the bottom over the DeformableManager.cs file for Deformables to update in a single frame [edit] or you can set your deformable's manager reference to null someDeformable.Manager = null; which will unregister it and allow you to update it yourself using the following methods: // Schedules the deformables jobs and returns a handle to them
someDeformable.Schedule();
// Forces the scheduled jobs to complete
someDeformable.Complete();
// Applies the modified mesh data. Make sure work is completed before calling this!
someDeformable.Apply(); The order of events in a single frame for Deform are as follows:
To try and get better performance, Deform's calculations carry over to the next frame so there's always a one frame delay. I thought I was forcing the Deformable to update in a single frame when it gets enabled to prevent the single undeformed frame from being rendered, but evidently it isn't working. I looked in the #if UNITY_EDITOR
if (!Application.isPlaying && handle.IsCompleted)
{
Schedule ().Complete ();
ApplyData ();
}
#endif I removed the if statement and preprocessor directive so it always schedules, completes and applies changes on the first frame, but it still didn't work! It seemed the I'll see if I can sort this out, but in the meantime, if you don't mind sacrificing a bit of performance for single-frame updates, replace your
using System.Collections.Generic;
using UnityEngine;
using Unity.Jobs;
namespace Deform
{
/// <summary>
/// Manages scheduling deformables.
/// </summary>
[HelpURL ("https://github.com/keenanwoodall/Deform/wiki/DeformableManager")]
public class DeformableManager : MonoBehaviour
{
private static readonly string DEF_MANAGER_NAME = "DefaultDeformableManager";
private static DeformableManager defaultInstance;
/// <summary>
/// Returns the default manager.
/// </summary>
/// <param name="createIfMissing">If true, a manager will be created if one doesn't exist.</param>
/// <returns></returns>
public static DeformableManager GetDefaultManager (bool createIfMissing)
{
if (defaultInstance == null)
defaultInstance = new GameObject (DEF_MANAGER_NAME).AddComponent<DeformableManager> ();
return defaultInstance;
}
/// <summary>
/// Should the manager update?
/// </summary>
public bool update = true;
private HashSet<IDeformable> deformables = new HashSet<IDeformable> ();
private void Start ()
{
if (update)
{
ScheduleDeformables ();
CompleteDeformables ();
}
}
private void Update ()
{
if (update)
{
ScheduleDeformables ();
CompleteDeformables ();
}
}
private void OnDisable ()
{
CompleteDeformables ();
}
/// <summary>
/// Creates a chain of work from the deformables and schedules it.
/// </summary>
public void ScheduleDeformables ()
{
foreach (var deformable in deformables)
deformable.PreSchedule ();
foreach (var deformable in deformables)
{
deformable.Schedule ();
}
// Schedule the chain.
JobHandle.ScheduleBatchedJobs ();
}
/// <summary>
/// Finishes the schedules work chain.
/// </summary>
public void CompleteDeformables ()
{
foreach (var deformable in deformables)
{
deformable.Complete();
deformable.ApplyData();
}
}
/// <summary>
/// Registers a deformable to be updated by this manager.
/// </summary>
public void AddDeformable (IDeformable deformable)
{
deformables.Add (deformable);
}
/// <summary>
/// Unregisters a deformable from this manager.
/// </summary>
public void RemoveDeformable (IDeformable deformable)
{
deformables.Remove (deformable);
}
}
} |
Works perfectly. |
Need to reopen. |
@mikech2000 I looked into it further and you are right, the problem is still there. To test I was calling |
@mikech2000 So the issue was a little further reaching than I thought. It was a mix of dealing with Unity's script execution order hell and a small bug. The change is currently on the development branch at this commit. Let me know if it works for you and it'll make its way into the next release. |
I'm having a similar issue, but with a setup of continuously rolling and deformed tiles, when one of the tiles is teleported (so not only on the first frame of the game). Is it possible to force to Deform to finish updating on the same frame? Would overriding DeformableManager as you described be the easiest way to do that? Here's a setup demonstrating the issue: (see the blue flash on the ground) |
I have installed the development branch and find that the first frame problem has been fixed. |
@mikech2000 You can change your manifest.json like this (~from Unity version 2018.3 I think)
|
@nathanvogel The performance gain of scheduling jobs on one frame and completing them on the next depends on a lot of factors. The basic thing to understand is that the jobs (the code that calculates the deformers) get scheduled but must also get completed. If the jobs get completed before the calculations are done, the main thread has to wait for them to complete which causes a lower framerate. The best-case scenario is when the jobs finish their work before they get forced to complete. When this happens, the frame rate of the main thread is mostly unaffected by all the calculations that were done on the worker threads. The issue with doing single frame updates is that the jobs are scheduled in In the images below, the purple line shows the parts of the frame the jobs are able to run. Single Frame UpdateIn pursuit of giving the jobs as much time to run as possible without affecting the main thread, Deform schedules work and doesn't complete it until the next frame. This introduces a single frame of latency, but also introduces the opportunity for much better performance. Double Frame UpdateAs you can see, the "frame buffer" allows the jobs to run during the physics, input, rendering and decommissioning portions of the frame in addition to the game logic. So it doesn't really take two frames worth of time to update, it just starts the jobs halfway through frame n and doesn't complete until halfway through frame n+1. Using the unity execution order graph in conjunction with knowledge of which parts of your game take up what portion of a frame you can judge the performance penalty you'll be paying for single frame updates. The best thing you can do is to profile your application (in a build). See how long it takes for Here's the profiler in a simple example scene I made (using the default "frame buffered manager"). You can see For the next test I tweaked the DeformableManager.cs script from the development branch to schedule in So even in a simple test scene the different was more than half a millisecond. However, that's not an entirely fair test as the only thing running in the scene was a deformable. If I had other game code running, it would have given more time for the jobs to complete and I recommend trying single frame updates and seeing if the impact is significant. Here's the modified version of the DeformableManager I used in the test for single frame updates. It has had minimal testing, but worked for me. tldr: The performance impact depends on a lot of things. Profile it to see if the impact actually matters in your game. Don't feel the need to use the deformable manager for everything. You're free to choose certain deformables you want to update manually. Here's an example script I wrote that should handle unregistering an assigned deformable from the manager and updating it manually in a single frame. It's quite easy to understand, so if necessary you should be able to repurpose it or extrapolate it into a custom manager that suits your needs directly. using UnityEngine;
using Deform;
public class DeformableUpdater : MonoBehaviour
{
public Deformable deformable;
private void OnEnable ()
{
if (deformable == null)
return;
deformable.Manager = null; // If you use the newest commit on the develop branch, setting UpdateMode to Custom automatically sets Manager to null
deformable.UpdateMode = UpdateMode.Custom;
}
private void OnDisable()
{
if (deformable == null)
return;
deformable.Complete();
}
private void Update ()
{
if (deformable == null)
return;
deformable.PreSchedule();
deformable.Schedule ();
}
private void LateUpdate ()
{
if (deformable == null)
return;
deformable.Complete ();
deformable.ApplyData ();
}
} |
Hi again @keenanwoodall ! Sorry for the late reply, other parts of the projects were more pressing ^^ Oh wow, I wasn't hoping for such a detailed answer! Thanks a lot! It's awesome to have such great support :) I'm trying to get my company to send a donation ;) |
This is a question, not an issue (I think).
Using Deformable in a prefab. When the PF is instantiated, the original mesh is briefly rendered (single frame) before the deform is applied. For example, the CylindrifyDeformer. Is there a method of forcing the initialization the Deformable before the first rendering? For example, triggered by Awake or whatever?
Thanks
Mike
The text was updated successfully, but these errors were encountered: