I have the following code, and wanted to get other set of eyes to make sure I have written the right code to calculate the frame rate of a scene. Could you please chime in?
This is written for the iPad using SDK 3.2.
thanks!
- (void)drawView:(id)sender
{
mach_timebase_info_data_t timer;
mach_timebase_info(&timer);
uint64_t t1 = mach_absolute_time();
[renderer render];
uint64_t delta = mach_absolute_time() - t1;
delta *= timer.numer;
delta /= timer.denom;
NSLog(#"%lld ms: %.2f FPS", delta, 1000000000.0f/delta);
}
In case you want to measure the time spent rendering OpenGL, this won't work. OpenGL operations are processed in parallel and will not affect timing on the CPU. You can profile the time it takes to issue the OpenGL calls, but you won't be able to see how long it took them to finish.
This is unfortunate, but it makes sense. This is probably the reason why everyone's just eying their framerate: if the GPU can't finish processing in time, your CPU gets blocked and your timer (most likely CADisplayLink) will not fire "in time".
You may want to look into (expensive) profiling tools like gDEBugger, but I'm not sure they work on iOS.
I use CFAbsoluteTime to compute the frame duration in an openGL app. I stopped using mach_time because the results were not reliable.
- (void)update {
// Compute Frame Duration
static CFAbsoluteTime sPreviousTime = 0;
const CFAbsoluteTime newTime = CFAbsoluteTimeGetCurrent();
const CFAbsoluteTime deltaTime = newTime - sPreviousTime;
sPreviousTime = newTime;
float frameDuration = deltaTime;
// keep frameDuration in [0.01 ; 0.5] seconds
if (frameDuration > 0.5f) {
frameDuration = 0.5f;
} else if (frameDuration < 0.01f) {
frameDuration = 0.01f;
}
[self tick:frameDuration]; // use frameDuration to do something every frame
}
Short answer : yes what you are doing is correct.
Longer answer : to get the time in seconds for a delta between two mach_absolute_time calls, you need to do the following:
// I do this once at launch.
mach_timebase_info_data_t timer;
mach_timebase_info( &timer );
// Start time.
uint64_t t1 = mach_absolute_time( );
// Do activity.
// End time.
uint64_t t2 = mach_absolute_time( );
// Calculate delta.
uint64_t delta = t2 - t1;
// Use denom/numer from timer.
delta *= timer.numer;
delta /= timer.denom;
// Convert nanoseconds to seconds.
float secondsElapsed = ( float )( delta / 1000000000.0 );
Of course, if you want a FPS, from you need the inverse of the seconds:
1.0f / secondsElapsed;
In your case, instead of doing:
float secondsElapsed = ( float )( delta / 1000000000.0 );
You are doing:
float inverseSecondsElapsed = ( float )( 1000000000.0 / delta );
So you do indeed get the FPS as intended, so all should work as intended.
Related
When I start up my game it stays around 95-101 rapidly changing, in between all of those numbers.. but when I open up the stats bar I'm getting upper 200's low 300's
so wondering why that is still new to c# so be easy on me lol. heres the code
thanks in advance as always ^_^.
float deltaTime = 0.0f;
void Update()
{
deltaTime += (Time.deltaTime - deltaTime) * 0.1f;
}
void OnGUI()
{
int w = Screen.width, h = Screen.height;
GUIStyle style = new GUIStyle ();
Rect rect = new Rect (0, 0, w, h * 2 / 100);
style.alignment = TextAnchor.UpperRight;
style.fontSize = h * 2 / 100;
style.normal.textColor = new Color (255.0f, 255.0f, 255.0f, 1.0f);
float msec = deltaTime * 1000.0f;
float fps = 1f / deltaTime;
string text = string.Format ("({1:0.} fps)", msec, fps);
GUI.Label (rect, text, style);
}
}
In order to display a meaningful FPS rate you need to measure how many frames were rendered over a constant period of time, for example one second. Then only after that period do you display the calculated value on screen. This will provide for an average frames per second as opposed to an instantaneous frames per second, the latter of which is not particularly useful in most cases as it leads to widely fluctuating values.
Code
First define some fields:
DateTime _lastTime; // marks the beginning the measurement began
int _framesRendered; // an increasing count
int _fps; // the FPS calculated from the last measurement
Then in your render method you increment the _framesRendered. You also check to see if one second has elapsed since the start of the period:
void Update()
{
_framesRendered++;
if ((DateTime.Now - _lastTime).TotalSeconds >= 1)
{
// one second has elapsed
_fps = _framesRendered;
_framesRendered = 0;
_lastTime = DateTime.Now;
}
// draw FPS on screen here using current value of _fps
}
Cross-technology
It should be pointed out that the above code makes no particular use of Unity whilst still being reasonably accurate and is compatible with many frameworks and APIs such as DirectX; OpenGL; XNA; WPF or even WinForms.
When I start up my game it stays around 95-101 rapidly changing, in between all of those numbers.. but when I open up the stats bar I'm getting upper 200's low 300's
The ASUS VG248QE is 1ms and the max it can do is 144Hz so it is unlikely you are getting "upper 200's low 300's". FPS is meaningless when VSYNC is turned off on a non-GSYNC monitor. Is your VSYNC turned on?
In Unity, FPS is equivalent to number of Updates that occur in 1 second. This is because Update() is called every Time.deltaTime seconds.
InvokeRepeating method
You can also use InvokeRepeating to implement your own FPS counter while using only integers, like this:
private int FrameCounter = 0;
private int Fps = 0;
void Start()
{
InvokeRepeating("CountFps", 0f, 1f);
}
void Update()
{
FrameCounter++;
}
private void CountFps()
{
Fps = FrameCounter;
FrameCounter = 0;
}
Then just display the Fps variable in the OnGUI() method. Using this method, your Fps value will get updated every second; if you want more frequent updates, change the last argument of InvokeRepeating call and then adjust the Fps calculation accordingly.
Note, however, that InvokeRepeating takes Time.timeScale into account, so e.g. if you pause the game with Time.timeScale = 0f; the counter will stop updating until you unpause the game.
FixedUpdate method
Another approach is to count the FPS in FixedUpdate() method instead of OnGUI() or Update(). This gets called every Time.fixedDeltaTime seconds which is always the same, no matter what. The value of Time.fixedDeltaTime can be set globally for the project via menu Edit->Project Settings->Time, item Fixed Timestep.
In this case, you would count frames the same way (in Update), but update your FPS counter in FixedUpdate - which is basically the same as calling you own method with InvokeRepeating("CountFps", 0f, 0.02f) (0.02f being a typical Time.fixedDeltaTime value, but this depends on your project settings as per above).
Conclusion
Most of the time, you won't need to update the displayed FPS that often, so I personally like to use the InvokeRepeating method and 1 second intervals.
OnGUI function is called at las twice per frame (sometimes more). You are calculating your "FPS" inside OnGUI so it will almost never be accurate.
Defining :
deltaTime += (Time.deltaTime - deltaTime) * 0.05f;
will bring your FPS values closest to real but it will not be accurate if you calc it on OnGUI method.
I guess (not sure) that you should use FixedUpdate() instead of OnGUI() to calc your FPS. (also you don't need to change your deltaTime to multiply by 0.05f if you use FixedUpdate)
I want to lerp the start color of two particle systems from one color to another in Unity if the player enters a triggerzone. I tried to do it with Color.Lerp but the result is that it lerps "laggy", meaning it has only 3 colors inbetween. My code:
public IEnumerator animateTriggerEnter(float duration = 0.1f)
{
float elapsedTime = 0f;
float lerp = 0f;
while (lerp <= 1f)
{
elapsedTime += Time.deltaTime;
lerp = elapsedTime / (float) duration;
topParticle.startColor = Color.Lerp(standardColor, triggerColor, lerp);
botParticle.startColor = Color.Lerp(standardColor, triggerColor, lerp);
yield return null;
}
}
For the value lerp, I always get the same 6 values, but should it not be more? It also remains laggy with a higher duration.
First of all I'd try isolating
Color.Lerp(standardColor, triggerColor, lerp);
and testing it's speed Stopwatch?
If I am right lerp is noting more than
result=startValue + (endValue - startValue) * lerpValue;
In that case + 1 constructor execution.
According to MSDN
math itself will should take less than
lerp = elapsedTime / (float) duration;
Are you sure it's lerp problem?
Is there a build-in way how to get a time by value from Animation curve in Unity3d? (The opposite way of Evaluate)
I need to achieve this (instead of getting value from time):
float time = AnimationCurve.Evaluate(float value);
Generically speaking getting X value from Y value.
I know this is 3 years old, but I found via a Google search, and in case someone else lands here:
I simply create an inverse curve, which allows me to look up by time.
public AnimationCurve speedCurve;
private AnimationCurve inverseSpeedCurve;
private void Start()
{
//create inverse speedcurve
inverseSpeedCurve = new AnimationCurve();
for (int i = 0; i < speedCurve.length; i++)
{
Keyframe inverseKey = new Keyframe(speedCurve.keys[i].value, speedCurve.keys[i].time);
inverseSpeedCurve.AddKey(inverseKey);
}
}
Just a basic implementation maybe it will give an idea for you. Method loops through all time and if your value is near that value at that time it will yield. It's a coroutine but you can change it to use inside Update maybe?
public AnimationCurve curve;
public float valToTime = .5f;
public float treshold = .005f;
public float yourTime;
IEnumerator valueToTime(float determineTime)
{
float timeCounter = 0;
Keyframe[] k = curve.keys;
float endTime = k[k.Length-1].time;
Debug.Log("end "+endTime);
while(timeCounter < endTime)
{
float val = curve.Evaluate(timeCounter);
Debug.Log("val "+ val + " time "+timeCounter);
// have to find a better solution for treshold sometimes it misses(use Update?)!
if(Mathf.Abs(val - determineTime) < treshold)
{
//Your time would be this
yourTime = timeCounter;
yield break;
}
else
{
//If it's -1 than a problem occured, try changing treshold
yourTime = -1f;
}
timeCounter += Time.deltaTime;
yield return null;
}
}
Putting together the best elements of most of the solutions posted, I've come up with an approach that produces pretty high accuracy. It involves doing the work upfront and so, is also quite efficient.
Note: If the original curve possesses any maximum/minimum point (points on the curve with a gradient of zero) this method will still attempt to invert it but can only do so by introducing several discontinuities to the inverted curve. It is not ideal for such cases.
Evaluate the original curve at several "sample-points" using a "sample-delta" constant.
For each "value" evaluated, compute the tangent at that point as the "sample-delta" / "value-delta".
Create keyframes that use the "value" as the "time" and the "sample-point" as the "value", and set the "inTangent" and "outTangent" to the tangent obtained in Step 3.
Add the keyframe generated at every "sample-point" to a new AnimationCurve().
The new AnimationCurve() is therefore an inverted version of the original.
Smooth the tangents of the new AnimationCurve() (the inverted version) to remove discontinuities caused by sudden and rapid tangent changes. NB: Smoothing the tangents may make the inverted curve lose it's general definition if the original curve had at least one maximum/minimum point.
Image of Normal Curve vs Inverted Curve:
invertedCurve = new AnimationCurve();
float totalTime = normalCurve.keys[normalCurve.length - 1].time;
float sampleX = 0; //The "sample-point"
float deltaX = 0.01f; //The "sample-delta"
float lastY = normalCurve.Evaluate(sampleX);
while (sampleX < totalTime)
{
float y = normalCurve.Evaluate(sampleX); //The "value"
float deltaY = y - lastY; //The "value-delta"
float tangent = deltaX / deltaY;
Keyframe invertedKey = new Keyframe(y, sampleX, tangent, tangent);
invertedCurve.AddKey(invertedKey);
sampleX += deltaX;
lastY = y;
}
for(int i = 0; i < invertedCurve.length; i++)
{
invertedCurve.SmoothTangents(i, 0.1f);
}
I needed this very thing just now, so I came up with this. I found it quite accurate and fast (an accuracy value of 10 was enough, and even lower may have done). But it will only work on curves that have ONE definite time for each value (i.e. nothing like waves with multiple times having the same value).
Similar to the other answer, it iterates through possible times - but rather than in a linear fashion the step value starts as the entire time range and halves each time.
Hope it's useful for you.
// NB. Will only work for curves with one definite time for each value
public float GetCurveTimeForValue( AnimationCurve curveToCheck, float value, int accuracy ) {
float startTime = curveToCheck.keys [0].time;
float endTime = curveToCheck.keys [curveToCheck.length - 1].time;
float nearestTime = startTime;
float step = endTime - startTime;
for (int i = 0; i < accuracy; i++) {
float valueAtNearestTime = curveToCheck.Evaluate (nearestTime);
float distanceToValueAtNearestTime = Mathf.Abs (value - valueAtNearestTime);
float timeToCompare = nearestTime + step;
float valueAtTimeToCompare = curveToCheck.Evaluate (timeToCompare);
float distanceToValueAtTimeToCompare = Mathf.Abs (value - valueAtTimeToCompare);
if (distanceToValueAtTimeToCompare < distanceToValueAtNearestTime) {
nearestTime = timeToCompare;
valueAtNearestTime = valueAtTimeToCompare;
}
step = Mathf.Abs(step * 0.5f) * Mathf.Sign(value-valueAtNearestTime);
}
return nearestTime;
}
just stumbled upon this problem myself and didn't like the solutions mentioned here, so i wanted to share my own. It's rather an adaption to the answer which inverts the keyframes.
I improved it by also inverting the tangents and the weight of the points.
I'm sure there is an easier way, but i found this working nicely for reversing the animationcurve.
Edit: Forgot to mention, for me it only worked when the tangents are set to weighted, i don't know what weight calculation unity does when you set it to auto or similar, so weighted was predicatable and easy to inverse.
inverseCurve = new AnimationCurve();
for (int i = 0; i < initialCurve.length; i++)
{
float inWeight = (initialCurve.keys[i].inTangent * initialCurve.keys[i].inWeight) / 1;
float outWeight = (initialCurve.keys[i].outTangent * initialCurve.keys[i].outWeight) / 1;
Keyframe inverseKey = new Keyframe(initialCurve.keys[i].value, initialCurve.keys[i].time, 1/initialCurve.keys[i].inTangent, 1/initialCurve.keys[i].outTangent, inWeight, outWeight);
inverseCurve.AddKey(inverseKey);
}
Thought I'd share my own version, as suggested in other forums too I tried looping over Evaluate() instead of reversing the whole curve which I think is overkill and not always feasible.
This checks for a value approximation down to the indicated decimals, it also assumes that the curve has "normalized" time (if it wasn't the case this could be expanded by looking for the smallest and the biggest time keys.
/// <summary>
/// Inverse of Evaluate()
/// </summary>
/// <param name="curve">normalized AnimationCurve (time goes from 0 to 1)</param>
/// <param name="value">value to search</param>
/// <returns>time at which we have the closest value not exceeding it</returns>
public static float EvaluateTime(this AnimationCurve curve, float value, int decimals = 6) {
// Retrieve the closest decimal and then go down
float time = 0.1f;
float step = 0.1f;
float evaluate = curve.Evaluate(time);
while(decimals > 0) {
// Loop until we pass our value
while(evaluate < value) {
time += step;
evaluate = curve.Evaluate(time);
}
// Go one step back and increase precision of the step by one decimal
time -= step;
evaluate = curve.Evaluate(time);
step /= 10f;
decimals--;
}
return time;
}
I am successfully making a CMTime with following code:
endPoint = CMTimeMake([mp currentPlaybackTime], 1);
The current position, which originally was a float value, of the movie in my MPMoviePlayerController is given back as a CMTime in seconds, which is not bad.
But how I can get this position in e.g. in milli seconds?
I played with the 'timescale' and set it to 10 and 100, but it didn't have an effect to the result.
Thank you in advance!
I’m not really sure what you want. Do you understand the logic behind CMTime?
1.0s = 1/1s = CMTimeMake(1, 1)
0.1s = 1/10s = CMTimeMake(1, 10)
0.2s = 1/5s = CMTimeMake(1, 5)
0.2s = 2/10s = CMTimeMake(2, 10)
…
In other words, CMTimeMake(a, b) is the time value a/b. Thus when you have a floating-point time value:
double time1 = 0.2;
// in ms, (0.2*1000)/1000 == 200/1000 == 0.2
CMTime time2 = CMTimeMake(time1*1000, 1000);
Maybe this is what you want?
I've tried several ways of measuring the steps a user makes with an iPhone by reading the accelerometer, but none have been very accurate. The most accurate implementation I've used is the following:
float xx = acceleration.x;
float yy = acceleration.y;
float zz = acceleration.z;
float dot = (mOldAccX * xx) + (mOldAccY * yy) + (mOldAccZ * zz);
float a = ABS(sqrt(mOldAccX * mOldAccX + mOldAccY * mOldAccY + mOldAccZ * mOldAccZ));
float b = ABS(sqrt(xx * xx + yy * yy + zz * zz));
dot /= (a * b);
if (dot <= 0.994 && dot > 0.90) // bounce
{
if (!isChange)
{
isChange = YES;
mNumberOfSteps += 1;
} else {
isChange = NO;
}
}
mOldAccX = xx;
mOldAccY = yy;
mOldAccZ = zz;
}
However, this only catches 80% of the user's steps. How can I improve the accuracy of my pedometer?
Here is some more precise answer to detect each step. But yes in my case I am getting + or - 1 step with every 25 steps. So I hope this might be helpful to you. :)
if (dot <= 0.90) {
if (!isSleeping) {
isSleeping = YES;
[self performSelector:#selector(wakeUp) withObject:nil afterDelay:0.3];
numSteps += 1;
self.stepsCount.text = [NSString stringWithFormat:#"%d", numSteps];
}
}
- (void)wakeUp {
isSleeping = NO;
}
ok, I'm assuming this code is within the addAcceleration function...
-(void)addAcceleration:(UIAcceleration*)accel
So, you could increase your sampling rate to get a finer granularity of detection. So for example, if you are currently taking 30 samples per second, you could increase it to 40, 50, or 60 etc... Then decide if you need to count a number of samples that fall within your bounce and consider that a single step. It sounds like you are not counting some steps due to missing some of the bounces.
Also, what is the purpose of toggling isChange? Shouldn't you use a counter with a reset after x number of counts? If you are within your bounce...
if (dot <= 0.994 && dot > 0.90) // bounce
you would have to hit this sweet spot 2 times, but the way you have set this up, it may not be two consecutive samples in a row, it may be a first sample and a 5th sample, or a 2nd sample and an 11th sample. That is where you are loosing step counts.
Keep in mind that not everyone makes the same big steps. So the dot calculation should be adjusted according to someone's length, step size.
You should adjust the bounce threshold accordingly. Try to make the program learn about it's passenger.