Как добавить задний буфер

Загрузить архив с примерами ЗДЕСЬ.

Мы не использовали backbuffer (задний буфер) для наглядности примеров, вы можете сами добавить backbuffer. Назначение заднего буфера следующее- когда мы прямо в видеопамять рисуем изображение- оно обычно мерцает на дисплее, так как мы рисуем в видеопамять, и паралельно дисплей читает эту видепамять и отображает на экране. Например у нас частота обновления дисплея 60 Герц. Это значит что дисплей обновляет изображение на экране (дисплей читает видеопамять) 60 раз в секунду. В тот момент когда дисплей читает видеопамять что бы обновить изображение на экране, мы к примеру проводим линию, и если правильно- то линия рисуется в видеобуфере, и изображение выглядит искаженным или мерцающим, так как дислей читает видеопамять и может быть где то по средине видеопамяти, и мы в этот момент рисуем линию, другую, третью - то есть заполняем эту видеопамять которую в этот момент читает дисплей- поэтому возникает мерцание. Что бы избежать мерцания изображения при выводе используют следующий метод- сначала все изображение рисуют в участок памяти так называемый задний буфер, а потом уже готовое изображение копируют в видеопамять. Этот подход позволяет избежать мерцания. Задний буфер будет использоватся в проектах в следующих главах.

Ниже приведено два варианта создания заднего буфера для графических приложений WinAPI.

Вариант #1 создания заднего буфера (используется функция WinAPI BitBlt) и рисования линии, загрузить приложение, код ниже, можно /src/01.003-back_buff/Draw_Line_BackBuff1.

Здесь необходимо уточнить, что если для очистки экрана вы используете функцию Rectangle() то эта функция рисует рамку черного цвета вокруг той области что бы очищаете. Что бы эту рамку небыло видно, необходимо создать перо HPEN такого же цвета как и очищаемая область, потом выбрать это перо SelectObject() и рамки видно не будет. Например вы очищаете область окна функцией Rectangle() и выбрали кисть для очистки HBRUSH RGB(255, 255, 255) значит и перо HPEN вы должны взять цветом RGB(255, 255, 255) тогда рамки вокруг очищаемой области не будет - рамка будет рисоваться белым цветом, тем цветом что вы заполняете очищаемую область. А функция FillRect() рамку не рисует, очищает область заданным цветом.


#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <conio.h>
#include <math.h>

//глобальные переменные
HWND 	hWnd;

//переменные для заднего буфера
HDC	hBackBuffer;
HBITMAP hBitmap;
HBITMAP hOldBitmap;

UINT nViewWidth;
UINT nViewHeight;

void Create_Backbuffer()
{
	RECT rc;
	GetClientRect(hWnd, &rc);

	nViewWidth = rc.right;
	nViewHeight = rc.bottom;

	HDC hDC = GetDC(hWnd);

	hBackBuffer = CreateCompatibleDC(hDC);
	hBitmap = CreateCompatibleBitmap(hDC, nViewWidth, nViewHeight);
    	hOldBitmap = (HBITMAP) SelectObject(hBackBuffer, hBitmap);

	ReleaseDC(hWnd, hDC);
}


void Clear_Backbuffer()
{
	HBRUSH hBrush = CreateSolidBrush( RGB( 0, 0, 0 ) );
	HBRUSH hOldBrush = (HBRUSH) SelectObject( hBackBuffer, hBrush);

	//можно так же использовать функцию FillRect()
	Rectangle(hBackBuffer, 0, 0, nViewWidth, nViewHeight);

    	SelectObject(hBackBuffer, hOldBrush);
	DeleteObject(hBrush);
}

void Present_Backbuffer()
{
	HDC hDC = GetDC(hWnd);

	BitBlt(hDC, 0, 0, nViewWidth, nViewHeight, hBackBuffer, 0, 0, SRCCOPY);

	ReleaseDC(hWnd, hDC);
}


void Delete_Backbuffer()
{
	SelectObject(hBackBuffer, hOldBitmap);
  	DeleteObject(hBitmap);
	DeleteDC(hBackBuffer);
}


