From 9efc23bb690eaed1718adf727a406140a1647d3d Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 9 Jun 2025 12:08:24 +0200 Subject: [PATCH 1/5] Fix #140018: no warning when using bake node or simulation in zone Baking and storing simulation state within loops or closures is not supported. Previously, attempting to use the bake node or simulation zone in such a zone would just silently fail. Now there is an error on the node and the bake settings are grayed out. Pull Request: https://projects.blender.org/blender/blender/pulls/140041 --- source/blender/editors/include/ED_node.hh | 9 +- .../blender/editors/space_node/space_node.cc | 94 +++++++++++-------- source/blender/nodes/CMakeLists.txt | 1 + .../nodes/NOD_geometry_nodes_lazy_function.hh | 8 +- source/blender/nodes/NOD_nested_node_id.hh | 20 ++++ .../nodes/geometry/include/NOD_geo_bake.hh | 1 + .../nodes/geometry/nodes/node_geo_bake.cc | 20 ++-- .../geometry/nodes/node_geo_simulation.cc | 7 ++ .../intern/geometry_nodes_lazy_function.cc | 36 +------ 9 files changed, 112 insertions(+), 84 deletions(-) create mode 100644 source/blender/nodes/NOD_nested_node_id.hh diff --git a/source/blender/editors/include/ED_node.hh b/source/blender/editors/include/ED_node.hh index 476d35d34ca..9ce12992737 100644 --- a/source/blender/editors/include/ED_node.hh +++ b/source/blender/editors/include/ED_node.hh @@ -14,6 +14,7 @@ #include "BKE_compute_context_cache_fwd.hh" #include "NOD_geometry_nodes_closure_location.hh" +#include "NOD_nested_node_id.hh" #include "ED_node_c.hh" @@ -82,7 +83,10 @@ void std_node_socket_colors_get(int socket_type, float *r_color); /** * Find the nested node id of a currently visible node in the root tree. */ -std::optional find_nested_node_id_in_root(const SpaceNode &snode, const bNode &node); +std::optional find_nested_node_id_in_root(const SpaceNode &snode, + const bNode &node); +std::optional find_nested_node_id_in_root( + const bNodeTree &root_tree, const ComputeContext *compute_context, const int node_id); struct ObjectAndModifier { const Object *object; @@ -112,6 +116,9 @@ bool node_editor_is_for_geometry_nodes_modifier(const SpaceNode &snode, bke::ComputeContextCache &compute_context_cache, const bNodeSocket &socket); +[[nodiscard]] const ComputeContext *compute_context_for_edittree_node( + const SpaceNode &snode, bke::ComputeContextCache &compute_context_cache, const bNode &node); + /** * Attempts to find a compute context that the closure is evaluated in. If none is found, null is * returned. If multiple are found, it currently picks the first one it finds which is somewhat diff --git a/source/blender/editors/space_node/space_node.cc b/source/blender/editors/space_node/space_node.cc index be03f905179..b04369adfa9 100644 --- a/source/blender/editors/space_node/space_node.cc +++ b/source/blender/editors/space_node/space_node.cc @@ -268,51 +268,53 @@ float2 space_node_group_offset(const SpaceNode &snode) return float2(0); } -static const bNode *group_node_by_name(const bNodeTree &ntree, StringRef name) -{ - for (const bNode *node : ntree.group_nodes()) { - if (node->name == name) { - return node; - } - } - return nullptr; -} - -std::optional find_nested_node_id_in_root(const SpaceNode &snode, const bNode &query_node) +std::optional find_nested_node_id_in_root(const SpaceNode &snode, + const bNode &query_node) { BLI_assert(snode.edittree->runtime->nodes_by_id.contains(const_cast(&query_node))); + bke::ComputeContextCache compute_context_cache; + const ComputeContext *compute_context = compute_context_for_edittree_node( + snode, compute_context_cache, query_node); + if (!compute_context) { + return {}; + } + return find_nested_node_id_in_root(*snode.nodetree, compute_context, query_node.identifier); +} - std::optional id_in_node; - const char *group_node_name = nullptr; - const bNode *node = &query_node; - LISTBASE_FOREACH_BACKWARD (const bNodeTreePath *, path, &snode.treepath) { - const bNodeTree *ntree = path->nodetree; - ntree->ensure_topology_cache(); - if (group_node_name) { - node = group_node_by_name(*ntree, group_node_name); +std::optional find_nested_node_id_in_root( + const bNodeTree &root_tree, const ComputeContext *compute_context, const int node_id) +{ + nodes::FoundNestedNodeID found; + Vector node_ids; + for (const ComputeContext *context = compute_context; context != nullptr; + context = context->parent()) + { + if (const auto *node_context = dynamic_cast(context)) { + node_ids.append(node_context->node_id()); } - bool found = false; - for (const bNestedNodeRef &ref : ntree->nested_node_refs_span()) { - if (node->is_group()) { - if (ref.path.node_id == node->identifier && ref.path.id_in_node == id_in_node) { - group_node_name = path->node_name; - id_in_node = ref.id; - found = true; - break; - } - } - else if (ref.path.node_id == node->identifier) { - group_node_name = path->node_name; - id_in_node = ref.id; - found = true; - break; - } + else if (dynamic_cast(context) != nullptr) { + found.is_in_loop = true; } - if (!found) { - return std::nullopt; + else if (dynamic_cast(context) != nullptr) { + found.is_in_simulation = true; + } + else if (dynamic_cast(context) != + nullptr) + { + found.is_in_loop = true; + } + else if (dynamic_cast(context) != nullptr) { + found.is_in_closure = true; } } - return id_in_node; + std::reverse(node_ids.begin(), node_ids.end()); + node_ids.append(node_id); + const bNestedNodeRef *nested_node_ref = root_tree.nested_node_ref_from_node_id_path(node_ids); + if (nested_node_ref == nullptr) { + return std::nullopt; + } + found.id = nested_node_ref->id; + return found; } std::optional get_modifier_for_node_editor(const SpaceNode &snode) @@ -645,6 +647,22 @@ const ComputeContext *compute_context_for_edittree_socket( return compute_context_for_zones(zone_stack, compute_context_cache, context); } +const ComputeContext *compute_context_for_edittree_node( + const SpaceNode &snode, bke::ComputeContextCache &compute_context_cache, const bNode &node) +{ + const ComputeContext *context = compute_context_for_edittree(snode, compute_context_cache); + if (!context) { + return nullptr; + } + const bke::bNodeTreeZones *zones = snode.edittree->zones(); + if (!zones) { + return nullptr; + } + const bke::bNodeTreeZone *zone = zones->get_zone_by_node(node.identifier); + const Vector zone_stack = zones->get_zones_to_enter_from_root(zone); + return compute_context_for_zones(zone_stack, compute_context_cache, context); +} + /* ******************** default callbacks for node space ***************** */ static SpaceLink *node_create(const ScrArea * /*area*/, const Scene * /*scene*/) diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index cc44f827003..2176230e60f 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -122,6 +122,7 @@ set(SRC NOD_inverse_eval_run.hh NOD_math_functions.hh NOD_multi_function.hh + NOD_nested_node_id.hh NOD_node_declaration.hh NOD_node_extra_info.hh NOD_node_in_compute_context.hh diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index b4dc788238b..39cfe4a00a7 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -27,6 +27,7 @@ #include "NOD_geometry_nodes_log.hh" #include "NOD_multi_function.hh" +#include "NOD_nested_node_id.hh" #include "BLI_compute_context.hh" #include "BLI_math_quaternion_types.hh" @@ -461,13 +462,6 @@ std::string make_anonymous_attribute_socket_inspection_string(const bNodeSocket std::string make_anonymous_attribute_socket_inspection_string(StringRef node_name, StringRef socket_name); -struct FoundNestedNodeID { - int id; - bool is_in_simulation = false; - bool is_in_loop = false; - bool is_in_closure = false; -}; - std::optional find_nested_node_id(const GeoNodesUserData &user_data, const int node_id); diff --git a/source/blender/nodes/NOD_nested_node_id.hh b/source/blender/nodes/NOD_nested_node_id.hh new file mode 100644 index 00000000000..5bccc267c10 --- /dev/null +++ b/source/blender/nodes/NOD_nested_node_id.hh @@ -0,0 +1,20 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +namespace blender::nodes { + +/** + * Utility struct to store information about a nested node id. Also see #bNestedNodeRef. + * Sometimes these IDs can only be used when they are at the top level and not within zones. + */ +struct FoundNestedNodeID { + int id; + bool is_in_simulation = false; + bool is_in_loop = false; + bool is_in_closure = false; +}; + +} // namespace blender::nodes diff --git a/source/blender/nodes/geometry/include/NOD_geo_bake.hh b/source/blender/nodes/geometry/include/NOD_geo_bake.hh index 4112aa96962..e5644b88add 100644 --- a/source/blender/nodes/geometry/include/NOD_geo_bake.hh +++ b/source/blender/nodes/geometry/include/NOD_geo_bake.hh @@ -111,6 +111,7 @@ struct BakeDrawContext { bool bake_still; bool is_baked; std::optional bake_target; + bool is_bakeable_in_current_context; }; [[nodiscard]] bool get_bake_draw_context(const bContext *C, diff --git a/source/blender/nodes/geometry/nodes/node_geo_bake.cc b/source/blender/nodes/geometry/nodes/node_geo_bake.cc index 29a2b402718..117dad29c4f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_bake.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_bake.cc @@ -489,6 +489,12 @@ static void node_extra_info(NodeExtraInfoParams ¶ms) if (!get_bake_draw_context(¶ms.C, params.node, ctx)) { return; } + if (!ctx.is_bakeable_in_current_context) { + NodeExtraInfoRow row; + row.text = TIP_("Can't bake in zone"); + row.icon = ICON_ERROR; + params.rows.append(std::move(row)); + } if (ctx.is_baked) { NodeExtraInfoRow row; row.text = get_baked_string(ctx); @@ -503,7 +509,7 @@ static void node_layout(uiLayout *layout, bContext *C, PointerRNA *ptr) if (!get_bake_draw_context(C, node, ctx)) { return; } - + uiLayoutSetActive(layout, ctx.is_bakeable_in_current_context); uiLayoutSetEnabled(layout, ID_IS_EDITABLE(ctx.object)); uiLayout *col = &layout->column(false); { @@ -524,6 +530,7 @@ static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) return; } + uiLayoutSetActive(layout, ctx.is_bakeable_in_current_context); uiLayoutSetEnabled(layout, ID_IS_EDITABLE(ctx.object)); { @@ -631,14 +638,15 @@ bool get_bake_draw_context(const bContext *C, const bNode &node, BakeDrawContext } r_ctx.object = object_and_modifier->object; r_ctx.nmd = object_and_modifier->nmd; - const std::optional bake_id = ed::space_node::find_nested_node_id_in_root(*r_ctx.snode, - *r_ctx.node); + const std::optional bake_id = ed::space_node::find_nested_node_id_in_root( + *r_ctx.snode, *r_ctx.node); if (!bake_id) { return false; } + r_ctx.is_bakeable_in_current_context = !bake_id->is_in_loop && !bake_id->is_in_closure; r_ctx.bake = nullptr; for (const NodesModifierBake &iter_bake : Span(r_ctx.nmd->bakes, r_ctx.nmd->bakes_num)) { - if (iter_bake.id == *bake_id) { + if (iter_bake.id == bake_id->id) { r_ctx.bake = &iter_bake; break; } @@ -653,7 +661,7 @@ bool get_bake_draw_context(const bContext *C, const bNode &node, BakeDrawContext const bke::bake::ModifierCache &cache = *r_ctx.nmd->runtime->cache; std::lock_guard lock{cache.mutex}; if (const std::unique_ptr *node_cache_ptr = - cache.bake_cache_by_id.lookup_ptr(*bake_id)) + cache.bake_cache_by_id.lookup_ptr(bake_id->id)) { const bke::bake::BakeNodeCache &node_cache = **node_cache_ptr; if (!node_cache.bake.frames.is_empty()) { @@ -663,7 +671,7 @@ bool get_bake_draw_context(const bContext *C, const bNode &node, BakeDrawContext } } else if (const std::unique_ptr *node_cache_ptr = - cache.simulation_cache_by_id.lookup_ptr(*bake_id)) + cache.simulation_cache_by_id.lookup_ptr(bake_id->id)) { const bke::bake::SimulationNodeCache &node_cache = **node_cache_ptr; if (!node_cache.bake.frames.is_empty() && diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation.cc index 0d0bf8013e5..f4f9f616b9c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation.cc @@ -225,6 +225,7 @@ static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *current_no if (!get_bake_draw_context(C, output_node, ctx)) { return; } + uiLayoutSetActive(layout, ctx.is_bakeable_in_current_context); draw_simulation_state(C, layout, ntree, output_node); @@ -865,6 +866,12 @@ static void node_extra_info(NodeExtraInfoParams ¶ms) if (!get_bake_draw_context(¶ms.C, params.node, ctx)) { return; } + if (!ctx.is_bakeable_in_current_context) { + NodeExtraInfoRow row; + row.text = TIP_("Can't bake in zone"); + row.icon = ICON_ERROR; + params.rows.append(std::move(row)); + } if (ctx.is_baked) { NodeExtraInfoRow row; row.text = get_baked_string(ctx); diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index b1bf2481b0b..89a068ac5d9 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -47,6 +47,8 @@ #include "BKE_node_tree_zones.hh" #include "BKE_type_conversions.hh" +#include "ED_node.hh" + #include "FN_lazy_function_graph_executor.hh" #include "DEG_depsgraph_query.hh" @@ -4302,38 +4304,8 @@ void GeoNodesLocalUserData::ensure_tree_logger(const GeoNodesUserData &user_data std::optional find_nested_node_id(const GeoNodesUserData &user_data, const int node_id) { - FoundNestedNodeID found; - Vector node_ids; - for (const ComputeContext *context = user_data.compute_context; context != nullptr; - context = context->parent()) - { - if (const auto *node_context = dynamic_cast(context)) { - node_ids.append(node_context->node_id()); - } - else if (dynamic_cast(context) != nullptr) { - found.is_in_loop = true; - } - else if (dynamic_cast(context) != nullptr) { - found.is_in_simulation = true; - } - else if (dynamic_cast(context) != - nullptr) - { - found.is_in_loop = true; - } - else if (dynamic_cast(context) != nullptr) { - found.is_in_closure = true; - } - } - std::reverse(node_ids.begin(), node_ids.end()); - node_ids.append(node_id); - const bNestedNodeRef *nested_node_ref = - user_data.call_data->root_ntree->nested_node_ref_from_node_id_path(node_ids); - if (nested_node_ref == nullptr) { - return std::nullopt; - } - found.id = nested_node_ref->id; - return found; + return ed::space_node::find_nested_node_id_in_root( + *user_data.call_data->root_ntree, user_data.compute_context, node_id); } GeoNodesOperatorDepsgraphs::~GeoNodesOperatorDepsgraphs() From b58c5f6e77705f98bf2cc240a6bf143c292c7901 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 9 Jun 2025 12:15:07 +0200 Subject: [PATCH 2/5] Fix #139172: group outputs show up in link-drag-search incorrectly --- source/blender/editors/space_node/link_drag_search.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/blender/editors/space_node/link_drag_search.cc b/source/blender/editors/space_node/link_drag_search.cc index 51fa1528fe4..7f1be80b741 100644 --- a/source/blender/editors/space_node/link_drag_search.cc +++ b/source/blender/editors/space_node/link_drag_search.cc @@ -295,6 +295,9 @@ static void gather_socket_link_operations(const bContext &C, } const bNodeTreeInterfaceSocket &interface_socket = reinterpret_cast(item); + if (!(interface_socket.flag & NODE_INTERFACE_SOCKET_INPUT)) { + return true; + } { const bke::bNodeSocketType *from_typeinfo = bke::node_socket_type_find( interface_socket.socket_type); From 1c47e31367b78a770d8ad5452d2e80cab5b51e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 9 Jun 2025 12:36:06 +0200 Subject: [PATCH 3/5] GPU: Enable GL multithreaded compilation by default This allows to reduce the waiting time caused by shader compilation on some GPU-driver combo. A new settings in the User Preferences make it possible to override the default amount of worker threads and optionally use subprocesses. We still use only one worker thread in cases where there is no benefit with adding more workers (like AMD pro driver and Intel windows). It doesn't scale as much as subprocesses for material shader compilation but that is for other reasons explained in #139818. Add some heuristic to avoid too much memory usage and / or too many stalls. Also add some heuristic to the default number of subprocess for the platform that shows scalling. Historically, multithreaded compilation was prevented by the need of context per thread inside `DRWShader` module. Also there was no good scaling at that time. But nowadays numbers shows different results with good scaling with reasonable amount of threads on many platforms. Even if we are going for vulkan in the next release most of the legacy hardware will still use OpenGL for a few other releases. So it is relevant to make this easy improvement. See pull request for measurements. Pull Request: https://projects.blender.org/blender/blender/pulls/139821 --- release/datafiles/userdef/userdef_default.c | 3 +- scripts/modules/rna_manual_reference.py | 2 +- scripts/startup/bl_ui/space_userpref.py | 7 +- .../blender/blenkernel/BKE_blender_version.h | 2 +- .../blenloader/intern/versioning_userdef.cc | 6 ++ .../draw/engines/eevee/eevee_instance.cc | 9 ++- source/blender/gpu/GPU_capabilities.hh | 2 +- source/blender/gpu/intern/gpu_capabilities.cc | 4 +- .../gpu/intern/gpu_capabilities_private.hh | 2 + source/blender/gpu/intern/gpu_pass.cc | 5 +- .../gpu/intern/gpu_shader_create_info.cc | 12 +--- source/blender/gpu/metal/mtl_shader.mm | 1 - source/blender/gpu/opengl/gl_backend.cc | 65 ++++++++++++++++--- source/blender/gpu/opengl/gl_backend.hh | 7 +- source/blender/gpu/opengl/gl_shader.hh | 4 +- source/blender/makesdna/DNA_userdef_types.h | 17 +++-- .../blender/makesdna/intern/dna_rename_defs.h | 1 + source/blender/makesrna/intern/rna_userdef.cc | 32 +++++++-- 18 files changed, 136 insertions(+), 45 deletions(-) diff --git a/release/datafiles/userdef/userdef_default.c b/release/datafiles/userdef/userdef_default.c index e81d5a32c26..dbf9f37a22e 100644 --- a/release/datafiles/userdef/userdef_default.c +++ b/release/datafiles/userdef/userdef_default.c @@ -116,7 +116,8 @@ const UserDef U_default = { #else .gpu_backend = GPU_BACKEND_OPENGL, #endif - .max_shader_compilation_subprocesses = 0, + .gpu_shader_workers = 0, + .shader_compilation_method = USER_SHADER_COMPILE_THREAD, /** Initialized by: #BKE_studiolight_default. */ .light_param = {{0}}, diff --git a/scripts/modules/rna_manual_reference.py b/scripts/modules/rna_manual_reference.py index 56cec1aa308..e1250f32156 100644 --- a/scripts/modules/rna_manual_reference.py +++ b/scripts/modules/rna_manual_reference.py @@ -30,7 +30,6 @@ url_manual_mapping = ( ("bpy.types.clothcollisionsettings.vertex_group_object_collisions*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings-vertex-group-object-collisions"), ("bpy.types.gpencilsculptsettings.use_automasking_material_active*", "grease_pencil/modes/sculpting/introduction.html#bpy-types-gpencilsculptsettings-use-automasking-material-active"), ("bpy.types.gpencilsculptsettings.use_automasking_material_stroke*", "grease_pencil/modes/sculpting/introduction.html#bpy-types-gpencilsculptsettings-use-automasking-material-stroke"), - ("bpy.types.preferencessystem.max_shader_compilation_subprocesses*", "editors/preferences/system.html#bpy-types-preferencessystem-max-shader-compilation-subprocesses"), ("bpy.types.cycleslightsettings.use_multiple_importance_sampling*", "render/cycles/light_settings.html#bpy-types-cycleslightsettings-use-multiple-importance-sampling"), ("bpy.types.fluiddomainsettings.sndparticle_potential_max_energy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-max-energy"), ("bpy.types.fluiddomainsettings.sndparticle_potential_min_energy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-min-energy"), @@ -80,6 +79,7 @@ url_manual_mapping = ( ("bpy.types.preferencesedit.grease_pencil_euclidean_distance*", "editors/preferences/editing.html#bpy-types-preferencesedit-grease-pencil-euclidean-distance"), ("bpy.types.preferencesedit.grease_pencil_manhattan_distance*", "editors/preferences/editing.html#bpy-types-preferencesedit-grease-pencil-manhattan-distance"), ("bpy.types.preferencesinput.mouse_emulate_3_button_modifier*", "editors/preferences/input.html#bpy-types-preferencesinput-mouse-emulate-3-button-modifier"), + ("bpy.types.preferencessystem.max_shader_compilation_workers*", "editors/preferences/system.html#bpy-types-preferencessystem-max-shader-compilation-workers"), ("bpy.types.brushgpencilsettings.use_stroke_random_strength*", "grease_pencil/modes/draw/brushes/draw.html#bpy-types-brushgpencilsettings-use-stroke-random-strength"), ("bpy.types.clothsettings.vertex_group_structural_stiffness*", "physics/cloth/settings/property_weights.html#bpy-types-clothsettings-vertex-group-structural-stiffness"), ("bpy.types.cyclesrendersettings.film_transparent_roughness*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-film-transparent-roughness"), diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index 9952ca2498f..71e2e251a28 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -809,8 +809,11 @@ class USERPREF_PT_system_memory(SystemPanel, CenterAlignMixIn, Panel): if sys.platform != "darwin": layout.separator() - col = layout.column() - col.prop(system, "max_shader_compilation_subprocesses") + col = layout.column(align=True) + col.active = system.gpu_backend != 'VULKAN' + col.row().prop(system, "shader_compilation_method", expand=True) + label = "Threads" if system.shader_compilation_method == 'THREAD' else "Subprocesses" + col.prop(system, "gpu_shader_workers", text=label) class USERPREF_PT_system_video_sequencer(SystemPanel, CenterAlignMixIn, Panel): diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index c3c1ece4fa9..9fd1a0c3f44 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 85 +#define BLENDER_FILE_SUBVERSION 86 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenloader/intern/versioning_userdef.cc b/source/blender/blenloader/intern/versioning_userdef.cc index afa4b9197b4..ba1d39df4b6 100644 --- a/source/blender/blenloader/intern/versioning_userdef.cc +++ b/source/blender/blenloader/intern/versioning_userdef.cc @@ -1496,6 +1496,12 @@ void blo_do_versions_userdef(UserDef *userdef) } } + if (!USER_VERSION_ATLEAST(405, 86)) { + if (userdef->gpu_shader_workers > 0) { + userdef->shader_compilation_method = USER_SHADER_COMPILE_SUBPROCESS; + } + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a USER_VERSION_ATLEAST check. diff --git a/source/blender/draw/engines/eevee/eevee_instance.cc b/source/blender/draw/engines/eevee/eevee_instance.cc index 509c49ce817..d566e1870e8 100644 --- a/source/blender/draw/engines/eevee/eevee_instance.cc +++ b/source/blender/draw/engines/eevee/eevee_instance.cc @@ -722,11 +722,14 @@ void Instance::draw_viewport() } if (materials.queued_shaders_count > 0) { info_append_i18n("Compiling shaders ({} remaining)", materials.queued_shaders_count); - if (!GPU_use_parallel_compilation() && - GPU_type_matches_ex(GPU_DEVICE_ANY, GPU_OS_ANY, GPU_DRIVER_ANY, GPU_BACKEND_OPENGL)) + if (GPU_backend_get_type() == GPU_BACKEND_OPENGL && !GPU_use_subprocess_compilation() && + /* Only recommend subprocesses when there is known gain. */ + (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_ANY) || + GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_WIN, GPU_DRIVER_ANY) || + GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OFFICIAL))) { info_append_i18n( - "Increasing Preferences > System > Max Shader Compilation Subprocesses may improve " + "Setting Preferences > System > Shader Compilation Method to Subprocess might improve " "compilation time."); } } diff --git a/source/blender/gpu/GPU_capabilities.hh b/source/blender/gpu/GPU_capabilities.hh index f8a36498127..62bb7ab6af6 100644 --- a/source/blender/gpu/GPU_capabilities.hh +++ b/source/blender/gpu/GPU_capabilities.hh @@ -43,7 +43,7 @@ const char *GPU_extension_get(int i); int GPU_texture_size_with_limit(int res); -bool GPU_use_parallel_compilation(); +bool GPU_use_subprocess_compilation(); int GPU_max_parallel_compilations(); bool GPU_stencil_clasify_buffer_workaround(); diff --git a/source/blender/gpu/intern/gpu_capabilities.cc b/source/blender/gpu/intern/gpu_capabilities.cc index d460d1743da..83e89a85070 100644 --- a/source/blender/gpu/intern/gpu_capabilities.cc +++ b/source/blender/gpu/intern/gpu_capabilities.cc @@ -131,9 +131,9 @@ int GPU_max_samplers() return GCaps.max_samplers; } -bool GPU_use_parallel_compilation() +bool GPU_use_subprocess_compilation() { - return GCaps.max_parallel_compilations > 0; + return GCaps.use_subprocess_shader_compilations; } int GPU_max_parallel_compilations() diff --git a/source/blender/gpu/intern/gpu_capabilities_private.hh b/source/blender/gpu/intern/gpu_capabilities_private.hh index 6d5c887efb2..982c009fb9b 100644 --- a/source/blender/gpu/intern/gpu_capabilities_private.hh +++ b/source/blender/gpu/intern/gpu_capabilities_private.hh @@ -63,6 +63,8 @@ struct GPUCapabilities { bool node_link_instancing_workaround = false; bool line_directive_workaround = false; + bool use_subprocess_shader_compilations = false; + /* Vulkan related workarounds. */ bool render_pass_workaround = false; diff --git a/source/blender/gpu/intern/gpu_pass.cc b/source/blender/gpu/intern/gpu_pass.cc index 7845127833e..f78ab33cc26 100644 --- a/source/blender/gpu/intern/gpu_pass.cc +++ b/source/blender/gpu/intern/gpu_pass.cc @@ -165,9 +165,8 @@ bool GPU_pass_should_optimize(GPUPass *pass) return (GPU_backend_get_type() == GPU_BACKEND_METAL) && pass->should_optimize; #if 0 - /* Returns optimization heuristic prepared during initial codegen. - * NOTE: Optimization limited to parallel compilation as it causes CPU stalls otherwise. */ - return pass->should_optimize && GPU_use_parallel_compilation(); + /* Returns optimization heuristic prepared during initial codegen. */ + return pass->should_optimize; #endif } diff --git a/source/blender/gpu/intern/gpu_shader_create_info.cc b/source/blender/gpu/intern/gpu_shader_create_info.cc index 6e10d388888..0bcd518a7c3 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info.cc +++ b/source/blender/gpu/intern/gpu_shader_create_info.cc @@ -574,16 +574,8 @@ bool gpu_shader_create_info_compile(const char *name_starts_with_filter) } } - Vector result; - if (GPU_use_parallel_compilation() == false) { - for (const GPUShaderCreateInfo *info : infos) { - result.append(GPU_shader_create_from_info(info)); - } - } - else { - BatchHandle batch = GPU_shader_batch_create_from_infos(infos); - result = GPU_shader_batch_finalize(batch); - } + BatchHandle batch = GPU_shader_batch_create_from_infos(infos); + Vector result = GPU_shader_batch_finalize(batch); for (int i : result.index_range()) { const ShaderCreateInfo *info = reinterpret_cast(infos[i]); diff --git a/source/blender/gpu/metal/mtl_shader.mm b/source/blender/gpu/metal/mtl_shader.mm index 5c1bed55b60..78d40b466a8 100644 --- a/source/blender/gpu/metal/mtl_shader.mm +++ b/source/blender/gpu/metal/mtl_shader.mm @@ -1551,7 +1551,6 @@ MTLComputePipelineStateInstance *MTLShader::bake_compute_pipeline_state( MTLShaderCompiler::MTLShaderCompiler() : ShaderCompiler(GPU_max_parallel_compilations(), GPUWorker::ContextType::PerThread, true) { - BLI_assert(GPU_use_parallel_compilation()); } Shader *MTLShaderCompiler::compile_shader(const shader::ShaderCreateInfo &info) diff --git a/source/blender/gpu/opengl/gl_backend.cc b/source/blender/gpu/opengl/gl_backend.cc index c0d6b17d219..e2d7ebbfe94 100644 --- a/source/blender/gpu/opengl/gl_backend.cc +++ b/source/blender/gpu/opengl/gl_backend.cc @@ -723,17 +723,66 @@ void GLBackend::capabilities_init() detect_workarounds(); #if BLI_SUBPROCESS_SUPPORT - if (GCaps.max_parallel_compilations == -1) { - GCaps.max_parallel_compilations = std::min(int(U.max_shader_compilation_subprocesses), - BLI_system_thread_count()); - } + GCaps.use_subprocess_shader_compilations = U.shader_compilation_method == + USER_SHADER_COMPILE_SUBPROCESS; +#else + GCaps.use_subprocess_shader_compilations = false; +#endif if (G.debug & G_DEBUG_GPU_RENDERDOC) { /* Avoid crashes on RenderDoc sessions. */ - GCaps.max_parallel_compilations = 0; + GCaps.use_subprocess_shader_compilations = false; } -#else - GCaps.max_parallel_compilations = 0; -#endif + + int thread_count = U.gpu_shader_workers; + + if (thread_count == 0) { + /* Good default based on measurements. */ + + /* Always have at least 1 worker. */ + thread_count = 1; + + if (GCaps.use_subprocess_shader_compilations) { + /* Use reasonable number of worker by default when there are known gains. */ + if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_OFFICIAL) || + GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OFFICIAL) || + GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_WIN, GPU_DRIVER_ANY)) + { + /* Subprocess is too costly in memory (>150MB per worker) to have better defaults. */ + thread_count = std::max(1, std::min(4, BLI_system_thread_count() / 2)); + } + } + else if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) { + /* Best middle ground between memory usage and speedup as Nvidia context memory footprint + * is quite heavy (~25MB). Moreover we have diminishing return after this because of PSO + * compilation blocking the main thread. + * Can be revisited if we find a way to delete the worker thread context after finishing + * compilation, and fix the scheduling bubbles (#139775). */ + thread_count = 4; + } + else if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OPENSOURCE) || + GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_UNIX, GPU_DRIVER_ANY)) + { + /* Mesa has very good compilation time and doesn't block the main thread. + * The memory footprint of the worker context is rather small (<10MB). + * Shader compilation gets much slower as the number of threads increases. */ + thread_count = 8; + } + else if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) { + /* AMD proprietary driver's context have huge memory footprint (~45MB). + * There is also not much gain from parallelization. */ + thread_count = 1; + } + else if (GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_WIN, GPU_DRIVER_ANY)) { + /* Intel windows driver offer almost no speedup with parallel compilation. */ + thread_count = 1; + } + } + + /* Allow thread count override option to limit the number of workers and avoid allocating more + * workers than needed. Also ensures that there is always 1 thread available for the UI. */ + int max_thread_count = std::max(1, BLI_system_thread_count() - 1); + + GCaps.max_parallel_compilations = std::min(thread_count, max_thread_count); /* Disable this feature entirely when not debugging. */ if ((G.debug & G_DEBUG_GPU) == 0) { diff --git a/source/blender/gpu/opengl/gl_backend.hh b/source/blender/gpu/opengl/gl_backend.hh index 2fe45d0fa66..9313d1f87cf 100644 --- a/source/blender/gpu/opengl/gl_backend.hh +++ b/source/blender/gpu/opengl/gl_backend.hh @@ -9,10 +9,15 @@ #pragma once #include "GPU_capabilities.hh" +#include "GPU_platform.hh" + #include "gpu_backend.hh" +#include "BLI_threads.h" #include "BLI_vector.hh" +#include "gpu_capabilities_private.hh" + #ifdef WITH_RENDERDOC # include "renderdoc_api.hh" #endif @@ -56,7 +61,7 @@ class GLBackend : public GPUBackend { void init_resources() override { - if (GPU_use_parallel_compilation()) { + if (GCaps.use_subprocess_shader_compilations) { compiler_ = MEM_new(__func__); } else { diff --git a/source/blender/gpu/opengl/gl_shader.hh b/source/blender/gpu/opengl/gl_shader.hh index 2d1257c2380..1c6de5daa66 100644 --- a/source/blender/gpu/opengl/gl_shader.hh +++ b/source/blender/gpu/opengl/gl_shader.hh @@ -198,8 +198,8 @@ class GLShader : public Shader { class GLShaderCompiler : public ShaderCompiler { public: - GLShaderCompiler(uint32_t threads_count = 1) - : ShaderCompiler(threads_count, GPUWorker::ContextType::PerThread, true){}; + GLShaderCompiler() + : ShaderCompiler(GPU_max_parallel_compilations(), GPUWorker::ContextType::PerThread, true){}; virtual void specialize_shader(ShaderSpecialization &specialization) override; }; diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 13b1600c4c8..71a2c1b5fcf 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -487,13 +487,17 @@ typedef struct UserDef { int gpu_preferred_index; uint32_t gpu_preferred_vendor_id; uint32_t gpu_preferred_device_id; - char _pad16[4]; + + /** Max number of parallel shader compilation workers. */ + short gpu_shader_workers; + /** eUserpref_ShaderCompileMethod (OpenGL only). */ + short shader_compilation_method; + + char _pad16[2]; + /** #eGPUBackendType */ short gpu_backend; - /** Max number of parallel shader compilation subprocesses. */ - short max_shader_compilation_subprocesses; - /** Number of samples for FPS display calculations. */ short playback_fps_samples; @@ -1125,6 +1129,11 @@ typedef enum eUserpref_SeqEditorFlags { USER_SEQ_ED_CONNECT_STRIPS_BY_DEFAULT = (1 << 1), } eUserpref_SeqEditorFlags; +typedef enum eUserpref_ShaderCompileMethod { + USER_SHADER_COMPILE_THREAD = 0, + USER_SHADER_COMPILE_SUBPROCESS = 1, +} eUserpref_ShaderCompileMethod; + /* Locale Ids. Auto will try to get local from OS. Our default is English though. */ /** #UserDef.language */ enum { diff --git a/source/blender/makesdna/intern/dna_rename_defs.h b/source/blender/makesdna/intern/dna_rename_defs.h index 40e0553b2cc..690194786d7 100644 --- a/source/blender/makesdna/intern/dna_rename_defs.h +++ b/source/blender/makesdna/intern/dna_rename_defs.h @@ -215,6 +215,7 @@ DNA_STRUCT_RENAME_MEMBER(UVProjectModifierData, num_projectors, projectors_num) DNA_STRUCT_RENAME_MEMBER(UserDef, autokey_flag, keying_flag) DNA_STRUCT_RENAME_MEMBER(UserDef, gp_manhattendist, gp_manhattandist) DNA_STRUCT_RENAME_MEMBER(UserDef, pythondir, pythondir_legacy) +DNA_STRUCT_RENAME_MEMBER(UserDef, max_shader_compilation_subprocesses, gpu_shader_workers) DNA_STRUCT_RENAME_MEMBER(VFont, name, filepath) DNA_STRUCT_RENAME_MEMBER(View3D, far, clip_end) DNA_STRUCT_RENAME_MEMBER(View3D, local_collections_uuid, local_collections_uid) diff --git a/source/blender/makesrna/intern/rna_userdef.cc b/source/blender/makesrna/intern/rna_userdef.cc index c2543906a96..e7e0aa9927c 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -6349,14 +6349,36 @@ static void rna_def_userdef_system(BlenderRNA *brna) "Preferred device to select during detection (requires restarting " "Blender for changes to take effect)"); - prop = RNA_def_property(srna, "max_shader_compilation_subprocesses", PROP_INT, PROP_NONE); - RNA_def_property_range(prop, 0, INT16_MAX); + prop = RNA_def_property(srna, "gpu_shader_workers", PROP_INT, PROP_NONE); + RNA_def_property_range(prop, 0, 32); RNA_def_property_ui_text(prop, - "Max Shader Compilation Subprocesses", - "Max number of parallel shader compilation subprocesses, " + "Shader Compilation Workers", + "Number of shader compilation threads or subprocesses, " "clamped at the max threads supported by the CPU " "(requires restarting Blender for changes to take effect). " - "Setting it to 0 disables subprocess shader compilation."); + "A higher number increases the RAM usage while reducing " + "compilation time. A value of 0 will use automatic configuration. " + "(OpenGL only)"); + + static const EnumPropertyItem shader_compilation_method_items[] = { + {USER_SHADER_COMPILE_THREAD, "THREAD", 0, "Thread", "Use threads for compiling shaders"}, + {USER_SHADER_COMPILE_SUBPROCESS, + "SUBPROCESS", + 0, + "Subprocess", + "Use subprocesses for compiling shaders"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + prop = RNA_def_property(srna, "shader_compilation_method", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, shader_compilation_method_items); + RNA_def_property_ui_text(prop, + "Shader Compilation Method", + "Compilation method used for compiling shaders in parallel. " + "Subprocess requires a lot more RAM for each worker " + "but might compile shaders faster on some systems. " + "Requires restarting Blender for changes to take effect. " + "(OpenGL only)"); /* Network. */ From bcbdd4adb0eae07e7e26111730096ca57cbd46ea Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 9 Jun 2025 16:27:46 +1000 Subject: [PATCH 4/5] Fix #140034: Unhandled Python exception in the spreadsheet UI Ref !140035 --- scripts/startup/bl_ui/space_spreadsheet.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/startup/bl_ui/space_spreadsheet.py b/scripts/startup/bl_ui/space_spreadsheet.py index 67b82c4633f..9d4e9da5448 100644 --- a/scripts/startup/bl_ui/space_spreadsheet.py +++ b/scripts/startup/bl_ui/space_spreadsheet.py @@ -23,7 +23,8 @@ class SPREADSHEET_HT_header(bpy.types.Header): row.prop(space, "use_filter", toggle=True, icon='FILTER', icon_only=True) def selection_filter_available(self, space): - root_context = space.viewer_path.path[0] + if (root_context := next(iter(space.viewer_path.path), None)) is None: + return False if root_context.type != 'ID': return False if not isinstance(root_context.id, bpy.types.Object): From 672d8cfc6f60a3c285f7a5511ecc3869afba20e0 Mon Sep 17 00:00:00 2001 From: casey-bianco-davis Date: Mon, 9 Jun 2025 14:14:31 +0200 Subject: [PATCH 5/5] Fix #130597: Grease Pencil: Brush cursor radius not working in perspective projection. This fix make it so that the brush cursor is always the same size as the drawn strokes. The code logic is modified from `pixel_radius_to_world_space_radius` in `grease_pencil_utils.cc` Co-authored-by: Falk David Pull Request: https://projects.blender.org/blender/blender/pulls/139964 --- .../intern/grease_pencil_utils.cc | 11 +++++- .../editors/include/ED_grease_pencil.hh | 1 + .../editors/sculpt_paint/paint_cursor.cc | 38 ++++++++++++++++--- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc index 5e7b192ff49..402ef743501 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc @@ -347,23 +347,30 @@ float3 DrawingPlacement::try_project_depth(const float2 co) const return proj_point; } -float3 DrawingPlacement::project(const float2 co) const +float3 DrawingPlacement::project(const float2 co, bool &r_clipped) const { float3 proj_point; if (depth_ == DrawingPlacementDepth::Surface) { /* Project using the viewport depth cache. */ proj_point = this->try_project_depth(co); + r_clipped = false; } else { if (placement_plane_) { - ED_view3d_win_to_3d_on_plane(region_, *placement_plane_, co, false, proj_point); + r_clipped = !ED_view3d_win_to_3d_on_plane(region_, *placement_plane_, co, true, proj_point); } else { ED_view3d_win_to_3d(view3d_, region_, placement_loc_, co, proj_point); + r_clipped = false; } } return math::transform_point(world_space_to_layer_space_, proj_point); } +float3 DrawingPlacement::project(const float2 co) const +{ + [[maybe_unused]] bool clipped_unused; + return this->project(co, clipped_unused); +} float3 DrawingPlacement::project_with_shift(const float2 co) const { diff --git a/source/blender/editors/include/ED_grease_pencil.hh b/source/blender/editors/include/ED_grease_pencil.hh index fea58ec2ec9..83a6ac514a9 100644 --- a/source/blender/editors/include/ED_grease_pencil.hh +++ b/source/blender/editors/include/ED_grease_pencil.hh @@ -175,6 +175,7 @@ class DrawingPlacement { /** * Projects a screen space coordinate to the local drawing space. */ + float3 project(float2 co, bool &clipped) const; float3 project(float2 co) const; void project(Span src, MutableSpan dst) const; /** diff --git a/source/blender/editors/sculpt_paint/paint_cursor.cc b/source/blender/editors/sculpt_paint/paint_cursor.cc index 9ed9a8444b7..094113b51b6 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.cc +++ b/source/blender/editors/sculpt_paint/paint_cursor.cc @@ -542,6 +542,27 @@ static int project_brush_radius(ViewContext *vc, float radius, const float locat return 0; } +static int project_brush_radius_grease_pencil(ViewContext *vc, + const float radius, + const float3 world_location, + const float4x4 &to_world) +{ + const float2 xy_delta = float2(1.0f, 0.0f); + + bool z_flip; + const float zfac = ED_view3d_calc_zfac_ex(vc->rv3d, world_location, &z_flip); + if (z_flip) { + /* Location is behind camera. Return 0 to make the cursor disappear. */ + return 0; + } + float3 delta; + ED_view3d_win_to_delta(vc->region, xy_delta, zfac, delta); + + const float scale = math::length( + math::transform_direction(to_world, float3(math::numbers::inv_sqrt3))); + return math::safe_divide(scale * radius, math::length(delta)); +} + /* Draw an overlay that shows what effect the brush's texture will * have on brush strength. */ static bool paint_draw_tex_overlay(UnifiedPaintSettings *ups, @@ -1584,11 +1605,18 @@ static void grease_pencil_brush_cursor_draw(PaintCursorContext &pcontext) const bke::greasepencil::Layer *layer = grease_pencil->get_active_layer(); const ed::greasepencil::DrawingPlacement placement( *pcontext.scene, *pcontext.region, *pcontext.vc.v3d, *object, layer); - const float3 location = math::transform_point( - placement.to_world_space(), - placement.project(float2(pcontext.mval.x, pcontext.mval.y))); - pcontext.pixel_radius = project_brush_radius( - &pcontext.vc, brush->unprojected_radius, location); + const float2 coordinate = float2(pcontext.mval.x - pcontext.region->winrct.xmin, + pcontext.mval.y - pcontext.region->winrct.ymin); + bool clipped = false; + const float3 pos = placement.project(coordinate, clipped); + if (!clipped) { + const float3 world_location = math::transform_point(placement.to_world_space(), pos); + pcontext.pixel_radius = project_brush_radius_grease_pencil( + &pcontext.vc, brush->unprojected_radius, world_location, placement.to_world_space()); + } + else { + pcontext.pixel_radius = 0; + } brush->size = std::max(pcontext.pixel_radius, 1); } else {