Volumes: refactor volume grid storage #116315

Merged
Jacques Lucke merged 190 commits from JacquesLucke/blender:volume-grid-refactor into main 2023-12-20 15:33:09 +01:00
Member

This is preparation for #115270.

This refactors how volume grids are stored with the following new goals in mind:

  • Get a stand-alone volume grid data structure that can be used by geometry nodes. Previously, the VolumeGrid data structure was tightly coupled with the Volume data block.
  • Support implicit sharing of grids and trees. Previously, it was possible to share data when multiple Volume data blocks loaded grids from the same .vdb files but this was not flexible enough.
  • Get a safe API for lazy-loading and unloading of grids without requiring explicit calls to some "load" function all the time.
  • Get a safe API for caching grids from files that is not coupled to the Volume data block.
  • Get a tiered API for different levels of openvdb involvement:
    • No OpenVDB: Since WITH_OPENVDB is optional, it's helpful to have parts of the API that still work in this case. This makes it possible to write high level code for volumes that does not require #ifdef WITH_OPENVDB checks everywhere. This is in BKE_volume_grid_fwd.hh.
    • Shallow OpenVDB: Code using this API requires WITH_OPENVDB checks. However, care is taken to not include the expensive parts of OpenVDB and to use forward declarations as much as possible. This is in BKE_volume_grid.hh and uses openvdb_fwd.hh.
    • "Full" OpenVDB: This API requires more heavy OpenVDB includes. Fortunately, it turned out to be not necessary for the common API. So this is only used for task specific APIs.

At the core of the new API is the VolumeGridData type. It's a wrapper around an openvdb::Grid and adds some features on top like implicit sharing, lazy-loading and unloading. Then there are GVolumeGrid and VolumeGrid which are containers for a volume grid. Semantically, each VolumeGrid has its own independent grid, but this is cheap due to implicit sharing. At highest level we currently have the Volume data-block which contains a list of VolumeGrid.

flowchart LR
  Volume --> VolumeGrid --> VolumeGridData --> openvdb::Grid

The loading of .vdb files is abstracted away behind the volume file cache API. This API makes it easy to load and reuse entire files and individual grids from disk. It also supports caching simplify levels for grids on disk.

An important new concept are the "tree access tokens". Whenever some code wants to work with an openvdb tree, it has to retrieve an access token from the corresponding VolumeGridData. This access token has to be kept alive for as long as the code works with the grid data. The same token is valid for read and write access. The purpose of these access tokens is to make it possible to detect when some code is currently working with the openvdb tree. This allows freeing it if it's possible to reload it later on (e.g. from disk). It's possible to free a tree that is referenced by multiple owners, but only no one is actively working with. In some sense, this is similar to the existing ImageUser concept.

The most important files to read are BKE_volume_grid.hh and BKE_volume_grid_file_cache.hh. Most other changes are updates to existing code to use the new API.

