运行时会调用驱动程序的 RecycleCreateCommandList 函数,使之前未使用的 DDI 句柄重新完全有效。
这些回收 DDI 函数提供了优化机会,有助于回收小内存量命令列表的资源。 以下伪代码展示了通过从 API 到 DDI 的函数调用流程来实现运行时的情况:
::FinishCommandList()
{
// Empty InterlockedSList, integrating into the cache
Loop { DC::pfnRecycleCommandList }
If (Previously Destroyed CommandList Available)
{ IC::pfnRecycleCreateCommandList }
else
{
IC::pfnCalcPrivateCommandListSize
IC::pfnCreateCommandList
IC::pfnCalcDeferredContextHandleSize(D3D11DDI_HT_COMMANDLIST)
}
Loop { DC::pfnDestroy* (context-local handle destroy) }
IC::pfnRecycleCreateDeferredContext
}
...
Sporadic: DC::pfnCreate* (context-local open during first-bind per CommandList)
CommandList::Destroy()
{
// If DC still alive, almost always recycle:
If (DC still alive)
{ IC::pfnRecycleDestroyCommandList }
Else
{ IC::pfnDestroyCommandList }
// Add to InterlockedSList
}
以下状态图显示了即时上下文 DDI 命令列表句柄的有效性。 绿色状态代表一个句柄,可与 CommandListExecute 一起使用。
1. 运行时与驱动协作流程
伪代码:命令列表生命周期(小内存量优化路径)
sequenceDiagram
participant App
participant Runtime
participant Driver
App->>Runtime: ID3D11DeviceContext::FinishCommandList
Runtime->>Driver: RecycleCreateCommandList (复用句柄)
alt 缓存命中
Driver->>Runtime: 返回已回收的hCommandList
Runtime->>Driver: RecordCommands (记录新命令)
Runtime->>Driver: RecycleCommandList (执行后回收)
else 缓存未命中
Driver->>Runtime: 返回E_OUTOFMEMORY
Runtime->>Driver: CreateCommandList (创建新句柄)
Runtime->>App: 返回失败或新句柄
end
2. 驱动函数实现规范
RecycleCreateCommandList (关键回收路径)
HRESULT APIENTRY RecycleCreateCommandList(
D3D11DDI_HDEFERREDCONTEXT hDeferredContext,
D3D11DDI_HCOMMANDLIST* phCommandList
) {
// 1. 从延迟上下文的线程本地缓存获取可复用句柄
if (!hDeferredContext->commandListCache.empty()) {
*phCommandList = hDeferredContext->commandListCache.back();
hDeferredContext->commandListCache.pop_back();
// 2. 重置内部状态(保留内存分配)
ResetCommandListData(*phCommandList);
return S_OK;
}
// 3. 缓存为空时直接返回错误(无回调)
return E_OUTOFMEMORY;
}
RecycleCommandList (内存整合)
void APIENTRY RecycleCommandList(
D3D11DDI_HCOMMANDLIST hCommandList,
D3D11DDI_HDEFERREDCONTEXT hDeferredContext
) {
// 1. 确保由正确的延迟上下文线程调用
assert(GetCurrentThreadId() == hDeferredContext->threadId);
// 2. 回收至线程本地缓存
hDeferredContext->commandListCache.push_back(hCommandList);
// 3. 可选:释放非必要内存(如大型临时缓冲区)
TrimCommandListMemory(hCommandList);
}
3. 内存管理优化策略
缓存层级设计
缓存级别 | 内容 | 优化目标 |
---|---|---|
线程本地缓存 | 高频小命令列表(如CopyResource ) |
零分配、无锁操作 |
全局备用池 | 低频大命令列表 | 减少内存碎片 |
动态缓存大小调整
// 根据使用频率动态调整线程本地缓存大小
void AdjustCacheSize(D3D11DDI_HDEFERREDCONTEXT hContext) {
const size_t currentSize = hContext->commandListCache.size();
if (currentSize > hContext->maxCacheSize && hContext->hits > hContext->misses) {
hContext->maxCacheSize *= 2; // 扩展缓存
} else if (currentSize < hContext->maxCacheSize / 4) {
hContext->maxCacheSize /= 2; // 收缩缓存
}
}
4. 错误处理与调试
严格校验(调试构建)
#if DBG
void ValidateRecycledHandle(D3D11DDI_HCOMMANDLIST hCommandList) {
if (IsHandleCorrupted(hCommandList)) {
DebugBreak(); // 捕获重复释放或无效状态
}
}
#endif
性能计数器
指标 | 采集方法 | 优化参考 |
---|---|---|
缓存命中率 | hits / (hits + misses) |
>90% 表示优化有效 |
平均回收延迟 | QueryPerformanceCounter |
应 <1μs |
5. 线程模型与同步
无锁实现要求
线程本地缓存:每个延迟上下文维护独立的 std::vector 或环形缓冲区,无需同步。
全局池(可选):使用原子操作或无锁队列(如 mpsc_queue)。
禁止行为
跨线程访问缓存:若检测到非所属线程操作,触发调试层断言。
阻塞操作:禁止在回收路径中使用 malloc/free 或系统锁。
6. 总结
高频复用路径:
RecycleCreateCommandList 和 RecycleCommandList 构成零分配闭环。
低开销设计:线程本地缓存 + 无锁结构确保小命令列表的极致性能。
弹性扩展:动态缓存大小适应不同负载场景。