Hi. I’ve been writing Vulkan stuff (just the basics) with Zig. Many SDL and Vk functions have this pattern of first getting the required list size by calling once with a null ptr, then calling again with a valid ptr to sufficient memory for holding a list.
With Zig I managed to get this to work using Allocator.alloc like so:
var count = 0;
c.vkEnumerateThings(&count, null); // ignore errors
var things = try allocator.alloc(c.VkThing, count);
c.vkEnumerateThings(&count, @ptrCast([*c]c.VkThing, &things[0]));
std.debug.assert(count == things.len);
// Now things is a valid slice of c.VkThing values.
One issue: I feel like @ptrCast
and especially this C++ -ism of taking the address of the first element (&things[0]
) is a hack. If I slip up and forget the [0] for example, the slice’s len
may get overwritten and terrible things may happen. Is there an automatic way to coerce a slice to a many-item pointer? Just writing things
or &things
there without a cast did not work (at least in some cases).
No need for @ptrCast
; use things.ptr
.
4 Likes
Thank you very much. It’s there in the docs, I just should’ve had some sleep to be able to see it.
1 Like
I can’t believe it was possible. I even abstracted this pattern in Zig like so:
fn EnumerateFunctionOutputType(comptime func: anytype) type {
const FuncType = @TypeOf(func);
const info = @typeInfo(FuncType);
if (info != .Fn) {
@compileError("Expected function, found " ++ @typeName(FuncType));
}
const params = info.Fn.params;
if (params.len < 2) {
@compileError("Expected function with at least two parameters");
}
const last_param = params[params.len - 1];
const param_info = @typeInfo(last_param.type.?);
if (param_info != .Pointer) {
@compileError("Expected function which has a pointer type as the last parameter");
}
return param_info.Pointer.child;
}
fn enumerateToSlice(
self: *VkBuilder,
enumerateFunc: anytype,
prefix_args: anytype,
success: anytype,
) Error![]EnumerateFunctionOutputType(enumerateFunc) {
var count: u32 = 0;
var ret = @call(.auto, enumerateFunc, prefix_args ++ .{ &count, null });
if (ret != success) {
return Error.VkInitError;
}
var buf = try self.allocator.alloc(EnumerateFunctionOutputType(enumerateFunc), count);
errdefer self.allocator.free(buf);
ret = @call(.auto, enumerateFunc, prefix_args ++ .{ &count, buf.ptr });
if (ret != success) {
return Error.VkInitError;
}
std.debug.assert(count == buf.len);
return buf;
}
Took me a day, but it’s so rewarding to have this work!
Now I can call it like this (and there are quite a few places to do so):
var devices = self.enumerateToSlice(
c.vkEnumeratePhysicalDevices,
.{instance},
c.VK_SUCCESS,
);
2 Likes