Open annevk opened 4 years ago
An assorted list of problems caused by not using this pattern:
requestAnimationFrame
vs. AudioWorkletProcessor.process
) are especially susceptible to this problem. GC in JavaScript engines are very fast these days, but they are still clearly unsuitable for real-time workloadsAudioWorkletProcess.process
parameters is a good example of this, not retaining the memory behind the parameters prevented the API from working as it should, details in https://github.com/WebAudio/web-audio-api/issues/1933)An example of a well designed API in this context would be AnalyzerNode.getFloatFrequencyData
: this is expected to be called in requestAnimationFrame
, with a buffer of the same size each time, it's natural to pass a buffer to get the values at a particular time.
This should go further than allowing passing in memory. It should specifically require that functions that use Typed Arrays of any sort must use this pattern. This allows maximal performances, for when it matters, and API designer at the design stage cannot assume a particular context of use. It also steers authors in the way of caring about performances, and potentially teaches them about the issues described above.
There's some discussion of this for WebXR in https://github.com/immersive-web/webxr-hand-input/issues/37 . Basically, to avoid recreating 3-5 objects per joint (for 50 joints), we want to introduce an API that has outparams, either of the form:
// setup
let set = new XRJointSet(inputSource.hand, ...);
// each frame
frame.populatePoses(set, ...);
for (joint in joints) {
let jointPosition = set[joint].position; // or `set.position[joint]`
let jointOrientation = set[joint].orientation; // or `set.position[joint]`
}
// OR (this one is more friendly to sending the data directly to the GPU)
// setup
let positions = new Float32Array();
let orientations = new Float32Array();
// each frame
frame.getPoses(inputSource.hand.joints, positions, orientations);
for (joint in joints) {
let jointPosition = positions[joint * 4, joint* 4 + 4];
let jointOrientation = orientations[joint * 4, joint* 4 + 4];
}
it would be nice to know if either of these is acceptable, and if the TAG has a preference between the two.
(WebGL uses this too, didn't look where.)
readPixels()
is an example of a WebGL API that fills a application-supplied array with data. It should be noted that since a design goal for WebGL was to be ~1:1 mapping of the C API there are naturally quite a few C-isms that came along with it. For that same reason, though, nobody points at WebGL as a shining example of API ergonomics. 😉
That said, I personally would very much appreciate having some platform-approved patterns available for APIs that want to allow developers to minimize object churn.
For performance-sensitive operations it can sometimes be important that the web developer is in charge of allocating memory. APIs that take a buffer can be designed for such cases (also known as "outparams"). https://encoding.spec.whatwg.org/#dom-textencoder-encodeinto has an example. I believe Web Audio has a similar API.
It would be good to discuss this in the document as an acceptable solution for performance-sensitive APIs. It might also be good to discuss the constraints. At least when discussing this for Encoding we decided that throwing an exception after some computation had already happened was not acceptable (we used a return value to encode that information instead).
cc @padenot