Practical Rendering and Computation with Direct3D 11
+ -

1.5开始

2024-06-13 11 0

本章的这一部分专门为读者提供如何运行基本Direct3D11应用程序的速成课程。这里提供的描述介绍了用于与Direct3D11交互的许多基本概念,并介绍了在通用应用中使用的基本程序流。这是一个基本的介绍,重点是使用Direct3D11需要什么操作,因此它不会解释Win32编程的所有细节。在本节的最后,我们将讨论本书提供的示例应用程序所使用的应用程序框架,该框架将大多数直接的Win32交互从开发人员那里抽象出来。

1.5.1与Direct3D 11交互

在了解实际的应用程序细节之前,我们首先需要介绍一些基本的Direct3D11概念。这包括Direct3D所在的基于COM的框架,以及了解如何调用和评估应用程序必须使用的各种API函数的成功。

COM基本接口

Direct3D 11以与先前版本的Direct3D相同的方式被实现为一系列组件对象模型(COM)接口。如果您不熟悉COM,那么对于API来说,这似乎是一个复杂问题。然而,一旦您了解了使用COM的一些设计含义,就会清楚为什么选择它来实现Direct3D11。我们不会深入研究COM,但我们将介绍应用程序可能使用的一些最明显的细节。COM提供了一个对象模型,该模型定义了所有COM对象必须实现的一组基本接口方法。这包括对COM对象执行引用计数的方法,以及允许在运行时检查对象以发现其实现的接口的方法。

引用计数

应用程序将主要对COM对象的引用计数行为感兴趣,因为它用于管理它们的生存期。当函数使用创建基于Direct3D的COM对象时,通常在应用程序提供的指针中返回对该对象的引用,作为函数调用的参数。对象总是通过函数或接口方法创建并以这种方式返回,而不是直接使用“new”运算符创建类的实例

当返回对象引用时,它的引用计数通常为1。当应用程序使用完对象后,它将调用对象的Release方法,这表示应该减少对象的引用计数。执行此操作,而不是使用“delete”操作符直接释放对象。调用Release方法后,应用程序应该清除指针引用,并将其视为直接删除的指针引用。只要应用程序准确地释放其对所创建对象的所有引用,COM框架就会管理对象的创建和销毁。

这种用法允许应用程序和Direct3D11运行时共享具有多个引用的对象。当资源作为渲染目标绑定到管道时,运行时会引用它,以确保在使用对象时不会删除该对象。类似地,如果应用程序和运行时都有对对象的引用,并且方法调用消除了运行时引用,则应用程序可以确保该对象在释放其引用之前不会被破坏。此引用计数对象管理适用于从IUnknown接口继承的任何Direct3D 11对象,并且必须遵守此管理以确保在应用程序终止后没有对象未释放。

接口查询

如上所述,IUnknown接口还提供了一些方法,用于查询对象以查找它实现的其他接口。这是通过IUnknown::QueryInterface()方法执行的,该方法使用一个通用唯一标识符(UUID)来标识所需接口,然后使用指向指针的指针,如果对象实现了所请求的接口,则该指针将填充适当的对象引用。如果在方法调用中成功返回了这样的引用,则该对象的引用计数会增加,并且应该以与任何其他对象相同的方式进行管理。

在Direct3D中,此机制主要与ID3D1 IDevice接口结合使用。有几种不同的情况需要额外的设备接口,例如获取设备的调试接口或从Direct3D设备获取DXGI设备接口。我们偶尔会在本文中看到这种方法的实际应用,并在出现时注意到其过程。

COM对象的一些其他属性没有像Direct3D11那样频繁地使用,例如二进制接口的语言独立性,或者对象可以从远程计算机实例化和使用的事实。这些用途超出了本书的范围,但仍然很有趣。有关COM的更多信息可以在MSDN网站上找到,由于该技术的使用寿命,在线上还有许多其他资源。

解释API调用结果

Direct3D11还实现了处理来自函数和方法的返回代码的标准方式。通常,方法的成功或失败由返回代码指示,返回代码作为函数调用的返回值提供给应用程序。此返回代码实现为HRESULT值,它实际上只是一个长整型变量。

