【使用D3D11直接渲染YUV数据】

使用D3D11直接渲染YUV数据

最初学习D3D11时,采取的是直接渲染RGB数据的方式,因为采集的时候采出来的是YUV420格式,需要利用libyuv库把YUVI420转成RGB格式。但是在实际项目中,这种转换会非常消耗CPU性能,因此需要寻求一种能够直接渲染YUV数据方式。

目前D3D11直接渲染YUV的主流方式有两种,第一种是创建三个纹理,用着色器转成RGB数据渲染(这部分转换操作应该是在GPU上进行的);第二种是D3D11.1之后支持直接渲染YUV数据。这里采取的是第一种方式,废话不多说,直接上代码。

bool D3D11Render::InitDirect3d()
{
	HRESULT hr = S_OK;
	//创建d3d设备及设备上下文

	//驱动类型数组
	D3D_DRIVER_TYPE driverTypes[] = {
		D3D_DRIVER_TYPE_HARDWARE,  //硬件驱动
		D3D_DRIVER_TYPE_WARP,	   //WARP驱动
		D3D_DRIVER_TYPE_REFERENCE, //软件驱动
	};
	UINT numDriverTypes = ARRAYSIZE(driverTypes);

	//特性等级数组
	D3D_FEATURE_LEVEL featureLevels[] = {
		D3D_FEATURE_LEVEL_11_1,
		D3D_FEATURE_LEVEL_11_0,
	};
	UINT numFeatureLevels = ARRAYSIZE(featureLevels);

	D3D_FEATURE_LEVEL featureLevel;
	D3D_DRIVER_TYPE d3dDriverType;
	for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++) {
		d3dDriverType = driverTypes[driverTypeIndex];
		hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, 0, featureLevels, numFeatureLevels, D3D11_SDK_VERSION,
			m_pd3dDevice.GetAddressOf(), &featureLevel, m_pd3dImmediateContex.GetAddressOf());
		if (hr == E_INVALIDARG) {
			//Direct3D 11.0的API不承认D3D_FEATURE_LEVEL_11_1,因此需要尝试D3D_FEATURE_LEVEL_11_0及其以下版本
			hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, 0, &featureLevels[1], numFeatureLevels - 1, D3D11_SDK_VERSION,
				m_pd3dDevice.GetAddressOf(), &featureLevel, m_pd3dImmediateContex.GetAddressOf());
		}
		if (SUCCEEDED(hr)) break;
	}
	if (FAILED(hr)) {
		std::cout << "Create Direct3D device failed." << std::endl;
		return false;
	}

	//检测是否支持特性等级11.0或者11.1
	if (featureLevel != D3D_FEATURE_LEVEL_11_1 && featureLevel != D3D_FEATURE_LEVEL_11_0) {
		std::cout << "Direct3D feature level 11 not support." << std::endl;
		return false;
	}
	//检测MSAA支持的质量等级
	m_pd3dDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_B8G8R8A8_UNORM, 4, &m_4xMsaaQuality);
	assert(m_4xMsaaQuality > 0);

	ComPtr<IDXGIDevice> dxgiDevice = nullptr;
	ComPtr<IDXGIAdapter> dxgiAdapter = nullptr;
	ComPtr<IDXGIFactory1> dxgiFactory1 = nullptr; 
	ComPtr<IDXGIFactory2> dxgiFactory2 = nullptr; 

	hr = m_pd3dDevice.As(&dxgiDevice);
	if (FAILED(hr)) {
		std::cout << "m_pd3dDevice.As(&dxgiDevice) failed." << std::endl;
		LogErrorText(hr);
		return false;
	}
	hr = dxgiDevice->GetAdapter(&dxgiAdapter);
	if (FAILED(hr)) {
		std::cout << "GetAdapter failed." << std::endl;
		LogErrorText(hr);
		return false;
	}
	hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(dxgiFactory1.GetAddressOf()));
	if (FAILED(hr)) {
		std::cout << "GetParent failed." << std::endl;
		LogErrorText(hr);
		return false;
	}

	hr = dxgiFactory1.As(&dxgiFactory2);
	if (dxgiFactory2 != nullptr) {
		hr = m_pd3dDevice.As(&m_pd3dDevice1);
		if (FAILED(hr)) {
			std::cout << "m_pd3dDevice.As(&m_pd3dDevice1) failed." << std::endl;
			LogErrorText(hr);
			return false;
		}
		m_pd3dImmediateContex.As(&m_pd3dImmediateContex1);
		if (FAILED(hr)) {
			std::cout << "m_pd3dImmediateContex.As(&m_pd3dImmediateContex1) failed." << std::endl;
			LogErrorText(hr);
			return false;
		}
		//创建11.1 Swap Chain
		DXGI_SWAP_CHAIN_DESC1 sd;
		ZeroMemory(&sd, sizeof(sd));
		sd.Width = m_width; 
		sd.Height = m_height; 
		sd.Format = DXGI_FORMAT_B8G8R8A8_UNORM; 
		if (m_Enable4xMsaa) {
			sd.SampleDesc.Count = 4; 
			sd.SampleDesc.Quality = m_4xMsaaQuality - 1; 
		}
		else {
			sd.SampleDesc.Count = 1;
			sd.SampleDesc.Quality = 0;
		}
		sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 
		sd.BufferCount = 1;								 
		sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;		  
		sd.Flags = 0;									
		

		DXGI_SWAP_CHAIN_FULLSCREEN_DESC fd;
		fd.RefreshRate.Denominator = 1; 
		fd.RefreshRate.Numerator = 60; 
		fd.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
		fd.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
		fd.Windowed = TRUE; 

		hr = dxgiFactory2->CreateSwapChainForHwnd(m_pd3dDevice.Get(), m_hwnd, &sd, &fd, nullptr, m_pSwapChain1.GetAddressOf());
		if (FAILED(hr)) {
			std::cout << "CreateSwapChain1 failed." << std::endl;
			LogErrorText(hr);
			return false;
		}
		hr = m_pSwapChain1.As(&m_pSwapChain);
		if (FAILED(hr)) {
			std::cout << "m_pSwapChain1.As(&m_pSwapChain) failed." << std::endl;
			LogErrorText(hr);
			return false;
		}
	}
	else {
		//创建11.0 Swap Chain
		DXGI_SWAP_CHAIN_DESC sd;
		ZeroMemory(&sd, sizeof(sd));
		sd.BufferDesc.Width = m_width;
		sd.BufferDesc.Height = m_height;
		sd.BufferDesc.RefreshRate.Numerator = 60;
		sd.BufferDesc.RefreshRate.Denominator = 1;
		sd.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
		sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
		sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
		if (m_Enable4xMsaa) {
			sd.SampleDesc.Count = 4; 
			sd.SampleDesc.Quality = m_4xMsaaQuality - 1; 
		}
		else {
			sd.SampleDesc.Count = 1;
			sd.SampleDesc.Quality = 0;
		}
		sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		sd.BufferCount = 1;
		sd.OutputWindow = m_hwnd;
		sd.Windowed = TRUE;
		sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
		sd.Flags = 0;

		hr = dxgiFactory1->CreateSwapChain(m_pd3dDevice.Get(), &sd, m_pSwapChain.GetAddressOf());
		if (FAILED(hr)) {
			std::cout << "CreateSwapChain failed." << std::endl;
			LogErrorText(hr);
			return false;
		}
	}
	dxgiFactory1->MakeWindowAssociation(m_hwnd, DXGI_MWA_NO_ALT_ENTER);

	//创建三个YUV纹理
	D3D11_TEXTURE2D_DESC textureDesc;
	textureDesc.Width = m_width;
	textureDesc.Height = m_height;
	textureDesc.MipLevels = 1; 
	textureDesc.ArraySize = 1; 
	textureDesc.Format = DXGI_FORMAT_R8_UNORM; 
	if (m_Enable4xMsaa) {
		textureDesc.SampleDesc.Count = 4;
		textureDesc.SampleDesc.Quality = m_4xMsaaQuality - 1;
	}
	else {
		textureDesc.SampleDesc.Count = 1;
		textureDesc.SampleDesc.Quality = 0;
	}
	textureDesc.Usage = D3D11_USAGE_DEFAULT;
	textureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
	textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; 
	textureDesc.MiscFlags = 0; 
	hr = m_pd3dDevice->CreateTexture2D(&textureDesc, nullptr, texturePlanes[0].GetAddressOf()); 
	if (FAILED(hr)) {
		std::cout << "CreateTexture2D failed." << std::endl;
		LogErrorText(hr);
		return false;
	}
	textureDesc.Width = m_width / 2;
	textureDesc.Height = m_height / 2;
	hr = m_pd3dDevice->CreateTexture2D(&textureDesc, nullptr, texturePlanes[1].GetAddressOf()); 
	if (FAILED(hr)) {
		std::cout << "CreateTexture2D failed." << std::endl;
		LogErrorText(hr);
		return false;
	}
	hr = m_pd3dDevice->CreateTexture2D(&textureDesc, nullptr, texturePlanes[2].GetAddressOf()); 
	if (FAILED(hr)) {
		std::cout << "CreateTexture2D failed." << std::endl;
		LogErrorText(hr);
		return false;
	}
	D3D11_SHADER_RESOURCE_VIEW_DESC resourceviewDesc;
	resourceviewDesc.Format = DXGI_FORMAT_R8_UNORM;
	resourceviewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
	resourceviewDesc.Texture2D.MipLevels = 1u;
	resourceviewDesc.Texture2D.MostDetailedMip = 0u;

	for (int i = 0; i < 3; i++) {
		hr = m_pd3dDevice->CreateShaderResourceView(texturePlanes[i].Get(), &resourceviewDesc, reourceviewPlaner[i].GetAddressOf());
		if (FAILED(hr)) {
			std::cout << "CreateShaderResourceView failed." << std::endl;
			LogErrorText(hr);
			return false;
		}
	}
	
	hr = m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf())); 
	if (FAILED(hr)) {
		std::cout << "GetBuffer failed." << std::endl;
		LogErrorText(hr);
		return false;
	}
	hr = m_pd3dDevice->CreateRenderTargetView(backBuffer.Get(), nullptr, m_pRenderTargetView.GetAddressOf()); 
	if (FAILED(hr)) {
		std::cout << "CreateRenderTargetView failed." << std::endl;
		LogErrorText(hr);
		return false;
	}
	m_pd3dImmediateContex->OMSetRenderTargets(1, m_pRenderTargetView.GetAddressOf(), nullptr);

	m_ScreenViewPort.TopLeftX = 0; 
	m_ScreenViewPort.TopLeftY = 0; 
	m_ScreenViewPort.Width = static_cast<float>(m_width);
	m_ScreenViewPort.Height = static_cast<float>(m_height);
	m_ScreenViewPort.MinDepth = 0.0f; 
	m_ScreenViewPort.MaxDepth = 1.0f; 
	m_pd3dImmediateContex->RSSetViewports(1, &m_ScreenViewPort); 
	SetWindowPos(m_hwnd, NULL, 0, 0, m_width, m_height, SWP_NOZORDER);

	std::cout << "Initialize D3D11 success." << std::endl;
	return true;
}

