Jump to content
  • Advertisement
Sign in to follow this  
AdeptStrain

DX11 Best way to organize HLSL code in DX11?

This topic is 2292 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hey all,

Given the way DirectX 11 works with shaders (and that effects are finally tossed to the wayside), I thought about organizing all my vertex shaders/pixel shaders/etc into one "library"-esque file. For the more complex shaders I would branch those out into their own files for organization sake, but for the basic stuff it seemed fitting to move everything into one file. Are there potential issues with this approach?

HLSL file below.

//--------------------------------------------------------------------------------------
// Constant Buffer Variables
cbuffer WVPConstantBuffer : register( b0 )
{
matrix World;
matrix View;
matrix Projection;
}

cbuffer GradientConstantBuffer : register(b1)
{
float4 sourceColor;
float4 destColor;
}
//--------------------------------------------------------------------------------------
// Textures
Texture2D diffuseTex : register( t0 );
//--------------------------------------------------------------------------------------
// Sampler states
SamplerState linearSamp : register( s0 );
//--------------------------------------------------------------------------------------
// Various supported structs
struct VS_InTexturedVert
{
float4 Pos : POSITION;
float2 Tex : TEXCOORD0;
};

struct VS_OutTexturedVert
{
float4 Pos : SV_POSITION;
float2 Tex : TEXCOORD0;
};

struct VS_InColoredVert
{
float4 Pos : POSITION;
float4 Color : COLOR0;
};

struct VS_OutColoredVert
{
float4 Pos : SV_POSITION;
float4 Color : COLOR0;
};
//--------------------------------------------------------------------------------------
// Vertex Shaders
VS_OutTexturedVert VS_TexturedVertWVP(VS_InTexturedVert input ) // Basic World x View x Projection vert shader.
{
VS_OutTexturedVert output = (VS_OutTexturedVert)0;
output.Pos = mul( input.Pos, World );
output.Pos = mul( output.Pos, View );
output.Pos = mul( output.Pos, Projection );
output.Tex = input.Tex;
return output;
}

VS_OutTexturedVert VS_TexturedVert(VS_InTexturedVert input ) // No world x view x projection multiplication so everything will be in screen space.
{
VS_OutTexturedVert output = (VS_OutTexturedVert)0;
output.Pos = input.Pos;
output.Tex = input.Tex;
return output;
}

VS_OutColoredVert VS_ColoredVertWVP(VS_InColoredVert input ) // Colored vertex, world x view x projection multi.
{
VS_OutColoredVert output = (VS_OutColoredVert)0;
output.Pos = mul( input.Pos, World );
output.Pos = mul( output.Pos, View );
output.Pos = mul( output.Pos, Projection );
output.Color = input.Color;
return output;
}

//--------------------------------------------------------------------------------------
// Pixel Shaders
float4 PS_TextureSample( VS_OutTexturedVert input ) : SV_Target0 // Simple texture sample pixel shader.
{
return diffuseTex.Sample( linearSamp, input.Tex );
}

float4 PS_Gradient( VS_OutTexturedVert input ) : SV_Target0 // Lerp between two color values based on texture coordinates.
{
float4 color;

color[0] = smoothstep(sourceColor[0], destColor[0], input.Tex.y);
color[1] = smoothstep(sourceColor[1], destColor[1], input.Tex.y);
color[2] = smoothstep(sourceColor[2], destColor[2], input.Tex.y);
color[3] = smoothstep(sourceColor[3], destColor[3], input.Tex.y);

return color;
}

float4 PS_Colored( VS_OutColoredVert input ) : SV_Target0 // Simple colored vertex pixel shader.
{
return input.Color;
}

Share this post


Link to post
Share on other sites
Advertisement
Look at how much code you have duplicated between functions.
VS_TexturedVertWVP() and VS_ColoredVertWVP() are the same except for one line.

As things grow, more and more code will be copy-pasted and you will have an unmaintainable mess in an attempt to handle the literally thousands of combinations of possible shader settings, inputs, and outputs.


There is really no way to avoid some kind of mess but you need to mitigate it as much as you can and reduce the amount of duplicated code.

