更明确的 Diligent Engine:掌握 Direct3D12 和 Vulkan 中的状态管理
本文介绍了Direct3D12和Vulkan等现代图形API中资源状态管理和同步的问题,并描述了Diligent Engine提供的一种解决方案。
免责声明:本文使用了Diligent Engine网站上发布的内容。
引言
明确的资源状态管理和同步是Direct3D12和Vulkan等现代图形API为应用程序开发者提供的主要优势和主要挑战之一。它使渲染命令的记录非常高效,但正确处理状态管理并非易事。本文解释了为什么明确的状态管理很重要,并介绍了Diligent Engine中实现的解决方案。Diligent Engine是一个现代化的跨平台低级图形库,拥有Direct3D11、Direct3D12、OpenGL/GLES和Vulkan后端,并支持Windows桌面、通用Windows、Linux、Android、Mac和iOS平台。其完整源代码可在GitHub上免费使用。本文对Diligent Engine进行了介绍。
背景
对Direct3D11、Direct3D12、OpenGL或Vulkan有一定的基本了解是可取的,但不是必需的。
为什么需要明确的状态管理和同步?
现代图形应用程序可以最好地描述为客户端-服务器系统,其中CPU是记录渲染命令并将其放入队列的客户端,而GPU是异步从队列中拉取命令并处理它们的服务器。因此,当CPU发出命令时,命令不会立即执行,而是在GPU到达队列中相应点时(通常是1到2帧)稍后执行。除此之外,GPU架构与CPU非常不同,因为GPU旨在处理的问提类型也不同。虽然CPU擅长运行具有大量流控制结构(分支、循环等)的算法,例如处理应用程序输入循环中的事件,但GPU更擅长通过执行相同的计算数千甚至数百万次来处理数字。当然,这种说法有点过于简化,因为现代CPU也具有宽SIMD(单指令多数据)单元,可以高效地执行计算。尽管如此,GPU在这些类型的问题上至少快一个数量级。
CPU和GPU都需要解决的主要挑战是内存延迟。CPU是具有强大核心和大型缓存的乱序执行机器,它们使用花哨的预取和分支预测电路来确保在核心实际需要数据时数据可用。相比之下,GPU是具有小缓存、数千个微小核心和非常深流水线的顺序执行野兽。它们不使用任何分支预测或预取,而是保持数万个线程同时运行,并能够瞬间在线程之间切换。当一组线程等待内存请求时,如果GPU有足够的工作,它可以简单地切换到另一组线程。
在编写CPU程序时(谈到CPU,我将指x86 CPU;对于ARM CPU,情况可能稍微复杂一些),硬件做了很多我们通常认为理所当然的事情。例如,当一个核心在一个内存地址写入一些内容后,我们知道另一个核心可以立即读取相同的内存。包含数据的缓存行需要通过CPU进行一些传输,但最终另一个核心将获得正确的信息,而应用程序无需额外的努力。相比之下,GPU很少提供明确的保证。在许多情况下,除非应用程序采取特殊措施,否则您不能期望写入对后续读取可见。除此之外,数据可能需要从一种形式转换为另一种形式才能被下一步使用。以下是一些可能需要明确同步的示例:
- 通过无序访问视图(Direct3D中的UAV)或图像(Vulkan/OpenGL术语)将数据写入纹理或缓冲区后,GPU可能需要等待所有写入完成并将缓存刷新到内存,然后才能由另一个着色器读取相同的纹理或缓冲区。
- 执行阴影贴图渲染命令后,GPU可能需要等待光栅化和所有写入完成,刷新缓存并将纹理布局更改为适合采样的格式,然后才能在光照着色器中使用该阴影贴图。
- 如果CPU需要读取GPU先前写入的数据,它可能需要使该内存区域无效,以确保缓存获得更新的字节。
这些只是GPU需要解决的同步依赖关系中的几个示例。传统上,所有这些问题都由API/驱动程序处理并对开发人员隐藏。Direct3D11和OpenGL/GLES等老式隐式API就是这样工作的。这种方法虽然从开发人员的角度来看很方便,但存在导致次优性能的主要限制。首先,驱动程序或API不知道开发人员的意图是什么,并且必须始终假设最坏情况来保证正确性。例如,如果一个着色器写入UAV的一个区域,但下一个着色器从另一个区域读取,则驱动程序必须始终插入一个屏障以保证所有写入都完成并可见,因为它无法知道这些区域不重叠并且屏障实际上不是必需的。
然而,最大的问题是这种方法使得并行命令记录几乎毫无用处。考虑这样一个场景:一个线程记录渲染阴影贴图的命令,而第二个线程同时记录在正向渲染过程中使用此阴影贴图的命令。第一个线程需要阴影贴图处于深度模板可写状态,而第二个线程需要它处于着色器可读状态。问题是第二个线程不知道阴影贴图的原始状态是什么。所以发生的情况是,当应用程序提交第二个命令缓冲区以执行时,API需要找出阴影贴图纹理的实际状态,并使用正确的状态转换来修补命令缓冲区。它不仅需要为我们的阴影贴图纹理这样做,还需要为命令列表可能使用的任何其他资源这样做。这是一个显著的序列化瓶颈,在旧API中无法解决。
下一代API(Direct3D12和Vulkan)通过使所有资源转换显式来解决上述问题。现在由应用程序来跟踪所有资源的状态并确保执行所有必需的屏障/转换。在上面的示例中,应用程序将知道当阴影贴图在正向渲染通道中使用时,它将处于深度模板可写状态,因此可以立即插入屏障,而无需等待第一个命令缓冲区被记录或提交。这里的缺点是应用程序现在负责跟踪所有资源状态,这可能是一个巨大的负担。
现在,让我们仔细看看Vulkan和Direct3D12中如何实现同步。
Vulkan中的同步
Vulkan支持对同步操作进行非常精细的控制,并提供工具来单独调整以下方面:
- 执行依赖项,即,哪些操作集必须完成才能开始另一组操作。
- 内存依赖项,即,哪些内存写入必须对后续读取可用。
- 布局转换,即,如果需要,必须执行哪些纹理内存布局转换。
执行依赖项表示为管线阶段之间的依赖项,这些阶段自然映射到传统的GPU管线。内存访问类型由VkAccessFlagBits枚举定义。某些访问类型仅对特定管线阶段有效。所有有效组合列在Vulkan规范的第6.1.3节中,也在下表中给出:
访问类型 (VK_ACCESS_) | 管线阶段 (VK_PIPELINE_STAGE_) | 访问类型描述 |
INDIRECT_COMMAND_READ_BIT | DRAW_INDIRECT_BIT | 对存储在缓冲区中的间接绘制/调度命令数据属性的读取访问 |
INDEX_READ_BIT | VERTEX_INPUT_BIT | 对索引缓冲区的读取访问 |
VERTEX_ATTRIBUTE_READ_BIT | STAGE_VERTEX_INPUT_BIT | 对顶点缓冲区的读取访问 |
UNIFORM_READ_BIT | VERTEX_SHADER_BIT TESS_CONTROL_SHADER_BIT TESS_EVALUATION_SHADER_BIT GEOMETRY_SHADER_BIT FRAGMENT_SHADER_BIT COMPUTE_SHADER_BIT | 对统一(常量)缓冲区的读取访问 |
SHADER_READ_BIT | VERTEX_SHADER_BIT TESS_CONTROL_SHADER_BIT TESS_EVALUATION_SHADER_BIT GEOMETRY_SHADER_BIT FRAGMENT_SHADER_BIT COMPUTE_SHADER_BIT | 对存储缓冲区(缓冲区UAV)、统一纹素缓冲区(缓冲区SRV)、采样图像(纹理SRV)、存储图像(纹理UAV)的读取访问 |
SHADER_WRITE_BIT | VERTEX_SHADER_BIT TESS_CONTROL_SHADER_BIT TESS_EVALUATION_SHADER_BIT GEOMETRY_SHADER_BIT FRAGMENT_SHADER_BIT COMPUTE_SHADER_BIT | 对存储缓冲区(缓冲区UAV)或存储图像(纹理UAV)的写入访问 |
INPUT_ATTACHMENT_READ_BIT | FRAGMENT_SHADER_BIT | 在片段着色期间对输入附件(渲染目标)的读取访问 |
COLOR_ATTACHMENT_READ_BIT | CLR_ATTACH_OUTPUT_BIT | 通过混合或逻辑操作等方式对颜色附件(渲染目标)的读取访问 |
COLOR_ATTACHMENT_WRITE_BIT | CLR_ATTACH_OUTPUT_BIT | 在渲染通道期间或通过混合等某些操作对颜色附件(渲染目标)的写入访问 |
DEPTH_STENCIL_ATTACHMENT_READ_BIT | EARLY_FRAGMENT_TESTS_BIT LATE_FRAGMENT_TESTS_BIT | 通过深度/模板操作对深度/模板缓冲区的读取访问 |
DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | EARLY_FRAGMENT_TESTS_BIT LATE_FRAGMENT_TESTS_BIT | 通过深度/模板操作对深度/模板缓冲区的写入访问 |
TRANSFER_READ_BIT | TRANSFER_BIT | 在复制操作中对图像(纹理)或缓冲区的读取访问 |
TRANSFER_WRITE_BIT | TRANSFER_BIT | 在清除或复制操作中对图像(纹理)或缓冲区的写入访问 |
HOST_READ_BIT | HOST_BIT | 主机读取访问 |
HOST_WRITE_BIT | HOST_BIT | 主机写入访问 |
如您所见,大多数访问标志与管线阶段一一对应。例如,顶点索引很自然地只能在顶点输入阶段读取,而最终颜色只能在颜色附件(Direct3D12术语中的渲染目标)输出阶段写入。对于某些访问类型,您可以精确指定哪个阶段将使用该访问类型。最重要的是,对于着色器读取(例如纹理采样)、写入(UAV/图像存储)和统一缓冲区访问,可以精确地告诉系统哪个着色器阶段将使用该访问类型。对于深度模板读/写访问,可以区分访问是发生在早期还是晚期片段测试阶段。老实说,我真的想不出任何例子,这种灵活性可能有用并导致可测量的性能改进。请注意,为不支持该类型访问的阶段(例如顶点着色器阶段的深度模板写入访问)指定访问标志是违反规范的。
应用程序可以使用这些工具非常精确地指定阶段之间的依赖关系。例如,它可能要求顶点着色器阶段对统一缓冲区的写入在后续绘制调用中对片段着色器的读取可用。这样做的好处是,由于依赖关系从片段着色器阶段开始,驱动程序将不需要同步顶点着色器阶段的执行,从而可能节省一些GPU周期。
对于图像(纹理)资源,同步屏障还定义了布局转换,即GPU可能需要执行的潜在数据重组,以支持请求的访问类型。Vulkan规范的第11.4节描述了可用的布局以及它们必须如何使用。由于每个布局只能在某些管线阶段使用(例如,颜色附件最佳布局只能由颜色附件读/写阶段使用),并且每个管线阶段只允许几种访问类型,因此我们可以列出每个布局所有允许的访问标志,如下表所示:
图像布局 (VK_IMAGE_LAYOUT) | 允许的访问类型 (VK_ACCESS_) | 描述 |
UNDEFINED | 不适用 | 此布局只能用作创建图像时的初始布局或图像转换中的旧布局。从此布局转换时,图像的内容不保留。 |
通用 | 任意 | 所有类型的设备访问。 |
COLOR_ATTACHMENT_OPTIMAL | CLR_ATTACH_READ_BIT CLR_ATTACH_WRITE_BIT | 只能用作颜色附件。 |
DEPTH_STENCIL_ATTACHMENT_OPTIMAL | DS_ATTACH_READ_BIT DS_ATTACH_WRITE_BIT | 只能用作深度模板附件。 |
DEPTH_STENCIL_READ_ONLY_OPTIMAL | DS_ATTACH_READ_BIT 或 SHADER_READ_BIT | 只能用作只读深度模板附件或着色器中的只读图像。 |
SHADER_READ_ONLY_OPTIMAL | SHADER_READ_BIT | 只能用作着色器中的只读图像(采样图像或输入附件) |
TRANSFER_SRC_OPTIMAL | TRANSFER_READ_BIT | 只能用作传输(复制)命令的源。 |
TRANSFER_DST_OPTIMAL | TRANSFER_WRITE_BIT | 只能用作传输(复制和清除)命令的目标。 |
PREINITIALIZED | 不适用 | 此布局只能用作创建图像时的初始布局或图像转换中的旧布局。从此布局转换时,图像的内容会保留,与UNDEFINED 布局相反。 |
与访问标志和管线阶段一样,图像布局和访问标志的组合自由度非常小。因此,图像布局、访问标志和管线阶段在许多情况下形成唯一确定的三元组。
请注意,Vulkan还公开了另一种同步形式,称为渲染通道和子通道。渲染通道的主要目的是提供隐式同步保证,使应用程序不需要在每个渲染命令(例如绘制或清除)之后插入屏障。渲染通道还允许以驱动程序(尤其是在使用平铺延迟渲染架构的GPU上)可以利用的形式表达相同的依赖关系,以实现更高效的渲染。渲染通道的全面讨论超出了本文的范围。
Direct3D12中的同步
Direct3D12 中的同步工具不如 Vulkan 那么富有表现力,但也不那么复杂。除了下面描述的 UAV 屏障之外,Direct3D12 没有定义执行屏障和内存屏障之间的区别,并使用资源状态(参见表 3)。
资源状态 (D3D12_RESOURCE_STATE_) | 描述 |
VERTEX_AND_CONSTANT_BUFFER | 资源用作顶点或常量缓冲区。 |
INDEX_BUFFER | 资源用作索引缓冲区。 |
RENDER_TARGET | 资源用作渲染目标。 |
UNORDERED_ACCESS | 资源通过无序访问视图 (UAV) 进行无序访问。 |
DEPTH_WRITE | 资源用于可写深度模板视图或清除命令。 |
DEPTH_READ | 资源用于只读深度模板视图。 |
NON_PIXEL_SHADER_RESOURCE | 资源通过除像素着色器以外的任何着色器阶段的着色器资源视图进行访问。 |
PIXEL_SHADER_RESOURCE | 资源通过像素着色器中的着色器资源视图进行访问。 |
INDIRECT_ARGUMENT | 资源用作间接绘制或调度命令的间接参数源。 |
COPY_DEST | 资源用作复制命令中的复制目标。 |
COPY_SOURCE | 资源用作复制命令中的复制源。 |
Direct3D12 定义了三种资源屏障类型:
- 状态转换屏障定义了从表3中列出的一个资源状态到另一个资源状态的转换。当旧的和新的访问标志和/或图像布局不同时,这种类型的屏障映射到Vulkan屏障。
- UAV屏障在Vulkan术语中是执行加内存屏障。它不改变状态(布局),而是指示对特定资源的所有UAV访问(读取或写入)必须在任何未来的UAV访问(读取或写入)开始之前完成。
- 别名屏障表示由同一内存支持的两个资源之间的使用转换,超出了本文的范围。
Diligent Engine中的资源状态管理
Diligent Engine的目的是提供高效的跨平台低级图形API,它易于使用,同时又足够灵活,不会限制应用程序表达其意图。在2.4版本之前,应用程序控制资源状态转换的能力非常有限。2.4版本使资源状态转换变得明确,并引入了两种管理状态的方式。第一种是完全自动的,引擎在内部跟踪状态并执行必要的转换。第二种是手动的,完全由应用程序驱动。
自动状态管理
每个可能执行状态转换的命令都使用以下状态转换模式之一:
RESOURCE_STATE_TRANSITION_MODE_NONE
- 不执行状态转换,不进行状态验证RESOURCE_STATE_TRANSITION_MODE_TRANSITION
- 将资源转换为命令所需的状态RESOURCE_STATE_TRANSITION_MODE_VERIFY
- 不转换,但验证状态是否正确
下面的代码片段给出了Diligent Engine 2.4中一系列典型渲染命令的示例:
// Clear the back buffer
const float ClearColor[] = { 0.350f, 0.350f, 0.350f, 1.0f };
m_pImmediateContext->ClearRenderTarget(nullptr, ClearColor,
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
m_pImmediateContext->ClearDepthStencil(nullptr, CLEAR_DEPTH_FLAG, 1.f, 0,
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
// Bind vertex buffer
Uint32 offset = 0;
IBuffer *pBuffs[] = {m_CubeVertexBuffer};
m_pImmediateContext->SetVertexBuffers(0, 1, pBuffs, &offset, RESOURCE_STATE_TRANSITION_MODE_TRANSITION,
SET_VERTEX_BUFFERS_FLAG_RESET);
m_pImmediateContext->SetIndexBuffer(m_CubeIndexBuffer, 0,
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
// Set pipeline state
m_pImmediateContext->SetPipelineState(m_pPSO);
// Commit shader resources
m_pImmediateContext->CommitShaderResources(m_pSRB, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
DrawAttribs DrawAttrs;
DrawAttrs.IsIndexed = true;
DrawAttrs.IndexType = VT_UINT32; // Index type
DrawAttrs.NumIndices = 36;
// Verify the state of vertex and index buffers
DrawAttrs.Flags = DRAW_FLAG_VERIFY_STATES;
m_pImmediateContext->Draw(DrawAttrs);
自动状态管理在许多场景中都很有用,尤其是在将旧应用程序移植到Diligent API时。但是,它具有以下限制:
- 状态仅针对整个资源进行跟踪。无法转换单个mip级别和/或纹理数组切片。
- 状态是全局资源属性。使用资源的每个设备上下文都看到相同的状态。
- 自动状态转换不是线程安全的。任何使用
RESOURCE_STATE_TRANSITION_MODE_TRANSITION
的操作都要求没有其他线程同时访问相同资源的状态。
显式状态管理
正如我们上面讨论的,无法以完全自动的方式高效地解决资源管理问题,因此 Diligent Engine 不会试图超越行业,而是将状态转换作为 API 的一部分。它引入了一组状态,这些状态主要映射到 Direct3D12 资源状态,因为我们认为这种方法足够富有表现力,并且比 Vulkan 的方法更清晰。如果应用程序需要非常精细的控制,它可以使用本地 API 互操作性直接将 Vulkan 屏障插入到命令缓冲区中。Diligent Engine 定义的状态列表以及它们与 Direct3D12 和 Vulkan 的映射如下表 4 所示。
Diligent状态 (RESOURCE_STATE_) | Direct3D12状态 (D3D12_RESOURCE_STATE_) | Vulkan图像布局 (VK_IMAGE_LAYOUT_) | Vulkan访问类型 |
UNKNOWN | 不适用 | 不适用 | 不适用 |
UNDEFINED | COMMON | UNDEFINED | 0 |
VERTEX_BUFFER | VERT_AND_CONST_BUFFER | 不适用 | VERTEX_ATTRIB_READ_BIT |
CONSTANT_BUFFER | VERT_AND_CONST_BUFFER | 不适用 | UNIFORM_READ_BIT |
INDEX_BUFFER | INDEX_BUFFER | 不适用 | INDEX_READ_BIT |
RENDER_TARGET | RENDER_TARGET | CLR_ATTACH_OPTIMAL |
|
UNORDERED_ACCESS | UNORDERED_ACCESS | 通用 | SHADER_WRITE_BIT | SHADER_READ_BIT |
DEPTH_READ | DEPTH_READ | DS_READ_ONLY_OPTIMAL | DS_ATTACH_READ_BIT |
DEPTH_WRITE | DEPTH_WRITE | DS_ATTACH_OPTIMAL |
|
SHADER_RESOURCE | NON_PS_RESOURCE | PS_RESOURCE | SHADER_READ_OPTIMAL | SHADER_READ_BIT |
INDIRECT_ARGUMENT | INDIRECT_ARGUMENT | 不适用 | INDIRECT_CMD_READ_BIT |
COPY_DEST | COPY_DEST | TRANSFER_DST_OPTIMAL | TRANSFER_WRITE_BIT |
COPY_SOURCE | COPY_SOURCE | TRANSFER_SRC_OPTIMAL | TRANSFER_READ_BIT |
PRESENT | PRESENT | PRESENT_SRC_KHR | MEMORY_READ_BIT |
Diligent 资源状态与 Direct3D12 资源状态几乎是一一对应的。唯一的真正区别在于 Diligent 中,SHADER_RESOURCE
状态映射到 NON_PIXEL_SHADER_RESOURCE
和 PIXEL_SHADER_RESOURCE
状态的并集,这似乎不是一个真正的问题。
与 Vulkan 相比,Diligent 中的资源状态更为通用,具体来说:
RENDER_TARGET
状态始终定义可写渲染目标(设置COLOR_ATTACHMENT_READ_BIT
和COLOR_ATTACHMENT_WRITE_BIT
访问类型标志)。UNORDERED_ACCESS
状态始终定义可写存储图像/存储缓冲区(设置SHADER_WRITE_BIT
和SHADER_READ_BIT
访问类型标志)。- 转换为
CONSTANT_BUFFER
、UNORDERED_ACCESS
和SHADER_RESOURCE
状态以及从这些状态转换时,始终设置表 1 中给出的所有适用的管线阶段标志。
上述限制似乎都没有导致任何可测量的性能下降。同样,如果应用程序确实需要指定更精确的屏障,它可以依赖于原生 API 互操作性。
请注意,Diligent 定义了 UNKNOWN
和 UNDEFINED
两种状态,它们具有非常不同的含义。UNKNOWN
意味着引擎不知道该状态,并且应用程序手动管理该资源的状态。UNDEFINED
意味着引擎知道该状态,并且从底层 API 的角度来看是未定义的。此状态在 Direct3D12 和 Vulkan 中具有明确对应的状态。
Diligent Engine中的显式资源状态转换通过IDeviceContext::TransitionResourceStates()
方法执行,该方法接受一个StateTransitionDesc
结构数组
void IDeviceContext::TransitionResourceStates(Uint32 BarrierCount,
StateTransitionDesc* pResourceBarriers)
数组中的每个元素定义了要转换的资源(纹理或缓冲区)、旧状态、新状态以及纹理资源的mip级别范围和数组切片范围
struct StateTransitionDesc
{
ITexture* pTexture = nullptr;
IBuffer* pBuffer = nullptr;
Uint32 FirstMipLevel = 0;
Uint32 MipLevelsCount = 0;
Uint32 FirstArraySlice= 0;
Uint32 ArraySliceCount= 0;
RESOURCE_STATE OldState = RESOURCE_STATE_UNKNOWN;
RESOURCE_STATE NewState = RESOURCE_STATE_UNKNOWN;
bool UpdateResourceState = false;
};
如果资源的状态对引擎是已知的,则OldState
成员可以设置为UNKNOWN
,在这种情况下,引擎将使用资源中的状态。如果资源的状态对引擎是未知的,则OldState
不得为UNKNOWN
。NewState
永远不能为UNKNOWN
。
一个重要的成员是UpdateResourceState
标志。如果设置为true
,引擎会将资源的状态设置为NewState
给定的值。否则,状态将保持不变。
在显式和自动状态管理之间切换
Diligent Engine 提供了允许在自动和手动状态管理之间切换和混合的工具。ITexture
和 IBuffer
接口都公开了 SetState()
和 GetState()
方法,允许应用程序获取和设置资源状态。当资源的状态设置为 UNKNOWN
时,所有使用 RESOURCE_STATE_TRANSITION_MODE_TRANSITION
模式的方法都将忽略此资源。对于已知状态的所有资源,状态转换仍将执行。因此,应用程序可以通过将手动管理的资源状态设置为 UNKNOWN
来混合自动和手动状态管理。如果应用程序希望将状态管理交还给系统,它可以使用 SetState()
方法设置资源状态。或者,它可以将 UpdateResourceState
标志设置为 true
,这将产生相同的效果。
多线程安全性
正如我们上面讨论的,手动资源状态管理的主要优点是能够并行记录渲染命令。由于Diligent Engine中资源状态是全局跟踪的,因此必须采取以下预防措施:
- 使用
IDeviceContext::TransitionResourceStates()
在多个线程中同时记录相同资源的状态转换是安全的,只要UpdateResourceState
标志设置为false
。 - 任何使用
RESOURCE_STATE_TRANSITION_MODE_TRANSITION
模式的方法的线程,必须是唯一访问可能转换的资源的线程。这也适用于IDeviceContext::TransitionShaderResources()
方法。 - 如果线程使用
RESOURCE_STATE_TRANSITION_MODE_VERIFY
模式的任何方法(尽可能建议),则不应有其他线程更改相同资源的状态。
讨论
Diligent Engine采用D3D11风格的API,带有即时和延迟上下文来记录渲染命令。由于众所周知,延迟上下文在Direct3D11中表现不佳,因此人们可能会问为什么它们在Diligent中工作。答案是因为显式状态转换控制。虽然在Direct3D11中,资源状态管理始终是自动和隐式的,但Diligent允许应用程序直接控制每个操作如何处理资源状态。同时,设备上下文实现动态内存、描述符管理以及需要由记录渲染命令的线程处理的其他任务。
结论与未来工作
Diligent Engine v2.4中引入的显式资源状态管理系统结合了灵活性、效率和易用性。应用程序可以在典型的渲染场景中依赖自动资源状态管理,并在引擎没有足够的知识来最佳管理状态或在多线程渲染命令记录等不可能的情况下切换到手动模式。
目前,Diligent Engine仅支持一个命令队列,并作为单个即时上下文公开。下一步之一是通过多个即时上下文公开多个命令队列,以及用于同步队列之间执行的基本功能,以实现异步计算和其他高级渲染技术。