Optimize frame capture performance and fix IOSurface alignment

- Add frame skip logic to prevent queue buildup and reduce lock contention
- Serialize operation queue to prevent concurrent frame processing
- Align IOSurface bytes per row to 16 bytes (required for IOSurfaceAccelerator)
- Only create renderServerSurface when scaling is needed, avoiding unnecessary allocations
- Support flexible resolution: width-only, height-only, or both dimensions
- Pass native dimensions to FrameUpdater for proper scaling decisions
- Enable 60 FPS limit on CADisplayLink
- Add IOSurface lock option constants for future use

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mousen
2025-12-08 06:22:17 +05:00
parent 297e88fa13
commit 404b9ace35
4 changed files with 127 additions and 61 deletions

View File

@@ -11,6 +11,8 @@
staticBuffer:(IOSurfaceRef)staticBuffer
width:(size_t)width
height:(size_t)height
nativeWidth:(size_t)nativeWidth
nativeHeight:(size_t)nativeHeight
useCADisplayLink:(BOOL)useCADisplayLink
renderServerSurface:(IOSurfaceRef)renderServerSurface
captureModeBlock:(CaptureMode(^)(void))captureModeBlock;

View File

@@ -15,10 +15,12 @@
IOSurfaceRef _staticBuffer;
size_t _width;
size_t _height;
size_t _nativeWidth;
size_t _nativeHeight;
BOOL _useCADisplayLink;
// Dual capture mode support
IOSurfaceRef _renderServerSurface;
IOSurfaceRef _renderServerSurface; // Separate surface for CARenderServer (avoids framebuffer locking)
CaptureMode(^_captureModeBlock)(void);
}
@@ -28,11 +30,14 @@
staticBuffer:(IOSurfaceRef)staticBuffer
width:(size_t)width
height:(size_t)height
nativeWidth:(size_t)nativeWidth
nativeHeight:(size_t)nativeHeight
useCADisplayLink:(BOOL)useCADisplayLink
renderServerSurface:(IOSurfaceRef)renderServerSurface
captureModeBlock:(CaptureMode(^)(void))captureModeBlock {
if ((self = [super init])) {
_q = [[NSOperationQueue alloc] init];
_q.maxConcurrentOperationCount = 1; // Serialize to prevent frame queue buildup
_updatingFrames = NO;
_lastUpdatedSeed = 0;
_updateFrameTimer = nil;
@@ -43,6 +48,8 @@
_staticBuffer = staticBuffer;
_width = width;
_height = height;
_nativeWidth = nativeWidth;
_nativeHeight = nativeHeight;
_useCADisplayLink = useCADisplayLink;
_renderServerSurface = renderServerSurface;
@@ -57,6 +64,12 @@
[self stopFrameLoop];
return;
}
// Skip if queue is busy to prevent frame buildup and reduce lock contention
if (_q.operationCount > 0) {
return;
}
bool updateFrame = true;
if (!_useCADisplayLink) {
bool updateFrame = false;
@@ -72,14 +85,22 @@
[_q addOperationWithBlock: ^{
// Get current capture mode
CaptureMode mode = _captureModeBlock ? _captureModeBlock() : CaptureModeCARenderServer;
BOOL needsScaling = (_width != _nativeWidth || _height != _nativeHeight);
if (mode == CaptureModeIOMobileFramebuffer) {
// Legacy mode: Use IOMobileFramebuffer surface transfer (shows passwords)
IOSurfaceAcceleratorTransferSurface(_accelerator, _screenSurface, _staticBuffer, NULL, NULL, NULL, NULL);
} else {
// Default mode: Use CARenderServer (faster, no secure fields)
// Uses separate surface to avoid touching the live framebuffer
if (needsScaling && _renderServerSurface) {
// Capture to our own surface at native resolution, then scale
CARenderServerRenderDisplay(0, CFSTR("LCD"), _renderServerSurface, 0, 0);
IOSurfaceAcceleratorTransferSurface(_accelerator, _renderServerSurface, _staticBuffer, NULL, NULL, NULL, NULL);
} else {
// No scaling needed - capture directly to output buffer
CARenderServerRenderDisplay(0, CFSTR("LCD"), _staticBuffer, 0, 0);
}
}
rfbMarkRectAsModified(_rfbScreenInfo, 0, 0, _width, _height);
@@ -104,12 +125,13 @@
if (_useCADisplayLink) {
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_updateFrame)];
// displayLink.preferredFramesPerSecond = 60; // Adjust as needed
displayLink.preferredFramesPerSecond = 60; // Limit to 60 FPS
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
_updateFrameTimer = (NSTimer *)displayLink;
} else {
dispatch_async(dispatch_get_main_queue(), ^(void){
_updateFrameTimer = [NSTimer scheduledTimerWithTimeInterval:1/400 target:self selector:@selector(_updateFrame) userInfo:nil repeats:YES];
// Reduced from 1/400 (400 Hz) to 1/60 (60 Hz) to reduce overhead
_updateFrameTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/400.0 target:self selector:@selector(_updateFrame) userInfo:nil repeats:YES];
});
}
}

