{"id":19218219,"url":"https://github.com/greggman/webgpu-utils","last_synced_at":"2025-05-15T20:00:44.400Z","repository":{"id":140563876,"uuid":"566085429","full_name":"greggman/webgpu-utils","owner":"greggman","description":"Some helpers for webgpu","archived":false,"fork":false,"pushed_at":"2025-04-11T03:28:09.000Z","size":13477,"stargazers_count":234,"open_issues_count":2,"forks_count":12,"subscribers_count":5,"default_branch":"dev","last_synced_at":"2025-05-13T04:11:14.650Z","etag":null,"topics":["webgpu"],"latest_commit_sha":null,"homepage":"https://greggman.github.io/webgpu-utils/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/greggman.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELIST.md","contributing":null,"funding":null,"license":"LICENSE.md","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":"2022-11-14T23:50:19.000Z","updated_at":"2025-04-13T17:17:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"360e811b-8bf5-430a-8671-e21988aa5602","html_url":"https://github.com/greggman/webgpu-utils","commit_stats":null,"previous_names":[],"tags_count":58,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/greggman%2Fwebgpu-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/greggman%2Fwebgpu-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/greggman%2Fwebgpu-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/greggman%2Fwebgpu-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/greggman","download_url":"https://codeload.github.com/greggman/webgpu-utils/tar.gz/refs/heads/dev","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254414456,"owners_count":22067262,"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":["webgpu"],"created_at":"2024-11-09T14:25:46.034Z","updated_at":"2025-05-15T20:00:43.096Z","avatar_url":"https://github.com/greggman.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# webgpu-utils\n\n![](https://img.shields.io/npm/v/webgpu-utils)\n\n## Docs\n\nSee [here](https://greggman.github.io/webgpu-utils/docs)\n\n* [ChangeList](https://greggman.github.io/webgpu-utils/CHANGELIST.html)\n* [Migration Notes](https://greggman.github.io/webgpu-utils/migration.html)\n\n## Random useful things for WebGPU\n\nAs I do more WebGPU I find I need more and more helpers to make things\nless tedious. These are the result. I expect I'll add more over time.\n\n### Easily set Uniforms (based on your WGSL structs/types)\n\nExample:\n\n```js\nimport {\n  makeShaderDataDefinitions,\n  makeStructuredView,\n} from 'webgpu-utils';\n\nconst code = `\nstruct MyUniforms {\n   color: vec4f,\n   brightness: f32,\n   kernel: array\u003cf32, 9\u003e,\n   projectionMatrix: mat4x4f,\n};\n@group(0) @binding(0) var\u003cuniform\u003e myUniforms: MyUniforms;\n`;\n\nconst defs = makeShaderDataDefinitions(code);\nconst myUniformValues = makeStructuredView(defs.uniforms.myUniforms);\n\n// create the correct sized buffer\nconst uniformBuffer = device.createBuffer({\n  size: myUniformValues.arrayBuffer.byteLength,\n  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n});\n\n// Set some values via set\nmyUniformValues.set({\n  color: [1, 0, 1, 1],\n  brightness: 0.8,\n  kernel: [\n     1, 0, -1,\n     2, 0, -2,\n     1, 0, -1,\n  ],\n});\n\n// Set a value by passing it to a math library\nmat4.perspective(\n    degToRad(45),\n    canvas.clientWidth / canvas.clientHeight,\n    0.1,\n    20,\n    myUniformValues.views.projectionMatrix);\n\n// Upload the data to the GPU\ndevice.queue.writeBuffer(uniformBuffer, 0, myUniformValues.arrayBuffer);\n```\n\nSee [makeStructuredView](https://greggman.github.io/webgpu-utils/docs/functions/makeStructuredView.html) for details.\n\n### Load an image URL as a texture (with mips)\n\n```js\nimport { createTextureFromImage } from 'webgpu-utils';\n\nconst texture = await createTextureFromImage(device, 'https://someimage.url', {\n  mips: true,\n  flipY: true,\n});\n```\n\n### Load a canvas/video/ImageBitmap as a texture (with mips)\n\n```js\nimport { createTextureFromSource } from 'webgpu-utils';\n\nconst texture = createTextureFromSource(device, someCanvasVideoImageBitmap, {\n  mips: true,\n  flipY: true,\n});\n```\n\n### Load 6 images as a cubemap (with mips)\n\n```js\nimport { createTextureFromImage } from 'webgpu-utils';\n\nconst texture = await createTextureFromImages(device, [\n  'images/yokohama/posx.jpg',\n  'images/yokohama/negx.jpg',\n  'images/yokohama/posy.jpg',\n  'images/yokohama/negy.jpg',\n  'images/yokohama/posz.jpg',\n  'images/yokohama/negz.jpg',\n], {\n  mips: true,\n});\n```\n\n### Load data as a texture\n\n```js\nimport { createTextureFromSource } from 'webgpu-utils';\n\nconst r = [255,   0,   0, 255];\nconst g = [  0, 255,   0, 255];\nconst b = [  0,   0, 255, 255];\nconst y = [255, 255,   0, 255];\n\n// if no width or height is passed, then assumes data is rgba8unorm\n// if sqrt(numPixels) is in then makes a square. Otherwise Nx1\nconst data2x2 = [ r, g, b, y ].flat();\nconst texture2x2 = createTextureFromSource(device, data2x2, {\n  mips: true,\n});\n```\n\n```js\nconst data4x1 = {\n  data: [ r, g, b, y ].flat();\n  width: 4,\n};\nconst texture4x1 = createTextureFromSource(device, data2x2, {\n  mips: true,\n});\n```\n\n```js\nconst singlePixelWhiteTexture = createTextureFromSource(\n    device, [255, 255, 255, 255]);\n```\n\n```js\nconst rg16sint2x2 = [\n  1,2  3,4,\n  5,6, 7,8,\n];\nconst rg16Texture2x2 = createTextureFromSource(\n  device, rg16sint2x2, { format: 'rg16sint' });\n```\n\nAll data above can be a TypedArray\n\n```js\nconst singlePixelRedTexture = createTextureFromSource(\n    device, new Uint8Array[255, 0, 0, 255]);\n```\n\n### Generate mips on an existing texture\n\n```js\nimport { numMipLevels, generateMipmap } from 'webgpu-utils';\n\nconst size = [8, 8, 1];\nconst texture = device.createTexture({\n  size,\n  mipLevelCount: numMipLevels(size);\n  format: 'rgba8unorm',\n  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT\n});\n\n... do whatever you do to fill out the mip level 0 ...\n\ngenerateMipmap(device, texture);\n```\n\n### Create Buffers and attributes (interleaved)\n\n```js\nimport { numMipLevels, generateMipmap } from 'webgpu-utils';\n\nconst bi = wgh.createBuffersAndAttributesFromArrays(device, {\n  position: [1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1],\n  normal: [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1],\n  texcoord: [1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1],\n  indices: [0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23],\n});\n\nconst pipeline = device.createRenderPipeline({\n  layout: 'auto',\n  vertex: {\n    module,\n    entryPoint: 'myVSMain',\n    buffers: bi.bufferLayouts,  // \u003c---\n  },\n  ...\n});\n\n// at render time\npassEncoder.setVertexBuffer(0, bi.buffers[0]);\npassEncoder.setIndexBuffer(bi.indexBuffer, bi.indexFormat);\npassEncoder.drawIndexed(bi.numElements);\n```\n\n### Create `GPUBindGroupLayoutDescriptors` from WGSL code\n\n```js\nimport {\n  makeShaderDataDefinitions,\n  makeBindGroupLayoutDescriptors,\n} from 'webgpu-utils';\n\nconst code = `\n@group(0) @binding(0) var\u003cuniform\u003e mat: mat4x4f;\n\nstruct MyVSOutput {\n  @builtin(position) position: vec4f,\n  @location(1) texcoord: vec2f,\n};\n\n@vertex\nfn myVSMain(v: MyVSInput) -\u003e MyVSOutput {\n  var vsOut: MyVSOutput;\n  vsOut.position = mat * v.position;\n  vsOut.texcoord = v.texcoord;\n  return vsOut;\n}\n\n@group(0) @binding(2) var diffuseSampler: sampler;\n@group(0) @binding(3) var diffuseTexture: texture_2d\u003cf32\u003e;\n\n@fragment\nfn myFSMain(v: MyVSOutput) -\u003e @location(0) vec4f {\n  return textureSample(diffuseTexture, diffuseSampler, v.texcoord);\n}\n`;\n\nconst module = device.createShaderModule({code});\nconst defs = wgh.makeShaderDataDefinitions(code);\n\nconst pipelineDesc = {\n  vertex: {\n    module,\n    entryPoint: 'myVSMain',\n    buffers: bufferLayouts,\n  },\n  fragment: {\n    module,\n    entryPoint: 'myFSMain',\n    targets: [\n      {format: presentationFormat},\n    ],\n  },\n};\n\nconst descriptors = wgh.makeBindGroupLayoutDescriptors(defs, pipelineDesc);\nconst group0Layout = device.createBindGroupLayout(descriptors[0]);\nconst layout = device.createPipelineLayout({\n  bindGroupLayouts: [group0Layout],\n});\nconst pipeline = device.createRenderPipeline({\n  layout,\n  ...pipelineDesc,\n});\n```\n\n## Examples:\n\n* [Cube](examples/cube.html)\n* [2d-array](examples/cube.html)\n* [Cube-map](examples/cube-map.html)\n* [Instancing](examples/instancing.html)\n* [Instancing 2](examples/instancing-size-only.html)\n\n## Notes about structured data\n\n### The first level of an array of intrinsic types is flattened by default.\n\nExample:\n\n```js\nconst code = `\n@group(0) @binding(0) var\u003cuniform\u003e uni1: array\u003cvec3f, 4\u003e;\n@group(0) @binding(1) var\u003cuniform\u003e uni2: array\u003carray\u003cvec3f, 3\u003e, 4\u003e;\n`;\nconst defs = makeShaderDataDefinitions(code);\nconst uni1 = makeStructuredView(defs.uniforms.uni1);\nconst uni2 = makeStructuredView(defs.uniforms.uni2);\n\nuni1.set([\n  1, 2, 3, 0,  // uni1[0]\n  4, 5, 6, 0,  // uni1[1]\n  //...\n]);\n\nuni2.set([\n  [\n    1, 2, 3, 0,  // uni2[0][0],\n    4, 5, 6, 0,  // uni2[0][1],\n  ],\n  ,  // uni2[1]\n  [\n    7, 8, 9, 0,  // uni2[2][0],\n    4, 5, 6, 0,  // uni2[2][1],\n  ],\n]);\n```\n\nThe reason it's this way is it's common to make large arrays of `f32`, `u32`,\n`vec2f`, `vec3f`, `vec4f` etc. We wouldn't want every element of an array to\nhave its own typedarray view.\n\nYou can configure this per type by calling `setIntrinsicsToView`.\nThe configuration is global. Given th example above\n\n```js\nconst code = `\n@group(0) @binding(0) var\u003cuniform\u003e uni1: array\u003cvec3f, 4\u003e;\n@group(0) @binding(1) var\u003cuniform\u003e uni2: array\u003carray\u003cvec3f, 3\u003e, 4\u003e;\n`;\nconst defs = makeShaderDataDefinitions(code);\nsetIntrinsicsToView(['vec3f']);\nconst uni1 = makeStructuredView(defs.uniforms.uni1);\n\nuni1.set([\n  [1, 2, 3],  // uni1[0]\n  [4, 5, 6],  // uni1[1]\n  ...\n]);\n```\n\nOr to put it another way, in the default case, `uni1.views is a Float32Array(16)`.\nIn the 2nd case it's an array of 4 `Float32Array` each 3 elements big\n\n### arrays of intrinsics can be set by arrays of arrays\n\n```js\nconst code = `\n@group(0) @binding(0) var\u003cuniform\u003e uni1: array\u003cvec2f, 4\u003e;\n`;\nconst defs = makeShaderDataDefinitions(code);\nconst uni1 = makeStructuredView(defs.uniforms.uni1);\n\nuni1.set([\n  [1, 2],  // uni1[0]\n  [3, 4],  // uni1[1]\n]);\n```\n\nCurrently this requires the length of each subarray to match the length of\nthe intrinsic. The reason being, there is no type data used in `uni1.set` so\nthere is nothing to tell it that it's a `vec2f`. In this case, it just advances\nwhere it's writing by the length of the source data sub arrays.\n\n### for unsized arrays you must pass in your own arrayBuffer\n\nThe reason is an unsized array's size is defined to WebGPU by its buffer binding\nsize. That information is provided at runtime so there's no way for webgpu-utils\nto know the size. The solution is you pass in an `ArrayBuffer`.\n\nExample:\n\n```js\nconst code = `\n@group(0) @binding(0) var\u003cstorage\u003e buf1: array\u003cvec3f\u003e;  // unsized array\n`;\nconst defs = makeShaderDataDefinitions(code);\nconst buf1 = makeStructuredView(defs.storages.buf1, new ArrayBuffer(4 * 16));\n\n// buf1.views will be a Float32Array representing 4 vec3fs\n```\n\nNote: If you have a complex array element type you can call\n`getSizeAndAlignmentOfUnsizedArrayElement` to get its size. Example:\n\n```js\nconst code = `\nstruct Light {\n  intensity: f32,\n  direction: vec3f,\n};\n@group(0) @binding(7) var\u003cstorage\u003e lights: array\u003cLight\u003e;\n`;\nconst defs = makeShaderDataDefinitions(code);\nconst {size} = getSizeAndAlignmentOfUnsizedArrayElement(defs.storages.lights);\nconst numLights = 4;\nconst buf1 = makeStructuredView(\n    defs.storages.lights, new ArrayBuffer(numLights * size));\n```\n\nSimilarly if you are using an unsized array as the last member of a struct\nyou might do this\n\n```js\nconst code = `\nstruct Kernel {\n  amount: f32,\n  entries: array\u003cvec3f\u003e,\n};\n@group(0) @binding(7) var\u003cstorage\u003e conv: Kernel;\n`;\nconst defs = makeShaderDataDefinitions(code);\nconst {size: elemSize} = getSizeAndAlignmentOfUnsizedArrayElement(defs.storages.conv);\nconst numKernelEntries = 4;\nconst size = defs.storages.conv.size + numKernelEntries * elemSize;\nconst buf1 = makeStructuredView(\n    defs.storages.conv, new ArrayBuffer(size));\n)\n```\n\n## Usage\n\n* include from the net\n\n```js\nimport { createTextureFromImage } from 'https://greggman.github.io/webgpu-utils/dist/1.x/webgpu-utils.module.js'\n\n...\n```\n\n* [Live Example 1](https://jsgist.org/?src=30dfc8cb81777219dd9e91d9471452d0)\n\n* npm\n\n```sh\nnpm install webgpu-utils\n```\n\n```js\nimport { createTextureFromImage } from 'webgpu-utils';\n\n...\n```\n\n## \u003ca id=\"examples\"\u003e\u003c/a\u003e Examples\n\n* [2d-array texture](examples/2d-array.html)\n* [cube](examples/cube.html)\n* [cube-map](examples/cube-map.html)\n* [instancing](examples/instancing.html)\n* [primitives](examples/primitives.html)\n* [reverse-z](examples/reverse-z.html)\n* [stencil](examples/stencil.html)\n* [stencil-cube](examples/stencil-cube.html)\n\n## Development\n\n```\ngit clone https://github.com/greggman/webgpu-utils.git\ncd webgpu-utils\nnpm ci\nnpm start\n```\n\nThis will run rollup in watch mode, building from typescript into\n`dist/1.x/webgpu-utils.js` and start a server\n\nNow open [`http://localhost:8080/test/`](http://localhost:8080/test/) to run tests.\n\n## Thanks\n\nSuper thanks to Brendan Duncan for [wgsl-reflect](https://github.com/brendan-duncan/wgsl_reflect) on which much of this is based.\n\n## License\n\n[MIT](LICENSE.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgreggman%2Fwebgpu-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgreggman%2Fwebgpu-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgreggman%2Fwebgpu-utils/lists"}