Directx12 chapter4

发布于:2025-02-10 ⋅ 阅读:(45) ⋅ 点赞:(0)

官方的初始化需要的组件

Initialize

初始化涉及到首次设置全局变量和类,initialize 函数必须准备管道和资产。

  • 初始化管道。
    • 启用调试层。
    • 创建设备。
    • 创建命令队列。
    • 创建交换链。
    • 创建渲染器目标视图 (RTV) 描述符堆。

备注

可将描述符堆视为描述符的数组。 其中的每个描述符完全描述 GPU 的对象。

    • 创建帧资源(每个帧的渲染器目标视图)。
    • 创建命令分配器。

备注

命令分配器管理命令列表和捆绑的基础存储。

  • 初始化资产。
    • 创建空的根签名。

备注

图形根签名定义哪些资源要绑定到图形管道。

    • 编译着色器。
    • 创建顶点输入布局。
    • 创建管道状态对象说明,然后创建对象。

备注

管道状态对象保留所有当前设置的着色器以及某些固定函数状态对象(例如输入汇编器、细分器、光栅器和输出合并器)的状态。

    • 创建命令列表。
    • 关闭命令列表。
    • 创建并加载顶点缓冲区。
    • 创建顶点缓冲区视图。
    • 创建围栏。

备注

围栏用于将 CPU 与 GPU 同步 (请参阅 多引擎同步) 。

    • 创建事件处理程序。
    • 等待 GPU 完成。

备注

检查围栏!

请参阅类 D3D12HelloTriangleOnInitLoadPipelineLoadAssets

实际编写

创建窗口

就和vulkan一样首先创建窗口

LRESULT WindowProcedure(HWND hwnd, UINT messageType, WPARAM wParam, LPARAM lParam)
{
	if (messageType == WM_DESTROY)
	{
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, messageType, wParam, lParam);
}

void HelloTriangle::LoadPipeline()
{
	WNDCLASSEX winClassMessage{};
	winClassMessage.cbSize = sizeof(WNDCLASSEX);
	winClassMessage.style = CS_HREDRAW | CS_VREDRAW;
	winClassMessage.lpfnWndProc = WindowProcedure;
	winClassMessage.hInstance = GetModuleHandle(0);
	winClassMessage.lpszClassName = L"MainWindow";

	RegisterClassEx(&winClassMessage);
	
	RECT wrc{ 0,0,m_Width, m_Height };
	m_Window = CreateWindow(winClassMessage.lpszClassName,
		L"TriangleWindow", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
		wrc.right - wrc.left, wrc.bottom - wrc.top, nullptr, nullptr,
		winClassMessage.hInstance, nullptr
	);

}

打印HRESULT的结果

定义了一个ThrowIfFailed去打印HRESULT的失败的结果

inline std::string HrToString(HRESULT hr)
{
	char s_str[64];
	sprintf_s(s_str, "HRESULT of 0x%08X", static_cast<UINT>(hr));
	return std::string(s_str);
}

class HrException : public std::runtime_error
{
public:
	HrException(HRESULT hr) :
		std::runtime_error(HrToString(hr)), m_Hr(hr){}
private:
	const HRESULT m_Hr;
};

void inline ThrowIfFailed(HRESULT hr)
{
	if (FAILED(hr))
	{
		throw HrException(hr);
	}
}

创建验证层

COM 对象通过接口来提供服务,当获取一个 COM 接口指针时,需要正确地传递接口的标识符(IID - Interface Identifier)以及用于接收指针的变量地址,并且要考虑接口对象的引用计数等问题。IID_PPV_ARGS是一个宏,它的主要作用是在获取 COM 接口时,正确地传递接口的全局唯一标识符(IID)和用于接收接口指针的指针(PPV - Pointer to Pointer of a Variable)。

#if defined(_DEBUG)
{
    ComPtr<ID3D12Debug> debugController;
    if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
    {
        debugController->EnableDebugLayer();
    }
}
#endif

创建factory

ComPtr<IDXGIFactory4> factory;
ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&factory)));

创建设备

这里的nullptr是使用默认的显示设备

HRESULT hardwareResult = D3D12CreateDevice(
	nullptr,
	D3D_FEATURE_LEVEL_11_0,
	IID_PPV_ARGS(&m_Device)
);

如果返回值是filed则使用warp设备

