如何用D3D11渲染FFmpeg解码的AV_PIX_FMT_NV12格式GPU帧?
D3D11渲染NV12视频帧解决方案
问题1:D3D11中与解码帧对应的概念是什么?是纹理吗?
- 是的,D3D11中存储视频帧数据的核心对象是Texture2D。由于NV12是双平面格式(Y平面为全分辨率单通道,UV平面为半分辨率双通道),需要创建两个独立的Texture2D分别存储Y分量和UV分量。
问题2:是否需要将NV12转换为RGB?
- 不需要在CPU端手动转换。D3D11可通过HLSL像素着色器直接对NV12格式的纹理采样,在GPU端实时完成YUV到RGB的色彩空间转换,这种方式效率远高于CPU转换,是视频渲染的标准做法。
问题3:GPU端的NV12帧能否无需复制直接渲染?
- 可以,通过GPU资源互操作实现:
- 若FFmpeg采用CUDA解码(帧数据在NVIDIA显存),可使用CUDA与D3D11的互操作接口,将CUDA显存资源直接映射为D3D11可访问的Texture2D,全程无数据复制。
- 若为其他GPU解码后端(如Intel QSV),也有对应D3D11互操作方案,核心是让解码后的GPU资源直接被D3D11复用。
简易实现示例(核心代码片段)
1. 初始化D3D11设备与交换链
// 简化初始化:创建设备、上下文、交换链 ID3D11Device* pDevice = nullptr; ID3D11DeviceContext* pContext = nullptr; IDXGISwapChain* pSwapChain = nullptr; DXGI_SWAP_CHAIN_DESC scDesc = {}; scDesc.BufferCount = 2; scDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; scDesc.OutputWindow = hWnd; // 你的窗口句柄 scDesc.SampleDesc.Count = 1; scDesc.Windowed = TRUE; D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION, &scDesc, &pSwapChain, &pDevice, nullptr, &pContext); // 获取渲染目标视图 ID3D11RenderTargetView* pRTV = nullptr; ID3D11Texture2D* pBackBuffer = nullptr; pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBackBuffer); pDevice->CreateRenderTargetView(pBackBuffer, nullptr, &pRTV); pBackBuffer->Release(); pContext->OMSetRenderTargets(1, &pRTV, nullptr);
2. 创建NV12格式的纹理(Y和UV平面)
// Y平面纹理(全分辨率单通道) D3D11_TEXTURE2D_DESC yTexDesc = {}; yTexDesc.Width = videoWidth; // 视频宽度 yTexDesc.Height = videoHeight; // 视频高度 yTexDesc.MipLevels = 1; yTexDesc.ArraySize = 1; yTexDesc.Format = DXGI_FORMAT_R8_UNORM; // Y分量为单字节 yTexDesc.SampleDesc.Count = 1; yTexDesc.Usage = D3D11_USAGE_DEFAULT; yTexDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; yTexDesc.CPUAccessFlags = 0; ID3D11Texture2D* pYTexture = nullptr; pDevice->CreateTexture2D(&yTexDesc, nullptr, &pYTexture); ID3D11ShaderResourceView* pYSRV = nullptr; pDevice->CreateShaderResourceView(pYTexture, nullptr, &pYSRV); // UV平面纹理(半分辨率双通道) D3D11_TEXTURE2D_DESC uvTexDesc = {}; uvTexDesc.Width = videoWidth / 2; uvTexDesc.Height = videoHeight / 2; uvTexDesc.MipLevels = 1; uvTexDesc.ArraySize = 1; uvTexDesc.Format = DXGI_FORMAT_R8G8_UNORM; // UV分量为双字节 uvTexDesc.SampleDesc.Count = 1; uvTexDesc.Usage = D3D11_USAGE_DEFAULT; uvTexDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; uvTexDesc.CPUAccessFlags = 0; ID3D11Texture2D* pUVTexture = nullptr; pDevice->CreateTexture2D(&uvTexDesc, nullptr, &pUVTexture); ID3D11ShaderResourceView* pUVSRV = nullptr; pDevice->CreateShaderResourceView(pUVTexture, nullptr, &pUVSRV);
3. HLSL着色器(NV12转RGB)
顶点着色器(最简版)
struct VS_INPUT { float4 pos : POSITION; float2 uv : TEXCOORD0; }; struct PS_INPUT { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; PS_INPUT VS(VS_INPUT input) { PS_INPUT output; output.pos = input.pos; output.uv = input.uv; return output; }
像素着色器(NV12转RGB)
Texture2D<float> YTex : register(t0); Texture2D<float2> UVTex : register(t1); SamplerState Sampler : register(s0); float4 PS(PS_INPUT input) : SV_TARGET { float y = YTex.Sample(Sampler, input.uv).r; float2 uv = UVTex.Sample(Sampler, input.uv * float2(0.5, 0.5)).rg; uv = uv * 2.0 - 1.0; // 转换至[-1,1]范围 // BT.601色彩转换公式 float r = y + 1.370705 * uv.g; float g = y - 0.698001 * uv.g - 0.337633 * uv.r; float b = y + 1.732446 * uv.r; return float4(r, g, b, 1.0); }
4. 渲染流程
// 更新纹理数据(CPU端NV12数据用UpdateSubresource;GPU互操作则跳过此步) // pYData为Y平面指针,pUVData为UV平面指针 pContext->UpdateSubresource(pYTexture, 0, nullptr, pYData, videoWidth, 0); pContext->UpdateSubresource(pUVTexture, 0, nullptr, pUVData, videoWidth, 0); // 设置着色器资源 pContext->PSSetShaderResources(0, 1, &pYSRV); pContext->PSSetShaderResources(1, 1, &pUVSRV); // 绘制全屏四边形(无需顶点缓冲区,直接调用Draw) pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); pContext->Draw(4, 0); // 交换缓冲区显示 pSwapChain->Present(1, 0);
5. GPU互操作示例(CUDA + D3D11)
// 假设已有CUDA解码后的cudaArray_t yArray、uvArray // 注册D3D11纹理为CUDA可访问资源 cudaGraphicsResource_t cudaYResource, cudaUVResource; cudaGraphicsD3D11RegisterResource(&cudaYResource, pYTexture, cudaGraphicsRegisterFlagsNone); cudaGraphicsD3D11RegisterResource(&cudaUVResource, pUVTexture, cudaGraphicsRegisterFlagsNone); // 映射资源,直接将CUDA显存数据写入D3D11纹理 cudaGraphicsMapResources(1, &cudaYResource, 0); cudaArray_t mappedYArray; cudaGraphicsSubResourceGetMappedArray(&mappedYArray, cudaYResource, 0, 0); cudaMemcpy2DToArray(mappedYArray, 0, 0, pYData, videoWidth, videoWidth, videoHeight, cudaMemcpyDeviceToDevice); cudaGraphicsUnmapResources(1, &cudaYResource, 0); // UV平面同理 cudaGraphicsMapResources(1, &cudaUVResource, 0); cudaArray_t mappedUVArray; cudaGraphicsSubResourceGetMappedArray(&mappedUVArray, cudaUVResource, 0, 0); cudaMemcpy2DToArray(mappedUVArray, 0, 0, pUVData, videoWidth, videoWidth/2, videoHeight/2, cudaMemcpyDeviceToDevice); cudaGraphicsUnmapResources(1, &cudaUVResource, 0);
内容的提问来源于stack exchange,提问作者mercuric taylor
相关产品推荐
相关产品推荐

