2012-06-10 54 views
2

我目前有一個應用程序,我正在編寫的C#(使用.NET),需要我儘快啓動一個計時器用戶在屏幕上看到一個圖像,直到他們用按鍵進行響應。有無論如何知道什麼時候屏幕已更新/刷新(OpenGL或DirectX也許?)

現在我意識到,實際上這是非常困難的給予顯示器輸入延遲和響應時間,鍵盤所花費的時間,以實際發送郵件,操作系統來處理它,等

但我想我最好把它降低到主要是一個常量錯誤(響應時間結果將用於比較一個用戶和下一個用戶,所以一個常量錯誤並不是真正的問題)。然而令人討厭的障礙是由顯示器刷新率引起的變量,因爲當我的onPaint消息被調用並完成時,它並不意味着圖像有實際上已被處理並從圖形緩衝區發送?

不幸的是時間限制和其他承諾會逼真地限制我繼續在c#for windows中執行此任務。

所以我想知道的是,如果在OpenGL或DirectX中處理所有繪圖,或者對我來說更好,如果可以在屏幕更新時使用OpenGL或DirectX創建事件?

以前給過我的另一個建議是關於V-Sync,如果我關掉它是一旦它被繪製就發送圖像?而不是以與顯示器刷新速率同步的設定速率發送圖像?

+1

你需要什麼精度爲你的計時器? –

+0

儘可能精確,如果我能在1ms內達到那將是理想的!我目前使用的秒錶,據我所知它只是一個精密計時器的包裝,我也運行在一個單獨的線程提升到高優先級的計時器(與應用程序運行在相同的優先級,但其他線程下降到正常,應用程序將以絕對最小的其他應用程序和進程在後臺運行) – aceaudio

+0

任何比10ms更糟的事情可能會成爲一個問題! – aceaudio

回答

3

爲了您必須呈現在一個單獨的線程您的圖形:

  • 使用垂直同步有圖像的有效顯示的精確計時。
  • 獲取用戶輸入的精確時間(因爲用戶界面是不一樣的線程比呈現環上

初始化Direct3D啓用垂直同步渲染期間:

// 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。設置()事件觸發器根據需要顯示圖像並啓動秒錶。

+0

我覺得現在得到它!非常感謝你的耐心和時間。我會放棄這個!我認爲它沒有義務使用DirectX來渲染圖像,我可以繼續使用現有的代碼來顯示圖像,但是現在我知道執行顯示圖像的代碼和顯示的圖像之間的時間,是恆定的。對? – aceaudio

+0

看起來你在爲我工作,非常感謝,非常感謝。 – aceaudio

+0

不客氣!我從SO用戶那裏得到了如此多的幫助,以至於我可以給我一隻手,我就這麼做:) –

1

首先啓用垂直同步您的應用程序空閒循環:

// 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啓用,device.Present功能塊,直到下一個幀同步,因此,如果您計算之間的時間renderTime和用戶輸入時間並刪除顯示設備延遲+ 16.67ms你應該得到你的用戶響應延遲。

+0

感謝您抽出寶貴時間。你不得不原諒我的無知,但我不確定我是否已經明白這是如何工作的。是否通過設置: 'presentParams.SwapEffect = SwapEffect.Discard; presentParams.BackBufferCount = 1; presentParams.PresentationInterval = PresentInterval.One;' 我們說圖像必須首先寫入後臺緩衝區,在下一個週期它將被寫入顯示屏? 然後通過使用peakmessage的while循環,我們檢查是否已經寫入顯示屏,通過測試que中不再有任何消息? – aceaudio

+0

Ok _maybe_我開始明白這個'Present()'將後臺緩衝區複製到前臺緩衝區的權利?而且我猜測在這個完成之前不會從問題中釋放出來?但如果是這樣的話,定時器不應該在while循環之後,而是在嘗試渲染圖像之前啓動,因爲它實際上並未在該點顯示。 – aceaudio

+0

我不相信'Present'具有明確的阻止行爲。在實踐中,你最終可能會同步到顯示器的刷新頻率,但那會是因爲驅動程序重複點擊某個排隊限制,只有在流水線結束時拉出一幀顯示纔會被釋放(每隔v-sync) – dave