Best way to pass material parameters to shaders

Started by
2 comments, last by Hodgman 10 years, 11 months ago

How do you pass material parameters such as floating-point uniforms to shader programs?

|

In most engines they query locations of each uniform (by name/semantic) and set them one by one

(e.g. UpdateUniform( int buffer, int offset, const void* data, int size)).

|

Each uniform, or constant buffer has a shadow copy in sysmem an 'isDirty' flag to skip reuploading unchanged data to videomem.

Wouldn't it be better to have inside the material a parameter blob for just memcpy()ing to entire uniform buffer

(the blob format matches the shader constant buffer layout)? But it won't be possible for modders to assign new shaders to existing materials, right?

Advertisement
I've worked with more than one engine, where in normal usage, the engine spent 25-50% of the main thread's time on setting individual uniform values... So, I much prefer using the simplest possible method, which yes, is blobs of bytes.

I think about uniforms in two types - ones that are set by the game/engine with different values over time, and ones that are set by artists when they create their material but do not change after.
For the first group, yes you have to standardize the layout. The shaders can be replaced/modified as long as people are aware of the layouts that have to used to receive engine-generated data.
For the second group, any layout at all can be used. I precompile material files into blobs of bytes at data-build time. If either a material or a shader is modified, you've just got to run the data compiler again.

1) Do you create separate immutable constant buffers (ID3D11Buffer*) for each material instance (StateGroup in your terminology from other thread) during loading?

(if i understand you correctly, material parameters defined by artists don't change at runtime so the CBs can be simply bound to slots without costly updating.)

2) How is your StateGroup structure defined in code?

How do you resolve pointers to graphics resources when loading StateGroups?

Does your low-level platform-independent graphics layer have functions like UpdateConstantBuffer(), SetConstantBuffers(), SetSamplers(), SetTextures() ?

mine goes like this:


// shader dependencies
struct rsShaderPass
{

TBuffer< HUniformBuffer > CBsItems; // handles to constant buffers
UINT16 CBsStart;

TBuffer< HSamplerState > SSsItems; // handles to sampler states
UINT16 SSsStart;

TBuffer< HShaderResource > SRsItems; // handles to shader resources
UINT16 SRsStart;  

// metadata for resolving shader dependencies (@todo: get rid of this crap)
rsShaderPassInfo	meta;

};

Do you create separate immutable constant buffers (ID3D11Buffer*) for each material instance (StateGroup in your terminology from other thread) during loading?

Yeah, pretty much. The material file contains a blob of bytes (or several of these blobs of bytes -- if the shader splits its parameters between more than one cbuffer), which are copied into actual buffer objects when the file is loaded. I actually store more than one material in a file (I have "material pack" files), and when building these packs, I compare different material's blobs against each other to remove duplicates, so multiple materials may actually share an immutable buffer if they contain the same values.

I also want to experiment with only creating a few dynamic buffer objects and copying data from memory into them before using each material, just to see how this performs in comparison. D3D9 doesn't support cbuffers, so I use that approach in D3D9 (copying these blobs from memory into the uniform registers before draw calls).

How is your StateGroup structure defined in code?

It's a variable-length blob of bytes, which starts with a header structure that describes the total length and a bitfield of the states contained. After the header is a series of state 'packets', which each start with a state-ID and then some data (such as D3D pointers). They're parsed like you would parse a file or a network packet.

How do you resolve pointers to graphics resources when loading StateGroups?

The state groups are stored on disc in the same format that they'll be used in memory, so they're just read in and cast to the right type. However, some of the state packets may contain pointers, which obviously can't be saved in the file. So, in place of the pointer, there will be some other data written, usually an integer index of the same size.

e.g. a material-pack might first create an array of ID3D11Buffer*'s. It then parses it's StateGroups, looking for any "SetCBuffer" packets in them.

In these packets, in place of a ID3D11Buffer* there will be written an integer that is an index into the material-pack's array of buffers. The appropriate pointer is fetched from this array using the index value, and then this pointer is written into the packet, over the top of that integer.

Pretty standard "pointer-patching" stuff for in-place loading.

With temporary data that is only required during loading and then discarded (like blobs of bytes that are immediately copied into D3D buffers and no longer used), these are stored in a different "part" of my data file. The loading system will create a different memory allocation for each "part" of the file, so that the main part can be kept around, but the "temp data" part can be free'ed after parsing is complete.

Does your low-level platform-independent graphics layer have functions like UpdateConstantBuffer(), SetConstantBuffers(), SetSamplers(), SetTextures() ?

Yes, there's an API for updating cbuffers.

The "set" commands are not directly exposed though. There is a StateGroupWriter class (used to create StateGroups), which contains these "set" commands. It takes the arguments and constructs state packets inside a state-group blob, which it returns to the user.

The user cannot set states directly, instead when they want to draw something, they submit the draw-call along with an array of StateGroups. This way, the "state machine" nature of the underlying API is hidden, and the user doesn't have to worry about whether some particular state was set previously or not (states cannot "leak" from one draw-call into the next).

This topic is closed to new replies.

Advertisement