From ea0efb11192c4aae7bb3e1371a15d3afa28e217a Mon Sep 17 00:00:00 2001 From: "Yan, Shaobo" Date: Tue, 5 Mar 2024 06:47:28 +0800 Subject: [PATCH] GPUExternalTexture: Add cts to cover non-YUV format video frame (#3451) The cts lacks coverage for import non-YUV format video frame. This PR construct such frame using webcodec VideoFrame and import it in WebGPU. --- src/webgpu/listing_meta.json | 1 + .../external_texture/video.spec.ts | 133 +++++++++++++++++- 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index f0585f3365e3..a8ecd45ed3de 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -2193,6 +2193,7 @@ "webgpu:web_platform,copyToTexture,video:copy_from_video:*": { "subcaseMS": 25.101 }, "webgpu:web_platform,external_texture,video:importExternalTexture,compute:*": { "subcaseMS": 36.270 }, "webgpu:web_platform,external_texture,video:importExternalTexture,sample:*": { "subcaseMS": 34.968 }, + "webgpu:web_platform,external_texture,video:importExternalTexture,sample_non_YUV_video_frame:*": { "subcaseMS": 36.270 }, "webgpu:web_platform,external_texture,video:importExternalTexture,sampleWithVideoFrameWithVisibleRectParam:*": { "subcaseMS": 29.160 }, "webgpu:web_platform,worker,worker:dedicated_worker:*": { "subcaseMS": 245.901 }, "webgpu:web_platform,worker,worker:shared_worker:*": { "subcaseMS": 26.801 }, diff --git a/src/webgpu/web_platform/external_texture/video.spec.ts b/src/webgpu/web_platform/external_texture/video.spec.ts index 2b5e0bdb40a9..8e812ccd2a34 100644 --- a/src/webgpu/web_platform/external_texture/video.spec.ts +++ b/src/webgpu/web_platform/external_texture/video.spec.ts @@ -10,6 +10,7 @@ TODO(#3193): Test video in BT.2020 color space import { makeTestGroup } from '../../../common/framework/test_group.js'; import { GPUTest, TextureTestMixin } from '../../gpu_test.js'; +import { createCanvas } from '../../util/create_elements.js'; import { startPlayingAndWaitForVideo, getVideoFrameFromVideoElement, @@ -27,7 +28,10 @@ const kFormat = 'rgba8unorm'; export const g = makeTestGroup(TextureTestMixin(GPUTest)); -function createExternalTextureSamplingTestPipeline(t: GPUTest): GPURenderPipeline { +function createExternalTextureSamplingTestPipeline( + t: GPUTest, + colorAttachmentFormat: GPUTextureFormat = kFormat +): GPURenderPipeline { const pipeline = t.device.createRenderPipeline({ layout: 'auto', vertex: { @@ -63,7 +67,7 @@ function createExternalTextureSamplingTestPipeline(t: GPUTest): GPURenderPipelin entryPoint: 'main', targets: [ { - format: kFormat, + format: colorAttachmentFormat, }, ], }, @@ -228,6 +232,131 @@ for several combinations of video format, video color spaces and dst color space }); }); +g.test('importExternalTexture,sample_non_YUV_video_frame') + .desc( + ` +Tests that we can import an VideoFrame with non-YUV pixel format into a GPUExternalTexture and sample it. +` + ) + .params(u => + u // + .combine('videoFrameFormat', ['RGBA', 'RGBX', 'BGRA', 'BGRX'] as const) + ) + .fn(t => { + const { videoFrameFormat } = t.params; + + if (typeof VideoFrame === 'undefined') { + t.skip('WebCodec is not supported'); + } + + const canvas = createCanvas(t, 'onscreen', kWidth, kHeight); + + const canvasContext = canvas.getContext('2d'); + + if (canvasContext === null) { + t.skip(' onscreen canvas 2d context not available'); + } + + const ctx = canvasContext as CanvasRenderingContext2D; + + const rectWidth = Math.floor(kWidth / 2); + const rectHeight = Math.floor(kHeight / 2); + + // Red + ctx.fillStyle = `rgba(255, 0, 0, 1.0)`; + ctx.fillRect(0, 0, rectWidth, rectHeight); + // Lime + ctx.fillStyle = `rgba(0, 255, 0, 1.0)`; + ctx.fillRect(rectWidth, 0, kWidth - rectWidth, rectHeight); + // Blue + ctx.fillStyle = `rgba(0, 0, 255, 1.0)`; + ctx.fillRect(0, rectHeight, rectWidth, kHeight - rectHeight); + // Fuchsia + ctx.fillStyle = `rgba(255, 0, 255, 1.0)`; + ctx.fillRect(rectWidth, rectHeight, kWidth - rectWidth, kHeight - rectHeight); + + const imageData = ctx.getImageData(0, 0, kWidth, kHeight); + + // Create video frame with default color space 'srgb' + const frameInit: VideoFrameBufferInit = { + format: videoFrameFormat, + codedWidth: kWidth, + codedHeight: kHeight, + timestamp: 0, + }; + + const frame = new VideoFrame(imageData.data.buffer, frameInit); + let textureFormat: GPUTextureFormat = 'rgba8unorm'; + + if (videoFrameFormat === 'BGRA' || videoFrameFormat === 'BGRX') { + textureFormat = 'bgra8unorm'; + } + + const colorAttachment = t.device.createTexture({ + format: textureFormat, + size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 }, + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const pipeline = createExternalTextureSamplingTestPipeline(t, textureFormat); + const bindGroup = createExternalTextureSamplingTestBindGroup( + t, + undefined /* checkNonStandardIsZeroCopy */, + frame, + pipeline, + 'srgb' + ); + + const commandEncoder = t.device.createCommandEncoder(); + const passEncoder = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: colorAttachment.createView(), + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, + loadOp: 'clear', + storeOp: 'store', + }, + ], + }); + passEncoder.setPipeline(pipeline); + passEncoder.setBindGroup(0, bindGroup); + passEncoder.draw(6); + passEncoder.end(); + t.device.queue.submit([commandEncoder.finish()]); + + const expected = { + topLeft: new Uint8Array([255, 0, 0, 255]), + topRight: new Uint8Array([0, 255, 0, 255]), + bottomLeft: new Uint8Array([0, 0, 255, 255]), + bottomRight: new Uint8Array([255, 0, 255, 255]), + }; + + // For validation, we sample a few pixels away from the edges to avoid compression + // artifacts. + t.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, [ + // Top-left. + { + coord: { x: kWidth * 0.25, y: kHeight * 0.25 }, + exp: expected.topLeft, + }, + // Top-right. + { + coord: { x: kWidth * 0.75, y: kHeight * 0.25 }, + exp: expected.topRight, + }, + // Bottom-left. + { + coord: { x: kWidth * 0.25, y: kHeight * 0.75 }, + exp: expected.bottomLeft, + }, + // Bottom-right. + { + coord: { x: kWidth * 0.75, y: kHeight * 0.75 }, + exp: expected.bottomRight, + }, + ]); + }); + g.test('importExternalTexture,sampleWithVideoFrameWithVisibleRectParam') .desc( `