C/C++教程

Direct3D初始化

本文主要是介绍Direct3D初始化,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

何为Direct3D 12?

  • 一组底层应用程序接口,可以对GPU进行控制和编程。凭此即可以硬件加速的方式渲染出3D场景

Direct3D流程

  • 创建windows窗口

    Direct3D初始化

    消息循环

    渲染图形

    应用程序结束,清除COM对象,程序退出

COM对象接口

  • 一种令DirectX不受编程语言限制,使之向后兼容的技术。通常COM被视为一种接口,亦可视为一个C++类

  • 我们不需知道COM的具体实现细节,亦无需了解其工作原理,只需知道如何获取和释放COM接口

  • 获取COM接口的指针并非使用new,而是借助特定的函数或COM接口的方法。且COM对象与C++类的生命周期有很大区别,而COM对象则通过控制对象的引用次数个数决定生命周期(智能指针

    Get:返回指向COM接口的指针

    GetAddressOf:返回指向COM接口指针的地址

    Reset:将此ComPtr置为nullptr释放与之相关的所有引用

  • COM接口都以大写字母”I“开头。如,表示命令列表的COM接口:ID3D12GraphicsCommandList

纹理格式:

  • 1D,2D,3D纹理实际相当于特定数据元素构成的1D,2D,3D数组
  • 纹理不仅可以存储2D图像数据,亦可存储3D向量
  • 尽管纹理常被认为是用来存储图像数据的,但实际用途十分广泛。如:mipmap,数据数组
  • 并不是任意类型的数据元素都能用于组成纹理,只可存储DXGI_FORMAT枚举类型中描述的特定格式的数据元素
  • 无类型格式的纹理仅预留内存

交换链和页面翻转

  • Direct3D工作模式与电影相似。为避免动画出现画面闪烁,应将动画帧完整地绘制在后台缓冲区的离屏纹理内,并会以完整的帧画面显示在屏幕上

  • Direct3D使用一种称作交换链的技术,让画面可平滑过渡。而交换链则由两个或两个以上的表面组成,每个表面储存2D图形的一个线性数组,其中每个元素都表示屏幕上的一个像素

  • 对于三维物体来说,则还需要一个称作深度的信息,Direct3D使用深度缓冲区为最终绘制的图像的每个像素都存储一个深度信息,而深度缓冲区只包含深度信息,不包含图像信息

  • 前台缓冲区存储当前显示在屏幕上的图像数据,后台缓冲区则存储动画的下一帧,后台缓冲区存储完成则,前后台缓冲区角色互换:后台缓冲区变为前台缓冲区呈现新一帧的画面,而前台缓冲区则为存储新一帧转为后台缓冲区,如此反复

    image-20220909125939545

    即,通过一系列后台缓冲区生成动画帧,再通过交换链技术,提交到到屏幕上显示。亦或是,按顺序逐个提交到前台显示的的多个后台缓冲区的集合

  • 呈现(提交、显示):前后台缓冲区互换。通过交换指向内存的指针实现,而非复制

  • IDXGISwapChain:表示交换链。存储了前后台缓冲区两种纹理

    IDXGISwapChain::ReSizeBuffers:修改缓冲区大小

    IDXGISwapChain::Present:呈现缓冲区

  • 双缓冲:使用两个缓冲区

    三重缓冲:使用三个缓冲区

  • 尽管后台缓冲区是一个纹理,但仍将其组成元素成为像素。因为后台缓冲区存储的内容为颜色信息,即便纹理中存储的不是颜色信息,又是也称纹理的元素为像素

z-buffer

  • 深度缓冲区:此纹理资源存储像素的深度信息,深度值为0.0~1.0。0.0代表观察者在能看到的空间范围中能看到的离自己最近的物体;1.0则是能看到的最远的物体
  • 深度缓冲区的元素和后台缓冲区内的像素成一一对应关系
  • z-buffer:计算每个像素的深度值,并执行深度测试,最小深度值的像素则胜出被写入后台缓冲区。若使用了z-buffer,则物体的绘制顺序也就无关紧要
  • 一个程序不一定要用到模板缓冲区。但一旦使用,则深度缓冲区和模板缓冲区应如影随形

资源与描述符

  • 渲染过程中,GPU可能会对资源进行读写,在发出命令前,需要将本次调用的资源绑定到渲染流水线上,但GPU资源并非直接绑定到渲染流水线上,而是通过描述符对象来对资源间接引用

  • 描述符:一种轻量级结构且是中间层。对送往GPU的资源进行描述

  • 为何使用描述符?

    • GPU资源实质为普通的内存块,由于资源的通用性,它们可以被送往渲染流水线不同阶段供其使用

    • 告知Direct3D此资源被绑定在渲染流水线哪个阶段

    • 借助描述符指定欲绑定资源中的局部数据

  • 视图(view)与描述符(descriptor)为同义词

  • 描述符堆:存放程序中某特定类型描述符的一块内存。需为每一种类型的描述符都创建单独的描述符堆

  • 可用多个描述符引用同一资源

  • 创建描述符的最佳实机为初始化期间。因为此时需要执行类型的检测和验证工作

MSAA

  • 采样:把一个函数离散化的过程。

    屏幕中显示的像素不可能为无穷小,因此不是任意一条直线都能在显示器上平滑地呈现出来

  • 走样:光栅化的图形显示器用离散量来表示连续量,因为其中采样的频率不满足Nyquist采样定理引起的信息失真,而造成图片具有锯齿状(走样原理请看GAME101闫老师的课)

  • 基于超采样的反走样方法:

    • SSAA(Super Sample Anti-Aliasing)

      • 原理:在每个像素内取多个子采样点,对子采样点进行颜色计算,再合成此像素最终的颜色

      • 缺点:计算量过大,内存占用,带宽

    • MSAA(Multisample Anti-Aliasing)

      • 原理:与SSAA一样将每个像素内取多个子采样点,但其子采样点的颜色和像素颜色值相同,不单独计算。每个子像素则计算自己的Z值和模板值,并根据几何覆盖信息进行处理

利用Direct3D进行多重采样

  • 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文档)

  • 若用户的硬件不支持某特定功能级别,应用程序应回退至版本更低的功能级别

