Brush Assets: Save As, Delete, Revert and Update operators #117513

Merged
Brecht Van Lommel merged 10 commits from mont29/blender:bap-saveas-asset into brush-assets-project 2024-01-28 00:01:17 +01:00
159 changed files with 3313 additions and 987 deletions
Showing only changes of commit 6158907827 - Show all commits

View File

@ -3920,27 +3920,18 @@ def km_grease_pencil_stroke_paint_draw_brush(params):
)
# Draw
if params.use_experimental_grease_pencil_version3:
items.extend([
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
])
else:
items.extend([
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS'},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False)]}),
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False)]}),
# Draw - straight lines
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
{"properties": [("mode", 'DRAW_STRAIGHT'), ("wait_for_input", False)]}),
# Erase
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'ERASER'), ("wait_for_input", False)]}),
])
items.extend([
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS'},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False)]}),
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False)]}),
# Draw - straight lines
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
{"properties": [("mode", 'DRAW_STRAIGHT'), ("wait_for_input", False)]}),
# Erase
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'ERASER'), ("wait_for_input", False)]}),
])
items.extend([
# Tablet Mappings for Drawing ------------------ */
@ -4566,7 +4557,8 @@ def km_grease_pencil_stroke_vertex_replace(_params):
return keymap
def km_grease_pencil_paint(_params):
# Grease Pencil v3
def km_grease_pencil_paint_mode(_params):
items = []
keymap = (
"Grease Pencil Paint Mode",
@ -4575,7 +4567,6 @@ def km_grease_pencil_paint(_params):
)
items.extend([
*_template_paint_radial_control("gpencil_paint"),
("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True},
{"properties": [("scalar", 0.9)]}),
("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True},
@ -4585,6 +4576,7 @@ def km_grease_pencil_paint(_params):
{"properties": [("mode", 'INVERT')]}),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
*_template_paint_radial_control("gpencil_paint"),
# Active material
op_menu("VIEW3D_MT_greasepencil_material_active", {"type": 'U', "value": 'PRESS'}),
# Active layer
@ -4599,7 +4591,7 @@ def km_grease_pencil_paint(_params):
return keymap
def km_grease_pencil_edit(params):
def km_grease_pencil_edit_mode(params):
items = []
keymap = (
"Grease Pencil Edit Mode",
@ -8588,8 +8580,9 @@ def generate_keymaps(params=None):
km_grease_pencil_stroke_vertex_average(params),
km_grease_pencil_stroke_vertex_smear(params),
km_grease_pencil_stroke_vertex_replace(params),
km_grease_pencil_paint(params),
km_grease_pencil_edit(params),
# Grease Pencil v3
km_grease_pencil_paint_mode(params),
km_grease_pencil_edit_mode(params),
# Object mode.
km_object_mode(params),
km_object_non_modal(params),

View File

@ -524,48 +524,6 @@ class ARMATURE_OT_copy_bone_color_to_selected(Operator):
return {'FINISHED'}
class ARMATURE_OT_collection_solo_visibility(Operator):
"""Hide all other bone collections and show the active one. """ \
"""Note that it is necessary to also show the ancestors of the active """ \
"""bone collection in order to ensure its visibility"""
bl_idname = "armature.collection_solo_visibility"
bl_label = "Solo Visibility"
bl_options = {'REGISTER', 'UNDO'}
name: StringProperty(name='Bone Collection')
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'ARMATURE' and context.object.data
def execute(self, context):
arm = context.object.data
# Find the named bone collection.
if self.name:
try:
solo_bcoll = arm.collections[self.name]
except KeyError:
self.report({'ERROR'}, "Bone collection %r not found" % self.name)
return {'CANCELLED'}
else:
solo_bcoll = arm.collections.active
if not solo_bcoll:
self.report({'ERROR'}, "Armature has no active Bone collection, nothing to solo")
return {'CANCELLED'}
# Hide everything first.
for bcoll in arm.collections_all:
bcoll.is_visible = False
# Show the named bone collection and all its ancestors.
while solo_bcoll:
solo_bcoll.is_visible = True
solo_bcoll = solo_bcoll.parent
return {'FINISHED'}
class ARMATURE_OT_collection_show_all(Operator):
"""Show all bone collections"""
bl_idname = "armature.collection_show_all"
@ -583,6 +541,28 @@ class ARMATURE_OT_collection_show_all(Operator):
return {'FINISHED'}
class ARMATURE_OT_collection_unsolo_all(Operator):
"""Clear the 'solo' setting on all bone collections"""
bl_idname = "armature.collection_unsolo_all"
bl_label = "Un-solo All"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not (context.object and context.object.type == 'ARMATURE' and context.object.data):
return False
if not context.object.data.collections.is_solo_active:
cls.poll_message_set("None of the bone collections is marked 'solo'")
return False
return True
def execute(self, context):
arm = context.object.data
for bcoll in arm.collections_all:
bcoll.is_solo = False
return {'FINISHED'}
class ARMATURE_OT_collection_remove_unused(Operator):
"""Remove all bone collections that have neither bones nor children. """ \
"""This is done recursively, so bone collections that only have unused children are also removed"""
@ -673,7 +653,7 @@ classes = (
ClearUselessActions,
UpdateAnimatedTransformConstraint,
ARMATURE_OT_copy_bone_color_to_selected,
ARMATURE_OT_collection_solo_visibility,
ARMATURE_OT_collection_show_all,
ARMATURE_OT_collection_unsolo_all,
ARMATURE_OT_collection_remove_unused,
)

View File

@ -386,6 +386,61 @@ class NODE_OT_interface_item_remove(NodeInterfaceOperator, Operator):
return {'FINISHED'}
class NODE_OT_enum_definition_item_add(Operator):
'''Add an enum item to the definition'''
bl_idname = "node.enum_definition_item_add"
bl_label = "Add Item"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
node = context.active_node
enum_def = node.enum_definition
item = enum_def.enum_items.new("Item")
enum_def.active_index = enum_def.enum_items[:].index(item)
return {'FINISHED'}
class NODE_OT_enum_definition_item_remove(Operator):
'''Remove the selected enum item from the definition'''
bl_idname = "node.enum_definition_item_remove"
bl_label = "Remove Item"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
node = context.active_node
enum_def = node.enum_definition
item = enum_def.active_item
if item:
enum_def.enum_items.remove(item)
enum_def.active_index = min(max(enum_def.active_index, 0), len(enum_def.enum_items) - 1)
return {'FINISHED'}
class NODE_OT_enum_definition_item_move(Operator):
'''Remove the selected enum item from the definition'''
bl_idname = "node.enum_definition_item_move"
bl_label = "Move Item"
bl_options = {'REGISTER', 'UNDO'}
direction: EnumProperty(
name="Direction",
description="Move up or down",
items=[("UP", "Up", ""), ("DOWN", "Down", "")]
)
def execute(self, context):
node = context.active_node
enum_def = node.enum_definition
index = enum_def.active_index
if self.direction == 'UP':
enum_def.enum_items.move(index, index - 1)
enum_def.active_index = min(max(index - 1, 0), len(enum_def.enum_items) - 1)
else:
enum_def.enum_items.move(index, index + 1)
enum_def.active_index = min(max(index + 1, 0), len(enum_def.enum_items) - 1)
return {'FINISHED'}
classes = (
NodeSetting,
@ -397,4 +452,7 @@ classes = (
NODE_OT_interface_item_duplicate,
NODE_OT_interface_item_remove,
NODE_OT_tree_path_parent,
NODE_OT_enum_definition_item_add,
NODE_OT_enum_definition_item_remove,
NODE_OT_enum_definition_item_move,
)

View File

@ -543,6 +543,7 @@ class NODE_MT_category_GEO_UTILITIES(Menu):
layout.menu("NODE_MT_category_GEO_UTILITIES_MATH")
layout.menu("NODE_MT_category_GEO_UTILITIES_ROTATION")
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeMenuSwitch")
node_add_menu.add_node_type(layout, "FunctionNodeRandomValue")
node_add_menu.add_repeat_zone(layout, label="Repeat Zone")
node_add_menu.add_node_type(layout, "GeometryNodeSwitch")

View File

@ -143,15 +143,9 @@ class ARMATURE_MT_collection_context_menu(Menu):
def draw(self, context):
layout = self.layout
arm = context.armature
active_bcoll = arm.collections.active
props = layout.operator("armature.collection_solo_visibility")
props.name = active_bcoll.name if active_bcoll else ""
layout.operator("armature.collection_show_all")
layout.operator("armature.collection_unsolo_all")
layout.separator()
layout.operator("armature.collection_remove_unused", text="Remove Unused")
@ -175,8 +169,8 @@ class ARMATURE_MT_collection_tree_context_menu(Menu):
layout.separator()
layout.operator("armature.collection_solo_visibility")
layout.operator("armature.collection_show_all")
layout.operator("armature.collection_unsolo_all")
layout.separator()

View File

@ -157,7 +157,8 @@ class BrushSelectPanel(BrushPanel):
col = row.column()
col.menu("VIEW3D_MT_brush_context_menu", icon='DOWNARROW_HLT', text="")
panel = layout.panel("customize", text="Customize", default_closed=True)
header, panel = layout.panel("customize", default_closed=True)
header.label(text="Customize")
if panel:
panel.use_property_split = True
panel.use_property_decorate = False

View File

@ -1213,6 +1213,60 @@ class NODE_PT_index_switch_node_items(Panel):
row.operator("node.index_switch_item_remove", icon='REMOVE', text="").index = i
class NODE_UL_enum_definition_items(bpy.types.UIList):
def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index):
layout.prop(item, "name", text="", emboss=False, icon_value=icon)
class NODE_PT_menu_switch_items(Panel):
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = "Node"
bl_label = "Menu Switch"
@classmethod
def poll(cls, context):
snode = context.space_data
if snode is None:
return False
node = context.active_node
if node is None or node.bl_idname != "GeometryNodeMenuSwitch":
return False
return True
def draw(self, context):
node = context.active_node
layout = self.layout
split = layout.row()
split.template_list(
"NODE_UL_enum_definition_items",
"",
node.enum_definition,
"enum_items",
node.enum_definition,
"active_index")
ops_col = split.column()
add_remove_col = ops_col.column(align=True)
add_remove_col.operator("node.enum_definition_item_add", icon='ADD', text="")
add_remove_col.operator("node.enum_definition_item_remove", icon='REMOVE', text="")
ops_col.separator()
up_down_col = ops_col.column(align=True)
props = up_down_col.operator("node.enum_definition_item_move", icon='TRIA_UP', text="")
props.direction = 'UP'
props = up_down_col.operator("node.enum_definition_item_move", icon='TRIA_DOWN', text="")
props.direction = 'DOWN'
active_item = node.enum_definition.active_item
if active_item is not None:
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(active_item, "description")
# Grease Pencil properties
class NODE_PT_annotation(AnnotationDataPanel, Panel):
bl_space_type = 'NODE_EDITOR'
@ -1285,6 +1339,8 @@ classes = (
NODE_PT_bake_node_items,
NODE_PT_index_switch_node_items,
NODE_PT_repeat_zone_items,
NODE_UL_enum_definition_items,
NODE_PT_menu_switch_items,
NODE_PT_active_node_properties,
node_panel(EEVEE_MATERIAL_PT_settings),

View File

@ -1383,17 +1383,23 @@ class _defs_particle:
class _defs_sculpt:
@staticmethod
def generate_from_brushes(context):
return generate_from_enum_ex(
context,
idname_prefix="builtin_brush.",
icon_prefix="brush.sculpt.",
type=bpy.types.Brush,
attr="sculpt_tool",
# TODO(@ideasman42): we may want to enable this,
# it causes awkward grouping with 2x column button layout.
use_separators=False,
)
def generate_brush_tool(context):
if not context:
return ()
brush = context.tool_settings.sculpt.brush
if not brush:
return ()
tool = brush.sculpt_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label=brush.name,
icon="brush.sculpt.paint",
data_block=tool
)
)
]
@ToolDef.from_fn
def hide_border():
@ -1772,6 +1778,19 @@ class _defs_weight_paint:
class _defs_paint_grease_pencil:
# FIXME: Replace brush tools with code below once they are all implemented:
#
# @staticmethod
# def generate_from_brushes(context):
# return generate_from_enum_ex(
# context,
# idname_prefix="builtin_brush.",
# icon_prefix="brush.gpencil_draw.",
# type=bpy.types.Brush,
# attr="gpencil_tool",
# cursor='DOT',
# )
@ToolDef.from_fn
def draw():
return dict(
@ -2429,18 +2448,23 @@ class _defs_gpencil_weight:
class _defs_curves_sculpt:
@staticmethod
def generate_from_brushes(context):
return generate_from_enum_ex(
context,
idname_prefix="builtin_brush.",
icon_prefix="ops.curves.sculpt_",
type=bpy.types.Brush,
attr="curves_sculpt_tool",
icon_map={
# Use the generic icon for selection painting.
"ops.curves.sculpt_selection_paint": "ops.generic.select_paint",
},
)
def generate_brush_tool(context):
if not context:
return ()
brush = context.tool_settings.curves_sculpt.brush
if not brush:
return ()
tool = brush.curves_sculpt_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label=brush.name,
icon="brush.sculpt.paint",
data_block=tool
)
)
]
class _defs_gpencil_vertex:
@ -3076,7 +3100,9 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
_defs_particle.generate_from_brushes,
],
'SCULPT': [
_defs_sculpt.generate_from_brushes,
lambda context: (
_defs_sculpt.generate_brush_tool(context)
),
None,
(
_defs_sculpt.mask_border,
@ -3221,7 +3247,9 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
),
],
'SCULPT_CURVES': [
_defs_curves_sculpt.generate_from_brushes,
lambda context: (
_defs_curves_sculpt.generate_brush_tool(context)
),
None,
*_tools_annotate,
],

View File

@ -239,6 +239,23 @@ void ANIM_armature_bonecoll_is_visible_set(bArmature *armature,
BoneCollection *bcoll,
bool is_visible);
/**
* Set or clear this bone collection's solo flag.
*/
void ANIM_armature_bonecoll_solo_set(bArmature *armature, BoneCollection *bcoll, bool is_solo);
/**
* Refresh the ARM_BCOLL_SOLO_ACTIVE flag.
*/
void ANIM_armature_refresh_solo_active(bArmature *armature);
/**
* Determine whether this bone collection is visible, taking into account the visibility of its
* ancestors and the "solo" flags that are in use.
*/
bool ANIM_armature_bonecoll_is_visible_effectively(const bArmature *armature,
const BoneCollection *bcoll);
/**
* Assign the bone to the bone collection.
*

View File

@ -696,7 +696,13 @@ void ANIM_armature_bonecoll_remove_from_index(bArmature *armature, int index)
}
}
const bool is_solo = bcoll->is_solo();
internal::bonecoll_unassign_and_free(armature, bcoll);
if (is_solo) {
/* This might have been the last solo'ed bone collection, so check whether
* solo'ing should still be active on the armature. */
ANIM_armature_refresh_solo_active(armature);
}
}
void ANIM_armature_bonecoll_remove(bArmature *armature, BoneCollection *bcoll)
@ -738,7 +744,7 @@ static void ancestors_visible_descendants_clear(bArmature *armature, BoneCollect
/** Set or clear #BONE_COLLECTION_ANCESTORS_VISIBLE on all descendants of this bone collection. */
static void ancestors_visible_descendants_update(bArmature *armature, BoneCollection *parent_bcoll)
{
if (!parent_bcoll->is_visible_effectively()) {
if (!parent_bcoll->is_visible_with_ancestors()) {
/* If this bone collection is not visible itself, or any of its ancestors are
* invisible, all descendants have an invisible ancestor. */
ancestors_visible_descendants_clear(armature, parent_bcoll);
@ -759,7 +765,7 @@ static void ancestors_visible_update(bArmature *armature,
const BoneCollection *parent_bcoll,
BoneCollection *bcoll)
{
if (parent_bcoll == nullptr || parent_bcoll->is_visible_effectively()) {
if (parent_bcoll == nullptr || parent_bcoll->is_visible_with_ancestors()) {
bcoll->flags |= BONE_COLLECTION_ANCESTORS_VISIBLE;
}
else {
@ -792,6 +798,54 @@ void ANIM_armature_bonecoll_is_visible_set(bArmature *armature,
}
}
void ANIM_armature_bonecoll_solo_set(bArmature *armature,
BoneCollection *bcoll,
const bool is_solo)
{
if (is_solo) {
/* Enabling solo is simple. */
bcoll->flags |= BONE_COLLECTION_SOLO;
armature->flag |= ARM_BCOLL_SOLO_ACTIVE;
return;
}
/* Disabling is harder, as the armature flag can only be disabled when there
* are no more bone collections with the SOLO flag set. */
bcoll->flags &= ~BONE_COLLECTION_SOLO;
ANIM_armature_refresh_solo_active(armature);
}
void ANIM_armature_refresh_solo_active(bArmature *armature)
{
bool any_bcoll_solo = false;
for (const BoneCollection *bcoll : armature->collections_span()) {
if (bcoll->flags & BONE_COLLECTION_SOLO) {
any_bcoll_solo = true;
break;
}
}
if (any_bcoll_solo) {
armature->flag |= ARM_BCOLL_SOLO_ACTIVE;
}
else {
armature->flag &= ~ARM_BCOLL_SOLO_ACTIVE;
}
}
bool ANIM_armature_bonecoll_is_visible_effectively(const bArmature *armature,
const BoneCollection *bcoll)
{
const bool is_solo_active = armature->flag & ARM_BCOLL_SOLO_ACTIVE;
if (is_solo_active) {
/* If soloing is active, nothing in the hierarchy matters except the solo flag. */
return bcoll->is_solo();
}
return bcoll->is_visible_with_ancestors();
}
/* Store the bone's membership on the collection. */
static void add_membership(BoneCollection *bcoll, Bone *bone)
{
@ -925,7 +979,8 @@ void ANIM_armature_bonecoll_reconstruct(bArmature *armature)
});
}
static bool any_bone_collection_visible(const ListBase /*BoneCollectionRef*/ *collection_refs)
static bool any_bone_collection_visible(const bArmature *armature,
const ListBase /*BoneCollectionRef*/ *collection_refs)
{
/* Special case: when a bone is not in any collection, it is visible. */
if (BLI_listbase_is_empty(collection_refs)) {
@ -934,23 +989,20 @@ static bool any_bone_collection_visible(const ListBase /*BoneCollectionRef*/ *co
LISTBASE_FOREACH (const BoneCollectionReference *, bcoll_ref, collection_refs) {
const BoneCollection *bcoll = bcoll_ref->bcoll;
if (bcoll->is_visible_effectively()) {
if (ANIM_armature_bonecoll_is_visible_effectively(armature, bcoll)) {
return true;
}
}
return false;
}
/* TODO: these two functions were originally implemented for armature layers, hence the armature
* parameters. These should be removed at some point. */
bool ANIM_bone_in_visible_collection(const bArmature * /*armature*/, const Bone *bone)
bool ANIM_bone_in_visible_collection(const bArmature *armature, const Bone *bone)
{
return any_bone_collection_visible(&bone->runtime.collections);
return any_bone_collection_visible(armature, &bone->runtime.collections);
}
bool ANIM_bonecoll_is_visible_editbone(const bArmature * /*armature*/, const EditBone *ebone)
bool ANIM_bonecoll_is_visible_editbone(const bArmature *armature, const EditBone *ebone)
{
return any_bone_collection_visible(&ebone->bone_collections);
return any_bone_collection_visible(armature, &ebone->bone_collections);
}
void ANIM_armature_bonecoll_show_all(bArmature *armature)

View File

@ -43,7 +43,7 @@ TEST(ANIM_bone_collections, bonecoll_default_name)
}
}
class ANIM_armature_bone_collections : public testing::Test {
class ArmatureBoneCollections : public testing::Test {
protected:
bArmature arm;
Bone bone1, bone2, bone3;
@ -78,7 +78,7 @@ class ANIM_armature_bone_collections : public testing::Test {
}
};
TEST_F(ANIM_armature_bone_collections, armature_owned_collections)
TEST_F(ArmatureBoneCollections, armature_owned_collections)
{
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "collection");
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "collection");
@ -90,7 +90,7 @@ TEST_F(ANIM_armature_bone_collections, armature_owned_collections)
ANIM_armature_bonecoll_remove(&arm, bcoll2);
}
TEST_F(ANIM_armature_bone_collections, collection_hierarchy_creation)
TEST_F(ArmatureBoneCollections, collection_hierarchy_creation)
{
/* Implicit root: */
BoneCollection *bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "wortel");
@ -146,7 +146,7 @@ TEST_F(ANIM_armature_bone_collections, collection_hierarchy_creation)
/* TODO: test with deeper hierarchy. */
}
TEST_F(ANIM_armature_bone_collections, collection_hierarchy_removal)
TEST_F(ArmatureBoneCollections, collection_hierarchy_removal)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "root_0");
@ -240,8 +240,7 @@ TEST_F(ANIM_armature_bone_collections, collection_hierarchy_removal)
EXPECT_EQ(0, arm.collection_array[2]->child_count);
}
TEST_F(ANIM_armature_bone_collections,
collection_hierarchy_removal__more_complex_remove_inner_child)
TEST_F(ArmatureBoneCollections, collection_hierarchy_removal__more_complex_remove_inner_child)
{
/* Set up a slightly bigger hierarchy. Contrary to the other tests these are
* actually declared in array order. */
@ -339,7 +338,7 @@ TEST_F(ANIM_armature_bone_collections,
EXPECT_EQ(0, arm.collection_array[5]->child_count);
}
TEST_F(ANIM_armature_bone_collections, collection_hierarchy_removal__more_complex_remove_root)
TEST_F(ArmatureBoneCollections, collection_hierarchy_removal__more_complex_remove_root)
{
/* Set up a slightly bigger hierarchy. Contrary to the other tests these are
* actually declared in array order. */
@ -411,7 +410,7 @@ TEST_F(ANIM_armature_bone_collections, collection_hierarchy_removal__more_comple
EXPECT_EQ(0, arm.collection_array[6]->child_count);
}
TEST_F(ANIM_armature_bone_collections, find_parent_index)
TEST_F(ArmatureBoneCollections, find_parent_index)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "root_0");
@ -455,7 +454,7 @@ TEST_F(ANIM_armature_bone_collections, find_parent_index)
EXPECT_EQ(2, armature_bonecoll_find_parent_index(&arm, 5));
}
TEST_F(ANIM_armature_bone_collections, collection_hierarchy_visibility)
TEST_F(ArmatureBoneCollections, collection_hierarchy_visibility)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root0 = ANIM_armature_bonecoll_new(&arm, "root0");
@ -520,7 +519,7 @@ TEST_F(ANIM_armature_bone_collections, collection_hierarchy_visibility)
EXPECT_FALSE(bcoll_r0_child2->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
}
TEST_F(ANIM_armature_bone_collections, bones_assign_unassign)
TEST_F(ArmatureBoneCollections, bones_assign_unassign)
{
BoneCollection *bcoll = ANIM_armature_bonecoll_new(&arm, "collection");
@ -547,7 +546,7 @@ TEST_F(ANIM_armature_bone_collections, bones_assign_unassign)
ANIM_armature_bonecoll_remove(&arm, bcoll);
}
TEST_F(ANIM_armature_bone_collections, bones_assign_remove)
TEST_F(ArmatureBoneCollections, bones_assign_remove)
{
BoneCollection *bcoll = ANIM_armature_bonecoll_new(&arm, "collection");
@ -563,7 +562,7 @@ TEST_F(ANIM_armature_bone_collections, bones_assign_remove)
"removed";
}
TEST_F(ANIM_armature_bone_collections, active_set_clear_by_pointer)
TEST_F(ArmatureBoneCollections, active_set_clear_by_pointer)
{
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "Bones 1");
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "Bones 2");
@ -592,7 +591,7 @@ TEST_F(ANIM_armature_bone_collections, active_set_clear_by_pointer)
ANIM_bonecoll_free(bcoll3);
}
TEST_F(ANIM_armature_bone_collections, active_set_clear_by_index)
TEST_F(ArmatureBoneCollections, active_set_clear_by_index)
{
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "Bones 1");
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "Bones 2");
@ -618,7 +617,7 @@ TEST_F(ANIM_armature_bone_collections, active_set_clear_by_index)
EXPECT_STREQ("", arm.active_collection_name);
}
TEST_F(ANIM_armature_bone_collections, bcoll_is_editable)
TEST_F(ArmatureBoneCollections, bcoll_is_editable)
{
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "Bones 1");
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "Bones 2");
@ -652,7 +651,7 @@ TEST_F(ANIM_armature_bone_collections, bcoll_is_editable)
<< "Expecting local bone collection on local armature override to be editable";
}
TEST_F(ANIM_armature_bone_collections, bcoll_move_to_index__roots)
TEST_F(ArmatureBoneCollections, bcoll_move_to_index__roots)
{
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "collection");
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "collection");
@ -687,7 +686,7 @@ TEST_F(ANIM_armature_bone_collections, bcoll_move_to_index__roots)
EXPECT_EQ(arm.collection_array[3], bcoll1);
}
TEST_F(ANIM_armature_bone_collections, bcoll_move_to_index__siblings)
TEST_F(ArmatureBoneCollections, bcoll_move_to_index__siblings)
{
BoneCollection *root = ANIM_armature_bonecoll_new(&arm, "root");
BoneCollection *child0 = ANIM_armature_bonecoll_new(&arm, "child0", 0);
@ -738,7 +737,7 @@ TEST_F(ANIM_armature_bone_collections, bcoll_move_to_index__siblings)
EXPECT_STREQ(child1_0->name, arm.collection_array[4]->name);
}
TEST_F(ANIM_armature_bone_collections, bcoll_move_to_parent)
TEST_F(ArmatureBoneCollections, bcoll_move_to_parent)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "root_0");
@ -877,7 +876,7 @@ TEST_F(ANIM_armature_bone_collections, bcoll_move_to_parent)
EXPECT_EQ(0, arm.collection_array[5]->child_count);
}
TEST_F(ANIM_armature_bone_collections, bcoll_move_to_parent__root_unroot)
TEST_F(ArmatureBoneCollections, bcoll_move_to_parent__root_unroot)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "root_0");
@ -965,7 +964,7 @@ TEST_F(ANIM_armature_bone_collections, bcoll_move_to_parent__root_unroot)
// TODO: test with circular parenthood.
}
TEST_F(ANIM_armature_bone_collections, bcoll_move_to_parent__within_siblings)
TEST_F(ArmatureBoneCollections, bcoll_move_to_parent__within_siblings)
{
/* Set up a small hierarchy. */
auto bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "root_0");
@ -1218,7 +1217,7 @@ TEST_F(ANIM_armature_bone_collections, bcoll_move_to_parent__within_siblings)
EXPECT_EQ(0, arm.collection_array[6]->child_count);
}
TEST_F(ANIM_armature_bone_collections, internal__bonecolls_rotate_block)
TEST_F(ArmatureBoneCollections, internal__bonecolls_rotate_block)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "root_0");
@ -1283,7 +1282,7 @@ TEST_F(ANIM_armature_bone_collections, internal__bonecolls_rotate_block)
EXPECT_EQ(0, arm.collection_array[5]->child_index);
}
class ANIM_armature_bone_collections_testlist : public testing::Test {
class ArmatureBoneCollectionsTestList : public testing::Test {
protected:
bArmature arm;
@ -1352,7 +1351,7 @@ class ANIM_armature_bone_collections_testlist : public testing::Test {
}
};
TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__before_first_sibling)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__before_first_sibling)
{
/* Set the active index to be one of the affected bone collections. */
ANIM_armature_bonecoll_active_name_set(&arm, "child2");
@ -1368,14 +1367,14 @@ TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__before_
EXPECT_STREQ("child2", arm.active_collection_name);
}
TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__after_first_sibling)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__after_first_sibling)
{
EXPECT_EQ(2, ANIM_armature_bonecoll_move_before_after_index(&arm, 3, 1, MoveLocation::After));
EXPECT_TRUE(expect_bcolls({"root", "child0", "child2", "child1", "child1_0"}));
EXPECT_EQ(0, armature_bonecoll_find_parent_index(&arm, 2));
}
TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__before_last_sibling)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__before_last_sibling)
{
/* Set the active index to be one of the affected bone collections. */
ANIM_armature_bonecoll_active_name_set(&arm, "child1");
@ -1391,60 +1390,56 @@ TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__before_
EXPECT_STREQ("child1", arm.active_collection_name);
}
TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__after_last_sibling)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__after_last_sibling)
{
EXPECT_EQ(3, ANIM_armature_bonecoll_move_before_after_index(&arm, 1, 3, MoveLocation::After));
EXPECT_TRUE(expect_bcolls({"root", "child1", "child2", "child0", "child1_0"}));
EXPECT_EQ(0, armature_bonecoll_find_parent_index(&arm, 3));
}
TEST_F(ANIM_armature_bone_collections_testlist,
move_before_after_index__other_parent_before__move_left)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__other_parent_before__move_left)
{
EXPECT_EQ(1, ANIM_armature_bonecoll_move_before_after_index(&arm, 4, 1, MoveLocation::Before));
EXPECT_TRUE(expect_bcolls({"root", "child1_0", "child0", "child1", "child2"}));
EXPECT_EQ(0, armature_bonecoll_find_parent_index(&arm, 1));
}
TEST_F(ANIM_armature_bone_collections_testlist,
move_before_after_index__other_parent_after__move_left)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__other_parent_after__move_left)
{
EXPECT_EQ(2, ANIM_armature_bonecoll_move_before_after_index(&arm, 4, 1, MoveLocation::After));
EXPECT_TRUE(expect_bcolls({"root", "child0", "child1_0", "child1", "child2"}));
EXPECT_EQ(0, armature_bonecoll_find_parent_index(&arm, 2));
}
TEST_F(ANIM_armature_bone_collections_testlist,
move_before_after_index__other_parent_before__move_right)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__other_parent_before__move_right)
{
EXPECT_EQ(3, ANIM_armature_bonecoll_move_before_after_index(&arm, 1, 4, MoveLocation::Before));
EXPECT_TRUE(expect_bcolls({"root", "child1", "child2", "child0", "child1_0"}));
EXPECT_EQ(1, armature_bonecoll_find_parent_index(&arm, 3));
}
TEST_F(ANIM_armature_bone_collections_testlist,
move_before_after_index__other_parent_after__move_right)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__other_parent_after__move_right)
{
EXPECT_EQ(4, ANIM_armature_bonecoll_move_before_after_index(&arm, 1, 4, MoveLocation::After));
EXPECT_TRUE(expect_bcolls({"root", "child1", "child2", "child1_0", "child0"}));
EXPECT_EQ(1, armature_bonecoll_find_parent_index(&arm, 4));
}
TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__to_root__before)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__to_root__before)
{
EXPECT_EQ(0, ANIM_armature_bonecoll_move_before_after_index(&arm, 4, 0, MoveLocation::Before));
EXPECT_TRUE(expect_bcolls({"child1_0", "root", "child0", "child1", "child2"}));
EXPECT_EQ(-1, armature_bonecoll_find_parent_index(&arm, 0));
}
TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__to_root__after)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__to_root__after)
{
EXPECT_EQ(1, ANIM_armature_bonecoll_move_before_after_index(&arm, 4, 0, MoveLocation::After));
EXPECT_TRUE(expect_bcolls({"root", "child1_0", "child0", "child1", "child2"}));
EXPECT_EQ(-1, armature_bonecoll_find_parent_index(&arm, 1));
}
TEST_F(ANIM_armature_bone_collections_testlist, child_number_set__roots)
TEST_F(ArmatureBoneCollectionsTestList, child_number_set__roots)
{
/* Test with only one root. */
EXPECT_EQ(0, armature_bonecoll_child_number_set(&arm, root, 0));
@ -1474,7 +1469,7 @@ TEST_F(ANIM_armature_bone_collections_testlist, child_number_set__roots)
EXPECT_TRUE(expect_bcolls({"root1", "root2", "root", "child0", "child1", "child2", "child1_0"}));
}
TEST_F(ANIM_armature_bone_collections_testlist, child_number_set__siblings)
TEST_F(ArmatureBoneCollectionsTestList, child_number_set__siblings)
{
/* Move child0 to itself. */
EXPECT_EQ(1, armature_bonecoll_child_number_set(&arm, child0, 0));
@ -1497,8 +1492,36 @@ TEST_F(ANIM_armature_bone_collections_testlist, child_number_set__siblings)
EXPECT_TRUE(expect_bcolls({"root", "child1", "child2", "child0", "child1_0"}));
}
class ANIM_armature_bone_collections_liboverrides
: public ANIM_armature_bone_collections_testlist {
TEST_F(ArmatureBoneCollectionsTestList, bone_collection_solo)
{
EXPECT_FALSE(arm.flag & ARM_BCOLL_SOLO_ACTIVE) << "By default no solo'ing should be active";
/* Enable solo. */
EXPECT_FALSE(child1->flags & BONE_COLLECTION_SOLO);
ANIM_armature_bonecoll_solo_set(&arm, child1, true);
EXPECT_TRUE(child1->flags & BONE_COLLECTION_SOLO);
EXPECT_TRUE(arm.flag & ARM_BCOLL_SOLO_ACTIVE);
/* Enable solo on another bone collection. */
EXPECT_FALSE(child1_0->flags & BONE_COLLECTION_SOLO);
ANIM_armature_bonecoll_solo_set(&arm, child1_0, true);
EXPECT_TRUE(child1_0->flags & BONE_COLLECTION_SOLO);
EXPECT_TRUE(arm.flag & ARM_BCOLL_SOLO_ACTIVE);
/* Disable the first solo flag. */
EXPECT_TRUE(child1->flags & BONE_COLLECTION_SOLO);
ANIM_armature_bonecoll_solo_set(&arm, child1, false);
EXPECT_FALSE(child1->flags & BONE_COLLECTION_SOLO);
EXPECT_TRUE(arm.flag & ARM_BCOLL_SOLO_ACTIVE);
/* Disable the second solo flag. This should also disable the ARM_BCOLL_SOLO_ACTIVE flag. */
EXPECT_TRUE(child1_0->flags & BONE_COLLECTION_SOLO);
ANIM_armature_bonecoll_solo_set(&arm, child1_0, false);
EXPECT_FALSE(child1_0->flags & BONE_COLLECTION_SOLO);
EXPECT_FALSE(arm.flag & ARM_BCOLL_SOLO_ACTIVE);
}
class ArmatureBoneCollectionsLiboverrides : public ArmatureBoneCollectionsTestList {
protected:
bArmature dst_arm;
@ -1510,7 +1533,7 @@ class ANIM_armature_bone_collections_liboverrides
void SetUp() override
{
ANIM_armature_bone_collections_testlist::SetUp();
ArmatureBoneCollectionsTestList::SetUp();
/* TODO: make this clone `arm` into `dst_arm`, instead of assuming the below
* code is still in sync with the super-class. */
@ -1535,12 +1558,12 @@ class ANIM_armature_bone_collections_liboverrides
void TearDown() override
{
ANIM_armature_bone_collections_testlist::TearDown();
ArmatureBoneCollectionsTestList::TearDown();
BKE_libblock_free_datablock(&dst_arm.id, 0);
}
};
TEST_F(ANIM_armature_bone_collections_liboverrides, bcoll_insert_copy_after)
TEST_F(ArmatureBoneCollectionsLiboverrides, bcoll_insert_copy_after)
{
/* Mimic that a new root, two children, and two grandchildren were added via library overrides.
* These were saved in `arm`, and now need to be copied into `dst_arm`. */

View File

@ -77,13 +77,12 @@ uint64_t BoneColor::hash() const
/* For custom colors, hash everything together. */
/* The last byte of the color is skipped, as it is inconsistent (see note above). */
const uint64_t hash_solid = get_default_hash_3(
custom.solid[0], custom.solid[1], custom.solid[2]);
const uint64_t hash_select = get_default_hash_3(
const uint64_t hash_solid = get_default_hash(custom.solid[0], custom.solid[1], custom.solid[2]);
const uint64_t hash_select = get_default_hash(
custom.select[0], custom.select[1], custom.select[2]);
const uint64_t hash_active = get_default_hash_3(
const uint64_t hash_active = get_default_hash(
custom.active[0], custom.active[1], custom.active[2]);
return get_default_hash_4(hash_solid, hash_select, hash_active, custom.flag);
return get_default_hash(hash_solid, hash_select, hash_active, custom.flag);
}
const BoneColor &ANIM_bonecolor_posebone_get(const bPoseChannel *pose_bone)

View File

@ -1328,6 +1328,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_GET_NAMED_GRID 2121
#define GEO_NODE_STORE_NAMED_GRID 2122
#define GEO_NODE_SORT_ELEMENTS 2123
#define GEO_NODE_MENU_SWITCH 2124
/** \} */

View File

@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <string>
#include "BLI_implicit_sharing.hh"
#include "BLI_vector.hh"
namespace blender::bke {
/* Flags for #bNodeSocketValueMenu. */
enum NodeSocketValueMenuRuntimeFlag {
/* Socket has conflicting menu connections and cannot resolve items. */
NODE_MENU_ITEMS_CONFLICT = (1 << 0),
};
/* -------------------------------------------------------------------- */
/** \name Runtime enum items list.
* \{ */
/**
* Runtime copy of #NodeEnumItem for use in #RuntimeNodeEnumItems.
*/
struct RuntimeNodeEnumItem {
std::string name;
std::string description;
/* Immutable unique identifier. */
int identifier;
};
/**
* Shared immutable list of enum items.
* These are owned by a node and can be referenced by node sockets.
*/
struct RuntimeNodeEnumItems : ImplicitSharingMixin {
Vector<RuntimeNodeEnumItem> items;
void delete_self() override
{
delete this;
}
};
/** \} */
} // namespace blender::bke

View File

@ -158,6 +158,7 @@ static const bNodeSocketStaticTypeInfo node_socket_subtypes[] = {
{"NodeSocketCollection", "NodeTreeInterfaceSocketCollection", SOCK_COLLECTION, PROP_NONE},
{"NodeSocketTexture", "NodeTreeInterfaceSocketTexture", SOCK_TEXTURE, PROP_NONE},
{"NodeSocketMaterial", "NodeTreeInterfaceSocketMaterial", SOCK_MATERIAL, PROP_NONE},
{"NodeSocketMenu", "NodeTreeInterfaceSocketMenu", SOCK_MENU, PROP_NONE},
};
template<typename Fn> bool socket_data_to_static_type(const eNodeSocketDatatype type, const Fn &fn)
@ -199,6 +200,9 @@ template<typename Fn> bool socket_data_to_static_type(const eNodeSocketDatatype
case SOCK_MATERIAL:
fn.template operator()<bNodeSocketValueMaterial>();
return true;
case SOCK_MENU:
fn.template operator()<bNodeSocketValueMenu>();
return true;
case SOCK_CUSTOM:
case SOCK_SHADER:

View File

@ -285,7 +285,7 @@ void BKE_pbvh_node_mark_update_face_sets(PBVHNode *node);
void BKE_pbvh_node_mark_update_visibility(PBVHNode *node);
void BKE_pbvh_node_mark_rebuild_draw(PBVHNode *node);
void BKE_pbvh_node_mark_redraw(PBVHNode *node);
void BKE_pbvh_node_mark_normals_update(PBVHNode *node);
void BKE_pbvh_node_mark_positions_update(PBVHNode *node);
void BKE_pbvh_node_mark_topology_update(PBVHNode *node);
void BKE_pbvh_node_fully_hidden_set(PBVHNode *node, int fully_hidden);
bool BKE_pbvh_node_fully_hidden_get(const PBVHNode *node);

View File

@ -232,6 +232,7 @@ set(SRC
intern/multires_versioning.cc
intern/nla.cc
intern/node.cc
intern/node_enum_definition.cc
intern/node_runtime.cc
intern/node_socket_value.cc
intern/node_tree_anonymous_attributes.cc
@ -456,6 +457,7 @@ set(SRC
BKE_nla.h
BKE_node.h
BKE_node.hh
BKE_node_enum.hh
BKE_node_runtime.hh
BKE_node_socket_value.hh
BKE_node_tree_anonymous_attributes.hh

View File

@ -3182,9 +3182,13 @@ bool BoneCollection::is_visible_ancestors() const
{
return this->flags & BONE_COLLECTION_ANCESTORS_VISIBLE;
}
bool BoneCollection::is_visible_effectively() const
bool BoneCollection::is_visible_with_ancestors() const
{
return this->is_visible() && this->is_visible_ancestors();
}
bool BoneCollection::is_solo() const
{
return this->flags & BONE_COLLECTION_SOLO;
}
/** \} */

View File

@ -1060,7 +1060,7 @@ static bool data_transfer_layersmapping_generate(ListBase *r_map,
dst_data = static_cast<float3 *>(CustomData_add_layer(
&me_dst->corner_data, CD_NORMAL, CD_SET_DEFAULT, me_dst->corners_num));
}
if (mix_factor != 1.0f) {
if (mix_factor != 1.0f || mix_weights) {
MutableSpan(dst_data, me_dst->corners_num).copy_from(me_dst->corner_normals());
}
/* Post-process will convert it back to CD_CUSTOMLOOPNORMAL. */

View File

@ -431,7 +431,7 @@ std::string AttributeFieldInput::socket_inspection_name() const
uint64_t AttributeFieldInput::hash() const
{
return get_default_hash_2(name_, type_);
return get_default_hash(name_, type_);
}
bool AttributeFieldInput::is_equal_to(const fn::FieldNode &other) const
@ -513,7 +513,7 @@ std::string AnonymousAttributeFieldInput::socket_inspection_name() const
uint64_t AnonymousAttributeFieldInput::hash() const
{
return get_default_hash_2(anonymous_id_.get(), type_);
return get_default_hash(anonymous_id_.get(), type_);
}
bool AnonymousAttributeFieldInput::is_equal_to(const fn::FieldNode &other) const
@ -575,7 +575,7 @@ GVArray NamedLayerSelectionFieldInput::get_varray_for_context(
uint64_t NamedLayerSelectionFieldInput::hash() const
{
return get_default_hash_2(layer_name_, type_);
return get_default_hash(layer_name_, type_);
}
bool NamedLayerSelectionFieldInput::is_equal_to(const fn::FieldNode &other) const

View File

@ -57,6 +57,7 @@ static size_t idp_size_table[] = {
sizeof(double), /* #IDP_DOUBLE */
0, /* #IDP_IDPARRAY (no fixed size). */
sizeof(int8_t), /* #IDP_BOOLEAN */
sizeof(int), /* #IDP_ENUM */
};
/* -------------------------------------------------------------------- */

View File

@ -343,6 +343,11 @@ void Mesh::tag_sharpness_changed()
this->runtime->corner_normals_cache.tag_dirty();
}
void Mesh::tag_custom_normals_changed()
{
this->runtime->corner_normals_cache.tag_dirty();
}
void Mesh::tag_face_winding_changed()
{
this->runtime->vert_normals_cache.tag_dirty();

View File

@ -67,6 +67,7 @@
#include "BKE_lib_query.hh"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_anonymous_attributes.hh"
#include "BKE_node_tree_interface.hh"
@ -357,6 +358,7 @@ static void library_foreach_node_socket(LibraryForeachIDData *data, bNodeSocket
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_GEOMETRY:
case SOCK_MENU:
break;
}
}
@ -701,6 +703,10 @@ static void write_node_socket_default_value(BlendWriter *writer, const bNodeSock
case SOCK_ROTATION:
BLO_write_struct(writer, bNodeSocketValueRotation, sock->default_value);
break;
case SOCK_MENU: {
BLO_write_struct(writer, bNodeSocketValueMenu, sock->default_value);
break;
}
case SOCK_CUSTOM:
/* Custom node sockets where default_value is defined uses custom properties for storage. */
break;
@ -853,6 +859,17 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
if (node->type == GEO_NODE_BAKE) {
blender::nodes::BakeItemsAccessor::blend_write(writer, *node);
}
if (node->type == GEO_NODE_MENU_SWITCH) {
const NodeMenuSwitch &storage = *static_cast<const NodeMenuSwitch *>(node->storage);
BLO_write_struct_array(writer,
NodeEnumItem,
storage.enum_definition.items_num,
storage.enum_definition.items_array);
for (const NodeEnumItem &item : storage.enum_definition.items()) {
BLO_write_string(writer, item.name);
BLO_write_string(writer, item.description);
}
}
}
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
@ -921,6 +938,7 @@ static bool is_node_socket_supported(const bNodeSocket *sock)
case SOCK_TEXTURE:
case SOCK_MATERIAL:
case SOCK_ROTATION:
case SOCK_MENU:
return true;
}
return false;
@ -937,6 +955,18 @@ static void direct_link_node_socket(BlendDataReader *reader, bNodeSocket *sock)
BLO_read_data_address(reader, &sock->default_value);
BLO_read_data_address(reader, &sock->default_attribute_name);
sock->runtime = MEM_new<bNodeSocketRuntime>(__func__);
switch (eNodeSocketDatatype(sock->type)) {
case SOCK_MENU: {
bNodeSocketValueMenu &default_value = *sock->default_value_typed<bNodeSocketValueMenu>();
/* Clear runtime data. */
default_value.enum_items = nullptr;
default_value.runtime_flag = 0;
break;
}
default:
break;
}
}
static void direct_link_node_socket_list(BlendDataReader *reader, ListBase *socket_list)
@ -1099,6 +1129,15 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree)
blender::nodes::BakeItemsAccessor::blend_read_data(reader, *node);
break;
}
case GEO_NODE_MENU_SWITCH: {
NodeMenuSwitch &storage = *static_cast<NodeMenuSwitch *>(node->storage);
BLO_read_data_address(reader, &storage.enum_definition.items_array);
for (const NodeEnumItem &item : storage.enum_definition.items()) {
BLO_read_data_address(reader, &item.name);
BLO_read_data_address(reader, &item.description);
}
break;
}
default:
break;
@ -1826,6 +1865,7 @@ static void socket_id_user_increment(bNodeSocket *sock)
case SOCK_ROTATION:
case SOCK_INT:
case SOCK_STRING:
case SOCK_MENU:
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_GEOMETRY:
@ -1872,6 +1912,7 @@ static bool socket_id_user_decrement(bNodeSocket *sock)
case SOCK_ROTATION:
case SOCK_INT:
case SOCK_STRING:
case SOCK_MENU:
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_GEOMETRY:
@ -1931,6 +1972,7 @@ void nodeModifySocketType(bNodeTree *ntree,
case SOCK_COLLECTION:
case SOCK_TEXTURE:
case SOCK_MATERIAL:
case SOCK_MENU:
break;
}
}
@ -2065,6 +2107,8 @@ const char *nodeStaticSocketType(const int type, const int subtype)
return "NodeSocketTexture";
case SOCK_MATERIAL:
return "NodeSocketMaterial";
case SOCK_MENU:
return "NodeSocketMenu";
case SOCK_CUSTOM:
break;
}
@ -2146,6 +2190,8 @@ const char *nodeStaticSocketInterfaceTypeNew(const int type, const int subtype)
return "NodeTreeInterfaceSocketTexture";
case SOCK_MATERIAL:
return "NodeTreeInterfaceSocketMaterial";
case SOCK_MENU:
return "NodeTreeInterfaceSocketMenu";
case SOCK_CUSTOM:
break;
}
@ -2183,6 +2229,8 @@ const char *nodeStaticSocketLabel(const int type, const int /*subtype*/)
return "Texture";
case SOCK_MATERIAL:
return "Material";
case SOCK_MENU:
return "Menu";
case SOCK_CUSTOM:
break;
}
@ -2222,6 +2270,13 @@ static void node_socket_free(bNodeSocket *sock, const bool do_id_user)
if (do_id_user) {
socket_id_user_decrement(sock);
}
if (sock->type == SOCK_MENU) {
auto &default_value_menu = *sock->default_value_typed<bNodeSocketValueMenu>();
if (default_value_menu.enum_items) {
/* Release shared data pointer. */
default_value_menu.enum_items->remove_user_and_delete_if_last();
}
}
MEM_freeN(sock->default_value);
}
if (sock->default_attribute_name) {
@ -2551,6 +2606,14 @@ static void node_socket_copy(bNodeSocket *sock_dst, const bNodeSocket *sock_src,
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
socket_id_user_increment(sock_dst);
}
if (sock_src->type == SOCK_MENU) {
auto &default_value_menu = *sock_dst->default_value_typed<bNodeSocketValueMenu>();
if (default_value_menu.enum_items) {
/* Copy of shared data pointer. */
default_value_menu.enum_items->add_user();
}
}
}
sock_dst->default_attribute_name = static_cast<char *>(
@ -2691,6 +2754,8 @@ static void *socket_value_storage(bNodeSocket &socket)
return &socket.default_value_typed<bNodeSocketValueMaterial>()->value;
case SOCK_ROTATION:
return &socket.default_value_typed<bNodeSocketValueRotation>()->value_euler;
case SOCK_MENU:
return &socket.default_value_typed<bNodeSocketValueMenu>()->value;
case SOCK_STRING:
/* We don't want do this now! */
return nullptr;

View File

@ -0,0 +1,149 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string.h"
#include "BLI_string_utils.hh"
#include "DNA_array_utils.hh"
#include "DNA_node_types.h"
#include "BKE_node.h"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
using blender::bke::NodeSocketValueMenuRuntimeFlag;
bool bNodeSocketValueMenu::has_conflict() const
{
return this->runtime_flag & NodeSocketValueMenuRuntimeFlag::NODE_MENU_ITEMS_CONFLICT;
}
blender::Span<NodeEnumItem> NodeEnumDefinition::items() const
{
return {this->items_array, this->items_num};
}
blender::MutableSpan<NodeEnumItem> NodeEnumDefinition::items_for_write()
{
return {this->items_array, this->items_num};
}
NodeEnumItem *NodeEnumDefinition::add_item(blender::StringRef name)
{
const int insert_index = this->items_num;
NodeEnumItem *old_items = this->items_array;
this->items_array = MEM_cnew_array<NodeEnumItem>(this->items_num + 1, __func__);
std::copy_n(old_items, insert_index, this->items_array);
NodeEnumItem &new_item = this->items_array[insert_index];
std::copy_n(old_items + insert_index + 1,
this->items_num - insert_index,
this->items_array + insert_index + 1);
new_item.identifier = this->next_identifier++;
this->set_item_name(new_item, name);
this->items_num++;
MEM_SAFE_FREE(old_items);
return &new_item;
}
static void free_enum_item(NodeEnumItem *item)
{
MEM_SAFE_FREE(item->name);
MEM_SAFE_FREE(item->description);
}
bool NodeEnumDefinition::remove_item(NodeEnumItem &item)
{
if (!this->items().contains_ptr(&item)) {
return false;
}
const int remove_index = &item - this->items().begin();
/* DNA fields are 16 bits, can't use directly. */
int items_num = this->items_num;
int active_index = this->active_index;
blender::dna::array::remove_index(
&this->items_array, &items_num, &active_index, remove_index, free_enum_item);
this->items_num = int16_t(items_num);
this->active_index = int16_t(active_index);
return true;
}
void NodeEnumDefinition::clear()
{
/* DNA fields are 16 bits, can't use directly. */
int items_num = this->items_num;
int active_index = this->active_index;
blender::dna::array::clear(&this->items_array, &items_num, &active_index, free_enum_item);
this->items_num = int16_t(items_num);
this->active_index = int16_t(active_index);
}
bool NodeEnumDefinition::move_item(uint16_t from_index, uint16_t to_index)
{
if (to_index < this->items_num) {
int items_num = this->items_num;
int active_index = this->active_index;
blender::dna::array::move_index(this->items_array, items_num, from_index, to_index);
this->items_num = int16_t(items_num);
this->active_index = int16_t(active_index);
}
return true;
}
const NodeEnumItem *NodeEnumDefinition::active_item() const
{
if (blender::IndexRange(this->items_num).contains(this->active_index)) {
return &this->items()[this->active_index];
}
return nullptr;
}
NodeEnumItem *NodeEnumDefinition::active_item()
{
if (blender::IndexRange(this->items_num).contains(this->active_index)) {
return &this->items_for_write()[this->active_index];
}
return nullptr;
}
void NodeEnumDefinition::active_item_set(NodeEnumItem *item)
{
this->active_index = this->items().contains_ptr(item) ? item - this->items_array : -1;
}
void NodeEnumDefinition::set_item_name(NodeEnumItem &item, blender::StringRef name)
{
char unique_name[MAX_NAME + 4];
STRNCPY(unique_name, name.data());
struct Args {
NodeEnumDefinition *enum_def;
const NodeEnumItem *item;
} args = {this, &item};
const char *default_name = items().is_empty() ? "Name" : items().last().name;
BLI_uniquename_cb(
[](void *arg, const char *name) {
const Args &args = *static_cast<Args *>(arg);
for (const NodeEnumItem &item : args.enum_def->items()) {
if (&item != args.item) {
if (STREQ(item.name, name)) {
return true;
}
}
}
return false;
},
&args,
default_name,
'.',
unique_name,
ARRAY_SIZE(unique_name));
MEM_SAFE_FREE(item.name);
item.name = BLI_strdup(unique_name);
}

View File

@ -6,6 +6,7 @@
#include "BKE_lib_id.hh"
#include "BKE_lib_query.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_tree_interface.hh"
#include "BLI_math_vector.h"
@ -171,6 +172,12 @@ template<> void socket_data_init_impl(bNodeSocketValueMaterial &data)
{
data.value = nullptr;
}
template<> void socket_data_init_impl(bNodeSocketValueMenu &data)
{
data.value = -1;
data.enum_items = nullptr;
data.runtime_flag = 0;
}
static void *make_socket_data(const StringRef socket_type)
{
@ -191,6 +198,13 @@ static void *make_socket_data(const StringRef socket_type)
* \{ */
template<typename T> void socket_data_free_impl(T & /*data*/, const bool /*do_id_user*/) {}
template<> void socket_data_free_impl(bNodeSocketValueMenu &dst, const bool /*do_id_user*/)
{
if (dst.enum_items) {
/* Release shared data pointer. */
dst.enum_items->remove_user_and_delete_if_last();
}
}
static void socket_data_free(bNodeTreeInterfaceSocket &socket, const bool do_id_user)
{
@ -210,6 +224,14 @@ static void socket_data_free(bNodeTreeInterfaceSocket &socket, const bool do_id_
* \{ */
template<typename T> void socket_data_copy_impl(T & /*dst*/, const T & /*src*/) {}
template<>
void socket_data_copy_impl(bNodeSocketValueMenu &dst, const bNodeSocketValueMenu & /*src*/)
{
/* Copy of shared data pointer. */
if (dst.enum_items) {
dst.enum_items->add_user();
}
}
static void socket_data_copy(bNodeTreeInterfaceSocket &dst,
const bNodeTreeInterfaceSocket &src,
@ -304,6 +326,10 @@ inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueMaterial
{
BLO_write_struct(writer, bNodeSocketValueMaterial, &data);
}
inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueMenu &data)
{
BLO_write_struct(writer, bNodeSocketValueMenu, &data);
}
static void socket_data_write(BlendWriter *writer, bNodeTreeInterfaceSocket &socket)
{
@ -323,6 +349,13 @@ template<typename T> void socket_data_read_data_impl(BlendDataReader *reader, T
{
BLO_read_data_address(reader, data);
}
template<> void socket_data_read_data_impl(BlendDataReader *reader, bNodeSocketValueMenu **data)
{
BLO_read_data_address(reader, data);
/* Clear runtime data. */
(*data)->enum_items = nullptr;
(*data)->runtime_flag = 0;
}
static void socket_data_read_data(BlendDataReader *reader, bNodeTreeInterfaceSocket &socket)
{

View File

@ -19,6 +19,7 @@
#include "BKE_image.h"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_anonymous_attributes.hh"
#include "BKE_node_tree_update.hh"
@ -485,6 +486,9 @@ class NodeTreeMainUpdater {
this->propagate_runtime_flags(ntree);
if (ntree.type == NTREE_GEOMETRY) {
if (this->propagate_enum_definitions(ntree)) {
result.interface_changed = true;
}
if (node_field_inferencing::update_field_inferencing(ntree)) {
result.interface_changed = true;
}
@ -799,6 +803,209 @@ class NodeTreeMainUpdater {
}
}
bool propagate_enum_definitions(bNodeTree &ntree)
{
ntree.ensure_interface_cache();
/* Propagation from right to left to determine which enum
* definition to use for menu sockets. */
for (bNode *node : ntree.toposort_right_to_left()) {
const bool node_updated = this->should_update_individual_node(ntree, *node);
if (node->typeinfo->type == GEO_NODE_MENU_SWITCH) {
/* Generate new enum items when the node has changed, otherwise keep existing items. */
if (node_updated) {
const NodeMenuSwitch &storage = *static_cast<NodeMenuSwitch *>(node->storage);
const RuntimeNodeEnumItems *enum_items = this->create_runtime_enum_items(
storage.enum_definition);
bNodeSocket &input = *node->input_sockets()[0];
BLI_assert(input.is_available() && input.type == SOCK_MENU);
this->set_enum_ptr(*input.default_value_typed<bNodeSocketValueMenu>(), enum_items);
}
continue;
}
else {
/* Clear current enum references. */
for (bNodeSocket *socket : node->input_sockets()) {
if (socket->is_available() && socket->type == SOCK_MENU) {
clear_enum_reference(*socket);
}
}
for (bNodeSocket *socket : node->output_sockets()) {
if (socket->is_available() && socket->type == SOCK_MENU) {
clear_enum_reference(*socket);
}
}
}
/* Propagate enum references from output links. */
for (bNodeSocket *output : node->output_sockets()) {
if (output->is_available() && output->type == SOCK_MENU) {
for (const bNodeSocket *input : output->directly_linked_sockets()) {
this->update_socket_enum_definition(
*output->default_value_typed<bNodeSocketValueMenu>(),
*input->default_value_typed<bNodeSocketValueMenu>());
}
}
}
if (node->is_group()) {
/* Node groups expose internal enum definitions. */
if (node->id == nullptr) {
continue;
}
const bNodeTree *group_tree = reinterpret_cast<bNodeTree *>(node->id);
group_tree->ensure_interface_cache();
for (const int socket_i : group_tree->interface_inputs().index_range()) {
bNodeSocket &input = *node->input_sockets()[socket_i];
const bNodeTreeInterfaceSocket &iosocket = *group_tree->interface_inputs()[socket_i];
BLI_assert(STREQ(input.identifier, iosocket.identifier));
if (input.is_available() && input.type == SOCK_MENU) {
BLI_assert(STREQ(iosocket.socket_type, "NodeSocketMenu"));
this->update_socket_enum_definition(
*input.default_value_typed<bNodeSocketValueMenu>(),
*static_cast<bNodeSocketValueMenu *>(iosocket.socket_data));
}
}
}
else if (node->type == GEO_NODE_MENU_SWITCH) {
/* First input is always the node's own menu, propagate only to the enum case inputs. */
const bNodeSocket *output = node->output_sockets().first();
for (bNodeSocket *input : node->input_sockets().drop_front(1)) {
if (input->is_available() && input->type == SOCK_MENU) {
this->update_socket_enum_definition(
*input->default_value_typed<bNodeSocketValueMenu>(),
*output->default_value_typed<bNodeSocketValueMenu>());
}
}
}
else {
/* Propagate over internal relations. */
/* XXX Placeholder implementation just propagates all outputs
* to all inputs for built-in nodes This could perhaps use
* input/output relations to handle propagation generically? */
for (bNodeSocket *input : node->input_sockets()) {
if (input->is_available() && input->type == SOCK_MENU) {
for (const bNodeSocket *output : node->output_sockets()) {
if (output->is_available() && output->type == SOCK_MENU) {
this->update_socket_enum_definition(
*input->default_value_typed<bNodeSocketValueMenu>(),
*output->default_value_typed<bNodeSocketValueMenu>());
}
}
}
}
}
}
/* Build list of new enum items for the node tree interface. */
Vector<bNodeSocketValueMenu> interface_enum_items(ntree.interface_inputs().size(), {0});
for (const bNode *group_input_node : ntree.group_input_nodes()) {
for (const int socket_i : ntree.interface_inputs().index_range()) {
const bNodeSocket &output = *group_input_node->output_sockets()[socket_i];
if (output.is_available() && output.type == SOCK_MENU) {
this->update_socket_enum_definition(interface_enum_items[socket_i],
*output.default_value_typed<bNodeSocketValueMenu>());
}
}
}
/* Move enum items to the interface and detect if anything changed. */
bool changed = false;
for (const int socket_i : ntree.interface_inputs().index_range()) {
bNodeTreeInterfaceSocket &iosocket = *ntree.interface_inputs()[socket_i];
if (STREQ(iosocket.socket_type, "NodeSocketMenu")) {
bNodeSocketValueMenu &dst = *static_cast<bNodeSocketValueMenu *>(iosocket.socket_data);
const bNodeSocketValueMenu &src = interface_enum_items[socket_i];
if (dst.enum_items != src.enum_items || dst.has_conflict() != src.has_conflict()) {
changed = true;
if (dst.enum_items) {
dst.enum_items->remove_user_and_delete_if_last();
}
/* Items are moved, no need to change user count. */
dst.enum_items = src.enum_items;
SET_FLAG_FROM_TEST(dst.runtime_flag, src.has_conflict(), NODE_MENU_ITEMS_CONFLICT);
}
}
}
return changed;
}
/**
* Make a runtime copy of the DNA enum items.
* The runtime items list is shared by sockets.
*/
const RuntimeNodeEnumItems *create_runtime_enum_items(const NodeEnumDefinition &enum_def)
{
RuntimeNodeEnumItems *enum_items = new RuntimeNodeEnumItems();
enum_items->items.reinitialize(enum_def.items_num);
for (const int i : enum_def.items().index_range()) {
const NodeEnumItem &src = enum_def.items()[i];
RuntimeNodeEnumItem &dst = enum_items->items[i];
dst.identifier = src.identifier;
dst.name = src.name ? src.name : "";
dst.description = src.description ? src.description : "";
}
return enum_items;
}
void clear_enum_reference(bNodeSocket &socket)
{
BLI_assert(socket.is_available() && socket.type == SOCK_MENU);
bNodeSocketValueMenu &default_value = *socket.default_value_typed<bNodeSocketValueMenu>();
this->reset_enum_ptr(default_value);
default_value.runtime_flag &= ~NODE_MENU_ITEMS_CONFLICT;
}
void update_socket_enum_definition(bNodeSocketValueMenu &dst, const bNodeSocketValueMenu &src)
{
if (dst.has_conflict()) {
/* Target enum already has a conflict. */
BLI_assert(dst.enum_items == nullptr);
return;
}
if (src.has_conflict()) {
/* Target conflict if any source enum has a conflict. */
this->reset_enum_ptr(dst);
dst.runtime_flag |= NODE_MENU_ITEMS_CONFLICT;
}
else if (!dst.enum_items) {
/* First connection, set the reference. */
this->set_enum_ptr(dst, src.enum_items);
}
else if (src.enum_items && dst.enum_items != src.enum_items) {
/* Error if enum ref does not match other connections. */
this->reset_enum_ptr(dst);
dst.runtime_flag |= NODE_MENU_ITEMS_CONFLICT;
}
}
void reset_enum_ptr(bNodeSocketValueMenu &dst)
{
if (dst.enum_items) {
dst.enum_items->remove_user_and_delete_if_last();
dst.enum_items = nullptr;
}
}
void set_enum_ptr(bNodeSocketValueMenu &dst, const RuntimeNodeEnumItems *enum_items)
{
if (dst.enum_items) {
dst.enum_items->remove_user_and_delete_if_last();
dst.enum_items = nullptr;
}
if (enum_items) {
enum_items->add_user();
dst.enum_items = enum_items;
}
}
void update_link_validation(bNodeTree &ntree)
{
const Span<const bNode *> toposort = ntree.toposort_left_to_right();
@ -810,12 +1017,24 @@ class NodeTreeMainUpdater {
toposort_indices[node.index()] = i;
}
/* Tests if enum references are undefined. */
const auto is_invalid_enum_ref = [](const bNodeSocket &socket) -> bool {
if (socket.type == SOCK_MENU) {
return socket.default_value_typed<bNodeSocketValueMenu>()->enum_items == nullptr;
}
return false;
};
LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
link->flag |= NODE_LINK_VALID;
if (!link->fromsock->is_available() || !link->tosock->is_available()) {
link->flag &= ~NODE_LINK_VALID;
continue;
}
if (is_invalid_enum_ref(*link->fromsock) || is_invalid_enum_ref(*link->tosock)) {
link->flag &= ~NODE_LINK_VALID;
continue;
}
const bNode &from_node = *link->fromnode;
const bNode &to_node = *link->tonode;
if (toposort_indices[from_node.index()] > toposort_indices[to_node.index()]) {
@ -837,8 +1056,8 @@ class NodeTreeMainUpdater {
{
tree.ensure_topology_cache();
/* Compute a hash that represents the node topology connected to the output. This always has to
* be updated even if it is not used to detect changes right now. Otherwise
/* Compute a hash that represents the node topology connected to the output. This always has
* to be updated even if it is not used to detect changes right now. Otherwise
* #btree.runtime.output_topology_hash will go out of date. */
const Vector<const bNodeSocket *> tree_output_sockets = this->find_output_sockets(tree);
const uint32_t old_topology_hash = tree.runtime->output_topology_hash;
@ -852,8 +1071,8 @@ class NodeTreeMainUpdater {
* be used without causing updates all the time currently. In the future we could try to
* handle other drivers better as well.
* Note that this optimization only works in practice when the depsgraph didn't also get a
* copy-on-write tag for the node tree (which happens when changing node properties). It does
* work in a few situations like adding reroutes and duplicating nodes though. */
* copy-on-write tag for the node tree (which happens when changing node properties). It
* does work in a few situations like adding reroutes and duplicating nodes though. */
LISTBASE_FOREACH (const FCurve *, fcurve, &adt->drivers) {
const ChannelDriver *driver = fcurve->driver;
const StringRef expression = driver->expression;
@ -876,7 +1095,8 @@ class NodeTreeMainUpdater {
return true;
}
/* The topology hash can only be used when only topology-changing operations have been done. */
/* The topology hash can only be used when only topology-changing operations have been done.
*/
if (tree.runtime->changed_flag ==
(tree.runtime->changed_flag & (NTREE_CHANGED_LINK | NTREE_CHANGED_REMOVED_NODE)))
{
@ -929,15 +1149,15 @@ class NodeTreeMainUpdater {
}
/**
* Computes a hash that changes when the node tree topology connected to an output node changes.
* Adding reroutes does not have an effect on the hash.
* Computes a hash that changes when the node tree topology connected to an output node
* changes. Adding reroutes does not have an effect on the hash.
*/
uint32_t get_combined_socket_topology_hash(const bNodeTree &tree,
Span<const bNodeSocket *> sockets)
{
if (tree.has_available_link_cycle()) {
/* Return dummy value when the link has any cycles. The algorithm below could be improved to
* handle cycles more gracefully. */
/* Return dummy value when the link has any cycles. The algorithm below could be improved
* to handle cycles more gracefully. */
return 0;
}
Array<uint32_t> hashes = this->get_socket_topology_hashes(tree, sockets);
@ -1121,8 +1341,8 @@ class NodeTreeMainUpdater {
}
}
}
/* The Normal node has a special case, because the value stored in the first output socket
* is used as input in the node. */
/* The Normal node has a special case, because the value stored in the first output
* socket is used as input in the node. */
if (node.type == SH_NODE_NORMAL && socket.index() == 1) {
BLI_assert(STREQ(socket.name, "Dot"));
const bNodeSocket &normal_output = node.output_socket(0);
@ -1381,7 +1601,7 @@ void BKE_ntree_update_tag_image_user_changed(bNodeTree *ntree, ImageUser * /*ius
uint64_t bNestedNodePath::hash() const
{
return blender::get_default_hash_2(this->node_id, this->id_in_node);
return blender::get_default_hash(this->node_id, this->id_in_node);
}
bool operator==(const bNestedNodePath &a, const bNestedNodePath &b)

View File

@ -69,7 +69,7 @@ struct ZoneRelation {
uint64_t hash() const
{
return get_default_hash_2(this->parent, this->child);
return get_default_hash(this->parent, this->child);
}
BLI_STRUCT_EQUALITY_OPERATORS_2(ZoneRelation, parent, child)

View File

@ -70,7 +70,7 @@ TreeStoreElemKey::TreeStoreElemKey(ID *id, short type, short nr) : id(id), type(
uint64_t TreeStoreElemKey::hash() const
{
return get_default_hash_3(id, type, nr);
return get_default_hash(id, type, nr);
}
bool operator==(const TreeStoreElemKey &a, const TreeStoreElemKey &b)

View File

@ -1729,7 +1729,7 @@ void BKE_pbvh_node_mark_redraw(PBVHNode *node)
node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw;
}
void BKE_pbvh_node_mark_normals_update(PBVHNode *node)
void BKE_pbvh_node_mark_positions_update(PBVHNode *node)
{
node->flag |= PBVH_UpdateNormals | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw | PBVH_UpdateBB;
}

View File

@ -2237,22 +2237,22 @@ uint64_t BKE_tracking_camera_distortion_hash(const MovieTrackingCamera *camera)
using namespace blender;
switch (camera->distortion_model) {
case TRACKING_DISTORTION_MODEL_POLYNOMIAL:
return get_default_hash_4(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float3(camera->k1, camera->k2, camera->k3));
return get_default_hash(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float3(camera->k1, camera->k2, camera->k3));
case TRACKING_DISTORTION_MODEL_DIVISION:
return get_default_hash_4(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float2(camera->division_k1, camera->division_k2));
return get_default_hash(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float2(camera->division_k1, camera->division_k2));
case TRACKING_DISTORTION_MODEL_NUKE:
return get_default_hash_4(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float2(camera->nuke_k1, camera->nuke_k2));
return get_default_hash(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float2(camera->nuke_k1, camera->nuke_k2));
case TRACKING_DISTORTION_MODEL_BROWN:
return get_default_hash_4(
return get_default_hash(
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float4(camera->brown_k1, camera->brown_k2, camera->brown_k3, camera->brown_k4),

View File

@ -220,7 +220,7 @@ template<typename T> uint64_t get_default_hash(const T &v)
return DefaultHash<std::decay_t<T>>{}(v);
}
template<typename T1, typename T2> uint64_t get_default_hash_2(const T1 &v1, const T2 &v2)
template<typename T1, typename T2> uint64_t get_default_hash(const T1 &v1, const T2 &v2)
{
const uint64_t h1 = get_default_hash(v1);
const uint64_t h2 = get_default_hash(v2);
@ -228,7 +228,7 @@ template<typename T1, typename T2> uint64_t get_default_hash_2(const T1 &v1, con
}
template<typename T1, typename T2, typename T3>
uint64_t get_default_hash_3(const T1 &v1, const T2 &v2, const T3 &v3)
uint64_t get_default_hash(const T1 &v1, const T2 &v2, const T3 &v3)
{
const uint64_t h1 = get_default_hash(v1);
const uint64_t h2 = get_default_hash(v2);
@ -237,7 +237,7 @@ uint64_t get_default_hash_3(const T1 &v1, const T2 &v2, const T3 &v3)
}
template<typename T1, typename T2, typename T3, typename T4>
uint64_t get_default_hash_4(const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4)
uint64_t get_default_hash(const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4)
{
const uint64_t h1 = get_default_hash(v1);
const uint64_t h2 = get_default_hash(v2);
@ -267,7 +267,7 @@ template<typename T> struct DefaultHash<std::reference_wrapper<T>> {
template<typename T1, typename T2> struct DefaultHash<std::pair<T1, T2>> {
uint64_t operator()(const std::pair<T1, T2> &value) const
{
return get_default_hash_2(value.first, value.second);
return get_default_hash(value.first, value.second);
}
};

View File

@ -203,6 +203,27 @@ void interpolate_bilinear_wrap_fl(const float *buffer,
void interpolate_cubic_bspline_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v);
/**
* Cubic Mitchell sampling.
*
* Takes 4x4 image samples at floor(u,v)-1 .. floor(u,v)+2, and blends them
* based on fractional parts of u,v. Uses Mitchell-Netravali filter (B=C=1/3),
* which has a good compromise between blur and ringing.
* Samples outside the image are clamped to texels at image edge.
*
* Note that you probably want to subtract 0.5 from u,v before this function,
* to get proper filtering.
*/
[[nodiscard]] uchar4 interpolate_cubic_mitchell_byte(
const uchar *buffer, int width, int height, float u, float v);
[[nodiscard]] float4 interpolate_cubic_mitchell_fl(
const float *buffer, int width, int height, float u, float v);
void interpolate_cubic_mitchell_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v);
} // namespace blender::math
#define EWA_MAXIDX 255

View File

@ -10,24 +10,41 @@
#include <cstring>
#include "BLI_math_base.h"
#include "BLI_math_base.hh"
#include "BLI_math_interp.hh"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_simd.h"
#include "BLI_strict_flags.h"
/* Cubic B-Spline coefficients. f is offset from texel center in pixel space.
* This is Mitchell-Netravali filter with B=1, C=0 parameters. */
static blender::float4 cubic_bspline_coefficients(float f)
enum class eCubicFilter {
BSpline,
Mitchell,
};
/* Calculate cubic filter coefficients, for samples at -1,0,+1,+2.
* f is 0..1 offset from texel center in pixel space. */
template<enum eCubicFilter filter> static blender::float4 cubic_filter_coefficients(float f)
{
float f2 = f * f;
float f3 = f2 * f;
float w3 = f3 / 6.0f;
float w0 = -w3 + f2 * 0.5f - f * 0.5f + 1.0f / 6.0f;
float w1 = f3 * 0.5f - f2 * 1.0f + 2.0f / 3.0f;
float w2 = 1.0f - w0 - w1 - w3;
return blender::float4(w0, w1, w2, w3);
if constexpr (filter == eCubicFilter::BSpline) {
/* Cubic B-Spline (Mitchell-Netravali filter with B=1, C=0 parameters). */
float w3 = f3 * (1.0f / 6.0f);
float w0 = -w3 + f2 * 0.5f - f * 0.5f + 1.0f / 6.0f;
float w1 = f3 * 0.5f - f2 * 1.0f + 2.0f / 3.0f;
float w2 = 1.0f - w0 - w1 - w3;
return blender::float4(w0, w1, w2, w3);
}
else if constexpr (filter == eCubicFilter::Mitchell) {
/* Cubic Mitchell-Netravali filter with B=1/3, C=1/3 parameters. */
float w0 = -7.0f / 18.0f * f3 + 5.0f / 6.0f * f2 - 0.5f * f + 1.0f / 18.0f;
float w1 = 7.0f / 6.0f * f3 - 2.0f * f2 + 8.0f / 9.0f;
float w2 = -7.0f / 6.0f * f3 + 3.0f / 2.0f * f2 + 0.5f * f + 1.0f / 18.0f;
float w3 = 7.0f / 18.0f * f3 - 1.0f / 3.0f * f2;
return blender::float4(w0, w1, w2, w3);
}
}
#if BLI_HAVE_SSE2
@ -51,6 +68,7 @@ BLI_INLINE __m128 floor_simd(__m128 v)
return v_floor;
}
template<eCubicFilter filter>
BLI_INLINE void bicubic_interpolation_uchar_simd(
const uchar *src_buffer, uchar *output, int width, int height, float u, float v)
{
@ -72,8 +90,8 @@ BLI_INLINE void bicubic_interpolation_uchar_simd(
__m128 frac_uv = _mm_sub_ps(uv, uv_floor);
/* Calculate pixel weights. */
blender::float4 wx = cubic_bspline_coefficients(_mm_cvtss_f32(frac_uv));
blender::float4 wy = cubic_bspline_coefficients(
blender::float4 wx = cubic_filter_coefficients<filter>(_mm_cvtss_f32(frac_uv));
blender::float4 wy = cubic_filter_coefficients<filter>(
_mm_cvtss_f32(_mm_shuffle_ps(frac_uv, frac_uv, 1)));
/* Read 4x4 source pixels and blend them. */
@ -102,7 +120,8 @@ BLI_INLINE void bicubic_interpolation_uchar_simd(
}
/* Pack and write to destination: pack to 16 bit signed, then to 8 bit
* unsigned, then write resulting 32-bit value. */
* unsigned, then write resulting 32-bit value. This will clamp
* out of range values too. */
out = _mm_add_ps(out, _mm_set1_ps(0.5f));
__m128i rgba32 = _mm_cvttps_epi32(out);
__m128i rgba16 = _mm_packs_epi32(rgba32, _mm_setzero_si128());
@ -111,7 +130,7 @@ BLI_INLINE void bicubic_interpolation_uchar_simd(
}
#endif /* BLI_HAVE_SSE2 */
template<typename T>
template<typename T, eCubicFilter filter>
static void bicubic_interpolation(
const T *src_buffer, T *output, int width, int height, int components, float u, float v)
{
@ -122,7 +141,7 @@ static void bicubic_interpolation(
#if BLI_HAVE_SSE2
if constexpr (std::is_same_v<T, uchar>) {
if (components == 4) {
bicubic_interpolation_uchar_simd(src_buffer, output, width, height, u, v);
bicubic_interpolation_uchar_simd<filter>(src_buffer, output, width, height, u, v);
return;
}
}
@ -143,8 +162,8 @@ static void bicubic_interpolation(
float4 out{0.0f};
/* Calculate pixel weights. */
float4 wx = cubic_bspline_coefficients(frac_u);
float4 wy = cubic_bspline_coefficients(frac_v);
float4 wx = cubic_filter_coefficients<filter>(frac_u);
float4 wy = cubic_filter_coefficients<filter>(frac_v);
/* Read 4x4 source pixels and blend them. */
for (int n = 0; n < 4; n++) {
@ -175,6 +194,16 @@ static void bicubic_interpolation(
}
}
/* Mitchell filter has negative lobes; prevent output from going out of range. */
if constexpr (filter == eCubicFilter::Mitchell) {
for (int i = 0; i < components; i++) {
out[i] = math::max(out[i], 0.0f);
if constexpr (std::is_same_v<T, uchar>) {
out[i] = math::min(out[i], 255.0f);
}
}
}
/* Write result. */
if constexpr (std::is_same_v<T, float>) {
if (components == 1) {
@ -551,21 +580,44 @@ float4 interpolate_bilinear_wrap_fl(const float *buffer, int width, int height,
uchar4 interpolate_cubic_bspline_byte(const uchar *buffer, int width, int height, float u, float v)
{
uchar4 res;
bicubic_interpolation<uchar>(buffer, res, width, height, 4, u, v);
bicubic_interpolation<uchar, eCubicFilter::BSpline>(buffer, res, width, height, 4, u, v);
return res;
}
float4 interpolate_cubic_bspline_fl(const float *buffer, int width, int height, float u, float v)
{
float4 res;
bicubic_interpolation<float>(buffer, res, width, height, 4, u, v);
bicubic_interpolation<float, eCubicFilter::BSpline>(buffer, res, width, height, 4, u, v);
return res;
}
void interpolate_cubic_bspline_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v)
{
bicubic_interpolation<float>(buffer, output, width, height, components, u, v);
bicubic_interpolation<float, eCubicFilter::BSpline>(
buffer, output, width, height, components, u, v);
}
uchar4 interpolate_cubic_mitchell_byte(
const uchar *buffer, int width, int height, float u, float v)
{
uchar4 res;
bicubic_interpolation<uchar, eCubicFilter::Mitchell>(buffer, res, width, height, 4, u, v);
return res;
}
float4 interpolate_cubic_mitchell_fl(const float *buffer, int width, int height, float u, float v)
{
float4 res;
bicubic_interpolation<float, eCubicFilter::Mitchell>(buffer, res, width, height, 4, u, v);
return res;
}
void interpolate_cubic_mitchell_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v)
{
bicubic_interpolation<float, eCubicFilter::Mitchell>(
buffer, output, width, height, components, u, v);
}
} // namespace blender::math

View File

@ -88,7 +88,7 @@ class Edge {
uint64_t hash() const
{
return get_default_hash_2(v_[0]->id, v_[1]->id);
return get_default_hash(v_[0]->id, v_[1]->id);
}
};

View File

@ -256,3 +256,65 @@ TEST(math_interp, CubicBSplineCharFullyOutsideImage)
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, 500.0f);
EXPECT_EQ(exp, res);
}
TEST(math_interp, CubicMitchellCharExactSamples)
{
uchar4 res;
uchar4 exp1 = {72, 101, 140, 223};
res = interpolate_cubic_mitchell_byte(image_char[0][0], image_width, image_height, 1.0f, 2.0f);
EXPECT_EQ(int4(exp1), int4(res));
uchar4 exp2 = {233, 162, 99, 37};
res = interpolate_cubic_mitchell_byte(image_char[0][0], image_width, image_height, 2.0f, 0.0f);
EXPECT_EQ(int4(exp2), int4(res));
}
TEST(math_interp, CubicMitchellCharSamples)
{
uchar4 res;
uchar4 exp1 = {135, 132, 130, 127};
res = interpolate_cubic_mitchell_byte(
image_char[0][0], image_width, image_height, 1.25f, 0.625f);
EXPECT_EQ(int4(exp1), int4(res));
uchar4 exp2 = {216, 189, 167, 143};
res = interpolate_cubic_mitchell_byte(image_char[0][0], image_width, image_height, 1.4f, 0.1f);
EXPECT_EQ(int4(exp2), int4(res));
}
TEST(math_interp, CubicMitchellFloatSamples)
{
float4 res;
float4 exp1 = {134.5659f, 131.91309f, 130.17685f, 126.66989f};
res = interpolate_cubic_mitchell_fl(image_fl[0][0], image_width, image_height, 1.25f, 0.625f);
EXPECT_V4_NEAR(exp1, res, float_tolerance);
float4 exp2 = {216.27115f, 189.30673f, 166.93599f, 143.31964f};
res = interpolate_cubic_mitchell_fl(image_fl[0][0], image_width, image_height, 1.4f, 0.1f);
EXPECT_V4_NEAR(exp2, res, float_tolerance);
}
TEST(math_interp, CubicMitchellCharPartiallyOutsideImage)
{
uchar4 res;
uchar4 exp1 = {0, 0, 0, 0};
res = interpolate_cubic_mitchell_byte(image_char[0][0], image_width, image_height, -0.5f, 2.0f);
EXPECT_EQ(int4(exp1), int4(res));
uchar4 exp2 = {88, 116, 151, 228};
res = interpolate_cubic_mitchell_byte(image_char[0][0], image_width, image_height, 1.25f, 2.9f);
EXPECT_EQ(int4(exp2), int4(res));
uchar4 exp3 = {239, 159, 89, 19};
res = interpolate_cubic_mitchell_byte(image_char[0][0], image_width, image_height, 2.2f, -0.1f);
EXPECT_EQ(int4(exp3), int4(res));
}
TEST(math_interp, CubicMitchellFloatPartiallyOutsideImage)
{
float4 res;
float4 exp1 = {0, 0, 0, 0};
res = interpolate_cubic_mitchell_fl(image_fl[0][0], image_width, image_height, -0.5f, 2.0f);
EXPECT_V4_NEAR(exp1, res, float_tolerance);
float4 exp2 = {87.98676f, 115.63634f, 151.13014f, 228.19823f};
res = interpolate_cubic_mitchell_fl(image_fl[0][0], image_width, image_height, 1.25f, 2.9f);
EXPECT_V4_NEAR(exp2, res, float_tolerance);
float4 exp3 = {238.6136f, 158.58293f, 88.55761f, 18.53225f};
res = interpolate_cubic_mitchell_fl(image_fl[0][0], image_width, image_height, 2.2f, -0.1f);
EXPECT_V4_NEAR(exp3, res, float_tolerance);
}

View File

@ -4888,7 +4888,7 @@ void blo_do_versions_280(FileData *fd, Library * /*lib*/, Main *bmain)
}
LISTBASE_FOREACH (bArmature *, arm, &bmain->armatures) {
arm->flag &= ~(ARM_FLAG_UNUSED_1 | ARM_DRAW_RELATION_FROM_HEAD | ARM_FLAG_UNUSED_6 |
arm->flag &= ~(ARM_FLAG_UNUSED_1 | ARM_DRAW_RELATION_FROM_HEAD | ARM_BCOLL_SOLO_ACTIVE |
ARM_FLAG_UNUSED_7 | ARM_FLAG_UNUSED_12);
}
@ -5367,7 +5367,7 @@ void blo_do_versions_280(FileData *fd, Library * /*lib*/, Main *bmain)
}
LISTBASE_FOREACH (bArmature *, arm, &bmain->armatures) {
arm->flag &= ~(ARM_FLAG_UNUSED_6);
arm->flag &= ~(ARM_BCOLL_SOLO_ACTIVE);
}
}

View File

@ -42,7 +42,7 @@ template<> struct blender::DefaultHash<blender::Set<const BMVert *>> {
{
uint64_t hash = 0;
for (const BMVert *vert : value) {
hash = get_default_hash_2(hash, vert);
hash = get_default_hash(hash, vert);
}
return hash;
}

View File

@ -41,7 +41,7 @@ const float *NodeOperation::get_constant_elem_default(const float *default_elem)
std::optional<NodeOperationHash> NodeOperation::generate_hash()
{
params_hash_ = get_default_hash_2(canvas_.xmin, canvas_.xmax);
params_hash_ = get_default_hash(canvas_.xmin, canvas_.xmax);
/* Hash subclasses params. */
is_hash_output_params_implemented_ = true;

View File

@ -672,12 +672,12 @@ class NodeOperation {
template<typename T1, typename T2> void hash_params(T1 param1, T2 param2)
{
combine_hashes(params_hash_, get_default_hash_2(param1, param2));
combine_hashes(params_hash_, get_default_hash(param1, param2));
}
template<typename T1, typename T2, typename T3> void hash_params(T1 param1, T2 param2, T3 param3)
{
combine_hashes(params_hash_, get_default_hash_3(param1, param2, param3));
combine_hashes(params_hash_, get_default_hash(param1, param2, param3));
}
void add_input_socket(DataType datatype, ResizeMode resize_mode = ResizeMode::Center);

View File

@ -36,7 +36,7 @@ BokehKernelKey::BokehKernelKey(
uint64_t BokehKernelKey::hash() const
{
return get_default_hash_3(
return get_default_hash(
size, size, get_default_hash(float4(rotation, roundness, catadioptric, lens_shift)));
}

View File

@ -42,7 +42,7 @@ CachedImageKey::CachedImageKey(ImageUser image_user, std::string pass_name)
uint64_t CachedImageKey::hash() const
{
return get_default_hash_4(image_user.framenr, image_user.layer, image_user.view, pass_name);
return get_default_hash(image_user.framenr, image_user.layer, image_user.view, pass_name);
}
bool operator==(const CachedImageKey &a, const CachedImageKey &b)

View File

@ -45,7 +45,7 @@ CachedMaskKey::CachedMaskKey(int2 size,
uint64_t CachedMaskKey::hash() const
{
return get_default_hash_4(
return get_default_hash(
size, use_feather, motion_blur_samples, float2(motion_blur_shutter, aspect_ratio));
}

View File

@ -29,7 +29,7 @@ CachedShaderKey::CachedShaderKey(const char *info_name, ResultPrecision precisio
uint64_t CachedShaderKey::hash() const
{
return get_default_hash_2(info_name, precision);
return get_default_hash(info_name, precision);
}
bool operator==(const CachedShaderKey &a, const CachedShaderKey &b)

View File

@ -40,7 +40,7 @@ CachedTextureKey::CachedTextureKey(int2 size, float3 offset, float3 scale)
uint64_t CachedTextureKey::hash() const
{
return get_default_hash_3(size, offset, scale);
return get_default_hash(size, offset, scale);
}
bool operator==(const CachedTextureKey &a, const CachedTextureKey &b)

View File

@ -40,7 +40,7 @@ DistortionGridKey::DistortionGridKey(MovieTrackingCamera camera,
uint64_t DistortionGridKey::hash() const
{
return get_default_hash_4(
return get_default_hash(
BKE_tracking_camera_distortion_hash(&camera), size, type, calibration_size);
}

View File

@ -44,7 +44,7 @@ KeyingScreenKey::KeyingScreenKey(int frame, float smoothness)
uint64_t KeyingScreenKey::hash() const
{
return get_default_hash_2(frame, smoothness);
return get_default_hash(frame, smoothness);
}
bool operator==(const KeyingScreenKey &a, const KeyingScreenKey &b)

View File

@ -35,7 +35,7 @@ MorphologicalDistanceFeatherWeightsKey::MorphologicalDistanceFeatherWeightsKey(i
uint64_t MorphologicalDistanceFeatherWeightsKey::hash() const
{
return get_default_hash_2(type, radius);
return get_default_hash(type, radius);
}
bool operator==(const MorphologicalDistanceFeatherWeightsKey &a,

View File

@ -43,7 +43,7 @@ OCIOColorSpaceConversionShaderKey::OCIOColorSpaceConversionShaderKey(std::string
uint64_t OCIOColorSpaceConversionShaderKey::hash() const
{
return get_default_hash_3(source, target, config_cache_id);
return get_default_hash(source, target, config_cache_id);
}
bool operator==(const OCIOColorSpaceConversionShaderKey &a,

View File

@ -33,7 +33,7 @@ SymmetricBlurWeightsKey::SymmetricBlurWeightsKey(int type, float2 radius)
uint64_t SymmetricBlurWeightsKey::hash() const
{
return get_default_hash_3(type, radius.x, radius.y);
return get_default_hash(type, radius.x, radius.y);
}
bool operator==(const SymmetricBlurWeightsKey &a, const SymmetricBlurWeightsKey &b)

View File

@ -32,7 +32,7 @@ SymmetricSeparableBlurWeightsKey::SymmetricSeparableBlurWeightsKey(int type, flo
uint64_t SymmetricSeparableBlurWeightsKey::hash() const
{
return get_default_hash_2(type, radius);
return get_default_hash(type, radius);
}
bool operator==(const SymmetricSeparableBlurWeightsKey &a,

View File

@ -29,7 +29,7 @@ TexturePoolKey::TexturePoolKey(const GPUTexture *texture)
uint64_t TexturePoolKey::hash() const
{
return get_default_hash_3(size.x, size.y, format);
return get_default_hash(size.x, size.y, format);
}
bool operator==(const TexturePoolKey &a, const TexturePoolKey &b)

View File

@ -139,8 +139,8 @@ bool LightSet::operator==(const LightSet &other) const
uint64_t LightSet::hash() const
{
return get_default_hash_2(get_default_hash(include_collection_mask),
get_default_hash(exclude_collection_mask));
return get_default_hash(get_default_hash(include_collection_mask),
get_default_hash(exclude_collection_mask));
}
/* EmitterSetMembership */

View File

@ -19,7 +19,9 @@ void HiZBuffer::sync()
{
int2 render_extent = inst_.film.render_extent_get();
/* Padding to avoid complexity during down-sampling and screen tracing. */
int2 hiz_extent = math::ceil_to_multiple(render_extent, int2(1u << (HIZ_MIP_COUNT - 1)));
int2 probe_extent = int2(inst_.reflection_probes.probe_render_extent());
int2 hiz_extent = math::ceil_to_multiple(math::max(render_extent, probe_extent),
int2(1u << (HIZ_MIP_COUNT - 1)));
int2 dispatch_size = math::divide_ceil(hiz_extent, int2(HIZ_GROUP_SIZE));
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;

View File

@ -140,6 +140,11 @@ eLightProbeResolution ReflectionProbeModule::reflection_probe_resolution() const
return LIGHT_PROBE_RESOLUTION_2048;
}
int ReflectionProbeModule::probe_render_extent() const
{
return instance_.scene->eevee.gi_cubemap_resolution / 2;
}
void ReflectionProbeModule::init()
{
if (!is_initialized) {

View File

@ -243,6 +243,13 @@ class ReflectionProbeModule {
return probes_tx_.width();
}
/**
* Get the resolution of a single cubemap side when rendering probes.
*
* The cubemaps are rendered half size of the size of the octahedral texture.
*/
int probe_render_extent() const;
ReflectionProbeAtlasCoordinate world_atlas_coord_get() const;
private:

View File

@ -16,6 +16,7 @@
#include "BLT_translation.h"
#include "DNA_material_types.h"
#include "DNA_scene_types.h"
#include "BKE_attribute.hh"
#include "BKE_context.hh"
@ -33,7 +34,6 @@
#include "ED_curves.hh"
#include "ED_grease_pencil.hh"
#include "ED_screen.hh"
#include "GEO_smooth_curves.hh"
#include "GEO_subdivide_curves.hh"
@ -44,71 +44,6 @@
namespace blender::ed::greasepencil {
bool active_grease_pencil_poll(bContext *C)
{
Object *object = CTX_data_active_object(C);
if (object == nullptr || object->type != OB_GREASE_PENCIL) {
return false;
}
return true;
}
bool editable_grease_pencil_poll(bContext *C)
{
Object *object = CTX_data_active_object(C);
if (object == nullptr || object->type != OB_GREASE_PENCIL) {
return false;
}
if (!ED_operator_object_active_editable_ex(C, object)) {
return false;
}
if ((object->mode & OB_MODE_EDIT) == 0) {
return false;
}
return true;
}
bool editable_grease_pencil_point_selection_poll(bContext *C)
{
if (!editable_grease_pencil_poll(C)) {
return false;
}
/* Allowed: point and segment selection mode, not allowed: stroke selection mode. */
ToolSettings *ts = CTX_data_tool_settings(C);
return (ts->gpencil_selectmode_edit != GP_SELECTMODE_STROKE);
}
bool grease_pencil_painting_poll(bContext *C)
{
if (!active_grease_pencil_poll(C)) {
return false;
}
Object *object = CTX_data_active_object(C);
if ((object->mode & OB_MODE_PAINT_GREASE_PENCIL) == 0) {
return false;
}
ToolSettings *ts = CTX_data_tool_settings(C);
if (!ts || !ts->gp_paint) {
return false;
}
return true;
}
static void keymap_grease_pencil_editing(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
keyconf, "Grease Pencil Edit Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = editable_grease_pencil_poll;
}
static void keymap_grease_pencil_painting(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
keyconf, "Grease Pencil Paint Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = grease_pencil_painting_poll;
}
/* -------------------------------------------------------------------- */
/** \name Smooth Stroke Operator
* \{ */
@ -1602,18 +1537,6 @@ static void GREASE_PENCIL_OT_stroke_subdivide(wmOperatorType *ot)
/** \} */
static void grease_pencil_operatormarcos_define()
{
wmOperatorType *ot;
ot = WM_operatortype_append_macro("GREASE_PENCIL_OT_stroke_subdivide_smooth",
"Subdivide and Smooth",
"Subdivide strokes and smooth them",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "GREASE_PENCIL_OT_stroke_subdivide");
WM_operatortype_macro_define(ot, "GREASE_PENCIL_OT_stroke_smooth");
}
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil_edit()
@ -1635,13 +1558,4 @@ void ED_operatortypes_grease_pencil_edit()
WM_operatortype_append(GREASE_PENCIL_OT_set_material);
WM_operatortype_append(GREASE_PENCIL_OT_clean_loose);
WM_operatortype_append(GREASE_PENCIL_OT_stroke_subdivide);
grease_pencil_operatormarcos_define();
}
void ED_keymap_grease_pencil(wmKeyConfig *keyconf)
{
using namespace blender::ed::greasepencil;
keymap_grease_pencil_editing(keyconf);
keymap_grease_pencil_painting(keyconf);
}

View File

@ -6,13 +6,87 @@
* \ingroup edgreasepencil
*/
#include "BKE_context.hh"
#include "DNA_scene_types.h"
#include "ED_grease_pencil.hh"
#include "ED_screen.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "RNA_access.hh"
namespace blender::ed::greasepencil {
bool active_grease_pencil_poll(bContext *C)
{
Object *object = CTX_data_active_object(C);
if (object == nullptr || object->type != OB_GREASE_PENCIL) {
return false;
}
return true;
}
bool editable_grease_pencil_poll(bContext *C)
{
Object *object = CTX_data_active_object(C);
if (object == nullptr || object->type != OB_GREASE_PENCIL) {
return false;
}
if (!ED_operator_object_active_editable_ex(C, object)) {
return false;
}
if ((object->mode & OB_MODE_EDIT) == 0) {
return false;
}
return true;
}
bool editable_grease_pencil_point_selection_poll(bContext *C)
{
if (!editable_grease_pencil_poll(C)) {
return false;
}
/* Allowed: point and segment selection mode, not allowed: stroke selection mode. */
ToolSettings *ts = CTX_data_tool_settings(C);
return (ts->gpencil_selectmode_edit != GP_SELECTMODE_STROKE);
}
bool grease_pencil_painting_poll(bContext *C)
{
if (!active_grease_pencil_poll(C)) {
return false;
}
Object *object = CTX_data_active_object(C);
if ((object->mode & OB_MODE_PAINT_GREASE_PENCIL) == 0) {
return false;
}
ToolSettings *ts = CTX_data_tool_settings(C);
if (!ts || !ts->gp_paint) {
return false;
}
return true;
}
static void keymap_grease_pencil_edit_mode(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
keyconf, "Grease Pencil Edit Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = editable_grease_pencil_poll;
}
static void keymap_grease_pencil_paint_mode(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
keyconf, "Grease Pencil Paint Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = grease_pencil_painting_poll;
}
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil()
{
ED_operatortypes_grease_pencil_draw();
@ -38,4 +112,19 @@ void ED_operatormacros_grease_pencil()
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
/* Subdivide and Smooth. */
ot = WM_operatortype_append_macro("GREASE_PENCIL_OT_stroke_subdivide_smooth",
"Subdivide and Smooth",
"Subdivide strokes and smooth them",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "GREASE_PENCIL_OT_stroke_subdivide");
WM_operatortype_macro_define(ot, "GREASE_PENCIL_OT_stroke_smooth");
}
void ED_keymap_grease_pencil(wmKeyConfig *keyconf)
{
using namespace blender::ed::greasepencil;
keymap_grease_pencil_edit_mode(keyconf);
keymap_grease_pencil_paint_mode(keyconf);
}

View File

@ -209,7 +209,7 @@ static int select_random_exec(bContext *C, wmOperator *op)
curves,
selectable_elements,
selection_domain,
blender::get_default_hash_2<int>(seed, info.layer_index),
blender::get_default_hash<int>(seed, info.layer_index),
ratio,
memory);

View File

@ -589,7 +589,7 @@ using uiButSearchTooltipFn =
using uiButSearchListenFn = void (*)(const wmRegionListenerParams *params, void *arg);
/** Must return an allocated string. */
using uiButToolTipFunc = char *(*)(bContext *C, void *argN, const char *tip);
using uiButToolTipFunc = std::string (*)(bContext *C, void *argN, const char *tip);
using uiButToolTipCustomFunc = void (*)(bContext *C, uiTooltipData *data, void *argN);
@ -2280,6 +2280,11 @@ Panel *uiLayoutGetRootPanel(uiLayout *layout);
uiLayout *uiLayoutRow(uiLayout *layout, bool align);
struct PanelLayout {
uiLayout *header;
uiLayout *body;
};
/**
* Create a "layout panel" which is a panel that is defined as part of the `uiLayout`. This allows
* creating expandable sections which can also be nested.
@ -2291,10 +2296,23 @@ uiLayout *uiLayoutRow(uiLayout *layout, bool align);
* context even of the open-property is `false`. This can happen with e.g. property search.
* \param layout: The `uiLayout` that should contain the sub-panel.
* Only layouts that span the full width of the region are supported for now.
* \param name: Text that's shown in the panel header. It should already be translated.
* \param open_prop_owner: Data that contains the open-property.
* \param open_prop_name: Name of the open-property in `open_prop_owner`.
*
* \return A #PanelLayout containing layouts for both the header row and the panel body. If the
* panel is closed and should not be drawn, the body layout will be NULL.
*/
PanelLayout uiLayoutPanelWithHeader(const bContext *C,
uiLayout *layout,
PointerRNA *open_prop_owner,
const char *open_prop_name);
/**
* Variant of #uiLayoutPanelWithHeader() that automatically creates the header row with the
* given label name and only returns the body layout.
*
* \param name: Text that's shown in the panel header. It should already be translated.
*
* \return NULL if the panel is closed and should not be drawn, otherwise the layout where the
* sub-panel should be inserted into.
*/

View File

@ -6396,41 +6396,43 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co
if ((event->modifier & KM_CTRL) == 0) {
float color[3];
Paint *paint = BKE_paint_get_active_from_context(C);
Brush *brush = BKE_paint_brush(paint);
if (paint != nullptr) {
Brush *brush = BKE_paint_brush(paint);
if (brush->flag & BRUSH_USE_GRADIENT) {
float *target = &brush->gradient->data[brush->gradient->cur].r;
if (brush->flag & BRUSH_USE_GRADIENT) {
float *target = &brush->gradient->data[brush->gradient->cur].r;
if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
IMB_colormanagement_srgb_to_scene_linear_v3(target, target);
if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
IMB_colormanagement_srgb_to_scene_linear_v3(target, target);
}
else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
}
}
else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
}
}
else {
Scene *scene = CTX_data_scene(C);
bool updated = false;
else {
Scene *scene = CTX_data_scene(C);
bool updated = false;
if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
BKE_brush_color_set(scene, brush, color);
updated = true;
}
else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
IMB_colormanagement_scene_linear_to_srgb_v3(color, color);
BKE_brush_color_set(scene, brush, color);
updated = true;
}
if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
BKE_brush_color_set(scene, brush, color);
updated = true;
}
else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
IMB_colormanagement_scene_linear_to_srgb_v3(color, color);
BKE_brush_color_set(scene, brush, color);
updated = true;
}
if (updated) {
PropertyRNA *brush_color_prop;
if (updated) {
PropertyRNA *brush_color_prop;
PointerRNA brush_ptr = RNA_id_pointer_create(&brush->id);
brush_color_prop = RNA_struct_find_property(&brush_ptr, "color");
RNA_property_update(C, &brush_ptr, brush_color_prop);
PointerRNA brush_ptr = RNA_id_pointer_create(&brush->id);
brush_color_prop = RNA_struct_find_property(&brush_ptr, "color");
RNA_property_update(C, &brush_ptr, brush_color_prop);
}
}
}

View File

@ -4974,11 +4974,10 @@ uiLayout *uiLayoutRow(uiLayout *layout, bool align)
return litem;
}
uiLayout *uiLayoutPanel(const bContext *C,
uiLayout *layout,
const char *name,
PointerRNA *open_prop_owner,
const char *open_prop_name)
PanelLayout uiLayoutPanelWithHeader(const bContext *C,
uiLayout *layout,
PointerRNA *open_prop_owner,
const char *open_prop_name)
{
const ARegion *region = CTX_wm_region(C);
@ -4986,6 +4985,7 @@ uiLayout *uiLayoutPanel(const bContext *C,
const bool search_filter_active = region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE;
const bool is_open = is_real_open || search_filter_active;
PanelLayout panel_layout{};
{
uiLayoutItemPanelHeader *header_litem = MEM_cnew<uiLayoutItemPanelHeader>(__func__);
uiLayout *litem = &header_litem->litem;
@ -4995,30 +4995,33 @@ uiLayout *uiLayoutPanel(const bContext *C,
header_litem->open_prop_owner = *open_prop_owner;
STRNCPY(header_litem->open_prop_name, open_prop_name);
UI_block_layout_set_current(layout->root->block, litem);
uiLayout *row = uiLayoutRow(litem, true);
uiLayoutSetUnitsY(row, 1.2f);
uiBlock *block = uiLayoutGetBlock(layout);
uiBlock *block = uiLayoutGetBlock(row);
const int icon = is_open ? ICON_DOWNARROW_HLT : ICON_RIGHTARROW;
const int width = ui_text_icon_width(layout, name, icon, false);
const int width = ui_text_icon_width(layout, "", icon, false);
uiDefIconTextBut(block,
UI_BTYPE_LABEL,
0,
icon,
name,
"",
0,
0,
width,
UI_UNIT_Y * 1.2f,
UI_UNIT_Y,
nullptr,
0.0,
0.0,
0.0,
0.0,
0.0f,
0.0f,
0.0f,
0.0f,
"");
panel_layout.header = row;
}
if (!is_open) {
return nullptr;
return panel_layout;
}
uiLayoutItemPanelBody *body_litem = MEM_cnew<uiLayoutItemPanelBody>(__func__);
@ -5027,7 +5030,21 @@ uiLayout *uiLayoutPanel(const bContext *C,
litem->space = layout->root->style->templatespace;
ui_litem_init_from_parent(litem, layout, false);
UI_block_layout_set_current(layout->root->block, litem);
return litem;
panel_layout.body = litem;
return panel_layout;
}
uiLayout *uiLayoutPanel(const bContext *C,
uiLayout *layout,
const char *name,
PointerRNA *open_prop_owner,
const char *open_prop_name)
{
PanelLayout panel = uiLayoutPanelWithHeader(C, layout, open_prop_owner, open_prop_name);
uiItemL(panel.header, name, ICON_NONE);
return panel.body;
}
bool uiLayoutEndsWithPanelHeader(const uiLayout &layout)

View File

@ -236,13 +236,22 @@ class BoneCollectionItem : public AbstractTreeViewItem {
/* Visibility eye icon. */
{
const bool is_solo_active = armature_.flag & ARM_BCOLL_SOLO_ACTIVE;
uiLayout *visibility_sub = uiLayoutRow(sub, true);
uiLayoutSetActive(visibility_sub, bone_collection_.is_visible_ancestors());
uiLayoutSetActive(visibility_sub,
!is_solo_active && bone_collection_.is_visible_ancestors());
const int icon = bone_collection_.is_visible() ? ICON_HIDE_OFF : ICON_HIDE_ON;
PointerRNA bcoll_ptr = rna_pointer();
uiItemR(visibility_sub, &bcoll_ptr, "is_visible", UI_ITEM_R_ICON_ONLY, "", icon);
}
/* Solo icon. */
{
const int icon = bone_collection_.is_solo() ? ICON_SOLO_ON : ICON_SOLO_OFF;
PointerRNA bcoll_ptr = rna_pointer();
uiItemR(sub, &bcoll_ptr, "is_solo", UI_ITEM_R_ICON_ONLY, "", icon);
}
}
void build_context_menu(bContext &C, uiLayout &column) const override

View File

@ -640,7 +640,7 @@ static void *uilist_item_use_dynamic_tooltip(PointerRNA *itemptr, const char *pr
return nullptr;
}
static char *uilist_item_tooltip_func(bContext * /*C*/, void *argN, const char *tip)
static std::string uilist_item_tooltip_func(bContext * /*C*/, void *argN, const char *tip)
{
char *dyn_tooltip = static_cast<char *>(argN);
std::string tooltip_string = dyn_tooltip;
@ -648,7 +648,7 @@ static char *uilist_item_tooltip_func(bContext * /*C*/, void *argN, const char *
tooltip_string += '\n';
tooltip_string += tip;
}
return BLI_strdupn(tooltip_string.c_str(), tooltip_string.size());
return tooltip_string;
}
/**

View File

@ -6143,7 +6143,7 @@ struct ProgressTooltip_Store {
void *owner;
};
static char *progress_tooltip_func(bContext * /*C*/, void *argN, const char * /*tip*/)
static std::string progress_tooltip_func(bContext * /*C*/, void *argN, const char * /*tip*/)
{
ProgressTooltip_Store *arg = static_cast<ProgressTooltip_Store *>(argN);
wmWindowManager *wm = arg->wm;
@ -6162,9 +6162,9 @@ static char *progress_tooltip_func(bContext * /*C*/, void *argN, const char * /*
BLI_timecode_string_from_time_simple(remaining_str, sizeof(remaining_str), remaining);
}
return BLI_sprintfN(
"Time Remaining: %s\n"
"Time Elapsed: %s",
return fmt::format(
"Time Remaining: {}\n"
"Time Elapsed: {}",
remaining_str,
elapsed_str);
}

View File

@ -579,7 +579,7 @@ static int mesh_customdata_clear_exec__internal(bContext *C,
CustomData_free_layers(data, type, tot);
}
DEG_id_tag_update(&mesh->id, 0);
DEG_id_tag_update(&mesh->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh);
return OPERATOR_FINISHED;
@ -761,14 +761,28 @@ static int mesh_customdata_custom_splitnormals_clear_exec(bContext *C, wmOperato
{
Mesh *mesh = ED_mesh_context(C);
if (BKE_mesh_has_custom_loop_normals(mesh)) {
BMEditMesh *em = mesh->edit_mesh;
if (em != nullptr && em->bm->lnor_spacearr != nullptr) {
BKE_lnor_spacearr_clear(em->bm->lnor_spacearr);
if (BMEditMesh *em = mesh->edit_mesh) {
BMesh &bm = *em->bm;
if (!CustomData_has_layer(&bm.ldata, CD_CUSTOMLOOPNORMAL)) {
return OPERATOR_CANCELLED;
}
BM_data_layer_free(&bm, &bm.ldata, CD_CUSTOMLOOPNORMAL);
if (bm.lnor_spacearr) {
BKE_lnor_spacearr_clear(bm.lnor_spacearr);
}
return mesh_customdata_clear_exec__internal(C, BM_LOOP, CD_CUSTOMLOOPNORMAL);
}
return OPERATOR_CANCELLED;
else {
if (!CustomData_has_layer(&mesh->corner_data, CD_CUSTOMLOOPNORMAL)) {
return OPERATOR_CANCELLED;
}
CustomData_free_layers(&mesh->corner_data, CD_CUSTOMLOOPNORMAL, mesh->corners_num);
}
mesh->tag_custom_normals_changed();
DEG_id_tag_update(&mesh->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh);
return OPERATOR_FINISHED;
}
void MESH_OT_customdata_custom_splitnormals_clear(wmOperatorType *ot)

View File

@ -120,7 +120,18 @@ static void stroke_done(const bContext *C, PaintStroke *stroke)
operation->~GreasePencilStrokeOperation();
}
static int grease_pencil_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static bool grease_pencil_brush_stroke_poll(bContext *C)
{
if (!ed::greasepencil::grease_pencil_painting_poll(C)) {
return false;
}
if (!WM_toolsystem_active_tool_is_brush(C)) {
return false;
}
return true;
}
static int grease_pencil_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const Scene *scene = CTX_data_scene(C);
const Object *object = CTX_data_active_object(C);
@ -192,12 +203,12 @@ static int grease_pencil_stroke_invoke(bContext *C, wmOperator *op, const wmEven
return OPERATOR_RUNNING_MODAL;
}
static int grease_pencil_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
static int grease_pencil_brush_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
return paint_stroke_modal(C, op, event, reinterpret_cast<PaintStroke **>(&op->customdata));
}
static void grease_pencil_stroke_cancel(bContext *C, wmOperator *op)
static void grease_pencil_brush_stroke_cancel(bContext *C, wmOperator *op)
{
paint_stroke_cancel(C, op, static_cast<PaintStroke *>(op->customdata));
}
@ -208,9 +219,10 @@ static void GREASE_PENCIL_OT_brush_stroke(wmOperatorType *ot)
ot->idname = "GREASE_PENCIL_OT_brush_stroke";
ot->description = "Draw a new stroke in the active Grease Pencil object";
ot->invoke = grease_pencil_stroke_invoke;
ot->modal = grease_pencil_stroke_modal;
ot->cancel = grease_pencil_stroke_cancel;
ot->poll = grease_pencil_brush_stroke_poll;
ot->invoke = grease_pencil_brush_stroke_invoke;
ot->modal = grease_pencil_brush_stroke_modal;
ot->cancel = grease_pencil_brush_stroke_cancel;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@ -223,9 +235,9 @@ static void GREASE_PENCIL_OT_brush_stroke(wmOperatorType *ot)
/** \name Toggle Draw Mode
* \{ */
static bool grease_pencil_mode_poll_view3d(bContext *C)
static bool grease_pencil_mode_poll_paint_cursor(bContext *C)
{
if (!ed::greasepencil::grease_pencil_painting_poll(C)) {
if (!grease_pencil_brush_stroke_poll(C)) {
return false;
}
if (CTX_wm_region_view3d(C) == nullptr) {
@ -246,7 +258,7 @@ static void grease_pencil_draw_mode_enter(bContext *C)
ob->mode = OB_MODE_PAINT_GREASE_PENCIL;
/* TODO: Setup cursor color. BKE_paint_init() could be used, but creates an additional brush. */
ED_paint_cursor_start(&grease_pencil_paint->paint, grease_pencil_mode_poll_view3d);
ED_paint_cursor_start(&grease_pencil_paint->paint, grease_pencil_mode_poll_paint_cursor);
paint_init_pivot(ob, scene);
/* Necessary to change the object mode on the evaluated object. */

View File

@ -1225,7 +1225,7 @@ static void mask_gesture_apply_task(SculptGestureContext *sgcontext,
undo::push_node(ob, node, undo::Type::Mask);
if (is_multires) {
BKE_pbvh_node_mark_normals_update(node);
BKE_pbvh_node_mark_positions_update(node);
}
}
const float new_mask = mask_flood_fill_get_new_value_for_elem(

View File

@ -862,36 +862,6 @@ static Brush *brush_tool_toggle(Main *bmain, Paint *paint, Brush *brush_orig, co
return nullptr;
}
/** The name of the active tool is "builtin_brush." concatenated with the returned string. */
static blender::StringRefNull curves_active_tool_name_get(const eBrushCurvesSculptTool tool)
{
switch (tool) {
case CURVES_SCULPT_TOOL_COMB:
return "comb";
case CURVES_SCULPT_TOOL_DELETE:
return "delete";
case CURVES_SCULPT_TOOL_SNAKE_HOOK:
return "snake_hook";
case CURVES_SCULPT_TOOL_ADD:
return "add";
case CURVES_SCULPT_TOOL_GROW_SHRINK:
return "grow_shrink";
case CURVES_SCULPT_TOOL_SELECTION_PAINT:
return "selection_paint";
case CURVES_SCULPT_TOOL_PINCH:
return "pinch";
case CURVES_SCULPT_TOOL_SMOOTH:
return "smooth";
case CURVES_SCULPT_TOOL_PUFF:
return "puff";
case CURVES_SCULPT_TOOL_DENSITY:
return "density";
case CURVES_SCULPT_TOOL_SLIDE:
return "slide";
}
return "";
}
static bool brush_generic_tool_set(bContext *C,
Main *bmain,
Paint *paint,
@ -923,26 +893,7 @@ static bool brush_generic_tool_set(bContext *C,
BKE_paint_invalidate_overlay_all();
WM_main_add_notifier(NC_BRUSH | NA_EDITED, brush);
/* Tool System
* This is needed for when there is a non-sculpt tool active (transform for e.g.).
* In case we are toggling (and the brush changed to the toggle_brush), we need to get the
* tool_name again. */
int tool_result = brush_tool(brush, paint->runtime.tool_offset);
ePaintMode paint_mode = BKE_paintmode_get_active_from_context(C);
if (paint_mode == PAINT_MODE_SCULPT_CURVES) {
tool_name = curves_active_tool_name_get(eBrushCurvesSculptTool(tool)).c_str();
}
else {
const EnumPropertyItem *items = BKE_paint_get_tool_enum_from_paintmode(paint_mode);
RNA_enum_name_from_value(items, tool_result, &tool_name);
}
char tool_id[MAX_NAME];
SNPRINTF(tool_id, "builtin_brush.%s", tool_name);
WM_toolsystem_ref_set_by_id(C, tool_id);
WM_toolsystem_ref_set_by_id(C, "builtin.brush");
return true;
}
return false;
@ -989,14 +940,6 @@ static int brush_select_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
if (paint_mode == PAINT_MODE_SCULPT_CURVES) {
tool_name = curves_active_tool_name_get(eBrushCurvesSculptTool(tool)).c_str();
}
else {
const EnumPropertyItem *items = BKE_paint_get_tool_enum_from_paintmode(paint_mode);
RNA_enum_name_from_value(items, tool, &tool_name);
}
if (brush_generic_tool_set(C, bmain, paint, tool, tool_name, create_missing, toggle)) {
return OPERATOR_FINISHED;
}
@ -1070,6 +1013,7 @@ static int brush_asset_select_exec(bContext *C, wmOperator *op)
}
WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, nullptr);
WM_toolsystem_ref_set_by_id(C, "builtin.brush");
return OPERATOR_FINISHED;
}
@ -1077,7 +1021,7 @@ static int brush_asset_select_exec(bContext *C, wmOperator *op)
static void BRUSH_OT_asset_select(wmOperatorType *ot)
{
ot->name = "Select Brush Asset";
ot->description = "Select a brush asset as currently sculpt/paint tool - TESTING PURPOSE ONLY";
ot->description = "Select a brush asset as current sculpt and paint tool";
ot->idname = "BRUSH_OT_asset_select";
ot->exec = brush_asset_select_exec;

View File

@ -121,7 +121,7 @@ void cache_init(bContext *C,
pbvh, [&](PBVHNode &node) { return !node_fully_masked_or_hidden(node); });
for (PBVHNode *node : ss->filter_cache->nodes) {
BKE_pbvh_node_mark_normals_update(node);
BKE_pbvh_node_mark_positions_update(node);
}
/* `mesh->runtime.subdiv_ccg` is not available. Updating of the normals is done during drawing.

View File

@ -295,7 +295,7 @@ static void update_modified_node_mesh(PBVHNode &node, PartialUpdateData &data)
if (!data.modified_position_verts.is_empty()) {
for (const int vert : verts) {
if (data.modified_position_verts[vert]) {
BKE_pbvh_node_mark_normals_update(&node);
BKE_pbvh_node_mark_positions_update(&node);
break;
}
}

View File

@ -523,7 +523,7 @@ static bool clip_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*eve
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE)) {
if (ELEM(file_type, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE)) {
return true;
}
}

View File

@ -323,7 +323,7 @@ static void file_draw_tooltip_custom_func(bContext * /*C*/, uiTooltipData *tip,
}
}
static char *file_draw_asset_tooltip_func(bContext * /*C*/, void *argN, const char * /*tip*/)
static std::string file_draw_asset_tooltip_func(bContext * /*C*/, void *argN, const char * /*tip*/)
{
const auto *asset = static_cast<blender::asset_system::AssetRepresentation *>(argN);
std::string complete_string = asset->get_name();
@ -332,7 +332,7 @@ static char *file_draw_asset_tooltip_func(bContext * /*C*/, void *argN, const ch
complete_string += '\n';
complete_string += meta_data.description;
}
return BLI_strdupn(complete_string.c_str(), complete_string.size());
return complete_string;
}
static void draw_tile_background(const rcti *draw_rect, int colorid, int shade)

View File

@ -99,7 +99,7 @@ struct PaintTileKey {
uint64_t hash() const
{
return blender::get_default_hash_4(x_tile, y_tile, image, ibuf);
return blender::get_default_hash(x_tile, y_tile, image, ibuf);
}
bool operator==(const PaintTileKey &other) const
{

View File

@ -258,7 +258,7 @@ static bool image_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
}
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE)) {
if (ELEM(file_type, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE)) {
return true;
}
}

View File

@ -23,6 +23,7 @@
#include "BKE_image.h"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_scene.h"
@ -1199,6 +1200,7 @@ static const float std_node_socket_colors[][4] = {
{0.62, 0.31, 0.64, 1.0}, /* SOCK_TEXTURE */
{0.92, 0.46, 0.51, 1.0}, /* SOCK_MATERIAL */
{0.65, 0.39, 0.78, 1.0}, /* SOCK_ROTATION */
{0.40, 0.40, 0.40, 1.0}, /* SOCK_MENU */
};
/* Callback for colors that does not depend on the socket pointer argument to get the type. */
@ -1233,6 +1235,7 @@ static const SocketColorFn std_node_socket_color_funcs[] = {
std_node_socket_color_fn<SOCK_TEXTURE>,
std_node_socket_color_fn<SOCK_MATERIAL>,
std_node_socket_color_fn<SOCK_ROTATION>,
std_node_socket_color_fn<SOCK_MENU>,
};
/* draw function for file output node sockets,
@ -1371,6 +1374,27 @@ static void std_node_socket_draw(
break;
}
case SOCK_MENU: {
const bNodeSocketValueMenu *default_value =
sock->default_value_typed<bNodeSocketValueMenu>();
if (default_value->enum_items) {
if (default_value->enum_items->items.is_empty()) {
uiLayout *row = uiLayoutSplit(layout, 0.4f, false);
uiItemL(row, text, ICON_NONE);
uiItemL(row, "No Items", ICON_NONE);
}
else {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
}
}
else if (default_value->has_conflict()) {
uiItemL(layout, IFACE_("Menu Error"), ICON_ERROR);
}
else {
uiItemL(layout, IFACE_("Menu Undefined"), ICON_QUESTION);
}
break;
}
case SOCK_OBJECT: {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, ICON_NONE);
break;
@ -1498,6 +1522,10 @@ static void std_node_socket_interface_draw(ID *id,
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
break;
}
case SOCK_MENU: {
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
break;
}
case SOCK_SHADER:
case SOCK_GEOMETRY:
break;

View File

@ -1550,9 +1550,9 @@ static bool node_socket_has_tooltip(const bNodeTree &ntree, const bNodeSocket &s
return false;
}
static char *node_socket_get_tooltip(const SpaceNode *snode,
const bNodeTree &ntree,
const bNodeSocket &socket)
static std::string node_socket_get_tooltip(const SpaceNode *snode,
const bNodeTree &ntree,
const bNodeSocket &socket)
{
TreeDrawContext tree_draw_ctx;
if (snode != nullptr) {
@ -1601,7 +1601,7 @@ static char *node_socket_get_tooltip(const SpaceNode *snode,
output << bke::nodeSocketLabel(&socket);
}
return BLI_strdup(output.str().c_str());
return output.str();
}
static void node_socket_add_tooltip_in_node_editor(const bNodeTree &ntree,
@ -2301,7 +2301,7 @@ struct NodeErrorsTooltipData {
Span<geo_log::NodeWarning> warnings;
};
static char *node_errors_tooltip_fn(bContext * /*C*/, void *argN, const char * /*tip*/)
static std::string node_errors_tooltip_fn(bContext * /*C*/, void *argN, const char * /*tip*/)
{
NodeErrorsTooltipData &data = *(NodeErrorsTooltipData *)argN;
@ -2318,7 +2318,7 @@ static char *node_errors_tooltip_fn(bContext * /*C*/, void *argN, const char * /
/* Let the tooltip system automatically add the last period. */
complete_string += data.warnings.last().message;
return BLI_strdupn(complete_string.c_str(), complete_string.size());
return complete_string;
}
#define NODE_HEADER_ICON_SIZE (0.8f * U.widget_unit)
@ -2500,7 +2500,7 @@ struct NamedAttributeTooltipArg {
Map<StringRefNull, geo_log::NamedAttributeUsage> usage_by_attribute;
};
static char *named_attribute_tooltip(bContext * /*C*/, void *argN, const char * /*tip*/)
static std::string named_attribute_tooltip(bContext * /*C*/, void *argN, const char * /*tip*/)
{
NamedAttributeTooltipArg &arg = *static_cast<NamedAttributeTooltipArg *>(argN);
@ -2547,7 +2547,7 @@ static char *named_attribute_tooltip(bContext * /*C*/, void *argN, const char *
ss << "\n";
ss << TIP_(
"Attributes with these names used within the group may conflict with existing attributes");
return BLI_strdup(ss.str().c_str());
return ss.str();
}
static NodeExtraInfoRow row_from_used_named_attribute(

View File

@ -207,7 +207,8 @@ void NODE_OT_group_edit(wmOperatorType *ot)
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "exit", false, "Exit", "");
PropertyRNA *prop = RNA_def_boolean(ot->srna, "exit", false, "Exit", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */

View File

@ -434,7 +434,8 @@ static bool socket_can_be_viewed(const bNode &node, const bNodeSocket &socket)
SOCK_INT,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_RGBA);
SOCK_RGBA,
SOCK_MENU);
}
/**
@ -2245,6 +2246,7 @@ static int get_main_socket_priority(const bNodeSocket *socket)
case SOCK_COLLECTION:
case SOCK_TEXTURE:
case SOCK_MATERIAL:
case SOCK_MENU:
return 6;
}
return -1;

View File

@ -394,6 +394,9 @@ static Vector<NodeLinkItem> ui_node_link_items(NodeLinkArg *arg,
else if (dynamic_cast<const decl::String *>(&socket_decl)) {
item.socket_type = SOCK_STRING;
}
else if (dynamic_cast<const decl::Menu *>(&socket_decl)) {
item.socket_type = SOCK_MENU;
}
else if (dynamic_cast<const decl::Image *>(&socket_decl)) {
item.socket_type = SOCK_IMAGE;
}
@ -994,6 +997,9 @@ static void ui_node_draw_input(uiLayout &layout,
split_wrapper.decorate_column, &inputptr, "default_value", RNA_NO_INDEX);
break;
}
case SOCK_MENU:
uiItemL(sub, RPT_("Unsupported Menu Socket"), ICON_NONE);
break;
case SOCK_CUSTOM:
input.typeinfo->draw(&C, sub, &inputptr, &nodeptr, input.name);
break;

View File

@ -888,7 +888,7 @@ static bool node_ima_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = static_cast<eFileSel_File_Types>(
WM_drag_get_path_file_type(drag));
return ELEM(file_type, 0, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE);
return ELEM(file_type, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE);
}
return WM_drag_is_ID_type(drag, ID_IM);
}

View File

@ -1287,7 +1287,7 @@ static std::string collection_drop_tooltip(bContext *C,
}
}
}
return nullptr;
return {};
}
static int collection_drop_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)

View File

@ -92,10 +92,10 @@ static void displayed_channel_range_get(const SeqChannelDrawContext *context,
CLAMP(r_channel_range[1], strip_boundbox.ymin, MAXSEQ);
}
static char *draw_channel_widget_tooltip(bContext * /*C*/, void *argN, const char * /*tip*/)
static std::string draw_channel_widget_tooltip(bContext * /*C*/, void *argN, const char * /*tip*/)
{
char *dyn_tooltip = static_cast<char *>(argN);
return BLI_strdup(dyn_tooltip);
return dyn_tooltip;
}
static float draw_channel_widget_mute(const SeqChannelDrawContext *context,

View File

@ -80,7 +80,7 @@ static bool image_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent *event
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_IMAGE)) {
if (file_type == FILE_TYPE_IMAGE) {
generic_poll_operations(event, TH_SEQ_IMAGE);
return true;
}
@ -98,7 +98,7 @@ static bool is_movie(wmDrag *drag)
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_MOVIE)) {
if (file_type == FILE_TYPE_MOVIE) {
return true;
}
}
@ -122,7 +122,7 @@ static bool is_sound(wmDrag *drag)
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_SOUND)) {
if (file_type == FILE_TYPE_SOUND) {
return true;
}
}
@ -690,7 +690,7 @@ static bool image_drop_preview_poll(bContext * /*C*/, wmDrag *drag, const wmEven
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_IMAGE)) {
if (file_type == FILE_TYPE_IMAGE) {
return true;
}
}
@ -702,7 +702,7 @@ static bool movie_drop_preview_poll(bContext * /*C*/, wmDrag *drag, const wmEven
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_MOVIE)) {
if (file_type == FILE_TYPE_MOVIE) {
return true;
}
}
@ -714,7 +714,7 @@ static bool sound_drop_preview_poll(bContext * /*C*/, wmDrag *drag, const wmEven
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_SOUND)) {
if (file_type == FILE_TYPE_SOUND) {
return true;
}
}

View File

@ -13,6 +13,7 @@ set(INC
../../makesrna
../../nodes
../../windowmanager
../../../../extern/fmtlib/include
# RNA_prototypes.h
${CMAKE_BINARY_DIR}/source/blender/makesrna

View File

@ -5,6 +5,8 @@
#include <iomanip>
#include <sstream>
#include <fmt/format.h>
#include "BLI_math_color.hh"
#include "BLI_math_quaternion_types.hh"
#include "BLI_math_vector_types.hh"
@ -389,11 +391,11 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer {
[](bContext * /*C*/, void *argN, const char * /*tip*/) {
const uint32_t uint_color = POINTER_AS_UINT(argN);
ColorGeometry4b color = *(ColorGeometry4b *)&uint_color;
return BLI_sprintfN(TIP_("Byte Color (sRGB encoded):\n%3d %3d %3d %3d"),
color.r,
color.g,
color.b,
color.a);
return fmt::format(TIP_("Byte Color (sRGB encoded):\n{} {} {} {}"),
color.r,
color.g,
color.b,
color.a);
},
POINTER_FROM_UINT(*(uint32_t *)&color),
nullptr);

View File

@ -307,7 +307,7 @@ static bool text_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*eve
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_PYSCRIPT, FILE_TYPE_TEXT)) {
if (ELEM(file_type, FILE_TYPE_PYSCRIPT, FILE_TYPE_TEXT)) {
return true;
}
}

View File

@ -633,7 +633,7 @@ static bool view3d_ima_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event
}
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
return ELEM(file_type, 0, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE);
return ELEM(file_type, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE);
}
return WM_drag_is_ID_type(drag, ID_IM);

View File

@ -126,7 +126,7 @@ template<typename NodePtr> class GFieldBase {
uint64_t hash() const
{
return get_default_hash_2(*node_, node_output_index_);
return get_default_hash(*node_, node_output_index_);
}
const CPPType &cpp_type() const

View File

@ -96,7 +96,7 @@ class DataType {
uint64_t hash() const
{
return get_default_hash_2(*type_, category_);
return get_default_hash(*type_, category_);
}
};

View File

@ -177,9 +177,9 @@ Batch *VKBackend::batch_alloc()
return new VKBatch();
}
DrawList *VKBackend::drawlist_alloc(int /*list_length*/)
DrawList *VKBackend::drawlist_alloc(int list_length)
{
return new VKDrawList();
return new VKDrawList(list_length);
}
Fence *VKBackend::fence_alloc()

View File

@ -31,4 +31,9 @@ class VKBatch : public Batch {
void draw_setup();
};
BLI_INLINE VKBatch *unwrap(GPUBatch *batch)
{
return static_cast<VKBatch *>(batch);
}
} // namespace blender::gpu

View File

@ -8,15 +8,81 @@
#include "GPU_batch.h"
#include "vk_batch.hh"
#include "vk_common.hh"
#include "vk_drawlist.hh"
#include "vk_index_buffer.hh"
#include "vk_vertex_buffer.hh"
namespace blender::gpu {
void VKDrawList::append(GPUBatch *batch, int instance_first, int instance_count)
VKDrawList::VKDrawList(int list_length)
: command_buffer_(
list_length * sizeof(VkDrawIndexedIndirectCommand), GPU_USAGE_STREAM, __func__),
length_(list_length)
{
GPU_batch_draw_advanced(batch, 0, 0, instance_first, instance_count);
command_buffer_.ensure_allocated();
}
void VKDrawList::submit() {}
void VKDrawList::append(GPUBatch *gpu_batch, int instance_first, int instance_count)
{
/* Check for different batch. When batch is different the previous commands should be flushed to
* the gpu. */
VKBatch *batch = unwrap(gpu_batch);
if (batch_ != batch) {
submit();
batch_ = batch;
}
/* Record the new command */
const VKIndexBuffer *index_buffer = batch_->index_buffer_get();
const bool is_indexed = index_buffer != nullptr;
if (is_indexed) {
VkDrawIndexedIndirectCommand &command = get_command<VkDrawIndexedIndirectCommand>();
command.firstIndex = index_buffer->index_base_get();
command.vertexOffset = index_buffer->index_start_get();
command.indexCount = index_buffer->index_len_get();
command.firstInstance = instance_first;
command.instanceCount = instance_count;
}
else {
const VKVertexBuffer *vertex_buffer = batch_->vertex_buffer_get(0);
if (vertex_buffer == nullptr) {
batch_ = nullptr;
return;
}
VkDrawIndirectCommand &command = get_command<VkDrawIndirectCommand>();
command.vertexCount = vertex_buffer->vertex_len;
command.instanceCount = instance_count;
command.firstVertex = 0;
command.firstInstance = instance_first;
}
command_index_++;
/* Submit commands when command buffer is full. */
if (command_index_ == length_) {
submit();
}
}
void VKDrawList::submit()
{
if (batch_ == nullptr || command_index_ == 0) {
command_index_ = 0;
batch_ = nullptr;
return;
}
const VKIndexBuffer *index_buffer = batch_->index_buffer_get();
const bool is_indexed = index_buffer != nullptr;
command_buffer_.buffer_get().flush();
batch_->multi_draw_indirect(wrap(wrap(&command_buffer_)),
command_index_,
0,
is_indexed ? sizeof(VkDrawIndexedIndirectCommand) :
sizeof(VkDrawIndirectCommand));
command_index_ = 0;
batch_ = nullptr;
}
} // namespace blender::gpu

View File

@ -10,12 +10,68 @@
#include "gpu_drawlist_private.hh"
#include "vk_storage_buffer.hh"
namespace blender::gpu {
class VKBatch;
class VKDrawList : public DrawList {
private:
/**
* Batch from who the commands are being recorded.
*/
VKBatch *batch_ = nullptr;
/**
* Storage buffer containing the commands.
*
* The storage buffer is host visible and new commands are directly added to the buffer. Reducing
* the need to copy the commands from an intermediate buffer to the GPU. The commands are only
* written once and used once.
*
* The data can be used to record VkDrawIndirectCommands or VkDrawIndirectIndexedCommands.
*/
VKStorageBuffer command_buffer_;
/**
* Maximum number of commands that can be recorded per batch. Commands will be flushed when this
* number of commands are added.
*/
const int length_;
/**
* Current number of recorded commands.
*/
int command_index_ = 0;
public:
VKDrawList(int list_length);
/**
* Append a new command for the given batch to the draw list.
*
* Will flush when batch is different than the previous one or when the command_buffer_ is full.
*/
void append(GPUBatch *batch, int instance_first, int instance_count) override;
/**
* Submit buffered commands to the GPU.
*
* NOTE: after calling this method the command_index_ and the batch_ are reset.
*/
void submit() override;
private:
/**
* Retrieve command to write to. The returned memory is part of the mapped memory of the
* commands_buffer_.
*/
template<typename CommandType> CommandType &get_command() const
{
return MutableSpan<CommandType>(
static_cast<CommandType *>(command_buffer_.buffer_get().mapped_memory_get()),
length_)[command_index_];
}
};
} // namespace blender::gpu

View File

@ -38,12 +38,14 @@ void VKStorageBuffer::ensure_allocated()
void VKStorageBuffer::allocate()
{
const bool is_host_visible = false;
buffer_.create(size_in_bytes_,
usage_,
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
is_host_visible);
const bool is_host_visible = ELEM(usage_, GPU_USAGE_STREAM);
VkBufferUsageFlags buffer_usage_flags = ELEM(usage_, GPU_USAGE_STREAM) ?
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT :
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT;
buffer_.create(size_in_bytes_, usage_, buffer_usage_flags, is_host_visible);
debug::object_label(buffer_.vk_handle(), name_);
}

View File

@ -49,6 +49,11 @@ class VKStorageBuffer : public StorageBuf, public VKBindableResource {
void ensure_allocated();
const VKBuffer &buffer_get() const
{
return buffer_;
}
private:
void allocate();
};
@ -57,5 +62,9 @@ BLI_INLINE VKStorageBuffer *unwrap(StorageBuf *storage_buffer)
{
return static_cast<VKStorageBuffer *>(storage_buffer);
}
BLI_INLINE StorageBuf *wrap(VKStorageBuffer *storage_buffer)
{
return static_cast<StorageBuf *>(storage_buffer);
}
} // namespace blender::gpu

View File

@ -264,7 +264,8 @@ void IMB_rectblend_threaded(ImBuf *dbuf,
enum eIMBInterpolationFilterMode {
IMB_FILTER_NEAREST,
IMB_FILTER_BILINEAR,
IMB_FILTER_BICUBIC,
IMB_FILTER_CUBIC_BSPLINE,
IMB_FILTER_CUBIC_MITCHELL,
};
/**

View File

@ -91,6 +91,16 @@ inline void interpolate_cubic_bspline_fl(const ImBuf *in, float output[4], float
memcpy(output, &col, sizeof(col));
}
[[nodiscard]] inline uchar4 interpolate_cubic_mitchell_byte(const ImBuf *in, float u, float v)
{
return math::interpolate_cubic_mitchell_byte(in->byte_buffer.data, in->x, in->y, u, v);
}
inline void interpolate_cubic_mitchell_byte(const ImBuf *in, uchar output[4], float u, float v)
{
uchar4 col = math::interpolate_cubic_mitchell_byte(in->byte_buffer.data, in->x, in->y, u, v);
memcpy(output, &col, sizeof(col));
}
} // namespace blender::imbuf
/**

View File

@ -146,10 +146,10 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
u = wrap_uv(u, source->x);
v = wrap_uv(v, source->y);
}
/* BLI_bilinear_interpolation functions use `floor(uv)` and `floor(uv)+1`
/* Bilinear/cubic interpolation functions use `floor(uv)` and `floor(uv)+1`
* texels. For proper mapping between pixel and texel spaces, need to
* subtract 0.5. Same for bicubic. */
if constexpr (ELEM(Filter, IMB_FILTER_BILINEAR, IMB_FILTER_BICUBIC)) {
* subtract 0.5. */
if constexpr (Filter != IMB_FILTER_NEAREST) {
u -= 0.5f;
v -= 0.5f;
}
@ -185,14 +185,24 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
math::interpolate_nearest_fl(
source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
}
else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v<T, float>) {
else if constexpr (Filter == IMB_FILTER_CUBIC_BSPLINE && std::is_same_v<T, float>) {
math::interpolate_cubic_bspline_fl(
source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
}
else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v<T, uchar> && NumChannels == 4)
else if constexpr (Filter == IMB_FILTER_CUBIC_BSPLINE && std::is_same_v<T, uchar> &&
NumChannels == 4)
{
interpolate_cubic_bspline_byte(source, r_sample, u, v);
}
else if constexpr (Filter == IMB_FILTER_CUBIC_MITCHELL && std::is_same_v<T, float>) {
math::interpolate_cubic_mitchell_fl(
source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
}
else if constexpr (Filter == IMB_FILTER_CUBIC_MITCHELL && std::is_same_v<T, uchar> &&
NumChannels == 4)
{
interpolate_cubic_mitchell_byte(source, r_sample, u, v);
}
else {
/* Unsupported sampler. */
BLI_assert_unreachable();
@ -385,8 +395,11 @@ void IMB_transform(const ImBuf *src,
else if (filter == IMB_FILTER_BILINEAR) {
transform_scanlines_filter<IMB_FILTER_BILINEAR>(ctx, y_range);
}
else if (filter == IMB_FILTER_BICUBIC) {
transform_scanlines_filter<IMB_FILTER_BICUBIC>(ctx, y_range);
else if (filter == IMB_FILTER_CUBIC_BSPLINE) {
transform_scanlines_filter<IMB_FILTER_CUBIC_BSPLINE>(ctx, y_range);
}
else if (filter == IMB_FILTER_CUBIC_MITCHELL) {
transform_scanlines_filter<IMB_FILTER_CUBIC_MITCHELL>(ctx, y_range);
}
});
}

View File

@ -87,9 +87,9 @@ TEST(imbuf_transform, bilinear_2x_smaller)
IMB_freeImBuf(res);
}
TEST(imbuf_transform, bicubic_2x_smaller)
TEST(imbuf_transform, cubic_bspline_2x_smaller)
{
ImBuf *res = transform_2x_smaller(IMB_FILTER_BICUBIC, 1);
ImBuf *res = transform_2x_smaller(IMB_FILTER_CUBIC_BSPLINE, 1);
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
EXPECT_EQ(got[0], ColorTheme4b(189, 126, 62, 250));
EXPECT_EQ(got[1], ColorTheme4b(134, 57, 33, 26));
@ -97,16 +97,26 @@ TEST(imbuf_transform, bicubic_2x_smaller)
IMB_freeImBuf(res);
}
TEST(imbuf_transform, bicubic_fractional_larger)
TEST(imbuf_transform, cubic_mitchell_2x_smaller)
{
ImBuf *res = transform_fractional_larger(IMB_FILTER_BICUBIC, 1);
ImBuf *res = transform_2x_smaller(IMB_FILTER_CUBIC_MITCHELL, 1);
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
EXPECT_EQ(got[0 + 0 * res->x], ColorTheme4b(35, 11, 1, 255));
EXPECT_EQ(got[1 + 0 * res->x], ColorTheme4b(131, 12, 6, 250));
EXPECT_EQ(got[7 + 0 * res->x], ColorTheme4b(54, 93, 19, 249));
EXPECT_EQ(got[2 + 2 * res->x], ColorTheme4b(206, 70, 56, 192));
EXPECT_EQ(got[3 + 2 * res->x], ColorTheme4b(165, 60, 42, 78));
EXPECT_EQ(got[8 + 6 * res->x], ColorTheme4b(57, 1, 90, 252));
EXPECT_EQ(got[0], ColorTheme4b(195, 130, 67, 255));
EXPECT_EQ(got[1], ColorTheme4b(132, 51, 28, 0));
EXPECT_EQ(got[2], ColorTheme4b(52, 52, 48, 255));
IMB_freeImBuf(res);
}
TEST(imbuf_transform, cubic_mitchell_fractional_larger)
{
ImBuf *res = transform_fractional_larger(IMB_FILTER_CUBIC_MITCHELL, 1);
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
EXPECT_EQ(got[0 + 0 * res->x], ColorTheme4b(0, 0, 0, 255));
EXPECT_EQ(got[1 + 0 * res->x], ColorTheme4b(127, 0, 0, 255));
EXPECT_EQ(got[7 + 0 * res->x], ColorTheme4b(49, 109, 13, 255));
EXPECT_EQ(got[2 + 2 * res->x], ColorTheme4b(236, 53, 50, 215));
EXPECT_EQ(got[3 + 2 * res->x], ColorTheme4b(155, 55, 35, 54));
EXPECT_EQ(got[8 + 6 * res->x], ColorTheme4b(57, 0, 98, 252));
IMB_freeImBuf(res);
}

View File

@ -78,7 +78,7 @@ struct uv_vertex_key {
uint64_t hash() const
{
return get_default_hash_3(uv.x, uv.y, vertex_index);
return get_default_hash(uv.x, uv.y, vertex_index);
}
};

Some files were not shown because too many files have changed in this diff Show More