使用 Context-Local DDI 句柄

本部分仅适用于 Windows 7 及更高版本以及 Windows 操作系统的 Windows Server 2008 R2 及更高版本。

每个对象 (例如资源、着色器等) 具有上下文本地 DDI 句柄。

假设对象与三个延迟上下文一起使用。 在这种情况下,四个句柄引用同一对象 (每个延迟上下文的一个句柄,另一个句柄用于直接上下文) 。 由于每个上下文都可以由线程同时操作,因此上下文本地句柄可确保多个 CPU 线程不会争用类似的内存 (有意或无意) 。 上下文本地句柄也非常直观,因为驱动程序可能必须修改在逻辑上与每个上下文关联的大部分数据 (例如,对象可能受上下文绑定,等等) 。

即时上下文句柄与延迟上下文句柄仍存在区别。 具体而言,即时上下文句柄保证是分配的第一个句柄和销毁的最后一个句柄。 在每个延迟上下文句柄的“打开”期间提供相应的即时上下文句柄,以将它们链接在一起。 目前没有对象具有每个设备 DDI 句柄的概念 (,即在直接上下文句柄之前创建和销毁的句柄,并且仅按上下文句柄创建) 的顺序进行引用。

某些句柄与其他句柄有依赖关系 (例如,视图依赖于其相应的资源) 。 即时上下文存在的创建和销毁排序保证扩展到延迟上下文句柄以及 (即,运行时在运行时创建该资源的任何上下文本地视图句柄之前创建上下文本地资源句柄,运行时销毁上下文本地资源句柄后,运行时销毁该资源) 的所有上下文本地视图句柄。 当运行时创建上下文本地句柄时,运行时还提供相应的上下文本地依赖项句柄。

驱动程序数据组织

对于驱动程序数据组织,有一些需要注意的问题。 与 Direct3D 版本 10 一样,数据的正确位置可以减少 API 和驱动程序之间的缓存未命中。 数据的适当位置还可以防止缓存抖动,当频繁访问的多个数据片段全部解析为同一缓存索引并耗尽缓存关联时,会发生此情况。 自 Direct3D 版本 10 起,DDI 就设计为有助于避免此类问题由驱动程序显示,从而告知 API 驱动程序满足句柄和分配句柄值的 API 所需的内存量。 但是,与线程相关的新问题会影响 Direct3D 版本 11 时间范围内 DDI 设计。

当然,上下文本地句柄提供了一种关联每个上下文的对象数据的方法,从而避免线程之间的争用问题。 但是,由于此类数据是为每个延迟上下文复制的,因此此类数据的大小是一个主要问题。 这提供了自然合理化,以在直接上下文句柄和延迟上下文句柄之间共享只读数据。 在创建延迟上下文句柄期间,将提供即时上下文句柄来建立句柄之间的连接。 但是,位于延迟上下文外的任何数据都处理 API 数据的本地化优势,而对只读数据的附加间接级别会阻止将局部性优势扩展到只读数据。 如果局部优势证明数据重复是正当的,则可以将某些只读数据复制到每个上下文句柄区域。 但是,应该考虑每个延迟上下文句柄的内存,因此,如果数据相对较大且不像其他数据那样频繁访问,则重新定位从句柄中不相邻的数据。 理想情况下,与每个延迟上下文句柄关联的数据类型无论如何都是高频率数据;因此,数据不够大,无法考虑需要重新定位。 当然,驱动因素必须平衡这些相互矛盾的动机。

