mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
102.25k stars 35.35k forks source link

The first step to implement drawIndirect in threejs #29568

Closed Spiri0 closed 1 week ago

Spiri0 commented 1 week ago

Description

The topic of DrawIndirect is a bigger one and so I think it makes sense to implement it piece by piece. In order to check the visibility of objects with compute shaders, a new StorageBuffer is necessary, because the class StorageBufferAttribute is "typeClass = Float32Array". DrawIndirect requires a very specific structure

From W3C WebGPU documentation: image

Because of this very specific form, I imagine a separate class for this that could be called: DrawIndirectStorageBufferAttribute maybe just IndirectStorageBufferAttribute so it won't be so long.

To do this, INDIRECT must be added to the WebGPUBackend.js

In WebGPUBackend.js add GPUBufferUsage.INDIRECT

createStorageAttribute( attribute ) {

    this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );

}

The DrawIndirectStorageBufferAttribute could then look like this.

class DrawIndirectStorageBufferAttribute extends BufferAttribute {

    constructor( array, itemSize = 4, typeClass = Uint32Array ) {

        if ( ArrayBuffer.isView( array ) === false ) array = new typeClass( array * itemSize );

        super( array, itemSize );

        this.isDrawIndirectStorageBufferAttribute = true;

    }

}

The itemSize of this class is always 4 with 16 bytes. If you call the class without passing an array, an empty Uint32Array would automatically be created with the entries 0, 0, 0, 0, which is exactly what is needed if you want to calculate the visibility with a compute shader.

The node system would then have to build from this:

 const drawBuffer = storage( new THREE.DrawIndirectStorageBufferAttribute() );

this WGSL code:

struct DrawBuffer {
      vertexCount: u32,
      instanceCount: atomic<u32>,
      firstVertex: u32,
      firstInstance: u32,
};

@group(x) @binding(y) var<storage, read_write> drawBuffer: DrawBuffer;

This drawBuffer can then be filled in the compute shader. Thanks to the pointer extension from Sunag, this would then be possible in both TSL and WGSL.

A few more things would be necessary (there are then two other important points on the visibility check side) but that would be too much all at once. This step is a very important part of being able to move the visibility check to the GPU in threejs.

Solution

I have included the solution in the description. The first step for DrawIndirect in threejs is to implement a DrawIndirectStorageBufferAttribute that can be filled in compute shaders.

Alternatives

The alternative is the visibility check on the CPU side, but this has limits.

Additional context

No response

Mugen87 commented 1 week ago

Merging into #28389.

sunag commented 1 week ago

@Spiri0 What do you think about doing a PR? Looks a good start, I would just suggest removing the Draw prefix from the class name.

Spiri0 commented 1 week ago

I created a createIndirectStorageAttribute with everything that goes with it on WE. I left out the draw throughout because indirect is simply more general. The draw would then be specific to the drawBuffer

createIndirectStorageAttribute( attribute ) {

    this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );

}

The associated extensions up to the binding generation are a bit more extensive, but my three.webgpu.js runs fine. Since I made all the changes in three.webgpu.js in order not to have to constantly recompile, I now have to make the extensions in the corresponding modules and test the compilation. This is the first time that I have worked so intensively with three.js myself. I would be happy if you could review it when I make PRs. You would then definitely come up with improvements. I know what is necessary to carry out the visibility check in the GPU because I have worked intensively on the topic over the past few months because I'm working on a virtual geometry system

dragons_wireframe

RenaudRohlinger commented 1 week ago

Awesome work @Spiri0, looking forward for the PR!

Spiri0 commented 1 week ago

@sunag @RenaudRohlinger: Ok PR done. That would be the first part. Since this is my first time doing such a comprehensive expansion, please feel free to improve me if you see potential.