www.xbdev.net
xbdev - software development
Tuesday April 29, 2025
Home | Contact | Support | WebGPU Graphics and Compute ... | WebGPU 'Compute'.. Compute, Algorithms, and Code.....
     
 

WebGPU 'Compute'..

Compute, Algorithms, and Code.....

 


Computing Pie


Calculating 'pie' on the GPU using a pseudo probability function (estimating Pi using Monte Carlo method). This approach for estimating the value of pi using random numbers can illustrate the relationship between the sample size, the estimate of pi, and the error in the estimate. The Monte Carlo method generates random points inside a square and counts the number of points that fall inside a circle inscribed in the square to estimate pi.


Unit circle numbers ranges.
Unit circle numbers ranges.


Functions Used: requestAdapter(), getPreferredCanvasFormat(), createCommandEncoder(), beginRenderPass(), setPipeline(), draw(), end(), submit(), getCurrentTexture(), createView(), createShaderModule()

First, let's start by understanding what pie actually is, the definition for pie is:

The number π is a mathematical constant that is the ratio of a circle's circumference to its diameter, approximately equal to 3.14159...

We're going to do a calculation using a unit circle. Instead of a whole circle which has a range of -1 to 1, we'll only do the calculation with a quarter (i.e., 0 to 1). A circle is the same - so any calculation on this small quadrant can be multipled by 4 to get the final answer.

Now we can generate lots of random numbers in the range 0 to 1 for the corner. Take a random point P at coordinate X, Y such that 0 <= x <= 1 and 0 <= y <= 1. If x2 + y2 <= 1, then the point is inside the quarter disk of radius 1, otherwise the point is outside.


Random points for the x and y (0 to 1) - how the points are distributed inside and outside the circle quarter.
Random points for the x and y (0 to 1) - how the points are distributed inside and outside the circle quarter.



Before implementing the calculation on the compute shader - we can do a small proof of concept in JavaScript to see that the number returned is in the ball-park (close to 3.14).

let iterations  10000;
let insideCount 0;

for(
let i 0iterations ;i++){

  
let x Math.random();
  
let y Math.random();

  if(
Math.pow(x2) + Math.pow(y2) <= 1){
    
insideCount++;
  }
}

let approximateValueOfPi * (insideCount iterations  );

console.log('approximateValueOfPi:'approximateValueOfPi );


The result returned from the JavaScript code is actually very good, it returned ["approximateValueOfPi:",3.1484]. It still has an error - but the value is close.

Random Numbers on Compute Shader


For the compute shader we'll create 128 threads - each thread will perform the same calculation - then we'll take the average of the final result (which should be more accurate as it's over a larger number of iterations).

However, one aspect of the calculation is how to generate a random number? For this example, we'll use a simple pseudo random number generator - for the seed we'll use the globalId. This is so each compute shader generates it own cluster of unique numbers.

While it's not really a very robust random number generator - it's good enough for testing here - provide a proof of concept. Later on you could try modifying the random number generator with other more complex ones to see if it improves the result.


fn random(uvvec2<f32>) -> f32 {
    return 
fract(sin(dot(uvvec2<f32>(12.989878.233))) * 43758.5453);
}


Complete Code


The complete code for the compute pie calculation using a Monte-carlo random approximation is given below. The code includes all of the setup code and the buffers for the result. The result is written to an output buffer which is summed on the JavaScript side.

let div document.createElement('div');
document.body.appendChilddiv );
div.style['font-size'] = '20pt';
function 
log)
{
  
console.log);
  
let args = [...arguments].join(' ');
  
div.innerHTML += args '<br>';
}

log('WebGPU Compute Example (PIE)');

if (!
navigator.gpu) { log("WebGPU is not supported (or is it disabled? flags/settings)"); return; }

const 
adapter await navigator.gpu.requestAdapter();
const 
device  await adapter.requestDevice();

const 
arraySize 128;

// OUT 
const buffer2 = new Float32Array( Array.from({lengtharraySize}, () => 0) );
const 
gbuffer2 device.createBuffer({ size:  buffer2.byteLengthusageGPUBufferUsage.STORAGE |  GPUBufferUsage.COPY_DST GPUBufferUsage.COPY_SRC });
device.queue.writeBuffer(gbuffer20buffer2);

