Seeing as though you said you need to support OSX I'm going to presume you request a 3.3 core context or better.
You said you don't want to pull in any extra dependencies and to keep it cross platform, so I will try to adhere to this as much as possible.
I am using stb_image.h
from the Nothing itself nothings.
I'm going to define a basic class for a texture:
#include "stb_image.h"
class texture{
public:
texture(const char *filepath)
: image(stbi_load(filepath, &width, &height, &components, STBI_rgba)){
// check image != nullptr
glGenTextures(1, &handle);
GLint curr_tex; // for saving original state
glGetIntegerv(GL_ACTIVE_TEXTURE, &curr_tex);
// I think this is good practice incase somebody else uses your code
glActiveTexture(GL_TEXTURE3); // or some other texture unit
// I use 3 because reasons
glBindTexture(GL_TEXTURE_2D, handle);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GLint gl_internal_format = ((components == 3) ? GL_RGB8 : GL_RGBA8);
GLenum gl_format = ((components == 3) ? GL_RGB : GL_RGBA);
glTexImage2D(
GL_TEXTURE_2D, 0,
gl_internal_format,
width, height, 0,
gl_format,
GL_UNSIGNED_BYTE, image
);
glActiveTexture(curr_tex); // restore original state
}
~texture(){
stbi_image_free(image);
glDeleteTextures(1, &handle);
}
int width, height, components;
unsigned char *image;
GLuint handle;
};
You could expand on this class, adding conversions and such.
Next I'm going to add a basic quad class:
class quad{
public:
quad(){
GLfloat vertices[] = { // format = x, y, z, u, v
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
glGenVertexArrays(1, &vao); // vao saves state of array buffer, element array, etc
glGenBuffers(1, &vbo); // vbo stores vertex data
GLint curr_vao; // original state
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vao);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float)*5, nullptr);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float)*5, (void*)(sizeof(float) * 3));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindVertexArray(curr_vao);
}
~quad(){
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &vbo);
}
GLuint vao, vbo;
};
Now the most important part: the shaders.
vertex.glsl
#version 330 core
layout(location = 0) in vec3 vertex;
layout(location = 0) in vec2 uv;
// will be used in fragment shader
out vec2 uv_frag;
void main(){
uv_frag = uv;
gl_Position = vec4(vertex, 1.0);
}
fragment.glsl
#version 330 core
// has to have same name as vertex shader
in vec2 uv_frag;
// our texture
uniform sampler2D tex;
// actual output
// gl_FragColor is deprecated
out vec4 frag_color;
void main(){
frag_color = texture(tex, uv_frag);
}
Lets make a class for loading these in
class shader_program{
public:
shader_program(const char *vert_path, const char *frag_path)
: program(glCreateProgram()), vert(glCreateShader(GL_VERTEX_SHADER)), frag(glCreateShader(GL_FRAGMENT_SHADER)){
std::ifstream vert_file(vert_path), frag_file(frag_path);
// check both opened fine
auto lvalue = [](auto &&arg) -> decltype(arg)& {return arg;}
std::string temp, vert_source, frag_source;
while(getline(vert_file, temp)) vert_source += temp + '\n';
while(getline(frag_file, temp)) frag_source += temp + '\n';
glShaderSource(vert, 1, &lvalue(vert_source.c_str()), nullptr);
glShaderSource(frag, 1, &lvalue(frag.source.c_str()), nullptr);
glCompileShader(vert);
glCompileShader(frag);
// check shaders compiled. google glGetShaderInfoLog
glAttachShader(program, vert);
glAttachShader(program, frag);
glLinkProgram(program);
// check program linked correctly. google glGetProgramInfoLog
}
~shader_program(){
glDeleteProgram(program);
glDeleteShader(vert);
glDeleteShader(frag);
}
GLuint program, vert, frag;
};
Then we throw it all together:
// set function pointers to no-op functions
// always initialize global function pointers
void(*glGenVertexArrays)(GLsizei n, GLuint *arrays) = [](){};
void(*glGenBuffers)(GLsizei n, GLuint *buffers) = [](){};
void(*glDeleteVertexArrays)(GLsizei n, GLuint *arrays) = [](){};
void(*glDeleteBuffers)(GLsizei n, GLuint *buffers) = [](){};
// more than probably a few more pointers
int main(int argc, char *argv[]){
// create window and context
glGenVertexArrays = reinterpret_cast<decltype(glGenVertexArrays)>(SDL_GL_GetProcAddress("glGenVertexArrays"));
// do this for every function pointer
texture t("mytexture.png");
quad q;
shader_program program("vertex.glsl", "fragment.glsl");
glBindVertexArray(q.vao);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, t.handle);
glUseProgram(program.program);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
bool running = true;
while(running){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
// we laid our quad out in a fan
SDL_GL_SwapBuffers();
}
glUseProgram(0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
// destroy window and context
// make sure they are destroyed after the texture, quad and shaders
}
I really recommend you don't go through getting all those function pointers yourself though.
I can't recommend glLoadGen enough; or something similar (glew).
I Gotta warn you now, I haven't tested this code because I'm not at home, but I've written code similar to that before and it's worked great. If any issues arise just leave a comment and I'll fix anything.