#pragma once // ----------------------------------------------------------------------------- #include "Vector.h" #include #include #include #include #include #include #include #include enum class VertexFormat { // 8 bit Byte, Byte2, Byte3, Byte4, ByteNorm, Byte2Norm, Byte3Norm, Byte4Norm, UByte, UByte2, UByte3, UByte4, UByteNorm, UByte2Norm, UByte3Norm, UByte4Norm, //Single Precision Float, Float2, Float3, Float4, // 32 bit Int, Int2, Int3, Int4, UInt, UInt2, UInt3, UInt4, }; enum class VertexFormatBaseType { Byte, UByte, Float, Int, UInt }; /** * @param format Vertex format * @return The base type of the vertex format */ VertexFormatBaseType GetVertexFormatBaseType(VertexFormat format); /** * @param format Vertex format * @return The GL type of the vertex format */ GLenum VertexFormatToGLType(VertexFormat format); /** * @param format Vertex format * @return The number of components in the vertex format */ GLint VertexFormatToGLSize(VertexFormat format); /** * @param format Vertex format * @return Whether the vertex format is a normalized type */ bool VertexFormatIsNormalized(VertexFormat format); /** * @param format Vertex format * @return Whether the vertex format is a normalized type as a GLboolean */ GLboolean VertexFormatToGLNormalized(VertexFormat format); /** * @param format Vertex format * @return The size of the vertex format in bytes */ std::size_t GetSizeOfVertexFormat(VertexFormat format); // ----------------------------------------------------------------------------- // DESCRIPTORS // ----------------------------------------------------------------------------- // Describes a single array held in the vbo struct BufferDesc { BufferDesc(const char* _attr_name, VertexFormat _format, std::size_t _data_size = 0, const void* _data_ptr = nullptr, std::uint32_t offset = 0) : attr_name(_attr_name) , m_format(_format) , data_size(_data_size) , data_ptr(_data_ptr) , offset(offset) { } const char* attr_name{nullptr}; VertexFormat m_format{VertexFormat::Float}; std::size_t data_size{}; const void* data_ptr{nullptr}; std::uint32_t offset{}; }; 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, FLOAT4_TO_UB4 }; 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(nullptr){} }; 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 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, VertexFormat _format, AttribDataOp _attrOps = {}) : attr_name(_attr_name) , m_format(_format) , attrOps(_attrOps) { } const char * attr_name { nullptr }; VertexFormat m_format { VertexFormat::Float }; int order { 0 }; AttribDataOp attrOps { }; unsigned char *default_value { nullptr }; unsigned char *repeat_value { nullptr }; int repeat_value_length { 0 }; }; 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 }; }; enum class buffer_layout { SEPARATE, // multiple vbos SEQUENTIAL, // single vbo INTERLEAVED // single vbo }; // ----------------------------------------------------------------------------- /* 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] ] */ class GenericBuffer : public gpuBuffer_t { friend class CShaderMgr; public: GenericBuffer(buffer_layout layout = buffer_layout::SEPARATE, GLenum usage = GL_STATIC_DRAW); GenericBuffer(const GenericBuffer&) = delete; GenericBuffer& operator=(const GenericBuffer&) = delete; GenericBuffer(GenericBuffer&&) = delete; GenericBuffer& operator=(GenericBuffer&&) = delete; ~GenericBuffer(); /** * Conditionally generates a GPU buffer for the given data descriptor * @param desc The buffer data descriptor * @return Whether the buffer data was successfully buffered * @note 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); /** * Generates a GPU buffer for the given data descriptor * @param desc The buffer data descriptor * @param data The data to buffer * @param len The length of the data * @param stride The stride of the data * @note assumes the data is interleaved */ bool bufferData( BufferDataDesc&& desc, const void* data, size_t len, size_t stride); // ----------------------------------------------------------------------------- /** * Updates (a portion of) the buffer data * @param offset The offset to start updating the buffer data * @param size The size of the data to update * @param data The data to update * @param index The index of the buffer data to update * @note This function assumes that the data layout hasn't changed */ void bufferSubData(size_t offset, size_t size, void* data, size_t index = 0); /** * Replaces the whole interleaved buffer data * @param data The data to replace the buffer data with * @param len The length of the data * @param data The data to replace the buffer data with * @note This function assumes that the data layout hasn't changed */ void bufferReplaceData(size_t offset, size_t len, const void* data); protected: /** * Generates GPU buffer(s) for the given data descriptor * @return Whether the buffer data was successfully buffered */ bool evaluate(); // USAGE PATTERNS /** * Generates a separate buffer for each data descriptor * @return Whether the buffer data was successfully buffered */ bool sepBufferData(); /** * Generates a single sequential buffer for all data descriptors * @return Whether the buffer data was successfully buffered */ bool seqBufferData(); /** * Generates a single interleaved buffer for all data descriptors * @return Whether the buffer data was successfully buffered */ bool interleaveBufferData(); /** * Generates an OpenGL buffer with given size and data * @param id The OpenGL buffer ID * @param size The size of the buffer * @param ptr The data to buffer * @return Whether the buffer was successfully generated */ bool genBuffer(GLuint& id, size_t size, const void* ptr); /** * @return OpenGL buffer type */ virtual GLenum bufferType() const = 0; protected: bool m_status{false}; bool m_interleaved{false}; GLuint m_interleavedID{0}; const GLenum m_buffer_usage{GL_STATIC_DRAW}; const buffer_layout m_layout{ buffer_layout::SEPARATE }; size_t m_stride{0}; BufferDataDesc m_desc; std::vector desc_glIDs; // m_desc's gl buffer IDs }; /** * Vertex buffer specialization */ class VertexBuffer : public GenericBuffer { void bind_attrib(GLuint prg, const BufferDesc& d, GLuint glID); public: VertexBuffer(buffer_layout layout = buffer_layout::SEPARATE, GLenum usage = GL_STATIC_DRAW); void bind() const override; void bind(GLuint prg, int index = -1); void unbind(); void maskAttributes(std::vector attrib_locs); void maskAttribute(GLint attrib_loc); GLenum bufferType() const override; private: // m_locs is only for interleaved data std::vector m_locs; std::vector m_attribmask; }; /** * Index buffer specialization */ class IndexBuffer : public GenericBuffer { public: using GenericBuffer::GenericBuffer; void bind() const override; void unbind(); GLenum bufferType() const override; }; // 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 override; 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 override; /** * Binds the texture to a specific texture unit * @param textureUnit The texture unit to bind to (0, 1, 2, etc) */ void bindToTextureUnit(std::uint8_t textureUnit) 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); /** * Specifies a 2D texture subimage * @param xoffset The x offset of the subimage * @param yoffset The y offset of the subimage * @param width The width of the subimage * @param height The height of the subimage * @param data The data to upload */ void texture_subdata_2D(int xoffset, int yoffset, int width, int height, 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 _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 override; void unbind() const; private: void genBuffer(); void freeBuffer(); void checkStatus(); protected: uint32_t _id { 0 }; std::vector> _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: using shape_type = glm::ivec2; renderTarget_t(shape_type size) : _size(size) {} renderTarget_t(int width, int height) : _size(width, height) {} ~renderTarget_t(); void bind() const override { bind(true); }; void bind(bool clear) const; void bindFBORBO() const { _fbo->bind(); _rbo->bind(); } void layout(std::vector&& desc, renderBuffer_t * with_rbo = nullptr); void resize(shape_type size); const shape_type& size() const { return _size; }; renderBuffer_t* rbo() const noexcept { return _rbo; } frameBuffer_t* fbo() const noexcept { return _fbo; } const std::vector& textures() const noexcept { return _textures; } protected: bool _shared_rbo { false }; shape_type _size; frameBuffer_t * _fbo; renderBuffer_t * _rbo; std::vector _desc; std::vector _textures; };