Add initial instance and adapter interface#23
Conversation
| WGPU_EXPORT void wgpuFenceOnCompletion(WGPUFence fence, uint64_t value, WGPUFenceOnCompletionCallback callback, void * userdata); | ||
|
|
||
| // Methods of Instance | ||
| WGPU_EXPORT WGPUAdapter wgpuInstanceCreateAdapter(WGPUInstance instance, WGPUAdapterDescriptor const * descriptor); |
There was a problem hiding this comment.
This one would also need to be async
| WGPU_EXPORT WGPUProc WGPUGetProcAddress(WGPUDevice device, const char* procName); | ||
|
|
||
| // Methods of Adapter | ||
| WGPU_EXPORT WGPUDevice wgpuAdapterCreateDevice(WGPUAdapter adapter, WGPUDeviceDescriptor const * descriptor); |
There was a problem hiding this comment.
for wasm, would need to be async (take a callback)
There was a problem hiding this comment.
Agreed that we should provide an async version that 100% mirrors requestDevice, how about the following:
// Maybe we could not have the status and instead use the device lost callback, would this match WebGPU better?
enum WGPUAdapterRequestDeviceCallbackStatus{
WGPUAdapterRequestDeviceCallbackStatus_Success,
WGPUAdapterRequestDeviceCallbackStatus_ValidationError,
WGPUAdapterRequestDeviceCallbackStatus_CreationError,
WGPUAdapterRequestDeviceCallbackStatus_Unknown,
};
typedef void (*WGPUAdapterRequestDeviceCallback)(WGPUAdapterRequestDeviceCallbackStatus status, WGPUDevice device);
void wgpuAdapterRequestDevice(WGPUAdapter, WGPUDeviceDescriptor * const descriptor);This would require having sorted out what webgpu.h's event loop looks like.
I still think there might be some divergence between WebGPU in JS and native for device creation, so maybe we could also have the immediate creation.
There was a problem hiding this comment.
requestDevice and requestAdapter can fail (reject with DOMException) in WebGPU, so we should keep the status.
Having both async and immediate creation would be ok.
There was a problem hiding this comment.
if we keep the immediate creation, how would this translate to WASM?
There was a problem hiding this comment.
If we want to have a synchronous version of these, we should probably move them behind a #ifdef WGPU_NATIVE_ONLY or separate header. For example, if an application is using these and tries to compile to wasm for the web, we can't make the synchronous version work (because we can't yield to the browser), so we'd have to abort/panic whenever they're called on the web.
I'd slightly prefer to match the WebIDL – mostly to avoid applications using these and later realizing that they can't actually compile to wasm. Applications not targeting wasm could use a small helper function to block on the async call and spin if they'd like (though I can appreciate that's not ideal).
There was a problem hiding this comment.
It might actually be very handy for applications to be able to pretend some operations are synchronous, and use compiler functionality like Emscripten's ASYNCIFY.
But I agree with putting any such things behind an ifdef.
There was a problem hiding this comment.
I think we should have both a synchronous and asynchronous version. If we provided only the synchronous version, people would still synchronously tick the event loop until the callback is called. It adds unnecessary hassle (in native device and adapter creation is synchronous), and it wouldn't help WASM ports because without ASYNCIFY they need to return all the way to the browser, spinning doesn't work.
So we should have createDevice/Adapter and requestDevice/Adapter. And I don't think it should be behind an ifdef because the synchronous versions are still useful in emscripten so the loader JS outside of the module can pass an adapter or device that's the one to be returned by createAdapter/Device. I feel less strongly about this (-3 because our code generator will doesn't support such ifdefs, -2 otherwise).
There was a problem hiding this comment.
If the preload JS sets a "preinitialized" device, I think we should just provide an emscripten_* entry point to grab that, instead of doing it through createDevice, because the C code calling createDevice would have to be modified anyway to not do createAdapter.
For adapter creation, it might be useful for createAdapter to return a preinitialized adapter, because the C code might not have to be modified. Plus, returning a preinitialized adapter is much more straightforward: it's valid to ignore all of the GPURequestAdapterOptions, which is not true of GPUDeviceDescriptor.
There was a problem hiding this comment.
If we provided only the synchronous version, people would still synchronously tick the event loop until the callback is called. It adds unnecessary hassle (in native device and adapter creation is synchronous), and it wouldn't help WASM ports because without ASYNCIFY they need to return all the way to the browser, spinning doesn't work.
FWIW this is basically how the SDL main loop works with Emscripten – it just means the adapter and device are also initialized as part of the main loop. In Rust I was thinking we could return Futures to make it easy to do async initialization and thought we could do something similar in C++/other languages that have some concept of tasks/futures.
So we should have
createDevice/AdapterandrequestDevice/Adapter. And I don't think it should be behind an ifdef because the synchronous versions are still useful in emscripten
How could we expose createDevice and createAdapter meaningfully for wasm targets when Emscripten isn't used though? It seems like we would have to panic depending on which target+compiler combination is used. If we do expose both versions I'd prefer to feature-gate the synchronous versions somehow (ifdef/separate header/etc.).
| typedef WGPUProc (*WGPUProcGetProcAddress)(WGPUDevice device, const char* procName); | ||
|
|
||
| // Procs of Adapter | ||
| typedef WGPUDevice (*WGPUProcAdapterCreateDevice)(WGPUAdapter adapter, WGPUDeviceDescriptor const * descriptor); |
There was a problem hiding this comment.
Are you intentionally calling it different from the upstream spec, which has RequestXxx for both device and adapter?
We'd need to talk a bit (on Thursday?) about async stuff in the native headers
There was a problem hiding this comment.
This was intentional because it is synchronous so it doesn't match the spec. createXXX is synchronous while requestXXX is asynchronous. Happy to chat about it next time we talk!
| WGPU_EXPORT WGPUProc WGPUGetProcAddress(WGPUDevice device, const char* procName); | ||
|
|
||
| // Methods of Adapter | ||
| WGPU_EXPORT WGPUDevice wgpuAdapterCreateDevice(WGPUAdapter adapter, WGPUDeviceDescriptor const * descriptor); |
There was a problem hiding this comment.
if we keep the immediate creation, how would this translate to WASM?
|
|
||
| // Methods of Instance | ||
| WGPU_EXPORT WGPUAdapter wgpuInstanceCreateAdapter(WGPUInstance instance, WGPUAdapterDescriptor const * descriptor); | ||
| WGPU_EXPORT WGPUSurface wgpuInstanceCreateSurface(WGPUInstance instance, WGPUSurfaceDescriptor const * descriptor); |
There was a problem hiding this comment.
I wonder if this needs to be async as well...
There was a problem hiding this comment.
I don't think there will be anything async in here. Just getContext (or maybe the surface descriptor gives us the context) and configureSwapChain (or some future equivalent thereof).
getSwapChainPreferredFormat is async, but not called inside here.
| typedef struct WGPUComputePipelineImpl* WGPUComputePipeline; | ||
| typedef struct WGPUDeviceImpl* WGPUDevice; | ||
| typedef struct WGPUFenceImpl* WGPUFence; | ||
| typedef struct WGPUInstanceImpl* WGPUInstance; |
There was a problem hiding this comment.
What's the benefit of having the separate Instance struct on native vs. just creating the adapter (without the adapter being attached to anything)?
There was a problem hiding this comment.
Some reasons off the top of my head:
- provides a place to map adapter ids to adapter objects
- provides a place for an event loop (however that ends up working)
There was a problem hiding this comment.
The Instance is the global object through which wgpu is used, and avoids the need for global static variables (unfortunately we have differing designs here). Global variables like these are useful for:
- Dependency injection before the implementation does anything.
- Caching queried adapters etc.
- Setting global options (like enable validation, or start capture) that are sometimes needed by backends when we create adapters. (could be in descriptors but we don't want to expose that in webgpu.h, these are dawn-specific concerns)
There was a problem hiding this comment.
Oh, sorry for the race conditions
| WGPU_EXPORT WGPUProc WGPUGetProcAddress(WGPUDevice device, const char* procName); | ||
|
|
||
| // Methods of Adapter | ||
| WGPU_EXPORT WGPUDevice wgpuAdapterCreateDevice(WGPUAdapter adapter, WGPUDeviceDescriptor const * descriptor); |
There was a problem hiding this comment.
If we want to have a synchronous version of these, we should probably move them behind a #ifdef WGPU_NATIVE_ONLY or separate header. For example, if an application is using these and tries to compile to wasm for the web, we can't make the synchronous version work (because we can't yield to the browser), so we'd have to abort/panic whenever they're called on the web.
I'd slightly prefer to match the WebIDL – mostly to avoid applications using these and later realizing that they can't actually compile to wasm. Applications not targeting wasm could use a small helper function to block on the async call and spin if they'd like (though I can appreciate that's not ideal).
|
Had a chat about this with @kvark, though it was mostly focused on the event loop. Summary is:
|
|
PTAL again:
|
Also moves the surface creation to the instance.
kvark
left a comment
There was a problem hiding this comment.
I think we can proceed with this, thanks for the update!
Also moves the surface creation to the instance.
PTAL, this is meant to start the discussion on what the interface for WGPUInstance and WGPUAdapter should look like.