Monitoring the FPS of a Direct X Application
Asked Answered
A

3

8

I am looking to create an external application that monitors the 'FPS' of a DirectX application (like FRAPS without the recording). I have read several Microsoft articles on performance measuring tools - but I am looking to get the feedback (and experience) of the community.

My question: what is the best method for obtaining the FPS of a DirectX application?

Amagasaki answered 20/8, 2013 at 16:31 Comment(0)
K
6

Windows has some Event Tracing for Windows providers related to DirectX profiling. The most intresting ones are Microsoft-Windows-D3D9 and Microsoft-Windows-DXGI, which allow tracing of the frame presentation events. The simplest way to calculate FPS is to count the number of PresentStart events withing a time interval and divide that by the length of the interval.

To work with ETW in C#, install Microsoft.Diagnostics.Tracing.TraceEvent package.

The following code sample displays FPS of running processes:

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
using Microsoft.Diagnostics.Tracing.Session;

namespace ConsoleApp1
{
    //helper class to store frame timestamps
    public class TimestampCollection
    {
        const int MAXNUM = 1000;

        public string Name { get; set; }

        List<long> timestamps = new List<long>(MAXNUM + 1);
        object sync = new object();

        //add value to the collection
        public void Add(long timestamp)
        {
            lock (sync)
            {
                timestamps.Add(timestamp);
                if (timestamps.Count > MAXNUM) timestamps.RemoveAt(0);
            }
        }

        //get the number of timestamps withing interval
        public int QueryCount(long from, long to)
        {
            int c = 0;

            lock (sync)
            {
                foreach (var ts in timestamps)
                {
                    if (ts >= from && ts <= to) c++;
                }
            }
            return c;
        }
    }

    class Program
    {
        //event codes (https://github.com/GameTechDev/PresentMon/blob/40ee99f437bc1061a27a2fc16a8993ee8ce4ebb5/PresentData/PresentMonTraceConsumer.cpp)
        public const int EventID_D3D9PresentStart = 1;
        public const int EventID_DxgiPresentStart = 42;

        //ETW provider codes
        public static readonly Guid DXGI_provider = Guid.Parse("{CA11C036-0102-4A2D-A6AD-F03CFED5D3C9}");
        public static readonly Guid D3D9_provider = Guid.Parse("{783ACA0A-790E-4D7F-8451-AA850511C6B9}");

        static TraceEventSession m_EtwSession;
        static Dictionary<int, TimestampCollection> frames = new Dictionary<int, TimestampCollection>();       
        static Stopwatch watch = null;
        static object sync = new object();

        static void EtwThreadProc()
        {            
            //start tracing
            m_EtwSession.Source.Process();
        }

        static void OutputThreadProc()
        {
            //console output loop
            while (true)
            {    
                long t1, t2;
                long dt = 2000;
                Console.Clear();
                Console.WriteLine(DateTime.Now.ToString() + "." + DateTime.Now.Millisecond.ToString());
                Console.WriteLine();

                lock (sync)
                {
                    t2 = watch.ElapsedMilliseconds;
                    t1 = t2 - dt;

                    foreach (var x in frames.Values)
                    {
                        Console.Write(x.Name + ": ");   

                        //get the number of frames
                        int count = x.QueryCount(t1, t2);

                        //calculate FPS
                        Console.WriteLine("{0} FPS", (double)count / dt * 1000.0);
                    }
                }

                Console.WriteLine();
                Console.WriteLine("Press any key to stop tracing...");
                Thread.Sleep(1000);
            }
        }

        public static void Main(string[] argv)
        {
            //create ETW session and register providers
            m_EtwSession = new TraceEventSession("mysess");
            m_EtwSession.StopOnDispose = true;
            m_EtwSession.EnableProvider("Microsoft-Windows-D3D9");
            m_EtwSession.EnableProvider("Microsoft-Windows-DXGI");

            //handle event
            m_EtwSession.Source.AllEvents += data =>
            {
                //filter out frame presentation events
                if (((int)data.ID == EventID_D3D9PresentStart && data.ProviderGuid == D3D9_provider) ||
                ((int)data.ID == EventID_DxgiPresentStart && data.ProviderGuid == DXGI_provider))
                {
                    int pid = data.ProcessID;
                    long t;

                    lock (sync)
                    {
                        t = watch.ElapsedMilliseconds; 

                        //if process is not yet in Dictionary, add it
                        if (!frames.ContainsKey(pid))
                        {
                            frames[pid] = new TimestampCollection();

                            string name = "";
                            var proc = Process.GetProcessById(pid);
                            if (proc != null)
                            {
                                using (proc)
                                {
                                    name = proc.ProcessName;
                                }
                            }
                            else name = pid.ToString();

                            frames[pid].Name = name;
                        }

                        //store frame timestamp in collection
                        frames[pid].Add(t);
                    }
                }
            };

            watch = new Stopwatch();
            watch.Start();            

            Thread thETW = new Thread(EtwThreadProc);
            thETW.IsBackground = true;
            thETW.Start();

            Thread thOutput = new Thread(OutputThreadProc);
            thOutput.IsBackground = true;
            thOutput.Start();

            Console.ReadKey();
            m_EtwSession.Dispose();
        }
    }
}

Based on the source code of PresentMon project.

Kohinoor answered 11/2, 2019 at 7:43 Comment(0)
T
2

Fraps inserts a DLL into every running application and hooks specific DX calls to figure out the framerate and capture video, pretty sure that you'll have to do something similar. After a bit of poking around I found a Github project that does some basic DX hooking for doing captures and overlays, so that might be a good spot to start out with. Though I've not used it personally so I can't totally vouch for the quality.

http://spazzarama.com/2011/03/14/c-screen-capture-and-overlays-for-direct3d-9-10-and-11-using-api-hooks/

Tommy answered 12/9, 2013 at 15:22 Comment(0)
C
0

Building on https://mcmap.net/q/1339540/-monitoring-the-fps-of-a-direct-x-application:

I had more success not using the stopwatch as the event triggers seems to be asynchronous with the actual frames. I kept getting batches of 20-50 frames all at once, making the estimated FPS fluctuate between 50 and 250% of the actual value.

Instead i used TimeStampRelativeMSec

//handle event
m_EtwSession.Source.AllEvents += data =>
{
    //filter out frame presentation events
    if((int) data.ID == EventID_DxgiPresentStart && data.ProviderGuid == DXGI_provider)
    {
        int pid = data.ProcessID;
        long t;

        t = watch.ElapsedMilliseconds;

        //if process is not yet in Dictionary, add it
        if (!frames.ContainsKey(pid))
        {
            frames[pid] = new TimestampCollection();
            string name = "";
            var proc = Process.GetProcessById(pid);

            if (proc != null)
            {
                using (proc)
                {
                    name = proc.ProcessName;
                }
            }
            else name = pid.ToString();

            frames[pid].Name = name;
        }
        frames[pid].Add((long)data.TimeStampRelativeMSec);
    }
};

property from the TraceEvent class, and calculate FPS by rounding the average time between an arbitrary number of past entries:

public double GetFrameTime(int count)
{
    double returnValue = 0;

    int listCount = timestamps.Count;

    if(listCount > count)
    {
        for(int i = 1; i <= count; i++)
        {
            returnValue += timestamps[listCount - i] - timestamps[listCount - (i + 1)];
        }

        returnValue /= count;
    }

    return returnValue;
}

This method gave me far more accurate (Compared to, as available, in-game counters) of several different games i've tried.

Clinquant answered 7/8, 2022 at 7:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.