Start · Sprachen · JavaScript · Referenz · GPUDevice.importExternalTexture()

GPUDevice.importExternalTexture()

Methode

Importiert das aktuelle Bild eines <code>HTMLVideoElement</code> oder <code>VideoFrame</code> ohne Kopie als <code>GPUExternalTexture</code>, damit Shader es direkt lesen können.

seit JavaScript WebGPU – Chrome/Edge 113+ (Desktop), weitere Brows Kategorie: webapi

Signatur

GPUDevice.prototype.importExternalTexture(descriptor)

Beschreibung

importExternalTexture() bindet Videobilder so effizient wie möglich an die GPU. Die Methode nimmt ein HTMLVideoElement oder ein VideoFrame (WebCodecs) entgegen und gibt ein GPUExternalTexture zurück. Diese Textur ist ein schreibgeschützter Schnappschuss des aktuellen Frames. Der Browser kann dabei oft den Speicher des Video-Decoders direkt nutzen. Kopien und Farbraum-Umrechnungen in JavaScript (etwa über copyExternalImageToTexture()) fallen damit weg.

Typische Anwendungen sind Videofilter in Echtzeit, Chroma-Keying, Webcam-Effekte, 360°-Videoplayer und Videos als Textur in 3D-Szenen. Die Umwandlung von YUV nach RGB und die Farbraum-Umrechnung erledigt der Browser. Im WGSL-Shader deklarieren Sie die Textur als texture_external und lesen sie mit textureSampleBaseClampToEdge() oder textureLoad(). Im GPUBindGroupLayout ist dafür ein Eintrag mit externalTexture: {} nötig.

Eine externe Textur ist nur kurz gültig. Wurde sie aus einem HTMLVideoElement importiert, verfällt sie automatisch, sobald der Browser sie freigibt – spätestens, wenn das Video einen neuen Frame zeigt. Wurde sie aus einem VideoFrame importiert, verfällt sie beim Aufruf von frame.close(). Deshalb gilt: in jedem Frame neu importieren und die Bind Group neu erzeugen, am besten in requestAnimationFrame() oder HTMLVideoElement.requestVideoFrameCallback().

Parameter

Name Typ Default Beschreibung
$descriptor Pflicht GPUExternalTextureDescriptor Konfigurationsobjekt mit den folgenden Eigenschaften.
$descriptor.source Pflicht HTMLVideoElement | VideoFrame Die Videoquelle, deren aktueller Frame importiert wird. Ein Video muss bereits Bilddaten haben (readyState >= HAVE_CURRENT_DATA). Ein VideoFrame darf noch nicht geschlossen sein.
$descriptor.colorSpace "srgb" | "display-p3" "srgb" Farbraum, in den die Pixel beim Auslesen im Shader umgerechnet werden.
$descriptor.label string "" Optionaler Name für Fehlermeldungen und Debugging-Werkzeuge.

Rückgabewert

Typ
GPUExternalTexture
Beschreibung
Ein schreibgeschütztes Objekt für den aktuellen Videoframe. Es kann in einer Bind Group an einen Eintrag mit externalTexture gebunden werden. Das Objekt ist nur kurz gültig, siehe Beschreibung.

Beispiele

Video in jedem Frame auf ein Canvas rendern

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();

const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format });

const video = document.createElement('video');
video.src = 'clip.mp4';
video.muted = true;
video.loop = true;
await video.play();

const shader = device.createShaderModule({
  code: /* wgsl */ `
    @group(0) @binding(0) var samp: sampler;
    @group(0) @binding(1) var tex: texture_external;

    struct VOut { @builtin(position) pos: vec4f, @location(0) uv: vec2f };

    @vertex fn vs(@builtin(vertex_index) i: u32) -> VOut {
      var p = array(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3));
      var o: VOut;
      o.pos = vec4f(p[i], 0, 1);
      o.uv = p[i] * vec2f(0.5, -0.5) + 0.5;
      return o;
    }

    @fragment fn fs(in: VOut) -> @location(0) vec4f {
      return textureSampleBaseClampToEdge(tex, samp, in.uv);
    }`
});

const pipeline = device.createRenderPipeline({
  layout: 'auto',
  vertex: { module: shader, entryPoint: 'vs' },
  fragment: { module: shader, entryPoint: 'fs', targets: [{ format }] }
});
const sampler = device.createSampler({ magFilter: 'linear', minFilter: 'linear' });

const frame = () => {
  // In JEDEM Frame neu importieren – die alte Textur ist dann bereits verfallen
  const externalTexture = device.importExternalTexture({ source: video });

  const bindGroup = device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),
    entries: [
      { binding: 0, resource: sampler },
      { binding: 1, resource: externalTexture }
    ]
  });

  const encoder = device.createCommandEncoder();
  const pass = encoder.beginRenderPass({
    colorAttachments: [{
      view: context.getCurrentTexture().createView(),
      loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 1]
    }]
  });
  pass.setPipeline(pipeline);
  pass.setBindGroup(0, bindGroup);
  pass.draw(3);
  pass.end();
  device.queue.submit([encoder.finish()]);

  requestAnimationFrame(frame);
};
requestAnimationFrame(frame);

Bind Group Layout explizit mit externalTexture anlegen

const bindGroupLayout = device.createBindGroupLayout({
  entries: [
    { binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
    { binding: 1, visibility: GPUShaderStage.FRAGMENT, externalTexture: {} }
  ]
});

const pipelineLayout = device.createPipelineLayout({
  bindGroupLayouts: [bindGroupLayout]
});

const externalTexture = device.importExternalTexture({
  source: video,
  colorSpace: 'display-p3',
  label: 'Webcam-Frame'
});
console.log(externalTexture.label);
Webcam-Frame

Webcam-Frames über WebCodecs (VideoFrame) verarbeiten

const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getVideoTracks();

// MediaStreamTrackProcessor liefert VideoFrame-Objekte (Chromium)
const processor = new MediaStreamTrackProcessor({ track });
const reader = processor.readable.getReader();

while (true) {
  const { value: videoFrame, done } = await reader.read();
  if (done) break;

  const externalTexture = device.importExternalTexture({ source: videoFrame });
  renderWithTexture(externalTexture); // eigene Render-Funktion wie im ersten Beispiel

  // Erst nach dem submit() schließen – danach verfällt die Textur
  videoFrame.close();
}