Refactor: Store a 'Mesh' in the editmesh snap cache and use it for snapping #117047

Merged
Germano Cavalcante merged 2 commits from mano-wii/blender:snap_refactor into main 2024-02-12 18:59:21 +01:00

In this commit, a temporary mesh is created representing the edit mesh.
This mesh is then used in the Edit Mesh snapping system instead of the
BMesh.

By using a Mesh object for snapping, we remove a considerable amount of
code and use a more optimized version of snapping.

This simplifies the code and makes it easier to implement new features.

The only downside is that more memory is required once a temporary mesh
is created.


I had this idea when I tried to update an old patch for the Knife Gizmo: https://archive.blender.org/developer/differential/0008/0008220/index.html

To support a snap in the style of what is seen in the knife snap, it would be necessary to use cageco obtained from mesh_runtime->edit_data->vertexCos;.
To do this, as we can be seen in that patch, we would need to add a considerable amount of complex code to support the new BVHTree type for snapping.
To avoid this, it would be much simpler to simply create a mesh that uses cageco. But that's a future plan.

This patch does not have a functional change perceptible to the user, it is just an improvement in the code.

Functions to focus on in the review:

  • void BM_mesh_bm_to_me_for_snapping(BMesh *bm, Mesh *mesh);
  • static Mesh *create_mesh(SnapObjectContext *sctx, Object *ob_eval, eSnapEditType /edit_mode_type/);
In this commit, a temporary mesh is created representing the edit mesh. This mesh is then used in the Edit Mesh snapping system instead of the BMesh. By using a Mesh object for snapping, we remove a considerable amount of code and use a more optimized version of snapping. This simplifies the code and makes it easier to implement new features. The only downside is that more memory is required once a temporary mesh is created. --- I had this idea when I tried to update an old patch for the Knife Gizmo: https://archive.blender.org/developer/differential/0008/0008220/index.html To support a snap in the style of what is seen in the knife snap, it would be necessary to use `cageco` obtained from `mesh_runtime->edit_data->vertexCos;`. To do this, as we can be seen in that patch, we would need to add a considerable amount of complex code to support the new BVHTree type for snapping. To avoid this, it would be much simpler to simply create a mesh that uses `cageco`. But that's a future plan. This patch does not have a functional change perceptible to the user, it is just an improvement in the code. **Functions to focus on in the review:** - void BM_mesh_bm_to_me_for_snapping(BMesh *bm, Mesh *mesh); - static Mesh *create_mesh(SnapObjectContext *sctx, Object *ob_eval, eSnapEditType /*edit_mode_type*/);
Germano Cavalcante requested review from Campbell Barton 2024-01-11 23:21:53 +01:00

