Typing diary
콘솔 창 Double buffering 구현하기 (C++) 본문
2020. 11. 28
Double buffering 이란?
디스플레이는 프레임버퍼라는 픽셀들의 배열로부터 값을 가져와 화면에 그림을 그린다.
문제는 컴퓨터는 순차적으로 작동하기 때문에 프레임버퍼에 한 픽셀씩 값을 전달하게 된다.
비디오 드라이버가 아직 완성되지 않은 프레임버퍼를 화면에 띄우게 되면 다 입력되지 못한 픽셀 값까지 같이
출력하게 되고 그림이 잘려서 나오게 된다. 이를 Tearing 현상이라고 한다.
이를 해결하기 위해 프레임버퍼를 2개 두고 한 프레임버퍼가 완성될 때까지 다른 프레임버퍼를 출력함으로써 완성된 프레임 버퍼만을 출력하게 하는 게 Double buffering이다.
2개의 프레임버퍼를 만든다.

사진 설명을 입력하세요.
1번 프레임버퍼를 출력하는 동안 2번 프레임 버퍼를 채운다.

사진 설명을 입력하세요.
2번 프레임버퍼가 완성됐으면 2번 프레임버퍼를 출력하고 2번 프레임버퍼를 출력하는 동안 1번 프레임버퍼를 다시 채운다.
코드
Screen.h
#pragma once
#include <Windows.h>
#include <iostream>
using namespace std;
class Screen
{
public:
HANDLE mScreenBuffer[2]; //화면 버퍼
int mCurBufferIndex = 0; //현재 쓰기 중인 버퍼의 인덱스
CONSOLE_SCREEN_BUFFER_INFO info; //버퍼의 크기를 가져오기 위한 info
public:
Screen() {};
virtual ~Screen();
bool Init(); //버퍼를 생성하고 info를 가져오는 함수
void SetPixel(const wstring& str, const COORD& position); //버퍼에 픽섹값을 입력하는 함수
bool ChangeScreenBuffer(); //버퍼를 교환하는 함수
};
Screen.cpp
Init()
bool Screen::Init()
{
//콘솔버퍼 생성
mScreenBuffer[0] =
CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
0, NULL,
CONSOLE_TEXTMODE_BUFFER,
NULL);
if (mScreenBuffer[0] == INVALID_HANDLE_VALUE) {
return false;
}
mScreenBuffer[1] =
CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
0, NULL,
CONSOLE_TEXTMODE_BUFFER,
NULL);
if (mScreenBuffer[1] == INVALID_HANDLE_VALUE) {
return false;
}
//커서 제거
CONSOLE_CURSOR_INFO cursorInfo = { 1,FALSE };
if (SetConsoleCursorInfo(mScreenBuffer[0], &cursorInfo) == false) {
std::wcout << GetLastError();
return false;
}
if (SetConsoleCursorInfo(mScreenBuffer[1], &cursorInfo) == false) {
return false;
}
//버퍼의 크기를 가져오기 위한 info
if (GetConsoleScreenBufferInfo(mScreenBuffer[0], &info))
return false;
return true;
}
버퍼를 2개 생성하고 콘솔 창의 깜박이는 커서를 지워준다.
버퍼의 크기를 가져오기 위해 GetConsoleScreenBufferInfo 함수로 버퍼 정보를 가져온다.
SetPixel()
void Screen::SetPixel(const wstring& str, const COORD& position)
{
//버퍼의 크기를 벗어나면 버퍼에 쓰지 않는다.
if (position.X >= 0 && position.X < info.dwSize.X-1
&& position.Y >= 0 && position.Y < info.dwSize.Y-1)
{
//커서 위치 설정
SetConsoleCursorPosition(mScreenBuffer[mCurBufferIndex], { position.X ,position.Y });
//유니코드를 출력하기 위해선 WriteConsole함수를 써야함
WriteConsole(mScreenBuffer[mCurBufferIndex], str.c_str(), str.size(), NULL, NULL);
}
}
버퍼의 커서를 position 값에 위치시키고 문자열 값을 입력한다.
ChangeScreenBuffer()
bool Screen::ChangeScreenBuffer()
{
//쓰기 버퍼를 출력하여 읽기 버퍼로 바꾼다.
if (SetConsoleActiveScreenBuffer(mScreenBuffer[mCurBufferIndex]) == false)
return false;
mCurBufferIndex = !mCurBufferIndex;
//다음 프레임에 쓸 버퍼를 초기화한다.
DWORD written;
FillConsoleOutputCharacterA(
mScreenBuffer[mCurBufferIndex], ' ', 100*100, { 0,0 }, &written
);
return true;
}
읽기, 쓰기 버퍼를 바꿔준다.
다음 프레임에 쓸 버퍼는 공백문자로 채워 초기화해준다.
~Screen()
Screen::~Screen()
{
CloseHandle(mScreenBuffer[0]);
CloseHandle(mScreenBuffer[1]);
}
소멸자에서 버퍼를 반환해 준다.
main.cpp
#include "Screen.h"
int main()
{
Screen screen;
wstring pixelStr[7] = { L"██", L"▓▓" ,L"░░", L" ", L"░░", L"▓▓",L"██" };
COORD position = { 0,0 };
int idx = 0;
if (screen.Init())
{
while (1)
{
//render
for (size_t i = 0; i < 5; i++)
{
for (size_t j = 0; j < 30; j++)
{
screen.SetPixel(
pixelStr[idx % 7],
{ (position.X - (short)(i+j)) % screen.info.dwSize.X,position.Y+ (short)j }
);
}
}
position.X++;
idx++;
//프레임 마지막에 버퍼를 바꾼다.
screen.ChangeScreenBuffer();
//속도조절
Sleep(50);
}
}
}
화면이 끊기거나 깜박이지 않고 잘 나오는 걸 볼 수 있다.
'그래픽스, 게임 수학' 카테고리의 다른 글
LU 분해 (0) | 2022.08.21 |
---|---|
확장 행렬과 가우스-조던 소거법 (0) | 2022.08.21 |
벡터 방정식 (0) | 2022.08.21 |
Dot Product ( 스칼라 곱) (0) | 2022.08.21 |
3D 그래픽 Projection(투영) (0) | 2022.08.21 |