|
|
@@ -0,0 +1,356 @@
|
|
|
+//
|
|
|
+// helper.cxx
|
|
|
+// graphics
|
|
|
+//
|
|
|
+// Created by Sam Jaffe on 5/18/19.
|
|
|
+// Copyright © 2019 Sam Jaffe. All rights reserved.
|
|
|
+//
|
|
|
+
|
|
|
+#include "helper.hpp"
|
|
|
+
|
|
|
+#include <iostream>
|
|
|
+#include <memory>
|
|
|
+#include <sstream>
|
|
|
+#ifdef __APPLE__
|
|
|
+#include <OpenGL/gl3.h>
|
|
|
+#endif
|
|
|
+
|
|
|
+#include "scope_guard/scope_guard.hpp"
|
|
|
+#include "vector/vector.hpp"
|
|
|
+
|
|
|
+#include "game/graphics/material.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 error_formatter {
|
|
|
+ void operator()() const;
|
|
|
+ static void shader_error(GLuint id, std::string const & path);
|
|
|
+ static void shader_program_error(GLuint id, std::string const & frag_path,
|
|
|
+ std::string const & vert_path);
|
|
|
+
|
|
|
+ GLuint obj;
|
|
|
+ std::string fileName, path, eNoticeMessage, eFileMessage, eWindowTitle;
|
|
|
+ GLGetIntFor getIntv;
|
|
|
+ GLGetLog getInfoLog;
|
|
|
+ };
|
|
|
+
|
|
|
+ void 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<char[]> 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 error_formatter::shader_error(GLuint id, std::string const & path) {
|
|
|
+ std::string fileName = path.substr(path.find_last_of("/"));
|
|
|
+ error_formatter{id,
|
|
|
+ fileName,
|
|
|
+ path,
|
|
|
+ "GLSL shader compile error!",
|
|
|
+ "File location" + path,
|
|
|
+ "GLSL Compile Error in " + fileName,
|
|
|
+ glGetShaderiv,
|
|
|
+ glGetShaderInfoLog}();
|
|
|
+ }
|
|
|
+
|
|
|
+ void error_formatter::shader_program_error(GLuint id,
|
|
|
+ std::string const & frag_path,
|
|
|
+ std::string const & vert_path) {
|
|
|
+ std::string fileName = frag_path.substr(frag_path.find_last_of("/"));
|
|
|
+ error_formatter{id,
|
|
|
+ fileName,
|
|
|
+ frag_path,
|
|
|
+ "GLSL program link error!",
|
|
|
+ "File location of vertex shader: " + vert_path +
|
|
|
+ "\nFile location of fragment shader: " + frag_path,
|
|
|
+ "GLSL Link Error",
|
|
|
+ glGetProgramiv,
|
|
|
+ glGetProgramInfoLog}();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+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<char const[]> 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) {
|
|
|
+ error_formatter::shader_error(id, abs_path);
|
|
|
+ throw compilation_error("Could not compile the shader file");
|
|
|
+ }
|
|
|
+ return id;
|
|
|
+ }
|
|
|
+
|
|
|
+ unsigned int init(flyweight<shader> const & fragmentShader,
|
|
|
+ flyweight<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) {
|
|
|
+ error_formatter::shader_program_error(id, fragmentShader.actual().path,
|
|
|
+ vertexShader.actual().path);
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ int uniform_location(unsigned int id, std::string const & uniform) {
|
|
|
+ return glGetUniformLocation(id, uniform.c_str());
|
|
|
+ }
|
|
|
+}}
|
|
|
+
|
|
|
+namespace graphics { namespace materials {
|
|
|
+ void activate(flyweight<shader_program> program,
|
|
|
+ std::vector<uniform> const & uniforms) {
|
|
|
+ glUseProgram(program.id);
|
|
|
+
|
|
|
+ for (unsigned int i = 0; i < uniforms.size(); i++) {
|
|
|
+ const uniform & uniform = uniforms[i];
|
|
|
+ glActiveTexture(i + GL_TEXTURE0);
|
|
|
+ // glEnable(GL_TEXTURE_2D);
|
|
|
+ glBindTexture(GL_TEXTURE_2D, uniform.texture.id);
|
|
|
+ glUniform1i(uniform.uniform_id, i);
|
|
|
+ }
|
|
|
+
|
|
|
+ glActiveTexture(GL_TEXTURE0);
|
|
|
+ }
|
|
|
+}}
|