mgpuLandExternalTexture function

bool mgpuLandExternalTexture({
  1. required JSObject externalTexture,
  2. required int dstBufferHandle,
  3. required int width,
  4. required int height,
  5. required int downscale,
})

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;
  }
}