Quick answer: Add row_major qualifier to the HLSL matrix uniform, or transpose the matrix in code before shader_set_uniform_matrix.

A 3D-effect shader expects a world transformation matrix. Object rotates around the wrong axis. Translation in the wrong direction. The matrix arrived transposed.

Row-Major Qualifier in HLSL

row_major float4x4 u_world;

PS_OUTPUT vs_main(VS_INPUT v) {
    PS_OUTPUT o;
    o.pos = mul(v.pos, u_world);
    return o;
}

Add row_major in front of the type. HLSL adjusts multiplication semantics — data is row-major, math still works correctly.

GLSL Equivalent

For OpenGL targets (HTML5, Linux):

layout(row_major) uniform mat4 u_world;

Or transpose in shader code: u_world = transpose(u_world). Match the convention; pick one.

Or Transpose in Code

function transpose_matrix(m) {
    return [m[0], m[4], m[8], m[12],
            m[1], m[5], m[9], m[13],
            m[2], m[6], m[10], m[14],
            m[3], m[7], m[11], m[15]];
}

shader_set_uniform_matrix_array(u_world_loc, transpose_matrix(world_mat));

Transposes 4×4 in GML. Costly per-frame; prefer the row_major qualifier in shader.

Multiplication Direction

In HLSL, mul(v, m) treats v as row vector; mul(m, v) treats v as column. Pick one convention; if results look transposed, swap multiplication order.

Verify with Identity

Send the identity matrix from CPU. Object should render unchanged. Any transformation = orientation mismatch — either data or shader convention is wrong.

Verifying

Send a known rotation (45° around Y). Object rotates as expected. Translation moves correctly. Cross-test on each target platform (HTML5 + Win + macOS).

“Matrix layout is a convention. Pick row-major end-to-end and the math works.”

For shaders shared across engines, document matrix layout in a header comment — saves the next dev hours of debugging.