Page MenuHome

Eevee: support accessing custom mesh attributes
ClosedPublic

Authored by Kévin Dietrich (kevindietrich) on Oct 22 2021, 5:03 PM.
Tags
None
Tokens
"Party Time" token, awarded by Bit."Love" token, awarded by zanqdo."Mountain of Wealth" token, awarded by duarteframos."Like" token, awarded by fclem."100" token, awarded by evilvoland."Love" token, awarded by Yuro."Burninate" token, awarded by kursadk."Love" token, awarded by lone_noel."Love" token, awarded by Leul."Party Time" token, awarded by brecht."Love" token, awarded by HooglyBoogly."Love" token, awarded by JacquesLucke.

Details

Summary

This adds generic attribute rendering support for meshes for Eevee and
Workbench. Each attribute is stored inside of the MeshBufferList as a
separate VBO, with a maximum of GPU_MAX_ATTR VBOs for consistency with
the GPU shader compilation code.

Since DRW_MeshCDMask is not general enough, attribute requests are
stored in new DRW_AttributeRequest structures inside of a convenient
DRW_MeshAttributes structure. The latter is used in a similar manner
as DRW_MeshCDMask, with the MeshBatchCache keeping track of needed,
used, and used-over-time attributes. Again, GPU_MAX_ATTR is used in
DRW_MeshAttributes to prevent too many attributes being used.

To ensure thread-safety when updating the used attributes list, a mutex
is added to the Mesh runtime. This mutex will also be used in the future
for other things when other part of the rendre pre-processing are multi-threaded.

GPU_BATCH_VBO_MAX_LEN was increased to 16 in order to accommodate for
this design.

Since CD_PROP_COLOR are a valid attribute type, sculpt vertex colors
are now handled using this system to avoid to complicate things. In the
future regular vertex colors will also use this. From this change, bit
operations for DRW_MeshCDMask are now using uint32_t (to match the
representation now used by the compiler).

