Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'intern/gawain')
-rw-r--r--intern/gawain/CMakeLists.txt50
-rw-r--r--intern/gawain/gawain/gwn_attr_binding.h19
-rw-r--r--intern/gawain/gawain/gwn_attr_binding_private.h20
-rw-r--r--intern/gawain/gawain/gwn_batch.h169
-rw-r--r--intern/gawain/gawain/gwn_batch_private.h30
-rw-r--r--intern/gawain/gawain/gwn_buffer_id.h30
-rw-r--r--intern/gawain/gawain/gwn_common.h39
-rw-r--r--intern/gawain/gawain/gwn_context.h34
-rw-r--r--intern/gawain/gawain/gwn_element.h79
-rw-r--r--intern/gawain/gawain/gwn_imm_util.h22
-rw-r--r--intern/gawain/gawain/gwn_immediate.h120
-rw-r--r--intern/gawain/gawain/gwn_primitive.h42
-rw-r--r--intern/gawain/gawain/gwn_primitive_private.h14
-rw-r--r--intern/gawain/gawain/gwn_shader_interface.h81
-rw-r--r--intern/gawain/gawain/gwn_vertex_array_id.h34
-rw-r--r--intern/gawain/gawain/gwn_vertex_buffer.h125
-rw-r--r--intern/gawain/gawain/gwn_vertex_format.h78
-rw-r--r--intern/gawain/gawain/gwn_vertex_format_private.h16
-rw-r--r--intern/gawain/src/gwn_attr_binding.c70
-rw-r--r--intern/gawain/src/gwn_batch.c632
-rw-r--r--intern/gawain/src/gwn_buffer_id.cpp70
-rw-r--r--intern/gawain/src/gwn_element.c304
-rw-r--r--intern/gawain/src/gwn_imm_util.c80
-rw-r--r--intern/gawain/src/gwn_immediate.c926
-rw-r--r--intern/gawain/src/gwn_primitive.c67
-rw-r--r--intern/gawain/src/gwn_shader_interface.c362
-rw-r--r--intern/gawain/src/gwn_vertex_array_id.cpp173
-rw-r--r--intern/gawain/src/gwn_vertex_buffer.c258
-rw-r--r--intern/gawain/src/gwn_vertex_format.c291
29 files changed, 4235 insertions, 0 deletions
diff --git a/intern/gawain/CMakeLists.txt b/intern/gawain/CMakeLists.txt
new file mode 100644
index 00000000000..7ebd3ee7622
--- /dev/null
+++ b/intern/gawain/CMakeLists.txt
@@ -0,0 +1,50 @@
+# WITH_OPENGL limits the visibility of the opengl headers to just gawain and bg_gpu,
+# to more easily highlight codepadths in other libraries that need to be refactored,
+# bf_intern_gawain is allowed to have opengl regardless of this option.
+
+if(NOT WITH_OPENGL)
+ add_definitions(-DWITH_OPENGL)
+endif()
+
+set(INC
+ gawain
+)
+
+set(INC_SYS
+ ${GLEW_INCLUDE_PATH}
+)
+
+set(SRC
+ src/gwn_attr_binding.c
+ src/gwn_batch.c
+ src/gwn_element.c
+ src/gwn_buffer_id.cpp
+ src/gwn_immediate.c
+ src/gwn_imm_util.c
+ src/gwn_primitive.c
+ src/gwn_shader_interface.c
+ src/gwn_vertex_array_id.cpp
+ src/gwn_vertex_buffer.c
+ src/gwn_vertex_format.c
+
+ gawain/gwn_attr_binding.h
+ gawain/gwn_attr_binding_private.h
+ gawain/gwn_batch.h
+ gawain/gwn_batch_private.h
+ gawain/gwn_buffer_id.h
+ gawain/gwn_common.h
+ gawain/gwn_element.h
+ gawain/gwn_imm_util.h
+ gawain/gwn_immediate.h
+ gawain/gwn_primitive.h
+ gawain/gwn_primitive_private.h
+ gawain/gwn_shader_interface.h
+ gawain/gwn_vertex_array_id.h
+ gawain/gwn_vertex_buffer.h
+ gawain/gwn_vertex_format.h
+ gawain/gwn_vertex_format_private.h
+)
+
+add_definitions(${GL_DEFINITIONS})
+
+blender_add_lib(bf_intern_gawain "${SRC}" "${INC}" "${INC_SYS}")
diff --git a/intern/gawain/gawain/gwn_attr_binding.h b/intern/gawain/gawain/gwn_attr_binding.h
new file mode 100644
index 00000000000..a209e1c4f0f
--- /dev/null
+++ b/intern/gawain/gawain/gwn_attr_binding.h
@@ -0,0 +1,19 @@
+
+// Gawain vertex attribute binding
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+#include "gwn_common.h"
+
+typedef struct Gwn_AttrBinding {
+ uint64_t loc_bits; // store 4 bits for each of the 16 attribs
+ uint16_t enabled_bits; // 1 bit for each attrib
+} Gwn_AttrBinding;
diff --git a/intern/gawain/gawain/gwn_attr_binding_private.h b/intern/gawain/gawain/gwn_attr_binding_private.h
new file mode 100644
index 00000000000..300945d464b
--- /dev/null
+++ b/intern/gawain/gawain/gwn_attr_binding_private.h
@@ -0,0 +1,20 @@
+
+// Gawain vertex attribute binding
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2017 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+#include "gwn_vertex_format.h"
+#include "gwn_shader_interface.h"
+
+void AttribBinding_clear(Gwn_AttrBinding*);
+
+void get_attrib_locations(const Gwn_VertFormat*, Gwn_AttrBinding*, const Gwn_ShaderInterface*);
+unsigned read_attrib_location(const Gwn_AttrBinding*, unsigned a_idx);
diff --git a/intern/gawain/gawain/gwn_batch.h b/intern/gawain/gawain/gwn_batch.h
new file mode 100644
index 00000000000..e0cf66637f6
--- /dev/null
+++ b/intern/gawain/gawain/gwn_batch.h
@@ -0,0 +1,169 @@
+
+// Gawain geometry batch
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+#include "gwn_vertex_buffer.h"
+#include "gwn_element.h"
+#include "gwn_shader_interface.h"
+
+typedef enum {
+ GWN_BATCH_READY_TO_FORMAT,
+ GWN_BATCH_READY_TO_BUILD,
+ GWN_BATCH_BUILDING,
+ GWN_BATCH_READY_TO_DRAW
+} Gwn_BatchPhase;
+
+#define GWN_BATCH_VBO_MAX_LEN 3
+#define GWN_BATCH_VAO_STATIC_LEN 3
+#define GWN_BATCH_VAO_DYN_ALLOC_COUNT 16
+
+typedef struct Gwn_Batch {
+ // geometry
+ Gwn_VertBuf* verts[GWN_BATCH_VBO_MAX_LEN]; // verts[0] is required, others can be NULL
+ Gwn_VertBuf* inst; // instance attribs
+ Gwn_IndexBuf* elem; // NULL if element list not needed
+ uint32_t gl_prim_type;
+
+ // cached values (avoid dereferencing later)
+ uint32_t vao_id;
+ uint32_t program;
+ const struct Gwn_ShaderInterface* interface;
+
+ // book-keeping
+ unsigned owns_flag;
+ struct Gwn_Context *context; // used to free all vaos. this implies all vaos were created under the same context.
+ Gwn_BatchPhase phase;
+ bool program_in_use;
+
+ // Vao management: remembers all geometry state (vertex attrib bindings & element buffer)
+ // for each shader interface. Start with a static number of vaos and fallback to dynamic count
+ // if necessary. Once a batch goes dynamic it does not go back.
+ bool is_dynamic_vao_count;
+ union {
+ // Static handle count
+ struct {
+ const struct Gwn_ShaderInterface* interfaces[GWN_BATCH_VAO_STATIC_LEN];
+ uint32_t vao_ids[GWN_BATCH_VAO_STATIC_LEN];
+ } static_vaos;
+ // Dynamic handle count
+ struct {
+ unsigned count;
+ const struct Gwn_ShaderInterface** interfaces;
+ uint32_t* vao_ids;
+ } dynamic_vaos;
+ };
+
+ // XXX This is the only solution if we want to have some data structure using
+ // batches as key to identify nodes. We must destroy these nodes with this callback.
+ void (*free_callback)(struct Gwn_Batch*, void*);
+ void* callback_data;
+} Gwn_Batch;
+
+enum {
+ GWN_BATCH_OWNS_VBO = (1 << 0),
+ /* each vbo index gets bit-shifted */
+ GWN_BATCH_OWNS_INSTANCES = (1 << 30),
+ GWN_BATCH_OWNS_INDEX = (1 << 31),
+};
+
+Gwn_Batch* GWN_batch_create_ex(Gwn_PrimType, Gwn_VertBuf*, Gwn_IndexBuf*, unsigned owns_flag);
+void GWN_batch_init_ex(Gwn_Batch*, Gwn_PrimType, Gwn_VertBuf*, Gwn_IndexBuf*, unsigned owns_flag);
+Gwn_Batch* GWN_batch_duplicate(Gwn_Batch* batch_src);
+
+#define GWN_batch_create(prim, verts, elem) \
+ GWN_batch_create_ex(prim, verts, elem, 0)
+#define GWN_batch_init(batch, prim, verts, elem) \
+ GWN_batch_init_ex(batch, prim, verts, elem, 0)
+
+void GWN_batch_discard(Gwn_Batch*); // verts & elem are not discarded
+
+void GWN_batch_callback_free_set(Gwn_Batch*, void (*callback)(Gwn_Batch*, void*), void*);
+
+void GWN_batch_instbuf_set(Gwn_Batch*, Gwn_VertBuf*, bool own_vbo); // Instancing
+
+int GWN_batch_vertbuf_add_ex(Gwn_Batch*, Gwn_VertBuf*, bool own_vbo);
+
+#define GWN_batch_vertbuf_add(batch, verts) \
+ GWN_batch_vertbuf_add_ex(batch, verts, false)
+
+void GWN_batch_program_set_no_use(Gwn_Batch*, uint32_t program, const Gwn_ShaderInterface*);
+void GWN_batch_program_set(Gwn_Batch*, uint32_t program, const Gwn_ShaderInterface*);
+// Entire batch draws with one shader program, but can be redrawn later with another program.
+// Vertex shader's inputs must be compatible with the batch's vertex format.
+
+void GWN_batch_program_use_begin(Gwn_Batch*); // call before Batch_Uniform (temp hack?)
+void GWN_batch_program_use_end(Gwn_Batch*);
+
+void GWN_batch_uniform_1ui(Gwn_Batch*, const char* name, int value);
+void GWN_batch_uniform_1i(Gwn_Batch*, const char* name, int value);
+void GWN_batch_uniform_1b(Gwn_Batch*, const char* name, bool value);
+void GWN_batch_uniform_1f(Gwn_Batch*, const char* name, float value);
+void GWN_batch_uniform_2f(Gwn_Batch*, const char* name, float x, float y);
+void GWN_batch_uniform_3f(Gwn_Batch*, const char* name, float x, float y, float z);
+void GWN_batch_uniform_4f(Gwn_Batch*, const char* name, float x, float y, float z, float w);
+void GWN_batch_uniform_2fv(Gwn_Batch*, const char* name, const float data[2]);
+void GWN_batch_uniform_3fv(Gwn_Batch*, const char* name, const float data[3]);
+void GWN_batch_uniform_4fv(Gwn_Batch*, const char* name, const float data[4]);
+void GWN_batch_uniform_2fv_array(Gwn_Batch*, const char* name, int len, const float *data);
+void GWN_batch_uniform_4fv_array(Gwn_Batch*, const char* name, int len, const float *data);
+void GWN_batch_uniform_mat4(Gwn_Batch*, const char* name, const float data[4][4]);
+
+void GWN_batch_draw(Gwn_Batch*);
+
+// This does not bind/unbind shader and does not call gpuBindMatrices()
+void GWN_batch_draw_range_ex(Gwn_Batch*, int v_first, int v_count, bool force_instance);
+
+// Does not even need batch
+void GWN_draw_primitive(Gwn_PrimType, int v_count);
+
+#if 0 // future plans
+
+// Can multiple batches share a Gwn_VertBuf? Use ref count?
+
+
+// We often need a batch with its own data, to be created and discarded together.
+// WithOwn variants reduce number of system allocations.
+
+typedef struct BatchWithOwnVertexBuffer {
+ Gwn_Batch batch;
+ Gwn_VertBuf verts; // link batch.verts to this
+} BatchWithOwnVertexBuffer;
+
+typedef struct BatchWithOwnElementList {
+ Gwn_Batch batch;
+ Gwn_IndexBuf elem; // link batch.elem to this
+} BatchWithOwnElementList;
+
+typedef struct BatchWithOwnVertexBufferAndElementList {
+ Gwn_Batch batch;
+ Gwn_IndexBuf elem; // link batch.elem to this
+ Gwn_VertBuf verts; // link batch.verts to this
+} BatchWithOwnVertexBufferAndElementList;
+
+Gwn_Batch* create_BatchWithOwnVertexBuffer(Gwn_PrimType, Gwn_VertFormat*, unsigned v_ct, Gwn_IndexBuf*);
+Gwn_Batch* create_BatchWithOwnElementList(Gwn_PrimType, Gwn_VertBuf*, unsigned prim_ct);
+Gwn_Batch* create_BatchWithOwnVertexBufferAndElementList(Gwn_PrimType, Gwn_VertFormat*, unsigned v_ct, unsigned prim_ct);
+// verts: shared, own
+// elem: none, shared, own
+Gwn_Batch* create_BatchInGeneral(Gwn_PrimType, VertexBufferStuff, ElementListStuff);
+
+#endif // future plans
+
+
+/* Macros */
+
+#define GWN_BATCH_DISCARD_SAFE(batch) do { \
+ if (batch != NULL) { \
+ GWN_batch_discard(batch); \
+ batch = NULL; \
+ } \
+} while (0)
diff --git a/intern/gawain/gawain/gwn_batch_private.h b/intern/gawain/gawain/gwn_batch_private.h
new file mode 100644
index 00000000000..6503429c237
--- /dev/null
+++ b/intern/gawain/gawain/gwn_batch_private.h
@@ -0,0 +1,30 @@
+
+// Gawain context
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2018 Mike Erwin, Clément Foucault
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "gwn_batch.h"
+#include "gwn_context.h"
+#include "gwn_shader_interface.h"
+
+void gwn_batch_remove_interface_ref(Gwn_Batch*, const Gwn_ShaderInterface*);
+void gwn_batch_vao_cache_clear(Gwn_Batch*);
+
+void gwn_context_add_batch(Gwn_Context*, Gwn_Batch*);
+void gwn_context_remove_batch(Gwn_Context*, Gwn_Batch*);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/intern/gawain/gawain/gwn_buffer_id.h b/intern/gawain/gawain/gwn_buffer_id.h
new file mode 100644
index 00000000000..6f51ca6905d
--- /dev/null
+++ b/intern/gawain/gawain/gwn_buffer_id.h
@@ -0,0 +1,30 @@
+
+// Gawain buffer IDs
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+// Manage GL buffer IDs in a thread-safe way
+// Use these instead of glGenBuffers & its friends
+// - alloc must be called from main thread
+// - free can be called from any thread
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "gwn_common.h"
+
+GLuint GWN_buf_id_alloc(void);
+void GWN_buf_id_free(GLuint buffer_id);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/intern/gawain/gawain/gwn_common.h b/intern/gawain/gawain/gwn_common.h
new file mode 100644
index 00000000000..6a56543da40
--- /dev/null
+++ b/intern/gawain/gawain/gwn_common.h
@@ -0,0 +1,39 @@
+
+// Gawain common #defines and #includes
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+#define PROGRAM_NO_OPTI 0
+
+#if defined(NDEBUG)
+ #define TRUST_NO_ONE 0
+#else
+ // strict error checking, enabled for debug builds during early development
+ #define TRUST_NO_ONE 1
+#endif
+
+#if defined(WITH_OPENGL)
+ #include <GL/glew.h>
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#if TRUST_NO_ONE
+ #include <assert.h>
+#endif
+
+/* GWN_INLINE */
+#if defined(_MSC_VER)
+# define GWN_INLINE static __forceinline
+#else
+# define GWN_INLINE static inline __attribute__((always_inline)) __attribute__((__unused__))
+#endif
diff --git a/intern/gawain/gawain/gwn_context.h b/intern/gawain/gawain/gwn_context.h
new file mode 100644
index 00000000000..3addce762b3
--- /dev/null
+++ b/intern/gawain/gawain/gwn_context.h
@@ -0,0 +1,34 @@
+
+// Gawain context
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2018 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+// This interface allow Gawain to manage VAOs for mutiple context and threads.
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "gwn_common.h"
+#include "gwn_batch.h"
+#include "gwn_shader_interface.h"
+
+typedef struct Gwn_Context Gwn_Context;
+
+Gwn_Context* GWN_context_create(void);
+void GWN_context_discard(Gwn_Context*);
+
+void GWN_context_active_set(Gwn_Context*);
+Gwn_Context* GWN_context_active_get(void);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/intern/gawain/gawain/gwn_element.h b/intern/gawain/gawain/gwn_element.h
new file mode 100644
index 00000000000..4bd0c34675a
--- /dev/null
+++ b/intern/gawain/gawain/gwn_element.h
@@ -0,0 +1,79 @@
+
+// Gawain element list (AKA index buffer)
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+#include "gwn_primitive.h"
+
+#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,
+ GWN_INDEX_U32
+} Gwn_IndexBufType;
+
+typedef struct Gwn_IndexBuf {
+ unsigned index_ct;
+#if GWN_TRACK_INDEX_RANGE
+ Gwn_IndexBufType index_type;
+ uint32_t gl_index_type;
+ unsigned min_index;
+ unsigned max_index;
+ unsigned base_index;
+#endif
+ uint32_t vbo_id; // 0 indicates not yet sent to VRAM
+ bool use_prim_restart;
+} Gwn_IndexBuf;
+
+void GWN_indexbuf_use(Gwn_IndexBuf*);
+unsigned GWN_indexbuf_size_get(const Gwn_IndexBuf*);
+
+typedef struct Gwn_IndexBufBuilder {
+ unsigned max_allowed_index;
+ unsigned max_index_ct;
+ unsigned index_ct;
+ Gwn_PrimType prim_type;
+ unsigned* data;
+ bool use_prim_restart;
+} Gwn_IndexBufBuilder;
+
+
+// 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_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);
+void GWN_indexbuf_add_tri_verts(Gwn_IndexBufBuilder*, unsigned v1, unsigned v2, unsigned v3);
+void GWN_indexbuf_add_line_adj_verts(Gwn_IndexBufBuilder*, unsigned v1, unsigned v2, unsigned v3, unsigned v4);
+
+Gwn_IndexBuf* GWN_indexbuf_build(Gwn_IndexBufBuilder*);
+void GWN_indexbuf_build_in_place(Gwn_IndexBufBuilder*, Gwn_IndexBuf*);
+
+void GWN_indexbuf_discard(Gwn_IndexBuf*);
+
+
+/* Macros */
+
+#define GWN_INDEXBUF_DISCARD_SAFE(elem) do { \
+ if (elem != NULL) { \
+ GWN_indexbuf_discard(elem); \
+ elem = NULL; \
+ } \
+} while (0)
diff --git a/intern/gawain/gawain/gwn_imm_util.h b/intern/gawain/gawain/gwn_imm_util.h
new file mode 100644
index 00000000000..5d17ec50669
--- /dev/null
+++ b/intern/gawain/gawain/gwn_imm_util.h
@@ -0,0 +1,22 @@
+
+// Gawain immediate mode drawing utilities
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+
+// Draw 2D rectangles (replaces glRect functions)
+// caller is reponsible for vertex format & shader
+void immRectf(unsigned pos, float x1, float y1, float x2, float y2);
+void immRecti(unsigned pos, int x1, int y1, int x2, int y2);
+
+// Same as immRectf/immRecti but does not call immBegin/immEnd. To use with GWN_PRIM_TRIS.
+void immRectf_fast_with_color(unsigned pos, unsigned col, float x1, float y1, float x2, float y2, const float color[4]);
+void immRecti_fast_with_color(unsigned pos, unsigned col, int x1, int y1, int x2, int y2, const float color[4]);
diff --git a/intern/gawain/gawain/gwn_immediate.h b/intern/gawain/gawain/gwn_immediate.h
new file mode 100644
index 00000000000..35f9c9d9e2c
--- /dev/null
+++ b/intern/gawain/gawain/gwn_immediate.h
@@ -0,0 +1,120 @@
+
+// Gawain immediate mode work-alike
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+#include "gwn_vertex_format.h"
+#include "gwn_primitive.h"
+#include "gwn_shader_interface.h"
+
+#define IMM_BATCH_COMBO 1
+
+
+Gwn_VertFormat* immVertexFormat(void); // returns a cleared vertex format, ready for add_attrib
+
+void immBindProgram(uint32_t program, const Gwn_ShaderInterface*); // every immBegin must have a program bound first
+void immUnbindProgram(void); // call after your last immEnd, or before binding another program
+
+void immBegin(Gwn_PrimType, unsigned vertex_ct); // must supply exactly vertex_ct vertices
+void immBeginAtMost(Gwn_PrimType, unsigned max_vertex_ct); // can supply fewer vertices
+void immEnd(void); // finishes and draws
+
+#if IMM_BATCH_COMBO
+#include "gwn_batch.h"
+// immBegin a batch, then use standard immFunctions as usual.
+// immEnd will finalize the batch instead of drawing.
+// Then you can draw it as many times as you like! Partially replaces the need for display lists.
+Gwn_Batch* immBeginBatch(Gwn_PrimType, unsigned vertex_ct);
+Gwn_Batch* immBeginBatchAtMost(Gwn_PrimType, unsigned vertex_ct);
+#endif
+
+
+// provide attribute values that can change per vertex
+// first vertex after immBegin must have all its attributes specified
+// skipped attributes will continue using the previous value for that attrib_id
+void immAttrib1f(unsigned attrib_id, float x);
+void immAttrib2f(unsigned attrib_id, float x, float y);
+void immAttrib3f(unsigned attrib_id, float x, float y, float z);
+void immAttrib4f(unsigned attrib_id, float x, float y, float z, float w);
+
+void immAttrib2i(unsigned attrib_id, int x, int y);
+
+void immAttrib1u(unsigned attrib_id, unsigned x);
+
+void immAttrib2s(unsigned attrib_id, short x, short y);
+
+void immAttrib2fv(unsigned attrib_id, const float data[2]);
+void immAttrib3fv(unsigned attrib_id, const float data[3]);
+void immAttrib4fv(unsigned attrib_id, const float data[4]);
+
+void immAttrib3ub(unsigned attrib_id, unsigned char r, unsigned char g, unsigned char b);
+void immAttrib4ub(unsigned attrib_id, unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+
+void immAttrib3ubv(unsigned attrib_id, const unsigned char data[4]);
+void immAttrib4ubv(unsigned attrib_id, const unsigned char data[4]);
+
+// explicitly skip an attribute
+// this advanced option kills automatic value copying for this attrib_id
+void immSkipAttrib(unsigned attrib_id);
+
+
+// provide one last attribute value & end the current vertex
+// this is most often used for 2D or 3D position (similar to glVertex)
+void immVertex2f(unsigned attrib_id, float x, float y);
+void immVertex3f(unsigned attrib_id, float x, float y, float z);
+void immVertex4f(unsigned attrib_id, float x, float y, float z, float w);
+
+void immVertex2i(unsigned attrib_id, int x, int y);
+
+void immVertex2s(unsigned attrib_id, short x, short y);
+
+void immVertex2fv(unsigned attrib_id, const float data[2]);
+void immVertex3fv(unsigned attrib_id, const float data[3]);
+
+void immVertex2iv(unsigned attrib_id, const int data[2]);
+
+
+// provide uniform values that don't change for the entire draw call
+void immUniform1i(const char* name, int x);
+void immUniform4iv(const char* name, const int data[4]);
+void immUniform1f(const char* name, float x);
+void immUniform2f(const char* name, float x, float y);
+void immUniform2fv(const char* name, const float data[2]);
+void immUniform3f(const char* name, float x, float y, float z);
+void immUniform3fv(const char* name, const float data[3]);
+void immUniformArray3fv(const char* name, const float *data, int count);
+void immUniform4f(const char* name, float x, float y, float z, float w);
+void immUniform4fv(const char* name, const float data[4]);
+void immUniformArray4fv(const char* bare_name, const float *data, int count);
+void immUniformMatrix4fv(const char* name, const float data[4][4]);
+
+
+// convenience functions for setting "uniform vec4 color"
+// the rgb functions have implicit alpha = 1.0
+void immUniformColor4f(float r, float g, float b, float a);
+void immUniformColor4fv(const float rgba[4]);
+void immUniformColor3f(float r, float g, float b);
+void immUniformColor3fv(const float rgb[3]);
+void immUniformColor3fvAlpha(const float rgb[3], float a);
+
+void immUniformColor3ub(unsigned char r, unsigned char g, unsigned char b);
+void immUniformColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+void immUniformColor3ubv(const unsigned char rgb[3]);
+void immUniformColor3ubvAlpha(const unsigned char rgb[3], unsigned char a);
+void immUniformColor4ubv(const unsigned char rgba[4]);
+
+
+// these are called by the system -- not part of drawing API
+
+void immInit(void);
+void immActivate(void);
+void immDeactivate(void);
+void immDestroy(void);
diff --git a/intern/gawain/gawain/gwn_primitive.h b/intern/gawain/gawain/gwn_primitive.h
new file mode 100644
index 00000000000..3359b3582bb
--- /dev/null
+++ b/intern/gawain/gawain/gwn_primitive.h
@@ -0,0 +1,42 @@
+
+// Gawain geometric primitives
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2017 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+#include "gwn_common.h"
+
+typedef enum {
+ GWN_PRIM_POINTS,
+ GWN_PRIM_LINES,
+ GWN_PRIM_TRIS,
+ GWN_PRIM_LINE_STRIP,
+ GWN_PRIM_LINE_LOOP, // GL has this, Vulkan does not
+ GWN_PRIM_TRI_STRIP,
+ GWN_PRIM_TRI_FAN,
+
+ GWN_PRIM_LINES_ADJ,
+ GWN_PRIM_TRIS_ADJ,
+ GWN_PRIM_LINE_STRIP_ADJ,
+
+ GWN_PRIM_NONE
+} Gwn_PrimType;
+
+// what types of primitives does each shader expect?
+typedef enum {
+ GWN_PRIM_CLASS_NONE = 0,
+ GWN_PRIM_CLASS_POINT = (1 << 0),
+ GWN_PRIM_CLASS_LINE = (1 << 1),
+ GWN_PRIM_CLASS_SURFACE = (1 << 2),
+ GWN_PRIM_CLASS_ANY = GWN_PRIM_CLASS_POINT | GWN_PRIM_CLASS_LINE | GWN_PRIM_CLASS_SURFACE
+} Gwn_PrimClass;
+
+Gwn_PrimClass GWN_primtype_class(Gwn_PrimType);
+bool GWN_primtype_belongs_to_class(Gwn_PrimType, Gwn_PrimClass);
diff --git a/intern/gawain/gawain/gwn_primitive_private.h b/intern/gawain/gawain/gwn_primitive_private.h
new file mode 100644
index 00000000000..d959cd89852
--- /dev/null
+++ b/intern/gawain/gawain/gwn_primitive_private.h
@@ -0,0 +1,14 @@
+
+// Gawain geometric primitives (private interface for use inside Gawain)
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2017 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+GLenum convert_prim_type_to_gl(Gwn_PrimType);
diff --git a/intern/gawain/gawain/gwn_shader_interface.h b/intern/gawain/gawain/gwn_shader_interface.h
new file mode 100644
index 00000000000..ee3d51fb3cc
--- /dev/null
+++ b/intern/gawain/gawain/gwn_shader_interface.h
@@ -0,0 +1,81 @@
+
+// Gawain shader interface (C --> GLSL)
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2017 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+#include "gwn_common.h"
+
+typedef enum {
+ GWN_UNIFORM_NONE = 0, // uninitialized/unknown
+
+ GWN_UNIFORM_MODEL, // mat4 ModelMatrix
+ GWN_UNIFORM_VIEW, // mat4 ViewMatrix
+ GWN_UNIFORM_MODELVIEW, // mat4 ModelViewMatrix
+ GWN_UNIFORM_PROJECTION, // mat4 ProjectionMatrix
+ GWN_UNIFORM_VIEWPROJECTION, // mat4 ViewProjectionMatrix
+ GWN_UNIFORM_MVP, // mat4 ModelViewProjectionMatrix
+
+ GWN_UNIFORM_MODEL_INV, // mat4 ModelMatrixInverse
+ GWN_UNIFORM_VIEW_INV, // mat4 ViewMatrixInverse
+ GWN_UNIFORM_MODELVIEW_INV, // mat4 ModelViewMatrixInverse
+ GWN_UNIFORM_PROJECTION_INV, // mat4 ProjectionMatrixInverse
+ GWN_UNIFORM_VIEWPROJECTION_INV, // mat4 ViewProjectionMatrixInverse
+
+ GWN_UNIFORM_NORMAL, // mat3 NormalMatrix
+ GWN_UNIFORM_WORLDNORMAL, // mat3 WorldNormalMatrix
+ GWN_UNIFORM_CAMERATEXCO, // vec4 CameraTexCoFactors
+ GWN_UNIFORM_ORCO, // vec3 OrcoTexCoFactors[]
+
+ GWN_UNIFORM_COLOR, // vec4 color
+ GWN_UNIFORM_EYE, // vec3 eye
+ GWN_UNIFORM_CALLID, // int callId
+
+ GWN_UNIFORM_CUSTOM, // custom uniform, not one of the above built-ins
+
+ GWN_NUM_UNIFORMS, // Special value, denotes number of builtin uniforms.
+} Gwn_UniformBuiltin;
+
+typedef struct Gwn_ShaderInput {
+ struct Gwn_ShaderInput* next;
+ uint32_t name_offset;
+ unsigned name_hash;
+ Gwn_UniformBuiltin builtin_type; // only for uniform inputs
+ uint32_t gl_type; // only for attrib inputs
+ int32_t size; // only for attrib inputs
+ int32_t location;
+} Gwn_ShaderInput;
+
+#define GWN_NUM_SHADERINTERFACE_BUCKETS 257
+#define GWN_SHADERINTERFACE_REF_ALLOC_COUNT 16
+
+typedef struct Gwn_ShaderInterface {
+ int32_t program;
+ uint32_t name_buffer_offset;
+ Gwn_ShaderInput* attrib_buckets[GWN_NUM_SHADERINTERFACE_BUCKETS];
+ Gwn_ShaderInput* uniform_buckets[GWN_NUM_SHADERINTERFACE_BUCKETS];
+ Gwn_ShaderInput* ubo_buckets[GWN_NUM_SHADERINTERFACE_BUCKETS];
+ Gwn_ShaderInput* builtin_uniforms[GWN_NUM_UNIFORMS];
+ char* name_buffer;
+ struct Gwn_Batch** batches; // references to batches using this interface
+ unsigned batches_ct;
+} Gwn_ShaderInterface;
+
+Gwn_ShaderInterface* GWN_shaderinterface_create(int32_t program_id);
+void GWN_shaderinterface_discard(Gwn_ShaderInterface*);
+
+const Gwn_ShaderInput* GWN_shaderinterface_uniform(const Gwn_ShaderInterface*, const char* name);
+const Gwn_ShaderInput* GWN_shaderinterface_uniform_builtin(const Gwn_ShaderInterface*, Gwn_UniformBuiltin);
+const Gwn_ShaderInput* GWN_shaderinterface_ubo(const Gwn_ShaderInterface*, const char* name);
+const Gwn_ShaderInput* GWN_shaderinterface_attr(const Gwn_ShaderInterface*, const char* name);
+
+// keep track of batches using this interface
+void GWN_shaderinterface_add_batch_ref(Gwn_ShaderInterface*, struct Gwn_Batch*);
+void GWN_shaderinterface_remove_batch_ref(Gwn_ShaderInterface*, struct Gwn_Batch*);
diff --git a/intern/gawain/gawain/gwn_vertex_array_id.h b/intern/gawain/gawain/gwn_vertex_array_id.h
new file mode 100644
index 00000000000..1c093d428ce
--- /dev/null
+++ b/intern/gawain/gawain/gwn_vertex_array_id.h
@@ -0,0 +1,34 @@
+
+// Gawain buffer IDs
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2018 Mike Erwin, Clément Foucault
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+// Manage GL vertex array IDs in a thread-safe way
+// Use these instead of glGenBuffers & its friends
+// - alloc must be called from a thread that is bound
+// to the context that will be used for drawing with
+// this vao.
+// - free can be called from any thread
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "gwn_common.h"
+#include "gwn_context.h"
+
+GLuint GWN_vao_default(void);
+GLuint GWN_vao_alloc(void);
+void GWN_vao_free(GLuint vao_id, Gwn_Context*);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/intern/gawain/gawain/gwn_vertex_buffer.h b/intern/gawain/gawain/gwn_vertex_buffer.h
new file mode 100644
index 00000000000..b92e4a87698
--- /dev/null
+++ b/intern/gawain/gawain/gwn_vertex_buffer.h
@@ -0,0 +1,125 @@
+
+// Gawain vertex buffer
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+#include "gwn_vertex_format.h"
+
+#define VRAM_USAGE 1
+// How to create a Gwn_VertBuf:
+// 1) verts = GWN_vertbuf_create() or GWN_vertbuf_init(verts)
+// 2) GWN_vertformat_attr_add(verts->format, ...)
+// 3) GWN_vertbuf_data_alloc(verts, vertex_ct) <-- finalizes/packs vertex format
+// 4) GWN_vertbuf_attr_fill(verts, pos, application_pos_buffer)
+
+// 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, // do not keep data in memory
+ GWN_USAGE_DYNAMIC
+} Gwn_UsageType;
+
+typedef struct Gwn_VertBuf {
+ Gwn_VertFormat format;
+ unsigned vertex_ct; // number of verts we want to draw
+ unsigned vertex_alloc; // number of verts data
+ bool dirty;
+ unsigned char* data; // NULL indicates data in VRAM (unmapped)
+ uint32_t vbo_id; // 0 indicates not yet allocated
+ Gwn_UsageType usage; // usage hint for GL optimisation
+} Gwn_VertBuf;
+
+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_discard(Gwn_VertBuf*);
+
+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_vertex_count_set(Gwn_VertBuf*, unsigned v_ct);
+
+// 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
+// to the vertex attribute's type and component count. They're in control of both, so this
+// should not be a problem.
+
+void GWN_vertbuf_attr_set(Gwn_VertBuf*, unsigned a_idx, unsigned v_idx, const void* data);
+void GWN_vertbuf_attr_fill(Gwn_VertBuf*, unsigned a_idx, const void* data); // tightly packed, non interleaved input data
+void GWN_vertbuf_attr_fill_stride(Gwn_VertBuf*, unsigned a_idx, unsigned stride, const void* data);
+
+// For low level access only
+typedef struct Gwn_VertBufRaw {
+ unsigned size;
+ unsigned stride;
+ unsigned char* data;
+ unsigned char* data_init;
+#if TRUST_NO_ONE
+ // Only for overflow check
+ unsigned char* _data_end;
+#endif
+} Gwn_VertBufRaw;
+
+GWN_INLINE void *GWN_vertbuf_raw_step(Gwn_VertBufRaw *a)
+ {
+ unsigned char* data = a->data;
+ a->data += a->stride;
+#if TRUST_NO_ONE
+ assert(data < a->_data_end);
+#endif
+ return (void *)data;
+ }
+
+GWN_INLINE unsigned GWN_vertbuf_raw_used(Gwn_VertBufRaw *a)
+ {
+ return ((a->data - a->data_init) / a->stride);
+ }
+
+void GWN_vertbuf_attr_get_raw_data(Gwn_VertBuf*, unsigned a_idx, Gwn_VertBufRaw *access);
+
+// TODO: decide whether to keep the functions below
+// doesn't immediate mode satisfy these needs?
+
+// void setAttrib1f(unsigned a_idx, unsigned v_idx, float x);
+// void setAttrib2f(unsigned a_idx, unsigned v_idx, float x, float y);
+// void setAttrib3f(unsigned a_idx, unsigned v_idx, float x, float y, float z);
+// void setAttrib4f(unsigned a_idx, unsigned v_idx, float x, float y, float z, float w);
+//
+// void setAttrib3ub(unsigned a_idx, unsigned v_idx, unsigned char r, unsigned char g, unsigned char b);
+// void setAttrib4ub(unsigned a_idx, unsigned v_idx, unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+
+void GWN_vertbuf_use(Gwn_VertBuf*);
+
+
+// Metrics
+
+unsigned GWN_vertbuf_get_memory_usage(void);
+
+
+// Macros
+
+#define GWN_VERTBUF_DISCARD_SAFE(verts) do { \
+ if (verts != NULL) { \
+ GWN_vertbuf_discard(verts); \
+ verts = NULL; \
+ } \
+} while (0)
diff --git a/intern/gawain/gawain/gwn_vertex_format.h b/intern/gawain/gawain/gwn_vertex_format.h
new file mode 100644
index 00000000000..503c2d03c42
--- /dev/null
+++ b/intern/gawain/gawain/gwn_vertex_format.h
@@ -0,0 +1,78 @@
+
+// Gawain vertex format
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+#include "gwn_common.h"
+
+#define GWN_VERT_ATTR_MAX_LEN 16
+#define GWN_VERT_ATTR_MAX_NAMES 3
+#define GWN_VERT_ATTR_NAME_AVERAGE_LEN 11
+#define GWN_VERT_ATTR_NAMES_BUF_LEN ((GWN_VERT_ATTR_NAME_AVERAGE_LEN + 1) * GWN_VERT_ATTR_MAX_LEN)
+
+typedef enum {
+ GWN_COMP_I8,
+ GWN_COMP_U8,
+ GWN_COMP_I16,
+ GWN_COMP_U16,
+ GWN_COMP_I32,
+ GWN_COMP_U32,
+
+ GWN_COMP_F32,
+
+ GWN_COMP_I10
+} Gwn_VertCompType;
+
+typedef enum {
+ GWN_FETCH_FLOAT,
+ GWN_FETCH_INT,
+ GWN_FETCH_INT_TO_FLOAT_UNIT, // 127 (ubyte) -> 0.5 (and so on for other int types)
+ GWN_FETCH_INT_TO_FLOAT // 127 (any int type) -> 127.0
+} Gwn_VertFetchMode;
+
+typedef struct Gwn_VertAttr {
+ Gwn_VertFetchMode fetch_mode;
+ Gwn_VertCompType comp_type;
+ unsigned gl_comp_type;
+ unsigned comp_ct; // 1 to 4 or 8 or 12 or 16
+ unsigned sz; // size in bytes, 1 to 64
+ unsigned offset; // from beginning of vertex, in bytes
+ unsigned name_ct; // up to GWN_VERT_ATTR_MAX_NAMES
+ const char* name[GWN_VERT_ATTR_MAX_NAMES];
+} Gwn_VertAttr;
+
+typedef struct Gwn_VertFormat {
+ unsigned attrib_ct; // 0 to 16 (GWN_VERT_ATTR_MAX_LEN)
+ unsigned name_ct; // total count of active vertex attrib
+ unsigned stride; // stride in bytes, 1 to 256
+ unsigned name_offset;
+ bool packed;
+ char names[GWN_VERT_ATTR_NAMES_BUF_LEN];
+ Gwn_VertAttr attribs[GWN_VERT_ATTR_MAX_LEN]; // TODO: variable-size attribs array
+} Gwn_VertFormat;
+
+void GWN_vertformat_clear(Gwn_VertFormat*);
+void GWN_vertformat_copy(Gwn_VertFormat* dest, const Gwn_VertFormat* src);
+
+unsigned GWN_vertformat_attr_add(Gwn_VertFormat*, const char* name, Gwn_VertCompType, unsigned comp_ct, Gwn_VertFetchMode);
+void GWN_vertformat_alias_add(Gwn_VertFormat*, const char* alias);
+
+// format conversion
+
+typedef struct Gwn_PackedNormal {
+ int x : 10;
+ int y : 10;
+ int z : 10;
+ int w : 2; // 0 by default, can manually set to { -2, -1, 0, 1 }
+} Gwn_PackedNormal;
+
+Gwn_PackedNormal GWN_normal_convert_i10_v3(const float data[3]);
+Gwn_PackedNormal GWN_normal_convert_i10_s3(const short data[3]);
diff --git a/intern/gawain/gawain/gwn_vertex_format_private.h b/intern/gawain/gawain/gwn_vertex_format_private.h
new file mode 100644
index 00000000000..c1a0f734eda
--- /dev/null
+++ b/intern/gawain/gawain/gwn_vertex_format_private.h
@@ -0,0 +1,16 @@
+
+// Gawain vertex format (private interface for use inside Gawain)
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016-2017 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+void VertexFormat_pack(Gwn_VertFormat*);
+unsigned padding(unsigned offset, unsigned alignment);
+unsigned vertex_buffer_size(const Gwn_VertFormat*, unsigned vertex_ct);
diff --git a/intern/gawain/src/gwn_attr_binding.c b/intern/gawain/src/gwn_attr_binding.c
new file mode 100644
index 00000000000..7647a927b1e
--- /dev/null
+++ b/intern/gawain/src/gwn_attr_binding.c
@@ -0,0 +1,70 @@
+
+// Gawain vertex attribute binding
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#include "gwn_attr_binding.h"
+#include "gwn_attr_binding_private.h"
+#include <stddef.h>
+
+#if GWN_VERT_ATTR_MAX_LEN != 16
+ #error "attrib binding code assumes GWN_VERT_ATTR_MAX_LEN = 16"
+#endif
+
+void AttribBinding_clear(Gwn_AttrBinding* binding)
+ {
+ binding->loc_bits = 0;
+ binding->enabled_bits = 0;
+ }
+
+unsigned read_attrib_location(const Gwn_AttrBinding* binding, unsigned a_idx)
+ {
+#if TRUST_NO_ONE
+ assert(a_idx < GWN_VERT_ATTR_MAX_LEN);
+ assert(binding->enabled_bits & (1 << a_idx));
+#endif
+
+ return (binding->loc_bits >> (4 * a_idx)) & 0xF;
+ }
+
+static void write_attrib_location(Gwn_AttrBinding* binding, unsigned a_idx, unsigned location)
+ {
+#if TRUST_NO_ONE
+ assert(a_idx < GWN_VERT_ATTR_MAX_LEN);
+ assert(location < GWN_VERT_ATTR_MAX_LEN);
+#endif
+
+ const unsigned shift = 4 * a_idx;
+ const uint64_t mask = ((uint64_t)0xF) << shift;
+ // overwrite this attrib's previous location
+ binding->loc_bits = (binding->loc_bits & ~mask) | (location << shift);
+ // mark this attrib as enabled
+ binding->enabled_bits |= 1 << a_idx;
+ }
+
+void get_attrib_locations(const Gwn_VertFormat* format, Gwn_AttrBinding* binding, const Gwn_ShaderInterface* shaderface)
+ {
+ AttribBinding_clear(binding);
+
+ for (unsigned a_idx = 0; a_idx < format->attrib_ct; ++a_idx)
+ {
+ const Gwn_VertAttr* a = format->attribs + a_idx;
+ for (unsigned n_idx = 0; n_idx < a->name_ct; ++n_idx)
+ {
+ const Gwn_ShaderInput* input = GWN_shaderinterface_attr(shaderface, a->name[n_idx]);
+
+#if TRUST_NO_ONE
+ assert(input != NULL);
+ // TODO: make this a recoverable runtime error? indicates mismatch between vertex format and program
+#endif
+
+ write_attrib_location(binding, a_idx, input->location);
+ }
+ }
+ }
diff --git a/intern/gawain/src/gwn_batch.c b/intern/gawain/src/gwn_batch.c
new file mode 100644
index 00000000000..adc72e171c0
--- /dev/null
+++ b/intern/gawain/src/gwn_batch.c
@@ -0,0 +1,632 @@
+
+// Gawain geometry batch
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#include "gwn_batch.h"
+#include "gwn_batch_private.h"
+#include "gwn_buffer_id.h"
+#include "gwn_vertex_array_id.h"
+#include "gwn_primitive_private.h"
+#include <stdlib.h>
+#include <string.h>
+
+// necessary functions from matrix API
+extern void gpuBindMatrices(const Gwn_ShaderInterface* shaderface);
+
+static void batch_update_program_bindings(Gwn_Batch* batch, unsigned int v_first);
+
+void gwn_batch_vao_cache_clear(Gwn_Batch* batch)
+ {
+ if (batch->context == NULL)
+ return;
+
+ if (batch->is_dynamic_vao_count)
+ {
+ for (int i = 0; i < batch->dynamic_vaos.count; ++i)
+ {
+ if (batch->dynamic_vaos.vao_ids[i])
+ GWN_vao_free(batch->dynamic_vaos.vao_ids[i], batch->context);
+ if (batch->dynamic_vaos.interfaces[i])
+ GWN_shaderinterface_remove_batch_ref((Gwn_ShaderInterface *)batch->dynamic_vaos.interfaces[i], batch);
+ }
+ free(batch->dynamic_vaos.interfaces);
+ free(batch->dynamic_vaos.vao_ids);
+ }
+ else
+ {
+ for (int i = 0; i < GWN_BATCH_VAO_STATIC_LEN; ++i)
+ {
+ if (batch->static_vaos.vao_ids[i])
+ GWN_vao_free(batch->static_vaos.vao_ids[i], batch->context);
+ if (batch->static_vaos.interfaces[i])
+ GWN_shaderinterface_remove_batch_ref((Gwn_ShaderInterface *)batch->static_vaos.interfaces[i], batch);
+ }
+ }
+
+ batch->is_dynamic_vao_count = false;
+ for (int i = 0; i < GWN_BATCH_VAO_STATIC_LEN; ++i)
+ {
+ batch->static_vaos.vao_ids[i] = 0;
+ batch->static_vaos.interfaces[i] = NULL;
+ }
+
+ gwn_context_remove_batch(batch->context, batch);
+ batch->context = NULL;
+ }
+
+Gwn_Batch* GWN_batch_create_ex(
+ Gwn_PrimType prim_type, Gwn_VertBuf* verts, Gwn_IndexBuf* elem,
+ unsigned owns_flag)
+ {
+ Gwn_Batch* batch = calloc(1, sizeof(Gwn_Batch));
+
+ GWN_batch_init_ex(batch, prim_type, verts, elem, owns_flag);
+
+ return batch;
+ }
+
+void GWN_batch_init_ex(
+ Gwn_Batch* batch, Gwn_PrimType prim_type, Gwn_VertBuf* verts, Gwn_IndexBuf* elem,
+ unsigned owns_flag)
+ {
+#if TRUST_NO_ONE
+ assert(verts != NULL);
+#endif
+
+ batch->verts[0] = verts;
+ for (int v = 1; v < GWN_BATCH_VBO_MAX_LEN; ++v)
+ batch->verts[v] = NULL;
+ batch->inst = NULL;
+ batch->elem = elem;
+ batch->gl_prim_type = convert_prim_type_to_gl(prim_type);
+ batch->phase = GWN_BATCH_READY_TO_DRAW;
+ batch->is_dynamic_vao_count = false;
+ batch->owns_flag = owns_flag;
+ batch->free_callback = NULL;
+ }
+
+// This will share the VBOs with the new batch
+Gwn_Batch* GWN_batch_duplicate(Gwn_Batch* batch_src)
+ {
+ Gwn_Batch* batch = GWN_batch_create_ex(GWN_PRIM_POINTS, batch_src->verts[0], batch_src->elem, 0);
+
+ batch->gl_prim_type = batch_src->gl_prim_type;
+ for (int v = 1; v < GWN_BATCH_VBO_MAX_LEN; ++v)
+ batch->verts[v] = batch_src->verts[v];
+
+ return batch;
+ }
+
+void GWN_batch_discard(Gwn_Batch* batch)
+ {
+ if (batch->owns_flag & GWN_BATCH_OWNS_INDEX)
+ GWN_indexbuf_discard(batch->elem);
+
+ if (batch->owns_flag & GWN_BATCH_OWNS_INSTANCES)
+ GWN_vertbuf_discard(batch->inst);
+
+ if ((batch->owns_flag & ~GWN_BATCH_OWNS_INDEX) != 0)
+ {
+ for (int v = 0; v < GWN_BATCH_VBO_MAX_LEN; ++v)
+ {
+ if (batch->verts[v] == NULL)
+ break;
+ if (batch->owns_flag & (1 << v))
+ GWN_vertbuf_discard(batch->verts[v]);
+ }
+ }
+
+ gwn_batch_vao_cache_clear(batch);
+
+ if (batch->free_callback)
+ batch->free_callback(batch, batch->callback_data);
+
+ free(batch);
+ }
+
+void GWN_batch_callback_free_set(Gwn_Batch* batch, void (*callback)(Gwn_Batch*, void*), void* user_data)
+ {
+ batch->free_callback = callback;
+ batch->callback_data = user_data;
+ }
+
+void GWN_batch_instbuf_set(Gwn_Batch* batch, Gwn_VertBuf* inst, bool own_vbo)
+ {
+#if TRUST_NO_ONE
+ assert(inst != NULL);
+#endif
+ // redo the bindings
+ gwn_batch_vao_cache_clear(batch);
+
+ if (batch->inst != NULL && (batch->owns_flag & GWN_BATCH_OWNS_INSTANCES))
+ GWN_vertbuf_discard(batch->inst);
+
+ batch->inst = inst;
+
+ if (own_vbo)
+ batch->owns_flag |= GWN_BATCH_OWNS_INSTANCES;
+ else
+ batch->owns_flag &= ~GWN_BATCH_OWNS_INSTANCES;
+ }
+
+int GWN_batch_vertbuf_add_ex(
+ Gwn_Batch* batch, Gwn_VertBuf* verts,
+ bool own_vbo)
+ {
+ // redo the bindings
+ gwn_batch_vao_cache_clear(batch);
+
+ for (unsigned v = 0; v < GWN_BATCH_VBO_MAX_LEN; ++v)
+ {
+ if (batch->verts[v] == NULL)
+ {
+#if TRUST_NO_ONE
+ // for now all VertexBuffers must have same vertex_ct
+ assert(verts->vertex_ct == batch->verts[0]->vertex_ct);
+ // in the near future we will enable instanced attribs which have their own vertex_ct
+#endif
+ batch->verts[v] = verts;
+ // TODO: mark dirty so we can keep attrib bindings up-to-date
+ if (own_vbo)
+ batch->owns_flag |= (1 << v);
+ return v;
+ }
+ }
+
+ // we only make it this far if there is no room for another Gwn_VertBuf
+#if TRUST_NO_ONE
+ assert(false);
+#endif
+ return -1;
+ }
+
+static GLuint batch_vao_get(Gwn_Batch *batch)
+ {
+ // Search through cache
+ if (batch->is_dynamic_vao_count)
+ {
+ for (int i = 0; i < batch->dynamic_vaos.count; ++i)
+ if (batch->dynamic_vaos.interfaces[i] == batch->interface)
+ return batch->dynamic_vaos.vao_ids[i];
+ }
+ else
+ {
+ for (int i = 0; i < GWN_BATCH_VAO_STATIC_LEN; ++i)
+ if (batch->static_vaos.interfaces[i] == batch->interface)
+ return batch->static_vaos.vao_ids[i];
+ }
+
+ // Set context of this batch.
+ // It will be bound to it until gwn_batch_vao_cache_clear is called.
+ // Until then it can only be drawn with this context.
+ if (batch->context == NULL)
+ {
+ batch->context = GWN_context_active_get();
+ gwn_context_add_batch(batch->context, batch);
+ }
+#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
+
+ // Cache miss, time to add a new entry!
+ GLuint new_vao = 0;
+ if (!batch->is_dynamic_vao_count)
+ {
+ int i; // find first unused slot
+ for (i = 0; i < GWN_BATCH_VAO_STATIC_LEN; ++i)
+ if (batch->static_vaos.vao_ids[i] == 0)
+ break;
+
+ if (i < GWN_BATCH_VAO_STATIC_LEN)
+ {
+ batch->static_vaos.interfaces[i] = batch->interface;
+ batch->static_vaos.vao_ids[i] = new_vao = GWN_vao_alloc();
+ }
+ else
+ {
+ // Not enough place switch to dynamic.
+ batch->is_dynamic_vao_count = true;
+ // Erase previous entries, they will be added back if drawn again.
+ for (int j = 0; j < GWN_BATCH_VAO_STATIC_LEN; ++j)
+ {
+ GWN_shaderinterface_remove_batch_ref((Gwn_ShaderInterface*)batch->static_vaos.interfaces[j], batch);
+ GWN_vao_free(batch->static_vaos.vao_ids[j], batch->context);
+ }
+ // Init dynamic arrays and let the branch below set the values.
+ batch->dynamic_vaos.count = GWN_BATCH_VAO_DYN_ALLOC_COUNT;
+ batch->dynamic_vaos.interfaces = calloc(batch->dynamic_vaos.count, sizeof(Gwn_ShaderInterface*));
+ batch->dynamic_vaos.vao_ids = calloc(batch->dynamic_vaos.count, sizeof(GLuint));
+ }
+ }
+
+ if (batch->is_dynamic_vao_count)
+ {
+ int i; // find first unused slot
+ for (i = 0; i < batch->dynamic_vaos.count; ++i)
+ if (batch->dynamic_vaos.vao_ids[i] == 0)
+ break;
+
+ if (i == batch->dynamic_vaos.count)
+ {
+ // Not enough place, realloc the array.
+ i = batch->dynamic_vaos.count;
+ batch->dynamic_vaos.count += GWN_BATCH_VAO_DYN_ALLOC_COUNT;
+ batch->dynamic_vaos.interfaces = realloc(batch->dynamic_vaos.interfaces, sizeof(Gwn_ShaderInterface*) * batch->dynamic_vaos.count);
+ batch->dynamic_vaos.vao_ids = realloc(batch->dynamic_vaos.vao_ids, sizeof(GLuint) * batch->dynamic_vaos.count);
+ memset(batch->dynamic_vaos.interfaces + i, 0, sizeof(Gwn_ShaderInterface*) * GWN_BATCH_VAO_DYN_ALLOC_COUNT);
+ memset(batch->dynamic_vaos.vao_ids + i, 0, sizeof(GLuint) * GWN_BATCH_VAO_DYN_ALLOC_COUNT);
+ }
+
+ batch->dynamic_vaos.interfaces[i] = batch->interface;
+ batch->dynamic_vaos.vao_ids[i] = new_vao = GWN_vao_alloc();
+ }
+
+ GWN_shaderinterface_add_batch_ref((Gwn_ShaderInterface*)batch->interface, batch);
+
+#if TRUST_NO_ONE
+ assert(new_vao != 0);
+#endif
+
+ // We just got a fresh VAO we need to initialize it.
+ glBindVertexArray(new_vao);
+ batch_update_program_bindings(batch, 0);
+ glBindVertexArray(0);
+
+ return new_vao;
+ }
+
+void GWN_batch_program_set_no_use(Gwn_Batch* batch, uint32_t program, const Gwn_ShaderInterface* shaderface)
+ {
+#if TRUST_NO_ONE
+ assert(glIsProgram(shaderface->program));
+ assert(batch->program_in_use == 0);
+#endif
+
+ batch->interface = shaderface;
+ batch->program = program;
+ batch->vao_id = batch_vao_get(batch);
+ }
+
+void GWN_batch_program_set(Gwn_Batch* batch, uint32_t program, const Gwn_ShaderInterface* shaderface)
+ {
+ GWN_batch_program_set_no_use(batch, program, shaderface);
+ GWN_batch_program_use_begin(batch); // hack! to make Batch_Uniform* simpler
+ }
+
+void gwn_batch_remove_interface_ref(Gwn_Batch* batch, const Gwn_ShaderInterface* interface)
+ {
+ if (batch->is_dynamic_vao_count)
+ {
+ for (int i = 0; i < batch->dynamic_vaos.count; ++i)
+ {
+ if (batch->dynamic_vaos.interfaces[i] == interface)
+ {
+ GWN_vao_free(batch->dynamic_vaos.vao_ids[i], batch->context);
+ batch->dynamic_vaos.vao_ids[i] = 0;
+ batch->dynamic_vaos.interfaces[i] = NULL;
+ break; // cannot have duplicates
+ }
+ }
+ }
+ else
+ {
+ int i;
+ for (i = 0; i < GWN_BATCH_VAO_STATIC_LEN; ++i)
+ {
+ if (batch->static_vaos.interfaces[i] == interface)
+ {
+ GWN_vao_free(batch->static_vaos.vao_ids[i], batch->context);
+ batch->static_vaos.vao_ids[i] = 0;
+ batch->static_vaos.interfaces[i] = NULL;
+ break; // cannot have duplicates
+ }
+ }
+ }
+ }
+
+static void create_bindings(Gwn_VertBuf* verts, const Gwn_ShaderInterface* interface, unsigned int v_first, const bool use_instancing)
+ {
+ const Gwn_VertFormat* format = &verts->format;
+
+ const unsigned attrib_ct = format->attrib_ct;
+ const unsigned stride = format->stride;
+
+ GWN_vertbuf_use(verts);
+
+ for (unsigned a_idx = 0; a_idx < attrib_ct; ++a_idx)
+ {
+ const Gwn_VertAttr* a = format->attribs + a_idx;
+
+ const GLvoid* pointer = (const GLubyte*)0 + a->offset + v_first * stride;
+
+ for (unsigned n_idx = 0; n_idx < a->name_ct; ++n_idx)
+ {
+ const Gwn_ShaderInput* input = GWN_shaderinterface_attr(interface, a->name[n_idx]);
+
+ if (input == NULL) continue;
+
+ if (a->comp_ct == 16 || a->comp_ct == 12 || a->comp_ct == 8)
+ {
+#if TRUST_NO_ONE
+ assert(a->fetch_mode == GWN_FETCH_FLOAT);
+ assert(a->gl_comp_type == GL_FLOAT);
+#endif
+ for (int i = 0; i < a->comp_ct / 4; ++i)
+ {
+ glEnableVertexAttribArray(input->location + i);
+ glVertexAttribDivisor(input->location + i, (use_instancing) ? 1 : 0);
+ glVertexAttribPointer(input->location + i, 4, a->gl_comp_type, GL_FALSE, stride,
+ (const GLubyte*)pointer + i * 16);
+ }
+ }
+ else
+ {
+ glEnableVertexAttribArray(input->location);
+ glVertexAttribDivisor(input->location, (use_instancing) ? 1 : 0);
+
+ switch (a->fetch_mode)
+ {
+ case GWN_FETCH_FLOAT:
+ case GWN_FETCH_INT_TO_FLOAT:
+ glVertexAttribPointer(input->location, a->comp_ct, a->gl_comp_type, GL_FALSE, stride, pointer);
+ break;
+ case GWN_FETCH_INT_TO_FLOAT_UNIT:
+ glVertexAttribPointer(input->location, a->comp_ct, a->gl_comp_type, GL_TRUE, stride, pointer);
+ break;
+ case GWN_FETCH_INT:
+ glVertexAttribIPointer(input->location, a->comp_ct, a->gl_comp_type, stride, pointer);
+ }
+ }
+ }
+ }
+ }
+
+static void batch_update_program_bindings(Gwn_Batch* batch, unsigned int v_first)
+ {
+ for (int v = 0; v < GWN_BATCH_VBO_MAX_LEN && batch->verts[v] != NULL; ++v)
+ create_bindings(batch->verts[v], batch->interface, (batch->inst) ? 0 : v_first, false);
+
+ if (batch->inst)
+ create_bindings(batch->inst, batch->interface, v_first, true);
+
+ if (batch->elem)
+ GWN_indexbuf_use(batch->elem);
+ }
+
+void GWN_batch_program_use_begin(Gwn_Batch* batch)
+ {
+ // NOTE: use_program & done_using_program are fragile, depend on staying in sync with
+ // the GL context's active program. use_program doesn't mark other programs as "not used".
+ // TODO: make not fragile (somehow)
+
+ if (!batch->program_in_use)
+ {
+ glUseProgram(batch->program);
+ batch->program_in_use = true;
+ }
+ }
+
+void GWN_batch_program_use_end(Gwn_Batch* batch)
+ {
+ if (batch->program_in_use)
+ {
+#if PROGRAM_NO_OPTI
+ glUseProgram(0);
+#endif
+ batch->program_in_use = false;
+ }
+ }
+
+#if TRUST_NO_ONE
+ #define GET_UNIFORM const Gwn_ShaderInput* uniform = GWN_shaderinterface_uniform(batch->interface, name); assert(uniform);
+#else
+ #define GET_UNIFORM const Gwn_ShaderInput* uniform = GWN_shaderinterface_uniform(batch->interface, name);
+#endif
+
+void GWN_batch_uniform_1ui(Gwn_Batch* batch, const char* name, int value)
+ {
+ GET_UNIFORM
+ glUniform1ui(uniform->location, value);
+ }
+
+void GWN_batch_uniform_1i(Gwn_Batch* batch, const char* name, int value)
+ {
+ GET_UNIFORM
+ glUniform1i(uniform->location, value);
+ }
+
+void GWN_batch_uniform_1b(Gwn_Batch* batch, const char* name, bool value)
+ {
+ GET_UNIFORM
+ glUniform1i(uniform->location, value ? GL_TRUE : GL_FALSE);
+ }
+
+void GWN_batch_uniform_2f(Gwn_Batch* batch, const char* name, float x, float y)
+ {
+ GET_UNIFORM
+ glUniform2f(uniform->location, x, y);
+ }
+
+void GWN_batch_uniform_3f(Gwn_Batch* batch, const char* name, float x, float y, float z)
+ {
+ GET_UNIFORM
+ glUniform3f(uniform->location, x, y, z);
+ }
+
+void GWN_batch_uniform_4f(Gwn_Batch* batch, const char* name, float x, float y, float z, float w)
+ {
+ GET_UNIFORM
+ glUniform4f(uniform->location, x, y, z, w);
+ }
+
+void GWN_batch_uniform_1f(Gwn_Batch* batch, const char* name, float x)
+ {
+ GET_UNIFORM
+ glUniform1f(uniform->location, x);
+ }
+
+void GWN_batch_uniform_2fv(Gwn_Batch* batch, const char* name, const float data[2])
+ {
+ GET_UNIFORM
+ glUniform2fv(uniform->location, 1, data);
+ }
+
+void GWN_batch_uniform_3fv(Gwn_Batch* batch, const char* name, const float data[3])
+ {
+ GET_UNIFORM
+ glUniform3fv(uniform->location, 1, data);
+ }
+
+void GWN_batch_uniform_4fv(Gwn_Batch* batch, const char* name, const float data[4])
+ {
+ GET_UNIFORM
+ glUniform4fv(uniform->location, 1, data);
+ }
+
+void GWN_batch_uniform_2fv_array(Gwn_Batch* batch, const char* name, const int len, const float *data)
+ {
+ GET_UNIFORM
+ glUniform2fv(uniform->location, len, data);
+ }
+
+void GWN_batch_uniform_4fv_array(Gwn_Batch* batch, const char* name, const int len, const float *data)
+ {
+ GET_UNIFORM
+ glUniform4fv(uniform->location, len, data);
+ }
+
+void GWN_batch_uniform_mat4(Gwn_Batch* batch, const char* name, const float data[4][4])
+ {
+ GET_UNIFORM
+ glUniformMatrix4fv(uniform->location, 1, GL_FALSE, (const float *)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
+ assert(batch->phase == GWN_BATCH_READY_TO_DRAW);
+ assert(batch->verts[0]->vbo_id != 0);
+#endif
+ GWN_batch_program_use_begin(batch);
+ gpuBindMatrices(batch->interface); // external call.
+
+ GWN_batch_draw_range_ex(batch, 0, 0, false);
+
+ GWN_batch_program_use_end(batch);
+ }
+
+void GWN_batch_draw_range_ex(Gwn_Batch* batch, int v_first, int v_count, bool force_instance)
+ {
+#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 && (do_instance || batch->elem))
+ {
+ glBindVertexArray(GWN_vao_default());
+ batch_update_program_bindings(batch, v_first);
+ }
+ else
+ glBindVertexArray(batch->vao_id);
+
+ if (do_instance)
+ {
+ // Infer length if vertex count is not given
+ if (v_count == 0)
+ v_count = batch->inst->vertex_ct;
+
+ if (batch->elem)
+ {
+ 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);
+ }
+ else
+ {
+ // Infer length if vertex count is not given
+ if (v_count == 0)
+ v_count = (batch->elem) ? batch->elem->index_ct : batch->verts[0]->vertex_ct;
+
+ if (batch->elem)
+ {
+ 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);
+ else
+ glDrawRangeElements(batch->gl_prim_type, el->min_index, el->max_index, v_count, el->gl_index_type, 0);
+#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, v_first, v_count);
+ }
+
+ // 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.
+void GWN_draw_primitive(Gwn_PrimType prim_type, int v_count)
+ {
+ // we cannot draw without vao ... annoying ...
+ glBindVertexArray(GWN_vao_default());
+
+ GLenum type = convert_prim_type_to_gl(prim_type);
+ glDrawArrays(type, 0, v_count);
+
+ // 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_buffer_id.cpp b/intern/gawain/src/gwn_buffer_id.cpp
new file mode 100644
index 00000000000..64bad855ca7
--- /dev/null
+++ b/intern/gawain/src/gwn_buffer_id.cpp
@@ -0,0 +1,70 @@
+
+// Gawain buffer IDs
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.#include "buffer_id.h"
+
+#include "gwn_buffer_id.h"
+#include <mutex>
+#include <vector>
+
+#define ORPHAN_DEBUG 0
+
+#if ORPHAN_DEBUG
+ #include <cstdio>
+#endif
+
+static std::vector<GLuint> orphaned_buffer_ids;
+
+static std::mutex orphan_mutex;
+
+extern "C" {
+extern int BLI_thread_is_main(void); // Blender-specific function
+}
+
+static bool thread_is_main()
+ {
+ // "main" here means the GL context's thread
+ return BLI_thread_is_main();
+ }
+
+GLuint GWN_buf_id_alloc()
+ {
+ // delete orphaned IDs
+ orphan_mutex.lock();
+ if (!orphaned_buffer_ids.empty())
+ {
+ const auto orphaned_buffer_ct = (unsigned)orphaned_buffer_ids.size();
+#if ORPHAN_DEBUG
+ printf("deleting %u orphaned VBO%s\n", orphaned_buffer_ct, orphaned_buffer_ct == 1 ? "" : "s");
+#endif
+ glDeleteBuffers(orphaned_buffer_ct, orphaned_buffer_ids.data());
+ orphaned_buffer_ids.clear();
+ }
+ orphan_mutex.unlock();
+
+ GLuint new_buffer_id = 0;
+ glGenBuffers(1, &new_buffer_id);
+ return new_buffer_id;
+ }
+
+void GWN_buf_id_free(GLuint buffer_id)
+ {
+ if (thread_is_main())
+ glDeleteBuffers(1, &buffer_id);
+ else
+ {
+ // add this ID to the orphaned list
+ orphan_mutex.lock();
+#if ORPHAN_DEBUG
+ printf("orphaning VBO %u\n", buffer_id);
+#endif
+ orphaned_buffer_ids.emplace_back(buffer_id);
+ orphan_mutex.unlock();
+ }
+ }
diff --git a/intern/gawain/src/gwn_element.c b/intern/gawain/src/gwn_element.c
new file mode 100644
index 00000000000..e2fbb657c78
--- /dev/null
+++ b/intern/gawain/src/gwn_element.c
@@ -0,0 +1,304 @@
+
+// Gawain element list (AKA index buffer)
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#include "gwn_element.h"
+#include "gwn_buffer_id.h"
+#include <stdlib.h>
+
+#define KEEP_SINGLE_COPY 1
+
+static GLenum convert_index_type_to_gl(Gwn_IndexBufType type)
+ {
+ static const GLenum table[] = {
+ [GWN_INDEX_U8] = GL_UNSIGNED_BYTE, // GL has this, Vulkan does not
+ [GWN_INDEX_U16] = GL_UNSIGNED_SHORT,
+ [GWN_INDEX_U32] = GL_UNSIGNED_INT
+ };
+ return table[type];
+ }
+
+unsigned GWN_indexbuf_size_get(const Gwn_IndexBuf* elem)
+ {
+#if GWN_TRACK_INDEX_RANGE
+ static const unsigned table[] = {
+ [GWN_INDEX_U8] = sizeof(GLubyte), // GL has this, Vulkan does not
+ [GWN_INDEX_U16] = sizeof(GLushort),
+ [GWN_INDEX_U32] = sizeof(GLuint)
+ };
+ return elem->index_ct * table[elem->index_type];
+#else
+ return elem->index_ct * sizeof(GLuint);
+#endif
+ }
+
+void GWN_indexbuf_init_ex(Gwn_IndexBufBuilder* builder, Gwn_PrimType prim_type, unsigned index_ct, unsigned vertex_ct, bool use_prim_restart)
+ {
+ 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)
+ {
+ unsigned verts_per_prim = 0;
+ switch (prim_type)
+ {
+ case GWN_PRIM_POINTS:
+ verts_per_prim = 1;
+ break;
+ case GWN_PRIM_LINES:
+ verts_per_prim = 2;
+ break;
+ case GWN_PRIM_TRIS:
+ verts_per_prim = 3;
+ break;
+ case GWN_PRIM_LINES_ADJ:
+ verts_per_prim = 4;
+ break;
+ default:
+#if TRUST_NO_ONE
+ assert(false);
+#endif
+ return;
+ }
+
+ 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)
+ {
+#if TRUST_NO_ONE
+ assert(builder->data != NULL);
+ assert(builder->index_ct < builder->max_index_ct);
+ assert(v <= builder->max_allowed_index);
+#endif
+
+ 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
+ assert(builder->prim_type == GWN_PRIM_POINTS);
+#endif
+
+ GWN_indexbuf_add_generic_vert(builder, v);
+ }
+
+void GWN_indexbuf_add_line_verts(Gwn_IndexBufBuilder* builder, unsigned v1, unsigned v2)
+ {
+#if TRUST_NO_ONE
+ assert(builder->prim_type == GWN_PRIM_LINES);
+ assert(v1 != v2);
+#endif
+
+ GWN_indexbuf_add_generic_vert(builder, v1);
+ GWN_indexbuf_add_generic_vert(builder, v2);
+ }
+
+void GWN_indexbuf_add_tri_verts(Gwn_IndexBufBuilder* builder, unsigned v1, unsigned v2, unsigned v3)
+ {
+#if TRUST_NO_ONE
+ assert(builder->prim_type == GWN_PRIM_TRIS);
+ assert(v1 != v2 && v2 != v3 && v3 != v1);
+#endif
+
+ GWN_indexbuf_add_generic_vert(builder, v1);
+ GWN_indexbuf_add_generic_vert(builder, v2);
+ GWN_indexbuf_add_generic_vert(builder, v3);
+ }
+
+void GWN_indexbuf_add_line_adj_verts(Gwn_IndexBufBuilder* builder, unsigned v1, unsigned v2, unsigned v3, unsigned v4)
+ {
+#if TRUST_NO_ONE
+ assert(builder->prim_type == GWN_PRIM_LINES_ADJ);
+ assert(v2 != v3); /* only the line need diff indices */
+#endif
+
+ GWN_indexbuf_add_generic_vert(builder, v1);
+ GWN_indexbuf_add_generic_vert(builder, v2);
+ GWN_indexbuf_add_generic_vert(builder, v3);
+ GWN_indexbuf_add_generic_vert(builder, v4);
+ }
+
+#if GWN_TRACK_INDEX_RANGE
+// Everything remains 32 bit while building to keep things simple.
+// Find min/max after, then convert to smallest index type possible.
+
+static unsigned index_range(const unsigned values[], unsigned value_ct, unsigned* min_out, unsigned* max_out)
+ {
+ if (value_ct == 0)
+ {
+ *min_out = 0;
+ *max_out = 0;
+ return 0;
+ }
+ unsigned min_value = values[0];
+ unsigned max_value = values[0];
+ for (unsigned i = 1; i < value_ct; ++i)
+ {
+ const unsigned value = values[i];
+ if (value == GWN_PRIM_RESTART)
+ continue;
+ else if (value < min_value)
+ min_value = value;
+ else if (value > max_value)
+ max_value = value;
+ }
+ *min_out = min_value;
+ *max_out = max_value;
+ return max_value - min_value;
+ }
+
+static void squeeze_indices_byte(Gwn_IndexBufBuilder *builder, Gwn_IndexBuf* elem)
+ {
+ const unsigned *values = builder->data;
+ const unsigned index_ct = elem->index_ct;
+
+ // 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)
+ {
+ const unsigned base = elem->min_index;
+
+ elem->base_index = base;
+ elem->min_index = 0;
+ elem->max_index -= base;
+
+ for (unsigned i = 0; i < index_ct; ++i)
+ data[i] = (values[i] == GWN_PRIM_RESTART) ? 0xFF : (GLubyte)(values[i] - base);
+ }
+ else
+ {
+ elem->base_index = 0;
+
+ for (unsigned i = 0; i < index_ct; ++i)
+ data[i] = (GLubyte)(values[i]);
+ }
+ }
+
+static void squeeze_indices_short(Gwn_IndexBufBuilder *builder, Gwn_IndexBuf* elem)
+ {
+ const unsigned *values = builder->data;
+ const unsigned index_ct = elem->index_ct;
+
+ // 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)
+ {
+ const unsigned base = elem->min_index;
+
+ elem->base_index = base;
+ elem->min_index = 0;
+ elem->max_index -= base;
+
+ for (unsigned i = 0; i < index_ct; ++i)
+ data[i] = (values[i] == GWN_PRIM_RESTART) ? 0xFFFF : (GLushort)(values[i] - base);
+ }
+ else
+ {
+ elem->base_index = 0;
+
+ for (unsigned i = 0; i < index_ct; ++i)
+ data[i] = (GLushort)(values[i]);
+ }
+ }
+
+#endif // GWN_TRACK_INDEX_RANGE
+
+Gwn_IndexBuf* GWN_indexbuf_build(Gwn_IndexBufBuilder* builder)
+ {
+ Gwn_IndexBuf* elem = calloc(1, sizeof(Gwn_IndexBuf));
+ GWN_indexbuf_build_in_place(builder, elem);
+ return elem;
+ }
+
+void GWN_indexbuf_build_in_place(Gwn_IndexBufBuilder* builder, Gwn_IndexBuf* elem)
+ {
+#if TRUST_NO_ONE
+ assert(builder->data != NULL);
+#endif
+
+ elem->index_ct = builder->index_ct;
+ elem->use_prim_restart = builder->use_prim_restart;
+
+#if GWN_TRACK_INDEX_RANGE
+ 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, elem);
+ }
+ else if (range <= 0xFFFF)
+ {
+ elem->index_type = GWN_INDEX_U16;
+ squeeze_indices_short(builder, elem);
+ }
+ else
+ {
+ elem->index_type = GWN_INDEX_U32;
+ elem->base_index = 0;
+ }
+
+ elem->gl_index_type = convert_index_type_to_gl(elem->index_type);
+#endif
+
+ if (elem->vbo_id == 0)
+ elem->vbo_id = GWN_buf_id_alloc();
+
+ // send data to GPU
+ // GL_ELEMENT_ARRAY_BUFFER changes the state of the last VAO bound,
+ // so we use the GL_ARRAY_BUFFER here to create a buffer without
+ // interfering in the VAO state.
+ glBindBuffer(GL_ARRAY_BUFFER, elem->vbo_id);
+ glBufferData(GL_ARRAY_BUFFER, GWN_indexbuf_size_get(elem), builder->data, GL_STATIC_DRAW);
+
+ // discard builder (one-time use)
+ 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);
+
+ free(elem);
+ }
diff --git a/intern/gawain/src/gwn_imm_util.c b/intern/gawain/src/gwn_imm_util.c
new file mode 100644
index 00000000000..a8d2b05c8a3
--- /dev/null
+++ b/intern/gawain/src/gwn_imm_util.c
@@ -0,0 +1,80 @@
+
+// Gawain immediate mode drawing utilities
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#include "gwn_imm_util.h"
+#include "gwn_immediate.h"
+
+
+void immRectf(unsigned pos, float x1, float y1, float x2, float y2)
+ {
+ immBegin(GWN_PRIM_TRI_FAN, 4);
+ immVertex2f(pos, x1, y1);
+ immVertex2f(pos, x2, y1);
+ immVertex2f(pos, x2, y2);
+ immVertex2f(pos, x1, y2);
+ immEnd();
+ }
+
+void immRecti(unsigned pos, int x1, int y1, int x2, int y2)
+ {
+ immBegin(GWN_PRIM_TRI_FAN, 4);
+ immVertex2i(pos, x1, y1);
+ immVertex2i(pos, x2, y1);
+ immVertex2i(pos, x2, y2);
+ immVertex2i(pos, x1, y2);
+ immEnd();
+ }
+
+void immRectf_fast_with_color(unsigned pos, unsigned col, float x1, float y1, float x2, float y2, const float color[4])
+ {
+ immAttrib4fv(col, color);
+ immVertex2f(pos, x1, y1);
+ immAttrib4fv(col, color);
+ immVertex2f(pos, x2, y1);
+ immAttrib4fv(col, color);
+ immVertex2f(pos, x2, y2);
+
+ immAttrib4fv(col, color);
+ immVertex2f(pos, x1, y1);
+ immAttrib4fv(col, color);
+ immVertex2f(pos, x2, y2);
+ immAttrib4fv(col, color);
+ immVertex2f(pos, x1, y2);
+ }
+
+void immRecti_fast_with_color(unsigned pos, unsigned col, int x1, int y1, int x2, int y2, const float color[4])
+ {
+ immAttrib4fv(col, color);
+ immVertex2i(pos, x1, y1);
+ immAttrib4fv(col, color);
+ immVertex2i(pos, x2, y1);
+ immAttrib4fv(col, color);
+ immVertex2i(pos, x2, y2);
+
+ immAttrib4fv(col, color);
+ immVertex2i(pos, x1, y1);
+ immAttrib4fv(col, color);
+ immVertex2i(pos, x2, y2);
+ immAttrib4fv(col, color);
+ immVertex2i(pos, x1, y2);
+ }
+
+#if 0 // more complete version in case we want that
+void immRecti_complete(int x1, int y1, int x2, int y2, const float color[4])
+ {
+ Gwn_VertFormat *format = immVertexFormat();
+ unsigned pos = add_attrib(format, "pos", GWN_COMP_I32, 2, GWN_FETCH_INT_TO_FLOAT);
+ immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+ immUniformColor4fv(color);
+ immRecti(pos, x1, y1, x2, y2);
+ immUnbindProgram();
+ }
+#endif
diff --git a/intern/gawain/src/gwn_immediate.c b/intern/gawain/src/gwn_immediate.c
new file mode 100644
index 00000000000..b0b587d1b8c
--- /dev/null
+++ b/intern/gawain/src/gwn_immediate.c
@@ -0,0 +1,926 @@
+
+// Gawain immediate mode work-alike
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#include "gwn_immediate.h"
+#include "gwn_buffer_id.h"
+#include "gwn_attr_binding.h"
+#include "gwn_attr_binding_private.h"
+#include "gwn_vertex_format_private.h"
+#include "gwn_vertex_array_id.h"
+#include "gwn_primitive_private.h"
+#include <string.h>
+
+// necessary functions from matrix API
+extern void gpuBindMatrices(const Gwn_ShaderInterface*);
+extern bool gpuMatricesDirty(void);
+
+typedef struct {
+ // TODO: organize this struct by frequency of change (run-time)
+
+#if IMM_BATCH_COMBO
+ Gwn_Batch* batch;
+#endif
+ Gwn_Context* context;
+
+ // current draw call
+ GLubyte* buffer_data;
+ unsigned buffer_offset;
+ unsigned buffer_bytes_mapped;
+ unsigned vertex_ct;
+ bool strict_vertex_ct;
+ Gwn_PrimType prim_type;
+
+ Gwn_VertFormat vertex_format;
+
+ // current vertex
+ unsigned vertex_idx;
+ GLubyte* vertex_data;
+ uint16_t unassigned_attrib_bits; // which attributes of current vertex have not been given values?
+
+ GLuint vbo_id;
+ GLuint vao_id;
+
+ GLuint bound_program;
+ const Gwn_ShaderInterface* shader_interface;
+ Gwn_AttrBinding attrib_binding;
+ uint16_t prev_enabled_attrib_bits; // <-- only affects this VAO, so we're ok
+} Immediate;
+
+// size of internal buffer -- make this adjustable?
+#define IMM_BUFFER_SIZE (4 * 1024 * 1024)
+
+static bool initialized = false;
+static Immediate imm;
+
+void immInit(void)
+ {
+#if TRUST_NO_ONE
+ assert(!initialized);
+#endif
+
+ memset(&imm, 0, sizeof(Immediate));
+
+ imm.vbo_id = GWN_buf_id_alloc();
+ glBindBuffer(GL_ARRAY_BUFFER, imm.vbo_id);
+ glBufferData(GL_ARRAY_BUFFER, IMM_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW);
+
+ imm.prim_type = GWN_PRIM_NONE;
+ imm.strict_vertex_ct = true;
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ initialized = true;
+
+ immActivate();
+ }
+
+void immActivate(void)
+ {
+#if TRUST_NO_ONE
+ assert(initialized);
+ assert(imm.prim_type == GWN_PRIM_NONE); // make sure we're not between a Begin/End pair
+ assert(imm.vao_id == 0);
+#endif
+ imm.vao_id = GWN_vao_alloc();
+ imm.context = GWN_context_active_get();
+ }
+
+void immDeactivate(void)
+ {
+#if TRUST_NO_ONE
+ assert(initialized);
+ assert(imm.prim_type == GWN_PRIM_NONE); // make sure we're not between a Begin/End pair
+ assert(imm.vao_id != 0);
+#endif
+ GWN_vao_free(imm.vao_id, imm.context);
+ imm.vao_id = 0;
+ imm.prev_enabled_attrib_bits = 0;
+ }
+
+void immDestroy(void)
+ {
+ immDeactivate();
+ GWN_buf_id_free(imm.vbo_id);
+ initialized = false;
+ }
+
+Gwn_VertFormat* immVertexFormat(void)
+ {
+ GWN_vertformat_clear(&imm.vertex_format);
+ return &imm.vertex_format;
+ }
+
+void immBindProgram(GLuint program, const Gwn_ShaderInterface* shaderface)
+ {
+#if TRUST_NO_ONE
+ assert(imm.bound_program == 0);
+ assert(glIsProgram(program));
+#endif
+
+ imm.bound_program = program;
+ imm.shader_interface = shaderface;
+
+ if (!imm.vertex_format.packed)
+ VertexFormat_pack(&imm.vertex_format);
+
+ glUseProgram(program);
+ get_attrib_locations(&imm.vertex_format, &imm.attrib_binding, shaderface);
+ gpuBindMatrices(shaderface);
+ }
+
+void immUnbindProgram(void)
+ {
+#if TRUST_NO_ONE
+ assert(imm.bound_program != 0);
+#endif
+#if PROGRAM_NO_OPTI
+ glUseProgram(0);
+#endif
+ imm.bound_program = 0;
+ }
+
+#if TRUST_NO_ONE
+static bool vertex_count_makes_sense_for_primitive(unsigned vertex_ct, Gwn_PrimType prim_type)
+ {
+ // does vertex_ct make sense for this primitive type?
+ if (vertex_ct == 0)
+ return false;
+
+ switch (prim_type)
+ {
+ case GWN_PRIM_POINTS:
+ return true;
+ case GWN_PRIM_LINES:
+ return vertex_ct % 2 == 0;
+ case GWN_PRIM_LINE_STRIP:
+ case GWN_PRIM_LINE_LOOP:
+ return vertex_ct >= 2;
+ case GWN_PRIM_LINE_STRIP_ADJ:
+ return vertex_ct >= 4;
+ case GWN_PRIM_TRIS:
+ return vertex_ct % 3 == 0;
+ case GWN_PRIM_TRI_STRIP:
+ case GWN_PRIM_TRI_FAN:
+ return vertex_ct >= 3;
+ default:
+ return false;
+ }
+ }
+#endif
+
+void immBegin(Gwn_PrimType prim_type, unsigned vertex_ct)
+ {
+#if TRUST_NO_ONE
+ assert(initialized);
+ assert(imm.prim_type == GWN_PRIM_NONE); // make sure we haven't already begun
+ assert(vertex_count_makes_sense_for_primitive(vertex_ct, prim_type));
+#endif
+
+ imm.prim_type = prim_type;
+ imm.vertex_ct = vertex_ct;
+ imm.vertex_idx = 0;
+ imm.unassigned_attrib_bits = imm.attrib_binding.enabled_bits;
+
+ // how many bytes do we need for this draw call?
+ const unsigned bytes_needed = vertex_buffer_size(&imm.vertex_format, vertex_ct);
+
+#if TRUST_NO_ONE
+ assert(bytes_needed <= IMM_BUFFER_SIZE);
+#endif
+
+ glBindBuffer(GL_ARRAY_BUFFER, imm.vbo_id);
+
+ // does the current buffer have enough room?
+ const unsigned available_bytes = IMM_BUFFER_SIZE - imm.buffer_offset;
+ // ensure vertex data is aligned
+ const unsigned pre_padding = padding(imm.buffer_offset, imm.vertex_format.stride); // might waste a little space, but it's safe
+ if ((bytes_needed + pre_padding) <= available_bytes)
+ imm.buffer_offset += pre_padding;
+ else
+ {
+ // orphan this buffer & start with a fresh one
+#if 1
+ // this method works on all platforms, old & new
+ glBufferData(GL_ARRAY_BUFFER, IMM_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW);
+#else
+ // TODO: use other (more recent) methods after thorough testing
+ if (GLEW_VERSION_4_3 || GLEW_ARB_invalidate_subdata)
+ glInvalidateBufferData(imm.vbo_id);
+ else
+ {
+ // glitches!
+// glMapBufferRange(GL_ARRAY_BUFFER, 0, IMM_BUFFER_SIZE, GL_MAP_INVALIDATE_BUFFER_BIT);
+
+ // works
+// glMapBufferRange(GL_ARRAY_BUFFER, 0, IMM_BUFFER_SIZE,
+// GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
+// glUnmapBuffer(GL_ARRAY_BUFFER);
+
+ // also works
+ glBufferData(GL_ARRAY_BUFFER, IMM_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW);
+ }
+#endif
+
+ imm.buffer_offset = 0;
+ }
+
+// printf("mapping %u to %u\n", imm.buffer_offset, imm.buffer_offset + bytes_needed - 1);
+
+ imm.buffer_data = glMapBufferRange(GL_ARRAY_BUFFER, imm.buffer_offset, bytes_needed,
+ GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | (imm.strict_vertex_ct ? 0 : GL_MAP_FLUSH_EXPLICIT_BIT));
+
+#if TRUST_NO_ONE
+ assert(imm.buffer_data != NULL);
+#endif
+
+ imm.buffer_bytes_mapped = bytes_needed;
+ imm.vertex_data = imm.buffer_data;
+ }
+
+void immBeginAtMost(Gwn_PrimType prim_type, unsigned vertex_ct)
+ {
+#if TRUST_NO_ONE
+ assert(vertex_ct > 0);
+#endif
+
+ imm.strict_vertex_ct = false;
+ immBegin(prim_type, vertex_ct);
+ }
+
+#if IMM_BATCH_COMBO
+
+Gwn_Batch* immBeginBatch(Gwn_PrimType prim_type, unsigned vertex_ct)
+ {
+#if TRUST_NO_ONE
+ assert(initialized);
+ assert(imm.prim_type == GWN_PRIM_NONE); // make sure we haven't already begun
+ assert(vertex_count_makes_sense_for_primitive(vertex_ct, prim_type));
+#endif
+
+ imm.prim_type = prim_type;
+ imm.vertex_ct = vertex_ct;
+ imm.vertex_idx = 0;
+ imm.unassigned_attrib_bits = imm.attrib_binding.enabled_bits;
+
+ Gwn_VertBuf* verts = GWN_vertbuf_create_with_format(&imm.vertex_format);
+ GWN_vertbuf_data_alloc(verts, vertex_ct);
+
+ imm.buffer_bytes_mapped = GWN_vertbuf_size_get(verts);
+ imm.vertex_data = verts->data;
+
+ imm.batch = GWN_batch_create_ex(prim_type, verts, NULL, GWN_BATCH_OWNS_VBO);
+ imm.batch->phase = GWN_BATCH_BUILDING;
+
+ return imm.batch;
+ }
+
+Gwn_Batch* immBeginBatchAtMost(Gwn_PrimType prim_type, unsigned vertex_ct)
+ {
+ imm.strict_vertex_ct = false;
+ return immBeginBatch(prim_type, vertex_ct);
+ }
+
+#endif // IMM_BATCH_COMBO
+
+static void immDrawSetup(void)
+ {
+ // set up VAO -- can be done during Begin or End really
+ glBindVertexArray(imm.vao_id);
+
+ // enable/disable vertex attribs as needed
+ if (imm.attrib_binding.enabled_bits != imm.prev_enabled_attrib_bits)
+ {
+ for (unsigned loc = 0; loc < GWN_VERT_ATTR_MAX_LEN; ++loc)
+ {
+ bool is_enabled = imm.attrib_binding.enabled_bits & (1 << loc);
+ bool was_enabled = imm.prev_enabled_attrib_bits & (1 << loc);
+
+ if (is_enabled && !was_enabled)
+ {
+// printf("enabling attrib %u\n", loc);
+ glEnableVertexAttribArray(loc);
+ }
+ else if (was_enabled && !is_enabled)
+ {
+// printf("disabling attrib %u\n", loc);
+ glDisableVertexAttribArray(loc);
+ }
+ }
+
+ imm.prev_enabled_attrib_bits = imm.attrib_binding.enabled_bits;
+ }
+
+ const unsigned stride = imm.vertex_format.stride;
+
+ for (unsigned a_idx = 0; a_idx < imm.vertex_format.attrib_ct; ++a_idx)
+ {
+ const Gwn_VertAttr* a = imm.vertex_format.attribs + a_idx;
+
+ const unsigned offset = imm.buffer_offset + a->offset;
+ const GLvoid* pointer = (const GLubyte*)0 + offset;
+
+ const unsigned loc = read_attrib_location(&imm.attrib_binding, a_idx);
+
+// printf("specifying attrib %u '%s' with offset %u, stride %u\n", loc, a->name, offset, stride);
+
+ switch (a->fetch_mode)
+ {
+ case GWN_FETCH_FLOAT:
+ case GWN_FETCH_INT_TO_FLOAT:
+ glVertexAttribPointer(loc, a->comp_ct, a->gl_comp_type, GL_FALSE, stride, pointer);
+ break;
+ case GWN_FETCH_INT_TO_FLOAT_UNIT:
+ glVertexAttribPointer(loc, a->comp_ct, a->gl_comp_type, GL_TRUE, stride, pointer);
+ break;
+ case GWN_FETCH_INT:
+ glVertexAttribIPointer(loc, a->comp_ct, a->gl_comp_type, stride, pointer);
+ }
+ }
+
+ if (gpuMatricesDirty())
+ gpuBindMatrices(imm.shader_interface);
+ }
+
+void immEnd(void)
+ {
+#if TRUST_NO_ONE
+ assert(imm.prim_type != GWN_PRIM_NONE); // make sure we're between a Begin/End pair
+#endif
+
+ unsigned buffer_bytes_used;
+ if (imm.strict_vertex_ct)
+ {
+#if TRUST_NO_ONE
+ assert(imm.vertex_idx == imm.vertex_ct); // with all vertices defined
+#endif
+ buffer_bytes_used = imm.buffer_bytes_mapped;
+ }
+ else
+ {
+#if TRUST_NO_ONE
+ assert(imm.vertex_idx <= imm.vertex_ct);
+#endif
+ // printf("used %u of %u verts,", imm.vertex_idx, imm.vertex_ct);
+ if (imm.vertex_idx == imm.vertex_ct)
+ {
+ buffer_bytes_used = imm.buffer_bytes_mapped;
+ }
+ else
+ {
+#if TRUST_NO_ONE
+ assert(imm.vertex_idx == 0 || vertex_count_makes_sense_for_primitive(imm.vertex_idx, imm.prim_type));
+#endif
+ imm.vertex_ct = imm.vertex_idx;
+ buffer_bytes_used = vertex_buffer_size(&imm.vertex_format, imm.vertex_ct);
+ // unused buffer bytes are available to the next immBegin
+ // printf(" %u of %u bytes\n", buffer_bytes_used, imm.buffer_bytes_mapped);
+ }
+
+ // tell OpenGL what range was modified so it doesn't copy the whole mapped range
+ // printf("flushing %u to %u\n", imm.buffer_offset, imm.buffer_offset + buffer_bytes_used - 1);
+ glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, buffer_bytes_used);
+ }
+
+#if IMM_BATCH_COMBO
+ if (imm.batch)
+ {
+ if (buffer_bytes_used != imm.buffer_bytes_mapped)
+ {
+ GWN_vertbuf_data_resize(imm.batch->verts[0], imm.vertex_ct);
+ // 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
+ }
+ else
+#endif
+ {
+ glUnmapBuffer(GL_ARRAY_BUFFER);
+
+ if (imm.vertex_ct > 0)
+ {
+ immDrawSetup();
+ glDrawArrays(convert_prim_type_to_gl(imm.prim_type), 0, imm.vertex_ct);
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
+
+ // prep for next immBegin
+ imm.buffer_offset += buffer_bytes_used;
+ }
+
+ // prep for next immBegin
+ imm.prim_type = GWN_PRIM_NONE;
+ imm.strict_vertex_ct = true;
+ }
+
+static void setAttribValueBit(unsigned attrib_id)
+ {
+ uint16_t mask = 1 << attrib_id;
+
+#if TRUST_NO_ONE
+ assert(imm.unassigned_attrib_bits & mask); // not already set
+#endif
+
+ imm.unassigned_attrib_bits &= ~mask;
+ }
+
+
+// --- generic attribute functions ---
+
+void immAttrib1f(unsigned attrib_id, float x)
+ {
+ Gwn_VertAttr* attrib = imm.vertex_format.attribs + attrib_id;
+
+#if TRUST_NO_ONE
+ assert(attrib_id < imm.vertex_format.attrib_ct);
+ assert(attrib->comp_type == GWN_COMP_F32);
+ assert(attrib->comp_ct == 1);
+ assert(imm.vertex_idx < imm.vertex_ct);
+ assert(imm.prim_type != GWN_PRIM_NONE); // make sure we're between a Begin/End pair
+#endif
+
+ setAttribValueBit(attrib_id);
+
+ float* data = (float*)(imm.vertex_data + attrib->offset);
+// printf("%s %td %p\n", __FUNCTION__, (GLubyte*)data - imm.buffer_data, data);
+
+ data[0] = x;
+ }
+
+void immAttrib2f(unsigned attrib_id, float x, float y)
+ {
+ Gwn_VertAttr* attrib = imm.vertex_format.attribs + attrib_id;
+
+#if TRUST_NO_ONE
+ assert(attrib_id < imm.vertex_format.attrib_ct);
+ assert(attrib->comp_type == GWN_COMP_F32);
+ assert(attrib->comp_ct == 2);
+ assert(imm.vertex_idx < imm.vertex_ct);
+ assert(imm.prim_type != GWN_PRIM_NONE); // make sure we're between a Begin/End pair
+#endif
+
+ setAttribValueBit(attrib_id);
+
+ float* data = (float*)(imm.vertex_data + attrib->offset);
+// printf("%s %td %p\n", __FUNCTION__, (GLubyte*)data - imm.buffer_data, data);
+
+ data[0] = x;
+ data[1] = y;
+ }
+
+void immAttrib3f(unsigned attrib_id, float x, float y, float z)
+ {
+ Gwn_VertAttr* attrib = imm.vertex_format.attribs + attrib_id;
+
+#if TRUST_NO_ONE
+ assert(attrib_id < imm.vertex_format.attrib_ct);
+ assert(attrib->comp_type == GWN_COMP_F32);
+ assert(attrib->comp_ct == 3);
+ assert(imm.vertex_idx < imm.vertex_ct);
+ assert(imm.prim_type != GWN_PRIM_NONE); // make sure we're between a Begin/End pair
+#endif
+
+ setAttribValueBit(attrib_id);
+
+ float* data = (float*)(imm.vertex_data + attrib->offset);
+// printf("%s %td %p\n", __FUNCTION__, (GLubyte*)data - imm.buffer_data, data);
+
+ data[0] = x;
+ data[1] = y;
+ data[2] = z;
+ }
+
+void immAttrib4f(unsigned attrib_id, float x, float y, float z, float w)
+ {
+ Gwn_VertAttr* attrib = imm.vertex_format.attribs + attrib_id;
+
+#if TRUST_NO_ONE
+ assert(attrib_id < imm.vertex_format.attrib_ct);
+ assert(attrib->comp_type == GWN_COMP_F32);
+ assert(attrib->comp_ct == 4);
+ assert(imm.vertex_idx < imm.vertex_ct);
+ assert(imm.prim_type != GWN_PRIM_NONE); // make sure we're between a Begin/End pair
+#endif
+
+ setAttribValueBit(attrib_id);
+
+ float* data = (float*)(imm.vertex_data + attrib->offset);
+// printf("%s %td %p\n", __FUNCTION__, (GLubyte*)data - imm.buffer_data, data);
+
+ data[0] = x;
+ data[1] = y;
+ data[2] = z;
+ data[3] = w;
+ }
+
+void immAttrib1u(unsigned attrib_id, unsigned x)
+ {
+ Gwn_VertAttr* attrib = imm.vertex_format.attribs + attrib_id;
+
+#if TRUST_NO_ONE
+ assert(attrib_id < imm.vertex_format.attrib_ct);
+ assert(attrib->comp_type == GWN_COMP_U32);
+ assert(attrib->comp_ct == 1);
+ assert(imm.vertex_idx < imm.vertex_ct);
+ assert(imm.prim_type != GWN_PRIM_NONE); // make sure we're between a Begin/End pair
+#endif
+
+ setAttribValueBit(attrib_id);
+
+ unsigned* data = (unsigned*)(imm.vertex_data + attrib->offset);
+
+ data[0] = x;
+ }
+
+void immAttrib2i(unsigned attrib_id, int x, int y)
+ {
+ Gwn_VertAttr* attrib = imm.vertex_format.attribs + attrib_id;
+
+#if TRUST_NO_ONE
+ assert(attrib_id < imm.vertex_format.attrib_ct);
+ assert(attrib->comp_type == GWN_COMP_I32);
+ assert(attrib->comp_ct == 2);
+ assert(imm.vertex_idx < imm.vertex_ct);
+ assert(imm.prim_type != GWN_PRIM_NONE); // make sure we're between a Begin/End pair
+#endif
+
+ setAttribValueBit(attrib_id);
+
+ int* data = (int*)(imm.vertex_data + attrib->offset);
+
+ data[0] = x;
+ data[1] = y;
+ }
+
+void immAttrib2s(unsigned attrib_id, short x, short y)
+ {
+ Gwn_VertAttr* attrib = imm.vertex_format.attribs + attrib_id;
+
+#if TRUST_NO_ONE
+ assert(attrib_id < imm.vertex_format.attrib_ct);
+ assert(attrib->comp_type == GWN_COMP_I16);
+ assert(attrib->comp_ct == 2);
+ assert(imm.vertex_idx < imm.vertex_ct);
+ assert(imm.prim_type != GWN_PRIM_NONE); // make sure we're between a Begin/End pair
+#endif
+
+ setAttribValueBit(attrib_id);
+
+ short* data = (short*)(imm.vertex_data + attrib->offset);
+
+ data[0] = x;
+ data[1] = y;
+ }
+
+void immAttrib2fv(unsigned attrib_id, const float data[2])
+ {
+ immAttrib2f(attrib_id, data[0], data[1]);
+ }
+
+void immAttrib3fv(unsigned attrib_id, const float data[3])
+ {
+ immAttrib3f(attrib_id, data[0], data[1], data[2]);
+ }
+
+void immAttrib4fv(unsigned attrib_id, const float data[4])
+ {
+ immAttrib4f(attrib_id, data[0], data[1], data[2], data[3]);
+ }
+
+void immAttrib3ub(unsigned attrib_id, unsigned char r, unsigned char g, unsigned char b)
+ {
+ Gwn_VertAttr* attrib = imm.vertex_format.attribs + attrib_id;
+
+#if TRUST_NO_ONE
+ assert(attrib_id < imm.vertex_format.attrib_ct);
+ assert(attrib->comp_type == GWN_COMP_U8);
+ assert(attrib->comp_ct == 3);
+ assert(imm.vertex_idx < imm.vertex_ct);
+ assert(imm.prim_type != GWN_PRIM_NONE); // make sure we're between a Begin/End pair
+#endif
+
+ setAttribValueBit(attrib_id);
+
+ GLubyte* data = imm.vertex_data + attrib->offset;
+// printf("%s %td %p\n", __FUNCTION__, data - imm.buffer_data, data);
+
+ data[0] = r;
+ data[1] = g;
+ data[2] = b;
+ }
+
+void immAttrib4ub(unsigned attrib_id, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+ {
+ Gwn_VertAttr* attrib = imm.vertex_format.attribs + attrib_id;
+
+#if TRUST_NO_ONE
+ assert(attrib_id < imm.vertex_format.attrib_ct);
+ assert(attrib->comp_type == GWN_COMP_U8);
+ assert(attrib->comp_ct == 4);
+ assert(imm.vertex_idx < imm.vertex_ct);
+ assert(imm.prim_type != GWN_PRIM_NONE); // make sure we're between a Begin/End pair
+#endif
+
+ setAttribValueBit(attrib_id);
+
+ GLubyte* data = imm.vertex_data + attrib->offset;
+// printf("%s %td %p\n", __FUNCTION__, data - imm.buffer_data, data);
+
+ data[0] = r;
+ data[1] = g;
+ data[2] = b;
+ data[3] = a;
+ }
+
+void immAttrib3ubv(unsigned attrib_id, const unsigned char data[3])
+ {
+ immAttrib3ub(attrib_id, data[0], data[1], data[2]);
+ }
+
+void immAttrib4ubv(unsigned attrib_id, const unsigned char data[4])
+ {
+ immAttrib4ub(attrib_id, data[0], data[1], data[2], data[3]);
+ }
+
+void immSkipAttrib(unsigned attrib_id)
+ {
+#if TRUST_NO_ONE
+ assert(attrib_id < imm.vertex_format.attrib_ct);
+ assert(imm.vertex_idx < imm.vertex_ct);
+ assert(imm.prim_type != GWN_PRIM_NONE); // make sure we're between a Begin/End pair
+#endif
+
+ setAttribValueBit(attrib_id);
+ }
+
+static void immEndVertex(void) // and move on to the next vertex
+ {
+#if TRUST_NO_ONE
+ assert(imm.prim_type != GWN_PRIM_NONE); // make sure we're between a Begin/End pair
+ assert(imm.vertex_idx < imm.vertex_ct);
+#endif
+
+ // have all attribs been assigned values?
+ // if not, copy value from previous vertex
+ if (imm.unassigned_attrib_bits)
+ {
+#if TRUST_NO_ONE
+ assert(imm.vertex_idx > 0); // first vertex must have all attribs specified
+#endif
+
+ for (unsigned a_idx = 0; a_idx < imm.vertex_format.attrib_ct; ++a_idx)
+ {
+ if ((imm.unassigned_attrib_bits >> a_idx) & 1)
+ {
+ const Gwn_VertAttr* a = imm.vertex_format.attribs + a_idx;
+
+// printf("copying %s from vertex %u to %u\n", a->name, imm.vertex_idx - 1, imm.vertex_idx);
+
+ GLubyte* data = imm.vertex_data + a->offset;
+ memcpy(data, data - imm.vertex_format.stride, a->sz);
+ // TODO: consolidate copy of adjacent attributes
+ }
+ }
+ }
+
+ imm.vertex_idx++;
+ imm.vertex_data += imm.vertex_format.stride;
+ imm.unassigned_attrib_bits = imm.attrib_binding.enabled_bits;
+ }
+
+void immVertex2f(unsigned attrib_id, float x, float y)
+ {
+ immAttrib2f(attrib_id, x, y);
+ immEndVertex();
+ }
+
+void immVertex3f(unsigned attrib_id, float x, float y, float z)
+ {
+ immAttrib3f(attrib_id, x, y, z);
+ immEndVertex();
+ }
+
+void immVertex4f(unsigned attrib_id, float x, float y, float z, float w)
+ {
+ immAttrib4f(attrib_id, x, y, z, w);
+ immEndVertex();
+ }
+
+void immVertex2i(unsigned attrib_id, int x, int y)
+ {
+ immAttrib2i(attrib_id, x, y);
+ immEndVertex();
+ }
+
+void immVertex2s(unsigned attrib_id, short x, short y)
+ {
+ immAttrib2s(attrib_id, x, y);
+ immEndVertex();
+ }
+
+void immVertex2fv(unsigned attrib_id, const float data[2])
+ {
+ immAttrib2f(attrib_id, data[0], data[1]);
+ immEndVertex();
+ }
+
+void immVertex3fv(unsigned attrib_id, const float data[3])
+ {
+ immAttrib3f(attrib_id, data[0], data[1], data[2]);
+ immEndVertex();
+ }
+
+void immVertex2iv(unsigned attrib_id, const int data[2])
+ {
+ immAttrib2i(attrib_id, data[0], data[1]);
+ immEndVertex();
+ }
+
+
+// --- generic uniform functions ---
+
+#if 0
+ #if TRUST_NO_ONE
+ #define GET_UNIFORM const Gwn_ShaderInput* uniform = GWN_shaderinterface_uniform(imm.shader_interface, name); assert(uniform);
+ #else
+ #define GET_UNIFORM const Gwn_ShaderInput* uniform = GWN_shaderinterface_uniform(imm.shader_interface, name);
+ #endif
+#else
+ // NOTE: It is possible to have uniform fully optimized out from the shader.
+ // In this case we can't assert failure or allow NULL-pointer dereference.
+ // TODO(sergey): How can we detect existing-but-optimized-out uniform but still
+ // catch typos in uniform names passed to immUniform*() functions?
+ #define GET_UNIFORM const Gwn_ShaderInput* uniform = GWN_shaderinterface_uniform(imm.shader_interface, name); if (uniform == NULL) return;
+#endif
+
+void immUniform1f(const char* name, float x)
+ {
+ GET_UNIFORM
+ glUniform1f(uniform->location, x);
+ }
+
+void immUniform2f(const char* name, float x, float y)
+ {
+ GET_UNIFORM
+ glUniform2f(uniform->location, x, y);
+ }
+
+void immUniform2fv(const char* name, const float data[2])
+ {
+ GET_UNIFORM
+ glUniform2fv(uniform->location, 1, data);
+ }
+
+void immUniform3f(const char* name, float x, float y, float z)
+ {
+ GET_UNIFORM
+ glUniform3f(uniform->location, x, y, z);
+ }
+
+void immUniform3fv(const char* name, const float data[3])
+ {
+ GET_UNIFORM
+ glUniform3fv(uniform->location, 1, data);
+ }
+
+// can increase this limit or move to another file
+#define MAX_UNIFORM_NAME_LEN 60
+
+void immUniformArray3fv(const char* bare_name, const float *data, int count)
+ {
+ // look up "name[0]" when given "name"
+ const size_t len = strlen(bare_name);
+#if TRUST_NO_ONE
+ assert(len <= MAX_UNIFORM_NAME_LEN);
+#endif
+ char name[MAX_UNIFORM_NAME_LEN];
+ strcpy(name, bare_name);
+ name[len + 0] = '[';
+ name[len + 1] = '0';
+ name[len + 2] = ']';
+ name[len + 3] = '\0';
+
+ GET_UNIFORM
+ glUniform3fv(uniform->location, count, data);
+ }
+
+void immUniform4f(const char* name, float x, float y, float z, float w)
+ {
+ GET_UNIFORM
+ glUniform4f(uniform->location, x, y, z, w);
+ }
+
+void immUniform4fv(const char* name, const float data[4])
+ {
+ GET_UNIFORM
+ glUniform4fv(uniform->location, 1, data);
+ }
+
+void immUniformArray4fv(const char* bare_name, const float *data, int count)
+ {
+ // look up "name[0]" when given "name"
+ const size_t len = strlen(bare_name);
+#if TRUST_NO_ONE
+ assert(len <= MAX_UNIFORM_NAME_LEN);
+#endif
+ char name[MAX_UNIFORM_NAME_LEN];
+ strcpy(name, bare_name);
+ name[len + 0] = '[';
+ name[len + 1] = '0';
+ name[len + 2] = ']';
+ name[len + 3] = '\0';
+
+ GET_UNIFORM
+ glUniform4fv(uniform->location, count, data);
+ }
+
+void immUniformMatrix4fv(const char* name, const float data[4][4])
+ {
+ GET_UNIFORM
+ glUniformMatrix4fv(uniform->location, 1, GL_FALSE, (float *)data);
+ }
+
+void immUniform1i(const char* name, int x)
+ {
+ GET_UNIFORM
+ glUniform1i(uniform->location, x);
+ }
+
+void immUniform4iv(const char* name, const int data[4])
+ {
+ GET_UNIFORM
+ glUniform4iv(uniform->location, 1, data);
+ }
+
+// --- convenience functions for setting "uniform vec4 color" ---
+
+void immUniformColor4f(float r, float g, float b, float a)
+ {
+ const Gwn_ShaderInput* uniform = GWN_shaderinterface_uniform_builtin(imm.shader_interface, GWN_UNIFORM_COLOR);
+
+#if TRUST_NO_ONE
+ assert(uniform != NULL);
+#endif
+
+ glUniform4f(uniform->location, r, g, b, a);
+ }
+
+void immUniformColor4fv(const float rgba[4])
+ {
+ immUniformColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
+ }
+
+void immUniformColor3f(float r, float g, float b)
+ {
+ immUniformColor4f(r, g, b, 1.0f);
+ }
+
+void immUniformColor3fv(const float rgb[3])
+ {
+ immUniformColor4f(rgb[0], rgb[1], rgb[2], 1.0f);
+ }
+
+void immUniformColor3fvAlpha(const float rgb[3], float a)
+ {
+ immUniformColor4f(rgb[0], rgb[1], rgb[2], a);
+ }
+
+// TODO: v-- treat as sRGB? --v
+
+void immUniformColor3ub(unsigned char r, unsigned char g, unsigned char b)
+ {
+ const float scale = 1.0f / 255.0f;
+ immUniformColor4f(scale * r, scale * g, scale * b, 1.0f);
+ }
+
+void immUniformColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+ {
+ const float scale = 1.0f / 255.0f;
+ immUniformColor4f(scale * r, scale * g, scale * b, scale * a);
+ }
+
+void immUniformColor3ubv(const unsigned char rgb[3])
+ {
+ immUniformColor3ub(rgb[0], rgb[1], rgb[2]);
+ }
+
+void immUniformColor3ubvAlpha(const unsigned char rgb[3], unsigned char alpha)
+ {
+ immUniformColor4ub(rgb[0], rgb[1], rgb[2], alpha);
+ }
+
+void immUniformColor4ubv(const unsigned char rgba[4])
+ {
+ immUniformColor4ub(rgba[0], rgba[1], rgba[2], rgba[3]);
+ }
diff --git a/intern/gawain/src/gwn_primitive.c b/intern/gawain/src/gwn_primitive.c
new file mode 100644
index 00000000000..c2638bcc8c8
--- /dev/null
+++ b/intern/gawain/src/gwn_primitive.c
@@ -0,0 +1,67 @@
+
+// Gawain geometric primitives
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2017 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#include "gwn_primitive.h"
+#include "gwn_primitive_private.h"
+
+Gwn_PrimClass GWN_primtype_class(Gwn_PrimType prim_type)
+ {
+ static const Gwn_PrimClass classes[] =
+ {
+ [GWN_PRIM_POINTS] = GWN_PRIM_CLASS_POINT,
+ [GWN_PRIM_LINES] = GWN_PRIM_CLASS_LINE,
+ [GWN_PRIM_LINE_STRIP] = GWN_PRIM_CLASS_LINE,
+ [GWN_PRIM_LINE_LOOP] = GWN_PRIM_CLASS_LINE,
+ [GWN_PRIM_TRIS] = GWN_PRIM_CLASS_SURFACE,
+ [GWN_PRIM_TRI_STRIP] = GWN_PRIM_CLASS_SURFACE,
+ [GWN_PRIM_TRI_FAN] = GWN_PRIM_CLASS_SURFACE,
+
+ [GWN_PRIM_LINES_ADJ] = GWN_PRIM_CLASS_LINE,
+ [GWN_PRIM_LINE_STRIP_ADJ] = GWN_PRIM_CLASS_LINE,
+ [GWN_PRIM_TRIS_ADJ] = GWN_PRIM_CLASS_SURFACE,
+
+ [GWN_PRIM_NONE] = GWN_PRIM_CLASS_NONE
+ };
+
+ return classes[prim_type];
+ }
+
+bool GWN_primtype_belongs_to_class(Gwn_PrimType prim_type, Gwn_PrimClass prim_class)
+ {
+ if (prim_class == GWN_PRIM_CLASS_NONE && prim_type == GWN_PRIM_NONE)
+ return true;
+
+ return prim_class & GWN_primtype_class(prim_type);
+ }
+
+GLenum convert_prim_type_to_gl(Gwn_PrimType prim_type)
+ {
+#if TRUST_NO_ONE
+ assert(prim_type != GWN_PRIM_NONE);
+#endif
+
+ static const GLenum table[] =
+ {
+ [GWN_PRIM_POINTS] = GL_POINTS,
+ [GWN_PRIM_LINES] = GL_LINES,
+ [GWN_PRIM_LINE_STRIP] = GL_LINE_STRIP,
+ [GWN_PRIM_LINE_LOOP] = GL_LINE_LOOP,
+ [GWN_PRIM_TRIS] = GL_TRIANGLES,
+ [GWN_PRIM_TRI_STRIP] = GL_TRIANGLE_STRIP,
+ [GWN_PRIM_TRI_FAN] = GL_TRIANGLE_FAN,
+
+ [GWN_PRIM_LINES_ADJ] = GL_LINES_ADJACENCY,
+ [GWN_PRIM_LINE_STRIP_ADJ] = GL_LINE_STRIP_ADJACENCY,
+ [GWN_PRIM_TRIS_ADJ] = GL_TRIANGLES_ADJACENCY,
+ };
+
+ return table[prim_type];
+ }
diff --git a/intern/gawain/src/gwn_shader_interface.c b/intern/gawain/src/gwn_shader_interface.c
new file mode 100644
index 00000000000..16451573bba
--- /dev/null
+++ b/intern/gawain/src/gwn_shader_interface.c
@@ -0,0 +1,362 @@
+
+// Gawain shader interface (C --> GLSL)
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2017 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#include "gwn_batch_private.h"
+#include "gwn_shader_interface.h"
+#include "gwn_vertex_array_id.h"
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+
+#define DEBUG_SHADER_INTERFACE 0
+
+#if DEBUG_SHADER_INTERFACE
+ #include <stdio.h>
+#endif
+
+static const char* BuiltinUniform_name(Gwn_UniformBuiltin u)
+ {
+ static const char* names[] =
+ {
+ [GWN_UNIFORM_NONE] = NULL,
+
+ [GWN_UNIFORM_MODEL] = "ModelMatrix",
+ [GWN_UNIFORM_VIEW] = "ViewMatrix",
+ [GWN_UNIFORM_MODELVIEW] = "ModelViewMatrix",
+ [GWN_UNIFORM_PROJECTION] = "ProjectionMatrix",
+ [GWN_UNIFORM_VIEWPROJECTION] = "ViewProjectionMatrix",
+ [GWN_UNIFORM_MVP] = "ModelViewProjectionMatrix",
+
+ [GWN_UNIFORM_MODEL_INV] = "ModelMatrixInverse",
+ [GWN_UNIFORM_VIEW_INV] = "ViewMatrixInverse",
+ [GWN_UNIFORM_MODELVIEW_INV] = "ModelViewMatrixInverse",
+ [GWN_UNIFORM_PROJECTION_INV] = "ProjectionMatrixInverse",
+ [GWN_UNIFORM_VIEWPROJECTION_INV] = "ViewProjectionMatrixInverse",
+
+ [GWN_UNIFORM_NORMAL] = "NormalMatrix",
+ [GWN_UNIFORM_WORLDNORMAL] = "WorldNormalMatrix",
+ [GWN_UNIFORM_CAMERATEXCO] = "CameraTexCoFactors",
+ [GWN_UNIFORM_ORCO] = "OrcoTexCoFactors",
+
+ [GWN_UNIFORM_COLOR] = "color",
+ [GWN_UNIFORM_EYE] = "eye",
+ [GWN_UNIFORM_CALLID] = "callId",
+
+ [GWN_UNIFORM_CUSTOM] = NULL,
+ [GWN_NUM_UNIFORMS] = NULL,
+ };
+
+ return names[u];
+ }
+
+GWN_INLINE bool match(const char* a, const char* b)
+ {
+ return strcmp(a, b) == 0;
+ }
+
+GWN_INLINE unsigned hash_string(const char *str)
+ {
+ unsigned i = 0, c;
+
+ while ((c = *str++))
+ {
+ i = i * 37 + c;
+ }
+
+ return i;
+ }
+
+GWN_INLINE void set_input_name(Gwn_ShaderInterface* shaderface, Gwn_ShaderInput* input,
+ const char* name, uint32_t name_len)
+ {
+ input->name_offset = shaderface->name_buffer_offset;
+ input->name_hash = hash_string(name);
+ shaderface->name_buffer_offset += name_len + 1; // include NULL terminator
+ }
+
+GWN_INLINE void shader_input_to_bucket(Gwn_ShaderInput* input,
+ Gwn_ShaderInput* buckets[GWN_NUM_SHADERINTERFACE_BUCKETS])
+ {
+ const unsigned bucket_index = input->name_hash % GWN_NUM_SHADERINTERFACE_BUCKETS;
+ input->next = buckets[bucket_index];
+ buckets[bucket_index] = input;
+ }
+
+GWN_INLINE const Gwn_ShaderInput* buckets_lookup(Gwn_ShaderInput* const buckets[GWN_NUM_SHADERINTERFACE_BUCKETS],
+ const char *name_buffer, const char *name)
+ {
+ const unsigned name_hash = hash_string(name);
+ const unsigned bucket_index = name_hash % GWN_NUM_SHADERINTERFACE_BUCKETS;
+ const Gwn_ShaderInput* input = buckets[bucket_index];
+ if (input == NULL)
+ {
+ // Requested uniform is not found at all.
+ return NULL;
+ }
+ // Optimization bit: if there is no hash collision detected when constructing shader interface
+ // it means we can only request the single possible uniform. Surely, it's possible we request
+ // uniform which causes hash collision, but that will be detected in debug builds.
+ if (input->next == NULL)
+ {
+ if (name_hash == input->name_hash)
+ {
+#if TRUST_NO_ONE
+ assert(match(name_buffer + input->name_offset, name));
+#endif
+ return input;
+ }
+ return NULL;
+ }
+ // Work through possible collisions.
+ const Gwn_ShaderInput* next = input;
+ while (next != NULL)
+ {
+ input = next;
+ next = input->next;
+
+ if (input->name_hash != name_hash)
+ {
+ continue;
+ }
+ if (match(name_buffer + input->name_offset, name))
+ {
+ return input;
+ }
+ }
+ return NULL; // not found
+ }
+
+GWN_INLINE void buckets_free(Gwn_ShaderInput* buckets[GWN_NUM_SHADERINTERFACE_BUCKETS])
+ {
+ for (unsigned bucket_index = 0; bucket_index < GWN_NUM_SHADERINTERFACE_BUCKETS; ++bucket_index)
+ {
+ Gwn_ShaderInput *input = buckets[bucket_index];
+ while (input != NULL)
+ {
+ Gwn_ShaderInput *input_next = input->next;
+ free(input);
+ input = input_next;
+ }
+ }
+ }
+
+static bool setup_builtin_uniform(Gwn_ShaderInput* input, const char* name)
+ {
+ // TODO: reject DOUBLE, IMAGE, ATOMIC_COUNTER gl_types
+
+ // detect built-in uniforms (name must match)
+ for (Gwn_UniformBuiltin u = GWN_UNIFORM_NONE + 1; u < GWN_UNIFORM_CUSTOM; ++u)
+ {
+ const char* builtin_name = BuiltinUniform_name(u);
+ if (match(name, builtin_name))
+ {
+ input->builtin_type = u;
+ return true;
+ }
+ }
+
+ input->builtin_type = GWN_UNIFORM_CUSTOM;
+ return false;
+ }
+
+static const Gwn_ShaderInput* add_uniform(Gwn_ShaderInterface* shaderface, const char* name)
+ {
+ Gwn_ShaderInput* input = malloc(sizeof(Gwn_ShaderInput));
+
+ input->location = glGetUniformLocation(shaderface->program, name);
+
+ unsigned name_len = strlen(name);
+ shaderface->name_buffer = realloc(shaderface->name_buffer, shaderface->name_buffer_offset + name_len + 1); // include NULL terminator
+ char* name_buffer = shaderface->name_buffer + shaderface->name_buffer_offset;
+ strcpy(name_buffer, name);
+
+ set_input_name(shaderface, input, name, name_len);
+ setup_builtin_uniform(input, name);
+
+ shader_input_to_bucket(input, shaderface->uniform_buckets);
+ if (input->builtin_type != GWN_UNIFORM_NONE &&
+ input->builtin_type != GWN_UNIFORM_CUSTOM)
+ {
+ shaderface->builtin_uniforms[input->builtin_type] = input;
+ }
+#if DEBUG_SHADER_INTERFACE
+ printf("Gwn_ShaderInterface %p, program %d, uniform[] '%s' at location %d\n", shaderface, shaderface->program, name, input->location);
+#endif
+ return input;
+ }
+
+Gwn_ShaderInterface* GWN_shaderinterface_create(int32_t program)
+ {
+ Gwn_ShaderInterface* shaderface = calloc(1, sizeof(Gwn_ShaderInterface));
+ shaderface->program = program;
+
+#if DEBUG_SHADER_INTERFACE
+ printf("%s {\n", __func__); // enter function
+ printf("Gwn_ShaderInterface %p, program %d\n", shaderface, program);
+#endif
+
+ GLint max_attrib_name_len, attrib_ct;
+ glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &max_attrib_name_len);
+ glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &attrib_ct);
+
+ GLint max_ubo_name_len, ubo_ct;
+ glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, &max_ubo_name_len);
+ glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &ubo_ct);
+
+ const uint32_t name_buffer_len = attrib_ct * max_attrib_name_len + ubo_ct * max_ubo_name_len;
+ shaderface->name_buffer = malloc(name_buffer_len);
+
+ // Attributes
+ for (uint32_t i = 0; i < attrib_ct; ++i)
+ {
+ Gwn_ShaderInput* input = malloc(sizeof(Gwn_ShaderInput));
+ GLsizei remaining_buffer = name_buffer_len - shaderface->name_buffer_offset;
+ char* name = shaderface->name_buffer + shaderface->name_buffer_offset;
+ GLsizei name_len = 0;
+
+ glGetActiveAttrib(program, i, remaining_buffer, &name_len, &input->size, &input->gl_type, name);
+
+ // remove "[0]" from array name
+ if (name[name_len-1] == ']')
+ {
+ name[name_len-3] = '\0';
+ name_len -= 3;
+ }
+
+ // TODO: reject DOUBLE gl_types
+
+ input->location = glGetAttribLocation(program, name);
+
+ set_input_name(shaderface, input, name, name_len);
+
+ shader_input_to_bucket(input, shaderface->attrib_buckets);
+
+#if DEBUG_SHADER_INTERFACE
+ printf("attrib[%u] '%s' at location %d\n", i, name, input->location);
+#endif
+ }
+
+ // Uniform Blocks
+ for (uint32_t i = 0; i < ubo_ct; ++i)
+ {
+ Gwn_ShaderInput* input = malloc(sizeof(Gwn_ShaderInput));
+ GLsizei remaining_buffer = name_buffer_len - shaderface->name_buffer_offset;
+ char* name = shaderface->name_buffer + shaderface->name_buffer_offset;
+ GLsizei name_len = 0;
+
+ glGetActiveUniformBlockName(program, i, remaining_buffer, &name_len, name);
+
+ input->location = i;
+
+ set_input_name(shaderface, input, name, name_len);
+
+ shader_input_to_bucket(input, shaderface->ubo_buckets);
+
+#if DEBUG_SHADER_INTERFACE
+ printf("ubo '%s' at location %d\n", name, input->location);
+#endif
+ }
+
+ // Builtin Uniforms
+ for (Gwn_UniformBuiltin u = GWN_UNIFORM_NONE + 1; u < GWN_UNIFORM_CUSTOM; ++u)
+ {
+ const char* builtin_name = BuiltinUniform_name(u);
+ if (glGetUniformLocation(program, builtin_name) != -1)
+ add_uniform((Gwn_ShaderInterface*)shaderface, builtin_name);
+ }
+
+ // Batches ref buffer
+ shaderface->batches_ct = GWN_SHADERINTERFACE_REF_ALLOC_COUNT;
+ shaderface->batches = calloc(shaderface->batches_ct, sizeof(Gwn_Batch*));
+
+ return shaderface;
+ }
+
+void GWN_shaderinterface_discard(Gwn_ShaderInterface* shaderface)
+ {
+ // Free memory used by buckets and has entries.
+ buckets_free(shaderface->uniform_buckets);
+ buckets_free(shaderface->attrib_buckets);
+ buckets_free(shaderface->ubo_buckets);
+ // Free memory used by name_buffer.
+ free(shaderface->name_buffer);
+ // Remove this interface from all linked Batches vao cache.
+ for (int i = 0; i < shaderface->batches_ct; ++i)
+ if (shaderface->batches[i] != NULL)
+ gwn_batch_remove_interface_ref(shaderface->batches[i], shaderface);
+
+ free(shaderface->batches);
+ // Free memory used by shader interface by its self.
+ free(shaderface);
+ }
+
+const Gwn_ShaderInput* GWN_shaderinterface_uniform(const Gwn_ShaderInterface* shaderface, const char* name)
+ {
+ // TODO: Warn if we find a matching builtin, since these can be looked up much quicker.
+ const Gwn_ShaderInput* input = buckets_lookup(shaderface->uniform_buckets, shaderface->name_buffer, name);
+
+ // If input is not found add it so it's found next time.
+ if (input == NULL)
+ input = add_uniform((Gwn_ShaderInterface*)shaderface, name);
+
+ return (input->location != -1) ? input : NULL;
+ }
+
+const Gwn_ShaderInput* GWN_shaderinterface_uniform_builtin(const Gwn_ShaderInterface* shaderface, Gwn_UniformBuiltin builtin)
+ {
+#if TRUST_NO_ONE
+ assert(builtin != GWN_UNIFORM_NONE);
+ assert(builtin != GWN_UNIFORM_CUSTOM);
+ assert(builtin != GWN_NUM_UNIFORMS);
+#endif
+ return shaderface->builtin_uniforms[builtin];
+ }
+
+const Gwn_ShaderInput* GWN_shaderinterface_ubo(const Gwn_ShaderInterface* shaderface, const char* name)
+ {
+ return buckets_lookup(shaderface->ubo_buckets, shaderface->name_buffer, name);
+ }
+
+const Gwn_ShaderInput* GWN_shaderinterface_attr(const Gwn_ShaderInterface* shaderface, const char* name)
+ {
+ return buckets_lookup(shaderface->attrib_buckets, shaderface->name_buffer, name);
+ }
+
+void GWN_shaderinterface_add_batch_ref(Gwn_ShaderInterface* shaderface, Gwn_Batch* batch)
+ {
+ int i; // find first unused slot
+ for (i = 0; i < shaderface->batches_ct; ++i)
+ if (shaderface->batches[i] == NULL)
+ break;
+
+ if (i == shaderface->batches_ct)
+ {
+ // Not enough place, realloc the array.
+ i = shaderface->batches_ct;
+ shaderface->batches_ct += GWN_SHADERINTERFACE_REF_ALLOC_COUNT;
+ shaderface->batches = realloc(shaderface->batches, sizeof(Gwn_Batch*) * shaderface->batches_ct);
+ memset(shaderface->batches + i, 0, sizeof(Gwn_Batch*) * GWN_SHADERINTERFACE_REF_ALLOC_COUNT);
+ }
+
+ shaderface->batches[i] = batch;
+ }
+
+void GWN_shaderinterface_remove_batch_ref(Gwn_ShaderInterface* shaderface, Gwn_Batch* batch)
+ {
+ for (int i = 0; i < shaderface->batches_ct; ++i)
+ {
+ if (shaderface->batches[i] == batch)
+ {
+ shaderface->batches[i] = NULL;
+ break; // cannot have duplicates
+ }
+ }
+ }
diff --git a/intern/gawain/src/gwn_vertex_array_id.cpp b/intern/gawain/src/gwn_vertex_array_id.cpp
new file mode 100644
index 00000000000..2ab38a8fe18
--- /dev/null
+++ b/intern/gawain/src/gwn_vertex_array_id.cpp
@@ -0,0 +1,173 @@
+
+// Gawain vertex array IDs
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin, Clément Foucault
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.#include "buffer_id.h"
+
+#include "gwn_batch_private.h"
+#include "gwn_vertex_array_id.h"
+#include "gwn_context.h"
+#include <vector>
+#include <string.h>
+#include <pthread.h>
+#include <mutex>
+#include <unordered_set>
+
+#if 0
+extern "C" {
+extern int BLI_thread_is_main(void); // Blender-specific function
+}
+
+static bool thread_is_main()
+ {
+ // "main" here means the GL context's thread
+ return BLI_thread_is_main();
+ }
+#endif
+
+struct Gwn_Context {
+ GLuint default_vao;
+ std::unordered_set<Gwn_Batch*> batches; // Batches that have VAOs from this context
+ std::vector<GLuint> orphaned_vertarray_ids;
+ std::mutex orphans_mutex; // todo: try spinlock instead
+#if TRUST_NO_ONE
+ pthread_t thread; // Thread on which this context is active.
+ bool thread_is_used;
+
+ Gwn_Context()
+ {
+ thread_is_used = false;
+ }
+#endif
+};
+
+#if defined(_MSC_VER) && (_MSC_VER == 1800)
+#define thread_local __declspec(thread)
+thread_local Gwn_Context* active_ctx = NULL;
+#else
+static thread_local Gwn_Context* active_ctx = NULL;
+#endif
+
+static void clear_orphans(Gwn_Context* ctx)
+ {
+ ctx->orphans_mutex.lock();
+ if (!ctx->orphaned_vertarray_ids.empty())
+ {
+ unsigned orphan_ct = (unsigned)ctx->orphaned_vertarray_ids.size();
+ glDeleteVertexArrays(orphan_ct, ctx->orphaned_vertarray_ids.data());
+ ctx->orphaned_vertarray_ids.clear();
+ }
+ ctx->orphans_mutex.unlock();
+ }
+
+Gwn_Context* GWN_context_create(void)
+ {
+#if TRUST_NO_ONE
+ /* We cannot rely on this anymore. */
+ // assert(thread_is_main());
+#endif
+ Gwn_Context* ctx = new Gwn_Context;
+ glGenVertexArrays(1, &ctx->default_vao);
+ GWN_context_active_set(ctx);
+ return ctx;
+ }
+
+// to be called after GWN_context_active_set(ctx_to_destroy)
+void GWN_context_discard(Gwn_Context* ctx)
+ {
+#if TRUST_NO_ONE
+ // Make sure no other thread has locked it.
+ assert(ctx == active_ctx);
+ assert(pthread_equal(pthread_self(), ctx->thread));
+ assert(ctx->orphaned_vertarray_ids.empty());
+#endif
+ // delete remaining vaos
+ while (!ctx->batches.empty())
+ {
+ // this removes the array entry
+ gwn_batch_vao_cache_clear(*ctx->batches.begin());
+ }
+ glDeleteVertexArrays(1, &ctx->default_vao);
+ delete ctx;
+ active_ctx = NULL;
+ }
+
+// ctx can be NULL
+void GWN_context_active_set(Gwn_Context* ctx)
+ {
+#if TRUST_NO_ONE
+ if (active_ctx)
+ active_ctx->thread_is_used = false;
+ // Make sure no other context is already bound to this thread.
+ if (ctx)
+ {
+ // Make sure no other thread has locked it.
+ assert(ctx->thread_is_used == false);
+ ctx->thread = pthread_self();
+ ctx->thread_is_used = true;
+ }
+#endif
+ if (ctx)
+ clear_orphans(ctx);
+ active_ctx = ctx;
+ }
+
+Gwn_Context* GWN_context_active_get(void)
+ {
+ return active_ctx;
+ }
+
+GLuint GWN_vao_default(void)
+ {
+#if TRUST_NO_ONE
+ assert(active_ctx); // need at least an active context
+ assert(pthread_equal(pthread_self(), active_ctx->thread)); // context has been activated by another thread!
+#endif
+ return active_ctx->default_vao;
+ }
+
+GLuint GWN_vao_alloc(void)
+ {
+#if TRUST_NO_ONE
+ assert(active_ctx); // need at least an active context
+ assert(pthread_equal(pthread_self(), active_ctx->thread)); // context has been activated by another thread!
+#endif
+ clear_orphans(active_ctx);
+
+ GLuint new_vao_id = 0;
+ glGenVertexArrays(1, &new_vao_id);
+ return new_vao_id;
+ }
+
+// this can be called from multiple thread
+void GWN_vao_free(GLuint vao_id, Gwn_Context* ctx)
+ {
+#if TRUST_NO_ONE
+ assert(ctx);
+#endif
+ if (ctx == active_ctx)
+ glDeleteVertexArrays(1, &vao_id);
+ else
+ {
+ ctx->orphans_mutex.lock();
+ ctx->orphaned_vertarray_ids.emplace_back(vao_id);
+ ctx->orphans_mutex.unlock();
+ }
+ }
+
+void gwn_context_add_batch(Gwn_Context* ctx, Gwn_Batch* batch)
+ {
+ ctx->batches.emplace(batch);
+ }
+
+void gwn_context_remove_batch(Gwn_Context* ctx, Gwn_Batch* batch)
+ {
+ ctx->orphans_mutex.lock();
+ ctx->batches.erase(batch);
+ ctx->orphans_mutex.unlock();
+ }
diff --git a/intern/gawain/src/gwn_vertex_buffer.c b/intern/gawain/src/gwn_vertex_buffer.c
new file mode 100644
index 00000000000..f621b4c01b9
--- /dev/null
+++ b/intern/gawain/src/gwn_vertex_buffer.c
@@ -0,0 +1,258 @@
+
+// Gawain vertex buffer
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#include "gwn_vertex_buffer.h"
+#include "gwn_buffer_id.h"
+#include "gwn_vertex_format_private.h"
+#include <stdlib.h>
+#include <string.h>
+
+#define KEEP_SINGLE_COPY 1
+
+static unsigned vbo_memory_usage;
+
+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, usage);
+ return verts;
+ }
+
+Gwn_VertBuf* GWN_vertbuf_create_with_format_ex(const Gwn_VertFormat* format, Gwn_UsageType usage)
+ {
+ Gwn_VertBuf* verts = GWN_vertbuf_create(usage);
+ GWN_vertformat_copy(&verts->format, format);
+ if (!format->packed)
+ VertexFormat_pack(&verts->format);
+ return verts;
+
+ // this function might seem redundant, but there is potential for memory savings here...
+ // TODO: implement those memory savings
+ }
+
+void GWN_vertbuf_init(Gwn_VertBuf* verts, Gwn_UsageType usage)
+ {
+ memset(verts, 0, sizeof(Gwn_VertBuf));
+ verts->usage = usage;
+ verts->dirty = true;
+ }
+
+void GWN_vertbuf_init_with_format_ex(Gwn_VertBuf* verts, const Gwn_VertFormat* format, Gwn_UsageType usage)
+ {
+ GWN_vertbuf_init(verts, usage);
+ GWN_vertformat_copy(&verts->format, format);
+ if (!format->packed)
+ VertexFormat_pack(&verts->format);
+ }
+
+void GWN_vertbuf_discard(Gwn_VertBuf* verts)
+ {
+ if (verts->vbo_id)
+ {
+ GWN_buf_id_free(verts->vbo_id);
+#if VRAM_USAGE
+ vbo_memory_usage -= GWN_vertbuf_size_get(verts);
+#endif
+ }
+
+ if (verts->data)
+ free(verts->data);
+
+ free(verts);
+ }
+
+unsigned GWN_vertbuf_size_get(const Gwn_VertBuf* verts)
+ {
+ return vertex_buffer_size(&verts->format, verts->vertex_ct);
+ }
+
+// create a new allocation, discarding any existing data
+void GWN_vertbuf_data_alloc(Gwn_VertBuf* verts, unsigned v_ct)
+ {
+ Gwn_VertFormat* format = &verts->format;
+ if (!format->packed)
+ VertexFormat_pack(format);
+
+#if TRUST_NO_ONE
+ // catch any unnecessary use
+ assert(verts->vertex_alloc != v_ct || verts->data == NULL);
+#endif
+
+ // only create the buffer the 1st time
+ if (verts->vbo_id == 0)
+ verts->vbo_id = GWN_buf_id_alloc();
+
+ // discard previous data if any
+ if (verts->data)
+ free(verts->data);
+
+#if VRAM_USAGE
+ unsigned new_size = vertex_buffer_size(&verts->format, v_ct);
+ vbo_memory_usage += new_size - GWN_vertbuf_size_get(verts);
+#endif
+
+ verts->dirty = true;
+ verts->vertex_ct = verts->vertex_alloc = v_ct;
+ verts->data = malloc(sizeof(GLubyte) * GWN_vertbuf_size_get(verts));
+ }
+
+// resize buffer keeping existing data
+void GWN_vertbuf_data_resize(Gwn_VertBuf* verts, unsigned v_ct)
+ {
+#if TRUST_NO_ONE
+ assert(verts->data != NULL);
+ assert(verts->vertex_alloc != v_ct);
+#endif
+
+#if VRAM_USAGE
+ unsigned new_size = vertex_buffer_size(&verts->format, v_ct);
+ vbo_memory_usage += new_size - GWN_vertbuf_size_get(verts);
+#endif
+
+ verts->dirty = true;
+ verts->vertex_ct = verts->vertex_alloc = v_ct;
+ verts->data = realloc(verts->data, sizeof(GLubyte) * GWN_vertbuf_size_get(verts));
+ }
+
+// set vertex count but does not change allocation
+// only this many verts will be uploaded to the GPU and rendered
+// this is usefull for streaming data
+void GWN_vertbuf_vertex_count_set(Gwn_VertBuf* verts, unsigned v_ct)
+ {
+#if TRUST_NO_ONE
+ assert(verts->data != NULL); // only for dynamic data
+ assert(v_ct <= verts->vertex_alloc);
+#endif
+
+#if VRAM_USAGE
+ unsigned new_size = vertex_buffer_size(&verts->format, v_ct);
+ vbo_memory_usage += new_size - GWN_vertbuf_size_get(verts);
+#endif
+
+ verts->vertex_ct = v_ct;
+ }
+
+void GWN_vertbuf_attr_set(Gwn_VertBuf* verts, unsigned a_idx, unsigned v_idx, const void* data)
+ {
+ const Gwn_VertFormat* format = &verts->format;
+ const Gwn_VertAttr* a = format->attribs + a_idx;
+
+#if TRUST_NO_ONE
+ assert(a_idx < format->attrib_ct);
+ assert(v_idx < verts->vertex_alloc);
+ assert(verts->data != NULL);
+#endif
+
+ verts->dirty = true;
+ memcpy((GLubyte*)verts->data + a->offset + v_idx * format->stride, data, a->sz);
+ }
+
+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;
+
+#if TRUST_NO_ONE
+ assert(a_idx < format->attrib_ct);
+#endif
+
+ const unsigned stride = a->sz; // tightly packed input data
+
+ GWN_vertbuf_attr_fill_stride(verts, a_idx, stride, data);
+ }
+
+void GWN_vertbuf_attr_fill_stride(Gwn_VertBuf* verts, unsigned a_idx, unsigned stride, const void* data)
+ {
+ const Gwn_VertFormat* format = &verts->format;
+ const Gwn_VertAttr* a = format->attribs + a_idx;
+
+#if TRUST_NO_ONE
+ assert(a_idx < format->attrib_ct);
+ assert(verts->data != NULL);
+#endif
+
+ verts->dirty = true;
+ const unsigned vertex_ct = verts->vertex_ct;
+
+ if (format->attrib_ct == 1 && stride == format->stride)
+ {
+ // we can copy it all at once
+ memcpy(verts->data, data, vertex_ct * a->sz);
+ }
+ else
+ {
+ // we must copy it per vertex
+ for (unsigned v = 0; v < vertex_ct; ++v)
+ memcpy((GLubyte*)verts->data + a->offset + v * format->stride, (const GLubyte*)data + v * stride, a->sz);
+ }
+ }
+
+void GWN_vertbuf_attr_get_raw_data(Gwn_VertBuf* verts, unsigned a_idx, Gwn_VertBufRaw *access)
+ {
+ const Gwn_VertFormat* format = &verts->format;
+ const Gwn_VertAttr* a = format->attribs + a_idx;
+
+#if TRUST_NO_ONE
+ assert(a_idx < format->attrib_ct);
+ assert(verts->data != NULL);
+#endif
+
+ verts->dirty = true;
+
+ access->size = a->sz;
+ access->stride = format->stride;
+ access->data = (GLubyte*)verts->data + a->offset;
+ access->data_init = access->data;
+#if TRUST_NO_ONE
+ access->_data_end = access->data_init + (size_t)(verts->vertex_alloc * format->stride);
+#endif
+ }
+
+static void VertBuffer_upload_data(Gwn_VertBuf* verts)
+ {
+ unsigned buffer_sz = GWN_vertbuf_size_get(verts);
+
+ // orphan the vbo to avoid sync
+ glBufferData(GL_ARRAY_BUFFER, buffer_sz, NULL, convert_usage_type_to_gl(verts->usage));
+ // upload data
+ glBufferSubData(GL_ARRAY_BUFFER, 0, buffer_sz, verts->data);
+
+ if (verts->usage == GWN_USAGE_STATIC)
+ {
+ free(verts->data);
+ verts->data = NULL;
+ }
+
+ verts->dirty = false;
+ }
+
+void GWN_vertbuf_use(Gwn_VertBuf* verts)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, verts->vbo_id);
+
+ if (verts->dirty)
+ VertBuffer_upload_data(verts);
+ }
+
+unsigned GWN_vertbuf_get_memory_usage(void)
+ {
+ return vbo_memory_usage;
+ }
diff --git a/intern/gawain/src/gwn_vertex_format.c b/intern/gawain/src/gwn_vertex_format.c
new file mode 100644
index 00000000000..c180c304d28
--- /dev/null
+++ b/intern/gawain/src/gwn_vertex_format.c
@@ -0,0 +1,291 @@
+
+// Gawain vertex format
+//
+// This code is part of the Gawain library, with modifications
+// specific to integration with Blender.
+//
+// Copyright 2016 Mike Erwin
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
+// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#include "gwn_vertex_format.h"
+#include "gwn_vertex_format_private.h"
+#include <stddef.h>
+#include <string.h>
+
+#define PACK_DEBUG 0
+
+#if PACK_DEBUG
+ #include <stdio.h>
+#endif
+
+void GWN_vertformat_clear(Gwn_VertFormat* format)
+ {
+#if TRUST_NO_ONE
+ memset(format, 0, sizeof(Gwn_VertFormat));
+#else
+ format->attrib_ct = 0;
+ format->packed = false;
+ format->name_offset = 0;
+ format->name_ct = 0;
+
+ for (unsigned i = 0; i < GWN_VERT_ATTR_MAX_LEN; i++)
+ format->attribs[i].name_ct = 0;
+#endif
+ }
+
+void GWN_vertformat_copy(Gwn_VertFormat* dest, const Gwn_VertFormat* src)
+ {
+ // copy regular struct fields
+ memcpy(dest, src, sizeof(Gwn_VertFormat));
+
+ for (unsigned i = 0; i < dest->attrib_ct; i++)
+ for (unsigned j = 0; j < dest->attribs[i].name_ct; j++)
+ dest->attribs[i].name[j] = (char *)dest + (src->attribs[i].name[j] - ((char *)src));
+ }
+
+static GLenum convert_comp_type_to_gl(Gwn_VertCompType type)
+ {
+ static const GLenum table[] = {
+ [GWN_COMP_I8] = GL_BYTE,
+ [GWN_COMP_U8] = GL_UNSIGNED_BYTE,
+ [GWN_COMP_I16] = GL_SHORT,
+ [GWN_COMP_U16] = GL_UNSIGNED_SHORT,
+ [GWN_COMP_I32] = GL_INT,
+ [GWN_COMP_U32] = GL_UNSIGNED_INT,
+
+ [GWN_COMP_F32] = GL_FLOAT,
+
+ [GWN_COMP_I10] = GL_INT_2_10_10_10_REV
+ };
+ return table[type];
+ }
+
+static unsigned comp_sz(Gwn_VertCompType type)
+ {
+#if TRUST_NO_ONE
+ assert(type <= GWN_COMP_F32); // other types have irregular sizes (not bytes)
+#endif
+
+ const GLubyte sizes[] = {1,1,2,2,4,4,4};
+ return sizes[type];
+ }
+
+static unsigned attrib_sz(const Gwn_VertAttr *a)
+ {
+ if (a->comp_type == GWN_COMP_I10)
+ return 4; // always packed as 10_10_10_2
+
+ return a->comp_ct * comp_sz(a->comp_type);
+ }
+
+static unsigned attrib_align(const Gwn_VertAttr *a)
+ {
+ if (a->comp_type == GWN_COMP_I10)
+ return 4; // always packed as 10_10_10_2
+
+ unsigned c = comp_sz(a->comp_type);
+ if (a->comp_ct == 3 && c <= 2)
+ return 4 * c; // AMD HW can't fetch these well, so pad it out (other vendors too?)
+ else
+ return c; // most fetches are ok if components are naturally aligned
+ }
+
+unsigned vertex_buffer_size(const Gwn_VertFormat* format, unsigned vertex_ct)
+ {
+#if TRUST_NO_ONE
+ assert(format->packed && format->stride > 0);
+#endif
+
+ return format->stride * vertex_ct;
+ }
+
+static const char* copy_attrib_name(Gwn_VertFormat* format, const char* name)
+ {
+ // strncpy does 110% of what we need; let's do exactly 100%
+ char* name_copy = format->names + format->name_offset;
+ unsigned available = GWN_VERT_ATTR_NAMES_BUF_LEN - format->name_offset;
+ bool terminated = false;
+
+ for (unsigned i = 0; i < available; ++i)
+ {
+ const char c = name[i];
+ name_copy[i] = c;
+ if (c == '\0')
+ {
+ terminated = true;
+ format->name_offset += (i + 1);
+ break;
+ }
+ }
+
+#if TRUST_NO_ONE
+ assert(terminated);
+ assert(format->name_offset <= GWN_VERT_ATTR_NAMES_BUF_LEN);
+#else
+ (void)terminated;
+#endif
+
+ return name_copy;
+ }
+
+unsigned GWN_vertformat_attr_add(Gwn_VertFormat* format, const char* name, Gwn_VertCompType comp_type, unsigned comp_ct, Gwn_VertFetchMode fetch_mode)
+ {
+#if TRUST_NO_ONE
+ assert(format->name_ct < GWN_VERT_ATTR_MAX_LEN); // there's room for more
+ assert(format->attrib_ct < GWN_VERT_ATTR_MAX_LEN); // there's room for more
+ assert(!format->packed); // packed means frozen/locked
+ assert((comp_ct >= 1 && comp_ct <= 4) || comp_ct == 8 || comp_ct == 12 || comp_ct == 16);
+ switch (comp_type)
+ {
+ case GWN_COMP_F32:
+ // float type can only kept as float
+ assert(fetch_mode == GWN_FETCH_FLOAT);
+ break;
+ case GWN_COMP_I10:
+ // 10_10_10 format intended for normals (xyz) or colors (rgb)
+ // extra component packed.w can be manually set to { -2, -1, 0, 1 }
+ assert(comp_ct == 3 || comp_ct == 4);
+ assert(fetch_mode == GWN_FETCH_INT_TO_FLOAT_UNIT); // not strictly required, may relax later
+ break;
+ default:
+ // integer types can be kept as int or converted/normalized to float
+ assert(fetch_mode != GWN_FETCH_FLOAT);
+ // only support float matrices (see Batch_update_program_bindings)
+ assert(comp_ct != 8 && comp_ct != 12 && comp_ct != 16);
+ }
+#endif
+ format->name_ct++; // multiname support
+
+ const unsigned attrib_id = format->attrib_ct++;
+ Gwn_VertAttr* attrib = format->attribs + attrib_id;
+
+ attrib->name[attrib->name_ct++] = copy_attrib_name(format, name);
+ attrib->comp_type = comp_type;
+ attrib->gl_comp_type = convert_comp_type_to_gl(comp_type);
+ attrib->comp_ct = (comp_type == GWN_COMP_I10) ? 4 : comp_ct; // system needs 10_10_10_2 to be 4 or BGRA
+ attrib->sz = attrib_sz(attrib);
+ attrib->offset = 0; // offsets & stride are calculated later (during pack)
+ attrib->fetch_mode = fetch_mode;
+
+ return attrib_id;
+ }
+
+void GWN_vertformat_alias_add(Gwn_VertFormat* format, const char* alias)
+ {
+ Gwn_VertAttr* attrib = format->attribs + (format->attrib_ct - 1);
+#if TRUST_NO_ONE
+ assert(format->name_ct < GWN_VERT_ATTR_MAX_LEN); // there's room for more
+ assert(attrib->name_ct < GWN_VERT_ATTR_MAX_NAMES);
+#endif
+ format->name_ct++; // multiname support
+ attrib->name[attrib->name_ct++] = copy_attrib_name(format, alias);
+ }
+
+unsigned padding(unsigned offset, unsigned alignment)
+ {
+ const unsigned mod = offset % alignment;
+ return (mod == 0) ? 0 : (alignment - mod);
+ }
+
+#if PACK_DEBUG
+static void show_pack(unsigned a_idx, unsigned sz, unsigned pad)
+ {
+ const char c = 'A' + a_idx;
+ for (unsigned i = 0; i < pad; ++i)
+ putchar('-');
+ for (unsigned i = 0; i < sz; ++i)
+ putchar(c);
+ }
+#endif
+
+void VertexFormat_pack(Gwn_VertFormat* format)
+ {
+ // for now, attributes are packed in the order they were added,
+ // making sure each attrib is naturally aligned (add padding where necessary)
+
+ // later we can implement more efficient packing w/ reordering
+ // (keep attrib ID order, adjust their offsets to reorder in buffer)
+
+ // TODO:
+ // realloc just enough to hold the final combo string. And just enough to
+ // hold used attribs, not all 16.
+
+ Gwn_VertAttr* a0 = format->attribs + 0;
+ a0->offset = 0;
+ unsigned offset = a0->sz;
+
+#if PACK_DEBUG
+ show_pack(0, a0->sz, 0);
+#endif
+
+ for (unsigned a_idx = 1; a_idx < format->attrib_ct; ++a_idx)
+ {
+ Gwn_VertAttr* a = format->attribs + a_idx;
+ unsigned mid_padding = padding(offset, attrib_align(a));
+ offset += mid_padding;
+ a->offset = offset;
+ offset += a->sz;
+
+#if PACK_DEBUG
+ show_pack(a_idx, a->sz, mid_padding);
+#endif
+ }
+
+ unsigned end_padding = padding(offset, attrib_align(a0));
+
+#if PACK_DEBUG
+ show_pack(0, 0, end_padding);
+ putchar('\n');
+#endif
+
+ format->stride = offset + end_padding;
+ format->packed = true;
+ }
+
+
+// OpenGL ES packs in a different order as desktop GL but component conversion is the same.
+// Of the code here, only struct Gwn_PackedNormal needs to change.
+
+#define SIGNED_INT_10_MAX 511
+#define SIGNED_INT_10_MIN -512
+
+static int clampi(int x, int min_allowed, int max_allowed)
+ {
+#if TRUST_NO_ONE
+ assert(min_allowed <= max_allowed);
+#endif
+
+ if (x < min_allowed)
+ return min_allowed;
+ else if (x > max_allowed)
+ return max_allowed;
+ else
+ return x;
+ }
+
+static int quantize(float x)
+ {
+ int qx = x * 511.0f;
+ return clampi(qx, SIGNED_INT_10_MIN, SIGNED_INT_10_MAX);
+ }
+
+static int convert_i16(short x)
+ {
+ // 16-bit signed --> 10-bit signed
+ return x >> 6;
+ // TODO: round?
+ }
+
+Gwn_PackedNormal GWN_normal_convert_i10_v3(const float data[3])
+ {
+ Gwn_PackedNormal n = { .x = quantize(data[0]), .y = quantize(data[1]), .z = quantize(data[2]) };
+ return n;
+ }
+
+Gwn_PackedNormal GWN_normal_convert_i10_s3(const short data[3])
+ {
+ Gwn_PackedNormal n = { .x = convert_i16(data[0]), .y = convert_i16(data[1]), .z = convert_i16(data[2]) };
+ return n;
+ }