View File

@@ -26,6 +26,11 @@ typedef io_service_t IOMobileFramebufferService;
extern void IOSurfaceFlushProcessorCaches(IOSurfaceRef buffer);
extern int IOSurfaceLock(IOSurfaceRef surface, uint32_t options, uint32_t *seed);
extern int IOSurfaceUnlock(IOSurfaceRef surface, uint32_t options, uint32_t *seed);
// IOSurface lock options
#define kIOSurfaceLockReadOnly 0x00000001
#define kIOSurfaceLockAvoidSync 0x00000002
extern Boolean IOSurfaceIsInUse(IOSurfaceRef buffer);
extern CFMutableDictionaryRef IOServiceMatching(const char *name);
extern const mach_port_t kIOMasterPortDefault;

View File

@@ -32,7 +32,7 @@
// Dual capture mode support
CaptureMode _captureMode;
NSTimer *_legacyModeTimer;
IOSurfaceRef _renderServerSurface;
IOSurfaceRef _renderServerSurface; // Separate surface for CARenderServer (not the live framebuffer)
}
+(void)load {
@@ -117,27 +117,39 @@
}
-(void)_adjustResolutionForAspectRatio {
// Only adjust if custom preferences are set
if (_prefsWidth == 0 || _prefsHeight == 0) {
NSLog(@"[ScreenDumpVNC] No custom dimensions set, using native resolution");
return;
}
size_t adjustedWidth, adjustedHeight;
// Case 1: No custom dimensions - use native resolution (still need alignment)
if (_prefsWidth == 0 && _prefsHeight == 0) {
NSLog(@"[ScreenDumpVNC] No custom dimensions set, using native resolution");
adjustedWidth = _nativeWidth;
adjustedHeight = _nativeHeight;
}
// Case 2: Only width specified - calculate height from aspect ratio
else if (_prefsWidth > 0 && _prefsHeight == 0) {
NSLog(@"[ScreenDumpVNC] Custom width %d specified, calculating height from aspect ratio", _prefsWidth);
adjustedWidth = _prefsWidth;
adjustedHeight = (size_t)floor((double)_prefsWidth / _displayAspectRatio);
}
// Case 3: Only height specified - calculate width from aspect ratio
else if (_prefsWidth == 0 && _prefsHeight > 0) {
NSLog(@"[ScreenDumpVNC] Custom height %d specified, calculating width from aspect ratio", _prefsHeight);
adjustedWidth = (size_t)floor((double)_prefsHeight * _displayAspectRatio);
adjustedHeight = _prefsHeight;
}
// Case 4: Both specified - fit within constraints maintaining aspect ratio
else {
// Validate custom preferences
if (_prefsWidth <= 0 || _prefsHeight <= 0) {
NSLog(@"[ScreenDumpVNC] ERROR: Invalid custom dimensions (%dx%d), ignoring",
NSLog(@"[ScreenDumpVNC] ERROR: Invalid custom dimensions (%dx%d), using native",
_prefsWidth, _prefsHeight);
_prefsWidth = 0;
_prefsHeight = 0;
return;
}
adjustedWidth = _nativeWidth;
adjustedHeight = _nativeHeight;
} else {
// Calculate ideal dimensions maintaining aspect ratio
double idealHeight = (double)_prefsWidth / _displayAspectRatio;
double idealWidth = (double)_prefsHeight * _displayAspectRatio;
size_t adjustedWidth, adjustedHeight;
// Choose option that maximizes pixels while staying within BOTH constraints
if (idealHeight <= (double)_prefsHeight) {
// Use full width, adjust height down
@@ -148,31 +160,36 @@
adjustedWidth = (size_t)floor(idealWidth);
adjustedHeight = _prefsHeight;
}
}
}
// Round width down to nearest multiple of 4 (VNC alignment requirement)
adjustedWidth = (adjustedWidth / 4) * 4;
size_t alignedWidth = (adjustedWidth / 4) * 4;
// Recalculate height based on aligned width to maintain exact aspect ratio
if (adjustedWidth > 0) {
adjustedHeight = (size_t)floor((double)adjustedWidth / _displayAspectRatio);
size_t alignedHeight;
if (alignedWidth > 0) {
alignedHeight = (size_t)floor((double)alignedWidth / _displayAspectRatio);
} else {
alignedHeight = adjustedHeight;
}
// Round height down to nearest multiple of 4 for consistency
adjustedHeight = (adjustedHeight / 4) * 4;
alignedHeight = (alignedHeight / 4) * 4;
// Ensure minimum dimensions (4x4 to satisfy alignment requirements)
if (adjustedWidth < 4) adjustedWidth = 4;
if (adjustedHeight < 4) adjustedHeight = 4;
if (alignedWidth < 4) alignedWidth = 4;
if (alignedHeight < 4) alignedHeight = 4;
// Log adjustment if changed
if (adjustedWidth != _prefsWidth || adjustedHeight != _prefsHeight) {
NSLog(@"[ScreenDumpVNC] Adjusted resolution from %dx%d to %zux%zu to maintain %.6f aspect ratio",
_prefsWidth, _prefsHeight, adjustedWidth, adjustedHeight, _displayAspectRatio);
// Log adjustment if dimensions changed due to alignment
if (alignedWidth != adjustedWidth || alignedHeight != adjustedHeight) {
NSLog(@"[ScreenDumpVNC] Aligned resolution from %zux%zu to %zux%zu (VNC requires multiples of 4)",
adjustedWidth, adjustedHeight, alignedWidth, alignedHeight);
}
// Update preferences with corrected values
_prefsWidth = (int)adjustedWidth;
_prefsHeight = (int)adjustedHeight;
_prefsWidth = (int)alignedWidth;
_prefsHeight = (int)alignedHeight;
}
-(void)setupScreenInfo {
@@ -191,12 +208,12 @@
// Calculate display aspect ratio from native IOSurface dimensions
[self _calculateDisplayAspectRatio];
// Adjust custom preferences to maintain aspect ratio
// Adjust/align dimensions (always sets _prefsWidth/_prefsHeight to valid aligned values)
[self _adjustResolutionForAspectRatio];
// Set final dimensions (preferences are now corrected if custom, or will use native)
_width = _prefsWidth == 0 ? _nativeWidth : _prefsWidth;
_height = _prefsHeight == 0 ? _nativeHeight : _prefsHeight;
// Set final dimensions (preferences now always contain aligned values)
_width = _prefsWidth;
_height = _prefsHeight;
_sizeImage = IOSurfaceGetAllocSize(_screenSurface);
// TODO: do these change at all? this might have been done for perf reasons
@@ -206,31 +223,49 @@
bytesPerPixel = 4; // IOSurfaceGetBytesPerElement(_screenSurface);
bitsPerSample = 8;
// Align bytes per row to 16 bytes (required for IOSurfaceAccelerator)
size_t staticBytesPerRow = bytesPerPixel * _width;
size_t alignedStaticBytesPerRow = ((staticBytesPerRow + 15) / 16) * 16;
_staticBuffer = IOSurfaceCreate((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
@"PurpleEDRAM", kIOSurfaceMemoryRegion,
// [NSNumber numberWithBool:YES], kIOSurfaceIsGlobal,
[NSNumber numberWithInt:bytesPerPixel*_width], kIOSurfaceBytesPerRow,
[NSNumber numberWithInt:alignedStaticBytesPerRow], kIOSurfaceBytesPerRow,
[NSNumber numberWithInt:bytesPerPixel], kIOSurfaceBytesPerElement,
[NSNumber numberWithInt:_width], kIOSurfaceWidth,
[NSNumber numberWithInt:_height], kIOSurfaceHeight,
[NSNumber numberWithInt:'BGRA'], kIOSurfacePixelFormat,
[NSNumber numberWithInt:(_width*_height*bytesPerPixel)], kIOSurfaceAllocSize,
[NSNumber numberWithInt:(alignedStaticBytesPerRow * _height)], kIOSurfaceAllocSize,
nil]);
// Create surface for CARenderServer capture (default mode)
// Create separate surface for CARenderServer capture (NOT the live framebuffer)
// This avoids touching the framebuffer which causes system lag
BOOL needsScaling = (_width != _nativeWidth || _height != _nativeHeight);
if (needsScaling) {
// Align bytes per row to 16 bytes (required for IOSurfaceAccelerator)
size_t nativeBytesPerRow = bytesPerPixel * _nativeWidth;
size_t alignedNativeBytesPerRow = ((nativeBytesPerRow + 15) / 16) * 16;
_renderServerSurface = IOSurfaceCreate((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
@"PurpleEDRAM", kIOSurfaceMemoryRegion,
[NSNumber numberWithInt:bytesPerPixel*_width], kIOSurfaceBytesPerRow,
[NSNumber numberWithInt:alignedNativeBytesPerRow], kIOSurfaceBytesPerRow,
[NSNumber numberWithInt:bytesPerPixel], kIOSurfaceBytesPerElement,
[NSNumber numberWithInt:_width], kIOSurfaceWidth,
[NSNumber numberWithInt:_height], kIOSurfaceHeight,
[NSNumber numberWithInt:_nativeWidth], kIOSurfaceWidth,
[NSNumber numberWithInt:_nativeHeight], kIOSurfaceHeight,
[NSNumber numberWithInt:'BGRA'], kIOSurfacePixelFormat,
[NSNumber numberWithInt:(_width*_height*bytesPerPixel)], kIOSurfaceAllocSize,
[NSNumber numberWithInt:(alignedNativeBytesPerRow * _nativeHeight)], kIOSurfaceAllocSize,
nil]);
NSLog(@"[ScreenDumpVNC] Created CARenderServer surface at %zux%zu (aligned row: %zu)",
_nativeWidth, _nativeHeight, alignedNativeBytesPerRow);
} else {
_renderServerSurface = nil; // Not needed when no scaling
}
// Initialize capture mode to CARenderServer (default)
_captureMode = CaptureModeCARenderServer;
_legacyModeTimer = nil;
NSLog(@"[ScreenDumpVNC] Native resolution: %zux%zu, Output resolution: %zux%zu",
_nativeWidth, _nativeHeight, _width, _height);
}
int argc = 1;
@@ -261,6 +296,8 @@
staticBuffer:_staticBuffer
width:_width
height:_height
nativeWidth:_nativeWidth
nativeHeight:_nativeHeight
useCADisplayLink:useCADisplayLink
renderServerSurface:_renderServerSurface
captureModeBlock:^CaptureMode(void) {