多个窗户绘画

时间:2019-04-23 20:08:34

标签: c winapi

如何避免窗口帧速率卡在瓶颈窗口?

  • 我有一个单独显示时以10 FPS运行的窗口。
  • 我还有一个单独显示时以60 FPS运行的窗口。

我的问题是当我同时显示两个窗口时,我的窗口获得10 FPS。

我期望快速窗口会比慢速窗口快,但是当同时绘制时,两个窗口的FPS速度会降低。

我原本希望慢速窗口获得5 FPS,快速窗口获得30 FPS。

尝试使用timeSetEvent()代替SetTimer(),并且仅在设置很小的时间时才绘制慢速窗口。

尝试使用timeSetEvent(TIME_ONESHOT)自定义绘制消息,这给了我与WM_TIMER相同的慢结果。

添加了关闭/打开按钮,可以轻松查看FPS在做什么,而无需更改代码。

据我了解,快速窗口正在等待慢速窗口完成其绘制。 非常非常感谢您的帮助...

此处是完整代码 main.c

#include <stdio.h>
#include <string.h>
#include <windows.h>

#define TIMER_WND0      0
#define TIMER_WND1      1

#define OFF_ON_WND0     0
#define OFF_ON_WND1     1

LRESULT CALLBACK procWndMain(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK procWnd0(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK procWnd1(HWND, UINT, WPARAM, LPARAM);

HINSTANCE hInst = NULL;
HWND hWndMain = NULL;
HWND hWnd0 = NULL;
HWND hWnd1 = NULL;

int offOnWnd0 = 1;
int offOnWnd1 = 1;

void uint2str(char *buf, unsigned int x) {
    sprintf(buf, "%u", x);
    }
double millitimeTick(void) {
    return GetTickCount()/1000.0;
    }
int randInt(int a, int b) {
    return rand()%(b-a) +a;
    }

void createWindowClass(HINSTANCE hInstance, WNDPROC wndProc, LPCSTR lpszClassName, int bg, unsigned int style) {
    WNDCLASSEX wc = {0};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = style;
    wc.lpfnWndProc = wndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = lpszClassName;
    if(bg) { wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); }
    else{ wc.hbrBackground = NULL; }
    if(!RegisterClassEx(&wc)) {
        MessageBox(NULL, lpszClassName, "Error RegisterClassEx", MB_OK);
        }
    }

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpszArg, int nCmdShow) {
    (void)hPrevInst;
    (void)lpszArg;
    MSG message = {0};
    hInst = hInstance;

    createWindowClass(hInstance, procWndMain, "procWndMain", 1, CS_HREDRAW|CS_VREDRAW);
    hWndMain = CreateWindowEx(
        WS_EX_CONTROLPARENT,
        "procWndMain",
        "multiWndPaint",
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        (int)CW_USEDEFAULT, (int)CW_USEDEFAULT,
        950, 370,
        HWND_DESKTOP,
        NULL,
        hInstance,
        NULL
        );
    ShowWindow(hWndMain, nCmdShow);
    UpdateWindow(hWndMain);

    while(GetMessage(&message, NULL, 0, 0)) {
        TranslateMessage(&message);
        DispatchMessage(&message);
        }

    UnregisterClass("procWndMain", hInst);
    return (int)message.wParam;
    }

