diff options
author | Mike Erwin <significant.bit@gmail.com> | 2017-03-17 06:32:35 +0300 |
---|---|---|
committer | Mike Erwin <significant.bit@gmail.com> | 2017-03-17 06:32:35 +0300 |
commit | 4452bea2f170ec7fe48c223c8c499f1eb9b5356c (patch) | |
tree | 28dcc9ac042fcce5c77f3c60b4ce8af8677fa4a8 /intern | |
parent | b4e8dc8c82dfcae60f33e39684272867745a8af2 (diff) |
move Gawain library to intern
Before now it lived in source/blender/gpu for convenience. Only a few files in the gpu module use Gawain directly.
Tested on Mac, time to push and test on Windows.
Todo: some CMake magic to make it easy to
#include "gawain/some_header.h"
from any C or H file. Main problem here is the many editors that include GPU_immediate.h which includes Gawain's immediate.h -- is there a way to avoid changing every editor's CMakeLists?
Diffstat (limited to 'intern')
23 files changed, 3077 insertions, 0 deletions
diff --git a/intern/CMakeLists.txt b/intern/CMakeLists.txt index bfe230250ae..af3b9296077 100644 --- a/intern/CMakeLists.txt +++ b/intern/CMakeLists.txt @@ -33,6 +33,7 @@ add_subdirectory(opencolorio) add_subdirectory(mikktspace) add_subdirectory(glew-mx) add_subdirectory(eigen) +add_subdirectory(gawain) if(WITH_GAMEENGINE_DECKLINK) add_subdirectory(decklink) diff --git a/intern/gawain/CMakeLists.txt b/intern/gawain/CMakeLists.txt new file mode 100644 index 00000000000..99adab851c7 --- /dev/null +++ b/intern/gawain/CMakeLists.txt @@ -0,0 +1,34 @@ + +set(INC gawain) + +set(INC_SYS ${GLEW_INCLUDE_PATH}) + +set(SRC + src/attrib_binding.c + src/batch.c + src/element.c + src/buffer_id.cpp + src/immediate.c + src/imm_util.c + src/primitive.c + src/shader_interface.c + src/vertex_buffer.c + src/vertex_format.c + + gawain/attrib_binding.h + gawain/batch.h + gawain/buffer_id.h + gawain/common.h + gawain/element.h + gawain/immediate.h + gawain/imm_util.h + gawain/primitive.h + gawain/shader_interface.h + gawain/vertex_buffer.h + gawain/vertex_format.h +) + +# remove this when we switch to core profile +add_definitions(-DWITH_GL_PROFILE_COMPAT) + +blender_add_lib(bf_intern_gawain "${SRC}" "${INC}" "${INC_SYS}") diff --git a/intern/gawain/gawain/attrib_binding.h b/intern/gawain/gawain/attrib_binding.h new file mode 100644 index 00000000000..9e2431ca379 --- /dev/null +++ b/intern/gawain/gawain/attrib_binding.h @@ -0,0 +1,24 @@ + +// 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 "vertex_format.h" + +typedef struct { + uint64_t loc_bits; // store 4 bits for each of the 16 attribs + uint16_t enabled_bits; // 1 bit for each attrib +} AttribBinding; + +void clear_AttribBinding(AttribBinding*); + +void get_attrib_locations(const VertexFormat*, AttribBinding*, GLuint program); +unsigned read_attrib_location(const AttribBinding*, unsigned a_idx); diff --git a/intern/gawain/gawain/batch.h b/intern/gawain/gawain/batch.h new file mode 100644 index 00000000000..189a586f964 --- /dev/null +++ b/intern/gawain/gawain/batch.h @@ -0,0 +1,110 @@ + +// 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 "vertex_buffer.h" +#include "element.h" + +typedef enum { + READY_TO_FORMAT, + READY_TO_BUILD, + BUILDING, + READY_TO_DRAW +} BatchPhase; + +#define BATCH_MAX_VBO_CT 3 + +typedef struct Batch { + // geometry + VertexBuffer* verts[BATCH_MAX_VBO_CT]; // verts[0] is required, others can be NULL + ElementList* elem; // NULL if element list not needed + PrimitiveType prim_type; + + // book-keeping + GLuint vao_id; // remembers all geometry state (vertex attrib bindings & element buffer) + BatchPhase phase; + bool program_dirty; + bool program_in_use; + + // state + GLuint program; +} Batch; + +Batch* Batch_create(PrimitiveType, VertexBuffer*, ElementList*); +void Batch_init(Batch*, PrimitiveType, VertexBuffer*, ElementList*); + +void Batch_discard(Batch*); // verts & elem are not discarded +void Batch_discard_all(Batch*); // including verts & elem + +int Batch_add_VertexBuffer(Batch*, VertexBuffer*); + +void Batch_set_program(Batch*, GLuint program); +// 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 Batch_use_program(Batch*); // call before Batch_Uniform (temp hack?) +void Batch_done_using_program(Batch*); + +void Batch_Uniform1i(Batch*, const char* name, int value); +void Batch_Uniform1b(Batch*, const char* name, bool value); +void Batch_Uniform1f(Batch*, const char* name, float value); +void Batch_Uniform2f(Batch*, const char* name, float x, float y); +void Batch_Uniform3f(Batch*, const char* name, float x, float y, float z); +void Batch_Uniform4f(Batch*, const char* name, float x, float y, float z, float w); +void Batch_Uniform3fv(Batch*, const char* name, const float data[3]); +void Batch_Uniform4fv(Batch*, const char* name, const float data[4]); + +void Batch_draw(Batch*); + + +// clement : temp stuff +void Batch_draw_stupid(Batch*); +void Batch_draw_stupid_instanced(Batch*, unsigned int instance_vbo, int instance_count, + int attrib_nbr, int attrib_stride, int attrib_loc[16], int attrib_size[16]); + + + + + + +#if 0 // future plans + +// Can multiple batches share a VertexBuffer? 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 { + Batch batch; + VertexBuffer verts; // link batch.verts to this +} BatchWithOwnVertexBuffer; + +typedef struct { + Batch batch; + ElementList elem; // link batch.elem to this +} BatchWithOwnElementList; + +typedef struct { + Batch batch; + ElementList elem; // link batch.elem to this + VertexBuffer verts; // link batch.verts to this +} BatchWithOwnVertexBufferAndElementList; + +Batch* create_BatchWithOwnVertexBuffer(PrimitiveType, VertexFormat*, unsigned v_ct, ElementList*); +Batch* create_BatchWithOwnElementList(PrimitiveType, VertexBuffer*, unsigned prim_ct); +Batch* create_BatchWithOwnVertexBufferAndElementList(PrimitiveType, VertexFormat*, unsigned v_ct, unsigned prim_ct); +// verts: shared, own +// elem: none, shared, own +Batch* create_BatchInGeneral(PrimitiveType, VertexBufferStuff, ElementListStuff); + +#endif // future plans diff --git a/intern/gawain/gawain/buffer_id.h b/intern/gawain/gawain/buffer_id.h new file mode 100644 index 00000000000..3f67458d060 --- /dev/null +++ b/intern/gawain/gawain/buffer_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 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 "common.h" + +GLuint buffer_id_alloc(void); +void buffer_id_free(GLuint buffer_id); + +GLuint vao_id_alloc(void); +void vao_id_free(GLuint vao_id); + + +#ifdef __cplusplus +} +#endif diff --git a/intern/gawain/gawain/common.h b/intern/gawain/gawain/common.h new file mode 100644 index 00000000000..a4c1e6cba6e --- /dev/null +++ b/intern/gawain/gawain/common.h @@ -0,0 +1,40 @@ + +// 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 TRUST_NO_ONE !defined(NDEBUG) +// strict error checking, enabled for debug builds during early development + +#include <GL/glew.h> +#include <stdbool.h> +#include <stdint.h> + +#if TRUST_NO_ONE + #include <assert.h> +#endif + +#define PER_THREAD +// #define PER_THREAD __thread +// MSVC uses __declspec(thread) for C code + +#define APPLE_LEGACY (defined(__APPLE__) && defined(WITH_GL_PROFILE_COMPAT)) + +#if APPLE_LEGACY + #undef glGenVertexArrays + #define glGenVertexArrays glGenVertexArraysAPPLE + + #undef glDeleteVertexArrays + #define glDeleteVertexArrays glDeleteVertexArraysAPPLE + + #undef glBindVertexArray + #define glBindVertexArray glBindVertexArrayAPPLE +#endif diff --git a/intern/gawain/gawain/element.h b/intern/gawain/gawain/element.h new file mode 100644 index 00000000000..f22d7c0ffda --- /dev/null +++ b/intern/gawain/gawain/element.h @@ -0,0 +1,64 @@ + +// 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 "primitive.h" + +#define TRACK_INDEX_RANGE 1 + +typedef enum { + INDEX_U8 = GL_UNSIGNED_BYTE, // GL has this, Vulkan does not + INDEX_U16 = GL_UNSIGNED_SHORT, + INDEX_U32 = GL_UNSIGNED_INT +} IndexType; + +typedef struct { + unsigned index_ct; +#if TRACK_INDEX_RANGE + IndexType index_type; + unsigned min_index; + unsigned max_index; + unsigned base_index; +#endif + void* data; // NULL indicates data in VRAM (unmapped) or not yet allocated + GLuint vbo_id; // 0 indicates not yet sent to VRAM +} ElementList; + +void ElementList_use(ElementList*); +unsigned ElementList_size(const ElementList*); + +typedef struct { + unsigned max_allowed_index; + unsigned max_index_ct; + unsigned index_ct; + PrimitiveType prim_type; + unsigned* data; +} ElementListBuilder; + +// supported primitives: +// PRIM_POINTS +// PRIM_LINES +// PRIM_TRIANGLES + +void ElementListBuilder_init(ElementListBuilder*, PrimitiveType, unsigned prim_ct, unsigned vertex_ct); +//void ElementListBuilder_init_custom(ElementListBuilder*, PrimitiveType, unsigned index_ct, unsigned vertex_ct); + +void add_generic_vertex(ElementListBuilder*, unsigned v); + +void add_point_vertex(ElementListBuilder*, unsigned v); +void add_line_vertices(ElementListBuilder*, unsigned v1, unsigned v2); +void add_triangle_vertices(ElementListBuilder*, unsigned v1, unsigned v2, unsigned v3); + +ElementList* ElementList_build(ElementListBuilder*); +void ElementList_build_in_place(ElementListBuilder*, ElementList*); + +void ElementList_discard(ElementList*); diff --git a/intern/gawain/gawain/imm_util.h b/intern/gawain/gawain/imm_util.h new file mode 100644 index 00000000000..730bd7c1a3c --- /dev/null +++ b/intern/gawain/gawain/imm_util.h @@ -0,0 +1,18 @@ + +// 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); diff --git a/intern/gawain/gawain/immediate.h b/intern/gawain/gawain/immediate.h new file mode 100644 index 00000000000..6a039542065 --- /dev/null +++ b/intern/gawain/gawain/immediate.h @@ -0,0 +1,112 @@ + +// 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 "vertex_format.h" +#include "primitive.h" + +#define IMM_BATCH_COMBO 1 + + +VertexFormat* immVertexFormat(void); // returns a cleared vertex format, ready for add_attrib + +void immBindProgram(GLuint program); // every immBegin must have a program bound first +void immUnbindProgram(void); // call after your last immEnd, or before binding another program + +void immBegin(PrimitiveType, unsigned vertex_ct); // must supply exactly vertex_ct vertices +void immBeginAtMost(PrimitiveType, unsigned max_vertex_ct); // can supply fewer vertices +void immEnd(void); // finishes and draws + +#if IMM_BATCH_COMBO +#include "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. +Batch* immBeginBatch(PrimitiveType, unsigned vertex_ct); +Batch* immBeginBatchAtMost(PrimitiveType, 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 immAttrib2s(unsigned attrib_id, short x, short y); + +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 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 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 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 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/primitive.h b/intern/gawain/gawain/primitive.h new file mode 100644 index 00000000000..d1b8f5b3ec7 --- /dev/null +++ b/intern/gawain/gawain/primitive.h @@ -0,0 +1,43 @@ + +// 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 "common.h" + +typedef enum { + PRIM_POINTS = GL_POINTS, + PRIM_LINES = GL_LINES, + PRIM_TRIANGLES = GL_TRIANGLES, + +#ifdef WITH_GL_PROFILE_COMPAT + PRIM_QUADS = GL_QUADS, // legacy GL has this, modern GL & Vulkan do not +#endif + + PRIM_LINE_STRIP = GL_LINE_STRIP, + PRIM_LINE_LOOP = GL_LINE_LOOP, // GL has this, Vulkan does not + PRIM_TRIANGLE_STRIP = GL_TRIANGLE_STRIP, + PRIM_TRIANGLE_FAN = GL_TRIANGLE_FAN, + + PRIM_NONE = 0xF +} PrimitiveType; + +// what types of primitives does each shader expect? +typedef enum { + PRIM_CLASS_NONE = 0, + PRIM_CLASS_POINT = (1 << 0), + PRIM_CLASS_LINE = (1 << 1), + PRIM_CLASS_SURFACE = (1 << 2), + PRIM_CLASS_ANY = PRIM_CLASS_POINT | PRIM_CLASS_LINE | PRIM_CLASS_SURFACE +} PrimitiveClass; + +PrimitiveClass prim_class_of_type(PrimitiveType); +bool prim_type_belongs_to_class(PrimitiveType, PrimitiveClass); diff --git a/intern/gawain/gawain/shader_interface.h b/intern/gawain/gawain/shader_interface.h new file mode 100644 index 00000000000..bdb0bbf4a8c --- /dev/null +++ b/intern/gawain/gawain/shader_interface.h @@ -0,0 +1,49 @@ + +// 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 "common.h" + +typedef enum { + UNIFORM_NONE, // uninitialized/unknown + + UNIFORM_MODELVIEW_3D, // mat4 ModelViewMatrix + UNIFORM_PROJECTION_3D, // mat4 ProjectionMatrix + UNIFORM_MVP_3D, // mat4 ModelViewProjectionMatrix + UNIFORM_NORMAL_3D, // mat3 NormalMatrix + UNIFORM_INV_NORMAL_3D, // mat3 InverseNormalMatrix + + UNIFORM_MODELVIEW_2D, // mat3 ModelViewMatrix + UNIFORM_PROJECTION_2D, // mat3 ProjectionMatrix + UNIFORM_MVP_2D, // mat3 ModelViewProjectionMatrix + + UNIFORM_COLOR, // vec4 color + + UNIFORM_CUSTOM // custom uniform, not one of the above built-ins +} BuiltinUniform; + +typedef struct { + const char* name; + GLenum gl_type; + BuiltinUniform builtin_type; // only for uniform inputs + GLint size; + GLint location; +} ShaderInput; + +typedef struct { + uint16_t uniform_ct; + uint16_t attrib_ct; + ShaderInput inputs[0]; // dynamic size, uniforms followed by attribs +} ShaderInterface; + +ShaderInterface* ShaderInterface_create(GLint program_id); +void ShaderInterface_discard(ShaderInterface*); diff --git a/intern/gawain/gawain/vertex_buffer.h b/intern/gawain/gawain/vertex_buffer.h new file mode 100644 index 00000000000..6a72cfe6ff3 --- /dev/null +++ b/intern/gawain/gawain/vertex_buffer.h @@ -0,0 +1,64 @@ + +// 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 "vertex_format.h" + +// How to create a VertexBuffer: +// 1) verts = create_VertexBuffer() or init_VertexBuffer(verts) +// 2) add_attrib(verts->format, ...) +// 3) allocate_vertex_data(verts, vertex_ct) <-- finalizes/packs vertex format +// 4) fillAttrib(verts, pos, application_pos_buffer) +// 5) prime_VertexBuffer(verts); + +// Is VertexBuffer always used as part of a Batch? + +typedef struct { + VertexFormat format; + unsigned vertex_ct; + GLubyte* data; // NULL indicates data in VRAM (unmapped) or not yet allocated + GLuint vbo_id; // 0 indicates not yet sent to VRAM +} VertexBuffer; + +VertexBuffer* VertexBuffer_create(void); +VertexBuffer* VertexBuffer_create_with_format(const VertexFormat*); + +void VertexBuffer_discard(VertexBuffer*); + +void VertexBuffer_init(VertexBuffer*); +void VertexBuffer_init_with_format(VertexBuffer*, const VertexFormat*); + +unsigned VertexBuffer_size(const VertexBuffer*); +void VertexBuffer_allocate_data(VertexBuffer*, unsigned v_ct); +void VertexBuffer_resize_data(VertexBuffer*, unsigned v_ct); + +// The most important setAttrib 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 setAttrib(VertexBuffer*, unsigned a_idx, unsigned v_idx, const void* data); +void fillAttrib(VertexBuffer*, unsigned a_idx, const void* data); // tightly packed, non interleaved input data +void fillAttribStride(VertexBuffer*, unsigned a_idx, unsigned stride, const void* data); + +// 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 VertexBuffer_use(VertexBuffer*); diff --git a/intern/gawain/gawain/vertex_format.h b/intern/gawain/gawain/vertex_format.h new file mode 100644 index 00000000000..4ed9e58f376 --- /dev/null +++ b/intern/gawain/gawain/vertex_format.h @@ -0,0 +1,87 @@ + +// 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 "common.h" + +#define MAX_VERTEX_ATTRIBS 16 +#define AVG_VERTEX_ATTRIB_NAME_LEN 5 +#define VERTEX_ATTRIB_NAMES_BUFFER_LEN ((AVG_VERTEX_ATTRIB_NAME_LEN + 1) * MAX_VERTEX_ATTRIBS) + +#define USE_10_10_10 defined(_WIN32) +// (GLEW_VERSION_3_3 || GLEW_ARB_vertex_type_2_10_10_10_rev) +// ^-- this is only guaranteed on Windows right now, will be true on all platforms soon + +typedef enum { + COMP_I8 = GL_BYTE, + COMP_U8 = GL_UNSIGNED_BYTE, + COMP_I16 = GL_SHORT, + COMP_U16 = GL_UNSIGNED_SHORT, + COMP_I32 = GL_INT, + COMP_U32 = GL_UNSIGNED_INT, + + COMP_F32 = GL_FLOAT, // TODO: drop the GL_ equivalence here, use a private lookup table + +#if USE_10_10_10 + COMP_I10 = GL_INT_2_10_10_10_REV +#endif +} VertexCompType; + +typedef enum { + KEEP_FLOAT, + KEEP_INT, + NORMALIZE_INT_TO_FLOAT, // 127 (ubyte) -> 0.5 (and so on for other int types) + CONVERT_INT_TO_FLOAT // 127 (any int type) -> 127.0 +} VertexFetchMode; + +typedef struct { + VertexCompType comp_type; + unsigned comp_ct; // 1 to 4 + unsigned sz; // size in bytes, 1 to 16 + unsigned offset; // from beginning of vertex, in bytes + VertexFetchMode fetch_mode; + const char* name; +} Attrib; + +typedef struct { + unsigned attrib_ct; // 0 to 16 (MAX_VERTEX_ATTRIBS) + unsigned stride; // stride in bytes, 1 to 256 + bool packed; + Attrib attribs[MAX_VERTEX_ATTRIBS]; // TODO: variable-size attribs array + char names[VERTEX_ATTRIB_NAMES_BUFFER_LEN]; + unsigned name_offset; +} VertexFormat; + +void VertexFormat_clear(VertexFormat*); +void VertexFormat_copy(VertexFormat* dest, const VertexFormat* src); + +unsigned add_attrib(VertexFormat*, const char* name, VertexCompType, unsigned comp_ct, VertexFetchMode); + +// format conversion + +#if USE_10_10_10 + +typedef struct { + int x : 10; + int y : 10; + int z : 10; + int w : 2; // 0 by default, can manually set to { -2, -1, 0, 1 } +} PackedNormal; + +PackedNormal convert_i10_v3(const float data[3]); + +#endif // USE_10_10_10 + +// for internal use +void VertexFormat_pack(VertexFormat*); +unsigned padding(unsigned offset, unsigned alignment); +unsigned vertex_buffer_size(const VertexFormat*, unsigned vertex_ct); diff --git a/intern/gawain/src/attrib_binding.c b/intern/gawain/src/attrib_binding.c new file mode 100644 index 00000000000..bb42aaf66eb --- /dev/null +++ b/intern/gawain/src/attrib_binding.c @@ -0,0 +1,69 @@ + +// 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 "attrib_binding.h" + +#if MAX_VERTEX_ATTRIBS != 16 + #error "attrib binding code assumes MAX_VERTEX_ATTRIBS = 16" +#endif + +void clear_AttribBinding(AttribBinding* binding) + { + binding->loc_bits = 0; + binding->enabled_bits = 0; + } + +unsigned read_attrib_location(const AttribBinding* binding, unsigned a_idx) + { +#if TRUST_NO_ONE + assert(a_idx < MAX_VERTEX_ATTRIBS); + assert(binding->enabled_bits & (1 << a_idx)); +#endif + + return (binding->loc_bits >> (4 * a_idx)) & 0xF; + } + +static void write_attrib_location(AttribBinding* binding, unsigned a_idx, unsigned location) + { +#if TRUST_NO_ONE + assert(a_idx < MAX_VERTEX_ATTRIBS); + assert(location < MAX_VERTEX_ATTRIBS); +#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 VertexFormat* format, AttribBinding* binding, GLuint program) + { +#if TRUST_NO_ONE + assert(glIsProgram(program)); +#endif + + clear_AttribBinding(binding); + + for (unsigned a_idx = 0; a_idx < format->attrib_ct; ++a_idx) + { + const Attrib* a = format->attribs + a_idx; + GLint loc = glGetAttribLocation(program, a->name); + +#if TRUST_NO_ONE + assert(loc != -1); + // TODO: make this a recoverable runtime error? indicates mismatch between vertex format and program +#endif + + write_attrib_location(binding, a_idx, loc); + } + } diff --git a/intern/gawain/src/batch.c b/intern/gawain/src/batch.c new file mode 100644 index 00000000000..cac34d445bb --- /dev/null +++ b/intern/gawain/src/batch.c @@ -0,0 +1,405 @@ + +// 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 "batch.h" +#include "buffer_id.h" +#include <stdlib.h> + +// necessary functions from matrix API +extern void gpuBindMatrices(GLuint program); +extern bool gpuMatricesDirty(void); // how best to use this here? + +Batch* Batch_create(PrimitiveType prim_type, VertexBuffer* verts, ElementList* elem) + { + Batch* batch = calloc(1, sizeof(Batch)); + + Batch_init(batch, prim_type, verts, elem); + + return batch; + } + +void Batch_init(Batch* batch, PrimitiveType prim_type, VertexBuffer* verts, ElementList* elem) + { +#if TRUST_NO_ONE + assert(verts != NULL); + // assert(prim_type == PRIM_POINTS || prim_type == PRIM_LINES || prim_type == PRIM_TRIANGLES); + // we will allow other primitive types in a future update +#endif + + batch->verts[0] = verts; + for (int v = 1; v < BATCH_MAX_VBO_CT; ++v) + batch->verts[v] = NULL; + batch->elem = elem; + batch->prim_type = prim_type; + batch->phase = READY_TO_DRAW; + } + +void Batch_discard(Batch* batch) + { + if (batch->vao_id) + vao_id_free(batch->vao_id); + + free(batch); + } + +void Batch_discard_all(Batch* batch) + { + for (int v = 0; v < BATCH_MAX_VBO_CT; ++v) + { + if (batch->verts[v] == NULL) + break; + VertexBuffer_discard(batch->verts[v]); + } + + if (batch->elem) + ElementList_discard(batch->elem); + + Batch_discard(batch); + } + +int Batch_add_VertexBuffer(Batch* batch, VertexBuffer* verts) + { + for (unsigned v = 0; v < BATCH_MAX_VBO_CT; ++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 + return v; + } + } + + // we only make it this far if there is no room for another VertexBuffer +#if TRUST_NO_ONE + assert(false); +#endif + return -1; + } + +void Batch_set_program(Batch* batch, GLuint program) + { +#if TRUST_NO_ONE + assert(glIsProgram(program)); +#endif + + batch->program = program; + batch->program_dirty = true; + + Batch_use_program(batch); // hack! to make Batch_Uniform* simpler + } + +static void Batch_update_program_bindings(Batch* batch) + { + // disable all as a precaution + // why are we not using prev_attrib_enabled_bits?? see immediate.c + for (unsigned a_idx = 0; a_idx < MAX_VERTEX_ATTRIBS; ++a_idx) + glDisableVertexAttribArray(a_idx); + + for (int v = 0; v < BATCH_MAX_VBO_CT; ++v) + { + VertexBuffer* verts = batch->verts[v]; + if (verts == NULL) + break; + + const VertexFormat* format = &verts->format; + + const unsigned attrib_ct = format->attrib_ct; + const unsigned stride = format->stride; + + VertexBuffer_use(verts); + + for (unsigned a_idx = 0; a_idx < attrib_ct; ++a_idx) + { + const Attrib* a = format->attribs + a_idx; + + const GLvoid* pointer = (const GLubyte*)0 + a->offset; + + const GLint loc = glGetAttribLocation(batch->program, a->name); + + if (loc == -1) continue; + + glEnableVertexAttribArray(loc); + + switch (a->fetch_mode) + { + case KEEP_FLOAT: + case CONVERT_INT_TO_FLOAT: + glVertexAttribPointer(loc, a->comp_ct, a->comp_type, GL_FALSE, stride, pointer); + break; + case NORMALIZE_INT_TO_FLOAT: + glVertexAttribPointer(loc, a->comp_ct, a->comp_type, GL_TRUE, stride, pointer); + break; + case KEEP_INT: + glVertexAttribIPointer(loc, a->comp_ct, a->comp_type, stride, pointer); + } + } + } + + batch->program_dirty = false; + } + +void Batch_use_program(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 Batch_done_using_program(Batch* batch) + { + if (batch->program_in_use) + { + glUseProgram(0); + batch->program_in_use = false; + } + } + +void Batch_Uniform1i(Batch* batch, const char* name, int value) + { + int loc = glGetUniformLocation(batch->program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform1i(loc, value); + } + +void Batch_Uniform1b(Batch* batch, const char* name, bool value) + { + int loc = glGetUniformLocation(batch->program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform1i(loc, value ? GL_TRUE : GL_FALSE); + } + +void Batch_Uniform2f(Batch* batch, const char* name, float x, float y) + { + int loc = glGetUniformLocation(batch->program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform2f(loc, x, y); + } + +void Batch_Uniform3f(Batch* batch, const char* name, float x, float y, float z) + { + int loc = glGetUniformLocation(batch->program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform3f(loc, x, y, z); + } + +void Batch_Uniform4f(Batch* batch, const char* name, float x, float y, float z, float w) + { + int loc = glGetUniformLocation(batch->program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform4f(loc, x, y, z, w); + } + +void Batch_Uniform1f(Batch* batch, const char* name, float x) + { + int loc = glGetUniformLocation(batch->program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform1f(loc, x); + } + +void Batch_Uniform3fv(Batch* batch, const char* name, const float data[3]) + { + int loc = glGetUniformLocation(batch->program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform3fv(loc, 1, data); + } + +void Batch_Uniform4fv(Batch* batch, const char* name, const float data[4]) + { + int loc = glGetUniformLocation(batch->program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform4fv(loc, 1, data); + } + +static void Batch_prime(Batch* batch) + { + batch->vao_id = vao_id_alloc(); + glBindVertexArray(batch->vao_id); + + for (int v = 0; v < BATCH_MAX_VBO_CT; ++v) + { + if (batch->verts[v] == NULL) + break; + VertexBuffer_use(batch->verts[v]); + } + + if (batch->elem) + ElementList_use(batch->elem); + + // vertex attribs and element list remain bound to this VAO + } + +void Batch_draw(Batch* batch) + { +#if TRUST_NO_ONE + assert(batch->phase == READY_TO_DRAW); + assert(glIsProgram(batch->program)); +#endif + + if (batch->vao_id) + glBindVertexArray(batch->vao_id); + else + Batch_prime(batch); + + if (batch->program_dirty) + Batch_update_program_bindings(batch); + + Batch_use_program(batch); + + gpuBindMatrices(batch->program); + + if (batch->elem) + { + const ElementList* el = batch->elem; + +#if TRACK_INDEX_RANGE + if (el->base_index) + glDrawRangeElementsBaseVertex(batch->prim_type, el->min_index, el->max_index, el->index_ct, el->index_type, 0, el->base_index); + else + glDrawRangeElements(batch->prim_type, el->min_index, el->max_index, el->index_ct, el->index_type, 0); +#else + glDrawElements(batch->prim_type, el->index_ct, GL_UNSIGNED_INT, 0); +#endif + } + else + glDrawArrays(batch->prim_type, 0, batch->verts[0]->vertex_ct); + + Batch_done_using_program(batch); + glBindVertexArray(0); + } + + + +// clement : temp stuff +void Batch_draw_stupid(Batch* batch) +{ + if (batch->vao_id) + glBindVertexArray(batch->vao_id); + else + Batch_prime(batch); + + if (batch->program_dirty) + Batch_update_program_bindings(batch); + + // Batch_use_program(batch); + + //gpuBindMatrices(batch->program); + + if (batch->elem) + { + const ElementList* el = batch->elem; + +#if TRACK_INDEX_RANGE + if (el->base_index) + glDrawRangeElementsBaseVertex(batch->prim_type, el->min_index, el->max_index, el->index_ct, el->index_type, 0, el->base_index); + else + glDrawRangeElements(batch->prim_type, el->min_index, el->max_index, el->index_ct, el->index_type, 0); +#else + glDrawElements(batch->prim_type, el->index_ct, GL_UNSIGNED_INT, 0); +#endif + } + else + glDrawArrays(batch->prim_type, 0, batch->verts[0]->vertex_ct); + + // Batch_done_using_program(batch); + glBindVertexArray(0); +} + +// clement : temp stuff +void Batch_draw_stupid_instanced(Batch* batch, unsigned int instance_vbo, int instance_count, + int attrib_nbr, int attrib_stride, int attrib_size[16], int attrib_loc[16]) +{ + if (batch->vao_id) + glBindVertexArray(batch->vao_id); + else + Batch_prime(batch); + + if (batch->program_dirty) + Batch_update_program_bindings(batch); + + glBindBuffer(GL_ARRAY_BUFFER, instance_vbo); + int ptr_ofs = 0; + for (int i = 0; i < attrib_nbr; ++i) { + int size = attrib_size[i]; + int loc = attrib_loc[i]; + int atr_ofs = 0; + + while (size > 0) { + glEnableVertexAttribArray(loc + atr_ofs); + glVertexAttribPointer(loc + atr_ofs, (size > 4) ? 4 : size, GL_FLOAT, GL_FALSE, + sizeof(float) * attrib_stride, (GLvoid*)(sizeof(float) * ptr_ofs)); + glVertexAttribDivisor(loc + atr_ofs, 1); + atr_ofs++; + ptr_ofs += (size > 4) ? 4 : size; + size -= 4; + } + } + glBindBuffer(GL_ARRAY_BUFFER, 0); + + // Batch_use_program(batch); + + //gpuBindMatrices(batch->program); + + if (batch->elem) + { + const ElementList* el = batch->elem; + + glDrawElementsInstanced(batch->prim_type, el->index_ct, GL_UNSIGNED_INT, 0, instance_count); + } + else + glDrawArraysInstanced(batch->prim_type, 0, batch->verts[0]->vertex_ct, instance_count); + + // Batch_done_using_program(batch); + glBindVertexArray(0); +} + diff --git a/intern/gawain/src/buffer_id.cpp b/intern/gawain/src/buffer_id.cpp new file mode 100644 index 00000000000..450656c4ebf --- /dev/null +++ b/intern/gawain/src/buffer_id.cpp @@ -0,0 +1,115 @@ + +// 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 "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::vector<GLuint> orphaned_vao_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 buffer_id_alloc() + { +#if TRUST_NO_ONE + assert(thread_is_main()); +#endif + + // 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 buffer_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(); + } + } + +GLuint vao_id_alloc() + { +#if TRUST_NO_ONE + assert(thread_is_main()); +#endif + + // delete orphaned IDs + orphan_mutex.lock(); + if (!orphaned_vao_ids.empty()) + { + const auto orphaned_vao_ct = (unsigned)orphaned_vao_ids.size(); +#if ORPHAN_DEBUG + printf("deleting %u orphaned VAO%s\n", orphaned_vao_ct, orphaned_vao_ct == 1 ? "" : "s"); +#endif + glDeleteVertexArrays(orphaned_vao_ct, orphaned_vao_ids.data()); + orphaned_vao_ids.clear(); + } + orphan_mutex.unlock(); + + GLuint new_vao_id = 0; + glGenVertexArrays(1, &new_vao_id); + return new_vao_id; + } + +void vao_id_free(GLuint vao_id) + { + if (thread_is_main()) + glDeleteVertexArrays(1, &vao_id); + else + { + // add this ID to the orphaned list + orphan_mutex.lock(); +#if ORPHAN_DEBUG + printf("orphaning VAO %u\n", vao_id); +#endif + orphaned_vao_ids.emplace_back(vao_id); + orphan_mutex.unlock(); + } + } diff --git a/intern/gawain/src/element.c b/intern/gawain/src/element.c new file mode 100644 index 00000000000..3c3ca1c7626 --- /dev/null +++ b/intern/gawain/src/element.c @@ -0,0 +1,283 @@ + +// 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 "element.h" +#include "buffer_id.h" +#include <stdlib.h> + +#define KEEP_SINGLE_COPY 1 + +unsigned ElementList_size(const ElementList* elem) + { +#if TRACK_INDEX_RANGE + switch (elem->index_type) + { + case INDEX_U8: return elem->index_ct * sizeof(GLubyte); + case INDEX_U16: return elem->index_ct * sizeof(GLushort); + case INDEX_U32: return elem->index_ct * sizeof(GLuint); + default: + #if TRUST_NO_ONE + assert(false); + #endif + return 0; + } + +#else + return elem->index_ct * sizeof(GLuint); +#endif + } + +static void ElementList_prime(ElementList* elem) + { + elem->vbo_id = buffer_id_alloc(); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elem->vbo_id); + // fill with delicious data & send to GPU the first time only + glBufferData(GL_ELEMENT_ARRAY_BUFFER, ElementList_size(elem), elem->data, GL_STATIC_DRAW); + +#if KEEP_SINGLE_COPY + // now that GL has a copy, discard original + free(elem->data); + elem->data = NULL; +#endif + } + +void ElementList_use(ElementList* elem) + { + if (elem->vbo_id) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elem->vbo_id); + else + ElementList_prime(elem); + } + +void ElementListBuilder_init(ElementListBuilder* builder, PrimitiveType prim_type, unsigned prim_ct, unsigned vertex_ct) + { + unsigned verts_per_prim = 0; + switch (prim_type) + { + case PRIM_POINTS: + verts_per_prim = 1; + break; + case PRIM_LINES: + verts_per_prim = 2; + break; + case PRIM_TRIANGLES: + verts_per_prim = 3; + break; + default: +#if TRUST_NO_ONE + assert(false); +#endif + return; + } + + builder->max_allowed_index = vertex_ct - 1; + builder->max_index_ct = prim_ct * verts_per_prim; + builder->index_ct = 0; // start empty + builder->prim_type = prim_type; + builder->data = calloc(builder->max_index_ct, sizeof(unsigned)); + } + +void add_generic_vertex(ElementListBuilder* 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 add_point_vertex(ElementListBuilder* builder, unsigned v) + { +#if TRUST_NO_ONE + assert(builder->prim_type == PRIM_POINTS); +#endif + + add_generic_vertex(builder, v); + } + +void add_line_vertices(ElementListBuilder* builder, unsigned v1, unsigned v2) + { +#if TRUST_NO_ONE + assert(builder->prim_type == PRIM_LINES); + assert(v1 != v2); +#endif + + add_generic_vertex(builder, v1); + add_generic_vertex(builder, v2); + } + +void add_triangle_vertices(ElementListBuilder* builder, unsigned v1, unsigned v2, unsigned v3) + { +#if TRUST_NO_ONE + assert(builder->prim_type == PRIM_TRIANGLES); + assert(v1 != v2 && v2 != v3 && v3 != v1); +#endif + + add_generic_vertex(builder, v1); + add_generic_vertex(builder, v2); + add_generic_vertex(builder, v3); + } + +#if 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) + { + 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 < 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(const unsigned values[], ElementList* elem) + { + const unsigned index_ct = elem->index_ct; + GLubyte* data = malloc(index_ct * sizeof(GLubyte)); + + 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] = (GLubyte)(values[i] - base); + } + else + { + elem->base_index = 0; + + for (unsigned i = 0; i < index_ct; ++i) + data[i] = (GLubyte)(values[i]); + } + + elem->data = data; + } + +static void squeeze_indices_short(const unsigned values[], ElementList* elem) + { + const unsigned index_ct = elem->index_ct; + GLushort* data = malloc(index_ct * sizeof(GLushort)); + + 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] = (GLushort)(values[i] - base); + } + else + { + elem->base_index = 0; + + for (unsigned i = 0; i < index_ct; ++i) + data[i] = (GLushort)(values[i]); + } + + elem->data = data; + } + +#endif // TRACK_INDEX_RANGE + +ElementList* ElementList_build(ElementListBuilder* builder) + { + ElementList* elem = calloc(1, sizeof(ElementList)); + ElementList_build_in_place(builder, elem); + return elem; + } + +void ElementList_build_in_place(ElementListBuilder* builder, ElementList* elem) + { +#if TRUST_NO_ONE + assert(builder->data != NULL); +#endif + + elem->index_ct = builder->index_ct; + +#if TRACK_INDEX_RANGE + const unsigned range = index_range(builder->data, builder->index_ct, &elem->min_index, &elem->max_index); + + if (range <= 0xFF) + { + elem->index_type = INDEX_U8; + squeeze_indices_byte(builder->data, elem); + } + else if (range <= 0xFFFF) + { + elem->index_type = INDEX_U16; + squeeze_indices_short(builder->data, elem); + } + else + { + elem->index_type = INDEX_U32; + elem->base_index = 0; + + if (builder->index_ct < builder->max_index_ct) + { + builder->data = realloc(builder->data, builder->index_ct * sizeof(unsigned)); + // TODO: realloc only if index_ct is much smaller than max_index_ct + } + + elem->data = builder->data; + } +#else + if (builder->index_ct < builder->max_index_ct) + { + builder->data = realloc(builder->data, builder->index_ct * sizeof(unsigned)); + // TODO: realloc only if index_ct is much smaller than max_index_ct + } + + elem->data = builder->data; +#endif + + // elem->data will never be *larger* than builder->data... how about converting + // in place to avoid extra allocation? + + elem->vbo_id = 0; + // TODO: create GL buffer object directly, based on an input flag + + // discard builder (one-time use) + if (builder->data != elem->data) + free(builder->data); + builder->data = NULL; + // other fields are safe to leave + } + +void ElementList_discard(ElementList* elem) + { + if (elem->vbo_id) + buffer_id_free(elem->vbo_id); +#if KEEP_SINGLE_COPY + else +#endif + if (elem->data) + free(elem->data); + + free(elem); + } diff --git a/intern/gawain/src/imm_util.c b/intern/gawain/src/imm_util.c new file mode 100644 index 00000000000..74caeb6fd3a --- /dev/null +++ b/intern/gawain/src/imm_util.c @@ -0,0 +1,46 @@ + +// 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 "imm_util.h" +#include "immediate.h" + + +void immRectf(unsigned pos, float x1, float y1, float x2, float y2) +{ + immBegin(PRIM_TRIANGLE_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(PRIM_TRIANGLE_FAN, 4); + immVertex2i(pos, x1, y1); + immVertex2i(pos, x2, y1); + immVertex2i(pos, x2, y2); + immVertex2i(pos, x1, y2); + immEnd(); +} + +#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]) +{ + VertexFormat *format = immVertexFormat(); + unsigned pos = add_attrib(format, "pos", COMP_I32, 2, CONVERT_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/immediate.c b/intern/gawain/src/immediate.c new file mode 100644 index 00000000000..f52e9271fe5 --- /dev/null +++ b/intern/gawain/src/immediate.c @@ -0,0 +1,880 @@ + +// 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 "immediate.h" +#include "attrib_binding.h" +#include "buffer_id.h" +#include <string.h> + +// necessary functions from matrix API +extern void gpuBindMatrices(GLuint program); +extern bool gpuMatricesDirty(void); + +typedef struct { + // TODO: organize this struct by frequency of change (run-time) + +#if IMM_BATCH_COMBO + Batch* batch; +#endif + + // current draw call + GLubyte* buffer_data; + unsigned buffer_offset; + unsigned buffer_bytes_mapped; + unsigned vertex_ct; + bool strict_vertex_ct; + PrimitiveType prim_type; + + VertexFormat 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; + AttribBinding 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 PER_THREAD bool initialized = false; +static PER_THREAD Immediate imm; + +void immInit(void) + { +#if TRUST_NO_ONE + assert(!initialized); +#endif + + memset(&imm, 0, sizeof(Immediate)); + + imm.vbo_id = buffer_id_alloc(); + glBindBuffer(GL_ARRAY_BUFFER, imm.vbo_id); + glBufferData(GL_ARRAY_BUFFER, IMM_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW); + +#if APPLE_LEGACY + glBufferParameteriAPPLE(GL_ARRAY_BUFFER, GL_BUFFER_SERIALIZED_MODIFY_APPLE, GL_FALSE); + glBufferParameteriAPPLE(GL_ARRAY_BUFFER, GL_BUFFER_FLUSHING_UNMAP_APPLE, GL_FALSE); +#endif + + imm.prim_type = 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 == PRIM_NONE); // make sure we're not between a Begin/End pair + assert(imm.vao_id == 0); +#endif + + imm.vao_id = vao_id_alloc(); + } + +void immDeactivate(void) + { +#if TRUST_NO_ONE + assert(initialized); + assert(imm.prim_type == PRIM_NONE); // make sure we're not between a Begin/End pair + assert(imm.vao_id != 0); +#endif + + vao_id_free(imm.vao_id); + imm.vao_id = 0; + imm.prev_enabled_attrib_bits = 0; + } + +void immDestroy(void) + { + immDeactivate(); + buffer_id_free(imm.vbo_id); + initialized = false; + } + +VertexFormat* immVertexFormat(void) + { + VertexFormat_clear(&imm.vertex_format); + return &imm.vertex_format; + } + +void immBindProgram(GLuint program) + { +#if TRUST_NO_ONE + assert(imm.bound_program == 0); + assert(glIsProgram(program)); +#endif + + if (!imm.vertex_format.packed) + VertexFormat_pack(&imm.vertex_format); + + glUseProgram(program); + get_attrib_locations(&imm.vertex_format, &imm.attrib_binding, program); + imm.bound_program = program; + + gpuBindMatrices(program); + } + +void immUnbindProgram(void) + { +#if TRUST_NO_ONE + assert(imm.bound_program != 0); +#endif + + glUseProgram(0); + imm.bound_program = 0; + } + +static bool vertex_count_makes_sense_for_primitive(unsigned vertex_ct, PrimitiveType prim_type) + { + // does vertex_ct make sense for this primitive type? + if (vertex_ct == 0) + return false; + + switch (prim_type) + { + case PRIM_POINTS: + return true; + case PRIM_LINES: + return vertex_ct % 2 == 0; + case PRIM_LINE_STRIP: + case PRIM_LINE_LOOP: + return vertex_ct >= 2; + case PRIM_TRIANGLES: + return vertex_ct % 3 == 0; + case PRIM_TRIANGLE_STRIP: + case PRIM_TRIANGLE_FAN: + return vertex_ct >= 3; + #ifdef WITH_GL_PROFILE_COMPAT + case PRIM_QUADS: + return vertex_ct % 4 == 0; + #endif + default: + return false; + } + } + +void immBegin(PrimitiveType prim_type, unsigned vertex_ct) + { +#if TRUST_NO_ONE + assert(initialized); + assert(imm.prim_type == 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 APPLE_LEGACY + glBufferData(GL_ARRAY_BUFFER, IMM_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW); +#else + if (GLEW_VERSION_4_3 || GLEW_ARB_invalidate_subdata) + glInvalidateBufferData(imm.vbo_id); + else + glMapBufferRange(GL_ARRAY_BUFFER, 0, IMM_BUFFER_SIZE, GL_MAP_INVALIDATE_BUFFER_BIT); +#endif + + imm.buffer_offset = 0; + } + +// printf("mapping %u to %u\n", imm.buffer_offset, imm.buffer_offset + bytes_needed - 1); + +#if APPLE_LEGACY + imm.buffer_data = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY) + imm.buffer_offset; +#else + 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)); +#endif + +#if TRUST_NO_ONE + assert(imm.buffer_data != NULL); +#endif + + imm.buffer_bytes_mapped = bytes_needed; + imm.vertex_data = imm.buffer_data; + } + +void immBeginAtMost(PrimitiveType 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 + +Batch* immBeginBatch(PrimitiveType prim_type, unsigned vertex_ct) + { +#if TRUST_NO_ONE + assert(initialized); + assert(imm.prim_type == 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; + + VertexBuffer* verts = VertexBuffer_create_with_format(&imm.vertex_format); + VertexBuffer_allocate_data(verts, vertex_ct); + + imm.buffer_bytes_mapped = VertexBuffer_size(verts); + imm.vertex_data = verts->data; + + imm.batch = Batch_create(prim_type, verts, NULL); + imm.batch->phase = BUILDING; + + Batch_set_program(imm.batch, imm.bound_program); + + return imm.batch; + } + +Batch* immBeginBatchAtMost(PrimitiveType 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 < MAX_VERTEX_ATTRIBS; ++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 Attrib* 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 KEEP_FLOAT: + case CONVERT_INT_TO_FLOAT: + glVertexAttribPointer(loc, a->comp_ct, a->comp_type, GL_FALSE, stride, pointer); + break; + case NORMALIZE_INT_TO_FLOAT: + glVertexAttribPointer(loc, a->comp_ct, a->comp_type, GL_TRUE, stride, pointer); + break; + case KEEP_INT: + glVertexAttribIPointer(loc, a->comp_ct, a->comp_type, stride, pointer); + } + } + + if (gpuMatricesDirty()) + gpuBindMatrices(imm.bound_program); + } + +void immEnd(void) + { +#if TRUST_NO_ONE + assert(imm.prim_type != 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); + } +#if !APPLE_LEGACY + // 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); +#endif + } + +#if IMM_BATCH_COMBO + if (imm.batch) + { + if (buffer_bytes_used != imm.buffer_bytes_mapped) + { + VertexBuffer_resize_data(imm.batch->verts[0], imm.vertex_ct); + // TODO: resize only if vertex count is much smaller + } + + imm.batch->phase = READY_TO_DRAW; + imm.batch = NULL; // don't free, batch belongs to caller + } + else +#endif + { +#if APPLE_LEGACY + // tell OpenGL what range was modified so it doesn't copy the whole buffer + // printf("flushing %u to %u\n", imm.buffer_offset, imm.buffer_offset + buffer_bytes_used - 1); + glFlushMappedBufferRangeAPPLE(GL_ARRAY_BUFFER, imm.buffer_offset, buffer_bytes_used); +#endif + glUnmapBuffer(GL_ARRAY_BUFFER); + + if (imm.vertex_ct > 0) + { + immDrawSetup(); + glDrawArrays(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 = 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) + { + Attrib* attrib = imm.vertex_format.attribs + attrib_id; + +#if TRUST_NO_ONE + assert(attrib_id < imm.vertex_format.attrib_ct); + assert(attrib->comp_type == COMP_F32); + assert(attrib->comp_ct == 1); + assert(imm.vertex_idx < imm.vertex_ct); + assert(imm.prim_type != 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) + { + Attrib* attrib = imm.vertex_format.attribs + attrib_id; + +#if TRUST_NO_ONE + assert(attrib_id < imm.vertex_format.attrib_ct); + assert(attrib->comp_type == COMP_F32); + assert(attrib->comp_ct == 2); + assert(imm.vertex_idx < imm.vertex_ct); + assert(imm.prim_type != 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) + { + Attrib* attrib = imm.vertex_format.attribs + attrib_id; + +#if TRUST_NO_ONE + assert(attrib_id < imm.vertex_format.attrib_ct); + assert(attrib->comp_type == COMP_F32); + assert(attrib->comp_ct == 3); + assert(imm.vertex_idx < imm.vertex_ct); + assert(imm.prim_type != 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) + { + Attrib* attrib = imm.vertex_format.attribs + attrib_id; + +#if TRUST_NO_ONE + assert(attrib_id < imm.vertex_format.attrib_ct); + assert(attrib->comp_type == COMP_F32); + assert(attrib->comp_ct == 4); + assert(imm.vertex_idx < imm.vertex_ct); + assert(imm.prim_type != 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 immAttrib2i(unsigned attrib_id, int x, int y) + { + Attrib* attrib = imm.vertex_format.attribs + attrib_id; + +#if TRUST_NO_ONE + assert(attrib_id < imm.vertex_format.attrib_ct); + assert(attrib->comp_type == COMP_I32); + assert(attrib->comp_ct == 2); + assert(imm.vertex_idx < imm.vertex_ct); + assert(imm.prim_type != 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) + { + Attrib* attrib = imm.vertex_format.attribs + attrib_id; + +#if TRUST_NO_ONE + assert(attrib_id < imm.vertex_format.attrib_ct); + assert(attrib->comp_type == COMP_I16); + assert(attrib->comp_ct == 2); + assert(imm.vertex_idx < imm.vertex_ct); + assert(imm.prim_type != 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 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) + { + Attrib* attrib = imm.vertex_format.attribs + attrib_id; + +#if TRUST_NO_ONE + assert(attrib_id < imm.vertex_format.attrib_ct); + assert(attrib->comp_type == COMP_U8); + assert(attrib->comp_ct == 3); + assert(imm.vertex_idx < imm.vertex_ct); + assert(imm.prim_type != 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) + { + Attrib* attrib = imm.vertex_format.attribs + attrib_id; + +#if TRUST_NO_ONE + assert(attrib_id < imm.vertex_format.attrib_ct); + assert(attrib->comp_type == COMP_U8); + assert(attrib->comp_ct == 4); + assert(imm.vertex_idx < imm.vertex_ct); + assert(imm.prim_type != 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 != 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 != 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 Attrib* 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 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 --- + +void immUniform1f(const char* name, float x) + { + int loc = glGetUniformLocation(imm.bound_program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform1f(loc, x); + } + +void immUniform2f(const char* name, float x, float y) +{ + int loc = glGetUniformLocation(imm.bound_program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform2f(loc, x, y); +} + +void immUniform2fv(const char* name, const float data[2]) +{ + int loc = glGetUniformLocation(imm.bound_program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform2fv(loc, 1, data); +} + +void immUniform3f(const char* name, float x, float y, float z) + { + int loc = glGetUniformLocation(imm.bound_program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform3f(loc, x, y, z); + } + +void immUniform3fv(const char* name, const float data[3]) + { + int loc = glGetUniformLocation(imm.bound_program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform3fv(loc, 1, data); + } + +void immUniformArray3fv(const char* name, const float *data, int count) + { + int loc = glGetUniformLocation(imm.bound_program, name); + +#if TRUST_NO_ONE + assert(loc != -1); + assert(count > 0); +#endif + + glUniform3fv(loc, count, data); + } + +void immUniform4f(const char* name, float x, float y, float z, float w) + { + int loc = glGetUniformLocation(imm.bound_program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform4f(loc, x, y, z, w); + } + +void immUniform4fv(const char* name, const float data[4]) + { + int loc = glGetUniformLocation(imm.bound_program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform4fv(loc, 1, data); + } + +void immUniformMatrix4fv(const char* name, const float data[4][4]) + { + int loc = glGetUniformLocation(imm.bound_program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniformMatrix4fv(loc, 1, GL_FALSE, (float *)data); + } + +void immUniform1i(const char* name, int x) + { + int loc = glGetUniformLocation(imm.bound_program, name); + +#if TRUST_NO_ONE + assert(loc != -1); +#endif + + glUniform1i(loc, x); + } + + +// --- convenience functions for setting "uniform vec4 color" --- + +void immUniformColor4f(float r, float g, float b, float a) + { + immUniform4f("color", r, g, b, a); + } + +void immUniformColor4fv(const float rgba[4]) + { + immUniform4fv("color", rgba); + } + +void immUniformColor3f(float r, float g, float b) + { + immUniform4f("color", r, g, b, 1.0f); + } + +void immUniformColor3fv(const float rgb[3]) + { + immUniform4f("color", rgb[0], rgb[1], rgb[2], 1.0f); + } + +void immUniformColor3fvAlpha(const float rgb[3], float a) + { + immUniform4f("color", 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; + immUniform4f("color", 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; + immUniform4f("color", scale * r, scale * g, scale * b, scale * a); + } + +void immUniformColor3ubv(const unsigned char rgb[3]) + { + immUniformColor3ub(rgb[0], rgb[1], rgb[2]); + } + +void immUniformColor4ubv(const unsigned char rgba[4]) + { + immUniformColor4ub(rgba[0], rgba[1], rgba[2], rgba[3]); + } diff --git a/intern/gawain/src/primitive.c b/intern/gawain/src/primitive.c new file mode 100644 index 00000000000..95472c289e8 --- /dev/null +++ b/intern/gawain/src/primitive.c @@ -0,0 +1,41 @@ + +// 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 "primitive.h" + +PrimitiveClass prim_class_of_type(PrimitiveType prim_type) + { + static const PrimitiveClass classes[] = + { + [PRIM_NONE] = PRIM_CLASS_NONE, + [PRIM_POINTS] = PRIM_CLASS_POINT, + [PRIM_LINES] = PRIM_CLASS_LINE, + [PRIM_LINE_STRIP] = PRIM_CLASS_LINE, + [PRIM_LINE_LOOP] = PRIM_CLASS_LINE, + [PRIM_TRIANGLES] = PRIM_CLASS_SURFACE, + [PRIM_TRIANGLE_STRIP] = PRIM_CLASS_SURFACE, + [PRIM_TRIANGLE_FAN] = PRIM_CLASS_SURFACE, + +#ifdef WITH_GL_PROFILE_COMPAT + [PRIM_QUADS] = PRIM_CLASS_SURFACE, +#endif + }; + + return classes[prim_type]; + } + +bool prim_type_belongs_to_class(PrimitiveType prim_type, PrimitiveClass prim_class) + { + if (prim_class == PRIM_CLASS_NONE && prim_type == PRIM_NONE) + return true; + + return prim_class & prim_class_of_type(prim_type); + } diff --git a/intern/gawain/src/shader_interface.c b/intern/gawain/src/shader_interface.c new file mode 100644 index 00000000000..93a1283d895 --- /dev/null +++ b/intern/gawain/src/shader_interface.c @@ -0,0 +1,140 @@ + +// 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 "shader_interface.h" +#include <stdlib.h> +#include <stddef.h> + +#define DEBUG_SHADER_INTERFACE 0 + +#if DEBUG_SHADER_INTERFACE + #include <stdio.h> +#endif + +#if 0 + +static const char* BuiltinUniform_name(BuiltinUniform u) + { + static const char* names[] = + { + [UNIFORM_NONE] = NULL, + + [UNIFORM_MODELVIEW_3D] = "ModelViewMatrix", + [UNIFORM_PROJECTION_3D] = "ProjectionMatrix", + [UNIFORM_MVP_3D] = "ModelViewProjectionMatrix", + [UNIFORM_NORMAL_3D] = "NormalMatrix", + [UNIFORM_INV_NORMAL_3D] = "InverseNormalMatrix", + + [UNIFORM_MODELVIEW_2D] = "ModelViewMatrix", + [UNIFORM_PROJECTION_2D] = "ProjectionMatrix", + [UNIFORM_MVP_2D] = "ModelViewProjectionMatrix", + + [UNIFORM_COLOR] = "color", + + [UNIFORM_CUSTOM] = NULL + }; + + return names[u]; + } + +#endif + +static bool setup_builtin_uniform(ShaderInput* input, const char* name) + { + // TODO: reject DOUBLE, IMAGE, ATOMIC_COUNTER gl_types + + // TODO: detect built-in uniforms (gl_type and name must match) + // if a match is found, use BuiltinUniform_name so name buffer space can be reclaimed + input->name = name; + input->builtin_type = UNIFORM_CUSTOM; + return false; + } + +ShaderInterface* ShaderInterface_create(GLint program) + { +#if DEBUG_SHADER_INTERFACE + printf("%s {\n", __func__); // enter function +#endif + + GLint uniform_ct, attrib_ct; + glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniform_ct); + glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &attrib_ct); + const GLint input_ct = uniform_ct + attrib_ct; + + GLint max_uniform_name_len, max_attrib_name_len; + glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_uniform_name_len); + glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &max_attrib_name_len); + const uint32_t name_buffer_len = uniform_ct * max_uniform_name_len + attrib_ct * max_attrib_name_len; + + // allocate enough space for input counts, details for each input, and a buffer for name strings + ShaderInterface* shaderface = calloc(1, offsetof(ShaderInterface, inputs) + input_ct * sizeof(ShaderInput) + name_buffer_len); + + char* name_buffer = (char*)shaderface + offsetof(ShaderInterface, inputs) + input_ct * sizeof(ShaderInput); + uint32_t name_buffer_offset = 0; + + for (uint32_t i = 0; i < uniform_ct; ++i) + { + ShaderInput* input = shaderface->inputs + i; + GLsizei remaining_buffer = name_buffer_len - name_buffer_offset; + char* name = name_buffer + name_buffer_offset; + GLsizei name_len = 0; + + glGetActiveUniform(program, i, remaining_buffer, &name_len, &input->size, &input->gl_type, name); + + if (setup_builtin_uniform(input, name)) + ; // reclaim space from name buffer (don't advance offset) + else + name_buffer_offset += name_len + 1; // include NULL terminator + + input->location = glGetUniformLocation(program, name); + +#if DEBUG_SHADER_INTERFACE + printf("uniform[%u] '%s' at location %d\n", i, name, input->location); +#endif + } + + for (uint32_t i = 0; i < attrib_ct; ++i) + { + ShaderInput* input = shaderface->inputs + uniform_ct + i; + GLsizei remaining_buffer = name_buffer_len - name_buffer_offset; + char* name = name_buffer + name_buffer_offset; + GLsizei name_len = 0; + + glGetActiveAttrib(program, i, remaining_buffer, &name_len, &input->size, &input->gl_type, name); + + // TODO: reject DOUBLE gl_types + + input->name = name; + name_buffer_offset += name_len + 1; // include NULL terminator + + input->location = glGetAttribLocation(program, name); + +#if DEBUG_SHADER_INTERFACE + printf("attrib[%u] '%s' at location %d\n", i, name, input->location); +#endif + } + + // TODO: realloc shaderface to shrink name buffer + // each input->name will need adjustment (except static built-in names) + +#if DEBUG_SHADER_INTERFACE + printf("using %u of %u bytes from name buffer\n", name_buffer_offset, name_buffer_len); + printf("}\n"); // exit function +#endif + + return shaderface; + } + +void ShaderInterface_discard(ShaderInterface* shaderface) + { + // allocated as one chunk, so discard is simple + free(shaderface); + } diff --git a/intern/gawain/src/vertex_buffer.c b/intern/gawain/src/vertex_buffer.c new file mode 100644 index 00000000000..827703403e3 --- /dev/null +++ b/intern/gawain/src/vertex_buffer.c @@ -0,0 +1,170 @@ + +// 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 "vertex_buffer.h" +#include "buffer_id.h" +#include <stdlib.h> +#include <string.h> + +#define KEEP_SINGLE_COPY 1 + +VertexBuffer* VertexBuffer_create(void) + { + VertexBuffer* verts = malloc(sizeof(VertexBuffer)); + VertexBuffer_init(verts); + return verts; + } + +VertexBuffer* VertexBuffer_create_with_format(const VertexFormat* format) + { + VertexBuffer* verts = VertexBuffer_create(); + VertexFormat_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 VertexBuffer_init(VertexBuffer* verts) + { + memset(verts, 0, sizeof(VertexBuffer)); + } + +void VertexBuffer_init_with_format(VertexBuffer* verts, const VertexFormat* format) + { + VertexBuffer_init(verts); + VertexFormat_copy(&verts->format, format); + if (!format->packed) + VertexFormat_pack(&verts->format); + } + +void VertexBuffer_discard(VertexBuffer* verts) + { + if (verts->vbo_id) + buffer_id_free(verts->vbo_id); +#if KEEP_SINGLE_COPY + else +#endif + if (verts->data) + free(verts->data); + + free(verts); + } + +unsigned VertexBuffer_size(const VertexBuffer* verts) + { + return vertex_buffer_size(&verts->format, verts->vertex_ct); + } + +void VertexBuffer_allocate_data(VertexBuffer* verts, unsigned v_ct) + { + VertexFormat* format = &verts->format; + if (!format->packed) + VertexFormat_pack(format); + + verts->vertex_ct = v_ct; + + // Data initially lives in main memory. Will be transferred to VRAM when we "prime" it. + verts->data = malloc(VertexBuffer_size(verts)); + } + +void VertexBuffer_resize_data(VertexBuffer* verts, unsigned v_ct) + { +#if TRUST_NO_ONE + assert(verts->vertex_ct != v_ct); // allow this? + assert(verts->data != NULL); // has already been allocated + assert(verts->vbo_id == 0); // has not been sent to VRAM +#endif + + verts->vertex_ct = v_ct; + verts->data = realloc(verts->data, VertexBuffer_size(verts)); + // TODO: skip realloc if v_ct < existing vertex count + // extra space will be reclaimed, and never sent to VRAM (see VertexBuffer_prime) + } + +void setAttrib(VertexBuffer* verts, unsigned a_idx, unsigned v_idx, const void* data) + { + const VertexFormat* format = &verts->format; + const Attrib* a = format->attribs + a_idx; + +#if TRUST_NO_ONE + assert(a_idx < format->attrib_ct); + assert(v_idx < verts->vertex_ct); + assert(verts->data != NULL); // data must be in main mem +#endif + + memcpy((GLubyte*)verts->data + a->offset + v_idx * format->stride, data, a->sz); + } + +void fillAttrib(VertexBuffer* verts, unsigned a_idx, const void* data) + { + const VertexFormat* format = &verts->format; + const Attrib* 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 + + fillAttribStride(verts, a_idx, stride, data); + } + +void fillAttribStride(VertexBuffer* verts, unsigned a_idx, unsigned stride, const void* data) + { + const VertexFormat* format = &verts->format; + const Attrib* a = format->attribs + a_idx; + +#if TRUST_NO_ONE + assert(a_idx < format->attrib_ct); + assert(verts->data != NULL); // data must be in main mem +#endif + + const unsigned vertex_ct = verts->vertex_ct; + + if (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); + } + } + +static void VertexBuffer_prime(VertexBuffer* verts) + { + const VertexFormat* format = &verts->format; + + verts->vbo_id = buffer_id_alloc(); + glBindBuffer(GL_ARRAY_BUFFER, verts->vbo_id); + // fill with delicious data & send to GPU the first time only + glBufferData(GL_ARRAY_BUFFER, vertex_buffer_size(format, verts->vertex_ct), verts->data, GL_STATIC_DRAW); + +#if KEEP_SINGLE_COPY + // now that GL has a copy, discard original + free(verts->data); + verts->data = NULL; +#endif + } + +void VertexBuffer_use(VertexBuffer* verts) + { + if (verts->vbo_id) + glBindBuffer(GL_ARRAY_BUFFER, verts->vbo_id); + else + VertexBuffer_prime(verts); + } diff --git a/intern/gawain/src/vertex_format.c b/intern/gawain/src/vertex_format.c new file mode 100644 index 00000000000..d42398f3a4f --- /dev/null +++ b/intern/gawain/src/vertex_format.c @@ -0,0 +1,248 @@ + +// 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 "vertex_format.h" +#include <stdlib.h> +#include <string.h> + +#define PACK_DEBUG 0 + +#if PACK_DEBUG + #include <stdio.h> +#endif + +void VertexFormat_clear(VertexFormat* format) + { +#if TRUST_NO_ONE + memset(format, 0, sizeof(VertexFormat)); +#else + format->attrib_ct = 0; + format->packed = false; + format->name_offset = 0; +#endif + } + +void VertexFormat_copy(VertexFormat* dest, const VertexFormat* src) + { + // copy regular struct fields + memcpy(dest, src, sizeof(VertexFormat)); + } + +static unsigned comp_sz(VertexCompType type) + { +#if TRUST_NO_ONE + assert(type >= GL_BYTE && type <= GL_FLOAT); +#endif + + const GLubyte sizes[] = {1,1,2,2,4,4,4}; + return sizes[type - GL_BYTE]; + } + +static unsigned attrib_sz(const Attrib *a) + { +#if USE_10_10_10 + if (a->comp_type == COMP_I10) + return 4; // always packed as 10_10_10_2 +#endif + + return a->comp_ct * comp_sz(a->comp_type); + } + +static unsigned attrib_align(const Attrib *a) + { +#if USE_10_10_10 + if (a->comp_type == COMP_I10) + return 4; // always packed as 10_10_10_2 +#endif + + 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 VertexFormat* 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(VertexFormat* 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 = VERTEX_ATTRIB_NAMES_BUFFER_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 <= VERTEX_ATTRIB_NAMES_BUFFER_LEN); +#endif + + return name_copy; + } + +unsigned add_attrib(VertexFormat* format, const char* name, VertexCompType comp_type, unsigned comp_ct, VertexFetchMode fetch_mode) + { +#if TRUST_NO_ONE + assert(format->attrib_ct < MAX_VERTEX_ATTRIBS); // there's room for more + assert(!format->packed); // packed means frozen/locked + assert(comp_ct >= 1 && comp_ct <= 4); + switch (comp_type) + { + case COMP_F32: + // float type can only kept as float + assert(fetch_mode == KEEP_FLOAT); + break; + #if USE_10_10_10 + case 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 == NORMALIZE_INT_TO_FLOAT); // not strictly required, may relax later + break; + #endif + default: + // integer types can be kept as int or converted/normalized to float + assert(fetch_mode != KEEP_FLOAT); + } +#endif + + const unsigned attrib_id = format->attrib_ct++; + Attrib* attrib = format->attribs + attrib_id; + + attrib->name = copy_attrib_name(format, name); + attrib->comp_type = comp_type; +#if USE_10_10_10 + attrib->comp_ct = (comp_type == COMP_I10) ? 4 : comp_ct; // system needs 10_10_10_2 to be 4 or BGRA +#else + attrib->comp_ct = comp_ct; +#endif + attrib->sz = attrib_sz(attrib); + attrib->offset = 0; // offsets & stride are calculated later (during pack) + attrib->fetch_mode = fetch_mode; + + return attrib_id; + } + +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(VertexFormat* 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. + + Attrib* 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) + { + Attrib* 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; + } + + +#if USE_10_10_10 + +// OpenGL ES packs in a different order as desktop GL but component conversion is the same. +// Of the code here, only struct 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); + } + +PackedNormal convert_i10_v3(const float data[3]) + { + PackedNormal n = { .x = quantize(data[0]), .y = quantize(data[1]), .z = quantize(data[2]) }; + return n; + } + +#endif // USE_10_10_10 |