WIP: Vulkan: Workbench #107886

Closed
Jeroen Bakker wants to merge 88 commits from Jeroen-Bakker:vulkan-draw-manager-workbench into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
104 changed files with 3421 additions and 1751 deletions
Showing only changes of commit 1c6556643a - Show all commits

View File

@ -90,10 +90,17 @@ set(USD_EXTRA_ARGS
-DTBB_LIBRARIES=${LIBDIR}/tbb/lib/${LIBPREFIX}${TBB_LIBRARY}${SHAREDLIBEXT}
-DTbb_TBB_LIBRARY=${LIBDIR}/tbb/lib/${LIBPREFIX}${TBB_LIBRARY}${SHAREDLIBEXT}
-DTBB_tbb_LIBRARY_RELEASE=${LIBDIR}/tbb/lib/${LIBPREFIX}${TBB_LIBRARY}${SHAREDLIBEXT}
)
# Ray: I'm not sure if the other platforms relied on this or not but this is no longer
# needed for windows. If mac/lin confirm, this can be removed.
if(NOT WIN32)
LIST(append USD_EXTRA_ARGS
# USD wants the tbb debug lib set even when you are doing a release build
# Otherwise it will error out during the cmake configure phase.
-DTBB_LIBRARIES_DEBUG=${LIBDIR}/tbb/lib/${LIBPREFIX}${TBB_LIBRARY}${SHAREDLIBEXT}
)
endif()
ExternalProject_Add(external_usd
URL file://${PACKAGE_DIR}/${USD_FILE}

View File

@ -104,12 +104,12 @@ if %ERRORLEVEL% NEQ 0 (
set StatusFile=%BUILD_DIR%\%1_%2.log
set original_path=%path%
set oiio_paths=%Staging%\%BuildDir%%ARCH%R\Release\openimageio\bin;%Staging%\%BuildDir%%ARCH%D\Debug\openimageio\bin
set boost_paths=%Staging%\%BuildDir%%ARCH%R\Release\boost\lib;%Staging%\%BuildDir%%ARCH%D\Debug\boost\lib
set openexr_paths=%Staging%\%BuildDir%%ARCH%R\Release\openexr\bin;%Staging%\%BuildDir%%ARCH%D\Debug\openexr\bin
set imath_paths=%Staging%\%BuildDir%%ARCH%R\Release\imath\bin;%Staging%\%BuildDir%%ARCH%D\Debug\imath\bin
set tbb_paths=%Staging%\%BuildDir%%ARCH%R\Release\tbb\bin;%Staging%\%BuildDir%%ARCH%D\Debug\tbb\bin
set path=%BUILD_DIR%\downloads\mingw\mingw64\msys\1.0\bin\;%BUILD_DIR%\downloads\nasm-2.12.01\;%path%;%boost_paths%;%oiio_paths%;%openexr_paths%;%imath_paths%;%tbb_paths%
set oiio_paths=%Staging%\%BuildDir%%ARCH%R\Release\openimageio\bin
set boost_paths=%Staging%\%BuildDir%%ARCH%R\Release\boost\lib
set openexr_paths=%Staging%\%BuildDir%%ARCH%R\Release\openexr\bin
set imath_paths=%Staging%\%BuildDir%%ARCH%R\Release\imath\bin
set tbb_paths=%Staging%\%BuildDir%%ARCH%R\Release\tbb\bin
set path=%BUILD_DIR%\downloads\mingw\mingw64\msys\1.0\bin\;%BUILD_DIR%\downloads\nasm-2.12.01\;%original_path%;%boost_paths%;%oiio_paths%;%openexr_paths%;%imath_paths%;%tbb_paths%
mkdir %STAGING%\%BuildDir%%ARCH%R
cd %Staging%\%BuildDir%%ARCH%R
echo %DATE% %TIME% : Start > %StatusFile%
@ -125,6 +125,12 @@ if "%NODEBUG%" == "1" goto exit
cd %BUILD_DIR%
mkdir %STAGING%\%BuildDir%%ARCH%D
cd %Staging%\%BuildDir%%ARCH%D
set oiio_paths=%Staging%\%BuildDir%%ARCH%D\Debug\openimageio\bin
set boost_paths=%Staging%\%BuildDir%%ARCH%D\Debug\boost\lib
set openexr_paths=%Staging%\%BuildDir%%ARCH%D\Debug\openexr\bin
set imath_paths=%Staging%\%BuildDir%%ARCH%D\Debug\imath\bin
set tbb_paths=%Staging%\%BuildDir%%ARCH%D\Debug\tbb\bin
set path=%BUILD_DIR%\downloads\mingw\mingw64\msys\1.0\bin\;%BUILD_DIR%\downloads\nasm-2.12.01\;%original_path%;%boost_paths%;%oiio_paths%;%openexr_paths%;%imath_paths%;%tbb_paths%
cmake -G "%CMAKE_BUILDER%" %CMAKE_BUILD_ARCH% -Thost=x64 %SOURCE_DIR% -DPACKAGE_DIR=%BUILD_DIR%/packages -DDOWNLOAD_DIR=%BUILD_DIR%/downloads -DCMAKE_BUILD_TYPE=Debug -DBUILD_MODE=Debug -DHARVEST_TARGET=%HARVEST_DIR%/%HARVESTROOT%%VSVER_SHORT%/ %CMAKE_DEBUG_OPTIONS%
echo %DATE% %TIME% : Debug Configuration done >> %StatusFile%
if "%dobuild%" == "1" (

View File

@ -30,7 +30,7 @@ int blender_device_threads(BL::Scene &b_scene)
return 0;
}
void adjust_device_info_from_preferences(DeviceInfo &info, PointerRNA cpreferences)
void static adjust_device_info_from_preferences(DeviceInfo &info, PointerRNA cpreferences)
{
if (!get_boolean(cpreferences, "peer_memory")) {
info.has_peer_memory = false;

View File

@ -610,6 +610,9 @@ def km_window(params):
("wm.open_mainfile", {"type": 'O', "value": 'PRESS', "ctrl": True}, None),
("wm.save_mainfile", {"type": 'S', "value": 'PRESS', "ctrl": True}, None),
("wm.save_as_mainfile", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True}, None),
("wm.save_mainfile",
{"type": 'S', "value": 'PRESS', "ctrl": True, "alt": True},
{"properties": [("incremental", True)]}),
("wm.quit_blender", {"type": 'Q', "value": 'PRESS', "ctrl": True}, None),
# Quick menu and toolbar
@ -2521,7 +2524,7 @@ def km_nla_generic(_params):
sidebar_key={"type": 'N', "value": 'PRESS'},
),
("nla.tweakmode_enter", {"type": 'TAB', "value": 'PRESS'},
{"properties": [("use_upper_stack_evaluation", False)]}),
{"properties": [("use_upper_stack_evaluation", True)]}),
("nla.tweakmode_exit", {"type": 'TAB', "value": 'PRESS'}, None),
("nla.tweakmode_enter", {"type": 'TAB', "value": 'PRESS', "shift": True},
{"properties": [("isolate_action", True)]}),
@ -4510,6 +4513,12 @@ def km_face_mask(params):
{"properties": [("deselect", True)]}),
("paint.face_select_more", {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True}, None),
("paint.face_select_less", {"type": 'NUMPAD_MINUS', "value": 'PRESS', "ctrl": True}, None),
("paint.face_select_loop", {"type": params.select_mouse, "value": 'PRESS', "alt": True},
{"properties": [('extend', False), ('select', True)]}),
("paint.face_select_loop", {"type": params.select_mouse, "value": 'PRESS', "alt": True, "shift": True},
{"properties": [('extend', True), ('select', True)]}),
("paint.face_select_loop", {"type": params.select_mouse, "value": 'PRESS', "alt": True, "shift": True, "ctrl": True},
{"properties": [('extend', True), ('select', False)]}),
])
return keymap
@ -4591,7 +4600,7 @@ def km_pose(params):
("armature.layers_show_all", {"type": 'ACCENT_GRAVE', "value": 'PRESS', "ctrl": True}, None),
("armature.armature_layers", {"type": 'M', "value": 'PRESS', "shift": True}, None),
("pose.bone_layers", {"type": 'M', "value": 'PRESS'}, None),
("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "ctrl": True, "alt": True}, None),
("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
("anim.keyframe_insert_menu", {"type": 'I', "value": 'PRESS'}, None),
("anim.keyframe_delete_v3d", {"type": 'I', "value": 'PRESS', "alt": True}, None),
("anim.keying_set_active_set", {"type": 'I', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
@ -5110,8 +5119,6 @@ def km_weight_paint(params):
("paint.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("paint.weight_sample", {"type": params.action_mouse, "value": 'PRESS', "ctrl": True}, None),
("paint.weight_sample_group", {"type": params.action_mouse, "value": 'PRESS', "shift": True}, None),
("paint.weight_gradient", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
{"properties": [("type", 'LINEAR')]}),
("paint.weight_gradient", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True, "alt": True},
{"properties": [("type", 'RADIAL')]}),
("paint.weight_set", {"type": 'K', "value": 'PRESS', "shift": True}, None),
@ -5522,7 +5529,7 @@ def km_armature(params):
("armature.bone_layers", {"type": 'M', "value": 'PRESS'}, None),
# Special transforms.
op_tool_optional(
("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "ctrl": True, "alt": True}, None),
("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
(op_tool_cycle, "builtin.bone_size"), params),
op_tool_optional(
("transform.transform", {"type": 'S', "value": 'PRESS', "alt": True},

View File

@ -188,6 +188,9 @@ def km_window(params):
("wm.open_mainfile", {"type": 'O', "value": 'PRESS', "ctrl": True}, None),
("wm.save_mainfile", {"type": 'S', "value": 'PRESS', "ctrl": True}, None),
("wm.save_as_mainfile", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True}, None),
("wm.save_mainfile",
{"type": 'S', "value": 'PRESS', "ctrl": True, "alt": True},
{"properties": [("incremental", True)]}),
("wm.quit_blender", {"type": 'Q', "value": 'PRESS', "ctrl": True}, None),
# Quick menu and toolbar

View File

@ -224,6 +224,16 @@ class TEXT_MT_view(Menu):
layout.separator()
props = layout.operator("wm.context_cycle_int", text="Zoom In")
props.data_path = "space_data.font_size"
props.reverse = False
props = layout.operator("wm.context_cycle_int", text="Zoom Out")
props.data_path = "space_data.font_size"
props.reverse = True
layout.separator()
layout.menu("TEXT_MT_view_navigation")
layout.separator()

View File

@ -283,6 +283,10 @@ class TOPBAR_MT_file(Menu):
layout.operator_context = 'EXEC_AREA' if context.blend_data.is_saved else 'INVOKE_AREA'
layout.operator("wm.save_mainfile", text="Save", icon='FILE_TICK')
sub = layout.row()
sub.enabled = context.blend_data.is_saved
sub.operator("wm.save_mainfile", text="Save Incremental").incremental = True
layout.operator_context = 'INVOKE_AREA'
layout.operator("wm.save_as_mainfile", text="Save As...")
layout.operator_context = 'INVOKE_AREA'

View File

@ -2311,6 +2311,7 @@ class VIEW3D_MT_grease_pencil_add(Menu):
layout = self.layout
layout.operator("object.grease_pencil_add", text="Empty", icon='EMPTY_AXIS').type = 'EMPTY'
layout.operator("object.grease_pencil_add", text="Stroke", icon='STROKE').type = 'STROKE'
layout.operator("object.grease_pencil_add", text="Suzanne", icon='MONKEY').type = 'MONKEY'
class VIEW3D_MT_add(Menu):

View File

@ -11,6 +11,7 @@
#include "BLI_generic_span.hh"
#include "BLI_generic_virtual_array.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_offset_indices.hh"
#include "BLI_set.hh"
#include "BKE_anonymous_attribute_id.hh"
@ -928,6 +929,20 @@ void gather_attributes(AttributeAccessor src_attributes,
const IndexMask &selection,
MutableAttributeAccessor dst_attributes);
/**
* Copy attribute values from groups groups defined by \a src_offsets to groups defined by \a
* dst_offsets. The group indices are gathered to the result by \a selection. The size of each
* source and result group must be the same.
*/
void gather_attributes_group_to_group(AttributeAccessor src_attributes,
eAttrDomain domain,
const AnonymousAttributePropagationInfo &propagation_info,
const Set<std::string> &skip,
OffsetIndices<int> src_offsets,
OffsetIndices<int> dst_offsets,
const IndexMask &selection,
MutableAttributeAccessor dst_attributes);
void copy_attributes(const AttributeAccessor src_attributes,
const eAttrDomain domain,
const AnonymousAttributePropagationInfo &propagation_info,

View File

@ -174,20 +174,6 @@ void ramp_blend(int type, float r_col[3], float fac, const float col[3]);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Copy/Paste
* \{ */
void BKE_material_copybuf_clear(void);
void BKE_material_copybuf_free(void);
void BKE_material_copybuf_copy(struct Main *bmain, struct Material *ma);
/**
* \return true when the material was modified.
*/
bool BKE_material_copybuf_paste(struct Main *bmain, struct Material *ma);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Default Materials
* \{ */

View File

@ -137,14 +137,13 @@ bool BKE_animdata_set_action(ReportList *reports, ID *id, bAction *act)
return false;
}
/* Reduce user-count for current action. */
/* Unassign current action. */
if (adt->action) {
id_us_min((ID *)adt->action);
adt->action = NULL;
}
if (act == NULL) {
/* Just clearing the action. */
adt->action = NULL;
return true;
}

View File

@ -1028,6 +1028,58 @@ void gather_attributes(const AttributeAccessor src_attributes,
});
}
template<typename T>
static void gather_group_to_group(const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const IndexMask &selection,
const Span<T> src,
MutableSpan<T> dst)
{
selection.foreach_index(GrainSize(512), [&](const int64_t src_i, const int64_t dst_i) {
dst.slice(dst_offsets[dst_i]).copy_from(src.slice(src_offsets[src_i]));
});
}
static void gather_group_to_group(const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const IndexMask &selection,
const GSpan src,
GMutableSpan dst)
{
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
gather_group_to_group(src_offsets, dst_offsets, selection, src.typed<T>(), dst.typed<T>());
});
}
void gather_attributes_group_to_group(const AttributeAccessor src_attributes,
const eAttrDomain domain,
const AnonymousAttributePropagationInfo &propagation_info,
const Set<std::string> &skip,
const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const IndexMask &selection,
MutableAttributeAccessor dst_attributes)
{
src_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
if (meta_data.domain != domain) {
return true;
}
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
return true;
}
if (skip.contains(id.name())) {
return true;
}
const GVArraySpan src = *src_attributes.lookup(id, domain);
bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
id, domain, meta_data.data_type);
gather_group_to_group(src_offsets, dst_offsets, selection, src, dst.span);
dst.finish();
return true;
});
}
void copy_attributes(const AttributeAccessor src_attributes,
const eAttrDomain domain,
const AnonymousAttributePropagationInfo &propagation_info,

View File

@ -970,18 +970,32 @@ void BKE_blendfile_workspace_config_data_free(WorkspaceConfigFileData *workspace
/** \name Blend File Write (Partial)
* \{ */
void BKE_blendfile_write_partial_begin(Main *bmain_src)
static void blendfile_write_partial_clear_flags(Main *bmain_src)
{
BKE_main_id_tag_all(bmain_src, LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT, false);
ListBase *lbarray[INDEX_ID_MAX];
int a = set_listbasepointers(bmain_src, lbarray);
while (a--) {
LISTBASE_FOREACH (ID *, id, lbarray[a]) {
id->tag &= ~(LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT);
id->flag &= ~(LIB_CLIPBOARD_MARK);
}
}
}
void BKE_blendfile_write_partial_begin(Main *bmain)
{
blendfile_write_partial_clear_flags(bmain);
}
void BKE_blendfile_write_partial_tag_ID(ID *id, bool set)
{
if (set) {
id->tag |= LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT;
id->flag |= LIB_CLIPBOARD_MARK;
}
else {
id->tag &= ~(LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT);
id->flag &= ~LIB_CLIPBOARD_MARK;
}
}
@ -1080,7 +1094,7 @@ bool BKE_blendfile_write_partial(Main *bmain_src,
void BKE_blendfile_write_partial_end(Main *bmain_src)
{
BKE_main_id_tag_all(bmain_src, LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT, false);
blendfile_write_partial_clear_flags(bmain_src);
}
/** \} */

View File

@ -1176,62 +1176,28 @@ void CurvesGeometry::remove_points(const IndexMask &points_to_delete,
*this = curves_copy_point_selection(*this, points_to_copy, propagation_info);
}
template<typename T>
static void gather_group_to_group(const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const IndexMask &selection,
const Span<T> src,
MutableSpan<T> dst)
{
selection.foreach_index(GrainSize(256), [&](const int64_t src_i, const int64_t dst_i) {
dst.slice(dst_offsets[dst_i]).copy_from(src.slice(src_offsets[src_i]));
});
}
static void gather_group_to_group(const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const IndexMask &selection,
const GSpan src,
GMutableSpan dst)
{
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
gather_group_to_group(src_offsets, dst_offsets, selection, src.typed<T>(), dst.typed<T>());
});
}
CurvesGeometry curves_copy_curve_selection(
const CurvesGeometry &curves,
const IndexMask &curves_to_copy,
const AnonymousAttributePropagationInfo &propagation_info)
{
const OffsetIndices points_by_curve = curves.points_by_curve();
CurvesGeometry dst_curves(0, curves_to_copy.size());
MutableSpan<int> new_curve_offsets = dst_curves.offsets_for_write();
offset_indices::gather_group_sizes(
curves.points_by_curve(), curves_to_copy, new_curve_offsets.drop_back(1));
offset_indices::accumulate_counts_to_offsets(new_curve_offsets);
dst_curves.resize(new_curve_offsets.last(), dst_curves.curves_num());
const OffsetIndices src_points_by_curve = curves.points_by_curve();
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
const OffsetIndices dst_points_by_curve = offset_indices::gather_selected_offsets(
points_by_curve, curves_to_copy, dst_curves.offsets_for_write());
dst_curves.resize(dst_points_by_curve.total_size(), dst_curves.curves_num());
const AttributeAccessor src_attributes = curves.attributes();
MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
src_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
if (meta_data.domain != ATTR_DOMAIN_POINT) {
return true;
}
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
return true;
}
const GVArraySpan src = *src_attributes.lookup(id);
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
id, meta_data.domain, meta_data.data_type);
gather_group_to_group(src_points_by_curve, dst_points_by_curve, curves_to_copy, src, dst.span);
dst.finish();
return true;
});
gather_attributes_group_to_group(src_attributes,
ATTR_DOMAIN_POINT,
propagation_info,
{},
points_by_curve,
dst_points_by_curve,
curves_to_copy,
dst_attributes);
gather_attributes(
src_attributes, ATTR_DOMAIN_CURVE, propagation_info, {}, curves_to_copy, dst_attributes);

View File

