ComfyUI Node

xShader

XShader Isn't a GPU Shader — and That's Fine

By criskb·Created 7 months ago·Updated 5 months ago· 0
xShader
  • image
  • image
  • shader_info
  • video
shader_languageGLSL
shader_codevec4 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);
time0.00
time_modeconstant
time_step0.0417
strength1.00
video_fps24
fallback_on_errortrue

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 - GLSL or HLSL; mostly it selects which name conventions are translated.
  • shader_code - the multiline body. This is the whole toy.
  • time_mode - the interesting one. Set to per_frame and xShader advances time by time_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. constant just uses the time value 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 - an MKR_VIDEO payload 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.

CategoryMKRShift Nodes/Utility/Shader

Inputs (9)

NameTypeDefaultDescription
imageIMAGE
shader_languageCOMBOGLSL2 options: GLSL, HLSL
shader_codeSTRINGvec4 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);
timeFLOAT0.00-100000–100000
time_modeCOMBOconstant2 options: constant, per_frame
time_stepFLOAT0.0417-1000–1000
strengthFLOAT1.000–2
video_fpsINT241–240
fallback_on_erroroptBOOLEANtrue

Outputs (3)

NameTypeDescription
imageIMAGE
shader_infoSTRING
videoMKR_VIDEO