簡體   English   中英

移動/調整窗口大小時閃爍

[英]Flicker when moving/resizing window

我開發了一個顯示jpeg圖像的應用程序。 它可以顯示4個圖像,每個圖像在屏幕的每個象限中。 它使用了4個窗口。 窗口沒有邊框(框架)和標題欄。 加載新圖像時,將為新圖像調整窗口大小,然后顯示圖像。

特別是當窗口變大時,通常會出現閃爍。 在我看到切口時,似乎在顯示新內容之前調整大小時會移動舊內容。

我咨詢了很多資源並使用了所有技巧:

  • 窗口只有樣式CS_DBLCLKS (沒有CS_HREDRAWCS_VREDRAW );

  • 背景畫筆為NULL;

  • WM_ERASEBKGND返回1;

  • WM_NCPAINT返回0;

  • WM_NCCALCSIZE告訴對齊未移動的一側(你能告訴它丟棄客戶區嗎?);

  • WM_WINDOWPOSCHANGING返回0;

  • SetWindowPos有標志SWP_NOCOPYBITS | SWP_DEFERERASE | SWP_NOREDRAW | SWP_NOSENDCHANGING SWP_NOCOPYBITS | SWP_DEFERERASE | SWP_NOREDRAW | SWP_NOSENDCHANGING SWP_NOCOPYBITS | SWP_DEFERERASE | SWP_NOREDRAW | SWP_NOSENDCHANGING

但是,在調整窗口大小時,會出現閃爍(或內容移動)。 我想要的是:

  • SetWindowPos到新的大小和位置;

  • InvalidateRect(hWnd,NULL,FALSE);

  • UpdateWindow(HWND);

沒有任何繪畫,背景刪除或內容移動,直到WM_PAINT

WM_PAINT

hDC= BeginPaint (hWnd, &ps);
hMemDC= CreateCompatibleDC (hDC);
hOldBitmap = SelectObject (hMemDC, hNewBitmap);
BitBlt (hDC,...,hMemDC,0,0,SRCCOPY);
SelectObject (hMemDC, hOldBitmap);
DeleteDC (hMemDC);
EndPaint (hWnd, &ps);

任何人都可以告訴我是否/我在哪里犯了錯誤導致窗口的舊內容被移動?

硬件等:HP Elitebook Core7 64位上的Windows 7,NVIDIA Quadro K1000m驅動程序9.18.13.3265(更新至341.44)。


更新 (17年7月)

我已經在另一台Windows計算機上看到了該程序的行為(Windows 8/10)。 它似乎不是NVIDIA的顯示驅動程序。

調整平鋪到屏幕中心(右下角= w / 2,h / 2)到左上角或左上角(0,0)的窗口大小時,行為最明顯。

我可能在WM_NCCALCSIZE消息的計算上遇到問題,告訴Windows不要做任何事情。 有人可以為我的目的舉例計算嗎? 另請參閱當用戶調整對話框大小時,如何強制窗口不重繪對話框中的任何內容?

你有一個令人印象深刻的反閃爍技巧列表:-)

我不知道這是否重要(因為它取決於你的工具窗口是如何創建的,特別是如果它們是普通父項的子窗口):嘗試在工具窗口的父窗口中設置窗口樣式WS_CLIPCHILDREN (如果有的話)是一個)。

如果未設置,則父窗口將擦除它的(整個)背景,然后將繪制消息轉發到子窗口,這將導致閃爍。 如果設置了WS_CLIPCHILDREN則父窗口不會對子窗口占用的客戶區域執行任何操作。 結果,子窗口的區域沒有被繪制兩次,並且沒有閃爍的機會。

這是一個理論而不是答案:

默認情況下,在現代Windows中,您的窗口只是視頻卡上的紋理,桌面窗口管理器將其映射到屏幕上的矩形。 您似乎已經做了一切必要的工作,以確保紋理一下子得到更新。

但是當您調整窗口大小時,桌面合成器可能會立即更新其幾何體,導致(仍未更改的)紋理顯示在屏幕上的新位置。 只有在你做油漆時才會更新紋理。

您可以通過暫時關閉桌面合成來測試此理論。 在Windows 7上,導航到“系統屬性”,選擇“高級”選項卡,在“性能選擇設置...”下,在“視覺效果”選項卡上,取消選中“啟用桌面組合”設置。 然后嘗試重現問題。 如果它消失了,那么這支持(但並不是絕對證明)我的理論。

(請記住重新啟用合成,因為這是大多數用戶大多數時間都在運行的方式。)

如果理論是正確的,那么似乎目標是在窗口調整大小后盡快進入繪畫。 如果時間窗口足夠小,則兩者都可能在監視器刷新周期內發生,並且不會出現閃爍。

具有諷刺意味的是,你在這里消除閃爍的努力可能對你不利,因為你有意抑制了通常由SetWindowPos產生的失效和重繪,直到你在后面的步驟中手動完成。

調試提示:嘗試在過程中的關鍵點引入延遲(例如, Sleep(1000); ),以便您可以看到調整大小和重繪是否實際在屏幕上呈現為兩個不同的步驟。