可能的返回值列表可以在DXSDK文档中找到,并且可以在不同版本之间添加其他返回代码。在某些情况下,这些返回代码并不能清楚地指示方法的成功或失败,而且代码的解释通常取决于返回代码的上下文。为了简化这些返回代码的测试,可以使用一个简单的宏来测试方法是否失败。清单1.1显示了FAILED宏是如何使用的。

HRESULT hr = m_pDevice->CreateBlendState( &Config, SpState );
if ( FAILED( hr ) )
{
    Log::Get().Write( L"Failed to create blend state!" );
    return( -1 );
}

通常,此宏返回真值的任何情况都应被视为错误并进行相应处理。如果宏返回一个假值,则表示该方法已成功,并且应被视为没有产生错误。这使得测试方法调用的结果更加简单,并且可以以相对简单的方式进行处理。

1.5.2应用需求

有了COM和HRESULT值的基础知识,我们可以继续研究Win32应用程序使用Direct3D11到底必须做什么。我们将首先概述应用程序的全部职责,然后更详细地了解它必须实现的每个流程。

程序流程图概述

Direct3D11应用程序的生命周期遵循与大多数其他Win32应用程序非常相似的顺序。该标准序列的框图如图1.3所示。
092132388782

从图1.3中,我们可以看到应用程序从一次初始化操作开始。这用于获取对设备和设备上下文的引用,以及创建将在应用程序运行时使用的任何资源。资源因应用程序而异,而设备和设备上下文的创建通常在多个应用程序中是相同的。

应用程序初始化后,它将进入消息循环。这个循环由两个阶段组成。首先,所有挂起的Windows消息都由应用程序的消息处理程序处理。在处理完所有可用消息后,可以执行实际的应用程序工作负载。对于我们的渲染应用程序,这包括清除渲染目标,然后执行所需的渲染操作,最后将结果显示到窗口的客户端区域。每次重复这两个过程表示一帧渲染。这将持续到用户请求终止应用程序为止。

用程序退出消息循环后,将执行关闭例程。在这个例程中,所有已创建的资源都将被释放。在它们被释放之后,设备上下文和设备本身也被释放。在这一点上,只要Direct3D 11对象的所有引用计数都得到了正确管理,应用程序就会完全释放其对所有对象的所有参考。

关闭过程完成后,应用程序将退出其主要功能而结束。现在我们已经看到了执行的每个步骤,我们可以更仔细地了解这些步骤中的每个步骤是如何涉及Direct3D11的,并了解使用API的实际代码是什么样子的

应用程序初始化

应用程序初始化过程从应用程序创建Win32窗口开始,该窗口将显示渲染操作的结果。创建窗口的基本Win32代码已经在许多书籍和教程中介绍了很多次,这里不再赘述。但是,示例程序呈现框架也包括这种类型的代码,并且可以在Win32RenderWindow类的类文件中找到源发行版中对此代码的快速引用。出于本讨论的目的,我们将假设该窗口已正确注册和创建,并且有一个窗口句柄可供使用。

设备及上下文创建

创建窗口后,下一步是获取对设备的引用。此过程使用D3DllCreateDevice()或D3DllCreateDeviceAndSwapChain()函数执行。对于这个例子,我们将使用后一个函数,因为它除了创建设备和设备上下文之外,还从同一个函数中创建交换链。

该函数的原型如清单1.2所示。我们将逐步了解该函数的参数,以了解创建设备所需的内容。

HRESULT D3DX10CreateDeviceAndSwapChain(
  _In_  IDXGIAdapter         *pAdapter,
  _In_  D3D10_DRIVER_TYPE    DriverType,
  _In_  HMODULE              Software,
  _In_  UINT                 Flags,
  _In_  DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,
  _Out_ IDXGISwapChain       **ppSwapChain,
  _Out_ ID3D10Device         **ppDevice
);

