• Create Account

[Questions] First RenderLoop with Fixed FPS and Animations ...

5 replies to this topic

#1justyourimage  Members   -  Reputation: 121

Like
0Likes
Like

Posted 29 July 2014 - 08:40 PM

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 ).

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 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();

//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; }
});

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:[/size]

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.

Attached Files

I don't sleep - I drink coffee!

#2tonemgub  Members   -  Reputation: 642

Like
1Likes
Like

Posted Yesterday, 05:35 AM

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...

#3justyourimage  Members   -  Reputation: 121

Like
0Likes
Like

Posted Yesterday, 06:29 AM

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),
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?

Edited by justyourimage, Yesterday, 06:39 AM.

I don't sleep - I drink coffee!

#4justyourimage  Members   -  Reputation: 121

Like
0Likes
Like

Posted Yesterday, 08:09 AM

*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.

Edited by justyourimage, Yesterday, 08:10 AM.

I don't sleep - I drink coffee!

#5tonemgub  Members   -  Reputation: 642

Like
0Likes
Like

Posted Today, 12:22 AM

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.

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"?

Edited by tonemgub, Today, 12:37 AM.

#6justyourimage  Members   -  Reputation: 121

Like
0Likes
Like

Posted Today, 05:45 AM

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.

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 or using a while loop to artificially skip frames. 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).

.... 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 time based "easily" 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 I just have to grasp the logic I guess.

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

Edited by justyourimage, Today, 05:48 AM.

I don't sleep - I drink coffee!

PARTNERS