// Note this buffer is not linked to the 'STORAAGE' compute (used to bring the data back to the CPU)
const buffer3 = new Float32Array( Array.from({lengtharraySize}, () => 0) );
const 
gbuffer3 device.createBuffer({ size:  buffer3.byteLengthusageGPUBufferUsage.COPY_DST GPUBufferUsage.MAP_READ});
device.queue.writeBuffer(gbuffer30buffer3);

// Bind group layout and bind group
const bindGroupLayout device.createBindGroupLayout({
  
entries: [ {binding0visibilityGPUShaderStage.COMPUTEbuffer: {type"storage"}            }
           ]
});

const 
bindGroup device.createBindGroup({
    
layoutbindGroupLayout,
    
entries: [ {binding0resource: {buffergbuffer2   }}
    ]
});

// Compute shader code
const computeShader = ` 
@group(0) @binding(0) var<storage, read_write> outputBuffer : array<f32, 
${arraySize}>;
      
fn random(uv: vec2<f32>) -> f32 {
    return fract(sin(dot(uv, vec2<f32>(12.9898, 78.233))) * 43758.5453);
}

@compute @workgroup_size(8, 1)
fn main(@builtin(global_invocation_id) globalId      : vec3<u32>,
        @builtin(local_invocation_id)  localId       : vec3<u32>,
        @builtin(workgroup_id)         workgroupId   : vec3<u32>,
        @builtin(num_workgroups)       workgroupSize : vec3<u32>
        ) 
{
    var index = globalId.x;

    var insideCount = 0.0;
    let iterations  = 10;

    // Computation loop
    for (var i = 0; i < iterations; i++) {
        var x = random( vec2<f32>( f32(i), f32(index)*10.0 ) );     // Random x position in [0,1]
        var y = random( vec2<f32>( f32(i*10), f32(index)*999.0 ) ); // Random y position in [0,1]
        
        if ( x*x + y*y <= 1.0 ){ insideCount += 1.0; }
    }
    let approximateValueOfPi = 4.0 * (insideCount / f32(iterations) );

    outputBuffer[ index ] = approximateValueOfPi;
}
`;

 
// Pipeline setup
const computePipeline device.createComputePipeline({
    
layout :   device.createPipelineLayout({bindGroupLayouts: [bindGroupLayout]}),
    
compute: { module    device.createShaderModule({code:computeShader}),
               
entryPoint"main" }
});

// Commands submission
const commandEncoder device.createCommandEncoder();
const 
passEncoder commandEncoder.beginComputePass();
passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0bindGroup);
passEncoder.dispatchWorkgroupsarraySize/8);
await passEncoder.end();


// Encode commands for copying buffer to buffer.
commandEncoder.copyBufferToBuffer(
    
gbuffer2,           // source buffer
    
0,                  // source offset
    
gbuffer3,           // destination buffer
    
0,                  // destination offset
    
buffer3.byteLength  // size
);

// Submit GPU commands.
const gpuCommands commandEncoder.finish();
await device.queue.submit([gpuCommands]);

// Read buffer.
await gbuffer3.mapAsync(GPUMapMode.READ);
const 
arrayBuffer gbuffer3.getMappedRange();
const array = new 
Float32ArrayarrayBuffer );
const 
outputArray = Array.from( array );
log('array size:'arraySize);
log('array contents:',outputArray );

let pieSum outputArray.reduce((ab) => b0)
log('Sum Array / array size = (pie):'pieSum arraySize  );

log('');

log('Error:'Math.PI - (pieSum arraySize) );


The result from our compute shader version is: 3.140625050291419


Things to Try


• Try other random number functions
• Plot the accuracy of the calculation vs number of threads



Resources and Links


JavaScript Pie
WebGPU Lab Compute Pie


















WebGPU by Example: Fractals, Image Effects, Ray-Tracing, Procedural Geometry, 2D/3D, Particles, Simulations WebGPU Compute graphics and animations using the webgpu api 12 week course kenwright learn webgpu api kenwright programming compute and graphics applications with html5 and webgpu api kenwright real-time 3d graphics with webgpu kenwright webgpu api develompent a quick start guide kenwright webgpu by example 2022 kenwright webgpu gems kenwright webgpu interactive compute and graphics visualization cookbook kenwright wgsl webgpu shading language cookbook kenwright wgsl webgpugems shading language cookbook kenwright



 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2025 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.