Proposal for attribute socket types

Proposal for attribute socket types

DISCLAIMER: This is a proposal for what i personally think is a better approach to handling attributes in geometry nodes. It is NOT an official design document or planned work, just my own contribution to the discussion and a suggestion for improvement.

A new socket type (or multiple types, see (Socket types) is added, which represents an attribute reference. This works much like the current string references, but has some additional features that greatly simplify workflow.

An attribute can be a node output, rather than an input that has to be named. Output attributes are named automatically. To the user the attribute is presented as unnamed, its internal generated name is largely irrelevant and only needs to be chosen such that it is unique and does not collide with user-defined attribute names.

An unnamed attribute is automatically removed from the geometry when all nodes accessing it have been executed. The user does not need to remove such attributes explicitly. If an attribute is supposed to be persistent outside the node tree it must be stored with an “Attribute Store” node, giving it an explicit user-defined name (see Storing an attribute).

Attributes can be loaded from existing geometry layers with an “Attribute Load” node. This simply looks for an existing attribute name on the input geometry and initializes the attribute reference.

Attribute references associate a data type as well as an optional domain type with the attribute name. This should be handled with some flexibility, doing conversions between attribute types as much as possible, just like current string references do. Domain can be set to “automatic” to chose whichever domain type matches best in a given context.

Why is this needed?

Geometry nodes in their current state are powerful, but also very difficult to use beyond simple setups. The way attributes are referenced with explicit names adds a lot of visual noise and mental overhead:

  1. Temporary attributes still require explicit naming. The user has to keep track of which names are used for which attributes, and where name collisions might occur.

  2. Temporary attributes have to be deleted explicitly. This can also cause side effects if an attribute is supposed to be internal and transient, but an attribute with the same name is used inside a group.

  3. Attribute changes are not reflected by node sockets.
    For any other data type a socket represents a particular state of the value. That could be a simple number output from a math node, or a geometry set that is passed between modifiers. Attributes on the other hand are always referred to by the same name and the user has to keep track of what the state is at each point in the node tree.

  4. All attributes become input sockets: The natural flow in nodes from inputs to outputs does not work, because there are no “unnamed” attributes.
    The suggested implementation would generate unique disposable names for such output attributes which only become “real” persistent attributes once stored by the user deliberately (see Storing an attribute).

  5. Operations that work element-wise (math nodes in particular) have to be specialized for accepting singular values.
    This wouldn’t be necessary if the attribute inputs would just accept singular values and automatically treat them as “filled” attributes.

With the proposed changes, attributes would behave much more like conventional values in a node tree. This will reduce the mental overhead when constructing large and complex node trees. Attribute output sockets will keep connections shorter and localized. Broadcasting will remove the need to specify for each input whether it accepts singular values or attributes.

How does this fit in with the Attribute Processor?

The attribute processor node (D11547) will make a lot of math operations simpler and remove the complexity of string attribute references from most low-level computations.

However, modifier-like operations still have to be performed outside of the attribute node trees, e.g.

  • Boolean operations
  • Joining Geometry
  • Subdivision
  • Instancing

Passing attribute references between such higher-level nodes still relies on string references, input-heavy nodes, and explicit singular vs. attribute input switching. The changes proposed in this document could still have significant impact even when most of the low-level math is moved into attribute processor nodes.

Attribute socket types and broadcasting

Multiple typed attribute sockets for float/int/vector/color/etc. values exist on the UI level. Internally these are largely handled the same way. Different UI socket types are a convenience feature to allow default input values and give some indication of what attribute a node expects. A generic attribute socket could be used for nodes such as “Reroute”.

Attributes support broadcasting to simplify nodes that support both singular values and attributes (e.g. Attribute Math). A singular value can be connected to an attribute input, which is equivalent to an “Attribute Fill” node (see examples). Connecting an attribute to a singular value input is NOT allowed. In the future specialized nodes might be available for this purpose, but that is out of scope of this proposal (e.g. Attribute Sum/Average/MinMax).

The advantage of broadcasting is that specifying if an input is a singular value or an attribute is no longer necessary. A singular value can be connected directly to an attribute and will be implicitly broadcasted. If an attribute input is unconnected it will broadcast the default input value.

Future optimization: propagating input modes

The way geometry nodes evaluate does not leave much room for constant-folding right now. With broadcasting it would be possible, in principle, to reduce many nodes (like math nodes or the new attribute processor) to a singular value computation automatically if all inputs are singular values.

Determining if a node can be “constant-folded” depends on whether all inputs are singular values, which in turn depends on their source nodes, etc. (Some nodes may generate full attribute layers regardless of input modes).

Such optimization is outside the scope of this design document, but should be part of future work.

Equivalent Node Setups

The proposed system should not require fundamental changes to the execution of nodes as they are now. Below are listed a number of node setups in the proposed attribute style together with their equivalents in the current string-based system.

Loading an attribute with explicit lookup

Store an attribute to geometry and make it persistent

Broadcasting to an attribute input

Use an unnamed output

19 Likes

To be perfectly honest (perhaps a little rudely so) the attribute processor stuff from day 1 felt kinda like a hack rather than a proper solution. I have already tried out the current prototype and first impressions weren’t great, though it is just a prototype so hopefully it will change for the better in the future, but this feels a lot more like a proper solution.

Thanks for the detailed proposal!

I can see how this solves some problems. However, for me this does not solve enough problems to justify the new concepts and problems it introduces. Most your arguments for why this is is needed are valid and I think most (including me) will agree with that.

The only thing I don’t necessarily agree with is that you should not be able to choose between value and attribute within a node (your 5th argument). Admittedly, the current ui for this is quite clumsy. However, there are ways to solve that. For example, have a look at my Compact Nodes experiment. Having to use an Attribute Load node every time you want to use an attribute in a node seems excessive. Related to that, I think that in the future one should be able to type in expressions into all attribute input sockets.

Tell me when I’m wrong, but this proposal does not make node trees less linear than before, right?
Furthermore, it introduces the new problem that e.g. the Result output of your Attribute Math node has no meaning when it is used on a different geometry, which could be quite confusing. This is especially problematic because there is no clear definition of what “different geometry” means. Does any geometry node output a new geometry? I assume the Attribute Math node outputs the same geometry, but what about the Point Distribute or the Join Geometry node? What happens when we add a “white list” mode to the Attribute Remove node, does it remove all the temporary attributes as well?


Overall, I think this is a bit of a mix between what we have right now and a “Fields” proposal I made some months ago (although yours is arguably much better presented). I think it solves the same problems you mention (except for the one I don’t agree with) and has the following benefits on top of that:

  • Normal Math and similar nodes can be used, no need for special Attribute Math nodes.
  • It allows breaking up long node chains that process attributes.
  • The constant folding you mention becomes trivial.
  • It’s easier to share node groups between shader and geometry nodes.

Since I wrote the proposal, I haven’t really worked on it anymore. Mainly because the initial feedback I got was rather negative. The fear is mostly that the concept of a “Field” is too abstract and can be confusing to most users, even though the behavior is well defined. Personally, I still see the great expressiveness and flexibility of the “Fields” proposal and do prefer it over your proposal here which does raise similar concerns.

If I had the ultimate decision power and people wouldn’t be suffering from the current workflow as much, I’d probably explore this idea a bit further and would probably prioritize it even higher than the Attribute Processor (which would become significantly less important if we had these “fields”).

However, I do think that the Attribute Processor is a good compromise that solves the most important usability problems we have right now in a good way. I’d argue that once all/most of the Attribute ... nodes are available in the Attribute Processor (and it becomes fast to use), most of the problems you mention become much less of an issue in practice. The “Fields” proposal would still add more flexibility and expressiveness to the geometry node tree, but there we would to discuss whether this is worth the added mental complexity.

7 Likes

Thanks for the thorough reply.

It’s disappointing that the current state of GN is apparently considered “good enough”. I’m going to try and reserve judgement since it’s still early days, but i don’t consider them user friendly as-is.

The only thing I don’t necessarily agree with is that you should not be able to choose between value and attribute within a node

What would be the argument for such explicit mode selection if it was entirely possible to decide automatically? I can see how it might be useful for formal parameters in node groups, where you want to specify that an input has to be a singular value, but on regular built-in nodes i don’t see any added value to forcing users to decide.

If the proposal would be rejected on this point alone i wouldn’t mind removing it and keeping explicit selection, but i just don’t see why.

Having to use an Attribute Load node every time you want to use an attribute in a node seems excessive

Yes, i can (partly) agree to that. The reason i introduced Load/Store nodes was that, when sockets become typed and have float/vector/int/etc. default values, the string field has to give way. My impression working with GN so far has been that accessing built-in attributes (e.g. position) is much less common than using temporary attributes, so most sockets wouldn’t need such a name field. You are right in that future expression support would probably make “Load” nodes redundant.

As a side note: The names “Load” and “Store” are not ideal because they suggest actual loading of data. Perhaps more fitting names would be “Lookup Attribute” and “Name Attribute”.

this proposal does not make node trees less linear than before, right?

By “linear” do you mean the width (number of nodes) of a typical expression? No, the proposal would not significantly change that. What it does change is the locality of connections because attributes are passed from one node to the next, rather than having a single string value on the left side that gets connected all over the node tree.

it introduces the new problem that e.g. the Result output of your Attribute Math node has no meaning when it is used on a different geometry

That is no different when using name strings! Missing attributes happens all the time when I use the string sockets too. I don’t think there is a good way to avoid the issue of mismatching geometry + attribute combinations, other than having good error reporting when it happens. Attribute sockets might even be able to detect some of those errors by checking upstream geometry sockets, although that’s more of a heuristic than reliable error check.

I cannot stress enough that attribute sockets are in fact attribute references implemented with strings. They are NOT full data arrays that get copied by value or exist outside of a geometry set. The only difference to current string sockets is that intermediate attribute names would be generated uniquely without user input.

My main goal and, i believe, a major “selling point” of this proposal is that it can work on top of the current execution system. I could write a python node system that implements attribute sockets and translates 1:1 to the current string sockets, which is just to show that the changes here stay largely on the UI level. Just to reiterate, the additions needed are:

  1. A function to auto-generate unique attribute names for “unnamed” outputs (compiler stage)
  2. A usage counter that removes attributes the last target node (compiler or runtime)
  3. Propagation of singular vs. attribute result mode (if this feature is accepted, the other changes can work without it)

Implementing this would not be all that difficult, it remains mostly a design decision. I’m hoping we can find some common ground here.

Cheers!

4 Likes

The reason i introduced Load/Store nodes was that, when sockets become typed and have float/vector/int/etc. default values, the string field has to give way.

We could also show a string/expression input within an int/float/… socket to solve that.

That is no different when using name strings!

I know! The difference is mainly that the Result output makes it look like it could be used in places where it can’t. I know, that some kind of reference will be necessary. I do think that using a string input was the right thing to do in the first version to make the core concept of attributes more obvious. This is a concept that won’t really change much over time imo. Now that we gained more experience with how geometry nodes is used in practice, we can have a more educated opinion on what layers we can build on top of attributes to make the system more user friendly. That is what we are actively working on.

My main goal and, i believe, a major “selling point” of this proposal is that it can work on top of the current execution system.

That’s a good point indeed. Note that my “Fields” proposal could be build on top of the current execution system as well.

Implementing this would not be all that difficult, it remains mostly a design decision. I’m hoping we can find some common ground here.

Right. Not sure how much time you have nowadays, but it would be great if you could join our design meetings every now and then. I think your input is very valuable.

6 Likes

There is a working prototype in the geometry-nodes-unnamed-attributes branch, if anyone wants to try this out.

Only a few nodes supported so far:

  • Math
  • Vector Math
  • Sample Texture
  • Remove Attribute
  • Get/Set Attribute (these are special nodes to get/set attributes by name)

More nodes should follow. In the meantime the attribute sockets are compatible with string sockets:

  • Connect an attribute to a string input to use it as an input in a conventional node
  • Use the “Get Attribute” node to use the result of a conventional node

Note that attributes are not yet removed automatically, so you will see a lot of generated names in the spreadsheet.

I’ve documented the process of converting nodes from string sockets to attribute sockets in doc/guides/GN_converting_attribute_sockets.md.

10 Likes

Great! Trees and parallel/tree flow is much more handy!
Also much more familiar for nodelovers and those, who already use shader-nodes