D3D11绘制NV12格式内存数据图像到窗口

最近我们播放器(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内存数据图像设置成一个灰色了(你们也可以根据自己需要改为其他颜色),所以你们执行程序最终会在窗口上看到一个灰色图像。

你可能感兴趣的:(D3D11,d3d11,nv12)