xShader
XShader Isn't a GPU Shader — and That's Fine
- image
- image
- shader_info
- video
The name oversells it a little: xShader is not a GPU shader compiler, and it won't run ShaderToy pastes at frame rate. What it actually is - a small interpreter that evaluates a GLSL/HLSL-flavored language per pixel, vectorized over the whole image with numpy - is more useful than that sounds, because it gives you a cheap, deterministic way to do the finishing moves people keep leaving ComfyUI for: vignettes, warm/cool grades, film curves, subtle warps. It's the kind of node that makes a static render feel like a still from a movie, and it runs in milliseconds on CPU. From the MKRShift Nodes pack, it sits under Utility → Shader.
How it works
You write shader code in the shader_code box, pick GLSL or HLSL, and xShader parses the body into statements, translates GLSL-isms into vectorized numpy operations, and evaluates them across every pixel at once. It supports the vocabulary you actually use: vec2/3/4 swizzles (.rgb, .xy, .a), mix, fract, saturate, ternary conditionals, and texture sampling via texture(srcTex, uv) - the input image arrives as srcTex. The default shader is a warm grade plus a vignette built from the UV distance to center, which is a good template to crib from.
The key limitation is baked into the design: it's a subset, not OpenGL. Full built-in libraries, loops, and arbitrary GLSL won't parse. That's the trade for not needing a GPU driver pipeline in ComfyUI, and for most finishing work the subset is enough.
The inputs that matter
Most of the required set is there to support animation, which is this node's quiet superpower:
image- the frame or batch to process.shader_language-GLSLorHLSL; mostly it selects which name conventions are translated.shader_code- the multiline body. This is the whole toy.time_mode- the interesting one. Set toper_frameandxShaderadvancestimebytime_step(default 0.0416667, i.e. one 24fps frame) for every frame in your batch. Feed it a video and you get an animated shader - pulsing vignettes, moving streaks, evolving grades.constantjust uses thetimevalue as-is.video_fps- sets the fps metadata on the video output, so downstream video nodes know the real rate.strength- 0–1 mixes toward the shaded result; above 1 it boosts contrast on top of the grade rather than just fading it in.fallback_on_error(optional) - default on. A frame that fails to parse or evaluate passes through unchanged instead of killing the whole queue. Great for batch resilience; flip it off when you're debugging and want errors loud.
What comes out
image- the shaded frames.shader_info- a status string reporting how many statements ran and, when things went wrong, the first error. Read it before assuming your shader worked.video- anMKR_VIDEOpayload carrying the frames, fps, and duration, ready for the pack's video/presave nodes.
Installing it
ComfyUI Manager → search "MKRShift Nodes", or:
cd ComfyUI/custom_nodes
git clone https://github.com/criskb/MKRShift_Nodes
No GPU toolchain, no driver SDKs, nothing to pip-install. Restart ComfyUI and it's under MKRShift Nodes → Utility → Shader.
Where people get burned
The big one is expectations: paste in a real fragment shader with loops and custom functions and you'll get a passthrough with a "No executable shader statements found" message. Keep to the subset - assignments, the swizzle/mix/fract/saturate vocabulary, ternary. Also remember per_frame only animates if your input is actually a batch of frames; feed it a single still and "animation" is one frozen frame. And if a frame fails silently, check fallback_on_error - it's the reason your output looks unprocessed while your shader_info says otherwise.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| shader_language | COMBO | GLSL | 2 options: GLSL, HLSL |
| shader_code | STRING | vec4 base = texture(srcTex, uv); vec2 p = uv * 2.0 - 1.0; float vignette = smoothstep(1.2, 0.2, length(p)); vec3 graded = base.rgb * vec3(1.04, 1.00, 0.96); return vec4(graded * vignette, base.a); | — |
| time | FLOAT | 0.00-100000–100000 | — |
| time_mode | COMBO | constant | 2 options: constant, per_frame |
| time_step | FLOAT | 0.0417-1000–1000 | — |
| strength | FLOAT | 1.000–2 | — |
| video_fps | INT | 241–240 | — |
| fallback_on_erroropt | BOOLEAN | true | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| shader_info | STRING | — |
| video | MKR_VIDEO | — |