TEX Wrangle
No Node for It? Write the Pixel Math Yourself
- _tex_any
- in_0
- in_1
- in_2
- in_3
- in_4
- in_5
- in_6
- in_7
- in_8
- in_9
- in_10
- in_11
- in_12
- in_13
- in_14
- in_15
- out_0
- out_1
- out_2
- out_3
- out_4
- out_5
- out_6
- out_7
You know the feeling. You've dragged thirty nodes together, and then you hit the one thing no one ships a node for: a blend that's almost overlay, a mask that should be a luma key of a luma key, a procedural texture you want to hand-tune. ComfyUI's whole pitch is that every operation is a node, but the graph stops being the right tool once the operation is per-pixel math. TEX Wrangle is the escape hatch: a small programming language inside a single node, so instead of hunting for the node that doesn't exist, you write two lines of code.
It's not a texture pack, despite the name. TEX is a Tensor Expression language - the author openly cribs from Houdini's VEX, Nuke's BlinkScript, and VDB AX, which tells you exactly who this is for. If you've never written a shader, the first few minutes will be slow. If you've ever wished a pipeline tool would just let you type, it's immediately home.
How it works
You write C-ish code in the node's code field. Reference any connected input with @name - sockets are created automatically, so there's no schema to fight:
float gray = luma(@image);
@OUT = vec3(gray);
That's the whole trick. @name on the right side of = becomes an input socket; any @name you assign to becomes an output. Prefix a variable with $ and it becomes a widget on the node:
f$strength = 0.5; // float slider
@OUT = lerp(@base, @overlay, $strength);
Underneath, the code compiles to PyTorch ops and runs vectorized across the whole image - a for loop or if/else isn't slow Python, it's tensor math. There are built-ins for pixel coordinates (ix, iy, normalized u/v), frame index (fi/fn for batch-aware video work), a 144-function stdlib for color, noise, SDFs, blends, and sampling, and static types (float, vec3, vec4, string, arrays). Right-click → Snippets dumps 116 worked examples - steal one, tweak it, run.
The inputs and outputs that matter
The code field is the whole show - everything else is optional. The few a beginner actually touches:
device-auto(default), which follows your input tensors. Leave it alone.compile_mode-noneis the default interpreter and the right call.autoandtorch_compileneed Triton to speed anything up on GPU, which most Windows installs don't have;cuda_graphis a GPU-only replay mode that's a real win for small programs. All of them fall back to the interpreter on failure, so nothing breaks - you just get the slow path.precision- keepfp32.fp16exists for experts chasing a ~1.4× win and will happily diverge on threshold or branch-heavy code.autois a nice idea (only drops to fp16 where a condition-number gate proves it safe) but it's accuracy-neutral by design, not a speedup.debug_nan_highlight- paints NaN/Inf pixels magenta. Turn it on the moment anything looks wrong; it's free when off.
Outputs are out_0 through out_7, with types auto-inferred from whatever you assign. Wire @OUT straight into anything that takes an image or mask - it's a standard tensor on the other end.
Installing it
ComfyUI Manager: search "TEX Wrangle" → Install, restart. Or the manual route:
cd ComfyUI/custom_nodes && git clone https://github.com/xavinitram/TEX.git TEX_Wrangle
Then restart ComfyUI. Here's the part worth celebrating in the dependency-hell era: there are no dependencies and no model downloads. PyTorch does all the work - the repo's pyproject.toml lists an empty dependencies = []. Install it, use it, and it will never be the thing that breaks your environment. There's even a standalone CLI (python -m TEX_Wrangle.tex_cli run prog.tex --in a.png --out b.png) that runs programs on image files with zero ComfyUI involved.
Common issues
- A 1×1 output with no inputs - output resolution comes from connected inputs, so purely procedural code has nothing to size itself against. Connect any image (or use
iw/ihfrom a reference) to set the extent. Undefined variable 'v'or weird errors onu,ix,iy,time- these are reserved built-ins.vis the normalized y-coordinate and always defined; rename your variable tovalorvalue.torch.compiledoing nothing on Windows - expected. It needs Visual Studio Build Tools for CPU andpip install "triton-windows<3.7"(matching your torch version) for CUDA. The defaultnonemode works everywhere.- Wondering why a tier isn't engaging? Hit
/tex_wrangle/doctorin your browser for a full environment report - torch/CUDA, Triton, cache state, which tiers are actually reachable.
Fair warning: this is a niche tool with a small community, so you won't find a hundred reddit threads to rescue you - you get the built-in ? card, the wiki, and the error-code docs. Because every failure is a compile error with a fix suggestion rather than a silent wrong image, that's usually enough. If you're comfortable reading an error message, TEX Wrangle quietly becomes the node you reach for whenever the graph is the wrong shape.
Inputs (22)
| Name | Type | Default | Description |
|---|---|---|---|
| code | STRING | // TEX Wrangle // Read inputs with @A, @B, etc. // Write output to @OUT // Right-click → TEX Snippets for 116 examples float gray = luma(@IN); @OUT = vec3(gray); | TEX source code. Use @name for inputs, @name = expr for outputs. Use $name for parameter widgets. |
| deviceopt | COMBO | auto | Execution device. auto: follows input tensors. cpu/cuda: force a specific device. |
| compile_modeopt | COMBO | none | none: standard interpreter (recommended default). auto: EXPERIMENTAL measured auto-tier — runs the fast codegen path and trials torch.compile in the background, committing only on a measured win. torch_compile: force JIT-compile via torch.compile. NOTE: auto/torch_compile need Triton for any GPU speedup — Triton is absent on most Windows installs, where they simply fall back to the interpreter on CUDA (CPU torch.compile works but is often slower for small programs). Run `tex doctor` to see whether Triton is present. cuda_graph: CUDA-graph replay (GPU only, needs NO Triton; big win for small launch-bound programs). All tiers fall back to the interpreter on failure. |
| precisionopt | COMBO | fp32 | fp32: full precision (default, recommended). auto: EXPERIMENTAL — runs fp16 ONLY where a condition-number gate proves it stays accurate (CUDA, >=1024x1024, smooth pointwise, no amplification/ill-conditioning; verified across 225 adversarial programs); everything else runs fp32. A per-cook finiteness net makes auto ~perf-NEUTRAL — it's an accuracy-safe convenience, not a speedup. fp16: EXPERT — force half-precision IMAGE temps for the raw ~1.35-1.45x win with NO safety net (coordinates & sampling stay fp32; ~1e-3 accuracy, diverges on threshold/branch/amplifying programs). LATENT stays fp32. |
| debug_nan_highlightopt | BOOLEAN | false | DBG-3: paint any pixel that is NaN or Inf bright magenta so non-finite output is visible at a glance (a 0/0, a log of a negative, an fp16 overflow). Off by default and zero-cost when off. Sits above all tiers, so it works on every execution path. |
| _tex_anyopt | * | TEX accepts any input type. Use @name in code to reference it. | |
| in_0opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_1opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_2opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_3opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_4opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_5opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_6opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_7opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_8opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_9opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_10opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_11opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_12opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_13opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_14opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. | |
| in_15opt | * | Internal lazy input slot — mapped from wired user inputs at queue time. |
Outputs (8)
| Name | Type | Description |
|---|---|---|
| out_0 | * | TEX output. Type auto-inferred from code. |
| out_1 | * | TEX output. Type auto-inferred from code. |
| out_2 | * | TEX output. Type auto-inferred from code. |
| out_3 | * | TEX output. Type auto-inferred from code. |
| out_4 | * | TEX output. Type auto-inferred from code. |
| out_5 | * | TEX output. Type auto-inferred from code. |
| out_6 | * | TEX output. Type auto-inferred from code. |
| out_7 | * | TEX output. Type auto-inferred from code. |