DirectX12下Direct2D UI叠加层的分摊渲染及Alpha混合问题
D3D11on12与Direct2D问题解决及Alpha混合实现
问题概述
使用D3D11on12结合Direct2D时遇到两个核心问题:
- 偶尔触发设备移除错误:
D3D12 ERROR: ID3D12Device::RemoveDevice: Device removal has been triggered for the following reason (DXGI_ERROR_ACCESS_DENIED: The application attempted to use a resource it does not access to. This could be, for example, rendering to a texture while only having read access.). [ EXECUTION ERROR #232: DEVICE_REMOVAL_PROCESS_AT_FAULT]
- 存在卡顿,计划通过UI分摊渲染优化,将UI渲染到独立透明位图后叠加到交换链缓冲区,但不知如何实现Alpha混合。
一、设备移除错误修复
- 权限检查:确认交换链后台缓冲区创建时,
DXGI_USAGE包含DXGI_USAGE_RENDER_TARGET_OUTPUT,确保拥有写入权限;使用ID3D11On12Device::CreateWrappedResource包装D3D12资源时,D3D11_RESOURCE_FLAGS需允许写入操作。 - 资源状态同步:D3D12资源状态必须匹配当前操作:
- 渲染到后台缓冲区前,通过
ID3D12GraphicsCommandList::ResourceBarrier将资源切换至D3D12_RESOURCE_STATE_RENDER_TARGET; - 完成渲染后切换回
D3D12_RESOURCE_STATE_PRESENT用于展示; - 避免D3D12和D3D11同时操作同一资源,操作前后需通过
ID3D11On12Device::AcquireWrappedResources和ReleaseWrappedResources同步。
- 渲染到后台缓冲区前,通过
- 包装资源生命周期:确保D3D12资源未被提前释放,包装的D3D11资源需与原D3D12资源生命周期保持一致。
二、卡顿优化与UI分摊渲染
- 分离UI与场景渲染:创建独立的UI专用D3D12纹理,通过
CreateWrappedResource转为D3D11资源后,用CreateBitmapFromDxgiSurface创建Direct2D位图。固定间隔更新UI(如每2帧或16ms一次),无需与场景帧完全同步。 - 缓存UI结果:保留最近一次渲染完成的UI位图,后续帧直接复用该资源叠加,避免每帧重绘全部UI元素。
- 异步渲染:将UI渲染任务放到独立的D3D11命令队列执行,与场景渲染并行,减少主线程阻塞。
三、Alpha混合实现(适配你的技术栈)
方案1:Direct2D直接叠加(零3D管线知识)
这是最简便的方式,利用Direct2D内置的混合功能:
- 将交换链后台缓冲区包装为D3D11渲染目标,再创建Direct2D渲染目标。
- 设置混合模式为
D2D1_BLEND_MODE_SRC_OVER,直接绘制UI位图:
此方式会自动根据UI位图的Alpha通道完成混合,无需手动计算。// 假设pRenderTarget是交换链对应的Direct2D渲染目标,pUIBitmap是UI透明位图 pRenderTarget->SetBlendState(D2D1::BlendStateDescription(D2D1_BLEND_MODE_SRC_OVER)); pRenderTarget->DrawBitmap( pUIBitmap, D2D1::RectF(0.0f, 0.0f, screenWidth, screenHeight), // 目标区域 1.0f, // 全局透明度 D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, D2D1::RectF(0.0f, 0.0f, uiWidth, uiHeight) // UI源区域 );
方案2:计算着色器实现(灵活定制混合逻辑)
如果你需要自定义混合规则,用熟悉的计算着色器实现:
- 为交换链后台缓冲区创建UAV(无序访问视图),为UI位图和后台缓冲区创建SRV(着色器资源视图)。
- 编写Alpha混合计算着色器:
// 注册绑定:UAV对应输出,两个SRV分别是场景和UI RWTexture2D<float4> sceneOutput : register(u0); Texture2D<float4> sceneInput : register(t0); Texture2D<float4> uiTexture : register(t1); SamplerState samplerLinear : register(s0); [numthreads(8, 8, 1)] void BlendCS(uint3 dispatchID : SV_DispatchThreadID) { uint2 coord = dispatchID.xy; // 确保坐标在纹理范围内 if (coord.x >= sceneOutput.Width || coord.y >= sceneOutput.Height) return; float4 sceneColor = sceneInput[coord]; float4 uiColor = uiTexture[coord]; // 标准Alpha混合公式:最终颜色 = UI颜色*Alpha + 场景颜色*(1-Alpha) float4 finalColor = uiColor * uiColor.a + sceneColor * (1.0f - uiColor.a); sceneOutput[coord] = finalColor; } - 在命令队列中绑定SRV、UAV和采样器,调度计算着色器完成混合:
// 假设pComputeCommandList是已记录的计算命令列表 pComputeCommandList->SetComputeShader(pBlendCS); pComputeCommandList->SetShaderResources(0, 1, &pSceneSRV); pComputeCommandList->SetShaderResources(1, 1, &pUISRV); pComputeCommandList->SetUnorderedAccessViews(0, 1, &pSceneUAV, nullptr); // 计算调度线程组数量 UINT groupX = (screenWidth + 7) / 8; UINT groupY = (screenHeight + 7) / 8; pComputeCommandList->Dispatch(groupX, groupY, 1);
关键注意事项
- 确保UI位图的像素格式包含Alpha通道,推荐
DXGI_FORMAT_R8G8B8A8_UNORM。 - 操作前后正确切换资源状态:使用Direct2D前切换到
D3D12_RESOURCE_STATE_RENDER_TARGET,使用计算着色器前切换到D3D12_RESOURCE_STATE_UNORDERED_ACCESS,完成后切换回D3D12_RESOURCE_STATE_PRESENT。
内容的提问来源于stack exchange,提问作者Tom Huntington
相关产品推荐
相关产品推荐

