// 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