[DX 11] Shaders/Materials design

Started by
3 comments, last by Aqua Costa 12 years, 7 months ago
Can you think of any downside of using a system like this:
I include this file in every shader I write:

SamplerState LinearSampler : register(s0);
SamplerState TriLinearSampler : register(s1);
SamplerState PointSampler : register(s2);
SamplerState AnisoSampler : register(s3);

Texture2D<float4> DiffuseMap : register(t0);
Texture2D<float4> NormalMap : register(t1);
Texture2D<float4> SpecMap : register(t2);
Texture2D<float4> EmissiveMap : register(t3);
Texture2D<float4> TranslucencyMap : register(t4);

Texture2D<float4> DepthBuffer : register(t5);
Texture2D<float4> GBuffer : register(t6);
Texture2D<float4> LightBuffer : register(t7);
Texture2D<float4> SSAOBuffer : register(t8);
Texture2D<float4> ShadowMap : register(t9);

cbuffer PerFrameEngineParameters : register(cb0)
{
float4x4 ViewMatrix;
float4x4 ProjMatrix;
float4x4 ViewProjMatrix;
float4x4 InverseViewMatrix;
float4x4 InverseProjMatrix;
float4x4 InverseViewProjMatrix;
//[...]
}

cbuffer ImmutableParamters : register(cb1)
{
int numCascades
//[...]
}


All sampler states are bound to their corresponding slots when my engine is launched... this way every shader has access to every sampler state, the same goes for every other resource that the shader might need.

Each sampler is only bound one time, however is there any problem related to the fact that every shader has access to sampler (and other resources) even though it might not use it?
Advertisement
I see no problem with using named samplers since it save performance.

The textures should have some texture channels that are userdefined for game specific needs. If someone using your engine want to have 5 diffuse textures, a blend map, an infra red map, an ultra violet map, a bump map and a displacement map, the predefined names will be confusing and make the user worried about using the engine in the wrong way.

Cascades should not be global in case that the user want to have a planet with 2 suns. Make an array with light sources that are version safe by using light sampling HLSL functions. Given a position, direction, camera position, you return how much diffuse and specular light you get there. You should be able to add more types of light without changing every shader. Be careful not to add too many types of light since it takes a lot of time to compile.

The shadow map should be a shadow atlas with dynamic allocation to allow many light sources to cast shadows.
Here is my C++ class for allocating parts of an atlas with proven correctness and optimality that have been unit tested in Visual Basic.
Call Reset when you clear your light atlas and call Allocate when a light source need a part of the atlas.
Size is defined as the number of times you divide width and height by 2 starting from the entire atlas.
(-1,-1,-1,-1) is returned if there is not enough memory remaining in the atlas. This can not be caused by fragmentation since you must clear all allocations to remove something.

QuadAllocator.h

#pragma once

#define QASmallestSize 4

struct QARect {
float MinU;
float MaxU;
float MinV;
float MaxV;
};

class QuadAllocator
{
private:
int HeapCount[QASmallestSize + 1];
QARect HeapArea[QASmallestSize + 1][4];
void Split(int Size);
bool Divide(int Size);
public:
QuadAllocator(void) {
Reset();
}
void Reset(void);
QARect Allocate(int Size);
QARect MakeQARect(float MinU, float MaxU, float MinV, float MaxV) {
QARect Result;
Result.MinU = MinU;
Result.MaxU = MaxU;
Result.MinV = MinV;
Result.MaxV = MaxV;
return Result;
}
};


QuadAllocator.cpp


#include "QuadAllocator.h"
#include "LanguageMacros.h"

//SideEffect: Resets the quad heap
void QuadAllocator::Reset(void) {
HeapCount[0] = 1;
HeapArea[0][0] = MakeQARect(0.0f, 1.0f, 0.0f, 1.0f);
int i;
LoopForward(1,i,QASmallestSize) {
HeapCount = 0;
}

}