This is preparation for #115270. This refactors how volume grids are stored with the following new goals in mind: * Get a **stand-alone volume grid** data structure that can be used by geometry nodes. Previously, the `VolumeGrid` data structure was tightly coupled with the `Volume` data block. * Support **implicit sharing of grids and trees**. Previously, it was possible to share data when multiple `Volume` data blocks loaded grids from the same `.vdb` files but this was not flexible enough. * Get a safe API for **lazy-loading and unloading** of grids without requiring explicit calls to some "load" function all the time. * Get a safe API for **caching grids from files** that is not coupled to the `Volume` data block. * Get a **tiered API** for different levels of `openvdb` involvement: * No `OpenVDB`: Since `WITH_OPENVDB` is optional, it's helpful to have parts of the API that still work in this case. This makes it possible to write high level code for volumes that does not require `#ifdef WITH_OPENVDB` checks everywhere. This is in `BKE_volume_grid_fwd.hh`. * Shallow `OpenVDB`: Code using this API requires `WITH_OPENVDB` checks. However, care is taken to not include the expensive parts of `OpenVDB` and to use forward declarations as much as possible. This is in `BKE_volume_grid.hh` and uses `openvdb_fwd.hh`. * "Full" `OpenVDB`: This API requires more heavy `OpenVDB` includes. Fortunately, it turned out to be not necessary for the common API. So this is only used for task specific APIs. At the core of the new API is the `VolumeGridData` type. It's a wrapper around an `openvdb::Grid` and adds some features on top like implicit sharing, lazy-loading and unloading. Then there are `GVolumeGrid` and `VolumeGrid` which are containers for a volume grid. Semantically, each `VolumeGrid` has its own independent grid, but this is cheap due to implicit sharing. At highest level we currently have the `Volume` data-block which contains a list of `VolumeGrid`. ```mermaid flowchart LR Volume --> VolumeGrid --> VolumeGridData --> openvdb::Grid ``` The loading of `.vdb` files is abstracted away behind the volume file cache API. This API makes it easy to load and reuse entire files and individual grids from disk. It also supports caching simplify levels for grids on disk. An important new concept are the "tree access tokens". Whenever some code wants to work with an openvdb tree, it has to retrieve an access token from the corresponding `VolumeGridData`. This access token has to be kept alive for as long as the code works with the grid data. The same token is valid for read and write access. The purpose of these access tokens is to make it possible to detect when some code is currently working with the openvdb tree. This allows freeing it if it's possible to reload it later on (e.g. from disk). It's possible to free a tree that is referenced by multiple owners, but only no one is actively working with. In some sense, this is similar to the existing `ImageUser` concept. The most important files to read are `BKE_volume_grid.hh` and `BKE_volume_grid_file_cache.hh`. Most other changes are updates to existing code to use the new API.
Jacques Lucke added 187 commits 2023-12-18 23:10:26 +01:00
7c87888c4a Use regular float/vector/etc. sockets to represent grids.
The socket value will be extended from ValueOrField to
"ValueOrFieldOrGrid" (with a nicer name). The data type is the same,
and the socket shape will be updated later when inferencing supports
grid data.
999b08ca3a Move ValueOrField into BKE.
This struct is used for geometry nodes sockets only and only used by
the geometry nodes module (with an indirect dependency via simulation
zone baking). It currently lives in the functions namespace, which is
problematic when adding grids in future, which should not be part of
the functions module.
e9d78c65b0 Added a Grid pointer to the ValueOrField struct.
This will be used for grid sockets to pass grid data at runtime.
The GridType is a forward-declared OpenVDB grid class, with a template
parameter that matches Blender math types and gets swapped with a
matching OpenVDB type for the actual grid.
9f45d22858 Use implicit sharing to create deep copies of grids when needed.
This allows branching from a node socket to multiple inputs.
bca4dffe22 Cleanup: Moved VolumeGrid into BKE_volume_grid.hh.
The BKE_grid_types header becomes BKE_volume_grid. The VolumeGrid struct
will be the main wrapper around openvdb::Grid. GVolumeGrid will be a
generic base class and VolumeGrid<T> the type variants.
021121382b Renamed VolumeGrid to GVolumeGrid as a generic grid wrapper.
VolumeGrid<T> will be a template for typed grid wrappers.
2f35ca5a91 Temp: Move VolumeGrid into bke namespace.
VolumeGrid -> blender::bke::GVolumeGrid

A type alias is added to existing volume code outside of bke working
with the existing type name. Unfortunately this does not work for RNA
where a plain C struct in the global namespace is needed.
cb88e6c113 Added dummy struct in RNA to act as a placeholder for GVolumeGrid.
DummyVolumeGrid is used as a stand-in for the actual VolumeGrid struct.
RNA callback wrappers around `rna_VolumeGrid_load` and
`rna_VolumeGrid_unload` need a C struct as the main "self" argument.
The struct does not have to be an actual DNA struct.
- VolumeGrid is a type alias ("using") that cannot be used directly for
  this purpose.
