Skip to content

Commit 9d231af

Browse files
huntiemeta-codesync[bot]
authored andcommitted
Add pixel diffing to RCTFrameTimingsObserver (#56043)
Summary: Pull Request resolved: #56043 Use pixel hash comparison to avoid encoding duplicate screenshots on iOS, reducing overhead when frames don't change. Matches Android behaviour. - Samples every 64th pixel with a fast FNV-1a hash (~0.1ms on main thread). - Skips JPEG encoding and CDP transport entirely for unchanged frames — the dominant per-frame cost. **Why this approach** Attempted `CATransaction` completion blocks, however these fire on commits that produce no visual change. No other public API on iOS. Changelog: [Internal] Reviewed By: sbuggay Differential Revision: D95981881 fbshipit-source-id: 76f492f0ceb6b3efea3fce822036ed6cb6067b83
1 parent e99f8f9 commit 9d231af

1 file changed

Lines changed: 21 additions & 0 deletions

File tree

packages/react-native/React/DevSupport/RCTFrameTimingsObserver.mm

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ @implementation RCTFrameTimingsObserver {
3131
uint64_t _frameCounter;
3232
dispatch_queue_t _encodingQueue;
3333
std::atomic<bool> _running;
34+
uint64_t _lastScreenshotHash;
3435
}
3536

3637
- (instancetype)initWithScreenshotsEnabled:(BOOL)screenshotsEnabled callback:(RCTFrameTimingCallback)callback
@@ -41,6 +42,7 @@ - (instancetype)initWithScreenshotsEnabled:(BOOL)screenshotsEnabled callback:(RC
4142
_frameCounter = 0;
4243
_encodingQueue = dispatch_queue_create("com.facebook.react.frame-timings-observer", DISPATCH_QUEUE_SERIAL);
4344
_running.store(false);
45+
_lastScreenshotHash = 0;
4446
}
4547
return self;
4648
}
@@ -49,6 +51,7 @@ - (void)start
4951
{
5052
_running.store(true, std::memory_order_relaxed);
5153
_frameCounter = 0;
54+
_lastScreenshotHash = 0;
5255

5356
// Emit an initial frame timing to ensure at least one frame is captured at the
5457
// start of tracing, even if no UI changes occur.
@@ -127,6 +130,24 @@ - (void)_captureScreenshotWithCompletion:(void (^)(std::optional<std::vector<uin
127130
[rootView drawViewHierarchyInRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height) afterScreenUpdates:NO];
128131
}];
129132

133+
// Skip duplicate frames via sampled FNV-1a pixel hash
134+
CGImageRef cgImage = image.CGImage;
135+
CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));
136+
uint64_t hash = 0xcbf29ce484222325ULL;
137+
const uint8_t *ptr = CFDataGetBytePtr(pixelData);
138+
CFIndex length = CFDataGetLength(pixelData);
139+
// Use prime stride to prevent row alignment on power-of-2 pixel widths
140+
for (CFIndex i = 0; i < length; i += 67) {
141+
hash ^= ptr[i];
142+
hash *= 0x100000001b3ULL;
143+
}
144+
CFRelease(pixelData);
145+
146+
if (hash == _lastScreenshotHash) {
147+
return;
148+
}
149+
_lastScreenshotHash = hash;
150+
130151
dispatch_async(_encodingQueue, ^{
131152
if (!self->_running.load(std::memory_order_relaxed)) {
132153
return;

0 commit comments

Comments
 (0)