If all hardware were bindless, this set/pool wouldn't be needed because you could change one texture anywhere with minimal GPU overhead like you do in OpenGL4 with bindless texture extensions.
Nonetheless this descriptor pool set is also useful for non-texture stuff, (e.g. anything that requires binding, like constant buffers). It is quite generic.
They're actually designed specifically to exploit the strengths of modern bindless GPU's, especially AMD GCN as they're basically copy&pasted from the Mantle specs (which were designed to be cross-vendor, but obviously somewhat biased by having AMD GCN as the min-spec).
There is something I don't really understand in Vulkan/DX12, it's the "descriptor" object. Apparently it acts as a gpu readable data chunk that hold texture pointer/size/layout and sampler info, but I don't understand the descriptor set/pool concept work, this sounds a lot like array of bindless texture handle to me.
A descriptor is a texture-view, buffer-view, sampler, or a pointer
A descriptor set is an array/table/struct of descriptors.
A descriptor pool is basically a large block of memory that acts as a memory allocator for descriptor sets.
So yes, it's very much like bindless handles, but instead of them being handles, they're the actual guts of a texture-view, or an actual sampler structure, etc...
Say you've got a HLSL shader with:
Texture2D texture0 : register(t0);
SamplerState samLinear : register(s0);
In D3D11, you'd bind resources to this shader using something like:
ID3D11SamplerState* mySampler = ...;
ID3D11ShaderResourceView* myTexture = ...;
ctx.PSSetSampelrs( 0, 1, &mySampler );
ctx.VSSetSampelrs( 0, 1, &mySampler );
ctx.PSSetShaderResources( 0, 1, &myTexture );
ctx.VSSetShaderResources( 0, 1, &myTexture );
ctx.Draw(...);//draw something using the bound resources
Let's say that these new APIs give us a nice new bindless way to describe the inputs to the shader. Instead of assigning resources to slots/registers, we'll just put them all into a struct -- that struct is the descriptor set.
Our hypothetical (
because I don't know the new/final syntax yet) HLSL shader code might look like:
struct DescriptorSet : register(d0)
{
Texture2D texture0;
SamplerState samLinear;
};
In our C/C++ code, we can now "bind resources" to the shader with something like this:
I'm inventing the API here -- vulkan doesn't look like this, it's just a guess of what it might look like:
struct MyDescriptorSet // this matches our shader's structure, using corresponding Vulkan C types instead of the "HLSL" types above.
{
VK_IMAGE_VIEW texture0; //n.b. these types are the actual structures that the GPU is hard-wired to interpret, which means
VK_SAMPLER_STATE samLinear;// they'll change from driver-to-driver, so there must be some abstraction here over my example
}; // such as using handles or pointers to the actual structures?
descriptorHandle = vkCreateDescriptorSet( sizeof(MyDescriptorSet), descriptorPool );//allocate an instance of the structure in GPU memory
//copy the resource views that you want to 'bind' into the descriptor set.
MyDescriptorSet* descriptorSet = (MyDescriptorSet*)vkMapDescriptorSet(descriptorHandle);
descriptorSet->texture0 = *myTexture; // CPU is writing into GPU memory here, via write-combined uncached pages!
descriptorSet->samLinear = *mySampler;
vkUnmapDescriptorSet(descriptorHandle);
//later when drawing something
vkCmdBindDescriptorSet(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, descriptorHandle, 0);
vkCmdDraw(cmdBuffer, ...);//draw something using the bound resources
You can see now, when drawing an object, there's only a single API call required to bind all of it's resources.
Also, earlier we required to double up our API calls if the pixel-shader and the vertex-shader both needed the same resources, but now the descriptor-set is shared among all stages.
If an object always uses the same resources every frame, then you can prepare it's descriptor set once, ahead of time, and then do pretty much nothing every frame! All you need to do is call vkCmdBindDescriptorSet and vkCmdDraw.
Even better, those two functions record their commands into a command buffer... so it's possible to record a command buffer for each object ahead of time, and then every frame you only need to call vkQueueSubmit per object to submit it's pre-prepared command buffer.
If we want to modify which resources that draw-call uses, we can simply write new descriptors into that descriptor set. The easiest way is by mapping/unmapping the tables and writing with the CPU as above, but in theory you could also use GPU copy or compute jobs to modify them. GPU modification of descriptor sets would only be possible on truely bindless GPUs, so I'm not sure if this feature will actually be exposed by Vulkan/D3D12 -- maybe in an extension later... This would mean that when you want to change which material a draw-item uses, you could use a compute job to update that draw-item's descriptor set! Along with multi-draw-indirect, you could move even more CPU side work over to the GPU.
Also, it's possible to put pointers to descriptor sets inside descriptor sets!
This is useful where you've got a lot of resource bindings that are shared across a series of draw-calls, so you don't want the CPU to have to re-copy all those bindings for each draw-call.
e.g. set up a shader with a per-object descriptor set, which points to a per-camera descriptor set:
cbuffer CameraData
{
Matrix4x4 viewProj;
};
struct SharedDescriptorSet
{
SamplerState samLinear;
CameraData camera;
}
struct MainDescriptorSet : register(d0)
{
Texture2D texture0;
SharedDescriptorSet* shared;
};
The C side would then make an instance of each, and make one link to the other. When drawing, you just have to bind the per-object one:
sharedDescriptorHandle = vkCreateDescriptorSet( sizeof(SharedDescriptorSet), descriptorPool );
obj0DescriptorHandle = vkCreateDescriptorSet( sizeof(MainDescriptorSet ), descriptorPool );
SharedDescriptorSet* descriptorSet = (SharedDescriptorSet*)vkMapDescriptorSet(sharedDescriptorHandle);
descriptorSet->camera = *myCbufferView;
descriptorSet->samLinear = *mySampler;
vkUnmapDescriptorSet(sharedDescriptorHandle);
MainDescriptorSet * descriptorSet = (MainDescriptorSet *)vkMapDescriptorSet(obj0DescriptorHandle);
descriptorSet->texture0 = *myTexture;
descriptorSet->shared = sharedDescriptorHandle;
vkUnmapDescriptorSet(obj0DescriptorHandle);
//bind obj0Descriptor, which is a MainDescriptorSet, which points to sharedDescriptor, which is a SharedDescriptorSet
vkCmdBindDescriptorSet(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, obj0DescriptorHandle, 0);
vkCmdDraw(cmdBuffer, ...);//draw something using the bound resources