if (FAILED(hardwareResult))
{
	ComPtr<IDXGIAdapter> pWrapAdater;
	ThrowIfFailed(factory->EnumWarpAdapter(IID_PPV_ARGS(&pWrapAdater)));
	ThrowIfFailed(D3D12CreateDevice(
		pWrapAdater.Get(),
		D3D_FEATURE_LEVEL_11_0,
		IID_PPV_ARGS(&m_Device)
	));
}

创建命令队列

D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;

ThrowIfFailed(m_Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_CommandQueue)));

创建命令分配器和命名列表

ThrowIfFailed(m_Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(m_CommandAllocator.GetAddressOf())));
ThrowIfFailed(m_Device->CreateCommandList(0,
    D3D12_COMMAND_LIST_TYPE_DIRECT,
    m_CommandAllocator.Get(),
    nullptr,
    IID_PPV_ARGS(m_CommandList.GetAddressOf())));
m_CommandList->Close();

创建交换链

DXGI_SWAP_CHAIN_DESC swapChainDesc{};
swapChainDesc.BufferDesc.Width = m_Width;
swapChainDesc.BufferDesc.Height = m_Height;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;   //60 / 1
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2;
swapChainDesc.OutputWindow = m_Window;
swapChainDesc.Windowed = true;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

//attention swap Chain need command queue to reflash
ThrowIfFailed(factory->CreateSwapChain(
    m_CommandQueue.Get(),
    &swapChainDesc,
    &m_SwapChain
    ));

创建描述符堆

D3D12中GPU资源并不直接绑定到渲染流水线上,而是通过名为描述符(Descriptor)的中间对象来绑定。描述符是一个轻量级的数据,用来描述资源的信息,在之前版本API中,类似的概念叫视图(view),在D3D12中这个名词依然保存,和描述符是同义词。

我们可以在不同的阶段,为资源创建不同的描述符,这样资源就可以有不同的用处。比如对于一个纹理资源,我们可以先使用它的渲染目标视图(render target view,RTV),把它作为着色器渲染的目标。然后我们使用纹理的着色器资源视图(shader resource view, SRV),把它作为着色器资源用于后续的渲染,典型的一个例子就是shadowmap

常用的描述符有:

  • CBV(constant buffer view):常量缓冲区视图
  • SRV(shader resource view):着色器资源视图
  • UAV(unorder access view):无序访问视图
  • Sampler:采样器,用于采样纹理资源
  • RTV(render target view):渲染目标视图
  • DSV(depth/stencil view):深度/模板视图

描述符堆(Descriptor Heap)

描述符堆实际上就是存放描述符的数组,本质上是存放特定类型描述符的一块内存。

描述符堆的类型

描述符堆有以下类型:

  • D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV:可以存放CBV, SRV和UAV
  • D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER: 存放sampler
  • D3D12_DESCRIPTOR_HEAP_TYPE_RTV:存放RTV
  • D3D12_DESCRIPTOR_HEAP_TYPE_DSV:存放DSV

其中CBV_SRV_UAV和SAMPLER都是可以被shader使用的数据,但却被分成两类,原因是在大多数硬件实现中,CBV_SRV_UAV和SAMPLER是分开管理的。

		D3D12_DESCRIPTOR_HEAP_DESC descriptorHeapDesc{};
		descriptorHeapDesc.NumDescriptors = swapChainBufferCount;
		descriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
		descriptorHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; //shader can't use
		descriptorHeapDesc.NodeMask = 0;
		
		ThrowIfFailed(m_Device->CreateDescriptorHeap(&descriptorHeapDesc, IID_PPV_ARGS(m_DescriptorHeap.GetAddressOf())));

很清楚可以看到,描述符就和资源一样(资源代言人)

创建渲染目标视图

参考链接

这里我理解,缓冲区就是存放了资源,我们需要在缓冲区和描述符之间创建联系,渲染目标视图就是描述符,我们首先需要得到对应的缓冲区资源

比如,为了将后台缓冲区绑定到流水线的输出合并阶段,我们需要为该后台缓冲区创建一个渲染目标视图。

从功能角度讲,交换链就像是一个缓冲区的管理者和调度者