Macros are a better way of adding dynamic branching to shaders at only the parts that need to be different from one permutation to another.
Macro spaghetti may be its own flavor of a mess, but it is much more flexible than adding a whole new function for every possible shader feature combination, and there are things you can do to improve the readability of the code despite the macro spaghetti.

This is an example:
// == Uniforms.
uniform float[4][4] g_mModelViewProjMatrix : WORLDVIEWPROJ;
uniform float[4][4] g_mModelViewMatrix : WORLDVIEW;
uniform float[4][4] g_mModelMatrix : WORLD;
uniform float[3][3] g_mNormalMatrix : NORMALMATRIX;
void Main(

#ifdef LSE_NORMALS
in vec3 _vInNormal : NORMAL0,
out vec3 _vOutNormal : NORMAL0,

#ifdef LSE_NORMALTEX
in vec3 _vInTangent : TANGENT0,
in vec3 _vInBiNormal : BINORMAL0,
out vec3 _vOutTangent : TANGENT1,
out vec3 _vOutBiNormal : BINORMAL1,
#endif // #ifdef LSE_NORMALTEX

#endif // #ifdef LSE_NORMALS

#ifdef LSE_2DTEX0
in vec2 _vIn2dTex0 : TEXCOORD0,
out vec2 _vOut2dTex0 : TEXCOORD2,
#endif // #ifdef LSE_2DTEX0

#if !defined( LSE_DEPTH_IS_READABLE ) && defined( LSE_MAKE_DEPTH_COPY )
out vec4 _vOutDepthPos : LSE_DEPTH_COPY_SEMANTIC,
#endif // #if !defined( LSE_DEPTH_IS_READABLE ) && defined( LSE_MAKE_DEPTH_COPY )

#ifdef LSE_ENVMAPTEX
out vec4 _vOutWorldPos : LSE_ENVMAP_POS_SEMANTIC,
#endif // #ifdef LSE_ENVMAPTEX
in vec3 _vInPos : POSITION0,
out vec4 _vOutPos : POSITION0,
out vec4 _vOutEyePos : TEXCOORD1 ) {
_vOutPos = mul( g_mModelViewProjMatrix, vec4( _vInPos, 1.0f ) );
_vOutEyePos = mul( g_mModelViewMatrix, vec4( _vInPos, 1.0f ) );

#if !defined( LSE_DEPTH_IS_READABLE ) && defined( LSE_MAKE_DEPTH_COPY )
_vOutDepthPos = _vOutPos;
#endif // #if !defined( LSE_DEPTH_IS_READABLE ) && defined( LSE_MAKE_DEPTH_COPY )

#ifdef LSE_NORMALS
_vOutNormal = mul( g_mNormalMatrix, _vInNormal );
#endif // #ifdef LSE_NORMALS

#ifdef LSE_2DTEX0
_vOut2dTex0 = _vIn2dTex0;
#endif // #ifdef LSE_2DTEX0

#ifdef LSE_NORMALTEX
_vOutTangent = normalize( mul( g_mNormalMatrix, _vInTangent ) );
_vOutBiNormal = normalize( mul( g_mNormalMatrix, _vInBiNormal ) );
#endif // #ifdef LSE_NORMALTEX

#ifdef LSE_ENVMAPTEX
_vOutWorldPos = mul( g_mModelMatrix, vec4( _vInPos, 1.0f ) );
#endif // #ifdef LSE_ENVMAPTEX
}



Proper and consistent spacing and commenting reduces macro spaghetti.
This system allows the shader to be recompiled with any combination of the above macros and without any duplicate code.

The time to start a new shader file comes under 2 conditions:
#1: When a bunch of functions are utility functions of a common group. For example lighting models in one file, shadow routines in another, to be included by other shaders as needed.
#2: When the shading result is significantly different from another. This is my per-pixel vertex shader. Per-vertex lighting is significantly different enough to warrant being in another file. Then there are the vertex shaders for GeoMipmap terrain and for GeoClipmap terrain. And one for generating shadow maps.


L. Spiro

Share this post


Link to post
Share on other sites
Hmm, interesting approach. Never thought of going with a macro driven design. Definitely would help me get closer to my "one source of truth" goal while remaining flexible.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!