[Questions] First RenderLoop with Fixed Time-Step and Animations ...

Started by
8 comments, last by Injur Fejis 9 years, 8 months ago

Hi there,

I'm pretty new to DirectX, Direct2D and C#-Development in General (not to Development in itself) so excuse me if there are any oblivious flaws.

The attached tags should give an rough overview about the Environment I'm using (or at least I hope so smile.png).

My SharpDX-Application is using DirectX10 combined with Direct2D in a SwapChain. I'm mostly drawing 2D UI-Stuff in Direct2D (the Application isn't a Game in itself but a Helper enabling Steam In-Home-Streaming for the Windows Desktop) however I might implement a few DirectX effects later on if I find time or patience to do so. Well let 's say I've just finished the first Prototype for my Animation Class so everything is in a pretty early stage. Sorry for the long introduction I'll show you my code that is important in getting accurate answers concerning my questions next

This is my RenderLoop and the most important methods build around it:


		public virtual void Initialize() { }
		public virtual void LoadContent(){ }
		public virtual void UnloadContent() { }
		public virtual void RunOnceBeforeRender() { }
		public virtual void RunOnceAfterRender() { }
		public virtual void Update(int fps, int cpu, double frameTime) { }
		public virtual void Draw(int fps, int cpu, double frameTime) { }
		public virtual void BeginFrame() { 
			Application.FPS.Frame(); 
			Application.CPU.Frame();
			Application.Timer.Frame(); // <- RenderTime gets updated here
		}
		public virtual void EndFrame() {
			swapChain.Present(1, PresentFlags.None);
		}

		public virtual void Run()
		{
			Initialize();
			LoadContent();

			//Reminder - http://gafferongames.com/game-physics/fix-your-timestep/
		
			RenderLoop.Run(renderForm, () =>
				{
					if(bRunOnceBeforeRender == false) { RunOnceBeforeRender(); bRunOnceBeforeRender = true; } 

					BeginFrame(); // <- Update FrameTime

					if(!renderForm.isResizing && renderForm.hasSizeChanged) HandleResize();

					Update(Application.FPS.Value, Application.CPU.Value,  Application.Timer.FrameTime);
					Draw(Application.FPS.Value, Application.CPU.Value,  Application.Timer.FrameTime);

					EndFrame();

					if(bRunOnceAfterRender == false) { RunOnceAfterRender(); bRunOnceAfterRender = true; } 
				});

			UnloadContent();
			Dispose();
		}

		public virtual void Dispose() 
		{
			Utilities.Dispose(ref backBuffer);
			Utilities.Dispose(ref renderView);
			Utilities.Dispose(ref d2dRenderTarget);
			Utilities.Dispose(ref surface);
		}


I limit the FPS to 60 (VSYNC enabled) via


swapChain.Present(1, PresentFlags.None);


in EndFrame()

(double) RenderTime (= Time in MS the last Frame took to Render) gets updated at the start of each next frame here BeginFrame():


public virtual void BeginFrame() { 
	...
	...
	Application.Timer.Frame(); // <- RenderTime gets updated here
}


The Frame() method that stores the time since the last frame got drawn looks like this:


public void Frame()
{
	_StopWatch.Stop();

	FrameTime = (double)_StopWatch.ElapsedTicks/(Stopwatch.Frequency/1000);
	LastUpdated = Stopwatch.GetTimestamp();

	//if(FrameTime > 250) FrameTime = 250; // Set Maximum Frametime to 250ms

	_StopWatch.Restart();
}

As you can see I'm using StopWatch due to the QueryPerformanceCounter/QueryPerformanceFrequency implementation for a High Resolution Timer.

Here comes my first question:


FrameTime = (double)_StopWatch.ElapsedTicks/(Stopwatch.Frequency/1000);


Is the calculation / storage of the FrameTime ok like this?

Could it be optimized in terms of speed or reliability (both in terms of reliability and performance)?

What I'm planning to add is limiting the maximum FrameTime to 250ms just in case I get ridiculus amounts of Frameskips.

Now lets move onto the topic of Animating stuff with the above Environment (Target FPS of 60, Time since last frame stored as double).

Like I said in my Introduction above I've written my first Prototype of my Animation class called PropertyAnimation since it 's aimed at animating any Numeric Property of any Object.

It 's not yet finished as in the Draw()/Render()/Update() Methods are still missing but the rest is already there for which the most important part for me is the calculation of the single steps with the targeted FPS and aimed animation time. The single step itself is returned with the method GetStep().


public object GetStep() {
	double step;
	step = (_to-_from)/(_durationS*_FPS);
	return step;
}


using all of the above this is what I'm logging with my Application with a sample Animation Testcase with the following Code:


public class TestObject 
{
	public int width = 100;
	public int height = 100;
}

