This issue has been seen on Adreno 530 with latest drivers from Android 8.1.0 on a Pixel and ZTEE Axon 7.
When a fragment shader uses discard, it's not preventing stencil REPLACE operations.
Normally, fragment tests (depth/stencil, etc.) are optimized to be run early when possible. Per Vulkan - 8.9.2 and Vulkan - 25.4, this should technically only happen when EarlyFragmentTests in in the shader (e.g. "layout(early_fragment_tests) in;") This is true in Vulkan 1.0 and 1.1.
According to the Vulkan Programming Guide, it's acceptable for an implementation to enable it anyway, provided the shader doesn't write to depth, doesn't have side effects like image writes, and doesn't include OpKill. However, OpKill at least is not properly being detected.
The scenario in which this happened was (possibly may occur in more scenarios):
- Depth test and depth bounds test disabled.
- Stencil test enabled.
- Stencil write mask, compare mask, and reference as dynamic state.
- Compare op ALWAYS.
- Depth fail, stencil fail, and stencil pass ops set to REPLACE.
- No attributes passed to vertex shader - using gl_VertexIndex to generate positions.
- Fragment shader conditionally or unconditionally calling discard / OpKill.
This bug does not occur on other implementations and appears Adreno specific. A workaround has been applied to statically write to gl_FragDepth, which has fixed the issue.
For those curious as to why one would perform the above, it's basically used to upload stencil values (using 8 passes, one per bit.) However, I suspect the issue would occur even in more general usage when stencil and discard are used together. The same basic strategy works well through GLES on Adreno, where the discard is detected properly.
More details can be seen here:
(ignore the GLES issue - that was a bug in the code due to a recent refactor and was not Adreno specific.)