第一个参数pAdapter是指向应该为其创建设备的图形适配器的指针。此参数可以作为NULL传递以使用默认适配器。第二个参数对于正确配置至关重要。DriverType参数指定使用此函数调用创建的设备将使用哪种类型的驱动程序。可用的选项如清单1.3所示

enum D3D_DRIVER_TYPE {
    D3D_DRIVER_TYPE_UNKN0WN,
    D3D_DRIVER_TYPE_HARDWARE,
    D3D_DRIVER_TYPE_REFERENCE,
    D3D_DRIVER_TYPE_NULL,
    D3D_DRIVER_TYPE_S0FTWARE,
    D3D_DRIVER_TYPE_WARP
}
  • 正如您所看到的,可以使用与实际硬件设备交互的基于硬件的驱动程序。这是大多数情况下将使用的配置,但也必须考虑其他几种驱动程序类型。
  • 下一个驱动程序类型是参考驱动程序。这是一个完全实现完整Direct3D11规范的软件驱动程序,但由于它在软件中运行,速度不是很快。此驱动程序类型通常用于测试一组特定的渲染命令是否与硬件驱动程序和参考驱动程序产生相同的结果。
  • null驱动程序仅用于测试,不实现任何渲染操作。
  • 软件驱动程序类型允许使用自定义软件驱动程序。这不是一个常见的选项,因为它需要在目标机器上提供完整的第三方软件驱动程序。最后一个选项可能是最有趣的选项之一。
  • WARP设备是一种针对渲染速度进行优化的软件渲染器,它将使用CPU的任何特殊功能,如多核或SIMD指令。这允许应用程序具有可在所有目标机器上使用的适度快速的软件驱动程序,从而使许多应用程序类型的可部署市场明显更大。不幸的是,这种设备类型只支持Direct3D 10.1功能级别1提供的功能,这意味着它不能用于实现本书中讨论的大多数技术!

在选择了所需的设备类型之后,下一个参数允许应用程序在选择软件驱动程序类型的情况下向软件驱动程序DLL提供句柄。如果选择了非软件设备,则此参数可以设置为NULL,通常情况下也是如此。接下来是Flags参数,它允许在创建设备时启用一些特殊功能。

enum D3D11_CREATE_DEVICE_FLAG {
    D3D11_CREATE_DEVICE_SINGLETHREADED,
    D3D11_CREATE_DEVICE_DEBUGJ
    D3D11_CREATE_DEVICE_SWITCH_T0_REFJ
    D3Dll_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS,
    D3D11_CREATE_DEVICE_BGRA_SUPP0RT
}
  • 第一个标志表示应该为单线程使用创建设备。如果此标志不存在,则默认行为是允许多线程使用设备。
  • 第二个标志指示是否应该创建设备的调试层。如果设置了此标志,则会创建设备,使其也实现ID3DllDebug接口。此接口用于各种调试操作,并使用COM查询接口技术进行检索。它还可以在运行时输出完整的错误/警告消息,并启用内存泄漏检测。
  • 第三个标志不支持在Direct3D11中使用,所以我们不会详细讨论它。
  • 第四个标志用于禁用设备内的多线程优化,同时仍然允许多线程使用。这可能会降低性能,但可以实现更简单的调试和/或分析特性。
  • 最后一个标志用于创建可以与Direct2D API互操作的设备。我们在这本书中没有介绍Direct2D,但知道这个功能是可用的仍然很重要。

以下两个参数还为创建设备提供了一组非常有趣的功能。第一个是指向D3D_FEATURE_LEVEL值数组的指针。功能级别的概念是替换旧版本Direct3D中的旧CAPS位。CAPS位是GPU制造商可以选择支持或不支持的无数选项的名称,应用程序负责在尝试使用特定功能之前检查其是否可用。

随着每一代新硬件的出现,可用CAPS比特的数量变得越来越难以管理。Direct3D 11 API不需要应用程序通过所有这些选项进行解析,而是将实现分组为称为功能级别的类别。这些功能级别为给定的GPU和驱动程序提供了可用功能的更粗略的表示,但使用它们提供了一个明显更简单的过程来确定GPU支持什么功能。

