使用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