You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 16:05:08