if(Log.IsDebugEnabled) {
	Log.Debug("Average FPS: " + FPS.AVG.ToString());
	Log.Debug("Min FPS: " + FPS.Min.ToString());
	Log.Debug("Max FPS: " + FPS.Max.ToString());
	Log.Debug("Last FPS Value: " + FPS.Value.ToString());
	Log.Debug("Last FrameTime is "+Timer.FrameTime.ToString()); 
 
	TestObject myTest = new TestObject();

	//animateMyTest.SetValue((int)400);

	string msg = "\n\n=== Test Animation Data ===\n";

	double toValue = 500;
	long timeInMS = 2000;
	int _FPS = 60;

	PropertyAnimation animateMyTest = new PropertyAnimation(myTest, "width", toValue, timeInMS, 60);

	double _frameTime = Timer.FrameTime;
	double animationStep = (double)animateMyTest.GetStep();
	double correctedStep = animationStep * ((_frameTime/1000)*_FPS);

	msg += "\n  Animate From       => " + animateMyTest.GetValue().ToString();
	msg += "\n  Animate To         => " + toValue.ToString();
	msg += "\n  Within MS          => " + timeInMS.ToString();
	msg += "\n  Target FPS         => " +_FPS.ToString();
	msg += "\n  Animation Step     => " + animationStep.ToString();
	msg += "\n  Step FPS-Corrected => " + correctedStep.ToString();

	msg += "\n\n===========================";
	msg = msg.Replace ("\n", System.Environment.NewLine);

	Log.Debug(msg);
}


... which is giving me the following Debug Output:


2014-07-30 02:56:42,753 [DEBUG] Average FPS: 55
2014-07-30 02:56:42,753 [DEBUG] Min FPS: 28
2014-07-30 02:56:42,753 [DEBUG] Max FPS: 60
2014-07-30 02:56:42,753 [DEBUG] Last FPS Value: 60
2014-07-30 02:56:42,753 [DEBUG] Last FrameTime is 16,766265060241
2014-07-30 02:56:42,753 [DEBUG] 

=== Test Animation Data ===

  Animate From       => 100
  Animate To         => 500
  Within MS          => 2000
  Target FPS         => 60
  Animation Step     => 3,33333333333333
  Step FPS-Corrected => 3,35325301204819

===========================


Here comes the second question:

To me, as a beginner in Frame-Dependent Developing with DirectX/D2D and similiar everything looks ok. However I'm wondering if I understood the principles of the FPS-Correction of the Animations with the Frame correctly (that 's called Interpolation right?). As in -

if my interpolation / correction calculation is correct, efficient and reliable enough to be called at each frame:


//GetStep() = (_to-_from)/(_durationS*_FPS);

double _frameTime = Timer.FrameTime;
double animationStep = (double)animateMyTest.GetStep();
double correctedStep = animationStep * ((_frameTime/1000)*_FPS);


Wow, ok I hope i didn't forget some important stuff, and my questions make sense for them to get answered. Like I said sorry if I'm grasping something wrong I just started C# 5 days ago and this is what I've come up with up to now. Cool that you took the time to read my thread - I'm welcoming any constructive feedback and of course absolutely anything that can provide towards solving my questions. Sorry if the post itself is rather long - I tried to be as specific as I could.

Cheers!

Simon

I've attached the PropertyAnimation class just in case any questions arise towards any specific functions.

I don't sleep - I drink coffee!

Advertisement

Just a small observation: this is not really a fixed-FPS implementation.


I limit the FPS to 60 (VSYNC enabled) via


swapChain.Present(1, PresentFlags.None);

Not really... You're limiting the FPS to the monitor's refresh rate, which may or may not be 60, and if your render loop cannot keep up to the monitor refresh rate, you will get a FPS equal to half the monitor's refresh rate, or worse, since you'll only be rendering every other frame... etc.

But if you don't rely on this "fixed FPS" for timing stuff, you're ok...

Good call. I've looked up IDXGISwapChain::Present on MSDN and it seems you're right. One thing I forgot to include is my SwapChain creation:


public virtual void CreateSwapChain()
{
	// SwapChain description
	var desc = new SwapChainDescription()
	{
		BufferCount = 1,
		ModeDescription = new ModeDescription(renderForm.ClientSize.Width, renderForm.ClientSize.Height, new Rational(60, 1), Format.R8G8B8A8_UNorm),
		IsWindowed = true,
		OutputHandle = renderForm.Handle,
		SampleDescription = new SampleDescription(1, 0),
		SwapEffect = SwapEffect.Discard,
		Usage = Usage.RenderTargetOutput,
		Flags = SwapChainFlags.None
	};

	// Create Device and SwapChain
	Device1.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.Debug | DeviceCreationFlags.BgraSupport, desc, FeatureLevel.Level_10_0, out device, out swapChain);
}

