diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..38d603c --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,4 @@ +*.pyc +.DS_Store +node_modules +output.png diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..cccfee5 --- /dev/null +++ b/example/README.md @@ -0,0 +1,5 @@ +# Example for node-webgpu + +* cd to this +* `npm ci` +* `node example.js` diff --git a/example/example.js b/example/example.js new file mode 100644 index 0000000..5236712 --- /dev/null +++ b/example/example.js @@ -0,0 +1,92 @@ +import fs from 'node:fs'; +import { PNG } from 'pngjs'; +import { create, globals } from 'node-webgpu'; + +Object.assign(globalThis, globals); +globalThis.navigator = { gpu: create([]) }; + +const adapter = await navigator.gpu?.requestAdapter(); +const device = await adapter?.requestDevice(); + +const module = device.createShaderModule({ + code: ` + @vertex fn vs( + @builtin(vertex_index) vertexIndex : u32 + ) -> @builtin(position) vec4f { + let pos = array( + vec2f( 0.0, 0.5), + vec2f(-0.5, -0.5), + vec2f( 0.5, -0.5), + ); + + return vec4f(pos[vertexIndex], 0.0, 1.0); + } + + @fragment fn fs() -> @location(0) vec4f { + return vec4f(1, 0, 0, 1); + } + `, +}); + +const pipeline = device.createRenderPipeline({ + layout: 'auto', + vertex: { module }, + fragment: { module, targets: [{ format: 'rgba8unorm' }] }, +}); + +const texture = device.createTexture({ + format: 'rgba8unorm', + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + size: [256, 256], +}); + +const align = (v, alignment) => Math.ceil(v / alignment) * alignment; + +const bytesPerRow = align(texture.width * 4, 256); +const buffer = device.createBuffer({ + size: bytesPerRow * texture.height, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, +}); + +const encoder = device.createCommandEncoder(); +const pass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: texture.createView(), + clearValue: [0.3, 0.3, 0.3, 1], + loadOp: 'clear', + storeOp: 'store', + }, + ], +}); +pass.setPipeline(pipeline); +pass.draw(3); +pass.end(); +encoder.copyTextureToBuffer( + { texture }, + { buffer, bytesPerRow }, + [ texture.width, texture.height ], +); +const commandBuffer = encoder.finish(); +device.queue.submit([commandBuffer]); + +await buffer.mapAsync(GPUMapMode.READ); +const rawPixelData = buffer.getMappedRange(); + +const png = new PNG({ + width: texture.width, + height: texture.height, + filterType: -1, +}); + +const dstBytesPerRow = texture.width * 4; +for (let y = 0; y < texture.height; y++) { + const dst = (texture.width * y) * 4; + const src = (y * bytesPerRow); + png.data.set(new Uint8Array(rawPixelData, src, dstBytesPerRow), dst); +} + +// Write the PNG to a file +fs.writeFileSync('output.png', PNG.sync.write(png, {colorType: 6})); +console.log('PNG file has been saved as output.png'); +process.exit(0); diff --git a/example/package-lock.json b/example/package-lock.json new file mode 100644 index 0000000..f1a98b9 --- /dev/null +++ b/example/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "example", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "example", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "node-webgpu": "^0.0.6", + "pngjs": "^7.0.0" + } + }, + "node_modules/node-webgpu": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/node-webgpu/-/node-webgpu-0.0.6.tgz", + "integrity": "sha512-FfLyotmDI3JjHogEf5SfB2Eem9OFPy7bkQ90NIeFO0hRoMx9NraRPc0O3SJG3/Vk7JBEVlGSUGMEjr8w4K4IVg==" + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "engines": { + "node": ">=14.19.0" + } + } + } +} diff --git a/example/package.json b/example/package.json new file mode 100644 index 0000000..0eedcfb --- /dev/null +++ b/example/package.json @@ -0,0 +1,16 @@ +{ + "name": "example", + "version": "1.0.0", + "description": "example using node-webgpu", + "main": "example.js", + "type": "module", + "scripts": { + "start": "node example.js" + }, + "author": "", + "license": "MIT", + "dependencies": { + "node-webgpu": "^0.0.6", + "pngjs": "^7.0.0" + } +}