Compare commits

...

22 Commits

Author SHA1 Message Date
4469102925 progress 2021-09-10 11:02:06 +02:00
0eb570cbbb progress 2021-09-09 18:55:38 +02:00
e3594c8e65 Merge branch 'master' into parallel-multi-function 2021-09-09 17:47:04 +02:00
bcae5507b8 Merge branch 'temp-geometry-nodes-fields' into parallel-multi-function 2021-09-09 17:45:46 +02:00
bf47fb40fd Geometry Nodes: fields and anonymous attributes
This implements the initial core framework for fields and anonymous
attributes (also see T91274).

The new functionality is hidden behind the "Geometry Nodes Fields"
feature flag. When enabled in the user preferences, the following
new nodes become available: `Position`, `Index`, `Normal`,
`Set Position` and `Attribute Capture`.

Socket inspection has not been updated to work with fields yet.

Besides these changes at the user level, this patch contains the
ground work for:
* building and evaluating fields at run-time (`FN_fields.hh`) and
* creating and accessing anonymous attributes on geometry
  (`BKE_anonymous_attribute.h`).

For evaluating fields we use a new so called multi-function procedure
(`FN_multi_function_procedure.hh`). It allows composing multi-functions
in arbitrary ways and supports efficient evaluation as is required by
fields. See `FN_multi_function_procedure.hh` for more details on how
this evaluation mechanism can be used.

A new `AttributeIDRef` has been added which allows handling named
and anonymous attributes in the same way in many places.

Hans and I worked on this patch together.

Differential Revision: https://developer.blender.org/D12414
2021-09-09 12:54:20 +02:00
2920a569b5 progress 2021-09-09 11:19:09 +02:00
0f6be4e152 Cleanup: Readfile: cleanup some logic checks. 2021-09-09 10:51:39 +02:00
0c0e5a8420 IDmanagement: makelocal: Fix mistake in recent commit.
rB8cc3d2d6f51f introduced option to force make_local code to either copy
or actually make a linked ID local, but logic of boolean options
handling was broken.

This commit simplifies logic here and fixes the issue.

NOTE: Since those new options were not used yet this was a harmless bug.
2021-09-09 10:51:10 +02:00
f8ead736a0 Fix FONT objects cannot use Object Font anymore
Mistake in {rB459974896228}.

To use Object Fonts, (vertex) instancing needs to be enabled.
So bring back the instancing panel and improve the instancing choice
(similar to rB6c0c766bcaa0) by just giving the 'Vertex' choice (or
'None') and explain this is only used for Object Fonts on characters.

Was reported in D11348 itself.

Differential Revision: https://developer.blender.org/D12438
2021-09-09 09:38:48 +02:00
9bb99532a5 Fix typo in BKE_object_as_kdtree
Seems like an oversight in {rB86635402d516}?

Stumbled over this while investigating another report, but this line in
its current form does not make sense (was taking  derivedFinal - not
derivedDeform - prior so I assume this has to be
BKE_object_get_evaluated_mesh now).

(it is now only used for vertex parenting where this should not be an
issue, but best keep this generic).

Differential Revision: https://developer.blender.org/D12425
2021-09-09 08:38:34 +02:00
b813648378 Fix typo checking empty gizmo keymap 2021-09-09 16:27:52 +10:00
3da09f4e29 Cleanup: remove newlines from logging text
Line endings are already added.
2021-09-09 16:26:15 +10:00
bda9e4238a Fix smooth-view failure to add mouse-move events
View operations that left the cursor over a gizmo were not being updated
because the mouse-move event was added while the view was animated
instead of once the animation had completed.
Mouse-move events were also missing when smooth-view was disabled.

This fixes a glitch with the 3D view navigation gizmo where multiple
clicks on the view aligned axis failed to switch to the opposite side
unless the user moved the cursor between clicks.
2021-09-09 15:33:44 +10:00
c8f80453d5 Modifier: add support for vertex groups
Allow blending the imported cache with the modifiers stack above the
MeshCache modifier.

This is particularly useful for instance when dealing with cloth
simulations performed in another software, where some parts of the cloth
are completely pinned (non-simulated, following the armature). Indeed,
this would allow modifying the animation in some areas without having to
rebake the other parts or the cloth, resulting in a much more flexible
workflow.

Reviewed By: #modeling, campbellbarton, mont29

Ref D9898
2021-09-09 14:05:35 +10:00
f9ebd17b4b Gizmo: warn when 2D projection fails with non-invertable matrices
Add a warning to quickly pinpoint the problem.

This would have simplified tracking down this problem in D12105.
2021-09-09 13:14:57 +10:00
df65103bf0 Fix: Incorrect default for exposed geometry nodes vectors 2021-09-08 18:44:35 -05:00
a131e3bec7 Fix GPU Buffer not allowing uint values greater than one byte
Error in format passed in `PyArg_Parse`
2021-09-08 20:28:00 -03:00
4e91cd5c11 Fix T91255: IDProperty UI as_dict() returns step as default value
Another typo in this section of code.
2021-09-08 15:46:02 -05:00
8f785524ae VSE: Adding a panning angle for multichannel audio.
The panning angle allows a more intuitive panning when the output is
surround sound. It sets the angle on the horizontal plane around the
listener. 0 degrees is to the front, negative values go to the left and
positive ones to the right. +/-180 degrees is directly from the back.

Technical detail: the panning value is linear with the panning angle
with a factor of 90 degrees. For stereo this means that -1 is left and
+1 right, since the speakers are exactly 90 degrees to either side.

Differential Revision: https://developer.blender.org/D12275
2021-09-08 21:18:08 +02:00
5bfc3a3421 Fix error running benchmark script with environment variables for builds
Ref D12434
2021-09-08 19:58:27 +02:00
6fc94d1848 Tests: updates for performance benchmarking
* Make "run" command (re-)run all tests, add "update" command to only
  run queued and outdated tests equivalent to the old "run" command.
* Support specifying environment variables for revisions, to easily
  compare multiple parameter values.
* Better sorting of revisions in graph.
2021-09-08 16:40:58 +02:00
068f012221 initial commit 2021-09-08 15:47:45 +02:00
40 changed files with 962 additions and 320 deletions

View File

@@ -267,7 +267,8 @@ class OBJECT_PT_instancing(ObjectButtonsPanel, Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
ob = context.object ob = context.object
return (ob.type in {'MESH', 'EMPTY', 'POINTCLOUD'}) # FONT objects need (vertex) instancing for the 'Object Font' feature
return (ob.type in {'MESH', 'EMPTY', 'POINTCLOUD', 'FONT'})
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout

View File

@@ -1659,7 +1659,7 @@ class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.use_property_split = True layout.use_property_split = False
st = context.space_data st = context.space_data
strip = context.active_sequence_strip strip = context.active_sequence_strip
@@ -1667,20 +1667,39 @@ class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel):
layout.active = not strip.mute layout.active = not strip.mute
col = layout.column()
col.prop(strip, "volume", text="Volume")
col.prop(strip, "pitch")
col = layout.column()
col.prop(strip, "pan")
col.enabled = sound is not None and sound.use_mono
if sound is not None: if sound is not None:
col = layout.column() col = layout.column()
split = col.split(factor=0.4)
split.label(text="")
split.prop(sound, "use_mono")
if st.waveform_display_type == 'DEFAULT_WAVEFORMS': if st.waveform_display_type == 'DEFAULT_WAVEFORMS':
col.prop(strip, "show_waveform") split = col.split(factor=0.4)
col.prop(sound, "use_mono") split.label(text="")
split.prop(strip, "show_waveform")
col = layout.column()
split = col.split(factor=0.4)
split.alignment = 'RIGHT'
split.label(text="Volume")
split.prop(strip, "volume", text="")
split = col.split(factor=0.4)
split.alignment = 'RIGHT'
split.label(text="Pitch")
split.prop(strip, "pitch", text="")
split = col.split(factor=0.4)
split.alignment = 'RIGHT'
split.label(text="Pan")
audio_channels = context.scene.render.ffmpeg.audio_channels
pan_text = ""
if audio_channels != 'MONO' and audio_channels != 'STEREO':
pan_text = "%.2f°" % (strip.pan * 90)
split.prop(strip, "pan", text=pan_text)
split.enabled = sound.use_mono and audio_channels != 'MONO'
class SEQUENCER_PT_adjust_comp(SequencerButtonsPanel, Panel): class SEQUENCER_PT_adjust_comp(SequencerButtonsPanel, Panel):

View File

@@ -148,8 +148,8 @@ static void brush_make_local(Main *bmain, ID *id, const int flags)
Brush *brush = (Brush *)id; Brush *brush = (Brush *)id;
const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0;
const bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0; bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
const bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0; bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
BLI_assert(force_copy == false || force_copy != force_local); BLI_assert(force_copy == false || force_copy != force_local);
bool is_local = false, is_lib = false; bool is_local = false, is_lib = false;
@@ -166,27 +166,33 @@ static void brush_make_local(Main *bmain, ID *id, const int flags)
if (!force_local && !force_copy) { if (!force_local && !force_copy) {
BKE_library_ID_test_usages(bmain, brush, &is_local, &is_lib); BKE_library_ID_test_usages(bmain, brush, &is_local, &is_lib);
if (lib_local || is_local) {
if (!is_lib) {
force_local = true;
}
else {
force_copy = true;
}
}
} }
if (lib_local || is_local || force_copy || force_local) { if (force_local) {
if (!is_lib || force_local) { BKE_lib_id_clear_library_data(bmain, &brush->id);
BKE_lib_id_clear_library_data(bmain, &brush->id); BKE_lib_id_expand_local(bmain, &brush->id);
BKE_lib_id_expand_local(bmain, &brush->id);
/* enable fake user by default */ /* enable fake user by default */
id_fake_user_set(&brush->id); id_fake_user_set(&brush->id);
} }
else { else if (force_copy) {
Brush *brush_new = (Brush *)BKE_id_copy(bmain, &brush->id); /* Ensures FAKE_USER is set */ Brush *brush_new = (Brush *)BKE_id_copy(bmain, &brush->id); /* Ensures FAKE_USER is set */
brush_new->id.us = 0; brush_new->id.us = 0;
/* setting newid is mandatory for complex make_lib_local logic... */ /* setting newid is mandatory for complex make_lib_local logic... */
ID_NEW_SET(brush, brush_new); ID_NEW_SET(brush, brush_new);
if (!lib_local) { if (!lib_local) {
BKE_libblock_remap(bmain, brush, brush_new, ID_REMAP_SKIP_INDIRECT_USAGE); BKE_libblock_remap(bmain, brush, brush_new, ID_REMAP_SKIP_INDIRECT_USAGE);
}
} }
} }
} }

View File

