Brush Engine Refactor #91473

Open
opened 2021-09-17 07:18:04 +02:00 by Joseph Eagar · 31 comments
Member

Sculpt mode is in dire need of a new brush system. A great deal of user confusion has been caused by the sheer mess that is the sculpt brush code. Bad API design has create strange workflow limitations and subpar user interfaces.

Our goal is to produce brush editor where users can compose different types of brushes together. Since this is a very ambitious goal it will be done in a series of stages. The immediate priority is to have a system that can be extended in the future without breaking backwards compatibility. Users will be using this system to create thousands of new brushes. We've hidden an awful lot of brushes from the users with bad UX, so if we must break backwards compatibility we should do it now--not after we've fixed the UX and users have gone on a brush creating spree.

Current Status

The system has been implemented in sculpt-dev through Stage 2. The API was initially implemented in C, but is currently in the process of being moved to C++.

Current core data structures:

BKE_brush_engine.hh

Stage 1: Brush Channels & Command Lists

Most Sculpt brushes execute up to four separate commands:

  1. The brush itself.
  2. Autosmooth.
  3. Topology rake.
  4. DynTopo.

Each of these require their own strength, radius and spacing parameters.

image.png

Yeesh.

Command Lists

We can simply the code and the user experience by explicitly building brushes out of
multiple logical operations, a command list. A brush command list is a set
of instructions for a very simple command processor. In the future these lists
will be generated from node graphs, but for now they'll be hardcoded.

The goal is to have a system that can survive the introduction of a composable
brush editor (either a node editor or a modifier-like stack) without breaking everyone's files.

Note that command lists will not be user-definable at this stage.

Brush Channels

A brush channel is a float, int, enum, bitmask, etc. It can be driven by input mappings (e.g. pen pressure) with
custom input curves.

Importantly, brush channels can be strung together in inheritance chains. Each channel can be flagged
to inherit from whatever it's logical "parent" is; in the node graph that would be group parents, but for
now it's brushes and the scene tool settings.

For example, each command in a command list has it's own list of brush channels. Each of these can be flagged to inherit from the "parent", which is a list of channels stored in Brush. These in turn can be flagged to inherit from a list of channels stored in the Scene toolsettings (in Sculpt).

This will serve as a replacement for the UnifiedPaintSettings and DynTopoSettings structures.

Value Semantics

Brush channels are meant to be copied by value. This avoids violating the architectural design rule in Blender that, in general, data owned by one datablock should not store pointers to another. It also simplifies the code. Instead of each brush channel set storing a pointer to it's original owner (so it can resolve inheritance), inheritance is applied in stages:

  1. Scene and Brush channels are combined into ss->cache->channels_final.
  2. ss->cache->channels_final and BrushCommand->params are combined into BrushCommand->params_final.
  3. Finally, BrushCommand->params_final is copied to BrushCommand->params_mapped and input mapping is applied (e.g. pressure).

And yes, there are API functions to lookup the final value of a brush channel given parent and child brush channel sets.

Compile-time Name Checking

Brush settings identifiers are just simple strings. The C API is designed to catch typos at compiler time. It does this by generating a bunch of global string variables like so:

const char *BRUSH_builtin_strength = "strength";
const char *BRUSH_builtin_radius = "radius";

Important API functions are then wrapped in a series of BRUSHSET_XXX macros. So to
lookup a channel you might do:

BrushChannel *ch = BRUSHSET_LOOKUP(brush->channels, radius);

The C++ API works a bit differently; BrushChannelSetIF has one accessor method per brush channel.
I tried to get compile-time string validation of channel names to work but failed (at least in C++17),
so I ended up using macros to create accessor methods instead.

Note the sculpt code has it own API. This is due to the StrokeCache structure (ss->cache), which is allowed to NULL. The Sculpt channel API thus requires backup pointers (to Brush and Sculpt structures) as a fallback to derive channel values.

Example:

SCULPT_get_float(ss, radius, [pointer to Sculpt], [pointer to Brush])

No ID pointers

Aside from textures (the implementation of which incomplete), no ID pointers are allowed in BrushChannels. This is so we can link in brushes from an asset file as library overrides without weird edge cases or performance degradations on file IO.

This decision may be revisited later.

Use id properties?

In the current implementation brush channels have four value members:

float fvalue;
int ivalue;
float vector[4];
BrushCurve curve;

Floats go in fvalue, vectors in vector, while ints, booleans, enums and bitmasks use ivalue. BrushCurve
is just a struct that stores a Brush curve preset and a pointer to a CurveMapping.

