Skip to main content

Chapter 112: Hybrid Compute Techniques

混合计算(Hybrid Compute)在实时图形工程中指 CPU、图形队列、计算队列、拷贝队列和专用 AI / reconstruction pass 共同完成一帧。读完本章后,读者应能定位一帧中哪些工作属于 CPU 责任,哪些工作适合交给 GPU compute,哪些同步点会扩大 frame latency,并能用资源读写关系判断一个优化方案是否成立。

本章用一个贯穿帧作为材料:场景中有 8 万个粒子、2 万个可见性候选对象、TAA 历史缓冲、一个低分辨率光追反射缓冲,以及最终上采样 pass。CPU 负责采集输入、提交命令和维护资源生命周期;compute pass 负责粒子更新、可见性压缩、indirect draw 参数生成、降噪或上采样前处理;graphics pass 负责主光栅、透明物体、后处理和呈现。

这类 frame 的难点在于资源路径跨越多个执行域。粒子 buffer 被 compute 写入后会被 vertex shader 读取;可见性 buffer 被 compute 压缩后会被 indirect draw 消耗;历史颜色、motion vector、depth 和 exposure 会进入 TAA 或 upscaling pass;少量统计结果可能被 CPU 读取,用于下一帧 LOD 或质量策略。任何一次读写交接都需要明确所有权、可见性和等待关系。

混合计算的核心结论是:优化顺序应先减少跨域交接,再把剩余交接组织成可预测的 pipeline,最后才讨论 async compute 是否能并行。并行执行依赖可重叠的硬件资源、互不争用的带宽、清晰的 buffer 版本和稀疏的 fence wait;缺少这些条件时,异步队列会把问题转换成更隐蔽的同步等待。

112.1 CPU GPU Work Split Ownership and Synchronization

CPU 与 GPU 的分工应从所有权开始,而非从“哪边算得快”开始。所有权在这里表示谁产生数据、谁修改数据、谁承诺数据在某个时间点可读。CPU 能低成本处理场景遍历、资源调度、命令构建、用户输入和少量策略决策;GPU compute 适合处理同构并行任务,例如粒子积分、实例剔除、tile 分类、前缀和、压缩、降噪和图像重建。分工一旦跨过 CPU / GPU 边界,就会引入 upload、readback、barrier、fence 和多帧延迟。

贯穿帧中,CPU 在 frame N 的开始读入相机、输入事件和场景脏标记。它可以把 camera constants、emitter 参数和质量等级上传到 uniform / constant buffer。GPU 在同一帧里根据这些参数运行 compute pass,写出 visible_instance_bufferindirect_args_bufferparticle_state_buffer。graphics pass 随后读取这些 buffer,完成 indirect draw 和粒子绘制。这里的所有权链条是:CPU 写小块常量,compute 写大块派生数据,graphics 读派生数据。

资源所有权可以用三类问题检查。第一,当前 pass 对资源执行读、写还是读写。第二,下一个 pass 位于同一队列、不同队列还是 CPU。第三,下一个 pass 需要最新结果、上一帧结果,还是允许读取最近可用版本。第三个问题会直接决定同步策略。可见性 buffer 通常要求同帧最新结果,TAA history 通常读取上一帧稳定结果,统计 readback 通常可以延迟一到数帧。

下面这张图描述贯穿帧的所有权流转。图中只保留 CPU、compute、graphics、copy / readback 四类角色,用来观察资源如何跨域移动。

这条路径中最容易出错的位置是 compute culling → indirect args buffer → graphics draws。compute shader 写出的 indirect 参数在内存中只是普通 buffer 字节;graphics queue 能读取这些字节,需要 API 层建立执行顺序和内存可见性。Vulkan 的官方同步示例把 compute 写 storage buffer 后被 draw indirect 消耗的情况表达为从 compute shader 写访问到 draw indirect 读访问的 barrier;Vulkan Guide 的 synchronization examples给出了对应的 stage 和 access 组合。这个例子支撑的工程判断是:同步对象必须描述生产者 stage、生产者 access、消费者 stage、消费者 access,光知道“前面有一个 compute pass”不足以保证后续 draw 读到正确内容。

