Skip to content
This repository was archived by the owner on Dec 8, 2022. It is now read-only.
This repository was archived by the owner on Dec 8, 2022. It is now read-only.

Shader Modularity #1

@litherum

Description

@litherum

Metal Shading Language

Metal Shading Language has "Function Constants," (Section 4.10 of the Metal Shading Language 2.1 spec) where you can say something like

constant int a [[function_constant(0)]];

and then there's a two-phase preparation of the shader:

  1. Compile the source of the shader by calling MTLDevice.makeLibrary(options:) where you specify the preprocessor macros
  2. Specialize the shader by specifying MTLLibrary.makeFunction(constantValues:) where you specify the function constants

GLSL

Similarly, GLSL (and SPIR-V) have "Specialization Constants," (Section 7.2.1 of the OpenGL 4.6 Core spec, and section 4.11 of the GLSL 4.6 spec) where you can say something like

layout (constant_id = 0) const int a = 42;

and then there's a three-phase preparation of the shader:

  1. Specify preprocessor macros by manually generating strings of the form "#define foo 5" and injecting them into the GLSL source string at the right place
  2. glShaderSource() which presumably does some compilation
  3. glSpecializeShader()

GLSL (but not SPIR-V?) also has a concept of subroutines (Section 7.10 of the OpenGL 4.6 Core spec, and section 6.1.2 of the GLSL 4.6 spec), which are just like specialization constants, except for functions. You can say

subroutine float MySignature(float a, float b);
subroutine(MySignature) float foo(float a, float b) { ... }
subroutine(MySignature) float bar(float a, float b) { ... }
subroutine uniform MySignature myUniformName;
... 
float r = myUniformName(3.3, 4.4);

and the OpenGL API hooks up one of foo()/bar to myUniformName. Notably, this happens after compilation, and the value for myUniformName could change each frame / draw call.

HLSL

AFAICT, HLSL doesn't have anything like function constants, specialization constants, or subroutines, but instead has preprocessor macros. You can specify these as arguments to D3DPreprocess().


Language Generics / Templates Specialization Constants Subroutines Preprocessor Macros Polymorphism
HLSL
GLSL
SPIR-V
MSL
WHLSL

These constant values lead to better performance than regular constants because they can cause dead code to be removed before the shader ever hits the GPU. This is important for ubershaders, where most of the code will end up being removed.

WHLSL does not include a preprocessor because of the additional complexity it brings. Similarly, on the last WebGPU call where we discussed shading languages, we agreed to remove the ability for user-defined structs/functions to accept type arguments.

The big game engines (Unity, Unreal, etc.) often don't have a single shader to run; instead, they often have families of related shaders. For example, an engine's shader might describe a forward-rendering algorithm, but leave the BRDF and lighting model up to the specific app linking with it. Generics, specialization constants, and preprocessor macros are all ways of making this easier.

Given that HLSL has been successful without specialization constants, perhaps they aren't necessary. On the other hand, WHLSL doesn't have the mechanism that people usually use to specialize HLSL shaders (preprocessor macros). GLSL has both specialization constants and subroutines, but GLSL ES has removed both of those features, so perhaps this entire problem isn't very important. We should figure out what to do here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions