vtkWin32OpenGLRenderWindow resize (memory leak)?

I am playing with the Win32Cone example because I need to implement a Win32 application:


I wanted to add resizing to the window so I simply added in the WndProc function the handling for the WM_SIZE message:
case WM_SIZE:
{
  UINT width = LOWORD(lParam);
  UINT height = HIWORD(lParam);
  theVTKApp->OnResize(width, height);
  return 0;
}

and added the OnResize function to myVTKApp class:

void myVTKApp::OnResize(unsigned int width, unsigned int height)
{
this->renWin->SetSize(width, height);
}

It simply assign a new size to the vtkRenderWindow.
Resizing then works as expected, but each time I resize the window the process memory increases by an incredibly huge amount. On my machine the process starts at 33.528 K (Private memory) and after a few resizing It reaches a few GB and eventually it will consume all the available memory and break.
Since I need to implement the resize behavior for the application I am working on, can anyone explain to me what is wrong here?

Here is what I see using a profiler. It seems that while resizing the window there is a huge allocation of strings. This is the stack for a string allocation:


Is there some kind of logging going on (vtkTimerLog)? It seems that this memory is never freed, but I could be wrong and maybe this is correct.

Hello, friend,

My approach to those kind of mysterious memory leaks, especially with low level calls like that, is to reduce the program to a trivial case (e.g. empty void main()), build, run, observe, add a bit of code, build, run, observe, etc. Until you trigger the misbehavior. Either that or use something like Valgrind’s Memcheck (https://valgrind.org/docs/manual/mc-manual.html). I think a profiler won’t help you much to pinpoint the memory leak.

take care,

Paulo

Thanks Paulo, I fully understand your approach to solve this type of problem. I did not write that the first time that I encountered the problem it was in a much more complex solution involving communication between gRPC processes etc. However as you suggested, I reduced even more the code from the VTK-src\Examples\GUI\Win32\SimpleCxx to this:

myVTKApp::myVTKApp(HWND hwnd)
{
// Similar to Examples/Tutorial/Step1/Cxx/Cone.cxx
// We create the basic parts of a pipeline and connect them
this->renderer = vtkRenderer::New();
this->renWin = vtkRenderWindow::New();
this->renWin->AddRenderer(this->renderer);

// setup the parent window
this->renWin->SetParentId(hwnd);

// Finally we start the interactor so that event will be handled
this->renWin->Render(); // this line triggers the memory leak. Without it there is no memory leak while resizing
}

The other modifications I did to the SimpleCxx is the code for resizing:

void myVTKApp::OnResize(unsigned int width, unsigned int height)
{
this->renWin->SetSize(width, height);
}

with the handling for message WM_SIZE in WndProc:

case WM_SIZE:
{
  UINT width = LOWORD(lParam);
  UINT height = HIWORD(lParam);
  theVTKApp->OnResize(width, height);
  return 0;
}

I guess the resizing code must be wrong, I am looking into it. Or maybe something in my VTK build or configuration. I forgot to mention that I am using mesa 3D too.
I am on a Windows 10 x64 machine, If I am not wrong Valgrind works on Linux only.

Using the Visual Studio CRT memory leak detection utility:


I detected those memory leaks after just one resize and a GB allocation:
memDump.txt (174.2 KB)

With the profiler I see that most of allocation comes from vtkTextureObject::Resize() function:

One more info: an application made with phyton and vtk (using mesa 3D) behaves correctly on my machine: the resize of the rendering window does not cause any memory leak. So I exclude a problem with my GPU driver

I suspect that a redraw event (likely followed by a VTK render) is being triggered for each mouse move. The VTK rendering routine is possibly not designed for that. I suggest you to take a look at this: https://stackoverflow.com/questions/2165759/how-do-i-force-windows-not-to-redraw-anything-in-my-dialog-when-the-user-is-resi . The point is to make sure there is just one VTK render only when the user finishes the resize, not while the window frame is being resized (e.g. during the mouse drag).

Hi Paulo, yes it definitely calls render while moving the mouse. I now have used the WM_EXITSIZEMOVE to resize only at the end of the mouse move. It partially solves the problem. Memory still gets consumed and with patience one could still break the program by resizing many times. For now I will use it this way. I wonder how the python vtk avoids this problem. I see that the python application does not leak any memory.

Python interface is a very high level API, so certainly there are lots of intemediary layers and libraries handling the gory details of lower level calls to the graphics system and the SO for you.

Your program is interfacing directly to Windows API, at the bottom end of the stack, so it is certainly missing a call, return value or message dispatch to trigger the de-allocation in VTK. Since I quit Windows API two decades ago (now I develop UIs with Qt), I don’t know the protocol Windows expects today.

Anyway, I suspect myVTKApp is being instantiated multiple times. Have you tried putting a printf() or something like this in the constructor? Can you show the part of your code where new myVTKApp(hWnd) is?

I see. You can check the code in my first message. It’s the Simple CX file taken from the VTK UI/Win32 example folder from the VTK installation. I just added the resize message handling as I described (eventually I called the UpdateSize function instead of SetSize but nothing changed). I can exclude that the constructor is called more than once. I have already debugged the program many times step by step. I also checked the MFC and python implementation from the VTK installation folders to get a clue. Made a few experiments still without success.
I’ve developed mainly for Windows, but GUI -wise mostly C# .Net. with native C++ for algorithms libraries or hardware interface

EDIT: here is the modified WndProc I use in the Win32Cone.Cxx:

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
      static HWND ewin;
      static myVTKApp* theVTKApp;
      static UINT width;
      static UINT height;

      switch (message)
      {
        case WM_CREATE:
        {
          ewin = CreateWindow("button", "Exit", WS_CHILD | WS_VISIBLE | SS_CENTER, 0, 400, 400, 60,
            hwnd, (HMENU)2, (HINSTANCE)vtkGetWindowLong(hwnd, vtkGWL_HINSTANCE), nullptr);
          theVTKApp = new myVTKApp(hwnd);
          return 0;
        }

        case WM_COMMAND:
          switch (wParam)
          {
            case 2:
              PostQuitMessage(0);
              delete theVTKApp;
              theVTKApp = nullptr;
              break;
          }
          return 0;

        case WM_DESTROY:
          PostQuitMessage(0);
          delete theVTKApp;
          theVTKApp = nullptr;
          return 0;

        case WM_SIZE:
        {
          width = LOWORD(lParam);
          height = HIWORD(lParam);
          return 0;
        }

        case WM_EXITSIZEMOVE:
        {
          theVTKApp->OnResize(width, height);
          return 0;
        }
      }
      return DefWindowProc(hwnd, message, wParam, lParam);
    }

