Sculpt: Start data-oriented refactor for draw brush #121835

Merged
Hans Goudey merged 98 commits from Sergey/blender:sculpt_brush_refactor into main 2024-06-05 14:09:28 +02:00
Member

This PR establishes the beginning of the transition to data-oriented code
for sculpt brushes described in #118145. The final brush "API" design is
still in progress, and further iteration will be required as more brushes are
refactored and other areas can be cleaned up.

Currently the main goal is making the code paths more obvious and easing
future development, but this change itself may also give a performance
improvement. In a simple test with a large mesh, that was about 14%.

This PR establishes the beginning of the transition to data-oriented code for sculpt brushes described in #118145. The final brush "API" design is still in progress, and further iteration will be required as more brushes are refactored and other areas can be cleaned up. Currently the main goal is making the code paths more obvious and easing future development, but this change itself may also give a performance improvement. In a simple test with a large mesh, that was about 14%.
Hans Goudey added 68 commits 2024-05-15 18:07:17 +02:00
Change overview:
- Move implementation into own file
- Split implementation into faces/grids/bmesh types

Such split allows to have short-living caches and passing
them down the road, but without making those caches a huge
structures.

The faces implementation is refactored further:
- Split the "mega-kernel" into a "wavefront" approach

It localizes calculations, keeps the flow more manageable,
and likely leads to better performance.
- The brush was applied twice for "old" and "new" methods.
- Some non-thread-safe code was invoked from threads:
  * Brush texture evaluation.
  * Access to `test` in the brush distance check.
- Better naming.
- Avoid passing brush and ss where it is not really needed.
While on a local time frame this potentially leaves some performance
in the table, it simplifies the code and allows to avoid mistakes.

Also, in the future the nodes needs to become smaller, and then we'll
want no threading from within a node.
Internally called draw_vector_displacement, which is a subject of
reconsideration.
Maybe we'll come back to this later, especially if nodes are smaller and
copying vertex indices to a local buffer is cheaper
Changing the values doesn't change the span itself (nothing does, in fact!)
All nodes in a range are to be handled.

Likely a leftover from a debugging session.
Avoid storing more redundant state than we need to
This was already a pretty leaky API, and it's arguably better not to
get ahead of ourselves and just do one thing at a time. For example,
the access to the positions shouldn't even from from the PBVH anyway.
The PBVH shouldn't be affected at all.
Add comments, move two functions
Some checks failed
buildbot/vexp-code-patch-darwin-arm64 Build done.
buildbot/vexp-code-patch-darwin-x86_64 Build done.
buildbot/vexp-code-patch-linux-x86_64 Build done.
buildbot/vexp-code-patch-lint Build done.
buildbot/vexp-code-patch-windows-amd64 Build done.
buildbot/vexp-code-patch-coordinator Build done.
14a3efcbd1
Hans Goudey requested review from Raul Fernandez Hernandez 2024-05-15 18:08:30 +02:00
Hans Goudey requested review from Sergey Sharybin 2024-05-15 18:08:30 +02:00
Hans Goudey requested review from Sean Kim 2024-05-15 18:08:31 +02:00
Hans Goudey added this to the Sculpt, Paint & Texture project 2024-05-15 18:08:47 +02:00

@blender-bot build

@blender-bot build
Gangneron reviewed 2024-05-15 22:59:24 +02:00
@ -0,0 +1,207 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
First-time contributor

Replace 2023 by 2024

Replace `2023` by `2024`
HooglyBoogly marked this conversation as resolved
Gangneron reviewed 2024-05-15 22:59:46 +02:00
@ -0,0 +1,229 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
First-time contributor

Replace 2023 by 2024

Replace `2023` by `2024`
HooglyBoogly marked this conversation as resolved
Gangneron reviewed 2024-05-15 23:00:04 +02:00
@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
First-time contributor

Replace 2023 by 2024

Replace `2023` by `2024`
HooglyBoogly marked this conversation as resolved
Sean Kim requested changes 2024-05-16 01:10:41 +02:00
Sean Kim left a comment
Member

Just a first pass on the code so far for high level / stylistic stuff, haven't gone super in depth into either of the brush algorithms to compare to the existing code.

