Skip to content

Port visualisations to ProjectM 4.x C API - #7438

Merged
hatstand merged 5 commits into
clementine-player:masterfrom
strophy:projectm-4
Apr 23, 2026
Merged

Port visualisations to ProjectM 4.x C API#7438
hatstand merged 5 commits into
clementine-player:masterfrom
strophy:projectm-4

Conversation

@strophy

@strophy strophy commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

I'm a package maintainer from Alpine Linux trying to update the ProjectM package to v4.1.6. This fails due to the Clementine dependency on the older ProjectM API, so I tried using Claude AI to update the Clementine to use the new version. This seems to have been successful - I am able to build against locally built and installed upstream ProjectM 4.1.6 and view visualizations on Ubuntu 25.10.

It would now make sense to unvendor the old ProjectM from the 3rdparty dir since this is dead code, but I thought it would be better to wait for guidance from Clementine maintainers on how to handle this, since ProjectM itself also vendors some deps, making this a maintenance burden.

Resolves #7307, ping @kblaschke as author of libprojectm for review. I'm curious to hear your opinion on the code quality, feel free to use this as a starting point if it needs more work or tell me if it's complete junk and I'll close the PR ;)

Claude AI comments on this PR follow below the snip


Summary

Replaces the bundled projectM 2.x library and its C++ API with the projectM 4 system library and its stable C API.

Changes

Source

  • projectmvisualisation.cpp/.h: fully ported to the projectM 4 C API (projectm_create, projectm_opengl_render_frame, projectm_pcm_add_int16, etc.). Playlist management now uses the separate libprojectM-playlist library.
  • projectmpresetmodel.cpp, visualisationselector.cpp: removed dead #ifdef USE_SYSTEM_PROJECTM / projectM.hpp include blocks.

Build

  • CMakeLists.txt: replaced pkg_check_modules(LIBPROJECTM …) and add_subdirectory(3rdparty/libprojectm) with find_package(projectM4) / find_package(projectM4Playlist). Visualisations gracefully auto-disable if projectM 4 is not found.
  • Removed BUNDLE_PROJECTM_PRESETS cmake option (it only controlled the now-deleted bundled copy).
  • clementine-config.h.in: removed USE_SYSTEM_PROJECTM define.

Removed

  • 3rdparty/libprojectm/the entire bundled projectM 2.x source tree is removed.

Dependency

Visualisations now require projectM ≥ 4.0 with the playlist component (libprojectM + libprojectM-playlist). If not found at configure time, visualisations are silently disabled (no build failure). Preset files (.milk) are loaded from the system preset directory (e.g. install the projectm-data package on Debian/Ubuntu).

CI impact

Existing CI containers (Debian, Ubuntu, Fedora) do not have projectM 4 packaged, so those builds will continue to compile without visualisations — identical to the previous behaviour on those platforms. No CI changes are required.

Testing

Built and tested on Ubuntu 24.04 with projectM 4.1.6 built from source. Visualisations menu enabled, preset list populated, rendering confirmed working.

@strophy strophy changed the title Projectm 4.x Port visualisations to ProjectM 4.x C API Apr 14, 2026
@strophy
strophy force-pushed the projectm-4 branch 3 times, most recently from 09a4e12 to 889406a Compare April 14, 2026 14:01
@hatstand

Copy link
Copy Markdown
Contributor

I think it's probably fine to remove the vendored projectm if it works without on common Linux distros.

@hatstand

Copy link
Copy Markdown
Contributor

Usually we try to compile support for everything in CI to prove it all works. Presumably projectm4 isn't packaged for most of these distros?

@kblaschke

Copy link
Copy Markdown

Would be great to see projectM being updated in Clementine, as the vendored version was a copy of a really old release (2.x) or projectM.

For the Windows build, make sure to call glewInit() before creating the projectM instance, otherwise it'll crash when used with projectM up to v4.1.x. We've replaced GLEW and all hard-linked GL dependencies in the upcoming 4.2.0 release with an internal glad loader and also a custom GL function resolver function, so it would make sense to check if GLEW is actually used (e.g. by using if (TARGET GLEW::glew OR TARGET GLEW::glew_s) in the CMakeLists.txt) and if so, add the call to glewInit() based on a compiler define. If you want the user to be able to use a 4.1.x projectM DLL while Clementine was built against 4.2.0 or higher, hard-linking GLEW and always calling the init function in Clementine wouldn't hurt, but I guess it's not really required. If in doubt, a runtime check against the libprojectM version of the loaded library via projectm_get_version_components() (can be called without an instance) and only allowing projectM 4.2 or higher if GLEW isn't linked could also be done.

