diff --git a/src/resources/four-colors.avif b/src/resources/four-colors.avif
new file mode 100644
index 00000000000..06394f43827
Binary files /dev/null and b/src/resources/four-colors.avif differ
diff --git a/src/resources/four-colors.bmp b/src/resources/four-colors.bmp
new file mode 100644
index 00000000000..d61f80398ed
Binary files /dev/null and b/src/resources/four-colors.bmp differ
diff --git a/src/resources/four-colors.gif b/src/resources/four-colors.gif
new file mode 100644
index 00000000000..4d4642f7e0c
Binary files /dev/null and b/src/resources/four-colors.gif differ
diff --git a/src/resources/four-colors.ico b/src/resources/four-colors.ico
new file mode 100644
index 00000000000..8ebb5d74799
Binary files /dev/null and b/src/resources/four-colors.ico differ
diff --git a/src/resources/four-colors.svg b/src/resources/four-colors.svg
new file mode 100644
index 00000000000..a3ba74a5640
--- /dev/null
+++ b/src/resources/four-colors.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/src/resources/four-colors.webp b/src/resources/four-colors.webp
new file mode 100644
index 00000000000..f7dd40bee9a
Binary files /dev/null and b/src/resources/four-colors.webp differ
diff --git a/src/webgpu/web_platform/copyToTexture/image_file.spec.ts b/src/webgpu/web_platform/copyToTexture/image_file.spec.ts
index aa9a16e16dc..682e2339d45 100644
--- a/src/webgpu/web_platform/copyToTexture/image_file.spec.ts
+++ b/src/webgpu/web_platform/copyToTexture/image_file.spec.ts
@@ -6,11 +6,14 @@ import { makeTestGroup } from '../../../common/framework/test_group.js';
import { TextureUploadingUtils } from '../../util/copy_to_texture.js';
import {
convertToUnorm8,
- GetSourceFromImageFile,
+ GetSourceFromEXIFImageFile,
kImageNames,
kImageInfo,
kImageExpectedColors,
kObjectTypeFromFiles,
+ kEXIFImageNames,
+ kEXIFImageInfo,
+ loadImageFileAndRun,
} from '../util.js';
export const g = makeTestGroup(TextureUploadingUtils);
@@ -38,7 +41,7 @@ g.test('from_orientation_metadata_file')
)
.params(u =>
u //
- .combine('imageName', kImageNames)
+ .combine('imageName', kEXIFImageNames)
.combine('objectTypeFromFile', kObjectTypeFromFiles)
.combine('srcDoFlipYDuringCopy', [true, false])
)
@@ -47,7 +50,7 @@ g.test('from_orientation_metadata_file')
const kColorFormat = 'rgba8unorm';
// Load image file.
- const source = await GetSourceFromImageFile(t, imageName, objectTypeFromFile);
+ const source = await GetSourceFromEXIFImageFile(t, imageName, objectTypeFromFile);
const width = source.width;
const height = source.height;
@@ -72,7 +75,7 @@ g.test('from_orientation_metadata_file')
}
);
- const expect = kImageInfo[imageName].display;
+ const expect = kEXIFImageInfo[imageName].display;
const presentColors = kImageExpectedColors.srgb;
if (srcDoFlipYDuringCopy) {
@@ -123,3 +126,111 @@ g.test('from_orientation_metadata_file')
]);
}
});
+
+g.test('from_multiple_formats')
+ .desc(
+ `
+ Test HTMLImageElements which loaded multiple image file formats. Including
+ *.jpg, *.png, *.bmp, *.webp, *.avif, *.svg, *.ico and *.gif.
+
+ It creates an HTMLImageElement using images in the 'resources' folder.
+
+ Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
+ of dst texture, and read one pixel out to compare with the manually documented expected color.
+
+ If 'flipY' in 'GPUCopyExternalImageSourceInfo' is set to 'true', copy will ensure the result
+ is flipped.
+
+ The tests covers:
+ - Image with multiple image file format
+ - Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcDoFlipYDuringCopy' in cases)
+ - TODO: partial copy tests should be added
+ - TODO: all valid dstColorFormat tests should be added.
+ - TODO(#4108): Make this work in service workers (see GetSourceFromImageFile)
+ `
+ )
+ .params(u =>
+ u //
+ .combine('imageName', kImageNames)
+ .combine('srcDoFlipYDuringCopy', [true, false])
+ )
+ .fn(async t => {
+ const { imageName, srcDoFlipYDuringCopy } = t.params;
+ const kColorFormat = 'rgba8unorm';
+ await loadImageFileAndRun(t, imageName, (source: HTMLImageElement) => {
+ const width = source.width;
+ const height = source.height;
+
+ const dstTexture = t.createTextureTracked({
+ size: { width, height },
+ format: kColorFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+
+ t.device.queue.copyExternalImageToTexture(
+ {
+ source,
+ flipY: srcDoFlipYDuringCopy,
+ },
+ {
+ texture: dstTexture,
+ },
+ {
+ width,
+ height,
+ }
+ );
+
+ const expect = kImageInfo[imageName].display;
+ const presentColors = kImageExpectedColors.srgb;
+
+ if (srcDoFlipYDuringCopy) {
+ t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
+ // Flipped top-left.
+ {
+ coord: { x: width * 0.25, y: height * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
+ },
+ // Flipped top-right.
+ {
+ coord: { x: width * 0.75, y: height * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
+ },
+ // Flipped bottom-left.
+ {
+ coord: { x: width * 0.25, y: height * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.topLeftColor]),
+ },
+ // Flipped bottom-right.
+ {
+ coord: { x: width * 0.75, y: height * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.topRightColor]),
+ },
+ ]);
+ } else {
+ t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
+ // Top-left.
+ {
+ coord: { x: width * 0.25, y: height * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.topLeftColor]),
+ },
+ // Top-right.
+ {
+ coord: { x: width * 0.75, y: height * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.topRightColor]),
+ },
+ // Bottom-left.
+ {
+ coord: { x: width * 0.25, y: height * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
+ },
+ // Bottom-right.
+ {
+ coord: { x: width * 0.75, y: height * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
+ },
+ ]);
+ }
+ });
+ });
diff --git a/src/webgpu/web_platform/util.ts b/src/webgpu/web_platform/util.ts
index 66a2163b738..7b5131d4ba8 100644
--- a/src/webgpu/web_platform/util.ts
+++ b/src/webgpu/web_platform/util.ts
@@ -626,7 +626,7 @@ const kFourColorsInfo = {
},
} as const;
-export const kImageInfo = makeTable({
+export const kEXIFImageInfo = makeTable({
table: {
'four-colors.jpg': kFourColorsInfo,
'four-colors-rotate-90-cw.jpg': kFourColorsInfo,
@@ -635,9 +635,25 @@ export const kImageInfo = makeTable({
},
} as const);
+export const kImageInfo = makeTable({
+ table: {
+ 'four-colors.jpg': kFourColorsInfo,
+ 'four-colors.png': kFourColorsInfo,
+ 'four-colors.bmp': kFourColorsInfo,
+ 'four-colors.webp': kFourColorsInfo,
+ 'four-colors.gif': kFourColorsInfo,
+ 'four-colors.avif': kFourColorsInfo,
+ 'four-colors.ico': kFourColorsInfo,
+ 'four-colors.svg': kFourColorsInfo,
+ },
+} as const);
+
type ImageName = keyof typeof kImageInfo;
export const kImageNames: readonly ImageName[] = keysOf(kImageInfo);
+type EXIFImageName = keyof typeof kEXIFImageInfo;
+export const kEXIFImageNames: readonly EXIFImageName[] = keysOf(kEXIFImageInfo);
+
type ObjectTypeFromFile = (typeof kObjectTypeFromFiles)[number];
export const kObjectTypeFromFiles = [
'ImageBitmap-from-Blob',
@@ -649,12 +665,12 @@ export const kObjectTypeFromFiles = [
* Load image file(e.g. *.jpg) from ImageBitmap, blob or HTMLImageElement. And
* convert the result to valid source that GPUCopyExternalImageSource supported.
*/
-export async function GetSourceFromImageFile(
+export async function GetSourceFromEXIFImageFile(
test: GPUTest,
- imageName: ImageName,
+ exifImageName: EXIFImageName,
objectTypeFromFile: ObjectTypeFromFile
): Promise {
- const imageUrl = getResourcePath(imageName);
+ const imageUrl = getResourcePath(exifImageName);
switch (objectTypeFromFile) {
case 'ImageBitmap-from-Blob': {
@@ -689,3 +705,44 @@ export async function GetSourceFromImageFile(
}
}
}
+
+/**
+ * Create HTMLImageElement and load image file and waits for it to be loaded.
+ * Returns a promise which resolves after `callback` (which may be async) completes.
+ *
+ * @param imageName An valid imageName in kkImageInfo table .
+ * @param callback Function to call when HTMLImageElement is loaded.
+ *
+ */
+export function loadImageFileAndRun(
+ test: GPUTest,
+ imageName: ImageName,
+ callback: (image: HTMLImageElement) => unknown | Promise
+): Promise {
+ return raceWithRejectOnTimeout(
+ new Promise((resolve, reject) => {
+ const callbackAndResolve = (image: HTMLImageElement) =>
+ void (async () => {
+ try {
+ await callback(image);
+ resolve();
+ } catch (ex) {
+ reject(ex);
+ }
+ })();
+ // Skip test if HTMLImageElement is not available, e.g. in worker.
+ if (typeof HTMLImageElement === 'undefined') {
+ test.skip(
+ 'Try to use HTMLImage do image file decoding but HTMLImageElement not available.'
+ );
+ }
+ const image = new Image();
+ image.src = getResourcePath(imageName);
+ image.onload = () => {
+ callbackAndResolve(image);
+ };
+ }),
+ 2000,
+ 'Video never became ready'
+ );
+}