C# 是否需要知道屏幕何时更新/刷新(可能是OpenGL或DirectX?)

C# 是否需要知道屏幕何时更新/刷新(可能是OpenGL或DirectX?),c#,opengl,directx,vsync,C#,Opengl,Directx,Vsync,我目前正在用c#(使用.NET)编写一个应用程序,它要求我在用户看到屏幕上的图像时立即启动计时器,直到他们按键响应 现在我意识到,考虑到显示器输入滞后和响应时间、键盘实际发送消息所需的时间、操作系统处理消息所需的时间等因素,这实际上是非常困难的 但是我正在尽我最大的努力将其减少到一个恒定的错误(响应时间结果将用于比较一个用户和下一个用户,所以恒定的错误不是真正的问题)。然而,令人恼火的障碍是监视器刷新率引起的变量,当调用并处理onPaint消息时,这并不意味着图像实际上已被处理并从图形缓冲区发送

我目前正在用c#(使用.NET)编写一个应用程序,它要求我在用户看到屏幕上的图像时立即启动计时器,直到他们按键响应

现在我意识到,考虑到显示器输入滞后和响应时间、键盘实际发送消息所需的时间、操作系统处理消息所需的时间等因素,这实际上是非常困难的

但是我正在尽我最大的努力将其减少到一个恒定的错误(响应时间结果将用于比较一个用户和下一个用户,所以恒定的错误不是真正的问题)。然而,令人恼火的障碍是监视器刷新率引起的变量,当调用并处理onPaint消息时,这并不意味着图像实际上已被处理并从图形缓冲区发送

不幸的是,时间限制和其他承诺实际上会限制我在c#for windows中继续执行此任务

所以我想知道的是,如果可以在屏幕更新时使用OpenGL或DirectX创建一个事件,是否可以在OpenGL或DirectX中处理所有图形,或者对我来说更好


之前给我的另一个建议是关于V-Sync,如果我关闭它,图像是否会在绘制后立即发送?与以与监视器刷新速率同步的设定速率发送图像不同,首先在应用程序空闲循环中启用VSync:

// DirectX example
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.BackBufferCount = 1;
presentParams.PresentationInterval = PresentInterval.One;

