I'm trying to use the new MTL4FX::FrameInterpolator(the Metal 4 variant that encodes to MTL4::CommandBuffer). It creates fine, accepts all texture bindings, and encodes without any error or assertion. The GPU signals completion via shared event. But the output texture is completely untouched — zero bytes changed from before the encode. It's a silent no-op.
I'm trying to call metal-cpp from python by making dynamic library with cpp.
Environment:
- macOS 26.6 (build 25G72)
- Apple M3, arm64
- Xcode 26 SDK
- MetalFX framework (MTL4FX API, macOS 26+)
- I'm on M3
Summary: MTL4FX::FrameInterpolator::encodeToCommandBuffer(MTL4::CommandBuffer*) records, commits, and the GPU signals completion, but writes zero bytes to the output texture. The encode is a silent no-op — identical to documented issues 146436460 and 146436741 for MTL4FXTemporalScaler/DenoisedScaler. Diagnostic trace: Output texture zeroed before encode: 0/32768 non-zero bytes Output texture after encode (delta=0.5): 0/32768 non-zero bytes Changed bytes: 0/32768 ← definitive no-op Input textures verified: correct (R=1, G=0 vs R=0, G=1)
I dont really know how to explain this, the result its just blank.
Thanks for the byte-count trace. Having a concrete before and after made this testable directly. I built a minimal Objective-C case against MTL4FXFrameInterpolator and reproduced a silent no-op with the same shape you describe. One change flipped it to writing full output: adding an MTLResidencySet that covers the textures.
Measured on an M5 Max running macOS 26.5.1. I encoded into an MTL4CommandBuffer, waited on a shared event, pre-filled the output, then compared it byte for byte:
- No residency set:
encodeToCommandBuffer:returns no error, the event signals, and about 960 of 32768 bytes differ afterward. The count varies between runs. - With a residency set covering the input and output textures: 32768 of 32768 bytes differ, and every pixel reads back the value I had written into
colorTexture.
I ran that at RGBA16Float 256x256, RGBA16Float 64x64, and RGBA8Unorm 128x64. The last two are both 32768 bytes, to cover either reading of your figure. The result was the same in all three.
Why residency is worth checking first: Metal 4 does not offer the per-encoder residency calls that Metal 3 had. There is no useResource: or useHeap: on any Metal 4 encoder. Resources are made resident through MTLResidencySet, attached either to the queue with addResidencySet: or to the command buffer with useResidencySet:. If nothing covers a resource, the encode still records and the command buffer still completes, which is the behavior you are seeing.
The MTL4CommandBuffer documentation notes an ordering detail that I did not measure, and it is easy to miss. beginCommandBufferWithAllocator: states that after calling it, "any prior calls to useResidencySet: and useResidencySets:count: on this command buffer instance no longer apply. Make sure to call these methods again to signal your residency requirements to Metal." So a useResidencySet: call placed before beginCommandBufferWithAllocator: has no effect, with no diagnostic.
Separately, your detection method may be hiding a result. Zeroing the output and counting non-zero bytes cannot tell "never written" apart from "written as black", since both score zero. My first version of this test had the same problem. Pre-filling the output with a sentinel byte and counting bytes that differ from it distinguishes the two. That is worth changing regardless of what the cause turns out to be, because it changes what your diagnostic is able to report.
There are a few things I cannot determine from your post, and the answer depends on them:
- Do you create an
MTLResidencySetcontaining the input and output textures, and attach it to the queue or the command buffer? If you already do, then residency is not your cause and I would look elsewhere. - What
usagedid you give the output texture? On my machine the interpolator reportsoutputTextureUsageasMTLTextureUsageShaderWrite. A texture created for render-target use only would not satisfy that. The interpolator also publishescolorTextureUsage,depthTextureUsage, andmotionTextureUsage, so reading those back and comparing against how you created each texture is a quick check. - Which textures are you binding? Frame interpolation reads
colorTexture,prevColorTexture,depthTexture, andmotionTexture, and writesoutputTexture. All of them are nullable, so binding only two of them encodes cleanly. In my test that alone did not cause the no-op, but it does affect what the output should contain once the encode does run. - What pixel formats and dimensions are you using, and does
newFrameInterpolatorWithDevice:compiler:return a non-nil object? Worth also checking+supportsMetal4FX:, which is a separate query from+supportsDevice:.
If you can share those, I can be more specific.
References: