创建windows窗口
Direct3D初始化
消息循环
渲染图形
应用程序结束,清除COM对象,程序退出
一种令DirectX不受编程语言限制,使之向后兼容的技术。通常COM被视为一种接口,亦可视为一个C++类
我们不需知道COM的具体实现细节,亦无需了解其工作原理,只需知道如何获取和释放COM接口
获取COM接口的指针并非使用new,而是借助特定的函数或COM接口的方法。且COM对象与C++类的生命周期有很大区别,而COM对象则通过控制对象的引用次数个数决定生命周期(智能指针)
Get:返回指向COM接口的指针
GetAddressOf:返回指向COM接口指针的地址
Reset:将此ComPtr置为nullptr释放与之相关的所有引用
COM接口都以大写字母”I“开头。如,表示命令列表的COM接口:ID3D12GraphicsCommandList
Direct3D工作模式与电影相似。为避免动画出现画面闪烁,应将动画帧完整地绘制在后台缓冲区的离屏纹理内,并会以完整的帧画面显示在屏幕上
Direct3D使用一种称作交换链的技术,让画面可平滑过渡。而交换链则由两个或两个以上的表面组成,每个表面储存2D图形的一个线性数组,其中每个元素都表示屏幕上的一个像素
对于三维物体来说,则还需要一个称作深度的信息,Direct3D使用深度缓冲区为最终绘制的图像的每个像素都存储一个深度信息,而深度缓冲区只包含深度信息,不包含图像信息
前台缓冲区存储当前显示在屏幕上的图像数据,后台缓冲区则存储动画的下一帧,后台缓冲区存储完成则,前后台缓冲区角色互换:后台缓冲区变为前台缓冲区呈现新一帧的画面,而前台缓冲区则为存储新一帧转为后台缓冲区,如此反复
即,通过一系列后台缓冲区生成动画帧,再通过交换链技术,提交到到屏幕上显示。亦或是,按顺序逐个提交到前台显示的的多个后台缓冲区的集合
呈现(提交、显示):前后台缓冲区互换。通过交换指向内存的指针实现,而非复制
IDXGISwapChain:表示交换链。存储了前后台缓冲区两种纹理
IDXGISwapChain::ReSizeBuffers:修改缓冲区大小
IDXGISwapChain::Present:呈现缓冲区
双缓冲:使用两个缓冲区
三重缓冲:使用三个缓冲区
尽管后台缓冲区是一个纹理,但仍将其组成元素成为像素。因为后台缓冲区存储的内容为颜色信息,即便纹理中存储的不是颜色信息,又是也称纹理的元素为像素
渲染过程中,GPU可能会对资源进行读写,在发出命令前,需要将本次调用的资源绑定到渲染流水线上,但GPU资源并非直接绑定到渲染流水线上,而是通过描述符对象来对资源间接引用
描述符:一种轻量级结构且是中间层。对送往GPU的资源进行描述
为何使用描述符?
GPU资源实质为普通的内存块,由于资源的通用性,它们可以被送往渲染流水线不同阶段供其使用
告知Direct3D此资源被绑定在渲染流水线哪个阶段
借助描述符指定欲绑定资源中的局部数据
视图(view)与描述符(descriptor)为同义词
描述符堆:存放程序中某特定类型描述符的一块内存。需为每一种类型的描述符都创建单独的描述符堆
可用多个描述符引用同一资源
创建描述符的最佳实机为初始化期间。因为此时需要执行类型的检测和验证工作
采样:把一个函数离散化的过程。
屏幕中显示的像素不可能为无穷小,因此不是任意一条直线都能在显示器上平滑地呈现出来
走样:光栅化的图形显示器用离散量来表示连续量,因为其中采样的频率不满足Nyquist采样定理引起的信息失真,而造成图片具有锯齿状(走样原理请看GAME101闫老师的课)
基于超采样的反走样方法:
SSAA(Super Sample Anti-Aliasing)
原理:在每个像素内取多个子采样点,对子采样点进行颜色计算,再合成此像素最终的颜色
缺点:计算量过大,内存占用,带宽
MSAA(Multisample Anti-Aliasing)
MSAA描述符结构体:
typedef struct DXGI_SAMPLE_DESC { UINT Count; //每个像素采样次数 UINT Qualitiy; //图像质量级别 }
查询质量级别:ID3D12Device::CheckFeatureSupport方法
创建交换链缓冲区和深度缓冲区时都需填写DXGI_SAMPLE_DESC结构体(Direct3D 12不支持创建MSAA交换链)。创建后台缓冲区和深度缓冲区时,多重采样的有关设置需相同
功能级别:以枚举类型D3D_FEATURE_LEVEL表示(9到11版本)
enum D3D_FEATURE_LEVEL { D3D_FEATURE_LEVEL_9_1 = 0x9100, D3D_FEATURE_LEVEL_9_2 = 0x9200, D3D_FEATURE_LEVEL_9_3 = 0x9300, D3D_FEATURE_LEVEL_10_0 = 0xa000, D3D_FEATURE_LEVEL_10_1 = 0xa100, D3D_FEATURE_LEVEL_11_0 = 0xb000, D3D_FEATURE_LEVEL_11_1 = 0xb100 }
功能级别为不同级别所支持的功能进行了严格的界定(详细请查阅SDK文档)
若用户的硬件不支持某特定功能级别,应用程序应回退至版本更低的功能级别
一组与Direct3D配合使用的API。设计的基本理念是使多种图形API中所共有的底层任务都能借助一种通用API处理
IDXGIFactory:主要用于创建IDXGISwapChain接口以及枚举显示适配器
显示适配器:硬件设备(如独立显卡),真正实现图形处理能力。但系统也可以用软件显示适配器来模拟硬件的图形处理功能
一个系统可能存在多个显示适配器。而适配器用接口IDXGIAdapter表示
一个系统也可能有多个显示设备,称每个显示设备都是一个显示输出,以IDXGIOutput接口表示。每个适配器与一组显示输出关联
进入全屏模式,枚举显示模式会显得尤为重要。为获得最优的全屏性能,指定的显示模式一定要与显示器支持的显示模式完全匹配
复杂的游戏会运用大量资源,但其中大多数并不需要总是置于显存中供GPU使用
Direct3D 12中,应用程序通过控制资源在显存中的去留,主动管理资源的驻留情况(无论资源是否已位于显存中,都可对其进行管理。Direct3D 11则由系统自动管理)
基本思路:使应用程序占用最小的显存空间
程序应避免短时间内于显存中交换进出相同的资源,会引起过高的开销
一般来说,资源创建时驻留在显存中,而它被销毁时则清出。但我们也可以自己控制资源的驻留
进行图形编程时,CPU和GPU两种处理器在参与工作。两者并行工作,有时也许同步。但为了获得最佳性能,尽量让两者同时工作,而非同步
GPU至少维护一个命令队列(环形缓冲区.ring buffer)。借助Direct3D API,CPU可利用命令列表将命令提交至此队列
当一系列命令被提交至命令队列时,GPU并不会立即执行它们
尽量保持cpu和GPU同时忙碌,以充分利用硬件资源
命令队列以ID3D12CommandQueue接口表示。先填写D3D12_COMMAND_QUEUE_DESC结构体描述队列,再调用ID3D12Device::CreateCommandQueue方法创建队列
IID_PPV_ARGS宏定义:
#define IID_ppv_ARGS(ppType) __uuidof(**(ppType), IID_PPV_ARGS_Helper(ppType))
__uuidof(**(ppType))将获取(**ppType)的COM接口ID,即为ID3D12CommandQueue接口的COM ID(globally unique identifier 全局唯一标识符)
IID_PPV_ARGS本质时将ppType强制转换为void类型**。因为调用Direct3D 12创建接口实例的API时,大多都有一个参数为类型void**的待创接口COM ID
ExecuteCommandLists方法将命令列表中的命令添加到命令队列:
ID3D12GraphicsCommandList接口封装了图形渲染命令,此接口有多种方法向命令列表添加命令,继承于ID3D12CommandList
将命令加入命令列表,并不会立即执行。而调用ExecuteCommandLists方法才将命令送入命令队列
当命令都被加入命令列表后,需调用ID3D12GraphicsCommandList::Close方法结束命令的记录
调用ID3D12CommandQueue::ExecutrCommandLists方法提交命令列表前,需将其关闭
ID3D12CommandAllocator内存管理类接口,与命令列表有关。记录在命令列表有关的命令,实际上存储在与之关联的命令分配器。当ID3D12CommandQueue::ExecuteCommandLists方法执行命令列表时,命令队列则去引用分配器的命令
ID3D12Device创建命令分配器:ID3D12Device::CreateCommandAllocator
ID3D12Device创建命令列表:ID3D12Device::CreateCommandList
ID3D12Device::GetNodeCount方法来查询系统中GPU适配器节点(物理GPU)的数量
可以创建多个关联与同一个命令分配器的命令列表,但不可同时使用它们来记录命令。因此,当其中一个命令列表记录命令时,需关闭同一命令分配器的其他命令列表
在调用ID3D12CommandList::ExecuteCommandLists(C)方法后,可通过ID3D12GraphicsCommandList::Reset方法安全地复用命令列表C占用的相关底层内存来记录新的命令。此方法将命令列表恢复为刚创建时的初始状态,就可以借此继续复用其底层内存,同时也避免释放旧列表再创建新列表的繁琐操作
重置命令列表并不会影响命令队列中的命令,因为相关的命令分配器仍在维护内存中被命令队列引用的命令
向GPU提交渲染命令后,可能要为了绘制下一帧而复用命令分配器的内存,此时使用ID3D12CommandAllocator::Reset方法
由于命令队列可能会引用命令分配器的数据,因此在没有确定GPU执行完命令分配器的所有命令前,不可重置命令分配器
CPU和GPU的同步
假设有一资源R存有位置信息,现令CPU对R中的数据进行更新,先把其位置信息改为p1,在向命令队列添加绘制资源R的命令C。由于向命令队列添加命令不会阻塞CPU,因此CPU会继续执行后续指令。在GPU执行绘制命令C前,若CPU率先覆写了数据R,把位置信息修改为p2,此行为将会造成严重的错误
我们可以强制CPU等待,直到GPU完成所有命令的处理,达到某个指定的围栏点为止。此方法称为刷新命令队列,通过围栏实现
为实现常见的渲染效果,需要通过GPU对资源进行先写后读的操作。然而,当GPU的写操作还没开始或未完成,却开始读取资源,会导致资源冒险。为此,Direct3D专门针对资源设计了一组相关状态。资源在刚创建时会处于默认状态,此状态可以一直持续到应用程序通过Direct3D将其转换为另一种状态,如此使得GPU能针对资源状态转换与防止资源冒险做出适当的行为
根据Direct3D给出的转换信息,GPU可以采取适当的措施避免资源冒险的发生
资源转换带来的负荷会造成程序性能下降。一个自动跟踪状态转换的系统也在增加程序的开销
通过命令列表设置转换资源屏障数组,即可指定资源的转换。特别是希望以一次API调用来转换多个资源
D3D12_RESOURCE_BARRIER结构体表示资源屏障
初始化Direct3D步骤: