react-native-skia-webgpu-video 0.0.0-stage → 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +303 -2
  3. package/android/BUILD_VALIDATION.md +62 -0
  4. package/android/CMakeLists.txt +99 -0
  5. package/android/build-validation.json +262 -0
  6. package/android/build.gradle +185 -0
  7. package/android/cpp/EncoderTargets.cpp +226 -0
  8. package/android/cpp/EncoderTargets.h +70 -0
  9. package/android/cpp/JNIHelpers.cpp +15 -0
  10. package/android/cpp/JNIHelpers.h +20 -0
  11. package/android/cpp/NativeEventDispatcher.cpp +59 -0
  12. package/android/cpp/NativeEventDispatcher.h +31 -0
  13. package/android/cpp/NativeHardwareBuffer.cpp +397 -0
  14. package/android/cpp/NativeHardwareBuffer.h +34 -0
  15. package/android/cpp/NativeRgbaBuffer.cpp +45 -0
  16. package/android/cpp/NativeRgbaBuffer.h +9 -0
  17. package/android/cpp/ReturnedVideoFrames.h +37 -0
  18. package/android/cpp/RgbaFrameLease.h +53 -0
  19. package/android/cpp/VideoCapabilities.cpp +87 -0
  20. package/android/cpp/VideoCapabilities.h +44 -0
  21. package/android/cpp/VideoComposition.cpp +170 -0
  22. package/android/cpp/VideoComposition.h +36 -0
  23. package/android/cpp/VideoCompositionFramesExtractor.cpp +82 -0
  24. package/android/cpp/VideoCompositionFramesExtractor.h +48 -0
  25. package/android/cpp/VideoCompositionFramesExtractorHostObject.cpp +230 -0
  26. package/android/cpp/VideoCompositionFramesExtractorHostObject.h +42 -0
  27. package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.cpp +133 -0
  28. package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.h +46 -0
  29. package/android/cpp/VideoEncoderHostObject.cpp +318 -0
  30. package/android/cpp/VideoEncoderHostObject.h +76 -0
  31. package/android/cpp/VideoFrame.cpp +293 -0
  32. package/android/cpp/VideoFrame.h +27 -0
  33. package/android/cpp/VideoPlayer.cpp +96 -0
  34. package/android/cpp/VideoPlayer.h +52 -0
  35. package/android/cpp/VideoPlayerHostObject.cpp +225 -0
  36. package/android/cpp/VideoPlayerHostObject.h +43 -0
  37. package/android/cpp/cpp-adapter.cpp +316 -0
  38. package/android/gradle.properties +5 -0
  39. package/android/instrumentation-inventory.json +178 -0
  40. package/android/proguard-rules.pro +4 -0
  41. package/android/src/main/AndroidManifest.xml +2 -0
  42. package/android/src/main/java/com/azzapp/rnskv/AudioCompositionExporter.java +537 -0
  43. package/android/src/main/java/com/azzapp/rnskv/AudioCompositionPlayer.java +321 -0
  44. package/android/src/main/java/com/azzapp/rnskv/BufferLifetime.java +55 -0
  45. package/android/src/main/java/com/azzapp/rnskv/DecodeRequests.java +100 -0
  46. package/android/src/main/java/com/azzapp/rnskv/DecoderWindow.java +55 -0
  47. package/android/src/main/java/com/azzapp/rnskv/DisplayVsyncPeriod.java +72 -0
  48. package/android/src/main/java/com/azzapp/rnskv/EGLResourcesHolder.java +132 -0
  49. package/android/src/main/java/com/azzapp/rnskv/EGLUtils.java +225 -0
  50. package/android/src/main/java/com/azzapp/rnskv/FrameSize.java +50 -0
  51. package/android/src/main/java/com/azzapp/rnskv/GLFrameExtractor.java +510 -0
  52. package/android/src/main/java/com/azzapp/rnskv/JNIHelpers.java +12 -0
  53. package/android/src/main/java/com/azzapp/rnskv/NativeEventDispatcher.java +25 -0
  54. package/android/src/main/java/com/azzapp/rnskv/NativeHardwareBuffer.java +62 -0
  55. package/android/src/main/java/com/azzapp/rnskv/NativeRgbaBuffer.java +43 -0
  56. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoModule.java +45 -0
  57. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoPackage.java +43 -0
  58. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoSpec.java +9 -0
  59. package/android/src/main/java/com/azzapp/rnskv/RgbaBufferAllocator.java +114 -0
  60. package/android/src/main/java/com/azzapp/rnskv/RgbaLayout.java +33 -0
  61. package/android/src/main/java/com/azzapp/rnskv/SurfaceOutputPolicy.java +13 -0
  62. package/android/src/main/java/com/azzapp/rnskv/TextureRenderer.java +150 -0
  63. package/android/src/main/java/com/azzapp/rnskv/TimeHelpers.java +11 -0
  64. package/android/src/main/java/com/azzapp/rnskv/VideoCapabilities.java +354 -0
  65. package/android/src/main/java/com/azzapp/rnskv/VideoComposition.java +147 -0
  66. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +779 -0
  67. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractor.java +463 -0
  68. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractorSync.java +241 -0
  69. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionItemDecoder.java +492 -0
  70. package/android/src/main/java/com/azzapp/rnskv/VideoEncoder.java +639 -0
  71. package/android/src/main/java/com/azzapp/rnskv/VideoFrame.java +74 -0
  72. package/android/src/main/java/com/azzapp/rnskv/VideoPlayer.java +361 -0
  73. package/cpp/RNSVCheckedSizes.h +54 -0
  74. package/cpp/RNSVEventEmitter.cpp +199 -0
  75. package/cpp/RNSVEventEmitter.h +31 -0
  76. package/cpp/RNSVHostObject.cpp +79 -0
  77. package/cpp/RNSVHostObject.h +67 -0
  78. package/cpp/RNSVMemoryBudget.cpp +68 -0
  79. package/cpp/RNSVMemoryBudget.h +100 -0
  80. package/cpp/RNSVRuntimeLifecycleMonitor.cpp +88 -0
  81. package/cpp/RNSVRuntimeLifecycleMonitor.h +33 -0
  82. package/docs/ANDROID_DEVICE_306.md +95 -0
  83. package/docs/ANDROID_EXPORT_CACHE_306.md +48 -0
  84. package/docs/ANDROID_MEMORY_RETENTION_306.md +67 -0
  85. package/docs/BENCHMARKS.md +368 -0
  86. package/docs/IOS_DEVICE_TESTS.md +115 -0
  87. package/docs/IOS_SIMULATOR_TESTS.md +74 -0
  88. package/docs/MIGRATION.md +82 -0
  89. package/docs/NATIVE_MEMORY_INVESTIGATION_306.md +88 -0
  90. package/docs/PERFORMANCE_AB_IPHONE_303.md +196 -0
  91. package/docs/PERFORMANCE_AB_SIMULATOR.md +135 -0
  92. package/docs/PERFORMANCE_AB_SOAK_SIMULATOR.md +84 -0
  93. package/docs/PERFORMANCE_IPHONE_306_OPTIMIZATION.md +76 -0
  94. package/docs/PERFORMANCE_SIMULATOR.md +89 -0
  95. package/docs/PERFORMANCE_SIMULATOR_INITIAL.md +96 -0
  96. package/docs/PERFORMANCE_SOAK_IPHONE_303.md +41 -0
  97. package/docs/UPSTREAM_NOTES.md +188 -0
  98. package/docs/assets/banner.svg +26 -0
  99. package/examples/webgpu-overlay.ts +147 -0
  100. package/ios/AVAssetTrackUtils.h +24 -0
  101. package/ios/AudioCompositionUtils.h +27 -0
  102. package/ios/AudioCompositionUtils.mm +113 -0
  103. package/ios/DecoderWindow.h +52 -0
  104. package/ios/NativeResourceStats.h +32 -0
  105. package/ios/RNSVColorSpace.h +50 -0
  106. package/ios/RNSVJSIUtils.h +46 -0
  107. package/ios/RNSVScopedPixelBuffer.h +17 -0
  108. package/ios/RNSVVideoPlayer.h +46 -0
  109. package/ios/RNSVVideoPlayer.mm +410 -0
  110. package/ios/ReactNativeSkiaVideo.h +8 -0
  111. package/ios/ReactNativeSkiaVideo.mm +310 -0
  112. package/ios/SeekPolicy.h +27 -0
  113. package/ios/VideoComposition.h +148 -0
  114. package/ios/VideoCompositionFramesExtractorHostObject.h +105 -0
  115. package/ios/VideoCompositionFramesExtractorHostObject.mm +686 -0
  116. package/ios/VideoCompositionFramesExtractorSyncHostObject.h +37 -0
  117. package/ios/VideoCompositionFramesExtractorSyncHostObject.mm +168 -0
  118. package/ios/VideoCompositionItemDecoder.h +60 -0
  119. package/ios/VideoCompositionItemDecoder.mm +480 -0
  120. package/ios/VideoEncoderHostObject.h +83 -0
  121. package/ios/VideoEncoderHostObject.mm +706 -0
  122. package/ios/VideoFrame.h +66 -0
  123. package/ios/VideoFrame.mm +108 -0
  124. package/ios/VideoPlayerHostObject.h +52 -0
  125. package/ios/VideoPlayerHostObject.mm +332 -0
  126. package/lib/module/NativeReactNativeSkiaVideo.js +5 -0
  127. package/lib/module/NativeReactNativeSkiaVideo.js.map +1 -0
  128. package/lib/module/RNSkiaVideoModule.js +20 -0
  129. package/lib/module/RNSkiaVideoModule.js.map +1 -0
  130. package/lib/module/canvas.js +18 -0
  131. package/lib/module/canvas.js.map +1 -0
  132. package/lib/module/exportCache.js +22 -0
  133. package/lib/module/exportCache.js.map +1 -0
  134. package/lib/module/exportFrames.js +338 -0
  135. package/lib/module/exportFrames.js.map +1 -0
  136. package/lib/module/exportQueue.js +52 -0
  137. package/lib/module/exportQueue.js.map +1 -0
  138. package/lib/module/exportTarget.js +137 -0
  139. package/lib/module/exportTarget.js.map +1 -0
  140. package/lib/module/exportVideoComposition.js +492 -0
  141. package/lib/module/exportVideoComposition.js.map +1 -0
  142. package/lib/module/frameInterop.js +532 -0
  143. package/lib/module/frameInterop.js.map +1 -0
  144. package/lib/module/gpu.js +222 -0
  145. package/lib/module/gpu.js.map +1 -0
  146. package/lib/module/gpuDevice.js +54 -0
  147. package/lib/module/gpuDevice.js.map +1 -0
  148. package/lib/module/index.js +36 -0
  149. package/lib/module/index.js.map +1 -0
  150. package/lib/module/memory.js +38 -0
  151. package/lib/module/memory.js.map +1 -0
  152. package/lib/module/package.json +1 -0
  153. package/lib/module/previewLifecycle.js +52 -0
  154. package/lib/module/previewLifecycle.js.map +1 -0
  155. package/lib/module/runtimeYield.js +10 -0
  156. package/lib/module/runtimeYield.js.map +1 -0
  157. package/lib/module/types.js +4 -0
  158. package/lib/module/types.js.map +1 -0
  159. package/lib/module/utils/useEventListener.js +18 -0
  160. package/lib/module/utils/useEventListener.js.map +1 -0
  161. package/lib/module/videoCompositionPlayer.js +368 -0
  162. package/lib/module/videoCompositionPlayer.js.map +1 -0
  163. package/lib/module/videoFrameImage.js +185 -0
  164. package/lib/module/videoFrameImage.js.map +1 -0
  165. package/lib/module/videoPlayer.js +172 -0
  166. package/lib/module/videoPlayer.js.map +1 -0
  167. package/lib/module/yuvBlit.js +299 -0
  168. package/lib/module/yuvBlit.js.map +1 -0
  169. package/lib/typescript/package.json +1 -0
  170. package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts +7 -0
  171. package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts.map +1 -0
  172. package/lib/typescript/src/RNSkiaVideoModule.d.ts +4 -0
  173. package/lib/typescript/src/RNSkiaVideoModule.d.ts.map +1 -0
  174. package/lib/typescript/src/canvas.d.ts +4 -0
  175. package/lib/typescript/src/canvas.d.ts.map +1 -0
  176. package/lib/typescript/src/exportCache.d.ts +6 -0
  177. package/lib/typescript/src/exportCache.d.ts.map +1 -0
  178. package/lib/typescript/src/exportFrames.d.ts +57 -0
  179. package/lib/typescript/src/exportFrames.d.ts.map +1 -0
  180. package/lib/typescript/src/exportQueue.d.ts +5 -0
  181. package/lib/typescript/src/exportQueue.d.ts.map +1 -0
  182. package/lib/typescript/src/exportTarget.d.ts +28 -0
  183. package/lib/typescript/src/exportTarget.d.ts.map +1 -0
  184. package/lib/typescript/src/exportVideoComposition.d.ts +68 -0
  185. package/lib/typescript/src/exportVideoComposition.d.ts.map +1 -0
  186. package/lib/typescript/src/frameInterop.d.ts +17 -0
  187. package/lib/typescript/src/frameInterop.d.ts.map +1 -0
  188. package/lib/typescript/src/gpu.d.ts +39 -0
  189. package/lib/typescript/src/gpu.d.ts.map +1 -0
  190. package/lib/typescript/src/gpuDevice.d.ts +11 -0
  191. package/lib/typescript/src/gpuDevice.d.ts.map +1 -0
  192. package/lib/typescript/src/index.d.ts +21 -0
  193. package/lib/typescript/src/index.d.ts.map +1 -0
  194. package/lib/typescript/src/memory.d.ts +25 -0
  195. package/lib/typescript/src/memory.d.ts.map +1 -0
  196. package/lib/typescript/src/previewLifecycle.d.ts +22 -0
  197. package/lib/typescript/src/previewLifecycle.d.ts.map +1 -0
  198. package/lib/typescript/src/runtimeYield.d.ts +4 -0
  199. package/lib/typescript/src/runtimeYield.d.ts.map +1 -0
  200. package/lib/typescript/src/types.d.ts +631 -0
  201. package/lib/typescript/src/types.d.ts.map +1 -0
  202. package/lib/typescript/src/utils/useEventListener.d.ts +5 -0
  203. package/lib/typescript/src/utils/useEventListener.d.ts.map +1 -0
  204. package/lib/typescript/src/videoCompositionPlayer.d.ts +105 -0
  205. package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -0
  206. package/lib/typescript/src/videoFrameImage.d.ts +60 -0
  207. package/lib/typescript/src/videoFrameImage.d.ts.map +1 -0
  208. package/lib/typescript/src/videoPlayer.d.ts +99 -0
  209. package/lib/typescript/src/videoPlayer.d.ts.map +1 -0
  210. package/lib/typescript/src/yuvBlit.d.ts +50 -0
  211. package/lib/typescript/src/yuvBlit.d.ts.map +1 -0
  212. package/package.json +155 -4
  213. package/patches/react-native-skia+3.7.0.patch +502 -0
  214. package/react-native-skia-webgpu-video.podspec +35 -0
  215. package/scripts/apply-skia-canvas-patch.mjs +48 -0
  216. package/scripts/skia-canvas-patch.json +291 -0
  217. package/src/NativeReactNativeSkiaVideo.ts +8 -0
  218. package/src/RNSkiaVideoModule.ts +27 -0
  219. package/src/canvas.ts +22 -0
  220. package/src/exportCache.ts +27 -0
  221. package/src/exportFrames.ts +421 -0
  222. package/src/exportQueue.ts +65 -0
  223. package/src/exportTarget.ts +179 -0
  224. package/src/exportVideoComposition.ts +718 -0
  225. package/src/frameInterop.ts +684 -0
  226. package/src/gpu.ts +290 -0
  227. package/src/gpuDevice.ts +80 -0
  228. package/src/index.tsx +45 -0
  229. package/src/memory.ts +48 -0
  230. package/src/previewLifecycle.ts +60 -0
  231. package/src/runtimeYield.ts +6 -0
  232. package/src/types.ts +661 -0
  233. package/src/utils/useEventListener.ts +22 -0
  234. package/src/videoCompositionPlayer.ts +568 -0
  235. package/src/videoFrameImage.ts +205 -0
  236. package/src/videoPlayer.ts +318 -0
  237. package/src/yuvBlit.ts +331 -0
@@ -0,0 +1,67 @@
1
+ # Android memory retention — Skia 3.0.6
2
+
3
+ Historical diagnostic checkpoint. Production follow-ups are documented in [export cache/provider ownership](ANDROID_EXPORT_CACHE_306.md) and the [October 8 export-worklet memory measurements](NATIVE_MEMORY_INVESTIGATION_306.md). The experimental broad purge below is not the production cleanup policy.
4
+
5
+ The eight-clip 4K montage completes, but substantial process memory remains after its resources close. A controlled diagnostic build shows that unused Graphite resources account for a reclaimable part of this retention. A separate source review also identifies an unbalanced reference in Skia's image-provider factory. Neither finding explains all remaining memory.
6
+
7
+ ## Device experiment
8
+
9
+ The [sanitized evidence](../benchmark/android-cache-retention-306.json) records raw-file hashes, conditions, memory categories and cache counters. All runs use one identical arm64 Release APK, SHA-256 `5dda76b6609ccadbd0ac8b8abb597c6fe34664d6c0105c869de410a5ee1a61fd`, derived from commit `249e03b6fa8f0820dec032206952000b809f58d8`. This is an instrumented diagnostic binary, distinct from the qualified production checkpoint.
10
+
11
+ The physical Pixel 8a runs Android 17, RN 0.86.2, Skia 3.0.6 and WebGPU 0.12.1. Each cold process executes `4k-x8-lazy-direct`: eight sequential full-resolution H264/HEVC clips, playback, seeks, scrub, loop, a 360-frame 1080-square export, 25 churn cycles and 40 remounts. The quota stays at 512 MiB and the preview at 720 × 720 physical pixels. No forced GC or system setting changes are used.
12
+
13
+ The diagnostic invokes the probe after successful preview cleanup and export cleanup, on the recorder's owning thread. It locks the shared context. It records both budgeted and purgeable bytes; the configured 256 MiB budgets are ceilings, not actual allocations.
14
+
15
+ | Mode | Intervention |
16
+ | --- | --- |
17
+ | 0 | Observe only. |
18
+ | 1 | Snap/insert the current recorder, then checked synchronous context submission. No purge. |
19
+ | 3 | Same drain, followed by `freeGpuResources()` on the current recorder and shared context. |
20
+
21
+ There is one execution per mode. These are memory diagnostics, not a statistically repeated speed comparison. The intervention runs at each successful cleanup, not just once at the final measurement. Mode 2 (current recorder only) is available in the probe but was not run; this experiment does not separate recorder purging from context purging.
22
+
23
+ All three workloads passed. All **3,539 native samples** reported nominal thermal state, normal power mode and active foreground lifecycle; no samples were dropped. Every recorded settled owned-byte/resource count was zero. The application was stopped after collection to release KEEP_SCREEN_ON.
24
+
25
+ Memory after 60 seconds at rest, in MiB (PSS):
26
+
27
+ | Mode | Total | Native heap | Java heap | EGL mtrack | GL mtrack | Unknown |
28
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
29
+ | 0 | 715.07 | 90.30 | 3.44 | 58.65 | 413.77 | 74.29 |
30
+ | 1 | 708.21 | 92.03 | 3.43 | 58.65 | 404.54 | 74.33 |
31
+ | 3 | 540.03 | 92.37 | 3.44 | 58.65 | 235.71 | 74.59 |
32
+
33
+ The purge run is **175.04 MiB (24.48%) below observation-only** and **168.18 MiB below drain-only** in total PSS. GL mtrack decreases by 178.06 MiB versus observation-only; the other listed categories remain similar. These single-run differences support reclaimable GPU-cache retention, not a guaranteed savings target. The drain-only control does not reproduce the reduction.
34
+
35
+ In mode 0, the last UI-recorder probe reports **112.74 MiB budgeted and purgeable**, and the export recorder reports **63.30 MiB** at export completion. The shared context reports **4.55 MiB budgeted**, nearly all purgeable. These observations occur at their respective cleanup points; they are not a simultaneous heap snapshot. In mode 3 both observed recorder caches return to zero after each probe, with 4 KiB remaining budgeted in the context at the last probe. Per-view recorders and other GPU owners are not individually instrumented.
36
+
37
+ Even after the purge, **540 MiB total PSS** remains, including **235.71 MiB GL mtrack**, **58.65 MiB EGL mtrack**, **92.37 MiB native-heap PSS** and **74.59 MiB Unknown**. Therefore cache trimming alone does not close the memory investigation. GL/EGL mtrack labels do not identify individual allocation owners; native-heap PSS is not live allocated heap size. The evidence keeps `dumpsys TOTAL RSS` separate from the harness's `/proc/self/status VmRSS`.
38
+
39
+ ## Separate reference-count defect
40
+
41
+ The installed Skia 3.0.6 `cpp/rnskia/RNImageProvider.h` factory returns `sk_ref_sp(new ImageProvider)`. In the same installed Skia headers, `SkRefCntBase` starts with one reference, and `sk_ref_sp` increments it. When the last smart pointer releases the provider, the construction reference remains. The appropriate ownership pattern for a newly allocated instance is `sk_make_sp<ImageProvider>()` (or an adopting `sk_sp`).
42
+
43
+ The [native reproducer](../benchmark/diagnostics/skia-provider-reference-count.cpp) uses the actual installed `SkRefCnt.h`, checks destruction after the final smart-pointer reset for both patterns, and passed with AddressSanitizer and UndefinedBehaviorSanitizer. It balances the orphaned reference before exiting. This proves the ownership error in the factory pattern; it does not measure the number or size of image providers retained in the app.
44
+
45
+ Each provider can retain up to 256 strong image references. Skia creates providers for thread-local recorders and for per-view recorders. This makes the defect relevant to repeated mounts, but attributing the measured per-cycle growth or the residual 540 MiB to this defect requires a separate corrected-build experiment. No such improvement is claimed here.
46
+
47
+ ## Implementation decision and remaining qualification
48
+
49
+ Do not install the diagnostic purge as an unconditional production cleanup. The context and UI recorder are shared with other Skia consumers; repeatedly draining them and discarding reusable resources can affect rendering latency, 3D and ML work. Trimming these shared caches should use application-level control, respect recorder thread affinity and use an idle/session boundary, with simultaneous-video and other-GPU-consumer tests. The private export runtime permits a narrower policy without trimming those shared caches.
50
+
51
+ The [production follow-up](ANDROID_EXPORT_CACHE_306.md) now fixes the image-provider factory reference and trims only the dedicated export recorder after session cleanup, through the version/hash-guarded Skia patch workflow. It tests the factory correction independently before combining the changes. The broad diagnostic purge above is still excluded from production. The investigation makes no old-library performance claim, iOS cache claim, HDR/audio qualification or endurance guarantee.
52
+
53
+ ## Reproducing the diagnostic
54
+
55
+ The [archived diagnostic patch](../benchmark/diagnostics/android-cache-probe-306.patch) contains the exact five-file instrumentation delta over the base commit and its already-applied production Skia patch. It is stored under `benchmark/`, excluded from the package's production patch installer, and was removed from active sources after building. Its context was checked with `git apply --check` against the restored sources. It is not a supported public API.
56
+
57
+ In an isolated checkout at that commit, install the locked dependencies and apply the normal guarded canvas patch first. Check/apply the diagnostic patch, build the Android arm64 Release example, sign and install the resulting APK. Select each mode through a distinct `RNSKV_BENCHMARK_RUN_ID` containing `cache-probe-mode0`, `cache-probe-mode1` or `cache-probe-mode3`. Select the full profile, one repetition, and only `4k-x8-lazy-direct`. Capture `VideoCacheProbe` logs for that process and `dumpsys meminfo -d com.skiawebgpuvideo.tests` during the run and at 0/10/30/60 seconds after completion. Keep the app in the foreground for the idle observation; stop it after collection. Never compare a newly built binary's identity with the frozen APK hash as if they were the same artifact.
58
+
59
+ Run the ownership reproducer from the repository root:
60
+
61
+ ```sh
62
+ clang++ -std=c++17 -DNDEBUG -fsanitize=address,undefined -fno-omit-frame-pointer \
63
+ -I node_modules/react-native-skia/cpp/skia \
64
+ benchmark/diagnostics/skia-provider-reference-count.cpp \
65
+ -o /tmp/skia-provider-reference-count
66
+ /tmp/skia-provider-reference-count
67
+ ```
@@ -0,0 +1,368 @@
1
+ # Comparer Skia Video et Skia 3 / WebGPU
2
+
3
+ Dernière campagne, **19 octobre 2026**, de **Skia 3.6.1 / WebGPU 0.16.0** vers **Skia 3.7.0 / WebGPU 0.17.0** (Dawn et Graphite 154.3.0 → 157.0.0). Trois répétitions par cas, médianes, en Release.
4
+ - **iPhone 15 Pro, hors chargeur :** neutre. HEVC 1080p 734 → 731 ms, 640×360 173–211 → 168–171 ms, 8×4K réduits 2,74–2,78 → 2,73–2,79 s. Une première série sur chargeur sans fil, en état thermique *fair*, a été écartée : elle ralentissait la 3.7.0 de 15 à 80 %.
5
+ - **Pixel 8a :** neutre. HEVC 1080p 3,02 → 2,92 s, 640×360 1,18 → 1,23 s, 8×4K réduits 12,2 → 12,5 s, 8×4K natifs 10,0 → 9,8 s et 3×4K simultanés 5,8 → 5,6 s (ces deux derniers face au run 3.6.1 du 16 octobre).
6
+ - **Contrôles de pixels sur Pixel :** décodage CPU/GPU 10/10 à ±1, exports de mire et de vidéo identiques au readback. Les fichiers issus d'`ImageReader` sont jusqu'à 5,7 % plus petits, cause non vérifiée.
7
+
8
+ Preuves : [`ios-device-370-ab.json`](../benchmark/ios-device-370-ab.json), [`android-device-370-ab.json`](../benchmark/android-device-370-ab.json) ; voir la [vérification](../migration/skia-3.7.0/verification.md).
9
+
10
+ Campagne précédente, **16 octobre 2026**, de **Skia 3.4.4 / WebGPU 0.14.2** vers **Skia 3.6.1 / WebGPU 0.16.0**. Trois répétitions par cas, médianes, en Release.
11
+ - **iPhone 15 Pro :** neutre, avec le même profil par image dans quatre séries alternées. HEVC 1080p 731–734 → 728–790 ms, 640×360 174–223 → 168–169 ms, 8×4K réduits 2,72–2,79 → 2,68–2,73 s.
12
+ - **Pixel 8a, avec l'import en place des frames `ImageReader` à l'export :** HEVC 1080p **3,80 → 3,00 s**, 640×360 **1,25 → 1,16 s**, 8×4K natifs **13,5 → 10,0 s**, 3×4K simultanés **8,8 → 5,8 s**. Les 8×4K réduits, décodés par EGL, restent à 12,0–12,5 s.
13
+ - **Contrôles de pixels sur Pixel :** décodage CPU/GPU 10/10 à ±1, export de mire et d'une vidéo décodée identiques au readback. Sans la correction de couleurs de cette migration, le décodage échoue sur 10/10 cas.
14
+
15
+ Preuves : [`ios-device-361-ab.json`](../benchmark/ios-device-361-ab.json), [`android-device-361-ab.json`](../benchmark/android-device-361-ab.json) ; voir la [vérification](../migration/skia-3.6.1/verification.md).
16
+
17
+ Campagne précédente, **14–15 octobre 2026**, sur **Skia 3.4.4 / WebGPU 0.14.2**. Elle mesure la vitesse d'export, pas la mémoire longue durée. Trois répétitions par cas, médianes, en Release.
18
+ - **iPhone 15 Pro, face à 3.0.6 :** HEVC 1080p **956 → 735–738 ms**, 640×360 **235 → 175–210 ms**, 8×4K réduits à 1280 **2,95 → 2,73–2,85 s**.
19
+ - **Pixel 8a, avec le décodage `ImageReader` :** HEVC 1080p **6,35 → 3,72 s**, 640×360 **1,75 → 1,29 s**, 8×4K natifs **23,8–25,1 → 13,5 s** (face au décodage EGL), 3×4K simultanés **20,6–21,2 → 8,7–9,0 s**.
20
+ - **Contrôles de pixels sur Pixel :** décodage CPU/GPU 10/10, export de mire réussi.
21
+
22
+ Preuves : [`ios-device-344-export-ab.json`](../benchmark/ios-device-344-export-ab.json), [`ios-simulator-344-export-ab.json`](../benchmark/ios-simulator-344-export-ab.json), [`android-device-344-export-ab.json`](../benchmark/android-device-344-export-ab.json) ; protocole, attribution et limites dans la [vérification](../migration/skia-3.4.4/verification.md). Le harness enregistre désormais `exportProfile`, le temps par étape d'export. Sur Android, `scripts/test-android-device-run.mjs` installe un APK signé, le démarre à froid avec les cas choisis et rapatrie le JSON.
23
+
24
+ Campagne précédente, **8 octobre 2026** : [mémoire de l’export Skia 3.0.6](NATIVE_MEMORY_INVESTIGATION_306.md), avec [preuves JSON](../benchmark/stable-export-worklet-306.json). Comparaison avant/après du correctif dans cette bibliothèque : deux montages puis 60 s au repos sur chaque appareil, suivis de six montages iPhone et quatre Android. Empreinte physique iPhone : **425,99 → 257,70 Mio** ; PSS Pixel : **737,83 → 577,20 Mio**. Les conditions sont nominal/normal/active. Ces chiffres ne comparent ni iOS à Android, ni cette bibliothèque à Skia Video 2 ; les campagnes historiques ci-dessous restent séparées.
25
+
26
+ Le dossier `benchmark/` contient un programme commun aux deux applications. Il reproduit les 31 compositions de l'ancien écran de stress, ajoute 28 cas de régression, trois lectures continues en copy et leurs trois variantes direct : **65 workloads déterministes**, version **`2026-10-07.1`**. Les sources vidéo, les seeks, les pauses, les tailles demandées et les séquences de montage sont identiques pour les deux versions.
27
+
28
+ Les essais physiques du **7 octobre sous Skia 3.0.3** ont passé **50/50 XCTest natifs**, sans échec ni skip, puis **5/5 cas du smoke candidat**. [ios-native-device-verification.json](../benchmark/ios-native-device-verification.json) conserve la preuve native. Ces smokes sont fonctionnels ; les premiers relevés d'un cas avant activation de l'application sont exclus du timing A/B. Le [comparatif physique court](PERFORMANCE_AB_IPHONE_303.md) a ensuite couvert huit scénarios en ABBA : **48/48 réussites candidat, 47/48 référence**, avec réserve thermique au premier passage de la référence et qualification stricte failed conservée. Cela ne qualifie pas tous les 65 workloads.
29
+
30
+ Le [diagnostic physique prolongé](PERFORMANCE_SOAK_IPHONE_303.md) a passé **3/3 blocs et sondé 300 exports**, avec croissance RSS/empreinte physique et ressources suivies finales à zéro. Le collecteur de repos a dépassé son délai sans fichier, tandis que la référence a subi un SIGTRAP natif sans JSON comparable. Aucun repos absent n'est remplacé par zéro ou un gain chiffré. Les tests 3.0.3 sont arrêtés à la demande utilisateur ; le manifeste courant **Skia 3.0.6 / WebGPU 0.12.1** a passé son premier run de **50 XCTest natifs sur iPhone**, sans échec ni skip, terminé à **08:23:15 UTC** : [preuve versionnée](../benchmark/ios-native-device-306-verification.json). Le run initial a aussi passé **154 Jest dans 8 suites** ; le transport TypeScript optimisé a ensuite passé **157 Jest**, les **4 tests du patch**, Bob et publint strict. La [preuve fonctionnelle du simulateur](../benchmark/ios-simulator-306-functional-verification.json) conserve **14/14 cas ciblés**, une répétition chacun, **27 exports / 1320 frames sondés** et compteurs suivis settled à zéro. Les pixels et l’immutabilité visuelle held restent non validés. Le smoke physique optimisé a ensuite passé **5/5 cas**. La [comparaison initial/optimisé 3.0.6](PERFORMANCE_IPHONE_306_OPTIMIZATION.md) conserve **96/96 exécutions, 24 fichiers / 1440 frames sondés**, avec **3260 relevés fair/normal/active** et 120 points settled à zéro pour les quatre compteurs suivis. L’empreinte physique au pic baisse d’environ 11 % en lecture 4K ; le RSS reste proche, les petits pipelines export sont plus lents et aucun gain global de vitesse n’est établi. Qualification stricte incomplete : nominal non satisfait et validateurs médias incomplets ; aucun palier physique qualifié.
31
+
32
+ Le premier smoke physique du **6 octobre** reste historique : **0 réussite, 1 échec, 4 non exécutés**, avec des réservations et décodeurs encore présents après fermeture. Il conserve de vrais relevés RSS, mais ne qualifie aucune performance ni absence de fuite. Sa correction et les mesures du [simulateur](IOS_SIMULATOR_TESTS.md) restent distinctes des nouveaux runs iPhone. Les tests exécutés sur l'hôte vérifient le programme de comparaison, pas la vitesse de Skia ou la mémoire des pilotes.
33
+
34
+ Les [diagnostics physiques Android 3.0.6](ANDROID_DEVICE_306.md) conservent les différents binaires testés sur Pixel 8a. Le binaire AHardwareBuffer a passé **24/24 essais**, trois répétitions des huit scénarios H.264 1080p, H.264/HEVC 4K et export H.264 en copy/direct, avec les réservations finales à zéro après fermeture stricte. Les **986 relevés** sont nominal/normal/active, sans échantillon perdu ; **6 exports / 360 frames** ont été sondés. L'oracle CPU/AHB séparé a passé **10/10 cas et 66 560 canaux SDR échantillonnés**, avec erreur maximale zéro ; les **6 tests natifs AHB** passent sans skip. Les checkpoints CPU restent conservés : **0/24 réussites strictes** initialement, puis **8/8 opérations sans OOM mais 0/8 fermetures strictes** avec scoped-copy. Cette dernière référence n'a qu'une tentative par cas ; aucun gain A/B contre Skia 2 ou qualification mémoire longue n'en est déduit.
35
+
36
+ Une série Android distincte a passé **4/4 cas de cycle de vie**, une répétition chacun : pause/reprise, retrait d'items de la frame map, pause/attente puis cinq seeks, et `4k-x8-lazy-direct`. Ce dernier a effectué ses huit opérations, **25 cycles de churn et 40 remontages**, puis sondé un export **1080×1080 H.264 de 360 frames sur 12 secondes**, avec PTS exacts et progression 360/360. Ses pics atteignent **800,25 Mio RSS / 1 037,91 Mio PSS** ; le RSS settled reste à **785,82 Mio**, alors que les réservations suivies sont à zéro. Les **1 267 relevés** de cette série sont nominal/normal/active, sans perte. Les seeks n'ont pas de validation pixels/PTS natifs ; le nom `held-image-survives-seek` ne prouve pas l'immutabilité d'une SkImage distincte. Les cinq tests JNI CPU ont repassé sur le même binaire. Cette portée ne remplace pas la suite complète ni une endurance longue.
37
+
38
+ ## Couverture de l'existant
39
+
40
+ L'inventaire complet est dans [`baseline-test-inventory.json`](../benchmark/baseline-test-inventory.json). Il pointe vers chaque fichier, méthode et ligne du dépôt source au commit `334348ffeee8a4f6887fd1f663f8cf584f579911`.
41
+
42
+ | Groupe | Cas nommés | Vérification attendue |
43
+ | --- | ---: | --- |
44
+ | Jest : API, hooks, export, crop/rotation et images | 40 | Mêmes assertions sur les sources ancienne et nouvelle |
45
+ | C++ : politique de seek et fenêtres des décodeurs | 20 | Exécution sur l'hôte, dont les fenêtres aléatoires |
46
+ | Android : tailles et fenêtres | 12 | Tests JVM existants |
47
+ | Android : lectures, compositions, export, codecs, erreurs et textures | 40 | Instrumentation physique ; les paramètres multiplient le nombre réel de cas |
48
+ | iOS : décodeurs, preview, export, tailles, slow motion et encodeurs | 50 | 50 réussis sans échec ni skip sur simulateur, puis sur iPhone physique le 7 octobre ; première tentative iPhone historique 45/50 avant correctifs |
49
+
50
+ Les cas Android paramétrés représentent **86 exécutions** avant les skips de codecs : `MediaTest` comporte neuf profils, `DirectTextureTest` cinq et `PlayerTest` deux modes. Les profils incluent les rotations 90/180/270°, HEVC, 4K, 60 fps et les dimensions 638×358.
51
+
52
+ L'inventaire distingue l'assertion native reproduite, le workload associé et la preuve d'exécution. Il associe les 162 cas nommés à un fichier et une assertion candidate. Les 50 méthodes XCTest iOS ont été portées avec leurs noms d'origine, compilées avec les vrais headers du SDK iPhoneSimulator puis liées dans la cible de test iPhone Release. Leur compilation ne vaut pas exécution sur cible.
53
+
54
+ Les assertions natives sur les pixels, les indices de frame, les threads et la fermeture des décodeurs restent dans les suites. Un workload commun ne les remplace pas. Trois assertions iOS sur l'ancien recyclage des textures ont été remplacées par des assertions explicites de lease : les pixels d'une frame détenue survivent au seek et à la fermeture du producteur, les deux leases de preview bornent les buffers en vol, puis la libération du consommateur permet le prochain buffer. L'inventaire indique ce changement de sécurité ; le test « retirer après quatre frames » conserve son nom pour retrouver sa provenance.
55
+
56
+ Les 65 workloads actuels couvrent :
57
+
58
+ - toutes les compositions de l'écran `StressTest`, en mode eager/lazy et copy/direct ;
59
+ - crossfades, huit clips 4K, trois clips simultanés et vingt clips courts ;
60
+ - audio mixé, volume, source audio absente et start après la fin du fichier ;
61
+ - seeks dans les deux directions, scrub fixe ou pendant la lecture, seek avant ready, seek après la fin et reprise ;
62
+ - pause avec horloge immobile, reprise, retrait des items fermés de la frame map et image détenue pendant un seek ;
63
+ - trois lectures continues sans seek, en H.264 1080p, H.264 4K et HEVC 4K, chacune en copy et direct, distinctes des scénarios historiques ;
64
+ - hold, pacing, 40 cycles de création/libération et 25 cycles courts de churn ;
65
+ - export H.264/HEVC, codec inconnu, copy/direct, annulation avant/pendant l'export ;
66
+ - vingt exports de tailles alternées et une composition de 120 clips ;
67
+ - LUT, compute, Three.js et Core ML, quand leurs adaptateurs de rendu sont installés.
68
+
69
+ Un fichier manquant ou un adaptateur absent donne un **skip explicite**. Le cas reste dans le catalogue. Il n'est jamais retiré pour rendre les résultats plus favorables.
70
+
71
+ ## Deux builds séparés
72
+
73
+ Construire deux applications identiques en mode release :
74
+
75
+ 1. L'application de référence contient `@azzapp/react-native-skia-video` et sa version Skia 2 effective.
76
+ 2. L'application candidate contient `react-native-skia-webgpu-video`, `react-native-skia` et `react-native-webgpu`.
77
+
78
+ Utiliser le même appareil, la même version de React Native, les mêmes assets et la même fréquence d'écran. Les deux noms de paquet Skia ne doivent pas être installés ensemble. Les importations du harness sont injectées depuis le build actif ; aucun module du harness ne charge les deux backends.
79
+
80
+ Le fallback Android testé conserve le readback RGBA EGL, puis une copie native synchrone vers un ArrayBuffer CPU privé réutilisable, `queue.writeTexture` et un snapshot Graphite indépendant. Il évite l'image raster, son cache amont et la matérialisation de `texture.data` lors du rendu normal. La voie AHardwareBuffer testée négocie ses capacités avant décodage, rend le codec dans un AHB RGBA, puis effectue le blit WebGPU et le snapshot indépendant ; elle évite le readback CPU des frames d'entrée. L'export garde dans les deux cas son readback CPU puis l'entrée native de l'encodeur. Aucun de ces chemins ne garantit une vidéo intégralement sans copie. Conserver le transport effectif de chaque binaire dans son JSON et distinguer les [réservations Java/JSI et caches Skia](UPSTREAM_NOTES.md#android--éviter-le-cache-dimages-raster-du-provider) : les relevés des anciens fallbacks ne qualifient pas le binaire AHB.
81
+
82
+ ## Protocole actuel sur iPhone
83
+
84
+ Le [guide physique](IOS_DEVICE_TESTS.md) donne les scripts de compilation séparés et [`test-ios-device-ab-run.mjs`](../scripts/test-ios-device-ab-run.mjs), qui utilise des produits signés `Release-iphoneos` déjà construits. Les deux builds mesurent les mêmes cas et les mêmes paramètres sur un processus neuf, avec trois répétitions ; les lectures au repos proviennent du collecteur natif de l'iPhone. Les JSON originaux, l'identité des binaires/fixtures et les interruptions de récupération restent conservés par label.
85
+
86
+ Le workload `2026-10-07.1` ajoute les variantes `steady-playback-h264-1080p30-audio-direct`, `steady-playback-h264-4k30-direct` et `steady-playback-hevc-4k30-direct`, avec les mêmes temps que leurs variantes copy. `encode-h264-direct` conserve le mode direct demandé à l'export. Ce choix d'API ne remplace pas le transport réellement annoncé par le backend : la référence et le candidat peuvent effectuer des copies différentes, et le candidat 3.0.3 conserve son chemin de référence avec readback CPU à l'export. Les [résultats physiques courts](PERFORMANCE_AB_IPHONE_303.md) et le [diagnostic de 300 exports](PERFORMANCE_SOAK_IPHONE_303.md) sont des expériences distinctes ; leur preuve ne s'étend pas à la migration 3.0.6.
87
+
88
+ ## Protocole A/B sur simulateur
89
+
90
+ Le build de référence utilise les sources du fork au commit `334348ffeee8a4f6887fd1f663f8cf584f579911`, sans modification de son algorithme. Le test historique de lecture/seek a reproduit un crash de cette version dans la concurrence entre lecture et annulation de `AVAssetReader`. Conserver ce crash comme résultat fonctionnel ; il ne fournit pas de durée de performance exploitable pour la suite interrompue.
91
+
92
+ Pour mesurer les parties exécutables sur les deux versions, `.5` ajoute les IDs `steady-playback-h264-1080p30-audio`, `steady-playback-h264-4k30` et `steady-playback-hevc-4k30`. Ils demandent quatre secondes de composition, les décodeurs eager, le mode copy et une preview de 720×720 pixels, puis une mesure continue de 3,8 secondes. Leur propriété `seekBeforePerf: false` est consignée dans le catalogue. Le runner conserve le seek avant mesure pour tous les autres cas. Les 59 cas précédents, leurs données aléatoires et leurs opérations restent présents. Une lecture continue réussie ne répare ni ne valide le seek historique.
93
+
94
+ Construire et installer les deux applications Release avant les mesures. [`test-ios-ab-run.mjs`](../scripts/test-ios-ab-run.mjs) ne compile rien : il arrête les deux applications de test, lance un processus neuf pour la version choisie, attend le JSON, puis en conserve les octets originaux avec les hashes du binaire, du bundle et des fixtures. Un crash sans JSON ou un délai dépassé reste un échec d'exécution. Ne lancer aucune compilation, suite native lourde ou autre mesure en parallèle.
95
+
96
+ ```sh
97
+ RNSKV_SIMULATOR="SIMULATOR_UDID"
98
+ run_skia_comparison() {
99
+ node scripts/test-ios-ab-run.mjs --simulator "$RNSKV_SIMULATOR" \
100
+ --backend "$1" --label "$2" --profile full --repetitions 3 \
101
+ --case steady-playback-h264-1080p30-audio \
102
+ --case steady-playback-h264-4k30 \
103
+ --case steady-playback-hevc-4k30 \
104
+ --case encode-h264-copy --case repeated-mixed-size-exports
105
+ }
106
+ run_skia_comparison baseline ab-baseline-1
107
+ run_skia_comparison candidate ab-candidate-1
108
+ run_skia_comparison candidate ab-candidate-2
109
+ run_skia_comparison baseline ab-baseline-2
110
+ ```
111
+
112
+ Les labels sont réservés de manière exclusive ; choisir de nouveaux noms pour une autre expérience. Vérifier les conditions thermiques et ménager une pause entre les lancements si elles ne sont plus nominales. Le même collecteur natif demande le RSS toutes les 100 ms dans les deux applications ; les deux exports utilisent un runtime Worklets distinct du runtime JavaScript de l'écran. Cette fréquence peut néanmoins manquer un pic court et ne mesure pas le temps GPU.
113
+
114
+ Le rapport [`benchmark-ab-summary.mjs`](../scripts/benchmark-ab-summary.mjs) garde les deux paires séparées et fournit aussi les médianes des six répétitions par version. Il déduit l'ordre réel des timestamps, refuse le chevauchement des fenêtres et conserve le verdict du comparateur strict : une validation média absente demeure incomplète, un échec ou une régression demeure signalé.
115
+
116
+ ```sh
117
+ node scripts/benchmark-ab-summary.mjs \
118
+ --pair AB benchmark/results/ab-baseline-1.local.json benchmark/results/ab-candidate-1.local.json \
119
+ --pair BA benchmark/results/ab-baseline-2.local.json benchmark/results/ab-candidate-2.local.json \
120
+ --output docs/PERFORMANCE_AB_SIMULATOR.md \
121
+ --json benchmark/results/ab-summary.local.json
122
+ ```
123
+
124
+ Le diagnostic prolongé est une expérience séparée : utiliser `--profile soak --case repeated-mixed-size-exports --repetitions 3 --idle-seconds 180`. Il produit 300 exports et des relevés du même processus au repos. Les relevés au repos proviennent de `ps` sur macOS ; leur source reste distincte du `mach task_info` embarqué. Ils n'autorisent aucune attribution aux caches, au ramasse-miettes ou aux textures sans profilage supplémentaire.
125
+
126
+ ## Fixtures locales et identité des fichiers
127
+
128
+ Générer les fichiers une seule fois sur macOS, puis transférer **ces mêmes fichiers binaires** dans les deux builds :
129
+
130
+ ```sh
131
+ swift scripts/benchmark-make-media.swift /private/tmp/skia-video-fixtures
132
+ node scripts/benchmark-fixture-manifest.mjs /private/tmp/skia-video-fixtures /private/tmp/skia-video-fixtures/manifest.json
133
+ swift scripts/benchmark-probe-media.swift /private/tmp/skia-video-fixtures /private/tmp/skia-video-fixtures/manifest.json /private/tmp/skia-video-fixtures/probe.json
134
+ ```
135
+
136
+ Le générateur est dérivé du générateur original Azzapp sous licence MIT. Il utilise AVFoundation, dessine les numéros de frame et les marqueurs d'orientation, crée de l'audio, ajoute les rotations 180/270° et produit une présentation slow motion par edit list. Il produit quatorze fichiers. L'option `--only=h264-638x358.mp4,h264-720p-rot180.mp4` permet une génération partielle en conservant les mêmes motifs et couleurs que la génération complète.
137
+
138
+ Les quatorze fichiers ont réellement été générés dans `example/fixtures`, dont H.264/HEVC 4K, HLG 10 bits, 60 fps, les rotations 90/180/270° et les pistes audio. Le lecteur AVFoundation a contrôlé les dimensions, codecs, durées, rotations, présence d'audio, comptes de frames et timestamps décodés de tous les fichiers : les quatorze sondes ont réussi. Leurs chemins, tailles, SHA-256 et observations sont enregistrés dans [`fixture-generation-status.json`](../benchmark/fixture-generation-status.json). Les quatorze médias réellement embarqués dans le build Release courant du simulateur, ainsi que le manifest, ont les mêmes tailles et SHA-256 que les sources : [`fixture-bundle-verification.json`](../benchmark/fixture-bundle-verification.json). Les treize premiers avaient aussi été vérifiés dans le build iPhone initial ; refaire cette vérification pour tout nouveau binaire.
139
+
140
+ La fixture `h264-slow-motion.mov` est désormais autonome : 180 frames présentées sur neuf secondes avec une vraie edit list. Ses timestamps ont été vérifiés après suppression du fichier source utilisé pour la construire. Une simple vidéo réencodée au ralenti ne reproduit pas cette présentation. Si cette fixture est absente d'un autre build, le cas conserve un skip explicite ; les tests iOS génèrent aussi leur propre présentation dans la suite.
141
+
142
+ Le manifest contient les tailles, codecs, durées attendues et les SHA-256 calculés sur les octets. Le lecteur sonde les médias réellement produits et décode leurs frames pour vérifier les timestamps ; une observation de métadonnées seule ne valide pas la précision HDR ou les pixels d'orientation. Toute modification de fichier oblige à recréer le manifest et à refaire les deux runs. Le comparateur refuse des hashes différents. Les tests natifs gardent leurs propres générateurs déterministes et assertions, à exécuter dans chaque build.
143
+
144
+ ## Monter le même écran dans l'application
145
+
146
+ `benchmark/screen.mjs` fournit `createBenchmarkScreen`. Ajouter `mjs` à `resolver.sourceExts` de Metro et conserver le plugin Babel Worklets/Reanimated configuré dans l'application.
147
+
148
+ Exemple d'intégration dans le build candidat :
149
+
150
+ ```js
151
+ import React from 'react';
152
+ import * as native from 'react-native';
153
+ import * as skia from 'react-native-skia';
154
+ import * as reanimated from 'react-native-reanimated';
155
+ import * as video from 'react-native-skia-webgpu-video';
156
+ import Blob from 'react-native-blob-util';
157
+ import { createBenchmarkScreen } from './benchmark/screen.mjs';
158
+
159
+ const files = {
160
+ outputPath: async (id, repetition, cycle) =>
161
+ `${Blob.fs.dirs.CacheDir}/benchmark-${id}-${repetition}-${cycle}.mp4`,
162
+ stat: (path) => Blob.fs.stat(path),
163
+ remove: async (path) => {
164
+ if (await Blob.fs.exists(path)) await Blob.fs.unlink(path);
165
+ },
166
+ writeResult: (result) => Blob.fs.writeFile(
167
+ `${Blob.fs.dirs.DocumentDir}/benchmark-results.json`,
168
+ JSON.stringify(result),
169
+ 'utf8'
170
+ ),
171
+ // probe: native metadata reader returning codec, width, height, fps and duration.
172
+ };
173
+
174
+ export const BenchmarkScreen = createBenchmarkScreen({
175
+ React, native, skia, reanimated, video, files,
176
+ // memory: {
177
+ // sample: async () => nativeProcessMemoryReadings,
178
+ // operatingConditions: async () => nativeThermalAndPowerReadings,
179
+ // },
180
+ // validation: native pixel/audio/timestamp checks, outside timed draw callbacks.
181
+ });
182
+ ```
183
+
184
+ Dans le build de référence, seules les importations `video` et `skia` changent. Le même écran et les mêmes `options` sont utilisés.
185
+
186
+ Passer à l'écran :
187
+
188
+ ```js
189
+ const options = {
190
+ profile: 'full', // smoke / full / soak
191
+ repetitions: 3,
192
+ memorySamplePeriodMs: 100,
193
+ memoryBudgetBytes: 512 * 1024 * 1024, // même limite demandée dans les deux builds
194
+ fixtureDirectory: DEVICE_FIXTURE_DIRECTORY,
195
+ fixtureManifest: VERIFIED_MANIFEST,
196
+ environment: {
197
+ deviceId: ACTUAL_DEVICE_ID,
198
+ deviceModel: ACTUAL_MODEL,
199
+ executionTarget: ACTUAL_EXECUTION_TARGET, // 'device' ou 'simulator'
200
+ measurementClock: 'performance.now', // horloge utilisée par le même écran
201
+ platform: native.Platform.OS,
202
+ osVersion: ACTUAL_OS_VERSION,
203
+ reactNativeVersion: ACTUAL_RN_VERSION,
204
+ buildMode: 'release',
205
+ displayRefreshRate: ACTUAL_DISPLAY_HZ,
206
+ thermalState: MEASURED_THERMAL_STATE_OR_UNAVAILABLE,
207
+ powerMode: ACTUAL_POWER_MODE,
208
+ },
209
+ backend: {
210
+ name: 'webgpu',
211
+ version: ACTUAL_LIBRARY_VERSION,
212
+ skiaVersion: ACTUAL_SKIA_VERSION,
213
+ paths: video.getVideoResourceStats?.().backend ?? DECLARED_BACKEND_PATHS,
214
+ },
215
+ };
216
+ ```
217
+
218
+ Les constantes ci-dessus viennent de l'application ou de l'appareil. Ne pas copier un faux identifiant, une version supposée ou un état thermique « nominal » non mesuré. Le profil `soak` porte les scrubs à 2 000 seeks et les cycles à 100. Le profil `smoke` ne qualifie pas la matrice complète.
219
+
220
+ Depuis le workload `2026-10-06.3`, la mesure demande une preview de **720×720 pixels physiques** dans les deux builds. À PixelRatio 3, le Canvas fait environ 240×240 points. Le résultat conserve les dimensions demandées, logiques et réellement dessinées ; un écart de résolution invalide la comparaison. Chaque répétition monte un enfant possédant son hook et son Canvas, le démonte, puis attend l'accusé de traitement de la queue UI avant les relevés de stabilisation. Les anciennes mesures à 720 points ne sont pas compatibles avec ce workload.
221
+
222
+ Le moteur candidat utilise par défaut un budget de réservations de 256 Mio. `memoryBudgetBytes` applique une limite explicite avant le premier cas via `configureVideoMemory({ maxBytes })`. Le résultat conserve la limite demandée, son application effective et la limite native rapportée. L'ancien backend n'a pas cette API : le JSON indique son absence et le comparateur signale la différence d'application. Une limite demandée différente rend les runs incompatibles.
223
+
224
+ Une frame 4K BGRA/RGBA 8 bits représente environ 31,6 Mio. Dans le chemin d'export CPU actuel, la surface de rendu, les deux réservations de readback et les trois buffers de l'encodeur peuvent réserver environ 190 Mio avant les frames natives, les images et les caches de codec/pilote. Les cas avec plusieurs clips 4K peuvent donc atteindre le budget de 256 Mio ; ils doivent signaler une limite de ressources et garder les dimensions demandées. La limite de 512 Mio de l'exemple est une configuration explicite du benchmark, à consigner dans les deux runs ; elle ne garantit pas que l'appareil dispose de cette mémoire physique. Le scénario qui demande `maxLongSide: 1280` reste un scénario distinct : aucune réduction silencieuse de qualité ne rend un cas 4K réussi.
225
+
226
+ L'écran mesure l'heure de composition et les IDs visibles pour attendre un seek. L'exactitude de l'index de frame repose sur les tests natifs et le validateur de pixels : afficher le bon item au bon temps n'est pas une preuve que ses pixels sont ceux attendus.
227
+
228
+ Les rendus d'extension s'injectent par `extensions: { lut: { drawFrame }, compute: { drawFrame }, three: { drawFrame }, coreml: { drawFrame } }`. Les ressources se créent au début de la session, se libèrent à sa fermeture et restent soumises au même budget. Une fonction de dessin export synchrone seule ne constitue pas encore un pipeline ML asynchrone. Un adaptateur doit préparer ses résultats au temps de composition demandé ; le cas reste skip tant que cet adaptateur n'existe pas.
229
+
230
+ ## Ce que le JSON mesure
231
+
232
+ | Champ | Signification |
233
+ | --- | --- |
234
+ | `drawHistogram` | Temps CPU passé dans le callback de dessin ; buckets fixes, stockage constant |
235
+ | `callbackGapHistogram` | Intervalles entre callbacks ; pas une mesure des présentations écran |
236
+ | `exportMs` | Pipeline complet : encodage, vérification du fichier, sonde des métadonnées et suppression ; sans l'attente de stabilisation préalable |
237
+ | `encodeMs`, `probeMs` | Durée de la promesse d'export et durée de contrôle du fichier mesurées séparément |
238
+ | `framesCompleted`, `expectedFrames`, `progressEvents` | Progression monotone et nombre de frames demandé |
239
+ | `actualOutput` | Codec, dimensions et timestamps sondés dans le fichier exporté |
240
+ | `rssBytes`, `pssBytes`, `nativeHeapBytes`, `gpuBytes` | Lectures d'un collecteur identifié, ou `value: null` avec une raison |
241
+ | `ownedBytes`, `ownedResources` | Réservations internes de la bibliothèque ; estimations distinctes des allocations physiques |
242
+ | `decoderCount`, `inFlightFrames` | Compteurs natifs exposés par le backend, quand disponibles |
243
+ | `memoryBudget` | Limite identique demandée et disponibilité de son application par chaque backend |
244
+ | `collection.preview` | Pixels physiques demandés/dessinés, taille logique, PixelRatio et étapes de fermeture |
245
+ | `operatingConditions` des points mémoire | État thermique/alimentation mesuré à chaque relevé, ou raison d'indisponibilité |
246
+ | `diagnostic`, `cleanupDiagnostic` | Stack bornée, opération et étape d'échec ; erreur d'origine séparée de l'erreur de fermeture |
247
+
248
+ Le nombre de frames perdues et le temps GPU sont `null` tant qu'un compteur réel n'est pas branché. Une différence d'espacement entre callbacks n'est pas automatiquement une frame perdue. Les histogrammes donnent des quantiles approximatifs à leur borne supérieure. Garder ce coût de mesure identique dans les deux builds.
249
+
250
+ Le harness conserve des résumés et des histogrammes, jamais les frames ni les textures. Les points mémoire sont bornés à 2 048 par cas par défaut ; une troncature est enregistrée et rend la comparaison incomplète. Le relevé asynchrone conserve la phase et l'heure de sa demande même s'il revient après le démontage. Un échec de collecte des conditions ne supprime pas un relevé RSS réussi.
251
+
252
+ Les sorties temporaires sont supprimées après la fin ou le rejet de l'export. En cas d'export qui dépasse le délai et ne termine pas son annulation, y compris dans le scénario de tailles alternées, les cas suivants sont bloqués et le JSON demande de redémarrer le processus : aucune fermeture fictive n'est rapportée. Une réserve possédée restant après fermeture bloque aussi les cas suivants. Le cas « audio absent » ne valide que l'erreur attendue de piste audio absente ; un échec GPU, codec ou une limite mémoire reste un échec.
253
+
254
+ ## Collecter la mémoire physique
255
+
256
+ Sur Android, le collecteur hôte continue à fonctionner même si l'ancien export bloque le runtime JavaScript :
257
+
258
+ ```sh
259
+ node scripts/benchmark-sample-android.mjs DEVICE_SERIAL APPLICATION_ID /private/tmp/android-memory.jsonl 1000
260
+ ```
261
+
262
+ Arrêter avec Ctrl-C à la fin du run. Il relève RSS, PSS et la contribution PSS du Native Heap quand elles sont accessibles. GL/EGL mtrack reste une mesure graphique partielle et n'est jamais renommé « mémoire GPU totale ». Les permissions et les pilotes peuvent rendre certaines mesures indisponibles.
263
+
264
+ Le fichier est ensuite aligné aux phases du résultat. Mesurer le décalage d'horloge appareil moins hôte avant le run ; le script exige cette valeur plutôt que de supposer une synchronisation :
265
+
266
+ ```sh
267
+ node scripts/benchmark-merge-memory.mjs device-results.json /private/tmp/android-memory.jsonl merged-results.json MEASURED_CLOCK_OFFSET_MS
268
+ ```
269
+
270
+ La fusion conserve les relevés indépendants pendant un export qui bloque le runtime JS, afin de garder ses pics RSS/PSS. Le résultat possède une timeline explicite des phases ; un relevé dont la collecte traverse une frontière de phase est exclu. Les points JS peuvent être complétés par le relevé de la même phase situé à moins de trois secondes. Le début, la fin, la durée de collecte et l'écart d'alignement sont enregistrés ; le stockage fusionné reste borné. Une ancienne timeline absente ou des conditions thermiques/alimentation non mesurées pendant un long blocage rendent la comparaison incomplète. Une fréquence trop faible peut manquer un pic court : utiliser les compteurs natifs de pic et un profil système en complément.
271
+
272
+ Sur iOS physique, les deux applications isolées fournissent le **RSS** via `mach task_info resident_size` et l'**empreinte physique** via `task_vm_info phys_footprint`, dans deux champs distincts de `memory.sample`. Le RSS n'est pas renommé footprint ni mémoire GPU totale ; ces valeurs ne s'additionnent pas. Les relevés au repos doivent être produits par l'iPhone et liés au même run/processus, sans `ps` du Mac. Utiliser Allocations/Leaks et Metal pour attribuer les allocations natives et GPU. Le heap JavaScript ne permet pas de conclure sur les CVPixelBuffers, les textures, les buffers de codec ou la mémoire d'un modèle Core ML.
273
+
274
+ Le mot `settled` désigne une phase après démontage effectif de l'enfant React/Canvas et traitement de sa fermeture sur la queue UI. Cet accusé ne prouve pas la libération des caches du pilote : les allocations physiques restent mesurées séparément. Si le backend ne fournit pas de fin de travaux explicite, le JSON le signale. Cette attente n'autorise pas à recycler un buffer encore utilisé par le GPU.
275
+
276
+ ## Comparer et interpréter
277
+
278
+ ```sh
279
+ node scripts/benchmark-compare.mjs baseline.json candidate.json comparison.json
280
+ ```
281
+
282
+ Le comparateur refuse des fichiers dont l'appareil, l'OS, React Native, le mode release, la cible appareil/simulateur, l'horloge de mesure, la période de collecte mémoire, PixelRatio, les fixtures, la seed, les dimensions, les codecs demandés ou les opérations diffèrent. Chaque répétition doit contenir toutes les opérations, validations et phases mémoire requises dans les deux builds. Il vérifie chaque fichier contre les dimensions, le codec, le compte de frames et les timestamps demandés : deux sorties également erronées ne deviennent pas un succès. Il conserve les modes et le chemin de transport pour expliquer les changements ; la disponibilité différente de l'API de budget et les changements de transport restent des informations de comparaison.
283
+
284
+ Il compare les médianes d'au moins trois répétitions et fournit les variations absolues et en pourcentage. Les seuils initiaux sont des détecteurs de régression à calibrer avec la variance des appareils :
285
+
286
+ | Mesure | Déclenchement initial |
287
+ | --- | --- |
288
+ | Durée d'export | Hausse supérieure à 10 % **et** 100 ms |
289
+ | p95 du callback de dessin | Hausse supérieure à 15 % **et** 1 ms |
290
+ | Pic mémoire | Hausse supérieure à 10 % **et** 8 Mio, à collecteur identique |
291
+ | Croissance après warmup | Pente supérieure à 1 Mio/min **et** gain supérieur à 8 Mio |
292
+
293
+ Les tendances de croissance utilisent au moins trois cycles distincts après les dix premiers cycles, avec des indices explicites et des timestamps croissants. Plusieurs relevés d'une même fermeture comptent pour un seul cycle ; le relevé final des exports alternés est rattaché au dernier export. Un export isolé ne fournit aucune preuve de warmup et ne déclenche pas une alerte de croissance après warmup. Les pics RSS restent comparés indépendamment. Elles donnent le pic, le gain et la pente ; elles ne remplacent pas une analyse des allocations. Un palier de cache pilote peut subsister après fermeture. Pour chaque session terminée, vérifier séparément que les ressources propres à la session reviennent à zéro ou à un cache global explicitement borné.
294
+
295
+ Codes de sortie : `0` = mesures comparables dans les limites ; `1` = régression ou échec ; `2` = fichiers incompatibles / invalides ; `3` = comparaison incomplète. Un skip, moins de trois répétitions, des métadonnées non sondées ou l'absence de mémoire physique ne donnent jamais un succès de qualification.
296
+
297
+ Alterner l'ordre A/B puis B/A, laisser refroidir entre les runs, garder le même mode d'alimentation et vérifier les états thermiques à chaque relevé. L'application iPhone fournit ces lectures via `NSProcessInfo` dans `memory.operatingConditions`. Des conditions inconnues, un état thermique non nominal ou un changement d'alimentation rendent les conclusions de performance incomplètes ; les erreurs de pixels et la croissance mémoire restent des constats à analyser. Le collecteur fait partie du coût du test et doit être identique dans les deux builds. Aucun seuil fixe ne garantit l'absence de fuite : il faut aussi une longue session et l'inspection des compteurs / allocations.
298
+
299
+ Les quatre traces natives de création/fermeture du candidat sont derrière `RNSV_TRACE_LIFECYCLE`, désactivé en Release. La référence conserve les traces originales d'ouverture/fermeture de fenêtre lazy afin de tester le fork inchangé. Ce coût clairsemé reste une différence d'instrumentation à consigner, notamment pour les exports lazy ; ce ne sont pas des traces par frame. Les avertissements `unrecognised property keys` viennent du codec HEVC Apple du simulateur (`VCPHEVC.videocodec`), y compris dans les XCTest HEVC qui passent. Ils ne justifient aucun patch JavaScript ; isoler ce bruit du codec si nécessaire pendant les mesures. Les mesures du candidat seul ne démontrent aucun gain par rapport à la référence.
300
+
301
+ ## Vérifications exécutées sur l'hôte
302
+
303
+ ```sh
304
+ node --test test/comparison/*.test.mjs
305
+ node scripts/benchmark-inventory.mjs
306
+ node scripts/test-jvm-host.mjs
307
+ node scripts/test-native.mjs --asan
308
+ node scripts/test-ios-parity-syntax.mjs
309
+ node scripts/test-skia-patch-syntax.mjs
310
+ swiftc -typecheck -module-cache-path /private/tmp/skia-benchmark-swift-cache scripts/benchmark-make-media.swift
311
+ ```
312
+
313
+ Les **112 tests du harness, du rapport A/B et de leurs preuves passent**, sans échec ni skip. Ils vérifient la déterminisation, la couverture de l'ancien écran, les montages/démontages effectifs, les pixels physiques demandés, les fixtures, l'annulation de tous les chemins d'export, les progressions invalides, la pause/reprise, les seeks, les frame maps et la classification des erreurs. Les régressions supplémentaires couvrent la télémétrie et les diagnostics bornés, les relevés asynchrones et externes, les conditions thermiques, les champs d'identité manquants et les résultats erronés identiques entre deux backends. Dix-sept nouvelles régressions vérifient l'horloge mobile et l'identité des frames pendant les seeks en lecture. Quatre tests vérifient le recorder XCTest ; son self-test séparé valide 24 assertions de rejet des preuves incomplètes ou périmées.
314
+
315
+ Le run historique de l’alpha **3.0.3** compte **138 tests Jest réussis dans 6 suites**, dont six nouvelles régressions autorelease avec le vrai Babel/runtime. La preuve hôte renouvelée le **6 octobre à 16:36:02 UTC** contient **170 assertions**. Avec les **50 XCTest sur simulateur**, tous passés sans skip à **16:36:31 UTC**, elle couvre **122 cas historiques sur 162** : 72 cas hôte et 50 sur simulateur, fingerprint `486b12a…`. Ces mêmes 50 cas ont aussi passé sur l'iPhone physique le **7 octobre à 07:23:57 UTC**, sans nouveau nom ajouté au dénominateur historique. Les 40 cas Android appareil restent à exécuter. La première tentative iPhone 45/50 est historique, avant correctifs. Les nouvelles régressions Jest ne changent pas le nombre de cas historiques.
316
+
317
+ Pour consigner les assertions hôte dans l'inventaire, enregistrer un run Jest complet et réussi, puis lancer `node scripts/benchmark-record-host-verification.mjs PASSED_JEST_JSON` et régénérer l'inventaire. Ce script réexécute les tests C++ et JVM, conserve les commandes et résultats, et lie la preuve aux hashes des sources et de `test/utils/serializedUiRuntime.ts`. Il refuse un JSON Jest antérieur à une modification de ce helper. Toute modification de source invalide ce statut jusqu'à une nouvelle vérification. Les fichiers JUnit/Hamcrest sont lus dans le cache local ou via `RNSV_JUNIT_JAR`/`RNSV_HAMCREST_JAR`, sans téléchargement automatique.
318
+
319
+ Pour consigner une nouvelle exécution native du simulateur, utiliser le `.xcresult` produit par le script iOS :
320
+
321
+ ```sh
322
+ node scripts/benchmark-record-ios-simulator-verification.mjs RESULT.xcresult
323
+ node scripts/benchmark-inventory.mjs
324
+ ```
325
+
326
+ Le recorder exige les 50 noms XCTest de la source, tous passés, aucun skip/échec, une cible Simulator réelle et des sources antérieures au début du build. Il lie la preuve aux hashes des sources, dépendances, projet et fixture. Le résultat actuel est dans [ios-simulator-verification.json](../benchmark/ios-simulator-verification.json) ; un run précédent réellement réussi a été rejeté parce qu'une source avait changé ensuite. La fixture JPEG 1279×719 y est identifiée par hash : ses 30 indices, pixels et dimensions aux temps demandés sont vérifiés par le test simulateur ; les PTS bruts du fichier ont été vérifiés séparément par AVFoundation sur macOS. Cela ne remplace pas les validateurs pixels/audio encore absents du catalogue applicatif.
327
+
328
+ Le runner natif exécute les 20 assertions C++ originales, 200 000 fenêtres aléatoires, 100 000 dispositions de buffers vérifiées contre les débordements, 40 000 réservations concurrentes, les callbacks JSI après destruction/teardown, 10 000 cycles de buffers iOS et un export H.264 réel de 120 frames. Ce dernier vérifie 60 frames rouges puis 60 bleues, les timestamps exacts et 100 seeks du lecteur. Les compteurs possédés reviennent à zéro ; les runs avec AddressSanitizer/UndefinedBehaviorSanitizer ont réussi. Ces tests de médias utilisent CoreVideo/AVFoundation sur macOS et nécessitent l'accès aux services IOSurface/VideoToolbox ; `--pure-only` n'exécute que les tests indépendants des médias. Les 21 tests JVM ont également réussi, dont les 12 assertions historiques.
329
+
330
+ Sur macOS, `test-native.mjs` compile aussi les 50 assertions XCTest portées et le vrai header du patch Canvas avec Skia 3 Graphite/Dawn et le SDK iPhoneSimulator. Les deux scripts de syntaxe se lancent séparément sans accès au service de codec. Ils ne déclarent aucune exécution des 50 XCTest ni aucun test GPU Dawn. Le générateur Swift utilise les mêmes API que l'ancien générateur ; le SDK macOS récent émet des avertissements de dépréciation, sans erreur de types.
331
+
332
+ L'application isolée `example/ios/ReactNativeSkiaWebGPUVideoExample.xcworkspace` et sa cible contenant les 50 XCTest ont passé le build complet Release pour l'architecture de l'iPhone connecté, incluant la liaison Skia 3 / Dawn / WebGPU, puis ont été signées et installées sur iPhone 15 Pro. Le build utilise les bundle IDs distincts `com.seb.skiawebgpuvideo.test` et `.tests`. [`ios-build-verification.json`](../benchmark/ios-build-verification.json) conserve la preuve du premier build sans signature. La confiance du certificat, la configuration UIScene et le linkage ont été corrigés. Le smoke initial du **6 octobre** avait ensuite échoué au premier montage avec `Cannot read property 'current' of undefined`. Le rapport local `benchmark/results/first-iphone-smoke.local.json` conserve les mesures et les quatre skips suivant cet échec. Le smoke actuel du **7 octobre a passé 5/5 cas**. Le module expose le RSS par `mach task_info`, les conditions thermiques/alimentation par `NSProcessInfo`, les fichiers de résultats et une sonde d'export AVFoundation ; il ne mesure pas la mémoire GPU totale.
333
+
334
+ [IOS_DEVICE_TESTS.md](IOS_DEVICE_TESTS.md) donne les étapes sur iPhone 15 Pro : exécution des 50 cas avec Cmd+U, profils de benchmark en Release avec Cmd+R et récupération des JSON dans `Documents/benchmark-results`. Le script `scripts/test-ios-device.mjs` reprend ces lancements. Sous 3.0.6, les applications Android Debug/Release, l’AAR Debug et l’APK d’instrumentation ont compilé ; la [preuve de compilation Android](../android/BUILD_VALIDATION.md) et les [diagnostics appareil](ANDROID_DEVICE_306.md) identifient chaque checkpoint. Le binaire AHB a passé **43 JVM, 6 tests natifs physiques et les 24 essais ciblés** ; la suite d'instrumentation entière, les sessions longues et la mémoire complète du pilote restent non qualifiées. Le code TypeScript courant passe **191 Jest dans huit suites et le contrôle des types**. La [preuve hôte de la révision iOS antérieure](../benchmark/host-verification.json) contient séparément **189 assertions : 157 Jest, 20 C++ et 12 JVM historiques**, sans qualifier ces changements suivants. Les résultats physiques 3.0.3 restent historiques. La preuve native 3.0.6 couvre les 50 XCTest du premier build iPhone ; les 14 cas optimisés sur simulateur et la [passe physique initial/optimisé 3.0.6](PERFORMANCE_IPHONE_306_OPTIMIZATION.md) sont des preuves distinctes. L'oracle Android ne valide que les pixels SDR échantillonnés ; audio, HDR et performances iOS nominales restent à qualifier.
335
+
336
+ La cible XCTest iOS candidate est rattachée et le build signé est installé. Le run natif iPhone a produit 45 réussites/5 échecs avant correctifs, puis **50/50 réussites sans échec ni skip le 7 octobre** : [preuve physique](../benchmark/ios-native-device-verification.json). Sur simulateur, **50/50 XCTest passent**, après correction du replay et activation des scopes autorelease synchrones. **Sept cas ciblés** (smoke cinq cas et seek/scrub en lecture) ont passé le **6 octobre entre 16:37:45 et 16:38:43 UTC**, en workload `.4`, avec les ressources suivies revenues à zéro. `benchmark/results/simulator-functional-pooled.local.json` et `final-functional-source-pooled.local.json` conservent le run et sa provenance `486b12a…` / bundle `eb434d2…`. Les exports sont sondés avec dimensions et timestamps exacts ; les validateurs pixels/audio du catalogue restent indisponibles.
337
+
338
+ Le premier `full`, achevé à **15:40:56 UTC** sur le build d'empreinte `4a087400…`, a parcouru les 59 workloads sur une répétition :
339
+
340
+ | Statut | Nombre | Détail |
341
+ | --- | --- | --- |
342
+ | Réussi | 50 | Opérations fonctionnelles exécutées selon le harness |
343
+ | Échec | 2 | `seek-while-playing` et `scrub-while-playing` : timeout sur le prédicat `.3`, réexécutions `.4` réussies séparément |
344
+ | Limite de budget | 3 | `montage-all-crossfade-eager`, `4k-x8-eager`, `4k-x3-simultaneous` : budget déclaré de 512 Mio atteint |
345
+ | Extension indisponible | 4 | LUT, compute, Three.js et Core ML non branchés |
346
+
347
+ Le rapport brut `benchmark/results/simulator-full-first.local.json` conserve les 59 résultats `.3`. Les 55 cas actifs rendent à zéro les octets/réservations/codecs/buffers en vol suivis, sans `cleanupError` ni `unsafeToContinue`. Ces compteurs ne représentent pas toute la mémoire physique et n'effacent pas les deux échecs historiques. La version `.4` valide le seek contre l'horloge qui avance, un nouveau rendu, les items visibles et le changement d'identité native disponible ; elle n'invente aucun PTS natif. Les réexécutions ciblées ne remplacent pas un full59 du build actuel.
348
+
349
+ La mesure actuelle du **candidat simulateur Release** a réussi **15/15 répétitions**, entre **16:40:14 et 16:43:27 UTC**, sur cinq cas : H.264 1080p/audio, H.264 4K, HEVC 4K, export H.264 et exports de tailles alternées. Les **63 fichiers exportés ont été sondés**, et les ressources suivies sont à zéro après chaque répétition. [PERFORMANCE_SIMULATOR.md](PERFORMANCE_SIMULATOR.md) conserve ces résultats de la révision `486b12a…`. Le RSS des exports alternés progresse d'environ 445 à 574 Mio après fermeture au fil des trois répétitions ; zéro owned ne prouve pas un palier physique ni une absence de fuite.
350
+
351
+ Le diagnostic distinct de cette révision a réussi **3/3 répétitions de 100 exports**, soit **300 fichiers sondés**, entre **16:44:32 et 16:47:41 UTC**, sans profilage concurrent ni télémétrie tronquée. Ses ressources suivies reviennent à zéro, mais le RSS final progresse d'environ **419 à 554 puis 627 Mio** ; le pic échantillonné atteint **877 Mio**. Les relevés du même processus au repos sont conservés séparément : **551,375 puis 353,453 Mio**, environ cinq minutes après les exports pour le dernier. Cette récupération différée ne fournit aucune attribution heap/GC/GPU ni preuve de palier stable ou d'absence de fuite.
352
+
353
+ [PERFORMANCE_SIMULATOR_INITIAL.md](PERFORMANCE_SIMULATOR_INITIAL.md) conserve la passe antérieure `621c20f…`, son long run de 300 exports et les relevés au repos. Le RSS de ce long run diminue entre répétitions puis redescend à 85,234 Mio après repos. Cette observation ne fournit aucune attribution heap/GC/GPU et aucun gain statistique n'est déduit entre les passes.
354
+
355
+ Le run utilise `performance.now` et des relevés mémoire demandés toutes les **100 ms**, dont le coût fait partie des mesures. `scripts/benchmark-performance-summary.mjs` exige les trois répétitions passées, des identités/conditions/collecteurs constants, les dimensions réelles et les sondes exactes. Il distingue cadence des callbacks, durée murale de leur travail, pipeline export hors sonde et pics mémoire échantillonnés ; aucun temps GPU ni heap natif/JS n'est disponible. Pour régénérer le rapport à partir des preuves locales :
356
+
357
+ ```sh
358
+ node scripts/benchmark-performance-summary.mjs benchmark/results/simulator-performance-pooled.local.json --host benchmark/results/performance-host.local.json --provenance benchmark/results/final-functional-source-pooled.local.json --long-run benchmark/results/simulator-mixed-soak-pooled.local.json --long-idle-observations benchmark/results/mixed-soak-pooled-idle-observations.local.json --output docs/PERFORMANCE_SIMULATOR.md
359
+ ```
360
+
361
+ Le hardware hôte vérifié est un MacBookPro18,3, Apple M1 Pro, 16 Gio, macOS 27.0.1 ; ce n'est pas le hardware de l'iPhone simulé. Les chiffres candidat seul ci-dessus restent historiques au workload `.4` ; les runs A/B `.5` doivent être consignés séparément. Les mesures physiques restent différées.
362
+
363
+
364
+ ## Comparaison prolongée achevée
365
+
366
+ Le diagnostic `.5` a terminé **300 exports par bibliothèque**, tous sondés : [résultats et repos](PERFORMANCE_AB_SOAK_SIMULATOR.md). Le RSS final des trois blocs vaut environ 308/315/300 Mio pour l’ancien, 416/553/626 Mio pour le nouveau. Après trois minutes de repos du même processus, le RSS mesuré par `ps` vaut **133,20 Mio contre 437,38 Mio**. Les ressources suivies du candidat sont à zéro, mais la rétention physique reste à expliquer ; aucun profil heap/GPU n’a été collecté. Cette passe confirme le besoin de traiter la consommation d’export avant la 3D/ML.
367
+
368
+ Les six runs comparatifs totalisent **66 exécutions de workloads et 852 fichiers sondés**, incluant les deux paires courtes et la session longue. L’ancien dépôt est conservé inchangé. Le premier crash historique de seek reste documenté séparément. [Preuve agrégée](../benchmark/ab-simulator-verification.json), [guide de migration](MIGRATION.md).