Roll WebGPU CTS

Roll in copyImageBitmapToTexture cts

Change-Id: Ie4ddf5f7dad5fa6487cbaedc5ca78e6e1d448602
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1986630
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Cr-Commit-Position: refs/heads/master@{#729235}
diff --git a/webgpu/cts.html b/webgpu/cts.html
index 6099f73..5c529e9 100644
--- a/webgpu/cts.html
+++ b/webgpu/cts.html
@@ -66,6 +66,7 @@
 <meta name=variant content='?q=cts:command_buffer/render/basic:'>
 <meta name=variant content='?q=cts:command_buffer/render/rendering:'>
 <meta name=variant content='?q=cts:command_buffer/render/storeop:'>
+<meta name=variant content='?q=cts:copyImageBitmapToTexture:'>
 <meta name=variant content='?q=cts:examples:'>
 <meta name=variant content='?q=cts:fences:'>
 <meta name=variant content='?q=cts:resource_init/sampled_texture_clear:'>
diff --git a/webgpu/framework/version.js b/webgpu/framework/version.js
index 9d1c081..8dd8302 100644
--- a/webgpu/framework/version.js
+++ b/webgpu/framework/version.js
@@ -1,3 +1,3 @@
 // AUTO-GENERATED - DO NOT EDIT. See tools/gen_version.
 
-export const version = '70754155a0bca2c24e3e3c249bab9ee3dd765a26';
+export const version = 'a5c59f8995f38843e332ff4d869a72f75d2f028d';
diff --git a/webgpu/suites/cts/copyImageBitmapToTexture.spec.js b/webgpu/suites/cts/copyImageBitmapToTexture.spec.js
new file mode 100644
index 0000000..c9b07e8
--- /dev/null
+++ b/webgpu/suites/cts/copyImageBitmapToTexture.spec.js
@@ -0,0 +1,148 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/
+
+export const description = `
+copy imageBitmap To texture tests.
+`;
+import { TestGroup, pcombine, poptions } from '../../framework/index.js';
+import { GPUTest } from './gpu_test.js';
+
+function calculateRowPitch(width, bytesPerPixel) {
+  const bytesPerRow = width * bytesPerPixel; // Rounds up to a multiple of 256 according to WebGPU requirements.
+
+  return (bytesPerRow - 1 >> 8) + 1 << 8;
+}
+
+class F extends GPUTest {
+  checkCopyImageBitmapResult(src, expected, width, height, bytesPerPixel) {
+    const exp = new Uint8Array(expected.buffer, expected.byteOffset, expected.byteLength);
+    const rowPitch = calculateRowPitch(width, bytesPerPixel);
+    const dst = this.createCopyForMapRead(src, rowPitch * height);
+    this.eventualAsyncExpectation(async niceStack => {
+      const actual = new Uint8Array((await dst.mapReadAsync()));
+      const check = this.checkBufferWithRowPitch(actual, exp, width, height, rowPitch, bytesPerPixel);
+
+      if (check !== undefined) {
+        niceStack.message = check;
+        this.rec.fail(niceStack);
+      }
+
+      dst.destroy();
+    });
+  }
+
+  checkBufferWithRowPitch(actual, exp, width, height, rowPitch, bytesPerPixel) {
+    const lines = [];
+    let failedPixels = 0;
+
+    for (let i = 0; i < height; ++i) {
+      const bytesPerRow = width * bytesPerPixel;
+
+      for (let j = 0; j < bytesPerRow; ++j) {
+        const indexExp = j + i * bytesPerRow;
+        const indexActual = j + rowPitch * i;
+
+        if (actual[indexActual] !== exp[indexExp]) {
+          if (failedPixels > 4) {
+            break;
+          }
+
+          failedPixels++;
+          lines.push(`at [${indexExp}], expected ${exp[indexExp]}, got ${actual[indexActual]}`);
+        }
+      }
+
+      if (failedPixels > 4) {
+        lines.push('... and more');
+        break;
+      }
+    }
+
+    return failedPixels > 0 ? lines.join('\n') : undefined;
+  } // Using drawImage to extract imageBitmap content.
+
+
+  imageBitmapToData(imageBitmap) {
+    const imageCanvas = document.createElement('canvas');
+    imageCanvas.width = imageBitmap.width;
+    imageCanvas.height = imageBitmap.height;
+    const imageCanvasContext = imageCanvas.getContext('2d');
+
+    if (!imageCanvasContext) {
+      throw new Error('Cannot create canvas context for reading back contents from imageBitmap.');
+    }
+
+    imageCanvasContext.drawImage(imageBitmap, 0, 0, imageBitmap.width, imageBitmap.height);
+    return imageCanvasContext.getImageData(0, 0, imageBitmap.width, imageBitmap.height).data;
+  }
+
+}
+
+export const g = new TestGroup(F);
+g.test('from ImageData', async t => {
+  const {
+    width,
+    height
+  } = t.params; // The texture format is rgba8uint, so the bytes per pixel is 4.
+
+  const bytesPerPixel = 4;
+  const imagePixels = new Uint8ClampedArray(bytesPerPixel * width * height);
+
+  for (let i = 0; i < width * height * bytesPerPixel; ++i) {
+    imagePixels[i] = i % 4 === 3 ? 255 : i % 256;
+  }
+
+  const imageData = new ImageData(imagePixels, width, height);
+  const imageBitmap = await createImageBitmap(imageData);
+  const dst = t.device.createTexture({
+    size: {
+      width: imageBitmap.width,
+      height: imageBitmap.height,
+      depth: 1
+    },
+    format: 'rgba8uint',
+    usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC
+  });
+  t.device.defaultQueue.copyImageBitmapToTexture({
+    imageBitmap,
+    origin: {
+      x: 0,
+      y: 0
+    }
+  }, {
+    texture: dst
+  }, {
+    width: imageBitmap.width,
+    height: imageBitmap.height,
+    depth: 1
+  });
+  const data = t.imageBitmapToData(imageBitmap);
+  const rowPitchValue = calculateRowPitch(imageBitmap.width, bytesPerPixel);
+  const testBuffer = t.device.createBuffer({
+    size: rowPitchValue * imageBitmap.height,
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+  });
+  const encoder = t.device.createCommandEncoder();
+  encoder.copyTextureToBuffer({
+    texture: dst,
+    mipLevel: 0,
+    origin: {
+      x: 0,
+      y: 0,
+      z: 0
+    }
+  }, {
+    buffer: testBuffer,
+    rowPitch: rowPitchValue,
+    imageHeight: 0
+  }, {
+    width: imageBitmap.width,
+    height: imageBitmap.height,
+    depth: 1
+  });
+  t.device.defaultQueue.submit([encoder.finish()]);
+  t.checkCopyImageBitmapResult(testBuffer, data, imageBitmap.width, imageBitmap.height, bytesPerPixel);
+}).params(pcombine(poptions('width', [1, 2, 4, 15, 255, 256]), //
+poptions('height', [1, 2, 4, 15, 255, 256])));
+//# sourceMappingURL=copyImageBitmapToTexture.spec.js.map
\ No newline at end of file
diff --git a/webgpu/suites/cts/gpu_test.js b/webgpu/suites/cts/gpu_test.js
index 67429b9..cf52a6c 100644
--- a/webgpu/suites/cts/gpu_test.js
+++ b/webgpu/suites/cts/gpu_test.js
@@ -99,19 +99,23 @@
     return this.device.createShaderModule({
       code
     });
-  } // TODO: add an expectContents for textures, which logs data: uris on failure
+  }
 
