Thanks for the detailed follow-up. I tested both workarounds on a Mac16,8 (Apple M4 Pro) running macOS 27.0.1 (26A434).
Every context is checked against its own serial reference, including the aggressors. Each line is 50 rounds of 64 concurrent renders, and the two control lines in the same run show the problem is still being caught.
Renders wrong (victims, aggressors):
No workaround (control): 1593/1600, 141/1600
Lock on the victim side only (control): 1600/1600, 221/1600
One lock shared by all contexts, held only around translucent fills and strokes: 0/1600, 0/1600
Antialiasing off on the victim only: 0/1600, 0/1600
Antialiasing off on the aggressor only: 0/1600, 0/1600
Victim in 16-bit float, converted to 8-bit by drawing makeImage() into an RGBA8 context: 0/1600, 0/1600
Victim in 16-bit float, converted with vImageBuffer_InitWithCGImage: 0/1600, 0/1600
Aggressor in 16-bit float, converted with vImage: 0/1600, 0/1600
With 2, 3 and 4 victims drawing different content and no aggressors, 612, 733 and 786 of 3,200 renders were wrong without a workaround. With the shared lock or the float path on every context, none were.
These results match yours: a lock on one side doesn't help, changing eit contexts, and the aggressors' own output is also corrupted. The floatpath isn't pixel-identical to drawing directly in 8-bit. About 2.5% of bytes differ, by at most 1, which is fine for my use. I didn't test text under the lock, and I only tested on this Mac.
As for my app: it currently renders all its artwork on a single thread, rmal use. The only place it was exposed is the parallel test runner, andI've serialised the rendering tests. If I move rendering onto several threads later, I'll use the float path or turn off antialiasing on all but one context, so the drawing itself doesn't have to be serialised.
Topic:
Graphics & Games
SubTopic:
General
Tags: