// // helper.cxx // graphics // // Created by Sam Jaffe on 5/18/19. // Copyright © 2019 Sam Jaffe. All rights reserved. // #include "helper.hpp" #include #include #include #ifdef __APPLE__ #include #endif #include "scope_guard/scope_guard.hpp" #include "vector/vector.hpp" #include "game/graphics/shader.hpp" #include "game/util/env.hpp" #include "game/util/files.hpp" namespace { typedef void (*GLGetIntFor)(GLuint, GLenum, GLint *); typedef void (*GLGetLog)(GLuint, GLsizei, GLsizei *, GLchar *); struct opengl_error_formatter { void operator()() const; static void shader_error(GLuint id, std::string const & path); GLuint obj; std::string fileName, path, eNoticeMessage, eFileMessage, eWindowTitle; GLGetIntFor getIntv; GLGetLog getInfoLog; }; void opengl_error_formatter::operator()() const { std::stringstream errorString; std::string errorLineNumber; int infologLength = 0; int maxLength = 0; getIntv(obj, GL_INFO_LOG_LENGTH, &maxLength); errorString << eNoticeMessage << "\n"; std::unique_ptr infoLog(new char[maxLength + 1]); getInfoLog(obj, maxLength, &infologLength, infoLog.get()); std::string errorLog(infoLog.get()); infoLog.reset(); if (infologLength <= 0) return; std::size_t openParen = errorLog.find('('); std::size_t closeParen = errorLog.find(')'); errorLineNumber = errorLog.substr(openParen + 1, closeParen - openParen - 1); errorString << fileName << " , " << "line " << errorLineNumber << ":\n"; std::string errorStart = errorLog.find("error") != std::string::npos ? errorLog.substr(errorLog.find("error")) : errorLog; errorString << errorLog << "\n"; errorString << "OpenGL Version: " << glGetString(GL_VERSION) << "\n"; errorString << "GLSL Shading Version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << "\n"; errorString << eFileMessage << "\n"; errorString << "RAW ERROR LOG: " << errorLog; std::stringstream consoleOutput; consoleOutput << "1>" << path << '(' << errorLineNumber << ')' << ": " << errorStart << "in OpenGL version: " << glGetString(GL_VERSION) << " and " << "GLSL Shading Version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl; #ifdef _WIN32 OutputDebugStringA(consoleOutput.str().c_str()); MessageBoxA(nullptr, errorString.str().c_str(), eWindowTitle.c_str(), MB_OK); #else std::cerr << consoleOutput.str() << std::endl; std::cerr << errorString.str() << std::endl; #endif } void opengl_error_formatter::shader_error(GLuint id, std::string const & path) { std::string fileName = path.substr(path.find_last_of("/")); opengl_error_formatter{id, fileName, path, "GLSL shader compile error!", "File location" + path, "GLSL Compile Error in " + fileName, glGetShaderiv, glGetShaderInfoLog}(); } } namespace graphics { namespace textures { static int glfmt(format color_fmt) { switch (color_fmt) { case format::RGB: return GL_RGB; case format::RGBA: return GL_RGBA; } } 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_fmt); // 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, dimension.x(), dimension.y(), 0, bufferFormat, GL_UNSIGNED_BYTE, data); 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 id; } }} namespace graphics { namespace shaders { struct file_read_error : std::runtime_error { using std::runtime_error::runtime_error; }; struct compilation_error : std::runtime_error { using std::runtime_error::runtime_error; }; struct linker_error : std::runtime_error { using std::runtime_error::runtime_error; }; static int gltype(type tp) { switch (tp) { case type::FRAGMENT: return GL_FRAGMENT_SHADER; case type::VERTEX: return GL_VERTEX_SHADER; } } unsigned int init(type tp, std::string const & path) { 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("Could not load shader file " + 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, nullptr); // 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) { opengl_error_formatter::shader_error(id, abs_path); throw compilation_error("Could not compile the shader file"); } return id; } unsigned int init_program(shader const & fragmentShader, shader const & vertexShader) { // 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) { // LogShaderProgramError(*id, vertPath, fragPath); throw linker_error("Could not link shader program"); } return id; } void activate(unsigned int id) { // 100. Use the shader program ID to "turn it on" for all subsequent drawing glUseProgram(id); /* // 101. Enable texturing and Bind texture(s) to GPU texture units glActiveTexture(GL_TEXTURE3); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, emissiveTextureID); glActiveTexture(GL_TEXTURE2); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, specularTextureID); glActiveTexture(GL_TEXTURE1); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, normalTextureID); glActiveTexture(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, diffuseTextureID); */ // 102. Get the location # of each named uniform you wish to pass in to the // shader int timeUniformLocation = glGetUniformLocation(id, "u_time"); int scale = glGetUniformLocation(id, "Scale"); int diffuseMapUniformLocation = glGetUniformLocation(id, "u_diffuseMap"); int normalMapUniformLocation = glGetUniformLocation(id, "u_normalMap"); int specularMapUniformLocation = glGetUniformLocation(id, "u_specularMap"); int emissiveMapUniformLocation = glGetUniformLocation(id, "u_emissiveMap"); int debugWave = glGetUniformLocation(id, "g_debugWave"); // 103. Set the uniform values, including the texture unit numbers for // texture (sampler) uniforms // Env::GetCurrentTimeSeconds() glUniform1f(timeUniformLocation, (float)time(NULL)); glUniform1f(scale, 2.f); glUniform1i(debugWave, 1); // TODO: m_waveEffectOn in ShaderProgram?? // for GL_TEXTURE0, texture unit 0 glUniform1i(diffuseMapUniformLocation, 0); // for GL_TEXTURE1, texture unit 1 glUniform1i(normalMapUniformLocation, 1); // for GL_TEXTURE2, texture unit 2 glUniform1i(specularMapUniformLocation, 2); // for GL_TEXTURE3, texture unit 3 glUniform1i(emissiveMapUniformLocation, 3); } }}