ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

DirectX 12 n-body 引力模拟实战:基于 DirectX-Graphics-Samples 的多引擎异步计算与跨队列 Fence 同步

DirectX 12 n-body 引力模拟实战:基于 DirectX-Graphics-Samples 的多引擎异步计算与跨队列 Fence 同步 示例工程【免费下载链接】DirectX-Graphics-SamplesThis repo contains the DirectX Graphics samples that demonstrate how to build graphics intensive applications on Windows.项目地址https://gitcode.com/gh_mirrors/di/DirectX-Graphics-Samples点击查看免费下载本篇技术指南以 DirectX-Graphics-Samples 仓库中的 D3D12nBodyGravity 示例 为对象深入讲解如何利用 Direct3D 12 的异步计算着色器多引擎multi-engine在专用计算命令队列上并行模拟 10,000 个粒子的 n-body 引力系统同时用跨命令队列的 Fence 同步机制协调计算与渲染两条流水线并兼顾 Root Signature 1.1 与 Enhanced Barriers 两项进阶特性。读完本文你将掌握多引擎异步计算应用的完整架构、双缓冲乒乓与 SRV/UAV 切换模式、Fence 跨队列同步写法以及如何在硬件不支持时优雅降级到传统资源屏障。示例概览为什么要用多引擎跑 n-body 模拟n-body 引力模拟每个粒子受所有其他粒子引力作用是典型的计算密集型负载非常适合交给 GPU 的异步计算队列D3D12_COMMAND_LIST_TYPE_COMPUTE处理。示例 readme 明确指出其核心价值在于两点readme.md图形命令与计算命令可同时录制渲染线程在图形命令队列DIRECT上录制绘制命令独立的工作线程在计算命令队列上录制并提交模拟命令GPU 在两个队列间并行执行跨命令队列的 Fence 同步用 Fence 保证计算写出的数据在渲染读取之前已完成以及渲染读取结束后计算才能覆写避免数据竞争。从源码结构看示例的核心骨架集中在 D3D12nBodyGravity.h 与 D3D12nBodyGravity.cpp 中主类继承DXSample通过OnInit/OnUpdate/OnRender/OnDestroy生命周期方法组织整个应用。整体架构渲染主线程 异步计算工作线程示例采用一主一从的线程模型主线程负责窗口、输入、相机、渲染与 Present后台计算线程持续运行模拟循环。头文件中的关键常量D3D12nBodyGravity.h奠定了数据规模static const UINT FrameCount 2; // 交换链双缓冲 static const UINT ThreadCount 1; // 异步计算线程上下文数量 static const float ParticleSpread; // 初始粒子散布半径实现中取 400.0f static const UINT ParticleCount 10000; // n-body 模拟的粒子总数ThreadCount是可扩展的每个线程拥有独立的计算命令队列、命令分配器、命令列表与 Fence见 D3D12nBodyGravity.h渲染端会按ThreadCount把屏幕切分为若干视口每个视口绘制一个计算上下文产生的粒子数据D3D12nBodyGravity.cpp。本示例ThreadCount 1时即单视口全屏渲染。初始粒子布局由 CreateParticleBuffers 完成将 10,000 个粒子平均分成两团分别以(±ParticleSpread×0.5, 0, 0)为中心、以±20的 z 轴初速度相向运动从而在引力作用下形成交互聚拢的双星系统——这是观察引力模拟最直观的初始条件。双缓冲乒乓SRV/UAV 交替读写模拟的正确性依赖读写分离计算着色器把上一帧的粒子数据SRV读入把新位置/速度写入另一块缓冲UAV渲染端则绘制没有被计算线程正在写入的那块缓冲。因此每个计算上下文维护两块粒子缓冲m_particleBuffer0/1并用m_srvIndex记录当前 SRV 是 0 号还是 1 号缓冲D3D12nBodyGravity.h。描述符堆中的资源视图按以下索引排布D3D12nBodyGravity.henum DescriptorHeapIndex : UINT32 { UavParticlePosVelo0 0, UavParticlePosVelo1 UavParticlePosVelo0 ThreadCount, SrvParticlePosVelo0 UavParticlePosVelo1 ThreadCount, SrvParticlePosVelo1 SrvParticlePosVelo0 ThreadCount, DescriptorCount SrvParticlePosVelo1 ThreadCount };每次模拟结束后计算线程执行m_srvIndex[threadIndex] 1 - m_srvIndex[threadIndex]D3D12nBodyGravity.cpp实现两块缓冲的角色互换。粒子数据格式为Particle{ float4 position; float4 velocity; }D3D12nBodyGravity.h其中position.w存放质量相关参数、velocity.w存放加速度模长供渲染端着色粒子颜色。计算着色器内核tile 化 n-body 引力求解核心计算内核位于 nBodyGravityCS.hlsl它实现了经典的块缓存tile-basedO(N²) 引力求解以共享内存减少全局内存读取#define blocksize 128 groupshared float4 sharedPos[blocksize];每个线程负责一个粒子的位置与速度更新。外层循环按 tile 遍历所有粒子每次把一个 128 粒子的 tile 拷入groupshared共享内存再内层循环完成该 tile 内所有粒子的两两引力计算nBodyGravityCS.hlsl。物理参数在文件顶部定义static float softeningSquared 0.0012500000f * 0.0012500000f; // 软化项避免奇点 static float g_fG 6.67300e-11f * 10000.0f; // 万有引力常数放大 static float g_fParticleMass g_fG * 10000.0f * 10000.0f; // 粒子质量核心的质点间引力更新函数bodyBodyInteraction采用经典的r / |r|³形式并加入软化项softening防止距离趋零时加速度发散nBodyGravityCS.hlslvoid bodyBodyInteraction(inout float3 ai, float4 bj, float4 bi, float mass, int particles) { float3 r bj.xyz - bi.xyz; float distSqr dot(r, r); distSqr softeningSquared; float invDist 1.0f / sqrt(distSqr); float invDistCube invDist * invDist * invDist; float s mass * invDistCube * particles; ai r * s; }计算着色器通过常量缓冲cbCS接收每帧参数nBodyGravityCS.hlsl对应 CPU 端 ConstantBufferCS 的填充成员值含义param[0]ParticleCount10000粒子总数MAX_PARTICLESparam[1]ceil(ParticleCount / 128.0f)79tile 数量即外层循环次数paramf[0]0.1f时间步长 deltaTimeparamf[1]1.0f速度阻尼系数 damping内核末尾有一个容易踩坑的细节当ParticleCount不是blocksize整数倍时tile 循环会产生越界读——而计算着色器中越界读返回 0相当于在原点 (0,0,0) 凭空多出若干幽灵粒子引力源因此需要显式回减nBodyGravityCS.hlslconst int tooManyParticles g_param.y * blocksize - g_param.x; bodyBodyInteraction(accel, float4(0, 0, 0, 0), pos, mass, -tooManyParticles);随后按vel accel * dt; vel * damping; pos vel * dt完成欧拉积分nBodyGravityCS.hlsl并把加速度模长写入velo.w供渲染使用。Dispatch 线程组数为ceil(ParticleCount / 128)即 79 组D3D12nBodyGravity.cpp。渲染管线几何着色器生成点精灵粒子绘制走顶点着色器 几何着色器 像素着色器三段管线着色器见 ParticleDraw.hlsl顶点着色器以SV_VERTEXID索引结构化缓冲g_bufPosVelo即计算线程刚写好的粒子缓冲取出位置与加速度模长按mag velo.w / 9在红色与基础色之间插值实现高速粒子更红的视觉反馈ParticleDraw.hlsl几何着色器把每个点展开为 4 个顶点的公告板四边形billboard用逆视图矩阵让粒子始终面向相机再经worldViewProjection变换输出ParticleDraw.hlsl像素着色器依据纹理坐标生成径向渐变形成柔和光斑效果ParticleDraw.hlsl。对应图形 PSO 配置混合模式为源 alpha 加性混合SrcBlend SRC_ALPHA, DestBlend ONE关闭深度写入D3D12nBodyGravity.cpp每帧以DrawInstanced(ParticleCount, 1, 0, 0)一次性绘制全部 10,000 个粒子D3D12nBodyGravity.cpp。跨命令队列的 Fence 同步计算与渲染的握手协议这是示例最值得研究的部分完整实现分布在 AsyncComputeThreadProc 与 OnRender 中。整个同步协议包含两个方向的等待渲染等待计算OnRender开头检查每个计算线程的 Fence 完成值若当前帧模拟未完成则调用m_commandQueue-Wait(m_threadFences[n], threadFenceValue)让渲染队列等待计算队列的信号D3D12nBodyGravity.cpp保证 SRV 数据已就绪计算等待渲染计算线程提交模拟后Signal自己的 Fence 并阻塞等待完成随后检查渲染队列的 Fence若渲染线程尚未释放该缓冲的 SRV 读取权则调用pCommandQueue-Wait(m_renderContextFence, renderContextFenceValue)让计算队列等待渲染队列的信号D3D12nBodyGravity.cpp避免下一轮模拟覆写正在被渲染的缓冲。计算线程主循环伪代码D3D12nBodyGravity.cpploop until m_terminating: Simulate(threadIndex) // 录制屏障 Dispatch 粒子模拟 提交计算命令列表 threadFenceValue InterlockedIncrement(m_threadFenceValues[i]) computeQueue-Signal(threadFence, threadFenceValue) // 通知渲染模拟完成 SetEventOnCompletion(threadFenceValue); WaitForSingleObject(...) if (renderContextFence 未完成该帧): // 渲染还在读 SRV computeQueue-Wait(renderContextFence, renderContextFenceValue) m_srvIndex[i] 1 - m_srvIndex[i] // 交换 SRV/UAV 角色 重置命令分配器与命令列表注意这里使用了InterlockedIncrement/InterlockedExchange/InterlockedCompareExchange等原子操作头文件顶部定义了InterlockedGetValue宏D3D12nBodyGravity.cpp确保跨线程读取 Fence 值安全。Fence 对象本身以D3D12_FENCE_FLAG_SHARED创建D3D12nBodyGravity.cpp保证跨队列可等待。资源生命周期管理同样值得借鉴头文件注释明确提醒ComPtr只管理 CPU 侧生命周期GPU 仍可能引用待销毁对象因此OnDestroy先置m_terminating终止计算线程、再WaitForRenderContext确保 GPU 排空后才释放资源D3D12nBodyGravity.h、D3D12nBodyGravity.cpp。可选特性一Root Signature 1.1 与静态数据声明readme 提到示例已面向 Windows 10 周年更新 SDK 构建并启用了 Root Signature 1.1。Root Signature 1.1 允许应用向驱动声明描述符不会变或描述符指向的数据不会变驱动据此做出原本不可能的优化例如提前缓存地址、减少重绑定开销。示例在 LoadAssets 中先通过CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, ...)探测最高可用版本最高请求D3D_ROOT_SIGNATURE_VERSION_1_1失败则回退到D3D_ROOT_SIGNATURE_VERSION_1_0再调用D3DX12SerializeVersionedRootSignature序列化——这是一套标准的版本协商降级模式。两套根签名分别服务于图形与计算管线且使用了不同的 Volatile/Static 声明策略根参数类型标志说明图形GraphicsRootCBV常量缓冲视图D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC每帧更新但数据地址稳定可静态图形GraphicsRootSRVTableSRV 描述符表D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC粒子数据按帧切换指向数据视为静态计算ComputeRootCBV常量缓冲视图D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC模拟参数初始化后不再变化计算ComputeRootSRVTableSRV 描述符表D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE描述符逐帧切换声明为 Volatile计算ComputeRootUAVTableUAV 描述符表D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE每帧由计算写入指向数据声明为 Volatile关键代码D3D12nBodyGravity.cppranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE); ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE);Root Signature 1.1 的价值正体现在这里描述符语义被显式化驱动可以针对DATA_STATIC的绑定做激进优化而VOLATILE声明则明确告诉驱动每次都必须重新解析语义清晰且安全。可选特性二Enhanced Barriers 与 Legacy Barriers 双路径readme 指出硬件支持时示例使用 Enhanced Barriers否则回退到 Legacy Barriers。示例在LoadPipeline中通过D3D12_FEATURE_D3D12_OPTIONS12探测EnhancedBarriersSupportedD3D12nBodyGravity.cpp并以m_bIsEnhancedBarriersEnabled贯穿全部资源创建与屏障代码。资源创建路径差异Enhanced Barriers 路径使用新 APICreateCommittedResource3配合D3D12_BARRIER_LAYOUT_UNDEFINED初始布局Legacy 路径则用CreateCommittedResource并直接指定初始状态如D3D12_RESOURCE_STATE_COPY_DESTD3D12nBodyGravity.cpp。屏障指令差异Enhanced Barriers 路径调用m_commandList-Barrier(...)提交D3D12_BUFFER_BARRIER/D3D12_TEXTURE_BARRIER显式给出SyncBefore/SyncAfter/AccessBefore/AccessAfterLegacy 路径则调用ResourceBarrier做状态转换Transition。例如粒子缓冲由 Copy 转入可读状态时D3D12nBodyGravity.cpp// Enhanced Barriers CD3DX12_BUFFER_BARRIER( D3D12_BARRIER_SYNC_COPY, // SyncBefore D3D12_BARRIER_SYNC_NON_PIXEL_SHADING, // SyncAfter D3D12_BARRIER_ACCESS_COPY_DEST, // AccessBefore D3D12_BARRIER_ACCESS_SHADER_RESOURCE, // AccessAfter m_particleBuffer0[index].Get()); // Legacy Barriers m_commandList-ResourceBarrier(1, CD3DX12_RESOURCE_BARRIER::Transition( m_particleBuffer0[index].Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE));一个值得注意的优化细节帧首屏障使用D3D12_BARRIER_SYNC_NONE与D3D12_BARRIER_ACCESS_NO_ACCESSD3D12nBodyGravity.cpp源码注释明确说明这样能避免 Legacy Barrier 必须 flush 已完成工作的隐式开销——Enhanced Barriers 把同步语义拆分成 Sync 与 Access 两个维度允许在确实无依赖时跳过不必要的同步等待这正是新屏障模型相对旧模型的精度优势。计算阶段的 UAV 读写屏障同样成对出现Dispatch 前SHADER_RESOURCE → UNORDERED_ACCESSDispatch 后反向D3D12nBodyGravity.cpp确保读写顺序正确。构建与运行示例源码位于 Samples/Desktop/D3D12nBodyGravity/src工程文件为 D3D12nBodyGravity.vcxproj支持 Debug/Release × x64/ARM64 四套配置。依赖以下 NuGet 包由工程文件中的EnsureNuGetPackageBuildImports目标强制校验Microsoft.Direct3D.D3D12.1.618.3D3D12 Agility SDKMicrosoft.Direct3D.DXC.1.8.2505.32HLSL 编译器WinPixEventRuntimePIX 事件标注支持着色器在构建期由dxc.exe编译为.cso工程内 CustomBuild 步骤D3D12nBodyGravity.vcxproj# 计算着色器 dxc.exe -nologo -Tcs_6_0 -ECSMain -Zi -Qembed_debug -FonBodyGravityCS.cso nBodyGravityCS.hlsl # 绘制着色器VS/GS/PS 三段 dxc.exe -nologo -Tvs_6_0 -EVSParticleDraw -Zi -Qembed_debug -FoParticleDraw_VS.cso ParticleDraw.hlsl dxc.exe -nologo -Tgs_6_0 -EGSParticleDraw -Zi -Qembed_debug -FoParticleDraw_GS.cso ParticleDraw.hlsl dxc.exe -nologo -Tps_6_0 -EPSParticleDraw -Zi -Qembed_debug -FoParticleDraw_PS.cso ParticleDraw.hlsl运行时说明直接用 Visual Studio 打开工程生成并运行代码通过D3D12SDKVersion 618与D3D12SDKPath .\\D3D12\\加载随包提供的 Agility SDKD3D12nBodyGravity.cpp命令行传入-warp或/warp可强制使用 WARP 软件光栅化适配器便于在无独显的机器上验证逻辑DXSample.cpp示例调用DXGIDeclareAdapterRemovalSupport声明支持设备移除并在OnRender捕获DXGI_ERROR_DEVICE_REMOVED/RESET后通过RestoreD3DResources重建全部 D3D 资源D3D12nBodyGravity.cppDebug 构建会启用 D3D12 调试层需要系统安装 Graphics Tools 可选功能详见 D3D12nBodyGravity.cpp。键盘操作为 WASD 移动相机、鼠标观察示例内嵌的SimpleCamera与StepTimer负责视角与帧计时D3D12nBodyGravity.cpp。小结这套架构能复用到哪里D3D12nBodyGravity 示例的价值不止于粒子效果本身其背后是可复用的通用模式多引擎异步计算把与渲染解耦的 GPU 计算物理、粒子、流体、后处理预处理等放到独立 COMPUTE 队列与图形队列并行执行充分压榨 GPU 空闲带宽跨队列 Fence 握手渲染端Queue::Wait(computeFence) 计算端Queue::Wait(renderFence)的双向同步协议是任何多队列应用都必须掌握的范式双缓冲乒乓通过 SRV/UAV 角色互换避免读写冲突无需为每帧分配新缓冲特性协商与降级Root Signature 版本探测回退、Enhanced Barriers 探测回退两套降级路径展示了面向不同硬件能力编写渐进增强代码的标准做法。如果你希望进一步扩展可将ThreadCount调大观察多计算上下文的视口分割效果或调整 nBodyGravityCS.hlsl 中的软化项、引力常数与cbCS的时间步长/阻尼参数观察不同物理行为下的模拟形态。赞分享示例工程【免费下载链接】DirectX-Graphics-SamplesThis repo contains the DirectX Graphics samples that demonstrate how to build graphics intensive applications on Windows.项目地址https://gitcode.com/gh_mirrors/di/DirectX-Graphics-Samples点击查看免费下载相关推荐如何快速上手C Interfaces and Implementations新手必备入门教程如何快速上手C Interfaces and Implementations新手必备入门教程 C Interfaces and Implementations开发工具DirectX 12保留资源技术基于DirectX-Graphics-Samples的高级特性DirectX 12保留资源技术基于DirectX Graphics Samples的高级特性 DirectX 12保留资源技术是现代图形编程中的高级特性能示例工程DirectX 12管线状态缓存基于DirectX-Graphics-Samples的性能优化终极指南DirectX 12管线状态缓存基于DirectX Graphics Samples的性能优化终极指南 在现代图形应用开发中DirectX 12管线状态缓存示例工程上一篇TimelineJS单元测试教程确保代码质量与稳定性下一篇Lexical富文本编辑器4大架构突破重构现代内容创作体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表