What I intended right now is using the time it took the last frame to render to Interpolate the animation itself which is made with 60FPS at mind (fixed value)

I'm doing the Interpolation (is this the correct word?) like this:


double animationStep = (double)animateMyTest.GetStep();
double correctedStep = animationStep * ((_frameTime/1000)*_FPS);

animationStep is a single step per frame in pixels at the optimum of 60 FPS. I take that value divide it by the frameTime (which is like I said the time the previous frame took to render in MS) and multiplicate it at the Animations intended FPS (60 which I'm working with). Since my targeted audience will high likely include gamers using 120hz monitor they should get 120FPS so in that case let me provide an example how it would work:

The user/gamer is using an 120hz Monitor so lets he has 120FPS (optimally) which would equal in an FrameTime of 8,3333~ ms so let 's say _frameTime would be 8,3333 in that case I divide that time by 1000 (to get it into seconds) and multiplicate it by the targeted FPS of 60 which would get me an modificator of 0.49998 meaning one step would be about half as fast as it would be running with 60 FPS.



animationStep = 100; //pixels per frame moved at 60 FPS
correctedStep = 100 * ((8.3333/1000)*60);
correctedStep = 100 * 0.49998;
correctedStep = 49.998; //pixels per frame moved at 120 FPS

... which sounds logical to me. Is everything correct here or did I make a misstake somewhere or is there a better way to implement this?

I don't sleep - I drink coffee!

*half as fast in terms of pixels per step of course and the same in terms of time. right?

Sorry I'm to scared to update my last post because somehow the forum messes up all the linebreaks at each time.

I don't sleep - I drink coffee!

According to MSDN and the Direct3D Debug Layer, you're not supposed to fill in the refresh rate manually in the DXGI_MODE_DESC structure - that value is returned by DXGI/EnumAdapters. If you set the refresh rate to non-zero values manually, and the whole DXGI_MODE_DESC structure doesn't match one of the modes returned by EnumAdapters, you will get a warning in debug mode. Setting the refresh rate to 0 (both values) is ok, though - in this case, you will get the default refresh rate for the resolution you chose. There is no way to force D3D to run at a specific refresh rate - it will always use the monitor's frequency... And if your application is not running full-screen, you should always set the refresh rate to 0 - in this case, the desktop resolution's refresh rate is always used.

That's why this is not called "fixed FPS" - it's actually called "VSYNC", and the animation timing you do around it is actually dynamic, not fixed, because you're actually using the time values of an external timer for it, not frame counts or FPS. And I might've, sort of mistook "fixed FPS" for "fixed-step", which are not the same thing. smile.png

It feels a bit weird that you're basing your animations on pixels per fixed-60Hz-frame and deriving your dynamic animation steps from that, you should just use pixels per second or millisecond, based on the time values that your external timer gives you, or was that just an explanation of why you're calling it "fixed-FPS"?

According to MSDN and the Direct3D Debug Layer, you're not supposed to fill in the refresh rate manually in the DXGI_MODE_DESC structure - that value is returned by DXGI/EnumAdapters. If you set the refresh rate to non-zero values manually, and the whole DXGI_MODE_DESC structure doesn't match one of the modes returned by EnumAdapters, you will get a warning in debug mode. Setting the refresh rate to 0 (both values) is ok, though - in this case, you will get the default refresh rate for the resolution you chose. There is no way to force D3D to run at a specific refresh rate - it will always use the monitor's frequency... And if your application is not running full-screen, you should always set the refresh rate to 0 - in this case, the desktop resolution's refresh rate is always used.

That's why this is not called "fixed FPS" - it's actually called "VSYNC", and the animation timing you do around it is actually dynamic, not fixed, because you're actually using the time values of an external timer for it, not frame counts or FPS. And I might've, sort of mistook "fixed FPS" for "fixed-step", which are not the same thing. smile.png

That 's right. I understood it will always run on the Monitors Refresh Rate. I'm not trying to force it otherwise. Callling it "Fixed-FPS" might have not been right in that case - I just didn't know the correct terms. I'm not looking into putting it into max performance as it should take as little resources as possible with it being a helper utility that 's also why I thought binding it to the Refresh Rate / Enabling VSYNC should be better then letting it run at ~1000 FPS+ freely. The former took around ~20%+ CPU (i5 @ 4GHZ) while the last one is just taking ~2-8% on average (at least that 's what the Taskmanager said). smile.png

.... you should just use pixels per second or millisecond, based on the time values that your external timer gives you ....

