在 window 上以 60 赫兹显示重新绘制图像

Display repaint an image on window at 60 hz

如果您打开 Skype 并单击 "share screen",它会向您显示将要流式传输的内容的视频预览。

到目前为止我有这个代码:

获取屏幕:

HBITMAP screenshot()
{
    // get the device context of the screen
    HDC hScreenDC = CreateDC("DISPLAY", NULL, NULL, NULL);
    // and a device context to put it in
    HDC hMemoryDC = CreateCompatibleDC(hScreenDC);

    int width = GetDeviceCaps(hScreenDC, HORZRES);
    int height = GetDeviceCaps(hScreenDC, VERTRES);

    // maybe worth checking these are positive values
    HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);

    // get a new bitmap
    HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);

    BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
    hBitmap = (HBITMAP)SelectObject(hMemoryDC, hOldBitmap);

    return hBitmap;

在表单上呈现:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    case WM_CREATE:
        //hBitmap = (HBITMAP)LoadImage(NULL, LPCSTR("c:/users/they/documents/file.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
    case WM_PAINT:
        hBitmap = screenshot();
        PAINTSTRUCT ps;
        HDC hdc;
        BITMAP bitmap;
        HDC hdcMem;
        HGDIOBJ oldBitmap;

        hdc = BeginPaint(hwnd, &ps);
        hdcMem = CreateCompatibleDC(hdc);
        oldBitmap = SelectObject(hdcMem, hBitmap);

        GetObject(hBitmap, sizeof(bitmap), &bitmap);
        BitBlt(hdc, 200, 50, bitmap.bmWidth,bitmap.bmHeight,
            hdcMem, 0, 0, SRCCOPY);
        SelectObject(hdcMem, oldBitmap);
        DeleteDC(hdcMem);
        EndPaint(hwnd, &ps);
        if(millis % 70) RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}

问题是计时 "millis % 70" 我读过有关计时器队列和标准计时器的内容,但听说它们在高速时不可靠, 重绘也是在没有库的情况下逐帧渲染 "video" 的最佳方式吗?

这里有几个不同的问题:

  1. GDI 可能不会快到每秒 60 或 70 帧。评论中还建议了其他技术(如@Richard Critten),旨在与硬件密切合作来执行这些类型的操作。不可否认,那些 API 可能更难学习和使用。

  2. 您是对的,这些类型的计时器不会可靠地触发您的 per-frame 代码。人们使用 hack 来提高这些计时器的分辨率,但这样做有很多缺点,特别是如果你不是很小心的话。

话虽这么说,但还是可以编写一个程序来使用 GDI 以较低的帧速率复制视频帧。我在 "Video for Windows" 天之前就做过这个。我设法以接近 30 fps 的速度从网络摄像头视频预览中获取 640x480 帧 windows,偶尔会掉帧。

为此,您可以使用 "game loop" 而不是依赖计时器事件。游戏循环是一个紧密的循环,它会监视时钟直到处理下一帧。这会消耗大量 CPU 并耗尽笔记本电脑的电池,但这实际上是大多数 Windows 视频游戏在您玩游戏时的方式。 (好游戏会在游戏暂停时停止"spinning"。)

一个典型的event-based Windows程序有一​​个这样的消息循环:

while (GetMessage(&msg, NULL, 0, 0) > 0) {
  DispatchMessage(&msg);
}

(实际代码可能稍微复杂一些,但这些是我们关心的内容。)

GetMessage 调用会一直等待,直到有消息供您的程序响应。

游戏循环不会等待。它检查是否有消息准备好而无需等待。它使用 PeekMessage 来执行此操作。它做的另一件事是跟踪时间。如果无事可做,它就会立即循环。在 semi-pseudocode 中,它看起来像这样:

SomeType next_frame_time = now;
for (;;) {
  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
    if (msg.message == WM_QUIT) break;
    DispatchMessage(msg);
  }
  if (current time >= next_frame_time) {
    HandleNextFrame();
    next_frame_time += frame interval;
  }
}

请注意,除非 WM_QUIT 消息到达,否则循环将永远运行。它会尽可能快地运行。不是在响应 WM_TIMER 和 WM_PAINT 时做你的工作,而是在调用 HandleNextFrame 时做它们。

剩下的技巧是使用高分辨率时钟。您可以为此使用 Windows API QueryPeformanceCounter。请注意,您必须在运行时使用 QueryPerformanceFrequency 确定 QueryPerformanceCounter 使用的单位。