bufferfragment-shaderwebgpu

webgpu transferring buffer from js doesnot unpack at right position


i am new to webgpu and trying to render some spheres using frgament shader spheres are defined as

struct Material {
    category: u32,
    attenuation:  vec3f,
    fuzz: f32,
    refraction_index: f32,
}
    
struct Sphere {
    center: vec3f,
    radius: f32,
    mat: Material
}
function createSpheresStorageBuffer2(spheres: Sphere[]): GPUBuffer {

    const bufferElements: (Float32Array | Uint32Array)[] = [];
    let size = 0;
    let arrayBuffer;

    for (const sphere of spheres) {
        arrayBuffer = new Float32Array([...sphere.center, sphere.radius]);
        size+=arrayBuffer.byteLength;
        bufferElements.push(arrayBuffer);

        const {type,attenuation,fuzz,refraction_index} = sphere.material;

        arrayBuffer = new Uint32Array([type]);
        size+=arrayBuffer.byteLength;
        bufferElements.push(arrayBuffer);
        
        arrayBuffer = new Float32Array([...attenuation,fuzz,refraction_index]);
        size+=arrayBuffer.byteLength;
        bufferElements.push(arrayBuffer);
    }

    const buffer = device.createBuffer({
        label: "spheres buffer",
        size: size,
        usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
    });

    let offset = 0;
    for(const buff of bufferElements){
        device.queue.writeBuffer(buffer, offset, buff);
        offset+=buff.byteLength
    }
    return buffer
}

enum MaterialType {
    Labertian = 0,
    Metal = 1,
}

const spheres: Sphere[] = [
    {
        center: [0, -100.5, -1],
        radius: 100,
        material: {
            type: MaterialType.Labertian,
            attenuation: [0.5,0.5,0.5],
            fuzz: 1.0,
            refraction_index: 1.0
        }
    },
    {
        center: [1, 0, -1],
        radius: 0.5,
        material: {
            type: MaterialType.Metal,
            attenuation: [0.5,0.5,0.5],
            fuzz: 0,
            refraction_index: 1.0
        }
    },
]

but the value of center of shpere 2 in shader is 0.5,0.5,1 which are the values from attenuation [2,3] + fuzz (which i can to know by trial and error)

the code works fine when the attenuation is f32 instead of vector


Solution

  • It doesn't appear that the way you are packing your data matches the memory layout from WebGPU

    Here's the layout

    enter image description here

    The diagram generated from here

    You might want to consider using a library to set storage and uniform buffers.

    On the other hand, you might also want to consider making one arraybuffer and 2 views. It will be more efficient to upload one buffer than 3 per sphere you're uploading now.

    Something along the lines of

    const elemsPerSphere = 16;
    const f32s = new Float32Array(elemsPerSphere * spheres.length);
    // make a u32 view of the same memory
    const u32s = new Uint32Array(f32s.buffer);
    
    const kCenterOffset = 0;
    const kRadiusOffset = 3;
    const kCategoryOffset = 4;
    const kAttenuationOffset = 8;
    const kFuzzOffset = 11;
    const kRefractionIndexOffset = 12;
    
    let offset = 0;
    for (const sphere of spheres) {
       f32s.set([...sphere.center, sphere.radius], offset + kCenterOffset);
    
       const {type,attenuation,fuzz,refraction_index} = sphere.material;
       u32s[offset + kCategoryOffset] = type;
    
       f32s.set([...attenuation,fuzz,refraction_index], offset + kAttenuationOffset);
    
       offset += kElemsPerSphere;
    }
    
    // Upload all spheres at once
    device.queue.writeBuffer(buffer, 0, f32s);
    

    See this article for more info

    Note: The method above has performance issues as it's creating temporary arrays for each call to set. I don't know if there is an optimal solution. I'd be fine using the solution above until I found it was too slow for my needs. Then I might try something different. One example might be

    let offset = 0;
    for (const sphere of spheres) {
       f32s.set(sphere.center, offset + kCenterOffset);
       f32s[offset + kRadiusOffset] = sphere.radius
    
       const {type,attenuation,fuzz,refraction_index} = sphere.material;
    
       u32s[offset + kCategoryOffset] = type;
    
       f32s.set(offset + kAttenuationOffset, attenuation);
       f32s[offset + kFuzzOffset] = fuzz;
       f32s[offset + kRefractionIndexOffset] = refaction_index;
    
       offset += kElemsPerSphere;
    }
    

    This code at least is not allocating temporary arrays. You'd have to profile if it's faster.

    disclosure: I contribute to the site that generated the diagram and the article and the library linked above and most likely the browser you're using when you asked this question