Now that 's interesting. I've read about how you make it (=Animations) time based "easily" in a Frame based RenderLoop but I don't get the logic behind it just found many different examples that 's why I've tried to come up with a logical way that I can understand (formula) rather then just using something. Can you give me an simple example (doesn't have to be complicated) and explanation how you would do it? Can be pseudocode as well or even a link to an simple example project (C#) I just have to grasp the logic I guess.

I just want to be sure to understand the basics properly before moving forward to fast and building onto practices I can't explain.

Thanks for looking into my stuff and have a nice day! smile.png

Btw.: The timer is stopped and tracked on the beginning on each frame so it 's measuring the time it took the last frame to render right now.

I don't sleep - I drink coffee!

I might be the only one, but so far I cannot understand what you're trying to do or what is your question.

So far I can only try to answer first question in original post.

The code you posted:


FrameTime = (double)_StopWatch.ElapsedTicks / (Stopwatch.Frequency / 1000);

Stopwatch.Frequency is number of ticks per second, therefore I fail to understand why you divide by 1000 (esp. why integer).

If you want time passed in seconds then you should be doing this:


FrameTime = (double)_StopWatch.ElapsedTicks / (double)Stopwatch.Frequency;

However there's one more thing I'd like to mention:


public void Frame()
{
	_StopWatch.Stop(); // time stops

	FrameTime = (double)_StopWatch.ElapsedTicks / (double)Stopwatch.Frequency;
	LastUpdated = Stopwatch.GetTimestamp();

	_StopWatch.Restart(); // time restarts
	
	// but did it count time that passed between Stop and Restart calls?
}

The code you posted:


FrameTime = (double)_StopWatch.ElapsedTicks / (Stopwatch.Frequency / 1000);

Stopwatch.Frequency is number of ticks per second, therefore I fail to understand why you divide by 1000 (esp. why integer).

If you want time passed in seconds then you should be doing this:


FrameTime = (double)_StopWatch.ElapsedTicks / (double)Stopwatch.Frequency;

Well you're true. That was just stupid of me I guess first converting it into MS and then back into seconds even when I'm using a double which should be accurate enough. Talk about needlessly converting stuff back and forth. Thanks for pointing that mistake out.

However there's one more thing I'd like to mention:


public void Frame()
{
	_StopWatch.Stop(); // time stops

	FrameTime = (double)_StopWatch.ElapsedTicks / (double)Stopwatch.Frequency;
	LastUpdated = Stopwatch.GetTimestamp();

	_StopWatch.Restart(); // time restarts
	
	// but did it count time that passed between Stop and Restart calls?
}

Thats also a good call. I don't think it 's possible as the time between Stop and Restart should be well within nano seconds as nothing more is done then 2 variables are set so I guess I'll have to live with that small inaccuracy. If I don't stop it beforehand FrameTime and LastUpdated might hold slightly differencens which I don't want.

I might be the only one, but so far I cannot understand what you're trying to do or what is your question.

Basically I want to make sure my RenderLoop runs properly, my Animations are smooth and running at the Time I've intended them to run independent of the FPS. And that I can grasp the logic behind it and not just use an existing mechanic / derivate without understanding it properly since I don't like to do stuff I don't understand. ph34r.png

Most of the examples I found (here as well) only show halfway trough and don't provide an basic example of Update() and Draw()

with an simple animation - just a call to them - so they're hard to understand and rebuild properly (at least for me).

If it might help I could upload the complete Project (Mono/Xamarin Studio) - I'm planning to make it Open-Source anyway.

Cheers!

I don't sleep - I drink coffee!


Basically I want to make sure my RenderLoop runs properly

Define properly. Most would say you need to use fixed time step to have proper game loop. There's a link in your code, and if that isn't sufficient I can also suggest this article.


running at the Time I've intended them to run independent of the FPS

You just measure time since last frame and move everything by that much. For example, your character moves 2 m/s, so you multiply that by time elapsed in seconds and you get distance character moved since last frame. This will happen in your Update() function.

However, if you are using fixed time step then elapsed time in Update() function will be constant, ex. 0.03(3) seconds if you want 30 UPS (updates per second). Then your Render() function interpolates between last position and current position (it's explained in the article I linked earlier).

Thank for very much for the link to L. Spiro 's explanation - and your additions to that as well!

I think it finally snapped to me how it works. It also has some fine nuances I would like to know so I'll be sure to study it properly before continuing further. Glenn Fiedler 's one is great in itself but a bit to specific aimed towards physics and to abstract with it 's example for me (variable names and methods in itself) unless you followed his whole tutorial series about physics which is something I'm planning to do at a later state after grasping the basics.

I would define properly as to understanding properly myself I know there 's no perfect solution out there.

Properly just means to me that I know as to what and how I'm doing something (to some extend).

Cheers!

I don't sleep - I drink coffee!

This topic is closed to new replies.

Advertisement