0
\$\begingroup\$

I am trying to use the same vertex shader for two different use cases to preserve memory, but I am having issues where the modifications of one shader run are passed on to the next run, even after being reset. Specifically, I am trying to use the shader to both show a regular texture, while also modifying the vertex attributes in some cases to show portions of a texture. The problem is, this "cropping" of the texture is applied to the texture that should be shown 100%.

Here is what it looks like, where the sprite on the right is drawn first: Application screenshot

Both textures are showing only a portion of their actual images, but only the one on the left should be doing so. Strangely, the one on the right shows a full texture for a second, before being replaced with what is shown. This tells me that the vertex attributes of the first sprite are modified by the second (left) sprite, but I don't know how to reset them. Here is my vertex shader:

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

out vec2 TexCoord;

uniform mat4 transform;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * transform * vec4(aPos, 1.0);
    TexCoord = aTexCoord;
}

Here is how I modify the vertex attributes:

void setSelection(Quad selection) {
        vertices[3] = selection.x + selection.w;
        vertices[4] = selection.y + selection.h;
        vertices[8] = selection.x + selection.w;
        vertices[9] = selection.y;
        vertices[13] = selection.x;
        vertices[14] = selection.y;
        vertices[18] = selection.x;
        vertices[19] = selection.y + selection.h;
};
...
float vertices[20] = {
          // positions         // texture coords
          0.5f,  0.5f, 0.0f,   1.0f, 1.0f, // top right
          0.5f, -0.5f, 0.0f,   1.0f, 0.0f, // bottom right
         -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, // bottom left
         -0.5f,  0.5f, 0.0f,   0.0f, 1.0f  // top left
};

This code changes the shown vertex array's texture coordinates. They are applied with this code:

void Sprite::render() {
    // Set shader uniforms if the shader is initialized
    if (shader != nullptr) {
        applyTransforms();
        shader->useShader();
        shader->setMat4("transform", trans);
        shader->setVec4("color", color.asVec4());
        shader->setMat4("view", Camera::getLookMat());
    }

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    tex->bind();

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}

However, if I have two objects (both instances of the same class), one with a modified array (left) and one with an unmodified array (right, as shown in the picture), the modified array takes precedence, and I can't figure out how to keep it from being changed without using a different shader object, but I don't want to do that. Is there anything I am missing that would allow me to change the attributes in between draw cycles?

ADDITION 1 Here is the entire Sprite class (right image is an object):

Sprite::Sprite(std::shared_ptr<Texture> texture, std::shared_ptr<Shader> shader) : tex(texture), color(1u, 1u, 1u, 1u), shader(shader) {
    glInit();
}

Sprite::Sprite(const std::string& path, std::shared_ptr<Shader> shader) : tex(new Texture(path)), color(1u, 1u, 1u, 1u), shader(shader) {
    glInit();
}

Sprite::~Sprite() {
    glDeleteVertexArrays(1, &vao);
    vao = NULL;
    glDeleteBuffers(1, &vbo);
    vbo = NULL;
    glDeleteBuffers(1, &ebo);
    ebo = NULL;
}

void Sprite::setColor(RGBA color) {
    this->color = color;
}

void Sprite::render() {
    // Set shader uniforms if the shader is initialized
    if (shader != nullptr) {
        applyTransforms();
        shader->useShader();
        shader->setMat4("transform", trans);
        shader->setVec4("color", color.asVec4());
        shader->setMat4("view", Camera::getLookMat());
    }

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    tex->bind();

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}

void Sprite::glInit() {
    // GL initialization
    // Buffers
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indicies), indicies, GL_STATIC_DRAW);

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // Verticies
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * FLOAT_SIZE, (void*)0);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * FLOAT_SIZE, (void*)(3 * FLOAT_SIZE));
    glEnableVertexAttribArray(1);
}

Here's the header:

class Sprite : public Object{
public:
    Sprite(std::shared_ptr<Texture> texture, std::shared_ptr<Shader> shader = nullptr);

    Sprite(const std::string& path, std::shared_ptr<Shader> shader = nullptr);

    ~Sprite();

