react-native-skia-webgpu-video 0.1.0-beta.3 → 0.1.0

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 (81) hide show
  1. package/README.md +48 -43
  2. package/android/BUILD_VALIDATION.md +1 -1
  3. package/android/CMakeLists.txt +3 -2
  4. package/android/cpp/ExportHintSession.cpp +114 -0
  5. package/android/cpp/ExportHintSession.h +35 -0
  6. package/android/cpp/VideoEncoderHostObject.cpp +76 -101
  7. package/android/cpp/VideoEncoderHostObject.h +14 -9
  8. package/android/cpp/VideoFrame.cpp +3 -5
  9. package/android/cpp/cpp-adapter.cpp +7 -4
  10. package/android/src/main/java/com/azzapp/rnskv/GLFrameExtractor.java +3 -9
  11. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +8 -11
  12. package/android/src/main/java/com/azzapp/rnskv/VideoEncoder.java +68 -28
  13. package/docs/FRAME_PIXEL_FORMAT.md +17 -2
  14. package/docs/MIGRATION.md +1 -1
  15. package/docs/UPSTREAM_NOTES.md +42 -2
  16. package/ios/RNSVColorSpace.h +8 -3
  17. package/lib/module/canvas.js +3 -5
  18. package/lib/module/canvas.js.map +1 -1
  19. package/lib/module/exportFrames.js +8 -10
  20. package/lib/module/exportFrames.js.map +1 -1
  21. package/lib/module/exportTarget.js +183 -40
  22. package/lib/module/exportTarget.js.map +1 -1
  23. package/lib/module/exportVideoComposition.js +78 -35
  24. package/lib/module/exportVideoComposition.js.map +1 -1
  25. package/lib/module/frameInterop.js +323 -82
  26. package/lib/module/frameInterop.js.map +1 -1
  27. package/lib/module/gpu.js +1 -3
  28. package/lib/module/gpu.js.map +1 -1
  29. package/lib/module/gpuDevice.js +12 -0
  30. package/lib/module/gpuDevice.js.map +1 -1
  31. package/lib/module/memory.js +7 -0
  32. package/lib/module/memory.js.map +1 -1
  33. package/lib/module/previewLifecycle.js +2 -1
  34. package/lib/module/previewLifecycle.js.map +1 -1
  35. package/lib/module/videoCompositionPlayer.js +65 -14
  36. package/lib/module/videoCompositionPlayer.js.map +1 -1
  37. package/lib/module/videoFrameImage.js +1 -1
  38. package/lib/module/yuvBlit.js +31 -24
  39. package/lib/module/yuvBlit.js.map +1 -1
  40. package/lib/typescript/src/canvas.d.ts.map +1 -1
  41. package/lib/typescript/src/exportFrames.d.ts +3 -2
  42. package/lib/typescript/src/exportFrames.d.ts.map +1 -1
  43. package/lib/typescript/src/exportTarget.d.ts +53 -14
  44. package/lib/typescript/src/exportTarget.d.ts.map +1 -1
  45. package/lib/typescript/src/exportVideoComposition.d.ts +2 -1
  46. package/lib/typescript/src/exportVideoComposition.d.ts.map +1 -1
  47. package/lib/typescript/src/frameInterop.d.ts +16 -5
  48. package/lib/typescript/src/frameInterop.d.ts.map +1 -1
  49. package/lib/typescript/src/gpu.d.ts.map +1 -1
  50. package/lib/typescript/src/gpuDevice.d.ts +5 -1
  51. package/lib/typescript/src/gpuDevice.d.ts.map +1 -1
  52. package/lib/typescript/src/memory.d.ts +2 -0
  53. package/lib/typescript/src/memory.d.ts.map +1 -1
  54. package/lib/typescript/src/previewLifecycle.d.ts +2 -0
  55. package/lib/typescript/src/previewLifecycle.d.ts.map +1 -1
  56. package/lib/typescript/src/types.d.ts +18 -12
  57. package/lib/typescript/src/types.d.ts.map +1 -1
  58. package/lib/typescript/src/videoCompositionPlayer.d.ts +4 -5
  59. package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -1
  60. package/lib/typescript/src/videoFrameImage.d.ts +1 -1
  61. package/lib/typescript/src/yuvBlit.d.ts +16 -14
  62. package/lib/typescript/src/yuvBlit.d.ts.map +1 -1
  63. package/package.json +6 -6
  64. package/patches/react-native-skia+3.10.0.patch +150 -0
  65. package/scripts/skia-canvas-patch.json +22 -258
  66. package/src/canvas.ts +3 -7
  67. package/src/exportFrames.ts +10 -11
  68. package/src/exportTarget.ts +217 -56
  69. package/src/exportVideoComposition.ts +130 -85
  70. package/src/frameInterop.ts +389 -96
  71. package/src/gpu.ts +1 -3
  72. package/src/gpuDevice.ts +26 -1
  73. package/src/memory.ts +6 -0
  74. package/src/previewLifecycle.ts +4 -1
  75. package/src/types.ts +18 -12
  76. package/src/videoCompositionPlayer.ts +76 -23
  77. package/src/videoFrameImage.ts +1 -1
  78. package/src/yuvBlit.ts +46 -31
  79. package/android/cpp/EncoderTargets.cpp +0 -226
  80. package/android/cpp/EncoderTargets.h +0 -70
  81. package/patches/react-native-skia+3.7.1.patch +0 -502
