Graphical tearing when setting precise FPS

I am using a script (copied below) to get the FPS of my program to run as close to 120fps as I can. Because targetFrameRate does not give me enough precision, I am instead using a coroutine which sleeps the thread.This works really well, however when the camera moves very quickly I get a lot of tearing, especially on objects which aren’t orthogonal to the camera.

I am just wondering, is there a way to prevent this tearing while still using this script, or will I essentially be forced to choose between a precision in FPS or no screen tearing?

To add a bit of extra info - I get the tearing at 60fps as well. In fact, as far as I can tell I get this issue regardless of the FPS target, it seems more related to the method I use in the script. If I increase vSyncCount then the tearing seems to be fixed in the editor, but in the build there is a lot of lag and the FPS drops. The program itself needs to run on three screens (so I have three cameras, each outputting to a different screen) so I am guessing with a higher vSyncCount and 3 separate screens it just becomes too computationally intensive.

I’m not very experienced with Unity or C#, if I have missed some information that’d help clarify my question please let me know.

Also anything in the code which seems like it is unused is probably for an OnGui function to display the FPS which I excluded from the code below.

Thanks

using UnityEngine;
using System.Collections;
using System.Threading;
using System.Diagnostics;
using System;

public class FPSControl : MonoBehaviour
{
    float deltaTime = 0.0f;

    public float Rate = 120.0f;
    float currentFrameTime;

    void Start()
    {
        QualitySettings.vSyncCount = 0;
        Application.targetFrameRate = 120;
        currentFrameTime = Time.realtimeSinceStartup;
        StartCoroutine("WaitForNextFrame");
    
    }

    IEnumerator WaitForNextFrame()
    {
        while (true)
        {
            yield return new WaitForEndOfFrame();
            currentFrameTime += 1.0f / Rate;
            var t = Time.realtimeSinceStartup;
            var sleepTime = currentFrameTime - t - 0.01f;
            if (sleepTime > 0)
                Thread.Sleep((int)(sleepTime * 1000));
            while (t < currentFrameTime)
                t = Time.realtimeSinceStartup;
        }
    }
}

edit: I should have mentioned, I don’t seem to get the tearing at all in the editor, it is only when I’m running the build it occurs.

You… really shouldn’t be mixing coroutines and threads. You shouldn’t be using threads like this full stop; this is not how Unity works.

Why exactly do you need “exactly” 120fps?

I don’t need exactly 120fps, but I do need the time between frame renders to be as consistent as possible, ideally to sub-millisecond precision. I can run the build far above 120fps as it is computationally very simple, but the standard methods of setting frame rates within Unity even at 60fps produces a lot of variability in the time between frames (e.g. the framerate will fluctuate between ~59-60fps regularly). This method is the only one I’ve found where the time between frames is consistently ~8.3ms (when at 120fps).

Do you have a link to something I can read about why mixing coroutines and threads are a bad idea? I’d be interested to learn more about it, but I haven’t been able to find much.

Okay but that still doesn’t tell me ‘why’?

All applications have frame rates that waver around the desired value. That’s not really going to change.

And unless you’re V-syncing, you’re going to get screen tearing.

Threads in general are bad in Unity unless you have a damn well good idea about what you’re doing. Most of the core of Unity happens on a single thread in the player loop. Coroutines are just iterator blocks that are run as part of this loop. Telling this thread, aka the sole, single thread that Unity runs on to sleep seems incredibly dangerous.

I don’t really have a link. I just know this from my time using Unity and hanging around on the forums.

I guess that was the core of my question, whether there was a way around this within the Unity editor/through scripting but I guess it is mostly a hardware limitation?

Honestly I thought the same thing but everything seemed to run perfectly fine.

Either way, you’ve given me a bit to think about/look into and play around with. Hopefully now I can come up with a better solution that just avoids this whole issue and fixes both problems at once.