diff options
Diffstat (limited to 'intern/gawain')
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..ced52b22e2c --- /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..07ef96061b7 --- /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 + GLenum gl_prim_type; + + // cached values (avoid dereferencing later) + GLuint vao_id; + GLuint 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]; + GLuint vao_ids[GWN_BATCH_VAO_STATIC_LEN]; + } static_vaos; + // Dynamic handle count + struct { + unsigned count; + const struct Gwn_ShaderInterface** interfaces; + GLuint* 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*, GLuint program, const Gwn_ShaderInterface*); +void GWN_batch_program_set(Gwn_Batch*, GLuint 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..a80da71e0e8 --- /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; + GLenum gl_index_type; + unsigned min_index; + unsigned max_index; + unsigned base_index; +#endif + GLuint 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..7866f83e774 --- /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(GLuint 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..150b3b4fcc0 --- /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 + GLenum gl_type; // only for attrib inputs + GLint size; // only for attrib inputs + GLint location; +} Gwn_ShaderInput; + +#define GWN_NUM_SHADERINTERFACE_BUCKETS 257 +#define GWN_SHADERINTERFACE_REF_ALLOC_COUNT 16 + +typedef struct Gwn_ShaderInterface { + GLint 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(GLint 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..e9a37519b36 --- /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; + GLubyte* data; // NULL indicates data in VRAM (unmapped) + GLuint vbo_id; // 0 indicates not yet allocated + Gwn_UsageType usage; // usage hint for GL optimisation +} Gwn_VertBuf; + +Gwn_VertBuf* GWN_vertbuf_create(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; + GLubyte* data; + GLubyte* data_init; +#if TRUST_NO_ONE + // Only for overflow check + GLubyte* _data_end; +#endif +} Gwn_VertBufRaw; + +GWN_INLINE void *GWN_vertbuf_raw_step(Gwn_VertBufRaw *a) + { + GLubyte* 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..64d5d146578 --- /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, GLuint 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, GLuint 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..95c18cf1d39 --- /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(GLint 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; + } |