- blender::bke::GVolumeGrid is the actual struct but C++ namespaces also
  don't work.
This dummy struct is used as a placeholder for the callbacks and
reinterpreted as the actual VolumeGrid type.
0858c6992b Make VolumeGridCommon an ImplicitSharingMixing class.
This currently breaks storage of grids in Volume, because the std::list
will allocate and decallocate grids, which does not respect the user
count of implicit sharing. Have to change the container to use pointers.
b5741095cb Store ImplicitSharingPtr to grids in VolumeGridVector.
Implicit sharing means that grids should not be allocated and freed
by the grid vector, otherwise they will not get deallocated properly.
3b3cc31141 Fix unexpected data destruction by making constructor explicit.
The ImplicitSharingPtr had an implicit constructor for raw pointers.
This had unintended effects when comparing an ImplicitSharingPtr to a
raw pointer: The raw pointer was implicitly converted to the shared
pointer (without change in refcount) and when going out of scope would
decrement user count, eventually freeing the data.

Conversion from raw pointer to shared pointer should not happen
implicitly. The constructor is made explicit now. This requires a little
more boilerplate when constructing a sharing pointer. A special
constructor for the nullptr is added so comparison with nullptr can
still happen without writing out a constructor.
b2c9503996 Rename VolumeGrid type alias to GVolumeGrid.
This is to avoid name conflicts once the FieldValueGrid gets renamed.
d5a98e0079 Another wrapper type around openvdb grid pointers.
The type structure for VolumeGrid needs to change: Casting from a
generic GVolumeGrid to a typed VolumeGrid<T> and vice versa would mean
constructing a new instance, which means deep copies of data because the
VolumeGrid is not just a reference but the ImplicitSharingData itself.

To fix this the structure should be:
1. `SharedGrid<GridType>`: A wrapper around some openvdb::Grid (can be
    GridBase).
2. `VolumeGrid<T>`/`GVolumeGrid`: Stores a `ImplicitSharingPtr` to a
    `SharedGrid`.
3. `VolumeGridVector` goes back to storing the `VolumeGrid` themselves,
    no need for another indirection here.
d123afbd31 Revert "Store ImplicitSharingPtr to grids in VolumeGridVector."
This reverts commit b5741095cb.

VolumeGridVector now stores plain GVolumeGrid instances again instead of
ImplicitSharingPtr. The shared pointers will get moved inside the
VolumeGrid struct, so that they can be casted as lightweight wrappers
without having to do deep copies of the data.
1127ac2e61 Restructured volume grid data types to separate shared data and pointers.
The VolumeGrid class will become the primary ImplicitSharingData, while
the VolumeGridPtr<T> and GVolumeGridPtr are ImplicitSharingPtrs.
Casing only happens on the pointer side, otherwise the casting would
create a deep copy of the data each time.
0336ed9273 Fixed copy-on-write grids in dilate/erode nodes.
If the grid type is correct, `grid_for_write` should always return a
valid grid. Also have to make sure to output the new grid pointer
instead of just copying the existing input.
0bc33f3f88 Fix Store Named Grid node with mutable VolumeGrid.
Volume API now has a "move" function for inserting an existing
VolumeGrid into the Volume. Store Named Grid node now creates a correct
mutable grid to store.
3d5b151366 Increment user count in cast constructor instead of `typed()` method.
The constructor also becomes private since it shouldn't be called
outside of the downcast function.
7af2717b1c Remove potential grid copy in VolumeGridPtr accessors.
The `grid`/`grid_for_write` getters are shortcuts to the internal
VolumeGrid data. They are useful because VolumeGrid is always un-typed,
so getting a typed openvdb::Grid from volume grid requires a second cast
to the type already known by VolumeGridPtr<T>. But the grid accessors
should work the same ways as accessing the openvdb::Grid from VolumeGrid
and leave copy-on-write to higher level methods.

