Start · Sprachen · JavaScript · Referenz · GPUCommandEncoder.copyBufferToTexture()

GPUCommandEncoder.copyBufferToTexture()

Methode

Zeichnet im Befehls-Encoder einen Befehl auf, der Texeldaten aus einem <code>GPUBuffer</code> in einen Bereich einer <code>GPUTexture</code> kopiert.

seit JavaScript WebGPU – Chrome/Edge 113+, Safari 26+, Firefox 141 Kategorie: webapi

Signatur

GPUCommandEncoder.prototype.copyBufferToTexture(source, destination, copySize)

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: true erzeugt 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

Typ
undefined
Beschreibung
Kein Rückgabewert. Der Kopierbefehl wird nur im Encoder aufgezeichnet. Ist ein Aufruf ungültig, wirft die Methode keine Ausnahme. Stattdessen wird der Encoder ungültig, und 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}`);
bytesPerRow: 512

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()]);