-
-  expectContents(src, expected) {
-    const exp = new Uint8Array(expected.buffer, expected.byteOffset, expected.byteLength);
-    const size = expected.buffer.byteLength;
+  createCopyForMapRead(src, size) {
     const dst = this.device.createBuffer({
-      size: expected.buffer.byteLength,
+      size,
       usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
     });
     const c = this.device.createCommandEncoder();
     c.copyBufferToBuffer(src, 0, dst, 0, size);
     this.queue.submit([c.finish()]);
+    return dst;
+  } // TODO: add an expectContents for textures, which logs data: uris on failure
+
+
+  expectContents(src, expected) {
+    const exp = new Uint8Array(expected.buffer, expected.byteOffset, expected.byteLength);
+    const dst = this.createCopyForMapRead(src, expected.buffer.byteLength);
     this.eventualAsyncExpectation(async niceStack => {
       const actual = new Uint8Array((await dst.mapReadAsync()));
       const check = this.checkBuffer(actual, exp);
@@ -153,7 +157,21 @@
         failedPixels++;
         lines.push(`at [${i}], expected ${exp[i]}, got ${actual[i]}`);
       }
-    }
+    } // TODO: Could make a more convenient message, which could look like e.g.:
+    //
+    //   Starting at offset 48,
+    //              got 22222222 ABCDABCD 99999999
+    //     but expected 22222222 55555555 99999999
+    //
+    // or
+    //
+    //   Starting at offset 0,
+    //              got 00000000 00000000 00000000 00000000 (... more)
+    //     but expected 00FF00FF 00FF00FF 00FF00FF 00FF00FF (... more)
+    //
+    // Or, maybe these diffs aren't actually very useful (given we have the prints just above here),
+    // and we should remove them. More important will be logging of texture data in a visual format.
+
 
     if (size <= 256 && failedPixels > 0) {
       const expHex = Array.from(exp).map(x => x.toString(16).padStart(2, '0')).join('');
diff --git a/webgpu/suites/cts/index.js b/webgpu/suites/cts/index.js
index 9fb8395..e566792 100644
--- a/webgpu/suites/cts/index.js
+++ b/webgpu/suites/cts/index.js
@@ -54,6 +54,10 @@
     "description": "renderPass store op test that drawn quad is either stored or cleared based on storeop"
   },
   {
+    "path": "copyImageBitmapToTexture",
+    "description": "copy imageBitmap To texture tests."
+  },
+  {
     "path": "examples",
     "description": "Examples of writing CTS tests with various features.\n\nStart here when looking for examples of basic framework usage."
   },