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

Optimizing HZB Mip-Chain Generation and Bindless Argument Tables in a Custom Metal Engine
Hi everyone, I’ve been developing a custom, end-to-end 3D rendering engine called Crescent from scratch using C++20 and Metal-cpp (targeting macOS and visionOS). My primary goal is to build a zero-bottleneck, GPU-driven pipeline that maximizes the potential of Apple Silicon’s Unified Memory and TBDR architecture. While the fundamental systems are stable, I am looking for architectural feedback from Metal framework engineers regarding specific synchronization and latency challenges. Current Core Implementations: GPU-Driven Instance Culling: High-performance occlusion culling using a Hierarchical Z-Buffer (HZB) approach via Compute Shaders. Clustered Forward Shading: Support for high-count dynamic lights through view-space clustering. Temporal Stability: Custom TAA with history rejection and Motion Blur resolve. Asset Infrastructure: Robust GUID-based scene serialization and a JSON-driven ECS hierarchy. The Architectural Challenge: I am currently seeing slight synchronization overhead when generating the HZB mip-chain. On Apple Silicon, I am evaluating the cost of encoder transitions versus cache-friendly barriers. && m_hzbInitPipeline && m_hzbDownsamplePipeline && !m_hzbMipViews.empty(); if (canBuildHzb) { MTL::ComputeCommandEncoder* hzbInit = commandBuffer->computeCommandEncoder(); hzbInit->setComputePipelineState(m_hzbInitPipeline); hzbInit->setTexture(m_depthTexture, 0); hzbInit->setTexture(m_hzbMipViews[0], 1); if (m_pointClampSampler) { hzbInit->setSamplerState(m_pointClampSampler, 0); } else if (m_linearClampSampler) { hzbInit->setSamplerState(m_linearClampSampler, 0); } const uint32_t hzbWidth = m_hzbMipViews[0]->width(); const uint32_t hzbHeight = m_hzbMipViews[0]->height(); const uint32_t threads = 8; MTL::Size tgSize = MTL::Size(threads, threads, 1); MTL::Size gridSize = MTL::Size((hzbWidth + threads - 1) / threads * threads, (hzbHeight + threads - 1) / threads * threads, 1); hzbInit->dispatchThreads(gridSize, tgSize); hzbInit->endEncoding(); for (size_t mip = 1; mip < m_hzbMipViews.size(); ++mip) { MTL::Texture* src = m_hzbMipViews[mip - 1]; MTL::Texture* dst = m_hzbMipViews[mip]; if (!src || !dst) { continue; } MTL::ComputeCommandEncoder* downEncoder = commandBuffer->computeCommandEncoder(); downEncoder->setComputePipelineState(m_hzbDownsamplePipeline); downEncoder->setTexture(src, 0); downEncoder->setTexture(dst, 1); const uint32_t mipWidth = dst->width(); const uint32_t mipHeight = dst->height(); MTL::Size downGrid = MTL::Size((mipWidth + threads - 1) / threads * threads, (mipHeight + threads - 1) / threads * threads, 1); downEncoder->dispatchThreads(downGrid, tgSize); downEncoder->endEncoding(); } if (m_instanceCullHzbPipeline) { dispatchInstanceCulling(m_instanceCullHzbPipeline, true); } } My Questions: Encoder Synchronization: Would you recommend moving this loop into a single ComputeCommandEncoder using MTLBarrier between dispatches to maintain L2 cache residency, or is the overhead of separate encoders negligible for depth-downsampling on TBDR? visionOS Bindless Latency: For stereo rendering on visionOS, what are the best practices for managing MTL4ArgumentTable updates at 90Hz+? I want to ensure that updating bindless resources for each eye doesn't introduce unnecessary CPU-to-GPU latency. Memory Management: Are there specific hints for Memoryless textures that could be applied to intermediate HZB levels to save bandwidth during this process? I’ve attached a screenshot of a scene rendered with the engine (PBR, SSR, and IBL).
0
0
490
Feb ’26
Open Shading Language (OSL) in Metal
Hi. I'm a 3D designer, using Blender for most of my work. The most recent Blender conference discussed utilizing the Open Shading Language (OSL) in their latest versions, which allows designers to write custom shaders for their workflows. At the moment, only Nvidia Optix GPU's can utilize this language for rendering (from what I understand), but Blender developers stated they are waiting on other GPU manufacturers to implement this feature as well. I'm not sure if there are any licensing issues here, but would this be something Apple could implement in Metal to make their hardware more attractive to the 3D design community? Any help or knowledge on this topic would be greatly appreciated.
0
0
312
Feb ’26
Unable to find intelgpu_kbl_gt2r0 slice or a compatible one in binary archive
Unable to find intelgpu_kbl_gt2r0 slice or a compatible one in binary archive 'file:///System/Library/PrivateFrameworks/IconRendering.framework/Resources/binary.metallib' available slices: applegpu_g13g, applegpu_g13s, applegpu_g13d, applegpu_g14g, applegpu_g14s, applegpu_g14d, applegpu_g15g, applegpu_g15s, applegpu_g15d, applegpu_g16g, applegpu_g16s, applegpu_g17g, applegpu_g15g, applegpu_g15s, applegpu_g15d, applegpu_g16s Is it related to performance of applications in macOS 26.2 on Intel Macs?
3
0
350
Feb ’26
Terminal Codes
Hello Apple Developers and users I am writing this message reguarding some help on some performance codes/settings I can use for my Macbook since I recently downloaded the MacOs Tahoe 26.2 and its been very glitchy and laggy with gaming and just using my mac normally I have tried using a FPS unlocker and downloading Metal 4 the FPS unlocker hasent worked at all I am still stuck on the normal 60 FPS and need some advice/help. Thank you. Kind regards Zachary
0
0
216
Feb ’26
The description of set_indices in the MSL reference seems incorrect.
I'm currently learning Metal. While reading the reference, I came across a strange description. Page 78 in Version 4 Reference (2025-10-25) says: It is legal to call the following set_indices functions to set the indices if the position in the index buffer is valid and if the position in the index buffer is a multiple of 2 (uchar2 overload) or 2 (uchar4 overload). The index I needs to be in the range [0, max_indices). void set_indices(uint I, uchar2 v); void set_indices(uint I, uchar4 v); However, it seems that the uchar4 overload should be multiple of 4. Furthermore, there is no explanation of what these methods actually do. I believe it involves setting two to four consecutive indices at once, but there is no mention of that here. I would like to know if the above understanding is correct.
0
0
166
Feb ’26
Metal 4 (validation / debug layer): residency set requirement mismatch for memoryless attachments
Setup: MSAA rendering using a memoryless texture as the color attachment (render_image) and a "normal" texture as the resolve attachment (resolve_image). MTL_DEBUG_LAYER / API validation is enabled for this. When trying to add the memoryless texture to a residency set, I get the following error: -[MTLDebugResidencySet validateResource:], line 114: error 'residency sets do not support memoryless resources. Which is as expected and identical to Metal 3. However, if I don't add it to the residency set, I then get the following error when committing to the command queue: -[MTL4DebugCommandQueue commit:count:options:], line 67: error 'Commit With Options Validation Attachment texture (Label: render_image) used in command buffer (at index 0) is not added to any residency set on the command buffer or command queue. So which way around is actually correct in Metal 4? Either way, this makes the use of memoryless textures/attachments impossible right now when validation is enabled. FWIW: when disabling all validation, either way seems to work just fine. Tested on: M1 Max, macOS 26.3, Xcode 26.2 & 26.4b2
0
0
127
Feb ’26
Using Metal compute for scientific simulation (lattice QCD gauge theory)
I've been using Metal compute shaders for lattice quantum chromodynamics simulations and wanted to share the experience in case others are doing scientific computing on Metal. The workload involves SU(2) matrix operations on 4D lattice grids — lots of 2x2 and 3x3 complex matrix multiplies, reductions over lattice sites, and nearest-neighbor stencil operations. The implementation bridges a C++ scientific framework (Grid) to Metal via Objective-C++ .mm files, with MSL kernels compiled into .metallib archives during the build. Things that work well: Shared memory on M-series eliminates the CPU↔GPU copy overhead that dominates in CUDA workflows The .metallib compilation integrates cleanly with autotools builds using xcrun Float4 packing for SU(2) matrices maps naturally to MSL vector types Things I'm still figuring out: Optimal threadgroup sizes for stencil operations on 4D grids Whether to use MTLHeap for gauge field storage or stick with individual buffers Best practices for double precision — some measurements need float64 but Metal's double support varies by hardware The application is measuring chromofield flux distributions between static quarks, ultimately targeting multi-quark systems. Production runs are on MacBook Pro M-series and Mac Studio. Code: https://github.com/ThinkOffApp/multiquark-lattice-qcd
0
0
215
Feb ’26
Metal Shader inside Swift Package not found?
Hello everyone! I am trying to wrap a ViewModifier inside a Swift Package that bundles a metal shader file to be used in the modifier. Everything works as expected in the Preview, in the Simulator and on a real device for iOS. It also works in Preview and in the Simulator for tvOS but not on a real AppleTV. I have tried this on a 4th generation Apple TV running tvOS 26.3 using Xcode 26.2.0. Xcode logs the following: The metallib is processed and exists in the bundle. Compiler failed to build request precondition failure: pipeline error: custom_effect-fg2a5cia7fmha4: error: unresolved visible function reference: custom_fn Reason: visible function not loaded Compiler failed to build request precondition failure: pipeline error: custom_effect-fg2a5cia7fmha4: error: unresolved visible function reference: custom_fn Reason: visible function not loaded Compiler failed to build request precondition failure: pipeline error: custom_effect-fg2a5cia7fmha4: error: unresolved visible function reference: custom_fn Reason: visible function not loaded Compiler failed to build request precondition failure: pipeline error: custom_effect-fg2a5cia7fmha4: error: unresolved visible function reference: custom_fn Reason: visible function not loaded Compiler failed to build request precondition failure: pipeline error: custom_effect-fg2a5cia7fmha4: error: unresolved visible function reference: custom_fn Reason: visible function not loaded Compiler failed to build request precondition failure: pipeline error: custom_effect-fg2a5cia7fmha4: error: unresolved visible function reference: custom_fn Reason: visible function not loaded Contents of Package.swift: import PackageDescription let package = Package( name: "Test", platforms: [ .iOS(.v17), .tvOS(.v17) ], products: [ .library( name: "Test", targets: [ "Test" ] ) ], targets: [ .target( name: "Test", resources: [ .process("Shaders") ] ), .testTarget( name: "TestTests", dependencies: [ "Test" ] ) ] ) Content of my metal file: #include <metal_stdlib> using namespace metal; [[ stitchable ]] float2 complexWave(float2 position, float time, float2 size, float speed, float strength, float frequency) { float2 normalizedPosition = position / size; float moveAmount = time * speed; position.x += sin((normalizedPosition.x + moveAmount) * frequency) * strength; position.y += cos((normalizedPosition.y + moveAmount) * frequency) * strength; return position; } And my ViewModifier: import MetalKit import SwiftUI extension ShaderFunction { static let complexWave: ShaderFunction = { ShaderFunction( library: .bundle(.module), name: "complexWave" ) }() } extension Shader { static func complexWave(arguments: [Shader.Argument]) -> Shader { Shader(function: .complexWave, arguments: arguments) } } struct WaveModifier: ViewModifier { let start: Date = .now func body(content: Content) -> some View { TimelineView(.animation) { context in let delta = context.date.timeIntervalSince(start) content .visualEffect { view, proxy in view.distortionEffect( .complexWave( arguments: [ .float(delta), .float2(proxy.size), .float(0.5), .float(8), .float(10) ] ), maxSampleOffset: .zero ) } } .onAppear { let paths = Bundle.module.paths(forResourcesOfType: "metallib", inDirectory: nil) print(paths) } } } extension View { public func wave() -> some View { modifier(WaveModifier()) } } #Preview { Image(systemName: "cart") .wave() } Any help is appreciated.
0
0
432
Mar ’26
Using setVertexBytes for index primitives
When using index primitives is there a method to provide the indices using a temp buffer like setVertexBytes? Right now I have to create a temp metal buffer even for a small number of vertices and toss it after rendering using drawIndexedPrimitives.
0
0
217
1w
Optimizing HZB Mip-Chain Generation and Bindless Argument Tables in a Custom Metal Engine
Hi everyone, I’ve been developing a custom, end-to-end 3D rendering engine called Crescent from scratch using C++20 and Metal-cpp (targeting macOS and visionOS). My primary goal is to build a zero-bottleneck, GPU-driven pipeline that maximizes the potential of Apple Silicon’s Unified Memory and TBDR architecture. While the fundamental systems are stable, I am looking for architectural feedback from Metal framework engineers regarding specific synchronization and latency challenges. Current Core Implementations: GPU-Driven Instance Culling: High-performance occlusion culling using a Hierarchical Z-Buffer (HZB) approach via Compute Shaders. Clustered Forward Shading: Support for high-count dynamic lights through view-space clustering. Temporal Stability: Custom TAA with history rejection and Motion Blur resolve. Asset Infrastructure: Robust GUID-based scene serialization and a JSON-driven ECS hierarchy. The Architectural Challenge: I am currently seeing slight synchronization overhead when generating the HZB mip-chain. On Apple Silicon, I am evaluating the cost of encoder transitions versus cache-friendly barriers. && m_hzbInitPipeline && m_hzbDownsamplePipeline && !m_hzbMipViews.empty(); if (canBuildHzb) { MTL::ComputeCommandEncoder* hzbInit = commandBuffer->computeCommandEncoder(); hzbInit->setComputePipelineState(m_hzbInitPipeline); hzbInit->setTexture(m_depthTexture, 0); hzbInit->setTexture(m_hzbMipViews[0], 1); if (m_pointClampSampler) { hzbInit->setSamplerState(m_pointClampSampler, 0); } else if (m_linearClampSampler) { hzbInit->setSamplerState(m_linearClampSampler, 0); } const uint32_t hzbWidth = m_hzbMipViews[0]->width(); const uint32_t hzbHeight = m_hzbMipViews[0]->height(); const uint32_t threads = 8; MTL::Size tgSize = MTL::Size(threads, threads, 1); MTL::Size gridSize = MTL::Size((hzbWidth + threads - 1) / threads * threads, (hzbHeight + threads - 1) / threads * threads, 1); hzbInit->dispatchThreads(gridSize, tgSize); hzbInit->endEncoding(); for (size_t mip = 1; mip < m_hzbMipViews.size(); ++mip) { MTL::Texture* src = m_hzbMipViews[mip - 1]; MTL::Texture* dst = m_hzbMipViews[mip]; if (!src || !dst) { continue; } MTL::ComputeCommandEncoder* downEncoder = commandBuffer->computeCommandEncoder(); downEncoder->setComputePipelineState(m_hzbDownsamplePipeline); downEncoder->setTexture(src, 0); downEncoder->setTexture(dst, 1); const uint32_t mipWidth = dst->width(); const uint32_t mipHeight = dst->height(); MTL::Size downGrid = MTL::Size((mipWidth + threads - 1) / threads * threads, (mipHeight + threads - 1) / threads * threads, 1); downEncoder->dispatchThreads(downGrid, tgSize); downEncoder->endEncoding(); } if (m_instanceCullHzbPipeline) { dispatchInstanceCulling(m_instanceCullHzbPipeline, true); } } My Questions: Encoder Synchronization: Would you recommend moving this loop into a single ComputeCommandEncoder using MTLBarrier between dispatches to maintain L2 cache residency, or is the overhead of separate encoders negligible for depth-downsampling on TBDR? visionOS Bindless Latency: For stereo rendering on visionOS, what are the best practices for managing MTL4ArgumentTable updates at 90Hz+? I want to ensure that updating bindless resources for each eye doesn't introduce unnecessary CPU-to-GPU latency. Memory Management: Are there specific hints for Memoryless textures that could be applied to intermediate HZB levels to save bandwidth during this process? I’ve attached a screenshot of a scene rendered with the engine (PBR, SSR, and IBL).
Replies
0
Boosts
0
Views
490
Activity
Feb ’26
Open Shading Language (OSL) in Metal
Hi. I'm a 3D designer, using Blender for most of my work. The most recent Blender conference discussed utilizing the Open Shading Language (OSL) in their latest versions, which allows designers to write custom shaders for their workflows. At the moment, only Nvidia Optix GPU's can utilize this language for rendering (from what I understand), but Blender developers stated they are waiting on other GPU manufacturers to implement this feature as well. I'm not sure if there are any licensing issues here, but would this be something Apple could implement in Metal to make their hardware more attractive to the 3D design community? Any help or knowledge on this topic would be greatly appreciated.
Replies
0
Boosts
0
Views
312
Activity
Feb ’26
Unable to find intelgpu_kbl_gt2r0 slice or a compatible one in binary archive
Unable to find intelgpu_kbl_gt2r0 slice or a compatible one in binary archive 'file:///System/Library/PrivateFrameworks/IconRendering.framework/Resources/binary.metallib' available slices: applegpu_g13g, applegpu_g13s, applegpu_g13d, applegpu_g14g, applegpu_g14s, applegpu_g14d, applegpu_g15g, applegpu_g15s, applegpu_g15d, applegpu_g16g, applegpu_g16s, applegpu_g17g, applegpu_g15g, applegpu_g15s, applegpu_g15d, applegpu_g16s Is it related to performance of applications in macOS 26.2 on Intel Macs?
Replies
3
Boosts
0
Views
350
Activity
Feb ’26
Terminal Codes
Hello Apple Developers and users I am writing this message reguarding some help on some performance codes/settings I can use for my Macbook since I recently downloaded the MacOs Tahoe 26.2 and its been very glitchy and laggy with gaming and just using my mac normally I have tried using a FPS unlocker and downloading Metal 4 the FPS unlocker hasent worked at all I am still stuck on the normal 60 FPS and need some advice/help. Thank you. Kind regards Zachary
Replies
0
Boosts
0
Views
216
Activity
Feb ’26
The description of set_indices in the MSL reference seems incorrect.
I'm currently learning Metal. While reading the reference, I came across a strange description. Page 78 in Version 4 Reference (2025-10-25) says: It is legal to call the following set_indices functions to set the indices if the position in the index buffer is valid and if the position in the index buffer is a multiple of 2 (uchar2 overload) or 2 (uchar4 overload). The index I needs to be in the range [0, max_indices). void set_indices(uint I, uchar2 v); void set_indices(uint I, uchar4 v); However, it seems that the uchar4 overload should be multiple of 4. Furthermore, there is no explanation of what these methods actually do. I believe it involves setting two to four consecutive indices at once, but there is no mention of that here. I would like to know if the above understanding is correct.
Replies
0
Boosts
0
Views
166
Activity
Feb ’26
Metal 4 (validation / debug layer): residency set requirement mismatch for memoryless attachments
Setup: MSAA rendering using a memoryless texture as the color attachment (render_image) and a "normal" texture as the resolve attachment (resolve_image). MTL_DEBUG_LAYER / API validation is enabled for this. When trying to add the memoryless texture to a residency set, I get the following error: -[MTLDebugResidencySet validateResource:], line 114: error 'residency sets do not support memoryless resources. Which is as expected and identical to Metal 3. However, if I don't add it to the residency set, I then get the following error when committing to the command queue: -[MTL4DebugCommandQueue commit:count:options:], line 67: error 'Commit With Options Validation Attachment texture (Label: render_image) used in command buffer (at index 0) is not added to any residency set on the command buffer or command queue. So which way around is actually correct in Metal 4? Either way, this makes the use of memoryless textures/attachments impossible right now when validation is enabled. FWIW: when disabling all validation, either way seems to work just fine. Tested on: M1 Max, macOS 26.3, Xcode 26.2 & 26.4b2
Replies
0
Boosts
0
Views
127
Activity
Feb ’26
Using Metal compute for scientific simulation (lattice QCD gauge theory)
I've been using Metal compute shaders for lattice quantum chromodynamics simulations and wanted to share the experience in case others are doing scientific computing on Metal. The workload involves SU(2) matrix operations on 4D lattice grids — lots of 2x2 and 3x3 complex matrix multiplies, reductions over lattice sites, and nearest-neighbor stencil operations. The implementation bridges a C++ scientific framework (Grid) to Metal via Objective-C++ .mm files, with MSL kernels compiled into .metallib archives during the build. Things that work well: Shared memory on M-series eliminates the CPU↔GPU copy overhead that dominates in CUDA workflows The .metallib compilation integrates cleanly with autotools builds using xcrun Float4 packing for SU(2) matrices maps naturally to MSL vector types Things I'm still figuring out: Optimal threadgroup sizes for stencil operations on 4D grids Whether to use MTLHeap for gauge field storage or stick with individual buffers Best practices for double precision — some measurements need float64 but Metal's double support varies by hardware The application is measuring chromofield flux distributions between static quarks, ultimately targeting multi-quark systems. Production runs are on MacBook Pro M-series and Mac Studio. Code: https://github.com/ThinkOffApp/multiquark-lattice-qcd
Replies
0
Boosts
0
Views
215
Activity
Feb ’26
Metal Shader inside Swift Package not found?
Hello everyone! I am trying to wrap a ViewModifier inside a Swift Package that bundles a metal shader file to be used in the modifier. Everything works as expected in the Preview, in the Simulator and on a real device for iOS. It also works in Preview and in the Simulator for tvOS but not on a real AppleTV. I have tried this on a 4th generation Apple TV running tvOS 26.3 using Xcode 26.2.0. Xcode logs the following: The metallib is processed and exists in the bundle. Compiler failed to build request precondition failure: pipeline error: custom_effect-fg2a5cia7fmha4: error: unresolved visible function reference: custom_fn Reason: visible function not loaded Compiler failed to build request precondition failure: pipeline error: custom_effect-fg2a5cia7fmha4: error: unresolved visible function reference: custom_fn Reason: visible function not loaded Compiler failed to build request precondition failure: pipeline error: custom_effect-fg2a5cia7fmha4: error: unresolved visible function reference: custom_fn Reason: visible function not loaded Compiler failed to build request precondition failure: pipeline error: custom_effect-fg2a5cia7fmha4: error: unresolved visible function reference: custom_fn Reason: visible function not loaded Compiler failed to build request precondition failure: pipeline error: custom_effect-fg2a5cia7fmha4: error: unresolved visible function reference: custom_fn Reason: visible function not loaded Compiler failed to build request precondition failure: pipeline error: custom_effect-fg2a5cia7fmha4: error: unresolved visible function reference: custom_fn Reason: visible function not loaded Contents of Package.swift: import PackageDescription let package = Package( name: "Test", platforms: [ .iOS(.v17), .tvOS(.v17) ], products: [ .library( name: "Test", targets: [ "Test" ] ) ], targets: [ .target( name: "Test", resources: [ .process("Shaders") ] ), .testTarget( name: "TestTests", dependencies: [ "Test" ] ) ] ) Content of my metal file: #include <metal_stdlib> using namespace metal; [[ stitchable ]] float2 complexWave(float2 position, float time, float2 size, float speed, float strength, float frequency) { float2 normalizedPosition = position / size; float moveAmount = time * speed; position.x += sin((normalizedPosition.x + moveAmount) * frequency) * strength; position.y += cos((normalizedPosition.y + moveAmount) * frequency) * strength; return position; } And my ViewModifier: import MetalKit import SwiftUI extension ShaderFunction { static let complexWave: ShaderFunction = { ShaderFunction( library: .bundle(.module), name: "complexWave" ) }() } extension Shader { static func complexWave(arguments: [Shader.Argument]) -> Shader { Shader(function: .complexWave, arguments: arguments) } } struct WaveModifier: ViewModifier { let start: Date = .now func body(content: Content) -> some View { TimelineView(.animation) { context in let delta = context.date.timeIntervalSince(start) content .visualEffect { view, proxy in view.distortionEffect( .complexWave( arguments: [ .float(delta), .float2(proxy.size), .float(0.5), .float(8), .float(10) ] ), maxSampleOffset: .zero ) } } .onAppear { let paths = Bundle.module.paths(forResourcesOfType: "metallib", inDirectory: nil) print(paths) } } } extension View { public func wave() -> some View { modifier(WaveModifier()) } } #Preview { Image(systemName: "cart") .wave() } Any help is appreciated.
Replies
0
Boosts
0
Views
432
Activity
Mar ’26
Using setVertexBytes for index primitives
When using index primitives is there a method to provide the indices using a temp buffer like setVertexBytes? Right now I have to create a temp metal buffer even for a small number of vertices and toss it after rendering using drawIndexedPrimitives.
Replies
0
Boosts
0
Views
217
Activity
1w