How much more memory is used? Are data-layers (UV's etc) be removed so the difference is minimal?

How much more memory is used? Are data-layers (UV's etc) be removed so the difference is minimal?
Hans Goudey requested changes 2024-01-11 23:42:58 +01:00
Hans Goudey left a comment
Member

Interesting idea! I've wanted to use a minimal mesh from a BMesh in a few places too; this probably won't just be useful for snapping. Plus, this is nice for the eventual possibility of non-BMesh edit mode.

A few things:

  • CD_MASK_BAREMESH and CustomData_MeshMasks aren't really good tools for this job anymore. For example, they include all float3 attributes on the point domain, not just position. So I'd consider this a first step-- a next step would be changing CustomData_merge_layout to have more flexibility.
  • The code for mesh to bmesh conversion shouldn't know about the specifics of snapping. So I'd argue the function BM_mesh_bm_to_me_for_snapping doesn't really fit in the code architecture. Maybe exposing a function with a Set<std::string> attributes_to_copy (or a custom data mask for now) would be a more general solution.
  • The PR should probably have some timing information. I think some slowdown would be acceptable (in exchange for simpler code, less code, and easier future optimization), but we should make sure things aren't getting 2x slower or so.
Interesting idea! I've wanted to use a minimal mesh from a BMesh in a few places too; this probably won't just be useful for snapping. Plus, this is nice for the eventual possibility of non-BMesh edit mode. A few things: - `CD_MASK_BAREMESH` and `CustomData_MeshMasks` aren't really good tools for this job anymore. For example, they include _all_ `float3` attributes on the point domain, not just position. So I'd consider this a first step-- a next step would be changing `CustomData_merge_layout` to have more flexibility. - The code for mesh to bmesh conversion shouldn't know about the specifics of snapping. So I'd argue the function `BM_mesh_bm_to_me_for_snapping` doesn't really fit in the code architecture. Maybe exposing a function with a `Set<std::string> attributes_to_copy` (or a custom data mask for now) would be a more general solution. - The PR should probably have some timing information. I think some slowdown would be acceptable (in exchange for simpler code, less code, and easier future optimization), but we should make sure things aren't getting 2x slower or so.
Author
Member

How much more memory is used? Are data-layers (UV's etc) be removed so the difference is minimal?

The difference in memory is that now a simple Mesh is created. It uses CD_MASK_BAREMESH and has no attributes (".select_vert", "sharp_edge", ".uv_seam", among others).

(But a mesh is still heavy because it has a lot of data, many arrays).

A few things:

I didn't know that all float3 attributes were included! In fact, it is something to work on!

> How much more memory is used? Are data-layers (UV's etc) be removed so the difference is minimal? The difference in memory is that now a simple `Mesh` is created. It uses `CD_MASK_BAREMESH` and has no attributes (".select_vert", "sharp_edge", ".uv_seam", among others). (But a mesh is still heavy because it has a lot of data, many arrays). > A few things: I didn't know that all `float3` attributes were included! In fact, it is something to work on!
Author
Member

CD_MASK_BAREMESH and CustomData_MeshMasks aren't really good tools for this job anymore.

To not copy data layers, calls to CustomData_merge_layout are now optional.

The code for mesh to bmesh conversion shouldn't know about the specifics of snapping.

The function is now called BM_mesh_bm_to_me_compact which has more generic parameters.

The PR should probably have some timing information.

Still to do. But to get an idea of what changes, here is a table comparing before and after:

Cache Step Before After
Mesh No Yes (normal + looptris)
BVHTree Loose Verts Yes Yes
BVHTree Loose Edges No Yes
BVHTree Edges Yes No
BVHTree Triangles Yes Yes

The Snap steps don't change much other than the fact that Snap for Edges is done with the BVHTree of triangles.

> `CD_MASK_BAREMESH` and `CustomData_MeshMasks` aren't really good tools for this job anymore. To not copy data layers, calls to `CustomData_merge_layout` are now optional. > The code for mesh to bmesh conversion shouldn't know about the specifics of snapping. The function is now called `BM_mesh_bm_to_me_compact` which has more generic parameters. > The PR should probably have some timing information. Still to do. But to get an idea of what changes, here is a table comparing before and after: | Cache Step | Before | After | |--|--|--| | Mesh | No | Yes (normal + looptris) | | BVHTree Loose Verts | Yes | Yes | | BVHTree Loose Edges | No | Yes | | BVHTree Edges | Yes | No | | BVHTree Triangles | Yes | Yes | The Snap steps don't change much other than the fact that Snap for Edges is done with the BVHTree of triangles.
Hans Goudey reviewed 2024-01-12 17:47:47 +01:00
@ -1773,0 +1793,4 @@
}
/**
* Add these parallel functions to have a complete conversion from BMesh to Mesh.
* For now, for complete conversion, use #BM_mesh_bm_to_me instead.
Member

Adding all this code in a comment seems quite strange to me

Adding all this code in a comment seems quite strange to me
mano-wii marked this conversation as resolved
@ -74,2 +74,4 @@
ATTR_NONNULL(2, 3, 4);
/**
* A version of #BM_mesh_bm_to_me but copying data layers and Mesh attributes is optional.
Member

Might it be simpler for this to be a modified version of BM_mesh_bm_to_me_for_eval instead? That's already the simpler of the two functions.

Might it be simpler for this to be a modified version of `BM_mesh_bm_to_me_for_eval` instead? That's already the simpler of the two functions.
mano-wii marked this conversation as resolved
@ -76,0 +88,4 @@
* \param add_mesh_attributes If true, adds mesh attributes during the conversion.
*/
void BM_mesh_bm_to_me_compact(BMesh *bm,
Mesh *mesh,
Member

Personally I'd use references instead of ATTR_NONNULL here

Personally I'd use references instead of `ATTR_NONNULL` here
mano-wii marked this conversation as resolved
@ -76,0 +91,4 @@
Mesh *mesh,
const CustomData_MeshMasks *mask,
const bool add_mesh_attributes,
const bool active_shapekey_to_mvert) ATTR_NONNULL(1, 2);
Member

const for by-value arguments doesn't do anything in headers

const for by-value arguments doesn't do anything in headers
mano-wii marked this conversation as resolved
@ -40,1 +55,3 @@
BVHTree_RayCastCallback raycast_callback;
bool has_mesh_updated(Mesh *me)
{
if (me != this->me_ref || me->runtime != this->runtime_ref) {
Member

It would be nice to use mesh instead of me for mesh variables, that's more common and nicer to read

It would be nice to use `mesh` instead of `me` for mesh variables, that's more common and nicer to read
mano-wii marked this conversation as resolved
@ -71,1 +81,3 @@
float r_max[3])
static void set_hidden(BMesh *bm,
BMIterType itype,
bool (*fn)(BMElem *, void *user_data),
Member

Use FunctionRef instead of a raw function pointer

Use `FunctionRef` instead of a raw function pointer
Author
Member

Unfortunately, adding FunctionRef in this case increases the complexity of the code as the original callback may be nullptr. I ended up inlining the code.

Unfortunately, adding `FunctionRef` in this case increases the complexity of the code as the original callback may be `nullptr`. I ended up inlining the code.
HooglyBoogly marked this conversation as resolved
@ -102,0 +120,4 @@
[&]() {
set_hidden(bm,
BM_VERTS_OF_MESH,
(bool (*)(BMElem *, void *))sctx->callbacks.edit_mesh.test_vert_fn,
Member

I guess these can change to use Mesh in the future too? That could possibly be much faster, especially if work can be skipped due to missing attributes. Could be done later though.

I guess these can change to use `Mesh` in the future too? That could possibly be much faster, especially if work can be skipped due to missing attributes. Could be done later though.
Germano Cavalcante force-pushed snap_refactor from 02bfe503b0 to 080a65fe09 2024-01-24 19:25:02 +01:00 Compare
Author
Member

I did some time profiling tests on a High-poly Edit Mesh (A subdivided Suzane).

For now I only focused on cache time (creation of BVHTrees and other datas), as this is the most affected.

I was surprised that with the result of mixed snap (Vertice + Edge + Face) as it is faster:

Snap to Vert+Edge+Face:
Before After
652 ms 461 ms

It is faster because the Mesh snap system (unlike the Edit Mesh system) reuses LoopTris' BVHTree to snap to vertices and edges. Therefore, creating the BVHTree from Edges is no longer necessary in this case.

I changed then the test to snap to Faces only (so the cached data looks more similar):

Snap to Face:
Before After
269 ms 403 ms

This time, as expected, we have a loss.
The difference is in the creation of the Mesh.
I tried to improve the situation by also converting the looptris and normals arrays.
But converting the looptris proved to be slower than recreating them (maybe because mesh of quads is simple to create looptris).
Copying the normals showed no real difference, but I left that part of the code there for now.

I did some time profiling tests on a High-poly Edit Mesh (A subdivided Suzane). For now I only focused on cache time (creation of BVHTrees and other datas), as this is the most affected. I was surprised that with the result of mixed snap (Vertice + Edge + Face) as it is faster: |**Snap to Vert+Edge+Face:**| |--| |Before|After| |--|--| |652 ms| 461 ms| It is faster because the Mesh snap system (unlike the Edit Mesh system) reuses LoopTris' BVHTree to snap to vertices and edges. Therefore, creating the BVHTree from Edges is no longer necessary in this case. I changed then the test to snap to Faces only (so the cached data looks more similar): |**Snap to Face:**| |--| |Before|After| |--|--| |269 ms| 403 ms| This time, as expected, we have a loss. The difference is in the creation of the Mesh. I tried to improve the situation by also converting the looptris and normals arrays. But converting the looptris proved to be slower than recreating them (maybe because mesh of quads is simple to create looptris). Copying the normals showed no real difference, but I left that part of the code there for now.
Hans Goudey reviewed 2024-01-24 19:44:40 +01:00
@ -1619,3 +1619,3 @@
}
void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *mesh, const CustomData_MeshMasks *cd_mask_extra)
void BM_mesh_bm_to_me_compact(BMesh &bm,
Member

+1, but better to split the change to use references into a separate commit to main. It makes the code harder to review than it should be.

+1, but better to split the change to use references into a separate commit to main. It makes the code harder to review than it should be.
mano-wii marked this conversation as resolved
@ -1622,0 +1622,4 @@
Mesh &mesh,
const CustomData_MeshMasks *mask,
bool add_mesh_attributes,
bool active_shapekey_to_mvert)
Member

bool -> const bool

Also mvert isn't a thing anymore, a better name might be "apply_shapekey_positions" or something

`bool` -> `const bool` Also `mvert` isn't a thing anymore, a better name might be "apply_shapekey_positions" or something
Author
Member

After analyzing active_shapekey_to_mvert, this involves more than it seems. In addition to copying the active shape key coordinates to the base mesh, it also handles some kind of offsets.

For now, it might be best to remove this parameter. it has a very specific usage in the undo system. And also confuse as the mesh->key attribute also needs to be set outside the function for this parameter to be used.

I implemented IT with the intention of perhaps replacing the BM_mesh_bm_to_me function as well.

After analyzing `active_shapekey_to_mvert`, this involves more than it seems. In addition to copying the active shape key coordinates to the base mesh, it also handles some kind of offsets. For now, it might be best to remove this parameter. it has a very specific usage in the undo system. And also confuse as the `mesh->key` attribute also needs to be set outside the function for this parameter to be used. I implemented IT with the intention of perhaps replacing the `BM_mesh_bm_to_me` function as well.
@ -1742,0 +1724,4 @@
select_edge = attrs.lookup_or_add_for_write_only_span<bool>(".select_edge",
AttrDomain::Edge);
}
if (need_hide_edge) {
Member

Could the order of these attribute retrievals stay the same, just to keep the diff smaller?

Could the order of these attribute retrievals stay the same, just to keep the diff smaller?
mano-wii marked this conversation as resolved
@ -1749,2 +1755,3 @@
[&]() {
bm_to_mesh_edges(*bm,
bm_to_mesh_verts(bm, vert_table, mesh, select_vert.span, hide_vert.span);
if (mesh.key) {
Member

Might be nice to skip this explicitly with active_shapekey_to_mvert.

Might be nice to skip this explicitly with `active_shapekey_to_mvert`.
mano-wii marked this conversation as resolved
@ -31,0 +29,4 @@
static Mesh *get_mesh_ref(Object *ob_eval)
{
if (G.moving) {
/* WORKAROUND: Pass. Avoid updating while transforming. */
Member

Is this really a "WORKAROUND"? It seems more like just a UX optimization that takes into account performance.

Is this really a "WORKAROUND"? It seems more like just a UX optimization that takes into account performance.
Author
Member

Since G.moving is a global variable, it doesn't seem like an ideal solution to avoid updating the cache and mesh.
It would be better if this was a parameter of the snapping function.
But it is working fine in this state. (Adding a parameter can only increase the complexity).

This G.moving was there before the patch, it was just moved.

Since `G.moving` is a global variable, it doesn't seem like an ideal solution to avoid updating the cache and mesh. It would be better if this was a parameter of the snapping function. But it is working fine in this state. (Adding a parameter can only increase the complexity). This `G.moving` was there before the patch, it was just moved.
Member

Ah okay, I didn't see that this was just moved from elsewhere, you can forget my comment then :)

Ah okay, I didn't see that this was just moved from elsewhere, you can forget my comment then :)
@ -92,3 +101,1 @@
if (editmesh_eval_final) {
return editmesh_eval_final->runtime;
}
/* Loop over all elements in parallel, copying attributes and building the Mesh topology. */
Member

and building the Mesh topology

Not here.. I guess this is a copy & paste?

>and building the Mesh topology Not here.. I guess this is a copy & paste?
mano-wii marked this conversation as resolved
@ -95,0 +102,4 @@
const bool use_threading = (mesh->faces_num + mesh->edges_num) > 1024;
threading::parallel_invoke(
use_threading,
#ifdef COPY_LOOPTRIS /* Tests have shown that recreating looptris is generally faster than \
Member

That's a bit sad about BMesh performance, but not too surprising. Better to just remove this then, no need to keep the complexity.

That's a bit sad about BMesh performance, but not too surprising. Better to just remove this then, no need to keep the complexity.
@ -95,0 +119,4 @@
#endif
[&]() {
/* Take the opportunity to copy the normal of the vertices too. */
mesh->runtime->vert_normals_cache.ensure([&](Vector<float3> &r_data) {
Member

I'm a bit skeptical about copying the normals here. It's typically much simpler if we don't let the low-level normals caching creep into too much extra code. This might not help so much in the future anyway if corner normals are necessary or the mesh is flat shaded, etc. Unless it shows a noticeable performance improvement, simpler might be better.

I'm a bit skeptical about copying the normals here. It's typically much simpler if we don't let the low-level normals caching creep into too much extra code. This might not help so much in the future anyway if corner normals are necessary or the mesh is flat shaded, etc. Unless it shows a noticeable performance improvement, simpler might be better.
mano-wii marked this conversation as resolved
Member

Thanks for the performance testing. The change still seems worth it to me. It would be much easier to improve our BVH performance if we have half the code generating them. And that's something we want to do for performance in other areas as well.

Thanks for the performance testing. The change still seems worth it to me. It would be much easier to improve our BVH performance if we have half the code generating them. And that's something we want to do for performance in other areas as well.
Germano Cavalcante force-pushed snap_refactor from 080a65fe09 to 4922841090 2024-01-24 22:20:26 +01:00 Compare
Hans Goudey approved these changes 2024-01-31 21:53:43 +01:00
Germano Cavalcante force-pushed snap_refactor from d01809de7f to a2aa93922a 2024-02-12 16:56:59 +01:00 Compare
Author
Member

I ran additional benchmark tests. There are improvements in performance in some situations and a decline in others.
The setback in snap after caching for Face was surprising. I expected it to be faster due to the mesh memory being more compact. Perhaps it's within the margin of error.

Face + Edge + Vert

Cache Gen. Snap
Before 680.88 ms 0.1250 ms
After 489.06 ms 0.1064 ms
Improv 28.65% 14.88%

Face

Cache Gen. Snap
Before 293.90 ms 0.0230 ms
After 411.92 ms 0.0256 ms
Improv -40.15% -11.30%

@blender-bot build

I ran additional benchmark tests. There are improvements in performance in some situations and a decline in others. The setback in snap after caching for Face was surprising. I expected it to be faster due to the mesh memory being more compact. Perhaps it's within the margin of error. ## Face + Edge + Vert | | Cache | Gen. Snap | |--------|-----------|-----------| | Before | 680.88 ms | 0.1250 ms | | After | 489.06 ms | 0.1064 ms | | Improv | 28.65% | 14.88% | ## Face | | Cache | Gen. Snap | |--------|-----------|-----------| | Before | 293.90 ms | 0.0230 ms | | After | 411.92 ms | 0.0256 ms | | Improv | -40.15% | -11.30% | @blender-bot build
Author
Member

I conducted additional tests to assess the extra memory consumption after the patch, due to the creation of a Mesh.

It's noteworthy that the memory consumed for Mesh creation is significantly lower than that for BVHTree creation. Thus, there doesn't seem to be any cause for concern.

High Poly Mesh

Mesh: 71,161.90 KB (approximately 24.78% of the BVHTree's memory usage)
BVHTree Triangles: 287,282.39 KB

I conducted additional tests to assess the extra memory consumption after the patch, due to the creation of a Mesh. It's noteworthy that the memory consumed for Mesh creation is significantly lower than that for BVHTree creation. Thus, there doesn't seem to be any cause for concern. ## High Poly Mesh Mesh: 71,161.90 KB (approximately 24.78% of the BVHTree's memory usage) BVHTree Triangles: 287,282.39 KB
Germano Cavalcante added 1 commit 2024-02-12 18:58:18 +01:00
Germano Cavalcante merged commit 1c77779160 into main 2024-02-12 18:59:21 +01:00
Germano Cavalcante deleted branch snap_refactor 2024-02-12 18:59:23 +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 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#117047
No description provided.