C# 监视Direct X应用程序的FPS
我希望创建一个外部应用程序来监视DirectX应用程序的“FPS”(比如没有录制的FRAP)。我读过好几本书,但我希望得到社区的反馈(和经验)C# 监视Direct X应用程序的FPS,c#,visual-studio,visual-studio-2012,directx,frame-rate,C#,Visual Studio,Visual Studio 2012,Directx,Frame Rate,我希望创建一个外部应用程序来监视DirectX应用程序的“FPS”(比如没有录制的FRAP)。我读过好几本书,但我希望得到社区的反馈(和经验) 我的问题:获取DirectX应用程序FPS的最佳方法是什么?Fraps在每个正在运行的应用程序中插入一个DLL,并钩住特定的DX调用以计算帧率和捕获视频,非常确定您必须执行类似的操作。经过一段时间的探索,我发现了一个Github项目,它做了一些基本的DX挂钩来进行捕获和覆盖,所以这可能是一个开始的好地方。虽然我没有亲自使用过,所以我不能完全保证它的质量
我的问题:获取DirectX应用程序FPS的最佳方法是什么?Fraps在每个正在运行的应用程序中插入一个DLL,并钩住特定的DX调用以计算帧率和捕获视频,非常确定您必须执行类似的操作。经过一段时间的探索,我发现了一个Github项目,它做了一些基本的DX挂钩来进行捕获和覆盖,所以这可能是一个开始的好地方。虽然我没有亲自使用过,所以我不能完全保证它的质量 Windows有一些与DirectX分析相关的提供程序。最有趣的是
Microsoft-Windows-D3D9
和Microsoft-Windows-DXGI
,它们允许跟踪帧显示事件。计算FPS的最简单方法是计算一个时间间隔内当前开始事件的数量,并将其除以时间间隔的长度
要在C#中使用ETW,请安装软件包
以下代码示例显示正在运行的进程的FPS:
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();
}
}
}
使用系统;
使用System.Collections.Generic;
使用系统文本;
使用系统诊断;
使用系统线程;
使用Microsoft.Diagnostics.Tracing.Session;
名称空间控制台EAPP1
{
//用于存储帧时间戳的helper类
公共类时间戳集合
{
常量int MAXNUM=1000;
公共字符串名称{get;set;}
列表时间戳=新列表(MAXNUM+1);
对象同步=新对象();
//为集合添加值
公共空添加(长时间戳)
{
锁定(同步)
{
时间戳。添加(时间戳);
如果(timestamps.Count>MAXNUM)timestamps.RemoveAt(0);
}
}
//获取具有时间间隔的时间戳数
公共整数查询计数(长从,长到)
{
int c=0;
锁定(同步)
{
foreach(时间戳中的变量ts)
{
如果(ts>=来自&&ts
{
//筛选出帧演示事件
if(((int)data.ID==EventID\u D3D9PresentStart&&data.ProviderGuid==D3D9\u provider)||
((int)data.ID==EventID\u DxgiPresentStart&&data.ProviderGuid==DXGI\u provider))
{
int pid=data.ProcessID;
长t;
锁定(同步)
{
t=watch.elapsedmillyses;
//如果进程尚未在字典中,请添加它
如果(!frames.ContainsKey(pid))
{
frames[pid]=新的TimestampCollection();
字符串名称=”;
var proc=Process.GetProcessById(pid);
如果(proc!=null)
{
使用(proc)
{
name=proc.ProcessName;
}
}
else name=pid.ToString();
帧[pid]。名称=名称;
}
//在集合中存储帧时间戳
帧[pid]。添加(t);
}
}
};
手表=新秒表();
watch.Start();
螺纹THTW=新螺纹(EtwThreadProc);
thETW.IsBackground=true;
thETW.Start();
Thread thOutput=新线程(OutputThreadProc);
thOutput.IsBackground=true;
thOutput.Start();
Console.ReadKey();
m_EtwSession.Dispose();
}
}
}
基于项目的源代码