void Draw_Line()
{
	//очищаем задний буфер от предыдущего изображения
	Clear_Backbuffer();

	//создаем перо, сохраняем старое перо
	HPEN hPen = CreatePen(PS_SOLID, 4, RGB( 255, 255, 127 ) );
	HPEN hOldPen = (HPEN) SelectObject(hBackBuffer, hPen);

	//проводим линию на экране
	MoveToEx(hBackBuffer, 50, 50, NULL);
	LineTo(hBackBuffer, 200, 200);

	//выбираем в контексте старое перо, удаляем старое перо
	SelectObject(hBackBuffer, hOldPen);
	DeleteObject(hPen);
	
	//выводим готовую картинку на экран
	Present_Backbuffer();
}

int main ()
{
	//получаем HWND консольного окна
	hWnd = GetConsoleWindow();

	Create_Backbuffer();

	//главный цикл рисования линии
	while ( !_kbhit() )
	{
		Draw_Line();
	}

	Delete_Backbuffer();

	return 0;
}	

Вариант #2 создания заднего буфера (используется функция WinAPI DrawDibDraw) и рисования линии, загрузить пример /src/01.003-back_buff/Draw_Line_BackBuff2.


#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <conio.h>

//для DrawDibDraw
#include <vfw.h>
#pragma comment(lib, "Vfw32.lib")

//глобальные переменные
HWND 	hWnd;
LPBYTE m_lpData;
LPBYTE m_lpData_Temp;
BITMAPINFOHEADER m_bih;
HDRAWDIB m_hDD;

UINT nViewWidth;
UINT nViewHeight;

#define BITS_PER_PIXEL	32

void Create_Backbuffer()
{
	RECT rc;
	GetClientRect(hWnd, &rc);

	int nViewX = 0;
	int nViewY = 0;
	nViewWidth = rc.right;
	nViewHeight = rc.bottom;
	
	DWORD m_dwSize = rc.right * (BITS_PER_PIXEL >> 3) * rc.bottom;

	//это наш задний буфер
	m_lpData = (LPBYTE)malloc(m_dwSize*sizeof(BYTE));
	//это задний буфер для перевернутой картинки
	//так как DrawDibDraw рисует перевернутое изображение
	m_lpData_Temp = (LPBYTE)malloc(m_dwSize * sizeof(BYTE));

	memset(&m_bih, 0, sizeof(BITMAPINFOHEADER));
	m_bih.biSize = sizeof(BITMAPINFOHEADER);
	m_bih.biWidth = rc.right;
	m_bih.biHeight = rc.bottom;
	m_bih.biPlanes = 1;
	m_bih.biBitCount = BITS_PER_PIXEL;
	m_bih.biCompression = BI_RGB;
	m_bih.biSizeImage = m_dwSize;
	
	m_hDD = DrawDibOpen();

}


void Clear_Backbuffer()
{
	for ( UINT i = 0; i <  nViewHeight; i++)
	{
		for ( UINT j = 0; j < nViewWidth; j++ )
		{
			int indx = i * 4 * nViewWidth + j * 4;

			m_lpData[indx] = (BYTE) (255.0 * 0.3f); // blue
			m_lpData[indx + 1] = (BYTE) (255.0 * 0.125f); // green
			m_lpData[indx + 2] = 0; // red

			m_lpData[indx + 3] = 0; 
		}
	}
}

void Present_Backbuffer()
{

	//переворачиваем задний буфер

	for (UINT h = 0; h < nViewHeight; h++ )
	{
		for (UINT w = 0; w < nViewWidth; w++)
		{
			int indx = h * 4 * nViewWidth + w * 4;

			BYTE b = m_lpData[indx]; // blue
			BYTE g = m_lpData[indx + 1]; // green
			BYTE r = m_lpData[indx + 2]; // red
			
			int indx_temp = (nViewHeight - 1 - h) * 4 * nViewWidth + w * 4;
			m_lpData_Temp[indx_temp] = b;
			m_lpData_Temp[indx_temp + 1] = g;
			m_lpData_Temp[indx_temp + 2] = r;
			m_lpData_Temp[indx_temp + 3] = 0;
		}
	}

	HDC hDC = GetDC(hWnd);
	DrawDibDraw(m_hDD, hDC, 0, 0, nViewWidth, nViewHeight, &m_bih, m_lpData_Temp, 0, 0, nViewWidth, nViewHeight, 0);
	ReleaseDC(hWnd, hDC);
}


void Delete_Backbuffer()
{
	DrawDibClose(m_hDD);

	free(m_lpData);
	m_lpData = NULL;
}