    void setColor(RGBA color);

    inline RGBA getColor() const { return color; };

    virtual void render();

    inline std::weak_ptr<Texture> getTexture() const { return tex; };

protected:
    // Pointers for texture and shader (if initialized)
    std::shared_ptr<Texture> tex;
    std::shared_ptr<Shader> shader;

    GLuint vbo = 0, vao = 0, ebo = 0;

    RGBA color;

    float vertices[20] = {
          // positions         // texture coords
          0.5f,  0.5f, 0.0f,   1.0f, 1.0f, // top right
          0.5f, -0.5f, 0.0f,   1.0f, 0.0f, // bottom right
         -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, // bottom left
         -0.5f,  0.5f, 0.0f,   0.0f, 1.0f  // top left
    };

    GLuint indicies[6] = {
        0, 1, 3,
        1, 2, 3
    };

    // Initialize GL objects
    virtual void glInit();
};

And here's the subclass (left image, header only):

class SpriteSheet : public Sprite {
public:
    SpriteSheet(std::shared_ptr<Texture> texture, std::shared_ptr<Shader> shader = nullptr) : Sprite(texture, shader) {};

    SpriteSheet(const std::string& path, std::shared_ptr<Shader> shader = nullptr) : Sprite(path, shader) {};

    ~SpriteSheet() { Sprite::~Sprite(); };

    void setSelection(Quad selection) {
        vertices[3] = selection.x + selection.w;
        vertices[4] = selection.y + selection.h;
        vertices[8] = selection.x + selection.w;
        vertices[9] = selection.y;
        vertices[13] = selection.x;
        vertices[14] = selection.y;
        vertices[18] = selection.x;
        vertices[19] = selection.y + selection.h;
    };
};

The Object class only contains positional vectors, and is therefore not relevant. RGBA and Quad are abstractions of glm::vec4.

EDIT 2 I have figured out that if two SpriteSheet objects are declared, the one declared last will be the one that takes priority with its texture coordinates being applied to all objects, regardless of render order.

\$\endgroup\$
6
  • \$\begingroup\$ "I am trying to use the same vertex shader for two different use cases to preserve memory"- even though you probably have an underlying problem (in code you've not yet shown us), are you aware that you're not even saving 1 kilobyte of memory here; this is a bad case of micro-optimization. \$\endgroup\$ Mar 23 '20 at 9:46
  • \$\begingroup\$ Just to confirm, setSelection is a class method and vertices are a member of the same class? So you are not modifying the same global array via a free function? \$\endgroup\$
    – Tyyppi_77
    Mar 23 '20 at 9:48
  • \$\begingroup\$ @Tyyppi_77 vertices is actually a member of the Sprite class (same as the render method), whereas setSelection is actually a member of a subclass of Sprite. All functions shown are class functions. \$\endgroup\$
    – ImTheSquid
    Mar 23 '20 at 15:40
  • \$\begingroup\$ @MaximusMinimus I understand where you are coming from, but another reason is that I don't want to have to iterate over every single object that has a copy of a shader object when I want to change a uniform. I will edit the question to show the whole class. \$\endgroup\$
    – ImTheSquid
    Mar 23 '20 at 15:41
  • \$\begingroup\$ I may not have understood the whole magnitude. A uniform is uniform during a draw call. To use a single draw call for multiple objects i'd assemble the buffer(s) so that it contains the vertices and indices in the order i need them. Have you considered primitive restart, or instancing and writing per object information for example into a texture or a different buffer for look up via the instance id ? Or use multiple draw calls for the time being. \$\endgroup\$
    – user136710
    Mar 23 '20 at 18:57
0
\$\begingroup\$

It turns out that before modifying vertex attributes (VBOs), I had to rebind the VAO of the created object. The modified render code looks like this:

void Sprite::render() {
    // Set shader uniforms if the shader is initialized
    if (shader != nullptr) {
        applyTransforms();
        shader->useShader();
        shader->setMat4("transform", trans);
        shader->setVec4("color", color.asVec4());
    }
    glBindVertexArray(vao);
    // ^ This is very important

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    tex->bind();

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.