bevyengine/bevy · error · ShaderCacheError

Could not create shader module: {0}

Error message

Could not create shader module: {0}

What it means

ShaderCacheError::CreateShaderModule(description) is produced in bevy_render's create_shader_module helper (pipeline_cache.rs:144-170): a wgpu error scope with ErrorFilter::Validation is pushed around module creation, and any validation error is captured and returned as this variant. It means naga/wgpu (or the driver) rejected the final WGSL at module creation. On native the error is caught early; on wasm the browser APIs are async, so the app instead crashes inside wgpu.

Source

Thrown at crates/bevy_shader/src/shader_cache.rs:546

        let asset_id = self.module_path_to_asset_id.get(&module_path)?;
        let shader = self.shaders.get(asset_id)?;
        Some(shader.path.clone())
    }
}

/// Type of error returned by a `PipelineCache` when the creation of a GPU pipeline object failed.
#[expect(missing_docs, reason = "Enum variants are self-explanatory")]
#[derive(Error, Debug)]
pub enum ShaderCacheError {
    #[error(
        "Pipeline could not be compiled because the following shader could not be loaded: {0:?}"
    )]
    ShaderNotLoaded(AssetId<Shader>),
    #[error("Failed to process shader:\n{0}")]
    ProcessShaderError(String),
    #[error("Shader import not yet available.")]
    ShaderImportNotYetAvailable,
    #[error("Could not create shader module: {0}")]
    CreateShaderModule(String),
}

#[cfg(test)]
mod tests {
    use super::*;

    fn test_cache() -> ShaderCache<String, ()> {
        ShaderCache::new((), |_, source, _| match source {
            ShaderCacheSource::Wgsl(wgsl) => Ok(wgsl),
            _ => panic!("expected wgsl output"),
        })
    }

    #[test]
    fn import_resolution() {
        let mut cache = test_cache();

View on GitHub (pinned to 227d3a6c66)

Solutions

  1. Read the wgpu validation description — it names the entry point and the rejected instruction
  2. Move implicit-LOD texture reads in vertex stages to textureSampleLevel/textureLoad
  3. Align @group/@binding annotations with your bind group layouts
  4. Validate the WGSL offline with the naga CLI or naga validators to get exact source spans
  5. Update wgpu/Bevy if validation rules changed

Example fix

// before — validation error: textureSample must only be used in fragment shaders
@vertex
fn vs_vs(vertex: VertexOutput) -> VertexOutput {
    let light = textureSample(t_light_map, s_light_map, vertex.uv);
    ...
}

// after — explicit level is valid in vertex stage
@vertex
fn vs_vs(vertex: VertexOutput) -> VertexOutput {
    let light = textureSampleLevel(t_light_map, s_light_map, vertex.uv, 0.0);
    ...
}
Defensive patterns

Strategy: try-catch

Try / catch

match create_shader_module(device, descriptor, ValidateShader::Enabled) {
    Ok(module) => module,
    Err(ShaderCacheError::CreateShaderModule(desc)) => {
        error!("wgpu rejected shader module: {desc}");
        fallback_module
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: WGSL that passes preprocessing but fails naga/driver validation: using textureSample (implicit derivatives) in a @vertex entry point, unknown functions/types, bindings that do not match the bind group layouts, or feature-gated WGSL not supported by the adapter.

Common situations: Porting shaders from other APIs (HLSL/GLSL habits); @group/@binding indices drifting out of sync with layout changes; enabling experimental WGSL extensions on older drivers; validation rules tightening after a wgpu/Bevy upgrade.

Related errors


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