Even that is more about control over the rendering process than what silicon is doing the work. With a lot of (older?) graphics APIs, you're implicitly giving the GPU's driver a lot of control over the output and how it is generated. This is how we got events like [0] where GPU vendors would make their drivers 'cheat' on benchmarks by trading image quality for speed when certain criteria were detected.
I imagine that tradeoff has changed somewhat as the industry has moved towards graphics APIs intended to give the programmer more direct control of the hardware.
[0] https://www.reddit.com/r/quake/comments/168btin/does_anyone_...
But...with today's world of pixel shaders (Really, a world that's existed for 10+ years now), I'd be surprised if there's actually any benefit to be had these days. With a proper pixel shader, I doubt there's anything you could do on a CPU that you couldn't do on a GPU, and the GPU would be massively parallel and do it much faster.
But to play devil's advocate against myself, I have heard that programming for GPUs can be harder for many things. So maybe usability and developer-friendliness is what is meant by CPUs being better?
Basically, GPUs execute instructions in lockstep groups of threads. Each group executes the same instruction at the same time. If there's a conditional, and only some of the threads in a group have a state that satisfies the condition, then the group is split and the paths are executed in serial rather than parallel. The threads following the "true" path execute while the threads that need to take the "false" path sit idle. Once the "true" threads complete, they sit idle while the "false" threads execute. Only once both threads complete do they reconverge and continue.
They're designed this way because it greatly simplifies the hardware. You don't need huge branch predictors or out-of-order execution engines, and it allows you to create a processor with thousands of cores (The RTX 5090 has over 24,000 CUDA cores!) without needing thousands of instruction decoders, which would be necessary to allow each core to do its own thing.
There ARE ways to work around this. For example, it can sometimes be faster to compute BOTH sides of a branch, but then merely apply the "if" on which result to select. Then, each thread would merely need to apply an assignment, so the stalls only last for an instruction or two.
Of course, it's worth noting that this non-optimal behavior is only an issue with divergent branches. If every thread decides the "if" is true, there's no performance penalty.
The simple calculations typically used for rendering graphics can easily be parallized on the GPU, hence it's faster. But the result should be identical if the same calculations are done on the CPU.
Also GUI frameworks like iced and egui typically support multiple rendering backands. I know iced is renderer agnostic, and can use a number of backands including the GPU graphics APIs Vulkan, DX12 and Metal.