Direct3D 12 把多队列同步放在 fence 和 command queue 的时间线上描述。Direct3D 12 multi-engine synchronization 文档明确说明 3D、compute 和 copy 队列都基于 command list,queue 可以 signal / wait fence,fence 值由应用维护。这个模型给图形工程提供了一个稳定检查法:同一队列内更多依赖可以用资源 barrier 表达;跨队列交接需要明确 signal / wait;CPU 参与等待时要把它视为 frame latency 风险。

112.2 数据传输与同步瓶颈分析

数据传输瓶颈来自两个来源:字节移动和等待关系。字节移动包括 CPU upload、GPU readback、copy queue transfer、render target resolve 和跨内存域访问;等待关系包括 barrier、queue wait、fence wait、present wait 和 CPU 阻塞。混合计算优化时,应先把这两类成本分开看。一个 pass 本身可能只消耗 0.2ms,但它让 CPU 等待 readback 完成,就会把一帧推进节奏从流水线变成串行。

离散 GPU 上,CPU 与 GPU 通常通过 PCIe 或平台互连传递数据。upload 多用于常量、骨骼矩阵、动态实例数据和贴图流式加载;readback 多用于遮挡统计、截图、调试、GPU timer、拾取结果和自适应质量控制。upload 的常见问题是每帧更新过多小 buffer,导致映射、拷贝、driver tracking 和 cache flush 开销扩散。readback 的常见问题是 CPU 请求 frame N 的 GPU 结果时,GPU 仍在执行 frame N 或更早的命令,CPU 被迫等待整条队列追上。

统一内存平台减少了显式拷贝的存在感,但同步语义仍然存在。CPU 与 GPU 共享物理内存时,数据从一个执行域进入另一个执行域仍然需要可见性、cache 一致性和访问阶段控制。把统一内存理解为“免费共享”会掩盖 cache flush、resource hazard 和命令顺序问题。工程上应继续按生产者、消费者、访问方式和帧号记录资源生命周期。

barrier 的成本来自两个层面。第一个层面是内存可见性:前一个 pass 的写入需要对后一个 pass 的读取可见。第二个层面是执行顺序:后一个 pass 需要等待前一个 pass 到达某个阶段。过宽的 barrier 会把本可并行的工作推到同一个等待点;过窄的 barrier 会留下未定义读写。Vulkan 同步示例中,storage image 被 compute 写入后被 fragment shader 采样,需要从 shader write 过渡到 shader read,并伴随 image layout 转换。这类例子说明 barrier 的目标是描述资源状态变化,范围越接近实际 subresource 和实际阶段,调度器越有空间安排其它工作。

fence wait 的风险更大,因为它经常把 GPU 时间线暴露给 CPU。贯穿帧中,如果 CPU 在 frame N 末尾读取粒子数量统计,并立即用统计结果决定 frame N 的 UI 显示,CPU 就必须等待 GPU 完成统计写入、copy 到 readback buffer、同步到 CPU 可见内存。一个更稳定的设计是让 CPU 在 frame N 使用 frame N-2 的统计结果。这样统计依旧参与质量控制,但 frame 提交线程保留连续推进能力。

同步瓶颈可以按下列顺序排查。先看 CPU frame time 是否出现 WaitForFenceGetData、map readback buffer 或 present wait。再看 GPU timeline 中是否有大段 idle,确认是 graphics 等 compute、compute 等 graphics,还是 copy queue 与 graphics 争用带宽。随后检查资源是否存在单 buffer 读写复用,把它改成 ring buffer 或多版本 buffer。最后检查 barrier 范围是否覆盖了整张 texture、整个 buffer 或全部 pipeline stage,必要时收窄到实际写入的 mip、slice、range 和 stage。

112.3 混合计算下优化策略

混合计算的优化策略应服务一条目标:让 frame 中的生产者和消费者保持可预测距离。这个距离可以是同一 command buffer 中的 pass 顺序,也可以是跨队列的 fence 值,也可以是多帧 ring buffer 的版本号。距离清楚后,读写冲突、延迟预算和资源复用都能被检查。