With the profiler, debugging the VTK library I noticed that most of the allocation while resizing comes from the method vtkTextureObject::Resize(unsigned int width, unsigned int height) when the glTexImage2DMultisample is called:

void vtkTextureObject::Resize(unsigned int width, unsigned int height)
{
  if (this->Width == width && this->Height == height)
  {
    return;
  }

  this->Width = width;
  this->Height = height;

  this->Context->ActivateTexture(this);
  this->Bind();

  if (this->NumberOfDimensions == 2)
  {
#ifdef GL_TEXTURE_2D_MULTISAMPLE
    if (this->Samples)
    {
      glTexImage2DMultisample(this->Target, this->Samples, static_cast<GLint> this->InternalFormat), static_cast<GLsizei>(this->Width), static_cast<GLsizei>(this->Height), GL_TRUE);
    }
    else
#endif
    {
      glTexImage2D(this->Target, 0, static_cast<GLint>(this->InternalFormat),
        static_cast<GLsizei>(this->Width), static_cast<GLsizei>(this->Height), 0, this->Format,
        this->Type, nullptr);
    }
  }
  else if (this->NumberOfDimensions == 3)
  {
    glTexImage3D(this->Target, 0, static_cast<GLint>(this->InternalFormat),
      static_cast<GLsizei>(this->Width), static_cast<GLsizei>(this->Height),
      static_cast<GLsizei>(this->Depth), 0, this->Format, this->Type, nullptr);
  }
#ifdef GL_TEXTURE_1D
  else if (this->NumberOfDimensions == 1)
  {
    glTexImage1D(this->Target, 0, static_cast<GLint>(this->InternalFormat),
      static_cast<GLsizei>(this->Width), 0, this->Format, this->Type, nullptr);
  }
#endif

  vtkOpenGLCheckErrorMacro("failed at texture resize");
  this->Deactivate();
}

In fact if I configure the vtkRenderWindow with SetMultiSamples(0) the memory leak disappears. This is not probably a solution given that the drawing degrades.

I suggest you to try the vtkDebugLeaks class: https://vtk.org/doc/nightly/html/classvtkDebugLeaks.html .

It requires rebuilding VTK, enabling it in its configuration: https://visit-sphinx-github-user-manual.readthedocs.io/en/develop/dev_manual/MemoryLeaks.html#building-visit-for-valgrind-and-vtkdebugleaks

