What is a resource attachment point?
It's a logical "slot" to which you are binding resources like textures and data buffers to make them accessible in the shaders. You declare them in shaders and then use some sort of API to link them to actual resources. For example, if you want to use a texture in DriectX 12, you'd do something like this in your shader code:
Code:
Texture2D my_texture : register(t0);
As you can see, DX uses a register model at the shader level, defining a set of abstract registers you can use to refer to attachment points. For this shader, you have one attachment point — the register t0. In the application code you can then create a resource descriptor (with the texture ID and some other information) and bind it to the register t0. When you run the shader, it can access the texture you have linked.
Metal uses a hybrid system, it also has a similar register model, but here I want to talk about their more flexible argument buffers model. With argument buffers you are describing shader resources just like regular C structs, e.g. like this:
Code:
struct Bindings {
// a pointer to some GPU buffer of floats
device float* data;
// a texture
device texture2d<float> my_texture;
// some sort of constant
int data_len;
}
// shader code gets passed a Bindings reference
kernel void shader(constant Bindings& bindings) {
....
}
On the API side, you create a data buffer that will hold a value of type Bindings and use a typed encoder (MTLArgumentEncoder) API to populate the struct members. In this case, the "attachment point" is any location within the struct where you can bind some GPU object. For the type Bindings there are two attachment points: the first member (pointer to data which must be bound to a Metal GPU data buffer) and the second member (which must be bound to a Metal GPU texture object). You can also use arrays etc. in which case every element of an array represents an attachment point.
There are number of important differences between the DX12 and the Metal binding model. DX12 model is flat — you just get a bunch of numbered registers, Metal binding model makes use of data buffers encoding typed structs which in turn can contain pointers to other typed structs etc. etc. You bind resources in DX12 by using a special restricted memory pool of resource descriptors that are linked to register ranges, while in Metal you simply use regular data buffers to lay out your structs.
Now to the core of the issue mentioned in @Colstan's post. Modern DX12 guarantees that at least one million of descriptors/registers is available to any application. Furthermore, it allows you to bind the resources sparsely (that is, if your shader uses one thousand texture registers, you don't actually have to bind a valid resource to all of them as long as you don't read the unset registers). This made one particular approach popular, where the developers would define an unbounded array of textures, allocate the biggest array of resource descriptors that they can (which means one million items) and then use a dynamic system of binding and rebinding the textures. Something like this:
Code:
// array wil a million textures (textures[0] is in t0, textures[1] is in t1, textures[I] is in t1 etc.)
Texture2D textures[] : register(t0);
...
// somewhere in the shader code
// my_texture_index is application provided data telling the shader which of the potentially millions textures to use
my_texture = textures[my_texture_index];
The current belief that it is impossible to emulate this approach in Metal (e.g. when you want to port a shader or write a DX12 implementation on top of Metal) since Apple only gives you 500,000 resources where DX12 guarantees at least one million. There are also other problems, e.g. Metal uses typed encoders while DX12 uses type-erased descriptors. At any rate, my experiments suggest that the 500,000 limit in Metal might be misunderstood. This leaves hope that modern DX12 and Vulkan API patterns (which work very similarly to each other) can in fact be efficiently implemented on top of modern Metal.