Unity游戏多线程调用ID3D11ImmediateContext::Map触发E_OUTOFMEMORY问题
解决多线程调用ID3D11ImmediateContext::Map时的无规律失败与E_OUTOFMEMORY错误
首先得敲黑板:ID3D11ImmediateContext绝对不是线程安全的!你遇到的无规律失败和内存错误,本质是多个线程同时抢占Immediate Context的资源访问权,导致D3D11内部状态混乱——看起来是内存不足,实际是线程竞争触发的异常。下面给你几个针对性的解决方案:
1. 改用Deferred Context做线程安全的资源修改
这是D3D11为多线程渲染/资源更新设计的标准方案。每个工作线程可以创建自己的ID3D11DeviceContextDeferred,在这个延迟上下文里独立执行Map、修改缓冲区/纹理的操作,最后把生成的命令列表提交给Immediate Context统一执行。
示例代码大概是这样:
// 每个线程创建自己的Deferred Context ID3D11DeviceContextDeferred* deferredCtx = nullptr; sU_m_D_device->CreateDeferredContext(0, &deferredCtx); // 在Deferred Context上安全地Map资源 ID3D11Buffer* d3dbuf = static_cast<ID3D11Buffer*>(bufferHandle); D3D11_MAPPED_SUBRESOURCE mappedRes; HRESULT hr = deferredCtx->Map(d3dbuf, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedRes); if (SUCCEEDED(hr)) { // 修改顶点数据... deferredCtx->Unmap(d3dbuf, 0); } // 生成命令列表并提交给Immediate Context执行 ID3D11CommandList* cmdList = nullptr; deferredCtx->FinishCommandList(FALSE, &cmdList); sU_m_D_context->ExecuteCommandList(cmdList, FALSE); // 别忘了释放资源 cmdList->Release(); deferredCtx->Release();
2. 给Immediate Context的访问加线程锁
如果暂时不想重构为Deferred Context,最直接的办法是用同步原语(比如临界区)保证同一时间只有一个线程调用Immediate Context的方法:
// 全局或类级别的临界区 CRITICAL_SECTION ctxLock; InitializeCriticalSection(&ctxLock); void* BeginModifyingVBO(void* bufferHandle) { ID3D11Buffer* d3dbuf = static_cast<ID3D11Buffer*>(bufferHandle); D3D11_MAPPED_SUBRESOURCE mappedRes; // 进入临界区,独占Immediate Context EnterCriticalSection(&ctxLock); HRESULT hr = sU_m_D_context->Map(d3dbuf, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedRes); if (FAILED(hr)) { LeaveCriticalSection(&ctxLock); return nullptr; } // 注意:Unmap的时候也要在临界区里! // 可以把Unmap封装到EndModifyingVBO函数里,同样加锁 return mappedRes.pData; } void EndModifyingVBO(void* bufferHandle) { ID3D11Buffer* d3dbuf = static_cast<ID3D11Buffer*>(bufferHandle); EnterCriticalSection(&ctxLock); sU_m_D_context->Unmap(d3dbuf, 0); LeaveCriticalSection(&ctxLock); }
3. 优化资源的创建与Map参数
有时候E_OUTOFMEMORY是假警报——比如资源正在被GPU使用,无法被Map。你需要确保:
- 创建顶点缓冲区时使用
D3D11_USAGE_DYNAMIC,绑定D3D11_BIND_VERTEX_BUFFER,这是为频繁更新的动态资源设计的。 - Map时优先用
D3D11_MAP_WRITE_DISCARD(适合完全重写的情况)或D3D11_MAP_WRITE_NO_OVERWRITE(适合追加数据的情况),这两种模式不需要等待GPU释放资源,能减少冲突概率。
4. 启用D3D11调试层排查真实错误
打开D3D11的调试层,它会输出更详细的错误日志——比如到底是真的内存不足,还是线程访问冲突,或者资源状态不兼容导致的Map失败。在创建D3D11设备时加上D3D11_CREATE_DEVICE_DEBUG标志即可。
内容的提问来源于stack exchange,提问作者MrDyrektor
相关产品推荐
相关产品推荐

