- Download from the Releases section.
- May require the .NET Desktop runtime
- May require the ASP.NET .NET runtime
GLSL Shader Shrinker is a Windows GUI tool that attempts to reduce the size of GLSL fragment shader code, whilst keeping it readable and understandable.
It is written in C# using WPF and Visual Studio 2019, and has several hundred NUnit-powered unit tests.
It is designed to work primarily with code from Shadertoy, but has limited support for other styles of GLSL too (E.g. Bonzomatic)
After writing a Shadertoy shader, usually from my boilerplate starting code, there is a sequence of operations I perform:
- Delete dead/commented-out code.
- Remove unused functions.
- Inline some constants (Max raymarching distance, 'hit test' accuracy, ...)
- If trying to get under the magic '4KB', simplify some of the calculations.
It occurred to me all of these steps can be automated.
This is not a tool to compete with certain other tools to absolutely MINIMIZE the size of the code - Useful for preparing GLSL for use in 4KB graphics demos, etc.
GLSL Shader Shrink will not:
- Rename functions and variable to single-characters.
- Inline functions.
- Introduce
#define
macros to minimize the code character count. - Replace code with a 'more compressible' equivalent.
- ...or otherwise 'GOLF' anything.
...although some of these items might be suggested as a 'hint'.
A small snippet of GLSL which shows some of the optimizations available.
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
// Time varying pixel color
vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
// Output to screen
fragColor = vec4(col,1.0);
}
void mainImage(out vec4 fragColor, vec2 fragCoord) {
vec2 uv = fragCoord / iResolution.xy;
fragColor = vec4((.5 + .5 * cos(iTime + uv.xyx + vec3(0, 2, 4))), 1);
}
in
parameter prefix removed.col
variable inlined.- Numbers simplified - Decimal places and in
vec4
construction. - Comments removed.
Note: All these changes are optional within the tool, and many other optimizations are available.
First, download and run the Windows installer from the 'Releases' section.
Note: The application requires the Microsoft .NET 5 runtimes to be installed. If they are not found the application will automatically prompt for them to be downloaded.
You first need to import your GLSL into the tool.
This can be achieved using:
- Copy'n'paste from the clipboard. (CTRL-V)
- Import from a text file.
- Download from Shadertoy using an 'id'.
Next choose the level of processing you want to apply.
- Maximum processing - All options enabled.
- Minimal processing - Minimal changes (Mostly code reformatting).
- Custom options - Toggle exactly which processing features you require.
Export the 'shrunk' GLSL.
This can be achieved using:
- Copy'n'paste from the clipboard. (CTRL-C)
- Export from a text file.
...and then use with Shadertoy, Bonzomatic, etc.
After shrinking your GLSL code, you might find some 'hints' are available. These range from 'this function isn't used' or 'this function is only used once, so you might like to inline it', all the way to some GOLFing hints.
Despite a lot of effort spent trying to ensure the tool produces great output every time, there are always going to be edge cases caused by different coding styles and patterns of content.
Heavy use of #define
macros and #if...#else...#endif
blocks can cause confusion when trying to parse the code. Compilers have the luxury of seeing which specific code path is enabled, but a tool like this needs to understand all possible code paths at the same time - Not always easy!
I apologize in advance if you find any issues - If I have the time I'll try my best to resolve them!
In most cases they can be worked-around using a set of 'custom' settings which disable the problematic feature.
- Remove Comments
- Keep Header Comments
- Remove Unused Functions
- Remove Unused Variables
- Remove Unreachable Code
- Remove Disabled Code
- Simplify Function Declarations
- Simplify Function Parameters
- Group Variable Declarations
- Join Variable Declarations and Assignments
- Detect New Constants
- Inline Constant Variables
- Inline Constant #defines
- Simplify Number Format
- Simplify Vector Construction
- Simplify Vector References
- Simplify Code Branching
- Combine Consecutive Assignments
- Combine Assignment With Single Use
- Introduce +=, -=, /=, *=
- Simplify Mathematical Expressions
- Perform Simple Arithmetic
- Replace Functions Calls With Result
- Move constant parameters to within called functions
Remove all C/C++ -style comments from the code.
// This comment will be removed.
int myFunc(vec3 p) { return 1; }
int myFunc(vec3 p) { return 1; }
Keep the top-most comments in the code, even when removing all others.
// 'My Shader' written Me.
// This comment will stay.
int aGlobal = 2;
// This comment will be removed.
int myFunc(vec3 p) { return 1; }
// 'My Shader' written Me.
// This comment will stay.
int aGlobal = 2;
int myFunc(vec3 p) { return 1; }
Remove any functions that are not called within the code.
Note: Only active if a main...()
function is defined.
Remove any global or local variables not used within the code.
int myFunc() {
int unused = 2; // <-This will be removed.
return 1;
}
int myFunc(vec3 p) { return 1; }
Remove any code which cannot be reached.
float myFunc(vec3 p) {
return p.x + p.y - p.z;
// This code cannot be reached.
a *= 2;
}
float myFunc(vec3 p) {
return p.x + p.y - p.z;
}
Remove any commented-out code, or code surrounded with #if 0...#endif
.
#if 1
float myFunc(vec3 p) { return p.x + p.y - p.z; }
#else
float myFunc(vec3 p) { return 3.141; }
#endif
float myFunc(vec3 p) { return p.x + p.y - p.z; }
- Removes function declarations with no matching definition.
- Removes declarations where the matching definition is early enough to be used by all its callers.
- Removes declaration parameter names.
// Declare a function.
float sum(float value1, float value2);
// Define the function.
float sum(float value1, float value2) { return value1 + value2; }
// Use the function.
void main() { myFunc(1, 2); }
// Define the function.
float sum(float value1, float value2) { return value1 + value2; }
// Use the function.
void main() { myFunc(1, 2); }
- Removes
void
parameters. - Removes
in
keywords (which is the default in GLSL).
float myFunc(void) { return 3.141; }
float sum(in float a, in float b) { return a + b; }
float myFunc() { return 3.141; }
float sum(float a, float b) { return a + b; }
- Merge multiple declarations of the same variable type (when it makes sense to do so).
- Applies to global variables, local variables, and fields in a
struct
.
struct MyType {
vec3 hit;
vec3 color;
vec2 uv;
};
struct MyType {
vec3 hit, color;
vec2 uv;
};
Join variable declarations with their corresponding assignments, removing the need for the variable name to be specified twice.
float myFunc() {
float result; // This will move.
float b = 1.0;
result = b * 3.141;
return result;
}
float myFunc() {
float b = 1.0;
float result = b * 3.141;
return result;
}
Note: Fully simplified this would become...
float myFunc() { return 3.141; }
Find any variables assigned a value that can be made const
.
Note: These can become candidates for inlining into the code, when used with other options.
float myFunc() {
float PI = 3.141;
return 2.0 * PI;
}
float myFunc() {
const float PI = 3.141;
return 2.0 * PI;
}
Remove a const
variable by inlining it in all the places it is used.
Note: This will only be performed if it will result in shorter code.
const float MAX_DIST = 128.0;
bool isVisible(float dist) { return dist <= MAX_DIST; }
bool isVisible(float dist) { return dist <= 128.0; }
Remove a #define
by inlining its (constant) value in all the places it is used.
Note: This will only be performed if it will result in shorter code.
#define MAX_DIST 128.0
bool isVisible(float dist) { return dist <= MAX_DIST; }
bool isVisible(float dist) { return dist <= 128.0; }
Performs a variety of formatting changes to represent numbers using less characters.
float a = 1.200;
float b = 001.00;
float c = 23.0f;
float d = float(1.2);
float e = float(12);
float f = 123000.0;
int g = int(1.2);
int h = int(23);
float a = 1.2;
float b = 1.;
float c = 23.;
float d = 1.2;
float e = 12.;
float f = 123e3;
int g = 1;
int h = 23;
Simplify the construction of vector and matrix types.
vec3 a = vec3(1.0, 2.0, 3.0);
vec2 b = vec2(4.0, 4.0);
vec3 c = a.xyz;
vec3 d = vec3(a);
vec3 a = vec3(1, 2, 3);
vec2 b = vec2(4);
vec3 c = a;
vec3 d = a;
Simplify the construction of vector and matrix types.
vec3 a = vec3(1, 2, 3);
vec2 b = vec2(a.x, a.y);
vec3 c = vec2(a.x, a.y, a.z, a.x);
vec3 d = vec3(other_vec3);
vec3 a = vec3(1, 2, 3);
vec2 b = a.xy;
vec3 c = a.xyzx;
vec3 d = other_vec3;
Simplify branches by removing the else
keyword where possible.
if (a == b)
return a;
else // < Not required.
return a + b;
if (a == b)
return a;
return a + b;
Consecutive assignments of the same variable can often be inlined.
float doMaths() {
float a = myFunc();
a = pow(a, 2.0);
a = a + 23.3;
return a;
}
float doMaths() {
float a = pow(myFunc(), 2.0) + 23.3;
return a;
}
A variable assignment used on the next line can often be inlined, if that next line is an assignment or if
condition.
float doMaths() {
float a, b, c;
a = myFunc();
b = pow(a, 2.0);
c = b * 2.2;
return c;
}
float doMaths() {
float c;
c = pow(myFunc(), 2.0) * 2.2;
return c;
}
Also
bool f() {
float a = getValue();
if (a > 2.)
return true;
return false;
}
bool f() {
if (getValue() > 2.)
return true;
return false;
}
Make use of a combined math operator/assignment when possible.
float doMaths() {
float a = 2.1;
a += 1.0;
a = a * 3.141;
return a;
}
float doMaths() {
float a = 2.1;
a++;
a *= 3.141;
return a;
}
Reduce unnecessary round brackets when performing arithmetic.
float doMaths() {
return (2.0 * (3.141)) * (1.1 + 2.2);
}
float doMaths() {
return 2.0 * 3.141 * (1.1 + 2.2);
}
Pre-evaluate simple arithmetic. E.g.
- Change
a = b + -c
toa = b - c
- Change
f * 1.0
orf / 1.0
tof
- Change
f + 0.0
orf - 0.0
tof
- Remove
f * 0.0
(when safe). - Change
pow(3.0, 2.0)
to9.0
- Change
float a = 1.2 / 2.3 * 4.5;
tofloat a = 2.3478;
- Change
vec2 f = vec2(1.1, 2.2) + 3.3 * 4.4;
tovec2 f = vec2(15.62, 16.72);
If the result of a function call can be calculated, replace the call with the result.
float doMaths(float a, float b, float c) {
return a * b + a + sin(c);
}
float f() {
float result = doMaths(1.0, 2.0, 3.14159);
}
float f() {
float result = 3.0;
}
If all calls to a function use the same constant parameter, attempt to remove the parameter from the call site and inline it into the called function.
float doMaths(float a, float b) {
return a * b;
}
float f() {
// All calls pass '2.0' for parameter 'a'.
float result = doMaths(2.0, 3.0) + doMaths(2.0, 5.0);
}
float doMaths(float b) {
return 2.0 * b;
}
float f() {
float result = doMaths(3.0) + doMaths(5.0);
}