Dextron
Dextron

Reputation: 628

Efficient function to draw textured squares OpenGL

I am following this tutorial: https://learnopengl.com/Getting-started/Textures to create a function where I can call it in my glfw loop in the main function to display multiple squares with different textures by calling this function per square to create.

Draw.h

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <stb_image.h>
#include <shader_s.h>
#include <iostream>


int width = 860;
int height = 640;




class Draw {
public:

    int drawErr = 0;


    void Square(int x, int y, int z, int w, int h, const char* file, bool usingTexture, bool flipImage){
        Shader ourShader("vertex.vs", "fragment.fs");
        if (!usingTexture) {
            //draw polygon with no texture
        }
        else {
            //shouldnt be in loop to draw
            float vertices[] = {
                //position            //color             //texCoords

                x+w,y,z, 0.0f, 0.0f, 0.0f,  1.0f, 1.0f, //top right
                x+w,y+h,z, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,//bottom right
                x,y+h,z, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, //bottom left
                x,y,z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f//top left
            };
            unsigned int indices[] = {
                0, 1, 3,
                1, 2, 3
            };
            unsigned int VAO, VBO, EBO;
            glGenVertexArrays(1, &VAO);
            glGenBuffers(1, &VBO);
            glGenBuffers(1, &EBO);
            glBindVertexArray(VAO);
            glBindBuffer(GL_ARRAY_BUFFER, VBO);
            glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
            //position
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
            glEnableVertexAttribArray(0);
            //color
            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
            glEnableVertexAttribArray(1);
            //texture
            glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
            glEnableVertexAttribArray(2);
            //load Texture
            stbi_set_flip_vertically_on_load(flipImage);
            unsigned int texture;
            glGenTextures(1, &texture);
            glBindTexture(GL_TEXTURE_2D, texture);
            int width, height, nrChannels;
            unsigned char* data = stbi_load(file, &width, &height, &nrChannels, 0);
            if (data) {
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
                glGenerateMipmap(GL_TEXTURE_2D);
            }
            else {
                std::cout << "Failed to load texture!" << std::endl;
                glfwTerminate();
                drawErr = -1;
            }
            stbi_image_free(data);

            //should be in loop to draw
            ourShader.use();
            glBindVertexArray(VAO);
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);


        }
    }

//other drawing functions here
};

Shader.h

#ifndef SHADER_H
#define SHADER_H

#include <gl/glew.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

class Shader
{
public:

    unsigned int ID;
    // constructor generates the shader on the fly
    // ------------------------------------------------------------------------
    Shader(const char* vertexPath, const char* fragmentPath)
    {
        // 1. retrieve the vertex/fragment source code from filePath
        std::string vertexCode;
        std::string fragmentCode;
        std::ifstream vShaderFile;
        std::ifstream fShaderFile;
        // ensure ifstream objects can throw exceptions:
        vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        try
        {
            // open files
            vShaderFile.open(vertexPath);
            fShaderFile.open(fragmentPath);
            std::stringstream vShaderStream, fShaderStream;
            // read file's buffer contents into streams
            vShaderStream << vShaderFile.rdbuf();
            fShaderStream << fShaderFile.rdbuf();
            // close file handlers
            vShaderFile.close();
            fShaderFile.close();
            // convert stream into string
            vertexCode = vShaderStream.str();
            fragmentCode = fShaderStream.str();
        }
        catch (std::ifstream::failure e)
        {
            std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
        }
        const char* vShaderCode = vertexCode.c_str();
        const char* fShaderCode = fragmentCode.c_str();
        // 2. compile shaders
        unsigned int vertex, fragment;
        // vertex shader
        vertex = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex, 1, &vShaderCode, NULL);
        glCompileShader(vertex);
        checkCompileErrors(vertex, "VERTEX");
        // fragment Shader
        fragment = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment, 1, &fShaderCode, NULL);
        glCompileShader(fragment);
        checkCompileErrors(fragment, "FRAGMENT");
        // shader Program
        ID = glCreateProgram();
        glAttachShader(ID, vertex);
        glAttachShader(ID, fragment);
        glLinkProgram(ID);
        checkCompileErrors(ID, "PROGRAM");
        // delete the shaders as they're linked into our program now and no longer necessary
        glDeleteShader(vertex);
        glDeleteShader(fragment);
    }
    // activate the shader
    // ------------------------------------------------------------------------
    void use()
    {
        glUseProgram(ID);
    }
    // utility uniform functions
    // ------------------------------------------------------------------------
    void setBool(const std::string& name, bool value) const
    {
        glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
    }
    // ------------------------------------------------------------------------
    void setInt(const std::string& name, int value) const
    {
        glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
    }
    // ------------------------------------------------------------------------
    void setFloat(const std::string& name, float value) const
    {
        glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
    }

private:
    // utility function for checking shader compilation/linking errors.
    // ------------------------------------------------------------------------
    void checkCompileErrors(unsigned int shader, std::string type)
    {
        int success;
        char infoLog[1024];
        if (type != "PROGRAM")
        {
            glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
            if (!success)
            {
                glGetShaderInfoLog(shader, 1024, NULL, infoLog);
                std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        }
        else
        {
            glGetProgramiv(shader, GL_LINK_STATUS, &success);
            if (!success)
            {
                glGetProgramInfoLog(shader, 1024, NULL, infoLog);
                std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        }
    }
};
#endif

Main function:

int main() {

    Draw draw;

    if (!glfwInit()) {
        std::cout << "failed to load GLFW" << std::endl;
        return -1;
    }
    GLFWwindow* window = glfwCreateWindow(width, height, "Electrocraft", NULL, NULL);
    glfwMakeContextCurrent(window);
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        std::cerr << "Error: " << glewGetErrorString(err) << std::endl;
    }
    std::cerr << "Status: Using GLEW " << glewGetString(GLEW_VERSION) << std::endl;




    while (!glfwWindowShouldClose(window)) {
        glClearColor(68.0f / 255.0f, 85.0f / 255.0f, 255.0f / 255.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        draw.Polygon(-1.0f, 1.0f, 0.0f, 2.0f, 2.0f, "Library\\Textures\\blocks\\dirt.bmp", true, false);

        glfwSwapBuffers(window);
        glfwPollEvents();

    }
}

Vertex.vs

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 ourColor;
out vec2 TexCoord;

void main(){
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
    }

Fragment.vs

#version 330 core
out vec4 fragColor;

in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;

void main(){
    fragColor = texture(ourTexture, TexCoord);
    }

The issue with my program is that it constantly reeloads the texture and rebinds it using a lot of memory to do so. I have tried to do it this way so I can call the square function and draw a multiple squares with different textures at the same time. I cannot figure out a way to reduce the memory and using the function to have multiple squares.

Upvotes: 0

Views: 508

Answers (2)

You want to create the shader once (not every time you draw a square!) and you want to load the texture once (not every time you draw a square!)

One very simple way to do this would be to make the OpenGL texture a global variable:

// put this outside of any function, and delete it from Square

unsigned int texture;



// put this in main and delete it from Square

stbi_set_flip_vertically_on_load(flipImage);
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
int width, height, nrChannels;
unsigned char* data = stbi_load(file, &width, &height, &nrChannels, 0);
if (data) {
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
}
else {
    std::cout << "Failed to load texture!" << std::endl;
    glfwTerminate();
    drawErr = -1;
}
stbi_image_free(data);



// put this in Square

glBindTexture(GL_TEXTURE_2D, texture);

This is the simplest way to load the texture once.

But then how can you have more than one texture?

Well, you could have more than one global variable (or put them in the Draw class if you want. It doesn't really matter for this program). dirtTexture, snowTexture, grassTexture. If you do this, you should take the code that I told you to put in main, and delete it from main, and create a loadTexture function which returns the texture ID. Then you can use dirtTexture = loadTexture("dirt.jpg"); snowTexture = loadTexture("snow.jpg"); and so on.

You have the same mistake for your shader, vertex array and buffer. For the shader, you can't make it a global variable, because then it will try to create the shader when the program starts up, before main runs, before glfwInitialize is called and then it will crash because you didn't initialize OpenGL yet. If you make it so the constructor doesn't load the shader, and add a function like LoadShader which loads the shader, then you can make it a global variable.

For the vertex array, same thing. Create it in main. Store the ID in a global variable. Bind the same vertex array over and over.

For the vertex buffer, same thing. But note that you do need to set new data every Square is called, because the squares are different. You can't really get around that. But you don't need to create a new vertex buffer every time Square is called, you only need to put new data into the same buffer. It shouldn't be very slow.

Upvotes: 1

Botje
Botje

Reputation: 31010

A simple improvement is to split your draw::Polygon method in two parts:

  • one generic function that loads a texture from disk and returns a Texture object that wraps the OpenGL texture ID
  • rewrite Polygon to accept such a Texture object.

See for example the sf::Texture class from SFML and how it combines with the sf::Shape class.

Upvotes: 1

Related Questions