Due to the difference in behavior for implicit type conversion for scalar types
between OpenGL and what users expect (a scalar s is converted to
vec4(s, 0, 0, 1) by OpenGL, vs. vec4(s, s, s, 1) in Blender's various node graphs) ,
all scalar types are using a float3 internally for now, which increases memory usage.
This will be resolved during or after the EEVEE rewrite as properly handling
this involves much deeper changes.

Ref T85075

Diff Detail

Repository
rB Blender

Event Timeline

Kévin Dietrich (kevindietrich) requested review of this revision.Oct 22 2021, 5:03 PM
Kévin Dietrich (kevindietrich) created this revision.
  • Attempt to resolve compiler error.

Worked well for the most part so far.

I get a crash when try to use an integer attribute: P2533.

Just type the name of the attribute in the Attribute node.

The compiler error is fixed btw.

  • Use proper component type for integer attributes.

Sorry about that, I made some last minute changes before uploading
the patch but only tested in a release build so I did not catch this
assertion failure. It should be fine now.

Solid work, I have little to add about the code itself.

As for the result I'm not sure what to expect. Some of the objects have NaN artifacts but this seems related to be blend modes used by the Mix RGB nodes.


So if this is the expected result (almost all red objects) I see no issues with this patch.

To me the setup cost of having 16 separated VBO is negligible, since using all of them on an animated object is quite unlikely.

source/blender/draw/intern/draw_cache_extract.h
73

Change assert message too.

83

Codestyle: Put comments on top of members using /** My comment. */.

That said I would use the real enum types instead (unless this is a concern for the conversions to int in C++).
If that's about including DNA_customdata_types.h, this header is only used by a handful of files that most likely already include it.

85

Same comment about enum types.

source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_attributes.cc
176

You don't need this to be an alias since you provide the same name.
Alias is only to make a different name point to the same attrib.

Kévin Dietrich (kevindietrich) marked 4 inline comments as done.
  • Address review comments.
  • Cleanup extractor creation macro.

Yes this is the expected result. The red color is because most attributes are scalar values converted to vec4(v, 0, 0, 1).

Just in case, here is my startup file with a prettier output:


Yes this is the expected result. The red color is because most attributes are scalar values converted to vec4(v, 0, 0, 1).

I don't think this is the expected behavior. Everywhere we convert scalar values to vectors with vec3(v, v, v) (e.g. when you connect a float socket to a vector socket in shading, compositing or geometry nodes).

Yes this is the expected result. The red color is because most attributes are scalar values converted to vec4(v, 0, 0, 1).

I don't think this is the expected behavior. Everywhere we convert scalar values to vectors with vec3(v, v, v) (e.g. when you connect a float socket to a vector socket in shading, compositing or geometry nodes).

In the shaders, all attributes are declared as vec4 (this is out of scope of this patch). If we do not give a vec4 type, it will be implicitly converted (or padded actually) to vec4 by OpenGL, and will give vec4(v, 0, 0, 1) for scalars

If you were to plug the float output socket of the attribute node to a vec3 socket, only the red channel will be read, and then converted to vec3(v, v, v) as we usually do. In the file that Clément used, which I posted in the task, the color socket of the attribute node is used, so the padded vec4(v, 0, 0, 1) is read directly with no conversions.

Note that this is already happening with UVs, they are implicitly converted to vec4(u, v, 0, 1) and this is what is read out of the color socket.

With UVs that behavior is expected, but not with scalar values. The cube really should be white in the example below, not red. Connecting Fac or Color to the output should yield the same result, because the same implicit conversion should be used internally. When converting scalars to colors in geometry nodes we use vec4(v, v, v, 1).
The fact that OpenGL gives you vec4(v, 0, 0, 1) is an implementation detail and should not impact our decision here imo.

The only real way of controlling how attributes are converted to vec4 is to do it ourselves. However, attribute type information is not available by the time shaders are compiled to GLSL (i.e. in the Attribute node GPU code generator). So we would need to pass this information at render time, but are limited by the amount of uniforms we can have overall. This would then be more of a technical limitation, than an "implementation detail". If there is an easy solution to this problem that I can look into today, which does not sacrifice artistic control too much, I'll try to do it. Otherwise, I'd consider this a do-do, and we should know, and document, that if we have a scalar attribute, and we want the scalar replicated in all dimensions of the output vector, the scalar output should be used.

The only way (that I can think of) to make scalar expanded as vec3, without having the overhead of using a bigger buffer, is to use buffer texture for these attributes and use texture state swizzle to map the attribute correctly. But this would require much more deeper code changes. This is how volume attributes do it so this might be useful to take a look at them.

Using individual uniforms for this (or even a 16bit bitmap) might also work.

I don't really like having to pass custom uniforms or samplers for drawing geometry, but if we have to do it, I would wait for EEVEE rewrite first as it is much cleaner to do it in the new code. Or you can try to implement that in the branch eevee-rewrite. ;)

This revision is now accepted and ready to land.Oct 25 2021, 2:12 PM

No idea if this can work, but maybe for the color and vector output we can read the value as float and as a vec4 and by comparing them in some way figure out the texture data type. I don't know the implicit conversions opengl does here, so it may not work. If it does work, I think we should use this hack instead of having inconsistent implicit conversions in common cases.
If this does not work, I'm fine with merging the patch as is, but we should try think of ways to warn the user about this behavior.. Definitely create a follow up task for it.

Your idea is going to fail if you have a redish color somewhere, or a float3 with a only the X-axis set. The conversion as I already mentioned before is to pad the values with vec4(0, 0, 0, 1), so a scalar or a vec3 with only the X-axis being non-zero will already become vec4(v, 0, 0, 1), and we would have no idea if this was a scalar, a vec3, or a color to begin with.

For now I'll edit the commit message to tell about this, and make sure the release notes are clear about this. I can also create a to-do task for it. As for warning the users, I guess we could also make a patch for the UI team to review to add a warning in the node or somewhere (I for now like when the UI tells me something). I remember there used to be warning on the SSS node in Cycles back when it was an experimental feature, so that should be doable.

A possibly related issue I just ran into. Shouldn't the brightness of the cube be the same when Fac vs. Value is connected to Surface?
Currently, when Fac is connected, the cube has a brightness of 1/3 in a render, while when Value is connected, it will be 1. That seems highly unintuitive and is inconsistent with cycles.

Good catch, the code for the float output socket is indeed outf = avg(attr.xyz);. Since this is for Blender 3.0, we could break compatibility with older files and let outf = attr.x for the time being.

Actually now that I think about it, "Fac" is a generic factor value, we could add a "Value" socket for scalars that will only select the first component of the vec4, thus not breaking compatibility.

I think having yet another output to circumvent an implementation issue is not great for the UX. If it is half working I would either delay it until it is fully working or ship it under "experimental".

I thought this patch was for 3.1 and not 3.0.

I think the fact that these attributes can't be rendered right now is almost a bug, I would rather have this in 3.0 even if it's not perfect.

The simplest solution to me seems to be to store floats as float3 always, and optimize the memory usage in 3.1.

  • Merge branch 'master' into attribute-render-review
  • Use float3 for all scalar types.

Seems to work as expected for me now :)