步骤一 从交换链得到目标缓冲区
HRESULT IDXGISwapChain::GetBuffer(
UINT Buffer,//希望获得的特定后台缓冲区的索引。是索引!!!后台缓冲区不只有一个,所以要用索引指明。
REFIID riid,//希望获得的ID3D12Resource接口的COMID
void **ppSurface//返回一个指向ID3D12Resourse接口的指针。这便是希望获得的后台缓冲区
)
【注意,这个函数会增加相关后台缓冲区的COM引用计数,所以在每次使用后一定要将其释放。
通过ComPtr便可以自动做到这一点。】
步骤二 为获取的后台缓冲区创建 渲染目标视图。
void ID3D12Device::CreateRenderTargetView(
ID3D12Resource *pResource,//用作渲染目标的资源
const D3D12_RENDER_TARGET_VIEW_DESC *pDesc,//
D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor,
)
使用
static const int swapChainBufferCount = 2;

ComPtr<ID3D12Resource> m_SwapChainBuffer[swapChainBufferCount];

根据交换链中的缓冲区的数量创建了缓冲区的资源数量,接下来我们需要将缓冲区绑定到描述符中,其实描述符的堆类型有两种,一种是gpu不可见,一种是gpu可见

我们先绑定cpu可见的gpu不可见的描述符

首先我们需要使用GetBuffer去绑定ID3D12Resourse,也就是我们的资源,我们通过

for (int i = 0; i < swapChainBufferCount; i++)
{
	ThrowIfFailed(m_SwapChain->GetBuffer(i, IID_PPV_ARGS(m_SwapChainBuffer[i].GetAddressOf())));
}

接下来就是绑定到我们的描述符上,在绑定之前,我们需要知道它其实是首先得到描述符堆中的首描述符然后不断的向下位移得到描述符,也就是说我们需要获得堆描述符的首地址,然后还需要知道最终能够位移多少个描述符防止过载,同时还可以拿到别的类型的描述符的数量

这里的描述符就是rtv的类型,我们只需要在设备上进行查询即可

m_RTVDescriptorMaxCount = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
m_CBVSRVUAVDescriptorMaxCount = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
m_DSVDescriptorMaxCount = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);

CD3DX12_CPU_DESCRIPTOR_HANDLE descriptorPtr(m_DescriptorHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < (UINT)swapChainBufferCount; i++)
{
	ThrowIfFailed(m_SwapChain->GetBuffer(i, IID_PPV_ARGS(m_SwapChainBuffer[i].GetAddressOf())));
	m_Device->CreateRenderTargetView(m_SwapChainBuffer[i].Get(), nullptr, descriptorPtr);
	descriptorPtr.Offset(1, m_RTVDescriptorMaxCount);
}
  • CreateRenderTargetView函数中,第二个参数用于指定渲染目标视图(RTV)的描述信息,类型通常是D3D12_RTV_DESC结构体。这个结构体包含了诸如格式、维度等关于视图的详细信息。
  • 当你传入nullptr作为这个参数时,DirectX 12 会使用默认的设置来创建渲染目标视图。这些默认设置是基于你提供的第一个参数(资源指针,这里是m_SwapChainBuffer[i].Get())的属性来确定的。对于交换链缓冲区(m_SwapChainBuffer[i]),它本身有自己的格式和维度等属性。DirectX 12 能够自动根据交换链缓冲区的资源类型和格式来确定合适的渲染目标视图设置。
  • 例如,如果交换链缓冲区是一个标准的 2D 纹理资源,DirectX 会自动按照 2D 纹理的默认规则来创建 RTV。这包括使用交换链缓冲区本身的纹理格式(如DXGI_FORMAT_R8G8B8A8_UNORM等常见格式)作为 RTV 的格式,并且视图维度会被设置为与纹理资源对应的 2D 维度(D3D12_RTV_DIMENSION_TEXTURE2D)。

创建fence

为了实现cpu和gpu的同步,我们还需要创建fence(围栏)

		//create fence
		ThrowIfFailed(m_Device->CreateFence(0,
			D3D12_FENCE_FLAG_NONE,
			IID_PPV_ARGS(m_FenceOne.GetAddressOf())));

检测对4X MSAA质量级别的支持

也就是检测一下是否对4X MSAA的支持

利用Device的CheckFeatureSupport()来查看是否支持

D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0;

ThrowIfFailed(m_Device->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
	&msQualityLevels,
	sizeof(msQualityLevels)));

UINT QualityLevels = msQualityLevels.NumQualityLevels;
std::cout << QualityLevels << std::endl; //1

由于这里一般都是支持的所以很多都不会写这段代码了。

