Newer
Older
Import / research / 3d-experiments / 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 "Common.h"
#include "OpenGL/gl.h"


#  define glGenVertexArraysOES     glGenVertexArraysAPPLE
#  define glBindVertexArrayOES     glBindVertexArrayAPPLE
#  define glDeleteVertexArraysOES  glDeleteVertexArraysAPPLE


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;
};


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
template <typename T>
inline GLenum glType(T v)      { return glType(v.m[0]); }


// 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)        { static_assert(bad_food<T>::value, "bad attribute unittype, try adding the type you need to above"); return GL_INT; }


#define DECLARE_ATTRIB_TYPE(name, gltype, unittype, ...) \
            struct name { \
                /* name& operator=(unittype* other) {} */ \
                union { \
                    struct { unittype  __VA_ARGS__; }; \
                    unittype m[4]; \
                }; \
                operator unittype*() { return &m[0]; } \
                static std::string getGLType() { return #gltype; } \
                static GLenum getUnitType() { return glType(unittype{}); } \
                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 void shaderDeclaration(std::string& a_string) { } \
                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 m_##name; \
              typedef struct { \
                static void shaderDeclaration(std::string& a_string) { \
                  detail_##name::shaderDeclaration(a_string); \
                  a_string += "attribute " + attribtype::getGLType() + " " #name ";\n"; \
                } \
                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::getSize(), attribtype::getUnitType(), normalized, sizeof(this_type), &((this_type*)(nullptr))->m_##name); \
                }

#define DECLARE_VERTEX_END \
              } detail_last; \
              static void shaderDeclaration(std::string& a_string) { \
                detail_last::shaderDeclaration(a_string); \
              } \
              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,  vec2, float,   x, y)