减少 readback 是第一优先级。readback 适合低频、可延迟、可批处理的数据,例如 GPU timer、统计曲线、自动画质指标和 debug dump。实时控制路径应优先使用 GPU 内部决策,例如 compute 根据 depth pyramid 生成可见性结果,随后写入 indirect draw 参数,由 graphics 直接消费。CPU 接收的是延迟后的统计摘要,用于下一段时间的预算调节。这样控制闭环仍存在,但 CPU 提交线程无需等待同帧 GPU 结果。

安排 async compute 时,应先判断它能与 graphics 重叠的资源条件。适合异步的任务通常满足三个条件:计算密度高、读取的纹理和 buffer 与 graphics 当前 pass 的热点资源不同、输出被后续 pass 使用且允许排队。粒子 simulation、skinning preprocessing、light list 构建、部分 denoising、部分 post-process 都可能满足条件。带宽密集的全屏滤波、频繁写同一 render target 的 pass、与 graphics 共享大量 texture cache 的任务,放入 async compute 后可能放大带宽争用。

simulation、update 和 render 的分离能降低同步密度。simulation 负责生成物理或动画状态;update 负责把状态转换成渲染可用的 instance、cluster、draw args 或 material 参数;render 负责消费稳定版本。贯穿帧中,粒子 simulation 可以写 frame N+1 的状态,render 消费 frame N 的状态;可见性 culling 可以基于当前相机写同帧 indirect args,因为它直接影响 draw count;统计 readback 可以读取 frame N-2 的结果,因为它只影响后续质量策略。不同数据选择不同帧距离,能把同步点控制在真正需要同帧结果的路径上。

多版本资源是混合计算的基础结构。常见做法是为动态 buffer、indirect args、readback buffer、history texture 和 transient scratch 分配 2 到 3 个版本,并用 frame index 选择当前写入位置。版本数并非越多越好。版本过少会导致写入者等待读取者释放;版本过多会增加显存、cache footprint 和调试复杂度。D3D12 多引擎同步文档中的 pipelined compute / graphics 示例使用计算结果与图形消费之间的固定延迟,说明多版本数据可以把同步从“同一帧硬等待”改成“有界延迟流水线”。

barrier 合并也需要边界。多个 compute pass 写入互不重叠的 buffer 区域,随后一个 graphics pass 一次性读取,通常可以把写后读 barrier 合并到消费点。多个 pass 写入同一 image 的不同 mip 或 slice,且后续消费范围不同,则应保持 subresource 级别信息。把所有 barrier 全部推迟到 frame 尾部会延长资源不可用时间;把每个 dispatch 后都插入全局 barrier 会压缩调度空间。稳定策略是按消费者需要设置 barrier:资源第一次被下游读取前建立可见性,读取范围按实际使用填写。

112.4 与现代渲染管线的协同案例

现代渲染管线中的混合计算通常围绕历史数据、屏幕空间数据、可见性数据和重建数据展开。它们共同特点是输入来自 graphics pass,处理过程适合 compute,输出又回到 graphics 或 present 路径。协同设计的目标是让每个 pass 明确读写资源,并把 temporal dependency 写进 frame graph。

TAA(temporal anti-aliasing,时间抗锯齿)是典型案例。graphics pass 输出当前颜色、depth、motion vector 和 exposure;compute 或 pixel shader resolve pass 读取当前帧与历史帧,输出稳定后的颜色;下一帧再把这个结果当作 history 输入。混合计算判断点在 history 的版本管理:当前帧写的 history 不能同时作为当前帧输入;motion vector 的坐标空间要与 jittered projection、un-jittered history lookup 保持一致;history texture 的读写需要明确 resource state 或 image layout。

Denoising 也依赖 compute 与 graphics 的协同。低采样 ray tracing 结果通常包含噪声,denoiser 读取 noisy radiance、normal、depth、motion vector、roughness 和 history confidence,输出重建后的 lighting。这个过程通常带有多级滤波和 temporal accumulation。它的瓶颈可能来自全屏纹理带宽、shared memory tile、邻域采样次数和 history 读写,而非单纯 ALU。把 denoiser 放入 async compute 前,应检查 graphics 同时段是否也在大量采样 G-buffer 或写 HDR target;两条队列并行使用同一批内存通道时,timeline 看似重叠,frame time 仍可能增长。

