gpu: make requestDevice callback-based, add waitForDevice helper
Previously we were attempting to turn WebGPU async functions, which are exposed by `webgpu.h` as callbacks, into Zig async functions. This in practice turns out harder than expected. For example, `Buffer.mapAsync` / `wgpuBufferMapAsync` cannot easily be exposed as a Zig async function for a few reasons: 1. The callback is merely guaranteed to be called once the buffer's content is ready to be accessed via `wgpuBufferGetMappedRange` - but there is no strict guarantee about when that is. It could be 1-3 frames later, in theory, I believe. 2. The non-deterministic timing means that one would wish to poll "has the async function returned?" but this isn't trivial without our own scheduler. 3. Zig has a fair amount of async rework in the future that is coming, and I imagine it will be one of the later things that is fully supported by the WebAssembly backend (but I am speculating) - so it seems wise to punt on this until later. Instead, we are now retaining async functions as callback-based ones, with a helper in this case to wait for the callback to be invoked. For `wgpuBufferMapAsync` we will just have the callback approach. Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
890dd57296
commit
aab99b5474
4 changed files with 74 additions and 41 deletions
|
|
@ -92,7 +92,7 @@ pub fn setup(allocator: std.mem.Allocator) !Setup {
|
||||||
props.driver_description,
|
props.driver_description,
|
||||||
});
|
});
|
||||||
|
|
||||||
const device = switch (nosuspend backend_adapter.requestDevice(&.{})) {
|
const device = switch (backend_adapter.waitForDevice(&.{})) {
|
||||||
.device => |v| v,
|
.device => |v| v,
|
||||||
.err => |err| {
|
.err => |err| {
|
||||||
std.debug.print("failed to get device: error={} {s}\n", .{ err.code, err.message });
|
std.debug.print("failed to get device: error={} {s}\n", .{ err.code, err.message });
|
||||||
|
|
|
||||||
|
|
@ -49,13 +49,14 @@ properties: Properties,
|
||||||
ptr: *anyopaque,
|
ptr: *anyopaque,
|
||||||
vtable: *const VTable,
|
vtable: *const VTable,
|
||||||
|
|
||||||
// The @frameSize(func) of the implementations requestDevice async function
|
|
||||||
request_device_frame_size: usize,
|
|
||||||
|
|
||||||
pub const VTable = struct {
|
pub const VTable = struct {
|
||||||
reference: fn (ptr: *anyopaque) void,
|
reference: fn (ptr: *anyopaque) void,
|
||||||
release: fn (ptr: *anyopaque) void,
|
release: fn (ptr: *anyopaque) void,
|
||||||
requestDevice: fn requestDevice(ptr: *anyopaque, descriptor: *const Device.Descriptor) callconv(.Async) RequestDeviceResponse,
|
requestDevice: fn requestDevice(
|
||||||
|
ptr: *anyopaque,
|
||||||
|
descriptor: *const Device.Descriptor,
|
||||||
|
callback: *RequestDeviceCallback,
|
||||||
|
) void,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub inline fn reference(adapter: Adapter) void {
|
pub inline fn reference(adapter: Adapter) void {
|
||||||
|
|
@ -143,24 +144,53 @@ pub const RequestDeviceResponse = union(RequestDeviceResponseTag) {
|
||||||
err: RequestDeviceError,
|
err: RequestDeviceError,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn requestDevice(adapter: Adapter, descriptor: *const Device.Descriptor) callconv(.Async) RequestDeviceResponse {
|
pub fn requestDevice(
|
||||||
var frame_buffer = std.heap.page_allocator.allocAdvanced(
|
adapter: Adapter,
|
||||||
u8,
|
descriptor: *const Device.Descriptor,
|
||||||
16,
|
callback: *RequestDeviceCallback,
|
||||||
adapter.request_device_frame_size,
|
) void {
|
||||||
std.mem.Allocator.Exact.at_least,
|
adapter.vtable.requestDevice(adapter.ptr, descriptor, callback);
|
||||||
) catch {
|
}
|
||||||
return .{ .err = .{
|
|
||||||
.message = "Out of memory",
|
|
||||||
.code = RequestDeviceErrorCode.Error,
|
|
||||||
} };
|
|
||||||
};
|
|
||||||
defer std.heap.page_allocator.free(frame_buffer);
|
|
||||||
|
|
||||||
var result: RequestDeviceResponse = undefined;
|
pub const RequestDeviceCallback = struct {
|
||||||
const f = @asyncCall(frame_buffer, &result, adapter.vtable.requestDevice, .{ adapter.ptr, descriptor });
|
type_erased_ctx: *anyopaque,
|
||||||
resume f;
|
type_erased_callback: fn (ctx: *anyopaque, response: RequestDeviceResponse) callconv(.Inline) void,
|
||||||
return result;
|
|
||||||
|
pub fn init(
|
||||||
|
comptime Context: type,
|
||||||
|
ctx: *Context,
|
||||||
|
comptime callback: fn (ctx: *Context, response: RequestDeviceResponse) void,
|
||||||
|
) RequestDeviceCallback {
|
||||||
|
const erased = (struct {
|
||||||
|
pub inline fn erased(type_erased_ctx: *anyopaque, response: RequestDeviceResponse) void {
|
||||||
|
callback(@ptrCast(*Context, @alignCast(@alignOf(*Context), type_erased_ctx)), response);
|
||||||
|
}
|
||||||
|
}).erased;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.type_erased_ctx = ctx,
|
||||||
|
.type_erased_callback = erased,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A helper which invokes requestDevice and blocks until the device is recieved.
|
||||||
|
pub fn waitForDevice(adapter: Adapter, descriptor: *const Device.Descriptor) RequestDeviceResponse {
|
||||||
|
const Context = RequestDeviceResponse;
|
||||||
|
var response: Context = undefined;
|
||||||
|
var callback = RequestDeviceCallback.init(Context, &response, (struct {
|
||||||
|
pub fn callback(ctx: *Context, callback_response: RequestDeviceResponse) void {
|
||||||
|
ctx.* = callback_response;
|
||||||
|
}
|
||||||
|
}).callback);
|
||||||
|
|
||||||
|
adapter.requestDevice(descriptor, &callback);
|
||||||
|
|
||||||
|
// TODO: FUTURE: Once crbug.com/dawn/1122 is fixed, we should process events here otherwise our
|
||||||
|
// callback would not be invoked:
|
||||||
|
//c.wgpuInstanceProcessEvents(adapter.instance)
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
test "syntax" {
|
test "syntax" {
|
||||||
|
|
@ -172,5 +202,6 @@ test "syntax" {
|
||||||
_ = RequestDeviceErrorCode;
|
_ = RequestDeviceErrorCode;
|
||||||
_ = RequestDeviceError;
|
_ = RequestDeviceError;
|
||||||
_ = RequestDeviceResponse;
|
_ = RequestDeviceResponse;
|
||||||
|
_ = RequestDeviceCallback;
|
||||||
_ = requestDevice;
|
_ = requestDevice;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const Adapter = @import("Adapter.zig");
|
||||||
const RequestDeviceErrorCode = Adapter.RequestDeviceErrorCode;
|
const RequestDeviceErrorCode = Adapter.RequestDeviceErrorCode;
|
||||||
const RequestDeviceError = Adapter.RequestDeviceError;
|
const RequestDeviceError = Adapter.RequestDeviceError;
|
||||||
const RequestDeviceResponse = Adapter.RequestDeviceResponse;
|
const RequestDeviceResponse = Adapter.RequestDeviceResponse;
|
||||||
|
const RequestDeviceCallback = Adapter.RequestDeviceCallback;
|
||||||
|
|
||||||
const Device = @import("Device.zig");
|
const Device = @import("Device.zig");
|
||||||
const Surface = @import("Surface.zig");
|
const Surface = @import("Surface.zig");
|
||||||
|
|
@ -230,7 +231,6 @@ fn wrapAdapter(adapter: c.WGPUAdapter) Adapter {
|
||||||
|
|
||||||
.ptr = adapter.?,
|
.ptr = adapter.?,
|
||||||
.vtable = &adapter_vtable,
|
.vtable = &adapter_vtable,
|
||||||
.request_device_frame_size = @frameSize(adapter_vtable.requestDevice),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,7 +246,11 @@ const adapter_vtable = Adapter.VTable{
|
||||||
}
|
}
|
||||||
}).release,
|
}).release,
|
||||||
.requestDevice = (struct {
|
.requestDevice = (struct {
|
||||||
pub fn requestDevice(ptr: *anyopaque, descriptor: *const Device.Descriptor) callconv(.Async) RequestDeviceResponse {
|
pub fn requestDevice(
|
||||||
|
ptr: *anyopaque,
|
||||||
|
descriptor: *const Device.Descriptor,
|
||||||
|
callback: *RequestDeviceCallback,
|
||||||
|
) void {
|
||||||
const adapter = @ptrCast(c.WGPUAdapter, @alignCast(@alignOf(c.WGPUAdapter), ptr));
|
const adapter = @ptrCast(c.WGPUAdapter, @alignCast(@alignOf(c.WGPUAdapter), ptr));
|
||||||
|
|
||||||
const required_limits = if (descriptor.required_limits) |l| c.WGPURequiredLimits{
|
const required_limits = if (descriptor.required_limits) |l| c.WGPURequiredLimits{
|
||||||
|
|
@ -262,14 +266,13 @@ const adapter_vtable = Adapter.VTable{
|
||||||
.requiredLimits = if (required_limits) |*l| l else null,
|
.requiredLimits = if (required_limits) |*l| l else null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const callback = (struct {
|
const cCallback = (struct {
|
||||||
pub fn callback(status: c.WGPURequestDeviceStatus, device: c.WGPUDevice, message: [*c]const u8, userdata: ?*anyopaque) callconv(.C) void {
|
pub fn cCallback(status: c.WGPURequestDeviceStatus, device: c.WGPUDevice, message: [*c]const u8, userdata: ?*anyopaque) callconv(.C) void {
|
||||||
const _callback_response = @ptrCast(*Adapter.RequestDeviceResponse, @alignCast(@alignOf(*Adapter.RequestDeviceResponse), userdata));
|
const callback_info = @ptrCast(*RequestDeviceCallback, @alignCast(@alignOf(*RequestDeviceCallback), userdata.?));
|
||||||
|
|
||||||
// Store the response into a field on the native instance for later reading.
|
const response = if (status == c.WGPURequestDeviceStatus_Success) RequestDeviceResponse{
|
||||||
_callback_response.* = if (status == c.WGPURequestDeviceStatus_Success) .{
|
|
||||||
.device = wrapDevice(device.?),
|
.device = wrapDevice(device.?),
|
||||||
} else .{
|
} else RequestDeviceResponse{
|
||||||
.err = Adapter.RequestDeviceError{
|
.err = Adapter.RequestDeviceError{
|
||||||
.message = std.mem.span(message),
|
.message = std.mem.span(message),
|
||||||
.code = switch (status) {
|
.code = switch (status) {
|
||||||
|
|
@ -279,18 +282,12 @@ const adapter_vtable = Adapter.VTable{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
callback_info.type_erased_callback(callback_info.type_erased_ctx, response);
|
||||||
}
|
}
|
||||||
}).callback;
|
}).cCallback;
|
||||||
|
|
||||||
var callback_response: Adapter.RequestDeviceResponse = undefined;
|
c.wgpuAdapterRequestDevice(adapter, &desc, cCallback, callback);
|
||||||
c.wgpuAdapterRequestDevice(adapter, &desc, callback, &callback_response);
|
|
||||||
// TODO: Once crbug.com/dawn/1122 is fixed, we should process events here otherwise our
|
|
||||||
// callback will not be invoked.
|
|
||||||
// c.wgpuInstanceProcessEvents(native.instance)
|
|
||||||
suspend {} // must suspend so that async caller can resume
|
|
||||||
|
|
||||||
// Return the response, asserting the callback has executed at this point.
|
|
||||||
return callback_response;
|
|
||||||
}
|
}
|
||||||
}).requestDevice,
|
}).requestDevice,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,15 @@ pub const RequestAdapterErrorCode = Interface.RequestAdapterErrorCode;
|
||||||
pub const RequestAdapterError = Interface.RequestAdapterError;
|
pub const RequestAdapterError = Interface.RequestAdapterError;
|
||||||
pub const RequestAdapterResponse = Interface.RequestAdapterResponse;
|
pub const RequestAdapterResponse = Interface.RequestAdapterResponse;
|
||||||
|
|
||||||
|
pub const Adapter = @import("Adapter.zig");
|
||||||
|
pub const RequestDeviceErrorCode = Adapter.RequestDeviceErrorCode;
|
||||||
|
pub const RequestDeviceError = Adapter.RequesatDeviceError;
|
||||||
|
pub const RequestDeviceCallback = Adapter.RequestDeviceCallback;
|
||||||
|
pub const RequestDeviceResponse = Adapter.RequestDeviceResponse;
|
||||||
|
|
||||||
pub const NativeInstance = @import("NativeInstance.zig");
|
pub const NativeInstance = @import("NativeInstance.zig");
|
||||||
|
|
||||||
// Interfaces
|
// Interfaces
|
||||||
pub const Adapter = @import("Adapter.zig");
|
|
||||||
pub const Device = @import("Device.zig");
|
pub const Device = @import("Device.zig");
|
||||||
pub const Surface = @import("Surface.zig");
|
pub const Surface = @import("Surface.zig");
|
||||||
pub const Queue = @import("Queue.zig");
|
pub const Queue = @import("Queue.zig");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue