Advertisement
Advertisement
⚡ Community Insights
Discussion Sentiment
37% Positive
Analyzed from 3527 words in the discussion.
Trending Topics
#cursor#hardware#plane#guard#gpu#fix#mouse#overlay#software#macos
Discussion Sentiment
Analyzed from 3527 words in the discussion.
Trending Topics
Discussion (85 Comments)Read Original on HackerNews
With the disclaimer that I have zero knowledge of the MacBook Neo hardware, but I do know a bit about GPUs in general (including having written some GPU-accelerated drivers for Windows and the associated cursor-handling code), I'm going to make a wild guess: this lag is caused by waiting for the GPU command queue to flush.
As a bit of background information: the GPU is fed commands from a queue that the CPU writes to. These commands perform the drawing operations that the GPU is designed to accelerate. A hardware cursor is basically a small bitmap that can be positioned anywhere on the screen and moved around by simply updating position registers (which is normally done per mouse interrupt); the hardware draws it automatically. A software cursor is manually drawn by the graphics stack, which saves what was under it, draws the cursor, and then whenever it needs to be moved, writes the original data back, saves the data at the new position, and then draws the cursor there.
Flushing the command queue is necessary when switching to a software cursor, or otherwise doing software writes to the framebuffer, because you need to wait for the GPU to finish drawing what it has queued, or it may end up drawing over what software wants to draw, including the cursor. Or worse, the command is a blit (e.g. scrolling a window) and you end up with remnants of the cursor at its previous position.
Updating plane properties such as to move the cursor plane around or disable it would by itself not block on render activities, as they are completely distinct blocks.
The render hardware could be powered down, but I doubt powering it up and compositing the cursor would take long enough to complete to cause any noticable lag.
Under the Linux APIs, updates to the display controller are done through KMS atomic commits, and one mistake you could do display-server side would be to provide a fence in this atomic commit that the scheduler will use to wait on long-running GPU work before using the provided graphics buffers. Under this API, none of the changes - including mouse movements - would then be applied until that fence is signalled. Changing plane associations can lead to resource reallocations that can be a bit heavy.
Not sure if the kernel driver in macOS works anything remotely similar to this, and the driver could also just be dumb and block on unrelated things ("let's just wait another vblank to see this apply....", "as we only need one plane now let's power down hardware and wait for that to settle..."). It could also just be windowserver that waits for work to finish on its own, not providing any cursor updates in the meantime.
The reality is that it will take reverse engineering or looking at actual code to know what's going on.
EDIT: Also note that there is nothing new with the Neo here, as all Macs since the M1 have used the same chip architecture as the iPhone.
Desktop GPU designs did not focus on tiny efficiency gains, and often only has a primary plane, a single overlay plane (for e.g., a video), and a dedicated cursor plane. Some even have to share a single overlay plane between all connected displays. It's a recent thing for desktop GPUs to get more flexible in this area, in part to improve laptop battery life in the cases where the laptop is almost entirely idle.
(For those unaware, a "plane" here is the entity in the display controller you configure to show a rendered graphics buffer, in a particular location and with particular transforms. You commonly have one plane that just covers the whole screen, and then sometimes put dynamic content on top in other planes so you can avoid having to redraw the main buffer when smaller bits of it change, like a video player or cursor. You could also e.g., scroll by rendering an entire document in advance and then move the plane around to reveal parts of it.)
Long story short, performance was disappointing and we abandoned the approach. It's easy to believe it's a real problem especially when there are other factors including GPU being clocked down to save power.
Same caveat as parent, I have no direct knowledge of MacBook Neo or this specific issue.
Do modern machines still have custom hardware for cursors? That would surprise me, as a GPU can easily blit a small cursor on top of whatever gets drawn.
the cursor could just be another small rectangle texture you position on top of the other surfaces. there is no need to read the framebuffer/write into it, its just a z-stack of 3d surfaces now
The problem with rendering the cursor into the primary plane is that, often, only the cursor changes, and you'd have to re-render the whole plane that contains the cursor. That is easily doable for modern hardware, but bad for power consumption and may also be higher latency. (The latency aspect gets interesting when dragging something on the primary plane - I think most compositors temporarily disable the hardware cursor in order to keep cursor and dragged object in sync.)
AFAIK this hasn't been true for a long time on most platforms, certainly on macOS. The desktop image is composited on the GPU by assembling the underlying windows with appropriate effects like shadows and scrolling/scaling. A software cursor is just another overlay which may also have a transparent shadow.
Actually preserving what was under the cursor and putting it back is the sort of thing you wouldn't do anymore, because that's a cache which requires babysitting based on everything that's underneath and around it.
e.g. On macOS there's full screen zooming for accessibility, and if you wiggle the mouse, the cursor grows in size briefly (maybe even too big for hardware cursor to support).
If a hardware layer is not being used the cursor layer will be treated like any other layer in the compositor. Modern compositors don't try and save and write pixels like that. It will just rerender it.
>(which is normally done per mouse interrupt);
It's normally done every frame the compositor makes.
>or it may end up drawing over what software wants to draw
The compositor composites everything at that will be shown on the next refresh of the display. Things don't indepently step on each others toes since it's just the compositor rendering and synchronizing all hardware layers (planes).
This was going to be my suggestion because it also fixed a similar CPU/GPU related issue many years ago: Apple's own TV.app would have minuscule color handling differences whenever subtitles would show during a movie. This was driving me nuts while showing a moody black & white film for a movie night - every time a subtitle would pop up, the entire scene's black levels would shift slightly (and it wasn't any kind of adaptive/localized brightness or anything like that, it was the actual rendering).
Some online sleuthing revealed it was GPU related (pure GPU video decoding vs. the CPU overlaying subtitles on the screen), and that bumping up the cursor size (even the tiniest amount) in mouse settings would fix it. It worked.
It's barely noticeable, but I actually prefer the slightly bigger mouse cursor now anyway, so it's part of my standard macOS setup.
- https://issues.chromium.org/issues/41359717
- https://bugzilla.mozilla.org/show_bug.cgi?id=1747999
macOS skipped compositing when only a single fullscreen surface with a black background was being rendered, but there were many ways for that render path to misbehave.
It only ever affected integrated graphics on dual GPU Macs, as far as I can remember.
Linux resolved this issue, but to work around it on windows, I changed my cursor to an animated dinosaur, which was built into the OS and had the side-effect of disabling the hardware cursor.
The cursor size adjustment is mentioned further down on the page.
So I wrote a bash script that auto-started on battery mode and then calculated a hash every few seconds. Boom, whine solved. Terrible fix, but I never measured how much battery it cost me, so it was... fine.
https://xkcd.com/1172
I'm genuinely surprised.
The way you word it, it looks like a famous ubiquitous problem. Mind sharing any details?
whenever the CPU works hard my cursor starts to lag
The other day my son and I were sitting in front of the XBOX ONE we (try to) use as a Plex client and laughing about how showing a new logo while the machine is doing something meaningless to us is an act of brand destruction and that they should be showing us a Playstation logo instead... And how with the NES you could just hit the power button and start playing. The cursor never lagged like that on the 1984 Mac.
I remember a long time ago, early days of Gnome 3 when doing large file operations would hang the cursor. It made the whole system feel like complete garbage, totally unacceptable.
A cursor lagging (or any kind of GUI/user interaction stutters) should be a critical high priority bug, all hands on deck sort of thing. The entire experience of using the damn thing hinges on the responsiveness of the interface and pointing devices.
When the counter hits e.g. 200, spam the user with notifications.
All an App needs on MacOS seems to be a binary and a little .plist
We showed [Gates] how the Macintosh mouse cursor moved smoothly, in a flicker-free fashion.
"What kind of hardware do you use to draw the cursor?", he asked. Many current personal computers had special hardware to draw small bitmaps called "sprites", and he thought we might be doing something similar.
The audacity of developers to restart the discussion whether the mouse should follow user input induces rage on so many levels.
But there were a lot of things we learned in the 80s and 90s that we have largely forgotten today, like "make clickable things look clickable" and "don't use Yes and No as button labels" and "active windows should look different from inactive windows."
Sun's framebuffers with 8 bit color + 1 bit monochrome + 1 bit enable (like the cgfour / GX aka Lego graphics accelerator) put the cursor in the monochrome layer, and NeWS supported it as an overlay plane, an optimization of xor drawing and undrawing. The enable layer would switch between the color and monochrome layers on a per-pixel basis.
With NeWS, I could open up the enable and monochrome layers directly and draw into them with PostScript to perform temporary non-destructive highlighting, and make monochrome overlay windows that didn't damage the color windows underneath. But it was a bit of a hack (much uglier than this cursor lag fix). Here is a window subclass that lifts a monochrome window into the overlay plane so it doesn't damage color windows behind it:
https://donhopkins.com/home/archive/psiber/cyber/overlay.ps
[...] The "Pseudo Scientific Visualizer" used it by making a PSVisualizerWindow subclass of OverlayWindow:https://donhopkins.com/home/archive/psiber/cyber/mics.ps
And the popup pointing hand shaped callout window also used it so it didn't have to repaint the color window underneath when you moved or dismissed it:
https://donhopkins.com/home/archive/psiber/cyber/pointer.ps
the follow on prank was having all the xterminals 'moo' whenever new code was deployed to prod.
Instead of doing swift in bash and calling swiftc, you can always shell out to Process() from inside a Swift script instead.
The more important use case for guard is that 'guard let' statements can pattern-match and introduce bindings that are valid for the rest of the scope:
This is useful enough that Rust copied it in the form of 'let ... else {}' statements (but did not bring over boolean guard statements).Using "if" or "unless", whichever is more appropriate, is far more readable than "guard".
Moreover, there are many languages where an assignment or an initialization can appear in any place where an expression can appear. Such general rules are always better than special rules like allowing new bindings in a "guard", but not in an "if". Pattern matching does not need any special syntax, it should work in the standard alternative program structure of that programming language, regardless whether it is called "select", "case" or "switch".
It always annoys me when the creators of new programming languages demonstrate amateurism, by inventing new worse alternatives than those that existed in various older programming language, already many decades ago.
There are plenty of new programming languages that claim to be better than C, which may be true, but they fail to match programming languages much older than C.
How is a different (and longer) keyword "far more readable"? That's just a matter of preference and familiarity. The reason for choosing a different keyword is that it's not quite equivalent to an unless as the {} block must exit the surrounding scope. You read it like an assert statement with a custom handler.
> Moreover, there are many languages where an assignment or an initialization can appear in any place where an expression can appear. Such general rules are always better than special rules like allowing new bindings in a "guard", but not in an "if".
You can introduce bindings in an if too. The special thing about guard is that you can introduce a binding which is valid for the remainder of the scope outside the {} block (where the condition is true) but not inside (where the condition is false).
It's pretty amazing at the price point. Thought last year the M4 Airs were on sale for $749 -- I'm doubtful we'll see that price again on those....
AND I'm not seeing this issue. What am I missing?
I'm on 26.4.
:shrugs:
EDIT: I guess I wait to update and install the latest version? Maybe the linked page could have stated there's a new regression in newer MacOS versions that introduced this?
Being flawless and polished at UX, hardware experience, and CX in general are the core of Apple's identity and revenue. Granted, the "hold it like this" thing was a huge gaff.
I also think the Neo is a culture miss that is diluting their brand. Their logo is synonymous with "expensive" and "quality" but the Neo is affordable and now it has cursor stutter.
It's just a simple one-liner
curl github.com/trustworthyguy/fix_everything | sh
And it all works. Not like recompiling entire kernel to play an mp3.
/s obviously
I had to set up a daemon to kill the downloader every 10 seconds.
The root cause for the issue is probably (I'm not an Apple developer) due to huge round rectangles on the window shape corners. Rendering the window with the corners would include rendering whatever other windows and widgets under the window. (Which will have a lag and some more operations with transparency, which the developers probably want to avoid - while I'm not sure about this part).