Sculpt: Rewrite BMLog to support customdata and geometry undo steps #113539
No reviewers
Labels
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
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: blender/blender#113539
Loading…
Reference in New Issue
No description provided.
Delete Branch "JosephEagar/blender:temp-dyntopo-bmlog"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This PR rewrites
BMLog
in C++ to support attributes and geometry (full mesh) undo steps.Developer Notes:
BMLogEntry
now stores an internal list of changesets; these can be delta change sets or full meshes.Thanks a lot for for splitting out this change. This does look mostly self contained like you mentioned. I did a first round of review-- I hope everything is clear. I mostly looked at
bmesh_log.cc
for now, not so much ofbmesh_idmap
. I just wanted to highlight two comments that are a bit larger than the others.CD_NORMAL
, which has been moving out of CustomData anyway for the past couple years. BMesh is definitely different, but it would be better to have this conversation outside of this PR, in other words remove the handling ofCD_FLAG_TEMPORARY
here in this PR.BMLogCallbacks
This was another non-obvious part of the design we noticed, and it doesn't seem used here. If possible, let's also consider that as a separate step.@ -1120,3 +1120,3 @@
Mesh *mesh = static_cast<Mesh *>(BKE_id_new_nomain(ID_ME, nullptr));
BM_mesh_bm_to_me(nullptr, bm, mesh, params);
BKE_mesh_copy_parameters_for_eval(mesh, me_settings);
if (me_settings) { /* BMLog calls this with me_settings as null. */
This function is really just two lines long. Better to inline it than to tweak it here just for that use case.
@ -195,7 +195,6 @@
#include "intern/bmesh_edgeloop.h"
#include "intern/bmesh_interp.h"
#include "intern/bmesh_iterators.h"
#include "intern/bmesh_log.h"
Better to revert this change and consider it more holistically as a next step
@ -0,0 +1,409 @@
#include "MEM_guardedalloc.h"
Missing copyright here and GPL license here
@ -0,0 +73,4 @@
void BM_idmap_clear_attributes_mesh(Mesh *me);
/* Elem -> ID. */
template<typename T = BMElem> BLI_INLINE int BM_idmap_get_id(BMIdMap *map, T *elem)
BLI_INLINE
->inline
@ -27,0 +46,4 @@
using blender::Vector;
/* Avoid C++ runtime type ids. */
enum BMLogSetType { LOG_SET_DIFF, LOG_SET_FULL };
The following disallows implicit conversions and looks a bit nicer later.
@ -27,0 +61,4 @@
return false;
}
CustomData test_a = *data_a;
Let's avoid making local copies here and just compare the members we care about explicitly, without
memcpy
. That seems less hacky to me, and clearer to the reader. If things change in BMesh customdata, this code will probably have to change anyway.@ -27,0 +114,4 @@
CustomData_update_typemap(dest);
}
Since it's a swap, there isn't really a "source", I'd suggest calling these
data_a
,data_b
,block_a
, andblock_b
.@ -27,0 +130,4 @@
CustomData_bmesh_set_default(source, dest_block);
}
void *scratch = alloca(dest->totsize);
In C++ we shouldn't have to use
alloca
at all-- we have vectors with inline buffers, but even better in this case,DynamicStackBuffer
, which should be able to replace this pretty simply.That's a nice little class.
@ -27,0 +159,4 @@
};
template<typename T> struct LogElemAlloc {
BLI_mempool *pool;
I'm not convinced it makes sense to have a type-safe wrapper around
BLI_mempool
that's local to this file. I would advocate for just usingBLI_mempool
directly here and saving further C++ conversion for later. Especially avoiding the iterator implementation here would be nice, those are a bit non-trivial.That said, I understand if you think this is essential to the rest of the patch, and it could also be generalized later if we had too.
The problem is that
BMLogFace
is a non-trivial type so it's constructor has to be called via in-place new. Maybe I should make a PR for this though.edit: Of course BMLogFace doesn't need a Vector, so this doesn't apply. I'll just get rid of the wrapper entirely.
@ -27,0 +164,4 @@
class iterator {
LogElemAlloc<T> *alloc;
BLI_mempool_iter iter;
void *elem = nullptr, *first;
Declare variables on separate lines, it's just clearer that way
@ -27,0 +280,4 @@
};
struct BMLogFace : public BMLogElem<BMFace> {
Vector<int, 3> verts;
Vector
has a fair amount of overhead in situations like this. Since the size is known at compile time and the code doesn't use more than that size,std::array
would be a better choice.I'd forgotten that master's PBVH_BMESH forcibly triangulates everything.
@ -27,0 +301,4 @@
struct BMLogSetBase {
BMLogSetType type;
BMLogEntry *entry = nullptr; /* Parent entry */
It would be very nice to avoid this back pointer. Is that possible?
I could go with a dependency injection pattern, bundle the API methods I need into a separate struct that forwards back to BMEntry.
@ -27,0 +311,4 @@
{
return "";
}
virtual void undo(BMesh * /*bm*/, BMLogCallbacks * /*callbacks*/) {}
virtual void undo(BMesh * /*bm*/, BMLogCallbacks * /*callbacks*/) = 0;
That's a nice way to force sub-classes to implement the methods and end up with an error otherwise.
@ -27,0 +364,4 @@
BMLogSetFullMesh(BMesh *bm, BMLogEntry *entry, BMIdMap *idmap)
: BMLogSetBase(entry, LOG_SET_FULL)
{
printf("Log full mesh\n");
Lost debug print here
@ -27,0 +371,4 @@
/* Ensure we write idmap attrs. */
set_idlayer_temporary_flag(&bm->vdata, BM_VERT, false);
set_idlayer_temporary_flag(&bm->edata, BM_EDGE, false);
I described this more in the overall comment, but it would be good to avoid the need to involve
CD_FLAG_TEMPORARY
here. I think it would be better if attributes were deleted or not copied explicitly when leaving sculpt mode. ThisCD_FLAG_TEMPORARY
stuff (currently mostly used for theCD_NORMAL
which is a not-great design anyway) complicates things and is clearly a leaky abstraction.Either way, I'd like to leave the complexity for later or not involve it here in the logging.
I'll get rid of it then and just have it delete the idmap layers on sculpt mode exit.
I'll get rid of it then and just have it delete the idmap layers on sculpt mode exit.
@ -27,0 +400,4 @@
void swap(BMesh *bm)
{
CustomData_MeshMasks cd_mask_extra = {0, 0, 0, 0, 0};
Can be a bit simpler in C++:
CustomData_MeshMasks cd_mask_extra{};
@ -27,0 +419,4 @@
BMeshFromMeshParams params2 = {};
params2.cd_mask_extra = cd_mask_extra;
params2.calc_face_normal = params2.add_key_index = params2.use_shapekey = false;
Setting these to false is already done by
= {}
above@ -27,0 +435,4 @@
bm->shapenr = shapenr;
bm->elem_index_dirty |= BM_VERT | BM_EDGE | BM_FACE;
bm->elem_table_dirty |= BM_VERT | BM_EDGE | BM_FACE;
Setting the indices and tables dirty seems like something
BM_mesh_bm_from_me
should be doing. If so, maybe we can split that out and commit it to main. It seems like it would be a problem to fill the BMesh with new data and not reset the tablesI'm actually not sure this is needed at all, you might be right. I'll try removing it.
@ -27,0 +464,4 @@
}
}
Mesh *mesh = nullptr;
The style guide mentions declaring class variables above all class methods
@ -27,0 +494,4 @@
return "loop";
case BM_FACE:
return "face";
default:
The default case can be moved out of the switch and a
BLI_assert_unreachable()
can be added@ -27,0 +518,4 @@
struct BMLogEntry {
BMLogEntry *next = nullptr, *prev = nullptr;
Vector<BMLogSetBase *> sets;
Vector<std::unique_ptr<BMLogSetBase>>
Since
BMLogSetBase
has a virtual destructor, this should work fine :)@ -27,0 +581,4 @@
return nullptr;
}
void copy_custom_data(CustomData *source, CustomData *dest, void *src_block, void **dest_block)
Make the source variables const
@ -27,0 +638,4 @@
CustomData_free(&pdata, 0);
}
void print()
Can we remove this print function for now, to avoid having the conversation about all these variable names and temporary code?
The print function is called by sculpt_undo's debug printing.
@ -47,3 +71,2 @@
int BM_log_length(const BMLog *log);
BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry, bool for_redo);
/** Apply a consistent ordering to BMesh vertices and faces. */
A lot of the changes to comments in this file look unnecessary. I do think that some of the previous comments were a bit too verbose or exposed internal details, but I think it would be much nicer to change them later, to make what's actually changing in this PR clearer.
So it would be great if you could edit the diff here to only keep the necessary changes.
@ -15,3 +47,2 @@
typedef struct BMLog BMLog;
typedef struct BMLogEntry BMLogEntry;
struct BMLogCallbacks {
It looks to me like the
BMLogCallbacks
aren't used. If that's so, I'd really prefer to not include them for now, to make this change more minimal and clearer.Which uses of CD_FLAG_TEMPORARY? Do you mean the idmap attributes?
That's for incrementally updating the PBVH. Since that's going to be a different PR it can be removed, yes.
Okay, yeah that's the part I didn't look into in depth in this PR. But yes, I do mean those. Just to be clear, the main intention of using
CD_FLAG_TEMPORARY
is to avoid having these idmap attributes when exiting dynamic topology, right?Right. I went ahead and removed
CD_FLAG_TEMPORARY
and just explicitly deleted them.One big picture question-- could you motivate the need for the change to the ID map in the description? It looks like this replaces this:
But the need to store an integer per element is not great-- seems that it will significantly increase the memory usage-- especially for edges and face corners. Maybe I'm missing something?
@ -0,0 +1,2 @@
#include "intern/bmesh_log.h"
This file should be removed for now. We can change to more granular bmesh includes as a separate step later.
@ -21,0 +56,4 @@
/* `customdata_layout_is_same` and `customdata_copy_all_layout` are
* used internally by BMLog and probably don't have much use elsewhere.
* They ignore all copy-on-write semantics which makes sense since this
Implicit sharing shouldn't make a difference in the BMesh case anyway, since
data
is always null. So it should be fine to just use the general API functions.@ -21,0 +68,4 @@
}
for (int i : IndexRange(data_a->totlayer)) {
CustomDataLayer &layer_a = data_a->layers[i];
const CustomDataLayer
@ -21,0 +84,4 @@
/* Copies all customdata layers without allocating data
* and without respect to type masks or NO_COPY/etc flags.
*/
static void customdata_copy_all_layout(const CustomData *source, CustomData *dest)
This seems unnecessary logically now,
CustomData_copy_layout
should do the trick, and would be consistent with howCustomData_bmesh_copy_data
is used later too@ -21,0 +302,4 @@
#if 0
bm->elem_index_dirty |= BM_VERT | BM_EDGE | BM_FACE;
bm->elem_table_dirty |= BM_VERT | BM_EDGE | BM_FACE;
I thought we agreed regenerating the indices and tables was unnecessary here, and should already be dealt with by
BM_mesh_bm_from_me
or elsewhere.Not sure what I was thinking. It's funny I
#if 0'
d it out.@ -103,1 +118,4 @@
/* Now log full mesh again after triangulation. */
if (for_undo && ss->bm->totface != faces_num) {
BM_log_full_mesh(ss->bm, ss->bm_log);
Why do we need to log the mesh before and after triangulation? It seems like just after triangulation would be enough, since the before state would be captured by the undo step before that.
I'll have to look into it. You do need two pushes per step (this is how geometry undo steps work too) but the first may've already happened at this point.
It uses less memory then storing the same amount of data in two hash maps and a range tree. :) Face corners don't get IDs btw, they don't need them (they're logged with their owning faces).
I did a lot of profiling on the performance of RangeTree, it was a significant source of performance loss. What I ended up doing was replacing it with a simple free list. If you need to remove an arbitrary ID from the freelist it does a simple linear search, unless the free list grows beyond a certain size in which case the code maintains an id-to-freelist-index hashmap.
Thanks for the info, that makes sense! My remaining comment about the idmap is probably the attribute names then. Ideally they wouldn't be specific to sculpt with
.sculpt
at the beginning.I wonder how the semantics compare to the
id
vertex attribute used in geometry nodes. If they were the same (or similar enough), they could beid
andid_edge
andid_face
.Checkout
From your project repository, check out a new branch and test the changes.