bevyengine/bevy · critical

Failed to create wgpu surface

Error message

Failed to create wgpu surface

What it means

During RenderPlugin initialization Bevy creates the wgpu Surface for the primary window from its raw handle (via instance.create_surface) and expects success. Surface creation fails when the chosen wgpu instance/backends cannot present to that window — missing GPU drivers, a backend/display-server mismatch, or (on web) a canvas without a WebGPU/WebGL2 context.

Source

Thrown at crates/bevy_render/src/renderer/mod.rs:253

    #[cfg(feature = "raw_vulkan_init")]
    let instance = raw_vulkan_init::create_raw_vulkan_instance(
        instance_descriptor,
        &raw_vulkan_init_settings,
        &mut additional_vulkan_features,
    );

    let surface = primary_window.and_then(|wrapper| {
        let maybe_handle = wrapper
            .0
            .lock()
            .expect("Couldn't get the window handle in time for renderer initialization");
        if let Some(wrapper) = maybe_handle.as_ref() {
            // SAFETY: Plugins should be set up on the main thread.
            let handle = unsafe { wrapper.get_handle() };
            Some(
                instance
                    .create_surface(handle)
                    .expect("Failed to create wgpu surface"),
            )
        } else {
            None
        }
    });

    let force_fallback_adapter = std::env::var("WGPU_FORCE_FALLBACK_ADAPTER")
        .map_or(options.force_fallback_adapter, |v| {
            !(v.is_empty() || v == "0" || v == "false")
        });

    let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME")
        .as_deref()
        .map_or(options.adapter_name.clone(), |x| Some(x.to_lowercase()));

    let request_adapter_options = RequestAdapterOptions {
        power_preference: options.power_preference,
        compatible_surface: surface.as_ref(),

View on GitHub (pinned to 396ca72708)

Solutions

  1. Install/verify GPU drivers and the required libraries (Vulkan loader, libx11/libwayland, EGL/GLX)
  2. Set WGPU_BACKEND to a backend the machine actually supports (e.g. WGPU_BACKEND=gl on X11-less setups)
  3. For headless rendering/CI/tests, run without a primary window: WindowPlugin { primary_window: None, ..default() } with Bevy's headless rendering support instead of a real surface
  4. On web, confirm the canvas element exists and the browser supports WebGL2 or WebGPU

Example fix

// before (needs a real surface)
App::new().add_plugins(DefaultPlugins);

// after (headless / CI)
App::new().add_plugins(
    DefaultPlugins.set(WindowPlugin {
        primary_window: None,
        exit_condition: ExitCondition::DontExit,
        ..default()
    }),
);
Defensive patterns

Strategy: fallback

Validate before calling

// Before building the app, decide whether a real surface is possible:
let headless = std::env::var("CI").is_ok() || std::env::var("DISPLAY").is_err() && cfg!(target_os = "linux");
// then choose WindowPlugin { primary_window: if headless { None } else { Some(Default::default()) }, ..default() }

Prevention

When it happens

Trigger: App::new() with a default primary window on a machine where the enabled wgpu backends cannot create a surface for it: no Vulkan/GL drivers, SSH session without X forwarding, Wayland/X11 mismatch, WGPU_BACKEND forcing an unsupported backend, or a browser canvas that fails context acquisition.

Common situations: Running on bare CI containers or remote servers without a display; forced WGPU_BACKEND=gl on a system without GLX/EGL; outdated GPU drivers on Windows/Linux; web builds targeting browsers without WebGL2.

Related errors


AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20). Data as JSON: /api/errors/fdfd625f5ea7d037. Report an issue: GitHub.