@@ -411,8 +411,8 @@ void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags)
} }
const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0;
const bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0; bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
const bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0; bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
BLI_assert(force_copy == false || force_copy != force_local); BLI_assert(force_copy == false || force_copy != force_local);
bool is_local = false, is_lib = false; bool is_local = false, is_lib = false;
@@ -426,42 +426,48 @@ void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags)
if (!force_copy && !force_local) { if (!force_copy && !force_local) {
BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib); BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib);
if (lib_local || is_local) {
if (!is_lib) {
force_local = true;
}
else {
force_copy = true;
}
}
} }
if (lib_local || is_local || force_copy || force_local) { if (force_local) {
if (!is_lib || force_local) { BKE_lib_id_clear_library_data(bmain, id);
BKE_lib_id_clear_library_data(bmain, id); BKE_lib_id_expand_local(bmain, id);
BKE_lib_id_expand_local(bmain, id); }
} else if (force_copy) {
else { ID *id_new = BKE_id_copy(bmain, id);
ID *id_new = BKE_id_copy(bmain, id);
/* Should not fail in expected use cases, /* Should not fail in expected use cases,
* but a few ID types cannot be copied (LIB, WM, SCR...). */ * but a few ID types cannot be copied (LIB, WM, SCR...). */
if (id_new != NULL) { if (id_new != NULL) {
id_new->us = 0; id_new->us = 0;
/* setting newid is mandatory for complex make_lib_local logic... */ /* setting newid is mandatory for complex make_lib_local logic... */
ID_NEW_SET(id, id_new); ID_NEW_SET(id, id_new);
Key *key = BKE_key_from_id(id), *key_new = BKE_key_from_id(id); Key *key = BKE_key_from_id(id), *key_new = BKE_key_from_id(id);
if (key && key_new) { if (key && key_new) {
ID_NEW_SET(key, key_new); ID_NEW_SET(key, key_new);
} }
bNodeTree *ntree = ntreeFromID(id), *ntree_new = ntreeFromID(id_new); bNodeTree *ntree = ntreeFromID(id), *ntree_new = ntreeFromID(id_new);
if (ntree && ntree_new) { if (ntree && ntree_new) {
ID_NEW_SET(ntree, ntree_new); ID_NEW_SET(ntree, ntree_new);
} }
if (GS(id->name) == ID_SCE) { if (GS(id->name) == ID_SCE) {
Collection *master_collection = ((Scene *)id)->master_collection, Collection *master_collection = ((Scene *)id)->master_collection,
*master_collection_new = ((Scene *)id_new)->master_collection; *master_collection_new = ((Scene *)id_new)->master_collection;
if (master_collection && master_collection_new) { if (master_collection && master_collection_new) {
ID_NEW_SET(master_collection, master_collection_new); ID_NEW_SET(master_collection, master_collection_new);
}
} }
}
if (!lib_local) { if (!lib_local) {
BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE); BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE);
}
} }
} }
} }

View File

@@ -331,8 +331,8 @@ static void object_make_local(Main *bmain, ID *id, const int flags)
Object *ob = (Object *)id; Object *ob = (Object *)id;
const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0;
const bool clear_proxy = (flags & LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING) == 0; const bool clear_proxy = (flags & LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING) == 0;
const bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0; bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
const bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0; bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
BLI_assert(force_copy == false || force_copy != force_local); BLI_assert(force_copy == false || force_copy != force_local);
bool is_local = false, is_lib = false; bool is_local = false, is_lib = false;
@@ -346,32 +346,38 @@ static void object_make_local(Main *bmain, ID *id, const int flags)
if (!force_local && !force_copy) { if (!force_local && !force_copy) {
BKE_library_ID_test_usages(bmain, ob, &is_local, &is_lib); BKE_library_ID_test_usages(bmain, ob, &is_local, &is_lib);
} if (lib_local || is_local) {
if (!is_lib) {
if (lib_local || is_local || force_copy || force_local) { force_local = true;
if (!is_lib || force_local) { }
BKE_lib_id_clear_library_data(bmain, &ob->id); else {
BKE_lib_id_expand_local(bmain, &ob->id); force_copy = true;
if (clear_proxy) {
if (ob->proxy_from != NULL) {
ob->proxy_from->proxy = NULL;
ob->proxy_from->proxy_group = NULL;
}
ob->proxy = ob->proxy_from = ob->proxy_group = NULL;
} }
} }
else { }
Object *ob_new = (Object *)BKE_id_copy(bmain, &ob->id);
id_us_min(&ob_new->id);
ob_new->proxy = ob_new->proxy_from = ob_new->proxy_group = NULL; if (force_local) {
BKE_lib_id_clear_library_data(bmain, &ob->id);
/* setting newid is mandatory for complex make_lib_local logic... */ BKE_lib_id_expand_local(bmain, &ob->id);
ID_NEW_SET(ob, ob_new); if (clear_proxy) {
if (ob->proxy_from != NULL) {
if (!lib_local) { ob->proxy_from->proxy = NULL;
BKE_libblock_remap(bmain, ob, ob_new, ID_REMAP_SKIP_INDIRECT_USAGE); ob->proxy_from->proxy_group = NULL;
} }
ob->proxy = ob->proxy_from = ob->proxy_group = NULL;
}
}
else if (force_copy) {
Object *ob_new = (Object *)BKE_id_copy(bmain, &ob->id);
id_us_min(&ob_new->id);
ob_new->proxy = ob_new->proxy_from = ob_new->proxy_group = NULL;
/* setting newid is mandatory for complex make_lib_local logic... */
ID_NEW_SET(ob, ob_new);
if (!lib_local) {
BKE_libblock_remap(bmain, ob, ob_new, ID_REMAP_SKIP_INDIRECT_USAGE);
} }
} }
} }
@@ -5312,7 +5318,7 @@ KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot)
unsigned int i; unsigned int i;
Mesh *me_eval = ob->runtime.mesh_deform_eval ? ob->runtime.mesh_deform_eval : Mesh *me_eval = ob->runtime.mesh_deform_eval ? ob->runtime.mesh_deform_eval :
ob->runtime.mesh_deform_eval; BKE_object_get_evaluated_mesh(ob);
const int *index; const int *index;
if (me_eval && (index = CustomData_get_layer(&me_eval->vdata, CD_ORIGINDEX))) { if (me_eval && (index = CustomData_get_layer(&me_eval->vdata, CD_ORIGINDEX))) {

View File

@@ -653,7 +653,8 @@ inline void devirtualize_varray2(const VArray<T1> &varray1,
return; return;
} }
} }
/* This fallback is used even when one of the inputs could be optimized. It's probably not worth /* This fallback is used even when one of the inputs could be optimized. It's probably not
worth
* it to optimize just one of the inputs, because then the compiler still has to call into * it to optimize just one of the inputs, because then the compiler still has to call into
* unknown code, which inhibits many compiler optimizations. */ * unknown code, which inhibits many compiler optimizations. */
func(varray1, varray2); func(varray1, varray2);

View File

