Merge branch 'master' into blender2.8
authorCampbell Barton <ideasman42@gmail.com>
Wed, 19 Sep 2018 02:14:36 +0000 (12:14 +1000)
committerCampbell Barton <ideasman42@gmail.com>
Wed, 19 Sep 2018 02:14:36 +0000 (12:14 +1000)
64 files changed:
1  2 
source/blender/blenkernel/intern/CCGSubSurf_opensubdiv.c
source/blender/blenkernel/intern/CCGSubSurf_opensubdiv_converter.c
source/blender/blenkernel/intern/armature.c
source/blender/blenkernel/intern/cdderivedmesh.c
source/blender/blenkernel/intern/editderivedmesh.c
source/blender/blenkernel/intern/font.c
source/blender/blenkernel/intern/icons.c
source/blender/blenkernel/intern/material.c
source/blender/blenkernel/intern/mesh.c
source/blender/blenkernel/intern/mesh_convert.c
source/blender/blenkernel/intern/mesh_evaluate.c
source/blender/blenkernel/intern/mesh_merge.c
source/blender/blenkernel/intern/mesh_remap.c
source/blender/blenkernel/intern/mesh_validate.c
source/blender/blenkernel/intern/object.c
source/blender/blenkernel/intern/object_deform.c
source/blender/blenkernel/intern/object_dupli.c
source/blender/blenkernel/intern/particle.c
source/blender/blenkernel/intern/particle_system.c
source/blender/blenkernel/intern/pbvh.c
source/blender/blenkernel/intern/subsurf_ccg.c
source/blender/blenlib/BLI_utildefines.h
source/blender/blenlib/intern/freetypefont.c
source/blender/blenloader/intern/versioning_260.c
source/blender/bmesh/tools/bmesh_bevel.c
source/blender/bmesh/tools/bmesh_intersect.c
source/blender/compositor/operations/COM_ScreenLensDistortionOperation.cpp
source/blender/draw/engines/workbench/workbench_deferred.c
source/blender/draw/engines/workbench/workbench_forward.c
source/blender/draw/intern/draw_cache_impl_mesh.c
source/blender/editors/animation/anim_channels_defines.c
source/blender/editors/armature/pose_edit.c
source/blender/editors/armature/pose_select.c
source/blender/editors/gpencil/gpencil_brush.c
source/blender/editors/gpencil/gpencil_data.c
source/blender/editors/gpencil/gpencil_paint.c
source/blender/editors/interface/interface.c
source/blender/editors/interface/interface_layout.c
source/blender/editors/interface/interface_templates.c
source/blender/editors/mesh/editmesh_knife.c
source/blender/editors/physics/particle_edit.c
source/blender/editors/sculpt_paint/paint_image_proj.c
source/blender/editors/sculpt_paint/paint_stroke.c
source/blender/editors/space_file/filelist.c
source/blender/editors/space_node/node_templates.c
source/blender/editors/space_sequencer/sequencer_select.c
source/blender/editors/transform/transform_snap.c
source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c
source/blender/gpu/intern/gpu_codegen.c
source/blender/makesdna/intern/dna_genfile.c
source/blender/modifiers/intern/MOD_build.c
source/blender/modifiers/intern/MOD_explode.c
source/blender/modifiers/intern/MOD_mask.c
source/blender/modifiers/intern/MOD_weighted_normal.c
source/blender/python/bmesh/bmesh_py_types.c
source/blender/python/bmesh/bmesh_py_types_customdata.c
source/blender/python/bmesh/bmesh_py_types_meshdata.c
source/blender/python/intern/bpy_app.c
source/blender/python/intern/bpy_app_handlers.c
source/blender/python/mathutils/mathutils_Quaternion.c
source/blender/python/mathutils/mathutils_Vector.c
source/blender/windowmanager/intern/wm_files_link.c
source/blender/windowmanager/intern/wm_window.c
source/creator/creator_args.c

index 23cb6c716969df8bc535ae2b3507ffdb303b423a,e49f24c812022a131b681fce031450ec91289876..2a3487513658cf274b72e73d3efb58a6bc4e9a8b
@@@ -557,18 -507,7 +557,18 @@@ static Icon *icon_create(int icon_id, i
        new_icon->drawinfo = NULL;
        new_icon->drawinfo_free = NULL;
  
-       BLI_ghash_insert(gIcons, SET_INT_IN_POINTER(icon_id), new_icon);
 -      BLI_ghash_insert(gIcons, POINTER_FROM_INT(id->icon_id), new_icon);
++      BLI_ghash_insert(gIcons, POINTER_FROM_INT(icon_id), new_icon);
 +
 +      return new_icon;
 +}
 +
 +static int icon_id_ensure_create_icon(struct ID *id)
 +{
 +      BLI_assert(BLI_thread_is_main());
 +
 +      Icon *icon = icon_create(id->icon_id, ICON_DATA_ID, id);
 +      icon->id_type = GS(id->name);
 +      icon->flag = ICON_FLAG_MANAGED;
  
        return id->icon_id;
  }
