由于工作需要处理视频流,对处理速度要求比较高。已经完成的图像处理的代码(有空补上博客)处理每帧(3*1920*1080)串行需要160ms左右,不能满足要求,所以在别人推荐下开始研究并行处理计算---DirectX。大致想法是想利用里面的渲染和着色器等功能实现图像处理的算法,由于是并行计算,所以速度肯定要快很多。
昨天开始大致看了很多博客文章还有几本书,现在这儿推荐两个博客
毛星云的DirectX游戏开发
X_Jun很详细的教程
大牛的博客只是浏览了一下,因为自己不太需要那么多功能。但是觉得还是模模糊糊,没有写过代码始终理解不上去。于是今天开始跟着一本书《Beginning DirectX11 Game Programming》的第二章,整合里面的例子,加上一些自己的注释。水平不高,注释一个学习笔记吧。以前做完事情都没有立即写,今天做完决心今天就写完这篇博客
DX的配置问题比较简单,有过常见库配置经验的都能够很简单完成。看了很多的文章,似乎较新版本的VS和系统已经集成了DX库在Windows Kit目录中,也不用另外安装。此处不再赘述,有需要的可以再查一查。
#include
#include
#include
//系统消息处理函数
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 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(msg.wParam);
}
主程序就是一个基本的win32下面的窗口函数。简单来说就是先声明一个新窗口,对窗口这个类继续赋值,比如大小,名字,回调函数等等,然后注册窗口,再创造该窗口。这些代码都很常见,也是模块化的,不容易出错。除去以上代码段中有关DX的几个语句,再编译运行,显示出来的就是一个空白窗口。
头文件包含的主要是DX主要头文件,在自己编译运行过程中,由于自己之前设置的库的问题,会报错,所以自己就把链接库也加上了。DX功能对应的库自己还不太熟悉,这个坑以后再来填。
#ifndef _DEMO_BASE_H_
#define _DEMO_BASE_H_
#include
#include
#include
#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(width);
viewport.Height = static_cast(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的功能实现算法。明天继续学习这部分内容,希望自己有空就把自己做过的写成博客,这样就可以越写越好了。
在写博客的时候有些内容没有想到,文章也一般,以后若是想到问题再回来修改。