@ -1650,8 +1650,8 @@ static void dynamicPaint_setInitialColor(const Scene *scene, DynamicPaintSurface
return;
}
/* for vertex surface loop through tfaces and find uv color
* that provides highest alpha */
/* For vertex surface loop through `looptris` and find UV color
* that provides highest alpha. */
if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX) {
struct ImagePool *pool = BKE_image_pool_new();

View File

@ -93,7 +93,7 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
int stroke_i = 0;
LISTBASE_FOREACH_INDEX (bGPDstroke *, gps, &gpf.strokes, stroke_i) {
/* TODO: check if gps->editcurve is not nullptr and parse bezier curve instead. */
/* TODO: check if `gps->editcurve` is not nullptr and parse bezier curve instead. */
/* Write curve attributes. */
stroke_cyclic.span[stroke_i] = (gps->flag & GP_STROKE_CYCLIC) != 0;

View File

@ -77,8 +77,6 @@
static CLG_LogRef LOG = {"bke.material"};
static void material_clear_data(ID *id);
static void material_init_data(ID *id)
{
Material *material = (Material *)id;
@ -136,26 +134,6 @@ static void material_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const
/* TODO: Duplicate Engine Settings and set runtime to nullptr. */
}
/**
* Ensure pointers to allocated memory is cleared
* (the kind of data that would have to be copied).
*
* \note Keep in sync with #material_free_data.
* \note Doesn't handle animation data (`ma->adt`).
*/
static void material_clear_data(ID *id)
{
Material *material = (Material *)id;
BLI_listbase_clear(&material->gpumaterial);
material->texpaintslot = nullptr;
material->gp_style = nullptr;
material->nodetree = nullptr;
material->preview = nullptr;
id->icon_id = 0;
}
static void material_free_data(ID *id)
{
Material *material = (Material *)id;
@ -1934,170 +1912,6 @@ void ramp_blend(int type, float r_col[3], const float fac, const float col[3])
}
}
/* -------------------------------------------------------------------- */
/** \name Material Copy/Paste
*
* As materials may reference other data, the clipboard only stores a subset of all possible data.
* The material and it's node-tree are stored and nothing else.
* Notably the following variables are *not* part of the clipboard.
*
* - The #ID (name, fake-user, custom-properties .. etc).
* - Animation data (#Material::adt, #bNodeTree::adt).
* - Texture paint slots (#Material::texpaintslot)
* as this is cache and references other ID's.
* - Grease pencil style (#Material::gp_style)
* could be supported but ID's and pointers would need to be handled carefully.
*
* When pasting, some data in the destination material is left as-is:
* - The #ID.
* - Animation data, with the exception that pasting a material without a node-tree
* will clear the existing materials node-tree & its animation.
* Note that applying existing animation to the pasted material might not make sense
* and may reference data-paths that don't resolve (depending on the kind of material).
* The user might need to clear the animation in this case.
*
* \{ */
static Material matcopybuf;
static short matcopied = 0;
void BKE_material_copybuf_clear(void)
{
if (matcopied) {
BKE_material_copybuf_free();
}
matcopybuf = blender::dna::shallow_zero_initialize();
matcopied = 0;
}
/**
* Some members should *never* be set, ensure this is always the case.
* Call at the beginning & end of functions that manipulate the clipboard.
*/
static void material_copybuf_assert_is_valid()
{
BLI_assert(!matcopybuf.id.icon_id);
BLI_assert(!matcopybuf.id.py_instance);
BLI_assert(!matcopybuf.adt);
BLI_assert(!matcopybuf.preview);
if (matcopybuf.nodetree) {
BLI_assert(!matcopybuf.nodetree->id.py_instance);
BLI_assert(!matcopybuf.nodetree->adt);
BLI_assert(!matcopybuf.nodetree->owner_id);
}
}
void BKE_material_copybuf_free(void)
{
material_copybuf_assert_is_valid();
material_free_data(&matcopybuf.id);
matcopied = 0;
}
void BKE_material_copybuf_copy(Main *bmain, Material *ma)
{
material_copybuf_assert_is_valid();
if (matcopied) {
BKE_material_copybuf_free();
}
matcopybuf = blender::dna::shallow_copy(*ma);
/* Not essential, but we never want to use any ID values from the source,
* this ensures that never happens. */
memset(&matcopybuf.id, 0, sizeof(ID));
/* Ensure dangling pointers are never copied back into a material. */
material_clear_data(&matcopybuf.id);
/* Unhandled by materials generic data functions. */
matcopybuf.adt = nullptr;
if (ma->nodetree != nullptr) {
/* Never copy animation data. */
struct {
AnimData *adt;
} backup;
backup.adt = ma->nodetree->adt;
ma->nodetree->adt = nullptr;
matcopybuf.nodetree = blender::bke::ntreeCopyTree_ex(ma->nodetree, bmain, false);
matcopybuf.nodetree->owner_id = nullptr;
ma->nodetree->adt = backup.adt;
}
/* TODO: Duplicate Engine Settings and set runtime to nullptr. */
matcopied = 1;
material_copybuf_assert_is_valid();
}
bool BKE_material_copybuf_paste(Main *bmain, Material *ma)
{
material_copybuf_assert_is_valid();
if (matcopied == 0) {
return false;
}
/* `matcopybuf` never has animation data, no need to check. */
const bool has_animdata = (ma->adt != nullptr || (ma->nodetree && ma->nodetree->adt));
const bool has_node_tree = (ma->nodetree || matcopybuf.nodetree);
AnimData *backup_nodetree_adt = nullptr;
if (ma->nodetree && matcopybuf.nodetree) {
/* Keep data to apply back to the new node-tree. */
std::swap(backup_nodetree_adt, ma->nodetree->adt);
}
/* Handles freeing nodes and and other run-time data (previews) for e.g. */
material_free_data(&ma->id);
/* Copy from `matcopybuf` preserving some members.
* NOTE: animation data isn't stored in the clipboard, any existing animation will be left as-is.
* Any undesired animation will have to be manually cleared by the user. */
{
struct {
ID id;
AnimData *adt;
} backup;
backup.id = ma->id;
backup.adt = ma->adt;
*ma = blender::dna::shallow_copy(matcopybuf);
ma->id = backup.id;
ma->adt = backup.adt;
}
if (matcopybuf.nodetree != nullptr) {
BLI_assert(matcopybuf.nodetree->adt == nullptr);
ma->nodetree = blender::bke::ntreeCopyTree_ex(matcopybuf.nodetree, bmain, false);
ma->nodetree->owner_id = &ma->id;
/* Restore animation pointer (if set). */
BLI_assert(ma->nodetree->adt == nullptr);
ma->nodetree->adt = backup_nodetree_adt;
}
if (has_node_tree || has_animdata) {
/* Important to run this when the embedded tree if freed,
* otherwise the depsgraph holds a reference to the (now freed) `ma->nodetree`.
* Also run this when a new node-tree is set to ensure it's accounted for.
* This also applies to animation data which is likely to be stored in the depsgraph. */
DEG_relations_tag_update(bmain);
}
material_copybuf_assert_is_valid();
return true;
}
/** \} */
void BKE_material_eval(struct Depsgraph *depsgraph, Material *material)
{
DEG_debug_print_eval(depsgraph, __func__, material->id.name, material);

View File

@ -1178,7 +1178,7 @@ static int mesh_tessface_calc(Mesh &mesh,
/* NOTE: quad detection issue - fourth vertex-index vs fourth loop-index:
* Polygons take care of their loops ordering, hence not of their vertices ordering.
* Currently, our tfaces' fourth vertex index might be 0 even for a quad.
* Currently, the #TFace fourth vertex index might be 0 even for a quad.
* However, we know our fourth loop index is never 0 for quads
* (because they are sorted for polygons, and our quads are still mere copies of their polygons).
* So we pass nullptr as #MFace pointer, and #mesh_loops_to_tessdata

View File

@ -38,7 +38,7 @@ template<typename T> class OffsetIndices {
/** Return the total number of elements in the referenced arrays. */
T total_size() const
{
return offsets_.size() == 1 ? 0 : offsets_.last();
return offsets_.size() > 1 ? offsets_.last() : 0;
}
/**
@ -146,6 +146,10 @@ void copy_group_sizes(OffsetIndices<int> offsets, const IndexMask &mask, Mutable
/** Gather the number of indices in each indexed group to sizes. */
void gather_group_sizes(OffsetIndices<int> offsets, const IndexMask &mask, MutableSpan<int> sizes);
/** Build new offsets that contains only the groups chosen by \a selection. */
OffsetIndices<int> gather_selected_offsets(OffsetIndices<int> src_offsets,
const IndexMask &selection,
MutableSpan<int> dst_offsets);
/**
* Create a map from indexed elements to the source indices, in other words from the larger array
* to the smaller array.

View File

@ -61,7 +61,7 @@ template<typename T> class SharedCache {
}
/** Retrieve the cached data. */
const T &data()
const T &data() const
{
BLI_assert(cache_->mutex.is_cached());
return cache_->data;

View File

@ -436,6 +436,15 @@ void BLI_str_rstrip(char *str) ATTR_NONNULL(1);
*/
int BLI_str_rstrip_float_zero(char *str, char pad) ATTR_NONNULL(1);
/**
* Strip trailing digits.
* ABC123 -> ABC
*
* \param str:
* \return The number of digits stripped.
*/
int BLI_str_rstrip_digits(char *str) ATTR_NONNULL();
/**
* Return index of a string in a string array.
*

View File

@ -17,7 +17,13 @@
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
# define NOMINMAX
# include <windows.h>
# undef NOMINMAX
#else
# include <windows.h>
#endif
#undef rad
#undef rad1

View File

@ -38,6 +38,19 @@ void gather_group_sizes(const OffsetIndices<int> offsets,
});
}
OffsetIndices<int> gather_selected_offsets(const OffsetIndices<int> src_offsets,
const IndexMask &selection,
MutableSpan<int> dst_offsets)
{
if (selection.is_empty()) {
return {};
}
BLI_assert(selection.size() == (dst_offsets.size() - 1));
gather_group_sizes(src_offsets, selection, dst_offsets);
accumulate_counts_to_offsets(dst_offsets);
return OffsetIndices<int>(dst_offsets);
}
void build_reverse_map(OffsetIndices<int> offsets, MutableSpan<int> r_map)
{
threading::parallel_for(offsets.index_range(), 1024, [&](const IndexRange range) {

View File

@ -1058,6 +1058,17 @@ int BLI_str_rstrip_float_zero(char *str, const char pad)
return totstrip;
}
int BLI_str_rstrip_digits(char *str)
{
int totstrip = 0;
int str_len = strlen(str);
while (str_len > 0 && isdigit(str[--str_len])) {
str[str_len] = '\0';
totstrip++;
}
return totstrip;
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -125,7 +125,7 @@ TEST(string, StrCopyUTF8_TerminateEncodingEarly)
/** \} */
/* -------------------------------------------------------------------- */
/** \name String Concatinate
/** \name String Concatenate
* \{ */
TEST(string, StrCat)

View File

@ -86,7 +86,7 @@ class AntiAliasing {
pass.framebuffer_set(&output_fb_);
pass.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM);
pass.shader_set(shaders_.static_shader_get(ANTIALIASING_RESOLVE));
/** \note use color_tx as dummy if AA is diabled. */
/** \note use color_tx as dummy if AA is disabled. */
pass.bind_texture("blendTex", anti_aliasing_enabled_ ? &blend_weight_tx_ : &color_tx);
pass.bind_texture("colorTex", &color_tx);
pass.bind_texture("revealTex", &reveal_tx);

View File

@ -103,7 +103,7 @@ class Instance {
objects.init(v3d, scene);
lights.init(v3d);
/* TODO(fclem): Vfx. */
/* TODO(@fclem): VFX. */
// vfx.init(use_vfx_, camera_, rv3d);
anti_aliasing.init(v3d, scene);
}

View File

@ -13,7 +13,7 @@
namespace blender::draw::greasepencil {
enum eShaderType {
/* SMAA antialiasing */
/* SMAA anti-aliasing. */
ANTIALIASING_EDGE_DETECT = 0,
ANTIALIASING_BLEND_WEIGHT,
ANTIALIASING_RESOLVE,

View File

@ -133,7 +133,7 @@ BLI_STATIC_ASSERT_ALIGN(gpLight, 16)
#endif
struct gpObject {
/** Wether or not to apply lighting to the GPencil object. */
/** Weather or not to apply lighting to the GPencil object. */
bool1 is_shadeless;
/** Switch between 2d and 3D stroke order. */
bool1 stroke_order3d;

View File

@ -18,8 +18,10 @@
namespace blender::draw::overlay {
/* Selection engine reuse most of the Overlay engine by creating selection IDs for each
* selectable component and using a special shaders for drawing.*/
/**
* Selection engine reuse most of the Overlay engine by creating selection IDs for each
* selectable component and using a special shaders for drawing.
*/
class Instance {
const SelectionType selection_type_;

View File

@ -160,12 +160,12 @@ BLI_INLINE int32_t pack_rotation_aspect_hardness(float rot, float asp, float har
/* Aspect uses 9 bits */
float asp_normalized = (asp > 1.0f) ? (1.0f / asp) : asp;
packed |= int32_t(unit_float_to_uchar_clamp(asp_normalized));
/* Store if inversed in the 9th bit. */
/* Store if inverted in the 9th bit. */
if (asp > 1.0f) {
packed |= 1 << 8;
}
/* Rotation uses 9 bits */
/* Rotation are in [-90°..90°] range, so we can encode the sign of the angle + the cosine
/* Rotation are in [-90..90] degree range, so we can encode the sign of the angle + the cosine
* because the cosine will always be positive. */
packed |= int32_t(unit_float_to_uchar_clamp(cosf(rot))) << 9;
/* Store sine sign in 9th bit. */
@ -197,7 +197,7 @@ static void grease_pencil_batches_ensure(GreasePencil &grease_pencil, int cfra)
cfra, [&](GreasePencilDrawing &drawing) { drawings.append(&drawing); });
/* First, count how many vertices and triangles are needed for the whole object. Also record the
* offsets into the curves for the verticies and triangles. */
* offsets into the curves for the vertices and triangles. */
int total_points_num = 0;
int total_triangles_num = 0;
int v_offset = 0;

View File

@ -51,6 +51,17 @@
#include "armature_intern.h"
/* -------------------------------------------------------------------- */
/** \name Local Utilities
* \{ */
static void pose_copybuffer_filepath_get(char filepath[FILE_MAX], size_t filepath_maxncpy)
{
BLI_path_join(filepath, filepath_maxncpy, BKE_tempdir_base(), "copybuffer_pose.blend");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Apply Pose as Rest Pose
* \{ */
@ -789,7 +800,8 @@ static int pose_copy_exec(bContext *C, wmOperator *op)
* existing on its own.
*/
BKE_copybuffer_copy_tag_ID(&ob_copy.id);
BLI_path_join(filepath, sizeof(filepath), BKE_tempdir_base(), "copybuffer_pose.blend");
pose_copybuffer_filepath_get(filepath, sizeof(filepath));
BKE_copybuffer_copy_end(temp_bmain, filepath, op->reports);
/* We clear the lists so no datablocks gets freed,
* This is required because objects in temp bmain shares same pointers
@ -842,27 +854,27 @@ static int pose_paste_exec(bContext *C, wmOperator *op)
/* Read copy buffer .blend file. */
char filepath[FILE_MAX];
Main *tmp_bmain = BKE_main_new();
STRNCPY(tmp_bmain->filepath, BKE_main_blendfile_path_from_global());
Main *temp_bmain = BKE_main_new();
STRNCPY(temp_bmain->filepath, BKE_main_blendfile_path_from_global());
BLI_path_join(filepath, sizeof(filepath), BKE_tempdir_base(), "copybuffer_pose.blend");
if (!BKE_copybuffer_read(tmp_bmain, filepath, op->reports, FILTER_ID_OB)) {
pose_copybuffer_filepath_get(filepath, sizeof(filepath));
if (!BKE_copybuffer_read(temp_bmain, filepath, op->reports, FILTER_ID_OB)) {
BKE_report(op->reports, RPT_ERROR, "Internal clipboard is empty");
BKE_main_free(tmp_bmain);
BKE_main_free(temp_bmain);
return OPERATOR_CANCELLED;
}
/* Make sure data from this file is usable for pose paste. */
if (BLI_listbase_count_at_most(&tmp_bmain->objects, 2) != 1) {
if (BLI_listbase_count_at_most(&temp_bmain->objects, 2) != 1) {
BKE_report(op->reports, RPT_ERROR, "Internal clipboard is not from pose mode");
BKE_main_free(tmp_bmain);
BKE_main_free(temp_bmain);
return OPERATOR_CANCELLED;
}
Object *object_from = tmp_bmain->objects.first;
Object *object_from = temp_bmain->objects.first;
bPose *pose_from = object_from->pose;
if (pose_from == NULL) {
BKE_report(op->reports, RPT_ERROR, "Internal clipboard has no pose");
BKE_main_free(tmp_bmain);
BKE_main_free(temp_bmain);
return OPERATOR_CANCELLED;
}
@ -889,7 +901,7 @@ static int pose_paste_exec(bContext *C, wmOperator *op)
}
}
}
BKE_main_free(tmp_bmain);
BKE_main_free(temp_bmain);
/* Update event for pose and deformation children. */
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);

View File

@ -22,8 +22,9 @@ extern "C" {
namespace blender::ed::greasepencil {
void create_blank(Main &bmain, Object &object, int frame_numer);
void create_stroke(Main &bmain, Object &object, float4x4 matrix, int frame_numer);
void create_blank(Main &bmain, Object &object, int frame_number);
void create_stroke(Main &bmain, Object &object, float4x4 matrix, int frame_number);
void create_suzanne(Main &bmain, Object &object, float4x4 matrix, const int frame_number);
} // namespace blender::ed::greasepencil
#endif

View File

@ -427,6 +427,8 @@ void paintface_select_linked(struct bContext *C,
struct Object *ob,
const int mval[2],
bool select);
void paintface_select_loop(struct bContext *C, struct Object *ob, const int mval[2], bool select);
/**
* Grow the selection of faces.
* \param face_step: If true will also select faces that only touch on the corner.

View File

@ -52,8 +52,8 @@ typedef enum {
TFM_TIME_SLIDE,
TFM_TIME_SCALE,
TFM_TIME_EXTEND,
TFM_TIME_DUPLICATE,
TFM_BAKE_TIME,
/* TFM_TIME_DUPLICATE (deprecated). */
TFM_BAKE_TIME = 26,
TFM_DEPRECATED, /* was BEVEL */
TFM_BWEIGHT,
TFM_ALIGN,

View File

@ -12,7 +12,9 @@
#include "BLI_bitmap.h"
#include "BLI_blenlib.h"
#include "BLI_math.h"
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
#include "BLI_vector_set.hh"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
@ -350,6 +352,177 @@ void paintface_select_linked(bContext *C, Object *ob, const int mval[2], const b
paintface_flush_flags(C, ob, true, false);
}
static int find_closest_edge_in_poly(ARegion *region,
blender::Span<blender::int2> edges,
blender::Span<int> poly_edges,
blender::Span<blender::float3> vert_positions,
const int mval[2])
{
using namespace blender;
int closest_edge_index;
const float2 mval_f = {float(mval[0]), float(mval[1])};
float min_distance = FLT_MAX;
for (const int i : poly_edges) {
float2 screen_coordinate;
const int2 edge = edges[i];
const float3 edge_vert_average = math::midpoint(vert_positions[edge[0]],
vert_positions[edge[1]]);
eV3DProjStatus status = ED_view3d_project_float_object(
region, edge_vert_average, screen_coordinate, V3D_PROJ_TEST_CLIP_DEFAULT);
if (status != V3D_PROJ_RET_OK) {
continue;
}
const float distance = math::distance_squared(mval_f, screen_coordinate);
if (distance < min_distance) {
min_distance = distance;
closest_edge_index = i;
}
}
return closest_edge_index;
}
static int get_opposing_edge_index(const blender::IndexRange poly,
const blender::Span<int> corner_edges,
const int current_edge_index)
{
const int index_in_poly = corner_edges.slice(poly).first_index(current_edge_index);
/* Assumes that edge index of opposing face edge is always off by 2 on quads. */
if (index_in_poly >= 2) {
return corner_edges[poly[index_in_poly - 2]];
}
/* Cannot be out of bounds because of the preceding if statement: if i < 2 then i+2 < 4. */
return corner_edges[poly[index_in_poly + 2]];
}
/**
* Follow quads around the mesh by finding opposing edges.
* \return True if the search has looped back on itself, finding the same index twice.
*/
static bool follow_face_loop(const int poly_start_index,
const int edge_start_index,
const blender::OffsetIndices<int> polys,
const blender::VArray<bool> &hide_poly,
const blender::Span<int> corner_edges,
const blender::GroupedSpan<int> edge_to_poly_map,
blender::VectorSet<int> &r_loop_polys)
{
using namespace blender;
int current_poly_index = poly_start_index;
int current_edge_index = edge_start_index;
while (current_edge_index > 0) {
int next_poly_index = -1;
for (const int poly_index : edge_to_poly_map[current_edge_index]) {
if (poly_index != current_poly_index) {
next_poly_index = poly_index;
break;
}
}
/* Edge might only have 1 poly connected. */
if (next_poly_index == -1) {
return false;
}
/* Only works on quads. */
if (polys[next_poly_index].size() != 4) {
return false;
}
/* Happens if we looped around the mesh. */
if (r_loop_polys.contains(next_poly_index)) {
return true;
}
/* Hidden polygons stop selection. */
if (hide_poly[next_poly_index]) {
return false;
}
r_loop_polys.add(next_poly_index);
const IndexRange next_poly = polys[next_poly_index];
current_edge_index = get_opposing_edge_index(next_poly, corner_edges, current_edge_index);
current_poly_index = next_poly_index;
}
return false;
}
void paintface_select_loop(bContext *C, Object *ob, const int mval[2], const bool select)
{
using namespace blender;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewContext vc;
ED_view3d_viewcontext_init(C, &vc, depsgraph);
ED_view3d_select_id_validate(&vc);
Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
if (!ob_eval) {
return;
}
uint poly_pick_index = uint(-1);
if (!ED_mesh_pick_face(C, ob, mval, ED_MESH_PICK_DEFAULT_FACE_DIST, &poly_pick_index)) {
return;
}
ARegion *region = CTX_wm_region(C);
RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
ED_view3d_init_mats_rv3d(ob_eval, rv3d);
Mesh *mesh = BKE_mesh_from_object(ob);
const Span<int> corner_edges = mesh->corner_edges();
const Span<float3> verts = mesh->vert_positions();
const OffsetIndices polys = mesh->polys();
const Span<int2> edges = mesh->edges();
const IndexRange poly = polys[poly_pick_index];
const int closest_edge_index = find_closest_edge_in_poly(
region, edges, corner_edges.slice(poly), verts, mval);
Array<int> edge_to_poly_offsets;
Array<int> edge_to_poly_indices;
const GroupedSpan<int> edge_to_poly_map = bke::mesh::build_edge_to_poly_map(
polys, corner_edges, mesh->totedge, edge_to_poly_offsets, edge_to_poly_indices);
VectorSet<int> polys_to_select;
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
const VArray<bool> hide_poly = *attributes.lookup_or_default<bool>(
".hide_poly", ATTR_DOMAIN_FACE, false);
const Span<int> polys_to_closest_edge = edge_to_poly_map[closest_edge_index];
const bool traced_full_loop = follow_face_loop(polys_to_closest_edge[0],
closest_edge_index,
polys,
hide_poly,
corner_edges,
edge_to_poly_map,
polys_to_select);
if (!traced_full_loop) {
/* Trace the other way. */
follow_face_loop(polys_to_closest_edge[1],
closest_edge_index,
polys,
hide_poly,
corner_edges,
edge_to_poly_map,
polys_to_select);
}
bke::SpanAttributeWriter<bool> select_poly = attributes.lookup_or_add_for_write_span<bool>(
".select_poly", ATTR_DOMAIN_FACE);
select_poly.span.fill_indices(polys_to_select.as_span(), select);
select_poly.finish();
paintface_flush_flags(C, ob, true, false);
}
static bool poly_has_selected_neighbor(blender::Span<int> poly_edges,
blender::Span<blender::int2> edges,
blender::Span<bool> select_vert,

View File

@ -1643,7 +1643,16 @@ static int object_grease_pencil_add_exec(bContext *C, wmOperator *op)
greasepencil::create_stroke(*bmain, *object, mat, scene->r.cfra);
break;
}
case GP_MONKEY:
case GP_MONKEY: {
const float radius = RNA_float_get(op->ptr, "radius");
const float3 scale(radius);
float4x4 mat;
ED_object_new_primitive_matrix(C, object, loc, rot, scale, mat.ptr());
greasepencil::create_suzanne(*bmain, *object, mat, scene->r.cfra);
break;
}
case GP_LRT_OBJECT:
case GP_LRT_SCENE:
case GP_LRT_COLLECTION: {

View File

@ -11,6 +11,7 @@
#include "MEM_guardedalloc.h"
#include "DNA_ID.h"
#include "DNA_curve_types.h"
#include "DNA_light_types.h"
#include "DNA_lightprobe_types.h"
@ -24,12 +25,15 @@
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_path_util.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "BKE_anim_data.h"
#include "BKE_animsys.h"
#include "BKE_appdir.h"
#include "BKE_blender_copybuffer.h"
#include "BKE_brush.h"
#include "BKE_context.h"
#include "BKE_curve.h"
@ -38,6 +42,8 @@
#include "BKE_image.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
#include "BKE_lib_query.h"
#include "BKE_lib_remap.h"
#include "BKE_lightprobe.h"
#include "BKE_linestyle.h"
#include "BKE_main.h"
@ -95,6 +101,11 @@ static bool object_materials_supported_poll_ex(bContext *C, const Object *ob);
/** \name Local Utilities
* \{ */
static void material_copybuffer_filepath_get(char filepath[FILE_MAX], size_t filepath_maxncpy)
{
BLI_path_join(filepath, filepath_maxncpy, BKE_tempdir_base(), "copybuffer_material.blend");
}
static bool object_array_for_shading_edit_mode_enabled_filter(const Object *ob, void *user_data)
{
bContext *C = static_cast<bContext *>(user_data);
@ -2731,8 +2742,7 @@ void TEXTURE_OT_slot_move(wmOperatorType *ot)
/** \name Material Copy Operator
* \{ */
/* material copy/paste */
static int copy_material_exec(bContext *C, wmOperator * /*op*/)
static int copy_material_exec(bContext *C, wmOperator *op)
{
Material *ma = static_cast<Material *>(
CTX_data_pointer_get_type(C, "material", &RNA_Material).data);
@ -2741,7 +2751,19 @@ static int copy_material_exec(bContext *C, wmOperator * /*op*/)
return OPERATOR_CANCELLED;
}
BKE_material_copybuf_copy(CTX_data_main(C), ma);
char filepath[FILE_MAX];
Main *bmain = CTX_data_main(C);
/* Mark is the material to use (others may be expanded). */
BKE_copybuffer_copy_begin(bmain);
BKE_copybuffer_copy_tag_ID(&ma->id);
material_copybuffer_filepath_get(filepath, sizeof(filepath));
BKE_copybuffer_copy_end(bmain, filepath, op->reports);
/* We are all done! */
BKE_report(op->reports, RPT_INFO, "Copied material to internal clipboard");
return OPERATOR_FINISHED;
}
@ -2767,8 +2789,47 @@ void MATERIAL_OT_copy(wmOperatorType *ot)
/** \name Material Paste Operator
* \{ */
/**
* Clear ID's as freeing the data-block doesn't handle reference counting.
*/
static int paste_material_nodetree_ids_decref(LibraryIDLinkCallbackData *cb_data)
{
if (cb_data->cb_flag & IDWALK_CB_USER) {
id_us_min(*cb_data->id_pointer);
}
*cb_data->id_pointer = nullptr;
return IDWALK_RET_NOP;
}
/**
* Re-map ID's from the clipboard to ID's in `bmain`, by name.
*/
static int paste_material_nodetree_ids_relink_or_clear(LibraryIDLinkCallbackData *cb_data)
{
Main *bmain = static_cast<Main *>(cb_data->user_data);
ID **id_p = cb_data->id_pointer;
if (*id_p) {
if (cb_data->cb_flag & IDWALK_CB_USER) {
id_us_min(*id_p);
}
ListBase *lb = which_libbase(bmain, GS((*id_p)->name));
ID *id_local = static_cast<ID *>(
BLI_findstring(lb, (*id_p)->name + 2, offsetof(ID, name) + 2));
*id_p = id_local;
if (cb_data->cb_flag & IDWALK_CB_USER) {
id_us_plus(id_local);
}
else if (cb_data->cb_flag & IDWALK_CB_USER_ONE) {
id_us_ensure_real(id_local);
}
id_lib_extern(id_local);
}
return IDWALK_RET_NOP;
}
static int paste_material_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Material *ma = static_cast<Material *>(
CTX_data_pointer_get_type(C, "material", &RNA_Material).data);
@ -2777,11 +2838,133 @@ static int paste_material_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
if (!BKE_material_copybuf_paste(CTX_data_main(C), ma)) {
BKE_report(op->reports, RPT_WARNING, "No material in the internal clipboard to paste");
/* Read copy buffer .blend file. */
char filepath[FILE_MAX];
Main *temp_bmain = BKE_main_new();
STRNCPY(temp_bmain->filepath, BKE_main_blendfile_path_from_global());
material_copybuffer_filepath_get(filepath, sizeof(filepath));
/* NOTE(@ideasman42) The node tree might reference different kinds of ID types.
* It's not clear-cut which ID types should be included, although it's unlikely
* users would want an entire scene & it's objects to be included.
* Filter a subset of ID types with some reasons for including them. */
const uint64_t ntree_filter = (
/* Material is necessary for reading the clipboard. */
FILTER_ID_MA |
/* Node-groups. */
FILTER_ID_NT |
/* Image textures. */
FILTER_ID_IM |
/* Internal text (scripts). */
FILTER_ID_TXT |
/* Texture coordinates may reference objects.
* Note that object data is *not* included. */
FILTER_ID_OB);
if (!BKE_copybuffer_read(temp_bmain, filepath, op->reports, ntree_filter)) {
BKE_report(op->reports, RPT_ERROR, "Internal clipboard is empty");
BKE_main_free(temp_bmain);
return OPERATOR_CANCELLED;
}
/* There may be multiple materials,
* check for a property that marks this as the active material. */
Material *ma_from = nullptr;
LISTBASE_FOREACH (Material *, ma_iter, &temp_bmain->materials) {
if (ma_iter->id.flag & LIB_CLIPBOARD_MARK) {
ma_from = ma_iter;
break;
}
}
/* Make sure data from this file is usable for material paste. */
if (ma_from == nullptr) {
BKE_report(op->reports, RPT_ERROR, "Internal clipboard is not from a material");
BKE_main_free(temp_bmain);
return OPERATOR_CANCELLED;
}
/* Keep animation by moving local animation to the paste node-tree. */
if (ma->nodetree && ma_from->nodetree) {
BLI_assert(ma_from->nodetree->adt == nullptr);
std::swap(ma->nodetree->adt, ma_from->nodetree->adt);
}
/* Needed to update #SpaceNode::nodetree else a stale pointer is used. */
if (ma->nodetree) {
bNodeTree *nodetree = ma->nodetree;
BKE_libblock_remap(bmain, ma->nodetree, ma_from->nodetree, ID_REMAP_FORCE_UI_POINTERS);
/* Free & clear data here, so user counts are handled, otherwise it's
* freed as part of #BKE_main_free which doesn't handle user-counts. */
/* Walk over all the embedded nodes ID's (non-recursively). */
BKE_library_foreach_ID_link(
bmain, &nodetree->id, paste_material_nodetree_ids_decref, nullptr, IDWALK_NOP);
ntreeFreeEmbeddedTree(nodetree);
MEM_freeN(nodetree);
ma->nodetree = nullptr;
}
/* Swap data-block content, while swapping isn't always needed,
* it means memory is properly freed in the case of allocations.. */
#define SWAP_MEMBER(member) std::swap(ma->member, ma_from->member)
/* Intentionally skip:
* - Texture painting slots.
* - Preview render.
* - Grease pencil styles (we could although they reference many ID's themselves).
*/
SWAP_MEMBER(flag);
SWAP_MEMBER(r);
SWAP_MEMBER(g);
SWAP_MEMBER(b);
SWAP_MEMBER(a);
SWAP_MEMBER(specr);
SWAP_MEMBER(specg);
SWAP_MEMBER(specb);
SWAP_MEMBER(spec);
SWAP_MEMBER(roughness);
SWAP_MEMBER(metallic);
SWAP_MEMBER(use_nodes);
SWAP_MEMBER(index);
SWAP_MEMBER(nodetree);
SWAP_MEMBER(line_col);
SWAP_MEMBER(line_priority);
SWAP_MEMBER(vcol_alpha);
SWAP_MEMBER(alpha_threshold);
SWAP_MEMBER(refract_depth);
SWAP_MEMBER(blend_method);
SWAP_MEMBER(blend_shadow);
SWAP_MEMBER(blend_flag);
SWAP_MEMBER(lineart);
#undef SWAP_MEMBER
/* The node-tree from the clipboard is now assigned to the local material,
* however the ID's it references are still part of `temp_bmain`.
* These data-blocks references must be cleared or replaces with references to `bmain`.
* TODO(@ideasman42): support merging indirectly referenced data-blocks besides the material,
* this would be useful for pasting materials with node-groups between files. */
if (ma->nodetree) {
ma->nodetree->owner_id = nullptr;
/* Map remote ID's to local ones. */
BKE_library_foreach_ID_link(
bmain, &ma->nodetree->id, paste_material_nodetree_ids_relink_or_clear, bmain, IDWALK_NOP);
}
BKE_main_free(temp_bmain);
/* Important to run this when the embedded tree if freed,
* otherwise the depsgraph holds a reference to the (now freed) `ma->nodetree`.
* Also run this when a new node-tree is set to ensure it's accounted for.
* This also applies to animation data which is likely to be stored in the depsgraph.
* Always call instead of checking when it *might* be needed. */
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&ma->id, ID_RECALC_COPY_ON_WRITE);
WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, ma);
@ -2870,7 +3053,22 @@ static void paste_mtex_copybuf(ID *id)
**mtex = blender::dna::shallow_copy(mtexcopybuf);
id_us_plus((ID *)mtexcopybuf.tex);
/* NOTE(@ideasman42): the simple memory copy has no special handling for ID data-blocks.
* Ideally this would use `BKE_copybuffer_*` API's, however for common using
* copy-pasting between slots, the case a users expects to copy between files
* seems quite niche. So, do primitive ID validation. */
/* WARNING: This isn't a fool-proof solution as it's possible memory locations are reused,
* or that the ID was relocated in memory since it was copied.
* it does however guard against references to dangling pointers. */
if ((*mtex)->tex && (BLI_findindex(&G_MAIN->textures, (*mtex)->tex) == -1)) {
(*mtex)->tex = nullptr;
}
if ((*mtex)->object && (BLI_findindex(&G_MAIN->objects, (*mtex)->object) == -1)) {
(*mtex)->object = nullptr;
}
id_us_plus((ID *)(*mtex)->tex);
id_lib_extern((ID *)(*mtex)->object);
}
}

View File

@ -369,6 +369,7 @@ void PAINT_OT_face_select_all(wmOperatorType *ot);
void PAINT_OT_face_select_more(wmOperatorType *ot);
void PAINT_OT_face_select_less(wmOperatorType *ot);
void PAINT_OT_face_select_hide(wmOperatorType *ot);
void PAINT_OT_face_select_loop(wmOperatorType *ot);
void PAINT_OT_face_vert_reveal(wmOperatorType *ot);

View File

@ -1540,6 +1540,7 @@ void ED_operatortypes_paint(void)
WM_operatortype_append(PAINT_OT_face_select_more);
WM_operatortype_append(PAINT_OT_face_select_less);
WM_operatortype_append(PAINT_OT_face_select_hide);
WM_operatortype_append(PAINT_OT_face_select_loop);
WM_operatortype_append(PAINT_OT_face_vert_reveal);

View File

@ -767,6 +767,34 @@ void PAINT_OT_face_select_less(wmOperatorType *ot)
ot->srna, "face_step", true, "Face Step", "Also deselect faces that only touch on a corner");
}
static int paintface_select_loop_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const bool select = RNA_boolean_get(op->ptr, "select");
const bool extend = RNA_boolean_get(op->ptr, "extend");
if (!extend) {
paintface_deselect_all_visible(C, CTX_data_active_object(C), SEL_DESELECT, false);
}
view3d_operator_needs_opengl(C);
paintface_select_loop(C, CTX_data_active_object(C), event->mval, select);
ED_region_tag_redraw(CTX_wm_region(C));
return OPERATOR_FINISHED;
}
void PAINT_OT_face_select_loop(wmOperatorType *ot)
{
ot->name = "Select Loop";
ot->description = "Select face loop under the cursor";
ot->idname = "PAINT_OT_face_select_loop";
ot->invoke = paintface_select_loop_invoke;
ot->poll = facemask_paint_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "select", true, "Select", "If false, faces will be deselected");
RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
}
static int vert_select_all_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);

View File

@ -84,7 +84,8 @@ void ED_operatormacros_action(void)
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "ACTION_OT_duplicate");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_transform");
RNA_enum_set(otmacro->ptr, "mode", TFM_TIME_DUPLICATE);
RNA_enum_set(otmacro->ptr, "mode", TFM_TIME_TRANSLATE);
RNA_boolean_set(otmacro->ptr, "use_automerge_and_split", true);
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
}

View File

@ -497,8 +497,8 @@ void ED_operatormacros_graph(void)
"Make a copy of all selected keyframes and move them",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "GRAPH_OT_duplicate");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_transform");
RNA_enum_set(otmacro->ptr, "mode", TFM_TIME_DUPLICATE);
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_automerge_and_split", true);
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
}

