Merge branch 'master' into blender2.8
authorCampbell Barton <ideasman42@gmail.com>
Wed, 14 Nov 2018 06:12:52 +0000 (17:12 +1100)
committerCampbell Barton <ideasman42@gmail.com>
Wed, 14 Nov 2018 06:21:34 +0000 (17:21 +1100)
131 files changed:
1  2 
intern/ghost/intern/GHOST_SystemCocoa.h
intern/ghost/intern/GHOST_SystemX11.h
source/blender/blenkernel/BKE_action.h
source/blender/blenkernel/BKE_animsys.h
source/blender/blenkernel/BKE_dynamicpaint.h
source/blender/blenkernel/BKE_fcurve.h
source/blender/blenkernel/BKE_shrinkwrap.h
source/blender/blenkernel/intern/action.c
source/blender/blenkernel/intern/anim.c
source/blender/blenkernel/intern/anim_sys.c
source/blender/blenkernel/intern/armature.c
source/blender/blenkernel/intern/armature_update.c
source/blender/blenkernel/intern/boids.c
source/blender/blenkernel/intern/cloth.c
source/blender/blenkernel/intern/constraint.c
source/blender/blenkernel/intern/dynamicpaint.c
source/blender/blenkernel/intern/fcurve.c
source/blender/blenkernel/intern/fmodifier.c
source/blender/blenkernel/intern/gpencil.c
source/blender/blenkernel/intern/ipo.c
source/blender/blenkernel/intern/lamp.c
source/blender/blenkernel/intern/material.c
source/blender/blenkernel/intern/mball_tessellate.c
source/blender/blenkernel/intern/nla.c
source/blender/blenkernel/intern/object_dupli.c
source/blender/blenkernel/intern/particle_system.c
source/blender/blenkernel/intern/rigidbody.c
source/blender/blenkernel/intern/seqeffects.c
source/blender/blenkernel/intern/shrinkwrap.c
source/blender/blenkernel/intern/smoke.c
source/blender/blenkernel/intern/softbody.c
source/blender/blenkernel/intern/text.c
source/blender/blenkernel/intern/texture.c
source/blender/blenkernel/intern/world.c
source/blender/blenlib/intern/BLI_kdopbvh.c
source/blender/blenlib/intern/math_matrix.c
source/blender/blenlib/intern/math_rotation.c
source/blender/blenloader/intern/readfile.c
source/blender/bmesh/intern/bmesh_mesh.c
source/blender/bmesh/intern/bmesh_query.c
source/blender/collada/AnimationExporter.cpp
source/blender/compositor/nodes/COM_ImageNode.cpp
source/blender/compositor/nodes/COM_RenderLayersNode.cpp
source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cpp
source/blender/compositor/operations/COM_OutputFileOperation.cpp
source/blender/compositor/operations/COM_RenderLayersProg.cpp
source/blender/compositor/operations/COM_ScreenLensDistortionOperation.cpp
source/blender/compositor/operations/COM_ScreenLensDistortionOperation.h
source/blender/editors/animation/anim_channels_defines.c
source/blender/editors/animation/anim_channels_edit.c
source/blender/editors/animation/anim_deps.c
source/blender/editors/animation/anim_draw.c
source/blender/editors/animation/anim_filter.c
source/blender/editors/animation/anim_intern.h
source/blender/editors/animation/anim_ops.c
source/blender/editors/animation/drivers.c
source/blender/editors/animation/keyframes_draw.c
source/blender/editors/animation/keyframes_edit.c
source/blender/editors/animation/keyframes_general.c
source/blender/editors/animation/keyframing.c
source/blender/editors/animation/keyingsets.c
source/blender/editors/armature/armature_add.c
source/blender/editors/armature/armature_edit.c
source/blender/editors/armature/armature_relations.c
source/blender/editors/armature/pose_group.c
source/blender/editors/armature/pose_lib.c
source/blender/editors/armature/pose_select.c
source/blender/editors/armature/pose_slide.c
source/blender/editors/armature/pose_transform.c
source/blender/editors/armature/pose_utils.c
source/blender/editors/gpencil/annotate_draw.c
source/blender/editors/gpencil/annotate_paint.c
source/blender/editors/gpencil/drawgpencil.c
source/blender/editors/gpencil/editaction_gpencil.c
source/blender/editors/gpencil/gpencil_brush.c
source/blender/editors/gpencil/gpencil_convert.c
source/blender/editors/gpencil/gpencil_fill.c
source/blender/editors/gpencil/gpencil_paint.c
source/blender/editors/gpencil/gpencil_select.c
source/blender/editors/gpencil/gpencil_undo.c
source/blender/editors/include/ED_anim_api.h
source/blender/editors/include/ED_armature.h
source/blender/editors/include/ED_keyframing.h
source/blender/editors/include/UI_view2d.h
source/blender/editors/interface/interface_ops.c
source/blender/editors/interface/interface_panel.c
source/blender/editors/interface/interface_templates.c
source/blender/editors/interface/view2d.c
source/blender/editors/interface/view2d_ops.c
source/blender/editors/mesh/editmesh_tools.c
source/blender/editors/mesh/meshtools.c
source/blender/editors/object/object_constraint.c
source/blender/editors/space_action/action_draw.c
source/blender/editors/space_action/action_edit.c
source/blender/editors/space_action/action_select.c
source/blender/editors/space_graph/graph_buttons.c
source/blender/editors/space_graph/graph_draw.c
source/blender/editors/space_graph/graph_edit.c
source/blender/editors/space_graph/graph_ops.c
source/blender/editors/space_graph/graph_select.c
source/blender/editors/space_graph/graph_utils.c
source/blender/editors/space_graph/space_graph.c
source/blender/editors/space_nla/nla_buttons.c
source/blender/editors/space_nla/nla_channels.c
source/blender/editors/space_nla/nla_draw.c
source/blender/editors/space_nla/nla_edit.c
source/blender/editors/space_nla/nla_ops.c
source/blender/editors/space_nla/nla_select.c
source/blender/editors/space_outliner/outliner_draw.c
source/blender/editors/space_outliner/outliner_edit.c
source/blender/editors/space_outliner/outliner_tree.c
source/blender/editors/space_sequencer/sequencer_add.c
source/blender/editors/transform/transform.c
source/blender/editors/transform/transform_conversions.c
source/blender/editors/transform/transform_generics.c
source/blender/gpu/GPU_texture.h
source/blender/gpu/intern/gpu_codegen.h
source/blender/makesdna/DNA_action_types.h
source/blender/makesdna/DNA_anim_types.h
source/blender/makesdna/DNA_constraint_types.h
source/blender/makesdna/DNA_gpencil_types.h
source/blender/makesdna/intern/makesdna.c
source/blender/makesrna/intern/makesrna.c
source/blender/makesrna/intern/rna_color.c
source/blender/makesrna/intern/rna_dynamicpaint.c
source/blender/makesrna/intern/rna_fcurve.c
source/blender/makesrna/intern/rna_key.c
source/blender/makesrna/intern/rna_nla.c
source/blender/makesrna/intern/rna_scene.c
source/blender/makesrna/intern/rna_space.c
source/blender/modifiers/intern/MOD_mask.c

Simple merge
   *  \ingroup bke
   */
  
 -struct EvaluationContext;
 +struct Depsgraph;
 +struct DynamicPaintCanvasSettings;
 +struct DynamicPaintModifierData;
  struct Main;
  struct Scene;
 +struct ViewLayer;
  
--/* Actual surface point       */
++/* Actual surface point */
  typedef struct PaintSurfaceData {
        void *format_data; /* special data for each surface "format" */
        void *type_data; /* data used by specific surface type */
@@@ -45,7 -42,7 +45,7 @@@
  
  } PaintSurfaceData;
  
--/* Paint type surface point   */
++/* Paint type surface point */
  typedef struct PaintPoint {
  
        /* Wet paint is handled at effect layer only
@@@ -56,7 -53,7 +56,7 @@@
        float color[4];
  } PaintPoint;
  
--/* height field waves */
++/* height field waves */
  typedef struct PaintWavePoint {
  
        float height;
@@@ -347,34 -367,25 +347,34 @@@ static void motionpaths_calc_bake_targe
        /* for each target, check if it can be baked on the current frame */
        for (mpt = targets->first; mpt; mpt = mpt->next) {
                bMotionPath *mpath = mpt->mpath;
 -              bMotionPathVert *mpv;
  
                /* current frame must be within the range the cache works for
-                *      - is inclusive of the first frame, but not the last otherwise we get buffer overruns
+                * - is inclusive of the first frame, but not the last otherwise we get buffer overruns
                 */
 -              if ((CFRA < mpath->start_frame) || (CFRA >= mpath->end_frame))
 +              if ((cframe < mpath->start_frame) || (cframe >= mpath->end_frame)) {
                        continue;
 +              }
  
                /* get the relevant cache vert to write to */
 -              mpv = mpath->points + (CFRA - mpath->start_frame);
 +              bMotionPathVert *mpv = mpath->points + (cframe - mpath->start_frame);
  
 -              /* pose-channel or object path baking? */
 +              Object *ob_eval = mpt->ob_eval;
 +
 +              /* Lookup evaluated pose channel, here because the depsgraph
 +               * evaluation can change them so they are not cached in mpt. */
 +              bPoseChannel *pchan_eval = NULL;
                if (mpt->pchan) {
 +                      pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, mpt->pchan->name);
 +              }
 +
 +              /* pose-channel or object path baking? */
 +              if (pchan_eval) {
                        /* heads or tails */
                        if (mpath->flag & MOTIONPATH_FLAG_BHEAD) {
 -                              copy_v3_v3(mpv->co, mpt->pchan->pose_head);
 +                              copy_v3_v3(mpv->co, pchan_eval->pose_head);
                        }
                        else {
 -                              copy_v3_v3(mpv->co, mpt->pchan->pose_tail);
 +                              copy_v3_v3(mpv->co, pchan_eval->pose_tail);
                        }
  
                        /* result must be in worldspace */
  }
  
  /* Perform baking of the given object's and/or its bones' transforms to motion paths
-  *    - scene: current scene
-  *    - ob: object whose flagged motionpaths should get calculated
-  *    - recalc: whether we need to
+  * - scene: current scene
+  * - ob: object whose flagged motionpaths should get calculated
+  * - recalc: whether we need to
   */
  /* TODO: include reports pointer? */
 -void animviz_calc_motionpaths(Main *bmain, Scene *scene, ListBase *targets)
 +void animviz_calc_motionpaths(Depsgraph *depsgraph,
 +                              Main *bmain,
 +                              Scene *scene,
 +                              ListBase *targets,
 +                              bool restore,
 +                              bool current_frame_only)
  {
 -      MPathTarget *mpt;
 -      int sfra, efra;
 -      int cfra;
 -
        /* sanity check */
        if (ELEM(NULL, targets, targets->first))
                return;
@@@ -1971,11 -1969,11 +1971,11 @@@ NlaEvalStrip *nlastrips_ctime_get_strip
        }
  
        /* evaluate strip's evaluation controls
-        *  - skip if no influence (i.e. same effect as muting the strip)
-        *      - negative influence is not supported yet... how would that be defined?
+        * - skip if no influence (i.e. same effect as muting the strip)
+        * - negative influence is not supported yet... how would that be defined?
         */
        /* TODO: this sounds a bit hacky having a few isolated F-Curves stuck on some data it operates on... */
 -      nlastrip_evaluate_controls(estrip, ctime);
 +      nlastrip_evaluate_controls(depsgraph, estrip, ctime);
        if (estrip->influence <= 0.0f)
                return NULL;
  
  /* ---------------------- */
  
  /* find an NlaEvalChannel that matches the given criteria
-  *    - ptr and prop are the RNA data to find a match for
+  * - ptr and prop are the RNA data to find a match for
   */
 -static NlaEvalChannel *nlaevalchan_find_match(ListBase *channels, PointerRNA *ptr, PropertyRNA *prop, int array_index)
 +static NlaEvalChannel *nlaevalchan_find_match(ListBase *channels, const PathResolvedRNA *prna)
  {
        NlaEvalChannel *nec;
  
                /* - comparing the PointerRNA's is done by comparing the pointers
                 *   to the actual struct the property resides in, since that all the
                 *   other data stored in PointerRNA cannot allow us to definitively
-                *      identify the data
+                *   identify the data
                 */
 -              if ((nec->ptr.data == ptr->data) && (nec->prop == prop) && (nec->index == array_index))
 +              if ((nec->rna.ptr.data == prna->ptr.data) && (nec->rna.prop == prna->prop) && ELEM(nec->rna.prop_index, -1, prna->prop_index))
                        return nec;
        }
  
@@@ -2585,10 -2625,21 +2585,10 @@@ static void animsys_evaluate_nla(Depsgr
  }
  
  /* NLA Evaluation function (mostly for use through do_animdata)
-  *    - All channels that will be affected are not cleared anymore. Instead, we just evaluate into
-  *            some temp channels, where values can be accumulated in one go.
+  * - All channels that will be affected are not cleared anymore. Instead, we just evaluate into
+  *   some temp channels, where values can be accumulated in one go.
   */
 -static void animsys_calculate_nla(PointerRNA *ptr, AnimData *adt, float ctime)
 +static void animsys_calculate_nla(Depsgraph *depsgraph, PointerRNA *ptr, AnimData *adt, float ctime)
  {
        ListBase echannels = {NULL, NULL};
  
@@@ -2682,9 -2746,9 +2682,9 @@@ void BKE_animsys_evaluate_animdata(Deps
                /* evaluate NLA data */
                if ((adt->nla_tracks.first) && !(adt->flag & ADT_NLA_EVAL_OFF)) {
                        /* evaluate NLA-stack
-                        *      - active action is evaluated as part of the NLA stack as the last item
+                        * - active action is evaluated as part of the NLA stack as the last item
                         */
 -                      animsys_calculate_nla(&id_ptr, adt, ctime);
 +                      animsys_calculate_nla(depsgraph, &id_ptr, adt, ctime);
                }
                /* evaluate Active Action only */
                else if (adt->action)
@@@ -2330,11 -2311,11 +2330,11 @@@ void BKE_pose_where_is(struct Depsgrap
                }
  
                /* 2a. construct the IK tree (standard IK) */
 -              BIK_initialize_tree(scene, ob, ctime);
 +              BIK_initialize_tree(depsgraph, scene, ob, ctime);
  
                /* 2b. construct the Spline IK trees
-                *  - this is not integrated as an IK plugin, since it should be able
-                *        to function in conjunction with standard IK
+                * - this is not integrated as an IK plugin, since it should be able
+                *   to function in conjunction with standard IK
                 */
                BKE_pose_splineik_init_tree(scene, ob, ctime);
  
@@@ -621,10 -602,10 +621,10 @@@ void BKE_pose_eval_init_ik(struct Depsg
                return;
        }
        /* construct the IK tree (standard IK) */
 -      BIK_initialize_tree(scene, ob, ctime);
 +      BIK_initialize_tree(depsgraph, scene, ob, ctime);
        /* construct the Spline IK trees
-        *  - this is not integrated as an IK plugin, since it should be able
-        *    to function in conjunction with standard IK
+        * - this is not integrated as an IK plugin, since it should be able
+        *   to function in conjunction with standard IK
         */
        BKE_pose_splineik_init_tree(scene, ob, ctime);
  }
@@@ -434,63 -443,48 +434,63 @@@ static void contarget_get_mesh_mat(Obje
                                        weightsum += dw->weight;
                                }
                        }
 +              }
 +      }
 +      else if (em) {
 +              if (CustomData_has_layer(&em->bm->vdata, CD_MDEFORMVERT)) {
 +                      BMVert *v;
 +                      BMIter iter;
  
 -                      /* calculate averages of normal and coordinates */
 -                      if (weightsum > 0) {
 -                              mul_v3_fl(vec, 1.0f / weightsum);
 -                              mul_v3_fl(normal, 1.0f / weightsum);
 -                      }
 -
 +                      BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) {
 +                              MDeformVert *dv = CustomData_bmesh_get(&em->bm->vdata, v->head.data, CD_MDEFORMVERT);
 +                              MDeformWeight *dw = defvert_find_index(dv, defgroup);
  
 -                      /* derive the rotation from the average normal:
 -                       * - code taken from transform_manipulator.c,
 -                       *   calc_manipulator_stats, V3D_MANIP_NORMAL case
 -                       */
 -                      /* we need the transpose of the inverse for a normal... */
 -                      copy_m3_m4(imat, ob->obmat);
 +                              if (dw && dw->weight > 0.0f) {
 +                                      madd_v3_v3fl(vec, v->co, dw->weight);
 +                                      madd_v3_v3fl(normal, v->no, dw->weight);
 +                                      weightsum += dw->weight;
 +                              }
 +                      }
 +              }
 +      }
 +      else {
 +              /* No valid edit or evaluated mesh, just abort. */
 +              return;
 +      }
  
 -                      invert_m3_m3(tmat, imat);
 -                      transpose_m3(tmat);
 -                      mul_m3_v3(tmat, normal);
 +      /* calculate averages of normal and coordinates */
 +      if (weightsum > 0) {
 +              mul_v3_fl(vec, 1.0f / weightsum);
 +              mul_v3_fl(normal, 1.0f / weightsum);
 +      }
  
 -                      normalize_v3(normal);
 -                      copy_v3_v3(plane, tmat[1]);
 +      /* derive the rotation from the average normal:
-        *              - code taken from transform_gizmo.c,
-        *                      calc_gizmo_stats, V3D_MANIP_NORMAL case
++       * - code taken from transform_gizmo.c,
++       *   calc_gizmo_stats, V3D_MANIP_NORMAL case
 +       */
 +      /*      we need the transpose of the inverse for a normal... */
 +      copy_m3_m4(imat, ob->obmat);
  
 -                      cross_v3_v3v3(mat[0], normal, plane);
 -                      if (len_squared_v3(mat[0]) < SQUARE(1e-3f)) {
 -                              copy_v3_v3(plane, tmat[0]);
 -                              cross_v3_v3v3(mat[0], normal, plane);
 -                      }
 +      invert_m3_m3(tmat, imat);
 +      transpose_m3(tmat);
 +      mul_m3_v3(tmat, normal);
  
 -                      copy_v3_v3(mat[2], normal);
 -                      cross_v3_v3v3(mat[1], mat[2], mat[0]);
 +      normalize_v3(normal);
 +      copy_v3_v3(plane, tmat[1]);
  
 -                      normalize_m4(mat);
 +      cross_v3_v3v3(mat[0], normal, plane);
 +      if (len_squared_v3(mat[0]) < SQUARE(1e-3f)) {
 +              copy_v3_v3(plane, tmat[0]);
 +              cross_v3_v3v3(mat[0], normal, plane);
 +      }
  
 +      copy_v3_v3(mat[2], normal);
 +      cross_v3_v3v3(mat[1], mat[2], mat[0]);
  
 -                      /* apply the average coordinate as the new location */
 -                      mul_v3_m4v3(mat[3], ob->obmat, vec);
 -              }
 -      }
 +      normalize_m4(mat);
  
 -      /* free temporary DerivedMesh created (in EditMode case) */
 -      if (dm && freeDM)
 -              dm->release(dm);
 +      /* apply the average coordinate as the new location */
 +      mul_v3_m4v3(mat[3], ob->obmat, vec);
  }
  
  /* function that sets the given matrix based on given vertex group in lattice */
