I've noticed that my camera app is returning blank images on developer beta 4. After some investigation there is an issue with core image custom kernels where the texture sampler is returning NaN / 0 floats.
I have a reproducible demo here: https://github.com/alexfoxy/ci-metal-shader-bug
Feedback ticket here: https://feedbackassistant.apple.com/feedback/23895753
Thanks for the thorough write-up, the reproducer, and the Feedback. That combination made this quick to confirm. The conclusion is clear: your kernels use the API correctly. What you are seeing is a regression in the current iOS 27 beta, not anything wrong in your code.
In fact the pattern in your sampler kernels, src.sample(src.coord()), is the passthrough example shown in the CIKernel documentation. It is exactly the documented usage.
What I confirmed
I built a reduced version of your case and ran it on a device on iOS 27.0 beta 4 (build 24A5390f). It renders through an unmanaged CIContext and reads the center pixel back as RGBAf, so a NaN survives as a NaN rather than being clamped. Four sampler-typed kernels over one flat input:
- A
CIColorKerneltakingsample_treturns the correct value. - A
CIKernelwhose body isfloat4 c = src.sample(src.coord());returnsNaNfor every pixel. - A probe returning
src.coord()as color reads(0, 0)at every pixel. - A sampler-typed kernel that never calls the sampler returns its constant correctly.
So the sampler binds, but the sampler member calls (src.coord(), and therefore src.sample(...) built on it) return no usable value on this build. That matches your blank output: a kernel sampling at src.coord() reads nothing, and the NaN propagates to a blank result.
A workaround, using only public API
The Core Image kernel library (CIKernelMetalLib.h) exposes the same operations as free functions alongside the sampler member methods:
src.coord()has the free-function formsamplerCoord(src)src.sample(p)has the free-function formsample(src, p)
Rewriting the member calls as the free functions avoids the affected path. Your identity example becomes:
// affected on iOS 27 beta 4
float4 c = src.sample(src.coord());
// same result, unaffected
float4 c = sample(src, samplerCoord(src));
I verified this on the same beta 4 build. Two kernels with byte-identical arithmetic: the member-call form returns NaN, the free-function form returns the correct luma. No metallib, build-flag, or pipeline change is involved, only the two source expressions.
Your app has a range of kernels. It is worth re-checking each affected one after the change rather than assuming a single edit covers all of them. Any other sampler member accessors you rely on (for example the extent or transform helpers) have free-function equivalents in the same header.
On the reports
On our side there is already an open bug for this issue, and I have related your Feedback (FB23895753) to it.
For reference: