SrorageBuffer.Read() works in Playgound but not in web app

Hi friends,

I have a simple shader code to add 2 arrays. It works well in Playground and I can see the resultArray being logged out to console. However, when I embed this code into my front-end web app, it does not print resultArray to console, even though the code is the same.

When I do browser debugging, I can see that the execution reach to the block bufferResultArray.read().then((res) at the end of my code; however, the code inside the brackets to log the buffer content to console never got executed.

May anyone help tell me the reason why ? Thank you


cv['onRuntimeInitialized'] = () => {
    // Get the input and canvas elements
    const imageInput = document.getElementById('imageInput');
    const canvasOutput = document.getElementById('canvasOutput');

    
    // Event listener for file input
    imageInput.addEventListener('change', (e) => {

        // Check if WebGPU is supported
        BABYLON.WebGPUEngine.IsSupportedAsync.then((isSupported) => {
            if (isSupported) {
                console.log("WebGPU is supported in current browser!");

                // Initialize the WebGPUEngine
                const canvas = document.getElementById('renderCanvas');
                engine = new BABYLON.WebGPUEngine(canvas);                             
                         
            } else {
                console.log("WebGPU is not supported, fallback to standard engine.");
                // Fallback to the standard Engine
                const canvas = document.getElementById('renderCanvas');
                engine = new BABYLON.Engine(canvas, true);
            }
        });

        const file = e.target.files[0];
        if (file) {
            const reader = new FileReader();
            
            // When the file is read, create an image and process it
            reader.onload = (event) => {
                const img = new Image();
                img.onload = () => {
                                 
                    engine.initAsync().then(() => {

                        const supportCS = engine.getCaps().supportComputeShaders;

                        if (supportCS) {
                            console.log("Compute shaders are supported!");
                        }
                        else {
                            console.log("Compute shaders are not supported!");
                        }                        

                        console.log('Engine started: ' + engine.getInfo().renderer);
                        console.log('Engine vendor:' + engine.getInfo().vendor);
                        scene = new BABYLON.Scene(engine);

                        fishviewer = new FishViewer(engine, scene);       

                        fishviewer.setMap(xmap, ymap);
                        fishviewer.setHomography(M);
                    

                        const shaderCode = `
                            @group(0) @binding(0) var<storage,read> firstArray: array<f32>;
                            @group(0) @binding(1) var<storage,read> secondArray: array<f32>;
                            @group(0) @binding(2) var<storage,read_write> resultArray: array<f32>;
                    
                            @compute @workgroup_size(1, 1, 1)
                            fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
                                let index: u32 = global_id.x;
                                resultArray[index] = firstArray[index] + secondArray[index];
                            }
                        `;
                    
                        const computeShader = new BABYLON.ComputeShader(
                            "MyAwesomeComputeShader", // give it a name
                            engine, // give it the WebGPU engine
                            { computeSource: shaderCode }, // give it our shader code
                            // Then declare the same bindings as what was in our code
                            {
                                bindingsMapping: {
                                    firstArray: { group: 0, binding: 0 },
                                    secondArray: { group: 0, binding: 1 },
                                    resultArray: { group: 0, binding: 2 }
                                }
                            }
                        );
                    
                        const firstArray = new Float32Array([1, 2, 3, 4, 5, 6, 7, 8, 9]);
                        const secondArray = new Float32Array([9, 8, 7, 6, 5, 4, 3, 2, 1]);
                    
                        const bufferFirstArray = new BABYLON.StorageBuffer(engine, firstArray.byteLength);
                        bufferFirstArray.update(firstArray);
                    
                        const bufferSecondArray = new BABYLON.StorageBuffer(engine, secondArray.byteLength);
                        bufferSecondArray.update(secondArray);
                    
                        const bufferResultArray = new BABYLON.StorageBuffer(engine, Float32Array.BYTES_PER_ELEMENT * firstArray.length);
                    
                        computeShader.setStorageBuffer("firstArray", bufferFirstArray);
                        computeShader.setStorageBuffer("secondArray", bufferSecondArray);
                        computeShader.setStorageBuffer("resultArray", bufferResultArray);
                    
                        computeShader.dispatchWhenReady(firstArray.length, 1, 1).then(() => {

                            bufferResultArray.read().then((res) => {
                                const result = new Float32Array(res.buffer);
                                console.log(result);
                            });

                        });                        

                    })
                    .catch((error) => {
                        console.error("Failed to initialize Babylon.js WebGPU engine", error);
                    }); 
                };
            };            
        }
    });
};

@Evgeni_Popov , probably know a lot more about WebGPU and StorageBuffers than I do.

By default, StorageBuffer.read uses the Engine.onEndFrameObservable observable to get the data from the buffer. It means a render loop must be setup, else Engine.onEndFrameObservable will never be notified.

Alternatively, you can pass true for the noDelay parameter (4th parameter) of StorageBuffer.read so that the buffer is read immediately, meaning you don’t need a render loop.

You can test both cases with this PG (see line 57):

1 Like

OMG your help is so valuable.

As a beginner, I think this use-case ( reading values out of GPU/Compute Shader with StorageBuffer.read() ) deserves a default tutorial in Babylon.js documentation as many people may need it when dealing with compute shaders.

Fortunately, the expert users available here are so helpful.

Thanks

1 Like

Agree, @Evgeni_Popov I think it would be a great doc addition

I added a FAQ section to the compute shader page:

1 Like