1

I created a simple render function as a test. I call this function with FPS 60. I'm testing 2 pictures in total. Each time the function is called, I draw one picture. With each new call, the picture changes. The idea is that due to the high frequency of the function call, I should get two flashing pictures on the screen.

The problem is that one picture is displayed for a long time on the screen, because of this, the second picture is not rendered. After some time, the second picture starts to be displayed on the screen for a long time, and the first one stops being drawn because of this.

The point is that each picture should be rendered 30 times on the screen in 1 second. But I don't get that flashing

I created two HDC because in the future I will need to be able to display many images at a time.

void Game::render()
{
    static HBITMAP hBitMap1 = (HBITMAP)LoadImage(NULL, L"..\\images\\player01.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
    static HBITMAP hBitMap2 = (HBITMAP)LoadImage(NULL, L"..\\images\\player02.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

    HDC memDC = CreateCompatibleDC(dc);
    HDC memDC1 = CreateCompatibleDC(dc);
    HBITMAP memBM = CreateCompatibleBitmap(dc, windowRect.right, windowRect.bottom);

    SelectObject(memDC, memBM);

    static int image_changing = 0;
    int x, y;

    HBITMAP hBitMap;
    if (image_changing < 1) {
        hBitMap = hBitMap1;
        x = 200;
        y = 200;
        image_changing++;
    } else {
        hBitMap = hBitMap2;
        x = 200;
        y = 50;
        image_changing = 0;
    }

    HBITMAP hbit = (HBITMAP)SelectObject(memDC1, hBitMap);
    BitBlt(memDC, x, y, IMAGE_SIZE_X, IMAGE_SIZE_Y, memDC1, 0, 0, SRCCOPY);
    SelectObject(memDC1, hbit);

    BitBlt(dc, 0, 0, windowRect.right, windowRect.bottom, memDC, 0, 0, SRCCOPY);

    DeleteDC(memDC1);
    DeleteDC(memDC);
    DeleteObject(memBM);
}

loop for updating render

void Game::run()
{
    // Initialize previous time to current time
    DWORD prevTime = timeGetTime();

    // Initialize frame counter and FPS variables
    int frameCount = 0;

    // Initialize timer variables
    DWORD timer = timeGetTime();
    int elapsedTime = 0;

    MSG msg = { };
    while (true)
    {

        if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
        {
            // Exit
            if (msg.message == WM_QUIT)
                break;
            
            // Add Bullets
            if (msg.message == WM_LBUTTONDOWN)
                if (player->getPlayerCanFire()) {
                    addBullet(LOWORD(msg.lParam), HIWORD(msg.lParam));
                    player->setPlayerCanFire(FALSE);
                    player->setPlayerFireElapsedTime(0);
                }
            
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            // Get current time
            DWORD currentTime = timeGetTime();

            // Calculate difference between current time and previous time
            DWORD deltaTime = currentTime - prevTime;

            // If difference is less than desired time interval, sleep for the difference
            if (deltaTime < FRAME_INTERVAL) {
                Sleep(FRAME_INTERVAL - deltaTime);
            }

            prevTime = currentTime;

            // convert to seconds
            update(deltaTime / 1000.0);

            render();

            // Increment frame counter
            frameCount++;

            // Check if timer interval has elapsed
            elapsedTime = currentTime - timer;
            if (elapsedTime >= 1000) {
                // Calculate FPS
                fps = frameCount * 1000 / elapsedTime;

                // Reset timer and frame counter
                timer = currentTime;
                frameCount = 0;
            }
        }
    }
}
Alex
  • 45
  • 5
  • You don't need `memDC1` or `memBM`, they are redundant. Just `SelectObject()` your source `HBITMAP` into `memDC` and then `BitBlt()` (or `StretchBlt()`) that into your target `dc`. In any case, please [edit] your question to include the code for your `render()` loop. And why are you using an `int` as a boolean? Why not use `bool` instead? – Remy Lebeau Jun 30 '23 at 20:39
  • **dc** is the context of my window. If I immediately display images using **BitBlt()**, then if I need to display more than 100 images, then I will not get smoothness. All images must be rendered at the same time – Alex Jul 01 '23 at 05:28
  • therefore, **memDC** is created in which all the pictures will be drawn first, and **memDC1** is only needed to load 1 picture and then draw everything in **memDC**. At the end, unload everything from **memDC** to **cd** – Alex Jul 01 '23 at 05:37
  • You can't draw more than 1 image at a time onto an HDC in a single thread. You have to draw multiple images sequentially, so using a single memory HDC will suffice. On each render iteration, select an image into the memory HDC, BitBlt it to your window, unselect it, and repeat for the other 99 images, reusing the same memory HDC – Remy Lebeau Jul 01 '23 at 14:28

1 Answers1

0

Note that Sleep is not a precise function. The clock rate may be quite coarse. If you ask it to sleep less than a full clock period, it might not sleep at all. If you ask it to sleep more than a clock period, the thread might sleep almost an additional clock period. Also note that once the time has elapsed and the thread is again ready to run, when it's actually run again is up to the scheduler.

My solution for game loops on Windows is to use Sleep only when I have many milliseconds to wait, and then I ask it to Sleep for slightly less than the full time. After that, I do a busy wait for the next frame time. Busy waits are generally bad, but it is a game loop, the spin time is limited, and it's better than changing the clock period for all processes.

Is your computed frame rate really 60 fps? Given the imprecision of sleeping and an unknown amount of time in update, I'd be surprised.

In the rendering code, there's an inefficiency in that you're blitting the bitmap twice: first from memDC1 to memDC, and then from memDC to dc. You seemed to try to justify that in the comments, but I couldn't quite follow your reasoning. Even if there's a good reason to do that in the long run, you might want to eliminate the extra step while debugging the current problem.

The clean-up of the GDI objects has a problem. Deleting memDC while memBM is still selected into it is technically an error. This may or may not be a factor in the problem you're seeing. Sometimes GDI seems to be more forgiving of these kinds of problems, but sometimes it can cause weird behavior.

If I recall correctly, the desktop window manager uses heuristics to figure out when GDI is "finished" with a frame so that it can update the texture for that window. For event-driven painting, this usually works fine. For rapid blitting, it's possible it's just not syncing up at the right moments. This could be because of the sequence problem in the clean-up or maybe the GDI commands are being batched. If fixing the sequence problem doesn't solve the problem, try calling GdiFlush at the end of your render call.

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
  • Thank you for such a detailed answer. I tested the frame rate using sleep and the values were confirmed. But I understand what you were talking about. *Deleting memDC while memBM is still selected into it is technically an error. This may or may not be a factor in the problem you're seeing.* That's a good point, thank you. GdiFlush didn't help either. It's strange, but in the test example I changed the images using indexes to display on the screen. But when I tried to write a change of images using a time interval, it worked! – Alex Jul 01 '23 at 09:44