Advertisement

Const everything in shaders? Practicality, readability, consistency

Started by May 09, 2020 05:03 PM
5 comments, last by MJP 4 years, 8 months ago

Hi,

Curious/want advice what other GPU devs perspectives are on spamming const in front of every variable in shader code or what guideline you follow? I'm on a new team and we have different opinions on this, but I find it hard to reason about it since it is partly a style thing and I see the style of this being a mixed bag in our org code, depending on a team. It's also not discussed in our org coding guidelines.

Some folks on my team believe in putting const in front of every single variable that's not going to change for consistency. However, when writing shaders, where you do a lot of data transformations, you define a lot of transitional/temporary/single use variables, so that you give specific names to them, or temporary variables to store values from textures, leading to const being suggested to be specified in front of every variable since they aren't expected to change and for consistency. That makes every statement longer, you have to skimread through const before getting to the variable name for every variable, increases mental/reading cost to process. It is a non-negligible cost to process it and adds up long-term. Especially when you have to use a longer var names naming for complex variables. There's a value in being concise (easier/faster to skimread through code, easier code reviews, since there's less text and less likely to text wrap when viewing diffs side by side on screen) and adding const to everything by default makes it all just longer. In my former job, I followed a guideline of specifying const where you explicitly want to guard something against change, or making it obvious it would break algorithm if the var changed, and/or making a var being constant obvious (such as const float kGravity = 9.81) , but not in front of everything. However coding style consistency is also very important when working on shared codebase.

What do you think/suggest/follow?

Consider an example where you put const everywhere:

float3 getWallBounceDirection(	in const uint2 DTid, 
								in const float3 incidentDirection,
								in const float speedScaledByDimensionY,
								in const float speedClamp,
								in const float3 rotateWallPlanePos,
								in const float3 wallPlaneBentNormal)
{
	const uint2 kResolution = uint2(1920, 1080);
	const uint linearIndex = DTid.y * kResolution.x + DTid.x;
	
	const float3 upDirection = float3(0, 1, 0);
	const float NdotUp = dot(normal, upDirection);
	const float depth = MyDepthTexture[DTid];

	// Get reflection vector
	const float3 normalVector = MyNormaTexture[DTid];
	const float3 IdotN = dot(incidentDirection, normalVector);
	const float3 reflectionVector = incidentDirection - 2 * IdotN * normalVector;
	
	// Example of more complex named vars
	const float kGravity = 9.81;
	const float3 windFieldCoefficient = MyWindFieldTexture1D[linearIndex];
	const float3 vectorWindFieldCoefficientScaledByGravityPull = 
		windFieldCoefficient * kGravity * (-upDirection); 
	const float3 surfacePosition = screenToWorldPosition(DTid, depth);
	const float kRangeRadius = 9.1;
	const uint numberOfParticlesInTheNeighborhood = 
		getNumberOfParticleNeighbors(surfacePosition, kRangeRadius);
	const float algebraicCoefficientMultipliedByFactorOfXYZ = ...
	
	// Get penalty force	
	const float kPenaltyForceCoeff = 27;
	const float kFriction = 21;
	conts float ...
	
	// imagine 1-50 lines of code reusing some vars above
	
	return bouncedDir;
}

Colour me paranoid, but usually i observe the const correctness. When a constant value shows up in a calculation, e.g. as an intermediate, I'd decorate it. At least when it is part of a longer calculation over more than a few lines.

But the example is rather artificial, isn't it ? Some of those constants would be uniforms and thus constant anyway. As I do glsl, the function parameters/arguments can be decorated in (default, doesn't need an extra const), out or inout.

It is personal style, isn't it ? The compiler knows well which values are needed down the line and which ones aren't …

Advertisement

I've always thought that C++ should default to const storage for everything, and required anything mutable to be explicitly declared as such; in other words, the opposite of what we do today. Probably not practical for legacy code, that cat's not going back in it's bag, but for new code a #pragma would sure be nice.

For shaders - I guess if it makes you happy. I don't see it being as clear a cost/benefit case as for C++, and code that's a wall of “const const const const const” sure is harder to read, not to mention that the shader compilation and execution model is sufficiently different from C++ to make it's benefits not as compelling. It's also useful if a team knew and understood the differences between const in shader code and const in C++ rather than just cargo culting it. But if a team really wanted to do it, opposing it is not a hill I'd choose to die on.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

21st Century Moose said:
not to mention that the shader compilation and execution model is sufficiently different from C++ to make it's benefits not as compelling. It's also useful if a team knew and understood the differences between const in shader code and const in C++ rather than just cargo culting it.

Could you elaborate on this? I've always assumed (and verified via compiler-explorer) that const in c++ does not have any effect on execution (talking about local variables and not compile-time constant statics), as through the existance of mutable and const-casts, the compiler would be hardpressed to prove that a constant really stays constant any more than it can already by analysing the control-flow.

21st Century Moose said:

It's also useful if a team knew and understood the differences between const in shader code and const in C++ rather than just cargo culting it. But if a team really wanted to do it, opposing it is not a hill I'd choose to die on.

It's hard to quantify the overhead cost from working with such code and plus its a cost you pay. For the other devs not seeing the difference in reading overhead between the two, they can more directly justify benefits of protecting vars and thus potentially preventing bugs, which any devs understand as an important benefit. I agree with the last statement and probably that's how it's going to go down.

Personally I am generally on Team Const, but I'm not sure I would be in favor of heavy-handed results about using it in shader code for temporary variables. I also don't think there's any hard data at all about the benefits vs. drawbacks for const in any language, and without that I think we just end up with a lot of anecdotes and opinions. ?

My own personal opinion is that strategic use of const can reduce the mental burden of reading code, since you know that the value of something doesn't change after the declaration. Without it you have to scan through all of the code for assignments after-the-fact before you can reason about the current value of a variable. But I also understand the counter-arguments, especially the ones you can make against taking const all the way to the full extreme (like in that code snippet).

The only other thing I would bring up is that shader code does tend to work a bit differently than C++ code due to the overall lack of things like constructors/destructors and side effects. When I copy around a big struct in HLSL I'm a hell of a lot more confident that the copies will end up getting optimized away than I would be in C++, and that may potentially play into how I write the code and make use of “const”

This topic is closed to new replies.

Advertisement