Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
71c4ebc
Initial work on making Instance structs/methods more idiomatic
bronter Jun 30, 2025
1f27a43
Similar initial work for Device structs/methods
TotoShampoin Jul 1, 2025
6d69a8e
Merge pull request #25 from TotoShampoin/idiomatic-zig
bronter Jul 2, 2025
c6929f7
use const for u8 slices
bronter Jul 2, 2025
61a4267
More idiomatic Adapter structs and methods
TotoShampoin Jul 2, 2025
3da9f41
Fixed typo
TotoShampoin Jul 2, 2025
ba7a6fd
Merge pull request #26 from TotoShampoin/idiomatic-zig
bronter Jul 3, 2025
3fe903e
Try new callback format on Instance.requestAdapter()
bronter Jul 4, 2025
40a48f3
Implement new callback format for Adapter.requestDevice()
bronter Jul 4, 2025
9751d0c
Implement toWGPU() method for structs in instance.zig
bronter Jul 5, 2025
6a65105
Implement toWGPU() method for structs in adapter.zig and device.zig
TotoShampoin Jul 5, 2025
b4ad365
Implement toWGPU() method for structs in adapter.zig
TotoShampoin Jul 5, 2025
c05d469
Implement toWGPU() method for structs in device.zig
TotoShampoin Jul 5, 2025
659bebc
Merge branch 'idiomatic-zig' of https://github.com/TotoShampoin/wgpu_…
TotoShampoin Jul 5, 2025
1daeaeb
Merge pull request #28 from TotoShampoin/idiomatic-zig
bronter Jul 7, 2025
3ed91cd
Merge branch 'main' into idiomatic-zig
bronter Jul 8, 2025
fb80134
Test and related bugfixes for DeviceDescriptor.toWGPU()
bronter Jul 16, 2025
1ad74c4
Run unit tests from root to avoid duplicate test runs
bronter Jul 17, 2025
ee41b19
Merge branch 'main' into idiomatic-zig
bronter Jul 17, 2025
fe8bbc7
refAllDecls from root
bronter Jul 17, 2025
7b8a8bc
Merge branch 'main' into idiomatic-zig
bronter Jul 17, 2025
2ab86e1
Using packed structs instead of ORed consts
TotoShampoin Jul 17, 2025
4417379
Added a missing `all` const
TotoShampoin Jul 17, 2025
9a42a59
Removed commented out code
TotoShampoin Jul 17, 2025
c4e00d8
Merge pull request #31 from TotoShampoin/idiomatic-zig
bronter Jul 17, 2025
e78ba41
Remove all Procs
bronter Jul 17, 2025
d86fa00
Added some constructor functions for DeviceLostCallbackInfo and Uncap…
bronter Jul 18, 2025
9930d3f
Add tests for deviceLostCallbackInfo() and uncapturedErrorCallbackInf…
bronter Jul 18, 2025
bebc63b
Tweak CallbackInfo struct construction a bit more
bronter Jul 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,17 +197,15 @@ b.getInstallStep().dependOn(&install_dll.step);
}).withDesiredMaxFrameLatency(2);
```
* `WGPUBool` is replaced with `bool` whenever possible.
* This pretty much means, it is replaced with `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`).
* This pretty much means, it is replaced with `bool` in the parameters and return values of methods, but not in structs

## TODO
* Test this on other machines with different OS/CPU. Currently only tested on x86_64-linux-gnu and x86_64-windows (msvc and gnu); zig version 0.14.0.
* Cleanup/organization:
* If types are only tied to a specific opaque struct, they should be decls inside that struct.
* The associated Procs struct should probably be a decl of the opaque struct as well.
* There are many things that seem to be in the wrong file.
* For example a lot of what is in `pipeline.zig` is actually only used by `Device`, and should probably be in `device.zig` instead.
* Since pointers to opaque structs are made explicit, it would be more consistent if pointers to callback functions are explicit as well.
* Port [wgpu-native-examples](https://github.com/samdauwe/webgpu-native-examples) using wrapper code, as a basic form of documentation.
* Custom-build `wgpu-native`; provided all the necessary tools/dependencies are present.
* Bindgen using [the webgpu-headers yaml](https://github.com/webgpu-native/webgpu-headers/blob/main/webgpu.yml)?
* The proc definitions are mainly there since they are also present in the webgpu headers and I didn't fully understand what they were for when I started working on this project. However, I know better now and they aren't really used for anything currently. They're supposed to be used with `wgpuGetProcAddress` but it's [unimplemented in `wgpu-native`](https://github.com/gfx-rs/wgpu-native/issues/223). 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.
82 changes: 34 additions & 48 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -263,64 +263,50 @@ fn triangle_example(b: *std.Build, context: *const WGPUBuildContext) void {
}

fn unit_tests(b: *std.Build, context: *const WGPUBuildContext) void {
const unit_test_step = b.step("test", "Run unit tests");
const unit_test_step = b.step("unit-tests", "Run unit tests");
if (context.is_windows) {
unit_test_step.dependOn(b.getInstallStep());
}

const test_files = [_][:0]const u8{
"src/instance.zig",
"src/adapter.zig",
"src/pipeline.zig",
};
comptime var test_names: [test_files.len][:0]const u8 = test_files;
comptime for (test_files, 0..) |test_file, idx| {
const test_name = test_file[4..(test_file.len - 4)] ++ "-test";
test_names[idx] = test_name;
};

for (test_files, test_names) |test_file, test_name| {
// TODO: Seems weird to have a mod for each unit test, should probably revisit this.
const test_mod = b.createModule(.{
.root_source_file = b.path(test_file),
.target = context.target,
.optimize = context.optimize,
});
const t = b.addTest(.{
.name = test_name,
.root_module = test_mod,
});
handle_rt(context, t);
if (context.libwgpu_path != null) {
t.addObjectFile(context.libwgpu_path.?);
}
if (context.is_windows) {
t.linkLibC();
} else {
t.linkLibCpp();
}
const test_mod = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = context.target,
.optimize = context.optimize,
});
const t = b.addTest(.{
.name = "root_unit",
.root_module = test_mod,
});
handle_rt(context, t);
if (context.libwgpu_path != null) {
t.addObjectFile(context.libwgpu_path.?);
}
if (context.is_windows) {
t.linkLibC();
} else {
t.linkLibCpp();
}

const run_test = b.addRunArtifact(t);
const run_test = b.addRunArtifact(t);

if (context.is_mac) {
link_mac_frameworks(t);
}
if (context.is_mac) {
link_mac_frameworks(t);
}

if (context.link_mode == .dynamic) {
dynamic_link(context, t, run_test);
} else if (context.is_windows) {
if (context.target.result.abi == .gnu) {
link_windows_system_libraries(std.Build.Step.Compile, t, true);
if (context.link_mode == .dynamic) {
dynamic_link(context, t, run_test);
} else if (context.is_windows) {
if (context.target.result.abi == .gnu) {
link_windows_system_libraries(std.Build.Step.Compile, t, true);

// TODO: Find out why this is only required here; seems suspicious
t.linkSystemLibrary2("unwind", .{});
} else {
link_windows_system_libraries(std.Build.Step.Compile, t, false);
}
// TODO: Find out why this is only required here; seems suspicious
t.linkSystemLibrary2("unwind", .{});
} else {
link_windows_system_libraries(std.Build.Step.Compile, t, false);
}

unit_test_step.dependOn(&run_test.step);
}

unit_test_step.dependOn(&run_test.step);
}

fn compute_tests(b: *std.Build, context: *const WGPUBuildContext) void {
Expand Down
42 changes: 17 additions & 25 deletions examples/triangle/triangle.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,28 @@ fn handleBufferMap(status: wgpu.MapAsyncStatus, _: wgpu.StringView, userdata1: ?
// Based off of headless triangle example from https://github.com/eliemichel/LearnWebGPU-Code/tree/step030-headless

pub fn main() !void {
const instance = wgpu.Instance.create(null).?;
const instance = try wgpu.Instance.create(null);
defer instance.release();

const adapter_request = instance.requestAdapterSync(&wgpu.RequestAdapterOptions {}, 0);
const adapter = switch(adapter_request.status) {
.success => adapter_request.adapter.?,
else => return error.NoAdapter,
};
const adapter = try instance.requestAdapterSync(wgpu.RequestAdapterOptions {}, 0);
defer adapter.release();

const device_request = adapter.requestDeviceSync(instance, &wgpu.DeviceDescriptor {
const device = try adapter.requestDeviceSync(instance, .{
.required_limits = null,
}, 0);
const device = switch(device_request.status) {
.success => device_request.device.?,
else => return error.NoDevice,
};
defer device.release();

const queue = device.getQueue().?;
const queue = try device.getQueue();
defer queue.release();

const swap_chain_format = wgpu.TextureFormat.bgra8_unorm_srgb;

const target_texture = device.createTexture(&wgpu.TextureDescriptor {
const target_texture = try device.createTexture(&wgpu.TextureDescriptor {
.label = wgpu.StringView.fromSlice("Render texture"),
.size = output_extent,
.format = swap_chain_format,
.usage = wgpu.TextureUsages.render_attachment | wgpu.TextureUsages.copy_src,
}).?;
.usage = wgpu.TextureUsage{ .render_attachment = true, .copy_src = true },
});
defer target_texture.release();

const target_texture_view = target_texture.createView(&wgpu.TextureViewDescriptor {
Expand All @@ -57,17 +49,17 @@ pub fn main() !void {
.array_layer_count = 1,
}).?;

const shader_module = device.createShaderModule(&wgpu.shaderModuleWGSLDescriptor(.{
const shader_module = try device.createShaderModule(&wgpu.shaderModuleWGSLDescriptor(.{
.code = @embedFile("./shader.wgsl"),
})).?;
}));
defer shader_module.release();

const staging_buffer = device.createBuffer(&wgpu.BufferDescriptor {
const staging_buffer = try device.createBuffer(&wgpu.BufferDescriptor {
.label = wgpu.StringView.fromSlice("staging_buffer"),
.usage = wgpu.BufferUsages.map_read | wgpu.BufferUsages.copy_dst,
.usage = wgpu.BufferUsage{ .map_read = true, .copy_dst = true },
.size = output_size,
.mapped_at_creation = @as(u32, @intFromBool(false)),
}).?;
});
defer staging_buffer.release();

const color_targets = &[_] wgpu.ColorTargetState{
Expand All @@ -88,7 +80,7 @@ pub fn main() !void {
},
};

const pipeline = device.createRenderPipeline(&wgpu.RenderPipelineDescriptor {
const pipeline = try device.createRenderPipeline(&wgpu.RenderPipelineDescriptor {
.vertex = wgpu.VertexState {
.module = shader_module,
.entry_point = wgpu.StringView.fromSlice("vs_main"),
Expand All @@ -101,15 +93,15 @@ pub fn main() !void {
.targets = color_targets.ptr
},
.multisample = wgpu.MultisampleState {},
}).?;
});
defer pipeline.release();

{ // Mock main "loop"
const next_texture = target_texture_view;

const encoder = device.createCommandEncoder(&wgpu.CommandEncoderDescriptor {
const encoder = try device.createCommandEncoder(&wgpu.CommandEncoderDescriptor {
.label = wgpu.StringView.fromSlice("Command Encoder"),
}).?;
});
defer encoder.release();

const color_attachments = &[_]wgpu.ColorAttachment{
Expand Down Expand Up @@ -155,7 +147,7 @@ pub fn main() !void {
queue.submit(&[_]*const wgpu.CommandBuffer{command_buffer});

var buffer_map_complete = false;
_ = staging_buffer.mapAsync(wgpu.MapModes.read, 0, output_size, wgpu.BufferMapCallbackInfo {
_ = staging_buffer.mapAsync(wgpu.MapMode{ .read = true }, 0, output_size, wgpu.BufferMapCallbackInfo {
.callback = handleBufferMap,
.userdata1 = @ptrCast(&buffer_map_complete),
});
Expand Down
Loading