源代碼下載
簡單說說
由於工作需要處理視頻流,對處理速度要求比較高。已經完成的圖像處理的代碼(有空補上博客)處理每幀(3*1920*1080)串行需要160ms左右,不能滿足要求,所以在別人推薦下開始研究並行處理計算---DirectX。大致想法是想利用裏面的渲染和着色器等功能實現圖像處理的算法,由於是並行計算,所以速度肯定要快很多。
昨天開始大致看了很多博客文章還有幾本書,現在這兒推薦兩個博客
大牛的博客只是瀏覽了一下,因爲自己不太需要那麼多功能。但是覺得還是模模糊糊,沒有寫過代碼始終理解不上去。於是今天開始跟着一本書《Beginning DirectX11 Game Programming》的第二章,整合裏面的例子,加上一些自己的註釋。水平不高,註釋一個學習筆記吧。以前做完事情都沒有立即寫,今天做完決心今天就寫完這篇博客
DirectX配置
DX的配置問題比較簡單,有過常見庫配置經驗的都能夠很簡單完成。看了很多的文章,似乎較新版本的VS和系統已經集成了DX庫在Windows Kit目錄中,也不用另外安裝。此處不再贅述,有需要的可以再查一查。
主程序
#include<Windows.h>
#include<memory>
#include<dx11demo.h>
//系統消息處理函數
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT paintStruct;
HDC hDC;
switch (message)
{
case WM_PAINT:
hDC = BeginPaint(hwnd, &paintStruct);
EndPaint(hwnd, &paintStruct);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
LPWSTR cmdLine, int cmdShow)
{
UNREFERENCED_PARAMETER(prevInstance); //避免對於未使用變量的系統警告
UNREFERENCED_PARAMETER(cmdLine);
WNDCLASSEX wndClass = { 0 }; //窗口的類WNDCLASSEX,並初始化
wndClass.cbSize = sizeof(WNDCLASSEX); //窗口大小
wndClass.style = CS_HREDRAW | CS_VREDRAW; //窗口風格
wndClass.lpfnWndProc = WndProc; //回調函數,處理系統發來的時間消息
wndClass.hInstance = hInstance;
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = "DX11BookWindowClass"; //窗口的名字
if (!RegisterClassEx(&wndClass)) //註冊窗口,創造窗口前必須註冊
return -1;
RECT rc = { 0,0,640,480 };
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
HWND hwnd = CreateWindowA("DX11BookWindowClass", "Blank Win32 Window",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left,
rc.bottom - rc.top, NULL, NULL, hInstance, NULL); //創造窗口
if (!hwnd)
return -1;
ShowWindow(hwnd, cmdShow); //顯示窗口
std::auto_ptr<Dx11DemoBase> demo(new Dx11DemoBase());
// 初始化
bool result = demo->Initialize(hInstance, hwnd);
if (!result)
return -1;
//Demo 初始化
MSG msg = { 0 };
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
//更新 畫圖
demo->Update(0.0f);
demo->Render();
}
}
//Demo 關閉
demo->Shutdown();
return static_cast<int>(msg.wParam);
}
主程序就是一個基本的win32下面的窗口函數。簡單來說就是先聲明一個新窗口,對窗口這個類繼續賦值,比如大小,名字,回調函數等等,然後註冊窗口,再創造該窗口。這些代碼都很常見,也是模塊化的,不容易出錯。除去以上代碼段中有關DX的幾個語句,再編譯運行,顯示出來的就是一個空白窗口。
DX的初始化
頭文件包含的主要是DX主要頭文件,在自己編譯運行過程中,由於自己之前設置的庫的問題,會報錯,所以自己就把鏈接庫也加上了。DX功能對應的庫自己還不太熟悉,這個坑以後再來填。
#ifndef _DEMO_BASE_H_
#define _DEMO_BASE_H_
#include<d3d11.h>
#include<d3dx11.h>
#include<DxErr.h>
#pragma comment(lib, "DXGI.lib")
#pragma comment(lib, "D3D11.lib")
#pragma comment(lib, "D3DX11.lib")
#pragma comment(lib, "D3DX10.lib")
#pragma comment(lib, "dxerr.lib")
#pragma comment(lib, "legacy_stdio_definitions.lib")
class Dx11DemoBase
{
public:
Dx11DemoBase();
virtual ~Dx11DemoBase();
bool Initialize(HINSTANCE hInstance, HWND hwnd); //初始化
void Shutdown(); //關閉釋放
bool LoadContent(); //加載
void UnloadContent(); //卸載
void Update(float dt); //更新
void Render(); //渲染
protected:
HINSTANCE hInstance_; //窗口實例
HWND hwnd_; //窗口句柄
D3D_DRIVER_TYPE driverType_; //設備類型
D3D_FEATURE_LEVEL featureLevel_; //特徵等級
ID3D11Device* d3dDevice_; //D3D設備實際對象
ID3D11DeviceContext* d3dContext_; //上下文,理解爲接口更好
IDXGISwapChain* swapChain_; //交換鏈
ID3D11RenderTargetView* backBufferTarget_; //後緩衝區目標視圖
};
#endif
頭文件就不說了,主要是定義了一些基本的函數,和一些DX的變量,比如設備號之類和feature level,看完初始化,再去看這些東西印象會更深刻一些。下面開始來說一下DX初始化的流程和代碼段。
bool Dx11DemoBase::Initialize(HINSTANCE hInstance, HWND hwnd)
hInstance_ = hInstance; //獲得當前實例
hwnd_ = hwnd; //獲取當前窗口句柄
RECT dimensions;
GetClientRect(hwnd, &dimensions); //獲取當前client area大小(窗口大小)
初始化函數傳入就是當前窗口的實例和句柄,因爲顯示在原來定義的窗口上顯示。GetClientRect函數可以獲得窗口的大小,RECT結構表示的是窗口的四個點座標。
設置設備類型和特徵等級
//設置設備類型和特徵等級(feature level)
D3D_DRIVER_TYPE driverTypes[] =
//設備類型(以下包括硬件,軟件,WARP,參考四種)
{
D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP,
D3D_DRIVER_TYPE_REFERENCE, D3D_DRIVER_TYPE_SOFTWARE
};
unsigned int totalDriverTypes = ARRAYSIZE(driverTypes); //ARRAYSIZE返回數組大小
D3D_FEATURE_LEVEL featureLevels[] = //feature level不同的硬件支持不同
{
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0
};
unsigned int totalFeatureLevels = ARRAYSIZE(featureLevels); //ARRAYSIZE返回數組大小
D3D_DRIVER_TYPE和D3D_FEATURE_LEVEL分別就是設備類型和設備特徵等級。設備包括軟件、硬件、參考和WARP四種設備,特徵等級包括這三種。這裏用數組存儲了,是因爲後面讀取不同的電腦硬件時,可能會遇到支持的設備類型和等級不同,需要循環設置。只要有一個設備類型和等級,就可以成功創建設備和後續的交換鏈。
設備和交換鏈創建
這個代碼段主要是完成了交換鏈這個東西的一些參數的賦值,交換鏈是一個新概念,但是理解上來還是比較簡單的。
//設備及交換鏈創建
DXGI_SWAP_CHAIN_DESC swapChainDesc; //變量類型爲 交換鍊形容子(swap chain description)
ZeroMemory(&swapChainDesc, sizeof(swapChainDesc)); //用零填充該變量的所有區域
swapChainDesc.BufferCount = 1; //緩衝區數(此處猜測爲1,則有兩個緩衝區,2有個)
swapChainDesc.BufferDesc.Width = width;
swapChainDesc.BufferDesc.Height = height; //緩衝區存儲的大小(和要顯示的一致)
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; //緩衝區存儲圖像的格式()
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60; //刷新率,60/1表示60Hz
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; //緩衝區使用
swapChainDesc.OutputWindow = hwnd; //窗口,就是win32要做顯示的窗口句柄
swapChainDesc.Windowed = true; //決定保存窗口或者到全屏的變量
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0; //sample description
交換鏈:交換始於前緩衝區和後緩衝區的交換。在顯示器的顯示過程中,一般還會有渲染處理的過程。前緩衝區中存儲的就是現在正在顯示的圖像,後緩衝區是處理結束等待顯示的下一張圖像。當顯示下一張圖像時,前後緩衝區就會交換,前變成後,後變成前。在DX裏面,形容交換鏈的容器就是DXGI_SWAP_CHAIN_DESC。值得注意的是,後緩衝區可以是兩個也可以是一個。
上面代碼裏註釋都比較詳細,也沒有太多解釋和說明的,還有問題可以參考文章開頭給出的那本書。
下面代碼就是創建設備和交換鏈了,for循環檢索之前給的數組,有一個存在,就可以創建成功。當然,如果熟悉自己的硬件支持的設備和等級,也可以直接使用那個函數創建。
for (driver = 0; driver < totalDriverTypes; ++driver)
//循環表示對所有類型設備都嘗試創建,有一個設備存在和特徵level存在,創建成功
{
//D3D11CreateDeviceAndSwapChain()創建交換鏈,設備,渲染context
result = D3D11CreateDeviceAndSwapChain(0, driverTypes[driver], 0, creationFlags,
featureLevels, totalFeatureLevels,
D3D11_SDK_VERSION, &swapChainDesc, &swapChain_,
&d3dDevice_, &featureLevel_, &d3dContext_);
if (SUCCEEDED(result))
{
driverType_ = driverTypes[driver];
break;
}
}
if (FAILED(result))
{
DXTRACE_MSG("創建 Direct3D 設備失敗!");
return false;
}
//渲染目標視圖創建Render Target View Creation
ID3D11Texture2D* backBufferTexture;
result = swapChain_->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferTexture);
if (FAILED(result))
{
DXTRACE_MSG("獲取交換鏈後臺緩存失敗!");
return false;
}
//創建渲染目標視圖
result = d3dDevice_->CreateRenderTargetView(backBufferTexture, 0, &backBufferTarget_);
if (backBufferTexture)
backBufferTexture->Release(); //釋放緩衝區指針,防止內存泄漏
if (FAILED(result))
{
DXTRACE_MSG("創建渲染目標視圖失敗!");
return false;
}
//渲染到一個固定視圖
d3dContext_->OMSetRenderTargets(1, &backBufferTarget_, 0);
//視窗創建(定義了要顯示的視窗範圍)
D3D11_VIEWPORT viewport;
viewport.Width = static_cast<float>(width);
viewport.Height = static_cast<float>(height); //高度和寬度
viewport.MinDepth = 0.0f; //深度範圍
viewport.MaxDepth = 1.0f;
viewport.TopLeftX = 0.0f; //視窗的頂部左座標
viewport.TopLeftY = 0.0f;
d3dContext_->RSSetViewports(1, &viewport); //設置到視窗範圍
初始化剩下的部分就是比較簡單:獲取內存,創建目標視圖數據,顯示等等。。這兒涉及到幾個device,context,發現一篇文章簡單的說明了一下,可以參考。
代碼主要側重點還是在設備的初始化部分,所以更深奧的部分還沒有涉及到。整理的渲染函數裏,就只是簡單的讓窗口顯示一個給定的顏色,由clearColor給定。另外一定是,顯示設備一般實際上有四個通道,分別是RGB和alpha,最後一個是透明度。
在render函數中,修改clearColor就可以得到不同的窗口顯示顏色。
void Dx11DemoBase::Render()
{
if (d3dContext_ == 0)
return;
float clearColor[4] = { 0.5f, 0.5f, 0.25f, 1.0f };
d3dContext_->ClearRenderTargetView(backBufferTarget_, clearColor); //清除屏幕,顯示一個特別的顏色
swapChain_->Present(0, 0); //顯示後緩衝區的渲染場景,也就是新的
}
在render函數中,修改clearColor就可以得到不同的窗口顯示顏色,如下圖所示。
總結
本文簡單介紹了一些D3D初始化的代碼,來源於《Beginning DirectX11 Game Programming》的第二章,簡單給代碼加上了註釋,並寫了一些自己的理解。初始化部分: 獲取當前設備的類型和特徵等級,設置交換鏈的參數,創建設備,設置後緩衝區內存等等。每一步都需要D3D11庫裏面的函數實現,主要的函數已經包括在代碼中,這部分函數知道固定的輸入參數即可。我覺得我將面臨的難點還是在後面移植算法過程中,如何用D3D11的功能實現算法。明天繼續學習這部分內容,希望自己有空就把自己做過的寫成博客,這樣就可以越寫越好了。
在寫博客的時候有些內容沒有想到,文章也一般,以後若是想到問題再回來修改。