You can also try FXAA for antialiasing instead of MSAA. Maybe you can workaround the leak without compromising the aesthetics of your renderings. FXAA is also faster than MSAA, though some people don’t like its results.

Thank you Paulo,
I enabled the vtkDebugLeaks did some resizing and got this dump:

Class "vtkMinimalStandardRandomSequence" has 1 instance still around.
Class "vtkBoxMuellerRandomSequence" has 1 instance still around.
Class "vtkCellArray" has 1 instance still around.
Class "vtkPoints" has 4 instances still around.
Class "vtkPropCollection" has 5 instances still around.
Class "vtkMathInternal" has 1 instance still around.
Class "vtkOpenGLShaderCache" has 1 instance still around.
Class "vtkWorldPointPicker" has 1 instance still around.
Class "vtkInteractionStyleObjectFactory" has 1 instance still around.
Class "vtkIdTypeArray" has 1 instance still around.
Class "vtkInformation" has 99 instances still around.
Class "vtkTypeInt64Array" has 2 instances still around.
Class "vtkCollection" has 2 instances still around.
Class "vtkCommand or subclass" has 19 instances still around.
Class "vtkOpenGLRenderTimerLog" has 1 instance still around.
Class "class vtkBuffer<__int64>" has 3 instances still around.
Class "vtkUnsignedCharArray" has 2 instances still around.
Class "vtkObjectFactoryCollection" has 1 instance still around.
Class "vtkIdList" has 5 instances still around.
Class "vtkStereoCompositor" has 1 instance still around.
Class "vtkRenderingUIObjectFactory" has 1 instance still around.
Class "vtkRenderingFreeTypeObjectFactory" has 1 instance still around.
Class "vtkRenderingOpenGL2ObjectFactory" has 1 instance still around.
Class "vtkRendererCollection" has 1 instance still around.
Class "vtkActor2DCollection" has 1 instance still around.
Class "vtkLightCollection" has 1 instance still around.
Class "vtkOpenGLVertexArrayObject" has 42 instances still around.
Class "vtkActorCollection" has 3 instances still around.
Class "vtkVolumeCollection" has 1 instance still around.
Class "class vtkBuffer<double>" has 4 instances still around.
Class "vtkCullerCollection" has 1 instance still around.
Class "vtkOpenGLVertexBufferObjectGroup" has 6 instances still around.
Class "vtkGenericCell" has 2 instances still around.
Class "vtkEmptyCell" has 2 instances still around.
Class "vtkFrustumCoverageCuller" has 1 instance still around.
Class "vtkFXAAOptions" has 1 instance still around.
Class "vtkOpenGLRenderer" has 1 instance still around.
Class "vtkMatrix3x3" has 7 instances still around.
Class "vtkInformationIterator" has 12 instances still around.
Class "class vtkBuffer<unsigned char>" has 2 instances still around.
Class "vtkOutlineSource" has 6 instances still around.
Class "vtkCellPicker" has 2 instances still around.
Class "vtkInformationIntegerValue" has 72 instances still around.
Class "vtkOpenGLFramebufferObject" has 1 instance still around.
Class "vtkWin32OpenGLRenderWindow" has 1 instance still around.
Class "vtkInformationVector" has 48 instances still around.
Class "vtkCompositeDataPipeline" has 12 instances still around.
Class "class vtkBuffer<float>" has 2 instances still around.
Class "vtkTimerLog" has 6 instances still around.
Class "vtkOpenGLCellToVTKCellMap" has 6 instances still around.
Class "vtkOpenGLIndexBufferObject" has 42 instances still around.
Class "vtkMatrix4x4" has 39 instances still around.
Class "vtkTransform" has 15 instances still around.
Class "vtkTDxInteractorStyleSettings" has 4 instances still around.
Class "vtkFloatArray" has 2 instances still around.
Class "vtkOpenGLPolyDataMapper" has 6 instances still around.
Class "vtkAlgorithmOutput" has 6 instances still around.
Class "vtkPropPicker" has 1 instance still around.
Class "vtkInformationExecutivePortValue" has 6 instances still around.
Class "vtkInformationExecutivePortVectorValue" has 6 instances still around.
Class "vtkTDxInteractorStyleCamera" has 4 instances still around.
Class "vtkDoubleArray" has 4 instances still around.
Class "vtkProp3DCollection" has 2 instances still around.
Class "vtkInteractorStyleJoystickActor" has 1 instance still around.
Class "vtkInteractorStyleJoystickCamera" has 1 instance still around.
Class "vtkInteractorStyleTrackballActor" has 1 instance still around.
Class "vtkInteractorStyleTrackballCamera" has 1 instance still around.
Class "vtkInteractorStyleMultiTouchCamera" has 1 instance still around.
Class "vtkInteractorStyleSwitch" has 1 instance still around.
Class "vtkPickingManager" has 1 instance still around.
Class "vtkWin32RenderWindowInteractor" has 1 instance still around.
Class "vtkOpenGLVertexBufferObjectCache" has 1 instance still around.
Class "vtkTextureUnitManager" has 1 instance still around.
Class "vtkOpenGLState" has 1 instance still around.
Class "vtkTextureObject" has 2 instances still around.
Class "vtkPerspectiveTransform" has 2 instances still around.
Class "vtkSimpleTransform" has 4 instances still around.
Class "vtkOpenGLCamera" has 1 instance still around.
Class "vtkOpenGLLight" has 1 instance still around.

