| // Copyright 2022 The IREE Authors | |
| // | |
| // Licensed under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| #include "experimental/webgpu/platform/emscripten/emscripten_driver.h" | |
| #include <emscripten.h> | |
| #define IREE_HAL_WEBGPU_DEVICE_ID_DEFAULT 0 | |
| //===----------------------------------------------------------------------===// | |
| // Driver and device options | |
| //===----------------------------------------------------------------------===// | |
| IREE_API_EXPORT void iree_hal_webgpu_driver_options_initialize( | |
| iree_hal_webgpu_driver_options_t* out_options) { | |
| IREE_ASSERT_ARGUMENT(out_options); | |
| memset(out_options, 0, sizeof(*out_options)); | |
| out_options->backend_preference = IREE_HAL_WEBGPU_DRIVER_BACKEND_ANY; | |
| out_options->log_level = IREE_HAL_WEBGPU_DRIVER_LOG_LEVEL_OFF; | |
| // TODO(benvanik): coming in future spec update. For now go high-perf. | |
| // out_options->power_preference = WGPUPowerPreference_Undefined; | |
| out_options->power_preference = WGPUPowerPreference_HighPerformance; | |
| iree_hal_webgpu_device_options_initialize(&out_options->device_options); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Synchronous adapter and device request functions | |
| //===----------------------------------------------------------------------===// | |
| // We use Asyncify for convenience - for now. | |
| // https://emscripten.org/docs/porting/asyncify.html | |
| // https://github.com/emscripten-core/emscripten/issues/15746 | |
| // https://github.com/juj/wasm_webgpu/blob/main/lib/lib_webgpu.h | |
| // | |
| // The requestAdapter and requestDevice functions are asynchronous, while the | |
| // HAL API has synchronous driver and device creation. We want to support both | |
| // platform-independent tests (CTS tests, 'check' framework tests) and user | |
| // applications. For platform-independent tests, we keep the APIs synchronous | |
| // by using Asyncify. For user applications, we could pass in a loop or add | |
| // asynchronous APIs that let those applications use async/await directly. | |
| // | |
| // An even simpler solution for user applications is to request an adapter and | |
| // and device purely up in JavaScript, then to pass the already created device | |
| // in via preinitializedWebGPUDevice / emscripten_webgpu_get_device(). | |
| #ifdef EM_ASYNC_JS | |
| EM_ASYNC_JS(WGPUAdapter, wgpuInstanceRequestAdapterSync, (), { | |
| // TODO(scotttodd): WGPURequestAdapterOptions struct | |
| const adapter = await navigator['gpu']['requestAdapter'](); | |
| // WARNING: this calls functions directly on Emscripten's library_webgpu.js. | |
| // This is not a stable API! | |
| const adapterId = WebGPU.mgrAdapter.create(adapter); | |
| return adapterId; | |
| }); | |
| EM_ASYNC_JS(WGPUDevice, wgpuAdapterRequestDeviceSync, (WGPUAdapter adapterId), { | |
| // WARNING: this calls functions directly on Emscripten's library_webgpu.js. | |
| // This is not a stable API! | |
| const adapter = WebGPU.mgrAdapter.get(adapterId); | |
| // TODO(scotttodd): WGPUDeviceDescriptor struct | |
| const descriptor = {}; | |
| const device = await adapter['requestDevice'](descriptor); | |
| const deviceWrapper = {queueId : WebGPU.mgrQueue.create(device["queue"])}; | |
| const deviceId = WebGPU.mgrDevice.create(device, deviceWrapper); | |
| return deviceId; | |
| }); | |
| #else | |
| WGPUAdapter wgpuInstanceRequestAdapterSync() { | |
| fprintf(stderr, "wgpuInstanceRequestAdapterSync requires -sASYNCIFY\n"); | |
| return NULL; | |
| } | |
| WGPUDevice wgpuAdapterRequestDeviceSync(WGPUAdapter adapterId) { | |
| fprintf(stderr, "wgpuAdapterRequestDeviceSync requires -sASYNCIFY\n"); | |
| return NULL; | |
| } | |
| #endif // EM_ASYNC_JS | |
| //===----------------------------------------------------------------------===// | |
| // iree_hal_webgpu_emscripten_driver_t | |
| //===----------------------------------------------------------------------===// | |
| typedef struct iree_hal_webgpu_emscripten_driver_t { | |
| iree_hal_resource_t resource; | |
| iree_allocator_t host_allocator; | |
| iree_string_view_t identifier; | |
| iree_hal_webgpu_device_options_t default_options; | |
| WGPUInstance instance; | |
| WGPUAdapter adapter; | |
| } iree_hal_webgpu_emscripten_driver_t; | |
| static const iree_hal_driver_vtable_t iree_hal_webgpu_emscripten_driver_vtable; | |
| static iree_hal_webgpu_emscripten_driver_t* | |
| iree_hal_webgpu_emscripten_driver_cast(iree_hal_driver_t* base_value) { | |
| IREE_HAL_ASSERT_TYPE(base_value, &iree_hal_webgpu_emscripten_driver_vtable); | |
| return (iree_hal_webgpu_emscripten_driver_t*)base_value; | |
| } | |
| iree_status_t iree_hal_webgpu_emscripten_driver_create( | |
| iree_string_view_t identifier, | |
| const iree_hal_webgpu_driver_options_t* options, | |
| iree_allocator_t host_allocator, iree_hal_driver_t** out_driver) { | |
| IREE_ASSERT_ARGUMENT(options); | |
| IREE_ASSERT_ARGUMENT(out_driver); | |
| *out_driver = NULL; | |
| IREE_TRACE_ZONE_BEGIN(z0); | |
| iree_hal_webgpu_emscripten_driver_t* driver = NULL; | |
| iree_host_size_t total_size = sizeof(*driver) + identifier.size + /*NUL=*/1; | |
| IREE_RETURN_AND_END_ZONE_IF_ERROR( | |
| z0, iree_allocator_malloc(host_allocator, total_size, (void**)&driver)); | |
| iree_hal_resource_initialize(&iree_hal_webgpu_emscripten_driver_vtable, | |
| &driver->resource); | |
| driver->host_allocator = host_allocator; | |
| iree_string_view_append_to_buffer(identifier, &driver->identifier, | |
| (char*)driver + sizeof(*driver)); | |
| memcpy(&driver->default_options, &options->device_options, | |
| sizeof(driver->default_options)); | |
| const WGPUInstanceDescriptor instance_descriptor = { | |
| .nextInChain = NULL, | |
| }; | |
| driver->instance = wgpuCreateInstance(&instance_descriptor); | |
| if (!driver->instance) { | |
| iree_hal_driver_release((iree_hal_driver_t*)driver); | |
| IREE_TRACE_ZONE_END(z0); | |
| return iree_make_status( | |
| IREE_STATUS_UNAVAILABLE, | |
| "WebGPU implementation not present or failed to load"); | |
| } | |
| // Request an adapter from the implementation. We only get one of these and it | |
| // may expose multiple devices so it's effectively what we consider a driver. | |
| // HACKS: sync via Asyncify | |
| WGPUAdapter adapter = wgpuInstanceRequestAdapterSync(); | |
| if (!adapter) { | |
| iree_hal_driver_release((iree_hal_driver_t*)driver); | |
| IREE_TRACE_ZONE_END(z0); | |
| return iree_make_status( | |
| IREE_STATUS_UNAVAILABLE, | |
| "WebGPU requestAdapter() failed to return a WGPUAdapter"); | |
| } | |
| driver->adapter = adapter; | |
| WGPUAdapterProperties adapter_props; | |
| memset(&adapter_props, 0, sizeof(adapter_props)); | |
| wgpuAdapterGetProperties(driver->adapter, &adapter_props); | |
| *out_driver = (iree_hal_driver_t*)driver; | |
| IREE_TRACE_ZONE_END(z0); | |
| return iree_ok_status(); | |
| } | |
| static void iree_hal_webgpu_emscripten_driver_destroy( | |
| iree_hal_driver_t* base_driver) { | |
| iree_hal_webgpu_emscripten_driver_t* driver = | |
| iree_hal_webgpu_emscripten_driver_cast(base_driver); | |
| iree_allocator_t host_allocator = driver->host_allocator; | |
| IREE_TRACE_ZONE_BEGIN(z0); | |
| // TODO(scotttodd): emscripten teardown? | |
| // driver->adapter = NULL; | |
| // driver->instance = NULL; | |
| iree_allocator_free(host_allocator, driver); | |
| IREE_TRACE_ZONE_END(z0); | |
| } | |
| static iree_status_t iree_hal_webgpu_emscripten_driver_query_available_devices( | |
| iree_hal_driver_t* base_driver, iree_allocator_t allocator, | |
| iree_host_size_t* out_device_info_count, | |
| iree_hal_device_info_t** out_device_infos) { | |
| // Unfortunately no queries in WebGPU; we can only request a single device. | |
| static const iree_hal_device_info_t device_infos[1] = { | |
| { | |
| .device_id = IREE_HAL_WEBGPU_DEVICE_ID_DEFAULT, | |
| .name = iree_string_view_literal("default"), | |
| }, | |
| }; | |
| *out_device_info_count = IREE_ARRAYSIZE(device_infos); | |
| return iree_allocator_clone( | |
| allocator, iree_make_const_byte_span(device_infos, sizeof(device_infos)), | |
| (void**)out_device_infos); | |
| } | |
| static iree_status_t iree_hal_webgpu_emscripten_driver_dump_device_info( | |
| iree_hal_driver_t* base_driver, iree_hal_device_id_t device_id, | |
| iree_string_builder_t* builder) { | |
| iree_hal_webgpu_emscripten_driver_t* driver = | |
| iree_hal_webgpu_emscripten_driver_cast(base_driver); | |
| // TODO(scotttodd): dump detailed device info. | |
| (void)driver; | |
| return iree_ok_status(); | |
| } | |
| static iree_status_t iree_hal_webgpu_emscripten_driver_create_device_by_id( | |
| iree_hal_driver_t* base_driver, iree_hal_device_id_t device_id, | |
| iree_host_size_t param_count, const iree_string_pair_t* params, | |
| iree_allocator_t host_allocator, iree_hal_device_t** out_device) { | |
| iree_hal_webgpu_emscripten_driver_t* driver = | |
| iree_hal_webgpu_emscripten_driver_cast(base_driver); | |
| // HACKS: sync via Asyncify | |
| WGPUDevice device = wgpuAdapterRequestDeviceSync(driver->adapter); | |
| if (!device) { | |
| return iree_make_status( | |
| IREE_STATUS_UNAVAILABLE, | |
| "WebGPU requestDevice() failed to return a WGPUDevice"); | |
| } | |
| return iree_hal_webgpu_wrap_device(driver->identifier, | |
| &driver->default_options, device, | |
| driver->host_allocator, out_device); | |
| } | |
| static iree_status_t iree_hal_webgpu_emscripten_driver_create_device_by_path( | |
| iree_hal_driver_t* base_driver, iree_string_view_t driver_name, | |
| iree_string_view_t device_path, iree_host_size_t param_count, | |
| const iree_string_pair_t* params, iree_allocator_t host_allocator, | |
| iree_hal_device_t** out_device) { | |
| if (!iree_string_view_is_empty(device_path)) { | |
| return iree_make_status(IREE_STATUS_NOT_FOUND, | |
| "device paths not yet implemented"); | |
| } | |
| return iree_hal_webgpu_emscripten_driver_create_device_by_id( | |
| base_driver, IREE_HAL_DEVICE_ID_DEFAULT, param_count, params, | |
| host_allocator, out_device); | |
| } | |
| static const iree_hal_driver_vtable_t iree_hal_webgpu_emscripten_driver_vtable = | |
| { | |
| .destroy = iree_hal_webgpu_emscripten_driver_destroy, | |
| .query_available_devices = | |
| iree_hal_webgpu_emscripten_driver_query_available_devices, | |
| .dump_device_info = iree_hal_webgpu_emscripten_driver_dump_device_info, | |
| .create_device_by_id = | |
| iree_hal_webgpu_emscripten_driver_create_device_by_id, | |
| .create_device_by_path = | |
| iree_hal_webgpu_emscripten_driver_create_device_by_path, | |
| }; |