We could replace the first three with a single ID Property; for the second we would need to implement
CurveMapping ID properties, which might be a good idea anyway?

C++

The current API implementation in sculpt-dev is mostly implemented in C, which has led to some code messiness. The process of switching the implementation to C++ has begun; the existing C API will be (mostly) kept, but it will wrap a C++ one.

The C++ API works by wrapping the C DNA structs, so e.g. BrushChannel has a BrushChannelIF C++ wrapper class ("IF" just stances for "Interface"; suggestions for better names are welcome). The C++ API is quite a bit cleaner (and clearer), and uses templates:

BrushChannelIF<float> radius = channelset.radius();
BrushChannelIF<bool> use_frontface = channelset.use_frontface();

//get the channel values:
float radius_value = radius.value();
bool frontface_value = use_frontface.value();

//can also assign
radius.value() = 5.0f;
use_frontface.value() = true;

Commands

Exactly how many commands to implement at this stage in an open question. The node editor will probably require a small VM similar to Cycle's SM nodes (if much simpler).

For now, the commands mirror the SCULPT_TOOL_XXX enumeration. There are several commands that are not available as brush
types:

  • SCULPT_TOOL_DYNTOPO
  • SCULPT_TOOL_TOPOLOGY_RAKE

SCULPT_TOOL_ENHANCE_DETAILS (currently exposed as a brush in sculpt-dev, but will probably be moved back inside the Smooth brush).

Textures

There will be no changes to brush textures at this stage. The Tool command will support textures in much the same way the existing codebase does. Fixing Blender's texture node mess is not the sculpt team's job.

Stage 2: Port Existing Brushes and UI

All current brushes will have to be ported to the new system, this shouldn't be hard. The existing brush UI
will be split into two pieces; 25-33% will remain in the workspace buttons, while the rest will be moved
someplace else (either a dedicated space type or another tab in the properties editor).

The settings themselves will be ported to brush channels, but the underlying functionality will remain
unchanged.

This is mostly finished in sculpt-dev, but needs more work on the UX design level.

Stage 3: Python Node Editor

Option A: Node Editor

Expose the command lists in RNA and start writing a proof of concept node editor in Python. Code to convert command lists to nodes will be written (probably in python to begin with), so we can convert old files.

Design Issues

There are 50+ brush parameters. Having nodes with 50 sockets is not going to work.
Instead we should let users add the brush channels they want to command nodes.

Option B: Command Stack

Node editors aren't always the right thing to do if the underlying system is fundamentally linear.
Brush commands are simple and sequential; they do not have outputs. A node editor would be useful
for configuring input mappings (e.g. using pen pressure to switch between commands), however this
should be possible with a stack metaphor as well.

Stage 4: Presets

At this stage brushes will be converted to command presets. There are a number of ways we can do this, mostly having to do with how we update builtin brushes on installation of new versions of blender:

  • .blend based solutions:
    • Command lists are created dynamically for built-in brushes; if users want to edit them they must mark the brush as user-defined.
    • Command lists are linked in from an asset .blend file.
    • Command lists are linked in as node groups from an asset .blend file (obviously only a possibility if we decide to go with a node editor).
  • UserDef solution:
    • Command lists are stored inside of brush presets that live in UserDef. Users can clone built-in brushes and edit them. These would be saved in UserDef, unless the user explicitly marks the brush as local to that particular .blend file.
