| if (!navigator.gpu || GPUBufferUsage.COPY_SRC === undefined) |
| document.body.className = "error"; |
| |
| const isChrome = navigator.userAgent.indexOf("Chrome") >= 0; |
| |
| const numParticles = 2; |
| const numThreadsInThreadGroup = 2; |
| const numThreadGroups = numParticles / numThreadsInThreadGroup; |
| |
| const computeShaderGLSL = `#version 450 |
| struct Particle { |
| vec2 pos; |
| vec2 vel; |
| }; |
| |
| layout(std140, set = 0, binding = 0) uniform SimParams { |
| float deltaT; |
| float rule1Distance; |
| float rule2Distance; |
| float rule3Distance; |
| float rule1Scale; |
| float rule2Scale; |
| float rule3Scale; |
| } params; |
| |
| layout(std140, set = 0, binding = 1) buffer ParticlesA { |
| Particle particles[${numParticles}]; |
| } particlesA; |
| |
| layout(std140, set = 0, binding = 2) buffer ParticlesB { |
| Particle particles[${numParticles}]; |
| } particlesB; |
| |
| layout(local_size_x = ${numThreadsInThreadGroup}, local_size_y = 1, local_size_z = 1) in; |
| |
| void main() { |
| |
| // https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp |
| |
| uint index = gl_GlobalInvocationID.x; |
| if (index >= ${numParticles}) { |
| return; |
| } |
| |
| vec2 vPos = particlesA.particles[index].pos; |
| vec2 vVel = particlesA.particles[index].vel; |
| |
| vec2 cMass = vec2(0.0, 0.0); |
| vec2 cVel = vec2(0.0, 0.0); |
| vec2 colVel = vec2(0.0, 0.0); |
| int cMassCount = 0; |
| int cVelCount = 0; |
| |
| //float rand = ${Math.random()}; |
| float rand = 0; |
| |
| vec2 pos; |
| vec2 vel; |
| for (int i = 0; i < ${numParticles}; ++i) { |
| if (i == index) { continue; } |
| pos = particlesA.particles[i].pos.xy; |
| vel = particlesA.particles[i].vel.xy; |
| |
| if (distance(pos, vPos) < params.rule1Distance) { |
| cMass += pos; |
| cMassCount++; |
| } |
| if (distance(pos, vPos) < params.rule2Distance) { |
| colVel -= (pos - vPos); |
| } |
| if (distance(pos, vPos) < params.rule3Distance) { |
| cVel += vel; |
| cVelCount++; |
| } |
| } |
| if (cMassCount > 0) { |
| cMass = cMass / cMassCount - vPos; |
| } |
| if (cVelCount > 0) { |
| cVel = cVel / cVelCount; |
| } |
| |
| vVel += cMass * params.rule1Scale + colVel * params.rule2Scale + cVel * params.rule3Scale; |
| |
| // clamp velocity for a more pleasing simulation. |
| vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1); |
| |
| // kinematic update |
| vPos += vVel * params.deltaT; |
| |
| // Wrap around boundary |
| if (vPos.x < -1.0) vPos.x = 1.0; |
| if (vPos.x > 1.0) vPos.x = -1.0; |
| if (vPos.y < -1.0) vPos.y = 1.0; |
| if (vPos.y > 1.0) vPos.y = -1.0; |
| |
| particlesB.particles[index].pos = vPos; |
| //particlesB.particles[index].vel = vVel; |
| particlesB.particles[index].vel = vVel + rand; |
| } |
| `; |
| |
| const computeShaderWHLSL = ` |
| struct Particle { |
| float2 pos; |
| float2 vel; |
| } |
| |
| struct SimParams { |
| float deltaT; |
| float rule1Distance; |
| float rule2Distance; |
| float rule3Distance; |
| float rule1Scale; |
| float rule2Scale; |
| float rule3Scale; |
| } |
| |
| [numthreads(${numThreadsInThreadGroup}, 1, 1)] |
| compute void main(device SimParams[] paramsBuffer : register(u0), device Particle[] particlesA : register(u1), device Particle[] particlesB : register(u2), float3 threadID : SV_DispatchThreadID) { |
| uint index = uint(threadID.x); |
| |
| SimParams params = paramsBuffer[0]; |
| |
| if (index >= ${numParticles}) { |
| return; |
| } |
| |
| float2 vPos = particlesA[index].pos; |
| float2 vVel = particlesA[index].vel; |
| |
| float2 cMass = float2(0.0, 0.0); |
| float2 cVel = float2(0.0, 0.0); |
| float2 colVel = float2(0.0, 0.0); |
| float cMassCount = 0.0; |
| float cVelCount = 0.0; |
| |
| //float rand = 25; |
| |
| float2 pos; |
| float2 vel; |
| for (uint i = 0; i < ${numParticles}; ++i) { |
| if (i == index) { continue; } |
| pos = particlesA[i].pos.xy; |
| vel = particlesA[i].vel.xy; |
| |
| if (distance(pos, vPos) < params.rule1Distance) { |
| cMass += pos; |
| cMassCount++; |
| } |
| if (distance(pos, vPos) < params.rule2Distance) { |
| colVel -= (pos - vPos); |
| } |
| if (distance(pos, vPos) < params.rule3Distance) { |
| cVel += vel; |
| cVelCount++; |
| } |
| } |
| if (cMassCount > 0.0) { |
| cMass = cMass / cMassCount - vPos; |
| } |
| if (cVelCount > 0.0) { |
| cVel = cVel / cVelCount; |
| } |
| |
| vVel += cMass * params.rule1Scale + colVel * params.rule2Scale + cVel * params.rule3Scale; |
| |
| // clamp velocity for a more pleasing simulation. |
| vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1); |
| |
| // kinematic update |
| vPos += vVel * params.deltaT; |
| |
| // Wrap around boundary |
| if (vPos.x < -1.0) vPos.x = 1.0; |
| if (vPos.x > 1.0) vPos.x = -1.0; |
| if (vPos.y < -1.0) vPos.y = 1.0; |
| if (vPos.y > 1.0) vPos.y = -1.0; |
| |
| particlesB[index].pos = vPos; |
| particlesB[index].vel = vVel; |
| } |
| `; |
| |
| async function setData(buffer, data) |
| { |
| if (isChrome) { |
| buffer.setSubData(0, data); |
| return; |
| } |
| |
| const bufferArrayBuffer = await buffer.mapWriteAsync(); |
| const array = new Float32Array(bufferArrayBuffer); |
| for (let i = 0; i < array.length; ++i) |
| array[i] = data[i]; |
| buffer.unmap(); |
| } |
| |
| function appendMessage(msg) { |
| let d = document.getElementById("results"); |
| let p = document.createElement('p'); |
| p.innerHTML = msg; |
| d.append(p); |
| } |
| |
| async function init() { |
| if (!isChrome) { |
| GPUBufferUsage.COPY_DST = GPUBufferUsage.TRANSFER_DST; |
| GPUBufferUsage.COPY_SRC = GPUBufferUsage.TRANSFER_SRC; |
| } |
| |
| const adapter = await navigator.gpu.requestAdapter(); |
| const device = await adapter.requestDevice(); |
| await Utils.ready; |
| |
| const canvas = document.querySelector('canvas'); |
| const context = canvas.getContext('gpupresent'); |
| |
| let computeBindGroupLayout; |
| if (isChrome) { |
| computeBindGroupLayout = device.createBindGroupLayout({ |
| bindings: [ |
| { binding: 0, visibility: GPUShaderStage.COMPUTE, type: "uniform-buffer" }, |
| { binding: 1, visibility: GPUShaderStage.COMPUTE, type: "storage-buffer" }, |
| { binding: 2, visibility: GPUShaderStage.COMPUTE, type: "storage-buffer" }, |
| ], |
| }); |
| } else { |
| computeBindGroupLayout = device.createBindGroupLayout({ |
| bindings: [ |
| { binding: 0, visibility: GPUShaderStage.COMPUTE, type: "storage-buffer" }, |
| { binding: 1, visibility: GPUShaderStage.COMPUTE, type: "storage-buffer" }, |
| { binding: 2, visibility: GPUShaderStage.COMPUTE, type: "storage-buffer" }, |
| ], |
| }); |
| } |
| |
| const computePipelineLayout = device.createPipelineLayout({ |
| bindGroupLayouts: [computeBindGroupLayout], |
| }); |
| |
| let computeCode; |
| |
| if (isChrome) |
| computeCode = Utils.compile("c", computeShaderGLSL); |
| else |
| computeCode = computeShaderWHLSL; |
| |
| const startTime = Date.now(); |
| |
| const computePipeline = device.createComputePipeline({ |
| layout: computePipelineLayout, |
| computeStage: { |
| module: device.createShaderModule({ |
| code: computeCode, |
| isWHLSL: isChrome ? false : true, |
| }), |
| entryPoint: "main", |
| } |
| }); |
| |
| const simParamData = new Float32Array([0.04, 0.1, 0.025, 0.025, 0.02, 0.05, 0.005]); |
| let simParamBuffer |
| if (isChrome) { |
| simParamBuffer = device.createBuffer({ |
| size: simParamData.byteLength, |
| usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, |
| }); |
| } else { |
| simParamBuffer = device.createBuffer({ |
| size: simParamData.byteLength, |
| usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, |
| }); |
| } |
| await setData(simParamBuffer, simParamData); |
| |
| const initialParticleData = new Float32Array(numParticles * 4); |
| for (let i = 0; i < numParticles; ++i) { |
| //initialParticleData[4 * i + 0] = 2 * (Math.random() - 0.5); |
| //initialParticleData[4 * i + 1] = 2 * (Math.random() - 0.5); |
| //initialParticleData[4 * i + 2] = 2 * (Math.random() - 0.5) * 0.1; |
| //initialParticleData[4 * i + 3] = 2 * (Math.random() - 0.5) * 0.1; |
| initialParticleData[4 * i + 0] = 5; |
| initialParticleData[4 * i + 1] = 5; |
| initialParticleData[4 * i + 2] = 5; |
| initialParticleData[4 * i + 3] = 5; |
| } |
| |
| const particleBuffers = new Array(2); |
| particleBuffers[0] = device.createBuffer({ |
| size: initialParticleData.byteLength, |
| usage: (!isChrome ? GPUBufferUsage.MAP_WRITE : 0) | GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE, |
| }); |
| |
| if (isChrome) { |
| particleBuffers[1] = device.createBuffer({ |
| size: initialParticleData.byteLength, |
| usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, |
| }); |
| } else { |
| particleBuffers[1] = device.createBuffer({ |
| size: initialParticleData.byteLength, |
| usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, |
| }); |
| } |
| |
| await setData(particleBuffers[0], initialParticleData); |
| |
| const bindGroup = device.createBindGroup({ |
| layout: computeBindGroupLayout, |
| bindings: [{ |
| binding: 0, |
| resource: { |
| buffer: simParamBuffer, |
| offset: 0, |
| size: simParamData.byteLength |
| }, |
| }, { |
| binding: 1, |
| resource: { |
| buffer: particleBuffers[0], |
| offset: 0, |
| size: initialParticleData.byteLength, |
| }, |
| }, { |
| binding: 2, |
| resource: { |
| buffer: particleBuffers[1], |
| offset: 0, |
| size: initialParticleData.byteLength, |
| }, |
| }], |
| }); |
| |
| async function foo() { |
| let result = device.createBuffer({ |
| size: initialParticleData.byteLength, |
| usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, |
| }); |
| const commandEncoder = device.createCommandEncoder({}); |
| const passEncoder = commandEncoder.beginComputePass(); |
| passEncoder.setPipeline(computePipeline); |
| passEncoder.setBindGroup(0, bindGroup); |
| passEncoder.dispatch(numThreadGroups, 1, 1); |
| passEncoder.endPass(); |
| |
| if (isChrome) |
| commandEncoder.copyBufferToBuffer(particleBuffers[1], 0, result, 0, initialParticleData.byteLength); |
| |
| device.getQueue().submit([commandEncoder.finish()]); |
| |
| let resultsArrayBuffer; |
| if (isChrome) { |
| resultsArrayBuffer = await result.mapReadAsync(); |
| } else { |
| resultsArrayBuffer = await particleBuffers[1].mapReadAsync(); |
| } |
| |
| appendMessage(`total time: ${Date.now() - startTime} ms`); |
| const resultsArray = new Float32Array(resultsArrayBuffer); |
| |
| //{ |
| // try { |
| // const error = await device.popErrorScope(); |
| // if (error) |
| // appendMessage("Error: " + error); |
| // } catch(e) { |
| // console.log(e); |
| // } |
| //} |
| |
| try { |
| if (resultsArray.length !== numParticles*4) |
| throw new Error("Bad length"); |
| for (let i = 0; i < resultsArray.length; i += 4) { |
| if (resultsArray[i] !== -1) |
| throw new Error("not -1, is: " + resultsArray[i]); |
| if (resultsArray[i + 1] !== -1) |
| throw new Error("not -1, is: " + resultsArray[i + 1]); |
| if (resultsArray[i + 2] !== 0.0707106813788414) |
| throw new Error("not fraction"); |
| if (resultsArray[i + 3] !== 0.0707106813788414) |
| throw new Error("not fraction"); |
| } |
| |
| appendMessage("Success."); |
| } catch (e) { |
| appendMessage(` error: ${e}`); |
| } |
| } |
| |
| foo(); |
| } |
| |
| window.onload = init; |