@@ -4500,7 +4500,8 @@ static void add_loose_objects_to_scene(Main *mainvar,
* or for a collection when *lib has been set. */ * or for a collection when *lib has been set. */
LISTBASE_FOREACH (Object *, ob, &mainvar->objects) { LISTBASE_FOREACH (Object *, ob, &mainvar->objects) {
bool do_it = (ob->id.tag & LIB_TAG_DOIT) != 0; bool do_it = (ob->id.tag & LIB_TAG_DOIT) != 0;
if (do_it || ((ob->id.tag & LIB_TAG_INDIRECT) && (ob->id.tag & LIB_TAG_PRE_EXISTING) == 0)) { if (do_it ||
((ob->id.tag & LIB_TAG_INDIRECT) != 0 && (ob->id.tag & LIB_TAG_PRE_EXISTING) == 0)) {
if (do_append) { if (do_append) {
if (ob->id.us == 0) { if (ob->id.us == 0) {
do_it = true; do_it = true;
@@ -4648,7 +4649,7 @@ static void add_collections_to_scene(Main *mainvar,
LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) { LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) {
Object *ob = coll_ob->ob; Object *ob = coll_ob->ob;
if ((ob->id.tag & (LIB_TAG_PRE_EXISTING | LIB_TAG_DOIT | LIB_TAG_INDIRECT)) == 0 && if ((ob->id.tag & (LIB_TAG_PRE_EXISTING | LIB_TAG_DOIT | LIB_TAG_INDIRECT)) == 0 &&
(ob->id.lib == lib) && (object_in_any_scene(bmain, ob) == 0)) { (ob->id.lib == lib) && (object_in_any_scene(bmain, ob) == false)) {
do_add_collection = true; do_add_collection = true;
break; break;
} }

View File

@@ -27,6 +27,7 @@ set(INC
../../makesdna ../../makesdna
../../makesrna ../../makesrna
../../windowmanager ../../windowmanager
../../../../intern/clog
../../../../intern/eigen ../../../../intern/eigen
../../../../intern/glew-mx ../../../../intern/glew-mx
../../../../intern/guardedalloc ../../../../intern/guardedalloc

View File

@@ -39,9 +39,13 @@
#include "ED_view3d.h" #include "ED_view3d.h"
#include "CLG_log.h"
/* own includes */ /* own includes */
#include "gizmo_library_intern.h" #include "gizmo_library_intern.h"
static CLG_LogRef LOG = {"ed.gizmo.library_utils"};
/* factor for precision tweaking */ /* factor for precision tweaking */
#define GIZMO_PRECISION_FAC 0.05f #define GIZMO_PRECISION_FAC 0.05f
@@ -182,7 +186,7 @@ bool gizmo_window_project_2d(bContext *C,
bool use_offset, bool use_offset,
float r_co[2]) float r_co[2])
{ {
float mat[4][4]; float mat[4][4], imat[4][4];
{ {
float mat_identity[4][4]; float mat_identity[4][4];
struct WM_GizmoMatrixParams params = {NULL}; struct WM_GizmoMatrixParams params = {NULL};
@@ -193,6 +197,14 @@ bool gizmo_window_project_2d(bContext *C,
WM_gizmo_calc_matrix_final_params(gz, &params, mat); WM_gizmo_calc_matrix_final_params(gz, &params, mat);
} }
if (!invert_m4_m4(imat, mat)) {
CLOG_WARN(&LOG,
"Gizmo \"%s\" of group \"%s\" has matrix that could not be inverted "
"(projection will fail)",
gz->type->idname,
gz->parent_gzgroup->type->idname);
}
/* rotate mouse in relation to the center and relocate it */ /* rotate mouse in relation to the center and relocate it */
if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) { if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) {
/* For 3d views, transform 2D mouse pos onto plane. */ /* For 3d views, transform 2D mouse pos onto plane. */
@@ -202,8 +214,6 @@ bool gizmo_window_project_2d(bContext *C,
plane_from_point_normal_v3(plane, mat[3], mat[2]); plane_from_point_normal_v3(plane, mat[3], mat[2]);
bool clip_ray = ((RegionView3D *)region->regiondata)->is_persp; bool clip_ray = ((RegionView3D *)region->regiondata)->is_persp;
if (ED_view3d_win_to_3d_on_plane(region, plane, mval, clip_ray, co)) { if (ED_view3d_win_to_3d_on_plane(region, plane, mval, clip_ray, co)) {
float imat[4][4];
invert_m4_m4(imat, mat);
mul_m4_v3(imat, co); mul_m4_v3(imat, co);
r_co[0] = co[(axis + 1) % 3]; r_co[0] = co[(axis + 1) % 3];
r_co[1] = co[(axis + 2) % 3]; r_co[1] = co[(axis + 2) % 3];
@@ -213,8 +223,6 @@ bool gizmo_window_project_2d(bContext *C,
} }
float co[3] = {mval[0], mval[1], 0.0f}; float co[3] = {mval[0], mval[1], 0.0f};
float imat[4][4];
invert_m4_m4(imat, mat);
mul_m4_v3(imat, co); mul_m4_v3(imat, co);
copy_v2_v2(r_co, co); copy_v2_v2(r_co, co);
return true; return true;
@@ -223,7 +231,7 @@ bool gizmo_window_project_2d(bContext *C,
bool gizmo_window_project_3d( bool gizmo_window_project_3d(
bContext *C, const struct wmGizmo *gz, const float mval[2], bool use_offset, float r_co[3]) bContext *C, const struct wmGizmo *gz, const float mval[2], bool use_offset, float r_co[3])
{ {
float mat[4][4]; float mat[4][4], imat[4][4];
{ {
float mat_identity[4][4]; float mat_identity[4][4];
struct WM_GizmoMatrixParams params = {NULL}; struct WM_GizmoMatrixParams params = {NULL};
@@ -234,20 +242,25 @@ bool gizmo_window_project_3d(
WM_gizmo_calc_matrix_final_params(gz, &params, mat); WM_gizmo_calc_matrix_final_params(gz, &params, mat);
} }
if (!invert_m4_m4(imat, mat)) {
CLOG_WARN(&LOG,
"Gizmo \"%s\" of group \"%s\" has matrix that could not be inverted "
"(projection will fail)",
gz->type->idname,
gz->parent_gzgroup->type->idname);
}
if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) { if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) {
View3D *v3d = CTX_wm_view3d(C); View3D *v3d = CTX_wm_view3d(C);
ARegion *region = CTX_wm_region(C); ARegion *region = CTX_wm_region(C);
/* NOTE: we might want a custom reference point passed in, /* NOTE: we might want a custom reference point passed in,
* instead of the gizmo center. */ * instead of the gizmo center. */
ED_view3d_win_to_3d(v3d, region, mat[3], mval, r_co); ED_view3d_win_to_3d(v3d, region, mat[3], mval, r_co);
invert_m4(mat); mul_m4_v3(imat, r_co);
mul_m4_v3(mat, r_co);
return true; return true;
} }
float co[3] = {mval[0], mval[1], 0.0f}; float co[3] = {mval[0], mval[1], 0.0f};
float imat[4][4];
invert_m4_m4(imat, mat);
mul_m4_v3(imat, co); mul_m4_v3(imat, co);
copy_v2_v2(r_co, co); copy_v2_v2(r_co, co);
return true; return true;

View File

@@ -137,7 +137,6 @@ void ED_view3d_smooth_view_ex(
{ {
RegionView3D *rv3d = region->regiondata; RegionView3D *rv3d = region->regiondata;
struct SmoothView3DStore sms = {{0}}; struct SmoothView3DStore sms = {{0}};
bool ok = false;
/* initialize sms */ /* initialize sms */
view3d_smooth_view_state_backup(&sms.dst, v3d, rv3d); view3d_smooth_view_state_backup(&sms.dst, v3d, rv3d);
@@ -200,29 +199,30 @@ void ED_view3d_smooth_view_ex(
sms.to_camera = true; /* restore view3d values in end */ sms.to_camera = true; /* restore view3d values in end */
} }
/* skip smooth viewing for external render engine draw */ bool changed = false; /* zero means no difference */
if (smooth_viewtx && !(v3d->shading.type == OB_RENDER && rv3d->render_engine)) {
bool changed = false; /* zero means no difference */
if (sview->camera_old != sview->camera) { if (sview->camera_old != sview->camera) {
changed = true; changed = true;
} }
else if (sms.dst.dist != rv3d->dist) { else if (sms.dst.dist != rv3d->dist) {
changed = true; changed = true;
} }
else if (sms.dst.lens != v3d->lens) { else if (sms.dst.lens != v3d->lens) {
changed = true; changed = true;
} }
else if (!equals_v3v3(sms.dst.ofs, rv3d->ofs)) { else if (!equals_v3v3(sms.dst.ofs, rv3d->ofs)) {
changed = true; changed = true;
} }
else if (!equals_v4v4(sms.dst.quat, rv3d->viewquat)) { else if (!equals_v4v4(sms.dst.quat, rv3d->viewquat)) {
changed = true; changed = true;
} }
/* The new view is different from the previous state. */
if (changed) {
/* Skip smooth viewing for external render engine draw. */
if (smooth_viewtx && !(v3d->shading.type == OB_RENDER && rv3d->render_engine)) {
/* The new view is different from the old one
* so animate the view */
if (changed) {
/* original values */ /* original values */
if (sview->camera_old) { if (sview->camera_old) {
Object *ob_camera_old_eval = DEG_get_evaluated_object(depsgraph, sview->camera_old); Object *ob_camera_old_eval = DEG_get_evaluated_object(depsgraph, sview->camera_old);
@@ -279,27 +279,25 @@ void ED_view3d_smooth_view_ex(
} }
/* #TIMER1 is hard-coded in key-map. */ /* #TIMER1 is hard-coded in key-map. */
rv3d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0); rv3d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0);
ok = true;
} }
} else {
if (sms.to_camera == false) {
copy_v3_v3(rv3d->ofs, sms.dst.ofs);
copy_qt_qt(rv3d->viewquat, sms.dst.quat);
rv3d->dist = sms.dst.dist;
v3d->lens = sms.dst.lens;
/* if we get here nothing happens */ ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
if (ok == false) { }
if (sms.to_camera == false) {
copy_v3_v3(rv3d->ofs, sms.dst.ofs);
copy_qt_qt(rv3d->viewquat, sms.dst.quat);
rv3d->dist = sms.dst.dist;
v3d->lens = sms.dst.lens;
ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d); if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
view3d_boxview_copy(area, region);
}
ED_region_tag_redraw(region);
WM_event_add_mousemove(win);
} }
if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
view3d_boxview_copy(area, region);
}
ED_region_tag_redraw(region);
} }
} }
@@ -320,6 +318,7 @@ void ED_view3d_smooth_view(bContext *C,
/* only meant for timer usage */ /* only meant for timer usage */
static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, bool sync_boxview) static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, bool sync_boxview)
{ {
wmWindowManager *wm = CTX_wm_manager(C);
RegionView3D *rv3d = region->regiondata; RegionView3D *rv3d = region->regiondata;
struct SmoothView3DStore *sms = rv3d->sms; struct SmoothView3DStore *sms = rv3d->sms;
float step, step_inv; float step, step_inv;
@@ -333,6 +332,7 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b
/* end timer */ /* end timer */
if (step >= 1.0f) { if (step >= 1.0f) {
wmWindow *win = CTX_wm_window(C);
/* if we went to camera, store the original */ /* if we went to camera, store the original */
if (sms->to_camera) { if (sms->to_camera) {
@@ -355,9 +355,12 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b
MEM_freeN(rv3d->sms); MEM_freeN(rv3d->sms);
rv3d->sms = NULL; rv3d->sms = NULL;
WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), rv3d->smooth_timer); WM_event_remove_timer(wm, win, rv3d->smooth_timer);
rv3d->smooth_timer = NULL; rv3d->smooth_timer = NULL;
rv3d->rflag &= ~RV3D_NAVIGATING; rv3d->rflag &= ~RV3D_NAVIGATING;
/* Event handling won't know if a UI item has been moved under the pointer. */
WM_event_add_mousemove(win);
} }
else { else {
/* ease in/out */ /* ease in/out */
@@ -380,12 +383,9 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b
const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d); ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
if (ED_screen_animation_playing(CTX_wm_manager(C))) { if (ED_screen_animation_playing(wm)) {
ED_view3d_camera_lock_autokey(v3d, rv3d, C, true, true); ED_view3d_camera_lock_autokey(v3d, rv3d, C, true, true);
} }
/* Event handling won't know if a UI item has been moved under the pointer. */
WM_event_add_mousemove(CTX_wm_window(C));
} }
if (sync_boxview && (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW)) { if (sync_boxview && (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW)) {

View File

@@ -34,9 +34,11 @@ set(SRC
intern/generic_virtual_vector_array.cc intern/generic_virtual_vector_array.cc
intern/multi_function.cc intern/multi_function.cc
intern/multi_function_builder.cc intern/multi_function_builder.cc
intern/multi_function_parallel.cc
intern/multi_function_procedure.cc intern/multi_function_procedure.cc
intern/multi_function_procedure_builder.cc intern/multi_function_procedure_builder.cc
intern/multi_function_procedure_executor.cc intern/multi_function_procedure_executor.cc
intern/multi_function_procedure_optimization.cc
FN_cpp_type.hh FN_cpp_type.hh
FN_cpp_type_make.hh FN_cpp_type_make.hh
@@ -54,9 +56,11 @@ set(SRC
FN_multi_function_data_type.hh FN_multi_function_data_type.hh
FN_multi_function_param_type.hh FN_multi_function_param_type.hh
FN_multi_function_params.hh FN_multi_function_params.hh
FN_multi_function_parallel.hh
FN_multi_function_procedure.hh FN_multi_function_procedure.hh
FN_multi_function_procedure_builder.hh FN_multi_function_procedure_builder.hh
FN_multi_function_procedure_executor.hh FN_multi_function_procedure_executor.hh
FN_multi_function_procedure_optimization.hh
FN_multi_function_signature.hh FN_multi_function_signature.hh
) )
@@ -64,6 +68,22 @@ set(LIB
bf_blenlib bf_blenlib
) )
if(WITH_TBB)
add_definitions(-DWITH_TBB)
if(WIN32)
# TBB includes Windows.h which will define min/max macros
# that will collide with the stl versions.
add_definitions(-DNOMINMAX)
endif()
list(APPEND INC_SYS
${TBB_INCLUDE_DIRS}
)
list(APPEND LIB
${TBB_LIBRARIES}
)
endif()
blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS) if(WITH_GTESTS)

View File

@@ -911,4 +911,50 @@ template<typename T> class GVMutableArray_Typed {
} }
}; };
class GVArray_For_SlicedGVArray : public GVArray {
protected:
const GVArray &varray_;
int64_t offset_;
public:
GVArray_For_SlicedGVArray(const GVArray &varray, const IndexRange slice)
: GVArray(varray.type(), slice.size()), varray_(varray), offset_(slice.start())
{
BLI_assert(slice.one_after_last() <= varray.size());
}
void get_impl(const int64_t index, void *r_value) const override;
void get_to_uninitialized_impl(const int64_t index, void *r_value) const override;
};
/**
* Utility class to create the "best" sliced virtual array.
*/
class GVArray_Slice {
private:
const GVArray *varray_;
/* Of these optional virtual arrays, at most one is constructed at any time. */
std::optional<GVArray_For_GSpan> varray_span_;
std::optional<GVArray_For_SingleValue> varray_single_;
std::optional<GVArray_For_SlicedGVArray> varray_any_;
public:
GVArray_Slice(const GVArray &varray, const IndexRange slice);
const GVArray &operator*()
{
return *varray_;
}
const GVArray *operator->()
{
return varray_;
}
operator const GVArray &()
{
return *varray_;
}
};
} // namespace blender::fn } // namespace blender::fn

View File

@@ -0,0 +1,39 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup fn
*/
#include "FN_multi_function.hh"
namespace blender::fn {
class ParallelMultiFunction : public MultiFunction {
private:
const MultiFunction &fn_;
const int64_t grain_size_;
bool threading_supported_;
public:
ParallelMultiFunction(const MultiFunction &fn, const int64_t grain_size);
void call(IndexMask mask, MFParams params, MFContext context) const override;
};
} // namespace blender::fn

View File

@@ -42,6 +42,55 @@ enum class MFInstructionType {
Return, Return,
}; };
/**
* An #MFInstructionCursor points to a position in a multi-function procedure, where an instruction
* can be inserted.
*/
class MFInstructionCursor {
public:
enum Type {
None,
Entry,
Call,
Destruct,
Branch,
Dummy,
};
private:
Type type_ = None;
MFInstruction *instruction_ = nullptr;
/* Only used when it is a branch instruction. */
bool branch_output_ = false;
public:
MFInstructionCursor() = default;
MFInstructionCursor(MFCallInstruction &instruction);
MFInstructionCursor(MFDestructInstruction &instruction);
MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output);
MFInstructionCursor(MFDummyInstruction &instruction);
static MFInstructionCursor ForEntry();
MFInstruction *next(MFProcedure &procedure) const;
void set_next(MFProcedure &procedure, MFInstruction *new_instruction) const;
MFInstruction *instruction() const;
Type type() const;
friend bool operator==(const MFInstructionCursor &a, const MFInstructionCursor &b)
{
return a.type_ == b.type_ && a.instruction_ == b.instruction_ &&
a.branch_output_ == b.branch_output_;
}
friend bool operator!=(const MFInstructionCursor &a, const MFInstructionCursor &b)
{
return !(a == b);
}
};
/** /**
* A variable is similar to a virtual register in other libraries. During evaluation, every is * A variable is similar to a virtual register in other libraries. During evaluation, every is
* either uninitialized or contains a value for every index (remember, a multi-function procedure * either uninitialized or contains a value for every index (remember, a multi-function procedure
@@ -73,7 +122,7 @@ class MFVariable : NonCopyable, NonMovable {
class MFInstruction : NonCopyable, NonMovable { class MFInstruction : NonCopyable, NonMovable {
protected: protected:
MFInstructionType type_; MFInstructionType type_;
Vector<MFInstruction *> prev_; Vector<MFInstructionCursor> prev_;
friend MFProcedure; friend MFProcedure;
friend MFCallInstruction; friend MFCallInstruction;
@@ -89,8 +138,7 @@ class MFInstruction : NonCopyable, NonMovable {
* Other instructions that come before this instruction. There can be multiple previous * Other instructions that come before this instruction. There can be multiple previous
* instructions when branching is used in the procedure. * instructions when branching is used in the procedure.
*/ */
Span<MFInstruction *> prev(); Span<MFInstructionCursor> prev() const;
Span<const MFInstruction *> prev() const;
}; };
/** /**
@@ -246,6 +294,9 @@ class MFProcedure : NonCopyable, NonMovable {
Span<MFVariable *> variables(); Span<MFVariable *> variables();
Span<const MFVariable *> variables() const; Span<const MFVariable *> variables() const;
Span<MFDestructInstruction *> destruct_instructions();
Span<const MFDestructInstruction *> destruct_instructions() const;
std::string to_dot() const; std::string to_dot() const;
bool validate() const; bool validate() const;
@@ -275,6 +326,50 @@ using MFDestructInstruction = fn::MFDestructInstruction;
using MFProcedure = fn::MFProcedure; using MFProcedure = fn::MFProcedure;
} // namespace multi_function_procedure_types } // namespace multi_function_procedure_types
/* --------------------------------------------------------------------
* MFInstructionCursor inline methods.
*/
inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction)
: type_(Call), instruction_(&instruction)
{
}
inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction)
: type_(Destruct), instruction_(&instruction)
{
}
inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction,
bool branch_output)
: type_(Branch), instruction_(&instruction), branch_output_(branch_output)
{
}
inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction)
: type_(Dummy), instruction_(&instruction)
{
}
inline MFInstructionCursor MFInstructionCursor::ForEntry()
{
MFInstructionCursor cursor;
cursor.type_ = Type::Entry;
return cursor;
}
inline MFInstruction *MFInstructionCursor::instruction() const
{
/* This isn't really const correct unfortunately, because to make it correct we'll need a const
* version of #MFInstructionCursor. */
return instruction_;
}
inline MFInstructionCursor::Type MFInstructionCursor::type() const
{
return type_;
}
/* -------------------------------------------------------------------- /* --------------------------------------------------------------------
* MFVariable inline methods. * MFVariable inline methods.
*/ */
@@ -308,12 +403,7 @@ inline MFInstructionType MFInstruction::type() const
return type_; return type_;
} }
inline Span<MFInstruction *> MFInstruction::prev() inline Span<MFInstructionCursor> MFInstruction::prev() const
{
return prev_;
}
inline Span<const MFInstruction *> MFInstruction::prev() const
{ {
return prev_; return prev_;
} }
@@ -449,4 +539,14 @@ inline Span<const MFVariable *> MFProcedure::variables() const
return variables_; return variables_;
} }
inline Span<MFDestructInstruction *> MFProcedure::destruct_instructions()
{
return destruct_instructions_;
}
inline Span<const MFDestructInstruction *> MFProcedure::destruct_instructions() const
{
return destruct_instructions_;
}
} // namespace blender::fn } // namespace blender::fn