创建深度/模板缓冲区及其视图

之前的buffer是资源,纹理也是一种资源,纹理是一种GPU资源,所以需要填写dec

//create depth material
D3D12_RESOURCE_DESC DepthBufferDesc{};
DepthBufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
DepthBufferDesc.Alignment = 0;
DepthBufferDesc.Width = m_Width;
DepthBufferDesc.Height = m_Height;
DepthBufferDesc.DepthOrArraySize = 1;
DepthBufferDesc.MipLevels = 1;
DepthBufferDesc.Format = DXGI_FORMAT_D16_UNORM;
DepthBufferDesc.SampleDesc.Count = multiSampleState ? 4 : 1;
DepthBufferDesc.SampleDesc.Quality = multiSampleState ? (QualityLevels - 1) : 0;
DepthBufferDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
DepthBufferDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

创建的深度和模板缓冲区需要设定清空的缓冲值是多少

D3D12_CLEAR_VALUE optClear;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;

针对资源通过CreateCommittedResource创建资源。

ThrowIfFailed(m_Device->CreateCommittedResource(
	&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
	D3D12_HEAP_FLAG_NONE,
	&DepthBufferDesc,
	D3D12_RESOURCE_STATE_COMMON,
	&optClear,
	IID_PPV_ARGS(&m_DepthStencilResource)
));

有了资源,就需要绑定描述符,同时还需要将resource的state状态改为write,写状态

m_Device->CreateDepthStencilView(
	m_DepthStencilResource.Get(), nullptr, descriptorPtr
);
descriptorPtr.Offset(1, m_RTVDescriptorMaxCount);
m_CommandList->ResourceBarrier(1,
	&CD3DX12_RESOURCE_BARRIER::Transition(m_DepthStencilResource.Get(), D3D12_RESOURCE_STATE_COMMON,
		D3D12_RESOURCE_STATE_DEPTH_WRITE)
	);

设置视口

D3D12_VIEWPORT vp;
vp.TopLeftX = 0.0f;
vp.TopLeftY = 0.0f;
vp.Width = m_Width;
vp.Height = m_Height;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;

m_CommandList->RSSetViewports(1, &vp);

设置裁剪视口

其实也就是裁剪测试中,允许程序员设定的一个视口,这个视口外的不会渲染

D3D12_RECT m_ScissorRect;
m_ScissorRect.left = 0;
m_ScissorRect.right = m_Width;
m_ScissorRect.bottom = m_Height;
m_ScissorRect.top = 0;

计时与动画

这里提到了一个方法去计算时间的间隔,也就是后期用来性能测试的

首先在程序中是计数的,但是多少秒每个数不知道,所以需要计算

void calculateTime()
{
	_int64 CurrentTime;
	QueryPerformanceCounter((LARGE_INTEGER*)&CurrentTime);

	_int64 countPerSec;
	QueryPerformanceFrequency((LARGE_INTEGER*)&countPerSec);

	double secPerCount = 1.0 / countPerSec;

	std::cout << secPerCount << std::endl;
}

通过QueryPerformanceCounter可以拿到当前的计数, QueryPerformanceFrequency是多少个计数每秒, 1.0 / countPerSec, 得到多少秒每个计数,然后有了这个就可以通过计算相差的计数个数进行计算间距的时间

InitDirect3DApp代码解读,和渲染流程

这个书中的意思就是基础的框架,继承自D3DApp,这个后面在分析

对以下四个方法进行复写,其中Update和Draw是一定要复写的内容,其它的方法复写是选择性的

  • HINSTANCE是一个 Windows 编程中的数据类型。它代表一个实例句柄(Handle to an Instance)。在 Windows 操作系统中,当一个程序被加载到内存中运行时,系统会为这个程序的每个实例分配一个唯一的句柄,这个句柄就是HINSTANCE类型。
  • 它主要用于标识应用程序或动态链接库(DLL)在内存中的实例。例如,当你启动多个副本(实例) of a Windows 应用程序时,每个实例都有自己的HINSTANCE值。
class InitDirect3DApp : public D3DApp
{
public:
	InitDirect3DApp(HINSTANCE hInstance);
	~InitDirect3DApp();

	virtual bool Initialize()override;

private:
    virtual void OnResize()override;
    virtual void Update(const GameTimer& gt)override;
    virtual void Draw(const GameTimer& gt)override;

};