@@@ -738,127 -649,21 +738,127 @@@ void BKE_icon_id_delete(struct ID *id
  /**
   * Remove icon and free data.
   */
 -void BKE_icon_delete(const int icon_id)
 +bool BKE_icon_delete(const int icon_id)
  {
 -      Icon *icon;
 +      if (icon_id == 0) {
 +              /* no icon defined for library object */
 +              return false;
 +      }
  
-       Icon *icon = BLI_ghash_popkey(gIcons, SET_INT_IN_POINTER(icon_id), NULL);
 -      if (!icon_id) return;  /* no icon defined for library object */
++      Icon *icon = BLI_ghash_popkey(gIcons, POINTER_FROM_INT(icon_id), NULL);
 +      if (icon) {
 +              icon_free_data(icon_id, icon);
 +              icon_free(icon);
 +              return true;
 +      }
 +      else {
 +              return false;
 +      }
 +}
  
 -      icon = BLI_ghash_popkey(gIcons, POINTER_FROM_INT(icon_id), NULL);
 +bool BKE_icon_delete_unmanaged(const int icon_id)
 +{
 +      if (icon_id == 0) {
 +              /* no icon defined for library object */
 +              return false;
 +      }
  
-       Icon *icon = BLI_ghash_popkey(gIcons, SET_INT_IN_POINTER(icon_id), NULL);
++      Icon *icon = BLI_ghash_popkey(gIcons, POINTER_FROM_INT(icon_id), NULL);
        if (icon) {
 -              if (icon->id_type != 0) {
 -                      ((ID *)(icon->obj))->icon_id = 0;
 +              if (UNLIKELY(icon->flag & ICON_FLAG_MANAGED)) {
-                       BLI_ghash_insert(gIcons, SET_INT_IN_POINTER(icon_id), icon);
++                      BLI_ghash_insert(gIcons, POINTER_FROM_INT(icon_id), icon);
 +                      return false;
                }
                else {
 -                      ((PreviewImage *)(icon->obj))->icon_id = 0;
 +                      icon_free_data(icon_id, icon);
 +                      icon_free(icon);
 +                      return true;
                }
 -              icon_free(icon);
        }
 +      else {
 +              return false;
 +      }
 +}
 +
 +/* -------------------------------------------------------------------- */
 +/** \name Geometry Icon
 + * \{ */
 +
 +int BKE_icon_geom_ensure(struct Icon_Geom *geom)
 +{
 +      BLI_assert(BLI_thread_is_main());
 +
 +      if (geom->icon_id) {
 +              return geom->icon_id;
 +      }
 +
 +      geom->icon_id = get_next_free_id();
 +
 +      icon_create(geom->icon_id, ICON_DATA_GEOM, geom);
 +      /* Not managed for now, we may want this to be configurable per icon). */
 +
 +      return geom->icon_id;
 +}
 +
 +struct Icon_Geom *BKE_icon_geom_from_memory(const uchar *data, size_t data_len)
 +{
 +      BLI_assert(BLI_thread_is_main());
 +      if (data_len <= 8) {
 +              goto fail;
 +      }
 +      /* Skip the header. */
 +      data_len -= 8;
 +      const int div = 3 * 2 * 3;
 +      const int coords_len = data_len / div;
 +      if (coords_len * div != data_len) {
 +              goto fail;
 +      }
 +
 +      const uchar header[4] = {'V', 'C', 'O', 0};
 +      const uchar *p = data;
 +      if (memcmp(p, header, ARRAY_SIZE(header)) != 0) {
 +              goto fail;
 +      }
 +      p += 4;
 +
 +      struct Icon_Geom *geom = MEM_mallocN(sizeof(*geom), __func__);
 +      geom->coords_range[0] = (int)*p++;
 +      geom->coords_range[1] = (int)*p++;
 +      /* x, y ignored for now */
 +      p += 2;
 +
 +      geom->coords_len = coords_len;
 +      geom->coords = (const void *)p;
 +      geom->colors = (const void *)(p + (data_len / 3));
 +      geom->icon_id = 0;
 +      geom->mem = data;
 +      return geom;
 +
 +fail:
 +      MEM_freeN((void *)data);
 +      return NULL;
 +}
 +
 +struct Icon_Geom *BKE_icon_geom_from_file(const char *filename)
 +{
 +      BLI_assert(BLI_thread_is_main());
 +      size_t data_len;
 +      uchar *data = BLI_file_read_binary_as_mem(filename, 0, &data_len);
 +      if (data == NULL) {
 +              return NULL;
 +      }
 +      return BKE_icon_geom_from_memory(data, data_len);
 +}
 +
 +/** \} */
 +
 +/** \name Studio Light Icon
 + * \{ */
 +int BKE_icon_ensure_studio_light(struct StudioLight *sl, int id_type)
 +{
 +      int icon_id = get_next_free_id();
 +      Icon *icon = icon_create(icon_id, ICON_DATA_STUDIOLIGHT, sl);
 +      icon->id_type = id_type;
 +      return icon_id;
  }
 +/** \} */
index d49ca507e024efd4acc2f3cec1ea7d0e958a7309,0000000000000000000000000000000000000000..5324c397ebfade37b04672a20689db89ee003bfa
mode 100644,000000..100644
--- /dev/null
@@@ -1,685 -1,0 +1,685 @@@
-                               newe[i] = GET_INT_FROM_POINTER(*val_p);
 +/*
 + * ***** 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) 2001-2002 by NaN Holding BV.
 + * All rights reserved.
 + *
 + * Contributor(s): Blender Foundation
 + *
 + * ***** END GPL LICENSE BLOCK *****
 + */
 +
 +/** \file blender/blenkernel/intern/mesh_merge.c
 + *  \ingroup bke
 + */
 +#include <string.h> // for memcpy
 +
 +#include "MEM_guardedalloc.h"
 +
 +#include "DNA_mesh_types.h"
 +#include "DNA_meshdata_types.h"
 +
 +#include "BLI_utildefines.h"
 +#include "BLI_utildefines_stack.h"
 +#include "BLI_edgehash.h"
 +#include "BLI_ghash.h"
 +
 +#include "BKE_customdata.h"
 +#include "BKE_library.h"
 +#include "BKE_mesh.h"
 +#include "BKE_mesh_mapping.h"
 +
 +
 +/**
 + * Poly compare with vtargetmap
 + * Function used by #BKE_mesh_merge_verts.
 + * The function compares poly_source after applying vtargetmap, with poly_target.
 + * The two polys are identical if they share the same vertices in the same order, or in reverse order,
 + * but starting position loopstart may be different.
 + * The function is called with direct_reverse=1 for same order (i.e. same normal),
 + * and may be called again with direct_reverse=-1 for reverse order.
 + * \return 1 if polys are identical,  0 if polys are different.
 + */
 +static int cddm_poly_compare(
 +        MLoop *mloop_array,
 +        MPoly *mpoly_source, MPoly *mpoly_target,
 +        const int *vtargetmap, const int direct_reverse)
 +{
 +      int vert_source, first_vert_source, vert_target;
 +      int i_loop_source;
 +      int i_loop_target, i_loop_target_start, i_loop_target_offset, i_loop_target_adjusted;
 +      bool compare_completed = false;
 +      bool same_loops = false;
 +
 +      MLoop *mloop_source, *mloop_target;
 +
 +      BLI_assert(direct_reverse == 1 || direct_reverse == -1);
 +
 +      i_loop_source = 0;
 +      mloop_source = mloop_array + mpoly_source->loopstart;
 +      vert_source = mloop_source->v;
 +
 +      if (vtargetmap[vert_source] != -1) {
 +              vert_source = vtargetmap[vert_source];
 +      }
 +      else {
 +              /* All source loop vertices should be mapped */
 +              BLI_assert(false);
 +      }
 +
 +      /* Find same vertex within mpoly_target's loops */
 +      mloop_target = mloop_array + mpoly_target->loopstart;
 +      for (i_loop_target = 0; i_loop_target < mpoly_target->totloop; i_loop_target++, mloop_target++) {
 +              if (mloop_target->v == vert_source) {
 +                      break;
 +              }
 +      }
 +
 +      /* If same vertex not found, then polys cannot be equal */
 +      if (i_loop_target >= mpoly_target->totloop) {
 +              return false;
 +      }
 +
 +      /* Now mloop_source and m_loop_target have one identical vertex */
 +      /* mloop_source is at position 0, while m_loop_target has advanced to find identical vertex */
 +      /* Go around the loop and check that all vertices match in same order */
 +      /* Skipping source loops when consecutive source vertices are mapped to same target vertex */
 +
 +      i_loop_target_start = i_loop_target;
 +      i_loop_target_offset = 0;
 +      first_vert_source = vert_source;
 +
 +      compare_completed = false;
 +      same_loops = false;
 +
 +      while (!compare_completed) {
 +
 +              vert_target = mloop_target->v;
 +
 +              /* First advance i_loop_source, until it points to different vertex, after mapping applied */
 +              do {
 +                      i_loop_source++;
 +
 +                      if (i_loop_source == mpoly_source->totloop) {
 +                              /* End of loops for source, must match end of loop for target.  */
 +                              if (i_loop_target_offset == mpoly_target->totloop - 1) {
 +                                      compare_completed = true;
 +                                      same_loops = true;
 +                                      break;  /* Polys are identical */
 +                              }
 +                              else {
 +                                      compare_completed = true;
 +                                      same_loops = false;
 +                                      break;  /* Polys are different */
 +                              }
 +                      }
 +
 +                      mloop_source++;
 +                      vert_source = mloop_source->v;
 +
 +                      if (vtargetmap[vert_source] != -1) {
 +                              vert_source = vtargetmap[vert_source];
 +                      }
 +                      else {
 +                              /* All source loop vertices should be mapped */
 +                              BLI_assert(false);
 +                      }
 +
 +              } while (vert_source == vert_target);
 +
 +              if (compare_completed) {
 +                      break;
 +              }
 +
 +              /* Now advance i_loop_target as well */
 +              i_loop_target_offset++;
 +
 +              if (i_loop_target_offset == mpoly_target->totloop) {
 +                      /* End of loops for target only, that means no match */
 +                      /* except if all remaining source vertices are mapped to first target */
 +                      for (; i_loop_source < mpoly_source->totloop; i_loop_source++, mloop_source++) {
 +                              vert_source = vtargetmap[mloop_source->v];
 +                              if (vert_source != first_vert_source) {
 +                                      compare_completed = true;
 +                                      same_loops = false;
 +                                      break;
 +                              }
 +                      }
 +                      if (!compare_completed) {
 +                              same_loops = true;
 +                      }
 +                      break;
 +              }
 +
 +              /* Adjust i_loop_target for cycling around and for direct/reverse order defined by delta = +1 or -1 */
 +              i_loop_target_adjusted = (i_loop_target_start + direct_reverse * i_loop_target_offset) % mpoly_target->totloop;
 +              if (i_loop_target_adjusted < 0) {
 +                      i_loop_target_adjusted += mpoly_target->totloop;
 +              }
 +              mloop_target = mloop_array + mpoly_target->loopstart + i_loop_target_adjusted;
 +              vert_target = mloop_target->v;
 +
 +              if (vert_target != vert_source) {
 +                      same_loops = false;  /* Polys are different */
 +                      break;
 +              }
 +      }
 +      return same_loops;
 +}
 +
 +
 +/* Utility stuff for using GHash with polys, used by vertex merging. */
 +
 +typedef struct PolyKey {
 +      int poly_index;   /* index of the MPoly within the derived mesh */
 +      int totloops;     /* number of loops in the poly */
 +      unsigned int hash_sum;  /* Sum of all vertices indices */
 +      unsigned int hash_xor;  /* Xor of all vertices indices */
 +} PolyKey;
 +
 +
 +static unsigned int poly_gset_hash_fn(const void *key)
 +{
 +      const PolyKey *pk = key;
 +      return pk->hash_sum;
 +}
 +
 +static bool poly_gset_compare_fn(const void *k1, const void *k2)
 +{
 +      const PolyKey *pk1 = k1;
 +      const PolyKey *pk2 = k2;
 +      if ((pk1->hash_sum == pk2->hash_sum) &&
 +          (pk1->hash_xor == pk2->hash_xor) &&
 +          (pk1->totloops == pk2->totloops))
 +      {
 +              /* Equality - note that this does not mean equality of polys */
 +              return false;
 +      }
 +      else {
 +              return true;
 +      }
 +}
 +
 +/**
 + * Merge Verts
 + *
 + * This frees the given mesh and returns a new mesh.
 + *
 + * \param vtargetmap  The table that maps vertices to target vertices.  a value of -1
 + * indicates a vertex is a target, and is to be kept.
 + * This array is aligned with 'mesh->totvert'
 + * \warning \a vtargetmap must **not** contain any chained mapping (v1 -> v2 -> v3 etc.), this is not supported
 + * and will likely generate corrupted geometry.
 + *
 + * \param tot_vtargetmap  The number of non '-1' values in vtargetmap. (not the size)
 + *
 + * \param merge_mode enum with two modes.
 + * - #MESH_MERGE_VERTS_DUMP_IF_MAPPED
 + * When called by the Mirror Modifier,
 + * In this mode it skips any faces that have all vertices merged (to avoid creating pairs
 + * of faces sharing the same set of vertices)
 + * - #MESH_MERGE_VERTS_DUMP_IF_EQUAL
 + * When called by the Array Modifier,
 + * In this mode, faces where all vertices are merged are double-checked,
 + * to see whether all target vertices actually make up a poly already.
 + * Indeed it could be that all of a poly's vertices are merged,
 + * but merged to vertices that do not make up a single poly,
 + * in which case the original poly should not be dumped.
 + * Actually this later behavior could apply to the Mirror Modifier as well, but the additional checks are
 + * costly and not necessary in the case of mirror, because each vertex is only merged to its own mirror.
 + *
 + * \note #BKE_mesh_recalc_tessellation has to run on the returned DM if you want to access tessfaces.
 + */
 +Mesh *BKE_mesh_merge_verts(Mesh *mesh, const int *vtargetmap, const int tot_vtargetmap, const int merge_mode)
 +{
 +      /* This was commented out back in 2013, see commit f45d8827bafe6b9eaf9de42f4054e9d84a21955d. */
 +// #define USE_LOOPS
 +
 +      Mesh *result = NULL;
 +
 +      const int totvert = mesh->totvert;
 +      const int totedge = mesh->totedge;
 +      const int totloop = mesh->totloop;
 +      const int totpoly = mesh->totpoly;
 +
 +      const int totvert_final = totvert - tot_vtargetmap;
 +
 +      MVert *mv, *mvert = MEM_malloc_arrayN(totvert_final, sizeof(*mvert), __func__);
 +      int *oldv         = MEM_malloc_arrayN(totvert_final, sizeof(*oldv), __func__);
 +      int *newv         = MEM_malloc_arrayN(totvert, sizeof(*newv), __func__);
 +      STACK_DECLARE(mvert);
 +      STACK_DECLARE(oldv);
 +
 +      /* Note: create (totedge + totloop) elements because partially invalid polys due to merge may require
 +       * generating new edges, and while in 99% cases we'll still end with less final edges than totedge,
 +       * cases can be forged that would end requiring more... */
 +      MEdge *med, *medge = MEM_malloc_arrayN((totedge + totloop), sizeof(*medge), __func__);
 +      int *olde          = MEM_malloc_arrayN((totedge + totloop), sizeof(*olde), __func__);
 +      int *newe          = MEM_malloc_arrayN((totedge + totloop), sizeof(*newe), __func__);
 +      STACK_DECLARE(medge);
 +      STACK_DECLARE(olde);
 +
 +      MLoop *ml, *mloop = MEM_malloc_arrayN(totloop, sizeof(*mloop), __func__);
 +      int *oldl         = MEM_malloc_arrayN(totloop, sizeof(*oldl), __func__);
 +#ifdef USE_LOOPS
 +      int *newl          = MEM_malloc_arrayN(totloop, sizeof(*newl), __func__);
 +#endif
 +      STACK_DECLARE(mloop);
 +      STACK_DECLARE(oldl);
 +
 +      MPoly *mp, *mpoly = MEM_malloc_arrayN(totpoly, sizeof(*medge), __func__);
 +      int *oldp         = MEM_malloc_arrayN(totpoly, sizeof(*oldp), __func__);
 +      STACK_DECLARE(mpoly);
 +      STACK_DECLARE(oldp);
 +
 +      EdgeHash *ehash = BLI_edgehash_new_ex(__func__, totedge);
 +
 +      int i, j, c;
 +
 +      PolyKey *poly_keys;
 +      GSet *poly_gset = NULL;
 +      MeshElemMap *poly_map = NULL;
 +      int *poly_map_mem = NULL;
 +
 +      STACK_INIT(oldv, totvert_final);
 +      STACK_INIT(olde, totedge);
 +      STACK_INIT(oldl, totloop);
 +      STACK_INIT(oldp, totpoly);
 +
 +      STACK_INIT(mvert, totvert_final);
 +      STACK_INIT(medge, totedge);
 +      STACK_INIT(mloop, totloop);
 +      STACK_INIT(mpoly, totpoly);
 +
 +      /* fill newv with destination vertex indices */
 +      mv = mesh->mvert;
 +      c = 0;
 +      for (i = 0; i < totvert; i++, mv++) {
 +              if (vtargetmap[i] == -1) {
 +                      STACK_PUSH(oldv, i);
 +                      STACK_PUSH(mvert, *mv);
 +                      newv[i] = c++;
 +              }
 +              else {
 +                      /* dummy value */
 +                      newv[i] = 0;
 +              }
 +      }
 +
 +      /* now link target vertices to destination indices */
 +      for (i = 0; i < totvert; i++) {
 +              if (vtargetmap[i] != -1) {
 +                      newv[i] = newv[vtargetmap[i]];
 +              }
 +      }
 +
 +      /* Don't remap vertices in cddm->mloop, because we need to know the original
 +       * indices in order to skip faces with all vertices merged.
 +       * The "update loop indices..." section further down remaps vertices in mloop.
 +       */
 +
 +      /* now go through and fix edges and faces */
 +      med = mesh->medge;
 +      c = 0;
 +      for (i = 0; i < totedge; i++, med++) {
 +              const unsigned int v1 = (vtargetmap[med->v1] != -1) ? vtargetmap[med->v1] : med->v1;
 +              const unsigned int v2 = (vtargetmap[med->v2] != -1) ? vtargetmap[med->v2] : med->v2;
 +              if (LIKELY(v1 != v2)) {
 +                      void **val_p;
 +
 +                      if (BLI_edgehash_ensure_p(ehash, v1, v2, &val_p)) {
-                               *val_p = SET_INT_IN_POINTER(c);
++                              newe[i] = POINTER_AS_INT(*val_p);
 +                      }
 +                      else {
 +                              STACK_PUSH(olde, i);
 +                              STACK_PUSH(medge, *med);
 +                              newe[i] = c;
-                                               last_valid_ml->e = GET_INT_FROM_POINTER(*val_p);
++                              *val_p = POINTER_FROM_INT(c);
 +                              c++;
 +                      }
 +              }
 +              else {
 +                      newe[i] = -1;
 +              }
 +      }
 +
 +      if (merge_mode == MESH_MERGE_VERTS_DUMP_IF_EQUAL) {
 +              /* In this mode, we need to determine,  whenever a poly' vertices are all mapped */
 +              /* if the targets already make up a poly, in which case the new poly is dropped */
 +              /* This poly equality check is rather complex.   We use a BLI_ghash to speed it up with a first level check */
 +              PolyKey *mpgh;
 +              poly_keys = MEM_malloc_arrayN(totpoly, sizeof(PolyKey), __func__);
 +              poly_gset = BLI_gset_new_ex(poly_gset_hash_fn, poly_gset_compare_fn, __func__, totpoly);
 +              /* Duplicates allowed because our compare function is not pure equality */
 +              BLI_gset_flag_set(poly_gset, GHASH_FLAG_ALLOW_DUPES);
 +
 +              mp = mesh->mpoly;
 +              mpgh = poly_keys;
 +              for (i = 0; i < totpoly; i++, mp++, mpgh++) {
 +                      mpgh->poly_index = i;
 +                      mpgh->totloops = mp->totloop;
 +                      ml = mesh->mloop + mp->loopstart;
 +                      mpgh->hash_sum = mpgh->hash_xor = 0;
 +                      for (j = 0; j < mp->totloop; j++, ml++) {
 +                              mpgh->hash_sum += ml->v;
 +                              mpgh->hash_xor ^= ml->v;
 +                      }
 +                      BLI_gset_insert(poly_gset, mpgh);
 +              }
 +
 +              /* Can we optimise by reusing an old pmap ?  How do we know an old pmap is stale ?  */
 +              /* When called by MOD_array.c, the cddm has just been created, so it has no valid pmap.   */
 +              BKE_mesh_vert_poly_map_create(
 +                      &poly_map, &poly_map_mem,
 +                      mesh->mpoly, mesh->mloop,
 +                      totvert, totpoly, totloop);
 +      }  /* done preparing for fast poly compare */
 +
 +
 +      mp = mesh->mpoly;
 +      mv = mesh->mvert;
 +      for (i = 0; i < totpoly; i++, mp++) {
 +              MPoly *mp_new;
 +
 +              ml = mesh->mloop + mp->loopstart;
 +
 +              /* check faces with all vertices merged */
 +              bool all_vertices_merged = true;
 +
 +              for (j = 0; j < mp->totloop; j++, ml++) {
 +                      if (vtargetmap[ml->v] == -1) {
 +                              all_vertices_merged = false;
 +                              /* This will be used to check for poly using several time the same vert. */
 +                              mv[ml->v].flag &= ~ME_VERT_TMP_TAG;
 +                      }
 +                      else {
 +                              /* This will be used to check for poly using several time the same vert. */
 +                              mv[vtargetmap[ml->v]].flag &= ~ME_VERT_TMP_TAG;
 +                      }
 +              }
 +
 +              if (UNLIKELY(all_vertices_merged)) {
 +                      if (merge_mode == MESH_MERGE_VERTS_DUMP_IF_MAPPED) {
 +                              /* In this mode, all vertices merged is enough to dump face */
 +                              continue;
 +                      }
 +                      else if (merge_mode == MESH_MERGE_VERTS_DUMP_IF_EQUAL) {
 +                              /* Additional condition for face dump:  target vertices must make up an identical face */
 +                              /* The test has 2 steps:  (1) first step is fast ghash lookup, but not failproof       */
 +                              /*                        (2) second step is thorough but more costly poly compare     */
 +                              int i_poly, v_target;
 +                              bool found = false;
 +                              PolyKey pkey;
 +
 +                              /* Use poly_gset for fast (although not 100% certain) identification of same poly */
 +                              /* First, make up a poly_summary structure */
 +                              ml = mesh->mloop + mp->loopstart;
 +                              pkey.hash_sum = pkey.hash_xor = 0;
 +                              pkey.totloops = 0;
 +                              for (j = 0; j < mp->totloop; j++, ml++) {
 +                                      v_target = vtargetmap[ml->v];   /* Cannot be -1, they are all mapped */
 +                                      pkey.hash_sum += v_target;
 +                                      pkey.hash_xor ^= v_target;
 +                                      pkey.totloops++;
 +                              }
 +                              if (BLI_gset_haskey(poly_gset, &pkey)) {
 +
 +                                      /* There might be a poly that matches this one.
 +                                       * We could just leave it there and say there is, and do a "continue".
 +                                       * ... but we are checking whether there is an exact poly match.
 +                                       * It's not so costly in terms of CPU since it's very rare, just a lot of complex code.
 +                                       */
 +
 +                                      /* Consider current loop again */
 +                                      ml = mesh->mloop + mp->loopstart;
 +                                      /* Consider the target of the loop's first vert */
 +                                      v_target = vtargetmap[ml->v];
 +                                      /* Now see if v_target belongs to a poly that shares all vertices with source poly,
 +                                       * in same order, or reverse order */
 +
 +                                      for (i_poly = 0; i_poly < poly_map[v_target].count; i_poly++) {
 +                                              MPoly *target_poly = mesh->mpoly + *(poly_map[v_target].indices + i_poly);
 +
 +                                              if (cddm_poly_compare(mesh->mloop, mp, target_poly, vtargetmap, +1) ||
 +                                                  cddm_poly_compare(mesh->mloop, mp, target_poly, vtargetmap, -1))
 +                                              {
 +                                                      found = true;
 +                                                      break;
 +                                              }
 +                                      }
 +                                      if (found) {
 +                                              /* Current poly's vertices are mapped to a poly that is strictly identical */
 +                                              /* Current poly is dumped */
 +                                              continue;
 +                                      }
 +                              }
 +                      }
 +              }
 +
 +
 +              /* Here either the poly's vertices were not all merged
 +               * or they were all merged, but targets do not make up an identical poly,
 +               * the poly is retained.
 +               */
 +              ml = mesh->mloop + mp->loopstart;
 +
 +              c = 0;
 +              MLoop *last_valid_ml = NULL;
 +              MLoop *first_valid_ml = NULL;
 +              bool need_edge_from_last_valid_ml = false;
 +              bool need_edge_to_first_valid_ml = false;
 +              int created_edges = 0;
 +              for (j = 0; j < mp->totloop; j++, ml++) {
 +                      const uint mlv = (vtargetmap[ml->v] != -1) ? vtargetmap[ml->v] : ml->v;
 +#ifndef NDEBUG
 +                      {
 +                              MLoop *next_ml = mesh->mloop + mp->loopstart + ((j + 1) % mp->totloop);
 +                              uint next_mlv = (vtargetmap[next_ml->v] != -1) ? vtargetmap[next_ml->v] : next_ml->v;
 +                              med = mesh->medge + ml->e;
 +                              uint v1 = (vtargetmap[med->v1] != -1) ? vtargetmap[med->v1] : med->v1;
 +                              uint v2 = (vtargetmap[med->v2] != -1) ? vtargetmap[med->v2] : med->v2;
 +                              BLI_assert((mlv == v1 && next_mlv == v2) || (mlv == v2 && next_mlv == v1));
 +                      }
 +#endif
 +                      /* A loop is only valid if its matching edge is, and it's not reusing a vertex already used by this poly. */
 +                      if (LIKELY((newe[ml->e] != -1) && ((mv[mlv].flag & ME_VERT_TMP_TAG) == 0))) {
 +                              mv[mlv].flag |= ME_VERT_TMP_TAG;
 +
 +                              if (UNLIKELY(last_valid_ml != NULL && need_edge_from_last_valid_ml)) {
 +                                      /* We need to create a new edge between last valid loop and this one! */
 +                                      void **val_p;
 +
 +                                      uint v1 = (vtargetmap[last_valid_ml->v] != -1) ? vtargetmap[last_valid_ml->v] : last_valid_ml->v;
 +                                      uint v2 = mlv;
 +                                      BLI_assert(v1 != v2);
 +                                      if (BLI_edgehash_ensure_p(ehash, v1, v2, &val_p)) {
-                                               *val_p = SET_INT_IN_POINTER(new_eidx);
++                                              last_valid_ml->e = POINTER_AS_INT(*val_p);
 +                                      }
 +                                      else {
 +                                              const int new_eidx = STACK_SIZE(medge);
 +                                              STACK_PUSH(olde, olde[last_valid_ml->e]);
 +                                              STACK_PUSH(medge, mesh->medge[last_valid_ml->e]);
 +                                              medge[new_eidx].v1 = last_valid_ml->v;
 +                                              medge[new_eidx].v2 = ml->v;
 +                                              /* DO NOT change newe mapping, could break actual values due to some deleted original edges. */
-                               last_valid_ml->e = GET_INT_FROM_POINTER(*val_p);
++                                              *val_p = POINTER_FROM_INT(new_eidx);
 +                                              created_edges++;
 +
 +                                              last_valid_ml->e = new_eidx;
 +                                      }
 +                                      need_edge_from_last_valid_ml = false;
 +                              }
 +
 +#ifdef USE_LOOPS
 +                              newl[j + mp->loopstart] = STACK_SIZE(mloop);
 +#endif
 +                              STACK_PUSH(oldl, j + mp->loopstart);
 +                              last_valid_ml = STACK_PUSH_RET_PTR(mloop);
 +                              *last_valid_ml = *ml;
 +                              if (first_valid_ml == NULL) {
 +                                      first_valid_ml = last_valid_ml;
 +                              }
 +                              c++;
 +
 +                              /* We absolutely HAVE to handle edge index remapping here, otherwise potential newly created edges
 +                               * in that part of code make remapping later totally unreliable. */
 +                              BLI_assert(newe[ml->e] != -1);
 +                              last_valid_ml->e = newe[ml->e];
 +                      }
 +                      else {
 +                              if (last_valid_ml != NULL) {
 +                                      need_edge_from_last_valid_ml = true;
 +                              }
 +                              else {
 +                                      need_edge_to_first_valid_ml = true;
 +                              }
 +                      }
 +              }
 +              if (UNLIKELY(last_valid_ml != NULL && !ELEM(first_valid_ml, NULL, last_valid_ml) &&
 +                           (need_edge_to_first_valid_ml || need_edge_from_last_valid_ml)))
 +              {
 +                      /* We need to create a new edge between last valid loop and first valid one! */
 +                      void **val_p;
 +
 +                      uint v1 = (vtargetmap[last_valid_ml->v] != -1) ? vtargetmap[last_valid_ml->v] : last_valid_ml->v;
 +                      uint v2 = (vtargetmap[first_valid_ml->v] != -1) ? vtargetmap[first_valid_ml->v] : first_valid_ml->v;
 +                      BLI_assert(v1 != v2);
 +                      if (BLI_edgehash_ensure_p(ehash, v1, v2, &val_p)) {
-                               *val_p = SET_INT_IN_POINTER(new_eidx);
++                              last_valid_ml->e = POINTER_AS_INT(*val_p);
 +                      }
 +                      else {
 +                              const int new_eidx = STACK_SIZE(medge);
 +                              STACK_PUSH(olde, olde[last_valid_ml->e]);
 +                              STACK_PUSH(medge, mesh->medge[last_valid_ml->e]);
 +                              medge[new_eidx].v1 = last_valid_ml->v;
 +                              medge[new_eidx].v2 = first_valid_ml->v;
 +                              /* DO NOT change newe mapping, could break actual values due to some deleted original edges. */
++                              *val_p = POINTER_FROM_INT(new_eidx);
 +                              created_edges++;
 +
 +                              last_valid_ml->e = new_eidx;
 +                      }
 +                      need_edge_to_first_valid_ml = need_edge_from_last_valid_ml = false;
 +              }
 +
 +              if (UNLIKELY(c == 0)) {
 +                      BLI_assert(created_edges == 0);
 +                      continue;
 +              }
 +              else if (UNLIKELY(c < 3)) {
 +                      STACK_DISCARD(oldl, c);
 +                      STACK_DISCARD(mloop, c);
 +                      if (created_edges > 0) {
 +                              for (j = STACK_SIZE(medge) - created_edges; j < STACK_SIZE(medge); j++) {
 +                                      BLI_edgehash_remove(ehash, medge[j].v1, medge[j].v2, NULL);
 +                              }
 +                              STACK_DISCARD(olde, created_edges);
 +                              STACK_DISCARD(medge, created_edges);
 +                      }
 +                      continue;
 +              }
 +
 +              mp_new = STACK_PUSH_RET_PTR(mpoly);
 +              *mp_new = *mp;
 +              mp_new->totloop = c;
 +              BLI_assert(mp_new->totloop >= 3);
 +              mp_new->loopstart = STACK_SIZE(mloop) - c;
 +
 +              STACK_PUSH(oldp, i);
 +      }  /* end of the loop that tests polys   */
 +
 +
 +      if (poly_gset) {
 +              // printf("hash quality %.6f\n", BLI_gset_calc_quality(poly_gset));
 +
 +              BLI_gset_free(poly_gset, NULL);
 +              MEM_freeN(poly_keys);
 +      }
 +
 +      /*create new cddm*/
 +      result = BKE_mesh_new_nomain_from_template(
 +              mesh, STACK_SIZE(mvert), STACK_SIZE(medge), 0, STACK_SIZE(mloop), STACK_SIZE(mpoly));
 +
 +      /*update edge indices and copy customdata*/
 +      med = medge;
 +      for (i = 0; i < result->totedge; i++, med++) {
 +              BLI_assert(newv[med->v1] != -1);
 +              med->v1 = newv[med->v1];
 +              BLI_assert(newv[med->v2] != -1);
 +              med->v2 = newv[med->v2];
 +
 +              /* Can happen in case vtargetmap contains some double chains, we do not support that. */
 +              BLI_assert(med->v1 != med->v2);
 +
 +              CustomData_copy_data(&mesh->edata, &result->edata, olde[i], i, 1);
 +      }
 +
 +      /*update loop indices and copy customdata*/
 +      ml = mloop;
 +      for (i = 0; i < result->totloop; i++, ml++) {
 +              /* Edge remapping has already be done in main loop handling part above. */
 +              BLI_assert(newv[ml->v] != -1);
 +              ml->v = newv[ml->v];
 +
 +              CustomData_copy_data(&mesh->ldata, &result->ldata, oldl[i], i, 1);
 +      }
 +
 +      /*copy vertex customdata*/
 +      mv = mvert;
 +      for (i = 0; i < result->totvert; i++, mv++) {
 +              CustomData_copy_data(&mesh->vdata, &result->vdata, oldv[i], i, 1);
 +      }
 +
 +      /*copy poly customdata*/
 +      mp = mpoly;
 +      for (i = 0; i < result->totpoly; i++, mp++) {
 +              CustomData_copy_data(&mesh->pdata, &result->pdata, oldp[i], i, 1);
 +      }
 +
 +      /*copy over data.  CustomData_add_layer can do this, need to look it up.*/
 +      memcpy(result->mvert, mvert, sizeof(MVert) * STACK_SIZE(mvert));
 +      memcpy(result->medge, medge, sizeof(MEdge) * STACK_SIZE(medge));
 +      memcpy(result->mloop, mloop, sizeof(MLoop) * STACK_SIZE(mloop));
 +      memcpy(result->mpoly, mpoly, sizeof(MPoly) * STACK_SIZE(mpoly));
 +
 +      MEM_freeN(mvert);
 +      MEM_freeN(medge);
 +      MEM_freeN(mloop);
 +      MEM_freeN(mpoly);
 +
 +      MEM_freeN(newv);
 +      MEM_freeN(newe);
 +#ifdef USE_LOOPS
 +      MEM_freeN(newl);
 +#endif
 +
 +      MEM_freeN(oldv);
 +      MEM_freeN(olde);
 +      MEM_freeN(oldl);
 +      MEM_freeN(oldp);
 +
 +      BLI_edgehash_free(ehash, NULL);
 +
 +      if (poly_map != NULL)
 +              MEM_freeN(poly_map);
 +      if (poly_map_mem != NULL)
 +              MEM_freeN(poly_map_mem);
 +
 +      BKE_id_free(NULL, mesh);
 +
 +      return result;
 +}
index ea346a3bfdaebd2c80162e4185aab12f467499fd,746e8b63a1887f06e591e172249c0eec27950c1b..413f0407c864391a7fe7c0c68675cae117354c55
@@@ -2087,7 -2117,12 +2087,7 @@@ void BKE_pbvh_draw_cb
        PBVHNode **nodes;
        int totnode;
  
-       BKE_pbvh_search_gather(bvh, update_search_cb, SET_INT_IN_POINTER(PBVH_UpdateNormals | PBVH_UpdateDrawBuffers),
 -      for (int a = 0; a < bvh->totnode; a++) {
 -              pbvh_node_check_diffuse_changed(bvh, &bvh->nodes[a]);
 -              pbvh_node_check_mask_changed(bvh, &bvh->nodes[a]);
 -      }
 -
+       BKE_pbvh_search_gather(bvh, update_search_cb, POINTER_FROM_INT(PBVH_UpdateNormals | PBVH_UpdateDrawBuffers),
                               &nodes, &totnode);
  
        pbvh_update_normals(bvh, nodes, totnode, fnors);
index ed32a9bbc2c1376aa139e88f1405eed12ba6093e,754db5415485232dabd9cb251aecc16636058b34..10910855de8b6f8bb63ddce8a5680fbec3a9fe0f
@@@ -1536,219 -1522,8 +1536,219 @@@ static void snap_to_superellipsoid(floa
        co[2] = z;
  }
  
-                               if (f_a && !BLI_ghash_haskey(tempfaceHash, SET_UINT_IN_POINTER(BM_elem_index_get(f_a)))) {
 +#define BEV_EXTEND_EDGE_DATA_CHECK(eh, flag) (BM_elem_flag_test(eh->e, flag))
 +
 +static void check_edge_data_seam_sharp_edges(BevVert *bv, int flag, bool neg)
 +{
 +      EdgeHalf *e = &bv->edges[0], *efirst = &bv->edges[0];
 +
 +      /* First first edge with seam or sharp edge data */
 +      while ((!neg && !BEV_EXTEND_EDGE_DATA_CHECK(e, flag)) || (neg && BEV_EXTEND_EDGE_DATA_CHECK(e, flag))) {
 +              e = e->next;
 +              if (e == efirst)
 +                      break;
 +      }
 +
 +      /* If no such edge found, return */
 +      if ((!neg && !BEV_EXTEND_EDGE_DATA_CHECK(e, flag)) || (neg && BEV_EXTEND_EDGE_DATA_CHECK(e, flag)))
 +              return;
 +
 +      /* Set efirst to this first encountered edge. */
 +      efirst = e;
 +
 +      do {
 +              int flag_count = 0;
 +              EdgeHalf *ne = e->next;
 +
 +              while (((!neg && !BEV_EXTEND_EDGE_DATA_CHECK(ne, flag)) || (neg && BEV_EXTEND_EDGE_DATA_CHECK(ne, flag))) &&
 +                     ne != efirst)
 +              {
 +                      if (ne->is_bev)
 +                              flag_count++;
 +                      ne = ne->next;
 +              }
 +              if (ne == e || (ne == efirst && ((!neg && !BEV_EXTEND_EDGE_DATA_CHECK(efirst, flag)) ||
 +                                               (neg && BEV_EXTEND_EDGE_DATA_CHECK(efirst, flag)))))
 +              {
 +                      break;
 +              }
 +              /* Set seam_len / sharp_len of starting edge */
 +              if (flag == BM_ELEM_SEAM) {
 +                      e->rightv->seam_len = flag_count;
 +              }
 +              else if (flag == BM_ELEM_SMOOTH) {
 +                      e->rightv->sharp_len = flag_count;
 +              }
 +              e = ne;
 +      } while (e != efirst);
 +}
 +
 +static void bevel_extend_edge_data(BevVert *bv)
 +{
 +      VMesh *vm = bv->vmesh;
 +
 +      BoundVert *bcur = bv->vmesh->boundstart, *start = bcur;
 +
 +      do {
 +              /* If current boundvert has a seam length > 0 then it has a seam running along its edges */
 +              if (bcur->seam_len) {
 +                      if (!bv->vmesh->boundstart->seam_len && start == bv->vmesh->boundstart)
 +                              start = bcur;                   /* set start to first boundvert with seam_len > 0 */
 +
 +                      /* Now for all the mesh_verts starting at current index and ending at idxlen
 +                       * We go through outermost ring and through all its segments and add seams
 +                       * for those edges */
 +                      int idxlen = bcur->index + bcur->seam_len;
 +                      for (int i = bcur->index; i < idxlen; i++) {
 +                              BMVert *v1 = mesh_vert(vm, i % vm->count, 0, 0)->v, *v2;
 +                              BMEdge *e;
 +                              for (int k = 1; k < vm->seg; k++) {
 +                                      v2 = mesh_vert(vm, i % vm->count, 0, k)->v;
 +
 +                                      /* Here v1 & v2 are current and next BMverts, we find common edge and set its edge data */
 +                                      e = v1->e;
 +                                      while (e->v1 != v2 && e->v2 != v2) {
 +                                              if (e->v1 == v1)
 +                                                      e = e->v1_disk_link.next;
 +                                              else
 +                                                      e = e->v2_disk_link.next;
 +                                      }
 +                                      BM_elem_flag_set(e, BM_ELEM_SEAM, true);
 +                                      v1 = v2;
 +                              }
 +                              BMVert *v3 = mesh_vert(vm, (i + 1) % vm->count, 0, 0)->v;
 +                              e = v1->e;                      //Do same as above for first and last vert
 +                              while (e->v1 != v3 && e->v2 != v3) {
 +                                      if (e->v1 == v1)
 +                                              e = e->v1_disk_link.next;
 +                                      else
 +                                              e = e->v2_disk_link.next;
 +                              }
 +                              BM_elem_flag_set(e, BM_ELEM_SEAM, true);
 +                              bcur = bcur->next;
 +                      }
 +              }
 +              else
 +                      bcur = bcur->next;
 +      } while (bcur != start);
 +
 +
 +      bcur = bv->vmesh->boundstart;
 +      start = bcur;
 +      do {
 +              if (bcur->sharp_len) {
 +                      if (!bv->vmesh->boundstart->sharp_len && start == bv->vmesh->boundstart)
 +                              start = bcur;
 +
 +                      int idxlen = bcur->index + bcur->sharp_len;
 +                      for (int i = bcur->index; i < idxlen; i++) {
 +                              BMVert *v1 = mesh_vert(vm, i % vm->count, 0, 0)->v, *v2;
 +                              BMEdge *e;
 +                              for (int k = 1; k < vm->seg; k++) {
 +                                      v2 = mesh_vert(vm, i % vm->count, 0, k)->v;
 +
 +                                      e = v1->e;
 +                                      while (e->v1 != v2 && e->v2 != v2) {
 +                                              if (e->v1 == v1)
 +                                                      e = e->v1_disk_link.next;
 +                                              else
 +                                                      e = e->v2_disk_link.next;
 +                                      }
 +                                      BM_elem_flag_set(e, BM_ELEM_SMOOTH, false);
 +                                      v1 = v2;
 +                              }
 +                              BMVert *v3 = mesh_vert(vm, (i + 1) % vm->count, 0, 0)->v;
 +                              e = v1->e;
 +                              while (e->v1 != v3 && e->v2 != v3) {
 +                                      if (e->v1 == v1)
 +                                              e = e->v1_disk_link.next;
 +                                      else
 +                                              e = e->v2_disk_link.next;
 +                              }
 +                              BM_elem_flag_set(e, BM_ELEM_SMOOTH, false);
 +                              bcur = bcur->next;
 +                      }
 +              }
 +              else
 +                      bcur = bcur->next;
 +      } while (bcur != start);
 +}
 +
 +static void bevel_harden_normals_mode(BevelParams *bp, BevVert *bv, BMOperator *op)
 +{
 +      if (bp->hnmode == BEVEL_HN_NONE)
 +              return;
 +
 +      VMesh *vm = bv->vmesh;
 +      BoundVert *bcur = vm->boundstart, *bstart = bcur;
 +
 +      BMEdge *e;
 +      BMIter eiter;
 +
 +      BMOpSlot *nslot = BMO_slot_get(op->slots_out, "normals.out");
 +      float n_final[3] = { 0.0f, 0.0f, 0.0f };
 +
 +      if (bp->hnmode == BEVEL_HN_FACE) {
 +              GHash *tempfaceHash = BLI_ghash_int_new(__func__);
 +
 +              /* Iterate through all faces of current BMVert and add their normal*face_area to n_final */
 +              BM_ITER_ELEM(e, &eiter, bv->v, BM_EDGES_OF_VERT) {
 +                      if (BM_elem_flag_test(e, BM_ELEM_TAG)) {
 +
 +                              BMFace *f_a, *f_b;
 +                              BM_edge_face_pair(e, &f_a, &f_b);
 +
-                                       BLI_ghash_insert(tempfaceHash, SET_UINT_IN_POINTER(BM_elem_index_get(f_a)), NULL);
++                              if (f_a && !BLI_ghash_haskey(tempfaceHash, POINTER_FROM_UINT(BM_elem_index_get(f_a)))) {
 +                                      int f_area = BM_face_calc_area(f_a);
 +                                      float f_no[3];
 +                                      copy_v3_v3(f_no, f_a->no);
 +                                      mul_v3_fl(f_no, f_area);
 +                                      add_v3_v3(n_final, f_no);
-                               if (f_b && !BLI_ghash_haskey(tempfaceHash, SET_UINT_IN_POINTER(BM_elem_index_get(f_b)))) {
++                                      BLI_ghash_insert(tempfaceHash, POINTER_FROM_UINT(BM_elem_index_get(f_a)), NULL);
 +                              }
-                                       BLI_ghash_insert(tempfaceHash, SET_UINT_IN_POINTER(BM_elem_index_get(f_b)), NULL);
++                              if (f_b && !BLI_ghash_haskey(tempfaceHash, POINTER_FROM_UINT(BM_elem_index_get(f_b)))) {
 +                                      int f_area = BM_face_calc_area(f_b);
 +                                      float f_no[3];
 +                                      copy_v3_v3(f_no, f_b->no);
 +                                      mul_v3_fl(f_no, f_area);
 +                                      add_v3_v3(n_final, f_no);
++                                      BLI_ghash_insert(tempfaceHash, POINTER_FROM_UINT(BM_elem_index_get(f_b)), NULL);
 +                              }
 +                      }
 +              }
 +              BLI_ghash_free(tempfaceHash, NULL, NULL);
 +              normalize_v3(n_final);
 +      }
 +      else if (bp->hnmode == BEVEL_HN_ADJ) {
 +              BM_ITER_ELEM(e, &eiter, bv->v, BM_EDGES_OF_VERT) {
 +                      if (BM_elem_flag_test(e, BM_ELEM_TAG)) {
 +                              if (e->v1 == bv->v) {
 +                                      add_v3_v3(n_final, e->v2->no);
 +                              }
 +                              else {
 +                                      add_v3_v3(n_final, e->v1->no);
 +                              }
 +                      }
 +              }
 +              normalize_v3(n_final);
 +      }
 +
 +      do {
 +              /* Set normals.out for vertices as computed earlier */
 +              if (BMO_slot_map_contains(nslot, bcur->nv.v) != true) {
 +
 +                      float(*vert_normal) = MEM_callocN(sizeof(*vert_normal) * 3, __func__);
 +                      add_v3_v3(vert_normal, n_final);
 +                      normalize_v3(vert_normal);
 +
 +                      BMO_slot_map_insert(op, nslot, bcur->nv.v, vert_normal);
 +              }
 +              bcur = bcur->next;
 +      } while (bcur != bstart);
 +}
 +
  /* Set the any_seam property for a BevVert and all its BoundVerts */
 -static void set_bound_vert_seams(BevVert *bv)
 +static void set_bound_vert_seams(BevVert *bv, bool mark_seam, bool mark_sharp)
  {
        BoundVert *v;
        EdgeHalf *e;
index f7b8d285cc2b796d05d5727856c15fd6472afa61,5d50640d582830cedf74862b06b4d7a8c399b805..35290d6644491f51dd88a61a9ee844cbc4e7f712
@@@ -61,10 -60,6 +61,10 @@@ void ScreenLensDistortionOperation::ini
        this->m_inputProgram = this->getInputSocketReader(0);
        this->initMutex();
  
-       rng_seed ^= (uint)GET_INT_FROM_POINTER(m_inputProgram);
 +      uint rng_seed = (uint)(PIL_check_seconds_timer_i() & UINT_MAX);
++      rng_seed ^= (uint)POINTER_AS_INT(m_inputProgram);
 +      this->m_rng = BLI_rng_new(rng_seed);
 +
        this->m_cx = 0.5f * (float)getWidth();
        this->m_cy = 0.5f * (float)getHeight();
  
index 56c2394a73172a007c5e789251c65f4def75cdbd,0000000000000000000000000000000000000000..1085476828fb5edeb29590b3943c46e15ad9dc2d
mode 100644,000000..100644
--- /dev/null
@@@ -1,973 -1,0 +1,973 @@@
-       material = BLI_ghash_lookup(wpd->material_hash, SET_UINT_IN_POINTER(hash));
 +/*
 + * Copyright 2016, Blender Foundation.
 + *
 + * 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.
 + *
 + * Contributor(s): Blender Institute
 + *
 + */
 +
 +/** \file workbench_deferred.c
 + *  \ingroup draw_engine
 + */
 +
 +#include "workbench_private.h"
 +
 +#include "BIF_gl.h"
 +
 +#include "BLI_alloca.h"
 +#include "BLI_dynstr.h"
 +#include "BLI_utildefines.h"
 +#include "BLI_rand.h"
 +
 +#include "BKE_node.h"
 +#include "BKE_modifier.h"
 +#include "BKE_particle.h"
 +
 +#include "DNA_image_types.h"
 +#include "DNA_mesh_types.h"
 +#include "DNA_modifier_types.h"
 +#include "DNA_node_types.h"
 +
 +#include "ED_uvedit.h"
 +
 +#include "GPU_shader.h"
 +#include "GPU_texture.h"
 +
 +#include "../eevee/eevee_lut.h" /* TODO find somewhere to share blue noise Table */
 +
 +/* *********** STATIC *********** */
 +
 +/* #define DEBUG_SHADOW_VOLUME */
 +
 +#ifdef DEBUG_SHADOW_VOLUME
 +#  include "draw_debug.h"
 +#endif
 +
 +static struct {
 +      struct GPUShader *prepass_sh_cache[MAX_SHADERS];
 +      struct GPUShader *composite_sh_cache[MAX_SHADERS];
 +      struct GPUShader *cavity_sh;
 +      struct GPUShader *ghost_resolve_sh;
 +      struct GPUShader *shadow_fail_sh;
 +      struct GPUShader *shadow_fail_manifold_sh;
 +      struct GPUShader *shadow_pass_sh;
 +      struct GPUShader *shadow_pass_manifold_sh;
 +      struct GPUShader *shadow_caps_sh;
 +      struct GPUShader *shadow_caps_manifold_sh;
 +
 +      struct GPUTexture *ghost_depth_tx; /* ref only, not alloced */
 +      struct GPUTexture *object_id_tx; /* ref only, not alloced */
 +      struct GPUTexture *color_buffer_tx; /* ref only, not alloced */
 +      struct GPUTexture *cavity_buffer_tx; /* ref only, not alloced */
 +      struct GPUTexture *specular_buffer_tx; /* ref only, not alloced */
 +      struct GPUTexture *normal_buffer_tx; /* ref only, not alloced */
 +      struct GPUTexture *composite_buffer_tx; /* ref only, not alloced */
 +
 +      SceneDisplay display; /* world light direction for shadows */
 +      int next_object_id;
 +      float normal_world_matrix[3][3];
 +
 +      struct GPUUniformBuffer *sampling_ubo;
 +      struct GPUTexture *jitter_tx;
 +      int cached_sample_num;
 +} e_data = {{NULL}};
 +
 +/* Shaders */
 +extern char datatoc_common_hair_lib_glsl[];
 +
 +extern char datatoc_workbench_prepass_vert_glsl[];
 +extern char datatoc_workbench_prepass_frag_glsl[];
 +extern char datatoc_workbench_cavity_frag_glsl[];
 +extern char datatoc_workbench_deferred_composite_frag_glsl[];
 +extern char datatoc_workbench_ghost_resolve_frag_glsl[];
 +
 +extern char datatoc_workbench_shadow_vert_glsl[];
 +extern char datatoc_workbench_shadow_geom_glsl[];
 +extern char datatoc_workbench_shadow_caps_geom_glsl[];
 +extern char datatoc_workbench_shadow_debug_frag_glsl[];
 +
 +extern char datatoc_workbench_background_lib_glsl[];
 +extern char datatoc_workbench_cavity_lib_glsl[];
 +extern char datatoc_workbench_common_lib_glsl[];
 +extern char datatoc_workbench_data_lib_glsl[];
 +extern char datatoc_workbench_object_outline_lib_glsl[];
 +extern char datatoc_workbench_world_light_lib_glsl[];
 +
 +static char *workbench_build_composite_frag(WORKBENCH_PrivateData *wpd)
 +{
 +      char *str = NULL;
 +
 +      DynStr *ds = BLI_dynstr_new();
 +
 +      BLI_dynstr_append(ds, datatoc_workbench_data_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_common_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_background_lib_glsl);
 +
 +      if ((wpd->shading.light & V3D_LIGHTING_MATCAP) || (wpd->shading.light & V3D_LIGHTING_STUDIO) || (wpd->shading.flag & V3D_SHADING_SPECULAR_HIGHLIGHT)) {
 +              BLI_dynstr_append(ds, datatoc_workbench_world_light_lib_glsl);
 +      }
 +      if (wpd->shading.flag & V3D_SHADING_OBJECT_OUTLINE) {
 +              BLI_dynstr_append(ds, datatoc_workbench_object_outline_lib_glsl);
 +      }
 +
 +      BLI_dynstr_append(ds, datatoc_workbench_deferred_composite_frag_glsl);
 +
 +      str = BLI_dynstr_get_cstring(ds);
 +      BLI_dynstr_free(ds);
 +      return str;
 +}
 +
 +static char *workbench_build_prepass_frag(void)
 +{
 +      char *str = NULL;
 +
 +      DynStr *ds = BLI_dynstr_new();
 +
 +      BLI_dynstr_append(ds, datatoc_workbench_data_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_common_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_prepass_frag_glsl);
 +
 +      str = BLI_dynstr_get_cstring(ds);
 +      BLI_dynstr_free(ds);
 +      return str;
 +}
 +
 +static char *workbench_build_prepass_vert(bool is_hair)
 +{
 +      char *str = NULL;
 +      if (!is_hair) {
 +              return BLI_strdup(datatoc_workbench_prepass_vert_glsl);
 +      }
 +
 +      DynStr *ds = BLI_dynstr_new();
 +
 +      BLI_dynstr_append(ds, datatoc_common_hair_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_prepass_vert_glsl);
 +
 +      str = BLI_dynstr_get_cstring(ds);
 +      BLI_dynstr_free(ds);
 +      return str;
 +}
 +
 +static char *workbench_build_cavity_frag(void)
 +{
 +      char *str = NULL;
 +
 +      DynStr *ds = BLI_dynstr_new();
 +
 +      BLI_dynstr_append(ds, datatoc_workbench_common_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_cavity_frag_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_cavity_lib_glsl);
 +
 +      str = BLI_dynstr_get_cstring(ds);
 +      BLI_dynstr_free(ds);
 +      return str;
 +}
 +
 +static void ensure_deferred_shaders(WORKBENCH_PrivateData *wpd, int index, bool use_textures, bool is_hair)
 +{
 +      if (e_data.prepass_sh_cache[index] == NULL) {
 +              char *defines = workbench_material_build_defines(wpd, use_textures, is_hair);
 +              char *composite_frag = workbench_build_composite_frag(wpd);
 +              char *prepass_vert = workbench_build_prepass_vert(is_hair);
 +              char *prepass_frag = workbench_build_prepass_frag();
 +              e_data.prepass_sh_cache[index] = DRW_shader_create(
 +                      prepass_vert, NULL,
 +                      prepass_frag, defines);
 +              if (!use_textures && !is_hair) {
 +                      e_data.composite_sh_cache[index] = DRW_shader_create_fullscreen(composite_frag, defines);
 +              }
 +              MEM_freeN(prepass_vert);
 +              MEM_freeN(prepass_frag);
 +              MEM_freeN(composite_frag);
 +              MEM_freeN(defines);
 +      }
 +}
 +
 +static void select_deferred_shaders(WORKBENCH_PrivateData *wpd)
 +{
 +      int index_solid = workbench_material_get_shader_index(wpd, false, false);
 +      int index_solid_hair = workbench_material_get_shader_index(wpd, false, true);
 +      int index_texture = workbench_material_get_shader_index(wpd, true, false);
 +      int index_texture_hair = workbench_material_get_shader_index(wpd, true, true);
 +
 +      ensure_deferred_shaders(wpd, index_solid, false, false);
 +      ensure_deferred_shaders(wpd, index_solid_hair, false, true);
 +      ensure_deferred_shaders(wpd, index_texture, true, false);
 +      ensure_deferred_shaders(wpd, index_texture_hair, true, true);
 +
 +      wpd->prepass_solid_sh = e_data.prepass_sh_cache[index_solid];
 +      wpd->prepass_solid_hair_sh = e_data.prepass_sh_cache[index_solid_hair];
 +      wpd->prepass_texture_sh = e_data.prepass_sh_cache[index_texture];
 +      wpd->prepass_texture_hair_sh = e_data.prepass_sh_cache[index_texture_hair];
 +      wpd->composite_sh = e_data.composite_sh_cache[index_solid];
 +}
 +
 +
 +/* Using Hammersley distribution */
 +static float *create_disk_samples(int num_samples, int num_iterations)
 +{
 +      /* vec4 to ensure memory alignment. */
 +      const int total_samples = num_samples * num_iterations;
 +      float(*texels)[4] = MEM_mallocN(sizeof(float[4]) * total_samples, __func__);
 +      const float num_samples_inv = 1.0f / num_samples;
 +
 +      for (int i = 0; i < total_samples; i++) {
 +              float it_add = (i / num_samples) * 0.499f;
 +              float r = fmodf((i + 0.5f + it_add) * num_samples_inv, 1.0f);
 +              double dphi;
 +              BLI_hammersley_1D(i, &dphi);
 +
 +              float phi = (float)dphi * 2.0f * M_PI + it_add;
 +              texels[i][0] = cosf(phi);
 +              texels[i][1] = sinf(phi);
 +              /* This deliberatly distribute more samples
 +               * at the center of the disk (and thus the shadow). */
 +              texels[i][2] = r;
 +      }
 +
 +      return (float *)texels;
 +}
 +
 +static struct GPUTexture *create_jitter_texture(int num_samples)
 +{
 +      float jitter[64 * 64][3];
 +      const float num_samples_inv = 1.0f / num_samples;
 +
 +      for (int i = 0; i < 64 * 64; i++) {
 +              float phi = blue_noise[i][0] * 2.0f * M_PI;
 +              /* This rotate the sample per pixels */
 +              jitter[i][0] = cosf(phi);
 +              jitter[i][1] = sinf(phi);
 +              /* This offset the sample along it's direction axis (reduce banding) */
 +              float bn = blue_noise[i][1] - 0.5f;
 +              CLAMP(bn, -0.499f, 0.499f); /* fix fireflies */
 +              jitter[i][2] = bn * num_samples_inv;
 +      }
 +
 +      UNUSED_VARS(bsdf_split_sum_ggx, btdf_split_sum_ggx, ltc_mag_ggx, ltc_mat_ggx, ltc_disk_integral);
 +
 +      return DRW_texture_create_2D(64, 64, GPU_RGB16F, DRW_TEX_FILTER | DRW_TEX_WRAP, &jitter[0][0]);
 +}
 +/* Functions */
 +
 +
 +static void workbench_init_object_data(DrawData *dd)
 +{
 +      WORKBENCH_ObjectData *data = (WORKBENCH_ObjectData *)dd;
 +      data->object_id = ((e_data.next_object_id++) & 0xff) + 1;
 +      data->shadow_bbox_dirty = true;
 +}
 +
 +void workbench_deferred_engine_init(WORKBENCH_Data *vedata)
 +{
 +      WORKBENCH_FramebufferList *fbl = vedata->fbl;
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_PassList *psl = vedata->psl;
 +      DefaultTextureList *dtxl = DRW_viewport_texture_list_get();
 +      const DRWContextState *draw_ctx = DRW_context_state_get();
 +
 +      if (!stl->g_data) {
 +              /* Alloc transient pointers */
 +              stl->g_data = MEM_callocN(sizeof(*stl->g_data), __func__);
 +      }
 +      if (!stl->effects) {
 +              stl->effects = MEM_callocN(sizeof(*stl->effects), __func__);
 +              workbench_effect_info_init(stl->effects);
 +      }
 +
 +      if (!e_data.next_object_id) {
 +              memset(e_data.prepass_sh_cache,   0x00, sizeof(struct GPUShader *) * MAX_SHADERS);
 +              memset(e_data.composite_sh_cache, 0x00, sizeof(struct GPUShader *) * MAX_SHADERS);
 +              e_data.next_object_id = 1;
 +#ifdef DEBUG_SHADOW_VOLUME
 +              const char *shadow_frag = datatoc_workbench_shadow_debug_frag_glsl;
 +#else
 +              const char *shadow_frag = NULL;
 +#endif
 +              e_data.shadow_pass_sh = DRW_shader_create(
 +                      datatoc_workbench_shadow_vert_glsl,
 +                      datatoc_workbench_shadow_geom_glsl,
 +                      shadow_frag,
 +                      "#define SHADOW_PASS\n"
 +                      "#define DOUBLE_MANIFOLD\n");
 +              e_data.shadow_pass_manifold_sh = DRW_shader_create(
 +                      datatoc_workbench_shadow_vert_glsl,
 +                      datatoc_workbench_shadow_geom_glsl,
 +                      shadow_frag,
 +                      "#define SHADOW_PASS\n");
 +              e_data.shadow_fail_sh = DRW_shader_create(
 +                      datatoc_workbench_shadow_vert_glsl,
 +                      datatoc_workbench_shadow_geom_glsl,
 +                      shadow_frag,
 +                      "#define SHADOW_FAIL\n"
 +                      "#define DOUBLE_MANIFOLD\n");
 +              e_data.shadow_fail_manifold_sh = DRW_shader_create(
 +                      datatoc_workbench_shadow_vert_glsl,
 +                      datatoc_workbench_shadow_geom_glsl,
 +                      shadow_frag,
 +                      "#define SHADOW_FAIL\n");
 +              e_data.shadow_caps_sh = DRW_shader_create(
 +                      datatoc_workbench_shadow_vert_glsl,
 +                      datatoc_workbench_shadow_caps_geom_glsl,
 +                      shadow_frag,
 +                      "#define SHADOW_FAIL\n"
 +                      "#define DOUBLE_MANIFOLD\n");
 +              e_data.shadow_caps_manifold_sh = DRW_shader_create(
 +                      datatoc_workbench_shadow_vert_glsl,
 +                      datatoc_workbench_shadow_caps_geom_glsl,
 +                      shadow_frag,
 +                      "#define SHADOW_FAIL\n");
 +
 +              char *cavity_frag = workbench_build_cavity_frag();
 +              e_data.cavity_sh = DRW_shader_create_fullscreen(cavity_frag, NULL);
 +              MEM_freeN(cavity_frag);
 +
 +              e_data.ghost_resolve_sh = DRW_shader_create_fullscreen(datatoc_workbench_ghost_resolve_frag_glsl, NULL);
 +      }
 +      workbench_volume_engine_init();
 +      workbench_fxaa_engine_init();
 +      workbench_taa_engine_init(vedata);
 +
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +      workbench_private_data_init(wpd);
 +
 +      {
 +              const float *viewport_size = DRW_viewport_size_get();
 +              const int size[2] = {(int)viewport_size[0], (int)viewport_size[1]};
 +              e_data.object_id_tx = DRW_texture_pool_query_2D(size[0], size[1], GPU_R32UI, &draw_engine_workbench_solid);
 +              e_data.color_buffer_tx = DRW_texture_pool_query_2D(size[0], size[1], GPU_RGBA8, &draw_engine_workbench_solid);
 +              e_data.cavity_buffer_tx = DRW_texture_pool_query_2D(size[0], size[1], GPU_RG16, &draw_engine_workbench_solid);
 +              e_data.specular_buffer_tx = DRW_texture_pool_query_2D(size[0], size[1], GPU_RGBA8, &draw_engine_workbench_solid);
 +              e_data.composite_buffer_tx = DRW_texture_pool_query_2D(
 +                      size[0], size[1], GPU_RGBA16F, &draw_engine_workbench_solid);
 +
 +              if (NORMAL_ENCODING_ENABLED()) {
 +                      e_data.normal_buffer_tx = DRW_texture_pool_query_2D(
 +                              size[0], size[1], GPU_RG16, &draw_engine_workbench_solid);
 +              }
 +              else {
 +                      e_data.normal_buffer_tx = DRW_texture_pool_query_2D(
 +                              size[0], size[1], GPU_RGBA32F, &draw_engine_workbench_solid);
 +              }
 +
 +              GPU_framebuffer_ensure_config(&fbl->prepass_fb, {
 +                      GPU_ATTACHMENT_TEXTURE(dtxl->depth),
 +                      GPU_ATTACHMENT_TEXTURE(e_data.object_id_tx),
 +                      GPU_ATTACHMENT_TEXTURE(e_data.color_buffer_tx),
 +                      GPU_ATTACHMENT_TEXTURE(e_data.specular_buffer_tx),
 +                      GPU_ATTACHMENT_TEXTURE(e_data.normal_buffer_tx),
 +              });
 +              GPU_framebuffer_ensure_config(&fbl->cavity_fb, {
 +                      GPU_ATTACHMENT_NONE,
 +                      GPU_ATTACHMENT_TEXTURE(e_data.cavity_buffer_tx),
 +              });
 +              GPU_framebuffer_ensure_config(&fbl->composite_fb, {
 +                      GPU_ATTACHMENT_TEXTURE(dtxl->depth),
 +                      GPU_ATTACHMENT_TEXTURE(e_data.composite_buffer_tx),
 +              });
 +              GPU_framebuffer_ensure_config(&fbl->volume_fb, {
 +                      GPU_ATTACHMENT_NONE,
 +                      GPU_ATTACHMENT_TEXTURE(e_data.composite_buffer_tx),
 +              });
 +              GPU_framebuffer_ensure_config(&fbl->effect_fb, {
 +                      GPU_ATTACHMENT_NONE,
 +                      GPU_ATTACHMENT_TEXTURE(e_data.color_buffer_tx),
 +              });
 +      }
 +
 +      {
 +              Scene *scene = draw_ctx->scene;
 +              /* AO Samples Tex */
 +              int num_iterations = workbench_taa_calculate_num_iterations(vedata);
 +
 +              const int ssao_samples_single_iteration = scene->display.matcap_ssao_samples;
 +              const int ssao_samples = MIN2(num_iterations * ssao_samples_single_iteration, 500);
 +
 +              if (e_data.sampling_ubo && (e_data.cached_sample_num != ssao_samples)) {
 +                      DRW_UBO_FREE_SAFE(e_data.sampling_ubo);
 +                      DRW_TEXTURE_FREE_SAFE(e_data.jitter_tx);
 +              }
 +
 +              if (e_data.sampling_ubo == NULL) {
 +                      float *samples = create_disk_samples(ssao_samples_single_iteration, num_iterations);
 +                      e_data.jitter_tx = create_jitter_texture(ssao_samples);
 +                      e_data.sampling_ubo = DRW_uniformbuffer_create(sizeof(float[4]) * ssao_samples, samples);
 +                      e_data.cached_sample_num = ssao_samples;
 +                      MEM_freeN(samples);
 +              }
 +      }
 +
 +      /* Prepass */
 +      {
 +              DRWShadingGroup *grp;
 +              const bool do_cull = (draw_ctx->v3d && (draw_ctx->v3d->flag2 & V3D_BACKFACE_CULLING));
 +
 +              int state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS_EQUAL;
 +              psl->prepass_pass = DRW_pass_create("Prepass", (do_cull) ? state | DRW_STATE_CULL_BACK : state);
 +              psl->prepass_hair_pass = DRW_pass_create("Prepass", state);
 +
 +              psl->ghost_prepass_pass = DRW_pass_create("Prepass Ghost", (do_cull) ? state | DRW_STATE_CULL_BACK : state);
 +              psl->ghost_prepass_hair_pass = DRW_pass_create("Prepass Ghost", state);
 +
 +              psl->ghost_resolve_pass = DRW_pass_create("Resolve Ghost Depth", DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_ALWAYS);
 +              grp = DRW_shgroup_create(e_data.ghost_resolve_sh, psl->ghost_resolve_pass);
 +              DRW_shgroup_uniform_texture_ref(grp, "depthBuffer", &e_data.ghost_depth_tx);
 +              DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
 +      }
 +
 +      {
 +              workbench_aa_create_pass(vedata, &e_data.color_buffer_tx);
 +      }
 +
 +      {
 +              int state = DRW_STATE_WRITE_COLOR;
 +              psl->cavity_pass = DRW_pass_create("Cavity", state);
 +              DRWShadingGroup *grp = DRW_shgroup_create(e_data.cavity_sh, psl->cavity_pass);
 +              DRW_shgroup_uniform_texture_ref(grp, "depthBuffer", &dtxl->depth);
 +              DRW_shgroup_uniform_texture_ref(grp, "colorBuffer", &e_data.color_buffer_tx);
 +              DRW_shgroup_uniform_texture_ref(grp, "normalBuffer", &e_data.normal_buffer_tx);
 +
 +              DRW_shgroup_uniform_vec2(grp, "invertedViewportSize", DRW_viewport_invert_size_get(), 1);
 +              DRW_shgroup_uniform_vec4(grp, "viewvecs[0]", (float *)wpd->viewvecs, 3);
 +              DRW_shgroup_uniform_vec4(grp, "ssao_params", wpd->ssao_params, 1);
 +              DRW_shgroup_uniform_vec4(grp, "ssao_settings", wpd->ssao_settings, 1);
 +              DRW_shgroup_uniform_mat4(grp, "WinMatrix", wpd->winmat);
 +              DRW_shgroup_uniform_texture(grp, "ssao_jitter", e_data.jitter_tx);
 +              DRW_shgroup_uniform_block(grp, "samples_block", e_data.sampling_ubo);
 +              DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
 +      }
 +}
 +
 +static void workbench_setup_ghost_framebuffer(WORKBENCH_FramebufferList *fbl)
 +{
 +      const float *viewport_size = DRW_viewport_size_get();
 +      const int size[2] = {(int)viewport_size[0], (int)viewport_size[1]};
 +
 +      e_data.ghost_depth_tx = DRW_texture_pool_query_2D(size[0], size[1], GPU_DEPTH_COMPONENT24, &draw_engine_workbench_solid);
 +      GPU_framebuffer_ensure_config(&fbl->ghost_prepass_fb, {
 +              GPU_ATTACHMENT_TEXTURE(e_data.ghost_depth_tx),
 +              GPU_ATTACHMENT_TEXTURE(e_data.object_id_tx),
 +              GPU_ATTACHMENT_TEXTURE(e_data.color_buffer_tx),
 +              GPU_ATTACHMENT_TEXTURE(e_data.specular_buffer_tx),
 +              GPU_ATTACHMENT_TEXTURE(e_data.normal_buffer_tx),
 +      });
 +}
 +
 +void workbench_deferred_engine_free(void)
 +{
 +      for (int index = 0; index < MAX_SHADERS; index++) {
 +              DRW_SHADER_FREE_SAFE(e_data.prepass_sh_cache[index]);
 +              DRW_SHADER_FREE_SAFE(e_data.composite_sh_cache[index]);
 +      }
 +      DRW_SHADER_FREE_SAFE(e_data.cavity_sh);
 +      DRW_SHADER_FREE_SAFE(e_data.ghost_resolve_sh);
 +      DRW_UBO_FREE_SAFE(e_data.sampling_ubo);
 +      DRW_TEXTURE_FREE_SAFE(e_data.jitter_tx);
 +
 +      DRW_SHADER_FREE_SAFE(e_data.shadow_pass_sh);
 +      DRW_SHADER_FREE_SAFE(e_data.shadow_pass_manifold_sh);
 +      DRW_SHADER_FREE_SAFE(e_data.shadow_fail_sh);
 +      DRW_SHADER_FREE_SAFE(e_data.shadow_fail_manifold_sh);
 +      DRW_SHADER_FREE_SAFE(e_data.shadow_caps_sh);
 +      DRW_SHADER_FREE_SAFE(e_data.shadow_caps_manifold_sh);
 +
 +      workbench_volume_engine_free();
 +      workbench_fxaa_engine_free();
 +      workbench_taa_engine_free();
 +}
 +
 +static void workbench_composite_uniforms(WORKBENCH_PrivateData *wpd, DRWShadingGroup *grp)
 +{
 +      DRW_shgroup_uniform_texture_ref(grp, "colorBuffer", &e_data.color_buffer_tx);
 +      DRW_shgroup_uniform_texture_ref(grp, "objectId", &e_data.object_id_tx);
 +      if (NORMAL_VIEWPORT_COMP_PASS_ENABLED(wpd)) {
 +              DRW_shgroup_uniform_texture_ref(grp, "normalBuffer", &e_data.normal_buffer_tx);
 +      }
 +      if (CAVITY_ENABLED(wpd)) {
 +              DRW_shgroup_uniform_texture_ref(grp, "cavityBuffer", &e_data.cavity_buffer_tx);
 +      }
 +      if (SPECULAR_HIGHLIGHT_ENABLED(wpd) || MATCAP_ENABLED(wpd)) {
 +              DRW_shgroup_uniform_texture_ref(grp, "specularBuffer", &e_data.specular_buffer_tx);
 +              DRW_shgroup_uniform_vec4(grp, "viewvecs[0]", (float *)wpd->viewvecs, 3);
 +      }
 +      DRW_shgroup_uniform_block(grp, "world_block", wpd->world_ubo);
 +      DRW_shgroup_uniform_vec2(grp, "invertedViewportSize", DRW_viewport_invert_size_get(), 1);
 +
 +      if (STUDIOLIGHT_ORIENTATION_VIEWNORMAL_ENABLED(wpd)) {
 +              BKE_studiolight_ensure_flag(wpd->studio_light, STUDIOLIGHT_EQUIRECTANGULAR_RADIANCE_GPUTEXTURE);
 +              DRW_shgroup_uniform_texture(grp, "matcapImage", wpd->studio_light->equirectangular_radiance_gputexture);
 +      }
 +
 +      workbench_material_set_normal_world_matrix(grp, wpd, e_data.normal_world_matrix);
 +}
 +
 +void workbench_deferred_cache_init(WORKBENCH_Data *vedata)
 +{
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_PassList *psl = vedata->psl;
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +      DRWShadingGroup *grp;
 +      const DRWContextState *draw_ctx = DRW_context_state_get();
 +
 +      Scene *scene = draw_ctx->scene;
 +
 +      workbench_volume_cache_init(vedata);
 +
 +      select_deferred_shaders(wpd);
 +
 +      /* Deferred Mix Pass */
 +      {
 +              workbench_private_data_get_light_direction(wpd, e_data.display.light_direction);
 +              studiolight_update_light(wpd, e_data.display.light_direction);
 +
 +              e_data.display.shadow_shift = scene->display.shadow_shift;
 +
 +              if (SHADOW_ENABLED(wpd)) {
 +                      psl->composite_pass = DRW_pass_create(
 +                              "Composite", DRW_STATE_WRITE_COLOR | DRW_STATE_STENCIL_EQUAL);
 +                      grp = DRW_shgroup_create(wpd->composite_sh, psl->composite_pass);
 +                      workbench_composite_uniforms(wpd, grp);
 +                      DRW_shgroup_stencil_mask(grp, 0x00);
 +                      DRW_shgroup_uniform_float_copy(grp, "lightMultiplier", 1.0f);
 +                      DRW_shgroup_uniform_float(grp, "shadowMultiplier", &wpd->shadow_multiplier, 1);
 +                      DRW_shgroup_uniform_float(grp, "shadowShift", &scene->display.shadow_shift, 1);
 +                      DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
 +
 +                      /* Stencil Shadow passes. */
 +#ifdef DEBUG_SHADOW_VOLUME
 +                      DRWState depth_pass_state = DRW_STATE_DEPTH_LESS | DRW_STATE_WRITE_COLOR | DRW_STATE_ADDITIVE;
 +                      DRWState depth_fail_state = DRW_STATE_DEPTH_GREATER_EQUAL | DRW_STATE_WRITE_COLOR | DRW_STATE_ADDITIVE;
 +#else
 +                      DRWState depth_pass_state = DRW_STATE_DEPTH_LESS | DRW_STATE_WRITE_STENCIL_SHADOW_PASS;
 +                      DRWState depth_fail_state = DRW_STATE_DEPTH_LESS | DRW_STATE_WRITE_STENCIL_SHADOW_FAIL;
 +#endif
 +                      psl->shadow_depth_pass_pass = DRW_pass_create("Shadow Pass", depth_pass_state);
 +                      psl->shadow_depth_pass_mani_pass = DRW_pass_create("Shadow Pass Mani", depth_pass_state);
 +                      psl->shadow_depth_fail_pass = DRW_pass_create("Shadow Fail", depth_fail_state);
 +                      psl->shadow_depth_fail_mani_pass = DRW_pass_create("Shadow Fail Mani", depth_fail_state);
 +                      psl->shadow_depth_fail_caps_pass = DRW_pass_create("Shadow Fail Caps", depth_fail_state);
 +                      psl->shadow_depth_fail_caps_mani_pass = DRW_pass_create("Shadow Fail Caps Mani", depth_fail_state);
 +
 +#ifndef DEBUG_SHADOW_VOLUME
 +                      grp = DRW_shgroup_create(e_data.shadow_pass_sh, psl->shadow_depth_pass_pass);
 +                      DRW_shgroup_stencil_mask(grp, 0xFF);
 +                      grp = DRW_shgroup_create(e_data.shadow_pass_manifold_sh, psl->shadow_depth_pass_mani_pass);
 +                      DRW_shgroup_stencil_mask(grp, 0xFF);
 +                      grp = DRW_shgroup_create(e_data.shadow_fail_sh, psl->shadow_depth_fail_pass);
 +                      DRW_shgroup_stencil_mask(grp, 0xFF);
 +                      grp = DRW_shgroup_create(e_data.shadow_fail_manifold_sh, psl->shadow_depth_fail_mani_pass);
 +                      DRW_shgroup_stencil_mask(grp, 0xFF);
 +                      grp = DRW_shgroup_create(e_data.shadow_caps_sh, psl->shadow_depth_fail_caps_pass);
 +                      DRW_shgroup_stencil_mask(grp, 0xFF);
 +                      grp = DRW_shgroup_create(e_data.shadow_caps_manifold_sh, psl->shadow_depth_fail_caps_mani_pass);
 +                      DRW_shgroup_stencil_mask(grp, 0xFF);
 +
 +                      psl->composite_shadow_pass = DRW_pass_create("Composite Shadow", DRW_STATE_WRITE_COLOR | DRW_STATE_STENCIL_NEQUAL);
 +                      grp = DRW_shgroup_create(wpd->composite_sh, psl->composite_shadow_pass);
 +                      DRW_shgroup_stencil_mask(grp, 0x00);
 +                      workbench_composite_uniforms(wpd, grp);
 +                      DRW_shgroup_uniform_float(grp, "lightMultiplier", &wpd->shadow_multiplier, 1);
 +                      DRW_shgroup_uniform_float(grp, "shadowMultiplier", &wpd->shadow_multiplier, 1);
 +                      DRW_shgroup_uniform_float(grp, "shadowShift", &scene->display.shadow_shift, 1);
 +                      DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
 +#endif
 +
 +              }
 +              else {
 +                      psl->composite_pass = DRW_pass_create(
 +                              "Composite", DRW_STATE_WRITE_COLOR);
 +                      grp = DRW_shgroup_create(wpd->composite_sh, psl->composite_pass);
 +                      workbench_composite_uniforms(wpd, grp);
 +                      DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
 +              }
 +      }
 +}
 +
 +static WORKBENCH_MaterialData *get_or_create_material_data(
 +        WORKBENCH_Data *vedata, Object *ob, Material *mat, Image *ima, int color_type)
 +{
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_PassList *psl = vedata->psl;
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +      WORKBENCH_MaterialData *material;
 +      WORKBENCH_ObjectData *engine_object_data = (WORKBENCH_ObjectData *)DRW_drawdata_ensure(
 +              &ob->id, &draw_engine_workbench_solid, sizeof(WORKBENCH_ObjectData), &workbench_init_object_data, NULL);
 +      WORKBENCH_MaterialData material_template;
 +      const bool is_ghost = (ob->dtx & OB_DRAWXRAY);
 +
 +      /* Solid */
 +      workbench_material_update_data(wpd, ob, mat, &material_template);
 +      material_template.object_id = OBJECT_ID_PASS_ENABLED(wpd) ? engine_object_data->object_id : 1;
 +      material_template.color_type = color_type;
 +      material_template.ima = ima;
 +      uint hash = workbench_material_get_hash(&material_template, is_ghost);
 +
-               BLI_ghash_insert(wpd->material_hash, SET_UINT_IN_POINTER(hash), material);
++      material = BLI_ghash_lookup(wpd->material_hash, POINTER_FROM_UINT(hash));
 +      if (material == NULL) {
 +              material = MEM_mallocN(sizeof(WORKBENCH_MaterialData), __func__);
 +              material->shgrp = DRW_shgroup_create(
 +                      (color_type == V3D_SHADING_TEXTURE_COLOR) ? wpd->prepass_texture_sh: wpd->prepass_solid_sh,
 +                      (ob->dtx & OB_DRAWXRAY) ? psl->ghost_prepass_pass : psl->prepass_pass);
 +              workbench_material_copy(material, &material_template);
 +              DRW_shgroup_stencil_mask(material->shgrp, (ob->dtx & OB_DRAWXRAY) ? 0x00 : 0xFF);
 +              DRW_shgroup_uniform_int(material->shgrp, "object_id", &material->object_id, 1);
 +              workbench_material_shgroup_uniform(wpd, material->shgrp, material, ob);
 +
++              BLI_ghash_insert(wpd->material_hash, POINTER_FROM_UINT(hash), material);
 +      }
 +      return material;
 +}
 +
 +static void workbench_cache_populate_particles(WORKBENCH_Data *vedata, Object *ob)
 +{
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_PassList *psl = vedata->psl;
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +      const DRWContextState *draw_ctx = DRW_context_state_get();
 +      if (ob == draw_ctx->object_edit) {
 +              return;
 +      }
 +      for (ModifierData *md = ob->modifiers.first; md; md = md->next) {
 +              if (md->type != eModifierType_ParticleSystem) {
 +                      continue;
 +              }
 +              ParticleSystem *psys = ((ParticleSystemModifierData *)md)->psys;
 +              if (!psys_check_enabled(ob, psys, false)) {
 +                      continue;
 +              }
 +              if (!DRW_check_psys_visible_within_active_context(ob, psys)) {
 +                      continue;
 +              }
 +              ParticleSettings *part = psys->part;
 +              const int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as;
 +
 +              if (draw_as == PART_DRAW_PATH) {
 +                      Image *image = NULL;
 +                      Material *mat = give_current_material(ob, part->omat);
 +                      ED_object_get_active_image(ob, part->omat, &image, NULL, NULL, NULL);
 +                      int color_type = workbench_material_determine_color_type(wpd, image, ob);
 +                      WORKBENCH_MaterialData *material = get_or_create_material_data(vedata, ob, mat, image, color_type);
 +
 +                      struct GPUShader *shader = (color_type != V3D_SHADING_TEXTURE_COLOR) ?
 +                              wpd->prepass_solid_hair_sh :
 +                              wpd->prepass_texture_hair_sh;
 +                      DRWShadingGroup *shgrp = DRW_shgroup_hair_create(
 +                              ob, psys, md,
 +                              (ob->dtx & OB_DRAWXRAY) ? psl->ghost_prepass_hair_pass : psl->prepass_hair_pass,
 +                              shader);
 +                      DRW_shgroup_stencil_mask(shgrp, (ob->dtx & OB_DRAWXRAY) ? 0x00 : 0xFF);
 +                      DRW_shgroup_uniform_int(shgrp, "object_id", &material->object_id, 1);
 +                      workbench_material_shgroup_uniform(wpd, shgrp, material, ob);
 +              }
 +      }
 +}
 +
 +void workbench_deferred_solid_cache_populate(WORKBENCH_Data *vedata, Object *ob)
 +{
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_PassList *psl = vedata->psl;
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +      const DRWContextState *draw_ctx = DRW_context_state_get();
 +      Scene *scene = draw_ctx->scene;
 +
 +      if (!DRW_object_is_renderable(ob))
 +              return;
 +
 +      if (ob->type == OB_MESH) {
 +              workbench_cache_populate_particles(vedata, ob);
 +      }
 +
 +      ModifierData *md;
 +      if (((ob->base_flag & BASE_FROMDUPLI) == 0) &&
 +          (md = modifiers_findByType(ob, eModifierType_Smoke)) &&
 +          (modifier_isEnabled(scene, md, eModifierMode_Realtime)) &&
 +          (((SmokeModifierData *)md)->domain != NULL))
 +      {
 +              workbench_volume_cache_populate(vedata, scene, ob, md);
 +              return; /* Do not draw solid in this case. */
 +      }
 +
 +      if (!DRW_check_object_visible_within_active_context(ob)) {
 +              return;
 +      }
 +
 +      WORKBENCH_MaterialData *material;
 +      if (ELEM(ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_MBALL)) {
 +              const bool is_active = (ob == draw_ctx->obact);
 +              const bool is_sculpt_mode = is_active && (draw_ctx->object_mode & OB_MODE_SCULPT) != 0;
 +              bool is_drawn = false;
 +              if (!is_sculpt_mode && TEXTURE_DRAWING_ENABLED(wpd) && ELEM(ob->type, OB_MESH)) {
 +                      const Mesh *me = ob->data;
 +                      if (me->mloopuv) {
 +                              const int materials_len = MAX2(1, (is_sculpt_mode ? 1 : ob->totcol));
 +                              struct GPUMaterial **gpumat_array = BLI_array_alloca(gpumat_array, materials_len);
 +                              struct GPUBatch **geom_array = me->totcol ? DRW_cache_mesh_surface_texpaint_get(ob) : NULL;
 +                              if (materials_len > 0 && geom_array) {
 +                                      for (int i = 0; i < materials_len; i++) {
 +                                              if (geom_array[i] == NULL) {
 +                                                      continue;
 +                                              }
 +
 +                                              Material *mat = give_current_material(ob, i + 1);
 +                                              Image *image;
 +                                              ED_object_get_active_image(ob, i + 1, &image, NULL, NULL, NULL);
 +                                              int color_type = workbench_material_determine_color_type(wpd, image, ob);
 +                                              material = get_or_create_material_data(vedata, ob, mat, image, color_type);
 +                                              DRW_shgroup_call_object_add(material->shgrp, geom_array[i], ob);
 +                                      }
 +                                      is_drawn = true;
 +                              }
 +                      }
 +              }
 +
 +              /* Fallback from not drawn OB_TEXTURE mode or just OB_SOLID mode */
 +              if (!is_drawn) {
 +                      if (ELEM(wpd->shading.color_type, V3D_SHADING_SINGLE_COLOR, V3D_SHADING_RANDOM_COLOR)) {
 +                              /* No material split needed */
 +                              struct GPUBatch *geom = DRW_cache_object_surface_get(ob);
 +                              if (geom) {
 +                                      material = get_or_create_material_data(vedata, ob, NULL, NULL, wpd->shading.color_type);
 +                                      if (is_sculpt_mode) {
 +                                              DRW_shgroup_call_sculpt_add(material->shgrp, ob, ob->obmat);
 +                                      }
 +                                      else {
 +                                              DRW_shgroup_call_object_add(material->shgrp, geom, ob);
 +                                      }
 +                              }
 +                      }
 +                      else { /* MATERIAL colors */
 +                              const int materials_len = MAX2(1, (is_sculpt_mode ? 1 : ob->totcol));
 +                              struct GPUMaterial **gpumat_array = BLI_array_alloca(gpumat_array, materials_len);
 +                              for (int i = 0; i < materials_len; i++) {
 +                                      gpumat_array[i] = NULL;
 +                              }
 +
 +                              struct GPUBatch **mat_geom = DRW_cache_object_surface_material_get(
 +                                      ob, gpumat_array, materials_len, NULL, NULL, NULL);
 +                              if (mat_geom) {
 +                                      for (int i = 0; i < materials_len; ++i) {
 +                                              if (mat_geom[i] == NULL) {
 +                                                      continue;
 +                                              }
 +
 +                                              Material *mat = give_current_material(ob, i + 1);
 +                                              material = get_or_create_material_data(vedata, ob, mat, NULL, V3D_SHADING_MATERIAL_COLOR);
 +                                              if (is_sculpt_mode) {
 +                                                      DRW_shgroup_call_sculpt_add(material->shgrp, ob, ob->obmat);
 +                                              }
 +                                              else {
 +                                                      DRW_shgroup_call_object_add(material->shgrp, mat_geom[i], ob);
 +                                              }
 +                                      }
 +                              }
 +                      }
 +              }
 +
 +              if (SHADOW_ENABLED(wpd) && (ob->display.flag & OB_SHOW_SHADOW)) {
 +                      bool is_manifold;
 +                      struct GPUBatch *geom_shadow = DRW_cache_object_edge_detection_get(ob, &is_manifold);
 +                      if (geom_shadow) {
 +                              if (is_sculpt_mode) {
 +                                      /* Currently unsupported in sculpt mode. We could revert to the slow
 +                                       * method in this case but I'm not sure if it's a good idea given that
 +                                       * sculpted meshes are heavy to begin with. */
 +                                      // DRW_shgroup_call_sculpt_add(wpd->shadow_shgrp, ob, ob->obmat);
 +                              }
 +                              else {
 +                                      WORKBENCH_ObjectData *engine_object_data = (WORKBENCH_ObjectData *)DRW_drawdata_ensure(
 +                                              &ob->id, &draw_engine_workbench_solid, sizeof(WORKBENCH_ObjectData), &workbench_init_object_data, NULL);
 +
 +                                      if (studiolight_object_cast_visible_shadow(wpd, ob, engine_object_data)) {
 +
 +                                              invert_m4_m4(ob->imat, ob->obmat);
 +                                              mul_v3_mat3_m4v3(engine_object_data->shadow_dir, ob->imat, e_data.display.light_direction);
 +
 +                                              DRWShadingGroup *grp;
 +                                              bool use_shadow_pass_technique = !studiolight_camera_in_object_shadow(wpd, ob, engine_object_data);
 +
 +                                              if (use_shadow_pass_technique) {
 +                                                      if (is_manifold) {
 +                                                              grp = DRW_shgroup_create(e_data.shadow_pass_manifold_sh, psl->shadow_depth_pass_mani_pass);
 +                                                      }
 +                                                      else {
 +                                                              grp = DRW_shgroup_create(e_data.shadow_pass_sh, psl->shadow_depth_pass_pass);
 +                                                      }
 +                                                      DRW_shgroup_uniform_vec3(grp, "lightDirection", engine_object_data->shadow_dir, 1);
 +                                                      DRW_shgroup_uniform_float_copy(grp, "lightDistance", 1e5f);
 +                                                      DRW_shgroup_call_add(grp, geom_shadow, ob->obmat);
 +#ifdef DEBUG_SHADOW_VOLUME
 +                                                      DRW_debug_bbox(&engine_object_data->shadow_bbox, (float[4]){1.0f, 0.0f, 0.0f, 1.0f});
 +#endif
 +                                              }
 +                                              else {
 +                                                      float extrude_distance = studiolight_object_shadow_distance(wpd, ob, engine_object_data);
 +
 +                                                      /* TODO(fclem): only use caps if they are in the view frustum. */
 +                                                      const bool need_caps = true;
 +                                                      if (need_caps) {
 +                                                              if (is_manifold) {
 +                                                                      grp = DRW_shgroup_create(e_data.shadow_caps_manifold_sh, psl->shadow_depth_fail_caps_mani_pass);
 +                                                              }
 +                                                              else {
 +                                                                      grp = DRW_shgroup_create(e_data.shadow_caps_sh, psl->shadow_depth_fail_caps_pass);
 +                                                              }
 +                                                              DRW_shgroup_uniform_vec3(grp, "lightDirection", engine_object_data->shadow_dir, 1);
 +                                                              DRW_shgroup_uniform_float_copy(grp, "lightDistance", extrude_distance);
 +                                                              DRW_shgroup_call_add(grp, DRW_cache_object_surface_get(ob), ob->obmat);
 +                                                      }
 +
 +                                                      if (is_manifold) {
 +                                                              grp = DRW_shgroup_create(e_data.shadow_fail_manifold_sh, psl->shadow_depth_fail_mani_pass);
 +                                                      }
 +                                                      else {
 +                                                              grp = DRW_shgroup_create(e_data.shadow_fail_sh, psl->shadow_depth_fail_pass);
 +                                                      }
 +                                                      DRW_shgroup_uniform_vec3(grp, "lightDirection", engine_object_data->shadow_dir, 1);
 +                                                      DRW_shgroup_uniform_float_copy(grp, "lightDistance", extrude_distance);
 +                                                      DRW_shgroup_call_add(grp, geom_shadow, ob->obmat);
 +#ifdef DEBUG_SHADOW_VOLUME
 +                                                      DRW_debug_bbox(&engine_object_data->shadow_bbox, (float[4]){0.0f, 1.0f, 0.0f, 1.0f});
 +#endif
 +                                              }
 +                                      }
 +                              }
 +                      }
 +              }
 +      }
 +}
 +
 +void workbench_deferred_cache_finish(WORKBENCH_Data *UNUSED(vedata))
 +{
 +}
 +
 +void workbench_deferred_draw_background(WORKBENCH_Data *vedata)
 +{
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_FramebufferList *fbl = vedata->fbl;
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +      const float clear_depth = 1.0f;
 +      const float clear_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
 +      uint clear_stencil = 0x00;
 +
 +      DRW_stats_group_start("Clear Background");
 +      GPU_framebuffer_bind(fbl->prepass_fb);
 +      int clear_bits = GPU_DEPTH_BIT | GPU_COLOR_BIT;
 +      SET_FLAG_FROM_TEST(clear_bits, SHADOW_ENABLED(wpd), GPU_STENCIL_BIT);
 +      GPU_framebuffer_clear(fbl->prepass_fb, clear_bits, clear_color, clear_depth, clear_stencil);
 +      DRW_stats_group_end();
 +}
 +
 +void workbench_deferred_draw_scene(WORKBENCH_Data *vedata)
 +{
 +      WORKBENCH_PassList *psl = vedata->psl;
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_FramebufferList *fbl = vedata->fbl;
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +      DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
 +
 +      if (TAA_ENABLED(wpd)) {
 +              workbench_taa_draw_scene_start(vedata);
 +      }
 +
 +      /* clear in background */
 +      GPU_framebuffer_bind(fbl->prepass_fb);
 +      DRW_draw_pass(psl->prepass_pass);
 +      DRW_draw_pass(psl->prepass_hair_pass);
 +
 +      if (GHOST_ENABLED(psl)) {
 +              /* meh, late init to not request a depth buffer we won't use. */
 +              workbench_setup_ghost_framebuffer(fbl);
 +
 +              GPU_framebuffer_bind(fbl->ghost_prepass_fb);
 +              GPU_framebuffer_clear_depth(fbl->ghost_prepass_fb, 1.0f);
 +              DRW_draw_pass(psl->ghost_prepass_pass);
 +              DRW_draw_pass(psl->ghost_prepass_hair_pass);
 +
 +              GPU_framebuffer_bind(dfbl->depth_only_fb);
 +              DRW_draw_pass(psl->ghost_resolve_pass);
 +      }
 +
 +      if (CAVITY_ENABLED(wpd)) {
 +              GPU_framebuffer_bind(fbl->cavity_fb);
 +              DRW_draw_pass(psl->cavity_pass);
 +      }
 +
 +      if (SHADOW_ENABLED(wpd)) {
 +#ifdef DEBUG_SHADOW_VOLUME
 +              GPU_framebuffer_bind(fbl->composite_fb);
 +              DRW_draw_pass(psl->composite_pass);
 +#else
 +              GPU_framebuffer_bind(dfbl->depth_only_fb);
 +#endif
 +              DRW_draw_pass(psl->shadow_depth_pass_pass);
 +              DRW_draw_pass(psl->shadow_depth_pass_mani_pass);
 +              DRW_draw_pass(psl->shadow_depth_fail_pass);
 +              DRW_draw_pass(psl->shadow_depth_fail_mani_pass);
 +              DRW_draw_pass(psl->shadow_depth_fail_caps_pass);
 +              DRW_draw_pass(psl->shadow_depth_fail_caps_mani_pass);
 +
 +              if (GHOST_ENABLED(psl)) {
 +                      /* We need to set the stencil buffer to 0 where Ghost objects
 +                       * else they will get shadow and even badly shadowed. */
 +                      DRW_pass_state_set(psl->ghost_prepass_pass, DRW_STATE_DEPTH_EQUAL | DRW_STATE_WRITE_STENCIL);
 +                      DRW_pass_state_set(psl->ghost_prepass_hair_pass, DRW_STATE_DEPTH_EQUAL | DRW_STATE_WRITE_STENCIL);
 +
 +                      DRW_draw_pass(psl->ghost_prepass_pass);
 +                      DRW_draw_pass(psl->ghost_prepass_hair_pass);
 +              }
 +#ifndef DEBUG_SHADOW_VOLUME
 +              GPU_framebuffer_bind(fbl->composite_fb);
 +              DRW_draw_pass(psl->composite_pass);
 +              DRW_draw_pass(psl->composite_shadow_pass);
 +#endif
 +      }
 +      else {
 +              GPU_framebuffer_bind(fbl->composite_fb);
 +              DRW_draw_pass(psl->composite_pass);
 +      }
 +
 +      /* TODO(fclem): only enable when needed (when there is overlays). */
 +      if (GHOST_ENABLED(psl)) {
 +              /* In order to not draw on top of ghost objects, we clear the stencil
 +               * to 0xFF and the ghost object to 0x00 and only draw overlays on top if
 +               * stencil is not 0. */
 +              GPU_framebuffer_bind(dfbl->depth_only_fb);
 +              GPU_framebuffer_clear_stencil(dfbl->depth_only_fb, 0xFF);
 +
 +              DRW_pass_state_set(psl->ghost_prepass_pass, DRW_STATE_DEPTH_EQUAL | DRW_STATE_WRITE_STENCIL);
 +              DRW_pass_state_set(psl->ghost_prepass_hair_pass, DRW_STATE_DEPTH_EQUAL | DRW_STATE_WRITE_STENCIL);
 +
 +              DRW_draw_pass(psl->ghost_prepass_pass);
 +              DRW_draw_pass(psl->ghost_prepass_hair_pass);
 +      }
 +
 +      if (wpd->volumes_do) {
 +              GPU_framebuffer_bind(fbl->volume_fb);
 +              DRW_draw_pass(psl->volume_pass);
 +      }
 +
 +      workbench_aa_draw_pass(vedata, e_data.composite_buffer_tx);
 +}
 +
 +void workbench_deferred_draw_finish(WORKBENCH_Data *vedata)
 +{
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +
 +      workbench_private_data_free(wpd);
 +      workbench_volume_smoke_textures_free(wpd);
 +}
index 410c693689f977152923ff0d730d899c7f3f8b7c,0000000000000000000000000000000000000000..39e5e10ca8fec4a428ce21825b9a91819707ca01
mode 100644,000000..100644
--- /dev/null
@@@ -1,638 -1,0 +1,638 @@@
-       material = BLI_ghash_lookup(wpd->material_hash, SET_UINT_IN_POINTER(hash));
 +/*
 + * Copyright 2016, Blender Foundation.
 + *
 + * 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.
 + *
 + * Contributor(s): Blender Institute
 + *
 + */
 +
 +/** \file workbench_forward.c
 + *  \ingroup draw_engine
 + */
 +
 +#include "workbench_private.h"
 +
 +#include "BIF_gl.h"
 +
 +#include "BLI_alloca.h"
 +#include "BLI_dynstr.h"
 +#include "BLI_utildefines.h"
 +
 +#include "BKE_node.h"
 +#include "BKE_particle.h"
 +#include "BKE_modifier.h"
 +
 +#include "DNA_image_types.h"
 +#include "DNA_mesh_types.h"
 +#include "DNA_modifier_types.h"
 +#include "DNA_node_types.h"
 +
 +#include "ED_uvedit.h"
 +
 +#include "GPU_shader.h"
 +#include "GPU_texture.h"
 +
 +#include "UI_resources.h"
 +
 +/* *********** STATIC *********** */
 +static struct {
 +      struct GPUShader *composite_sh_cache[MAX_SHADERS];
 +      struct GPUShader *transparent_accum_sh_cache[MAX_SHADERS];
 +      struct GPUShader *object_outline_sh;
 +      struct GPUShader *object_outline_texture_sh;
 +      struct GPUShader *object_outline_hair_sh;
 +      struct GPUShader *checker_depth_sh;
 +
 +      struct GPUTexture *object_id_tx; /* ref only, not alloced */
 +      struct GPUTexture *transparent_accum_tx; /* ref only, not alloced */
 +      struct GPUTexture *transparent_revealage_tx; /* ref only, not alloced */
 +      struct GPUTexture *composite_buffer_tx; /* ref only, not alloced */
 +
 +      int next_object_id;
 +      float normal_world_matrix[3][3];
 +} e_data = {{NULL}};
 +
 +/* Shaders */
 +extern char datatoc_common_hair_lib_glsl[];
 +
 +extern char datatoc_workbench_forward_composite_frag_glsl[];
 +extern char datatoc_workbench_forward_depth_frag_glsl[];
 +extern char datatoc_workbench_forward_transparent_accum_frag_glsl[];
 +extern char datatoc_workbench_data_lib_glsl[];
 +extern char datatoc_workbench_background_lib_glsl[];
 +extern char datatoc_workbench_checkerboard_depth_frag_glsl[];
 +extern char datatoc_workbench_object_outline_lib_glsl[];
 +extern char datatoc_workbench_prepass_vert_glsl[];
 +extern char datatoc_workbench_common_lib_glsl[];
 +extern char datatoc_workbench_world_light_lib_glsl[];
 +
 +/* static functions */
 +static char *workbench_build_forward_vert(bool is_hair)
 +{
 +      char *str = NULL;
 +      if (!is_hair) {
 +              return BLI_strdup(datatoc_workbench_prepass_vert_glsl);
 +      }
 +
 +      DynStr *ds = BLI_dynstr_new();
 +
 +      BLI_dynstr_append(ds, datatoc_common_hair_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_prepass_vert_glsl);
 +
 +      str = BLI_dynstr_get_cstring(ds);
 +      BLI_dynstr_free(ds);
 +      return str;
 +}
 +
 +static char *workbench_build_forward_transparent_accum_frag(void)
 +{
 +      char *str = NULL;
 +
 +      DynStr *ds = BLI_dynstr_new();
 +
 +      BLI_dynstr_append(ds, datatoc_workbench_data_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_common_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_world_light_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_forward_transparent_accum_frag_glsl);
 +
 +      str = BLI_dynstr_get_cstring(ds);
 +      BLI_dynstr_free(ds);
 +      return str;
 +}
 +
 +static char *workbench_build_forward_composite_frag(void)
 +{
 +      char *str = NULL;
 +
 +      DynStr *ds = BLI_dynstr_new();
 +
 +      BLI_dynstr_append(ds, datatoc_workbench_data_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_common_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_background_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_object_outline_lib_glsl);
 +      BLI_dynstr_append(ds, datatoc_workbench_forward_composite_frag_glsl);
 +
 +      str = BLI_dynstr_get_cstring(ds);
 +      BLI_dynstr_free(ds);
 +      return str;
 +}
 +
 +static void workbench_init_object_data(DrawData *dd)
 +{
 +      WORKBENCH_ObjectData *data = (WORKBENCH_ObjectData *)dd;
 +      data->object_id = ((e_data.next_object_id++) & 0xff) + 1;
 +}
 +
 +static WORKBENCH_MaterialData *get_or_create_material_data(
 +        WORKBENCH_Data *vedata, Object *ob, Material *mat, Image *ima, int color_type)
 +{
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_PassList *psl = vedata->psl;
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +      WORKBENCH_MaterialData *material;
 +      WORKBENCH_ObjectData *engine_object_data = (WORKBENCH_ObjectData *)DRW_drawdata_ensure(
 +              &ob->id, &draw_engine_workbench_solid, sizeof(WORKBENCH_ObjectData), &workbench_init_object_data, NULL);
 +      WORKBENCH_MaterialData material_template;
 +      DRWShadingGroup *grp;
 +
 +      /* Solid */
 +      workbench_material_update_data(wpd, ob, mat, &material_template);
 +      material_template.object_id = OBJECT_ID_PASS_ENABLED(wpd) ? engine_object_data->object_id : 1;
 +      material_template.color_type = color_type;
 +      material_template.ima = ima;
 +      uint hash = workbench_material_get_hash(&material_template, false);
 +
-               BLI_ghash_insert(wpd->material_hash, SET_UINT_IN_POINTER(hash), material);
++      material = BLI_ghash_lookup(wpd->material_hash, POINTER_FROM_UINT(hash));
 +      if (material == NULL) {
 +              material = MEM_mallocN(sizeof(WORKBENCH_MaterialData), __func__);
 +
 +              /* transparent accum */
 +              grp = DRW_shgroup_create(
 +                      color_type == V3D_SHADING_TEXTURE_COLOR ? wpd->transparent_accum_texture_sh: wpd->transparent_accum_sh,
 +                      psl->transparent_accum_pass);
 +              DRW_shgroup_uniform_block(grp, "world_block", wpd->world_ubo);
 +              DRW_shgroup_uniform_float(grp, "alpha", &wpd->shading.xray_alpha, 1);
 +              DRW_shgroup_uniform_vec4(grp, "viewvecs[0]", (float *)wpd->viewvecs, 3);
 +              workbench_material_set_normal_world_matrix(grp, wpd, e_data.normal_world_matrix);
 +              workbench_material_copy(material, &material_template);
 +              if (STUDIOLIGHT_ORIENTATION_VIEWNORMAL_ENABLED(wpd)) {
 +                      BKE_studiolight_ensure_flag(wpd->studio_light, STUDIOLIGHT_EQUIRECTANGULAR_RADIANCE_GPUTEXTURE);
 +                      DRW_shgroup_uniform_texture(grp, "matcapImage", wpd->studio_light->equirectangular_radiance_gputexture);
 +              }
 +              if (SPECULAR_HIGHLIGHT_ENABLED(wpd) || MATCAP_ENABLED(wpd)) {
 +                      DRW_shgroup_uniform_vec2(grp, "invertedViewportSize", DRW_viewport_invert_size_get(), 1);
 +              }
 +
 +              workbench_material_shgroup_uniform(wpd, grp, material, ob);
 +              material->shgrp = grp;
 +
 +              /* Depth */
 +              if (workbench_material_determine_color_type(wpd, material->ima, ob) == V3D_SHADING_TEXTURE_COLOR) {
 +                      material->shgrp_object_outline = DRW_shgroup_create(
 +                              e_data.object_outline_texture_sh, psl->object_outline_pass);
 +                      GPUTexture *tex = GPU_texture_from_blender(material->ima, NULL, GL_TEXTURE_2D, false, 0.0f);
 +                      DRW_shgroup_uniform_texture(material->shgrp_object_outline, "image", tex);
 +              }
 +              else {
 +                      material->shgrp_object_outline = DRW_shgroup_create(
 +                              e_data.object_outline_sh, psl->object_outline_pass);
 +              }
 +              material->object_id = engine_object_data->object_id;
 +              DRW_shgroup_uniform_int(material->shgrp_object_outline, "object_id", &material->object_id, 1);
++              BLI_ghash_insert(wpd->material_hash, POINTER_FROM_UINT(hash), material);
 +      }
 +      return material;
 +}
 +
 +static void ensure_forward_shaders(WORKBENCH_PrivateData *wpd, int index, bool use_textures, bool is_hair)
 +{
 +      if (e_data.composite_sh_cache[index] == NULL && !use_textures && !is_hair) {
 +              char *defines = workbench_material_build_defines(wpd, use_textures, is_hair);
 +              char *composite_frag = workbench_build_forward_composite_frag();
 +              e_data.composite_sh_cache[index] = DRW_shader_create_fullscreen(composite_frag, defines);
 +              MEM_freeN(composite_frag);
 +              MEM_freeN(defines);
 +      }
 +
 +      if (e_data.transparent_accum_sh_cache[index] == NULL) {
 +              char *defines = workbench_material_build_defines(wpd, use_textures, is_hair);
 +              char *transparent_accum_vert = workbench_build_forward_vert(is_hair);
 +              char *transparent_accum_frag = workbench_build_forward_transparent_accum_frag();
 +              e_data.transparent_accum_sh_cache[index] = DRW_shader_create(
 +                      transparent_accum_vert, NULL,
 +                      transparent_accum_frag, defines);
 +              MEM_freeN(transparent_accum_vert);
 +              MEM_freeN(transparent_accum_frag);
 +              MEM_freeN(defines);
 +      }
 +}
 +
 +static void select_forward_shaders(WORKBENCH_PrivateData *wpd)
 +{
 +      int index_solid = workbench_material_get_shader_index(wpd, false, false);
 +      int index_solid_hair = workbench_material_get_shader_index(wpd, false, true);
 +      int index_texture = workbench_material_get_shader_index(wpd, true, false);
 +      int index_texture_hair = workbench_material_get_shader_index(wpd, true, true);
 +
 +      ensure_forward_shaders(wpd, index_solid, false, false);
 +      ensure_forward_shaders(wpd, index_solid_hair, false, true);
 +      ensure_forward_shaders(wpd, index_texture, true, false);
 +      ensure_forward_shaders(wpd, index_texture_hair, true, true);
 +
 +      wpd->composite_sh = e_data.composite_sh_cache[index_solid];
 +      wpd->transparent_accum_sh = e_data.transparent_accum_sh_cache[index_solid];
 +      wpd->transparent_accum_hair_sh = e_data.transparent_accum_sh_cache[index_solid_hair];
 +      wpd->transparent_accum_texture_sh = e_data.transparent_accum_sh_cache[index_texture];
 +      wpd->transparent_accum_texture_hair_sh = e_data.transparent_accum_sh_cache[index_texture_hair];
 +}
 +
 +/* public functions */
 +void workbench_forward_engine_init(WORKBENCH_Data *vedata)
 +{
 +      WORKBENCH_FramebufferList *fbl = vedata->fbl;
 +      WORKBENCH_PassList *psl = vedata->psl;
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      DefaultTextureList *dtxl = DRW_viewport_texture_list_get();
 +      const DRWContextState *draw_ctx = DRW_context_state_get();
 +      DRWShadingGroup *grp;
 +
 +      if (!stl->g_data) {
 +              /* Alloc transient pointers */
 +              stl->g_data = MEM_callocN(sizeof(*stl->g_data), __func__);
 +      }
 +      if (!stl->effects) {
 +              stl->effects = MEM_callocN(sizeof(*stl->effects), __func__);
 +              workbench_effect_info_init(stl->effects);
 +      }
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +      workbench_private_data_init(wpd);
 +      float light_direction[3];
 +      workbench_private_data_get_light_direction(wpd, light_direction);
 +
 +      if (!e_data.next_object_id) {
 +              e_data.next_object_id = 1;
 +              memset(e_data.composite_sh_cache, 0x00, sizeof(struct GPUShader *) * MAX_SHADERS);
 +              memset(e_data.transparent_accum_sh_cache, 0x00, sizeof(struct GPUShader *) * MAX_SHADERS);
 +
 +              char *defines = workbench_material_build_defines(wpd, false, false);
 +              char *defines_texture = workbench_material_build_defines(wpd, true, false);
 +              char *defines_hair = workbench_material_build_defines(wpd, false, true);
 +              char *forward_vert = workbench_build_forward_vert(false);
 +              char *forward_hair_vert = workbench_build_forward_vert(true);
 +              e_data.object_outline_sh = DRW_shader_create(
 +                      forward_vert, NULL,
 +                      datatoc_workbench_forward_depth_frag_glsl, defines);
 +              e_data.object_outline_texture_sh = DRW_shader_create(
 +                      forward_vert, NULL,
 +                      datatoc_workbench_forward_depth_frag_glsl, defines_texture);
 +              e_data.object_outline_hair_sh = DRW_shader_create(
 +                      forward_hair_vert, NULL,
 +                      datatoc_workbench_forward_depth_frag_glsl, defines_hair);
 +
 +
 +              e_data.checker_depth_sh = DRW_shader_create_fullscreen(
 +                      datatoc_workbench_checkerboard_depth_frag_glsl, NULL);
 +              MEM_freeN(forward_hair_vert);
 +              MEM_freeN(forward_vert);
 +              MEM_freeN(defines);
 +              MEM_freeN(defines_texture);
 +              MEM_freeN(defines_hair);
 +      }
 +      workbench_volume_engine_init();
 +      workbench_fxaa_engine_init();
 +      workbench_taa_engine_init(vedata);
 +
 +      select_forward_shaders(wpd);
 +
 +      const float *viewport_size = DRW_viewport_size_get();
 +      const int size[2] = {(int)viewport_size[0], (int)viewport_size[1]};
 +
 +      e_data.object_id_tx = DRW_texture_pool_query_2D(
 +              size[0], size[1], GPU_R32UI, &draw_engine_workbench_transparent);
 +      e_data.transparent_accum_tx = DRW_texture_pool_query_2D(
 +              size[0], size[1], GPU_RGBA16F, &draw_engine_workbench_transparent);
 +      e_data.transparent_revealage_tx = DRW_texture_pool_query_2D(
 +              size[0], size[1], GPU_R16F, &draw_engine_workbench_transparent);
 +      e_data.composite_buffer_tx = DRW_texture_pool_query_2D(
 +              size[0], size[1], GPU_R11F_G11F_B10F, &draw_engine_workbench_transparent);
 +
 +      GPU_framebuffer_ensure_config(&fbl->object_outline_fb, {
 +              GPU_ATTACHMENT_TEXTURE(dtxl->depth),
 +              GPU_ATTACHMENT_TEXTURE(e_data.object_id_tx),
 +      });
 +      GPU_framebuffer_ensure_config(&fbl->transparent_accum_fb, {
 +              GPU_ATTACHMENT_NONE,
 +              GPU_ATTACHMENT_TEXTURE(e_data.transparent_accum_tx),
 +              GPU_ATTACHMENT_TEXTURE(e_data.transparent_revealage_tx),
 +      });
 +      GPU_framebuffer_ensure_config(&fbl->composite_fb, {
 +              GPU_ATTACHMENT_NONE,
 +              GPU_ATTACHMENT_TEXTURE(e_data.composite_buffer_tx),
 +      });
 +      GPU_framebuffer_ensure_config(&fbl->effect_fb, {
 +              GPU_ATTACHMENT_NONE,
 +              GPU_ATTACHMENT_TEXTURE(e_data.transparent_accum_tx),
 +      });
 +
 +      workbench_volume_cache_init(vedata);
 +      const bool do_cull = (draw_ctx->v3d && (draw_ctx->v3d->flag2 & V3D_BACKFACE_CULLING));
 +      const int cull_state = (do_cull) ? DRW_STATE_CULL_BACK : 0;
 +
 +      /* Transparency Accum */
 +      {
 +              int state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_OIT | cull_state;
 +              psl->transparent_accum_pass = DRW_pass_create("Transparent Accum", state);
 +      }
 +      /* Depth */
 +      {
 +              int state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | cull_state;
 +              psl->object_outline_pass = DRW_pass_create("Object Outline Pass", state);
 +      }
 +      /* Composite */
 +      {
 +              int state = DRW_STATE_WRITE_COLOR;
 +              psl->composite_pass = DRW_pass_create("Composite", state);
 +
 +              grp = DRW_shgroup_create(wpd->composite_sh, psl->composite_pass);
 +              if (OBJECT_ID_PASS_ENABLED(wpd)) {
 +                      DRW_shgroup_uniform_texture_ref(grp, "objectId", &e_data.object_id_tx);
 +              }
 +              DRW_shgroup_uniform_texture_ref(grp, "transparentAccum", &e_data.transparent_accum_tx);
 +              DRW_shgroup_uniform_texture_ref(grp, "transparentRevealage", &e_data.transparent_revealage_tx);
 +              DRW_shgroup_uniform_block(grp, "world_block", wpd->world_ubo);
 +              DRW_shgroup_uniform_vec2(grp, "invertedViewportSize", DRW_viewport_invert_size_get(), 1);
 +              DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
 +      }
 +
 +      {
 +              workbench_aa_create_pass(vedata, &e_data.transparent_accum_tx);
 +      }
 +
 +      /* Checker Depth */
 +      {
 +              int state = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_ALWAYS;
 +              psl->checker_depth_pass = DRW_pass_create("Checker Depth", state);
 +              grp = DRW_shgroup_create(e_data.checker_depth_sh, psl->checker_depth_pass);
 +              DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
 +              DRW_shgroup_uniform_float_copy(grp, "threshold", 0.75f - wpd->shading.xray_alpha * 0.5f);
 +      }
 +}
 +
 +void workbench_forward_engine_free()
 +{
 +      for (int index = 0; index < MAX_SHADERS; index++) {
 +              DRW_SHADER_FREE_SAFE(e_data.composite_sh_cache[index]);
 +              DRW_SHADER_FREE_SAFE(e_data.transparent_accum_sh_cache[index]);
 +      }
 +      DRW_SHADER_FREE_SAFE(e_data.object_outline_sh);
 +      DRW_SHADER_FREE_SAFE(e_data.object_outline_texture_sh);
 +      DRW_SHADER_FREE_SAFE(e_data.object_outline_hair_sh);
 +      DRW_SHADER_FREE_SAFE(e_data.checker_depth_sh);
 +
 +      workbench_volume_engine_free();
 +      workbench_fxaa_engine_free();
 +      workbench_taa_engine_free();
 +}
 +
 +void workbench_forward_cache_init(WORKBENCH_Data *UNUSED(vedata))
 +{
 +}
 +
 +static void workbench_forward_cache_populate_particles(WORKBENCH_Data *vedata, Object *ob)
 +{
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_PassList *psl = vedata->psl;
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +      const DRWContextState *draw_ctx = DRW_context_state_get();
 +
 +      if (ob == draw_ctx->object_edit) {
 +              return;
 +      }
 +      for (ModifierData *md = ob->modifiers.first; md; md = md->next) {
 +              if (md->type != eModifierType_ParticleSystem) {
 +                      continue;
 +              }
 +              ParticleSystem *psys = ((ParticleSystemModifierData *)md)->psys;
 +              if (!psys_check_enabled(ob, psys, false)) {
 +                      continue;
 +              }
 +              if (!DRW_check_psys_visible_within_active_context(ob, psys)) {
 +                      continue;
 +              }
 +              ParticleSettings *part = psys->part;
 +              const int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as;
 +
 +              if (draw_as == PART_DRAW_PATH) {
 +                      Image *image = NULL;
 +                      Material *mat = give_current_material(ob, part->omat);
 +                      ED_object_get_active_image(ob, part->omat, &image, NULL, NULL, NULL);
 +                      int color_type = workbench_material_determine_color_type(wpd, image, ob);
 +                      WORKBENCH_MaterialData *material = get_or_create_material_data(vedata, ob, mat, image, color_type);
 +
 +                      struct GPUShader *shader = (color_type != V3D_SHADING_TEXTURE_COLOR)
 +                                                 ? wpd->transparent_accum_hair_sh
 +                                                 : wpd->transparent_accum_texture_hair_sh;
 +                      DRWShadingGroup *shgrp = DRW_shgroup_hair_create(
 +                                                      ob, psys, md,
 +                                                      psl->transparent_accum_pass,
 +                                                      shader);
 +                      workbench_material_set_normal_world_matrix(shgrp, wpd, e_data.normal_world_matrix);
 +                      DRW_shgroup_uniform_block(shgrp, "world_block", wpd->world_ubo);
 +                      workbench_material_shgroup_uniform(wpd, shgrp, material, ob);
 +                      DRW_shgroup_uniform_vec4(shgrp, "viewvecs[0]", (float *)wpd->viewvecs, 3);
 +                      /* Hairs have lots of layer and can rapidly become the most prominent surface.
 +                       * So lower their alpha artificially. */
 +                      float hair_alpha = wpd->shading.xray_alpha * 0.33f;
 +                      DRW_shgroup_uniform_float_copy(shgrp, "alpha", hair_alpha);
 +                      if (STUDIOLIGHT_ORIENTATION_VIEWNORMAL_ENABLED(wpd)) {
 +                              BKE_studiolight_ensure_flag(wpd->studio_light, STUDIOLIGHT_EQUIRECTANGULAR_RADIANCE_GPUTEXTURE);
 +                              DRW_shgroup_uniform_texture(shgrp, "matcapImage", wpd->studio_light->equirectangular_radiance_gputexture);
 +                      }
 +                      if (SPECULAR_HIGHLIGHT_ENABLED(wpd) || MATCAP_ENABLED(wpd)) {
 +                              DRW_shgroup_uniform_vec2(shgrp, "invertedViewportSize", DRW_viewport_invert_size_get(), 1);
 +                      }
 +                      shgrp = DRW_shgroup_hair_create(ob, psys, md,
 +                                              vedata->psl->object_outline_pass,
 +                                              e_data.object_outline_hair_sh);
 +                      DRW_shgroup_uniform_int(shgrp, "object_id", &material->object_id, 1);
 +              }
 +      }
 +}
 +
 +void workbench_forward_cache_populate(WORKBENCH_Data *vedata, Object *ob)
 +{
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +      const DRWContextState *draw_ctx = DRW_context_state_get();
 +      Scene *scene = draw_ctx->scene;
 +
 +      if (!DRW_object_is_renderable(ob))
 +              return;
 +
 +      if (ob->type == OB_MESH) {
 +              workbench_forward_cache_populate_particles(vedata, ob);
 +      }
 +
 +      ModifierData *md;
 +      if (((ob->base_flag & BASE_FROMDUPLI) == 0) &&
 +          (md = modifiers_findByType(ob, eModifierType_Smoke)) &&
 +          (modifier_isEnabled(scene, md, eModifierMode_Realtime)) &&
 +          (((SmokeModifierData *)md)->domain != NULL))
 +      {
 +              workbench_volume_cache_populate(vedata, scene, ob, md);
 +              return; /* Do not draw solid in this case. */
 +      }
 +
 +      if (!DRW_check_object_visible_within_active_context(ob)) {
 +              return;
 +      }
 +
 +      WORKBENCH_MaterialData *material;
 +      if (ELEM(ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_MBALL)) {
 +              const bool is_active = (ob == draw_ctx->obact);
 +              const bool is_sculpt_mode = is_active && (draw_ctx->object_mode & OB_MODE_SCULPT) != 0;
 +              bool is_drawn = false;
 +
 +              if (!is_sculpt_mode && TEXTURE_DRAWING_ENABLED(wpd) && ELEM(ob->type, OB_MESH)) {
 +                      const Mesh *me = ob->data;
 +                      if (me->mloopuv) {
 +                              const int materials_len = MAX2(1, (is_sculpt_mode ? 1 : ob->totcol));
 +                              struct GPUMaterial **gpumat_array = BLI_array_alloca(gpumat_array, materials_len);
 +                              struct GPUBatch **geom_array = me->totcol ? DRW_cache_mesh_surface_texpaint_get(ob) : NULL;
 +                              if (materials_len > 0 && geom_array) {
 +                                      for (int i = 0; i < materials_len; i++) {
 +                                              if (geom_array[i] == NULL) {
 +                                                      continue;
 +                                              }
 +
 +                                              Material *mat = give_current_material(ob, i + 1);
 +                                              Image *image;
 +                                              ED_object_get_active_image(ob, i + 1, &image, NULL, NULL, NULL);
 +                                              /* use OB_SOLID when no texture could be determined */
 +
 +                                              int color_type = wpd->shading.color_type;
 +                                              if (color_type == V3D_SHADING_TEXTURE_COLOR) {
 +                                                      /* use OB_SOLID when no texture could be determined */
 +                                                      if (image == NULL) {
 +                                                              color_type = V3D_SHADING_MATERIAL_COLOR;
 +                                                      }
 +                                              }
 +
 +                                              material = get_or_create_material_data(vedata, ob, mat, image, color_type);
 +                                              DRW_shgroup_call_object_add(material->shgrp_object_outline, geom_array[i], ob);
 +                                              DRW_shgroup_call_object_add(material->shgrp, geom_array[i], ob);
 +                                      }
 +                                      is_drawn = true;
 +                              }
 +                      }
 +              }
 +
 +              /* Fallback from not drawn OB_TEXTURE mode or just OB_SOLID mode */
 +              if (!is_drawn) {
 +                      if (ELEM(wpd->shading.color_type, V3D_SHADING_SINGLE_COLOR, V3D_SHADING_RANDOM_COLOR)) {
 +                              /* No material split needed */
 +                              struct GPUBatch *geom = DRW_cache_object_surface_get(ob);
 +                              if (geom) {
 +                                      material = get_or_create_material_data(vedata, ob, NULL, NULL, wpd->shading.color_type);
 +                                      if (is_sculpt_mode) {
 +                                              DRW_shgroup_call_sculpt_add(material->shgrp_object_outline, ob, ob->obmat);
 +                                              DRW_shgroup_call_sculpt_add(material->shgrp, ob, ob->obmat);
 +                                      }
 +                                      else {
 +                                              DRW_shgroup_call_object_add(material->shgrp_object_outline, geom, ob);
 +                                              DRW_shgroup_call_object_add(material->shgrp, geom, ob);
 +                                      }
 +                              }
 +                      }
 +                      else {
 +                              const int materials_len = MAX2(1, (is_sculpt_mode ? 1 : ob->totcol));
 +                              struct GPUMaterial **gpumat_array = BLI_array_alloca(gpumat_array, materials_len);
 +                              for (int i = 0; i < materials_len; i++) {
 +                                      gpumat_array[i] = NULL;
 +                              }
 +
 +                              struct GPUBatch **mat_geom = DRW_cache_object_surface_material_get(
 +                                      ob, gpumat_array, materials_len, NULL, NULL, NULL);
 +                              if (mat_geom) {
 +                                      for (int i = 0; i < materials_len; ++i) {
 +                                              if (mat_geom[i] == NULL) {
 +                                                      continue;
 +                                              }
 +
 +                                              Material *mat = give_current_material(ob, i + 1);
 +                                              material = get_or_create_material_data(vedata, ob, mat, NULL, V3D_SHADING_MATERIAL_COLOR);
 +                                              if (is_sculpt_mode) {
 +                                                      DRW_shgroup_call_sculpt_add(material->shgrp_object_outline, ob, ob->obmat);
 +                                                      DRW_shgroup_call_sculpt_add(material->shgrp, ob, ob->obmat);
 +                                              }
 +                                              else {
 +                                                      DRW_shgroup_call_object_add(material->shgrp_object_outline, mat_geom[i], ob);
 +                                                      DRW_shgroup_call_object_add(material->shgrp, mat_geom[i], ob);
 +                                              }
 +                                      }
 +                              }
 +                      }
 +              }
 +      }
 +}
 +
 +void workbench_forward_cache_finish(WORKBENCH_Data *UNUSED(vedata))
 +{
 +}
 +
 +void workbench_forward_draw_background(WORKBENCH_Data *UNUSED(vedata))
 +{
 +      const float clear_depth = 1.0f;
 +      DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
 +      DRW_stats_group_start("Clear depth");
 +      GPU_framebuffer_bind(dfbl->default_fb);
 +      GPU_framebuffer_clear_depth(dfbl->default_fb, clear_depth);
 +      DRW_stats_group_end();
 +}
 +
 +void workbench_forward_draw_scene(WORKBENCH_Data *vedata)
 +{
 +      WORKBENCH_PassList *psl = vedata->psl;
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_FramebufferList *fbl = vedata->fbl;
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +      DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
 +
 +      if (TAA_ENABLED(wpd)) {
 +              workbench_taa_draw_scene_start(vedata);
 +      }
 +
 +      /* Write Depth + Object ID */
 +      const float clear_outline[4] = {0.0f};
 +      GPU_framebuffer_bind(fbl->object_outline_fb);
 +      GPU_framebuffer_clear_color(fbl->object_outline_fb, clear_outline);
 +      DRW_draw_pass(psl->object_outline_pass);
 +
 +      if (wpd->shading.xray_alpha > 0.0) {
 +              const float clear_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
 +              GPU_framebuffer_bind(fbl->transparent_accum_fb);
 +              GPU_framebuffer_clear_color(fbl->transparent_accum_fb, clear_color);
 +              DRW_draw_pass(psl->transparent_accum_pass);
 +      }
 +      else {
 +              /* TODO(fclem): this is unecessary and takes up perf.
 +               * Better change the composite frag shader to not use the tx. */
 +              const float clear_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
 +              GPU_framebuffer_bind(fbl->transparent_accum_fb);
 +              GPU_framebuffer_clear_color(fbl->transparent_accum_fb, clear_color);
 +      }
 +
 +      /* Composite */
 +      GPU_framebuffer_bind(fbl->composite_fb);
 +      DRW_draw_pass(psl->composite_pass);
 +      DRW_draw_pass(psl->volume_pass);
 +
 +      /* Color correct and Anti aliasing */
 +      workbench_aa_draw_pass(vedata, e_data.composite_buffer_tx);
 +
 +      /* Apply checker pattern */
 +      GPU_framebuffer_bind(dfbl->depth_only_fb);
 +      DRW_draw_pass(psl->checker_depth_pass);
 +}
 +
 +void workbench_forward_draw_finish(WORKBENCH_Data *vedata)
 +{
 +      WORKBENCH_StorageList *stl = vedata->stl;
 +      WORKBENCH_PrivateData *wpd = stl->g_data;
 +
 +      workbench_private_data_free(wpd);
 +      workbench_volume_smoke_textures_free(wpd);
 +}
index f0f83d76bc2d4ede2a2f5ca354835a9b1f87ac20,0000000000000000000000000000000000000000..4bd9accec4ba8ec64ca889ac926f59541cdf7ff2
mode 100644,000000..100644
--- /dev/null
@@@ -1,4468 -1,0 +1,4468 @@@
-                               int v_data = GET_INT_FROM_POINTER(*pval);
 +/*
 + * ***** 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) 2017 by Blender Foundation.
 + * All rights reserved.
 + *
 + * Contributor(s): Blender Foundation, Mike Erwin, Dalai Felinto
 + *
 + * ***** END GPL LICENSE BLOCK *****
 + */
 +
 +/** \file draw_cache_impl_mesh.c
 + *  \ingroup draw
 + *
 + * \brief Mesh API for render engines
 + */
 +
 +#include "MEM_guardedalloc.h"
 +
 +#include "BLI_utildefines.h"
 +#include "BLI_math_vector.h"
 +#include "BLI_math_bits.h"
 +#include "BLI_string.h"
 +#include "BLI_alloca.h"
 +#include "BLI_edgehash.h"
 +
 +#include "DNA_mesh_types.h"
 +#include "DNA_meshdata_types.h"
 +#include "DNA_object_types.h"
 +
 +#include "BKE_customdata.h"
 +#include "BKE_deform.h"
 +#include "BKE_editmesh.h"
 +#include "BKE_editmesh_tangent.h"
 +#include "BKE_mesh.h"
 +#include "BKE_mesh_tangent.h"
 +#include "BKE_colorband.h"
 +
 +#include "bmesh.h"
 +
 +#include "GPU_batch.h"
 +#include "GPU_draw.h"
 +#include "GPU_material.h"
 +#include "GPU_texture.h"
 +
 +#include "DRW_render.h"
 +
 +#include "draw_cache_impl.h"  /* own include */
 +
 +static void mesh_batch_cache_clear(Mesh *me);
 +
 +/* ---------------------------------------------------------------------- */
 +
 +/** \name Mesh/BMesh Interface (direct access to basic data).
 + * \{ */
 +
 +static int mesh_render_verts_len_get(Mesh *me)
 +{
 +      return me->edit_btmesh ? me->edit_btmesh->bm->totvert : me->totvert;
 +}
 +
 +static int mesh_render_edges_len_get(Mesh *me)
 +{
 +      return me->edit_btmesh ? me->edit_btmesh->bm->totedge : me->totedge;
 +}
 +
 +static int mesh_render_looptri_len_get(Mesh *me)
 +{
 +      return me->edit_btmesh ? me->edit_btmesh->tottri : poly_to_tri_count(me->totpoly, me->totloop);
 +}
 +
 +static int mesh_render_polys_len_get(Mesh *me)
 +{
 +      return me->edit_btmesh ? me->edit_btmesh->bm->totface : me->totpoly;
 +}
 +
 +static int mesh_render_mat_len_get(Mesh *me)
 +{
 +      return MAX2(1, me->totcol);
 +}
 +
 +static int UNUSED_FUNCTION(mesh_render_loops_len_get)(Mesh *me)
 +{
 +      return me->edit_btmesh ? me->edit_btmesh->bm->totloop : me->totloop;
 +}
 +
 +/** \} */
 +
 +
 +/* ---------------------------------------------------------------------- */
 +
 +/** \name Mesh/BMesh Interface (indirect, partially cached access to complex data).
 + * \{ */
 +
 +typedef struct EdgeAdjacentPolys {
 +      int count;
 +      int face_index[2];
 +} EdgeAdjacentPolys;
 +
 +typedef struct EdgeAdjacentVerts {
 +      int vert_index[2]; /* -1 if none */
 +} EdgeAdjacentVerts;
 +
 +typedef struct EdgeDrawAttr {
 +      uchar v_flag;
 +      uchar e_flag;
 +      uchar crease;
 +      uchar bweight;
 +} EdgeDrawAttr;
 +
 +typedef struct MeshRenderData {
 +      int types;
 +
 +      int vert_len;
 +      int edge_len;
 +      int tri_len;
 +      int loop_len;
 +      int poly_len;
 +      int mat_len;
 +      int loose_vert_len;
 +      int loose_edge_len;
 +
 +      BMEditMesh *edit_bmesh;
 +      struct EditMeshData *edit_data;
 +
 +      MVert *mvert;
 +      MEdge *medge;
 +      MLoop *mloop;
 +      MPoly *mpoly;
 +      float (*orco)[3];  /* vertex coordinates normalized to bounding box */
 +      bool is_orco_allocated;
 +      MDeformVert *dvert;
 +      MLoopUV *mloopuv;
 +      MLoopCol *mloopcol;
 +      float (*loop_normals)[3];
 +
 +      /* CustomData 'cd' cache for efficient access. */
 +      struct {
 +              struct {
 +                      MLoopUV **uv;
 +                      int       uv_len;
 +                      int       uv_active;
 +
 +                      MLoopCol **vcol;
 +                      int        vcol_len;
 +                      int        vcol_active;
 +
 +                      float (**tangent)[4];
 +                      int      tangent_len;
 +                      int      tangent_active;
 +
 +                      bool *auto_vcol;
 +              } layers;
 +
 +              /* Custom-data offsets (only needed for BMesh access) */
 +              struct {
 +                      int crease;
 +                      int bweight;
 +                      int *uv;
 +                      int *vcol;
 +              } offset;
 +
 +              struct {
 +                      char (*auto_mix)[32];
 +                      char (*uv)[32];
 +                      char (*vcol)[32];
 +                      char (*tangent)[32];
 +              } uuid;
 +
 +              /* for certain cases we need an output loop-data storage (bmesh tangents) */
 +              struct {
 +                      CustomData ldata;
 +                      /* grr, special case variable (use in place of 'dm->tangent_mask') */
 +                      short tangent_mask;
 +              } output;
 +      } cd;
 +
 +      BMVert *eve_act;
 +      BMEdge *eed_act;
 +      BMFace *efa_act;
 +
 +      /* Data created on-demand (usually not for bmesh-based data). */
 +      EdgeAdjacentPolys *edges_adjacent_polys;
 +      MLoopTri *mlooptri;
 +      int *loose_edges;
 +      int *loose_verts;
 +
 +      float (*poly_normals)[3];
 +      float (*vert_weight_color)[3];
 +      char (*vert_color)[3];
 +      GPUPackedNormal *poly_normals_pack;
 +      GPUPackedNormal *vert_normals_pack;
 +      bool *edge_select_bool;
 +} MeshRenderData;
 +
 +enum {
 +      MR_DATATYPE_VERT       = 1 << 0,
 +      MR_DATATYPE_EDGE       = 1 << 1,
 +      MR_DATATYPE_LOOPTRI    = 1 << 2,
 +      MR_DATATYPE_LOOP       = 1 << 3,
 +      MR_DATATYPE_POLY       = 1 << 4,
 +      MR_DATATYPE_OVERLAY    = 1 << 5,
 +      MR_DATATYPE_SHADING    = 1 << 6,
 +      MR_DATATYPE_DVERT      = 1 << 7,
 +      MR_DATATYPE_LOOPCOL    = 1 << 8,
 +      MR_DATATYPE_LOOPUV     = 1 << 9,
 +};
 +
 +/**
 + * These functions look like they would be slow but they will typically return true on the first iteration.
 + * Only false when all attached elements are hidden.
 + */
 +static bool bm_vert_has_visible_edge(const BMVert *v)
 +{
 +      const BMEdge *e_iter, *e_first;
 +
 +      e_iter = e_first = v->e;
 +      do {
 +              if (!BM_elem_flag_test(e_iter, BM_ELEM_HIDDEN)) {
 +                      return true;
 +              }
 +      } while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v)) != e_first);
 +      return false;
 +}
 +
 +static bool bm_edge_has_visible_face(const BMEdge *e)
 +{
 +      const BMLoop *l_iter, *l_first;
 +      l_iter = l_first = e->l;
 +      do {
 +              if (!BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) {
 +                      return true;
 +              }
 +      } while ((l_iter = l_iter->radial_next) != l_first);
 +      return false;
 +}
 +
 +
 +static void mesh_cd_calc_used_gpu_layers(
 +        CustomData *UNUSED(cd_vdata), uchar cd_vused[CD_NUMTYPES],
 +        CustomData *cd_ldata, ushort cd_lused[CD_NUMTYPES],
 +        struct GPUMaterial **gpumat_array, int gpumat_array_len)
 +{
 +      /* See: DM_vertex_attributes_from_gpu for similar logic */
 +      GPUVertexAttribs gattribs = {{{0}}};
 +
 +      for (int i = 0; i < gpumat_array_len; i++) {
 +              GPUMaterial *gpumat = gpumat_array[i];
 +              if (gpumat) {
 +                      GPU_material_vertex_attributes(gpumat, &gattribs);
 +                      for (int j = 0; j < gattribs.totlayer; j++) {
 +                              const char *name = gattribs.layer[j].name;
 +                              int type = gattribs.layer[j].type;
 +                              int layer = -1;
 +
 +                              if (type == CD_AUTO_FROM_NAME) {
 +                                      /* We need to deduct what exact layer is used.
 +                                       *
 +                                       * We do it based on the specified name.
 +                                       */
 +                                      if (name[0] != '\0') {
 +                                              layer = CustomData_get_named_layer(cd_ldata, CD_MLOOPUV, name);
 +                                              type = CD_MTFACE;
 +
 +                                              if (layer == -1) {
 +                                                      layer = CustomData_get_named_layer(cd_ldata, CD_MLOOPCOL, name);
 +                                                      type = CD_MCOL;
 +                                              }
 +#if 0                                 /* Tangents are always from UV's - this will never happen. */
 +                                              if (layer == -1) {
 +                                                      layer = CustomData_get_named_layer(cd_ldata, CD_TANGENT, name);
 +                                                      type = CD_TANGENT;
 +                                              }
 +#endif
 +                                              if (layer == -1) {
 +                                                      continue;
 +                                              }
 +                                      }
 +                                      else {
 +                                              /* Fall back to the UV layer, which matches old behavior. */
 +                                              type = CD_MTFACE;
 +                                      }
 +                              }
 +
 +                              switch (type) {
 +                                      case CD_MTFACE:
 +                                      {
 +                                              if (layer == -1) {
 +                                                      layer = (name[0] != '\0') ?
 +                                                              CustomData_get_named_layer(cd_ldata, CD_MLOOPUV, name) :
 +                                                              CustomData_get_active_layer(cd_ldata, CD_MLOOPUV);
 +                                              }
 +                                              if (layer != -1) {
 +                                                      cd_lused[CD_MLOOPUV] |= (1 << layer);
 +                                              }
 +                                              break;
 +                                      }
 +                                      case CD_TANGENT:
 +                                      {
 +                                              if (layer == -1) {
 +                                                      layer = (name[0] != '\0') ?
 +                                                              CustomData_get_named_layer(cd_ldata, CD_MLOOPUV, name) :
 +                                                              CustomData_get_active_layer(cd_ldata, CD_MLOOPUV);
 +                                              }
 +                                              if (layer != -1) {
 +                                                      cd_lused[CD_TANGENT] |= (1 << layer);
 +                                              }
 +                                              else {
 +                                                      /* no UV layers at all => requesting orco */
 +                                                      cd_lused[CD_TANGENT] |= DM_TANGENT_MASK_ORCO;
 +                                                      cd_vused[CD_ORCO] |= 1;
 +                                              }
 +                                              break;
 +                                      }
 +                                      case CD_MCOL:
 +                                      {
 +                                              if (layer == -1) {
 +                                                      layer = (name[0] != '\0') ?
 +                                                              CustomData_get_named_layer(cd_ldata, CD_MLOOPCOL, name) :
 +                                                              CustomData_get_active_layer(cd_ldata, CD_MLOOPCOL);
 +                                              }
 +                                              if (layer != -1) {
 +                                                      cd_lused[CD_MLOOPCOL] |= (1 << layer);
 +                                              }
 +                                              break;
 +                                      }
 +                                      case CD_ORCO:
 +                                      {
 +                                              cd_vused[CD_ORCO] |= 1;
 +                                              break;
 +                                      }
 +                              }
 +                      }
 +              }
 +      }
 +}
 +
 +
 +static void mesh_render_calc_normals_loop_and_poly(const Mesh *me, const float split_angle, MeshRenderData *rdata)
 +{
 +      BLI_assert((me->flag & ME_AUTOSMOOTH) != 0);
 +
 +      int totloop = me->totloop;
 +      int totpoly = me->totpoly;
 +      float (*loop_normals)[3] = MEM_mallocN(sizeof(*loop_normals) * totloop, __func__);
 +      float (*poly_normals)[3] = MEM_mallocN(sizeof(*poly_normals) * totpoly, __func__);
 +      short (*clnors)[2] = CustomData_get_layer(&me->ldata, CD_CUSTOMLOOPNORMAL);
 +
 +      BKE_mesh_calc_normals_poly(
 +              me->mvert, NULL, me->totvert,
 +              me->mloop, me->mpoly, totloop, totpoly, poly_normals, false);
 +
 +      BKE_mesh_normals_loop_split(
 +              me->mvert, me->totvert, me->medge, me->totedge,
 +              me->mloop, loop_normals, totloop, me->mpoly, poly_normals, totpoly,
 +              true, split_angle, NULL, clnors, NULL);
 +
 +      rdata->loop_len = totloop;
 +      rdata->poly_len = totpoly;
 +      rdata->loop_normals = loop_normals;
 +      rdata->poly_normals = poly_normals;
 +}
 +
 +
 +/**
 + * TODO(campbell): 'gpumat_array' may include materials linked to the object.
 + * While not default, object materials should be supported.
 + * Although this only impacts the data thats generated, not the materials that display.
 + */
 +static MeshRenderData *mesh_render_data_create_ex(
 +        Mesh *me, const int types,
 +        struct GPUMaterial **gpumat_array, uint gpumat_array_len)
 +{
 +      MeshRenderData *rdata = MEM_callocN(sizeof(*rdata), __func__);
 +      rdata->types = types;
 +      rdata->mat_len = mesh_render_mat_len_get(me);
 +
 +      CustomData_reset(&rdata->cd.output.ldata);
 +
 +      const bool is_auto_smooth = (me->flag & ME_AUTOSMOOTH) != 0;
 +      const float split_angle = is_auto_smooth ? me->smoothresh : (float)M_PI;
 +
 +      if (me->edit_btmesh) {
 +              BMEditMesh *embm = me->edit_btmesh;
 +              BMesh *bm = embm->bm;
 +
 +              rdata->edit_bmesh = embm;
 +              rdata->edit_data = me->runtime.edit_data;
 +
 +              int bm_ensure_types = 0;
 +              if (types & MR_DATATYPE_VERT) {
 +                      rdata->vert_len = bm->totvert;
 +                      bm_ensure_types |= BM_VERT;
 +              }
 +              if (types & MR_DATATYPE_EDGE) {
 +                      rdata->edge_len = bm->totedge;
 +                      bm_ensure_types |= BM_EDGE;
 +              }
 +              if (types & MR_DATATYPE_LOOPTRI) {
 +                      BKE_editmesh_tessface_calc(embm);
 +                      int tottri = embm->tottri;
 +                      rdata->mlooptri = MEM_mallocN(sizeof(*rdata->mlooptri) * embm->tottri, __func__);
 +                      for (int index = 0; index < tottri ; index ++ ) {
 +                              BMLoop **bmtri = embm->looptris[index];
 +                              MLoopTri *mtri = &rdata->mlooptri[index];
 +                              mtri->tri[0] = BM_elem_index_get(bmtri[0]);
 +                              mtri->tri[1] = BM_elem_index_get(bmtri[1]);
 +                              mtri->tri[2] = BM_elem_index_get(bmtri[2]);
 +                      }
 +                      rdata->tri_len = tottri;
 +              }
 +              if (types & MR_DATATYPE_LOOP) {
 +                      int totloop = bm->totloop;
 +                      if (is_auto_smooth) {
 +                              rdata->loop_normals = MEM_mallocN(sizeof(*rdata->loop_normals) * totloop, __func__);
 +                              int cd_loop_clnors_offset = CustomData_get_layer_index(&bm->ldata, CD_CUSTOMLOOPNORMAL);
 +                              BM_loops_calc_normal_vcos(
 +                                      bm, NULL, NULL, NULL, true, split_angle, rdata->loop_normals, NULL, NULL,
 +                                      cd_loop_clnors_offset, false);
 +                      }
 +                      rdata->loop_len = totloop;
 +                      bm_ensure_types |= BM_LOOP;
 +              }
 +              if (types & MR_DATATYPE_POLY) {
 +                      rdata->poly_len = bm->totface;
 +                      bm_ensure_types |= BM_FACE;
 +              }
 +              if (types & MR_DATATYPE_OVERLAY) {
 +                      rdata->efa_act = BM_mesh_active_face_get(bm, false, true);
 +                      rdata->eed_act = BM_mesh_active_edge_get(bm);
 +                      rdata->eve_act = BM_mesh_active_vert_get(bm);
 +                      rdata->cd.offset.crease = CustomData_get_offset(&bm->edata, CD_CREASE);
 +                      rdata->cd.offset.bweight = CustomData_get_offset(&bm->edata, CD_BWEIGHT);
 +              }
 +              if (types & (MR_DATATYPE_DVERT)) {
 +                      bm_ensure_types |= BM_VERT;
 +              }
 +              if (rdata->edit_data != NULL) {
 +                      bm_ensure_types |= BM_VERT;
 +              }
 +
 +              BM_mesh_elem_index_ensure(bm, bm_ensure_types);
 +              BM_mesh_elem_table_ensure(bm, bm_ensure_types & ~BM_LOOP);
 +              if (types & MR_DATATYPE_OVERLAY) {
 +                      rdata->loose_vert_len = rdata->loose_edge_len = 0;
 +
 +                      int *lverts = rdata->loose_verts = MEM_mallocN(rdata->vert_len * sizeof(int), "Loose Vert");
 +                      int *ledges = rdata->loose_edges = MEM_mallocN(rdata->edge_len * sizeof(int), "Loose Edges");
 +
 +                      {
 +                              BLI_assert((bm->elem_table_dirty & BM_VERT) == 0);
 +                              BMVert **vtable = bm->vtable;
 +                              for (int i = 0; i < bm->totvert; i++) {
 +                                      const BMVert *eve = vtable[i];
 +                                      if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) {
 +                                              /* Loose vert */
 +                                              if (eve->e == NULL || !bm_vert_has_visible_edge(eve)) {
 +                                                      lverts[rdata->loose_vert_len++] = i;
 +                                              }
 +                                      }
 +                              }
 +                      }
 +
 +                      {
 +                              BLI_assert((bm->elem_table_dirty & BM_EDGE) == 0);
 +                              BMEdge **etable = bm->etable;
 +                              for (int i = 0; i < bm->totedge; i++) {
 +                                      const BMEdge *eed = etable[i];
 +                                      if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) {
 +                                              /* Loose edge */
 +                                              if (eed->l == NULL || !bm_edge_has_visible_face(eed)) {
 +                                                      ledges[rdata->loose_edge_len++] = i;
 +                                              }
 +                                      }
 +                              }
 +                      }
 +
 +                      rdata->loose_verts = MEM_reallocN(rdata->loose_verts, rdata->loose_vert_len * sizeof(int));
 +                      rdata->loose_edges = MEM_reallocN(rdata->loose_edges, rdata->loose_edge_len * sizeof(int));
 +              }
 +      }
 +      else {
 +              if (types & (MR_DATATYPE_VERT)) {
 +                      rdata->vert_len = me->totvert;
 +                      rdata->mvert = CustomData_get_layer(&me->vdata, CD_MVERT);
 +              }
 +              if (types & (MR_DATATYPE_EDGE)) {
 +                      rdata->edge_len = me->totedge;
 +                      rdata->medge = CustomData_get_layer(&me->edata, CD_MEDGE);
 +              }
 +              if (types & MR_DATATYPE_LOOPTRI) {
 +                      const int tri_len = rdata->tri_len = poly_to_tri_count(me->totpoly, me->totloop);
 +                      rdata->mlooptri = MEM_mallocN(sizeof(*rdata->mlooptri) * tri_len, __func__);
 +                      BKE_mesh_recalc_looptri(me->mloop, me->mpoly, me->mvert, me->totloop, me->totpoly, rdata->mlooptri);
 +              }
 +              if (types & MR_DATATYPE_LOOP) {
 +                      rdata->loop_len = me->totloop;
 +                      rdata->mloop = CustomData_get_layer(&me->ldata, CD_MLOOP);
 +
 +                      if (is_auto_smooth) {
 +                              mesh_render_calc_normals_loop_and_poly(me, split_angle, rdata);
 +                      }
 +              }
 +              if (types & MR_DATATYPE_POLY) {
 +                      rdata->poly_len = me->totpoly;
 +                      rdata->mpoly = CustomData_get_layer(&me->pdata, CD_MPOLY);
 +              }
 +              if (types & MR_DATATYPE_DVERT) {
 +                      rdata->vert_len = me->totvert;
 +                      rdata->dvert = CustomData_get_layer(&me->vdata, CD_MDEFORMVERT);
 +              }
 +              if (types & MR_DATATYPE_LOOPCOL) {
 +                      rdata->loop_len = me->totloop;
 +                      rdata->mloopcol = CustomData_get_layer(&me->ldata, CD_MLOOPCOL);
 +              }
 +              if (types & MR_DATATYPE_LOOPUV) {
 +                      rdata->loop_len = me->totloop;
 +                      rdata->mloopuv = CustomData_get_layer(&me->ldata, CD_MLOOPUV);
 +              }
 +      }
 +
 +      if (types & MR_DATATYPE_SHADING) {
 +              CustomData *cd_vdata, *cd_ldata;
 +
 +              if (me->edit_btmesh) {
 +                      BMesh *bm = me->edit_btmesh->bm;
 +                      cd_vdata = &bm->vdata;
 +                      cd_ldata = &bm->ldata;
 +              }
 +              else {
 +                      cd_vdata = &me->vdata;
 +                      cd_ldata = &me->ldata;
 +              }
 +
 +              /* Add edge/poly if we need them */
 +              uchar cd_vused[CD_NUMTYPES] = {0};
 +              ushort cd_lused[CD_NUMTYPES] = {0};
 +
 +              mesh_cd_calc_used_gpu_layers(
 +                      cd_vdata, cd_vused,
 +                      cd_ldata, cd_lused,
 +                      gpumat_array, gpumat_array_len);
 +
 +
 +              rdata->cd.layers.uv_active = CustomData_get_active_layer(cd_ldata, CD_MLOOPUV);
 +              rdata->cd.layers.vcol_active = CustomData_get_active_layer(cd_ldata, CD_MLOOPCOL);
 +              rdata->cd.layers.tangent_active = rdata->cd.layers.uv_active;
 +
 +#define CD_VALIDATE_ACTIVE_LAYER(active_index, used) \
 +              if ((active_index != -1) && (used & (1 << active_index)) == 0) { \
 +                      active_index = -1; \
 +              } ((void)0)
 +
 +              CD_VALIDATE_ACTIVE_LAYER(rdata->cd.layers.uv_active, cd_lused[CD_MLOOPUV]);
 +              CD_VALIDATE_ACTIVE_LAYER(rdata->cd.layers.tangent_active, cd_lused[CD_TANGENT]);
 +              CD_VALIDATE_ACTIVE_LAYER(rdata->cd.layers.vcol_active, cd_lused[CD_MLOOPCOL]);
 +
 +#undef CD_VALIDATE_ACTIVE_LAYER
 +
 +              rdata->is_orco_allocated = false;
 +              if (cd_vused[CD_ORCO] & 1) {
 +                      rdata->orco = CustomData_get_layer(cd_vdata, CD_ORCO);
 +                      /* If orco is not available compute it ourselves */
 +                      if (!rdata->orco) {
 +                              rdata->is_orco_allocated = true;
 +                              if (me->edit_btmesh) {
 +                                      BMesh *bm = me->edit_btmesh->bm;
 +                                      rdata->orco = MEM_mallocN(sizeof(*rdata->orco) * rdata->vert_len, "orco mesh");
 +                                      BLI_assert((bm->elem_table_dirty & BM_VERT) == 0);
 +                                      BMVert **vtable = bm->vtable;
 +                                      for (int i = 0; i < bm->totvert; i++) {
 +                                              copy_v3_v3(rdata->orco[i], vtable[i]->co);
 +                                      }
 +                                      BKE_mesh_orco_verts_transform(me, rdata->orco, rdata->vert_len, 0);
 +                              }
 +                              else {
 +                                      rdata->orco = MEM_mallocN(sizeof(*rdata->orco) * rdata->vert_len, "orco mesh");
 +                                      MVert *mvert = rdata->mvert;
 +                                      for (int a = 0; a < rdata->vert_len; a++, mvert++) {
 +                                              copy_v3_v3(rdata->orco[a], mvert->co);
 +                                      }
 +                                      BKE_mesh_orco_verts_transform(me, rdata->orco, rdata->vert_len, 0);
 +                              }
 +                      }
 +              }
 +              else {
 +                      rdata->orco = NULL;
 +              }
 +
 +              /* don't access mesh directly, instead use vars taken from BMesh or Mesh */
 +#define me DONT_USE_THIS
 +#ifdef  me /* quiet warning */
 +#endif
 +              struct {
 +                      uint uv_len;
 +                      uint vcol_len;
 +              } cd_layers_src = {
 +                      .uv_len = CustomData_number_of_layers(cd_ldata, CD_MLOOPUV),
 +                      .vcol_len = CustomData_number_of_layers(cd_ldata, CD_MLOOPCOL),
 +              };
 +
 +              rdata->cd.layers.uv_len = count_bits_i(cd_lused[CD_MLOOPUV]);
 +              rdata->cd.layers.tangent_len = count_bits_i(cd_lused[CD_TANGENT]);
 +              rdata->cd.layers.vcol_len = count_bits_i(cd_lused[CD_MLOOPCOL]);
 +
 +              rdata->cd.layers.uv = MEM_mallocN(sizeof(*rdata->cd.layers.uv) * rdata->cd.layers.uv_len, __func__);
 +              rdata->cd.layers.vcol = MEM_mallocN(sizeof(*rdata->cd.layers.vcol) * rdata->cd.layers.vcol_len, __func__);
 +              rdata->cd.layers.tangent = MEM_mallocN(sizeof(*rdata->cd.layers.tangent) * rdata->cd.layers.tangent_len, __func__);
 +
 +              rdata->cd.uuid.uv = MEM_mallocN(sizeof(*rdata->cd.uuid.uv) * rdata->cd.layers.uv_len, __func__);
 +              rdata->cd.uuid.vcol = MEM_mallocN(sizeof(*rdata->cd.uuid.vcol) * rdata->cd.layers.vcol_len, __func__);
 +              rdata->cd.uuid.tangent = MEM_mallocN(sizeof(*rdata->cd.uuid.tangent) * rdata->cd.layers.tangent_len, __func__);
 +
 +              rdata->cd.offset.uv = MEM_mallocN(sizeof(*rdata->cd.offset.uv) * rdata->cd.layers.uv_len, __func__);
 +              rdata->cd.offset.vcol = MEM_mallocN(sizeof(*rdata->cd.offset.vcol) * rdata->cd.layers.vcol_len, __func__);
 +
 +              /* Allocate max */
 +              rdata->cd.layers.auto_vcol = MEM_callocN(
 +                      sizeof(*rdata->cd.layers.auto_vcol) * rdata->cd.layers.vcol_len, __func__);
 +              rdata->cd.uuid.auto_mix = MEM_mallocN(
 +                      sizeof(*rdata->cd.uuid.auto_mix) * (rdata->cd.layers.vcol_len + rdata->cd.layers.uv_len), __func__);
 +
 +              /* XXX FIXME XXX */
 +              /* We use a hash to identify each data layer based on its name.
 +               * Gawain then search for this name in the current shader and bind if it exists.
 +               * NOTE : This is prone to hash collision.
 +               * One solution to hash collision would be to format the cd layer name
 +               * to a safe glsl var name, but without name clash.
 +               * NOTE 2 : Replicate changes to code_generate_vertex_new() in gpu_codegen.c */
 +              if (rdata->cd.layers.vcol_len != 0) {
 +                      for (int i_src = 0, i_dst = 0; i_src < cd_layers_src.vcol_len; i_src++, i_dst++) {
 +                              if ((cd_lused[CD_MLOOPCOL] & (1 << i_src)) == 0) {
 +                                      i_dst--;
 +                                      if (rdata->cd.layers.vcol_active >= i_src) {
 +                                              rdata->cd.layers.vcol_active--;
 +                                      }
 +                              }
 +                              else {
 +                                      const char *name = CustomData_get_layer_name(cd_ldata, CD_MLOOPCOL, i_src);
 +                                      uint hash = BLI_ghashutil_strhash_p(name);
 +                                      BLI_snprintf(rdata->cd.uuid.vcol[i_dst], sizeof(*rdata->cd.uuid.vcol), "c%u", hash);
 +                                      rdata->cd.layers.vcol[i_dst] = CustomData_get_layer_n(cd_ldata, CD_MLOOPCOL, i_src);
 +                                      if (rdata->edit_bmesh) {
 +                                              rdata->cd.offset.vcol[i_dst] = CustomData_get_n_offset(
 +                                                      &rdata->edit_bmesh->bm->ldata, CD_MLOOPCOL, i_src);
 +                                      }
 +
 +                                      /* Gather number of auto layers. */
 +                                      /* We only do vcols that are not overridden by uvs */
 +                                      if (CustomData_get_named_layer_index(cd_ldata, CD_MLOOPUV, name) == -1) {
 +                                              BLI_snprintf(
 +                                                      rdata->cd.uuid.auto_mix[rdata->cd.layers.uv_len + i_dst],
 +                                                      sizeof(*rdata->cd.uuid.auto_mix), "a%u", hash);
 +                                              rdata->cd.layers.auto_vcol[i_dst] = true;
 +                                      }
 +                              }
 +                      }
 +              }
 +
 +              /* Start Fresh */
 +              CustomData_free_layers(cd_ldata, CD_TANGENT, rdata->loop_len);
 +              CustomData_free_layers(cd_ldata, CD_MLOOPTANGENT, rdata->loop_len);
 +
 +              if (rdata->cd.layers.uv_len != 0) {
 +                      for (int i_src = 0, i_dst = 0; i_src < cd_layers_src.uv_len; i_src++, i_dst++) {
 +                              if ((cd_lused[CD_MLOOPUV] & (1 << i_src)) == 0) {
 +                                      i_dst--;
 +                                      if (rdata->cd.layers.uv_active >= i_src) {
 +                                              rdata->cd.layers.uv_active--;
 +                                      }
 +                              }
 +                              else {
 +                                      const char *name = CustomData_get_layer_name(cd_ldata, CD_MLOOPUV, i_src);
 +                                      uint hash = BLI_ghashutil_strhash_p(name);
 +
 +                                      BLI_snprintf(rdata->cd.uuid.uv[i_dst], sizeof(*rdata->cd.uuid.uv), "u%u", hash);
 +                                      rdata->cd.layers.uv[i_dst] = CustomData_get_layer_n(cd_ldata, CD_MLOOPUV, i_src);
 +                                      if (rdata->edit_bmesh) {
 +                                              rdata->cd.offset.uv[i_dst] = CustomData_get_n_offset(
 +                                                      &rdata->edit_bmesh->bm->ldata, CD_MLOOPUV, i_src);
 +                                      }
 +                                      BLI_snprintf(rdata->cd.uuid.auto_mix[i_dst], sizeof(*rdata->cd.uuid.auto_mix), "a%u", hash);
 +                              }
 +                      }
 +              }
 +
 +              if (rdata->cd.layers.tangent_len != 0) {
 +
 +                      /* -------------------------------------------------------------------- */
 +                      /* Pre-calculate tangents into 'rdata->cd.output.ldata' */
 +
 +                      BLI_assert(!CustomData_has_layer(&rdata->cd.output.ldata, CD_TANGENT));
 +
 +                      /* Tangent Names */
 +                      char tangent_names[MAX_MTFACE][MAX_NAME];
 +                      for (int i_src = 0, i_dst = 0; i_src < cd_layers_src.uv_len; i_src++, i_dst++) {
 +                              if ((cd_lused[CD_TANGENT] & (1 << i_src)) == 0) {
 +                                      i_dst--;
 +                              }
 +                              else {
 +                                      BLI_strncpy(
 +                                              tangent_names[i_dst],
 +                                              CustomData_get_layer_name(cd_ldata, CD_MLOOPUV, i_src), MAX_NAME);
 +                              }
 +                      }
 +
 +                      /* If tangent from orco is requested, decrement tangent_len */
 +                      int actual_tangent_len = (cd_lused[CD_TANGENT] & DM_TANGENT_MASK_ORCO) ?
 +                              rdata->cd.layers.tangent_len - 1 : rdata->cd.layers.tangent_len;
 +                      if (rdata->edit_bmesh) {
 +                              BMEditMesh *em = rdata->edit_bmesh;
 +                              BMesh *bm = em->bm;
 +
 +                              if (is_auto_smooth && rdata->loop_normals == NULL) {
 +                                      /* Should we store the previous array of `loop_normals` in somewhere? */
 +                                      rdata->loop_len = bm->totloop;
 +                                      rdata->loop_normals = MEM_mallocN(sizeof(*rdata->loop_normals) * rdata->loop_len, __func__);
 +                                      BM_loops_calc_normal_vcos(bm, NULL, NULL, NULL, true, split_angle, rdata->loop_normals, NULL, NULL, -1, false);
 +                              }
 +
 +                              bool calc_active_tangent = false;
 +
 +                              BKE_editmesh_loop_tangent_calc(
 +                                      em, calc_active_tangent,
 +                                      tangent_names, actual_tangent_len,
 +                                      rdata->poly_normals, rdata->loop_normals,
 +                                      rdata->orco,
 +                                      &rdata->cd.output.ldata, bm->totloop,
 +                                      &rdata->cd.output.tangent_mask);
 +                      }
 +                      else {
 +#undef me
 +
 +                              if (is_auto_smooth && rdata->loop_normals == NULL) {
 +                                      /* Should we store the previous array of `loop_normals` in CustomData? */
 +                                      mesh_render_calc_normals_loop_and_poly(me, split_angle, rdata);
 +                              }
 +
 +                              bool calc_active_tangent = false;
 +
 +                              BKE_mesh_calc_loop_tangent_ex(
 +                                      me->mvert,
 +                                      me->mpoly, me->totpoly,
 +                                      me->mloop,
 +                                      rdata->mlooptri, rdata->tri_len,
 +                                      cd_ldata,
 +                                      calc_active_tangent,
 +                                      tangent_names, actual_tangent_len,
 +                                      rdata->poly_normals, rdata->loop_normals,
 +                                      rdata->orco,
 +                                      &rdata->cd.output.ldata, me->totloop,
 +                                      &rdata->cd.output.tangent_mask);
 +
 +                                      /* If we store tangents in the mesh, set temporary. */
 +#if 0
 +                              CustomData_set_layer_flag(cd_ldata, CD_TANGENT, CD_FLAG_TEMPORARY);
 +#endif
 +
 +#define me DONT_USE_THIS
 +#ifdef  me /* quiet warning */
 +#endif
 +                      }
 +
 +                      /* End tangent calculation */
 +                      /* -------------------------------------------------------------------- */
 +
 +                      BLI_assert(CustomData_number_of_layers(&rdata->cd.output.ldata, CD_TANGENT) == rdata->cd.layers.tangent_len);
 +
 +                      int i_dst = 0;
 +                      for (int i_src = 0; i_src < cd_layers_src.uv_len; i_src++, i_dst++) {
 +                              if ((cd_lused[CD_TANGENT] & (1 << i_src)) == 0) {
 +                                      i_dst--;
 +                                      if (rdata->cd.layers.tangent_active >= i_src) {
 +                                              rdata->cd.layers.tangent_active--;
 +                                      }
 +                              }
 +                              else {
 +                                      const char *name = CustomData_get_layer_name(cd_ldata, CD_MLOOPUV, i_src);
 +                                      uint hash = BLI_ghashutil_strhash_p(name);
 +
 +                                      BLI_snprintf(rdata->cd.uuid.tangent[i_dst], sizeof(*rdata->cd.uuid.tangent), "t%u", hash);
 +
 +                                      /* Done adding tangents. */
 +
 +                                      /* note: BKE_editmesh_loop_tangent_calc calculates 'CD_TANGENT',
 +                                       * not 'CD_MLOOPTANGENT' (as done below). It's OK, they're compatible. */
 +
 +                                      /* note: normally we'd use 'i_src' here, but 'i_dst' is in sync with 'rdata->cd.output' */
 +                                      rdata->cd.layers.tangent[i_dst] = CustomData_get_layer_n(&rdata->cd.output.ldata, CD_TANGENT, i_dst);
 +                                      if (rdata->tri_len != 0) {
 +                                              BLI_assert(rdata->cd.layers.tangent[i_dst] != NULL);
 +                                      }
 +                              }
 +                      }
 +                      if (cd_lused[CD_TANGENT] & DM_TANGENT_MASK_ORCO) {
 +                              const char *name = CustomData_get_layer_name(&rdata->cd.output.ldata, CD_TANGENT, i_dst);
 +                              uint hash = BLI_ghashutil_strhash_p(name);
 +                              BLI_snprintf(rdata->cd.uuid.tangent[i_dst], sizeof(*rdata->cd.uuid.tangent), "t%u", hash);
 +
 +                              rdata->cd.layers.tangent[i_dst] = CustomData_get_layer_n(&rdata->cd.output.ldata, CD_TANGENT, i_dst);
 +                      }
 +              }
 +
 +#undef me
 +      }
 +
 +      return rdata;
 +}
 +
 +static void mesh_render_data_free(MeshRenderData *rdata)
 +{
 +      if (rdata->is_orco_allocated) {
 +              MEM_SAFE_FREE(rdata->orco);
 +      }
 +      MEM_SAFE_FREE(rdata->cd.offset.uv);
 +      MEM_SAFE_FREE(rdata->cd.offset.vcol);
 +      MEM_SAFE_FREE(rdata->cd.uuid.auto_mix);
 +      MEM_SAFE_FREE(rdata->cd.uuid.uv);
 +      MEM_SAFE_FREE(rdata->cd.uuid.vcol);
 +      MEM_SAFE_FREE(rdata->cd.uuid.tangent);
 +      MEM_SAFE_FREE(rdata->cd.layers.uv);
 +      MEM_SAFE_FREE(rdata->cd.layers.vcol);
 +      MEM_SAFE_FREE(rdata->cd.layers.tangent);
 +      MEM_SAFE_FREE(rdata->cd.layers.auto_vcol);
 +      MEM_SAFE_FREE(rdata->loose_verts);
 +      MEM_SAFE_FREE(rdata->loose_edges);
 +      MEM_SAFE_FREE(rdata->edges_adjacent_polys);
 +      MEM_SAFE_FREE(rdata->mlooptri);
 +      MEM_SAFE_FREE(rdata->loop_normals);
 +      MEM_SAFE_FREE(rdata->poly_normals);
 +      MEM_SAFE_FREE(rdata->poly_normals_pack);
 +      MEM_SAFE_FREE(rdata->vert_normals_pack);
 +      MEM_SAFE_FREE(rdata->vert_weight_color);
 +      MEM_SAFE_FREE(rdata->edge_select_bool);
 +      MEM_SAFE_FREE(rdata->vert_color);
 +
 +      CustomData_free(&rdata->cd.output.ldata, rdata->loop_len);
 +
 +      MEM_freeN(rdata);
 +}
 +
 +static MeshRenderData *mesh_render_data_create(Mesh *me, const int types)
 +{
 +      return mesh_render_data_create_ex(me, types, NULL, 0);
 +}
 +
 +/** \} */
 +
 +
 +/* ---------------------------------------------------------------------- */
 +
 +/** \name Accessor Functions
 + * \{ */
 +
 +static const char *mesh_render_data_uv_auto_layer_uuid_get(const MeshRenderData *rdata, int layer)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_SHADING);
 +      return rdata->cd.uuid.auto_mix[layer];
 +}
 +
 +static const char *mesh_render_data_vcol_auto_layer_uuid_get(const MeshRenderData *rdata, int layer)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_SHADING);
 +      return rdata->cd.uuid.auto_mix[rdata->cd.layers.uv_len + layer];
 +}
 +
 +static const char *mesh_render_data_uv_layer_uuid_get(const MeshRenderData *rdata, int layer)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_SHADING);
 +      return rdata->cd.uuid.uv[layer];
 +}
 +
 +static const char *mesh_render_data_vcol_layer_uuid_get(const MeshRenderData *rdata, int layer)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_SHADING);
 +      return rdata->cd.uuid.vcol[layer];
 +}
 +
 +static const char *mesh_render_data_tangent_layer_uuid_get(const MeshRenderData *rdata, int layer)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_SHADING);
 +      return rdata->cd.uuid.tangent[layer];
 +}
 +
 +static int mesh_render_data_verts_len_get(const MeshRenderData *rdata)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_VERT);
 +      return rdata->vert_len;
 +}
 +
 +static int mesh_render_data_loose_verts_len_get(const MeshRenderData *rdata)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_OVERLAY);
 +      return rdata->loose_vert_len;
 +}
 +
 +static int mesh_render_data_edges_len_get(const MeshRenderData *rdata)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_EDGE);
 +      return rdata->edge_len;
 +}
 +
 +static int mesh_render_data_loose_edges_len_get(const MeshRenderData *rdata)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_OVERLAY);
 +      return rdata->loose_edge_len;
 +}
 +
 +static int mesh_render_data_looptri_len_get(const MeshRenderData *rdata)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_LOOPTRI);
 +      return rdata->tri_len;
 +}
 +
 +static int mesh_render_data_mat_len_get(const MeshRenderData *rdata)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_POLY);
 +      return rdata->mat_len;
 +}
 +
 +static int UNUSED_FUNCTION(mesh_render_data_loops_len_get)(const MeshRenderData *rdata)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_LOOP);
 +      return rdata->loop_len;
 +}
 +
 +static int mesh_render_data_polys_len_get(const MeshRenderData *rdata)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_POLY);
 +      return rdata->poly_len;
 +}
 +
 +/** \} */
 +
 +
 +/* ---------------------------------------------------------------------- */
 +
 +/** \name Internal Cache (Lazy Initialization)
 + * \{ */
 +
 +/** Ensure #MeshRenderData.poly_normals_pack */
 +static void mesh_render_data_ensure_poly_normals_pack(MeshRenderData *rdata)
 +{
 +      GPUPackedNormal *pnors_pack = rdata->poly_normals_pack;
 +      if (pnors_pack == NULL) {
 +              if (rdata->edit_bmesh) {
 +                      BMesh *bm = rdata->edit_bmesh->bm;
 +                      BMIter fiter;
 +                      BMFace *efa;
 +                      int i;
 +
 +                      pnors_pack = rdata->poly_normals_pack = MEM_mallocN(sizeof(*pnors_pack) * rdata->poly_len, __func__);
 +                      BM_ITER_MESH_INDEX(efa, &fiter, bm, BM_FACES_OF_MESH, i) {
 +                              pnors_pack[i] = GPU_normal_convert_i10_v3(efa->no);
 +                      }
 +              }
 +              else {
 +                      float (*pnors)[3] = rdata->poly_normals;
 +
 +                      if (!pnors) {
 +                              pnors = rdata->poly_normals = MEM_mallocN(sizeof(*pnors) * rdata->poly_len, __func__);
 +                              BKE_mesh_calc_normals_poly(
 +                                      rdata->mvert, NULL, rdata->vert_len,
 +                                      rdata->mloop, rdata->mpoly, rdata->loop_len, rdata->poly_len, pnors, true);
 +                      }
 +
 +                      pnors_pack = rdata->poly_normals_pack = MEM_mallocN(sizeof(*pnors_pack) * rdata->poly_len, __func__);
 +                      for (int i = 0; i < rdata->poly_len; i++) {
 +                              pnors_pack[i] = GPU_normal_convert_i10_v3(pnors[i]);
 +                      }
 +              }
 +      }
 +}
 +
 +/** Ensure #MeshRenderData.vert_normals_pack */
 +static void mesh_render_data_ensure_vert_normals_pack(MeshRenderData *rdata)
 +{
 +      GPUPackedNormal *vnors_pack = rdata->vert_normals_pack;
 +      if (vnors_pack == NULL) {
 +              if (rdata->edit_bmesh) {
 +                      BMesh *bm = rdata->edit_bmesh->bm;
 +                      BMIter viter;
 +                      BMVert *eve;
 +                      int i;
 +
 +                      vnors_pack = rdata->vert_normals_pack = MEM_mallocN(sizeof(*vnors_pack) * rdata->vert_len, __func__);
 +                      BM_ITER_MESH_INDEX(eve, &viter, bm, BM_VERT, i) {
 +                              vnors_pack[i] = GPU_normal_convert_i10_v3(eve->no);
 +                      }
 +              }
 +              else {
 +                      /* data from mesh used directly */
 +                      BLI_assert(0);
 +              }
 +      }
 +}
 +
 +
 +/** Ensure #MeshRenderData.vert_color */
 +static void mesh_render_data_ensure_vert_color(MeshRenderData *rdata)
 +{
 +      char (*vcol)[3] = rdata->vert_color;
 +      if (vcol == NULL) {
 +              if (rdata->edit_bmesh) {
 +                      BMesh *bm = rdata->edit_bmesh->bm;
 +                      const int cd_loop_color_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPCOL);
 +                      if (cd_loop_color_offset == -1) {
 +                              goto fallback;
 +                      }
 +
 +                      vcol = rdata->vert_color = MEM_mallocN(sizeof(*vcol) * rdata->loop_len, __func__);
 +
 +                      BMIter fiter;
 +                      BMFace *efa;
 +                      int i = 0;
 +
 +                      BM_ITER_MESH(efa, &fiter, bm, BM_FACES_OF_MESH) {
 +                              BMLoop *l_iter, *l_first;
 +                              l_iter = l_first = BM_FACE_FIRST_LOOP(efa);
 +                              do {
 +                                      const MLoopCol *lcol = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_color_offset);
 +                                      vcol[i][0] = lcol->r;
 +                                      vcol[i][1] = lcol->g;
 +                                      vcol[i][2] = lcol->b;
 +                                      i += 1;
 +                              } while ((l_iter = l_iter->next) != l_first);
 +                      }
 +                      BLI_assert(i == rdata->loop_len);
 +              }
 +              else {
 +                      if (rdata->mloopcol == NULL) {
 +                              goto fallback;
 +                      }
 +
 +                      vcol = rdata->vert_color = MEM_mallocN(sizeof(*vcol) * rdata->loop_len, __func__);
 +
 +                      for (int i = 0; i < rdata->loop_len; i++) {
 +                              vcol[i][0] = rdata->mloopcol[i].r;
 +                              vcol[i][1] = rdata->mloopcol[i].g;
 +                              vcol[i][2] = rdata->mloopcol[i].b;
 +                      }
 +              }
 +      }
 +      return;
 +
 +fallback:
 +      vcol = rdata->vert_color = MEM_mallocN(sizeof(*vcol) * rdata->loop_len, __func__);
 +
 +      for (int i = 0; i < rdata->loop_len; i++) {
 +              vcol[i][0] = 255;
 +              vcol[i][1] = 255;
 +              vcol[i][2] = 255;
 +      }
 +}
 +
 +/* TODO, move into shader? */
 +static void rgb_from_weight(float r_rgb[3], const float weight)
 +{
 +      const float blend = ((weight / 2.0f) + 0.5f);
 +
 +      if (weight <= 0.25f) {    /* blue->cyan */
 +              r_rgb[0] = 0.0f;
 +              r_rgb[1] = blend * weight * 4.0f;
 +              r_rgb[2] = blend;
 +      }
 +      else if (weight <= 0.50f) {  /* cyan->green */
 +              r_rgb[0] = 0.0f;
 +              r_rgb[1] = blend;
 +              r_rgb[2] = blend * (1.0f - ((weight - 0.25f) * 4.0f));
 +      }
 +      else if (weight <= 0.75f) {  /* green->yellow */
 +              r_rgb[0] = blend * ((weight - 0.50f) * 4.0f);
 +              r_rgb[1] = blend;
 +              r_rgb[2] = 0.0f;
 +      }
 +      else if (weight <= 1.0f) {  /* yellow->red */
 +              r_rgb[0] = blend;
 +              r_rgb[1] = blend * (1.0f - ((weight - 0.75f) * 4.0f));
 +              r_rgb[2] = 0.0f;
 +      }
 +      else {
 +              /* exceptional value, unclamped or nan,
 +               * avoid uninitialized memory use */
 +              r_rgb[0] = 1.0f;
 +              r_rgb[1] = 0.0f;
 +              r_rgb[2] = 1.0f;
 +      }
 +}
 +
 +
 +/** Ensure #MeshRenderData.vert_weight_color */
 +static void mesh_render_data_ensure_vert_weight_color(MeshRenderData *rdata, const int defgroup)
 +{
 +      float (*vweight)[3] = rdata->vert_weight_color;
 +      if (vweight == NULL) {
 +              if (defgroup == -1) {
 +                      goto fallback;
 +              }
 +
 +              if (rdata->edit_bmesh) {
 +                      BMesh *bm = rdata->edit_bmesh->bm;
 +                      const int cd_dvert_offset = CustomData_get_offset(&bm->vdata, CD_MDEFORMVERT);
 +                      if (cd_dvert_offset == -1) {
 +                              goto fallback;
 +                      }
 +
 +                      BMIter viter;
 +                      BMVert *eve;
 +                      int i;
 +
 +                      vweight = rdata->vert_weight_color = MEM_mallocN(sizeof(*vweight) * rdata->vert_len, __func__);
 +                      BM_ITER_MESH_INDEX(eve, &viter, bm, BM_VERT, i) {
 +                              const MDeformVert *dvert = BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset);
 +                              float weight = defvert_find_weight(dvert, defgroup);
 +                              if (U.flag & USER_CUSTOM_RANGE) {
 +                                      BKE_colorband_evaluate(&U.coba_weight, weight, vweight[i]);
 +                              }
 +                              else {
 +                                      rgb_from_weight(vweight[i], weight);
 +                              }
 +                      }
 +              }
 +              else {
 +                      if (rdata->dvert == NULL) {
 +                              goto fallback;
 +                      }
 +
 +                      vweight = rdata->vert_weight_color = MEM_mallocN(sizeof(*vweight) * rdata->vert_len, __func__);
 +                      for (int i = 0; i < rdata->vert_len; i++) {
 +                              float weight = defvert_find_weight(&rdata->dvert[i], defgroup);
 +                              if (U.flag & USER_CUSTOM_RANGE) {
 +                                      BKE_colorband_evaluate(&U.coba_weight, weight, vweight[i]);
 +                              }
 +                              else {
 +                                      rgb_from_weight(vweight[i], weight);
 +                              }
 +                      }
 +              }
 +      }
 +      return;
 +
 +fallback:
 +      vweight = rdata->vert_weight_color = MEM_callocN(sizeof(*vweight) * rdata->vert_len, __func__);
 +
 +      for (int i = 0; i < rdata->vert_len; i++) {
 +              vweight[i][2] = 0.5f;
 +      }
 +}
 +
 +/** Ensure #MeshRenderData.edge_select_bool */
 +static void mesh_render_data_ensure_edge_select_bool(MeshRenderData *rdata, bool use_wire)
 +{
 +      bool *edge_select_bool = rdata->edge_select_bool;
 +      if (edge_select_bool == NULL) {
 +              edge_select_bool = rdata->edge_select_bool =
 +                      MEM_callocN(sizeof(*edge_select_bool) * rdata->edge_len, __func__);
 +
 +              for (int i = 0; i < rdata->poly_len; i++) {
 +                      MPoly *poly = &rdata->mpoly[i];
 +
 +                      if (poly->flag & ME_FACE_SEL) {
 +                              for (int j = 0; j < poly->totloop; j++) {
 +                                      MLoop *loop = &rdata->mloop[poly->loopstart + j];
 +                                      if (use_wire) {
 +                                              edge_select_bool[loop->e] = true;
 +                                      }
 +                                      else {
 +                                              /* Not totally correct, will cause problems for edges with 3x faces. */
 +                                              edge_select_bool[loop->e] = !edge_select_bool[loop->e];
 +                                      }
 +                              }
 +                      }
 +              }
 +      }
 +}
 +
 +/** \} */
 +
 +/* ---------------------------------------------------------------------- */
 +
 +/** \name Internal Cache Generation
 + * \{ */
 +
 +static bool mesh_render_data_pnors_pcenter_select_get(
 +        MeshRenderData *rdata, const int poly,
 +        float r_pnors[3], float r_center[3], bool *r_selected)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_LOOP | MR_DATATYPE_POLY));
 +
 +      if (rdata->edit_bmesh) {
 +              const BMFace *efa = BM_face_at_index(rdata->edit_bmesh->bm, poly);
 +              if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) {
 +                      return false;
 +              }
 +              BM_face_calc_center_mean(efa, r_center);
 +              copy_v3_v3(r_pnors, efa->no);
 +              *r_selected = (BM_elem_flag_test(efa, BM_ELEM_SELECT) != 0) ? true : false;
 +      }
 +      else {
 +              MVert *mvert = rdata->mvert;
 +              const MPoly *mpoly = rdata->mpoly + poly;
 +              const MLoop *mloop = rdata->mloop + mpoly->loopstart;
 +
 +              BKE_mesh_calc_poly_center(mpoly, mloop, mvert, r_center);
 +              BKE_mesh_calc_poly_normal(mpoly, mloop, mvert, r_pnors);
 +
 +              *r_selected = false; /* No selection if not in edit mode */
 +      }
 +
 +      return true;
 +}
 +
 +static bool mesh_render_data_edge_vcos_manifold_pnors(
 +        MeshRenderData *rdata, const int edge_index,
 +        float **r_vco1, float **r_vco2, float **r_pnor1, float **r_pnor2, bool *r_is_manifold)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_EDGE | MR_DATATYPE_LOOP | MR_DATATYPE_POLY));
 +
 +      if (rdata->edit_bmesh) {
 +              BMesh *bm = rdata->edit_bmesh->bm;
 +              BMEdge *eed = BM_edge_at_index(bm, edge_index);
 +              if (BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) {
 +                      return false;
 +              }
 +              *r_vco1 = eed->v1->co;
 +              *r_vco2 = eed->v2->co;
 +              if (BM_edge_is_manifold(eed)) {
 +                      *r_pnor1 = eed->l->f->no;
 +                      *r_pnor2 = eed->l->radial_next->f->no;
 +                      *r_is_manifold = true;
 +              }
 +              else if (eed->l != NULL) {
 +                      *r_pnor1 = eed->l->f->no;
 +                      *r_pnor2 = eed->l->f->no;
 +                      *r_is_manifold = false;
 +              }
 +              else {
 +                      *r_pnor1 = eed->v1->no;
 +                      *r_pnor2 = eed->v1->no;
 +                      *r_is_manifold = false;
 +              }
 +      }
 +      else {
 +              MVert *mvert = rdata->mvert;
 +              MEdge *medge = rdata->medge;
 +              EdgeAdjacentPolys *eap = rdata->edges_adjacent_polys;
 +              float (*pnors)[3] = rdata->poly_normals;
 +
 +              if (!eap) {
 +                      const MLoop *mloop = rdata->mloop;
 +                      const MPoly *mpoly = rdata->mpoly;
 +                      const int poly_len = rdata->poly_len;
 +                      const bool do_pnors = (poly_len != 0 && pnors == NULL);
 +
 +                      eap = rdata->edges_adjacent_polys = MEM_mallocN(sizeof(*eap) * rdata->edge_len, __func__);
 +                      for (int i = 0; i < rdata->edge_len; i++) {
 +                              eap[i].count = 0;
 +                              eap[i].face_index[0] = -1;
 +                              eap[i].face_index[1] = -1;
 +                      }
 +                      if (do_pnors) {
 +                              pnors = rdata->poly_normals = MEM_mallocN(sizeof(*pnors) * poly_len, __func__);
 +                      }
 +
 +                      for (int i = 0; i < poly_len; i++, mpoly++) {
 +                              if (do_pnors) {
 +                                      BKE_mesh_calc_poly_normal(mpoly, mloop + mpoly->loopstart, mvert, pnors[i]);
 +                              }
 +
 +                              const int loopend = mpoly->loopstart + mpoly->totloop;
 +                              for (int j = mpoly->loopstart; j < loopend; j++) {
 +                                      const int edge_idx = mloop[j].e;
 +                                      if (eap[edge_idx].count < 2) {
 +                                              eap[edge_idx].face_index[eap[edge_idx].count] = i;
 +                                      }
 +                                      eap[edge_idx].count++;
 +                              }
 +                      }
 +              }
 +              BLI_assert(eap && (rdata->poly_len == 0 || pnors != NULL));
 +
 +              *r_vco1 = mvert[medge[edge_index].v1].co;
 +              *r_vco2 = mvert[medge[edge_index].v2].co;
 +              if (eap[edge_index].face_index[0] == -1) {
 +                      /* Edge has no poly... */
 +                      *r_pnor1 = *r_pnor2 = mvert[medge[edge_index].v1].co; /* XXX mvert.no are shorts... :( */
 +                      *r_is_manifold = false;
 +              }
 +              else {
 +                      *r_pnor1 = pnors[eap[edge_index].face_index[0]];
 +
 +                      float nor[3], v1[3], v2[3], r_center[3];
 +                      const MPoly *mpoly = rdata->mpoly + eap[edge_index].face_index[0];
 +                      const MLoop *mloop = rdata->mloop + mpoly->loopstart;
 +
 +                      BKE_mesh_calc_poly_center(mpoly, mloop, mvert, r_center);
 +                      sub_v3_v3v3(v1, *r_vco2, *r_vco1);
 +                      sub_v3_v3v3(v2, r_center, *r_vco1);
 +                      cross_v3_v3v3(nor, v1, v2);
 +
 +                      if (dot_v3v3(nor, *r_pnor1) < 0.0) {
 +                              SWAP(float *, *r_vco1, *r_vco2);
 +                      }
 +
 +                      if (eap[edge_index].count == 2) {
 +                              BLI_assert(eap[edge_index].face_index[1] >= 0);
 +                              *r_pnor2 = pnors[eap[edge_index].face_index[1]];
 +                              *r_is_manifold = true;
 +                      }
 +                      else {
 +                              *r_pnor2 = pnors[eap[edge_index].face_index[0]];
 +                              *r_is_manifold = false;
 +                      }
 +              }
 +      }
 +
 +      return true;
 +}
 +
 +static uchar mesh_render_data_looptri_flag(MeshRenderData *rdata, const BMFace *efa)
 +{
 +      uchar fflag = 0;
 +
 +      if (efa == rdata->efa_act)
 +              fflag |= VFLAG_FACE_ACTIVE;
 +
 +      if (BM_elem_flag_test(efa, BM_ELEM_SELECT))
 +              fflag |= VFLAG_FACE_SELECTED;
 +
 +#ifdef WITH_FREESTYLE
 +      BMesh *bm = rdata->edit_bmesh->bm;
 +      if (CustomData_has_layer(&bm->pdata, CD_FREESTYLE_FACE)) {
 +              FreestyleFace *ffa = CustomData_bmesh_get(&bm->pdata, efa->head.data, CD_FREESTYLE_FACE);
 +
 +              if (ffa->flag & FREESTYLE_FACE_MARK)
 +                      fflag |= VFLAG_FACE_FREESTYLE;
 +      }
 +#endif
 +
 +      return fflag;
 +}
 +
 +static void mesh_render_data_edge_flag(
 +        const MeshRenderData *rdata, const BMEdge *eed,
 +        EdgeDrawAttr *eattr)
 +{
 +      eattr->e_flag |= VFLAG_EDGE_EXISTS;
 +
 +      if (eed == rdata->eed_act)
 +              eattr->e_flag |= VFLAG_EDGE_ACTIVE;
 +
 +      if (BM_elem_flag_test(eed, BM_ELEM_SELECT))
 +              eattr->e_flag |= VFLAG_EDGE_SELECTED;
 +
 +      if (BM_elem_flag_test(eed, BM_ELEM_SEAM))
 +              eattr->e_flag |= VFLAG_EDGE_SEAM;
 +
 +      if (!BM_elem_flag_test(eed, BM_ELEM_SMOOTH))
 +              eattr->e_flag |= VFLAG_EDGE_SHARP;
 +
 +#ifdef WITH_FREESTYLE
 +      BMesh *bm = rdata->edit_bmesh->bm;
 +      if (CustomData_has_layer(&bm->edata, CD_FREESTYLE_EDGE)) {
 +              FreestyleEdge *fed = CustomData_bmesh_get(&bm->edata, eed->head.data, CD_FREESTYLE_EDGE);
 +
 +              if (fed->flag & FREESTYLE_EDGE_MARK)
 +                      eattr->e_flag |= VFLAG_EDGE_FREESTYLE;
 +      }
 +#endif
 +
 +      /* Use a byte for value range */
 +      if (rdata->cd.offset.crease != -1) {
 +              float crease = BM_ELEM_CD_GET_FLOAT(eed, rdata->cd.offset.crease);
 +              if (crease > 0) {
 +                      eattr->crease = (uchar)(crease * 255.0f);
 +              }
 +      }
 +
 +      /* Use a byte for value range */
 +      if (rdata->cd.offset.bweight != -1) {
 +              float bweight = BM_ELEM_CD_GET_FLOAT(eed, rdata->cd.offset.bweight);
 +              if (bweight > 0) {
 +                      eattr->bweight = (uchar)(bweight * 255.0f);
 +              }
 +      }
 +}
 +
 +static uchar mesh_render_data_vertex_flag(MeshRenderData *rdata, const BMVert *eve)
 +{
 +      uchar vflag = 0;
 +
 +      /* Current vertex */
 +      if (eve == rdata->eve_act)
 +              vflag |= VFLAG_VERTEX_ACTIVE;
 +
 +      if (BM_elem_flag_test(eve, BM_ELEM_SELECT))
 +              vflag |= VFLAG_VERTEX_SELECTED;
 +
 +      return vflag;
 +}
 +
 +static void add_overlay_tri(
 +        MeshRenderData *rdata, GPUVertBuf *vbo_pos, GPUVertBuf *vbo_nor, GPUVertBuf *vbo_data,
 +        const uint pos_id, const uint vnor_id, const uint lnor_id, const uint data_id,
 +        const BMLoop **bm_looptri, const int base_vert_idx)
 +{
 +      uchar fflag;
 +      uchar vflag;
 +
 +      if (vbo_pos) {
 +              /* TODO(sybren): deduplicate this and all the other places it's pasted to in this file. */
 +              if (rdata->edit_data && rdata->edit_data->vertexCos) {
 +                      for (uint i = 0; i < 3; i++) {
 +                              int vidx = BM_elem_index_get(bm_looptri[i]->v);
 +                              const float *pos = rdata->edit_data->vertexCos[vidx];
 +                              GPU_vertbuf_attr_set(vbo_pos, pos_id, base_vert_idx + i, pos);
 +                      }
 +              }
 +              else {
 +                      for (uint i = 0; i < 3; i++) {
 +                              const float *pos = bm_looptri[i]->v->co;
 +                              GPU_vertbuf_attr_set(vbo_pos, pos_id, base_vert_idx + i, pos);
 +                      }
 +              }
 +      }
 +
 +      if (vbo_nor) {
 +              /* TODO real loop normal */
 +              GPUPackedNormal lnor = GPU_normal_convert_i10_v3(bm_looptri[0]->f->no);
 +              for (uint i = 0; i < 3; i++) {
 +                      GPUPackedNormal vnor = GPU_normal_convert_i10_v3(bm_looptri[i]->v->no);
 +                      GPU_vertbuf_attr_set(vbo_nor, vnor_id, base_vert_idx + i, &vnor);
 +                      GPU_vertbuf_attr_set(vbo_nor, lnor_id, base_vert_idx + i, &lnor);
 +              }
 +      }
 +
 +      if (vbo_data) {
 +              fflag = mesh_render_data_looptri_flag(rdata, bm_looptri[0]->f);
 +              uint i_prev = 1, i = 2;
 +              for (uint i_next = 0; i_next < 3; i_next++) {
 +                      vflag = mesh_render_data_vertex_flag(rdata, bm_looptri[i]->v);
 +                      EdgeDrawAttr eattr = {0};
 +                      if (bm_looptri[i_next] == bm_looptri[i_prev]->prev) {
 +                              mesh_render_data_edge_flag(rdata, bm_looptri[i_next]->e, &eattr);
 +                      }
 +                      eattr.v_flag = fflag | vflag;
 +                      GPU_vertbuf_attr_set(vbo_data, data_id, base_vert_idx + i, &eattr);
 +
 +                      i_prev = i;
 +                      i = i_next;
 +              }
 +      }
 +}
 +
 +static void add_overlay_loose_edge(
 +        MeshRenderData *rdata, GPUVertBuf *vbo_pos, GPUVertBuf *vbo_nor, GPUVertBuf *vbo_data,
 +        const uint pos_id, const uint vnor_id, const uint data_id,
 +        const BMEdge *eed, const int base_vert_idx)
 +{
 +      if (vbo_pos) {
 +              /* TODO(sybren): deduplicate this and all the other places it's pasted to in this file. */
 +              if (rdata->edit_data && rdata->edit_data->vertexCos) {
 +                      for (uint i = 0; i < 2; i++) {
 +                              int vidx = BM_elem_index_get((&eed->v1)[i]);
 +                              const float *pos = rdata->edit_data->vertexCos[vidx];
 +                              GPU_vertbuf_attr_set(vbo_pos, pos_id, base_vert_idx + i, pos);
 +                      }
 +              }
 +              else {
 +                      for (int i = 0; i < 2; ++i) {
 +                              const float *pos = (&eed->v1)[i]->co;
 +                              GPU_vertbuf_attr_set(vbo_pos, pos_id, base_vert_idx + i, pos);
 +                      }
 +              }
 +      }
 +
 +      if (vbo_nor) {
 +              for (int i = 0; i < 2; ++i) {
 +                      GPUPackedNormal vnor = GPU_normal_convert_i10_v3((&eed->v1)[i]->no);
 +                      GPU_vertbuf_attr_set(vbo_nor, vnor_id, base_vert_idx + i, &vnor);
 +              }
 +      }
 +
 +      if (vbo_data) {
 +              EdgeDrawAttr eattr = {0};
 +              mesh_render_data_edge_flag(rdata, eed, &eattr);
 +              for (int i = 0; i < 2; ++i) {
 +                      eattr.v_flag = mesh_render_data_vertex_flag(rdata, (&eed->v1)[i]);
 +                      GPU_vertbuf_attr_set(vbo_data, data_id, base_vert_idx + i, &eattr);
 +              }
 +      }
 +}
 +
 +static void add_overlay_loose_vert(
 +        MeshRenderData *rdata, GPUVertBuf *vbo_pos, GPUVertBuf *vbo_nor, GPUVertBuf *vbo_data,
 +        const uint pos_id, const uint vnor_id, const uint data_id,
 +        const BMVert *eve, const int base_vert_idx)
 +{
 +      if (vbo_pos) {
 +              /* TODO(sybren): deduplicate this and all the other places it's pasted to in this file. */
 +              if (rdata->edit_data && rdata->edit_data->vertexCos) {
 +                      int vidx = BM_elem_index_get(eve);
 +                      const float *pos = rdata->edit_data->vertexCos[vidx];
 +                      GPU_vertbuf_attr_set(vbo_pos, pos_id, base_vert_idx, pos);
 +              }
 +              else {
 +                      const float *pos = eve->co;
 +                      GPU_vertbuf_attr_set(vbo_pos, pos_id, base_vert_idx, pos);
 +              }
 +      }
 +
 +      if (vbo_nor) {
 +              GPUPackedNormal vnor = GPU_normal_convert_i10_v3(eve->no);
 +              GPU_vertbuf_attr_set(vbo_nor, vnor_id, base_vert_idx, &vnor);
 +      }
 +
 +      if (vbo_data) {
 +              uchar vflag[4] = {0, 0, 0, 0};
 +              vflag[0] = mesh_render_data_vertex_flag(rdata, eve);
 +              GPU_vertbuf_attr_set(vbo_data, data_id, base_vert_idx, vflag);
 +      }
 +}
 +
 +/** \} */
 +
 +
 +/* ---------------------------------------------------------------------- */
 +
 +/** \name Mesh GPUBatch Cache
 + * \{ */
 +
 +typedef struct MeshBatchCache {
 +      GPUVertBuf *pos_in_order;
 +      GPUIndexBuf *edges_in_order;
 +      GPUIndexBuf *edges_adjacency; /* Store edges with adjacent vertices. */
 +      GPUIndexBuf *triangles_in_order;
 +      GPUIndexBuf *ledges_in_order;
 +
 +      GPUTexture *pos_in_order_tx; /* Depending on pos_in_order */
 +
 +      GPUBatch *all_verts;
 +      GPUBatch *all_edges;
 +      GPUBatch *all_triangles;
 +
 +      GPUVertBuf *pos_with_normals;
 +      GPUVertBuf *tri_aligned_uv;  /* Active UV layer (mloopuv) */
 +
 +      /**
 +       * Other uses are all positions or loose elements.
 +       * This stores all visible elements, needed for selection.
 +       */
 +      GPUVertBuf *ed_fcenter_pos_with_nor_and_sel;
 +      GPUVertBuf *ed_edge_pos;
 +      GPUVertBuf *ed_vert_pos;
 +
 +      GPUBatch *triangles_with_normals;
 +      GPUBatch *ledges_with_normals;
 +
 +      /* Skip hidden (depending on paint select mode) */
 +      GPUBatch *triangles_with_weights;
 +      GPUBatch *triangles_with_vert_colors;
 +      /* Always skip hidden */
 +      GPUBatch *triangles_with_select_mask;
 +      GPUBatch *triangles_with_select_id;
 +      uint       triangles_with_select_id_offset;
 +
 +      GPUBatch *facedot_with_select_id;  /* shares vbo with 'overlay_facedots' */
 +      GPUBatch *edges_with_select_id;
 +      GPUBatch *verts_with_select_id;
 +
 +      uint facedot_with_select_id_offset;
 +      uint edges_with_select_id_offset;
 +      uint verts_with_select_id_offset;
 +
 +      GPUBatch *points_with_normals;
 +      GPUBatch *fancy_edges; /* owns its vertex buffer (not shared) */
 +
 +      GPUBatch *edge_detection;
 +
 +      GPUVertBuf *edges_face_overlay;
 +      GPUTexture *edges_face_overlay_tx;
 +      int edges_face_overlay_tri_count; /* Number of tri in edges_face_overlay(_adj)_tx */
 +
 +      /* Maybe have shaded_triangles_data split into pos_nor and uv_tangent
 +       * to minimize data transfer for skinned mesh. */
 +      GPUVertFormat shaded_triangles_format;
 +      GPUVertBuf *shaded_triangles_data;
 +      GPUIndexBuf **shaded_triangles_in_order;
 +      GPUBatch **shaded_triangles;
 +
 +      /* Texture Paint.*/
 +      /* per-texture batch */
 +      GPUBatch **texpaint_triangles;
 +      GPUBatch  *texpaint_triangles_single;
 +
 +      /* Edit Cage Mesh buffers */
 +      GPUVertBuf *ed_tri_pos;
 +      GPUVertBuf *ed_tri_nor; /* LoopNor, VertNor */
 +      GPUVertBuf *ed_tri_data;
 +
 +      GPUVertBuf *ed_ledge_pos;
 +      GPUVertBuf *ed_ledge_nor; /* VertNor */
 +      GPUVertBuf *ed_ledge_data;
 +
 +      GPUVertBuf *ed_lvert_pos;
 +      GPUVertBuf *ed_lvert_nor; /* VertNor */
 +      GPUVertBuf *ed_lvert_data;
 +
 +      GPUBatch *overlay_triangles;
 +      GPUBatch *overlay_triangles_nor; /* GPU_PRIM_POINTS */
 +      GPUBatch *overlay_loose_edges;
 +      GPUBatch *overlay_loose_edges_nor; /* GPU_PRIM_POINTS */
 +      GPUBatch *overlay_loose_verts;
 +      GPUBatch *overlay_facedots;
 +
 +      GPUBatch *overlay_weight_faces;
 +      GPUBatch *overlay_weight_verts;
 +      GPUBatch *overlay_paint_edges;
 +
 +      /* arrays of bool uniform names (and value) that will be use to
 +       * set srgb conversion for auto attribs.*/
 +      char *auto_layer_names;
 +      int *auto_layer_is_srgb;
 +      int auto_layer_len;
 +
 +      /* settings to determine if cache is invalid */
 +      bool is_maybe_dirty;
 +      bool is_dirty; /* Instantly invalidates cache, skipping mesh check */
 +      int edge_len;
 +      int tri_len;
 +      int poly_len;
 +      int vert_len;
 +      int mat_len;
 +      bool is_editmode;
 +
 +      /* XXX, only keep for as long as sculpt mode uses shaded drawing. */
 +      bool is_sculpt_points_tag;
 +
 +      /* Valid only if edges_adjacency is up to date. */
 +      bool is_manifold;
 +} MeshBatchCache;
 +
 +/* GPUBatch cache management. */
 +
 +static bool mesh_batch_cache_valid(Mesh *me)
 +{
 +      MeshBatchCache *cache = me->runtime.batch_cache;
 +
 +      if (cache == NULL) {
 +              return false;
 +      }
 +
 +      /* XXX find another place for this */
 +      if (cache->mat_len != mesh_render_mat_len_get(me)) {
 +              cache->is_maybe_dirty = true;
 +      }
 +
 +      if (cache->is_editmode != (me->edit_btmesh != NULL)) {
 +              return false;
 +      }
 +
 +      if (cache->is_dirty) {
 +              return false;
 +      }
 +
 +      if (cache->is_maybe_dirty == false) {
 +              return true;
 +      }
 +      else {
 +              if (cache->is_editmode) {
 +                      return false;
 +              }
 +              else if ((cache->vert_len != mesh_render_verts_len_get(me)) ||
 +                       (cache->edge_len != mesh_render_edges_len_get(me)) ||
 +                       (cache->tri_len  != mesh_render_looptri_len_get(me)) ||
 +                       (cache->poly_len != mesh_render_polys_len_get(me)) ||
 +                       (cache->mat_len   != mesh_render_mat_len_get(me)))
 +              {
 +                      return false;
 +              }
 +      }
 +
 +      return true;
 +}
 +
 +static void mesh_batch_cache_init(Mesh *me)
 +{
 +      MeshBatchCache *cache = me->runtime.batch_cache;
 +
 +      if (!cache) {
 +              cache = me->runtime.batch_cache = MEM_callocN(sizeof(*cache), __func__);
 +      }
 +      else {
 +              memset(cache, 0, sizeof(*cache));
 +      }
 +
 +      cache->is_editmode = me->edit_btmesh != NULL;
 +
 +      if (cache->is_editmode == false) {
 +              cache->edge_len = mesh_render_edges_len_get(me);
 +              cache->tri_len = mesh_render_looptri_len_get(me);
 +              cache->poly_len = mesh_render_polys_len_get(me);
 +              cache->vert_len = mesh_render_verts_len_get(me);
 +      }
 +
 +      cache->mat_len = mesh_render_mat_len_get(me);
 +
 +      cache->is_maybe_dirty = false;
 +      cache->is_dirty = false;
 +}
 +
 +static MeshBatchCache *mesh_batch_cache_get(Mesh *me)
 +{
 +      if (!mesh_batch_cache_valid(me)) {
 +              mesh_batch_cache_clear(me);
 +              mesh_batch_cache_init(me);
 +      }
 +      return me->runtime.batch_cache;
 +}
 +
 +void DRW_mesh_batch_cache_dirty_tag(Mesh *me, int mode)
 +{
 +      MeshBatchCache *cache = me->runtime.batch_cache;
 +      if (cache == NULL) {
 +              return;
 +      }
 +      switch (mode) {
 +              case BKE_MESH_BATCH_DIRTY_MAYBE_ALL:
 +                      cache->is_maybe_dirty = true;
 +                      break;
 +              case BKE_MESH_BATCH_DIRTY_SELECT:
 +                      GPU_VERTBUF_DISCARD_SAFE(cache->ed_tri_data);
 +                      GPU_VERTBUF_DISCARD_SAFE(cache->ed_ledge_data);
 +                      GPU_VERTBUF_DISCARD_SAFE(cache->ed_lvert_data);
 +                      GPU_VERTBUF_DISCARD_SAFE(cache->ed_fcenter_pos_with_nor_and_sel); /* Contains select flag */
 +                      GPU_VERTBUF_DISCARD_SAFE(cache->ed_edge_pos);
 +                      GPU_VERTBUF_DISCARD_SAFE(cache->ed_vert_pos);
 +
 +                      GPU_BATCH_DISCARD_SAFE(cache->overlay_triangles);
 +                      GPU_BATCH_DISCARD_SAFE(cache->overlay_loose_verts);
 +                      GPU_BATCH_DISCARD_SAFE(cache->overlay_loose_edges);
 +                      GPU_BATCH_DISCARD_SAFE(cache->overlay_facedots);
 +                      /* Edit mode selection. */
 +                      GPU_BATCH_DISCARD_SAFE(cache->facedot_with_select_id);
 +                      GPU_BATCH_DISCARD_SAFE(cache->edges_with_select_id);
 +                      GPU_BATCH_DISCARD_SAFE(cache->verts_with_select_id);
 +                      break;
 +              case BKE_MESH_BATCH_DIRTY_ALL:
 +                      cache->is_dirty = true;
 +                      break;
 +              case BKE_MESH_BATCH_DIRTY_SHADING:
 +                      /* TODO: This should only update UV and tangent data,
 +                       * and not free the entire cache. */
 +                      cache->is_dirty = true;
 +                      break;
 +              case BKE_MESH_BATCH_DIRTY_SCULPT_COORDS:
 +                      cache->is_sculpt_points_tag = true;
 +                      break;
 +              default:
 +                      BLI_assert(0);
 +      }
 +}
 +
 +/**
 + * This only clear the batches associated to the given vertex buffer.
 + **/
 +static void mesh_batch_cache_clear_selective(Mesh *me, GPUVertBuf *vert)
 +{
 +      MeshBatchCache *cache = me->runtime.batch_cache;
 +      if (!cache) {
 +              return;
 +      }
 +
 +      BLI_assert(vert != NULL);
 +
 +      if (cache->pos_with_normals == vert) {
 +              GPU_BATCH_DISCARD_SAFE(cache->triangles_with_normals);
 +              GPU_BATCH_DISCARD_SAFE(cache->triangles_with_weights);
 +              GPU_BATCH_DISCARD_SAFE(cache->triangles_with_vert_colors);
 +              GPU_BATCH_DISCARD_SAFE(cache->triangles_with_select_id);
 +              GPU_BATCH_DISCARD_SAFE(cache->triangles_with_select_mask);
 +              GPU_BATCH_DISCARD_SAFE(cache->points_with_normals);
 +              GPU_BATCH_DISCARD_SAFE(cache->ledges_with_normals);
 +              if (cache->shaded_triangles) {
 +                      for (int i = 0; i < cache->mat_len; ++i) {
 +                              GPU_BATCH_DISCARD_SAFE(cache->shaded_triangles[i]);
 +                      }
 +              }
 +              MEM_SAFE_FREE(cache->shaded_triangles);
 +              if (cache->texpaint_triangles) {
 +                      for (int i = 0; i < cache->mat_len; ++i) {
 +                              GPU_BATCH_DISCARD_SAFE(cache->texpaint_triangles[i]);
 +                      }
 +              }
 +              MEM_SAFE_FREE(cache->texpaint_triangles);
 +              GPU_BATCH_DISCARD_SAFE(cache->texpaint_triangles_single);
 +      }
 +      /* TODO: add the other ones if needed. */
 +      else {
 +              /* Does not match any vertbuf in the batch cache! */
 +              BLI_assert(0);
 +      }
 +}
 +
 +static void mesh_batch_cache_clear(Mesh *me)
 +{
 +      MeshBatchCache *cache = me->runtime.batch_cache;
 +      if (!cache) {
 +              return;
 +      }
 +
 +      GPU_BATCH_DISCARD_SAFE(cache->all_verts);
 +      GPU_BATCH_DISCARD_SAFE(cache->all_edges);
 +      GPU_BATCH_DISCARD_SAFE(cache->all_triangles);
 +
 +      GPU_VERTBUF_DISCARD_SAFE(cache->pos_in_order);
 +      DRW_TEXTURE_FREE_SAFE(cache->pos_in_order_tx);
 +      GPU_INDEXBUF_DISCARD_SAFE(cache->edges_in_order);
 +      GPU_INDEXBUF_DISCARD_SAFE(cache->triangles_in_order);
 +      GPU_INDEXBUF_DISCARD_SAFE(cache->ledges_in_order);
 +
 +      GPU_VERTBUF_DISCARD_SAFE(cache->ed_tri_pos);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->ed_tri_nor);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->ed_tri_data);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->ed_ledge_pos);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->ed_ledge_nor);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->ed_ledge_data);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->ed_lvert_pos);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->ed_lvert_nor);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->ed_lvert_data);
 +      GPU_BATCH_DISCARD_SAFE(cache->overlay_triangles);
 +      GPU_BATCH_DISCARD_SAFE(cache->overlay_triangles_nor);
 +      GPU_BATCH_DISCARD_SAFE(cache->overlay_loose_verts);
 +      GPU_BATCH_DISCARD_SAFE(cache->overlay_loose_edges);
 +      GPU_BATCH_DISCARD_SAFE(cache->overlay_loose_edges_nor);
 +
 +      GPU_BATCH_DISCARD_SAFE(cache->overlay_weight_faces);
 +      GPU_BATCH_DISCARD_SAFE(cache->overlay_weight_verts);
 +      GPU_BATCH_DISCARD_SAFE(cache->overlay_paint_edges);
 +      GPU_BATCH_DISCARD_SAFE(cache->overlay_facedots);
 +
 +      GPU_BATCH_DISCARD_SAFE(cache->triangles_with_normals);
 +      GPU_BATCH_DISCARD_SAFE(cache->points_with_normals);
 +      GPU_BATCH_DISCARD_SAFE(cache->ledges_with_normals);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->pos_with_normals);
 +      GPU_BATCH_DISCARD_SAFE(cache->triangles_with_weights);
 +      GPU_BATCH_DISCARD_SAFE(cache->triangles_with_vert_colors);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->tri_aligned_uv);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->ed_fcenter_pos_with_nor_and_sel);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->ed_edge_pos);
 +      GPU_VERTBUF_DISCARD_SAFE(cache->ed_vert_pos);
 +      GPU_BATCH_DISCARD_SAFE(cache->triangles_with_select_mask);
 +      GPU_BATCH_DISCARD_SAFE(cache->triangles_with_select_id);
 +      GPU_BATCH_DISCARD_SAFE(cache->facedot_with_select_id);
 +      GPU_BATCH_DISCARD_SAFE(cache->edges_with_select_id);
 +      GPU_BATCH_DISCARD_SAFE(cache->verts_with_select_id);
 +
 +      GPU_BATCH_DISCARD_SAFE(cache->fancy_edges);
 +
 +      GPU_INDEXBUF_DISCARD_SAFE(cache->edges_adjacency);
 +      GPU_BATCH_DISCARD_SAFE(cache->edge_detection);
 +
 +      GPU_VERTBUF_DISCARD_SAFE(cache->edges_face_overlay);
 +      DRW_TEXTURE_FREE_SAFE(cache->edges_face_overlay_tx);
 +
 +      GPU_VERTBUF_DISCARD_SAFE(cache->shaded_triangles_data);
 +      if (cache->shaded_triangles_in_order) {
 +              for (int i = 0; i < cache->mat_len; ++i) {
 +                      GPU_INDEXBUF_DISCARD_SAFE(cache->shaded_triangles_in_order[i]);
 +              }
 +      }
 +      if (cache->shaded_triangles) {
 +              for (int i = 0; i < cache->mat_len; ++i) {
 +                      GPU_BATCH_DISCARD_SAFE(cache->shaded_triangles[i]);
 +              }
 +      }
 +
 +      MEM_SAFE_FREE(cache->shaded_triangles_in_order);
 +      MEM_SAFE_FREE(cache->shaded_triangles);
 +
 +      MEM_SAFE_FREE(cache->auto_layer_names);
 +      MEM_SAFE_FREE(cache->auto_layer_is_srgb);
 +
 +      if (cache->texpaint_triangles) {
 +              for (int i = 0; i < cache->mat_len; ++i) {
 +                      GPU_BATCH_DISCARD_SAFE(cache->texpaint_triangles[i]);
 +              }
 +      }
 +      MEM_SAFE_FREE(cache->texpaint_triangles);
 +
 +      GPU_BATCH_DISCARD_SAFE(cache->texpaint_triangles_single);
 +
 +}
 +
 +void DRW_mesh_batch_cache_free(Mesh *me)
 +{
 +      mesh_batch_cache_clear(me);
 +      MEM_SAFE_FREE(me->runtime.batch_cache);
 +}
 +
 +/* GPUBatch cache usage. */
 +
 +static GPUVertBuf *mesh_batch_cache_get_tri_shading_data(MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_LOOPTRI | MR_DATATYPE_LOOP | MR_DATATYPE_POLY));
 +#define USE_COMP_MESH_DATA
 +
 +      if (cache->shaded_triangles_data == NULL) {
 +              const uint uv_len = rdata->cd.layers.uv_len;
 +              const uint tangent_len = rdata->cd.layers.tangent_len;
 +              const uint vcol_len = rdata->cd.layers.vcol_len;
 +              const uint layers_combined_len = uv_len + vcol_len + tangent_len;
 +
 +              if (layers_combined_len == 0) {
 +                      return NULL;
 +              }
 +
 +              GPUVertFormat *format = &cache->shaded_triangles_format;
 +
 +              GPU_vertformat_clear(format);
 +
 +              /* initialize vertex format */
 +              uint *layers_combined_id = BLI_array_alloca(layers_combined_id, layers_combined_len);
 +              uint *uv_id = layers_combined_id;
 +              uint *tangent_id = uv_id + uv_len;
 +              uint *vcol_id = tangent_id + tangent_len;
 +
 +              /* Not needed, just for sanity. */
 +              if (uv_len == 0) { uv_id = NULL; }
 +              if (tangent_len == 0) { tangent_id = NULL; }
 +              if (vcol_len == 0) { vcol_id = NULL; }
 +
 +              /* Count number of auto layer and allocate big enough name buffer. */
 +              uint auto_names_len = 0;
 +              uint auto_ofs = 0;
 +              uint auto_id = 0;
 +              cache->auto_layer_len = 0;
 +              for (uint i = 0; i < uv_len; i++) {
 +                      const char *attrib_name = mesh_render_data_uv_auto_layer_uuid_get(rdata, i);
 +                      auto_names_len += strlen(attrib_name) + 2; /* include null terminator and b prefix. */
 +                      cache->auto_layer_len++;
 +              }
 +              for (uint i = 0; i < vcol_len; i++) {
 +                      if (rdata->cd.layers.auto_vcol[i]) {
 +                              const char *attrib_name = mesh_render_data_vcol_auto_layer_uuid_get(rdata, i);
 +                              auto_names_len += strlen(attrib_name) + 2; /* include null terminator and b prefix. */
 +                              cache->auto_layer_len++;
 +                      }
 +              }
 +              auto_names_len += 1; /* add an ultimate '\0' terminator */
 +              cache->auto_layer_names = MEM_callocN(auto_names_len * sizeof(char), "Auto layer name buf");
 +              cache->auto_layer_is_srgb = MEM_mallocN(cache->auto_layer_len * sizeof(int), "Auto layer value buf");
 +
 +              for (uint i = 0; i < uv_len; i++) {
 +                      /* UV */
 +                      const char *attrib_name = mesh_render_data_uv_layer_uuid_get(rdata, i);
 +#if defined(USE_COMP_MESH_DATA) && 0 /* these are clamped. Maybe use them as an option in the future */
 +                      uv_id[i] = GPU_vertformat_attr_add(format, attrib_name, GPU_COMP_I16, 2, GPU_FETCH_INT_TO_FLOAT_UNIT);
 +#else
 +                      uv_id[i] = GPU_vertformat_attr_add(format, attrib_name, GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
 +#endif
 +
 +                      /* Auto Name */
 +                      attrib_name = mesh_render_data_uv_auto_layer_uuid_get(rdata, i);
 +                      GPU_vertformat_alias_add(format, attrib_name);
 +
 +                      /* +1 include null terminator. */
 +                      auto_ofs += 1 + BLI_snprintf_rlen(
 +                              cache->auto_layer_names + auto_ofs, auto_names_len - auto_ofs, "b%s", attrib_name);
 +                      cache->auto_layer_is_srgb[auto_id++] = 0; /* tag as not srgb */
 +
 +                      if (i == rdata->cd.layers.uv_active) {
 +                              GPU_vertformat_alias_add(format, "u");
 +                      }
 +              }
 +
 +              for (uint i = 0; i < tangent_len; i++) {
 +                      const char *attrib_name = mesh_render_data_tangent_layer_uuid_get(rdata, i);
 +                      /* WATCH IT : only specifying 3 component instead of 4 (4th is sign).
 +                       * That may cause some problem but I could not make it to fail (fclem) */
 +#ifdef USE_COMP_MESH_DATA
 +                      /* Tangents need more precision than 10_10_10 */
 +                      tangent_id[i] = GPU_vertformat_attr_add(format, attrib_name, GPU_COMP_I16, 3, GPU_FETCH_INT_TO_FLOAT_UNIT);
 +#else
 +                      tangent_id[i] = GPU_vertformat_attr_add(format, attrib_name, GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
 +#endif
 +
 +                      if (i == rdata->cd.layers.tangent_active) {
 +                              GPU_vertformat_alias_add(format, "t");
 +                      }
 +              }
 +
 +              for (uint i = 0; i < vcol_len; i++) {
 +                      const char *attrib_name = mesh_render_data_vcol_layer_uuid_get(rdata, i);
 +                      vcol_id[i] = GPU_vertformat_attr_add(format, attrib_name, GPU_COMP_U8, 3, GPU_FETCH_INT_TO_FLOAT_UNIT);
 +
 +                      /* Auto layer */
 +                      if (rdata->cd.layers.auto_vcol[i]) {
 +                              attrib_name = mesh_render_data_vcol_auto_layer_uuid_get(rdata, i);
 +
 +                              GPU_vertformat_alias_add(format, attrib_name);
 +
 +                              /* +1 include null terminator. */
 +                              auto_ofs += 1 + BLI_snprintf_rlen(
 +                                      cache->auto_layer_names + auto_ofs, auto_names_len - auto_ofs, "b%s", attrib_name);
 +                              cache->auto_layer_is_srgb[auto_id++] = 1; /* tag as srgb */
 +                      }
 +
 +                      if (i == rdata->cd.layers.vcol_active) {
 +                              GPU_vertformat_alias_add(format, "c");
 +                      }
 +              }
 +
 +              const uint tri_len = mesh_render_data_looptri_len_get(rdata);
 +
 +              GPUVertBuf *vbo = cache->shaded_triangles_data = GPU_vertbuf_create_with_format(format);
 +
 +              const int vbo_len_capacity = tri_len * 3;
 +              int vbo_len_used = 0;
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +
 +              GPUVertBufRaw *layers_combined_step = BLI_array_alloca(layers_combined_step, layers_combined_len);
 +
 +              GPUVertBufRaw *uv_step      = layers_combined_step;
 +              GPUVertBufRaw *tangent_step = uv_step + uv_len;
 +              GPUVertBufRaw *vcol_step    = tangent_step + tangent_len;
 +
 +              /* Not needed, just for sanity. */
 +              if (uv_len == 0) { uv_step = NULL; }
 +              if (tangent_len == 0) { tangent_step = NULL; }
 +              if (vcol_len == 0) { vcol_step = NULL; }
 +
 +              for (uint i = 0; i < uv_len; i++) {
 +                      GPU_vertbuf_attr_get_raw_data(vbo, uv_id[i], &uv_step[i]);
 +              }
 +              for (uint i = 0; i < tangent_len; i++) {
 +                      GPU_vertbuf_attr_get_raw_data(vbo, tangent_id[i], &tangent_step[i]);
 +              }
 +              for (uint i = 0; i < vcol_len; i++) {
 +                      GPU_vertbuf_attr_get_raw_data(vbo, vcol_id[i], &vcol_step[i]);
 +              }
 +
 +              /* TODO deduplicate all verts and make use of GPUIndexBuf in
 +               * mesh_batch_cache_get_triangles_in_order_split_by_material. */
 +              if (rdata->edit_bmesh) {
 +                      for (uint i = 0; i < tri_len; i++) {
 +                              const BMLoop **bm_looptri = (const BMLoop **)rdata->edit_bmesh->looptris[i];
 +                              if (BM_elem_flag_test(bm_looptri[0]->f, BM_ELEM_HIDDEN)) {
 +                                      continue;
 +                              }
 +                              /* UVs */
 +                              for (uint j = 0; j < uv_len; j++) {
 +                                      const uint layer_offset = rdata->cd.offset.uv[j];
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              const float *elem = ((MLoopUV *)BM_ELEM_CD_GET_VOID_P(bm_looptri[t], layer_offset))->uv;
 +                                              copy_v2_v2(GPU_vertbuf_raw_step(&uv_step[j]), elem);
 +                                      }
 +                              }
 +                              /* TANGENTs */
 +                              for (uint j = 0; j < tangent_len; j++) {
 +                                      float (*layer_data)[4] = rdata->cd.layers.tangent[j];
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              const float *elem = layer_data[BM_elem_index_get(bm_looptri[t])];
 +                                              normal_float_to_short_v3(GPU_vertbuf_raw_step(&tangent_step[j]), elem);
 +                                      }
 +                              }
 +                              /* VCOLs */
 +                              for (uint j = 0; j < vcol_len; j++) {
 +                                      const uint layer_offset = rdata->cd.offset.vcol[j];
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              const uchar *elem = &((MLoopCol *)BM_ELEM_CD_GET_VOID_P(bm_looptri[t], layer_offset))->r;
 +                                              copy_v3_v3_uchar(GPU_vertbuf_raw_step(&vcol_step[j]), elem);
 +                                      }
 +                              }
 +                      }
 +              }
 +              else {
 +                      for (uint i = 0; i < tri_len; i++) {
 +                              const MLoopTri *mlt = &rdata->mlooptri[i];
 +
 +                              /* UVs */
 +                              for (uint j = 0; j < uv_len; j++) {
 +                                      const MLoopUV *layer_data = rdata->cd.layers.uv[j];
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              const float *elem = layer_data[mlt->tri[t]].uv;
 +                                              copy_v2_v2(GPU_vertbuf_raw_step(&uv_step[j]), elem);
 +                                      }
 +                              }
 +                              /* TANGENTs */
 +                              for (uint j = 0; j < tangent_len; j++) {
 +                                      float (*layer_data)[4] = rdata->cd.layers.tangent[j];
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              const float *elem = layer_data[mlt->tri[t]];
 +#ifdef USE_COMP_MESH_DATA
 +                                              normal_float_to_short_v3(GPU_vertbuf_raw_step(&tangent_step[j]), elem);
 +#else
 +                                              copy_v3_v3(GPU_vertbuf_raw_step(&tangent_step[j]), elem);
 +#endif
 +                                      }
 +                              }
 +                              /* VCOLs */
 +                              for (uint j = 0; j < vcol_len; j++) {
 +                                      const MLoopCol *layer_data = rdata->cd.layers.vcol[j];
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              const uchar *elem = &layer_data[mlt->tri[t]].r;
 +                                              copy_v3_v3_uchar(GPU_vertbuf_raw_step(&vcol_step[j]), elem);
 +                                      }
 +                              }
 +                      }
 +              }
 +
 +              vbo_len_used = GPU_vertbuf_raw_used(&layers_combined_step[0]);
 +
 +#ifndef NDEBUG
 +              /* Check all layers are write aligned. */
 +              if (layers_combined_len > 1) {
 +                      for (uint i = 1; i < layers_combined_len; i++) {
 +                              BLI_assert(vbo_len_used == GPU_vertbuf_raw_used(&layers_combined_step[i]));
 +                      }
 +              }
 +#endif
 +
 +              if (vbo_len_capacity != vbo_len_used) {
 +                      GPU_vertbuf_data_resize(vbo, vbo_len_used);
 +              }
 +      }
 +
 +#undef USE_COMP_MESH_DATA
 +
 +      return cache->shaded_triangles_data;
 +}
 +
 +static GPUVertBuf *mesh_batch_cache_get_tri_uv_active(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_LOOPTRI | MR_DATATYPE_LOOP | MR_DATATYPE_LOOPUV));
 +
 +
 +      if (cache->tri_aligned_uv == NULL) {
 +              const MLoopUV *mloopuv = rdata->mloopuv;
 +              if (mloopuv == NULL) {
 +                      return NULL;
 +              }
 +
 +              uint vidx = 0;
 +
 +              static GPUVertFormat format = { 0 };
 +              static struct { uint uv; } attr_id;
 +              if (format.attr_len == 0) {
 +                      attr_id.uv = GPU_vertformat_attr_add(&format, "uv", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
 +              }
 +
 +              const int tri_len = mesh_render_data_looptri_len_get(rdata);
 +
 +              GPUVertBuf *vbo = cache->tri_aligned_uv = GPU_vertbuf_create_with_format(&format);
 +
 +              const int vbo_len_capacity = tri_len * 3;
 +              int vbo_len_used = 0;
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +
 +
 +              BMEditMesh *embm = rdata->edit_bmesh;
 +              /* get uv's from active UVMap */
 +              if (rdata->edit_bmesh) {
 +                      /* edit mode */
 +                      BMesh *bm = embm->bm;
 +
 +                      const int layer_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV);
 +                      for (uint i = 0; i < tri_len; i++) {
 +                              const BMLoop **bm_looptri = (const BMLoop **)embm->looptris[i];
 +                              if (BM_elem_flag_test(bm_looptri[0]->f, BM_ELEM_HIDDEN)) {
 +                                      continue;
 +                              }
 +                              for (uint t = 0; t < 3; t++) {
 +                                      const BMLoop *loop = bm_looptri[t];
 +                                      const int index = BM_elem_index_get(loop);
 +                                      if (index != -1) {
 +                                              const float *elem = ((MLoopUV *)BM_ELEM_CD_GET_VOID_P(loop, layer_offset))->uv;
 +                                              GPU_vertbuf_attr_set(vbo, attr_id.uv, vidx++, elem);
 +                                      }
 +                              }
 +                      }
 +              }
 +              else {
 +                      /* object mode */
 +                      for (int i = 0; i < tri_len; i++) {
 +                              const MLoopTri *mlt = &rdata->mlooptri[i];
 +                              GPU_vertbuf_attr_set(vbo, attr_id.uv, vidx++, mloopuv[mlt->tri[0]].uv);
 +                              GPU_vertbuf_attr_set(vbo, attr_id.uv, vidx++, mloopuv[mlt->tri[1]].uv);
 +                              GPU_vertbuf_attr_set(vbo, attr_id.uv, vidx++, mloopuv[mlt->tri[2]].uv);
 +                      }
 +              }
 +
 +              vbo_len_used = vidx;
 +
 +              BLI_assert(vbo_len_capacity == vbo_len_used);
 +              UNUSED_VARS_NDEBUG(vbo_len_used);
 +      }
 +
 +      return cache->tri_aligned_uv;
 +}
 +
 +static GPUVertBuf *mesh_batch_cache_get_tri_pos_and_normals_ex(
 +        MeshRenderData *rdata, const bool use_hide,
 +        GPUVertBuf **r_vbo)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_LOOPTRI | MR_DATATYPE_LOOP | MR_DATATYPE_POLY));
 +
 +      if (*r_vbo == NULL) {
 +              static GPUVertFormat format = { 0 };
 +              static struct { uint pos, nor; } attr_id;
 +              if (format.attr_len == 0) {
 +                      attr_id.pos = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
 +                      attr_id.nor = GPU_vertformat_attr_add(&format, "nor", GPU_COMP_I10, 3, GPU_FETCH_INT_TO_FLOAT_UNIT);
 +              }
 +
 +              const int tri_len = mesh_render_data_looptri_len_get(rdata);
 +
 +              GPUVertBuf *vbo = *r_vbo = GPU_vertbuf_create_with_format(&format);
 +
 +              const int vbo_len_capacity = tri_len * 3;
 +              int vbo_len_used = 0;
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +
 +              GPUVertBufRaw pos_step, nor_step;
 +              GPU_vertbuf_attr_get_raw_data(vbo, attr_id.pos, &pos_step);
 +              GPU_vertbuf_attr_get_raw_data(vbo, attr_id.nor, &nor_step);
 +
 +              float (*lnors)[3] = rdata->loop_normals;
 +
 +              if (rdata->edit_bmesh) {
 +                      GPUPackedNormal *pnors_pack, *vnors_pack;
 +
 +                      if (lnors == NULL) {
 +                              mesh_render_data_ensure_poly_normals_pack(rdata);
 +                              mesh_render_data_ensure_vert_normals_pack(rdata);
 +
 +                              pnors_pack = rdata->poly_normals_pack;
 +                              vnors_pack = rdata->vert_normals_pack;
 +                      }
 +
 +                      for (int i = 0; i < tri_len; i++) {
 +                              const BMLoop **bm_looptri = (const BMLoop **)rdata->edit_bmesh->looptris[i];
 +                              const BMFace *bm_face = bm_looptri[0]->f;
 +
 +                              /* use_hide always for edit-mode */
 +                              if (BM_elem_flag_test(bm_face, BM_ELEM_HIDDEN)) {
 +                                      continue;
 +                              }
 +
 +                              if (lnors) {
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              const float *nor = lnors[BM_elem_index_get(bm_looptri[t])];
 +                                              *((GPUPackedNormal *)GPU_vertbuf_raw_step(&nor_step)) = GPU_normal_convert_i10_v3(nor);
 +                                      }
 +                              }
 +                              else if (BM_elem_flag_test(bm_face, BM_ELEM_SMOOTH)) {
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              *((GPUPackedNormal *)GPU_vertbuf_raw_step(&nor_step)) = vnors_pack[BM_elem_index_get(bm_looptri[t]->v)];
 +                                      }
 +                              }
 +                              else {
 +                                      const GPUPackedNormal *snor_pack = &pnors_pack[BM_elem_index_get(bm_face)];
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              *((GPUPackedNormal *)GPU_vertbuf_raw_step(&nor_step)) = *snor_pack;
 +                                      }
 +                              }
 +
 +                              /* TODO(sybren): deduplicate this and all the other places it's pasted to in this file. */
 +                              if (rdata->edit_data && rdata->edit_data->vertexCos) {
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              int vidx = BM_elem_index_get(bm_looptri[t]->v);
 +                                              const float *pos = rdata->edit_data->vertexCos[vidx];
 +                                              copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), pos);
 +                                      }
 +                              }
 +                              else {
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), bm_looptri[t]->v->co);
 +                                      }
 +                              }
 +                      }
 +              }
 +              else {
 +                      if (lnors == NULL) {
 +                              /* Use normals from vertex. */
 +                              mesh_render_data_ensure_poly_normals_pack(rdata);
 +                      }
 +
 +                      for (int i = 0; i < tri_len; i++) {
 +                              const MLoopTri *mlt = &rdata->mlooptri[i];
 +                              const MPoly *mp = &rdata->mpoly[mlt->poly];
 +
 +                              if (use_hide && (mp->flag & ME_HIDE)) {
 +                                      continue;
 +                              }
 +
 +                              const uint vtri[3] = {
 +                                      rdata->mloop[mlt->tri[0]].v,
 +                                      rdata->mloop[mlt->tri[1]].v,
 +                                      rdata->mloop[mlt->tri[2]].v,
 +                              };
 +
 +                              if (lnors) {
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              const float *nor = lnors[mlt->tri[t]];
 +                                              *((GPUPackedNormal *)GPU_vertbuf_raw_step(&nor_step)) = GPU_normal_convert_i10_v3(nor);
 +                                      }
 +                              }
 +                              else if (mp->flag & ME_SMOOTH) {
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              const MVert *mv = &rdata->mvert[vtri[t]];
 +                                              *((GPUPackedNormal *)GPU_vertbuf_raw_step(&nor_step)) = GPU_normal_convert_i10_s3(mv->no);
 +                                      }
 +                              }
 +                              else {
 +                                      const GPUPackedNormal *pnors_pack = &rdata->poly_normals_pack[mlt->poly];
 +                                      for (uint t = 0; t < 3; t++) {
 +                                              *((GPUPackedNormal *)GPU_vertbuf_raw_step(&nor_step)) = *pnors_pack;
 +                                      }
 +                              }
 +
 +                              for (uint t = 0; t < 3; t++) {
 +                                      const MVert *mv = &rdata->mvert[vtri[t]];
 +                                      copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), mv->co);
 +                              }
 +                      }
 +              }
 +
 +              vbo_len_used = GPU_vertbuf_raw_used(&pos_step);
 +              BLI_assert(vbo_len_used == GPU_vertbuf_raw_used(&nor_step));
 +
 +              if (vbo_len_capacity != vbo_len_used) {
 +                      GPU_vertbuf_data_resize(vbo, vbo_len_used);
 +              }
 +      }
 +      return *r_vbo;
 +}
 +
 +static GPUVertBuf *mesh_batch_cache_get_tri_pos_and_normals(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      return mesh_batch_cache_get_tri_pos_and_normals_ex(
 +              rdata, false,
 +              &cache->pos_with_normals);
 +}
 +static GPUVertBuf *mesh_create_tri_pos_and_normals_visible_only(
 +        MeshRenderData *rdata)
 +{
 +      GPUVertBuf *vbo_dummy = NULL;
 +      return mesh_batch_cache_get_tri_pos_and_normals_ex(
 +              rdata, true,
 +              &vbo_dummy);
 +}
 +
 +static GPUVertBuf *mesh_batch_cache_get_facedot_pos_with_normals_and_flag(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_LOOP | MR_DATATYPE_POLY));
 +
 +      if (cache->ed_fcenter_pos_with_nor_and_sel == NULL) {
 +              static GPUVertFormat format = { 0 };
 +              static struct { uint pos, data; } attr_id;
 +              if (format.attr_len == 0) {
 +                      attr_id.pos = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
 +                      attr_id.data = GPU_vertformat_attr_add(&format, "norAndFlag", GPU_COMP_I10, 4, GPU_FETCH_INT_TO_FLOAT_UNIT);
 +              }
 +
 +              const int vbo_len_capacity = mesh_render_data_polys_len_get(rdata);
 +              int vidx = 0;
 +
 +              GPUVertBuf *vbo = cache->ed_fcenter_pos_with_nor_and_sel = GPU_vertbuf_create_with_format(&format);
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +              for (int i = 0; i < vbo_len_capacity; ++i) {
 +                      float pcenter[3], pnor[3];
 +                      bool selected = false;
 +
 +                      if (mesh_render_data_pnors_pcenter_select_get(rdata, i, pnor, pcenter, &selected)) {
 +
 +                              GPUPackedNormal nor = { .x = 0, .y = 0, .z = -511 };
 +                              nor = GPU_normal_convert_i10_v3(pnor);
 +                              nor.w = selected ? 1 : 0;
 +                              GPU_vertbuf_attr_set(vbo, attr_id.data, vidx, &nor);
 +
 +                              GPU_vertbuf_attr_set(vbo, attr_id.pos, vidx, pcenter);
 +
 +                              vidx += 1;
 +                      }
 +              }
 +              const int vbo_len_used = vidx;
 +              BLI_assert(vbo_len_used <= vbo_len_capacity);
 +              if (vbo_len_used != vbo_len_capacity) {
 +                      GPU_vertbuf_data_resize(vbo, vbo_len_used);
 +              }
 +      }
 +
 +      return cache->ed_fcenter_pos_with_nor_and_sel;
 +}
 +
 +static GPUVertBuf *mesh_batch_cache_get_edges_visible(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_EDGE));
 +
 +      if (cache->ed_edge_pos == NULL) {
 +              static GPUVertFormat format = { 0 };
 +              static struct { uint pos, data; } attr_id;
 +              if (format.attr_len == 0) {
 +                      attr_id.pos = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
 +              }
 +
 +              const int vbo_len_capacity = mesh_render_data_edges_len_get(rdata) * 2;
 +              int vidx = 0;
 +
 +              GPUVertBuf *vbo = cache->ed_edge_pos = GPU_vertbuf_create_with_format(&format);
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +              if (rdata->edit_bmesh) {
 +                      BMesh *bm = rdata->edit_bmesh->bm;
 +                      BMIter iter;
 +                      BMEdge *eed;
 +
 +                      BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) {
 +                              if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) {
 +                                      GPU_vertbuf_attr_set(vbo, attr_id.pos, vidx, eed->v1->co);
 +                                      vidx += 1;
 +                                      GPU_vertbuf_attr_set(vbo, attr_id.pos, vidx, eed->v2->co);
 +                                      vidx += 1;
 +                              }
 +                      }
 +              }
 +              else {
 +                      /* not yet done! */
 +                      BLI_assert(0);
 +              }
 +              const int vbo_len_used = vidx;
 +              if (vbo_len_used != vbo_len_capacity) {
 +                      GPU_vertbuf_data_resize(vbo, vbo_len_used);
 +              }
 +              UNUSED_VARS_NDEBUG(vbo_len_used);
 +      }
 +
 +      return cache->ed_edge_pos;
 +}
 +
 +static GPUVertBuf *mesh_batch_cache_get_verts_visible(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_VERT);
 +
 +      if (cache->ed_vert_pos == NULL) {
 +              static GPUVertFormat format = { 0 };
 +              static struct { uint pos, data; } attr_id;
 +              if (format.attr_len == 0) {
 +                      attr_id.pos = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
 +              }
 +
 +              const int vbo_len_capacity = mesh_render_data_verts_len_get(rdata);
 +              uint vidx = 0;
 +
 +              GPUVertBuf *vbo = cache->ed_vert_pos = GPU_vertbuf_create_with_format(&format);
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +              if (rdata->edit_bmesh) {
 +                      BMesh *bm = rdata->edit_bmesh->bm;
 +                      BMIter iter;
 +                      BMVert *eve;
 +
 +                      BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
 +                              if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) {
 +                                      GPU_vertbuf_attr_set(vbo, attr_id.pos, vidx, eve->co);
 +                                      vidx += 1;
 +                              }
 +                      }
 +              }
 +              else {
 +                      for (int i = 0; i < vbo_len_capacity; i++) {
 +                              const MVert *mv = &rdata->mvert[i];
 +                              if (!(mv->flag & ME_HIDE)) {
 +                                      GPU_vertbuf_attr_set(vbo, attr_id.pos, vidx, mv->co);
 +                                      vidx += 1;
 +                              }
 +                      }
 +              }
 +              const uint vbo_len_used = vidx;
 +              if (vbo_len_used != vbo_len_capacity) {
 +                      GPU_vertbuf_data_resize(vbo, vbo_len_used);
 +              }
 +
 +              UNUSED_VARS_NDEBUG(vbo_len_used);
 +      }
 +
 +      return cache->ed_vert_pos;
 +}
 +
 +static GPUVertBuf *mesh_create_facedot_select_id(
 +        MeshRenderData *rdata, uint select_id_offset)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_LOOP | MR_DATATYPE_POLY));
 +
 +      GPUVertBuf *vbo;
 +      {
 +              static GPUVertFormat format = { 0 };
 +              static struct { uint pos, col; } attr_id;
 +              if (format.attr_len == 0) {
 +                      attr_id.col = GPU_vertformat_attr_add(&format, "color", GPU_COMP_I32, 1, GPU_FETCH_INT);
 +              }
 +
 +              const int vbo_len_capacity = mesh_render_data_polys_len_get(rdata);
 +              int vidx = 0;
 +
 +              vbo = GPU_vertbuf_create_with_format(&format);
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +              uint select_index = select_id_offset;
 +
 +              if (rdata->edit_bmesh) {
 +                      BMesh *bm = rdata->edit_bmesh->bm;
 +                      BMIter iter;
 +                      BMEdge *efa;
 +
 +                      BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
 +                              if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) {
 +                                      int select_id;
 +                                      GPU_select_index_get(select_index, &select_id);
 +                                      GPU_vertbuf_attr_set(vbo, attr_id.col, vidx, &select_id);
 +                                      vidx += 1;
 +                              }
 +                              select_index += 1;
 +                      }
 +              }
 +              else {
 +                      /* not yet done! */
 +                      BLI_assert(0);
 +              }
 +              const int vbo_len_used = vidx;
 +              if (vbo_len_used != vbo_len_capacity) {
 +                      GPU_vertbuf_data_resize(vbo, vbo_len_used);
 +              }
 +      }
 +
 +      return vbo;
 +}
 +
 +static GPUVertBuf *mesh_create_edges_select_id(
 +        MeshRenderData *rdata, uint select_id_offset)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_EDGE));
 +
 +      GPUVertBuf *vbo;
 +      {
 +              static GPUVertFormat format = { 0 };
 +              static struct { uint pos, col; } attr_id;
 +              if (format.attr_len == 0) {
 +                      attr_id.col = GPU_vertformat_attr_add(&format, "color", GPU_COMP_I32, 1, GPU_FETCH_INT);
 +              }
 +
 +              const int vbo_len_capacity = mesh_render_data_edges_len_get(rdata) * 2;
 +              int vidx = 0;
 +
 +              vbo = GPU_vertbuf_create_with_format(&format);
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +              uint select_index = select_id_offset;
 +
 +              if (rdata->edit_bmesh) {
 +                      BMesh *bm = rdata->edit_bmesh->bm;
 +                      BMIter iter;
 +                      BMEdge *eed;
 +
 +                      BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) {
 +                              if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) {
 +                                      int select_id;
 +                                      GPU_select_index_get(select_index, &select_id);
 +                                      GPU_vertbuf_attr_set(vbo, attr_id.col, vidx, &select_id);
 +                                      vidx += 1;
 +                                      GPU_vertbuf_attr_set(vbo, attr_id.col, vidx, &select_id);
 +                                      vidx += 1;
 +                              }
 +                              select_index += 1;
 +                      }
 +              }
 +              else {
 +                      /* not yet done! */
 +                      BLI_assert(0);
 +              }
 +              const int vbo_len_used = vidx;
 +              if (vbo_len_used != vbo_len_capacity) {
 +                      GPU_vertbuf_data_resize(vbo, vbo_len_used);
 +              }
 +      }
 +
 +      return vbo;
 +}
 +
 +static GPUVertBuf *mesh_create_verts_select_id(
 +        MeshRenderData *rdata, uint select_id_offset)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_LOOP | MR_DATATYPE_POLY));
 +
 +      GPUVertBuf *vbo;
 +      {
 +              static GPUVertFormat format = { 0 };
 +              static struct { uint pos, col; } attr_id;
 +              if (format.attr_len == 0) {
 +                      attr_id.col = GPU_vertformat_attr_add(&format, "color", GPU_COMP_I32, 1, GPU_FETCH_INT);
 +              }
 +
 +              const int vbo_len_capacity = mesh_render_data_verts_len_get(rdata);
 +              int vidx = 0;
 +
 +              vbo = GPU_vertbuf_create_with_format(&format);
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +              uint select_index = select_id_offset;
 +
 +              if (rdata->edit_bmesh) {
 +                      BMesh *bm = rdata->edit_bmesh->bm;
 +                      BMIter iter;
 +                      BMVert *eve;
 +
 +                      BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
 +                              if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) {
 +                                      int select_id;
 +                                      GPU_select_index_get(select_index, &select_id);
 +                                      GPU_vertbuf_attr_set(vbo, attr_id.col, vidx, &select_id);
 +                                      vidx += 1;
 +                              }
 +                              select_index += 1;
 +                      }
 +              }
 +              else {
 +                      for (int i = 0; i < vbo_len_capacity; i++) {
 +                              const MVert *mv = &rdata->mvert[i];
 +                              if (!(mv->flag & ME_HIDE)) {
 +                                      int select_id;
 +                                      GPU_select_index_get(select_index, &select_id);
 +                                      GPU_vertbuf_attr_set(vbo, attr_id.col, vidx, &select_id);
 +                                      vidx += 1;
 +                              }
 +                              select_index += 1;
 +                      }
 +              }
 +              const int vbo_len_used = vidx;
 +              if (vbo_len_used != vbo_len_capacity) {
 +                      GPU_vertbuf_data_resize(vbo, vbo_len_used);
 +              }
 +      }
 +
 +      return vbo;
 +}
 +
 +static GPUVertBuf *mesh_create_tri_weights(
 +        MeshRenderData *rdata, bool use_hide, int defgroup)
 +{
 +      BLI_assert(
 +              rdata->types &
 +              (MR_DATATYPE_VERT | MR_DATATYPE_LOOPTRI | MR_DATATYPE_LOOP | MR_DATATYPE_POLY | MR_DATATYPE_DVERT));
 +
 +      GPUVertBuf *vbo;
 +      {
 +              uint cidx = 0;
 +
 +              static GPUVertFormat format = { 0 };
 +              static struct { uint col; } attr_id;
 +              if (format.attr_len == 0) {
 +                      attr_id.col = GPU_vertformat_attr_add(&format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
 +              }
 +
 +              vbo = GPU_vertbuf_create_with_format(&format);
 +
 +              const int tri_len = mesh_render_data_looptri_len_get(rdata);
 +              const int vbo_len_capacity = tri_len * 3;
 +              int vbo_len_used = 0;
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +
 +              mesh_render_data_ensure_vert_weight_color(rdata, defgroup);
 +              const float (*vert_weight_color)[3] = rdata->vert_weight_color;
 +
 +              if (rdata->edit_bmesh) {
 +                      for (int i = 0; i < tri_len; i++) {
 +                              const BMLoop **ltri = (const BMLoop **)rdata->edit_bmesh->looptris[i];
 +                              /* Assume 'use_hide' */
 +                              if (!BM_elem_flag_test(ltri[0]->f, BM_ELEM_HIDDEN)) {
 +                                      for (uint tri_corner = 0; tri_corner < 3; tri_corner++) {
 +                                              const int v_index = BM_elem_index_get(ltri[tri_corner]->v);
 +                                              GPU_vertbuf_attr_set(vbo, attr_id.col, cidx++, vert_weight_color[v_index]);
 +                                      }
 +                              }
 +                      }
 +              }
 +              else {
 +                      for (int i = 0; i < tri_len; i++) {
 +                              const MLoopTri *mlt = &rdata->mlooptri[i];
 +                              if (!(use_hide && (rdata->mpoly[mlt->poly].flag & ME_HIDE))) {
 +                                      for (uint tri_corner = 0; tri_corner < 3; tri_corner++) {
 +                                              const uint v_index = rdata->mloop[mlt->tri[tri_corner]].v;
 +                                              GPU_vertbuf_attr_set(vbo, attr_id.col, cidx++, vert_weight_color[v_index]);
 +                                      }
 +                              }
 +                      }
 +              }
 +              vbo_len_used = cidx;
 +
 +              if (vbo_len_capacity != vbo_len_used) {
 +                      GPU_vertbuf_data_resize(vbo, vbo_len_used);
 +              }
 +      }
 +
 +      return vbo;
 +}
 +
 +static GPUVertBuf *mesh_create_tri_vert_colors(
 +        MeshRenderData *rdata, bool use_hide)
 +{
 +      BLI_assert(
 +              rdata->types &
 +              (MR_DATATYPE_VERT | MR_DATATYPE_LOOPTRI | MR_DATATYPE_LOOP | MR_DATATYPE_POLY | MR_DATATYPE_LOOPCOL));
 +
 +      GPUVertBuf *vbo;
 +      {
 +              uint cidx = 0;
 +
 +              static GPUVertFormat format = { 0 };
 +              static struct { uint col; } attr_id;
 +              if (format.attr_len == 0) {
 +                      attr_id.col = GPU_vertformat_attr_add(&format, "color", GPU_COMP_U8, 3, GPU_FETCH_INT_TO_FLOAT_UNIT);
 +              }
 +
 +              const int tri_len = mesh_render_data_looptri_len_get(rdata);
 +
 +              vbo = GPU_vertbuf_create_with_format(&format);
 +
 +              const uint vbo_len_capacity = tri_len * 3;
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +
 +              mesh_render_data_ensure_vert_color(rdata);
 +              const char (*vert_color)[3] = rdata->vert_color;
 +
 +              if (rdata->edit_bmesh) {
 +                      for (int i = 0; i < tri_len; i++) {
 +                              const BMLoop **ltri = (const BMLoop **)rdata->edit_bmesh->looptris[i];
 +                              /* Assume 'use_hide' */
 +                              if (!BM_elem_flag_test(ltri[0]->f, BM_ELEM_HIDDEN)) {
 +                                      for (uint tri_corner = 0; tri_corner < 3; tri_corner++) {
 +                                              const int l_index = BM_elem_index_get(ltri[tri_corner]);
 +                                              GPU_vertbuf_attr_set(vbo, attr_id.col, cidx++, vert_color[l_index]);
 +                                      }
 +                              }
 +                      }
 +              }
 +              else {
 +                      for (int i = 0; i < tri_len; i++) {
 +                              const MLoopTri *mlt = &rdata->mlooptri[i];
 +                              if (!(use_hide && (rdata->mpoly[mlt->poly].flag & ME_HIDE))) {
 +                                      for (uint tri_corner = 0; tri_corner < 3; tri_corner++) {
 +                                              const uint l_index = mlt->tri[tri_corner];
 +                                              GPU_vertbuf_attr_set(vbo, attr_id.col, cidx++, vert_color[l_index]);
 +                                      }
 +                              }
 +                      }
 +              }
 +              const uint vbo_len_used = cidx;
 +
 +              if (vbo_len_capacity != vbo_len_used) {
 +                      GPU_vertbuf_data_resize(vbo, vbo_len_used);
 +              }
 +      }
 +
 +      return vbo;
 +}
 +
 +static GPUVertBuf *mesh_create_tri_select_id(
 +        MeshRenderData *rdata, bool use_hide, uint select_id_offset)
 +{
 +      BLI_assert(
 +              rdata->types &
 +              (MR_DATATYPE_VERT | MR_DATATYPE_LOOPTRI | MR_DATATYPE_LOOP | MR_DATATYPE_POLY));
 +
 +      GPUVertBuf *vbo;
 +      {
 +              uint cidx = 0;
 +
 +              static GPUVertFormat format = { 0 };
 +              static struct { uint col; } attr_id;
 +              if (format.attr_len == 0) {
 +                      attr_id.col = GPU_vertformat_attr_add(&format, "color", GPU_COMP_I32, 1, GPU_FETCH_INT);
 +              }
 +
 +              const int tri_len = mesh_render_data_looptri_len_get(rdata);
 +
 +              vbo = GPU_vertbuf_create_with_format(&format);
 +
 +              const int vbo_len_capacity = tri_len * 3;
 +              int vbo_len_used = 0;
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +
 +              if (rdata->edit_bmesh) {
 +                      for (int i = 0; i < tri_len; i++) {
 +                              const BMLoop **ltri = (const BMLoop **)rdata->edit_bmesh->looptris[i];
 +                              /* Assume 'use_hide' */
 +                              if (!BM_elem_flag_test(ltri[0]->f, BM_ELEM_HIDDEN)) {
 +                                      const int poly_index = BM_elem_index_get(ltri[0]->f);
 +                                      int select_id;
 +                                      GPU_select_index_get(poly_index + select_id_offset, &select_id);
 +                                      for (uint tri_corner = 0; tri_corner < 3; tri_corner++) {
 +                                              GPU_vertbuf_attr_set(vbo, attr_id.col, cidx++, &select_id);
 +                                      }
 +                              }
 +                      }
 +              }
 +              else {
 +                      for (int i = 0; i < tri_len; i++) {
 +                              const MLoopTri *mlt = &rdata->mlooptri[i];
 +                              const int poly_index = mlt->poly;
 +                              if (!(use_hide && (rdata->mpoly[poly_index].flag & ME_HIDE))) {
 +                                      int select_id;
 +                                      GPU_select_index_get(poly_index + select_id_offset, &select_id);
 +                                      for (uint tri_corner = 0; tri_corner < 3; tri_corner++) {
 +                                              GPU_vertbuf_attr_set(vbo, attr_id.col, cidx++, &select_id);
 +                                      }
 +                              }
 +                      }
 +              }
 +              vbo_len_used = cidx;
 +
 +              if (vbo_len_capacity != vbo_len_used) {
 +                      GPU_vertbuf_data_resize(vbo, vbo_len_used);
 +              }
 +      }
 +      return vbo;
 +}
 +
 +static GPUVertBuf *mesh_batch_cache_get_vert_pos_and_nor_in_order(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_VERT);
 +
 +      if (cache->pos_in_order == NULL) {
 +              static GPUVertFormat format = { 0 };
 +              static struct { uint pos, nor; } attr_id;
 +              if (format.attr_len == 0) {
 +                      /* Normal is padded so that the vbo can be used as a buffer texture */
 +                      attr_id.pos = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
 +                      attr_id.nor = GPU_vertformat_attr_add(&format, "nor", GPU_COMP_I16, 4, GPU_FETCH_INT_TO_FLOAT_UNIT);
 +              }
 +
 +              GPUVertBuf *vbo = cache->pos_in_order = GPU_vertbuf_create_with_format(&format);
 +              const int vbo_len_capacity = mesh_render_data_verts_len_get(rdata);
 +              GPU_vertbuf_data_alloc(vbo, vbo_len_capacity);
 +
 +              if (rdata->edit_bmesh) {
 +                      BMesh *bm = rdata->edit_bmesh->bm;
 +                      BMIter iter;
 +                      BMVert *eve;
 +                      uint i;
 +
 +                      BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) {
 +                              static short no_short[4];
 +                              normal_float_to_short_v3(no_short, eve->no);
 +
 +                              GPU_vertbuf_attr_set(vbo, attr_id.pos, i, eve->co);
 +                              GPU_vertbuf_attr_set(vbo, attr_id.nor, i, no_short);
 +                      }
 +                      BLI_assert(i == vbo_len_capacity);
 +              }
 +              else {
 +                      for (int i = 0; i < vbo_len_capacity; ++i) {
 +                              GPU_vertbuf_attr_set(vbo, attr_id.pos, i, rdata->mvert[i].co);
 +                              GPU_vertbuf_attr_set(vbo, attr_id.nor, i, rdata->mvert[i].no); /* XXX actually reading 4 shorts */
 +                      }
 +              }
 +      }
 +
 +      return cache->pos_in_order;
 +}
 +
 +static GPUVertFormat *edit_mesh_overlay_pos_format(uint *r_pos_id)
 +{
 +      static GPUVertFormat format_pos = { 0 };
 +      static uint pos_id;
 +      if (format_pos.attr_len == 0) {
 +              pos_id = GPU_vertformat_attr_add(&format_pos, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
 +      }
 +      *r_pos_id = pos_id;
 +      return &format_pos;
 +}
 +
 +static GPUVertFormat *edit_mesh_overlay_nor_format(uint *r_vnor_id, uint *r_lnor_id)
 +{
 +      static GPUVertFormat format_nor = { 0 };
 +      static GPUVertFormat format_nor_loop = { 0 };
 +      static uint vnor_id, vnor_loop_id, lnor_id;
 +      if (format_nor.attr_len == 0) {
 +              vnor_id = GPU_vertformat_attr_add(&format_nor, "vnor", GPU_COMP_I10, 3, GPU_FETCH_INT_TO_FLOAT_UNIT);
 +              vnor_loop_id = GPU_vertformat_attr_add(&format_nor_loop, "vnor", GPU_COMP_I10, 3, GPU_FETCH_INT_TO_FLOAT_UNIT);
 +              lnor_id = GPU_vertformat_attr_add(&format_nor_loop, "lnor", GPU_COMP_I10, 3, GPU_FETCH_INT_TO_FLOAT_UNIT);
 +      }
 +      if (r_lnor_id) {
 +              *r_vnor_id = vnor_loop_id;
 +              *r_lnor_id = lnor_id;
 +              return &format_nor_loop;
 +      }
 +      else {
 +              *r_vnor_id = vnor_id;
 +              return &format_nor;
 +      }
 +}
 +
 +static GPUVertFormat *edit_mesh_overlay_data_format(uint *r_data_id)
 +{
 +      static GPUVertFormat format_flag = { 0 };
 +      static uint data_id;
 +      if (format_flag.attr_len == 0) {
 +              data_id = GPU_vertformat_attr_add(&format_flag, "data", GPU_COMP_U8, 4, GPU_FETCH_INT);
 +      }
 +      *r_data_id = data_id;
 +      return &format_flag;
 +}
 +
 +static void mesh_batch_cache_create_overlay_tri_buffers(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_LOOPTRI));
 +
 +      const int tri_len = mesh_render_data_looptri_len_get(rdata);
 +
 +      const int vbo_len_capacity = tri_len * 3;
 +      int vbo_len_used = 0;
 +
 +      /* Positions */
 +      GPUVertBuf *vbo_pos = NULL;
 +      static struct { uint pos, vnor, lnor, data; } attr_id;
 +      if (cache->ed_tri_pos == NULL) {
 +              vbo_pos = cache->ed_tri_pos =
 +                      GPU_vertbuf_create_with_format(edit_mesh_overlay_pos_format(&attr_id.pos));
 +              GPU_vertbuf_data_alloc(vbo_pos, vbo_len_capacity);
 +      }
 +
 +      /* Normals */
 +      GPUVertBuf *vbo_nor = NULL;
 +      if (cache->ed_tri_nor == NULL) {
 +              vbo_nor = cache->ed_tri_nor =
 +                      GPU_vertbuf_create_with_format(edit_mesh_overlay_nor_format(&attr_id.vnor, &attr_id.lnor));
 +              GPU_vertbuf_data_alloc(vbo_nor, vbo_len_capacity);
 +      }
 +
 +      /* Data */
 +      GPUVertBuf *vbo_data = NULL;
 +      if (cache->ed_tri_data == NULL) {
 +              vbo_data = cache->ed_tri_data =
 +                      GPU_vertbuf_create_with_format(edit_mesh_overlay_data_format(&attr_id.data));
 +              GPU_vertbuf_data_alloc(vbo_data, vbo_len_capacity);
 +      }
 +
 +      for (int i = 0; i < tri_len; i++) {
 +              const BMLoop **bm_looptri = (const BMLoop **)rdata->edit_bmesh->looptris[i];
 +              if (!BM_elem_flag_test(bm_looptri[0]->f, BM_ELEM_HIDDEN)) {
 +                      add_overlay_tri(
 +                              rdata, vbo_pos, vbo_nor, vbo_data,
 +                              attr_id.pos, attr_id.vnor, attr_id.lnor, attr_id.data,
 +                              bm_looptri, vbo_len_used);
 +
 +                      vbo_len_used += 3;
 +              }
 +      }
 +
 +      /* Finish */
 +      if (vbo_len_used != vbo_len_capacity) {
 +              if (vbo_pos != NULL) {
 +                      GPU_vertbuf_data_resize(vbo_pos, vbo_len_used);
 +              }
 +              if (vbo_nor != NULL) {
 +                      GPU_vertbuf_data_resize(vbo_nor, vbo_len_used);
 +              }
 +              if (vbo_data != NULL) {
 +                      GPU_vertbuf_data_resize(vbo_data, vbo_len_used);
 +              }
 +      }
 +}
 +
 +static void mesh_batch_cache_create_overlay_ledge_buffers(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_LOOPTRI));
 +
 +      const int ledge_len = mesh_render_data_loose_edges_len_get(rdata);
 +
 +      const int vbo_len_capacity = ledge_len * 2;
 +      int vbo_len_used = 0;
 +
 +      /* Positions */
 +      GPUVertBuf *vbo_pos = NULL;
 +      static struct { uint pos, vnor, data; } attr_id;
 +      if (cache->ed_ledge_pos == NULL) {
 +              vbo_pos = cache->ed_ledge_pos =
 +                      GPU_vertbuf_create_with_format(edit_mesh_overlay_pos_format(&attr_id.pos));
 +              GPU_vertbuf_data_alloc(vbo_pos, vbo_len_capacity);
 +      }
 +
 +      /* Normals */
 +      GPUVertBuf *vbo_nor = NULL;
 +      if (cache->ed_ledge_nor == NULL) {
 +              vbo_nor = cache->ed_ledge_nor =
 +                      GPU_vertbuf_create_with_format(edit_mesh_overlay_nor_format(&attr_id.vnor, NULL));
 +              GPU_vertbuf_data_alloc(vbo_nor, vbo_len_capacity);
 +      }
 +
 +      /* Data */
 +      GPUVertBuf *vbo_data = NULL;
 +      if (cache->ed_ledge_data == NULL) {
 +              vbo_data = cache->ed_ledge_data =
 +                      GPU_vertbuf_create_with_format(edit_mesh_overlay_data_format(&attr_id.data));
 +              GPU_vertbuf_data_alloc(vbo_data, vbo_len_capacity);
 +      }
 +
 +      if (rdata->edit_bmesh) {
 +              BMesh *bm = rdata->edit_bmesh->bm;
 +              for (uint i = 0; i < ledge_len; i++) {
 +                      const BMEdge *eed = BM_edge_at_index(bm, rdata->loose_edges[i]);
 +                      if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) {
 +                              add_overlay_loose_edge(
 +                                      rdata, vbo_pos, vbo_nor, vbo_data,
 +                                      attr_id.pos, attr_id.vnor, attr_id.data,
 +                                      eed, vbo_len_used);
 +                              vbo_len_used += 2;
 +                      }
 +              }
 +      }
 +
 +      /* Finish */
 +      if (vbo_len_used != vbo_len_capacity) {
 +              if (vbo_pos != NULL) {
 +                      GPU_vertbuf_data_resize(vbo_pos, vbo_len_used);
 +              }
 +              if (vbo_nor != NULL) {
 +                      GPU_vertbuf_data_resize(vbo_nor, vbo_len_used);
 +              }
 +              if (vbo_data != NULL) {
 +                      GPU_vertbuf_data_resize(vbo_data, vbo_len_used);
 +              }
 +      }
 +}
 +
 +static void mesh_batch_cache_create_overlay_lvert_buffers(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & (MR_DATATYPE_VERT | MR_DATATYPE_LOOPTRI));
 +
 +      BMesh *bm = rdata->edit_bmesh->bm;
 +      const int lvert_len = mesh_render_data_loose_verts_len_get(rdata);
 +
 +      const int vbo_len_capacity = lvert_len;
 +      int vbo_len_used = 0;
 +
 +      static struct { uint pos, vnor, data; } attr_id;
 +
 +      /* Positions */
 +      GPUVertBuf *vbo_pos = NULL;
 +      if (cache->ed_lvert_pos == NULL) {
 +              vbo_pos = cache->ed_lvert_pos =
 +                      GPU_vertbuf_create_with_format(edit_mesh_overlay_pos_format(&attr_id.pos));
 +              GPU_vertbuf_data_alloc(vbo_pos, vbo_len_capacity);
 +      }
 +
 +      /* Normals */
 +      GPUVertBuf *vbo_nor = NULL;
 +      if (cache->ed_lvert_nor == NULL) {
 +              vbo_nor = cache->ed_lvert_nor =
 +                      GPU_vertbuf_create_with_format(edit_mesh_overlay_nor_format(&attr_id.vnor, NULL));
 +              GPU_vertbuf_data_alloc(vbo_nor, vbo_len_capacity);
 +      }
 +
 +      /* Data */
 +      GPUVertBuf *vbo_data = NULL;
 +      if (cache->ed_lvert_data == NULL) {
 +              vbo_data = cache->ed_lvert_data =
 +                      GPU_vertbuf_create_with_format(edit_mesh_overlay_data_format(&attr_id.data));
 +              GPU_vertbuf_data_alloc(vbo_data, vbo_len_capacity);
 +      }
 +
 +      for (uint i = 0; i < lvert_len; i++) {
 +              BMVert *eve = BM_vert_at_index(bm, rdata->loose_verts[i]);
 +              add_overlay_loose_vert(
 +                      rdata, vbo_pos, vbo_nor, vbo_data,
 +                      attr_id.pos, attr_id.vnor, attr_id.data,
 +                      eve, vbo_len_used);
 +              vbo_len_used += 1;
 +      }
 +
 +      /* Finish */
 +      if (vbo_len_used != vbo_len_capacity) {
 +              if (vbo_pos != NULL) {
 +                      GPU_vertbuf_data_resize(vbo_pos, vbo_len_used);
 +              }
 +              if (vbo_nor != NULL) {
 +                      GPU_vertbuf_data_resize(vbo_nor, vbo_len_used);
 +              }
 +              if (vbo_data != NULL) {
 +                      GPU_vertbuf_data_resize(vbo_data, vbo_len_used);
 +              }
 +      }
 +}
 +
 +/* Position */
 +static GPUVertBuf *mesh_batch_cache_get_edit_tri_pos(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_VERT);
 +
 +      if (cache->ed_tri_pos == NULL) {
 +              mesh_batch_cache_create_overlay_tri_buffers(rdata, cache);
 +      }
 +
 +      return cache->ed_tri_pos;
 +}
 +
 +static GPUVertBuf *mesh_batch_cache_get_edit_ledge_pos(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_VERT);
 +
 +      if (cache->ed_ledge_pos == NULL) {
 +              mesh_batch_cache_create_overlay_ledge_buffers(rdata, cache);
 +      }
 +
 +      return cache->ed_ledge_pos;
 +}
 +
 +static GPUVertBuf *mesh_batch_cache_get_edit_lvert_pos(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_VERT);
 +
 +      if (cache->ed_lvert_pos == NULL) {
 +              mesh_batch_cache_create_overlay_lvert_buffers(rdata, cache);
 +      }
 +
 +      return cache->ed_lvert_pos;
 +}
 +
 +/* Normal */
 +static GPUVertBuf *mesh_batch_cache_get_edit_tri_nor(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_VERT);
 +
 +      if (cache->ed_tri_nor == NULL) {
 +              mesh_batch_cache_create_overlay_tri_buffers(rdata, cache);
 +      }
 +
 +      return cache->ed_tri_nor;
 +}
 +
 +static GPUVertBuf *mesh_batch_cache_get_edit_ledge_nor(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_VERT);
 +
 +      if (cache->ed_ledge_nor == NULL) {
 +              mesh_batch_cache_create_overlay_ledge_buffers(rdata, cache);
 +      }
 +
 +      return cache->ed_ledge_nor;
 +}
 +
 +static GPUVertBuf *mesh_batch_cache_get_edit_lvert_nor(
 +        MeshRenderData *rdata, MeshBatchCache *cache)
 +{
 +      BLI_assert(rdata->types & MR_DATATYPE_VERT);
 +
 +      if (cache->ed_lvert_nor == NULL) {
 +              mesh_batch_cache_create_overlay_lvert_buffers(rdata, cache);
 +      }
 +