Skip to content

Commit

Permalink
GPUExternalTexture: Add cts to cover non-YUV format video frame (#3451)
Browse files Browse the repository at this point in the history
The cts lacks coverage for import non-YUV format video frame.
This PR construct such frame using webcodec VideoFrame and import
it in WebGPU.
  • Loading branch information
shaoboyan authored Mar 4, 2024
1 parent 8ae71f6 commit ea0efb1
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/webgpu/listing_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
133 changes: 131 additions & 2 deletions src/webgpu/web_platform/external_texture/video.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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: {
Expand Down Expand Up @@ -63,7 +67,7 @@ function createExternalTextureSamplingTestPipeline(t: GPUTest): GPURenderPipelin
entryPoint: 'main',
targets: [
{
format: kFormat,
format: colorAttachmentFormat,
},
],
},
Expand Down Expand Up @@ -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(
`
Expand Down

0 comments on commit ea0efb1

Please sign in to comment.