As a side note, once this is ready, should this wait until we have the v4.2 branch?

Just a first pass on the code so far for high level / stylistic stuff, haven't gone super in depth into either of the brush algorithms to compare to the existing code. As a side note, once this is ready, should this wait until we have the v4.2 branch?
@ -0,0 +40,4 @@
const PBVHNode &node,
Object &object,
LocalData &tls,
const MutableSpan<float3> positions_sculpt,
Member

r_ prefixes in this file for return args

`r_` prefixes in this file for return args
HooglyBoogly marked this conversation as resolved
@ -0,0 +172,4 @@
const float3 offset = effective_normal * ss.cache->radius * ss.cache->scale * bstrength;
switch (BKE_pbvh_type(*object.sculpt->pbvh)) {
Member

We're going to have this switch statement repeated a fair amount across the brushes as we migrate them, do you think there's a common abstract class we could extract for the different PBVH types and their respective calc_ methods?

We're going to have this switch statement repeated a fair amount across the brushes as we migrate them, do you think there's a common abstract class we could extract for the different PBVH types and their respective `calc_` methods?
Author
Member

Yeah, I think we might end up with something like that. Maybe even with some storage data during a brush stroke. But I would rather keep this simple for now, and only add abstractions a bit later when it's clearer.

Yeah, I think we might end up with something like that. Maybe even with some storage data during a brush stroke. But I would rather keep this simple for now, and only add abstractions a bit later when it's clearer.
Sean-Kim marked this conversation as resolved
@ -0,0 +10,4 @@
struct Object;
struct PBVHNode;
namespace blender::ed::sculpt_paint {
Member

How about we add a new namespace here for brushes?

How about we add a new namespace here for brushes?
Author
Member

I tried this but didn't really like it. It conflicted a bit with more specific namespaces like cloth,pose, and smooth. Such a large portion of the code is brushes that maybe it's not necessary? It also sort of relates to how the sculpt_paint module is currently shared between meshes, curves, and grease pencil, and vertex painting, etc. We could split those two separate modules that all depend on some smaller module that defines the brush system.

I tried this but didn't really like it. It conflicted a bit with more specific namespaces like `cloth`,`pose`, and `smooth`. Such a large portion of the code is brushes that maybe it's not necessary? It also sort of relates to how the `sculpt_paint` module is currently shared between meshes, curves, and grease pencil, and vertex painting, etc. We could split those two separate modules that all depend on some smaller module that defines the brush system.
Sean-Kim marked this conversation as resolved
@ -0,0 +12,4 @@
namespace blender::ed::sculpt_paint {
void do_draw_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
Member

Documentation for both of these methods? Maybe just something about the difference between the regular draw_brush and draw_vector_displacement_brush?

Documentation for both of these methods? Maybe just something about the difference between the regular `draw_brush` and `draw_vector_displacement_brush`?
HooglyBoogly marked this conversation as resolved
@ -0,0 +11,4 @@
#include "DNA_brush_enums.h"
/**
* This file contains common operations useful for the implementation of various different brush
Member

Minor nit, I'm personally not a fan of utils as a suffix, it tends to lead to the file / namespace / class being a dumping ground of loosely related concepts instead of something more cohesive. That being said I don't think I have a good name for this right now - maybe something to do with factor and displacement, since that seems to be the main type of output of these functions?

Minor nit, I'm personally not a fan of `utils` as a suffix, it tends to lead to the file / namespace / class being a dumping ground of loosely related concepts instead of something more cohesive. That being said I don't think I have a good name for this right now - maybe something to do with `factor` and `displacement`, since that seems to be the main type of output of these functions?
Author
Member

Agreed, I didn't really like it either. It's meant to be a place for common methods shared between brushes, so I renamed it to mesh_brush_common.hh, which is a little better anyway. I don't think this organization is set in stone, I expect things to continue moving around as we figure out how to share these methods between brushes. We'll also find common themes too, which may give better ways to organize.

Agreed, I didn't really like it either. It's meant to be a place for common methods shared between brushes, so I renamed it to `mesh_brush_common.hh`, which is a little better anyway. I don't think this organization is set in stone, I expect things to continue moving around as we figure out how to share these methods between brushes. We'll also find common themes too, which may give better ways to organize.
Sean-Kim marked this conversation as resolved
@ -0,0 +58,4 @@
*/
void calc_mesh_hide_and_mask(const Mesh &mesh,
Span<int> vert_indices,
MutableSpan<float> r_factors);
Member

Since factors comes up a fair bit in this file, I think it's worth defining what it is somewhere in the main header.

Since `factors` comes up a fair bit in this file, I think it's worth defining what it is somewhere in the main header.
HooglyBoogly marked this conversation as resolved
@ -0,0 +66,4 @@
void calc_front_face(const float3 &view_normal,
Span<float3> vert_normals,
Span<int> vert_indices,
MutableSpan<float> factors);
Member

factors -> r_factors, this and elsewhere in the other methods.

`factors` -> `r_factors`, this and elsewhere in the other methods.
HooglyBoogly marked this conversation as resolved
@ -0,0 +117,4 @@
* calculate various effects like clipping. After they are processed, this function can be used to
* simply add them to the final vertex positions.
*/
void apply_translations(Span<float3> translations, Span<int> verts, MutableSpan<float3> positions);
Member

positions -> r_positions here and elsewhere in the file

`positions` -> `r_positions` here and elsewhere in the file
HooglyBoogly marked this conversation as resolved
@ -0,0 +128,4 @@
*/
void apply_crazyspace_to_translations(Span<float3x3> deform_imats,
Span<int> verts,
MutableSpan<float3> translations);
Member

translations -> r_translations here and elsewhere in the file

`translations` -> `r_translations` here and elsewhere in the file
HooglyBoogly marked this conversation as resolved
@ -6282,0 +6310,4 @@
if (const VArray mask = *attributes.lookup<float>(".sculpt_mask", bke::AttrDomain::Point)) {
const VArraySpan span(mask);
for (const int i : verts.index_range()) {
r_factors[i] = 1.0f - mask[verts[i]];
Member

Should this be span[verts[i]] instead? I don't see what's the point of line 6311 otherwise.

Should this be `span[verts[i]]` instead? I don't see what's the point of line 6311 otherwise.
HooglyBoogly marked this conversation as resolved
@ -6282,0 +6325,4 @@
}
}
void calc_front_face(const float3 &view_normal,
Member

Apply the same BLI_assert as above in this function?

Apply the same `BLI_assert as above` in this function?
Author
Member

I tried removing the asserts. They're redundant since each span access has an assert too. And they're a bit verbose.

I tried removing the asserts. They're redundant since each span access has an assert too. And they're a bit verbose.
Member

Just my 2 cents - I think them being redundant is fine, to me the asserts at this level provide two benefits:

  1. Gives more contextual information on this line with the sizes being unequal than later when it's just a span index out of range in cases where we're debugging an issue
  2. Documents the preconditions that we expect to be true for these methods

Of the two, I think the latter is more valuable as with the different sized spans that we're dealing with in these different methods, having it laid out makes reading this code in the future more clear

Just my 2 cents - I think them being redundant is fine, to me the asserts at this level provide two benefits: 1. Gives more contextual information on this line with the sizes being unequal than later when it's just a span index out of range in cases where we're debugging an issue 2. Documents the preconditions that we expect to be true for these methods Of the two, I think the latter is more valuable as with the different sized spans that we're dealing with in these different methods, having it laid out makes reading this code in the future more clear
HooglyBoogly marked this conversation as resolved
@ -6282,0 +6418,4 @@
void apply_translations(const Span<float3> translations,
const Span<int> verts,
const MutableSpan<float3> positions)
{
Member

BLI_assert(translations.size() == verts.size()) here?

`BLI_assert(translations.size() == verts.size())` here?
HooglyBoogly marked this conversation as resolved
@ -6282,0 +6429,4 @@
const Span<int> verts,
const MutableSpan<float3> translations)
{
for (const int i : verts.index_range()) {
Member

Same assert as above

Same assert as above
HooglyBoogly marked this conversation as resolved
@ -6282,0 +6440,4 @@
const Span<int> verts,
const MutableSpan<float3> translations)
{
const StrokeCache *cache = ss.cache;
Member

Same assert as above

Same assert as above
HooglyBoogly marked this conversation as resolved
@ -1125,6 +1125,13 @@ float SCULPT_brush_strength_factor(
int thread_id,
const blender::ed::sculpt_paint::auto_mask::NodeData *automask_data);
void sculpt_apply_texture(const SculptSession *ss,
Member

Documentation

Documentation
HooglyBoogly marked this conversation as resolved
@ -1127,1 +1127,4 @@
void sculpt_apply_texture(const SculptSession *ss,
const Brush *brush,
const float brush_point[3],
Member

Is it worth changing this to float3 and float r_rgba[4] to float4 in this PR?

Is it worth changing this to `float3` and `float r_rgba[4]` to `float4` in this PR?
Author
Member

I don't think so, that's a separate cleanup. I'm just exposing this function but this really shouldn't be the final state of things.

I don't think so, that's a separate cleanup. I'm just exposing this function but this really shouldn't be the final state of things.
HooglyBoogly marked this conversation as resolved
Hans Goudey changed title from Sculpt: Initial data-oriented brush refactor for draw brush to Sculpt: Initial data-oriented refactor for draw brush 2024-05-16 15:00:17 +02:00
Hans Goudey added 3 commits 2024-05-16 15:08:56 +02:00
Author
Member

Thanks for the review!

For the r_ prefixes, I don't actually think they apply here. I've always viewed those as a way to express that the values were initialized or otherwise "completely filled from scratch" by the function. For these functions, the existing values matter since we multiply with them for the final factors (factors[i] *= new_factor;). So I don't really see it as a "return argument" but a reference to mutable data instead. I guess the difference is subtle. But I also think our much stronger const correctness makes the prefix less necessary too.

Thanks for the review! For the `r_` prefixes, I don't actually think they apply here. I've always viewed those as a way to express that the values were initialized or otherwise "completely filled from scratch" by the function. For these functions, the existing values matter since we multiply with them for the final factors (`factors[i] *= new_factor;`). So I don't really see it as a "return argument" but a reference to mutable data instead. I guess the difference is subtle. But I also think our _much_ stronger const correctness makes the prefix less necessary too.
Hans Goudey added 6 commits 2024-05-16 15:26:22 +02:00
Hans Goudey changed title from Sculpt: Initial data-oriented refactor for draw brush to Sculpt: Start data-oriented refactor for draw brush 2024-05-16 15:32:53 +02:00
Member

Thanks for the review!

For the r_ prefixes, I don't actually think they apply here. I've always viewed those as a way to express that the values were initialized or otherwise "completely filled from scratch" by the function. For these functions, the existing values matter since we multiply with them for the final factors (factors[i] *= new_factor;). So I don't really see it as a "return argument" but a reference to mutable data instead. I guess the difference is subtle. But I also think our much stronger const correctness makes the prefix less necessary too.

I get your intent now, I think the thing that tripped me up on first reading was that it just looked like a missed formatting on the first instance in the calc_mesh_hide_and_mask call. I do agree that the const correctness makes the naming less necessary.

> Thanks for the review! > > For the `r_` prefixes, I don't actually think they apply here. I've always viewed those as a way to express that the values were initialized or otherwise "completely filled from scratch" by the function. For these functions, the existing values matter since we multiply with them for the final factors (`factors[i] *= new_factor;`). So I don't really see it as a "return argument" but a reference to mutable data instead. I guess the difference is subtle. But I also think our _much_ stronger const correctness makes the prefix less necessary too. I get your intent now, I think the thing that tripped me up on first reading was that it just looked like a missed formatting on the first instance in the `calc_mesh_hide_and_mask` call. I do agree that the const correctness makes the naming less necessary.
Sean Kim reviewed 2024-05-16 21:18:06 +02:00
Sean Kim left a comment
Member

Some further thoughts after the last round of review.

Some further thoughts after the last round of review.
@ -6282,0 +6308,4 @@
if (const VArray mask = *attributes.lookup<float>(".sculpt_mask", bke::AttrDomain::Point)) {
const VArraySpan span(mask);
for (const int i : verts.index_range()) {
r_factors[i] = 1.0f - span[verts[i]];
Member

Is adding a clamp to [0.0, 1.0] here worthwhile?

Is adding a clamp to [0.0, 1.0] here worthwhile?
Author
Member

I don't really think so, I think it's fine to rely on the mask values being between 0 and 1, optimizing for that common case. Worst case there is some strange deformation.

I don't really think so, I think it's fine to rely on the mask values being between 0 and 1, optimizing for that common case. Worst case there is some strange deformation.
Sean-Kim marked this conversation as resolved
@ -6282,0 +6312,4 @@
}
}
else {
r_factors.fill(1.0f);
Member

Something that I thought of based on your comment about the initialize vs modify comment on these spans - what do you think about moving the initialization of the r_factors Vector here and the r_distances Vector later to somewhere outside of these functions?

My goal is to remove some of the implicit ordering that we have for the methods, since in theory aside from the mask step, any of the multiplicative ones could go in whatever order (outside of performance reasons to do more broad strokes first)

Something that I thought of based on your comment about the initialize vs modify comment on these spans - what do you think about moving the initialization of the `r_factors` `Vector` here and the `r_distances` `Vector` later to somewhere outside of these functions? My goal is to remove some of the implicit ordering that we have for the methods, since in theory aside from the mask step, any of the multiplicative ones could go in whatever order (outside of performance reasons to do more broad strokes first)
Author
Member

I think you have a good point. But I wanted to avoid the overhead of initializing the values only to set them later. The "best" way to do that would be to track whether the factors have been set yet for each function and have an assignment and multiplication code path for every new loop. But that doesn't seem worth it, not yet at least. I thought a reasonable compromise was having one common function that goes first. Maybe once we have a better way to do performance testing we can change this.

For now I renamed calc_mesh_hide_and_mask to fill_factor_from_hide_and_mask, hopefully that clarifies things.

Also, I think eventually this order won't exist in too many places, I'm guessing it will just be called from intermediate function.

I think you have a good point. But I wanted to avoid the overhead of initializing the values only to set them later. The "best" way to do that would be to track whether the factors have been set yet for each function and have an assignment and multiplication code path for every new loop. But that doesn't seem worth it, not yet at least. I thought a reasonable compromise was having one common function that goes first. Maybe once we have a better way to do performance testing we can change this. For now I renamed `calc_mesh_hide_and_mask` to `fill_factor_from_hide_and_mask`, hopefully that clarifies things. Also, I think eventually this order won't exist in too many places, I'm guessing it will just be called from intermediate function.
Sean-Kim marked this conversation as resolved
@ -6282,0 +6368,4 @@
const StrokeCache &cache = *ss.cache;
for (const int i : verts.index_range()) {
if (factors[i] == 0.0f) {
Member

Minor nit - the comment below makes me think that we should compare distances[i]to FLT_MAX instead

Minor nit - the comment below makes me think that we should compare `distances[i]`to `FLT_MAX` instead
HooglyBoogly marked this conversation as resolved
Hans Goudey added 5 commits 2024-05-20 04:23:15 +02:00
Hans Goudey requested review from Sean Kim 2024-05-20 04:27:01 +02:00
Hans Goudey added 2 commits 2024-05-20 04:27:04 +02:00
Hans Goudey added 1 commit 2024-05-21 21:47:35 +02:00
Hans Goudey added 1 commit 2024-05-21 21:55:59 +02:00
Hans Goudey added 1 commit 2024-05-23 05:52:45 +02:00
We should do this more in the future
Raul Fernandez Hernandez reviewed 2024-05-23 17:58:29 +02:00
@ -0,0 +16,4 @@
void do_draw_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
/** A simple normal-direction displacement based on image texture RGB/XYZ values. */
void do_draw_vector_displacement_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);

In the future, new/ported brushes should be added here?

In the future, new/ported brushes should be added here?

Yep, we've opted out from having per-brush header.

Yep, we've opted out from having per-brush header.
farsthary marked this conversation as resolved
Raul Fernandez Hernandez reviewed 2024-05-23 18:11:41 +02:00
@ -0,0 +86,4 @@
/**
* Modify the factors based on distances to the brush cursor, using various brush settings.
*/
void calc_brush_strength_factors(const SculptSession &ss,

If both calc_distance_falloff() and calc_brush_strength_factors() modify influence factors based on the distance from the brush cursor and various other settings, what's the main functional difference?

If both calc_distance_falloff() and calc_brush_strength_factors() modify influence factors based on the distance from the brush cursor and various other settings, what's the main functional difference?

It is indeed a bit tricky difference. The calc_distance_falloff only depends on the brush position. The calc_brush_strength_factors does more things like hardness, curve etc.

Splitting those into two functions basically:

  • Allows each of them to be simpler, likely benefiting from cache coherencies and such much better in comparison with if they were more of a mega-kernel.
  • Allows to have an early outputs in the latter function, without affecting branching and such in the comparison to the mega-kernel.
It is indeed a bit tricky difference. The `calc_distance_falloff ` only depends on the brush position. The `calc_brush_strength_factors` does more things like hardness, curve etc. Splitting those into two functions basically: - Allows each of them to be simpler, likely benefiting from cache coherencies and such much better in comparison with if they were more of a mega-kernel. - Allows to have an early outputs in the latter function, without affecting branching and such in the comparison to the mega-kernel.
farsthary marked this conversation as resolved
Raul Fernandez Hernandez approved these changes 2024-05-23 18:25:55 +02:00
Hans Goudey added 1 commit 2024-05-24 04:33:54 +02:00
Hans Goudey added 1 commit 2024-05-27 15:58:06 +02:00
Hans Goudey added 1 commit 2024-05-28 23:51:12 +02:00
Hans Goudey added 2 commits 2024-06-03 13:28:09 +02:00
Hans Goudey added 1 commit 2024-06-03 14:45:23 +02:00
Hans Goudey added 1 commit 2024-06-04 17:57:46 +02:00
Hans Goudey added 2 commits 2024-06-04 23:52:55 +02:00
Merge branch 'main' into sculpt_brush_refactor
All checks were successful
buildbot/vexp-code-patch-lint Build done.
buildbot/vexp-code-patch-linux-x86_64 Build done.
buildbot/vexp-code-patch-darwin-x86_64 Build done.
buildbot/vexp-code-patch-windows-amd64 Build done.
buildbot/vexp-code-patch-darwin-arm64 Build done.
buildbot/vexp-code-patch-coordinator Build done.
ea01cdd3b9
Author
Member

@blender-bot build

@blender-bot build
Sergey Sharybin approved these changes 2024-06-05 13:57:31 +02:00
@ -6270,0 +6512,4 @@
/* Modifying of basis key should update mesh. */
if (active_key == mesh.key->refkey) {
/* XXX: There are too many positions arrays getting passed around. We should have a better

XXX is typically used to indicate something that needs to be solved ASAP, possibly even prior to anding a patch. Is it the case here? Or is it more of a regular TODO?

`XXX` is typically used to indicate something that needs to be solved ASAP, possibly even prior to anding a patch. Is it the case here? Or is it more of a regular `TODO`?
Author
Member

There are currently 318 cases of XXX: in Blender's code, haha, but it's good to know what it means anyway. This is a todo that will get easier to resolve as we refactor the remainder of the brushes.

There are currently 318 cases of `XXX:` in Blender's code, haha, but it's good to know what it means anyway. This is a todo that will get easier to resolve as we refactor the remainder of the brushes.
Hans Goudey added 2 commits 2024-06-05 14:00:24 +02:00
Hans Goudey merged commit 782f3411d0 into main 2024-06-05 14:09:28 +02:00
Hans Goudey deleted branch sculpt_brush_refactor 2024-06-05 14:09:33 +02:00
Sign in to join this conversation.
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset System
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Code Documentation
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
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
Viewport & EEVEE
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Asset Browser Project
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
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
Module
Viewport & EEVEE
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Severity
High
Severity
Low
Severity
Normal
Severity
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
5 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#121835
No description provided.