enum D3D_FEATURE_LEVEL {
    D3D_FEATURE_LEVEL_9_1,
    D3D_FEATURE_LEVEL_9_2,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D FEATURE LEVEL 11 0
}

如您所见,自版本9以来,为Direct3D发布的每个次要修订都有一个功能级别。每个功能级别都是上一个功能级别的严格超集,这意味着如果应用程序请求,更高级别的GPU将支持更低的功能级别。例如,如果一个特定的应用程序只需要10.0功能级别的功能,那么它将能够在比需要11.0功能级别的应用程序更广泛的硬件上运行。应用程序传递一个要在pFeatureLevels参数中使用的功能级别数组,数组元素按首选顺序排列。数组中的元素数在FeatureLevels参数中传递,以确保函数在处理输入数组时不会超过数组大小。设备创建方法将尝试创建具有每个功能级别的设备,从阵列中的第一个元素开始。如果请求的驱动程序类型支持该功能级别,则会创建设备。如果没有,则该过程继续到下一个功能级别。如果阵列中不存在支持的功能级别,则该方法将返回一个失败代码。

我们在设备创建功能中还有两个输入参数。SDKVersion参数仅随D3D11_SDK_VERSI0N宏一起提供。这是一个定义的值,随着DirectX SDK的每个新版本而变化。但是,由于该值是随着新的SDK安装自动更新的,因此开发人员在转换到新的SDK时不需要进行任何更改。

最后一个输入参数是指向交换链描述的指针。如本章前面所述,交换链是DXGI创建的一个对象,用于管理窗口客户端区域的内容。交换链对象定义DXGI正确管理窗口内容所需的所有较低级别选项。我们通过填充DXGI_SWAP_CHAIN_DESC结构,在pSwapChainDesc参数中指定所需的交换链选项。该结构如图1.4所示,其成员有可用的选项。

093631449476

这些结构成员各自控制交换链行为的各个方面。BufferDesc参数提供了有关将保存窗口内容的缓冲区的详细信息。其“宽度”和“高度”参数以像素为单位提供缓冲区的大小。RefreshRate参数是一种结构,它提供分子和分母来定义窗口内容的刷新率。Format参数是DXGI_Format枚举的成员,该枚举为纹理资源提供了所有可用的格式。ScanlineOrdering参数定义用于生成图像的光栅技术。最后,Scaling参数指定缓冲区将如何应用于窗口的客户端区域,允许缩放或居中显示。其中许多选项只选择一次,然后作为默认行为简单地重复使用。在缓冲区模式描述结构之后,我们接下来指定多样本抗锯齿(MSAA)的选项。这需要我们选择样本的数量及其采样模式的质量。在第2章和第3章中对该功能进行了更详细的讨论,但目前我们将通过指定每像素一个质量为零的样本来禁用它。

接下来,我们指出交换链的预期用途,它将在我们的案例中用作渲染目标输出。BufferCount参数用于指示交换链中将使用的缓冲区数量。具有多个缓冲区允许DXGI使用其中一个缓冲区来显示渲染的图像,同时Direct3D可以在辅助缓冲区中生成下一帧。这样可以使窗口中的动画更加平滑。为此,通常使用两个缓冲区,但如有必要,也可以使用额外的缓冲区。

接下来的两个参数配置到应用程序窗口的连接。OutputWindow参数只是将接收交换链内容的窗口的窗口句柄。下一个参数Windowed表示它的声音——窗口是否会在桌面上显示为窗口,或者它是否会占据整个屏幕。这将因应用程序而异,但我们现在将坚持使用窗口模式——SwapEffect参数配置缓冲区内容显示到窗口后对其执行的操作。此标志通常设置为在显示后丢弃缓冲区内容,但也可以根据需要进行更改。创建交换链所需的最后一个参数是许多其他标志的组合,这些标志可以进一步配置缓冲区,以及DXGI如何使用缓冲区。通常,这些是特殊使用标志,可以允许应用程序进行特殊操作,例如处理监视器旋转、在窗口模式和全屏模式之间切换,以及允许GDI访问缓冲区。我们不会在示例中使用这些标志中的任何一个。在填写完所有参数后,我们还提供指向我们希望接收回的所有对象的指针。这些是交换链、设备、创建的设备的功能级别和即时设备上下文。

