@kurot/core 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,373 @@
1
+ # @kurot/core 架构文档
2
+
3
+ > 当前版本:2.0.1。逐条变更记录见 [CHANGELOG.md](../CHANGELOG.md)。
4
+ > 面向 AI 智能体的速查文档见 [ai-context.md](./ai-context.md)(目录地图、反直觉行为清单、术语表、任务→文件速查表)。本文档面向人类读者,讲设计动机与内部机制,两份文档不重复内容,互相引用。
5
+
6
+ ---
7
+
8
+ ## 一、项目概述
9
+
10
+ `@kurot/core` 是 Kurot 引擎的核心运行时,提供显示对象、渲染、事件、几何、文本、资源、网络与媒体能力。对外 API 沿用 Egret 的 `DisplayObject`/事件模型,渲染内核则重写为 Pixi.js 8 风格的扁平 "InstructionSet + RenderPipe" 管线。
11
+
12
+ | 维度 | 说明 |
13
+ | -------- | ----------------------------------------------- |
14
+ | 模块系统 | ES Module(`@kurot/core` npm 包) |
15
+ | 类型系统 | `strict: true`,全量类型安全,零 `any` |
16
+ | 编译目标 | ES2022,仅支持现代浏览器 |
17
+ | 渲染架构 | InstructionSet 指令驱动(借鉴 Pixi.js 8) |
18
+ | 批处理 | 多纹理批处理(8 张/批) |
19
+ | 包管理 | `@kurot/core` workspace 包,pnpm |
20
+ | 资源管理 | Resource 完整资源生命周期 |
21
+ | WebGL | WebGL 1 + WebGL 2 双后端,运行时自动选择 |
22
+
23
+ 与 Egret 的差异、Breaking Changes 及迁移方式见根目录 [egret-migration.md](../../../docs/egret-migration.md)。
24
+
25
+ ---
26
+
27
+ ## 二、模块结构
28
+
29
+ ```
30
+ packages/core/src/kurot/
31
+ ├── display/ # 显示对象层(场景图)
32
+ │ ├── DisplayObject.ts # 基类,含 renderDirty/cacheDirty/renderMode/RenderObjectType/bounds 缓存
33
+ │ ├── DisplayObjectContainer.ts # 容器,含 isRenderGroup/sortableChildren/zIndex 排序
34
+ │ ├── Bitmap.ts # 位图显示(含 scale9Grid 九宫格)
35
+ │ ├── Sprite.ts # 精灵(容器 + Graphics)
36
+ │ ├── Shape.ts # 矢量图形
37
+ │ ├── Mesh.ts # 网格(vertices/indices/uvs)
38
+ │ ├── Stage.ts # 舞台根节点
39
+ │ ├── Graphics.ts # 矢量绘图命令
40
+ │ ├── GraphicsPath.ts # 命令类型定义(PathCommandType, GraphicsCommand)
41
+ │ ├── enums/ # BlendMode, BitmapFillMode, CapsStyle, GradientType, JointStyle, OrientationMode, StageScaleMode
42
+ │ └── texture/ # BitmapData, Texture, SpriteSheet, RenderTexture
43
+ ├── player/ # 渲染管线 + 游戏循环
44
+ │ ├── Player.ts # 播放器(Stage + Renderer 绑定,含 perf 性能指标)
45
+ │ ├── SystemTicker.ts # 帧循环(RAF + ENTER_FRAME,含 callLater 延迟调用队列)
46
+ │ ├── RenderPipe.ts # RenderPipe 接口定义
47
+ │ ├── TouchHandler.ts # 触摸/鼠标输入
48
+ │ ├── ScreenAdapter.ts # 屏幕适配(7种缩放模式)
49
+ │ ├── createPlayer.ts # 统一创建入口(含 Capabilities 初始化、RenderTexture 渲染器注入)
50
+ │ ├── KurotOptions.ts # 配置接口
51
+ │ ├── canvas/ # Canvas 2D 渲染后端(降级方案)
52
+ │ │ ├── CanvasRenderer.ts # 直接遍历渲染器(支持全部 DisplayObject 类型 + 滤镜降级)
53
+ │ │ ├── DisplayList.ts # cacheAsBitmap 离屏缓存
54
+ │ │ └── CanvasBuffer.ts # Canvas 2D 缓冲区(含 hitTestBuffer 像素级命中测试)
55
+ │ └── webgl/ # WebGL 渲染后端(主渲染器)
56
+ │ ├── WebGLRenderer.ts # 两阶段渲染器(build + execute,含 RenderGroup/DisplayListCache 支持)
57
+ │ ├── WebGLRenderContext.ts # WebGL 状态管理 + draw 调度(WebGL1/2 自动选择,配合 WebGLFilterSystem 管理效果)
58
+ │ ├── WebGLRenderBuffer.ts # WebGL 缓冲区(含对象池,支持 offscreen/stencil/scissor)
59
+ │ ├── WebGLRenderTarget.ts # FBO 管理
60
+ │ ├── WebGLVertexArrayObject.ts # VAO 顶点管理(单纹理 20B + 多纹理 24B 双布局)
61
+ │ ├── WebGLDrawCmdManager.ts # 绘制命令队列(12 种命令类型,含 MULTI_TEXTURE)
62
+ │ ├── WebGLProgram.ts # 着色器程序缓存
63
+ │ ├── WebGLUtils.ts # WebGL 工具(compileShader, createProgram, premultiplyTint, deleteWebGLTexture)
64
+ │ ├── InstructionSet.ts # 指令集(renderableIndex O(1) 查找,dirtyRenderables 增量更新)
65
+ │ ├── MultiTextureBatcher.ts # 多纹理批处理(8张/批,slotMap 纹理槽位分配)
66
+ │ ├── pipes/ # RenderPipe 实现
67
+ │ │ ├── BitmapPipe.ts # 位图渲染指令
68
+ │ │ ├── GraphicsPipe.ts # 矢量图形指令(Canvas光栅化 → 纹理上传)
69
+ │ │ ├── MeshPipe.ts # 网格渲染指令
70
+ │ │ ├── FilterPipe.ts # 滤镜 push/pop 指令(含对象池)
71
+ │ │ ├── MaskPipe.ts # 遮罩 push/pop 指令(含对象池)
72
+ │ │ ├── TextPipe.ts # 文本 WebGL 渲染(offscreen canvas → 纹理上传)
73
+ │ │ └── ParticlePipe.ts # 粒子系统渲染
74
+ │ └── shaders/ # GLSL 着色器
75
+ │ ├── ShaderLib.ts # GLSL ES 1.00 着色器库(WebGL1)
76
+ │ └── ShaderLib2.ts # GLSL ES 3.00 着色器库(WebGL2)
77
+ ├── events/ # 事件系统(11 个事件类 + EventPhase + IEventDispatcher 接口)
78
+ ├── geom/ # 几何工具(Matrix, Point, Rectangle)
79
+ ├── filters/ # 滤镜(Blur, Glow, DropShadow, ColorMatrix, CustomFilter, MultiPassFilter, BloomFilter)
80
+ ├── text/ # 文本渲染
81
+ │ ├── TextField.ts # 核心文本字段(含 textFlow 富文本、输入模式、密码模式)
82
+ │ ├── BitmapText.ts # 位图文本
83
+ │ ├── BitmapFont.ts # 位图字体
84
+ │ ├── StageText.ts # INPUT 模式 DOM 输入框
85
+ │ ├── HtmlTextParser.ts # HTML 文本解析
86
+ │ ├── InputController.ts # 输入控制器(选择、光标、键盘处理)
87
+ │ ├── TextMeasurer.ts # 文本测量(measureText, getFontString)
88
+ │ ├── LineBreaks.ts # Unicode 17.0 UAX #14 断行边界与字典分词
89
+ │ ├── TextLineLayout.ts # 整段富文本断行、宽度拟合与 UTF-16 源偏移
90
+ │ ├── WordWrap.ts # 独立分词与字素工具(tokenize, splitGraphemes)
91
+ │ ├── enums/ # HorizontalAlign, VerticalAlign, TextFieldType, TextFieldInputType
92
+ │ └── types/ # ITextElement, IWTextElement, ILineElement, IHitTextElement 等类型定义
93
+ ├── resource/ # 资源管理
94
+ │ ├── Resource.ts # 资源核心(单例模式,async/await API,加载/缓存/生命周期)
95
+ │ ├── ResourceConfig.ts # 资源配置
96
+ │ ├── ResourceEvent.ts # 资源事件
97
+ │ ├── ResourceItem.ts # 资源项
98
+ │ ├── ResourceLoader.ts # 资源加载器
99
+ │ └── analyzers/ # 资源分析器(AnalyzerBase, ImageAnalyzer, JsonAnalyzer, SheetAnalyzer, SoundAnalyzer, TextAnalyzer)
100
+ ├── system/ # 系统能力
101
+ │ └── Capabilities.ts # 运行时 WebGL 扩展/平台能力检测(UA + Client Hints)
102
+ ├── net/ # 网络加载(HttpRequest, ImageLoader, HttpMethod, HttpResponseType)
103
+ ├── media/ # 媒体(Sound, SoundChannel, Video)
104
+ ├── utils/ # 工具类(ByteArray, Timer, Logger, FontManager, DebugLog, Base64Util, NumberUtils, toColorString)
105
+ │ # 注:不提供 HashObject,对象身份比较用 === 或 WeakMap 键控查找
106
+ ├── localStorage/ # 本地存储
107
+ └── external/ # 外部接口(ExternalInterface)
108
+ ```
109
+
110
+ ---
111
+
112
+ ## 三、渲染管线架构
113
+
114
+ ### 3.1 两阶段渲染(借鉴 Pixi.js 8)
115
+
116
+ ```
117
+ Phase A — Build(仅 structureDirty 时):
118
+ 遍历 DisplayObject 树 → 生成 Instruction(含 transform 快照)→ InstructionSet
119
+
120
+ Phase A' — Update(仅 renderDirty 时):
121
+ 遍历 dirtyRenderables → 刷新 transform 快照(O(1) lookup via renderableIndex Map)
122
+
123
+ Phase B — Execute(每帧):
124
+ 按指令顺序分发到 Pipe → 无场景图遍历
125
+ ```
126
+
127
+ ### 3.2 RenderPipe 体系
128
+
129
+ ```
130
+ RenderPipe<T extends DisplayObject>
131
+ ├── addToInstructionSet(renderable, set) — 结构变化时调用
132
+ ├── updateRenderable(renderable) — 数据变化时调用
133
+ └── destroyRenderable(renderable) — 对象销毁时调用(可选,见下方 GPU 纹理回收说明)
134
+
135
+ 实现:
136
+ ├── BitmapPipe → BitmapInstruction → drawImage()(含 scale9Grid 九宫格)
137
+ ├── GraphicsPipe → GraphicsInstruction → Canvas光栅化 → 纹理上传 → drawTexture()
138
+ ├── MeshPipe → MeshInstruction → drawMesh()
139
+ ├── TextPipe → TextInstruction → offscreen canvas → 纹理上传 → drawImage()
140
+ ├── ParticlePipe → ParticleInstruction → 粒子批处理渲染
141
+ ├── FilterPipe → FilterPush/Pop → 离屏FBO → 着色器滤镜
142
+ └── MaskPipe → MaskPush/Pop → stencil/scissor/离屏合成
143
+ ```
144
+
145
+ **GPU 纹理回收**:`destroyRenderable()` 目前不会被 `WebGLRenderer` 自动调用(`DisplayObject` 没有"永久销毁"信号——`$onRemoveFromStage` 会在临时移出舞台又重新加入时反复触发,接上调用链会让虚拟列表等场景反复重新栅格化/上传纹理)。因此 `GraphicsPipe`、`TextPipe` 各自缓存纹理的清理不依赖调用方显式调用,而是用 `FinalizationRegistry` 在对应的 `Graphics` / `TextField` 被 JS 引擎 GC 时自动 `gl.deleteTexture()`。`destroyRenderable()` 仍保留为可选的立即释放路径(调用时会 `unregister` 对应的 GC 回调,避免重复删除),供未来需要显式销毁时机的场景使用。`BitmapPipe` / `MeshPipe` 的 `destroyRenderable` 是 no-op,因为它们绘制用的纹理归属于 `BitmapData` 的生命周期,不由 Pipe 自身管理。
146
+
147
+ ### 3.3 RenderGroup 分层
148
+
149
+ ```typescript
150
+ backgroundLayer.isRenderGroup = true;
151
+ ```
152
+
153
+ - 每个 RenderGroup 拥有独立的 InstructionSet
154
+ - 父 set 只包含一条 `renderGroup` 指令,指向子树 set
155
+ - 子树结构变化只触发自身 set 重建,不影响父 set
156
+ - 静态子树的 JS 遍历开销降为零
157
+ - 实现通过 `WeakMap<DisplayObjectContainer, InstructionSet>` 管理 group sets
158
+ - 使用 `WeakRef` 跟踪 group 生命周期,便于 GC
159
+
160
+ ### 3.4 多纹理批处理
161
+
162
+ - `MultiTextureBatcher` 管理最多 8 个纹理槽位(WebGL1 最小保证纹理单元数)
163
+ - 顶点格式扩展:增加 `aTextureId` float 属性(stride 从 20B 增至 24B)
164
+ - Fragment shader 使用 if/else 链采样(WebGL1 兼容)
165
+ - mesh、filter、blend 变化自动 flush 回退到单纹理路径
166
+ - `WebGLVertexArrayObject` 维护双缓冲区(单纹理 + 多纹理),按需切换 GPU buffer 大小
167
+
168
+ ### 3.5 脏标记系统
169
+
170
+ ```
171
+ DisplayObject 脏标记:
172
+ ├── cacheDirty — cacheAsBitmap 缓存失效,向上传播
173
+ ├── renderDirty — 视觉数据变化(位置/纹理/alpha/tint),向上传播
174
+ └── renderMode — 渲染模式(NONE/FILTER/CLIP/SCROLLRECT)
175
+
176
+ 通知机制(单 Player 静态钩子,由 Player 构造函数赋值):
177
+ ├── $onStructureChange?: () => void — 结构变化回调
178
+ ├── $onRenderableDirty?: (obj: DisplayObject) => void — 渲染脏回调
179
+ ├── DisplayObjectContainer.$onContainerStructureChange?: (owner) => void — 容器结构变化回调
180
+ ├── $markDirty()
181
+ │ ├── 更新 worldAlpha / worldTint 缓存(O(1) 读取)
182
+ │ ├── 调用 $onRenderableDirty?.(this) → WebGLRenderer.markRenderableDirty()
183
+ │ └── 向上传播 cacheDirty + renderDirty
184
+ ├── $updateRenderMode()
185
+ │ └── 调用 $onStructureChange?.() → WebGLRenderer.markStructureDirty()
186
+ └── DisplayObjectContainer.markDirtyInternal()
187
+ └── 调用 $onContainerStructureChange?.(this) → markStructureDirty(owner)
188
+ ```
189
+
190
+ 引擎设计上始终是单 Player:`Player` 的构造函数直接把上述三个静态字段赋值
191
+ 为具体的闭包函数(见 `player/Player.ts`),并在 `destroy()` 里清空它们。
192
+
193
+ ### 3.6 RenderObjectType 快速路由
194
+
195
+ 为避免热路径中的 `instanceof` 检查,DisplayObject 使用 `renderObjectType` 枚举:
196
+
197
+ | 枚举值 | 值 | 对应类型 | 路由 Pipe |
198
+ | ---------- | --- | --------- | ------------ |
199
+ | `NONE` | 0 | 无渲染 | — |
200
+ | `BITMAP` | 1 | Bitmap | BitmapPipe |
201
+ | `MESH` | 2 | Mesh | MeshPipe |
202
+ | `SHAPE` | 3 | Shape | GraphicsPipe |
203
+ | `SPRITE` | 4 | Sprite | GraphicsPipe |
204
+ | `TEXT` | 5 | TextField | TextPipe |
205
+ | `PARTICLE` | 6 | Particle | ParticlePipe |
206
+
207
+ ---
208
+
209
+ ## 四、WebGL 渲染后端
210
+
211
+ ### 4.1 WebGL 版本选择
212
+
213
+ `WebGLRenderContext` 在初始化时自动检测并选择最佳后端:
214
+
215
+ - **WebGL 2 优先**:使用 `canvas.getContext('webgl2')`,加载 `ShaderLib2`(GLSL ES 3.00)
216
+ - **WebGL 1 降级**:回退到 `canvas.getContext('webgl')`,加载 `ShaderLib`(GLSL ES 1.00)。
217
+ 不使用 `experimental-webgl` 前缀——该别名只有 IE11/早期 Safari/旧版
218
+ Android Chrome 才需要,现代浏览器的标准 `'webgl'` 已经足够。
219
+ - **无独立探测 canvas**:`Player` 直接在应用方传入的 canvas 上依次尝试
220
+ `webgl2` → `webgl`,两者都失败才降级到 Canvas 2D,不额外创建临时 canvas
221
+ 探测能力。`checkWebGLSupport()` 函数仍导出,供需要独立探测的调用方使用,
222
+ 但 `Player` 自身不调用它。
223
+ - 着色器库通过实例属性动态绑定:`ctx.shaders`、`ctx.blurTierFn`、`ctx.makeBlurH`、`ctx.makeBlurV`
224
+
225
+ ### 4.2 渲染流程
226
+
227
+ ```
228
+ Player.render()
229
+ → WebGLRenderer.render(stage, buffer, matrix)
230
+ → Phase A: _buildInstructions() / _updateDirtyRenderables()
231
+ → Phase B: _executeInstructions()
232
+ → Pipe.execute() → WebGLRenderContext.drawImage/drawMesh/drawTexture()
233
+ → WebGLRenderContext.flush() → _flush()
234
+ → 上传顶点(bufferSubData)→ 遍历 DrawCmdManager → 分发 draw batch
235
+ ```
236
+
237
+ ### 4.3 着色器体系
238
+
239
+ | 着色器 | 用途 | 来源 |
240
+ | ---------------------------------------- | ---------------------------------- | --------- |
241
+ | default_vert + texture_frag | 标准纹理绘制 | ShaderLib |
242
+ | multi_vert + multi_frag | 多纹理批处理(8单元) | ShaderLib |
243
+ | default_vert + colorTransform_frag | ColorMatrixFilter | ShaderLib |
244
+ | default_vert + glow_frag | Glow/DropShadow | ShaderLib |
245
+ | fullscreen_vert + makeBlurH/ VFrag | 水平/垂直模糊(ping-pong 双 pass) | ShaderLib |
246
+ | default_vert + primitive_frag | 纯色矩形(stencil mask) | ShaderLib |
247
+ | fullscreen_vert | 全屏 quad blit(滤镜 pass) | ShaderLib |
248
+
249
+ WebGL2(ShaderLib2)提供等价的 GLSL ES 3.00 版本着色器,使用 `in`/`out` 语法替代 `attribute`/`varying`,
250
+ `texture()` 替代 `texture2D()`,blur 着色器按 4/8/16/32 档生成固定上限循环,仍使用三角权重核。
251
+
252
+ ### 4.4 滤镜渲染
253
+
254
+ 滤镜通过 FilterPipe 的 push/pop 指令对实现:
255
+
256
+ - **push 阶段**:分配离屏 FBO(含 filter padding 扩展),将后续 draw 重定向到离屏 buffer
257
+ - **pop 阶段**:调用 `compositeFilterResult()` 将离屏结果合成回父 buffer
258
+
259
+ 合成流程(`compositeFilterResult`):
260
+
261
+ 1. `flush()` 确保离屏输入完整。
262
+ 2. `WebGLFilterSystem` 按数组顺序执行;中间 pass 关闭 blend/stencil/scissor,使用独立输出。
263
+ 3. 显式恢复父 FBO、viewport 与裁剪状态。
264
+ 4. 使用 `drawFramebufferTexture()` 合成最终结果,不重复应用已经写入子树的 alpha/tint。
265
+ 5. 立即 flush 后归还中间纹理;失败时清理 pass 资源与待执行命令,下一帧可以恢复。
266
+
267
+ - **ColorMatrixFilter**:无 mask 的叶子、单滤镜、继承分辨率时保留 inline 快速路径;组与组合走离屏。
268
+ - **BlurFilter**:横纵分离,quality 控制 pass 对数;大半径先降采样,使 shader 半径不超过 32 物理像素。
269
+ - **Glow / DropShadow**:保留原有 `glow_frag`;knockout 绑定与“是否保留原图”的 shader 语义对应。
270
+ - **CustomFilter**:显式 GLSL 100/300 源码、按链接类型校验 numeric uniforms、独立采样的辅助 BitmapData 上传。
271
+ - **MultiPassFilter / BloomFilter**:有序无环 pass 图,可读取原图或较早输出,支持各 pass 缩放。
272
+
273
+ `WebGLFilterTargetPool` 缓存中间 GPU 目标,空闲上限 16 个 / 64 MiB;子树捕获缓冲与辅助图片不计入这个限制。
274
+ `WebGLFilterTextures` 管理辅助图像上传和恢复。着色器编译缓存按 GL context 与实际源码隔离。
275
+
276
+ Filter padding 按链累加,并包含子级效果的渲染范围,不修改布局 bounds。
277
+ 离屏 pass 统一使用 framebuffer UV(左下为 0,0),每一步保持图像方向。
278
+ DropShadow 传入正常角度弧度;辅助 DOM 图片的自动 UV 矩阵负责纵向转换。
279
+
280
+ 具体 API、示例、Canvas 与缓存限制见 [GPU filters](filters.md)。
281
+
282
+ ### 4.5 遮罩渲染
283
+
284
+ - scrollRect/maskRect 无旋转且没有外层 scissor:scissor 裁剪(GPU 硬件加速)
285
+ - scrollRect/maskRect 有旋转或嵌套在外层 scissor 内:stencil 裁剪;WebGL 主画布与能力探测共用 2D 上下文配置(不申请 depth,申请 stencil)
286
+ - DisplayObject mask:离屏 buffer + destination-in 合成
287
+
288
+ ---
289
+
290
+ ## 五、Canvas 2D 渲染后端
291
+
292
+ Canvas 2D 渲染器保持直接遍历模式,作为 WebGL 不可用时的降级方案。
293
+
294
+ - Graphics 离屏 Canvas 缓存(`canvasCacheDirty` 脏标记)
295
+ - CSS filter 快速路径(Blur → `blur()`,DropShadow → `drop-shadow()`)
296
+ - ColorMatrixFilter CPU 像素操作降级
297
+ - cacheAsBitmap 支持(通过 DisplayList)
298
+ - 像素级命中测试(3x3 离屏 buffer,hitTestBuffer 函数)
299
+ - 支持所有 DisplayObject 类型:Bitmap, Shape, Sprite, Mesh, TextField(含样式、对齐、自动换行)
300
+ - TextField 渲染:测量 → 分行 → Canvas 2D fillText/strokeText 绘制;断行规则与输入偏移契约见 [text-layout.md](./text-layout.md)
301
+
302
+ ---
303
+
304
+ ## 六、快速启动 API
305
+
306
+ ### `createPlayer(options)`
307
+
308
+ ```typescript
309
+ import { createPlayer, Event } from '@kurot/core';
310
+
311
+ const app = createPlayer({
312
+ canvas: document.getElementById('game-canvas') as HTMLCanvasElement,
313
+ frameRate: 60,
314
+ scaleMode: 'showAll',
315
+ contentWidth: 640,
316
+ contentHeight: 1136,
317
+ });
318
+
319
+ app.start(root);
320
+ ```
321
+
322
+ `createPlayer` 自动完成:
323
+
324
+ 1. 调用 `Capabilities._init()` 检测运行环境(OS、isMobile、language)
325
+ 2. 首次调用时注入 `RenderTexture.renderer`(Canvas 2D 离屏渲染器)
326
+ 3. 创建 Stage、Player(WebGL 优先,Canvas 2D 降级)、TouchHandler、ScreenAdapter
327
+ 4. 调用 `setupLifecycle(stage)` 绑定 ENTER_FRAME / RENDER 广播
328
+
329
+ ### KurotOptions
330
+
331
+ | 属性 | 类型 | 默认值 | 说明 |
332
+ | --------------- | ------------------- | --------------- | ------------ |
333
+ | `canvas` | `HTMLCanvasElement` | (必填) | 渲染画布 |
334
+ | `frameRate` | `number` | `60` | 目标帧率 |
335
+ | `scaleMode` | `StageScaleMode` | `'showAll'` | 屏幕适配模式 |
336
+ | `contentWidth` | `number` | `canvas.width` | 逻辑内容宽度 |
337
+ | `contentHeight` | `number` | `canvas.height` | 逻辑内容高度 |
338
+ | `resolution` | `number` | `min(DPR, 2)` | 后备缓冲像素密度 |
339
+ | `orientation` | `OrientationMode` | `'auto'` | 屏幕方向 |
340
+ | `maxTouches` | `number` | `99` | 最大触点数 |
341
+ | `background` | `string` | — | CSS 背景色 |
342
+
343
+ ### KurotApp 返回值
344
+
345
+ | 属性 | 类型 | 说明 |
346
+ | --------------- | --------------- | ------------- |
347
+ | `player` | `Player` | 渲染播放器 |
348
+ | `stage` | `Stage` | 舞台根节点 |
349
+ | `touchHandler` | `TouchHandler` | 触摸/鼠标处理 |
350
+ | `screenAdapter` | `ScreenAdapter` | 屏幕适配器 |
351
+ | `start(root?)` | `() => void` | 启动游戏循环 |
352
+ | `stop()` | `() => void` | 停止游戏循环 |
353
+
354
+ ---
355
+
356
+ ## 七、测试覆盖
357
+
358
+ `test/` 下按模块组织测试文件,运行 `pnpm --dir packages/core test` 查看当前
359
+ 文件数与用例数。
360
+
361
+ | 模块 | 主要覆盖内容 |
362
+ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
363
+ | events/ | Event, EventDispatcher, EventPropagation, EventMap, TouchEvent, HTTPStatusEvent, ProgressEvent |
364
+ | geom/ | Matrix, Point, Rectangle |
365
+ | utils/ | ByteArray, Base64Util, Logger, DebugLog, NumberUtils, toColorString |
366
+ | display/ | DisplayObject, DisplayObjectContainer, DisplayObjectIntegration, Bitmap, BitmapData, Sprite, Shape, Mesh, Stage, StageText, Graphics, Texture, SpriteSheet, BlendMode, DisplayList |
367
+ | filters/ | Filter (全部滤镜), CustomFilter, filters(滤镜集成), EffectTransform(离屏效果变换正确性) |
368
+ | media/ | Sound, SoundChannel, Video |
369
+ | player/ | InstructionSet, InstructionPool, RenderGroup, TextPipe, MaskPipe, WebGLRendererDirty, WebGLRendererLeaf, WebGLVertexArrayObject, WebGLRenderBuffer, WebGLBlurFramebufferPool, CreatePlayer, TouchHandler |
370
+ | net/ | HttpRequest, ImageLoader |
371
+ | resource/ | Resource(并发 loadGroup 队列化), ResourceLoader(并发/重试/无 analyzer 场景) |
372
+ | examples/benchmark/tests/ | 性能基准运行时与跨引擎对比测试 |
373
+ | text/ | 通过 display 集成测试覆盖 |
@@ -0,0 +1,232 @@
1
+ # GPU filters
2
+
3
+ Kurot executes `displayObject.filters` in array order. Each filter reads the
4
+ previous filter's image. WebGL 1 and WebGL 2 are supported; shader sources are
5
+ compiled verbatim, with no automatic GLSL translation.
6
+
7
+ ## Built-in effects
8
+
9
+ - `ColorMatrixFilter`: color-matrix transform with premultiplied-alpha handling.
10
+ - `BlurFilter(x, y, quality)`: non-negative logical-pixel radii; `quality` is an
11
+ integer from 1 to 16. Each quality step adds one horizontal/vertical pair,
12
+ using radius / sqrt(quality) to approximately preserve variance. The kernel
13
+ remains the existing triangular kernel. Large radii use a reduced-resolution
14
+ image so the per-axis shader radius stays within 32 physical pixels, then
15
+ resample back. This trades fine detail for wider blur; it is not bokeh DoF.
16
+ - `GlowFilter` / `DropShadowFilter`: the existing radial alpha-sampling shader.
17
+ `knockout=false` preserves the original. `hideObject` hides the original for
18
+ shadows. Their `quality` field remains serialized metadata; it does not
19
+ change this shader's fixed sample count.
20
+ - `BloomFilter`: LDR brightness extraction, blur, and original-image composition.
21
+
22
+ ```ts
23
+ import { BloomFilter, BlurFilter } from '@kurot/core';
24
+
25
+ const blur = new BlurFilter(12, 12, 2);
26
+ blur.resolution = 0.5;
27
+ background.filters = [blur];
28
+
29
+ const bloom = new BloomFilter({
30
+ threshold: 0.7,
31
+ intensity: 1,
32
+ blur: 12,
33
+ blurScale: 0.5,
34
+ });
35
+ world.filters = [bloom];
36
+ bloom.intensity = 0.8;
37
+ ```
38
+
39
+ `Filter.resolution` specifies physical pixels per logical unit. `undefined`
40
+ inherits the parent. A filter chain uses the minimum of the parent resolution
41
+ and its explicit filter resolutions, additionally limited by GPU texture size.
42
+ `BloomFilter.blurScale` reduces only extraction/blur; the original is retained
43
+ at the effect's full resolution.
44
+
45
+ Filtering includes the rendered descendants. Padding from consecutive filters
46
+ is added; descendant effect extents are included without changing layout bounds.
47
+ A mask/scroll clip on the filtered object clips its final result. Layered
48
+ containers provide control over whether a mask clips before or after another
49
+ effect. A lone ColorMatrix on an unmasked leaf with inherited resolution retains
50
+ its inline fast path; groups and combinations use offscreen composition.
51
+
52
+ ## CustomFilter
53
+
54
+ Use `CustomFilter.from()` to provide explicit backend variants. Omit a vertex
55
+ source to use Kurot's standard filter quad. A missing backend throws a descriptive
56
+ error rather than silently drawing the unfiltered image.
57
+
58
+ ```ts
59
+ import { CustomFilter } from '@kurot/core';
60
+
61
+ const dissolve = CustomFilter.from({
62
+ webgl1: {
63
+ fragment: `precision mediump float;
64
+ varying vec2 vTextureCoord;
65
+ uniform sampler2D uSampler;
66
+ uniform float threshold;
67
+ void main() {
68
+ gl_FragColor = texture2D(uSampler, vTextureCoord)
69
+ * step(threshold, vTextureCoord.x);
70
+ }`,
71
+ },
72
+ webgl2: {
73
+ fragment: `#version 300 es
74
+ precision mediump float;
75
+ in vec2 vTextureCoord;
76
+ uniform sampler2D uSampler;
77
+ uniform float threshold;
78
+ out vec4 result;
79
+ void main() {
80
+ result = texture(uSampler, vTextureCoord)
81
+ * step(threshold, vTextureCoord.x);
82
+ }`,
83
+ },
84
+ uniforms: { threshold: 0 },
85
+ padding: 0,
86
+ });
87
+ actor.filters = [dissolve];
88
+ dissolve.setUniform('threshold', 0.5);
89
+ ```
90
+
91
+ The existing `new CustomFilter(vertex, fragment, uniforms)` constructor submits
92
+ that exact source pair on either backend; the caller is responsible for source
93
+ compatibility. An empty vertex source selects the backend's standard vertex.
94
+ The source-pair key distinguishes vertex/fragment boundaries. Compiled programs
95
+ are cached by context and actual source, so uniforms are never part of the key.
96
+ Do not generate new shader text every frame; update uniforms instead.
97
+
98
+ Supported uniforms: `float`, `int`, `bool`, floating/integer/boolean vectors,
99
+ `mat2`, `mat3`, `mat4`, and their numeric arrays. Values are numbers, booleans,
100
+ numeric arrays, `Float32Array`, or `Int32Array`. Matrices use column-major order.
101
+ Active uniforms must be supplied with the correct component count; missing,
102
+ non-finite, or incompatible values throw before drawing. Unsigned uniforms,
103
+ non-square matrices, sampler arrays and uniform blocks are not part of this API.
104
+
105
+ `setUniform()` / `setTexture()` invalidate attached objects. After direct edits
106
+ to an array, `filter.uniforms`, or binding descriptor, call `filter.invalidate()`.
107
+ Built-in effect setters invalidate their users as well.
108
+
109
+ ### Coordinates and automatic uniforms
110
+
111
+ The default vertex accepts `aVertexPosition` (vec2), `aTextureCoord` (vec2), and
112
+ `aColor` (vec4), with `projectionVector` (vec2). Custom vertex programs must use
113
+ this attribute layout. Vertex displacement needs sufficient `padding` and does
114
+ not change scene geometry, layout or hit testing.
115
+
116
+ `vTextureCoord` uses **bottom-left framebuffer UVs**: the top is y=1. It spans
117
+ the entire padded input, not just the unpadded object. Input/output colors are
118
+ premultiplied RGBA. `vColor` is neutral in custom intermediate passes because
119
+ subtree vertices have already received world alpha/tint. For effects that need
120
+ straight RGB, divide by alpha only when alpha is positive, then premultiply the
121
+ output again.
122
+
123
+ | Reserved uniform | GLSL type | Value |
124
+ | --- | --- | --- |
125
+ | `uSampler` | sampler2D | Previous pass image, texture unit 0 |
126
+ | `projectionVector` | vec2 | Quad projection, managed by the renderer |
127
+ | `uTextureSize` | vec2 | Input width/height in physical pixels |
128
+ | `uInputSize` | vec4 | Input width, height, inverse width, inverse height |
129
+ | `uInputClamp` | vec4 | Input texel-center UV min/max |
130
+ | `uOutputSize` | vec4 | Output width, height, inverse width, inverse height |
131
+ | `uResolution` | float | Requested output pixels per logical unit for this pass |
132
+
133
+ Only declare the uniforms needed by the shader. They cannot be overridden in
134
+ `uniforms` or auxiliary bindings. Small textures may round to one physical pixel.
135
+ Use actual `uInputSize` / `uOutputSize` when exact texel spacing matters.
136
+
137
+ UV distortion should clamp to `uInputClamp`. A sample outside [0,1] otherwise
138
+ uses clamp-to-edge sampling, not automatic transparent border behavior.
139
+
140
+ ## Auxiliary images
141
+
142
+ `textures` maps sampler names to full-image `BitmapData` resources. The caller
143
+ retains ownership. Cropped/rotated atlas `Texture` objects are not accepted.
144
+ Auxiliary uploads have their own nearest/linear sampling state, so they do not
145
+ change the same image's sprite sampling.
146
+
147
+ ```ts
148
+ filter.setTexture('uMap', { source: mapBitmapData, smoothing: false });
149
+ ```
150
+
151
+ Declare `uniform sampler2D uMap`. Optionally declare `uMapMatrix` (mat3) and
152
+ `uMapClamp` (vec4). The default matrix converts bottom-left filter UV to top-left
153
+ image UV. For example, in GLSL 1.00:
154
+
155
+ ```glsl
156
+ vec2 mapUV = (uMapMatrix * vec3(vTextureCoord, 1.0)).xy;
157
+ vec2 displacement = texture2D(uMap, clamp(mapUV, uMapClamp.xy, uMapClamp.zw)).rg * 2.0 - 1.0;
158
+ vec2 uv = vTextureCoord + displacement * amplitude / uTextureSize;
159
+ gl_FragColor = texture2D(uSampler, clamp(uv, uInputClamp.xy, uInputClamp.zw));
160
+ ```
161
+
162
+ This maps the image across the padded input. `transform: [a,b,c,d,tx,ty]` can
163
+ replace the affine mapping; it is expressed directly in normalized UV units.
164
+ The default is `[1,0,0,-1,0,1]`.
165
+
166
+ After editing canvas/video content, call `BitmapData.invalidate(source)` so the
167
+ next pass uploads the new pixels. Call `filter.invalidate()` when application
168
+ scheduling also needs an explicit invalidation. Disposed or unavailable sources
169
+ throw instead of sampling stale pixels. Texture units are checked against the
170
+ context's fragment-stage limit, including the primary input.
171
+
172
+ ## Multi-pass effects
173
+
174
+ `MultiPassFilter` accepts 1–32 immutable pass descriptions. A pass reads the
175
+ previous output by default. `input: 'original'` reads the input to this effect;
176
+ `input: 0` reads the first pass output. Additional `textures` references use the
177
+ same notation and are supported by CustomFilter passes only. Forward references,
178
+ cycles and nested MultiPassFilter instances are rejected.
179
+
180
+ ```ts
181
+ import { MultiPassFilter } from '@kurot/core';
182
+
183
+ const effect = new MultiPassFilter([
184
+ { filter: extractHighlights, scale: 0.5 },
185
+ { filter: blurHighlights, scale: 0.5 },
186
+ { filter: combine, textures: { uOriginal: 'original' } },
187
+ ]);
188
+ world.filters = [effect];
189
+ ```
190
+
191
+ Here `combine` declares `uniform sampler2D uOriginal`. Borrowed framebuffer
192
+ inputs share the primary input's bottom-left UV convention; their automatic
193
+ `<name>Matrix` is identity. Primary and additional inputs may differ in pixel
194
+ size but cover the same logical rectangle. `scale` is relative to the enclosing
195
+ effect resolution, not the previous pass. Per-pass filter `resolution` can
196
+ further reduce its output resolution. Update child filter uniforms to animate;
197
+ replace the MultiPassFilter to change its pass graph.
198
+
199
+ GPU targets are never exposed to application code. The renderer preserves inputs
200
+ until their consumers finish, prohibits framebuffer feedback and recycles owned
201
+ targets even after errors. The idle intermediate pool is limited to 16 targets
202
+ and 64 MiB. Active pass images, subtree capture buffers and auxiliary images are
203
+ not included in that idle limit. Player's framebuffer pool counters report this
204
+ intermediate pool. `Player.destroy()` releases idle effect targets and auxiliary
205
+ uploads; caller image objects remain intact. Context restoration recreates
206
+ programs, intermediate targets and auxiliary uploads.
207
+
208
+ ## Backend and caching boundaries
209
+
210
+ CustomFilter, MultiPassFilter and BloomFilter are GPU effects. Canvas 2D skips
211
+ them; it continues to approximate supported built-in effects through its existing
212
+ CSS/pixel paths. Its filter combinations and Blur quality are not pixel-equivalent
213
+ to WebGL.
214
+
215
+ `cacheAsTexture` and `RenderTexture` still rasterize through Canvas 2D. Do not use
216
+ them to capture or cache these GPU effects. For post-processing use filter chains
217
+ or MultiPassFilter, which retain GPU images.
218
+
219
+ ## Validation
220
+
221
+ Run from the repository root:
222
+
223
+ ```sh
224
+ pnpm --dir packages/core build
225
+ pnpm --dir packages/core test
226
+ pnpm --dir packages/core test:visual
227
+ ```
228
+
229
+ The browser suite includes deterministic scene screenshots and explicit pixel
230
+ assertions on both WebGL versions, resource restoration, error recovery, filter
231
+ ordering, masks, high-resolution inputs, large-radius blur and Bloom. It is a
232
+ Chromium baseline, not a guarantee for every physical GPU or mobile browser.