Newer
Older
Import / applications / HighwayDash / ports / Framework / GLProgram.h
//  BlockyFroggy
//  Copyright © 2017 John Ryland.
//  All rights reserved.
#pragma once
#ifndef GL_PROGRAM_H
#define GL_PROGRAM_H


#include <functional>
#include <memory>
#include <string>
#include <map>
#include <cassert>
#include "OpenGL.h"


struct StrCmp { bool operator()(const char *a, const char *b) const { return ::strcmp(a, b) < 0; } };
typedef std::map<const char *, bool, StrCmp>  OptionsType;


enum Precision
{
  High,
  Medium,
  Low
};


class GLProgram
{
public:
  GLProgram();
  ~GLProgram();
    
  typedef std::function<void(uint32_t a_program)> ProgramCallback;

  bool loadShaders(const char *a_vertexShader, const char *a_fragmentShader,
                   Precision a_defaultPrecision, const OptionsType& a_options,
                   ProgramCallback a_bindAttribs, ProgramCallback a_setupUniforms);
  void useProgram();
  void deleteProgram();

private:
  struct Pimpl;
  std::unique_ptr<Pimpl> m_pimpl;
};


/*
template <typename T, size_t size>
size_t ArraySize(T(&)[size]) { return size; }

template <typename T>
size_t ArraySize(std::vector<T> x) { return x.size(); }

template <typename T, size_t size>
const T& GetElement(const T (&array)[size], size_t index, const T& nullValue) { return (index < 0 || index >= size) ? nullValue : array[index]; }
*/


inline GLenum glType(bool)     { return GL_UNSIGNED_INT; }
inline GLenum glType(uint8_t)  { return GL_UNSIGNED_BYTE; }
inline GLenum glType(uint16_t) { return GL_UNSIGNED_SHORT; }
inline GLenum glType(uint32_t) { return GL_UNSIGNED_INT; }
inline GLenum glType(int8_t)   { return GL_BYTE; }
inline GLenum glType(int16_t)  { return GL_SHORT; }
inline GLenum glType(int32_t)  { return GL_INT; }
inline GLenum glType(float)    { return GL_FLOAT; }
#ifndef USE_OPENGLES2
inline GLenum glType(double)   { return GL_DOUBLE; }
#endif


// Not sure this works, but idea is that this templated version might prevent type coersion to one of the functions above and catch an error
template <typename T>
inline GLenum glType(T)        { assert("bad attribute unittype, try adding the type you need to above" && false); return GL_INT; }


#define DECLARE_ATTRIB_TYPE(name, unittype, ...) \
            struct name##attrib { \
                union { \
                    struct { unittype  __VA_ARGS__; }; \
                    unittype  attrib[4]; \
                }; \
                static GLenum getUnitType() { return glType(unittype(0)); } \
                static GLint  getSize()     { struct { unittype  __VA_ARGS__; } x; return sizeof(x)/sizeof(unittype); } \
            };

#define DECLARE_VERTEX(name) \
            struct name { \
              typedef name this_type; \
              static const char* this_name() { return #name; } \
              typedef struct { \
                static GLuint attribIndex() { return -1; } \
                template <typename T> static inline void visit(T&) { }

// Using "Ryland's Device"
#define DECLARE_ATTRIB(attribtype, name, normalized) \
              } detail_##name; \
              attribtype##attrib m_##name; \
              typedef struct { \
                static GLuint attribIndex() { return detail_##name::attribIndex() + 1; } \
                template <typename T> \
                static inline void visit(T& v) { \
                  detail_##name::visit(v); \
                  v(#name, attribIndex(), attribtype##attrib::getSize(), attribtype##attrib::getUnitType(), normalized, sizeof(this_type), &((this_type*)(nullptr))->m_##name); \
                }

#define DECLARE_VERTEX_END \
              } detail_last; \
              template <typename T> \
              static void visit(T& v) { \
                detail_last::visit(v); \
              } \
            };


#define VERTEX_ATTRIB_INDEX(vertextype, attrib) \
            GLint(vertextype::detail_##attrib::attribIndex() + 1)


DECLARE_ATTRIB_TYPE(vec2,  float, x, y)
DECLARE_ATTRIB_TYPE(vec3,  float, x, y, z)
DECLARE_ATTRIB_TYPE(vec4,  float, x, y, z, w)
DECLARE_ATTRIB_TYPE(col3f, float, r, g, b)
DECLARE_ATTRIB_TYPE(col4f, float, r, g, b, a)
DECLARE_ATTRIB_TYPE(col4i, uint8_t, r, g, b, a)
//DECLARE_ATTRIB_TYPE(col4i, uint8_t, a, b, g, r)


void AttribPointerVisitor(const char* name, GLuint index, GLsizei size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer);


void glSetUniform(GLint a_location, mat3 a_matrix3);
void glSetUniform(GLint a_location, mat4 a_matrix4);
void glSetUniform(GLint a_location, int a_int);
void glSetUniform(GLint a_location, bool a_bool);
void glSetUniform(GLint a_location, float a_flt);
void glSetUniform(GLint a_location, vec2attrib a_vec2);
void glSetUniform(GLint a_location, vec3attrib a_vec3);
void glSetUniform(GLint a_location, vec4attrib a_vec4);

#ifndef USE_OPENGLES2
GLAPI GLint APIENTRY glGetUniformLocation (GLuint program, const GLchar *name);
#endif

// Not sure this works, but idea is that this templated version might prevent type coersion to one of the functions above and catch an error
template <typename T>
inline void glSetUniform(GLint a_location, T val)    { assert("bad uniform type, try adding to above" && false); }


#define DECLARE_PROGRAM_UNIFORMS(name) \
            struct name { \
              typedef name this_type; \
              static const char* this_name() { return #name; } \
              typedef struct { \
                static void getLocation(GLint, this_type*) { } \
                static void setUniform(this_type*) { }

// Using Ryland's device
#define DECLARE_UNIFORM(uniformType, name) \
              } detail_##name; \
              bool m_inited_##name = false; \
              uniformType m_last_##name; \
              uniformType m_##name; \
              GLint       m_##name##_index = -1; \
              typedef struct { \
                static void getLocation(GLint a_program, this_type* _this) { \
                  detail_##name::getLocation(a_program, _this); \
                  _this->m_##name##_index = glGetUniformLocation(a_program, #name); \
                  if (glGetError() != GL_NO_ERROR) \
                    LogTag(LL_Error, "GLP", "bad uniform location: %s::%s\n", this_name(), #name); \
                } \
                static void setUniform(this_type* _this) { \
                  detail_##name::setUniform(_this); \
                  if (true || memcmp(&_this->m_##name, &_this->m_last_##name, sizeof(uniformType))==0) { \
                  /* if (_this->m_inited_##name == false || memcmp(&_this->m_##name, &_this->m_last_##name, sizeof(uniformType))==0) { */ \
                    glSetUniform(_this->m_##name##_index, _this->m_##name); \
                    _this->m_inited_##name = true; \
                    memcpy(&_this->m_last_##name, &_this->m_##name, sizeof(uniformType)); \
                    /* _this->m_last_##name = _this->m_##name; */ \
                  } \
                }

#define DECLARE_PROGRAM_UNIFORMS_END \
              } detail_last; \
              void getLocations(GLint a_program) { \
                detail_last::getLocation(a_program, this); \
              } \
              void setUniforms() { \
                detail_last::setUniform(this); \
              } \
            };




#endif // GL_PROGRAM_H