Skip to content

Commit 90d613a

Browse files
committed
Strengthen frame readiness while testing fromPixels with video element
This fixes the error in WebGPU-backed unit tests: OperationError: Failed to execute 'importExternalTexture' on 'GPUDevice': Failed to import texture from video element that doesn't have back resource.
1 parent 89f59aa commit 90d613a

File tree

3 files changed

+37
-25
lines changed

3 files changed

+37
-25
lines changed

tfjs-backend-webgpu/src/from_pixels_webgpu_test.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import * as tf from '@tensorflow/tfjs-core';
19+
import {test_util} from '@tensorflow/tfjs-core';
1920
import {WebGPUBackend} from './backend_webgpu';
2021
import {describeWebGPU} from './test_util';
2122

@@ -37,26 +38,15 @@ describeWebGPU('fromPixels', () => {
3738
const textureManager = backend.textureManager;
3839
textureManager.dispose();
3940

40-
const video = document.createElement('video');
4141
const source = document.createElement('source');
4242
source.src =
4343
// tslint:disable-next-line:max-line-length
4444
'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAu1tZGF0AAACrQYF//+p3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE1NSByMjkwMSA3ZDBmZjIyIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxOCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTMgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTEgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD00MCByYz1jcmYgbWJ0cmVlPTEgY3JmPTI4LjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IGlwX3JhdGlvPTEuNDAgYXE9MToxLjAwAIAAAAAwZYiEAD//8m+P5OXfBeLGOfKE3xkODvFZuBflHv/+VwJIta6cbpIo4ABLoKBaYTkTAAAC7m1vb3YAAABsbXZoZAAAAAAAAAAAAAAAAAAAA+gAAAPoAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAIYdHJhawAAAFx0a2hkAAAAAwAAAAAAAAAAAAAAAQAAAAAAAAPoAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAACgAAAAWgAAAAAAJGVkdHMAAAAcZWxzdAAAAAAAAAABAAAD6AAAAAAAAQAAAAABkG1kaWEAAAAgbWRoZAAAAAAAAAAAAAAAAAAAQAAAAEAAVcQAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAAATttaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAD7c3RibAAAAJdzdHNkAAAAAAAAAAEAAACHYXZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAACgAFoASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAADFhdmNDAWQACv/hABhnZAAKrNlCjfkhAAADAAEAAAMAAg8SJZYBAAZo6+JLIsAAAAAYc3R0cwAAAAAAAAABAAAAAQAAQAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAAC5QAAAAEAAAAUc3RjbwAAAAAAAAABAAAAMAAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWlsc3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNTguMTIuMTAw';
4545
source.type = 'video/mp4';
46-
video.appendChild(source);
47-
document.body.appendChild(video);
48-
49-
video.autoplay = true;
50-
video.loop = true;
51-
video.muted = true;
52-
video.preload = 'auto';
53-
await video.play();
5446

55-
// ensure video element to be loaded
56-
if ('requestVideoFrameCallback' in video) {
57-
// tslint:disable-next-line:no-any
58-
await new Promise(go => (video as any).requestVideoFrameCallback(go));
59-
}
47+
const video = await test_util.createVideoElement(source);
48+
document.body.appendChild(video);
49+
await test_util.play(video);
6050

6151
{
6252
tf.env().set('WEBGPU_IMPORT_EXTERNAL_TEXTURE', true);

tfjs-core/src/ops/from_pixels_test.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import * as tf from '../index';
1919
import {BROWSER_ENVS, describeWithFlags, NODE_ENVS} from '../jasmine_util';
20-
import {expectArraysClose, expectArraysEqual} from '../test_util';
20+
import {createVideoElement, expectArraysClose, expectArraysEqual, play} from '../test_util';
2121

2222
class MockContext {
2323
getImageData(x: number, y: number, width: number, height: number) {
@@ -209,22 +209,15 @@ describeWithFlags('fromPixels', BROWSER_ENVS, () => {
209209
expect(data.length).toEqual(10 * 10 * 3);
210210
});
211211
it('fromPixels for HTMLVideoElement', async () => {
212-
const video = document.createElement('video');
213-
video.autoplay = true;
214212
const source = document.createElement('source');
215213
source.src =
216214
// tslint:disable-next-line:max-line-length
217215
'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAu1tZGF0AAACrQYF//+p3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE1NSByMjkwMSA3ZDBmZjIyIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxOCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTMgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTEgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD00MCByYz1jcmYgbWJ0cmVlPTEgY3JmPTI4LjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IGlwX3JhdGlvPTEuNDAgYXE9MToxLjAwAIAAAAAwZYiEAD//8m+P5OXfBeLGOfKE3xkODvFZuBflHv/+VwJIta6cbpIo4ABLoKBaYTkTAAAC7m1vb3YAAABsbXZoZAAAAAAAAAAAAAAAAAAAA+gAAAPoAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAIYdHJhawAAAFx0a2hkAAAAAwAAAAAAAAAAAAAAAQAAAAAAAAPoAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAACgAAAAWgAAAAAAJGVkdHMAAAAcZWxzdAAAAAAAAAABAAAD6AAAAAAAAQAAAAABkG1kaWEAAAAgbWRoZAAAAAAAAAAAAAAAAAAAQAAAAEAAVcQAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAAATttaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAD7c3RibAAAAJdzdHNkAAAAAAAAAAEAAACHYXZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAACgAFoASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAADFhdmNDAWQACv/hABhnZAAKrNlCjfkhAAADAAEAAAMAAg8SJZYBAAZo6+JLIsAAAAAYc3R0cwAAAAAAAAABAAAAAQAAQAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAAC5QAAAAEAAAAUc3RjbwAAAAAAAAABAAAAMAAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWlsc3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNTguMTIuMTAw';
218216
source.type = 'video/mp4';
219-
video.appendChild(source);
220-
document.body.appendChild(video);
221217

222-
// On mobile safari the ready state is ready immediately.
223-
if (video.readyState < 2) {
224-
await new Promise(resolve => {
225-
video.addEventListener('loadeddata', () => resolve(video));
226-
});
227-
}
218+
const video = await createVideoElement(source);
219+
document.body.appendChild(video);
220+
await play(video);
228221

229222
const res = tf.browser.fromPixels(video);
230223
expect(res.shape).toEqual([90, 160, 3]);

tfjs-core/src/test_util.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,32 @@ export function encodeStrings(a: RecursiveArray<{}>):
190190
}
191191
return a as RecursiveArray<Uint8Array>;
192192
}
193+
194+
/** Creates an HTMLVideoElement with autoplay-friendly default settings. */
195+
export function createVideoElement(source: HTMLSourceElement):
196+
Promise<HTMLVideoElement> {
197+
const video = document.createElement('video');
198+
video.style.position = 'fixed';
199+
video.style.left = '0px';
200+
video.style.top = '0px';
201+
video.muted = true;
202+
// tslint:disable-next-line:no-any
203+
(video as any).playsInline = true;
204+
video.loop = true;
205+
video.preload = 'auto';
206+
video.appendChild(source);
207+
return new Promise(resolve => {
208+
video.addEventListener('loadeddata', _ => resolve(video));
209+
video.load();
210+
});
211+
}
212+
213+
export async function play(video: HTMLVideoElement) {
214+
await video.play();
215+
if ('requestVideoFrameCallback' in video) {
216+
await new Promise(resolve => {
217+
// tslint:disable-next-line:no-any
218+
(video as any).requestVideoFrameCallback(resolve);
219+
});
220+
}
221+
}

0 commit comments

Comments
 (0)