@@@ -1288,10 -1257,16 +1288,10 @@@ static void followpath_get_tarmat(struc
                unit_m4(ct->matrix);
  
                /* note: when creating constraints that follow path, the curve gets the CU_PATH set now,
-                *              currently for paths to work it needs to go through the bevlist/displist system (ton)
+                * currently for paths to work it needs to go through the bevlist/displist system (ton)
                 */
  
 -#ifdef CYCLIC_DEPENDENCY_WORKAROUND
 -              if (ct->tar->curve_cache == NULL) {
 -                      BKE_displist_make_curveTypes(cob->scene, ct->tar, false);
 -              }
 -#endif
 -
 -              if (ct->tar->curve_cache->path && ct->tar->curve_cache->path->data) {
 +              if (ct->tar->runtime.curve_cache && ct->tar->runtime.curve_cache->path && ct->tar->runtime.curve_cache->path->data) {
                        float quat[4];
                        if ((data->followflag & FOLLOWPATH_STATIC) == 0) {
                                /* animated position along curve depending on time */
@@@ -3855,12 -3680,12 +3857,12 @@@ static void damptrack_do_transform(floa
                float rmat[3][3], tmat[4][4];
  
                /* find the (unit) direction that the axis we're interested in currently points
-                *      - mul_mat3_m4_v3() only takes the 3x3 (rotation+scaling) components of the 4x4 matrix
-                *      - the normalization step at the end should take care of any unwanted scaling
-                *        left over in the 3x3 matrix we used
+                * - mul_mat3_m4_v3() only takes the 3x3 (rotation+scaling) components of the 4x4 matrix
+                * - the normalization step at the end should take care of any unwanted scaling
+                *   left over in the 3x3 matrix we used
                 */
 -              copy_v3_v3(obvec, track_dir_vecs[data->trackflag]);
 -              mul_mat3_m4_v3(cob->matrix, obvec);
 +              copy_v3_v3(obvec, track_dir_vecs[track_axis]);
 +              mul_mat3_m4_v3(matrix, obvec);
  
                if (normalize_v3(obvec) == 0.0f) {
                        /* exceptional case - just use the track vector as appropriate */
@@@ -1857,12 -1880,12 +1857,12 @@@ static void dynamic_paint_apply_surface
  }
  
  /*
-  *    Apply canvas data to the object derived mesh
+  * Apply canvas data to the object derived mesh
   */
 -static DerivedMesh *dynamicPaint_Modifier_apply(
 -        DynamicPaintModifierData *pmd, Object *ob, DerivedMesh *dm)
 +static Mesh *dynamicPaint_Modifier_apply(
 +        DynamicPaintModifierData *pmd, Object *ob, Mesh *mesh)
  {
 -      DerivedMesh *result = CDDM_copy(dm);
 +      Mesh *result = BKE_mesh_copy_for_eval(mesh, false);
  
        if (pmd->canvas && !(pmd->canvas->flags & MOD_DPAINT_BAKING)) {
  
@@@ -2049,11 -2070,10 +2049,11 @@@ static void canvas_copyMesh(DynamicPain
  }
  
  /*
-  *    Updates derived mesh copy and processes dynamic paint step / caches.
+  * Updates derived mesh copy and processes dynamic paint step / caches.
   */
  static void dynamicPaint_frameUpdate(
 -        Main *bmain, EvaluationContext *eval_ctx, DynamicPaintModifierData *pmd, Scene *scene, Object *ob, DerivedMesh *dm)
 +        DynamicPaintModifierData *pmd, struct Depsgraph *depsgraph, Scene *scene,
 +        Object *ob, Mesh *mesh)
  {
        if (pmd->canvas) {
                DynamicPaintCanvasSettings *canvas = pmd->canvas;
@@@ -2776,17 -2795,17 +2776,17 @@@ int dynamicPaint_createUVSurface(Scene 
        if (surface->format != MOD_DPAINT_SURFACE_F_IMAGESEQ)
                return setError(canvas, N_("Cannot bake non-'image sequence' formats"));
  
 -      mloop = dm->getLoopArray(dm);
 -      mlooptri = dm->getLoopTriArray(dm);
 -      const int tottri = dm->getNumLoopTri(dm);
 +      mloop = mesh->mloop;
 +      mlooptri = BKE_mesh_runtime_looptri_ensure(mesh);;
 +      const int tottri = BKE_mesh_runtime_looptri_len(mesh);
  
        /* get uv map */
 -      if (CustomData_has_layer(&dm->loopData, CD_MLOOPUV)) {
 -              CustomData_validate_layer_name(&dm->loopData, CD_MLOOPUV, surface->uvlayer_name, uvname);
 -              mloopuv = CustomData_get_layer_named(&dm->loopData, CD_MLOOPUV, uvname);
 +      if (CustomData_has_layer(&mesh->ldata, CD_MLOOPUV)) {
 +              CustomData_validate_layer_name(&mesh->ldata, CD_MLOOPUV, surface->uvlayer_name, uvname);
 +              mloopuv = CustomData_get_layer_named(&mesh->ldata, CD_MLOOPUV, uvname);
        }
  
-       /* Check for validity   */
+       /* Check for validity */
        if (!mloopuv)
                return setError(canvas, N_("No UV data on canvas"));
        if (surface->image_resolution < 16 || surface->image_resolution > 8192)
@@@ -4047,7 -4156,14 +4047,7 @@@ static void dynamic_paint_paint_mesh_ce
                        sampleColor[1] = brush->g;
                        sampleColor[2] = brush->b;
  
-                       /* Sample proximity colorband if required       */
 -                      /* Get material+textures color on hit point if required */
 -                      if (brush_usesMaterial(brush, scene)) {
 -                              dynamicPaint_doMaterialTex(bMats, sampleColor, &alpha_factor, brushOb,
 -                                                         bData->realCoord[bData->s_pos[index] + ss].v,
 -                                                         hitCoord, hitTri, dm);
 -                      }
 -
+                       /* Sample proximity colorband if required */
                        if ((hit_found == HIT_PROXIMITY) &&
                            (brush->proximity_falloff == MOD_DPAINT_PRFALL_RAMP))
                        {
@@@ -4123,15 -4242,15 +4123,15 @@@ static int dynamicPaint_paintMesh(Depsg
                Bounds3D mesh_bb = {{0}};
                VolumeGrid *grid = bData->grid;
  
 -              dm = CDDM_copy(brush->dm);
 -              mvert = dm->getVertArray(dm);
 -              mlooptri = dm->getLoopTriArray(dm);
 -              mloop = dm->getLoopArray(dm);
 -              numOfVerts = dm->getNumVerts(dm);
 +              mesh = BKE_mesh_copy_for_eval(brush->mesh, false);
 +              mvert = mesh->mvert;
 +              mlooptri = BKE_mesh_runtime_looptri_ensure(mesh);
 +              mloop = mesh->mloop;
 +              numOfVerts = mesh->totvert;
  
-               /*      Transform collider vertices to global space
-                *      (Faster than transforming per surface point
-                *      coordinates and normals to object space) */
+               /* Transform collider vertices to global space
+                * (Faster than transforming per surface point
+                * coordinates and normals to object space) */
                for (ii = 0; ii < numOfVerts; ii++) {
                        mul_m4_v3(brushOb->obmat, mvert[ii].co);
                        boundInsert(&mesh_bb, mvert[ii].co);
  
                /* check bounding box collision */
                if (grid && meshBrush_boundsIntersect(&grid->grid_bounds, &mesh_bb, brush, brush_radius)) {
-                       /* Build a bvh tree from transformed vertices   */
+                       /* Build a bvh tree from transformed vertices */
 -                      if (bvhtree_from_mesh_get(&treeData, dm, BVHTREE_FROM_LOOPTRI, 4)) {
 +                      if (BKE_bvhtree_from_mesh_get(&treeData, mesh, BVHTREE_FROM_LOOPTRI, 4)) {
                                int c_index;
                                int total_cells = grid->dim[0] * grid->dim[1] * grid->dim[2];
  
@@@ -4590,12 -4726,12 +4590,12 @@@ static int dynamicPaint_paintSinglePoin
        Vec3f brushVel;
  
        if (brush->flags & MOD_DPAINT_USES_VELOCITY)
 -              dynamicPaint_brushObjectCalculateVelocity(bmain, eval_ctx, scene, brushOb, &brushVel, timescale);
 +              dynamicPaint_brushObjectCalculateVelocity(depsgraph, scene, brushOb, &brushVel, timescale);
  
 -      const MVert *mvert = brush->dm->getVertArray(brush->dm);
 +      const MVert *mvert = brush->mesh->mvert;
  
        /*
-        *      Loop through every surface point
+        * Loop through every surface point
         */
        DynamicPaintPaintData data = {
            .surface = surface,
@@@ -5915,16 -6052,41 +5915,16 @@@ static int dynamicPaint_doStep
                int scene_frame = scene->r.cfra;
                float scene_subframe = scene->r.subframe;
  
 -              /* either from group or from all objects */
 -              if (surface->brush_group)
 -                      go = surface->brush_group->gobject.first;
 -              else
 -                      base = scene->base.first;
 -
 -              while (base || go) {
 -                      brushObj = NULL;
 -                      /* select object */
 -                      if (surface->brush_group) {
 -                              if (go->ob)
 -                                      brushObj = go->ob;
 -                      }
 -                      else
 -                              brushObj = base->object;
 -
 -                      /* next item */
 -                      if (surface->brush_group)
 -                              go = go->next;
 -                      else
 -                              base = base->next;
 -
 -                      if (!brushObj) {
 -                              /* skip item */
 -                              continue;
 -                      }
 +              for (int i = 0; i < numobjects; i++) {
 +                      Object *brushObj = objects[i];
  
-                       /* check if target has an active dp modifier    */
+                       /* check if target has an active dp modifier */
 -                      md = modifiers_findByType(brushObj, eModifierType_DynamicPaint);
 +                      ModifierData *md = modifiers_findByType(brushObj, eModifierType_DynamicPaint);
                        if (md && md->mode & (eModifierMode_Realtime | eModifierMode_Render)) {
                                DynamicPaintModifierData *pmd2 = (DynamicPaintModifierData *)md;
-                               /* make sure we're dealing with a brush */
+                               /* make sure we're dealing with a brush */
                                if (pmd2->brush) {
                                        DynamicPaintBrushSettings *brush = pmd2->brush;
 -                                      BrushMaterials bMats = {NULL};
  
                                        /* calculate brush speed vectors if required */
                                        if (surface->type == MOD_DPAINT_SURFACE_T_PAINT && brush->flags & MOD_DPAINT_DO_SMUDGE) {
@@@ -2033,11 -1920,10 +2033,11 @@@ float driver_get_variable_value(Channel
  }
  
  /* Evaluate an Channel-Driver to get a 'time' value to use instead of "evaltime"
-  *    - "evaltime" is the frame at which F-Curve is being evaluated
-  *  - has to return a float value
-  *  - driver_orig is where we cache Python expressions, in case of COW
+  * - "evaltime" is the frame at which F-Curve is being evaluated
+  * - has to return a float value
++ * - driver_orig is where we cache Python expressions, in case of COW
   */
 -float evaluate_driver(PathResolvedRNA *anim_rna, ChannelDriver *driver, const float evaltime)
 +float evaluate_driver(PathResolvedRNA *anim_rna, ChannelDriver *driver, ChannelDriver *driver_orig, const float evaltime)
  {
        DriverVar *dvar;
  
                        {
                                driver->curval = 0.0f;
                        }
 -                      else {
 +                      else if (!driver_try_evaluate_simple_expr(driver, driver_orig, &driver->curval, evaltime)) {
 +#ifdef WITH_PYTHON
                                /* this evaluates the expression using Python, and returns its result:
-                                *  - on errors it reports, then returns 0.0f
+                                * - on errors it reports, then returns 0.0f
                                 */
                                BLI_mutex_lock(&python_driver_lock);
  
@@@ -1332,9 -1312,9 +1332,9 @@@ static void rigidbody_update_sim_ob(Dep
                        pd_point_from_loc(scene, eff_loc, eff_vel, 0, &epoint);
  
                        /* calculate net force of effectors, and apply to sim object
-                        *      - we use 'central force' since apply force requires a "relative position" which we don't have...
+                        * - we use 'central force' since apply force requires a "relative position" which we don't have...
                         */
 -                      pdDoEffectors(effectors, NULL, effector_weights, &epoint, eff_force, NULL);
 +                      BKE_effectors_apply(effectors, NULL, effector_weights, &epoint, eff_force, NULL);
                        if (G.f & G_DEBUG)
                                printf("\tapplying force (%f,%f,%f) to '%s'\n", eff_force[0], eff_force[1], eff_force[2], ob->id.name + 2);
                        /* activate object in case it is deactivated */
@@@ -438,13 -422,14 +438,13 @@@ static void acf_summary_backdrop(bAnimC
  
        /* set backdrop drawing color */
        acf->get_backdrop_color(ac, ale, color);
 -      glColor3fv(color);
  
        /* rounded corners on LHS only
-        *      - top and bottom
-        *      - special hack: make the top a bit higher, since we are first...
+        * - top and bottom
+        * - special hack: make the top a bit higher, since we are first...
         */
        UI_draw_roundbox_corner_set(UI_CNR_TOP_LEFT | UI_CNR_BOTTOM_LEFT);
 -      UI_draw_roundbox_gl_mode(GL_POLYGON, 0,  yminc - 2, v2d->cur.xmax + EXTRA_SCROLL_PAD, ymaxc, 8);
 +      UI_draw_roundbox_3fvAlpha(true, 0,  yminc - 2, v2d->cur.xmax + EXTRA_SCROLL_PAD, ymaxc, 8, color, 1.0f);
  }
  
  /* name for summary entries */
@@@ -3856,12 -3847,12 +3856,12 @@@ void ANIM_channel_draw(bAnimContext *ac
        }
  
        /* turn off blending, since not needed anymore... */
 -      glDisable(GL_BLEND);
 +      GPU_blend(false);
  
        /* step 4) draw special toggles  .................................
-        *      - in Graph Editor, checkboxes for visibility in curves area
-        *      - in NLA Editor, glowing dots for solo/not solo...
-        *      - in Grease Pencil mode, color swatches for layer color
+        * - in Graph Editor, checkboxes for visibility in curves area
+        * - in NLA Editor, glowing dots for solo/not solo...
+        * - in Grease Pencil mode, color swatches for layer color
         */
        if (ac->sl) {
                if ((ac->spacetype == SPACE_IPO) &&
  
  
                /* finally draw a backdrop rect behind these
-                *      - starts from the point where the first toggle/slider starts,
-                *      - ends past the space that might be reserved for a scroller
+                * - starts from the point where the first toggle/slider starts,
+                * - ends past the space that might be reserved for a scroller
                 */
 -              glRectf(v2d->cur.xmax - (float)offset, yminc + ymin_ofs, v2d->cur.xmax + EXTRA_SCROLL_PAD, ymaxc);
 +              immRectf(pos, v2d->cur.xmax - (float)offset, yminc + ymin_ofs, v2d->cur.xmax + EXTRA_SCROLL_PAD, ymaxc);
 +
 +              immUnbindProgram();
        }
  }
  
@@@ -86,18 -76,19 +86,18 @@@ void ANIM_draw_cfra_number(const bConte
        int slen;
  
        /* because the frame number text is subject to the same scaling as the contents of the view */
 -      UI_view2d_scale_get(v2d, &xscale, &yscale);
 -      glScalef(1.0f / xscale, 1.0f, 1.0f);
 +      UI_view2d_scale_get(v2d, &xscale, NULL);
 +      GPU_matrix_push();
 +      GPU_matrix_scale_2f(1.0f / xscale, 1.0f);
  
        /* get timecode string
-        *      - padding on str-buf passed so that it doesn't sit on the frame indicator
+        * - padding on str-buf passed so that it doesn't sit on the frame indicator
 -       * - power = 0, gives 'standard' behavior for time
 -       *   but power = 1 is required for frames (to get integer frames)
         */
 -      if (time) {
 -              BLI_timecode_string_from_time(&numstr[4], sizeof(numstr) - 4, 0, FRA2TIME(cfra), FPS, U.timecode_style);
 +      if (show_time) {
 +              BLI_timecode_string_from_time(&numstr[2], sizeof(numstr) - 2, 0, FRA2TIME(cfra), FPS, U.timecode_style);
        }
        else {
 -              BLI_timecode_string_from_time_seconds(&numstr[4], sizeof(numstr) - 4, 1, cfra);
 +              BLI_timecode_string_from_time_seconds(&numstr[2], sizeof(numstr) - 2, 1, cfra);
        }
  
        slen = UI_fontstyle_string_width(fstyle, numstr) - 1;
@@@ -1740,10 -1711,10 +1740,10 @@@ static size_t animdata_filter_gpencil(b
  
                                /* check if object belongs to the filtering group if option to filter
                                 * objects by the grouped status is on
-                                *      - used to ease the process of doing multiple-character choreographies
+                                * - used to ease the process of doing multiple-character choreographies
                                 */
 -                              if (ads->filterflag & ADS_FILTER_ONLYOBGROUP) {
 -                                      if (BKE_group_object_exists(ads->filter_grp, ob) == 0)
 +                              if (ads->filter_grp != NULL) {
 +                                      if (BKE_collection_has_object_recursive(ads->filter_grp, ob) == 0)
                                                continue;
                                }
  
@@@ -2909,10 -2905,10 +2909,10 @@@ static bool animdata_filter_base_is_ok(
  
        /* check if object belongs to the filtering group if option to filter
         * objects by the grouped status is on
-        *      - used to ease the process of doing multiple-character choreographies
+        * - used to ease the process of doing multiple-character choreographies
         */
 -      if (ads->filterflag & ADS_FILTER_ONLYOBGROUP) {
 -              if (BKE_group_object_exists(ads->filter_grp, ob) == 0)
 +      if (ads->filter_grp != NULL) {
 +              if (BKE_collection_has_object_recursive(ads->filter_grp, ob) == 0)
                        return false;
        }
  
@@@ -2989,13 -2984,13 +2989,13 @@@ static size_t animdata_filter_dopesheet
  
        /* If filtering for channel drawing, we want the objects in alphabetical order,
         * to make it easier to predict where items are in the hierarchy
-        *  - This order only really matters if we need to show all channels in the list (e.g. for drawing)
-        *    (XXX: What about lingering "active" flags? The order may now become unpredictable)
-        *  - Don't do this if this behaviour has been turned off (i.e. due to it being too slow)
-        *  - Don't do this if there's just a single object
+        * - This order only really matters if we need to show all channels in the list (e.g. for drawing)
+        *   (XXX: What about lingering "active" flags? The order may now become unpredictable)
+        * - Don't do this if this behaviour has been turned off (i.e. due to it being too slow)
+        * - Don't do this if there's just a single object
         */
        if ((filter_mode & ANIMFILTER_LIST_CHANNELS) && !(ads->flag & ADS_FLAG_NO_DB_SORT) &&
 -          (scene->base.first != scene->base.last))
 +          (view_layer->object_bases.first != view_layer->object_bases.last))
        {
                /* Filter list of bases (i.e. objects), sort them, then add their contents normally... */
                // TODO: Cache the old sorted order - if the set of bases hasn't changed, don't re-sort...
@@@ -735,49 -670,19 +735,49 @@@ static void draw_keylist(View2D *v2d, D
                }
        }
  
 -      /* draw keys */
        if (keys) {
 -              for (ak = keys->first; ak; ak = ak->next) {
 +              /* count keys */
 +              uint key_len = 0;
 +              for (ActKeyColumn *ak = keys->first; ak; ak = ak->next) {
                        /* optimization: if keyframe doesn't appear within 5 units (screenspace) in visible area, don't draw
-                        *      - this might give some improvements, since we current have to flip between view/region matrices
+                        * - this might give some improvements, since we current have to flip between view/region matrices
                         */
 -                      if (IN_RANGE_INCL(ak->cfra, v2d->cur.xmin, v2d->cur.xmax) == 0)
 -                              continue;
 +                      if (IN_RANGE_INCL(ak->cfra, v2d->cur.xmin, v2d->cur.xmax))
 +                              key_len++;
 +              }
  
 -                      /* draw using OpenGL - uglier but faster */
 -                      /* NOTE1: a previous version of this didn't work nice for some intel cards
 -                       * NOTE2: if we wanted to go back to icons, these are  icon = (ak->sel & SELECT) ? ICON_SPACE2 : ICON_SPACE3; */
 -                      draw_keyframe_shape(ak->cfra, ypos, xscale, iconsize, (ak->sel & SELECT), ak->key_type, KEYFRAME_SHAPE_BOTH, alpha);
 +              if (key_len > 0) {
 +                      /* draw keys */
 +                      GPUVertFormat *format = immVertexFormat();
 +                      uint pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
 +                      uint size_id = GPU_vertformat_attr_add(format, "size", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
 +                      uint color_id = GPU_vertformat_attr_add(format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT);
 +                      uint outline_color_id = GPU_vertformat_attr_add(format, "outlineColor", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT);
 +                      uint flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT);
 +                      immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND);
 +                      GPU_enable_program_point_size();
 +                      immUniform2f("ViewportSize", BLI_rcti_size_x(&v2d->mask) + 1, BLI_rcti_size_y(&v2d->mask) + 1);
 +                      immBegin(GPU_PRIM_POINTS, key_len);
 +
 +                      short handle_type = KEYFRAME_HANDLE_NONE, extreme_type = KEYFRAME_EXTREME_NONE;
 +
 +                      for (ActKeyColumn *ak = keys->first; ak; ak = ak->next) {
 +                              if (IN_RANGE_INCL(ak->cfra, v2d->cur.xmin, v2d->cur.xmax)) {
 +                                      if (show_ipo) {
 +                                              handle_type = ak->handle_type;
 +                                      }
 +                                      if (saction_flag & SACTION_SHOW_EXTREMES) {
 +                                              extreme_type = ak->extreme_type;
 +                                      }
 +
 +                                      draw_keyframe_shape(ak->cfra, ypos, icon_sz, (ak->sel & SELECT), ak->key_type, KEYFRAME_SHAPE_BOTH, alpha,
 +                                                          pos_id, size_id, color_id, outline_color_id, flags_id, handle_type, extreme_type);
 +                              }
 +                      }
 +
 +                      immEnd();
 +                      GPU_disable_program_point_size();
 +                      immUnbindProgram();
                }
        }
  
@@@ -956,16 -879,16 +956,16 @@@ static float visualkey_get_value(Depsgr
  /* ------------------------- Insert Key API ------------------------- */
  
  /* Secondary Keyframing API call:
-  *  Use this when validation of necessary animation data is not necessary, since an RNA-pointer to the necessary
-  *    data being keyframed, and a pointer to the F-Curve to use have both been provided.
+  * Use this when validation of necessary animation data is not necessary, since an RNA-pointer to the necessary
+  * data being keyframed, and a pointer to the F-Curve to use have both been provided.
   *
-  *  keytype is the "keyframe type" (eBezTriple_KeyframeType), as shown in the Dope Sheet.
+  * keytype is the "keyframe type" (eBezTriple_KeyframeType), as shown in the Dope Sheet.
   *
-  *    The flag argument is used for special settings that alter the behavior of
-  *    the keyframe insertion. These include the 'visual' keyframing modes, quick refresh,
-  *    and extra keyframe filtering.
+  * The flag argument is used for special settings that alter the behavior of
+  * the keyframe insertion. These include the 'visual' keyframing modes, quick refresh,
+  * and extra keyframe filtering.
   */
 -bool insert_keyframe_direct(ReportList *reports, PointerRNA ptr, PropertyRNA *prop, FCurve *fcu, float cfra, eBezTriple_KeyframeType keytype, eInsertKeyFlags flag)
 +bool insert_keyframe_direct(Depsgraph *depsgraph, ReportList *reports, PointerRNA ptr, PropertyRNA *prop, FCurve *fcu, float cfra, eBezTriple_KeyframeType keytype, eInsertKeyFlags flag)
  {
        float curval = 0.0f;
  
  }
  
  /* Main Keyframing API call:
-  *    Use this when validation of necessary animation data is necessary, since it may not exist yet.
+  * Use this when validation of necessary animation data is necessary, since it may not exist yet.
   *
-  *    The flag argument is used for special settings that alter the behavior of
-  *    the keyframe insertion. These include the 'visual' keyframing modes, quick refresh,
-  *    and extra keyframe filtering.
+  * The flag argument is used for special settings that alter the behavior of
+  * the keyframe insertion. These include the 'visual' keyframing modes, quick refresh,
+  * and extra keyframe filtering.
   *
-  *    index of -1 keys all array indices
+  * index of -1 keys all array indices
   */
  short insert_keyframe(
 -        Main *bmain, ReportList *reports, ID *id, bAction *act,
 +        Main *bmain, Depsgraph *depsgraph, ReportList *reports, ID *id, bAction *act,
          const char group[], const char rna_path[], int array_index, float cfra, eBezTriple_KeyframeType keytype, eInsertKeyFlags flag)
  {
        PointerRNA id_ptr, ptr;
        /* will only loop once unless the array index was -1 */
        for (; array_index < array_index_max; array_index++) {
                /* make sure the F-Curve exists
-                *      - if we're replacing keyframes only, DO NOT create new F-Curves if they do not exist yet
-                *        but still try to get the F-Curve if it exists...
+                * - if we're replacing keyframes only, DO NOT create new F-Curves if they do not exist yet
+                *   but still try to get the F-Curve if it exists...
                 */
 -              fcu = verify_fcurve(act, group, &ptr, rna_path, array_index, (flag & INSERTKEY_REPLACE) == 0);
 +              fcu = verify_fcurve(bmain, act, group, &ptr, rna_path, array_index, (flag & INSERTKEY_REPLACE) == 0);
  
                /* we may not have a F-Curve when we're replacing only... */
                if (fcu) {
@@@ -1311,15 -1215,13 +1311,15 @@@ short delete_keyframe(Main *bmain, Repo
  /* KEYFRAME CLEAR */
  
  /* Main Keyframing API call:
-  *    Use this when validation of necessary animation data isn't necessary as it
-  *    already exists. It will clear the current buttons fcurve(s).
+  * Use this when validation of necessary animation data isn't necessary as it
+  * already exists. It will clear the current buttons fcurve(s).
   *
-  *    The flag argument is used for special settings that alter the behavior of
-  *    the keyframe deletion. These include the quick refresh options.
+  * The flag argument is used for special settings that alter the behavior of
+  * the keyframe deletion. These include the quick refresh options.
   */
 -static short clear_keyframe(ReportList *reports, ID *id, bAction *act, const char group[], const char rna_path[], int array_index, eInsertKeyFlags UNUSED(flag))
 +static short clear_keyframe(Main *bmain, ReportList *reports, ID *id, bAction *act,
 +                            const char group[], const char rna_path[], int array_index,
 +                            eInsertKeyFlags UNUSED(flag))
  {
        AnimData *adt = BKE_animdata_from_id(id);
        PointerRNA id_ptr, ptr;
@@@ -924,14 -923,13 +924,14 @@@ short ANIM_validate_keyingset(bContext 
  /* Determine which keying flags apply based on the override flags */
  static short keyingset_apply_keying_flags(const short base_flags, const short overrides, const short own_flags)
  {
 -      short result = 0;
 +      /* Pass through all flags by default (i.e. even not explicitly listed ones). */
 +      short result = base_flags;
  
        /* The logic for whether a keying flag applies is as follows:
-        *  - If the flag in question is set in "overrides", that means that the
-        *    status of that flag in "own_flags" is used
-        *  - If however the flag isn't set, then its value in "base_flags" is used
-        *    instead (i.e. no override)
+        * - If the flag in question is set in "overrides", that means that the
+        *   status of that flag in "own_flags" is used
+        * - If however the flag isn't set, then its value in "base_flags" is used
+        *   instead (i.e. no override)
         */
  #define APPLY_KEYINGFLAG_OVERRIDE(kflag) \
        if (overrides & kflag) {             \
@@@ -1070,99 -1010,89 +1070,99 @@@ static void armature_clear_swap_done_fl
  
  static int armature_switch_direction_exec(bContext *C, wmOperator *UNUSED(op))
  {
 -      Object *ob = CTX_data_edit_object(C);
 -      bArmature *arm = (bArmature *)ob->data;
 -      ListBase chains = {NULL, NULL};
 -      LinkData *chain;
 +      ViewLayer *view_layer = CTX_data_view_layer(C);
 +      uint objects_len = 0;
 +      Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
  
 -      /* get chains of bones (ends on chains) */
 -      chains_find_tips(arm->edbo, &chains);
 -      if (BLI_listbase_is_empty(&chains)) return OPERATOR_CANCELLED;
 +      for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
 +              Object *ob = objects[ob_index];
 +              bArmature *arm = ob->data;
  
 -      /* ensure that mirror bones will also be operated on */
 -      armature_tag_select_mirrored(arm);
 +              ListBase chains = {NULL, NULL};
 +              LinkData *chain;
  
 -      /* clear BONE_TRANSFORM flags
 -       * - used to prevent duplicate/canceling operations from occurring [#34123]
 -       * - BONE_DONE cannot be used here as that's already used for mirroring
 -       */
 -      armature_clear_swap_done_flags(arm);
 +              /* get chains of bones (ends on chains) */
 +              chains_find_tips(arm->edbo, &chains);
 +              if (BLI_listbase_is_empty(&chains)) {
 +                      continue;
 +              }
  
 -      /* loop over chains, only considering selected and visible bones */
 -      for (chain = chains.first; chain; chain = chain->next) {
 -              EditBone *ebo, *child = NULL, *parent = NULL;
 +              /* ensure that mirror bones will also be operated on */
 +              armature_tag_select_mirrored(arm);
  
 -              /* loop over bones in chain */
 -              for (ebo = chain->data; ebo; ebo = parent) {
 -                      /* parent is this bone's original parent
 -                       * - we store this, as the next bone that is checked is this one
 -                       *   but the value of ebo->parent may change here...
 -                       */
 -                      parent = ebo->parent;
 -
 -                      /* skip bone if already handled... [#34123] */
 -                      if ((ebo->flag & BONE_TRANSFORM) == 0) {
 -                              /* only if selected and editable */
 -                              if (EBONE_VISIBLE(arm, ebo) && EBONE_EDITABLE(ebo)) {
 -                                      /* swap head and tail coordinates */
 -                                      swap_v3_v3(ebo->head, ebo->tail);
 -
 -                                      /* do parent swapping:
 -                                       * - use 'child' as new parent
 -                                       * - connected flag is only set if points are coincidental
 -                                       */
 -                                      ebo->parent = child;
 -                                      if ((child) && equals_v3v3(ebo->head, child->tail))
 -                                              ebo->flag |= BONE_CONNECTED;
 -                                      else
 -                                              ebo->flag &= ~BONE_CONNECTED;
 -
 -                                      /* get next bones
 -                                       * - child will become the new parent of next bone
 -                                       */
 -                                      child = ebo;
 -                              }
 -                              else {
 -                                      /* not swapping this bone, however, if its 'parent' got swapped, unparent us from it
 -                                       * as it will be facing in opposite direction
 -                                       */
 -                                      if ((parent) && (EBONE_VISIBLE(arm, parent) && EBONE_EDITABLE(parent))) {
 -                                              ebo->parent = NULL;
 -                                              ebo->flag &= ~BONE_CONNECTED;
 +              /* clear BONE_TRANSFORM flags
 +               * - used to prevent duplicate/canceling operations from occurring [#34123]
 +               * - BONE_DONE cannot be used here as that's already used for mirroring
 +               */
 +              armature_clear_swap_done_flags(arm);
 +
 +              /* loop over chains, only considering selected and visible bones */
 +              for (chain = chains.first; chain; chain = chain->next) {
 +                      EditBone *ebo, *child = NULL, *parent = NULL;
 +
 +                      /* loop over bones in chain */
 +                      for (ebo = chain->data; ebo; ebo = parent) {
 +                              /* parent is this bone's original parent
-                                *      - we store this, as the next bone that is checked is this one
-                                *        but the value of ebo->parent may change here...
++                               * - we store this, as the next bone that is checked is this one
++                               *   but the value of ebo->parent may change here...
 +                               */
 +                              parent = ebo->parent;
 +
 +                              /* skip bone if already handled... [#34123] */
 +                              if ((ebo->flag & BONE_TRANSFORM) == 0) {
 +                                      /* only if selected and editable */
 +                                      if (EBONE_VISIBLE(arm, ebo) && EBONE_EDITABLE(ebo)) {
 +                                              /* swap head and tail coordinates */
 +                                              swap_v3_v3(ebo->head, ebo->tail);
 +
 +                                              /* do parent swapping:
-                                                *      - use 'child' as new parent
-                                                *      - connected flag is only set if points are coincidental
++                                               * - use 'child' as new parent
++                                               * - connected flag is only set if points are coincidental
 +                                               */
 +                                              ebo->parent = child;
 +                                              if ((child) && equals_v3v3(ebo->head, child->tail))
 +                                                      ebo->flag |= BONE_CONNECTED;
 +                                              else
 +                                                      ebo->flag &= ~BONE_CONNECTED;
 +
 +                                              /* get next bones
-                                                *      - child will become the new parent of next bone
++                                               * - child will become the new parent of next bone
 +                                               */
 +                                              child = ebo;
                                        }
 +                                      else {
 +                                              /* not swapping this bone, however, if its 'parent' got swapped, unparent us from it
 +                                               * as it will be facing in opposite direction
 +                                               */
 +                                              if ((parent) && (EBONE_VISIBLE(arm, parent) && EBONE_EDITABLE(parent))) {
 +                                                      ebo->parent = NULL;
 +                                                      ebo->flag &= ~BONE_CONNECTED;
 +                                              }
  
 -                                      /* get next bones
 -                                       * - child will become new parent of next bone (not swapping occurred,
 -                                       *   so set to NULL to prevent infinite-loop)
 -                                       */
 -                                      child = NULL;
 -                              }
 +                                              /* get next bones
-                                                *      - child will become new parent of next bone (not swapping occurred,
-                                                *        so set to NULL to prevent infinite-loop)
++                                               * - child will become new parent of next bone (not swapping occurred,
++                                               *   so set to NULL to prevent infinite-loop)
 +                                               */
 +                                              child = NULL;
 +                                      }
  
 -                              /* tag as done (to prevent double-swaps) */
 -                              ebo->flag |= BONE_TRANSFORM;
 +                                      /* tag as done (to prevent double-swaps) */
 +                                      ebo->flag |= BONE_TRANSFORM;
 +                              }
                        }
                }
 -      }
  
 -      /* free chains */
 -      BLI_freelistN(&chains);
 +              /* free chains */
 +              BLI_freelistN(&chains);
  
 -      /* clear temp flags */
 -      armature_clear_swap_done_flags(arm);
 -      armature_tag_unselect(arm);
 +              /* clear temp flags */
 +              armature_clear_swap_done_flags(arm);
 +              armature_tag_unselect(arm);
  
 -      /* note, notifier might evolve */
 -      WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
 +              /* note, notifier might evolve */
 +              WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
 +      }
 +      MEM_freeN(objects);
  
        return OPERATOR_FINISHED;
  }
@@@ -588,67 -597,47 +588,67 @@@ static int separate_armature_exec(bCont
        }
        CTX_DATA_END;
  
 -      /* 1) store starting settings and exit editmode */
 -      oldob = obedit;
 -      oldbase = BASACT;
 -      oldob->mode &= ~OB_MODE_POSE;
 -      //oldbase->flag &= ~OB_POSEMODE;
 +      for (uint base_index = 0; base_index < bases_len; base_index++) {
 +              Base *base_iter = bases[base_index];
 +              Object *obedit = base_iter->object;
  
 -      ED_armature_from_edit(bmain, obedit->data);
 -      ED_armature_edit_free(obedit->data);
 +              Object *oldob, *newob;
 +              Base *oldbase, *newbase;
  
 -      /* 2) duplicate base */
 -      newbase = ED_object_add_duplicate(bmain, scene, oldbase, USER_DUP_ARM); /* only duplicate linked armature */
 -      DAG_relations_tag_update(bmain);
 +              /* we are going to do this as follows (unlike every other instance of separate):
-                *      1. exit editmode +posemode for active armature/base. Take note of what this is.
-                *      2. duplicate base - BASACT is the new one now
-                *      3. for each of the two armatures, enter editmode -> remove appropriate bones -> exit editmode + recalc
-                *      4. fix constraint links
-                *      5. make original armature active and enter editmode
++               * 1. exit editmode +posemode for active armature/base. Take note of what this is.
++               * 2. duplicate base - BASACT is the new one now
++               * 3. for each of the two armatures, enter editmode -> remove appropriate bones -> exit editmode + recalc
++               * 4. fix constraint links
++               * 5. make original armature active and enter editmode
 +               */
  
 -      newob = newbase->object;
 -      newbase->flag &= ~SELECT;
 +              /* 1) only edit-base selected */
 +              ED_object_base_select(base_iter, BA_SELECT);
  
 +              /* 1) store starting settings and exit editmode */
 +              oldob = obedit;
 +              oldbase = base_iter;
 +              oldob->mode &= ~OB_MODE_POSE;
 +              //oldbase->flag &= ~OB_POSEMODE;
  
 -      /* 3) remove bones that shouldn't still be around on both armatures */
 -      separate_armature_bones(bmain, oldob, 1);
 -      separate_armature_bones(bmain, newob, 0);
 +              ED_armature_from_edit(bmain, obedit->data);
 +              ED_armature_edit_free(obedit->data);
  
 +              /* 2) duplicate base */
 +              newbase = ED_object_add_duplicate(bmain, scene, view_layer, oldbase, USER_DUP_ARM); /* only duplicate linked armature */
 +              DEG_relations_tag_update(bmain);
  
 -      /* 4) fix links before depsgraph flushes */ // err... or after?
 -      separated_armature_fix_links(bmain, oldob, newob);
 +              newob = newbase->object;
 +              newbase->flag &= ~BASE_SELECTED;
  
 -      DAG_id_tag_update(&oldob->id, OB_RECALC_DATA);  /* this is the original one */
 -      DAG_id_tag_update(&newob->id, OB_RECALC_DATA);  /* this is the separated one */
  
 +              /* 3) remove bones that shouldn't still be around on both armatures */
 +              separate_armature_bones(bmain, oldob, 1);
 +              separate_armature_bones(bmain, newob, 0);
  
 -      /* 5) restore original conditions */
 -      obedit = oldob;
  
 -      ED_armature_to_edit(obedit->data);
 +              /* 4) fix links before depsgraph flushes */ // err... or after?
 +              separated_armature_fix_links(bmain, oldob, newob);
  
 -      /* parents tips remain selected when connected children are removed. */
 -      ED_armature_edit_deselect_all(obedit);
 +              DEG_id_tag_update(&oldob->id, OB_RECALC_DATA);  /* this is the original one */
 +              DEG_id_tag_update(&newob->id, OB_RECALC_DATA);  /* this is the separated one */
  
 -      BKE_report(op->reports, RPT_INFO, "Separated bones");
  
 -      /* note, notifier might evolve */
 -      WM_event_add_notifier(C, NC_OBJECT | ND_POSE, obedit);
 +              /* 5) restore original conditions */
 +              obedit = oldob;
 +
 +              ED_armature_to_edit(obedit->data);
 +
 +              /* parents tips remain selected when connected children are removed. */
 +              ED_armature_edit_deselect_all(obedit);
 +
 +              ok = true;
 +
 +              /* note, notifier might evolve */
 +              WM_event_add_notifier(C, NC_OBJECT | ND_POSE, obedit);
 +      }
 +      MEM_freeN(bases);
  
        /* recalc/redraw + cleanup */
        WM_cursor_wait(0);
@@@ -1610,12 -1609,12 +1610,12 @@@ static void poselib_preview_cleanup(bCo
                poselib_backup_restore(pld);
  
                /* old optimize trick... this enforces to bypass the depgraph
-                *      - note: code copied from transform_generics.c -> recalcData()
+                * - note: code copied from transform_generics.c -> recalcData()
                 */
                if ((arm->flag & ARM_DELAYDEFORM) == 0)
 -                      DAG_id_tag_update(&ob->id, OB_RECALC_DATA);  /* sets recalc flags */
 +                      DEG_id_tag_update(&ob->id, OB_RECALC_DATA);  /* sets recalc flags */
                else
 -                      BKE_pose_where_is(scene, ob);
 +                      BKE_pose_where_is(CTX_data_depsgraph(C), scene, ob);
        }
        else if (pld->state == PL_PREVIEW_CONFIRM) {
                /* tag poses as appropriate */
@@@ -672,59 -623,34 +672,59 @@@ typedef enum ePose_SelectSame_Mode 
        POSE_SEL_SAME_KEYINGSET  = 2,
  } ePose_SelectSame_Mode;
  
 -static bool pose_select_same_group(bContext *C, Object *ob, bool extend)
 +static bool pose_select_same_group(bContext *C, bool extend)
  {
 -      bArmature *arm = (ob) ? ob->data : NULL;
 -      bPose *pose = (ob) ? ob->pose : NULL;
 -      char *group_flags;
 -      int numGroups = 0;
 +      ViewLayer *view_layer = CTX_data_view_layer(C);
 +      bool *group_flags_array;
 +      bool *group_flags = NULL;
 +      int groups_len = 0;
        bool changed = false, tagged = false;
 +      Object *ob_prev = NULL;
 +      uint ob_index;
 +
 +      uint objects_len = 0;
 +      Object **objects = BKE_view_layer_array_from_objects_in_mode_unique_data(view_layer, &objects_len, OB_MODE_POSE);
 +      for (ob_index = 0; ob_index < objects_len; ob_index++) {
 +              Object *ob = BKE_object_pose_armature_get(objects[ob_index]);
 +              bArmature *arm = (ob) ? ob->data : NULL;
 +              bPose *pose = (ob) ? ob->pose : NULL;
 +
 +              /* Sanity checks. */
 +              if (ELEM(NULL, ob, pose, arm)) {
 +                      continue;
 +              }
  
 -      /* sanity checks */
 -      if (ELEM(NULL, ob, pose, arm))
 -              return 0;
 +              ob->id.tag &= ~LIB_TAG_DOIT;
 +              groups_len = MAX2(groups_len, BLI_listbase_count(&pose->agroups));
 +      }
  
 -      /* count the number of groups */
 -      numGroups = BLI_listbase_count(&pose->agroups);
 -      if (numGroups == 0)
 -              return 0;
 +      /* Nothing to do here. */
 +      if (groups_len == 0) {
 +              MEM_freeN(objects);
 +              return false;
 +      }
  
        /* alloc a small array to keep track of the groups to use
-        *  - each cell stores on/off state for whether group should be used
-        *      - size is (groups_len + 1), since (index = 0) is used for no-group
+        * - each cell stores on/off state for whether group should be used
 -       * - size is (numGroups + 1), since (index = 0) is used for no-group
++       * - size is (groups_len + 1), since (index = 0) is used for no-group
         */
 -      group_flags = MEM_callocN(numGroups + 1, "pose_select_same_group");
 +      groups_len++;
 +      group_flags_array = MEM_callocN(objects_len * groups_len * sizeof(bool), "pose_select_same_group");
  
 -      CTX_DATA_BEGIN (C, bPoseChannel *, pchan, visible_pose_bones)
 +      group_flags = NULL;
 +      ob_index = -1;
 +      ob_prev = NULL;
 +      CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, visible_pose_bones, Object, *ob)
        {
 +              if (ob != ob_prev) {
 +                      ob_index++;
 +                      group_flags = group_flags_array + (ob_index * groups_len);
 +                      ob_prev = ob;
 +              }
 +
                /* keep track of group as group to use later? */
                if (pchan->bone->flag & BONE_SELECTED) {
 -                      group_flags[pchan->agrp_index] = 1;
 +                      group_flags[pchan->agrp_index] = true;
                        tagged = true;
                }
  
@@@ -1334,13 -1261,13 +1334,13 @@@ typedef union tPosePropagate_ModeData 
  /* get frame on which the "hold" for the bone ends
   * XXX: this may not really work that well if a bone moves on some channels and not others
   *      if this happens to be a major issue, scrap this, and just make this happen
-  *            independently per F-Curve
+  *      independently per F-Curve
   */
 -static float pose_propagate_get_boneHoldEndFrame(Object *ob, tPChanFCurveLink *pfl, float startFrame)
 +static float pose_propagate_get_boneHoldEndFrame(tPChanFCurveLink *pfl, float startFrame)
  {
 -      DLRBT_Tree keys, blocks;
 -      ActKeyBlock *ab;
 +      DLRBT_Tree keys;
  
 +      Object *ob = pfl->ob;
        AnimData *adt = ob->adt;
        LinkData *ld;
        float endFrame = startFrame;
  
        for (ld = pfl->fcurves.first; ld; ld = ld->next) {
                FCurve *fcu = (FCurve *)ld->data;
 -              fcurve_to_keylist(adt, fcu, &keys, &blocks);
 +              fcurve_to_keylist(adt, fcu, &keys, 0);
        }
  
 -      BLI_dlrbTree_linkedlist_sync(&keys);
 -      BLI_dlrbTree_linkedlist_sync(&blocks);
 -
        /* find the long keyframe (i.e. hold), and hence obtain the endFrame value
-        *      - the best case would be one that starts on the frame itself
+        * - the best case would be one that starts on the frame itself
         */
 -      ab = (ActKeyBlock *)BLI_dlrbTree_search_exact(&blocks, compare_ab_cfraPtr, &startFrame);
 -
 -      if (actkeyblock_is_valid(ab, &keys) == 0) {
 -              /* There are only two cases for no-exact match:
 -               *  1) the current frame is just before another key but not on a key itself
 -               *  2) the current frame is on a key, but that key doesn't link to the next
 -               *
 -               * If we've got the first case, then we can search for another block,
 -               * otherwise forget it, as we'd be overwriting some valid data.
 -               */
 -              if (BLI_dlrbTree_search_exact(&keys, compare_ak_cfraPtr, &startFrame) == NULL) {
 -                      /* we've got case 1, so try the one after */
 -                      ab = (ActKeyBlock *)BLI_dlrbTree_search_next(&blocks, compare_ab_cfraPtr, &startFrame);
 -
 -                      if (actkeyblock_is_valid(ab, &keys) == 0) {
 -                              /* try the block before this frame then as last resort */
 -                              ab = (ActKeyBlock *)BLI_dlrbTree_search_prev(&blocks, compare_ab_cfraPtr, &startFrame);
 -
 -                              /* whatever happens, stop searching now... */
 -                              if (actkeyblock_is_valid(ab, &keys) == 0) {
 -                                      /* restrict range to just the frame itself
 -                                       * i.e. everything is in motion, so no holds to safely overwrite
 -                                       */
 -                                      ab = NULL;
 -                              }
 -                      }
 -              }
 -              else {
 -                      /* we've got case 2 - set ab to NULL just in case, since we shouldn't do anything in this case */
 -                      ab = NULL;
 +      ActKeyColumn *ab = (ActKeyColumn *)BLI_dlrbTree_search_exact(&keys, compare_ak_cfraPtr, &startFrame);
 +
 +      /* There are only two cases for no-exact match:
 +       *  1) the current frame is just before another key but not on a key itself
 +       *  2) the current frame is on a key, but that key doesn't link to the next
 +       *
 +       * If we've got the first case, then we can search for another block,
 +       * otherwise forget it, as we'd be overwriting some valid data.
 +       */
 +      if (ab == NULL) {
 +              /* we've got case 1, so try the one after */
 +              ab = (ActKeyColumn *)BLI_dlrbTree_search_next(&keys, compare_ak_cfraPtr, &startFrame);
 +
 +              if ((actkeyblock_get_valid_hold(ab) & ACTKEYBLOCK_FLAG_STATIC_HOLD) == 0) {
 +                      /* try the block before this frame then as last resort */
 +                      ab = (ActKeyColumn *)BLI_dlrbTree_search_prev(&keys, compare_ak_cfraPtr, &startFrame);
                }
        }
  
@@@ -133,12 -125,12 +133,12 @@@ static int apply_armature_pose2bones_ex
                curbone = ED_armature_ebone_find_name(arm->edbo, pchan->name);
  
                /* simply copy the head/tail values from pchan over to curbone */
 -              copy_v3_v3(curbone->head, pchan->pose_head);
 -              copy_v3_v3(curbone->tail, pchan->pose_tail);
 +              copy_v3_v3(curbone->head, pchan_eval->pose_head);
 +              copy_v3_v3(curbone->tail, pchan_eval->pose_tail);
  
                /* fix roll:
-                *      1. find auto-calculated roll value for this bone now
-                *      2. remove this from the 'visual' y-rotation
+                * 1. find auto-calculated roll value for this bone now
+                * 2. remove this from the 'visual' y-rotation
                 */
                {
                        float premat[3][3], imat[3][3], pmat[3][3], tmat[3][3];
@@@ -284,71 -234,44 +284,71 @@@ void poseAnim_mapping_reset(ListBase *p
  }
  
  /* perform autokeyframing after changes were made + confirmed */
 -void poseAnim_mapping_autoKeyframe(bContext *C, Scene *scene, Object *ob, ListBase *pfLinks, float cframe)
 +void poseAnim_mapping_autoKeyframe(bContext *C, Scene *scene, ListBase *pfLinks, float cframe)
  {
 -      Main *bmain = CTX_data_main(C);
 +      ViewLayer *view_layer = CTX_data_view_layer(C);
 +      bool skip = true;
 +
 +      FOREACH_OBJECT_IN_MODE_BEGIN(view_layer, OB_MODE_POSE, ob) {
 +              ob->id.tag &= ~LIB_TAG_DOIT;
 +              ob = poseAnim_object_get(ob);
 +
 +              /* Ensure validity of the settings from the context. */
 +              if (ob == NULL) {
 +                      continue;
 +              }
 +
 +              if (autokeyframe_cfra_can_key(scene, &ob->id)) {
 +                      ob->id.tag |= LIB_TAG_DOIT;
 +                      skip = false;
 +              }
 +      } FOREACH_OBJECT_IN_MODE_END;
 +
 +      if (skip) {
 +              return;
 +      }
  
        /* insert keyframes as necessary if autokeyframing */
 -      if (autokeyframe_cfra_can_key(scene, &ob->id)) {
 -              KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_WHOLE_CHARACTER_ID);
 -              ListBase dsources = {NULL, NULL};
 -              tPChanFCurveLink *pfl;
 -
 -              /* iterate over each pose-channel affected, tagging bones to be keyed */
 -              /* XXX: here we already have the information about what transforms exist, though
 -               * it might be easier to just overwrite all using normal mechanisms
 -               */
 -              for (pfl = pfLinks->first; pfl; pfl = pfl->next) {
 -                      bPoseChannel *pchan = pfl->pchan;
 -
 -                      /* add datasource override for the PoseChannel, to be used later */
 -                      ANIM_relative_keyingset_add_source(&dsources, &ob->id, &RNA_PoseBone, pchan);
 -
 -                      /* clear any unkeyed tags */
 -                      if (pchan->bone)
 -                              pchan->bone->flag &= ~BONE_UNKEYED;
 +      KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_WHOLE_CHARACTER_ID);
 +      ListBase dsources = {NULL, NULL};
 +      tPChanFCurveLink *pfl;
 +
 +      /* iterate over each pose-channel affected, tagging bones to be keyed */
 +      /* XXX: here we already have the information about what transforms exist, though
 +       * it might be easier to just overwrite all using normal mechanisms
 +       */
 +      for (pfl = pfLinks->first; pfl; pfl = pfl->next) {
 +              bPoseChannel *pchan = pfl->pchan;
 +
 +              if ((pfl->ob->id.tag & LIB_TAG_DOIT) == 0) {
 +                      continue;
                }
  
 -              /* insert keyframes for all relevant bones in one go */
 -              ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, cframe);
 -              BLI_freelistN(&dsources);
 -
 -              /* do the bone paths
 -               * - only do this if keyframes should have been added
 -               * - do not calculate unless there are paths already to update...
 -               */
 -              if (ob->pose->avs.path_bakeflag & MOTIONPATH_BAKE_HAS_PATHS) {
 -                      //ED_pose_clear_paths(C, ob); // XXX for now, don't need to clear
 -                      ED_pose_recalculate_paths(bmain, scene, ob);
 +              /* add datasource override for the PoseChannel, to be used later */
 +              ANIM_relative_keyingset_add_source(&dsources, &pfl->ob->id, &RNA_PoseBone, pchan);
 +
 +              /* clear any unkeyed tags */
 +              if (pchan->bone) {
 +                      pchan->bone->flag &= ~BONE_UNKEYED;
                }
        }
-        *      - only do this if keyframes should have been added
-        *      - do not calculate unless there are paths already to update...
 +
 +      /* insert keyframes for all relevant bones in one go */
 +      ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, cframe);
 +      BLI_freelistN(&dsources);
 +
 +      /* do the bone paths
++       * - only do this if keyframes should have been added
++       * - do not calculate unless there are paths already to update...
 +       */
 +      FOREACH_OBJECT_IN_MODE_BEGIN(view_layer, OB_MODE_POSE, ob) {
 +              if (ob->id.tag & LIB_TAG_DOIT) {
 +                      if (ob->pose->avs.path_bakeflag & MOTIONPATH_BAKE_HAS_PATHS) {
 +                              //ED_pose_clear_paths(C, ob); // XXX for now, don't need to clear
 +                              ED_pose_recalculate_paths(C, scene, ob, false);
 +                      }
 +              }
 +      } FOREACH_OBJECT_IN_MODE_END;
  }
  
  /* ------------------------- */
index 1a2d6d6,0000000..1a51875
mode 100644,000000..100644
--- /dev/null
@@@ -1,1053 -1,0 +1,1053 @@@
-                                *      - make points slightly closer to center (about halfway across)
 +/*
 + * ***** BEGIN GPL LICENSE BLOCK *****
 + *
 + * 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.
 + *
 + * The Original Code is Copyright (C) 2008, Blender Foundation
 + * This is a new part of Blender
 + *
 + * Contributor(s): Joshua Leung, Antonio Vazquez
 + *
 + * ***** END GPL LICENSE BLOCK *****
 + */
 +
 +/** \file blender/editors/gpencil/annotate_draw.c
 + *  \ingroup edgpencil
 + */
 +
 +
 +#include <stdio.h>
 +#include <string.h>
 +#include <stdlib.h>
 +#include <stddef.h>
 +#include <math.h>
 +#include <float.h>
 +
 +#include "MEM_guardedalloc.h"
 +
 +#include "BLI_sys_types.h"
 +
 +#include "BLI_math.h"
 +#include "BLI_utildefines.h"
 +#include "BLI_polyfill_2d.h"
 +
 +#include "BLF_api.h"
 +#include "BLT_translation.h"
 +
 +#include "DNA_gpencil_types.h"
 +#include "DNA_scene_types.h"
 +#include "DNA_screen_types.h"
 +#include "DNA_space_types.h"
 +#include "DNA_view3d_types.h"
 +#include "DNA_userdef_types.h"
 +#include "DNA_object_types.h"
 +
 +#include "BKE_context.h"
 +#include "BKE_global.h"
 +#include "BKE_gpencil.h"
 +
 +#include "WM_api.h"
 +
 +#include "BIF_glutil.h"
 +
 +#include "GPU_immediate.h"
 +#include "GPU_draw.h"
 +#include "GPU_state.h"
 +
 +#include "ED_gpencil.h"
 +#include "ED_screen.h"
 +#include "ED_view3d.h"
 +#include "ED_space_api.h"
 +
 +#include "UI_interface_icons.h"
 +#include "UI_resources.h"
 +
 +/* ************************************************** */
 +/* GREASE PENCIL DRAWING */
 +
 +/* ----- General Defines ------ */
 +/* flags for sflag */
 +typedef enum eDrawStrokeFlags {
 +      GP_DRAWDATA_NOSTATUS    = (1 << 0),   /* don't draw status info */
 +      GP_DRAWDATA_ONLY3D      = (1 << 1),   /* only draw 3d-strokes */
 +      GP_DRAWDATA_ONLYV2D     = (1 << 2),   /* only draw 'canvas' strokes */
 +      GP_DRAWDATA_ONLYI2D     = (1 << 3),   /* only draw 'image' strokes */
 +      GP_DRAWDATA_IEDITHACK   = (1 << 4),   /* special hack for drawing strokes in Image Editor (weird coordinates) */
 +      GP_DRAWDATA_NO_XRAY     = (1 << 5),   /* don't draw xray in 3D view (which is default) */
 +      GP_DRAWDATA_NO_ONIONS   = (1 << 6),       /* no onionskins should be drawn (for animation playback) */
 +} eDrawStrokeFlags;
 +
 +
 +/* ----- Tool Buffer Drawing ------ */
 +
 +/* draw stroke defined in buffer (simple ogl lines/points for now, as dotted lines) */
 +static void gp_draw_stroke_buffer(
 +        const tGPspoint *points, int totpoints, short thickness,
 +        short dflag, short sflag, float ink[4])
 +{
 +      int draw_points = 0;
 +
 +      /* error checking */
 +      if ((points == NULL) || (totpoints <= 0))
 +              return;
 +
 +      /* check if buffer can be drawn */
 +      if (dflag & (GP_DRAWDATA_ONLY3D | GP_DRAWDATA_ONLYV2D))
 +              return;
 +
 +      if (sflag & GP_STROKE_ERASER) {
 +              /* don't draw stroke at all! */
 +              return;
 +      }
 +
 +      GPUVertFormat *format = immVertexFormat();
 +      uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
 +
 +      const tGPspoint *pt = points;
 +
 +      if (totpoints == 1) {
 +              /* if drawing a single point, draw it larger */
 +              GPU_point_size((float)(thickness + 2) * points->pressure);
 +              immBindBuiltinProgram(GPU_SHADER_3D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA);
 +              immUniformColor3fvAlpha(ink, ink[3]);
 +              immBegin(GPU_PRIM_POINTS, 1);
 +              immVertex2iv(pos, &pt->x);
 +      }
 +      else {
 +              float oldpressure = points[0].pressure;
 +
 +              /* draw stroke curve */
 +              GPU_line_width(max_ff(oldpressure * thickness, 1.0));
 +
 +              immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
 +              immUniformColor3fvAlpha(ink, ink[3]);
 +
 +              /* TODO: implement this with a geometry shader to draw one continuous tapered stroke */
 +              immBeginAtMost(GPU_PRIM_LINE_STRIP, totpoints);
 +
 +              for (int i = 0; i < totpoints; i++, pt++) {
 +                      /* if there was a significant pressure change, stop the curve, change the thickness of the stroke,
 +                       * and continue drawing again (since line-width cannot change in middle of GL_LINE_STRIP)
 +                       */
 +                      if (fabsf(pt->pressure - oldpressure) > 0.2f) {
 +                              /* need to have 2 points to avoid immEnd assert error */
 +                              if (draw_points < 2) {
 +                                      immVertex2iv(pos, &(pt - 1)->x);
 +                              }
 +
 +                              immEnd();
 +                              draw_points = 0;
 +
 +                              GPU_line_width(max_ff(pt->pressure * thickness, 1.0f));
 +                              immBeginAtMost(GPU_PRIM_LINE_STRIP, totpoints - i + 1);
 +
 +                              /* need to roll-back one point to ensure that there are no gaps in the stroke */
 +                              if (i != 0) {
 +                                      immVertex2iv(pos, &(pt - 1)->x);
 +                                      draw_points++;
 +                              }
 +
 +                              oldpressure = pt->pressure; /* reset our threshold */
 +                      }
 +
 +                      /* now the point we want */
 +                      immVertex2iv(pos, &pt->x);
 +                      draw_points++;
 +              }
 +              /* need to have 2 points to avoid immEnd assert error */
 +              if (draw_points < 2) {
 +                      immVertex2iv(pos, &(pt - 1)->x);
 +              }
 +      }
 +
 +      immEnd();
 +      immUnbindProgram();
 +}
 +
 +/* --------- 2D Stroke Drawing Helpers --------- */
 +/* change in parameter list */
 +static void gp_calc_2d_stroke_fxy(const float pt[3], short sflag, int offsx, int offsy, int winx, int winy, float r_co[2])
 +{
 +      if (sflag & GP_STROKE_2DSPACE) {
 +              r_co[0] = pt[0];
 +              r_co[1] = pt[1];
 +      }
 +      else if (sflag & GP_STROKE_2DIMAGE) {
 +              const float x = (float)((pt[0] * winx) + offsx);
 +              const float y = (float)((pt[1] * winy) + offsy);
 +
 +              r_co[0] = x;
 +              r_co[1] = y;
 +      }
 +      else {
 +              const float x = (float)(pt[0] / 100 * winx) + offsx;
 +              const float y = (float)(pt[1] / 100 * winy) + offsy;
 +
 +              r_co[0] = x;
 +              r_co[1] = y;
 +      }
 +}
 +
 +/* ----- Existing Strokes Drawing (3D and Point) ------ */
 +
 +/* draw a given stroke - just a single dot (only one point) */
 +static void gp_draw_stroke_point(
 +        const bGPDspoint *points, short thickness, short UNUSED(dflag), short sflag,
 +        int offsx, int offsy, int winx, int winy, const float ink[4])
 +{
 +      const bGPDspoint *pt = points;
 +
 +      /* get final position using parent matrix */
 +      float fpt[3];
 +      copy_v3_v3(fpt, &pt->x);
 +
 +      GPUVertFormat *format = immVertexFormat();
 +      uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
 +
 +      if (sflag & GP_STROKE_3DSPACE) {
 +              immBindBuiltinProgram(GPU_SHADER_3D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA);
 +      }
 +      else {
 +              immBindBuiltinProgram(GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA);
 +
 +              /* get 2D coordinates of point */
 +              float co[3] = { 0.0f };
 +              gp_calc_2d_stroke_fxy(fpt, sflag, offsx, offsy, winx, winy, co);
 +              copy_v3_v3(fpt, co);
 +      }
 +
 +      /* set color */
 +      immUniformColor3fvAlpha(ink, ink[3]);
 +
 +      /* set point thickness (since there's only one of these) */
 +      immUniform1f("size", (float)(thickness + 2) * pt->pressure);
 +
 +      immBegin(GPU_PRIM_POINTS, 1);
 +      immVertex3fv(pos, fpt);
 +      immEnd();
 +
 +      immUnbindProgram();
 +}
 +
 +/* draw a given stroke in 3d (i.e. in 3d-space), using simple ogl lines */
 +static void gp_draw_stroke_3d(
 +        const bGPDspoint *points, int totpoints, short thickness, bool UNUSED(debug),
 +        short UNUSED(sflag), const float ink[4], bool cyclic)
 +{
 +      float curpressure = points[0].pressure;
 +      float cyclic_fpt[3];
 +      int draw_points = 0;
 +
 +      /* if cyclic needs one vertex more */
 +      int cyclic_add = 0;
 +      if (cyclic) {
 +              cyclic_add++;
 +      }
 +
 +
 +      GPUVertFormat *format = immVertexFormat();
 +      uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
 +
 +      immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
 +      immUniformColor3fvAlpha(ink, ink[3]);
 +
 +      /* TODO: implement this with a geometry shader to draw one continuous tapered stroke */
 +
 +      /* draw stroke curve */
 +      GPU_line_width(max_ff(curpressure * thickness, 1.0f));
 +      immBeginAtMost(GPU_PRIM_LINE_STRIP, totpoints + cyclic_add);
 +      const bGPDspoint *pt = points;
 +      for (int i = 0; i < totpoints; i++, pt++) {
 +              /* if there was a significant pressure change, stop the curve, change the thickness of the stroke,
 +               * and continue drawing again (since line-width cannot change in middle of GL_LINE_STRIP)
 +               * Note: we want more visible levels of pressures when thickness is bigger.
 +               */
 +              if (fabsf(pt->pressure - curpressure) > 0.2f / (float)thickness) {
 +                      /* if the pressure changes before get at least 2 vertices, need to repeat last point to avoid assert in immEnd() */
 +                      if (draw_points < 2) {
 +                              const bGPDspoint *pt2 = pt - 1;
 +                              immVertex3fv(pos, &pt2->x);
 +                      }
 +                      immEnd();
 +                      draw_points = 0;
 +
 +                      curpressure = pt->pressure;
 +                      GPU_line_width(max_ff(curpressure * thickness, 1.0f));
 +                      immBeginAtMost(GPU_PRIM_LINE_STRIP, totpoints - i + 1 + cyclic_add);
 +
 +                      /* need to roll-back one point to ensure that there are no gaps in the stroke */
 +                      if (i != 0) {
 +                              const bGPDspoint *pt2 = pt - 1;
 +                              immVertex3fv(pos, &pt2->x);
 +                              draw_points++;
 +                      }
 +              }
 +
 +              /* now the point we want */
 +              immVertex3fv(pos, &pt->x);
 +              draw_points++;
 +
 +              if (cyclic && i == 0) {
 +                      /* save first point to use in cyclic */
 +                      copy_v3_v3(cyclic_fpt, &pt->x);
 +              }
 +      }
 +
 +      if (cyclic) {
 +              /* draw line to first point to complete the cycle */
 +              immVertex3fv(pos, cyclic_fpt);
 +              draw_points++;
 +      }
 +
 +      /* if less of two points, need to repeat last point to avoid assert in immEnd() */
 +      if (draw_points < 2) {
 +              const bGPDspoint *pt2 = pt - 1;
 +              immVertex3fv(pos, &pt2->x);
 +      }
 +
 +      immEnd();
 +      immUnbindProgram();
 +}
 +
 +/* ----- Fancy 2D-Stroke Drawing ------ */
 +
 +/* draw a given stroke in 2d */
 +static void gp_draw_stroke_2d(
 +        const bGPDspoint *points, int totpoints, short thickness_s, short dflag, short sflag,
 +        bool UNUSED(debug), int offsx, int offsy, int winx, int winy, const float ink[4])
 +{
 +      /* otherwise thickness is twice that of the 3D view */
 +      float thickness = (float)thickness_s * 0.5f;
 +
 +      /* strokes in Image Editor need a scale factor, since units there are not pixels! */
 +      float scalefac  = 1.0f;
 +      if ((dflag & GP_DRAWDATA_IEDITHACK) && (dflag & GP_DRAWDATA_ONLYV2D)) {
 +              scalefac = 0.001f;
 +      }
 +
 +      /* TODO: fancy++ with the magic of shaders */
 +
 +      /* tessellation code - draw stroke as series of connected quads (triangle strips in fact) with connection
 +       * edges rotated to minimize shrinking artifacts, and rounded endcaps
 +       */
 +      {
 +              const bGPDspoint *pt1, *pt2;
 +              float s0[2], s1[2];     /* segment 'center' points */
 +              float pm[2];  /* normal from previous segment. */
 +              int i;
 +
 +              GPUVertFormat *format = immVertexFormat();
 +              uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
 +
 +              immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
 +              immUniformColor3fvAlpha(ink, ink[3]);
 +              immBegin(GPU_PRIM_TRI_STRIP, totpoints * 2 + 4);
 +
 +              /* get x and y coordinates from first point */
 +              gp_calc_2d_stroke_fxy(&points->x, sflag, offsx, offsy, winx, winy, s0);
 +
 +              for (i = 0, pt1 = points, pt2 = points + 1; i < (totpoints - 1); i++, pt1++, pt2++) {
 +                      float t0[2], t1[2];     /* tessellated coordinates */
 +                      float m1[2], m2[2];     /* gradient and normal */
 +                      float mt[2], sc[2];     /* gradient for thickness, point for end-cap */
 +                      float pthick;           /* thickness at segment point */
 +
 +                      /* get x and y coordinates from point2 (point1 has already been computed in previous iteration). */
 +                      gp_calc_2d_stroke_fxy(&pt2->x, sflag, offsx, offsy, winx, winy, s1);
 +
 +                      /* calculate gradient and normal - 'angle'=(ny/nx) */
 +                      m1[1] = s1[1] - s0[1];
 +                      m1[0] = s1[0] - s0[0];
 +                      normalize_v2(m1);
 +                      m2[1] = -m1[0];
 +                      m2[0] = m1[1];
 +
 +                      /* always use pressure from first point here */
 +                      pthick = (pt1->pressure * thickness * scalefac);
 +
 +                      /* if the first segment, start of segment is segment's normal */
 +                      if (i == 0) {
 +                              /* draw start cap first
-                                *      - if cross-section not as thick as it should be, add extra padding to fix it
++                               * - make points slightly closer to center (about halfway across)
 +                               */
 +                              mt[0] = m2[0] * pthick * 0.5f;
 +                              mt[1] = m2[1] * pthick * 0.5f;
 +                              sc[0] = s0[0] - (m1[0] * pthick * 0.75f);
 +                              sc[1] = s0[1] - (m1[1] * pthick * 0.75f);
 +
 +                              t0[0] = sc[0] - mt[0];
 +                              t0[1] = sc[1] - mt[1];
 +                              t1[0] = sc[0] + mt[0];
 +                              t1[1] = sc[1] + mt[1];
 +
 +                              /* First two points of cap. */
 +                              immVertex2fv(pos, t0);
 +                              immVertex2fv(pos, t1);
 +
 +                              /* calculate points for start of segment */
 +                              mt[0] = m2[0] * pthick;
 +                              mt[1] = m2[1] * pthick;
 +
 +                              t0[0] = s0[0] - mt[0];
 +                              t0[1] = s0[1] - mt[1];
 +                              t1[0] = s0[0] + mt[0];
 +                              t1[1] = s0[1] + mt[1];
 +
 +                              /* Last two points of start cap (and first two points of first segment). */
 +                              immVertex2fv(pos, t0);
 +                              immVertex2fv(pos, t1);
 +                      }
 +                      /* if not the first segment, use bisector of angle between segments */
 +                      else {
 +                              float mb[2];         /* bisector normal */
 +                              float athick, dfac;  /* actual thickness, difference between thicknesses */
 +
 +                              /* calculate gradient of bisector (as average of normals) */
 +                              mb[0] = (pm[0] + m2[0]) / 2;
 +                              mb[1] = (pm[1] + m2[1]) / 2;
 +                              normalize_v2(mb);
 +
 +                              /* calculate gradient to apply
 +                               *  - as basis, use just pthick * bisector gradient
-                                *      - make points slightly closer to center (about halfway across)
++                               * - if cross-section not as thick as it should be, add extra padding to fix it
 +                               */
 +                              mt[0] = mb[0] * pthick;
 +                              mt[1] = mb[1] * pthick;
 +                              athick = len_v2(mt);
 +                              dfac = pthick - (athick * 2);
 +
 +                              if (((athick * 2.0f) < pthick) && (IS_EQF(athick, pthick) == 0)) {
 +                                      mt[0] += (mb[0] * dfac);
 +                                      mt[1] += (mb[1] * dfac);
 +                              }
 +
 +                              /* calculate points for start of segment */
 +                              t0[0] = s0[0] - mt[0];
 +                              t0[1] = s0[1] - mt[1];
 +                              t1[0] = s0[0] + mt[0];
 +                              t1[1] = s0[1] + mt[1];
 +
 +                              /* Last two points of previous segment, and first two points of current segment. */
 +                              immVertex2fv(pos, t0);
 +                              immVertex2fv(pos, t1);
 +                      }
 +
 +                      /* if last segment, also draw end of segment (defined as segment's normal) */
 +                      if (i == totpoints - 2) {
 +                              /* for once, we use second point's pressure (otherwise it won't be drawn) */
 +                              pthick = (pt2->pressure * thickness * scalefac);
 +
 +                              /* calculate points for end of segment */
 +                              mt[0] = m2[0] * pthick;
 +                              mt[1] = m2[1] * pthick;
 +
 +                              t0[0] = s1[0] - mt[0];
 +                              t0[1] = s1[1] - mt[1];
 +                              t1[0] = s1[0] + mt[0];
 +                              t1[1] = s1[1] + mt[1];
 +
 +                              /* Last two points of last segment (and first two points of end cap). */
 +                              immVertex2fv(pos, t0);
 +                              immVertex2fv(pos, t1);
 +
 +                              /* draw end cap as last step
-  *    We need to review the calls below, since they may be/are not that suitable for
-  *    the new ways that we intend to be drawing data...
++                               * - make points slightly closer to center (about halfway across)
 +                               */
 +                              mt[0] = m2[0] * pthick * 0.5f;
 +                              mt[1] = m2[1] * pthick * 0.5f;
 +                              sc[0] = s1[0] + (m1[0] * pthick * 0.75f);
 +                              sc[1] = s1[1] + (m1[1] * pthick * 0.75f);
 +
 +                              t0[0] = sc[0] - mt[0];
 +                              t0[1] = sc[1] - mt[1];
 +                              t1[0] = sc[0] + mt[0];
 +                              t1[1] = sc[1] + mt[1];
 +
 +                              /* Last two points of end cap. */
 +                              immVertex2fv(pos, t0);
 +                              immVertex2fv(pos, t1);
 +                      }
 +
 +                      /* store computed point2 coordinates as point1 ones of next segment. */
 +                      copy_v2_v2(s0, s1);
 +                      /* store stroke's 'natural' normal for next stroke to use */
 +                      copy_v2_v2(pm, m2);
 +              }
 +
 +              immEnd();
 +              immUnbindProgram();
 +      }
 +}
 +
 +/* ----- Strokes Drawing ------ */
 +
 +/* Helper for doing all the checks on whether a stroke can be drawn */
 +static bool gp_can_draw_stroke(const bGPDstroke *gps, const int dflag)
 +{
 +      /* skip stroke if it isn't in the right display space for this drawing context */
 +      /* 1) 3D Strokes */
 +      if ((dflag & GP_DRAWDATA_ONLY3D) && !(gps->flag & GP_STROKE_3DSPACE))
 +              return false;
 +      if (!(dflag & GP_DRAWDATA_ONLY3D) && (gps->flag & GP_STROKE_3DSPACE))
 +              return false;
 +
 +      /* 2) Screen Space 2D Strokes */
 +      if ((dflag & GP_DRAWDATA_ONLYV2D) && !(gps->flag & GP_STROKE_2DSPACE))
 +              return false;
 +      if (!(dflag & GP_DRAWDATA_ONLYV2D) && (gps->flag & GP_STROKE_2DSPACE))
 +              return false;
 +
 +      /* 3) Image Space (2D) */
 +      if ((dflag & GP_DRAWDATA_ONLYI2D) && !(gps->flag & GP_STROKE_2DIMAGE))
 +              return false;
 +      if (!(dflag & GP_DRAWDATA_ONLYI2D) && (gps->flag & GP_STROKE_2DIMAGE))
 +              return false;
 +
 +      /* skip stroke if it doesn't have any valid data */
 +      if ((gps->points == NULL) || (gps->totpoints < 1))
 +              return false;
 +
 +      /* stroke can be drawn */
 +      return true;
 +}
 +
 +/* draw a set of strokes */
 +static void gp_draw_strokes(
 +        bGPdata *UNUSED(gpd), bGPDlayer *UNUSED(gpl), const bGPDframe *gpf, int offsx, int offsy, int winx, int winy,
 +        int dflag, bool debug, short lthick, const float color[4])
 +{
 +      GPU_enable_program_point_size();
 +
 +      for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
 +              /* check if stroke can be drawn */
 +              if (gp_can_draw_stroke(gps, dflag) == false) {
 +                      continue;
 +              }
 +
 +              /* check which stroke-drawer to use */
 +              if (dflag & GP_DRAWDATA_ONLY3D) {
 +                      const int no_xray = (dflag & GP_DRAWDATA_NO_XRAY);
 +                      int mask_orig = 0;
 +
 +                      if (no_xray) {
 +                              glGetIntegerv(GL_DEPTH_WRITEMASK, &mask_orig);
 +                              glDepthMask(0);
 +                              GPU_depth_test(true);
 +
 +                              /* first arg is normally rv3d->dist, but this isn't
 +                               * available here and seems to work quite well without */
 +                              bglPolygonOffset(1.0f, 1.0f);
 +                      }
 +
 +                      /* 3D Lines - OpenGL primitives-based */
 +                      if (gps->totpoints == 1) {
 +                              gp_draw_stroke_point(gps->points, lthick, dflag, gps->flag, offsx, offsy, winx, winy, color);
 +                      }
 +                      else {
 +                              gp_draw_stroke_3d(
 +                                      gps->points, gps->totpoints, lthick, debug, gps->flag,
 +                                      color, gps->flag & GP_STROKE_CYCLIC);
 +                      }
 +
 +                      if (no_xray) {
 +                              glDepthMask(mask_orig);
 +                              GPU_depth_test(false);
 +
 +                              bglPolygonOffset(0.0, 0.0);
 +                      }
 +              }
 +              else {
 +                      /* 2D Strokes... */
 +                      if (gps->totpoints == 1) {
 +                              gp_draw_stroke_point(gps->points, lthick, dflag, gps->flag, offsx, offsy, winx, winy, color);
 +                      }
 +                      else {
 +                              gp_draw_stroke_2d(
 +                                      gps->points, gps->totpoints, lthick, dflag, gps->flag, debug,
 +                                      offsx, offsy, winx, winy, color);
 +                      }
 +              }
 +      }
 +
 +      GPU_disable_program_point_size();
 +}
 +
 +/* Draw selected verts for strokes being edited */
 +static void gp_draw_strokes_edit(
 +        bGPdata *gpd, bGPDlayer *gpl, const bGPDframe *gpf,
 +        int offsx, int offsy, int winx, int winy,
 +        short dflag, short UNUSED(lflag), float alpha)
 +{
 +      /* if alpha 0 do not draw */
 +      if (alpha == 0.0f)
 +              return;
 +
 +      const bool no_xray = (dflag & GP_DRAWDATA_NO_XRAY) != 0;
 +      int mask_orig = 0;
 +
 +      /* set up depth masks... */
 +      if (dflag & GP_DRAWDATA_ONLY3D) {
 +              if (no_xray) {
 +                      glGetIntegerv(GL_DEPTH_WRITEMASK, &mask_orig);
 +                      glDepthMask(0);
 +                      GPU_depth_test(true);
 +
 +                      /* first arg is normally rv3d->dist, but this isn't
 +                       * available here and seems to work quite well without */
 +                      bglPolygonOffset(1.0f, 1.0f);
 +              }
 +      }
 +
 +      GPU_enable_program_point_size();
 +
 +      /* draw stroke verts */
 +      for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
 +              /* check if stroke can be drawn */
 +              if (gp_can_draw_stroke(gps, dflag) == false)
 +                      continue;
 +
 +              /* Optimisation: only draw points for selected strokes
 +               * We assume that selected points can only occur in
 +               * strokes that are selected too.
 +               */
 +              if ((gps->flag & GP_STROKE_SELECT) == 0)
 +                      continue;
 +
 +              /* Get size of verts:
 +               * - The selected state needs to be larger than the unselected state so that
 +               *   they stand out more.
 +               * - We use the theme setting for size of the unselected verts
 +               */
 +              float bsize = UI_GetThemeValuef(TH_GP_VERTEX_SIZE);
 +              float vsize;
 +              if ((int)bsize > 8) {
 +                      vsize = 10.0f;
 +                      bsize = 8.0f;
 +              }
 +              else {
 +                      vsize = bsize + 2;
 +              }
 +
 +              float selectColor[4];
 +              UI_GetThemeColor3fv(TH_GP_VERTEX_SELECT, selectColor);
 +              selectColor[3] = alpha;
 +
 +              GPUVertFormat *format = immVertexFormat();
 +              uint pos; /* specified later */
 +              uint size = GPU_vertformat_attr_add(format, "size", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
 +              uint color = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
 +
 +              if (gps->flag & GP_STROKE_3DSPACE) {
 +                      pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
 +                      immBindBuiltinProgram(GPU_SHADER_3D_POINT_VARYING_SIZE_VARYING_COLOR);
 +              }
 +              else {
 +                      pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
 +                      immBindBuiltinProgram(GPU_SHADER_2D_POINT_VARYING_SIZE_VARYING_COLOR);
 +              }
 +
 +              immBegin(GPU_PRIM_POINTS, gps->totpoints);
 +
 +              /* Draw start and end point differently if enabled stroke direction hint */
 +              bool show_direction_hint = (gpd->flag & GP_DATA_SHOW_DIRECTION) && (gps->totpoints > 1);
 +
 +              /* Draw all the stroke points (selected or not) */
 +              bGPDspoint *pt = gps->points;
 +              for (int i = 0; i < gps->totpoints; i++, pt++) {
 +                      /* size and color first */
 +                      if (show_direction_hint && i == 0) {
 +                              /* start point in green bigger */
 +                              immAttr3f(color, 0.0f, 1.0f, 0.0f);
 +                              immAttr1f(size, vsize + 4);
 +                      }
 +                      else if (show_direction_hint && (i == gps->totpoints - 1)) {
 +                              /* end point in red smaller */
 +                              immAttr3f(color, 1.0f, 0.0f, 0.0f);
 +                              immAttr1f(size, vsize + 1);
 +                      }
 +                      else if (pt->flag & GP_SPOINT_SELECT) {
 +                              immAttr3fv(color, selectColor);
 +                              immAttr1f(size, vsize);
 +                      }
 +                      else {
 +                              immAttr3fv(color, gpl->color);
 +                              immAttr1f(size, bsize);
 +                      }
 +
 +                      /* then position */
 +                      if (gps->flag & GP_STROKE_3DSPACE) {
 +                              immVertex3fv(pos, &pt->x);
 +                      }
 +                      else {
 +                              float co[2];
 +                              gp_calc_2d_stroke_fxy(&pt->x, gps->flag, offsx, offsy, winx, winy, co);
 +                              immVertex2fv(pos, co);
 +                      }
 +              }
 +
 +              immEnd();
 +              immUnbindProgram();
 +      }
 +
 +      GPU_disable_program_point_size();
 +
 +      /* clear depth mask */
 +      if (dflag & GP_DRAWDATA_ONLY3D) {
 +              if (no_xray) {
 +                      glDepthMask(mask_orig);
 +                      GPU_depth_test(false);
 +
 +                      bglPolygonOffset(0.0, 0.0);
 +#if 0
 +                      glDisable(GL_POLYGON_OFFSET_LINE);
 +                      glPolygonOffset(0, 0);
 +#endif
 +              }
 +      }
 +}
 +
 +/* ----- General Drawing ------ */
 +
 +/* loop over gpencil data layers, drawing them */
 +static void gp_draw_data_layers(
 +        bGPdata *gpd, int offsx, int offsy, int winx, int winy,
 +        int cfra, int dflag, float alpha)
 +{
 +      float ink[4];
 +
 +      for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
 +              bool debug = (gpl->flag & GP_LAYER_DRAWDEBUG);
 +              short lthick = gpl->thickness;
 +
 +              /* apply layer opacity */
 +              copy_v3_v3(ink, gpl->color);
 +              ink[3] = gpl->opacity;
 +
 +              /* don't draw layer if hidden */
 +              if (gpl->flag & GP_LAYER_HIDE)
 +                      continue;
 +
 +              /* get frame to draw */
 +              bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, cfra, GP_GETFRAME_USE_PREV);
 +              if (gpf == NULL)
 +                      continue;
 +
 +              /* set basic stroke thickness */
 +              GPU_line_width(lthick);
 +
 +              /* Add layer drawing settings to the set of "draw flags"
 +               * NOTE: If the setting doesn't apply, it *must* be cleared,
 +               *       as dflag's carry over from the previous layer
 +               */
 +
 +              /* xray... */
 +              SET_FLAG_FROM_TEST(dflag, gpl->flag & GP_LAYER_NO_XRAY, GP_DRAWDATA_NO_XRAY);
 +
 +              /* draw the strokes already in active frame */
 +              gp_draw_strokes(gpd, gpl, gpf, offsx, offsy, winx, winy, dflag, debug, lthick, ink);
 +
 +              /* Draw verts of selected strokes
 +               *  - when doing OpenGL renders, we don't want to be showing these, as that ends up flickering
 +               *  - locked layers can't be edited, so there's no point showing these verts
 +               *    as they will have no bearings on what gets edited
 +               *  - only show when in editmode, since operators shouldn't work otherwise
 +               *    (NOTE: doing it this way means that the toggling editmode shows visible change immediately)
 +               */
 +              /* XXX: perhaps we don't want to show these when users are drawing... */
 +              if ((G.f & G_RENDER_OGL) == 0 &&
 +                  (gpl->flag & GP_LAYER_LOCKED) == 0 &&
 +                  (gpd->flag & GP_DATA_STROKE_EDITMODE))
 +              {
 +                      gp_draw_strokes_edit(gpd, gpl, gpf, offsx, offsy, winx, winy, dflag, gpl->flag, alpha);
 +              }
 +
 +              /* Check if may need to draw the active stroke cache, only if this layer is the active layer
 +               * that is being edited. (Stroke buffer is currently stored in gp-data)
 +               */
 +              if (ED_gpencil_session_active() && (gpl->flag & GP_LAYER_ACTIVE) &&
 +                  (gpf->flag & GP_FRAME_PAINT))
 +              {
 +                      /* Buffer stroke needs to be drawn with a different linestyle
 +                       * to help differentiate them from normal strokes.
 +                       *
 +                       * It should also be noted that sbuffer contains temporary point types
 +                       * i.e. tGPspoints NOT bGPDspoints
 +                       */
 +                      gp_draw_stroke_buffer(
 +                              gpd->runtime.sbuffer,
 +                              gpd->runtime.sbuffer_size, lthick,
 +                              dflag, gpd->runtime.sbuffer_sflag, ink);
 +              }
 +      }
 +}
 +
 +/* draw a short status message in the top-right corner */
 +static void gp_draw_status_text(const bGPdata *gpd, ARegion *ar)
 +{
 +      rcti rect;
 +
 +      /* Cannot draw any status text when drawing OpenGL Renders */
 +      if (G.f & G_RENDER_OGL)
 +              return;
 +
 +      /* Get bounds of region - Necessary to avoid problems with region overlap */
 +      ED_region_visible_rect(ar, &rect);
 +
 +      /* for now, this should only be used to indicate when we are in stroke editmode */
 +      if (gpd->flag & GP_DATA_STROKE_EDITMODE) {
 +              const char *printable = IFACE_("GPencil Stroke Editing");
 +              float       printable_size[2];
 +
 +              int font_id = BLF_default();
 +
 +              BLF_width_and_height(font_id, printable, BLF_DRAW_STR_DUMMY_MAX, &printable_size[0], &printable_size[1]);
 +
 +              int xco = (rect.xmax - U.widget_unit) - (int)printable_size[0];
 +              int yco = (rect.ymax - U.widget_unit);
 +
 +              /* text label */
 +              UI_FontThemeColor(font_id, TH_TEXT_HI);
 +#ifdef WITH_INTERNATIONAL
 +              BLF_draw_default(xco, yco, 0.0f, printable, BLF_DRAW_STR_DUMMY_MAX);
 +#else
 +              BLF_draw_default_ascii(xco, yco, 0.0f, printable, BLF_DRAW_STR_DUMMY_MAX);
 +#endif
 +
 +              /* grease pencil icon... */
 +              // XXX: is this too intrusive?
 +              GPU_blend_set_func_separate(GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
 +              GPU_blend(true);
 +
 +              xco -= U.widget_unit;
 +              yco -= (int)printable_size[1] / 2;
 +
 +              UI_icon_draw(xco, yco, ICON_GREASEPENCIL);
 +
 +              GPU_blend(false);
 +      }
 +}
 +
 +/* draw grease-pencil datablock */
 +static void gp_draw_data(
 +        bGPdata *gpd, int offsx, int offsy, int winx, int winy,
 +        int cfra, int dflag, float alpha)
 +{
 +      /* turn on smooth lines (i.e. anti-aliasing) */
 +      GPU_line_smooth(true);
 +
 +      /* turn on alpha-blending */
 +      GPU_blend_set_func_separate(GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
 +      GPU_blend(true);
 +
 +      /* draw! */
 +      gp_draw_data_layers(gpd, offsx, offsy, winx, winy, cfra, dflag, alpha);
 +
 +      /* turn off alpha blending, then smooth lines */
 +      GPU_blend(false); // alpha blending
 +      GPU_line_smooth(false); // smooth lines
 +}
 +
 +/* if we have strokes for scenes (3d view)/clips (movie clip editor)
 + * and objects/tracks, multiple data blocks have to be drawn */
 +static void gp_draw_data_all(
 +        Scene *scene, bGPdata *gpd, int offsx, int offsy, int winx, int winy,
 +        int cfra, int dflag, const char spacetype)
 +{
 +      bGPdata *gpd_source = NULL;
 +      float alpha = 1.0f;
 +
 +      if (scene) {
 +              if (spacetype == SPACE_VIEW3D) {
 +                      gpd_source = (scene->gpd ? scene->gpd : NULL);
 +              }
 +              else if (spacetype == SPACE_CLIP && scene->clip) {
 +                      /* currently drawing only gpencil data from either clip or track, but not both - XXX fix logic behind */
 +                      gpd_source = (scene->clip->gpd ? scene->clip->gpd : NULL);
 +              }
 +
 +              if (gpd_source) {
 +                      gp_draw_data(gpd_source, offsx, offsy, winx, winy, cfra, dflag, alpha);
 +              }
 +      }
 +
 +      /* scene/clip data has already been drawn, only object/track data is drawn here
 +       * if gpd_source == gpd, we don't have any object/track data and we can skip */
 +      if (gpd_source == NULL || (gpd_source && gpd_source != gpd)) {
 +              gp_draw_data(gpd, offsx, offsy, winx, winy, cfra, dflag, alpha);
 +      }
 +}
 +
 +/* ----- Grease Pencil Sketches Drawing API ------ */
 +
 +/* ............................
 + * XXX
++ * We need to review the calls below, since they may be/are not that suitable for
++ * the new ways that we intend to be drawing data...
 + * ............................ */
 +
 +/* draw grease-pencil sketches to specified 2d-view that uses ibuf corrections */
 +void ED_gpencil_draw_2dimage(const bContext *C)
 +{
 +      wmWindowManager *wm = CTX_wm_manager(C);
 +      ScrArea *sa = CTX_wm_area(C);
 +      ARegion *ar = CTX_wm_region(C);
 +      Scene *scene = CTX_data_scene(C);
 +
 +      int offsx, offsy, sizex, sizey;
 +      int dflag = GP_DRAWDATA_NOSTATUS;
 +
 +      bGPdata *gpd = ED_gpencil_data_get_active(C); // XXX
 +      if (gpd == NULL) return;
 +
 +      /* calculate rect */
 +      switch (sa->spacetype) {
 +              case SPACE_IMAGE: /* image */
 +              case SPACE_CLIP: /* clip */
 +              {
 +                      /* just draw using standard scaling (settings here are currently ignored anyways) */
 +                      /* FIXME: the opengl poly-strokes don't draw at right thickness when done this way, so disabled */
 +                      offsx = 0;
 +                      offsy = 0;
 +                      sizex = ar->winx;
 +                      sizey = ar->winy;
 +
 +                      wmOrtho2(ar->v2d.cur.xmin, ar->v2d.cur.xmax, ar->v2d.cur.ymin, ar->v2d.cur.ymax);
 +
 +                      dflag |= GP_DRAWDATA_ONLYV2D | GP_DRAWDATA_IEDITHACK;
 +                      break;
 +              }
 +              case SPACE_SEQ: /* sequence */
 +              {
 +                      /* just draw using standard scaling (settings here are currently ignored anyways) */
 +                      offsx = 0;
 +                      offsy = 0;
 +                      sizex = ar->winx;
 +                      sizey = ar->winy;
 +
 +                      /* NOTE: I2D was used in 2.4x, but the old settings for that have been deprecated
 +                       * and everything moved to standard View2d
 +                       */
 +                      dflag |= GP_DRAWDATA_ONLYV2D;
 +                      break;
 +              }
 +              default: /* for spacetype not yet handled */
 +                      offsx = 0;
 +                      offsy = 0;
 +                      sizex = ar->winx;
 +                      sizey = ar->winy;
 +
 +                      dflag |= GP_DRAWDATA_ONLYI2D;
 +                      break;
 +      }
 +
 +      if (ED_screen_animation_playing(wm)) {
 +              /* don't show onionskins during animation playback/scrub (i.e. it obscures the poses)
 +               * OpenGL Renders (i.e. final output), or depth buffer (i.e. not real strokes)
 +               */
 +              dflag |= GP_DRAWDATA_NO_ONIONS;
 +      }
 +
 +      /* draw it! */
 +      gp_draw_data_all(scene, gpd, offsx, offsy, sizex, sizey, CFRA, dflag, sa->spacetype);
 +}
 +
 +/* draw grease-pencil sketches to specified 2d-view assuming that matrices are already set correctly
 + * Note: this gets called twice - first time with onlyv2d=true to draw 'canvas' strokes,
 + * second time with onlyv2d=false for screen-aligned strokes */
 +void ED_gpencil_draw_view2d(const bContext *C, bool onlyv2d)
 +{
 +      wmWindowManager *wm = CTX_wm_manager(C);
 +      ScrArea *sa = CTX_wm_area(C);
 +      ARegion *ar = CTX_wm_region(C);
 +      Scene *scene = CTX_data_scene(C);
 +      int dflag = 0;
 +
 +      /* check that we have grease-pencil stuff to draw */
 +      if (sa == NULL) return;
 +      bGPdata *gpd = ED_gpencil_data_get_active(C); // XXX
 +      if (gpd == NULL) return;
 +
 +      /* special hack for Image Editor */
 +      /* FIXME: the opengl poly-strokes don't draw at right thickness when done this way, so disabled */
 +      if (ELEM(sa->spacetype, SPACE_IMAGE, SPACE_CLIP))
 +              dflag |= GP_DRAWDATA_IEDITHACK;
 +
 +      /* draw it! */
 +      if (onlyv2d) dflag |= (GP_DRAWDATA_ONLYV2D | GP_DRAWDATA_NOSTATUS);
 +      if (ED_screen_animation_playing(wm)) dflag |= GP_DRAWDATA_NO_ONIONS;
 +
 +      gp_draw_data_all(scene, gpd, 0, 0, ar->winx, ar->winy, CFRA, dflag, sa->spacetype);
 +
 +      /* draw status text (if in screen/pixel-space) */
 +      if (!onlyv2d) {
 +              gp_draw_status_text(gpd, ar);
 +      }
 +}
 +
 +
 +/* draw annotations sketches to specified 3d-view assuming that matrices are already set correctly
 + * Note: this gets called twice - first time with only3d=true to draw 3d-strokes,
 + * second time with only3d=false for screen-aligned strokes */
 +void ED_gpencil_draw_view3d_annotations(
 +        Scene *scene, struct Depsgraph *depsgraph,
 +        View3D *v3d, ARegion *ar,
 +        bool only3d)
 +{
 +      int dflag = 0;
 +      RegionView3D *rv3d = ar->regiondata;
 +      int offsx,  offsy,  winx,  winy;
 +
 +      /* check that we have grease-pencil stuff to draw */
 +      /* XXX: Hardcoded reference here may get out of sync if we change how we fetch annotation data */
 +      bGPdata *gpd = scene->gpd;
 +      if (gpd == NULL) return;
 +
 +      /* when rendering to the offscreen buffer we don't want to
 +       * deal with the camera border, otherwise map the coords to the camera border. */
 +      if ((rv3d->persp == RV3D_CAMOB) && !(G.f & G_RENDER_OGL)) {
 +              rctf rectf;
 +              ED_view3d_calc_camera_border(scene, depsgraph, ar, v3d, rv3d, &rectf, true); /* no shift */
 +
 +              offsx = round_fl_to_int(rectf.xmin);
 +              offsy = round_fl_to_int(rectf.ymin);
 +              winx  = round_fl_to_int(rectf.xmax - rectf.xmin);
 +              winy  = round_fl_to_int(rectf.ymax - rectf.ymin);
 +      }
 +      else {
 +              offsx = 0;
 +              offsy = 0;
 +              winx  = ar->winx;
 +              winy  = ar->winy;
 +      }
 +
 +      /* set flags */
 +      if (only3d) {
 +              /* 3D strokes/3D space:
 +               * - only 3D space points
 +               * - don't status text either (as it's the wrong space)
 +               */
 +              dflag |= (GP_DRAWDATA_ONLY3D | GP_DRAWDATA_NOSTATUS);
 +      }
 +
 +      if (v3d->flag2 & V3D_RENDER_OVERRIDE) {
 +              /* don't draw status text when "only render" flag is set */
 +              dflag |= GP_DRAWDATA_NOSTATUS;
 +      }
 +
 +      /* draw it! */
 +      gp_draw_data_all(scene, gpd, offsx, offsy, winx, winy, CFRA, dflag, v3d->spacetype);
 +}
 +
 +/* ************************************************** */
index 57be09d,0000000..e9b862c
mode 100644,000000..100644
--- /dev/null
@@@ -1,2318 -1,0 +1,2318 @@@
-        *      - aims to eliminate some jitter-noise from input when trying to draw straight lines freehand
 +/*
 + * ***** BEGIN GPL LICENSE BLOCK *****
 + *
 + * 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.
 + *
 + * The Original Code is Copyright (C) 2008/2018, Blender Foundation
 + * This is a new part of Blender
 + *
 + * Contributor(s): Joshua Leung
 + *
 + * ***** END GPL LICENSE BLOCK *****
 + */
 +
 +/** \file blender/editors/gpencil/annotate_paint.c
 + *  \ingroup edgpencil
 + */
 +
 +
 +#include <stdio.h>
 +#include <stddef.h>
 +#include <stdlib.h>
 +#include <string.h>
 +#include <math.h>
 +
 +#include "MEM_guardedalloc.h"
 +
 +#include "BLI_blenlib.h"
 +#include "BLI_math.h"
 +#include "BLI_utildefines.h"
 +#include "BLI_math_geom.h"
 +
 +#include "BLT_translation.h"
 +
 +#include "PIL_time.h"
 +
 +#include "BKE_colortools.h"
 +#include "BKE_context.h"
 +#include "BKE_global.h"
 +#include "BKE_gpencil.h"
 +#include "BKE_main.h"
 +#include "BKE_report.h"
 +#include "BKE_screen.h"
 +#include "BKE_tracking.h"
 +
 +#include "DNA_object_types.h"
 +#include "DNA_scene_types.h"
 +#include "DNA_gpencil_types.h"
 +#include "DNA_windowmanager_types.h"
 +
 +#include "UI_view2d.h"
 +
 +#include "ED_gpencil.h"
 +#include "ED_screen.h"
 +#include "ED_view3d.h"
 +#include "ED_clip.h"
 +
 +#include "BIF_glutil.h"
 +
 +#include "GPU_immediate.h"
 +#include "GPU_immediate_util.h"
 +#include "GPU_state.h"
 +
 +#include "RNA_access.h"
 +#include "RNA_define.h"
 +
 +#include "WM_api.h"
 +#include "WM_types.h"
 +
 +#include "DEG_depsgraph.h"
 +
 +#include "gpencil_intern.h"
 +
 +/* ******************************************* */
 +/* 'Globals' and Defines */
 +
 +/* values for tGPsdata->status */
 +typedef enum eGPencil_PaintStatus {
 +      GP_STATUS_IDLING = 0,   /* stroke isn't in progress yet */
 +      GP_STATUS_PAINTING,     /* a stroke is in progress */
 +      GP_STATUS_ERROR,        /* something wasn't correctly set up */
 +      GP_STATUS_DONE          /* painting done */
 +} eGPencil_PaintStatus;
 +
 +/* Return flags for adding points to stroke buffer */
 +typedef enum eGP_StrokeAdd_Result {
 +      GP_STROKEADD_INVALID    = -2,       /* error occurred - insufficient info to do so */
 +      GP_STROKEADD_OVERFLOW   = -1,       /* error occurred - cannot fit any more points */
 +      GP_STROKEADD_NORMAL,                /* point was successfully added */
 +      GP_STROKEADD_FULL                   /* cannot add any more points to buffer */
 +} eGP_StrokeAdd_Result;
 +
 +/* Runtime flags */
 +typedef enum eGPencil_PaintFlags {
 +      GP_PAINTFLAG_FIRSTRUN       = (1 << 0),    /* operator just started */
 +      GP_PAINTFLAG_STROKEADDED    = (1 << 1),
 +      GP_PAINTFLAG_V3D_ERASER_DEPTH = (1 << 2),
 +      GP_PAINTFLAG_SELECTMASK     = (1 << 3),
 +} eGPencil_PaintFlags;
 +
 +
 +/* Temporary 'Stroke' Operation data
 + *   "p" = op->customdata
 + */
 +typedef struct tGPsdata {
 +      Main *bmain;
 +      Scene *scene;       /* current scene from context */
 +      struct Depsgraph *depsgraph;
 +
 +      wmWindow *win;      /* window where painting originated */
 +      ScrArea *sa;        /* area where painting originated */
 +      ARegion *ar;        /* region where painting originated */
 +      View2D *v2d;        /* needed for GP_STROKE_2DSPACE */
 +      rctf *subrect;      /* for using the camera rect within the 3d view */
 +      rctf subrect_data;
 +
 +      GP_SpaceConversion gsc; /* settings to pass to gp_points_to_xy() */
 +
 +      PointerRNA ownerPtr; /* pointer to owner of gp-datablock */
 +      bGPdata *gpd;       /* gp-datablock layer comes from */
 +      bGPDlayer *gpl;     /* layer we're working on */
 +      bGPDframe *gpf;     /* frame we're working on */
 +
 +      char *align_flag;   /* projection-mode flags (toolsettings - eGPencil_Placement_Flags) */
 +
 +      eGPencil_PaintStatus status;     /* current status of painting */
 +      eGPencil_PaintModes  paintmode;  /* mode for painting */
 +      eGPencil_PaintFlags  flags;      /* flags that can get set during runtime (eGPencil_PaintFlags) */
 +
 +      short radius;       /* radius of influence for eraser */
 +
 +      int mval[2];        /* current mouse-position */
 +      int mvalo[2];       /* previous recorded mouse-position */
 +
 +      float pressure;     /* current stylus pressure */
 +      float opressure;    /* previous stylus pressure */
 +
 +      /* These need to be doubles, as (at least under unix) they are in seconds since epoch,
 +       * float (and its 7 digits precision) is definitively not enough here!
 +       * double, with its 15 digits precision, ensures us millisecond precision for a few centuries at least.
 +       */
 +      double inittime;    /* Used when converting to path */
 +      double curtime;     /* Used when converting to path */
 +      double ocurtime;    /* Used when converting to path */
 +
 +      float imat[4][4];   /* inverted transformation matrix applying when converting coords from screen-space
 +                           * to region space */
 +      float mat[4][4];
 +
 +      float custom_color[4]; /* custom color - hack for enforcing a particular color for track/mask editing */
 +
 +      void *erasercursor; /* radial cursor data for drawing eraser */
 +
 +      short straight[2];   /* 1: line horizontal, 2: line vertical, other: not defined, second element position */
 +
 +      short keymodifier;   /* key used for invoking the operator */
 +} tGPsdata;
 +
 +/* ------ */
 +
 +/* Macros for accessing sensitivity thresholds... */
 +/* minimum number of pixels mouse should move before new point created */
 +#define MIN_MANHATTEN_PX    (U.gp_manhattendist)
 +/* minimum length of new segment before new point can be added */
 +#define MIN_EUCLIDEAN_PX    (U.gp_euclideandist)
 +
 +static bool gp_stroke_added_check(tGPsdata *p)
 +{
 +      return (p->gpf && p->gpf->strokes.last && p->flags & GP_PAINTFLAG_STROKEADDED);
 +}
 +
 +static void gp_stroke_added_enable(tGPsdata *p)
 +{
 +      BLI_assert(p->gpf->strokes.last != NULL);
 +      p->flags |= GP_PAINTFLAG_STROKEADDED;
 +}
 +
 +/* ------ */
 +/* Forward defines for some functions... */
 +
 +static void gp_session_validatebuffer(tGPsdata *p);
 +
 +/* ******************************************* */
 +/* Context Wrangling... */
 +
 +/* check if context is suitable for drawing */
 +static bool gpencil_draw_poll(bContext *C)
 +{
 +      if (ED_operator_regionactive(C)) {
 +              /* check if current context can support GPencil data */
 +              if (ED_gpencil_data_get_pointers(C, NULL) != NULL) {
 +                      /* check if Grease Pencil isn't already running */
 +                      if (ED_gpencil_session_active() == 0)
 +                              return 1;
 +                      else
 +                              CTX_wm_operator_poll_msg_set(C, "Annotation operator is already active");
 +              }
 +              else {
 +                      CTX_wm_operator_poll_msg_set(C, "Failed to find Grease Pencil data to draw into");
 +              }
 +      }
 +      else {
 +              CTX_wm_operator_poll_msg_set(C, "Active region not set");
 +      }
 +
 +      return 0;
 +}
 +
 +/* check if projecting strokes into 3d-geometry in the 3D-View */
 +static bool gpencil_project_check(tGPsdata *p)
 +{
 +      bGPdata *gpd = p->gpd;
 +      return ((gpd->runtime.sbuffer_sflag & GP_STROKE_3DSPACE) && (*p->align_flag & (GP_PROJECT_DEPTH_VIEW | GP_PROJECT_DEPTH_STROKE)));
 +}
 +
 +/* ******************************************* */
 +/* Calculations/Conversions */
 +
 +/* Utilities --------------------------------- */
 +
 +/* get the reference point for stroke-point conversions */
 +static void gp_get_3d_reference(tGPsdata *p, float vec[3])
 +{
 +      View3D *v3d = p->sa->spacedata.first;
 +      const float *fp = ED_view3d_cursor3d_get(p->scene, v3d)->location;
 +
 +      /* use 3D-cursor */
 +      copy_v3_v3(vec, fp);
 +}
 +
 +/* Stroke Editing ---------------------------- */
 +
 +/* check if the current mouse position is suitable for adding a new point */
 +static bool gp_stroke_filtermval(tGPsdata *p, const int mval[2], int pmval[2])
 +{
 +      int dx = abs(mval[0] - pmval[0]);
 +      int dy = abs(mval[1] - pmval[1]);
 +
 +      /* if buffer is empty, just let this go through (i.e. so that dots will work) */
 +      if (p->gpd->runtime.sbuffer_size == 0)
 +              return true;
 +
 +      /* check if mouse moved at least certain distance on both axes (best case)
-        *      - prevents points being added too densely
-        *      - distance here doesn't use sqrt to prevent slowness... we should still be safe from overflows though
++       * - aims to eliminate some jitter-noise from input when trying to draw straight lines freehand
 +       */
 +      else if ((dx > MIN_MANHATTEN_PX) && (dy > MIN_MANHATTEN_PX))
 +              return true;
 +
 +      /* check if the distance since the last point is significant enough
-                        *      - nothing more needs to be done here, since view_autodist_simple() has already done it
++       * - prevents points being added too densely
++       * - distance here doesn't use sqrt to prevent slowness... we should still be safe from overflows though
 +       */
 +      else if ((dx * dx + dy * dy) > MIN_EUCLIDEAN_PX * MIN_EUCLIDEAN_PX)
 +              return true;
 +
 +      /* mouse 'didn't move' */
 +      else
 +              return false;
 +}
 +
 +/* convert screen-coordinates to buffer-coordinates */
 +static void gp_stroke_convertcoords(tGPsdata *p, const int mval[2], float out[3], float *depth)
 +{
 +      bGPdata *gpd = p->gpd;
 +
 +      /* in 3d-space - pt->x/y/z are 3 side-by-side floats */
 +      if (gpd->runtime.sbuffer_sflag & GP_STROKE_3DSPACE) {
 +              if (gpencil_project_check(p) && (ED_view3d_autodist_simple(p->ar, mval, out, 0, depth))) {
 +                      /* projecting onto 3D-Geometry
-                        *      - investigate using nearest point(s) on a previous stroke as
-                        *        reference point instead or as offset, for easier stroke matching
++                       * - nothing more needs to be done here, since view_autodist_simple() has already done it
 +                       */
 +              }
 +              else {
 +                      float mval_prj[2];
 +                      float rvec[3], dvec[3];
 +                      float mval_f[2] = {UNPACK2(mval)};
 +                      float zfac;
 +
 +                      /* Current method just converts each point in screen-coordinates to
 +                       * 3D-coordinates using the 3D-cursor as reference. In general, this
 +                       * works OK, but it could of course be improved.
 +                       *
 +                       * TODO:
-                        *      - assume that pointers for this are always valid...
++                       * - investigate using nearest point(s) on a previous stroke as
++                       *   reference point instead or as offset, for easier stroke matching
 +                       */
 +
 +                      gp_get_3d_reference(p, rvec);
 +                      zfac = ED_view3d_calc_zfac(p->ar->regiondata, rvec, NULL);
 +
 +                      if (ED_view3d_project_float_global(p->ar, rvec, mval_prj, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) {
 +                              sub_v2_v2v2(mval_f, mval_prj, mval_f);
 +                              ED_view3d_win_to_delta(p->ar, mval_f, dvec, zfac);
 +                              sub_v3_v3v3(out, rvec, dvec);
 +                      }
 +                      else {
 +                              zero_v3(out);
 +                      }
 +              }
 +      }
 +
 +      /* 2d - on 'canvas' (assume that p->v2d is set) */
 +      else if ((gpd->runtime.sbuffer_sflag & GP_STROKE_2DSPACE) && (p->v2d)) {
 +              UI_view2d_region_to_view(p->v2d, mval[0], mval[1], &out[0], &out[1]);
 +              mul_v3_m4v3(out, p->imat, out);
 +      }
 +
 +      /* 2d - relative to screen (viewport area) */
 +      else {
 +              if (p->subrect == NULL) { /* normal 3D view */
 +                      out[0] = (float)(mval[0]) / (float)(p->ar->winx) * 100;
 +                      out[1] = (float)(mval[1]) / (float)(p->ar->winy) * 100;
 +              }
 +              else { /* camera view, use subrect */
 +                      out[0] = ((mval[0] - p->subrect->xmin) / BLI_rctf_size_x(p->subrect)) * 100;
 +                      out[1] = ((mval[1] - p->subrect->ymin) / BLI_rctf_size_y(p->subrect)) * 100;
 +              }
 +      }
 +}
 +
 +/* add current stroke-point to buffer (returns whether point was successfully added) */
 +static short gp_stroke_addpoint(
 +        tGPsdata *p, const int mval[2], float pressure, double curtime)
 +{
 +      bGPdata *gpd = p->gpd;
 +      tGPspoint *pt;
 +      ToolSettings *ts = p->scene->toolsettings;
 +
 +      /* check painting mode */
 +      if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) {
 +              /* straight lines only - i.e. only store start and end point in buffer */
 +              if (gpd->runtime.sbuffer_size == 0) {
 +                      /* first point in buffer (start point) */
 +                      pt = (tGPspoint *)(gpd->runtime.sbuffer);
 +
 +                      /* store settings */
 +                      copy_v2_v2_int(&pt->x, mval);
 +                      pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */
 +                      pt->strength = 1.0f;
 +                      pt->time = (float)(curtime - p->inittime);
 +
 +                      /* increment buffer size */
 +                      gpd->runtime.sbuffer_size++;
 +              }
 +              else {
 +                      /* just reset the endpoint to the latest value
-  *    - applies a reverse Chaikin filter
-  *    - code adapted from etch-a-ton branch
++                       * - assume that pointers for this are always valid...
 +                       */
 +                      pt = ((tGPspoint *)(gpd->runtime.sbuffer) + 1);
 +
 +                      /* store settings */
 +                      copy_v2_v2_int(&pt->x, mval);
 +                      pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */
 +                      pt->strength = 1.0f;
 +                      pt->time = (float)(curtime - p->inittime);
 +
 +                      /* now the buffer has 2 points (and shouldn't be allowed to get any larger) */
 +                      gpd->runtime.sbuffer_size = 2;
 +              }
 +
 +              /* can keep carrying on this way :) */
 +              return GP_STROKEADD_NORMAL;
 +      }
 +      else if (p->paintmode == GP_PAINTMODE_DRAW) { /* normal drawing */
 +              /* check if still room in buffer */
 +              if (gpd->runtime.sbuffer_size >= GP_STROKE_BUFFER_MAX)
 +                      return GP_STROKEADD_OVERFLOW;
 +
 +              /* get pointer to destination point */
 +              pt = ((tGPspoint *)(gpd->runtime.sbuffer) + gpd->runtime.sbuffer_size);
 +
 +              /* store settings */
 +              copy_v2_v2_int(&pt->x, mval);
 +              pt->pressure = pressure;
 +              pt->strength = 1.0f;  /* unused for annotations, but initialise for easier conversions to GP Object */
 +
 +              /* point time */
 +              pt->time = (float)(curtime - p->inittime);
 +
 +              /* increment counters */
 +              gpd->runtime.sbuffer_size++;
 +
 +              /* check if another operation can still occur */
 +              if (gpd->runtime.sbuffer_size == GP_STROKE_BUFFER_MAX)
 +                      return GP_STROKEADD_FULL;
 +              else
 +                      return GP_STROKEADD_NORMAL;
 +      }
 +      else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) {
 +              /* get pointer to destination point */
 +              pt = (tGPspoint *)(gpd->runtime.sbuffer);
 +
 +              /* store settings */
 +              copy_v2_v2_int(&pt->x, mval);
 +              pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */
 +              pt->strength = 1.0f;
 +              pt->time = (float)(curtime - p->inittime);
 +
 +              /* if there's stroke for this poly line session add (or replace last) point
 +               * to stroke. This allows to draw lines more interactively (see new segment
 +               * during mouse slide, e.g.)
 +               */
 +              if (gp_stroke_added_check(p)) {
 +                      bGPDstroke *gps = p->gpf->strokes.last;
 +                      bGPDspoint *pts;
 +
 +                      /* first time point is adding to temporary buffer -- need to allocate new point in stroke */
 +                      if (gpd->runtime.sbuffer_size == 0) {
 +                              gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1));
 +                              gps->totpoints++;
 +                      }
 +
 +                      pts = &gps->points[gps->totpoints - 1];
 +
 +                      /* special case for poly lines: normally,
 +                       * depth is needed only when creating new stroke from buffer,
 +                       * but poly lines are converting to stroke instantly,
 +                       * so initialize depth buffer before converting coordinates
 +                       */
 +                      if (gpencil_project_check(p)) {
 +                              View3D *v3d = p->sa->spacedata.first;
 +
 +                              view3d_region_operator_needs_opengl(p->win, p->ar);
 +                              ED_view3d_autodist_init(
 +                                      p->depsgraph, p->ar, v3d, (ts->annotate_v3d_align & GP_PROJECT_DEPTH_STROKE) ? 1 : 0);
 +                      }
 +
 +                      /* convert screen-coordinates to appropriate coordinates (and store them) */
 +                      gp_stroke_convertcoords(p, &pt->x, &pts->x, NULL);
 +
 +                      /* copy pressure and time */
 +                      pts->pressure = pt->pressure;
 +                      pts->strength = pt->strength;
 +                      pts->time = pt->time;
 +
 +                      /* force fill recalc */
 +                      gps->flag |= GP_STROKE_RECALC_CACHES;
 +              }
 +
 +              /* increment counters */
 +              if (gpd->runtime.sbuffer_size == 0)
 +                      gpd->runtime.sbuffer_size++;
 +
 +              return GP_STROKEADD_NORMAL;
 +      }
 +
 +      /* return invalid state for now... */
 +      return GP_STROKEADD_INVALID;
 +}
 +
 +/* simplify a stroke (in buffer) before storing it
-        *      - firstly set sbuffer to NULL, so a new one is allocated
-        *      - secondly, reset flag after, as it gets cleared auto
++ * - applies a reverse Chaikin filter
++ * - code adapted from etch-a-ton branch
 + */
 +static void gp_stroke_simplify(tGPsdata *p)
 +{
 +      bGPdata *gpd = p->gpd;
 +      tGPspoint *old_points = (tGPspoint *)gpd->runtime.sbuffer;
 +      short num_points = gpd->runtime.sbuffer_size;
 +      short flag = gpd->runtime.sbuffer_sflag;
 +      short i, j;
 +
 +      /* only simplify if simplification is enabled, and we're not doing a straight line */
 +      if (!(U.gp_settings & GP_PAINT_DOSIMPLIFY) || (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT))
 +              return;
 +
 +      /* don't simplify if less than 4 points in buffer */
 +      if ((num_points <= 4) || (old_points == NULL))
 +              return;
 +
 +      /* clear buffer (but don't free mem yet) so that we can write to it
-  *    - used due to the mixture of datatypes in use here
++       * - firstly set sbuffer to NULL, so a new one is allocated
++       * - secondly, reset flag after, as it gets cleared auto
 +       */
 +      gpd->runtime.sbuffer = NULL;
 +      gp_session_validatebuffer(p);
 +      gpd->runtime.sbuffer_sflag = flag;
 +
 +/* macro used in loop to get position of new point
-        *      - drawing straight-lines only requires the endpoints
++ * - used due to the mixture of datatypes in use here
 + */
 +#define GP_SIMPLIFY_AVPOINT(offs, sfac) \
 +      { \
 +              co[0] += (float)(old_points[offs].x * sfac); \
 +              co[1] += (float)(old_points[offs].y * sfac); \
 +              pressure += old_points[offs].pressure * sfac; \
 +              time += old_points[offs].time * sfac; \
 +      } (void)0
 +
 +      /* XXX Here too, do not lose start and end points! */
 +      gp_stroke_addpoint(p, &old_points->x, old_points->pressure, p->inittime + (double)old_points->time);
 +      for (i = 0, j = 0; i < num_points; i++) {
 +              if (i - j == 3) {
 +                      float co[2], pressure, time;
 +                      int mco[2];
 +
 +                      /* initialize values */
 +                      co[0] = 0.0f;
 +                      co[1] = 0.0f;
 +                      pressure = 0.0f;
 +                      time = 0.0f;
 +
 +                      /* using macro, calculate new point */
 +                      GP_SIMPLIFY_AVPOINT(j, -0.25f);
 +                      GP_SIMPLIFY_AVPOINT(j + 1, 0.75f);
 +                      GP_SIMPLIFY_AVPOINT(j + 2, 0.75f);
 +                      GP_SIMPLIFY_AVPOINT(j + 3, -0.25f);
 +
 +                      /* set values for adding */
 +                      mco[0] = (int)co[0];
 +                      mco[1] = (int)co[1];
 +
 +                      /* ignore return values on this... assume to be ok for now */
 +                      gp_stroke_addpoint(p, mco, pressure, p->inittime + (double)time);
 +
 +                      j += 2;
 +              }
 +      }
 +      gp_stroke_addpoint(
 +              p, &old_points[num_points - 1].x, old_points[num_points - 1].pressure,
 +              p->inittime + (double)old_points[num_points - 1].time);
 +
 +      /* free old buffer */
 +      MEM_freeN(old_points);
 +}
 +
 +/* make a new stroke from the buffer data */
 +static void gp_stroke_newfrombuffer(tGPsdata *p)
 +{
 +      bGPdata *gpd = p->gpd;
 +      bGPDlayer *gpl = p->gpl;
 +      bGPDstroke *gps;
 +      bGPDspoint *pt;
 +      tGPspoint *ptc;
 +      ToolSettings *ts = p->scene->toolsettings;
 +
 +      int i, totelem;
 +      /* since strokes are so fine, when using their depth we need a margin otherwise they might get missed */
 +      int depth_margin = (ts->annotate_v3d_align & GP_PROJECT_DEPTH_STROKE) ? 4 : 0;
 +
 +      /* get total number of points to allocate space for
-                        *      - must verify that region data is 3D-view (and not something else)
++       * - drawing straight-lines only requires the endpoints
 +       */
 +      if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT)
 +              totelem = (gpd->runtime.sbuffer_size >= 2) ? 2 : gpd->runtime.sbuffer_size;
 +      else
 +              totelem = gpd->runtime.sbuffer_size;
 +
 +      /* exit with error if no valid points from this stroke */
 +      if (totelem == 0) {
 +              if (G.debug & G_DEBUG)
 +                      printf("Error: No valid points in stroke buffer to convert (tot=%d)\n", gpd->runtime.sbuffer_size);
 +              return;
 +      }
 +
 +      /* special case for poly line -- for already added stroke during session
 +       * coordinates are getting added to stroke immediately to allow more
 +       * interactive behavior
 +       */
 +      if (p->paintmode == GP_PAINTMODE_DRAW_POLY) {
 +              if (gp_stroke_added_check(p)) {
 +                      return;
 +              }
 +      }
 +
 +      /* allocate memory for a new stroke */
 +      gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke");
 +
 +      /* copy appropriate settings for stroke */
 +      gps->totpoints = totelem;
 +      gps->thickness = gpl->thickness;
 +      gps->flag = gpd->runtime.sbuffer_sflag;
 +      gps->inittime = p->inittime;
 +
 +      /* enable recalculation flag by default (only used if hq fill) */
 +      gps->flag |= GP_STROKE_RECALC_CACHES;
 +
 +      /* allocate enough memory for a continuous array for storage points */
 +      gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
 +      gps->tot_triangles = 0;
 +
 +      /* set pointer to first non-initialized point */
 +      pt = gps->points + (gps->totpoints - totelem);
 +
 +      /* copy points from the buffer to the stroke */
 +      if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) {
 +              /* straight lines only -> only endpoints */
 +              {
 +                      /* first point */
 +                      ptc = gpd->runtime.sbuffer;
 +
 +                      /* convert screen-coordinates to appropriate coordinates (and store them) */
 +                      gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL);
 +
 +                      /* copy pressure and time */
 +                      pt->pressure = ptc->pressure;
 +                      pt->strength = ptc->strength;
 +                      CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f);
 +                      pt->time = ptc->time;
 +
 +                      pt++;
 +              }
 +
 +              if (totelem == 2) {
 +                      /* last point if applicable */
 +                      ptc = ((tGPspoint *)gpd->runtime.sbuffer) + (gpd->runtime.sbuffer_size - 1);
 +
 +                      /* convert screen-coordinates to appropriate coordinates (and store them) */
 +                      gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL);
 +
 +                      /* copy pressure and time */
 +                      pt->pressure = ptc->pressure;
 +                      pt->strength = ptc->strength;
 +                      CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f);
 +                      pt->time = ptc->time;
 +              }
 +      }
 +      else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) {
 +              /* first point */
 +              ptc = gpd->runtime.sbuffer;
 +
 +              /* convert screen-coordinates to appropriate coordinates (and store them) */
 +              gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL);
 +
 +              /* copy pressure and time */
 +              pt->pressure = ptc->pressure;
 +              pt->strength = ptc->strength;
 +              pt->time = ptc->time;
 +      }
 +      else {
 +              float *depth_arr = NULL;
 +
 +              /* get an array of depths, far depths are blended */
 +              if (gpencil_project_check(p)) {
 +                      int mval[2], mval_prev[2] = { 0 };
 +                      int interp_depth = 0;
 +                      int found_depth = 0;
 +
 +                      depth_arr = MEM_mallocN(sizeof(float) * gpd->runtime.sbuffer_size, "depth_points");
 +
 +                      for (i = 0, ptc = gpd->runtime.sbuffer; i < gpd->runtime.sbuffer_size; i++, ptc++, pt++) {
 +                              copy_v2_v2_int(mval, &ptc->x);
 +
 +                              if ((ED_view3d_autodist_depth(p->ar, mval, depth_margin, depth_arr + i) == 0) &&
 +                                  (i && (ED_view3d_autodist_depth_seg(p->ar, mval, mval_prev, depth_margin + 1, depth_arr + i) == 0)))
 +                              {
 +                                      interp_depth = true;
 +                              }
 +                              else {
 +                                      found_depth = true;
 +                              }
 +
 +                              copy_v2_v2_int(mval_prev, mval);
 +                      }
 +
 +                      if (found_depth == false) {
 +                              /* eeh... not much we can do.. :/, ignore depth in this case, use the 3D cursor */
 +                              for (i = gpd->runtime.sbuffer_size - 1; i >= 0; i--)
 +                                      depth_arr[i] = 0.9999f;
 +                      }
 +                      else {
 +                              if (ts->annotate_v3d_align & GP_PROJECT_DEPTH_STROKE_ENDPOINTS) {
 +                                      /* remove all info between the valid endpoints */
 +                                      int first_valid = 0;
 +                                      int last_valid = 0;
 +
 +                                      for (i = 0; i < gpd->runtime.sbuffer_size; i++) {
 +                                              if (depth_arr[i] != FLT_MAX)
 +                                                      break;
 +                                      }
 +                                      first_valid = i;
 +
 +                                      for (i = gpd->runtime.sbuffer_size - 1; i >= 0; i--) {
 +                                              if (depth_arr[i] != FLT_MAX)
 +                                                      break;
 +                                      }
 +                                      last_valid = i;
 +
 +                                      /* invalidate non-endpoints, so only blend between first and last */
 +                                      for (i = first_valid + 1; i < last_valid; i++)
 +                                              depth_arr[i] = FLT_MAX;
 +
 +                                      interp_depth = true;
 +                              }
 +
 +                              if (interp_depth) {
 +                                      interp_sparse_array(depth_arr, gpd->runtime.sbuffer_size, FLT_MAX);
 +                              }
 +                      }
 +              }
 +
 +
 +              pt = gps->points;
 +
 +              /* convert all points (normal behavior) */
 +              for (i = 0, ptc = gpd->runtime.sbuffer; i < gpd->runtime.sbuffer_size && ptc; i++, ptc++, pt++) {
 +                      /* convert screen-coordinates to appropriate coordinates (and store them) */
 +                      gp_stroke_convertcoords(p, &ptc->x, &pt->x, depth_arr ? depth_arr + i : NULL);
 +
 +                      /* copy pressure and time */
 +                      pt->pressure = ptc->pressure;
 +                      pt->strength = ptc->strength;
 +                      CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f);
 +                      pt->time = ptc->time;
 +              }
 +
 +              if (depth_arr)
 +                      MEM_freeN(depth_arr);
 +      }
 +
 +      /* add stroke to frame */
 +      BLI_addtail(&p->gpf->strokes, gps);
 +      gp_stroke_added_enable(p);
 +}
 +
 +/* --- 'Eraser' for 'Paint' Tool ------ */
 +
 +/* helper to free a stroke
 + * NOTE: gps->dvert and gps->triangles should be NULL, but check anyway for good measure
 + */
 +static void gp_free_stroke(bGPDframe *gpf, bGPDstroke *gps)
 +{
 +      if (gps->points) {
 +              MEM_freeN(gps->points);
 +      }
 +
 +      if (gps->dvert) {
 +              BKE_gpencil_free_stroke_weights(gps);
 +              MEM_freeN(gps->dvert);
 +      }
 +
 +      if (gps->triangles) {
 +              MEM_freeN(gps->triangles);
 +      }
 +
 +      BLI_freelinkN(&gpf->strokes, gps);
 +}
 +
 +
 +/* which which point is infront (result should only be used for comparison) */
 +static float view3d_point_depth(const RegionView3D *rv3d, const float co[3])
 +{
 +      if (rv3d->is_persp) {
 +              return ED_view3d_calc_zfac(rv3d, co, NULL);
 +      }
 +      else {
 +              return -dot_v3v3(rv3d->viewinv[2], co);
 +      }
 +}
 +
 +/* only erase stroke points that are visible (3d view) */
 +static bool gp_stroke_eraser_is_occluded(tGPsdata *p, const bGPDspoint *pt, const int x, const int y)
 +{
 +      if ((p->sa->spacetype == SPACE_VIEW3D) &&
 +          (p->flags & GP_PAINTFLAG_V3D_ERASER_DEPTH))
 +      {
 +              RegionView3D *rv3d = p->ar->regiondata;
 +              const int mval[2] = {x, y};
 +              float mval_3d[3];
 +
 +              if (ED_view3d_autodist_simple(p->ar, mval, mval_3d, 0, NULL)) {
 +                      const float depth_mval = view3d_point_depth(rv3d, mval_3d);
 +                      const float depth_pt   = view3d_point_depth(rv3d, &pt->x);
 +
 +                      if (depth_pt > depth_mval) {
 +                              return true;
 +                      }
 +              }
 +      }
 +      return false;
 +}
 +
 +/* eraser tool - evaluation per stroke */
 +/* TODO: this could really do with some optimization (KD-Tree/BVH?) */
 +static void gp_stroke_eraser_dostroke(
 +        tGPsdata *p,
 +        bGPDframe *gpf, bGPDstroke *gps,
 +        const int mval[2], const int mvalo[2],
 +        const int radius, const rcti *rect)
 +{
 +      bGPDspoint *pt1, *pt2;
 +      int pc1[2] = {0};
 +      int pc2[2] = {0};
 +      int i;
 +
 +      if (gps->totpoints == 0) {
 +              /* just free stroke */
 +              gp_free_stroke(gpf, gps);
 +      }
 +      else if (gps->totpoints == 1) {
 +              /* only process if it hasn't been masked out... */
 +              if (!(p->flags & GP_PAINTFLAG_SELECTMASK) || (gps->points->flag & GP_SPOINT_SELECT)) {
 +                      gp_point_to_xy(&p->gsc, gps, gps->points, &pc1[0], &pc1[1]);
 +
 +                      /* do boundbox check first */
 +                      if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) {
 +                              /* only check if point is inside */
 +                              if (len_v2v2_int(mval, pc1) <= radius) {
 +                                      /* free stroke */
 +                                      gp_free_stroke(gpf, gps);
 +                              }
 +                      }
 +              }
 +      }
 +      else {
 +              /* Perform culling? */
 +              bool do_cull = false;
 +
 +              /* Clear Tags
 +               *
 +               * Note: It's better this way, as we are sure that
 +               * we don't miss anything, though things will be
 +               * slightly slower as a result
 +               */
 +              for (i = 0; i < gps->totpoints; i++) {
 +                      bGPDspoint *pt = &gps->points[i];
 +                      pt->flag &= ~GP_SPOINT_TAG;
 +              }
 +
 +              /* First Pass: Loop over the points in the stroke
 +               *   1) Thin out parts of the stroke under the brush
 +               *   2) Tag "too thin" parts for removal (in second pass)
 +               */
 +              for (i = 0; (i + 1) < gps->totpoints; i++) {
 +                      /* get points to work with */
 +                      pt1 = gps->points + i;
 +                      pt2 = gps->points + i + 1;
 +
 +                      /* only process if it hasn't been masked out... */
 +                      if ((p->flags & GP_PAINTFLAG_SELECTMASK) && !(gps->points->flag & GP_SPOINT_SELECT))
 +                              continue;
 +
 +                      gp_point_to_xy(&p->gsc, gps, pt1, &pc1[0], &pc1[1]);
 +                      gp_point_to_xy(&p->gsc, gps, pt2, &pc2[0], &pc2[1]);
 +
 +                      /* Check that point segment of the boundbox of the eraser stroke */
 +                      if (((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) ||
 +                          ((!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1])) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1])))
 +                      {
 +                              /* Check if point segment of stroke had anything to do with
 +                               * eraser region  (either within stroke painted, or on its lines)
 +                               *  - this assumes that linewidth is irrelevant
 +                               */
 +                              if (gp_stroke_inside_circle(mval, mvalo, radius, pc1[0], pc1[1], pc2[0], pc2[1])) {
 +                                      if ((gp_stroke_eraser_is_occluded(p, pt1, pc1[0], pc1[1]) == false) ||
 +                                          (gp_stroke_eraser_is_occluded(p, pt2, pc2[0], pc2[1]) == false))
 +                                      {
 +                                              /* Edge is affected - Check individual points now */
 +                                              if (len_v2v2_int(mval, pc1) <= radius) {
 +                                                      pt1->flag |= GP_SPOINT_TAG;
 +                                              }
 +                                              if (len_v2v2_int(mval, pc2) <= radius) {
 +                                                      pt2->flag |= GP_SPOINT_TAG;
 +                                              }
 +                                              do_cull = true;
 +                                      }
 +                              }
 +                      }
 +              }
 +
 +              /* Second Pass: Remove any points that are tagged */
 +              if (do_cull) {
 +                      gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false);
 +              }
 +      }
 +}
 +
 +/* erase strokes which fall under the eraser strokes */
 +static void gp_stroke_doeraser(tGPsdata *p)
 +{
 +      bGPDframe *gpf = p->gpf;
 +      bGPDstroke *gps, *gpn;
 +      rcti rect;
 +
 +      /* rect is rectangle of eraser */
 +      rect.xmin = p->mval[0] - p->radius;
 +      rect.ymin = p->mval[1] - p->radius;
 +      rect.xmax = p->mval[0] + p->radius;
 +      rect.ymax = p->mval[1] + p->radius;
 +
 +      if (p->sa->spacetype == SPACE_VIEW3D) {
 +              if (p->flags & GP_PAINTFLAG_V3D_ERASER_DEPTH) {
 +                      View3D *v3d = p->sa->spacedata.first;
 +                      view3d_region_operator_needs_opengl(p->win, p->ar);
 +                      ED_view3d_autodist_init(p->depsgraph, p->ar, v3d, 0);
 +              }
 +      }
 +
 +      /* loop over strokes of active layer only (session init already took care of ensuring validity),
 +       * checking segments for intersections to remove
 +       */
 +      for (gps = gpf->strokes.first; gps; gps = gpn) {
 +              gpn = gps->next;
 +              /* Not all strokes in the datablock may be valid in the current editor/context
 +               * (e.g. 2D space strokes in the 3D view, if the same datablock is shared)
 +               */
 +              if (ED_gpencil_stroke_can_use_direct(p->sa, gps)) {
 +                      gp_stroke_eraser_dostroke(p, gpf, gps, p->mval, p->mvalo, p->radius, &rect);
 +              }
 +      }
 +}
 +
 +/* ******************************************* */
 +/* Sketching Operator */
 +
 +/* clear the session buffers (call this before AND after a paint operation) */
 +static void gp_session_validatebuffer(tGPsdata *p)
 +{
 +      bGPdata *gpd = p->gpd;
 +
 +      /* clear memory of buffer (or allocate it if starting a new session) */
 +      if (gpd->runtime.sbuffer) {
 +              /* printf("\t\tGP - reset sbuffer\n"); */
 +              memset(gpd->runtime.sbuffer, 0, sizeof(tGPspoint) * GP_STROKE_BUFFER_MAX);
 +      }
 +      else {
 +              /* printf("\t\tGP - allocate sbuffer\n"); */
 +              gpd->runtime.sbuffer = MEM_callocN(sizeof(tGPspoint) * GP_STROKE_BUFFER_MAX, "gp_session_strokebuffer");
 +      }
 +
 +      /* reset indices */
 +      gpd->runtime.sbuffer_size = 0;
 +
 +      /* reset flags */
 +      gpd->runtime.sbuffer_sflag = 0;
 +
 +      /* reset inittime */
 +      p->inittime = 0.0;
 +}
 +
 +/* (re)init new painting data */
 +static bool gp_session_initdata(bContext *C, tGPsdata *p)
 +{
 +      Main *bmain = CTX_data_main(C);
 +      bGPdata **gpd_ptr = NULL;
 +      ScrArea *curarea = CTX_wm_area(C);
 +      ARegion *ar = CTX_wm_region(C);
 +      ToolSettings *ts = CTX_data_tool_settings(C);
 +
 +      /* make sure the active view (at the starting time) is a 3d-view */
 +      if (curarea == NULL) {
 +              p->status = GP_STATUS_ERROR;
 +              if (G.debug & G_DEBUG)
 +                      printf("Error: No active view for painting\n");
 +              return 0;
 +      }
 +
 +      /* pass on current scene and window */
 +      p->bmain = CTX_data_main(C);
 +      p->scene = CTX_data_scene(C);
 +      p->depsgraph = CTX_data_depsgraph(C);
 +      p->win = CTX_wm_window(C);
 +
 +      unit_m4(p->imat);
 +      unit_m4(p->mat);
 +
 +      switch (curarea->spacetype) {
 +              /* supported views first */
 +              case SPACE_VIEW3D:
 +              {
 +                      /* View3D *v3d = curarea->spacedata.first; */
 +                      /* RegionView3D *rv3d = ar->regiondata; */
 +
 +                      /* set current area
++                       * - must verify that region data is 3D-view (and not something else)
 +                       */
 +                      /* CAUTION: If this is the "toolbar", then this will change on the first stroke */
 +                      p->sa = curarea;
 +                      p->ar = ar;
 +                      p->align_flag = &ts->annotate_v3d_align;
 +
 +                      if (ar->regiondata == NULL) {
 +                              p->status = GP_STATUS_ERROR;
 +                              if (G.debug & G_DEBUG)
 +                                      printf("Error: 3D-View active region doesn't have any region data, so cannot be drawable\n");
 +                              return 0;
 +                      }
 +                      break;
 +              }
 +              case SPACE_NODE:
 +              {
 +                      /* SpaceNode *snode = curarea->spacedata.first; */
 +
 +                      /* set current area */
 +                      p->sa = curarea;
 +                      p->ar = ar;
 +                      p->v2d = &ar->v2d;
 +                      p->align_flag = &ts->gpencil_v2d_align;
 +                      break;
 +              }
 +              case SPACE_SEQ:
 +              {
 +                      SpaceSeq *sseq = curarea->spacedata.first;
 +
 +                      /* set current area */
 +                      p->sa = curarea;
 +                      p->ar = ar;
 +                      p->v2d = &ar->v2d;
 +                      p->align_flag = &ts->gpencil_seq_align;
 +
 +                      /* check that gpencil data is allowed to be drawn */
 +                      if (sseq->mainb == SEQ_DRAW_SEQUENCE) {
 +                              p->status = GP_STATUS_ERROR;
 +                              if (G.debug & G_DEBUG)
 +                                      printf("Error: In active view (sequencer), active mode doesn't support Grease Pencil\n");
 +                              return 0;
 +                      }
 +                      break;
 +              }
 +              case SPACE_IMAGE:
 +              {
 +                      /* SpaceImage *sima = curarea->spacedata.first; */
 +
 +                      /* set the current area */
 +                      p->sa = curarea;
 +                      p->ar = ar;
 +                      p->v2d = &ar->v2d;
 +                      p->align_flag = &ts->gpencil_ima_align;
 +                      break;
 +              }
 +              case SPACE_CLIP:
 +              {
 +                      SpaceClip *sc = curarea->spacedata.first;
 +                      MovieClip *clip = ED_space_clip_get_clip(sc);
 +
 +                      if (clip == NULL) {
 +                              p->status = GP_STATUS_ERROR;
 +                              return false;
 +                      }
 +
 +                      /* set the current area */
 +                      p->sa = curarea;
 +                      p->ar = ar;
 +                      p->v2d = &ar->v2d;
 +                      p->align_flag = &ts->gpencil_v2d_align;
 +
 +                      invert_m4_m4(p->imat, sc->unistabmat);
 +
 +                      /* custom color for new layer */
 +                      p->custom_color[0] = 1.0f;
 +                      p->custom_color[1] = 0.0f;
 +                      p->custom_color[2] = 0.5f;
 +                      p->custom_color[3] = 0.9f;
 +
 +                      if (sc->gpencil_src == SC_GPENCIL_SRC_TRACK) {
 +                              int framenr = ED_space_clip_get_clip_frame_number(sc);
 +                              MovieTrackingTrack *track = BKE_tracking_track_get_active(&clip->tracking);
 +                              MovieTrackingMarker *marker = track ? BKE_tracking_marker_get(track, framenr) : NULL;
 +
 +                              if (marker) {
 +                                      p->imat[3][0] -= marker->pos[0];
 +                                      p->imat[3][1] -= marker->pos[1];
 +                              }
 +                              else {
 +                                      p->status = GP_STATUS_ERROR;
 +                                      return false;
 +                              }
 +                      }
 +
 +                      invert_m4_m4(p->mat, p->imat);
 +                      copy_m4_m4(p->gsc.mat, p->mat);
 +                      break;
 +              }
 +              /* unsupported views */
 +              default:
 +              {
 +                      p->status = GP_STATUS_ERROR;
 +                      if (G.debug & G_DEBUG)
 +                              printf("Error: Annotations are not supported in this editor\n");
 +                      return 0;
 +              }
 +      }
 +
 +      /* get gp-data */
 +      gpd_ptr = ED_gpencil_data_get_pointers(C, &p->ownerPtr);
 +      if ((gpd_ptr == NULL) || !ED_gpencil_data_owner_is_annotation(&p->ownerPtr)) {
 +              p->status = GP_STATUS_ERROR;
 +              if (G.debug & G_DEBUG)
 +                      printf("Error: Current context doesn't allow for any Annotation data\n");
 +              return 0;
 +      }
 +      else {
 +              /* if no existing GPencil block exists, add one */
 +              if (*gpd_ptr == NULL) {
 +                      bGPdata *gpd = BKE_gpencil_data_addnew(bmain, "Annotations");
 +                      *gpd_ptr = gpd;
 +
 +                      /* mark datablock as being used for annotations */
 +                      gpd->flag |= GP_DATA_ANNOTATIONS;
 +
 +                      /* annotations always in front of all objects */
 +                      gpd->xray_mode = GP_XRAY_FRONT;
 +              }
 +              p->gpd = *gpd_ptr;
 +      }
 +
 +      if (ED_gpencil_session_active() == 0) {
 +              /* initialize undo stack,
 +               * also, existing undo stack would make buffer drawn
 +               */
 +              gpencil_undo_init(p->gpd);
 +      }
 +
 +      /* clear out buffer (stored in gp-data), in case something contaminated it */
 +      gp_session_validatebuffer(p);
 +
 +      return 1;
 +}
 +
 +/* init new painting session */
 +static tGPsdata *gp_session_initpaint(bContext *C)
 +{
 +      tGPsdata *p = NULL;
 +
 +      /* create new context data */
 +      p = MEM_callocN(sizeof(tGPsdata), "Annotation Drawing Data");
 +
 +      /* Try to initialise context data
 +       * WARNING: This may not always succeed (e.g. using GP in an annotation-only context)
 +       */
 +      if (gp_session_initdata(C, p) == 0) {
 +              /* Invalid state - Exit
 +               * NOTE: It should be safe to just free the data, since failing context checks should
 +               * only happen when no data has been allocated.
 +               */
 +              MEM_freeN(p);
 +              return NULL;
 +      }
 +
 +      /* Radius for eraser circle is defined in userprefs */
 +      /* NOTE: we do this here, so that if we exit immediately,
 +       *       erase size won't get lost
 +       */
 +      p->radius = U.gp_eraser;
 +
 +      /* return context data for running paint operator */
 +      return p;
 +}
 +
 +/* cleanup after a painting session */
 +static void gp_session_cleanup(tGPsdata *p)
 +{
 +      bGPdata *gpd = (p) ? p->gpd : NULL;
 +
 +      /* error checking */
 +      if (gpd == NULL)
 +              return;
 +
 +      /* free stroke buffer */
 +      if (gpd->runtime.sbuffer) {
 +              /* printf("\t\tGP - free sbuffer\n"); */
 +              MEM_freeN(gpd->runtime.sbuffer);
 +              gpd->runtime.sbuffer = NULL;
 +      }
 +
 +      /* clear flags */
 +      gpd->runtime.sbuffer_size = 0;
 +      gpd->runtime.sbuffer_sflag = 0;
 +      p->inittime = 0.0;
 +}
 +
 +static void gp_session_free(tGPsdata *p)
 +{
 +      MEM_freeN(p);
 +}
 +
 +
 +/* init new stroke */
 +static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Depsgraph *depsgraph)
 +{
 +      Scene *scene = p->scene;
 +      ToolSettings *ts = scene->toolsettings;
 +
 +      /* get active layer (or add a new one if non-existent) */
 +      p->gpl = BKE_gpencil_layer_getactive(p->gpd);
 +      if (p->gpl == NULL) {
 +              p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("Note"), true);
 +
 +              if (p->custom_color[3])
 +                      copy_v3_v3(p->gpl->color, p->custom_color);
 +      }
 +      if (p->gpl->flag & GP_LAYER_LOCKED) {
 +              p->status = GP_STATUS_ERROR;
 +              if (G.debug & G_DEBUG)
 +                      printf("Error: Cannot paint on locked layer\n");
 +              return;
 +      }
 +
 +      /* get active frame (add a new one if not matching frame) */
 +      if (paintmode == GP_PAINTMODE_ERASER) {
 +              /* Eraser mode:
 +               * 1) Only allow erasing on the active layer (unlike for 3d-art Grease Pencil),
 +               *    since we won't be exposing layer locking in the UI
 +               * 2) Ensure that p->gpf refers to the frame used for the active layer
 +               *    (to avoid problems with other tools which expect it to exist)
 +               */
 +              bool has_layer_to_erase = false;
 +
 +              if (gpencil_layer_is_editable(p->gpl)) {
 +                      /* Ensure that there's stuff to erase here (not including selection mask below)... */
 +                      if (p->gpl->actframe && p->gpl->actframe->strokes.first) {
 +                              has_layer_to_erase = true;
 +                      }
 +              }
 +
 +              /* Ensure active frame is set correctly... */
 +              p->gpf = p->gpl->actframe;
 +
 +              /* Restrict eraser to only affecting selected strokes, if the "selection mask" is on
 +               * (though this is only available in editmode)
 +               */
 +              if (p->gpd->flag & GP_DATA_STROKE_EDITMODE) {
 +                      if (ts->gp_sculpt.flag & GP_BRUSHEDIT_FLAG_SELECT_MASK) {
 +                              p->flags |= GP_PAINTFLAG_SELECTMASK;
 +                      }
 +              }
 +
 +              if (has_layer_to_erase == false) {
 +                      p->status = GP_STATUS_ERROR;
 +                      //if (G.debug & G_DEBUG)
 +                              printf("Error: Eraser will not be affecting anything (gpencil_paint_init)\n");
 +                      return;
 +              }
 +      }
 +      else {
 +              /* Drawing Modes - Add a new frame if needed on the active layer */
 +              short add_frame_mode = GP_GETFRAME_ADD_NEW;
 +
 +              if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST)
 +                      add_frame_mode = GP_GETFRAME_ADD_COPY;
 +              else
 +                      add_frame_mode = GP_GETFRAME_ADD_NEW;
 +
 +              p->gpf = BKE_gpencil_layer_getframe(p->gpl, CFRA, add_frame_mode);
 +
 +              if (p->gpf == NULL) {
 +                      p->status = GP_STATUS_ERROR;
 +                      if (G.debug & G_DEBUG)
 +                              printf("Error: No frame created (gpencil_paint_init)\n");
 +                      return;
 +              }
 +              else {
 +                      p->gpf->flag |= GP_FRAME_PAINT;
 +              }
 +      }
 +
 +      /* set 'eraser' for this stroke if using eraser */
 +      p->paintmode = paintmode;
 +      if (p->paintmode == GP_PAINTMODE_ERASER) {
 +              p->gpd->runtime.sbuffer_sflag |= GP_STROKE_ERASER;
 +
 +              /* check if we should respect depth while erasing */
 +              if (p->sa->spacetype == SPACE_VIEW3D) {
 +                      if (p->gpl->flag & GP_LAYER_NO_XRAY) {
 +                              p->flags |= GP_PAINTFLAG_V3D_ERASER_DEPTH;
 +                      }
 +              }
 +      }
 +      else {
 +              /* disable eraser flags - so that we can switch modes during a session */
 +              p->gpd->runtime.sbuffer_sflag &= ~GP_STROKE_ERASER;
 +
 +              if (p->sa->spacetype == SPACE_VIEW3D) {
 +                      if (p->gpl->flag & GP_LAYER_NO_XRAY) {
 +                              p->flags &= ~GP_PAINTFLAG_V3D_ERASER_DEPTH;
 +                      }
 +              }
 +      }
 +
 +      /* set 'initial run' flag, which is only used to denote when a new stroke is starting */
 +      p->flags |= GP_PAINTFLAG_FIRSTRUN;
 +
 +
 +      /* when drawing in the camera view, in 2D space, set the subrect */
 +      p->subrect = NULL;
 +      if ((*p->align_flag & GP_PROJECT_VIEWSPACE) == 0) {
 +              if (p->sa->spacetype == SPACE_VIEW3D) {
 +                      View3D *v3d = p->sa->spacedata.first;
 +                      RegionView3D *rv3d = p->ar->regiondata;
 +
 +                      /* for camera view set the subrect */
 +                      if (rv3d->persp == RV3D_CAMOB) {
 +                              ED_view3d_calc_camera_border(p->scene, depsgraph, p->ar, v3d, rv3d, &p->subrect_data, true); /* no shift */
 +                              p->subrect = &p->subrect_data;
 +                      }
 +              }
 +      }
 +
 +      /* init stroke point space-conversion settings... */
 +      p->gsc.gpd = p->gpd;
 +      p->gsc.gpl = p->gpl;
 +
 +      p->gsc.sa = p->sa;
 +      p->gsc.ar = p->ar;
 +      p->gsc.v2d = p->v2d;
 +
 +      p->gsc.subrect_data = p->subrect_data;
 +      p->gsc.subrect = p->subrect;
 +
 +      copy_m4_m4(p->gsc.mat, p->mat);
 +
 +
 +      /* check if points will need to be made in view-aligned space */
 +      if (*p->align_flag & GP_PROJECT_VIEWSPACE) {
 +              switch (p->sa->spacetype) {
 +                      case SPACE_VIEW3D:
 +                      {
 +                              p->gpd->runtime.sbuffer_sflag |= GP_STROKE_3DSPACE;
 +                              break;
 +                      }
 +                      case SPACE_NODE:
 +                      {
 +                              p->gpd->runtime.sbuffer_sflag |= GP_STROKE_2DSPACE;
 +                              break;
 +                      }
 +                      case SPACE_SEQ:
 +                      {
 +                              p->gpd->runtime.sbuffer_sflag |= GP_STROKE_2DSPACE;
 +                              break;
 +                      }
 +                      case SPACE_IMAGE:
 +                      {
 +                              SpaceImage *sima = (SpaceImage *)p->sa->spacedata.first;
 +
 +                              /* only set these flags if the image editor doesn't have an image active,
 +                               * otherwise user will be confused by strokes not appearing after they're drawn
 +                               *
 +                               * Admittedly, this is a bit hacky, but it works much nicer from an ergonomic standpoint!
 +                               */
 +                              if (ELEM(NULL, sima, sima->image)) {
 +                                      /* make strokes be drawn in screen space */
 +                                      p->gpd->runtime.sbuffer_sflag &= ~GP_STROKE_2DSPACE;
 +                                      *(p->align_flag) &= ~GP_PROJECT_VIEWSPACE;
 +                              }
 +                              else {
 +                                      p->gpd->runtime.sbuffer_sflag |= GP_STROKE_2DSPACE;
 +                              }
 +                              break;
 +                      }
 +                      case SPACE_CLIP:
 +                      {
 +                              p->gpd->runtime.sbuffer_sflag |= GP_STROKE_2DSPACE;
 +                              break;
 +                      }
 +              }
 +      }
 +}
 +
 +/* finish off a stroke (clears buffer, but doesn't finish the paint operation) */
 +static void gp_paint_strokeend(tGPsdata *p)
 +{
 +      ToolSettings *ts = p->scene->toolsettings;
 +      /* for surface sketching, need to set the right OpenGL context stuff so that
 +       * the conversions will project the values correctly...
 +       */
 +      if (gpencil_project_check(p)) {
 +              View3D *v3d = p->sa->spacedata.first;
 +
 +              /* need to restore the original projection settings before packing up */
 +              view3d_region_operator_needs_opengl(p->win, p->ar);
 +              ED_view3d_autodist_init(p->depsgraph, p->ar, v3d, (ts->annotate_v3d_align & GP_PROJECT_DEPTH_STROKE) ? 1 : 0);
 +      }
 +
 +      /* check if doing eraser or not */
 +      if ((p->gpd->runtime.sbuffer_sflag & GP_STROKE_ERASER) == 0) {
 +              /* simplify stroke before transferring? */
 +              gp_stroke_simplify(p);
 +
 +              /* transfer stroke to frame */
 +              gp_stroke_newfrombuffer(p);
 +      }
 +
 +      /* clean up buffer now */
 +      gp_session_validatebuffer(p);
 +}
 +
 +/* finish off stroke painting operation */
 +static void gp_paint_cleanup(tGPsdata *p)
 +{
 +      /* p->gpd==NULL happens when stroke failed to initialize,
 +       * for example when GP is hidden in current space (sergey)
 +       */
 +      if (p->gpd) {
 +              /* finish off a stroke */
 +              gp_paint_strokeend(p);
 +      }
 +
 +      /* "unlock" frame */
 +      if (p->gpf)
 +              p->gpf->flag &= ~GP_FRAME_PAINT;
 +}
 +
 +/* ------------------------------- */
 +
 +/* Helper callback for drawing the cursor itself */
 +static void gpencil_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_ptr)
 +{
 +      tGPsdata *p = (tGPsdata *)p_ptr;
 +
 +      if (p->paintmode == GP_PAINTMODE_ERASER) {
 +              GPUVertFormat *format = immVertexFormat();
 +              const uint shdr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
 +              immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
 +
 +              GPU_line_smooth(true);
 +              GPU_blend(true);
 +              GPU_blend_set_func_separate(GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
 +
 +              immUniformColor4ub(255, 100, 100, 20);
 +              imm_draw_circle_fill_2d(shdr_pos, x, y, p->radius, 40);
 +
 +              immUnbindProgram();
 +
 +              immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR);
 +
 +              float viewport_size[4];
 +              GPU_viewport_size_get_f(viewport_size);
 +              immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
 +
 +              immUniformColor4f(1.0f, 0.39f, 0.39f, 0.78f);
 +              immUniform1i("colors_len", 0);  /* "simple" mode */
 +              immUniform1f("dash_width", 12.0f);
 +              immUniform1f("dash_factor", 0.5f);
 +
 +              imm_draw_circle_wire_2d(
 +                      shdr_pos, x, y, p->radius,
 +                      /* XXX Dashed shader gives bad results with sets of small segments currently,
 +                       *     temp hack around the issue. :( */
 +                      max_ii(8, p->radius / 2));  /* was fixed 40 */
 +
 +              immUnbindProgram();
 +
 +              GPU_blend(false);
 +              GPU_line_smooth(false);
 +      }
 +}
 +
 +/* Turn brush cursor in 3D view on/off */
 +static void gpencil_draw_toggle_eraser_cursor(bContext *C, tGPsdata *p, short enable)
 +{
 +      if (p->erasercursor && !enable) {
 +              /* clear cursor */
 +              WM_paint_cursor_end(CTX_wm_manager(C), p->erasercursor);
 +              p->erasercursor = NULL;
 +      }
 +      else if (enable && !p->erasercursor) {
 +              /* enable cursor */
 +              p->erasercursor = WM_paint_cursor_activate(
 +                      CTX_wm_manager(C),
 +                      SPACE_TYPE_ANY, RGN_TYPE_ANY,
 +                      NULL, /* XXX */
 +                      gpencil_draw_eraser, p);
 +      }
 +}
 +
 +/* Check if tablet eraser is being used (when processing events) */
 +static bool gpencil_is_tablet_eraser_active(const wmEvent *event)
 +{
 +      if (event->tablet_data) {
 +              const wmTabletData *wmtab = event->tablet_data;
 +              return (wmtab->Active == EVT_TABLET_ERASER);
 +      }
 +
 +      return false;
 +}
 +
 +/* ------------------------------- */
 +
 +static void gpencil_draw_exit(bContext *C, wmOperator *op)
 +{
 +      tGPsdata *p = op->customdata;
 +
 +      /* restore cursor to indicate end of drawing */
 +      WM_cursor_modal_restore(CTX_wm_window(C));
 +
 +      /* don't assume that operator data exists at all */
 +      if (p) {
 +              /* check size of buffer before cleanup, to determine if anything happened here */
 +              if (p->paintmode == GP_PAINTMODE_ERASER) {
 +                      /* turn off radial brush cursor */
 +                      gpencil_draw_toggle_eraser_cursor(C, p, false);
 +              }
 +
 +              /* always store the new eraser size to be used again next time
 +               * NOTE: Do this even when not in eraser mode, as eraser may
 +               *       have been toggled at some point.
 +               */
 +              U.gp_eraser = p->radius;
 +
 +              /* clear undo stack */
 +              gpencil_undo_finish();
 +
 +              /* cleanup */
 +              gp_paint_cleanup(p);
 +              gp_session_cleanup(p);
 +              gp_session_free(p);
 +              p = NULL;
 +      }
 +
 +      op->customdata = NULL;
 +}
 +
 +static void gpencil_draw_cancel(bContext *C, wmOperator *op)
 +{
 +      /* this is just a wrapper around exit() */
 +      gpencil_draw_exit(C, op);
 +}
 +
 +/* ------------------------------- */
 +
 +
 +static int gpencil_draw_init(bContext *C, wmOperator *op, const wmEvent *event)
 +{
 +      tGPsdata *p;
 +      eGPencil_PaintModes paintmode = RNA_enum_get(op->ptr, "mode");
 +
 +      /* check context */
 +      p = op->customdata = gp_session_initpaint(C);
 +      if ((p == NULL) || (p->status == GP_STATUS_ERROR)) {
 +              /* something wasn't set correctly in context */
 +              gpencil_draw_exit(C, op);
 +              return 0;
 +      }
 +
 +      /* init painting data */
 +      gp_paint_initstroke(p, paintmode, CTX_data_depsgraph(C));
 +      if (p->status == GP_STATUS_ERROR) {
 +              gpencil_draw_exit(C, op);
 +              return 0;
 +      }
 +
 +      if (event != NULL) {
 +              p->keymodifier = event->keymodifier;
 +      }
 +      else {
 +              p->keymodifier = -1;
 +      }
 +
 +      /* everything is now setup ok */
 +      return 1;
 +}
 +
 +
 +/* ------------------------------- */
 +
 +/* ensure that the correct cursor icon is set */
 +static void gpencil_draw_cursor_set(tGPsdata *p)
 +{
 +      if (p->paintmode == GP_PAINTMODE_ERASER)
 +              WM_cursor_modal_set(p->win, BC_CROSSCURSOR);  /* XXX need a better cursor */
 +      else
 +              WM_cursor_modal_set(p->win, BC_PAINTBRUSHCURSOR);
 +}
 +
 +/* update UI indicators of status, including cursor and header prints */
 +static void gpencil_draw_status_indicators(bContext *C, tGPsdata *p)
 +{
 +      /* header prints */
 +      switch (p->status) {
 +              case GP_STATUS_PAINTING:
 +                      switch (p->paintmode) {
 +                              case GP_PAINTMODE_DRAW_POLY:
 +                                      /* Provide usage tips, since this is modal, and unintuitive without hints */
 +                                      ED_workspace_status_text(C, IFACE_("Annotation Create Poly: LMB click to place next stroke vertex | "
 +                                                                        "ESC/Enter to end  (or click outside this area)"));
 +                                      break;
 +                              default:
 +                                      /* Do nothing - the others are self explanatory, exit quickly once the mouse is released
 +                                       * Showing any text would just be annoying as it would flicker.
 +                                       */
 +                                      break;
 +                      }
 +                      break;
 +
 +              case GP_STATUS_IDLING:
 +                      /* print status info */
 +                      switch (p->paintmode) {
 +                              case GP_PAINTMODE_ERASER:
 +                                      ED_workspace_status_text(C, IFACE_("Annotation Eraser: Hold and drag LMB or RMB to erase | "
 +                                                                        "ESC/Enter to end  (or click outside this area)"));
 +                                      break;
 +                              case GP_PAINTMODE_DRAW_STRAIGHT:
 +                                      ED_workspace_status_text(C, IFACE_("Annotation Line Draw: Hold and drag LMB to draw | "
 +                                                                        "ESC/Enter to end  (or click outside this area)"));
 +                                      break;
 +                              case GP_PAINTMODE_DRAW:
 +                                      ED_workspace_status_text(C, IFACE_("Annotation Freehand Draw: Hold and drag LMB to draw | "
 +                                                                        "E/ESC/Enter to end  (or click outside this area)"));
 +                                      break;
 +                              case GP_PAINTMODE_DRAW_POLY:
 +                                      ED_workspace_status_text(C, IFACE_("Annotation Create Poly: LMB click to place next stroke vertex | "
 +                                                                        "ESC/Enter to end  (or click outside this area)"));
 +                                      break;
 +
 +                              default: /* unhandled future cases */
 +                                      ED_workspace_status_text(C, IFACE_("Annotation Session: ESC/Enter to end   (or click outside this area)"));
 +                                      break;
 +                      }
 +                      break;
 +
 +              case GP_STATUS_ERROR:
 +              case GP_STATUS_DONE:
 +                      /* clear status string */
 +                      ED_workspace_status_text(C, NULL);
 +                      break;
 +      }
 +}
 +
 +/* ------------------------------- */
 +
 +/* create a new stroke point at the point indicated by the painting context */
 +static void gpencil_draw_apply(wmOperator *op, tGPsdata *p, Depsgraph *depsgraph)
 +{
 +      /* handle drawing/erasing -> test for erasing first */
 +      if (p->paintmode == GP_PAINTMODE_ERASER) {
 +              /* do 'live' erasing now */
 +              gp_stroke_doeraser(p);
 +
 +              /* store used values */
 +              p->mvalo[0] = p->mval[0];
 +              p->mvalo[1] = p->mval[1];
 +              p->opressure = p->pressure;
 +      }
 +      /* only add current point to buffer if mouse moved (even though we got an event, it might be just noise) */
 +      else if (gp_stroke_filtermval(p, p->mval, p->mvalo)) {
 +              /* try to add point */
 +              short ok = gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime);
 +
 +              /* handle errors while adding point */
 +              if ((ok == GP_STROKEADD_FULL) || (ok == GP_STROKEADD_OVERFLOW)) {
 +                      /* finish off old stroke */
 +                      gp_paint_strokeend(p);
 +                      /* And start a new one!!! Else, projection errors! */
 +                      gp_paint_initstroke(p, p->paintmode, depsgraph);
 +
 +                      /* start a new stroke, starting from previous point */
 +                      /* XXX Must manually reset inittime... */
 +                      /* XXX We only need to reuse previous point if overflow! */
 +                      if (ok == GP_STROKEADD_OVERFLOW) {
 +                              p->inittime = p->ocurtime;
 +                              gp_stroke_addpoint(p, p->mvalo, p->opressure, p->ocurtime);
 +                      }
 +                      else {
 +                              p->inittime = p->curtime;
 +                      }
 +                      gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime);
 +              }
 +              else if (ok == GP_STROKEADD_INVALID) {
 +                      /* the painting operation cannot continue... */
 +                      BKE_report(op->reports, RPT_ERROR, "Cannot paint stroke");
 +                      p->status = GP_STATUS_ERROR;
 +
 +                      if (G.debug & G_DEBUG)
 +                              printf("Error: Grease-Pencil Paint - Add Point Invalid\n");
 +                      return;
 +              }
 +
 +              /* store used values */
 +              p->mvalo[0] = p->mval[0];
 +              p->mvalo[1] = p->mval[1];
 +              p->opressure = p->pressure;
 +              p->ocurtime = p->curtime;
 +      }
 +}
 +
 +/* handle draw event */
 +static void gpencil_draw_apply_event(wmOperator *op, const wmEvent *event, Depsgraph *depsgraph)
 +{
 +      tGPsdata *p = op->customdata;
 +      PointerRNA itemptr;
 +      float mousef[2];
 +      int tablet = 0;
 +
 +      /* convert from window-space to area-space mouse coordinates
 +       * NOTE: float to ints conversions, +1 factor is probably used to ensure a bit more accurate rounding...
 +       */
 +      p->mval[0] = event->mval[0] + 1;
 +      p->mval[1] = event->mval[1] + 1;
 +
 +      /* verify key status for straight lines */
 +      if ((event->ctrl > 0) || (event->alt > 0)) {
 +              if (p->straight[0] == 0) {
 +                      int dx = abs(p->mval[0] - p->mvalo[0]);
 +                      int dy = abs(p->mval[1] - p->mvalo[1]);
 +                      if ((dx > 0) || (dy > 0)) {
 +                              /* check mouse direction to replace the other coordinate with previous values */
 +                              if (dx >= dy) {
 +                                      /* horizontal */
 +                                      p->straight[0] = 1;
 +                                      p->straight[1] = p->mval[1]; /* save y */
 +                              }
 +                              else {
 +                                      /* vertical */
 +                                      p->straight[0] = 2;
 +                                      p->straight[1] = p->mval[0]; /* save x */
 +                              }
 +                      }
 +              }
 +      }
 +      else {
 +              p->straight[0] = 0;
 +      }
 +
 +      p->curtime = PIL_check_seconds_timer();
 +
 +      /* handle pressure sensitivity (which is supplied by tablets) */
 +      if (event->tablet_data) {
 +              const wmTabletData *wmtab = event->tablet_data;
 +
 +              tablet = (wmtab->Active != EVT_TABLET_NONE);
 +              p->pressure = wmtab->Pressure;
 +
 +              /* Hack for pressure sensitive eraser on D+RMB when using a tablet:
 +               * The pen has to float over the tablet surface, resulting in
 +               * zero pressure (T47101). Ignore pressure values if floating
 +               * (i.e. "effectively zero" pressure), and only when the "active"
 +               * end is the stylus (i.e. the default when not eraser)
 +               */
 +              if (p->paintmode == GP_PAINTMODE_ERASER) {
 +                      if ((wmtab->Active != EVT_TABLET_ERASER) && (p->pressure < 0.001f)) {
 +                              p->pressure = 1.0f;
 +                      }
 +              }
 +      }
 +      else {
 +              /* No tablet data -> No pressure info is available */
 +              p->pressure = 1.0f;
 +      }
 +
 +      /* special exception for start of strokes (i.e. maybe for just a dot) */
 +      if (p->flags & GP_PAINTFLAG_FIRSTRUN) {
 +              p->flags &= ~GP_PAINTFLAG_FIRSTRUN;
 +
 +              p->mvalo[0] = p->mval[0];
 +              p->mvalo[1] = p->mval[1];
 +              p->opressure = p->pressure;
 +              p->inittime = p->ocurtime = p->curtime;
 +              p->straight[0] = 0;
 +              p->straight[1] = 0;
 +
 +              /* special exception here for too high pressure values on first touch in
 +               * windows for some tablets, then we just skip first touch...
 +               */
 +              if (tablet && (p->pressure >= 0.99f))
 +                      return;
 +      }
 +
 +      /* check if alt key is pressed and limit to straight lines */
 +      if (p->straight[0] != 0) {
 +              if (p->straight[0] == 1) {
 +                      /* horizontal */
 +                      p->mval[1] = p->straight[1]; /* replace y */
 +              }
 +              else {
 +                      /* vertical */
 +                      p->mval[0] = p->straight[1]; /* replace x */
 +              }
 +      }
 +
 +      /* fill in stroke data (not actually used directly by gpencil_draw_apply) */
 +      RNA_collection_add(op->ptr, "stroke", &itemptr);
 +
 +      mousef[0] = p->mval[0];
 +      mousef[1] = p->mval[1];
 +      RNA_float_set_array(&itemptr, "mouse", mousef);
 +      RNA_float_set(&itemptr, "pressure", p->pressure);
 +      RNA_boolean_set(&itemptr, "is_start", (p->flags & GP_PAINTFLAG_FIRSTRUN) != 0);
 +
 +      RNA_float_set(&itemptr, "time", p->curtime - p->inittime);
 +
 +      /* apply the current latest drawing point */
 +      gpencil_draw_apply(op, p, depsgraph);
 +
 +      /* force refresh */
 +      ED_region_tag_redraw(p->ar); /* just active area for now, since doing whole screen is too slow */
 +}
 +
 +/* ------------------------------- */
 +
 +/* operator 'redo' (i.e. after changing some properties, but also for repeat last) */
 +static int gpencil_draw_exec(bContext *C, wmOperator *op)
 +{
 +      tGPsdata *p = NULL;
 +      Depsgraph *depsgraph = CTX_data_depsgraph(C);
 +
 +      /* printf("GPencil - Starting Re-Drawing\n"); */
 +
 +      /* try to initialize context data needed while drawing */
 +      if (!gpencil_draw_init(C, op, NULL)) {
 +              if (op->customdata) MEM_freeN(op->customdata);
 +              /* printf("\tGP - no valid data\n"); */
 +              return OPERATOR_CANCELLED;
 +      }
 +      else
 +              p = op->customdata;
 +
 +      /* printf("\tGP - Start redrawing stroke\n"); */
 +
 +      /* loop over the stroke RNA elements recorded (i.e. progress of mouse movement),
 +       * setting the relevant values in context at each step, then applying
 +       */
 +      RNA_BEGIN (op->ptr, itemptr, "stroke")
 +      {
 +              float mousef[2];
 +
 +              /* printf("\t\tGP - stroke elem\n"); */
 +
 +              /* get relevant data for this point from stroke */
 +              RNA_float_get_array(&itemptr, "mouse", mousef);
 +              p->mval[0] = (int)mousef[0];
 +              p->mval[1] = (int)mousef[1];
 +              p->pressure = RNA_float_get(&itemptr, "pressure");
 +              p->curtime = (double)RNA_float_get(&itemptr, "time") + p->inittime;
 +
 +              if (RNA_boolean_get(&itemptr, "is_start")) {
 +                      /* if first-run flag isn't set already (i.e. not true first stroke),
 +                       * then we must terminate the previous one first before continuing
 +                       */
 +                      if ((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) {
 +                              /* TODO: both of these ops can set error-status, but we probably don't need to worry */
 +                              gp_paint_strokeend(p);
 +                              gp_paint_initstroke(p, p->paintmode, depsgraph);
 +                      }
 +              }
 +
 +              /* if first run, set previous data too */
 +              if (p->flags & GP_PAINTFLAG_FIRSTRUN) {
 +                      p->flags &= ~GP_PAINTFLAG_FIRSTRUN;
 +
 +                      p->mvalo[0] = p->mval[0];
 +                      p->mvalo[1] = p->mval[1];
 +                      p->opressure = p->pressure;
 +                      p->ocurtime = p->curtime;
 +              }
 +
 +              /* apply this data as necessary now (as per usual) */
 +              gpencil_draw_apply(op, p, depsgraph);
 +      }
 +      RNA_END;
 +
 +      /* printf("\tGP - done\n"); */
 +
 +      /* cleanup */
 +      gpencil_draw_exit(C, op);
 +
 +      /* refreshes */
 +      WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
 +
 +      /* done */
 +      return OPERATOR_FINISHED;
 +}
 +
 +/* ------------------------------- */
 +
 +/* start of interactive drawing part of operator */
 +static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 +{
 +      Object *ob = CTX_data_active_object(C);
 +      ScrArea *sa = CTX_wm_area(C);
 +      tGPsdata *p = NULL;
 +
 +      /* GPXX Need a better solution */
 +      if (sa && sa->spacetype == SPACE_VIEW3D) {
 +              if ((ob != NULL) && (ob->type == OB_GPENCIL)) {
 +                      BKE_report(op->reports, RPT_ERROR, "Cannot draw annotation with a Grease Pencil object active");
 +                      return OPERATOR_CANCELLED;
 +              }
 +      }
 +      if (G.debug & G_DEBUG)
 +              printf("GPencil - Starting Drawing\n");
 +
 +      /* try to initialize context data needed while drawing */
 +      if (!gpencil_draw_init(C, op, event)) {
 +              if (op->customdata)
 +                      MEM_freeN(op->customdata);
 +              if (G.debug & G_DEBUG)
 +                      printf("\tGP - no valid data\n");
 +              return OPERATOR_CANCELLED;
 +      }
 +      else
 +              p = op->customdata;
 +
 +      /* TODO: set any additional settings that we can take from the events?
 +       * TODO? if tablet is erasing, force eraser to be on? */
 +
 +      /* TODO: move cursor setting stuff to stroke-start so that paintmode can be changed midway... */
 +
 +      /* if eraser is on, draw radial aid */
 +      if (p->paintmode == GP_PAINTMODE_ERASER) {
 +              gpencil_draw_toggle_eraser_cursor(C, p, true);
 +      }
 +      /* set cursor
 +       * NOTE: This may change later (i.e. intentionally via brush toggle,
 +       *       or unintentionally if the user scrolls outside the area)...
 +       */
 +      gpencil_draw_cursor_set(p);
 +
 +      /* only start drawing immediately if we're allowed to do so... */
 +      if (RNA_boolean_get(op->ptr, "wait_for_input") == false) {
 +              /* hotkey invoked - start drawing */
 +              /* printf("\tGP - set first spot\n"); */
 +              p->status = GP_STATUS_PAINTING;
 +
 +              /* handle the initial drawing - i.e. for just doing a simple dot */
 +              gpencil_draw_apply_event(op, event, CTX_data_depsgraph(C));
 +              op->flag |= OP_IS_MODAL_CURSOR_REGION;
 +      }
 +      else {
 +              /* toolbar invoked - don't start drawing yet... */
 +              /* printf("\tGP - hotkey invoked... waiting for click-drag\n"); */
 +              op->flag |= OP_IS_MODAL_CURSOR_REGION;
 +      }
 +
 +      WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
 +      /* add a modal handler for this operator, so that we can then draw continuous strokes */
 +      WM_event_add_modal_handler(C, op);
 +      return OPERATOR_RUNNING_MODAL;
 +}
 +
 +/* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */
 +static bool gpencil_area_exists(bContext *C, ScrArea *sa_test)
 +{
 +      bScreen *sc = CTX_wm_screen(C);
 +      return (BLI_findindex(&sc->areabase, sa_test) != -1);
 +}
 +
 +static tGPsdata *gpencil_stroke_begin(bContext *C, wmOperator *op)
 +{
 +      tGPsdata *p = op->customdata;
 +
 +      /* we must check that we're still within the area that we're set up to work from
 +       * otherwise we could crash (see bug #20586)
 +       */
 +      if (CTX_wm_area(C) != p->sa) {
 +              printf("\t\t\tGP - wrong area execution abort!\n");
 +              p->status = GP_STATUS_ERROR;
 +      }
 +
 +      /* printf("\t\tGP - start stroke\n"); */
 +
 +      /* we may need to set up paint env again if we're resuming */
 +      /* XXX: watch it with the paintmode! in future,
 +       *      it'd be nice to allow changing paint-mode when in sketching-sessions */
 +
 +      if (gp_session_initdata(C, p))
 +              gp_paint_initstroke(p, p->paintmode, CTX_data_depsgraph(C));
 +
 +      if (p->status != GP_STATUS_ERROR) {
 +              p->status = GP_STATUS_PAINTING;
 +              op->flag &= ~OP_IS_MODAL_CURSOR_REGION;
 +      }
 +
 +      return op->customdata;
 +}
 +
 +static void gpencil_stroke_end(wmOperator *op)
 +{
 +      tGPsdata *p = op->customdata;
 +
 +      gp_paint_cleanup(p);
 +
 +      gpencil_undo_push(p->gpd);
 +
 +      gp_session_cleanup(p);
 +
 +      p->status = GP_STATUS_IDLING;
 +      op->flag |= OP_IS_MODAL_CURSOR_REGION;
 +
 +      p->gpd = NULL;
 +      p->gpl = NULL;
 +      p->gpf = NULL;
 +}
 +
 +/* events handling during interactive drawing part of operator */
 +static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
 +{
 +      tGPsdata *p = op->customdata;
 +      int estate = OPERATOR_PASS_THROUGH; /* default exit state - pass through to support MMB view nav, etc. */
 +
 +      /* if (event->type == NDOF_MOTION)
 +       *    return OPERATOR_PASS_THROUGH;
 +       * -------------------------------
 +       * [mce] Not quite what I was looking
 +       * for, but a good start! GP continues to
 +       * draw on the screen while the 3D mouse
 +       * moves the viewpoint. Problem is that
 +       * the stroke is converted to 3D only after
 +       * it is finished. This approach should work
 +       * better in tools that immediately apply
 +       * in 3D space.
 +       */
 +
 +      if (p->status == GP_STATUS_IDLING) {
 +              ARegion *ar = CTX_wm_region(C);
 +              p->ar = ar;
 +      }
 +
 +      /* we don't pass on key events, GP is used with key-modifiers - prevents Dkey to insert drivers */
 +      if (ISKEYBOARD(event->type)) {
 +              if (ELEM(event->type, LEFTARROWKEY, DOWNARROWKEY, RIGHTARROWKEY, UPARROWKEY, ZKEY)) {
 +                      /* allow some keys:
 +                       *   - for frame changing [#33412]
 +                       *   - for undo (during sketching sessions)
 +                       */
 +              }
 +              else if (ELEM(event->type, PAD0, PAD1, PAD2, PAD3, PAD4, PAD5, PAD6, PAD7, PAD8, PAD9)) {
 +                      /* allow numpad keys so that camera/view manipulations can still take place
 +                       * - PAD0 in particular is really important for Grease Pencil drawing,
 +                       *   as animators may be working "to camera", so having this working
 +                       *   is essential for ensuring that they can quickly return to that view
 +                       */
 +              }
 +              else if ((event->type == BKEY) && (event->val == KM_RELEASE)) {
 +                      /* Add Blank Frame
 +                       * - Since this operator is non-modal, we can just call it here, and keep going...
 +                       * - This operator is especially useful when animating
 +                       */
 +                      WM_operator_name_call(C, "GPENCIL_OT_blank_frame_add", WM_OP_EXEC_DEFAULT, NULL);
 +                      estate = OPERATOR_RUNNING_MODAL;
 +              }
 +              else {
 +                      estate = OPERATOR_RUNNING_MODAL;
 +              }
 +      }
 +
 +      //printf("\tGP - handle modal event...\n");
 +
 +      /* exit painting mode (and/or end current stroke)
 +       * NOTE: cannot do RIGHTMOUSE (as is standard for canceling) as that would break polyline [#32647]
 +       */
 +      if (ELEM(event->type, RETKEY, PADENTER, ESCKEY, SPACEKEY, EKEY)) {
 +              /* exit() ends the current stroke before cleaning up */
 +              /* printf("\t\tGP - end of paint op + end of stroke\n"); */
 +              p->status = GP_STATUS_DONE;
 +              estate = OPERATOR_FINISHED;
 +      }
 +
 +      /* toggle painting mode upon mouse-button movement
 +       *  - LEFTMOUSE  = standard drawing (all) / straight line drawing (all) / polyline (toolbox only)
 +       *  - RIGHTMOUSE = polyline (hotkey) / eraser (all)
 +       *    (Disabling RIGHTMOUSE case here results in bugs like [#32647])
 +       * also making sure we have a valid event value, to not exit too early
 +       */
 +      if (ELEM(event->type, LEFTMOUSE, RIGHTMOUSE) && (ELEM(event->val, KM_PRESS, KM_RELEASE))) {
 +              /* if painting, end stroke */
 +              if (p->status == GP_STATUS_PAINTING) {
 +                      int sketch = 0;
 +
 +                      /* basically, this should be mouse-button up = end stroke
 +                       * BUT, polyline drawing is an exception -- all knots should be added during one session
 +                       */
 +                      sketch |= (p->paintmode == GP_PAINTMODE_DRAW_POLY);
 +
 +                      if (sketch) {
 +                              /* end stroke only, and then wait to resume painting soon */
 +                              /* printf("\t\tGP - end stroke only\n"); */
 +                              gpencil_stroke_end(op);
 +
 +                              /* If eraser mode is on, turn it off after the stroke finishes
 +                               * NOTE: This just makes it nicer to work with drawing sessions
 +                               */
 +                              if (p->paintmode == GP_PAINTMODE_ERASER) {
 +                                      p->paintmode = RNA_enum_get(op->ptr, "mode");
 +
 +                                      /* if the original mode was *still* eraser,
 +                                       * we'll let it say for now, since this gives
 +                                       * users an opportunity to have visual feedback
 +                                       * when adjusting eraser size
 +                                       */
 +                                      if (p->paintmode != GP_PAINTMODE_ERASER) {
 +                                              /* turn off cursor...
 +                                               * NOTE: this should be enough for now
 +                                               *       Just hiding this makes it seem like
 +                                               *       you can paint again...
 +                                               */
 +                                              gpencil_draw_toggle_eraser_cursor(C, p, false);
 +                                      }
 +                              }
 +
 +                              /* we've just entered idling state, so this event was processed (but no others yet) */
 +                              estate = OPERATOR_RUNNING_MODAL;
 +
 +                              /* stroke could be smoothed, send notifier to refresh screen */
 +                              WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
 +                      }
 +                      else {
 +                              /* printf("\t\tGP - end of stroke + op\n"); */
 +                              p->status = GP_STATUS_DONE;
 +                              estate = OPERATOR_FINISHED;
 +                      }
 +              }
 +              else if (event->val == KM_PRESS) {
 +                      bool in_bounds = false;
 +
 +                      /* Check if we're outside the bounds of the active region
 +                       * NOTE: An exception here is that if launched from the toolbar,
 +                       *       whatever region we're now in should become the new region
 +                       */
 +                      if ((p->ar) && (p->ar->regiontype == RGN_TYPE_TOOLS)) {
 +                              /* Change to whatever region is now under the mouse */
 +                              ARegion *current_region = BKE_area_find_region_xy(p->sa, RGN_TYPE_ANY, event->x, event->y);
 +
 +                              if (G.debug & G_DEBUG) {
 +                                      printf("found alternative region %p (old was %p) - at %d %d (sa: %d %d -> %d %d)\n",
 +                                              current_region, p->ar, event->x, event->y,
 +                                              p->sa->totrct.xmin, p->sa->totrct.ymin, p->sa->totrct.xmax, p->sa->totrct.ymax);
 +                              }
 +
 +                              if (current_region) {
 +                                      /* Assume that since we found the cursor in here, it is in bounds
 +                                       * and that this should be the region that we begin drawing in
 +                                       */
 +                                      p->ar = current_region;
 +                                      in_bounds = true;
 +                              }
 +                              else {
 +                                      /* Out of bounds, or invalid in some other way */
 +                                      p->status = GP_STATUS_ERROR;
 +                                      estate = OPERATOR_CANCELLED;
 +
 +                                      if (G.debug & G_DEBUG)
 +                                              printf("%s: Region under cursor is out of bounds, so cannot be drawn on\n", __func__);
 +                              }
 +                      }
 +                      else if (p->ar) {
 +                              rcti region_rect;
 +
 +                              /* Perform bounds check using  */
 +                              ED_region_visible_rect(p->ar, &region_rect);
 +                              in_bounds = BLI_rcti_isect_pt_v(&region_rect, event->mval);
 +                      }
 +                      else {
 +                              /* No region */
 +                              p->status = GP_STATUS_ERROR;
 +                              estate = OPERATOR_CANCELLED;
 +
 +                              if (G.debug & G_DEBUG)
 +                                      printf("%s: No active region found in GP Paint session data\n", __func__);
 +                      }
 +
 +                      if (in_bounds) {
 +                              /* Switch paintmode (temporarily if need be) based on which button was used
 +                               * NOTE: This is to make it more convenient to erase strokes when using drawing sessions
 +                               */
 +                              if ((event->type == RIGHTMOUSE) || gpencil_is_tablet_eraser_active(event)) {
 +                                      /* turn on eraser */
 +                                      p->paintmode = GP_PAINTMODE_ERASER;
 +                              }
 +                              else if (event->type == LEFTMOUSE) {
 +                                      /* restore drawmode to default */
 +                                      p->paintmode = RNA_enum_get(op->ptr, "mode");
 +                              }
 +
 +                              gpencil_draw_toggle_eraser_cursor(C, p, p->paintmode == GP_PAINTMODE_ERASER);
 +
 +                              /* not painting, so start stroke (this should be mouse-button down) */
 +                              p = gpencil_stroke_begin(C, op);
 +
 +                              if (p->status == GP_STATUS_ERROR) {
 +                                      estate = OPERATOR_CANCELLED;
 +                              }
 +                      }
 +                      else if (p->status != GP_STATUS_ERROR) {
 +                              /* User clicked outside bounds of window while idling, so exit paintmode
 +                               * NOTE: Don't enter this case if an error occurred while finding the
 +                               *       region (as above)
 +                               */
 +                              p->status = GP_STATUS_DONE;
 +                              estate = OPERATOR_FINISHED;
 +                      }
 +              }
 +              else if (event->val == KM_RELEASE) {
 +                      p->status = GP_STATUS_IDLING;
 +                      op->flag |= OP_IS_MODAL_CURSOR_REGION;
 +              }
 +      }
 +
 +      /* handle mode-specific events */
 +      if (p->status == GP_STATUS_PAINTING) {
 +              /* handle painting mouse-movements? */
 +              if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) {
 +                      /* handle drawing event */
 +                      /* printf("\t\tGP - add point\n"); */
 +                      gpencil_draw_apply_event(op, event, CTX_data_depsgraph(C));
 +
 +                      /* finish painting operation if anything went wrong just now */
 +                      if (p->status == GP_STATUS_ERROR) {
 +                              printf("\t\t\t\tGP - add error done!\n");
 +                              estate = OPERATOR_CANCELLED;
 +                      }
 +                      else {
 +                              /* event handled, so just tag as running modal */
 +                              /* printf("\t\t\t\tGP - add point handled!\n"); */
 +                              estate = OPERATOR_RUNNING_MODAL;
 +                      }
 +              }
 +              /* eraser size */
 +              else if ((p->paintmode == GP_PAINTMODE_ERASER) &&
 +                       ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE, PADPLUSKEY, PADMINUS))
 +              {
 +                      /* just resize the brush (local version)
 +                       * TODO: fix the hardcoded size jumps (set to make a visible difference) and hardcoded keys
 +                       */
 +                      /* printf("\t\tGP - resize eraser\n"); */
 +                      switch (event->type) {
 +                              case WHEELDOWNMOUSE: /* larger */
 +                              case PADPLUSKEY:
 +                                      p->radius += 5;
 +                                      break;
 +
 +                              case WHEELUPMOUSE: /* smaller */
 +                              case PADMINUS:
 +                                      p->radius -= 5;
 +
 +                                      if (p->radius <= 0)
 +                                              p->radius = 1;
 +                                      break;
 +                      }
 +
 +                      /* force refresh */
 +                      ED_region_tag_redraw(p->ar); /* just active area for now, since doing whole screen is too slow */
 +
 +                      /* event handled, so just tag as running modal */
 +                      estate = OPERATOR_RUNNING_MODAL;
 +              }
 +              /* there shouldn't be any other events, but just in case there are, let's swallow them
 +               * (i.e. to prevent problems with undo)
 +               */
 +              else {
 +                      /* swallow event to save ourselves trouble */
 +                      estate = OPERATOR_RUNNING_MODAL;
 +              }
 +      }
 +
 +      /* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */
 +      if (0 == gpencil_area_exists(C, p->sa))
 +              estate = OPERATOR_CANCELLED;
 +      else {
 +              /* update status indicators - cursor, header, etc. */
 +              gpencil_draw_status_indicators(C, p);
 +              gpencil_draw_cursor_set(p); /* cursor may have changed outside our control - T44084 */
 +      }
 +
 +      /* process last operations before exiting */
 +      switch (estate) {
 +              case OPERATOR_FINISHED:
 +                      /* one last flush before we're done */
 +                      gpencil_draw_exit(C, op);
 +                      WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
 +                      break;
 +
 +              case OPERATOR_CANCELLED:
 +                      gpencil_draw_exit(C, op);
 +                      break;
 +
 +              case OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH:
 +                      /* event doesn't need to be handled */
 +#if 0
 +                      printf("unhandled event -> %d (mmb? = %d | mmv? = %d)\n",
 +                             event->type, event->type == MIDDLEMOUSE, event->type==MOUSEMOVE);
 +#endif
 +                      break;
 +      }
 +
 +      /* return status code */
 +      return estate;
 +}
 +
 +/* ------------------------------- */
 +
 +static const EnumPropertyItem prop_gpencil_drawmodes[] = {
 +      {GP_PAINTMODE_DRAW, "DRAW", 0, "Draw Freehand", "Draw freehand stroke(s)"},
 +      {GP_PAINTMODE_DRAW_STRAIGHT, "DRAW_STRAIGHT", 0, "Draw Straight Lines", "Draw straight line segment(s)"},
 +      {GP_PAINTMODE_DRAW_POLY, "DRAW_POLY", 0, "Draw Poly Line", "Click to place endpoints of straight line segments (connected)"},
 +      {GP_PAINTMODE_ERASER, "ERASER", 0, "Eraser", "Erase Annotation strokes"},
 +      {0, NULL, 0, NULL, NULL}
 +};
 +
 +void GPENCIL_OT_annotate(wmOperatorType *ot)
 +{
 +      PropertyRNA *prop;
 +
 +      /* identifiers */
 +      ot->name = "Annotation Draw";
 +      ot->idname = "GPENCIL_OT_annotate";
 +      ot->description = "Make annotations on the active data";
 +
 +      /* api callbacks */
 +      ot->exec = gpencil_draw_exec;
 +      ot->invoke = gpencil_draw_invoke;
 +      ot->modal = gpencil_draw_modal;
 +      ot->cancel = gpencil_draw_cancel;
 +      ot->poll = gpencil_draw_poll;
 +
 +      /* flags */
 +      ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
 +
 +      /* settings for drawing */
 +      ot->prop = RNA_def_enum(ot->srna, "mode", prop_gpencil_drawmodes, 0, "Mode", "Way to interpret mouse movements");
 +
 +      prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
 +      RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
 +
 +      /* NOTE: wait for input is enabled by default, so that all UI code can work properly without needing users to know about this */
 +      prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", "Wait for first click instead of painting immediately");
 +      RNA_def_property_flag(prop, PROP_SKIP_SAVE);
 +}
@@@ -960,12 -807,15 +960,12 @@@ static void gp_draw_stroke_2d
                                t1[0] = s1[0] + mt[0];
                                t1[1] = s1[1] + mt[1];
  
 -                              /* draw this line twice (once for end of stroke, and once for endcap)*/
 -                              glVertex2fv(t1);
 -                              glVertex2fv(t0);
 -                              glVertex2fv(t0);
 -                              glVertex2fv(t1);
 -
 +                              /* Last two points of last segment (and first two points of end cap). */
 +                              immVertex2fv(attr_id.pos, t0);
 +                              immVertex2fv(attr_id.pos, t1);
  
                                /* draw end cap as last step
-                                *      - make points slightly closer to center (about halfway across)
+                                * - make points slightly closer to center (about halfway across)
                                 */
                                mt[0] = m2[0] * pthick * 0.5f;
                                mt[1] = m2[1] * pthick * 0.5f;
@@@ -1536,32 -1390,48 +1536,32 @@@ static void gp_draw_data_layers(RegionV
                 * NOTE: If the setting doesn't apply, it *must* be cleared,
                 *       as dflag's carry over from the previous layer
                 */
 -#define GP_DRAWFLAG_APPLY(condition, draw_flag_value)     { \
 -                      if (condition) dflag |= (draw_flag_value);      \
 -                      else           dflag &= ~(draw_flag_value);     \
 -              } (void)0
  
                /* xray... */
 -              GP_DRAWFLAG_APPLY((gpl->flag & GP_LAYER_NO_XRAY), GP_DRAWDATA_NO_XRAY);
 +              SET_FLAG_FROM_TEST(dflag, gpl->flag & GP_LAYER_NO_XRAY, GP_DRAWDATA_NO_XRAY);
  
                /* volumetric strokes... */
 -              GP_DRAWFLAG_APPLY((gpl->flag & GP_LAYER_VOLUMETRIC), GP_DRAWDATA_VOLUMETRIC);
 -
 -              /* HQ fills... */
 -              GP_DRAWFLAG_APPLY((gpl->flag & GP_LAYER_HQ_FILL), GP_DRAWDATA_HQ_FILL);
 -
 -#undef GP_DRAWFLAG_APPLY
 -
 -              /* Draw 'onionskins' (frame left + right)
 -               * - It is only possible to show these if the option is enabled
 -               * - The "no onions" flag prevents ghosts from appearing during animation playback/scrubbing
 -               *   and in renders
 -               * - The per-layer "always show" flag however overrides the playback/render restriction,
 -               *   allowing artists to selectively turn onionskins on/off during playback
 -               */
 -              if ((gpl->flag & GP_LAYER_ONIONSKIN) &&
 -                  ((dflag & GP_DRAWDATA_NO_ONIONS) == 0 || (gpl->flag & GP_LAYER_GHOST_ALWAYS)))
 -              {
 -                      /* Drawing method - only immediately surrounding (gstep = 0),
 -                       * or within a frame range on either side (gstep > 0)
 -                       */
 -                      gp_draw_onionskins(gpd, gpl, gpf, offsx, offsy, winx, winy, cfra, dflag, debug, diff_mat);
 -              }
 +              SET_FLAG_FROM_TEST(dflag, gpl->flag & GP_LAYER_VOLUMETRIC, GP_DRAWDATA_VOLUMETRIC);
 +
 +              tgpw.gpl = gpl;
 +              tgpw.gpf = gpf;
 +              tgpw.t_gpf = gpf; // XXX?
 +              tgpw.lthick = gpl->line_change;
 +              tgpw.opacity = gpl->opacity;
 +              copy_v4_v4(tgpw.tintcolor, gpl->tintcolor);
 +              tgpw.onion = false;
 +              tgpw.custonion = false;
 +              copy_m4_m4(tgpw.diff_mat, diff_mat);
  
                /* draw the strokes already in active frame */
 -              gp_draw_strokes(gpd, gpf, offsx, offsy, winx, winy, dflag, debug, gpl->thickness,
 -                              gpl->opacity, gpl->tintcolor, false, false, diff_mat);
 +              gp_draw_strokes(&tgpw);
  
                /* Draw verts of selected strokes
-                *  - when doing OpenGL renders, we don't want to be showing these, as that ends up flickering
-                *      - locked layers can't be edited, so there's no point showing these verts
-                *    as they will have no bearings on what gets edited
-                *  - only show when in editmode, since operators shouldn't work otherwise
-                *    (NOTE: doing it this way means that the toggling editmode shows visible change immediately)
+                * - when doing OpenGL renders, we don't want to be showing these, as that ends up flickering
+                *   - locked layers can't be edited, so there's no point showing these verts
+                *     as they will have no bearings on what gets edited
+                * - only show when in editmode, since operators shouldn't work otherwise
+                *   (NOTE: doing it this way means that the toggling editmode shows visible change immediately)
                 */
                /* XXX: perhaps we don't want to show these when users are drawing... */
                if ((G.f & G_RENDER_OGL) == 0 &&