diff --git a/CMakeLists.txt b/CMakeLists.txt
index bfff781e922c07b8ca41344ff6306667f27b75b8..d45cfefcb78c03565f40b365d247888a5fb8dcf8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1461,6 +1461,7 @@ elseif(CMAKE_C_COMPILER_ID MATCHES "MSVC")
 		"/wd4267"  # conversion from 'size_t' to 'type', possible loss of data
 		"/wd4305"  # truncation from 'type1' to 'type2'
 		"/wd4800"  # forcing value to bool 'true' or 'false'
+		"/wd4828"  # The file contains a character that is illegal 
 		# errors:
 		"/we4013"  # 'function' undefined; assuming extern returning int
 		"/we4133"  # incompatible pointer types
diff --git a/build_files/build_environment/cmake/boost.cmake b/build_files/build_environment/cmake/boost.cmake
index 2c6bf195e07894217f76da7bac7116e47ea5dab2..46840b7ead457a414d18054f258683e7e4552e6f 100644
--- a/build_files/build_environment/cmake/boost.cmake
+++ b/build_files/build_environment/cmake/boost.cmake
@@ -53,17 +53,20 @@ if(WIN32)
 	if(BUILD_MODE STREQUAL Release)
 		set(BOOST_HARVEST_CMD ${BOOST_HARVEST_CMD} && ${CMAKE_COMMAND} -E copy_directory ${LIBDIR}/boost/include/boost-1_60/ ${HARVEST_TARGET}/boost/include/)
 	endif()
+	set(BOOST_PATCH_COMMAND ${PATCH_CMD} --verbose -p 1 -N -d ${BUILD_DIR}/boost/src/external_boost < ${PATCH_DIR}/boost.diff)
 
 elseif(APPLE)
 	set(BOOST_CONFIGURE_COMMAND ./bootstrap.sh)
 	set(BOOST_BUILD_COMMAND ./bjam)
 	set(BOOST_BUILD_OPTIONS toolset=clang cxxflags=${PLATFORM_CXXFLAGS} linkflags=${PLATFORM_LDFLAGS} --disable-icu boost.locale.icu=off)
 	set(BOOST_HARVEST_CMD echo .)
+	set(BOOST_PATCH_COMMAND echo .)
 else()
 	set(BOOST_HARVEST_CMD echo .)
 	set(BOOST_CONFIGURE_COMMAND ./bootstrap.sh)
 	set(BOOST_BUILD_COMMAND ./bjam)
 	set(BOOST_BUILD_OPTIONS cxxflags=${PLATFORM_CXXFLAGS} --disable-icu boost.locale.icu=off)
+	set(BOOST_PATCH_COMMAND echo .)
 endif()
 
 set(BOOST_OPTIONS
@@ -96,6 +99,7 @@ ExternalProject_Add(external_boost
 	URL_HASH MD5=${BOOST_MD5}
 	PREFIX ${BUILD_DIR}/boost
 	UPDATE_COMMAND	""
+	PATCH_COMMAND ${BOOST_PATCH_COMMAND}
 	CONFIGURE_COMMAND ${BOOST_CONFIGURE_COMMAND}
 	BUILD_COMMAND ${BOOST_BUILD_COMMAND} ${BOOST_BUILD_OPTIONS} -j${MAKE_THREADS} architecture=x86 address-model=${BOOST_ADDRESS_MODEL} variant=${BOOST_BUILD_TYPE} link=static threading=multi ${BOOST_OPTIONS}	--prefix=${LIBDIR}/boost install
 	BUILD_IN_SOURCE 1
diff --git a/build_files/build_environment/patches/boost.diff b/build_files/build_environment/patches/boost.diff
new file mode 100644
index 0000000000000000000000000000000000000000..ea3ec035518d79a620fbd3629b1b93fb67914baa
--- /dev/null
+++ b/build_files/build_environment/patches/boost.diff
@@ -0,0 +1,15 @@
+--- a/boost/config/compiler/visualc.hpp       2015-12-08 11:55:19 -0700
++++ b/boost/config/compiler/visualc.hpp    2018-03-17 10:29:52 -0600
+@@ -287,12 +287,3 @@
+ #  define BOOST_COMPILER "Microsoft Visual C++ version " BOOST_STRINGIZE(BOOST_COMPILER_VERSION)
+ #endif
+
+-//
+-// last known and checked version is 19.00.23026 (VC++ 2015 RTM):
+-#if (_MSC_VER > 1900)
+-#  if defined(BOOST_ASSERT_CONFIG)
+-#     error "Unknown compiler version - please run the configure tests and report the results"
+-#  else
+-#     pragma message("Unknown compiler version - please run the configure tests and report the results")
+-#  endif
+-#endif
diff --git a/build_files/cmake/config/blender_lite.cmake b/build_files/cmake/config/blender_lite.cmake
index 7db26c3f7c0d6c99b8d1051606c24a087980279f..1a5e6a3158ada50f8c71be94f5b5e8a3cc95da8f 100644
--- a/build_files/cmake/config/blender_lite.cmake
+++ b/build_files/cmake/config/blender_lite.cmake
@@ -9,6 +9,7 @@ set(WITH_INSTALL_PORTABLE    ON  CACHE BOOL "" FORCE)
 set(WITH_SYSTEM_GLEW         ON  CACHE BOOL "" FORCE)
 
 set(WITH_ALEMBIC             OFF CACHE BOOL "" FORCE)
+set(WITH_BOOST               OFF CACHE BOOL "" FORCE)
 set(WITH_BUILDINFO           OFF CACHE BOOL "" FORCE)
 set(WITH_BULLET              OFF CACHE BOOL "" FORCE)
 set(WITH_CODEC_AVI           OFF CACHE BOOL "" FORCE)
@@ -54,4 +55,3 @@ set(WITH_RAYOPTIMIZATION     OFF CACHE BOOL "" FORCE)
 set(WITH_SDL                 OFF CACHE BOOL "" FORCE)
 set(WITH_X11_XINPUT          OFF CACHE BOOL "" FORCE)
 set(WITH_X11_XF86VMODE       OFF CACHE BOOL "" FORCE)
-
diff --git a/doc/python_api/examples/bpy.props.1.py b/doc/python_api/examples/bpy.props.1.py
index 5153462893002245435adb2cd50539facc85f6b4..dd3a3ebc4325bcc09bf0ee594ef995696d8dc7ba 100644
--- a/doc/python_api/examples/bpy.props.1.py
+++ b/doc/python_api/examples/bpy.props.1.py
@@ -2,30 +2,56 @@
 Operator Example
 ++++++++++++++++
 
-A common use of custom properties is for python based :class:`Operator` classes.
+A common use of custom properties is for python based :class:`Operator`
+classes. Test this code by running it in the text editor, or by clicking the
+button in the 3D Viewport's Tools panel. The latter will show the properties
+in the Redo panel and allow you to change them.
 """
-
 import bpy
 
 
-class DialogOperator(bpy.types.Operator):
-    bl_idname = "object.dialog_operator"
+class OBJECT_OT_property_example(bpy.types.Operator):
+    bl_idname = "object.property_example"
     bl_label = "Property Example"
+    bl_options = {'REGISTER', 'UNDO'}
 
     my_float = bpy.props.FloatProperty(name="Some Floating Point")
     my_bool = bpy.props.BoolProperty(name="Toggle Option")
     my_string = bpy.props.StringProperty(name="String Value")
 
     def execute(self, context):
-        print("Dialog Runs")
+        self.report({'INFO'}, 'F: %.2f  B: %s  S: %r' %
+                    (self.my_float, self.my_bool, self.my_string))
+        print('My float:', self.my_float)
+        print('My bool:', self.my_bool)
+        print('My string:', self.my_string)
         return {'FINISHED'}
 
-    def invoke(self, context, event):
-        wm = context.window_manager
-        return wm.invoke_props_dialog(self)
-
 
-bpy.utils.register_class(DialogOperator)
-
-# test call
-bpy.ops.object.dialog_operator('INVOKE_DEFAULT')
+class OBJECT_PT_property_example(bpy.types.Panel):
+    bl_idname = "object_PT_property_example"
+    bl_label = "Property Example"
+    bl_space_type = 'VIEW_3D'
+    bl_region_type = 'TOOLS'
+    bl_category = "Tools"
+
+    def draw(self, context):
+        # You can set the property values that should be used when the user
+        # presses the button in the UI.
+        props = self.layout.operator('object.property_example')
+        props.my_bool = True
+        props.my_string = "Shouldn't that be 47?"
+
+        # You can set properties dynamically:
+        if context.object:
+            props.my_float = context.object.location.x
+        else:
+            props.my_float = 327
+
+
+bpy.utils.register_class(OBJECT_OT_property_example)
+bpy.utils.register_class(OBJECT_PT_property_example)
+
+# Demo call. Be sure to also test in the 3D Viewport.
+bpy.ops.object.property_example(my_float=47, my_bool=True,
+                                my_string="Shouldn't that be 327?")
diff --git a/doc/python_api/examples/bpy.props.5.py b/doc/python_api/examples/bpy.props.5.py
index 87741cbab8ab3a0b887798c7d9e2bed644013985..a9e79fa0272459692f4550c3c07fa79e8eea1033 100644
--- a/doc/python_api/examples/bpy.props.5.py
+++ b/doc/python_api/examples/bpy.props.5.py
@@ -1,13 +1,12 @@
 """
-Get/Set Example
-+++++++++++++++
+Getter/Setter Example
++++++++++++++++++++++
 
-Get/Set functions can be used for boolean, int, float, string and enum properties.
+Getter/setter functions can be used for boolean, int, float, string and enum properties.
 If these callbacks are defined the property will not be stored in the ID properties
-automatically, instead the get/set functions will be called when the property is
-read or written from the API.
+automatically. Instead, the `get` and `set` functions will be called when the property
+is respectively read or written from the API.
 """
-
 import bpy
 
 
@@ -65,25 +64,24 @@ def set_enum(self, value):
 bpy.types.Scene.test_enum = bpy.props.EnumProperty(items=test_items, get=get_enum, set=set_enum)
 
 
-# Testing
-
+# Testing the properties:
 scene = bpy.context.scene
 
 scene.test_float = 12.34
-print(scene.test_float)
+print('test_float:', scene.test_float)
 
 scene.test_array = (True, False)
-print([x for x in scene.test_array])
+print('test_array:', tuple(scene.test_array))
 
 # scene.test_date = "blah"   # this would fail, property is read-only
-print(scene.test_date)
+print('test_date:', scene.test_date)
 
 scene.test_enum = 'BLUE'
-print(scene.test_enum)
-
-
-# >>> 12.34000015258789
-# >>> [True, False]
-# >>> 2013-01-05 16:33:52.135340
-# >>> setting value 3
-# >>> GREEN
+print('test_enum:', scene.test_enum)
+
+# The above outputs:
+# test_float: 12.34000015258789
+# test_array: (True, False)
+# test_date: 2018-03-14 11:36:53.158653
+# setting value 3
+# test_enum: GREEN
diff --git a/intern/cycles/app/cycles_standalone.cpp b/intern/cycles/app/cycles_standalone.cpp
index 2d4b0d35e548a87bd45982a7fd55b3bb6fac988e..c682744f5fafb8ca1d92a9ab53d8e749fd99cfde 100644
--- a/intern/cycles/app/cycles_standalone.cpp
+++ b/intern/cycles/app/cycles_standalone.cpp
@@ -51,6 +51,7 @@ struct Options {
 	SessionParams session_params;
 	bool quiet;
 	bool show_help, interactive, pause;
+	string output_path;
 } options;
 
 static void session_print(const string& str)
@@ -86,6 +87,34 @@ static void session_print_status()
 	session_print(status);
 }
 
+static bool write_render(const uchar *pixels, int w, int h, int channels)
+{
+	string msg = string_printf("Writing image %s", options.output_path.c_str());
+	session_print(msg);
+
+	ImageOutput *out = ImageOutput::create(options.output_path);
+	if(!out) {
+		return false;
+	}
+
+	ImageSpec spec(w, h, channels, TypeDesc::UINT8);
+	if(!out->open(options.output_path, spec)) {
+		return false;
+	}
+
+	/* conversion for different top/bottom convention */
+	out->write_image(TypeDesc::UINT8,
+		pixels + (h - 1) * w * channels,
+		AutoStride,
+		-w * channels,
+		AutoStride);
+
+	out->close();
+	delete out;
+
+	return true;
+}
+
 static BufferParams& session_buffer_params()
 {
 	static BufferParams buffer_params;
@@ -120,6 +149,7 @@ static void scene_init()
 
 static void session_init()
 {
+	options.session_params.write_render_cb = write_render;
 	options.session = new Session(options.session_params);
 
 	if(options.session_params.background && !options.quiet)
@@ -364,7 +394,7 @@ static void options_parse(int argc, const char **argv)
 		"--background", &options.session_params.background, "Render in background, without user interface",
 		"--quiet", &options.quiet, "In background mode, don't print progress messages",
 		"--samples %d", &options.session_params.samples, "Number of samples to render",
-		"--output %s", &options.session_params.output_path, "File path to write output image",
+		"--output %s", &options.output_path, "File path to write output image",
 		"--threads %d", &options.session_params.threads, "CPU Rendering Threads",
 		"--width  %d", &options.width, "Window width in pixel",
 		"--height %d", &options.height, "Window height in pixel",
diff --git a/intern/cycles/render/buffers.cpp b/intern/cycles/render/buffers.cpp
index e4931a6317cd1390a2080d9545f4d763957c75d5..91b739741bbdbe18867efc8137a2405a907033cb 100644
--- a/intern/cycles/render/buffers.cpp
+++ b/intern/cycles/render/buffers.cpp
@@ -21,7 +21,6 @@
 
 #include "util/util_foreach.h"
 #include "util/util_hash.h"
-#include "util/util_image.h"
 #include "util/util_math.h"
 #include "util/util_opengl.h"
 #include "util/util_time.h"
@@ -452,37 +451,5 @@ bool DisplayBuffer::draw_ready()
 	return (draw_width != 0 && draw_height != 0);
 }
 
-void DisplayBuffer::write(const string& filename)
-{
-	int w = draw_width;
-	int h = draw_height;
-
-	if(w == 0 || h == 0)
-		return;
-	
-	if(half_float)
-		return;
-
-	/* read buffer from device */
-	uchar4 *pixels = rgba_byte.copy_from_device(0, w, h);
-
-	/* write image */
-	ImageOutput *out = ImageOutput::create(filename);
-	ImageSpec spec(w, h, 4, TypeDesc::UINT8);
-
-	out->open(filename, spec);
-
-	/* conversion for different top/bottom convention */
-	out->write_image(TypeDesc::UINT8,
-		(uchar*)(pixels + (h-1)*w),
-		AutoStride,
-		-w*sizeof(uchar4),
-		AutoStride);
-
-	out->close();
-
-	delete out;
-}
-
 CCL_NAMESPACE_END
 
diff --git a/intern/cycles/render/buffers.h b/intern/cycles/render/buffers.h
index 028bfb83735d549062449de8e21f5bf2f768d055..dfc98fe20619e40707a7d89a58728d284819c928 100644
--- a/intern/cycles/render/buffers.h
+++ b/intern/cycles/render/buffers.h
@@ -113,7 +113,6 @@ public:
 	~DisplayBuffer();
 
 	void reset(BufferParams& params);
-	void write(const string& filename);
 
 	void draw_set(int width, int height);
 	void draw(Device *device, const DeviceDrawParams& draw_params);
diff --git a/intern/cycles/render/session.cpp b/intern/cycles/render/session.cpp
index 4115603855840dbf5d4af04bec7dcf7410393538..bb636dd962abbc5e9c1a26ad700ba14a67367bc5 100644
--- a/intern/cycles/render/session.cpp
+++ b/intern/cycles/render/session.cpp
@@ -55,7 +55,7 @@ Session::Session(const SessionParams& params_)
 
 	device = Device::create(params.device, stats, params.background);
 
-	if(params.background && params.output_path.empty()) {
+	if(params.background && !params.write_render_cb) {
 		buffers = NULL;
 		display = NULL;
 	}
@@ -101,7 +101,7 @@ Session::~Session()
 		wait();
 	}
 
-	if(!params.output_path.empty()) {
+	if(params.write_render_cb) {
 		/* tonemap and write out image if requested */
 		delete display;
 
@@ -109,8 +109,10 @@ Session::~Session()
 		display->reset(buffers->params);
 		tonemap(params.samples);
 
-		progress.set_status("Writing Image", params.output_path);
-		display->write(params.output_path);
+		int w = display->draw_width;
+		int h = display->draw_height;
+		uchar4 *pixels = display->rgba_byte.copy_from_device(0, w, h);
+		params.write_render_cb((uchar*)pixels, w, h, 4);
 	}
 
 	/* clean up */
diff --git a/intern/cycles/render/session.h b/intern/cycles/render/session.h
index 8495d95666b772b598a77b4f213c06a371611701..e63cad0d9777c8b6dfebc533efad3df8905b1309 100644
--- a/intern/cycles/render/session.h
+++ b/intern/cycles/render/session.h
@@ -45,7 +45,6 @@ public:
 	DeviceInfo device;
 	bool background;
 	bool progressive_refine;
-	string output_path;
 
 	bool progressive;
 	bool experimental;
@@ -71,11 +70,15 @@ public:
 
 	ShadingSystem shadingsystem;
 
+	function<bool(const uchar *pixels,
+	              int width,
+	              int height,
+	              int channels)> write_render_cb;
+
 	SessionParams()
 	{
 		background = false;
 		progressive_refine = false;
-		output_path = "";
 
 		progressive = false;
 		experimental = false;
@@ -106,7 +109,6 @@ public:
 	{ return !(device == params.device
 		&& background == params.background
 		&& progressive_refine == params.progressive_refine
-		&& output_path == params.output_path
 		/* && samples == params.samples */
 		&& progressive == params.progressive
 		&& experimental == params.experimental
diff --git a/intern/gawain/gawain/gwn_element.h b/intern/gawain/gawain/gwn_element.h
index 3081305769ff40a76dd4d2309cc1a8f9b37c7c17..7a28ab183f82be51c9baec410bab1ccb233292af 100644
--- a/intern/gawain/gawain/gwn_element.h
+++ b/intern/gawain/gawain/gwn_element.h
@@ -15,6 +15,8 @@
 
 #define GWN_TRACK_INDEX_RANGE 1
 
+#define GWN_PRIM_RESTART 0xFFFFFFFF
+
 typedef enum {
 	GWN_INDEX_U8, // GL has this, Vulkan does not
 	GWN_INDEX_U16,
@@ -30,8 +32,8 @@ typedef struct Gwn_IndexBuf {
 	unsigned max_index;
 	unsigned base_index;
 #endif
-	void* data; // NULL indicates data in VRAM (unmapped) or not yet allocated
 	GLuint vbo_id; // 0 indicates not yet sent to VRAM
+	bool use_prim_restart;
 } Gwn_IndexBuf;
 
 void GWN_indexbuf_use(Gwn_IndexBuf*);
@@ -43,17 +45,18 @@ typedef struct Gwn_IndexBufBuilder {
 	unsigned index_ct;
 	Gwn_PrimType prim_type;
 	unsigned* data;
+	bool use_prim_restart;
 } Gwn_IndexBufBuilder;
 
-// supported primitives:
-//  GWN_PRIM_POINTS
-//  GWN_PRIM_LINES
-//  GWN_PRIM_TRIS
 
+// supports all primitive types.
+void GWN_indexbuf_init_ex(Gwn_IndexBufBuilder*, Gwn_PrimType, unsigned index_ct, unsigned vertex_ct, bool use_prim_restart);
+
+// supports only GWN_PRIM_POINTS, GWN_PRIM_LINES and GWN_PRIM_TRIS.
 void GWN_indexbuf_init(Gwn_IndexBufBuilder*, Gwn_PrimType, unsigned prim_ct, unsigned vertex_ct);
-//void GWN_indexbuf_init_custom(Gwn_IndexBufBuilder*, Gwn_PrimType, unsigned index_ct, unsigned vertex_ct);
 
 void GWN_indexbuf_add_generic_vert(Gwn_IndexBufBuilder*, unsigned v);
+void GWN_indexbuf_add_primitive_restart(Gwn_IndexBufBuilder*);
 
 void GWN_indexbuf_add_point_vert(Gwn_IndexBufBuilder*, unsigned v);
 void GWN_indexbuf_add_line_verts(Gwn_IndexBufBuilder*, unsigned v1, unsigned v2);
diff --git a/intern/gawain/gawain/gwn_vertex_buffer.h b/intern/gawain/gawain/gwn_vertex_buffer.h
index 34f12754f40e46a53af7097d531562ca68de0c61..d9faae4f55f794067d91ca6ae568b1fd1758a93b 100644
--- a/intern/gawain/gawain/gwn_vertex_buffer.h
+++ b/intern/gawain/gawain/gwn_vertex_buffer.h
@@ -22,33 +22,41 @@
 
 // Is Gwn_VertBuf always used as part of a Gwn_Batch?
 
+typedef enum {
+	// can be extended to support more types
+	GWN_USAGE_STREAM,
+	GWN_USAGE_STATIC,
+	GWN_USAGE_DYNAMIC
+} Gwn_UsageType;
+
 typedef struct Gwn_VertBuf {
 	Gwn_VertFormat format;
 	unsigned vertex_ct;
-	unsigned alloc_ct; // size in vertex of alloced data
-#if VRAM_USAGE
-	unsigned vram_size; // size in byte of data present in the VRAM
-#endif
-	unsigned data_dirty : 1; // does the data has been touched since last transfert
-	unsigned data_dynamic : 1; // do we keep the RAM allocation for further updates?
-	unsigned data_resized : 1; // does the data has been resized since last transfert
-	GLubyte* data; // NULL indicates data in VRAM (unmapped) or not yet allocated
-	GLuint vbo_id; // 0 indicates not yet sent to VRAM
+	GLubyte* data; // NULL indicates data in VRAM (unmapped)
+	GLuint vbo_id; // 0 indicates not yet allocated
+	Gwn_UsageType usage; // usage hint for GL optimisation
 } Gwn_VertBuf;
 
-Gwn_VertBuf* GWN_vertbuf_create(void);
-Gwn_VertBuf* GWN_vertbuf_create_with_format(const Gwn_VertFormat*);
-Gwn_VertBuf* GWN_vertbuf_create_dynamic_with_format(const Gwn_VertFormat*);
+Gwn_VertBuf* GWN_vertbuf_create(Gwn_UsageType);
+Gwn_VertBuf* GWN_vertbuf_create_with_format_ex(const Gwn_VertFormat*, Gwn_UsageType);
+
+#define GWN_vertbuf_create_with_format(format) \
+	GWN_vertbuf_create_with_format_ex(format, GWN_USAGE_STATIC)
 
-void GWN_vertbuf_clear(Gwn_VertBuf*);
 void GWN_vertbuf_discard(Gwn_VertBuf*);
 
-void GWN_vertbuf_init(Gwn_VertBuf*);
-void GWN_vertbuf_init_with_format(Gwn_VertBuf*, const Gwn_VertFormat*);
+void GWN_vertbuf_init(Gwn_VertBuf*, Gwn_UsageType);
+void GWN_vertbuf_init_with_format_ex(Gwn_VertBuf*, const Gwn_VertFormat*, Gwn_UsageType);
+
+#define GWN_vertbuf_init_with_format(verts, format) \
+	GWN_vertbuf_init_with_format_ex(verts, format, GWN_USAGE_STATIC)
 
 unsigned GWN_vertbuf_size_get(const Gwn_VertBuf*);
 void GWN_vertbuf_data_alloc(Gwn_VertBuf*, unsigned v_ct);
-void GWN_vertbuf_data_resize(Gwn_VertBuf*, unsigned v_ct);
+void GWN_vertbuf_data_resize_ex(Gwn_VertBuf*, unsigned v_ct, bool keep_data);
+
+#define GWN_vertbuf_data_resize(verts, v_ct) \
+	GWN_vertbuf_data_resize_ex(verts, v_ct, true)
 
 // The most important set_attrib variant is the untyped one. Get it right first.
 // It takes a void* so the app developer is responsible for matching their app data types
diff --git a/intern/gawain/src/gwn_batch.c b/intern/gawain/src/gwn_batch.c
index 9dab2a4d7d9a3e8a7dc0cf3e13a5fd58980f057c..c720ba967b598145c008e0f8b46b22690b533f45 100644
--- a/intern/gawain/src/gwn_batch.c
+++ b/intern/gawain/src/gwn_batch.c
@@ -211,7 +211,7 @@ static GLuint batch_vao_get(Gwn_Batch *batch)
 		batch->context = GWN_context_active_get();
 		gwn_context_add_batch(batch->context, batch);
 		}
-#if TRUST_NO_ONE && 0 // disabled until we use a separate single context for UI.
+#if TRUST_NO_ONE
 	else // Make sure you are not trying to draw this batch in another context.
 		assert(batch->context == GWN_context_active_get());
 #endif
@@ -489,6 +489,27 @@ void GWN_batch_uniform_4fv(Gwn_Batch* batch, const char* name, const float data[
 	glUniform4fv(uniform->location, 1, data);
 	}
 
+static void primitive_restart_enable(const Gwn_IndexBuf *el)
+{
+	// TODO(fclem) Replace by GL_PRIMITIVE_RESTART_FIXED_INDEX when we have ogl 4.3
+	glEnable(GL_PRIMITIVE_RESTART);
+	GLuint restart_index = (GLuint)0xFFFFFFFF;
+
+#if GWN_TRACK_INDEX_RANGE
+	if (el->index_type == GWN_INDEX_U8)
+		restart_index = (GLuint)0xFF;
+	else if (el->index_type == GWN_INDEX_U16)
+		restart_index = (GLuint)0xFFFF;
+#endif
+
+	glPrimitiveRestartIndex(restart_index);
+}
+
+static void primitive_restart_disable(void)
+{
+	glDisable(GL_PRIMITIVE_RESTART);
+}
+
 void GWN_batch_draw(Gwn_Batch* batch)
 	{
 #if TRUST_NO_ONE
@@ -508,9 +529,10 @@ void GWN_batch_draw_range_ex(Gwn_Batch* batch, int v_first, int v_count, bool fo
 #if TRUST_NO_ONE
 	assert(!(force_instance && (batch->inst == NULL)) || v_count > 0); // we cannot infer length if force_instance
 #endif
+	const bool do_instance = (force_instance || batch->inst);
 
 	// If using offset drawing, use the default VAO and redo bindings.
-	if (v_first != 0)
+	if (v_first != 0 && (do_instance || batch->elem))
 		{
 		glBindVertexArray(GWN_vao_default());
 		batch_update_program_bindings(batch, v_first);
@@ -518,7 +540,7 @@ void GWN_batch_draw_range_ex(Gwn_Batch* batch, int v_first, int v_count, bool fo
 	else
 		glBindVertexArray(batch->vao_id);
 
-	if (force_instance || batch->inst)
+	if (do_instance)
 		{
 		// Infer length if vertex count is not given
 		if (v_count == 0)
@@ -528,11 +550,16 @@ void GWN_batch_draw_range_ex(Gwn_Batch* batch, int v_first, int v_count, bool fo
 			{
 			const Gwn_IndexBuf* el = batch->elem;
 
+			if (el->use_prim_restart)
+				primitive_restart_enable(el);
+
 #if GWN_TRACK_INDEX_RANGE
 			glDrawElementsInstancedBaseVertex(batch->gl_prim_type, el->index_ct, el->gl_index_type, 0, v_count, el->base_index);
 #else
 			glDrawElementsInstanced(batch->gl_prim_type, el->index_ct, GL_UNSIGNED_INT, 0, v_count);
 #endif
+			if (el->use_prim_restart)
+				primitive_restart_disable();
 			}
 		else
 			glDrawArraysInstanced(batch->gl_prim_type, 0, batch->verts[0]->vertex_ct, v_count);
@@ -547,6 +574,9 @@ void GWN_batch_draw_range_ex(Gwn_Batch* batch, int v_first, int v_count, bool fo
 			{
 			const Gwn_IndexBuf* el = batch->elem;
 
+			if (el->use_prim_restart)
+				primitive_restart_enable(el);
+
 #if GWN_TRACK_INDEX_RANGE
 			if (el->base_index)
 				glDrawRangeElementsBaseVertex(batch->gl_prim_type, el->min_index, el->max_index, v_count, el->gl_index_type, 0, el->base_index);
@@ -555,13 +585,16 @@ void GWN_batch_draw_range_ex(Gwn_Batch* batch, int v_first, int v_count, bool fo
 #else
 			glDrawElements(batch->gl_prim_type, v_count, GL_UNSIGNED_INT, 0);
 #endif
+			if (el->use_prim_restart)
+				primitive_restart_disable();
 			}
 		else
-			glDrawArrays(batch->gl_prim_type, 0, v_count);
+			glDrawArrays(batch->gl_prim_type, v_first, v_count);
 		}
 
-
-	glBindVertexArray(0);
+	// Performance hog if you are drawing with the same vao multiple time.
+	// Only activate for debugging.
+	// glBindVertexArray(0);
 	}
 
 // just draw some vertices and let shader place them where we want.
@@ -573,5 +606,7 @@ void GWN_draw_primitive(Gwn_PrimType prim_type, int v_count)
 	GLenum type = convert_prim_type_to_gl(prim_type);
 	glDrawArrays(type, 0, v_count);
 
-	glBindVertexArray(0);
+	// Performance hog if you are drawing with the same vao multiple time.
+	// Only activate for debugging.
+	// glBindVertexArray(0);
 	}
diff --git a/intern/gawain/src/gwn_element.c b/intern/gawain/src/gwn_element.c
index f31b64fa2324cf0beda5ca174b3699e5d0b930d7..0b7dc675f87f53ad383b4e19601abbcfbf95032e 100644
--- a/intern/gawain/src/gwn_element.c
+++ b/intern/gawain/src/gwn_element.c
@@ -39,26 +39,14 @@ unsigned GWN_indexbuf_size_get(const Gwn_IndexBuf* elem)
 #endif
 	}
 
-static void ElementList_prime(Gwn_IndexBuf* elem)
+void GWN_indexbuf_init_ex(Gwn_IndexBufBuilder* builder, Gwn_PrimType prim_type, unsigned index_ct, unsigned vertex_ct, bool use_prim_restart)
 	{
-	elem->vbo_id = GWN_buf_id_alloc();
-	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elem->vbo_id);
-	// fill with delicious data & send to GPU the first time only
-	glBufferData(GL_ELEMENT_ARRAY_BUFFER, GWN_indexbuf_size_get(elem), elem->data, GL_STATIC_DRAW);
-
-#if KEEP_SINGLE_COPY
-	// now that GL has a copy, discard original
-	free(elem->data);
-	elem->data = NULL;
-#endif
-	}
-
-void GWN_indexbuf_use(Gwn_IndexBuf* elem)
-	{
-	if (elem->vbo_id)
-		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elem->vbo_id);
-	else
-		ElementList_prime(elem);
+	builder->use_prim_restart = use_prim_restart;
+	builder->max_allowed_index = vertex_ct - 1;
+	builder->max_index_ct = index_ct;
+	builder->index_ct = 0; // start empty
+	builder->prim_type = prim_type;
+	builder->data = calloc(builder->max_index_ct, sizeof(unsigned));
 	}
 
 void GWN_indexbuf_init(Gwn_IndexBufBuilder* builder, Gwn_PrimType prim_type, unsigned prim_ct, unsigned vertex_ct)
@@ -82,11 +70,7 @@ void GWN_indexbuf_init(Gwn_IndexBufBuilder* builder, Gwn_PrimType prim_type, uns
 			return;
 		}
 
-	builder->max_allowed_index = vertex_ct - 1;
-	builder->max_index_ct = prim_ct * verts_per_prim;
-	builder->index_ct = 0; // start empty
-	builder->prim_type = prim_type;
-	builder->data = calloc(builder->max_index_ct, sizeof(unsigned));
+	GWN_indexbuf_init_ex(builder, prim_type, prim_ct * verts_per_prim, vertex_ct, false);
 	}
 
 void GWN_indexbuf_add_generic_vert(Gwn_IndexBufBuilder* builder, unsigned v)
@@ -100,6 +84,17 @@ void GWN_indexbuf_add_generic_vert(Gwn_IndexBufBuilder* builder, unsigned v)
 	builder->data[builder->index_ct++] = v;
 	}
 
+void GWN_indexbuf_add_primitive_restart(Gwn_IndexBufBuilder* builder)
+	{
+#if TRUST_NO_ONE
+	assert(builder->data != NULL);
+	assert(builder->index_ct < builder->max_index_ct);
+	assert(builder->use_prim_restart);
+#endif
+
+	builder->data[builder->index_ct++] = GWN_PRIM_RESTART;
+	}
+
 void GWN_indexbuf_add_point_vert(Gwn_IndexBufBuilder* builder, unsigned v)
 	{
 #if TRUST_NO_ONE
@@ -149,7 +144,9 @@ static unsigned index_range(const unsigned values[], unsigned value_ct, unsigned
 	for (unsigned i = 1; i < value_ct; ++i)
 		{
 		const unsigned value = values[i];
-		if (value < min_value)
+		if (value == GWN_PRIM_RESTART)
+			continue;
+		else if (value < min_value)
 			min_value = value;
 		else if (value > max_value)
 			max_value = value;
@@ -159,10 +156,14 @@ static unsigned index_range(const unsigned values[], unsigned value_ct, unsigned
 	return max_value - min_value;
 	}
 
-static void squeeze_indices_byte(const unsigned values[], Gwn_IndexBuf* elem)
+static void squeeze_indices_byte(Gwn_IndexBufBuilder *builder, Gwn_IndexBuf* elem)
 	{
+	const unsigned *values = builder->data;
 	const unsigned index_ct = elem->index_ct;
-	GLubyte* data = malloc(index_ct * sizeof(GLubyte));
+
+	// data will never be *larger* than builder->data...
+	// converting in place to avoid extra allocation
+	GLubyte *data = (GLubyte *)builder->data;
 
 	if (elem->max_index > 0xFF)
 		{
@@ -173,7 +174,7 @@ static void squeeze_indices_byte(const unsigned values[], Gwn_IndexBuf* elem)
 		elem->max_index -= base;
 
 		for (unsigned i = 0; i < index_ct; ++i)
-			data[i] = (GLubyte)(values[i] - base);
+			data[i] = (values[i] == GWN_PRIM_RESTART) ? 0xFF : (GLubyte)(values[i] - base);
 		}
 	else
 		{
@@ -182,14 +183,16 @@ static void squeeze_indices_byte(const unsigned values[], Gwn_IndexBuf* elem)
 		for (unsigned i = 0; i < index_ct; ++i)
 			data[i] = (GLubyte)(values[i]);
 		}
-
-	elem->data = data;
 	}
 
-static void squeeze_indices_short(const unsigned values[], Gwn_IndexBuf* elem)
+static void squeeze_indices_short(Gwn_IndexBufBuilder *builder, Gwn_IndexBuf* elem)
 	{
+	const unsigned *values = builder->data;
 	const unsigned index_ct = elem->index_ct;
-	GLushort* data = malloc(index_ct * sizeof(GLushort));
+
+	// data will never be *larger* than builder->data...
+	// converting in place to avoid extra allocation
+	GLushort *data = (GLushort *)builder->data;
 
 	if (elem->max_index > 0xFFFF)
 		{
@@ -200,7 +203,7 @@ static void squeeze_indices_short(const unsigned values[], Gwn_IndexBuf* elem)
 		elem->max_index -= base;
 
 		for (unsigned i = 0; i < index_ct; ++i)
-			data[i] = (GLushort)(values[i] - base);
+			data[i] = (values[i] == GWN_PRIM_RESTART) ? 0xFFFF : (GLushort)(values[i] - base);
 		}
 	else
 		{
@@ -209,8 +212,6 @@ static void squeeze_indices_short(const unsigned values[], Gwn_IndexBuf* elem)
 		for (unsigned i = 0; i < index_ct; ++i)
 			data[i] = (GLushort)(values[i]);
 		}
-
-	elem->data = data;
 	}
 
 #endif // GWN_TRACK_INDEX_RANGE
@@ -229,67 +230,56 @@ void GWN_indexbuf_build_in_place(Gwn_IndexBufBuilder* builder, Gwn_IndexBuf* ele
 #endif
 
 	elem->index_ct = builder->index_ct;
+	elem->use_prim_restart = builder->use_prim_restart;
 
 #if GWN_TRACK_INDEX_RANGE
-	const unsigned range = index_range(builder->data, builder->index_ct, &elem->min_index, &elem->max_index);
+	unsigned range = index_range(builder->data, builder->index_ct, &elem->min_index, &elem->max_index);
+
+	// count the primitive restart index.
+	if (elem->use_prim_restart)
+		range += 1;
 
 	if (range <= 0xFF)
 		{
 		elem->index_type = GWN_INDEX_U8;
-		squeeze_indices_byte(builder->data, elem);
+		squeeze_indices_byte(builder, elem);
 		}
 	else if (range <= 0xFFFF)
 		{
 		elem->index_type = GWN_INDEX_U16;
-		squeeze_indices_short(builder->data, elem);
+		squeeze_indices_short(builder, elem);
 		}
 	else
 		{
 		elem->index_type = GWN_INDEX_U32;
 		elem->base_index = 0;
-
-		if (builder->index_ct < builder->max_index_ct)
-			{
-			builder->data = realloc(builder->data, builder->index_ct * sizeof(unsigned));
-			// TODO: realloc only if index_ct is much smaller than max_index_ct
-			}
-
-		elem->data = builder->data;
 		}
 
 	elem->gl_index_type = convert_index_type_to_gl(elem->index_type);
-#else
-	if (builder->index_ct < builder->max_index_ct)
-		{
-		builder->data = realloc(builder->data, builder->index_ct * sizeof(unsigned));
-		// TODO: realloc only if index_ct is much smaller than max_index_ct
-		}
-
-	elem->data = builder->data;
 #endif
 
-	// elem->data will never be *larger* than builder->data... how about converting
-	// in place to avoid extra allocation?
+	if (elem->vbo_id == 0)
+		elem->vbo_id = GWN_buf_id_alloc();
 
-	elem->vbo_id = 0;
-	// TODO: create GL buffer object directly, based on an input flag
+	// send data to GPU
+	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elem->vbo_id);
+	glBufferData(GL_ELEMENT_ARRAY_BUFFER, GWN_indexbuf_size_get(elem), builder->data, GL_STATIC_DRAW);
 
 	// discard builder (one-time use)
-	if (builder->data != elem->data)
-		free(builder->data);
+	free(builder->data);
 	builder->data = NULL;
 	// other fields are safe to leave
 	}
 
+void GWN_indexbuf_use(Gwn_IndexBuf* elem)
+	{
+	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elem->vbo_id);
+	}
+
 void GWN_indexbuf_discard(Gwn_IndexBuf* elem)
 	{
 	if (elem->vbo_id)
 		GWN_buf_id_free(elem->vbo_id);
-#if KEEP_SINGLE_COPY
-	else
-#endif
-	if (elem->data)
-		free(elem->data);
 
 	free(elem);
 	}
diff --git a/intern/gawain/src/gwn_immediate.c b/intern/gawain/src/gwn_immediate.c
index f063665b423c9f108f39e0bec7085fc1a5139ee2..c6df3ada0185baf3d8ca365c7030a91533decc0e 100644
--- a/intern/gawain/src/gwn_immediate.c
+++ b/intern/gawain/src/gwn_immediate.c
@@ -277,8 +277,6 @@ Gwn_Batch* immBeginBatch(Gwn_PrimType prim_type, unsigned vertex_ct)
 	imm.batch = GWN_batch_create(prim_type, verts, NULL);
 	imm.batch->phase = GWN_BATCH_BUILDING;
 
-	GWN_batch_program_set(imm.batch, imm.bound_program, imm.shader_interface);
-
 	return imm.batch;
 	}
 
@@ -398,6 +396,7 @@ void immEnd(void)
 			// TODO: resize only if vertex count is much smaller
 			}
 
+		GWN_batch_program_set(imm.batch, imm.bound_program, imm.shader_interface);
 		imm.batch->phase = GWN_BATCH_READY_TO_DRAW;
 		imm.batch = NULL; // don't free, batch belongs to caller
 		}
diff --git a/intern/gawain/src/gwn_vertex_buffer.c b/intern/gawain/src/gwn_vertex_buffer.c
index 32bef765390c163ac776cb7cb8fb3dde84f18320..35538342c2dbbd9fab08df151c3c7a9d14a90150 100644
--- a/intern/gawain/src/gwn_vertex_buffer.c
+++ b/intern/gawain/src/gwn_vertex_buffer.c
@@ -19,16 +19,26 @@
 
 static unsigned vbo_memory_usage;
 
-Gwn_VertBuf* GWN_vertbuf_create(void)
+static GLenum convert_usage_type_to_gl(Gwn_UsageType type)
+	{
+	static const GLenum table[] = {
+		[GWN_USAGE_STREAM] = GL_STREAM_DRAW,
+		[GWN_USAGE_STATIC] = GL_STATIC_DRAW,
+		[GWN_USAGE_DYNAMIC] = GL_DYNAMIC_DRAW
+		};
+	return table[type];
+	}
+
+Gwn_VertBuf* GWN_vertbuf_create(Gwn_UsageType usage)
 	{
 	Gwn_VertBuf* verts = malloc(sizeof(Gwn_VertBuf));
-	GWN_vertbuf_init(verts);
+	GWN_vertbuf_init(verts, usage);
 	return verts;
 	}
 
-Gwn_VertBuf* GWN_vertbuf_create_with_format(const Gwn_VertFormat* format)
+Gwn_VertBuf* GWN_vertbuf_create_with_format_ex(const Gwn_VertFormat* format, Gwn_UsageType usage)
 	{
-	Gwn_VertBuf* verts = GWN_vertbuf_create();
+	Gwn_VertBuf* verts = GWN_vertbuf_create(usage);
 	GWN_vertformat_copy(&verts->format, format);
 	if (!format->packed)
 		VertexFormat_pack(&verts->format);
@@ -38,61 +48,30 @@ Gwn_VertBuf* GWN_vertbuf_create_with_format(const Gwn_VertFormat* format)
 	// TODO: implement those memory savings
 	}
 
-Gwn_VertBuf* GWN_vertbuf_create_dynamic_with_format(const Gwn_VertFormat* format)
-	{
-	Gwn_VertBuf* verts = GWN_vertbuf_create_with_format(format);
-	verts->data_dynamic = true;
-	return verts;
-	}
-
-void GWN_vertbuf_init(Gwn_VertBuf* verts)
+void GWN_vertbuf_init(Gwn_VertBuf* verts, Gwn_UsageType usage)
 	{
 	memset(verts, 0, sizeof(Gwn_VertBuf));
+	verts->usage = usage;
 	}
 
-void GWN_vertbuf_init_with_format(Gwn_VertBuf* verts, const Gwn_VertFormat* format)
+void GWN_vertbuf_init_with_format_ex(Gwn_VertBuf* verts, const Gwn_VertFormat* format, Gwn_UsageType usage)
 	{
-	GWN_vertbuf_init(verts);
+	GWN_vertbuf_init(verts, usage);
 	GWN_vertformat_copy(&verts->format, format);
 	if (!format->packed)
 		VertexFormat_pack(&verts->format);
 	}
 
-/**
- * Like #GWN_vertbuf_discard but doesn't free.
- */
-void GWN_vertbuf_clear(Gwn_VertBuf* verts)
-	{
-	if (verts->vbo_id) {
-		GWN_buf_id_free(verts->vbo_id);
-#if VRAM_USAGE
-		vbo_memory_usage -= verts->vram_size;
-#endif
-	}
-
-	if (verts->data)
-		{
-		free(verts->data);
-		verts->data = NULL;
-		}
-	}
-
 void GWN_vertbuf_discard(Gwn_VertBuf* verts)
 	{
 	if (verts->vbo_id)
 		{
 		GWN_buf_id_free(verts->vbo_id);
 #if VRAM_USAGE
-		vbo_memory_usage -= verts->vram_size;
+		vbo_memory_usage -= GWN_vertbuf_size_get(verts);
 #endif
 		}
 
-	if (verts->data)
-		{
-		free(verts->data);
-		}
-
-
 	free(verts);
 	}
 
@@ -109,25 +88,82 @@ void GWN_vertbuf_data_alloc(Gwn_VertBuf* verts, unsigned v_ct)
 
 	verts->vertex_ct = v_ct;
 
-	// Data initially lives in main memory. Will be transferred to VRAM when we "prime" it.
-	verts->data = malloc(GWN_vertbuf_size_get(verts));
+#if TRUST_NO_ONE
+	assert(verts->vbo_id == 0);
+#endif
+
+	unsigned buffer_sz = GWN_vertbuf_size_get(verts);
+#if VRAM_USAGE
+	vbo_memory_usage += buffer_sz;
+#endif
+
+	// create an array buffer and map it to memory
+	verts->vbo_id = GWN_buf_id_alloc();
+	glBindBuffer(GL_ARRAY_BUFFER, verts->vbo_id);
+	glBufferData(GL_ARRAY_BUFFER, buffer_sz, NULL, convert_usage_type_to_gl(verts->usage));
+	verts->data = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
 	}
 
-void GWN_vertbuf_data_resize(Gwn_VertBuf* verts, unsigned v_ct)
+void GWN_vertbuf_data_resize_ex(Gwn_VertBuf* verts, unsigned v_ct, bool keep_data)
 	{
 #if TRUST_NO_ONE
-	assert(verts->vertex_ct != v_ct); // allow this?
-	assert(verts->data != NULL); // has already been allocated
-	assert(verts->vbo_id == 0 || verts->data_dynamic); // has not been sent to VRAM
+	assert(verts->vbo_id != 0);
 #endif
 
-	// for dynamic buffers
-	verts->data_resized = true;
+	if (verts->vertex_ct == v_ct)
+		return;
 
+	unsigned old_buf_sz = GWN_vertbuf_size_get(verts);
 	verts->vertex_ct = v_ct;
-	verts->data = realloc(verts->data, GWN_vertbuf_size_get(verts));
-	// TODO: skip realloc if v_ct < existing vertex count
-	// extra space will be reclaimed, and never sent to VRAM (see VertexBuffer_prime)
+	unsigned new_buf_sz = GWN_vertbuf_size_get(verts);
+#if VRAM_USAGE
+	vbo_memory_usage += new_buf_sz - old_buf_sz;
+#endif
+
+	if (keep_data)
+		{
+		// we need to do a copy to keep the existing data
+		GLuint vbo_tmp;
+		glGenBuffers(1, &vbo_tmp);
+		// only copy the data that can fit in the new buffer
+		unsigned copy_sz = (old_buf_sz < new_buf_sz) ? old_buf_sz : new_buf_sz;
+		glBindBuffer(GL_COPY_WRITE_BUFFER, vbo_tmp);
+		glBufferData(GL_COPY_WRITE_BUFFER, copy_sz, NULL, GL_STREAM_COPY);
+
+		glBindBuffer(GL_COPY_READ_BUFFER, verts->vbo_id);
+		// we cannot copy from/to a mapped buffer
+		if (verts->data)
+			glUnmapBuffer(GL_COPY_READ_BUFFER);
+
+		// save data, resize the buffer, restore data
+		glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, copy_sz);
+		glBufferData(GL_COPY_READ_BUFFER, new_buf_sz, NULL, convert_usage_type_to_gl(verts->usage));
+		glCopyBufferSubData(GL_COPY_WRITE_BUFFER, GL_COPY_READ_BUFFER, 0, 0, copy_sz);
+
+		glDeleteBuffers(1, &vbo_tmp);
+		}
+	else
+		{
+		glBindBuffer(GL_COPY_READ_BUFFER, verts->vbo_id);
+		glBufferData(GL_COPY_READ_BUFFER, new_buf_sz, NULL, convert_usage_type_to_gl(verts->usage));
+		}
+
+	// if the buffer was mapped, update it's pointer
+	if (verts->data)
+		verts->data = glMapBuffer(GL_COPY_READ_BUFFER, GL_WRITE_ONLY);
+	}
+
+static void VertexBuffer_map(Gwn_VertBuf* verts)
+	{
+	glBindBuffer(GL_ARRAY_BUFFER, verts->vbo_id);
+	verts->data = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
+	}
+
+static void VertexBuffer_unmap(Gwn_VertBuf* verts)
+	{
+	glBindBuffer(GL_ARRAY_BUFFER, verts->vbo_id);
+	glUnmapBuffer(GL_ARRAY_BUFFER);
+	verts->data = NULL;
 	}
 
 void GWN_vertbuf_attr_set(Gwn_VertBuf* verts, unsigned a_idx, unsigned v_idx, const void* data)
@@ -135,14 +171,14 @@ void GWN_vertbuf_attr_set(Gwn_VertBuf* verts, unsigned a_idx, unsigned v_idx, co
 	const Gwn_VertFormat* format = &verts->format;
 	const Gwn_VertAttr* a = format->attribs + a_idx;
 
-	verts->data_dirty = true;
-
 #if TRUST_NO_ONE
 	assert(a_idx < format->attrib_ct);
 	assert(v_idx < verts->vertex_ct);
-	assert(verts->data != NULL); // data must be in main mem
 #endif
 
+	if (verts->data == NULL)
+		VertexBuffer_map(verts);
+
 	memcpy((GLubyte*)verts->data + a->offset + v_idx * format->stride, data, a->sz);
 	}
 
@@ -151,8 +187,6 @@ void GWN_vertbuf_attr_fill(Gwn_VertBuf* verts, unsigned a_idx, const void* data)
 	const Gwn_VertFormat* format = &verts->format;
 	const Gwn_VertAttr* a = format->attribs + a_idx;
 
-	verts->data_dirty = true;
-
 #if TRUST_NO_ONE
 	assert(a_idx < format->attrib_ct);
 #endif
@@ -167,15 +201,15 @@ void GWN_vertbuf_attr_fill_stride(Gwn_VertBuf* verts, unsigned a_idx, unsigned s
 	const Gwn_VertFormat* format = &verts->format;
 	const Gwn_VertAttr* a = format->attribs + a_idx;
 
-	verts->data_dirty = true;
-
 #if TRUST_NO_ONE
 	assert(a_idx < format->attrib_ct);
-	assert(verts->data != NULL); // data must be in main mem
 #endif
 
 	const unsigned vertex_ct = verts->vertex_ct;
 
+	if (verts->data == NULL)
+		VertexBuffer_map(verts);
+
 	if (format->attrib_ct == 1 && stride == format->stride)
 		{
 		// we can copy it all at once
@@ -196,9 +230,11 @@ void GWN_vertbuf_attr_get_raw_data(Gwn_VertBuf* verts, unsigned a_idx, Gwn_VertB
 
 #if TRUST_NO_ONE
 	assert(a_idx < format->attrib_ct);
-	assert(verts->data != NULL); // data must be in main mem
 #endif
 
+	if (verts->data == NULL)
+		VertexBuffer_map(verts);
+
 	access->size = a->sz;
 	access->stride = format->stride;
 	access->data = (GLubyte*)verts->data + a->offset;
@@ -208,59 +244,11 @@ void GWN_vertbuf_attr_get_raw_data(Gwn_VertBuf* verts, unsigned a_idx, Gwn_VertB
 #endif
 	}
 
-
-static void VertexBuffer_prime(Gwn_VertBuf* verts)
-	{
-	unsigned buffer_sz = GWN_vertbuf_size_get(verts);
-
-#if VRAM_USAGE
-	vbo_memory_usage += buffer_sz;
-	verts->vram_size = buffer_sz;
-#endif
-
-	verts->vbo_id = GWN_buf_id_alloc();
-	glBindBuffer(GL_ARRAY_BUFFER, verts->vbo_id);
-	// fill with delicious data & send to GPU the first time only
-	glBufferData(GL_ARRAY_BUFFER, verts->vram_size, verts->data, (verts->data_dynamic) ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
-
-	// now that GL has a copy, discard original
-	if (!verts->data_dynamic)
-		{
-		free(verts->data);
-		verts->data = NULL;
-		}
-
-	verts->data_dirty = false;
-	}
-
-static void VertexBuffer_update(Gwn_VertBuf* verts)
-	{
-	unsigned buffer_sz = GWN_vertbuf_size_get(verts);
-
-#if VRAM_USAGE
-	vbo_memory_usage -= verts->vram_size;
-	vbo_memory_usage += buffer_sz;
-	verts->vram_size = buffer_sz;
-#endif
-
-	glBindBuffer(GL_ARRAY_BUFFER, verts->vbo_id);
-
-	// fill with delicious data & send to GPU ... AGAIN
-	if (verts->data_resized)
-		glBufferData(GL_ARRAY_BUFFER, buffer_sz, verts->data, GL_DYNAMIC_DRAW);
-	else
-		glBufferSubData(GL_ARRAY_BUFFER, 0, buffer_sz, verts->data); // .. todo try glMapBuffer
-
-	verts->data_dirty = false;
-	verts->data_resized = false;
-	}
-
 void GWN_vertbuf_use(Gwn_VertBuf* verts)
 	{
-	if (!verts->vbo_id)
-		VertexBuffer_prime(verts);
-	else if (verts->data_dirty)
-		VertexBuffer_update(verts);
+	if (verts->data)
+		// this also calls glBindBuffer
+		VertexBuffer_unmap(verts);
 	else
 		glBindBuffer(GL_ARRAY_BUFFER, verts->vbo_id);
 	}
diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt
index 7771af93d4fc7baa09851d96193cd7df447b7cf4..e4b74ae24af73ed2bf3dc16e10a170ea53699e91 100644
--- a/intern/ghost/CMakeLists.txt
+++ b/intern/ghost/CMakeLists.txt
@@ -178,10 +178,12 @@ elseif(WITH_X11)
 		intern/GHOST_DisplayManagerX11.cpp
 		intern/GHOST_SystemX11.cpp
 		intern/GHOST_WindowX11.cpp
+		intern/GHOST_TaskbarX11.cpp
 
 		intern/GHOST_DisplayManagerX11.h
 		intern/GHOST_SystemX11.h
 		intern/GHOST_WindowX11.h
+		intern/GHOST_TaskbarX11.h
 	)
 
 	if(NOT WITH_GL_EGL)
diff --git a/intern/ghost/intern/GHOST_ContextWGL.cpp b/intern/ghost/intern/GHOST_ContextWGL.cpp
index a23c0b0b26cb18ff2ad5cc4e846c3ec191ede055..58ade795e3f8adc2c74526d7e77a4a35700a6908 100644
--- a/intern/ghost/intern/GHOST_ContextWGL.cpp
+++ b/intern/ghost/intern/GHOST_ContextWGL.cpp
@@ -363,7 +363,8 @@ void GHOST_ContextWGL::initContextWGLEW(PIXELFORMATDESCRIPTOR &preferredPFD)
 		dummyHDC = GetDC(dummyHWND);
 	}
 	else {
-		dummyhBuffer = wglCreatePbufferARB(m_hDC, iPixelFormat, 1, 1, 0);
+		int iAttribList[] = {0};
+		dummyhBuffer = wglCreatePbufferARB(m_hDC, iPixelFormat, 1, 1, iAttribList);
 		dummyHDC = wglGetPbufferDCARB(dummyhBuffer);
 	}
 
@@ -812,7 +813,8 @@ GHOST_TSuccess GHOST_ContextWGL::initializeDrawingContext()
 
 		if (create_hDC) {
 			/* create an off-screen pixel buffer (Pbuffer) */
-			m_dummyPbuffer = wglCreatePbufferARB(m_hDC, iPixelFormat, 1, 1, 0);
+			int iAttribList[] = {0};
+			m_dummyPbuffer = wglCreatePbufferARB(m_hDC, iPixelFormat, 1, 1, iAttribList);
 			m_hDC = wglGetPbufferDCARB(m_dummyPbuffer);
 		}
 
diff --git a/intern/ghost/intern/GHOST_TaskbarX11.cpp b/intern/ghost/intern/GHOST_TaskbarX11.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b47068df39f51517d23f3eac521e2be349baaf08
--- /dev/null
+++ b/intern/ghost/intern/GHOST_TaskbarX11.cpp
@@ -0,0 +1,130 @@
+/*
+ * ***** 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.
+ *
+ * Contributor(s):
+ *   Lukas Stockner
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file ghost/intern/GHOST_TaskbarX11.cpp
+ *  \ingroup GHOST
+ */
+
+#include "GHOST_TaskbarX11.h"
+
+#include <dlfcn.h>
+#include <cstdio>
+#include <cassert>
+#include <cstdlib>
+
+typedef void*(*unity_get_entry_t)(const char*);
+typedef void(*unity_set_progress_t)(void*, double);
+typedef void(*unity_set_progress_visible_t)(void*, int);
+typedef int(*unity_event_loop_t)(void*, int);
+
+static unity_get_entry_t unity_get_entry;
+static unity_set_progress_t unity_set_progress;
+static unity_set_progress_visible_t unity_set_progress_visible;
+static unity_event_loop_t unity_event_loop;
+
+static bool libunity_initialized = false;
+static bool libunity_available = false;
+void* libunity_handle = NULL;
+
+void GHOST_TaskBarX11::free()
+{
+	if(libunity_handle) {
+		dlclose(libunity_handle);
+		libunity_handle = NULL;
+	}
+}
+
+bool GHOST_TaskBarX11::init()
+{
+	if(libunity_initialized) {
+		return libunity_available;
+	}
+
+	libunity_initialized = true;
+
+	const char *libunity_names[] = {"libunity.so.4", "libunity.so.6", "libunity.so.9", "libunity.so", NULL};
+	for(int i = 0; libunity_names[i]; i++) {
+		libunity_handle = dlopen(libunity_names[i], RTLD_LAZY);
+		if(libunity_handle) {
+			break;
+		}
+	}
+
+	if(!libunity_handle) {
+		return false;
+	}
+
+	unity_get_entry = (unity_get_entry_t) dlsym(libunity_handle, "unity_launcher_entry_get_for_desktop_id");
+	if(!unity_get_entry) {
+		fprintf(stderr, "failed to load libunity: %s\n", dlerror());
+		return false;
+	}
+	unity_set_progress = (unity_set_progress_t) dlsym(libunity_handle, "unity_launcher_entry_set_progress");
+	if(!unity_set_progress) {
+		fprintf(stderr, "failed to load libunity: %s\n", dlerror());
+		return false;
+	}
+	unity_set_progress_visible = (unity_set_progress_visible_t) dlsym(libunity_handle, "unity_launcher_entry_set_progress_visible");
+	if(!unity_set_progress_visible) {
+		fprintf(stderr, "failed to load libunity: %s\n", dlerror());
+		return false;
+	}
+	unity_event_loop = (unity_event_loop_t) dlsym(libunity_handle, "g_main_context_iteration");
+	if(!unity_event_loop) {
+		fprintf(stderr, "failed to load libunity: %s\n", dlerror());
+		return false;
+	}
+
+	atexit(GHOST_TaskBarX11::free);
+
+	libunity_available = true;
+	return true;
+}
+
+GHOST_TaskBarX11::GHOST_TaskBarX11(const char *name)
+{
+	if(GHOST_TaskBarX11::init()) {
+		handle = unity_get_entry(name);
+	}
+	else {
+		handle = NULL;
+	}
+}
+
+bool GHOST_TaskBarX11::is_valid()
+{
+	return (handle != NULL);
+}
+
+void GHOST_TaskBarX11::set_progress(double progress)
+{
+	assert(is_valid());
+	unity_set_progress(handle, progress);
+}
+
+void GHOST_TaskBarX11::set_progress_enabled(bool enabled)
+{
+	assert(is_valid());
+	unity_set_progress_visible(handle, enabled ? 1 : 0);
+	unity_event_loop(NULL, 0);
+}
\ No newline at end of file
diff --git a/intern/ghost/intern/GHOST_TaskbarX11.h b/intern/ghost/intern/GHOST_TaskbarX11.h
new file mode 100644
index 0000000000000000000000000000000000000000..ed0e5f9b3295cd6043c0b46c63b66235b7a2b77c
--- /dev/null
+++ b/intern/ghost/intern/GHOST_TaskbarX11.h
@@ -0,0 +1,45 @@
+/*
+ * ***** 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.
+ *
+ * Contributor(s):
+ *   Lukas Stockner
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file ghost/intern/GHOST_TaskbarX11.h
+ *  \ingroup GHOST
+ */
+#ifndef __GHOST_TASKBARX11_H__
+#define __GHOST_TASKBARX11_H__
+
+class GHOST_TaskBarX11
+{
+public:
+	static bool init();
+	static void free();
+
+	GHOST_TaskBarX11(const char *name);
+
+	bool is_valid();
+	void set_progress(double progress);
+	void set_progress_enabled(bool enabled);
+private:
+	void *handle;
+};
+
+#endif /*__GHOST_TASKBARX11_H__*/
diff --git a/intern/ghost/intern/GHOST_WindowX11.cpp b/intern/ghost/intern/GHOST_WindowX11.cpp
index 78a1c841934cbc8941e65be740c63731a9affa46..3c291bd5ec6513748a08382ba6e2c3f19e675c31 100644
--- a/intern/ghost/intern/GHOST_WindowX11.cpp
+++ b/intern/ghost/intern/GHOST_WindowX11.cpp
@@ -334,6 +334,7 @@ GHOST_WindowX11(GHOST_SystemX11 *system,
       m_empty_cursor(None),
       m_custom_cursor(None),
       m_visible_cursor(None),
+      m_taskbar("blender.desktop"),
 #ifdef WITH_XDND
       m_dropTarget(NULL),
 #endif
@@ -1697,3 +1698,24 @@ getDPIHint()
 	int dpi = pixelDiagonal / inchDiagonal;
 	return dpi;
 }
+
+GHOST_TSuccess GHOST_WindowX11::setProgressBar(float progress)
+{
+	if (m_taskbar.is_valid()) {
+		m_taskbar.set_progress(progress);
+		m_taskbar.set_progress_enabled(true);
+		return GHOST_kSuccess;
+	}
+
+	return GHOST_kFailure;
+}
+
+GHOST_TSuccess GHOST_WindowX11::endProgressBar()
+{
+	if (m_taskbar.is_valid()) {
+		m_taskbar.set_progress_enabled(false);
+		return GHOST_kSuccess;
+	}
+
+	return GHOST_kFailure;
+}
diff --git a/intern/ghost/intern/GHOST_WindowX11.h b/intern/ghost/intern/GHOST_WindowX11.h
index 5c54c1e8162bb3746ca20f12a90234db2c43c6a3..236fe0b76b00cce36d3dce00b133a0473c34ffbd 100644
--- a/intern/ghost/intern/GHOST_WindowX11.h
+++ b/intern/ghost/intern/GHOST_WindowX11.h
@@ -41,6 +41,8 @@
 #  include <X11/extensions/XInput.h>
 #endif
 
+#include "GHOST_TaskbarX11.h"
+
 #include <map>
 
 class STR_String;
@@ -166,6 +168,9 @@ public:
 	invalidate(
 	    );
 
+	GHOST_TSuccess setProgressBar(float progress);
+	GHOST_TSuccess endProgressBar();
+
 	/**
 	 * Destructor.
 	 * Closes the window and disposes resources allocated.
@@ -347,6 +352,8 @@ private:
 	/** Cache of XC_* ID's to XCursor structures */
 	std::map<unsigned int, Cursor> m_standard_cursors;
 
+	GHOST_TaskBarX11 m_taskbar;
+
 #ifdef WITH_XDND
 	GHOST_DropTargetX11 *m_dropTarget;
 #endif
diff --git a/source/blender/blenkernel/BKE_global.h b/source/blender/blenkernel/BKE_global.h
index 2b670cdf9ddd23ffafc42e7087f34e81f0df1936..9adc00a67e66ad9b647b1ad162cbd543b974012c 100644
--- a/source/blender/blenkernel/BKE_global.h
+++ b/source/blender/blenkernel/BKE_global.h
@@ -127,15 +127,16 @@ enum {
 	G_DEBUG_DEPSGRAPH_TAG        = (1 << 10),  /* depsgraph tagging messages */
 	G_DEBUG_DEPSGRAPH_TIME       = (1 << 11),  /* depsgraph timing statistics and messages */
 	G_DEBUG_DEPSGRAPH_NO_THREADS = (1 << 12),  /* single threaded depsgraph */
+	G_DEBUG_DEPSGRAPH_PRETTY     = (1 << 13),  /* use pretty colors in depsgraph messages */
 	G_DEBUG_DEPSGRAPH = (G_DEBUG_DEPSGRAPH_BUILD |
 	                     G_DEBUG_DEPSGRAPH_EVAL |
 	                     G_DEBUG_DEPSGRAPH_TAG |
 	                     G_DEBUG_DEPSGRAPH_TIME),
-	G_DEBUG_SIMDATA =   (1 << 13), /* sim debug data display */
-	G_DEBUG_GPU_MEM =   (1 << 14), /* gpu memory in status bar */
-	G_DEBUG_GPU =        (1 << 15), /* gpu debug */
-	G_DEBUG_IO = (1 << 13),   /* IO Debugging (for Collada, ...)*/
-	G_DEBUG_GPU_SHADERS = (1 << 16),   /* GLSL shaders */
+	G_DEBUG_SIMDATA =   (1 << 14), /* sim debug data display */
+	G_DEBUG_GPU_MEM =   (1 << 15), /* gpu memory in status bar */
+	G_DEBUG_GPU =        (1 << 16), /* gpu debug */
+	G_DEBUG_IO = (1 << 17),   /* IO Debugging (for Collada, ...)*/
+	G_DEBUG_GPU_SHADERS = (1 << 18),   /* GLSL shaders */
 };
 
 #define G_DEBUG_ALL  (G_DEBUG | G_DEBUG_FFMPEG | G_DEBUG_PYTHON | G_DEBUG_EVENTS | G_DEBUG_WM | G_DEBUG_JOBS | \
diff --git a/source/blender/blenkernel/BKE_icons.h b/source/blender/blenkernel/BKE_icons.h
index 39e6b1457552f24eab047b73a448d60e7d090baa..a9ca5cc8bbbc32d1d484f63aedf998adfe3e1551 100644
--- a/source/blender/blenkernel/BKE_icons.h
+++ b/source/blender/blenkernel/BKE_icons.h
@@ -58,19 +58,19 @@ int BKE_icon_id_ensure(struct ID *id);
 int BKE_icon_preview_ensure(struct ID *id, struct PreviewImage *preview);
 
 /* retrieve icon for id */
-struct Icon *BKE_icon_get(int icon_id);
+struct Icon *BKE_icon_get(const int icon_id);
 
 /* set icon for id if not already defined */
 /* used for inserting the internal icons */
-void BKE_icon_set(int icon_id, struct Icon *icon);
+void BKE_icon_set(const int icon_id, struct Icon *icon);
 
 /* remove icon and free data if library object becomes invalid */
 void BKE_icon_id_delete(struct ID *id);
 
-void BKE_icon_delete(int icon_id);
+void BKE_icon_delete(const int icon_id);
 
 /* report changes - icon needs to be recalculated */
-void BKE_icon_changed(int icon_id);
+void BKE_icon_changed(const int icon_id);
 
 /* free all icons */
 void BKE_icons_free(void);
diff --git a/source/blender/blenkernel/intern/anim_sys.c b/source/blender/blenkernel/intern/anim_sys.c
index b79a8d5cbed5cecbf899d9135e857d537074a0f8..ecdb180d2ede39e2b8d5bc1aa6bb57eca6d39cba 100644
--- a/source/blender/blenkernel/intern/anim_sys.c
+++ b/source/blender/blenkernel/intern/anim_sys.c
@@ -77,6 +77,8 @@
 
 #include "atomic_ops.h"
 
+#include "DEG_depsgraph.h"
+
 /* ***************************************** */
 /* AnimData API */
 
@@ -2879,18 +2881,34 @@ void BKE_animsys_evaluate_all_animation(Main *main, Scene *scene, float ctime)
 /* ************** */
 /* Evaluation API */
 
-#define DEBUG_PRINT if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) printf
-
 void BKE_animsys_eval_animdata(const EvaluationContext *eval_ctx, ID *id)
 {
 	AnimData *adt = BKE_animdata_from_id(id);
 	Scene *scene = NULL; /* XXX: this is only needed for flushing RNA updates,
 	                      * which should get handled as part of the dependency graph instead...
 	                      */
-	DEBUG_PRINT("%s on %s, time=%f\n\n", __func__, id->name, (double)eval_ctx->ctime);
+	DEG_debug_print_eval_time(__func__, id->name, id, eval_ctx->ctime);
 	BKE_animsys_evaluate_animdata(scene, id, adt, eval_ctx->ctime, ADT_RECALC_ANIM);
 }
 
+/* TODO(sergey): This is slow lookup of driver from CoW datablock.
+ * Keep this for until we've got something smarter for depsgraph
+ * building.\
+ */
+static FCurve *find_driver_from_evaluated_id(ID *id, FCurve *fcu)
+{
+	/* We've got non-CoW datablock, can use f-curve as-is. */
+	if (id->orig_id == NULL) {
+		return fcu;
+	}
+	/*const*/ ID *id_orig = id->orig_id;
+	const AnimData *adt_orig = BKE_animdata_from_id(id_orig);
+	const AnimData *adt_cow = BKE_animdata_from_id(id);
+	const int fcu_index = BLI_findindex(&adt_orig->drivers, fcu);
+	BLI_assert(fcu_index != -1);
+	return BLI_findlink(&adt_cow->drivers, fcu_index);
+}
+
 void BKE_animsys_eval_driver(const EvaluationContext *eval_ctx,
                              ID *id,
                              FCurve *fcu)
@@ -2900,11 +2918,10 @@ void BKE_animsys_eval_driver(const EvaluationContext *eval_ctx,
 	PointerRNA id_ptr;
 	bool ok = false;
 
-	DEBUG_PRINT("%s on %s (%s[%d])\n",
-	            __func__,
-	            id->name,
-	            fcu->rna_path,
-	            fcu->array_index);
+	fcu = find_driver_from_evaluated_id(id, fcu);
+
+	DEG_debug_print_eval_subdata_index(
+	        __func__, id->name, id, "fcu", fcu->rna_path, fcu, fcu->array_index);
 
 	RNA_id_pointer_create(id, &id_ptr);
 
@@ -2937,5 +2954,3 @@ void BKE_animsys_eval_driver(const EvaluationContext *eval_ctx,
 		}
 	}
 }
-
-#undef DEBUG_PRINT
diff --git a/source/blender/blenkernel/intern/armature_update.c b/source/blender/blenkernel/intern/armature_update.c
index 203dcbf247cbf0bf3a6e175cdae1258e64bd3a0b..bf065ef992c8931bf2cda5403ed33a3ad71f8183 100644
--- a/source/blender/blenkernel/intern/armature_update.c
+++ b/source/blender/blenkernel/intern/armature_update.c
@@ -50,7 +50,7 @@
 #include "BKE_global.h"
 #include "BKE_main.h"
 
-#define DEBUG_PRINT if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) printf
+#include "DEG_depsgraph.h"
 
 /* ********************** SPLINE IK SOLVER ******************* */
 
@@ -566,7 +566,7 @@ void BKE_pose_eval_init(const struct EvaluationContext *UNUSED(eval_ctx),
 {
 	bPoseChannel *pchan;
 
-	DEBUG_PRINT("%s on %s\n", __func__, ob->id.name);
+	DEG_debug_print_eval(__func__, ob->id.name, ob);
 
 	BLI_assert(ob->type == OB_ARMATURE);
 
@@ -588,7 +588,7 @@ void BKE_pose_eval_init_ik(const struct EvaluationContext *eval_ctx,
                            Object *ob,
                            bPose *UNUSED(pose))
 {
-	DEBUG_PRINT("%s on %s\n", __func__, ob->id.name);
+	DEG_debug_print_eval(__func__, ob->id.name, ob);
 	BLI_assert(ob->type == OB_ARMATURE);
 	const float ctime = BKE_scene_frame_get(scene); /* not accurate... */
 	bArmature *arm = (bArmature *)ob->data;
@@ -609,7 +609,8 @@ void BKE_pose_eval_bone(const struct EvaluationContext *eval_ctx,
                         Object *ob,
                         bPoseChannel *pchan)
 {
-	DEBUG_PRINT("%s on %s pchan %s\n", __func__, ob->id.name, pchan->name);
+	DEG_debug_print_eval_subdata(
+	        __func__, ob->id.name, ob, "pchan", pchan->name, pchan);
 	BLI_assert(ob->type == OB_ARMATURE);
 	bArmature *arm = (bArmature *)ob->data;
 	if (arm->edbo || (arm->flag & ARM_RESTPOS)) {
@@ -644,7 +645,8 @@ void BKE_pose_constraints_evaluate(const struct EvaluationContext *eval_ctx,
                                    Object *ob,
                                    bPoseChannel *pchan)
 {
-	DEBUG_PRINT("%s on %s pchan %s\n", __func__, ob->id.name, pchan->name);
+	DEG_debug_print_eval_subdata(
+	        __func__, ob->id.name, ob, "pchan", pchan->name, pchan);
 	bArmature *arm = (bArmature *)ob->data;
 	if (arm->flag & ARM_RESTPOS) {
 		return;
@@ -664,7 +666,7 @@ void BKE_pose_bone_done(const struct EvaluationContext *UNUSED(eval_ctx),
                         bPoseChannel *pchan)
 {
 	float imat[4][4];
-	DEBUG_PRINT("%s on pchan %s\n", __func__, pchan->name);
+	DEG_debug_print_eval(__func__, pchan->name, pchan);
 	if (pchan->bone) {
 		invert_m4_m4(imat, pchan->bone->arm_mat);
 		mul_m4_m4m4(pchan->chan_mat, pchan->pose_mat, imat);
@@ -676,7 +678,8 @@ void BKE_pose_iktree_evaluate(const struct EvaluationContext *eval_ctx,
                               Object *ob,
                               bPoseChannel *rootchan)
 {
-	DEBUG_PRINT("%s on %s pchan %s\n", __func__, ob->id.name, rootchan->name);
+	DEG_debug_print_eval_subdata(
+	        __func__, ob->id.name, ob, "rootchan", rootchan->name, rootchan);
 	BLI_assert(ob->type == OB_ARMATURE);
 	const float ctime = BKE_scene_frame_get(scene); /* not accurate... */
 	bArmature *arm = (bArmature *)ob->data;
@@ -690,8 +693,10 @@ void BKE_pose_splineik_evaluate(const struct EvaluationContext *eval_ctx,
                                 Scene *scene,
                                 Object *ob,
                                 bPoseChannel *rootchan)
+
 {
-	DEBUG_PRINT("%s on %s pchan %s\n", __func__, ob->id.name, rootchan->name);
+	DEG_debug_print_eval_subdata(
+	        __func__, ob->id.name, ob, "rootchan", rootchan->name, rootchan);
 	BLI_assert(ob->type == OB_ARMATURE);
 	const float ctime = BKE_scene_frame_get(scene); /* not accurate... */
 	bArmature *arm = (bArmature *)ob->data;
@@ -707,7 +712,7 @@ void BKE_pose_eval_flush(const struct EvaluationContext *UNUSED(eval_ctx),
                          bPose *UNUSED(pose))
 {
 	float ctime = BKE_scene_frame_get(scene); /* not accurate... */
-	DEBUG_PRINT("%s on %s\n", __func__, ob->id.name);
+	DEG_debug_print_eval(__func__, ob->id.name, ob);
 	BLI_assert(ob->type == OB_ARMATURE);
 
 	/* 6. release the IK tree */
@@ -717,7 +722,7 @@ void BKE_pose_eval_flush(const struct EvaluationContext *UNUSED(eval_ctx),
 void BKE_pose_eval_proxy_copy(const struct EvaluationContext *UNUSED(eval_ctx), Object *ob)
 {
 	BLI_assert(ID_IS_LINKED(ob) && ob->proxy_from != NULL);
-	DEBUG_PRINT("%s on %s\n", __func__, ob->id.name);
+	DEG_debug_print_eval(__func__, ob->id.name, ob);
 	if (BKE_pose_copy_result(ob->pose, ob->proxy_from->pose) == false) {
 		printf("Proxy copy error, lib Object: %s proxy Object: %s\n",
 		       ob->id.name + 2, ob->proxy_from->id.name + 2);
diff --git a/source/blender/blenkernel/intern/curve.c b/source/blender/blenkernel/intern/curve.c
index b5ad2bcef4e56fa1e12a86802dc2addb8c945153..0c4dbdf7763d76ed94059697c92976bcee27b8d7 100644
--- a/source/blender/blenkernel/intern/curve.c
+++ b/source/blender/blenkernel/intern/curve.c
@@ -5258,9 +5258,7 @@ void BKE_curve_rect_from_textbox(const struct Curve *cu, const struct TextBox *t
 void BKE_curve_eval_geometry(const EvaluationContext *UNUSED(eval_ctx),
                              Curve *curve)
 {
-	if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) {
-		printf("%s on %s\n", __func__, curve->id.name);
-	}
+	DEG_debug_print_eval(__func__, curve->id.name, curve);
 	if (curve->bb == NULL || (curve->bb->flag & BOUNDBOX_DIRTY)) {
 		BKE_curve_texspace_calc(curve);
 	}
diff --git a/source/blender/blenkernel/intern/group.c b/source/blender/blenkernel/intern/group.c
index c3fe586bbbbab78a94d5b1add237832eae4aa599..20da1e7b7ac3c34ee0aec82e36b5da9cd2d31935 100644
--- a/source/blender/blenkernel/intern/group.c
+++ b/source/blender/blenkernel/intern/group.c
@@ -55,7 +55,7 @@
 #include "BKE_object.h"
 #include "BKE_scene.h"
 
-#define DEBUG_PRINT if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) printf
+#include "DEG_depsgraph.h"
 
 /** Free (or release) any data used by this group (does not free the group itself). */
 void BKE_group_free(Group *group)
@@ -403,7 +403,7 @@ static void group_eval_layer_collections(
 void BKE_group_eval_view_layers(const struct EvaluationContext *eval_ctx,
                                 Group *group)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, group->id.name, group);
+	DEG_debug_print_eval(__func__, group->id.name, group);
 	BKE_layer_eval_layer_collection_pre(eval_ctx, &group->id, group->view_layer);
 	group_eval_layer_collections(eval_ctx,
 	                             group,
diff --git a/source/blender/blenkernel/intern/icons.c b/source/blender/blenkernel/intern/icons.c
index a407fd0bae8c874bf5a3cbe1ba82c25e6dd605dd..f3ff2c4425afc759c42fec7cfbf04b08d270e77c 100644
--- a/source/blender/blenkernel/intern/icons.c
+++ b/source/blender/blenkernel/intern/icons.c
@@ -433,23 +433,28 @@ void BKE_previewimg_ensure(PreviewImage *prv, const int size)
 	}
 }
 
-void BKE_icon_changed(int id)
+void BKE_icon_changed(const int icon_id)
 {
 	Icon *icon = NULL;
 	
-	if (!id || G.background) return;
+	if (!icon_id || G.background) return;
 
-	icon = BLI_ghash_lookup(gIcons, SET_INT_IN_POINTER(id));
+	icon = BLI_ghash_lookup(gIcons, SET_INT_IN_POINTER(icon_id));
 	
 	if (icon) {
-		PreviewImage *prv = BKE_previewimg_id_ensure((ID *)icon->obj);
+		/* We *only* expect ID-tied icons here, not non-ID icon/preview! */
+		BLI_assert(icon->type != 0);
+
+		/* Do not enforce creation of previews for valid ID types using BKE_previewimg_id_ensure() here ,
+		 * we only want to ensure *existing* preview images are properly tagged as changed/invalid, that's all. */
+		PreviewImage **p_prv = BKE_previewimg_id_get_p((ID *)icon->obj);
 
-		/* all previews changed */
-		if (prv) {
+		/* If we have previews, they all are now invalid changed. */
+		if (p_prv && *p_prv) {
 			int i;
 			for (i = 0; i < NUM_ICON_SIZES; ++i) {
-				prv->flag[i] |= PRV_CHANGED;
-				prv->changed_timestamp[i]++;
+				(*p_prv)->flag[i] |= PRV_CHANGED;
+				(*p_prv)->changed_timestamp[i]++;
 			}
 		}
 	}
@@ -549,7 +554,7 @@ int BKE_icon_preview_ensure(ID *id, PreviewImage *preview)
 	return preview->icon_id;
 }
 
-Icon *BKE_icon_get(int icon_id)
+Icon *BKE_icon_get(const int icon_id)
 {
 	Icon *icon = NULL;
 
@@ -563,7 +568,7 @@ Icon *BKE_icon_get(int icon_id)
 	return icon;
 }
 
-void BKE_icon_set(int icon_id, struct Icon *icon)
+void BKE_icon_set(const int icon_id, struct Icon *icon)
 {
 	void **val_p;
 
@@ -586,7 +591,7 @@ void BKE_icon_id_delete(struct ID *id)
 /**
  * Remove icon and free data.
  */
-void BKE_icon_delete(int icon_id)
+void BKE_icon_delete(const int icon_id)
 {
 	Icon *icon;
 
diff --git a/source/blender/blenkernel/intern/idprop.c b/source/blender/blenkernel/intern/idprop.c
index 33a665ba06e39c371b8426e6e48a750b32e0b30b..87e5ed8cc1e7f22757ff500cefd52736bea5c312 100644
--- a/source/blender/blenkernel/intern/idprop.c
+++ b/source/blender/blenkernel/intern/idprop.c
@@ -905,9 +905,10 @@ bool IDP_EqualsProperties_ex(IDProperty *prop1, IDProperty *prop2, const bool is
 			if (prop1->len != prop2->len)
 				return false;
 
-			for (i = 0; i < prop1->len; i++)
-				if (!IDP_EqualsProperties(&array1[i], &array2[i]))
+			for (i = 0; i < prop1->len; i++) {
+				if (!IDP_EqualsProperties_ex(&array1[i], &array2[i], is_strict))
 					return false;
+			}
 			return true;
 		}
 		case IDP_ID:
diff --git a/source/blender/blenkernel/intern/layer.c b/source/blender/blenkernel/intern/layer.c
index 6d5ac08398c738e32c1044b976af8b04dc0169dd..662f724da937a965ee02014f94daf42202f6285b 100644
--- a/source/blender/blenkernel/intern/layer.c
+++ b/source/blender/blenkernel/intern/layer.c
@@ -59,8 +59,6 @@
 
 #include "MEM_guardedalloc.h"
 
-#define DEBUG_PRINT if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) printf
-
 /* prototype */
 struct EngineSettingsCB_Type;
 static void layer_collections_sync_flags(ListBase *layer_collections_dst, const ListBase *layer_collections_src);
@@ -2300,7 +2298,7 @@ static void idproperty_reset(IDProperty **props, IDProperty *props_ref)
 void BKE_layer_eval_layer_collection_pre(const struct EvaluationContext *UNUSED(eval_ctx),
                                          ID *owner_id, ViewLayer *view_layer)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, view_layer->name, view_layer);
+	DEG_debug_print_eval(__func__, view_layer->name, view_layer);
 	Scene *scene = (GS(owner_id->name) == ID_SCE) ? (Scene *)owner_id : NULL;
 
 	for (Base *base = view_layer->object_bases.first; base != NULL; base = base->next) {
@@ -2344,14 +2342,17 @@ void BKE_layer_eval_layer_collection(const EvaluationContext *eval_ctx,
                                      LayerCollection *layer_collection,
                                      LayerCollection *parent_layer_collection)
 {
-	DEBUG_PRINT("%s on %s (%p) [%s], parent %s (%p) [%s]\n",
-	            __func__,
-	            layer_collection->scene_collection->name,
-	            layer_collection->scene_collection,
-	            collection_type_lookup[layer_collection->scene_collection->type],
-	            (parent_layer_collection != NULL) ? parent_layer_collection->scene_collection->name : "NONE",
-	            (parent_layer_collection != NULL) ? parent_layer_collection->scene_collection : NULL,
-	            (parent_layer_collection != NULL) ? collection_type_lookup[parent_layer_collection->scene_collection->type] : "");
+	if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) {
+		/* TODO)sergey): Try to make it more generic and handled by depsgraph messaging. */
+		printf("%s on %s (%p) [%s], parent %s (%p) [%s]\n",
+		       __func__,
+		       layer_collection->scene_collection->name,
+		       layer_collection->scene_collection,
+		       collection_type_lookup[layer_collection->scene_collection->type],
+		       (parent_layer_collection != NULL) ? parent_layer_collection->scene_collection->name : "NONE",
+		       (parent_layer_collection != NULL) ? parent_layer_collection->scene_collection : NULL,
+		       (parent_layer_collection != NULL) ? collection_type_lookup[parent_layer_collection->scene_collection->type] : "");
+	}
 	BLI_assert(layer_collection != parent_layer_collection);
 
 	/* visibility */
@@ -2400,7 +2401,7 @@ void BKE_layer_eval_layer_collection(const EvaluationContext *eval_ctx,
 void BKE_layer_eval_layer_collection_post(const struct EvaluationContext *UNUSED(eval_ctx),
                                           ViewLayer *view_layer)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, view_layer->name, view_layer);
+	DEG_debug_print_eval(__func__, view_layer->name, view_layer);
 	/* if base is not selectabled, clear select */
 	for (Base *base = view_layer->object_bases.first; base; base = base->next) {
 		if ((base->flag & BASE_SELECTABLED) == 0) {
diff --git a/source/blender/blenkernel/intern/mask_evaluate.c b/source/blender/blenkernel/intern/mask_evaluate.c
index 841008d1accec918f4a1db86e7b9f628681cbe07..7d977463abf0b3fb8357485205f3a8b8ec75acb1 100644
--- a/source/blender/blenkernel/intern/mask_evaluate.c
+++ b/source/blender/blenkernel/intern/mask_evaluate.c
@@ -47,7 +47,6 @@
 
 #include "DEG_depsgraph.h"
 
-
 unsigned int BKE_mask_spline_resolution(MaskSpline *spline, int width, int height)
 {
 	float max_segment = 0.01f;
@@ -898,11 +897,9 @@ void BKE_mask_layer_evaluate_deform(MaskLayer *masklay, const float ctime)
 	}
 }
 
-#define DEBUG_PRINT if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) printf
-
 void BKE_mask_eval_animation(struct EvaluationContext *eval_ctx, Mask *mask)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, mask->id.name, mask);
+	DEG_debug_print_eval(__func__, mask->id.name, mask);
 	for (MaskLayer *mask_layer = mask->masklayers.first;
 	     mask_layer != NULL;
 	     mask_layer = mask_layer->next)
@@ -913,7 +910,7 @@ void BKE_mask_eval_animation(struct EvaluationContext *eval_ctx, Mask *mask)
 
 void BKE_mask_eval_update(struct EvaluationContext *eval_ctx, Mask *mask)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, mask->id.name, mask);
+	DEG_debug_print_eval(__func__, mask->id.name, mask);
 	for (MaskLayer *mask_layer = mask->masklayers.first;
 	     mask_layer != NULL;
 	     mask_layer = mask_layer->next)
diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c
index 87a57e2973ac024813b80aee91993efb516833fa..a0059a99473f246b80f60f23423dbc487d1d08e8 100644
--- a/source/blender/blenkernel/intern/material.c
+++ b/source/blender/blenkernel/intern/material.c
@@ -72,6 +72,7 @@
 #include "BKE_editmesh.h"
 #include "BKE_font.h"
 
+#include "DEG_depsgraph.h"
 #include "DEG_depsgraph_build.h"
 
 #include "GPU_material.h"
@@ -1774,9 +1775,7 @@ bool BKE_object_material_edit_image_set(Object *ob, short mat_nr, Image *image)
 
 void BKE_material_eval(const struct EvaluationContext *UNUSED(eval_ctx), Material *material)
 {
-	if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) {
-		printf("%s on %s (%p)\n", __func__, material->id.name, material);
-	}
+	DEG_debug_print_eval(__func__, material->id.name, material);
 	if ((BLI_listbase_is_empty(&material->gpumaterial) == false)) {
 		GPU_material_uniform_buffer_tag_dirty(&material->gpumaterial);
 	}
diff --git a/source/blender/blenkernel/intern/mesh.c b/source/blender/blenkernel/intern/mesh.c
index 9f346d10272f52364e53779ca742c68114744276..abbf073f6463d3be3085e7e3476bcb55000c6553 100644
--- a/source/blender/blenkernel/intern/mesh.c
+++ b/source/blender/blenkernel/intern/mesh.c
@@ -2670,9 +2670,7 @@ Mesh *BKE_mesh_new_from_object(
 void BKE_mesh_eval_geometry(const EvaluationContext *UNUSED(eval_ctx),
                             Mesh *mesh)
 {
-	if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) {
-		printf("%s on %s\n", __func__, mesh->id.name);
-	}
+	DEG_debug_print_eval(__func__, mesh->id.name, mesh);
 	if (mesh->bb == NULL || (mesh->bb->flag & BOUNDBOX_DIRTY)) {
 		BKE_mesh_texspace_calc(mesh);
 	}
diff --git a/source/blender/blenkernel/intern/movieclip.c b/source/blender/blenkernel/intern/movieclip.c
index 607307554da383c754c6cf626921cea2789f9420..9ed715d75910d360d625b4c3b57e38af3839ea2c 100644
--- a/source/blender/blenkernel/intern/movieclip.c
+++ b/source/blender/blenkernel/intern/movieclip.c
@@ -73,12 +73,12 @@
 #include "IMB_imbuf.h"
 #include "IMB_moviecache.h"
 
+#include "DEG_depsgraph.h"
+
 #ifdef WITH_OPENEXR
 #  include "intern/openexr/openexr_multi.h"
 #endif
 
-#define DEBUG_PRINT if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) printf
-
 /*********************** movieclip buffer loaders *************************/
 
 static int sequence_guess_offset(const char *full_name, int head_len, unsigned short numlen)
@@ -1611,6 +1611,6 @@ bool BKE_movieclip_put_frame_if_possible(MovieClip *clip,
 
 void BKE_movieclip_eval_update(struct EvaluationContext *UNUSED(eval_ctx), MovieClip *clip)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, clip->id.name, clip);
+	DEG_debug_print_eval(__func__, clip->id.name, clip);
 	BKE_tracking_dopesheet_tag_update(&clip->tracking);
 }
diff --git a/source/blender/blenkernel/intern/node.c b/source/blender/blenkernel/intern/node.c
index 1e41c276de4a99f4dbcf700abfcef6525c3ed889..41b7cd1f48e8feaefbffae0b3a32a26f487c90b2 100644
--- a/source/blender/blenkernel/intern/node.c
+++ b/source/blender/blenkernel/intern/node.c
@@ -75,6 +75,8 @@
 #include "NOD_shader.h"
 #include "NOD_texture.h"
 
+#include "DEG_depsgraph.h"
+
 #define NODE_DEFAULT_MAX_WIDTH 700
 
 /* Fallback types for undefined tree, nodes, sockets */
@@ -3871,8 +3873,6 @@ void BKE_nodetree_shading_params_eval(const struct EvaluationContext *UNUSED(eva
                                       bNodeTree *ntree_dst,
                                       const bNodeTree *ntree_src)
 {
-	if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) {
-		printf("%s on %s (%p)\n", __func__, ntree_src->id.name, ntree_dst);
-	}
+	DEG_debug_print_eval(__func__, ntree_src->id.name, ntree_dst);
 	BKE_nodetree_copy_default_values(ntree_dst, ntree_src);
 }
diff --git a/source/blender/blenkernel/intern/object_update.c b/source/blender/blenkernel/intern/object_update.c
index e9af3a3d74e6e4b9478346f8507806d3c96fd52d..199e91a3dd994cbb34a56b4eb850852509792f33 100644
--- a/source/blender/blenkernel/intern/object_update.c
+++ b/source/blender/blenkernel/intern/object_update.c
@@ -65,12 +65,11 @@
 #include "MEM_guardedalloc.h"
 #include "DEG_depsgraph.h"
 
-#define DEBUG_PRINT if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) printf
 
 void BKE_object_eval_local_transform(const EvaluationContext *UNUSED(eval_ctx),
                                      Object *ob)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, ob->id.name, ob);
+	DEG_debug_print_eval(__func__, ob->id.name, ob);
 
 	/* calculate local matrix */
 	BKE_object_to_mat4(ob, ob->obmat);
@@ -88,7 +87,7 @@ void BKE_object_eval_parent(const EvaluationContext *UNUSED(eval_ctx),
 	float tmat[4][4];
 	float locmat[4][4];
 
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, ob->id.name, ob);
+	DEG_debug_print_eval(__func__, ob->id.name, ob);
 
 	/* get local matrix (but don't calculate it, as that was done already!) */
 	// XXX: redundant?
@@ -117,7 +116,7 @@ void BKE_object_eval_constraints(const EvaluationContext *eval_ctx,
 	bConstraintOb *cob;
 	float ctime = BKE_scene_frame_get(scene);
 
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, ob->id.name, ob);
+	DEG_debug_print_eval(__func__, ob->id.name, ob);
 
 	/* evaluate constraints stack */
 	/* TODO: split this into:
@@ -135,7 +134,7 @@ void BKE_object_eval_constraints(const EvaluationContext *eval_ctx,
 
 void BKE_object_eval_done(const EvaluationContext *UNUSED(eval_ctx), Object *ob)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, ob->id.name, ob);
+	DEG_debug_print_eval(__func__, ob->id.name, ob);
 
 	/* Set negative scale flag in object. */
 	if (is_negative_m4(ob->obmat)) ob->transflag |= OB_NEG_SCALE;
@@ -313,7 +312,7 @@ void BKE_object_eval_uber_data(const EvaluationContext *eval_ctx,
                                Scene *scene,
                                Object *ob)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, ob->id.name, ob);
+	DEG_debug_print_eval(__func__, ob->id.name, ob);
 	BLI_assert(ob->type != OB_ARMATURE);
 	BKE_object_handle_data_update(eval_ctx, scene, ob);
 
@@ -391,7 +390,7 @@ void BKE_object_eval_cloth(const EvaluationContext *UNUSED(eval_ctx),
                            Scene *scene,
                            Object *object)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, object->id.name, object);
+	DEG_debug_print_eval(__func__, object->id.name, object);
 	BKE_ptcache_object_reset(scene, object, PTCACHE_RESET_DEPSGRAPH);
 }
 
@@ -414,7 +413,7 @@ void BKE_object_eval_transform_all(const EvaluationContext *eval_ctx,
 void BKE_object_eval_update_shading(const EvaluationContext *UNUSED(eval_ctx),
                                     Object *object)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, object->id.name, object);
+	DEG_debug_print_eval(__func__, object->id.name, object);
 	if (object->type == OB_MESH) {
 		BKE_mesh_batch_cache_dirty(object->data, BKE_MESH_BATCH_DIRTY_SHADING);
 	}
@@ -423,7 +422,7 @@ void BKE_object_eval_update_shading(const EvaluationContext *UNUSED(eval_ctx),
 void BKE_object_data_select_update(const EvaluationContext *UNUSED(eval_ctx),
                                    struct ID *object_data)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, object_data->name, object_data);
+	DEG_debug_print_eval(__func__, object_data->name, object_data);
 	switch (GS(object_data->name)) {
 		case ID_ME:
 			BKE_mesh_batch_cache_dirty((Mesh *)object_data,
@@ -445,7 +444,7 @@ void BKE_object_data_select_update(const EvaluationContext *UNUSED(eval_ctx),
 void BKE_object_eval_flush_base_flags(const EvaluationContext *UNUSED(eval_ctx),
                                       Object *object, Base *base, bool is_from_set)
 {
-	DEBUG_PRINT("%s on %s (%p)\n", __func__, object->id.name, object);
+	DEG_debug_print_eval(__func__, object->id.name, object);
 
 	/* Make sure we have the base collection settings is already populated.
 	 * This will fail when BKE_layer_eval_layer_collection_pre hasn't run yet.
diff --git a/source/blender/blenkernel/intern/particle_system.c b/source/blender/blenkernel/intern/particle_system.c
index 4cf8533d843e767d7e8395d2b2b42d86aa18c928..f27a570ca29e4bd73573f244b4865d651e0ef8a9 100644
--- a/source/blender/blenkernel/intern/particle_system.c
+++ b/source/blender/blenkernel/intern/particle_system.c
@@ -95,6 +95,7 @@
 #include "PIL_time.h"
 
 #include "RE_shader_ext.h"
+#include "DEG_depsgraph.h"
 
 /* fluid sim particle import */
 #ifdef WITH_MOD_FLUID
@@ -4419,18 +4420,14 @@ void BKE_particlesystem_id_loop(ParticleSystem *psys, ParticleSystemIDFunc func,
 void BKE_particle_system_settings_eval(const struct EvaluationContext *UNUSED(eval_ctx),
                                        ParticleSystem *psys)
 {
-	if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) {
-		printf("%s on %s (%p)\n", __func__, psys->name, psys);
-	}
+	DEG_debug_print_eval(__func__, psys->name, psys);
 	psys->recalc |= psys->part->recalc;
 }
 
 void BKE_particle_system_settings_recalc_clear(struct EvaluationContext *UNUSED(eval_ctx),
                                                ParticleSettings *particle_settings)
 {
-	if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) {
-		printf("%s on %s (%p)\n", __func__, particle_settings->id.name, particle_settings);
-	}
+	DEG_debug_print_eval(__func__, particle_settings->id.name, particle_settings);
 	particle_settings->recalc = 0;
 }
 
@@ -4438,8 +4435,6 @@ void BKE_particle_system_eval_init(const struct EvaluationContext *UNUSED(eval_c
                                    Scene *scene,
                                    Object *ob)
 {
-	if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) {
-		printf("%s on %s (%p)\n", __func__, ob->id.name, ob);
-	}
+	DEG_debug_print_eval(__func__, ob->id.name, ob);
 	BKE_ptcache_object_reset(scene, ob, PTCACHE_RESET_DEPSGRAPH);
 }
diff --git a/source/blender/blenkernel/intern/rigidbody.c b/source/blender/blenkernel/intern/rigidbody.c
index 12a53f5f3e5a1f798ba958e5d4193a96c52e86fd..69e36f66dad05af30dc4ae938931df2723569682 100644
--- a/source/blender/blenkernel/intern/rigidbody.c
+++ b/source/blender/blenkernel/intern/rigidbody.c
@@ -66,6 +66,8 @@
 #include "BKE_rigidbody.h"
 #include "BKE_scene.h"
 
+#include "DEG_depsgraph.h"
+
 /* ************************************** */
 /* Memory Management */
 
@@ -1688,11 +1690,7 @@ void BKE_rigidbody_rebuild_sim(const struct EvaluationContext *eval_ctx,
                                Scene *scene)
 {
 	float ctime = BKE_scene_frame_get(scene);
-
-	if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) {
-		printf("%s at %f\n", __func__, ctime);
-	}
-
+	DEG_debug_print_eval_time(__func__, scene->id.name, scene, ctime);
 	/* rebuild sim data (i.e. after resetting to start of timeline) */
 	if (BKE_scene_check_rigidbody_active(scene)) {
 		BKE_rigidbody_rebuild_world(eval_ctx, scene, ctime);
@@ -1703,11 +1701,7 @@ void BKE_rigidbody_eval_simulation(const struct EvaluationContext *eval_ctx,
                                    Scene *scene)
 {
 	float ctime = BKE_scene_frame_get(scene);
-
-	if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) {
-		printf("%s at %f\n", __func__, ctime);
-	}
-
+	DEG_debug_print_eval_time(__func__, scene->id.name, scene, ctime);
 	/* evaluate rigidbody sim */
 	if (BKE_scene_check_rigidbody_active(scene)) {
 		BKE_rigidbody_do_simulation(eval_ctx, scene, ctime);
@@ -1720,11 +1714,7 @@ void BKE_rigidbody_object_sync_transforms(const struct EvaluationContext *UNUSED
 {
 	RigidBodyWorld *rbw = scene->rigidbody_world;
 	float ctime = BKE_scene_frame_get(scene);
-
-	if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) {
-		printf("%s on %s\n", __func__, ob->id.name);
-	}
-
+	DEG_debug_print_eval_time(__func__, ob->id.name, ob, ctime);
 	/* read values pushed into RBO from sim/cache... */
 	BKE_rigidbody_sync_transforms(rbw, ob, ctime);
 }
diff --git a/source/blender/blenlib/BLI_array.h b/source/blender/blenlib/BLI_array.h
index 825acb18e91c97252648616b566b1e0e14130609..8e94b8197ef495ede23ad31f1b1d41231c426bc6 100644
--- a/source/blender/blenlib/BLI_array.h
+++ b/source/blender/blenlib/BLI_array.h
@@ -62,9 +62,10 @@
  * Doing the realloc in a macro isn't so simple,
  * so use a function the macros can use.
  */
-void _bli_array_grow_func(void **arr_p, const void *arr_static,
-                          const int sizeof_arr_p, const int arr_count, const int num,
-                          const char *alloc_str);
+void _bli_array_grow_func(
+        void **arr_p, const void *arr_static,
+        const int sizeof_arr_p, const int arr_len, const int num,
+        const char *alloc_str);
 
 
 /* -------------------------------------------------------------------- */
@@ -74,18 +75,18 @@ void _bli_array_grow_func(void **arr_p, const void *arr_static,
 
 /** use ``sizeof(*(arr))`` to ensure the array exists and is an array */
 #define BLI_array_declare(arr)                                                \
-	int   _##arr##_count = ((void)(sizeof(*(arr))), 0);                       \
+	int   _##arr##_len = ((void)(sizeof(*(arr))), 0);                         \
 	void *_##arr##_static = NULL
 
 /**
  * this will use stack space, up to maxstatic array elements, before
  * switching to dynamic heap allocation */
 #define BLI_array_staticdeclare(arr, maxstatic)                               \
-	int   _##arr##_count = 0;                                                 \
+	int   _##arr##_len = 0;                                                   \
 	char  _##arr##_static[maxstatic * sizeof(*(arr))]
 
 /** returns the logical size of the array, not including buffering. */
-#define BLI_array_count(arr) ((void)0, _##arr##_count)
+#define BLI_array_len(arr) ((void)0, _##arr##_len)
 
 /**
  * Grow the array by a fixed number of items.
@@ -95,23 +96,23 @@ void _bli_array_grow_func(void **arr_p, const void *arr_static,
 #define BLI_array_reserve(arr, num)  (void)(                                  \
 	(((void *)(arr) == NULL) &&                                               \
 	 ((void *)(_##arr##_static) != NULL) &&                                   \
-	/* don't add _##arr##_count below because it must be zero */              \
-	 (_bli_array_totalsize_static(arr) >= _##arr##_count + (num))) ?          \
+	/* don't add _##arr##_len below because it must be zero */                \
+	 (_bli_array_totalsize_static(arr) >= _##arr##_len + (num))) ?            \
 	/* we have an empty array and a static var big enough */                  \
 	(void)(arr = (void *)_##arr##_static)                                     \
 	    :                                                                     \
 	/* use existing static array or allocate */                               \
-	(LIKELY(_bli_array_totalsize(arr) >= _##arr##_count + (num)) ?            \
+	(LIKELY(_bli_array_totalsize(arr) >= _##arr##_len + (num)) ?              \
 	 (void)0 /* do nothing */ :                                               \
 	 _bli_array_grow_func((void **)&(arr), _##arr##_static,                   \
-	                       sizeof(*(arr)), _##arr##_count, num,               \
+	                       sizeof(*(arr)), _##arr##_len, num,                 \
 	                       "BLI_array." #arr))                                \
 	)
 
 
 /** returns length of array */
 #define BLI_array_grow_items(arr, num) \
-	(BLI_array_reserve(arr, num), (_##arr##_count += num))
+	(BLI_array_reserve(arr, num), (_##arr##_len += num))
 
 #define BLI_array_grow_one(arr) \
 	BLI_array_grow_items(arr, 1)
@@ -119,7 +120,7 @@ void _bli_array_grow_func(void **arr_p, const void *arr_static,
 /** appends an item to the array. */
 #define BLI_array_append(arr, item)  (                                        \
 	(void) BLI_array_grow_one(arr),                                           \
-	(void) (arr[_##arr##_count - 1] = item)                                   \
+	(void) (arr[_##arr##_len - 1] = item)                                     \
 )
 
 /**
@@ -127,13 +128,13 @@ void _bli_array_grow_func(void **arr_p, const void *arr_static,
  * item is not a pointer, but actual data value.*/
 #define BLI_array_append_r(arr, item)  (                                      \
 	(void) BLI_array_grow_one(arr),                                           \
-	(void) (arr[_##arr##_count - 1] = item),                                  \
-	(&arr[_##arr##_count - 1])                                                \
+	(void) (arr[_##arr##_len - 1] = item),                                    \
+	(&arr[_##arr##_len - 1])                                                  \
 )
 
 /** appends (grows) & returns a pointer to the uninitialized memory */
 #define BLI_array_append_ret(arr) \
-	(BLI_array_reserve(arr, 1), &arr[(_##arr##_count++)])
+	(BLI_array_reserve(arr, 1), &arr[(_##arr##_len++)])
 
 #define BLI_array_free(arr) {                                                 \
 	if (arr && (char *)arr != _##arr##_static) {                              \
@@ -143,26 +144,26 @@ void _bli_array_grow_func(void **arr_p, const void *arr_static,
 } ((void)0)
 
 #define BLI_array_pop(arr)  (                                                 \
-	(arr && _##arr##_count) ?                                                 \
-	    arr[--_##arr##_count] :                                               \
+	(arr && _##arr##_len) ?                                                   \
+	    arr[--_##arr##_len] :                                                 \
 	    NULL                                                                  \
 )
 
 /**
- * resets the logical size of an array to zero, but doesn't
+ * Resets the logical size of an array to zero, but doesn't
  * free the memory. */
 #define BLI_array_clear(arr)                                                  \
-	{ _##arr##_count = 0; } (void)0
+	{ _##arr##_len = 0; } ((void)0)
 
 /**
- * set the count of the array, doesn't actually increase the allocated array
+ * Set the length of the array, doesn't actually increase the allocated array
  * size.  don't use this unless you know what you're doing. */
-#define BLI_array_count_set(arr, count)                                      \
-	{ _##arr##_count = (count); }(void)0
+#define BLI_array_len_set(arr, len)                                           \
+	{ _##arr##_len = (len); } ((void)0)
 
 /** only to prevent unused warnings */
 #define BLI_array_fake_user(arr)                                              \
-	((void)_##arr##_count,                                                    \
+	((void)_##arr##_len,                                                      \
 	 (void)_##arr##_static)
 
 /** \} */
@@ -191,7 +192,7 @@ void _bli_array_grow_func(void **arr_p, const void *arr_static,
 #define BLI_array_fixedstack_free(arr)                                        \
 	if (_##arr##_is_static) {                                                 \
 		MEM_freeN(arr);                                                       \
-	} (void)0
+	} ((void)0)
 
 /** \} */
 
diff --git a/source/blender/blenlib/BLI_assert.h b/source/blender/blenlib/BLI_assert.h
new file mode 100644
index 0000000000000000000000000000000000000000..9fb0954e77fee5daaf20465b05320b2b534cc76e
--- /dev/null
+++ b/source/blender/blenlib/BLI_assert.h
@@ -0,0 +1,111 @@
+/*
+ * ***** 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.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#ifndef __BLI_ASSERT_H__
+#define __BLI_ASSERT_H__
+
+/** \file BLI_assert.h
+ *  \ingroup bli
+ *
+ * Defines:
+ * - #BLI_assert
+ * - #BLI_STATIC_ASSERT
+ * - #BLI_STATIC_ASSERT_ALIGN
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef NDEBUG /* for BLI_assert */
+#include <stdio.h>
+#endif
+
+/* BLI_assert(), default only to print
+ * for aborting need to define WITH_ASSERT_ABORT
+ */
+/* For 'abort' only. */
+#include <stdlib.h>
+
+#ifndef NDEBUG
+#  include "BLI_system.h"
+#  ifdef WITH_ASSERT_ABORT
+#    define _BLI_DUMMY_ABORT abort
+#  else
+#    define _BLI_DUMMY_ABORT() (void)0
+#  endif
+#  if defined(__GNUC__) || defined(_MSC_VER) /* check __func__ is available */
+#    define BLI_assert(a)                                                     \
+	(void)((!(a)) ?  (                                                        \
+		(                                                                     \
+		BLI_system_backtrace(stderr),                                         \
+		fprintf(stderr,                                                       \
+			"BLI_assert failed: %s:%d, %s(), at \'%s\'\n",                    \
+			__FILE__, __LINE__, __func__, "" #a),                             \
+		_BLI_DUMMY_ABORT(),                                                   \
+		NULL)) : NULL)
+#  else
+#    define BLI_assert(a)                                                     \
+	(void)((!(a)) ?  (                                                        \
+		(                                                                     \
+		fprintf(stderr,                                                       \
+			"BLI_assert failed: %s:%d, at \'%s\'\n",                          \
+			__FILE__, __LINE__, "" #a),                                       \
+		_BLI_DUMMY_ABORT(),                                                   \
+		NULL)) : NULL)
+#  endif
+#else
+#  define BLI_assert(a) (void)0
+#endif
+
+/* C++ can't use _Static_assert, expects static_assert() but c++0x only,
+ * Coverity also errors out. */
+#if (!defined(__cplusplus)) && \
+    (!defined(__COVERITY__)) && \
+    (defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 406))  /* gcc4.6+ only */
+#  define BLI_STATIC_ASSERT(a, msg) __extension__ _Static_assert(a, msg);
+#else
+/* Code adapted from http://www.pixelbeat.org/programming/gcc/static_assert.html */
+/* Note we need the two concats below because arguments to ## are not expanded, so we need to
+ * expand __LINE__ with one indirection before doing the actual concatenation. */
+#  define ASSERT_CONCAT_(a, b) a##b
+#  define ASSERT_CONCAT(a, b) ASSERT_CONCAT_(a, b)
+   /* These can't be used after statements in c89. */
+#  if defined(__COUNTER__)  /* MSVC */
+#    define BLI_STATIC_ASSERT(a, msg) \
+         ; enum { ASSERT_CONCAT(static_assert_, __COUNTER__) = 1 / (int)(!!(a)) };
+#  else  /* older gcc, clang... */
+    /* This can't be used twice on the same line so ensure if using in headers
+     * that the headers are not included twice (by wrapping in #ifndef...#endif)
+     * Note it doesn't cause an issue when used on same line of separate modules
+     * compiled with gcc -combine -fwhole-program. */
+#    define BLI_STATIC_ASSERT(a, msg) \
+         ; enum { ASSERT_CONCAT(assert_line_, __LINE__) = 1 / (int)(!!(a)) };
+#  endif
+#endif
+
+#define BLI_STATIC_ASSERT_ALIGN(st, align) \
+  BLI_STATIC_ASSERT((sizeof(st) % (align) == 0), "Structure must be strictly aligned")
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* __BLI_ASSERT_H__ */
diff --git a/source/blender/blenlib/BLI_console.h b/source/blender/blenlib/BLI_console.h
new file mode 100644
index 0000000000000000000000000000000000000000..bf433f78f70010be83786a34ccbc29694942bd31
--- /dev/null
+++ b/source/blender/blenlib/BLI_console.h
@@ -0,0 +1,42 @@
+/*
+ * ***** 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) 2018 Blender Foundation.
+ * All rights reserved.
+ *
+ * Contributor(s): Srrgey Sharybin.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#ifndef __BLI_CONSOLE_H__
+#define __BLI_CONSOLE_H__
+
+/** \file BLI_console.h
+ *  \ingroup bli
+ *  \brief Set of utility functions and constants to work with consoles.
+ */
+
+/* Format string where one could BLI_snprintf() R, G and B values
+ * and get proper marker to start colored output in the console.
+ */
+#define TRUECOLOR_ANSI_COLOR_FORMAT "\x1b[38;2;%d;%d;%dm"
+
+/* Marker which indicates that colored output is finished. */
+#define TRUECOLOR_ANSI_COLOR_FINISH "\x1b[0m"
+
+#endif  /* __BLI_CONSOLE_H__ */
diff --git a/source/blender/blenlib/BLI_hash.h b/source/blender/blenlib/BLI_hash.h
index fa79481e25cbc87b3c25fc82dc22ac2669770b39..9ca7898d6b89616351362712a9704900cbc85f1b 100644
--- a/source/blender/blenlib/BLI_hash.h
+++ b/source/blender/blenlib/BLI_hash.h
@@ -68,4 +68,16 @@ BLI_INLINE float BLI_hash_int_01(unsigned int k)
 	return (float)BLI_hash_int(k) * (1.0f / (float)0xFFFFFFFF);
 }
 
+BLI_INLINE void BLI_hash_pointer_to_color(const void *ptr, int *r, int *g, int *b)
+{
+	size_t val = (size_t)ptr;
+	const size_t hash_a = BLI_hash_int(val & 0x0000ffff);
+	const size_t hash_b = BLI_hash_int((val & 0xffff0000) >> 32);
+	const size_t hash =
+	        hash_a ^ (hash_b + 0x9e3779b9 + (hash_a << 6) + (hash_a >> 2));
+	*r = (hash & 0xff0000) >> 16;
+	*g = (hash & 0x00ff00) >> 8;
+	*b = hash & 0x0000ff;
+}
+
 #endif // __BLI_HASH_H__
diff --git a/source/blender/blenlib/BLI_math_base.h b/source/blender/blenlib/BLI_math_base.h
index 3b24cae018d21ed90c088f10b1c3b145738d1b35..6f8e48d83b22c062d3e31d2b11bbdb31ea88b3d0 100644
--- a/source/blender/blenlib/BLI_math_base.h
+++ b/source/blender/blenlib/BLI_math_base.h
@@ -35,6 +35,7 @@
 #endif
 
 #include <math.h>
+#include "BLI_assert.h"
 #include "BLI_math_inline.h"
 
 #ifndef M_PI
diff --git a/source/blender/blenlib/BLI_utildefines.h b/source/blender/blenlib/BLI_utildefines.h
index ee1acc5afddb1e7e812194b700ed20cd28ebe0ab..11c8a586784076290604fde538d13baea7c3aecd 100644
--- a/source/blender/blenlib/BLI_utildefines.h
+++ b/source/blender/blenlib/BLI_utildefines.h
@@ -41,9 +41,8 @@ extern "C" {
 #include "BLI_compiler_compat.h"
 #include "BLI_utildefines_variadic.h"
 
-#ifndef NDEBUG /* for BLI_assert */
-#include <stdio.h>
-#endif
+/* We could remove in future. */
+#include "BLI_assert.h"
 
 /* useful for finding bad use of min/max */
 #if 0
@@ -615,74 +614,6 @@ extern bool BLI_memory_is_zero(const void *arr, const size_t arr_size);
 #  define UNUSED_VARS_NDEBUG UNUSED_VARS
 #endif
 
-
-/* BLI_assert(), default only to print
- * for aborting need to define WITH_ASSERT_ABORT
- */
-/* For 'abort' only. */
-#include <stdlib.h>
-
-#ifndef NDEBUG
-#  include "BLI_system.h"
-#  ifdef WITH_ASSERT_ABORT
-#    define _BLI_DUMMY_ABORT abort
-#  else
-#    define _BLI_DUMMY_ABORT() (void)0
-#  endif
-#  if defined(__GNUC__) || defined(_MSC_VER) /* check __func__ is available */
-#    define BLI_assert(a)                                                     \
-	(void)((!(a)) ?  (                                                        \
-		(                                                                     \
-		BLI_system_backtrace(stderr),                                         \
-		fprintf(stderr,                                                       \
-			"BLI_assert failed: %s:%d, %s(), at \'%s\'\n",                    \
-			__FILE__, __LINE__, __func__, STRINGIFY(a)),                      \
-		_BLI_DUMMY_ABORT(),                                                   \
-		NULL)) : NULL)
-#  else
-#    define BLI_assert(a)                                                     \
-	(void)((!(a)) ?  (                                                        \
-		(                                                                     \
-		fprintf(stderr,                                                       \
-			"BLI_assert failed: %s:%d, at \'%s\'\n",                          \
-			__FILE__, __LINE__, STRINGIFY(a)),                                \
-		_BLI_DUMMY_ABORT(),                                                   \
-		NULL)) : NULL)
-#  endif
-#else
-#  define BLI_assert(a) (void)0
-#endif
-
-/* C++ can't use _Static_assert, expects static_assert() but c++0x only,
- * Coverity also errors out. */
-#if (!defined(__cplusplus)) && \
-    (!defined(__COVERITY__)) && \
-    (defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 406))  /* gcc4.6+ only */
-#  define BLI_STATIC_ASSERT(a, msg) __extension__ _Static_assert(a, msg);
-#else
-/* Code adapted from http://www.pixelbeat.org/programming/gcc/static_assert.html */
-/* Note we need the two concats below because arguments to ## are not expanded, so we need to
- * expand __LINE__ with one indirection before doing the actual concatenation. */
-#  define ASSERT_CONCAT_(a, b) a##b
-#  define ASSERT_CONCAT(a, b) ASSERT_CONCAT_(a, b)
-   /* These can't be used after statements in c89. */
-#  if defined(__COUNTER__)  /* MSVC */
-#    define BLI_STATIC_ASSERT(a, msg) \
-         ; enum { ASSERT_CONCAT(static_assert_, __COUNTER__) = 1 / (int)(!!(a)) };
-#  else  /* older gcc, clang... */
-    /* This can't be used twice on the same line so ensure if using in headers
-     * that the headers are not included twice (by wrapping in #ifndef...#endif)
-     * Note it doesn't cause an issue when used on same line of separate modules
-     * compiled with gcc -combine -fwhole-program. */
-#    define BLI_STATIC_ASSERT(a, msg) \
-         ; enum { ASSERT_CONCAT(assert_line_, __LINE__) = 1 / (int)(!!(a)) };
-#  endif
-#endif
-
-
-#define BLI_STATIC_ASSERT_ALIGN(st, align) \
-  BLI_STATIC_ASSERT((sizeof(st) % (align) == 0), "Structure must be strictly aligned")
-
 /* hints for branch prediction, only use in code that runs a _lot_ where */
 #ifdef __GNUC__
 #  define LIKELY(x)       __builtin_expect(!!(x), 1)
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index ef36b5ac6bd0efa26e4556f55e5a0a051b0627fc..5bcf4303a841563bc1a273f1d108d5b5dcad1ff1 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -139,6 +139,7 @@ set(SRC
 	BLI_compiler_attrs.h
 	BLI_compiler_compat.h
 	BLI_compiler_typecheck.h
+	BLI_console.h
 	BLI_convexhull_2d.h
 	BLI_dial_2d.h
 	BLI_dlrbTree.h
diff --git a/source/blender/blenlib/intern/BLI_array.c b/source/blender/blenlib/intern/BLI_array.c
index f681d222e695789c3307db595b0f7c9ac7e78964..d16dd36763da2391b914270391070090145a5ac2 100644
--- a/source/blender/blenlib/intern/BLI_array.c
+++ b/source/blender/blenlib/intern/BLI_array.c
@@ -66,21 +66,23 @@
 /**
  * This function is only to be called via macros.
  *
- * \note The caller must adjust \a arr_count
+ * \note The caller must adjust \a arr_len
  */
-void _bli_array_grow_func(void **arr_p, const void *arr_static,
-                          const int sizeof_arr_p, const int arr_count, const int num,
-                          const char *alloc_str)
+void _bli_array_grow_func(
+        void **arr_p, const void *arr_static,
+        const int sizeof_arr_p, const int arr_len, const int num,
+        const char *alloc_str)
 {
 	void *arr = *arr_p;
 	void *arr_tmp;
 
-	arr_tmp = MEM_mallocN(sizeof_arr_p *
-	                      ((num < arr_count) ?
-	                      (arr_count * 2 + 2) : (arr_count + num)), alloc_str);
+	arr_tmp = MEM_mallocN(
+	        sizeof_arr_p *
+	        ((num < arr_len) ?
+	         (arr_len * 2 + 2) : (arr_len + num)), alloc_str);
 
 	if (arr) {
-		memcpy(arr_tmp, arr, sizeof_arr_p * arr_count);
+		memcpy(arr_tmp, arr, sizeof_arr_p * arr_len);
 
 		if (arr != arr_static) {
 			MEM_freeN(arr);
@@ -91,6 +93,6 @@ void _bli_array_grow_func(void **arr_p, const void *arr_static,
 
 	/* caller must do */
 #if 0
-	arr_count += num;
+	arr_len += num;
 #endif
 }
diff --git a/source/blender/blenlib/intern/BLI_kdopbvh.c b/source/blender/blenlib/intern/BLI_kdopbvh.c
index c1187fdd8bb54c4e8fa9b1c0594f85c4ee15b786..06e8ade68a218f7665524f92c03d4063a8c07ab4 100644
--- a/source/blender/blenlib/intern/BLI_kdopbvh.c
+++ b/source/blender/blenlib/intern/BLI_kdopbvh.c
@@ -856,9 +856,8 @@ static void non_recursive_bvh_div_nodes_task_cb(
 		else {
 			break;
 		}
-
-		parent->totnode = (char)(k + 1);
 	}
+	parent->totnode = (char)k;
 }
 
 /**
diff --git a/source/blender/blenlib/intern/math_base_inline.c b/source/blender/blenlib/intern/math_base_inline.c
index 1f517471407ef272d628b6bd8e48d22b1081bdc3..eed06c7841b3dc69173462990e044d0e9b2c455c 100644
--- a/source/blender/blenlib/intern/math_base_inline.c
+++ b/source/blender/blenlib/intern/math_base_inline.c
@@ -368,10 +368,8 @@ MINLINE int compare_ff_relative(float a, float b, const float max_diff, const in
 {
 	union {float f; int i;} ua, ub;
 
-#if 0  /* No BLI_assert in INLINE :/ */
 	BLI_assert(sizeof(float) == sizeof(int));
 	BLI_assert(max_ulps < (1 << 22));
-#endif
 
 	if (fabsf(a - b) <= max_diff) {
 		return 1;
diff --git a/source/blender/blenlib/intern/math_bits_inline.c b/source/blender/blenlib/intern/math_bits_inline.c
index 37fdcd7878a5f45f6f77946ad15078cfcb44436d..9b16756134e3285820104db3a44a2de1c2edf1af 100644
--- a/source/blender/blenlib/intern/math_bits_inline.c
+++ b/source/blender/blenlib/intern/math_bits_inline.c
@@ -34,7 +34,7 @@
 MINLINE int bitscan_forward_i(int a)
 {
 	BLI_assert(a != 0);
-#  ifdef _MSC_VER
+#ifdef _MSC_VER
 	unsigned long ctz;
 	_BitScanForward(&ctz, a);
 	return ctz;
@@ -63,7 +63,7 @@ MINLINE unsigned int bitscan_forward_clear_uint(unsigned int *a)
 MINLINE int bitscan_reverse_i(int a)
 {
 	BLI_assert(a != 0);
-#  ifdef _MSC_VER
+#ifdef _MSC_VER
 	unsigned long clz;
 	_BitScanReverse(&clz, a);
 	return clz;
diff --git a/source/blender/bmesh/intern/bmesh_core.c b/source/blender/bmesh/intern/bmesh_core.c
index aba0160622f714ab206721e7fa4d896ec4239cca..c4b29e91fb421b9af01e61f5d8e81838786d8430 100644
--- a/source/blender/bmesh/intern/bmesh_core.c
+++ b/source/blender/bmesh/intern/bmesh_core.c
@@ -1282,8 +1282,8 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del)
 	}
 
 	/* create region face */
-	f_new = BLI_array_count(edges) ?
-	        BM_face_create_ngon(bm, v1, v2, edges, BLI_array_count(edges), faces[0], BM_CREATE_NOP) : NULL;
+	f_new = BLI_array_len(edges) ?
+	        BM_face_create_ngon(bm, v1, v2, edges, BLI_array_len(edges), faces[0], BM_CREATE_NOP) : NULL;
 	if (UNLIKELY(f_new == NULL)) {
 		/* Invalid boundary region to join faces */
 		goto error;
@@ -1347,11 +1347,11 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del)
 
 	/* delete old geometry */
 	if (do_del) {
-		for (i = 0; i < BLI_array_count(deledges); i++) {
+		for (i = 0; i < BLI_array_len(deledges); i++) {
 			BM_edge_kill(bm, deledges[i]);
 		}
 
-		for (i = 0; i < BLI_array_count(delverts); i++) {
+		for (i = 0; i < BLI_array_len(delverts); i++) {
 			BM_vert_kill(bm, delverts[i]);
 		}
 	}
diff --git a/source/blender/bmesh/intern/bmesh_mods.c b/source/blender/bmesh/intern/bmesh_mods.c
index 1cd51528e0620b39e401944d042e1c42fd1a2da4..961cc4587840bd4abcfab486bf8475185ba14955 100644
--- a/source/blender/bmesh/intern/bmesh_mods.c
+++ b/source/blender/bmesh/intern/bmesh_mods.c
@@ -482,8 +482,8 @@ BMEdge *BM_vert_collapse_faces(
 			BLI_array_append(faces, f);
 		}
 
-		if (BLI_array_count(faces) >= 2) {
-			BMFace *f2 = BM_faces_join(bm, faces, BLI_array_count(faces), true);
+		if (BLI_array_len(faces) >= 2) {
+			BMFace *f2 = BM_faces_join(bm, faces, BLI_array_len(faces), true);
 			if (f2) {
 				BMLoop *l_a, *l_b;
 
@@ -499,7 +499,7 @@ BMEdge *BM_vert_collapse_faces(
 			}
 		}
 
-		BLI_assert(BLI_array_count(faces) < 8);
+		BLI_assert(BLI_array_len(faces) < 8);
 
 		BLI_array_free(faces);
 	}
@@ -608,7 +608,7 @@ BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac)
 		} while (l != e->l);
 		
 		/* flag existing faces so we can differentiate oldfaces from new faces */
-		for (i = 0; i < BLI_array_count(oldfaces); i++) {
+		for (i = 0; i < BLI_array_len(oldfaces); i++) {
 			BM_ELEM_API_FLAG_ENABLE(oldfaces[i], _FLAG_OVERLAP);
 			oldfaces[i] = BM_face_copy(bm, bm, oldfaces[i], true, true);
 			BM_ELEM_API_FLAG_DISABLE(oldfaces[i], _FLAG_OVERLAP);
@@ -639,7 +639,7 @@ BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac)
 		int i, j;
 
 		/* interpolate new/changed loop data from copied old faces */
-		for (i = 0; i < BLI_array_count(oldfaces); i++) {
+		for (i = 0; i < BLI_array_len(oldfaces); i++) {
 			float f_center_old[3];
 
 			BM_face_calc_center_mean(oldfaces[i], f_center_old);
@@ -671,7 +671,7 @@ BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac)
 		}
 		
 		/* destroy the old faces */
-		for (i = 0; i < BLI_array_count(oldfaces); i++) {
+		for (i = 0; i < BLI_array_len(oldfaces); i++) {
 			BM_face_verts_kill(bm, oldfaces[i]);
 		}
 		
diff --git a/source/blender/bmesh/intern/bmesh_polygon_edgenet.c b/source/blender/bmesh/intern/bmesh_polygon_edgenet.c
index 37e68f1d5ff73188f83cf7dae525bc0efe4a6c3a..85dfd3f58e28348498664d38bb005a2fd06ab56f 100644
--- a/source/blender/bmesh/intern/bmesh_polygon_edgenet.c
+++ b/source/blender/bmesh/intern/bmesh_polygon_edgenet.c
@@ -665,7 +665,7 @@ bool BM_face_split_edgenet(
 		BM_ELEM_API_FLAG_DISABLE(l_iter->v, VERT_VISIT);
 	} while ((l_iter = l_iter->next) != l_first);
 
-	if (BLI_array_count(face_arr)) {
+	if (BLI_array_len(face_arr)) {
 		bmesh_face_swap_data(f, face_arr[0]);
 		BM_face_kill(bm, face_arr[0]);
 		face_arr[0] = f;
@@ -674,13 +674,13 @@ bool BM_face_split_edgenet(
 		BM_ELEM_API_FLAG_DISABLE(f, FACE_NET);
 	}
 
-	for (i = 0; i < BLI_array_count(face_arr); i++) {
+	for (i = 0; i < BLI_array_len(face_arr); i++) {
 		BM_ELEM_API_FLAG_DISABLE(face_arr[i], FACE_NET);
 	}
 
 	if (r_face_arr) {
 		*r_face_arr = face_arr;
-		*r_face_arr_len = BLI_array_count(face_arr);
+		*r_face_arr_len = BLI_array_len(face_arr);
 	}
 	else {
 		if (face_arr) {
diff --git a/source/blender/bmesh/operators/bmo_dissolve.c b/source/blender/bmesh/operators/bmo_dissolve.c
index 5a2f07be70a1a7603d638d356f4124f335f3bb90..816d2e8d009c7fc68b3cad179ac081914a2838d6 100644
--- a/source/blender/bmesh/operators/bmo_dissolve.c
+++ b/source/blender/bmesh/operators/bmo_dissolve.c
@@ -179,7 +179,7 @@ void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op)
 		}
 		BMW_end(&regwalker);
 		
-		for (i = 0; i < BLI_array_count(faces); i++) {
+		for (i = 0; i < BLI_array_len(faces); i++) {
 			f_iter = faces[i];
 			BMO_face_flag_disable(bm, f_iter, FACE_TAG);
 			BMO_face_flag_enable(bm, f_iter, FACE_ORIG);
@@ -198,7 +198,7 @@ void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op)
 	/* track how many faces we should end up with */
 	int totface_target = bm->totface;
 
-	for (i = 0; i < BLI_array_count(regions); i++) {
+	for (i = 0; i < BLI_array_len(regions); i++) {
 		BMFace *f_new;
 		int tot = 0;
 		
@@ -259,7 +259,7 @@ void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op)
 
 cleanup:
 	/* free/cleanup */
-	for (i = 0; i < BLI_array_count(regions); i++) {
+	for (i = 0; i < BLI_array_len(regions); i++) {
 		if (regions[i]) MEM_freeN(regions[i]);
 	}
 
diff --git a/source/blender/bmesh/operators/bmo_edgenet.c b/source/blender/bmesh/operators/bmo_edgenet.c
index 744ef866128b6766932e4bd2a9cfad2d254954f1..931ac684b07156b5aa97a59e3f2ccb1fe723bbd0 100644
--- a/source/blender/bmesh/operators/bmo_edgenet.c
+++ b/source/blender/bmesh/operators/bmo_edgenet.c
@@ -179,22 +179,22 @@ void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op)
 
 		if (!count) {
 			edges1 = edges;
-			BLI_array_count_set(edges1, BLI_array_count(edges));
+			BLI_array_len_set(edges1, BLI_array_len(edges));
 		}
 		else {
 			edges2 = edges;
-			BLI_array_count_set(edges2, BLI_array_count(edges));
+			BLI_array_len_set(edges2, BLI_array_len(edges));
 		}
 
 		BLI_array_clear(edges);
 		count++;
 	}
 
-	if (edges1 && BLI_array_count(edges1) > 2 &&
-	    BM_edge_share_vert_check(edges1[0], edges1[BLI_array_count(edges1) - 1]))
+	if (edges1 && BLI_array_len(edges1) > 2 &&
+	    BM_edge_share_vert_check(edges1[0], edges1[BLI_array_len(edges1) - 1]))
 	{
-		if (edges2 && BLI_array_count(edges2) > 2 &&
-		    BM_edge_share_vert_check(edges2[0], edges2[BLI_array_count(edges2) - 1]))
+		if (edges2 && BLI_array_len(edges2) > 2 &&
+		    BM_edge_share_vert_check(edges2[0], edges2[BLI_array_len(edges2) - 1]))
 		{
 			BLI_array_free(edges1);
 			BLI_array_free(edges2);
@@ -206,8 +206,8 @@ void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op)
 		}
 	}
 
-	if (edges2 && BLI_array_count(edges2) > 2 &&
-	    BM_edge_share_vert_check(edges2[0], edges2[BLI_array_count(edges2) - 1]))
+	if (edges2 && BLI_array_len(edges2) > 2 &&
+	    BM_edge_share_vert_check(edges2[0], edges2[BLI_array_len(edges2) - 1]))
 	{
 		edges2 = NULL;
 	}
@@ -218,23 +218,23 @@ void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op)
 		float dvec1[3];
 		float dvec2[3];
 
-		if (BLI_array_count(edges1) == 1) {
+		if (BLI_array_len(edges1) == 1) {
 			v1 = edges1[0]->v1;
 			v2 = edges1[0]->v2;
 		}
 		else {
 			v1 = BM_vert_in_edge(edges1[1], edges1[0]->v1) ? edges1[0]->v2 : edges1[0]->v1;
-			i  = BLI_array_count(edges1) - 1;
+			i  = BLI_array_len(edges1) - 1;
 			v2 = BM_vert_in_edge(edges1[i - 1], edges1[i]->v1) ? edges1[i]->v2 : edges1[i]->v1;
 		}
 
-		if (BLI_array_count(edges2) == 1) {
+		if (BLI_array_len(edges2) == 1) {
 			v3 = edges2[0]->v1;
 			v4 = edges2[0]->v2;
 		}
 		else {
 			v3 = BM_vert_in_edge(edges2[1], edges2[0]->v1) ? edges2[0]->v2 : edges2[0]->v1;
-			i  = BLI_array_count(edges2) - 1;
+			i  = BLI_array_len(edges2) - 1;
 			v4 = BM_vert_in_edge(edges2[i - 1], edges2[i]->v1) ? edges2[i]->v2 : edges2[i]->v1;
 		}
 
@@ -265,9 +265,9 @@ void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op)
 	else if (edges1) {
 		BMVert *v1, *v2;
 
-		if (BLI_array_count(edges1) > 1) {
+		if (BLI_array_len(edges1) > 1) {
 			v1 = BM_vert_in_edge(edges1[1], edges1[0]->v1) ? edges1[0]->v2 : edges1[0]->v1;
-			i  = BLI_array_count(edges1) - 1;
+			i  = BLI_array_len(edges1) - 1;
 			v2 = BM_vert_in_edge(edges1[i - 1], edges1[i]->v1) ? edges1[i]->v2 : edges1[i]->v1;
 			e  = BM_edge_create(bm, v1, v2, NULL, BM_CREATE_NO_DOUBLE);
 			BMO_edge_flag_enable(bm, e, ELE_NEW);
diff --git a/source/blender/bmesh/operators/bmo_subdivide.c b/source/blender/bmesh/operators/bmo_subdivide.c
index 7d3419b591014c5064e59d62b92ef574185d8df5..8f998797a16c81890b19c56e4133c346e7c374b4 100644
--- a/source/blender/bmesh/operators/bmo_subdivide.c
+++ b/source/blender/bmesh/operators/bmo_subdivide.c
@@ -1157,7 +1157,7 @@ void bmo_subdivide_edges_exec(BMesh *bm, BMOperator *op)
 				loops[a] = l;
 			}
 			
-			vlen = BLI_array_count(loops);
+			vlen = BLI_array_len(loops);
 
 			/* find the boundary of one of the split edges */
 			for (a = 1; a < vlen; a++) {
@@ -1236,9 +1236,9 @@ void bmo_subdivide_edges_exec(BMesh *bm, BMOperator *op)
 			 * - concave corner of an ngon.
 			 * - 2 edges being used in 2+ ngons.
 			 */
-//			BM_face_splits_check_legal(bm, face, loops_split, BLI_array_count(loops_split));
+//			BM_face_splits_check_legal(bm, face, loops_split, BLI_array_len(loops_split));
 
-			for (j = 0; j < BLI_array_count(loops_split); j++) {
+			for (j = 0; j < BLI_array_len(loops_split); j++) {
 				if (loops_split[j][0]) {
 					BMFace *f_new;
 					BLI_assert(BM_edge_exists(loops_split[j][0]->v, loops_split[j][1]->v) == NULL);
diff --git a/source/blender/bmesh/tools/bmesh_bevel.c b/source/blender/bmesh/tools/bmesh_bevel.c
index beee9065eceeb6316143e346cdcf1389e40b666d..457e74ce1aa020cadea1b308e4e983b113ae5501 100644
--- a/source/blender/bmesh/tools/bmesh_bevel.c
+++ b/source/blender/bmesh/tools/bmesh_bevel.c
@@ -3209,7 +3209,7 @@ static void build_center_ngon(BMesh *bm, BevVert *bv, int mat_nr)
 			BLI_array_append(ve, NULL);
 		}
 	} while ((v = v->next) != vm->boundstart);
-	bev_create_ngon(bm, vv, BLI_array_count(vv), vf, frep, ve, mat_nr, true);
+	bev_create_ngon(bm, vv, BLI_array_len(vv), vf, frep, ve, mat_nr, true);
 
 	BLI_array_free(vv);
 	BLI_array_free(vf);
@@ -3960,7 +3960,7 @@ static int bevel_edge_order_extend(BMesh *bm, BevVert *bv, int i)
 			BLI_array_append(sucs, bme2);
 		}
 	}
-	nsucs = BLI_array_count(sucs);
+	nsucs = BLI_array_len(sucs);
 
 	bestj = j = i;
 	for (sucindex = 0; sucindex < nsucs; sucindex++) {
@@ -4500,15 +4500,15 @@ static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f)
 		}
 	}
 	if (do_rebuild) {
-		n = BLI_array_count(vv);
+		n = BLI_array_len(vv);
 		f_new = bev_create_ngon(bm, vv, n, NULL, f, NULL, -1, true);
 
-		for (k = 0; k < BLI_array_count(vv_fix); k++) {
+		for (k = 0; k < BLI_array_len(vv_fix); k++) {
 			bev_merge_uvs(bm, vv_fix[k]);
 		}
 
 		/* copy attributes from old edges */
-		BLI_assert(n == BLI_array_count(ee));
+		BLI_assert(n == BLI_array_len(ee));
 		bme_prev = ee[n - 1];
 		for (k = 0; k < n; k++) {
 			bme_new = BM_edge_exists(vv[k], vv[(k + 1) % n]);
diff --git a/source/blender/collada/AnimationExporter.cpp b/source/blender/collada/AnimationExporter.cpp
index 53c1afd173f32df2f2c7a14395383992715c00d3..1df0705c855f96c5e5086a75be4f78caf8bc751d 100644
--- a/source/blender/collada/AnimationExporter.cpp
+++ b/source/blender/collada/AnimationExporter.cpp
@@ -772,7 +772,7 @@ void AnimationExporter::dae_baked_animation(std::vector<float> &fra, Object *ob_
 
 	addSampler(sampler);
 
-	std::string target = get_joint_id(bone, ob_arm) + "/transform";
+	std::string target = get_joint_id(ob_arm, bone) + "/transform";
 	addChannel(COLLADABU::URI(empty, sampler_id), target);
 
 	closeAnimation();
diff --git a/source/blender/collada/ArmatureExporter.cpp b/source/blender/collada/ArmatureExporter.cpp
index 844be2dd60bd76a57609c399f390168a15209816..fbceb6e077f200436149a796c336ea3239b9f7f7 100644
--- a/source/blender/collada/ArmatureExporter.cpp
+++ b/source/blender/collada/ArmatureExporter.cpp
@@ -89,7 +89,7 @@ void ArmatureExporter::add_armature_bones(const EvaluationContext *eval_ctx, Obj
 void ArmatureExporter::write_bone_URLs(COLLADASW::InstanceController &ins, Object *ob_arm, Bone *bone)
 {
 	if (bc_is_root_bone(bone, this->export_settings->deform_bones_only))
-		ins.addSkeleton(COLLADABU::URI(COLLADABU::Utils::EMPTY_STRING, get_joint_id(bone, ob_arm)));
+		ins.addSkeleton(COLLADABU::URI(COLLADABU::Utils::EMPTY_STRING, get_joint_id(ob_arm, bone)));
 	else {
 		for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
 			write_bone_URLs(ins, ob_arm, child);
@@ -162,9 +162,9 @@ void ArmatureExporter::add_bone_node(const EvaluationContext *eval_ctx, Bone *bo
                                      std::list<Object *>& child_objects)
 {
 	if (!(this->export_settings->deform_bones_only && bone->flag & BONE_NO_DEFORM)) {
-		std::string node_id = get_joint_id(bone, ob_arm);
+		std::string node_id = get_joint_id(ob_arm, bone);
 		std::string node_name = std::string(bone->name);
-		std::string node_sid = get_joint_sid(bone, ob_arm);
+		std::string node_sid = get_joint_sid(bone);
 
 		COLLADASW::Node node(mSW);
 
diff --git a/source/blender/collada/ControllerExporter.cpp b/source/blender/collada/ControllerExporter.cpp
index ae99e6bafdece1e9d4bdb09827bdff3cbe2a26a2..f32cb624f780a7d5ddb8cd7598d467b8964bc09f 100644
--- a/source/blender/collada/ControllerExporter.cpp
+++ b/source/blender/collada/ControllerExporter.cpp
@@ -71,7 +71,7 @@ bool ControllerExporter::is_skinned_mesh(Object *ob)
 void ControllerExporter::write_bone_URLs(COLLADASW::InstanceController &ins, Object *ob_arm, Bone *bone)
 {
 	if (bc_is_root_bone(bone, this->export_settings->deform_bones_only))
-		ins.addSkeleton(COLLADABU::URI(COLLADABU::Utils::EMPTY_STRING, get_joint_id(bone, ob_arm)));
+		ins.addSkeleton(COLLADABU::URI(COLLADABU::Utils::EMPTY_STRING, get_joint_id(ob_arm, bone)));
 	else {
 		for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
 			write_bone_URLs(ins, ob_arm, child);
@@ -456,7 +456,7 @@ std::string ControllerExporter::add_joints_source(Object *ob_arm, ListBase *defb
 	for (def = (bDeformGroup *)defbase->first; def; def = def->next) {
 		Bone *bone = get_bone_from_defgroup(ob_arm, def);
 		if (bone)
-			source.appendValues(get_joint_sid(bone, ob_arm));
+			source.appendValues(get_joint_sid(bone));
 	}
 
 	source.finish();
diff --git a/source/blender/collada/collada_internal.cpp b/source/blender/collada/collada_internal.cpp
index 6ebde6bd773a7cd4457af782fe485329cf46c6f4..cf4dcb5eb4232af51d75baa269895a9b349b5fae 100644
--- a/source/blender/collada/collada_internal.cpp
+++ b/source/blender/collada/collada_internal.cpp
@@ -327,12 +327,12 @@ std::string get_light_id(Object *ob)
 	return translate_id(id_name(ob)) + "-light";
 }
 
-std::string get_joint_id(Bone *bone, Object *ob_arm)
+std::string get_joint_id(Object *ob, Bone *bone)
 {
-	return translate_id(id_name(ob_arm) + "_" + bone->name);
+	return translate_id(id_name(ob) + "_" + bone->name);
 }
 
-std::string get_joint_sid(Bone *bone, Object *ob_arm)
+std::string get_joint_sid(Bone *bone)
 {
 	return translate_id(bone->name);
 }
diff --git a/source/blender/collada/collada_internal.h b/source/blender/collada/collada_internal.h
index 1c7aa160f5782b4c0c5aacfdbcfde51d8ec50e75..299e13326ce8aba18bcda1bf32d7ea97de180d1c 100644
--- a/source/blender/collada/collada_internal.h
+++ b/source/blender/collada/collada_internal.h
@@ -97,8 +97,8 @@ extern std::string get_geometry_id(Object *ob, bool use_instantiation);
 
 extern std::string get_light_id(Object *ob);
 
-extern std::string get_joint_id(Bone *bone, Object *ob_arm);
-extern std::string get_joint_sid(Bone *bone, Object *ob_arm);
+extern std::string get_joint_id(Object *ob, Bone *bone);
+extern std::string get_joint_sid(Bone *bone);
 
 extern std::string get_camera_id(Object *ob);
 
diff --git a/source/blender/depsgraph/DEG_depsgraph.h b/source/blender/depsgraph/DEG_depsgraph.h
index 477c0dd36ea2570ed1b0075e288c59234f30e843..5cd1b48e80e332bf7477fd3661ad215bf1f3ec0f 100644
--- a/source/blender/depsgraph/DEG_depsgraph.h
+++ b/source/blender/depsgraph/DEG_depsgraph.h
@@ -280,6 +280,32 @@ typedef void (*DEG_EditorUpdateSceneCb)(
 void DEG_editors_set_update_cb(DEG_EditorUpdateIDCb id_func,
                                DEG_EditorUpdateSceneCb scene_func);
 
+/* Evaluation Debug ------------------------------ */
+
+void DEG_debug_print_eval(const char* function_name,
+                          const char* object_name,
+                          const void* object_address);
+
+void DEG_debug_print_eval_subdata(const char *function_name,
+                                  const char *object_name,
+                                  const void *object_address,
+                                  const char *subdata_comment,
+                                  const char *subdata_name,
+                                  const void *subdata_address);
+
+void DEG_debug_print_eval_subdata_index(const char *function_name,
+                                        const char *object_name,
+                                        const void *object_address,
+                                        const char *subdata_comment,
+                                        const char *subdata_name,
+                                        const void *subdata_address,
+                                        const int subdata_index);
+
+void DEG_debug_print_eval_time(const char* function_name,
+                               const char* object_name,
+                               const void* object_address,
+                               float time);
+
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
diff --git a/source/blender/depsgraph/intern/depsgraph.cc b/source/blender/depsgraph/intern/depsgraph.cc
index ee4ffee772e6daa415f81969633c195868c84293..a2e6993e44262991f3faede453c9ffecee06a15e 100644
--- a/source/blender/depsgraph/intern/depsgraph.cc
+++ b/source/blender/depsgraph/intern/depsgraph.cc
@@ -35,6 +35,8 @@
 #include "MEM_guardedalloc.h"
 
 #include "BLI_utildefines.h"
+#include "BLI_console.h"
+#include "BLI_hash.h"
 #include "BLI_ghash.h"
 #include "BLI_listbase.h"
 
@@ -526,6 +528,31 @@ void deg_editors_scene_update(const DEGEditorUpdateContext *update_ctx,
 	}
 }
 
+bool deg_terminal_do_color(void)
+{
+	return (G.debug & G_DEBUG_DEPSGRAPH_PRETTY) != 0;
+}
+
+string deg_color_for_pointer(const void *pointer)
+{
+	if (!deg_terminal_do_color()) {
+		return "";
+	}
+	int r, g, b;
+	BLI_hash_pointer_to_color(pointer, &r, &g, &b);
+	char buffer[64];
+	BLI_snprintf(buffer, sizeof(buffer), TRUECOLOR_ANSI_COLOR_FORMAT, r, g, b);
+	return string(buffer);
+}
+
+string deg_color_end(void)
+{
+	if (!deg_terminal_do_color()) {
+		return "";
+	}
+	return string(TRUECOLOR_ANSI_COLOR_FINISH);
+}
+
 }  // namespace DEG
 
 /* **************** */
@@ -553,3 +580,85 @@ void DEG_editors_set_update_cb(DEG_EditorUpdateIDCb id_func,
 	DEG::deg_editor_update_id_cb = id_func;
 	DEG::deg_editor_update_scene_cb = scene_func;
 }
+
+/* Evaluation and debug */
+
+void DEG_debug_print_eval(const char *function_name,
+                          const char *object_name,
+                          const void *object_address)
+{
+	if ((G.debug & G_DEBUG_DEPSGRAPH_EVAL) == 0) {
+		return;
+	}
+	printf("%s on %s %s(%p)%s\n",
+	       function_name,
+	       object_name,
+	       DEG::deg_color_for_pointer(object_address).c_str(),
+	       object_address,
+	       DEG::deg_color_end().c_str());
+}
+
+void DEG_debug_print_eval_subdata(const char *function_name,
+                                  const char *object_name,
+                                  const void *object_address,
+                                  const char *subdata_comment,
+                                  const char *subdata_name,
+                                  const void *subdata_address)
+{
+	if ((G.debug & G_DEBUG_DEPSGRAPH_EVAL) == 0) {
+		return;
+	}
+	printf("%s on %s %s(%p)%s %s %s %s(%p)%s\n",
+	       function_name,
+	       object_name,
+	       DEG::deg_color_for_pointer(object_address).c_str(),
+	       object_address,
+	       DEG::deg_color_end().c_str(),
+	       subdata_comment,
+	       subdata_name,
+	       DEG::deg_color_for_pointer(subdata_address).c_str(),
+	       subdata_address,
+	       DEG::deg_color_end().c_str());
+}
+
+void DEG_debug_print_eval_subdata_index(const char *function_name,
+                                        const char *object_name,
+                                        const void *object_address,
+                                        const char *subdata_comment,
+                                        const char *subdata_name,
+                                        const void *subdata_address,
+                                        const int subdata_index)
+{
+	if ((G.debug & G_DEBUG_DEPSGRAPH_EVAL) == 0) {
+		return;
+	}
+	printf("%s on %s %s(%p)^%s %s %s[%d] %s(%p)%s\n",
+	       function_name,
+	       object_name,
+	       DEG::deg_color_for_pointer(object_address).c_str(),
+	       object_address,
+	       DEG::deg_color_end().c_str(),
+	       subdata_comment,
+	       subdata_name,
+	       subdata_index,
+	       DEG::deg_color_for_pointer(subdata_address).c_str(),
+	       subdata_address,
+	       DEG::deg_color_end().c_str());
+}
+
+void DEG_debug_print_eval_time(const char *function_name,
+                               const char *object_name,
+                               const void *object_address,
+                               float time)
+{
+	if ((G.debug & G_DEBUG_DEPSGRAPH_EVAL) == 0) {
+		return;
+	}
+	printf("%s on %s %s(%p)%s at time %f\n",
+	       function_name,
+	       object_name,
+	       DEG::deg_color_for_pointer(object_address).c_str(),
+	       object_address,
+	       DEG::deg_color_end().c_str(),
+	       time);
+}
diff --git a/source/blender/depsgraph/intern/depsgraph_intern.h b/source/blender/depsgraph/intern/depsgraph_intern.h
index e310608b25e430906a8160505ff848012808d0af..9961723ed175452e0a36b3ad2d05e27dd938297b 100644
--- a/source/blender/depsgraph/intern/depsgraph_intern.h
+++ b/source/blender/depsgraph/intern/depsgraph_intern.h
@@ -125,4 +125,8 @@ void deg_editors_scene_update(const DEGEditorUpdateContext *update_ctx,
 		fflush(stderr);                     \
 	} while (0)
 
+bool deg_terminal_do_color(void);
+string deg_color_for_pointer(const void *pointer);
+string deg_color_end(void);
+
 }  // namespace DEG
diff --git a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc
index f9e99227337ea847972423d7765104110b6c2db0..5be955438010a3e6064a4e6cda152b6ffcf635db 100644
--- a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc
+++ b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc
@@ -870,6 +870,22 @@ ID *deg_update_copy_on_write_datablock(const Depsgraph *depsgraph,
 				gpumaterial_ptr = &world->gpumaterial;
 				break;
 			}
+			case ID_NT:
+			{
+				/* Node trees should try to preserve their socket pointers
+				 * as much as possible. This is due to UBOs code in GPU,
+				 * which references sockets from trees.
+				 *
+				 * These flags CURRENTLY don't need full datablock update,
+				 * everything is done by node tree update function which
+				 * only copies socket values.
+				 */
+				const int ignore_flag = (ID_RECALC_DRAW | ID_RECALC_ANIMATION);
+				if ((id_cow->recalc & ~ignore_flag) == 0) {
+					return id_cow;
+				}
+				break;
+			}
 			case ID_OB:
 			{
 				Object *object = (Object *)id_cow;
diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt
index 6618aaea4f4786b8c1f57dd89db06dca6cbb2be5..733e4b0b52412113d42076f4ffe88975003dec7a 100644
--- a/source/blender/draw/CMakeLists.txt
+++ b/source/blender/draw/CMakeLists.txt
@@ -132,6 +132,9 @@ if(WITH_CLAY_ENGINE)
 endif()
 
 data_to_c_simple(engines/clay/shaders/clay_frag.glsl SRC)
+data_to_c_simple(engines/clay/shaders/clay_fxaa.glsl SRC)
+data_to_c_simple(engines/clay/shaders/clay_copy.glsl SRC)
+data_to_c_simple(engines/clay/shaders/clay_prepass_frag.glsl SRC)
 data_to_c_simple(engines/clay/shaders/clay_vert.glsl SRC)
 data_to_c_simple(engines/clay/shaders/clay_particle_vert.glsl SRC)
 data_to_c_simple(engines/clay/shaders/clay_particle_strand_frag.glsl SRC)
diff --git a/source/blender/draw/engines/clay/clay_engine.c b/source/blender/draw/engines/clay/clay_engine.c
index 655031b83b819b9a22fcc23869d363f9662ce5bd..382551b16e40df596b2c08baa20b806a272220b2 100644
--- a/source/blender/draw/engines/clay/clay_engine.c
+++ b/source/blender/draw/engines/clay/clay_engine.c
@@ -20,7 +20,7 @@
  */
 
 #include "BLI_utildefines.h"
-#include "BLI_dynstr.h"
+#include "BLI_string_utils.h"
 #include "BLI_rand.h"
 
 #include "DNA_particle_types.h"
@@ -51,17 +51,24 @@
 
 #define MAX_CLAY_MAT 512 /* 512 = 9 bit material id */
 
-#define SHADER_DEFINES \
+#define SHADER_DEFINES_NO_AO \
 	"#define MAX_MATERIAL " STRINGIFY(MAX_CLAY_MAT) "\n" \
 	"#define USE_ROTATION\n" \
-	"#define USE_AO\n" \
 	"#define USE_HSV\n"
 
+#define SHADER_DEFINES \
+	SHADER_DEFINES_NO_AO \
+	"#define USE_AO\n"
+
 extern char datatoc_clay_frag_glsl[];
+extern char datatoc_clay_prepass_frag_glsl[];
+extern char datatoc_clay_copy_glsl[];
 extern char datatoc_clay_vert_glsl[];
+extern char datatoc_clay_fxaa_glsl[];
 extern char datatoc_clay_particle_vert_glsl[];
 extern char datatoc_clay_particle_strand_frag_glsl[];
 extern char datatoc_ssao_alchemy_glsl[];
+extern char datatoc_common_fxaa_lib_glsl[];
 
 /* *********** LISTS *********** */
 
@@ -111,6 +118,8 @@ typedef struct CLAY_Storage {
 	int hair_ubo_current_id;
 	DRWShadingGroup *shgrps[MAX_CLAY_MAT];
 	DRWShadingGroup *shgrps_flat[MAX_CLAY_MAT];
+	DRWShadingGroup *shgrps_pre[MAX_CLAY_MAT];
+	DRWShadingGroup *shgrps_pre_flat[MAX_CLAY_MAT];
 	DRWShadingGroup *hair_shgrps[MAX_CLAY_MAT];
 } CLAY_Storage;
 
@@ -120,24 +129,33 @@ typedef struct CLAY_StorageList {
 } CLAY_StorageList;
 
 typedef struct CLAY_FramebufferList {
-	/* default */
-	struct GPUFrameBuffer *default_fb;
-	/* engine specific */
-	struct GPUFrameBuffer *dupli_depth;
+	struct GPUFrameBuffer *antialias_fb;
+	struct GPUFrameBuffer *prepass_fb;
 } CLAY_FramebufferList;
 
 typedef struct CLAY_PassList {
-	struct DRWPass *depth_pass;
-	struct DRWPass *depth_pass_cull;
-	struct DRWPass *clay_pass;
-	struct DRWPass *clay_pass_flat;
+	struct DRWPass *clay_ps;
+	struct DRWPass *clay_cull_ps;
+	struct DRWPass *clay_flat_ps;
+	struct DRWPass *clay_flat_cull_ps;
+	struct DRWPass *clay_pre_ps;
+	struct DRWPass *clay_pre_cull_ps;
+	struct DRWPass *clay_flat_pre_ps;
+	struct DRWPass *clay_flat_pre_cull_ps;
+	struct DRWPass *clay_deferred_ps;
+	struct DRWPass *fxaa_ps;
+	struct DRWPass *copy_ps;
 	struct DRWPass *hair_pass;
 } CLAY_PassList;
 
+typedef struct CLAY_TextureList {
+	struct GPUTexture *color_copy; /* only used if fxaa */
+} CLAY_TextureList;
+
 typedef struct CLAY_Data {
 	void *engine_type;
 	CLAY_FramebufferList *fbl;
-	DRWViewportEmptyList *txl;
+	CLAY_TextureList *txl;
 	CLAY_PassList *psl;
 	CLAY_StorageList *stl;
 } CLAY_Data;
@@ -154,27 +172,22 @@ typedef struct CLAY_ViewLayerData {
 /* *********** STATIC *********** */
 
 static struct {
-	/* Depth Pre Pass */
-	struct GPUShader *depth_sh;
 	/* Shading Pass */
 	struct GPUShader *clay_sh;
 	struct GPUShader *clay_flat_sh;
+	struct GPUShader *clay_prepass_flat_sh;
+	struct GPUShader *clay_prepass_sh;
+	struct GPUShader *clay_deferred_shading_sh;
+	struct GPUShader *fxaa_sh;
+	struct GPUShader *copy_sh;
 	struct GPUShader *hair_sh;
-
 	/* Matcap textures */
 	struct GPUTexture *matcap_array;
 	float matcap_colors[24][4];
-
-	/* Ssao */
-	float winmat[4][4];
-	float viewvecs[3][4];
-	float ssao_params[4];
-
 	/* Just a serie of int from 0 to MAX_CLAY_MAT-1 */
 	int ubo_mat_idxs[MAX_CLAY_MAT];
-
-	/* engine specific */
-	struct GPUTexture *depth_dup;
+	/* To avoid useless texture and ubo binds. */
+	bool first_shgrp;
 } e_data = {NULL}; /* Engine data */
 
 typedef struct CLAY_PrivateData {
@@ -184,7 +197,15 @@ typedef struct CLAY_PrivateData {
 	DRWShadingGroup *depth_shgrp_cull;
 	DRWShadingGroup *depth_shgrp_cull_select;
 	DRWShadingGroup *depth_shgrp_cull_active;
-	bool enable_ao;
+	/* Deferred shading */
+	struct GPUTexture *depth_tx; /* ref only, not alloced */
+	struct GPUTexture *normal_tx; /* ref only, not alloced */
+	struct GPUTexture *id_tx; /* ref only, not alloced */
+	bool enable_deferred_path;
+	/* Ssao */
+	float winmat[4][4];
+	float viewvecs[3][4];
+	float ssao_params[4];
 } CLAY_PrivateData; /* Transient data */
 
 /* Functions */
@@ -330,6 +351,7 @@ static struct GPUTexture *create_jitter_texture(int num_samples)
 static void clay_engine_init(void *vedata)
 {
 	CLAY_StorageList *stl = ((CLAY_Data *)vedata)->stl;
+	CLAY_TextureList *txl = ((CLAY_Data *)vedata)->txl;
 	CLAY_FramebufferList *fbl = ((CLAY_Data *)vedata)->fbl;
 	CLAY_ViewLayerData *sldata = CLAY_view_layer_data_get();
 
@@ -366,31 +388,44 @@ static void clay_engine_init(void *vedata)
 		e_data.matcap_array = load_matcaps(prv, 24);
 	}
 
-	/* Depth prepass */
-	if (!e_data.depth_sh) {
-		e_data.depth_sh = DRW_shader_create_3D_depth_only();
-	}
-
 	/* Shading pass */
 	if (!e_data.clay_sh) {
-		DynStr *ds = BLI_dynstr_new();
-		char *matcap_with_ao;
-
-		BLI_dynstr_append(ds, datatoc_clay_frag_glsl);
-		BLI_dynstr_append(ds, datatoc_ssao_alchemy_glsl);
-
-		matcap_with_ao = BLI_dynstr_get_cstring(ds);
+		char *matcap_with_ao = BLI_string_joinN(
+		        datatoc_clay_frag_glsl,
+		        datatoc_ssao_alchemy_glsl);
 
 		e_data.clay_sh = DRW_shader_create(
-		        datatoc_clay_vert_glsl, NULL, matcap_with_ao,
-		        SHADER_DEFINES);
+		        datatoc_clay_vert_glsl, NULL, datatoc_clay_frag_glsl,
+		        SHADER_DEFINES_NO_AO);
 		e_data.clay_flat_sh = DRW_shader_create(
-		        datatoc_clay_vert_glsl, NULL, matcap_with_ao,
+		        datatoc_clay_vert_glsl, NULL, datatoc_clay_frag_glsl,
+		        SHADER_DEFINES_NO_AO
+		        "#define USE_FLAT_NORMAL\n");
+
+		e_data.clay_prepass_sh = DRW_shader_create(
+		        datatoc_clay_vert_glsl, NULL, datatoc_clay_prepass_frag_glsl,
+		        SHADER_DEFINES);
+		e_data.clay_prepass_flat_sh = DRW_shader_create(
+		        datatoc_clay_vert_glsl, NULL, datatoc_clay_prepass_frag_glsl,
 		        SHADER_DEFINES
 		        "#define USE_FLAT_NORMAL\n");
 
-		BLI_dynstr_free(ds);
+		e_data.clay_deferred_shading_sh = DRW_shader_create_fullscreen(
+		        matcap_with_ao,
+		        SHADER_DEFINES
+		        "#define DEFERRED_SHADING\n");
+
 		MEM_freeN(matcap_with_ao);
+
+		char *fxaa_str = BLI_string_joinN(
+		        datatoc_common_fxaa_lib_glsl,
+		        datatoc_clay_fxaa_glsl);
+
+		e_data.fxaa_sh = DRW_shader_create_fullscreen(fxaa_str, NULL);
+
+		MEM_freeN(fxaa_str);
+
+		e_data.copy_sh = DRW_shader_create_fullscreen(datatoc_clay_copy_glsl, NULL);
 	}
 
 	if (!e_data.hair_sh) {
@@ -403,6 +438,12 @@ static void clay_engine_init(void *vedata)
 		stl->storage = MEM_callocN(sizeof(CLAY_Storage), "CLAY_Storage");
 	}
 
+	if (!stl->g_data) {
+		stl->g_data = MEM_mallocN(sizeof(*stl->g_data), "CLAY_PrivateStorage");
+	}
+
+	CLAY_PrivateData *g_data = stl->g_data;
+
 	if (!sldata->mat_ubo) {
 		sldata->mat_ubo = DRW_uniformbuffer_create(sizeof(CLAY_UBO_Storage), NULL);
 	}
@@ -424,8 +465,15 @@ static void clay_engine_init(void *vedata)
 
 	if (DRW_state_is_fbo()) {
 		const float *viewport_size = DRW_viewport_size_get();
-		DRWFboTexture tex = {&e_data.depth_dup, DRW_TEX_DEPTH_24_STENCIL_8, DRW_TEX_TEMP};
-		DRW_framebuffer_init(&fbl->dupli_depth, &draw_engine_clay_type,
+		DRWFboTexture texs[2] = {{&g_data->normal_tx, DRW_TEX_RG_8, DRW_TEX_TEMP},
+		                         {&g_data->id_tx, DRW_TEX_R_16I, DRW_TEX_TEMP}};
+		DRW_framebuffer_init(&fbl->prepass_fb, &draw_engine_clay_type,
+		                     (int)viewport_size[0], (int)viewport_size[1],
+		                     texs, 2);
+
+		/* For FXAA */
+		DRWFboTexture tex = {&txl->color_copy, DRW_TEX_RGBA_8, DRW_TEX_FILTER};
+		DRW_framebuffer_init(&fbl->antialias_fb, &draw_engine_clay_type,
 		                     (int)viewport_size[0], (int)viewport_size[1],
 		                     &tex, 1);
 	}
@@ -453,14 +501,14 @@ static void clay_engine_init(void *vedata)
 
 		DRW_state_dfdy_factors_get(dfdyfacs);
 
-		e_data.ssao_params[0] = ssao_samples;
-		e_data.ssao_params[1] = size[0] / 64.0;
-		e_data.ssao_params[2] = size[1] / 64.0;
-		e_data.ssao_params[3] = dfdyfacs[1]; /* dfdy sign for offscreen */
+		g_data->ssao_params[0] = ssao_samples;
+		g_data->ssao_params[1] = size[0] / 64.0;
+		g_data->ssao_params[2] = size[1] / 64.0;
+		g_data->ssao_params[3] = dfdyfacs[1]; /* dfdy sign for offscreen */
 
 		/* invert the view matrix */
-		DRW_viewport_matrix_get(e_data.winmat, DRW_MAT_WIN);
-		invert_m4_m4(invproj, e_data.winmat);
+		DRW_viewport_matrix_get(g_data->winmat, DRW_MAT_WIN);
+		invert_m4_m4(invproj, g_data->winmat);
 
 		/* convert the view vectors to view space */
 		for (i = 0; i < 3; i++) {
@@ -472,19 +520,19 @@ static void clay_engine_init(void *vedata)
 				mul_v3_fl(viewvecs[i], 1.0f / viewvecs[i][2]);
 			viewvecs[i][3] = 1.0;
 
-			copy_v4_v4(e_data.viewvecs[i], viewvecs[i]);
+			copy_v4_v4(g_data->viewvecs[i], viewvecs[i]);
 		}
 
 		/* we need to store the differences */
-		e_data.viewvecs[1][0] -= e_data.viewvecs[0][0];
-		e_data.viewvecs[1][1] = e_data.viewvecs[2][1] - e_data.viewvecs[0][1];
+		g_data->viewvecs[1][0] -= g_data->viewvecs[0][0];
+		g_data->viewvecs[1][1] = g_data->viewvecs[2][1] - g_data->viewvecs[0][1];
 
 		/* calculate a depth offset as well */
 		if (!is_persp) {
 			float vec_far[] = {-1.0f, -1.0f, 1.0f, 1.0f};
 			mul_m4_v4(invproj, vec_far);
 			mul_v3_fl(vec_far, 1.0f / vec_far[3]);
-			e_data.viewvecs[1][2] = vec_far[2] - e_data.viewvecs[0][2];
+			g_data->viewvecs[1][2] = vec_far[2] - g_data->viewvecs[0][2];
 		}
 
 		/* AO Samples Tex */
@@ -503,37 +551,56 @@ static void clay_engine_init(void *vedata)
 	}
 }
 
-static DRWShadingGroup *CLAY_shgroup_create(CLAY_Data *UNUSED(vedata), DRWPass *pass, int *material_id, bool use_flat)
+static DRWShadingGroup *CLAY_shgroup_create(DRWPass *pass, GPUShader *sh, int id)
 {
 	CLAY_ViewLayerData *sldata = CLAY_view_layer_data_get();
-	DRWShadingGroup *grp = DRW_shgroup_create(use_flat ? e_data.clay_flat_sh : e_data.clay_sh, pass);
+	DRWShadingGroup *grp = DRW_shgroup_create(sh, pass);
+	DRW_shgroup_uniform_int(grp, "mat_id", &e_data.ubo_mat_idxs[id], 1);
+	if (e_data.first_shgrp) {
+		DRW_shgroup_uniform_texture_persistent(grp, "matcaps", e_data.matcap_array);
+		DRW_shgroup_uniform_block_persistent(grp, "material_block", sldata->mat_ubo);
+		DRW_shgroup_uniform_block_persistent(grp, "matcaps_block", sldata->matcaps_ubo);
+	}
+	return grp;
+}
 
-	DRW_shgroup_uniform_vec2(grp, "screenres", DRW_viewport_size_get(), 1);
-	DRW_shgroup_uniform_buffer(grp, "depthtex", &e_data.depth_dup);
-	DRW_shgroup_uniform_texture(grp, "matcaps", e_data.matcap_array);
-	DRW_shgroup_uniform_mat4(grp, "WinMatrix", (float *)e_data.winmat);
-	DRW_shgroup_uniform_vec4(grp, "viewvecs[0]", (float *)e_data.viewvecs, 3);
-	DRW_shgroup_uniform_vec4(grp, "ssao_params", e_data.ssao_params, 1);
+static DRWShadingGroup *CLAY_shgroup_deferred_prepass_create(DRWPass *pass, GPUShader *sh, int id)
+{
+	DRWShadingGroup *grp = DRW_shgroup_create(sh, pass);
+	DRW_shgroup_uniform_int(grp, "mat_id", &e_data.ubo_mat_idxs[id], 1);
 
-	DRW_shgroup_uniform_int(grp, "mat_id", material_id, 1);
+	return grp;
+}
 
+static DRWShadingGroup *CLAY_shgroup_deferred_shading_create(DRWPass *pass, CLAY_PrivateData *g_data)
+{
+	CLAY_ViewLayerData *sldata = CLAY_view_layer_data_get();
+	DRWShadingGroup *grp = DRW_shgroup_create(e_data.clay_deferred_shading_sh, pass);
+	DRW_shgroup_uniform_buffer(grp, "depthtex", &g_data->depth_tx);
+	DRW_shgroup_uniform_buffer(grp, "normaltex", &g_data->normal_tx);
+	DRW_shgroup_uniform_buffer(grp, "idtex", &g_data->id_tx);
+	DRW_shgroup_uniform_texture(grp, "matcaps", e_data.matcap_array);
 	DRW_shgroup_uniform_texture(grp, "ssao_jitter", sldata->jitter_tx);
 	DRW_shgroup_uniform_block(grp, "samples_block", sldata->sampling_ubo);
 	DRW_shgroup_uniform_block(grp, "material_block", sldata->mat_ubo);
 	DRW_shgroup_uniform_block(grp, "matcaps_block", sldata->matcaps_ubo);
-
+	/* TODO put in ubo */
+	DRW_shgroup_uniform_mat4(grp, "WinMatrix", (float *)g_data->winmat);
+	DRW_shgroup_uniform_vec2(grp, "invscreenres", DRW_viewport_invert_size_get(), 1);
+	DRW_shgroup_uniform_vec4(grp, "viewvecs[0]", (float *)g_data->viewvecs, 3);
+	DRW_shgroup_uniform_vec4(grp, "ssao_params", g_data->ssao_params, 1);
 	return grp;
 }
 
-static DRWShadingGroup *CLAY_hair_shgroup_create(CLAY_Data *UNUSED(vedata), DRWPass *pass, int *material_id)
+static DRWShadingGroup *CLAY_hair_shgroup_create(DRWPass *pass, int id)
 {
 	CLAY_ViewLayerData *sldata = CLAY_view_layer_data_get();
-	DRWShadingGroup *grp = DRW_shgroup_create(e_data.hair_sh, pass);
 
+	DRWShadingGroup *grp = DRW_shgroup_create(e_data.hair_sh, pass);
 	DRW_shgroup_uniform_texture(grp, "matcaps", e_data.matcap_array);
-	DRW_shgroup_uniform_int(grp, "mat_id", material_id, 1);
 	DRW_shgroup_uniform_block(grp, "material_block", sldata->mat_ubo);
 	DRW_shgroup_uniform_block(grp, "matcaps_block", sldata->matcaps_ubo);
+	DRW_shgroup_uniform_int(grp, "mat_id", &e_data.ubo_mat_idxs[id], 1);
 
 	return grp;
 }
@@ -568,25 +635,17 @@ static int search_hair_mat_to_ubo(CLAY_Storage *storage, const CLAY_HAIR_UBO_Mat
 
 static int push_mat_to_ubo(CLAY_Storage *storage, const CLAY_UBO_Material *mat_ubo_test)
 {
-	int id = storage->ubo_current_id;
-	CLAY_UBO_Material *ubo = &storage->mat_storage.materials[id];
-
-	*ubo = *mat_ubo_test;
-
-	storage->ubo_current_id++;
-
+	int id = storage->ubo_current_id++;
+	id = min_ii(MAX_CLAY_MAT, id);
+	storage->mat_storage.materials[id] = *mat_ubo_test;
 	return id;
 }
 
 static int push_hair_mat_to_ubo(CLAY_Storage *storage, const CLAY_HAIR_UBO_Material *hair_mat_ubo_test)
 {
-	int id = storage->hair_ubo_current_id;
-	CLAY_HAIR_UBO_Material *ubo = &storage->hair_mat_storage.materials[id];
-
-	*ubo = *hair_mat_ubo_test;
-
-	storage->hair_ubo_current_id++;
-
+	int id = storage->hair_ubo_current_id++;
+	id = min_ii(MAX_CLAY_MAT, id);
+	storage->hair_mat_storage.materials[id] = *hair_mat_ubo_test;
 	return id;
 }
 
@@ -616,11 +675,11 @@ static int hair_mat_in_ubo(CLAY_Storage *storage, const CLAY_HAIR_UBO_Material *
 	return id;
 }
 
-static void ubo_mat_from_object(Object *ob, CLAY_UBO_Material *r_ubo, bool *r_needs_ao)
+static void ubo_mat_from_object(CLAY_Storage *storage, Object *ob, bool *r_needs_ao, int *r_id)
 {
 	IDProperty *props = BKE_layer_collection_engine_evaluated_get(ob, COLLECTION_MODE_NONE, RE_engine_id_BLENDER_CLAY);
 
-	/* Default Settings */
+	int matcap_icon = BKE_collection_engine_property_value_get_int(props, "matcap_icon");
 	float matcap_rot = BKE_collection_engine_property_value_get_float(props, "matcap_rotation");
 	float matcap_hue = BKE_collection_engine_property_value_get_float(props, "matcap_hue");
 	float matcap_sat = BKE_collection_engine_property_value_get_float(props, "matcap_saturation");
@@ -629,41 +688,45 @@ static void ubo_mat_from_object(Object *ob, CLAY_UBO_Material *r_ubo, bool *r_ne
 	float ssao_factor_cavity = BKE_collection_engine_property_value_get_float(props, "ssao_factor_cavity");
 	float ssao_factor_edge = BKE_collection_engine_property_value_get_float(props, "ssao_factor_edge");
 	float ssao_attenuation = BKE_collection_engine_property_value_get_float(props, "ssao_attenuation");
-	int matcap_icon = BKE_collection_engine_property_value_get_int(props, "matcap_icon");
+
+	CLAY_UBO_Material r_ubo = {{0.0f}};
 
 	if (((ssao_factor_cavity > 0.0) || (ssao_factor_edge > 0.0)) &&
 	    (ssao_distance > 0.0))
 	{
 		*r_needs_ao = true;
+
+		r_ubo.ssao_params_var[0] = ssao_distance;
+		r_ubo.ssao_params_var[1] = ssao_factor_cavity;
+		r_ubo.ssao_params_var[2] = ssao_factor_edge;
+		r_ubo.ssao_params_var[3] = ssao_attenuation;
+	}
+	else {
+		*r_needs_ao = false;
 	}
 
-	memset(r_ubo, 0x0, sizeof(*r_ubo));
+	r_ubo.matcap_rot[0] = cosf(matcap_rot * 3.14159f * 2.0f);
+	r_ubo.matcap_rot[1] = sinf(matcap_rot * 3.14159f * 2.0f);
 
-	r_ubo->matcap_rot[0] = cosf(matcap_rot * 3.14159f * 2.0f);
-	r_ubo->matcap_rot[1] = sinf(matcap_rot * 3.14159f * 2.0f);
+	r_ubo.matcap_hsv[0] = matcap_hue + 0.5f;
+	r_ubo.matcap_hsv[1] = matcap_sat * 2.0f;
+	r_ubo.matcap_hsv[2] = matcap_val * 2.0f;
 
-	r_ubo->matcap_hsv[0] = matcap_hue + 0.5f;
-	r_ubo->matcap_hsv[1] = matcap_sat * 2.0f;
-	r_ubo->matcap_hsv[2] = matcap_val * 2.0f;
+	r_ubo.matcap_id = matcap_to_index(matcap_icon);
 
-	r_ubo->ssao_params_var[0] = ssao_distance;
-	r_ubo->ssao_params_var[1] = ssao_factor_cavity;
-	r_ubo->ssao_params_var[2] = ssao_factor_edge;
-	r_ubo->ssao_params_var[3] = ssao_attenuation;
-	r_ubo->matcap_id = matcap_to_index(matcap_icon);
+	*r_id = mat_in_ubo(storage, &r_ubo);
 }
 
-static void hair_ubo_mat_from_object(Object *ob,  CLAY_HAIR_UBO_Material *r_ubo)
+static void hair_ubo_mat_from_object(Object *ob, CLAY_HAIR_UBO_Material *r_ubo)
 {
 	IDProperty *props = BKE_layer_collection_engine_evaluated_get(ob, COLLECTION_MODE_NONE, RE_engine_id_BLENDER_CLAY);
 
-	/* Default Settings */
+	int matcap_icon = BKE_collection_engine_property_value_get_int(props, "matcap_icon");
 	float matcap_rot = BKE_collection_engine_property_value_get_float(props, "matcap_rotation");
 	float matcap_hue = BKE_collection_engine_property_value_get_float(props, "matcap_hue");
 	float matcap_sat = BKE_collection_engine_property_value_get_float(props, "matcap_saturation");
 	float matcap_val = BKE_collection_engine_property_value_get_float(props, "matcap_value");
 	float hair_randomness = BKE_collection_engine_property_value_get_float(props, "hair_brightness_randomness");
-	int matcap_icon = BKE_collection_engine_property_value_get_int(props, "matcap_icon");
 
 	memset(r_ubo, 0x0, sizeof(*r_ubo));
 
@@ -676,25 +739,56 @@ static void hair_ubo_mat_from_object(Object *ob,  CLAY_HAIR_UBO_Material *r_ubo)
 	r_ubo->matcap_id = matcap_to_index(matcap_icon);
 }
 
-static DRWShadingGroup *CLAY_object_shgrp_get(
-        CLAY_Data *vedata, Object *ob, CLAY_StorageList *stl, CLAY_PassList *psl, bool use_flat)
+static DRWShadingGroup *CLAY_object_shgrp_get(CLAY_Data *vedata, Object *ob, bool use_flat, bool cull)
 {
-	DRWShadingGroup **shgrps = use_flat ? stl->storage->shgrps_flat : stl->storage->shgrps;
-	CLAY_UBO_Material mat_ubo_test;
+	bool prepass; int id;
+	CLAY_PassList *psl = vedata->psl;
+	CLAY_Storage *storage = vedata->stl->storage;
+	DRWShadingGroup **shgrps;
+	DRWPass *pass; GPUShader *sh;
+
+	ubo_mat_from_object(storage, ob, &prepass, &id);
+
+	if (prepass) {
+		if (use_flat) {
+			shgrps = storage->shgrps_pre_flat;
+			pass = (cull) ? psl->clay_flat_pre_cull_ps : psl->clay_flat_pre_ps;
+			sh = e_data.clay_prepass_flat_sh;
+		}
+		else {
+			shgrps = storage->shgrps_pre;
+			pass = (cull) ? psl->clay_pre_cull_ps : psl->clay_pre_ps;
+			sh = e_data.clay_prepass_sh;
+		}
 
-	ubo_mat_from_object(ob, &mat_ubo_test, &stl->g_data->enable_ao);
+		if (shgrps[id] == NULL) {
+			shgrps[id] = CLAY_shgroup_deferred_prepass_create(pass, sh, id);
+		}
 
-	int id = mat_in_ubo(stl->storage, &mat_ubo_test);
+		vedata->stl->g_data->enable_deferred_path = true;
+	}
+	else {
+		if (use_flat) {
+			shgrps = storage->shgrps_flat;
+			pass = (cull) ? psl->clay_flat_cull_ps : psl->clay_flat_ps;
+			sh = e_data.clay_flat_sh;
+		}
+		else {
+			shgrps = storage->shgrps;
+			pass = (cull) ? psl->clay_cull_ps : psl->clay_ps;
+			sh = e_data.clay_sh;
+		}
 
-	if (shgrps[id] == NULL) {
-		shgrps[id] = CLAY_shgroup_create(
-		        vedata, use_flat ? psl->clay_pass_flat : psl->clay_pass, &e_data.ubo_mat_idxs[id], use_flat);
+		if (shgrps[id] == NULL) {
+			shgrps[id] = CLAY_shgroup_create(pass, sh, id);
+			e_data.first_shgrp = false;
+		}
 	}
 
 	return shgrps[id];
 }
 
-static DRWShadingGroup *CLAY_hair_shgrp_get(CLAY_Data *vedata, Object *ob, CLAY_StorageList *stl, CLAY_PassList *psl)
+static DRWShadingGroup *CLAY_hair_shgrp_get(CLAY_Data *UNUSED(vedata), Object *ob, CLAY_StorageList *stl, CLAY_PassList *psl)
 {
 	DRWShadingGroup **hair_shgrps = stl->storage->hair_shgrps;
 
@@ -704,54 +798,44 @@ static DRWShadingGroup *CLAY_hair_shgrp_get(CLAY_Data *vedata, Object *ob, CLAY_
 	int hair_id = hair_mat_in_ubo(stl->storage, &hair_mat_ubo_test);
 
 	if (hair_shgrps[hair_id] == NULL) {
-		hair_shgrps[hair_id] = CLAY_hair_shgroup_create(vedata, psl->hair_pass, &e_data.ubo_mat_idxs[hair_id]);
+		hair_shgrps[hair_id] = CLAY_hair_shgroup_create(psl->hair_pass, hair_id);
 	}
 
 	return hair_shgrps[hair_id];
 }
 
-static DRWShadingGroup *CLAY_object_shgrp_default_mode_get(
-        CLAY_Data *vedata, Object *ob, CLAY_StorageList *stl, CLAY_PassList *psl)
-{
-	bool use_flat = DRW_object_is_flat_normal(ob);
-	return CLAY_object_shgrp_get(vedata, ob, stl, psl, use_flat);
-}
-
 static void clay_cache_init(void *vedata)
 {
+	DefaultTextureList *dtxl = DRW_viewport_texture_list_get();
 	CLAY_PassList *psl = ((CLAY_Data *)vedata)->psl;
 	CLAY_StorageList *stl = ((CLAY_Data *)vedata)->stl;
-
-	if (!stl->g_data) {
-		/* Alloc transient pointers */
-		stl->g_data = MEM_mallocN(sizeof(*stl->g_data), __func__);
-	}
+	CLAY_TextureList *txl = ((CLAY_Data *)vedata)->txl;
 
 	/* Disable AO unless a material needs it. */
-	stl->g_data->enable_ao = false;
+	stl->g_data->enable_deferred_path = false;
 
-	/* Depth Pass */
-	{
-		psl->depth_pass = DRW_pass_create("Depth Pass", DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS);
-		stl->g_data->depth_shgrp = DRW_shgroup_create(e_data.depth_sh, psl->depth_pass);
-
-		psl->depth_pass_cull = DRW_pass_create(
-		        "Depth Pass Cull",
-		        DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_CULL_BACK);
-		stl->g_data->depth_shgrp_cull = DRW_shgroup_create(e_data.depth_sh, psl->depth_pass_cull);
-	}
+	/* Reset UBO datas, shgrp pointers and material id counters. */
+	memset(stl->storage, 0, sizeof(*stl->storage));
+	e_data.first_shgrp = true;
 
-	/* Clay Pass */
+	/* Solid Passes */
 	{
-		psl->clay_pass = DRW_pass_create("Clay Pass", DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL);
-		stl->storage->ubo_current_id = 0;
-		memset(stl->storage->shgrps, 0, sizeof(DRWShadingGroup *) * MAX_CLAY_MAT);
-	}
-
-	/* Clay Pass (Flat) */
-	{
-		psl->clay_pass_flat = DRW_pass_create("Clay Pass Flat", DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL);
-		memset(stl->storage->shgrps_flat, 0, sizeof(DRWShadingGroup *) * MAX_CLAY_MAT);
+		DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS;
+		psl->clay_ps =           DRW_pass_create("Clay", state);
+		psl->clay_cull_ps =      DRW_pass_create("Clay Culled", state | DRW_STATE_CULL_BACK);
+		psl->clay_flat_ps =      DRW_pass_create("Clay Flat", state);
+		psl->clay_flat_cull_ps = DRW_pass_create("Clay Flat Culled", state | DRW_STATE_CULL_BACK);
+
+		DRWState prepass_state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS;
+		DRWState prepass_cull_state = prepass_state | DRW_STATE_CULL_BACK;
+		psl->clay_pre_ps =           DRW_pass_create("Clay Deferred Pre", prepass_state);
+		psl->clay_pre_cull_ps =      DRW_pass_create("Clay Deferred Pre Culled", prepass_cull_state);
+		psl->clay_flat_pre_ps =      DRW_pass_create("Clay Deferred Flat Pre", prepass_state);
+		psl->clay_flat_pre_cull_ps = DRW_pass_create("Clay Deferred Flat Pre Culled", prepass_cull_state);
+
+		psl->clay_deferred_ps = DRW_pass_create("Clay Deferred Shading", DRW_STATE_WRITE_COLOR);
+		DRWShadingGroup *grp = CLAY_shgroup_deferred_shading_create(psl->clay_deferred_ps, stl->g_data);
+		DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
 	}
 
 	/* Hair Pass */
@@ -759,8 +843,19 @@ static void clay_cache_init(void *vedata)
 		psl->hair_pass = DRW_pass_create(
 		                     "Hair Pass",
 		                     DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_WIRE);
-		stl->storage->hair_ubo_current_id = 0;
-		memset(stl->storage->hair_shgrps, 0, sizeof(DRWShadingGroup *) * MAX_CLAY_MAT);
+	}
+
+	{
+		psl->fxaa_ps = DRW_pass_create("Fxaa", DRW_STATE_WRITE_COLOR);
+		DRWShadingGroup *grp = DRW_shgroup_create(e_data.fxaa_sh, psl->fxaa_ps);
+		DRW_shgroup_uniform_buffer(grp, "colortex", &dtxl->color);
+		DRW_shgroup_uniform_vec2(grp, "invscreenres", DRW_viewport_invert_size_get(), 1);
+		DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
+
+		psl->copy_ps = DRW_pass_create("Copy", DRW_STATE_WRITE_COLOR);
+		grp = DRW_shgroup_create(e_data.copy_sh, psl->copy_ps);
+		DRW_shgroup_uniform_buffer(grp, "colortex", &txl->color_copy);
+		DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
 	}
 }
 
@@ -795,9 +890,6 @@ static void clay_cache_populate_particles(void *vedata, Object *ob)
 
 static void clay_cache_populate(void *vedata, Object *ob)
 {
-	CLAY_PassList *psl = ((CLAY_Data *)vedata)->psl;
-	CLAY_StorageList *stl = ((CLAY_Data *)vedata)->stl;
-
 	DRWShadingGroup *clay_shgrp;
 
 	if (!DRW_object_is_renderable(ob))
@@ -825,26 +917,9 @@ static void clay_cache_populate(void *vedata, Object *ob)
 		IDProperty *ces_mode_ob = BKE_layer_collection_engine_evaluated_get(ob, COLLECTION_MODE_OBJECT, "");
 		const bool do_cull = BKE_collection_engine_property_value_get_bool(ces_mode_ob, "show_backface_culling");
 		const bool is_sculpt_mode = is_active && (draw_ctx->object_mode & OB_MODE_SCULPT) != 0;
-		const bool is_default_mode_shader = is_sculpt_mode;
+		const bool use_flat = is_sculpt_mode && DRW_object_is_flat_normal(ob);
 
-		/* Depth Prepass */
-		{
-			DRWShadingGroup *depth_shgrp = do_cull ? stl->g_data->depth_shgrp_cull : stl->g_data->depth_shgrp;
-			if (is_sculpt_mode) {
-				DRW_shgroup_call_sculpt_add(depth_shgrp, ob, ob->obmat);
-			}
-			else {
-				DRW_shgroup_call_object_add(depth_shgrp, geom, ob);
-			}
-		}
-
-		/* Shading */
-		if (is_default_mode_shader) {
-			clay_shgrp = CLAY_object_shgrp_default_mode_get(vedata, ob, stl, psl);
-		}
-		else {
-			clay_shgrp = CLAY_object_shgrp_get(vedata, ob, stl, psl, false);
-		}
+		clay_shgrp = CLAY_object_shgrp_get(vedata, ob, use_flat, do_cull);
 
 		if (is_sculpt_mode) {
 			DRW_shgroup_call_sculpt_add(clay_shgrp, ob, ob->obmat);
@@ -870,37 +945,50 @@ static void clay_draw_scene(void *vedata)
 	CLAY_PassList *psl = ((CLAY_Data *)vedata)->psl;
 	CLAY_FramebufferList *fbl = ((CLAY_Data *)vedata)->fbl;
 	DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
+	DefaultTextureList *dtxl = DRW_viewport_texture_list_get();
+	stl->g_data->depth_tx = dtxl->depth;
+
+	/* Passes are ordered to have less _potential_ overdraw */
+	DRW_draw_pass(psl->clay_cull_ps);
+	DRW_draw_pass(psl->clay_flat_cull_ps);
+	DRW_draw_pass(psl->clay_ps);
+	DRW_draw_pass(psl->clay_flat_ps);
+	DRW_draw_pass(psl->hair_pass);
 
-	/* Pass 1 : Depth pre-pass */
-	if (stl->g_data->enable_ao) {
-		DRW_draw_pass(psl->depth_pass);
-		DRW_draw_pass(psl->depth_pass_cull);
-	}
-	else {
-		DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS;
-		DRW_pass_state_set(psl->clay_pass, state);
-		DRW_pass_state_set(psl->clay_pass_flat, state);
-	}
+	if (stl->g_data->enable_deferred_path) {
+		if (DRW_state_is_fbo()) {
+			DRW_framebuffer_texture_detach(dtxl->depth);
+			DRW_framebuffer_texture_attach(fbl->prepass_fb, dtxl->depth, 0, 0);
+			DRW_framebuffer_texture_attach(fbl->prepass_fb, stl->g_data->normal_tx, 0, 0);
+			DRW_framebuffer_texture_attach(fbl->prepass_fb, stl->g_data->id_tx, 1, 0);
+			DRW_framebuffer_bind(fbl->prepass_fb);
+			/* We need to clear the id texture unfortunately. */
+			DRW_framebuffer_clear(true, false, false, (float[4]){0.0f, 0.0f, 0.0f, 0.0f}, 0.0f);
+		}
 
-	/* Pass 2 : Duplicate depth */
-	/* Unless we go for deferred shading we need this to avoid manual depth test and artifacts */
-	if (DRW_state_is_fbo() && stl->g_data->enable_ao) {
-		/* attach temp textures */
-		DRW_framebuffer_texture_attach(fbl->dupli_depth, e_data.depth_dup, 0, 0);
+		DRW_draw_pass(psl->clay_pre_cull_ps);
+		DRW_draw_pass(psl->clay_flat_pre_cull_ps);
+		DRW_draw_pass(psl->clay_pre_ps);
+		DRW_draw_pass(psl->clay_flat_pre_ps);
 
-		DRW_framebuffer_blit(dfbl->default_fb, fbl->dupli_depth, true, false);
+		if (DRW_state_is_fbo()) {
+			DRW_framebuffer_texture_detach(dtxl->depth);
+			DRW_framebuffer_bind(dfbl->default_fb);
 
-		/* detach temp textures */
-		DRW_framebuffer_texture_detach(e_data.depth_dup);
+			DRW_draw_pass(psl->clay_deferred_ps);
 
-		/* restore default fb */
-		DRW_framebuffer_bind(dfbl->default_fb);
+			DRW_framebuffer_texture_attach(dfbl->default_fb, dtxl->depth, 0, 0);
+			DRW_framebuffer_bind(dfbl->default_fb);
+		}
 	}
 
-	/* Pass 3 : Shading */
-	DRW_draw_pass(psl->clay_pass);
-	DRW_draw_pass(psl->clay_pass_flat);
-	DRW_draw_pass(psl->hair_pass);
+	if (true) { /* Always on for now. We might want a parameter for this. */
+		DRW_framebuffer_bind(fbl->antialias_fb);
+		DRW_draw_pass(psl->fxaa_ps);
+
+		DRW_framebuffer_bind(dfbl->default_fb);
+		DRW_draw_pass(psl->copy_ps);
+	}
 }
 
 static void clay_layer_collection_settings_create(RenderEngine *UNUSED(engine), IDProperty *props)
@@ -935,6 +1023,11 @@ static void clay_engine_free(void)
 {
 	DRW_SHADER_FREE_SAFE(e_data.clay_sh);
 	DRW_SHADER_FREE_SAFE(e_data.clay_flat_sh);
+	DRW_SHADER_FREE_SAFE(e_data.clay_prepass_flat_sh);
+	DRW_SHADER_FREE_SAFE(e_data.clay_prepass_sh);
+	DRW_SHADER_FREE_SAFE(e_data.clay_deferred_shading_sh);
+	DRW_SHADER_FREE_SAFE(e_data.fxaa_sh);
+	DRW_SHADER_FREE_SAFE(e_data.copy_sh);
 	DRW_SHADER_FREE_SAFE(e_data.hair_sh);
 	DRW_TEXTURE_FREE_SAFE(e_data.matcap_array);
 }
diff --git a/source/blender/draw/engines/clay/shaders/clay_copy.glsl b/source/blender/draw/engines/clay/shaders/clay_copy.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..ec462978e67c3f1e4623e1dbfcef9e4219e65e16
--- /dev/null
+++ b/source/blender/draw/engines/clay/shaders/clay_copy.glsl
@@ -0,0 +1,10 @@
+
+in vec4 uvcoordsvar;
+out vec4 fragColor;
+
+uniform sampler2D colortex;
+
+void main()
+{
+	fragColor = texture(colortex, uvcoordsvar.st);
+}
diff --git a/source/blender/draw/engines/clay/shaders/clay_frag.glsl b/source/blender/draw/engines/clay/shaders/clay_frag.glsl
index 619843e2a02d3b9f92a7be9fae51eaa260ba9984..f2c6cd5f78079dcaa0b22000d17f15d4d9ddb87d 100644
--- a/source/blender/draw/engines/clay/shaders/clay_frag.glsl
+++ b/source/blender/draw/engines/clay/shaders/clay_frag.glsl
@@ -1,5 +1,4 @@
-uniform vec2 screenres;
-uniform sampler2D depthtex;
+uniform vec2 invscreenres;
 uniform mat4 WinMatrix;
 
 /* Matcap */
@@ -33,7 +32,14 @@ layout(std140) uniform material_block {
 	Material matcaps_param[MAX_MATERIAL];
 };
 
+#ifdef DEFERRED_SHADING
+uniform sampler2D depthtex;
+uniform sampler2D normaltex;
+uniform isampler2D idtex;
+int mat_id; /* global */
+#else
 uniform int mat_id;
+#endif
 
 /* Aliases */
 #define ssao_samples_num	ssao_params.x
@@ -44,10 +50,12 @@ uniform int mat_id;
 #define matcap_index		matcaps_param[mat_id].matcap_hsv_id.w
 #define matcap_rotation		matcaps_param[mat_id].matcap_rot.xy
 
-#ifdef USE_FLAT_NORMAL
+#ifndef DEFERRED_SHADING
+#  ifdef USE_FLAT_NORMAL
 flat in vec3 normal;
-#else
+#  else
 in vec3 normal;
+#  endif
 #endif
 
 out vec4 fragColor;
@@ -169,24 +177,33 @@ void ssao_factors(
         out float cavities, out float edges);
 #endif
 
-void main() {
-	vec2 screenco = vec2(gl_FragCoord.xy) / screenres;
-	float depth = texture(depthtex, screenco).r;
-
-	vec3 position = get_view_space_from_depth(screenco, depth);
+/* From http://aras-p.info/texts/CompactNormalStorage.html
+ * Using Method #4: Spheremap Transform */
+vec3 normal_decode(vec2 enc)
+{
+	vec2 fenc = enc * 4.0 - 2.0;
+	float f = dot(fenc, fenc);
+	float g = sqrt(1.0 - f / 4.0);
+	vec3 n;
+	n.xy = fenc*g;
+	n.z = 1 - f / 2;
+	return n;
+}
 
+vec3 shade(vec3 N, vec3 position, float depth, vec2 screenco)
+{
 #ifdef USE_ROTATION
 	/* Rotate texture coordinates */
 	vec2 rotY = vec2(-matcap_rotation.y, matcap_rotation.x);
-	vec2 texco = abs(vec2(dot(normal.xy, matcap_rotation), dot(normal.xy, rotY)) * .49 + 0.5);
+	vec2 texco = abs(vec2(dot(N.xy, matcap_rotation), dot(N.xy, rotY)) * .49 + 0.5);
 #else
-	vec2 texco = abs(normal.xy * .49 + 0.5);
+	vec2 texco = abs(N.xy * .49 + 0.5);
 #endif
 	vec3 col = texture(matcaps, vec3(texco, matcap_index)).rgb;
 
 #ifdef USE_AO
-	float cavity, edges;
-	ssao_factors(depth, normal, position, screenco, cavity, edges);
+	float cavity = 0.0, edges = 0.0;
+	ssao_factors(depth, N, position, screenco, cavity, edges);
 
 	col *= mix(vec3(1.0), matcaps_color[int(matcap_index)].rgb, cavity);
 #endif
@@ -200,5 +217,35 @@ void main() {
 	col *= edges + 1.0;
 #endif
 
+	return col;
+}
+
+void main()
+{
+	vec2 screenco = vec2(gl_FragCoord.xy) * invscreenres;
+
+#ifdef DEFERRED_SHADING
+	mat_id = texture(idtex, screenco).r;
+
+	/* early out (manual stencil test) */
+	if (mat_id == 0)
+		discard;
+
+	float depth = texture(depthtex, screenco).r;
+	vec3 N = normal_decode(texture(normaltex, screenco).rg);
+	/* see the prepass for explanations. */
+	if (mat_id < 0) {
+		N = -N;
+	}
+	mat_id = abs(mat_id) - 1;
+#else
+	float depth = gl_FragCoord.z;
+	vec3 N = normal;
+#endif
+
+	vec3 position = get_view_space_from_depth(screenco, depth);
+
+	vec3 col = shade(N, position, depth, screenco);
+
 	fragColor = vec4(col, 1.0);
 }
diff --git a/source/blender/draw/engines/clay/shaders/clay_fxaa.glsl b/source/blender/draw/engines/clay/shaders/clay_fxaa.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..1a34927aaa65677e99dfc931c0ad4feff558ae43
--- /dev/null
+++ b/source/blender/draw/engines/clay/shaders/clay_fxaa.glsl
@@ -0,0 +1,18 @@
+
+in vec4 uvcoordsvar;
+out vec4 fragColor;
+
+uniform vec2 invscreenres;
+uniform sampler2D colortex;
+
+void main()
+{
+	fragColor = FxaaPixelShader(
+		uvcoordsvar.st,
+		colortex,
+		invscreenres,
+		1.0,
+		0.166,
+		0.0833
+	);
+}
diff --git a/source/blender/draw/engines/clay/shaders/clay_prepass_frag.glsl b/source/blender/draw/engines/clay/shaders/clay_prepass_frag.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..8eb22a7854c8e0a2af68218d40af4f58acf44f47
--- /dev/null
+++ b/source/blender/draw/engines/clay/shaders/clay_prepass_frag.glsl
@@ -0,0 +1,44 @@
+uniform int mat_id;
+
+#ifdef USE_FLAT_NORMAL
+flat in vec3 normal;
+#else
+in vec3 normal;
+#endif
+
+layout(location = 0) out vec2 outNormals;
+layout(location = 1) out int outIndex;
+
+/* From http://aras-p.info/texts/CompactNormalStorage.html
+ * Using Method #4: Spheremap Transform */
+vec2 normal_encode(vec3 n)
+{
+	float p = sqrt(n.z * 8.0 + 8.0);
+	return n.xy / p + 0.5;
+}
+
+/* 4x4 bayer matrix prepared for 8bit UNORM precision error. */
+#define P(x) (((x + 0.5) * (1.0 / 16.0) - 0.5) * (1.0 / 255.0))
+const mat4 dither_mat = mat4(
+	vec4( P(0.0),  P(8.0),  P(2.0), P(10.0)),
+	vec4(P(12.0),  P(4.0), P(14.0),  P(6.0)),
+	vec4( P(3.0), P(11.0),  P(1.0),  P(9.0)),
+	vec4(P(15.0),  P(7.0), P(13.0),  P(5.0))
+);
+
+void main() {
+	outIndex = (mat_id + 1); /* 0 is clear color */
+	/**
+	 * To fix the normal buffer precision issue for backfaces,
+	 * we invert normals and use the sign of the index buffer
+	 * to tag them, and re-invert in deferred pass.
+	 **/
+	vec3 N = (gl_FrontFacing) ? normal : -normal;
+	outIndex = (gl_FrontFacing) ? outIndex : -outIndex;
+
+	outNormals = normal_encode(N);
+
+	/* Dither the output to fight low quality. */
+	ivec2 tx = ivec2(gl_FragCoord.xy) % 4;
+	outNormals += dither_mat[tx.x][tx.y];
+}
diff --git a/source/blender/draw/engines/eevee/eevee_bloom.c b/source/blender/draw/engines/eevee/eevee_bloom.c
index 1b015a51f6a91baf2a3a6f8da6ba70f95df1a4de..c62f35a70e706a4fa43e76ece50a79688fed0e71 100644
--- a/source/blender/draw/engines/eevee/eevee_bloom.c
+++ b/source/blender/draw/engines/eevee/eevee_bloom.c
@@ -41,7 +41,7 @@ static struct {
 	struct GPUShader *bloom_downsample_sh[2];
 	struct GPUShader *bloom_upsample_sh[2];
 	struct GPUShader *bloom_resolve_sh[2];
-} e_data = {NULL}; /* Engine data */
+} e_data = {{NULL}}; /* Engine data */
 
 extern char datatoc_effect_bloom_frag_glsl[];
 
diff --git a/source/blender/draw/engines/eevee/eevee_engine.c b/source/blender/draw/engines/eevee/eevee_engine.c
index b1bfc45037d1ef631c0e50f25a2d682894d4a57c..6cfecd0a226fe06660ebae0276516b5603747250 100644
--- a/source/blender/draw/engines/eevee/eevee_engine.c
+++ b/source/blender/draw/engines/eevee/eevee_engine.c
@@ -334,10 +334,6 @@ static void eevee_draw_background(void *vedata)
 
 	EEVEE_volumes_free_smoke_textures();
 
-	if (DRW_state_is_image_render()) {
-		MULTISAMPLE_SYNC_ENABLE(dfbl);
-	}
-
 	stl->g_data->view_updated = false;
 }
 
diff --git a/source/blender/draw/engines/eevee/eevee_materials.c b/source/blender/draw/engines/eevee/eevee_materials.c
index f1124436838b9470abf60e9b3a8395295589e847..6215445e11371f7996a77eab405fb5d5596293a0 100644
--- a/source/blender/draw/engines/eevee/eevee_materials.c
+++ b/source/blender/draw/engines/eevee/eevee_materials.c
@@ -858,6 +858,8 @@ static struct DRWShadingGroup *EEVEE_default_shading_group_get(
 		vedata->psl->default_pass[options] = DRW_pass_create("Default Lit Pass", state);
 
 		DRWShadingGroup *shgrp = DRW_shgroup_create(e_data.default_lit[options], vedata->psl->default_pass[options]);
+		/* XXX / WATCH: This creates non persistent binds for the ubos and textures.
+		 * But it's currently OK because the following shgroups does not add any bind. */
 		add_standard_uniforms(shgrp, sldata, vedata, &ssr_id, NULL, false, false);
 	}
 
diff --git a/source/blender/draw/engines/eevee/eevee_screen_raytrace.c b/source/blender/draw/engines/eevee/eevee_screen_raytrace.c
index 4ae18b24bdc49a9078ca1a6c19b2c38f21eb5ff2..96d560688f342ac9257b7522b70f3773ccd13ce9 100644
--- a/source/blender/draw/engines/eevee/eevee_screen_raytrace.c
+++ b/source/blender/draw/engines/eevee/eevee_screen_raytrace.c
@@ -48,7 +48,7 @@ static struct {
 	/* Theses are just references, not actually allocated */
 	struct GPUTexture *depth_src;
 	struct GPUTexture *color_src;
-} e_data = {NULL}; /* Engine data */
+} e_data = {{NULL}}; /* Engine data */
 
 extern char datatoc_ambient_occlusion_lib_glsl[];
 extern char datatoc_common_view_lib_glsl[];
diff --git a/source/blender/draw/engines/eevee/eevee_subsurface.c b/source/blender/draw/engines/eevee/eevee_subsurface.c
index be62a4076154400887b3a3dd393c2d0a9cf47236..12a70cc2fe746e58710df557c410996efcf1a562 100644
--- a/source/blender/draw/engines/eevee/eevee_subsurface.c
+++ b/source/blender/draw/engines/eevee/eevee_subsurface.c
@@ -34,7 +34,7 @@
 
 static struct {
 	struct GPUShader *sss_sh[4];
-} e_data = {NULL}; /* Engine data */
+} e_data = {{NULL}}; /* Engine data */
 
 extern char datatoc_common_view_lib_glsl[];
 extern char datatoc_common_uniforms_lib_glsl[];
diff --git a/source/blender/draw/engines/eevee/eevee_volumes.c b/source/blender/draw/engines/eevee/eevee_volumes.c
index 26ac449807377a540d4c30d94b2b8c5c6a19b466..0c0ffa391463a5663e617682209f2b1cb3f6c75a 100644
--- a/source/blender/draw/engines/eevee/eevee_volumes.c
+++ b/source/blender/draw/engines/eevee/eevee_volumes.c
@@ -472,17 +472,20 @@ void EEVEE_volumes_cache_object_add(EEVEE_ViewLayerData *sldata, EEVEE_Data *ved
 
 	DRWShadingGroup *grp = DRW_shgroup_material_empty_tri_batch_create(mat, vedata->psl->volumetric_objects_ps, sldata->common_data.vol_tex_size[2]);
 
+	/* If shader failed to compile or is currently compiling. */
+	if (grp == NULL) {
+		return;
+	}
+
 	/* Making sure it's updated. */
 	invert_m4_m4(ob->imat, ob->obmat);
 
 	BKE_mesh_texspace_get_reference((struct Mesh *)ob->data, NULL, &texcoloc, NULL, &texcosize);
 
-	if (grp) {
-		DRW_shgroup_uniform_block(grp, "common_block", sldata->common_ubo);
-		DRW_shgroup_uniform_mat4(grp, "volumeObjectMatrix", (float *)ob->imat);
-		DRW_shgroup_uniform_vec3(grp, "volumeOrcoLoc", texcoloc, 1);
-		DRW_shgroup_uniform_vec3(grp, "volumeOrcoSize", texcosize, 1);
-	}
+	DRW_shgroup_uniform_block(grp, "common_block", sldata->common_ubo);
+	DRW_shgroup_uniform_mat4(grp, "volumeObjectMatrix", (float *)ob->imat);
+	DRW_shgroup_uniform_vec3(grp, "volumeOrcoLoc", texcoloc, 1);
+	DRW_shgroup_uniform_vec3(grp, "volumeOrcoSize", texcosize, 1);
 
 	/* Smoke Simulation */
 	if (((ob->base_flag & BASE_FROMDUPLI) == 0) &&
diff --git a/source/blender/draw/intern/DRW_render.h b/source/blender/draw/intern/DRW_render.h
index 937e88051c2d164c3341a2db05ab1e3eebe2792f..791d38ff62f721f2d53e8bf5855d1a1b800b1148 100644
--- a/source/blender/draw/intern/DRW_render.h
+++ b/source/blender/draw/intern/DRW_render.h
@@ -182,6 +182,7 @@ typedef enum {
 	DRW_TEX_RG_32,
 	DRW_TEX_R_8,
 	DRW_TEX_R_16,
+	DRW_TEX_R_16I,
 	DRW_TEX_R_32,
 	DRW_TEX_DEPTH_16,
 	DRW_TEX_DEPTH_24,
@@ -381,7 +382,9 @@ void DRW_shgroup_state_disable(DRWShadingGroup *shgroup, DRWState state);
 void DRW_shgroup_stencil_mask(DRWShadingGroup *shgroup, unsigned int mask);
 
 void DRW_shgroup_uniform_texture(DRWShadingGroup *shgroup, const char *name, const struct GPUTexture *tex);
+void DRW_shgroup_uniform_texture_persistent(DRWShadingGroup *shgroup, const char *name, const struct GPUTexture *tex);
 void DRW_shgroup_uniform_block(DRWShadingGroup *shgroup, const char *name, const struct GPUUniformBuffer *ubo);
+void DRW_shgroup_uniform_block_persistent(DRWShadingGroup *shgroup, const char *name, const struct GPUUniformBuffer *ubo);
 void DRW_shgroup_uniform_buffer(DRWShadingGroup *shgroup, const char *name, struct GPUTexture **tex);
 void DRW_shgroup_uniform_float(DRWShadingGroup *shgroup, const char *name, const float *value, int arraysize);
 void DRW_shgroup_uniform_vec2(DRWShadingGroup *shgroup, const char *name, const float *value, int arraysize);
@@ -428,6 +431,7 @@ void DRW_viewport_matrix_override_unset(DRWViewportMatrixType type);
 void DRW_viewport_matrix_override_unset_all(void);
 
 const float *DRW_viewport_size_get(void);
+const float *DRW_viewport_invert_size_get(void);
 const float *DRW_viewport_screenvecs_get(void);
 const float *DRW_viewport_pixelsize_get(void);
 bool DRW_viewport_is_persp_get(void);
diff --git a/source/blender/draw/intern/draw_cache_impl_mesh.c b/source/blender/draw/intern/draw_cache_impl_mesh.c
index b44c5b8a20b856a512b0ed65b60d8abb13df3cd3..f7a82c6d0c53c4890dae5ff43492c62f26828db6 100644
--- a/source/blender/draw/intern/draw_cache_impl_mesh.c
+++ b/source/blender/draw/intern/draw_cache_impl_mesh.c
@@ -1665,6 +1665,46 @@ void DRW_mesh_batch_cache_dirty(Mesh *me, int mode)
 	}
 }
 
+/**
+ * This only clear the batches associated to the given vertex buffer.
+ **/
+static void mesh_batch_cache_clear_selective(Mesh *me, Gwn_VertBuf *vert)
+{
+	MeshBatchCache *cache = me->batch_cache;
+	if (!cache) {
+		return;
+	}
+
+	BLI_assert(vert != NULL);
+
+	if (cache->pos_with_normals == vert) {
+		GWN_BATCH_DISCARD_SAFE(cache->triangles_with_normals);
+		GWN_BATCH_DISCARD_SAFE(cache->triangles_with_weights);
+		GWN_BATCH_DISCARD_SAFE(cache->triangles_with_vert_colors);
+		GWN_BATCH_DISCARD_SAFE(cache->triangles_with_select_id);
+		GWN_BATCH_DISCARD_SAFE(cache->triangles_with_select_mask);
+		GWN_BATCH_DISCARD_SAFE(cache->points_with_normals);
+		if (cache->shaded_triangles) {
+			for (int i = 0; i < cache->mat_len; ++i) {
+				GWN_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) {
+				GWN_BATCH_DISCARD_SAFE(cache->texpaint_triangles[i]);
+			}
+		}
+		MEM_SAFE_FREE(cache->texpaint_triangles);
+		GWN_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->batch_cache;
@@ -3878,21 +3918,10 @@ void DRW_mesh_cache_sculpt_coords_ensure(Mesh *me)
 	if (me->batch_cache) {
 		MeshBatchCache *cache = mesh_batch_cache_get(me);
 		if (cache && cache->pos_with_normals && cache->is_sculpt_points_tag) {
-
-			const int datatype = MR_DATATYPE_VERT | MR_DATATYPE_LOOPTRI | MR_DATATYPE_LOOP | MR_DATATYPE_POLY;
-			MeshRenderData *rdata = mesh_render_data_create(me, datatype);
-
-			Gwn_VertBuf *pos_with_normals = cache->pos_with_normals;
-			cache->pos_with_normals = NULL;
-			GWN_vertbuf_clear(pos_with_normals);
-			Gwn_VertBuf *vbo = mesh_batch_cache_get_tri_pos_and_normals(rdata, cache);
-			*pos_with_normals = *vbo;
-			GWN_vertformat_copy(&pos_with_normals->format, &vbo->format);
-
-			free(vbo);
-			cache->pos_with_normals = pos_with_normals;
-
-			mesh_render_data_free(rdata);
+			/* XXX Force update of all the batches that contains the pos_with_normals buffer.
+			 * TODO(fclem): Ideally, Gawain should provide a way to update a buffer without destroying it. */
+			mesh_batch_cache_clear_selective(me, cache->pos_with_normals);
+			GWN_VERTBUF_DISCARD_SAFE(cache->pos_with_normals);
 		}
 		cache->is_sculpt_points_tag = false;
 	}
diff --git a/source/blender/draw/intern/draw_instance_data.c b/source/blender/draw/intern/draw_instance_data.c
index 86b2af5080c40825c713aed570ff8d4e60941cc7..ee73a2ba2c63ba42e1fe2fd12603f61f9be18d1e 100644
--- a/source/blender/draw/intern/draw_instance_data.c
+++ b/source/blender/draw/intern/draw_instance_data.c
@@ -156,7 +156,7 @@ void DRW_batching_buffer_request(
 	}
 	/* Create the batch. */
 	bbuf = chunk->bbufs + new_id;
-	bbuf->vert = *r_vert = GWN_vertbuf_create_dynamic_with_format(format);
+	bbuf->vert = *r_vert = GWN_vertbuf_create_with_format_ex(format, GWN_USAGE_DYNAMIC);
 	bbuf->batch = *r_batch = GWN_batch_create_ex(type, bbuf->vert, NULL, 0);
 	bbuf->format = format;
 	bbuf->shgroup = shgroup;
@@ -197,7 +197,7 @@ void DRW_instancing_buffer_request(
 	}
 	/* Create the batch. */
 	ibuf = chunk->ibufs + new_id;
-	ibuf->vert = *r_vert = GWN_vertbuf_create_dynamic_with_format(format);
+	ibuf->vert = *r_vert = GWN_vertbuf_create_with_format_ex(format, GWN_USAGE_DYNAMIC);
 	ibuf->batch = *r_batch = GWN_batch_duplicate(instance);
 	ibuf->format = format;
 	ibuf->shgroup = shgroup;
diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c
index 2f8cf04f476e748191ae2be055444f660365ea6c..dbcfa02c555713346f0f44285e19a30a0c223c99 100644
--- a/source/blender/draw/intern/draw_manager.c
+++ b/source/blender/draw/intern/draw_manager.c
@@ -297,7 +297,12 @@ void DRW_engine_viewport_data_size_get(
 
 const float *DRW_viewport_size_get(void)
 {
-	return &DST.size[0];
+	return DST.size;
+}
+
+const float *DRW_viewport_invert_size_get(void)
+{
+	return DST.inv_size;
 }
 
 const float *DRW_viewport_screenvecs_get(void)
@@ -364,6 +369,8 @@ static void drw_viewport_var_init(void)
 		GPU_viewport_size_get(DST.viewport, size);
 		DST.size[0] = size[0];
 		DST.size[1] = size[1];
+		DST.inv_size[0] = 1.0f / size[0];
+		DST.inv_size[1] = 1.0f / size[1];
 
 		DefaultFramebufferList *fbl = (DefaultFramebufferList *)GPU_viewport_framebuffer_list_get(DST.viewport);
 		DST.default_framebuffer = fbl->default_fb;
@@ -393,6 +400,9 @@ static void drw_viewport_var_init(void)
 		DST.size[0] = 0;
 		DST.size[1] = 0;
 
+		DST.inv_size[0] = 0;
+		DST.inv_size[1] = 0;
+
 		DST.default_framebuffer = NULL;
 		DST.vmempool = NULL;
 	}
@@ -436,13 +446,13 @@ static void drw_viewport_var_init(void)
 		DST.RST.bound_texs = MEM_callocN(sizeof(GPUTexture *) * GPU_max_textures(), "Bound GPUTexture refs");
 	}
 	if (DST.RST.bound_tex_slots == NULL) {
-		DST.RST.bound_tex_slots = MEM_callocN(sizeof(bool) * GPU_max_textures(), "Bound Texture Slots");
+		DST.RST.bound_tex_slots = MEM_callocN(sizeof(char) * GPU_max_textures(), "Bound Texture Slots");
 	}
 	if (DST.RST.bound_ubos == NULL) {
 		DST.RST.bound_ubos = MEM_callocN(sizeof(GPUUniformBuffer *) * GPU_max_ubo_binds(), "Bound GPUUniformBuffer refs");
 	}
 	if (DST.RST.bound_ubo_slots == NULL) {
-		DST.RST.bound_ubo_slots = MEM_callocN(sizeof(bool) * GPU_max_textures(), "Bound Ubo Slots");
+		DST.RST.bound_ubo_slots = MEM_callocN(sizeof(char) * GPU_max_textures(), "Bound Ubo Slots");
 	}
 
 	if (view_ubo == NULL) {
@@ -1133,7 +1143,11 @@ void DRW_draw_render_loop_ex(
 		drw_engines_cache_finish();
 
 		DRW_render_instance_buffer_finish();
-		PROFILE_END_ACCUM(DST.cache_time, stime);
+
+#ifdef USE_PROFILE
+		double *cache_time = GPU_viewport_cache_time_get(DST.viewport);
+		PROFILE_END_UPDATE(*cache_time, stime);
+#endif
 	}
 
 	DRW_stats_begin();
diff --git a/source/blender/draw/intern/draw_manager.h b/source/blender/draw/intern/draw_manager.h
index bde6a4ab24d64f22d8c09d6c8099c87bcfe50c72..c7e9b3dc08de3bd1f69197fee1ae21960d338538 100644
--- a/source/blender/draw/intern/draw_manager.h
+++ b/source/blender/draw/intern/draw_manager.h
@@ -52,7 +52,7 @@
 #ifdef USE_PROFILE
 #  include "PIL_time.h"
 
-#  define PROFILE_TIMER_FALLOFF 0.1
+#  define PROFILE_TIMER_FALLOFF 0.04
 
 #  define PROFILE_START(time_start) \
 	double time_start = PIL_check_seconds_timer();
@@ -159,8 +159,10 @@ typedef enum {
 	DRW_UNIFORM_INT,
 	DRW_UNIFORM_FLOAT,
 	DRW_UNIFORM_TEXTURE,
+	DRW_UNIFORM_TEXTURE_PERSIST,
 	DRW_UNIFORM_BUFFER,
-	DRW_UNIFORM_BLOCK
+	DRW_UNIFORM_BLOCK,
+	DRW_UNIFORM_BLOCK_PERSIST
 } DRWUniformType;
 
 struct DRWUniform {
@@ -278,6 +280,7 @@ typedef struct DRWManager {
 	GPUViewport *viewport;
 	struct GPUFrameBuffer *default_framebuffer;
 	float size[2];
+	float inv_size[2];
 	float screenvecs[2][3];
 	float pixsize;
 
@@ -301,9 +304,6 @@ typedef struct DRWManager {
 
 	bool buffer_finish_called; /* Avoid bad usage of DRW_render_instance_buffer_finish */
 
-	/* Profiling */
-	double cache_time;
-
 	/* View dependant uniforms. */
 	DRWMatrixState original_mat; /* Original rv3d matrices. */
 	int override_mat;            /* Bitflag of which matrices are overriden. */
@@ -334,10 +334,10 @@ typedef struct DRWManager {
 	/** GPU Resource State: Memory storage between drawing. */
 	struct {
 		GPUTexture **bound_texs;
-		bool *bound_tex_slots;
+		char *bound_tex_slots;
 		int bind_tex_inc;
 		GPUUniformBuffer **bound_ubos;
-		bool *bound_ubo_slots;
+		char *bound_ubo_slots;
 		int bind_ubo_inc;
 	} RST;
 } DRWManager;
diff --git a/source/blender/draw/intern/draw_manager_data.c b/source/blender/draw/intern/draw_manager_data.c
index 4648c321f2652c520b47b5e33016853e5854402b..b1db3b63777a0ca00a1a0ef202c5536f71816d9b 100644
--- a/source/blender/draw/intern/draw_manager_data.c
+++ b/source/blender/draw/intern/draw_manager_data.c
@@ -99,7 +99,7 @@ static void drw_interface_uniform(DRWShadingGroup *shgroup, const char *name,
                                   DRWUniformType type, const void *value, int length, int arraysize)
 {
 	int location;
-	if (type == DRW_UNIFORM_BLOCK) {
+	if (ELEM(type, DRW_UNIFORM_BLOCK, DRW_UNIFORM_BLOCK_PERSIST)) {
 		location = GPU_shader_get_uniform_block(shgroup->shader, name);
 	}
 	else {
@@ -126,12 +126,26 @@ void DRW_shgroup_uniform_texture(DRWShadingGroup *shgroup, const char *name, con
 	drw_interface_uniform(shgroup, name, DRW_UNIFORM_TEXTURE, tex, 0, 1);
 }
 
+/* Same as DRW_shgroup_uniform_texture but is garanteed to be bound if shader does not change between shgrp. */
+void DRW_shgroup_uniform_texture_persistent(DRWShadingGroup *shgroup, const char *name, const GPUTexture *tex)
+{
+	BLI_assert(tex != NULL);
+	drw_interface_uniform(shgroup, name, DRW_UNIFORM_TEXTURE_PERSIST, tex, 0, 1);
+}
+
 void DRW_shgroup_uniform_block(DRWShadingGroup *shgroup, const char *name, const GPUUniformBuffer *ubo)
 {
 	BLI_assert(ubo != NULL);
 	drw_interface_uniform(shgroup, name, DRW_UNIFORM_BLOCK, ubo, 0, 1);
 }
 
+/* Same as DRW_shgroup_uniform_block but is garanteed to be bound if shader does not change between shgrp. */
+void DRW_shgroup_uniform_block_persistent(DRWShadingGroup *shgroup, const char *name, const GPUUniformBuffer *ubo)
+{
+	BLI_assert(ubo != NULL);
+	drw_interface_uniform(shgroup, name, DRW_UNIFORM_BLOCK_PERSIST, ubo, 0, 1);
+}
+
 void DRW_shgroup_uniform_buffer(DRWShadingGroup *shgroup, const char *name, GPUTexture **tex)
 {
 	drw_interface_uniform(shgroup, name, DRW_UNIFORM_BUFFER, tex, 0, 1);
@@ -461,7 +475,7 @@ static void drw_interface_init(DRWShadingGroup *shgroup, GPUShader *shader)
 	int view_ubo_location = GPU_shader_get_uniform_block(shader, "viewBlock");
 
 	if (view_ubo_location != -1) {
-		drw_interface_uniform_create_ex(shgroup, view_ubo_location, DRW_UNIFORM_BLOCK, view_ubo, 0, 1);
+		drw_interface_uniform_create_ex(shgroup, view_ubo_location, DRW_UNIFORM_BLOCK_PERSIST, view_ubo, 0, 1);
 	}
 	else {
 		/* Only here to support builtin shaders. This should not be used by engines. */
diff --git a/source/blender/draw/intern/draw_manager_exec.c b/source/blender/draw/intern/draw_manager_exec.c
index 19b55190e6c9e06aaad67b862a3e20aca808a992..e90c1f63ecd280575b84eaa70eea1a7343300400 100644
--- a/source/blender/draw/intern/draw_manager_exec.c
+++ b/source/blender/draw/intern/draw_manager_exec.c
@@ -704,63 +704,87 @@ static void draw_geometry_execute(DRWShadingGroup *shgroup, Gwn_Batch *geom)
 	draw_geometry_execute_ex(shgroup, geom, 0, 0, false);
 }
 
-static void bind_texture(GPUTexture *tex)
+enum {
+	BIND_NONE = 0,
+	BIND_TEMP = 1,         /* Release slot after this shading group. */
+	BIND_PERSIST = 2,      /* Release slot only after the next shader change. */
+};
+
+static void bind_texture(GPUTexture *tex, char bind_type)
 {
+	int index;
+	char *slot_flags = DST.RST.bound_tex_slots;
 	int bind_num = GPU_texture_bound_number(tex);
 	if (bind_num == -1) {
 		for (int i = 0; i < GPU_max_textures(); ++i) {
-			DST.RST.bind_tex_inc = (DST.RST.bind_tex_inc + 1) % GPU_max_textures();
-			if (DST.RST.bound_tex_slots[DST.RST.bind_tex_inc] == false) {
-				if (DST.RST.bound_texs[DST.RST.bind_tex_inc] != NULL) {
-					GPU_texture_unbind(DST.RST.bound_texs[DST.RST.bind_tex_inc]);
+			index = DST.RST.bind_tex_inc = (DST.RST.bind_tex_inc + 1) % GPU_max_textures();
+			if (slot_flags[index] == BIND_NONE) {
+				if (DST.RST.bound_texs[index] != NULL) {
+					GPU_texture_unbind(DST.RST.bound_texs[index]);
 				}
-				GPU_texture_bind(tex, DST.RST.bind_tex_inc);
-				DST.RST.bound_texs[DST.RST.bind_tex_inc] = tex;
-				DST.RST.bound_tex_slots[DST.RST.bind_tex_inc] = true;
+				GPU_texture_bind(tex, index);
+				DST.RST.bound_texs[index] = tex;
+				slot_flags[index] = bind_type;
 				// printf("Binds Texture %d %p\n", DST.RST.bind_tex_inc, tex);
 				return;
 			}
 		}
-
 		printf("Not enough texture slots! Reduce number of textures used by your shader.\n");
 	}
-	DST.RST.bound_tex_slots[bind_num] = true;
+	slot_flags[bind_num] = bind_type;
 }
 
-static void bind_ubo(GPUUniformBuffer *ubo)
+static void bind_ubo(GPUUniformBuffer *ubo, char bind_type)
 {
+	int index;
+	char *slot_flags = DST.RST.bound_ubo_slots;
 	int bind_num = GPU_uniformbuffer_bindpoint(ubo);
 	if (bind_num == -1) {
 		for (int i = 0; i < GPU_max_ubo_binds(); ++i) {
-			DST.RST.bind_ubo_inc = (DST.RST.bind_ubo_inc + 1) % GPU_max_ubo_binds();
-			if (DST.RST.bound_ubo_slots[DST.RST.bind_ubo_inc] == false) {
-				if (DST.RST.bound_ubos[DST.RST.bind_ubo_inc] != NULL) {
-					GPU_uniformbuffer_unbind(DST.RST.bound_ubos[DST.RST.bind_ubo_inc]);
+			index = DST.RST.bind_ubo_inc = (DST.RST.bind_ubo_inc + 1) % GPU_max_ubo_binds();
+			if (slot_flags[index] == BIND_NONE) {
+				if (DST.RST.bound_ubos[index] != NULL) {
+					GPU_uniformbuffer_unbind(DST.RST.bound_ubos[index]);
 				}
-				GPU_uniformbuffer_bind(ubo, DST.RST.bind_ubo_inc);
-				DST.RST.bound_ubos[DST.RST.bind_ubo_inc] = ubo;
-				DST.RST.bound_ubo_slots[DST.RST.bind_ubo_inc] = true;
+				GPU_uniformbuffer_bind(ubo, index);
+				DST.RST.bound_ubos[index] = ubo;
+				slot_flags[bind_num] = bind_type;
 				return;
 			}
 		}
-		/* This is not depending on user input.
-		 * It is our responsability to make sure there enough slots. */
-		BLI_assert(0 && "Not enough ubo slots! This should not happen!\n");
-
 		/* printf so user can report bad behaviour */
 		printf("Not enough ubo slots! This should not happen!\n");
+		/* This is not depending on user input.
+		 * It is our responsability to make sure there is enough slots. */
+		BLI_assert(0);
 	}
-	DST.RST.bound_ubo_slots[bind_num] = true;
+	slot_flags[bind_num] = bind_type;
 }
 
-static void release_texture_slots(void)
+static void release_texture_slots(bool with_persist)
 {
-	memset(DST.RST.bound_tex_slots, 0x0, sizeof(bool) * GPU_max_textures());
+	if (with_persist) {
+		memset(DST.RST.bound_tex_slots, 0x0, sizeof(*DST.RST.bound_tex_slots) * GPU_max_textures());
+	}
+	else {
+		for (int i = 0; i < GPU_max_textures(); ++i) {
+			if (DST.RST.bound_tex_slots[i] != BIND_PERSIST)
+				DST.RST.bound_tex_slots[i] = BIND_NONE;
+		}
+	}
 }
 
-static void release_ubo_slots(void)
+static void release_ubo_slots(bool with_persist)
 {
-	memset(DST.RST.bound_ubo_slots, 0x0, sizeof(bool) * GPU_max_textures());
+	if (with_persist) {
+		memset(DST.RST.bound_ubo_slots, 0x0, sizeof(*DST.RST.bound_ubo_slots) * GPU_max_ubo_binds());
+	}
+	else {
+		for (int i = 0; i < GPU_max_ubo_binds(); ++i) {
+			if (DST.RST.bound_ubo_slots[i] != BIND_PERSIST)
+				DST.RST.bound_ubo_slots[i] = BIND_NONE;
+		}
+	}
 }
 
 static void draw_shgroup(DRWShadingGroup *shgroup, DRWState pass_state)
@@ -771,16 +795,17 @@ static void draw_shgroup(DRWShadingGroup *shgroup, DRWState pass_state)
 	GPUUniformBuffer *ubo;
 	int val;
 	float fval;
+	const bool shader_changed = (DST.shader != shgroup->shader);
 
-	if (DST.shader != shgroup->shader) {
+	if (shader_changed) {
 		if (DST.shader) GPU_shader_unbind();
 		GPU_shader_bind(shgroup->shader);
 		DST.shader = shgroup->shader;
-
-		release_texture_slots();
-		release_ubo_slots();
 	}
 
+	release_ubo_slots(shader_changed);
+	release_texture_slots(shader_changed);
+
 	drw_state_set((pass_state & shgroup->state_extra_disable) | shgroup->state_extra);
 	drw_stencil_set(shgroup->stencil_mask);
 
@@ -810,7 +835,13 @@ static void draw_shgroup(DRWShadingGroup *shgroup, DRWState pass_state)
 			case DRW_UNIFORM_TEXTURE:
 				tex = (GPUTexture *)uni->value;
 				BLI_assert(tex);
-				bind_texture(tex);
+				bind_texture(tex, BIND_TEMP);
+				GPU_shader_uniform_texture(shgroup->shader, uni->location, tex);
+				break;
+			case DRW_UNIFORM_TEXTURE_PERSIST:
+				tex = (GPUTexture *)uni->value;
+				BLI_assert(tex);
+				bind_texture(tex, BIND_PERSIST);
 				GPU_shader_uniform_texture(shgroup->shader, uni->location, tex);
 				break;
 			case DRW_UNIFORM_BUFFER:
@@ -819,12 +850,17 @@ static void draw_shgroup(DRWShadingGroup *shgroup, DRWState pass_state)
 				}
 				tex = *((GPUTexture **)uni->value);
 				BLI_assert(tex);
-				bind_texture(tex);
+				bind_texture(tex, BIND_TEMP);
 				GPU_shader_uniform_texture(shgroup->shader, uni->location, tex);
 				break;
 			case DRW_UNIFORM_BLOCK:
 				ubo = (GPUUniformBuffer *)uni->value;
-				bind_ubo(ubo);
+				bind_ubo(ubo, BIND_TEMP);
+				GPU_shader_uniform_buffer(shgroup->shader, uni->location, ubo);
+				break;
+			case DRW_UNIFORM_BLOCK_PERSIST:
+				ubo = (GPUUniformBuffer *)uni->value;
+				bind_ubo(ubo, BIND_PERSIST);
 				GPU_shader_uniform_buffer(shgroup->shader, uni->location, ubo);
 				break;
 		}
diff --git a/source/blender/draw/intern/draw_manager_profiling.c b/source/blender/draw/intern/draw_manager_profiling.c
index 8e5f98b5a49a7f70e3d4d947a85a158d01e3ae25..47769b1fb18382ccfaa4bb654e5cbbd76b12349e 100644
--- a/source/blender/draw/intern/draw_manager_profiling.c
+++ b/source/blender/draw/intern/draw_manager_profiling.c
@@ -286,9 +286,10 @@ void DRW_stats_draw(rcti *rect)
 	v += 2;
 
 	u = 0;
+	double *cache_time = GPU_viewport_cache_time_get(DST.viewport);
 	sprintf(col_label, "Cache Time");
 	draw_stat_5row(rect, u++, v, col_label, sizeof(col_label));
-	sprintf(time_to_txt, "%.2fms", DST.cache_time);
+	sprintf(time_to_txt, "%.2fms", *cache_time);
 	draw_stat_5row(rect, u++, v, time_to_txt, sizeof(time_to_txt));
 	v += 2;
 
diff --git a/source/blender/draw/intern/draw_manager_texture.c b/source/blender/draw/intern/draw_manager_texture.c
index bbef680e77a87b0d7fe6ac4d0f7d9e9049d68faf..e033a0c506eda6932f8c06f56a7bb16c86d93292 100644
--- a/source/blender/draw/intern/draw_manager_texture.c
+++ b/source/blender/draw/intern/draw_manager_texture.c
@@ -35,6 +35,7 @@ void drw_texture_get_format(
 			/* Only add formats that are COMPATIBLE with FB.
 			 * Generally they are multiple of 16bit. */
 			case DRW_TEX_R_16:
+			case DRW_TEX_R_16I:
 			case DRW_TEX_R_32:
 			case DRW_TEX_RG_8:
 			case DRW_TEX_RG_16:
@@ -70,6 +71,7 @@ void drw_texture_get_format(
 		case DRW_TEX_RG_32: *r_data_type = GPU_RG32F; break;
 		case DRW_TEX_R_8: *r_data_type = GPU_R8; break;
 		case DRW_TEX_R_16: *r_data_type = GPU_R16F; break;
+		case DRW_TEX_R_16I: *r_data_type = GPU_R16I; break;
 		case DRW_TEX_R_32: *r_data_type = GPU_R32F; break;
 #if 0
 		case DRW_TEX_RGB_8: *r_data_type = GPU_RGB8; break;
diff --git a/source/blender/editors/animation/anim_channels_edit.c b/source/blender/editors/animation/anim_channels_edit.c
index d137e5f1c74c4b5496aab4b68921bbf5ce4512fe..2f9d32eadb6ab0a0e080e3dd921e58896fb386e5 100644
--- a/source/blender/editors/animation/anim_channels_edit.c
+++ b/source/blender/editors/animation/anim_channels_edit.c
@@ -3090,7 +3090,7 @@ static bool select_anim_channel_keys(bAnimContext *ac, int channel_index, bool e
 			for (ale = anim_data.first; ale; ale = ale->next) {
 				FCurve *fcu_inner = (FCurve *)ale->key_data;
 
-				if (fcu_inner) {
+				if (fcu_inner != NULL && fcu_inner->bezt != NULL) {
 					for (i = 0, bezt = fcu_inner->bezt; i < fcu_inner->totvert; i++, bezt++) {
 						bezt->f2 = bezt->f1 = bezt->f3 = 0;
 					}
diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c
index a4eaf800801b352f6df03844027c22ac21382bd7..d6385276b1a75ad26133947ad1d20e3ca0549e64 100644
--- a/source/blender/editors/interface/interface.c
+++ b/source/blender/editors/interface/interface.c
@@ -1221,11 +1221,9 @@ static void ui_menu_block_set_keymaps(const bContext *C, uiBlock *block)
 
 void ui_but_override_flag(uiBut *but)
 {
-	bool is_overridden;
+	const int override_status = RNA_property_override_status(&but->rnapoin, but->rnaprop, but->rnaindex);
 
-	RNA_property_override_status(&but->rnapoin, but->rnaprop, but->rnaindex, NULL, &is_overridden, NULL, NULL);
-
-	if (is_overridden) {
+	if (override_status & RNA_OVERRIDE_STATUS_OVERRIDDEN) {
 		but->flag |= UI_BUT_OVERRIDEN;
 	}
 	else {
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c
index 3a01ff16297d361448c466db2738aecd3529ef76..b1dc30945c4c1a3d9589e9e4c25ab0ee7b5d9c99 100644
--- a/source/blender/editors/interface/interface_handlers.c
+++ b/source/blender/editors/interface/interface_handlers.c
@@ -6819,11 +6819,11 @@ static bool ui_but_menu(bContext *C, uiBut *but)
 		const PropertySubType subtype = RNA_property_subtype(prop);
 		bool is_anim = RNA_property_animateable(ptr, prop);
 		bool is_editable = RNA_property_editable(ptr, prop);
-		bool is_overridable;
 		/*bool is_idprop = RNA_property_is_idprop(prop);*/ /* XXX does not work as expected, not strictly needed */
 		bool is_set = RNA_property_is_set(ptr, prop);
 
-		RNA_property_override_status(ptr, prop, -1, &is_overridable, NULL, NULL, NULL);
+		const int override_status = RNA_property_override_status(ptr, prop, -1);
+		const bool is_overridable = (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE) != 0;
 
 		/* second slower test, saved people finding keyframe items in menus when its not possible */
 		if (is_anim)
diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c
index 16525dfbc9eb0817c82b2ad376c31e15c9730036..ad4aaf599989a0e6bff6ad5fd2978d9f34019752 100644
--- a/source/blender/editors/interface/interface_ops.c
+++ b/source/blender/editors/interface/interface_ops.c
@@ -482,13 +482,12 @@ static int override_type_set_button_poll(bContext *C)
 	PointerRNA ptr;
 	PropertyRNA *prop;
 	int index;
-	bool is_overridable;
 
 	UI_context_active_but_prop_get(C, &ptr, &prop, &index);
 
-	RNA_property_override_status(&ptr, prop, index, &is_overridable, NULL, NULL, NULL);
+	const int override_status = RNA_property_override_status(&ptr, prop, index);
 
-	return (ptr.data && prop && is_overridable);
+	return (ptr.data && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE));
 }
 
 static int override_type_set_button_exec(bContext *C, wmOperator *op)
@@ -572,13 +571,12 @@ static int override_remove_button_poll(bContext *C)
 	PointerRNA ptr;
 	PropertyRNA *prop;
 	int index;
-	bool is_overridden;
 
 	UI_context_active_but_prop_get(C, &ptr, &prop, &index);
 
-	RNA_property_override_status(&ptr, prop, index, NULL, &is_overridden, NULL, NULL);
+	const int override_status = RNA_property_override_status(&ptr, prop, index);
 
-	return (ptr.data && ptr.id.data && prop && is_overridden);
+	return (ptr.data && ptr.id.data && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDDEN));
 }
 
 static int override_remove_button_exec(bContext *C, wmOperator *op)
diff --git a/source/blender/editors/io/io_alembic.c b/source/blender/editors/io/io_alembic.c
index ca4ab30a08d74791d656575a9820109123439261..b584782e183d1fa0869fa7386cb8c41609aff274 100644
--- a/source/blender/editors/io/io_alembic.c
+++ b/source/blender/editors/io/io_alembic.c
@@ -59,6 +59,8 @@
 #include "RNA_define.h"
 #include "RNA_enum_types.h"
 
+#include "ED_object.h"
+
 #include "UI_interface.h"
 #include "UI_resources.h"
 
@@ -543,6 +545,12 @@ static int wm_alembic_import_exec(bContext *C, wmOperator *op)
 		}
 	}
 
+	/* Switch out of edit mode to avoid being stuck in it (T54326). */
+	Object *obedit = CTX_data_edit_object(C);
+	if (obedit) {
+		ED_object_mode_toggle(C, OB_MODE_EDIT);
+	}
+
 	bool ok = ABC_import(C, filename, scale, is_sequence, set_frame_range,
 	                     sequence_len, offset, validate_meshes,
 	                     as_background_job);
diff --git a/source/blender/editors/mesh/CMakeLists.txt b/source/blender/editors/mesh/CMakeLists.txt
index 80e1187609c2168bb8f9d2e11fea441ea8d3e7f5..3877838ec54cc5718f0973f82b89b9159d938252 100644
--- a/source/blender/editors/mesh/CMakeLists.txt
+++ b/source/blender/editors/mesh/CMakeLists.txt
@@ -60,6 +60,7 @@ set(SRC
 	editmesh_undo.c
 	editmesh_utils.c
 	mesh_data.c
+	mesh_mirror.c
 	mesh_ops.c
 	meshtools.c
 
diff --git a/source/blender/editors/mesh/editface.c b/source/blender/editors/mesh/editface.c
index 113173c1c03448393af9e790be4adfeeb0b469d2..1b9ee70ccf5f5c517af89fe47666e99dbed4db00 100644
--- a/source/blender/editors/mesh/editface.c
+++ b/source/blender/editors/mesh/editface.c
@@ -24,7 +24,6 @@
  *  \ingroup edmesh
  */
 
-
 #include "MEM_guardedalloc.h"
 
 #include "BLI_blenlib.h"
@@ -41,7 +40,6 @@
 #include "BKE_global.h"
 #include "BKE_mesh.h"
 #include "BKE_context.h"
-#include "BKE_editmesh.h"
 
 #include "BIF_gl.h"
 
@@ -609,261 +607,3 @@ void paintvert_select_ungrouped(Object *ob, bool extend, bool flush_flags)
 		paintvert_flush_flags(ob);
 	}
 }
-
-/* ********************* MESH VERTEX MIRR TOPO LOOKUP *************** */
-/* note, this is not the best place for the function to be but moved
- * here for the purpose of syncing with bmesh */
-
-typedef unsigned int MirrTopoHash_t;
-
-typedef struct MirrTopoVert_t {
-	MirrTopoHash_t hash;
-	int v_index;
-} MirrTopoVert_t;
-
-static int mirrtopo_hash_sort(const void *l1, const void *l2)
-{
-	if      ((MirrTopoHash_t)(intptr_t)l1 > (MirrTopoHash_t)(intptr_t)l2) return 1;
-	else if ((MirrTopoHash_t)(intptr_t)l1 < (MirrTopoHash_t)(intptr_t)l2) return -1;
-	return 0;
-}
-
-static int mirrtopo_vert_sort(const void *v1, const void *v2)
-{
-	if      (((MirrTopoVert_t *)v1)->hash > ((MirrTopoVert_t *)v2)->hash) return 1;
-	else if (((MirrTopoVert_t *)v1)->hash < ((MirrTopoVert_t *)v2)->hash) return -1;
-	return 0;
-}
-
-bool ED_mesh_mirrtopo_recalc_check(Mesh *me, DerivedMesh *dm, MirrTopoStore_t *mesh_topo_store)
-{
-	const bool is_editmode = (me->edit_btmesh != NULL);
-	int totvert;
-	int totedge;
-
-	if (dm) {
-		totvert = dm->getNumVerts(dm);
-		totedge = dm->getNumEdges(dm);
-	}
-	else if (me->edit_btmesh) {
-		totvert = me->edit_btmesh->bm->totvert;
-		totedge = me->edit_btmesh->bm->totedge;
-	}
-	else {
-		totvert = me->totvert;
-		totedge = me->totedge;
-	}
-
-	if ((mesh_topo_store->index_lookup == NULL) ||
-	    (mesh_topo_store->prev_is_editmode != is_editmode) ||
-	    (totvert != mesh_topo_store->prev_vert_tot) ||
-	    (totedge != mesh_topo_store->prev_edge_tot))
-	{
-		return true;
-	}
-	else {
-		return false;
-	}
-
-}
-
-void ED_mesh_mirrtopo_init(
-        Mesh *me, DerivedMesh *dm, MirrTopoStore_t *mesh_topo_store,
-        const bool skip_em_vert_array_init)
-{
-	const bool is_editmode = (me->edit_btmesh != NULL);
-	MEdge *medge = NULL, *med;
-	BMEditMesh *em = dm ?  NULL : me->edit_btmesh;
-
-	/* editmode*/
-	BMEdge *eed;
-	BMIter iter;
-
-	int a, last;
-	int totvert, totedge;
-	int tot_unique = -1, tot_unique_prev = -1;
-	int tot_unique_edges = 0, tot_unique_edges_prev;
-
-	MirrTopoHash_t *topo_hash = NULL;
-	MirrTopoHash_t *topo_hash_prev = NULL;
-	MirrTopoVert_t *topo_pairs;
-	MirrTopoHash_t  topo_pass = 1;
-
-	intptr_t *index_lookup; /* direct access to mesh_topo_store->index_lookup */
-
-	/* reallocate if needed */
-	ED_mesh_mirrtopo_free(mesh_topo_store);
-
-	mesh_topo_store->prev_is_editmode = is_editmode;
-
-	if (em) {
-		BM_mesh_elem_index_ensure(em->bm, BM_VERT);
-
-		totvert = em->bm->totvert;
-	}
-	else {
-		totvert = dm ? dm->getNumVerts(dm) : me->totvert;
-	}
-
-	topo_hash = MEM_callocN(totvert * sizeof(MirrTopoHash_t), "TopoMirr");
-
-	/* Initialize the vert-edge-user counts used to detect unique topology */
-	if (em) {
-		totedge = me->edit_btmesh->bm->totedge;
-
-		BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) {
-			const int i1 = BM_elem_index_get(eed->v1), i2 = BM_elem_index_get(eed->v2);
-			topo_hash[i1]++;
-			topo_hash[i2]++;
-		}
-	}
-	else {
-		totedge = dm ? dm->getNumEdges(dm) : me->totedge;
-		medge = dm ? dm->getEdgeArray(dm) : me->medge;
-
-		for (a = 0, med = medge; a < totedge; a++, med++) {
-			const unsigned int i1 = med->v1, i2 = med->v2;
-			topo_hash[i1]++;
-			topo_hash[i2]++;
-		}
-	}
-
-	topo_hash_prev = MEM_dupallocN(topo_hash);
-
-	tot_unique_prev = -1;
-	tot_unique_edges_prev = -1;
-	while (1) {
-		/* use the number of edges per vert to give verts unique topology IDs */
-
-		tot_unique_edges = 0;
-
-		/* This can make really big numbers, wrapping around here is fine */
-		if (em) {
-			BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) {
-				const int i1 = BM_elem_index_get(eed->v1), i2 = BM_elem_index_get(eed->v2);
-				topo_hash[i1] += topo_hash_prev[i2] * topo_pass;
-				topo_hash[i2] += topo_hash_prev[i1] * topo_pass;
-				tot_unique_edges += (topo_hash[i1] != topo_hash[i2]);
-			}
-		}
-		else {
-			for (a = 0, med = medge; a < totedge; a++, med++) {
-				const unsigned int i1 = med->v1, i2 = med->v2;
-				topo_hash[i1] += topo_hash_prev[i2] * topo_pass;
-				topo_hash[i2] += topo_hash_prev[i1] * topo_pass;
-				tot_unique_edges += (topo_hash[i1] != topo_hash[i2]);
-			}
-		}
-		memcpy(topo_hash_prev, topo_hash, sizeof(MirrTopoHash_t) * totvert);
-
-		/* sort so we can count unique values */
-		qsort(topo_hash_prev, totvert, sizeof(MirrTopoHash_t), mirrtopo_hash_sort);
-
-		tot_unique = 1; /* account for skiping the first value */
-		for (a = 1; a < totvert; a++) {
-			if (topo_hash_prev[a - 1] != topo_hash_prev[a]) {
-				tot_unique++;
-			}
-		}
-
-		if ((tot_unique <= tot_unique_prev) && (tot_unique_edges <= tot_unique_edges_prev)) {
-			/* Finish searching for unique values when 1 loop dosnt give a
-			 * higher number of unique values compared to the previous loop */
-			break;
-		}
-		else {
-			tot_unique_prev = tot_unique;
-			tot_unique_edges_prev = tot_unique_edges;
-		}
-		/* Copy the hash calculated this iter, so we can use them next time */
-		memcpy(topo_hash_prev, topo_hash, sizeof(MirrTopoHash_t) * totvert);
-
-		topo_pass++;
-	}
-
-	/* Hash/Index pairs are needed for sorting to find index pairs */
-	topo_pairs = MEM_callocN(sizeof(MirrTopoVert_t) * totvert, "MirrTopoPairs");
-
-	/* since we are looping through verts, initialize these values here too */
-	index_lookup = MEM_mallocN(totvert * sizeof(*index_lookup), "mesh_topo_lookup");
-
-	if (em) {
-		if (skip_em_vert_array_init == false) {
-			BM_mesh_elem_table_ensure(em->bm, BM_VERT);
-		}
-	}
-
-	for (a = 0; a < totvert; a++) {
-		topo_pairs[a].hash    = topo_hash[a];
-		topo_pairs[a].v_index = a;
-
-		/* initialize lookup */
-		index_lookup[a] = -1;
-	}
-
-	qsort(topo_pairs, totvert, sizeof(MirrTopoVert_t), mirrtopo_vert_sort);
-
-	last = 0;
-
-	/* Get the pairs out of the sorted hashes, note, totvert+1 means we can use the previous 2,
-	 * but you cant ever access the last 'a' index of MirrTopoPairs */
-	if (em) {
-		BMVert **vtable = em->bm->vtable;
-		for (a = 1; a <= totvert; a++) {
-			/* printf("I %d %ld %d\n", (a - last), MirrTopoPairs[a].hash, MirrTopoPairs[a].v_indexs); */
-			if ((a == totvert) || (topo_pairs[a - 1].hash != topo_pairs[a].hash)) {
-				const int match_count = a - last;
-				if (match_count == 2) {
-					const int j = topo_pairs[a - 1].v_index, k = topo_pairs[a - 2].v_index;
-					index_lookup[j] = (intptr_t)vtable[k];
-					index_lookup[k] = (intptr_t)vtable[j];
-				}
-				else if (match_count == 1) {
-					/* Center vertex. */
-					const int j = topo_pairs[a - 1].v_index;
-					index_lookup[j] = (intptr_t)vtable[j];
-				}
-				last = a;
-			}
-		}
-	}
-	else {
-		/* same as above, for mesh */
-		for (a = 1; a <= totvert; a++) {
-			if ((a == totvert) || (topo_pairs[a - 1].hash != topo_pairs[a].hash)) {
-				const int match_count = a - last;
-				if (match_count == 2) {
-					const int j = topo_pairs[a - 1].v_index, k = topo_pairs[a - 2].v_index;
-					index_lookup[j] = k;
-					index_lookup[k] = j;
-				}
-				else if (match_count == 1) {
-					/* Center vertex. */
-					const int j = topo_pairs[a - 1].v_index;
-					index_lookup[j] = j;
-				}
-				last = a;
-			}
-		}
-	}
-
-	MEM_freeN(topo_pairs);
-	topo_pairs = NULL;
-
-	MEM_freeN(topo_hash);
-	MEM_freeN(topo_hash_prev);
-
-	mesh_topo_store->index_lookup  = index_lookup;
-	mesh_topo_store->prev_vert_tot = totvert;
-	mesh_topo_store->prev_edge_tot = totedge;
-}
-
-void ED_mesh_mirrtopo_free(MirrTopoStore_t *mesh_topo_store)
-{
-	if (mesh_topo_store->index_lookup) {
-		MEM_freeN(mesh_topo_store->index_lookup);
-	}
-	mesh_topo_store->index_lookup  = NULL;
-	mesh_topo_store->prev_vert_tot = -1;
-	mesh_topo_store->prev_edge_tot = -1;
-}
diff --git a/source/blender/editors/mesh/editmesh_add.c b/source/blender/editors/mesh/editmesh_add.c
index a21fc2fffdefb67dc52bcea58fa9f93ee3d6ac1d..292b28c772caf3e5461186c90f6fc5be8fb4bb40 100644
--- a/source/blender/editors/mesh/editmesh_add.c
+++ b/source/blender/editors/mesh/editmesh_add.c
@@ -65,9 +65,10 @@ typedef struct MakePrimitiveData {
 	bool was_editmode;
 } MakePrimitiveData;
 
-static Object *make_prim_init(bContext *C, const char *idname,
-                              const float loc[3], const float rot[3], const unsigned int layer,
-                              MakePrimitiveData *r_creation_data)
+static Object *make_prim_init(
+        bContext *C, const char *idname,
+        const float loc[3], const float rot[3], const unsigned int layer,
+        MakePrimitiveData *r_creation_data)
 {
 	Object *obedit = CTX_data_edit_object(C);
 
diff --git a/source/blender/editors/mesh/editmesh_extrude.c b/source/blender/editors/mesh/editmesh_extrude.c
index 18320ec65f575e2e95c5d445cc3d99b761b64106..aee9785ea83bdfcd76d47623803d03fd6691bd72 100644
--- a/source/blender/editors/mesh/editmesh_extrude.c
+++ b/source/blender/editors/mesh/editmesh_extrude.c
@@ -66,6 +66,10 @@
 #include "ED_util.h"
 #endif
 
+/* -------------------------------------------------------------------- */
+/** \name Extrude Internal Utilities
+ * \{ */
+
 static void edbm_extrude_edge_exclude_mirror(
         Object *obedit, BMEditMesh *em,
         const char hflag,
@@ -154,7 +158,7 @@ static bool edbm_extrude_discrete_faces(BMEditMesh *em, wmOperator *op, const ch
 	EDBM_flag_disable_all(em, BM_ELEM_SELECT);
 
 	BMO_op_exec(em->bm, &bmop);
-	
+
 	BMO_ITER (f, &siter, bmop.slots_out, "faces.out", BM_FACE) {
 		BM_face_select_set(em->bm, f, true);
 
@@ -254,7 +258,7 @@ static bool edbm_extrude_ex(
 	BMOIter siter;
 	BMOperator extop;
 	BMElem *ele;
-	
+
 	/* needed to remove the faces left behind */
 	if (htype & BM_FACE) {
 		htype |= BM_EDGE;
@@ -276,7 +280,7 @@ static bool edbm_extrude_ex(
 	BM_SELECT_HISTORY_RESTORE(bm);
 
 	BMO_op_exec(bm, &extop);
-	
+
 	BMO_ITER (ele, &siter, extop.slots_out, "geom.out", BM_ALL_NOLOOP) {
 		BM_elem_select_set(bm, ele, true);
 	}
@@ -286,14 +290,20 @@ static bool edbm_extrude_ex(
 	return true;
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Extrude Repeat Operator
+ * \{ */
+
 static int edbm_extrude_repeat_exec(bContext *C, wmOperator *op)
 {
 	Object *obedit = CTX_data_edit_object(C);
 	BMEditMesh *em = BKE_editmesh_from_object(obedit);
 	RegionView3D *rv3d = CTX_wm_region_view3d(C);
-		
+
 	const int steps = RNA_int_get(op->ptr, "steps");
-	
+
 	const float offs = RNA_float_get(op->ptr, "offset");
 	float dvec[3], tmat[3][3], bmat[3][3];
 	short a;
@@ -314,7 +324,7 @@ static int edbm_extrude_repeat_exec(bContext *C, wmOperator *op)
 		        "translate vec=%v verts=%hv",
 		        dvec, BM_ELEM_SELECT);
 	}
-	
+
 	EDBM_mesh_normals_update(em);
 
 	EDBM_update_generic(em, true, true);
@@ -328,19 +338,25 @@ void MESH_OT_extrude_repeat(wmOperatorType *ot)
 	ot->name = "Extrude Repeat Mesh";
 	ot->description = "Extrude selected vertices, edges or faces repeatedly";
 	ot->idname = "MESH_OT_extrude_repeat";
-	
+
 	/* api callbacks */
 	ot->exec = edbm_extrude_repeat_exec;
 	ot->poll = ED_operator_editmesh_view3d;
-	
+
 	/* flags */
 	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-	
+
 	/* props */
 	RNA_def_float_distance(ot->srna, "offset", 2.0f, 0.0f, 1e12f, "Offset", "", 0.0f, 100.0f);
 	RNA_def_int(ot->srna, "steps", 10, 0, 1000000, "Steps", "", 0, 180);
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Extrude Operator
+ * \{ */
+
 /* generic extern called extruder */
 static bool edbm_extrude_mesh(Object *obedit, BMEditMesh *em, wmOperator *op)
 {
@@ -377,7 +393,7 @@ static bool edbm_extrude_mesh(Object *obedit, BMEditMesh *em, wmOperator *op)
 			changed = edbm_extrude_edges_indiv(em, op, BM_ELEM_SELECT);
 			break;
 	}
-	
+
 	if (changed) {
 		return true;
 	}
@@ -392,7 +408,7 @@ static int edbm_extrude_region_exec(bContext *C, wmOperator *op)
 {
 	Object *obedit = CTX_data_edit_object(C);
 	BMEditMesh *em = BKE_editmesh_from_object(obedit);
-	
+
 	edbm_extrude_mesh(obedit, em, op);
 
 	/* This normally happens when pushing undo but modal operators
@@ -401,7 +417,7 @@ static int edbm_extrude_region_exec(bContext *C, wmOperator *op)
 	EDBM_mesh_normals_update(em);
 
 	EDBM_update_generic(em, true, true);
-	
+
 	return OPERATOR_FINISHED;
 }
 
@@ -411,27 +427,33 @@ void MESH_OT_extrude_region(wmOperatorType *ot)
 	ot->name = "Extrude Region";
 	ot->idname = "MESH_OT_extrude_region";
 	ot->description = "Extrude region of faces";
-	
+
 	/* api callbacks */
 	//ot->invoke = mesh_extrude_region_invoke;
 	ot->exec = edbm_extrude_region_exec;
 	ot->poll = ED_operator_editmesh;
-	
+
 	/* flags */
 	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
 	Transform_Properties(ot, P_NO_DEFAULTS | P_MIRROR_DUMMY);
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Extrude Verts Operator
+ * \{ */
+
 static int edbm_extrude_verts_exec(bContext *C, wmOperator *op)
 {
 	Object *obedit = CTX_data_edit_object(C);
 	BMEditMesh *em = BKE_editmesh_from_object(obedit);
 
 	edbm_extrude_verts_indiv(em, op, BM_ELEM_SELECT);
-	
+
 	EDBM_update_generic(em, true, true);
-	
+
 	return OPERATOR_FINISHED;
 }
 
@@ -441,11 +463,11 @@ void MESH_OT_extrude_verts_indiv(wmOperatorType *ot)
 	ot->name = "Extrude Only Vertices";
 	ot->idname = "MESH_OT_extrude_verts_indiv";
 	ot->description = "Extrude individual vertices only";
-	
+
 	/* api callbacks */
 	ot->exec = edbm_extrude_verts_exec;
 	ot->poll = ED_operator_editmesh;
-	
+
 	/* flags */
 	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
@@ -453,15 +475,21 @@ void MESH_OT_extrude_verts_indiv(wmOperatorType *ot)
 	Transform_Properties(ot, P_NO_DEFAULTS | P_MIRROR_DUMMY);
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Extrude Edges Operator
+ * \{ */
+
 static int edbm_extrude_edges_exec(bContext *C, wmOperator *op)
 {
 	Object *obedit = CTX_data_edit_object(C);
 	BMEditMesh *em = BKE_editmesh_from_object(obedit);
 
 	edbm_extrude_edges_indiv(em, op, BM_ELEM_SELECT);
-	
+
 	EDBM_update_generic(em, true, true);
-	
+
 	return OPERATOR_FINISHED;
 }
 
@@ -471,11 +499,11 @@ void MESH_OT_extrude_edges_indiv(wmOperatorType *ot)
 	ot->name = "Extrude Only Edges";
 	ot->idname = "MESH_OT_extrude_edges_indiv";
 	ot->description = "Extrude individual edges only";
-	
+
 	/* api callbacks */
 	ot->exec = edbm_extrude_edges_exec;
 	ot->poll = ED_operator_editmesh;
-	
+
 	/* flags */
 	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
@@ -483,15 +511,21 @@ void MESH_OT_extrude_edges_indiv(wmOperatorType *ot)
 	Transform_Properties(ot, P_NO_DEFAULTS | P_MIRROR_DUMMY);
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Extrude Faces Operator
+ * \{ */
+
 static int edbm_extrude_faces_exec(bContext *C, wmOperator *op)
 {
 	Object *obedit = CTX_data_edit_object(C);
 	BMEditMesh *em = BKE_editmesh_from_object(obedit);
 
 	edbm_extrude_discrete_faces(em, op, BM_ELEM_SELECT);
-	
+
 	EDBM_update_generic(em, true, true);
-	
+
 	return OPERATOR_FINISHED;
 }
 
@@ -501,18 +535,25 @@ void MESH_OT_extrude_faces_indiv(wmOperatorType *ot)
 	ot->name = "Extrude Individual Faces";
 	ot->idname = "MESH_OT_extrude_faces_indiv";
 	ot->description = "Extrude individual faces only";
-	
+
 	/* api callbacks */
 	ot->exec = edbm_extrude_faces_exec;
 	ot->poll = ED_operator_editmesh;
-	
+
 	/* flags */
 	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
 	Transform_Properties(ot, P_NO_DEFAULTS | P_MIRROR_DUMMY);
 }
 
-/* *************** add-click-mesh (extrude) operator ************** */
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Dupli-Extrude Operator
+ *
+ * Add-click-mesh (extrude) operator.
+ * \{ */
+
 static int edbm_dupli_extrude_cursor_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 {
 	ViewContext vc;
@@ -533,7 +574,7 @@ static int edbm_dupli_extrude_cursor_invoke(bContext *C, wmOperator *op, const w
 
 	zero_v3(center);
 	verts_len = 0;
-	
+
 	BM_ITER_MESH (v1, &iter, vc.em->bm, BM_VERTS_OF_MESH) {
 		if (BM_elem_flag_test(v1, BM_ELEM_SELECT)) {
 			add_v3_v3(center, v1->co);
@@ -596,7 +637,7 @@ static int edbm_dupli_extrude_cursor_invoke(bContext *C, wmOperator *op, const w
 			cross_v3_v3v3(nor, view_vec, cross);
 			normalize_v3(nor);
 		}
-		
+
 		/* center */
 		copy_v3_v3(ofs, center);
 
@@ -605,7 +646,7 @@ static int edbm_dupli_extrude_cursor_invoke(bContext *C, wmOperator *op, const w
 		mul_m4_v3(vc.obedit->imat, ofs); // back in object space
 
 		sub_v3_v3(ofs, center);
-		
+
 		/* calculate rotation */
 		unit_m3(mat);
 		if (done) {
@@ -628,7 +669,7 @@ static int edbm_dupli_extrude_cursor_invoke(bContext *C, wmOperator *op, const w
 				axis_angle_to_mat3(mat, axis, angle);
 			}
 		}
-		
+
 		if (rot_src) {
 			EDBM_op_callf(vc.em, op, "rotate verts=%hv cent=%v matrix=%m3",
 			              BM_ELEM_SELECT, center, mat);
@@ -653,7 +694,7 @@ static int edbm_dupli_extrude_cursor_invoke(bContext *C, wmOperator *op, const w
 		ED_view3d_win_to_3d_int(vc.v3d, vc.ar, center, event->mval, center);
 
 		mul_m4_v3(vc.obedit->imat, center); // back in object space
-		
+
 		EDBM_op_init(vc.em, &bmop, op, "create_vert co=%v", center);
 		BMO_op_exec(vc.em->bm, &bmop);
 
@@ -685,17 +726,22 @@ void MESH_OT_dupli_extrude_cursor(wmOperatorType *ot)
 	ot->name = "Duplicate or Extrude to Cursor";
 	ot->idname = "MESH_OT_dupli_extrude_cursor";
 	ot->description = "Duplicate and extrude selected vertices, edges or faces towards the mouse cursor";
-	
+
 	/* api callbacks */
 	ot->invoke = edbm_dupli_extrude_cursor_invoke;
 	ot->poll = ED_operator_editmesh_region_view3d;
-	
+
 	/* flags */
 	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
 	RNA_def_boolean(ot->srna, "rotate_source", true, "Rotate Source", "Rotate initial selection giving better shape");
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Spin Operator
+ * \{ */
 
 static int edbm_spin_exec(bContext *C, wmOperator *op)
 {
@@ -815,8 +861,7 @@ void MESH_OT_spin(wmOperatorType *ot)
 #ifdef USE_MANIPULATOR
 
 /* -------------------------------------------------------------------- */
-
-/** \name Spin Manipulator
+/** \name Screw Operator
  * \{ */
 
 typedef struct ManipulatorSpinGroup {
@@ -1315,3 +1360,5 @@ void MESH_OT_screw(wmOperatorType *ot)
 	RNA_def_float_vector(ot->srna, "axis", 3, NULL, -1.0f, 1.0f,
 	                     "Axis", "Axis in global view space", -1.0f, 1.0f);
 }
+
+/** \} */
diff --git a/source/blender/editors/mesh/editmesh_knife.c b/source/blender/editors/mesh/editmesh_knife.c
index d9bbec7082db8d434ae0a75bfbdd1277654bba47..cd894502bc4fc9a031b1fa549d367ac301c8f10c 100644
--- a/source/blender/editors/mesh/editmesh_knife.c
+++ b/source/blender/editors/mesh/editmesh_knife.c
@@ -1782,7 +1782,7 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
 	}
 
 	kcd->linehits = linehits;
-	kcd->totlinehit = BLI_array_count(linehits);
+	kcd->totlinehit = BLI_array_len(linehits);
 
 	/* find position along screen line, used for sorting */
 	for (i = 0; i < kcd->totlinehit; i++) {
diff --git a/source/blender/editors/mesh/editmesh_path.c b/source/blender/editors/mesh/editmesh_path.c
index 795c7b6aa530b1bae943364c5393d3e121dbd5f7..2ae48bee09592dbc9a0a373f091687bb89868bad 100644
--- a/source/blender/editors/mesh/editmesh_path.c
+++ b/source/blender/editors/mesh/editmesh_path.c
@@ -63,6 +63,10 @@
 
 #include "mesh_intern.h"  /* own include */
 
+/* -------------------------------------------------------------------- */
+/** \name Path Select Struct & Properties
+ * \{ */
+
 struct PathSelectParams {
 	bool track_active;  /* ensure the active element is the last selected item (handy for picking) */
 	bool use_topology_distance;
@@ -102,8 +106,11 @@ struct UserData {
 	const struct PathSelectParams *op_params;
 };
 
+/** \} */
+
 /* -------------------------------------------------------------------- */
-/* Vert Path */
+/** \name Vert Path
+ * \{ */
 
 /* callbacks */
 static bool verttag_filter_cb(BMVert *v, void *UNUSED(user_data_v))
@@ -205,10 +212,11 @@ static void mouse_mesh_shortest_path_vert(
 	EDBM_update_generic(em, false, false);
 }
 
-
+/** \} */
 
 /* -------------------------------------------------------------------- */
-/* Edge Path */
+/** \name Edge Path
+ * \{ */
 
 /* callbacks */
 static bool edgetag_filter_cb(BMEdge *e, void *UNUSED(user_data_v))
@@ -427,10 +435,11 @@ static void mouse_mesh_shortest_path_edge(
 	EDBM_update_generic(em, false, false);
 }
 
-
+/** \} */
 
 /* -------------------------------------------------------------------- */
-/* Face Path */
+/** \name Face Path
+ * \{ */
 
 /* callbacks */
 static bool facetag_filter_cb(BMFace *f, void *UNUSED(user_data_v))
@@ -477,7 +486,6 @@ static void mouse_mesh_shortest_path_face(
 			        facetag_filter_cb, &user_data);
 		}
 
-
 		if (f_act != f_dst) {
 			if (path) {
 				if (op_params->track_active) {
@@ -538,10 +546,11 @@ static void mouse_mesh_shortest_path_face(
 	EDBM_update_generic(em, false, false);
 }
 
-
+/** \} */
 
 /* -------------------------------------------------------------------- */
-/* Main Operator for vert/edge/face tag */
+/** \name Main Operator for vert/edge/face tag
+ * \{ */
 
 static bool edbm_shortest_path_pick_ex(
         Scene *scene, Object *obedit, const struct PathSelectParams *op_params,
@@ -710,9 +719,11 @@ void MESH_OT_shortest_path_pick(wmOperatorType *ot)
 	RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
 }
 
+/** \} */
 
 /* -------------------------------------------------------------------- */
-/* Select path between existing selection */
+/** \name Select Path Between Existing Selection
+ * \{ */
 
 static int edbm_shortest_path_select_exec(bContext *C, wmOperator *op)
 {
@@ -797,3 +808,5 @@ void MESH_OT_shortest_path_select(wmOperatorType *ot)
 	/* properties */
 	path_select_properties(ot);
 }
+
+/** \} */
diff --git a/source/blender/editors/mesh/editmesh_select.c b/source/blender/editors/mesh/editmesh_select.c
index ce44eaaaa0e42dc4deb8a9deaf190a1e778fa460..5d33f1d57c5021f4ff699a0318ea8bdce69f3e63 100644
--- a/source/blender/editors/mesh/editmesh_select.c
+++ b/source/blender/editors/mesh/editmesh_select.c
@@ -4244,7 +4244,7 @@ static int loop_find_region(
 	BLI_array_append(stack, l->f);
 	BLI_gset_insert(visit_face_set, l->f);
 
-	while (BLI_array_count(stack) > 0) {
+	while (BLI_array_len(stack) > 0) {
 		BMIter liter1, liter2;
 		BMLoop *l1, *l2;
 
@@ -4272,7 +4272,7 @@ static int loop_find_region(
 	BLI_array_free(stack);
 
 	*region_out = region;
-	return BLI_array_count(region);
+	return BLI_array_len(region);
 }
 
 static int verg_radial(const void *va, const void *vb)
diff --git a/source/blender/editors/mesh/editmesh_utils.c b/source/blender/editors/mesh/editmesh_utils.c
index 70ee162753778c027328c380c44cd202cc84f640..312dc000a2b58adcaa2c5c380beeabd90e55ffd1 100644
--- a/source/blender/editors/mesh/editmesh_utils.c
+++ b/source/blender/editors/mesh/editmesh_utils.c
@@ -64,8 +64,14 @@
 
 #include "mesh_intern.h"  /* own include */
 
-/* mesh backup implementation. This would greatly benefit from some sort of binary diffing
- * just as the undo stack would. So leaving this as an interface for further work */
+/* -------------------------------------------------------------------- */
+/** \name Redo API
+ * \{ */
+
+/* Mesh backup implementation.
+ * This would greatly benefit from some sort of binary diffing
+ * just as the undo stack would.
+ * So leaving this as an interface for further work */
 
 BMBackup EDBM_redo_state_store(BMEditMesh *em)
 {
@@ -77,8 +83,9 @@ BMBackup EDBM_redo_state_store(BMEditMesh *em)
 void EDBM_redo_state_restore(BMBackup backup, BMEditMesh *em, int recalctess)
 {
 	BMesh *tmpbm;
-	if (!em || !backup.bmcopy)
+	if (!em || !backup.bmcopy) {
 		return;
+	}
 
 	BM_mesh_data_free(em->bm);
 	tmpbm = BM_mesh_copy(backup.bmcopy);
@@ -86,8 +93,9 @@ void EDBM_redo_state_restore(BMBackup backup, BMEditMesh *em, int recalctess)
 	MEM_freeN(tmpbm);
 	tmpbm = NULL;
 
-	if (recalctess)
+	if (recalctess) {
 		BKE_editmesh_tessface_calc(em);
+	}
 }
 
 void EDBM_redo_state_free(BMBackup *backup, BMEditMesh *em, int recalctess)
@@ -100,68 +108,21 @@ void EDBM_redo_state_free(BMBackup *backup, BMEditMesh *em, int recalctess)
 		BM_mesh_data_free(backup->bmcopy);
 	}
 
-	if (backup->bmcopy)
+	if (backup->bmcopy) {
 		MEM_freeN(backup->bmcopy);
+	}
 	backup->bmcopy = NULL;
 
-	if (recalctess && em)
+	if (recalctess && em) {
 		BKE_editmesh_tessface_calc(em);
-}
-
-void EDBM_mesh_normals_update(BMEditMesh *em)
-{
-	BM_mesh_normals_update(em->bm);
-}
-
-void EDBM_mesh_clear(BMEditMesh *em)
-{
-	/* clear bmesh */
-	BM_mesh_clear(em->bm);
-	
-	/* free derived meshes */
-	BKE_editmesh_free_derivedmesh(em);
-	
-	/* free tessellation data */
-	em->tottri = 0;
-	if (em->looptris) {
-		MEM_freeN(em->looptris);
-		em->looptris = NULL;
 	}
 }
 
-void EDBM_stats_update(BMEditMesh *em)
-{
-	const char iter_types[3] = {BM_VERTS_OF_MESH,
-	                            BM_EDGES_OF_MESH,
-	                            BM_FACES_OF_MESH};
-
-	BMIter iter;
-	BMElem *ele;
-	int *tots[3];
-	int i;
+/** \} */
 
-	tots[0] = &em->bm->totvertsel;
-	tots[1] = &em->bm->totedgesel;
-	tots[2] = &em->bm->totfacesel;
-	
-	em->bm->totvertsel = em->bm->totedgesel = em->bm->totfacesel = 0;
-
-	for (i = 0; i < 3; i++) {
-		ele = BM_iter_new(&iter, em->bm, iter_types[i], NULL);
-		for ( ; ele; ele = BM_iter_step(&iter)) {
-			if (BM_elem_flag_test(ele, BM_ELEM_SELECT)) {
-				(*tots[i])++;
-			}
-		}
-	}
-}
-
-DerivedMesh *EDBM_mesh_deform_dm_get(BMEditMesh *em)
-{
-	return ((em->derivedFinal != NULL) &&
-	        (em->derivedFinal->type == DM_TYPE_EDITBMESH) &&
-	        (em->derivedFinal->deformedOnly != false)) ? em->derivedFinal : NULL;
-}
+/* -------------------------------------------------------------------- */
+/** \name BMesh Operator (BMO) API Wrapper
+ * \{ */
 
 bool EDBM_op_init(BMEditMesh *em, BMOperator *bmop, wmOperator *op, const char *fmt, ...)
 {
@@ -175,9 +136,10 @@ bool EDBM_op_init(BMEditMesh *em, BMOperator *bmop, wmOperator *op, const char *
 		va_end(list);
 		return false;
 	}
-	
-	if (!em->emcopy)
+
+	if (!em->emcopy) {
 		em->emcopy = BKE_editmesh_copy(em);
+	}
 	em->emcopyusers++;
 
 	va_end(list);
@@ -185,12 +147,11 @@ bool EDBM_op_init(BMEditMesh *em, BMOperator *bmop, wmOperator *op, const char *
 	return true;
 }
 
-
 /* returns 0 on error, 1 on success.  executes and finishes a bmesh operator */
 bool EDBM_op_finish(BMEditMesh *em, BMOperator *bmop, wmOperator *op, const bool do_report)
 {
 	const char *errmsg;
-	
+
 	BMO_op_finish(em->bm, bmop);
 
 	if (BMO_error_get(em->bm, &errmsg, NULL)) {
@@ -245,8 +206,9 @@ bool EDBM_op_callf(BMEditMesh *em, wmOperator *op, const char *fmt, ...)
 		return false;
 	}
 
-	if (!em->emcopy)
+	if (!em->emcopy) {
 		em->emcopy = BKE_editmesh_copy(em);
+	}
 	em->emcopyusers++;
 
 	BMO_op_exec(bm, &bmop);
@@ -255,9 +217,10 @@ bool EDBM_op_callf(BMEditMesh *em, wmOperator *op, const char *fmt, ...)
 	return EDBM_op_finish(em, &bmop, op, true);
 }
 
-bool EDBM_op_call_and_selectf(BMEditMesh *em, wmOperator *op,
-                              const char *select_slot_out, const bool select_extend,
-                              const char *fmt, ...)
+bool EDBM_op_call_and_selectf(
+        BMEditMesh *em, wmOperator *op,
+        const char *select_slot_out, const bool select_extend,
+        const char *fmt, ...)
 {
 	BMOpSlot *slot_select_out;
 	BMesh *bm = em->bm;
@@ -273,8 +236,9 @@ bool EDBM_op_call_and_selectf(BMEditMesh *em, wmOperator *op,
 		return false;
 	}
 
-	if (!em->emcopy)
+	if (!em->emcopy) {
 		em->emcopy = BKE_editmesh_copy(em);
+	}
 	em->emcopyusers++;
 
 	BMO_op_exec(bm, &bmop);
@@ -306,8 +270,9 @@ bool EDBM_op_call_silentf(BMEditMesh *em, const char *fmt, ...)
 		return false;
 	}
 
-	if (!em->emcopy)
+	if (!em->emcopy) {
 		em->emcopy = BKE_editmesh_copy(em);
+	}
 	em->emcopyusers++;
 
 	BMO_op_exec(bm, &bmop);
@@ -316,20 +281,13 @@ bool EDBM_op_call_silentf(BMEditMesh *em, const char *fmt, ...)
 	return EDBM_op_finish(em, &bmop, NULL, false);
 }
 
-void EDBM_selectmode_to_scene(bContext *C)
-{
-	Scene *scene = CTX_data_scene(C);
-	Object *obedit = CTX_data_edit_object(C);
-	BMEditMesh *em = BKE_editmesh_from_object(obedit);
-
-	if (!em)
-		return;
+/** \} */
 
-	scene->toolsettings->selectmode = em->selectmode;
-
-	/* Request redraw of header buttons (to show new select mode) */
-	WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, scene);
-}
+/* -------------------------------------------------------------------- */
+/** \name Edit BMesh API
+ *
+ * Make/Clear/Free functions.
+ * \{ */
 
 void EDBM_mesh_make(Object *ob, const int select_mode, const bool add_key_index)
 {
@@ -413,6 +371,22 @@ void EDBM_mesh_load(Object *ob)
 #endif
 }
 
+void EDBM_mesh_clear(BMEditMesh *em)
+{
+	/* clear bmesh */
+	BM_mesh_clear(em->bm);
+
+	/* free derived meshes */
+	BKE_editmesh_free_derivedmesh(em);
+
+	/* free tessellation data */
+	em->tottri = 0;
+	if (em->looptris) {
+		MEM_freeN(em->looptris);
+		em->looptris = NULL;
+	}
+}
+
 /**
  * Should only be called on the active editmesh, otherwise call #BKE_editmesh_free
  */
@@ -427,6 +401,28 @@ void EDBM_mesh_free(BMEditMesh *em)
 	BKE_editmesh_free(em);
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Selection Utilities
+ * \{ */
+
+void EDBM_selectmode_to_scene(bContext *C)
+{
+	Scene *scene = CTX_data_scene(C);
+	Object *obedit = CTX_data_edit_object(C);
+	BMEditMesh *em = BKE_editmesh_from_object(obedit);
+
+	if (!em) {
+		return;
+	}
+
+	scene->toolsettings->selectmode = em->selectmode;
+
+	/* Request redraw of header buttons (to show new select mode) */
+	WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, scene);
+}
+
 void EDBM_selectmode_flush_ex(BMEditMesh *em, const short selectmode)
 {
 	BM_mesh_select_mode_flush_ex(em->bm, selectmode);
@@ -444,7 +440,6 @@ void EDBM_deselect_flush(BMEditMesh *em)
 	BM_mesh_deselect_flush(em->bm);
 }
 
-
 void EDBM_select_flush(BMEditMesh *em)
 {
 	/* function below doesnt use. just do this to keep the values in sync */
@@ -457,9 +452,10 @@ void EDBM_select_more(BMEditMesh *em, const bool use_face_step)
 	BMOperator bmop;
 	const bool use_faces = (em->selectmode == SCE_SELECT_FACE);
 
-	BMO_op_initf(em->bm, &bmop, BMO_FLAG_DEFAULTS,
-	             "region_extend geom=%hvef use_contract=%b use_faces=%b use_face_step=%b",
-	             BM_ELEM_SELECT, false, use_faces, use_face_step);
+	BMO_op_initf(
+	        em->bm, &bmop, BMO_FLAG_DEFAULTS,
+	        "region_extend geom=%hvef use_contract=%b use_faces=%b use_face_step=%b",
+	        BM_ELEM_SELECT, false, use_faces, use_face_step);
 	BMO_op_exec(em->bm, &bmop);
 	/* don't flush selection in edge/vertex mode  */
 	BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "geom.out", BM_ALL_NOLOOP, BM_ELEM_SELECT, use_faces ? true : false);
@@ -473,9 +469,10 @@ void EDBM_select_less(BMEditMesh *em, const bool use_face_step)
 	BMOperator bmop;
 	const bool use_faces = (em->selectmode == SCE_SELECT_FACE);
 
-	BMO_op_initf(em->bm, &bmop, BMO_FLAG_DEFAULTS,
-	             "region_extend geom=%hvef use_contract=%b use_faces=%b use_face_step=%b",
-	             BM_ELEM_SELECT, true, use_faces, use_face_step);
+	BMO_op_initf(
+	        em->bm, &bmop, BMO_FLAG_DEFAULTS,
+	        "region_extend geom=%hvef use_contract=%b use_faces=%b use_face_step=%b",
+	        BM_ELEM_SELECT, true, use_faces, use_face_step);
 	BMO_op_exec(em->bm, &bmop);
 	/* don't flush selection in edge/vertex mode  */
 	BMO_slot_buffer_hflag_disable(em->bm, bmop.slots_out, "geom.out", BM_ALL_NOLOOP, BM_ELEM_SELECT, use_faces ? true : false);
@@ -497,6 +494,12 @@ void EDBM_flag_enable_all(BMEditMesh *em, const char hflag)
 	BM_mesh_elem_hflag_enable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, hflag, true);
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name UV Vertex Map API
+ * \{ */
+
 /**
  * Return a new UVVertMap from the editmesh
  */
@@ -519,7 +522,7 @@ UvVertMap *BM_uv_vert_map_create(
 	BLI_buffer_declare_static(vec2f, tf_uv_buf, BLI_BUFFER_NOP, BM_DEFAULT_NGON_STACK_SIZE);
 
 	BM_mesh_elem_index_ensure(bm, BM_VERT | BM_FACE);
-	
+
 	totfaces = bm->totface;
 	totverts = bm->totvert;
 	totuv = 0;
@@ -549,7 +552,7 @@ UvVertMap *BM_uv_vert_map_create(
 		BKE_mesh_uv_vert_map_free(vmap);
 		return NULL;
 	}
-	
+
 	BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, a) {
 		if ((use_select == false) || BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
 			float (*tf_uv)[2];
@@ -562,7 +565,7 @@ UvVertMap *BM_uv_vert_map_create(
 				buf->tfindex = i;
 				buf->f = a;
 				buf->separate = 0;
-				
+
 				buf->next = vmap->vert[BM_elem_index_get(l->v)];
 				vmap->vert[BM_elem_index_get(l->v)] = buf;
 				buf++;
@@ -578,7 +581,7 @@ UvVertMap *BM_uv_vert_map_create(
 			}
 		}
 	}
-	
+
 	/* sort individual uvs for each vert */
 	BM_ITER_MESH_INDEX (ev, &iter, bm, BM_VERTS_OF_MESH, a) {
 		UvMapVert *newvlist = NULL, *vlist = vmap->vert[a];
@@ -592,22 +595,21 @@ UvVertMap *BM_uv_vert_map_create(
 			newvlist = v;
 
 			efa = BM_face_at_index(bm, v->f);
-			
+
 			l = BM_iter_at_index(bm, BM_LOOPS_OF_FACE, efa, v->tfindex);
 			luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
 			uv = luv->uv;
-			
+
 			lastv = NULL;
 			iterv = vlist;
 
 			while (iterv) {
 				next = iterv->next;
 				efa = BM_face_at_index(bm, iterv->f);
-				
 				l = BM_iter_at_index(bm, BM_LOOPS_OF_FACE, efa, iterv->tfindex);
 				luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
 				uv2 = luv->uv;
-				
+
 				sub_v2_v2v2(uvdiff, uv2, uv);
 
 				if (fabsf(uvdiff[0]) < limit[0] && fabsf(uvdiff[1]) < limit[1] &&
@@ -640,13 +642,11 @@ UvVertMap *BM_uv_vert_map_create(
 	return vmap;
 }
 
-
 UvMapVert *BM_uv_vert_map_at_index(UvVertMap *vmap, unsigned int v)
 {
 	return vmap->vert[v];
 }
 
-
 /* A specialized vert map used by stitch operator */
 UvElementMap *BM_uv_element_map_create(
         BMesh *bm,
@@ -904,23 +904,30 @@ void BM_uv_element_map_free(UvElementMap *element_map)
 
 UvElement *BM_uv_element_get(UvElementMap *map, BMFace *efa, BMLoop *l)
 {
-	UvElement *element;
-
-	element = map->vert[BM_elem_index_get(l->v)];
-
-	for (; element; element = element->next)
-		if (element->l->f == efa)
+	for (UvElement *element = map->vert[BM_elem_index_get(l->v)];
+	     element;
+	     element = element->next)
+	{
+		if (element->l->f == efa) {
 			return element;
+		}
+	}
 
 	return NULL;
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Data Layer Checks
+ * \{ */
+
 /* last_sel, use em->act_face otherwise get the last selected face in the editselections
  * at the moment, last_sel is mainly useful for making sure the space image dosnt flicker */
 BMFace *EDBM_uv_active_face_get(BMEditMesh *em, const bool sloppy, const bool selected)
 {
 	BMFace *efa = NULL;
-	
+
 	if (!EDBM_uv_check(em)) {
 		return NULL;
 	}
@@ -948,6 +955,12 @@ bool EDBM_vert_color_check(BMEditMesh *em)
 	return em && em->bm->totface && CustomData_has_layer(&em->bm->ldata, CD_MLOOPCOL);
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Mirror Cache API
+ * \{ */
+
 static BMVert *cache_mirr_intptr_as_bmvert(intptr_t *index_lookup, int index)
 {
 	intptr_t eve_i = index_lookup[index];
@@ -982,9 +995,10 @@ static BMVert *cache_mirr_intptr_as_bmvert(intptr_t *index_lookup, int index)
  * \param maxdist  Distance for close point test.
  * \param r_index  Optional array to write into, as an alternative to a customdata layer (length of total verts).
  */
-void EDBM_verts_mirror_cache_begin_ex(BMEditMesh *em, const int axis, const bool use_self, const bool use_select,
-                                      /* extra args */
-                                      const bool use_topology, float maxdist, int *r_index)
+void EDBM_verts_mirror_cache_begin_ex(
+        BMEditMesh *em, const int axis, const bool use_self, const bool use_select,
+        /* extra args */
+        const bool use_topology, float maxdist, int *r_index)
 {
 	Mesh *me = (Mesh *)em->ob->data;
 	BMesh *bm = em->bm;
@@ -1008,8 +1022,9 @@ void EDBM_verts_mirror_cache_begin_ex(BMEditMesh *em, const int axis, const bool
 			em->mirror_cdlayer = CustomData_get_named_layer_index(&bm->vdata, CD_PROP_INT, layer_id);
 		}
 
-		cd_vmirr_offset = CustomData_get_n_offset(&bm->vdata, CD_PROP_INT,
-		                                          em->mirror_cdlayer - CustomData_get_layer_index(&bm->vdata, CD_PROP_INT));
+		cd_vmirr_offset = CustomData_get_n_offset(
+		        &bm->vdata, CD_PROP_INT,
+		        em->mirror_cdlayer - CustomData_get_layer_index(&bm->vdata, CD_PROP_INT));
 
 		bm->vdata.layers[em->mirror_cdlayer].flag |= CD_FLAG_TEMPORARY;
 	}
@@ -1082,14 +1097,16 @@ void EDBM_verts_mirror_cache_begin_ex(BMEditMesh *em, const int axis, const bool
 	}
 }
 
-void EDBM_verts_mirror_cache_begin(BMEditMesh *em, const int axis,
-                                   const bool use_self, const bool use_select,
-                                   const bool use_topology)
+void EDBM_verts_mirror_cache_begin(
+        BMEditMesh *em, const int axis,
+        const bool use_self, const bool use_select,
+        const bool use_topology)
 {
-	EDBM_verts_mirror_cache_begin_ex(em, axis,
-	                                 use_self, use_select,
-	                                 /* extra args */
-	                                 use_topology, BM_SEARCH_MAXDIST_MIRR, NULL);
+	EDBM_verts_mirror_cache_begin_ex(
+	        em, axis,
+	        use_self, use_select,
+	        /* extra args */
+	        use_topology, BM_SEARCH_MAXDIST_MIRR, NULL);
 }
 
 BMVert *EDBM_verts_mirror_get(BMEditMesh *em, BMVert *v)
@@ -1177,6 +1194,11 @@ void EDBM_verts_mirror_apply(BMEditMesh *em, const int sel_from, const int sel_t
 	}
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Hide/Reveal API
+ * \{ */
 
 /* swap is 0 or 1, if 1 it hides not selected */
 void EDBM_mesh_hide(BMEditMesh *em, bool swap)
@@ -1211,17 +1233,18 @@ void EDBM_mesh_hide(BMEditMesh *em, bool swap)
 	 */
 }
 
-
 void EDBM_mesh_reveal(BMEditMesh *em, bool select)
 {
-	const char iter_types[3] = {BM_VERTS_OF_MESH,
-	                            BM_EDGES_OF_MESH,
-	                            BM_FACES_OF_MESH};
+	const char iter_types[3] = {
+		BM_VERTS_OF_MESH,
+		BM_EDGES_OF_MESH,
+		BM_FACES_OF_MESH,
+	};
 
 	const bool sels[3] = {
-	    (em->selectmode & SCE_SELECT_VERTEX) != 0,
-	    (em->selectmode & SCE_SELECT_EDGE) != 0,
-	    (em->selectmode & SCE_SELECT_FACE) != 0,
+		(em->selectmode & SCE_SELECT_VERTEX) != 0,
+		(em->selectmode & SCE_SELECT_EDGE) != 0,
+		(em->selectmode & SCE_SELECT_FACE) != 0,
 	};
 	int i;
 
@@ -1261,6 +1284,46 @@ void EDBM_mesh_reveal(BMEditMesh *em, bool select)
 	EDBM_mesh_normals_update(em);
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Update API
+ * \{ */
+
+void EDBM_mesh_normals_update(BMEditMesh *em)
+{
+	BM_mesh_normals_update(em->bm);
+}
+
+void EDBM_stats_update(BMEditMesh *em)
+{
+	const char iter_types[3] = {
+		BM_VERTS_OF_MESH,
+		BM_EDGES_OF_MESH,
+		BM_FACES_OF_MESH,
+	};
+
+	BMIter iter;
+	BMElem *ele;
+	int *tots[3];
+	int i;
+
+	tots[0] = &em->bm->totvertsel;
+	tots[1] = &em->bm->totedgesel;
+	tots[2] = &em->bm->totfacesel;
+
+	em->bm->totvertsel = em->bm->totedgesel = em->bm->totfacesel = 0;
+
+	for (i = 0; i < 3; i++) {
+		ele = BM_iter_new(&iter, em->bm, iter_types[i], NULL);
+		for ( ; ele; ele = BM_iter_step(&iter)) {
+			if (BM_elem_flag_test(ele, BM_ELEM_SELECT)) {
+				(*tots[i])++;
+			}
+		}
+	}
+}
+
 /* so many tools call these that we better make it a generic function.
  */
 void EDBM_update_generic(BMEditMesh *em, const bool do_tessface, const bool is_destructive)
@@ -1296,15 +1359,41 @@ void EDBM_update_generic(BMEditMesh *em, const bool do_tessface, const bool is_d
 #endif
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Data Access
+ * \{ */
+
+DerivedMesh *EDBM_mesh_deform_dm_get(BMEditMesh *em)
+{
+	return ((em->derivedFinal != NULL) &&
+	        (em->derivedFinal->type == DM_TYPE_EDITBMESH) &&
+	        (em->derivedFinal->deformedOnly != false)) ? em->derivedFinal : NULL;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Operator Helpers
+ * \{ */
+
 /* poll call for mesh operators requiring a view3d context */
 int EDBM_view3d_poll(bContext *C)
 {
-	if (ED_operator_editmesh(C) && ED_operator_view3d_active(C))
+	if (ED_operator_editmesh(C) && ED_operator_view3d_active(C)) {
 		return 1;
+	}
 
 	return 0;
 }
 
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name BMesh Element API
+ * \{ */
+
 BMElem *EDBM_elem_from_selectmode(BMEditMesh *em, BMVert *eve, BMEdge *eed, BMFace *efa)
 {
 	BMElem *ele = NULL;
@@ -1369,22 +1458,19 @@ BMElem *EDBM_elem_from_index_any(BMEditMesh *em, int index)
 	return NULL;
 }
 
-/* -------------------------------------------------------------------- */
-/* BMBVH functions */
-// XXX
-#if 0 //BMESH_TODO: not implemented yet
-int BMBVH_VertVisible(BMBVHTree *tree, BMEdge *e, RegionView3D *r3d)
-{
+/** \} */
 
-}
-#endif
+/* -------------------------------------------------------------------- */
+/** \name BMesh BVH API
+ * \{ */
 
 static BMFace *edge_ray_cast(struct BMBVHTree *tree, const float co[3], const float dir[3], float *r_hitout, BMEdge *e)
 {
 	BMFace *f = BKE_bmbvh_ray_cast(tree, co, dir, 0.0f, NULL, r_hitout, NULL);
 
-	if (f && BM_edge_in_face(e, f))
+	if (f && BM_edge_in_face(e, f)) {
 		return NULL;
+	}
 
 	return f;
 }
@@ -1405,8 +1491,10 @@ bool BMBVH_EdgeVisible(struct BMBVHTree *tree, BMEdge *e,
 	float origin[3], invmat[4][4];
 	float epsilon = 0.01f;
 	float end[3];
-	const float mval_f[2] = {ar->winx / 2.0f,
-	                         ar->winy / 2.0f};
+	const float mval_f[2] = {
+		ar->winx / 2.0f,
+		ar->winy / 2.0f,
+	};
 
 	ED_view3d_win_to_segment(depsgraph, ar, v3d, mval_f, origin, end, false);
 
@@ -1442,12 +1530,17 @@ bool BMBVH_EdgeVisible(struct BMBVHTree *tree, BMEdge *e,
 
 	/* do three samplings: left, middle, right */
 	f = edge_ray_cast(tree, co1, dir1, NULL, e);
-	if (f && !edge_ray_cast(tree, co2, dir2, NULL, e))
+	if (f && !edge_ray_cast(tree, co2, dir2, NULL, e)) {
 		return true;
-	else if (f && !edge_ray_cast(tree, co3, dir3, NULL, e))
+	}
+	else if (f && !edge_ray_cast(tree, co3, dir3, NULL, e)) {
 		return true;
-	else if (!f)
+	}
+	else if (!f) {
 		return true;
+	}
 
 	return false;
 }
+
+/** \} */
diff --git a/source/blender/editors/mesh/mesh_mirror.c b/source/blender/editors/mesh/mesh_mirror.c
new file mode 100644
index 0000000000000000000000000000000000000000..22bfd8eedeaafc29c976958c6b2d5b1bde2f22bf
--- /dev/null
+++ b/source/blender/editors/mesh/mesh_mirror.c
@@ -0,0 +1,377 @@
+/*
+ * ***** 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.
+ *
+ * Contributor(s): Blender Foundation, Campbell Barton
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/mesh/mesh_mirror.c
+ *  \ingroup edmesh
+ *
+ * Mirror calculation for edit-mode and object mode.
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+#include "BLI_bitmap.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_DerivedMesh.h"
+#include "BLI_kdtree.h"
+#include "BKE_editmesh.h"
+
+#include "ED_mesh.h"
+
+/* -------------------------------------------------------------------- */
+/** \name Mesh Spatial Mirror API
+ * \{ */
+
+#define KD_THRESH      0.00002f
+
+static struct { void *tree; } MirrKdStore = {NULL};
+
+/* mode is 's' start, or 'e' end, or 'u' use */
+/* if end, ob can be NULL */
+int ED_mesh_mirror_spatial_table(Object *ob, BMEditMesh *em, DerivedMesh *dm, const float co[3], char mode)
+{
+	if (mode == 'u') {        /* use table */
+		if (MirrKdStore.tree == NULL)
+			ED_mesh_mirror_spatial_table(ob, em, dm, NULL, 's');
+
+		if (MirrKdStore.tree) {
+			KDTreeNearest nearest;
+			const int i = BLI_kdtree_find_nearest(MirrKdStore.tree, co, &nearest);
+
+			if (i != -1) {
+				if (nearest.dist < KD_THRESH) {
+					return i;
+				}
+			}
+		}
+		return -1;
+	}
+	else if (mode == 's') {   /* start table */
+		Mesh *me = ob->data;
+		const bool use_em = (!dm && em && me->edit_btmesh == em);
+		const int totvert = use_em ? em->bm->totvert : dm ? dm->getNumVerts(dm) : me->totvert;
+
+		if (MirrKdStore.tree) /* happens when entering this call without ending it */
+			ED_mesh_mirror_spatial_table(ob, em, dm, co, 'e');
+
+		MirrKdStore.tree = BLI_kdtree_new(totvert);
+
+		if (use_em) {
+			BMVert *eve;
+			BMIter iter;
+			int i;
+
+			/* this needs to be valid for index lookups later (callers need) */
+			BM_mesh_elem_table_ensure(em->bm, BM_VERT);
+
+			BM_ITER_MESH_INDEX (eve, &iter, em->bm, BM_VERTS_OF_MESH, i) {
+				BLI_kdtree_insert(MirrKdStore.tree, i, eve->co);
+			}
+		}
+		else {
+			MVert *mvert = dm ? dm->getVertArray(dm) : me->mvert;
+			int i;
+			
+			for (i = 0; i < totvert; i++, mvert++) {
+				BLI_kdtree_insert(MirrKdStore.tree, i, mvert->co);
+			}
+		}
+
+		BLI_kdtree_balance(MirrKdStore.tree);
+	}
+	else if (mode == 'e') { /* end table */
+		if (MirrKdStore.tree) {
+			BLI_kdtree_free(MirrKdStore.tree);
+			MirrKdStore.tree = NULL;
+		}
+	}
+	else {
+		BLI_assert(0);
+	}
+
+	return 0;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Mesh Topology Mirror API
+ * \{ */
+
+typedef unsigned int MirrTopoHash_t;
+
+typedef struct MirrTopoVert_t {
+	MirrTopoHash_t hash;
+	int v_index;
+} MirrTopoVert_t;
+
+static int mirrtopo_hash_sort(const void *l1, const void *l2)
+{
+	if      ((MirrTopoHash_t)(intptr_t)l1 > (MirrTopoHash_t)(intptr_t)l2) return 1;
+	else if ((MirrTopoHash_t)(intptr_t)l1 < (MirrTopoHash_t)(intptr_t)l2) return -1;
+	return 0;
+}
+
+static int mirrtopo_vert_sort(const void *v1, const void *v2)
+{
+	if      (((MirrTopoVert_t *)v1)->hash > ((MirrTopoVert_t *)v2)->hash) return 1;
+	else if (((MirrTopoVert_t *)v1)->hash < ((MirrTopoVert_t *)v2)->hash) return -1;
+	return 0;
+}
+
+bool ED_mesh_mirrtopo_recalc_check(Mesh *me, DerivedMesh *dm, MirrTopoStore_t *mesh_topo_store)
+{
+	const bool is_editmode = (me->edit_btmesh != NULL);
+	int totvert;
+	int totedge;
+
+	if (dm) {
+		totvert = dm->getNumVerts(dm);
+		totedge = dm->getNumEdges(dm);
+	}
+	else if (me->edit_btmesh) {
+		totvert = me->edit_btmesh->bm->totvert;
+		totedge = me->edit_btmesh->bm->totedge;
+	}
+	else {
+		totvert = me->totvert;
+		totedge = me->totedge;
+	}
+
+	if ((mesh_topo_store->index_lookup == NULL) ||
+	    (mesh_topo_store->prev_is_editmode != is_editmode) ||
+	    (totvert != mesh_topo_store->prev_vert_tot) ||
+	    (totedge != mesh_topo_store->prev_edge_tot))
+	{
+		return true;
+	}
+	else {
+		return false;
+	}
+
+}
+
+void ED_mesh_mirrtopo_init(
+        Mesh *me, DerivedMesh *dm, MirrTopoStore_t *mesh_topo_store,
+        const bool skip_em_vert_array_init)
+{
+	const bool is_editmode = (me->edit_btmesh != NULL);
+	MEdge *medge = NULL, *med;
+	BMEditMesh *em = dm ?  NULL : me->edit_btmesh;
+
+	/* editmode*/
+	BMEdge *eed;
+	BMIter iter;
+
+	int a, last;
+	int totvert, totedge;
+	int tot_unique = -1, tot_unique_prev = -1;
+	int tot_unique_edges = 0, tot_unique_edges_prev;
+
+	MirrTopoHash_t *topo_hash = NULL;
+	MirrTopoHash_t *topo_hash_prev = NULL;
+	MirrTopoVert_t *topo_pairs;
+	MirrTopoHash_t  topo_pass = 1;
+
+	intptr_t *index_lookup; /* direct access to mesh_topo_store->index_lookup */
+
+	/* reallocate if needed */
+	ED_mesh_mirrtopo_free(mesh_topo_store);
+
+	mesh_topo_store->prev_is_editmode = is_editmode;
+
+	if (em) {
+		BM_mesh_elem_index_ensure(em->bm, BM_VERT);
+
+		totvert = em->bm->totvert;
+	}
+	else {
+		totvert = dm ? dm->getNumVerts(dm) : me->totvert;
+	}
+
+	topo_hash = MEM_callocN(totvert * sizeof(MirrTopoHash_t), "TopoMirr");
+
+	/* Initialize the vert-edge-user counts used to detect unique topology */
+	if (em) {
+		totedge = me->edit_btmesh->bm->totedge;
+
+		BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) {
+			const int i1 = BM_elem_index_get(eed->v1), i2 = BM_elem_index_get(eed->v2);
+			topo_hash[i1]++;
+			topo_hash[i2]++;
+		}
+	}
+	else {
+		totedge = dm ? dm->getNumEdges(dm) : me->totedge;
+		medge = dm ? dm->getEdgeArray(dm) : me->medge;
+
+		for (a = 0, med = medge; a < totedge; a++, med++) {
+			const unsigned int i1 = med->v1, i2 = med->v2;
+			topo_hash[i1]++;
+			topo_hash[i2]++;
+		}
+	}
+
+	topo_hash_prev = MEM_dupallocN(topo_hash);
+
+	tot_unique_prev = -1;
+	tot_unique_edges_prev = -1;
+	while (1) {
+		/* use the number of edges per vert to give verts unique topology IDs */
+
+		tot_unique_edges = 0;
+
+		/* This can make really big numbers, wrapping around here is fine */
+		if (em) {
+			BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) {
+				const int i1 = BM_elem_index_get(eed->v1), i2 = BM_elem_index_get(eed->v2);
+				topo_hash[i1] += topo_hash_prev[i2] * topo_pass;
+				topo_hash[i2] += topo_hash_prev[i1] * topo_pass;
+				tot_unique_edges += (topo_hash[i1] != topo_hash[i2]);
+			}
+		}
+		else {
+			for (a = 0, med = medge; a < totedge; a++, med++) {
+				const unsigned int i1 = med->v1, i2 = med->v2;
+				topo_hash[i1] += topo_hash_prev[i2] * topo_pass;
+				topo_hash[i2] += topo_hash_prev[i1] * topo_pass;
+				tot_unique_edges += (topo_hash[i1] != topo_hash[i2]);
+			}
+		}
+		memcpy(topo_hash_prev, topo_hash, sizeof(MirrTopoHash_t) * totvert);
+
+		/* sort so we can count unique values */
+		qsort(topo_hash_prev, totvert, sizeof(MirrTopoHash_t), mirrtopo_hash_sort);
+
+		tot_unique = 1; /* account for skiping the first value */
+		for (a = 1; a < totvert; a++) {
+			if (topo_hash_prev[a - 1] != topo_hash_prev[a]) {
+				tot_unique++;
+			}
+		}
+
+		if ((tot_unique <= tot_unique_prev) && (tot_unique_edges <= tot_unique_edges_prev)) {
+			/* Finish searching for unique values when 1 loop dosnt give a
+			 * higher number of unique values compared to the previous loop */
+			break;
+		}
+		else {
+			tot_unique_prev = tot_unique;
+			tot_unique_edges_prev = tot_unique_edges;
+		}
+		/* Copy the hash calculated this iter, so we can use them next time */
+		memcpy(topo_hash_prev, topo_hash, sizeof(MirrTopoHash_t) * totvert);
+
+		topo_pass++;
+	}
+
+	/* Hash/Index pairs are needed for sorting to find index pairs */
+	topo_pairs = MEM_callocN(sizeof(MirrTopoVert_t) * totvert, "MirrTopoPairs");
+
+	/* since we are looping through verts, initialize these values here too */
+	index_lookup = MEM_mallocN(totvert * sizeof(*index_lookup), "mesh_topo_lookup");
+
+	if (em) {
+		if (skip_em_vert_array_init == false) {
+			BM_mesh_elem_table_ensure(em->bm, BM_VERT);
+		}
+	}
+
+	for (a = 0; a < totvert; a++) {
+		topo_pairs[a].hash    = topo_hash[a];
+		topo_pairs[a].v_index = a;
+
+		/* initialize lookup */
+		index_lookup[a] = -1;
+	}
+
+	qsort(topo_pairs, totvert, sizeof(MirrTopoVert_t), mirrtopo_vert_sort);
+
+	last = 0;
+
+	/* Get the pairs out of the sorted hashes, note, totvert+1 means we can use the previous 2,
+	 * but you cant ever access the last 'a' index of MirrTopoPairs */
+	if (em) {
+		BMVert **vtable = em->bm->vtable;
+		for (a = 1; a <= totvert; a++) {
+			/* printf("I %d %ld %d\n", (a - last), MirrTopoPairs[a].hash, MirrTopoPairs[a].v_indexs); */
+			if ((a == totvert) || (topo_pairs[a - 1].hash != topo_pairs[a].hash)) {
+				const int match_count = a - last;
+				if (match_count == 2) {
+					const int j = topo_pairs[a - 1].v_index, k = topo_pairs[a - 2].v_index;
+					index_lookup[j] = (intptr_t)vtable[k];
+					index_lookup[k] = (intptr_t)vtable[j];
+				}
+				else if (match_count == 1) {
+					/* Center vertex. */
+					const int j = topo_pairs[a - 1].v_index;
+					index_lookup[j] = (intptr_t)vtable[j];
+				}
+				last = a;
+			}
+		}
+	}
+	else {
+		/* same as above, for mesh */
+		for (a = 1; a <= totvert; a++) {
+			if ((a == totvert) || (topo_pairs[a - 1].hash != topo_pairs[a].hash)) {
+				const int match_count = a - last;
+				if (match_count == 2) {
+					const int j = topo_pairs[a - 1].v_index, k = topo_pairs[a - 2].v_index;
+					index_lookup[j] = k;
+					index_lookup[k] = j;
+				}
+				else if (match_count == 1) {
+					/* Center vertex. */
+					const int j = topo_pairs[a - 1].v_index;
+					index_lookup[j] = j;
+				}
+				last = a;
+			}
+		}
+	}
+
+	MEM_freeN(topo_pairs);
+	topo_pairs = NULL;
+
+	MEM_freeN(topo_hash);
+	MEM_freeN(topo_hash_prev);
+
+	mesh_topo_store->index_lookup  = index_lookup;
+	mesh_topo_store->prev_vert_tot = totvert;
+	mesh_topo_store->prev_edge_tot = totedge;
+}
+
+void ED_mesh_mirrtopo_free(MirrTopoStore_t *mesh_topo_store)
+{
+	if (mesh_topo_store->index_lookup) {
+		MEM_freeN(mesh_topo_store->index_lookup);
+	}
+	mesh_topo_store->index_lookup  = NULL;
+	mesh_topo_store->prev_vert_tot = -1;
+	mesh_topo_store->prev_edge_tot = -1;
+}
+
+/** \} */
diff --git a/source/blender/editors/mesh/meshtools.c b/source/blender/editors/mesh/meshtools.c
index da73d7205c3743d9f75d0fa973d285675c6786eb..3cf0f43fe39bd9299b968fca02a2a9f7770225df 100644
--- a/source/blender/editors/mesh/meshtools.c
+++ b/source/blender/editors/mesh/meshtools.c
@@ -47,8 +47,6 @@
 #include "BLI_math.h"
 #include "BLI_blenlib.h"
 
-
-#include "BLI_kdtree.h"
 #include "BKE_context.h"
 #include "BKE_deform.h"
 #include "BKE_DerivedMesh.h"
@@ -563,12 +561,10 @@ int join_mesh_exec(bContext *C, wmOperator *op)
 		if (ma)
 			id_us_min(&ma->id);
 	}
-	if (ob->mat) MEM_freeN(ob->mat);
-	if (ob->matbits) MEM_freeN(ob->matbits);
-	if (me->mat) MEM_freeN(me->mat);
-	ob->mat = me->mat = NULL;
-	ob->matbits = NULL;
-	
+	MEM_SAFE_FREE(ob->mat);
+	MEM_SAFE_FREE(ob->matbits);
+	MEM_SAFE_FREE(me->mat);
+
 	if (totcol) {
 		me->mat = matar;
 		ob->mat = MEM_callocN(sizeof(*ob->mat) * totcol, "join obmatar");
@@ -683,84 +679,6 @@ int join_mesh_shapes_exec(bContext *C, wmOperator *op)
 	return OPERATOR_FINISHED;
 }
 
-/* -------------------------------------------------------------------- */
-/* Mesh Mirror (Spatial) */
-
-/** \name Mesh Spatial Mirror API
- * \{ */
-
-#define KD_THRESH      0.00002f
-
-static struct { void *tree; } MirrKdStore = {NULL};
-
-/* mode is 's' start, or 'e' end, or 'u' use */
-/* if end, ob can be NULL */
-int ED_mesh_mirror_spatial_table(Object *ob, BMEditMesh *em, DerivedMesh *dm, const float co[3], char mode)
-{
-	if (mode == 'u') {        /* use table */
-		if (MirrKdStore.tree == NULL)
-			ED_mesh_mirror_spatial_table(ob, em, dm, NULL, 's');
-
-		if (MirrKdStore.tree) {
-			KDTreeNearest nearest;
-			const int i = BLI_kdtree_find_nearest(MirrKdStore.tree, co, &nearest);
-
-			if (i != -1) {
-				if (nearest.dist < KD_THRESH) {
-					return i;
-				}
-			}
-		}
-		return -1;
-	}
-	else if (mode == 's') {   /* start table */
-		Mesh *me = ob->data;
-		const bool use_em = (!dm && em && me->edit_btmesh == em);
-		const int totvert = use_em ? em->bm->totvert : dm ? dm->getNumVerts(dm) : me->totvert;
-
-		if (MirrKdStore.tree) /* happens when entering this call without ending it */
-			ED_mesh_mirror_spatial_table(ob, em, dm, co, 'e');
-
-		MirrKdStore.tree = BLI_kdtree_new(totvert);
-
-		if (use_em) {
-			BMVert *eve;
-			BMIter iter;
-			int i;
-
-			/* this needs to be valid for index lookups later (callers need) */
-			BM_mesh_elem_table_ensure(em->bm, BM_VERT);
-
-			BM_ITER_MESH_INDEX (eve, &iter, em->bm, BM_VERTS_OF_MESH, i) {
-				BLI_kdtree_insert(MirrKdStore.tree, i, eve->co);
-			}
-		}
-		else {
-			MVert *mvert = dm ? dm->getVertArray(dm) : me->mvert;
-			int i;
-			
-			for (i = 0; i < totvert; i++, mvert++) {
-				BLI_kdtree_insert(MirrKdStore.tree, i, mvert->co);
-			}
-		}
-
-		BLI_kdtree_balance(MirrKdStore.tree);
-	}
-	else if (mode == 'e') { /* end table */
-		if (MirrKdStore.tree) {
-			BLI_kdtree_free(MirrKdStore.tree);
-			MirrKdStore.tree = NULL;
-		}
-	}
-	else {
-		BLI_assert(0);
-	}
-
-	return 0;
-}
-
-/** \} */
-
 
 /* -------------------------------------------------------------------- */
 /* Mesh Mirror (Topology) */
diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c
index 5e7e0fb68a3f5e7beafa78a1d7e22610285aa56b..00c5fdf3cc728ea302b0979bc0199995a0e1bad4 100644
--- a/source/blender/editors/object/object_relations.c
+++ b/source/blender/editors/object/object_relations.c
@@ -1632,33 +1632,30 @@ void OBJECT_OT_make_links_data(wmOperatorType *ot)
 
 static Object *single_object_users_object(Main *bmain, Scene *scene, Object *ob, const bool copy_groups)
 {
-	if (!ID_IS_LINKED(ob) && ob->id.us > 1) {
-		/* base gets copy of object */
-		Object *obn = ID_NEW_SET(ob, BKE_object_copy(bmain, ob));
+	/* base gets copy of object */
+	Object *obn = ID_NEW_SET(ob, BKE_object_copy(bmain, ob));
 
-		if (copy_groups) {
-			if (ob->flag & OB_FROMGROUP) {
-				obn->flag |= OB_FROMGROUP;
-			}
-		}
-		else {
-			/* copy already clears */
+	if (copy_groups) {
+		if (ob->flag & OB_FROMGROUP) {
+			obn->flag |= OB_FROMGROUP;
 		}
-		/* remap gpencil parenting */
+	}
+	else {
+		/* copy already clears */
+	}
+	/* remap gpencil parenting */
 
-		if (scene->gpd) {
-			bGPdata *gpd = scene->gpd;
-			for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
-				if (gpl->parent == ob) {
-					gpl->parent = obn;
-				}
+	if (scene->gpd) {
+		bGPdata *gpd = scene->gpd;
+		for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+			if (gpl->parent == ob) {
+				gpl->parent = obn;
 			}
 		}
-
-		id_us_min(&ob->id);
-		return obn;
 	}
-	return NULL;
+
+	id_us_min(&ob->id);
+	return obn;
 }
 
 static void libblock_relink_scene_collection(SceneCollection *sc)
@@ -1678,7 +1675,9 @@ static void single_object_users_scene_collection(Main *bmain, Scene *scene, Scen
 		Object *ob = link->data;
 		/* an object may be in more than one collection */
 		if ((ob->id.newid == NULL) && ((ob->flag & flag) == flag)) {
-			link->data = single_object_users_object(bmain, scene, link->data, copy_groups);
+			if (!ID_IS_LINKED(ob) && ob->id.us > 1) {
+				link->data = single_object_users_object(bmain, scene, link->data, copy_groups);
+			}
 		}
 	}
 
diff --git a/source/blender/editors/object/object_transform.c b/source/blender/editors/object/object_transform.c
index 033f9d190b7f6b2365db5042d297fb3e2b71f12a..7ea1a04f31f271e33668ad40c6de5379194872ce 100644
--- a/source/blender/editors/object/object_transform.c
+++ b/source/blender/editors/object/object_transform.c
@@ -1343,9 +1343,9 @@ static int object_transform_axis_target_invoke(bContext *C, wmOperator *op, cons
 		CTX_DATA_END;
 
 		xfd->object_data = object_data;
-		xfd->object_data_len = BLI_array_count(object_data);
+		xfd->object_data_len = BLI_array_len(object_data);
 
-		if (xfd->object_data_len != BLI_array_count(object_data)) {
+		if (xfd->object_data_len != BLI_array_len(object_data)) {
 			xfd->object_data = MEM_reallocN(xfd->object_data, xfd->object_data_len * sizeof(*xfd->object_data));
 		}
 	}
diff --git a/source/blender/editors/object/object_vgroup.c b/source/blender/editors/object/object_vgroup.c
index 00a24e7fe7257de67e6cf00898733394629afe4a..eb04de5feb271695f495fb6065528ea897bf86ce 100644
--- a/source/blender/editors/object/object_vgroup.c
+++ b/source/blender/editors/object/object_vgroup.c
@@ -1192,7 +1192,7 @@ static int *getSurroundingVerts(Mesh *me, int vert, int *count)
 				}
 
 				/* Append a and b verts to array, if not yet present. */
-				k = BLI_array_count(verts);
+				k = BLI_array_len(verts);
 				/* XXX Maybe a == b is enough? */
 				while (k-- && !(a == b && a == -1)) {
 					if (verts[k] == a)
@@ -1214,7 +1214,7 @@ static int *getSurroundingVerts(Mesh *me, int vert, int *count)
 	}
 
 	/* Do not free the array! */
-	*count = BLI_array_count(verts);
+	*count = BLI_array_len(verts);
 	return verts;
 }
 
diff --git a/source/blender/editors/render/render_internal.c b/source/blender/editors/render/render_internal.c
index aba4b7788f86c49792451c3022afa8396606b088..33ca6ea74954500f886f78a4a5b968cf6c24b12d 100644
--- a/source/blender/editors/render/render_internal.c
+++ b/source/blender/editors/render/render_internal.c
@@ -536,10 +536,8 @@ static void render_image_update_pass_and_layer(RenderJob *rj, RenderResult *rr,
 			int layer = BLI_findstringindex(&main_rr->layers,
 			                                (char *)rr->renlay->name,
 			                                offsetof(RenderLayer, name));
-			if (layer != rj->last_layer) {
-				sima->iuser.layer = layer;
-				rj->last_layer = layer;
-			}
+			sima->iuser.layer = layer;
+			rj->last_layer = layer;
 		}
 
 		iuser->pass = sima->iuser.pass;
@@ -637,7 +635,21 @@ static void render_image_restore_layer(RenderJob *rj)
 				if (sa == rj->sa) {
 					if (sa->spacetype == SPACE_IMAGE) {
 						SpaceImage *sima = sa->spacedata.first;
-						sima->iuser.layer = rj->orig_layer;
+
+						if (RE_HasSingleLayer(rj->re)) {
+							/* For single layer renders keep the active layer
+							 * visible, or show the compositing result. */
+							RenderResult *rr = RE_AcquireResultRead(rj->re);
+							if(RE_HasCombinedLayer(rr)) {
+								sima->iuser.layer = 0;
+							}
+							RE_ReleaseResult(rj->re);
+						}
+						else {
+							/* For multiple layer render, set back the layer
+							 * that was set at the start of rendering. */
+							sima->iuser.layer = rj->orig_layer;
+						}
 					}
 					return;
 				}
diff --git a/source/blender/editors/space_node/node_templates.c b/source/blender/editors/space_node/node_templates.c
index c791b9f6eae40d2c69b61b09849bc4db8b35c2de..1047c498e4d975c8afc0d8118cae360759870690 100644
--- a/source/blender/editors/space_node/node_templates.c
+++ b/source/blender/editors/space_node/node_templates.c
@@ -482,10 +482,10 @@ static void ui_node_menu_column(NodeLinkArg *arg, int nclass, const char *cname)
 	}
 	NODE_TYPES_END
 
-	qsort(sorted_ntypes, BLI_array_count(sorted_ntypes), sizeof(bNodeType *), ui_node_item_name_compare);
+	qsort(sorted_ntypes, BLI_array_len(sorted_ntypes), sizeof(bNodeType *), ui_node_item_name_compare);
 
 	/* generate UI */
-	for (int j = 0; j < BLI_array_count(sorted_ntypes); j++) {
+	for (int j = 0; j < BLI_array_len(sorted_ntypes); j++) {
 		bNodeType *ntype = sorted_ntypes[j];
 		NodeLinkItem *items;
 		int totitems;
diff --git a/source/blender/editors/uvedit/uvedit_draw.c b/source/blender/editors/uvedit/uvedit_draw.c
index 9d02c8589b29052b4624a0ad7a754883c36bda82..b22cbf6d1552af6e3a14361d678bd12382089c34 100644
--- a/source/blender/editors/uvedit/uvedit_draw.c
+++ b/source/blender/editors/uvedit/uvedit_draw.c
@@ -60,6 +60,7 @@
 #include "DEG_depsgraph.h"
 #include "DEG_depsgraph_query.h"
 
+#include "GPU_batch.h"
 #include "GPU_immediate.h"
 #include "GPU_immediate_util.h"
 #include "GPU_matrix.h"
@@ -74,7 +75,7 @@
 
 #include "uvedit_intern.h"
 
-static void draw_uvs_lineloop_bmface(BMFace *efa, const int cd_loop_uv_offset, const uint shdr_pos);
+static void draw_uvs_lineloop_bmfaces(BMesh *bm, const int cd_loop_uv_offset, const uint shdr_pos);
 
 void ED_image_draw_cursor(ARegion *ar, const float cursor[2])
 {
@@ -161,8 +162,6 @@ static void draw_uvs_shadow(Object *obedit)
 {
 	BMEditMesh *em = BKE_editmesh_from_object(obedit);
 	BMesh *bm = em->bm;
-	BMFace *efa;
-	BMIter iter;
 
 	const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV);
 
@@ -173,9 +172,7 @@ static void draw_uvs_shadow(Object *obedit)
 	/* draws the mesh when painting */
 	immUniformThemeColor(TH_UV_SHADOW);
 
-	BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
-		draw_uvs_lineloop_bmface(efa, cd_loop_uv_offset, pos);
-	}
+	draw_uvs_lineloop_bmfaces(bm, cd_loop_uv_offset, pos);
 
 	immUnbindProgram();
 }
@@ -385,18 +382,43 @@ static void draw_uvs_stretch(SpaceImage *sima, Scene *scene, Object *obedit, BME
 	BLI_buffer_free(&tf_uvorig_buf);
 }
 
-static void draw_uvs_lineloop_bmface(BMFace *efa, const int cd_loop_uv_offset, const uint shdr_pos)
+static void draw_uvs_lineloop_bmfaces(BMesh *bm, const int cd_loop_uv_offset, const uint shdr_pos)
 {
-	BMIter liter;
+	BMIter iter, liter;
+	BMFace *efa;
 	BMLoop *l;
 	MLoopUV *luv;
 
-	immBegin(GWN_PRIM_LINE_LOOP, efa->len);
+	/* For more efficiency first transfer the entire buffer to vram. */
+	Gwn_Batch *uv_batch = immBeginBatchAtMost(GWN_PRIM_LINE_LOOP, bm->totloop);
+
+	BM_ITER_MESH(efa, &iter, bm, BM_FACES_OF_MESH) {
+		if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
+			continue;
+
+		BM_ITER_ELEM(l, &liter, efa, BM_LOOPS_OF_FACE) {
+			luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
+			immVertex2fv(shdr_pos, luv->uv);
+		}
+	}
+	immEnd();
 
-	BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
-		luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
-		immVertex2fv(shdr_pos, luv->uv);
+	/* Then draw each face contour separately. */
+	GWN_batch_program_use_begin(uv_batch);
+	unsigned int index = 0;
+	BM_ITER_MESH(efa, &iter, bm, BM_FACES_OF_MESH) {
+		if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
+			continue;
+
+		GWN_batch_draw_range_ex(uv_batch, index, efa->len, false);
+		index += efa->len;
 	}
+	GWN_batch_program_use_end(uv_batch);
+
+	GWN_vertbuf_discard(uv_batch->verts[0]);
+	GWN_batch_discard(uv_batch);
+
+	immUnbindProgram();
 
 	immEnd();
 }
@@ -616,7 +638,7 @@ static void draw_uvs(SpaceImage *sima, Scene *scene, ViewLayer *view_layer, Obje
 	BMLoop *l;
 	BMIter iter, liter;
 	MLoopUV *luv;
-	unsigned char col1[4], col2[4];
+	float col1[4], col2[4];
 	float pointsize;
 	int drawfaces, interpedges;
 	Image *ima = sima->image;
@@ -686,14 +708,14 @@ static void draw_uvs(SpaceImage *sima, Scene *scene, ViewLayer *view_layer, Obje
 
 		if (tri_count && !(sima->flag & SI_NO_DRAWFACES)) {
 			/* draw transparent faces */
-			UI_GetThemeColor4ubv(TH_FACE, col1);
-			UI_GetThemeColor4ubv(TH_FACE_SELECT, col2);
+			UI_GetThemeColor4fv(TH_FACE, col1);
+			UI_GetThemeColor4fv(TH_FACE_SELECT, col2);
 			glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
 			glEnable(GL_BLEND);
 
 			Gwn_VertFormat *format = immVertexFormat();
 			pos = GWN_vertformat_attr_add(format, "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
-			color = GWN_vertformat_attr_add(format, "color", GWN_COMP_U8, 4, GWN_FETCH_INT_TO_FLOAT_UNIT);
+			color = GWN_vertformat_attr_add(format, "color", GWN_COMP_F32, 4, GWN_FETCH_FLOAT);
 
 			immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
 
@@ -705,12 +727,12 @@ static void draw_uvs(SpaceImage *sima, Scene *scene, ViewLayer *view_layer, Obje
 
 					if (efa == efa_act) {
 						/* only once */
-						unsigned char tmp_col[4];
-						UI_GetThemeColor4ubv(TH_EDITMESH_ACTIVE, tmp_col);
-						immAttrib4ubv(color, tmp_col);
+						float tmp_col[4];
+						UI_GetThemeColor4fv(TH_EDITMESH_ACTIVE, tmp_col);
+						immAttrib4fv(color, tmp_col);
 					}
 					else {
-						immAttrib4ubv(color, is_select ? col2 : col1);
+						immAttrib4fv(color, is_select ? col2 : col1);
 					}
 
 					draw_uvs_looptri(em, &i, cd_loop_uv_offset, pos);
@@ -740,13 +762,11 @@ static void draw_uvs(SpaceImage *sima, Scene *scene, ViewLayer *view_layer, Obje
 		glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
 	}
 
-	glLineWidth(1);
+	pos = GWN_vertformat_attr_add(immVertexFormat(), "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
 
 	switch (sima->dt_uv) {
 		case SI_UVDT_DASH:
 		{
-			const uint shdr_pos = GWN_vertformat_attr_add(immVertexFormat(), "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
-
 			immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR);
 
 			float viewport_size[4];
@@ -756,141 +776,160 @@ static void draw_uvs(SpaceImage *sima, Scene *scene, ViewLayer *view_layer, Obje
 			immUniform1i("num_colors", 2);  /* "advanced" mode */
 			immUniformArray4fv("colors", (float *)(float[][4]){{0.56f, 0.56f, 0.56f, 1.0f}, {0.07f, 0.07f, 0.07f, 1.0f}}, 2);
 			immUniform1f("dash_width", 4.0f);
-
-			BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
-				if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
-					continue;
-
-				draw_uvs_lineloop_bmface(efa, cd_loop_uv_offset, shdr_pos);
-			}
-
-			immUnbindProgram();
+			glLineWidth(1.0f);
 
 			break;
 		}
 		case SI_UVDT_BLACK: /* black/white */
 		case SI_UVDT_WHITE:
-			pos = GWN_vertformat_attr_add(immVertexFormat(), "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
-
 			immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
-
 			if (sima->dt_uv == SI_UVDT_WHITE) {
 				immUniformColor3f(1.0f, 1.0f, 1.0f);
 			}
 			else {
 				immUniformColor3f(0.0f, 0.0f, 0.0f);
 			}
-
-			BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
-				if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
-					continue;
-
-				draw_uvs_lineloop_bmface(efa, cd_loop_uv_offset, pos);
-			}
-
-			immUnbindProgram();
+			glLineWidth(1.0f);
 
 			break;
 		case SI_UVDT_OUTLINE:
-			pos = GWN_vertformat_attr_add(immVertexFormat(), "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
-
 			immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
-
-			glLineWidth(3);
 			imm_cpack(0x0);
+			glLineWidth(3.0f);
 
-			BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
-				if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
-					continue;
+			break;
+	}
 
-				draw_uvs_lineloop_bmface(efa, cd_loop_uv_offset, pos);
-			}
+	/* For more efficiency first transfer the entire buffer to vram. */
+	Gwn_Batch *uv_batch = immBeginBatchAtMost(GWN_PRIM_LINE_LOOP, bm->totloop);
+	Gwn_VertBuf* uv_vbo = uv_batch->verts[0];
+	BM_ITER_MESH(efa, &iter, bm, BM_FACES_OF_MESH) {
+		if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
+			continue;
 
-			immUnbindProgram();
+		BM_ITER_ELEM(l, &liter, efa, BM_LOOPS_OF_FACE) {
+			luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
+			immVertex2fv(pos, luv->uv);
+		}
+	}
+	immEnd();
 
-			glLineWidth(1);
-			UI_GetThemeColor4ubv(TH_WIRE_EDIT, col2);
+	/* Then draw each face contour separately. */
+	GWN_batch_program_use_begin(uv_batch);
+	unsigned int index = 0, vbo_len_used;
+	BM_ITER_MESH(efa, &iter, bm, BM_FACES_OF_MESH) {
+		if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
+			continue;
 
-			if (me->drawflag & ME_DRAWEDGES) {
-				int sel;
-				UI_GetThemeColor4ubv(TH_EDGE_SELECT, col1);
+		GWN_batch_draw_range_ex(uv_batch, index, efa->len, false);
+		index += efa->len;
+	}
+	vbo_len_used = index;
+	GWN_batch_program_use_end(uv_batch);
+	immUnbindProgram();
 
-				Gwn_VertFormat *format = immVertexFormat();
-				pos = GWN_vertformat_attr_add(format, "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
-				color = GWN_vertformat_attr_add(format, "color", GWN_COMP_U8, 4, GWN_FETCH_INT_TO_FLOAT_UNIT);
 
-				if (interpedges) {
-					immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR);
+	if (sima->dt_uv == SI_UVDT_OUTLINE) {
+		glLineWidth(1.0f);
+		UI_GetThemeColor4fv(TH_WIRE_EDIT, col2);
 
-					BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
-						if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
-							continue;
+		if (me->drawflag & ME_DRAWEDGES) {
+			int sel;
+			UI_GetThemeColor4fv(TH_EDGE_SELECT, col1);
 
-						immBegin(GWN_PRIM_LINE_LOOP, efa->len);
+			if (interpedges) {
+				/* Create a color buffer. */
+				static Gwn_VertFormat format = {0};
+				static uint shdr_col;
+				if (format.attrib_ct == 0) {
+					shdr_col = GWN_vertformat_attr_add(&format, "color", GWN_COMP_F32, 4, GWN_FETCH_FLOAT);
+				}
 
-						BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
-							sel = uvedit_uv_select_test(scene, l, cd_loop_uv_offset);
-							immAttrib4ubv(color, sel ? (GLubyte *)col1 : (GLubyte *)col2);
+				Gwn_VertBuf *vbo_col = GWN_vertbuf_create_with_format(&format);
+				GWN_vertbuf_data_alloc(vbo_col, vbo_len_used);
 
-							luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
-							immVertex2fv(pos, luv->uv);
-						}
+				index = 0;
+				BM_ITER_MESH(efa, &iter, bm, BM_FACES_OF_MESH) {
+					if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
+						continue;
 
-						immEnd();
+					BM_ITER_ELEM(l, &liter, efa, BM_LOOPS_OF_FACE) {
+						sel = uvedit_uv_select_test(scene, l, cd_loop_uv_offset);
+						GWN_vertbuf_attr_set(vbo_col, shdr_col, index++, sel ? col1 : col2);
 					}
-
-					immUnbindProgram();
 				}
-				else {
-					immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
+				/* Reuse the UV buffer and add the color buffer. */
+				GWN_batch_vertbuf_add_ex(uv_batch, vbo_col, true);
 
-					BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
-						int lastsel = -1;
+				/* Now draw each face contour separately with another builtin program. */
+				GWN_batch_program_set_builtin(uv_batch, GPU_SHADER_2D_SMOOTH_COLOR);
+				gpuBindMatrices(uv_batch->interface);
 
-						if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
-							continue;
-
-						immBegin(GWN_PRIM_LINES, efa->len * 2);
-
-						BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
-							sel = uvedit_edge_select_test(scene, l, cd_loop_uv_offset);
-							if (sel != lastsel) {
-								immAttrib4ubv(color, sel ? (GLubyte *)col1 : (GLubyte *)col2);
-								lastsel = sel;
-							}
-
-							luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
-							immVertex2fv(pos, luv->uv);
-							luv = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset);
-							immVertex2fv(pos, luv->uv);
-						}
-
-						immEnd();
-					}
+				GWN_batch_program_use_begin(uv_batch);
+				index = 0;
+				BM_ITER_MESH(efa, &iter, bm, BM_FACES_OF_MESH) {
+					if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
+						continue;
 
-					immUnbindProgram();
+					GWN_batch_draw_range_ex(uv_batch, index, efa->len, false);
+					index += efa->len;
 				}
+				GWN_batch_program_use_end(uv_batch);
 			}
 			else {
-				pos = GWN_vertformat_attr_add(immVertexFormat(), "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
+				Gwn_VertFormat *format = immVertexFormat();
+				pos = GWN_vertformat_attr_add(format, "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
+				color = GWN_vertformat_attr_add(format, "color", GWN_COMP_F32, 4, GWN_FETCH_FLOAT);
 
-				immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
-				immUniformColor4ubv(col2);
+				immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
 
-				/* no nice edges */
+				/* Use batch here to avoid problems with `IMM_BUFFER_SIZE`. */
+				Gwn_Batch *flat_edges_batch = immBeginBatchAtMost(GWN_PRIM_LINES, vbo_len_used * 2);
 				BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
 					if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
 						continue;
-				
-					draw_uvs_lineloop_bmface(efa, cd_loop_uv_offset, pos);
+
+					BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
+						sel = uvedit_edge_select_test(scene, l, cd_loop_uv_offset);
+						immAttrib4fv(color, sel ? col1 : col2);
+
+						luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
+						immVertex2fv(pos, luv->uv);
+						luv = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset);
+						immVertex2fv(pos, luv->uv);
+					}
 				}
+				immEnd();
+
+				GWN_batch_draw(flat_edges_batch);
+				GWN_vertbuf_discard(flat_edges_batch->verts[0]);
+				GWN_batch_discard(flat_edges_batch);
 
 				immUnbindProgram();
 			}
+		}
+		else {
+			GWN_batch_uniform_4fv(uv_batch, "color", col2);
+			immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
 
-			break;
+			/* no nice edges */
+			GWN_batch_program_use_begin(uv_batch);
+			index = 0;
+			BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
+				if (!BM_elem_flag_test(efa, BM_ELEM_TAG))
+					continue;
+
+				GWN_batch_draw_range_ex(uv_batch, index, efa->len, false);
+				index += efa->len;
+			}
+			GWN_batch_program_use_end(uv_batch);
+			immUnbindProgram();
+		}
 	}
 
+	GWN_vertbuf_discard(uv_vbo);
+	GWN_batch_discard(uv_batch);
+
 	if (sima->flag & SI_SMOOTH_UV) {
 		glDisable(GL_LINE_SMOOTH);
 		glDisable(GL_BLEND);
@@ -904,7 +943,7 @@ static void draw_uvs(SpaceImage *sima, Scene *scene, ViewLayer *view_layer, Obje
 
 		Gwn_VertFormat *format = immVertexFormat();
 		pos = GWN_vertformat_attr_add(format, "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
-		color = GWN_vertformat_attr_add(format, "color", GWN_COMP_U8, 3, GWN_FETCH_INT_TO_FLOAT_UNIT);
+		color = GWN_vertformat_attr_add(format, "color", GWN_COMP_F32, 3, GWN_FETCH_FLOAT);
 
 		immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
 
@@ -922,8 +961,8 @@ static void draw_uvs(SpaceImage *sima, Scene *scene, ViewLayer *view_layer, Obje
 			if (!uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) {
 				/* Only set color for the first face */
 				if (!col_set) {
-					UI_GetThemeColor3ubv(TH_WIRE, col1);
-					immAttrib3ubv(color, col1);
+					UI_GetThemeColor3fv(TH_WIRE, col1);
+					immAttrib3fv(color, col1);
 
 					col_set = true;
 				}
@@ -944,8 +983,8 @@ static void draw_uvs(SpaceImage *sima, Scene *scene, ViewLayer *view_layer, Obje
 			if (uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) {
 				/* Only set color for the first face */
 				if (!col_set) {
-					UI_GetThemeColor3ubv(TH_FACE_DOT, col1);
-					immAttrib3ubv(color, col1);
+					UI_GetThemeColor3fv(TH_FACE_DOT, col1);
+					immAttrib3fv(color, col1);
 
 					col_set = true;
 				}
diff --git a/source/blender/editors/uvedit/uvedit_ops.c b/source/blender/editors/uvedit/uvedit_ops.c
index 8f051327265c94962b81d8b0700282d72b2ed9dc..8ec9fe355120b90d06157ead1ee7c3637a976526 100644
--- a/source/blender/editors/uvedit/uvedit_ops.c
+++ b/source/blender/editors/uvedit/uvedit_ops.c
@@ -1723,13 +1723,13 @@ static void uv_weld_align(bContext *C, int tool)
 			}
 
 			/* now we have all verts, make into a line */
-			if (BLI_array_count(eve_line) > 2) {
+			if (BLI_array_len(eve_line) > 2) {
 
 				/* we know the returns from these must be valid */
 				const float *uv_start = uv_sel_co_from_eve(
 				        scene, obedit, ima, em, eve_line[0]);
 				const float *uv_end   = uv_sel_co_from_eve(
-				        scene, obedit, ima, em, eve_line[BLI_array_count(eve_line) - 1]);
+				        scene, obedit, ima, em, eve_line[BLI_array_len(eve_line) - 1]);
 				/* For t & u modes */
 				float a = 0.0f;
 
@@ -1747,7 +1747,7 @@ static void uv_weld_align(bContext *C, int tool)
 				}
 
 				/* go over all verts except for endpoints */
-				for (i = 0; i < BLI_array_count(eve_line); i++) {
+				for (i = 0; i < BLI_array_len(eve_line); i++) {
 					BM_ITER_ELEM (l, &liter, eve_line[i], BM_LOOPS_OF_VERT) {
 						if (!uvedit_face_visible_test(scene, obedit, ima, l->f))
 							continue;
@@ -1878,7 +1878,7 @@ static int uv_remove_doubles_exec(bContext *C, wmOperator *op)
 			}
 		}
 
-		for (uv_a_index = 0; uv_a_index < BLI_array_count(vert_arr); uv_a_index++) {
+		for (uv_a_index = 0; uv_a_index < BLI_array_len(vert_arr); uv_a_index++) {
 			if (vert_arr[uv_a_index].weld == false) {
 				float uv_min[2];
 				float uv_max[2];
@@ -1892,7 +1892,7 @@ static int uv_remove_doubles_exec(bContext *C, wmOperator *op)
 				copy_v2_v2(uv_min, uv_a);
 
 				vert_arr[uv_a_index].weld = true;
-				for (uv_b_index = uv_a_index + 1; uv_b_index < BLI_array_count(vert_arr); uv_b_index++) {
+				for (uv_b_index = uv_a_index + 1; uv_b_index < BLI_array_len(vert_arr); uv_b_index++) {
 					uv_b = vert_arr[uv_b_index].uv_loop->uv;
 					if ((vert_arr[uv_b_index].weld == false) &&
 					    (len_manhattan_v2v2(uv_a, uv_b) < threshold))
@@ -1902,10 +1902,10 @@ static int uv_remove_doubles_exec(bContext *C, wmOperator *op)
 						vert_arr[uv_b_index].weld = true;
 					}
 				}
-				if (BLI_array_count(loop_arr)) {
+				if (BLI_array_len(loop_arr)) {
 					float uv_mid[2];
 					mid_v2_v2v2(uv_mid, uv_min, uv_max);
-					for (uv_b_index = 0; uv_b_index < BLI_array_count(loop_arr); uv_b_index++) {
+					for (uv_b_index = 0; uv_b_index < BLI_array_len(loop_arr); uv_b_index++) {
 						copy_v2_v2(loop_arr[uv_b_index]->uv, uv_mid);
 					}
 				}
@@ -1939,12 +1939,12 @@ static int uv_remove_doubles_exec(bContext *C, wmOperator *op)
 			}
 		}
 
-		for (uv_a_index = 0; uv_a_index < BLI_array_count(loop_arr); uv_a_index++) {
+		for (uv_a_index = 0; uv_a_index < BLI_array_len(loop_arr); uv_a_index++) {
 			float dist_best = FLT_MAX, dist;
 			const float *uv_best = NULL;
 
 			uv_a = loop_arr[uv_a_index]->uv;
-			for (uv_b_index = 0; uv_b_index < BLI_array_count(loop_arr_unselected); uv_b_index++) {
+			for (uv_b_index = 0; uv_b_index < BLI_array_len(loop_arr_unselected); uv_b_index++) {
 				uv_b = loop_arr_unselected[uv_b_index]->uv;
 				dist = len_manhattan_v2v2(uv_a, uv_b);
 				if ((dist < threshold) && (dist < dist_best)) {
diff --git a/source/blender/gpu/GPU_texture.h b/source/blender/gpu/GPU_texture.h
index 486af0a8a74c0351b31c69e0afc92c024ff35bc4..0fde0edcf2bce6fe49a9b22dda2db3d51ea881ec 100644
--- a/source/blender/gpu/GPU_texture.h
+++ b/source/blender/gpu/GPU_texture.h
@@ -70,6 +70,7 @@ typedef enum GPUTextureFormat {
 	GPU_RG16I,
 	GPU_R32F,
 	GPU_R16F,
+	GPU_R16I,
 	GPU_RG8,
 	GPU_R8,
 #if 0
@@ -88,7 +89,6 @@ typedef enum GPUTextureFormat {
 	GPU_RG8UI,
 	GPU_R32I,
 	GPU_R32UI,
-	GPU_R16I,
 	GPU_R16UI,
 	GPU_R16,
 	GPU_R8I,
diff --git a/source/blender/gpu/GPU_viewport.h b/source/blender/gpu/GPU_viewport.h
index b733027b0f33e73dee26703d838930746739c7c9..20d468459e6e4d277229eb058076a88f192ae587 100644
--- a/source/blender/gpu/GPU_viewport.h
+++ b/source/blender/gpu/GPU_viewport.h
@@ -83,7 +83,6 @@ typedef struct ViewportEngineData {
 
 	/* Profiling data */
 	double init_time;
-	double cache_time;
 	double render_time;
 	double background_time;
 } ViewportEngineData;
@@ -114,6 +113,9 @@ void *GPU_viewport_texture_list_get(GPUViewport *viewport);
 void  GPU_viewport_size_get(const GPUViewport *viewport, int size[2]);
 void  GPU_viewport_size_set(GPUViewport *viewport, const int size[2]);
 
+/* Profiling */
+double *GPU_viewport_cache_time_get(GPUViewport *viewport);
+
 void GPU_viewport_tag_update(GPUViewport *viewport);
 bool GPU_viewport_do_update(GPUViewport *viewport);
 
diff --git a/source/blender/gpu/intern/gpu_batch_presets.c b/source/blender/gpu/intern/gpu_batch_presets.c
index 950f1a2dab3c81678e48ab94f4583f6a11320263..696143a385787c79e93b077f626fecc529435220 100644
--- a/source/blender/gpu/intern/gpu_batch_presets.c
+++ b/source/blender/gpu/intern/gpu_batch_presets.c
@@ -51,7 +51,7 @@ static struct {
 	struct {
 		uint pos, nor;
 	} attr_id;
-} g_presets_3d = {0};
+} g_presets_3d = {{0}};
 
 /* We may want 2D presets later. */
 
diff --git a/source/blender/gpu/intern/gpu_texture.c b/source/blender/gpu/intern/gpu_texture.c
index 651cbda00e82d05483dbdce46fa6a23d16c41a22..bd25dd03f133b888349a7e33ba3cc5470a52700b 100644
--- a/source/blender/gpu/intern/gpu_texture.c
+++ b/source/blender/gpu/intern/gpu_texture.c
@@ -136,7 +136,7 @@ static GLenum gpu_texture_get_format(
 		*is_stencil = false;
 
 		/* Integer formats */
-		if (ELEM(data_type, GPU_RG16I)) {
+		if (ELEM(data_type, GPU_RG16I, GPU_R16I)) {
 			*data_format = GL_INT;
 
 			switch (components) {
@@ -185,6 +185,7 @@ static GLenum gpu_texture_get_format(
 			break;
 		case GPU_DEPTH_COMPONENT16:
 		case GPU_R16F:
+		case GPU_R16I:
 		case GPU_RG8:
 			*bytesize = 2;
 			break;
@@ -209,6 +210,7 @@ static GLenum gpu_texture_get_format(
 		case GPU_RGBA8: return GL_RGBA8;
 		case GPU_R32F: return GL_R32F;
 		case GPU_R16F: return GL_R16F;
+		case GPU_R16I: return GL_R16I;
 		case GPU_RG8: return GL_RG8;
 		case GPU_R8: return GL_R8;
 		/* Special formats texture & renderbuffer */
diff --git a/source/blender/gpu/intern/gpu_viewport.c b/source/blender/gpu/intern/gpu_viewport.c
index 42dfe59389d6ab4e9a6f22646861653969973e7a..2ad89bd1345d5527c7caed916c9c1d7d194be987 100644
--- a/source/blender/gpu/intern/gpu_viewport.c
+++ b/source/blender/gpu/intern/gpu_viewport.c
@@ -87,6 +87,9 @@ struct GPUViewport {
 	struct DRWInstanceDataList *idatalist; /* Used for rendering data structure. */
 
 	ListBase tex_pool;  /* ViewportTempTexture list : Temporary textures shared across draw engines */
+
+	/* Profiling data */
+	double cache_time;
 };
 
 enum {
@@ -153,15 +156,22 @@ GPUViewport *GPU_viewport_create_from_offscreen(struct GPUOffScreen *ofs)
  */
 void GPU_viewport_clear_from_offscreen(GPUViewport *viewport)
 {
-	if (viewport->fbl->multisample_fb) {
-		viewport->fbl->multisample_fb = NULL;
-		viewport->txl->multisample_color = NULL;
-		viewport->txl->multisample_depth = NULL;
+	DefaultFramebufferList *dfbl = viewport->fbl;
+	DefaultTextureList *dtxl = viewport->txl;
+
+	if (dfbl->multisample_fb) {
+		/* GPUViewport expect the final result to be in default_fb but
+		 * GPUOffscreen wants it in its multisample_fb, so we sync it back. */
+		GPU_framebuffer_blit(dfbl->default_fb, 0, dfbl->multisample_fb, 0, false, false);
+		GPU_framebuffer_blit(dfbl->default_fb, 0, dfbl->multisample_fb, 0, true, false);
+		dfbl->multisample_fb = NULL;
+		dtxl->multisample_color = NULL;
+		dtxl->multisample_depth = NULL;
 	}
 	else {
 		viewport->fbl->default_fb = NULL;
-		viewport->txl->color = NULL;
-		viewport->txl->depth = NULL;
+		dtxl->color = NULL;
+		dtxl->depth = NULL;
 	}
 }
 
@@ -268,6 +278,11 @@ void GPU_viewport_size_set(GPUViewport *viewport, const int size[2])
 	viewport->size[1] = size[1];
 }
 
+double *GPU_viewport_cache_time_get(GPUViewport *viewport)
+{
+	return &viewport->cache_time;
+}
+
 /**
  * Try to find a texture coresponding to params into the texture pool.
  * If no texture was found, create one and add it to the pool.
diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h
index 5e3cbeaa0571441bfdbef1fd5867fcfaf2371e27..1058099e9ef7fb9c970b1ca4a4c14279279ebaa4 100644
--- a/source/blender/makesrna/RNA_access.h
+++ b/source/blender/makesrna/RNA_access.h
@@ -1278,6 +1278,13 @@ typedef enum eRNAOverrideMatchResult {
 	RNA_OVERRIDE_MATCH_RESULT_RESTORED = 1 << 1,
 } eRNAOverrideMatchResult;
 
+typedef enum eRNAOverrideStatus {
+	RNA_OVERRIDE_STATUS_OVERRIDABLE = 1 << 0,  /* The property is overridable. */
+	RNA_OVERRIDE_STATUS_OVERRIDDEN = 1 << 1,  /* The property is overridden. */
+	RNA_OVERRIDE_STATUS_MANDATORY = 1 << 2,  /* Overriding this property is mandatory when creating an override. */
+	RNA_OVERRIDE_STATUS_LOCKED = 1 << 3,  /* The override status of this property is locked. */
+} eRNAOverrideStatus;
+
 bool RNA_struct_override_matches(
         struct PointerRNA *ptr_local, struct PointerRNA *ptr_reference, const char *root_path,
         struct IDOverrideStatic *override, const eRNAOverrideMatch flags,
@@ -1300,9 +1307,7 @@ struct IDOverrideStaticPropertyOperation *RNA_property_override_property_operati
         PointerRNA *ptr, PropertyRNA *prop, const short operation, const int index,
         const bool strict, bool *r_strict, bool *r_created);
 
-void RNA_property_override_status(
-        PointerRNA *ptr, PropertyRNA *prop, const int index,
-        bool *r_overridable, bool *r_overridden, bool *r_mandatory, bool *r_locked);
+eRNAOverrideStatus RNA_property_override_status(PointerRNA *ptr, PropertyRNA *prop, const int index);
 
 void        RNA_struct_state_owner_set(const char *name);
 const char *RNA_struct_state_owner_get(void);
diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c
index 0f47a461cdaff40974fb728fbfc2f03bd6cc6c8b..b63cebbdde247ae69a60557bf00422fed2b1894f 100644
--- a/source/blender/makesrna/intern/rna_access.c
+++ b/source/blender/makesrna/intern/rna_access.c
@@ -451,8 +451,9 @@ static void *rna_idproperty_check_ex(PropertyRNA **prop, PointerRNA *ptr, const
 
 			return idprop;
 		}
-		else
+		else {
 			return return_rnaprop ? *prop : NULL;
+		}
 	}
 
 	{
@@ -7149,10 +7150,13 @@ bool RNA_struct_equals(PointerRNA *ptr_a, PointerRNA *ptr_b, eRNACompareMode mod
  * When \a prop is given, \a prop_a and \a prop_b should always be NULL, and vice-versa.
  * This is necessary, because we cannot perform 'set/unset' checks on resolved properties
  * (unset IDProps would merely be NULL then).
+ *
+ * \note When there is no equality, but we cannot determine an order (greater than/lesser than), we return 1.
  */
 static int rna_property_override_diff(
-        PointerRNA *ptr_a, PointerRNA *ptr_b, PropertyRNA *prop, PropertyRNA *prop_a, PropertyRNA *prop_b, const char *rna_path,
-        eRNACompareMode mode, IDOverrideStatic *override, const int flags, eRNAOverrideMatchResult *r_report_flags)
+        PointerRNA *ptr_a, PointerRNA *ptr_b, PropertyRNA *prop, PropertyRNA *prop_a, PropertyRNA *prop_b,
+        const char *rna_path, eRNACompareMode mode,
+        IDOverrideStatic *override, const int flags, eRNAOverrideMatchResult *r_report_flags)
 {
 	if (prop != NULL) {
 		BLI_assert(prop_a == NULL && prop_b == NULL);
@@ -7678,29 +7682,30 @@ IDOverrideStaticPropertyOperation *RNA_property_override_property_operation_get(
 	return BKE_override_static_property_operation_get(op, operation, NULL, NULL, index, index, strict, r_strict, r_created);
 }
 
-void RNA_property_override_status(
-        PointerRNA *ptr, PropertyRNA *prop, const int index,
-        bool *r_overridable, bool *r_overridden, bool *r_mandatory, bool *r_locked)
+eRNAOverrideStatus RNA_property_override_status(PointerRNA *ptr, PropertyRNA *prop, const int index)
 {
-#define SET_RET(_name, _val) if (_name != NULL) *_name = (_val)
-
-	SET_RET(r_overridable, false);
-	SET_RET(r_overridden, false);
-	SET_RET(r_mandatory, false);
-	SET_RET(r_locked, false);
+	int override_status = 0;
 
 	if (!ptr || !prop || !ptr->id.data || !((ID *)ptr->id.data)->override_static) {
-		return;
+		return override_status;
 	}
 
-	SET_RET(r_overridable, (prop->flag & PROP_OVERRIDABLE_STATIC) && (prop->flag & PROP_EDITABLE));
+	if ((prop->flag & PROP_OVERRIDABLE_STATIC) && (prop->flag & PROP_EDITABLE)) {
+		override_status |= RNA_OVERRIDE_STATUS_OVERRIDABLE;
+	}
 
-	if (r_overridden || r_mandatory || r_locked) {
-		IDOverrideStaticPropertyOperation *opop = RNA_property_override_property_operation_find(ptr, prop, index, false, NULL);
-		SET_RET(r_overridden, opop != NULL);
-		SET_RET(r_mandatory, (opop->flag & IDOVERRIDESTATIC_FLAG_MANDATORY) != 0);
-		SET_RET(r_locked, (opop->flag & IDOVERRIDESTATIC_FLAG_LOCKED) != 0);
+	IDOverrideStaticPropertyOperation *opop = RNA_property_override_property_operation_find(ptr, prop, index, false, NULL);
+	if (opop != NULL) {
+		override_status |= RNA_OVERRIDE_STATUS_OVERRIDDEN;
+		if (opop->flag & IDOVERRIDESTATIC_FLAG_MANDATORY) {
+			override_status |= RNA_OVERRIDE_STATUS_MANDATORY;
+		}
+		if (opop->flag & IDOVERRIDESTATIC_FLAG_LOCKED) {
+			override_status |= RNA_OVERRIDE_STATUS_LOCKED;
+		}
 	}
+
+	return override_status;
 }
 
 
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index aa37c9ffa88c4e0e7a96db49494a718a51cbd3f3..ca8abdc8b48bbca80c3a49bcb87f230e13209cdd 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -441,7 +441,6 @@ static void rna_Brush_icon_update(Main *UNUSED(bmain), Scene *UNUSED(scene), Poi
 	br->id.icon_id = 0;
 
 	if (br->flag & BRUSH_CUSTOM_ICON) {
-		BKE_previewimg_id_ensure(&br->id);
 		BKE_icon_changed(BKE_icon_id_ensure(&br->id));
 	}
 
diff --git a/source/blender/makesrna/intern/rna_dynamicpaint.c b/source/blender/makesrna/intern/rna_dynamicpaint.c
index b973e0f27ee6f3fadab1007765a69c4b5b4ed578..466a01a72712e2818da109b2ab423c85e851c51c 100644
--- a/source/blender/makesrna/intern/rna_dynamicpaint.c
+++ b/source/blender/makesrna/intern/rna_dynamicpaint.c
@@ -723,11 +723,11 @@ static void rna_def_canvas_surface(BlenderRNA *brna)
 	RNA_def_property_boolean_sdna(prop, NULL, "flags", MOD_DPAINT_WAVE_OPEN_BORDERS);
 	RNA_def_property_ui_text(prop, "Open Borders", "Pass waves through mesh edges");
 
-	
 	/* cache */
 	prop = RNA_def_property(srna, "point_cache", PROP_POINTER, PROP_NONE);
 	RNA_def_property_flag(prop, PROP_NEVER_NULL);
 	RNA_def_property_pointer_sdna(prop, NULL, "pointcache");
+	RNA_def_property_struct_type(prop, "PointCache");
 	RNA_def_property_ui_text(prop, "Point Cache", "");
 
 	/* is cache used */
diff --git a/source/blender/makesrna/intern/rna_object_force.c b/source/blender/makesrna/intern/rna_object_force.c
index 4a89ca001ac0bfc1ebb0c45100234f7ebdc8985c..649cb523e92bde94ae79c1ee4258f3f94d745b29 100644
--- a/source/blender/makesrna/intern/rna_object_force.c
+++ b/source/blender/makesrna/intern/rna_object_force.c
@@ -772,31 +772,8 @@ static const EnumPropertyItem *rna_Effector_shape_itemf(bContext *UNUSED(C), Poi
 
 #else
 
-/* ptcache.point_caches */
-static void rna_def_ptcache_point_caches(BlenderRNA *brna, PropertyRNA *cprop)
+static void rna_def_pointcache_common(StructRNA *srna)
 {
-	StructRNA *srna;
-	PropertyRNA *prop;
-
-	/* FunctionRNA *func; */
-	/* PropertyRNA *parm; */
-
-	RNA_def_property_srna(cprop, "PointCaches");
-	srna = RNA_def_struct(brna, "PointCaches", NULL);
-	RNA_def_struct_sdna(srna, "PointCache");
-	RNA_def_struct_ui_text(srna, "Point Caches", "Collection of point caches");
-
-	prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED);
-	RNA_def_property_int_funcs(prop, "rna_Cache_active_point_cache_index_get",
-	                           "rna_Cache_active_point_cache_index_set",
-	                           "rna_Cache_active_point_cache_index_range");
-	RNA_def_property_ui_text(prop, "Active Point Cache Index", "");
-	RNA_def_property_update(prop, NC_OBJECT, "rna_Cache_change");
-}
-
-static void rna_def_pointcache(BlenderRNA *brna)
-{
-	StructRNA *srna;
 	PropertyRNA *prop;
 
 	static const EnumPropertyItem point_cache_compress_items[] = {
@@ -806,16 +783,12 @@ static void rna_def_pointcache(BlenderRNA *brna)
 		{0, NULL, 0, NULL, NULL}
 	};
 
-	srna = RNA_def_struct(brna, "PointCache", NULL);
-	RNA_def_struct_ui_text(srna, "Point Cache", "Point cache for physics simulations");
-	RNA_def_struct_ui_icon(srna, ICON_PHYSICS);
-	
 	prop = RNA_def_property(srna, "frame_start", PROP_INT, PROP_TIME);
 	RNA_def_property_int_sdna(prop, NULL, "startframe");
 	RNA_def_property_range(prop, -MAXFRAME, MAXFRAME);
 	RNA_def_property_ui_range(prop, 1, MAXFRAME, 1, 1);
 	RNA_def_property_ui_text(prop, "Start", "Frame on which the simulation starts");
-	
+
 	prop = RNA_def_property(srna, "frame_end", PROP_INT, PROP_TIME);
 	RNA_def_property_int_sdna(prop, NULL, "endframe");
 	RNA_def_property_range(prop, 1, MAXFRAME);
@@ -896,13 +869,59 @@ static void rna_def_pointcache(BlenderRNA *brna)
 	                         "Use this file's path for the disk cache when library linked into another file "
 	                         "(for local bakes per scene file, disable this option)");
 	RNA_def_property_update(prop, NC_OBJECT, "rna_Cache_idname_change");
+}
+
+static void rna_def_ptcache_point_caches(BlenderRNA *brna, PropertyRNA *cprop)
+{
+	StructRNA *srna;
+	PropertyRNA *prop;
+
+	/* FunctionRNA *func; */
+	/* PropertyRNA *parm; */
+
+	RNA_def_property_srna(cprop, "PointCaches");
+	srna = RNA_def_struct(brna, "PointCaches", NULL);
+	RNA_def_struct_sdna(srna, "PointCache");
+	RNA_def_struct_ui_text(srna, "Point Caches", "Collection of point caches");
+
+	prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED);
+	RNA_def_property_int_funcs(prop, "rna_Cache_active_point_cache_index_get",
+	                           "rna_Cache_active_point_cache_index_set",
+	                           "rna_Cache_active_point_cache_index_range");
+	RNA_def_property_ui_text(prop, "Active Point Cache Index", "");
+	RNA_def_property_update(prop, NC_OBJECT, "rna_Cache_change");
+
+	/* And define another RNA type for those collection items. */
+	srna = RNA_def_struct(brna, "PointCacheItem", NULL);
+	RNA_def_struct_sdna(srna, "PointCache");
+	RNA_def_struct_ui_text(srna, "Point Cache", "point cache for physics simulations");
+	RNA_def_struct_ui_icon(srna, ICON_PHYSICS);
+
+	rna_def_pointcache_common(srna);
+}
+
+static void rna_def_pointcache_active(BlenderRNA *brna)
+{
+	StructRNA *srna;
+	PropertyRNA *prop;
+
+	srna = RNA_def_struct(brna, "PointCache", NULL);
+	RNA_def_struct_ui_text(srna, "Active Point Cache", "Active point cache for physics simulations");
+	RNA_def_struct_ui_icon(srna, ICON_PHYSICS);
+
+	rna_def_pointcache_common(srna);
 
+	/* This first-level RNA pointer also has list of all caches from owning ID.
+	 * Those caches items have exact same content as 'active' one, except for that collection,
+	 * to prevent ugly recursive layout pattern.
+	 * Note: This shall probably be redone from scratch in a proper way at some poitn, but for now that will do,
+	 *       and shall not break anything in the API. */
 	prop = RNA_def_property(srna, "point_caches", PROP_COLLECTION, PROP_NONE);
 	RNA_def_property_collection_funcs(prop, "rna_Cache_list_begin", "rna_iterator_listbase_next",
 	                                  "rna_iterator_listbase_end", "rna_iterator_listbase_get",
 	                                  NULL, NULL, NULL, NULL);
-	RNA_def_property_struct_type(prop, "PointCache");
-	RNA_def_property_ui_text(prop, "Point Cache List", "Point cache list");
+	RNA_def_property_struct_type(prop, "PointCacheItem");
+	RNA_def_property_ui_text(prop, "Point Cache List", "");
 	rna_def_ptcache_point_caches(brna, prop);
 }
 
@@ -1882,7 +1901,7 @@ static void rna_def_softbody(BlenderRNA *brna)
 
 void RNA_def_object_force(BlenderRNA *brna)
 {
-	rna_def_pointcache(brna);
+	rna_def_pointcache_active(brna);
 	rna_def_collision(brna);
 	rna_def_effector_weight(brna);
 	rna_def_field(brna);
diff --git a/source/blender/makesrna/intern/rna_rigidbody.c b/source/blender/makesrna/intern/rna_rigidbody.c
index bc85e9c650311efd071abf8c085fb212879e919c..ae325651a318c2ddf78b056aca3d70bf61580dbc 100644
--- a/source/blender/makesrna/intern/rna_rigidbody.c
+++ b/source/blender/makesrna/intern/rna_rigidbody.c
@@ -802,6 +802,7 @@ static void rna_def_rigidbody_world(BlenderRNA *brna)
 	prop = RNA_def_property(srna, "point_cache", PROP_POINTER, PROP_NONE);
 	RNA_def_property_flag(prop, PROP_NEVER_NULL);
 	RNA_def_property_pointer_sdna(prop, NULL, "pointcache");
+	RNA_def_property_struct_type(prop, "PointCache");
 	RNA_def_property_ui_text(prop, "Point Cache", "");
 
 	/* effector weights */
diff --git a/source/blender/makesrna/intern/rna_rna.c b/source/blender/makesrna/intern/rna_rna.c
index 8cf9bc7d39c49d6ef00acb73b0d0f80e0bd7abc5..9c043c3563a75574cee9be64f98be78ac680b64c 100644
--- a/source/blender/makesrna/intern/rna_rna.c
+++ b/source/blender/makesrna/intern/rna_rna.c
@@ -1458,6 +1458,10 @@ int rna_property_override_diff_default(PointerRNA *ptr_a, PointerRNA *ptr_b,
 				if (propname != NULL) {
 					propname_a = RNA_property_string_get_alloc(&iter_a.ptr, propname, propname_buff_a, sizeof(propname_buff_a), NULL);
 					propname_b = RNA_property_string_get_alloc(&iter_b.ptr, propname, propname_buff_b, sizeof(propname_buff_b), NULL);
+				}
+				/* There may be a propname defined in some cases, while no actual name set
+				 * (e.g. happens with point cache), in that case too we want to fall back to index. */
+				if ((propname_a != NULL && propname_a[0] != '\0') || (propname_b != NULL && propname_b[0] != '\0')) {
 					if (!STREQ(propname_a, propname_b)) {
 						/* Same as above, not same structs. */
 						equals = false;
diff --git a/source/blender/makesrna/intern/rna_smoke.c b/source/blender/makesrna/intern/rna_smoke.c
index a49c71fc54b8c9f33d848893dd7dec9f93a2cd0b..8fe97885f497f17066dcfd1e778505030e137166 100644
--- a/source/blender/makesrna/intern/rna_smoke.c
+++ b/source/blender/makesrna/intern/rna_smoke.c
@@ -624,6 +624,7 @@ static void rna_def_smoke_domain_settings(BlenderRNA *brna)
 	prop = RNA_def_property(srna, "point_cache", PROP_POINTER, PROP_NONE);
 	RNA_def_property_flag(prop, PROP_NEVER_NULL);
 	RNA_def_property_pointer_sdna(prop, NULL, "point_cache[0]");
+	RNA_def_property_struct_type(prop, "PointCache");
 	RNA_def_property_ui_text(prop, "Point Cache", "");
 
 	prop = RNA_def_property(srna, "point_cache_compress_type", PROP_ENUM, PROP_NONE);
diff --git a/source/blender/modifiers/intern/MOD_skin.c b/source/blender/modifiers/intern/MOD_skin.c
index d2d8da289b745ac84292cf3e5668189ddc6c228f..4afe6ca33e235a7d81972742672c54010e5bbaa6 100644
--- a/source/blender/modifiers/intern/MOD_skin.c
+++ b/source/blender/modifiers/intern/MOD_skin.c
@@ -1304,9 +1304,9 @@ static void skin_fix_hole_no_good_verts(BMesh *bm, Frame *frame, BMFace *split_f
 	else if (split_face->len > 4) {
 		/* Maintain a dynamic vert array containing the split_face's
 		 * vertices, avoids frequent allocs in collapse_face_corners() */
-		if (BLI_array_count(vert_buf) < split_face->len) {
+		if (BLI_array_len(vert_buf) < split_face->len) {
 			BLI_array_grow_items(vert_buf, (split_face->len -
-			                                BLI_array_count(vert_buf)));
+			                                BLI_array_len(vert_buf)));
 		}
 
 		/* Get split face's verts */
diff --git a/source/blender/python/intern/bpy_app.c b/source/blender/python/intern/bpy_app.c
index ec77dc07ba701d8d48400d8d68b212554a853491..1e2ce3727272f68561781e45d3567466982d6aba 100644
--- a/source/blender/python/intern/bpy_app.c
+++ b/source/blender/python/intern/bpy_app.c
@@ -361,6 +361,7 @@ static PyGetSetDef bpy_app_getsets[] = {
 	{(char *)"debug_depsgraph_eval", bpy_app_debug_get, bpy_app_debug_set, (char *)bpy_app_debug_doc, (void *)G_DEBUG_DEPSGRAPH_EVAL},
 	{(char *)"debug_depsgraph_tag", bpy_app_debug_get, bpy_app_debug_set, (char *)bpy_app_debug_doc, (void *)G_DEBUG_DEPSGRAPH_TAG},
 	{(char *)"debug_depsgraph_time", bpy_app_debug_get, bpy_app_debug_set, (char *)bpy_app_debug_doc, (void *)G_DEBUG_DEPSGRAPH_TIME},
+	{(char *)"debug_depsgraph_pretty", bpy_app_debug_get, bpy_app_debug_set, (char *)bpy_app_debug_doc, (void *)G_DEBUG_DEPSGRAPH_PRETTY},
 	{(char *)"debug_simdata",   bpy_app_debug_get, bpy_app_debug_set, (char *)bpy_app_debug_doc, (void *)G_DEBUG_SIMDATA},
 	{(char *)"debug_gpumem",    bpy_app_debug_get, bpy_app_debug_set, (char *)bpy_app_debug_doc, (void *)G_DEBUG_GPU_MEM},
 
diff --git a/source/blender/python/mathutils/mathutils_Matrix.c b/source/blender/python/mathutils/mathutils_Matrix.c
index 924e46a8c000410bbc5a377cc0906b3924f83c1c..cc6a536789569d1aabadeedb8a87561ad040c9f8 100644
--- a/source/blender/python/mathutils/mathutils_Matrix.c
+++ b/source/blender/python/mathutils/mathutils_Matrix.c
@@ -1710,10 +1710,18 @@ static PyObject *Matrix_lerp(MatrixObject *self, PyObject *args)
 
 	/* TODO, different sized matrix */
 	if (self->num_col == 4 && self->num_row == 4) {
+#ifdef MATH_STANDALONE
+		blend_m4_m4m4((float (*)[4])mat, (float (*)[4])self->matrix, (float (*)[4])mat2->matrix, fac);
+#else
 		interp_m4_m4m4((float (*)[4])mat, (float (*)[4])self->matrix, (float (*)[4])mat2->matrix, fac);
+#endif
 	}
 	else if (self->num_col == 3 && self->num_row == 3) {
+#ifdef MATH_STANDALONE
+		blend_m3_m3m3((float (*)[3])mat, (float (*)[3])self->matrix, (float (*)[3])mat2->matrix, fac);
+#else
 		interp_m3_m3m3((float (*)[3])mat, (float (*)[3])self->matrix, (float (*)[3])mat2->matrix, fac);
+#endif
 	}
 	else {
 		PyErr_SetString(PyExc_ValueError,
diff --git a/source/blender/render/extern/include/RE_pipeline.h b/source/blender/render/extern/include/RE_pipeline.h
index 822993a2372dd4af55b782d5fa7b1aa74ebec69f..93ac53cdfcc676a3c77a4219c9c26bdf81f129ad 100644
--- a/source/blender/render/extern/include/RE_pipeline.h
+++ b/source/blender/render/extern/include/RE_pipeline.h
@@ -238,6 +238,8 @@ void RE_render_result_rect_from_ibuf(
 struct RenderLayer *RE_GetRenderLayer(struct RenderResult *rr, const char *name);
 float *RE_RenderLayerGetPass(volatile struct RenderLayer *rl, const char *name, const char *viewname);
 
+bool RE_HasSingleLayer(struct Render *re);
+
 /* add passes for grease pencil */
 struct RenderPass *RE_create_gp_pass(struct RenderResult *rr, const char *layername, const char *viewname);
 
diff --git a/source/blender/render/intern/source/pipeline.c b/source/blender/render/intern/source/pipeline.c
index 33c633c76bc40328dd7a0f20c811b2bf1466dc3d..170cd0ad41997984561bb9d947752e10135d83e9 100644
--- a/source/blender/render/intern/source/pipeline.c
+++ b/source/blender/render/intern/source/pipeline.c
@@ -261,6 +261,11 @@ RenderLayer *RE_GetRenderLayer(RenderResult *rr, const char *name)
 	}
 }
 
+bool RE_HasSingleLayer(Render *re)
+{
+	return (re->r.scemode & R_SINGLE_LAYER);
+}
+
 RenderResult *RE_MultilayerConvert(void *exrhandle, const char *colorspace, bool predivide, int rectx, int recty)
 {
 	return render_result_new_from_exr(exrhandle, colorspace, predivide, rectx, recty);
@@ -268,12 +273,19 @@ RenderResult *RE_MultilayerConvert(void *exrhandle, const char *colorspace, bool
 
 RenderLayer *render_get_active_layer(Render *re, RenderResult *rr)
 {
-	RenderLayer *rl = BLI_findlink(&rr->layers, re->active_view_layer);
-	
-	if (rl)
-		return rl;
-	else 
-		return rr->layers.first;
+	ViewLayer *view_layer = BLI_findlink(&re->view_layers, re->active_view_layer);
+
+	if (view_layer) {
+		RenderLayer *rl = BLI_findstring(&rr->layers,
+		                                 view_layer->name,
+		                                 offsetof(RenderLayer, name));
+
+		if (rl) {
+			return rl;
+		}
+	}
+
+	return rr->layers.first;
 }
 
 static int UNUSED_FUNCTION(render_scene_needs_vector)(Render *re)
diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c
index b6317a1f0e2cfd2599c13dc77d7aec38a01d98ee..2ecbad81a94305fee3d652367190c4dc04feb871 100644
--- a/source/blender/windowmanager/intern/wm_operators.c
+++ b/source/blender/windowmanager/intern/wm_operators.c
@@ -1814,7 +1814,7 @@ static int wm_operator_tool_set_exec(bContext *C, wmOperator *op)
 {
 	ScrArea *sa = CTX_wm_area(C);
 
-	bToolDef tool_def = {0};
+	bToolDef tool_def = {{0}};
 
 	tool_def.index = RNA_int_get(op->ptr, "index");
 	tool_def.spacetype = sa->spacetype;
diff --git a/source/blender/windowmanager/intern/wm_subwindow.c b/source/blender/windowmanager/intern/wm_subwindow.c
index 5ca3488074316429359b85aa689156e933fbba5c..f34b5bf9c9500ffbd209e683ddad231bcec960e7 100644
--- a/source/blender/windowmanager/intern/wm_subwindow.c
+++ b/source/blender/windowmanager/intern/wm_subwindow.c
@@ -69,8 +69,6 @@ void wmPartialViewport(rcti *drawrct, const rcti *winrct, const rcti *partialrct
 		scissor_pad = false;
 	}
 
-	int x = drawrct->xmin;
-	int y = drawrct->ymin;
 	int width  = BLI_rcti_size_x(winrct) + 1;
 	int height = BLI_rcti_size_y(winrct) + 1;
 
@@ -85,8 +83,8 @@ void wmPartialViewport(rcti *drawrct, const rcti *winrct, const rcti *partialrct
 		scissor_height += 1;
 	}
 
-	glViewport(x, y, width, height);
-	glScissor(x, y, scissor_width, scissor_height);
+	glViewport(winrct->xmin, winrct->ymin, width, height);
+	glScissor(drawrct->xmin, drawrct->ymin, scissor_width, scissor_height);
 
 	wmOrtho2_pixelspace(width, height);
 	gpuLoadIdentity();
diff --git a/source/blender/windowmanager/message_bus/intern/wm_message_bus.c b/source/blender/windowmanager/message_bus/intern/wm_message_bus.c
index dba38dc8c8cb09512b8301eed261d687893f9413..06a9c2de69b122fc2f17d65b0a8b223222f5f5df 100644
--- a/source/blender/windowmanager/message_bus/intern/wm_message_bus.c
+++ b/source/blender/windowmanager/message_bus/intern/wm_message_bus.c
@@ -40,7 +40,7 @@
 /** \name Public API
  * \{ */
 
-static wmMsgTypeInfo wm_msg_types[WM_MSG_TYPE_NUM] = {NULL};
+static wmMsgTypeInfo wm_msg_types[WM_MSG_TYPE_NUM] = {{{NULL}}};
 
 typedef void (*wmMsgTypeInitFn)(wmMsgTypeInfo *);
 
diff --git a/source/blender/windowmanager/message_bus/intern/wm_message_bus_rna.c b/source/blender/windowmanager/message_bus/intern/wm_message_bus_rna.c
index 03177d9ac6a418fb177ee30a534a9895d385a77d..c9b43cc2a91d504e1c39d92ad57b2b49e5a647d4 100644
--- a/source/blender/windowmanager/message_bus/intern/wm_message_bus_rna.c
+++ b/source/blender/windowmanager/message_bus/intern/wm_message_bus_rna.c
@@ -300,14 +300,14 @@ void WM_msg_subscribe_ID(
         struct wmMsgBus *mbus, ID *id, const wmMsgSubscribeValue *msg_val_params,
         const char *id_repr)
 {
-	wmMsgParams_RNA msg_key_params = {NULL};
+	wmMsgParams_RNA msg_key_params = {{{NULL}}};
 	RNA_id_pointer_create(id, &msg_key_params.ptr);
 	WM_msg_subscribe_rna_params(mbus, &msg_key_params, msg_val_params, id_repr);
 }
 
 void WM_msg_publish_ID(struct wmMsgBus *mbus, ID *id)
 {
-	wmMsgParams_RNA msg_key_params = {NULL};
+	wmMsgParams_RNA msg_key_params = {{{NULL}}};
 	RNA_id_pointer_create(id, &msg_key_params.ptr);
 	WM_msg_publish_rna_params(mbus, &msg_key_params);
 }
diff --git a/source/creator/creator_args.c b/source/creator/creator_args.c
index f5ec13fc0dfe077f37349647fc0a85a952adff0e..df4946a817576dc5164ffcdeaa1e13d74a24ffe0 100644
--- a/source/creator/creator_args.c
+++ b/source/creator/creator_args.c
@@ -759,6 +759,8 @@ static const char arg_handle_debug_mode_generic_set_doc_depsgraph_eval[] =
 "\n\tEnable debug messages from dependency graph related on evaluation.";
 static const char arg_handle_debug_mode_generic_set_doc_depsgraph_no_threads[] =
 "\n\tSwitch dependency graph to a single threaded evaluation.";
+static const char arg_handle_debug_mode_generic_set_doc_depsgraph_pretty[] =
+"\n\tEnable colors for dependency graph debug messages.";
 static const char arg_handle_debug_mode_generic_set_doc_gpumem[] =
 "\n\tEnable GPU memory stats in status bar.";
 
@@ -1854,6 +1856,8 @@ void main_args_setup(bContext *C, bArgs *ba, SYS_SystemHandle *syshandle)
 	            CB_EX(arg_handle_debug_mode_generic_set, depsgraph_time), (void *)G_DEBUG_DEPSGRAPH_TIME);
 	BLI_argsAdd(ba, 1, NULL, "--debug-depsgraph-no-threads",
 	            CB_EX(arg_handle_debug_mode_generic_set, depsgraph_no_threads), (void *)G_DEBUG_DEPSGRAPH_NO_THREADS);
+	BLI_argsAdd(ba, 1, NULL, "--debug-depsgraph-pretty",
+	            CB_EX(arg_handle_debug_mode_generic_set, depsgraph_pretty), (void *)G_DEBUG_DEPSGRAPH_PRETTY);
 	BLI_argsAdd(ba, 1, NULL, "--debug-gpumem",
 	            CB_EX(arg_handle_debug_mode_generic_set, gpumem), (void *)G_DEBUG_GPU_MEM);
 	BLI_argsAdd(ba, 1, NULL, "--debug-gpu-shaders",
diff --git a/tests/python/view_layer/CMakeLists.txt b/tests/python/view_layer/CMakeLists.txt
index 77a56fb47f9f95308068ffe44a69099a3833375c..9cb33420ac51b3e7874c5f3916fd4750932069b6 100644
--- a/tests/python/view_layer/CMakeLists.txt
+++ b/tests/python/view_layer/CMakeLists.txt
@@ -108,6 +108,7 @@ VIEW_LAYER_TEST(object_link_a)
 VIEW_LAYER_TEST(object_link_b)
 VIEW_LAYER_TEST(object_link_c)
 VIEW_LAYER_TEST(operator_context)
+VIEW_LAYER_TEST(make_single_user)
 VIEW_LAYER_TEST(move_above_below_scene_collection_a)
 VIEW_LAYER_TEST(move_above_below_scene_collection_b)
 VIEW_LAYER_TEST(move_above_below_scene_collection_c)
diff --git a/tests/python/view_layer/test_make_single_user.py b/tests/python/view_layer/test_make_single_user.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a8a479bab26371b8a644b3c04d8ababe3d811ba
--- /dev/null
+++ b/tests/python/view_layer/test_make_single_user.py
@@ -0,0 +1,54 @@
+# ############################################################
+# Importing - Same For All Render Layer Tests
+# ############################################################
+
+import unittest
+import os
+import sys
+
+from view_layer_common import *
+
+
+# ############################################################
+# Testing
+# ############################################################
+
+class UnitTesting(ViewLayerTesting):
+    def test_make_single_user(self):
+        """
+        Really basic test, just to check for crashes on basic files.
+        """
+        import bpy
+        scene = bpy.context.scene
+        master_collection = scene.master_collection
+        view_layer = bpy.context.view_layer
+        ob = bpy.context.object
+
+        # clean up the scene a bit
+        for o in (o for o in view_layer.objects if o != ob):
+            view_layer.collections[0].collection.objects.unlink(o)
+
+        for v in (v for v in scene.view_layers if v != view_layer):
+            scene.view_layers.remove(v)
+
+        while master_collection.collections:
+            master_collection.collections.remove(
+                    master_collection.collections[0])
+
+        view_layer.collections.link(master_collection)
+        ob.select_set('SELECT')
+
+        # update depsgraph
+        scene.update()
+
+        # test itself
+        bpy.ops.object.make_single_user(object=True)
+
+
+# ############################################################
+# Main - Same For All Render Layer Tests
+# ############################################################
+
+if __name__ == '__main__':
+    UnitTesting._extra_arguments = setup_extra_arguments(__file__)
+    unittest.main()