Skip to content

Commit f47bef3

Browse files
authored
WebGPU: runtime keepalive, callUserCallback (#15251)
1 parent b1907ce commit f47bef3

File tree

3 files changed

+159
-79
lines changed

3 files changed

+159
-79
lines changed

src/library_webgpu.js

Lines changed: 145 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -660,75 +660,99 @@ var LibraryWebGPU = {
660660
device["pushErrorScope"](WebGPU.ErrorFilter[filter]);
661661
},
662662

663+
wgpuDevicePopErrorScope__deps: [
664+
'$callUserCallback',
663665
#if MINIMAL_RUNTIME
664-
wgpuDevicePopErrorScope__deps: ['$allocateUTF8'],
666+
'$allocateUTF8',
667+
#else
668+
'$runtimeKeepalivePush', '$runtimeKeepalivePop',
665669
#endif
670+
],
666671
wgpuDevicePopErrorScope: function(deviceId, callback, userdata) {
667672
var device = WebGPU.mgrDevice.get(deviceId);
668-
device["popErrorScope"]()["then"](function(gpuError) {
669-
if (!gpuError) {
670-
{{{ makeDynCall('viii', 'callback') }}}(
671-
{{{ gpu.ErrorType.NoError }}}, 0, userdata);
672-
} else if (gpuError instanceof GPUOutOfMemoryError) {
673-
{{{ makeDynCall('viii', 'callback') }}}(
674-
{{{ gpu.ErrorType.OutOfMemory }}}, 0, userdata);
675-
} else {
673+
{{{ runtimeKeepalivePush() }}}
674+
device["popErrorScope"]().then(function(gpuError) {
675+
{{{ runtimeKeepalivePop() }}}
676+
callUserCallback(function() {
677+
if (!gpuError) {
678+
{{{ makeDynCall('viii', 'callback') }}}(
679+
{{{ gpu.ErrorType.NoError }}}, 0, userdata);
680+
} else if (gpuError instanceof GPUOutOfMemoryError) {
681+
{{{ makeDynCall('viii', 'callback') }}}(
682+
{{{ gpu.ErrorType.OutOfMemory }}}, 0, userdata);
683+
} else {
676684
#if ASSERTIONS
677-
assert(gpuError instanceof GPUValidationError);
685+
assert(gpuError instanceof GPUValidationError);
678686
#endif
679-
var messagePtr = allocateUTF8(gpuError.message);
680-
{{{ makeDynCall('viii', 'callback') }}}({{{ gpu.ErrorType.Validation }}}, messagePtr, userdata);
681-
_free(messagePtr);
682-
}
687+
var messagePtr = allocateUTF8(gpuError.message);
688+
{{{ makeDynCall('viii', 'callback') }}}({{{ gpu.ErrorType.Validation }}}, messagePtr, userdata);
689+
_free(messagePtr);
690+
}
691+
});
683692
}, function(ex) {
684-
var messagePtr = allocateUTF8(ex.message);
685-
// TODO: This can mean either the device was lost or the error scope stack was empty. Figure
686-
// out how to synthesize the DeviceLost error type. (Could be by simply tracking the error
687-
// scope depth, but that isn't ideal.)
688-
{{{ makeDynCall('viii', 'callback') }}}({{{ gpu.ErrorType.Unknown }}}, messagePtr, userdata);
689-
_free(messagePtr);
693+
{{{ runtimeKeepalivePop() }}}
694+
callUserCallback(function() {
695+
var messagePtr = allocateUTF8(ex.message);
696+
// TODO: This can mean either the device was lost or the error scope stack was empty. Figure
697+
// out how to synthesize the DeviceLost error type. (Could be by simply tracking the error
698+
// scope depth, but that isn't ideal.)
699+
{{{ makeDynCall('viii', 'callback') }}}({{{ gpu.ErrorType.Unknown }}}, messagePtr, userdata);
700+
_free(messagePtr);
701+
});
690702
});
691703
},
692704

705+
wgpuDeviceSetDeviceLostCallback__deps: [
706+
'$callUserCallback',
693707
#if MINIMAL_RUNTIME
694-
wgpuDeviceSetDeviceLostCallback__deps: ['$allocateUTF8'],
708+
'$allocateUTF8',
695709
#endif
710+
],
696711
wgpuDeviceSetDeviceLostCallback: function(deviceId, callback, userdata) {
697712
var deviceWrapper = WebGPU.mgrDevice.objects[deviceId];
698713
{{{ gpu.makeCheckDefined('deviceWrapper') }}}
699714
if (!deviceWrapper.lostCallback) {
700715
// device.lost hasn't been registered yet - register it.
701-
deviceWrapper.object["lost"]["then"](function(info) {
716+
deviceWrapper.object["lost"].then(function(info) {
702717
deviceWrapper.lostCallback(info);
703718
});
704719
}
705720
deviceWrapper.lostCallback = function(info) {
706-
var messagePtr = allocateUTF8(info.message);
707-
{{{ makeDynCall('viii', 'callback') }}}(
708-
WebGPU.DeviceLostReason[info.reason], messagePtr, userdata);
709-
_free(messagePtr);
721+
// This will skip the callback if the runtime is no longer alive.
722+
callUserCallback(function() {
723+
var messagePtr = allocateUTF8(info.message);
724+
{{{ makeDynCall('viii', 'callback') }}}(WebGPU.DeviceLostReason[info.reason], messagePtr, userdata);
725+
_free(messagePtr);
726+
});
710727
};
711728
},
712729

730+
wgpuDeviceSetUncapturedErrorCallback__deps: [
731+
'$callUserCallback',
713732
#if MINIMAL_RUNTIME
714-
wgpuDeviceSetUncapturedErrorCallback__deps: ['$allocateUTF8'],
733+
'$allocateUTF8',
715734
#endif
735+
],
716736
wgpuDeviceSetUncapturedErrorCallback: function(deviceId, callback, userdata) {
717737
var device = WebGPU.mgrDevice.get(deviceId);
718738
device["onuncapturederror"] = function(ev) {
719-
// WGPUErrorType type, const char* message, void* userdata
720-
var Validation = 0x00000001;
721-
var OutOfMemory = 0x00000002;
722-
var type;
739+
// This will skip the callback if the runtime is no longer alive.
740+
callUserCallback(function() {
741+
// WGPUErrorType type, const char* message, void* userdata
742+
var Validation = 0x00000001;
743+
var OutOfMemory = 0x00000002;
744+
var type;
723745
#if ASSERTIONS
724-
assert(typeof GPUValidationError !== 'undefined');
725-
assert(typeof GPUOutOfMemoryError !== 'undefined');
746+
assert(typeof GPUValidationError !== 'undefined');
747+
assert(typeof GPUOutOfMemoryError !== 'undefined');
726748
#endif
727-
if (ev.error instanceof GPUValidationError) type = Validation;
728-
else if (ev.error instanceof GPUOutOfMemoryError) type = OutOfMemory;
729-
var messagePtr = allocateUTF8(ev.error.message);
730-
{{{ makeDynCall('viii', 'callback') }}}(type, messagePtr, userdata);
731-
_free(messagePtr);
749+
if (ev.error instanceof GPUValidationError) type = Validation;
750+
else if (ev.error instanceof GPUOutOfMemoryError) type = OutOfMemory;
751+
752+
var messagePtr = allocateUTF8(ev.error.message);
753+
{{{ makeDynCall('viii', 'callback') }}}(type, messagePtr, userdata);
754+
_free(messagePtr);
755+
});
732756
};
733757
},
734758

@@ -1345,13 +1369,26 @@ var LibraryWebGPU = {
13451369
queue["submit"](cmds);
13461370
},
13471371

1372+
wgpuQueueOnSubmittedWorkDone__deps: [
1373+
'$callUserCallback',
1374+
#if !MINIMAL_RUNTIME
1375+
'$runtimeKeepalivePush', '$runtimeKeepalivePop',
1376+
#endif
1377+
],
13481378
wgpuQueueOnSubmittedWorkDone: function(queueId, callback, userdata) {
13491379
var queue = WebGPU.mgrQueue.get(queueId);
13501380
return queue["onSubmittedWorkDone"]();
1351-
queue["onSubmittedWorkDone"]()["then"](function() {
1352-
{{{ makeDynCall('vii', 'callback') }}}({{{ gpu.QueueWorkDoneStatus.Success }}}, userdata);
1381+
{{{ runtimeKeepalivePush() }}}
1382+
queue["onSubmittedWorkDone"]().then(function() {
1383+
{{{ runtimeKeepalivePop() }}}
1384+
callUserCallback(function() {
1385+
{{{ makeDynCall('vii', 'callback') }}}({{{ gpu.QueueWorkDoneStatus.Success }}}, userdata);
1386+
});
13531387
}, function() {
1354-
{{{ makeDynCall('vii', 'callback') }}}({{{ gpu.QueueWorkDoneStatus.Error }}}, userdata);
1388+
{{{ runtimeKeepalivePop() }}}
1389+
callUserCallback(function() {
1390+
{{{ makeDynCall('vii', 'callback') }}}({{{ gpu.QueueWorkDoneStatus.Error }}}, userdata);
1391+
});
13551392
});
13561393
},
13571394

@@ -1637,6 +1674,12 @@ var LibraryWebGPU = {
16371674
return data;
16381675
},
16391676

1677+
wgpuBufferMapAsync__deps: [
1678+
'$callUserCallback',
1679+
#if !MINIMAL_RUNTIME
1680+
'$runtimeKeepalivePush', '$runtimeKeepalivePop',
1681+
#endif
1682+
],
16401683
wgpuBufferMapAsync: function(bufferId, mode, offset, size, callback, userdata) {
16411684
var bufferWrapper = WebGPU.mgrBuffer.objects[bufferId];
16421685
{{{ gpu.makeCheckDefined('bufferWrapper') }}}
@@ -1646,11 +1689,18 @@ var LibraryWebGPU = {
16461689

16471690
// `callback` takes (WGPUBufferMapAsyncStatus status, void * userdata)
16481691

1649-
buffer["mapAsync"](mode, offset, size)["then"](function() {
1650-
{{{ makeDynCall('vii', 'callback') }}}({{{ gpu.BufferMapAsyncStatus.Success }}}, userdata);
1692+
{{{ runtimeKeepalivePush() }}}
1693+
buffer["mapAsync"](mode, offset, size).then(function() {
1694+
{{{ runtimeKeepalivePop() }}}
1695+
callUserCallback(function() {
1696+
{{{ makeDynCall('vii', 'callback') }}}({{{ gpu.BufferMapAsyncStatus.Success }}}, userdata);
1697+
});
16511698
}, function() {
1652-
// TODO(kainino0x): Figure out how to pick other error status values.
1653-
{{{ makeDynCall('vii', 'callback') }}}({{{ gpu.BufferMapAsyncStatus.Error }}}, userdata);
1699+
{{{ runtimeKeepalivePop() }}}
1700+
callUserCallback(function() {
1701+
// TODO(kainino0x): Figure out how to pick other error status values.
1702+
{{{ makeDynCall('vii', 'callback') }}}({{{ gpu.BufferMapAsyncStatus.Error }}}, userdata);
1703+
});
16541704
});
16551705
},
16561706

@@ -2042,9 +2092,15 @@ var LibraryWebGPU = {
20422092
abort('wgpuInstanceProcessEvents is unsupported (use requestAnimationFrame via html5.h instead)');
20432093
#endif
20442094
},
2095+
2096+
wgpuInstanceRequestAdapter__deps: [
2097+
'$callUserCallback',
20452098
#if MINIMAL_RUNTIME
2046-
wgpuInstanceRequestAdapter__deps: ['$allocateUTF8'],
2099+
'$allocateUTF8',
2100+
#else
2101+
'$runtimeKeepalivePush', '$runtimeKeepalivePop',
20472102
#endif
2103+
],
20482104
wgpuInstanceRequestAdapter: function(instanceId, options, callback, userdata) {
20492105
{{{ gpu.makeCheck('instanceId === 0, "WGPUInstance is ignored"') }}}
20502106

@@ -2063,21 +2119,29 @@ var LibraryWebGPU = {
20632119
var messagePtr = allocateUTF8('WebGPU not available on this browser (navigator.gpu is not available)');
20642120
{{{ makeDynCall('viiii', 'callback') }}}({{{ gpu.RequestAdapterStatus.Unavailable }}}, 0, messagePtr, userdata);
20652121
_free(messagePtr);
2122+
return;
20662123
}
20672124

2068-
navigator["gpu"]["requestAdapter"](opts)["then"](function(adapter) {
2069-
if (adapter) {
2070-
var adapterId = WebGPU.mgrAdapter.create(adapter);
2071-
{{{ makeDynCall('viiii', 'callback') }}}({{{ gpu.RequestAdapterStatus.Success }}}, adapterId, 0, userdata);
2072-
} else {
2073-
var messagePtr = allocateUTF8('WebGPU not available on this system (requestAdapter returned null)');
2074-
{{{ makeDynCall('viiii', 'callback') }}}({{{ gpu.RequestAdapterStatus.Unavailable }}}, 0, messagePtr, userdata);
2075-
_free(messagePtr);
2076-
}
2125+
{{{ runtimeKeepalivePush() }}}
2126+
navigator["gpu"]["requestAdapter"](opts).then(function(adapter) {
2127+
{{{ runtimeKeepalivePop() }}}
2128+
callUserCallback(function() {
2129+
if (adapter) {
2130+
var adapterId = WebGPU.mgrAdapter.create(adapter);
2131+
{{{ makeDynCall('viiii', 'callback') }}}({{{ gpu.RequestAdapterStatus.Success }}}, adapterId, 0, userdata);
2132+
} else {
2133+
var messagePtr = allocateUTF8('WebGPU not available on this system (requestAdapter returned null)');
2134+
{{{ makeDynCall('viiii', 'callback') }}}({{{ gpu.RequestAdapterStatus.Unavailable }}}, 0, messagePtr, userdata);
2135+
_free(messagePtr);
2136+
}
2137+
});
20772138
}, function(ex) {
2078-
var messagePtr = allocateUTF8(ex.message);
2079-
{{{ makeDynCall('viiii', 'callback') }}}({{{ gpu.RequestAdapterStatus.Error }}}, 0, messagePtr, userdata);
2080-
_free(messagePtr);
2139+
{{{ runtimeKeepalivePop() }}}
2140+
callUserCallback(function() {
2141+
var messagePtr = allocateUTF8(ex.message);
2142+
{{{ makeDynCall('viiii', 'callback') }}}({{{ gpu.RequestAdapterStatus.Error }}}, 0, messagePtr, userdata);
2143+
_free(messagePtr);
2144+
});
20812145
});
20822146
},
20832147

@@ -2092,19 +2156,23 @@ var LibraryWebGPU = {
20922156
{{{ makeSetValue('properties', C_STRUCTS.WGPUAdapterProperties.adapterType, gpu.AdapterType.Unknown, 'i32') }}};
20932157
{{{ makeSetValue('properties', C_STRUCTS.WGPUAdapterProperties.backendType, gpu.BackendType.WebGPU, 'i32') }}};
20942158
},
2159+
20952160
wgpuAdapterGetLimits: function(adapterId, limitsOutPtr) {
2096-
#if ASSERTIONS
20972161
abort('TODO: wgpuAdapterGetLimits unimplemented');
2098-
#endif
20992162
},
2163+
21002164
wgpuAdapterHasFeature: function(adapterId, feature) {
2101-
#if ASSERTIONS
21022165
abort('TODO: wgpuAdapterHasFeature unimplemented');
2103-
#endif
21042166
},
2167+
2168+
wgpuAdapterRequestDevice__deps: [
2169+
'$callUserCallback',
21052170
#if MINIMAL_RUNTIME
2106-
wgpuAdapterRequestDevice__deps: ['$allocateUTF8'],
2171+
'$allocateUTF8',
2172+
#else
2173+
'$runtimeKeepalivePush', '$runtimeKeepalivePop',
21072174
#endif
2175+
],
21082176
wgpuAdapterRequestDevice: function(adapterId, descriptor, callback, userdata) {
21092177
var adapter = WebGPU.mgrAdapter.get(adapterId);
21102178

@@ -2169,14 +2237,21 @@ var LibraryWebGPU = {
21692237
}
21702238
}
21712239

2172-
adapter["requestDevice"](desc)["then"](function(device) {
2173-
var deviceWrapper = { queueId: WebGPU.mgrQueue.create(device["queue"]) };
2174-
var deviceId = WebGPU.mgrDevice.create(device, deviceWrapper);
2175-
{{{ makeDynCall('viiii', 'callback') }}}({{{ gpu.RequestDeviceStatus.Success }}}, deviceId, 0, userdata);
2240+
{{{ runtimeKeepalivePush() }}}
2241+
adapter["requestDevice"](desc).then(function(device) {
2242+
{{{ runtimeKeepalivePop() }}}
2243+
callUserCallback(function() {
2244+
var deviceWrapper = { queueId: WebGPU.mgrQueue.create(device["queue"]) };
2245+
var deviceId = WebGPU.mgrDevice.create(device, deviceWrapper);
2246+
{{{ makeDynCall('viiii', 'callback') }}}({{{ gpu.RequestDeviceStatus.Success }}}, deviceId, 0, userdata);
2247+
});
21762248
}, function(ex) {
2177-
var messagePtr = allocateUTF8(ex.message);
2178-
{{{ makeDynCall('viiii', 'callback') }}}({{{ gpu.RequestDeviceStatus.Error }}}, 0, messagePtr, userdata);
2179-
_free(messagePtr);
2249+
{{{ runtimeKeepalivePop() }}}
2250+
callUserCallback(function() {
2251+
var messagePtr = allocateUTF8(ex.message);
2252+
{{{ makeDynCall('viiii', 'callback') }}}({{{ gpu.RequestDeviceStatus.Error }}}, 0, messagePtr, userdata);
2253+
_free(messagePtr);
2254+
});
21802255
});
21812256
},
21822257

tests/test_browser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4481,11 +4481,11 @@ def test_webgl_simple_enable_extensions(self):
44814481
@requires_graphics_hardware
44824482
def test_webgpu_basic_rendering(self):
44834483
for args in [[], ['-s', 'ASSERTIONS', '--closure=1'], ['-s', 'MAIN_MODULE=1']]:
4484-
self.btest_exit('webgpu_basic_rendering.cpp', args=['-s', 'USE_WEBGPU', '-s', 'EXPORTED_RUNTIME_METHODS=["ccall"]'] + args)
4484+
self.btest_exit('webgpu_basic_rendering.cpp', args=['-s', 'USE_WEBGPU'] + args)
44854485

44864486
def test_webgpu_get_device(self):
44874487
for args in [['-s', 'ASSERTIONS', '--closure=1']]:
4488-
self.btest_exit('webgpu_get_device.cpp', args=['-s', 'USE_WEBGPU', '-s', 'EXPORTED_RUNTIME_METHODS=["ccall"]'] + args)
4488+
self.btest_exit('webgpu_get_device.cpp', args=['-s', 'USE_WEBGPU'] + args)
44894489

44904490
# Tests the feature that shell html page can preallocate the typed array and place it
44914491
# to Module.buffer before loading the script page.

tests/webgpu_basic_rendering.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ void GetDevice(void (*callback)(wgpu::Device)) {
2727
}
2828
if (status == WGPURequestAdapterStatus_Unavailable) {
2929
printf("WebGPU unavailable; exiting cleanly\n");
30+
// exit(0) (rather than emscripten_force_exit(0)) ensures there is no dangling keepalive.
3031
exit(0);
3132
}
3233
assert(status == WGPURequestAdapterStatus_Success);
@@ -315,10 +316,6 @@ void doRenderTest() {
315316

316317
wgpu::SwapChain swapChain;
317318

318-
void finishTest(void*) {
319-
emscripten_force_exit(0);
320-
}
321-
322319
void frame() {
323320
wgpu::TextureView backbuffer = swapChain.GetCurrentTextureView();
324321
render(backbuffer);
@@ -327,7 +324,9 @@ void frame() {
327324
// check the result.
328325

329326
emscripten_cancel_main_loop();
330-
emscripten_async_call(finishTest, (void*)0, 100);
327+
328+
// exit(0) (rather than emscripten_force_exit(0)) ensures there is no dangling keepalive.
329+
exit(0);
331330
}
332331

333332
void run() {
@@ -365,6 +364,12 @@ int main() {
365364
run();
366365
});
367366

368-
// result will be reported when the main_loop completes
369-
emscripten_exit_with_live_runtime();
367+
// The test result will be reported when the main_loop completes.
368+
// emscripten_exit_with_live_runtime isn't needed because the WebGPU
369+
// callbacks should all automatically keep the runtime alive until
370+
// emscripten_set_main_loop, and that should keep it alive until
371+
// emscripten_cancel_main_loop.
372+
//
373+
// This code is returned when the runtime exits unless something else sets it, like exit(0).
374+
return 99;
370375
}

0 commit comments

Comments
 (0)