状态:已完成 · 目标版本:2.0.0 · 最后核验:2026-07-18
范围:源码、示例、测试、构建、类型声明、API 文档、站点、npm 包、CI 与发布流程
RHI 与 renderer 架构部分已由
RENDERING_ARCHITECTURE.md取代。旧 feature driver、旧 RHI wrapper 与同提交 A/B 基准已经删除;后续性能基线以当前共享 Renderer/RHI 实现的冻结快照为准。
本仓库已经从“JavaScript 文件改名为 TypeScript、但主体仍按旧工程运行”的过渡状态,改造成一套单一、严格、可复现的现代前端库工程。
现在的工程事实是:
src/ 与 examples/
中不再存在一方 JavaScript 实现,npm run check:modernity
会阻止旧 JavaScript 或旧构建配置重新进入受维护目录。class、显式领域类型和标准浏览器 API;旧动态类/mixin
API 与 CommonJS/UMD 输出已完全删除。ComputeShader 使用受控 Direct WGSL;storage-aware raster 使用受控 GLSL ES 3.10
readonly-std430 → Naga。不存在手写 graphics WGSL 镜像、WebGL 1、GLSL 1.00 或逐项 numeric
uniform 路径。src/render,公开入口由
Renderer/RenderTarget、SRP 与受控 compute/storage 对象组成,后端实现收口于
src/render/rhi/backends/webgl2 与 src/render/rhi/backends/webgpu;UniformBuffer 与 frame
planning 保持后端中立,WebGPU 通过同步 renderFrame()
合并资源已就绪的多 pass,并以 submission 内 UBO
revision 快照保证阴影与多相机数据不会互相覆盖。Shader variant 使用有界结构化 hash 与精确碰撞校验。npm run validate
执行完整双后端浏览器矩阵。默认 hosted
CI 把等价的 portable 门禁拆成预检、coverage、RHI、包/API/文档和四个隔离的 WebGL 2
presentation/UI/视觉分片;WebGPU native/offscreen RHI 保持独立进程,non-evidence portable
benchmark smoke 则由按性能路径、定时或手动触发的独立工作流执行。build/、已提交的旧
docs/、旧测试页、运行时 vendor 脚本和远程 CDN 依赖已经退出活跃工程。本次改造没有使用 @ts-nocheck、@ts-ignore、@ts-expect-error、显式
any、目录级 lint 逃逸、空视觉断言、覆盖率排除核心目录或捕获错误后继续成功等临时手段。
| 领域 | 改造前 | 当前实现 |
|---|---|---|
| 语言 | 大量 .ts 仅透传旧 JavaScript,主体使用 Class.create |
全部一方代码严格 TypeScript;对象模型统一为原生 class 与 ESM |
| 类型检查 | 单一配置混合浏览器、测试与 Node 环境 | base/lib/test/examples/node project references,严格规则全覆盖 |
| 静态质量 | lint 文件白名单,无统一 formatter | typed ESLint flat config 覆盖整仓,Prettier 与 EditorConfig 固化格式 |
| 库构建 | Gulp、Webpack、Babel 和历史 polyfill | Vite 8 + Rolldown,ES2022 ESM、Naga/WASM 按需分包与完整 source map |
| 类型发布 | 手工维护的 namespace/CommonJS 声明 | tsc 从源码 emit,声明 rollup 后由 Bundler/NodeNext 消费配置校验 |
| API 契约 | JSDoc 静态产物,与源码和包入口脱节 | TypeDoc 零警告文档 + API Extractor 签名基线 |
| 单元测试 | 旧断言、旧 mock、浏览器错误不一定失败 | Vitest 原生 expect/vi,Chromium Browser Mode,错误门禁与 V8 覆盖率 |
| UI 测试 | 少量代表页面 smoke test | 81 个 HTML 自动清单;78 个双后端,WebXR 单页与两个 compute 页明确单后端 |
| 视觉测试 | 截图比较为空实现 | 两个后端共用确定场景、readback 断言、截图基线与像素差异阈值 |
| 示例 | 旧全局变量、vendor 脚本、远程运行时资源 | 严格 TS 多页面应用,本地 npm 依赖与本地静态资产 |
| 渲染 ABI | WebGL 1/2 分支、GLSL 1.00 转译与逐项 uniform | portable raster GLSL→Naga→WGSL;受控 Direct WGSL compute 与 storage ABI |
| npm 包 | 仓库内入口能运行即视为通过 | publint、Are the Types Wrong、Bundler/NodeNext 与真实 ESM 运行时消费 |
| CI/发布 | 老版本 Actions、Node 与零散命令 | 固定 Node 22.22.2、npm 12、Chromium 与单一完整发布门禁 |
| 文档站点 | 跟踪旧生成物 | CI 现场生成 TypeDoc 与 Vite 示例站点并部署 Pages |
共享配置启用了 strict、noUncheckedIndexedAccess、exactOptionalPropertyTypes、
noImplicitOverride、noImplicitReturns、verbatimModuleSyntax、isolatedModules、
noFallthroughCasesInSwitch 等规则。根 tsconfig.json 只维护 project references:
tsconfig.lib.json:浏览器引擎源码与 declaration emit;tsconfig.test.json:Vitest 浏览器测试;tsconfig.examples.json:全部示例应用;tsconfig.node.json:Vite、Vitest、ESLint、Playwright 和工程脚本。生产源码不会被测试全局类型或 Node 类型污染。lint 同时使用 TypeScript 类型信息检查
src/、examples/、test/、scripts/ 与工程配置,生成物是唯一的目录级忽略对象。
引擎内部的 scene graph、renderer、geometry、material、texture、loader、animation、math、light、camera 与 helper 均已迁移到原生类。构造参数、事件、WebGL 资源、uniform、shader semantic、glTF、动画状态、纹理来源等动态结构均有明确的 interface、union 或 generic。
旧 Class.create、Class.mix 与 EventMixin
已从源码、根 barrel、类型声明和测试删除。所有可监听对象直接继承类型化的
EventDispatcher,资源生命周期统一使用 backend-neutral
releaseGPUResources();不保留 backend 名称泄漏到公共 API 的旧别名。
Stage 与 Renderer 只提供异步 create(...) 创建入口。Stage.create(...) 接受
backend: 'auto' | 'webgl2' | 'webgpu';省略字段等同 auto。auto 通过
Renderer.isBackendSupported('webgpu', options) 只执行 requestAdapter 与 adapter
policy/feature/limit 校验,兼容时正式初始化一次 WebGPU,不兼容时直接创建 WebGL
2。显式请求 WebGPU 后绝不会静默回退。ready。Naga 首次加载或 WASM 初始化失败不会污染进程级 Promise,后续新 renderer 可以重试。运行中 device
lost 会按 generation/device identity 过滤旧回调,发出
webgpuDeviceLost,释放旧 managers/context/GPU 资源,按冻结的 adapter
options 重新获取等价 adapter,复核 fallback policy、required
features/limits,再用相同的有效 device
descriptor 自动重建。恢复期间 render 安全跳帧;成功时保留公共 RenderTarget identity 并发出
webgpuDeviceRestored,失败时发出
webgpuDeviceRecoveryFailed,之后的 render 显式抛出原始恢复错误。renderer
destroy 会取消正在进行的恢复。KHR_techniques_webgl loader、类型和 SampleTechniques 资产。该历史扩展以任意 GLSL
1.00 与 classic numeric uniform 为接口,无法满足固定 UBO
ABI;仅改内置样例而继续宣称支持会使外部资产在 program link 时失败,因此不保留伪兼容入口。XRWebGLLayer,因此 webxr.html
被明确声明为 WebGL 2-only。它暂不计入 WebGPU 上线门槛,也不会先请求 WebGPU、失败后再回退 WebGL
2;其余 78 个 HTML 示例均进入双后端门禁。renderer.resourceManager.getDiagnostics(rootNode?)
返回的后端中立快照,包括已跟踪 mesh/resource、当前使用、待销毁数量和 frame 状态。会读取 WebGL 私有 cache 并产生日志副作用的
logGLResource() 已从公共入口和源码删除。cannon-es。原 Draco 示例适配器及演示资产已退出:上游只提供 UMD/CommonJS 风格的浏览器 wrapper,而可用的纯 TypeScript 候选无法通过本仓库的严格 TypeScript
6 声明检查;不通过构建期字符串改写、假声明或 skipLibCheck 伪装成现代模块。node_modules
中的 UMD/CommonJS 源码;依赖必须提供可直接消费的 ESM 与严格类型契约,否则删除该集成,等待合格实现后再按正常模块边界接入。Renderer 的实际后端仍只有 webgl2 与 webgpu;异步工厂额外接受 auto
选择策略。所有后端都通过异步工厂创建,让调用点明确表达初始化边界:
const webglRenderer = await Renderer.create({ backend: 'webgl2', domElement: canvas });
const autoRenderer = await Renderer.create({ backend: 'auto', domElement: canvas });
const webgpuRenderer = await Renderer.create({ backend: 'webgpu', domElement: canvas });
const autoStage = await Stage.create({ camera });
const webglStage = await Stage.create({ backend: 'webgl2', camera });
const webgpuStage = await Stage.create({ backend: 'webgpu', camera });
const webgpuSupported = await Renderer.isBackendSupported('webgpu');
省略 backend 等同于 auto。auto 先调用公开的
Renderer.isBackendSupported('webgpu', options):它只读取 navigator.gpu、调用
requestAdapter(),再校验 fallback-adapter policy、required feature/limit 与引擎最低 adapter
limits;不得调用
requestDevice()、canvas.getContext('webgpu'),不得初始化 Naga、pipeline、manager 或任何 GPU 资源。探测返回
false 时直接构造 WebGL 2;传入 preserveDrawingBuffer 时也直接选择 WebGL 2;请求
alpha: true, premultipliedAlpha: false 的 straight-alpha canvas 合成时同样直接选择 WebGL
2,不发起 WebGPU 探测。显式 WebGL 2 与自动选择使用同一个异步初始化边界。
探测返回 true 后只正式创建一次 WebGPU Stage。此后的 device/context 请求、shader
compiler、pipeline 或资源初始化错误都直接 reject,不得被 catch 后切换 WebGL
2。显式选择 WebGPU 会跳过 auto 探测;浏览器不支持、adapter 不存在、feature/limit 不满足、恢复失败或 shader 编译失败均为可观察错误。引擎内部禁止吞掉错误后关闭 feature 或换一条低能力渲染路径继续运行。
WebGL 2 backend 直接使用 core API 和原生 GLSL ES 3.00;项目不创建 WebGL 1 context,不保留 WebGL 1 extension adapter,也不接受 GLSL 1.00 自定义 shader。
生产渲染底层以 src/render/rhi/core
的 RHI 契约为唯一硬件边界:device 创建 buffer、texture、sampler、prepared shader、bind-group
layout、pipeline layout、bind group 与 graphics pipeline;frame-scoped command context、render
pass、queue 和 submission 表达执行、提交与完成。RHI 产品代码不引用 Mesh、Material、Stage、Light、RenderList 或 shader
variant,也不负责 GLSL→WGSL;这些引擎语义属于 shared Renderer、src/shader variant 层和
src/render/shader 编译层。具体设备只允许位于 src/render/rhi/backends/webgl2 与
src/render/rhi/backends/webgpu,RHI core 和 Render Graph 都不得反向 import
renderer 或 scene 类型。
src/render/rhi/RHIFactory.ts 是唯一硬件组合根,负责 support probe、构造 RHI
device/surface 并等待 ready;它不产生逐 command 转发对象。src/render/internal/RendererFactory.ts
无论后端都只构造 SharedRendererDriver,backend 选择仅决定其持有的 concrete RHI device。scene
traversal、resource preparation、Render
Graph、pass、present、readback 与恢复流程不再分叉到两个 feature driver。
WebGPU RHI resource/context/pass/queue 直接持有并调用对应 native object,不使用 Proxy、二次 command
list 或 GL 风格全局状态机。WebGL2 RHI 在同一可移植契约下实现 pipeline、bind group 与 render
pass,但 command context 是明确的 immediate context:GL 命令在调用时经 device-owned state-diff
cache 即时执行,endFrame() 只封闭 frame
scope 并产生 submission,不回放软件命令列表。两后端的 descriptor snapshot、resource
generation、in-flight
lifetime 与 validation 共享同一契约;VAO、framebuffer、pipeline、sampler 和 immutable layout
cache 有界且只归 device 所有。
旧 RHI wrapper 与 WebGL2/WebGPU feature driver 已删除。性能回归使用当前 RHI 的跨提交冻结快照,生产树不再为同提交 A/B 保留第二套 renderer、manager 或 native fast path。RHI 的 draw/context 稳态热段不得创建临时 descriptor、array、iterator 或 typed-array;pass setup、shader/pipeline prepare、graph compile 与 transient allocation 必须在 execute 前完成。
公开 Renderer 不携带 gl 或 gpuDevice 字段。需要 XR、验证或第三方 native 互操作时,调用方通过
renderer.getExtension('webgl2-native') 或 renderer.getExtension('webgpu-native')
显式请求能力并检查返回值;常规 scene、material、render
target 与 readback 不得依赖 extension。等待提交完成统一使用
await renderer.waitForIdle(),调用方不直接访问 native queue fence。
可移植契约不伪造完整 WebGPU。RHI 已包含 WebGPU-only compute pipeline/pass、storage
buffer/texture、buffer clear、direct/indirect dispatch 与 indirect draw,但它们在 WebGL
2 上通过缺失的 features、limit 和创建/执行时错误明确暴露,不使用 texture、transform
feedback、fragment pass 或 CPU 隐藏降级来模拟。1D texture、异步通用 buffer
mapping、base-vertex 等未公开能力同样 fail-closed。每个 texture
format 的 sampled/filterable/renderable/storage 与 sample-count 子集在 RHI 内部按 WebGPU feature
tier 或 WebGL extension 做保守 gate,不会误报硬件实际无法创建的组合。
统一 Renderer 公开 initializing/ready/recovering/failed/destroyed 恢复状态与当前
recoveryPromise。活跃 WebGPU device 丢失时,renderer 立即发出
webgpuDeviceLost,暂停 target 的 native
resource,销毁旧设备绑定的 manager/context。初始化成功时保存并冻结 adapter options、fallback adapter
policy 与实际生效的 device descriptor;恢复时不复用可能已经失效的旧 adapter,而是通过
navigator.gpu.requestAdapter()
按相同 options 重新获取等价 adapter,重新验证 fallback 策略、全部 required
feature、bind-group/UBO/texture/vertex 等引擎 limits,再按相同的有效 descriptor 请求替代 device。恢复期间的正常帧不会访问已丢失资源、创建 command
encoder 或提交 queue,而是安全返回。
成功后重建 buffer/texture/uniform/pipeline/bind-group manager 和 canvas context,并在原有
RenderTarget/attachment Texture
对象上重建 native 资源;当前选中 target、所有权与对象 identity 都不变,然后发出
webgpuDeviceRestored。adapter/device 请求或资源恢复失败会保存原始 cause、发出
webgpuDeviceRecoveryFailed 并进入 failed;后续 render 明确抛错,不吞错、不跳过功能、不切换 WebGL
2。generation 与 device
identity 保证过期 loss/recovery 回调不能覆盖新设备;显式 destroy 使恢复结果失效并确定性释放已创建资源。
isImageCanRelease 不以丢失恢复能力换取公开 CPU image 的释放。公开 image 清空前,引擎按逻辑
Texture identity 建立 backend-neutral recovery backing:TypedArray/DataView、ImageData、cube
face、显式 mipmap 与 sub-texture update 保存不可变副本,DOM/canvas/ImageBitmap/video 等 external
source 保留私有引用。WebGL 2 新 context allocation 与 WebGPU 新 device
manager 都从它建立各自的上传快照并重放同一 descriptor、base
level、mipmap 与增量更新;公开 image 始终不可访问。替换公开 source 会使旧 backing 失效;各 renderer 的 device-local 副本在资源释放时确定性清除,identity 不再可达后 engine
backing 由 WeakMap 回收。
公共 releaseGPUResources() 与完整 device teardown 分离:它释放 owned RenderTarget、device
allocation、UBO 和 pipeline/bind-group
cache,但保留可继续使用的 device/context/manager 与纹理恢复数据,并立即重建主画布 depth/MSAA
attachment,因此调用后的下一次 render 仍会创建 render pass 并提交。device
loss 的内部 suspend 路径则保留公共 target/attachment
identity 与 ownership,待新 device 激活后原位恢复。
全部可移植 raster shader 都以 src/shader/ 中的 GLSL ES
3.00 为唯一源码。两个后端复用材质、阴影与 fullscreen present shader;仅 WebGPU 需要的 mipmap utility
pass 也继续写成 GLSL,而不是另建 WGSL 特例。WebGPU 不维护第二套手写 WGSL,对每个已解析的 shader
variant 和内部 utility pass 执行同一确定流水线:
#define、嵌套条件分支与 include,得到当前材质、灯光、geometry 和 render-state 对应的有效源码;预处理表达式完整处理位运算、移位、一元
~、三元表达式、整数后缀、指数与八进制,宏参数按 GLSL
token边界展开,递归或非法表达式直接报告源位置,不使用“解析失败视为 1”的宽松行为;HILO_WEBGPU、分配稳定且紧凑的 vertex/varying/fragment location、拆分 matrix IO、把 std140
block 映射到固定 set/binding;main 改为内部函数,再生成稳定 entry wrapper;wrapper 在用户函数返回后完成 matrix
varying 写回以及 WebGL [-w, w] 到 WebGPU [0, w] 的 clip-space
depth 转换,因此用户代码中的提前 return 也不能绕过后处理;web-naga 的 WASM GLSL frontend 生成 WGSL,把公共 std140
ABI 确定性表达为 WGSL 默认 uniform 布局可接受的 @align/@size wrapper,再建立 shader
module 与 pipeline。预处理结果同时输出 vertex inputs、fragment outputs、uniform blocks 与 sampler bindings 元数据;pipeline、vertex layout 与 bind group 都消费同一份结果,不再各自用正则猜测接口。Naga 错误保留 stage 和送入 Naga 的完整 GLSL 4.50,避免只暴露下游 WGSL 错误。共享 present 与 WebGPU mipmap pass 直接消费转译元数据中的默认 sampler ABI(group 1、texture binding 1、sampler binding 2,并显式提供空 group 0),不存在硬编码 WGSL、备用 module 或失败后 fallback。内置 Basic/PBR、灯光、阴影、skinning、morph、instancing、quantized、geometry、present 和 mipmap shader corpus 都必须在单元测试中真实通过 Naga。
GLSL frontend 同时解析全局多声明、固定长度数组以及带 instance name、数组成员和 interpolation
qualifier 的 stage interface block,并把它们展平为稳定 location;UBO 的 layout(...)
qualifier 可以合法重排或包含显式 set/binding,但必须声明
std140,最终 binding 仍由固定 ABI 规范化。项目只接受 WebGL 2 对应的 GLSL ES 3.00
builtin 集:textureProj* 与 constant-offset 形式会进入 sampler-array 类型化 lowering,而
textureGather*、textureQueryLevels、textureQueryLod
等更高版本接口在两个后端之前统一失败,不能制造只在 WebGPU 可运行的方言。depth-only
pass 使用同一片元源码和 Naga 流水线,但把 color output 降为 private sink,只保留
discard、gl_FragDepth 与其他片元副作用,因此阴影不会依赖虚假的 color
attachment,也没有第二套 shadow shader。
这条路径不申请、探测或依赖可选的 uniform_buffer_standard_layout language feature。标量/vec2
数组、非方阵以及 bool/bvec
都会在 Naga 前后按同一 std140 字节契约做结构化降级与语义恢复;WebGPU 直接上传 WebGL
2 使用的同一份字节,不维护第二套压缩布局。内置 vertex/fragment corpus 除 Naga 转译外还必须通过真实
GPUShaderModule.getCompilationInfo()。
shader variant key 使用当前 geometry 产生的完整结构宏签名,包括四类 decode、color
size 与 morph 结构,而不是缓存某个 geometry 首次渲染时的粗略布尔值。公共 precision
header 按 renderer 实例参与 shader cache key,因此不同 WebGL 2/WebGPU
renderer 或运行时 precision 变化不会交叉复用错误源码。commonOptions
每次生成不可变快照并把规范化 signature 纳入 header/variant key;不存在可变的全局
commonHeader,也不要求调用方手动清 cache。
WebGPU-only compute 是独立且受控的例外:ComputeShader 接受 Direct
WGSL,不走 GLSL 转换,但必须经过 Naga WGSL
frontend/validator、selected-entry/workgroup/workgroup-storage/override metadata 检查、显式 binding
ABI 比对和真实 WebGPU pipeline validation。它只能声明 @compute,不能借此提交 graphics
WGSL。当前 Naga 路径不能可靠完整验证 f16,因此 Direct WGSL f16 在 pipeline 创建前 fail-closed。
WebGPU-only storage-aware raster 仍不接受 graphics WGSL。StorageGraphicsShader 使用受控 GLSL ES
3.10 readonly-std430 subset,经引擎 preprocessing、Vulkan GLSL
4.50 与 Naga 生成 WGSL;普通 Shader/Material 继续严格使用 GLSL ES 3.00/std140/sampler ABI。
所有内置数值 shader 数据按所有权和更新频率进入固定 std140 block。WebGL 2 使用稳定的 flat binding;WebGPU 将同一逻辑 ABI 分到四个 bind group,避免高频对象数据导致全局或材质资源重新绑定:
| Block | 数据所有者与典型内容 | 更新时机 | WebGL 2 | WebGPU |
|---|---|---|---|---|
FrameBlock |
render size、时间与帧序号 | 每个 renderer frame 一次 | 0 | 0/0 |
CameraBlock |
view/projection、逆矩阵、相机位置、裁剪平面与物理像素 viewport | 每个 camera/render pass 一次 | 1 | 0/1 |
SceneBlock |
fog 等 scene-owned 状态 | scene revision 变化时 | 2 | 0/2 |
LightBlock |
view-space 灯光、衰减、阴影 atlas 参数 | 每个 camera/render pass 一次 | 3 | 0/3 |
MaterialBlock |
Basic/PBR、IBL、UV 变换与材质标志 | 最终 std140 字段变化时 | 4 | 1/0 |
ModelBlock |
model 与 world-normal 变换 | 非实例对象 transform 变化时 | 5 | 2/0 |
GeometryBlock |
position/normal/UV decode 变换 | geometry revision 变化时 | 6 | 2/1 |
SkinningBlock |
joint palette | skeleton pose 变化时 | 7 | 2/2 |
MorphBlock |
morph weights 与 target 状态 | morph pose 变化时 | 8 | 2/3 |
InstanceBlock |
每批 model/normal matrix array | WebGPU instanced batch 变化时 | — | 2/4 |
WebGPU group 0 是 frame/pass/scene 全局域,group 1 是 material 与纹理域,group
2 是 object/geometry/pose 域,group
3 专供应用自定义 block。自定义 block 仍先通过共享 registry 注册:WebGL 2 flat binding
9 映射到 WebGPU group 3 binding 0,后续依次递增。内置名称和位置不允许按 shader variant 临时分配。
CameraBlock.u_viewport 的语义固定为当前 attachment 的物理像素
(x, y, width, height);canvas、RenderTarget、平面阴影与点光六面阴影切换 camera/pass 时都更新同一 std140 字段,不保留 classic
uniform 兼容路径。
公开 Std140Schema
是刻意固定的扁平 scalar/vector/matrix(含定长数组)模型;嵌套 struct 不在这套跨 WebGL
2/WebGPU 可移植 ABI 中,构造时会明确拒绝,不能把嵌套对象强转后按错误 offset 打包。UniformBuffer
必须由 Std140Layout 创建,不再接受无布局的裸 TypedArray。
引擎在创建 backend 时校验 uniform buffer 数量、单 block 大小、alignment、bind group 数量、纹理/vertex 资源上限和显式 required feature/limit。std140 schema 由 offset、matrix stride、array stride 与固定 ABI 测试锁定;CPU revision 与 dirty byte range 驱动上传,禁止为了“统一”而在每次 draw 重建或整块重传所有 block。
GeometryData、Geometry、Material 与 Texture 都发布单调 revision;每个 renderer/device 分别记录自己已经上传的版本,不清除会影响另一个 backend 的全局脏标记。MaterialBlock
还会把最终 std140 byte image 写入复用的候选区并直接逐字节比较,因此直接修改
Color/Matrix、纹理派生的 mip 数量或其他嵌套数值也会自动刷新;字节未变时不推进
UniformBuffer.revision,两后端都不会重复上传,也不要求应用手工设置 isDirty。WebGL
2 还为每个 VAO 单独记录 attribute/index 的 revision、shape 与 count,不能让阴影 VAO 的首次上传吞掉主渲染 VAO 的绑定更新。partial
buffer/texture/UBO 更新保留带 revision 的有界快照,上传成功后只推进当前 backend 的游标;慢消费者越过 64 条 UBO
history 后执行一次完整上传,再恢复 partial。资源以
mesh → pass owner → material/shader/instancing variant
保存;阴影、主画布和每个 RenderTarget 各自更新命中的 variant,不会用一次 pass 的快照覆盖其他 pass。材质/几何 identity 替换、target 销毁和 mesh 销毁会确定性释放对应状态,每个 mesh 的 variant 使用 32 项 LRU 上限;完整帧成功后才原子提交,异常帧只回收未提交的新资源。共享资源按 renderer 全局最终引用判断,不能被另一个 scene/pass 提前销毁。owner 释放会同时删除 CPU
UBO cache 和 native wrapper。缓存归属只保留一层:RHI device 缓存 immutable sampler、bind-group
layout、pipeline layout 与 render pipeline,Renderer 缓存 material、Mesh、shader variant、binding
set 与 upload revision。RHI 不按 descriptor 去重 buffer、texture、shader module 或 bind
group,label 不参与 device key;device/context 丢失、恢复和 destroy 会清除 device
cache。vertex/instance/index buffer variant 使用 per-owner LRU,每纹理 resolved snapshot 与每个 base
shader 的 numeric-depth 专化同样使用有界 access-order
LRU;pipeline 把不可淘汰的 in-flight 去重层与已完成的有界 LRU 分开。UBO/texture
identity 变化只失效 bind group,不重建稳定的 pipeline
layout,资源 churn 不会让历史 variant 永久常驻、重复并发编译或触发无关 pipeline 重编译。
WebGPU geometry 上传同样消费有界 dirty history:interleaved
attribute 只重打包受影响的 AoS 顶点区间,支持 matrix
column、normalized、stride/offset、多 source 与离散 range;只命中 padding 时不产生
writeBuffer。Uint8/Uint16/Uint32 index update 会按 WebGPU 的 4-byte copy
alignment 合并写入并保持 primitive-restart 规范化。历史游标落后或 shape/byteLength 变化时才执行一次完整重建,旧 native
buffer 在新资源成功后确定性销毁。
当前灯光着色使用 view-space 数据,因此 LightBlock 与 CameraBlock
同属 render-pass 频率。环境贴图、球谐与 IBL 强度允许逐材质变化,因此属于
MaterialBlock。WebGPU 阴影资源使用单一 depth-comparison atlas;atlas 尺寸、slice
rect、bias 与 light-space matrix 都进入 LightBlock,不会退回逐灯散装 uniform 或占用一组 sampler
array。WebGL 2 点光源 cube shadow 会为六个面分别显式开始 camera pass,即使复用同一个 shadow
camera 对象也必须刷新六组 CameraBlock view/view-projection bytes;不能用 camera
identity 代替 pass 边界。只有 Directional/Spot/Point light 接受
ShadowCastingLightParameters;Area/Ambient/base light 在构造或运行时设置 shadow
时就于后端中立层失败,不允许 WebGL 报错而 WebGPU 静默忽略。renderer-owned shadow camera 与 debug
helper 按 renderer/light 使用明确的 owner map;debug 关闭、shadow 置空、light
disabled、light 离开 stage、GPU resource release、device suspend/recovery 与 renderer
destroy 都会 prune 对应节点并解除 parent/listener。多个 renderer 不共享这些节点,重复恢复也不会让 light
children 增长。
WebGL 2 的 native resource 也不使用进程级单例所有权。Program、Buffer 与 VertexArrayObject 按
WebGL2RenderingContext 分区;Texture 与 UniformBuffer allocation、upload
cursor 和 cache 则由每个 WebGLState 的 backend manager 持有;不可变 WebGLSampler
variant 由有界 sampler manager 按 context 和 texture unit 管理。共享 Texture
只保存 CPU 内容、revision 与恢复 snapshot,WebGL uploader 不再位于
src/texture,公开 API 也不暴露 native texture/cache。VAO current binding、Framebuffer
reset/destroy、capability snapshot 与 extension object 同样是 context-local。
同一 shader/geometry/texture identity 可在多个 renderer 中各自建立 native
allocation,不会把 A 的 program/state/gl 返回给 B。A 的 context
loss、restore、releaseGPUResources()
或 destroy 只清理 A 的 namespace,不删除 B 的 handle、不覆盖 B 的 extension/capability 视图。Texture
content descriptor snapshot 会检测 size/format/mipmap 变化,并在 framebuffer
resize/reset 时稳定复用 native object;一个 texture attachment 不能同时归属两个 framebuffer
graph。不可取消的内部 destroy observer 会先释放所有 backend
allocation,再触发公开事件;Texture.needDestroy 也会同步失效该逻辑纹理在所有 WebGL
context 与 WebGPU device 上的旧 allocation,不能由第一个消费者吞掉全局状态。
这一所有权边界由两个真实 WebGL 2
context 的首次分配、reset/release 和继续渲染回归锁定,不使用单 context mock 代替。旧的根导出
capabilities/extensions
单例已删除,因为它们无法表示“当前”多个 context;需要 native互操作的调用方从所属 Renderer 的
getExtension('webgl2-native') 读取 context-local capability
snapshot。Program、Buffer、VAO 与 Texture native cache 都由 backend
manager 私有持有,不再从根 barrel 暴露。
固定 ABI 同时固定容量:最多 8 个 directional light、8 个 spot light、16 个 point light、8 个 area light、128 个 skin joint、8 个 morph weight 与每个 WebGPU instanced batch 128 个实例。超过容量必须分批或抛出带领域名称的错误;禁止截断数组、复用最后一项或退回 classic uniform。
GLSL opaque sampler 不能成为 uniform block 成员,因此 sampler 是 block 外唯一合法的 uniform
声明。WebGL 2 在 program link 后为 active sampler 分配整数 texture
unit;WebGPU 预处理器把同一声明降级为独立的 texture/sampler binding pair。材质纹理、环境贴图、BRDF
LUT、LTC、shadow atlas 和 post-process input 都遵守这一规则。
共享 shader/texture 契约完整支持 GLSL ES 3.00 的 sampler3D、sampler2DArray、
sampler2DArrayShadow,以及 isampler*/usampler*
在 2D、3D、cube、2D-array 上的全部组合。预处理器在保留 sampler
array 与函数参数语义的同时生成准确的 WGSL dimension、sample type 和 comparison binding;bind-group
layout 根据 float/depth/sint/uint 选择 filtering、comparison 或 non-filtering
sampler,不依赖不可用的占位资源。
managed Texture 的公共 target 契约覆盖 2D、cube、3D 与 2D-array,raw upload、显式 mip、recovery
backing、flipY 和 device/context limit 验证都保留 depth/layer 维度;signed/unsigned integer
internal format 使用严格匹配的 typed storage 与 WGSL sint/uint sample type。integer texture 必须使用
NEAREST magnification、NEAREST 或 NEAREST_MIPMAP_NEAREST
minification、anisotropic: 1;一旦使用 mipmap filter 就必须提供显式完整 chain。3D
texture 的 mip 深度逐层收缩,也必须提供显式完整 chain。WebGPU 规范没有 compressed 3D
texture,因此 WebGL 2 与 WebGPU 都在原生分配前拒绝该组合;compressed 2D-array 则保留,并在 format
feature 可用时上传完整 layer/mip 数据。
Texture.updateSubTexture() 只接受完整 descriptor,不保留旧的 (xOffset, yOffset, image)
重载。所有 target 都必须提供
mipLevel/x/y/width/height/image:2D 不接受子资源选择字段;cube 必须提供
face: 0..5;2D-array 必须提供 layer/depth;3D 必须提供
z/depth。这些字段互斥,并在后端选择前完成范围、source 尺寸和 raw 数据长度校验。非 base
level 只能更新显式 mip chain,自动生成的 mip 始终由 level 0 派生。
cube 显式 mip chain 包含 level 0,每个 level 连续提供六项,顺序固定为
+X, -X, +Y, -Y, +Z, -Z,总项数为
mipmapCount * 6;每项的 face、width 与 height 必须与所在 level 完全一致。managed raw depth 支持
DEPTH_COMPONENT16/DEPTH_COMPONENT/UNSIGNED_SHORT、
DEPTH_COMPONENT32F/DEPTH_COMPONENT/FLOAT,以及 device 提供 depth32float-stencil8 feature 时的
DEPTH32F_STENCIL8/DEPTH_STENCIL/FLOAT_32_UNSIGNED_INT_24_8_REV。DEPTH24/DEPTH24_STENCIL8 仍可作为无初始数据的 attachment
allocation,但 raw base/sub-update 因没有可移植 WebGPU 字节表示而在后端中立层拒绝;depth external
image 与 depth mipmap filter 同样拒绝。
compressed sub-update 只接受 raw
block 数据;origin 必须按 block 对齐,width/height 只有到达逻辑 mip 边缘时才允许不是 block 整数倍,payload 必须与 block 数精确相符。WebGPU 对 2×2、1×1 等 mip
tail 使用底层格式要求的物理 4×4 copy extent,因此完整 8→4→2→1 chain 可正常上传。WebGL2 的 compressed
buffer upload 只保留完整 mip slice;partial region 无法在不解析、重编码 BC/ETC2/ASTC opaque block
payload 的前提下满足 portable top-left row 契约,因此必须在 native command 前以
unsupported-feature 失败。array/cube 的单 layer/face 完整 mip
slice 仍然支持,WebGPU 的 block-aligned partial
update 契约不变。Texture 每次局部更新都会同步维护准确的完整 CPU
checkpoint,同时只保留最多 64 条增量历史。每个 WebGL context/WebGPU
device 独立确认 revision;消费者落后于 checkpoint 边界时,
TextureUpdateSnapshot.requiresFullUpload 为
true,从完整 checkpoint 重放成功后再继续增量,不清除其他 backend 的游标。
depth texture 同时支持 comparison 与 numeric 两条正式 ABI:shadow sampler 直接使用 Naga 生成的
texture_depth_*/comparison sampler;普通 sampler 在运行时解析出真实 depth resource 后,将对应 Naga
WGSL binding 专门化为 texture_depth_*,并把标量 sample/load 结果恢复成 GLSL 的 vec4 .r
语义。WebGPU numeric depth 必须是 nearest-only、anisotropic: 1 的 non-filtering
sampler,非法 filter 在创建 bind group 前失败;WebGL 2 按 Program 反射出的 shadow/ordinary
sampler 类型选择按 texture unit 绑定的不可变 WebGLSampler variant,comparison
function 来自同一 render-target 契约;WebGPU bind group 使用相同的默认 compare,因此同一个 depth
attachment 可安全用于两种读取方式。sampler 仍按完整 descriptor 缓存,并在 resolved
binding 中保存不可变快照。
纹理色彩管理也只有一条跨后端语义:DOM/external
image 一律进入浏览器标准的 sRGB-managed 上传路径;旧的 colorSpaceConversion
布尔开关已删除,因为 WebGPU 没有与 WebGL NONE
等价且同步的 copy 语义。TypedArray/DataView 是未标记的原始数值,不发生隐式色彩转换,并统一采用紧密行布局;flipY
在两个后端上传前都形成相同的确定性行顺序,非对齐 DataView 会先安全复制到其 type
声明的分量视图,绝不静默忽略或依赖驱动默认 unpack 对齐。
HTMLVideoElement 也不使用特例回退。WebGPU 通过 requestVideoFrameCallback
只观察已解码帧,将完整帧写入该 texture 私有的 staging canvas,再用 copyExternalImageToTexture
上传。queue copy 在途时不允许改写 canvas;等待 onSubmittedWorkDone()
后才接受下一帧,允许视频正常播放时丢弃中间帧而不阻塞 decoder。首帧未到达时保持新建 GPU
texture 的合法零初始内容,不推进 uploaded revision、不生成 mipmap、不释放 source。视频 source
identity 变更、texture destroy、manager suspend/device
recovery 都会取消旧 callback 并重建观察。捕获、copy 或 queue 失败会保留 cause 并在下次资源同步时显式抛出;不直传缺少 decoder
backing 的 video、不停止播放伪造静态纹理、不回退 WebGL。
任何 block 外的 float、vector、matrix、integer、boolean 或其数组都会被拒绝,不能借
ShaderMaterial、onBeforeCompile、示例代码或 backend 分支绕过。纹理 UV
channel、强度、尺寸、阈值和 kernel 等数值元数据必须进入 Scene、Light、Material 或注册过的自定义 block;opaque
resource 例外不是保留通用逐项 uniform 系统的理由。
公共离屏契约只有后端中立的 Renderer 与 RenderTarget:同一个 Renderer 在两个后端都提供
createRenderTarget()、setRenderTarget() 和 renderToTarget(),调用方不接触原生
WebGLFramebuffer、GPUTexture 或 backend-specific handle。统一 descriptor 支持多 color
attachment、1×/4× MSAA resolve、可选 depth/stencil、合法情况下可采样的 color/depth
texture、resize、所有权、显式 present 选择与异步区域 readback;跨 backend
target、非法 attachment、MSAA depth sampling、无附件 target、销毁后复用和 depth-only
present 均直接报错。
WebGPU MRT pipeline target 与 attachment location 一一对应,支持中间为 null 的 sparse color
slot;depth-only target 不声明颜色输出。resize 在新 color/depth/MSAA
allocation 全部成功后才提交,失败会销毁新资源并保留旧 target
identity、尺寸和可用 allocation,不存在部分替换。
shared RenderTarget cache 明确区分 persistent resolve/readable 纹理与 graph-transient MSAA render source。仅当 4× MSAA 的全部相关 color/depth/stencil attachment 都是首 pass clear、末 pass discard 时,cache 才不创建持久 multisample source/depth handle,由 Render Graph 在 execution 前分配并在 submission 完成后回收;任一 load/store 需要跨 graph 保留内容就原子切回 persistent policy。opaque 与 transparent pass 在同一 graph 中共用同一个 source,resize、recovery 与 partial allocation failure 保持该 lifetime policy 的事务语义,不允许同时保留 persistent 和 transient 两份 MSAA backing。
材质采样公共 RenderTarget texture 时必须声明显式 graph read。若本 graph 没有 writer,它读取跨帧保留的 persistent 内容;若存在 opaque/transparent 等一个或多个 writer,compiler 保留完整 writer 链,并让 pure consumer 依赖最后 writer,即使 consumer 先被插入也一样。同 pass feedback 继续作为 duplicate access 拒绝,普通 imported texture 不改变原有插入顺序语义。
两个实现返回相同的紧凑 top-to-bottom texel 结果。WebGPU 内部遵守每行 256-byte copy
alignment 后再压紧,WebGL 2 内部处理 framebuffer
resolve 和坐标方向;这些 native 差异不会泄漏到调用方。target-owned texture 继续使用引擎 Texture
identity,因此材质 sampler、bind-group/cache 与资源释放共享同一生命周期,不复制旁路纹理系统。attachment
identity 在 resize、WebGL context restore 与 WebGPU device
recovery 前后保持不变。两个 backend 的 target owner 都跟踪 attachment allocation generation:公开
Texture.destroy()、target 变化或上传失败会先清除旧 native
handle;WebGL 重新挂接 manager 的当前 allocation,WebGPU 则在同一逻辑 texture
identity 下原子重建,设备 suspend 期间禁止在失效 device 上分配。若这类失效发生在 WebGPU command
recording 期间,整帧会被 poison 且不提交,下一帧再恢复,旧 observer 或 material
binding 不能创建脱离 target 的旁路纹理。WebGL resize 会事务性更新全部 draw/resolve/depth
attachment,任一 allocation 失败就完整回滚,回滚本身失败则销毁 target 并抛出聚合错误,绝不留下半提交状态。显式 present 在两个 backend 都使用 fullscreen
texture-load pipeline;WebGL
2 不会把单采样 FBO 非法 blit 到浏览器可能启用 MSAA 的默认 framebuffer。WebGL
pass 的 clear 写掩码、begin/resolve/invalidate 异常恢复和嵌套 framebuffer 保存状态都有硬门禁,任何失败都在抛错前恢复 canvas
binding,下一帧会完整重绑 program、材质状态与 sampler。
示例 PostProcess 只使用公共 RenderTarget、UBO 和 fullscreen draw 组成 ping-pong
graph;MeshPicker 同样通过公共 target 执行 object-ID pass,再异步 readback。两者都在 WebGL
2 与 WebGPU 上运行,拾取不存在 CPU ray-cast 回退,后处理也不存在 WebGL-only
framebuffer 分支。旧的公共 Framebuffer、LightShadow、CubeLightShadow、灯光上的 lightShadow
字段、renderer-owned useFramebuffer/framebufferOption、隐式 present 以及 WebGPU 的无效 clear*
兼容桩已删除;renderTarget.html 只展示显式创建、resize 和 scoped render。shadow
allocation 通过内部 registry 持有;WebGL 原生 framebuffer
wrapper 仅是 RenderTarget/阴影的内部实现,不进入根出口或 API report。
WebGPU 不支持的 LINE_LOOP 与 TRIANGLE_FAN 不通过 backend 分支跳过:Geometry
在上传前把它们规范化为显式 LINES/TRIANGLES indices,glTF
sync/async/extension 解析路径也在暴露 geometry 前执行同一转换;索引类型、非索引 primitive、短输入和重复调用均有测试锁定。
Renderer.supportsTextureCompression() 是两个后端共享的显式能力入口。WebGL
2 根据对应扩展报告 BC、ETC1、ETC2、ASTC 与 PVRTC;WebGPU 在创建设备时启用 adapter 实际暴露的
texture-compression-bc、 texture-compression-etc2 和 texture-compression-astc,ETC1
source 复用 ETC2 native feature;ETC2/EAC 映射覆盖 WebGL 2 定义的全部 10 个 core
format。WebGPU 对 PVRTC 明确返回不支持,loader/示例据此跳过该 source,不做 CPU 解码、格式替换或空纹理回退。缺少 required
feature 的压缩格式在 GPU 分配前报错。只要选择 mipmap
filter,两个后端都要求层数恰好覆盖到 1×1,并校验每层 width/height;base-only 或 partial
KTX 可以配合非 mipmap filter 使用,超过纹理理论最大层数的容器则在 loader 阶段失败。KTX1
header 与每级 imageSize
都服从容器端序标志;无效标志、运行时生成 mip、截断 payload/padding 立即失败。格式、尺寸、压缩状态、image 与 mipmaps 只由容器决定,外部请求只能提供明确白名单中的 sampler、名称、UV 和生命周期选项,不能覆盖 texel
layout。
InstanceBlock,由 gl_InstanceIndex
读取固定数组,并在超过 128 个实例时拆成多个 draw。两个路径都由 HILO_WEBGPU 的编译期 shader
variant 明确表达,不运行时改写 uniform 声明。ModelBlock 只服务普通 draw 或整个 batch 的公共数据;model-view 与 MVP 在 shader 中由
CameraBlock × ModelBlock 推导,不重复上传 camera-dependent 派生矩阵。maxVertexAttributes 与 maxVertexBuffers;不能假设开发机暴露超过 WebGPU 保证下限的 slot。ivec*/uvec* 只使用
vertexAttribIPointer 并校验 signed/unsigned storage,float/vector/matrix 才使用
vertexAttribPointer;不允许 normalized 或符号不匹配的整数旁路。GeometryBlock,绝不能因为 vertex shader 使用它就退化为每 model
draw 重写。SkinningBlock,morph weight 进入
MorphBlock。骨骼、形变与实例化组合必须使用各自 revision,不能共享错误 pose。旧的数值 semantic/uniform 写法:
new ShaderMaterial({
uniforms: {
u_color: {
get: () => [1, 0, 0, 1]
}
}
});
必须迁为注册过的 std140 block:
import {
ShaderMaterial,
UniformBuffer,
createStd140Layout,
registerUniformBlockBinding
} from 'hilo3d';
registerUniformBlockBinding('EffectBlock');
const effectLayout = createStd140Layout({
color: 'vec4',
strength: 'float'
});
const effectBlock = UniformBuffer.fromSchema(effectLayout, {
color: [1, 0, 0, 1],
strength: 0.75
});
const material = new ShaderMaterial({
attributes: { a_position: 'POSITION' },
uniformBlocks: { EffectBlock: effectBlock },
vs: `#version 300 es
layout(std140) uniform EffectBlock {
vec4 color;
float strength;
};
in vec3 a_position;
out vec4 v_color;
void main() {
v_color = vec4(color.rgb * strength, color.a);
gl_Position = vec4(a_position, 1.0);
}`,
fs: `#version 300 es
precision highp float;
in vec4 v_color;
layout(location = 0) out vec4 outColor;
void main() {
outColor = v_color;
}`
});
effectBlock.set('strength', 1);
如果自定义 shader 使用相机、场景或模型数据,应声明对应的 canonical block,而不是复制一套同义散装 uniform。如果使用纹理,sampler 可以保留在 block 外;与纹理相关的其他数值仍放在 block 内。
hilo3d:唯一的 ES2022 ESM 入口;gl-matrix 保持 external,便于依赖去重。web-naga 通过动态 import 进入发布模块图;主引擎 chunk 约 1.06 MB,约 2.05 MB 的 Naga
JavaScript/WASM 作为独立 chunk,只在 WebGPU 初始化时请求,不增加纯 WebGL 2 应用的初始下载。require condition、CommonJS/UMD 文件、浏览器全局对象或 namespace 声明镜像。.d.ts 与 .d.ts.map 由 src/Hilo3d.ts 的真实依赖图生成,不存在手写镜像声明。package.json 用 exports 明确入口,files 只允许发布
dist/、README、CHANGELOG 与许可证。Node 最低版本、npm 版本和 ESM 包语义都有机器可读约束。
发布校验覆盖:
moduleResolution: "Bundler" 的 ESM 类型消费;moduleResolution: "NodeNext" 的 ESM 类型消费;npm pack --dry-run 文件白名单检查。校验对象是构建后的真实包内容,因而能够发现错误 export condition、缺失声明、错误扩展名、未发布依赖或多打包文件。
API 工程有两个互补产物:
npm run docs:build 使用 TypeDoc 从 src/Hilo3d.ts
生成可浏览的 API 站点,并把本文件、中文 README、CHANGELOG 与贡献规则作为 project
documents 纳入同一个站点;无效链接、未导出类型和文档警告都会失败。npm run api:check 使用 API Extractor 将当前声明与 etc/hilo3d.api.md
比较;公共签名意外改变会失败,计划内变更必须运行 npm run api:update 并审查 diff。API Extractor 通过 --typescript-compiler-folder node_modules/typescript 使用项目锁定的 TypeScript
system declarations。这样 WebGPU DOM 类型、声明 emit 与 API 分析使用同一版本,不会让工具内置的旧
lib.dom.d.ts 猜测或漏掉 GPU* 公共类型;这保留全部 API 检查,只消除编译器版本漂移。
API Extractor 的 release-tag 提示按项目级固定政策关闭:Hilo3d 2.x 的根 barrel 导出面全部视为 public,不设置 alpha/beta 分层。setter 文档提示也按固定政策关闭:访问器说明由 getter/TypeDoc 作为唯一正文来源。两项都不关闭 TypeScript 诊断、forgotten export、API 差异或 TypeDoc 验证,也不是待删除的迁移豁免。
唯一的 Renderer、backend-neutral RenderTarget、Stage<'webgpu'>、异步
MeshPicker、ShadowCastingLightParameters、KTXTextureOptions、资源诊断与压缩纹理 capability 从根 barrel 导出,因而同时进入
.d.ts、TypeDoc 与 API report。Concrete backend driver、target、Naga translator、native
manager 和 binding API 保持内部;不存在文档手写声称为公共接口的影子 API。包消费类型测试直接通过
Renderer/RenderTarget 使用 MRT、MSAA、readback、压缩纹理查询和资源诊断,不把 backend-specific
target 当成跨后端主契约。
Renderer 在首次 WebGPU device 创建和 device recovery 前复用已经完成 await initialize() 的
ShaderArtifactCompiler,把 mipmap GLSL utility 准备为 vertex/fragment Shader
Artifact 后显式注入具体设备。内部 RHIFactory 把 mipmap Artifact 定义为 WebGPU device create
options 的必填依赖,并在运行时再次校验;它和 WebGPU backend 都不反向 import
renderer/GLSL、不隐式启动 Naga,也没有 WGSL fallback。shader
module/layout/sampler 在 device 创建时建立,按 format 复用的 pipeline 与逐 mip/layer view、bind
group 在 texture allocation 时准备,因此 command execute 只编码 mipmap render pass。
npm run site:build 将维护的静态首页放在站点根路径,将 TypeDoc 输出放入
/docs/,将完整 Vite 示例构建放入 /examples/,并复制
CNAME。首页、文档和案例页互相提供导航入口。生成目录不提交到主工作树,由 Pages 工作流在 dev
分支更新后重新构建并通过 GitHub
Actions 部署。站点构建完成后还会扫描生成的 HTML/CSS,拒绝缺失的内部资源和依赖站点根路径的内部链接,确保自定义域名与 GitHub
Pages 子路径部署使用同一份产物。
单元测试运行于 Vitest Browser Mode + Playwright
Chromium,因此 DOM、Canvas 与 WebGL 行为来自真实浏览器上下文。WebGPU 资源管理器、std140/WGSL
layout、bind group、pipeline state、buffer packing 与 texture lifecycle 使用确定的 typed device
doubles 做边界测试;shader corpus 则加载真实 Naga WASM 完成 GLSL→WGSL 编译。测试初始化会把未预期的
console.error、shader compile/link error 与未捕获异常升级为失败。
纹理单元测试覆盖 2D/cube/3D/2D-array 的 descriptor 校验与 raw/external/compressed 上传分派、cube 显式 mip 顺序、managed
raw depth aspect、压缩边缘与 2×2/1×1 tail
extent,以及 200 次局部更新后快慢 backend 的 checkpoint 恢复;构造测试还锁定 CubeTexture/DataTexture/LazyTexture 均经过基类校验,并保留 LazyTexture 加载后的
depth 与 wrapR。
覆盖率统计显式包含
src/**/*.ts,仅排除声明文件;没有为了达到数字排除 loader、renderer、animation 等低覆盖模块。阈值是完整源码的无回退基线:60/40/58/62
分别约束statements/branches/functions/lines。后续改动不得降低基线;新增功能应同时提高相关模块覆盖率。
WebGPU device recovery 还有独立状态机测试,覆盖 target suspend/restore、同一 target
identity 与所有权恢复、重新获取 adapter、完整 capability 复核、released texture
backing 重放、恢复失败、过期 generation/device loss 回调、destroy 取消、releaseGPUResources()
后重新渲染,以及 shadow owner 在 debug/shadow/enabled/stage
membership 和 recovery 边界上的精确 prune,不以单纯修改状态字段冒充 GPU 资源重建。独立 Playwright
fixture 还在真实 Chromium/Dawn 设备上主动调用 GPUDevice.destroy(),要求 Lost → 新 adapter/device →
Restored 事件顺序正确、选中的 RenderTarget
identity 不变、已释放 texture 能重新上传,恢复后实际 draw/queue/readback 成功,且恢复前后 scene
pixel 逐字节完全相等并区别于 clear color。
Playwright 递归扫描 examples/
自动生成页面清单,不维护容易漏项的手工白名单。当前清单固定为 81 个 HTML:其中 78 个页面分别以
?backend=webgl2 和 ?backend=webgpu 运行;webxr.html 因浏览器 WebXR 当前使用 XRWebGLLayer
而只运行 WebGL 2;compute_gpu_driven.html 与 compute_particles.html
因公开能力明确 WebGPU-only 而只运行 WebGPU,共形成 159 个 page/backend 组合。三个单后端页面都是创建前的显式产品边界,不是初始化失败后的 runtime
fallback。
每个组合都必须通过以下检查:
data-hilo3d-backend 与请求后端一致;pageerror、非预期 console.error、失败 request 或 HTTP 4xx/5xx response;GPUQueue
执行 onSubmittedWorkDone(),成功后才允许终态采样;queue fence reject、uncaptured
error 与非主动销毁的 device loss 都是硬失败,不允许延迟 validation event 越过门禁;额外交互门禁在两个后端分别点击 life-game canvas、拖动 ShaderToy、切换运行中 post-process
kernel、验证 GPU MeshPicker 返回同一对象,并让 glTF
Viewer 完成初次加载、GLB 替换和页面释放。这些用例不以 DOM
value 或“没有抛错”代替行为断言:操作前后必须出现新的 native draw,WebGPU 还必须出现新的 queue
submission;life-game 验证 render-target
attachment 的局部写入像素,ShaderToy 在冻结时间后比较 pointer 前后的离屏 readback,post-process 对同一 scene
texture 比较两套 kernel 输出,glTF
Viewer 则比较替换前后实际模型渲染的 readback。页面、request/response、console、DevTools
graphics 与 uncaptured GPU
error 门禁一直保持到交互和 pagehide 清理完成,最后再等待稳定帧做终态采样。video 示例在 WebGL
2/WebGPU 上都必须从本地媒体资源产生真实 draw,且无 GPU
validation 或 decoder-backing 上传错误。renderTarget、bloom、SSAO、life-game 还在 DPR
1.25/1.5 下验证取整后的 backing size、真实 draw 和相同终态错误门禁,避免非整数尺寸进入 GPU
descriptor。CI 使用单 worker控制 SwiftShader GPU/内存峰值;没有重试或吞错来掩盖失败。
ShaderToy 的专用交互门禁会暂停 ticker,通过 320×180 offscreen
target 做两次显式 readback,断言彩色像素、pointer 坐标、输出 hash 变化和新的 native
draw/submit;普通示例门禁因此不再重复加载同一重型 ray-march 页面。这个唯一例外仍在两个后端保留 GPU
health、页面、网络、console、DevTools graphics 与终态稳定帧门禁,并由
DEDICATED_RELEASE_TEST_EXAMPLE_PATHS
契约锁定;通用门禁与专用门禁合计仍覆盖完整的 159 个 page/backend 组合。由于该 ray-march 交互在 GitHub
hosted runner 上成本过高,GitHub
Actions 明确跳过这一个专用用例;本地与发布前完整 Playwright 门禁仍会执行它。
npm run test:webgpu 在 Chromium 中以最小 ANGLE/WebGPU 参数启用 SwiftShader
adapter。它属于本地/发布完整浏览器门禁;GitHub hosted Linux 的 SwiftShader 无法稳定完成 canvas
presentation,因此 hosted CI 不伪装执行这条通道。测试创建真实 GPUAdapter、GPUDevice、canvas
context、Naga shader module、bind group、render pipeline 与 command
encoder,在同一帧覆盖 Basic/PBR、InstanceBlock 批处理、带 primitive restart 和局部更新的 Uint8
indexed strip、mipmap 2D texture 替换、directional/spot/point 三类阴影、4× MSAA/stencil offscreen
target、双 attachment MRT、fullscreen present 与对齐 readback。测试同时断言 backend、draw
count、face count、attachment/sample count、texture revision、非零像素、GPU validation
error、页面异常和控制台错误;它不是只检查 navigator.gpu、只 mock device 或只测试 WGSL 字符串。
同一真实浏览器闭环还通过 managed Texture 创建 3D、2D-array、unsigned-integer
array、depth-array、numeric depth 与动态 sampler-array 资源,让 Naga 转译包含 sampler3D、
sampler2DArray、sampler2DArrayShadow、usampler2DArray、普通 depth sampler2D
和 UBO 动态索引的 GLSL ES 3.00,随后建立真实 bind-group layout、bind group 与 render
pipeline,执行 draw、queue submit 和 buffer map。门禁要求 shader compilation message 与 GPU
validation error 均为空,全部纹理解析出的 dimension 正确,并且 1×1 输出像素精确为
[64, 128, 200, 255];这证明能力不是只在 translator 或 fake-device 单测中存在。
这项专用 fixture 是普通双后端示例之外的深度能力测试,不是唯一的 WebGPU UI 覆盖。同一套 test:webgpu
还让压缩纹理示例分别通过两个后端渲染各自声明支持的 source,确认 WebGPU 原生 BC/ETC2/ASTC 路径且明确跳过 PVRTC,并让异步 GPU
MeshPicker 在两个后端选择同一 mesh。
Compute/storage 另有 WebGPU-only showcase/acceptance fixture:它通过公共 SRP 记录 depth prepass、sampled-depth tile culling、Scene group-3 readonly storage、Gaussian reorder 与 1024 粒子 Hilo3D wordmark 的 fractal value/curl noise、呼吸、涡旋、回归和 compaction,再用 GPU-generated indirect draw 产生 Gaussian 与 additive particle glow。门禁检查真实 compute pass/dispatch/indirect draw、最终 readback 和 reload determinism;只允许最后一次颜色验收 readback,不把 visible count、排序或 indirect arguments 映射到 CPU。Forward+/Gaussian 算法仍是 acceptance-scale,页面也不是性能 baseline。
独立的 compute_particles.html 继续复用同一公共路径,把 65,536 个持久 GPU body 分成 4096 个 Hilo3D
word-lattice 粒子和 61,440 个分层星空、极光/星云与 cyber-dune deep-field body;三 octave value/curl
noise、回归力、轨道力场、低频流星头部碰撞和尾迹力场、边界反弹与鼠标磁吸/shockwave/vortex 都在一次 Direct
WGSL compute 中更新,再由三次 indirect draw 分别绘制 deep
field、additive 速度 halo 与 alpha-blended 发光 core。专用 Playwright 门禁发送真实 pointer 事件,检查上下坐标映射、字形采样覆盖和边缘背景亮度,冻结步进后比较确定性 readback
hash,并检查 compute dispatch、indirect draw 和终态 GPU validation;页面不把粒子状态映射回 CPU。
WebGPU shader corpus(包括 present/mipmap 内部 pass)与真实运行时测试是互补门禁:前者扩大 feature/variant 覆盖,后者证明浏览器端 Naga WASM 加载、pipeline creation、draw 与 queue submission 端到端可用。
npm run test:webgpu:native 使用独立的 chromium-native-webgpu Playwright
project:它不带任何 SwiftShader adapter/ANGLE 参数,禁用软件光栅器,给每次 requestAdapter 强制传入
forceFallbackAdapter: false,并拒绝 adapter.info.isFallbackAdapter 不是 false
或指纹明确属于 SwiftShader、llvmpipe、lavapipe 等软件实现的 adapter。通过 adapter 校验后,它复用正式 WebGPU 深度 fixture,要求 draw、submit、所有 queue 完成、device
recovery、纹理重放与 readback 全部成功。
物理 GPU 是否暴露给 Chromium 取决于 runner 驱动和宿主环境,因此这条命令不属于可移植的
npm run validate,也不会伪装成普通 GitHub-hosted CI 的必跑门禁。.github/workflows/native_gpu.yml
只支持手动触发,并要求带 self-hosted、linux、gpu 标签的 runner;默认 hosted
CI 通过 SwiftShader native/offscreen RHI 测试覆盖 WebGPU 核心,不声称具有 WebGPU
presentation 能力。WebXR 明确不属于该 WebGPU 通道。
视觉套件使用固定 viewport、UTC、英文 locale、固定 device
scale、禁用动画的 Chromium 和 SwiftShader。同一个确定性灯光 PBR 场景分别通过 WebGL
2 与 WebGPU 渲染;除截图外还断言 readback 背景像素、变换后像素数、方向光覆盖和前景颜色数量,并在运行时要求两个后端的首帧截图逐字节相同。两个后端仍各自保存
test/ui/__screenshots__/
基线以定位单后端回归;失败时保留 screenshot 与 trace,本地运行还会保留 video。GitHub hosted
CI 只运行稳定的 WebGL 2 visual baseline,完整双后端视觉与像素 parity 属于本地/发布或物理 GPU 验证。
CI 只保留项目最低版本 Node 22.22.2 这一个测试档位,使用当前维护的 GitHub
Actions、锁文件安装、固定 npm 12.0.1 和显式 Chromium 系统依赖,避免在 Node
22/24 上重复运行同一套高成本 GPU 矩阵。PR、dev、master
与版本 tag 先执行 modernity、格式、声明、lint、TypeScript project
references 和示例目录合同预检;预检成功后并行执行两个 Vitest
coverage 分片、RHI/架构、包/API/文档,以及四个 Playwright WebGL 2 页面/交互/视觉分片。每个 GPU
job 内仍只使用一个 worker,不在同一 SwiftShader 进程并发争用设备;coverage 模式因此关闭 Vitest 文件并行,同时保留跨 runner 的两个分片。coverage
artifact 保留上传时的仓库相对目录,汇总 job 从其嵌套 reports/vitest 目录只读取 blob
report;跨 runner 分片完成后分别合并 coverage 和 Playwright 报告,并由稳定的 Required CI
聚合门禁统一给 branch protection 使用。Linux hosted runner 的 SwiftShader 会在 coverage
instrumentation 下销毁 storage-aware raster 的真实设备,因此只有对应的一项真实设备集成测试在 GitHub
Actions coverage 中跳过;本地 coverage 仍执行该测试,portable storage/RHI 合同继续由独立 RHI
job 验证。
WebGPU 由独立 RHI job 中的 native/offscreen SwiftShader
lane 验证 adapter、device、pipeline、draw、submit、readback 与 backend contract。GitHub hosted
Linux 不稳定的 WebGPU canvas presentation 不作为虚假的合并门禁;完整双后端页面/视觉矩阵仍由本地
npm run validate 与手动 physical-GPU workflow 负责。串行的 npm run validate:ci
保留为 hosted 门禁的本地复现入口,过期任务由 concurrency 自动取消。
Portable RHI benchmark smoke 只以单 draw 生产场景验证 fixture、allocation
profiler、readback 和当前 RHI 路径。它不属于普通合并门禁;独立 workflow 仅在 benchmark/performance 路径变化、每日定时或手动触发时运行 WebGL
2 production smoke。GitHub hosted Linux 的 SwiftShader WebGPU production
fixture 会在首帧前丢失 fallback device,因此 WebGPU 仍由独立 test:rhi native
lane、本地完整 UI/视觉矩阵和 enrolled physical-GPU
benchmark 覆盖。脚本直接运行时默认按 WebGPU、WebGL
2 顺序为每个 scenario/backend 启动独立 Chromium,且非证据 SwiftShader 用例不申请正式 rig 才需要的 WebGPU
timestamp-query。smoke 始终标记为 non-evidence,不替代物理 GPU 多场景冻结基线;需要扩展诊断时显式使用
--all 入口。
站点发布工作流同样从干净 checkout 执行 npm ci,只构建一次声明,再执行 npm run api:check:built 与
npm run site:build:built
后部署生成 artifact。仓库不再跟踪旧 API 生成物,也不会从开发者本机的残留目录发布。
npm ci
npx playwright install chromium
npm run validate
validate 按顺序执行:清理生成物、旧 JavaScript/旧工具配置门禁、格式检查、typed
lint、全部 TypeScript project
references、浏览器单测与覆盖率、库构建、两类 ESM 类型消费、81 个 HTML 后端适用矩阵(78 个双后端、WebXR 显式 WebGL
2-only、两个 compute/GPU-driven 页面显式 WebGPU-only)、双后端交互、WebGPU 深度运行时、双后端视觉回归、全部示例构建、TypeDoc 验证、API 签名比较、npm 包契约验证和 pack 文件检查。任一步失败都会阻止 CI 与发布。
其中 shader 静态门禁会扫描 src/shader/ 和示例中的 shader 源码:禁止 GLSL 1.00
attribute/varying、texture2D/textureCube、gl_FragColor/gl_FragData、WebGL 1 shader
extensions,以及 block 外的 non-sampler uniform。现代性门禁只允许与 new ComputeShader(...)
结构化关联并由 compiler/Naga 验证的 Direct WGSL @compute;@vertex/@fragment graphics
WGSL、普通 Shader 中的 GLSL ES 3.10 和未受控 raw source 继续禁止。StorageGraphicsShader
是唯一 GLSL ES 3.10 readonly-storage 入口。扫描范围同时覆盖生产 .vert/.frag/.glsl/.wgsl
源文件,不允许通过非 TypeScript 扩展绕过。运行时门禁会再次在 program link、Naga
translation 或 WebGPU pipeline creation 时拒绝漏网接口,静态规则、WebGL 2 link、Naga
corpus 与真实 WebGPU pipeline 互为补充。
any。check:modernity 阻止 .js/.jsx/.mjs/.cjs 实现、旧工具配置、CommonJS
require/source 改写、WebGL 1 core-extension wrapper、类型/lint/coverage suppression 与显式
any 回流。EventDispatcher;动态类/mixin API 已删除。src/**/*.ts,不排除 renderer 或 WebGPU 核心目录。Stage.create() 默认用无 device/resource 分配的 adapter probe 实现 WebGPU-first auto;显式
webgl2/webgpu 不切换,auto 选中 WebGPU 后的真实初始化错误也不会触发回退。uniform_buffer_standard_layout。InstanceBlock 分批,阴影使用 comparison depth atlas 与 LightBlock
元数据。Texture 在双后端统一支持 2D/cube/3D/2D-array 与 integer format;integer/3D mip
chain、nearest/anisotropy 约束、compressed 3D 拒绝和 compressed 2D-array 路径都有自动测试。updateSubTexture() 只有 descriptor API,四类 target 的 raw/external/compressed update、cube
mip 顺序、depth 原始上传、压缩尾级 block extent、有界历史与慢 backend
checkpoint 恢复都有自动测试。Renderer/RenderTarget 为双后端统一实现,覆盖 MRT、1×/4× MSAA、sampleable
attachment、resize、render-to-target 与异步 readback;PostProcess 和 MeshPicker 不含 backend
fallback。Framebuffer/LightShadow/CubeLightShadow、灯光 lightShadow 与 renderer-owned
framebuffer 配置已删除,没有临时兼容入口。getDiagnostics() 替代并删除会探查 WebGL cache 的 logGLResource()。dist/、dist-examples/、docs/、site/、coverage 或浏览器报告;发布只接受 CI 现场生成物。ComputeShader,storage-aware raster 只能通过 StorageGraphicsShader 的受控 GLSL ES 3.10
contract。Renderer/RenderTarget
上;不得新增只服务单后端的业务层 framebuffer 包装、CPU fallback 或静默能力替换。资源工具必须消费
getDiagnostics(),不得恢复 backend cache 日志探针。