| <!DOCTYPE html> |
| <html> |
| <head><title>Y16 texSubImage2D UNPACK_ROW_LENGTH OOB Test</title></head> |
| <body> |
| <video id="v" autoplay playsinline muted></video> |
| <canvas id="c" width="8" height="8"></canvas> |
| <pre id="log"></pre> |
| <script> |
| const log = m => { document.getElementById('log').textContent += m + '\n'; console.log(m); }; |
| |
| async function run() { |
| // 1. Open the Y16 depth-style camera. With Chrome's fake-device factory and |
| // device-count=2, device index 1 (label "fake_device_1") emits |
| // PIXEL_FORMAT_Y16 frames -- the same pipeline a RealSense camera uses. |
| await navigator.mediaDevices.getUserMedia({video: true}); // prime labels |
| const devs = await navigator.mediaDevices.enumerateDevices(); |
| for (const d of devs) log(`dev: kind=${d.kind} label="${d.label}" id=${d.deviceId}`); |
| const y16 = devs.find(d => d.kind === 'videoinput' && /fake_device_1/.test(d.label)); |
| if (!y16) { |
| log('FAIL: no Y16 fake device found (need device-count>=2)'); |
| if (window.domAutomationController) { |
| window.domAutomationController.send('failed: no Y16 fake device found'); |
| } |
| return; |
| } |
| |
| const stream = await navigator.mediaDevices.getUserMedia({ |
| video: { deviceId: { exact: y16.deviceId }, width: 96, height: 96 } |
| }); |
| const v = document.getElementById('v'); |
| v.srcObject = stream; |
| await new Promise(r => v.onloadeddata = r); |
| await v.play().catch(()=>{}); |
| // wait for an actual frame to land in the compositor |
| await new Promise(r => { |
| if ('requestVideoFrameCallback' in v) v.requestVideoFrameCallback(()=>r()); |
| else setTimeout(r, 500); |
| }); |
| const W = v.videoWidth, H = v.videoHeight; |
| log(`video ${W}x${H}`); |
| |
| // 2. WebGL2 context + destination texture sized to the video. |
| const gl = document.getElementById('c').getContext('webgl2'); |
| if (!gl) { |
| log('FAIL: no WebGL2'); |
| if (window.domAutomationController) { |
| window.domAutomationController.send('failed: no WebGL2'); |
| } |
| return; |
| } |
| const tex = gl.createTexture(); |
| gl.bindTexture(gl.TEXTURE_2D, tex); |
| // Allocate destination storage (R32F so RED/FLOAT sub-upload is legal). |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.R32F, W, H, 0, gl.RED, gl.FLOAT, null); |
| log('texStorage err=0x' + gl.getError().toString(16)); |
| |
| // 3. Set UNPACK_ROW_LENGTH > frame width. Leave UNPACK_FLIP_Y_WEBGL and |
| // UNPACK_PREMULTIPLY_ALPHA_WEBGL at their defaults (false) so that |
| // ScopedUnpackParametersResetRestore in the Y16 fast path is a no-op |
| // and ROW_LENGTH stays active inside the GLES2 client. |
| const ROW_LENGTH = W + 256; |
| gl.pixelStorei(gl.UNPACK_ROW_LENGTH, ROW_LENGTH); |
| log(`UNPACK_ROW_LENGTH=${ROW_LENGTH}`); |
| |
| // 4. Upload the Y16 video frame for 5 seconds. |
| const startTime = performance.now(); |
| let frameCount = 0; |
| while (performance.now() - startTime < 5000) { |
| gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RED, gl.FLOAT, v); |
| const err = gl.getError(); |
| if (err !== gl.NO_ERROR) { |
| log(`texSubImage2D err=0x` + err.toString(16)); |
| } |
| frameCount++; |
| await new Promise(r => requestAnimationFrame(r)); |
| } |
| |
| log(`Done ${frameCount} uploads. Reporting completion.`); |
| if (window.domAutomationController) { |
| window.domAutomationController.send('SUCCESS'); |
| } else { |
| log('FAIL: domAutomationController not found'); |
| } |
| } |
| run().catch(e => { |
| log('EXC: ' + e); |
| if (window.domAutomationController) { |
| window.domAutomationController.send('exception: ' + e); |
| } |
| }); |
| </script> |
| </body> |
| </html> |