Как добавить задний буфер
Загрузить архив с примерами ЗДЕСЬ.
Мы не использовали backbuffer (задний буфер) для наглядности примеров, вы можете сами добавить backbuffer. Назначение заднего буфера следующее- когда мы прямо в видеопамять рисуем изображение- оно обычно мерцает на дисплее, так как мы рисуем в видеопамять, и паралельно дисплей читает эту видепамять и отображает на экране. Например у нас частота обновления дисплея 60 Герц. Это значит что дисплей обновляет изображение на экране (дисплей читает видеопамять) 60 раз в секунду. В тот момент когда дисплей читает видеопамять что бы обновить изображение на экране, мы к примеру проводим линию, и если правильно- то линия рисуется в видеобуфере, и изображение выглядит искаженным или мерцающим, так как дислей читает видеопамять и может быть где то по средине видеопамяти, и мы в этот момент рисуем линию, другую, третью - то есть заполняем эту видеопамять которую в этот момент читает дисплей- поэтому возникает мерцание. Что бы избежать мерцания изображения при выводе используют следующий метод- сначала все изображение рисуют в участок памяти так называемый задний буфер, а потом уже готовое изображение копируют в видеопамять. Этот подход позволяет избежать мерцания. Задний буфер будет использоватся в проектах в следующих главах.
Ниже приведено два варианта создания заднего буфера для графических приложений WinAPI.
Вариант создания заднего буфера (используется функция 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; }
Еще один вариант создания заднего буфера (используется функция WinAPI BitBlt и CreateDIBSection) и рисования линии, загрузить приложение, код ниже, можно /src/01.003-back_buff/Draw_Line_BackBuff2.
Вариант создания заднего буфера (используется функция WinAPI DrawDibDraw) и рисования линии, загрузить пример /src/01.003-back_buff/Draw_Line_BackBuff3.
#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; }Вариант создания заднего буфера с использованием DirectX 9, пример можно загрузить /src/01.003-back_buff/Draw_Line_BackBuff4. Для того что бы скомпилировать этот пример вам необходимо иметь пакет 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_BackBuff5. Пример будет работать на ОС не ниже 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):
Загрузить проект можно /src/01.003-back_buff/Draw_Line_BackBuff6 - DX12 SAQ.
Возможено еще создание заднего буфера при помощи DirectDraw. Пример можно загрузить /src/01.003-back_buff/Draw_Line_BackBuff7, для компиляции вам необходимо 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_BackBuff8 (24 бит глубина заднего буфера) и /src/01.003-back_buff/Draw_Line_BackBuff9 (8 бит глубина заднего буфера). В примере используется оконный режим работы, проект для VS2019.