GPU culling 与 visibility buffer 代表几何侧协同。compute pass 读取 instance bounds、camera planes、depth pyramid 或 cluster metadata,写出 compacted visible list 和 indirect args。graphics pass 随后按 visible list 绘制,或生成 visibility buffer,再由后续 shading pass 读取 primitive / material id。这里的关键资源是 visible_listdraw_argsdepth_pyramid 和 material / mesh descriptor。可见性路径的收益来自减少无效 draw、减少 vertex work、降低 overdraw 或改善 material sorting;成本来自 compute culling、prefix sum、barrier、indirect draw 调度和 buffer 带宽。

Upscaling 把混合计算推到最终图像质量阶段。现代上采样通常读取低分辨率颜色、depth、motion vector、exposure、reactive mask、history 和 jitter 参数,输出接近目标分辨率的颜色。NVIDIA 在 DLSS developer page 中把 Super Resolution 描述为从较低分辨率输入、运动数据和历史反馈构造高分辨率输出,并把 Ray Reconstruction 描述为用 AI 生成 ray-traced 场景中采样光线之间的高质量像素。对本章而言,这些资料支持的结论是:AI upscaling / denoising 仍然处在 frame graph 中,它需要明确的输入资源、历史版本、执行位置和同步边界。

Particle 系统能展示 simulation 与 render 的分离。compute pass 更新粒子位置、速度、生命期和排序 key;另一个 compute pass 可能执行 prefix sum、dead particle compaction 或 tile binning;graphics pass 读取 compacted list 绘制 billboard、mesh particle 或 volumetric sprite。高粒子数场景中,CPU 逐粒子更新会扩大 upload;GPU 内部更新可以减少 CPU 到 GPU 的动态传输。但透明粒子的排序、深度冲突和 overdraw 仍会把压力转移到 graphics pass。优化结论必须同时看 simulation cost、buffer bandwidth、blend cost 和视觉稳定性。

这些案例共享一个复盘表。设计混合计算 pass 时,应记录输入资源、输出资源、写入帧号、读取帧号、队列、barrier、是否允许旧数据、可选工具证据。RenderDoc、Nsight、Xcode GPU tools 或平台自带 profiler 可以分别观察 draw / dispatch 顺序、resource state、pass timing、bandwidth、queue overlap 和 idle 区间。工具只提供证据入口,最终判断仍要回到资源读写和 frame latency。

112.5 未来趋势:融合 AI 与 Compute 模式

AI 与 compute 的融合正在改变实时渲染中的 pass 组织方式。neural rendering、AI denoiser、super resolution、material generation、frame generation 和 neural compression 都把传统图形数据转换成模型输入,再把模型输出放回 frame graph。它们增加了新的资源形态:feature buffer、history feature、confidence mask、network weights、intermediate activation 和 vendor SDK 管线对象。

这类技术的工程边界依然可以沿用本章的判断顺序。先确定输入是否来自当前帧 graphics、上一帧 history、CPU asset pipeline 或常驻模型权重。再确定模型执行位置:通用 compute shader、vendor SDK、专用 tensor / matrix 单元接口,还是离线生成阶段。随后检查输出如何进入下游:直接写 swapchain 前颜色、写 lighting buffer、写 material texture,还是写下一帧 history。最后记录同步点、延迟和 fallback path。

Neural rendering 的主要挑战是输入稳定性。一个模型如果读取 depth、normal、motion vector 和 roughness,就要求这些 buffer 在坐标空间、jitter、分辨率和历史版本上保持一致。输入不稳定会表现为 ghosting、闪烁、边缘拖影、细节呼吸或亮度漂移。这里的调试方法不是先怀疑模型质量,而是先检查 frame graph 中的输入资源:motion vector 是否覆盖透明物体,reactive mask 是否标记剧烈变化区域,history reset 是否跟随 camera cut,exposure 是否在重建前后使用一致版本。

AI denoiser 与 super resolution 还会改变性能瓶颈。传统 full resolution shading 把成本放在更多像素、更多采样和更重 BRDF 上;AI 重建把一部分成本转移到低分辨率渲染、特征 buffer 生成、history 管理和重建 pass。它的收益来自减少前段 shading / ray tracing work,代价是增加后端 compute、额外纹理读写、模型执行和 SDK 集成复杂度。一个方案成立,需要最终 frame time、显存占用、画面稳定性和 fallback 行为同时过关。

