bevyengine/bevy · error · MeshToMeshletMeshConversionError

Mesh vertex attributes must be {required:?}, but got {provid

Error message

Mesh vertex attributes must be {required:?}, but got {provided:?}

What it means

`MeshletMesh::from_mesh` converts a regular `Mesh` into the meshlet format used by bevy_pbr's meshlet renderer. Before conversion, `validate_input_mesh` (crates/bevy_pbr/src/meshlet/from_mesh.rs:249) requires the mesh's vertex attribute list to EXACTLY equal `[Mesh::ATTRIBUTE_POSITION (Float32x3), Mesh::ATTRIBUTE_NORMAL (Float32x3), Mesh::ATTRIBUTE_UV_0 (Float32x2)]` — same ids, same formats, same order, no extras. The error message prints both the required and the provided attribute lists so you can diff them.

Source

Thrown at crates/bevy_pbr/src/meshlet/from_mesh.rs:1095

        n.xy()
    } else {
        octahedral_wrap
    }
}

// https://www.w3.org/TR/WGSL/#pack2x16snorm-builtin
fn pack2x16snorm(v: Vec2) -> u32 {
    let v = v.clamp(Vec2::NEG_ONE, Vec2::ONE);
    let v = (v * 32767.0 + 0.5).floor().as_i16vec2();
    bytemuck::cast(v)
}

/// An error produced by [`MeshletMesh::from_mesh`].
#[derive(Error, Debug)]
pub enum MeshToMeshletMeshConversionError {
    #[error("Mesh primitive topology is not TriangleList")]
    WrongMeshPrimitiveTopology,
    #[error("Mesh vertex attributes must be {required:?}, but got {provided:?}")]
    WrongMeshVertexAttributes {
        required: [MeshVertexAttribute; 3],
        provided: Vec<MeshVertexAttribute>,
    },
    #[error("Mesh has no indices")]
    MeshMissingIndices,
}

View on GitHub (pinned to 396ca72708)

Solutions

  1. Diff the two lists in the error message against `[POSITION, NORMAL, UV_0]`; the `provided` list shows exactly what is missing, extra, or mis-ordered
  2. Generate missing data with the Mesh helpers: `mesh.generate_padded_normals()` and `mesh.generate_padded_uvs()`
  3. Remove extra attributes the meshlet pipeline rejects: `mesh.remove_attribute(Mesh::ATTRIBUTE_TANGENT)` (also VERTEX_COLOR, UV_1, …)
  4. Ensure formats are Float32x3/Float32x3/Float32x2; re-insert attributes in that exact order with `mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, Vec<Vec2>)`

Example fix

// before: shape primitive has no UVs -> WrongMeshVertexAttributes
let mesh = Mesh::from(shape::Plane::from_size(2.0));
let meshlet = MeshletMesh::from_mesh(mesh, &viewer_position);

// after: fill in the missing attribute, strip extras
let mut mesh = Mesh::from(shape::Plane::from_size(2.0));
mesh.generate_padded_normals();
mesh.generate_padded_uvs();
mesh.remove_attribute(Mesh::ATTRIBUTE_TANGENT); // if present
let meshlet = MeshletMesh::from_mesh(mesh, &viewer_position);
Defensive patterns

Strategy: validation

Validate before calling

fn has_meshlet_attributes(mesh: &Mesh) -> bool {
    let required = [
        (Mesh::ATTRIBUTE_POSITION.id, Mesh::ATTRIBUTE_POSITION.format),
        (Mesh::ATTRIBUTE_NORMAL.id, Mesh::ATTRIBUTE_NORMAL.format),
        (Mesh::ATTRIBUTE_UV_0.id, Mesh::ATTRIBUTE_UV_0.format),
    ];
    let provided: Vec<_> = mesh
        .attributes()
        .map(|(a, _)| (a.id, a.format))
        .collect();
    provided == required
}

if !has_meshlet_attributes(&mesh) {
    mesh.generate_padded_normals();
    mesh.generate_padded_uvs();
    // also strip TANGENT / extra UVs, then re-check
}

Try / catch

match MeshletMesh::from_mesh(mesh, &viewer) {
    Err(MeshToMeshletMeshConversionError::WrongMeshVertexAttributes { required, provided }) => {
        warn!(?provided, ?required, "mesh rejected by meshlet converter");
        continue;
    }
    Err(e) => return Err(e.into()),
    Ok(meshlet) => { /* ... */ }
}

Prevention

When it happens

Trigger: Calling `MeshletMesh::from_mesh(&mesh)` (or running the meshlet asset processor) on a mesh that is missing `NORMAL` or `UV_0`, carries extra attributes (`TANGENT`, `VERTEX_COLOR`, a second UV set), stores a required attribute in a different format (e.g. normals as non-Float32x3, UV as Float32x3), or lists attributes in a different order — the comparison uses iterator `.ne`, so any difference in the sequence fails.

Common situations: GLTF assets usually include TANGENT or extra UV channels; built-in shape primitives (`Mesh::from(shape::Plane::default())` etc.) ship without UVs; procedurally generated meshes often omit normals. Switching a project to `bevy_meshlet` assets surfaces all of these at asset-load time.

Related errors


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