//Pre: HeapCount[Size] > 0 and HeapCount[Size+1] = 0
//SideEffect: from Size to Size + 1
void QuadAllocator::Split(int Size) {
QARect Uncut;
Uncut = HeapArea[Size][HeapCount[Size] - 1];
HeapCount[Size + 1] = 4;
HeapArea[Size + 1][0] = MakeQARect(Uncut.MinU, (Uncut.MinU + Uncut.MaxU) / 2.0f, Uncut.MinV, (Uncut.MinV + Uncut.MaxV) / 2.0f);
HeapArea[Size + 1][1] = MakeQARect((Uncut.MinU + Uncut.MaxU) / 2.0f, Uncut.MaxU, Uncut.MinV, (Uncut.MinV + Uncut.MaxV) / 2.0f);
HeapArea[Size + 1][2] = MakeQARect(Uncut.MinU, (Uncut.MinU + Uncut.MaxU) / 2.0f, (Uncut.MinV + Uncut.MaxV) / 2.0f, Uncut.MaxV);
HeapArea[Size + 1][3] = MakeQARect((Uncut.MinU + Uncut.MaxU) / 2.0f, Uncut.MaxU, (Uncut.MinV + Uncut.MaxV) / 2.0f, Uncut.MaxV);
HeapCount[Size]--;
}

//Post: True iff something larger could be divided to Size
//SideEffect: HeapCount[Size] > 0 if true is returned
bool QuadAllocator::Divide(int Size) {
if (Size < 0 || Size > QASmallestSize) {
return false;
} else if (HeapCount[Size] > 0) {
Split(Size);
return true;
} else if (Divide(Size - 1)) {
Split(Size);
return true;
} else {
return false;
}
}

//SideEffect: Split bigger squares if needed and store the new allocation in the camera if there is enough memory left.
QARect QuadAllocator::Allocate(int Size) {
QARect RectResult;
bool CanAllocate;
if (HeapCount[Size] > 0) {
// We have found something to allocate
CanAllocate = true;
} else {
// We might be able to find something bigger to split
CanAllocate = Divide(Size - 1);
}
if (CanAllocate == true) {
// We could allocate something and return it
RectResult = HeapArea[Size][HeapCount[Size] - 1];
HeapCount[Size]--;
return RectResult;
} else {
return MakeQARect(-1,-1,-1,-1);
}
}


LanguageMacros.h

#define LoopForward(min,var,max) for(var=min;var<=max;var++)
#define LoopBackward(min,var,max) for(var=max;var>=min;var--)
#define LoopForwardStartAndLength(var,start,length) LoopForward(start,var,((start)+(length))-1)
#define LoopBackwardStartAndLength(var,start,length) LoopBackward(start,var,((start)+(length))-1)
#define LoopForwardLengthFromZero(var,length) LoopForward(0,var,(length)-1)
#define LoopBackwardLengthFromZero(var,length) LoopBackward(0,var,(length)-1)
The game engine used in my last job did pretty much this -- all of the shader input registers were specified in a shared header file. Some of the variables would be set automatically by the engine (e.g. projection, etc), and other slots were set by the game or the material (e.g. diffuse texture).
is there any problem related to the fact that every shader has access to sampler (and other resources) even though it might not use it?[/quote]* If a shader doesn't use a variable from the header, but the engine uploads/binds that variable -- this won't cause any problems, but it's a bit of wasted CPU performance if you still try to set the variable.
* If a shader uses a variable, but the engine doesn't bind that variable -- this is an error condition, you need to detect it and log an error for the game developer (e.g. to fix their materials, etc). If you detect this, you can also substitute a default value (we bound a green/pink checkerboard image in the case of an unspecified texture).

You can detect these case by looking at the reflection/meta-data of the shader (the unused variables are stripped by the compiler), and comparing it to your current render commands / materials / state / etc...

The textures should have some texture channels that are userdefined for game specific needs. If someone using your engine want to have 5 diffuse textures, a blend map, an infra red map, an ultra violet map, a bump map and a displacement map, the predefined names will be confusing and make the user worried about using the engine in the wrong way.

Well, if someone using my engine wants to use 5 diffuse textures he can use 5 of the other almost 100 texture slots available. Those predefined texture names are just there to help building simple materials. :cool:

* If a shader doesn't use a variable from the header, but the engine uploads/binds that variable -- this won't cause any problems, but it's a bit of wasted CPU performance if you still try to set the variable.

Each texture parameter in my engine has a Bind flag (BING_TO_VERTEX_SHADER, etc) so the textures are only bound to the shaders that use it.


If you detect this, you can also substitute a default value (we bound a green/pink checkerboard image in the case of an unspecified texture).

Sounds like a good idea.

This topic is closed to new replies.

Advertisement