1
1

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
def poll(cls, context):
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):
layout = self.layout

View File

@@ -1659,7 +1659,7 @@ class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_split = False
st = context.space_data
strip = context.active_sequence_strip
@@ -1667,20 +1667,39 @@ class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel):
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:
col = layout.column()
split = col.split(factor=0.4)
split.label(text="")
split.prop(sound, "use_mono")
if st.waveform_display_type == 'DEFAULT_WAVEFORMS':
col.prop(strip, "show_waveform")
col.prop(sound, "use_mono")
split = col.split(factor=0.4)
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):

View File

@@ -148,8 +148,8 @@ static void brush_make_local(Main *bmain, ID *id, const int flags)
Brush *brush = (Brush *)id;
const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0;
const bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
const bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
BLI_assert(force_copy == false || force_copy != force_local);
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) {
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 (!is_lib || force_local) {
BKE_lib_id_clear_library_data(bmain, &brush->id);
BKE_lib_id_expand_local(bmain, &brush->id);
if (force_local) {
BKE_lib_id_clear_library_data(bmain, &brush->id);
BKE_lib_id_expand_local(bmain, &brush->id);
/* enable fake user by default */
id_fake_user_set(&brush->id);
}
else {
Brush *brush_new = (Brush *)BKE_id_copy(bmain, &brush->id); /* Ensures FAKE_USER is set */
/* enable fake user by default */
id_fake_user_set(&brush->id);
}
else if (force_copy) {
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... */
ID_NEW_SET(brush, brush_new);
/* setting newid is mandatory for complex make_lib_local logic... */
ID_NEW_SET(brush, brush_new);
if (!lib_local) {
BKE_libblock_remap(bmain, brush, brush_new, ID_REMAP_SKIP_INDIRECT_USAGE);
}
if (!lib_local) {
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 force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
const bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
BLI_assert(force_copy == false || force_copy != force_local);
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) {
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 (!is_lib || force_local) {
BKE_lib_id_clear_library_data(bmain, id);
BKE_lib_id_expand_local(bmain, id);
}
else {
ID *id_new = BKE_id_copy(bmain, id);
if (force_local) {
BKE_lib_id_clear_library_data(bmain, id);
BKE_lib_id_expand_local(bmain, id);
}
else if (force_copy) {
ID *id_new = BKE_id_copy(bmain, id);
/* Should not fail in expected use cases,
* but a few ID types cannot be copied (LIB, WM, SCR...). */
if (id_new != NULL) {
id_new->us = 0;
/* Should not fail in expected use cases,
* but a few ID types cannot be copied (LIB, WM, SCR...). */
if (id_new != NULL) {
id_new->us = 0;
/* setting newid is mandatory for complex make_lib_local logic... */
ID_NEW_SET(id, id_new);
Key *key = BKE_key_from_id(id), *key_new = BKE_key_from_id(id);
if (key && key_new) {
ID_NEW_SET(key, key_new);
}
bNodeTree *ntree = ntreeFromID(id), *ntree_new = ntreeFromID(id_new);
if (ntree && ntree_new) {
ID_NEW_SET(ntree, ntree_new);
}
if (GS(id->name) == ID_SCE) {
Collection *master_collection = ((Scene *)id)->master_collection,
*master_collection_new = ((Scene *)id_new)->master_collection;
if (master_collection && master_collection_new) {
ID_NEW_SET(master_collection, master_collection_new);
}
/* setting newid is mandatory for complex make_lib_local logic... */
ID_NEW_SET(id, id_new);
Key *key = BKE_key_from_id(id), *key_new = BKE_key_from_id(id);
if (key && key_new) {
ID_NEW_SET(key, key_new);
}
bNodeTree *ntree = ntreeFromID(id), *ntree_new = ntreeFromID(id_new);
if (ntree && ntree_new) {
ID_NEW_SET(ntree, ntree_new);
}
if (GS(id->name) == ID_SCE) {
Collection *master_collection = ((Scene *)id)->master_collection,
*master_collection_new = ((Scene *)id_new)->master_collection;
if (master_collection && master_collection_new) {
ID_NEW_SET(master_collection, master_collection_new);
}
}
if (!lib_local) {
BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE);
}
if (!lib_local) {
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;
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 force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
const bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
BLI_assert(force_copy == false || force_copy != force_local);
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) {
BKE_library_ID_test_usages(bmain, ob, &is_local, &is_lib);
}
if (lib_local || is_local || force_copy || force_local) {
if (!is_lib || force_local) {
BKE_lib_id_clear_library_data(bmain, &ob->id);
BKE_lib_id_expand_local(bmain, &ob->id);
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;
if (lib_local || is_local) {
if (!is_lib) {
force_local = true;
}
else {
force_copy = true;
}
}
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;
/* 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);
if (force_local) {
BKE_lib_id_clear_library_data(bmain, &ob->id);
BKE_lib_id_expand_local(bmain, &ob->id);
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 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;
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;
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;
}
}
/* 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
* unknown code, which inhibits many compiler optimizations. */
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. */
LISTBASE_FOREACH (Object *, ob, &mainvar->objects) {
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 (ob->id.us == 0) {
do_it = true;
@@ -4648,7 +4649,7 @@ static void add_collections_to_scene(Main *mainvar,
LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) {
Object *ob = coll_ob->ob;
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;
break;
}

View File

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

View File

@@ -39,9 +39,13 @@
#include "ED_view3d.h"
#include "CLG_log.h"
/* own includes */
#include "gizmo_library_intern.h"
static CLG_LogRef LOG = {"ed.gizmo.library_utils"};
/* factor for precision tweaking */
#define GIZMO_PRECISION_FAC 0.05f
@@ -182,7 +186,7 @@ bool gizmo_window_project_2d(bContext *C,
bool use_offset,
float r_co[2])
{
float mat[4][4];
float mat[4][4], imat[4][4];
{
float mat_identity[4][4];
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);
}
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 */
if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) {
/* 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]);
bool clip_ray = ((RegionView3D *)region->regiondata)->is_persp;
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);
r_co[0] = co[(axis + 1) % 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 imat[4][4];
invert_m4_m4(imat, mat);
mul_m4_v3(imat, co);
copy_v2_v2(r_co, co);
return true;
@@ -223,7 +231,7 @@ bool gizmo_window_project_2d(bContext *C,
bool gizmo_window_project_3d(
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];
struct WM_GizmoMatrixParams params = {NULL};
@@ -234,20 +242,25 @@ bool gizmo_window_project_3d(
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) {
View3D *v3d = CTX_wm_view3d(C);
ARegion *region = CTX_wm_region(C);
/* NOTE: we might want a custom reference point passed in,
* instead of the gizmo center. */
ED_view3d_win_to_3d(v3d, region, mat[3], mval, r_co);
invert_m4(mat);
mul_m4_v3(mat, r_co);
mul_m4_v3(imat, r_co);
return true;
}
float co[3] = {mval[0], mval[1], 0.0f};
float imat[4][4];
invert_m4_m4(imat, mat);
mul_m4_v3(imat, co);
copy_v2_v2(r_co, co);
return true;

View File

@@ -137,7 +137,6 @@ void ED_view3d_smooth_view_ex(
{
RegionView3D *rv3d = region->regiondata;
struct SmoothView3DStore sms = {{0}};
bool ok = false;
/* initialize sms */
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 */
}
/* skip smooth viewing for external render engine draw */
if (smooth_viewtx && !(v3d->shading.type == OB_RENDER && rv3d->render_engine)) {
bool changed = false; /* zero means no difference */
bool changed = false; /* zero means no difference */
if (sview->camera_old != sview->camera) {
changed = true;
}
else if (sms.dst.dist != rv3d->dist) {
changed = true;
}
else if (sms.dst.lens != v3d->lens) {
changed = true;
}
else if (!equals_v3v3(sms.dst.ofs, rv3d->ofs)) {
changed = true;
}
else if (!equals_v4v4(sms.dst.quat, rv3d->viewquat)) {
changed = true;
}
if (sview->camera_old != sview->camera) {
changed = true;
}
else if (sms.dst.dist != rv3d->dist) {
changed = true;
}
else if (sms.dst.lens != v3d->lens) {
changed = true;
}
else if (!equals_v3v3(sms.dst.ofs, rv3d->ofs)) {
changed = true;
}
else if (!equals_v4v4(sms.dst.quat, rv3d->viewquat)) {
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 */
if (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. */
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 */
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);
}
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 */
static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, bool sync_boxview)
{
wmWindowManager *wm = CTX_wm_manager(C);
RegionView3D *rv3d = region->regiondata;
struct SmoothView3DStore *sms = rv3d->sms;
float step, step_inv;
@@ -333,6 +332,7 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b
/* end timer */
if (step >= 1.0f) {
wmWindow *win = CTX_wm_window(C);
/* if we went to camera, store the original */
if (sms->to_camera) {
@@ -355,9 +355,12 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b
MEM_freeN(rv3d->sms);
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->rflag &= ~RV3D_NAVIGATING;
/* Event handling won't know if a UI item has been moved under the pointer. */
WM_event_add_mousemove(win);
}
else {
/* 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);
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);
}
/* 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)) {

View File

@@ -34,9 +34,11 @@ set(SRC
intern/generic_virtual_vector_array.cc
intern/multi_function.cc
intern/multi_function_builder.cc
intern/multi_function_parallel.cc
intern/multi_function_procedure.cc
intern/multi_function_procedure_builder.cc
intern/multi_function_procedure_executor.cc
intern/multi_function_procedure_optimization.cc
FN_cpp_type.hh
FN_cpp_type_make.hh
@@ -54,9 +56,11 @@ set(SRC
FN_multi_function_data_type.hh
FN_multi_function_param_type.hh
FN_multi_function_params.hh
FN_multi_function_parallel.hh
FN_multi_function_procedure.hh
FN_multi_function_procedure_builder.hh
FN_multi_function_procedure_executor.hh
FN_multi_function_procedure_optimization.hh
FN_multi_function_signature.hh
)
@@ -64,6 +68,22 @@ set(LIB
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}")
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

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,
};
/**
* 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
* 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 {
protected:
MFInstructionType type_;
Vector<MFInstruction *> prev_;
Vector<MFInstructionCursor> prev_;
friend MFProcedure;
friend MFCallInstruction;
@@ -89,8 +138,7 @@ class MFInstruction : NonCopyable, NonMovable {
* Other instructions that come before this instruction. There can be multiple previous
* instructions when branching is used in the procedure.
*/
Span<MFInstruction *> prev();
Span<const MFInstruction *> prev() const;
Span<MFInstructionCursor> prev() const;
};
/**
@@ -246,6 +294,9 @@ class MFProcedure : NonCopyable, NonMovable {
Span<MFVariable *> variables();
Span<const MFVariable *> variables() const;
Span<MFDestructInstruction *> destruct_instructions();
Span<const MFDestructInstruction *> destruct_instructions() const;
std::string to_dot() const;
bool validate() const;
@@ -275,6 +326,50 @@ using MFDestructInstruction = fn::MFDestructInstruction;
using MFProcedure = fn::MFProcedure;
} // 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.
*/
@@ -308,12 +403,7 @@ inline MFInstructionType MFInstruction::type() const
return type_;
}
inline Span<MFInstruction *> MFInstruction::prev()
{
return prev_;
}
inline Span<const MFInstruction *> MFInstruction::prev() const
inline Span<MFInstructionCursor> MFInstruction::prev() const
{
return prev_;
}
@@ -449,4 +539,14 @@ inline Span<const MFVariable *> MFProcedure::variables() const
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

View File

@@ -24,31 +24,6 @@
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.
*/
@@ -64,7 +39,7 @@ class MFProcedureBuilder {
struct Loop;
MFProcedureBuilder(MFProcedure &procedure,
MFInstructionCursor initial_cursor = MFInstructionCursor::Entry());
MFInstructionCursor initial_cursor = MFInstructionCursor::ForEntry());
MFProcedureBuilder(Span<MFProcedureBuilder *> builders);
@@ -121,38 +96,6 @@ struct MFProcedureBuilder::Loop {
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.
*/
@@ -253,7 +196,7 @@ inline void MFProcedureBuilder::add_output_parameter(MFVariable &variable)
inline void MFProcedureBuilder::link_to_cursors(MFInstruction *instruction)
{
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_set.hh"
#include "BLI_stack.hh"
#include "BLI_timeit.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"
namespace blender::fn {
@@ -183,8 +188,8 @@ static void build_multi_function_procedure_for_fields(MFProcedure &procedure,
const Span<GField> operation_inputs = operation.inputs();
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
* input index. */
/* Not all inputs are handled yet. Push the next input field to the stack and increment
* the input index. */
fields_to_check.push({operation_inputs[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,
* because that can be more efficient when they share common sub-fields.
* Evaluate fields in the given context. If possible, multiple fields should be evaluated
* 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
* 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,
Span<GVMutableArray *> dst_varrays)
{
Vector<const GVArray *> r_varrays(fields_to_evaluate.size(), nullptr);
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(
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
* processing. */
/* Finish fields that output an input varray directly. For those we don't have to do any
* further processing. */
for (const int out_index : fields_to_evaluate.index_range()) {
const GFieldRef &field = fields_to_evaluate[out_index];
if (!field.node().is_input()) {
@@ -333,8 +339,14 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
MFProcedure procedure;
build_multi_function_procedure_for_fields(
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};
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;
/* Provide inputs to the procedure executor. */
@@ -376,7 +388,8 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
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. */
@@ -419,8 +432,8 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
procedure_executor.call(IndexRange(1), mf_params, mf_context);
}
/* Copy data to supplied destination arrays if necessary. In some cases the evaluation above has
* written the computed data in the right place already. */
/* Copy data to supplied destination arrays if necessary. In some cases the evaluation above
* has written the computed data in the right place already. */
if (!dst_varrays.is_empty()) {
for (const int out_index : fields_to_evaluate.index_range()) {
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;
}
/* --------------------------------------------------------------------
* 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

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 {
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)
{
name_ = std::move(name);
@@ -29,10 +88,10 @@ void MFVariable::set_name(std::string name)
void MFCallInstruction::set_next(MFInstruction *instruction)
{
if (next_ != nullptr) {
next_->prev_.remove_first_occurrence_and_reorder(this);
next_->prev_.remove_first_occurrence_and_reorder(*this);
}
if (instruction != nullptr) {
instruction->prev_.append(this);
instruction->prev_.append(*this);
}
next_ = instruction;
}
@@ -71,10 +130,10 @@ void MFBranchInstruction::set_condition(MFVariable *variable)
void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
{
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) {
instruction->prev_.append(this);
instruction->prev_.append({*this, true});
}
branch_true_ = instruction;
}
@@ -82,10 +141,10 @@ void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
void MFBranchInstruction::set_branch_false(MFInstruction *instruction)
{
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) {
instruction->prev_.append(this);
instruction->prev_.append({*this, false});
}
branch_false_ = instruction;
}
@@ -104,10 +163,10 @@ void MFDestructInstruction::set_variable(MFVariable *variable)
void MFDestructInstruction::set_next(MFInstruction *instruction)
{
if (next_ != nullptr) {
next_->prev_.remove_first_occurrence_and_reorder(this);
next_->prev_.remove_first_occurrence_and_reorder(*this);
}
if (instruction != nullptr) {
instruction->prev_.append(this);
instruction->prev_.append(*this);
}
next_ = instruction;
}
@@ -115,10 +174,10 @@ void MFDestructInstruction::set_next(MFInstruction *instruction)
void MFDummyInstruction::set_next(MFInstruction *instruction)
{
if (next_ != nullptr) {
next_->prev_.remove_first_occurrence_and_reorder(this);
next_->prev_.remove_first_occurrence_and_reorder(*this);
}
if (instruction != nullptr) {
instruction->prev_.append(this);
instruction->prev_.append(*this);
}
next_ = instruction;
}
@@ -420,7 +479,11 @@ MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction
Set<const MFInstruction *> checked_instructions;
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()) {
const MFInstruction &instruction = *instructions_to_check.pop();
@@ -467,7 +530,11 @@ MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction
if (&instruction == entry_) {
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)
{
if (procedure_.entry() == &instruction) {
return true;
}
if (instruction.prev().size() != 1) {
return true;
}
if (instruction.prev()[0]->type() == MFInstructionType::Branch) {
if (instruction.prev()[0].type() == MFInstructionCursor::Type::Branch) {
return true;
}
return false;
@@ -623,7 +687,7 @@ class MFProcedureDotExport {
{
const MFInstruction *current = &representative;
while (!this->has_to_be_block_begin(*current)) {
current = current->prev()[0];
current = current->prev()[0].instruction();
if (current == &representative) {
/* There is a loop without entry or exit, just break it up here. */
break;

View File

@@ -18,50 +18,6 @@
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)
{
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 "BLI_timeit.hh"
#include "FN_multi_function.hh"
#include "FN_multi_function_builder.hh"
#include "FN_multi_function_parallel.hh"
#include "FN_multi_function_test_common.hh"
namespace blender::fn::tests {
@@ -328,5 +331,29 @@ TEST(multi_function, CustomMF_Convert)
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 blender::fn::tests

View File

@@ -118,7 +118,7 @@ void ABCAbstractWriter::update_bounding_box(Object *object)
if (!bb) {
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_.max.x = bounding_box_.max.y = bounding_box_.max.z = 0;

View File

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

View File

@@ -788,7 +788,7 @@ bool RNA_struct_override_matches(Main *bmain,
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);
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(Lattice, name);
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(NormalEdit, defgrp_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_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 */
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,
{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
#undef INSTANCE_ITEMS_SHARED
#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) {
item = instance_items_pointcloud;
}
else if (ob->type == OB_FONT) {
item = instance_items_font;
}
else {
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);
}
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)
{
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);
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_float_funcs(prop, NULL, NULL, "rna_Sequence_pan_range");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_audio_update");
prop = RNA_def_property(srna, "show_waveform", PROP_BOOLEAN, PROP_NONE);

View File

@@ -36,8 +36,11 @@
#include "DNA_screen_types.h"
#include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_mesh_wrapper.h"
#include "BKE_scene.h"
#include "BKE_screen.h"
@@ -53,6 +56,7 @@
#include "MOD_meshcache_util.h" /* utility functions */
#include "MOD_modifiertypes.h"
#include "MOD_ui_common.h"
#include "MOD_util.h"
static void initData(ModifierData *md)
{
@@ -84,11 +88,16 @@ static bool isDisabled(const struct Scene *UNUSED(scene),
static void meshcache_do(MeshCacheModifierData *mcmd,
Scene *scene,
Object *ob,
Mesh *mesh,
float (*vertexCos_Real)[3],
int numVerts)
{
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)) ?
MEM_malloc_arrayN(
numVerts, sizeof(*vertexCos_Store), __func__) :
@@ -256,7 +265,29 @@ static void meshcache_do(MeshCacheModifierData *mcmd,
if (vertexCos_Store) {
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);
}
else {
@@ -270,34 +301,59 @@ static void meshcache_do(MeshCacheModifierData *mcmd,
static void deformVerts(ModifierData *md,
const ModifierEvalContext *ctx,
Mesh *UNUSED(mesh),
Mesh *mesh,
float (*vertexCos)[3],
int numVerts)
{
MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
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,
const ModifierEvalContext *ctx,
struct BMEditMesh *UNUSED(editData),
Mesh *UNUSED(mesh),
struct BMEditMesh *editData,
Mesh *mesh,
float (*vertexCos)[3],
int numVerts)
{
MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
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)
{
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);
@@ -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, "deform_mode", 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);
}

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->default_array = (double *)MEM_mallocN(sizeof(double[3]), "mod_prop_default");
ui_data->default_array_len = 3;
for (int i = 3; i < 3; i++) {
ui_data->default_array[i] = (double)value->value[i];
for (const int i : IndexRange(3)) {
ui_data->default_array[i] = double(value->value[i]);
}
return property;
}

View File

@@ -468,7 +468,7 @@ static void idprop_ui_data_to_dict_int(IDProperty *property, PyObject *dict)
Py_DECREF(list);
}
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);
}
}
@@ -499,7 +499,7 @@ static void idprop_ui_data_to_dict_float(IDProperty *property, PyObject *dict)
Py_DECREF(list);
}
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);
}
}

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_24_8:
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:
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);
}
else {
CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not found\n", member);
CLOG_INFO(BPY_LOG_CONTEXT, 1, "'%s' not found", member);
}
}
else {

View File

@@ -3749,7 +3749,7 @@ wmKeyMap *WM_event_get_keymap_from_toolsystem_fallback(wmWindowManager *wm,
const char *keymap_id = NULL;
/* 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;
wmGizmoGroup *gzgroup = NULL;
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {

View File

@@ -705,7 +705,7 @@ bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data,
}
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;
}

View File

@@ -25,6 +25,7 @@ class TestEntry:
category: str = ''
revision: str = ''
git_hash: str = ''
environment: Dict = field(default_factory=dict)
executable: str = ''
date: int = 0
device_type: str = 'CPU'
@@ -160,7 +161,13 @@ class TestConfig:
def read_blender_executables(env, name) -> List:
config = TestConfig._read_config_module(env.base_dir / name)
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
def _read_config_module(base_dir: pathlib.Path) -> None:
@@ -191,9 +198,10 @@ class TestConfig:
# Get entries for specified commits, tags and branches.
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)
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.
revisions_to_build = set()
@@ -204,6 +212,7 @@ class TestConfig:
# Get entries for revisions based on existing builds.
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))
if not executable_path:
sys.stderr.write(f'Error: build {executable} not found\n')
@@ -214,7 +223,7 @@ class TestConfig:
env.set_default_blender_executable()
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.
categories = set()
@@ -229,6 +238,7 @@ class TestConfig:
revision_name: str,
git_hash: str,
executable: pathlib.Path,
environment: str,
date: int) -> None:
entries = []
for test in self.tests.tests:
@@ -241,10 +251,12 @@ class TestConfig:
# Test if revision hash or executable changed.
if entry.git_hash != git_hash or \
entry.executable != executable or \
entry.environment != environment or \
entry.benchmark_type != self.benchmark_type or \
entry.date != date:
# Update existing entry.
entry.git_hash = git_hash
entry.environment = environment
entry.executable = executable
entry.benchmark_type = self.benchmark_type
entry.date = date
@@ -256,6 +268,7 @@ class TestConfig:
revision=revision_name,
git_hash=git_hash,
executable=executable,
environment=environment,
date=date,
test=test_name,
category=test_category,
@@ -266,3 +279,10 @@ class TestConfig:
entries.append(entry)
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()
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.
self.blender_executable = executable_path
self.blender_executable_environment = environment
def _blender_executable_name(self) -> pathlib.Path:
if platform.system() == "Windows":
@@ -150,6 +151,7 @@ class TestEnvironment:
def set_default_blender_executable(self) -> None:
self.blender_executable = self.default_blender_executable
self.blender_executable_environment = {}
def set_log_file(self, filepath: pathlib.Path, clear=True) -> None:
# Log all commands and output to this file.
@@ -161,7 +163,7 @@ class TestEnvironment:
def unset_log_file(self) -> 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,
# and return combined stdout and stderr output.
@@ -173,7 +175,13 @@ class TestEnvironment:
f = open(self.log_file, 'a')
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
lines = []
@@ -208,7 +216,8 @@ class TestEnvironment:
else:
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,
function: Callable[[Dict], Dict],

View File

@@ -42,7 +42,7 @@ class TestGraph:
# Generate one graph for every device x category x result key combination.
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()
for entry in entries:
@@ -58,8 +58,6 @@ class TestGraph:
self.json = json.dumps(data, indent=2)
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.
tests = {}
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.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.
if entry.status not in ('queued', 'outdated'):
if update_only and entry.status not in ('queued', 'outdated'):
print_row(config, row, end='\r')
return False
# Run test entry.
revision = entry.revision
git_hash = entry.git_hash
environment = entry.environment
testname = entry.test
testcategory = entry.category
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')
executable_ok = True
if len(entry.executable):
env.set_blender_executable(pathlib.Path(entry.executable))
env.set_blender_executable(pathlib.Path(entry.executable), environment)
else:
env.checkout(git_hash)
executable_ok = env.build()
if not executable_ok:
entry.status = 'failed'
entry.error_msg = 'Failed to build'
else:
env.set_blender_executable(env.blender_executable, environment)
# Run test and update output and status.
if executable_ok:
@@ -219,7 +226,7 @@ def cmd_reset(env: api.TestEnvironment, argv: List):
config.queue.write()
def cmd_run(env: api.TestEnvironment, argv: List):
def cmd_run(env: api.TestEnvironment, argv: List, update_only: bool):
# Run tests.
parser = argparse.ArgumentParser()
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)):
if match_entry(row[0], args):
for entry in row:
if run_entry(env, config, row, entry):
if run_entry(env, config, row, entry, update_only):
updated = True
# Write queue every time in case running gets interrupted,
# so it can be resumed.
@@ -268,8 +275,9 @@ def main():
' \n'
' list List available tests, devices and configurations\n'
' \n'
' run [<config>] [<test>] Execute tests for configuration\n'
' reset [<config>] [<test>] Clear tests results from config, for re-running\n'
' run [<config>] [<test>] Execute all tests in configuration\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'
' \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':
cmd_list(env, argv)
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':
cmd_reset(env, argv)
elif args.command == 'status':