这里回在父类中去创建这个D3DApp的类,同时保证只有一个实例被创建

D3DApp::D3DApp(HINSTANCE hInstance)
:	mhAppInst(hInstance)
{
    // Only one D3DApp can be constructed.
    assert(mApp == nullptr);
    mApp = this;
}

初始化

bool InitDirect3DApp::Initialize()
{
    if(!D3DApp::Initialize())
        return false;
        
    return true;
}
bool D3DApp::Initialize()
{
	if(!InitMainWindow())
		return false;

	if(!InitDirect3D())
		return false;

    // Do the initial resize code.
    OnResize();

	return true;
}

初始化window

涉及到的就是初始化窗口的类,注册这个窗口,创建窗口

bool D3DApp::InitMainWindow()
{
	WNDCLASS wndClassEx{};

	wndClassEx.style = CS_HREDRAW | CS_VREDRAW;
	wndClassEx.lpfnWndProc = MainWndProc;
	wndClassEx.cbClsExtra = 0;
	wndClassEx.cbWndExtra = 0;
	wndClassEx.hInstance - m_AppInstance;
	wndClassEx.hIcon = LoadIcon(0, IDI_APPLICATION);
	wndClassEx.hCursor = LoadCursor(0, IDC_ARROW);
	wndClassEx.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
	wndClassEx.lpszMenuName = 0;
	wndClassEx.lpszClassName = L"MainWindow";

	if (!RegisterClass(&wndClassEx))
	{
		MessageBox(0, L"RegisterClass Failed", 0, 0);
		return false;
	}

	RECT R = { 0, 0, m_ClientWidth, m_ClientHeight };
	
	AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false); //adjust the window rect
	int width = R.right - R.left;
	int height = R.bottom - R.top;

	m_Window = CreateWindow(wndClassEx.lpszClassName, L"FirstProgram", WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, m_AppInstance, 0);
	if (!m_Window)
	{
		MessageBox(0, L"CreateWindow Failed", 0, 0);
		return false;
	}

	ShowWindow(m_Window, SW_SHOW);
	UpdateWindow(m_Window);
	return true;
}

初始化direcxt3D

然后就是初始化direcxt3D

bool D3DApp::InitDirectx3D()
{
    #if defined(DEBUG) || defined(_DEBUG)
    {
        ComPtr<ID3D12Debug> debug;
        ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(debug.GetAddressOf())));
        debug->EnableDebugLayer();
    }
    #endif
    ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(m_Factory.GetAddressOf())));

    HRESULT isokDevice = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(m_Device.GetAddressOf()));
    if (FAILED(isokDevice))
    {
        ComPtr<IDXGIAdapter> softAdapter;
        ThrowIfFailed(m_Factory->EnumWarpAdapter(IID_PPV_ARGS(softAdapter.GetAddressOf())));

        ThrowIfFailed(D3D12CreateDevice(softAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(m_Device.GetAddressOf())));
    }

    ThrowIfFailed(m_Device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(m_Fence.GetAddressOf())));

    m_CBVSRVUAVSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
    m_RTVDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
    m_DSVDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);

    D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msaaSupport{};
    msaaSupport.Format = m_BackBufferFormat;
    msaaSupport.SampleCount = 4;
    msaaSupport.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
    msaaSupport.NumQualityLevels = 0;
    ThrowIfFailed(m_Device->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msaaSupport,
        sizeof(msaaSupport)
        ));
    m4xMsaaQuality = msaaSupport.NumQualityLevels;
    assert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");

    #if defined(DEBUG) || defined(_DEBUG)
    {
        LogAdapter();
    }
    #endif
    CreateCommandObjects();
    createSwapChain();
    createRtvAndDsvDescriptorHeaps();
    return true;
}
}

void D3DApp::CreateCommandObjects()
{
    D3D12_COMMAND_QUEUE_DESC commandQueueDesc{};
    commandQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    commandQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    ThrowIfFailed(m_Device->CreateCommandQueue(&commandQueueDesc, IID_PPV_ARGS(m_CommandQueue.GetAddressOf())));

    ThrowIfFailed(m_Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(m_CommandAllocator.GetAddressOf())));

    ThrowIfFailed(m_Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,
        m_CommandAllocator.Get(), nullptr, IID_PPV_ARGS(m_CommandList.GetAddressOf())));
    m_CommandList->Close();
}