除了你的技巧列表。 在使用左/上邊緣調整窗口大小時,我的戴爾XPS筆記本上的閃爍也存在同樣的問題。 我已經嘗試過你提到的所有技巧。 據我所知,窗口邊框是在GPU中繪制的,窗口內容是在GDI子系統中准備的,並傳輸到視頻內存中進行窗口合成(Windows 8.1中引入的DWM)。 我試圖完全刪除GDI渲染(設置WS_EX_NOREDIRECTIONBITMAP樣式),這使得窗口沒有任何內容,然后使用DXGI子系統直接創建內容表面(使用CreateSwapChainForComposition,有關如何執行此操作的示例)。 但問題仍然存在。 渲染窗口框架和調整大小/顯示內容表面之間仍然存在滯后。

但是它可以解決您的問題,因為您沒有邊框,您不會注意到這種滯后。 但是你可以完全控制窗口重繪,它將在GPU端進行。

你確實有一套很好的技巧。

首先,我可以建議一些現有技巧的變種,特別是在XP / Vista / 7上有所幫助,其次我想提一下你在Win8 / 10上看到的持續閃爍可能來自何處以及減少這種變化的一些技巧。

首先,盡管在其他OP帖子中有相反的建議,你可能會發現如果你設置之前避免的CS_HREDRAW|CS_VREDRAW窗口樣式,它實際上消除了在窗口大小調整期間內部SetWindowPos內部完成的BitBlt (但是 - - 這是一個令人困惑的部分 - 在Windows 8/10上你仍會看到來自其他來源的閃爍...更多關於下面的內容。

如果你不想包含CS_HREDRAW|CS_VREDRAW ,你也可以攔截WM_WINDOWPOSCHANGING (首先將它傳遞給DefWindowProc )並設置WINDOWPOS.flags |= SWP_NOCOPYBITS ,這會禁止Windows在窗口內對SetWindowPos()內部調用中的BitBlt大小調整。

或者,您可以通過返回一組值來添加WM_NCCALCSIZE技巧,這些值將告訴Windows在其自身之上只有BitBlt 1像素,這樣即使BitBlt確實發生,它也不會做任何明顯的事情:

case WM_NCCALCSIZE:
{
    RECT ocr; // old client rect
    if (wParam)
    {
        NCCALCSIZE_PARAMS *np = (NCCALCSIZE_PARAMS *)lParam;
        // np->rgrc[0] is new window rect
        // np->rgrc[1] is old window rect
        // np->rgrc[2] is old client rect
        ocr = np->rgrc[2];
    }
    else
    {
        RECT *r = (RECT *)lParam;
        // *r is window rect
    }

    // first give Windoze a crack at it
    lRet = DefWindowProc(hWnd, uMsg, wParam, lParam);

    if (wParam)
    {
        NCCALCSIZE_PARAMS *np = (NCCALCSIZE_PARAMS *)lParam;

        // np->rgrc[0] is new client rect computed
        // np->rgrc[1] is going to be dst blit rect
        // np->rgrc[2] is going to be src blit rect
        // 
        ncr = np->rgrc[0];
        RECT &dst = np->rgrc[1];
        RECT &src = np->rgrc[2];

        // FYI DefWindowProc gives us new client rect that
        // - in y
        //   - shares bottom edge if user dragging top border
        //   - shares top edge if user dragging bottom border
        // - in x
        //   - shares left edge if user dragging right border
        //   - shares right edge if user dragging left border
        //
        src = ocr;
        dst = ncr; 

        // - so src and dst may have different size
        // - ms dox are totally unclear about what this means
        // https://docs.microsoft.com/en-us/windows/desktop/
        //         winmsg/wm-nccalcsize
        // https://docs.microsoft.com/en-us/windows/desktop/
        //         api/winuser/ns-winuser-tagnccalcsize_params
        // - they just say "src is clipped by dst"
        // - they don't say how src and dst align for blt
        // - resolve ambiguity

        // essentially disable BitBlt by making src/dst same
        // make it 1 px to avoid waste in case Windoze still does it

        dst.right  = dst.left + 1;
        dst.bottom = dst.top  + 1;

        src.right  = dst.left + 1;
        src.bottom = dst.top  + 1;

        lRet = WVR_VALIDRECTS;
    }
    else // wParam == 0: Windoze wants us to map a single rect w->c
    {
        RECT *r = (RECT *)lParam;
        // *r is client rect
    }

    return lRet;

所以這一切都非常好,但為什么Windows 8/10看起來如此糟糕?

Windows 8/10 Aero下的應用程序不直接繪制到屏幕上,而是繪制到屏幕外緩沖區,然后由邪惡的DWM.exe窗口管理器合成。 DWM實際上在現有的舊版XP / Vista / 7 BitBlt行為之上添加了另一層BitBlt類型的行為。

DWM blit行為更加瘋狂,因為它們不僅復制客戶區,而且它們實際上復制舊客戶區邊緣的像素以創建新客戶區。 不幸的是,讓DWM不做它的blit要比傳遞一些額外的標志要困難得多。

我沒有100%的解決方案,但我希望上面的信息會有所幫助,請參閱此問答,了解可用於減少DWM層blit發生在您應用中的機會的時間技巧:

如何在調整窗口大小時平滑丑陋的抖動/閃爍/跳躍,特別是拖動左/上邊框(Win 7-10; bg,bitblt和DWM)?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM