denoland / deno

A modern runtime for JavaScript and TypeScript.
https://deno.com
MIT License
93.39k stars 5.18k forks source link

WebGPU: "Error: Invalid Surface Status" triggered randomly #23407

Open chirsz-ever opened 2 months ago

chirsz-ever commented 2 months ago

Environment:

Repuroducable code:

// https://deno.com/blog/v1.40#webgpu-windowing--bring-your-own-window

import {
    EventType,
    WindowBuilder,
} from "jsr:@divy/sdl2@0.10.5";

const width = 800;
const height = 600;

const win = new WindowBuilder("Hello, World!", width, height).build();

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
    console.error("WebGPU not supported!");
    Deno.exit(1);
}
const device = await adapter.requestDevice();

/* Returns a Deno.UnsafeWindowSurface */
const surface = win.windowSurface();
/* Returns a WebGPU GPUCanvasContext */
const context = surface.getContext("webgpu");

context.configure({
    device,
    format: navigator.gpu.getPreferredCanvasFormat(),
    width,
    height,
});

for await (const event of win.events()) {
    if (event.type === EventType.Quit) break;
    if (event.type !== EventType.Draw) continue;

    // Sine wave
    const r = Math.sin(Date.now() / 1000) / 2 + 0.5;
    const g = Math.sin(Date.now() / 1000 + 2) / 2 + 0.5;
    const b = Math.sin(Date.now() / 1000 + 4) / 2 + 0.5;

    const textureView = context.getCurrentTexture().createView();

    const commandEncoder = device.createCommandEncoder();
    const passEncoder = commandEncoder.beginRenderPass({
        colorAttachments: [
            {
                view: textureView,
                clearValue: { r, g, b, a: 1.0 },
                loadOp: "clear",
                storeOp: "store",
            },
        ],
    });
    passEncoder.end();

    device.queue.submit([commandEncoder.finish()]);
    surface.present();
}

Save it as "example.ts" and run (SDL needs to be installed in advance):

deno run --unstable-webgpu --unstable-ffi -A example.ts

Got:

picture

Only one success.

If replacing deno_sdl2 with dwm, the same error would occur:

// https://deno.com/blog/v1.40#webgpu-windowing--bring-your-own-window

import {
    createWindow,
    mainloop,
} from "https://deno.land/x/dwm@0.3.6/mod.ts";

const width = 800;
const height = 600;

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
    console.error("WebGPU not supported!");
    Deno.exit(1);
}
const device = await adapter.requestDevice();

const win = createWindow({
    title: "Deno Dwm WebGL",
    width,
    height,
});

/* Returns a Deno.UnsafeWindowSurface */
const surface = win.windowSurface();
/* Returns a WebGPU GPUCanvasContext */
const context = surface.getContext("webgpu");

context.configure({
    device,
    format: navigator.gpu.getPreferredCanvasFormat(),
    width,
    height,
});

function frame() {
    // Sine wave
    console.log("Draw!");
    const r = Math.sin(Date.now() / 1000) / 2 + 0.5;
    const g = Math.sin(Date.now() / 1000 + 2) / 2 + 0.5;
    const b = Math.sin(Date.now() / 1000 + 4) / 2 + 0.5;

    const textureView = context.getCurrentTexture().createView();

    const commandEncoder = device.createCommandEncoder();
    const passEncoder = commandEncoder.beginRenderPass({
        colorAttachments: [
            {
                view: textureView,
                clearValue: { r, g, b, a: 1.0 },
                loadOp: "clear",
                storeOp: "store",
            },
        ],
    });
    passEncoder.end();

    device.queue.submit([commandEncoder.finish()]);
    surface.present();
}

mainloop(frame);

Maybe this is a wgpu bug on Xwayland? When I start my desktop environment with X11, this problem disappears.

By the way, I think it worthy of adding a "wayland" backend for Deno.UnsafeWindowSurface

chirsz-ever commented 2 weeks ago

The error raised from a SurfaceError::Outdated at ext/webgpu/surface.rs:

https://github.com/denoland/deno/blob/ab2ab0f7fc4e7bda16544ef96a5e6ba114e799b5/ext/webgpu/surface.rs#L100-L102

I found that reconfiguration can fix the problem:

// https://deno.com/blog/v1.40#webgpu-windowing--bring-your-own-window

import {
    createWindow,
    mainloop,
} from "https://deno.land/x/dwm@0.3.6/mod.ts";

const width = 800;
const height = 600;

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
    console.error("WebGPU not supported!");
    Deno.exit(1);
}
const device = await adapter.requestDevice();

const win = createWindow({
    title: "Deno Dwm WebGL",
    width,
    height,
});

/* Returns a Deno.UnsafeWindowSurface */
const surface = win.windowSurface();
/* Returns a WebGPU GPUCanvasContext */
const context = surface.getContext("webgpu");

context.configure({
    device,
    format: navigator.gpu.getPreferredCanvasFormat(),
    width,
    height,
});

function frame() {
    // Sine wave
    // console.log("Draw!");
    const r = Math.sin(Date.now() / 1000) / 2 + 0.5;
    const g = Math.sin(Date.now() / 1000 + 2) / 2 + 0.5;
    const b = Math.sin(Date.now() / 1000 + 4) / 2 + 0.5;

    let textureView;
    try {
        textureView = context.getCurrentTexture().createView();
    } catch (e) {
        console.error(e);
        context.configure({
            device,
            format: navigator.gpu.getPreferredCanvasFormat(),
            width,
            height,
        });
        console.log("reconfigured");
        return;
    }

    const commandEncoder = device.createCommandEncoder();
    const passEncoder = commandEncoder.beginRenderPass({
        colorAttachments: [
            {
                view: textureView,
                clearValue: { r, g, b, a: 1.0 },
                loadOp: "clear",
                storeOp: "store",
            },
        ],
    });
    passEncoder.end();

    device.queue.submit([commandEncoder.finish()]);
    surface.present();
}

mainloop(frame);

@crowlKats Do you have any suggestions? Can we just add code in op_webgpu_surface_get_current_texture to reconfigure the context when SurfaceError::Outdated occured?