Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

macOS and iOS CPU ussage fix #5713

Open
Kees-van-Beilen opened this issue Aug 16, 2022 · 15 comments · May be fixed by #9034
Open

macOS and iOS CPU ussage fix #5713

Kees-van-Beilen opened this issue Aug 16, 2022 · 15 comments · May be fixed by #9034
Labels
A-Windowing Platform-agnostic interface layer to run your app in C-Enhancement A new feature

Comments

@Kees-van-Beilen
Copy link
Contributor

What problem does this solve or what need does it fill?

increasing iOS and macOS game performance whilst saving battery live is a hard task but can be done quite easily, since macOS and iOS usually fix application framerate (with the exclusion to scrollviews or when programmatically demmanded). Currently a basic bevy app uses ~100% cpu on mac, which is a shame because it can be lower

What solution would you like?

Since application framerate is capped, using CADisplayLink on iOS and CVDisplayLink on macOS will give you a non-blocking 60 tick per second callback

What alternative(s) have you considered?

There's no other way on macOS , because the most common run loop [NSApp getevent.....] is really slow and CoreVideo bypasses all of the objc event system

Additional context

As someone who has implemented both CADisplayLink and CVDisplayLink once in the past. I can assure you it saves alot on cpu usage going from ~>100% to <20% which in turn saves on a lot of battery life. Also because apple ctr-alt-deleted there good old CoreVideo documention here’s a quick overview in obj-c (even though corevideo is mostly a c-api):

//create a display link
CVDisplayLinkRef displayLink;
CGDirectDisplayID   displayID = CGMainDisplayID();
CVReturn  error = kCVReturnSuccess;
error = CVDisplayLinkCreateWithCGDisplay(displayID, &displayLink);
if (error){
      NSLog(@"error:%d", error);
      displayLink = NULL;
}
//link callback
CVDisplayLinkSetOutputCallback(displayLink, renderCallback, (__bridge void *)self);
//start
CVDisplayLinkStart(displayLink);

//callback
CVReturn renderCallback(CVDisplayLinkRef displayLink, 
                               const CVTimeStamp *inNow, 
                               const CVTimeStamp *inOutputTime, 
                               CVOptionFlags flagsIn, 
                               CVOptionFlags *flagsOut, 
                               void *displayLinkContext){
    @autoreleasepool {
        update();
        //get access to the main thread when drawing to screen (this method is also blocking but way less demanding than the NSApp runloop)
        dispatch_sync(dispatch_get_main_queue(), ^{ 
            [gview setNeedsDisplay:YES];
        });
    }
    return 0;
}
@Kees-van-Beilen Kees-van-Beilen added C-Enhancement A new feature S-Needs-Triage This issue needs to be labelled labels Aug 16, 2022
@bjorn3
Copy link
Contributor

bjorn3 commented Aug 16, 2022

You can enable vsync for this, right?

@Kees-van-Beilen
Copy link
Contributor Author

You can enable vsync for this, right?

No it seems like the runloop used in all scenarios is the NSApp runloop which shouldn’t be used like this.

@bjorn3
Copy link
Contributor

bjorn3 commented Aug 16, 2022

We need NSApp for input events, right?

@Kees-van-Beilen
Copy link
Contributor Author

Well, no actually. Fetching mouse position and getting current keyboard state doesn’t require the NSApp runloop

@bjorn3
Copy link
Contributor

bjorn3 commented Aug 16, 2022

And what about window events and game controllers? Also if you use a CoreVideo based event loop can you still use NSView/UIView? Wgpu requires one to show the rendered framebuffer in.

@Kees-van-Beilen
Copy link
Contributor Author

I don’t know why you would want to recieve window events but you can still use apple's Game controller api and you can update the NSView/UIView although on mac when updating the NSView you have to do a dispatch_sync (main_queue) because the NSView can only be updated from the main thread.

@bjorn3
Copy link
Contributor

bjorn3 commented Aug 16, 2022

Wgpu doesn't support running on a thread that can't access the NSView AFAIK.

@Kees-van-Beilen
Copy link
Contributor Author

Maybe it would be possible to add a new optional runloop mode and window manager for macOS that is optimised for games?

@bjorn3
Copy link
Contributor

bjorn3 commented Aug 16, 2022

Maybe? I think it makes sense to prototype it first as external plugin though. Using vsync would solve the cpu usage issue AFAIK and the only benefit to using CoreVideo is improving latency, so maybe it could be combined with https://github.com/aevyrie/bevy_framepace? cc @superdump @aevyrie

@Kees-van-Beilen
Copy link
Contributor Author

Oké, I'm currently writting a proof of concept Plugin to demonstrate what I mean

@aevyrie
Copy link
Member

aevyrie commented Aug 16, 2022

It would be interesting your results to bevy_framepace, which is OS agnostic, but should do something very similar.

@Kees-van-Beilen
Copy link
Contributor Author

I'm almost done (going from 100%-150% cpu to 20%-30%) but, I can’t seem to find where bevy actually draws the renderer texture. Do you know where @bjorn3 ?

@bjorn3
Copy link
Contributor

bjorn3 commented Aug 17, 2022

let frame = match surface.get_current_texture() {
Ok(swap_chain_frame) => swap_chain_frame,
Err(wgpu::SurfaceError::Outdated) => {
render_device.configure_surface(surface, &swap_chain_descriptor);
surface
.get_current_texture()
.expect("Error reconfiguring surface")
}
err => err.expect("Failed to acquire next swap chain texture!"),
};
is what you need I think. This is also where vsync blocks.

@bjorn3
Copy link
Contributor

bjorn3 commented Aug 17, 2022

let swap_chain_texture = if let Some(swap_chain_texture) = &window.swap_chain_texture {
may also be relevant.

@Kees-van-Beilen
Copy link
Contributor Author

I've looked into it and created https://github.com/Kees-van-Beilen/CocoaBird, which whilst in debug mode grants a cpu benefit of ~50% to ~ 10% comming to an avarage of 60% cpu usage (with winit this is about 90%). In release mode these two are virtually the same.

@Weibye Weibye added A-Windowing Platform-agnostic interface layer to run your app in and removed S-Needs-Triage This issue needs to be labelled labels Aug 18, 2022
@B-head B-head linked a pull request Jul 6, 2023 that will close this issue
8 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Windowing Platform-agnostic interface layer to run your app in C-Enhancement A new feature
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants