Live OpenGL shader coding playground written in C89, nostdlib in a single file and tiny executable (~28kb).
It allows you to live edit GLSL fragment shaders and automatically displays the changes.
shade_it.mp4
Warning
THIS PROJECT IS A WORK IN PROGRESS! ANYTHING CAN CHANGE AT ANY MOMENT WITHOUT ANY NOTICE! USE THIS PROJECT AT YOUR OWN RISK!
- Hot GLSL Shader reloading - Saved changes to the GLSL file will be immediatly visible
- Minimal binary size — around 28kb binary
- Zero dependencies — own win32 layer implementation, own OpenGL context creation
- Debug UI - Press F1 to see performance metrics and GLSL compilation errors/logs
- Screen Recording - Press F2 to start/stop recording the screen to a raw video file
- Fullscreen Support - Press F9 for borderless. Press F11 for classic fullscreen mode
- XInput Gamepad Support - Full XInput compatible gamepad/controller support in shaders
- C89 compliant — portable and legacy-friendly
- nostdlib — no dependency on the C Standard Library
- no memory allocation — There is no memory allocation by the tool itself. It only uses the memory Windows is acquiring for the OpenGL Framebuffers, ...
- Strict compilation — built with aggressive warnings & safety checks
Download the latest Windows executable for X64 or ARM from the Latest Release:
Clone the repository and build manually. Either GCC or Clang is required to run the build script.
git clone https://github.com/nickscha/shade_it.git
cd shade_it
win32_shade_it_build.batYou can now run the win32_shade_it.exe program.
Important
If you have downloaded the release executeable depending on your platform use win32_shade_it_x64.exe or win32_shade_it_arm.exe
REM This expects a file named "shade_it.fs" to be present
win32_shade_it_x64.exe
REM Alternativly you can define your own fragment shader file
win32_shade_it_x64.exe examples\shade_it_star_nest.fsWhile running you can edit and save the fragment shader source file and shade_it will immediatly hot-reload the code.
If you want to create your own GLSL fragment shader from scratch you can use this template providing all uniforms shade_it hands over to the fragment shader:
#version 330 core
out vec4 FragColor;
/* Uniforms provided */
uniform vec3 iResolution; // viewport width and height
uniform float iTime; // time elapsed in seconds
uniform float iTimeDelta; // render time in seconds
uniform int iFrame; // total frame counter
uniform float iFrameRate; // current frames per second
uniform vec4 iMouse; // mouse position (x,y)
uniform vec4 iTextureInfo; // texture width, height, cell width, cell height
uniform sampler2D iTexture; // texture slot
uniform vec4 iController[2]; // full controller state
/* DEMO: Render gradient colors accross the entire screen */
void mainImage(out vec4 outColor, in vec2 fragCoord)
{
vec2 uv = fragCoord / iResolution.xy;
float t = iTime;
outColor = vec4(uv, 0.5 + 0.5 * sin(t), 1.0);
}
void main()
{
vec2 fragCoord = gl_FragCoord.xy;
mainImage(FragColor, fragCoord);
}Under the examples folder you can find different kinds of fragment shaders for inspiration that you can run directly.
When the application is started it will write a log file named shade_it.log. Beside the Debug UI you can also find here GLSL compilation errors if a shader is invalid.
If you encounter a problem/bug please attach this log file to the issue.
Press F1 to show/hide the debug information, performance metrics and GLSL compilation errors/logs.
Press F2 to start/stop recording the screen to a RAW video file.
The file created is named according to the screen size and target fps:
- Format: shade_it_capture_<window_width>x<window_height>_<target_frames_per_second>.raw
- Example: shade_it_capture_800x600_60.raw
Warning
Since it is a RAW video output the size of the file size can be huge.
After the recording has been stopped you can use a tool like FFMPEG or similar to convert it to a video format.
Example of creating a MP4 video using FFMPEG:
ffmpeg -f rawvideo -pix_fmt rgb24 -s 800x600 -r 60 -i shade_it_capture_800x600_60.raw -vf vflip out.mp4Note that the size (here 800x600) and the FPS (here 60) have to match with your recorded data.
You can enter/leave the borderless window mode by pressing F9.
You can enter/leave the "classic" fullscreen window mode by pressing F11.
If you want to freeze/pause the current shader display you can press P.
If you want to reset the current elapsed time (iTime) and processed frames (iFrame) you can press R.
You can change the default target FPS (60) in the Debug UI by pressing LEFT or RIGHT arrow key. A target fps of 0 means to render as fast as possible without constraints.
SHADE_IT supports XInput compatible controllers. When connected you can also see button presses, thumbstick and trigger values in the Debug UI (F1).
The controller state is packed into a uniform vec4 iController[2] for shaders.
output.mp4
| Vector Index | Component | GLSL Access | Type | Description | Range |
|---|---|---|---|---|---|
iController[0] |
.x |
.x |
float |
Left Stick X | [-1.0, 1.0] |
.y |
.y |
float |
Left Stick Y | [-1.0, 1.0] |
|
.z |
.z |
float |
Right Stick X | [-1.0, 1.0] |
|
.w |
.w |
float |
Right Stick Y | [-1.0, 1.0] |
|
iController[1] |
.x |
.x |
float |
Trigger Left | [0.0, 1.0] |
.y |
.y |
float |
Trigger Right | [0.0, 1.0] |
|
.z |
.z |
uint* |
Packed Buttons | See Bitmask Table | |
.w |
.w |
N/A |
Padding | Unused |
Use the bitwise AND operator & with (1u << Index) to check for button states.
| Bit Index | Button / Input | Description |
|---|---|---|
| 0 | BUTTON_A |
|
| 1 | BUTTON_B |
|
| 2 | BUTTON_X |
|
| 3 | BUTTON_Y |
|
| 4 | SHOULDER_L |
Left Bumper |
| 5 | SHOULDER_R |
Right Bumper |
| 6 | TRIGGER_L |
Digital Threshold (Is Pulled) |
| 7 | TRIGGER_R |
Digital Threshold (Is Pulled) |
| 8 | DPAD_UP |
|
| 9 | DPAD_DOWN |
|
| 10 | DPAD_LEFT |
|
| 11 | DPAD_RIGHT |
|
| 12 | STICK_L_CLICK |
Left Thumbstick Button |
| 13 | STICK_R_CLICK |
Right Thumbstick Button |
| 14 | START |
|
| 15 | BACK |
|
| 16 | CONNECTED |
1 if controller is active |
Important
iController[1].z contains raw bit data. In your shader, you must use floatBitsToUint() to decode the button states correctly.
A basic controller shader example can be found in examples/shade_it_controller.fs.
Here is a minimal example of how to access the data:
bool controller_button_down(uint bit_index)
{
uint bits = floatBitsToUint(iController[1].z);
return (bits & (1u << bit_index)) != 0u;
}
void mainImage(out vec4 outColor, in vec2 fragCoord)
{
// Access Analog Sticks
vec2 left_stick = iController[0].xy;
vec2 right_stick = iController[0].zw;
// Access Triggers
float left_trigger = iController[1].x;
float right_trigger = iController[1].y;
// Access Buttons
bool btn_a = controller_button_down(0u);
bool btn_b = controller_button_down(1u);
bool btn_x = controller_button_down(2u);
bool btn_y = controller_button_down(3u);
bool dpad_up = controller_button_down(8u);
// ...
}For the next release of SHADE_IT the following features are planend.
- Optimize stack usage with arena as suggested by @skeeto
- Efficient XInput state retrieval using HID notifications (only check connection state when a device is connected or disconnected from the machine). This improves performance since XInputGetState for disconnected or invalid IDs can cause full BUS scan and is quiet slow. With this change we only query XInputState if a device is connected/disconnected.
- win32 entry name change as suggested by @skeeto
- Improved OpenGL functions loader. Some GPU Drivers can return from wglGetProcAddress: (void *)0x1, (void *)0x2, (void *)0x3, (void *)-1 instead of a NULL pointer!
- UI Font Rendering improvements (static glyphs are calcuated once)
- Controllable target FPS via Debug UI (Use left or right arrow key when UI is enabled. Target FPS = 0 means unlimited)
- Win32 ARM release build
- Fullscreen support for both windowed and borderless (F11 and F9)
- Reset Shader iTime and iFrame (R)
- Full XInput Controller support (Retrieving logic implemented in v0.5 but not yet optimized and passed to shaders)
nostdlib is a lightweight, minimalistic approach to C development that removes dependencies on the standard library. The motivation behind this project is to provide developers with greater control over their code by eliminating unnecessary overhead, reducing binary size, and enabling deployment in resource-constrained environments.
Many modern development environments rely heavily on the standard library, which, while convenient, introduces unnecessary bloat, security risks, and unpredictable dependencies. nostdlib aims to give developers fine-grained control over memory management, execution flow, and system calls by working directly with the underlying platform.
By removing the standard library, nostdlib significantly reduces runtime overhead, allowing for faster execution and smaller binary sizes.
Standard libraries often include unnecessary functions that increase the attack surface of an application. nostdlib mitigates security risks by removing unused and potentially vulnerable components.
Without linking to the standard library, binaries are smaller, making them ideal for embedded systems, bootloaders, and operating systems where storage is limited.
Direct control over system calls and memory management leads to performance gains by eliminating abstraction layers imposed by standard libraries.
By relying only on fundamental system interfaces, nostdlib allows for easier porting across different platforms without worrying about standard library availability.