上面的过程主要是初始化D3D,包括D3D设备,上下文,交换链的创建,并且这里创建了三个纹理,分别对应Y、U、V数据,注意,因为U和V的宽高是Y的一半,创建U和V的纹理时需要把宽高除2。

//创建着色器
bool D3D11Render::InitEffect()
{
	HRESULT hr = S_OK;
	ComPtr<ID3DBlob> blob;

	D3D11_SAMPLER_DESC sampleDesc;
	ZeroMemory(&sampleDesc, sizeof(sampleDesc));
	sampleDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	sampleDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
	sampleDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
	sampleDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
	sampleDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
	sampleDesc.MinLOD = 0;
	sampleDesc.MaxLOD = D3D11_FLOAT32_MAX;
	hr = m_pd3dDevice->CreateSamplerState(&sampleDesc, samplerState.GetAddressOf());
	if (FAILED(hr)) {
		std::cout << "InitEffect::CreateSamplerState failed." << std::endl;
		LogErrorText(hr);
		return false;
	}

	D3D11_BLEND_DESC blendDesc;
	blendDesc.AlphaToCoverageEnable = FALSE;
	blendDesc.IndependentBlendEnable = FALSE;
	blendDesc.RenderTarget[0].BlendEnable = TRUE;
	blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
	blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
	blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
	blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
	blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
	blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
	blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
	hr = m_pd3dDevice->CreateBlendState(&blendDesc, blendState.GetAddressOf());
	if (FAILED(hr)) {
		std::cout << "InitEffect::CreateBlendState failed." << std::endl;
		LogErrorText(hr);
		return false;
	}

	//创建顶点着色器
	hr = CreateShaderFromFile(L"HLSL\\Triangle_VS.cso", L"HLSL\\Triangle_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf());
	if (FAILED(hr)) {
		std::cout << "InitEffect::CreateShaderFromFile for vs failed." << std::endl;
		LogErrorText(hr);
		return false;
	}
	hr = m_pd3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pVertexShader.GetAddressOf());
	if (FAILED(hr)) {
		std::cout << "InitEffect::CreateVertexShader failed." << std::endl;
		LogErrorText(hr);
		return false;
	}
	//创建并绑定顶点布局
	hr = m_pd3dDevice->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout), blob->GetBufferPointer(), 
		blob->GetBufferSize(), m_pInputLayout.GetAddressOf());
	if (FAILED(hr)) {
		std::cout << "InitEffect::CreateInputLayout failed." << std::endl;
		LogErrorText(hr);
		return false;
	}
	m_pd3dImmediateContex->IASetInputLayout(m_pInputLayout.Get());

	//创建像素着色器
	hr = CreateShaderFromFile(L"HLSL\\Triangle_PS.cso", L"HLSL\\Triangle_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf());
	if (FAILED(hr)) {
		std::cout << "InitEffect::CreateShaderFromFile for ps failed." << std::endl;
		LogErrorText(hr);
		return false;
	}
	hr = m_pd3dDevice->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pPixelShader.GetAddressOf());
	if (FAILED(hr)) {
		std::cout << "InitEffect::CreatePixelShader failed." << std::endl;
		LogErrorText(hr);
		return false;
	}
	return true;
}

