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

Metal Documentation

Posts under Metal subtopic

Post

Replies

Boosts

Views

Activity

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
386
15h
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
0
1.8k
1d
MPS bf16 softmax produces NaN on M5 Max (regression from M4) — breaks all on-device diffusion inference
Metal Performance Shaders produces from bf16/fp16 softmax for large attention tensors on , forcing the entire local generative-AI ecosystem to fall back to fp32. This is a . Environment Minimal reproduction Decomposed softmax on MPS — diffs = x - maxes produces NaN even though every element is identical (result should be all zeros): Real-world impact Running ComfyUI (the dominant local generative-AI UI) on M5 Max: Root cause (per PyTorch MPS maintainers) PyTorch maintainers (@drisspg, @albanD) have traced this to in the MPS/MPSGraph fused kernels. The NaN originates in the x - maxes subtraction inside softmax, then propagates through the attention block and the entire network. PyTorch cannot fix this — it is in the Metal/MPS kernel layer. Not isolated The M4-era fix ("fixed on macOS 15.1") did not survive onto M5, indicating the MPS fused-kernel precision fix was either reverted or not ported to the M5 GPU architecture. Request
0
0
417
3d
Behaviour of a 0-value `accelerationStructureID`
When constructing a MTLIndirectAccelerationStructureInstanceDescriptor, one specifies an accelerationStructureID. In the equivalents in both Vulkan and DirectX12, one can set it to zero to be an "inactive" instance. However, on Metal, this field does not appear to have any documentation, and thus it is difficult to figure out if there is similar behavior (and no other Metal documentation seems to mention this). Does setting this field to 0 (i.e. null) disable the instance? If not, is there any other way to have an equivalent effect?
0
0
436
3d
Best Way to Use MetalFX in Unreal Engine 5.7 for macOS Port?
Hi everyone, We’re currently porting a high-fidelity AA+ PC title built on Unreal Engine 5.7 to macOS (Apple Silicon), and we’re looking for guidance from anyone with experience in this area. At the moment, the game is already runnable on Mac, but not yet at a playable level — we’re seeing performance around 10–15 FPS on an M4 device. We’re actively analyzing and defining the work needed to reach production-quality performance on macOS. One of the key areas we’re exploring is leveraging MetalFX to improve frame rate. However, it seems there’s no official MetalFX plugin or direct integration available for Unreal Engine. Has anyone here successfully integrated MetalFX into a UE5 rendering pipeline, or found a recommended approach to do so? Any insights on best practices, workflows, or references (docs, samples, etc.) would be greatly appreciated. Thanks in advance!
4
0
1.7k
4d
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
574
6d
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
555
1w
Using CARenderer for off-screen rendering of WKWebView results in a blank screen for the web page content on iOS 16 system version.
Using the CRenderer off-screen rendering method for WKWebView results in a blank screen for the web page content on the iOS 16 system version, but it can successfully obtain the web page content screen on the iOS 18 system version. I need a solution to achieve the display of web page content on the 16 system version, with a frame rate of more than 60 frames per second.
1
0
1.1k
1w
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
3
2.1k
2w
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
773
2w
M5 Pro external 5K 165Hz display: Window animations and scrolling UI appear to render at ~60Hz/jitter while cursor remains perfectly smooth
Hello Apple engineers, I’m trying to determine whether what I’m seeing is expected behavior or a software issue with the new M5 Pro platform. System MacBook Pro (M5 Pro) Latest macOS Beta External 5K 165Hz monitor connected via DisplayPort Refresh rate correctly detected as 165Hz What I observe The display itself is clearly running at 165Hz. For example: Mouse cursor movement is extremely smooth. Dragging the desktop by holding an empty area is also perfectly smooth. However: Moving application windows feels much closer to 60Hz. Scrolling in Safari, Chrome and other applications also appears to run at a much lower frame rate than the display refresh rate. Mission Control animations sometimes show similar micro-stutters. This makes the cursor and desktop movement noticeably smoother than normal window animations. ⸻ Troubleshooting already performed Different DisplayPort cables Different timing configurations Different resolutions / HiDPI modes DSC enabled and disabled Refresh rate confirmed at 165Hz Same behavior across multiple applications The issue appears unrelated to the monitor itself because the cursor is clearly rendered at the full refresh rate. ⸻ Additional observation Interestingly, I previously used another external 4K 144Hz HDR monitor and did not notice this behavior. I also found another M5 Pro user reporting nearly the same issue: external 165Hz display smooth cursor window dragging jitter / micro-stuttering At the same time, I haven’t found similar reports from M4 Pro or the base M5 running the same monitor. ⸻ My question Could this be related to the new M5 Pro display pipeline (WindowServer, Display Engine, or DCP)? Is there any known issue regarding high-refresh-rate external displays on the M5 Pro platform? Or is there additional diagnostic logging (WindowServer, DCP, Metal, etc.) that would help identify whether frames are actually being presented at the display refresh rate? I’d be happy to provide: sysdiagnose WindowServer logs Screen recordings Display timing information IORegistry dumps if they would be helpful. Thank you!
3
1
331
Jul ’26
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.
0
0
380
Jul ’26
MDLAsset loads texture in usdz file loaded with wrong colorspace
I have a very basic usdz file from this repo I call loadTextures() after loading the usdz via MDLAsset. Inspecting the MDLTexture object I can tell it is assigning a colorspace of linear rgb instead of srgb although the image file in the usdz is srgb. This causes the textures to ultimately render as over saturated. In the code I later convert the MDLTexture to MTLTexture via MTKTextureLoader but if I set the srgb option it seems to ignore it. This significantly impacts the usefulness of Model I/O if it can't load a simple usdz texture correctly. Am I missing something? Thanks!
6
3
1.6k
Jul ’26
Background GPU Access availability
I would love to use Background GPU Access to do some video processing in the background. However the documentation of BGContinuedProcessingTaskRequest.Resources.gpu clearly states: Not all devices support background GPU use. For more information, see Performing long-running tasks on iOS and iPadOS. Is there a list available of currently released devices that do (or don't) support GPU background usage? That would help to understand what part of our user base can use this feature. (And what hardware we need to test this on as developers.) For example it seems that it isn't supported on an iPad Pro M1 with the current iOS 26 beta. The simulators also seem to not support the background GPU resource. So would be great to understand what hardware is capable of using this feature!
7
0
1.8k
Jul ’26
Timestamp counter heap always returns zero
Hi, I am trying to use a timestamp counter heap, but it always seems to report timestamp zero. Consider this example program: #include <Metal/Metal.h> #include <assert.h> int main(int argc, char *argv[]) { auto device = MTLCreateSystemDefaultDevice(); assert(device); auto descriptor = [MTL4CounterHeapDescriptor new]; [descriptor setType:MTL4CounterHeapTypeTimestamp]; [descriptor setCount:1]; auto heap = [device newCounterHeapWithDescriptor:descriptor error:nullptr]; assert(heap); [heap invalidateCounterRange:NSMakeRange(0, 1)]; auto command_buffer = [device newCommandBuffer]; assert(command_buffer); auto allocator = [device newCommandAllocator]; assert(allocator); [command_buffer beginCommandBufferWithAllocator:allocator]; auto encoder = [command_buffer computeCommandEncoder]; assert(encoder); [encoder writeTimestampWithGranularity:MTL4TimestampGranularityPrecise intoHeap:heap atIndex:0]; [encoder endEncoding]; [command_buffer endCommandBuffer]; auto queue = [device newMTL4CommandQueue]; assert(queue); auto event = [device newSharedEvent]; assert(event); [queue commit:&command_buffer count:1]; [queue signalEvent:event value:1]; [event waitUntilSignaledValue:1 timeoutMS:UINT64_MAX]; auto data = [heap resolveCounterRange:NSMakeRange(0, 1)]; printf("size %lu: %llu\n", data.length, *(uint64_t*)data.bytes); return 0; } Trying to compile and run: % clang++ -g -O0 -o test test.mm -framework Metal -framework Foundation && MTL_DEBUG_LAYER=1 ./test 2026-06-23 14:44:48.006 test[26472:1588857] Metal API Validation Enabled size 8: 0 I would have expected to receive size 8: [some random non-zero number] that number being a GPU timestamp of when the command was executed, but I always get zero. Does anybody have an idea of what I am doing wrong?
1
0
509
Jun ’26
Comprehensive documentation and literature
The WWDC videos like the new "Boost your graphics performance with the M5 and A19 GPUs" contain extremely valuable information and tips on how to discover, diagnose and remedy performance issues. They seem to serve as quick reminders and distilled summaries of more comprehensive documentation that I assume can be found somewhere. Where do we find the underlying comprehensive documentation that explains Apple Silicon GPU architecture? How can I learn to understand the basis of the data presented by the Xcode Metal Debugger? Any hints at external literature and resources are welcome.
1
0
496
Jun ’26
Documentation and literature
The WWDC videos like the new "Boost your graphics performance with the M5 and A19 GPUs" contain extremely valuable information and tips on how to discover, diagnose and remedy performance issues. They seem to serve as quick reminders and distilled summaries of more comprehensive documentation that I assume can be found somewhere. Where do we find the underlying comprehensive documentation that explains Apple Silicon GPU architecture? How can I learn to understand the basis of the data presented by the Xcode Metal Debugger? Any hints at external literature and resources are welcome.
0
0
487
Jun ’26
Performance Optimization for Large-Kernel Image Processing
I am processing large images where each output pixel depends on a large neighborhood of surrounding pixels. As a result, the shader performs a very high number of texture sampling operations, which appears to cause cache misses and becomes a performance bottleneck. Since neighboring threads often process adjacent pixels, many of the sampled pixels overlap between threads. Although each thread operates on a slightly different output pixel, a large portion of the texture accesses are effectively identical. Does Metal provide mechanisms that allow neighboring threads to share or synchronize intermediate results in order to reduce redundant texture fetches? Are there recommended approaches for exploiting data reuse across threads, for example through threadgroup memory or other Metal-specific features? In this type of workload, how effective is texture gathering (gather) for reducing sampling overhead, especially when only the RGB channels of an RGBA texture are required? Would using gather generally improve cache utilization and performance in this scenario? When using gather, what is the preferred way to handle texture borders and edge conditions without introducing per-thread branching (e.g., explicit if statements)? Any recommendations for optimizing large-radius neighborhood operations in Metal would be greatly appreciated.
1
0
511
Jun ’26
Memory allocation of textures in Metal
At which time does Metal allocate and deallocate memory for textures? I've observed that the textures live for the whole time of the commandBuffer. So, if I have multiple large textures that I need in subsequent shaders, it would make sense to work with multiple commandBuffers to enable deallocation in order to reduce peak memory usage. Is that correct? Do you have any other suggestions on how to reduce peak memory usage when working with large metal textures? Hint: I am using compute shaders only.
1
1
493
Jun ’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
386
Activity
15h
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
0
Views
1.8k
Activity
1d
MPS bf16 softmax produces NaN on M5 Max (regression from M4) — breaks all on-device diffusion inference
Metal Performance Shaders produces from bf16/fp16 softmax for large attention tensors on , forcing the entire local generative-AI ecosystem to fall back to fp32. This is a . Environment Minimal reproduction Decomposed softmax on MPS — diffs = x - maxes produces NaN even though every element is identical (result should be all zeros): Real-world impact Running ComfyUI (the dominant local generative-AI UI) on M5 Max: Root cause (per PyTorch MPS maintainers) PyTorch maintainers (@drisspg, @albanD) have traced this to in the MPS/MPSGraph fused kernels. The NaN originates in the x - maxes subtraction inside softmax, then propagates through the attention block and the entire network. PyTorch cannot fix this — it is in the Metal/MPS kernel layer. Not isolated The M4-era fix ("fixed on macOS 15.1") did not survive onto M5, indicating the MPS fused-kernel precision fix was either reverted or not ported to the M5 GPU architecture. Request
Replies
0
Boosts
0
Views
417
Activity
3d
Behaviour of a 0-value `accelerationStructureID`
When constructing a MTLIndirectAccelerationStructureInstanceDescriptor, one specifies an accelerationStructureID. In the equivalents in both Vulkan and DirectX12, one can set it to zero to be an "inactive" instance. However, on Metal, this field does not appear to have any documentation, and thus it is difficult to figure out if there is similar behavior (and no other Metal documentation seems to mention this). Does setting this field to 0 (i.e. null) disable the instance? If not, is there any other way to have an equivalent effect?
Replies
0
Boosts
0
Views
436
Activity
3d
Best Way to Use MetalFX in Unreal Engine 5.7 for macOS Port?
Hi everyone, We’re currently porting a high-fidelity AA+ PC title built on Unreal Engine 5.7 to macOS (Apple Silicon), and we’re looking for guidance from anyone with experience in this area. At the moment, the game is already runnable on Mac, but not yet at a playable level — we’re seeing performance around 10–15 FPS on an M4 device. We’re actively analyzing and defining the work needed to reach production-quality performance on macOS. One of the key areas we’re exploring is leveraging MetalFX to improve frame rate. However, it seems there’s no official MetalFX plugin or direct integration available for Unreal Engine. Has anyone here successfully integrated MetalFX into a UE5 rendering pipeline, or found a recommended approach to do so? Any insights on best practices, workflows, or references (docs, samples, etc.) would be greatly appreciated. Thanks in advance!
Replies
4
Boosts
0
Views
1.7k
Activity
4d
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
574
Activity
6d
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
555
Activity
1w
Using CARenderer for off-screen rendering of WKWebView results in a blank screen for the web page content on iOS 16 system version.
Using the CRenderer off-screen rendering method for WKWebView results in a blank screen for the web page content on the iOS 16 system version, but it can successfully obtain the web page content screen on the iOS 18 system version. I need a solution to achieve the display of web page content on the 16 system version, with a frame rate of more than 60 frames per second.
Replies
1
Boosts
0
Views
1.1k
Activity
1w
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
3
Views
2.1k
Activity
2w
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
773
Activity
2w
M5 Pro external 5K 165Hz display: Window animations and scrolling UI appear to render at ~60Hz/jitter while cursor remains perfectly smooth
Hello Apple engineers, I’m trying to determine whether what I’m seeing is expected behavior or a software issue with the new M5 Pro platform. System MacBook Pro (M5 Pro) Latest macOS Beta External 5K 165Hz monitor connected via DisplayPort Refresh rate correctly detected as 165Hz What I observe The display itself is clearly running at 165Hz. For example: Mouse cursor movement is extremely smooth. Dragging the desktop by holding an empty area is also perfectly smooth. However: Moving application windows feels much closer to 60Hz. Scrolling in Safari, Chrome and other applications also appears to run at a much lower frame rate than the display refresh rate. Mission Control animations sometimes show similar micro-stutters. This makes the cursor and desktop movement noticeably smoother than normal window animations. ⸻ Troubleshooting already performed Different DisplayPort cables Different timing configurations Different resolutions / HiDPI modes DSC enabled and disabled Refresh rate confirmed at 165Hz Same behavior across multiple applications The issue appears unrelated to the monitor itself because the cursor is clearly rendered at the full refresh rate. ⸻ Additional observation Interestingly, I previously used another external 4K 144Hz HDR monitor and did not notice this behavior. I also found another M5 Pro user reporting nearly the same issue: external 165Hz display smooth cursor window dragging jitter / micro-stuttering At the same time, I haven’t found similar reports from M4 Pro or the base M5 running the same monitor. ⸻ My question Could this be related to the new M5 Pro display pipeline (WindowServer, Display Engine, or DCP)? Is there any known issue regarding high-refresh-rate external displays on the M5 Pro platform? Or is there additional diagnostic logging (WindowServer, DCP, Metal, etc.) that would help identify whether frames are actually being presented at the display refresh rate? I’d be happy to provide: sysdiagnose WindowServer logs Screen recordings Display timing information IORegistry dumps if they would be helpful. Thank you!
Replies
3
Boosts
1
Views
331
Activity
Jul ’26
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
0
Boosts
0
Views
380
Activity
Jul ’26
MDLAsset loads texture in usdz file loaded with wrong colorspace
I have a very basic usdz file from this repo I call loadTextures() after loading the usdz via MDLAsset. Inspecting the MDLTexture object I can tell it is assigning a colorspace of linear rgb instead of srgb although the image file in the usdz is srgb. This causes the textures to ultimately render as over saturated. In the code I later convert the MDLTexture to MTLTexture via MTKTextureLoader but if I set the srgb option it seems to ignore it. This significantly impacts the usefulness of Model I/O if it can't load a simple usdz texture correctly. Am I missing something? Thanks!
Replies
6
Boosts
3
Views
1.6k
Activity
Jul ’26
Background GPU Access availability
I would love to use Background GPU Access to do some video processing in the background. However the documentation of BGContinuedProcessingTaskRequest.Resources.gpu clearly states: Not all devices support background GPU use. For more information, see Performing long-running tasks on iOS and iPadOS. Is there a list available of currently released devices that do (or don't) support GPU background usage? That would help to understand what part of our user base can use this feature. (And what hardware we need to test this on as developers.) For example it seems that it isn't supported on an iPad Pro M1 with the current iOS 26 beta. The simulators also seem to not support the background GPU resource. So would be great to understand what hardware is capable of using this feature!
Replies
7
Boosts
0
Views
1.8k
Activity
Jul ’26
Timestamp counter heap always returns zero
Hi, I am trying to use a timestamp counter heap, but it always seems to report timestamp zero. Consider this example program: #include <Metal/Metal.h> #include <assert.h> int main(int argc, char *argv[]) { auto device = MTLCreateSystemDefaultDevice(); assert(device); auto descriptor = [MTL4CounterHeapDescriptor new]; [descriptor setType:MTL4CounterHeapTypeTimestamp]; [descriptor setCount:1]; auto heap = [device newCounterHeapWithDescriptor:descriptor error:nullptr]; assert(heap); [heap invalidateCounterRange:NSMakeRange(0, 1)]; auto command_buffer = [device newCommandBuffer]; assert(command_buffer); auto allocator = [device newCommandAllocator]; assert(allocator); [command_buffer beginCommandBufferWithAllocator:allocator]; auto encoder = [command_buffer computeCommandEncoder]; assert(encoder); [encoder writeTimestampWithGranularity:MTL4TimestampGranularityPrecise intoHeap:heap atIndex:0]; [encoder endEncoding]; [command_buffer endCommandBuffer]; auto queue = [device newMTL4CommandQueue]; assert(queue); auto event = [device newSharedEvent]; assert(event); [queue commit:&command_buffer count:1]; [queue signalEvent:event value:1]; [event waitUntilSignaledValue:1 timeoutMS:UINT64_MAX]; auto data = [heap resolveCounterRange:NSMakeRange(0, 1)]; printf("size %lu: %llu\n", data.length, *(uint64_t*)data.bytes); return 0; } Trying to compile and run: % clang++ -g -O0 -o test test.mm -framework Metal -framework Foundation && MTL_DEBUG_LAYER=1 ./test 2026-06-23 14:44:48.006 test[26472:1588857] Metal API Validation Enabled size 8: 0 I would have expected to receive size 8: [some random non-zero number] that number being a GPU timestamp of when the command was executed, but I always get zero. Does anybody have an idea of what I am doing wrong?
Replies
1
Boosts
0
Views
509
Activity
Jun ’26
Comprehensive documentation and literature
The WWDC videos like the new "Boost your graphics performance with the M5 and A19 GPUs" contain extremely valuable information and tips on how to discover, diagnose and remedy performance issues. They seem to serve as quick reminders and distilled summaries of more comprehensive documentation that I assume can be found somewhere. Where do we find the underlying comprehensive documentation that explains Apple Silicon GPU architecture? How can I learn to understand the basis of the data presented by the Xcode Metal Debugger? Any hints at external literature and resources are welcome.
Replies
1
Boosts
0
Views
496
Activity
Jun ’26
Documentation and literature
The WWDC videos like the new "Boost your graphics performance with the M5 and A19 GPUs" contain extremely valuable information and tips on how to discover, diagnose and remedy performance issues. They seem to serve as quick reminders and distilled summaries of more comprehensive documentation that I assume can be found somewhere. Where do we find the underlying comprehensive documentation that explains Apple Silicon GPU architecture? How can I learn to understand the basis of the data presented by the Xcode Metal Debugger? Any hints at external literature and resources are welcome.
Replies
0
Boosts
0
Views
487
Activity
Jun ’26
Performance Optimization for Large-Kernel Image Processing
I am processing large images where each output pixel depends on a large neighborhood of surrounding pixels. As a result, the shader performs a very high number of texture sampling operations, which appears to cause cache misses and becomes a performance bottleneck. Since neighboring threads often process adjacent pixels, many of the sampled pixels overlap between threads. Although each thread operates on a slightly different output pixel, a large portion of the texture accesses are effectively identical. Does Metal provide mechanisms that allow neighboring threads to share or synchronize intermediate results in order to reduce redundant texture fetches? Are there recommended approaches for exploiting data reuse across threads, for example through threadgroup memory or other Metal-specific features? In this type of workload, how effective is texture gathering (gather) for reducing sampling overhead, especially when only the RGB channels of an RGBA texture are required? Would using gather generally improve cache utilization and performance in this scenario? When using gather, what is the preferred way to handle texture borders and edge conditions without introducing per-thread branching (e.g., explicit if statements)? Any recommendations for optimizing large-radius neighborhood operations in Metal would be greatly appreciated.
Replies
1
Boosts
0
Views
511
Activity
Jun ’26
Opportunities to use Apple intelligence.
Are there opportunities for developers to use Apple Intelligence models through Metal in ways that unlock new rendering, simulation, or real-time content generation techniques?
Replies
1
Boosts
0
Views
473
Activity
Jun ’26
Memory allocation of textures in Metal
At which time does Metal allocate and deallocate memory for textures? I've observed that the textures live for the whole time of the commandBuffer. So, if I have multiple large textures that I need in subsequent shaders, it would make sense to work with multiple commandBuffers to enable deallocation in order to reduce peak memory usage. Is that correct? Do you have any other suggestions on how to reduce peak memory usage when working with large metal textures? Hint: I am using compute shaders only.
Replies
1
Boosts
1
Views
493
Activity
Jun ’26