@@ -1 +1 @@
1
- {"version":3,"file":"yuvBlit.d.ts","sourceRoot":"","sources":["../../../src/yuvBlit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,8BAA8B,EAC9B,gBAAgB,EACjB,MAAM,qBAAqB,CAAC;AAO7B;;4EAE4E;AAC5E,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,8BAA8B,CAAC;IACxC,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,EAAE,YAAY,CAAC;IACxB,sDAAsD;IACtD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AA8CF;;;2BAG2B;AAC3B,eAAO,MAAM,cAAc,GACzB,QAAQ;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC/D,UAAU,MAAM,EAChB,QAAQ;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACzC,OAAO;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KACvC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAyBxB,CAAC;AAyEF,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,GAAI,OAAO,SAAS,KAAG,IAKnD,CAAC;AAEF;yDACyD;AACzD,eAAO,MAAM,iBAAiB,GAC5B,SAAS,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,EACjC,WAAW,MAAM,KAChB,IAUF,CAAC;AAoDF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GACvB,QAAQ,SAAS,EACjB,SAAS,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,EACjC,UAAU,MAAM,EAChB,QAAQ,gBAAgB,EACxB,UAAU,MAAM,EAChB,QAAQ;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAChC,OAAO,MAAM,EACb,QAAQ,MAAM,EACd,QAAQ,UAAU,KACjB,IAgEF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,SAAS,EACjB,QAAQ,gBAAgB,EACxB,QAAQ,UAAU,KACjB,IAiDF,CAAC"}
1
+ {"version":3,"file":"yuvBlit.d.ts","sourceRoot":"","sources":["../../../src/yuvBlit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,8BAA8B,EAC9B,gBAAgB,EACjB,MAAM,qBAAqB,CAAC;AAO7B;;4EAE4E;AAC5E,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,8BAA8B,CAAC;IACxC,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,EAAE,YAAY,CAAC;IACxB,sDAAsD;IACtD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AA8CF;;;8CAG8C;AAC9C,eAAO,MAAM,cAAc,GACzB,QAAQ;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC/D,UAAU,MAAM,EAChB,QAAQ;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KACxC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAsCvD,CAAC;AAyEF,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,GAAI,OAAO,SAAS,KAAG,IAKnD,CAAC;AAEF;yDACyD;AACzD,eAAO,MAAM,iBAAiB,GAC5B,SAAS,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,EACjC,WAAW,MAAM,KAChB,IAUF,CAAC;AAoDF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GACvB,QAAQ,SAAS,EACjB,SAAS,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,EACjC,UAAU,MAAM,EAChB,QAAQ,gBAAgB,EACxB,UAAU,MAAM,EAChB,QAAQ;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC/D,QAAQ,UAAU,KACjB,IAgEF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,SAAS,EACjB,QAAQ,gBAAgB,EACxB,QAAQ,UAAU,KACjB,IAqDF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-skia-webgpu-video",
3
- "version": "0.1.0-beta.3",
3
+ "version": "0.1.0",
4
4
  "description": "Bounded video composition, playback and export for React Native Skia 3 / Graphite",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "!**/.*",
41
41
  "scripts/apply-skia-canvas-patch.mjs",
42
42
  "scripts/skia-canvas-patch.json",
43
- "patches/react-native-skia+3.7.1.patch",
43
+ "patches/react-native-skia+3.10.0.patch",
44
44
  "docs/UPSTREAM_NOTES.md",
45
45
  "!ios/tests",
46
46
  "!android/src/androidTest",
@@ -101,8 +101,8 @@
101
101
  "react-native": "0.86.2",
102
102
  "react-native-builder-bob": "^0.43.0",
103
103
  "react-native-reanimated": "4.5.3",
104
- "react-native-skia": "3.7.1",
105
- "react-native-webgpu": "0.17.0",
104
+ "react-native-skia": "3.10.0",
105
+ "react-native-webgpu": "0.21.3",
106
106
  "react-native-worklets": "0.11.3",
107
107
  "react-test-renderer": "19.2.3",
108
108
  "typescript": "^6.0.3"
@@ -111,8 +111,8 @@
111
111
  "react": ">=19",
112
112
  "react-native": ">=0.83 <0.87",
113
113
  "react-native-reanimated": "4.5.3",
114
- "react-native-skia": "3.7.1",
115
- "react-native-webgpu": "0.17.0",
114
+ "react-native-skia": "3.10.0",
115
+ "react-native-webgpu": "0.21.3",
116
116
  "react-native-worklets": "0.11.3"
117
117
  },
118
118
  "jest": {
@@ -0,0 +1,150 @@
1
+ --- a/node_modules/react-native-skia/cpp/rnskia/RNDawnContext.h
2
+ +++ b/node_modules/react-native-skia/cpp/rnskia/RNDawnContext.h
3
+ @@ -492,6 +492,44 @@
4
+ }
5
+ current->context->submit(syncToCpu);
6
+ return inserted;
7
+ + }
8
+ +
9
+ + // Checked only for surfaces that explicitly opt into this contract: like
10
+ + // submitRecording, but a recording that would not run throws instead of
11
+ + // being dropped. A valid empty Recording is accepted; Recorder::snap()
12
+ + // returns nullptr on failure.
13
+ + void submitRecordingChecked(
14
+ + skgpu::graphite::Recorder *recorder,
15
+ + skgpu::graphite::SyncToCpu syncToCpu = skgpu::graphite::SyncToCpu::kNo) {
16
+ + if (recorder == nullptr) {
17
+ + throw std::runtime_error(
18
+ + "Checked GPU submissions require a Graphite recorder");
19
+ + }
20
+ + // As in submitRecording: the snap compiles the pipelines the recording
21
+ + // needs, inline, GPU work the pipeline warmup stays behind (see
22
+ + // RNSkGpuActivity), as is the run.
23
+ + RNSkGpuActivity::Scope activity;
24
+ + auto recording = recorder->snap();
25
+ + if (recording == nullptr) {
26
+ + throw std::runtime_error("Cannot create checked Graphite recording");
27
+ + }
28
+ + std::lock_guard<std::mutex> lock(_mutex);
29
+ + auto *current = _current.load(std::memory_order_acquire);
30
+ + if (generationOf(recorder) != current->id ||
31
+ + current->lost.load(std::memory_order_acquire)) {
32
+ + throw std::runtime_error(
33
+ + "The GPU device was lost; a checked recording cannot run");
34
+ + }
35
+ + skgpu::graphite::InsertRecordingInfo info;
36
+ + info.fRecording = recording.get();
37
+ + auto status = current->context->insertRecording(info);
38
+ + if (!static_cast<bool>(status)) {
39
+ + throw std::runtime_error("Graphite rejected a checked recording: " +
40
+ + status.message());
41
+ + }
42
+ + if (!current->context->submit(syncToCpu)) {
43
+ + throw std::runtime_error("Graphite checked submission failed");
44
+ + }
45
+ }
46
+
47
+ /**
48
+ --- a/node_modules/react-native-skia/cpp/api/JsiSkSurface.h
49
+ +++ b/node_modules/react-native-skia/cpp/api/JsiSkSurface.h
50
+ @@ -52,6 +52,26 @@
51
+ }
52
+ }
53
+
54
+ + // Opt-in for consumers that release native buffers after GPU completion.
55
+ + // Other Skia surfaces preserve their existing flush/snapshot behavior.
56
+ + void enableCheckedSubmissions() {
57
+ + if (!getObject()->recorder()) {
58
+ + throw std::runtime_error(
59
+ + "Checked GPU submissions require a Graphite surface");
60
+ + }
61
+ + _checkedSubmissions = true;
62
+ + }
63
+ +
64
+ + void submitRecording(skgpu::graphite::Recorder *recorder,
65
+ + skgpu::graphite::SyncToCpu syncToCpu =
66
+ + skgpu::graphite::SyncToCpu::kNo) {
67
+ + if (_checkedSubmissions) {
68
+ + DawnContext::getInstance().submitRecordingChecked(recorder, syncToCpu);
69
+ + } else {
70
+ + DawnContext::getInstance().submitRecording(recorder, syncToCpu);
71
+ + }
72
+ + }
73
+ +
74
+ // TODO-API: Properties?
75
+ double width() { return static_cast<double>(getObject()->width()); }
76
+ double height() { return static_cast<double>(getObject()->height()); }
77
+ @@ -85,9 +105,8 @@
78
+ // only Graphite-backed surfaces need to snap and submit a recording. One
79
+ // made on a GPU device lost since submits nothing (see DawnContext).
80
+ if (auto *recorder = surface->recorder()) {
81
+ - DawnContext::getInstance().submitRecording(
82
+ - recorder, sync ? skgpu::graphite::SyncToCpu::kYes
83
+ - : skgpu::graphite::SyncToCpu::kNo);
84
+ + submitRecording(recorder, sync ? skgpu::graphite::SyncToCpu::kYes
85
+ + : skgpu::graphite::SyncToCpu::kNo);
86
+ }
87
+ }
88
+
89
+ @@ -120,10 +139,15 @@
90
+ } else {
91
+ image = surface->makeImageSnapshot();
92
+ }
93
+ + // A checked surface keeps the previously published image if snapshot
94
+ + // allocation failed.
95
+ + if (!image && _checkedSubmissions) {
96
+ + throw jsi::JSError(runtime, "Cannot allocate Skia surface snapshot");
97
+ + }
98
+ // A raster surface (e.g. Skia.Surface.Make) has no Graphite recorder; its
99
+ // snapshot is already a valid CPU image, so skip the recording submit.
100
+ if (auto *recorder = surface->recorder()) {
101
+ - DawnContext::getInstance().submitRecording(recorder);
102
+ + submitRecording(recorder);
103
+ }
104
+ if (count > 1 && arguments[1].isObject()) {
105
+ auto jsiImage = getJsiObject<JsiSkImage>(runtime, arguments[1]);
106
+ @@ -196,6 +220,8 @@
107
+ &JsiSkSurface::makeImageSnapshot);
108
+ installMethod(runtime, prototype, "asImage", &JsiSkSurface::asImage);
109
+ installMethod(runtime, prototype, "flush", &JsiSkSurface::flush);
110
+ + installMethod(runtime, prototype, "enableCheckedSubmissions",
111
+ + &JsiSkSurface::enableCheckedSubmissions);
112
+ }
113
+
114
+ protected:
115
+ @@ -222,6 +248,7 @@
116
+
117
+ std::shared_ptr<Dispatcher> _dispatcher;
118
+ std::shared_ptr<JsiSkCanvas> _canvas;
119
+ + bool _checkedSubmissions = false;
120
+ };
121
+
122
+ } // namespace RNSkia
123
+ --- a/node_modules/react-native-skia/cpp/api/JsiSkApi.h
124
+ +++ b/node_modules/react-native-skia/cpp/api/JsiSkApi.h
125
+ @@ -167,6 +167,16 @@
126
+ }
127
+ JSI_HOST_FUNCTION(Color) {
128
+ return JsiSkColor::createCtor()(runtime, thisValue, arguments, count);
129
+ + }
130
+ + // Private video-export cleanup. Only the calling thread's recorders are
131
+ + // trimmed; the shared Context cache and other threads' recorders are
132
+ + // untouched.
133
+ + JSI_HOST_FUNCTION(__rnskvTrimRecorderCache) {
134
+ + auto &context = DawnContext::getInstance();
135
+ + context.submitRecordingChecked(context.getRecorder(),
136
+ + skgpu::graphite::SyncToCpu::kYes);
137
+ + DawnContext::trimThreadRecorders(std::chrono::milliseconds(0), true);
138
+ + return jsi::Value::undefined();
139
+ }
140
+ JSI_HOST_FUNCTION(getNativeDevice) {
141
+ // Raw WGPUDevice pointer of the Graphite device, as a BigInt. Consumed by
142
+ @@ -267,6 +277,8 @@
143
+ }
144
+
145
+ static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
146
+ + installHostMethod(runtime, prototype, "__rnskvTrimRecorderCache",
147
+ + &JsiSkApi::__rnskvTrimRecorderCache);
148
+ installHostMethod(runtime, prototype, "getNativeDevice",
149
+ &JsiSkApi::getNativeDevice);
150
+ installGetterWithRuntime(runtime, prototype, "gpu", &JsiSkApi::getGpu);
@@ -1,289 +1,53 @@
1
1
  {
2
- "version": "3.7.1",
2
+ "version": "3.10.0",
3
3
  "files": {
4
- "cpp/api/JsiSkCanvas.h": {
5
- "before": "91e6f97f34bef620740fabf7a6bb516c462a18daa72afb46ef5a84d1cd4f3158",
6
- "after": "3198bed18bbdad6e61b0ef0d42fbc01c4f1792955af33eb14080a5329de2ff4d",
7
- "edits": [
8
- {
9
- "before": "#include <memory>\n#include <string>\n",
10
- "after": "#include <memory>\n#include <stdexcept>\n#include <string>\n"
11
- },
12
- {
13
- "before": " void drawPaint(std::shared_ptr<SkPaint> paint) { _canvas->drawPaint(*paint); }\n",
14
- "after": " void drawPaint(std::shared_ptr<SkPaint> paint) { getCanvas()->drawPaint(*paint); }\n"
15
- },
16
- {
17
- "before": " _canvas->drawLine(x1, y1, x2, y2, *paint);\n",
18
- "after": " getCanvas()->drawLine(x1, y1, x2, y2, *paint);\n"
19
- },
20
- {
21
- "before": " _canvas->drawRect(*rect, *paint);\n",
22
- "after": " getCanvas()->drawRect(*rect, *paint);\n"
23
- },
24
- {
25
- "before": " _canvas->drawImage(image, x, y, SkSamplingOptions(), paint.get());\n",
26
- "after": " getCanvas()->drawImage(image, x, y, SkSamplingOptions(), paint.get());\n"
27
- },
28
- {
29
- "before": " _canvas->drawImageRect(image, *src, *dest, SkSamplingOptions(), paint.get(),\n",
30
- "after": " getCanvas()->drawImageRect(image, *src, *dest, SkSamplingOptions(), paint.get(),\n"
31
- },
32
- {
33
- "before": " _canvas->drawImage(image, x, y, SkSamplingOptions({B, C}), paint.get());\n",
34
- "after": " getCanvas()->drawImage(image, x, y, SkSamplingOptions({B, C}), paint.get());\n"
35
- },
36
- {
37
- "before": " _canvas->drawImage(image, x, y, SkSamplingOptions(fm, mm), paint.get());\n",
38
- "after": " getCanvas()->drawImage(image, x, y, SkSamplingOptions(fm, mm), paint.get());\n"
39
- },
40
- {
41
- "before": " _canvas->drawImageNine(image.get(), center->round(), *dest, fm,\n",
42
- "after": " getCanvas()->drawImageNine(image.get(), center->round(), *dest, fm,\n"
43
- },
44
- {
45
- "before": " _canvas->drawImageRect(image.get(), *src, *dest, SkSamplingOptions({B, C}),\n",
46
- "after": " getCanvas()->drawImageRect(image.get(), *src, *dest, SkSamplingOptions({B, C}),\n"
47
- },
48
- {
49
- "before": " _canvas->drawImageRect(image.get(), *src, *dest, {filter, mipmap},\n",
50
- "after": " getCanvas()->drawImageRect(image.get(), *src, *dest, {filter, mipmap},\n"
51
- },
52
- {
53
- "before": " _canvas->drawCircle(cx, cy, radius, *paint);\n",
54
- "after": " getCanvas()->drawCircle(cx, cy, radius, *paint);\n"
55
- },
56
- {
57
- "before": " _canvas->drawArc(*oval, startAngle, sweepAngle, useCenter, *paint);\n",
58
- "after": " getCanvas()->drawArc(*oval, startAngle, sweepAngle, useCenter, *paint);\n"
59
- },
60
- {
61
- "before": " _canvas->drawRRect(*rect, *paint);\n",
62
- "after": " getCanvas()->drawRRect(*rect, *paint);\n"
63
- },
64
- {
65
- "before": " _canvas->drawDRRect(*outer, *inner, *paint);\n",
66
- "after": " getCanvas()->drawDRRect(*outer, *inner, *paint);\n"
67
- },
68
- {
69
- "before": " _canvas->drawOval(*rect, *paint);\n",
70
- "after": " getCanvas()->drawOval(*rect, *paint);\n"
71
- },
72
- {
73
- "before": " void restoreToCount(double c) { _canvas->restoreToCount(c); }\n",
74
- "after": " void restoreToCount(double c) { getCanvas()->restoreToCount(c); }\n"
75
- },
76
- {
77
- "before": " int getSaveCount() { return static_cast<int>(_canvas->getSaveCount()); }\n",
78
- "after": " int getSaveCount() { return static_cast<int>(getCanvas()->getSaveCount()); }\n"
79
- },
80
- {
81
- "before": " _canvas->getTotalMatrix());\n",
82
- "after": " getCanvas()->getTotalMatrix());\n"
83
- },
84
- {
85
- "before": " _canvas->drawPoints(static_cast<SkCanvas::PointMode>(pointMode), p, *paint);\n",
86
- "after": " getCanvas()->drawPoints(static_cast<SkCanvas::PointMode>(pointMode), p, *paint);\n"
87
- },
88
- {
89
- "before": " _canvas->drawVertices(vertices, toBlendMode(blendMode), *paint);\n",
90
- "after": " getCanvas()->drawVertices(vertices, toBlendMode(blendMode), *paint);\n"
91
- },
92
- {
93
- "before": " _canvas->drawPatch(cubics.data(), colors.empty() ? nullptr : colors.data(),\n",
94
- "after": " getCanvas()->drawPatch(cubics.data(), colors.empty() ? nullptr : colors.data(),\n"
95
- },
96
- {
97
- "before": " _canvas->drawPath(path->snapshot(), *paint);\n",
98
- "after": " getCanvas()->drawPath(path->snapshot(), *paint);\n"
99
- },
100
- {
101
- "before": " _canvas->drawSimpleText(text, strlen(text), SkTextEncoding::kUTF8, x, y,\n",
102
- "after": " getCanvas()->drawSimpleText(text, strlen(text), SkTextEncoding::kUTF8, x, y,\n"
103
- },
104
- {
105
- "before": " _canvas->drawTextBlob(blob, x, y, *paint);\n",
106
- "after": " getCanvas()->drawTextBlob(blob, x, y, *paint);\n"
107
- },
108
- {
109
- "before": " _canvas->drawGlyphs(g, p, origin, *font, *paint);\n",
110
- "after": " getCanvas()->drawGlyphs(g, p, origin, *font, *paint);\n"
111
- },
112
- {
113
- "before": " auto canvasSize = _canvas->getBaseLayerSize();\n",
114
- "after": " auto canvasSize = getCanvas()->getBaseLayerSize();\n"
115
- },
116
- {
117
- "before": " svgdom->render(_canvas);\n",
118
- "after": " svgdom->render(getCanvas());\n"
119
- },
120
- {
121
- "before": " _canvas->clipPath(path->snapshot(), static_cast<SkClipOp>(op), doAntiAlias);\n",
122
- "after": " getCanvas()->clipPath(path->snapshot(), static_cast<SkClipOp>(op), doAntiAlias);\n"
123
- },
124
- {
125
- "before": " _canvas->clipRect(*rect, static_cast<SkClipOp>(op), doAntiAlias);\n",
126
- "after": " getCanvas()->clipRect(*rect, static_cast<SkClipOp>(op), doAntiAlias);\n"
127
- },
128
- {
129
- "before": " _canvas->clipRRect(*rrect, static_cast<SkClipOp>(op), doAntiAlias);\n",
130
- "after": " getCanvas()->clipRRect(*rrect, static_cast<SkClipOp>(op), doAntiAlias);\n"
131
- },
132
- {
133
- "before": " int save() { return _canvas->save(); }\n",
134
- "after": " int save() { return getCanvas()->save(); }\n"
135
- },
136
- {
137
- "before": " return jsi::Value(_canvas->saveLayer(SkCanvas::SaveLayerRec(\n",
138
- "after": " return jsi::Value(getCanvas()->saveLayer(SkCanvas::SaveLayerRec(\n"
139
- },
140
- {
141
- "before": " void restore() { _canvas->restore(); }\n",
142
- "after": " void restore() { getCanvas()->restore(); }\n"
143
- },
144
- {
145
- "before": " _canvas->rotate(degrees, rx, ry);\n",
146
- "after": " getCanvas()->rotate(degrees, rx, ry);\n"
147
- },
148
- {
149
- "before": " void translate(double dx, double dy) { _canvas->translate(dx, dy); }\n",
150
- "after": " void translate(double dx, double dy) { getCanvas()->translate(dx, dy); }\n"
151
- },
152
- {
153
- "before": " void scale(double sx, double sy) { _canvas->scale(sx, sy); }\n",
154
- "after": " void scale(double sx, double sy) { getCanvas()->scale(sx, sy); }\n"
155
- },
156
- {
157
- "before": " void skew(double sx, double sy) { _canvas->skew(sx, sy); }\n",
158
- "after": " void skew(double sx, double sy) { getCanvas()->skew(sx, sy); }\n"
159
- },
160
- {
161
- "before": " _canvas->drawColor(cl, toBlendMode(*mode));\n",
162
- "after": " getCanvas()->drawColor(cl, toBlendMode(*mode));\n"
163
- },
164
- {
165
- "before": " _canvas->drawColor(cl);\n",
166
- "after": " getCanvas()->drawColor(cl);\n"
167
- },
168
- {
169
- "before": " void clear(JsiColor cl) { _canvas->clear(cl); }\n",
170
- "after": " void clear(JsiColor cl) { getCanvas()->clear(cl); }\n"
171
- },
172
- {
173
- "before": " void concat(std::shared_ptr<SkMatrix> matrix) { _canvas->concat(*matrix); }\n",
174
- "after": " void concat(std::shared_ptr<SkMatrix> matrix) { getCanvas()->concat(*matrix); }\n"
175
- },
176
- {
177
- "before": " void drawPicture(sk_sp<SkPicture> picture) { _canvas->drawPicture(picture); }\n",
178
- "after": " void drawPicture(sk_sp<SkPicture> picture) { getCanvas()->drawPicture(picture); }\n"
179
- },
180
- {
181
- "before": " _canvas->drawAtlas(atlas.get(), x, t, c, blendMode, sampling, nullptr,\n",
182
- "after": " getCanvas()->drawAtlas(atlas.get(), x, t, c, blendMode, sampling, nullptr,\n"
183
- },
184
- {
185
- "before": " JSI_HOST_FUNCTION(readPixels) {\n auto srcX = static_cast<int>(arguments[0].asNumber());\n",
186
- "after": " JSI_HOST_FUNCTION(readPixels) {\n getCanvas(); // Validate before allocating a readback buffer.\n auto srcX = static_cast<int>(arguments[0].asNumber());\n"
187
- },
188
- {
189
- "before": " if (!_canvas->readPixels(*info, bfrPtr, bytesPerRow, srcX, srcY)) {\n",
190
- "after": " if (!getCanvas()->readPixels(*info, bfrPtr, bytesPerRow, srcX, srcY)) {\n"
191
- },
192
- {
193
- "before": " static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {\n installMethod(runtime, prototype, \"drawPaint\", &JsiSkCanvas::drawPaint);\n",
194
- "after": " static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {\n installMethod(runtime, prototype, \"dispose\", &JsiSkCanvas::dispose);\n installMethod(runtime, prototype, \"drawPaint\", &JsiSkCanvas::drawPaint);\n"
195
- },
196
- {
197
- "before": " SkCanvas *getCanvas() { return _canvas; }\n",
198
- "after": " SkCanvas *getCanvas() {\n if (!_canvas) throw std::runtime_error(\"Canvas has been disposed\");\n return _canvas;\n }\n // Release the owning surface deterministically. The caller must\n // submit/wait for its recording before disposal. Off the creating\n // thread, the surface is handed to that thread's dispatcher.\n void dispose() {\n _canvas = nullptr;\n if (_dispatcher) {\n _dispatcher->release(std::move(_surface));\n } else {\n _surface.reset();\n }\n }\n"
199
- },
200
- {
201
- "before": " SkCanvas *_canvas;\n",
202
- "after": " SkCanvas *_canvas = nullptr;\n"
203
- }
204
- ]
205
- },
206
- "src/skia/types/Canvas.ts": {
207
- "before": "224e207ad4e9e5bee1efd2e1fb07b61a2261b14f498314cb5657f3201bd99791",
208
- "after": "e0ca74f4c4ab9d3647a48751f3243d38f64d55fcb95af725be1b88eace052027",
209
- "edits": [
210
- {
211
- "before": "export interface SkCanvas {\n /**\n",
212
- "after": "export interface SkCanvas {\n /** Release the owning surface after flushing; idempotent. */\n dispose(): void;\n /**\n"
213
- }
214
- ]
215
- },
216
- "lib/typescript/src/skia/types/Canvas.d.ts": {
217
- "before": "7cc41d6183212cfd4f3d7d0065a7b360aabf98fae20565428e876f5da8c1bb15",
218
- "after": "972901e64599c40d54db73a49881323bca457e5b87c07c30a0016a5a89339d99",
4
+ "cpp/rnskia/RNDawnContext.h": {
5
+ "before": "ed26c1390ac4912089b354651c970801d42db7318ed40c51c19357b2252c52d4",
6
+ "after": "bceaaf3cabcde96d4d3843210d74e2de23991631e85427ec34c4b481ff30e5a0",
219
7
  "edits": [
220
8
  {
221
- "before": "export interface SkCanvas {\n /**\n",
222
- "after": "export interface SkCanvas {\n /** Release the owning surface after flushing; idempotent. */\n dispose(): void;\n /**\n"
9
+ "before": " current->context->submit(syncToCpu);\n return inserted;\n }\n",
10
+ "after": " current->context->submit(syncToCpu);\n return inserted;\n }\n\n // Checked only for surfaces that explicitly opt into this contract: like\n // submitRecording, but a recording that would not run throws instead of\n // being dropped. A valid empty Recording is accepted; Recorder::snap()\n // returns nullptr on failure.\n void submitRecordingChecked(\n skgpu::graphite::Recorder *recorder,\n skgpu::graphite::SyncToCpu syncToCpu = skgpu::graphite::SyncToCpu::kNo) {\n if (recorder == nullptr) {\n throw std::runtime_error(\n \"Checked GPU submissions require a Graphite recorder\");\n }\n // As in submitRecording: the snap compiles the pipelines the recording\n // needs, inline, GPU work the pipeline warmup stays behind (see\n // RNSkGpuActivity), as is the run.\n RNSkGpuActivity::Scope activity;\n auto recording = recorder->snap();\n if (recording == nullptr) {\n throw std::runtime_error(\"Cannot create checked Graphite recording\");\n }\n std::lock_guard<std::mutex> lock(_mutex);\n auto *current = _current.load(std::memory_order_acquire);\n if (generationOf(recorder) != current->id ||\n current->lost.load(std::memory_order_acquire)) {\n throw std::runtime_error(\n \"The GPU device was lost; a checked recording cannot run\");\n }\n skgpu::graphite::InsertRecordingInfo info;\n info.fRecording = recording.get();\n auto status = current->context->insertRecording(info);\n if (!static_cast<bool>(status)) {\n throw std::runtime_error(\"Graphite rejected a checked recording: \" +\n status.message());\n }\n if (!current->context->submit(syncToCpu)) {\n throw std::runtime_error(\"Graphite checked submission failed\");\n }\n }\n"
223
11
  }
224
12
  ]
225
13
  },
226
14
  "cpp/api/JsiSkSurface.h": {
227
- "before": "de3226499673a8d731167a0feb299257628999ce5d4810217e962062f625302a",
228
- "after": "d78ce02da437cd1b8df03ecf8ac659b9d25c30018ff74733fb2e5185fc078bf2",
15
+ "before": "24797c709ae34c0082e6d0e5efc5a136d82ddc452f4a319ce22479f8303130cd",
16
+ "after": "4c8ab5d01676a1b8b8baed0cf67d4bbe8a8dc437eab124f629f6f1f5e07f6796",
229
17
  "edits": [
230
18
  {
231
- "before": "\n ~JsiSkSurface() override {\n",
232
- "after": "\n // Opt-in for consumers that release native buffers after GPU completion.\n // Other Skia surfaces preserve their existing flush/snapshot behavior.\n void enableCheckedSubmissions() {\n if (!getObject()->recorder()) {\n throw std::runtime_error(\"Checked GPU submissions require a Graphite surface\");\n }\n _checkedSubmissions = true;\n }\n\n void submitRecording(skgpu::graphite::Recording *recording,\n skgpu::graphite::SyncToCpu syncToCpu =\n skgpu::graphite::SyncToCpu::kNo) {\n if (_checkedSubmissions) {\n DawnContext::getInstance().submitRecordingChecked(recording, syncToCpu);\n } else {\n DawnContext::getInstance().submitRecording(recording, syncToCpu);\n }\n }\n\n ~JsiSkSurface() override {\n"
233
- },
234
- {
235
- "before": " DawnContext::getInstance().submitRecording(\n",
236
- "after": " submitRecording(\n"
19
+ "before": " // TODO-API: Properties?\n",
20
+ "after": " // Opt-in for consumers that release native buffers after GPU completion.\n // Other Skia surfaces preserve their existing flush/snapshot behavior.\n void enableCheckedSubmissions() {\n if (!getObject()->recorder()) {\n throw std::runtime_error(\n \"Checked GPU submissions require a Graphite surface\");\n }\n _checkedSubmissions = true;\n }\n\n void submitRecording(skgpu::graphite::Recorder *recorder,\n skgpu::graphite::SyncToCpu syncToCpu =\n skgpu::graphite::SyncToCpu::kNo) {\n if (_checkedSubmissions) {\n DawnContext::getInstance().submitRecordingChecked(recorder, syncToCpu);\n } else {\n DawnContext::getInstance().submitRecording(recorder, syncToCpu);\n }\n }\n\n // TODO-API: Properties?\n"
237
21
  },
238
22
  {
239
- "before": " }\n // A raster surface (e.g. Skia.Surface.Make) has no Graphite recorder; its\n",
240
- "after": " }\n // Preserve the previously published image if snapshot allocation failed.\n if (!image) {\n throw jsi::JSError(runtime, \"Cannot allocate Skia surface snapshot\");\n }\n // A raster surface (e.g. Skia.Surface.Make) has no Graphite recorder; its\n"
23
+ "before": " DawnContext::getInstance().submitRecording(\n recorder, sync ? skgpu::graphite::SyncToCpu::kYes\n : skgpu::graphite::SyncToCpu::kNo);\n",
24
+ "after": " submitRecording(recorder, sync ? skgpu::graphite::SyncToCpu::kYes\n : skgpu::graphite::SyncToCpu::kNo);\n"
241
25
  },
242
26
  {
243
- "before": " DawnContext::getInstance().submitRecording(recording.get());\n",
244
- "after": " submitRecording(recording.get());\n"
27
+ "before": " // A raster surface (e.g. Skia.Surface.Make) has no Graphite recorder; its\n // snapshot is already a valid CPU image, so skip the recording submit.\n if (auto *recorder = surface->recorder()) {\n DawnContext::getInstance().submitRecording(recorder);\n }\n",
28
+ "after": " // A checked surface keeps the previously published image if snapshot\n // allocation failed.\n if (!image && _checkedSubmissions) {\n throw jsi::JSError(runtime, \"Cannot allocate Skia surface snapshot\");\n }\n // A raster surface (e.g. Skia.Surface.Make) has no Graphite recorder; its\n // snapshot is already a valid CPU image, so skip the recording submit.\n if (auto *recorder = surface->recorder()) {\n submitRecording(recorder);\n }\n"
245
29
  },
246
30
  {
247
31
  "before": " installMethod(runtime, prototype, \"flush\", &JsiSkSurface::flush);\n }\n",
248
32
  "after": " installMethod(runtime, prototype, \"flush\", &JsiSkSurface::flush);\n installMethod(runtime, prototype, \"enableCheckedSubmissions\",\n &JsiSkSurface::enableCheckedSubmissions);\n }\n"
249
33
  },
250
34
  {
251
- "before": " std::shared_ptr<Dispatcher> _dispatcher;\n};\n",
252
- "after": " std::shared_ptr<Dispatcher> _dispatcher;\n bool _checkedSubmissions = false;\n};\n"
253
- }
254
- ]
255
- },
256
- "cpp/rnskia/RNDawnContext.h": {
257
- "before": "f1be623839d9716d1ae7ce9bc66a02055989c9b69acbfec49d607338f711d0e7",
258
- "after": "c0de4e4b37271a77a429c78e646c2e8a7172b6a46e713ff01f31e0782d05738a",
259
- "edits": [
260
- {
261
- "before": " fGraphiteContext->submit(syncToCpu);\n }\n",
262
- "after": " fGraphiteContext->submit(syncToCpu);\n }\n\n // Checked only for surfaces that explicitly opt into this contract. A valid\n // empty Recording is accepted; Recorder::snap() returns nullptr on failure.\n void submitRecordingChecked(\n skgpu::graphite::Recording *recording,\n skgpu::graphite::SyncToCpu syncToCpu = skgpu::graphite::SyncToCpu::kNo) {\n std::lock_guard<std::mutex> lock(_mutex);\n if (!recording) {\n throw std::runtime_error(\"Cannot create checked Graphite recording\");\n }\n skgpu::graphite::InsertRecordingInfo info;\n info.fRecording = recording;\n auto status = fGraphiteContext->insertRecording(info);\n if (!static_cast<bool>(status)) {\n throw std::runtime_error(\"Graphite rejected a checked recording: \" +\n status.message());\n }\n if (!fGraphiteContext->submit(syncToCpu)) {\n throw std::runtime_error(\"Graphite checked submission failed\");\n }\n }\n"
263
- }
264
- ]
265
- },
266
- "cpp/rnskia/RNImageProvider.h": {
267
- "before": "8b9a552bfcf088058eb06366ba78984b7e54d6ae0442633ecbc0cdd726205fc2",
268
- "after": "f6c329e5d43fcecfcecc95c8516df159e1bfb68582d27ce92b67facfcd84b4eb",
269
- "edits": [
270
- {
271
- "before": " static sk_sp<ImageProvider> Make() { return sk_ref_sp(new ImageProvider); }",
272
- "after": " static sk_sp<ImageProvider> Make() { return sk_make_sp<ImageProvider>(); }"
35
+ "before": " std::shared_ptr<JsiSkCanvas> _canvas;\n};\n",
36
+ "after": " std::shared_ptr<JsiSkCanvas> _canvas;\n bool _checkedSubmissions = false;\n};\n"
273
37
  }
274
38
  ]
275
39
  },
276
40
  "cpp/api/JsiSkApi.h": {
277
- "before": "9abd4dbaa14c13f3ce80a5c475b8ad51f08400fe6b2c119781032486ae72f7eb",
278
- "after": "e3e8358e0ecf91b4a0547cdd4467400b14d5d0963402692648d6d0635864a769",
41
+ "before": "07846e9776f57bd2638e2e3092931e5a1324d66710693a2f7e550f648fe13934",
42
+ "after": "5c0bf4231d658366c8a902221238c3a244b6444cf407468f80c1cd1718b2be16",
279
43
  "edits": [
280
44
  {
281
- "before": " JSI_HOST_FUNCTION(getNativeDevice) {",
282
- "after": " // Private video-export cleanup. Only the calling thread's Recorder cache\n // is trimmed; the shared Context cache and other Recorders are untouched.\n JSI_HOST_FUNCTION(__rnskvTrimRecorderCache) {\n auto &context = DawnContext::getInstance();\n auto *recorder = context.getRecorder();\n {\n auto recording = recorder->snap();\n context.submitRecordingChecked(recording.get(),\n skgpu::graphite::SyncToCpu::kYes);\n }\n recorder->freeGpuResources();\n return jsi::Value::undefined();\n }\n JSI_HOST_FUNCTION(getNativeDevice) {"
45
+ "before": " JSI_HOST_FUNCTION(getNativeDevice) {\n",
46
+ "after": " // Private video-export cleanup. Only the calling thread's recorders are\n // trimmed; the shared Context cache and other threads' recorders are\n // untouched.\n JSI_HOST_FUNCTION(__rnskvTrimRecorderCache) {\n auto &context = DawnContext::getInstance();\n context.submitRecordingChecked(context.getRecorder(),\n skgpu::graphite::SyncToCpu::kYes);\n DawnContext::trimThreadRecorders(std::chrono::milliseconds(0), true);\n return jsi::Value::undefined();\n }\n JSI_HOST_FUNCTION(getNativeDevice) {\n"
283
47
  },
284
48
  {
285
- "before": " static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {",
286
- "after": " static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {\n installHostMethod(runtime, prototype, \"__rnskvTrimRecorderCache\",\n &JsiSkApi::__rnskvTrimRecorderCache);"
49
+ "before": " static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {\n",
50
+ "after": " static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {\n installHostMethod(runtime, prototype, \"__rnskvTrimRecorderCache\",\n &JsiSkApi::__rnskvTrimRecorderCache);\n"
287
51
  }
288
52
  ]
289
53
  }
package/src/canvas.ts CHANGED
@@ -12,11 +12,7 @@ export const getVideoCanvas = (surface: SkSurface): SkCanvas => {
12
12
  );
13
13
  }
14
14
  checked.enableCheckedSubmissions();
15
- const canvas = surface.getCanvas();
16
- if (typeof canvas.dispose !== "function") {
17
- throw new Error(
18
- "Skia canvas lifecycle patch is missing from the native binary. Apply the patch and rebuild the native app.",
19
- );
20
- }
21
- return canvas;
15
+ // Owned by the surface: its dispose() detaches the canvas, and drawing on
16
+ // it throws from then on.
17
+ return surface.getCanvas();
22
18
  };
@@ -22,7 +22,7 @@ import {
22
22
  blitNv12Frame,
23
23
  blitYuvFrame,
24
24
  releaseYuvImports,
25
- yuvFrameOrigin,
25
+ yuvFrameRegion,
26
26
  } from "./yuvBlit";
27
27
  import type { YuvImport } from "./yuvBlit";
28
28
 
@@ -174,9 +174,9 @@ const importYuvExportFrame = (
174
174
  entry.nativeFrame.height !== frame.height)
175
175
  )
176
176
  throw new Error("Native video buffer dimensions mismatch");
177
- const origin = nv12
178
- ? { x: 0, y: 0 }
179
- : yuvFrameOrigin(source.region!, rotation, entry.nativeFrame, frame);
177
+ const region = nv12
178
+ ? null
179
+ : yuvFrameRegion(source.region!, rotation, entry.nativeFrame);
180
180
  const device = getVideoGpuDevice();
181
181
  target = imports.textures.get(producer);
182
182
  if (
@@ -220,12 +220,10 @@ const importYuvExportFrame = (
220
220
  producer,
221
221
  entry.nativeFrame,
222
222
  rotation,
223
- origin,
224
- frame.width,
225
- frame.height,
223
+ region!,
226
224
  target.texture,
227
225
  );
228
- entry.image = Skia.Image.MakeImageFromGPUTexture(target.texture);
226
+ entry.image = Skia.Image.MakeFromGPUTexture(target.texture);
229
227
  if (!entry.image) throw new Error("Cannot wrap the decoded frame");
230
228
  return entry;
231
229
  } catch (error) {
@@ -292,7 +290,7 @@ export const importExportFrame = (
292
290
  entry.texture = entry.memory.createTexture();
293
291
  entry.memory.beginAccess(entry.texture, true);
294
292
  entry.accessing = true;
295
- entry.image = Skia.Image.MakeImageFromGPUTexture(entry.texture);
293
+ entry.image = Skia.Image.MakeFromGPUTexture(entry.texture);
296
294
  if (!entry.image) throw new Error("Cannot wrap the decoded frame");
297
295
  return entry;
298
296
  } catch (error) {
@@ -329,8 +327,9 @@ export const releaseExportFrames = (imports: ExportFrameImports): void => {
329
327
 
330
328
  /** The export counterpart of ownVideoFrames on iOS: decoded frames are drawn
331
329
  * in place, and a producer's previous frame is released once its next one is
332
- * imported, since every exported frame ends with a checked GPU wait. Other
333
- * frames, or every frame after a first failed import, use `fallback`. */
330
+ * imported, since the export waits for the GPU to complete a frame before it
331
+ * imports the next one. Other frames, or every frame after a first failed
332
+ * import, use `fallback`. */
334
333
  export const ownExportFrames = (
335
334
  imports: ExportFrameImports,
336
335
  decoded: Record<string, VideoFrame>,