-
Notifications
You must be signed in to change notification settings - Fork 61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Functions Pointers #19
Comments
My idea for doing this was to create different wrapper structs depending on the required features: const DeviceDispatch = vk.DeviceWrapper(.{
.DestroyDevice,
});
const DeviceSwapchainDispatch = vk.DeviceWrapper(.{
.CreateSwapchainKHR,
});
device_functions: DeviceDispatch,
swapchain_functions: ?SwapchainDispatch,
I don't think loading all the function pointers at once by default is a good idea, though there might be something that can be done here. For example, since #11 wrappers are made by passing an array of the desired functions instead of a struct, and so the generator could export array literals that define all commands of different versions/extensions. Think something like
There is no hard dependency on GLFW, that is just for the example. That uses GLFW for the window, and GLFW loads Vulkan regardless (if the Vulkan backend is selected). Loading the Vulkan dynamic library yourself while also using GLFW is asking for trouble, because both implementations might resolve to a different dynamic library (for instance, one might choose the SDK version and the other the system version), which leads to horrible nightmares. |
I was actually able to fix it on my local copy by modifying the renderer. I ran into some problems like extensions that only existed on certain platforms, which I fixed by adding a whitelist parameter to the build and only loading functions not part of an extension or from an extension from the whitelist. I can submit a PR if you'd like but I'm currently using the 0.8 compat branch so there may be some merge conflicts. Also, I made some convenience changes to the wrappers that allow you to pass in the instance/device+allocator and saves it in the wrapper since AFAIK they cannot change once they are set so no point in manually passing it in each time, if you're also interested in that. pub const InstanceWrapper = comptime blk: {
const cmds = @typeInfo(InstanceCommand).Enum.fields;
comptime var fields: [cmds.len]std.builtin.TypeInfo.StructField = undefined;
inline for (cmds) |cmd, i| {
const cmd_type = @field(pfn, cmd.name);
fields[i] = .{
.name = "vk" ++ cmd.name,
.field_type = cmd_type,
.default_value = null,
.is_comptime = false,
.alignment = @alignOf(*cmd_type),
};
}
const Dispatch = @Type(.{
.Struct = .{
.layout = .Auto,
.fields = &fields,
.decls = &[_]std.builtin.TypeInfo.Declaration{},
.is_tuple = false,
},
});
break :blk struct {
handle: Instance,
allocator: ?*const AllocationCallbacks,
dispatch: Dispatch,
const Self = @This();
pub fn load(handle: Instance, allocator: ?*const AllocationCallbacks, loader: anytype) !Self {
var self: Self = .{
.handle = handle,
.allocator = allocator,
.dispatch = undefined,
};
inline for (std.meta.fields(Dispatch)) |field| {
const name = @ptrCast([*:0]const u8, field.name ++ "\x00");
if (loader(handle, name)) |cmd_ptr| @field(self.dispatch, field.name) = @ptrCast(field.field_type, cmd_ptr);
}
return self;
}
// Example of a function that uses the handle and allocator in the wrapper.
pub fn destroyInstance(
self: Self,
) void {
self.dispatch.vkDestroyInstance(
self.handle,
self.allocator,
);
}
// Other functions here...
} I'm also considering passing success codes (besides VK_SUCCESS) as errors since usually you want to handle them like errors anyway. But that's probably more a matter of taste and I'll have to try it out for sure.
I didn't actually know about that. In that case I understand. |
I think that allocators may be changed between calls, so long as you make sure the create, destroy and any related functions accept the same allocator. I'm not sure how well that is supported in drivers though. Regarding the instance, i thought about doing that also, but there are a number of things with that. For example, it would require you to store the instance/device twice if you're making two different wrappers like i did above, although i guess you could argue that the overhead is relatively small compared to all the function pointers in that struct anyway. Initially my opinion was that passing the handle with the pointers creates this weird asymmetry where if you use multiple wrappers where one has the device and the other not like how it works with ash, but i guess it would not be an issue if invalid function pointers are simply ignored. In that case it would be the programmer's discretion to not call invalid function pointers. In your example, i recommend setting the function pointer explicitly to undefined, so that you can catch invalid calls in debug mode: const name = @ptrCast([*:0]const u8, field.name ++ "\x00");
@field(self.dispatch, field.name) = @ptrCast(field.field_type, loader(handle, name) orelse undefined);
Note that Vulkan might return a success code AND a valid return value, so this is invalid. For example I was hoping ziglang/zig#498 would be accepted to allow a user to write |
I have added an additional
According to the vulkan spec calling |
It may be due to the way I'm gathering the functions but I get this error:
This is the diff of the
Note that I'm not actually using any win32 functions, just referencing |
Is that still from your own fork? If so, it's because it's trying to reference some functions for windows-extensions which normally don't work under another operating system. I think this could be resolved by doing something like const functions = instance_functions ++ (if (is_windows) windows_functions else ...); in the upstream version, but it's not quite related to any issue i had in mind with |
Hi. If I load a function pointer of a disabled extension, the segfault happens in NVIDIA auxiliary thread not the main thread. There will be no stack trace. You can only capture this if validation layer is enabled. Regardless of use
dhh@dhhpc ~> strings /usr/lib/libnvidia-glcore.so.545.29.02 | grep vkcf
[vkcf] Analysis
vkcfPass::TransferToSys
dhh@dhhpc ~> strings /usr/lib/libnvidia-glcore.so.545.29.02 | grep vkrt
[vkrt] Analysis
dhh@dhhpc ~> strings /usr/lib/libnvidia-glcore.so.545.29.02 | grep vkps
[vkps] Update
Separate dispatchs is a good approach. This is how rust ash crate does it. I think it might be a good idea to create a new enhancement issue for associating an extension with the functions it provides. |
Thats a good idea, something that I probably should have looked into a while ago... created #112 |
Some of the extensions I'm using are optional which means that sometimes the function pointers will not exist. The problem with this is that the api fails when it hits a function pointer it can't find.
const cmd_ptr = loader(.null_handle, name) orelse return error.CommandLoadFailure;
Maybe it could return one after it has set all the pointers.
Also, why not just load all the function pointers you can instead of having to define them one-by-one? If the pointer doesn't exist just skip it. Less hassle.
Finally, I think you could probably offer a builtin load library to remove the dependency on something like GLFW. Here's what I use:
The text was updated successfully, but these errors were encountered: