Im ieee
Im ieee

Reputation: 469

Different program behaviour in Windows XP and Windows 7

This code draws two vertical stripes from top to bottom of the window in two threads (corresponding function for each thread is thread_func). The first thread draws a part of the left stripe, then the second draws a part of the right one, then the first again and so on. A semaphore and a critical section are used to ensure this order.

#include <windows.h>
#include <cstdint>

HDC hDC; 
HDC  hDCMem;
HBITMAP hbitmap;
HWND  hwnd; 
int ScreenMaxX;
int ScreenMaxY;

short pattern[8]={~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF};
HBRUSH brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern));

void bar(int nLeft, int nTop, int nRight, int nBottom)
{
    RECT rect;
    rect.left   = nLeft;
    rect.right  = nRight;
    rect.top    = nTop;
    rect.bottom = nBottom;

    ::SetTextColor(hDCMem, 0xFF00FF);
    ::SetBkColor(hDCMem, 0xFF00FF);
    //brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern));
    ::FillRect(hDCMem, &rect, brush);
}

void flush(){
    ::BitBlt(hDC, 0, 0, ScreenMaxX, ScreenMaxY, hDCMem, 0, 0, SRCCOPY);
}

CRITICAL_SECTION graphics_cs;

uint8_t thread_cnt=0;
uint8_t total_threads=2;
HANDLE turnstile1=CreateSemaphoreW(nullptr, 0, 2, nullptr);

void thread_func(int num){
    int x,y;
    if(num==0){
        x=20; y=0;
    } else {
        x=110; y=0;
    }

    while(true) {
        while(true) {
            EnterCriticalSection(&graphics_cs);
            if (thread_cnt == num) {
                thread_cnt++;
                bar(x, y, x+40, y+40);
                y+=1;
                //flush();
                if(thread_cnt==total_threads){
                    thread_cnt = 0;
                    flush();
                    ReleaseSemaphore(turnstile1, total_threads, nullptr);
                }
                LeaveCriticalSection(&graphics_cs);
                break;
            } else {
                LeaveCriticalSection(&graphics_cs);
            }
        }

        WaitForSingleObject(turnstile1, INFINITE);

        Sleep(100);
    }
}

void mainx ()
{
    InitializeCriticalSection(&graphics_cs);
    for(int i=0; i<total_threads; i++){
        CreateThread (nullptr, 0, (LPTHREAD_START_ROUTINE)thread_func, (LPVOID)i, 0, nullptr);
    }
}

DWORD Th(LPVOID param)
{
    (void)param;
    ::SetWindowPos(hwnd, HWND_TOP,
                   10,
                   10,
                   400,
                   500,
                   SWP_SHOWWINDOW
    );
    mainx();
    flush();
    return 0;
}
DWORD g_nMainThreadID;

//processing main window messages
long FAR PASCAL WindowProc(HWND   hWnd,UINT   message, WPARAM wParam,LPARAM lParam )
{
    switch (message)
    {
        case WM_PAINT:   flush();
            break;
        case WM_DESTROY: PostQuitMessage(0);
            break;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR  lpCmdLine,  int  nShowCmd )
{
    (void)hPrevInstance, (void)lpCmdLine;
    WNDCLASS  wc;
    MSG       msg;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WindowProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName  = "Menu_one";
    wc.lpszClassName = "NAME";

    if (!RegisterClass(&wc)) {return 0; };


    //main window
    hwnd = CreateWindow("NAME",    
                        "!", 
                        WS_OVERLAPPEDWINDOW,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        HWND_DESKTOP, 
                        NULL, 
                        hInstance,
                        NULL
    );
    ScreenMaxX = ::GetSystemMetrics(SM_CXSCREEN);
    ScreenMaxY = ::GetSystemMetrics(SM_CYSCREEN);
    hDC = ::GetDC(hwnd);
    hDCMem  = ::CreateCompatibleDC(hDC);
    hbitmap = ::CreateCompatibleBitmap(hDC, ScreenMaxX, ScreenMaxY );
    ::SelectObject(hDCMem, hbitmap);
    auto hbrush = (HBRUSH)::GetStockObject(WHITE_BRUSH);
    ::SelectObject(hDCMem, hbrush);
    ::PatBlt(hDCMem, 0,0, ScreenMaxX, ScreenMaxY, PATCOPY );
    ::DeleteObject(hbrush);


    CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Th, (LPVOID)hwnd, 0,&g_nMainThreadID);

    ShowWindow(hwnd, nShowCmd);
    UpdateWindow(hwnd);


    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

In Windows 7 the stripes are drawn with the same speed as expected. But in Windows XP the speed is different:

different speed

If I uncomment either //brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern)); or //flush(); lines, the speed of drawing in Windows XP will be the same. Why does this fix the problem and why does the behaviour of the initital code differ in different versions of Windows?

Update

When I add std::cout<<"num = "<<num<<" : bar call\n"; and std::cout<<"num = "<<num<<" : flush call\n"; after bar and flush calls in thread_func, the output is

num = 0 : bar call
num = 1 : bar call
num = 1 : flush call
num = 0 : bar call
num = 1 : bar call
num = 1 : flush call
num = 0 : bar call
num = 1 : bar call
num = 1 : flush call
num = 0 : bar call
num = 1 : bar call
num = 1 : flush call
...

The order seems correct, but the left stripe is not drawn immediately after the flush call.

Upvotes: 1

Views: 181

Answers (1)

Adrian McCarthy
Adrian McCarthy

Reputation: 48022

Pulling bits from my comments into an answer.

Be aware that GDI batches operations which can cause bursty rendering on slower machines. While GDI batches on both XP and 7, in practice that doesn't seem to be much of an issue anymore on 7. Try calling GdiSetBatchLimit to set the limit to 1. This will cause GDI to flush after every call, which may be slower overall, but should eliminate the bursty behavior.

Upvotes: 1

Related Questions