Skip to content

[DEV BUG] Memory leak in libprojectM 4.1.6 renderer arrays during manual playlist switches on Windows #1000

@HiroyukiHirohata

Description

@HiroyukiHirohata

Please confirm the following points:

  • This report is NOT about the Android apps in the Play Store
  • I have searched the project page to check if the issue was already reported

Affected Project

libprojectM (including the playlist library)

Affected Version

4.1.6

Operating Systems and Architectures

Windows (x64)

Build Tools

Compiler: Microsoft Windows SDK

Additional Project, OS and Toolset Details

No response

Type of Defect

Specific bug in projectM code (please link the code in question)

Log Output

Describe the Issue

Summary
Memory leak identified in libprojectM 4.1.6 during continuous playlist-driven preset transitions on Windows. Memory usage climbs linearly from an 80MB baseline to over 1.2GB within a 7-8 hour window.
Environment
Library Version: libprojectM v4.1.6 Core Release
Operating System: Windows 11 x64
IDE / Toolchain: Visual Studio 2026 / MSVC Compiler

How to Reproduce
The leak occurs when disabling projectM's internal auto-transitioning and driving preset switches authoritatively from the application layer using a Win32 timer:

Instantiate the engine and immediately lock preset switching:
projectm_set_preset_locked(m_pProjectM, true);

Set up a standard Win32 WM_TIMER callback.
Every interval (e.g., matching preset durations), manually call:
projectm_playlist_play_next(m_pProjectM, false);

Observe memory usage over time via Task Manager or Diagnostic Tools.

Visual Studio Diagnostic Profiler Evidence
A heap snapshot differential taken between preset transition cycles reveals that while parent container structures are torn down, primitive array allocations inside the renderer namespace accumulate indefinitely on the process heap.
Here is the raw data dump from the Visual Studio 2026 Memory Profiler Diff (sorted by size impact):

void 4094 93016
projectM-4d.dll!libprojectM::Renderer::Color[] 10 84811
projectM-4d.dll!libprojectM::Renderer::Point[] 18 52042
char[] 172 20327
unsigned int[] 4 16007
projectM-4d.dll!libprojectM::MilkdropPreset::MilkdropPreset 1 15632
projectM-4d.dll!M4::HLSLTree::NodePage 2 8208
projectM-4d.dll!libprojectM::Renderer::TextureUV[] 3 6255
projectM-4d.dll!libprojectM::MilkdropPreset::CustomShape 4 5920
projectM-4d.dll!libprojectM::MilkdropPreset::CustomWaveform 4 5440
projectM-4d.dll!std::_Container_proxy 173 2768
projectM-4d.dll!libprojectM::MilkdropPreset::MilkdropShader 2 1840
char *[] 1 1464
projectM-4d.dll!std::_Ref_count_obj2libprojectM::Renderer::Texture 11 1056
projectM-4d.dll!M4::HLSLMacro *[] 1 736
projectM-4d.dll!std::_Tree_node<std::pair<unsigned int const ,std::shared_ptrlibprojectM::Renderer::TextureAttachment >,void *> 8 448
projectM-4d.dll!std::_Tree_node<std::pair<int const ,std::map<unsigned int,std::shared_ptrlibprojectM::Renderer::TextureAttachment,std::less,std::allocator<std::pair<unsigned int const ,std::shared_ptrlibprojectM::Renderer::TextureAttachment > > > >,void *> 7 448
projectM-4d.dll!std::_Tree_node<std::basic_string<char,std::char_traits,std::allocator >,void *> 4 288
projectM-4d.dll!std::_Ref_count_obj2libprojectM::Renderer::TextureAttachment 5 280
projectM-4d.dll!libprojectM::Renderer::TextureSamplerDescriptor 2 240
projectM-4d.dll!std::_Tree_node<std::pair<int const ,libprojectM::Renderer::TextureSamplerDescriptor>,void *> 1 160
projectM-4d.dll!std::_Tree_node<std::pair<M4::matrixCtor const ,std::basic_string<char,std::char_traits,std::allocator > >,void *> 1 112
projectM-4d.dll!std::_Ref_count_obj2libprojectM::Renderer::Sampler 2 64
bool *[] 1 64
projectM-4-playlistd.dll!std::_List_node<unsigned int,void *> 1 24
projectM-4-playlistd.dll!std::_Container_proxy 1 16
bool[] 1 16
unsigned int 2 8
char 1 1

Technical Observation
The numbers indicate a clear structural correlation. For every single new MilkdropPreset initialized, the system accumulates a matching set of CustomShape (+4) and CustomWaveform (+4) allocations.
Crucially, the raw vertex-drawing arrays associated with them (Color[] and Point[]) are left abandoned on the heap with positive count deltas. This confirms that while the parent objects are tracked, their low-level heap arrays lack a matching delete[] invocation during the transition sequence.
Technical Analysis
Since ~CustomShape() inside CustomShape.hpp is properly declared as a defaulted virtual destructor (virtual ~CustomShape() = default;), the parent wrapper object drops correctly.
However, the raw coordinate and color arrays (Color[], Point[]) used to instantiate the meshes for waveforms and shape elements are persisting on the system heap. This suggests that during a manual track change, the rendering pipeline misses an explicit array deallocation step (delete[]) or drops pointers before freeing the underlying memory boundaries of the active shader canvas elements.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThe issue is (potentially) a bug.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions