| import { assert, memcpy } from '../../common/util/util.js'; |
| import { RegularTextureFormat } from '../format_info.js'; |
| import { AllFeaturesMaxLimitsGPUTest } from '../gpu_test.js'; |
| import * as ttu from '../texture_test_utils.js'; |
| import { reifyExtent3D, reifyOrigin3D } from '../util/unions.js'; |
| |
| import { makeInPlaceColorConversion } from './color_space_conversion.js'; |
| import { TexelView } from './texture/texel_view.js'; |
| import { TexelCompareOptions } from './texture/texture_ok.js'; |
| |
| /** |
| * Predefined copy sub rect meta infos. |
| */ |
| export const kCopySubrectInfo = [ |
| { |
| srcOrigin: { x: 2, y: 2 }, |
| dstOrigin: { x: 0, y: 0, z: 0 }, |
| srcSize: { width: 16, height: 16 }, |
| dstSize: { width: 4, height: 4 }, |
| copyExtent: { width: 4, height: 4, depthOrArrayLayers: 1 }, |
| }, |
| { |
| srcOrigin: { x: 10, y: 2 }, |
| dstOrigin: { x: 0, y: 0, z: 0 }, |
| srcSize: { width: 16, height: 16 }, |
| dstSize: { width: 4, height: 4 }, |
| copyExtent: { width: 4, height: 4, depthOrArrayLayers: 1 }, |
| }, |
| { |
| srcOrigin: { x: 2, y: 10 }, |
| dstOrigin: { x: 0, y: 0, z: 0 }, |
| srcSize: { width: 16, height: 16 }, |
| dstSize: { width: 4, height: 4 }, |
| copyExtent: { width: 4, height: 4, depthOrArrayLayers: 1 }, |
| }, |
| { |
| srcOrigin: { x: 10, y: 10 }, |
| dstOrigin: { x: 0, y: 0, z: 0 }, |
| srcSize: { width: 16, height: 16 }, |
| dstSize: { width: 4, height: 4 }, |
| copyExtent: { width: 4, height: 4, depthOrArrayLayers: 1 }, |
| }, |
| { |
| srcOrigin: { x: 2, y: 2 }, |
| dstOrigin: { x: 2, y: 2, z: 0 }, |
| srcSize: { width: 16, height: 16 }, |
| dstSize: { width: 16, height: 16 }, |
| copyExtent: { width: 4, height: 4, depthOrArrayLayers: 1 }, |
| }, |
| { |
| srcOrigin: { x: 10, y: 2 }, |
| dstOrigin: { x: 2, y: 2, z: 0 }, |
| srcSize: { width: 16, height: 16 }, |
| dstSize: { width: 16, height: 16 }, |
| copyExtent: { width: 4, height: 4, depthOrArrayLayers: 1 }, |
| }, |
| ] as const; |
| |
| export class TextureUploadingUtils extends 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 |
| ); |
| |
| ttu.expectTexelViewComparisonIsOkInTexture( |
| this, |
| { texture: dstTextureCopyView.texture, origin: dstTextureCopyView.origin }, |
| expTexelView, |
| copySize, |
| texelCompareOptions |
| ); |
| } |
| } |