其中,顶点布局信息是:

const D3D11_INPUT_ELEMENT_DESC D3D11Render::VertexPosColor::inputLayout[2] = {
	{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
	{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
};

这里顶点布局信息对应着色器里面输入的结构体。比如,HLSL代码中输入信息为:

struct PS_INPUT
{
    float4 Pos : SV_POSITION;
    float2 Tex : TEXCOORD;
};

与之对应C++结构体为:

struct VertexPosColor {
		DirectX::XMFLOAT3 pos;
		DirectX::XMFLOAT2 texture;
		static const D3D11_INPUT_ELEMENT_DESC inputLayout[2];
	};

然后就是创建顶点缓冲区,以及给渲染管线各个阶段绑定资源

bool D3D11Render::InitResource()
{
	HRESULT hr = S_OK;
	VertexPosColor vertices[6] = {
		{DirectX::XMFLOAT3(-1.0f, -1.0f, 0), DirectX::XMFLOAT2(0.0f, 1.0f)},
		{DirectX::XMFLOAT3(-1.0f, 1.0f, 0), DirectX::XMFLOAT2(0.0f, 0.0f)},
		{DirectX::XMFLOAT3(1.0f, -1.0f, 0), DirectX::XMFLOAT2(1.0f, 1.0f)},
		{DirectX::XMFLOAT3(1.0f, -1.0f, 0), DirectX::XMFLOAT2(1.0f, 1.0f)},
		{DirectX::XMFLOAT3(-1.0f, 1.0f, 0), DirectX::XMFLOAT2(0.0f, 0.0f)},
		{DirectX::XMFLOAT3(1.0f, 1.0f, 0), DirectX::XMFLOAT2(1.0f, 0.0f)},
	};

	UINT stride = sizeof(VertexPosColor); 
	UINT offset = 0;
	FLOAT blendFactor[4] = { 0.f, 0.f, 0.f, 0.f };
	m_pd3dImmediateContex->OMSetBlendState(nullptr, blendFactor, 0xffffffff);
	//将着色器绑定到渲染管线
	m_pd3dImmediateContex->VSSetShader(m_pVertexShader.Get(), nullptr, 0);
	m_pd3dImmediateContex->PSSetShader(m_pPixelShader.Get(), nullptr, 0);

	m_pd3dImmediateContex->PSSetShaderResources(0, 3, reourceviewPlaner->GetAddressOf());
	m_pd3dImmediateContex->PSSetSamplers(0, 1, samplerState.GetAddressOf());
	//设置图源类型 
	m_pd3dImmediateContex->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	//设置顶点缓冲区描述
	D3D11_BUFFER_DESC vbd;
	ZeroMemory(&vbd, sizeof(vbd));
	vbd.Usage = D3D11_USAGE_DEFAULT; 
	vbd.ByteWidth = sizeof vertices; /
	vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER; 
	vbd.CPUAccessFlags = 0;

	//新建顶点缓冲区
	D3D11_SUBRESOURCE_DATA InitData;
	ZeroMemory(&InitData, sizeof(InitData));
	InitData.pSysMem = vertices; 
	
	//创建顶点缓冲区
	hr = m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffer.GetAddressOf());
	if (FAILED(hr)) {
		std::cout << "InitResource::CreateBuffer vertex failed." << std::endl;
		LogErrorText(hr);
		return false;
	}

	//输入装配阶段的顶点缓冲区设置
	m_pd3dImmediateContex->IASetVertexBuffers(0, 1, m_pVertexBuffer.GetAddressOf() ,&stride, &offset);

	return true;
}

此处顶点缓冲区数组赋值可以对应下面图片来看。
一张图片由{0, 1, 2}三角形,和{2, 1,3}三角形,共6个顶点组成,所以此处顶点数组大小为6.注意这里给值一定要按照顺时针方向,并且位置坐标和纹理坐标要对应上,不然渲染出来的画面会不正确。

在这里插入图片描述
以上步骤完成,就可以执行画图操作了。

void D3D11Render::DoRender()
{
	HRESULT hr = S_OK;
	int interval = 0;
	if (m_fps > 0) {
		interval = static_cast<int>(1000 / m_fps);
	}
	uint8_t* frame = nullptr;
	int linesize[3] = { m_width, m_width / 2, m_width / 2 };
	while (m_run) {
		uint8_t* src_y = (uint8_t*)frame;
		uint8_t* src_u = src_y + m_width * m_height;
		uint8_t* src_v = src_u + m_width * m_height / 4;
		uint8_t* data[3] = { src_y, src_u, src_v };
		for (int i = 0; i < 3; i++) {
			m_pd3dImmediateContex->UpdateSubresource(texturePlanes[i].Get(), 0, NULL, data[i], linesize[i], 0);
		}
		m_pd3dImmediateContex->Draw(6, 0);
		hr = m_pSwapChain->Present(0, 0); //前后备缓冲区交换并呈现
		if (FAILED(hr)) {
			std::cout << "Present failed." << std::endl;
			break;
		}
		Sleep(interval );
	}
}

这里如果创建纹理时采用的是D3D11_USAGE_DYNAMIC,则按照MAP/UNMAP方法更新。
看一下渲染效果图:
在这里插入图片描述
等后面研究一下第二种方法再和大家分享一波。

附上代码下载链接:
D3D11Render.rar

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值