Win32_API - GDI로 선그리기5(깜빡이는 현상 제거/더블버퍼링)

컴퓨터/Win32-API

728x90
반응형

서론

지난 글에서 마우스를 이용하여 선을 그려보고 다양한 선들을 계속해서 그릴 수 있는 방법에 대해서 알아보았습니다. 

하지만 수많은 선을 그리다 보면 어느 순간부터 화면이 깜빡거리는 현상이 보이게 됩니다. 

그리고 많은 선을 그리면 그릴수록 이 현상은 더 자주 보이게 됩니다. 

본문에서는 이런 현상에 대해서 알아보고 극복할 수 있는 방법을 알아보도록 합시다.

 

 

Win32_API - GDI 선그리기4(Vector 활용해서 여러 선 저장하기)

서론 지난 글에서 직접 링크드 리스트를 구현하여 여러 선들의 좌표를 저장하여 선을 그렸습니다. 이런 식으로 하나하나 구현을 하면 공부에는 도움이 되지만, 효율에서는 너무나 떨어지게 됩

blog-of-gon.tistory.com

 

원인 - 컴퓨터가 일하는 모습을 포착한다.

깜빡거리는 현상은 왜 일어나는 걸까요?

이전 글에서 만든 소스코드를 보면 WM_MOUSEMOVE메세시가 발생할 때마다 무효화 영역을 생성합니다. 

그리고 WM_PAINT메시지에서 DC를 통해 프로그래머가 의도한 형태로 다양한 그림을 그리게 됩니다. 

물론, 이 그리는 과정은 인간에게는 더없이 빠른 속도입니다.

무효화 영역을 계속해서 생성하는 과정에서 WM_PAINT에서의 처리과정이 증가한다면, 사람의 눈에서 그 과정을 순간적으로 포착하게 됩니다. 이러한 원인 대문에 깜빡거리는 현상이 발생하게 됩니다.

 

해결방법 

원인은 수많은 WM_PAINT메시지의 발생과 많은 양의 처리입니다. 

이 방법을 해결하기 크게 2가지 방법이 있습니다.

 

  • 무효화 영역의 제한과 WM_PAINT의 호출의 제한

물론 이 방법이 효과적인 경우도 있습니다. 하지만 많은 경우에서 특정 부분만 다시 그리거나, WM_PAINT의 호출하는 제한을 하기는 쉽지 않습니다. 

간단한 게임의 경우에도 전반적으로 전체를 그리는 과정이 적합합니다. 이 때문에 더블 버퍼링이라는 개념을 적용하게 됩니다.

더블 버퍼링이라는 개념은 다음과 같습니다.

  • 다른 공간에 미리 그려서 한 번에 전달해주자. 

위의 방법으로 해결한다면 사용자에게 깜빡이는 현상은 존재할 수 없게 됩니다.

 

 

더블 버퍼링

위에서 설명한 것처럼 미리 그림을 그리고 완성된 그림을 전달해주는 방법입니다.

A4용지에 사람이 100개의 선을 그린다면 눈으로 선을 그리는 과정을 다 볼 수 있습니다.

하지만 100개의 선을 그린 A4용지를 전달받는다면 100개의 선이 그려져 있는 장면만을 볼 수 있게 됩니다.

이러한 개념이 더블 버퍼링입니다. 

 

조금 더 프로그래머처럼 말한다면 가상의 메모리에 DC를 이용하여 그린 후 보여주고자 하는 윈도에 그림을 전달해 주는 것입니다. 이 더블 버퍼링을 사용해서 본문에서는 지난 글 예제의 문제점을 해결해 보도록 합시다.

 

전체 소스코드 보기

더보기
// Windows 헤더 파일
#include <windows.h>
// C 런타임 헤더 파일입니다.
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <Windowsx.h>
#include <vector>
#include <algorithm>







// 전역 변수:
HINSTANCE hInst;                                // 현재 인스턴스입니다.
POINT start_pos,end_pos;
    //그리기를 상태를 파악하기 위한변수
int status;
int count;
std::vector<std::pair<POINT,POINT>> list;

// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:

LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);


int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{

    //윈도우 창 구조체 정의 및 적용
    WNDCLASSEXW wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = NULL;
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = L"Test";
    wcex.hIconSm = NULL;
    RegisterClassExW(&wcex);

    //적용한 윈도우 생성 및 업데이트
    hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
    HWND hWnd = CreateWindowW(L"Test", L"Test", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
    if (!hWnd)
    {
        return FALSE;
    }
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    MSG msg;

    // 기본 메시지 루프입니다:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    switch (message)
    {
    case WM_CREATE:

        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            hdc = BeginPaint(hWnd, &ps);

            //메모리에 DC 가지고 생성
            HDC memdc = CreateCompatibleDC(hdc);
            //현재 윈도우 창 크기 받아오기
            RECT rect; 
            GetClientRect(hWnd, &rect);

            //메모리에 윈도우 창과 동일한 그기에 그릴수 있도록 셋팅하기 
            HBITMAP memBitmap = CreateCompatibleBitmap(memdc, rect.right, rect.bottom);
            //HBITMAP또한 하나의 그리기 도구이므로 선택하기 및 예전 그리기 도구 저장
            HBITMAP oldBitmap = (HBITMAP)SelectObject(memdc, memBitmap);
            //가상공간 그리는 공간 백그라운드 컬러 설정해주기
            FillRect(memdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
            //메모리공간에 그림 그리기
            if (status == 1) {
                MoveToEx(memdc, start_pos.x, start_pos.y, NULL);
                LineTo(memdc, end_pos.x, end_pos.y);
            }
            for (int i = 0; i < list.size(); i++)
            {
                MoveToEx(memdc, list[i].first.x, list[i].first.y, NULL);
                LineTo(memdc, list[i].second.x, list[i].second.y);
            }

            //넘겨주고자 하는 메인 윈도우로 전달하기 (메모리공간 그림 --> 메인 화면)
            BitBlt(hdc, 0, 0, rect.right, rect.bottom, memdc, 0, 0, SRCCOPY);

            //생성해둔 메모리 공간 제거
            SelectObject(memdc,oldBitmap);
            DeleteObject(memBitmap);
            DeleteDC(memdc);


            EndPaint(hWnd, &ps);
        }
        break;
    //마우스 동작에 따른 처리
        //움직일 때
    case WM_MOUSEMOVE:
        /*
        //예제 3을 위한 소스코드
        if (status == 1)
        {
            end_pos.x = GET_X_LPARAM(lParam);
            end_pos.y = GET_Y_LPARAM(lParam);
            hdc = GetDC(hWnd);
            MoveToEx(hdc, start_pos.x, start_pos.y, NULL);
            LineTo(hdc, end_pos.x, end_pos.y);
            ReleaseDC(hWnd, hdc);
        }
        */
        //예제 4를 위한 소스코드
            end_pos.x = GET_X_LPARAM(lParam);
            end_pos.y = GET_Y_LPARAM(lParam);
            InvalidateRect(hWnd, NULL, FALSE);
            UpdateWindow(hWnd);

        break;
        //좌클릭 눌렀을 때
    case WM_LBUTTONDOWN:
        /*
        //예제 1을 위한 소스코드
        //WM_PAINT가 아닌 곳에서 DC를 불러오는 방법
        hdc = GetDC(hWnd);
        MoveToEx(hdc, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),NULL);
        LineTo(hdc, (GET_X_LPARAM(lParam)+100), GET_Y_LPARAM(lParam));
        ReleaseDC(hWnd, hdc);
        */

        /*
        //예제 2,3를 위한 소스코드
        start_pos.x = GET_X_LPARAM(lParam);
        start_pos.y = GET_Y_LPARAM(lParam);
        //예제 3을 위한 소스코드
        status = 1;
        */
        //예제 4를 위한 소스코드
        start_pos.x = GET_X_LPARAM(lParam);
        start_pos.y = GET_Y_LPARAM(lParam);
        status = 1;
        break;
        //좌클릭 해제할 때
    case WM_LBUTTONUP:
        {
        /*
        //예제 2,3를 위한 소스코드
        end_pos.x = GET_X_LPARAM(lParam);
        end_pos.y = GET_Y_LPARAM(lParam);
        hdc = GetDC(hWnd);
        MoveToEx(hdc, start_pos.x, start_pos.y, NULL);
        LineTo(hdc, end_pos.x, end_pos.y);
        ReleaseDC(hWnd, hdc);
        //예제 3을 위한 소스코드
        status = 0;
        */
        //예제 4를 위한 소스코드
        end_pos.x = GET_X_LPARAM(lParam);
        end_pos.y = GET_Y_LPARAM(lParam);

        std::pair<POINT, POINT> temp = { start_pos,end_pos };
        list.push_back(temp);
        count++;
        status = 0;
        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

주의사항 및 당부의말 

함수들에 대해서는 한번 직접 찾아보시길 바랍니다.

추가로 WM_MOUSEMOVE에서 InvalidateRect에서 배경 다시 그리기는 (FALSE)로 해주어야 됩니다.!

 

728x90
반응형

Commnet

G91개발일지

Gon91(지구일)

91년생 공학엔지니어의 개발일지

TODAY :

YESTER DAY :

TOTAL :