if (!navigator.gpu) { throw new Error("WebGPU not supported on this browser."); } const adapter = await navigator.gpu.requestAdapter(); if (!adapter) { throw new Error("No WebGPU adapter"); } const device = await adapter.requestDevice(); const canvas = document.querySelector("canvas"); canvas.width = canvas.clientWidth canvas.height = canvas.clientHeight const context = canvas.getContext("webgpu"); const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, }); const encoder = device.createCommandEncoder(); const renderPass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(), loadOp: "clear", clearValue: { r: 0.9, g: 0.5, b: 0.2, a: 1 }, storeOp: "store", }] }); renderPass.end(); const commandBuffer = encoder.finish(); device.queue.submit([commandBuffer]); const cubeResponse = await fetch("obj/cube.bin"); const cubeObj = await cubeResponse.arrayBuffer(); const cubeView = new DataView(cubeObj); const indexCount = cubeView.getUint32(0, true); const indexBufferOffset = cubeView.getUint32(4, true); const indexBufferSize = cubeView.getUint32(8, true); const vertexBufferOffset = cubeView.getUint32(12, true); const vertexBufferSize = cubeView.getUint32(16, true); console.log(`indexBufferOffset ${indexBufferOffset} indexBufferSize ${indexBufferSize}`); console.log(`vertexBufferOffset ${vertexBufferOffset} vertexBufferSize ${vertexBufferSize}`); const cubeIndexCount = indexBufferSize / 2; const cubeVerticesStride = 36; const indexVertexBuffer = device.createBuffer({ label: "cell vertices", size: indexBufferSize + vertexBufferSize, usage: GPUBufferUsage.INDEX | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, }); device.queue.writeBuffer(indexVertexBuffer, 0, cubeObj, indexBufferOffset, indexBufferSize); device.queue.writeBuffer(indexVertexBuffer, indexBufferSize, cubeObj, vertexBufferOffset, vertexBufferSize); const vertexBufferLayout = { arrayStride: cubeVerticesStride, attributes: [{ format: "float32x3", offset: 0, shaderLocation: 0, }, { format: "float32x2", offset: 12, shaderLocation: 1, }, { format: "float16x4", offset: 20, shaderLocation: 2, }, { format: "float16x4", offset: 28, shaderLocation: 3, }], stepMode: "vertex", }; function getPath(path) { return fetch(path).then((response) => { if (!response.ok) { throw new Error(`${path}: ${response.status}`) } return response.blob() }).then((blob) => { return blob.text() }) } const cellShaderModule = device.createShaderModule({ label: "cell shader", code: await getPath("index.wgsl") }); const bindGroupLayout = device.createBindGroupLayout({ label: "bind group layout", entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE, buffer: { type: "uniform" } }, { binding: 1, visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } }, { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } }] }); const pipelineLayout = device.createPipelineLayout({ label: "pipeline layout", bindGroupLayouts: [ bindGroupLayout ], }); const cellPipeline = device.createRenderPipeline({ label: "cell pipeline", layout: pipelineLayout, vertex: { module: cellShaderModule, entryPoint: "vertexMain", buffers: [vertexBufferLayout], }, fragment: { module: cellShaderModule, entryPoint: "fragmentMain", targets: [{ format: canvasFormat, }] }, primitive: { topology: 'triangle-list', }, depthStencil: { depthWriteEnabled: true, depthCompare: 'less', format: 'depth24plus', }, }); const computeShaderModule = device.createShaderModule({ label: "compute shader", code: await getPath("compute.wgsl") }); const computePipeline = device.createComputePipeline({ label: "compute pipeline", layout: pipelineLayout, compute: { module: computeShaderModule, entryPoint: "computeMain", }, }); const gridSize = 32; const workGroupSize = 16; //const uniformArray2 = new Float32Array([gridSize, gridSize, 1, 1]); const memory = new WebAssembly.Memory({ initial: 1 }); const uniformArray = new Float32Array(memory.buffer); uniformArray[0] = gridSize; uniformArray[1] = gridSize; uniformArray[2] = 1; uniformArray[3] = 1; const uniformArraySize = 4 * 4 + 4 * 4 * 4; const rotate = await WebAssembly.instantiateStreaming(fetch("rotate.wasm"), { env: { memory: memory } }); const uniformBuffer = device.createBuffer({ label: "grid uniform", size: uniformArraySize, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); device.queue.writeBuffer(uniformBuffer, 0, uniformArray, 0, uniformArraySize / 4); const cellStateArray = new Uint32Array(gridSize * gridSize); const cellStateStorage = [ device.createBuffer({ label: "cell state 0", size: cellStateArray.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, }), device.createBuffer({ label: "cell state 1", size: cellStateArray.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, }), ]; for (let i = 0; i < cellStateArray.length; i += 1) { cellStateArray[i] = Math.random() > 0.6 ? 1 : 0; } device.queue.writeBuffer(cellStateStorage[0], 0, cellStateArray); device.queue.writeBuffer(cellStateStorage[1], 0, cellStateArray); const bindGroups = [ device.createBindGroup({ label: "cell bind group 0", layout: bindGroupLayout, entries: [{ binding: 0, resource: { buffer: uniformBuffer }, }, { binding: 1, resource: { buffer: cellStateStorage[0] }, }, { binding: 2, resource: { buffer: cellStateStorage[1] }, }], }), device.createBindGroup({ label: "cell bind group 1", layout: bindGroupLayout, entries: [{ binding: 0, resource: { buffer: uniformBuffer }, }, { binding: 1, resource: { buffer: cellStateStorage[1] }, }, { binding: 2, resource: { buffer: cellStateStorage[0] }, }], }) ]; let tick = 0; let which = 0; function createDepthTexture() { return device.createTexture({ size: [canvas.width, canvas.height], format: 'depth24plus', usage: GPUTextureUsage.RENDER_ATTACHMENT, }); } var depthTexture = undefined; function render() { if (canvas.clientWidth !== canvas.width || canvas.clientWidth !== canvas.height) { canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; if (depthTexture !== undefined) { depthTexture.destroy(); } depthTexture = createDepthTexture(); } const encoder = device.createCommandEncoder(); const computePass = encoder.beginComputePass(); computePass.setPipeline(computePipeline); computePass.setBindGroup(0, bindGroups[which]); const workgroupCount = Math.ceil((gridSize * gridSize) / workGroupSize); computePass.dispatchWorkgroups(workgroupCount); computePass.end(); const aspect = canvas.width / canvas.height; rotate.instance.exports.rotate(tick * 0.01, aspect, 4 * 4); tick += 1; if ((tick % 5) == 0) { which ^= 1; } if (canvas.width > canvas.height) { uniformArray[2] = aspect; uniformArray[3] = 1; } else { uniformArray[2] = 1; uniformArray[3] = 1 / aspect; } device.queue.writeBuffer(uniformBuffer, 0, uniformArray, 0, uniformArraySize / 4); const renderPass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(), loadOp: "clear", clearValue: { r: 0.2, g: 0.2, b: 0.4, a: 1 }, storeOp: "store", }], depthStencilAttachment: { view: depthTexture.createView(), depthClearValue: 1.0, depthLoadOp: 'clear', depthStoreOp: 'store', }, }); renderPass.setPipeline(cellPipeline); const vertexOffset = indexBufferSize; renderPass.setVertexBuffer(0, indexVertexBuffer, vertexOffset, vertexBufferSize); renderPass.setIndexBuffer(indexVertexBuffer, "uint16", 0, indexBufferSize); renderPass.setBindGroup(0, bindGroups[which]); //renderPass.draw(cubeVerticesCount, gridSize * gridSize); renderPass.drawIndexed(cubeIndexCount, gridSize * gridSize); //renderPass.drawIndexed(cubeIndexCount); renderPass.end(); const commandBuffer = encoder.finish(); device.queue.submit([commandBuffer]); requestAnimationFrame(render) } requestAnimationFrame(render)