mfc 橡皮筋类绘制矩形_DX12初始化篇:绘制命令

前几天突然肚子疼,晚上疼的睡不着,医院一日游花了千把检查费,配了点消炎药,现在好多了。所以我们干IT的真的要好好保护身体,攒够革命的本钱,别给医院打工了。好了,废话不多说,今天我们接上一篇博客的代码,继续初始化。

我们对之前的代码略微做点封装,但是为了利于逻辑的理解,不会一下子封装的很深。首先,我们建一个InitWindow函数,然后将之前写的创建窗口代码拷贝进去。此函数返回一个布尔值,如果窗口创建成功则返回true。

bool InitWindow(HINSTANCE hInstance, int nShowCmd)
{
	//窗口初始化描述结构体(WNDCLASS)
	WNDCLASS wc;
	wc.style = CS_HREDRAW | CS_VREDRAW;	//当工作区宽高改变,则重新绘制窗口
	wc.lpfnWndProc = MainWndProc;	//指定窗口过程
	wc.cbClsExtra = 0;	//借助这两个字段来为当前应用分配额外的内存空间(这里不分配,所以置0)
	wc.cbWndExtra = 0;	//借助这两个字段来为当前应用分配额外的内存空间(这里不分配,所以置0)
	wc.hInstance = hInstance;	//应用程序实例句柄(由WinMain传入)
	wc.hIcon = LoadIcon(0, IDC_ARROW);	//使用默认的应用程序图标
	wc.hCursor = LoadCursor(0, IDC_ARROW);	//使用标准的鼠标指针样式
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);	//指定了白色背景画刷句柄
	wc.lpszMenuName = 0;	//没有菜单栏
	wc.lpszClassName = L"MainWnd";	//窗口名
	//窗口类注册失败
	if (!RegisterClass(&wc))
	{
		//消息框函数,参数1:消息框所属窗口句柄,可为NULL。参数2:消息框显示的文本信息。参数3:标题文本。参数4:消息框样式
		MessageBox(0, L"RegisterClass Failed", 0, 0);
		return 0;
	}

	//窗口类注册成功
	RECT R;	//裁剪矩形
	R.left = 0;
	R.top = 0;
	R.right = 1280;
	R.bottom = 720;
	AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);	//根据窗口的客户区大小计算窗口的大小
	int width = R.right - R.left;
	int hight = R.bottom - R.top;

	//创建窗口,返回布尔值
	//CreateWindowW(lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)
	mhMainWnd = CreateWindow(L"MainWnd", L"DX12Initialize", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, hight, 0, 0, hInstance, 0);
	//窗口创建失败
	if (!mhMainWnd)
	{
		MessageBox(0, L"CreatWindow Failed", 0, 0);
		return 0;
	}
	//窗口创建成功,则显示并更新窗口
	ShowWindow(mhMainWnd, nShowCmd);
	UpdateWindow(mhMainWnd);

	return true;
}

然后我们新建一个Run函数,将之前的消息循环代码复制进去,并将其结构略作调整。因为考虑到我们是游戏程序,所以调整了分支结构,如果没有消息处理,就执行游戏画面和逻辑的计算,这里的Draw函数之后会定义(Draw函数每运行一次,其实就是一帧,所以后期会在它前面加上帧时间的计算)。

int Run()
{
    //消息循环
    //定义消息结构体
    MSG msg = { 0 };
    //如果GetMessage函数不等于0,说明没有接受到WM_QUIT
    while (msg.message != WM_QUIT)
    {
	//如果有窗口消息就进行处理
	if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) //PeekMessage函数会自动填充msg结构体元素
	{
	    TranslateMessage(&msg);	//键盘按键转换,将虚拟键消息转换为字符消息
	    DispatchMessage(&msg);	//把消息分派给相应的窗口过程
	}
	//否则就执行动画和游戏逻辑
	else
	{
	    Draw();
	}
    }
	return (int)msg.wParam;
}

接下来我们建一个InitDirect3D函数,将之前我们列出的初始化步骤,除了最后两条(这两条在Draw函数中执行),都拷贝至函数中(这里对之前的步骤都做了函数封装)。我们再来回顾下,做了哪些事:

  1. 开启D3D12调试层。
  2. 创建设备。
  3. 创建围栏,同步CPU和GPU。
  4. 获取描述符大小。
  5. 设置MSAA抗锯齿属性。
  6. 创建命令队列、命令列表、命令分配器。
  7. 创建交换链。
  8. 创建描述符堆。
  9. 创建描述符。
  10. 资源转换。
  11. 设置视口和裁剪矩形。

注意:由于封装了函数,我们将一些公用的ComPtr和结构体以及变量都提到了外面。

//声明指针接口和变量
ComPtr<ID3D12Device> d3dDevice;
ComPtr<IDXGIFactory4> dxgiFactory;
ComPtr<ID3D12Fence> fence;
ComPtr<ID3D12CommandAllocator> cmdAllocator;
ComPtr<ID3D12CommandQueue> cmdQueue;
ComPtr<ID3D12GraphicsCommandList> cmdList;
ComPtr<ID3D12Resource> depthStencilBuffer;
ComPtr<ID3D12Resource> swapChainBuffer[2];
ComPtr<IDXGISwapChain> swapChain;
ComPtr<ID3D12DescriptorHeap> rtvHeap;
ComPtr<ID3D12DescriptorHeap> dsvHeap;