DiectX图形基础结构(DirectX Graphics Infrastructure)

  • 一组与Direct3D配合使用的API。设计的基本理念是使多种图形API中所共有的底层任务都能借助一种通用API处理

    • 如:2D、3D渲染都要用到交换链和页面反转功能,IDXGISwapChain
  • IDXGIFactory:主要用于创建IDXGISwapChain接口以及枚举显示适配器

  • 显示适配器:硬件设备(如独立显卡),真正实现图形处理能力。但系统也可以用软件显示适配器来模拟硬件的图形处理功能

  • 一个系统可能存在多个显示适配器。而适配器用接口IDXGIAdapter表示

  • 一个系统也可能有多个显示设备,称每个显示设备都是一个显示输出,以IDXGIOutput接口表示。每个适配器与一组显示输出关联

  • 进入全屏模式,枚举显示模式会显得尤为重要。为获得最优的全屏性能,指定的显示模式一定要与显示器支持的显示模式完全匹配

功能支持的检测

  • ID3D12Device::CheckFeatureSupport方法可以检测当前图形驱动对多重采样的支持

资源驻留

  • 复杂的游戏会运用大量资源,但其中大多数并不需要总是置于显存中供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方法将命令列表中的命令添加到命令队列:

    • GPU从数组里第一个命令开始顺序执行
  • 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完成所有命令的处理,达到某个指定的围栏点为止。此方法称为刷新命令队列,通过围栏实现

      • ID3D12Fence接口表示围栏,创建一个围栏对象的方法:ID3D12Device::CreateFence

资源转换

  • ​ 为实现常见的渲染效果,需要通过GPU对资源进行先写后读的操作。然而,当GPU的写操作还没开始或未完成,却开始读取资源,会导致资源冒险。为此,Direct3D专门针对资源设计了一组相关状态。资源在刚创建时会处于默认状态,此状态可以一直持续到应用程序通过Direct3D将其转换为另一种状态,如此使得GPU能针对资源状态转换与防止资源冒险做出适当的行为

  • 根据Direct3D给出的转换信息,GPU可以采取适当的措施避免资源冒险的发生

  • 资源转换带来的负荷会造成程序性能下降。一个自动跟踪状态转换的系统也在增加程序的开销

  • 通过命令列表设置转换资源屏障数组,即可指定资源的转换。特别是希望以一次API调用来转换多个资源

  • D3D12_RESOURCE_BARRIER结构体表示资源屏障

命令与多线程

  • Direct3D的设计目的时提供一个高效的多线程环境,命令列表则可以发挥多线程优势
  • 对于庞大场景而言,仅构建一个命令列表绘制场景会占用不少CPU时间。因此,可以采用并行创建命令列表
  • 命令列表非自由线程对象。多线程不可同时共享同一个命令列表,亦不能同时调用同一命令列表的方法。因此每个线程通常只使用各自的命令列表
  • 命令分配器非自由线程对象。多线程不可同时共享同一个命令分配器,亦不能同时调用同一命令分配器的方法。因此每个线程只使用自己的命令分配器
  • 命令队列为线程自由对象。多线程可同时访问同一命令队列,也可同时调用它的方法,每个线程可向命令队列提交它们自己的命令列表
  • 出于性能考虑,应用程序需在初始化期间指出用于并行记录命令的命令列表的最大数量

初始化Direct3D

  • 初始化Direct3D步骤:

    • 启用调试层
    • D3D12CreateDevice函数创建ID3D12Device接口实例
    • 创建一个ID3D12Fence对象,并查询描述符大小
    • 检测用户设备对4X MSAA质量级别的支持情况
    • 依次创建命令队列、命令列表分配器和主命令列表
    • 描述并创建交换链
    • 创建应用程序所需的描述符堆
    • 调整后台缓冲区大小,并为它创建渲染目标视图
    • 创建深度/模板缓冲区及与之相关的深度/模板视图
    • 设置视口和裁剪矩阵
这篇关于Direct3D初始化的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!