36 releases

0.9.1 Jun 23, 2021
0.8.2 May 9, 2021
0.7.0 Jan 30, 2021
0.6.5 Nov 27, 2020
0.1.0 Dec 27, 2018

#946 in Graphics APIs

Download history 2386/week @ 2024-01-06 2821/week @ 2024-01-13 2203/week @ 2024-01-20 1747/week @ 2024-01-27 1360/week @ 2024-02-03 2618/week @ 2024-02-10 3408/week @ 2024-02-17 2250/week @ 2024-02-24 2706/week @ 2024-03-02 3118/week @ 2024-03-09 3230/week @ 2024-03-16 3132/week @ 2024-03-23 3764/week @ 2024-03-30 2904/week @ 2024-04-06 3881/week @ 2024-04-13 2847/week @ 2024-04-20

13,732 downloads per month
Used in 135 crates (10 directly)


16K SLoC


Metal backend for gfx-rs.

Normalized Coordinates

Render Depth Texture
render_coordinates depth_coordinates texture_coordinates

Binding Model

Dimensions of the model:

  1. Shader stage: vs, fs, cs
  2. Register: buffers, textures, samplers
  3. Binding: 0..31 buffers, 0..128 textures, 0..16 samplers




Metal backend internals.

Pipeline Layout

In Metal, push constants, vertex buffers, and resources in the descriptor sets are all placed together in the native resource bindings, which work similarly to D3D11: there are tables of textures, buffers, and samplers.

We put push constants first (if any) in the table, followed by descriptor set 0 resource, followed by other descriptor sets. The vertex buffers are bound at the very end of the VS buffer table.

When argument buffers are supported, each descriptor set becomes a buffer binding, but the general placement rule is the same.

Command recording

One-time-submit primary command buffers are recorded "live" into MTLCommandBuffer. Special care is taken to the recording state: active bindings are restored at the start of any render or compute pass.

Multi-submit and secondary command buffers are recorded as "soft" commands into Journal. Actual native recording is done at either submit or execute_commands correspondingly. When that happens, we enqueue the command buffer at the start of recording, which allows the driver to work on pass translation at the same time as we are recording the following passes.


In general, "Shared" storage is used for CPU-coherent memory. "Managed" is used for non-coherent CPU-visible memory. Finally, "Private" storage is backing device-local memory types.

Metal doesn't have CPU-visible memory for textures. We only allow RGBA8 2D textures to be allocated from it, and only for the matter of transfer operations, which is the minimum required by Vulkan. In fact, these become just glorified staging buffers.


Events are represented by just an atomic bool. When recording, a command buffer keeps track of all events set or reset. Signalling within a command buffer is therefore a matter of simply checking that local list. When making a submission, used events are also accumulated temporarily, so that we can change their values in the completion handler of the last command buffer. We also check this list in order to resolve events fired in one command buffer and waited in another one within the same submission.

Waiting for an event from a different submission is accomplished similar to waiting for the host. We block all the submissions until the host blockers are resolved, and these are checked at certain points like setting an event by the device, or waiting for a fence. !


~260K SLoC