View File

@@ -24,31 +24,6 @@
namespace blender::fn { namespace blender::fn {
/**
* An #MFInstructionCursor points to a position in a multi-function procedure, where an instruction
* can be inserted.
*/
class MFInstructionCursor {
private:
MFInstruction *instruction_ = nullptr;
/* Only used when it is a branch instruction. */
bool branch_output_ = false;
/* Only used when instruction is null. */
bool is_entry_ = false;
public:
MFInstructionCursor() = default;
MFInstructionCursor(MFCallInstruction &instruction);
MFInstructionCursor(MFDestructInstruction &instruction);
MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output);
MFInstructionCursor(MFDummyInstruction &instruction);
static MFInstructionCursor Entry();
void insert(MFProcedure &procedure, MFInstruction *new_instruction);
};
/** /**
* Utility class to build a #MFProcedure. * Utility class to build a #MFProcedure.
*/ */
@@ -64,7 +39,7 @@ class MFProcedureBuilder {
struct Loop; struct Loop;
MFProcedureBuilder(MFProcedure &procedure, MFProcedureBuilder(MFProcedure &procedure,
MFInstructionCursor initial_cursor = MFInstructionCursor::Entry()); MFInstructionCursor initial_cursor = MFInstructionCursor::ForEntry());
MFProcedureBuilder(Span<MFProcedureBuilder *> builders); MFProcedureBuilder(Span<MFProcedureBuilder *> builders);
@@ -121,38 +96,6 @@ struct MFProcedureBuilder::Loop {
MFDummyInstruction *end = nullptr; MFDummyInstruction *end = nullptr;
}; };
/* --------------------------------------------------------------------
* MFInstructionCursor inline methods.
*/
inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction)
: instruction_(&instruction)
{
}
inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction)
: instruction_(&instruction)
{
}
inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction,
bool branch_output)
: instruction_(&instruction), branch_output_(branch_output)
{
}
inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction)
: instruction_(&instruction)
{
}
inline MFInstructionCursor MFInstructionCursor::Entry()
{
MFInstructionCursor cursor;
cursor.is_entry_ = true;
return cursor;
}
/* -------------------------------------------------------------------- /* --------------------------------------------------------------------
* MFProcedureBuilder inline methods. * MFProcedureBuilder inline methods.
*/ */
@@ -253,7 +196,7 @@ inline void MFProcedureBuilder::add_output_parameter(MFVariable &variable)
inline void MFProcedureBuilder::link_to_cursors(MFInstruction *instruction) inline void MFProcedureBuilder::link_to_cursors(MFInstruction *instruction)
{ {
for (MFInstructionCursor &cursor : cursors_) { for (MFInstructionCursor &cursor : cursors_) {
cursor.insert(*procedure_, instruction); cursor.set_next(*procedure_, instruction);
} }
} }

View File

@@ -0,0 +1,29 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup fn
*/
#include "FN_multi_function_procedure.hh"
namespace blender::fn::procedure_optimization {
void move_destructs_up(MFProcedure &procedure);
}

View File

