2.8
Shaders

- 2.8.1. Overview
- 2.8.2. Interface And Calling Protocol
- 2.8.3. Shader Modules
- 2.8.4. Types

2.8.1
Overview
1
A shader is a small program that executes on the GPU and is used to produce images. In the r2 package, shaders perform a variety of tasks and the programmer is not always exposed to them directly. The primary shader types to which the programmer is directly exposed are instance and light shaders.
2.8.2
Interface And Calling Protocol
1
Every shader in the r2 package has an associated Java class. Each class may implement one of the interfaces that are themselves subtypes of the R2ShaderType interface. Each class is responsible for uploading parameters to the actual compiled GLSL shader on the GPU. Certain parameters, such as view matrices, the current size of the screen, etc, are only calculated during each rendering pass and therefore will be supplied to the shader classes at more or less the last possible moment. The calculated parameters are supplied via methods defined on the R2ShaderType subinterfaces, and implementations of the subinterfaces can rely on the methods being called in a very strict predefined order. For example, instances of type R2ShaderInstanceSingleUsableType will receive calls in exactly this order:
2.8.2.2 R2ShaderInstanceSingleUsableType call order

1. First, onActivate will be called. It is the class's responsibility to activate the GLSL shader at this point.
2. Then onReceiveViewValues will be called when the current view-specific values have been calculated.
3. Now, for each material m that uses the current shader:
   1. onReceiveMaterialValues will be called once.
   2. For each instance i using that uses a material that uses the current shader, onReceiveInstanceTransformValues will be called, followed by onValidate.

3
The final onValidate call allows the shader to check that all of the required method calls have actually been made by the caller, and the method is permitted to throw R2ExceptionShaderValidationFailed if the caller makes a mistake at any point. The implicit promise is that callers will call all of the methods in the correct order and the correct number of times, and shaders are allowed to loudly complain if and when this does not happen.
4
Of course, actually requiring the programmer to manually implement all of the above for each new shader would be unreasonable and would just become a new source of bugs. The r2 provides verifiers to perform the run-time checks listed above without forcing the programmer to implement them all manually. The R2ShaderInstanceSingleVerifier type, for example, implements the R2ShaderInstanceSingleUsableType interface and wraps call calls to an existing shader in calls that enforce the invariants and call order described above. The programmer writes the simple version of the shader that simply uploads parameters, and the verifier prevents callers from calling shader methods in the wrong order.
5
The calling protocol described both ensures that all shader parameters will be set and that the renderers themselves are insulated from the interfaces of actual GLSL shaders. Failing to set parameters, attempting to set parameters that no longer exist, or passing values of the wrong types to GLSL shaders is a common source of bugs in OpenGL programs and almost always results in either silent failure or corrupted visuals. The r2 package takes care to ensure that mistakes of that type are difficult to make.
2.8.3
Shader Modules
1
Although the GLSL shading language is anti-modular in the sense that it has one large namespace, the r2 package attempts to relieve some of the pain of shader management by delegating to the sombrero package. The sombrero package provides a preprocessor for shader code, allowing shader code to make use of #include directives. It also provides a system for publishing and importing modules full of shaders based internally on the standard Java ServiceLoader API. This allows users that want to write their own shaders to import much of the re-usable shader code from the r2 package into their own shaders without needing to do anything more than have the correct shader jar on the Java classpath [14].
2
As a simple example, if the user writing custom shaders wants to take advantage of the bilinear interpolation functions used in many r2 shaders, the following #include is sufficient:
2.8.3.3 Include

    #include <com.io7m.r2.shaders.core/R2Bilinear.h>

    vec3 x = R2_bilinearInterpolate3(...);

4
The text com.io7m.r2.shaders.core is considered to be the module name, and the R2Bilinear.h name refers to that file within the module. The sombrero resolver maps the request to a concrete resource on the filesystem or in a jar file and returns the content for inclusion.
2.8.4
Types
1
In the r2 package, shaders are instances of R2ShaderType.

[14]
The core of the r2 package depends directly on the shader package, so the correct jars will inevitably be on the classpath already.