Struct packing in Odin and how to match C

I have recently come across an issue porting some C code to Odin, specifically copying data from a struct into a UniformBuffer for the Vulkan API.

Original C code:

typedef struct {
	float t;
	mat4 mvps[1000];
} UniformBufferObject;

// SNIP

memcpy(uniform_buffers_mapped[current_frame], &ubo, sizeof(ubo));
printf("\nHEX DUMP:\n");
for (int i=0; i<256; i++) {
	printf("%02X", ((unsigned char*)uniform_buffers_mapped[current_frame])[i]);
	if (i % 16 == 15) {
		printf("\n");
	}
	else if (i % 4 == 3) {
		printf(" ");
	}
}
printf("\n");

Result:

HEX DUMP:  
B1D3AA40 00000000 00000000 00000000  
0000803F 00000000 00000000 00000000  
00000000 0000803F 00000000 00000000  
00000000 00000000 0000803F 00000000  
00000000 00000000 00000000 0000803F  
0000803F 00000000 00000000 00000000  
00000000 0000803F 00000000 00000000  
00000000 00000000 0000803F 00000000  
00000000 00000000 00000000 0000803F  

Odin Version:

UniformBufferObject :: struct {
	t: f32,
	mvps: [1000]glsl.mat4,
}

// SNIP

intrinsics.mem_copy(uniform_buffers_mapped[current_frame], &ubo, size_of(ubo))
fmt.println("\nHEX DUMP:")
bytes := slice.bytes_from_ptr(uniform_buffers_mapped[current_frame], size_of(ubo))
for i := 0; i < 265; i += 1 {
	if i >= len(bytes) {
		fmt.printfln("Broke at %d", i)
		break
	}

	v := bytes[i]
	fmt.printf("%02X", v)

	if i % 16 == 15 {
		fmt.print("\n")
	} else if i % 4 == 3 {
		fmt.print(" ")
	}
}

fmt.print("\n")

Output:

HEX DUMP:  
4685853F 00000000 00000000 00000000  
00000000 00000000 00000000 00000000  
0000803F 00000000 00000000 00000000  
00000000 0000803F 00000000 00000000  
00000000 00000000 0000803F 00000000  
00000000 00000000 00000000 0000803F  
0000803F 00000000 00000000 00000000  
00000000 0000803F 00000000 00000000  
00000000 00000000 0000803F 00000000  
00000000 00000000 00000000 0000803F 

Notice the row of zeroes on the second line in the above.

Fixed Version

UniformBufferObject :: struct #packed {
	t: f32,
	
	// Alignment issues...
	_: f32,
	_: f32,
	_: f32,

	mvps: [1000]glsl.mat4,
}

On this page: https://odin-lang.org/news/binding-to-c/#structs it says:

Odin structs with fields that are the same size as their C equivalents, (see core:c) Odin structs have the same layout as the equivalent C struct.

But when I ported the code, the alignment seems to be different (16 bytes vs 32). Is this:

  • Something I’m doing wrong?
  • A bug in the compiler?
  • The documentation is not accurate?
  • Something else entirely?

glsl.mat4 is a matrix[4,4]f32. For better SIMD support, this has an alignment of 32 bytes. That’s where your extra padding comes from. The difference is probably the mat4 in the original C code (not shown), vs. glsl.mat4.

You can also sort-of fix this by using #max_field_align(16) instead of #packed, though being explicit about the padding will probably work out better in the long run.

3 Likes

Thank you, that’s very helpful to know. I thought I was going crazy for a while today!

This definitely felt like a “gotcha” - the few examples of opengl / vulkan use the glsl package as if it were a drop-in replacement for glm (a lot of people seem to even import it with the name glm) but this is a difficult to spot and unintuitive difference!

I’m not saying it’s wrong by the way, but this took me a good couple of hours to debug.

I might talk to the guy who maintains the examples repository and see if he wants to include my test project as a Vulkan example. I should probably read his book too…

1 Like