Sculpt mode is in dire need of a new brush system. A great deal of user confusion has been caused by the sheer mess that is the sculpt brush code. Bad API design has create strange workflow limitations and subpar user interfaces. Our goal is to produce brush editor where users can compose different types of brushes together. Since this is a very ambitious goal it will be done in a series of stages. The immediate priority is to have a system that can be extended in the future without breaking backwards compatibility. Users will be using this system to create thousands of new brushes. We've hidden an awful lot of brushes from the users with bad UX, so if we must break backwards compatibility we should do it now--not after we've fixed the UX and users have gone on a brush creating spree. ## Current Status The system has been implemented in sculpt-dev through Stage 2. The API was initially implemented in C, but is currently in the process of being moved to C++. ### Current core data structures: - [DNA_sculpt_brush_types.h ](https://git.blender.org/gitweb/gitweb.cgi/blender.git/blob/refs/heads/sculpt-dev:/source/blender/makesdna/DNA_sculpt_brush_types.h) - [BKE_brush_engine.h ](https://git.blender.org/gitweb/gitweb.cgi/blender.git/blob/refs/heads/sculpt-dev:/source/blender/blenkernel/BKE_brush_engine.h) # [BKE_brush_engine.hh ](https://git.blender.org/gitweb/gitweb.cgi/blender.git/blob/refs/heads/sculpt-dev:/source/blender/blenkernel/BKE_brush_engine.hh) # Stage 1: Brush Channels & Command Lists Most Sculpt brushes execute up to four separate commands: 1. The brush itself. 2. Autosmooth. 3. Topology rake. 4. DynTopo. Each of these require their own strength, radius and spacing parameters. ![image.png](https://archive.blender.org/developer/F10440728/image.png) Yeesh. ## Command Lists We can simply the code and the user experience by explicitly building brushes out of multiple logical operations, a *command list.* A brush command list is a set of instructions for a very simple command processor. In the future these lists will be generated from node graphs, but for now they'll be hardcoded. The goal is to have a system that can survive the introduction of a composable brush editor (either a node editor or a modifier-like stack) without breaking everyone's files. Note that command lists will not be user-definable at this stage. ## Brush Channels A brush channel is a float, int, enum, bitmask, etc. It can be driven by input mappings (e.g. pen pressure) with custom input curves. Importantly, brush channels can be strung together in inheritance chains. Each channel can be flagged to inherit from whatever it's logical "parent" is; in the node graph that would be group parents, but for now it's brushes and the scene tool settings. For example, each command in a command list has it's own list of brush channels. Each of these can be flagged to inherit from the "parent", which is a list of channels stored in Brush. These in turn can be flagged to inherit from a list of channels stored in the Scene toolsettings (in Sculpt). This will serve as a replacement for the UnifiedPaintSettings and DynTopoSettings structures. ### Value Semantics Brush channels are meant to be copied by value. This avoids violating the architectural design rule in Blender that, in general, data owned by one datablock should not store pointers to another. It also simplifies the code. Instead of each brush channel set storing a pointer to it's original owner (so it can resolve inheritance), inheritance is applied in stages: 1. Scene and Brush channels are combined into `ss->cache->channels_final`. 2. `ss->cache->channels_final` and `BrushCommand->params` are combined into `BrushCommand->params_final`. 3. Finally, `BrushCommand->params_final` is copied to `BrushCommand->params_mapped` and input mapping is applied (e.g. pressure). And yes, there are API functions to lookup the final value of a brush channel given parent and child brush channel sets. ### Compile-time Name Checking Brush settings identifiers are just simple strings. The C API is designed to catch typos at compiler time. It does this by generating a bunch of global string variables like so: ``` const char *BRUSH_builtin_strength = "strength"; const char *BRUSH_builtin_radius = "radius"; ``` Important API functions are then wrapped in a series of `BRUSHSET_XXX` macros. So to lookup a channel you might do: ``` BrushChannel *ch = BRUSHSET_LOOKUP(brush->channels, radius); ``` The C++ API works a bit differently; `BrushChannelSetIF` has one accessor method per brush channel. I tried to get compile-time string validation of channel names to work but failed (at least in C++17), so I ended up using macros to create accessor methods instead. Note the sculpt code has it own API. This is due to the `StrokeCache` structure (ss->cache), which is allowed to `NULL`. The Sculpt channel API thus requires backup pointers (to `Brush` and `Sculpt` structures) as a fallback to derive channel values. Example: ``` SCULPT_get_float(ss, radius, [pointer to Sculpt], [pointer to Brush]) ``` ### No ID pointers Aside from textures (the implementation of which incomplete), no ID pointers are allowed in BrushChannels. This is so we can link in brushes from an asset file as library overrides without weird edge cases or performance degradations on file IO. This decision may be revisited later. ### Use id properties? In the current implementation brush channels have four value members: ``` float fvalue; int ivalue; float vector[4]; BrushCurve curve; ``` Floats go in `fvalue`, vectors in `vector`, while ints, booleans, enums and bitmasks use `ivalue`. BrushCurve is just a struct that stores a Brush curve preset and a pointer to a CurveMapping. We could replace the first three with a single ID Property; for the second we would need to implement CurveMapping ID properties, which might be a good idea anyway? ### C++ The current API implementation in sculpt-dev is mostly implemented in C, which has led to some code messiness. The process of switching the implementation to C++ has begun; the existing C API will be (mostly) kept, but it will wrap a C++ one. The C++ API works by wrapping the C DNA structs, so e.g. `BrushChannel` has a `BrushChannelIF` C++ wrapper class ("IF" just stances for "Interface"; suggestions for better names are welcome). The C++ API is quite a bit cleaner (and clearer), and uses templates: ``` BrushChannelIF<float> radius = channelset.radius(); BrushChannelIF<bool> use_frontface = channelset.use_frontface(); //get the channel values: float radius_value = radius.value(); bool frontface_value = use_frontface.value(); //can also assign radius.value() = 5.0f; use_frontface.value() = true; ``` ## Commands Exactly how many commands to implement at this stage in an open question. The node editor will probably require a small VM similar to Cycle's SM nodes (if much simpler). For now, the commands mirror the `SCULPT_TOOL_XXX` enumeration. There are several commands that are not available as brush types: - `SCULPT_TOOL_DYNTOPO` - `SCULPT_TOOL_TOPOLOGY_RAKE` # `SCULPT_TOOL_ENHANCE_DETAILS` (currently exposed as a brush in sculpt-dev, but will probably be moved back inside the Smooth brush). ## Textures There will be no changes to brush textures at this stage. The Tool command will support textures in much the same way the existing codebase does. Fixing Blender's texture node mess is not the sculpt team's job. # Stage 2: Port Existing Brushes and UI All current brushes will have to be ported to the new system, this shouldn't be hard. The existing brush UI will be split into two pieces; 25-33% will remain in the workspace buttons, while the rest will be moved someplace else (either a dedicated space type or another tab in the properties editor). The settings themselves will be ported to brush channels, but the underlying functionality will remain unchanged. This is mostly finished in sculpt-dev, but needs more work on the UX design level. # Stage 3: Python Node Editor ## Option A: Node Editor Expose the command lists in RNA and start writing a proof of concept node editor in Python. Code to convert command lists to nodes will be written (probably in python to begin with), so we can convert old files. ### Design Issues There are 50+ brush parameters. Having nodes with 50 sockets is not going to work. Instead we should let users add the brush channels they want to command nodes. ## Option B: Command Stack Node editors aren't always the right thing to do if the underlying system is fundamentally linear. Brush commands are simple and sequential; they do not have outputs. A node editor would be useful for configuring input mappings (e.g. using pen pressure to switch between commands), however this should be possible with a stack metaphor as well. # Stage 4: Presets At this stage brushes will be converted to command presets. There are a number of ways we can do this, mostly having to do with how we update builtin brushes on installation of new versions of blender: - .blend based solutions: - Command lists are created dynamically for built-in brushes; if users want to edit them they must mark the brush as user-defined. - Command lists are linked in from an asset .blend file. - Command lists are linked in as node groups from an asset .blend file (obviously only a possibility if we decide to go with a node editor). - UserDef solution: - Command lists are stored inside of brush presets that live in UserDef. Users can clone built-in brushes and edit them. These would be saved in UserDef, unless the user explicitly marks the brush as local to that particular .blend file.
Joseph Eagar self-assigned this 2021-09-17 07:18:04 +02:00
Author
Member

