这里是全站文章的归档入口,下面是按时间倒序排列的全部文章。若想按主题浏览,请从首页的专题分类进入。
《游戏引擎实现原理》4.3 GPU 内存池与预算
资源能传上去之后,一帧里反复创建销毁 buffer/texture 会把驱动拖垮、把显存切碎。本节复用卷一的大小类思路做一个 GPU 内存池,配显式预算与超限策略,统计复用率与峰值占用,并在真实 WebGPU 设备上跑三帧验证第 2/3 帧确实复用了第 1 帧创建的 buffer。
posts
这里是全站文章的归档入口,下面是按时间倒序排列的全部文章。若想按主题浏览,请从首页的专题分类进入。
资源能传上去之后,一帧里反复创建销毁 buffer/texture 会把驱动拖垮、把显存切碎。本节复用卷一的大小类思路做一个 GPU 内存池,配显式预算与超限策略,统计复用率与峰值占用,并在真实 WebGPU 设备上跑三帧验证第 2/3 帧确实复用了第 1 帧创建的 buffer。
上一节把资源绑进了管线,但资源本身还没进 GPU。本节讲 writeTexture 与 copyBufferToTexture 两条上传路径,撞上 WebGPU 最著名的 bytesPerRow 必须对齐 256 字节的坑并抓出真实报错,再用一条 textureLoad 的 box filter 通道逐级生成 mipmap 并回读断言。
着色器怎么知道去哪拿 uniform、贴图和采样器?本节讲 BindGroupLayout 与 BindGroup 如何和 WGSL 里的 @group/@binding 一一对应,先写一个从着色器注解反射出布局的纯 TS 模块,再在真实 WebGPU 上跑通「uniform + texture + sampler」三合一绑定并回读像素验证。
GPU 可能还在读帧 N 的 uniform,而 CPU 已经在写帧 N+1——直接复用同一块 buffer 会画错。本节解释为什么需要 2~3 份 uniform buffer 与 bind group,并用纯 TypeScript 实现一个环形分配的三缓冲池,在复用仍在飞的槽位时立刻报错,把竞态变成可断言的异常。
queue.submit 只是把命令缓冲排进 GPU 的队列,真正「做完了」要靠 fence 通知 CPU。本节说清 GPU 与 CPU 的同步点在哪,用纯 TypeScript 实现一个记录在飞帧的 FrameRing,并写出「每帧 await」与「流水线」两种策略的确定性时间线,实测出两者的吞吐差异。
命令编码器把一帧的 GPU 工作录成命令缓冲:beginRenderPass 开通道、setPipeline 绑管线、draw 提交绘制、end 收尾。本节先用纯 TypeScript 把这套顺序约束做成可断言的录制器状态机,再在真实 WebGPU 设备上录一条渲染通道,清屏后画三角形并把像素回读出来验证。
一条管线状态对象把顶点布局、图元拓扑、深度与混合状态、绑定布局全部钉死;着色器模块则是它的 vertex / fragment 两半。本节给出 helio 的 PSO 描述符,然后在真实 WebGPU 上真编译一份 WGSL,断言 getCompilationInfo() 零错误,再故意写错一处,验证诊断能定位到具体行列。
渲染层的一切最终都是三类 GPU 资源:缓冲、纹理、采样器。本节给出 helio 的资源描述符与使用标志(与 WebGPU 逐位一致),用纯逻辑校验行距对齐与 mip 链长度,再在真实 WebGPU 上创建一块 buffer、写入、读回,逐字节断言两条上传路径的结果一致。
从零实现渲染器的第一件事是划一条硬件抽象线。本节讲清 WebGPU、WebGL2 与 NullRHI 三个后端在特性、格式与上限上的真实差异,给出 helio 的 RHI 统一词汇(仅类型接口),并用一张能力查询表把「能不能做 X」变成一次查表,最后在 headless 浏览器里真探测一次设备的 limits。
本卷和站内 graphics 专题到底谁写什么?本节用一张逐条对照的三列表,把 graphics 的 16 篇文章各自负责的算法与原理,和本卷只做的「在 helio RHI 上实现并跑出真实产物」一一对齐,并把这张表编码成一个可运行的路由函数,让「越界」变成一条可断言的失败用例,而不是一句口号。
上一节看到 CPU 与 GPU 是两条流水线,那渲染器该怎样分层来管好它们?本节把渲染器拆成 RHI(硬件接口)、渲染器(帧组织)、场景(数据)三层,给出 helio 的目录与接口草图,并用一个可运行的依赖方向守卫,把「只能向下、不能成环」这条铁律钉成编译器与测试都能把关的断言。
渲染器的第一课不是画三角形,而是搞清楚一帧里 CPU 和 GPU 谁在等谁。本节用离散事件模型模拟 CPU 提交与 GPU 执行的流水线,算出单/双/三缓冲下的稳态帧率与延迟,再在真实 WebGPU 设备上量出 CPU 提交与 GPU 排空的耗时,用数字说明为什么「CPU 帧」与「GPU 帧」不是同一件事。
讲清原生镜像封闭世界假设下反射、资源与动态代理为何失败,逐一核实 RuntimeHints 的五个子 hint、RuntimeHintsRegistrar 与 @RegisterReflectionForBinding 的声明方式,并对照 reflect-config.json 与新版 reachability-metadata.json,附排查命令。
压测做错比不做更危险:它会给出一个被信任的错误容量结论。本节讲清压测目标、场景与数据设计、工具选型、压测机与被测机分离、预热与稳定期、指标采集口径,拆解「连接池与并发不匹配」等假瓶颈,并给出用实测 QPS 乘安全系数倒推实例数的完整方法与示例。
世界时钟把不稳定的墙钟时间折算成固定步长的逻辑时间,让「第 N 帧发生了什么」完全确定、可重放。本节从零实现一个逻辑帧时钟:固定步长与累加器、单帧步数上限、追帧 / 丢弃 / 降频三种超支策略,以及时间缩放;并用确定性测试演示「死亡螺旋」是怎么形成的、丢弃策略又是怎么把它掐断的。
时间轮用「一个数组 + 一个游标」把所有定时器塞进固定数量的槽位,把挂载从 O(log n) 压到 O(1),代价是精度退化到一格。本节从零写一个支持任意时长的哈希时间轮:槽位、cursor、级联搬迁、Tick 推进,以及为什么回调必须在锁外执行;再用确定性测试验证跨圈的定时器仍能准时触发。
心跳超时、技能冷却、Buff 到期是三类时间尺度完全不同的定时任务,却常被同一套「一个任务一个 time.Timer」的写法一锅端。本节先厘清三种尺度,再给 aegis 写两个真正省掉定时器的结构:惰性判定的冷却表与单节拍扫描的心跳看门狗,并用本机基准对比每定时器一个堆节点与共享扫描的成本。
把同一条消息发给一万个玩家,成本到底花在哪?本节给 aegis 写一个扇出器:证明「编一次发 N 次」比「编 N 次」快得多,用批量写把 K 次系统调用压成一次,并说清广播时锁该锁多细,最后给出本机实测的两组基准对比数字,解释为什么只能报相对量级。
TCP 只保证字节有序,不保证你的业务命令有序。本节给每条连接加一个 32 位单调序号,用 TCP 同款的滑窗位图做去重,再写一个乱序缓冲把先到的高序号暂存到缺口补齐,并说清顺序保证的三条边界,最后量化每帧去重的成本——本机实测约 7 纳秒、零分配。
一条帧到达服务端之后要去哪里?本节给 aegis 写一张真正的路由表:用命令号把请求、推送与通知分派到各自的 handler,定义可测试的 handler 上下文,让未知命令和 handler 报错都退化成错误通知而不是 panic,最后用一组 L1 测试真跑验证请求-响应与推送两条路径。