{"id":25974605,"url":"https://github.com/eddow/webgpgpu","last_synced_at":"2025-03-05T02:32:01.428Z","repository":{"id":278414084,"uuid":"935239089","full_name":"eddow/webgpgpu","owner":"eddow","description":"WebGPU usage for compute-shaders automation","archived":false,"fork":false,"pushed_at":"2025-02-26T16:57:07.000Z","size":482,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-26T17:42:07.172Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eddow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-19T06:00:35.000Z","updated_at":"2025-02-26T16:57:11.000Z","dependencies_parsed_at":"2025-02-19T17:33:56.956Z","dependency_job_id":null,"html_url":"https://github.com/eddow/webgpgpu","commit_stats":null,"previous_names":["eddow/webgpgpu"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eddow%2Fwebgpgpu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eddow%2Fwebgpgpu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eddow%2Fwebgpgpu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eddow%2Fwebgpgpu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eddow","download_url":"https://codeload.github.com/eddow/webgpgpu/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241953550,"owners_count":20048207,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-05T02:32:00.228Z","updated_at":"2025-03-05T02:32:01.411Z","avatar_url":"https://github.com/eddow.png","language":"TypeScript","funding_links":[],"categories":["Libraries"],"sub_categories":["Safari"],"readme":"TODO:\n- no more defaults\n- batch\n\n![npm](https://img.shields.io/npm/v/webgpgpu.ts)\n\n# WebGpGpu\n\nThis package provides WebGPU based GPU computing.\n\nversions:\n - 0.0.x: alpha\n\n## Getting Started\n\n### Installation\n\n```bash\nnpm install --save webgpgpu.ts\n```\n\n### Usage\n\n```ts\nimport createWebGpGpu, { f32 } from 'webgpgpu.ts'\n\nasync function main() {\n\tconst webGpGpu = await createWebGpGpu()\n\n\tconst kernel = webGpGpu\n\t\t.input({\n\t\t\tmyUniform: f32,\n\t\t\tdata: f32.array('threads.x')\n\t\t})\n\t\t.output({ produced: f32.array('threads.x') })\n\t\t.kernel(/*wgsl*/`\n\tproduced[thread.x] = myUniform * data[thread.x];\n\t\t`)\n\n\tconst { produced } = await kernel({\n\t\tmyUniform: 2,\n\t\tdata: [1, 2, 3, 4, 5]\n\t})\n\t// produced -\u003e [2, 4, 6, 8, 10]\n}\n```\n\n### Presentation\n\nBasically, WebGpGpu manages purely `compute` shaders in order to make in-memory GPU computing possible.\n\nThe GPU parallelize loops that would be here standardized like\n```js\nfor (thread.x = 0; thread.x \u003c threads.x; thread.x++) {\n\tfor (thread.y = 0; thread.y \u003c threads.y; thread.y++) {\n\t\tfor (thread.z = 0; thread.z \u003c threads.z; thread.z++) {\n\t\t\t/* here */\n\t\t}\n\t}\n}\n```\n\nThe point of the library is to automatize the parallelization and all the configurations and concepts and learning curve that usually come with it.\nFor those who tried a bit, all the bindings, buffer writing/reading, and other things that are necessary to write a GPU program, are hidden from the user.\n\nWith real pieces of :\n- TypeScript, as the whole is highly typed.\n- Sizes assertion and even inference.\n- Optimizations\n  - buffer re-usage\n  - workgroup-size calculation\n  - `ArrayBuffer` optimization js-side (no superfluous read/writes, ...)\n  - etc.\n- Compatibility:\n  - browser: Many browsers still require some manipulation as WebGPU is not yet completely standardized\n  - node.js through the library [node-webgpu](https://github.com/dawn-gpu/node-webgpu)\n\n## WebGPU code\n\nExample kernel produced :\n```rust\n// #generated\n@group(0) @binding(0) var\u003cstorage, read\u003e a : array\u003cmat2x2f\u003e;\n@group(0) @binding(1) var\u003cstorage, read\u003e b : array\u003cmat2x2f\u003e;\n@group(0) @binding(2) var\u003cuniform\u003e threads : vec3u;\n@group(0) @binding(3) var\u003cstorage, read_write\u003e output : array\u003cmat2x2f\u003e;\n\n// #user-defined\n\nfn myFunc(a: mat2x2f, b: mat2x2f) -\u003e mat2x2f {\n\treturn a + b;\n}\n\n// #generated\n@compute @workgroup_size(256,1,1)\nfn main(@builtin(global_invocation_id) thread : vec3u) {\n\tif(all(thread \u003c threads)) {\n// #user-defined\n\n\t\toutput[thread.x] = myFunc(a[thread.x], b[thread.x]);\n\n// #generated\n\t}\n}\n```\n\nThe 2 reserved variables are `thread` (the `xyz` of the current thread) and `threads` (the size of all the threads). There is no workgroup interaction for now.\n\nPre-function code chunks can be added freely (the library never parses the wgsl code) and the content of the (guarded) main function as well\n\n## WebGpGpu class\n\nA main function allows to create a root `WebGpGpu`: `const webGpGpu = await createWebGpGpu()` that allows to create sub-instances by specification (the values are never modified as such), so each specification code indeed creates a new instance who is \"more specific\" than the parent.\n\n### kernel\n\nThis is the only non-chainable function : creates a kernel (in javascript, a function) that can be applied on the inputs. It takes the main code (the one of the main function) as argument.\n\n```ts\nconst kernel = webGpGpu\n\t...\n\t.kernel(/*wgsl*/`\noutput[thread.x] = a[thread.x] * b;\n\t`)\n```\n\n\u003e Note: The kernel function retrieves the *whole* generated code on `toString()`\n\n### define \u0026 import\n\nAdds a chunk of code to be inserted before the main function. Plays the role of `#define` and `#include`. They use a structure with optionals `declaration` and `computation`. The former is added outside the function, the latter inside the main function, before the main code\n\n- direct definition\n```ts\nwebGpGpu.define({\n\tdeclaration: /*wgsl*/`\nfn myFunc(a: f32, b: f32) -\u003e f32 { return a + b; }\n`\n\t})\n```\n\n- non-repeating usage\n`WebGpGpu` has a static property `imports` that is editable at will and just contain a named collection of code chunks. The function `webGpGpu.import(...)` can be used with the key of such import making sure the import will be included once.\n\n### workGroup\n\nIf you know what a workgroup is and really want to specify its size, do it here.\n\n```ts\nwebGpGpu.workGroup(8, 8)\n```\n\n## Bindings\n\nThese functions are shortcuts to [`Bindings`](./src/binding/README.md) creation and are chainable.\n\nExample of equivalence:\n```ts\nwebGpGpu.input({a: f32})\n\nwebGpGpu.bind(inputs/*-\u003eInputBindings*/({a: f32}))\n```\n\n### input\n\nDeclares inputs for the kernel. Takes an object `{name: type}`.\n\n```ts\nwebGpGpu.input({\n\tmyUniform: f32,\n\tdata: f32.array('threads.x'),\n\trandoms: i32.array(133)\n})\n```\n\n### output\n\nDeclares outputs for the kernel. Takes an object `{name: type}`.\n\n```ts\nwebGpGpu.output({\n\tproduced: f32.array('threads.x')\n})\n```\n\n### common\n\nDefines a common input value to all calls (and makes a unique transfer to the GPU)\n\n```ts\nconst kernel = webGpGpu\n\t.input({ b: f32.array('threads.x') })\n\t.common({ a: f32.array('threads.x').value([1, 2, 3]) })\n\t.output({ output: f32.array('threads.x') })\n\t.kernel('output[thread.x] = a[thread.x] + b[thread.x];')\nconst { output } = await kernel({b: [4, 5, 6]})\t// output ~= [5, 7, 9]\n```\n\n### infer \u0026 specifyInference\n\n`infer` allows to create an inference (cf. [Size inference](#size-inference) section).\n\n```ts\nwebGpGpu\n\t.infer({ myTableSize: [undefined, undefined] })\n\t.input({ myTable: f32.array('myTableSize.x', 'myTableSize.y') })\n```\n\nWith this code, the variable `myTableSize` will be a `vec2u` available in the wgsl code that will be fixed (here, when a `myTable` of a certain size will be given as argument)\n\nTo fix (assert) an existing inference, `specifyInference` can be used.\n```ts\nwebGpGpu.specifyInference({ 'myTableSize.x': 10 })\n```\n\n## Kernel\n\nThe kernel is the function that takes the input and returns (a `Promise` of) the output(s).\n\n```ts\nconst kernel = webGpGpu\n\t.input({ a: f32.array('threads.x'), b: f32.array('threads.x') })\n\t.output({ output: f32.array('threads.x') })\n\t.kernel('output[thread.x] = a[thread.x] + b[thread.x];')\nconst { output } = await kernel({ a: [1, 2, 3], b: [4, 5, 6] }) // output ~= [5, 7, 9]\n```\n\n### Calling\n\nThe kernel can take as a second argument an object containing defaults for the inferences. These values *will not* be forced/asserted and might not be used. See [Size inference](#size-inference) section.\n\n### Inputs\n\nInputs are given as an object `{name: value}`. Values can be either an `ArrayBufferLike` or\n- Their element if not an array (`D = 0`), like a number, a triplet of vector (depending on the type used)\n- An array of dimension `D - 1` inputs when it is an array of some dimension (`D \u003e 0`).\n\n### Outputs\n\nThe given values is a dynamic `ArrayBuffer`-reader that act as JS arrays. The `operator[](index: number)` is hacked in and the array interface will be forwarded.\n\n\u003e Note: There is no array creation so to speak while not specifically asked for, it all end up being an access to the underlying `ArrayBuffer`.\n\n## Types\n\nThe main types from wgsl are available with their wgsl name (`f32`, `vec2f`, etc.). Note: These are *values* who specify a wgsl *type* - it is not a typescript type. These  types (like `Input1D\u003c[number, number]\u003e`) are produced and used automatically (here, from a `vec2f.array(x)`).\n\nArguments (simple, arrays of any dimension) can always be passed as corresponding `ArrayBuffer`. So, `mat3x2f.array(5).value(Float32Array.from([...]))` is doing the job! (even if array sizes are still validated)\n\nTypes also specify how to read/write elements from/to an `ArrayBuffer`.\n \nFor convenience, these types have been added:\n- `Vector2`\n- `Vector3`\n- `Vector4`\n- `RGB`\n- `RGBA`\n\nThese actually encode/decode in order to use their respective interface, ex. `{x: number, y: number}` for `Vector2`.\nThese \"shaped\" types use `f16` for the precision\n\n### f16\n\n16-bit float is a thing in gpus and should be taken into account as it's a bit the \"native\" or \"optimized\" work size (important when working with mobile devices for ex). The big draw back is that *all devices don't support it*.\n\nHence, in order to know if it's supported, `webGpGpu.f16` tells if it exists and all the f16 types (`vec2h`, `vec3h` and `vec4h`) will be set to their `f32` equivalent until when the first `WebGpGpu` is ready and confirms their availability.\n\nThe system has not yet been completely tested and remains the question of writing f16 immediate values \u0026c.\n\n### \"Type\" objects\n\nThese types object offer (if needed) these functions. The functions changing the definition are chainable and have no side effect, they create a new type object from the original one and the given specifications.\n\n#### array\n\nDeclares an `array of` something. Ex:\n```ts\nf32.array(3)\nf32.array(3).array(4)\n//or\nf32.array(4, 3)\t// take care .array(X).array(Y) -\u003e .array(Y, X)\n```\n\nIn all array accesses in TS, the multi-dimensional indexes are given *most-important first*.\n- `f32.array(3).value([1, 2, 3])`\n- `f32.array(2, 3) -\u003e f32.array(3).array(2)` \n  - `value([[1, 2, 3], [4, 5, 6]]).at(1, 2) === 6`\n  - `value([[1, 2, 3], [4, 5, 6]]).slice(0) ~ [1, 2, 3]`\n- `f32.array(3, 2) -\u003e f32.array(2).array(3)`\n  - `value([[1, 2], [3, 4], [5, 6]]).at(2, 1) === 6`\n  - `value([[1, 2], [3, 4], [5, 6]]).slice(0) ~ [1, 2]`\n\nIn WGSL, a \"stride\" is computed and accessible in the whole code (as `var\u003cprivate\u003e` for now - 0.0.7) named after the wgsl name of the value (input/output/...) post-fixed with `Stride`\n\neg: \n```ts\ninput({ myTable: f32.array('threads.y', 'threads.x') }).kernel(...)\n```\ncan be indexed in the wgsl code with:\n```rust\nlet entry = myTable[dot(thread.yx, myTableStride)];\n```\n\n\u003e Note: It is advised to keep threads.x as the last (right-most, least-significant) index of the array.\n\n#### value\n\nJust creates a \"typed value\" (ex: `f32.value(1)`) that can be used as argument of many WebGpGpu functions.\n\nWays to give the value happen the same as for the [inputs](#inputs).\n\n\u003e Not chainable! A \"typed value\" is not a type. It wraps it as buffable in `{ buffable, value }`\n\n## Size inference\n\nOne inference exists in all computation: `threads: vec3u`, but others can be declared and used.\n\nWhen sizes are specified - bound as commons or given as inputs, an inference can be used - the WebGpGpu engine remembers an inferring status (what is known what is not), deduce from given arrays and assert sizes.\n\nIn the shader code, inferences can be used directly (they are declared in their `u32` shade) and the values will be provided as uniforms.\n\nInferences are meant to replace `arrayLength` and other mechanism. If really a random-size table has to be given and its size retrieved, this can be used:\n```ts\nwebGpGpu\n\t.infer({ myTableSize: [undefined, undefined] })\n\t.input({ myTable: f32.array('myTableSize.x', 'myTableSize.y') })\n```\nand `myTableSize` will be a provided `vec2u`.\n\n### Inference declarations and value forcing\n\ncf. [infer \u0026 specifyInference](#infer--specifyinference)\n\n### Inference defaulting\n\nIf inferences cannot be retrieved from an array size, they can be defaulted to a number (or will default to `1`) when defining or calling the kernel.\n\nNote: This defaulting system doesn't assert anything and will perhaps not even be taken into account if the value was already inferred. To force a value, use `.infer`.\n\nGenerate the N first squares:\n```ts\nconst kernel = webGpGpu\n\t.output({output: f32.array('threads.x')})\n\t.kernel('output[thread.x] = thread.x*thread.x;')\nconst { output } = await kernel({}, { 'threads.x': 10 })\n```\n\nGenerate the N(default 10) first squares:\n```ts\nconst kernel = webGpGpu\n\t.output({output: f32.array('threads.x')})\n\t.kernel('output[thread.x] = thread.x*thread.x;', { 'threads.x': 10 })\nconst { output } = await kernel({})\n```\n\n## System calls\n\n### Creation\n\nThe library exposes a function `createWebGpGpu` that creates a root `WebGpGpu` object.\n```ts\nfunction createWebGpGpu(\n\tadapterOptions?: GPURequestAdapterOptions,\n\tdeviceDescriptor?: GPUDeviceDescriptor,\n\t[...WebGPUOptions: string[]]\n)\n```\n\n#### Node.js only\n\nThe `WebGPUOptions` are only available to the node.js clients.\nThe library uses [node-webgpu](https://github.com/dawn-gpu/node-webgpu) who allows giving parameters when creating the GPU object. These parameters can be given to the default creation export.\n\n```ts\nimport createWebGpGpu from 'webgpgpu.ts'\n\nasync function main() {\n\tconst webGpGpu = createWebGpGpu({}, {}, 'enable-dawn-features=allow_unsafe_apis,dump_shaders,disable_symbol_renaming', ...)\n\t...\n}\n```\n\n#### Hand-made\n\nIf you manage to have your own adapter/device, want to share a device, ...\n`WebGpGpu` exposes :\n```ts\nclass WebGpGpu {\n\tstatic createRoot(device: GPUDevice, options?: { dispose?: () =\u003e void }): RootWebGpGpu\n\tstatic createRoot(\n\t\tadapter: GPUAdapter,\n\t\toptions?: { dispose?: (device: GPUDevice) =\u003e void; deviceDescriptor?: GPUDeviceDescriptor }\n\t): Promise\u003cRootWebGpGpu\u003e\n\tstatic createRoot(\n\t\tgpu: GPU,\n\t\toptions?: {\n\t\t\tdispose?: (device: GPUDevice) =\u003e void\n\t\t\tdeviceDescriptor?: GPUDeviceDescriptor\n\t\t\tadapterOptions?: GPURequestAdapterOptions\n\t\t}\n\t): Promise\u003cRootWebGpGpu\u003e\n\n\tget device(): GPUDevice\n\tdispose(): void\n}\n```\n\nNote: the `dispose` function disposes the all the WebGpGpu objects from the root (created by `createWebGpGpu` or `WebGpGpu.createRoot`)\n\n### Logging\n\n`WebGpGpu` exposes:\n```ts\nWebGpGpu.log: {\n\twarn(message: string): void,\n\terror(message: string): void,\n}\n```\n\n`warn` and `error` can be set separately to redirect the whole library logs. (mainly for compilation messages) or extreme cases as \"uploaded size(0) array\", ...\nNote that a `log.error` will always have its associated exception throw.\n\n## Exceptions\n\n- `CompilationError` Has the exact messages in the `cause` (they are also logged)\n- `ArraySizeValidationError` Occurs when arguments size are not fitting\n- `ParameterError` Mainly for parameter names conflicts \u0026c.\n\n## Ecosystem\n\n- Configured VSCode plugins:\n  - [karma test explorer](https://marketplace.visualstudio.com/items?itemName=lucono.karma-test-explorer)\n  - [mocha test explorer](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-mocha-test-adapter)\n- Other useful VSCode extensions:\n  - [inline-wgsl syntax highlighting](https://marketplace.visualstudio.com/items?itemName=ggsimm.wgsl-literal)\n\n### Ubuntu\n\nDo *not* use chromium, it will not support WebGPU - install chrome/firefox(untested)/...\n\n```bash\n# 1. Add Google Repository\necho \"deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main\" | \\\nsudo tee /etc/apt/sources.list.d/google-chrome.list\n\n# 2. Add Google Signing Key\nwget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg\n\n# 3. Update and Install\nsudo apt update\nsudo apt install google-chrome-stable\n\ngoogle-chrome\n```\n\n## TODOs \u0026 limitations\n\n### Limitations\n\nThe main limitation is WebGPU support.\n\nIt is supported in some browsers but poorly support automated testing.\n\nFor node, this library uses [node-webgpu](https://github.com/dawn-gpu/node-webgpu) who is really fresh and does not yet allow a smooth ride for all cases (automated testing is possible in some specific circumstances)\nFor instance, for now, a complete mocha testing run is impossible: some async fixture system:error or something else breaks - but tests are usable few by few\n\n### Roadmap\n\n- Structures and automatic organization for size optimization\n- UBO creation: for now, a single `f32` as input *is* an UBO. We need UBO (and their types) built automatically \n  - CODE PARSING! replace `myUniform` by `UBO0.myUniform`\n- Automatic array strides computations : now var\u003cprivate\u003e -\u003e uniform ?\n  - Code parsing: allow some operators for like `myArray[\u003cmyVec2Index\u003e]` -\u003e `myArray[dot(myVec2Index, myArrayStride)]`\n- Arrays position optimization:\n  - When possible, use fixed-size arrays (if size is completely inferred at layout time)\n  - If such happen, have stride object given as const, not uniform\n  - Check GPU limitations to have input arrays with fixed-size small enough given directly in the UBOs\n\n#### Parallel\n\n- Size assertion/inference when ArrayBuffers are provided directly as X-D inputs (X \u003e 1) \n- Make BufferReader more ArrayLike (iterator, array prototype forward, ...)\n- Code parsing: `f16` replacement: for immediate values:\n  - `###h` -\u003e `###f` (with `###` being a valid number)\n  - `vec2h`, `vec3h`, `vec4h` -\u003e `vec2f`, `vec3f`, `vec4f`: for now it happens in the JS declarations, so the generated code - but not if used directly in the code\n- Some wizardry à la gpu.js (js-\u003e wgsl) ?","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feddow%2Fwebgpgpu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feddow%2Fwebgpgpu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feddow%2Fwebgpgpu/lists"}