However I don’t know if those are true memory leaks. I called vtkDebugLeaks::PrintCurrentLeaks(); just before setting m_renderer->SetRenderWindow(nullptr); in my main class destructor.
All the class members are vtkSmartPointer or std::unique_ptr:

vtkSmartPointer< vtkRenderer > m_renderer;
vtkSmartPointer< vtkRenderWindow > m_renderWindow;
vtkSmartPointer< vtkRenderWindowInteractor > m_interactor;
std::unique_ptr< TestCallback > m_testCallback;

Those objects could rightfully be still alive since the Dump is called inside the class destructor.

From that dump I found nothing abnormal. Those instance counts are within the expected. Did you get that dump after resizing a number of times? You know, to see whether there is any count increasing abnormally.

Yes, I did about ten resizes which caused memory to jump from 63.908 K to 276.998 K

Then I suspect the OpenGL side or even on the graphics card’s driver is causing the problem. Make sure you have the latest drivers. You said before that other 3D applications work fine, ruling out something in the drivers, but they don’t necessarily result in exactly the same OpenGL calls. Either that or you’re using some older VTK version.

I see. I have built the latest stable 9.01. The python application could use an older version I will try to find out. Perhaps the VTK initialization in python is different, the multisample setting or some other setting. I tried to find the python code without success till now. I know that the application was built with Mayavi, a scientific data visualizer written in Python which uses VTK. The memory leak is shown on another windows machine. Both machines have AMD Radeon graphic card, though.
I have a concern: while rebuilding the VTK to use the vtkDebugLeaks, I set the preprocessor definition VTK_DEBUG_LEAKS only in projects CommonCore and RenderingOpenGL2. Is it enough?
Thanks a lot for your time

I’d enable it for all modules whose header files your program is including.

Or better, for all modules whose libvtk*.DLL's your program requires. You can use something like Dependency Walker to find runtime dependencies in Windows.

With the Win32Cone example (VTK-src\Examples\GUI\Win32\SimpleCxx) modified with the resize code and VTK_DEBUG_LEAKS added to the projects:

CommonCore
RenderingOpenGL2
RenderingCore
FiltersSources
CommonExecutionModel
CommonColor
RenderingUI
FiltersGeneral
FiltersCore
CommonDataModel
CommonTransforms
FiltersGeometry
CommonComputationalGeometry
glew
CommonSystem
CommonMisc
CommonMath
vtksys
loguru

and recompiled I obtained this dump:

Class "vtkMinimalStandardRandomSequence" has 1 instance still around.
Class "vtkCellArray" has 1 instance still around.
Class "vtkWin32OutputWindow" has 1 instance still around.
Class "class vtkBuffer<__int64>" has 3 instances still around.
Class "vtkBoxMuellerRandomSequence" has 1 instance still around.
Class "vtkMathInternal" has 1 instance still around.
Class "vtkIdTypeArray" has 1 instance still around.
Class "vtkTypeInt64Array" has 2 instances still around.
Class "vtkObjectFactoryCollection" has 1 instance still around.
Class "vtkIdList" has 1 instance still around.
Class "vtkRenderingUIObjectFactory" has 1 instance still around.
Class "vtkRenderingOpenGL2ObjectFactory" has 1 instance still around.

This time the dump is created after destroying all vtk objects in destructor:

myVTKApp::~myVTKApp()
{
  renWin->Delete();
  renderer->Delete();

  iren->Delete();
  cone->Delete();
  coneMapper->Delete();
  coneActor->Delete();

  vtkDebugLeaks::PrintCurrentLeaks();
}