device = new Device(...

Application.Idle += new EventHandler(OnApplicationIdle);

// More on this here : http://blogs.msdn.com/tmiller/archive/2005/05/05/415008.aspx
internal void OnApplicationIdle(object sender, EventArgs e)
{
    Msg msg = new Msg();
    while (true)
    {
        if (PeekMessage(out msg, IntPtr.Zero, 0, 0, 0))
            break;
    }

    // Clearing render
    // ...

    if (displayImage)
    {
        // Render image
        // ...

        renderTime = DateTime.now();
    }
    device.Present();
}

启用vsync后,设备.Present功能块将一直运行到下一帧同步,因此,如果您计算renderTime和用户输入时间之间的时间,并删除显示设备延迟+16.67ms,则应获得用户响应延迟

首先在应用程序空闲循环上启用VSync:

// DirectX example
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.BackBufferCount = 1;
presentParams.PresentationInterval = PresentInterval.One;

device = new Device(...

Application.Idle += new EventHandler(OnApplicationIdle);

// More on this here : http://blogs.msdn.com/tmiller/archive/2005/05/05/415008.aspx
internal void OnApplicationIdle(object sender, EventArgs e)
{
    Msg msg = new Msg();
    while (true)
    {
        if (PeekMessage(out msg, IntPtr.Zero, 0, 0, 0))
            break;
    }

    // Clearing render
    // ...

    if (displayImage)
    {
        // Render image
        // ...

        renderTime = DateTime.now();
    }
    device.Present();
}

启用vsync后,设备.Present功能块将一直运行到下一帧同步,因此,如果您计算renderTime和用户输入时间之间的时间,并删除显示设备延迟+16.67ms,则应获得用户响应延迟

必须在单独的线程中渲染图形,以便:

  • 使用垂直同步可精确定时图像的有效显示
  • 获取用户输入的精确计时(因为用户界面与渲染循环不在同一线程上)
初始化Direct3D以在渲染期间启用VSync:

// DirectX example
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.BackBufferCount = 1;
presentParams.PresentationInterval = PresentInterval.One;

device = new Device(...
在单独的线程中执行渲染:

Thread renderThread = new Thread(RenderLoop);
renderThread.Start();

shouldDisplayImageEvent = new AutoResetEvent();
然后使用以下渲染循环:

void RenderLoop()
{
    while(applicationActive)
    {
          device.BeginScene();

        // Other rendering task

        if (shouldDisplayImageEvent.WaitOne(0))
        {
            // Render image
            // ...

            userResponseStopwatch = new Stopwatch();
            userResponseStopwatch.Start();
        }

        device.EndScene();

        device.Present();
    }
}
然后处理用户输入:

void OnUserInput(object sender, EventArgs e)
{
    if (userResponseStopwatch != null)
    {
        userResponseStopwatch.Stop();

        float userResponseDuration = userResponseStopwatch.ElapsedMillisecond - 1000 / device.DisplayMode.RefreshRate - displayDeviceDelayConstant;
        userResponseStopwatch = null;
    }
}

现在,您可以使用shouldDisplayImageEvent.Set()事件触发器根据需要显示图像并启动秒表。

您必须在单独的线程中渲染图形,以便:

  • 使用垂直同步可精确定时图像的有效显示
  • 获取用户输入的精确计时(因为用户界面与渲染循环不在同一线程上)
初始化Direct3D以在渲染期间启用VSync:

// DirectX example
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.BackBufferCount = 1;
presentParams.PresentationInterval = PresentInterval.One;

device = new Device(...
在单独的线程中执行渲染:

Thread renderThread = new Thread(RenderLoop);
renderThread.Start();

shouldDisplayImageEvent = new AutoResetEvent();
然后使用以下渲染循环:

void RenderLoop()
{
    while(applicationActive)
    {
          device.BeginScene();

        // Other rendering task

        if (shouldDisplayImageEvent.WaitOne(0))
        {
            // Render image
            // ...

            userResponseStopwatch = new Stopwatch();
            userResponseStopwatch.Start();
        }

        device.EndScene();

        device.Present();
    }
}
然后处理用户输入:

void OnUserInput(object sender, EventArgs e)
{
    if (userResponseStopwatch != null)
    {
        userResponseStopwatch.Stop();

        float userResponseDuration = userResponseStopwatch.ElapsedMillisecond - 1000 / device.DisplayMode.RefreshRate - displayDeviceDelayConstant;
        userResponseStopwatch = null;
    }
}

现在使用shouldDisplayImageEvent.Set()命令事件触发器,根据需要显示图像并启动秒表。

您的计时器需要什么精度?如果我能在1ms内达到理想的精度,请尽可能精确!我目前正在使用秒表,据我所知,它只是精确计时器的包装器,我也在单独的线程中运行计时器d到高优先级(当应用程序以相同优先级运行,但其他线程降至正常时,应用程序将以绝对最少的其他应用程序和进程在后台运行)任何比10毫秒更糟糕的事情都很可能成为一个问题!您正在测量人类的反应?无论如何,使用
System.Diagnostics.Stopwatch类,在显示图像时启动它(使用任何会导致图像显示的代码),然后在用户点击按钮时停止。您真正需要的精度是多少?60fps监视器上实际显示的误差,从您启动代码显示时算起,平均约7毫秒(是的,这是一个简化)。谢谢你的回答,克里斯。事实上,这是一个心理学实验的人类反应,我目前正在做所有这些。预期的反应时间不是很快,但不同变量之间的时间差是,这意味着一个0-17ms(@60hz)的变量当然可能会对结果产生影响。我意识到,平均而言,这不是一个巨大的数量,并且收集了足够的数据,这不应该是一个大问题,但如果有一种方法,我可以通过某种方式将每个屏幕的绘图与显示器的刷新率同步,以提高准确性,那么我认为这是明智的。你认为精度如何你需要你的计时器吗?尽可能精确,如果我能在1ms内实现这一点,那将是理想的!我目前正在使用秒表,据我所知,它只是精确计时器的包装,我还在一个单独的线程中运行计时器,并提升到高优先级(当应用程序以相同的优先级运行,但其他线程降至正常时,应用程序将以绝对最少的其他应用程序和进程在后台运行)任何比10毫秒更糟糕的事情都很可能成为一个问题!您正在测量人类的反应?无论如何,使用
System.Diagnostics.Stopwatch类,在显示图像时启动它(使用任何会导致图像显示的代码),然后在用户点击按钮时停止。您真正需要的精度是多少?60fps监视器上实际显示的误差,从您启动代码显示时算起,平均约7毫秒(是的,这是一个简化)谢谢你的回答,克里斯,这确实是人类对心理学实验的反应,我正在做所有这些