Open xiaoiver opened 4 years ago
目前已有的 GPGPU 实践从使用者角度可以分成两类:
Tensorflow.js 使用了第一种方式,开发者不需要关心 GLSL 语法:
类似的还有 P4、P5 这样的图表库,开发者在使用 data transform 时也不需要关心这些操作实际上是在 GPU 端执行的:
但有些类型的计算任务只能通过用户编程式描述,无法通过 API 组合完成,例如:
此时就需要提供一种开发者更容易接受的 DSL,GPGPU 库在运行时完成转译。
GPU.js 选择 JS/TS 作为 DSL,在运行时传入 function.toString() 通过 acorn 生成 AST,最终根据目标平台生成 WebGL 1/2 的 Shader 代码。
function.toString()
这种做法的优点很明显,前端开发者很容易上手。但局限性同样存在:
toString()
babel
因此我们需要选择一种强类型的 DSL。Stardust 选择了在 TS 的语法基础上进行扩展。
另外,Stardust 也解决了和渲染结合(sanddance 效果)的问题:
我们优先解决布局计算的问题,在这个场景下并不涉及和渲染结合的问题,即只考虑 Compute Shader 的实现。
https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)
标量:
double
let a: float = 10; // -> float a = 10.0;
let a: int = 10; // -> int a = 10;
let a: uint = 10; // -> uint a = 10;
let a = true; // 类型自动推导 bool a = true;
向量:
// 默认自动推导 let b = [1, 1, 1]; // -> vec3 b = vec3(1.0, 1.0, 1.0);
let b = [true, true]; // -> bvec2 b = bvec2(true, true);
// 报错 let b = [true, 1];
// 显式声明类型 let b: ivec4 = [1, 1, 1, 1]; // -> ivec4 b = ivec4(1, 1, 1, 1);
swizzling,GLSL 特有语法糖,不需要转译。
let b = [1, 2, 2]; let c = b.xxy; let d = b.rgb; // -> vec3 b = vec3(1.0, 2.0, 2.0); vec3 c = b.xxy; vec3 d = b.rgb;
矩阵:
let b: mat4; // -> mat4 b;
全局常量:
const SPEED_DIVISOR = 800.0;
结构体:
interface Light { eyePosOrDir: vec3; isDirectional: bool; } const variableName: Light;
struct Light { vec3 eyePosOrDir; bool isDirectional; } variableName;
由于我们暂时只考虑 Compute Shader 的转译,`sampler` 类型不考虑。 ### main 函数 区分其他函数,通过 `export default` 声明 `main` 函数:
function a() {}
export default compute() {}
// -> function a() {}
void main() {}
### threadID 在数据并行中,所有线程执行相同的核函数处理不同的数据,因此需要在函数中获取当前的线程 ID。考虑到 WebGPU 和 WebGL 不同的实现方式:
// -> WebGPU uint threadID = gl_GlobalInvocationID.x;
// -> WebGL uniform float u_TexSize; varying vec2 v_TexCoord; uint threadID = uint(floor(v_TexCoord.s * u_TexSize + 0.5));
### 数据读写 通过 threadID 就可以获取当前线程需要处理的数据。同样考虑到 GLSL 4.5 和 1.0 的差异,用户需要调用我们封装好的数据读写方法:
import { getData, setData } from 'g-webgpu';
export default function compute(threadID: uint) { let node = getData(threadID); setData(threadID, [1, 1, 1, 1]); }
// -> WebGPU GLSL 4.5 function getData(i) { return u_Data[i]; } function setData(i, data) { u_Data[i] = data; } void main() { uint threadID = gl_GlobalInvocationID.x; vec4 node = getData(threadID); setData(threadID, vec4(1.0)); }
// -> WebGL GLSL 1.0 function getData(i) { return texture2D(u_Data, vec2((float(i) + 0.5) / u_TexSize , 1)); } void main() { uint threadID = uint(floor(v_TexCoord.s * u_TexSize + 0.5)); vec4 node = getData(threadID); gl_FragColor = vec4(1.0); }
### Uniform 区别于全局常量(全大写生成 `define`),配合 interface 生成结构体:
interface Params { float k; float k2; } const params: Params;
// -> WebGPU GLSL 4.5 layout(std140, set = 0, binding = 1) uniform Params { float k; float k2; } params;
// -> WebGL GLSL 1.0 struct Params { float k; float k2; } params; uniform Params params;
### 内置数学函数 GPU.js 会转译 `Math` 方法到 GLSL 原生方法: https://github.com/gpujs/gpu.js#supported-math-functions
Math.sin() // -> sin()
### 限制 #### 固定长度的循环 GLSL 中的循环长度必须是固定的:
for (int j = 0; j < PARTICLE_NUM; j++) {
#### 其他 JS 原生对象 `Date` `RegExp` `String` 这些显然不能使用。 ## Parser 生成 关于 Parser 的生成考虑两个方案: * Antlr。定义 g4 格式的 Lexer/Parser,可以在已有 [TS 语法文件](https://github.com/antlr/grammars-v4/blob/master/typescript/TypeScriptLexer.g4)基础上进行修改。通过 Visitor/Listener 实现 AST 生成。 * Pegjs。更加轻量,可以将 AST 生成代码(JS 描述)写在语法文件中。 我们希望生成的 Parser 本身是用 TS 编写的,这一点 antlr4ts 和 ts-pegjs 都可以做到。 ## AST 参考 ESTree 规范对于节点的定义: * https://github.com/estree/estree/ * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/typescript-estree/src/ts-estree/ts-estree.ts#L1677 例如变量声明: ```typescript export interface VariableDeclaration extends BaseNode { type: AST_NODE_TYPES.VariableDeclaration; declarations: VariableDeclarator[]; kind: 'let' | 'const' | 'var'; declare?: boolean; }
问题背景
目前已有的 GPGPU 实践从使用者角度可以分成两类:
Tensorflow.js 使用了第一种方式,开发者不需要关心 GLSL 语法:
类似的还有 P4、P5 这样的图表库,开发者在使用 data transform 时也不需要关心这些操作实际上是在 GPU 端执行的:
但有些类型的计算任务只能通过用户编程式描述,无法通过 API 组合完成,例如:
此时就需要提供一种开发者更容易接受的 DSL,GPGPU 库在运行时完成转译。
DSL 选择
GPU.js 选择 JS/TS 作为 DSL,在运行时传入
function.toString()
通过 acorn 生成 AST,最终根据目标平台生成 WebGL 1/2 的 Shader 代码。这种做法的优点很明显,前端开发者很容易上手。但局限性同样存在:
toString()
结果会受babel
等转译工具的影响,需要做额外处理。因此我们需要选择一种强类型的 DSL。Stardust 选择了在 TS 的语法基础上进行扩展。
另外,Stardust 也解决了和渲染结合(sanddance 效果)的问题:
我们的实现
我们优先解决布局计算的问题,在这个场景下并不涉及和渲染结合的问题,即只考虑 Compute Shader 的实现。
DSL 定义
数据类型
https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)
标量:
double
不支持(考虑 WebGL 兼容性)let a: int = 10; // -> int a = 10;
let a: uint = 10; // -> uint a = 10;
let a = true; // 类型自动推导 bool a = true;
// 默认自动推导 let b = [1, 1, 1]; // -> vec3 b = vec3(1.0, 1.0, 1.0);
let b = [true, true]; // -> bvec2 b = bvec2(true, true);
// 报错 let b = [true, 1];
// 显式声明类型 let b: ivec4 = [1, 1, 1, 1]; // -> ivec4 b = ivec4(1, 1, 1, 1);
let b = [1, 2, 2]; let c = b.xxy; let d = b.rgb; // -> vec3 b = vec3(1.0, 2.0, 2.0); vec3 c = b.xxy; vec3 d = b.rgb;
let b: mat4; // -> mat4 b;
const SPEED_DIVISOR = 800.0;
define SPEED_DIVISOR 800.0
interface Light { eyePosOrDir: vec3; isDirectional: bool; } const variableName: Light;
struct Light { vec3 eyePosOrDir; bool isDirectional; } variableName;
function a() {}
export default compute() {}
// -> function a() {}
void main() {}
// -> WebGPU uint threadID = gl_GlobalInvocationID.x;
// -> WebGL uniform float u_TexSize; varying vec2 v_TexCoord; uint threadID = uint(floor(v_TexCoord.s * u_TexSize + 0.5));
import { getData, setData } from 'g-webgpu';
export default function compute(threadID: uint) { let node = getData(threadID); setData(threadID, [1, 1, 1, 1]); }
// -> WebGPU GLSL 4.5 function getData(i) { return u_Data[i]; } function setData(i, data) { u_Data[i] = data; } void main() { uint threadID = gl_GlobalInvocationID.x; vec4 node = getData(threadID); setData(threadID, vec4(1.0)); }
// -> WebGL GLSL 1.0 function getData(i) { return texture2D(u_Data, vec2((float(i) + 0.5) / u_TexSize , 1)); } void main() { uint threadID = uint(floor(v_TexCoord.s * u_TexSize + 0.5)); vec4 node = getData(threadID); gl_FragColor = vec4(1.0); }
interface Params { float k; float k2; } const params: Params;
// -> WebGPU GLSL 4.5 layout(std140, set = 0, binding = 1) uniform Params { float k; float k2; } params;
// -> WebGL GLSL 1.0 struct Params { float k; float k2; } params; uniform Params params;
Math.sin() // -> sin()
for (int j = 0; j < PARTICLE_NUM; j++) {