void Draw_Line()
{
	//очищаем задний буфер от предыдущего изображения
	Clear_Backbuffer();

	RECT rc;
	GetClientRect(hWnd, &rc);

	//рисуем линиию толщиной 20 пикселей и длинной 150 пикселей
	for ( int y = 50; y < 70; y++)
	{
		for ( int x = 100; x < 250; x++)
		{
			int indx =  y * 4 * rc.right + x * 4;
			m_lpData[indx] = 0; // blue
			m_lpData[indx + 1] = 0; // green
			m_lpData[indx + 2] = 255; // red
			m_lpData[indx + 3] = 0; 
		}
	}	
	//выводим готовую картинку на экран
	Present_Backbuffer();
}

int main ()
{
	//получаем HWND консольного окна
	hWnd = GetConsoleWindow();

	Create_Backbuffer();

	//главный цикл рисования линии
	while ( !_kbhit() )
	{
		Draw_Line();
	}

	Delete_Backbuffer();

	return 0;
}	

Вариант #3 создания заднего буфера с использованием DirectX 9, пример можно загрузить /src/01.003-back_buff/Draw_Line_BackBuff3. Для того что бы скомпилировать этот пример вам необходимо иметь пакет DirectX SDK June 2010.

#include <windows.h>

#include <d3dx9.h>
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

HWND hWnd;
LPDIRECT3DDEVICE9 p_d3d_Device;
LPDIRECT3D9 p_d3d;

void Draw_Line(UCHAR *back_buffer, int back_lpitch)
{
	//проводим линию шириной 400 пикслей
	//координатами от x = 100 до x = 500
	//по высоте от y = 150 до y = 160

	UCHAR *screen_ptr  = NULL;
	screen_ptr = back_buffer + (150 * back_lpitch);

	for ( int y = 150; y < 160; y++ )
	{
		int indx =  100 * 4;

		for ( int x = 100; x < 500; x++ )
		{
			screen_ptr[indx] = 127; //blue
			indx++;

			screen_ptr[indx] = 255; //green
			indx++;

			screen_ptr[indx] = 255; //red
			indx++;
			
			screen_ptr[indx] = 0;
			indx++;
		}

		screen_ptr+= back_lpitch;
	}

}

void Render_Scene()
{

	HRESULT hr;

	hr = p_d3d_Device->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB(0, 60, 100, 150), 1.0f, 0 );
	if(FAILED(hr)) return;

	hr = p_d3d_Device->BeginScene();
	if(FAILED(hr)) return;
	
	IDirect3DSurface9* pBackBuffer;
	p_d3d_Device->GetBackBuffer(0,0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer);

	D3DSURFACE_DESC pDesc;
	pBackBuffer->GetDesc(&pDesc);

	int iNumWidth  = pDesc.Width;
	int iNumHeight = pDesc.Height;

	D3DLOCKED_RECT lockedrect;
	::ZeroMemory(&lockedrect, sizeof(lockedrect));
	
	pBackBuffer->LockRect(&lockedrect,NULL,D3DLOCK_NOSYSLOCK);
	
	UCHAR *pSurfBits = static_cast<UCHAR*>(lockedrect.pBits);

	UCHAR *screen_ptr  = NULL;
	screen_ptr = pSurfBits + (150 * lockedrect.Pitch);

	Draw_Line(pSurfBits, lockedrect.Pitch) ;

	pBackBuffer->UnlockRect();

	hr = p_d3d_Device->EndScene();
	if(FAILED(hr)) return;
    
    hr = p_d3d_Device->Present( NULL, NULL, NULL, NULL );
	if(FAILED(hr)) return;


}

LRESULT CALLBACK WndProc(HWND hWnd,
						 UINT uMsg,
						 WPARAM wParam,
						 LPARAM lParam)
{
	switch(uMsg)
	{
		case WM_CLOSE:
			PostQuitMessage(0);
			break;
		default:
			return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}

	return 0;
	
}