I don’t see how this dump might justify what I see in the profiler:
image

1- taken at startup
2- taken after vtk first cone rendering
3- taken after some resizing (about 100)
4- taken before calling PrintCurrentLeaks() in destructor

This is the memory dump taken before calling PrintCurrentLeaks() (so that heap should likely be free of most of vtk objects). These are the first lines but there are more. I see a different result from the vtkDebugLeaks (many more vtk objects alive):

(more entries in memory are not shown)

EDIT:
comparing to the case when Multi sample is set to 0 and no memory leaks. ------------------

vtkDebugLeaks Dump:

Class "vtkMinimalStandardRandomSequence" has 1 instance still around.
Class "class vtkBuffer<__int64>" has 3 instances still around.
Class "vtkBoxMuellerRandomSequence" has 1 instance still around.
Class "vtkMathInternal" has 1 instance still around.
Class "vtkIdTypeArray" has 1 instance still around.
Class "vtkTypeInt64Array" has 2 instances still around.
Class "vtkIdList" has 1 instance still around.
Class "vtkCellArray" has 1 instance still around.
Class "vtkRenderingUIObjectFactory" has 1 instance still around.
Class "vtkObjectFactoryCollection" has 1 instance still around.
Class "vtkRenderingOpenGL2ObjectFactory" has 1 instance still around.
Class "vtkWin32OutputWindow" has 1 instance still around.

Same output as before (just some entries in different position).
Heap allocation (taken at the same steps as before):
image
Memory dump at the end (as expected many fewer objects):

EDIT2:
I have found the Mayavi code:

I don’t know which version of mayavi and vtk the working application uses.
The Mayavi Scene control doesn’t seem to do anything fancy. Excerpt from the create control routine:

def _create_control(self, parent):
   // Create the toolkit-specific control that represents the widget.
    if self.off_screen_rendering:
        if hasattr(tvtk, 'EGLRenderWindow'):
            renwin = tvtk.EGLRenderWindow()
        elif hasattr(tvtk, 'OSOpenGLRenderWindow'):
            renwin = tvtk.OSOpenGLRenderWindow()
        else:
            renwin = tvtk.RenderWindow()
            # If we are doing offscreen rendering we set the window size to
            # (1,1) so the window does not appear at all
            renwin.size = (1, 1)

        self._renwin = renwin
        self._interactor = tvtk.GenericRenderWindowInteractor(
            render_window=renwin
        )
    else:
        renwin = self._renwin = tvtk.RenderWindow()
        self._interactor = tvtk.RenderWindowInteractor(
            render_window=renwin
        )

    renwin.trait_set(point_smoothing=self.point_smoothing,
                     line_smoothing=self.line_smoothing,
                     polygon_smoothing=self.polygon_smoothing)
    # Create a renderer and add it to the renderwindow
    self._renderer = tvtk.Renderer()
    renwin.add_renderer(self._renderer)
    # Save a reference to our camera so it is not GC'd -- needed for
    # the sync_traits to work.
    self._camera = self.camera

    # Sync various traits.
    self._renderer.background = self.background
    self.sync_trait('background', self._renderer)
    self._renderer.on_trait_change(self.render, 'background')
    self._camera.parallel_projection = self.parallel_projection
    self.sync_trait('parallel_projection', self._camera)
    renwin.off_screen_rendering = self.off_screen_rendering
    self.sync_trait('off_screen_rendering', self._renwin)
    self.render_window.on_trait_change(self.render, 'off_screen_rendering')
    self.render_window.on_trait_change(self.render, 'stereo_render')
    self.render_window.on_trait_change(self.render, 'stereo_type')
    self.camera.on_trait_change(self.render, 'parallel_projection')

    self._interactor.initialize()
    self._interactor.render()
    self.light_manager = light_manager.LightManager(self)
    if self.off_screen_rendering:
        # We want the default size to be the normal (300, 300).
        # Setting the size now should not resize the window if
        # offscreen is working properly in VTK.
        renwin.size = (300, 300)

    return self._interactor

Set size routine:

def set_size(self, size):
    // Set the size of the window.
    self._interactor.size = size
    self._renwin.size

It looks very clean to me.

The leak is somewhere else by the looks of it. vtkDebugLeaks only monitors the garbage collection of VTK objects. Your OpenGL back end (driver) may be causing it and it won’t show up in the dump.

MSAA is done in the graphics card. Your driver may be to blame still.

I insist: make sure you have the latest graphics card driver installed.