GlUniformDelegate
The GlUniformDelegate is a key component of KraftShade's shader system that provides an elegant and efficient way to manage shader uniform parameters. It leverages Kotlin's property delegation feature to create a clean, type-safe API for setting shader parameters. For more information about how this fits into the shader system, see the KraftShader documentation.
Overview
In OpenGL, uniform variables are used to pass data from the application to the shader program. Setting these values typically requires:
- Getting the location of the uniform variable in the shader program
- Calling the appropriate
glUniform*function to set the value
The GlUniformDelegate abstracts away these details, allowing shader parameters to be defined and used as simple Kotlin properties.
How It Works
The GlUniformDelegate works through a deferred update mechanism:
- When a shader parameter is set, the value is stored locally in the delegate
- A task is queued with the shader's
runOnDrawmethod - During the next draw call, the
runPendingOnDrawTasksmethod executes all queued parameter updates - The uniform value is then sent to the GPU using the appropriate
glUniform*function
This approach offers several advantages:
- Multiple parameter changes are batched together
- Updates only happen when needed (during draw calls)
- Redundant updates are avoided by tracking value changes
Implementation Details
The GlUniformDelegate is implemented as a Kotlin property delegate that implements the ReadWriteProperty interface:
open class GlUniformDelegate<T : Any>(
protected val name: String,
protected val required: Boolean = true,
protected val checkValueForSet: (T) -> Unit = {},
) : ReadWriteProperty<KraftShader, T> {
// Implementation details...
}
Key Components
-
Uniform Location Caching:
- The location of the uniform in the shader program is cached for performance
- The location is lazily initialized when first needed
-
Value Storage:
- The current value is stored locally in the delegate
- A hash code of the value is stored to detect changes
-
Deferred Updates:
- When a value is set, a task is queued with the shader's
runOnDrawmethod - The task will be executed during the next draw call
- When a value is set, a task is queued with the shader's
-
Type Handling:
- The delegate supports various types (Float, Int, Boolean, vectors, matrices)
- Each type is mapped to the appropriate
glUniform*function
Usage Example
Here's how GlUniformDelegate is used in a shader class:
class OpacityKraftShader(opacity: Float = 1.0f) : TextureInputKraftShader() {
var opacity: Float by GlUniformDelegate("opacity")
init {
this.opacity = opacity
}
override fun loadFragmentShader(): String = OPACITY_FRAGMENT_SHADER
}
In this example:
- The
opacityproperty is defined usingGlUniformDelegate - The delegate is configured to update the "opacity" uniform in the shader
- When
opacityis set, the update is queued for the next draw call
Parameter Update Flow
The following sequence illustrates how parameter updates flow through the system:
-
Property Access:
shader.opacity = 0.5f -
Delegate Setter:
- The
setValuemethod of the delegate is called - The value is stored locally
- A task is queued with the shader's
runOnDrawmethod
- The
-
Draw Call:
- When
shader.draw()is called,runPendingOnDrawTasksexecutes all queued tasks - The delegate's task retrieves the uniform location
- The appropriate
glUniform*function is called to set the value in the shader program
- When
-
Rendering:
- The shader program uses the updated uniform value during rendering
Performance Optimizations
The GlUniformDelegate includes several optimizations:
-
Value Change Detection:
- The delegate computes a hash code for the current value
- If the new value has the same hash code, the update is skipped
- This prevents redundant GPU updates when values haven't changed
-
Location Caching:
- Uniform locations are cached to avoid repeated calls to
glGetUniformLocation - This reduces CPU overhead during parameter updates
- Uniform locations are cached to avoid repeated calls to
-
Batched Updates:
- All parameter updates are batched and applied during the draw call
- This minimizes OpenGL state changes and improves performance
Supported Types
The GlUniformDelegate supports a wide range of types:
- Primitive Types: Boolean, Int, Float
- Array Types: FloatArray, GlFloatArray
- Vector Types: GlVec2, GlVec3, GlVec4
- Matrix Types: GlMat2, GlMat3, GlMat4
- Size Types: GlSize, GlSizeF
Each type is automatically mapped to the appropriate glUniform* function.