Memory Leak C++ + Ubuntu 20.04

Hello everyone.

Hope you coulod help me. I’m newbie to GLFW and the code in C++ attached below has a severe memory leak detected by valgrind. This is part of a larger project, bue I isolated the GLFW part and it seems the problem is here:

/*
# Released under MIT License

Copyright (c) 2017 insaneyilin.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#include <iostream>
#include <string>
#include <vector>

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include <opencv2/opencv.hpp>

#include "glwindow.hpp"

using std::cout;
using std::endl;

volatile int window_width = 640;
volatile int window_height = 480;
volatile bool omit_windows;

// List and names of the windows created by the user
std::vector<GLFWwindow*> windowList; // Pointers to the windows
std::vector<std::string> windowName; // Names of the windows

// HHAA
void glfwSetWidthHeight(unsigned int w, unsigned int h){

  window_width = w;
  window_height = h;

}

// HHAA
bool glfwWindowsDie(void){
 
   // Consider the first window only
   if (windowList.size() > 0 && glfwWindowShouldClose(windowList[0])){
      return true;
   }
        
 return false;         

}


// HHAA
void glfwDestroyWindows(void){ 

    for (int i = 0; i < windowList.size(); i++){
        glfwDestroyWindow(windowList[i]);
    }

    windowList.clear();
    windowName.clear();

    glfwTerminate();   

}

// HHAA
GLFWwindow* getWindow(const std::string winName){

    for (int i = 0; i < windowName.size(); i++){
        if (windowName[i].compare(winName) == 0){
           return windowList[i];
        }
    }   
    
  return NULL; 

}

// HHAA
bool glfw_Init(bool ow){

   omit_windows = ow;
   if (omit_windows){
      cout << "Omit windows was set to true " << endl;
      return true;
   }   
  
   glfwSetErrorCallback(error_callback);

   if (!glfwInit()) {
       exit(EXIT_FAILURE);
   }

   glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
   glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); 
   
 return true;  

}


// HHAA
bool glfwNamedWindow(const std::string winName, int flags){

 static GLenum err;
 
   if (omit_windows){
      return true;
   }
   
   GLFWwindow* window = glfwCreateWindow(window_width, 
                          window_height, winName.c_str(), NULL, NULL); 
   if (window == NULL) {
        glfwTerminate();
        cout << "Error creating the window " << winName << glewGetErrorString(err) << endl;
        std::cout.flush();
        exit(EXIT_FAILURE);
   }
   
   windowList.push_back(window);
   windowName.push_back(winName); 

   glfwSetKeyCallback(windowList[windowList.size() - 1], key_callback);
   //glfwSetWindowSizeCallback(windowList[windowList.size() - 1], resize_callback);

   glfwMakeContextCurrent(windowList[windowList.size() - 1]);
   glfwSwapInterval(1);

   // First time
   if (windowList.size() == 1){

      //  Initialise glew (must occur AFTER window creation or glew will error)
      err = glewInit();
      if (GLEW_OK != err){
         cout << "GLEW initialisation error: " << glewGetErrorString(err) << endl;
         std::cout.flush();    
         exit(-1);
      }
      cout << "GLEW okay - using version: " << glewGetString(GLEW_VERSION) << endl;
            
   } 

      glViewport(0, 0, window_width, window_height); // use a screen size of WIDTH x HEIGHT

      glMatrixMode(GL_PROJECTION);     // Make a simple 2D projection on the entire window
      glLoadIdentity();
      glOrtho(0.0, window_width, window_height, 0.0, 0.0, 100.0);

      glMatrixMode(GL_MODELVIEW);    // Set the matrix mode to object modeling

      glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
      glClearDepth(0.0f);
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the window    
       
  return true;   

}


// Function turn a cv::Mat into a texture, and return the texture ID as a GLuint for use
GLuint matToTexture(const cv::Mat &mat, GLenum minFilter, GLenum magFilter, GLenum wrapFilter) {
    // Generate a number for our textureID's unique handle
    GLuint textureID;
    glGenTextures(1, &textureID);

    // Bind to our texture handle
    glBindTexture(GL_TEXTURE_2D, textureID);

    // Catch silly-mistake texture interpolation method for magnification
    if (magFilter == GL_LINEAR_MIPMAP_LINEAR  ||
            magFilter == GL_LINEAR_MIPMAP_NEAREST ||
            magFilter == GL_NEAREST_MIPMAP_LINEAR ||
            magFilter == GL_NEAREST_MIPMAP_NEAREST)
    {
        cout << "You can't use MIPMAPs for magnification - setting filter to GL_LINEAR" << endl;
        std::cout.flush();
        magFilter = GL_LINEAR;
    }

    // Set texture interpolation methods for minification and magnification
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);

    // Set texture clamping method
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapFilter);

    // Set incoming texture format to:
    // GL_BGR       for CV_CAP_OPENNI_BGR_IMAGE,
    // GL_LUMINANCE for CV_CAP_OPENNI_DISPARITY_MAP,
    // Work out other mappings as required ( there's a list in comments in main() )
    GLenum inputColourFormat = GL_BGR;
    if (mat.channels() == 1)
    {
        inputColourFormat = GL_LUMINANCE;
    }

    // Create the texture
    glTexImage2D(GL_TEXTURE_2D,     // Type of texture
                 0,                 // Pyramid level (for mip-mapping) - 0 is the top level
                 GL_RGB,            // Internal colour format to convert to
                 mat.cols,          // Image width  i.e. 640 for Kinect in standard mode
                 mat.rows,          // Image height i.e. 480 for Kinect in standard mode
                 0,                 // Border width in pixels (can either be 1 or 0)
                 inputColourFormat, // Input image format (i.e. GL_RGB, GL_RGBA, GL_BGR etc.)
                 GL_UNSIGNED_BYTE,  // Image data type
                 mat.ptr());        // The actual image data itself

    // If we're using mipmaps then generate them. Note: This requires OpenGL 3.0 or higher
    if (minFilter == GL_LINEAR_MIPMAP_LINEAR  ||
            minFilter == GL_LINEAR_MIPMAP_NEAREST ||
            minFilter == GL_NEAREST_MIPMAP_LINEAR ||
            minFilter == GL_NEAREST_MIPMAP_NEAREST)
    {
        glGenerateMipmap(GL_TEXTURE_2D);
    }

    return textureID;
}

void error_callback(int error, const char* description) {
    fprintf(stderr, "Error: %s\n", description);
}


void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, GLFW_TRUE);
    }
}


void resize_callback(GLFWwindow* window, int new_width, int new_height) {
    glViewport(0, 0, window_width = new_width, window_height = new_height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, window_width, window_height, 0.0, 0.0, 100.0);
    glMatrixMode(GL_MODELVIEW);
}

// HHAA
bool glfwImageShow(const std::string winName, const cv::Mat& frame) {

    if (omit_windows)
       return true;
    
    GLFWwindow* window = getWindow(winName);
    if (window == NULL){    
        cout << "Error: No window with the name " << winName << endl;
        std::cout.flush(); 
        return false;
    }
    
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);

    // Clear color and depth buffers
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);     // Operate on model-view matrix

    glEnable(GL_TEXTURE_2D);
    GLuint image_tex = matToTexture(frame, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP);

    /* Draw a quad */
    glBegin(GL_QUADS);
    glTexCoord2i(0, 0); glVertex2i(0, 0);
    glTexCoord2i(0, 1); glVertex2i(0, window_height);
    glTexCoord2i(1, 1); glVertex2i(window_width, window_height);
    glTexCoord2i(1, 0); glVertex2i(window_width, 0);
    glEnd();

    glDeleteTextures(1, &image_tex);
    glDisable(GL_TEXTURE_2D);
    
    glfwSwapBuffers(window);
    glfwPollEvents();
    
  return true;  
    
}

Functions are called in the following order:
glfw_Init(omit_windows);
glfwSetWidthHeight(width, height); 

// four windows opened
glfwNamedWindow("Copy RGB video", cv::WINDOW_AUTOSIZE); 

// This function is called several times:
glfwImageShow("Copy RGB video", img);

Assuming you’re destroy window and terminating glfw afterwards, there’s no memory leaks here.

Just be aware the GPU drivers (libraries outside glfw control) like to allocate some global memory and keep it allocated even when GL/Vulkan context is destroyed. They have some globals/statics that are never released. So if valgrind shows memory coming from them, this is expected.

Also remember that clear() method on std::vector may not release memory, it keeps it for next push_back’s. shrink_to_fit() should actually release it, when called after clear(). But this has nothing to do with GLFW.

Dear Mārtiņš,

thank you for your kind response. I isolated and tested the code (ie, without any other memory assignment) and that’s how I found the leak. Surprisingly, I called glfwSetErrorCallback(error_callback) after glfwInit() and things went fairly better. Don’t know why. However, I will follow your advice l will release memory appropriately as you suggest.

Thank your very much again.

Cheers.