DECLARE_ATTRIB_TYPE(vec3,  vec3, float,   x, y, z)
DECLARE_ATTRIB_TYPE(vec4,  vec4, float,   x, y, z, w)
DECLARE_ATTRIB_TYPE(col3f, vec3, float,   r, g, b)
DECLARE_ATTRIB_TYPE(col4f, vec4, float,   r, g, b, a)
DECLARE_ATTRIB_TYPE(col4i, vec4, uint8_t, r, g, b, a)
DECLARE_ATTRIB_TYPE(mat3,  mat3, vec4,    m0, m1, m2)
DECLARE_ATTRIB_TYPE(mat4,  mat4, vec4,    m0, m1, m2, m3)


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, vec2  a_vec2);
void glSetUniform(GLint a_location, vec3  a_vec3);
void glSetUniform(GLint a_location, vec4  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 shaderDeclaration(std::string& a_string) { } \
                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 shaderDeclaration(std::string& a_string) { \
                  detail_##name::shaderDeclaration(a_string); \
                  a_string += "uniform " #uniformType " " #name ";\n"; \
                } \
                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) \
                    Log(LL_Error, "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; \
              static void shaderDeclaration(std::string& a_string) { \
                detail_last::shaderDeclaration(a_string); \
              } \
              void getLocations(GLint a_program) { \
                detail_last::getLocation(a_program, this); \
              } \
              void setUniforms() { \
                detail_last::setUniform(this); \
              } \
            };



typedef GLProgram::ProgramCallback VertexBindFunc;


enum PrimitiveType
{
  Triangles,
  Lines
};


template <typename VertexType>
class VertexArray
{
public:
  void createVertexBuffer(enum PrimitiveType a_type = Triangles)
  {
    m_primitiveType = (a_type == Triangles) ? GL_TRIANGLES : GL_LINES;
    glGenVertexArraysOES(1, &m_vertexArray);
    glBindVertexArrayOES(m_vertexArray);
    glGenBuffers(1, &m_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
    VertexType::visit(AttribPointerVisitor);
    glBindVertexArrayOES(0);
    
    // init data
    update();
  }

  void destroyVertexBuffer()
  {
    glDeleteBuffers(1, &m_vertexBuffer);
    glDeleteVertexArraysOES(1, &m_vertexArray);
    m_vertexData.clear();
    m_vertexDataSize = 0;
  }

  void update()
  {
    // update data
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
    m_vertexDataSize = m_vertexData.size();
    glBufferData(GL_ARRAY_BUFFER, sizeof(VertexType) * m_vertexDataSize, (void*)m_vertexData.data(), GL_STATIC_DRAW);
    m_gpuAllocatedSize = m_vertexDataSize;
  }

  void draw()
  {
    if (!m_vertexDataSize)
      return;
    glBindVertexArrayOES(m_vertexArray);
    glDrawArrays(m_primitiveType, 0, GLsizei(m_vertexDataSize)); // tri-strips are better
  }

  void sync()
  {
    if (m_vertexData.size())
    {
      update();
      m_vertexData.clear();
      m_vertexData.shrink_to_fit();
    }
  }

  VertexBindFunc bindAttributesFunction()
  {
    return [](GLuint a_program) {
      auto bindAttribFunc = [a_program](const char* name, GLuint index, GLsizei, GLenum, GLboolean, GLsizei, const void*) {
        glBindAttribLocation(a_program, index, name);
      };
      VertexType::visit(bindAttribFunc);
    };
  }

  GLuint                  m_primitiveType = GL_TRIANGLES;
  GLuint                  m_vertexArray = 0;
  GLuint                  m_vertexBuffer = 0;
  std::vector<VertexType> m_vertexData;
  size_t                  m_gpuAllocatedSize = 0;
  size_t                  m_vertexDataSize = 0;
};


enum Flag
{
  clearColor = 1,
  clearDepth = 2,
  enableCullFace = 4,
  enableDepthTest = 5,
};
typedef int Flags;


// Material Context
//   - pass it a texture
//   - pass it a shader
//   - pass it shader parameters
// Objects
//   - array of vertexes
//   - a material reference
template <class UniformsType, class VertexType>
class ProgramContext2
{
  static constexpr int MaxTextureCount() { return 32; }
public:
  void setup(const char *a_vertexShader, const char *a_fragmentShader,
                   Precision a_defaultPrecision, const OptionsType& a_options, int a_textures = 0, enum PrimitiveType a_type = Triangles)
  {
    // Setup main program
    auto bindUniformsFunc = [this](GLuint a_program) { m_uniforms.getLocations(a_program); };
    m_program.deleteProgram();
    VertexArray<VertexType> v;
    std::string uDeclStr;
    UniformsType::shaderDeclaration(uDeclStr);
    std::string vDeclStr = uDeclStr;
    VertexType::shaderDeclaration(vDeclStr);
    vDeclStr += a_vertexShader;
    uDeclStr += a_fragmentShader;
    m_program.loadShaders(vDeclStr.c_str(), uDeclStr.c_str(), a_defaultPrecision, a_options, m_vertexArray.bindAttributesFunction(), bindUniformsFunc);
    m_vertexArray.createVertexBuffer(a_type);

    m_textureCount = std::min(a_textures, MaxTextureCount());
    glGenTextures(m_textureCount, m_textureIds);
    for (int i = 0; i < m_textureCount; i++)
    {
      glBindTexture(GL_TEXTURE_2D, m_textureIds[i]);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid *)nullptr);
    }
  }

  void shutdown()
  {
    m_program.deleteProgram();
    m_vertexArray.destroyVertexBuffer();
  }
  
  void setTextureData(int a_textureSlot, unsigned int a_width, unsigned int a_height, const unsigned char* a_data)
  {
    if (a_textureSlot < m_textureCount)
    {
      glBindTexture(GL_TEXTURE_2D, m_textureIds[a_textureSlot]);
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, a_width, a_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid *)a_data);
    }
  }
  
  void update()
  {
    m_vertexArray.update();
  }
  
  void draw()
  {
    if (m_flags & clearColor)
      glClearColor(m_clearColor.x, m_clearColor.y, m_clearColor.z, m_clearColor.w);
    glClear(((m_flags & clearColor) ? GL_COLOR_BUFFER_BIT : 0) | ((m_flags & clearDepth) ? GL_DEPTH_BUFFER_BIT : 0));
    setEnabled(m_flags & enableCullFace, GL_CULL_FACE);
    setEnabled(m_flags & enableDepthTest, GL_DEPTH_TEST);
    //glCullFace(GL_BACK);
    //glDepthMask(GL_TRUE);

    for (int i = 0; i < m_textureCount; i++)
    {
      glActiveTexture(GL_TEXTURE0 + i);
      glBindTexture(GL_TEXTURE_2D, m_textureIds[i]);
    }
    m_program.useProgram();
    m_uniforms.setUniforms();
    m_vertexArray.draw();
  }

  void setFlag(Flags a_flag)
  {
    m_flags |= a_flag;
  }
  
  void clearFlag(Flags a_flag)
  {
    m_flags &= ~a_flag;
  }
  
  void setBackgroundColor(float r, float g, float b)
  {
    m_clearColor = { r, g, b, 1.0f };
  }

  GLProgram               m_program;
  UniformsType            m_uniforms;
  VertexArray<VertexType> m_vertexArray;
  int                     m_textureCount;
  GLuint                  m_textureIds[MaxTextureCount()];

private:
  vec4f                   m_clearColor;
  int                     m_flags = 0;

  void setEnabled(bool a_enabled, GLenum a_glFlag)
  {
    if (a_enabled)
      glEnable(a_glFlag);
    else
      glDisable(a_glFlag);
  }
};




#endif // GL_PROGRAM_H