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_vertex_buffer.c b/intern/gawain/src/gwn_vertex_buffer.c
index 32b547f9a58fae7ef4de918117751f7efa627abb..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, buffer_sz, 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/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;