Repository navigation
macOS grabber: use a persistent SCStream instead of a screenshot per frame - #1637
Open
PiyalAhmed wants to merge 1 commit into
Open
PiyalAhmed wants to merge 1 commit into
PiyalAhmed wants to merge 1 commit into
Conversation
…frame The ScreenCaptureKit path called SCShareableContent and SCScreenshotManager captureImageWithFilter on every timer tick, so each frame enumerated all displays/windows (TCC + WindowServer queries) and set up and tore down a one-shot capture session in replayd. On a Mac mini M4 at 60 fps this kept WindowServer, tccd, opendirectoryd, Control Centre and replayd busy (together several times HyperHDR's own CPU) and replayd's Mach port count kept growing while HyperHDR was running. Now the SCContentFilter / SCStreamConfiguration / SCStream are created once when the grabber starts. Frames arrive through an SCStreamOutput delegate on its own serial queue, which keeps only the latest complete BGRA CVPixelBuffer in a mutex-protected slot. The existing Qt timer picks up that frame on the grabber thread and never blocks. - output size is scaled by ScreenCaptureKit (GPU) to "Max stream width" instead of capturing at full native resolution; crop values (native pixels) are rescaled to the delivered frame - minimumFrameInterval follows the configured FPS - the stream is rebuilt on start/restart (display, FPS and width changes already go through uninit()/start()) and when the display mode changes - stream errors (display removed, permission revoked) are marshalled to the grabber thread and use the existing uninit() + 3s retry - the stream is stopped and every ObjC/CF object released when the grabber stops; completion handlers never touch a destroyed grabber - the non-ScreenCaptureKit (CGDisplayCreateImage) path is unchanged Approach based on Hyperion.ng's fix for the same issue: hyperion-project/hyperion.ng#2047 (hyperion-project/hyperion.ng#1921)
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The macOS system grabber (ScreenCaptureKit build, macOS 15+) took a new ScreenCaptureKit screenshot on every frame. On each timer tick
macOsGrabber::grabFrame()did the following:SCShareableContent getShareableContentWithCompletionHandler: lists all displays and windows, which means TCC and WindowServer queries every frame.SCContentFilterandSCStreamConfiguration.SCScreenshotManager captureImageWithFilter: creates and tears down a one-shot capture session in replayd every frame, at full native resolution.At 60 fps this put heavy load on system daemons (WindowServer, tccd, opendirectoryd, Control Centre, replayd), much more than HyperHDR used itself. replayd's Mach port count also kept growing while HyperHDR ran. The completion handlers also called
decodeFrame(),uninit()andQTimer::singleShot(..., this, ...)from a ScreenCaptureKit queue instead of the grabber thread.This PR switches to a persistent
SCStream, following Hyperion.ng's fix for the same bug (hyperion-project/hyperion.ng#2047, issue hyperion-project/hyperion.ng#1921):SCContentFilter,SCStreamConfigurationandSCStreamare created once when the grabber starts.SCStreamOutputdelegate on its own serial queue keeps only the latest complete BGRACVPixelBufferin a mutex-protected slot. The existingQTimerprocesses it on the grabber thread and never blocks.minimumFrameIntervalfollows the configured FPS.uninit()/start()path.uninit()+ 3-second retry.CGDisplayCreateImage) path.What kind of change does this PR introduce? (check at least one)
If changing the UI of web configuration, please provide the before/after screenshot:
N/A (no UI changes)
Does this PR introduce a breaking change? (check one)
If yes, please describe the impact and migration path for existing setups:
The PR fulfills these requirements:
Fixes: #xxx[,#xxx], where "xxx" is the issue number)To avoid wasting your time, it's best to open a feature request issue first and wait for approval before working on it.
Other information:
Before / After
Test setup: Mac mini M4, 16 GB, macOS 26.6.2, 5K display (5120×2880, looks like 2560×1440), system grabber enabled, 60 fps, Max stream width 1280. CPU values are
toppercentages of a single core.For "After", the grabber was switched off and on twice, with 20 s samples in each state. Most of WindowServer's load in this session came from other on-screen activity, since it was there with the grabber off as well. Capture itself adds about 2–5 %.
HyperHDR's own CPU stays about the same. A profile shows it is spent in the regular frame pipeline (
FrameDecoder::processSystemImageBGRA,ImageColorAveraging::getMulticolorForLeds) at 60 fps × 1280 px, not in ScreenCaptureKit. On this 5K display the old code copied a 5120×2880 frame per tick before downscaling on the CPU; ScreenCaptureKit now delivers 1280×720 directly.replayd Mach ports:
Testing