mgpuLandExternalTexture function
Run the external-texture landing pass: externalTexture -> packed RGBA8
array<u32> in the buffer behind dstBufferHandle, the same destination
layout the native D3D11 path writes.
Returns false — having said why, once — if any step fails. 🔴 A false here must NOT be treated as "try the ordinary route instead": no other route on this platform can read an external texture.
Implementation
bool mgpuLandExternalTexture({
required JSObject externalTexture,
required int dstBufferHandle,
required int width,
required int height,
required int downscale,
}) {
final dev = _importDevice();
if (dev == null) {
_sayLandFailed('no GPUDevice');
return false;
}
final dst = getWebGpuJsObject(dstBufferHandle);
if (dst == null) {
_sayLandFailed('destination WGPUBuffer $dstBufferHandle is not in the '
'Emscripten object table');
return false;
}
final s = downscale < 1 ? 1 : downscale;
final key = '${width}x${height}:$s';
var step = 'createComputePipeline';
try {
final pipeline = _landPipelines.putIfAbsent(key, () {
final module = dev.createShaderModule(
{'code': _webLandWgsl(width, height, s)}.jsify() as JSObject);
return dev.createComputePipeline({
'layout': 'auto',
'compute': {'module': module, 'entryPoint': 'main'},
}.jsify() as JSObject);
});
// 🔴 THE BIND GROUP CANNOT BE CACHED. A GPUExternalTexture is SINGLE-USE:
// it expires with the frame it came from, so a cached group would
// reference a dead texture on the very next pass.
step = 'createBindGroup';
final bindGroup = dev.createBindGroup({
'layout': (pipeline as _GPUPipeline).getBindGroupLayout(0),
'entries': [
{'binding': 0, 'resource': externalTexture},
{
'binding': 1,
'resource': {'buffer': dst},
},
],
}.jsify() as JSObject);
step = 'dispatch';
final encoder = dev.createCommandEncoder() as _GPUEncoder;
final pass = encoder.beginComputePass() as _GPUComputePass;
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups((width + 7) ~/ 8, (height + 7) ~/ 8, 1);
pass.end();
(dev.queue as _GPUQueue).submit(<JSObject>[encoder.finish()].toJS);
return true;
} catch (e) {
// The step is named because these are all one-line interop calls that fail
// identically from the outside, and this path cannot be unit-tested.
_sayLandFailed('$step threw: $e');
_landPipelines.remove(key);
return false;
}
}