【问题标题】:Display repaint an image on window at 60 hz以 60 赫兹显示在窗口上重绘图像
【发布时间】:2019-11-21 08:08:30
【问题描述】:

如果您打开 Skype 并点击“共享屏幕”,它会显示将要流式传输的视频预览。

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

获取屏幕:

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);
}

问题是,我读过有关计时器队列和标准计时器的计时“毫秒 % 70”,但听说它们在快速时不可靠, 重绘是否也是在没有库的情况下逐帧渲染“视频”的最佳方式?

【问题讨论】:

  • 使用 DirectX 或 OpenGL Windows API 不是这项工作的最佳工具。
  • 如果你正在渲染视频有samples for this

标签: c++ winapi win32gui


【解决方案1】:

这里有几个不同的问题:

  1. GDI 可能不够快,无法达到每秒 60 或 70 帧的速度。 cmets 中建议的其他技术(如@Richard Critten)旨在与硬件密切合作执行此类操作。诚然,这些 API 可能更难学习和使用。

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

话虽如此,可以将一个程序组合在一起,以使用 GDI 以较低的帧速率复制视频帧。在“Windows 视频”时代,我已经这样做了。我设法以接近 30 fps 的速度从网络摄像头视频预览窗口捕捉 640x480 帧,偶尔会丢帧。

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

一个典型的基于事件的 Windows 程序有一个这样的消息循环:

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

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

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

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

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 在运行时使用的单位。

【讨论】:

  • 作为注释添加此内容:GDI 长期以来一直进行优化,以不快速绘制,并且设计为尽可能保守。 DirectX 是执行此操作的正确方法。
  • 详细说明我认为@Mgetz 想说的是:GDI 曾经是硬件的低级接口,并且曾经被优化为非常快。在现代版本的 Windows 中,GDI 构建在其他图形层之上,这些图形层已经取代了其“接近硬件”的角色。许多 GDI 的功能现在可以在 CPU 上呈现。也就是说,矩形 blit 可能是优化的代码路径。 GPU 和 CPU 内存之间的复制通常是一个瓶颈。如果您必须将帧放入系统内存,那么无论您依赖哪个图形层,这都会成为您的瓶颈。
  • 不完全。更多的是,GDI 已被调整为在光栅化以节省电池等方面非常保守。它真的根本不想鼓励这种事情。也就是说,GDI 可能会在 CPU 上实现,具体取决于您使用的 Windows 版本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-01
  • 2020-09-06
  • 1970-01-01
相关资源
最近更新 更多