VolumeGridPtr might be better off untyped, with typed grid accessors
inside VolumeGrid. But that is for a later change.
0c788585ca Return grid pointer directly when extracting grid inputs.
The `as_grid` function will construct an empty grid if none is present.
That is not usually what we want when extracting an input, since a
nullptr grid is a valid pointer and can be used as the default input.

For example, using a mask grid can be optional, and the input should be
a nullptr when no mask is used. With the as_grid method the result would
always be a (empty) grid rather than nullptr.
869cf6bb30 Reimplemented the file cache for volume grids.
The file cache stores VolumeGrid implicit sharing data now. It uses
a combination of file path, grid name, and the simplification leve as
the key. That way simplified grids can be handled as cached data without
complicated internal refcounting. Generated grids will not have cached
simplification, but that is expected and in line with other caching
systems in Blender.

The `is_loaded` flag has been replaced with a more explicit enum
`VolumeTreeSource` to make more transparent decisions about what should
happen to grids on load/unload, simplification, copy, etc.
6d7effb088 Unnamed grids are valid, ignore grid names in file cache keys.
This is to fix the blendfile_versioning test which loads a vdb file
with an unnamed grid.
2f2a784fbd Fix cycles test: Replace the grid when unloading instead of new tree.
Cycles "steals" the OpenVDB grid from the VolumeGrid when constructing
the scene and then unloads the grid again. This unloading was replacing
the tree pointer inside the grid, breaking cycles volume rendering.

Do what the old cache did and replace the entire OpenVDB grid in case
some code holds a reference to the pointer. This is not great but it
keeps things working.
buildbot/vexp-code-patch-lint Build done. Details
buildbot/vexp-code-patch-linux-x86_64 Build done. Details
buildbot/vexp-code-patch-darwin-arm64 Build done. Details
buildbot/vexp-code-patch-darwin-x86_64 Build done. Details
buildbot/vexp-code-patch-windows-amd64 Build done. Details
buildbot/vexp-code-patch-coordinator Build done. Details
6c8211baf2
Fix for USD volume bounds calculation, have to load the grid first.
This pattern is not at all ideal but currently common: Any code that
wants to access grid data has to call BKE_volume_grid_load first to
ensure that the potential grid placeholder gets the full tree data.

Like in other places (Cycles, viewport drawing) the code first checks
if the volume grid is a placeholder, and in that case unloads the grid
again after getting the bounds.

Eventually grids should automatically be loaded when accessing the
internal OpenVDB grid data. This would remove the need for
BKE_volume_grid_load, but it does not cover the case of automatic
unloading after use. Some transient struct that unloads a placeholder
grid after going out of scope might handle this.
buildbot/vexp-code-patch-lint Build done. Details
buildbot/vexp-code-patch-linux-x86_64 Build done. Details
buildbot/vexp-code-patch-windows-amd64 Build done. Details
buildbot/vexp-code-patch-darwin-x86_64 Build done. Details
buildbot/vexp-code-patch-darwin-arm64 Build done. Details
buildbot/vexp-code-patch-coordinator Build done. Details
50cc5f5404
cleanup
Author
Member

@blender-bot build

@blender-bot build
Jacques Lucke requested review from Lukas Tönne 2023-12-18 23:52:09 +01:00
Jacques Lucke requested review from Hans Goudey 2023-12-18 23:52:10 +01:00
Jacques Lucke added 1 commit 2023-12-19 00:28:36 +01:00
buildbot/vexp-code-patch-lint Build done. Details
buildbot/vexp-code-patch-linux-x86_64 Build done. Details
buildbot/vexp-code-patch-darwin-x86_64 Build done. Details
buildbot/vexp-code-patch-darwin-arm64 Build done. Details
buildbot/vexp-code-patch-windows-amd64 Build done. Details
buildbot/vexp-code-patch-coordinator Build done. Details
9e43698d73
fix unit test with asserts enabled
Author
Member

@blender-bot build

