layer0/GenericBuffer.h (564 lines of code) (raw):

#pragma once // ----------------------------------------------------------------------------- #include "GraphicsUtil.h" #include "Vector.h" #include <vector> #include <tuple> #include <map> #include <array> #include <type_traits> #include <cstdlib> #include <string.h> // ----------------------------------------------------------------------------- // DESCRIPTORS // ----------------------------------------------------------------------------- // Describes a single array held in the vbo struct BufferDesc { BufferDesc(const char * _attr_name, GLenum _type_size, size_t _type_dim, size_t _data_size, const void * _data_ptr, bool _data_norm) : attr_name(_attr_name), type_size(_type_size), type_dim(_type_dim), data_size(_data_size), data_ptr(_data_ptr), data_norm(_data_norm) {} // Constructor for just layout BufferDesc(const char * _attr_name, GLenum _type_size, size_t _type_dim, size_t _offset, bool _data_norm) : attr_name(_attr_name), type_size(_type_size), type_dim(_type_dim), data_norm(_data_norm), offset(_offset) {} // Constructor used for index buffers BufferDesc(GLenum _type_size, size_t _data_size, const void * _data_ptr, size_t _offset = 0) : type_size(_type_size), data_size(_data_size), data_ptr(_data_ptr), offset(_offset) {} // Constructor used for data replication BufferDesc(const char * _attr_name, GLuint _gl_id) : attr_name(_attr_name), gl_id(_gl_id) {} const char * attr_name { nullptr }; GLenum type_size { GL_FLOAT }; size_t type_dim { 0 }; size_t data_size { 0 }; const void * data_ptr { nullptr }; bool data_norm { false }; GLuint gl_id { 0 }; size_t offset { 0 }; }; using BufferDataDesc = std::vector< BufferDesc >; /* different types of AttribOp */ enum attrib_op_type { NO_COPY = 0, FLOAT_TO_FLOAT, FLOAT2_TO_FLOAT2, FLOAT3_TO_FLOAT3, FLOAT4_TO_FLOAT4, FLOAT3_TO_UB3, FLOAT1_TO_UB_4TH, UB3_TO_UB3, UINT_INT_TO_PICK_DATA, UB1_INTERP_TO_CAP, FLOAT1_TO_INTERP, UB4_TO_UB4, PICK_DATA_TO_PICK_DATA, CYL_CAP_TO_CAP, FLOAT1_INTERP_TO_CAP, UB1_TO_INTERP, CYL_CAPS_ARE_ROUND, CYL_CAPS_ARE_FLAT, CYL_CAPS_ARE_CUSTOM }; struct AttribDesc; typedef void (*AttribOpFuncDataFunctionPtr)(void *varData, const float * pc, void *globalData, int idx); /* AttribOpFuncData : This structure holds information a callback that sets/post-processes data for a particular attribute. Currently, a list of these functions are attached to the AttribOp so that when vertices are created (i.e., incr_vertices > 0) then for each vertex, this function is called for the particular attribute attribName. funcDataConversion - pointer to function that sets/post-processes the attribute data funcDataGlobalArg - pointer to global structure that can be used in each call to the callback attribName - attribute name this function is processing. (this calling function specifies the name, and the CGOConvertToShader() sets attrib to the associated AttribDesc. */ struct AttribOpFuncData { void (*funcDataConversion)(void *varData, const float * pc, void *globalData, int idx); // if set, should be called on every output value for this attribute void *funcDataGlobalArg; const char *attribName; AttribDesc *attrib; AttribOpFuncDataFunctionPtr _funcDataConversion; AttribOpFuncData(AttribOpFuncDataFunctionPtr _funcDataConversion, void *_funcDataGlobalArg, const char *_attribName) : funcDataConversion(_funcDataConversion), funcDataGlobalArg(_funcDataGlobalArg), attribName(_attribName), attrib(NULL){} }; using AttribOpFuncDataDesc = std::vector< AttribOpFuncData >; /* * defines an operation that copies and (optionally) creates new vertices in * a VBO operation for a particular CGO operation (op). * * op - the CGO operation * order - the order for this operation to be executed for the given CGO operation * offset - the offset into the CGO operation to copy * conv_type - type of copy (can be general or specific, see above, e.g. FLOAT3_TO_FLOAT3, UB1_TO_INTERP) * incr_vertices - the number of vertices (if any) that are generated for the VBO after this operation * is executed. * */ struct AttribOp { AttribOp(unsigned short _op, size_t _order, size_t _conv_type, size_t _offset, size_t _incr_vertices=0, int _copyFromAttr=-1) : op(_op) , order(_order) , offset(_offset) , conv_type(_conv_type) , incr_vertices(_incr_vertices) , copyFromAttr(_copyFromAttr) {} unsigned short op { 0 }; size_t order { 0 }; size_t offset { 0 }; size_t conv_type { 0 }; size_t incr_vertices { 0 }; int copyFromAttr { -1 }; struct AttribDesc *desc { 0 }; struct AttribDesc *copyAttribDesc { 0 }; std::vector<AttribOpFuncData> funcDataConversions; }; using AttribDataOp = std::vector< AttribOp >; /* * defines an attribute that is used in a shader. this description has all of the necessary information * for our "optimize" function to generate either an array for input into the VBO or a call to the * related glVertexAttrib() call when this attribute has the same value throughout the CGO. * * attr_name - the name of this attribute inside the shaders * order - order of attribute used in VBO * attrOps - all AttribOp for this particular attribute. This allows the user to define how this * attribute gets populated from the primitive CGO's one or many CGO operations. * default_value - pointer to the default value of this attribute (optional, needs to be the same * size of the attribute's type) * repeat_value/repeat_value_length - specified if the attribute has repeating values * repeat_value - a pointer to the type and data for repeat values * repeat_value_length - number of repeat values * type_size - size of type for this attribute (e.g., GL_FLOAT, GL_UNSIGNED_BYTE) * type_dim - number of primitives (i.e., type_size) for each vertex of this attribute * data_norm - whether this attribute is normalized when passed to the VBO (GL_TRUE or GL_FALSE) * */ struct AttribDesc { AttribDesc(const char * _attr_name, GLenum _type_size, size_t _type_dim, bool _data_norm, AttribDataOp _attrOps={}) : attr_name(_attr_name) , attrOps(_attrOps) , default_value(NULL) , type_size(_type_size) , type_dim(_type_dim) , data_norm(_data_norm) {} const char * attr_name { nullptr }; int order { 0 }; AttribDataOp attrOps { }; unsigned char *default_value { nullptr }; unsigned char *repeat_value { nullptr }; int repeat_value_length { 0 }; GLenum type_size { GL_FLOAT }; size_t type_dim { 0 }; bool data_norm { false }; }; using AttribDataDesc = std::vector< AttribDesc >; class gpuBuffer_t { friend class CShaderMgr; public: virtual ~gpuBuffer_t() {}; virtual size_t get_hash_id() { return _hashid; } virtual void bind() const = 0; protected: virtual void set_hash_id(size_t id) { _hashid = id; } private: size_t _hashid { 0 }; }; // ----------------------------------------------------------------------------- /* Vertexbuffer rules: * ----------------------------------------------------------------------------- * - If the buffer data is interleaved then buffer sub data functionality cannot * be used. * - The same order of buffer data must be maintained when uploading and binding * *----------------------------------------------------------------------- * USAGE_PATTERN: * SEPARATE: * vbo1 [ data1 ] * vbo2 [ data2 ] * ... * vboN [ dataN ] * SEQUENTIAL: * vbo [ data1 | data2 | ... | dataN ] * INTERLEAVED: * vbo [ data1[0], data2[0], ..., dataN[0] | ... | data1[M], data2[M], ..., dataN[M] ] */ template <GLenum _TYPE> class GenericBuffer : public gpuBuffer_t { friend class CShaderMgr; public: static const GLenum TYPE = _TYPE; enum buffer_layout { SEPARATE, // multiple vbos SEQUENTIAL, // single vbo INTERLEAVED // single vbo }; GenericBuffer( buffer_layout layout = SEPARATE, GLenum usage = GL_STATIC_DRAW ) : m_buffer_usage(usage), m_layout(layout) {} ~GenericBuffer() { if (m_generated) freeBuffers(); } bool freeBuffers() { for (auto &d : m_desc) { if (d.gl_id) { glDeleteBuffers(1, &d.gl_id); } } if (m_interleavedID) { glDeleteBuffers(1, &m_interleavedID); } m_generated = false; return true; } /*********************************************************************** * bufferData *---------------------------------------------------------------------- * Takes a vector of the struct at the top of this file which describes * the layout of the vbo object. The supplied data ptr in the struct can * be zero, in which case if the default usage is STATIC_DRAW then no * opengl buffer will be generated for that, else it is assumed that the * data will be supplied at a later point because it's dynamic draw. ***********************************************************************/ bool bufferData(BufferDataDesc && desc) { m_desc = std::move(desc); return evaluate(); } bool bufferData(BufferDataDesc && desc, const void * data, size_t len, size_t stride) { bool ok = true; m_desc = std::move(desc); m_interleaved = true; m_stride = stride; ok = genBuffer(m_interleavedID, len, data); return ok; } // ----------------------------------------------------------------------------- // bufferSubData : // This function assumes that the data layout hasn't change bool bufferSubData(const void * data, size_t index = 0) { auto &d = m_desc[index]; if (m_interleavedID) { glBindBuffer(TYPE, m_interleavedID); } else { glBindBuffer(TYPE, d.gl_id); } glBufferSubData(TYPE, 0, d.data_size, data); return glCheckOkay(); } void bufferSubData(size_t offset, size_t size, void * data, size_t index = 0) { // maybe assert that the index is within range if (m_interleavedID) { glBindBuffer(TYPE, m_interleavedID); } else { glBindBuffer(TYPE, m_desc[index].gl_id); } glBufferSubData(TYPE, offset, size, data); } // for interleaved dat only, replaces the whole interleaved vbo void bufferReplaceData(size_t offset, size_t len, const void * data) { glBindBuffer(TYPE, m_interleavedID); glBufferSubData(TYPE, offset, len, data); } protected: bool evaluate() { bool ok = true; if (TYPE == GL_ELEMENT_ARRAY_BUFFER) { ok = seqBufferData(); } else { switch (m_layout) { case SEPARATE: ok = sepBufferData(); break; case SEQUENTIAL: ok = seqBufferData(); break; case INTERLEAVED: ok = interleaveBufferData(); break; } } return ok; } // USAGE PATTERNS bool sepBufferData() { for ( auto &d : m_desc ) { // If the specified size is 0 but we have a valid pointer // then we are going to glVertexAttribXfv X in {1,2,3,4} if (d.data_ptr && (m_buffer_usage == GL_STATIC_DRAW)) { if (d.data_size) { if (!genBuffer(d.gl_id, d.data_size, d.data_ptr)) return false; } } } m_generated = true; return true; } bool seqBufferData() { // this is only going to use a single opengl vbo m_interleaved = true; size_t buffer_size { 0 }; for ( auto & d : m_desc ) { buffer_size += d.data_size; } uint8_t * buffer_data = new uint8_t[buffer_size]; uint8_t * data_ptr = buffer_data; size_t offset = 0; for ( auto & d : m_desc ) { d.offset = offset; if (d.data_ptr) memcpy(data_ptr, d.data_ptr, d.data_size); else memset(data_ptr, 0, d.data_size); data_ptr += d.data_size; offset += d.data_size; } m_generated = true; bool ok = true; ok = genBuffer(m_interleavedID, buffer_size, buffer_data); m_generated = true; delete[] buffer_data; return ok; } bool interleaveBufferData() { size_t interleaved_size = 0; const size_t buffer_count = m_desc.size(); size_t stride = 0; std::vector<uint8_t *> data_table(buffer_count); std::vector<uint8_t *> ptr_table(buffer_count); std::vector<size_t> size_table(buffer_count); size_t count = m_desc[0].data_size / (gl_sizeof(m_desc[0].type_size) * m_desc[0].type_dim); // Maybe assert that all pointers in d_desc are valid? for ( size_t i = 0; i < buffer_count; ++i ) { auto &d = m_desc[i]; size_t size = gl_sizeof(d.type_size); // offset is the current stride d.offset = stride; // These must come after so that offset starts at 0 // Size of 3 normals or whatever the current type is size_table[i] = size * d.type_dim; // Increase our current estimate of the stride by this amount stride += size_table[i]; // Does the addition of that previous stride leave us on a word boundry? int m = stride % 4; stride = (m ? (stride + (4 - m)) : stride); // data_table a pointer to the begining of each array data_table[i] = (uint8_t *)d.data_ptr; // We will move these pointers along by the values in the size table ptr_table[i] = data_table[i]; } m_stride = stride; interleaved_size = count * stride; uint8_t *interleaved_data = (uint8_t *)calloc(interleaved_size, sizeof(uint8_t)); uint8_t *i_ptr = interleaved_data; while (i_ptr != (interleaved_data + interleaved_size)) { for ( size_t i = 0; i < buffer_count; ++i ) { if (ptr_table[i]){ memcpy( i_ptr, ptr_table[i], size_table[i] ); ptr_table[i] += size_table[i]; } i_ptr += size_table[i]; } } bool ok = true; ok = genBuffer(m_interleavedID, interleaved_size, interleaved_data); m_interleaved = true; m_generated = true; free(interleaved_data); return ok; } bool genBuffer(GLuint &id, size_t size, const void * ptr) { glGenBuffers(1, &id); if (!glCheckOkay()) return false; glBindBuffer(TYPE, id); if (!glCheckOkay()) return false; glBufferData(TYPE, size, ptr, GL_STATIC_DRAW); if (!glCheckOkay()) return false; return true; } protected: bool m_status { false }; bool m_generated { false }; bool m_interleaved { false }; GLuint m_interleavedID { 0 }; const GLenum m_buffer_usage { GL_STATIC_DRAW }; const buffer_layout m_layout { SEPARATE }; size_t m_stride { 0 }; BufferDataDesc m_desc; }; /* * Vertex buffer specialization */ class VertexBuffer : public GenericBuffer<GL_ARRAY_BUFFER> { void bind_attrib(GLuint prg, const BufferDesc & d) { GLint loc = glGetAttribLocation(prg, d.attr_name); bool masked = false; for (GLint lid : m_attribmask) if (lid == loc) masked = true; if ( loc >= 0 ) m_locs.push_back(loc); if ( loc >= 0 && !masked ) { if (!m_interleaved && d.gl_id) glBindBuffer( TYPE, d.gl_id ); glEnableVertexAttribArray( loc ); glVertexAttribPointer( loc, d.type_dim, d.type_size, d.data_norm, m_stride, (const void *)d.offset ); } }; public: VertexBuffer( buffer_layout layout = SEPARATE, GLenum usage = GL_STATIC_DRAW ) : GenericBuffer<GL_ARRAY_BUFFER>(layout, usage){} void bind() const { // we shouldn't use this one if (m_interleaved) glBindBuffer(TYPE, m_interleavedID); } void bind(GLuint prg, int index = -1) { if (index >= 0) { glBindBuffer( TYPE, m_interleavedID ); bind_attrib(prg, m_desc[index]); } else { if (m_interleaved && m_interleavedID) glBindBuffer( TYPE, m_interleavedID ); for (auto & d : m_desc) { bind_attrib(prg, d); } m_attribmask.clear(); } } void unbind() { for (auto &d : m_locs) { glDisableVertexAttribArray(d); } m_locs.clear(); glBindBuffer(TYPE, 0); } void maskAttributes(std::vector<GLint> attrib_locs) { m_attribmask = std::move(attrib_locs); } void maskAttribute(GLint attrib_loc) { m_attribmask.push_back(attrib_loc); } void replicate_data(const char * attrib_name, int index) { auto & d = m_desc[index]; BufferDesc newdesc(attrib_name, d.gl_id); newdesc.offset = d.offset; newdesc.data_norm = d.data_norm; newdesc.type_size = d.type_size; newdesc.type_dim = d.type_dim; m_desc.push_back(newdesc); } private: // m_locs is only for interleaved data std::vector<GLint> m_locs; std::vector<GLint> m_attribmask; }; /* * Index buffer specialization */ class IndexBuffer : public GenericBuffer<GL_ELEMENT_ARRAY_BUFFER> { public: using GenericBuffer::GenericBuffer; void bind() const { glBindBuffer(TYPE, m_interleavedID); } void unbind() { glBindBuffer(TYPE, 0); } }; // Forward Decls class frameBuffer_t; class renderBuffer_t; /*********************************************************************** * RENDERBUFFER ***********************************************************************/ namespace rbo { enum storage { DEPTH16 = 0, DEPTH24, COUNT }; void unbind(); }; class renderBuffer_t : public gpuBuffer_t { friend class frameBuffer_t; friend class CShaderMgr; public: renderBuffer_t(int width, int height, rbo::storage storage) : _width(width), _height(height), _storage(storage) { genBuffer(); } ~renderBuffer_t() { freeBuffer(); } void bind() const; void unbind() const; private: void genBuffer(); void freeBuffer(); protected: uint32_t _id; int _dim[2]; int _width; int _height; rbo::storage _storage; }; /*********************************************************************** * TEXTURE ***********************************************************************/ namespace tex { enum class dim : int { D1 = 0, D2, D3, COUNT }; enum class format : int { R = (int)dim::COUNT, RG, RGB, RGBA, COUNT }; enum class data_type : int { UBYTE = (int)format::COUNT, FLOAT, HALF_FLOAT, COUNT }; enum class filter : int { NEAREST = (int)data_type::COUNT, LINEAR, NEAREST_MIP_NEAREST, NEAREST_MIP_LINEAR, LINEAR_MIP_NEAREST, LINEAR_MIP_LINEAR, COUNT }; enum class wrap : int { REPEAT = (int)filter::COUNT, CLAMP, MIRROR_REPEAT, CLAMP_TO_EDGE, CLAMP_TO_BORDER, MIRROR_CLAMP_TO_EDGE, COUNT }; enum class env_name : int { ENV_MODE = (int)wrap::COUNT, COUNT }; enum class env_param : int { REPLACE = (int)env_name::COUNT, COUNT }; const uint32_t max_params = (int)env_param::COUNT; void env(tex::env_name, tex::env_param); }; class textureBuffer_t : public gpuBuffer_t { friend class frameBuffer_t; public: // Generates a 1D texture textureBuffer_t(tex::format format, tex::data_type type, tex::filter mag, tex::filter min, tex::wrap wrap_s) : _dim(tex::dim::D1), _format(format), _type(type), _sampling({(int)mag, (int)min, (int)wrap_s, 0, 0}) { genBuffer(); }; // Generates a 2D texture textureBuffer_t(tex::format format, tex::data_type type, tex::filter mag, tex::filter min, tex::wrap wrap_s, tex::wrap wrap_t) : _dim(tex::dim::D2), _format(format), _type(type), _sampling({(int)mag, (int)min, (int)wrap_s, (int)wrap_t, 0}) { genBuffer(); }; // Generates a 3D texture textureBuffer_t(tex::format format, tex::data_type type, tex::filter mag, tex::filter min, tex::wrap wrap_s, tex::wrap wrap_t, tex::wrap wrap_r) : _dim(tex::dim::D3), _format(format), _type(type), _sampling({(int)mag, (int)min, (int)wrap_s, (int)wrap_t, (int)wrap_r}) { genBuffer(); }; ~textureBuffer_t() { freeBuffer(); } void bind() const; void unbind() const; void texture_data_1D(int width, const void * data); void texture_data_2D(int width, int height, const void * data); void texture_data_3D(int width, int height, int depth, const void * data); private: void genBuffer(); void freeBuffer(); private: const tex::dim _dim; const tex::format _format; const tex::data_type _type; const std::array<int, 5> _sampling; uint32_t _id { 0 }; int _width { 0 }; int _height { 0 }; int _depth { 0 }; }; /*********************************************************************** * FRAMEBUFFER ***********************************************************************/ namespace fbo { enum attachment { COLOR0 = 0, COLOR1, COLOR2, COLOR3, DEPTH, COUNT }; // global unbind for fbos void unbind(); } class frameBuffer_t : public gpuBuffer_t { friend class CShaderMgr; public: frameBuffer_t() { genBuffer(); } ~frameBuffer_t() { freeBuffer(); } void attach_texture(textureBuffer_t * texture, fbo::attachment loc); void attach_renderbuffer(renderBuffer_t * renderbuffer, fbo::attachment loc); void print_fbo(); void bind() const; void unbind() const; private: void genBuffer(); void freeBuffer(); void checkStatus(); protected: uint32_t _id { 0 }; std::vector<std::tuple<size_t, fbo::attachment>> _attachments; }; /*********************************************************************** * RENDERTARGET *---------------------------------------------------------------------- * A 2D render target that automatically has depth, used for postprocess ***********************************************************************/ struct rt_layout_t { enum data_type { UBYTE, FLOAT }; rt_layout_t(uint8_t _nchannels, data_type _type) : nchannels(_nchannels), type(_type) {} rt_layout_t(uint8_t _nchannels, data_type _type, int _width, int _height) : nchannels(_nchannels), type(_type), width(_width), height(_height) {} uint8_t nchannels; data_type type; int width { 0 }; int height { 0 }; }; class renderTarget_t : public gpuBuffer_t { friend class CShaderMgr; public: renderTarget_t(ivec2 size) : _size(size) {} ~renderTarget_t(); void bind() const { bind(true); }; void bind(bool clear) const; void layout(std::vector<rt_layout_t>&& desc, renderBuffer_t * with_rbo = nullptr); void resize(ivec2 size); protected: bool _shared_rbo { false }; ivec2 _size; frameBuffer_t * _fbo; renderBuffer_t * _rbo; std::vector<rt_layout_t> _desc; std::vector<textureBuffer_t *> _textures; };