bronter/wgpu_native_zig
Zig bindings for wgpu-native
master
master
master
master
master
master
master
master
master
master
Zig bindings for wgpu-native
This package exposes two modules: wgpu-c
and wgpu
.
wgpu-c
is just wgpu.h
(and by extension webgpu.h
) run through translate-c
, so as close to wgpu-native's original C API as is possible in Zig.
wgpu
is a module full of pure Zig bindings for libwgpu_native
, it does not import any C code and instead relies on extern fn
declarations to hook up to wgpu-native
.
Add the package to your dependencies, either with:
zig fetch --save https://github.com/bronter/wgpu_native_zig/archive/refs/tags/v6.0.0.tar.gz
or by manually adding to your build.zig.zon
:
.{
// ...other stuff
.dependencies = .{
// ...other dependencies
.wgpu_native_zig = .{
// You can either use a commit hash:
.url="https://github.com/bronter/wgpu_native_zig/archive/<commit_hash>.tar.gz",
// or a tagged release:
// .url = "https://github.com/bronter/wgpu_native_zig/archive/refs/tags/v6.0.0.tar.gz`
.hash="<dependency hash>"
}
}
}
Then, in build.zig
add:
const wgpu_native_dep = b.dependency("wgpu_native_zig", .{});
// Add module to your exe (wgpu-c can also be added like this, just pass in "wgpu-c" instead)
exe.root_module.addImport("wgpu", wgpu_native_dep.module("wgpu"));
// Or, add to your lib similarly:
lib.root_module.addImport("wgpu", wgpu_native_dep.module("wgpu"));
Windows x86_64 has two options for ABI: GNU and MSVC. For i686 and aarch64, only the MSVC option is available. If you need to specify the build target, you can do that with:
const target = b.standardTargetOptions(.{
.default_target = .{
// If not specified, defaults to the GNU abi
.abi = .msvc,
}
});
Or, specify it with your build command. For example, the triangle example in this repository can be run like so:
zig build run-triangle-example -Dtarget=x86_64-windows-msvc
Either way, pass the resolved target to the dependency like so:
const wgpu_native_dep = b.dependency("wgpu_native_zig", .{
.target = target
});
When using static linking with MSVC, you might encounter duplicate symbol errors. If so, try
if (target.result.abi == .msvc) {
// "exe" here is the *std.Build.Step.Compile from b.addExecutable() (or b.addTest())
exe.bundle_compiler_rt = false;
exe.bundle_ubsan_rt = false;
}
An example of using wgpu-native-zig
with static linking on Windows can be found at wgpu-native-zig-windows-test.
Dynamic linking can be made to work, though it is a bit messy to use.
When you initialize your wgpu_native_dep
, add the option for dynamic linking like so:
const wgpu_native_dep = b.dependency("wgpu_native_zig", .{
// Defaults to .static if you don't specify
.link_mode = .dynamic
});
Then add the following with your install step dependencies:
const lib_dir = wgpu_native_dep.namedWriteFiles("lib").getDirectory();
// This would also work with .so files on linux
const dll_path = lib_dir.join(b.allocator, "wgpu_native.dll") catch return;
// addInstallBinFile puts the dll in the same directory as your executable
const install_dll = b.addInstallBinFile(dll_path, "wgpu_native.dll");
// Make sure that the dll is installed when the install step is run
b.getInstallStep().dependOn(&install_dll.step);
wgpu
module differs from wgpu-c
wgpu.WGPUSurfaceDescriptor
becomes wgpu.SurfaceDescriptor
[*c]
) are replaced with more specific pointer types.[*c]const u8
is replaced with ?[*:0]const u8
.wgpu.WGPUAdapter
from webgpu.h
would instead be expressed as *wgpu.Adapter
or ?*wgpu.Adapter
, depending on the context.wgpu.wgpuInstanceCreateSurface(instance: WGPUInstance, descriptor: [*c]const WGPUSurfaceDescriptor) WGPUSurface
becomesInstance.createSurface(self: *Instance, descriptor: *const SurfaceDescriptor) ?*Surface
fn handleRequestAdapter(
status: RequestAdapterStatus,
adapter: ?*Adapter,
message: StringView,
userdata1: ?*anyopaque,
userdata2: ?*anyopaque
) callconv(.C) void {
switch(status) {
.success => {
const ud_adapter: **Adapter = @ptrCast(@alignCast(userdata1));
ud_adapter.* = adapter.?;
},
else => {
std.log.err("{s}\n", .{message.toSlice()});
}
}
const completed: *bool = @ptrCast(@alignCast(userdata2));
completed.* = true;
}
var adapter_ptr: ?*Adapter = null;
var completed = false;
const request_adapter_info = RequestAdapterInfo {
.callback = handleRequestAdapter,
.userdata1 = @ptrCast(&adapter_ptr),
.userdata2 = @ptrCast(&completed),
}
const ra_future = instance.requestAdapter(null, request_adapter_info);
// There is currently no way to use a `Future`,
// it's supposed to be passed into `Instance.waitAny()`,
// which is unimplemented as of `wgpu_native` v24.0.3.1.
_ = ra_future;
instance.processEvents();
while(!completed) {
std.Thread.sleep(200_000_000);
instance.processEvents();
}
whereas the non-callback version looks like// The wrapper methods use polling, so 200_000_000 is the polling interval in nanoseconds.
const response = instance.requestAdapterSync(null, 200_000_000);
const adapter_ptr: ?*Adapter = switch (response.status) {
.success => response.adapter,
else => blk: {
std.log.err("{s}\n", .{response.message});
break :blk null;
}
};
SurfaceDescriptor{
.next_in_chain = @ptrCast(&SurfaceDescriptorFromXlibWindow {
.chain = ChainedStruct {
.s_type = SType.surface_descriptor_from_xlib_window,
},
.display = display,
.window = window,
}),
.label = "xlib_surface_descriptor",
};
or use a function to construct them:// Here the descriptors from SurfaceDescriptor and SurfaceDescriptorFromXlibWindow have been merged,
// so just pass in an anonymous struct with the things that you need; default values will take care of the rest.
surfaceDescriptorFromXlibWindow(.{
.label = "xlib_surface_descriptor",
.display = display,
.window = window
});
&(SurfaceConfiguration {
.device = device,
// other stuff
}).withDesiredMaxFrameLatency(2);
WGPUBool
is replaced with bool
whenever possible.bool
in the parameters and return values of methods, but not in structs or the parameters/return values of procs (which are supposed to be function pointers to things returned by wgpuGetProcAddress
).pipeline.zig
is actually only used by Device
, and should probably be in device.zig
instead.wgpu-native
; provided all the necessary tools/dependencies are present.wgpuGetProcAddress
but it's unimplemented in wgpu-native
. They are a pain to update by hand, so maybe they should be removed for now and made optional once we have a working bindings generator? Like the bindgen could put them in a separate wgpu-procs
module.