[C언어/C++] 화면 깜빡임 없애기 (더블 버퍼링)

콘솔로 개발을 진행할때 화면을 모두 지우고 재출력을 해야하는 상황에서 대개 while문 안에  system("cls") 명령어를 많이 사용하는데 콘솔의 내용을 지우고 다시 쓰고 하는 과정에서 정신산만한 깜빡임이 발생합니다. 

콘솔 깜빡임

이러한 문제가 생기는 원인은 싱글 버퍼링을 사용할 경우에 ( 화면 출력 -> 초기화 -> 화면 출력 ) 이 과정을 빠른속도로 무한 반복하는 도중 계속해서 공백이 노출되기 때문입니다. 이러한 현상을 없애주려면 그래픽스에서 많이 사용하는 더블 버퍼링이라는 기법을 사용하여야 합니다.

 

더블 버퍼링이란?

더블 버퍼링이란 싱글 버퍼링으로 화면을 그릴 경우 데이터를 저장하는 동안에는 다음 그림의 데이터를 전송할 수 없기 때문에 지우고 그리고 지우고를 반복 할 경우 필연적으로 발생하는 깜빡임 등의 상황을 막기 위해서 사용되는 기법입니다. 

더블 버퍼링 구조

더블 버퍼링의 방식은 간단합니다. 위와 같이 화면에 보이지않는 Back Buffer를 먼저 하나 둔 뒤 이 Back Buffer에서 작업을 진행합니다. 그런 뒤 그것을 출력할 Front Buffer에 옮기는 작업을 반복해주면서 출력하는 방식입니다. 화면을 초기화 하는 과정이 없고 메모리(버퍼)상의 그림을 화면에 그리는 작업만 하게 되어 검은 화면은 나오지 않게 되고 깜박임 현상도 사라지게 됩니다.

 

컴퓨터는 순차적으로 동작합니다. 하지만 사용자 입장에서는 순차적으로 혹은 동시에 진행되는 여러 작업을 한 번에 모아서 봐야 할때가 있습니다. 대표적인 예가 게임에서의 렌더링입니다. 화면을 그리는 중간 과정이 보이면 굉장히 산만해 보이기에 화면에 몰입할 수가 없습니다. 게임에서의 각 장면은 굉장히 부드럽고 빠르게 업데이트 되어야 하고 매 프레임이 완성되면 한 번에 보여줘야 합니다. 그런면에서 더블 버퍼링을 사용하여 화면을 부드럽게 만드는 과정은 선택이 아닌 필수가 되야 할 것입니다.

 

더블 버퍼링 예제

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
#include <ctime>

static int g_nScreenIndex;
static HANDLE g_hScreen[2];
int g_numofFPS;
clock_t CurTime, OldTime;
char* FPSTextInfo;

void ScreenInit()
{
    CONSOLE_CURSOR_INFO cci;

    // 화면 버퍼 2개를 만든다.
    g_hScreen[0] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
    g_hScreen[1] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);

    // 커서를 숨긴다.
    cci.dwSize = 1;
    cci.bVisible = FALSE;
    SetConsoleCursorInfo(g_hScreen[0], &cci);
    SetConsoleCursorInfo(g_hScreen[1], &cci);
}

void ScreenFlipping()
{
    SetConsoleActiveScreenBuffer(g_hScreen[g_nScreenIndex]);
    g_nScreenIndex = !g_nScreenIndex;
}

void ScreenClear()
{
    COORD Coor = { 0, 0 };
    DWORD dw;
    FillConsoleOutputCharacter(g_hScreen[g_nScreenIndex], ' ', 80 * 25, Coor, &dw);
}

void ScreenRelease()
{
    CloseHandle(g_hScreen[0]);
    CloseHandle(g_hScreen[1]);
}

void ScreenPrint(int x, int y, char* string)
{
    DWORD dw;
    COORD CursorPosition = { x, y };
    SetConsoleCursorPosition(g_hScreen[g_nScreenIndex], CursorPosition);
    WriteFile(g_hScreen[g_nScreenIndex], string, strlen(string), &dw, NULL);
}

void Render()
{
    ScreenClear();
    
    if (CurTime - OldTime >= 1000) // 출력 코드
    {
        sprintf(FPSTextInfo, "FPS : %d", g_numofFPS);
        OldTime = CurTime;
        g_numofFPS = 0;
    }

    g_numofFPS++;
    ScreenPrint(0, 0, FPSTextInfo);
    ScreenFlipping();
}

void Release()
{
    delete[] FPSTextInfo;
}

int main()
{
    g_numofFPS = 0;
    FPSTextInfo = new char[128];
    memset(FPSTextInfo, '\0', 128);

    ScreenInit();

    OldTime = clock(); // 시간을 측정한다. 1초마다 갱신한다.

    while (1)
    {
        CurTime = clock();
        Render();
    }

    Release();
    ScreenRelease();

    return 0;
}

C언어/C++에서는 화면 버퍼를 사용할 수 있는 다양한 함수들을 제공합니다. CreateConsoleScreenBuffer, SetConsoleActiveScreenBuffer, SetConsoleCursorPosition들을 사용하면 더블 버퍼링을 쉽게 구현할 수 있습니다.

댓글

Designed by JB FACTORY