Sorry, i was in a pinch for time and this broke the windows build pretty badly, i could reach none of the people involved in chat, so i have reverted the commit for now

MSVC does not enjoy the offset of into an array for some reason and fails building with the following error

C:\Users\blender\git\blender-vdev\blender.git\source\blender\draw\intern\draw_cache_impl_mesh.c(198): error C2057: expected constant expression

something along this way MSVC seems to like though

diff --git a/source/blender/draw/intern/draw_cache_impl_mesh.c b/source/blender/draw/intern/draw_cache_impl_mesh.c
index 847913927f7..f23b0438f0c 100644
--- a/source/blender/draw/intern/draw_cache_impl_mesh.c
+++ b/source/blender/draw/intern/draw_cache_impl_mesh.c
@@ -79,8 +79,9 @@
  * \{ */

 /* clang-format off */
-
+#define member_size(type, member) sizeof(((type *)0)->member)
 #define BUFFER_INDEX(buff_name) ((offsetof(MeshBufferList, buff_name) - offsetof(MeshBufferList, vbo)) / sizeof(void *))
+#define BUFFER_INDEX_ARRAY(buff_name,index) (((offsetof(MeshBufferList, buff_name) - offsetof(MeshBufferList, vbo)) + (member_size(MeshBufferList, vbo.attr[0]) * index) ) / sizeof(void *))
 #define BUFFER_LEN (sizeof(MeshBufferList) / sizeof(void *))

 #define _BATCH_FLAG1(b) (1u << MBC_BATCH_INDEX(b))
@@ -195,22 +196,21 @@ static const DRWBatchFlag g_buffer_deps[] = {
     [BUFFER_INDEX(vbo.edge_idx)] = BATCH_FLAG(edit_selection_edges),
     [BUFFER_INDEX(vbo.poly_idx)] = BATCH_FLAG(edit_selection_faces),
     [BUFFER_INDEX(vbo.fdot_idx)] = BATCH_FLAG(edit_selection_fdots),
-    [BUFFER_INDEX(vbo.attr[0])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[1])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[2])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[3])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[4])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[5])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[6])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[7])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[8])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[9])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[10])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[11])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[12])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[13])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-    [BUFFER_INDEX(vbo.attr[14])] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
-
+    [BUFFER_INDEX_ARRAY(vbo.attr, 0)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 1)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 2)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 3)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 4)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 5)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 6)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 7)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 8)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 9)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 10)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 11)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 12)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 13)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
+    [BUFFER_INDEX_ARRAY(vbo.attr, 14)] = BATCH_FLAG(surface) | SURFACE_PER_MAT_FLAG,
     [BUFFER_INDEX(ibo.tris)] = BATCH_FLAG(surface,
                                           surface_weights,
                                           edit_triangles,

However BUFFER_INDEX_ARRAY references vbo.attr[0] directly, some macro magic will still have to be done there.

This revision is now accepted and ready to land.Oct 26 2021, 10:55 PM

I implemented that macro madness in order to safely free a batch.
Committed a fix for the build issue. rB3e3ff1a464b9
(It would be nice if that part of the code was simplified).