| 479 | * Includes queue wait + execution + result readback. Only one probe in-flight to avoid promise spam. |
| 480 | */ |
| 481 | const scheduleGpuTimingProbe = (submitTs: number): void => { |
| 482 | const device = gpuContext?.device; |
| 483 | if (!device) return; |
| 484 | if (gpuTimingInFlight) return; |
| 485 | |
| 486 | gpuTimingInFlight = true; |
| 487 | gpuTimingSubmitTs = submitTs; |
| 488 | device.queue |
| 489 | .onSubmittedWorkDone() |
| 490 | .then(() => { |
| 491 | gpuTimingInFlight = false; |
| 492 | // If GPUContext/device was torn down or replaced, ignore this sample. |
| 493 | if (gpuContext?.device !== device) return; |
| 494 | const doneTs = performance.now(); |
| 495 | gpuMs.push(doneTs - gpuTimingSubmitTs); |
| 496 | }) |
| 497 | .catch(() => { |
| 498 | // Ignore errors (device lost/destroyed). |
| 499 | gpuTimingInFlight = false; |
| 500 | }); |
| 501 | }; |
| 502 | |
| 503 | /** |
| 504 | * Schedules a single frame render (coalescing multiple requests). |