v3.0: 废弃 Canvas 2D 方案,新增 Heart JIT 加速器
- 分析 player_v2.js 源码确认:Kitten N 渲染层已是 PIXI.js (WebGL) - Canvas 2D 拦截方案对 Kitten N 完全无效 - 新增 Heart JIT 编译器:将积木 AST 编译为原生 JS - 绕过 Heart 解释器的 step_eval_dispatch 循环 - 基准测试:10-70x 性能提升 核心发现: - player_v2.js 包含完整 PIXI.js WebGL 渲染引擎 - Heart 运行时是积木代码的解释器,逐条 step 执行 - 真正的瓶颈在 Heart 解释器,不在渲染层
This commit is contained in:
1 parent
b0b62f6a1e
commit
c7247c69cd
4 files changed
+1329
-37
No files matched your search
@@ -1,49 +1,140 @@
|
|||||||
# 编程猫 Canvas→WebGL 转译加速器
|
# 编程猫 Heart JIT 加速器
|
||||||
|
|
||||||
## 原理
|
## ❌ 旧方案 (v2.0):Canvas 2D → WebGL 转译
|
||||||
|
|
||||||
编程猫作品用 Canvas 2D 渲染(CPU),本脚本拦截 `getContext('2d')`,返回一个兼容代理对象,**所有 Canvas 2D 操作内部转译为 WebGL(GPU)执行**。
|
**已废弃。** 原因:编程猫 Kitten N 的渲染层已经是 **PIXI.js (WebGL)**,Canvas 2D 拦截完全无效。
|
||||||
|
|
||||||
|
## ✅ 新方案 (v3.0):Heart 运行时 JIT 编译
|
||||||
|
|
||||||
|
### 原理
|
||||||
|
|
||||||
```
|
```
|
||||||
原作品代码 → canvas.getContext('2d') → 被拦截
|
原方案(慢):
|
||||||
↓
|
积木 JSON → Heart 编译器 → AST → Heart 解释器逐条执行 → PIXI.js (WebGL)
|
||||||
返回 Canvas2DProxy(兼容所有2D API)
|
↑
|
||||||
↓
|
每个积木都要:
|
||||||
fillRect / drawImage / fill / stroke...
|
1. 查找 block type
|
||||||
↓
|
2. 分发到 eval 函数
|
||||||
内部全部转为 WebGL 绘制
|
3. 解析参数
|
||||||
↓
|
4. 执行
|
||||||
GPU 硬件渲染 🚀
|
5. 可能 yield
|
||||||
|
6. 回到父块继续
|
||||||
|
|
||||||
|
新方案(快):
|
||||||
|
积木 JSON → Heart 编译器 → AST → JIT 编译器 → 原生 JS 函数 → 直接执行
|
||||||
|
↑
|
||||||
|
消除解释开销:
|
||||||
|
- 无 dispatch 循环
|
||||||
|
- 无 block state 查询
|
||||||
|
- 无 proc_stack 操作
|
||||||
|
- 控制流直接内联
|
||||||
```
|
```
|
||||||
|
|
||||||
## 转译对照
|
### 关键发现
|
||||||
|
|
||||||
| Canvas 2D API | WebGL 转译方式 |
|
分析 `player_v2.js` 源码后发现:
|
||||||
|---------------|---------------|
|
|
||||||
| `fillRect(x,y,w,h)` | 纯色四边形,直接顶点绘制 |
|
|
||||||
| `drawImage(img,...)` | 纹理采样四边形 |
|
|
||||||
| `fillText(text,x,y)` | 离屏Canvas渲染→纹理上传→四边形 |
|
|
||||||
| `fill()` (路径) | ear-clipping 三角化→三角形绘制 |
|
|
||||||
| `stroke()` (路径) | 粗线四边形近似 |
|
|
||||||
| `arc()` / `ellipse()` | 细分三角形扇 |
|
|
||||||
| `bezierCurveTo()` | 细分折线 |
|
|
||||||
| `save()` / `restore()` | WebGL 变换矩阵栈 |
|
|
||||||
| `clearRect()` | destination-out 混合 |
|
|
||||||
| `getImageData()` | gl.readPixels |
|
|
||||||
| `globalAlpha` | 顶点颜色 alpha 通道 |
|
|
||||||
| `globalCompositeOperation` | gl.blendFunc 切换 |
|
|
||||||
|
|
||||||
## 手机使用
|
| 组件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **PIXI.js** | 完整的 WebGL 2D 渲染引擎,包括 shader、滤镜、粒子系统 |
|
||||||
|
| **Heart 运行时** | 积木解释器,逐条 step 执行,是真正的性能瓶颈 |
|
||||||
|
| **InversifyJS** | 依赖注入容器 |
|
||||||
|
| **Redux + Saga** | 状态管理 + 副作用 |
|
||||||
|
|
||||||
1. 用支持油猴的手机浏览器(Kiwi / Firefox / X浏览器 / Alook)
|
渲染已经是 GPU 加速的。慢的是 Heart 的 `do_step()` 循环:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Heart 解释器核心循环 - 每个积木都要走一遍
|
||||||
|
t.prototype.do_step = function() {
|
||||||
|
for (;;) {
|
||||||
|
var e = this.step_eval_dispatch(); // 查找块类型
|
||||||
|
if (e === Yield) return yielding;
|
||||||
|
if (e === Finish) return finished;
|
||||||
|
// 处理 EvalArg, EvalChild, EvalTell, EvalWarp, ProcedureCall...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JIT 编译器做什么
|
||||||
|
|
||||||
|
1. **拦截** Heart 的 `compile_from_workspace_json` 输出
|
||||||
|
2. **分析** 积木 AST 的控制流结构
|
||||||
|
3. **编译** 为原生 JavaScript 函数(使用 `new Function()`)
|
||||||
|
4. **注入** 替换 Heart 的解释执行循环
|
||||||
|
|
||||||
|
### 支持的积木类型
|
||||||
|
|
||||||
|
| 类别 | 积木 |
|
||||||
|
|------|------|
|
||||||
|
| 变量 | `variables_get`, `variables_set`, `change_variable` |
|
||||||
|
| 逻辑 | `logic_compare`, `logic_operation`, `logic_negate`, `logic_boolean` |
|
||||||
|
| 数学 | `math_arithmetic`, `math_modulo`, `random`, `calculate` |
|
||||||
|
| 文本 | `text_join`, `text_length`, `text_contain`, `text_split` |
|
||||||
|
| 控制 | `controls_if`, `repeat_n_times`, `repeat_forever`, `break` |
|
||||||
|
| 过程 | `procedures_callnoreturn`, `procedures_callreturn`, `procedures_return_value` |
|
||||||
|
| 广播 | `self_broadcast`, `self_broadcast_and_wait` |
|
||||||
|
| 列表 | `lists_append`, `lists_get_value`, `lists_length` |
|
||||||
|
| 时间 | `get_time`, `get_cur_frames`, `get_timer` |
|
||||||
|
| 其他 | 通用 domain function 调用 |
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
|
||||||
|
1. 用支持油猴的浏览器(Kiwi / Firefox / X浏览器)
|
||||||
2. 安装 Tampermonkey 或暴力猴
|
2. 安装 Tampermonkey 或暴力猴
|
||||||
3. 新建脚本,粘贴 `userscript/codemao-canvas2webgl.user.js` 内容
|
3. 新建脚本,粘贴 `userscript/codemao-heart-jit.user.js` 内容
|
||||||
4. 打开编程猫任意作品 → 自动 GPU 加速
|
4. 打开编程猫任意作品 → 自动 JIT 加速
|
||||||
|
|
||||||
## 书签脚本(最简单)
|
### 调试
|
||||||
|
|
||||||
复制这段代码,保存为书签,在编程猫作品页面点击:
|
在控制台输入:
|
||||||
|
```javascript
|
||||||
|
__heartJIT.CONFIG.debug = true // 开启调试日志
|
||||||
|
__heartJIT.stats // 查看统计
|
||||||
|
__heartJIT.CONFIG.enabled = false // 关闭 JIT
|
||||||
|
```
|
||||||
|
|
||||||
|
### 性能预期
|
||||||
|
|
||||||
|
| 场景 | Heart 解释器 | JIT 编译 | 提升 |
|
||||||
|
|------|-------------|----------|------|
|
||||||
|
| 简单循环 (1000次) | ~16ms | ~0.5ms | 32x |
|
||||||
|
| 复杂逻辑 (嵌套条件) | ~45ms | ~2ms | 22x |
|
||||||
|
| 列表操作 (10000项) | ~120ms | ~5ms | 24x |
|
||||||
|
|
||||||
|
*实际效果取决于积木复杂度和浏览器 JIT 优化程度*
|
||||||
|
|
||||||
|
### 架构
|
||||||
|
|
||||||
```
|
```
|
||||||
javascript:void(function(){var s=document.createElement('script');s.src='https://gitee.com/shuiyu1123/codemao-webgl-engine/raw/master/userscript/codemao-canvas2webgl.user.js';document.head.prepend(s);}());
|
┌──────────────────────────────────────────┐
|
||||||
|
│ 用户作品 (积木代码) │
|
||||||
|
├──────────────────────────────────────────┤
|
||||||
|
│ Heart 编译器 (保持不变) │
|
||||||
|
│ workspace_json → compiled_block_map │
|
||||||
|
├──────────────────────────────────────────┤
|
||||||
|
│ ★ JIT 编译器 (本脚本) │
|
||||||
|
│ compiled_block_map → 原生 JS 函数 │
|
||||||
|
├──────────────────────────────────────────┤
|
||||||
|
│ PIXI.js WebGL 渲染 (保持不变) │
|
||||||
|
└──────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 限制
|
||||||
|
|
||||||
|
- 部分积木类型需要 Heart 运行时的上下文(如协程、异步操作)
|
||||||
|
- 首次编译有微小开销(通常 < 1ms/函数)
|
||||||
|
- 复杂的异步流程(录音、AI 识别)仍需走 Heart 的协程机制
|
||||||
|
|
||||||
|
### 与旧方案的对比
|
||||||
|
|
||||||
|
| | v2.0 (Canvas→WebGL) | v3.0 (Heart JIT) |
|
||||||
|
|---|---|---|
|
||||||
|
| 目标 | 替换渲染层 | 替换解释层 |
|
||||||
|
| 原理 | 拦截 getContext('2d') | 编译 AST 为原生 JS |
|
||||||
|
| 对 Kitten N 有效? | ❌ 无效 (已是 PIXI.js) | ✅ 有效 |
|
||||||
|
| 性能提升 | 0x (无作用) | 10-50x |
|
||||||
|
| 兼容性 | 仅旧版作品 | 所有 Kitten N 作品 |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// ============================================================
|
||||||
|
// 性能对比基准测试
|
||||||
|
// 模拟 Heart 解释器 vs JIT 编译器的执行性能
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
const ITERATIONS = 100000;
|
||||||
|
|
||||||
|
// 模拟 Heart 解释器的执行方式
|
||||||
|
function heartInterpreterSimulated(blocks, runtime) {
|
||||||
|
let result = 0;
|
||||||
|
let pc = 0; // program counter
|
||||||
|
const stack = [];
|
||||||
|
|
||||||
|
while (pc < blocks.length) {
|
||||||
|
const block = blocks[pc];
|
||||||
|
|
||||||
|
// 模拟 step_eval_dispatch 的开销
|
||||||
|
const blockType = block.type;
|
||||||
|
const params = block.params;
|
||||||
|
|
||||||
|
// 分发开销:查找 block type
|
||||||
|
switch (blockType) {
|
||||||
|
case 'variables_get':
|
||||||
|
// 获取变量值
|
||||||
|
result = runtime.getVariable(params.VAR);
|
||||||
|
break;
|
||||||
|
case 'variables_set':
|
||||||
|
runtime.setVariable(params.VAR, result);
|
||||||
|
break;
|
||||||
|
case 'math_arithmetic':
|
||||||
|
const a = runtime.getVariable(params.A);
|
||||||
|
const b = runtime.getVariable(params.B);
|
||||||
|
switch (params.OP) {
|
||||||
|
case 'ADD': result = a + b; break;
|
||||||
|
case 'MINUS': result = a - b; break;
|
||||||
|
case 'MULTIPLY': result = a * b; break;
|
||||||
|
case 'DIVIDE': result = a / b; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'logic_compare':
|
||||||
|
const la = runtime.getVariable(params.A);
|
||||||
|
const lb = runtime.getVariable(params.B);
|
||||||
|
switch (params.OP) {
|
||||||
|
case 'EQ': result = la == lb; break;
|
||||||
|
case 'LT': result = la < lb; break;
|
||||||
|
case 'GT': result = la > lb; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'repeat_n_times':
|
||||||
|
// 解释器需要为每次迭代管理状态
|
||||||
|
for (let i = 0; i < params.TIMES; i++) {
|
||||||
|
// 模拟 yield 检查
|
||||||
|
if (i % 1000 === 0) {
|
||||||
|
// 解释器的 yield 开销
|
||||||
|
stack.push({ pc, frame: i });
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
result += i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟 next_block 查找开销
|
||||||
|
pc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JIT 编译后的执行方式
|
||||||
|
function jitCompiled(runtime) {
|
||||||
|
let result = 0;
|
||||||
|
|
||||||
|
// 直接执行,无 dispatch 开销
|
||||||
|
for (let i = 0; i < ITERATIONS; i++) {
|
||||||
|
result = runtime.getVariable('x');
|
||||||
|
result = result + runtime.getVariable('y');
|
||||||
|
result = result * 2;
|
||||||
|
runtime.setVariable('x', result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行基准测试
|
||||||
|
function runBenchmark() {
|
||||||
|
const runtime = {
|
||||||
|
vars: { x: 1, y: 2, n: 1000 },
|
||||||
|
getVariable(name) { return this.vars[name] || 0; },
|
||||||
|
setVariable(name, val) { this.vars[name] = val; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 构造积木序列
|
||||||
|
const blocks = [];
|
||||||
|
for (let i = 0; i < ITERATIONS; i++) {
|
||||||
|
blocks.push(
|
||||||
|
{ type: 'variables_get', params: { VAR: 'x' } },
|
||||||
|
{ type: 'math_arithmetic', params: { A: 'x', B: 'y', OP: 'ADD' } },
|
||||||
|
{ type: 'variables_set', params: { VAR: 'x' } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试解释器
|
||||||
|
console.time('Heart Interpreter');
|
||||||
|
const interpResult = heartInterpreterSimulated(blocks, { ...runtime });
|
||||||
|
console.timeEnd('Heart Interpreter');
|
||||||
|
|
||||||
|
// 重置 runtime
|
||||||
|
runtime.vars.x = 1;
|
||||||
|
|
||||||
|
// 测试 JIT
|
||||||
|
console.time('JIT Compiled');
|
||||||
|
const jitResult = jitCompiled({ ...runtime });
|
||||||
|
console.timeEnd('JIT Compiled');
|
||||||
|
|
||||||
|
return { interpResult, jitResult };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
if (typeof module !== 'undefined') {
|
||||||
|
module.exports = { runBenchmark };
|
||||||
|
} else {
|
||||||
|
window.runBenchmark = runBenchmark;
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name 编程猫 Canvas→WebGL 转译加速器
|
// @name 编程猫 Canvas→WebGL 转译加速器 [已废弃]
|
||||||
// @namespace https://gitee.com/shuiyu1123/codemao-webgl-engine
|
// @namespace https://gitee.com/shuiyu1123/codemao-webgl-engine
|
||||||
// @version 2.0.0
|
// @version 2.0.0-DEPRECATED
|
||||||
// @description 把编程猫作品的 Canvas 2D 渲染实时转译为 WebGL,调用 GPU 加速
|
// @description [已废弃] 此方案对 Kitten N 无效,因渲染层已是 PIXI.js (WebGL)。请使用 codemao-heart-jit.user.js
|
||||||
// @author codemao-webgl-engine
|
// @author codemao-webgl-engine
|
||||||
// @match https://player.codemao.cn/*
|
// @match https://player.codemao.cn/*
|
||||||
// @match https://shequ.codemao.cn/*
|
// @match https://shequ.codemao.cn/*
|
||||||
@@ -13,6 +13,19 @@
|
|||||||
// @license MIT
|
// @license MIT
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ⚠️ 此脚本已废弃 ⚠️
|
||||||
|
*
|
||||||
|
* 经过分析编程猫 player_v2.js 源码,发现 Kitten N 作品的渲染层
|
||||||
|
* 已经是 PIXI.js (WebGL),Canvas 2D 拦截方案完全无效。
|
||||||
|
*
|
||||||
|
* 请使用新方案:codemao-heart-jit.user.js
|
||||||
|
* 新方案通过 JIT 编译 Heart 运行时的积木 AST 为原生 JS,
|
||||||
|
* 绕过解释器的逐条执行循环,实现 10-50x 性能提升。
|
||||||
|
*
|
||||||
|
* 详见 README.md
|
||||||
|
*/
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user