zig-gamedev/zgpu
Cross-platform graphics lib for Zig built on top of Dawn native WebGPU implementation.
7829cf02f78e8c39e19b802ccb47ed44037299c2.tar.gz
c0dbf11cdc17da5904ea8a17eadc54dee26567ec.tar.gz
d3a68014e6b6b53fd330a0ccba99e4dcfffddae5.tar.gz
7d70db023bf254546024629cbec5ee6113e12a42.tar.gz
c1f55e740a62f6942ff046e709ecd509a005dbeb.tar.gz
d2360cdfff0cf4a780cb77aa47c57aca03cc6dfe.tar.gz
901716b10b31ce3e0d3fe479326b41e91d59c661.tar.gz
Cross-platform graphics lib for Zig built on top of Dawn native WebGPU implementation.
Supports Windows 10+ (DirectX 12), macOS 12+ (Metal) and Linux (Vulkan).
Example build.zig
:
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{ ... });
@import("zgpu").addLibraryPathsTo(exe);
const zgpu = b.dependency("zgpu", .{});
exe.root_module.addImport("zgpu", zgpu.module("root"));
if (target.result.os.tag != .emscripten) {
exe.linkLibrary(zgpu.artifact("zdawn"));
}
}
Below you can find an overview of main zgpu
features.
You can override default options in your build.zig
:
pub fn build(b: *std.Build) void {
...
const zgpu = @import("zgpu").package(b, target, optimize, .{
.options = .{
.uniforms_buffer_size = 4 * 1024 * 1024,
.dawn_skip_validation = false,
.buffer_pool_size = 256,
.texture_pool_size = 256,
.texture_view_pool_size = 256,
.sampler_pool_size = 16,
.render_pipeline_pool_size = 128,
.compute_pipeline_pool_size = 128,
.bind_group_pool_size = 32,
.bind_group_layout_pool_size = 32,
.pipeline_layout_pool_size = 32,
},
});
zgpu.link(exe);
...
}
Create a GraphicsContext
using a WindowProvider
. For example, using zglfw:
const gctx = try zgpu.GraphicsContext.create(
allocator,
.{
.window = window,
.fn_getTime = @ptrCast(&zglfw.getTime),
.fn_getFramebufferSize = @ptrCast(&zglfw.Window.getFramebufferSize),
// optional fields
.fn_getWin32Window = @ptrCast(&zglfw.getWin32Window),
.fn_getX11Display = @ptrCast(&zglfw.getX11Display),
.fn_getX11Window = @ptrCast(&zglfw.getX11Window),
.fn_getWaylandDisplay = @ptrCast(&zglfw.getWaylandDisplay),
.fn_getWaylandSurface = @ptrCast(&zglfw.getWaylandWindow),
.fn_getCocoaWindow = @ptrCast(&zglfw.getCocoaWindow),
},
.{}, // default context creation options
);
struct DrawUniforms = extern struct {
object_to_world: zm.Mat,
};
const mem = gctx.uniformsAllocate(DrawUniforms, 1);
mem.slice[0] = .{ .object_to_world = zm.transpose(zm.translation(...)) };
pass.setBindGroup(0, bind_group, &.{mem.offset});
pass.drawIndexed(...);
// When you are done encoding all commands for a frame:
gctx.submit(...); // Injects *one* copy operation to transfer *all* allocated uniforms
TextureViewHandle
knows about its
parent texture and becomes invalid when parent texture becomes invalid; BindGroupHandle
knows
about all resources it binds so it becomes invalid if any of those resources become invalidconst buffer_handle = gctx.createBuffer(...);
if (gctx.isResourceValid(buffer_handle)) {
const buffer = gctx.lookupResource(buffer_handle).?; // Returns `wgpu.Buffer`
const buffer_info = gctx.lookupResourceInfo(buffer_handle).?; // Returns `zgpu.BufferInfo`
std.debug.print("Buffer size is: {d}", .{buffer_info.size});
}
// If you want to destroy a resource before shutting down graphics context:
gctx.destroyResource(buffer_handle);
const DemoState = struct {
pipeline_handle: zgpu.PipelineLayoutHandle = .{},
...
};
const demo = try allocator.create(DemoState);
// Below call schedules pipeline compilation and returns immediately. When compilation is complete
// valid pipeline handle will be stored in `demo.pipeline_handle`.
gctx.createRenderPipelineAsync(allocator, pipeline_layout, pipeline_descriptor, &demo.pipeline_handle);
// Pass using our pipeline will be skipped until compilation is ready
pass: {
const pipeline = gctx.lookupResource(demo.pipeline_handle) orelse break :pass;
...
pass.setPipeline(pipeline);
pass.drawIndexed(...);
}
rgba8_unorm
, rg16_float
, rgba32_float
, etc.)texture_width == texture_height and isPowerOfTwo(texture_width)
rgba8_unorm
texture on GTX 1660// Usage:
gctx.generateMipmaps(arena, command_encoder, texture_handle);