1. 介绍
目前D3D版本很多(10、11、12), 本文以及后续的实例都将使用D3D9。
使用D3DAPI之前,首先需要安装DXSDK_Jun10.exe,这在前边的文章有介绍。具体安装非常容易,下载安装包一路下一步即可。
本文目的:本文将介绍如何将一帧YUV(NV12)数据渲染显示。
主要方法:创建离屏表面
2. 基本流程
代码流程:
- 创建D3D对象
- 创建D3D设备
- 创建离屏表面
- 加载YUV数据,将YUV数据拷贝到离屏表面
- 开始渲染
3. 接口介绍
3.1 CreateDevice()
函数原型:
CreateDevice(THIS_ UINT Adapter,
D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS* pPresentationParameters,
IDirect3DDevice9** ppReturnedDeviceInterface);
-
第一个参数:Adapter
代理模式,即将要使用的显卡适配器的标识号,一般选择 D3DADAPTER_DEFAULT 就ok。 -
第二个参数:DeviceType
D3DDEVTYPE_HAL:硬件抽象层,通过显示硬件来完成图形渲染工作
D3DDEVTYPE_REF:参考光栅器,一般用于测试显卡不支持的D3D功能
D3DDEVTYPE_SW:用于支持第三方软件 -
第三个参数:hFocusWindow
创建的窗口句柄,也即图形将出现的窗口。
是焦点窗口,当应用程序从前台模式切换到后台模式的时候向Direct3D提示的窗口。
如果是全屏模式,这个窗口指定的焦点窗口必须是最顶层窗口。
对于窗口模式,这个参数可以是NULL(用的是当前显示的窗口),除非PRESENTPARAMETERS的hDeviceWindow参数是一个合法的非NULL值。
交换链有多个后台缓存的时候,每个后台缓存需要各自hDeviceWindow设备窗口,可以共用一个hFocusWindow焦点窗口(一个焦点窗口可以只给一个后台缓存当做自己的设备窗口)。
- 第四个参数:BehaviorFlags
D3DCREATE_SOFTWARE_VERTEXPROCESSING
由D3D软件进行顶点运算(常用)
D3DCREATE_FPU_PRESERVE
激活双精度浮点运算或浮点运算异常 检测,设置该项会降低系统性能
D3DCREATE_MULTITHREADED
保证D3D是多线程安全的,设置该项 会降低系统性能
D3DCREATE_MIXED_VERTEXPROCESSING
由混合方式进行顶点运算
D3DCREATE_HARDWARE_VERTEXPROCESSING
由D3D硬件进行顶点运算
D3DCREATE_PUREDEVICE
禁用D3D的Get*()函数,禁止D3D 使用虚拟设备模拟顶点运算
注意
D3DCREATE_HARDWARE_VERTEXPROCESSING
D3DCREATE_MIXED_VERTEXPROCESSING
D3DCREATE_SOFTWARE_VERTEXPROCESSING
这三个顶点运算时互斥的,且在该函数中必须被指定一个。
后台缓存只用在D3DPRESENT_PARAMETERS中用D3DPRESENTFLAG_LOCKABLE_BACKBUFFER指定了才能被锁定,多重采样纹理的后台缓存和深度缓存不能被锁定。
-
第五个参数:pPresentationParameters
渲染参数设定,根据需要创建。 -
第六个参数:ppReturnedDeviceInterface
该参数是输出参数,D3D设备,调用这个借口之后,创建成功的D3D设备将使用该参数返回。
3.2 StretchRect()
可以将一个矩形区域的像素从设备内存的一个Surface转移到另一个Surface上。
StretchRect()函数的原型如下
HRESULT StretchRect(
IDirect3DSurface9 * pSourceSurface,
CONST RECT * pSourceRect,
IDirect3DSurface9 * pDestSurface,
CONST RECT * pDestRect,
D3DTEXTUREFILTERTYPE Filter
);
- pSourceSurface
指向源Surface的指针。 - pSourceRect
使用一个 RECT结构体指定源Surface需要复制的区域。如果为NULL的话就是整个区域。 - pDestSurface
指向目标Surface的指针。 - pDestRect
使用一个 RECT结构体指定目标Surface的区域。 - Filter
设置图像大小变换的时候的插值方法。例如: - D3DTEXF_POINT
邻域法。质量较差。 - D3DTEXF_LINEAR
线性插值,最常用。
// 以下代码实例了,如何将离屏表面的内存拷贝到后台缓冲区:
IDirect3DDevice9 *m_pDirect3DDevice;
IDirect3DSurface9 *m_pDirect3DSurfaceRender;
IDirect3DSurface9 * pBackBuffer;
m_pDirect3DDevice->GetBackBuffer(0,0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer);
m_pDirect3DDevice->StretchRect(m_pDirect3DSurfaceRender,NULL,pBackBuffer,&m_rtViewport,D3DTEXF_LINEAR);
// 将离屏表面的数据传给了后台缓冲表面。一但传给了后台缓冲表面,就可以用于显示了。
3.3 BeginScene、EndScene
在D3D中,需要使用BeginScene和EndScene函数,然后所有的图元绘制代码才能被调用。通过了解,需要注意下下面一些问题:
- BeginScene和EndScene必须成对实现。
- BeginScene不能连续调用两次否则会出现调用异常。
- EndScene属于作用是实际应用所有的绘制命令至显卡,但是它是异步执行的,即它会立即返回,但是不保证绘制命令执行完成。
- Present函数会最终等待所有绘制命令结束并将结果从back buffer拷贝到front buffer。
- BeginScene和EndScene之间的绘制代码量可以不要太多,当然太少也不好。这样才能保证最后的present不至于等待大量的CPU时间等显卡完成所有的绘制指令。这样可以在present之前多次使用BeginScene/EndScene队,保证CPU和GPU始终基本同时在工作(并行)。
- 如果确实存在BeginScene/EndScene之间绘制命令过多,可以在EndScene/Present之间添加一些CPU指令,避免CPU在Present时过多时间空等GPU完成绘制返回。
4. 代码
代码仅作为参考。
/**
* 函数调用步骤如下:
*
* [初始化]
* Direct3DCreate9():获得IDirect3D9
* IDirect3D9->CreateDevice():通过IDirect3D9创建Device(设备)。
* IDirect3DDevice9->CreateOffscreenPlainSurface():通过Device创建一个Surface(离屏表面)。
*
* [循环渲染数据]
* IDirect3DSurface9->LockRect():锁定离屏表面。
* memcpy():填充数据
* IDirect3DSurface9->UnLockRect():解锁离屏表面。
* IDirect3DDevice9->BeginScene():开始绘制。
* IDirect3DDevice9->GetBackBuffer():获得后备缓冲。
* IDirect3DDevice9->StretchRect():拷贝Surface数据至后备缓冲。
* IDirect3DDevice9->EndScene():结束绘制。
* IDirect3DDevice9->Present():显示出来。
*/
#include <stdio.h>
#include <tchar.h>
#include <d3d9.h>
#include <windows.h>
#include <stdio.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <tchar.h>
#include <time.h>
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib")
#pragma comment(lib,"dxguid.lib")
#pragma comment(lib, "winmm.lib")
CRITICAL_SECTION m_critial;
IDirect3D9* m_pDirect3D9 = NULL;
IDirect3DDevice9* m_pDirect3DDevice = NULL;
IDirect3DSurface9* m_pDirect3DSurfaceRender = NULL;
RECT m_rtViewport;
#define LOAD_BGRA 0
#define LOAD_YUV420P 1
const int screen_w = 1000, screen_h = 600;
const int pixel_w = 1920, pixel_h = 1080;
FILE* fp = NULL;
unsigned char buffer[pixel_w * pixel_h * bpp / 8];
void Cleanup()
{
EnterCriticalSection(&m_critial);
if (m_pDirect3DSurfaceRender)
m_pDirect3DSurfaceRender->Release();
if (m_pDirect3DDevice)
m_pDirect3DDevice->Release();
if (m_pDirect3D9)
m_pDirect3D9->Release();
LeaveCriticalSection(&m_critial);
}
int InitD3D(HWND hwnd, unsigned long lWidth, unsigned long lHeight)
{
HRESULT lRet;
InitializeCriticalSection(&m_critial);
Cleanup();
m_pDirect3D9 = Direct3DCreate9(D3D_SDK_VERSION);
if (m_pDirect3D9 == NULL)
return -1;
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
GetClientRect(hwnd, &m_rtViewport);
lRet = m_pDirect3D9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &m_pDirect3DDevice);
if (FAILED(lRet))
return -1;
#if LOAD_BGRA
lRet = m_pDirect3DDevice->CreateOffscreenPlainSurface(
lWidth, lHeight,
D3DFMT_X8R8G8B8,
D3DPOOL_DEFAULT,
&m_pDirect3DSurfaceRender,
NULL);
#elif LOAD_YUV420P
lRet = m_pDirect3DDevice->CreateOffscreenPlainSurface(
lWidth, lHeight,
(D3DFORMAT)MAKEFOURCC('N', 'V', '1', '2'),
D3DPOOL_DEFAULT,
&m_pDirect3DSurfaceRender,
NULL);
#endif
if (FAILED(lRet))
return -1;
return 0;
}
bool Render()
{
HRESULT lRet;
if (fread(buffer, 1, pixel_w * pixel_h * bpp / 8, fp) != pixel_w * pixel_h * bpp / 8) {
fseek(fp, 0, SEEK_SET);
fread(buffer, 1, pixel_w * pixel_h * bpp / 8, fp);
}
if (m_pDirect3DSurfaceRender == NULL)
return -1;
D3DLOCKED_RECT d3d_rect;
lRet = m_pDirect3DSurfaceRender->LockRect(&d3d_rect, NULL, D3DLOCK_DONOTWAIT);
if (FAILED(lRet))
return -1;
byte* pSrc = buffer;
byte* pDest = (BYTE*)d3d_rect.pBits;
int stride = d3d_rect.Pitch;
unsigned long i = 0;
//Copy Data
#if LOAD_BGRA
int pixel_w_size = pixel_w * 4;
for (i = 0; i < pixel_h; i++) {
memcpy(pDest, pSrc, pixel_w_size);
pDest += stride;
pSrc += pixel_w_size;
}
#elif LOAD_YUV420P
for (i = 0; i < pixel_h; i++) {
memcpy(pDest + i * stride, pSrc + i * pixel_w, pixel_w);
}
for (i = 0; i < pixel_h / 2; i++) {
memcpy(pDest + stride * pixel_h + i * stride / 2, pSrc + pixel_w * pixel_h + pixel_w * pixel_h / 4 + i * pixel_w / 2, pixel_w / 2);
}
for (i = 0; i < pixel_h / 2; i++) {
memcpy(pDest + stride * pixel_h + stride * pixel_h / 4 + i * stride / 2, pSrc + pixel_w * pixel_h + i * pixel_w / 2, pixel_w / 2);
}
#endif
lRet = m_pDirect3DSurfaceRender->UnlockRect();
if (FAILED(lRet))
return -1;
if (m_pDirect3DDevice == NULL)
return -1;
m_pDirect3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 0, 0), 1.0f, 0);
m_pDirect3DDevice->BeginScene();
IDirect3DSurface9* pBackBuffer = NULL;
m_pDirect3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer);
m_rtViewport.left = 100;
m_rtViewport.top = 100;
m_rtViewport.right = 600;
m_rtViewport.bottom = 400;
m_pDirect3DDevice->StretchRect(m_pDirect3DSurfaceRender, NULL, pBackBuffer, &m_rtViewport, D3DTEXF_LINEAR);
m_pDirect3DDevice->EndScene();
m_pDirect3DDevice->Present(NULL, NULL, NULL, NULL);
pBackBuffer->Release();
return true;
}
LRESULT WINAPI MyWndProc(HWND hwnd, UINT msg, WPARAM wparma, LPARAM lparam)
{
switch (msg) {
case WM_DESTROY:
Cleanup();
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wparma, lparam);
}
int WINAPI WinMain(__in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in LPSTR lpCmdLine, __in int nShowCmd)
{
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpfnWndProc = (WNDPROC)MyWndProc;
wc.lpszClassName = L"D3D";
wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClassEx(&wc);
HWND hwnd = NULL;
hwnd = CreateWindow(L"D3D", L"DireectX3D render YUV", WS_OVERLAPPEDWINDOW, 100, 100, 800, 600, NULL, NULL, hInstance, NULL);
if (hwnd == NULL) {
return -1;
}
if (InitD3D(hwnd, pixel_w, pixel_h) == E_FAIL) {
return -1;
}
ShowWindow(hwnd, nShowCmd);
UpdateWindow(hwnd);
#if LOAD_BGRA
fp = fopen("../test_bgra_320x180.rgb", "rb+");
#elif LOAD_YUV420P
fp = fopen("./12_1920_1088.yuv", "rb+");
#endif
if (fp == NULL) {
printf("Cannot open this file.\n");
return -1;
}
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
Sleep(400);
Render();
}
}
UnregisterClass(L"D3D", hInstance);
return 0;
}
参考资料
[1] http://www.voidcn.com/article/p-egqvdlga-bdz.html
[2] https://blog.csdn.net/yaoxinchao/article/details/7984820
[3] https://blog.csdn.net/cnjet/article/details/5950483?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param