compressonatorcli documents directory/multi-file batch processing as a core, supported feature (see the CLI's own --help output and the README's second usage example: CompressonatorCLI -fd BC7 .\images .results). But each file processed in a single invocation leaks the previous file's output buffer rather than freeing it. As a result, memory usage grows linearly with the number of files processed, for the life of the process.
Root cause
At cmdline.cpp:3817, once per file in the batch loop, memset(&g_MipSetOut, 0, sizeof(MipSet)) zeroes the tracking struct, including the pointer to the mip-level table, without freeing the table or its payload buffer first. That buffer is allocated per file at cmp_mips.cpp:1095 via plain malloc, with no corresponding free.
DeallocateMipSet(&g_MipSetOut) (cmdline.cpp:1290) exists, but it only runs from cleanup() (cmdline.cpp:1279) which is an error/exit path and final teardown. The normal per-file loop tail (cmdline.cpp:4478–4523) moves straight to the next file without calling it.
Net effect: N files processed in one invocation orphans N−1 buffers' worth of memory for the life of that process.
Related note
Since these buffers are malloc'd with no zero-fill, any code path that fails to write a complete output block (see #349) leaves genuinely uninitialized memory in the result, not a deterministic fallback value.
Suggested fix
Free (or call DeallocateMipSet on) the previous file's buffer at the top of each loop iteration, rather than only at final cleanup.
compressonatorcli documents directory/multi-file batch processing as a core, supported feature (see the CLI's own --help output and the README's second usage example: CompressonatorCLI -fd BC7 .\images .results). But each file processed in a single invocation leaks the previous file's output buffer rather than freeing it. As a result, memory usage grows linearly with the number of files processed, for the life of the process.
Root cause
At cmdline.cpp:3817, once per file in the batch loop, memset(&g_MipSetOut, 0, sizeof(MipSet)) zeroes the tracking struct, including the pointer to the mip-level table, without freeing the table or its payload buffer first. That buffer is allocated per file at cmp_mips.cpp:1095 via plain malloc, with no corresponding free.
DeallocateMipSet(&g_MipSetOut) (cmdline.cpp:1290) exists, but it only runs from cleanup() (cmdline.cpp:1279) which is an error/exit path and final teardown. The normal per-file loop tail (cmdline.cpp:4478–4523) moves straight to the next file without calling it.
Net effect: N files processed in one invocation orphans N−1 buffers' worth of memory for the life of that process.
Related note
Since these buffers are malloc'd with no zero-fill, any code path that fails to write a complete output block (see #349) leaves genuinely uninitialized memory in the result, not a deterministic fallback value.
Suggested fix
Free (or call DeallocateMipSet on) the previous file's buffer at the top of each loop iteration, rather than only at final cleanup.