in src/webgpu/util/copy_to_texture.ts [58:191]
export class TextureUploadingUtils extends TextureTestMixin(AllFeaturesMaxLimitsGPUTest) {
doFlipY(
sourcePixels: Uint8ClampedArray,
width: number,
height: number,
bytesPerPixel: number
): Uint8ClampedArray {
const dstPixels = new Uint8ClampedArray(width * height * bytesPerPixel);
for (let i = 0; i < height; ++i) {
for (let j = 0; j < width; ++j) {
const srcPixelPos = i * width + j;
// WebGL readPixel returns pixels from bottom-left origin. Using CopyExternalImageToTexture
// to copy from WebGL Canvas keeps top-left origin. So the expectation from webgl.readPixel should
// be flipped.
const dstPixelPos = (height - i - 1) * width + j;
memcpy(
{ src: sourcePixels, start: srcPixelPos * bytesPerPixel, length: bytesPerPixel },
{ dst: dstPixels, start: dstPixelPos * bytesPerPixel }
);
}
}
return dstPixels;
}
getExpectedDstPixelsFromSrcPixels({
srcPixels,
srcOrigin,
srcSize,
dstOrigin,
dstSize,
subRectSize,
format,
flipSrcBeforeCopy,
srcDoFlipYDuringCopy,
conversion,
}: {
srcPixels: Uint8ClampedArray;
srcOrigin: GPUOrigin2D;
srcSize: GPUExtent3D;
dstOrigin: GPUOrigin3D;
dstSize: GPUExtent3D;
subRectSize: GPUExtent3D;
format: RegularTextureFormat;
flipSrcBeforeCopy: boolean;
srcDoFlipYDuringCopy: boolean;
conversion: {
srcPremultiplied: boolean;
dstPremultiplied: boolean;
srcColorSpace?: PredefinedColorSpace;
dstColorSpace?: PredefinedColorSpace;
};
}): TexelView {
const applyConversion = makeInPlaceColorConversion(conversion);
const reifySrcOrigin = reifyOrigin3D(srcOrigin);
const reifySrcSize = reifyExtent3D(srcSize);
const reifyDstOrigin = reifyOrigin3D(dstOrigin);
const reifyDstSize = reifyExtent3D(dstSize);
const reifySubRectSize = reifyExtent3D(subRectSize);
assert(
reifyDstOrigin.x + reifySubRectSize.width <= reifyDstSize.width &&
reifyDstOrigin.y + reifySubRectSize.height <= reifyDstSize.height,
'subrect is out of bounds'
);
const divide = 255.0;
return TexelView.fromTexelsAsColors(
format,
coords => {
assert(
coords.x >= reifyDstOrigin.x &&
coords.y >= reifyDstOrigin.y &&
coords.x < reifyDstOrigin.x + reifySubRectSize.width &&
coords.y < reifyDstOrigin.y + reifySubRectSize.height &&
coords.z === 0,
'out of bounds'
);
// Map dst coords to get candidate src pixel position in y.
let yInSubRect = coords.y - reifyDstOrigin.y;
// If srcDoFlipYDuringCopy is true, a flipY op has been applied to src during copy.
// WebGPU spec requires origin option relative to the top-left corner of the source image,
// increasing downward consistently.
// https://www.w3.org/TR/webgpu/#dom-gpuimagecopyexternalimage-flipy
// Flip only happens in copy rect contents and src origin always top-left.
// Get candidate src pixel position in y by mirroring in copy sub rect.
if (srcDoFlipYDuringCopy) yInSubRect = reifySubRectSize.height - 1 - yInSubRect;
let src_y = yInSubRect + reifySrcOrigin.y;
// Test might generate flipped source based on srcPixels, e.g. Create ImageBitmap based on srcPixels but set orientation to 'flipY'
// Get candidate src pixel position in y by mirroring in source.
if (flipSrcBeforeCopy) src_y = reifySrcSize.height - src_y - 1;
const pixelPos =
src_y * reifySrcSize.width + (coords.x - reifyDstOrigin.x) + reifySrcOrigin.x;
const rgba = {
R: srcPixels[pixelPos * 4] / divide,
G: srcPixels[pixelPos * 4 + 1] / divide,
B: srcPixels[pixelPos * 4 + 2] / divide,
A: srcPixels[pixelPos * 4 + 3] / divide,
};
applyConversion(rgba);
return rgba;
},
{ clampToFormatRange: true }
);
}
doTestAndCheckResult(
imageCopyExternalImage: GPUCopyExternalImageSourceInfo,
dstTextureCopyView: GPUCopyExternalImageDestInfo,
expTexelView: TexelView,
copySize: Required<GPUExtent3DDict>,
texelCompareOptions: TexelCompareOptions
): void {
this.device.queue.copyExternalImageToTexture(
imageCopyExternalImage,
dstTextureCopyView,
copySize
);
this.expectTexelViewComparisonIsOkInTexture(
{ texture: dstTextureCopyView.texture, origin: dstTextureCopyView.origin },
expTexelView,
copySize,
texelCompareOptions
);
}
}