zNear=1 and zFar=1000. Tweaking these values does not affect the outcome
I mean z-Fighting shouldn’t even happen here. I render all the triangles for the letters in one single call to glDrawElements with one vao and vbo for all positions

I’m using java and lwjgl3 if this should affect anything, but I think that’s unlikely

The Q and R are on the same plane. Any fragment which belongs to both triangles will have the same object-space position in both and thus the same NDC position in both regardless of the transformations. x=y => f(x)=f(y).

The stippling effect arises due to rounding error. As the overlapping polygons don’t have identical vertex coordinates, calculations which would produce identical values with infinite precision produce slightly different results when performed with finite precision.

That doesn’t mean there won’t be depth-fighting. The primitives are still rendered in the order specified by the element array. GL_LEQUAL favours the primitive which is drawn later (presumably the R), but it won’t prevent the case where the actual depth value is half-way between representable values, with the depth value from the Q being rounded down while that from the R is rounded up.

If you want to prevent this, you’ll need to either modify the mesh so that primitives don’t overlap unless they’re identical, or disable depth testing or depth writes, or have the fragment shader discard wholly-transparent fragments.

If you were to tessellate the rectangles for the Q and R so that the overlapping portions were identical, it wouldn’t happen. Rendering identical triangles (where all three vertices have identical positions) should result in corresponding fragments having identical depth values in spite of rounding error (you’ll have identical calculations, rather than different calculations which should theoretically produce identical results).

Discarding transparent fragments is the simplest solution, but the use of discard in a fragment shader inhibits early depth testing, which has a performance cost.