务必检查API调用的返回值,以确保函数成功。除了功能级别之外,所有返回的项都是COM对象,因此必须相应地管理它们的引用计数。在我们有了交换链接口之后,我们必须从中获得纹理资源接口,以便在渲染管道中使用。这是通过IDXGISwapChain::GetBuffer()方法完成的,该方法的工作原理与上述COM查询接口方法类似。不同之处在于,我们试图获取的缓冲区的索引是作为第一个参数传递的。纹理界面的获取如清单1.6所示.

ID3DllTexture2D* pSwapChainBuffer = 0;
hr = pSwapChain->GetBuffer( 0,__uuidof( ID3DllTexture2D ), (void **)&pSwapChainBuffer );

此外,如果我们创建的设备安装了调试层,我们需要以相同的方式从设备本身获取调试接口。

m_pDebugger = 0;
hr = pDevice->QueryInterface( uuidof( ID3DllDebug ), (void **)&pDebugger );

在获取了这些接口之后,我们可以考虑对Direct3D11进行初始化,并可以进入下一个应用程序阶段。

资源创建

在应用程序初始化并获取了设备和设备上下文,并且还具有要渲染到的交换链之后,它应该创建将在应用程序的渲染部分使用的任何资源、着色器对象和状态对象。最佳做法是在启动时获取尽可能多的这些对象,因为在渲染操作期间获取这些对象可能会导致渲染过程中的瞬时延迟。这些小故障大多可以通过多线程渲染来缓解,但在初始化期间获取资源完全消除了问题。

由于我们还没有检查可用的资源或渲染管道的组件,因此在这里描述这些对象的创建是没有用的。这两个主题都有一整章专门针对它们(第2章和第3章),所以我们不会试图在这里提供一个被掩盖的描述。然而,为了使用我们从交换链中获得的纹理接口,我们需要一个名为资源视图的对象来绑定纹理作为管道的渲染目标。所需的特定类型的资源视图称为渲染目标视图。与Direct3D 11中的大多数对象的情况一样,它是用设备接口创建的,然后与设备上下文一起使用以执行一些操作。清单1.8显示了在纹理资源已经可用时创建渲染目标视图的代码列表。

ID3DllRenderTargetView* pView = 0;
HRESULT hr = m_pDevice->CreateRenderTargetView(pSwapChainBuffer,0, &pView );

对资源创建渲染视图,这个资源是交换连中的2D纹理。

在这种情况下,我们传递纹理资源作为第一个参数,而不是视图描述,我们只是传递NULL以使用默认的视图描述。资源视图也是引用计数的,因此应用程序必须确保在完成引用时正确释放引用。获取资源视图后,我们现在可以使用交换链纹理资源了。

除了渲染目标,我们还需要创建一个深度模具缓冲区和相应的深度模具视图,以便将其绑定到管道。执行此操作所需的代码如清单1.9所示。第二章将再次详细介绍这个创建过程。

    D3D11_TEXTURE2D_DESC desc;
    desc.Width = width;
    desc.Height = height;
    desc.MipLevels = 1;
    desc.ArraySize = 1;
    desc.Format = DXGI_FORMAT_D32_FLOAT;
    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;
    desc.Usage = D3D11_USAGE_DEFAULT;
    desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    desc.CPUAccessFlags = 0;
    desc.MiscFlags = 0;
    ID3DllTexture2D* pDepthStencilBuffer = 0;
    HRESULT hr = m_pDevice->CreateTexture2D( &desc,0, &pDepthStencilBuffer );
    ID3DllDepthStencilView* pDepthview = 0;
    HRESULT hr = m_pDevice->CreateDepthStencilView( pDepthStencilBuffer,pDesc, &pDepthView );