void D3DApp::createSwapChain()
{
    DXGI_SWAP_CHAIN_DESC swapChainDesc{};
    swapChainDesc.BufferDesc.Width = m_ClientWidth;
    swapChainDesc.BufferDesc.Height = m_ClientHeight;
    swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
    swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
    swapChainDesc.BufferDesc.Format = m_BackBufferFormat;
    swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

    swapChainDesc.SampleDesc.Count = m4xMsaaQuality > 0 ? 4 : 1;
    swapChainDesc.SampleDesc.Quality = m4xMsaaQuality > 0 ? (m4xMsaaQuality - 1) : 0;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.BufferCount = SwapChainBufferCount;
    swapChainDesc.OutputWindow = m_Window;
    swapChainDesc.Windowed = true;
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

    ThrowIfFailed(m_Factory->CreateSwapChain(m_CommandQueue.Get(), &swapChainDesc, m_SwapChain.GetAddressOf()));
}

void D3DApp::createRtvAndDsvDescriptorHeaps()
{
    D3D12_DESCRIPTOR_HEAP_DESC descriptorDesc{};
    descriptorDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
    descriptorDesc.NumDescriptors = SwapChainBufferCount;
    descriptorDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    descriptorDesc.NodeMask = 0;
    ThrowIfFailed(m_Device->CreateDescriptorHeap(&descriptorDesc, IID_PPV_ARGS(m_RtvDescriptorHeap.GetAddressOf())));

    D3D12_DESCRIPTOR_HEAP_DESC DsvDescriptorDesc{};
    DsvDescriptorDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
    DsvDescriptorDesc.NumDescriptors = 1;
    DsvDescriptorDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    DsvDescriptorDesc.NodeMask = 0;
    ThrowIfFailed(m_Device->CreateDescriptorHeap(&DsvDescriptorDesc, IID_PPV_ARGS(m_DsvDescriptorHeap.GetAddressOf())));
}

这里第四章中是针对显卡信息,和显卡绑定的显示输出,以及显示输出对于特定format的显示模式进行输出

void D3DApp::LogAdapter()
{
	IDXGIAdapter* adapter;
	UINT i = 0;
	std::vector<IDXGIAdapter*> adapterList;
	while (m_Factory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND)
	{
		DXGI_ADAPTER_DESC adapterDesc{};
		adapter->GetDesc(&adapterDesc);

		std::wstring text = L"***Adapter: ";
		text += adapterDesc.Description;
		text += L"\n";

		OutputDebugString(text.c_str());

		adapterList.push_back(adapter);
		++i;
	}
	
	for (size_t i = 0; i < adapterList.size(); i++)
	{
		LogAdapterOutputs(adapterList[i]);
		adapterList[i]->Release();
	}
}

void D3DApp::LogAdapterOutputs(IDXGIAdapter* adapter)
{
	UINT i = 0;
	IDXGIOutput* output;
	while (adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND)
	{
		DXGI_OUTPUT_DESC outputDesc{};
		output->GetDesc(&outputDesc);
		std::wstring text = L"***output: ";
		text += outputDesc.DeviceName;
		text += L"\n";
		OutputDebugString(text.c_str());

		LogOutputDisplayModes(output, m_BackBufferFormat);

		output->Release();
	}
}

void D3DApp::LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format)
{
	UINT count = 0;
	UINT flag = 0;
	output->GetDisplayModeList(format, flag, &count, nullptr);

	std::vector<DXGI_MODE_DESC> modeList(count);
	output->GetDisplayModeList(format, flag, &count, modeList.data());

	for (auto& x : modeList)
	{
		UINT n = x.RefreshRate.Numerator;
		UINT d = x.RefreshRate.Denominator;
		std::wstring text =
			L"Width = " + std::to_wstring(x.Width) + L" " +
			L"Height = " + std::to_wstring(x.Height) + L" " +
			L"Refresh = " + std::to_wstring(n) + L"/" + std::to_wstring(d) +
			L"\n";
		OutputDebugString(text.c_str());
	}
}

resize

resize实际上就是窗口初始化或者每次改变窗口的时候图像的对应操作。对于resize首先就是需要resize 输出的buffer,resizedsv, resetSwapchain,然后就是创建rtv, dsv

最后是执行命令和存储视图

首先我们在resize之前保证,我们之前的命令执行完毕,这里就是设置这个同步机制,保证之前的命令结束