Material generation 和 texture synthesis 更适合分成离线、加载时和运行时三档。离线生成适合高质量材质库和烘焙资产;加载时生成适合根据硬件 profile 选择分辨率、压缩格式或变体;运行时生成适合小范围、低延迟、可缓存的变化,例如 decal、局部污渍、程序 mask 或天气响应。把大型生成模型放入每帧实时路径,需要为权重驻留、activation memory、queue 调度和质量回退付出成本。可迁移的判断不是“AI 是否先进”,而是它是否减少了当前 frame 的主要瓶颈。

未来的混合计算管线会更像显式数据流系统。CPU 不再逐项决定每个对象是否绘制,而是维护场景数据、预算和策略;GPU 通过 compute pass 生成可见性、LOD、材质变体和重建输入;AI pass 在 frame graph 的固定位置消费特征并输出图像或材质结果。引擎的关键能力会从“写一个效果”转向“声明资源依赖、控制延迟、复盘证据、为不同硬件选择稳定路径”。

最小自检任务

题目:给定一帧渲染路径:CPU 上传相机常量;compute pass 根据 instance bounds 和 depth pyramid 生成 visible_listindirect_args;graphics pass 使用 indirect_args 绘制主场景;TAA pass 读取当前颜色、motion vector 和上一帧 history;GPU 写出一个对象数量统计,CPU 用它调整下一帧粒子预算。请判断这条路径中哪些资源需要同帧同步,哪些结果适合延迟读取,并给出排查卡顿的顺序。

答案要点

visible_listindirect_args 需要从 compute 写入过渡到 graphics 读取,属于同帧生产消费路径;同步描述应覆盖 compute shader 写访问和 indirect draw / shader 读访问。TAA 当前颜色、motion vector 与上一帧 history 需要在 frame graph 中保持版本清楚,当前帧写出的 history 应供后续帧读取。对象数量统计适合放入 readback ring buffer,让 CPU 在下一帧或更晚读取,减少提交线程等待 GPU 的概率。排查顺序应先看 CPU 是否等待 fence 或 readback,再看 GPU timeline 是否出现 graphics 等 compute 或 compute 等 graphics 的 idle,然后检查 visible_listindirect_args、history 和 readback buffer 是否有多版本设计,最后检查 barrier 范围是否过宽、是否把无关资源放进同一个全局等待点。

本章知识点总结

  • 混合计算:混合计算把 CPU、graphics、compute、copy 和 AI / reconstruction pass 放入同一帧数据流中,核心判断对象是资源生产者、消费者和同步距离。
  • 所有权链:CPU 适合提交命令和维护策略,GPU compute 适合并行派生数据,graphics pass 适合消费稳定的绘制资源。
  • 同步描述:正确同步需要说明生产者 stage、生产者 access、消费者 stage、消费者 access 和资源范围。
  • 跨队列等待:同一队列依赖多由资源 barrier 表达,跨队列交接需要 fence 或 semaphore 级时间线,CPU wait 会扩大 frame latency。
  • 传输瓶颈:upload、readback、copy 和 resource transition 要分别观察字节移动与等待关系,二者会造成不同症状。
  • 延迟读取:统计、timer、质量指标和 debug 数据适合多帧 readback,实时绘制路径应减少同帧 CPU 等待。
  • 异步条件:async compute 适合计算密度高、资源热点独立、输出允许排队的任务,带宽争用会削弱并行收益。
  • 多版本资源:ring buffer 和 history 版本能把硬等待转换成有界流水线,版本数需要在延迟、显存和调试成本之间取舍。
  • 现代案例:TAA、denoising、GPU culling、visibility buffer、upscaling 和 particle 都依赖清晰的输入、输出、history 和 barrier。
  • AI 融合:AI denoiser、super resolution 和 neural rendering 仍属于 frame graph 节点,必须检查输入稳定性、执行位置、输出路径和 fallback。
  • 排查顺序:先查 CPU wait,再查 GPU idle,再查资源版本,最后查 barrier 范围和队列交接。