diff options
Diffstat (limited to 'source/blender/windowmanager')
32 files changed, 1445 insertions, 247 deletions
diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index 0f26ec50816..183b22c9791 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -203,6 +203,7 @@ if(WITH_XR_OPENXR) list(APPEND SRC xr/intern/wm_xr.c + xr/intern/wm_xr_actions.c xr/intern/wm_xr_draw.c xr/intern/wm_xr_session.c diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 1a505b91ac5..3525502a6dc 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -71,6 +71,11 @@ struct wmTabletData; struct wmNDOFMotionData; #endif +#ifdef WITH_XR_OPENXR +struct wmXrActionState; +struct wmXrPose; +#endif + typedef struct wmGizmo wmGizmo; typedef struct wmGizmoMap wmGizmoMap; typedef struct wmGizmoMapType wmGizmoMapType; @@ -185,6 +190,7 @@ struct wmWindow *WM_window_open(struct bContext *C, int sizex, int sizey, int space_type, + bool toplevel, bool dialog, bool temp, WindowAlignment alignment); @@ -200,6 +206,13 @@ void WM_autosave_init(struct wmWindowManager *wm); bool WM_recover_last_session(struct bContext *C, struct ReportList *reports); void WM_file_tag_modified(void); +struct ID *WM_file_link_datablock(struct Main *bmain, + struct Scene *scene, + struct ViewLayer *view_layer, + struct View3D *v3d, + const char *filepath, + const short id_code, + const char *id_name); struct ID *WM_file_append_datablock(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, @@ -861,6 +874,7 @@ int WM_event_modifier_flag(const struct wmEvent *event); bool WM_event_is_modal_tweak_exit(const struct wmEvent *event, int tweak_event); bool WM_event_is_last_mousemove(const struct wmEvent *event); +bool WM_event_is_mouse_drag(const struct wmEvent *event); int WM_event_drag_threshold(const struct wmEvent *event); bool WM_event_drag_test(const struct wmEvent *event, const int prev_xy[2]); @@ -928,7 +942,7 @@ void WM_generic_user_data_free(struct wmGenericUserData *wm_userdata); bool WM_region_use_viewport(struct ScrArea *area, struct ARegion *region); #ifdef WITH_XR_OPENXR -/* wm_xr.c */ +/* wm_xr_session.c */ bool WM_xr_session_exists(const wmXrData *xr); bool WM_xr_session_is_ready(const wmXrData *xr); struct wmXrSessionState *WM_xr_session_state_handle_get(const wmXrData *xr); @@ -938,7 +952,74 @@ bool WM_xr_session_state_viewer_pose_rotation_get(const wmXrData *xr, float r_ro bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr, float r_viewmat[4][4], float *r_focal_len); -#endif +bool WM_xr_session_state_controller_pose_location_get(const wmXrData *xr, + unsigned int subaction_idx, + float r_location[3]); +bool WM_xr_session_state_controller_pose_rotation_get(const wmXrData *xr, + unsigned int subaction_idx, + float r_rotation[4]); + +/* wm_xr_actions.c */ +/* XR action functions to be called pre-XR session start. + * Note: The "destroy" functions can also be called post-session start. */ +bool WM_xr_action_set_create(wmXrData *xr, const char *action_set_name); +void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name); +bool WM_xr_action_create(wmXrData *xr, + const char *action_set_name, + const char *action_name, + eXrActionType type, + unsigned int count_subaction_paths, + const char **subaction_paths, + const float *float_threshold, + struct wmOperatorType *ot, + struct IDProperty *op_properties, + eXrOpFlag op_flag); +void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name); +bool WM_xr_action_space_create(wmXrData *xr, + const char *action_set_name, + const char *action_name, + unsigned int count_subaction_paths, + const char **subaction_paths, + const struct wmXrPose *poses); +void WM_xr_action_space_destroy(wmXrData *xr, + const char *action_set_name, + const char *action_name, + unsigned int count_subaction_paths, + const char **subaction_paths); +bool WM_xr_action_binding_create(wmXrData *xr, + const char *action_set_name, + const char *profile_path, + const char *action_name, + unsigned int count_interaction_paths, + const char **interaction_paths); +void WM_xr_action_binding_destroy(wmXrData *xr, + const char *action_set_name, + const char *profile_path, + const char *action_name, + unsigned int count_interaction_paths, + const char **interaction_paths); + +bool WM_xr_active_action_set_set( + wmXrData *xr, const char *action_set_name); /* If action_set_name is NULL, then + * all action sets will be treated as active. */ +bool WM_xr_controller_pose_action_set(wmXrData *xr, + const char *action_set_name, + const char *action_name); + +/* XR action functions to be called post-XR session start. */ +bool WM_xr_action_state_get(const wmXrData *xr, + const char *action_set_name, + const char *action_name, + const char *subaction_path, + struct wmXrActionState *r_state); +bool WM_xr_haptic_action_apply(wmXrData *xr, + const char *action_set_name, + const char *action_name, + const long long *duration, + const float *frequency, + const float *amplitude); +void WM_xr_haptic_action_stop(wmXrData *xr, const char *action_set_name, const char *action_name); +#endif /* WITH_XR_OPENXR */ #ifdef __cplusplus } diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index d54925272de..0b26f3c54ae 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -132,17 +132,21 @@ struct wmWindowManager; extern "C" { #endif +typedef void (*wmGenericUserDataFreeFn)(void *data); + typedef struct wmGenericUserData { void *data; /** When NULL, use #MEM_freeN. */ - void (*free_fn)(void *data); + wmGenericUserDataFreeFn free_fn; bool use_free; } wmGenericUserData; +typedef void (*wmGenericCallbackFn)(struct bContext *C, void *user_data); + typedef struct wmGenericCallback { - void (*exec)(struct bContext *C, void *user_data); + wmGenericCallbackFn exec; void *user_data; - void (*free_user_data)(void *user_data); + wmGenericUserDataFreeFn free_user_data; } wmGenericCallback; /* ************** wmOperatorType ************************ */ @@ -680,6 +684,25 @@ typedef struct wmNDOFMotionData { } wmNDOFMotionData; #endif /* WITH_INPUT_NDOF */ +#ifdef WITH_XR_OPENXR +/* Similar to GHOST_XrPose. */ +typedef struct wmXrPose { + float position[3]; + /* Blender convention (w, x, y, z) */ + float orientation_quat[4]; +} wmXrPose; + +typedef struct wmXrActionState { + union { + bool state_boolean; + float state_float; + float state_vector2f[2]; + wmXrPose state_pose; + }; + int type; /* eXrActionType */ +} wmXrActionState; +#endif + /** Timer flags. */ typedef enum { /** Do not attempt to free customdata pointer even if non-NULL. */ @@ -897,6 +920,7 @@ typedef struct wmDragAsset { /* Always freed. */ const char *path; int id_type; + int import_type; /* eFileAssetImportType */ } wmDragAsset; typedef struct wmDrag { diff --git a/source/blender/windowmanager/gizmo/WM_gizmo_api.h b/source/blender/windowmanager/gizmo/WM_gizmo_api.h index cf1a7628267..c7a4b064d0e 100644 --- a/source/blender/windowmanager/gizmo/WM_gizmo_api.h +++ b/source/blender/windowmanager/gizmo/WM_gizmo_api.h @@ -146,6 +146,7 @@ void WM_gizmotype_append(void (*gtfunc)(struct wmGizmoType *)); void WM_gizmotype_append_ptr(void (*gtfunc)(struct wmGizmoType *, void *), void *userdata); bool WM_gizmotype_remove(struct bContext *C, struct Main *bmain, const char *idname); void WM_gizmotype_remove_ptr(struct bContext *C, struct Main *bmain, struct wmGizmoType *gzt); +void WM_gizmotype_free_ptr(struct wmGizmoType *gzt); void WM_gizmotype_iter(struct GHashIterator *ghi); /* wm_gizmo_group_type.c */ @@ -154,8 +155,6 @@ struct wmGizmoGroupType *WM_gizmogrouptype_append(void (*wtfunc)(struct wmGizmoG struct wmGizmoGroupType *WM_gizmogrouptype_append_ptr(void (*wtfunc)(struct wmGizmoGroupType *, void *), void *userdata); -bool WM_gizmogrouptype_free(const char *idname); -void WM_gizmogrouptype_free_ptr(struct wmGizmoGroupType *gzgt); void WM_gizmogrouptype_iter(struct GHashIterator *ghi); struct wmGizmoGroupTypeRef *WM_gizmogrouptype_append_and_link( @@ -378,6 +377,9 @@ void WM_gizmo_group_unlink_delayed_ptr_from_space(struct wmGizmoGroupType *gzgt, struct wmGizmoMapType *gzmap_type, struct ScrArea *area); +void WM_gizmo_group_type_free_ptr(wmGizmoGroupType *gzgt); +bool WM_gizmo_group_type_free(const char *idname); + /* Has the result of unlinking and linking (re-initializes gizmo's). */ void WM_gizmo_group_type_reinit_ptr_ex(struct Main *bmain, struct wmGizmoGroupType *gzgt, diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c index 32b6a6e6b31..062731dfb3d 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c @@ -592,7 +592,7 @@ static int gizmo_tweak_invoke(bContext *C, wmOperator *op, const wmEvent *event) const int highlight_part_init = gz->highlight_part; if (gz->drag_part != -1) { - if (ISTWEAK(event->type) || (event->val == KM_CLICK_DRAG)) { + if (WM_event_is_mouse_drag(event)) { gz->highlight_part = gz->drag_part; } } @@ -1058,12 +1058,14 @@ bool WM_gizmo_group_type_ensure(const char *idname) return WM_gizmo_group_type_ensure_ptr(gzgt); } +/** + * Call #WM_gizmo_group_type_free_ptr after to remove & free. + */ void WM_gizmo_group_type_remove_ptr_ex(struct Main *bmain, wmGizmoGroupType *gzgt, wmGizmoMapType *gzmap_type) { WM_gizmomaptype_group_unlink(NULL, bmain, gzmap_type, gzgt); - WM_gizmogrouptype_free_ptr(gzgt); } void WM_gizmo_group_type_remove_ptr(struct Main *bmain, wmGizmoGroupType *gzgt) { diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c index ab5a265547d..6ebeb5a76b6 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c @@ -154,7 +154,7 @@ static void gizmogrouptype_free(wmGizmoGroupType *gzgt) MEM_freeN(gzgt); } -void WM_gizmogrouptype_free_ptr(wmGizmoGroupType *gzgt) +void WM_gizmo_group_type_free_ptr(wmGizmoGroupType *gzgt) { BLI_assert(gzgt == WM_gizmogrouptype_find(gzgt->idname, false)); @@ -165,7 +165,7 @@ void WM_gizmogrouptype_free_ptr(wmGizmoGroupType *gzgt) /* XXX, TODO, update the world! */ } -bool WM_gizmogrouptype_free(const char *idname) +bool WM_gizmo_group_type_free(const char *idname) { wmGizmoGroupType *gzgt = BLI_ghash_lookup(global_gizmogrouptype_hash, idname); @@ -173,7 +173,7 @@ bool WM_gizmogrouptype_free(const char *idname) return false; } - WM_gizmogrouptype_free_ptr(gzgt); + WM_gizmo_group_type_free_ptr(gzgt); return true; } diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c index 45950a32d85..2ffa04bd8ae 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c @@ -620,7 +620,7 @@ static int gizmo_find_intersected_3d_intern(wmGizmo **visible_gizmos, struct GPUMatrixUnproject_Precalc unproj_precalc; GPU_matrix_unproject_precalc(&unproj_precalc, rv3d->viewmat, rv3d->winmat, viewport); - GPU_matrix_unproject_with_precalc(&unproj_precalc, co_screen, co_3d_origin); + GPU_matrix_unproject_3fv_with_precalc(&unproj_precalc, co_screen, co_3d_origin); uint *buf_iter = buffer; int hit_found = -1; @@ -631,7 +631,7 @@ static int gizmo_find_intersected_3d_intern(wmGizmo **visible_gizmos, wmGizmo *gz = visible_gizmos[buf_iter[3] >> 8]; float co_3d[3]; co_screen[2] = int_as_float(buf_iter[1]); - GPU_matrix_unproject_with_precalc(&unproj_precalc, co_screen, co_3d); + GPU_matrix_unproject_3fv_with_precalc(&unproj_precalc, co_screen, co_3d); float select_bias = gz->select_bias; if ((gz->flag & WM_GIZMO_DRAW_NO_SCALE) == 0) { select_bias *= gz->scale_final; @@ -731,15 +731,6 @@ wmGizmo *wm_gizmomap_highlight_find(wmGizmoMap *gzmap, BLI_buffer_declare_static(wmGizmo *, visible_3d_gizmos, BLI_BUFFER_NOP, 128); bool do_step[WM_GIZMOMAP_DRAWSTEP_MAX]; - int mval[2] = {UNPACK2(event->mval)}; - - /* Ensure for drag events we use the location where the user clicked. - * Without this click-dragging on a gizmo can accidentally act on the wrong gizmo. */ - if (ISTWEAK(event->type) || (event->val == KM_CLICK_DRAG)) { - mval[0] += event->x - event->prevclickx; - mval[1] += event->y - event->prevclicky; - } - for (int i = 0; i < ARRAY_SIZE(do_step); i++) { do_step[i] = WM_gizmo_context_check_drawstep(C, i); } @@ -775,7 +766,7 @@ wmGizmo *wm_gizmomap_highlight_find(wmGizmoMap *gzmap, } else if (step == WM_GIZMOMAP_DRAWSTEP_2D) { if ((gz = wm_gizmogroup_find_intersected_gizmo( - wm, gzgroup, C, event_modifier, mval, r_part))) { + wm, gzgroup, C, event_modifier, event->mval, r_part))) { break; } } @@ -787,7 +778,7 @@ wmGizmo *wm_gizmomap_highlight_find(wmGizmoMap *gzmap, /* 2D gizmos get priority. */ if (gz == NULL) { gz = gizmo_find_intersected_3d( - C, mval, visible_3d_gizmos.data, visible_3d_gizmos.count, r_part); + C, event->mval, visible_3d_gizmos.data, visible_3d_gizmos.count, r_part); } } BLI_buffer_free(&visible_3d_gizmos); diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c index 185854b1ca0..1523246d08b 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c @@ -121,7 +121,7 @@ void WM_gizmotype_append_ptr(void (*gtfunc)(struct wmGizmoType *, void *), void /** * Free but don't remove from ghash. */ -static void gizmotype_free(wmGizmoType *gzt) +void WM_gizmotype_free_ptr(wmGizmoType *gzt) { if (gzt->rna_ext.srna) { /* python gizmo, allocs own string */ MEM_freeN((void *)gzt->idname); @@ -169,8 +169,6 @@ void WM_gizmotype_remove_ptr(bContext *C, Main *bmain, wmGizmoType *gzt) BLI_ghash_remove(global_gizmotype_hash, gzt->idname, NULL, NULL); gizmotype_unlink(C, bmain, gzt); - - gizmotype_free(gzt); } bool WM_gizmotype_remove(bContext *C, Main *bmain, const char *idname) @@ -186,9 +184,9 @@ bool WM_gizmotype_remove(bContext *C, Main *bmain, const char *idname) return true; } -static void wm_gizmotype_ghash_free_cb(wmGizmoType *mt) +static void wm_gizmotype_ghash_free_cb(wmGizmoType *gzt) { - gizmotype_free(mt); + WM_gizmotype_free_ptr(gzt); } void wm_gizmotype_free(void) diff --git a/source/blender/windowmanager/gizmo/wm_gizmo_fn.h b/source/blender/windowmanager/gizmo/wm_gizmo_fn.h index 84e6308223f..ac4f6b761ac 100644 --- a/source/blender/windowmanager/gizmo/wm_gizmo_fn.h +++ b/source/blender/windowmanager/gizmo/wm_gizmo_fn.h @@ -62,9 +62,9 @@ typedef void (*wmGizmoFnMatrixBasisGet)(const struct wmGizmo *, float[4][4]); typedef int (*wmGizmoFnInvoke)(struct bContext *, struct wmGizmo *, const struct wmEvent *); typedef void (*wmGizmoFnExit)(struct bContext *, struct wmGizmo *, const bool); typedef int (*wmGizmoFnCursorGet)(struct wmGizmo *); -typedef void (*wmGizmoFnScreenBoundsGet)(struct bContext *, +typedef bool (*wmGizmoFnScreenBoundsGet)(struct bContext *, struct wmGizmo *, - rcti *r_bounding_box); + rcti *r_bounding_box) ATTR_WARN_UNUSED_RESULT; typedef void (*wmGizmoFnSelectRefresh)(struct wmGizmo *); typedef void (*wmGizmoFnFree)(struct wmGizmo *); diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c index ae5b6c468f7..46e47ae95c4 100644 --- a/source/blender/windowmanager/intern/wm.c +++ b/source/blender/windowmanager/intern/wm.c @@ -186,6 +186,7 @@ static void window_manager_blend_read_data(BlendDataReader *reader, ID *id) win->addmousemove = true; win->event_queue_check_click = 0; win->event_queue_check_drag = 0; + win->event_queue_check_drag_handled = 0; BLO_read_data_address(reader, &win->stereo3d_format); /* Multi-view always fallback to anaglyph at file opening diff --git a/source/blender/windowmanager/intern/wm_cursors.c b/source/blender/windowmanager/intern/wm_cursors.c index cdb7b591907..11783ae3517 100644 --- a/source/blender/windowmanager/intern/wm_cursors.c +++ b/source/blender/windowmanager/intern/wm_cursors.c @@ -264,7 +264,7 @@ void WM_cursor_grab_enable(wmWindow *win, int wrap, bool hide, int bounds[4]) if (wrap == WM_CURSOR_WRAP_X) { mode_axis = GHOST_kAxisX; } - if (wrap == WM_CURSOR_WRAP_Y) { + else if (wrap == WM_CURSOR_WRAP_Y) { mode_axis = GHOST_kGrabAxisY; } } diff --git a/source/blender/windowmanager/intern/wm_dragdrop.c b/source/blender/windowmanager/intern/wm_dragdrop.c index 9684c21605a..fc62d0c6e06 100644 --- a/source/blender/windowmanager/intern/wm_dragdrop.c +++ b/source/blender/windowmanager/intern/wm_dragdrop.c @@ -26,6 +26,7 @@ #include <string.h> #include "DNA_screen_types.h" +#include "DNA_space_types.h" #include "DNA_windowmanager_types.h" #include "MEM_guardedalloc.h" @@ -372,14 +373,22 @@ wmDragAsset *WM_drag_get_asset_data(const wmDrag *drag, int idcode) } wmDragAsset *asset_drag = drag->poin; - return (idcode == 0 || asset_drag->id_type == idcode) ? asset_drag : NULL; + return (ELEM(idcode, 0, asset_drag->id_type)) ? asset_drag : NULL; } static ID *wm_drag_asset_id_import(wmDragAsset *asset_drag) { - /* Append only for now, wmDragAsset could have a `link` bool. */ - return WM_file_append_datablock( - G_MAIN, NULL, NULL, NULL, asset_drag->path, asset_drag->id_type, asset_drag->name); + switch ((eFileAssetImportType)asset_drag->import_type) { + case FILE_ASSET_IMPORT_LINK: + return WM_file_link_datablock( + G_MAIN, NULL, NULL, NULL, asset_drag->path, asset_drag->id_type, asset_drag->name); + case FILE_ASSET_IMPORT_APPEND: + return WM_file_append_datablock( + G_MAIN, NULL, NULL, NULL, asset_drag->path, asset_drag->id_type, asset_drag->name); + } + + BLI_assert_unreachable(); + return NULL; } /** diff --git a/source/blender/windowmanager/intern/wm_draw.c b/source/blender/windowmanager/intern/wm_draw.c index e0c4ab8eaf3..0922aaaee53 100644 --- a/source/blender/windowmanager/intern/wm_draw.c +++ b/source/blender/windowmanager/intern/wm_draw.c @@ -218,13 +218,6 @@ static bool wm_draw_region_stereo_set(Main *bmain, return false; } -static void wm_area_mark_invalid_backbuf(ScrArea *area) -{ - if (area->spacetype == SPACE_VIEW3D) { - ((View3D *)area->spacedata.first)->flag |= V3D_INVALID_BACKBUF; - } -} - static void wm_region_test_gizmo_do_draw(bContext *C, ScrArea *area, ARegion *region, @@ -739,7 +732,6 @@ static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo) } } - wm_area_mark_invalid_backbuf(area); CTX_wm_area_set(C, NULL); GPU_debug_group_end(); diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c index 9b9be6bb497..0050c834a56 100644 --- a/source/blender/windowmanager/intern/wm_event_query.c +++ b/source/blender/windowmanager/intern/wm_event_query.c @@ -265,6 +265,11 @@ bool WM_event_is_last_mousemove(const wmEvent *event) return true; } +bool WM_event_is_mouse_drag(const wmEvent *event) +{ + return ISTWEAK(event->type) || (ISMOUSE_BUTTON(event->type) && (event->val == KM_CLICK_DRAG)); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -277,15 +282,17 @@ bool WM_event_is_last_mousemove(const wmEvent *event) int WM_event_drag_threshold(const struct wmEvent *event) { int drag_threshold; - if (WM_event_is_tablet(event)) { - drag_threshold = U.drag_threshold_tablet; - } - else if (ISMOUSE(event->prevtype)) { + if (ISMOUSE(event->prevtype)) { BLI_assert(event->prevtype != MOUSEMOVE); /* Using the previous type is important is we want to check the last pressed/released button, * The `event->type` would include #MOUSEMOVE which is always the case when dragging * and does not help us know which threshold to use. */ - drag_threshold = U.drag_threshold_mouse; + if (WM_event_is_tablet(event)) { + drag_threshold = U.drag_threshold_tablet; + } + else { + drag_threshold = U.drag_threshold_mouse; + } } else { /* Typically keyboard, could be NDOF button or other less common types. */ diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 791aeeb39cd..a6588c40f0f 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -580,7 +580,7 @@ void wm_event_do_notifiers(bContext *C) if ((note->category == NC_SPACE) && note->reference) { /* Filter out notifiers sent to other spaces. RNA sets the reference to the owning ID * though, the screen, so let notifiers through that reference the entire screen. */ - if ((note->reference != area->spacedata.first) && (note->reference != screen)) { + if (!ELEM(note->reference, area->spacedata.first, screen)) { continue; } } @@ -1048,7 +1048,7 @@ static int wm_operator_exec(bContext *C, wmOperator *op, const bool repeat, cons wmWindowManager *wm = CTX_wm_manager(C); int retval = OPERATOR_CANCELLED; - CTX_wm_operator_poll_msg_set(C, NULL); + CTX_wm_operator_poll_msg_clear(C); if (op == NULL || op->type == NULL) { return retval; @@ -1469,7 +1469,7 @@ static int wm_operator_call_internal(bContext *C, { int retval; - CTX_wm_operator_poll_msg_set(C, NULL); + CTX_wm_operator_poll_msg_clear(C); /* Dummy test. */ if (ot) { @@ -1621,7 +1621,7 @@ int WM_operator_name_call_with_properties(struct bContext *C, { PointerRNA props_ptr; wmOperatorType *ot = WM_operatortype_find(opstring, false); - RNA_pointer_create(NULL, ot->srna, properties, &props_ptr); + RNA_pointer_create(G_MAIN->wm.first, ot->srna, properties, &props_ptr); return WM_operator_name_call_ptr(C, ot, context, &props_ptr); } @@ -2595,6 +2595,15 @@ static int wm_handlers_do_gizmo_handler(bContext *C, ListBase *handlers, const bool do_debug_handler) { + /* Drag events use the previous click location to highlight the gizmos, + * Get the highlight again in case the user dragged off the gizmo. */ + const bool is_event_drag = ISTWEAK(event->type) || (event->val == KM_CLICK_DRAG); + const bool is_event_modifier = ISKEYMODIFIER(event->type); + /* Only keep the highlight if the gizmo becomes modal as result of event handling. + * Without this check, even un-handled drag events will set the highlight if the drag + * was initiated over a gizmo. */ + const bool restore_highlight_unless_activated = is_event_drag; + int action = WM_HANDLER_CONTINUE; ScrArea *area = CTX_wm_area(C); ARegion *region = CTX_wm_region(C); @@ -2608,13 +2617,25 @@ static int wm_handlers_do_gizmo_handler(bContext *C, if (region->type->clip_gizmo_events_by_ui) { if (UI_region_block_find_mouse_over(region, &event->x, true)) { if (gz != NULL && event->type != EVT_GIZMO_UPDATE) { - WM_tooltip_clear(C, CTX_wm_window(C)); - wm_gizmomap_highlight_set(gzmap, C, NULL, 0); + if (restore_highlight_unless_activated == false) { + WM_tooltip_clear(C, CTX_wm_window(C)); + wm_gizmomap_highlight_set(gzmap, C, NULL, 0); + } } return action; } } + struct { + wmGizmo *gz_modal; + wmGizmo *gz; + int part; + } prev = { + .gz_modal = wm_gizmomap_modal_get(gzmap), + .gz = gz, + .part = gz ? gz->highlight_part : 0, + }; + if (region->gizmo_map != handler->gizmo_map) { WM_gizmomap_tag_refresh(handler->gizmo_map); } @@ -2622,16 +2643,11 @@ static int wm_handlers_do_gizmo_handler(bContext *C, wm_gizmomap_handler_context_gizmo(C, handler); wm_region_mouse_co(C, event); - /* Drag events use the previous click location to highlight the gizmos, - * Get the highlight again in case the user dragged off the gizmo. */ - const bool is_event_drag = ISTWEAK(event->type) || (event->val == KM_CLICK_DRAG); - const bool is_event_modifier = ISKEYMODIFIER(event->type); - bool handle_highlight = false; bool handle_keymap = false; /* Handle gizmo highlighting. */ - if (!wm_gizmomap_modal_get(gzmap) && + if ((prev.gz_modal == NULL) && ((event->type == MOUSEMOVE) || is_event_modifier || is_event_drag)) { handle_highlight = true; if (is_event_modifier || is_event_drag) { @@ -2642,14 +2658,15 @@ static int wm_handlers_do_gizmo_handler(bContext *C, handle_keymap = true; } + /* There is no need to handle this event when the key-map isn't being applied + * since any change to the highlight will be restored to the previous value. */ + if (restore_highlight_unless_activated) { + if ((handle_highlight == true) && (handle_keymap == false)) { + return action; + } + } + if (handle_highlight) { - struct { - wmGizmo *gz; - int part; - } prev = { - .gz = gz, - .part = gz ? gz->highlight_part : 0, - }; int part = -1; gz = wm_gizmomap_highlight_find(gzmap, C, event, &part); @@ -2734,6 +2751,16 @@ static int wm_handlers_do_gizmo_handler(bContext *C, } } + if (handle_highlight) { + if (restore_highlight_unless_activated) { + /* Check handling the key-map didn't activate a gizmo. */ + wmGizmo *gz_modal = wm_gizmomap_modal_get(gzmap); + if (!(gz_modal && (gz_modal != prev.gz_modal))) { + wm_gizmomap_highlight_set(gzmap, C, prev.gz, prev.part); + } + } + } + if (is_event_handle_all) { if (action == WM_HANDLER_CONTINUE) { action |= WM_HANDLER_BREAK | WM_HANDLER_MODAL; @@ -2941,6 +2968,8 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers) if (wm_action_not_handled(action)) { if (win->event_queue_check_drag) { if (WM_event_drag_test(event, &event->prevclickx)) { + win->event_queue_check_drag_handled = true; + int x = event->x; int y = event->y; short val = event->val; @@ -2984,6 +3013,7 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers) if (event->is_repeat == false) { win->event_queue_check_click = true; win->event_queue_check_drag = true; + win->event_queue_check_drag_handled = false; } } else if (event->val == KM_RELEASE) { @@ -3470,6 +3500,13 @@ void wm_event_do_handlers(bContext *C) win->event_queue_check_click = false; } + /* If the drag even was handled, don't attempt to keep re-handing the same + * drag event on every cursor motion, see: T87511. */ + if (win->event_queue_check_drag_handled) { + win->event_queue_check_drag = false; + win->event_queue_check_drag_handled = false; + } + /* Update previous mouse position for following events to use. */ win->eventstate->prevx = event->x; win->eventstate->prevy = event->y; @@ -4441,16 +4478,21 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void event.prevtype = event.type; event.prevval = event.val; - /* Ensure the event state is correct, any deviation from this may cause bugs. */ + /* Ensure the event state is correct, any deviation from this may cause bugs. + * + * NOTE: #EVENT_NONE is set when unknown keys are pressed, + * while not common, avoid a false alarm. */ #ifndef NDEBUG if ((event_state->type || event_state->val) && /* Ignore cleared event state. */ - !(ISMOUSE_BUTTON(event_state->type) || ISKEYBOARD(event_state->type))) { + !(ISMOUSE_BUTTON(event_state->type) || ISKEYBOARD(event_state->type) || + (event_state->type == EVENT_NONE))) { CLOG_WARN(WM_LOG_HANDLERS, "Non-keyboard/mouse button found in 'win->eventstate->type = %d'", event_state->type); } if ((event_state->prevtype || event_state->prevval) && /* Ignore cleared event state. */ - !(ISMOUSE_BUTTON(event_state->prevtype) || ISKEYBOARD(event_state->prevtype))) { + !(ISMOUSE_BUTTON(event_state->prevtype) || ISKEYBOARD(event_state->prevtype) || + (event_state->type == EVENT_NONE))) { CLOG_WARN(WM_LOG_HANDLERS, "Non-keyboard/mouse button found in 'win->eventstate->prevtype = %d'", event_state->prevtype); diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index bbcb0669cce..0aff2c00644 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -911,7 +911,10 @@ void wm_homefile_read(bContext *C, const char *app_template_override, bool *r_is_factory_startup) { - Main *bmain = G_MAIN; /* Context does not always have valid main pointer here... */ +#if 0 /* UNUSED, keep as this may be needed later & the comment below isn't self evident. */ + /* Context does not always have valid main pointer here. */ + Main *bmain = G_MAIN; +#endif ListBase wmbase; bool success = false; @@ -1170,7 +1173,7 @@ void wm_homefile_read(bContext *C, BLI_strncpy(U.app_template, app_template_override, sizeof(U.app_template)); } - bmain = CTX_data_main(C); + Main *bmain = CTX_data_main(C); if (use_userdef) { /* check userdef before open window, keymaps etc */ @@ -2157,21 +2160,9 @@ static void wm_homefile_read_after_dialog_callback(bContext *C, void *user_data) C, "WM_OT_read_homefile", WM_OP_EXEC_DEFAULT, (IDProperty *)user_data); } -static void wm_free_operator_properties_callback(void *user_data) -{ - IDProperty *properties = (IDProperty *)user_data; - IDP_FreeProperty(properties); -} - static int wm_homefile_read_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - if (U.uiflag & USER_SAVE_PROMPT && - wm_file_or_image_is_modified(CTX_data_main(C), CTX_wm_manager(C))) { - wmGenericCallback *callback = MEM_callocN(sizeof(*callback), __func__); - callback->exec = wm_homefile_read_after_dialog_callback; - callback->user_data = IDP_CopyProperty(op->properties); - callback->free_user_data = wm_free_operator_properties_callback; - wm_close_file_dialog(C, callback); + if (wm_operator_close_file_dialog_if_needed(C, op, wm_homefile_read_after_dialog_callback)) { return OPERATOR_INTERFACE; } return wm_homefile_read_exec(C, op); @@ -2331,13 +2322,7 @@ static int wm_open_mainfile__discard_changes(bContext *C, wmOperator *op) set_next_operator_state(op, OPEN_MAINFILE_STATE_OPEN); } - if (U.uiflag & USER_SAVE_PROMPT && - wm_file_or_image_is_modified(CTX_data_main(C), CTX_wm_manager(C))) { - wmGenericCallback *callback = MEM_callocN(sizeof(*callback), __func__); - callback->exec = wm_open_mainfile_after_dialog_callback; - callback->user_data = IDP_CopyProperty(op->properties); - callback->free_user_data = wm_free_operator_properties_callback; - wm_close_file_dialog(C, callback); + if (wm_operator_close_file_dialog_if_needed(C, op, wm_open_mainfile_after_dialog_callback)) { return OPERATOR_INTERFACE; } return wm_open_mainfile_dispatch(C, op); @@ -2500,12 +2485,11 @@ static void wm_open_mainfile_ui(bContext *UNUSED(C), wmOperator *op) { struct FileRuntime *file_info = (struct FileRuntime *)&op->customdata; uiLayout *layout = op->layout; - uiLayout *col = op->layout; const char *autoexec_text; uiItemR(layout, op->ptr, "load_ui", 0, NULL, ICON_NONE); - col = uiLayoutColumn(layout, false); + uiLayout *col = uiLayoutColumn(layout, false); if (file_info->is_untrusted) { autoexec_text = IFACE_("Trusted Source [Untrusted Path]"); uiLayoutSetActive(col, false); @@ -2637,12 +2621,25 @@ static int wm_recover_last_session_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } -static int wm_recover_last_session_invoke(bContext *C, wmOperator *op, const wmEvent *event) +static void wm_recover_last_session_after_dialog_callback(bContext *C, void *user_data) +{ + WM_operator_name_call_with_properties( + C, "WM_OT_recover_last_session", WM_OP_EXEC_DEFAULT, (IDProperty *)user_data); +} + +static int wm_recover_last_session_invoke(bContext *C, + wmOperator *op, + const wmEvent *UNUSED(event)) { /* Keep the current setting instead of using the preferences since a file selector * doesn't give us the option to change the setting. */ wm_open_init_use_scripts(op, false); - return WM_operator_confirm(C, op, event); + + if (wm_operator_close_file_dialog_if_needed( + C, op, wm_recover_last_session_after_dialog_callback)) { + return OPERATOR_INTERFACE; + } + return wm_recover_last_session_exec(C, op); } void WM_OT_recover_last_session(wmOperatorType *ot) @@ -3270,7 +3267,10 @@ static void wm_block_file_close_save(bContext *C, void *arg_block, void *arg_dat bool file_has_been_saved_before = BKE_main_blendfile_path(bmain)[0] != '\0'; if (file_has_been_saved_before) { - WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, NULL); + if (WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, NULL) & + OPERATOR_CANCELLED) { + execute_callback = false; + } } else { WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_INVOKE_DEFAULT, NULL); @@ -3456,4 +3456,31 @@ void wm_close_file_dialog(bContext *C, wmGenericCallback *post_action) } } +static void wm_free_operator_properties_callback(void *user_data) +{ + IDProperty *properties = (IDProperty *)user_data; + IDP_FreeProperty(properties); +} + +/** + * \return True if the dialog was created, the calling operator should return #OPERATOR_INTERFACE + * then. + */ +bool wm_operator_close_file_dialog_if_needed(bContext *C, + wmOperator *op, + wmGenericCallbackFn post_action_fn) +{ + if (U.uiflag & USER_SAVE_PROMPT && + wm_file_or_image_is_modified(CTX_data_main(C), CTX_wm_manager(C))) { + wmGenericCallback *callback = MEM_callocN(sizeof(*callback), __func__); + callback->exec = post_action_fn; + callback->user_data = IDP_CopyProperty(op->properties); + callback->free_user_data = wm_free_operator_properties_callback; + wm_close_file_dialog(C, callback); + return true; + } + + return false; +} + /** \} */ diff --git a/source/blender/windowmanager/intern/wm_files_link.c b/source/blender/windowmanager/intern/wm_files_link.c index 840debad01b..e467dcd243e 100644 --- a/source/blender/windowmanager/intern/wm_files_link.c +++ b/source/blender/windowmanager/intern/wm_files_link.c @@ -642,23 +642,23 @@ void WM_OT_append(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Append Single Data-Block & Return it +/** \name Link/Append Single Data-Block & Return it * - * Used for appending workspace from startup files. * \{ */ -ID *WM_file_append_datablock(Main *bmain, - Scene *scene, - ViewLayer *view_layer, - View3D *v3d, - const char *filepath, - const short id_code, - const char *id_name) +static ID *wm_file_link_datablock_ex(Main *bmain, + Scene *scene, + ViewLayer *view_layer, + View3D *v3d, + const char *filepath, + const short id_code, + const char *id_name, + bool clear_pre_existing_flag) { /* Tag everything so we can make local only the new datablock. */ BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true); - /* Define working data, with just the one item we want to append. */ + /* Define working data, with just the one item we want to link. */ WMLinkAppendData *lapp_data = wm_link_append_data_new(0); wm_link_append_data_library_add(lapp_data, filepath); @@ -672,6 +672,36 @@ ID *WM_file_append_datablock(Main *bmain, ID *id = item->new_id; wm_link_append_data_free(lapp_data); + if (clear_pre_existing_flag) { + BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); + } + + return id; +} + +ID *WM_file_link_datablock(Main *bmain, + Scene *scene, + ViewLayer *view_layer, + View3D *v3d, + const char *filepath, + const short id_code, + const char *id_name) +{ + return wm_file_link_datablock_ex( + bmain, scene, view_layer, v3d, filepath, id_code, id_name, true); +} + +ID *WM_file_append_datablock(Main *bmain, + Scene *scene, + ViewLayer *view_layer, + View3D *v3d, + const char *filepath, + const short id_code, + const char *id_name) +{ + ID *id = wm_file_link_datablock_ex( + bmain, scene, view_layer, v3d, filepath, id_code, id_name, false); + /* Make datablock local. */ BKE_library_make_local(bmain, NULL, NULL, true, false); diff --git a/source/blender/windowmanager/intern/wm_gesture.c b/source/blender/windowmanager/intern/wm_gesture.c index 4c1b403ac96..ae2cdb5608c 100644 --- a/source/blender/windowmanager/intern/wm_gesture.c +++ b/source/blender/windowmanager/intern/wm_gesture.c @@ -385,7 +385,7 @@ static void draw_filled_lasso(wmGesture *gt) mcoords[i][1] = lasso[1]; } - BLI_lasso_boundbox(&rect, (const int(*)[2])mcoords, mcoords_len); + BLI_lasso_boundbox(&rect, mcoords, mcoords_len); BLI_rcti_translate(&rect, gt->winrct.xmin, gt->winrct.ymin); BLI_rcti_isect(>->winrct, &rect, &rect); @@ -402,7 +402,7 @@ static void draw_filled_lasso(wmGesture *gt) rect.ymin, rect.xmax, rect.ymax, - (const int(*)[2])mcoords, + mcoords, mcoords_len, draw_filled_lasso_px_cb, &lasso_fill_data); diff --git a/source/blender/windowmanager/intern/wm_gesture_ops.c b/source/blender/windowmanager/intern/wm_gesture_ops.c index 07d68293714..94535427dac 100644 --- a/source/blender/windowmanager/intern/wm_gesture_ops.c +++ b/source/blender/windowmanager/intern/wm_gesture_ops.c @@ -316,6 +316,7 @@ int WM_gesture_circle_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (gesture->wait_for_input == false) { gesture->is_active = true; gesture_circle_apply(C, op); + gesture->is_active_prev = true; } /* add modal handler */ diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index 56fd51ac6fd..15f0978f87d 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -343,8 +343,13 @@ void WM_init(bContext *C, int argc, const char **argv) (void)argv; /* unused */ #endif - if (!G.background && !wm_start_with_console) { - GHOST_toggleConsole(3); + if (!G.background) { + if (wm_start_with_console) { + GHOST_toggleConsole(1); + } + else { + GHOST_toggleConsole(3); + } } BKE_material_copybuf_clear(); @@ -496,7 +501,7 @@ void WM_exit_ex(bContext *C, const bool do_python) if ((has_edited && BLO_write_file( bmain, filename, fileflags, &(const struct BlendFileWriteParams){0}, NULL)) || - (undo_memfile && BLO_memfile_write_file(undo_memfile, filename))) { + (BLO_memfile_write_file(undo_memfile, filename))) { printf("Saved session recovery to '%s'\n", filename); } } diff --git a/source/blender/windowmanager/intern/wm_keymap.c b/source/blender/windowmanager/intern/wm_keymap.c index 38d06ea83d3..0a157e63b09 100644 --- a/source/blender/windowmanager/intern/wm_keymap.c +++ b/source/blender/windowmanager/intern/wm_keymap.c @@ -80,6 +80,9 @@ static wmKeyMapItem *wm_keymap_item_copy(wmKeyMapItem *kmi) kmin->ptr = MEM_callocN(sizeof(PointerRNA), "UserKeyMapItemPtr"); WM_operator_properties_create(kmin->ptr, kmin->idname); + /* Signal for no context, see #STRUCT_NO_CONTEXT_WITHOUT_OWNER_ID. */ + kmin->ptr->owner_id = NULL; + kmin->properties = IDP_CopyProperty(kmin->properties); kmin->ptr->data = kmin->properties; } @@ -106,6 +109,9 @@ static void wm_keymap_item_properties_set(wmKeyMapItem *kmi) { WM_operator_properties_alloc(&(kmi->ptr), &(kmi->properties), kmi->idname); WM_operator_properties_sanitize(kmi->ptr, 1); + + /* Signal for no context, see #STRUCT_NO_CONTEXT_WITHOUT_OWNER_ID. */ + kmi->ptr->owner_id = NULL; } /** @@ -136,6 +142,9 @@ static void wm_keymap_item_properties_update_ot(wmKeyMapItem *kmi) kmi->ptr->data = kmi->properties; } WM_operator_properties_sanitize(kmi->ptr, 1); + + /* Signal for no context, see #STRUCT_NO_CONTEXT_WITHOUT_OWNER_ID. */ + kmi->ptr->owner_id = NULL; } } else { diff --git a/source/blender/windowmanager/intern/wm_operator_type.c b/source/blender/windowmanager/intern/wm_operator_type.c index 0e57a92b685..f33db7d50b5 100644 --- a/source/blender/windowmanager/intern/wm_operator_type.c +++ b/source/blender/windowmanager/intern/wm_operator_type.c @@ -252,7 +252,7 @@ void WM_operatortype_props_advanced_end(wmOperatorType *ot) return; } - RNA_pointer_create(NULL, ot->srna, NULL, &struct_ptr); + WM_operator_properties_create_ptr(&struct_ptr, ot); RNA_STRUCT_BEGIN (&struct_ptr, prop) { counter++; diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index 84c16999c1b..9175eb2dbf7 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -583,7 +583,8 @@ char *WM_prop_pystring_assign(bContext *C, PointerRNA *ptr, PropertyRNA *prop, i void WM_operator_properties_create_ptr(PointerRNA *ptr, wmOperatorType *ot) { - RNA_pointer_create(NULL, ot->srna, NULL, ptr); + /* Set the ID so the context can be accessed: see #STRUCT_NO_CONTEXT_WITHOUT_OWNER_ID. */ + RNA_pointer_create(G_MAIN->wm.first, ot->srna, NULL, ptr); } void WM_operator_properties_create(PointerRNA *ptr, const char *opstring) @@ -594,7 +595,8 @@ void WM_operator_properties_create(PointerRNA *ptr, const char *opstring) WM_operator_properties_create_ptr(ptr, ot); } else { - RNA_pointer_create(NULL, &RNA_OperatorProperties, NULL, ptr); + /* Set the ID so the context can be accessed: see #STRUCT_NO_CONTEXT_WITHOUT_OWNER_ID. */ + RNA_pointer_create(G_MAIN->wm.first, &RNA_OperatorProperties, NULL, ptr); } } @@ -1205,7 +1207,7 @@ IDProperty *WM_operator_last_properties_ensure_idprops(wmOperatorType *ot) void WM_operator_last_properties_ensure(wmOperatorType *ot, PointerRNA *ptr) { IDProperty *props = WM_operator_last_properties_ensure_idprops(ot); - RNA_pointer_create(NULL, ot->srna, props, ptr); + RNA_pointer_create(G_MAIN->wm.first, ot->srna, props, ptr); } /** @@ -1909,6 +1911,9 @@ static bool wm_operator_winactive_normal(bContext *C) if (!((screen = WM_window_get_active_screen(win)) && (screen->state == SCREENNORMAL))) { return 0; } + if (G.background) { + return 0; + } return 1; } diff --git a/source/blender/windowmanager/intern/wm_playanim.c b/source/blender/windowmanager/intern/wm_playanim.c index 58030920fc7..5300649a0cd 100644 --- a/source/blender/windowmanager/intern/wm_playanim.c +++ b/source/blender/windowmanager/intern/wm_playanim.c @@ -47,9 +47,11 @@ #include "BLI_fileops.h" #include "BLI_listbase.h" #include "BLI_path_util.h" +#include "BLI_rect.h" #include "BLI_string.h" #include "BLI_utildefines.h" +#include "IMB_colormanagement.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -95,64 +97,85 @@ static AUD_Device *audio_device = NULL; struct PlayState; static void playanim_window_zoom(struct PlayState *ps, const float zoom_offset); +/** + * The current state of the player. + * + * \warning Don't store results of parsing command-line arguments + * in this struct if they need to persist across playing back different + * files as these will be cleared when playing other files (drag & drop). + */ typedef struct PlayState { - /* window and viewport size */ + /** Window and viewport size. */ int win_x, win_y; - /* current zoom level */ + /** Current zoom level. */ float zoom; - /* playback state */ + /** Playback direction (-1, 1). */ short direction; + /** Set the next frame to implement frame stepping (using shortcuts). */ short next_frame; + /** Playback once then wait. */ bool once; - bool turbo; + /** Play forwards/backwards. */ bool pingpong; + /** Disable frame skipping. */ bool noskip; + /** Display current frame over the window. */ bool indicator; + /** Single-frame stepping has been enabled (frame loading and update pending). */ bool sstep; + /** Playback has stopped the image has been displayed. */ bool wait2; + /** Playback stopped state once stop/start variables have been handled. */ bool stopped; + /** + * When disabled the current animation will exit, + * after this either the application exits or a new animation window is opened. + * + * This is used so drag & drop can load new files which setup a newly created animation window. + */ bool go; - /* waiting for images to load */ + /** True when waiting for images to load. */ bool loading; - /* x/y image flip */ + /** X/Y image flip (set via key bindings). */ bool draw_flip[2]; + /** The number of frames to step each update (default to 1, command line argument). */ int fstep; - /* current picture */ + /** Current frame (picture). */ struct PlayAnimPict *picture; - /* set once at the start */ + /** Image size in pixels, set once at the start. */ int ibufx, ibufy; + /** Mono-space font ID. */ int fontid; - /* saves passing args */ - struct ImBuf *curframe_ibuf; - - /* restarts player for file drop */ + /** Restarts player for file drop (drag & drop). */ char dropped_file[FILE_MAX]; + /** Force update when scrubbing with the cursor. */ bool need_frame_update; + /** The current frame calculated by scrubbing the mouse cursor. */ int frame_cursor_x; + + ColorManagedViewSettings view_settings; + ColorManagedDisplaySettings display_settings; } PlayState; /* for debugging */ #if 0 -void print_ps(PlayState *ps) +static void print_ps(PlayState *ps) { printf("ps:\n"); printf(" direction=%d,\n", (int)ps->direction); - printf(" next=%d,\n", ps->next); printf(" once=%d,\n", ps->once); - printf(" turbo=%d,\n", ps->turbo); printf(" pingpong=%d,\n", ps->pingpong); printf(" noskip=%d,\n", ps->noskip); printf(" sstep=%d,\n", ps->sstep); - printf(" pause=%d,\n", ps->pause); printf(" wait2=%d,\n", ps->wait2); printf(" stopped=%d,\n", ps->stopped); printf(" go=%d,\n\n", ps->go); @@ -237,6 +260,12 @@ typedef struct PlayAnimPict { struct anim *anim; int frame; int IB_flags; + +#ifdef USE_FRAME_CACHE_LIMIT + /** Back pointer to the #LinkData node for this struct in the #g_frame_cache.pics list. */ + LinkData *frame_cache_node; + size_t size_in_memory; +#endif } PlayAnimPict; static struct ListBase picsbase = {NULL, NULL}; @@ -248,9 +277,103 @@ static double fps_movie; #endif #ifdef USE_FRAME_CACHE_LIMIT -static struct ListBase inmempicsbase = {NULL, NULL}; -static int added_images = 0; -#endif +static struct { + /** A list of #LinkData nodes referencing #PlayAnimPict to track cached frames. */ + struct ListBase pics; + /** Number if elements in `pics`. */ + int pics_len; + /** Keep track of memory used by #g_frame_cache.pics when `g_frame_cache.memory_limit != 0`. */ + size_t pics_size_in_memory; + /** Optionally limit the amount of memory used for cache (in bytes), ignored when zero. */ + size_t memory_limit; +} g_frame_cache = { + .pics = {NULL, NULL}, + .pics_len = 0, + .pics_size_in_memory = 0, + .memory_limit = 0, +}; + +static void frame_cache_add(PlayAnimPict *pic) +{ + pic->frame_cache_node = BLI_genericNodeN(pic); + BLI_addhead(&g_frame_cache.pics, pic->frame_cache_node); + g_frame_cache.pics_len++; + + if (g_frame_cache.memory_limit != 0) { + BLI_assert(pic->size_in_memory == 0); + pic->size_in_memory = IMB_get_size_in_memory(pic->ibuf); + g_frame_cache.pics_size_in_memory += pic->size_in_memory; + } +} + +static void frame_cache_remove(PlayAnimPict *pic) +{ + LinkData *node = pic->frame_cache_node; + IMB_freeImBuf(pic->ibuf); + if (g_frame_cache.memory_limit != 0) { + BLI_assert(pic->size_in_memory != 0); + g_frame_cache.pics_size_in_memory -= pic->size_in_memory; + pic->size_in_memory = 0; + } + pic->ibuf = NULL; + pic->frame_cache_node = NULL; + BLI_freelinkN(&g_frame_cache.pics, node); + g_frame_cache.pics_len--; +} + +/* Don't free the current frame by moving it to the head of the list. */ +static void frame_cache_touch(PlayAnimPict *pic) +{ + BLI_assert(pic->frame_cache_node->data == pic); + BLI_remlink(&g_frame_cache.pics, pic->frame_cache_node); + BLI_addhead(&g_frame_cache.pics, pic->frame_cache_node); +} + +static bool frame_cache_limit_exceeded(void) +{ + return g_frame_cache.memory_limit ? + (g_frame_cache.pics_size_in_memory > g_frame_cache.memory_limit) : + (g_frame_cache.pics_len > PLAY_FRAME_CACHE_MAX); +} + +static void frame_cache_limit_apply(ImBuf *ibuf_keep) +{ + /* Really basic memory conservation scheme. Keep frames in a FIFO queue. */ + LinkData *node = g_frame_cache.pics.last; + while (node && frame_cache_limit_exceeded()) { + PlayAnimPict *pic = node->data; + BLI_assert(pic->frame_cache_node == node); + + node = node->prev; + if (pic->ibuf && pic->ibuf != ibuf_keep) { + frame_cache_remove(pic); + } + } +} + +#endif /* USE_FRAME_CACHE_LIMIT */ + +static ImBuf *ibuf_from_picture(PlayAnimPict *pic) +{ + ImBuf *ibuf = NULL; + + if (pic->ibuf) { + ibuf = pic->ibuf; + } + else if (pic->anim) { + ibuf = IMB_anim_absolute(pic->anim, pic->frame, IMB_TC_NONE, IMB_PROXY_NONE); + } + else if (pic->mem) { + /* use correct colorspace here */ + ibuf = IMB_ibImageFromMemory(pic->mem, pic->size, pic->IB_flags, NULL, pic->name); + } + else { + /* use correct colorspace here */ + ibuf = IMB_loadiffname(pic->name, pic->IB_flags, NULL); + } + + return ibuf; +} static PlayAnimPict *playanim_step(PlayAnimPict *playanim, int step) { @@ -278,6 +401,151 @@ static int pupdate_time(void) return (ptottime < 0); } +static void *ocio_transform_ibuf(PlayState *ps, + ImBuf *ibuf, + bool *r_glsl_used, + eGPUTextureFormat *r_format, + eGPUDataFormat *r_data, + void **r_buffer_cache_handle) +{ + void *display_buffer; + bool force_fallback = false; + *r_glsl_used = false; + force_fallback |= (ED_draw_imbuf_method(ibuf) != IMAGE_DRAW_METHOD_GLSL); + force_fallback |= (ibuf->dither != 0.0f); + + /* Default */ + *r_format = GPU_RGBA8; + *r_data = GPU_DATA_UBYTE; + + /* Fallback to CPU based color space conversion. */ + if (force_fallback) { + *r_glsl_used = false; + display_buffer = NULL; + } + else if (ibuf->rect_float) { + display_buffer = ibuf->rect_float; + + *r_data = GPU_DATA_FLOAT; + if (ibuf->channels == 4) { + *r_format = GPU_RGBA16F; + } + else if (ibuf->channels == 3) { + /* Alpha is implicitly 1. */ + *r_format = GPU_RGB16F; + } + + if (ibuf->float_colorspace) { + *r_glsl_used = IMB_colormanagement_setup_glsl_draw_from_space(&ps->view_settings, + &ps->display_settings, + ibuf->float_colorspace, + ibuf->dither, + false, + false); + } + else { + *r_glsl_used = IMB_colormanagement_setup_glsl_draw( + &ps->view_settings, &ps->display_settings, ibuf->dither, false); + } + } + else if (ibuf->rect) { + display_buffer = ibuf->rect; + *r_glsl_used = IMB_colormanagement_setup_glsl_draw_from_space(&ps->view_settings, + &ps->display_settings, + ibuf->rect_colorspace, + ibuf->dither, + false, + false); + } + else { + display_buffer = NULL; + } + + /* There is data to be displayed, but GLSL is not initialized + * properly, in this case we fallback to CPU-based display transform. */ + if ((ibuf->rect || ibuf->rect_float) && !*r_glsl_used) { + display_buffer = IMB_display_buffer_acquire( + ibuf, &ps->view_settings, &ps->display_settings, r_buffer_cache_handle); + *r_format = GPU_RGBA8; + *r_data = GPU_DATA_UBYTE; + } + + return display_buffer; +} + +static void draw_display_buffer(PlayState *ps, ImBuf *ibuf) +{ + void *display_buffer; + + /* Format needs to be created prior to any #immBindShader call. + * Do it here because OCIO binds its own shader. */ + eGPUTextureFormat format; + eGPUDataFormat data; + bool glsl_used = false; + GPUVertFormat *imm_format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(imm_format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + uint texCoord = GPU_vertformat_attr_add( + imm_format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + + void *buffer_cache_handle = NULL; + display_buffer = ocio_transform_ibuf(ps, ibuf, &glsl_used, &format, &data, &buffer_cache_handle); + + GPUTexture *texture = GPU_texture_create_2d("display_buf", ibuf->x, ibuf->y, 1, format, NULL); + GPU_texture_update(texture, data, display_buffer); + GPU_texture_filter_mode(texture, false); + + GPU_texture_bind(texture, 0); + + if (!glsl_used) { + immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_COLOR); + immUniformColor3f(1.0f, 1.0f, 1.0f); + immUniform1i("image", 0); + } + + immBegin(GPU_PRIM_TRI_FAN, 4); + + rctf preview; + rctf canvas; + + BLI_rctf_init(&canvas, 0.0f, 1.0f, 0.0f, 1.0f); + BLI_rctf_init(&preview, 0.0f, 1.0f, 0.0f, 1.0f); + + if (ps->draw_flip[0]) { + SWAP(float, canvas.xmin, canvas.xmax); + } + if (ps->draw_flip[1]) { + SWAP(float, canvas.ymin, canvas.ymax); + } + + immAttr2f(texCoord, canvas.xmin, canvas.ymin); + immVertex2f(pos, preview.xmin, preview.ymin); + + immAttr2f(texCoord, canvas.xmin, canvas.ymax); + immVertex2f(pos, preview.xmin, preview.ymax); + + immAttr2f(texCoord, canvas.xmax, canvas.ymax); + immVertex2f(pos, preview.xmax, preview.ymax); + + immAttr2f(texCoord, canvas.xmax, canvas.ymin); + immVertex2f(pos, preview.xmax, preview.ymin); + + immEnd(); + + GPU_texture_unbind(texture); + GPU_texture_free(texture); + + if (!glsl_used) { + immUnbindProgram(); + } + else { + IMB_colormanagement_finish_glsl_draw(); + } + + if (buffer_cache_handle) { + IMB_display_buffer_release(buffer_cache_handle); + } +} + static void playanim_toscreen( PlayState *ps, PlayAnimPict *picture, struct ImBuf *ibuf, int fontid, int fstep) { @@ -285,13 +553,6 @@ static void playanim_toscreen( printf("%s: no ibuf for picture '%s'\n", __func__, picture ? picture->name : "<NIL>"); return; } - if (ibuf->rect == NULL && ibuf->rect_float) { - IMB_rect_from_float(ibuf); - imb_freerectfloatImBuf(ibuf); - } - if (ibuf->rect == NULL) { - return; - } GHOST_ActivateWindowDrawingContext(g_WS.ghost_window); @@ -321,19 +582,7 @@ static void playanim_toscreen( 8); } - IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); - - immDrawPixelsTex(&state, - offs_x + (ps->draw_flip[0] ? span_x : 0.0f), - offs_y + (ps->draw_flip[1] ? span_y : 0.0f), - ibuf->x, - ibuf->y, - GPU_RGBA8, - false, - ibuf->rect, - ((ps->draw_flip[0] ? -1.0f : 1.0f)) * (ps->zoom / (float)ps->win_x), - ((ps->draw_flip[1] ? -1.0f : 1.0f)) * (ps->zoom / (float)ps->win_y), - NULL); + draw_display_buffer(ps, ibuf); GPU_blend(GPU_BLEND_NONE); @@ -413,6 +662,14 @@ static void build_pict_list_ex( } } else { + /* Load images into cache until the cache is full, + * this resolves choppiness for images that are slow to load, see: T81751. */ +#ifdef USE_FRAME_CACHE_LIMIT + bool fill_cache = true; +#else + bool fill_cache = false; +#endif + int count = 0; int fp_framenr; @@ -441,7 +698,7 @@ static void build_pict_list_ex( */ while (IMB_ispic(filepath) && totframes) { - bool hasevent; + bool has_event; size_t size; int file; @@ -499,22 +756,33 @@ static void build_pict_list_ex( pupdate_time(); - if (ptottime > 1.0) { + const bool display_imbuf = ptottime > 1.0; + + if (display_imbuf || fill_cache) { /* OCIO_TODO: support different input color space */ - struct ImBuf *ibuf; - if (picture->mem) { - ibuf = IMB_ibImageFromMemory( - picture->mem, picture->size, picture->IB_flags, NULL, picture->name); - } - else { - ibuf = IMB_loadiffname(picture->name, picture->IB_flags, NULL); - } + ImBuf *ibuf = ibuf_from_picture(picture); + if (ibuf) { - playanim_toscreen(ps, picture, ibuf, fontid, fstep); - IMB_freeImBuf(ibuf); + if (display_imbuf) { + playanim_toscreen(ps, picture, ibuf, fontid, fstep); + } +#ifdef USE_FRAME_CACHE_LIMIT + if (fill_cache) { + picture->ibuf = ibuf; + frame_cache_add(picture); + fill_cache = !frame_cache_limit_exceeded(); + } + else +#endif + { + IMB_freeImBuf(ibuf); + } + } + + if (display_imbuf) { + pupdate_time(); + ptottime = 0.0; } - pupdate_time(); - ptottime = 0.0; } /* create a new filepath each time */ @@ -522,7 +790,7 @@ static void build_pict_list_ex( BLI_path_sequence_encode( filepath, fp_decoded.head, fp_decoded.tail, fp_decoded.digits, fp_framenr); - while ((hasevent = GHOST_ProcessEvents(g_WS.ghost_system, 0))) { + while ((has_event = GHOST_ProcessEvents(g_WS.ghost_system, false))) { GHOST_DispatchEvents(g_WS.ghost_system); if (ps->loading == false) { return; @@ -622,16 +890,14 @@ static void change_frame(PlayState *ps) static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) { PlayState *ps = (PlayState *)ps_void; - GHOST_TEventType type = GHOST_GetEventType(evt); - int val; + const GHOST_TEventType type = GHOST_GetEventType(evt); + /* Convert ghost event into value keyboard or mouse. */ + const int val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown); // print_ps(ps); playanim_event_qual_update(); - /* convert ghost event into value keyboard or mouse */ - val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown); - /* first check if we're busy loading files */ if (ps->loading) { switch (type) { @@ -655,8 +921,8 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) return 1; } - if (ps->wait2 && ps->stopped) { - ps->stopped = false; + if (ps->wait2 && ps->stopped == false) { + ps->stopped = true; } if (ps->wait2) { @@ -815,9 +1081,9 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) case GHOST_kKeyNumpadSlash: if (val) { if (g_WS.qual & WS_QUAL_SHIFT) { - if (ps->curframe_ibuf) { + if (ps->picture && ps->picture->ibuf) { printf(" Name: %s | Speed: %.2f frames/s\n", - ps->curframe_ibuf->name, + ps->picture->ibuf->name, ps->fstep / swaptime); } } @@ -1054,7 +1320,8 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) playanim_gl_matrix(); ptottime = 0.0; - playanim_toscreen(ps, ps->picture, ps->curframe_ibuf, ps->fontid, ps->fstep); + playanim_toscreen( + ps, ps->picture, ps->picture ? ps->picture->ibuf : NULL, ps->fontid, ps->fstep); break; } @@ -1131,7 +1398,9 @@ static void playanim_window_zoom(PlayState *ps, const float zoom_offset) GHOST_SetClientSize(g_WS.ghost_window, sizex, sizey); } -/* return path for restart */ +/** + * \return The a path used to restart the animation player or NULL to exit. + */ static char *wm_main_playanim_intern(int argc, const char **argv) { struct ImBuf *ibuf = NULL; @@ -1150,7 +1419,6 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.direction = true; ps.next_frame = 1; ps.once = false; - ps.turbo = false; ps.pingpong = false; ps.noskip = false; ps.sstep = false; @@ -1168,6 +1436,11 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.fontid = -1; + STRNCPY(ps.display_settings.display_device, + IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE)); + IMB_colormanagement_init_default_view_settings(&ps.view_settings, &ps.display_settings); + + /* Skip the first argument which is assumed to be '-a' (used to launch this player). */ while (argc > 1) { if (argv[1][0] == '-') { switch (argv[1][1]) { @@ -1222,6 +1495,15 @@ static char *wm_main_playanim_intern(int argc, const char **argv) argc--; argv++; break; + case 'c': { +#ifdef USE_FRAME_CACHE_LIMIT + const int memory_in_mb = max_ii(0, atoi(argv[2])); + g_frame_cache.memory_limit = (size_t)memory_in_mb * (1024 * 1024); +#endif + argc--; + argv++; + break; + } default: printf("unknown option '%c': skipping\n", argv[1][1]); break; @@ -1389,60 +1671,29 @@ static char *wm_main_playanim_intern(int argc, const char **argv) #endif while (ps.picture) { - int hasevent; + bool has_event; #ifndef USE_IMB_CACHE if (ibuf != NULL && ibuf->ftype == IMB_FTYPE_NONE) { IMB_freeImBuf(ibuf); } #endif - if (ps.picture->ibuf) { - ibuf = ps.picture->ibuf; - } - else if (ps.picture->anim) { - ibuf = IMB_anim_absolute(ps.picture->anim, ps.picture->frame, IMB_TC_NONE, IMB_PROXY_NONE); - } - else if (ps.picture->mem) { - /* use correct colorspace here */ - ibuf = IMB_ibImageFromMemory( - ps.picture->mem, ps.picture->size, ps.picture->IB_flags, NULL, ps.picture->name); - } - else { - /* use correct colorspace here */ - ibuf = IMB_loadiffname(ps.picture->name, ps.picture->IB_flags, NULL); - } - if (ibuf) { -#ifdef USE_FRAME_CACHE_LIMIT - LinkData *node; -#endif + ibuf = ibuf_from_picture(ps.picture); + if (ibuf) { #ifdef USE_IMB_CACHE ps.picture->ibuf = ibuf; #endif #ifdef USE_FRAME_CACHE_LIMIT - /* Really basic memory conservation scheme. Keep frames in a FIFO queue. */ - node = inmempicsbase.last; - - while (node && added_images > PLAY_FRAME_CACHE_MAX) { - PlayAnimPict *pic = node->data; - - if (pic->ibuf && pic->ibuf != ibuf) { - LinkData *node_tmp; - IMB_freeImBuf(pic->ibuf); - pic->ibuf = NULL; - node_tmp = node->prev; - BLI_freelinkN(&inmempicsbase, node); - added_images--; - node = node_tmp; - } - else { - node = node->prev; - } + if (ps.picture->frame_cache_node == NULL) { + frame_cache_add(ps.picture); + } + else { + frame_cache_touch(ps.picture); } + frame_cache_limit_apply(ibuf); - BLI_addhead(&inmempicsbase, BLI_genericNodeN(ps.picture)); - added_images++; #endif /* USE_FRAME_CACHE_LIMIT */ BLI_strncpy(ibuf->name, ps.picture->name, sizeof(ibuf->name)); @@ -1474,14 +1725,14 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.next_frame = ps.direction; - while ((hasevent = GHOST_ProcessEvents(g_WS.ghost_system, 0))) { + while ((has_event = GHOST_ProcessEvents(g_WS.ghost_system, false))) { GHOST_DispatchEvents(g_WS.ghost_system); } if (ps.go == false) { break; } change_frame(&ps); - if (!hasevent) { + if (!has_event) { PIL_sleep_ms(1); } if (ps.wait2) { @@ -1490,14 +1741,15 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.wait2 = ps.sstep; - if (ps.wait2 == false && ps.stopped == false) { - ps.stopped = true; + if (ps.wait2 == false && ps.stopped) { + ps.stopped = false; } pupdate_time(); if (ps.picture && ps.next_frame) { - /* always at least set one step */ + /* Advance to the next frame, always at least set one step. + * Implement frame-skipping when enabled and playback is not fast enough. */ while (ps.picture) { ps.picture = playanim_step(ps.picture, ps.next_frame); @@ -1510,7 +1762,7 @@ static char *wm_main_playanim_intern(int argc, const char **argv) } } - if (ps.wait2 || ptottime < swaptime || ps.turbo || ps.noskip) { + if (ps.wait2 || ptottime < swaptime || ps.noskip) { break; } ptottime -= swaptime; @@ -1550,8 +1802,12 @@ static char *wm_main_playanim_intern(int argc, const char **argv) #endif BLI_freelistN(&picsbase); - BLI_freelistN(&inmempicsbase); - added_images = 0; + +#ifdef USE_FRAME_CACHE_LIMIT + BLI_freelistN(&g_frame_cache.pics); + g_frame_cache.pics_len = 0; + g_frame_cache.pics_size_in_memory = 0; +#endif #ifdef WITH_AUDASPACE if (playback_handle) { diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index eb1da15807c..6aedfb10dde 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -133,7 +133,7 @@ static struct WMInitStruct { * \{ */ static void wm_window_set_drawable(wmWindowManager *wm, wmWindow *win, bool activate); -static int wm_window_timer(const bContext *C); +static bool wm_window_timer(const bContext *C); /* XXX this one should correctly check for apple top header... * done for Cocoa : returns window contents (and not frame) max size*/ @@ -550,7 +550,7 @@ static void wm_window_ghostwindow_add(wmWindowManager *wm, } int scr_w, scr_h; - wm_get_screensize(&scr_w, &scr_h); + wm_get_desktopsize(&scr_w, &scr_h); int posy = (scr_h - win->posy - win->sizey); /* Clear drawable so we can set the new window. */ @@ -756,6 +756,7 @@ static bool wm_window_update_size_position(wmWindow *win) /** * \param space_type: SPACE_VIEW3D, SPACE_INFO, ... (eSpace_Type) + * \param toplevel: Not a child owned by other windows. A peer of main window. * \param dialog: whether this should be made as a dialog-style window * \param temp: whether this is considered a short-lived window * \param alignment: how this window is positioned relative to its parent @@ -768,6 +769,7 @@ wmWindow *WM_window_open(bContext *C, int sizex, int sizey, int space_type, + bool toplevel, bool dialog, bool temp, WindowAlignment alignment) @@ -812,7 +814,7 @@ wmWindow *WM_window_open(bContext *C, LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) { if (WM_window_is_temp_screen(win_iter)) { char *wintitle = GHOST_GetTitle(win_iter->ghostwin); - if (strcmp(title, wintitle) == 0) { + if (STREQ(title, wintitle)) { win = win_iter; } free(wintitle); @@ -822,7 +824,7 @@ wmWindow *WM_window_open(bContext *C, /* add new window? */ if (win == NULL) { - win = wm_window_new(bmain, wm, win_prev, dialog); + win = wm_window_new(bmain, wm, toplevel ? NULL : win_prev, dialog); win->posx = rect.xmin; win->posy = rect.ymin; *win->stereo3d_format = *win_prev->stereo3d_format; @@ -925,6 +927,7 @@ int wm_window_new_exec(bContext *C, wmOperator *UNUSED(op)) area->spacetype, false, false, + false, WIN_ALIGN_PARENT_CENTER) != NULL); return ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED; @@ -1498,12 +1501,12 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr * Timer handlers should check for delta to decide if they just update, or follow real time. * Timer handlers can also set duration to match frames passed */ -static int wm_window_timer(const bContext *C) +static bool wm_window_timer(const bContext *C) { Main *bmain = CTX_data_main(C); wmWindowManager *wm = CTX_wm_manager(C); double time = PIL_check_seconds_timer(); - int retval = 0; + bool has_event = false; /* Mutable in case the timer gets removed. */ LISTBASE_FOREACH_MUTABLE (wmTimer *, wt, &wm->timers) { @@ -1540,31 +1543,34 @@ static int wm_window_timer(const bContext *C) event.customdata = wt; wm_event_add(win, &event); - retval = 1; + has_event = true; } } } - return retval; + return has_event; } void wm_window_process_events(const bContext *C) { BLI_assert(BLI_thread_is_main()); - int hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */ + bool has_event = GHOST_ProcessEvents(g_system, false); /* `false` is no wait. */ - if (hasevent) { + if (has_event) { GHOST_DispatchEvents(g_system); } - hasevent |= wm_window_timer(C); + has_event |= wm_window_timer(C); #ifdef WITH_XR_OPENXR /* XR events don't use the regular window queues. So here we don't only trigger * processing/dispatching but also handling. */ - hasevent |= wm_xr_events_handle(CTX_wm_manager(C)); + has_event |= wm_xr_events_handle(CTX_wm_manager(C)); #endif - /* no event, we sleep 5 milliseconds */ - if (hasevent == 0) { + /* When there is no event, sleep 5 milliseconds not to use too much CPU when idle. + * + * Skip sleeping when simulating events so tests don't idle unnecessarily as simulated + * events are typically generated from a timer that runs in the main loop. */ + if ((has_event == false) && !(G.f & G_FLAG_EVENT_SIMULATE)) { PIL_sleep_ms(5); } } diff --git a/source/blender/windowmanager/wm.h b/source/blender/windowmanager/wm.h index 7fddf60eb97..af696ddd8f9 100644 --- a/source/blender/windowmanager/wm.h +++ b/source/blender/windowmanager/wm.h @@ -23,7 +23,6 @@ #pragma once -struct ReportList; struct wmWindow; #include "gizmo/wm_gizmo_wmapi.h" diff --git a/source/blender/windowmanager/wm_files.h b/source/blender/windowmanager/wm_files.h index d54090a6025..c7fe07cad7f 100644 --- a/source/blender/windowmanager/wm_files.h +++ b/source/blender/windowmanager/wm_files.h @@ -45,6 +45,9 @@ void wm_homefile_read(struct bContext *C, void wm_file_read_report(bContext *C, struct Main *bmain); void wm_close_file_dialog(bContext *C, struct wmGenericCallback *post_action); +bool wm_operator_close_file_dialog_if_needed(bContext *C, + wmOperator *op, + wmGenericCallbackFn exec_fn); bool wm_file_or_image_is_modified(const Main *bmain, const wmWindowManager *wm); void WM_OT_save_homefile(struct wmOperatorType *ot); diff --git a/source/blender/windowmanager/xr/intern/wm_xr.c b/source/blender/windowmanager/xr/intern/wm_xr.c index 439d611b085..2a67c2bee9f 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr.c +++ b/source/blender/windowmanager/xr/intern/wm_xr.c @@ -128,6 +128,11 @@ bool wm_xr_events_handle(wmWindowManager *wm) if (wm->xr.runtime && wm->xr.runtime->context) { GHOST_XrEventsHandle(wm->xr.runtime->context); + /* Process OpenXR action events. */ + if (WM_xr_session_is_ready(&wm->xr)) { + wm_xr_session_actions_update(&wm->xr); + } + /* wm_window_process_events() uses the return value to determine if it can put the main thread * to sleep for some milliseconds. We never want that to happen while the VR session runs on * the main thread. So always return true. */ diff --git a/source/blender/windowmanager/xr/intern/wm_xr_actions.c b/source/blender/windowmanager/xr/intern/wm_xr_actions.c new file mode 100644 index 00000000000..51ed3dcfd3c --- /dev/null +++ b/source/blender/windowmanager/xr/intern/wm_xr_actions.c @@ -0,0 +1,480 @@ +/* + * 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. + */ + +/** \file + * \ingroup wm + * + * \name Window-Manager XR Actions + * + * Uses the Ghost-XR API to manage OpenXR actions. + * All functions are designed to be usable by RNA / the Python API. + */ + +#include "BLI_math.h" + +#include "GHOST_C-api.h" + +#include "MEM_guardedalloc.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "wm_xr_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name XR-Action API + * + * API functions for managing OpenXR actions. + * + * \{ */ + +static wmXrActionSet *action_set_create(const char *action_set_name) +{ + wmXrActionSet *action_set = MEM_callocN(sizeof(*action_set), __func__); + action_set->name = MEM_mallocN(strlen(action_set_name) + 1, "XrActionSet_Name"); + strcpy(action_set->name, action_set_name); + + return action_set; +} + +static void action_set_destroy(void *val) +{ + wmXrActionSet *action_set = val; + + MEM_SAFE_FREE(action_set->name); + + MEM_freeN(action_set); +} + +static wmXrActionSet *action_set_find(wmXrData *xr, const char *action_set_name) +{ + return GHOST_XrGetActionSetCustomdata(xr->runtime->context, action_set_name); +} + +static wmXrAction *action_create(const char *action_name, + eXrActionType type, + unsigned int count_subaction_paths, + const char **subaction_paths, + const float *float_threshold, + wmOperatorType *ot, + IDProperty *op_properties, + eXrOpFlag op_flag) +{ + wmXrAction *action = MEM_callocN(sizeof(*action), __func__); + action->name = MEM_mallocN(strlen(action_name) + 1, "XrAction_Name"); + strcpy(action->name, action_name); + action->type = type; + + const unsigned int count = count_subaction_paths; + action->count_subaction_paths = count; + + action->subaction_paths = MEM_mallocN(sizeof(*action->subaction_paths) * count, + "XrAction_SubactionPaths"); + for (unsigned int i = 0; i < count; ++i) { + action->subaction_paths[i] = MEM_mallocN(strlen(subaction_paths[i]) + 1, + "XrAction_SubactionPath"); + strcpy(action->subaction_paths[i], subaction_paths[i]); + } + + size_t size; + switch (type) { + case XR_BOOLEAN_INPUT: + size = sizeof(bool); + break; + case XR_FLOAT_INPUT: + size = sizeof(float); + break; + case XR_VECTOR2F_INPUT: + size = sizeof(float) * 2; + break; + case XR_POSE_INPUT: + size = sizeof(GHOST_XrPose); + break; + case XR_VIBRATION_OUTPUT: + return action; + } + action->states = MEM_calloc_arrayN(count, size, "XrAction_States"); + action->states_prev = MEM_calloc_arrayN(count, size, "XrAction_StatesPrev"); + + if (float_threshold) { + BLI_assert(type == XR_FLOAT_INPUT || type == XR_VECTOR2F_INPUT); + action->float_threshold = *float_threshold; + CLAMP(action->float_threshold, 0.0f, 1.0f); + } + + action->ot = ot; + action->op_properties = op_properties; + action->op_flag = op_flag; + + return action; +} + +static void action_destroy(void *val) +{ + wmXrAction *action = val; + + MEM_SAFE_FREE(action->name); + + const unsigned int count = action->count_subaction_paths; + char **subaction_paths = action->subaction_paths; + if (subaction_paths) { + for (unsigned int i = 0; i < count; ++i) { + MEM_SAFE_FREE(subaction_paths[i]); + } + MEM_freeN(subaction_paths); + } + + MEM_SAFE_FREE(action->states); + MEM_SAFE_FREE(action->states_prev); + + MEM_freeN(action); +} + +static wmXrAction *action_find(wmXrData *xr, const char *action_set_name, const char *action_name) +{ + return GHOST_XrGetActionCustomdata(xr->runtime->context, action_set_name, action_name); +} + +bool WM_xr_action_set_create(wmXrData *xr, const char *action_set_name) +{ + if (action_set_find(xr, action_set_name)) { + return false; + } + + wmXrActionSet *action_set = action_set_create(action_set_name); + + GHOST_XrActionSetInfo info = { + .name = action_set_name, + .customdata_free_fn = action_set_destroy, + .customdata = action_set, + }; + + if (!GHOST_XrCreateActionSet(xr->runtime->context, &info)) { + return false; + } + + return true; +} + +void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name) +{ + wmXrActionSet *action_set = action_set_find(xr, action_set_name); + if (!action_set) { + return; + } + + wmXrSessionState *session_state = &xr->runtime->session_state; + + if (action_set == session_state->active_action_set) { + if (action_set->controller_pose_action) { + wm_xr_session_controller_data_clear(session_state); + action_set->controller_pose_action = NULL; + } + if (action_set->active_modal_action) { + action_set->active_modal_action = NULL; + } + session_state->active_action_set = NULL; + } + + GHOST_XrDestroyActionSet(xr->runtime->context, action_set_name); +} + +bool WM_xr_action_create(wmXrData *xr, + const char *action_set_name, + const char *action_name, + eXrActionType type, + unsigned int count_subaction_paths, + const char **subaction_paths, + const float *float_threshold, + wmOperatorType *ot, + IDProperty *op_properties, + eXrOpFlag op_flag) +{ + if (action_find(xr, action_set_name, action_name)) { + return false; + } + + wmXrAction *action = action_create(action_name, + type, + count_subaction_paths, + subaction_paths, + float_threshold, + ot, + op_properties, + op_flag); + + GHOST_XrActionInfo info = { + .name = action_name, + .count_subaction_paths = count_subaction_paths, + .subaction_paths = subaction_paths, + .states = action->states, + .customdata_free_fn = action_destroy, + .customdata = action, + }; + + switch (type) { + case XR_BOOLEAN_INPUT: + info.type = GHOST_kXrActionTypeBooleanInput; + break; + case XR_FLOAT_INPUT: + info.type = GHOST_kXrActionTypeFloatInput; + break; + case XR_VECTOR2F_INPUT: + info.type = GHOST_kXrActionTypeVector2fInput; + break; + case XR_POSE_INPUT: + info.type = GHOST_kXrActionTypePoseInput; + break; + case XR_VIBRATION_OUTPUT: + info.type = GHOST_kXrActionTypeVibrationOutput; + break; + } + + if (!GHOST_XrCreateActions(xr->runtime->context, action_set_name, 1, &info)) { + return false; + } + + return true; +} + +void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name) +{ + wmXrActionSet *action_set = action_set_find(xr, action_set_name); + if (!action_set) { + return; + } + + if (action_set->controller_pose_action && + STREQ(action_set->controller_pose_action->name, action_name)) { + if (action_set == xr->runtime->session_state.active_action_set) { + wm_xr_session_controller_data_clear(&xr->runtime->session_state); + } + action_set->controller_pose_action = NULL; + } + if (action_set->active_modal_action && + STREQ(action_set->active_modal_action->name, action_name)) { + action_set->active_modal_action = NULL; + } + + wmXrAction *action = action_find(xr, action_set_name, action_name); + if (!action) { + return; + } +} + +bool WM_xr_action_space_create(wmXrData *xr, + const char *action_set_name, + const char *action_name, + unsigned int count_subaction_paths, + const char **subaction_paths, + const wmXrPose *poses) +{ + GHOST_XrActionSpaceInfo info = { + .action_name = action_name, + .count_subaction_paths = count_subaction_paths, + .subaction_paths = subaction_paths, + }; + + GHOST_XrPose *ghost_poses = MEM_malloc_arrayN( + count_subaction_paths, sizeof(*ghost_poses), __func__); + for (unsigned int i = 0; i < count_subaction_paths; ++i) { + const wmXrPose *pose = &poses[i]; + GHOST_XrPose *ghost_pose = &ghost_poses[i]; + copy_v3_v3(ghost_pose->position, pose->position); + copy_qt_qt(ghost_pose->orientation_quat, pose->orientation_quat); + } + info.poses = ghost_poses; + + bool ret = GHOST_XrCreateActionSpaces(xr->runtime->context, action_set_name, 1, &info) ? true : + false; + MEM_freeN(ghost_poses); + return ret; +} + +void WM_xr_action_space_destroy(wmXrData *xr, + const char *action_set_name, + const char *action_name, + unsigned int count_subaction_paths, + const char **subaction_paths) +{ + GHOST_XrActionSpaceInfo info = { + .action_name = action_name, + .count_subaction_paths = count_subaction_paths, + .subaction_paths = subaction_paths, + }; + + GHOST_XrDestroyActionSpaces(xr->runtime->context, action_set_name, 1, &info); +} + +bool WM_xr_action_binding_create(wmXrData *xr, + const char *action_set_name, + const char *profile_path, + const char *action_name, + unsigned int count_interaction_paths, + const char **interaction_paths) +{ + GHOST_XrActionBindingInfo binding_info = { + .action_name = action_name, + .count_interaction_paths = count_interaction_paths, + .interaction_paths = interaction_paths, + }; + + GHOST_XrActionProfileInfo profile_info = { + .profile_path = profile_path, + .count_bindings = 1, + .bindings = &binding_info, + }; + + return GHOST_XrCreateActionBindings(xr->runtime->context, action_set_name, 1, &profile_info); +} + +void WM_xr_action_binding_destroy(wmXrData *xr, + const char *action_set_name, + const char *profile_path, + const char *action_name, + unsigned int count_interaction_paths, + const char **interaction_paths) +{ + GHOST_XrActionBindingInfo binding_info = { + .action_name = action_name, + .count_interaction_paths = count_interaction_paths, + .interaction_paths = interaction_paths, + }; + + GHOST_XrActionProfileInfo profile_info = { + .profile_path = profile_path, + .count_bindings = 1, + .bindings = &binding_info, + }; + + GHOST_XrDestroyActionBindings(xr->runtime->context, action_set_name, 1, &profile_info); +} + +bool WM_xr_active_action_set_set(wmXrData *xr, const char *action_set_name) +{ + wmXrActionSet *action_set = action_set_find(xr, action_set_name); + if (!action_set) { + return false; + } + + { + /* Unset active modal action (if any). */ + wmXrActionSet *active_action_set = xr->runtime->session_state.active_action_set; + if (active_action_set) { + wmXrAction *active_modal_action = active_action_set->active_modal_action; + if (active_modal_action) { + if (active_modal_action->active_modal_path) { + active_modal_action->active_modal_path = NULL; + } + active_action_set->active_modal_action = NULL; + } + } + } + + xr->runtime->session_state.active_action_set = action_set; + + if (action_set->controller_pose_action) { + wm_xr_session_controller_data_populate(action_set->controller_pose_action, xr); + } + + return true; +} + +bool WM_xr_controller_pose_action_set(wmXrData *xr, + const char *action_set_name, + const char *action_name) +{ + wmXrActionSet *action_set = action_set_find(xr, action_set_name); + if (!action_set) { + return false; + } + + wmXrAction *action = action_find(xr, action_set_name, action_name); + if (!action) { + return false; + } + + action_set->controller_pose_action = action; + + if (action_set == xr->runtime->session_state.active_action_set) { + wm_xr_session_controller_data_populate(action, xr); + } + + return true; +} + +bool WM_xr_action_state_get(const wmXrData *xr, + const char *action_set_name, + const char *action_name, + const char *subaction_path, + wmXrActionState *r_state) +{ + const wmXrAction *action = action_find((wmXrData *)xr, action_set_name, action_name); + if (!action) { + return false; + } + + BLI_assert(action->type == (eXrActionType)r_state->type); + + /* Find the action state corresponding to the subaction path. */ + for (unsigned int i = 0; i < action->count_subaction_paths; ++i) { + if (STREQ(subaction_path, action->subaction_paths[i])) { + switch ((eXrActionType)r_state->type) { + case XR_BOOLEAN_INPUT: + r_state->state_boolean = ((bool *)action->states)[i]; + break; + case XR_FLOAT_INPUT: + r_state->state_float = ((float *)action->states)[i]; + break; + case XR_VECTOR2F_INPUT: + copy_v2_v2(r_state->state_vector2f, ((float(*)[2])action->states)[i]); + break; + case XR_POSE_INPUT: { + const GHOST_XrPose *pose = &((GHOST_XrPose *)action->states)[i]; + copy_v3_v3(r_state->state_pose.position, pose->position); + copy_qt_qt(r_state->state_pose.orientation_quat, pose->orientation_quat); + break; + } + case XR_VIBRATION_OUTPUT: + BLI_assert_unreachable(); + break; + } + return true; + } + } + + return false; +} + +bool WM_xr_haptic_action_apply(wmXrData *xr, + const char *action_set_name, + const char *action_name, + const long long *duration, + const float *frequency, + const float *amplitude) +{ + return GHOST_XrApplyHapticAction( + xr->runtime->context, action_set_name, action_name, duration, frequency, amplitude) ? + true : + false; +} + +void WM_xr_haptic_action_stop(wmXrData *xr, const char *action_set_name, const char *action_name) +{ + GHOST_XrStopHapticAction(xr->runtime->context, action_set_name, action_name); +} + +/** \} */ /* XR-Action API */ diff --git a/source/blender/windowmanager/xr/intern/wm_xr_draw.c b/source/blender/windowmanager/xr/intern/wm_xr_draw.c index cc4a7e41e82..1f722855696 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_draw.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_draw.c @@ -45,6 +45,12 @@ void wm_xr_pose_to_viewmat(const GHOST_XrPose *pose, float r_viewmat[4][4]) translate_m4(r_viewmat, -pose->position[0], -pose->position[1], -pose->position[2]); } +void wm_xr_controller_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]) +{ + quat_to_mat4(r_mat, pose->orientation_quat); + copy_v3_v3(r_mat[3], pose->position); +} + static void wm_xr_draw_matrices_create(const wmXrDrawData *draw_data, const GHOST_XrDrawViewInfo *draw_view, const XrSessionSettings *session_settings, diff --git a/source/blender/windowmanager/xr/intern/wm_xr_intern.h b/source/blender/windowmanager/xr/intern/wm_xr_intern.h index 25e3da3ffb4..9bf63be61dd 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_intern.h +++ b/source/blender/windowmanager/xr/intern/wm_xr_intern.h @@ -24,6 +24,21 @@ #include "wm_xr.h" +struct wmXrActionSet; + +typedef struct wmXrControllerData { + /** OpenXR path identifier. Length is dependent on OpenXR's XR_MAX_PATH_LENGTH (256). + This subaction path will later be combined with a component path, and that combined path should + also have a max of XR_MAX_PATH_LENGTH (e.g. subaction_path = /user/hand/left, component_path = + /input/trigger/value, interaction_path = /user/hand/left/input/trigger/value). + */ + char subaction_path[64]; + /** Last known controller pose (in world space) stored for queries. */ + GHOST_XrPose pose; + /** The last known controller matrix, calculated from above's controller pose. */ + float mat[4][4]; +} wmXrControllerData; + typedef struct wmXrSessionState { bool is_started; @@ -39,11 +54,23 @@ typedef struct wmXrSessionState { Object *prev_base_pose_object; /** Copy of XrSessionSettings.flag created on the last draw call, stored to detect changes. */ int prev_settings_flag; + /** Copy of wmXrDrawData.base_pose. */ + GHOST_XrPose prev_base_pose; + /** Copy of GHOST_XrDrawViewInfo.local_pose. */ + GHOST_XrPose prev_local_pose; /** Copy of wmXrDrawData.eye_position_ofs. */ float prev_eye_position_ofs[3]; bool force_reset_to_base_pose; bool is_view_data_set; + + /** Last known controller data. */ + wmXrControllerData controllers[2]; + + /** The currently active action set that will be updated on calls to + * wm_xr_session_actions_update(). If NULL, all action sets will be treated as active and + * updated. */ + struct wmXrActionSet *active_action_set; } wmXrSessionState; typedef struct wmXrRuntimeData { @@ -79,6 +106,40 @@ typedef struct wmXrDrawData { float eye_position_ofs[3]; /* Local/view space. */ } wmXrDrawData; +typedef struct wmXrAction { + char *name; + eXrActionType type; + unsigned int count_subaction_paths; + char **subaction_paths; + /** States for each subaction path. */ + void *states; + /** Previous states, stored to determine XR events. */ + void *states_prev; + + /** Input threshold for float/vector2f actions. */ + float float_threshold; + + /** The currently active subaction path (if any) for modal actions. */ + char **active_modal_path; + + /** Operator to be called on XR events. */ + struct wmOperatorType *ot; + IDProperty *op_properties; + eXrOpFlag op_flag; +} wmXrAction; + +typedef struct wmXrActionSet { + char *name; + + /** The XR pose action that determines the controller + * transforms. This is usually identified by the OpenXR path "/grip/pose" or "/aim/pose", + * although it could differ depending on the specification and hardware. */ + wmXrAction *controller_pose_action; + + /** The currently active modal action (if any). */ + wmXrAction *active_modal_action; +} wmXrActionSet; + wmXrRuntimeData *wm_xr_runtime_data_create(void); void wm_xr_runtime_data_free(wmXrRuntimeData **runtime); @@ -95,5 +156,12 @@ bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data, void *wm_xr_session_gpu_binding_context_create(void); void wm_xr_session_gpu_binding_context_destroy(GHOST_ContextHandle context); +void wm_xr_session_actions_init(wmXrData *xr); +void wm_xr_session_actions_update(wmXrData *xr); +void wm_xr_session_controller_data_populate(const wmXrAction *controller_pose_action, + wmXrData *xr); +void wm_xr_session_controller_data_clear(wmXrSessionState *state); + void wm_xr_pose_to_viewmat(const GHOST_XrPose *pose, float r_viewmat[4][4]); +void wm_xr_controller_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]); void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata); diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c index b9ef40e3398..1ddbe228e05 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_session.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c @@ -18,7 +18,9 @@ * \ingroup wm */ +#include "BKE_callbacks.h" #include "BKE_context.h" +#include "BKE_global.h" #include "BKE_main.h" #include "BKE_scene.h" @@ -49,11 +51,24 @@ static CLG_LogRef LOG = {"wm.xr"}; /* -------------------------------------------------------------------- */ +static void wm_xr_session_create_cb(void) +{ + Main *bmain = G_MAIN; + wmWindowManager *wm = bmain->wm.first; + wmXrData *xr_data = &wm->xr; + + /* Get action set data from Python. */ + BKE_callback_exec_null(bmain, BKE_CB_EVT_XR_SESSION_START_PRE); + + wm_xr_session_actions_init(xr_data); +} + static void wm_xr_session_exit_cb(void *customdata) { wmXrData *xr_data = customdata; xr_data->runtime->session_state.is_started = false; + if (xr_data->runtime->exit_fn) { xr_data->runtime->exit_fn(xr_data); } @@ -65,6 +80,10 @@ static void wm_xr_session_exit_cb(void *customdata) static void wm_xr_session_begin_info_create(wmXrData *xr_data, GHOST_XrSessionBeginInfo *r_begin_info) { + /* Callback for when the session is created. This is needed to create and bind OpenXR actions + * after the session is created but before it is started. */ + r_begin_info->create_fn = wm_xr_session_create_cb; + /* WM-XR exit function, does some own stuff and calls callback passed to wm_xr_session_toggle(), * to allow external code to execute its own session-exit logic. */ r_begin_info->exit_fn = wm_xr_session_exit_cb; @@ -289,6 +308,7 @@ void wm_xr_session_draw_data_update(const wmXrSessionState *state, /** * Update information that is only stored for external state queries. E.g. for Python API to * request the current (as in, last known) viewer pose. + * Controller data and action sets will be updated separately via wm_xr_session_actions_update(). */ void wm_xr_session_state_update(const XrSessionSettings *settings, const wmXrDrawData *draw_data, @@ -322,6 +342,8 @@ void wm_xr_session_state_update(const XrSessionSettings *settings, DEFAULT_SENSOR_WIDTH); copy_v3_v3(state->prev_eye_position_ofs, draw_data->eye_position_ofs); + memcpy(&state->prev_base_pose, &draw_data->base_pose, sizeof(state->prev_base_pose)); + memcpy(&state->prev_local_pose, &draw_view->local_pose, sizeof(state->prev_local_pose)); state->prev_settings_flag = settings->flag; state->prev_base_pose_type = settings->base_pose_type; state->prev_base_pose_object = settings->base_pose_object; @@ -373,6 +395,132 @@ bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr, return true; } +bool WM_xr_session_state_controller_pose_location_get(const wmXrData *xr, + unsigned int subaction_idx, + float r_location[3]) +{ + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set || + subaction_idx >= ARRAY_SIZE(xr->runtime->session_state.controllers)) { + zero_v3(r_location); + return false; + } + + copy_v3_v3(r_location, xr->runtime->session_state.controllers[subaction_idx].pose.position); + return true; +} + +bool WM_xr_session_state_controller_pose_rotation_get(const wmXrData *xr, + unsigned int subaction_idx, + float r_rotation[4]) +{ + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set || + subaction_idx >= ARRAY_SIZE(xr->runtime->session_state.controllers)) { + unit_qt(r_rotation); + return false; + } + + copy_v4_v4(r_rotation, + xr->runtime->session_state.controllers[subaction_idx].pose.orientation_quat); + return true; +} + +/* -------------------------------------------------------------------- */ +/** \name XR-Session Actions + * + * XR action processing and event dispatching. + * + * \{ */ + +void wm_xr_session_actions_init(wmXrData *xr) +{ + if (!xr->runtime) { + return; + } + + GHOST_XrAttachActionSets(xr->runtime->context); +} + +static void wm_xr_session_controller_mats_update(const XrSessionSettings *settings, + const wmXrAction *controller_pose_action, + wmXrSessionState *state) +{ + const unsigned int count = (unsigned int)min_ii( + (int)controller_pose_action->count_subaction_paths, (int)ARRAY_SIZE(state->controllers)); + + float view_ofs[3]; + float base_inv[4][4]; + float tmp[4][4]; + + zero_v3(view_ofs); + if ((settings->flag & XR_SESSION_USE_POSITION_TRACKING) == 0) { + add_v3_v3(view_ofs, state->prev_local_pose.position); + } + + wm_xr_pose_to_viewmat(&state->prev_base_pose, base_inv); + invert_m4(base_inv); + + for (unsigned int i = 0; i < count; ++i) { + wmXrControllerData *controller = &state->controllers[i]; + + /* Calculate controller matrix in world space. */ + wm_xr_controller_pose_to_mat(&((GHOST_XrPose *)controller_pose_action->states)[i], tmp); + + /* Apply eye position and base pose offsets. */ + sub_v3_v3(tmp[3], view_ofs); + mul_m4_m4m4(controller->mat, base_inv, tmp); + + /* Save final pose. */ + mat4_to_loc_quat( + controller->pose.position, controller->pose.orientation_quat, controller->mat); + } +} + +void wm_xr_session_actions_update(wmXrData *xr) +{ + if (!xr->runtime) { + return; + } + + GHOST_XrContextHandle xr_context = xr->runtime->context; + wmXrSessionState *state = &xr->runtime->session_state; + wmXrActionSet *active_action_set = state->active_action_set; + + int ret = GHOST_XrSyncActions(xr_context, active_action_set ? active_action_set->name : NULL); + if (!ret) { + return; + } + + /* Only update controller mats for active action set. */ + if (active_action_set) { + if (active_action_set->controller_pose_action) { + wm_xr_session_controller_mats_update( + &xr->session_settings, active_action_set->controller_pose_action, state); + } + } +} + +void wm_xr_session_controller_data_populate(const wmXrAction *controller_pose_action, wmXrData *xr) +{ + wmXrSessionState *state = &xr->runtime->session_state; + + const unsigned int count = (unsigned int)min_ii( + (int)ARRAY_SIZE(state->controllers), (int)controller_pose_action->count_subaction_paths); + + for (unsigned int i = 0; i < count; ++i) { + wmXrControllerData *c = &state->controllers[i]; + strcpy(c->subaction_path, controller_pose_action->subaction_paths[i]); + memset(&c->pose, 0, sizeof(c->pose)); + zero_m4(c->mat); + } +} + +void wm_xr_session_controller_data_clear(wmXrSessionState *state) +{ + memset(state->controllers, 0, sizeof(state->controllers)); +} + +/** \} */ /* XR-Session Actions */ + /* -------------------------------------------------------------------- */ /** \name XR-Session Surface * |