View File

@ -57,6 +57,7 @@ set(SRC
tree/tree_element_driver.cc
tree/tree_element_gpencil_layer.cc
tree/tree_element_id.cc
tree/tree_element_id_curve.cc
tree/tree_element_id_library.cc
tree/tree_element_id_mesh.cc
tree/tree_element_id_scene.cc
@ -78,6 +79,7 @@ set(SRC
tree/tree_element_driver.hh
tree/tree_element_gpencil_layer.hh
tree/tree_element_id.hh
tree/tree_element_id_curve.hh
tree/tree_element_id_library.hh
tree/tree_element_id_mesh.hh
tree/tree_element_id_scene.hh

View File

@ -74,6 +74,18 @@ static void outliner_show_active(SpaceOutliner *space_outliner,
TreeElement *te,
ID *id);
/* -------------------------------------------------------------------- */
/** \name Local Utilities
* \{ */
static void outliner_copybuffer_filepath_get(char filepath[FILE_MAX], size_t filepath_maxncpy)
{
/* NOTE: this uses the same path as the 3D viewport. */
BLI_path_join(filepath, filepath_maxncpy, BKE_tempdir_base(), "copybuffer.blend");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Highlight on Cursor Motion Operator
* \{ */
@ -817,7 +829,7 @@ static int outliner_id_copy_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
BLI_path_join(filepath, sizeof(filepath), BKE_tempdir_base(), "copybuffer.blend");
outliner_copybuffer_filepath_get(filepath, sizeof(filepath));
BKE_copybuffer_copy_end(bmain, filepath, op->reports);
BKE_reportf(op->reports, RPT_INFO, "Copied %d selected data-block(s)", num_ids);
@ -851,7 +863,7 @@ static int outliner_id_paste_exec(bContext *C, wmOperator *op)
char filepath[FILE_MAX];
const short flag = FILE_AUTOSELECT | FILE_ACTIVE_COLLECTION;
BLI_path_join(filepath, sizeof(filepath), BKE_tempdir_base(), "copybuffer.blend");
outliner_copybuffer_filepath_get(filepath, sizeof(filepath));
const int num_pasted = BKE_copybuffer_paste(C, filepath, flag, op->reports, 0);
if (num_pasted == 0) {

View File

@ -552,24 +552,13 @@ static void outliner_add_id_contents(SpaceOutliner *space_outliner,
case ID_LI:
case ID_SCE:
case ID_ME:
case ID_CU_LEGACY:
BLI_assert_msg(0, "ID type expected to be expanded through new tree-element design");
break;
case ID_OB: {
outliner_add_object_contents(space_outliner, te, tselem, (Object *)id);
break;
}
case ID_CU_LEGACY: {
Curve *cu = (Curve *)id;
if (outliner_animdata_test(cu->adt)) {
outliner_add_element(space_outliner, &te->subtree, cu, te, TSE_ANIM_DATA, 0);
}
for (int a = 0; a < cu->totcol; a++) {
outliner_add_element(space_outliner, &te->subtree, cu->mat[a], te, TSE_SOME_ID, a);
}
break;
}
case ID_MB: {
MetaBall *mb = (MetaBall *)id;

View File

@ -20,6 +20,7 @@
#include "../outliner_intern.hh"
#include "common.hh"
#include "tree_element_id_curve.hh"
#include "tree_element_id_library.hh"
#include "tree_element_id_mesh.hh"
#include "tree_element_id_scene.hh"
@ -42,8 +43,9 @@ std::unique_ptr<TreeElementID> TreeElementID::createFromID(TreeElement &legacy_t
return std::make_unique<TreeElementIDScene>(legacy_te, (Scene &)id);
case ID_ME:
return std::make_unique<TreeElementIDMesh>(legacy_te, (Mesh &)id);
case ID_OB:
case ID_CU_LEGACY:
return std::make_unique<TreeElementIDCurve>(legacy_te, (Curve &)id);
case ID_OB:
case ID_MB:
case ID_MA:
case ID_TE:

View File

@ -0,0 +1,44 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup spoutliner
*/
#include "DNA_curve_types.h"
#include "DNA_listBase.h"
#include "DNA_outliner_types.h"
#include "../outliner_intern.hh"
#include "tree_element_id_curve.hh"
namespace blender::ed::outliner {
TreeElementIDCurve::TreeElementIDCurve(TreeElement &legacy_te, Curve &curve)
: TreeElementID(legacy_te, curve.id), curve_(curve)
{
}
bool TreeElementIDCurve::isExpandValid() const
{
return true;
}
void TreeElementIDCurve::expand(SpaceOutliner &space_outliner) const
{
expand_animation_data(space_outliner, curve_.adt);
expandMaterials(space_outliner);
}
void TreeElementIDCurve::expandMaterials(SpaceOutliner &space_outliner) const
{
for (int a = 0; a < curve_.totcol; a++) {
outliner_add_element(
&space_outliner, &legacy_te_.subtree, curve_.mat[a], &legacy_te_, TSE_SOME_ID, a);
}
}
} // namespace blender::ed::outliner

View File

@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup spoutliner
*/
#pragma once
#include "tree_element_id.hh"
namespace blender::ed::outliner {
class TreeElementIDCurve final : public TreeElementID {
Curve &curve_;
public:
TreeElementIDCurve(TreeElement &legacy_te, Curve &curve);
void expand(SpaceOutliner &) const override;
bool isExpandValid() const override;
private:
void expandMaterials(SpaceOutliner &) const;
};
} // namespace blender::ed::outliner

View File

@ -41,6 +41,17 @@
# include "BLI_math_base.h" /* M_PI */
#endif
/* -------------------------------------------------------------------- */
/** \name Local Utilities
* \{ */
static void view3d_copybuffer_filepath_get(char filepath[FILE_MAX], size_t filepath_maxncpy)
{
BLI_path_join(filepath, filepath_maxncpy, BKE_tempdir_base(), "copybuffer.blend");
}
/** \} */
/* ************************** copy paste ***************************** */
static int view3d_copybuffer_exec(bContext *C, wmOperator *op)
@ -60,7 +71,7 @@ static int view3d_copybuffer_exec(bContext *C, wmOperator *op)
}
CTX_DATA_END;
BLI_path_join(filepath, sizeof(filepath), BKE_tempdir_base(), "copybuffer.blend");
view3d_copybuffer_filepath_get(filepath, sizeof(filepath));
BKE_copybuffer_copy_end(bmain, filepath, op->reports);
BKE_reportf(op->reports, RPT_INFO, "Copied %d selected object(s)", num_copied);
@ -92,7 +103,7 @@ static int view3d_pastebuffer_exec(bContext *C, wmOperator *op)
flag |= FILE_ACTIVE_COLLECTION;
}
BLI_path_join(filepath, sizeof(filepath), BKE_tempdir_base(), "copybuffer.blend");
view3d_copybuffer_filepath_get(filepath, sizeof(filepath));
const int num_pasted = BKE_copybuffer_paste(C, filepath, flag, op->reports, FILTER_ID_OB);
if (num_pasted == 0) {

View File

@ -782,7 +782,7 @@ static void special_aftertrans_update__actedit(bContext *C, TransInfo *t)
bAnimContext ac;
const bool canceled = (t->state == TRANS_CANCEL);
const bool duplicate = (t->mode == TFM_TIME_DUPLICATE);
const bool duplicate = (t->flag & T_AUTOMERGE) != 0;
/* initialize relevant anim-context 'context' data */
if (ANIM_animdata_get_context(C, &ac) == 0) {

View File

@ -136,7 +136,7 @@ static void bezt_to_transdata(TransData *td,
static bool graph_edit_is_translation_mode(TransInfo *t)
{
return ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_TRANSLATE, TFM_TIME_SLIDE, TFM_TIME_DUPLICATE);
return ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_TRANSLATE, TFM_TIME_SLIDE);
}
static bool graph_edit_use_local_center(TransInfo *t)
@ -983,7 +983,7 @@ static void special_aftertrans_update__graph(bContext *C, TransInfo *t)
const bool use_handle = (sipo->flag & SIPO_NOHANDLES) == 0;
const bool canceled = (t->state == TRANS_CANCEL);
const bool duplicate = (t->mode == TFM_TIME_DUPLICATE);
const bool duplicate = (t->flag & T_AUTOMERGE) != 0;
/* initialize relevant anim-context 'context' data */
if (ANIM_animdata_get_context(C, &ac) == 0) {

View File

@ -630,7 +630,6 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
t->options |= CTX_NO_PET;
}
if (t->obedit_type == OB_MESH) {
if (op && (prop = RNA_struct_find_property(op->ptr, "use_automerge_and_split")) &&
RNA_property_is_set(op->ptr, prop))
{
@ -638,7 +637,7 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
t->flag |= T_AUTOMERGE | T_AUTOSPLIT;
}
}
else {
else if (t->obedit_type == OB_MESH) {
char automerge = t->scene->toolsettings->automerge;
if (automerge & AUTO_MERGE) {
t->flag |= T_AUTOMERGE;
@ -647,7 +646,6 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
}
}
}
}
/* Mirror is not supported with proportional editing, turn it off. */
#if 0

View File

@ -1167,17 +1167,6 @@ void transform_mode_init(TransInfo *t, wmOperator *op, const int mode)
case TFM_TIME_SCALE:
initTimeScale(t);
break;
case TFM_TIME_DUPLICATE:
/* same as TFM_TIME_EXTEND, but we need the mode info for later
* so that duplicate-culling will work properly
*/
if (ELEM(t->spacetype, SPACE_GRAPH, SPACE_NLA)) {
initTranslation(t);
}
else {
initTimeTranslate(t);
}
break;
case TFM_TIME_EXTEND:
/* now that transdata has been made, do like for TFM_TIME_TRANSLATE (for most Animation
* Editors because they have only 1D transforms for time values) or TFM_TRANSLATION

View File

@ -13,6 +13,7 @@
#include "BLI_task.h"
#include "BKE_context.h"
#include "BKE_report.h"
#include "BKE_unit.h"
#include "ED_screen.h"
@ -171,13 +172,11 @@ static void applyShrinkFatten(TransInfo *t, const int UNUSED(mval[2]))
void initShrinkFatten(TransInfo *t)
{
/* If not in mesh edit mode, fallback to Resize. */
if ((t->flag & T_EDIT) == 0 || (t->obedit_type != OB_MESH)) {
float no_mouse_dir_constraint[3];
zero_v3(no_mouse_dir_constraint);
initResize(t, no_mouse_dir_constraint);
BKE_report(t->reports, RPT_ERROR, "'Shrink/Fatten' meshes is only supported in edit mode");
t->state = TRANS_CANCEL;
}
else {
t->mode = TFM_SHRINKFATTEN;
t->transform = applyShrinkFatten;
t->handleEvent = shrinkfatten_handleEvent;
@ -200,6 +199,5 @@ void initShrinkFatten(TransInfo *t)
t->custom.mode.data = (void *)WM_modalkeymap_find_propvalue(t->keymap, TFM_MODAL_RESIZE);
}
}
}
/** \} */

View File

@ -1380,7 +1380,8 @@ static void TRANSFORM_OT_transform(struct wmOperatorType *ot)
Transform_Properties(ot,
P_ORIENT_AXIS | P_ORIENT_MATRIX | P_CONSTRAINT | P_PROPORTIONAL | P_MIRROR |
P_ALIGN_SNAP | P_GPENCIL_EDIT | P_CENTER | P_VIEW3D_NAVIGATION);
P_ALIGN_SNAP | P_GPENCIL_EDIT | P_CENTER | P_VIEW3D_NAVIGATION |
P_POST_TRANSFORM);
}
static int transform_from_gizmo_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)

View File

@ -67,6 +67,17 @@ static bool lib_id_preview_editing_poll(bContext *C)
return true;
}
static ID *lib_id_load_custom_preview_id_get(bContext *C, const wmOperator *op)
{
/* #invoke() gets the ID from context and saves it in the custom data. */
if (op->customdata) {
return static_cast<ID *>(op->customdata);
}
PointerRNA idptr = CTX_data_pointer_get(C, "id");
return static_cast<ID *>(idptr.data);
}
static int lib_id_load_custom_preview_exec(bContext *C, wmOperator *op)
{
char filepath[FILE_MAX];
@ -78,8 +89,12 @@ static int lib_id_load_custom_preview_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
PointerRNA idptr = CTX_data_pointer_get(C, "id");
ID *id = (ID *)idptr.data;
ID *id = lib_id_load_custom_preview_id_get(C, op);
if (!id) {
BKE_report(
op->reports, RPT_ERROR, "Failed to set preview: no ID in context (incorrect context?)");
return OPERATOR_CANCELLED;
}
BKE_previewimg_id_custom_set(id, filepath);
@ -88,6 +103,18 @@ static int lib_id_load_custom_preview_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
/**
* Obtain the ID from context, and spawn a File Browser to select the preview image. The
* File Browser may re-use the Asset Browser under the cursor, and clear the file-list on
* confirmation, leading to failure to obtain the ID at that point. So get it before spawning the
* File Browser (store it in the operator custom data).
*/
static int lib_id_load_custom_preview_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
op->customdata = lib_id_load_custom_preview_id_get(C, op);
return WM_operator_filesel(C, op, event);
}
static void ED_OT_lib_id_load_custom_preview(wmOperatorType *ot)
{
ot->name = "Load Custom Preview";
@ -97,7 +124,7 @@ static void ED_OT_lib_id_load_custom_preview(wmOperatorType *ot)
/* api callbacks */
ot->poll = lib_id_preview_editing_poll;
ot->exec = lib_id_load_custom_preview_exec;
ot->invoke = WM_operator_filesel;
ot->invoke = lib_id_load_custom_preview_invoke;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;

View File

@ -67,11 +67,12 @@ bool LazyFunction::always_used_inputs_available(const Params &params) const
void Params::set_default_remaining_outputs()
{
for (const int i : fn_.outputs().index_range()) {
const Span<Output> outputs = fn_.outputs();
for (const int i : outputs.index_range()) {
if (this->output_was_set(i)) {
continue;
}
const Output &fn_output = fn_.outputs()[i];
const Output &fn_output = outputs[i];
const CPPType &type = *fn_output.type;
void *data_ptr = this->get_output_data_ptr(i);
type.value_initialize(data_ptr);

View File

@ -19,6 +19,7 @@ set(SRC
intern/add_curves_on_mesh.cc
intern/curve_constraints.cc
intern/fillet_curves.cc
intern/mesh_copy_selection.cc
intern/mesh_flip_faces.cc
intern/mesh_merge_by_distance.cc
intern/mesh_primitive_cuboid.cc
@ -43,6 +44,7 @@ set(SRC
GEO_mesh_merge_by_distance.hh
GEO_mesh_primitive_cuboid.hh
GEO_mesh_split_edges.hh
GEO_mesh_copy_selection.hh
GEO_mesh_to_curve.hh
GEO_mesh_to_volume.hh
GEO_point_merge_by_distance.hh

View File

@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <optional>
#include "BLI_index_mask.hh"
#include "BKE_attribute.h"
struct Mesh;
namespace blender {
namespace fn {
template<typename T> class Field;
}
namespace bke {
class AnonymousAttributePropagationInfo;
}
} // namespace blender
namespace blender::geometry {
std::optional<Mesh *> mesh_copy_selection(
const Mesh &src_mesh,
const fn::Field<bool> &selection,
eAttrDomain selection_domain,
const bke::AnonymousAttributePropagationInfo &propagation_info);
std::optional<Mesh *> mesh_copy_selection_keep_verts(
const Mesh &src_mesh,
const fn::Field<bool> &selection,
eAttrDomain selection_domain,
const bke::AnonymousAttributePropagationInfo &propagation_info);
std::optional<Mesh *> mesh_copy_selection_keep_edges(
const Mesh &mesh,
const fn::Field<bool> &selection,
eAttrDomain selection_domain,
const bke::AnonymousAttributePropagationInfo &propagation_info);
} // namespace blender::geometry

View File

@ -0,0 +1,520 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_index_mask.hh"
#include "BKE_attribute.hh"
#include "BKE_geometry_fields.hh"
#include "BKE_mesh.hh"
#include "GEO_mesh_copy_selection.hh"
namespace blender::geometry {
static void create_reverse_map(const IndexMask &mask, MutableSpan<int> r_map)
{
#ifdef DEBUG
r_map.fill(-1);
#endif
mask.foreach_index_optimized<int>(
GrainSize(4096), [&](const int src_i, const int dst_i) { r_map[src_i] = dst_i; });
}
static void remap_verts(const OffsetIndices<int> src_polys,
const OffsetIndices<int> dst_polys,
const int src_verts_num,
const IndexMask &vert_mask,
const IndexMask &edge_mask,
const IndexMask &poly_mask,
const Span<int2> src_edges,
const Span<int> src_corner_verts,
MutableSpan<int2> dst_edges,
MutableSpan<int> dst_corner_verts)
{
Array<int> map(src_verts_num);
create_reverse_map(vert_mask, map);
threading::parallel_invoke(
vert_mask.size() > 1024,
[&]() {
poly_mask.foreach_index(GrainSize(512), [&](const int64_t src_i, const int64_t dst_i) {
const IndexRange src_poly = src_polys[src_i];
const IndexRange dst_poly = dst_polys[dst_i];
for (const int i : src_poly.index_range()) {
dst_corner_verts[dst_poly[i]] = map[src_corner_verts[src_poly[i]]];
}
});
},
[&]() {
edge_mask.foreach_index(GrainSize(512), [&](const int64_t src_i, const int64_t dst_i) {
dst_edges[dst_i][0] = map[src_edges[src_i][0]];
dst_edges[dst_i][1] = map[src_edges[src_i][1]];
});
});
}
static void remap_edges(const OffsetIndices<int> src_polys,
const OffsetIndices<int> dst_polys,
const int src_edges_num,
const IndexMask &edge_mask,
const IndexMask &poly_mask,
const Span<int> src_corner_edges,
MutableSpan<int> dst_corner_edges)
{
Array<int> map(src_edges_num);
create_reverse_map(edge_mask, map);
poly_mask.foreach_index(GrainSize(512), [&](const int64_t src_i, const int64_t dst_i) {
const IndexRange src_poly = src_polys[src_i];
const IndexRange dst_poly = dst_polys[dst_i];
for (const int i : src_poly.index_range()) {
dst_corner_edges[dst_poly[i]] = map[src_corner_edges[src_poly[i]]];
}
});
}
/** A vertex is selected if it's used by a selected edge. */
static IndexMask vert_selection_from_edge(const Span<int2> edges,
const IndexMask &edge_mask,
const int verts_num,
IndexMaskMemory &memory)
{
Array<bool> array(verts_num, false);
edge_mask.foreach_index_optimized<int>([&](const int i) {
array[edges[i][0]] = true;
array[edges[i][1]] = true;
});
return IndexMask::from_bools(array, memory);
}
static IndexMask mapped_corner_selection_from_poly(const OffsetIndices<int> polys,
const IndexMask &poly_mask,
const Span<int> corner_verts_or_edges,
const int verts_or_edges_num,
IndexMaskMemory &memory)
{
Array<bool> array(verts_or_edges_num, false);
poly_mask.foreach_index(GrainSize(512), [&](const int64_t i) {
array.as_mutable_span().fill_indices(corner_verts_or_edges.slice(polys[i]), true);
});
return IndexMask::from_bools(array, memory);
}
/** A vertex is selected if it is used by a selected face. */
static IndexMask vert_selection_from_poly(const OffsetIndices<int> polys,
const IndexMask &poly_mask,
const Span<int> corner_verts,
const int verts_num,
IndexMaskMemory &memory)
{
return mapped_corner_selection_from_poly(polys, poly_mask, corner_verts, verts_num, memory);
}
/** An edge is selected if it is used by a selected face. */
static IndexMask edge_selection_from_poly(const OffsetIndices<int> polys,
const IndexMask &poly_mask,
const Span<int> corner_edges,
const int edges_num,
IndexMaskMemory &memory)
{
return mapped_corner_selection_from_poly(polys, poly_mask, corner_edges, edges_num, memory);
}
/** An edge is selected if both of its vertices are selected. */
static IndexMask edge_selection_from_vert(const Span<int2> edges,
const Span<bool> vert_selection,
IndexMaskMemory &memory)
{
return IndexMask::from_predicate(
edges.index_range(), GrainSize(1024), memory, [&](const int64_t i) {
const int2 edge = edges[i];
return vert_selection[edge[0]] && vert_selection[edge[1]];
});
}
static IndexMask poly_selection_from_mapped_corner(const OffsetIndices<int> polys,
const Span<int> corner_verts_or_edges,
const Span<bool> vert_or_edge_selection,
IndexMaskMemory &memory)
{
return IndexMask::from_predicate(
polys.index_range(), GrainSize(1024), memory, [&](const int64_t i) {
const Span<int> indices = corner_verts_or_edges.slice(polys[i]);
return std::all_of(indices.begin(), indices.end(), [&](const int i) {
return vert_or_edge_selection[i];
});
});
}
/** A face is selected if all of its vertices are selected. */
static IndexMask poly_selection_from_vert(const OffsetIndices<int> polys,
const Span<int> corner_verts,
const Span<bool> vert_selection,
IndexMaskMemory &memory)
{
return poly_selection_from_mapped_corner(polys, corner_verts, vert_selection, memory);
}
/** A face is selected if all of its edges are selected. */
static IndexMask poly_selection_from_edge(const OffsetIndices<int> polys,
const Span<int> corner_edges,
const Span<bool> edge_mask,
IndexMaskMemory &memory)
{
return poly_selection_from_mapped_corner(polys, corner_edges, edge_mask, memory);
}
/** Create a mesh with no built-in attributes. */
static Mesh *create_mesh_no_attributes(const Mesh &params_mesh,
const int verts_num,
const int edges_num,
const int polys_num,
const int corners_num)
{
Mesh *mesh = BKE_mesh_new_nomain(0, 0, polys_num, 0);
mesh->totvert = verts_num;
mesh->totedge = edges_num;
mesh->totloop = corners_num;
CustomData_free_layer_named(&mesh->vdata, "position", 0);
CustomData_free_layer_named(&mesh->edata, ".edge_verts", 0);
CustomData_free_layer_named(&mesh->ldata, ".corner_vert", 0);
CustomData_free_layer_named(&mesh->ldata, ".corner_edge", 0);
BKE_mesh_copy_parameters_for_eval(mesh, &params_mesh);
return mesh;
}
static void copy_loose_vert_hint(const Mesh &src, Mesh &dst)
{
const auto &src_cache = src.runtime->loose_verts_cache;
if (src_cache.is_cached() && src_cache.data().count == 0) {
dst.tag_loose_verts_none();
}
}
static void copy_loose_edge_hint(const Mesh &src, Mesh &dst)
{
const auto &src_cache = src.runtime->loose_edges_cache;
if (src_cache.is_cached() && src_cache.data().count == 0) {
dst.tag_loose_edges_none();
}
}
std::optional<Mesh *> mesh_copy_selection(
const Mesh &src_mesh,
const fn::Field<bool> &selection_field,
const eAttrDomain selection_domain,
const bke::AnonymousAttributePropagationInfo &propagation_info)
{
const Span<int2> src_edges = src_mesh.edges();
const OffsetIndices src_polys = src_mesh.polys();
const Span<int> src_corner_verts = src_mesh.corner_verts();
const Span<int> src_corner_edges = src_mesh.corner_edges();
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
const bke::MeshFieldContext context(src_mesh, selection_domain);
fn::FieldEvaluator evaluator(context, src_attributes.domain_size(selection_domain));
evaluator.add(selection_field);
evaluator.evaluate();
const VArray<bool> selection = evaluator.get_evaluated<bool>(0);
if (selection.is_empty()) {
return std::nullopt;
}
if (const std::optional<bool> single = selection.get_if_single()) {
return *single ? std::nullopt : std::make_optional<Mesh *>(nullptr);
}
threading::EnumerableThreadSpecific<IndexMaskMemory> memory;
IndexMask vert_mask;
IndexMask edge_mask;
IndexMask poly_mask;
switch (selection_domain) {
case ATTR_DOMAIN_POINT: {
const VArraySpan<bool> span(selection);
threading::parallel_invoke(
src_mesh.totvert > 1024,
[&]() { vert_mask = IndexMask::from_bools(span, memory.local()); },
[&]() { edge_mask = edge_selection_from_vert(src_edges, span, memory.local()); },
[&]() {
poly_mask = poly_selection_from_vert(
src_polys, src_corner_verts, span, memory.local());
});
break;
}
case ATTR_DOMAIN_EDGE: {
const VArraySpan<bool> span(selection);
threading::parallel_invoke(
src_edges.size() > 1024,
[&]() {
edge_mask = IndexMask::from_bools(span, memory.local());
vert_mask = vert_selection_from_edge(
src_edges, edge_mask, src_mesh.totvert, memory.local());
},
[&]() {
poly_mask = poly_selection_from_edge(
src_polys, src_corner_edges, span, memory.local());
});
break;
}
case ATTR_DOMAIN_FACE: {
const VArraySpan<bool> span(selection);
poly_mask = IndexMask::from_bools(span, memory.local());
threading::parallel_invoke(
poly_mask.size() > 1024,
[&]() {
vert_mask = vert_selection_from_poly(
src_polys, poly_mask, src_corner_verts, src_mesh.totvert, memory.local());
},
[&]() {
edge_mask = edge_selection_from_poly(
src_polys, poly_mask, src_corner_edges, src_mesh.totedge, memory.local());
});
break;
}
default:
BLI_assert_unreachable();
break;
}
if (vert_mask.is_empty()) {
return nullptr;
}
if (vert_mask.size() == src_mesh.totvert) {
return std::nullopt;
}
Mesh *dst_mesh = create_mesh_no_attributes(
src_mesh, vert_mask.size(), edge_mask.size(), poly_mask.size(), 0);
bke::MutableAttributeAccessor dst_attributes = dst_mesh->attributes_for_write();
dst_attributes.add<int2>(".edge_verts", ATTR_DOMAIN_EDGE, bke::AttributeInitConstruct());
MutableSpan<int2> dst_edges = dst_mesh->edges_for_write();
const OffsetIndices<int> dst_polys = offset_indices::gather_selected_offsets(
src_polys, poly_mask, dst_mesh->poly_offsets_for_write());
dst_mesh->totloop = dst_polys.total_size();
dst_attributes.add<int>(".corner_vert", ATTR_DOMAIN_CORNER, bke::AttributeInitConstruct());
dst_attributes.add<int>(".corner_edge", ATTR_DOMAIN_CORNER, bke::AttributeInitConstruct());
MutableSpan<int> dst_corner_verts = dst_mesh->corner_verts_for_write();
MutableSpan<int> dst_corner_edges = dst_mesh->corner_edges_for_write();
threading::parallel_invoke(
vert_mask.size() > 1024,
[&]() {
remap_verts(src_polys,
dst_polys,
src_mesh.totvert,
vert_mask,
edge_mask,
poly_mask,
src_edges,
src_corner_verts,
dst_edges,
dst_corner_verts);
},
[&]() {
remap_edges(src_polys,
dst_polys,
src_edges.size(),
edge_mask,
poly_mask,
src_corner_edges,
dst_corner_edges);
},
[&]() {
bke::gather_attributes(
src_attributes, ATTR_DOMAIN_POINT, propagation_info, {}, vert_mask, dst_attributes);
bke::gather_attributes(src_attributes,
ATTR_DOMAIN_EDGE,
propagation_info,
{".edge_verts"},
edge_mask,
dst_attributes);
bke::gather_attributes(
src_attributes, ATTR_DOMAIN_FACE, propagation_info, {}, poly_mask, dst_attributes);
bke::gather_attributes_group_to_group(src_attributes,
ATTR_DOMAIN_CORNER,
propagation_info,
{".corner_edge", ".corner_vert"},
src_polys,
dst_polys,
poly_mask,
dst_attributes);
});
if (selection_domain == ATTR_DOMAIN_EDGE) {
copy_loose_vert_hint(src_mesh, *dst_mesh);
}
else if (selection_domain == ATTR_DOMAIN_FACE) {
copy_loose_vert_hint(src_mesh, *dst_mesh);
copy_loose_edge_hint(src_mesh, *dst_mesh);
}
return dst_mesh;
}
std::optional<Mesh *> mesh_copy_selection_keep_verts(
const Mesh &src_mesh,
const fn::Field<bool> &selection_field,
const eAttrDomain selection_domain,
const bke::AnonymousAttributePropagationInfo &propagation_info)
{
const Span<int2> src_edges = src_mesh.edges();
const OffsetIndices src_polys = src_mesh.polys();
const Span<int> src_corner_verts = src_mesh.corner_verts();
const Span<int> src_corner_edges = src_mesh.corner_edges();
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
const bke::MeshFieldContext context(src_mesh, selection_domain);
fn::FieldEvaluator evaluator(context, src_attributes.domain_size(selection_domain));
evaluator.add(selection_field);
evaluator.evaluate();
const VArray<bool> selection = evaluator.get_evaluated<bool>(0);
if (selection.is_empty()) {
return std::nullopt;
}
threading::EnumerableThreadSpecific<IndexMaskMemory> memory;
IndexMask edge_mask;
IndexMask poly_mask;
switch (selection_domain) {
case ATTR_DOMAIN_POINT: {
const VArraySpan<bool> span(selection);
threading::parallel_invoke(
src_edges.size() > 1024,
[&]() { edge_mask = edge_selection_from_vert(src_edges, span, memory.local()); },
[&]() {
poly_mask = poly_selection_from_vert(
src_polys, src_corner_verts, span, memory.local());
});
break;
}
case ATTR_DOMAIN_EDGE: {
const VArraySpan<bool> span(selection);
threading::parallel_invoke(
src_edges.size() > 1024,
[&]() { edge_mask = IndexMask::from_bools(span, memory.local()); },
[&]() {
poly_mask = poly_selection_from_edge(
src_polys, src_corner_edges, span, memory.local());
});
break;
}
case ATTR_DOMAIN_FACE: {
const VArraySpan<bool> span(selection);
poly_mask = IndexMask::from_bools(span, memory.local());
edge_mask = edge_selection_from_poly(
src_polys, poly_mask, src_corner_edges, src_edges.size(), memory.local());
break;
}
default:
BLI_assert_unreachable();
break;
}
Mesh *dst_mesh = create_mesh_no_attributes(
src_mesh, src_mesh.totvert, edge_mask.size(), poly_mask.size(), 0);
bke::MutableAttributeAccessor dst_attributes = dst_mesh->attributes_for_write();
const OffsetIndices<int> dst_polys = offset_indices::gather_selected_offsets(
src_polys, poly_mask, dst_mesh->poly_offsets_for_write());
dst_mesh->totloop = dst_polys.total_size();
dst_attributes.add<int>(".corner_edge", ATTR_DOMAIN_CORNER, bke::AttributeInitConstruct());
MutableSpan<int> dst_corner_edges = dst_mesh->corner_edges_for_write();
threading::parallel_invoke(
[&]() {
remap_edges(src_polys,
dst_polys,
src_edges.size(),
edge_mask,
poly_mask,
src_corner_edges,
dst_corner_edges);
},
[&]() {
bke::copy_attributes(
src_attributes, ATTR_DOMAIN_POINT, propagation_info, {}, dst_attributes);
bke::gather_attributes(
src_attributes, ATTR_DOMAIN_EDGE, propagation_info, {}, edge_mask, dst_attributes);
bke::gather_attributes(
src_attributes, ATTR_DOMAIN_FACE, propagation_info, {}, poly_mask, dst_attributes);
bke::gather_attributes_group_to_group(src_attributes,
ATTR_DOMAIN_CORNER,
propagation_info,
{".corner_edge"},
src_polys,
dst_polys,
poly_mask,
dst_attributes);
});
/* Positions are not changed by the operation, so the bounds are the same. */
dst_mesh->runtime->bounds_cache = src_mesh.runtime->bounds_cache;
copy_loose_vert_hint(src_mesh, *dst_mesh);
return dst_mesh;
}
std::optional<Mesh *> mesh_copy_selection_keep_edges(
const Mesh &src_mesh,
const fn::Field<bool> &selection_field,
const eAttrDomain selection_domain,
const bke::AnonymousAttributePropagationInfo &propagation_info)
{
const OffsetIndices src_polys = src_mesh.polys();
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
const bke::MeshFieldContext context(src_mesh, selection_domain);
fn::FieldEvaluator evaluator(context, src_attributes.domain_size(selection_domain));
evaluator.add(selection_field);
evaluator.evaluate();
const VArray<bool> selection = evaluator.get_evaluated<bool>(0);
if (selection.is_empty()) {
return std::nullopt;
}
IndexMaskMemory memory;
IndexMask poly_mask;
switch (selection_domain) {
case ATTR_DOMAIN_POINT:
poly_mask = poly_selection_from_vert(
src_polys, src_mesh.corner_verts(), VArraySpan(selection), memory);
break;
case ATTR_DOMAIN_EDGE:
poly_mask = poly_selection_from_edge(
src_polys, src_mesh.corner_edges(), VArraySpan(selection), memory);
break;
case ATTR_DOMAIN_FACE:
poly_mask = IndexMask::from_bools(selection, memory);
break;
default:
BLI_assert_unreachable();
break;
}
Mesh *dst_mesh = create_mesh_no_attributes(
src_mesh, src_mesh.totvert, src_mesh.totedge, poly_mask.size(), 0);
bke::MutableAttributeAccessor dst_attributes = dst_mesh->attributes_for_write();
const OffsetIndices<int> dst_polys = offset_indices::gather_selected_offsets(
src_polys, poly_mask, dst_mesh->poly_offsets_for_write());
dst_mesh->totloop = dst_polys.total_size();
dst_attributes.add<int>(".corner_vert", ATTR_DOMAIN_CORNER, bke::AttributeInitConstruct());
dst_attributes.add<int>(".corner_edge", ATTR_DOMAIN_CORNER, bke::AttributeInitConstruct());
bke::copy_attributes(src_attributes, ATTR_DOMAIN_POINT, propagation_info, {}, dst_attributes);
bke::copy_attributes(src_attributes, ATTR_DOMAIN_EDGE, propagation_info, {}, dst_attributes);
bke::gather_attributes(
src_attributes, ATTR_DOMAIN_FACE, propagation_info, {}, poly_mask, dst_attributes);
bke::gather_attributes_group_to_group(src_attributes,
ATTR_DOMAIN_CORNER,
propagation_info,
{},
src_polys,
dst_polys,
poly_mask,
dst_attributes);
/* Positions are not changed by the operation, so the bounds are the same. */
dst_mesh->runtime->bounds_cache = src_mesh.runtime->bounds_cache;
copy_loose_vert_hint(src_mesh, *dst_mesh);
return dst_mesh;
}
} // namespace blender::geometry

View File

@ -375,7 +375,7 @@ class UVAABBIsland {
*/
static void pack_islands_alpaca_turbo(const int64_t exclude_index,
const rctf &exclude,
const Span<UVAABBIsland *> islands,
const Span<std::unique_ptr<UVAABBIsland>> islands,
const float target_aspect_y,
MutableSpan<uv_phi> r_phis,
rctf *r_extent)
@ -390,9 +390,9 @@ static void pack_islands_alpaca_turbo(const int64_t exclude_index,
/* Visit every island in order, except the excluded islands at the start. */
for (int64_t index = exclude_index; index < islands.size(); index++) {
UVAABBIsland *island = islands[index];
const float dsm_u = island->uv_diagonal.x;
const float dsm_v = island->uv_diagonal.y;
UVAABBIsland &island = *islands[index];
const float dsm_u = island.uv_diagonal.x;
const float dsm_v = island.uv_diagonal.y;
bool restart = false;
if (zigzag) {
@ -409,7 +409,7 @@ static void pack_islands_alpaca_turbo(const int64_t exclude_index,
}
/* Place the island. */
uv_phi &phi = r_phis[island->index];
uv_phi &phi = r_phis[island.index];
phi.rotation = 0.0f;
phi.translation.x = u0 + dsm_u * 0.5f;
phi.translation.y = v0 + dsm_v * 0.5f;
@ -486,7 +486,7 @@ static void update_hole_rotate(float2 &hole,
*/
static void pack_islands_alpaca_rotate(const int64_t exclude_index,
const rctf &exclude,
const Span<UVAABBIsland *> islands,
const Span<std::unique_ptr<UVAABBIsland>> islands,
const float target_aspect_y,
MutableSpan<uv_phi> r_phis,
rctf *r_extent)
@ -506,30 +506,30 @@ static void pack_islands_alpaca_rotate(const int64_t exclude_index,
/* Visit every island in order, except the excluded islands at the start. */
for (int64_t index = exclude_index; index < islands.size(); index++) {
UVAABBIsland *island = islands[index];
uv_phi &phi = r_phis[island->index];
const float uvdiag_x = island->uv_diagonal.x * island->aspect_y;
float min_dsm = std::min(uvdiag_x, island->uv_diagonal.y);
float max_dsm = std::max(uvdiag_x, island->uv_diagonal.y);
UVAABBIsland &island = *islands[index];
uv_phi &phi = r_phis[island.index];
const float uvdiag_x = island.uv_diagonal.x * island.aspect_y;
float min_dsm = std::min(uvdiag_x, island.uv_diagonal.y);
float max_dsm = std::max(uvdiag_x, island.uv_diagonal.y);
if (min_dsm < hole_diagonal.x && max_dsm < hole_diagonal.y) {
/* Place island in the hole. */
if (hole_rotate == (min_dsm == island->uv_diagonal.x)) {
if (hole_rotate == (min_dsm == island.uv_diagonal.x)) {
phi.rotation = DEG2RADF(90.0f);
phi.translation.x = hole[0] + island->uv_diagonal.y * 0.5f / island->aspect_y;
phi.translation.y = hole[1] + island->uv_diagonal.x * 0.5f * island->aspect_y;
phi.translation.x = hole[0] + island.uv_diagonal.y * 0.5f / island.aspect_y;
phi.translation.y = hole[1] + island.uv_diagonal.x * 0.5f * island.aspect_y;
}
else {
phi.rotation = 0.0f;
phi.translation.x = hole[0] + island->uv_diagonal.x * 0.5f;
phi.translation.y = hole[1] + island->uv_diagonal.y * 0.5f;
phi.translation.x = hole[0] + island.uv_diagonal.x * 0.5f;
phi.translation.y = hole[1] + island.uv_diagonal.y * 0.5f;
}
/* Update space left in the hole. */
float p[6];
p[0] = hole[0];
p[1] = hole[1];
p[2] = hole[0] + (hole_rotate ? max_dsm : min_dsm) / island->aspect_y;
p[2] = hole[0] + (hole_rotate ? max_dsm : min_dsm) / island.aspect_y;
p[3] = hole[1] + (hole_rotate ? min_dsm : max_dsm);
p[4] = hole[0] + (hole_rotate ? hole_diagonal.y : hole_diagonal.x);
p[5] = hole[1] + (hole_rotate ? hole_diagonal.x : hole_diagonal.y);
@ -546,7 +546,7 @@ static void pack_islands_alpaca_rotate(const int64_t exclude_index,
restart = (next_v1 < v0 + min_dsm);
}
else {
restart = (next_u1 < u0 + min_dsm / island->aspect_y);
restart = (next_u1 < u0 + min_dsm / island.aspect_y);
}
if (restart) {
update_hole_rotate(hole, hole_diagonal, hole_rotate, u0, v0, next_u1, next_v1);
@ -559,25 +559,25 @@ static void pack_islands_alpaca_rotate(const int64_t exclude_index,
/* Place the island. */
if (zigzag == (min_dsm == uvdiag_x)) {
phi.rotation = DEG2RADF(90.0f);
phi.translation.x = u0 + island->uv_diagonal.y * 0.5f / island->aspect_y;
phi.translation.y = v0 + island->uv_diagonal.x * 0.5f * island->aspect_y;
phi.translation.x = u0 + island.uv_diagonal.y * 0.5f / island.aspect_y;
phi.translation.y = v0 + island.uv_diagonal.x * 0.5f * island.aspect_y;
}
else {
phi.rotation = 0.0f;
phi.translation.x = u0 + island->uv_diagonal.x * 0.5f;
phi.translation.y = v0 + island->uv_diagonal.y * 0.5f;
phi.translation.x = u0 + island.uv_diagonal.x * 0.5f;
phi.translation.y = v0 + island.uv_diagonal.y * 0.5f;
}
/* Move according to the "Alpaca rules", with rotation. */
if (zigzag) {
/* Move upwards. */
v0 += min_dsm;
next_u1 = max_ff(next_u1, u0 + max_dsm / island->aspect_y);
next_u1 = max_ff(next_u1, u0 + max_dsm / island.aspect_y);
next_v1 = max_ff(next_v1, v0);
}
else {
/* Move sideways. */
u0 += min_dsm / island->aspect_y;
u0 += min_dsm / island.aspect_y;
next_v1 = max_ff(next_v1, v0 + max_dsm);
next_u1 = max_ff(next_u1, u0);
}
@ -592,7 +592,7 @@ static void pack_islands_alpaca_rotate(const int64_t exclude_index,
*/
static void pack_islands_fast(const int64_t exclude_index,
const rctf &exclude,
const Span<UVAABBIsland *> aabbs,
const Span<std::unique_ptr<UVAABBIsland>> aabbs,
const bool rotate,
const float target_aspect_y,
MutableSpan<uv_phi> r_phis,
@ -607,7 +607,7 @@ static void pack_islands_fast(const int64_t exclude_index,
}
/** Frits Göbel, 1979. */
static void pack_gobel(const Span<UVAABBIsland *> aabbs,
static void pack_gobel(const Span<std::unique_ptr<UVAABBIsland>> aabbs,
const float scale,
const int m,
MutableSpan<uv_phi> r_phis)
@ -643,7 +643,7 @@ static void pack_gobel(const Span<UVAABBIsland *> aabbs,
}
}
/* Attempt to find an "Optimal" packing of the islands, e.g. assuming squares or circles. */
static void pack_islands_optimal_pack(const Span<UVAABBIsland *> aabbs,
static void pack_islands_optimal_pack(const Span<std::unique_ptr<UVAABBIsland>> aabbs,
const UVPackIsland_Params &params,
MutableSpan<uv_phi> r_phis,
rctf *r_extent)
@ -694,7 +694,7 @@ static void pack_islands_optimal_pack(const Span<UVAABBIsland *> aabbs,
}
/* Wrapper around #BLI_box_pack_2d. */
static void pack_island_box_pack_2d(const Span<UVAABBIsland *> aabbs,
static void pack_island_box_pack_2d(const Span<std::unique_ptr<UVAABBIsland>> aabbs,
const UVPackIsland_Params &params,
MutableSpan<uv_phi> r_phis,
rctf *r_extent)
@ -1093,7 +1093,7 @@ class UVMinimumEnclosingSquareFinder {
* If that square is smaller than `r_extent`, then update `r_phis` accordingly.
* \return True if `r_phis` and `r_extent` are modified.
*/
static bool rotate_inside_square(const Span<UVAABBIsland *> island_indices,
static bool rotate_inside_square(const Span<std::unique_ptr<UVAABBIsland>> island_indices,
const Span<PackIsland *> islands,
const UVPackIsland_Params &params,
const float scale,
@ -1109,7 +1109,7 @@ static bool rotate_inside_square(const Span<UVAABBIsland *> island_indices,
}
if (params.shape_method == ED_UVPACK_SHAPE_AABB) {
/* AABB margin calculations are not preserved under rotations. */
if (island_indices.size() > 1) { /* Unless there's only one island...*/
if (island_indices.size() > 1) { /* Unless there's only one island. */
if (params.target_aspect_y != 1.0f) {
/* TODO: Check for possible 90 degree rotation. */
@ -1194,7 +1194,7 @@ static bool rotate_inside_square(const Span<UVAABBIsland *> island_indices,
* Performance would normally be `O(n^4)`, however the occupancy
* bitmap_radix is fixed, which gives a reduced time complexity of `O(n^3)`.
*/
static int64_t pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
static int64_t pack_island_xatlas(const Span<std::unique_ptr<UVAABBIsland>> island_indices,
const Span<PackIsland *> islands,
const float scale,
const float margin,
@ -1402,27 +1402,28 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
const bool all_can_rotate = can_rotate(islands, params);
/* First, copy information from our input into the AABB structure. */
Array<UVAABBIsland *> aabbs(islands.size());
Array<std::unique_ptr<UVAABBIsland>> aabbs(islands.size());
for (const int64_t i : islands.index_range()) {
PackIsland *pack_island = islands[i];
float island_scale = scale;
if (!pack_island->can_scale_(params)) {
island_scale = 1.0f;
}
UVAABBIsland *aabb = new UVAABBIsland();
std::unique_ptr<UVAABBIsland> aabb = std::make_unique<UVAABBIsland>();
aabb->index = i;
aabb->uv_diagonal.x = pack_island->half_diagonal_.x * 2 * island_scale + 2 * margin;
aabb->uv_diagonal.y = pack_island->half_diagonal_.y * 2 * island_scale + 2 * margin;
aabb->aspect_y = pack_island->aspect_y;
aabbs[i] = aabb;
aabbs[i] = std::move(aabb);
}
/* Sort from "biggest" to "smallest". */
if (all_can_rotate) {
std::stable_sort(aabbs.begin(),
std::stable_sort(
aabbs.begin(),
aabbs.end(),
[&params, &islands](const UVAABBIsland *a, const UVAABBIsland *b) {
[&](const std::unique_ptr<UVAABBIsland> &a, const std::unique_ptr<UVAABBIsland> &b) {
const bool can_translate_a = islands[a->index]->can_translate_(params);
const bool can_translate_b = islands[b->index]->can_translate_(params);
if (can_translate_a != can_translate_b) {
@ -1451,10 +1452,10 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
});
}
else {
std::stable_sort(aabbs.begin(),
std::stable_sort(
aabbs.begin(),
aabbs.end(),
[&params, &islands](const UVAABBIsland *a, const UVAABBIsland *b) {
[&](const std::unique_ptr<UVAABBIsland> &a, const std::unique_ptr<UVAABBIsland> &b) {
const bool can_translate_a = islands[a->index]->can_translate_(params);
const bool can_translate_b = islands[b->index]->can_translate_(params);
if (can_translate_a != can_translate_b) {
@ -1462,8 +1463,7 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
}
/* Choose the AABB with larger rectangular area. */
return b->uv_diagonal.x * b->uv_diagonal.y <
a->uv_diagonal.x * a->uv_diagonal.y;
return b->uv_diagonal.x * b->uv_diagonal.y < a->uv_diagonal.x * a->uv_diagonal.y;
});
}
@ -1499,7 +1499,7 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
alpaca_cutoff = std::max(alpaca_cutoff, locked_island_count); /* ...TODO... */
Span<UVAABBIsland *> slow_aabbs = aabbs.as_span().take_front(
Span<std::unique_ptr<UVAABBIsland>> slow_aabbs = aabbs.as_span().take_front(
std::min(alpaca_cutoff, islands.size()));
rctf extent = {0.0f, 1e30f, 0.0f, 1e30f};
@ -1546,13 +1546,6 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
r_phis,
&final_extent);
/* Housekeeping. */
for (const int64_t i : aabbs.index_range()) {
UVAABBIsland *aabb = aabbs[i];
aabbs[i] = nullptr;
delete aabb;
}
return get_aspect_scaled_extent(final_extent, params);
}

View File

@ -1070,8 +1070,8 @@ id<MTLBuffer> MTLBatch::get_emulated_toplogy_buffer(GPUPrimType &in_out_prim_typ
case GPU_PRIM_LINE_LOOP: {
int line = 0;
for (line = 0; line < output_prim_count - 1; line++) {
data[line * 3 + 0] = line + 0;
data[line * 3 + 1] = line + 1;
data[line * 2 + 0] = line + 0;
data[line * 2 + 1] = line + 1;
}
/* Closing line. */
data[line * 2 + 0] = line + 0;

View File

@ -11,7 +11,7 @@
#include "BKE_global.h"
#include "CLG_log.h"
/** Options for organising Metal GPU debug captures. */
/** Options for organizing Metal GPU debug captures. */
/* Maximum nested debug group depth. Groups beyond this will still have the pass name pulled into
* the RenderCommandEncoder, but will not display in the trace.
* Use -1 for unlimited. */

View File

@ -5,6 +5,7 @@
#pragma once
#include <atomic>
#include <ctime>
#include <functional>
#include <map>
#include <mutex>
@ -233,17 +234,20 @@ class MTLCircularBuffer {
struct MTLBufferHandle {
gpu::MTLBuffer *buffer;
uint64_t buffer_size;
time_t insert_time;
inline MTLBufferHandle(gpu::MTLBuffer *buf)
{
this->buffer = buf;
this->buffer_size = this->buffer->get_size();
this->insert_time = std::time(nullptr);
}
inline MTLBufferHandle(uint64_t compare_size)
{
this->buffer = nullptr;
this->buffer_size = compare_size;
this->insert_time = 0;
}
};
@ -356,7 +360,6 @@ class MTLBufferPool {
/* Debug statistics. */
std::atomic<int> per_frame_allocation_count_;
std::atomic<int64_t> allocations_in_pool_;
std::atomic<int64_t> buffers_in_pool_;
#endif
@ -401,6 +404,7 @@ class MTLBufferPool {
/* MTLBuffer::free() can be called from separate threads, due to usage within animation
* system/worker threads. */
std::atomic<MTLSafeFreeList *> current_free_list_;
std::atomic<int64_t> allocations_in_pool_;
public:
void init(id<MTLDevice> device);

View File

@ -11,6 +11,12 @@
using namespace blender;
using namespace blender::gpu;
/* Memory size in bytes macros, used as pool flushing frequency thresholds. */
#define MEMORY_SIZE_2GB 2147483648LL
#define MEMORY_SIZE_1GB 1073741824LL
#define MEMORY_SIZE_512MB 536870912LL
#define MEMORY_SIZE_256MB 268435456LL
namespace blender::gpu {
/* -------------------------------------------------------------------- */
@ -28,9 +34,10 @@ void MTLBufferPool::init(id<MTLDevice> mtl_device)
/* Debug statistics. */
total_allocation_bytes_ = 0;
per_frame_allocation_count_ = 0;
allocations_in_pool_ = 0;
buffers_in_pool_ = 0;
#endif
/* Track pool allocation size. */
allocations_in_pool_ = 0;
/* Free pools -- Create initial safe free pool */
BLI_assert(current_free_list_ == nullptr);
@ -159,11 +166,13 @@ gpu::MTLBuffer *MTLBufferPool::allocate_aligned(uint64_t size,
#if MTL_DEBUG_MEMORY_STATISTICS == 1
/* Debug. */
allocations_in_pool_ -= new_buffer->get_size();
buffers_in_pool_--;
BLI_assert(allocations_in_pool_ >= 0);
#endif
/* Decrement size of pool. */
BLI_assert(allocations_in_pool_ >= 0);
allocations_in_pool_ -= new_buffer->get_size();
/* Ensure buffer memory is correctly backed. */
BLI_assert(new_buffer->get_metal_buffer());
}
@ -275,6 +284,59 @@ void MTLBufferPool::update_memory_pools()
}
}
/* Release memory allocations which have not been used in a while.
* This ensures memory pressure stays low for scenes with compounding complexity during
* animation.
* If memory is continually used, then we do not want to free this memory as it will be
* re-allocated during a short time period. */
const time_t time_now = std::time(nullptr);
for (auto buffer_pool_list : buffer_pools_.items()) {
MTLBufferPoolOrderedList *pool_allocations = buffer_pool_list.value;
MTLBufferPoolOrderedList::iterator pool_iterator = pool_allocations->begin();
while (pool_iterator != pool_allocations->end()) {
const MTLBufferHandle handle = *pool_iterator;
const time_t time_passed = time_now - handle.insert_time;
/* Free allocations if a certain amount of time has passed.
* Deletion frequency depends on how much excess memory
* the application is using. */
time_t deletion_time_threshold_s = 600;
/* Spare pool memory >= 2GB. */
if (allocations_in_pool_ >= MEMORY_SIZE_2GB) {
deletion_time_threshold_s = 2;
}
else
/* Spare pool memory >= 1GB. */
if (allocations_in_pool_ >= MEMORY_SIZE_1GB)
{
deletion_time_threshold_s = 4;
}
/* Spare pool memory >= 512MB.*/
else if (allocations_in_pool_ >= MEMORY_SIZE_512MB) {
deletion_time_threshold_s = 15;
}
/* Spare pool memory >= 256MB. */
else if (allocations_in_pool_ >= MEMORY_SIZE_256MB) {
deletion_time_threshold_s = 60;
}
if (time_passed > deletion_time_threshold_s) {
/* Delete allocation. */
delete handle.buffer;
pool_iterator = pool_allocations->erase(pool_iterator);
allocations_in_pool_ -= handle.buffer_size;
#if MTL_DEBUG_MEMORY_STATISTICS == 1
total_allocation_bytes_ -= handle.buffer_size;
buffers_in_pool_--;
#endif
continue;
}
pool_iterator++;
}
}
#if MTL_DEBUG_MEMORY_STATISTICS == 1
printf("--- Allocation Stats ---\n");
printf(" Num buffers processed in pool (this frame): %u\n", num_buffers_added);
@ -383,10 +445,10 @@ void MTLBufferPool::insert_buffer_into_pool(MTLResourceOptions options, gpu::MTL
std::multiset<MTLBufferHandle, CompareMTLBuffer> *pool = buffer_pools_.lookup(options);
pool->insert(MTLBufferHandle(buffer));
allocations_in_pool_ += buffer->get_size();
#if MTL_DEBUG_MEMORY_STATISTICS == 1
/* Debug statistics. */
allocations_in_pool_ += buffer->get_size();
buffers_in_pool_++;
#endif
}

View File

@ -249,7 +249,7 @@ struct MSLTextureResource {
MSLTextureSamplerAccess access;
/* Whether resource is a texture sampler or an image. */
bool is_texture_sampler;
/* Index in shader bind table [[texture(N)]].*/
/* Index in shader bind table `[[texture(N)]]`. */
uint slot;
/* Explicit bind index provided by ShaderCreateInfo. */
uint location;

View File

@ -602,7 +602,9 @@ inline MTLTextureUsage mtl_usage_from_gpu(eGPUTextureUsage usage)
if (usage == GPU_TEXTURE_USAGE_GENERAL) {
return MTLTextureUsageUnknown;
}
if (usage & GPU_TEXTURE_USAGE_SHADER_READ) {
/* Host read implies general read support, as the compute-based host read routine requires
* reading of texture data. */
if (usage & GPU_TEXTURE_USAGE_SHADER_READ || usage & GPU_TEXTURE_USAGE_HOST_READ) {
mtl_usage = mtl_usage | MTLTextureUsageShaderRead;
}
if (usage & GPU_TEXTURE_USAGE_SHADER_WRITE) {

View File

@ -42,7 +42,7 @@ void VKCommandBuffer::init(const VkDevice vk_device,
/* When a the last GHOST context is destroyed the device is deallocate. A moment later the GPU
* context is destroyed. The first step is to activate it. Activating would retrieve the device
* from GHOST which in that case is a VK_NULL_HANDLE.*/
* from GHOST which in that case is a #VK_NULL_HANDLE. */
if (vk_device == VK_NULL_HANDLE) {
return;
}

View File

@ -6,6 +6,8 @@
* \ingroup gpu
*/
#include "BLI_utildefines.h"
#include "vk_common.hh"
namespace blender::gpu {
@ -750,4 +752,109 @@ VkCullModeFlags to_vk_cull_mode_flags(const eGPUFaceCullTest cull_test)
return VK_CULL_MODE_NONE;
}
const char *to_string(VkObjectType type)
{
switch (type) {
case VK_OBJECT_TYPE_UNKNOWN:
return STRINGIFY_ARG(VK_OBJECT_TYPE_UNKNOWN);
case VK_OBJECT_TYPE_INSTANCE:
return STRINGIFY_ARG(VK_OBJECT_TYPE_INSTANCE);
case VK_OBJECT_TYPE_PHYSICAL_DEVICE:
return STRINGIFY_ARG(VK_OBJECT_TYPE_PHYSICAL_DEVICE);
case VK_OBJECT_TYPE_DEVICE:
return STRINGIFY_ARG(VK_OBJECT_TYPE_DEVICE);
case VK_OBJECT_TYPE_QUEUE:
return STRINGIFY_ARG(VK_OBJECT_TYPE_QUEUE);
case VK_OBJECT_TYPE_SEMAPHORE:
return STRINGIFY_ARG(VK_OBJECT_TYPE_SEMAPHORE);
case VK_OBJECT_TYPE_COMMAND_BUFFER:
return STRINGIFY_ARG(VK_OBJECT_TYPE_COMMAND_BUFFER);
case VK_OBJECT_TYPE_FENCE:
return STRINGIFY_ARG(VK_OBJECT_TYPE_FENCE);
case VK_OBJECT_TYPE_DEVICE_MEMORY:
return STRINGIFY_ARG(VK_OBJECT_TYPE_DEVICE_MEMORY);
case VK_OBJECT_TYPE_BUFFER:
return STRINGIFY_ARG(VK_OBJECT_TYPE_BUFFER);
case VK_OBJECT_TYPE_IMAGE:
return STRINGIFY_ARG(VK_OBJECT_TYPE_IMAGE);
case VK_OBJECT_TYPE_EVENT:
return STRINGIFY_ARG(VK_OBJECT_TYPE_EVENT);
case VK_OBJECT_TYPE_QUERY_POOL:
return STRINGIFY_ARG(VK_OBJECT_TYPE_QUERY_POOL);
case VK_OBJECT_TYPE_BUFFER_VIEW:
return STRINGIFY_ARG(VK_OBJECT_TYPE_BUFFER_VIEW);
case VK_OBJECT_TYPE_IMAGE_VIEW:
return STRINGIFY_ARG(VK_OBJECT_TYPE_IMAGE_VIEW);
case VK_OBJECT_TYPE_SHADER_MODULE:
return STRINGIFY_ARG(VK_OBJECT_TYPE_SHADER_MODULE);
case VK_OBJECT_TYPE_PIPELINE_CACHE:
return STRINGIFY_ARG(VK_OBJECT_TYPE_PIPELINE_CACHE);
case VK_OBJECT_TYPE_PIPELINE_LAYOUT:
return STRINGIFY_ARG(VK_OBJECT_TYPE_PIPELINE_LAYOUT);
case VK_OBJECT_TYPE_RENDER_PASS:
return STRINGIFY_ARG(VK_OBJECT_TYPE_RENDER_PASS);
case VK_OBJECT_TYPE_PIPELINE:
return STRINGIFY_ARG(VK_OBJECT_TYPE_PIPELINE);
case VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT:
return STRINGIFY_ARG(VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT);
case VK_OBJECT_TYPE_SAMPLER:
return STRINGIFY_ARG(VK_OBJECT_TYPE_SAMPLER);
case VK_OBJECT_TYPE_DESCRIPTOR_POOL:
return STRINGIFY_ARG(VK_OBJECT_TYPE_DESCRIPTOR_POOL);
case VK_OBJECT_TYPE_DESCRIPTOR_SET:
return STRINGIFY_ARG(VK_OBJECT_TYPE_DESCRIPTOR_SET);
case VK_OBJECT_TYPE_FRAMEBUFFER:
return STRINGIFY_ARG(VK_OBJECT_TYPE_FRAMEBUFFER);
case VK_OBJECT_TYPE_COMMAND_POOL:
return STRINGIFY_ARG(VK_OBJECT_TYPE_COMMAND_POOL);
case VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION:
return STRINGIFY_ARG(VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION);
case VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE:
return STRINGIFY_ARG(VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE);
case VK_OBJECT_TYPE_SURFACE_KHR:
return STRINGIFY_ARG(VK_OBJECT_TYPE_SURFACE_KHR);
case VK_OBJECT_TYPE_SWAPCHAIN_KHR:
return STRINGIFY_ARG(VK_OBJECT_TYPE_SWAPCHAIN_KHR);
case VK_OBJECT_TYPE_DISPLAY_KHR:
return STRINGIFY_ARG(VK_OBJECT_TYPE_DISPLAY_KHR);
case VK_OBJECT_TYPE_DISPLAY_MODE_KHR:
return STRINGIFY_ARG(VK_OBJECT_TYPE_DISPLAY_MODE_KHR);
case VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT:
return STRINGIFY_ARG(VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT);
#ifdef VK_ENABLE_BETA_EXTENSIONS
case VK_OBJECT_TYPE_VIDEO_SESSION_KHR:
return STRINGIFY_ARG(VK_OBJECT_TYPE_VIDEO_SESSION_KHR);
#endif
#ifdef VK_ENABLE_BETA_EXTENSIONS
case VK_OBJECT_TYPE_VIDEO_SESSION_PARAMETERS_KHR:
return STRINGIFY_ARG(VK_OBJECT_TYPE_VIDEO_SESSION_PARAMETERS_KHR);
#endif
case VK_OBJECT_TYPE_CU_MODULE_NVX:
return STRINGIFY_ARG(VK_OBJECT_TYPE_CU_MODULE_NVX);
case VK_OBJECT_TYPE_CU_FUNCTION_NVX:
return STRINGIFY_ARG(VK_OBJECT_TYPE_CU_FUNCTION_NVX);
case VK_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT:
return STRINGIFY_ARG(VK_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT);
case VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR:
return STRINGIFY_ARG(VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR);
case VK_OBJECT_TYPE_VALIDATION_CACHE_EXT:
return STRINGIFY_ARG(VK_OBJECT_TYPE_VALIDATION_CACHE_EXT);
case VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV:
return STRINGIFY_ARG(VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV);
case VK_OBJECT_TYPE_PERFORMANCE_CONFIGURATION_INTEL:
return STRINGIFY_ARG(VK_OBJECT_TYPE_PERFORMANCE_CONFIGURATION_INTEL);
case VK_OBJECT_TYPE_DEFERRED_OPERATION_KHR:
return STRINGIFY_ARG(VK_OBJECT_TYPE_DEFERRED_OPERATION_KHR);
case VK_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NV:
return STRINGIFY_ARG(VK_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NV);
case VK_OBJECT_TYPE_PRIVATE_DATA_SLOT_EXT:
return STRINGIFY_ARG(VK_OBJECT_TYPE_PRIVATE_DATA_SLOT_EXT);
case VK_OBJECT_TYPE_BUFFER_COLLECTION_FUCHSIA:
return STRINGIFY_ARG(VK_OBJECT_TYPE_BUFFER_COLLECTION_FUCHSIA);
default:
BLI_assert_unreachable();
}
return "NotFound";
};
} // namespace blender::gpu

View File

@ -35,6 +35,7 @@ VkClearColorValue to_vk_clear_color_value(const eGPUDataFormat format, const voi
VkIndexType to_vk_index_type(const GPUIndexBufType index_type);
VkPrimitiveTopology to_vk_primitive_topology(const GPUPrimType prim_type);
VkCullModeFlags to_vk_cull_mode_flags(const eGPUFaceCullTest cull_test);
const char *to_string(VkObjectType type);
template<typename T> VkObjectType to_vk_object_type(T /*vk_obj*/)
{

View File

@ -7,11 +7,15 @@
*/
#include "BKE_global.h"
#include "BLI_dynstr.h"
#include "CLG_log.h"
#include "vk_backend.hh"
#include "vk_context.hh"
#include "vk_debug.hh"
static CLG_LogRef LOG = {"gpu.debug.vulkan"};
namespace blender::gpu {
void VKContext::debug_group_begin(const char *name, int)
{
@ -70,6 +74,7 @@ void VKDebuggingTools::init(VkInstance vk_instance)
{
PFN_vkGetInstanceProcAddr instance_proc_addr = vkGetInstanceProcAddr;
enabled = false;
vk_debug_utils_messenger = nullptr;
vkCmdBeginDebugUtilsLabelEXT_r = (PFN_vkCmdBeginDebugUtilsLabelEXT)instance_proc_addr(
vk_instance, "vkCmdBeginDebugUtilsLabelEXT");
vkCmdEndDebugUtilsLabelEXT_r = (PFN_vkCmdEndDebugUtilsLabelEXT)instance_proc_addr(
@ -94,11 +99,15 @@ void VKDebuggingTools::init(VkInstance vk_instance)
vk_instance, "vkSubmitDebugUtilsMessageEXT");
if (vkCmdBeginDebugUtilsLabelEXT_r) {
enabled = true;
init_messenger(vk_instance);
}
}
void VKDebuggingTools::deinit()
void VKDebuggingTools::deinit(VkInstance vk_instance)
{
if (enabled) {
destroy_messenger(vk_instance);
}
vkCmdBeginDebugUtilsLabelEXT_r = nullptr;
vkCmdEndDebugUtilsLabelEXT_r = nullptr;
vkCmdInsertDebugUtilsLabelEXT_r = nullptr;
@ -206,3 +215,231 @@ void pop_marker(const VKDevice &device)
}
} // namespace blender::gpu::debug
namespace blender::gpu::debug {
VKDebuggingTools::~VKDebuggingTools()
{
BLI_assert(vk_debug_utils_messenger == nullptr);
};
void VKDebuggingTools::print_vulkan_version()
{
uint32_t instanceVersion = VK_API_VERSION_1_0;
vkEnumerateInstanceVersion(&instanceVersion);
uint32_t major = VK_VERSION_MAJOR(instanceVersion);
uint32_t minor = VK_VERSION_MINOR(instanceVersion);
uint32_t patch = VK_VERSION_PATCH(instanceVersion);
printf("Vulkan Version:%u.%u.%u\n", major, minor, patch);
}
void VKDebuggingTools::print_labels(const VkDebugUtilsMessengerCallbackDataEXT *callback_data)
{
std::stringstream ss;
if (callback_data->objectCount > 0) {
ss << std::endl;
ss << callback_data->objectCount << " Object[s] related \n";
for (uint32_t object = 0; object < callback_data->objectCount; ++object) {
ss << "ObjectType[" << to_string(callback_data->pObjects[object].objectType) << "],";
ss << "Handle[0x" << std::hex
<< static_cast<uintptr_t>(callback_data->pObjects[object].objectHandle) << "]";
if (callback_data->pObjects[object].pObjectName) {
ss << ",Name[" << callback_data->pObjects[object].pObjectName << "]";
}
ss << std::endl;
}
}
if (callback_data->cmdBufLabelCount > 0) {
ss << std::endl;
ss << callback_data->cmdBufLabelCount << " Command Buffer Label[s] " << std::endl;
for (uint32_t label = 0; label < callback_data->cmdBufLabelCount; ++label) {
if (callback_data->pCmdBufLabels[label].pLabelName) {
ss << "CmdBufLabelName : " << callback_data->pCmdBufLabels[label].pLabelName << std::endl;
}
}
}
if (callback_data->queueLabelCount > 0) {
ss << std::endl;
ss << callback_data->queueLabelCount << " Queue Label[s]\n";
for (uint32_t label = 0; label < callback_data->queueLabelCount; ++label) {
if (callback_data->pQueueLabels[label].pLabelName) {
ss << "QueueLabelName : " << callback_data->pQueueLabels[label].pLabelName << std::endl;
}
}
}
printf("%s\n", ss.str().c_str());
fflush(stdout);
}
VKAPI_ATTR VkBool32 VKAPI_CALL
messenger_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT /* message_type*/,
const VkDebugUtilsMessengerCallbackDataEXT *callback_data,
void *user_data);
VKAPI_ATTR VkBool32 VKAPI_CALL
messenger_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT /* message_type*/,
const VkDebugUtilsMessengerCallbackDataEXT *callback_data,
void *user_data)
{
VKDebuggingTools &debugging_tools = *reinterpret_cast<VKDebuggingTools *>(user_data);
if (debugging_tools.is_ignore(callback_data->messageIdNumber)) {
return VK_FALSE;
}
bool use_color = CLG_color_support_get(&LOG);
UNUSED_VARS(use_color);
bool enabled = false;
if ((message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) ||
(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT))
{
if ((LOG.type->flag & CLG_FLAG_USE) && (LOG.type->level >= CLG_SEVERITY_INFO)) {
const char *format = "{0x%x}% s\n %s ";
CLG_logf(LOG.type,
CLG_SEVERITY_INFO,
"",
"",
format,
callback_data->messageIdNumber,
callback_data->pMessageIdName,
callback_data->pMessage);
enabled = true;
}
}
else {
CLG_Severity clog_severity;
switch (message_severity) {
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
clog_severity = CLG_SEVERITY_WARN;
break;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
clog_severity = CLG_SEVERITY_ERROR;
break;
default:
BLI_assert_unreachable();
}
enabled = true;
if (clog_severity == CLG_SEVERITY_ERROR) {
const char *format = " %s {0x%x}\n %s ";
CLG_logf(LOG.type,
clog_severity,
"",
"",
format,
callback_data->pMessageIdName,
callback_data->messageIdNumber,
callback_data->pMessage);
}
else if (LOG.type->level >= CLG_SEVERITY_WARN) {
const char *format = " %s {0x%x}\n %s ";
CLG_logf(LOG.type,
clog_severity,
"",
"",
format,
callback_data->pMessageIdName,
callback_data->messageIdNumber,
callback_data->pMessage);
}
}
if ((enabled) && ((callback_data->objectCount > 0) || (callback_data->cmdBufLabelCount > 0) ||
(callback_data->queueLabelCount > 0)))
{
debugging_tools.print_labels(callback_data);
}
return VK_TRUE;
};
VkResult VKDebuggingTools::init_messenger(VkInstance vk_instance)
{
print_vulkan_version();
vk_message_id_number_ignored.clear();
BLI_assert(enabled);
VkDebugUtilsMessengerCreateInfoEXT create_info;
create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
create_info.pNext = nullptr;
create_info.flags = 0;
create_info.messageSeverity = message_severity;
create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
create_info.pfnUserCallback = messenger_callback;
create_info.pUserData = this;
VkResult res = vkCreateDebugUtilsMessengerEXT_r(
vk_instance, &create_info, nullptr, &vk_debug_utils_messenger);
BLI_assert(res == VK_SUCCESS);
return res;
}
void VKDebuggingTools::destroy_messenger(VkInstance vk_instance)
{
if (vk_debug_utils_messenger == nullptr) {
return;
}
BLI_assert(enabled);
vkDestroyDebugUtilsMessengerEXT_r(vk_instance, vk_debug_utils_messenger, nullptr);
vk_message_id_number_ignored.clear();
vk_debug_utils_messenger = nullptr;
return;
}
bool VKDebuggingTools::is_ignore(int32_t id_number)
{
bool found = false;
{
std::scoped_lock lock(ignore_mutex);
found = vk_message_id_number_ignored.contains(id_number);
}
return found;
}
void VKDebuggingTools::add_group(int32_t id_number)
{
std::scoped_lock lock(ignore_mutex);
vk_message_id_number_ignored.add(id_number);
};
void VKDebuggingTools::remove_group(int32_t id_number)
{
std::scoped_lock lock(ignore_mutex);
vk_message_id_number_ignored.remove(id_number);
};
void raise_message(int32_t id_number,
VkDebugUtilsMessageSeverityFlagBitsEXT vk_severity_flag_bits,
const char *format,
...)
{
const VKDevice &device = VKBackend::get().device_get();
const VKDebuggingTools &debugging_tools = device.debugging_tools_get();
if (debugging_tools.enabled) {
DynStr *ds = nullptr;
va_list arg;
char *info = nullptr;
va_start(arg, format);
ds = BLI_dynstr_new();
BLI_dynstr_vappendf(ds, format, arg);
info = BLI_dynstr_get_cstring(ds);
BLI_dynstr_free(ds);
va_end(arg);
static VkDebugUtilsMessengerCallbackDataEXT vk_call_back_data;
vk_call_back_data.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT;
vk_call_back_data.pNext = VK_NULL_HANDLE;
vk_call_back_data.messageIdNumber = id_number;
vk_call_back_data.pMessageIdName = "VulkanMessenger";
vk_call_back_data.objectCount = 0;
vk_call_back_data.flags = 0;
vk_call_back_data.pObjects = VK_NULL_HANDLE;
vk_call_back_data.pMessage = info;
debugging_tools.vkSubmitDebugUtilsMessageEXT_r(device.instance_get(),
vk_severity_flag_bits,
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT,
&vk_call_back_data);
MEM_freeN((void *)info);
}
}
}; // namespace blender::gpu::debug

View File

@ -8,10 +8,12 @@
#pragma once
#include "BKE_global.h"
#include "BLI_set.hh"
#include "BLI_string.h"
#include "vk_common.hh"
#include <mutex>
#include <typeindex>
namespace blender::gpu {
@ -19,8 +21,14 @@ class VKContext;
class VKDevice;
namespace debug {
struct VKDebuggingTools {
class VKDebuggingTools {
public:
bool enabled = false;
VkDebugUtilsMessageSeverityFlagsEXT message_severity =
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
/* Function pointer definitions. */
PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT_r = nullptr;
PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT_r = nullptr;
@ -33,9 +41,22 @@ struct VKDebuggingTools {
PFN_vkQueueInsertDebugUtilsLabelEXT vkQueueInsertDebugUtilsLabelEXT_r = nullptr;
PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT_r = nullptr;
PFN_vkSetDebugUtilsObjectTagEXT vkSetDebugUtilsObjectTagEXT_r = nullptr;
VKDebuggingTools() = default;
~VKDebuggingTools();
void init(VkInstance vk_instance);
void deinit();
void deinit(VkInstance vk_instance);
bool is_ignore(int32_t id_number);
VkResult init_messenger(VkInstance vk_instance);
void destroy_messenger(VkInstance vk_instance);
void print_labels(const VkDebugUtilsMessengerCallbackDataEXT *callback_data);
private:
VkDebugUtilsMessengerEXT vk_debug_utils_messenger = nullptr;
Set<int32_t> vk_message_id_number_ignored;
std::mutex ignore_mutex;
void print_vulkan_version();
void add_group(int32_t id_number);
void remove_group(int32_t id_number);
};
void object_label(VkObjectType vk_object_type, uint64_t object_handle, const char *name);
@ -58,5 +79,11 @@ void pop_marker(VkCommandBuffer vk_command_buffer);
void push_marker(const VKDevice &device, const char *name);
void set_marker(const VKDevice &device, const char *name);
void pop_marker(const VKDevice &device);
/* how to use : debug::raise_message(0xB41ca2,VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,"This
* is a raise message. %llx", (uintptr_t)vk_object); */
void raise_message(int32_t id_number,
VkDebugUtilsMessageSeverityFlagBitsEXT vk_severity_flag_bits,
const char *fmt,
...);
} // namespace debug
} // namespace blender::gpu

View File

@ -18,7 +18,7 @@ void VKDevice::deinit()
{
vmaDestroyAllocator(mem_allocator_);
mem_allocator_ = VK_NULL_HANDLE;
debugging_tools_.deinit();
debugging_tools_.deinit(vk_instance_);
vk_instance_ = VK_NULL_HANDLE;
vk_physical_device_ = VK_NULL_HANDLE;

View File

@ -441,7 +441,7 @@ void VKFrameBuffer::render_pass_create()
size_set(size[0], size[1]);
}
else {
/* A framebuffer should at least be 1 by 1.*/
/* A frame-buffer should at least be 1 by 1. */
this->size_set(1, 1);
}
viewport_reset();

View File

@ -113,9 +113,9 @@ class VKFrameBuffer : public FrameBuffer {
}
/**
* Is this framebuffer immutable?
* Is this frame-buffer immutable?
*
* Framebuffers that are owned by GHOST are immutable and
* Frame-buffers that are owned by GHOST are immutable and
* don't have any attachments assigned. It should be assumed that there is a single color texture
* in slot 0.
*/

View File

@ -79,9 +79,8 @@ void VKPipelineStateManager::finalize_color_blend_state(const VKFrameBuffer &fra
{
color_blend_attachments.clear();
if (framebuffer.is_immutable()) {
/* Immutable framebuffers are owned by GHOST and don't have any attachments assigned. In this
* case we assume that there is a single color texture assigned.
*/
/* Immutable frame-buffers are owned by GHOST and don't have any attachments assigned. In this
* case we assume that there is a single color texture assigned. */
color_blend_attachments.append(color_blend_attachment_template);
}
else {

View File

@ -119,10 +119,12 @@ static bool export_params_valid(const USDExportParams &params)
return valid;
}
/* Create the root Xform primitive, if the Root Prim path has been set
/**
* Create the root Xform primitive, if the Root Prim path has been set
* in the export options. In the future, this function can be extended
* to author transforms and additional schema data (e.g., model Kind)
* on the root prim. */
* on the root prim.
*/
static void ensure_root_prim(pxr::UsdStageRefPtr stage, const USDExportParams &params)
{
if (params.root_prim_path[0] == '\0') {

View File

@ -711,6 +711,15 @@ enum {
* was kept around (because e.g. detected as user-edited).
*/
LIB_LIB_OVERRIDE_RESYNC_LEFTOVER = 1 << 13,
/**
* This `id` was explicitly copied as part of a clipboard copy operation.
* When reading the clipboard back, this can be used to check which ID's are
* intended to be part of the clipboard, compared with ID's that were indirectly referenced.
*
* While the flag is typically cleared, a saved file may have this set for some data-blocks,
* so it must be treated as dirty.
*/
LIB_CLIPBOARD_MARK = 1 << 14,
};
/**

View File

@ -201,7 +201,6 @@ typedef enum uiPanelDataExpansion {
UI_SUBPANEL_DATA_EXPAND_13 = (1 << 13),
UI_SUBPANEL_DATA_EXPAND_14 = (1 << 14),
UI_SUBPANEL_DATA_EXPAND_15 = (1 << 15),
UI_SUBPANEL_DATA_EXPAND_16 = (1 << 16),
} uiPanelDataExpansion;
/**

View File

@ -5,207 +5,18 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "BLI_array.hh"
#include "BLI_array_utils.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_pointcloud_types.h"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.h"
#include "BKE_instances.hh"
#include "BKE_mesh.hh"
#include "BKE_pointcloud.h"
#include "GEO_mesh_copy_selection.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_delete_geometry_cc {
template<typename T>
static void copy_data_based_on_map(const Span<T> src,
const Span<int> index_map,
MutableSpan<T> dst)
{
threading::parallel_for(index_map.index_range(), 1024, [&](const IndexRange range) {
for (const int i_src : range) {
const int i_dst = index_map[i_src];
if (i_dst != -1) {
dst[i_dst] = src[i_src];
}
}
});
}
static void copy_attributes_based_on_map(const bke::AttributeAccessor src_attributes,
bke::MutableAttributeAccessor dst_attributes,
const eAttrDomain domain,
const AnonymousAttributePropagationInfo &propagation_info,
const Set<std::string> &skip,
const Span<int> index_map)
{
src_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
if (meta_data.domain != domain) {
return true;
}
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
return true;
}
if (skip.contains(id.name())) {
return true;
}
const GVArraySpan src = *src_attributes.lookup(id);
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
id, meta_data.domain, meta_data.data_type);
bke::attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) {
using T = decltype(dummy);
copy_data_based_on_map(src.typed<T>(), index_map, dst.span.typed<T>());
});
dst.finish();
return true;
});
}
static void copy_face_corner_attributes(const bke::AttributeAccessor src_attributes,
bke::MutableAttributeAccessor dst_attributes,
const AnonymousAttributePropagationInfo &propagation_info,
const Set<std::string> &skip,
const int selected_loops_num,
const Span<int> selected_poly_indices,
const Mesh &mesh_in)
{
const OffsetIndices polys = mesh_in.polys();
Vector<int64_t> indices;
indices.reserve(selected_loops_num);
for (const int src_poly_index : selected_poly_indices) {
for (const int corner : polys[src_poly_index]) {
indices.append_unchecked(corner);
}
}
IndexMaskMemory memory;
bke::gather_attributes(src_attributes,
ATTR_DOMAIN_CORNER,
propagation_info,
skip,
IndexMask::from_indices<int64_t>(indices, memory),
dst_attributes);
}
static void copy_masked_edges_to_new_mesh(const Mesh &src_mesh, Mesh &dst_mesh, Span<int> edge_map)
{
BLI_assert(src_mesh.totedge == edge_map.size());
const Span<int2> src_edges = src_mesh.edges();
MutableSpan<int2> dst_edges = dst_mesh.edges_for_write();
threading::parallel_for(src_edges.index_range(), 1024, [&](const IndexRange range) {
for (const int i_src : range) {
const int i_dst = edge_map[i_src];
if (ELEM(i_dst, -1, -2)) {
continue;
}
dst_edges[i_dst] = src_edges[i_src];
}
});
}
static void copy_masked_edges_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
Span<int> vertex_map,
Span<int> edge_map)
{
BLI_assert(src_mesh.totvert == vertex_map.size());
BLI_assert(src_mesh.totedge == edge_map.size());
const Span<int2> src_edges = src_mesh.edges();
MutableSpan<int2> dst_edges = dst_mesh.edges_for_write();
threading::parallel_for(src_edges.index_range(), 1024, [&](const IndexRange range) {
for (const int i_src : range) {
const int i_dst = edge_map[i_src];
if (i_dst == -1) {
continue;
}
dst_edges[i_dst][0] = vertex_map[src_edges[i_src][0]];
dst_edges[i_dst][1] = vertex_map[src_edges[i_src][1]];
}
});
}
/* Faces and edges changed but vertices are the same. */
static void copy_masked_polys_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
Span<int> edge_map,
Span<int> masked_poly_indices,
Span<int> new_loop_starts)
{
const OffsetIndices src_polys = src_mesh.polys();
const Span<int> src_corner_verts = src_mesh.corner_verts();
const Span<int> src_corner_edges = src_mesh.corner_edges();
MutableSpan<int> dst_poly_offsets = dst_mesh.poly_offsets_for_write();
MutableSpan<int> dst_corner_verts = dst_mesh.corner_verts_for_write();
MutableSpan<int> dst_corner_edges = dst_mesh.corner_edges_for_write();
threading::parallel_for(masked_poly_indices.index_range(), 512, [&](const IndexRange range) {
for (const int i_dst : range) {
const int i_src = masked_poly_indices[i_dst];
const IndexRange poly_src = src_polys[i_src];
const Span<int> src_poly_verts = src_corner_verts.slice(poly_src);
const Span<int> src_poly_edges = src_corner_edges.slice(poly_src);
dst_poly_offsets[i_dst] = new_loop_starts[i_dst];
MutableSpan<int> dst_poly_verts = dst_corner_verts.slice(dst_poly_offsets[i_dst],
poly_src.size());
MutableSpan<int> dst_poly_edges = dst_corner_edges.slice(dst_poly_offsets[i_dst],
poly_src.size());
dst_poly_verts.copy_from(src_poly_verts);
for (const int i : IndexRange(poly_src.size())) {
dst_poly_edges[i] = edge_map[src_poly_edges[i]];
}
}
});
}
static void copy_masked_polys_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
Span<int> vertex_map,
Span<int> edge_map,
Span<int> masked_poly_indices,
Span<int> new_loop_starts)
{
const OffsetIndices src_polys = src_mesh.polys();
const Span<int> src_corner_verts = src_mesh.corner_verts();
const Span<int> src_corner_edges = src_mesh.corner_edges();
MutableSpan<int> dst_poly_offsets = dst_mesh.poly_offsets_for_write();
MutableSpan<int> dst_corner_verts = dst_mesh.corner_verts_for_write();
MutableSpan<int> dst_corner_edges = dst_mesh.corner_edges_for_write();
threading::parallel_for(masked_poly_indices.index_range(), 512, [&](const IndexRange range) {
for (const int i_dst : range) {
const int i_src = masked_poly_indices[i_dst];
const IndexRange poly_src = src_polys[i_src];
const Span<int> src_poly_verts = src_corner_verts.slice(poly_src);
const Span<int> src_poly_edges = src_corner_edges.slice(poly_src);
dst_poly_offsets[i_dst] = new_loop_starts[i_dst];
MutableSpan<int> dst_poly_verts = dst_corner_verts.slice(dst_poly_offsets[i_dst],
poly_src.size());
MutableSpan<int> dst_poly_edges = dst_corner_edges.slice(dst_poly_offsets[i_dst],
poly_src.size());
for (const int i : IndexRange(poly_src.size())) {
dst_poly_verts[i] = vertex_map[src_poly_verts[i]];
}
for (const int i : IndexRange(poly_src.size())) {
dst_poly_edges[i] = edge_map[src_poly_edges[i]];
}
}
});
}
/** \return std::nullopt if the geometry should remain unchanged. */
static std::optional<Curves *> separate_curves_selection(
const Curves &src_curves_id,
@ -291,729 +102,24 @@ static void delete_selected_instances(GeometrySet &geometry_set,
instances.remove(selection, propagation_info);
}
static void compute_selected_verts_from_vertex_selection(const Span<bool> vertex_selection,
MutableSpan<int> r_vertex_map,
int *r_selected_verts_num)
{
BLI_assert(vertex_selection.size() == r_vertex_map.size());
int selected_verts_num = 0;
for (const int i : r_vertex_map.index_range()) {
if (vertex_selection[i]) {
r_vertex_map[i] = selected_verts_num;
selected_verts_num++;
}
else {
r_vertex_map[i] = -1;
}
}
*r_selected_verts_num = selected_verts_num;
}
static void compute_selected_edges_from_vertex_selection(const Mesh &mesh,
const Span<bool> vertex_selection,
MutableSpan<int> r_edge_map,
int *r_selected_edges_num)
{
BLI_assert(mesh.totedge == r_edge_map.size());
const Span<int2> edges = mesh.edges();
int selected_edges_num = 0;
for (const int i : IndexRange(mesh.totedge)) {
const int2 &edge = edges[i];
/* Only add the edge if both vertices will be in the new mesh. */
if (vertex_selection[edge[0]] && vertex_selection[edge[1]]) {
r_edge_map[i] = selected_edges_num;
selected_edges_num++;
}
else {
r_edge_map[i] = -1;
}
}
*r_selected_edges_num = selected_edges_num;
}
static void compute_selected_polys_from_vertex_selection(const Mesh &mesh,
const Span<bool> vertex_selection,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
int *r_selected_polys_num,
int *r_selected_loops_num)
{
BLI_assert(mesh.totvert == vertex_selection.size());
const OffsetIndices polys = mesh.polys();
const Span<int> corner_verts = mesh.corner_verts();
r_selected_poly_indices.reserve(mesh.totpoly);
r_loop_starts.reserve(mesh.totloop);
int selected_loops_num = 0;
for (const int i : polys.index_range()) {
const IndexRange poly_src = polys[i];
bool all_verts_in_selection = true;
for (const int vert : corner_verts.slice(poly_src)) {
if (!vertex_selection[vert]) {
all_verts_in_selection = false;
break;
}
}
if (all_verts_in_selection) {
r_selected_poly_indices.append_unchecked(i);
r_loop_starts.append_unchecked(selected_loops_num);
selected_loops_num += poly_src.size();
}
}
*r_selected_polys_num = r_selected_poly_indices.size();
*r_selected_loops_num = selected_loops_num;
}
/**
* Checks for every edge if it is in `edge_selection`. If it is, then the two vertices of the
* edge are kept along with the edge.
*/
static void compute_selected_verts_and_edges_from_edge_selection(const Mesh &mesh,
const Span<bool> edge_selection,
MutableSpan<int> r_vertex_map,
MutableSpan<int> r_edge_map,
int *r_selected_verts_num,
int *r_selected_edges_num)
{
BLI_assert(mesh.totedge == edge_selection.size());
const Span<int2> edges = mesh.edges();
int selected_edges_num = 0;
int selected_verts_num = 0;
for (const int i : IndexRange(mesh.totedge)) {
const int2 &edge = edges[i];
if (edge_selection[i]) {
r_edge_map[i] = selected_edges_num;
selected_edges_num++;
if (r_vertex_map[edge[0]] == -1) {
r_vertex_map[edge[0]] = selected_verts_num;
selected_verts_num++;
}
if (r_vertex_map[edge[1]] == -1) {
r_vertex_map[edge[1]] = selected_verts_num;
selected_verts_num++;
}
}
else {
r_edge_map[i] = -1;
}
}
*r_selected_verts_num = selected_verts_num;
*r_selected_edges_num = selected_edges_num;
}
/**
* Checks for every edge if it is in `edge_selection`.
*/
static void compute_selected_edges_from_edge_selection(const Mesh &mesh,
const Span<bool> edge_selection,
MutableSpan<int> r_edge_map,
int *r_selected_edges_num)
{
BLI_assert(mesh.totedge == edge_selection.size());
int selected_edges_num = 0;
for (const int i : IndexRange(mesh.totedge)) {
if (edge_selection[i]) {
r_edge_map[i] = selected_edges_num;
selected_edges_num++;
}
else {
r_edge_map[i] = -1;
}
}
*r_selected_edges_num = selected_edges_num;
}
/**
* Checks for every polygon if all the edges are in `edge_selection`. If they are, then that
* polygon is kept.
*/
static void compute_selected_polys_from_edge_selection(const Mesh &mesh,
const Span<bool> edge_selection,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
int *r_selected_polys_num,
int *r_selected_loops_num)
{
const OffsetIndices polys = mesh.polys();
const Span<int> corner_edges = mesh.corner_edges();
r_selected_poly_indices.reserve(mesh.totpoly);
r_loop_starts.reserve(mesh.totloop);
int selected_loops_num = 0;
for (const int i : polys.index_range()) {
const IndexRange poly_src = polys[i];
bool all_edges_in_selection = true;
for (const int edge : corner_edges.slice(poly_src)) {
if (!edge_selection[edge]) {
all_edges_in_selection = false;
break;
}
}
if (all_edges_in_selection) {
r_selected_poly_indices.append_unchecked(i);
r_loop_starts.append_unchecked(selected_loops_num);
selected_loops_num += poly_src.size();
}
}
*r_selected_polys_num = r_selected_poly_indices.size();
*r_selected_loops_num = selected_loops_num;
}
/**
* Checks for every edge and polygon if all its vertices are in `vertex_selection`.
*/
static void compute_selected_mesh_data_from_vertex_selection_edge_face(
static std::optional<Mesh *> separate_mesh_selection(
const Mesh &mesh,
const Span<bool> vertex_selection,
MutableSpan<int> r_edge_map,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
int *r_selected_edges_num,
int *r_selected_polys_num,
int *r_selected_loops_num)
{
threading::parallel_invoke(
mesh.totedge > 1000,
[&]() {
compute_selected_edges_from_vertex_selection(
mesh, vertex_selection, r_edge_map, r_selected_edges_num);
},
[&]() {
compute_selected_polys_from_vertex_selection(mesh,
vertex_selection,
r_selected_poly_indices,
r_loop_starts,
r_selected_polys_num,
r_selected_loops_num);
});
}
/**
* Checks for every vertex if it is in `vertex_selection`. The polygons and edges are kept if all
* vertices of that polygon or edge are in the selection.
*/
static void compute_selected_mesh_data_from_vertex_selection(const Mesh &mesh,
const Span<bool> vertex_selection,
MutableSpan<int> r_vertex_map,
MutableSpan<int> r_edge_map,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
int *r_selected_verts_num,
int *r_selected_edges_num,
int *r_selected_polys_num,
int *r_selected_loops_num)
{
threading::parallel_invoke(
mesh.totedge > 1000,
[&]() {
compute_selected_verts_from_vertex_selection(
vertex_selection, r_vertex_map, r_selected_verts_num);
},
[&]() {
compute_selected_edges_from_vertex_selection(
mesh, vertex_selection, r_edge_map, r_selected_edges_num);
},
[&]() {
compute_selected_polys_from_vertex_selection(mesh,
vertex_selection,
r_selected_poly_indices,
r_loop_starts,
r_selected_polys_num,
r_selected_loops_num);
});
}
/**
* Checks for every edge if it is in `edge_selection`. The polygons are kept if all edges are in
* the selection.
*/
static void compute_selected_mesh_data_from_edge_selection_edge_face(
const Mesh &mesh,
const Span<bool> edge_selection,
MutableSpan<int> r_edge_map,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
int *r_selected_edges_num,
int *r_selected_polys_num,
int *r_selected_loops_num)
{
threading::parallel_invoke(
mesh.totedge > 1000,
[&]() {
compute_selected_edges_from_edge_selection(
mesh, edge_selection, r_edge_map, r_selected_edges_num);
},
[&]() {
compute_selected_polys_from_edge_selection(mesh,
edge_selection,
r_selected_poly_indices,
r_loop_starts,
r_selected_polys_num,
r_selected_loops_num);
});
}
/**
* Checks for every edge if it is in `edge_selection`. If it is, the vertices belonging to
* that edge are kept as well. The polys are kept if all edges are in the selection.
*/
static void compute_selected_mesh_data_from_edge_selection(const Mesh &mesh,
const Span<bool> edge_selection,
MutableSpan<int> r_vertex_map,
MutableSpan<int> r_edge_map,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
int *r_selected_verts_num,
int *r_selected_edges_num,
int *r_selected_polys_num,
int *r_selected_loops_num)
{
threading::parallel_invoke(
mesh.totedge > 1000,
[&]() {
r_vertex_map.fill(-1);
compute_selected_verts_and_edges_from_edge_selection(mesh,
edge_selection,
r_vertex_map,
r_edge_map,
r_selected_verts_num,
r_selected_edges_num);
},
[&]() {
compute_selected_polys_from_edge_selection(mesh,
edge_selection,
r_selected_poly_indices,
r_loop_starts,
r_selected_polys_num,
r_selected_loops_num);
});
}
/**
* Checks for every polygon if it is in `poly_selection`.
*/
static void compute_selected_polys_from_poly_selection(const Mesh &mesh,
const Span<bool> poly_selection,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
int *r_selected_polys_num,
int *r_selected_loops_num)
{
BLI_assert(mesh.totpoly == poly_selection.size());
const OffsetIndices polys = mesh.polys();
r_selected_poly_indices.reserve(mesh.totpoly);
r_loop_starts.reserve(mesh.totloop);
int selected_loops_num = 0;
for (const int i : polys.index_range()) {
const IndexRange poly_src = polys[i];
/* We keep this one. */
if (poly_selection[i]) {
r_selected_poly_indices.append_unchecked(i);
r_loop_starts.append_unchecked(selected_loops_num);
selected_loops_num += poly_src.size();
}
}
*r_selected_polys_num = r_selected_poly_indices.size();
*r_selected_loops_num = selected_loops_num;
}
/**
* Checks for every polygon if it is in `poly_selection`. If it is, the edges
* belonging to that polygon are kept as well.
*/
static void compute_selected_mesh_data_from_poly_selection_edge_face(
const Mesh &mesh,
const Span<bool> poly_selection,
MutableSpan<int> r_edge_map,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
int *r_selected_edges_num,
int *r_selected_polys_num,
int *r_selected_loops_num)
{
BLI_assert(mesh.totpoly == poly_selection.size());
BLI_assert(mesh.totedge == r_edge_map.size());
const OffsetIndices polys = mesh.polys();
const Span<int> corner_edges = mesh.corner_edges();
r_edge_map.fill(-1);
r_selected_poly_indices.reserve(mesh.totpoly);
r_loop_starts.reserve(mesh.totloop);
int selected_loops_num = 0;
int selected_edges_num = 0;
for (const int i : polys.index_range()) {
const IndexRange poly_src = polys[i];
/* We keep this one. */
if (poly_selection[i]) {
r_selected_poly_indices.append_unchecked(i);
r_loop_starts.append_unchecked(selected_loops_num);
selected_loops_num += poly_src.size();
/* Add the vertices and the edges. */
for (const int edge : corner_edges.slice(poly_src)) {
/* Check first if it has not yet been added. */
if (r_edge_map[edge] == -1) {
r_edge_map[edge] = selected_edges_num;
selected_edges_num++;
}
}
}
}
*r_selected_edges_num = selected_edges_num;
*r_selected_polys_num = r_selected_poly_indices.size();
*r_selected_loops_num = selected_loops_num;
}
/**
* Checks for every polygon if it is in `poly_selection`. If it is, the edges and vertices
* belonging to that polygon are kept as well.
*/
static void compute_selected_mesh_data_from_poly_selection(const Mesh &mesh,
const Span<bool> poly_selection,
MutableSpan<int> r_vertex_map,
MutableSpan<int> r_edge_map,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
int *r_selected_verts_num,
int *r_selected_edges_num,
int *r_selected_polys_num,
int *r_selected_loops_num)
{
BLI_assert(mesh.totpoly == poly_selection.size());
BLI_assert(mesh.totedge == r_edge_map.size());
const OffsetIndices polys = mesh.polys();
const Span<int> corner_verts = mesh.corner_verts();
const Span<int> corner_edges = mesh.corner_edges();
r_vertex_map.fill(-1);
r_edge_map.fill(-1);
r_selected_poly_indices.reserve(mesh.totpoly);
r_loop_starts.reserve(mesh.totloop);
int selected_loops_num = 0;
int selected_verts_num = 0;
int selected_edges_num = 0;
for (const int i : polys.index_range()) {
const IndexRange poly_src = polys[i];
/* We keep this one. */
if (poly_selection[i]) {
r_selected_poly_indices.append_unchecked(i);
r_loop_starts.append_unchecked(selected_loops_num);
selected_loops_num += poly_src.size();
/* Add the vertices and the edges. */
for (const int corner : poly_src) {
const int vert = corner_verts[corner];
const int edge = corner_edges[corner];
/* Check first if it has not yet been added. */
if (r_vertex_map[vert] == -1) {
r_vertex_map[vert] = selected_verts_num;
selected_verts_num++;
}
if (r_edge_map[edge] == -1) {
r_edge_map[edge] = selected_edges_num;
selected_edges_num++;
}
}
}
}
*r_selected_verts_num = selected_verts_num;
*r_selected_edges_num = selected_edges_num;
*r_selected_polys_num = r_selected_poly_indices.size();
*r_selected_loops_num = selected_loops_num;
}
/**
* Keep the parts of the mesh that are in the selection.
*/
static void do_mesh_separation(GeometrySet &geometry_set,
const Mesh &mesh_in,
const Span<bool> selection,
const eAttrDomain domain,
const GeometryNodeDeleteGeometryMode mode,
const AnonymousAttributePropagationInfo &propagation_info)
{
/* Needed in all cases. */
Vector<int> selected_poly_indices;
Vector<int> new_loop_starts;
int selected_polys_num = 0;
int selected_loops_num = 0;
IndexMaskMemory memory;
Mesh *mesh_out;
switch (mode) {
case GEO_NODE_DELETE_GEOMETRY_MODE_ALL: {
Array<int> vertex_map(mesh_in.totvert);
int selected_verts_num = 0;
Array<int> edge_map(mesh_in.totedge);
int selected_edges_num = 0;
switch (domain) {
case ATTR_DOMAIN_POINT:
compute_selected_mesh_data_from_vertex_selection(mesh_in,
selection,
vertex_map,
edge_map,
selected_poly_indices,
new_loop_starts,
&selected_verts_num,
&selected_edges_num,
&selected_polys_num,
&selected_loops_num);
break;
case ATTR_DOMAIN_EDGE:
compute_selected_mesh_data_from_edge_selection(mesh_in,
selection,
vertex_map,
edge_map,
selected_poly_indices,
new_loop_starts,
&selected_verts_num,
&selected_edges_num,
&selected_polys_num,
&selected_loops_num);
break;
case ATTR_DOMAIN_FACE:
compute_selected_mesh_data_from_poly_selection(mesh_in,
selection,
vertex_map,
edge_map,
selected_poly_indices,
new_loop_starts,
&selected_verts_num,
&selected_edges_num,
&selected_polys_num,
&selected_loops_num);
break;
default:
BLI_assert_unreachable();
break;
}
mesh_out = BKE_mesh_new_nomain_from_template(&mesh_in,
selected_verts_num,
selected_edges_num,
selected_polys_num,
selected_loops_num);
copy_masked_edges_to_new_mesh(mesh_in, *mesh_out, vertex_map, edge_map);
copy_masked_polys_to_new_mesh(
mesh_in, *mesh_out, vertex_map, edge_map, selected_poly_indices, new_loop_starts);
copy_attributes_based_on_map(mesh_in.attributes(),
mesh_out->attributes_for_write(),
ATTR_DOMAIN_POINT,
propagation_info,
{},
vertex_map);
copy_attributes_based_on_map(mesh_in.attributes(),
mesh_out->attributes_for_write(),
ATTR_DOMAIN_EDGE,
propagation_info,
{".edge_verts"},
edge_map);
bke::gather_attributes(mesh_in.attributes(),
ATTR_DOMAIN_FACE,
propagation_info,
{},
IndexMask::from_indices(selected_poly_indices.as_span(), memory),
mesh_out->attributes_for_write());
copy_face_corner_attributes(mesh_in.attributes(),
mesh_out->attributes_for_write(),
propagation_info,
{".corner_vert", ".corner_edge"},
selected_loops_num,
selected_poly_indices,
mesh_in);
break;
}
case GEO_NODE_DELETE_GEOMETRY_MODE_EDGE_FACE: {
Array<int> edge_map(mesh_in.totedge);
int selected_edges_num = 0;
switch (domain) {
case ATTR_DOMAIN_POINT:
compute_selected_mesh_data_from_vertex_selection_edge_face(mesh_in,
selection,
edge_map,
selected_poly_indices,
new_loop_starts,
&selected_edges_num,
&selected_polys_num,
&selected_loops_num);
break;
case ATTR_DOMAIN_EDGE:
compute_selected_mesh_data_from_edge_selection_edge_face(mesh_in,
selection,
edge_map,
selected_poly_indices,
new_loop_starts,
&selected_edges_num,
&selected_polys_num,
&selected_loops_num);
break;
case ATTR_DOMAIN_FACE:
compute_selected_mesh_data_from_poly_selection_edge_face(mesh_in,
selection,
edge_map,
selected_poly_indices,
new_loop_starts,
&selected_edges_num,
&selected_polys_num,
&selected_loops_num);
break;
default:
BLI_assert_unreachable();
break;
}
mesh_out = BKE_mesh_new_nomain_from_template(
&mesh_in, mesh_in.totvert, selected_edges_num, selected_polys_num, selected_loops_num);
copy_masked_edges_to_new_mesh(mesh_in, *mesh_out, edge_map);
copy_masked_polys_to_new_mesh(
mesh_in, *mesh_out, edge_map, selected_poly_indices, new_loop_starts);
bke::copy_attributes(mesh_in.attributes(),
ATTR_DOMAIN_POINT,
propagation_info,
{},
mesh_out->attributes_for_write());
copy_attributes_based_on_map(mesh_in.attributes(),
mesh_out->attributes_for_write(),
ATTR_DOMAIN_EDGE,
propagation_info,
{".edge_verts"},
edge_map);
bke::gather_attributes(mesh_in.attributes(),
ATTR_DOMAIN_FACE,
propagation_info,
{},
IndexMask::from_indices(selected_poly_indices.as_span(), memory),
mesh_out->attributes_for_write());
copy_face_corner_attributes(mesh_in.attributes(),
mesh_out->attributes_for_write(),
propagation_info,
{".corner_vert", ".corner_edge"},
selected_loops_num,
selected_poly_indices,
mesh_in);
/* Positions are not changed by the operation, so the bounds are the same. */
mesh_out->runtime->bounds_cache = mesh_in.runtime->bounds_cache;
break;
}
case GEO_NODE_DELETE_GEOMETRY_MODE_ONLY_FACE: {
switch (domain) {
case ATTR_DOMAIN_POINT:
compute_selected_polys_from_vertex_selection(mesh_in,
selection,
selected_poly_indices,
new_loop_starts,
&selected_polys_num,
&selected_loops_num);
break;
case ATTR_DOMAIN_EDGE:
compute_selected_polys_from_edge_selection(mesh_in,
selection,
selected_poly_indices,
new_loop_starts,
&selected_polys_num,
&selected_loops_num);
break;
case ATTR_DOMAIN_FACE:
compute_selected_polys_from_poly_selection(mesh_in,
selection,
selected_poly_indices,
new_loop_starts,
&selected_polys_num,
&selected_loops_num);
break;
default:
BLI_assert_unreachable();
break;
}
mesh_out = BKE_mesh_new_nomain_from_template(
&mesh_in, mesh_in.totvert, mesh_in.totedge, selected_polys_num, selected_loops_num);
mesh_out->poly_offsets_for_write().drop_back(1).copy_from(new_loop_starts);
bke::copy_attributes(mesh_in.attributes(),
ATTR_DOMAIN_POINT,
propagation_info,
{},
mesh_out->attributes_for_write());
bke::copy_attributes(mesh_in.attributes(),
ATTR_DOMAIN_EDGE,
propagation_info,
{},
mesh_out->attributes_for_write());
bke::gather_attributes(mesh_in.attributes(),
ATTR_DOMAIN_FACE,
propagation_info,
{},
IndexMask::from_indices(selected_poly_indices.as_span(), memory),
mesh_out->attributes_for_write());
copy_face_corner_attributes(mesh_in.attributes(),
mesh_out->attributes_for_write(),
propagation_info,
{},
selected_loops_num,
selected_poly_indices,
mesh_in);
/* Positions are not changed by the operation, so the bounds are the same. */
mesh_out->runtime->bounds_cache = mesh_in.runtime->bounds_cache;
break;
}
}
geometry_set.replace_mesh(mesh_out);
}
static void separate_mesh_selection(GeometrySet &geometry_set,
const Field<bool> &selection_field,
const Field<bool> &selection,
const eAttrDomain selection_domain,
const GeometryNodeDeleteGeometryMode mode,
const AnonymousAttributePropagationInfo &propagation_info)
{
const Mesh &src_mesh = *geometry_set.get_mesh_for_read();
const bke::MeshFieldContext field_context{src_mesh, selection_domain};
fn::FieldEvaluator evaluator{field_context, src_mesh.attributes().domain_size(selection_domain)};
evaluator.add(selection_field);
evaluator.evaluate();
const VArray<bool> selection = evaluator.get_evaluated<bool>(0);
/* Check if there is anything to delete. */
if (selection.is_empty() || (selection.is_single() && selection.get_internal_single())) {
return;
switch (mode) {
case GEO_NODE_DELETE_GEOMETRY_MODE_ALL:
return geometry::mesh_copy_selection(mesh, selection, selection_domain, propagation_info);
case GEO_NODE_DELETE_GEOMETRY_MODE_EDGE_FACE:
return geometry::mesh_copy_selection_keep_verts(
mesh, selection, selection_domain, propagation_info);
case GEO_NODE_DELETE_GEOMETRY_MODE_ONLY_FACE:
return geometry::mesh_copy_selection_keep_edges(
mesh, selection, selection_domain, propagation_info);
}
const VArraySpan<bool> selection_span{selection};
do_mesh_separation(
geometry_set, src_mesh, selection_span, selection_domain, mode, propagation_info);
return nullptr;
}
} // namespace blender::nodes::node_geo_delete_geometry_cc
@ -1040,9 +146,13 @@ void separate_geometry(GeometrySet &geometry_set,
some_valid_domain = true;
}
}
if (geometry_set.has_mesh()) {
if (const Mesh *mesh = geometry_set.get_mesh_for_read()) {
if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE, ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER)) {
file_ns::separate_mesh_selection(geometry_set, selection, domain, mode, propagation_info);
std::optional<Mesh *> dst_mesh = file_ns::separate_mesh_selection(
*mesh, selection, domain, mode, propagation_info);
if (dst_mesh) {
geometry_set.replace_mesh(*dst_mesh);
}
some_valid_domain = true;
}
}

View File

@ -133,17 +133,33 @@ const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item)
return get_simulation_item_cpp_type(eNodeSocketDatatype(item.socket_type));
}
static void remove_materials(Material ***materials, short *materials_num)
{
MEM_SAFE_FREE(*materials);
*materials_num = 0;
}
/**
* Removess parts of the geometry that can't be stored in the simulation state:
* - Anonymous attributes can't be stored because it is not known which of them will or will not be
* used in the future.
* - Materials can't be stored directly, because they are linked ID data blocks that can't be
* restored from baked data currently.
*/
static void cleanup_geometry_for_simulation_state(GeometrySet &main_geometry)
{
main_geometry.modify_geometry_sets([&](GeometrySet &geometry) {
if (Mesh *mesh = geometry.get_mesh_for_write()) {
mesh->attributes_for_write().remove_anonymous();
remove_materials(&mesh->mat, &mesh->totcol);
}
if (Curves *curves = geometry.get_curves_for_write()) {
curves->geometry.wrap().attributes_for_write().remove_anonymous();
remove_materials(&curves->mat, &curves->totcol);
}
if (PointCloud *pointcloud = geometry.get_pointcloud_for_write()) {
pointcloud->attributes_for_write().remove_anonymous();
remove_materials(&pointcloud->mat, &pointcloud->totcol);
}
if (bke::Instances *instances = geometry.get_instances_for_write()) {
instances->attributes_for_write().remove_anonymous();

View File

@ -3258,6 +3258,31 @@ static int wm_save_as_mainfile_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
if (RNA_boolean_get(op->ptr, "incremental")) {
char head[FILE_MAXFILE], tail[FILE_MAXFILE];
ushort digits;
int num = BLI_path_sequence_decode(filepath, head, sizeof(head), tail, sizeof(tail), &digits);
/* Numbers greater than INT_MAX return 0, resulting in always appending "1" to the name. */
if (num == 0 && digits == 0) {
/* This does nothing if there are no numbers at the end of the head. */
BLI_str_rstrip_digits(head);
}
const int tries_limit = 1000;
int tries = 0;
bool in_use = true;
do {
num++;
tries++;
BLI_path_sequence_encode(filepath, sizeof(filepath), head, tail, digits, num);
in_use = BLI_exists(filepath);
} while (in_use && tries < tries_limit && num < INT_MAX);
if (in_use) {
BKE_report(op->reports, RPT_ERROR, "Unable to find an available incremented file name");
return OPERATOR_CANCELLED;
}
}
const int fileflags_orig = G.fileflags;
int fileflags = G.fileflags;
@ -3392,6 +3417,18 @@ static int wm_save_mainfile_invoke(bContext *C, wmOperator *op, const wmEvent *
return ret;
}
static char *wm_save_mainfile_get_description(bContext * /*C*/,
wmOperatorType * /*ot*/,
PointerRNA *ptr)
{
if (RNA_boolean_get(ptr, "incremental")) {
return BLI_strdup(
TIP_("Save the current Blender file with a numerically incremented name that does not "
"overwrite any existing files"));
}
return nullptr;
}
void WM_OT_save_mainfile(wmOperatorType *ot)
{
ot->name = "Save Blender File";
@ -3401,6 +3438,7 @@ void WM_OT_save_mainfile(wmOperatorType *ot)
ot->invoke = wm_save_mainfile_invoke;
ot->exec = wm_save_as_mainfile_exec;
ot->check = wm_save_mainfile_check;
ot->get_description = wm_save_mainfile_get_description;
/* Omit window poll so this can work in background mode. */
PropertyRNA *prop;
@ -3420,6 +3458,14 @@ void WM_OT_save_mainfile(wmOperatorType *ot)
prop = RNA_def_boolean(ot->srna, "exit", false, "Exit", "Exit Blender after saving");
RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE));
prop = RNA_def_boolean(ot->srna,
"incremental",
false,
"Incremental",
"Save the current Blender file with a numerically incremented name that "
"does not overwrite any existing files");
RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE));
}
/** \} */

View File

@ -350,7 +350,6 @@ void WM_init(bContext *C, int argc, const char **argv)
}
}
BKE_material_copybuf_clear();
ED_render_clear_mtex_copybuf();
wm_history_file_read();
@ -622,7 +621,6 @@ void WM_exit_ex(bContext *C, const bool do_python, const bool do_user_exit_actio
DRW_subdiv_free();
}
BKE_material_copybuf_free();
ANIM_fcurves_copybuf_free();
ANIM_drivers_copybuf_free();
ANIM_driver_vars_copybuf_free();

View File

@ -538,14 +538,14 @@ static void draw_display_buffer(PlayState *ps, ImBuf *ibuf)
static void playanim_toscreen(
PlayState *ps, PlayAnimPict *picture, struct ImBuf *ibuf, int fontid, int fstep)
{
if (ibuf == NULL) {
printf("%s: no ibuf for picture '%s'\n", __func__, picture ? picture->filepath : "<NIL>");
return;
}
GHOST_ActivateWindowDrawingContext(g_WS.ghost_window);
/* size within window */
GPU_clear_color(0.1f, 0.1f, 0.1f, 0.0f);
/* A null `ibuf` is an exceptional case and should almost never happen.
* if it does, this function displays a warning along with the file-path that failed. */
if (ibuf) {
/* Size within window. */
float span_x = (ps->zoom * ibuf->x) / (float)ps->win_x;
float span_y = (ps->zoom * ibuf->y) / (float)ps->win_y;
@ -556,8 +556,6 @@ static void playanim_toscreen(
CLAMP(offs_x, 0.0f, 1.0f);
CLAMP(offs_y, 0.0f, 1.0f);
GPU_clear_color(0.1f, 0.1f, 0.1f, 0.0f);
/* checkerboard for case alpha */
if (ibuf->planes == 32) {
GPU_blend(GPU_BLEND_ALPHA);
@ -574,14 +572,24 @@ static void playanim_toscreen(
draw_display_buffer(ps, ibuf);
GPU_blend(GPU_BLEND_NONE);
}
pupdate_time();
if (picture && (g_WS.qual & (WS_QUAL_SHIFT | WS_QUAL_LMOUSE)) && (fontid != -1)) {
if ((fontid != -1) && picture &&
((g_WS.qual & (WS_QUAL_SHIFT | WS_QUAL_LMOUSE) ||
/* Always inform the user of an error, this should be an exceptional case. */
(ibuf == NULL))))
{
int sizex, sizey;
float fsizex_inv, fsizey_inv;
char label[32 + FILE_MAX];
if (ibuf) {
SNPRINTF(label, "%s | %.2f frames/s", picture->filepath, fstep / swaptime);
}
else {
SNPRINTF(label, "%s | <failed to load buffer>", picture->filepath);
}
playanim_window_get_size(&sizex, &sizey);
fsizex_inv = 1.0f / sizex;
@ -1678,11 +1686,11 @@ static char *wm_main_playanim_intern(int argc, const char **argv)
ibuf = ibuf_from_picture(ps.picture);
if (ibuf) {
{
#ifdef USE_IMB_CACHE
ps.picture->ibuf = ibuf;
#endif
if (ibuf) {
#ifdef USE_FRAME_CACHE_LIMIT
if (ps.picture->frame_cache_node == NULL) {
frame_cache_add(ps.picture);
@ -1691,10 +1699,10 @@ static char *wm_main_playanim_intern(int argc, const char **argv)
frame_cache_touch(ps.picture);
}
frame_cache_limit_apply(ibuf);
#endif /* USE_FRAME_CACHE_LIMIT */
STRNCPY(ibuf->filepath, ps.picture->filepath);
}
/* why only windows? (from 2.4x) - campbell */
#ifdef _WIN32
@ -1706,10 +1714,6 @@ static char *wm_main_playanim_intern(int argc, const char **argv)
}
ptottime -= swaptime;
playanim_toscreen(&ps, ps.picture, ibuf, ps.fontid, ps.fstep);
} /* else delete */
else {
printf("error: can't play this image type\n");
exit(EXIT_SUCCESS);
}
if (ps.once) {