int PASCAL WinMain(HINSTANCE hInstance,
				   HINSTANCE hPrevInstance,
					LPSTR lpCmdLine,
					int nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

	WNDCLASS wcl;
	wcl.style = CS_HREDRAW | CS_VREDRAW;
	wcl.lpfnWndProc = WndProc;
	wcl.cbClsExtra = 0L;
	wcl.cbWndExtra = 0L;
	wcl.hInstance = hInstance;
	wcl.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wcl.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcl.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
	wcl.lpszMenuName = NULL;
	wcl.lpszClassName = "Sample";

	if(!RegisterClass(&wcl))
		return 0;

	hWnd = CreateWindow("Sample", "Sample Application",
					WS_OVERLAPPEDWINDOW,
					0, 0,
					640, 480,
					NULL,
					NULL,
					hInstance,
					NULL);
	if(!hWnd)
		return 0;

	p_d3d = Direct3DCreate9 (D3D_SDK_VERSION);

	D3DPRESENT_PARAMETERS d3dpp;
    	ZeroMemory( &d3dpp, sizeof( d3dpp ) );
   		d3dpp.Windowed = TRUE;
    	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    	d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
    	d3dpp.EnableAutoDepthStencil = TRUE;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D24X8;

	d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;

	if( FAILED( p_d3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                      D3DCREATE_HARDWARE_VERTEXPROCESSING,
                                      &d3dpp, &p_d3d_Device ) ) )
    {
		MessageBox(NULL,"Error create device!", "Info",MB_OK);
		return false;
    }


	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	MSG msg;

	while(true)
	{
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if(msg.message ==	WM_QUIT)
				break;
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		if(GetKeyState(VK_ESCAPE) & 0xFF00)
			break;

		Render_Scene();
	}

	DestroyWindow(hWnd);
	UnregisterClass(wcl.lpszClassName, wcl.hInstance);

	return (int)msg.wParam;
}	

В следующем примере мы будем пользоваться задним буфером DX12. Пример можно загрузить /src/01.003-back_buff/Draw_Line_BackBuff4. Пример будет работать на ОС не ниже Win10, так как используется DirectX12 (DirectX12 работает под Win10 ОС не ниже), для создания кода примера использовалась Visual Studio 2019. В этом примере очень сложный код, кто не знаком с программированием DX12, все что нужно знать- это то что есть буфер unsigned char m_BackBuffer - размер буфера 800 * 600 * 4, т.е. по размеру окна приложения 800 на 600, и 4 компоненты RGBA для одного пикселя. В этот буфер мы рисуем нашу картинку. Это происходит в функции Draw_Line(). Потом это буфер мы копируем Back Buffer DX12, который является обыкновенной текстурой. В каждом кадре мы эту текстуру обновляем.

Есть другой способ пользоваться буфером DX12, или DX11 и т.д. и этот способ построен на шейдерах HLSL. Этот способ состоит из следующих шагов (нужно быть немного знакомым с программированием на DirectX):

  1. Создаем буфер unsigned char m_BackBuffer [800*600*4] в который рисуем нашу картинку (в функции инициализации).
  2. Создаем текстуру DirectX размером 800 * 600 RGBA (в функции инициализации).
  3. Создаем так называемый Screen Aligned Quad (SAQ в функции инициализации).
  4. Копируем наш буфер pBuffer в эту текстуру DirectX (в функции update scene).
  5. Текстуру DirectX с нашей картинкой накладываем на этот Screen Alighed Quad т.е. выводим Screen Alighed Quad на экран функцией DirectX DrawPrimitive() (в функции render scene).

Загрузить проект можно /src/01.003-back_buff/Draw_Line_BackBuff5 - DX12 SAQ.

Возможено еще создание заднего буфера при помощи DirectDraw. Пример можно загрузить /src/01.003-back_buff/Draw_Line_BackBuff6, для компиляции вам необходимо Visual Studio 2019 и Windows SDK, или пакет DirectX 9 SDK 2005.

Как компилировать проект Visual Studio при помощи DirectX SDK. Сначала нужно установить DirectX SDK. Потом открыть Visual Studio и в свойствах Visual Studio установить директории INCLUDE/LIB из DirectX SDK. Например это могут быть такие пути на диске:


	C:\Program Files\Microsoft DirectX SDK (June 2010)\Include
	C:\Program Files\Microsoft DirectX SDK (June 2010)\Lib\x86

После указания директорий INCLUDE/LIB из DirectX SDK проект можно компилировать.

Еще один способ создания заднего буфера при помощи библиотеки MegaGraph Graphics Library компании SciTech. Эта библиотека использовалась в 1996 году что бы обеспечить игру Quake 1 задним буфером. Для более подробной информации смотрите исходный код игры Quake 1. Пример можно загрузить /src/01.003-back_buff/Draw_Line_BackBuff7. В примере используется оконный режим работы, проект для VS2019.