Skip to content

Commit 21ecca5

Browse files
authored
Add importExternalTexture test for video frame display size diff with coded size (#4589)
* Add importExternalTexture test for video frame display size diff with coded size Validation error was thrown if the video frame's dispaly size was different with its coded size in importExternalTexture, because it used the visible size for copySize. This issue had been fixed by updating the copySize to reflect the coded size rather than the visible size, and add a test for this case, see crbug.com/471021591. * Add test to check display size returned by textureDimensions * Fix lint error * Use expectGPUBufferValuesEqual for buffer checking * Remove unnecessary async
1 parent b043a4e commit 21ecca5

1 file changed

Lines changed: 218 additions & 0 deletions

File tree

src/webgpu/web_platform/external_texture/video.spec.ts

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Tests for external textures from HTMLVideoElement (and other video-type sources?
77
TODO: consider whether external_texture and copyToTexture video tests should be in the same file
88
TODO(#3193): Test video in BT.2020 color space
99
TODO(#4364): Test camera capture with copyExternalImageToTexture (not necessarily in this file)
10+
TODO(#4605): Test importExternalTexture with video frame display size different with coded size from
11+
a video file
1012
`;
1113

1214
import { makeTestGroup } from '../../../common/framework/test_group.js';
@@ -162,6 +164,69 @@ function checkNonStandardIsZeroCopyIfAvailable(): { checkNonStandardIsZeroCopy?:
162164
}
163165
}
164166

167+
function createVideoFrameWithDisplayScale(
168+
t: GPUTest,
169+
displayScale: 'smaller' | 'same' | 'larger'
170+
): { frame: VideoFrame; displayWidth: number; displayHeight: number } {
171+
const canvas = createCanvas(t, 'onscreen', kWidth, kHeight);
172+
const canvasContext = canvas.getContext('2d');
173+
174+
if (canvasContext === null) {
175+
t.skip(' onscreen canvas 2d context not available');
176+
}
177+
178+
const ctx = canvasContext;
179+
180+
const rectWidth = Math.floor(kWidth / 2);
181+
const rectHeight = Math.floor(kHeight / 2);
182+
183+
// Red
184+
ctx.fillStyle = `rgba(255, 0, 0, 1.0)`;
185+
ctx.fillRect(0, 0, rectWidth, rectHeight);
186+
// Lime
187+
ctx.fillStyle = `rgba(0, 255, 0, 1.0)`;
188+
ctx.fillRect(rectWidth, 0, kWidth - rectWidth, rectHeight);
189+
// Blue
190+
ctx.fillStyle = `rgba(0, 0, 255, 1.0)`;
191+
ctx.fillRect(0, rectHeight, rectWidth, kHeight - rectHeight);
192+
// Fuchsia
193+
ctx.fillStyle = `rgba(255, 0, 255, 1.0)`;
194+
ctx.fillRect(rectWidth, rectHeight, kWidth - rectWidth, kHeight - rectHeight);
195+
196+
const imageData = ctx.getImageData(0, 0, kWidth, kHeight);
197+
198+
let displayWidth = kWidth;
199+
let displayHeight = kHeight;
200+
switch (displayScale) {
201+
case 'smaller':
202+
displayWidth = Math.floor(kWidth / 2);
203+
displayHeight = Math.floor(kHeight / 2);
204+
break;
205+
case 'same':
206+
displayWidth = kWidth;
207+
displayHeight = kHeight;
208+
break;
209+
case 'larger':
210+
displayWidth = kWidth * 2;
211+
displayHeight = kHeight * 2;
212+
break;
213+
default:
214+
unreachable();
215+
}
216+
217+
const frameInit: VideoFrameBufferInit = {
218+
format: 'RGBA',
219+
codedWidth: kWidth,
220+
codedHeight: kHeight,
221+
displayWidth,
222+
displayHeight,
223+
timestamp: 0,
224+
};
225+
226+
const frame = new VideoFrame(imageData.data.buffer, frameInit);
227+
return { frame, displayWidth, displayHeight };
228+
}
229+
165230
g.test('importExternalTexture,sample')
166231
.desc(
167232
`
@@ -381,6 +446,159 @@ Tests that we can import an VideoFrame with non-YUV pixel format into a GPUExter
381446
]);
382447
});
383448

449+
g.test('importExternalTexture,video_frame_display_size_diff_with_coded_size')
450+
.desc(
451+
`
452+
Tests that we can import a VideoFrame with display size different with its coded size, and
453+
sampling works without validation errors.
454+
`
455+
)
456+
.params(u =>
457+
u //
458+
.combine('displayScale', ['smaller', 'same', 'larger'] as const)
459+
)
460+
.fn(t => {
461+
if (typeof VideoFrame === 'undefined') {
462+
t.skip('WebCodec is not supported');
463+
}
464+
465+
const { frame } = createVideoFrameWithDisplayScale(t, t.params.displayScale);
466+
467+
const colorAttachment = t.createTextureTracked({
468+
format: kFormat,
469+
size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
470+
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
471+
});
472+
473+
const pipeline = createExternalTextureSamplingTestPipeline(t, kFormat);
474+
const bindGroup = createExternalTextureSamplingTestBindGroup(
475+
t,
476+
undefined /* checkNonStandardIsZeroCopy */,
477+
frame,
478+
pipeline,
479+
'srgb'
480+
);
481+
482+
const commandEncoder = t.device.createCommandEncoder();
483+
const passEncoder = commandEncoder.beginRenderPass({
484+
colorAttachments: [
485+
{
486+
view: colorAttachment.createView(),
487+
clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
488+
loadOp: 'clear',
489+
storeOp: 'store',
490+
},
491+
],
492+
});
493+
passEncoder.setPipeline(pipeline);
494+
passEncoder.setBindGroup(0, bindGroup);
495+
passEncoder.draw(6);
496+
passEncoder.end();
497+
t.device.queue.submit([commandEncoder.finish()]);
498+
499+
const expected = {
500+
topLeft: new Uint8Array([255, 0, 0, 255]),
501+
topRight: new Uint8Array([0, 255, 0, 255]),
502+
bottomLeft: new Uint8Array([0, 0, 255, 255]),
503+
bottomRight: new Uint8Array([255, 0, 255, 255]),
504+
};
505+
506+
ttu.expectSinglePixelComparisonsAreOkInTexture(t, { texture: colorAttachment }, [
507+
// Top-left.
508+
{
509+
coord: { x: kWidth * 0.25, y: kHeight * 0.25 },
510+
exp: expected.topLeft,
511+
},
512+
// Top-right.
513+
{
514+
coord: { x: kWidth * 0.75, y: kHeight * 0.25 },
515+
exp: expected.topRight,
516+
},
517+
// Bottom-left.
518+
{
519+
coord: { x: kWidth * 0.25, y: kHeight * 0.75 },
520+
exp: expected.bottomLeft,
521+
},
522+
// Bottom-right.
523+
{
524+
coord: { x: kWidth * 0.75, y: kHeight * 0.75 },
525+
exp: expected.bottomRight,
526+
},
527+
]);
528+
529+
frame.close();
530+
});
531+
532+
g.test('importExternalTexture,video_frame_display_size_from_textureDimensions')
533+
.desc(
534+
`
535+
Tests that textureDimensions() for texture_external matches VideoFrame display size.
536+
`
537+
)
538+
.params(u =>
539+
u //
540+
.combine('displayScale', ['smaller', 'same', 'larger'] as const)
541+
)
542+
.fn(t => {
543+
if (typeof VideoFrame === 'undefined') {
544+
t.skip('WebCodec is not supported');
545+
}
546+
547+
const { frame, displayWidth, displayHeight } = createVideoFrameWithDisplayScale(
548+
t,
549+
t.params.displayScale
550+
);
551+
552+
const externalTexture = t.device.importExternalTexture({
553+
source: frame,
554+
colorSpace: 'srgb',
555+
});
556+
557+
const storageBuffer = t.createBufferTracked({
558+
size: 8,
559+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
560+
});
561+
562+
const pipeline = t.device.createComputePipeline({
563+
layout: 'auto',
564+
compute: {
565+
module: t.device.createShaderModule({
566+
code: `
567+
@group(0) @binding(0) var t : texture_external;
568+
@group(0) @binding(1) var<storage, read_write> outDims : array<u32>;
569+
570+
@compute @workgroup_size(1) fn main() {
571+
let d = textureDimensions(t);
572+
outDims[0] = d.x;
573+
outDims[1] = d.y;
574+
}
575+
`,
576+
}),
577+
entryPoint: 'main',
578+
},
579+
});
580+
581+
const bindGroup = t.device.createBindGroup({
582+
layout: pipeline.getBindGroupLayout(0),
583+
entries: [
584+
{ binding: 0, resource: externalTexture },
585+
{ binding: 1, resource: { buffer: storageBuffer } },
586+
],
587+
});
588+
589+
const encoder = t.device.createCommandEncoder();
590+
const pass = encoder.beginComputePass();
591+
pass.setPipeline(pipeline);
592+
pass.setBindGroup(0, bindGroup);
593+
pass.dispatchWorkgroups(1);
594+
pass.end();
595+
t.device.queue.submit([encoder.finish()]);
596+
597+
t.expectGPUBufferValuesEqual(storageBuffer, new Uint32Array([displayWidth, displayHeight]));
598+
599+
frame.close();
600+
});
601+
384602
g.test('importExternalTexture,sampleWithVideoFrameWithVisibleRectParam')
385603
.desc(
386604
`

0 commit comments

Comments
 (0)