Added subscriber: @JosephEagar

Added subscriber: @JosephEagar

Added subscriber: @TheRedWaxPolice

Added subscriber: @TheRedWaxPolice

Added subscriber: @TonatiuhdeSanJulian

Added subscriber: @TonatiuhdeSanJulian

Added subscriber: @lowpolysaac

Added subscriber: @lowpolysaac

Added subscriber: @AlexeyAdamitsky

Added subscriber: @AlexeyAdamitsky

Added subscriber: @thinsoldier

Added subscriber: @thinsoldier

Added subscriber: @ErickNyanduKabongo

Added subscriber: @ErickNyanduKabongo

Added subscriber: @tux21b

Added subscriber: @tux21b

Added subscriber: @GeorgiaPacific

Added subscriber: @GeorgiaPacific

Added subscriber: @JulianPerez

Added subscriber: @JulianPerez

Added subscriber: @pawel.palenica

Added subscriber: @pawel.palenica

Added subscriber: @Zunio

Added subscriber: @Zunio

Added subscriber: @ostry

Added subscriber: @ostry

Hi Joe,
After reading all this, I m wondering if you will consider old design and discussion about sculpt mode brushes we had in the past? People has been posting really good UI and user-friendly ways to use sculpt brushes.

Hi Joe, After reading all this, I m wondering if you will consider old design and discussion about sculpt mode brushes we had in the past? People has been posting really good UI and user-friendly ways to use sculpt brushes.
Author
Member

In #91473#1221717, @ErickNyanduKabongo wrote:
Hi Joe,
After reading all this, I m wondering if you will consider old design and discussion about sculpt mode brushes we had in the past? People has been posting really good UI and user-friendly ways to use sculpt brushes.

Feel free to post links or mockup images.