LRESULT CALLBACK procWndMain(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch(msg) {
        case WM_CREATE: {
            createWindowClass(hInst, procWnd0, "procWnd0", 1, 0);
            hWnd0 = CreateWindowEx(
                0,
                "procWnd0",
                "",
                WS_CHILD | WS_VISIBLE | WS_DLGFRAME,
                20, 22,
                440, 300,
                hwnd,
                NULL,
                NULL,
                NULL
                );
            ShowWindow(hWnd0, SW_SHOW);

            createWindowClass(hInst, procWnd1, "procWnd1", 1, 0);
            hWnd1 = CreateWindowEx(
                0,
                "procWnd1",
                "",
                WS_CHILD | WS_VISIBLE | WS_DLGFRAME,
                480, 22,
                440, 300,
                hwnd,
                NULL,
                NULL,
                NULL
                );
            ShowWindow(hWnd1, SW_SHOW);

            HWND btnWnd0 = CreateWindowEx(0, "BUTTON", "Off/On Wnd0",
                WS_CHILD | WS_VISIBLE,
                20, 0, 104, 24,
                hwnd,
                (HMENU)OFF_ON_WND0,
                NULL, NULL
                );
            ShowWindow(btnWnd0, SW_SHOW);

            HWND btnWnd1 = CreateWindowEx(0, "BUTTON", "Off/On Wnd1",
                WS_CHILD | WS_VISIBLE,
                480, 0, 104, 24,
                hwnd,
                (HMENU)OFF_ON_WND1,
                NULL, NULL
                );
            ShowWindow(btnWnd1, SW_SHOW);
            break;
            }

        case WM_COMMAND: {
            if(LOWORD(wParam) == OFF_ON_WND0) {
                if(offOnWnd0) {
                    KillTimer(hWnd0, TIMER_WND0);
                    offOnWnd0 = 0;
                    }
                else{
                    SetTimer(hWnd0, TIMER_WND0, 1, NULL);
                    offOnWnd0 = 1;
                    }
                }

            else if(LOWORD(wParam) == OFF_ON_WND1) {
                if(offOnWnd1) {
                    KillTimer(hWnd1, TIMER_WND1);
                    offOnWnd1 = 0;
                    }
                else{
                    SetTimer(hWnd1, TIMER_WND1, 1, NULL);
                    offOnWnd1 = 1;
                    }
                }
            break;
            }

        case WM_DESTROY: {
            KillTimer(hWnd0, TIMER_WND0);
            KillTimer(hWnd1, TIMER_WND1);
            UnregisterClass("procWnd0", hInst);
            UnregisterClass("procWnd1", hInst);
            PostQuitMessage(0);
            break;
            }

        default: {
            return DefWindowProc(hwnd, msg, wParam, lParam);
            }
        }
    return 0;
    }

void wndInfo(HDC *hdcMem, char *name, unsigned int frame, unsigned int frameRate, unsigned int frameMax) {
    char buf64[24];
    char textInfo[256];
    RECT rect = {0};

    strcpy(textInfo, name);
    strcat(textInfo, "\n");

    uint2str(buf64, frame);
    strcat(textInfo, "Frame : ");
    strcat(textInfo, buf64);

    uint2str(buf64, frameRate);
    strcat(textInfo, "\nFPS : ");
    strcat(textInfo, buf64);

    uint2str(buf64, frameMax);
    strcat(textInfo, "\nMax : ");
    strcat(textInfo, buf64);

    HBRUSH brush = (HBRUSH)GetStockObject(WHITE_BRUSH);
    rect.right = 100;
    rect.bottom = 70;
    FillRect(*hdcMem, &rect, brush);

    rect.left = 4;
    rect.top = 2;
    rect.right = 100;
    rect.bottom = 70;
    SetTextColor(*hdcMem, RGB(0,0,255));
    DrawText(*hdcMem, textInfo, -1, &rect, DT_NOCLIP|DT_NOPREFIX);

    DeleteObject(brush);
    }

LRESULT CALLBACK procWnd0(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    static double timestamp = 0;
    static unsigned int frame = 0;
    static unsigned int frameRate = 0;
    static unsigned int frameMax = 0;

    switch(msg) {
        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            RECT rect = {0};
            GetClientRect(hwnd, &rect);

            HDC hdcMem = CreateCompatibleDC(hdc);
            HBITMAP bmpMem = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
            HBITMAP bmpMemOld = SelectObject(hdcMem, bmpMem);

            SetBkMode(hdcMem, TRANSPARENT);
            SetTextColor(hdcMem, RGB(255,255,255));

            HBRUSH brush = (HBRUSH)GetStockObject(GRAY_BRUSH);
            FillRect(hdcMem, &rect, brush);

            POINT point = {0};
            size_t i = 0;
            size_t len = 99999;
            while(i < len) {
                point.x = randInt(-rect.right*8, rect.right*8);
                point.y = randInt(-rect.bottom*8, rect.bottom*8);
                TextOut(hdcMem, point.x, point.y, "foobar", 6);
                i++;
                }

            frame++;
            double timestampCur = millitimeTick();
            if(timestampCur > timestamp+1.0) {
                timestamp = timestampCur;
                frameRate = frame;
                if(frame > frameMax) { frameMax = frame; }
                frame = 0;
                }

            wndInfo(&hdcMem, "Wnd0", frame, frameRate, frameMax);

            BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);

            DeleteObject(brush);
            SelectObject(hdcMem, bmpMemOld);
            DeleteDC(hdcMem);
            DeleteObject(bmpMem);
            EndPaint(hwnd, &ps);
            break;
            }

        case WM_ERASEBKGND: {
            break;
            }

        case WM_TIMER: {
            if(wParam == TIMER_WND0) {
                InvalidateRect(hwnd, NULL, TRUE);
                }
            break;
            }

        case WM_CREATE: {
            SetTimer(hwnd, TIMER_WND0, 1, NULL);
            break;
            }

        default: {
            return DefWindowProc(hwnd, msg, wParam, lParam);
            }
        }
    return 0;
    }

