【DirectX3D - 2】渲染YUV图片(离屏表面方式)

1. 介绍

目前D3D版本很多(10、11、12), 本文以及后续的实例都将使用D3D9。
使用D3DAPI之前,首先需要安装DXSDK_Jun10.exe,这在前边的文章有介绍。具体安装非常容易,下载安装包一路下一步即可。

本文目的:本文将介绍如何将一帧YUV(NV12)数据渲染显示。
主要方法:创建离屏表面

2. 基本流程

代码流程:

  1. 创建D3D对象
  2. 创建D3D设备
  3. 创建离屏表面
  4. 加载YUV数据,将YUV数据拷贝到离屏表面
  5. 开始渲染

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函数,然后所有的图元绘制代码才能被调用。通过了解,需要注意下下面一些问题:

  1. BeginScene和EndScene必须成对实现。
  2. BeginScene不能连续调用两次否则会出现调用异常。
  3. EndScene属于作用是实际应用所有的绘制命令至显卡,但是它是异步执行的,即它会立即返回,但是不保证绘制命令执行完成。
  4. Present函数会最终等待所有绘制命令结束并将结果从back buffer拷贝到front buffer。
  5. BeginScene和EndScene之间的绘制代码量可以不要太多,当然太少也不好。这样才能保证最后的present不至于等待大量的CPU时间等显卡完成所有的绘制指令。这样可以在present之前多次使用BeginScene/EndScene队,保证CPU和GPU始终基本同时在工作(并行)。
  6. 如果确实存在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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值