最近我们播放器(Kommander)有需要绘制NV12格式的内存数据图像到窗口上,为了搞清楚NV12的内存数据如何送入纹理对象,我专门写了个demo来实现这一块的功能,一是为了彻底搞清楚这一块原理,二也是为了方便其他人的学习。
demo代码如下,分别用3种方式实现了NV12格式数据送入纹理:
/**
* @details 此demo主要是验证NV12格式的内存数据图像如何通过d3d11渲染到窗口
* @author knight
* @email [email protected]
* @version 1.0.0
* @date 2023-07-08
*/
#include
#include
#include
#include
#include
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")
using namespace DirectX;
struct VertexPosTex
{
XMFLOAT3 pos;
XMFLOAT2 tex;
static const D3D11_INPUT_ELEMENT_DESC inputLayout[2];
};
const D3D11_INPUT_ELEMENT_DESC VertexPosTex::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 }
};
// 以下代码省略了错误检查以简洁清晰,实际项目中你需要添加适当的错误处理
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_CLOSE || message == WM_DESTROY)
{
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX windowClass = { 0 };
windowClass.cbSize = sizeof(WNDCLASSEX);
windowClass.style = CS_HREDRAW | CS_VREDRAW;
windowClass.lpfnWndProc = WndProc;
windowClass.hInstance = hInstance;
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
windowClass.lpszClassName = "D3D11Class";
RegisterClassEx(&windowClass);
RECT windowRect = { 0, 0, 800, 600 };
AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, FALSE);
HWND hwnd = CreateWindow(
"D3D11Class",
"D3D11 Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top,
NULL,
NULL,
hInstance,
NULL
);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// 创建D3D11设备和上下文
ID3D11Device* device;
ID3D11DeviceContext* context;
D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, NULL, 0, D3D11_SDK_VERSION, &device, NULL, &context);
// 获取IDXGIDevice接口
IDXGIDevice* dxgiDevice;
device->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
// 获取IDXGIAdapter接口
IDXGIAdapter* dxgiAdapter;
dxgiDevice->GetAdapter(&dxgiAdapter);
// 获取IDXGIFactory接口
IDXGIFactory* dxgiFactory;
dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory);
// 创建交换链
DXGI_SWAP_CHAIN_DESC swapChainDesc = { 0 };
swapChainDesc.BufferCount = 2;
swapChainDesc.BufferDesc.Width = 800;
swapChainDesc.BufferDesc.Height = 600;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = hwnd;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.Windowed = TRUE;
IDXGISwapChain* swapChain;
dxgiFactory->CreateSwapChain(device ,&swapChainDesc, &swapChain);
// 创建顶点着色器
const char* vertexShaderCode =
"struct VertexPosIn"
"{"
" float3 PosL : POSITION;"
" float2 Tex : TEXCOORD;"
"};"
"struct VertexPosOut"
"{"
" float4 PosH : SV_POSITION;"
" float2 Tex : TEXCOORD;"
"};"
"VertexPosOut main(VertexPosIn vIn)"
"{"
" VertexPosOut vOut;"
" vOut.PosH=float4(vIn.PosL,1.0f);"
" vOut.Tex=vIn.Tex;"
" return vOut;"
"}";
ID3DBlob* vsBlob = NULL;
HRESULT hr=D3DCompile(vertexShaderCode, strlen(vertexShaderCode), NULL, NULL, NULL, "main", "vs_4_0", 0, 0, &vsBlob, NULL);
ID3D11VertexShader* vertexShader;
device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), NULL, &vertexShader);
ID3D11InputLayout* inputLayout;
device->CreateInputLayout(VertexPosTex::inputLayout, ARRAYSIZE(VertexPosTex::inputLayout),
vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), &inputLayout);
// 创建像素着色器
const char* pixelShaderCode =
"Texture2D yTexture : register(t0);"
"Texture2D uvTexture : register(t1);"
"SamplerState samplerState : register(s0);"
"float4 main(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target"
"{"
" float y = yTexture.Sample(samplerState, uv).r;"
//" return float4(y,y,y,1.0f);"
" float u = uvTexture.Sample(samplerState, uv).r-0.5f;"
" float v = uvTexture.Sample(samplerState, uv).g-0.5f;"
//" float u=0.0f;"
//" float v=0.0f;"
" float3 yuv = float3(y, u, v);"
//" float3 rgb = mul(yuv,float3x3(1.0, 1.0, 1.0,0.0,-0.39465,2.03211,1.13983,-0.58060,0.0));"//如果矩阵放在mul第二个参数位置,则是取每一列相乘
" float3 rgb = mul(float3x3(1.0, 0.0, 1.13983, 1.0, -0.39465, -0.58060, 1.0, 2.03211, 0.0),yuv);"//如果矩阵放在mul第一个参数位置,则是取每一行相乘
" return float4(rgb, 1.0);"
"}";
ID3DBlob* psBlob = NULL;
D3DCompile(pixelShaderCode, strlen(pixelShaderCode), NULL, NULL, NULL, "main", "ps_4_0", 0, 0, &psBlob, NULL);
ID3D11PixelShader* pixelShader = NULL;
device->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), NULL, &pixelShader);
//创建采样状态
D3D11_SAMPLER_DESC sampDesc;
ZeroMemory(&sampDesc, sizeof(sampDesc));
sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
sampDesc.MinLOD = 0;
sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
ID3D11SamplerState* samplerState=NULL;
device->CreateSamplerState(&sampDesc, &samplerState);
// 创建四边形
VertexPosTex quadVertices[] =
{
{ DirectX::XMFLOAT3(-1.0f, 1.0f, 0.0f), DirectX::XMFLOAT2(0.0f, 0.0f) },
{ DirectX::XMFLOAT3(1.0f, 1.0f, 0.0f), DirectX::XMFLOAT2(1.0f, 0.0f) },
{ DirectX::XMFLOAT3(-1.0f, -1.0f, 0.0f), DirectX::XMFLOAT2(0.0f, 1.0f) },
{ DirectX::XMFLOAT3(1.0f, -1.0f, 0.0f), DirectX::XMFLOAT2(1.0f, 1.0f) }
};
D3D11_BUFFER_DESC vertexBufferDesc = {};
vertexBufferDesc.ByteWidth = sizeof(quadVertices);
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
D3D11_SUBRESOURCE_DATA vertexData = {};
vertexData.pSysMem = quadVertices;
ID3D11Buffer* vertexBuffer= NULL;
device->CreateBuffer(&vertexBufferDesc, &vertexData, &vertexBuffer);
//初始化NV12纹理内存数据
int texWidth = 800;
int texHeight = 600;
const int dataSize = texWidth * (texHeight + texHeight / 2);
const uint8_t* pSysData = new uint8_t[dataSize];
memset((void*)pSysData, 150, texWidth* texHeight);
memset((void*)(pSysData+texWidth*texHeight), 128, texWidth* texHeight/2);
//创建NV12纹理
ID3D11Texture2D* textureNV12 = NULL;
#if 0 //创建GPU读纹理,在创建的时候上传数据
D3D11_TEXTURE2D_DESC textureDesc = {};
textureDesc.Width = texWidth;
textureDesc.Height = texHeight;
textureDesc.MipLevels = 1;
textureDesc.ArraySize = 1;
textureDesc.Format = DXGI_FORMAT_NV12; // YUV数据通常是8位单通道
textureDesc.SampleDesc.Count = 1;
textureDesc.Usage = D3D11_USAGE_IMMUTABLE;
textureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
//把NV12纹理内存数据传入纹理
D3D11_SUBRESOURCE_DATA textureSubresourceData = {};
textureSubresourceData.pSysMem = pSysData;
textureSubresourceData.SysMemPitch = texWidth; // 1字节每像素
device->CreateTexture2D(&textureDesc, &textureSubresourceData, &textureNV12);
#elif 0 //创建GPU读写纹理,用UpdateResource上传数据
D3D11_TEXTURE2D_DESC textureDesc = {};
textureDesc.Width = texWidth;
textureDesc.Height = texHeight;
textureDesc.MipLevels = 1;
textureDesc.ArraySize = 1;
textureDesc.Format = DXGI_FORMAT_NV12; // YUV数据通常是8位单通道
textureDesc.SampleDesc.Count = 1;
textureDesc.Usage = D3D11_USAGE_DEFAULT;
textureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
device->CreateTexture2D(&textureDesc, nullptr, &textureNV12);
//把NV12纹理内存数据传入纹理
context->UpdateSubresource(textureNV12, 0, NULL, pSysData, texWidth, 0);
#else //创建CPU写,GPU读纹理,用Map/Unmap上传数据
D3D11_TEXTURE2D_DESC textureDesc = {};
textureDesc.Width = texWidth;
textureDesc.Height = texHeight;
textureDesc.MipLevels = 1;
textureDesc.ArraySize = 1;
textureDesc.Format = DXGI_FORMAT_NV12; // YUV数据通常是8位单通道
textureDesc.SampleDesc.Count = 1;
textureDesc.Usage = D3D11_USAGE_DYNAMIC;
textureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
device->CreateTexture2D(&textureDesc, nullptr, &textureNV12);
D3D11_MAPPED_SUBRESOURCE mappedData;
if (SUCCEEDED(context->Map(textureNV12, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData)))
{
byte* pDest = (BYTE*)mappedData.pData;
int stride = mappedData.RowPitch;
int dataHeight = texHeight + texHeight / 2;
for (int i = 0; i < dataHeight; i++)
{
memcpy(pDest + i * stride, pSysData + i * texWidth, texWidth);
}
context->Unmap(textureNV12,0);
}
#endif
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Format = DXGI_FORMAT_R8_UNORM;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = textureDesc.MipLevels;
ID3D11ShaderResourceView* textureViewY = NULL;
device->CreateShaderResourceView(textureNV12, &srvDesc, &textureViewY);
ID3D11ShaderResourceView* textureViewUV = NULL;
srvDesc.Format = DXGI_FORMAT_R8G8_UNORM;
device->CreateShaderResourceView(textureNV12, &srvDesc, &textureViewUV);
// 创建渲染目标视图
ID3D11Texture2D* backBuffer;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer);
ID3D11RenderTargetView* renderTargetView;
device->CreateRenderTargetView(backBuffer, NULL, &renderTargetView);
//设置渲染状态
context->OMSetRenderTargets(1, &renderTargetView, NULL);
// 设置视口
D3D11_VIEWPORT viewport = { 0, 0, 800, 600, 0, 1 };
context->RSSetViewports(1, &viewport);
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
//设置顶点输入格式
context->IASetInputLayout(inputLayout);
UINT stride = sizeof(VertexPosTex);
UINT offset = 0;
context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
// 设置顶点着色器
context->VSSetShader(vertexShader, NULL, 0);
// 设置着色器和纹理
context->PSSetShader(pixelShader, NULL, 0);
//设置采样状态
context->PSSetSamplers(0, 1, &samplerState);
context->PSSetShaderResources(0, 1, &textureViewY);
context->PSSetShaderResources(1, 1, &textureViewUV);
MSG msg = { 0 };
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
float clearColor[4] = { 100.0f / 255.0f, 149.0f / 255.0f, 237.0f / 255.0f, 1.0f }; // 替换CornflowerBlue颜色
// 渲染
context->ClearRenderTargetView(renderTargetView, clearColor);
//... 在这里添加你的绘制调用
context->Draw(4, 0);
swapChain->Present(0, 0);
}
}
// 释放所有Direct3D资源
textureViewUV->Release();
textureViewY->Release();
textureNV12->Release();
samplerState->Release();
pixelShader->Release();
vertexShader->Release();
vertexBuffer->Release();
inputLayout->Release();
renderTargetView->Release();
backBuffer->Release();
swapChain->Release();
context->Release();
device->Release();
return 0;
}
上面demo中,我自己创建了一块NV12内存数据,为了简单起见,我把NV12内存数据图像设置成一个灰色了(你们也可以根据自己需要改为其他颜色),所以你们执行程序最终会在窗口上看到一个灰色图像。