Post 144

The Node That Knew It Was Broken

A shader that will not compile, a glTF file that is not there, a pipeline the driver refuses: all of it used to happen in a terminal you were not reading, while the patch just went quiet. Now the node that failed turns red and tells you why.

There is a particular flavour of despair that comes from a patch which renders nothing and complains about nothing. You wired it up correctly. The node is there. The output is black. Somewhere, in a terminal you closed an hour ago, a line of text is patiently explaining that your WGSL is missing a semicolon on line 3.

That line now arrives where you are actually looking: on the node.

A node whose shader fails to compile gets the same red border and status dot that a crashed node has always had, and the compiler’s own diagnostic turns up in the inspector when you select it. Not “something went wrong.” The actual message, with the actual line, from the actual shader compiler.

The same goes for a GltfMesh pointed at a file that is not there. This one was quietly the worst of the lot, because it did not merely fail, it failed politely: it handed back an empty mesh and let the rest of the patch render an immaculate picture of nothing. It now lights up and names the path it could not open, which is usually the entire diagnosis. The character is not missing. The character is at /assets/hero.glb and you are in a different directory.

Why it took this long

The render engine did not know what a node was.

By the time your shader reaches the GPU it has become a texture op in a flat list, and that op is labelled with a texture handle and nothing else. The engine could see, very clearly, that a shader had failed. It simply had no way to say whose. The handles cannot be worked backwards either, because they come off one shared counter, so the “obvious” fix of reverse-mapping a handle to a node produces confident, wrong answers.

So the evaluator now writes down who emitted each op as it emits them, which is the one moment in the whole frame when both facts are in the same place at the same time. The engine carries that along the op stream and hands failures back with a name attached.

The one that took the session with it

While testing this, the app kept dying instead of reporting.

A shader gets three separate chances to be rejected, and they are not the same gate. It can fail to compose, which is the one everybody expects. It can compose and then be refused by the device, which is what happens when you ask for a texture binding past the limit your GPU actually has. Or it can compose, satisfy the device, and fall over only when the pipeline is assembled, because the pipeline is the first thing in the chain that goes looking for the entry point you promised it.

That last one is the typo. Misspell fs_main and every gate before the pipeline waves you through, because nothing is malformed. Nothing is wrong with the shader except that it is not the shader you said it was. That refusal used to arrive as an uncaptured error, which is a polite name for taking the process down and your unsaved patch with it.

All three gates now sit inside an error scope, so the driver’s “no” comes back as a message rather than an exit. A bad shader costs you the node. It does not cost you the session. This seems like the sort of thing that should have been true all along, and now it is.

A node that goes red also goes back to normal: fix the shader, and the border clears on the next frame. It does not sulk.

Staying broken cheaply

A red node stays red for as long as it is broken, which sounds obvious until you remember that a shader node re-emits its work every frame. Left to its own devices it would rebuild the rejected pipeline from scratch on every one of them, forever, on the same thread that is meant to be drawing your patch. Your typo would not merely fail. It would charge you frame rate for the privilege.

So a refused shader is compiled once and the driver’s answer is kept. The node stays red, the message stays put, and the frame budget stays yours. Fix the typo and the source really has changed, so it gets a genuine second chance on the very next frame.

All of which goes for compute shaders too, including the ones plugins dispatch on their own. A shader is a shader, and every one of them now fails the same way: locally, legibly, and without taking the rest of your evening with it.

The red dot itself is older than this post: it arrived back in Tooltips, Error Dots, and the F8 Profiler, where it could only ever mean “this node panicked.” It now covers everything that can go wrong on a node, and it has finally learned to say what.

← All posts