// // helper.cxx // graphics // // Created by Sam Jaffe on 5/18/19. // Copyright © 2019 Sam Jaffe. All rights reserved. // #include "helper.hpp" #include "opengl_renderer.h" #include #include #include #ifdef __APPLE__ #include #endif #include "scope_guard/scope_guard.hpp" #include "vector/vector.hpp" #include "game/graphics/exception.h" #include "game/graphics/material.hpp" #include "game/graphics/shader.hpp" #include "game/graphics/shader_program.hpp" #include "game/graphics/texture.hpp" #include "game/util/env.hpp" #include "game/util/files.hpp" #include "game/util/time.hpp" namespace graphics { extern void print_shader_error(GLuint, std::string const &); extern void print_shader_program_error(GLuint, std::string const &, std::string const &); struct compilation_error : std::runtime_error { using std::runtime_error::runtime_error; }; struct linker_error : std::runtime_error { using std::runtime_error::runtime_error; }; } using namespace graphics; namespace { int glfmt(textures::format color_fmt) { switch (color_fmt) { case textures::format::RGB: return GL_RGB; case textures::format::RGBA: return GL_RGBA; } } int gltype(shaders::type tp) { switch (tp) { case shaders::type::FRAGMENT: return GL_FRAGMENT_SHADER; case shaders::type::VERTEX: return GL_VERTEX_SHADER; } } } texture opengl_manager::compile(textures::format color, math::vec2i size, void const * buffer) const { // textures::external_data data(env::resource_file(path)); // unsigned int init(format color_fmt, math::vec2i dimension, // void const * data) { unsigned int id; // Enable texturings // glEnable( GL_TEXTURE_2D ); // Tell OpenGL that our pixel data is single-byte aligned glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Ask OpenGL for an unused texName (ID number) to use for this texture glGenTextures(1, &id); // Tell OpenGL to bind (set) this as the currently active texture glBindTexture(GL_TEXTURE_2D, id); // Set texture clamp vs. wrap (repeat) // one of: GL_CLAMP_TO_EDGE, GL_REPEAT, GL_MIRRORED_REPEAT, // GL_MIRROR_CLAMP_TO_EDGE, ... glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // the format our source pixel data is currently in; any of: GL_RGB, // GL_RGBA, GL_LUMINANCE, GL_LUMINANCE_ALPHA, ... int bufferFormat = glfmt(color); // the format we want the texture to me on the card; allows us to translate // into a different texture format as we upload to OpenGL int internalFormat = bufferFormat; /* glTexImage2D: Load a 2d texture image * target: Creating this as a 2d texture. * level: Which mipmap level to use as the "root" (0 = the highest-quality, * full-res image), if mipmaps are enabled. * internalFormat: Type of texel format we want OpenGL to use for this * texture internally on the video card. * width: Texel-width of image; for maximum compatibility, use 2^N + 2^B, * where N is some integer in the range [3,10], and B is the border * thickness [0,1] * height: Texel-height of image; for maximum compatibility, use 2^M + 2^B, * where M is some integer in the range [3,10], and B is the border * thickness [0,1] * border: Border size, in texels (must be 0 or 1) * format: Pixel format describing the composition of the pixel data in * buffer * type: Pixel color components are unsigned bytes (one byte per color/alpha * channel) * pixels: Location of the actual pixel data bytes/buffer */ glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, size.x(), size.y(), 0, bufferFormat, GL_UNSIGNED_BYTE, buffer); glGenerateMipmap(GL_TEXTURE_2D); // Set magnification (texel > pixel) and minification (texel < pixel) // filters one of: GL_NEAREST, GL_LINEAR glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // one of: GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, // GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, // GL_LINEAR_MIPMAP_LINEAR glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 5); return texture{id, size}; } shader opengl_manager::compile(shaders::type tp, std::string const & path) const { std::unique_ptr buffer; // 1. Load the vertex shader code (text file) to a new memory buffer std::string const abs_path = env::resource_file(path); if (!(buffer = files::load(abs_path))) { throw file_read_error("shader", abs_path); } // 2. Create a new shader ID // GL_VERTEX_SHADER or GL_FRAGMENT_SHADER unsigned int id = glCreateShader(gltype(tp)); // 3. Associate the shader code with the new shader ID char const * buffer_ptr = buffer.get(); glShaderSource(id, 1, &buffer_ptr, NULL); // 4. Compile the shader (the shader compiler is built in to your graphics // card driver) glCompileShader(id); // 5. Check for compile errors int return_code; glGetShaderiv(id, GL_COMPILE_STATUS, &return_code); if (return_code != GL_TRUE) { print_shader_error(id, abs_path); throw compilation_error("Could not compile the shader file"); } return shader{id, tp, path}; } shader_program opengl_manager::compile(identity fragmentShader, identity vertexShader) const { // 9. Create a new shader program ID unsigned int id = glCreateProgram(); // 10. Attach the vertex and fragment shaders to the new shader program glAttachShader(id, vertexShader.id); glAttachShader(id, fragmentShader.id); // 11. Do some other advanced stuff we'll do later on (like setting generic // vertex attrib locations) glBindAttribLocation(id, 0, "a_position"); glBindAttribLocation(id, 1, "a_color"); glBindAttribLocation(id, 2, "a_texCoords"); glBindAttribLocation(id, 3, "a_normal"); // 12. Link the program glLinkProgram(id); // 13. Check for link errors int return_code; glGetProgramiv(id, GL_LINK_STATUS, &return_code); if (return_code != GL_TRUE) { // print_shader_program_error(id, fragmentShader.actual().path, // vertexShader.actual().path); throw linker_error("Could not link shader program"); } return shader_program{id, fragmentShader, vertexShader}; } opengl_uniform_data & opengl_manager::data(identity program) { auto & data = data_[program]; // TODO: perform this in compile() if (data.uniform_id.empty()) { glUseProgram(program.id); using materials::uniform; data.uniform_id = { {uniform::NORMAL, glGetUniformLocation(program.id, "u_normalMap")}, {uniform::DIFFUSE, glGetUniformLocation(program.id, "u_diffuseMap")}, {uniform::SPECULAR, glGetUniformLocation(program.id, "u_specularMap")}}; } return data; }