Signatur
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
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-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();
}