@blender-bot build
Hans Goudey reviewed 2023-12-19 16:12:02 +01:00
Hans Goudey left a comment
Member

Overall this seems like a nice API, and I really appreciate the documentation in the headers and the PR description.

The part I understand least now is why it's worth having implicit sharing at the VolumeGridData level when it's already implemented at the grid level with tree_sharing_info_. VolumeGridData seems very light in comparison-- not really a problem to copy. But maybe it is if we're passing it around as socket data.

Overall this seems like a nice API, and I really appreciate the documentation in the headers and the PR description. The part I understand least now is why it's worth having implicit sharing at the `VolumeGridData` level when it's already implemented at the grid level with `tree_sharing_info_`. `VolumeGridData` seems very light in comparison-- not really a problem to copy. But maybe it is if we're passing it around as socket data.
@ -0,0 +37,4 @@
*
* Features:
* - Implicit sharing of the #VolumeGridData: This makes it cheap to copy e.g. a #VolumeGrid<T>,
* because it just increases the number of users. On actual copy is only done, when the grid is
Member

On actual copy is only done, when -> An actual copy is only done when

`On actual copy is only done, when` -> `An actual copy is only done when`
JacquesLucke marked this conversation as resolved
@ -0,0 +105,4 @@
* not. If this variable is the only owner of the `shared_ptr`, no one else has access to the
* tree.
*/
std::shared_ptr<AccessToken> tree_access_token_;
Member

I guess a shared pointer is just a simple way of keeping a use count?

I guess a shared pointer is just a simple way of keeping a use count?
Author
Member

Correct. Will extend the comment.