尽管我们不会在这个示例中创建额外的资源或对象,但我们仍然可以考虑应该在这个初始化例程中创建的项的类型。本质上,所有Direct3D资源都应该在初始化阶段创建,除非特定的用例绝对要求在运行时创建并销毁资源。例如,如果特定纹理是按程序生成的,并且其大小取决于某个运行时参数,则在启动过程中创建纹理将不容易。然而,可能值得考虑更改算法以允许早期创建。在我们的示例中,这可能包括仅声明一个大型资源,该资源由所有需要这种过程纹理的对象共享。在启动时创建资源,然后在运行时修改其内容也是可以接受的,特别是在使用Direct3D 11的多线程功能的情况下。

此外,所有管道配置都应预先创建。这包括将由固定功能阶段使用的任何状态对象,以及将用于可编程着色器阶段的着色器程序对象。由于这些项目通常是提前计划好的,所以不应该有任何理由不能在启动时创建它们,而不是使用动态加载方案。除非应用程序使用大量这样的对象,否则它们不应该引起任何内存消耗问题。特别是,如果这些对象是动态加载的,那么着色器程序将需要在运行时从硬盘中读取——这几乎肯定会导致应用程序的短暂暂停。

消息循环

初始化完成后,应用程序可以继续执行循环部分。该阶段由三个单独的部分组成,将在以下部分中进行描述。

处理挂起的消息

应用程序进入其应用程序循环后,必须响应并处理从操作系统接收的Windows消息。这是由注册和创建应用程序窗口时指定的回调函数执行的。消息的处理与Direct3D编程没有直接关系,因此我们现在将跳过它。然而,本书中的所有示例程序都实现了消息处理,因此可以从中看到基本原理。可以在main中找到消息处理程序。源代码分发中的cpp文件。

更新

我们的简单测试应用程序不执行任何模拟或实现特殊的物理计算,但它确实执行了一个简单的动画来更改渲染目标的颜色。这是在应用程序循环的更新阶段执行的。这是执行此类更新的典型位置,因此任何更改的状态都可以反映在当前帧的渲染中,渲染将在循环的下一步中执行。对于我们的示例应用程序,我们只需计算一个正弦变化的值,该值将用于确定窗口的背景色。如清单1.10所示

float fBlue = sinf(m_pTimer->Runtime() * m_pTimer->Runtime() ) * 0.25f + 0.5f;

在这里,我们看到一个计时器类用于获取应用程序运行的时间量。这个类取自本书的示例框架,关于其内部工作的更多细节可以在Timer类文件的源代码分布中找到。现在,我们只假设它为我们提供了应用程序运行的浮点秒数。

渲染

更新应用程序的状态后,我们接下来将呈现当前场景的表示,供用户查看。此过程首先将渲染目标视图和深度模具视图绑定到管道,以接收任何渲染操作的结果。由于这是一个管道操作,因此它是在设备上下文中执行的。清单1.11展示了如何做到这一点。

ID3DllRenderTargetView* RenderTargetViews[l] = { pView };
ID3DllDepthStencilView* DepthTargetView = pDepthView;
pContext->OMSetRenderTargets( 1, RenderTargetViews, DepthTargetView );

渲染目标和深度模具目标都使用相同的方法调用绑定到管道。此设置渲染目标的方法采用渲染目标视图的阵列,因此即使仅使用单个渲染目标,也最好使用阵列将其引用传递到函数中。在绑定了渲染和深度目标之后,我们可以清除它们,为即将到来的渲染过程做准备。如您所见,清除过程也是使用设备上下文执行的.

ID3DllRenderTargetView* pRenderTargetViews[D3Dll_SIMULTANE0US_RENDER_TARGET_COUNT] = {NULL};
ID3DllDepthStencilView* pDepthStencilView = 0;
m_pContext->OMGetRenderTargets(D3D11_SIMULTANE0US_RENDER_TARGET_C0UNT,pRenderTargetViewsj SpDepthStencilView);
for ( UINT i = 0; i < D3D11_SIMULTANE0US_RENDER_TARGET_C0UNT; ++i )
{
    if ( pRenderTargetViews[i] != NULL )
    {
        float clearColours[] = { color.x, color.y, color.z, color.w }; // RGBA
        m_pContext->ClearRenderTargetView( pRenderTargetViews[i],clearColours);
        SAFE_RELEASE( pRenderTargetViews[i] );
    }
}

