@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.
- package/CHANGELOG.md +56 -0
- package/README.md +44 -20
- package/dist/kurot/player/canvas/CanvasRenderer.d.ts.map +1 -1
- package/dist/kurot/player/canvas/CanvasRenderer.js +7 -2
- package/dist/kurot/player/canvas/CanvasRenderer.js.map +1 -1
- package/dist/kurot/text/LineBreaks.d.ts +17 -0
- package/dist/kurot/text/LineBreaks.d.ts.map +1 -0
- package/dist/kurot/text/LineBreaks.js +37 -0
- package/dist/kurot/text/LineBreaks.js.map +1 -0
- package/dist/kurot/text/TextField.d.ts +15 -1
- package/dist/kurot/text/TextField.d.ts.map +1 -1
- package/dist/kurot/text/TextField.js +40 -132
- package/dist/kurot/text/TextField.js.map +1 -1
- package/dist/kurot/text/TextLineLayout.d.ts +18 -0
- package/dist/kurot/text/TextLineLayout.d.ts.map +1 -0
- package/dist/kurot/text/TextLineLayout.js +151 -0
- package/dist/kurot/text/TextLineLayout.js.map +1 -0
- package/dist/kurot/text/WordWrap.d.ts +5 -7
- package/dist/kurot/text/WordWrap.d.ts.map +1 -1
- package/dist/kurot/text/WordWrap.js +5 -7
- package/dist/kurot/text/WordWrap.js.map +1 -1
- package/docs/ai-context.md +238 -0
- package/docs/architecture.md +373 -0
- package/docs/filters.md +232 -0
- package/docs/resource.md +333 -0
- package/docs/text-layout.md +77 -0
- package/package.json +5 -1
|
@@ -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 集成测试覆盖 |
|
package/docs/filters.md
ADDED
|
@@ -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.
|