void D3DApp::FlushCommandQueue()
{
	m_CurrentFence++;

	ThrowIfFailed(m_CommandQueue->Signal(m_Fence.Get(), m_CurrentFence));

	if (m_Fence->GetCompletedValue() < m_CurrentFence)
	{
		HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
		ThrowIfFailed(m_Fence->SetEventOnCompletion(m_CurrentFence, eventHandle));

		WaitForSingleObject(eventHandle, INFINITE);
		CloseHandle(eventHandle);
	}
}
void D3DApp::OnResize()
{
	assert(m_Device);
	assert(m_SwapChain);
	assert(m_CommandAllocator);
	//flush everything before have finished
	FlushCommandQueue();
	ThrowIfFailed(m_CommandList->Reset(m_CommandAllocator.Get(), nullptr));

	for (int i = 0; i < SwapChainBufferCount; i++)
	{
		m_SwapchainBuffers[i].Reset();
	}
	m_DepthStencileBuffer.Reset();

	ThrowIfFailed(m_SwapChain->ResizeBuffers(SwapChainBufferCount,
		m_ClientWidth, m_ClientHeight,
		m_BackBufferFormat, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH));

	CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_RtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
	for (int i = 0; i < SwapChainBufferCount; i++)
	{
		ThrowIfFailed(m_SwapChain->GetBuffer(i, IID_PPV_ARGS(m_SwapchainBuffers[i].GetAddressOf())));
		m_Device->CreateRenderTargetView(m_SwapchainBuffers[i].Get(), nullptr, rtvHandle);
		rtvHandle.Offset(1, m_RTVDescriptorSize);
	}

	D3D12_RESOURCE_DESC depthResouceDesc{};
	depthResouceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
	depthResouceDesc.Alignment = 0;
	depthResouceDesc.Width = m_ClientWidth;
	depthResouceDesc.Height = m_ClientHeight;
	depthResouceDesc.DepthOrArraySize = 1;
	depthResouceDesc.MipLevels = 1;
	depthResouceDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;
	depthResouceDesc.SampleDesc.Count = m4xMsaaQuality > 0 ? 4 : 1;
	depthResouceDesc.SampleDesc.Quality = m4xMsaaQuality > 0 ? (m4xMsaaQuality - 1) : 0;
	depthResouceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
	depthResouceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

	D3D12_CLEAR_VALUE optClear;
	optClear.DepthStencil.Depth = 1.0f;
	optClear.DepthStencil.Stencil = 0;
	optClear.Format = m_DepthStencilFormat;
	ThrowIfFailed(m_Device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
		D3D12_HEAP_FLAG_NONE, &depthResouceDesc, D3D12_RESOURCE_STATE_COMMON, &optClear, IID_PPV_ARGS(m_DepthStencileBuffer.GetAddressOf())));

	D3D12_DEPTH_STENCIL_VIEW_DESC depthStencilDesc{};
	depthStencilDesc.Flags = D3D12_DSV_FLAG_NONE;
	depthStencilDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
	depthStencilDesc.Format = m_DepthStencilFormat;
	depthStencilDesc.Texture2D.MipSlice = 0;

	m_Device->CreateDepthStencilView(m_DepthStencileBuffer.Get(), &depthStencilDesc, DepthStencilView());

	m_CommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_DepthStencileBuffer.Get(),
		D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE));

	// Execute the resize commands.
	ThrowIfFailed(m_CommandList->Close());
	ID3D12CommandList* cmdsLists[] = { m_CommandList.Get() };
	m_CommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
		// Wait until resize is complete.
	FlushCommandQueue();

	m_ScreenViewport.TopLeftX = 0;
	m_ScreenViewport.TopLeftY = 0;
	m_ScreenViewport.Width = static_cast<float>(m_ClientWidth);
	m_ScreenViewport.Height = static_cast<float>(m_ClientHeight);
	m_ScreenViewport.MinDepth = 0.0f;
	m_ScreenViewport.MaxDepth = 1.0f;

	m_ScissorRect = { 0, 0, m_ClientWidth, m_ClientHeight };
}

实际的使用类,书中例子

//***************************************************************************************
// Init Direct3D.cpp by Frank Luna (C) 2015 All Rights Reserved.
//
// Demonstrates the sample framework by initializing Direct3D, clearing 
// the screen, and displaying frame stats.
//
//***************************************************************************************

#include "../../Common/d3dApp.h"
#include <DirectXColors.h>

