Render advanced 3D graphics and perform data-parallel computations using graphics processors using Metal.

Posts under Metal tag

200 Posts

Post

Replies

Boosts

Views

Activity

Data corruption when using MTLBlitCommandEncoder.copy from buffer to texture
When we use MTLBlitCommandEncoder.copy to copy from buffer to textures with specific size, we find that the texture content is corruppt. The specific rules we found are: On Apple GPUs (reproduced on Apple M5, macOS 26.3 and iPhone 13 / A15), for ASTC textures uploaded to private storage via the blit encoder, the sampler / texel-fetch mis-addresses the tail of a mip level when ALL of the following hold: that level's texel width is an exact multiple of the 16 KB page width (32 blocks) — 128 texels for ASTC 4x4, 192 for 6x6, 256 for 8x8; the texture width is not divisible by 2^level (a partial block column exists in the chain); that level is taller than one page (>32 block rows). The level's first 128 texel rows (first page row, 4x4) are always read correctly; everything beyond is mis-addressed. Reads landing on unmapped/invalid memory decode as opaque magenta (1,0,1); reads aliasing valid memory show wrong image content. This produced visible purple artifacts in a shipping game (impostor tree atlas, 514x1024 ASTC 4x4, mip2 bottom half). The minimum reproduce code is: ASTCMipTailBugRepro.swift The output is: 514x1024 blit(private) mip2: CORRUPTED (bottom of mip2 mis-addressed) 512x1024 blit(private) mip2: INTACT 514x1024 replaceRegion mip2: INTACT
1
0
887
2d
Metal FP32 arithmetic rounding-mode and denormal controls for deterministic shaders
Metal already provides a strong precise floating-point contract. With fast math disabled (mathMode = .safe and mathFloatingPointFunctions = .precise, or -fno-fast-math), the Metal Shading Language specification requires correctly rounded FP32 add, subtract, multiply, reciprocal, divide, sqrt, rsqrt, and fma. I am looking for clarification and, if necessary, API support for the two remaining pieces needed for portable bit-exact numerical shaders: Arithmetic rounding mode MSL §8.2 says either round-to-nearest-ties-to-even or round-toward-zero may be supported for floating-point operations. I cannot find a way to select or query the arithmetic rounding mode. The newer MTLCompileOptions.floatingPointConversionRoundingMode appears to apply only to narrowing float-to-float conversions, not arithmetic operations. Do all currently supported Apple GPU families use round-to-nearest-ties-to-even for precise FP32 add/subtract/multiply/divide/sqrt? If so, could that be made a documented guarantee? Otherwise, could Metal expose an arithmetic rounding-mode compile option and a corresponding MTLDevice capability query? Denormal behavior MSL §8.1 and §8.5 permit denormalized FP32 operands and results to be flushed to zero, including with fast math disabled. I cannot find a control or capability query for preserving denormal inputs and results. Do current Apple GPU families support denormal-preserving FP32 arithmetic? Could Metal expose a preserve/flush mode and a MTLDevice query? A convenient end state would be a queryable strict FP32 configuration combining: safe math; precise FP32 functions; contraction disabled when separate rounding points are required; round-to-nearest-ties-to-even arithmetic; preserved FP32 denormal inputs and results; defined signed-zero, infinity, and NaN behavior. My use case is deterministic GPU numerical simulation. A small compute-shader probe can identify effective rounding and denormal behavior on one GPU/OS/compiler combination, but it cannot provide the portable or future-proof contract needed by applications and higher-level APIs such as WebGPU. Related cross-API work: SPIR-V/Vulkan: https://github.com/KhronosGroup/SPIRV-Registry/issues/448 HLSL/DXIL/D3D12: https://github.com/microsoft/hlsl-specs/issues/926 WebGPU/WGSL umbrella issue: https://github.com/gpuweb/gpuweb/issues/2259 Relevant Metal documentation: Metal Shading Language Specification, §§1.6.3 and 8.1–8.5: https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf MTLCompileOptions.mathMode: https://developer.apple.com/documentation/metal/mtlcompileoptions/mathmode MTLCompileOptions.floatingPointConversionRoundingMode: https://developer.apple.com/documentation/metal/mtlcompileoptions/floatingpointconversionroundingmode
1
0
2.1k
3d
Metal 4 and object lifetime
I have a metal kit view and drain the draw method of its delegate like shown below. Let's say I have one or more MTLBuffers with vertex resources bound via the argument table. When is it ok to drop these buffers? As far as I know one cannot schedule a completion handler In Metal 4 and I haven't been able to find any documentation about the lifetime requirements here. Any pointers/ideas appreciated. class RenderCoordinator: NSObject, MTKViewDelegate { public func draw(in view: MTKView) { let commandAllocator: any MTL4CommandAllocator = ... let commandBuffer: any MTL4CommandBuffer = ... let commandQueue: any MTL4CommandQueue = ... guard let drawable = view.currentDrawable else { return } commandBuffer.beginCommandBuffer(allocator: commandAllocator) let state: any MTLRenderPipelineState = ... let encoder: any MTL4RenderCommandEncoder = ... let argTable: any MTL4ArgumentTable = ... encoder.setRenderPipelineState(state) encoder.setArgumentTable(argTable, stages: .vertex) commandBuffer.endCommandBuffer() commandQueue.waitForDrawable(drawable) commandQueue.commit([commandBuffer]) commandQueue.signalDrawable(drawable) drawable.present() } }
2
0
1.3k
3d
macOS 27 beta: ProMotion refresh cadence is unstable, causing constant scroll judder
FB24091347 On macOS 27.0 beta (26A5388g), MacBook Pro M4 Pro, the built-in ProMotion display never settles on a stable refresh cadence. Scrolling in SwiftUI judders constantly. The same app binary was smooth on macOS 26, and is smooth on a 120 Hz ProMotion iPad. I captured two 60-second Instruments traces — same app, same scene, same scrolling, no external display — changing only the display's refresh-rate setting. On ProMotion the vsync interval standard deviation is 4.093 ms across six different cadences, mostly flip-flopping between 120 Hz and 60 Hz. Forced to a fixed 60 Hz it drops to 0.391 ms with a single cadence. The app presented an identical 59 fps median in both runs — frame production is perfectly steady, the display just holds each frame for an unpredictable length of time. That's what makes this nasty: it's invisible to every frame-rate metric, so it looks like the app got slow when nothing about the app changed. I spent most of a day profiling my own code before realising the app was never the problem. Workaround: force the built-in display to 60 Hz. Worth noting, because it complicates the picture: attaching a 60 Hz Studio Display makes the built-in smooth, but the Studio itself then judders — despite its own vsync cadence measuring perfectly stable. So refresh rate alone isn't the whole story, and there may be a second mechanism. The clean, reproducible, single-variable result is the ProMotion vs forced-60 Hz comparison on the built-in panel. If you can reproduce this on an M-series MacBook Pro on 27 beta, please file a duplicate referencing FB24091347.
5
0
1.4k
1w
Stress-testing Metal compute pipelines using Autolykos workload characteristics
I have been using a small macOS research project to exercise Metal with a workload that differs from rendering and dense machine-learning kernels. Autolykos v2 is useful for this because it combines a large, height-dependent working set with pseudo-random reads, integer-heavy hashing, sustained execution, and periodic replacement of the dataset. The project happens to be a miner, but my question here is strictly about Metal compute behaviour. On the Apple M4 system used for these measurements, the full dataset contained 216,430,305 elements of 32 bytes each: 6.93 GB, or about 6.45 GiB, held in a .storageModePrivate buffer. For every nonce, the search kernel: computes an index seed, performs 32 pseudo-random 32-byte dataset reads, accumulates the eight 32-bit limbs into a 256-bit sum, and applies a final BLAKE2b compression and target comparison. The normal dispatch uses 128 threads per threadgroup. The wider pipeline also builds the next height's dataset in chunks on a separate command queue while search continues, and keeps two search command buffers in flight. I record command-buffer wall time, gpuStartTime/gpuEndTime, unions of overlapping intervals, and thermal state. To estimate the ceiling imposed by the random gathers, I added a non-consensus microbenchmark. It retains the normal seed calculation, index distribution, all 32 dataset reads, and the complete accumulation, but omits the final BLAKE2b compression. The accumulated result remains observable through a comparison, so the gather loop cannot simply disappear. I expected this stripped kernel to be at least slightly faster. Instead, an order-balanced campaign on an M4 produced: complete search kernel: 3.108 million nonces/s median active throughput gather-only kernel: 2.952 million nonces/s ratio: 105.3% All four same-round ratios were between 103.18% and 105.74%. Each measured run used the full dataset, a 30-second search interval, an excluded warm-up, and a start-temperature gate below 50 °C. Both compute pipeline states reported maxTotalThreadsPerThreadgroup == 1024. My conservative conclusion was not to pursue speculative register-pressure or manual memory-level-parallelism rewrites. The access pattern appears sufficiently dominant, while the supposedly simpler microbenchmark may have changed the compiled pipeline in a way that makes it a poor upper-bound model. My questions are: Can removing the trailing arithmetic legitimately make a memory-latency-heavy Metal kernel slower by changing register allocation, instruction scheduling, or the amount of useful latency hiding? Or would you first suspect a flaw in this kind of gather-only benchmark construction? Also, which Metal GPU counters are the most reliable way to distinguish memory-latency saturation from register-limited occupancy in a long-running compute kernel? I am looking at compute occupancy, buffer and ALU limiters, bandwidth, and cache behaviour, but maxTotalThreadsPerThreadgroup alone is clearly too coarse to explain the result. This is one hardware-specific observation rather than a general claim about Apple GPUs. If useful, I can reduce the workload to a smaller standalone reproducer. The source code, benchmark driver, and complete campaign report are available here: https://github.com/giffeler/ergometal The detailed measurements and validation procedure for this comparison are documented here: https://github.com/giffeler/ergometal/blob/main/Benchmarks/2026-08-15-search-gather-ceiling-ab.md
0
0
650
2w
On-screen RealityView starves CADisplayLink to 30 Hz on ProMotion (Mac Catalyst)
FB24536235 On Mac Catalyst under macOS 27, a plain CADisplayLink asking for CAFrameRateRange(minimum: 60, maximum: 60, preferred: 60) gets serviced at 30 Hz for as long as a RealityView is on screen in the same window. The link does nothing per tick but count, so there's nothing of mine to blame it on. RealityKit's own statistics overlay reads 60.41 fps in the same frame. Click a segmented control that removes the RealityView and the same link goes straight back to 60. Nothing else changes. That's the whole reproducer, and I've attached it to the radar. It only happens while the display panel is in ProMotion mode. Set the built-in to a fixed 60 Hz and it's correct again. With an external 60 Hz display attached the roles swap: the built-in is fine and the external drops to somewhere between 18 and 30, and setting the built-in to 60 Hz fixes that one too without touching the external's own settings. A raw MTKView presenting continuously at 60, at 120, and on a 120 Hz link presenting every second callback are all fine, so it isn't continuous presentation and it isn't the requested rate. It's RealityKit specifically. Worth knowing if you're testing: RealityView on Catalyst is an ARView underneath, so both paths give you the same answer. This is VERY rough for anything that puts RealityKit next to a UI. In an editor that's the sidebar, the inspector, gizmos, drag handles, every display-link-driven or UIKit animation in the window running at half rate around a viewport that stays smooth. Likely Related to FB24091347, which is the same defect seen as SwiftUI scroll judder. If you can reproduce either, please file a duplicate. Attached two screenshots; first with promotion enabled, second with promotion off. PLEASE fix this, it drives me crazy and there seems to be no workaround. On release day of macOS 27 our app will likely be blamed for it by users and my hands are tied. Thank you!
0
1
233
2w
Xcode 27 Beta 5 unable to link Core Image kernels unless MACOSX_DEPLOYMENT_TARGET = macOS 27
The Metal linker in the latest Xcode 27 Beta 5 is failing to link against the Core Image framework unless the current MACOSX_DEPLOYMENT_TARGET is set to macOS 27 Golden Gate Beta. MTLLINKER_FLAGS = -framework CoreImage It looks like the CoreImage framework that shipped in the Beta 5 SDK fails to include versions of CoreImage.metallib to support prior versions of macOS. If your current deployment target is, for example, macOS 13.5, the build log will show a warning: air-lld: warning: ignoring file '/Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage.metallib', file AIR version (2.9) is bigger than the one of the target being linked (2.5) ...and produce this error: air-lld: error: symbol(s) not found for target 'air64_v25-apple-macosx13.5.0' metal: error: air-lld command failed with exit code 1 (use -v to see invocation) The above warning + error happen for any MACOSX_DEPLOYMENT_TARGET up to and including macOS 26. Only when setting it to macOS 27 does the linker succeed. Linking against the Core Image framework is required to package your own CIKernels that use Metal’s [[stitchable]] attribute, in lieu of the previous method based on: MTL_COMPILER_FLAGS = -fcikernel and MTLLINKER_FLAGS = -fcikernel. Has anyone else run into this very same issue? (Filed as FB24345950) Any workarounds you’d be willing to share? Would pulling an older version of the CoreImage framework from a previous SDK, and linking explicitly against it work too? Thanks! Gabe
1
1
132
2w
Xcode 27.0 b5, macOS 26.6.1, Metal build fails: symbols not found for air64_v28
I've just downloaded the Xcode 27.0 beta 5 on a macOS 26.6.1 machine and tried to build my app (which includes Metal CoreImage kernels). I'm met with a new (to me) error; /Users/…/Developer/…/air-lld:1:1 symbol(s) not found for target 'air64_v28-apple-macosx26.0.0' and from the build log; air-lld: warning: ignoring file '/Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage.metallib', file AIR version (2.9) is bigger than the one of the target being linked (2.8) air-lld: error: symbol(s) not found for target 'air64_v28-apple-macosx26.0.0' metal: error: air-lld command failed with exit code 1 (use -v to see invocation) I have no MTL_LANGUAGE_REVISION in my build settings. If I add one, with value Metal41 the app builds fine, but crashes at runtime as Metal 4.1 isn't supported on macOS 26. I imagine this is a beta Xcode and or macOS SDK bug, is there a workaround?
1
1
1.2k
2w
Photogrammetry with Object masks hangs and terminates with masks of objects
[PhotogrammetrySample]) with objectMask set and traps on my ios26.5.2, see the attached screenshot on feedback FB24379913 , it gets to the function and hangs . Even the folder reconstruction with lazy sequence as recommended from your video sample also doesn;t complete as it can't find alignment and displays the CoreOC.PhotogrammetrySession.Error error 6 means alignment failed. What can be done or do you guys expose any functions that can be used to check or trace or handle these internally The ObjectMasks are actually segmentation masks from an segmentation algorithm I will appreciate a timely response and willing to provide more clarity and informations, thank you so much for your understanding
2
0
710
3w
Metal Shader Converter thread safety
Hello Apple! We've got offline shader compilation from HLSL -> Metallib using DXC -> SPIR-V -> metal.exe. This works okay for the most part, but it requires the creation of intermediate files to pass to/from the metal.exe process and we've had some issues with metal.exe sometimes not launching (probably our fault). Then we noticed Metal Shader Converter (MSC) exists and has a DLL - this looks way better since there's no need to launch processes or store intermediate files. However, upon trying to replace metal.exe with it I quickly ran into rampant heap corruption. I was surprised because the docs claim this: Each thread in your program needs to create its own instance of IRCompiler to avoid race conditions. But once I start calling IRCompilerAllocCompileAndLink in parallel all hell breaks loose, whether or not each thread has its own IRCompiler. I figured I must be doing something wrong, so I removed my attempt and compiled DXC locally with the MSC integration and encountered the exact same heap corruption. So I'm inclined to think the library isn't actually thread safe, but I'm wondering if there's something I'm missing? I tried all 3 versions of MSC just in case it was a problem with 3.0, but I got the same result each time. The only way to make it work was to surround compilation with a mutex, which makes its use pointless in our case.
2
0
1.2k
3w
How can a local AI agent use MLX/Metal unattended on macOS while remaining confined to an authorized workspace?
How can a local AI agent use MLX/Metal unattended while remaining confined to an authorized workspace? I am developing an AI-driven local media-processing workflow on an Apple-silicon Mac and am trying to understand the correct architecture for allowing it to run unattended without giving the AI agent unrestricted access to my primary personal computer. I am not a software engineer, so I may be missing an established macOS mechanism or using the wrong terminology. I would appreciate guidance from people familiar with MLX, Metal, sandboxing, and macOS security. What I am building I use OpenAI Codex as the local execution/software-development agent. The working system currently: ingests and verifies original video and still media while preserving immutable originals; performs visual semantic analysis and divides video into meaningful time-coded segments; separately analyzes spoken language rather than assuming audio and video are semantically equivalent; uses MLX Whisper locally on Apple silicon for time-coded speech transcription; stores visual and language semantics in a relational SQLite media catalog. These five stages are working. My current test corpus contains 148 original media files, 126 visual semantic segments, and 765 speech segments. The next stages are AI editorial construction from the semantic database and generation of instructions/scripts for a DaVinci Resolve rough cut. The security architecture I want Codex to operate autonomously within a deliberately bounded development environment. I do not want to solve this simply by granting an autonomous agent Full Disk Access to my primary personal Mac. The concern is ordinary fault containment. Codex generates and executes scripts, invokes applications and command-line tools, and manipulates files. A mistaken path or defective generated script should not have unrestricted consequences for the rest of my computer. I therefore separated AI execution from ordinary personal files. Codex is configured for Workspace Write access with explicitly authorized project roots. Canonical media resides on a separately authorized external SSD, and temporary AI working artifacts are kept separately. Ordinary Python and FFmpeg operations now run autonomously within these authorized areas. The problem The difficulty appears when the workflow invokes capabilities that cannot operate inside the ordinary Codex sandbox. The clearest example is MLX Whisper. I am using: MLX Whisper 0.4.3 mlx-community/whisper-small-mlx Apple silicon local transcription MLX Whisper works successfully and its transcription quality is sufficient for my semantic-retrieval application. However, MLX could not access Apple Metal/GPU execution from inside the ordinary Codex sandbox. Codex therefore requested permission to execute the transcription operation outside the sandbox. Once approved, MLX/Metal worked and the entire corpus was successfully transcribed. The processing therefore works, but the workflow cannot run genuinely unattended. A future operation should be able to run: new media → integrity verification → visual semantic analysis → MLX Whisper transcription → language semantic analysis → SQLite update → QA But if execution stops midway waiting for a human to click Allow, the pipeline is not operationally autonomous. What I have already tried I initially encountered permission problems even with ordinary file operations. I therefore: separated Codex work from ordinary personal documents; created dedicated project/work areas; explicitly authorized the required working roots; configured Workspace Write; separately authorized the external media repository; tested shell/Python and FFmpeg operations within those boundaries. Those changes worked. Routine Python and FFmpeg operations now run without approval prompts. The remaining issue occurs with MLX/Metal and some other application/runtime operations that require sandbox escalation. My question Is there a supported architecture for allowing a local AI agent to invoke MLX/Metal and other deliberately authorized development tools unattended, while still confining the agent to defined project/workspace boundaries rather than granting unrestricted access to the entire Mac? For example, should I be investigating: App Sandbox entitlements; a signed helper tool or XPC service; security-scoped resources; a dedicated executable with appropriate entitlements; a different method of launching MLX/Metal; or another macOS mechanism? In particular, can Metal/GPU access coexist with persistent bounded filesystem access without requiring interactive approval each time the AI invokes it? I am also unsure which security layer is actually responsible here: the Codex sandbox, macOS App Sandbox, TCC, executable/code-signing rules, Metal restrictions, or some interaction among them. If this kind of bounded unattended execution is intentionally not supported, that would also be useful to know. My alternative would be a dedicated Apple-silicon Mac containing only the AI-development environment and replaceable project data, where broader permissions would have a much smaller failure domain. I can provide the Codex configuration, exact successful and failing commands, directory/root configuration, macOS/hardware information, and sandbox diagnostics. I would particularly appreciate guidance on which security layer is causing the MLX/Metal escalation and what the supported architecture would be for this use case. Thank you.
0
0
441
3w
2D Soft Shadows with SpriteKit and Metal
Hi! I'd like to share an implementation of 2D soft shadows using SpriteKit and custom Metal rendering. GitHub Repo SpriteKit-SoftShadows The demo app runs on iOS and Mac Catalyst. The soft shadow implementation is based on Scott Lembcke's algorithm. Pipeline The app uses MTKView to drive the rendering loop at the desired frame rate. Each frame: SpriteKit renders the scene into a Metal texture using SKRenderer. The CPU sends each light's properties and the relevant shape edges to a Metal vertex shader. The vertex shader projects shadow geometry from each edge. A fragment shader calculates the shadow opacity at each pixel, producing a soft shadow mask for each light. A final fragment shader combines the SpriteKit texture, lights, and shadow masks to produce the displayed image. The SwiftUI controls update variables inside the SpriteKit scene through @Observable. The rendering loop consumes the new values on its next cycle. Let me know if you have any feedback!
0
0
599
Aug ’26
Are relaxed threadgroup atomics (atomic_fetch_add) officially supported on the Metal 4.0 feature set, or only 4.1?
I'm writing GPU compute kernels (parallel prefix-sum and histogram) that rely on threadgroup-address-space atomics specifically atomic_fetch_add_explicit on a threadgroup atomic_uint, with memory_order_relaxed. Setup: Device: Apple M5, macOS 26.5 Toolchain reports MSL 4.0 / AIR 2.8 (i.e. the "Metal 4.0" feature level) Note: I'm generating AIR (Apple IR) directly rather than emitting MSL source this is through a custom compute backend (Julia's Metal.jl), not the standard MSL front end. What I observe: These threadgroup atomics compile and produce correct results, and give a meaningful speedup over a non-atomic (scan-based) fallback. I've validated correctness across a 256-bin histogram and a full multi-pass radix sort no mismatches. The question: Some capability checks gate threadgroup atomic support behind Metal 4.1, and my device reports 4.0 yet they clearly work. So: Are relaxed integer threadgroup atomics (atomic_fetch_add on threadgroup atomic_uint, relaxed ordering) officially supported on the Metal 4.0 feature set for current Apple Silicon, or is this unsupported behavior that happens to work? Is there a specific MSL version or GPU family that is the true minimum for these operations? Does the answer differ at the AIR / feature-set level (what I'm targeting) vs. the MSL front end, given I'm feeding AIR to the compiler directly? I ask because a downstream library is (reasonably) hesitant to enable this path unless it's officially supported rather than relying on undefined behavior. Any authoritative guidance or a pointer to the relevant feature-set/GPU-family documentation would be hugely appreciated. Thanks!
1
0
674
Aug ’26
Metal rendering application is not releasing resources
I am developing a metal based ray tracing rendering application (running heavy GPU kernels). I am sometimes "forcefully quitting" my application and I can see the application is not in the activity monitor. But I can see the windowserver is using %97 the GPU. The mac gets hotter and hotter. I kill the windowserver, re-login it is still the case. The only way to fix is to restart the mac. I have checked if there are any zombie processes, there are none. I am 3-4 month into Mac development (I used many rendering APIs e.g. before under Windows and Linux, they release the resources automatically unless the driver is very broken), but I believe when you force quit or exit gracefully, regarding application should release resources. I may be missing some knowledge. Does anybody have an idea? I had added every corner a graceful exit code but once the kernel has some infinite loop the clean up cannot happen. In Windows there are some driver reload mechanisms to recover when GPU is stuck, is there a similar system ?
2
1
1.9k
Aug ’26
MTL4FXFrameInterpolator no-op on MTL4CommandBuffer
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.
1
0
721
Aug ’26
What would case a compute kernel to run 100x slower only if it's run after a previous kernel.
I have two compute kernels. The first kernel pre_initization_0 initializes some buffers using a parallel random number generator initialize some buffers. The second kernel pre_sum_weights_0 effectively sums weighted values buffer of a much smaller dimension. #include <metal_stdlib> #include <metal_simdgroup> using namespace metal; struct mt_state { array<uint32_t, 624> array; uint16_t index; }; float random(device mt_state &state) { uint16_t k = state.index; uint16_t j = (k + 1) % 624; uint32_t x = (state.array[k] & 0x80000000U) | (state.array[j] & 0x7fffffffU); uint32_t xA = x >> 1; if (x & 0x00000001U) { xA ^= 0x9908b0dfU; } j = (k + 397) % 624; x = state.array[j]^xA; state.array[k] = x; state.index = (k + 1) % 624; uint32_t y = x^(x >> 11); y = y^((y << 7) & 0x9d2c5680U); y = y^((y << 15) & 0xefc60000U); return static_cast<float> (y^(y >> 18)); } kernel void pre_initization_0( device float *vc30c09b98 [[buffer(0)]], // x used 0 device float *vc30c09c38 [[buffer(1)]], // v_{||} used 0 device float *vc30c09cd8 [[buffer(2)]], // v_{\perp} used 0 device mt_state *sc30c50c18 [[buffer(3)]], constant uint32_t &offset [[buffer(4)]], uint index [[thread_position_in_grid]]) { if (offset + index < 3000000) { device mt_state &rc30c50c18 = sc30c50c18[index]; // used 4 const float rc30c06218 = 2.17689351e-08; // used 1 const float rc30c06318 = -46.7484322; // used 1 const float rc30c0a458 = fma(rc30c06218, random(rc30c50c18), rc30c06318); // used 1 const float rc30c07718 = 5.18059896e-05; // used 1 const float rc30c06798 = -1; // used 1 const float rc30c06618 = 2.32830644e-10; // used 2 const float rc30c06718 = 1.17549435e-38; // used 2 const float rc30c0a4f8 = fma(rc30c06618, random(rc30c50c18), rc30c06718); // used 1 const float r1034f02f8 = log(rc30c0a4f8); // used 1 const float rc30c06818 = rc30c06798*r1034f02f8; // used 1 const float r1034eedf8 = sqrt(rc30c06818); // used 1 const float rc30c06418 = 1.46291812e-09; // used 1 const float rc30c06498 = rc30c06418*random(rc30c50c18); // used 1 const float r1034eee68 = sin(rc30c06498); // used 1 const float rc30c06a18 = r1034eedf8*r1034eee68; // used 1 const float rc30c07698 = rc30c07718*rc30c06a18; // used 1 const float rc30c07598 = 3.3356411e-09; // used 1 const float rc30c07318 = -241213328; // used 1 const float rc30c0a598 = fma(rc30c06618, random(rc30c50c18), rc30c06718); // used 1 const float r1034ef238 = log(rc30c0a598); // used 1 const float rc30c06c98 = rc30c07318*r1034ef238; // used 1 const float rc310b0018 = sqrt(rc30c06c98); // used 1 const float rc30c07618 = rc30c07598*rc310b0018; // used 1 vc30c09b98[offset + index] = rc30c0a458; vc30c09c38[offset + index] = rc30c07698; vc30c09cd8[offset + index] = rc30c07618; } } kernel void pre_sum_weights_0( constant float *vc30c09b98 [[buffer(0)]], // x used 7 device atomic_float *vc30c0a098 [[buffer(1)]], const texture1d<float, access::read> ac310a9000 [[texture(0)]], const texture1d<float, access::read> ac310d8000 [[texture(1)]], const texture1d<float, access::read> ac310d9000 [[texture(2)]], const texture1d<float, access::read> ac310da000 [[texture(3)]], uint index [[thread_position_in_grid]]) { if (index < 3000000) { const float rc30c09b98 = vc30c09b98[index]; // x used 7 const float rc30c07798 = 0.0935904533; // used 2 const float rc30d08318 = rc30c09b98 - rc30c07798; // used 1 const ushort ic30d08398 = (ushort)min(max((rc30d08318 - -46.7484322)/0.0935904533,(float)0),(float)999); // used 1 const float rc30c07c98 = -5.34242535; // used 1 const ushort ic30d08018 = (ushort)min(max((rc30c09b98 - -46.7484322)/0.0935904533,(float)0),(float)999); // used 5 const float rc30c51018 = ac310da000.read(ic30d08018).r; // used 1 const float rc30c0a6d8 = fma(rc30c07c98, rc30c09b98, rc30c51018); // used 1 const float rc30c07a18 = -10.6848507; // used 1 const float rc30c50e98 = ac310d8000.read(ic30d08018).r; // used 1 const float rc30c0a778 = fma(rc30c07a18, rc30c09b98, rc30c50e98); // used 1 const float rc30c07b98 = rc30c0a6d8*rc30c0a778; // used 1 atomic_fetch_add_explicit(&vc30c0a098[ic30d08398], rc30c07b98, memory_order_relaxed); // used 1 const float rc30c07c18 = 0.75; // used 1 const float rc30c07e18 = 114.166031; // used 1 const float rc30c50d18 = ac310a9000.read(ic30d08018).r; // used 1 const float rc30d08098 = rc30c50d18 - rc30c09b98; // used 1 const float rc30c07d98 = rc30d08098*rc30d08098; // used 1 const float rc30c07e98 = rc30c07e18*rc30c07d98; // used 1 const float rc30d08218 = rc30c07c18 - rc30c07e98; // used 1 atomic_fetch_add_explicit(&vc30c0a098[ic30d08018], rc30d08218, memory_order_relaxed); // used 1 const float rc30d08498 = rc30c07798 + rc30c09b98; // used 1 const ushort ic30d08418 = (ushort)min(max((rc30d08498 - -46.7484322)/0.0935904533,(float)0),(float)999); // used 1 const float rc30c07b18 = 0.5; // used 1 const float rc30c07918 = 1.5; // used 1 const float rc30c07818 = 10.6848507; // used 1 const float rc30c50f98 = ac310d9000.read(ic30d08018).r; // used 1 const float rc30d08298 = rc30c50f98 - rc30c09b98; // used 1 const float rc30c07a98 = rc30c07818*rc30d08298; // used 1 const float rc30d08118 = rc30c07918 - rc30c07a98; // used 1 const float rc30d34018 = rc30d08118*rc30d08118; // used 1 const float rc30d34098 = rc30c07b18*rc30d34018; // used 1 atomic_fetch_add_explicit(&vc30c0a098[ic30d08418], rc30d34098, memory_order_relaxed); // used 1 } } Running some timing experiments, If I initialize [[buffer(0)]] of the pre_sum_weights_0 kernel on the CPU, and run the kernel, it will run in 0.0489688 s measured using GPUEndTime - GPUStartTime in a completedHander for the command buffer. If I run the initialization kernel pre_initization_0 before this, the kernel execution time of pre_sum_weights_0 slows to 7.20396 s. Note I am launching these kernels from different command buffers.
1
0
730
Aug ’26
Pinch gesture not recognized on MTKView when attaching it to a RealityView using a ViewAttachmentComponent in a immersive space
Hello! We are seeing a problem with a SwiftUI view that wraps an MTKView and that MTKView uses gesture recognizers from UIKit. One of those gestures we are using is UIPinchGestureRecognizer. And that gesture isn’t recognized at all when the SwiftUI view is attached to a RealityView using the ViewAttachmentComponent AND the RealityView is being shown in an ImmersiveSpace. If the SwiftUI view is attached to the RealityView using the init that has an attachment closure then pinching works fine there. So this definitely seems like a bug. Here is some code to help you reproduce the problem. Run this on a Vision Pro device. A simple red square will be rendered and if a single tap or pinch gesture is recognized on the red square, it will print to the console. App Code: import SwiftUI @main struct VisionPinchProblemsApp: App { var body: some Scene { WindowGroup { MenuView() } ImmersiveSpace(id: "RedSquare") { RedSquareView() } } } View code: import MetalKit import RealityKit import SwiftUI import UIKit struct MenuView: View { @Environment(\.openImmersiveSpace) private var openImmersiveSpace @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace @State private var showImmersiveSpace = false @State private var immersiveSpaceIsOpen = false var body: some View { Form { Toggle("Show red square", isOn: $showImmersiveSpace) .task(id: showImmersiveSpace) { if showImmersiveSpace { await openImmersiveSpace(id: "RedSquare") immersiveSpaceIsOpen = true } else { if immersiveSpaceIsOpen { await dismissImmersiveSpace() immersiveSpaceIsOpen = false } } } } .onDisappear { // Attempt to close the immersive space on the way out. Task { if immersiveSpaceIsOpen { await dismissImmersiveSpace() } } } } } struct RedSquareView: View { let metalViewAttachmentID = "metalID" var body: some View { // Adds SwiftUI view using attachments closure. // Pinching and single taps are recognized here! // RealityView { content, attachments in // if let metalViewEntity = attachments.entity(for: metalViewAttachmentID) { // metalViewEntity.position = [0, 1, -1.25] // content.add(metalViewEntity) // } // } placeholder: { // ProgressView() // } attachments: { // Attachment(id: metalViewAttachmentID) { // MetalView() // } // } // Add SwiftUI view using ViewAttachmentComponent. // Pinching is not recognized here! // Single tapping is recognized ! // Why doesn't the red square show up in the Vision Pro simulator? RealityView { content in let metalViewEntity = Entity() let metalView = MetalView() .frame(width: 500, height: 500) let component = ViewAttachmentComponent(rootView: metalView) metalViewEntity.components.set(component) metalViewEntity.position = [0, 1, -1.25] content.add(metalViewEntity) } placeholder: { ProgressView() } } } struct MetalView: UIViewRepresentable { var device: MTLDevice? init() { self.device = MTLCreateSystemDefaultDevice() } func makeUIView(context: Context) -> MTKView { let mtkView = MTKView() mtkView.device = device mtkView.clearColor = MTLClearColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) mtkView.delegate = context.coordinator let pinchGesture = UIPinchGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handlePinch(_:))) mtkView.addGestureRecognizer(pinchGesture) let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handleTap(_:))) mtkView.addGestureRecognizer(tapGesture) return mtkView } func updateUIView(_ uiView: MTKView, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, MTKViewDelegate { var parent: MetalView init(_ parent: MetalView) { self.parent = parent } func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } func draw(in view: MTKView) { guard let drawable = view.currentDrawable else { return } guard let descriptor = view.currentRenderPassDescriptor else { return } let commandQueue = parent.device?.makeCommandQueue() let commandBuffer = commandQueue?.makeCommandBuffer() let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: descriptor) renderEncoder?.endEncoding() commandBuffer?.present(drawable) commandBuffer?.commit() } @objc func handlePinch(_ sender: UIPinchGestureRecognizer) { print("Pinch detected") } @objc func handleTap(_ sender: UITapGestureRecognizer) { print("Tap detected") } } }
1
0
803
Aug ’26
Core Image kernel sampling broken in iOS27 DB4
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
2
4
2.4k
Jul ’26
Residency set memory not freed if process performs no GPU operation
Feedback report: FB23959296 If a process creates a residency set, calls requestResidency, endResidency, and then releases the residency set without ever having done any GPU operations, the memory from the residency set is not freed. Workaround: if the application runs any GPU operation (even an operation not involving the residency set) at any point in its lifecycle (before/while/after creating/releasing the residency set), the memory is freed properly. This was observed in the context of an application that makes an AI model resident in GPU-accessible memory. If the user unloads the model without running any prompts, the memory is not freed. The model occupies ~16GB of RAM so a lot of memory is being leaked. Reproduction: Store the repro.m and workaround.m files from below Run the following commands (repro.m demonstrates the bug; workaround.m demonstrates the workaround): $ clang -framework Foundation -framework Metal -o repro repro.m $ ./repro Footprint at start: 0.00 GB Footprint after buffer allocation: 4.30 GB Footprint 5s after teardown: 4.30 GB $ clang -framework Foundation -framework Metal -o workaround workaround.m $ ./workaround Footprint at start: 0.00 GB Footprint after buffer allocation: 4.37 GB Footprint 5s after teardown: 0.01 GB Expected behavior: Footprint 5s after teardown should be ~0 GB, i.e., the memory is freed. Observed behavior: Footprint 5s after teardown is 4.30 GB, i.e., the memory is not freed. Versions: XCode: 26.6 (17F113) Clang: 21.0.0 (clang-2100.1.1.101, arm64-apple-darwin25.5.0) macOS: 26.5.2 (25F84) Files: repro.m: // Build: clang -framework Foundation -framework Metal -o repro repro.m #import <Metal/Metal.h> #include <mach/mach.h> // Returns the physical memory footprint of the process. static double footprint_gb(void) { task_vm_info_data_t info; mach_msg_type_number_t n = TASK_VM_INFO_COUNT; task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &n); return (double)info.phys_footprint / 1e9; } int main(int argc, char ** argv) { printf("Footprint at start: %5.2f GB\n", footprint_gb()); @autoreleasepool { id<MTLDevice> dev = MTLCreateSystemDefaultDevice(); // Allocate ~4GB of memory. const size_t size = 4ULL << 30; id<MTLBuffer> buf = [dev newBufferWithLength:size options:MTLResourceStorageModeShared]; memset(buf.contents, 0xab, size); // fault the pages in printf("Footprint after buffer allocation: %5.2f GB\n", footprint_gb()); MTLResidencySetDescriptor * desc = [[MTLResidencySetDescriptor alloc] init]; id<MTLResidencySet> rset = [dev newResidencySetWithDescriptor:desc error:nil]; [desc release]; [rset addAllocation:buf]; [rset commit]; [rset requestResidency]; [rset endResidency]; [rset removeAllAllocations]; [rset commit]; [rset release]; [buf release]; [dev release]; } sleep(5); printf("Footprint 5s after teardown: %5.2f GB\n", footprint_gb()); return 0; } workaround.m: // Build: clang -framework Foundation -framework Metal -o workaround workaround.m #import <Metal/Metal.h> #include <mach/mach.h> // Returns the physical memory footprint of the process. static double footprint_gb(void) { task_vm_info_data_t info; mach_msg_type_number_t n = TASK_VM_INFO_COUNT; task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &n); return (double)info.phys_footprint / 1e9; } // Performing any work on the GPU ensures the memory from the residency set will be released. static void do_dummy_work(id<MTLDevice> dev, id<MTLCommandQueue> queue) { @autoreleasepool { id<MTLBuffer> tmp = [dev newBufferWithLength:1 options:MTLResourceStorageModeShared]; id<MTLCommandBuffer> cb = [queue commandBuffer]; id<MTLBlitCommandEncoder> enc = [cb blitCommandEncoder]; [enc fillBuffer:tmp range:NSMakeRange(0, 1) value:0]; [enc endEncoding]; [cb commit]; [tmp release]; } } int main(int argc, char ** argv) { printf("Footprint at start: %5.2f GB\n", footprint_gb()); @autoreleasepool { id<MTLDevice> dev = MTLCreateSystemDefaultDevice(); id<MTLCommandQueue> queue = [dev newCommandQueue]; // Workaround that ensures the memory will be released. // It also works if we call this after the residency set release or at any point in between. do_dummy_work(dev, queue); // Allocate ~4GB of memory. const size_t size = 4ULL << 30; id<MTLBuffer> buf = [dev newBufferWithLength:size options:MTLResourceStorageModeShared]; memset(buf.contents, 0xab, size); // fault the pages in printf("Footprint after buffer allocation: %5.2f GB\n", footprint_gb()); MTLResidencySetDescriptor * desc = [[MTLResidencySetDescriptor alloc] init]; id<MTLResidencySet> rset = [dev newResidencySetWithDescriptor:desc error:nil]; [desc release]; [rset addAllocation:buf]; [rset commit]; [rset requestResidency]; [rset endResidency]; [rset removeAllAllocations]; [rset commit]; [rset release]; [buf release]; [queue release]; [dev release]; } sleep(5); printf("Footprint 5s after teardown: %5.2f GB\n", footprint_gb()); return 0; }
0
0
910
Jul ’26
Data corruption when using MTLBlitCommandEncoder.copy from buffer to texture
When we use MTLBlitCommandEncoder.copy to copy from buffer to textures with specific size, we find that the texture content is corruppt. The specific rules we found are: On Apple GPUs (reproduced on Apple M5, macOS 26.3 and iPhone 13 / A15), for ASTC textures uploaded to private storage via the blit encoder, the sampler / texel-fetch mis-addresses the tail of a mip level when ALL of the following hold: that level's texel width is an exact multiple of the 16 KB page width (32 blocks) — 128 texels for ASTC 4x4, 192 for 6x6, 256 for 8x8; the texture width is not divisible by 2^level (a partial block column exists in the chain); that level is taller than one page (>32 block rows). The level's first 128 texel rows (first page row, 4x4) are always read correctly; everything beyond is mis-addressed. Reads landing on unmapped/invalid memory decode as opaque magenta (1,0,1); reads aliasing valid memory show wrong image content. This produced visible purple artifacts in a shipping game (impostor tree atlas, 514x1024 ASTC 4x4, mip2 bottom half). The minimum reproduce code is: ASTCMipTailBugRepro.swift The output is: 514x1024 blit(private) mip2: CORRUPTED (bottom of mip2 mis-addressed) 512x1024 blit(private) mip2: INTACT 514x1024 replaceRegion mip2: INTACT
Replies
1
Boosts
0
Views
887
Activity
2d
Metal FP32 arithmetic rounding-mode and denormal controls for deterministic shaders
Metal already provides a strong precise floating-point contract. With fast math disabled (mathMode = .safe and mathFloatingPointFunctions = .precise, or -fno-fast-math), the Metal Shading Language specification requires correctly rounded FP32 add, subtract, multiply, reciprocal, divide, sqrt, rsqrt, and fma. I am looking for clarification and, if necessary, API support for the two remaining pieces needed for portable bit-exact numerical shaders: Arithmetic rounding mode MSL §8.2 says either round-to-nearest-ties-to-even or round-toward-zero may be supported for floating-point operations. I cannot find a way to select or query the arithmetic rounding mode. The newer MTLCompileOptions.floatingPointConversionRoundingMode appears to apply only to narrowing float-to-float conversions, not arithmetic operations. Do all currently supported Apple GPU families use round-to-nearest-ties-to-even for precise FP32 add/subtract/multiply/divide/sqrt? If so, could that be made a documented guarantee? Otherwise, could Metal expose an arithmetic rounding-mode compile option and a corresponding MTLDevice capability query? Denormal behavior MSL §8.1 and §8.5 permit denormalized FP32 operands and results to be flushed to zero, including with fast math disabled. I cannot find a control or capability query for preserving denormal inputs and results. Do current Apple GPU families support denormal-preserving FP32 arithmetic? Could Metal expose a preserve/flush mode and a MTLDevice query? A convenient end state would be a queryable strict FP32 configuration combining: safe math; precise FP32 functions; contraction disabled when separate rounding points are required; round-to-nearest-ties-to-even arithmetic; preserved FP32 denormal inputs and results; defined signed-zero, infinity, and NaN behavior. My use case is deterministic GPU numerical simulation. A small compute-shader probe can identify effective rounding and denormal behavior on one GPU/OS/compiler combination, but it cannot provide the portable or future-proof contract needed by applications and higher-level APIs such as WebGPU. Related cross-API work: SPIR-V/Vulkan: https://github.com/KhronosGroup/SPIRV-Registry/issues/448 HLSL/DXIL/D3D12: https://github.com/microsoft/hlsl-specs/issues/926 WebGPU/WGSL umbrella issue: https://github.com/gpuweb/gpuweb/issues/2259 Relevant Metal documentation: Metal Shading Language Specification, §§1.6.3 and 8.1–8.5: https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf MTLCompileOptions.mathMode: https://developer.apple.com/documentation/metal/mtlcompileoptions/mathmode MTLCompileOptions.floatingPointConversionRoundingMode: https://developer.apple.com/documentation/metal/mtlcompileoptions/floatingpointconversionroundingmode
Replies
1
Boosts
0
Views
2.1k
Activity
3d
Metal 4 and object lifetime
I have a metal kit view and drain the draw method of its delegate like shown below. Let's say I have one or more MTLBuffers with vertex resources bound via the argument table. When is it ok to drop these buffers? As far as I know one cannot schedule a completion handler In Metal 4 and I haven't been able to find any documentation about the lifetime requirements here. Any pointers/ideas appreciated. class RenderCoordinator: NSObject, MTKViewDelegate { public func draw(in view: MTKView) { let commandAllocator: any MTL4CommandAllocator = ... let commandBuffer: any MTL4CommandBuffer = ... let commandQueue: any MTL4CommandQueue = ... guard let drawable = view.currentDrawable else { return } commandBuffer.beginCommandBuffer(allocator: commandAllocator) let state: any MTLRenderPipelineState = ... let encoder: any MTL4RenderCommandEncoder = ... let argTable: any MTL4ArgumentTable = ... encoder.setRenderPipelineState(state) encoder.setArgumentTable(argTable, stages: .vertex) commandBuffer.endCommandBuffer() commandQueue.waitForDrawable(drawable) commandQueue.commit([commandBuffer]) commandQueue.signalDrawable(drawable) drawable.present() } }
Replies
2
Boosts
0
Views
1.3k
Activity
3d
macOS 27 beta: ProMotion refresh cadence is unstable, causing constant scroll judder
FB24091347 On macOS 27.0 beta (26A5388g), MacBook Pro M4 Pro, the built-in ProMotion display never settles on a stable refresh cadence. Scrolling in SwiftUI judders constantly. The same app binary was smooth on macOS 26, and is smooth on a 120 Hz ProMotion iPad. I captured two 60-second Instruments traces — same app, same scene, same scrolling, no external display — changing only the display's refresh-rate setting. On ProMotion the vsync interval standard deviation is 4.093 ms across six different cadences, mostly flip-flopping between 120 Hz and 60 Hz. Forced to a fixed 60 Hz it drops to 0.391 ms with a single cadence. The app presented an identical 59 fps median in both runs — frame production is perfectly steady, the display just holds each frame for an unpredictable length of time. That's what makes this nasty: it's invisible to every frame-rate metric, so it looks like the app got slow when nothing about the app changed. I spent most of a day profiling my own code before realising the app was never the problem. Workaround: force the built-in display to 60 Hz. Worth noting, because it complicates the picture: attaching a 60 Hz Studio Display makes the built-in smooth, but the Studio itself then judders — despite its own vsync cadence measuring perfectly stable. So refresh rate alone isn't the whole story, and there may be a second mechanism. The clean, reproducible, single-variable result is the ProMotion vs forced-60 Hz comparison on the built-in panel. If you can reproduce this on an M-series MacBook Pro on 27 beta, please file a duplicate referencing FB24091347.
Replies
5
Boosts
0
Views
1.4k
Activity
1w
Stress-testing Metal compute pipelines using Autolykos workload characteristics
I have been using a small macOS research project to exercise Metal with a workload that differs from rendering and dense machine-learning kernels. Autolykos v2 is useful for this because it combines a large, height-dependent working set with pseudo-random reads, integer-heavy hashing, sustained execution, and periodic replacement of the dataset. The project happens to be a miner, but my question here is strictly about Metal compute behaviour. On the Apple M4 system used for these measurements, the full dataset contained 216,430,305 elements of 32 bytes each: 6.93 GB, or about 6.45 GiB, held in a .storageModePrivate buffer. For every nonce, the search kernel: computes an index seed, performs 32 pseudo-random 32-byte dataset reads, accumulates the eight 32-bit limbs into a 256-bit sum, and applies a final BLAKE2b compression and target comparison. The normal dispatch uses 128 threads per threadgroup. The wider pipeline also builds the next height's dataset in chunks on a separate command queue while search continues, and keeps two search command buffers in flight. I record command-buffer wall time, gpuStartTime/gpuEndTime, unions of overlapping intervals, and thermal state. To estimate the ceiling imposed by the random gathers, I added a non-consensus microbenchmark. It retains the normal seed calculation, index distribution, all 32 dataset reads, and the complete accumulation, but omits the final BLAKE2b compression. The accumulated result remains observable through a comparison, so the gather loop cannot simply disappear. I expected this stripped kernel to be at least slightly faster. Instead, an order-balanced campaign on an M4 produced: complete search kernel: 3.108 million nonces/s median active throughput gather-only kernel: 2.952 million nonces/s ratio: 105.3% All four same-round ratios were between 103.18% and 105.74%. Each measured run used the full dataset, a 30-second search interval, an excluded warm-up, and a start-temperature gate below 50 °C. Both compute pipeline states reported maxTotalThreadsPerThreadgroup == 1024. My conservative conclusion was not to pursue speculative register-pressure or manual memory-level-parallelism rewrites. The access pattern appears sufficiently dominant, while the supposedly simpler microbenchmark may have changed the compiled pipeline in a way that makes it a poor upper-bound model. My questions are: Can removing the trailing arithmetic legitimately make a memory-latency-heavy Metal kernel slower by changing register allocation, instruction scheduling, or the amount of useful latency hiding? Or would you first suspect a flaw in this kind of gather-only benchmark construction? Also, which Metal GPU counters are the most reliable way to distinguish memory-latency saturation from register-limited occupancy in a long-running compute kernel? I am looking at compute occupancy, buffer and ALU limiters, bandwidth, and cache behaviour, but maxTotalThreadsPerThreadgroup alone is clearly too coarse to explain the result. This is one hardware-specific observation rather than a general claim about Apple GPUs. If useful, I can reduce the workload to a smaller standalone reproducer. The source code, benchmark driver, and complete campaign report are available here: https://github.com/giffeler/ergometal The detailed measurements and validation procedure for this comparison are documented here: https://github.com/giffeler/ergometal/blob/main/Benchmarks/2026-08-15-search-gather-ceiling-ab.md
Replies
0
Boosts
0
Views
650
Activity
2w
On-screen RealityView starves CADisplayLink to 30 Hz on ProMotion (Mac Catalyst)
FB24536235 On Mac Catalyst under macOS 27, a plain CADisplayLink asking for CAFrameRateRange(minimum: 60, maximum: 60, preferred: 60) gets serviced at 30 Hz for as long as a RealityView is on screen in the same window. The link does nothing per tick but count, so there's nothing of mine to blame it on. RealityKit's own statistics overlay reads 60.41 fps in the same frame. Click a segmented control that removes the RealityView and the same link goes straight back to 60. Nothing else changes. That's the whole reproducer, and I've attached it to the radar. It only happens while the display panel is in ProMotion mode. Set the built-in to a fixed 60 Hz and it's correct again. With an external 60 Hz display attached the roles swap: the built-in is fine and the external drops to somewhere between 18 and 30, and setting the built-in to 60 Hz fixes that one too without touching the external's own settings. A raw MTKView presenting continuously at 60, at 120, and on a 120 Hz link presenting every second callback are all fine, so it isn't continuous presentation and it isn't the requested rate. It's RealityKit specifically. Worth knowing if you're testing: RealityView on Catalyst is an ARView underneath, so both paths give you the same answer. This is VERY rough for anything that puts RealityKit next to a UI. In an editor that's the sidebar, the inspector, gizmos, drag handles, every display-link-driven or UIKit animation in the window running at half rate around a viewport that stays smooth. Likely Related to FB24091347, which is the same defect seen as SwiftUI scroll judder. If you can reproduce either, please file a duplicate. Attached two screenshots; first with promotion enabled, second with promotion off. PLEASE fix this, it drives me crazy and there seems to be no workaround. On release day of macOS 27 our app will likely be blamed for it by users and my hands are tied. Thank you!
Replies
0
Boosts
1
Views
233
Activity
2w
Xcode 27 Beta 5 unable to link Core Image kernels unless MACOSX_DEPLOYMENT_TARGET = macOS 27
The Metal linker in the latest Xcode 27 Beta 5 is failing to link against the Core Image framework unless the current MACOSX_DEPLOYMENT_TARGET is set to macOS 27 Golden Gate Beta. MTLLINKER_FLAGS = -framework CoreImage It looks like the CoreImage framework that shipped in the Beta 5 SDK fails to include versions of CoreImage.metallib to support prior versions of macOS. If your current deployment target is, for example, macOS 13.5, the build log will show a warning: air-lld: warning: ignoring file '/Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage.metallib', file AIR version (2.9) is bigger than the one of the target being linked (2.5) ...and produce this error: air-lld: error: symbol(s) not found for target 'air64_v25-apple-macosx13.5.0' metal: error: air-lld command failed with exit code 1 (use -v to see invocation) The above warning + error happen for any MACOSX_DEPLOYMENT_TARGET up to and including macOS 26. Only when setting it to macOS 27 does the linker succeed. Linking against the Core Image framework is required to package your own CIKernels that use Metal’s [[stitchable]] attribute, in lieu of the previous method based on: MTL_COMPILER_FLAGS = -fcikernel and MTLLINKER_FLAGS = -fcikernel. Has anyone else run into this very same issue? (Filed as FB24345950) Any workarounds you’d be willing to share? Would pulling an older version of the CoreImage framework from a previous SDK, and linking explicitly against it work too? Thanks! Gabe
Replies
1
Boosts
1
Views
132
Activity
2w
Xcode 27.0 b5, macOS 26.6.1, Metal build fails: symbols not found for air64_v28
I've just downloaded the Xcode 27.0 beta 5 on a macOS 26.6.1 machine and tried to build my app (which includes Metal CoreImage kernels). I'm met with a new (to me) error; /Users/…/Developer/…/air-lld:1:1 symbol(s) not found for target 'air64_v28-apple-macosx26.0.0' and from the build log; air-lld: warning: ignoring file '/Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage.metallib', file AIR version (2.9) is bigger than the one of the target being linked (2.8) air-lld: error: symbol(s) not found for target 'air64_v28-apple-macosx26.0.0' metal: error: air-lld command failed with exit code 1 (use -v to see invocation) I have no MTL_LANGUAGE_REVISION in my build settings. If I add one, with value Metal41 the app builds fine, but crashes at runtime as Metal 4.1 isn't supported on macOS 26. I imagine this is a beta Xcode and or macOS SDK bug, is there a workaround?
Replies
1
Boosts
1
Views
1.2k
Activity
2w
XCTHitchMetric doesn't work with MTKView
Hello! I was recently trying out XCTHitchMetric with my MTKView and it doesn't seem to record anything. But if I use the Animation Hitches instrument then I do see the hitches get recorded when a frame is presented on screen later than expected. I'm curious if there is a reason for this or maybe I am missing something?
Replies
0
Boosts
0
Views
118
Activity
3w
Photogrammetry with Object masks hangs and terminates with masks of objects
[PhotogrammetrySample]) with objectMask set and traps on my ios26.5.2, see the attached screenshot on feedback FB24379913 , it gets to the function and hangs . Even the folder reconstruction with lazy sequence as recommended from your video sample also doesn;t complete as it can't find alignment and displays the CoreOC.PhotogrammetrySession.Error error 6 means alignment failed. What can be done or do you guys expose any functions that can be used to check or trace or handle these internally The ObjectMasks are actually segmentation masks from an segmentation algorithm I will appreciate a timely response and willing to provide more clarity and informations, thank you so much for your understanding
Replies
2
Boosts
0
Views
710
Activity
3w
Metal Shader Converter thread safety
Hello Apple! We've got offline shader compilation from HLSL -> Metallib using DXC -> SPIR-V -> metal.exe. This works okay for the most part, but it requires the creation of intermediate files to pass to/from the metal.exe process and we've had some issues with metal.exe sometimes not launching (probably our fault). Then we noticed Metal Shader Converter (MSC) exists and has a DLL - this looks way better since there's no need to launch processes or store intermediate files. However, upon trying to replace metal.exe with it I quickly ran into rampant heap corruption. I was surprised because the docs claim this: Each thread in your program needs to create its own instance of IRCompiler to avoid race conditions. But once I start calling IRCompilerAllocCompileAndLink in parallel all hell breaks loose, whether or not each thread has its own IRCompiler. I figured I must be doing something wrong, so I removed my attempt and compiled DXC locally with the MSC integration and encountered the exact same heap corruption. So I'm inclined to think the library isn't actually thread safe, but I'm wondering if there's something I'm missing? I tried all 3 versions of MSC just in case it was a problem with 3.0, but I got the same result each time. The only way to make it work was to surround compilation with a mutex, which makes its use pointless in our case.
Replies
2
Boosts
0
Views
1.2k
Activity
3w
How can a local AI agent use MLX/Metal unattended on macOS while remaining confined to an authorized workspace?
How can a local AI agent use MLX/Metal unattended while remaining confined to an authorized workspace? I am developing an AI-driven local media-processing workflow on an Apple-silicon Mac and am trying to understand the correct architecture for allowing it to run unattended without giving the AI agent unrestricted access to my primary personal computer. I am not a software engineer, so I may be missing an established macOS mechanism or using the wrong terminology. I would appreciate guidance from people familiar with MLX, Metal, sandboxing, and macOS security. What I am building I use OpenAI Codex as the local execution/software-development agent. The working system currently: ingests and verifies original video and still media while preserving immutable originals; performs visual semantic analysis and divides video into meaningful time-coded segments; separately analyzes spoken language rather than assuming audio and video are semantically equivalent; uses MLX Whisper locally on Apple silicon for time-coded speech transcription; stores visual and language semantics in a relational SQLite media catalog. These five stages are working. My current test corpus contains 148 original media files, 126 visual semantic segments, and 765 speech segments. The next stages are AI editorial construction from the semantic database and generation of instructions/scripts for a DaVinci Resolve rough cut. The security architecture I want Codex to operate autonomously within a deliberately bounded development environment. I do not want to solve this simply by granting an autonomous agent Full Disk Access to my primary personal Mac. The concern is ordinary fault containment. Codex generates and executes scripts, invokes applications and command-line tools, and manipulates files. A mistaken path or defective generated script should not have unrestricted consequences for the rest of my computer. I therefore separated AI execution from ordinary personal files. Codex is configured for Workspace Write access with explicitly authorized project roots. Canonical media resides on a separately authorized external SSD, and temporary AI working artifacts are kept separately. Ordinary Python and FFmpeg operations now run autonomously within these authorized areas. The problem The difficulty appears when the workflow invokes capabilities that cannot operate inside the ordinary Codex sandbox. The clearest example is MLX Whisper. I am using: MLX Whisper 0.4.3 mlx-community/whisper-small-mlx Apple silicon local transcription MLX Whisper works successfully and its transcription quality is sufficient for my semantic-retrieval application. However, MLX could not access Apple Metal/GPU execution from inside the ordinary Codex sandbox. Codex therefore requested permission to execute the transcription operation outside the sandbox. Once approved, MLX/Metal worked and the entire corpus was successfully transcribed. The processing therefore works, but the workflow cannot run genuinely unattended. A future operation should be able to run: new media → integrity verification → visual semantic analysis → MLX Whisper transcription → language semantic analysis → SQLite update → QA But if execution stops midway waiting for a human to click Allow, the pipeline is not operationally autonomous. What I have already tried I initially encountered permission problems even with ordinary file operations. I therefore: separated Codex work from ordinary personal documents; created dedicated project/work areas; explicitly authorized the required working roots; configured Workspace Write; separately authorized the external media repository; tested shell/Python and FFmpeg operations within those boundaries. Those changes worked. Routine Python and FFmpeg operations now run without approval prompts. The remaining issue occurs with MLX/Metal and some other application/runtime operations that require sandbox escalation. My question Is there a supported architecture for allowing a local AI agent to invoke MLX/Metal and other deliberately authorized development tools unattended, while still confining the agent to defined project/workspace boundaries rather than granting unrestricted access to the entire Mac? For example, should I be investigating: App Sandbox entitlements; a signed helper tool or XPC service; security-scoped resources; a dedicated executable with appropriate entitlements; a different method of launching MLX/Metal; or another macOS mechanism? In particular, can Metal/GPU access coexist with persistent bounded filesystem access without requiring interactive approval each time the AI invokes it? I am also unsure which security layer is actually responsible here: the Codex sandbox, macOS App Sandbox, TCC, executable/code-signing rules, Metal restrictions, or some interaction among them. If this kind of bounded unattended execution is intentionally not supported, that would also be useful to know. My alternative would be a dedicated Apple-silicon Mac containing only the AI-development environment and replaceable project data, where broader permissions would have a much smaller failure domain. I can provide the Codex configuration, exact successful and failing commands, directory/root configuration, macOS/hardware information, and sandbox diagnostics. I would particularly appreciate guidance on which security layer is causing the MLX/Metal escalation and what the supported architecture would be for this use case. Thank you.
Replies
0
Boosts
0
Views
441
Activity
3w
2D Soft Shadows with SpriteKit and Metal
Hi! I'd like to share an implementation of 2D soft shadows using SpriteKit and custom Metal rendering. GitHub Repo SpriteKit-SoftShadows The demo app runs on iOS and Mac Catalyst. The soft shadow implementation is based on Scott Lembcke's algorithm. Pipeline The app uses MTKView to drive the rendering loop at the desired frame rate. Each frame: SpriteKit renders the scene into a Metal texture using SKRenderer. The CPU sends each light's properties and the relevant shape edges to a Metal vertex shader. The vertex shader projects shadow geometry from each edge. A fragment shader calculates the shadow opacity at each pixel, producing a soft shadow mask for each light. A final fragment shader combines the SpriteKit texture, lights, and shadow masks to produce the displayed image. The SwiftUI controls update variables inside the SpriteKit scene through @Observable. The rendering loop consumes the new values on its next cycle. Let me know if you have any feedback!
Replies
0
Boosts
0
Views
599
Activity
Aug ’26
Are relaxed threadgroup atomics (atomic_fetch_add) officially supported on the Metal 4.0 feature set, or only 4.1?
I'm writing GPU compute kernels (parallel prefix-sum and histogram) that rely on threadgroup-address-space atomics specifically atomic_fetch_add_explicit on a threadgroup atomic_uint, with memory_order_relaxed. Setup: Device: Apple M5, macOS 26.5 Toolchain reports MSL 4.0 / AIR 2.8 (i.e. the "Metal 4.0" feature level) Note: I'm generating AIR (Apple IR) directly rather than emitting MSL source this is through a custom compute backend (Julia's Metal.jl), not the standard MSL front end. What I observe: These threadgroup atomics compile and produce correct results, and give a meaningful speedup over a non-atomic (scan-based) fallback. I've validated correctness across a 256-bin histogram and a full multi-pass radix sort no mismatches. The question: Some capability checks gate threadgroup atomic support behind Metal 4.1, and my device reports 4.0 yet they clearly work. So: Are relaxed integer threadgroup atomics (atomic_fetch_add on threadgroup atomic_uint, relaxed ordering) officially supported on the Metal 4.0 feature set for current Apple Silicon, or is this unsupported behavior that happens to work? Is there a specific MSL version or GPU family that is the true minimum for these operations? Does the answer differ at the AIR / feature-set level (what I'm targeting) vs. the MSL front end, given I'm feeding AIR to the compiler directly? I ask because a downstream library is (reasonably) hesitant to enable this path unless it's officially supported rather than relying on undefined behavior. Any authoritative guidance or a pointer to the relevant feature-set/GPU-family documentation would be hugely appreciated. Thanks!
Replies
1
Boosts
0
Views
674
Activity
Aug ’26
Metal rendering application is not releasing resources
I am developing a metal based ray tracing rendering application (running heavy GPU kernels). I am sometimes "forcefully quitting" my application and I can see the application is not in the activity monitor. But I can see the windowserver is using %97 the GPU. The mac gets hotter and hotter. I kill the windowserver, re-login it is still the case. The only way to fix is to restart the mac. I have checked if there are any zombie processes, there are none. I am 3-4 month into Mac development (I used many rendering APIs e.g. before under Windows and Linux, they release the resources automatically unless the driver is very broken), but I believe when you force quit or exit gracefully, regarding application should release resources. I may be missing some knowledge. Does anybody have an idea? I had added every corner a graceful exit code but once the kernel has some infinite loop the clean up cannot happen. In Windows there are some driver reload mechanisms to recover when GPU is stuck, is there a similar system ?
Replies
2
Boosts
1
Views
1.9k
Activity
Aug ’26
MTL4FXFrameInterpolator no-op on MTL4CommandBuffer
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.
Replies
1
Boosts
0
Views
721
Activity
Aug ’26
What would case a compute kernel to run 100x slower only if it's run after a previous kernel.
I have two compute kernels. The first kernel pre_initization_0 initializes some buffers using a parallel random number generator initialize some buffers. The second kernel pre_sum_weights_0 effectively sums weighted values buffer of a much smaller dimension. #include <metal_stdlib> #include <metal_simdgroup> using namespace metal; struct mt_state { array<uint32_t, 624> array; uint16_t index; }; float random(device mt_state &state) { uint16_t k = state.index; uint16_t j = (k + 1) % 624; uint32_t x = (state.array[k] & 0x80000000U) | (state.array[j] & 0x7fffffffU); uint32_t xA = x >> 1; if (x & 0x00000001U) { xA ^= 0x9908b0dfU; } j = (k + 397) % 624; x = state.array[j]^xA; state.array[k] = x; state.index = (k + 1) % 624; uint32_t y = x^(x >> 11); y = y^((y << 7) & 0x9d2c5680U); y = y^((y << 15) & 0xefc60000U); return static_cast<float> (y^(y >> 18)); } kernel void pre_initization_0( device float *vc30c09b98 [[buffer(0)]], // x used 0 device float *vc30c09c38 [[buffer(1)]], // v_{||} used 0 device float *vc30c09cd8 [[buffer(2)]], // v_{\perp} used 0 device mt_state *sc30c50c18 [[buffer(3)]], constant uint32_t &offset [[buffer(4)]], uint index [[thread_position_in_grid]]) { if (offset + index < 3000000) { device mt_state &rc30c50c18 = sc30c50c18[index]; // used 4 const float rc30c06218 = 2.17689351e-08; // used 1 const float rc30c06318 = -46.7484322; // used 1 const float rc30c0a458 = fma(rc30c06218, random(rc30c50c18), rc30c06318); // used 1 const float rc30c07718 = 5.18059896e-05; // used 1 const float rc30c06798 = -1; // used 1 const float rc30c06618 = 2.32830644e-10; // used 2 const float rc30c06718 = 1.17549435e-38; // used 2 const float rc30c0a4f8 = fma(rc30c06618, random(rc30c50c18), rc30c06718); // used 1 const float r1034f02f8 = log(rc30c0a4f8); // used 1 const float rc30c06818 = rc30c06798*r1034f02f8; // used 1 const float r1034eedf8 = sqrt(rc30c06818); // used 1 const float rc30c06418 = 1.46291812e-09; // used 1 const float rc30c06498 = rc30c06418*random(rc30c50c18); // used 1 const float r1034eee68 = sin(rc30c06498); // used 1 const float rc30c06a18 = r1034eedf8*r1034eee68; // used 1 const float rc30c07698 = rc30c07718*rc30c06a18; // used 1 const float rc30c07598 = 3.3356411e-09; // used 1 const float rc30c07318 = -241213328; // used 1 const float rc30c0a598 = fma(rc30c06618, random(rc30c50c18), rc30c06718); // used 1 const float r1034ef238 = log(rc30c0a598); // used 1 const float rc30c06c98 = rc30c07318*r1034ef238; // used 1 const float rc310b0018 = sqrt(rc30c06c98); // used 1 const float rc30c07618 = rc30c07598*rc310b0018; // used 1 vc30c09b98[offset + index] = rc30c0a458; vc30c09c38[offset + index] = rc30c07698; vc30c09cd8[offset + index] = rc30c07618; } } kernel void pre_sum_weights_0( constant float *vc30c09b98 [[buffer(0)]], // x used 7 device atomic_float *vc30c0a098 [[buffer(1)]], const texture1d<float, access::read> ac310a9000 [[texture(0)]], const texture1d<float, access::read> ac310d8000 [[texture(1)]], const texture1d<float, access::read> ac310d9000 [[texture(2)]], const texture1d<float, access::read> ac310da000 [[texture(3)]], uint index [[thread_position_in_grid]]) { if (index < 3000000) { const float rc30c09b98 = vc30c09b98[index]; // x used 7 const float rc30c07798 = 0.0935904533; // used 2 const float rc30d08318 = rc30c09b98 - rc30c07798; // used 1 const ushort ic30d08398 = (ushort)min(max((rc30d08318 - -46.7484322)/0.0935904533,(float)0),(float)999); // used 1 const float rc30c07c98 = -5.34242535; // used 1 const ushort ic30d08018 = (ushort)min(max((rc30c09b98 - -46.7484322)/0.0935904533,(float)0),(float)999); // used 5 const float rc30c51018 = ac310da000.read(ic30d08018).r; // used 1 const float rc30c0a6d8 = fma(rc30c07c98, rc30c09b98, rc30c51018); // used 1 const float rc30c07a18 = -10.6848507; // used 1 const float rc30c50e98 = ac310d8000.read(ic30d08018).r; // used 1 const float rc30c0a778 = fma(rc30c07a18, rc30c09b98, rc30c50e98); // used 1 const float rc30c07b98 = rc30c0a6d8*rc30c0a778; // used 1 atomic_fetch_add_explicit(&vc30c0a098[ic30d08398], rc30c07b98, memory_order_relaxed); // used 1 const float rc30c07c18 = 0.75; // used 1 const float rc30c07e18 = 114.166031; // used 1 const float rc30c50d18 = ac310a9000.read(ic30d08018).r; // used 1 const float rc30d08098 = rc30c50d18 - rc30c09b98; // used 1 const float rc30c07d98 = rc30d08098*rc30d08098; // used 1 const float rc30c07e98 = rc30c07e18*rc30c07d98; // used 1 const float rc30d08218 = rc30c07c18 - rc30c07e98; // used 1 atomic_fetch_add_explicit(&vc30c0a098[ic30d08018], rc30d08218, memory_order_relaxed); // used 1 const float rc30d08498 = rc30c07798 + rc30c09b98; // used 1 const ushort ic30d08418 = (ushort)min(max((rc30d08498 - -46.7484322)/0.0935904533,(float)0),(float)999); // used 1 const float rc30c07b18 = 0.5; // used 1 const float rc30c07918 = 1.5; // used 1 const float rc30c07818 = 10.6848507; // used 1 const float rc30c50f98 = ac310d9000.read(ic30d08018).r; // used 1 const float rc30d08298 = rc30c50f98 - rc30c09b98; // used 1 const float rc30c07a98 = rc30c07818*rc30d08298; // used 1 const float rc30d08118 = rc30c07918 - rc30c07a98; // used 1 const float rc30d34018 = rc30d08118*rc30d08118; // used 1 const float rc30d34098 = rc30c07b18*rc30d34018; // used 1 atomic_fetch_add_explicit(&vc30c0a098[ic30d08418], rc30d34098, memory_order_relaxed); // used 1 } } Running some timing experiments, If I initialize [[buffer(0)]] of the pre_sum_weights_0 kernel on the CPU, and run the kernel, it will run in 0.0489688 s measured using GPUEndTime - GPUStartTime in a completedHander for the command buffer. If I run the initialization kernel pre_initization_0 before this, the kernel execution time of pre_sum_weights_0 slows to 7.20396 s. Note I am launching these kernels from different command buffers.
Replies
1
Boosts
0
Views
730
Activity
Aug ’26
Pinch gesture not recognized on MTKView when attaching it to a RealityView using a ViewAttachmentComponent in a immersive space
Hello! We are seeing a problem with a SwiftUI view that wraps an MTKView and that MTKView uses gesture recognizers from UIKit. One of those gestures we are using is UIPinchGestureRecognizer. And that gesture isn’t recognized at all when the SwiftUI view is attached to a RealityView using the ViewAttachmentComponent AND the RealityView is being shown in an ImmersiveSpace. If the SwiftUI view is attached to the RealityView using the init that has an attachment closure then pinching works fine there. So this definitely seems like a bug. Here is some code to help you reproduce the problem. Run this on a Vision Pro device. A simple red square will be rendered and if a single tap or pinch gesture is recognized on the red square, it will print to the console. App Code: import SwiftUI @main struct VisionPinchProblemsApp: App { var body: some Scene { WindowGroup { MenuView() } ImmersiveSpace(id: "RedSquare") { RedSquareView() } } } View code: import MetalKit import RealityKit import SwiftUI import UIKit struct MenuView: View { @Environment(\.openImmersiveSpace) private var openImmersiveSpace @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace @State private var showImmersiveSpace = false @State private var immersiveSpaceIsOpen = false var body: some View { Form { Toggle("Show red square", isOn: $showImmersiveSpace) .task(id: showImmersiveSpace) { if showImmersiveSpace { await openImmersiveSpace(id: "RedSquare") immersiveSpaceIsOpen = true } else { if immersiveSpaceIsOpen { await dismissImmersiveSpace() immersiveSpaceIsOpen = false } } } } .onDisappear { // Attempt to close the immersive space on the way out. Task { if immersiveSpaceIsOpen { await dismissImmersiveSpace() } } } } } struct RedSquareView: View { let metalViewAttachmentID = "metalID" var body: some View { // Adds SwiftUI view using attachments closure. // Pinching and single taps are recognized here! // RealityView { content, attachments in // if let metalViewEntity = attachments.entity(for: metalViewAttachmentID) { // metalViewEntity.position = [0, 1, -1.25] // content.add(metalViewEntity) // } // } placeholder: { // ProgressView() // } attachments: { // Attachment(id: metalViewAttachmentID) { // MetalView() // } // } // Add SwiftUI view using ViewAttachmentComponent. // Pinching is not recognized here! // Single tapping is recognized ! // Why doesn't the red square show up in the Vision Pro simulator? RealityView { content in let metalViewEntity = Entity() let metalView = MetalView() .frame(width: 500, height: 500) let component = ViewAttachmentComponent(rootView: metalView) metalViewEntity.components.set(component) metalViewEntity.position = [0, 1, -1.25] content.add(metalViewEntity) } placeholder: { ProgressView() } } } struct MetalView: UIViewRepresentable { var device: MTLDevice? init() { self.device = MTLCreateSystemDefaultDevice() } func makeUIView(context: Context) -> MTKView { let mtkView = MTKView() mtkView.device = device mtkView.clearColor = MTLClearColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) mtkView.delegate = context.coordinator let pinchGesture = UIPinchGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handlePinch(_:))) mtkView.addGestureRecognizer(pinchGesture) let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handleTap(_:))) mtkView.addGestureRecognizer(tapGesture) return mtkView } func updateUIView(_ uiView: MTKView, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, MTKViewDelegate { var parent: MetalView init(_ parent: MetalView) { self.parent = parent } func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } func draw(in view: MTKView) { guard let drawable = view.currentDrawable else { return } guard let descriptor = view.currentRenderPassDescriptor else { return } let commandQueue = parent.device?.makeCommandQueue() let commandBuffer = commandQueue?.makeCommandBuffer() let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: descriptor) renderEncoder?.endEncoding() commandBuffer?.present(drawable) commandBuffer?.commit() } @objc func handlePinch(_ sender: UIPinchGestureRecognizer) { print("Pinch detected") } @objc func handleTap(_ sender: UITapGestureRecognizer) { print("Tap detected") } } }
Replies
1
Boosts
0
Views
803
Activity
Aug ’26
Core Image kernel sampling broken in iOS27 DB4
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
Replies
2
Boosts
4
Views
2.4k
Activity
Jul ’26
Residency set memory not freed if process performs no GPU operation
Feedback report: FB23959296 If a process creates a residency set, calls requestResidency, endResidency, and then releases the residency set without ever having done any GPU operations, the memory from the residency set is not freed. Workaround: if the application runs any GPU operation (even an operation not involving the residency set) at any point in its lifecycle (before/while/after creating/releasing the residency set), the memory is freed properly. This was observed in the context of an application that makes an AI model resident in GPU-accessible memory. If the user unloads the model without running any prompts, the memory is not freed. The model occupies ~16GB of RAM so a lot of memory is being leaked. Reproduction: Store the repro.m and workaround.m files from below Run the following commands (repro.m demonstrates the bug; workaround.m demonstrates the workaround): $ clang -framework Foundation -framework Metal -o repro repro.m $ ./repro Footprint at start: 0.00 GB Footprint after buffer allocation: 4.30 GB Footprint 5s after teardown: 4.30 GB $ clang -framework Foundation -framework Metal -o workaround workaround.m $ ./workaround Footprint at start: 0.00 GB Footprint after buffer allocation: 4.37 GB Footprint 5s after teardown: 0.01 GB Expected behavior: Footprint 5s after teardown should be ~0 GB, i.e., the memory is freed. Observed behavior: Footprint 5s after teardown is 4.30 GB, i.e., the memory is not freed. Versions: XCode: 26.6 (17F113) Clang: 21.0.0 (clang-2100.1.1.101, arm64-apple-darwin25.5.0) macOS: 26.5.2 (25F84) Files: repro.m: // Build: clang -framework Foundation -framework Metal -o repro repro.m #import <Metal/Metal.h> #include <mach/mach.h> // Returns the physical memory footprint of the process. static double footprint_gb(void) { task_vm_info_data_t info; mach_msg_type_number_t n = TASK_VM_INFO_COUNT; task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &n); return (double)info.phys_footprint / 1e9; } int main(int argc, char ** argv) { printf("Footprint at start: %5.2f GB\n", footprint_gb()); @autoreleasepool { id<MTLDevice> dev = MTLCreateSystemDefaultDevice(); // Allocate ~4GB of memory. const size_t size = 4ULL << 30; id<MTLBuffer> buf = [dev newBufferWithLength:size options:MTLResourceStorageModeShared]; memset(buf.contents, 0xab, size); // fault the pages in printf("Footprint after buffer allocation: %5.2f GB\n", footprint_gb()); MTLResidencySetDescriptor * desc = [[MTLResidencySetDescriptor alloc] init]; id<MTLResidencySet> rset = [dev newResidencySetWithDescriptor:desc error:nil]; [desc release]; [rset addAllocation:buf]; [rset commit]; [rset requestResidency]; [rset endResidency]; [rset removeAllAllocations]; [rset commit]; [rset release]; [buf release]; [dev release]; } sleep(5); printf("Footprint 5s after teardown: %5.2f GB\n", footprint_gb()); return 0; } workaround.m: // Build: clang -framework Foundation -framework Metal -o workaround workaround.m #import <Metal/Metal.h> #include <mach/mach.h> // Returns the physical memory footprint of the process. static double footprint_gb(void) { task_vm_info_data_t info; mach_msg_type_number_t n = TASK_VM_INFO_COUNT; task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &n); return (double)info.phys_footprint / 1e9; } // Performing any work on the GPU ensures the memory from the residency set will be released. static void do_dummy_work(id<MTLDevice> dev, id<MTLCommandQueue> queue) { @autoreleasepool { id<MTLBuffer> tmp = [dev newBufferWithLength:1 options:MTLResourceStorageModeShared]; id<MTLCommandBuffer> cb = [queue commandBuffer]; id<MTLBlitCommandEncoder> enc = [cb blitCommandEncoder]; [enc fillBuffer:tmp range:NSMakeRange(0, 1) value:0]; [enc endEncoding]; [cb commit]; [tmp release]; } } int main(int argc, char ** argv) { printf("Footprint at start: %5.2f GB\n", footprint_gb()); @autoreleasepool { id<MTLDevice> dev = MTLCreateSystemDefaultDevice(); id<MTLCommandQueue> queue = [dev newCommandQueue]; // Workaround that ensures the memory will be released. // It also works if we call this after the residency set release or at any point in between. do_dummy_work(dev, queue); // Allocate ~4GB of memory. const size_t size = 4ULL << 30; id<MTLBuffer> buf = [dev newBufferWithLength:size options:MTLResourceStorageModeShared]; memset(buf.contents, 0xab, size); // fault the pages in printf("Footprint after buffer allocation: %5.2f GB\n", footprint_gb()); MTLResidencySetDescriptor * desc = [[MTLResidencySetDescriptor alloc] init]; id<MTLResidencySet> rset = [dev newResidencySetWithDescriptor:desc error:nil]; [desc release]; [rset addAllocation:buf]; [rset commit]; [rset requestResidency]; [rset endResidency]; [rset removeAllAllocations]; [rset commit]; [rset release]; [buf release]; [queue release]; [dev release]; } sleep(5); printf("Footprint 5s after teardown: %5.2f GB\n", footprint_gb()); return 0; }
Replies
0
Boosts
0
Views
910
Activity
Jul ’26