#ifndef MAIN_CPP #include #include #include #include // I am using standart library RNG because I am lazy and wanted to create quick code snippet // upgrade to this: https://arvid.io/2018/06/30/on-cxx-random-number-generator-quality/ whenever, if ever I feel like it #include // for std::chrono #include "constants.hpp" void configureGLFW() { // first argument tells us what option to configure // second is to what we set the value of this option // see: https://www.glfw.org/docs/latest/window.html#window_hints glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // we set GLFW to 3.3 CORE // core profile gives us access to smaller subset of OGL without backwards compatible features // if we are on Mac OS X we need this for our code to work #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif } void instantiateGLFWwindow() { // Initialize GLFW glfwInit(); configureGLFW(); } GLFWwindow* createWindowObject() { // First two arguments are width and height // Third is the name of the window // We ignore last two GLFWwindow* window = glfwCreateWindow(constants::MAIN_WINDOW_WIDTH, constants::MAIN_WINDOW_HEIGHT, constants::MAIN_WINDOW_NAME, NULL, NULL); return window; } int initializeGLAD() { // we load address of OGL OS-specific function pointers if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; } return 0; } // resizes viewport when user resizes window void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); } void viewPort(GLFWwindow* window) { // We tell OGL size of rendering window // First two define left corner of window // 3th and 4th width and height of rendering window // we could set them to be smaller than window dimension, ogl rendering will be then displayed in smaller window glViewport(0, 0, constants::MAIN_WINDOW_WIDTH, constants::MAIN_WINDOW_HEIGHT); // processed coordinates are between -1 and 1 so here we map: // (-1 to 1) to (0, constants::MAIN_WINDOW_WIDTH) and (0, constants::MAIN_WINDOW_HEIGHT) glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // we call framebuffer_size_callback on every window resize } bool processInput(GLFWwindow *window, bool whatToDraw) { // glfwGetKey takes window and key as an input and checks is currently being pressed // if the user pressed escape we close window if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); if(glfwGetKey(window, GLFW_KEY_C) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); if(glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS || glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS ) return !whatToDraw; return whatToDraw; } int shaderProgramLinkingSuccessful(const unsigned int shaderProgram) { // check if compilation was successful // int because glGetShaderiv requires int int successfulLinking; // here we store info about compilation char infoLog[512]; // check if compilation was successful glGetProgramiv(shaderProgram, GL_LINK_STATUS, &successfulLinking); // if not display compilation log if(!successfulLinking) { glGetProgramInfoLog(shaderProgram, sizeof(infoLog) / sizeof(infoLog[0]), NULL, infoLog); std::cout << "ERROR shaderProgram compilation failed \n" << infoLog << std::endl; } return successfulLinking; } int shaderCompilationSuccessful(const unsigned int shader) { // check if compilation was successful // int because glGetShaderiv requires int int successfulCompilation; // here we store info about compilation char infoLog[512]; // check if compilation was successful glGetShaderiv(shader, GL_COMPILE_STATUS, &successfulCompilation); // if not display compilation log if(!successfulCompilation) { glGetShaderInfoLog(shader, sizeof(infoLog) / sizeof(infoLog[0]), NULL, infoLog); std::cout << "ERROR vertex shader compilation failed \n" << infoLog << std::endl; } return successfulCompilation; } unsigned int compileShader(const GLenum shaderType, const char * shaderSource) { // we create vertex shader and assign its id to shader variable unsigned int shaderID; shaderID = glCreateShader(shaderType); // attach shader source code to shader object // from left: shader object to compile, how many strings as source code, actual source code (we leave the 4th as NULL) glShaderSource(shaderID, 1, &shaderSource, NULL); // compile shader glCompileShader(shaderID); if(!shaderCompilationSuccessful(shaderID)) return 0; return shaderID; } template // https://stackoverflow.com/a/25680092 unsigned int copyVerticesMemory(const T vertices[], const size_t sizeOfVertices, const GLenum boundBufferTarget) { // stores vertices in gpu memory unsigned int vertexBufferObject; // this is open gl object so we refer it by its ID generated here and stored in vertexBufferObject variable glGenBuffers(1, &vertexBufferObject); // buffer type of vertex buffer object is GL_ARRAY_BUFFER glBindBuffer(boundBufferTarget, vertexBufferObject); // now whenever we change GL_ARRAY_BUFFER we change bound buffer vertexBufferObject /* we copy vertex data into buffer memory GL_STREAM_DRAW: the data is set only once and used by the GPU at most a few times. GL_STATIC_DRAW: the data is set only once and used many times. GL_DYNAMIC_DRAW: the data is changed a lot and used many times. */ glBufferData(boundBufferTarget, sizeOfVertices, vertices, GL_STATIC_DRAW); return vertexBufferObject; } std::pair compileShaders() { unsigned int vertexShader = compileShader(GL_VERTEX_SHADER, constants::vertexShaderSource); if(vertexShader == 0) { std::cout << "Vertex Shader Compilation Failed" << std::endl; return std::make_pair(0, 0); } unsigned int fragmentShader = compileShader(GL_FRAGMENT_SHADER, constants::fragmentShaderSource); if(fragmentShader == 0) { std::cout << "Fragment Shader Compilation Failed" << std::endl; return std::make_pair(0, 0); } return std::make_pair(vertexShader, fragmentShader); } unsigned int linkShaderObjectsShaderProgram(unsigned int vertexShaders, unsigned int fragmentShader) { // link shader objects into shader program // will store shader program id unsigned int shaderProgram; // creates program shaderProgram = glCreateProgram(); // attachShaders glAttachShader(shaderProgram, vertexShaders); glAttachShader(shaderProgram, fragmentShader); // link shaders glLinkProgram(shaderProgram); if(!shaderProgramLinkingSuccessful(shaderProgram)) return 0; // activate program // after that every shader and rendering call will use this program object glUseProgram(shaderProgram); // delete shaders (they are linked into shaderProgram and we do not need them anymore) glDeleteShader(vertexShaders); glDeleteShader(fragmentShader); if(shaderProgram == 0) std::cout << "Shader Program Linking Failed" << std::endl; return shaderProgram; } void configureVertexAttribute() { // specify how OGL interprets vertex data // From left: // which vertex attribute we configure (from shader source code layout (location = 0)) // size of vertex attribute (we use vec3 so it contains 3 values) // type of data of which vec consists of // should data be normalized, (useful when we use integer data) // space between vertex attributes, each position data is 3 times the size of float // offset of where position data begins in buffer // see: https://learnopengl.com/img/getting-started/vertex_attribute_pointer.png // vertex attribute data take data from memory managed by VBO bound to GL_ARRAY_BUFFER glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); // enable vertex attribute glEnableVertexAttribArray(0); } unsigned int generateBindVAO() { // vertex array object is used to draw objects by binding them to vao // generate vao unsigned int vertexArrayObject; glGenVertexArrays(1, &vertexArrayObject); // bind vao glBindVertexArray(vertexArrayObject); return vertexArrayObject; } void copyVerticesArray(unsigned int vertexBufferObject, const float vertices[], const size_t sizeOfVertices, const GLenum boundBufferTarget) { // copy vertices array in array useful for OGL glBindBuffer(boundBufferTarget, vertexBufferObject); glBufferData(boundBufferTarget, sizeOfVertices, vertices, GL_STATIC_DRAW); } void doDrawArrays(const unsigned int shaderProgram, const unsigned int vertexArrayObject, const GLenum drawArrayMode, const int firstIndex, const unsigned int numberOfIndicesToBeRendered ) { // use shader program to render an object glUseProgram(shaderProgram); glBindVertexArray(vertexArrayObject); // From left: // primitive type we want to draw // starting index of vertex array // how many vertices we want to draw glDrawArrays(drawArrayMode, firstIndex, numberOfIndicesToBeRendered); } int drawTriangle() { unsigned int vertexBufferObject = copyVerticesMemory(constants::TRIANGLE_VERTICES, constants::TRIANGLE_VERTICES_SIZE, GL_ARRAY_BUFFER); std::pair shaders = compileShaders(); if(shaders.first == 0 || shaders.second == 0) return -1; unsigned int shaderProgram = linkShaderObjectsShaderProgram(shaders.first, shaders.second); if(shaderProgram == 0) return -1; configureVertexAttribute(); unsigned int vertexArrayObject = generateBindVAO(); copyVerticesArray(vertexBufferObject, constants::TRIANGLE_VERTICES, constants::TRIANGLE_VERTICES_SIZE, GL_ARRAY_BUFFER); // set vertex attribute pointers glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); doDrawArrays(shaderProgram, vertexArrayObject, GL_TRIANGLES, 0, 3); return 0; } void doDrawElements(const unsigned int shaderProgram, const unsigned int vertexArrayObject, const GLenum drawArrayMode, const GLenum drawType, const int numberOfElementsToDraw) { glUseProgram(shaderProgram); glBindVertexArray(vertexArrayObject); glDrawElements(drawArrayMode, numberOfElementsToDraw, drawType, 0); glBindVertexArray(0); } int drawSquare() { std::pair shaders = compileShaders(); if(shaders.first == 0 || shaders.second == 0) return -1; unsigned int shaderProgram = linkShaderObjectsShaderProgram(shaders.first, shaders.second); if(shaderProgram == 0) return -1; unsigned int VAO = generateBindVAO(); copyVerticesMemory(constants::SQUARE_VERTICES, constants::SQUARE_VERTICES_SIZE, GL_ARRAY_BUFFER); copyVerticesMemory(constants::SQUARE_INDICES, constants::SQUARE_INDICES_SIZE, GL_ELEMENT_ARRAY_BUFFER); // set vertex attribute pointers glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); doDrawElements(shaderProgram, VAO, GL_TRIANGLES, GL_UNSIGNED_INT, std::size(constants::SQUARE_INDICES)); return 0; } int drawFigure(const bool whatToDraw) { if(whatToDraw) { if(drawTriangle() == -1) { std::cout << "Error with drawing triangle! " << std::endl; return -1; } }else { if(drawSquare() == -1) { std::cout << "Error with drawing square! " << std::endl; return -1; } } return 0; } void renderLoop(GLFWwindow* window) { bool whatToDraw = true; // glfwWindowShouldClose checks if GLFW was instructed to close while(!glfwWindowShouldClose(window)) { // input whatToDraw = processInput(window, whatToDraw); // We specify the color to clear the screen with // RGB and alpha value glClearColor( constants::LEARN_OPEN_GL_COLOR.red, constants::LEARN_OPEN_GL_COLOR.green, constants::LEARN_OPEN_GL_COLOR.blue, constants::LEARN_OPEN_GL_COLOR.alpha); // There is GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT and GL_STENCIL_BUFFER_BIT glClear(GL_COLOR_BUFFER_BIT); if(drawFigure(whatToDraw) == -1) { std::cout << "error with drawing!" << std::endl; glfwSetWindowShouldClose(window, true); }; // swaps buffer containing color values of each pixel in window // there is front buffer (final image) and back buffer (where all rendering commands draw to) // when back buffer is ready we swap it with front buffer to eliminate flickering glfwSwapBuffers(window); // glfwPollEvents checks if any event (like mouse/keyboard input was triggered), updates window state and calls functions (which we register via callback methods) glfwPollEvents(); } } int main() { instantiateGLFWwindow(); GLFWwindow* window = createWindowObject(); // function returns GLFWWindow object if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } // we make context of this window main context of current thread glfwMakeContextCurrent(window); if(initializeGLAD() == -1) return -1; viewPort(window); renderLoop(window); // clean GLFW resources glfwTerminate(); return 0; } #endif