Correct. Will extend the comment.
@ -0,0 +23,4 @@
namespace blender::bke::volume_grid::file_cache {
/**
* Get the volume grid identified by the parameters from a cache. This does load the tree data in
Member

This does load -> This does not load?

`This does load` -> `This does not load`?
JacquesLucke marked this conversation as resolved
@ -0,0 +26,4 @@
* Get the volume grid identified by the parameters from a cache. This does load the tree data in
* grid because that is done on demand when it is accessed.
*/
GVolumeGrid get_grid_from_file(StringRef file_path, StringRef grid_name, int simplify_level = 0);
Member

I guess it might be nice if this function could give an error message too, at least optionally?

EDIT: Looks like the error is stored in the grid data-- that's how it already is in main. Seems a bit odd but not a big deal.

I guess it might be nice if this function could give an error message too, at least optionally? EDIT: Looks like the error is stored in the grid data-- that's how it already is in `main`. Seems a bit odd but not a big deal.
Author
Member

Yeah, one issue is that you can't really reliably know whether there is a load error in this function, because it doesn't actually load the grid.

Yeah, one issue is that you can't really reliably know whether there is a load error in this function, because it doesn't actually load the grid.
@ -0,0 +34,4 @@
class GVolumeGrid;
/**
* Same as #GVolumeGrid but makes sure that the contained grid is of a specific type.
Member

I'd say "means" or something instead of "makes sure" which implies some sort of action (like implicit conversion or something). Even if that happened, this wouldn't be the place to mention it IMO.

I'd say "means" or something instead of "makes sure" which implies some sort of action (like implicit conversion or something). Even if that happened, this wouldn't be the place to mention it IMO.
JacquesLucke marked this conversation as resolved
@ -29,3 +29,3 @@
bool BKE_volume_grid_dense_floats(const Volume *volume,
const VolumeGrid *volume_grid,
const blender::bke::VolumeGridData *volume_grid,
Member

Missing forward declarations here

Missing forward declarations here
JacquesLucke marked this conversation as resolved
Author
Member

The part I understand least now is why it's worth having implicit sharing at the VolumeGridData level when it's already implemented at the grid level with tree_sharing_info_. VolumeGridData seems very light in comparison-- not really a problem to copy. But maybe it is if we're passing it around as socket data.

It's a good point. It's definitely light in comparison to the tree, but it's also "copied" more often. I see little reason not to use implicit sharing here. A more important reason may be that it is more natural to share the grid that's loaded from a file than it is to just share the tree. Also, I don't actually know if it's common to put lots of meta data into a grid or not. There are a fair amount of allocations involved when copying a grid, even if the tree is shared (it also wouldn't fit into SocketValueVariant).

That said, almost everything is light compared to a tree haha

> The part I understand least now is why it's worth having implicit sharing at the `VolumeGridData` level when it's already implemented at the grid level with `tree_sharing_info_.` `VolumeGridData` seems very light in comparison-- not really a problem to copy. But maybe it is if we're passing it around as socket data. It's a good point. It's definitely light in comparison to the tree, but it's also "copied" more often. I see little reason not to use implicit sharing here. A more important reason may be that it is more natural to share the grid that's loaded from a file than it is to just share the tree. Also, I don't actually know if it's common to put lots of meta data into a grid or not. There are a fair amount of allocations involved when copying a grid, even if the tree is shared (it also wouldn't fit into `SocketValueVariant`). That said, almost everything is light compared to a tree haha
Member

Okay, interesting. Sounds a bit like "since we have to allocate it anyway, and it does have some relatively non-trivial data in it, might as well make it implicitly shared". I can get behind that, since it seems like a nice goal for most allocated Blender data in general. OTOH, it would probably simplify understanding the design if it wasn't shared, but that's fine.

Okay, interesting. Sounds a bit like "since we have to allocate it anyway, and it does have some relatively non-trivial data in it, might as well make it implicitly shared". I can get behind that, since it seems like a nice goal for most allocated Blender data in general. OTOH, it would probably simplify understanding the design if it wasn't shared, but that's fine.
Jacques Lucke added 2 commits 2023-12-20 00:02:19 +01:00
buildbot/vexp-code-patch-lint Build done. Details
buildbot/vexp-code-patch-linux-x86_64 Build done. Details
buildbot/vexp-code-patch-windows-amd64 Build done. Details
buildbot/vexp-code-patch-darwin-x86_64 Build done. Details
buildbot/vexp-code-patch-darwin-arm64 Build done. Details
buildbot/vexp-code-patch-coordinator Build done. Details
4946e7c574
various smaller cleanups
Hans Goudey approved these changes 2023-12-20 00:05:43 +01:00
Author
Member

OTOH, it would probably simplify understanding the design if it wasn't shared, but that's fine.

Maybe it would, not 100% sure. I'm especially not sure if this might become a problem with lazy-loading if we don't have this intermediate level.

That aside, I think the "since we have to allocate it anyway, and it does have some relatively non-trivial data in it, might as well make it implicitly shared" argument isn't too bad in itself either. At least as long as the API used to work with the data is safe and generally hides the sharing if possible, which I think is the case here. It's not really something one has to think about when implementing nodes for example: https://projects.blender.org/blender/blender/pulls/115270/files#diff-b343e08adb4ec2030715a70d36bd88ea641779d6

> OTOH, it would probably simplify understanding the design if it wasn't shared, but that's fine. Maybe it would, not 100% sure. I'm especially not sure if this might become a problem with lazy-loading if we don't have this intermediate level. That aside, I think the "since we have to allocate it anyway, and it does have some relatively non-trivial data in it, might as well make it implicitly shared" argument isn't too bad in itself either. At least as long as the API used to work with the data is safe and generally hides the sharing if possible, which I think is the case here. It's not really something one has to think about when implementing nodes for example: https://projects.blender.org/blender/blender/pulls/115270/files#diff-b343e08adb4ec2030715a70d36bd88ea641779d6
Author
Member

@blender-bot build

@blender-bot build
Jacques Lucke merged commit a72e7a220d into main 2023-12-20 15:33:09 +01:00
Jacques Lucke deleted branch volume-grid-refactor 2023-12-20 15:33:11 +01:00
Sign in to join this conversation.
No reviewers
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 project
No Assignees
3 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#116315
No description provided.