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 set 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 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.
Resource Binding
The shader system only supports the DX11 / DX12 register syntax for resource binding. Both the set and slot can be bound. If no set is given, it is implicitly set 0. 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, Set)
and BIND_SET(Set)
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 set a resource should reside in. Either use the macro SLOT_AUTO
when setting a slot or just use the BIND_SET
macro which omits the slot entirely. While you can set any integer for the set, some platforms like Vulkan have a limit on how many sets can be managed at the same time with a minimum of four. Plasma defines macros for these four sets: SET_FRAME
, SET_RENDER_PASS
, SET_MATERIAL
and SET_DRAW_CALL
. Resources should ideally be bound to these sets according to their update frequency.
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
FLOAT1
entries.A
FLOAT3
can't be followed by anotherFLOAT3
. It should be followed by aFLOAT1
first or some other types of the same byte counts to ensure the nextFLOAT3
starts 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::TexelBuffer
in 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::StructuredBuffer
in C++.ByteAddressBuffer
in 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::StructuredBuffer
in C++.
Read-write variants of these buffers are also supported and map to plGALShaderResourceType::TexelBufferRW
and plGALShaderResourceType::StructuredBufferRW
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.
See Also
ShaderCompiler