@strophy

strophy commented Apr 15, 2026

Copy link
Copy Markdown
Contributor Author

Usually we try to compile support for everything in CI to prove it all works. Presumably projectm4 isn't packaged for most of these distros?

Ubuntu packages 2.1.0
Fedora packages 3.1.12
Alpine packages 3.1.12 (but I have an MR ready with 4.1.6)

@hatstand I guess the best solution is replace vendored v2.x with v4.1.6 in this PR, and configure CMake to build using that if a v4.x system dependency is not available? This would allow it to be built and tested in CI and work around the old dependencies packaged on most common OS targets.

@strophy

strophy commented Apr 16, 2026

Copy link
Copy Markdown
Contributor Author

After vendoring ProjectM 4.1.6, I'm running into a couple of problems:

  • @kblaschke src/api/version.h.in uses CMAKE_PROJECT_VERSION_* to set the version, but this assumes that the top-level project is ProjectM. When vendored, this value is empty because the top-level project is Clementine, which doesn't set these values. Changing to PROJECT_VERSION_* like this seems to fix it, I think this change should be upstreamed?
  • @hatstand ProjectM 4.1.6 requires CMake 3.21 or higher but the Ubuntu Focal image used on some of the workflows (Focal, Windows, Source) in your CI can only provide 3.16.3, causing the build to fail. Should we skip building ProjectM on older systems, or how do you want to proceed here? @kblaschke I suppose you don't want to support older CMake so ProjectM 4.x can build on Ubuntu Focal?

@hatstand

Copy link
Copy Markdown
Contributor
  • @hatstand ProjectM 4.1.6 requires CMake 3.21 or higher but the Ubuntu Focal image used on some of the workflows (Focal, Windows, Source) in your CI can only provide 3.16.3, causing the build to fail. Should we skip building ProjectM on older systems, or how do you want to proceed here? @kblaschke I suppose you don't want to support older CMake so ProjectM 4.x can build on Ubuntu Focal?

Focal is probably old enough to drop, yep.

@strophy

strophy commented Apr 16, 2026

Copy link
Copy Markdown
Contributor Author

I see you're already on it. 🍻 I think we'll need to merge these two PRs, then I'll rebase to try again:

@strophy

strophy commented Apr 17, 2026

Copy link
Copy Markdown
Contributor Author

Still getting some GLEW error, I think it needs the file libglew32.dll.a but that doesn't exist in GLEW 1.5.5 from 2010. We probably need to either update the GLEW version in the Windows Dockerfile or wait for projectM 4.2.x and drop GLEW entirely.

@hatstand

Copy link
Copy Markdown
Contributor

Yeah, there's a few things to fix bumping the mingw build. I think glew is actually fine as we do produce glew32.dll.

@hatstand

Copy link
Copy Markdown
Contributor

Adding -DGLEW_SHARED_LIBRARY_RELEASE=/target/bin/glew32.dll to the cmake line will probably fix at least this error.

@strophy

strophy commented Apr 20, 2026

Copy link
Copy Markdown
Contributor Author

Adding -DGLEW_SHARED_LIBRARY_RELEASE=/target/bin/glew32.dll to the cmake line will probably fix at least this error.

Now we're getting:

[ 76%] Building CXX object src/CMakeFiles/clementine_lib.dir/visualisations/projectmvisualisation.cpp.obj
In file included from /__w/Clementine/Clementine/src/visualisations/projectmvisualisation.cpp:44:
/target/include/GL/glew.h:84:2: error: #error gl.h included before glew.h
   84 | #error gl.h included before glew.h
      |  ^~~~~
/target/include/GL/glew.h:87:2: error: #error glext.h included before glew.h
   87 | #error glext.h included before glew.h
      |  ^~~~~

I'm pretty far out of my depth here with only a vague understanding of how GL components interact, but my instinct would be to either bump the GLEW version or wait for projectM 4.2, what do you think?

@hatstand

Copy link
Copy Markdown
Contributor

As the error says, I think that's just about incorrect order of inclusion. The glew.h include probably needs to be before anything else to avoid any transitive includes of gl.h before it.

@strophy

strophy commented Apr 20, 2026

Copy link
Copy Markdown
Contributor Author

My bad, I thought this was an error from the vendored projectM source rather than from Clementine source. Pushed again!

@hatstand

hatstand commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

AI figured out the last issue (@kblaschke might be worth upstreaming?):

The _imp__ prefix means the playlist library was compiled expecting projectm functions to come from a DLL (via __declspec(dllimport)), but you're linking against a static library.