> In #91473#1221717, @ErickNyanduKabongo wrote: > Hi Joe, > After reading all this, I m wondering if you will consider old design and discussion about sculpt mode brushes we had in the past? People has been posting really good UI and user-friendly ways to use sculpt brushes. Feel free to post links or mockup images.

Added subscriber: @Francis_J

Added subscriber: @Francis_J
Member

Added subscriber: @lichtwerk

Added subscriber: @lichtwerk

Added subscriber: @RC12

Added subscriber: @RC12

In #91473#1222085, @JosephEagar wrote:

In #91473#1221717, @ErickNyanduKabongo wrote:
Hi Joe,
After reading all this, I m wondering if you will consider old design and discussion about sculpt mode brushes we had in the past? People has been posting really good UI and user-friendly ways to use sculpt brushes.

Feel free to post links or mockup images.

Here is a good thread, in there you can find many UI suggestions. The thread was started by Pablo, the sculptor. -> https://developer.blender.org/T80384

> In #91473#1222085, @JosephEagar wrote: >> In #91473#1221717, @ErickNyanduKabongo wrote: >> Hi Joe, >> After reading all this, I m wondering if you will consider old design and discussion about sculpt mode brushes we had in the past? People has been posting really good UI and user-friendly ways to use sculpt brushes. > > Feel free to post links or mockup images. Here is a good thread, in there you can find many UI suggestions. The thread was started by Pablo, the sculptor. -> https://developer.blender.org/T80384

Added subscriber: @NelsonNAS

Added subscriber: @NelsonNAS

In #91473#1224267, @ErickNyanduKabongo wrote:

Here is a good thread, in there you can find many UI suggestions. The thread was started by Pablo, the sculptor. -> https://developer.blender.org/T80384

what we really need is this {https://developer.blender.org/D7616}

> In #91473#1224267, @ErickNyanduKabongo wrote: >> Here is a good thread, in there you can find many UI suggestions. The thread was started by Pablo, the sculptor. -> https://developer.blender.org/T80384 what we really need is this {https://developer.blender.org/D7616}

Added subscriber: @Debuk

Added subscriber: @Debuk

Added subscriber: @AndyCuccaro

Added subscriber: @AndyCuccaro

Added subscriber: @wevon-2

Added subscriber: @wevon-2

If you really want to work with brush assets and have a global palette, I think it is convenient that most of them derive from the same parent and their individual attributes are the ones that define their function. The clothes brush already works like this.
Cloth.png
Although it seems to me that it is not the most suitable place, some time ago I made a proposal and mockups in "Brushes Storage" in reference to this.
https://developer.blender.org/T70412
Image:
https://dev-files.blender.org/file/data/ksbbwvuyerwu4im6gg2k/PHID-FILE-e655fe2gkbnf5hqdzhev/Tools.png
In it I show in a basic way the unification of brushes and how to quickly access the assets, both from the upper toolbar and from the attributes panel.
Now that Asset Manager is more developed, maybe it's time to do some rethinking.

There are many proposals on the table, but none official on the table, although there is XD. Couldn't it be made public yet?

If you really want to work with brush assets and have a global palette, I think it is convenient that most of them derive from the same parent and their individual attributes are the ones that define their function. The clothes brush already works like this. ![Cloth.png](https://archive.blender.org/developer/F11000452/Cloth.png) Although it seems to me that it is not the most suitable place, some time ago I made a proposal and mockups in "Brushes Storage" in reference to this. https://developer.blender.org/T70412 Image: https://dev-files.blender.org/file/data/ksbbwvuyerwu4im6gg2k/PHID-FILE-e655fe2gkbnf5hqdzhev/Tools.png In it I show in a basic way the unification of brushes and how to quickly access the assets, both from the upper toolbar and from the attributes panel. Now that Asset Manager is more developed, maybe it's time to do some rethinking. There are many proposals on the table, but none official on the table, although there is XD. Couldn't it be made public yet?

Added subscriber: @ckohl_art

Added subscriber: @ckohl_art

Added subscriber: @shinyuu

Added subscriber: @shinyuu

Added subscriber: @Garek

Added subscriber: @Garek

Added subscriber: @zNight

Added subscriber: @zNight

Added subscriber: @hzuika

Added subscriber: @hzuika

Added subscriber: @Fran11o11D

Added subscriber: @Fran11o11D
Julien Kaspar added this to the Sculpt, Paint & Texture project 2023-02-08 10:20:48 +01:00
Philipp Oeser removed the
Interest
Sculpt, Paint & Texture
label 2023-02-10 09:11:45 +01:00
Sign in to join this conversation.
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No Assignees
26 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#91473
No description provided.