if ( pDepthStencilView )
{
    m_pContext->ClearDepthStencilView( pDepthStencilView,D3Dll_CLEAR_DEPTH, depth, stencil );
}
// Release the depth stencil view
SAFE_RELEASE( pDepthStencilView );

一旦清除了渲染目标和深度模板目标,应用程序就可以执行所需的任何渲染操作。这包括渲染当前场景、任何二维屏幕元素(如用户界面)以及给定帧可能需要的任何文本。再一次,我们的示例应用程序不会执行任何实际渲染。相反,它只是使用清单1.12中所示的方法将渲染目标清除为更新阶段指定的颜色。

提交渲染结果到窗口显示

在完成给定帧的所有渲染后,可以将其内容呈现给窗口的客户端区域。这是非常简单的,使用交换链接口的单个方法调用。

pSwapChain->Present( 0, 0 );

调用此方法时,渲染目标的内容将显示在窗口的客户端区域。如果交换链中使用了多个缓冲区,则应用程序不需要执行任何手动渲染目标操作。相反,这是由交换链本身管理的,这简化了应用程序的职责。

此方法完成后,用户将能够在窗口中看到渲染目标内容。显示渲染的输出后,应用程序循环将重新启动,并检查是否有任何挂起的Windows消息。这个循环序列重复,直到用户决定终止应用程序。这通常是通过按下escape键或从应用程序用户界面中选择“退出”选项来完成的。

程序退出资源释放

在应用程序可以终止之前,它需要清理已获取的所有打开的引用。对于Direct3D 11对象,这通常意味着为应用程序使用的每个引用调用Release方法。还需要考虑的一点是,管道本身将保留对已绑定到它的某些对象的引用。为了确保这些管道引用被释放,应用程序可以调用设备上下文的ClearState方法。这将清除管道中的任何引用,并将其置于默认状态。我们的示例应用程序的关闭代码如清单1.14所示。

pContext->ClearState();
SAFE_RELEASE( pView );
SAFE_RELEASE( pDepthView );
SAFE_RELEASE( pDepthStencilBuffer );
SAFE_RELEASE( pSwapChainBuffer );
SAFE_RELEASE( pSwapChain );
SAFE_RELEASE( pContext );
SAFE_RELEASE( pDebugger );
SAFE_RELEASE( pDevice );

如果仍有一些引用未正确发布,则会向调试控制台打印一条调试消息,指示哪些接口对象类型仍有未发布的未完成引用。如果所有引用都已正确发布,则该应用程序将退出,就像任何其他应用程序一样。

1.5.3应用程序扩展思考

虽然我们在本章后半部分介绍的示例步骤确实很重要,但它们并不是为您创建的每个应用程序执行的最令人兴奋的操作。其中许多任务,如创建窗口或初始化设备,都可以使用库函数来处理,这些函数可以用于您创建的所有应用程序。在实时渲染上下文中,这种类型的库通常被称为引擎,这个术语最早由id Software的John Carmack创造。

这是我们为本书中的示例程序选择的路径。所有的样本都建立在作者开发的名为Hieroglyph 3的开源引擎上。引擎和样本都可以从Hieroglyph 3项目页面免费下载,该页面可以在http://hieroglyph3.codeplex.com .除了本书中的示例之外,源代码存储库中还包括许多其他示例应用程序,用于学习库的基础知识。

源地址失效,可在这里下载:https://github.com/FTD2012/Hieroglyph3

通过使用这样一个开源库,我们可以在本书中更多地介绍Direct3D11的概念和使用,而不是花时间解释基本的Win32应用程序代码或给出重复的代码示例。图书馆注重基础知识,这样我们就可以专注于主题中更有趣的部分,并最终提供一本更好的书。象形文字3库具有麻省理工学院许可证,其中包括一套自由的使用指南,您可以将其用作自己项目的基础。

0 篇笔记 写笔记

作者信息
站长漫谈
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!