Let me check the relevant headers:

Searched for files matching **/3rdparty/libprojectm/**/*.h, 20 matches

Searched for regex PROJECTM_EXPORT|dllimport|dllexport (**/3rdparty/libprojectm/**), 20 results

Read , lines 130 to 165

Searched for text PROJECTM_STATIC_DEFINE (**/3rdparty/libprojectm/src/playlist/**), 1 result

Read , lines 60 to 100

Now I see the issue. The playlist library correctly sets PROJECTM_STATIC_DEFINE for itself when building statically, but when the playlist library calls functions from the core projectM library, those functions are declared with PROJECTM_EXPORT which expands to __declspec(dllimport) on Windows — because PROJECTM_STATIC_DEFINE was not propagated from the projectM target to the playlist's compilation.

Let me check the playlist's link dependencies:

Read , lines 1 to 60

Now the picture is clear. The projectM_playlist_main OBJECT library links against libprojectM::API (the header-only API target), but does not have PROJECTM_STATIC_DEFINE set at compile time when consuming headers from libprojectM::API. The PROJECTM_STATIC_DEFINE is only added to projectM_playlist (the final target), but OBJECT library targets get their compile definitions separately.

Let me verify what libprojectM::API is:

Searched for text projectM_api (**/3rdparty/libprojectm/src/libprojectM/CMakeLists.txt), 2 results

Searched for text libprojectM::API (**/3rdparty/libprojectm/src/api/**), 1 result

Read , lines 1 to 50

Now the root cause is confirmed. The projectM_api INTERFACE library (which provides the headers) does not propagate PROJECTM_STATIC_DEFINE when building statically. So when the playlist code includes callbacks.h, PROJECTM_EXPORT expands to __declspec(dllimport), causing the linker to look for _imp__ symbols.

The fix in the upstream CMake is in CMakeLists.txt around line 141-149 — it propagates PROJECTM_STATIC_DEFINE to the INTERFACE of the projectM target, but only when ENABLE_CXX_INTERFACE is off. However, the projectM_api interface target itself never gets that definition, so any consumer that only links libprojectM::API (as projectM_playlist_main does) won't get it.

The fix: The projectM_api INTERFACE target needs to propagate PROJECTM_STATIC_DEFINE when building statically. This needs to happen in CMakeLists.txt.

The correct fix is to add to src/api/CMakeLists.txt:

if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(projectM_api
            INTERFACE
            PROJECTM_STATIC_DEFINE
            )
endif()

This ensures that any target linking against libprojectM::API (including projectM_playlist_main) will have PROJECTM_STATIC_DEFINE defined, which causes PROJECTM_EXPORT to expand to nothing instead of __declspec(dllimport).

Why this happens on MinGW: On Linux, undefined dllimport/dllexport isn't a concern. On Windows (including MinGW), __declspec(dllimport) on a function symbol changes its mangled name to _imp__<name>, which can only be satisfied by a DLL import library — not a static .a file.

@hatstand
hatstand merged commit 11539ae into clementine-player:master Apr 23, 2026
12 checks passed
@hatstand

Copy link
Copy Markdown
Contributor

Thanks!

@kblaschke

Copy link
Copy Markdown

I'll also release libprojectM v4.1.7 with the static linking fix shortly. Great work!

@strophy

strophy commented Apr 24, 2026

Copy link
Copy Markdown
Contributor Author

@kblaschke thanks! Would 775956d be something for upstreaming as well? Not sure the best way to handle that.

@kblaschke

kblaschke commented Apr 24, 2026

Copy link
Copy Markdown

@kblaschke thanks! Would 775956d be something for upstreaming as well? Not sure the best way to handle that.

Yes, that'd make it way easier to use it as a submodule. I remember others asking for this a while ago, but I never actually fixed it. Apart from those variables, there might be others in more hidden places, where the "wrong" (non-project) variables are used, but I can't recall any from the back of my head right now. Guess if it works with these changes, it's good.

Feel free to open a PR for the master branch - and for the 4.1.x branch as well if you like (I'll otherwise cherry-pick it, no biggie).

@strophy

strophy commented May 11, 2026

Copy link
Copy Markdown
Contributor Author

Feel free to open a PR for the master branch - and for the 4.1.x branch as well if you like (I'll otherwise cherry-pick it, no biggie).

Looks like this was already fixed on master in projectM-visualizer/projectm@ddf607a

I'll update projectM in Alpine once 4.2.0 is released!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update projectM visualizer to use the new upstream 4.x versions/API

3 participants