Signatur
Beschreibung
copyBufferToTexture() kopiert Daten aus einem GPUBuffer in eine GPUTexture. Die Kopie passiert nicht sofort: Die Methode zeichnet sie nur im GPUCommandEncoder auf. Die GPU führt sie erst aus, wenn Sie den mit encoder.finish() erzeugten GPUCommandBuffer über device.queue.submit() abschicken. So können Sie die Kopie in derselben Befehlsfolge vor oder nach Render- und Compute-Durchläufen einplanen.
Typische Einsatzfälle:
- Bild- oder Höhendaten über einen Staging-Buffer hochladen, zum Beispiel einen, der mit
mappedAtCreation: trueerzeugt wurde. - Ergebnisse eines Compute-Shaders aus einem Storage-Buffer in eine Textur übertragen, damit ein Fragment-Shader sie abtasten kann.
- Einzelne Mip-Stufen, Array-Ebenen oder Teilbereiche (über
origin) einer Textur gezielt befüllen.
Anders als bei GPUQueue.writeTexture() muss bytesPerRow hier ein Vielfaches von 256 sein. Sind die Nutzdaten einer Zeile kürzer, müssen Sie die Zeile im Buffer mit Füllbytes auf diese Länge auffüllen. Der Buffer braucht das Nutzungsflag GPUBufferUsage.COPY_SRC, die Textur das Flag GPUTextureUsage.COPY_DST.
Wenn Sie nur einmal Daten aus dem JavaScript-Speicher hochladen wollen, ist writeTexture() meist einfacher. copyBufferToTexture() lohnt sich, wenn die Daten schon auf der GPU liegen, wenn Sie einen Buffer mehrfach wiederverwenden oder wenn die Kopie genau zwischen anderen GPU-Befehlen eingeordnet werden muss.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $source Pflicht | GPUTexelCopyBufferInfo | Objekt, das die Quelle beschreibt: buffer (GPUBuffer, Pflicht), offset (Byte-Versatz, Standard 0, muss ein Vielfaches der Texel-Blockgröße sein), bytesPerRow (Bytes pro Zeile, Vielfaches von 256; nötig, sobald mehr als eine Blockzeile kopiert wird) und rowsPerImage (Blockzeilen pro Bild; nötig bei mehr als einer Tiefen- bzw. Array-Ebene). |
|
| $destination Pflicht | GPUTexelCopyTextureInfo | Objekt, das das Ziel beschreibt: texture (GPUTexture, Pflicht), mipLevel (Standard 0), origin ({x, y, z} oder Array, Standard {x: 0, y: 0, z: 0}; z ist die Array-Ebene bzw. Tiefe) und aspect ("all", "stencil-only" oder "depth-only", Standard "all"). |
|
| $copySize Pflicht | GPUExtent3D | Größe des zu kopierenden Bereichs in Texeln, entweder als {width, height, depthOrArrayLayers} oder als Array [width, height, depthOrArrayLayers]. height und depthOrArrayLayers sind standardmäßig 1. |
Rückgabewert
finish() bzw. submit() erzeugen einen Validierungsfehler.Beispiele
RGBA-Bild über einen Staging-Buffer hochladen (mit Zeilen-Padding)
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const width = 100;
const height = 50;
const bytesPerPixel = 4; // rgba8unorm
const unpadded = width * bytesPerPixel; // 400 Bytes
const bytesPerRow = Math.ceil(unpadded / 256) * 256; // 512 Bytes
// Beispiel-Pixeldaten: Farbverlauf
const pixels = new Uint8Array(unpadded * height);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = (y * width + x) * 4;
pixels.set([x * 2, y * 5, 128, 255], i);
}
}
// Staging-Buffer mit gepaddeten Zeilen erzeugen
const buffer = device.createBuffer({
size: bytesPerRow * height,
usage: GPUBufferUsage.COPY_SRC,
mappedAtCreation: true,
});
const mapped = new Uint8Array(buffer.getMappedRange());
for (let y = 0; y < height; y++) {
mapped.set(pixels.subarray(y * unpadded, (y + 1) * unpadded), y * bytesPerRow);
}
buffer.unmap();
const texture = device.createTexture({
size: [width, height],
format: 'rgba8unorm',
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
});
const encoder = device.createCommandEncoder();
encoder.copyBufferToTexture(
{ buffer, bytesPerRow, rowsPerImage: height },
{ texture },
[width, height]
);
device.queue.submit([encoder.finish()]);
console.log(`bytesPerRow: ${bytesPerRow}`);
Compute-Ergebnis aus einem Storage-Buffer in eine Textur übertragen
// Annahme: device existiert, ein Compute-Shader schreibt 256×256 r32float-Werte
const size = 256;
const resultBuffer = device.createBuffer({
size: size * size * 4,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
});
const heightMap = device.createTexture({
size: [size, size],
format: 'r32float',
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
});
const encoder = device.createCommandEncoder();
// ... hier: computePass = encoder.beginComputePass(); ... computePass.end();
// 256 Texel × 4 Bytes = 1024 Bytes pro Zeile → bereits Vielfaches von 256
encoder.copyBufferToTexture(
{ buffer: resultBuffer, bytesPerRow: size * 4 },
{ texture: heightMap },
{ width: size, height: size }
);
// ... danach Render-Pass, der heightMap abtastet
device.queue.submit([encoder.finish()]);
Teilbereich einer bestimmten Mip-Stufe und Array-Ebene befüllen
const tileBuffer = device.createBuffer({
size: 256 * 16, // 16 Zeilen à 256 Bytes (64 Texel rgba8)
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(tileBuffer, 0, new Uint8Array(256 * 16).fill(255));
const atlas = device.createTexture({
size: [512, 512, 4],
mipLevelCount: 2,
format: 'rgba8unorm',
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
});
const encoder = device.createCommandEncoder();
encoder.copyBufferToTexture(
{ buffer: tileBuffer, bytesPerRow: 256, rowsPerImage: 16 },
{ texture: atlas, mipLevel: 1, origin: { x: 64, y: 32, z: 2 } },
{ width: 64, height: 16, depthOrArrayLayers: 1 }
);
device.queue.submit([encoder.finish()]);