Quick answer: The vertex format definition, the order/type of values you write per vertex, and the shader’s attribute layout must all agree. One mismatch = garbage or crash.
A custom mesh renderer crashes on submit, or draws a scrambled mess. The data written per vertex doesn’t match the declared vertex format.
Define the Format Once
vertex_format_begin();
vertex_format_add_position_3d(); // 3 floats
vertex_format_add_normal(); // 3 floats
vertex_format_add_texcoord(); // 2 floats
vertex_format_add_colour(); // 4 bytes
global.fmt = vertex_format_end();
Write in the Exact Same Order
vertex_begin(vbuff, global.fmt);
vertex_position_3d(vbuff, x, y, z); // position
vertex_normal(vbuff, nx, ny, nz); // normal
vertex_texcoord(vbuff, u, v); // texcoord
vertex_colour(vbuff, c_white, 1); // colour
vertex_end(vbuff);
The write calls must follow the format declaration order exactly. Swap two and every vertex is misread.
Match the Shader
The vertex shader’s in attributes must line up with the format. GameMaker’s default attributes are in_Position, in_Normal, in_TextureCoord, in_Colour. A custom shader must declare matching attributes in the same order.
Don't Mix Formats
A vertex buffer is bound to one format. If you reuse a buffer expecting a different format, you get garbage. One format per buffer; recreate if the layout changes.
Verifying
The mesh renders correctly — positions, normals (lighting), UVs, and colors all right. No crash on submit. Changing the format and writes together keeps it valid.
“Format declaration, write order, and shader attributes are one contract. Change them together.”
Keep the format definition and the vertex-write function next to each other in the same script file — editing one without the other is the usual cause of mismatch.