Use IOSurface on macOS/iOS#329
Conversation
| // Block until back buffer is no longer being used by the compositor. | ||
| // | ||
| // TODO: Allow configuring this: https://github.com/rust-windowing/softbuffer/issues/29 | ||
| // TODO: Is this actually the check we want to do? It seems like the compositor doesn't | ||
| // properly set the usage state when the application loses focus, even if you continue | ||
| // rendering there? | ||
| // | ||
| // Should we instead set up a `CVDisplayLink`, and only allow using the back buffer once a | ||
| // certain number of frames have passed since it was presented? Would be better though not | ||
| // perfect, `CVDisplayLink` isn't guaranteed to actually match the display's refresh rate: | ||
| // https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/CoreVideo/CVProg_Concepts/CVProg_Concepts.html#//apple_ref/doc/uid/TP40001536-CH202-DontLinkElementID_2 | ||
| // | ||
| // Another option would be to keep a boundless queue as described in: | ||
| // https://github.com/commercial-emacs/commercial-emacs/blob/68f5a28a316ea0c553d4274ce86e95fc4a5a701a/src/nsterm.m#L10552-L10571 | ||
| while self.back.surface.is_in_use() { | ||
| std::thread::yield_now(); | ||
| } |
There was a problem hiding this comment.
The way we wait for the compositor to stop using the buffer(s) here is kinda bad, but that can be resolved later, and shouldn't be a problem for applications that either:
- Only re-render when something changes.
- Do proper frame-pacing (Winit doesn't yet provide that though).
There was a problem hiding this comment.
When I run the animation example, it seems to sometimes work, but sometimes get stuck in this loop after the three buffers have been allocated.
e02da58 to
f1882c5
Compare
31f0a29 to
45b790a
Compare
|
Hm. When I try this on an M1 Mac Mini running Tahoe 26.2, I'm not seeing it work correctly. Examples create windows with white contents, except it becomes partly visible if I drag another window over it. But that doesn't show up in a screenshot, which just shows the window white. |
|
It requires #321 (the alpha mode is diff --git a/src/pixel.rs b/src/pixel.rs
index bcc223d..eb08f30 100644
--- a/src/pixel.rs
+++ b/src/pixel.rs
@@ -101,7 +101,7 @@ impl Pixel {
/// ```
pub const fn new_rgb(r: u8, g: u8, b: u8) -> Self {
// FIXME(madsmtm): Change alpha to `0xff` once we support transparency.
- Self { r, g, b, a: 0x00 }
+ Self { r, g, b, a: 0xff }
}
/// Create a new pixel from a blue, a green and a red component. |
|
Idk., I just preferred to wait for #321 (and I won't merge this one before that) |
e970fe8 to
3c8ac6e
Compare
|
Comparing this against the current let display_id = match self.window_handle.window_handle().unwrap().as_raw() {
RawWindowHandle::AppKit(handle) => {
let view: &NSView = unsafe { handle.ns_view.cast().as_ref() };
let window = view.window().unwrap();
let screen = window.screen().unwrap();
let desc = screen.deviceDescription()
.objectForKey(&NSString::from_str("NSScreenNumber"))
.unwrap()
.downcast::<NSNumber>()
.unwrap()
.unsignedIntValue()
}
_ => todo!(),
};
surface.color_space = CGDisplayCopyColorSpace(display_id);Unsure how QuartzCore handles this, does it re-render in the client application, or does it somehow tell the compositor what the color space is supposed to be? Will have to reverse-engineer. (Not that we're really specified what the color space is supposed to be in Softbuffer, see also #15, but |
Write directly into a shared memory buffer that can be shared directly with the compositor and avoids copying bytes when presenting (QuartzCore was copying stuff before). This also implements double and triple buffering of the surface, to avoid creating a bunch of unnecessary buffers. The compositor seems to sometimes work on two buffers at the same time? A bit unsure why. The way we wait for the compositor to stop using the buffer(s) is kinda bad, but that can be resolved later, and shouldn't be a problem for applications that do proper frame-pacing (which Winit doesn't yet provide though).
3c8ac6e to
10dccf3
Compare
|
This is much much faster than |
|
The memory usage of this is looking to me (doesn't have the same problem that an earlier PR had) |
Probably a Winit bug, as discussed in #275 (comment), Winit sends two redraw events when resizing? And I wouldn't be surprised if one of them had different But if you use |
|
I wouldn't be too concerned if there was just a frame (of few) lag on the sizes converging. What's more concerning to me is that even after resizing stops, the buffer/window sizes never end up matching again once the window has been resized once (they will occasionally match up again during resizing if you resize a lot, but I have been unable to get it to finish a resize in a matching state). |
|
And that's just with this PR? |
|
@madsmtm I looked into this again today. And I have good news: the issue here was just that I wasn't accounting for the buffer's stride. Accounting for that now, and this is working great. It does seem to be slightly slower to copy pixels a row at a time to account for the stride compared to simple copy of the entire buffer (I'm currently rendering into an intermediate |
I can imagine, but nothing we can do to get around that, stride is fundamental to the problem. |
|
To make it a bit clearer what my plans are, I'll mark this as a draft, since I haven't found a good way to handle the color space issues noted in #329 (comment). |
|
I now have stride support implemented in |
|
Tested color spaces a bit more, if you're rendering with But if you set the color space to the current screen's color space (such as P3, retrieved as described in #329 (comment)), the presentation time drops to between 200µs and 700µs. In comparison, presentation times when using just IOSurface lies between 50µs and 150µs. This leads me to believe that when rendering with the current screen's color space, CoreGraphics has a fast path where it copies from the provided buffer to an internal IOSurface (or similar DMA-backed buffer) without any sort of color space conversion that it otherwise does in the normal sRGB case. Something interesting to test would be to see what happens when you drag a window rendered with one screen's color space (such that it uses the above fast path) onto a different screen while the application is blocked (such that CoreGraphics doesn't get a chance to submit further work to the compositor)? A theory is that if the window is rendered in the wrong color space on the new screen, then that would be a hint that the compositor simply doesn't support color space conversions, but if not, then that would hint that it does, and that there might be a way to make use of it? |
|
So, turns out that I was wrong about Looking at WebKit sources with @jrmoulton at RustWeek today (thanks for the help!), I realise now that there's a difference between "properties" and "values" on Using this instead, I've now managed to get the same visual result as with current |
Make
Buffer<'_>be backed by a shared memory buffer that can be shared directly with the compositor. This makes the CoreGraphics backend zero-copy (QuartzCore was copying from theCGImageProviderinternally before when committing the transaction).This also implements triple buffering of the surface, to avoid creating a bunch of unnecessary buffers. The compositor seems to want to work on two buffers at the same time? A bit unsure why, but I know that we do get tearing if we try to touch things while it's working on it.
Fixes #83.
This requires #321, because
IOSurfacedoes not support RGBX (so the alpha channel must be opaque to render correctly).Tested on:
Replaces #95 and #96.
TODO:
Figure out what to do about color spaces.