renderer/metal: fix 2x sizeof over-allocation in Buffer.sync (#13490)

Seems to me the grow path in `Buffer.sync` (and `syncFromArrayLists`)
multiplies by `@sizeOf(T)` twice: `req_bytes` is already a byte count,
it gets doubled into size, and then the `newBufferWithLength:` call does
`size * @sizeOf(T)` on top of that. So every reallocation ends up being
`data.len` × `@sizeOf(T)`^2 × 2 bytes instead of the intended `data.len`
× `@sizeOf(T)` × 2.

The OpenGL version of this same helper does it what seems to be the
intended way, so this looks like a mixup rather than a deliberate safety
margin.

This makes the Metal implementation match the OpenGL one: track the new
length in units of T (which also fixes self.len going stale after a
grow. It's documented as the allocated element count but was never
updated here)
This commit is contained in:
Mitchell Hashimoto
2026-07-27 19:55:34 -07:00
committed by GitHub

View File

@@ -81,12 +81,12 @@ pub fn Buffer(comptime T: type) type {
self.buffer.msgSend(void, objc.sel("release"), .{});
// Allocate a new buffer with enough to hold double what we require.
const size = req_bytes * 2;
self.len = data.len * 2;
self.buffer = self.opts.device.msgSend(
objc.Object,
objc.sel("newBufferWithLength:options:"),
.{
@as(c_ulong, @intCast(size * @sizeOf(T))),
@as(c_ulong, @intCast(self.len * @sizeOf(T))),
self.opts.resource_options,
},
);
@@ -138,12 +138,12 @@ pub fn Buffer(comptime T: type) type {
self.buffer.msgSend(void, objc.sel("release"), .{});
// Allocate a new buffer with enough to hold double what we require.
const size = req_bytes * 2;
self.len = total_len * 2;
self.buffer = self.opts.device.msgSend(
objc.Object,
objc.sel("newBufferWithLength:options:"),
.{
@as(c_ulong, @intCast(size * @sizeOf(T))),
@as(c_ulong, @intCast(self.len * @sizeOf(T))),
self.opts.resource_options,
},
);