Shaders Resources
Shader resources are things like textures, samplers constant buffers etc. that need to be separately bound in the renderer for the shader to function. Each resource must be bound to a bind group and slot. Depending on the platform used, the requirements for this binding can be very different. E.g. in Vulkan slot assignments must be unique within a bind group (Vulkan descriptor set) across all stages while in DX11 most slots only need to be unique within a stage. Not following these rules will result in a runtime error. Manually assigning slots is an option but is very tedious. To make this easier, the shader system can automate this process provided some constraints are met how resourced are declared.
Currently, Plasma does not support arrays of resources like
Texture2D Diffuse[3]in its shaders.Resources must have unique names across all shader stages. The same resource name can be used in multiple stages as long as the resource it maps to is exactly the same.
Only four bind groups are supported (DX12 register space, Vulkan descriptor set).
Resource Binding
The shader system only supports the DX11 / DX12 register syntax for binding resources to bind groups. Both the bind group and slot can be bound. If no bind group is given, it is implicitly set to 0.
Plasma uses the following bind groups - resources should ideally be bound to these sets according to their update frequency:
BG_FRAME (0): Should only contain resources that are set only once per frame or once per view.
BG_RENDER_PASS (1): Resources that are unqiue to a render pass. Also use it for small shaders that e.g. do some image processing within a render pass.
BG_MATERIAL (2): This group is normally occupied by the material resources. If a material exposes a resource like a texture, it must be put into this bind group.
BG_DRAW_CALL (3): Resources that change every few draw calls should go in here.
Here is a list of a few examples of how to bind resources properly:
The HLSL register syntax is a bit impractical, so the macros BIND_RESOURCE(Slot, BindGroup) and BIND_GROUP(BindGroup) were introduced. These will generate invalid HLSL code which the shader compiler will eventually parse, organize and patch to do the correct thing on each platform. In most cases, you should only be concerned about deciding in which bind group a resource should reside in. Either use the macro SLOT_AUTO when setting a slot or just use the BIND_GROUP macro which omits the slot entirely.
C++ resource binding
Before a shader can be used to render something, all the resources need to be bound to their respective bind groups. This is happening automatically for material resources but for custom shaders used in e.g. a custom render pass, this has to be done manually. To access the individual bind groups, use the following code:
When binding a resource, it is sufficient to only provide the resource name and GAL handle to bind the entire resource. If you want to bind only a subset of the resource, you can use plGALTextureRange or plGALBufferRange like this:
Constant Buffers
Constant buffers map to plGALShaderResourceType::ConstantBuffer in C++. To facilitate C++ interop, constant buffers should be placed into a separate header file that looks like this:
By using the macros defined in ConstantBufferMacros.h like CONSTANT_BUFFER and the data types like FLOAT4, the file can be included in both shader and C++ code. This makes it easy to create an instance of the constant buffer as a C++ struct in code to update it. Care must be taken to ensure that the constant buffer has the same layout in C++ and HLSL though:
Make sure that the size of your struct is a multiple of 16 bytes. Fill out any missing bytes with dummy
FLOAT1entries.A
FLOAT3can't be followed by anotherFLOAT3. It should be followed by aFLOAT1first or some other types of the same byte counts to ensure the nextFLOAT3starts at a 16 byte boundary. This is necessary as the layout rules are different between HLSL and C++.
Push Constants
Push constants map to plGALShaderResourceType::PushConstants in C++. Push constants allow for fast updates of a small set of bytes. Usually at least 128 bytes. You can check plGALDeviceCapabilities::m_uiMaxPushConstantsSize for the max push constant buffer size. On platforms that don't support push constants like DX11, this is emulated via a constant buffer. Only one push constants block is supported across all shader stages of a shader. Like with constant buffers, special macros have to be used and the declaration should be put into a separate header so it can be included in both shader and C++ code:
The BEGIN_PUSH_CONSTANTS and END_PUSH_CONSTANTS macros define the struct. Unlike with constant buffers, you can't simply access the values inside a shader by just the name of the variable, e.g. VertexColor. This is because depending on the platform, a different syntax needs to be used to access the content. To make the same shader compile on all platforms, you need to use the GET_PUSH_CONSTANT(Name, Constant) macro to access a member of the push constant buffer.
Samplers
Samplers map to plGALShaderResourceType::Sampler or plGALShaderResourceType::TextureAndSampler in C++. Two types of samplers are supported: SamplerState and SamplerComparisonState. The naming of the samplers is important, as it can be used to optimize your workflow. Plasma has a concept of immutable Samplers, these samplers are automatically bound so you can use them in the shader without needing to define them in C++. Immutable samplers are registered in code via plGALImmutableSamplers::RegisterImmutableSampler. Currently, these samplers are registered: LinearSampler, LinearClampSampler, PointSampler and PointClampSampler.
Plasma does not allow for two different resources to have the same name, the only exception is textures and samplers which can have the same name by calling the sampler NAME_AutoSampler. The compiler will rename the sampler to NAME and on platforms that support combined image samplers both will be combined into a single resource of type plGALShaderResourceType::TextureAndSampler. The benefit of this approach is that when binding a texture resource to a material for example, the texture resource can define both the texture as well as the sampler state, binding both to the same name.
Textures
Textures map to plGALShaderResourceType::Texture or plGALShaderResourceType::TextureAndSampler in C++ (see samplers above). Plasma supports all HLSL texture types except for 1D textures. You can work around this by creating 1xN 2DTextures.
Read-write variants are also supported and map to plGALShaderResourceType::TextureRW in C++.
Buffers
There are three types of buffers supported by Plasma:
HLSL's
Buffer<T>type is very similar to a 1D texture. A buffer of the same type T needs to be bound to the resource. Maps toplGALShaderResourceType::TexelBufferin C++.StructuredBuffer<T>should follow the same rules as for constant buffers: Put the declaration in a separate header file to allow access to it from C++ and ensure each struct is 16 bytes aligned. Maps toplGALShaderResourceType::StructuredBufferin C++.ByteAddressBufferin just an array of bytes. A raw buffer needs to be bound to the resource. With HLSL 5.1, you can cast any offset of the buffer into a struct. Maps toplGALShaderResourceType::ByteAddressBufferin C++.
Read-write variants of these buffers are also supported and map to plGALShaderResourceType::TexelBufferRW, plGALShaderResourceType::StructuredBufferRW and plGALShaderResourceType::ByteAddressBufferRW respectively.
Append / Consume Buffers
TODO: Future work: Append / consume buffers can be defined in shaders and are correctly reflected, but Plasma does not support binding resources to them right now.