D3D12_VIEWPORT viewPort;
D3D12_RECT scissorRect;
UINT rtvDescriptorSize = 0;
UINT dsvDescriptorSize = 0;
UINT cbv_srv_uavDescriptorSize = 0;
UINT mCurrentBackBuffer = 0;

 bool InitDirect3D()
{
	 /*开启D3D12调试层*/
	#if defined(DEBUG) || defined(_DEBUG)
	{
		ComPtr<ID3D12Debug> debugController;
		ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
		debugController->EnableDebugLayer();
	}
	#endif

	CreateDevice();
	CreateFence();
	GetDescriptorSize();
	SetMSAA();
	CreateCommandObject();
	CreateSwapChain();
	CreateDescriptorHeap();
	CreateRTV();
	CreateDSV();
	CreateViewPortAndScissorRect();

	return true;
}

接下来我们建一个总的Init函数,将InitWindow和InitDirect3D放一起做一个判断,如果两个初始化都正常执行,则判断总的初始化正常执行,返回true。

bool Init(HINSTANCE hInstance, int nShowCmd)
{
	if (!InitWindow(hInstance, nShowCmd))
	{
		return false;
	}		
	else if (!InitDirect3D())
	{
		return false;
	} 
	else
	{
		return true;
	}		
}

然后我们修改WinMain函数,因为要处理抛出异常,所以我们使用try-catch结构。如果初始化成功,则执行Run函数,并通过DxException类捕获异常,返回异常的函数名,以及所在行号。

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, int nShowCmd)
{
#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
	try
	{
		if ( ! Init(hInstance, nShowCmd) )
			return 0;

		return Run();
	}
	catch (DxException& e)
	{
		MessageBox(nullptr, e.ToString().c_str(), L"HR Failed", MB_OK);
		return 0;
	}
}

大致的代码结构就是这样,现在我们来处理刚提到的Draw函数,这是今天的重点。

void Draw();

Draw函数主要是将我们的各种资源设置到渲染流水线上,并最终发出绘制命令。首先重置命令分配器cmdAllocator和命令列表cmdList,目的是重置命令和列表,复用相关内存。

ThrowIfFailed(cmdAllocator->Reset());//重复使用记录命令的相关内存
ThrowIfFailed(cmdList->Reset(cmdAllocator.Get(), nullptr));//复用命令列表及其内存

接着我们将后台缓冲资源从呈现状态转换到渲染目标状态(即准备接收图像渲染)。

UINT& ref_mCurrentBackBuffer = mCurrentBackBuffer;
cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(swapChainBuffer[ref_mCurrentBackBuffer].Get(),//转换资源为后台缓冲区资源
	D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));//从呈现到渲染目标转换

接下来设置视口和裁剪矩形。

cmdList->RSSetViewports(1, &viewPort);
cmdList->RSSetScissorRects(1, &scissorRect);

然后清除后台缓冲区和深度缓冲区,并赋值。步骤是先获得堆中描述符句柄(即地址),再通过ClearRenderTargetView函数和ClearDepthStencilView函数做清除和赋值。这里我们将RT资源背景色赋值为DarkRed(暗红)。

D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = CD3DX12_CPU_DESCRIPTOR_HANDLE(rtvHeap->GetCPUDescriptorHandleForHeapStart(), ref_mCurrentBackBuffer, rtvDescriptorSize);
cmdList->ClearRenderTargetView(rtvHandle, DirectX::Colors::DarkRed, 0, nullptr);//清除RT背景色为暗红,并且不设置裁剪矩形
D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = dsvHeap->GetCPUDescriptorHandleForHeapStart();
cmdList->ClearDepthStencilView(dsvHandle,	//DSV描述符句柄
	D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,	//FLAG
	1.0f,	//默认深度值
	0,	//默认模板值
	0,	//裁剪矩形数量
	nullptr);	//裁剪矩形指针

然后我们指定将要渲染的缓冲区,即指定RTV和DSV。

cmdList->OMSetRenderTargets(1,//待绑定的RTV数量
	&rtvHandle,	//指向RTV数组的指针
	true,	//RTV对象在堆内存中是连续存放的
	&dsvHandle);	//指向DSV的指针

等到渲染完成,我们要将后台缓冲区的状态改成呈现状态,使其之后推到前台缓冲区显示。完了,关闭命令列表,等待传入命令队列。

cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(swapChainBuffer[ref_mCurrentBackBuffer].Get(),
	D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));//从渲染目标到呈现
//完成命令的记录关闭命令列表
ThrowIfFailed(cmdList->Close());

等CPU将命令都准备好后,需要将待执行的命令列表加入GPU的命令队列。使用的是ExecuteCommandLists函数。

ID3D12CommandList* commandLists[] = { cmdList.Get() };//声明并定义命令列表数组
cmdQueue->ExecuteCommandLists(_countof(commandLists), commandLists);//将命令从命令列表传至命令队列

然后交换前后台缓冲区索引(这里的算法是1变0,0变1,为了让后台缓冲区索引永远为0)。

ThrowIfFailed(swapChain->Present(0, 0));
ref_mCurrentBackBuffer = (ref_mCurrentBackBuffer + 1) % 2;

最后设置围栏值,刷新命令队列,使CPU和GPU同步,这段代码在第一篇中有详细解释,这里直接封装。

FlushCmdQueue();

至此,第一阶段的初始化代码全部完成,运行结果如下:

41da0b1ab26257a0c42b985b04157ce9.png

可以看到,背景就是我们设置的暗红色。

为了学习理解,我是在一个cpp文件中写的代码,但是考虑到项目后期的使用,还得整理下。所以后面的博客我会重构代码并加入帧的间隔时间,显示FPS,以及每帧更新必要数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值