@@ -18,8 +18,13 @@
#include "BLI_multi_value_map.hh" #include "BLI_multi_value_map.hh"
#include "BLI_set.hh" #include "BLI_set.hh"
#include "BLI_stack.hh" #include "BLI_stack.hh"
#include "BLI_timeit.hh"
#include "BLI_vector_set.hh" #include "BLI_vector_set.hh"
#include "FN_field.hh"
#include "FN_multi_function_parallel.hh"
#include "FN_multi_function_procedure_optimization.hh"
#include "FN_field.hh" #include "FN_field.hh"
namespace blender::fn { namespace blender::fn {
@@ -183,8 +188,8 @@ static void build_multi_function_procedure_for_fields(MFProcedure &procedure,
const Span<GField> operation_inputs = operation.inputs(); const Span<GField> operation_inputs = operation.inputs();
if (field_with_index.current_input_index < operation_inputs.size()) { if (field_with_index.current_input_index < operation_inputs.size()) {
/* Not all inputs are handled yet. Push the next input field to the stack and increment the /* Not all inputs are handled yet. Push the next input field to the stack and increment
* input index. */ * the input index. */
fields_to_check.push({operation_inputs[field_with_index.current_input_index]}); fields_to_check.push({operation_inputs[field_with_index.current_input_index]});
field_with_index.current_input_index++; field_with_index.current_input_index++;
} }
@@ -248,8 +253,8 @@ struct PartiallyInitializedArray : NonCopyable, NonMovable {
}; };
/** /**
* Evaluate fields in the given context. If possible, multiple fields should be evaluated together, * Evaluate fields in the given context. If possible, multiple fields should be evaluated
* because that can be more efficient when they share common sub-fields. * together, because that can be more efficient when they share common sub-fields.
* *
* \param scope: The resource scope that owns data that makes up the output virtual arrays. Make * \param scope: The resource scope that owns data that makes up the output virtual arrays. Make
* sure the scope is not destructed when the output virtual arrays are still used. * sure the scope is not destructed when the output virtual arrays are still used.
@@ -271,6 +276,7 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
const FieldContext &context, const FieldContext &context,
Span<GVMutableArray *> dst_varrays) Span<GVMutableArray *> dst_varrays)
{ {
Vector<const GVArray *> r_varrays(fields_to_evaluate.size(), nullptr); Vector<const GVArray *> r_varrays(fields_to_evaluate.size(), nullptr);
const int array_size = mask.min_array_size(); const int array_size = mask.min_array_size();
@@ -290,8 +296,8 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
Vector<const GVArray *> field_context_inputs = get_field_context_inputs( Vector<const GVArray *> field_context_inputs = get_field_context_inputs(
scope, mask, context, field_tree_info.deduplicated_field_inputs); scope, mask, context, field_tree_info.deduplicated_field_inputs);
/* Finish fields that output an input varray directly. For those we don't have to do any further /* Finish fields that output an input varray directly. For those we don't have to do any
* processing. */ * further processing. */
for (const int out_index : fields_to_evaluate.index_range()) { for (const int out_index : fields_to_evaluate.index_range()) {
const GFieldRef &field = fields_to_evaluate[out_index]; const GFieldRef &field = fields_to_evaluate[out_index];
if (!field.node().is_input()) { if (!field.node().is_input()) {
@@ -333,8 +339,14 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
MFProcedure procedure; MFProcedure procedure;
build_multi_function_procedure_for_fields( build_multi_function_procedure_for_fields(
procedure, scope, field_tree_info, varying_fields_to_evaluate); procedure, scope, field_tree_info, varying_fields_to_evaluate);
// std::cout << procedure.to_dot() << "\n";
// fn::procedure_optimization::move_destructs_up(procedure);
// std::cout << procedure.to_dot() << "\n";
MFProcedureExecutor procedure_executor{"Procedure", procedure}; MFProcedureExecutor procedure_executor{"Procedure", procedure};
MFParamsBuilder mf_params{procedure_executor, array_size}; fn::ParallelMultiFunction parallel_fn{procedure_executor, 10000};
const MultiFunction &fn_to_execute = parallel_fn;
MFParamsBuilder mf_params{fn_to_execute, array_size};
MFContextBuilder mf_context; MFContextBuilder mf_context;
/* Provide inputs to the procedure executor. */ /* Provide inputs to the procedure executor. */
@@ -376,7 +388,8 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
mf_params.add_uninitialized_single_output(span); mf_params.add_uninitialized_single_output(span);
} }
procedure_executor.call(mask, mf_params, mf_context); SCOPED_TIMER(__func__);
fn_to_execute.call(mask, mf_params, mf_context);
} }
/* Evaluate constant fields if necessary. */ /* Evaluate constant fields if necessary. */
@@ -419,8 +432,8 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
procedure_executor.call(IndexRange(1), mf_params, mf_context); procedure_executor.call(IndexRange(1), mf_params, mf_context);
} }
/* Copy data to supplied destination arrays if necessary. In some cases the evaluation above has /* Copy data to supplied destination arrays if necessary. In some cases the evaluation above
* written the computed data in the right place already. */ * has written the computed data in the right place already. */
if (!dst_varrays.is_empty()) { if (!dst_varrays.is_empty()) {
for (const int out_index : fields_to_evaluate.index_range()) { for (const int out_index : fields_to_evaluate.index_range()) {
GVMutableArray *output_varray = get_dst_varray_if_available(out_index); GVMutableArray *output_varray = get_dst_varray_if_available(out_index);

View File

@@ -387,4 +387,43 @@ void GVMutableArray_GSpan::disable_not_applied_warning()
show_not_saved_warning_ = false; show_not_saved_warning_ = false;
} }
/* --------------------------------------------------------------------
* GVArray_For_SlicedGVArray.
*/
void GVArray_For_SlicedGVArray::get_impl(const int64_t index, void *r_value) const
{
varray_.get(index + offset_, r_value);
}
void GVArray_For_SlicedGVArray::get_to_uninitialized_impl(const int64_t index, void *r_value) const
{
varray_.get_to_uninitialized(index + offset_, r_value);
}
/* --------------------------------------------------------------------
* GVArray_Slice.
*/
GVArray_Slice::GVArray_Slice(const GVArray &varray, const IndexRange slice)
{
const CPPType &type = varray.type();
if (varray.is_span()) {
const GSpan span = varray.get_internal_span();
varray_span_.emplace(span.slice(slice.start(), slice.size()));
varray_ = &*varray_span_;
}
else if (varray.is_single()) {
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
varray_->get_internal_single_to_uninitialized(buffer);
varray_single_.emplace(type, slice.size(), buffer);
type.destruct(buffer);
varray_ = &*varray_single_;
}
else {
varray_any_.emplace(varray, slice);
varray_ = &*varray_any_;
}
}
} // namespace blender::fn } // namespace blender::fn

View File

@@ -0,0 +1,109 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "FN_multi_function_parallel.hh"
#include "BLI_task.hh"
#include <mutex>
namespace blender::fn {
ParallelMultiFunction::ParallelMultiFunction(const MultiFunction &fn, const int64_t grain_size)
: fn_(fn), grain_size_(grain_size)
{
this->set_signature(&fn.signature());
threading_supported_ = true;
for (const int param_index : fn.param_indices()) {
const MFParamType param_type = fn.param_type(param_index);
if (param_type.data_type().category() == MFDataType::Vector) {
threading_supported_ = false;
break;
}
}
}
void ParallelMultiFunction::call(IndexMask mask, MFParams params, MFContext context) const
{
if (mask.size() <= grain_size_ || !threading_supported_) {
fn_.call(mask, params, context);
return;
}
threading::parallel_for(mask.index_range(), grain_size_, [&](const IndexRange range) {
const int size = range.size();
IndexMask original_sub_mask{mask.indices().slice(range)};
const int64_t offset = original_sub_mask.indices().first();
const int64_t slice_size = original_sub_mask.indices().last() - offset + 1;
const IndexRange slice_range{offset, slice_size};
IndexMask sub_mask;
Vector<int64_t> sub_mask_indices;
if (original_sub_mask.is_range()) {
sub_mask = IndexMask(size);
}
else {
sub_mask_indices.resize(size);
for (const int i : IndexRange(size)) {
sub_mask_indices[i] = original_sub_mask[i] - offset;
}
sub_mask = sub_mask_indices.as_span();
}
MFParamsBuilder sub_params{fn_, sub_mask.min_array_size()};
ResourceScope scope;
// static std::mutex mutex;
// {
// std::lock_guard lock{mutex};
// std::cout << range << " " << sub_mask.min_array_size() << "\n";
// }
for (const int param_index : fn_.param_indices()) {
const MFParamType param_type = fn_.param_type(param_index);
switch (param_type.category()) {
case MFParamType::SingleInput: {
const GVArray &varray = params.readonly_single_input(param_index);
const GVArray &sliced_varray = scope.construct<GVArray_Slice>(
"sliced varray", varray, slice_range);
sub_params.add_readonly_single_input(sliced_varray);
break;
}
case MFParamType::SingleMutable: {
const GMutableSpan span = params.single_mutable(param_index);
const GMutableSpan sliced_span = span.slice(slice_range.start(), slice_range.size());
sub_params.add_single_mutable(sliced_span);
break;
}
case MFParamType::SingleOutput: {
const GMutableSpan span = params.uninitialized_single_output(param_index);
const GMutableSpan sliced_span = span.slice(slice_range.start(), slice_range.size());
sub_params.add_uninitialized_single_output(sliced_span);
break;
}
case MFParamType::VectorInput:
case MFParamType::VectorMutable:
case MFParamType::VectorOutput: {
BLI_assert_unreachable();
break;
}
}
}
fn_.call(sub_mask, sub_params, context);
});
}
} // namespace blender::fn

View File

@@ -21,6 +21,65 @@
namespace blender::fn { namespace blender::fn {
void MFInstructionCursor::set_next(MFProcedure &procedure, MFInstruction *new_instruction) const
{
switch (type_) {
case Type::None: {
break;
}
case Type::Entry: {
procedure.set_entry(*new_instruction);
break;
}
case Type::Call: {
static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction);
break;
}
case Type::Branch: {
MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(instruction_);
if (branch_output_) {
branch_instruction.set_branch_true(new_instruction);
}
else {
branch_instruction.set_branch_false(new_instruction);
}
break;
}
case Type::Destruct: {
static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction);
break;
}
case Type::Dummy: {
static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction);
break;
}
}
}
MFInstruction *MFInstructionCursor::next(MFProcedure &procedure) const
{
switch (type_) {
case Type::None:
return nullptr;
case Type::Entry:
return procedure.entry();
case Type::Call:
return static_cast<MFCallInstruction *>(instruction_)->next();
case Type::Branch: {
MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(instruction_);
if (branch_output_) {
return branch_instruction.branch_true();
}
return branch_instruction.branch_false();
}
case Type::Destruct:
return static_cast<MFDestructInstruction *>(instruction_)->next();
case Type::Dummy:
return static_cast<MFDummyInstruction *>(instruction_)->next();
}
return nullptr;
}
void MFVariable::set_name(std::string name) void MFVariable::set_name(std::string name)
{ {
name_ = std::move(name); name_ = std::move(name);
@@ -29,10 +88,10 @@ void MFVariable::set_name(std::string name)
void MFCallInstruction::set_next(MFInstruction *instruction) void MFCallInstruction::set_next(MFInstruction *instruction)
{ {
if (next_ != nullptr) { if (next_ != nullptr) {
next_->prev_.remove_first_occurrence_and_reorder(this); next_->prev_.remove_first_occurrence_and_reorder(*this);
} }
if (instruction != nullptr) { if (instruction != nullptr) {
instruction->prev_.append(this); instruction->prev_.append(*this);
} }
next_ = instruction; next_ = instruction;
} }
@@ -71,10 +130,10 @@ void MFBranchInstruction::set_condition(MFVariable *variable)
void MFBranchInstruction::set_branch_true(MFInstruction *instruction) void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
{ {
if (branch_true_ != nullptr) { if (branch_true_ != nullptr) {
branch_true_->prev_.remove_first_occurrence_and_reorder(this); branch_true_->prev_.remove_first_occurrence_and_reorder({*this, true});
} }
if (instruction != nullptr) { if (instruction != nullptr) {
instruction->prev_.append(this); instruction->prev_.append({*this, true});
} }
branch_true_ = instruction; branch_true_ = instruction;
} }
@@ -82,10 +141,10 @@ void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
void MFBranchInstruction::set_branch_false(MFInstruction *instruction) void MFBranchInstruction::set_branch_false(MFInstruction *instruction)
{ {
if (branch_false_ != nullptr) { if (branch_false_ != nullptr) {
branch_false_->prev_.remove_first_occurrence_and_reorder(this); branch_false_->prev_.remove_first_occurrence_and_reorder({*this, false});
} }
if (instruction != nullptr) { if (instruction != nullptr) {
instruction->prev_.append(this); instruction->prev_.append({*this, false});
} }
branch_false_ = instruction; branch_false_ = instruction;
} }
@@ -104,10 +163,10 @@ void MFDestructInstruction::set_variable(MFVariable *variable)
void MFDestructInstruction::set_next(MFInstruction *instruction) void MFDestructInstruction::set_next(MFInstruction *instruction)
{ {
if (next_ != nullptr) { if (next_ != nullptr) {
next_->prev_.remove_first_occurrence_and_reorder(this); next_->prev_.remove_first_occurrence_and_reorder(*this);
} }
if (instruction != nullptr) { if (instruction != nullptr) {
instruction->prev_.append(this); instruction->prev_.append(*this);
} }
next_ = instruction; next_ = instruction;
} }
@@ -115,10 +174,10 @@ void MFDestructInstruction::set_next(MFInstruction *instruction)
void MFDummyInstruction::set_next(MFInstruction *instruction) void MFDummyInstruction::set_next(MFInstruction *instruction)
{ {
if (next_ != nullptr) { if (next_ != nullptr) {
next_->prev_.remove_first_occurrence_and_reorder(this); next_->prev_.remove_first_occurrence_and_reorder(*this);
} }
if (instruction != nullptr) { if (instruction != nullptr) {
instruction->prev_.append(this); instruction->prev_.append(*this);
} }
next_ = instruction; next_ = instruction;
} }
@@ -420,7 +479,11 @@ MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction
Set<const MFInstruction *> checked_instructions; Set<const MFInstruction *> checked_instructions;
Stack<const MFInstruction *> instructions_to_check; Stack<const MFInstruction *> instructions_to_check;
instructions_to_check.push_multiple(target_instruction.prev_); for (const MFInstructionCursor &cursor : target_instruction.prev_) {
if (cursor.instruction() != nullptr) {
instructions_to_check.push(cursor.instruction());
}
}
while (!instructions_to_check.is_empty()) { while (!instructions_to_check.is_empty()) {
const MFInstruction &instruction = *instructions_to_check.pop(); const MFInstruction &instruction = *instructions_to_check.pop();
@@ -467,7 +530,11 @@ MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction
if (&instruction == entry_) { if (&instruction == entry_) {
check_entry_instruction(); check_entry_instruction();
} }
instructions_to_check.push_multiple(instruction.prev_); for (const MFInstructionCursor &cursor : instruction.prev_) {
if (cursor.instruction() != nullptr) {
instructions_to_check.push(cursor.instruction());
}
}
} }
} }
@@ -607,13 +674,10 @@ class MFProcedureDotExport {
bool has_to_be_block_begin(const MFInstruction &instruction) bool has_to_be_block_begin(const MFInstruction &instruction)
{ {
if (procedure_.entry() == &instruction) {
return true;
}
if (instruction.prev().size() != 1) { if (instruction.prev().size() != 1) {
return true; return true;
} }
if (instruction.prev()[0]->type() == MFInstructionType::Branch) { if (instruction.prev()[0].type() == MFInstructionCursor::Type::Branch) {
return true; return true;
} }
return false; return false;
@@ -623,7 +687,7 @@ class MFProcedureDotExport {
{ {
const MFInstruction *current = &representative; const MFInstruction *current = &representative;
while (!this->has_to_be_block_begin(*current)) { while (!this->has_to_be_block_begin(*current)) {
current = current->prev()[0]; current = current->prev()[0].instruction();
if (current == &representative) { if (current == &representative) {
/* There is a loop without entry or exit, just break it up here. */ /* There is a loop without entry or exit, just break it up here. */
break; break;

View File

@@ -18,50 +18,6 @@
namespace blender::fn { namespace blender::fn {
void MFInstructionCursor::insert(MFProcedure &procedure, MFInstruction *new_instruction)
{
if (instruction_ == nullptr) {
if (is_entry_) {
procedure.set_entry(*new_instruction);
}
else {
/* The cursors points at nothing, nothing to do. */
}
}
else {
switch (instruction_->type()) {
case MFInstructionType::Call: {
static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction);
break;
}
case MFInstructionType::Branch: {
MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(
instruction_);
if (branch_output_) {
branch_instruction.set_branch_true(new_instruction);
}
else {
branch_instruction.set_branch_false(new_instruction);
}
break;
}
case MFInstructionType::Destruct: {
static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction);
break;
}
case MFInstructionType::Dummy: {
static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction);
break;
}
case MFInstructionType::Return: {
/* It shouldn't be possible to build a cursor that points to a return instruction. */
BLI_assert_unreachable();
break;
}
}
}
}
void MFProcedureBuilder::add_destruct(MFVariable &variable) void MFProcedureBuilder::add_destruct(MFVariable &variable)
{ {
MFDestructInstruction &instruction = procedure_->new_destruct_instruction(); MFDestructInstruction &instruction = procedure_->new_destruct_instruction();

View File

@@ -0,0 +1,66 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "FN_multi_function_procedure_optimization.hh"
namespace blender::fn::procedure_optimization {
static bool uses_variable(const MFInstruction &instr, const MFVariable &variable)
{
switch (instr.type()) {
case MFInstructionType::Branch:
return static_cast<const MFBranchInstruction &>(instr).condition() == &variable;
case MFInstructionType::Call:
return static_cast<const MFCallInstruction &>(instr).params().contains(&variable);
case MFInstructionType::Destruct:
return static_cast<const MFDestructInstruction &>(instr).variable() == &variable;
default:
return false;
}
}
void move_destructs_up(MFProcedure &procedure)
{
for (MFDestructInstruction *destruct_instr : procedure.destruct_instructions()) {
MFVariable *variable = destruct_instr->variable();
if (variable == nullptr) {
continue;
}
MFInstruction *last_use_in_block_instr = nullptr;
MFInstruction *current_instr = destruct_instr;
while (current_instr->prev().size() == 1) {
current_instr = current_instr->prev()[0].instruction();
if (current_instr == nullptr) {
break;
}
if (uses_variable(*current_instr, *variable)) {
last_use_in_block_instr = current_instr;
break;
}
}
if (last_use_in_block_instr == nullptr) {
continue;
}
if (last_use_in_block_instr->type() == MFInstructionType::Call) {
MFCallInstruction &call_instr = static_cast<MFCallInstruction &>(*last_use_in_block_instr);
destruct_instr->prev()[0].set_next(procedure, destruct_instr->next());
destruct_instr->set_next(call_instr.next());
call_instr.set_next(destruct_instr);
}
}
}
} // namespace blender::fn::procedure_optimization

View File

@@ -2,8 +2,11 @@
#include "testing/testing.h" #include "testing/testing.h"
#include "BLI_timeit.hh"
#include "FN_multi_function.hh" #include "FN_multi_function.hh"
#include "FN_multi_function_builder.hh" #include "FN_multi_function_builder.hh"
#include "FN_multi_function_parallel.hh"
#include "FN_multi_function_test_common.hh" #include "FN_multi_function_test_common.hh"
namespace blender::fn::tests { namespace blender::fn::tests {
@@ -328,5 +331,29 @@ TEST(multi_function, CustomMF_Convert)
EXPECT_EQ(outputs[2], 9); EXPECT_EQ(outputs[2], 9);
} }
TEST(multi_function, Parallel)
{
CustomMF_SI_SI_SO<float, float, float> add_fn{
"add", [](float a, float b) { return std::tan(std::sin(a)) * std::tanh(std::cos(b)); }};
ParallelMultiFunction parallel_fn{add_fn, int64_t(1e5)};
const MultiFunction &fn_to_evaluate = parallel_fn;
const int amount = 1e8;
Array<float> inputs_a(amount, 1);
Array<float> inputs_b(amount, 1);
Array<float> outputs(amount, 1);
for (int i = 0; i < 10; i++) {
SCOPED_TIMER(__func__);
MFParamsBuilder params(fn_to_evaluate, amount);
params.add_readonly_single_input(inputs_a.as_span());
params.add_readonly_single_input(inputs_b.as_span());
params.add_uninitialized_single_output(outputs.as_mutable_span());
MFContextBuilder context;
fn_to_evaluate.call(IndexRange(amount), params, context);
}
}
} // namespace } // namespace
} // namespace blender::fn::tests } // namespace blender::fn::tests

View File

@@ -118,7 +118,7 @@ void ABCAbstractWriter::update_bounding_box(Object *object)
if (!bb) { if (!bb) {
if (object->type != OB_CAMERA) { if (object->type != OB_CAMERA) {
CLOG_WARN(&LOG, "Bounding box is null!\n"); CLOG_WARN(&LOG, "Bounding box is null!");
} }
bounding_box_.min.x = bounding_box_.min.y = bounding_box_.min.z = 0; bounding_box_.min.x = bounding_box_.min.y = bounding_box_.min.z = 0;
bounding_box_.max.x = bounding_box_.max.y = bounding_box_.max.z = 0; bounding_box_.max.x = bounding_box_.max.y = bounding_box_.max.z = 0;

View File

@@ -1942,6 +1942,7 @@ typedef struct MeshCacheModifierData {
float factor; float factor;
char deform_mode; char deform_mode;
char defgrp_name[64];
char _pad[7]; char _pad[7];
/* play_mode == MOD_MESHCACHE_PLAY_CFEA */ /* play_mode == MOD_MESHCACHE_PLAY_CFEA */
@@ -1958,6 +1959,11 @@ typedef struct MeshCacheModifierData {
char filepath[1024]; char filepath[1024];
} MeshCacheModifierData; } MeshCacheModifierData;
/* MeshCache modifier flags. */
enum {
MOD_MESHCACHE_INVERT_VERTEX_GROUP = 1 << 0,
};
enum { enum {
MOD_MESHCACHE_TYPE_MDD = 1, MOD_MESHCACHE_TYPE_MDD = 1,
MOD_MESHCACHE_TYPE_PC2 = 2, MOD_MESHCACHE_TYPE_PC2 = 2,

View File

@@ -788,7 +788,7 @@ bool RNA_struct_override_matches(Main *bmain,
continue; continue;
} }
CLOG_INFO(&LOG, 5, "Override Checking %s\n", rna_path); CLOG_INFO(&LOG, 5, "Override Checking %s", rna_path);
IDOverrideLibraryProperty *op = BKE_lib_override_library_property_find(override, rna_path); IDOverrideLibraryProperty *op = BKE_lib_override_library_property_find(override, rna_path);
if (ignore_overridden && op != NULL) { if (ignore_overridden && op != NULL) {

View File

@@ -755,6 +755,7 @@ RNA_MOD_VGROUP_NAME_SET(LaplacianDeform, anchor_grp_name);
RNA_MOD_VGROUP_NAME_SET(LaplacianSmooth, defgrp_name); RNA_MOD_VGROUP_NAME_SET(LaplacianSmooth, defgrp_name);
RNA_MOD_VGROUP_NAME_SET(Lattice, name); RNA_MOD_VGROUP_NAME_SET(Lattice, name);
RNA_MOD_VGROUP_NAME_SET(Mask, vgroup); RNA_MOD_VGROUP_NAME_SET(Mask, vgroup);
RNA_MOD_VGROUP_NAME_SET(MeshCache, defgrp_name);
RNA_MOD_VGROUP_NAME_SET(MeshDeform, defgrp_name); RNA_MOD_VGROUP_NAME_SET(MeshDeform, defgrp_name);
RNA_MOD_VGROUP_NAME_SET(NormalEdit, defgrp_name); RNA_MOD_VGROUP_NAME_SET(NormalEdit, defgrp_name);
RNA_MOD_VGROUP_NAME_SET(Shrinkwrap, vgroup_name); RNA_MOD_VGROUP_NAME_SET(Shrinkwrap, vgroup_name);
@@ -6045,6 +6046,20 @@ static void rna_def_modifier_meshcache(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Influence", "Influence of the deformation"); RNA_def_property_ui_text(prop, "Influence", "Influence of the deformation");
RNA_def_property_update(prop, 0, "rna_Modifier_update"); RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "defgrp_name");
RNA_def_property_ui_text(
prop,
"Vertex Group",
"Name of the Vertex Group which determines the influence of the modifier per point");
RNA_def_property_string_funcs(prop, NULL, NULL, "rna_MeshCacheModifier_defgrp_name_set");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "invert_vertex_group", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", MOD_MESHCACHE_INVERT_VERTEX_GROUP);
RNA_def_property_ui_text(prop, "Invert", "Invert vertex group influence");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/* Axis Conversion */ /* Axis Conversion */
prop = RNA_def_property(srna, "forward_axis", PROP_ENUM, PROP_NONE); prop = RNA_def_property(srna, "forward_axis", PROP_ENUM, PROP_NONE);

View File

@@ -224,6 +224,12 @@ static EnumPropertyItem instance_items_empty[] = {
INSTANCE_ITEM_COLLECTION, INSTANCE_ITEM_COLLECTION,
{0, NULL, 0, NULL, NULL}, {0, NULL, 0, NULL, NULL},
}; };
static EnumPropertyItem instance_items_font[] = {
{0, "NONE", 0, "None", ""},
{OB_DUPLIVERTS, "VERTS", 0, "Vertices", "Use Object Font on characters"},
{0, NULL, 0, NULL, NULL},
};
#endif #endif
#undef INSTANCE_ITEMS_SHARED #undef INSTANCE_ITEMS_SHARED
#undef INSTANCE_ITEM_COLLECTION #undef INSTANCE_ITEM_COLLECTION
@@ -762,6 +768,9 @@ static const EnumPropertyItem *rna_Object_instance_type_itemf(bContext *UNUSED(C
else if (ob->type == OB_POINTCLOUD) { else if (ob->type == OB_POINTCLOUD) {
item = instance_items_pointcloud; item = instance_items_pointcloud;
} }
else if (ob->type == OB_FONT) {
item = instance_items_font;
}
else { else {
item = instance_items_nogroup; item = instance_items_nogroup;
} }

View File

@@ -845,6 +845,17 @@ static void rna_Sequence_audio_update(Main *UNUSED(bmain), Scene *scene, Pointer
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
} }
static void rna_Sequence_pan_range(
PointerRNA *ptr, float *min, float *max, float *softmin, float *softmax)
{
Scene *scene = (Scene *)ptr->owner_id;
*min = -FLT_MAX;
*max = FLT_MAX;
*softmax = 1 + (int)(scene->r.ffcodecdata.audio_channels > 2);
*softmin = -*softmax;
}
static int rna_Sequence_input_count_get(PointerRNA *ptr) static int rna_Sequence_input_count_get(PointerRNA *ptr)
{ {
Sequence *seq = (Sequence *)(ptr->data); Sequence *seq = (Sequence *)(ptr->data);
@@ -2559,8 +2570,10 @@ static void rna_def_sound(BlenderRNA *brna)
prop = RNA_def_property(srna, "pan", PROP_FLOAT, PROP_NONE); prop = RNA_def_property(srna, "pan", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "pan"); RNA_def_property_float_sdna(prop, NULL, "pan");
RNA_def_property_range(prop, -2.0f, 2.0f); RNA_def_property_range(prop, -FLT_MAX, FLT_MAX);
RNA_def_property_ui_range(prop, -2, 2, 1, 2);
RNA_def_property_ui_text(prop, "Pan", "Playback panning of the sound (only for Mono sources)"); RNA_def_property_ui_text(prop, "Pan", "Playback panning of the sound (only for Mono sources)");
RNA_def_property_float_funcs(prop, NULL, NULL, "rna_Sequence_pan_range");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_audio_update"); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_audio_update");
prop = RNA_def_property(srna, "show_waveform", PROP_BOOLEAN, PROP_NONE); prop = RNA_def_property(srna, "show_waveform", PROP_BOOLEAN, PROP_NONE);

View File

@@ -36,8 +36,11 @@
#include "DNA_screen_types.h" #include "DNA_screen_types.h"
#include "BKE_context.h" #include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_lib_id.h"
#include "BKE_main.h" #include "BKE_main.h"
#include "BKE_mesh.h" #include "BKE_mesh.h"
#include "BKE_mesh_wrapper.h"
#include "BKE_scene.h" #include "BKE_scene.h"
#include "BKE_screen.h" #include "BKE_screen.h"
@@ -53,6 +56,7 @@
#include "MOD_meshcache_util.h" /* utility functions */ #include "MOD_meshcache_util.h" /* utility functions */
#include "MOD_modifiertypes.h" #include "MOD_modifiertypes.h"
#include "MOD_ui_common.h" #include "MOD_ui_common.h"
#include "MOD_util.h"
static void initData(ModifierData *md) static void initData(ModifierData *md)
{ {
@@ -84,11 +88,16 @@ static bool isDisabled(const struct Scene *UNUSED(scene),
static void meshcache_do(MeshCacheModifierData *mcmd, static void meshcache_do(MeshCacheModifierData *mcmd,
Scene *scene, Scene *scene,
Object *ob, Object *ob,
Mesh *mesh,
float (*vertexCos_Real)[3], float (*vertexCos_Real)[3],
int numVerts) int numVerts)
{ {
const bool use_factor = mcmd->factor < 1.0f; const bool use_factor = mcmd->factor < 1.0f;
float(*vertexCos_Store)[3] = (use_factor || int influence_group_index;
MDeformVert *dvert;
MOD_get_vgroup(ob, mesh, mcmd->defgrp_name, &dvert, &influence_group_index);
float(*vertexCos_Store)[3] = (use_factor || influence_group_index != -1 ||
(mcmd->deform_mode == MOD_MESHCACHE_DEFORM_INTEGRATE)) ? (mcmd->deform_mode == MOD_MESHCACHE_DEFORM_INTEGRATE)) ?
MEM_malloc_arrayN( MEM_malloc_arrayN(
numVerts, sizeof(*vertexCos_Store), __func__) : numVerts, sizeof(*vertexCos_Store), __func__) :
@@ -256,7 +265,29 @@ static void meshcache_do(MeshCacheModifierData *mcmd,
if (vertexCos_Store) { if (vertexCos_Store) {
if (ok) { if (ok) {
if (use_factor) { if (influence_group_index != -1) {
const float global_factor = (mcmd->flag & MOD_MESHCACHE_INVERT_VERTEX_GROUP) ?
-mcmd->factor :
mcmd->factor;
const float global_offset = (mcmd->flag & MOD_MESHCACHE_INVERT_VERTEX_GROUP) ?
mcmd->factor :
0.0f;
if (mesh->dvert != NULL) {
for (int i = 0; i < numVerts; i++) {
/* For each vertex, compute its blending factor between the mesh cache (for `fac = 0`)
* and the former position of the vertex (for `fac = 1`). */
const MDeformVert *currentIndexDVert = dvert + i;
const float local_vertex_fac = global_offset +
BKE_defvert_find_weight(currentIndexDVert,
influence_group_index) *
global_factor;
interp_v3_v3v3(
vertexCos_Real[i], vertexCos_Real[i], vertexCos_Store[i], local_vertex_fac);
}
}
}
else if (use_factor) {
/* Influence_group_index is -1. */
interp_vn_vn(*vertexCos_Real, *vertexCos_Store, mcmd->factor, numVerts * 3); interp_vn_vn(*vertexCos_Real, *vertexCos_Store, mcmd->factor, numVerts * 3);
} }
else { else {
@@ -270,34 +301,59 @@ static void meshcache_do(MeshCacheModifierData *mcmd,
static void deformVerts(ModifierData *md, static void deformVerts(ModifierData *md,
const ModifierEvalContext *ctx, const ModifierEvalContext *ctx,
Mesh *UNUSED(mesh), Mesh *mesh,
float (*vertexCos)[3], float (*vertexCos)[3],
int numVerts) int numVerts)
{ {
MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md; MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph); Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph);
meshcache_do(mcmd, scene, ctx->object, vertexCos, numVerts); Mesh *mesh_src = NULL;
if (ctx->object->type == OB_MESH && mcmd->defgrp_name[0] != '\0') {
/* `mesh_src` is only needed for vertex groups. */
mesh_src = MOD_deform_mesh_eval_get(ctx->object, NULL, mesh, NULL, numVerts, false, false);
}
meshcache_do(mcmd, scene, ctx->object, mesh_src, vertexCos, numVerts);
if (!ELEM(mesh_src, NULL, mesh)) {
BKE_id_free(NULL, mesh_src);
}
} }
static void deformVertsEM(ModifierData *md, static void deformVertsEM(ModifierData *md,
const ModifierEvalContext *ctx, const ModifierEvalContext *ctx,
struct BMEditMesh *UNUSED(editData), struct BMEditMesh *editData,
Mesh *UNUSED(mesh), Mesh *mesh,
float (*vertexCos)[3], float (*vertexCos)[3],
int numVerts) int numVerts)
{ {
MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md; MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph); Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph);
meshcache_do(mcmd, scene, ctx->object, vertexCos, numVerts); Mesh *mesh_src = NULL;
if (ctx->object->type == OB_MESH && mcmd->defgrp_name[0] != '\0') {
/* `mesh_src` is only needed for vertex groups. */
mesh_src = MOD_deform_mesh_eval_get(ctx->object, editData, mesh, NULL, numVerts, false, false);
}
if (mesh_src != NULL) {
BKE_mesh_wrapper_ensure_mdata(mesh_src);
}
meshcache_do(mcmd, scene, ctx->object, mesh_src, vertexCos, numVerts);
if (!ELEM(mesh_src, NULL, mesh)) {
BKE_id_free(NULL, mesh_src);
}
} }
static void panel_draw(const bContext *UNUSED(C), Panel *panel) static void panel_draw(const bContext *UNUSED(C), Panel *panel)
{ {
uiLayout *layout = panel->layout; uiLayout *layout = panel->layout;
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL); PointerRNA ob_ptr;
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
uiLayoutSetPropSep(layout, true); uiLayoutSetPropSep(layout, true);
@@ -307,6 +363,7 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel)
uiItemR(layout, ptr, "factor", UI_ITEM_R_SLIDER, NULL, ICON_NONE); uiItemR(layout, ptr, "factor", UI_ITEM_R_SLIDER, NULL, ICON_NONE);
uiItemR(layout, ptr, "deform_mode", 0, NULL, ICON_NONE); uiItemR(layout, ptr, "deform_mode", 0, NULL, ICON_NONE);
uiItemR(layout, ptr, "interpolation", 0, NULL, ICON_NONE); uiItemR(layout, ptr, "interpolation", 0, NULL, ICON_NONE);
modifier_vgroup_ui(layout, ptr, &ob_ptr, "vertex_group", "invert_vertex_group", NULL);
modifier_panel_end(layout, ptr); modifier_panel_end(layout, ptr);
} }

View File

@@ -330,8 +330,8 @@ static IDProperty *id_property_create_from_socket(const bNodeSocket &socket)
ui_data->max = ui_data->soft_max = (double)value->max; ui_data->max = ui_data->soft_max = (double)value->max;
ui_data->default_array = (double *)MEM_mallocN(sizeof(double[3]), "mod_prop_default"); ui_data->default_array = (double *)MEM_mallocN(sizeof(double[3]), "mod_prop_default");
ui_data->default_array_len = 3; ui_data->default_array_len = 3;
for (int i = 3; i < 3; i++) { for (const int i : IndexRange(3)) {
ui_data->default_array[i] = (double)value->value[i]; ui_data->default_array[i] = double(value->value[i]);
} }
return property; return property;
} }

View File

@@ -468,7 +468,7 @@ static void idprop_ui_data_to_dict_int(IDProperty *property, PyObject *dict)
Py_DECREF(list); Py_DECREF(list);
} }
else { else {
PyDict_SetItemString(dict, "default", item = PyLong_FromLong(ui_data->step)); PyDict_SetItemString(dict, "default", item = PyLong_FromLong(ui_data->default_value));
Py_DECREF(item); Py_DECREF(item);
} }
} }
@@ -499,7 +499,7 @@ static void idprop_ui_data_to_dict_float(IDProperty *property, PyObject *dict)
Py_DECREF(list); Py_DECREF(list);
} }
else { else {
PyDict_SetItemString(dict, "default", item = PyFloat_FromDouble(ui_data->step)); PyDict_SetItemString(dict, "default", item = PyFloat_FromDouble(ui_data->default_value));
Py_DECREF(item); Py_DECREF(item);
} }
} }

View File

@@ -485,7 +485,7 @@ static int pygpu_buffer__sq_ass_item(BPyGPUBuffer *self, int i, PyObject *v)
case GPU_DATA_UINT: case GPU_DATA_UINT:
case GPU_DATA_UINT_24_8: case GPU_DATA_UINT_24_8:
case GPU_DATA_10_11_11_REV: case GPU_DATA_10_11_11_REV:
return PyArg_Parse(v, "b:Expected ints", &self->buf.as_uint[i]) ? 0 : -1; return PyArg_Parse(v, "I:Expected unsigned ints", &self->buf.as_uint[i]) ? 0 : -1;
default: default:
return 0; /* should never happen */ return 0; /* should never happen */
} }

View File

@@ -753,7 +753,7 @@ int BPY_context_member_get(bContext *C, const char *member, bContextDataResult *
CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not a valid type", member); CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not a valid type", member);
} }
else { else {
CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not found\n", member); CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not found", member);
} }
} }
else { else {

View File

@@ -3749,7 +3749,7 @@ wmKeyMap *WM_event_get_keymap_from_toolsystem_fallback(wmWindowManager *wm,
const char *keymap_id = NULL; const char *keymap_id = NULL;
/* Support for the gizmo owning the tool keymap. */ /* Support for the gizmo owning the tool keymap. */
if (tref_rt->gizmo_group[0] != '\0' && tref_rt->keymap_fallback[0] != '\n') { if (tref_rt->gizmo_group[0] != '\0' && tref_rt->keymap_fallback[0] != '\0') {
wmGizmoMap *gzmap = NULL; wmGizmoMap *gzmap = NULL;
wmGizmoGroup *gzgroup = NULL; wmGizmoGroup *gzgroup = NULL;
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {

View File

@@ -705,7 +705,7 @@ bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data,
} }
if (failure) { if (failure) {
CLOG_ERROR(&LOG, "Failed to get buffer, %s\n", err_out); CLOG_ERROR(&LOG, "Failed to get buffer, %s", err_out);
return false; return false;
} }

View File

@@ -25,6 +25,7 @@ class TestEntry:
category: str = '' category: str = ''
revision: str = '' revision: str = ''
git_hash: str = '' git_hash: str = ''
environment: Dict = field(default_factory=dict)
executable: str = '' executable: str = ''
date: int = 0 date: int = 0
device_type: str = 'CPU' device_type: str = 'CPU'
@@ -160,7 +161,13 @@ class TestConfig:
def read_blender_executables(env, name) -> List: def read_blender_executables(env, name) -> List:
config = TestConfig._read_config_module(env.base_dir / name) config = TestConfig._read_config_module(env.base_dir / name)
builds = getattr(config, 'builds', {}) builds = getattr(config, 'builds', {})
return [pathlib.Path(build) for build in builds.values()] executables = []
for executable in builds.values():
executable, _ = TestConfig._split_environment_variables(executable)
executables.append(pathlib.Path(executable))
return executables
@staticmethod @staticmethod
def _read_config_module(base_dir: pathlib.Path) -> None: def _read_config_module(base_dir: pathlib.Path) -> None:
@@ -191,9 +198,10 @@ class TestConfig:
# Get entries for specified commits, tags and branches. # Get entries for specified commits, tags and branches.
for revision_name, revision_commit in self.revisions.items(): for revision_name, revision_commit in self.revisions.items():
revision_commit, environment = self._split_environment_variables(revision_commit)
git_hash = env.resolve_git_hash(revision_commit) git_hash = env.resolve_git_hash(revision_commit)
date = env.git_hash_date(git_hash) date = env.git_hash_date(git_hash)
entries += self._get_entries(revision_name, git_hash, '', date) entries += self._get_entries(revision_name, git_hash, '', environment, date)
# Optimization to avoid rebuilds. # Optimization to avoid rebuilds.
revisions_to_build = set() revisions_to_build = set()
@@ -204,6 +212,7 @@ class TestConfig:
# Get entries for revisions based on existing builds. # Get entries for revisions based on existing builds.
for revision_name, executable in self.builds.items(): for revision_name, executable in self.builds.items():
executable, environment = self._split_environment_variables(executable)
executable_path = env._blender_executable_from_path(pathlib.Path(executable)) executable_path = env._blender_executable_from_path(pathlib.Path(executable))
if not executable_path: if not executable_path:
sys.stderr.write(f'Error: build {executable} not found\n') sys.stderr.write(f'Error: build {executable} not found\n')
@@ -214,7 +223,7 @@ class TestConfig:
env.set_default_blender_executable() env.set_default_blender_executable()
mtime = executable_path.stat().st_mtime mtime = executable_path.stat().st_mtime
entries += self._get_entries(revision_name, git_hash, executable, mtime) entries += self._get_entries(revision_name, git_hash, executable, environment, mtime)
# Detect number of categories for more compact printing. # Detect number of categories for more compact printing.
categories = set() categories = set()
@@ -229,6 +238,7 @@ class TestConfig:
revision_name: str, revision_name: str,
git_hash: str, git_hash: str,
executable: pathlib.Path, executable: pathlib.Path,
environment: str,
date: int) -> None: date: int) -> None:
entries = [] entries = []
for test in self.tests.tests: for test in self.tests.tests:
@@ -241,10 +251,12 @@ class TestConfig:
# Test if revision hash or executable changed. # Test if revision hash or executable changed.
if entry.git_hash != git_hash or \ if entry.git_hash != git_hash or \
entry.executable != executable or \ entry.executable != executable or \
entry.environment != environment or \
entry.benchmark_type != self.benchmark_type or \ entry.benchmark_type != self.benchmark_type or \
entry.date != date: entry.date != date:
# Update existing entry. # Update existing entry.
entry.git_hash = git_hash entry.git_hash = git_hash
entry.environment = environment
entry.executable = executable entry.executable = executable
entry.benchmark_type = self.benchmark_type entry.benchmark_type = self.benchmark_type
entry.date = date entry.date = date
@@ -256,6 +268,7 @@ class TestConfig:
revision=revision_name, revision=revision_name,
git_hash=git_hash, git_hash=git_hash,
executable=executable, executable=executable,
environment=environment,
date=date, date=date,
test=test_name, test=test_name,
category=test_category, category=test_category,
@@ -266,3 +279,10 @@ class TestConfig:
entries.append(entry) entries.append(entry)
return entries return entries
@staticmethod
def _split_environment_variables(revision):
if isinstance(revision, str):
return revision, {}
else:
return revision[0], revision[1]

View File

@@ -104,9 +104,10 @@ class TestEnvironment:
self._init_default_blender_executable() self._init_default_blender_executable()
return True return True
def set_blender_executable(self, executable_path: pathlib.Path) -> None: def set_blender_executable(self, executable_path: pathlib.Path, environment: Dict = {}) -> None:
# Run all Blender commands with this executable. # Run all Blender commands with this executable.
self.blender_executable = executable_path self.blender_executable = executable_path
self.blender_executable_environment = environment
def _blender_executable_name(self) -> pathlib.Path: def _blender_executable_name(self) -> pathlib.Path:
if platform.system() == "Windows": if platform.system() == "Windows":
@@ -150,6 +151,7 @@ class TestEnvironment:
def set_default_blender_executable(self) -> None: def set_default_blender_executable(self) -> None:
self.blender_executable = self.default_blender_executable self.blender_executable = self.default_blender_executable
self.blender_executable_environment = {}
def set_log_file(self, filepath: pathlib.Path, clear=True) -> None: def set_log_file(self, filepath: pathlib.Path, clear=True) -> None:
# Log all commands and output to this file. # Log all commands and output to this file.
@@ -161,7 +163,7 @@ class TestEnvironment:
def unset_log_file(self) -> None: def unset_log_file(self) -> None:
self.log_file = None self.log_file = None
def call(self, args: List[str], cwd: pathlib.Path, silent=False) -> List[str]: def call(self, args: List[str], cwd: pathlib.Path, silent: bool=False, environment: Dict={}) -> List[str]:
# Execute command with arguments in specified directory, # Execute command with arguments in specified directory,
# and return combined stdout and stderr output. # and return combined stdout and stderr output.
@@ -173,7 +175,13 @@ class TestEnvironment:
f = open(self.log_file, 'a') f = open(self.log_file, 'a')
f.write('\n' + ' '.join([str(arg) for arg in args]) + '\n\n') f.write('\n' + ' '.join([str(arg) for arg in args]) + '\n\n')
proc = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) env = os.environ
if len(environment):
env = env.copy()
for key, value in environment.items():
env[key] = value
proc = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
# Read line by line # Read line by line
lines = [] lines = []
@@ -208,7 +216,8 @@ class TestEnvironment:
else: else:
common_args += ['--background'] common_args += ['--background']
return self.call([self.blender_executable] + common_args + args, cwd=self.base_dir) return self.call([self.blender_executable] + common_args + args, cwd=self.base_dir,
environment=self.blender_executable_environment)
def run_in_blender(self, def run_in_blender(self,
function: Callable[[Dict], Dict], function: Callable[[Dict], Dict],

View File

@@ -42,7 +42,7 @@ class TestGraph:
# Generate one graph for every device x category x result key combination. # Generate one graph for every device x category x result key combination.
for category, category_entries in categories.items(): for category, category_entries in categories.items():
entries = sorted(category_entries, key=lambda entry: (entry.revision, entry.test)) entries = sorted(category_entries, key=lambda entry: (entry.revision, entry.test, entry.date))
outputs = set() outputs = set()
for entry in entries: for entry in entries:
@@ -58,8 +58,6 @@ class TestGraph:
self.json = json.dumps(data, indent=2) self.json = json.dumps(data, indent=2)
def chart(self, device_name: str, chart_name: str, entries: List, chart_type: str, output: str) -> Dict: def chart(self, device_name: str, chart_name: str, entries: List, chart_type: str, output: str) -> Dict:
entries = sorted(entries, key=lambda entry: entry.date)
# Gather used tests. # Gather used tests.
tests = {} tests = {}
for entry in entries: for entry in entries:

View File

@@ -83,15 +83,20 @@ def match_entry(entry: api.TestEntry, args: argparse.Namespace):
entry.test.find(args.test) != -1 or \ entry.test.find(args.test) != -1 or \
entry.category.find(args.test) != -1 entry.category.find(args.test) != -1
def run_entry(env: api.TestEnvironment, config: api.TestConfig, row: List, entry: api.TestEntry): def run_entry(env: api.TestEnvironment,
config: api.TestConfig,
row: List,
entry: api.TestEntry,
update_only: bool):
# Check if entry needs to be run. # Check if entry needs to be run.
if entry.status not in ('queued', 'outdated'): if update_only and entry.status not in ('queued', 'outdated'):
print_row(config, row, end='\r') print_row(config, row, end='\r')
return False return False
# Run test entry. # Run test entry.
revision = entry.revision revision = entry.revision
git_hash = entry.git_hash git_hash = entry.git_hash
environment = entry.environment
testname = entry.test testname = entry.test
testcategory = entry.category testcategory = entry.category
device_type = entry.device_type device_type = entry.device_type
@@ -116,13 +121,15 @@ def run_entry(env: api.TestEnvironment, config: api.TestConfig, row: List, entry
print_row(config, row, end='\r') print_row(config, row, end='\r')
executable_ok = True executable_ok = True
if len(entry.executable): if len(entry.executable):
env.set_blender_executable(pathlib.Path(entry.executable)) env.set_blender_executable(pathlib.Path(entry.executable), environment)
else: else:
env.checkout(git_hash) env.checkout(git_hash)
executable_ok = env.build() executable_ok = env.build()
if not executable_ok: if not executable_ok:
entry.status = 'failed' entry.status = 'failed'
entry.error_msg = 'Failed to build' entry.error_msg = 'Failed to build'
else:
env.set_blender_executable(env.blender_executable, environment)
# Run test and update output and status. # Run test and update output and status.
if executable_ok: if executable_ok:
@@ -219,7 +226,7 @@ def cmd_reset(env: api.TestEnvironment, argv: List):
config.queue.write() config.queue.write()
def cmd_run(env: api.TestEnvironment, argv: List): def cmd_run(env: api.TestEnvironment, argv: List, update_only: bool):
# Run tests. # Run tests.
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('config', nargs='?', default=None) parser.add_argument('config', nargs='?', default=None)
@@ -233,7 +240,7 @@ def cmd_run(env: api.TestEnvironment, argv: List):
for row in config.queue.rows(use_revision_columns(config)): for row in config.queue.rows(use_revision_columns(config)):
if match_entry(row[0], args): if match_entry(row[0], args):
for entry in row: for entry in row:
if run_entry(env, config, row, entry): if run_entry(env, config, row, entry, update_only):
updated = True updated = True
# Write queue every time in case running gets interrupted, # Write queue every time in case running gets interrupted,
# so it can be resumed. # so it can be resumed.
@@ -268,8 +275,9 @@ def main():
' \n' ' \n'
' list List available tests, devices and configurations\n' ' list List available tests, devices and configurations\n'
' \n' ' \n'
' run [<config>] [<test>] Execute tests for configuration\n' ' run [<config>] [<test>] Execute all tests in configuration\n'
' reset [<config>] [<test>] Clear tests results from config, for re-running\n' ' update [<config>] [<test>] Execute only queued and outdated tests\n'
' reset [<config>] [<test>] Clear tests results in configuration\n'
' status [<config>] [<test>] List configurations and their tests\n' ' status [<config>] [<test>] List configurations and their tests\n'
' \n' ' \n'
' graph a.json b.json... -o out.html Create graph from results in JSON files\n') ' graph a.json b.json... -o out.html Create graph from results in JSON files\n')
@@ -304,7 +312,9 @@ def main():
if args.command == 'list': if args.command == 'list':
cmd_list(env, argv) cmd_list(env, argv)
elif args.command == 'run': elif args.command == 'run':
cmd_run(env, argv) cmd_run(env, argv, update_only=False)
elif args.command == 'update':
cmd_run(env, argv, update_only=True)
elif args.command == 'reset': elif args.command == 'reset':
cmd_reset(env, argv) cmd_reset(env, argv)
elif args.command == 'status': elif args.command == 'status':