LRESULT CALLBACK procWnd1(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    static double timestamp = 0;
    static unsigned int frame = 0;
    static unsigned int frameRate = 0;
    static unsigned int frameMax = 0;

    switch(msg) {
        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            RECT rect = {0};
            GetClientRect(hwnd, &rect);

            HDC hdcMem = CreateCompatibleDC(hdc);
            HBITMAP bmpMem = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
            HBITMAP bmpMemOld = SelectObject(hdcMem, bmpMem);

            SetBkMode(hdcMem, TRANSPARENT);
            SetTextColor(hdcMem, RGB(255,255,255));

            HBRUSH brush = (HBRUSH)GetStockObject(GRAY_BRUSH);
            FillRect(hdcMem, &rect, brush);

            POINT point = {0};
            size_t i = 0;
            size_t len = 999;
            while(i < len) {
                point.x = randInt(-rect.right*8, rect.right*8);
                point.y = randInt(-rect.bottom*8, rect.bottom*8);
                TextOut(hdcMem, point.x, point.y, "foobar", 6);
                i++;
                }

            frame++;
            double timestampCur = millitimeTick();
            if(timestampCur > timestamp+1.0) {
                timestamp = timestampCur;
                frameRate = frame;
                if(frame > frameMax) { frameMax = frame; }
                frame = 0;
                }

            wndInfo(&hdcMem, "Wnd1", frame, frameRate, frameMax);

            BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);

            DeleteObject(brush);
            SelectObject(hdcMem, bmpMemOld);
            DeleteDC(hdcMem);
            DeleteObject(bmpMem);
            EndPaint(hwnd, &ps);
            break;
            }

        case WM_ERASEBKGND: {
            break;
            }

        case WM_TIMER: {
            if(wParam == TIMER_WND1) {
                InvalidateRect(hwnd, NULL, TRUE);
                }
            break;
            }

        case WM_CREATE: {
            SetTimer(hwnd, TIMER_WND1, 1, NULL);
            break;
            }

        default: {
            return DefWindowProc(hwnd, msg, wParam, lParam);
            }
        }
    return 0;
    }

1 个答案:

答案 0 :(得分:2)

绘制60 FPS窗口大约需要16毫秒或更短的时间。我说“ 或更少”是因为,尽管您请求的计时器间隔为1 ms,但您可能会得到16 ms的数量级,因为这是这些计时器的默认分辨率。因此,您的快速窗口受到计时器分辨率的限制,而不是其绘制速度的限制。

您的10 FPS窗口大约需要100毫秒绘制,并且“下一个”间隔已经过去,因此计时器应立即触发。下一个帧大约需要100毫秒的事实表明,实际绘制时间非常接近100毫秒。

当您同时显示两个窗口时,不会同时绘制。当您的消息循环调用GetMessage和DispatchMessage时,将发送WM_TIMER消息。如果您的代码在第二个窗口的计时器到期时正在忙于绘制第一个窗口,那么第二个窗口的WM_TIMER消息将被延迟,直到第一个窗口完成,然后从其窗口过程返回到消息循环。

所以其中一个被涂上了,另一个。再次受两个窗口的总时间限制。快速窗口不能比慢速窗口运行得快。

(还有其他并发症,但这是实验中的主要因素。)

这里并没有很好的解决方案。众所周知,在同一进程中使用多个线程的GDI非常困难。如果您可以将一个窗口置于一个进程中,而将另一个窗口置于另一个进程中,则可以直观地看到它们并排运行。