using namespace DirectX;

class InitDirect3DApp : public D3DApp
{
public:
	InitDirect3DApp(HINSTANCE hInstance);
	~InitDirect3DApp();

	virtual bool Initialize()override;

private:
    virtual void OnResize()override;
    virtual void Update(const GameTimer& gt)override;
    virtual void Draw(const GameTimer& gt)override;

};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
				   PSTR cmdLine, int showCmd)
{
	// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

    try
    {
        InitDirect3DApp theApp(hInstance);
        if(!theApp.Initialize())
            return 0;

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

InitDirect3DApp::InitDirect3DApp(HINSTANCE hInstance)
: D3DApp(hInstance) 
{
}

InitDirect3DApp::~InitDirect3DApp()
{
}

bool InitDirect3DApp::Initialize()
{
    if(!D3DApp::Initialize())
		return false;
		
	return true;
}

void InitDirect3DApp::OnResize()
{
	D3DApp::OnResize();
}

void InitDirect3DApp::Update(const GameTimer& gt)
{

}

void InitDirect3DApp::Draw(const GameTimer& gt)
{
    // Reuse the memory associated with command recording.
    // We can only reset when the associated command lists have finished execution on the GPU.
	ThrowIfFailed(mDirectCmdListAlloc->Reset());

	// A command list can be reset after it has been added to the command queue via ExecuteCommandList.
    // Reusing the command list reuses memory.
    ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));

	// Indicate a state transition on the resource usage.
	mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
		D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));

    // Set the viewport and scissor rect.  This needs to be reset whenever the command list is reset.
    mCommandList->RSSetViewports(1, &mScreenViewport);
    mCommandList->RSSetScissorRects(1, &mScissorRect);

    // Clear the back buffer and depth buffer.
	mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
	mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
	
    // Specify the buffers we are going to render to.
	mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());
	
    // Indicate a state transition on the resource usage.
	mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
		D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));

    // Done recording commands.
	ThrowIfFailed(mCommandList->Close());
 
    // Add the command list to the queue for execution.
	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
	
	// swap the back and front buffers
	ThrowIfFailed(mSwapChain->Present(0, 0));
	mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;

	// Wait until frame commands are complete.  This waiting is inefficient and is
	// done for simplicity.  Later we will show how to organize our rendering code
	// so we do not have to wait per frame.
	FlushCommandQueue();
}

我的使用

#include "D3DApp.h"

class InitDirect3DApp : public D3DApp
{
	InitDirect3DApp(HINSTANCE hInstance)
		:D3DApp(hInstance)
	{ }
	~InitDirect3DApp();

	virtual bool Initialize() override;

private:
	virtual void OnResize() override;
	virtual void Update() override;
	virtual void Draw() override;
};

InitDirect3DApp::~InitDirect3DApp()
{
}

bool InitDirect3DApp::Initialize()
{
	if (!D3DApp::Initialize())
		return false;
	return true;
}

void InitDirect3DApp::OnResize()
{
	D3DApp::OnResize();
}

void InitDirect3DApp::Update()
{
}

void InitDirect3DApp::Draw()
{
	ThrowIfFailed(m_CommandAllocator->Reset());
	ThrowIfFailed(m_CommandList->Reset(m_CommandAllocator.Get(), nullptr));

	m_CommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
		D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));

	m_CommandList->RSSetViewports(1, &m_ScreenViewport);
	m_CommandList->RSSetScissorRects(1, &m_ScissorRect);

	m_CommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());
	m_CommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
		D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));

	ThrowIfFailed(m_CommandList->Close());

	ID3D12CommandList* cmdsLists[] = { m_CommandList.Get() };
	m_CommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	ThrowIfFailed(m_SwapChain->Present(0, 0));
	m_CurrentBackBuffer = (m_CurrentBackBuffer + 1) % SwapChainBufferCount;

	FlushCommandQueue();
}


显示

这里写了一个main的方法去使用

int WINAPI main(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, int showCmd)
{
#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
	try
	{
		InitDirect3DApp theApp(hInstance);
		if (!theApp.Initialize())
			return 0;
		return theApp.Run();
	}
	catch (std::exception& e)
	{
		std::cerr << e.what() << std::endl;
	}
}

说实话,我现在还是一头雾水,先开第五章了

 


网站公告

今日签到

点亮在社区的每一天
去签到