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:
Stephen Gutekanst 2022-03-15 10:42:14 -07:00 committed by Stephen Gutekanst
parent 890dd57296
commit aab99b5474
4 changed files with 74 additions and 41 deletions

View file

@ -49,13 +49,14 @@ properties: Properties,
ptr: *anyopaque,
vtable: *const VTable,
// The @frameSize(func) of the implementations requestDevice async function
request_device_frame_size: usize,
pub const VTable = struct {
reference: 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 {
@ -143,24 +144,53 @@ pub const RequestDeviceResponse = union(RequestDeviceResponseTag) {
err: RequestDeviceError,
};
pub fn requestDevice(adapter: Adapter, descriptor: *const Device.Descriptor) callconv(.Async) RequestDeviceResponse {
var frame_buffer = std.heap.page_allocator.allocAdvanced(
u8,
16,
adapter.request_device_frame_size,
std.mem.Allocator.Exact.at_least,
) catch {
return .{ .err = .{
.message = "Out of memory",
.code = RequestDeviceErrorCode.Error,
} };
};
defer std.heap.page_allocator.free(frame_buffer);
pub fn requestDevice(
adapter: Adapter,
descriptor: *const Device.Descriptor,
callback: *RequestDeviceCallback,
) void {
adapter.vtable.requestDevice(adapter.ptr, descriptor, callback);
}
var result: RequestDeviceResponse = undefined;
const f = @asyncCall(frame_buffer, &result, adapter.vtable.requestDevice, .{ adapter.ptr, descriptor });
resume f;
return result;
pub const RequestDeviceCallback = struct {
type_erased_ctx: *anyopaque,
type_erased_callback: fn (ctx: *anyopaque, response: RequestDeviceResponse) callconv(.Inline) void,
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" {
@ -172,5 +202,6 @@ test "syntax" {
_ = RequestDeviceErrorCode;
_ = RequestDeviceError;
_ = RequestDeviceResponse;
_ = RequestDeviceCallback;
_ = requestDevice;
}