How to publish a library that supports both WebGLRenderer and WebGPURenderer?

Looking for guidance on making libraries support both WebGL and WebGPU.

I am maintaining a library that exports a subclass of THREE.Object3D, which currently depends on ShaderMaterial. I would like to modify/rewrite this to support WebGPU as well, so I will need to change the library to import from "three/webgpu" and "three/tsl". However, I’d like to avoid breaking downstream users who are still using import { WebGLRenderer } from "three".

Is there a good pattern for this? It’s not clear to me the best way to approach it:

  • Should I duplicate the code and provide two totally separate exports from my package which use two different materials?
  • For parts of the code which use renderer-agnostic types like new THREE.InstancedBufferGeometry(), do I need to change these to import { InstancedBufferGeometry } from "three/webgpu" or import { InstancedBufferGeometry } from "three" depending on what the consumer wants? (This would definitely mean I need to provide two different exports.) – Same question also applies to THREE.Object3D itself.

Thanks!

There’s currently no simple way to write one material that supports WebGPURenderer and WebGLRenderer. There’s a thread here discussing adding basic support for TSL to WebGLRenderer to address this and there’s a PR here that adds an adapter for TSL (with limitations) to enable this kind of cross-renderer support and migration. My hope is that the PR can be merged for the release at the end of February but we’ll see. Any feedback on the PR from people that are looking for a solution to this issue would be appreciated.

Otherwise the only other real option is to maintain two copies of all materials export webgl and webgpu variants.

Thanks for the context and reading material. Beyond just two copies of materials, do you think it’s necessary to also maintain two copies of all objects/classes, since they are supposed to use different imports even for the APIs that are not different between three and three/webgpu?

It’s not exactly clear to me what you mean or are concerned about but it’s valid to import from both “three” and “three/webgpu”. They share common exports between them and should otherwise tree shake. I would still provide dedicated entry points for webgl and webgpu that only include the necessary shaders: eg exporting my-package/webgl with onBeforeCompile and my-package/webgpu with tsl.

You don’t need to duplicate everything. Keep your geometry and Object3D stuff renderer-agnostic, only switch materials and renderer calls based on whether it’s WebGPU or WebGL. For example, you can do something like:

import { Object3D, ShaderMaterial } from "three";
import { ShaderMaterial as WebGPUShaderMaterial } from "three/webgpu";

class MyObject extends Object3D {
    constructor(useWebGPU: boolean) {
        super();
        this.material = useWebGPU ? new WebGPUShaderMaterial(myShader) : new ShaderMaterial(myShader);
    }
}

Your render snippet is fine:

if (!postProcessing) 
    await renderer[`render${_pm.useWebGPU ? "Async" : ""}`](scene, camera);
else 
    postProcessing.render();

Basically, one export with a useWebGPU flag is enough. Only touch materials/shaders and render calls, leave geometry and Object3D imports from “three” so you don’t break users still on WebGL.

There is also this transpiler:

Thanks for the input. Yes, the transpiler was helpful when doing the initial translation to NodeMaterial.

Here is what I ended up with — a …/webgpu export for WebGPU-specific types:

Certainly, support for NodeMaterial in WebGLRenderer would have made this easier, and less effort for the consumer, but it looks like this approach should work alright (although I haven’t validated for certain that it tree-shakes properly).