为了使驱动程序数据设计与 Direct3D 版本 10 高效兼容,但不在实现中出现分歧,只读数据应位于连续 (但仍与) 直接上下文句柄数据后分离。 如果驱动程序使用此设计,则驱动程序必须注意,在直接上下文句柄数据和只读数据之间需要缓存行填充。 由于线程可能会频繁地操作每个上下文句柄数据(如果不是并发) ) (,因此,如果未使用缓存行填充,则直接上下文句柄数据和延迟上下文句柄数据之间会发生错误共享处罚。 如果建立指针并在上下文句柄内存区域之间定期遍历,则驱动程序设计必须认识到错误共享处罚。

Direct3D 运行时将以下 Direct3D 11 DDI 用于延迟的上下文本地句柄:

要使 Direct3D 运行时检索驱动程序所需的延迟上下文句柄大小,必须使用前面的 DDI 函数。 在为即时上下文创建对象后,运行时立即调用 CalcDeferredContextHandleSize 来查询驱动程序所需的存储空间量,以满足此对象的延迟上下文句柄。 但是,Direct3D API 必须通过确定访问的唯一句柄大小及其值来优化其 CLS 内存分配器;运行时调用驱动程序的 CheckDeferredContextHandleSizes 函数来获取此信息。 因此,在设备实例化期间,API 通过双重轮询请求延迟上下文句柄大小的数组。 第一个轮询是请求返回多少个大小,而第二个轮询传入数组以检索每个大小的值。 驱动程序必须指示它需要多少内存来满足句柄以及哪个句柄类型。 驱动程序可以返回与特定句柄类型关联的多个大小。 但是,驱动程序从未定义过从 CalcDeferredContextHandleSize 返回 的值,该值在 CheckDeferredContextHandleSizes 数组中也没有相应返回。

至于创建 DDI 句柄,将使用延迟上下文中的 create 方法。 例如,检查 CreateBlendState (D3D10_1) DestroyBlendState 函数。 HDEVICE 自然指向适当的延迟上下文 (而不是直接上下文) ;其他 CONST 结构指针为 NULL , (假设对象) 没有依赖项;并且,D3D10DDI_HRT* 句柄是对应即时上下文对象的D3D10DDI_H* 句柄。

例如,对于具有依赖项 (的对象,视图在其相应的资源) 上具有依赖关系,提供依赖项句柄的结构指针不是 NULL。 但是,结构的唯一有效成员是依赖项句柄;而其余成员用零填充。 例如,当运行时在延迟上下文上调用 此函数时 ,调用驱动程序的 CreateShaderResourceView (D3D11) 函数的D3D11DDIARG_CREATESHADERRESOURCEVIEW指针将不会为 NULL 。 在此 CreateShaderResourceView (D3D11) 调用中,运行时将资源的相应上下文本地句柄分配给 D3D11DDIARG_CREATESHADERRESOURCEVIEW 的 hDrvResource 成员。 不过,D3D11DDIARG_CREATESHADERRESOURCEVIEW的其余成员填充了零。

以下示例代码演示 Direct3D 运行时如何转换应用程序的创建请求,以及如何首次使用延迟上下文调用用户模式显示驱动程序来创建即时上下文和延迟上下文。 应用程序对 ID3D11Device::CreateTexture2D 的调用将启动以下“资源创建”部分中的运行时代码。 应用程序对 ID3D11Device::CopyResource 的调用将启动以下“延迟上下文资源使用情况”部分中的运行时代码。

// Device Create
 IC::pfnCheckDeferredContextHandleSizes( hIC, &u, NULL );
pArray = malloc( u * ... );
IC::pfnCheckDeferredContextHandleSizes( hIC, &u, pArray );

// Resource Create
 s = IC::pfnCalcPrivateResourceSize( hIC, &Args );
pICRHandle = malloc( s );
 IC::pfnCreateResource( hIC, &Args, pICRHandle, hRTResource );
 s2 = IC::pfnCalcDeferredContextHandleSize( hIC, D3D10DDI_HT_RESOURCE, pICRHandle );

// Deferred Context Resource Usage
pDCRHandle = malloc( s2 );
 DC::pfnCreateResource( hDC, NULL, pDCRHandle, pICRHandle );

pfnSetErrorCb 的问题

创建函数均不会返回错误代码,这对于 Direct3D 版本 11 线程模型来说是理想的。 所有 create 函数都使用 pfnSetErrorCb 从驱动程序中检索回错误代码。 为了最大程度地与 Direct3D 版本 10 驱动程序模型的兼容性,未引入返回错误代码的新 DDI 创建函数。 相反,驱动程序必须在创建函数期间继续使用 具有 pfnSetErrorCb 的统一设备/即时上下文D3D10DDI_HRTCORELAYER句柄。 当驱动程序支持命令列表时,驱动程序应使用与相应上下文关联的相应 pfnSetErrorCb 。 也就是说,延迟上下文错误应转到具有相应句柄的 pfnSetErrorCb 的特定延迟上下文调用,依此类推。

延迟上下文可以通过调用 pfnSetErrorCb 来返回E_OUTOFMEMORY,这些函数以前只允许D3DDDIERR_DEVICEREMOVED (如 DrawSetBlendState 等) ,因为延迟上下文内存需求随着每次调用 DDI 函数而永久增长。 Direct3D API 会触发本地上下文删除,以帮助驱动程序处理此类故障情况,从而有效地删除部分生成的命令列表。 应用程序继续确定它正在记录命令列表;但是,当应用程序最终调用 FinishCommandList 函数时, FinishCommandList 返回E_OUTOFMEMORY失败代码。