WebGPU Breaking Changes

This page is describing the breaking changes and differences in behaviour between WebGL and WebGPU.

We try to avoid breaking changes as much as possible in the core library, but sometimes we can't avoid them, and with the big new evolution that supporting WebGPU as a new engine is you need to be aware of a number of things if trying to port your existing projects to WebGPU.

readPixels is now asynchronous

Probably the biggest change between WebGPU and WebGL is that reading from a texture is asynchronous in WebGPU, no way around it. So, all methods (even in WebGL mode) reading pixels from textures now return a promise:

To match how WebGL works there is a flushFramebuffer call that is automatically performed before reading a texture to be sure you get up to date data. However, if you know your texture is up to date when you call a readPixels method, you can avoid this flush (save some tiny bit of perf) by passing the appropriate parameter to the function call (flushRenderer = false, see docs). Note that if you are doing the read in engine.onEndFrameObservable you don't need to flush, as this observer triggers after the flushing for the current frame has been done.

Note also that currently readPixels is slow if width is not divisible by 64! Also, it is very slow when reading data from half float textures: use full float textures instead if possible. Speeding those things up is on our roadmap.

Creation of the WebGPU engine is asynchronous

Creating the engine is also asynchronous in WebGPU. You can do something like this to create a WebGPU engine if supported by the browser, or else a WebGL engine:

async function createEngine() {
const webGPUSupported = await BABYLON.WebGPUEngine.IsSupportedAsync;
if (webGPUSupported) {
const engine = new BABYLON.WebGPUEngine(document.getElementById("renderCanvas"));
await engine.initAsync();
return engine;
return new BABYLON.Engine(document.getElementById("renderCanvas"), true);

Or using the EngineFactory helper (it will try first to create a WebGPU engine if supported, then a WebGL engine then a null engine):

async function createEngine() {
return BABYLON.EngineFactory.CreateAsync(document.getElementById("renderCanvas"));

Shader code differences

Array of textures

Array of textures in shader code can't be accessed with a varying index, it must be an immediate value. For eg, myTextures[0] / myTextures[1] does work but not myTextures[i] (i being a variable loop for instance).

Passing samplers to functions

In shaders, you can't pass samplers to functions:

vec4 getPixel(sampler2D sampler, vec2 uv) {
return texture2D(sampler, uv);

Calling this function will fail with a compilation error in WebGPU.

To simplify porting existing code we have added a pre-pass shader code inliner which will replace a function call by the code of the function itself. You need to tag the function(s) to be inlined with #define inline for the inliner to perform its processing:

#define inline
vec4 getPixel(sampler2D sampler, vec2 uv) {
return texture2D(sampler, uv);

Binding values to samplers

WebGPU is less forgiving than WebGL, all sampler variables declared in a shader must have a value bound, even if you don't use that variable, contrary to WebGL. If you get a warning message like "numBindings mismatch", it means that you probably defined a uniform sampler variable in the shader code but didn't bind a value to it (by calling something like setTexture("myvar", texture) on the shader/material).


If using a custom attribute in a ShaderMaterial (or CustomMaterial / PBRCustomMaterial), it must be declared in the list of attributes used by that shader. For eg, for ShaderMaterial, you must pass its name in the attributes array of the options passed to the constructor. In WebGL you can omit this declaration and it will still work (but as a side-effect, it is not really supported).

In WebGL you could list several times the same attribute when creating a ShaderMaterial and it would work (it was as if you gave this attribute a single time), but in WebGPU it will fail.


The viewport can't spread outside the framebuffer/texture, contrary to WebGL. So, if you call something like:

new BABYLON.Viewport(x, y, w, h);
  • x, y, w, h must be >= 0 and <= 1.
  • x + w must be <= 1
  • y + h must be <= 1

If you still want to use out-of-bounds values, you can use a material plugin like in this PG: Viewport out-of-bounds values

The TEXTUREFORMAT_LUMINANCE format is not supported in WebGPU.