diff --git a/.gitignore b/.gitignore index a9d3de4962..7d057f4aa4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,8 @@ Makefile.in /aclocal.m4 /autom4te.cache /build-aux -/config.* +/config.h +/config.h.in /configure /libtool /stamp-h1 @@ -41,6 +42,10 @@ m4/lt~obsolete.m4 /build /dist +# Visual Studio w/CMake +/out +/.vs +/CMakeSettings.json _sync.bat _sync.sh @@ -48,4 +53,8 @@ to_sync/ .DS_Store src/projectM-sdl/build/ src/libprojectM/build/ -*.pkg \ No newline at end of file +*.pkg + +# CLion +cmake-build-* +.idea diff --git a/BUILDING-cmake.md b/BUILDING-cmake.md new file mode 100644 index 0000000000..f173ac15ad --- /dev/null +++ b/BUILDING-cmake.md @@ -0,0 +1,279 @@ +# Building with CMake + +This file contains in-depth information for building with the CMake build system. + +## Selecting a specific project file generator + +To specify a CMake generator, use the `-G` switch, followed by the generator name. Some newer generators take an +additional architecture using the `-A` switch. To list all available generators available on your current platform, +leave out the generator name: + +```shell +cmake -G +``` + +Additional information on the supported generators can be +found [in the CMake documentation](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + +### Popular generators + +By default, CMake will use the [`Unix Makefiles`](https://cmake.org/cmake/help/latest/generator/Unix%20Makefiles.html) +generator on Linux and macOS, which is a good choice and should work. Yet in some circumstances, you might want to +generate project files for a specific build tool or IDE: + +```shell +cmake -G "Unix Makefiles" -S /path/to/source/dir -B /path/to/build/dir +``` + +A common alternative is the [`Ninja`](https://cmake.org/cmake/help/latest/generator/Ninja.html) generator, which +requires `ninja` to be installed. It is mostly a `make` +replacement with less overhead and should work equally well. It is supported on all major platforms, including Windows: + +```shell +cmake -G Ninja -S /path/to/source/dir -B /path/to/build/dir +``` + +On macOS, CMake also supports the [`Xcode`](https://cmake.org/cmake/help/latest/generator/Xcode.html) generator. It will +create an `.xcodeproj` bundle which you can open in Xcode. It also adds support for automatic code signing, which might +be required if your application using projectM needs to be notarized for store deployment. + +```shell +cmake -G "Xcode" -S /path/to/source/dir -B /path/to/build/dir +``` + +If you develop on Windows, you will possibly use Visual Studio. While recent visual Studio versions have CMake support +built-in, you can still pre-generate the solution and project files and open the `.sln` file from the build directory. +CMake provides a separate generator for each Visual Studio release. For Visual Studio 2019 you would use +the [`Visual Studio 16 2019`](https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2016%202019.html) generator +and provide an additional architecture parameter: + +```shell +cmake -G "Visual Studio 16 2019" -A "X64" -S /path/to/source/dir -B /path/to/build/dir +``` + +It is not possible to generate multi-arch solutions with CMake though. You need to create separate build directories and +use the respective `-A` switch for each. + +## Project-specific configuration options + +CMake has no built-in way of printing all available configuration options. You can either refer to the +top-level `CMakeLists.txt` which contains a block of `option` and `cmake_dependent_option` commands, or use one of the +available CMake UIs which will display the options after configuring the project once. + +### Boolean switches + +The following table also gives you an overview of the available options and their defaults. All options accept a boolean +value (`YES`/`NO`, `TRUE`/`FALSE`, `ON`/`OFF` or `1`/`0`) and can be provided on the configuration-phase command line +using the `-D` switch. + +| CMake option | Default | Required dependencies | Description | +|-------------------------|---------|-----------------------|------------------------------------------------------------------------------------------------------------| +| `ENABLE_DEBUG_PREFIX` | `ON` | | Adds `d` to the name of any binary file in debug builds. | +| `ENABLE_EMSCRIPTEN` | `OFF` | `GLES`, `Emscripten` | Build for the web using Emscripten. | +| `ENABLE_SDL` | `ON` | `SDL2` | Builds and installs the standalone SDL visualizer application. Also required for Emscripten and the tests. | +| `ENABLE_GLES` | `OFF` | `GLES` | Use OpenGL ES 3 profile for rendering instead of the Core profile. | +| `ENABLE_THREADING` | `ON` | `pthreads` | Enable multithreading support. If enabled, will cause an error if pthreads are not available. | +| `ENABLE_NATIVE_PRESETS` | `OFF` | | Builds and installs the binary (native) preset libraries. | +| `ENABLE_PRESETS` | `ON` | | Installs several thousand shipped presets. | +| `ENABLE_PULSEAUDIO` | `OFF` | `Qt5`, `Pulseaudio` | Build the Qt5-based Pulseaudio visualizer application. | +| `ENABLE_JACK` | `OFF` | `Qt5`, `JACK` | Build the Qt5-based JACK visualizer application. | +| `ENABLE_LIBVISUAL` | `OFF` | `libvisual-0.4` | Build the libvisual plug-in/actor library. | +| `ENABLE_TESTING` | `OFF` | `SDL2` | Builds the unit tests. | +| `ENABLE_LLVM` | `OFF` | `LLVM` | Enables experimental LLVM JIT support. | + +### Path options + +There are also a few textual parameters that can be used to fine-tune the installation directories. Relative paths in +the following options are appended to the value +of [`CMAKE_INSTALL_PREFIX`](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html) (which, on most UNIX +platforms, defaults to `/usr/local`): + +| CMake option | Default | Description | +|-------------------------|------------------|----------------------------------------------------------------------------------| +| `PROJECTM_BIN_DIR` | `bin` | Directory where executables (e.g. the SDL standalone application) are installed. | +| `PROJECTM_LIB_DIR` | `lib` | Directory where libprojectM is installed[[*]](#libvisual-path). | +| `PROJECTM_INCLUDE_DIR` | `include` | Directory where the libprojectM include files will be installed under. | +| `PROJECTM_DATADIR_PATH` | `share/projectM` | Directory where the default configuration and presets are installed under. | + +[*]: The libvisual projectM plug-in install location is determined automatically via +pkgconfig and not influenced by this option. + +## Always perform out-of-tree builds! + +Most classic IDEs and build systems directly make use of the source tree and create project files, temporary build +artifacts (e.g. object files) and the final binaries in the same directory structure as the source files. An advantage +of this approach is that you can find all compiled binaries side-by-side with their sources and generated headers are +already in the same directories as the source files including them. This approach has some drawbacks though: + +- Only a single build configuration is supported as files are overwritten in-place. +- A lot of noise is created in the source directory, making it hard to distinguish between generated and original source + files. +- A very large `.gitignore` file is required to cover all unwanted files. +- Mistakes in the build scripts can overwrite source files, causing errors and destroy uncommitted work. + +Some of these can be mitigated by providing additional targets (`make clean` and `make distclean`) or creating +subdirectories for Debug/Release build configurations. + +While CMake also supports in-tree builds, it is "discouraged" in the official documentation, for the above reasons. +Building out-of-tree allows it to create multiple build directories with different configurations which do not influence +each other in any way. If a build directory contains unwanted artifacts, and you want to start fresh, simply delete and +recreate the whole directory - no work is lost. + +This project follow this principle by treating the original source tree as read-only and avoiding potential conflicts: + +- Everything under [`CMAKE_SOURCE_DIR`](https://cmake.org/cmake/help/latest/variable/CMAKE_SOURCE_DIR.html) must only be + read, never changed or written to. +- Everything under [`CMAKE_BINARY_DIR`](https://cmake.org/cmake/help/latest/variable/CMAKE_BINARY_DIR.html) is temporary + and related to the current build configuration. +- When generating configuration-dependent files, + use [`CMAKE_CONFIGURATION_TYPES`](https://cmake.org/cmake/help/latest/variable/CMAKE_CONFIGURATION_TYPES.html) + and [`CMAKE_BUILD_TYPE`](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html) to create non-conflicting + files in the build tree. + +While this project will not force you to build out-of-tree, there is no mechanism to clean up the generated files after +running cmake in-tree. + +## CMake build directory layout + +If you are new to CMake, the way of how CMake creates the build directory and where it creates the build targets might +be confusing. Here is a summary of what's in the build directory and how it is structured in general. + +### Using files from the build tree + +It is generally not good practice to directly take binaries and other files from the build tree for packaging, for +several reasons: + +1. The directory structure is generated by CMake and depends on the generator used. The layout might change between + CMake versions, even for the same generator. +2. On platforms with RPATH support, CMake will store absolute paths in executables and shared libraries which point to + the absolute paths of any linked dependencies, either from the build tree or external libraries as well. These + binaries are not relocatable and will most certainly not work if run on any other computer (or even on the same after + deleting the build directory). +3. For some configurations, even Release build artifacts may contain debug symbols until they are installed. + +It is fine to build and run executables from the build directory for development and debugging. For packaging, always +use the `install` target and copy files from there. + +### Generated files + +In the top-level build directory, CMake creates a few files that are present on any platform: + +- `CMakeCache.txt`: This file contains all variables and build settings CMake needs to remember from the first + configuration run. This file can be edited on demand either manually or using a CMake UI to change any values. On the + next build, CMake will regenerate the project files if this file has been modified. +- `cmake_install.cmake`: Contains generated install-related settings. +- `install_manifest.txt`: After installing the project, this file contains a list with absolute filenames of all + installed files. It can be used for packaging or deleting installed files as CMake doesn't define an `uninstall` + target. +- The top-level project file for use with the selected build toolset, e.g. `Makefile`, `build.ninja`, `projectm.sln` + or `projectm.xcodeproj`, plus additional toolset-specific files. + +The projectM build files generate additional files used in the build and install phases: + +- `config.inp`: The default configuration file, by default installed to `/share/projectM`. +- `libprojectM.pc`: Package configuration file for use with `pkgconfig`. +- `include/config.h`: A forced include file that contains macro definitions to enable or disable certain code features + based on the build configuration and availability of platform headers and features. Similar to the + autoheader-generated file. + +### Subdirectory structure + +The rest of the directory structure generally resembles the source tree. Source directories containing +a `CMakeLists.txt` file will also be created in the build tree with the same relative path. Each of these subdirectories +contains a `CMakeFiles` directory with CMake-internal data, generated project files for the select toolset, e.g. +makefiles and any temporary compile artifacts. + +### Executable and library locations + +Build targets - shared/static libraries and executables - are created in the same subdirectory in the build tree as +the `CMakeLists.txt` file that defines the target in the source tree (which, in most cases, resides in the same +directory as the source files). Depending on the generator used, the binaries are created directly in the directory for +single-configuration generators (like `Unix Makefiles` or `Ninja`) and in a subdirectory with the configuration name, +e.g. `Debug` or `Release`, for multi-configuration generators like `Xcode` or `Visual Studio 16 2019`. + +You may also find additional files and symbolic links in the same location depending on the platform, e.g. `.pdb` files +on Windows. + +## Using libprojectM in other CMake projects + +The projectM library can be used as a static library and, on non-Windows platforms, as a shared library in other +CMake-based projects to provide embedded audio visualization. There are two ways: + +- Importing the library CMake targets directly from the build tree without installation. +- Using the library from an installed location or package with the provided CMake package config files. + +### Importing libprojectM targets from the build tree + +This approach is useful for projects that either require more in-depth access to the projectM library files, especially +to headers that are not installed as part of the public API. This might cause issues if the internal headers change, but +gives a broader set of features and more control to the developer. + +To use the targets, follow these steps: + +- Configure the build directory as needed. +- Build the required library targets `projectM_static` and `projectM_shared` as needed, or simply everything. +- Include the file `src/libprojectM/projectM-exports.cmake` from the projectM build tree in your project. + +All targets and their interface properties are now defined and can be used. + +#### Example + +```cmake +# libprojectM.a/.lib is already built. +set(PROJECTM_BUILD_DIR "/some/path/to/projectM/build") +include("${PROJECTM_BUILD_DIR}/src/libprojectM/projectM-exports.cmake") + +add_executable(MyApp main.cpp) + +target_link_libraries(MyApp PRIVATE libprojectM::static) +``` + +This will also add all projectM include directories to the target's source files, pointing to the respective projectM +source directories. + +Look at the generated file in the build tree if you need more details about the available targets. + +### Importing libprojectM targets from an installed version + +This is the recommended way of importing libprojectM in your project. This project installs a set of CMake files +in `//cmake/libprojectM`, containing target definitions, version and dependency checks as well as any +additional libraries required for linking. Other projects then use CMake's `find_package` command to search for these +files in [different locations](https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure). + +In the case projectM libraries and headers are not installed in any system search path, you need to add either the +install prefix path (the top-level install dir) or the directory containing the libraries (the `lib` dir by default) to +the [`CMAKE_PREFIX_PATH`](https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html) list. + +If the package was found, you can then link against one of these provided targets: + +- `libprojectM::static` - The static library (`.a` or `.lib`). +- `libprojectM::shared` - The shared library (`.so`, `.dylib` or `.dll`). + +Note that the availability of the targets depend on the platform and build configuration, so only one of those might be +available. You can check with `if(TARGET libprojectM::static)` and `if(TARGET libprojectM::shared)` to select the +available or preferred one. + +Depending on how the package was built, targets might be available for multiple configurations or only `Release`. CMake +will automatically select the most appropriate one to link. + +Include dirs, additional link dependencies and possible compiler options will be propagated to any target the library is +linked to. + +#### Example + +Links the shared library preferably, with fallback to static: + +```cmake +find_package(libprojectM REQUIRED) + +if(TARGET libprojectM::shared) + set(PROJECTM_LINK_TARGET libprojectM::shared) +else() + # If this target is also unavailable, CMake will error out on target_link_libraries(). + set(PROJECTM_LINK_TARGET libprojectM::static) +endif() + +add_executable(MyApp main.cpp) + +target_link_libraries(MyApp PRIVATE ${PROJECTM_LINK_TARGET}) +``` diff --git a/BUILDING.md b/BUILDING.md index 2e0c9d4dd5..8953b0957d 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1,56 +1,369 @@ # Building projectM from source -### Dependencies -Relevant for Linux distributions, FreeBSD, macOS: +## Quick Start (Debian / Ubuntu) -* `autotools` `autoconf` -* `libtool`, or at least `pkg-config` -* `which` +For other operating systems (Windows/macOS), see the OS-specific sections below. -Main build options & their requirements: +### Install the build tools and dependencies -| Configure flag | Required dependency | Produced binary | -|-----------------------|----------------------------------------------------------------------------------------|-----------------------| -| `--enable-sdl` | `libsdl2-dev` | `projectMSDL` | -| `--enable-pulseaudio` | `qt5-default` `qtdeclarative5-dev` `libpulse-dev` `libqt5opengl5-dev` | `projectM-pulseaudio` | -| `--enable-jack` | `libjack2-dev`OR`libjack1-dev`; `qt5-default` `qtdeclarative5-dev` `libqt5opengl5-dev` | `projectM-jack` | +Mandatory packages: -#### Additional information on dependencies -* `libglm` (headers only) for matrix math is required. lives in `vendor/glm`. -* A modified version of `hlslparser` is included in Renderer and used to transpile HLSL shaders to GLSL -* OpenGL 3+ or OpenGLES is required -* `libsdl >= 2.0.5` is required for the SDL and emscripten apps. `src/projectM-sdl` is the current reference application implementation. maybe try getting that to build and run as your testbench. +```bash +sudo apt install build-essential libgl1-mesa-dev mesa-common-dev libsdl2-dev libglm-dev +``` -If extra information needed - you can refere to `configure.ac` and the assorted `Makefile.am` files. +Optional packages for additional features: -### Building process under *nix systems -```sh +```bash +sudo apt install qtbase5-dev # For building Qt-based UIs +sudo apt install llvm-dev # for using the experimental LLVM Jit +sudo apt install libvisual-0.4-dev # To build the libvisual plug-in +sudo apt install libjack-jackd2-dev # To build the JACK visualizer application +``` + +### Download the projectM sources + +If you want to use a stable version of projectM, download the latest release from +the [Releases page on GitHub](https://github.com/projectM-visualizer/projectm/releases) and unpack it. You can then skip +to the next step. + +If you prefer a bleeding-edge version or want to modify the code, clone the Git repository: + +```bash +sudo apt install git # Probably already installed +git clone https://github.com/projectM-visualizer/projectm.git /path/to/local/repo +cd /path/to/local/repo +git fetch --all --tags +``` + +### Build and install projectM + +Older projectM releases use autoconf/automake for building. If your repository has a `CMakeLists.txt` file on the top +level, skip to the CMake part right below. + +Replace `/usr/local` with your preferred installation prefix. + +#### Configure the project using autoconf + +```bash +sudo apt install autoconf automake libtool ./autogen.sh +./configure --prefix=/usr/local +``` + +#### Configure the project using CMake + +```bash +sudo apt install cmake +mkdir build +cd build +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local .. +``` + +#### Build and install + +Independent of the method of configuration, this will build projectM and install it to /usr/local or the configured +installation prefix set in the step before: + +```bash +make -j && sudo make install +``` + +**Note**: You won't need to use `sudo` if the install prefix is writeable by your non-privileged user. + +#### Test projectM + +If you have a desktop environment installed, you can now run `[prefix]/bin/projectMSDL`. + +## Dependencies + +Depending on the OS/distribution and packaging system, libraries might be split into separate packages with binaries and +development files. To build projectM, both binaries and development files need to be installed. + +#### General build dependencies for all platforms: + +* A working build toolchain. +* **OpenGL**: 3D graphics library. Used to render the visualizations. +* **GLES3**: OpenGL libraries for embedded systems, version 3. Required to build projectM on mobile devices, Raspberry + Pi and Emscripten. +* [**glm**](https://github.com/g-truc/glm): OpenGL Mathematics library. Optional, will use a bundled version with + autotools or if not installed. +* [**SDL2**](https://github.com/libsdl-org/SDL): Simple Directmedia Layer. Version 2.0.5 or higher is required to build + the standalone visualizer application (projectMSDL). +* [**LLVM**](https://llvm.org/): Low-Level Virtual Machine. Optional and **experimental**, used to speed up preset + execution by leveraging the LLVM JIT compiler. + +#### Only relevant for Linux distributions, FreeBSD and macOS: + +* **pkgconfig**: Required to find some library dependencies. +* [**Qt5**](https://www.qt.io/): Qt cross-platform UI framework. Used to build the Pulseaudio and JACK visualizer + applications. Requires the `Gui` and `OpenGL` component libraries/frameworks. +* [**Pulseaudio**](https://www.freedesktop.org/wiki/Software/PulseAudio/): Sound system for POSIX platforms. Required to + build the Pulseaudio visualizer application. +* [**JACK**](https://jackaudio.org/): Real-time audio server. Required to build the JACK visualizer application. +* [**libvisual 0.4**](http://libvisual.org/): Audio visualization library with plug-in support. Required to build the + projectM libvisual plug-in. + +#### When using the classic autotools-based build system on UNIX platforms: + +* **GNU [automake](https://www.gnu.org/software/automake/) and [autoconf](https://www.gnu.org/software/autoconf/)**: + Used to create the configuration script and generate the Makefiles. +* [**libtool**](https://www.gnu.org/software/libtool/): Optional. Used by autoconf is available. +* [**which**](https://carlowood.github.io/which/): Determines full paths of shell executables. Should already be + installed by default on the majority of POSIX-compliant OSes. + +#### When using the new CMake-based build system: + +* [**CMake**](https://cmake.org/): Used to generate platform-specific build files. + +### Only relevant for Windows: + +* [**vcpkg**](https://github.com/microsoft/vcpkg): C++ Library Manager for Windows. Optional, but recommended to install + the aforementioned library dependencies and/or using CMake to configure the build. +* [**NuGet**](https://www.nuget.org/): Dependency manager for .NET. Optional, but recommended when building with the + pre-created Visual Studio solutions. +* [**GLEW**](http://glew.sourceforge.net/): The OpenGL Extension Wrangler Library. Only required if using CMake to + configure the build, the pre-created solutions use a bundled copy of GLEW. + +## Building on Linux and macOS + +### Installing dependencies + +- Linux distributions will have packages available for most (if not all) required libraries. The package names and + commands to install them vary widely between distributions (and even versions of the same distribution). Please refer + to the documentation of your build OS on how to find and install the required libraries. +- On *BSD, install the appropriate Ports with `pkg install`. +- On macOS, using [Homebrew](https://brew.sh/) is the recommended way of installing any dependencies not supplied by + Xcode. + +### Building with CMake + +--- + +:exclamation: **IMPORTANT NOTE**: Currently, CMake build support is still in active development and considered +unfinished. It is working and produces running binaries, but there are still some features, build internals and whole +targets missing. While testing the CMake build files on any platform and feedback on this is strongly encouraged, +CMake-based builds should not yet be used in any production environment until this message is gone. + +--- + +The steps documented below are a bare minimum quickstart guide on how to build and install the project. If you want to +configure the build to your needs, require more in-depth information about the build process and available tweaks, or on +how to use libprojectM in your own CMake-based projects, see [BUILDING-cmake.md](BUILDING-cmake.md). + +Using CMake is the recommended and future-proof way of building projectM. CMake is a platform-independent tool that is +able to generate files for multiple build systems and toolsets while using only a single set of build instructions. +CMake support is still new and in development, but will replace the other project files (automake/autoconf scripts, +Visual Studio solutions and Xcode projects) in this repository once mature and stable. + +Building the project with CMake requires two steps: + +- Configure the build and generate project files. +- Build and install the project using the selected build tools. + +**Note:** When building with CMake, the build directory should always be separate from the source directory. Generating +the build files directly inside the source tree is possible, but strongly discouraged. Using a subdirectory, +e.g. `cmake-build` inside the source directory is fine though. + +This documentation only covers project-specific information. CMake is way too versatile and feature-rich to cover any +possible platform- and toolset-specific configuration details here. If you are not experienced in using CMake, please +first read the [official CMake documentation](https://cmake.org/cmake/help/latest/) (at least +the [User Interaction Guide](https://cmake.org/cmake/help/latest/guide/user-interaction/index.html)) for basic usage +instructions. + +#### Configure the build + +Configuring a non-debug build with default options and install prefix (`/usr/local`) can be done with these commands, +building in a subdirectory inside the source directory: + +```shell +cd /path/to/source +mkdir cmake-build +cd cmake-build +cmake -DCMAKE_BUILD_TYPE=Release .. +``` + +CMake will check all required dependencies and display any errors. If configuration was successful, a summary of the +build configuration is printed and CMake should display a `Generating done` line. The project is now ready to build. + +#### Compile and install the project + +Depending on your generator choice, you can use your selected toolset as usual to build and install projectM: + +- With `Unix Makefiles`, run `make && sudo make install`. +- With `Ninja`, run `ninja && sudo ninja install`. +- With `Xcode`, select the appropriate target and configuration in Xcode and build it, or `INSTALL` to install the + project. + +You can also use CMake's build mode to run the selected toolset and build any specified target. CMake knows which +command to call and which parameters to pass, so the syntax works on all platforms with all generators. If you've +already set the top-level build directory as working directory, simply pass `.` as `/path/to/build/dir`: + +```shell +cmake --build /path/to/build/dir --config Release +sudo cmake --build /path/to/build/dir --config Release --target install +``` + +If you don't need root permissions to install running the second command without `sudo` is sufficient. + +If you want to provide arguments directly to the toolset command, add `--` at the end of the CMake command line followed +by any additional arguments. CMake will pass these *unchanged and unchecked* to the subcommand: + +```shell +cmake --build /path/to/build/dir --config Release -- -j 4 +``` + +### Building with automake/autoconf + +projectM ships with a set of scripts to check build dependencies and configure the build according to the user's +preferences and toolchain. + +**Note**: These scripts might be removed in the future in favor of using CMake as the sole build system tool on all +platforms, so if you're planning to base new work on the projectM libraries, consider using CMake instead. + +The source distribution only contains templates for the configure script and other files, so these need to be generated +first. `cd` into the top-level source directory and execute: + +```shell +./autogen.sh +``` + +You should now have an executable `configure` script ready. This will be used to check the platform and dependencies and +finally configure the Makefiles for the actual build. The script accepts numerous parameters to customize the build. The +most important ones and their requirements are listed in the table below. To get a full list of available parameters, +options and influential environment variables, run `./configure --help`. + +#### Important build options and their requirements: + +| Configure flag | Required dependencies | Produced binary | +|-----------------------|-----------------------|-----------------------| +| `--enable-sdl` | `SDL2` | `projectMSDL` | +| `--enable-pulseaudio` | `Qt5`, `Pulseaudio` | `projectM-pulseaudio` | +| `--enable-jack` | `Qt5`, `JACK` | `projectM-jack` | +| `--enable-threading` | `pthreads` | | +| `--enable-llvm` | `LLVM` | | +| `--enable-gles` | `GLES3` | | + +For example, to configure the project to build and install only the libprojectM libraries and the SDL-based standalone +visualizer in the default location (`/usr/local`), run the following commands: + +```shell ./configure --enable-sdl # supply additional options here, info in Dependencies make sudo make install ``` -### Debian/Ubuntu/Mint -```sh -sudo apt install clang libsdl2-dev libgl1-mesa-dev qt5-default qtdeclarative5-dev libqt5opengl5-dev libjack-dev libpulse-dev -./configure && make -j4 && sudo make install +## Building on Windows + +### Using the provided project files + +Windows build bypasses the autogen/configure pipeline and uses manually created Visual Studio/MSVC project files +in `msvc/`. See `.appveyor.yml` for command line building. + +Some dependencies are included verbatim (GLEW), while others leverage the NuGet ecosystem and are downloaded +automatically (SDL2). + +The Visual Studio solution is quite old and unmaintained. If you experience issues importing it, try using CMake to +generate the solution for your Visual Studio version instead. + +### With CMake + +To build the projectM library and the SDL-based standalone application, CMake can be used as on any other platform. +Using vcpkg to pull in the build dependencies is highly recommended, as CMake can't use NuGet (NuGet pulls in +dependencies using the project files, while CMake requires the libraries before creating the project files). + +#### Installing the dependencies with vcpkg + +As stated above, using vcpkg is the easiest way to get the required dependencies. First, +install [vcpkg from GitHub](https://github.com/microsoft/vcpkg) by following the official guide. Then install the +following packages for your desired architecture (called "triplet"): + +- `glew` +- `sdl2` + +The `glew` package will also pull in the `opengl` libraries. + +Example to install the libraries for the x64 architecture, run from a Visual Studio command prompt: + +```commandline +vcpkg install glew:x64-windows sdl2:x64-windows ``` -### OpenGL ES -projectM supports OpenGL ES 3 for embedded systems. Be sure to configure with the `--enable-gles` flag. +#### Creating the Visual Studio solution + +CMake provides separate generators for different Visual Studio versions. Newer CMake versions will support recent Visual +Studio releases, but may remove generators for older ones. To get a list of available generators from the command line, +use the `-G` switch without an argument. The CMake GUI will present you a dropdown list you can easily select from. + +To set the build architecture in Visual Studio builds, use the `-A` switch and specify either `Win32` or `X64` as the +argument. If you want to build for both architectures, create separate build directories and configure them accordingly. + +To make CMake aware of the installed vcpkg packages, simply use the provided toolchain file when configuring the +projectM build by +pointing [`CMAKE_TOOLCHAIN_FILE`](https://cmake.org/cmake/help/latest/variable/CMAKE_TOOLCHAIN_FILE.html) to it. + +Here is a full command line example to create a Visual Studio 2019 solution for X64: + +```commandline +cmake -G "Visual Studio 16 2019" -A "X64" -DCMAKE_TOOLCHAIN_FILE="/scripts/buildsystems/vcpkg.cmake" -S "" -B "" +``` + +If you use the CMake GUI, check the "Specify toolchain file for cross-compiling" option in the first page of the +configuration assistant, then select the above `vcpkg.cmake` file on the second page. + +Another option is to open the project folder in a recent Visual Studio version as a CMake project and configure CMake +using Visual Studio's JSON-based settings file. + +#### Building the solution + +To build the project, open the generated solution in Visual Studio and build it like any other solution. Each time the +CMake files are changed, Visual Studio will automatically regenerate the CMake build files and reload the solution +before continuing the build. Be aware that in old Visual Studio versions (2015 and earlier) the reload-and-continue +might not work properly. + +You can also build the solution with msbuild via the command line, or use CMake's build wrapper to do that for you: + +```commandline +cmake --build "" --config Release +``` + +#### Using Ninja to build + +The Ninja build system is shipped with Visual Studio since version 2019 and used by default if loading a CMake project +directly from within the IDE. Ninja can also be [installed separately](https://github.com/ninja-build/ninja/releases). + +To configure the build directory for Ninja, pass `Ninja` or `Ninja Multi-Config` as the argument for the `-G` switch. +The difference between both generators is that the former uses `CMAKE_BUILD_TYPE` to specify the configuration ( +e.g. `Debug` or `Release`) while the latter supports all configurations in a single build directory, specified during +build time. + +The architecture is determined from the toolset, so make sure to run the commands in the correct Visual Studio command +prompt, e.g. "Native Tools for X64". + +Configure and build for a single-configuration Release build with vcpkg: + +```commandline +cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="/scripts/buildsystems/vcpkg.cmake" -S "" -B "" +cmake --build "" +``` + +Same, but using the multi-configuration generator: + +```commandline +cmake -G "Ninja Multi-Config" -DCMAKE_TOOLCHAIN_FILE="/scripts/buildsystems/vcpkg.cmake" -S "" -B "" +cmake --build "" --config Release +``` + +## Notes on other platforms and features ### Raspberry Pi (and other embedded systems) + * projectM is arch-independent, although there are some SSE2 enhancements for x86 * [Notes on running on raspberry pi](https://github.com/projectM-visualizer/projectm/issues/115) -### Building on Windows -Windows build bypasses the autogen/configure pipeline and uses the Visual Studio/MSVC files in `msvc/`. See `.appveyor.yml` for command line building. - -Some dependencies are included verbatim (glew), while others leverage the NuGet ecosystem and are downloaded automatically (sdl2). - ### Build using NDK for Android + Install Android Studio, launch SDK Manager and install NDK ```sh @@ -59,27 +372,38 @@ Install Android Studio, launch SDK Manager and install NDK make && make install-strip ``` -Now you should be able to copy ./src/libprojectM/.libs/libprojectM.so -and appropriate headers to projectm-android, and build it using Android Studio +Now you should be able to copy ./src/libprojectM/.libs/libprojectM.so and appropriate headers to projectm-android, and +build it using Android Studio ### LLVM JIT -There are some optmizations for parsing preset equations that leverage the LLVM JIT. You can try `./compile --enable--llvm` to enable them. They may not work with newer version of LLVM (https://github.com/projectM-visualizer/projectm/pull/360) +There are some optimizations for parsing preset equations that leverage the LLVM JIT. You can +try `./compile --enable--llvm` to enable them. They may not work with newer version of +LLVM (https://github.com/projectM-visualizer/projectm/pull/360) ## libprojectM `libprojectM` is the core library. It is made up of three sub-libraries: #### Renderer + Made up of everything in `src/libprojectM/Renderer`. These files compose the `libRenderer` sub-library. #### MilkdropPresetFactory / NativePresetFactory -From their respective folders. Native presets are visualizations that are implemented in C++ instead of `.milk` preset files. They are completely optional. Milkdrop presets are technically optional but the whole thing is basically useless without them. -If you don't want native presets, and you probably don't, don't bother with them. Ideally there should be a configure option to disable them, probably on by default (at this time this is needed for autoconf: https://github.com/projectM-visualizer/projectm/issues/99). +From their respective folders. Native presets are visualizations that are implemented in C++ instead of `.milk` preset +files. They are completely optional. Milkdrop presets are technically optional but the whole thing is basically useless +without them. +If you don't want native presets, and you probably don't, don't bother with them. Ideally there should be a configure +option to disable them, probably on by default (at this time this is needed for +autoconf: https://github.com/projectM-visualizer/projectm/issues/99). ### Assets -`libprojectM` can either have a configuration hard-coded or load from a configuration file. It's up to each application to decide how to load the config file. The config file can have paths defined specifying where to load fonts and presets from. -You will want to install the config file and presets somewhere, and then define that path for the application you're trying to build. +`libprojectM` can either have a configuration hard-coded or load from a configuration file. It's up to each application +to decide how to load the config file. The config file can have paths defined specifying where to load fonts and presets +from. + +You will want to install the config file and presets somewhere, and then define that path for the application you're +trying to build. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..3d1ebe1716 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,199 @@ +cmake_minimum_required(VERSION 3.14 FATAL_ERROR) + +if(CMAKE_VERSION VERSION_LESS 3.19 AND CMAKE_GENERATOR STREQUAL "Xcode") + message(AUTHOR_WARNING "Using a CMake version before 3.19 with a recent Xcode SDK and the Xcode generator " + "will likely result in CMake failing to find the AppleClang compiler. Either upgrade CMake to at least " + "version 3.19 or use a different generator, e.g. \"Unix Makefiles\" or \"Ninja\".") +endif() + +include(CMakeDependentOption) +include(CheckSymbolExists) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED YES) +set(CMAKE_POSITION_INDEPENDENT_CODE YES) + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +option(ENABLE_DEBUG_POSTFIX "Add \"d\" after library names for debug builds" ON) +if(ENABLE_DEBUG_POSTFIX) + set(CMAKE_DEBUG_POSTFIX d) +endif() + +# The API (SO) and detailed library versions for the shared library. +set(PROJECTM_SO_VERSION "3") +set(PROJECTM_LIB_VERSION "3.1.1") + +project(projectm + LANGUAGES C CXX + VERSION 3.1.13 + ) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + +set(PROJECTM_BIN_DIR "bin" CACHE STRING "Executable installation directory, relative to the install prefix.") +set(PROJECTM_DATADIR_PATH "share/projectM" CACHE STRING "Default (absolute) path to the projectM data files (presets etc.)") +set(PROJECTM_LIB_DIR "lib" CACHE STRING "Library installation directory, relative to the install prefix.") +set(PROJECTM_INCLUDE_DIR "include" CACHE STRING "Header installation directory, relative to the install prefix.") + +# Feature options, including dependencies. +option(ENABLE_PRESETS "Build and install bundled presets" ON) +option(ENABLE_NATIVE_PRESETS "Build and install native libraries written in C/C++" OFF) +option(ENABLE_TESTING "Build and install the projectM test suite" OFF) +option(ENABLE_EMSCRIPTEN "Build for web with emscripten" OFF) +cmake_dependent_option(ENABLE_SDL "Enable SDL2 support" ON "NOT ENABLE_EMSCRIPTEN;ENABLE_TESTING" ON) +cmake_dependent_option(ENABLE_GLES "Enable OpenGL ES support" OFF "NOT ENABLE_EMSCRIPTEN" ON) +cmake_dependent_option(ENABLE_THREADING "Enable multithreading support" ON "NOT ENABLE_EMSCRIPTEN;NOT CMAKE_SYSTEM_NAME STREQUAL Windows" OFF) +cmake_dependent_option(ENABLE_PULSEAUDIO "Build Pulseaudio-based Qt UI" OFF "ENABLE_QT;NOT ENABLE_EMSCRIPTEN;CMAKE_SYSTEM_NAME STREQUAL Linux" OFF) +cmake_dependent_option(ENABLE_JACK "Build JACK-based Qt and SDL UIs" OFF "ENABLE_QT;NOT ENABLE_EMSCRIPTEN;CMAKE_SYSTEM_NAME STREQUAL Linux" OFF) +cmake_dependent_option(ENABLE_LLVM "Enable LLVM JIT support" OFF "NOT ENABLE_EMSCRIPTEN" OFF) +cmake_dependent_option(ENABLE_LIBVISUAL "Build and install the projectM libvisual plug-in" OFF "NOT ENABLE_EMSCRIPTEN;CMAKE_SYSTEM_NAME STREQUAL Linux" OFF) + +find_package(GLM) +if(NOT TARGET GLM::GLM) + add_library(GLM::GLM INTERFACE IMPORTED) + set_target_properties(GLM::GLM PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/vendor" + ) + + message(STATUS "GLM library not found, using bundled version.") + set(USE_SYSTEM_GLM OFF) +else() + set(USE_SYSTEM_GLM ON) +endif() + +if(ENABLE_EMSCRIPTEN) + message(STATUS "${CMAKE_C_COMPILER}") + check_symbol_exists(__EMSCRIPTEN__ "" HAVE_EMSCRIPTEN) + if(NOT HAVE_EMSCRIPTEN) + message(FATAL_ERROR "You are not using an emscripten compiler.") + endif() +endif() + +if(ENABLE_SDL) + find_package(SDL2 REQUIRED) + + # Temporary fix to deal with wrong include dir set by SDL2's CMake configuration. + get_target_property(_SDL2_INCLUDE_DIR SDL2::SDL2 INTERFACE_INCLUDE_DIRECTORIES) + if(_SDL2_INCLUDE_DIR MATCHES "(.+)/SDL2\$") + message(STATUS "SDL2 include dir contains \"SDL2\" subdir (SDL bug #4004) - fixing to \"${CMAKE_MATCH_1}\".") + set_target_properties(SDL2::SDL2 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_MATCH_1}" + ) + endif() + + if(SDL2_VERSION VERSION_LESS "2.0.5") + message(FATAL_ERROR "SDL2 libraries were found, but have version ${SDL2_VERSION}. At least version 2.0.5 is required.") + endif() +endif() + +if(ENABLE_GLES) + find_package(GLES3 REQUIRED) +else() + find_package(OpenGL REQUIRED) + set(PROJECTM_OPENGL_LIBRARIES OpenGL::GL) + # GLX is required by SOIL2 on platforms with the X Window System (e.g. most Linux distributions) + if(TARGET OpenGL::GLX) + list(APPEND PROJECTM_OPENGL_LIBRARIES OpenGL::GLX) + endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + find_package(GLEW REQUIRED) + list(APPEND PROJECTM_OPENGL_LIBRARIES GLEW::glew) + endif() +endif() + +if(ENABLE_THREADING) + set(CMAKE_THREAD_PREFER_PTHREAD YES) + find_package(Threads REQUIRED) + if(NOT CMAKE_USE_PTHREADS_INIT) + message(FATAL_ERROR "Threading support requested - pthread support is required, but not available.") + endif() + set(USE_THREADS YES) +endif() + +if(ENABLE_LLVM) + find_package(LLVM REQUIRED) + set(HAVE_LLVM TRUE) +else() + unset(HAVE_LLVM) +endif() + +if(ENABLE_PULSEAUDIO) + find_package(Pulseaudio REQUIRED) +endif() + +if(ENABLE_JACK) + find_package(JACK REQUIRED) +endif() + +if(ENABLE_PULSEAUDIO OR ENABLE_JACK) + find_package(Qt5 REQUIRED COMPONENTS Gui OpenGL) +endif() + +if(ENABLE_LIBVISUAL) + find_package(libvisual REQUIRED) + set(PROJECTM_LIBVISUAL_DIR "${LIBVISUAL_PLUGINSBASEDIR}/actor" CACHE STRING "Installation directory for the libvisual plug-in library.") +endif() + +include(features.cmake) + +add_subdirectory(presets) +add_subdirectory(src) + +message(STATUS "") +message(STATUS "projectM v${PROJECT_VERSION}") +message(STATUS " =====") +message(STATUS "") +message(STATUS " prefix: ${CMAKE_INSTALL_PREFIX}") +message(STATUS " libdir: ${PROJECTM_LIB_DIR}") +message(STATUS " includedir: ${PROJECTM_INCLUDE_DIR}") +message(STATUS " bindir: ${PROJECTM_BIN_DIR}") +message(STATUS " libvisual plugin dir: ${PROJECTM_LIBVISUAL_DIR}") +message(STATUS "") +message(STATUS " compiler: ${CMAKE_CXX_COMPILER}") +message(STATUS " cflags: ${CMAKE_C_FLAGS}") +message(STATUS " cxxflags: ${CMAKE_CXX_FLAGS}") +message(STATUS " ldflags: ${CMAKE_SHARED_LINKER_FLAGS}") +message(STATUS "") +message(STATUS " DATADIR_PATH: ${PROJECTM_DATADIR_PATH}") +message(STATUS " - - -") +message(STATUS "") +message(STATUS " Applications:") +message(STATUS " =====") +message(STATUS "") +message(STATUS " libprojectM: ON") +message(STATUS " Presets: ${ENABLE_PRESETS}") +message(STATUS " Native presets: ${ENABLE_NATIVE_PRESETS}") +message(STATUS " Tests: ${ENABLE_TESTING}") +message(STATUS " Threading: ${ENABLE_THREADING}") +message(STATUS " SDL2: ${ENABLE_SDL}") +if(ENABLE_SDL) + message(STATUS " SDL2 version: ${SDL2_VERSION}") +endif() +message(STATUS " Pulseaudio UI: ${ENABLE_PULSEAUDIO}") +if(ENABLE_PULSEAUDIO) + message(STATUS " Pulseaudio version: ${PULSEAUDIO_VERSION}") +endif() +message(STATUS " JACK UI: ${ENABLE_JACK}") +if(ENABLE_JACK) + message(STATUS " JACK version: ${JACK_VERSION}") +endif() +if(ENABLE_PULSEAUDIO OR ENABLE_JACK) + message(STATUS " Qt version: ${Qt5_VERSION}") +endif() +message(STATUS " OpenGLES: ${ENABLE_GLES}") +message(STATUS " Emscripten: ${ENABLE_EMSCRIPTEN}") +message(STATUS " LLVM JIT: ${ENABLE_LLVM}") +if(ENABLE_LLVM) + message(STATUS " LLVM version: ${LLVM_VERSION}") +endif() +message(STATUS " libvisual plug-in: ${ENABLE_LIBVISUAL}") +message(STATUS " Use system GLM: ${USE_SYSTEM_GLM}") + +message(AUTHOR_WARNING + "The CMake build scripts for projectM are still in active development.\n" + "This means that build parameters, exported target names and other things can change " + "at any time until the new build system is officially documented and announced to be " + "fully supported.\n" + "DO NOT base any production work on it yet!\n" + ) diff --git a/cmake/CheckEnumValueExists.cmake b/cmake/CheckEnumValueExists.cmake new file mode 100644 index 0000000000..9930d3f1fc --- /dev/null +++ b/cmake/CheckEnumValueExists.cmake @@ -0,0 +1,26 @@ +include_guard() + +include(CheckCSourceCompiles) + +macro(check_enum_value_exists enum_value include variable) + if(NOT DEFINED ${variable}) + message(STATUS "Looking for enum value ${enum_value}") + set(_CMAKE_REQUIRED_QUIET_tmp "${CMAKE_REQUIRED_QUIET}") + set(CMAKE_REQUIRED_QUIET ON) + check_c_source_compiles(" +#include <${include}> +int main() { + int tmp = ${enum_value}; + return 0; +} +" + ${variable} + ) + if(${variable}) + message(STATUS "Looking for enum value ${enum_value} - found") + else() + message(STATUS "Looking for enum value ${enum_value} - not found") + endif() + set(CMAKE_REQUIRED_QUIET "${_CMAKE_REQUIRED_QUIET_tmp}") + endif() +endmacro() diff --git a/cmake/EnableCFlagsIfSupported.cmake b/cmake/EnableCFlagsIfSupported.cmake new file mode 100644 index 0000000000..25643fdd10 --- /dev/null +++ b/cmake/EnableCFlagsIfSupported.cmake @@ -0,0 +1,47 @@ +include(CheckCXXCompilerFlag) +include(CheckCXXSourceCompiles) + +macro(enable_cflags_if_supported) + foreach(flag ${ARGN}) + check_cxx_compiler_flag("${flag}" CXXFLAG_${flag}_SUPPORTED) + if(CXXFLAG_${flag}_SUPPORTED) + add_compile_options("${flag}") + endif() + endforeach() +endmacro() + +macro(enable_linker_flags_if_supported) + set(_old_CMAKE_REQUIRED_LINK_OPTIONS "${CMAKE_REQUIRED_LINK_OPTIONS}") + foreach(flag ${ARGN}) + set(CMAKE_REQUIRED_LINK_OPTIONS "${flag}") + check_cxx_source_compiles("int main(){return 0;}" LDCFLAG_${flag}_SUPPORTED) + if(LDCFLAG_${flag}_SUPPORTED) + add_link_options(${flag}) + else() + set(CMAKE_REQUIRED_LINK_OPTIONS "LINKER:${flag}") + check_cxx_source_compiles("int main(){return 0;}" LDCFLAG_LINKER_${flag}_SUPPORTED) + if(LDFLAG_LINKER_${flag}_SUPPORTED) + add_link_options(LINKER:${flag}) + endif() + endif() + endforeach() + set(CMAKE_REQUIRED_LINK_OPTIONS "${_old_CMAKE_REQUIRED_LINK_OPTIONS}") +endmacro() + +macro(enable_target_linker_flags_if_supported target access) + set(_old_CMAKE_REQUIRED_LINK_OPTIONS "${CMAKE_REQUIRED_LINK_OPTIONS}") + foreach(flag ${ARGN}) + set(CMAKE_REQUIRED_LINK_OPTIONS "${flag}") + check_cxx_source_compiles("int main(){return 0;}" LDCFLAG_${flag}_SUPPORTED) + if(LDCFLAG_${flag}_SUPPORTED) + target_link_options(${target} ${access} ${flag}) + else() + set(CMAKE_REQUIRED_LINK_OPTIONS "LINKER:${flag}") + check_cxx_source_compiles("int main(){return 0;}" LDCFLAG_LINKER_${flag}_SUPPORTED) + if(LDFLAG_LINKER_${flag}_SUPPORTED) + target_link_options(${target} ${access} LINKER:${flag}) + endif() + endif() + endforeach() + set(CMAKE_REQUIRED_LINK_OPTIONS "${_old_CMAKE_REQUIRED_LINK_OPTIONS}") +endmacro() diff --git a/cmake/FindGLES3.cmake b/cmake/FindGLES3.cmake new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cmake/FindGLM.cmake b/cmake/FindGLM.cmake new file mode 100644 index 0000000000..18e5393a53 --- /dev/null +++ b/cmake/FindGLM.cmake @@ -0,0 +1,20 @@ +# The GLM library is header-only, so we only need to find the glm/glm.hpp header. +find_path(GLM_INCLUDE_DIR + glm/glm.hpp + ) + +find_file(GLM_INCLUDE_FILE + glm/glm.hpp + ) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(GLM DEFAULT_MSG GLM_INCLUDE_FILE GLM_INCLUDE_DIR) + +if(GLM_FOUND AND NOT TARGET GLM::GLM) + add_library(GLM::GLM INTERFACE IMPORTED) + + set_target_properties(GLM::GLM PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GLM_INCLUDE_DIR}" + ) +endif() diff --git a/cmake/FindJACK.cmake b/cmake/FindJACK.cmake new file mode 100644 index 0000000000..2df4db8e9b --- /dev/null +++ b/cmake/FindJACK.cmake @@ -0,0 +1,33 @@ +# First try to use PKG_CONFIG to find JACK. +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(JACK jack QUIET) +endif() + +if(NOT JACK_INCLUDEDIR OR NOT JACK_LIBRARIES) + find_path(JACK_INCLUDEDIR + jack/jack.h + ) + + find_library(JACK_LIBRARIES + jack + ) +endif() + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(JACK + REQUIRED_VARS JACK_LIBRARIES JACK_INCLUDEDIR + VERSION_VAR JACK_VERSION + ) + +if(JACK_FOUND AND NOT TARGET JACK::JACK) + add_library(JACK::JACK INTERFACE IMPORTED) + + set_target_properties(JACK::JACK PROPERTIES + INTERFACE_LINK_LIBRARIES "${JACK_LIBRARIES}" + INTERFACE_LINK_DIRECTORIES "${JACK_LIBRARY_DIRS}" + INTERFACE_INCLUDE_DIRECTORIES "${JACK_INCLUDEDIR}" + INTERFACE_COMPILE_OPTIONS "${JACK_CFLAGS}" + ) +endif() diff --git a/cmake/FindLLVM.cmake b/cmake/FindLLVM.cmake new file mode 100644 index 0000000000..ba5fd68756 --- /dev/null +++ b/cmake/FindLLVM.cmake @@ -0,0 +1,55 @@ +# Find and use llvm-config to determine the proper library and include locations. + +find_program(LLVM_CONFIG_COMMAND + NAMES llvm-config llvm-config.exe + ) + +if(LLVM_CONFIG_COMMAND) + execute_process(COMMAND + ${LLVM_CONFIG_COMMAND} --version + OUTPUT_VARIABLE LLVM_VERSION + ) + string(STRIP "${LLVM_VERSION}" LLVM_VERSION) + + execute_process(COMMAND + ${LLVM_CONFIG_COMMAND} --includedir + OUTPUT_VARIABLE LLVM_INCLUDE_DIR + ) + string(STRIP "${LLVM_INCLUDE_DIR}" LLVM_INCLUDE_DIR) + + execute_process(COMMAND + ${LLVM_CONFIG_COMMAND} --libdir + OUTPUT_VARIABLE LLVM_LIB_DIR + ) + string(STRIP "${LLVM_LIB_DIR}" LLVM_LIB_DIR) + + execute_process(COMMAND + ${LLVM_CONFIG_COMMAND} --libs + OUTPUT_VARIABLE LLVM_LIBRARIES + ) + string(STRIP "${LLVM_LIBRARIES}" LLVM_LIBRARIES) + + execute_process(COMMAND + ${LLVM_CONFIG_COMMAND} --ldflags + OUTPUT_VARIABLE LLVM_LDFLAGS + ) + string(STRIP "${LLVM_LDFLAGS}" LLVM_LDFLAGS) +endif() + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(LLVM + REQUIRED_VARS LLVM_CONFIG_COMMAND LLVM_LIBRARIES LLVM_LIB_DIR LLVM_INCLUDE_DIR LLVM_LDFLAGS + VERSION_VAR LLVM_VERSION + ) + +if(LLVM_FOUND AND NOT TARGET LLVM::LLVM) + add_library(LLVM::LLVM INTERFACE IMPORTED) + + set_target_properties(LLVM::LLVM PROPERTIES + INTERFACE_LINK_LIBRARIES "${LLVM_LIBRARIES}" + INTERFACE_LINK_DIRECTORIES "${LLVM_LIB_DIR}" + INTERFACE_INCLUDE_DIRECTORIES "${LLVM_INCLUDE_DIR}" + INTERFACE_LINK_OPTIONS "${LLVM_LDFLAGS}" + ) +endif() diff --git a/cmake/FindPulseaudio.cmake b/cmake/FindPulseaudio.cmake new file mode 100644 index 0000000000..9fff91e68c --- /dev/null +++ b/cmake/FindPulseaudio.cmake @@ -0,0 +1,44 @@ +# First try to use PKG_CONFIG to find Pulseaudio. +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PULSEAUDIO libpulse QUIET) +endif() + +if(NOT PULSEAUDIO_INCLUDEDIR OR NOT PULSEAUDIO_LIBRARIES) + find_path(PULSEAUDIO_INCLUDEDIR + pulse/pulseaudio.h + ) + + find_library(PULSEAUDIO_LIBRARIES + pulse + ) + + if(PULSEAUDIO_INCLUDEDIR AND EXISTS "${PULSEAUDIO_INCLUDEDIR}/pulse/version.h") + file(STRINGS "${PULSEAUDIO_INCLUDEDIR}/pulse/version.h" pulseaudio_version_str + REGEX "pa_get_headers_version\(\)" + ) + if(pulseaudio_version_str AND "${pulseaudio_version_str}" MATCHES "\\(\"([0-9.]+)\"\\)") + set(PULSEAUDIO_VERSION "${CMAKE_MATCH_1}") + endif() + endif() + + set(PULSEAUDIO_CFLAGS "-D_REENTRANT") +endif() + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(Pulseaudio + REQUIRED_VARS PULSEAUDIO_LIBRARIES PULSEAUDIO_INCLUDEDIR + VERSION_VAR PULSEAUDIO_VERSION + ) + +if(Pulseaudio_FOUND AND NOT TARGET Pulseaudio::Pulseaudio) + add_library(Pulseaudio::Pulseaudio INTERFACE IMPORTED) + + set_target_properties(Pulseaudio::Pulseaudio PROPERTIES + INTERFACE_LINK_LIBRARIES "${PULSEAUDIO_LIBRARIES}" + INTERFACE_LINK_DIRECTORIES "${PULSEAUDIO_LIBRARY_DIRS}" + INTERFACE_INCLUDE_DIRECTORIES "${PULSEAUDIO_INCLUDEDIR}" + INTERFACE_COMPILE_OPTIONS "${PULSEAUDIO_CFLAGS}" + ) +endif() diff --git a/cmake/Findlibvisual.cmake b/cmake/Findlibvisual.cmake new file mode 100644 index 0000000000..ee04ebf914 --- /dev/null +++ b/cmake/Findlibvisual.cmake @@ -0,0 +1,42 @@ +# First try to use PKG_CONFIG to find libvisual. +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(LIBVISUAL libvisual-0.4 QUIET) + + if(LIBVISUAL_INCLUDEDIR) + # Retrieve the plug-in install directory + pkg_get_variable(LIBVISUAL_PLUGINSBASEDIR libvisual-0.4 pluginsbasedir) + endif() +endif() + +if(NOT LIBVISUAL_INCLUDEDIR OR NOT LIBVISUAL_LIBRARIES) + find_path(LIBVISUAL_INCLUDEDIR + libvisual/libvisual.h + PATH_SUFFIXES libvisual-0.4 + ) + + find_library(LIBVISUAL_LIBRARIES + visual-0.4 + ) + + # Use the default path. + set(LIBVISUAL_PLUGINSBASEDIR "lib/libvisual-0.4") +endif() + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(libvisual + REQUIRED_VARS LIBVISUAL_INCLUDEDIR LIBVISUAL_LIBRARIES + VERSION_VAR LIBVISUAL_VERSION + ) + +if(LIBVISUAL_FOUND AND NOT TARGET libvisual::libvisual) + add_library(libvisual::libvisual INTERFACE IMPORTED) + + set_target_properties(libvisual::libvisual PROPERTIES + INTERFACE_LINK_LIBRARIES "${LIBVISUAL_LIBRARIES}" + INTERFACE_LINK_DIRECTORIES "${LIBVISUAL_LIBRARY_DIRS}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBVISUAL_INCLUDEDIR}" + INTERFACE_COMPILE_OPTIONS "${LIBVISUAL_CFLAGS}" + ) +endif() diff --git a/config.h.cmake.in b/config.h.cmake.in new file mode 100644 index 0000000000..c56b0d1c1a --- /dev/null +++ b/config.h.cmake.in @@ -0,0 +1,73 @@ +#pragma once + +/* Define to 1 if you have the `aligned_alloc' function. */ +#cmakedefine01 HAVE_ALIGNED_ALLOC + +/* Define to 1 if you have the header file. */ +#cmakedefine01 HAVE_DLFCN_H + +/* Define to 1 if you have the header file. */ +#cmakedefine01 HAVE_FTS_H + +/* Defined if a valid OpenGL implementation is found. */ +#cmakedefine01 HAVE_GL + +/* Define to 1 if you have the header file. */ +#cmakedefine01 HAVE_GLES_GL_H + +/* Define to 1 if you have the header file. */ +#cmakedefine01 HAVE_GL_GL_H + +/* Define to 1 if you have the header file. */ +#cmakedefine01 HAVE_INTTYPES_H + +/* Define HAVE_LLVM */ +#cmakedefine01 HAVE_LLVM + +/* Define to 1 if you have the header file. */ +#cmakedefine01 HAVE_MEMORY_H + +/* Define to 1 if you have the header file. */ +#cmakedefine01 HAVE_OPENGL_GL_H + +/* Define to 1 if you have the `posix_memalign' function. */ +#cmakedefine01 HAVE_POSIX_MEMALIGN + +/* Have PTHREAD_PRIO_INHERIT. */ +#cmakedefine01 HAVE_PTHREAD_PRIO_INHERIT + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRING_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_WINDOWS_H + +/* Define to 1 if you have the ANSI C header files. */ +#cmakedefine STDC_HEADERS + +/* Define USE_GLES */ +#cmakedefine01 USE_GLES + +/* Define USE_THREADS */ +#cmakedefine01 USE_THREADS + +/* Version number of package */ +#define VERSION "@PROJECT_VERSION@" diff --git a/features.cmake b/features.cmake new file mode 100644 index 0000000000..2541e4bbb8 --- /dev/null +++ b/features.cmake @@ -0,0 +1,103 @@ +# Tests for various platform features and generates a header with the appropriate defines, +# similar to the one created by autotools. + +include(CheckSymbolExists) +include(CheckCXXSymbolExists) +include(CheckFunctionExists) +include(CheckIncludeFileCXX) +include(CheckEnumValueExists) +include(CheckIncludeFiles) +include(EnableCFlagsIfSupported) + +add_compile_definitions( + $<$:DEBUG> + ) + +if(NOT MSVC) + enable_cflags_if_supported( + -Wall + -Wchar-subscripts + -Wformat-security + -Wpointer-arith + -Wshadow + -Wsign-compare + -Wtype-limits + -fopenmp + ) + + enable_linker_flags_if_supported( + -fopenmp + ) +else() + enable_cflags_if_supported( + /W4 + /openmp + ) + + enable_linker_flags_if_supported( + /openmp + ) +endif() + +check_function_exists(aligned_alloc HAVE_ALIGNED_ALLOC) +check_include_file_cxx("dlfcn.h" HAVE_DLFCN_H) +check_include_file_cxx("fts.h" HAVE_FTS_H) +check_include_file_cxx("GL/gl.h" HAVE_GL_GL_H) +check_include_file_cxx("inttypes.h" HAVE_INTTYPES_H) +check_include_file_cxx("memory.h" HAVE_MEMORY_H) +check_function_exists(posix_memalign HAVE_POSIX_MEMALIGN) +check_enum_value_exists("PTHREAD_PRIO_INHERIT" "pthread.h" HAVE_PTHREAD_PRIO_INHERIT) +check_include_file_cxx("fts.h" HAVE_FTS_H) +check_include_file_cxx("stdint.h" HAVE_STDINT_H) +check_include_file_cxx("stdlib.h" HAVE_STDLIB_H) +check_include_file_cxx("strings.h" HAVE_STRINGS_H) +check_include_file_cxx("string.h" HAVE_STRING_H) +check_include_file_cxx("sys/stat.h" HAVE_SYS_STAT_H) +check_include_file_cxx("sys/types.h" HAVE_SYS_TYPES_H) +check_include_file_cxx("unistd.h" HAVE_UNISTD_H) +check_include_file_cxx("windows.h" HAVE_WINDOWS_H) + +set(_std_c_headers + # C89/C90 + assert.h + ctype.h + errno.h + float.h + limits.h + locale.h + math.h + setjmp.h + signal.h + stdarg.h + stddef.h + stdio.h + stdlib.h + string.h + time.h + # C95/NA1 + iso646.h + wctype.h + # C99 + complex.h + fenv.h + inttypes.h + stdbool.h + stdint.h + tgmath.h + ) +check_include_files("${_std_c_headers}" STDC_HEADERS LANGUAGE C) +unset(_std_c_headers) + +# Create global configuration header +file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/include") +configure_file(config.h.cmake.in "${CMAKE_BINARY_DIR}/include/config.h") +include_directories("${CMAKE_BINARY_DIR}/include") +add_compile_definitions(HAVE_CONFIG_H) + +# Force-include the file in all targets +if(MSVC) + add_definitions(/FI"config.h") +else() + # GCC or Clang + add_definitions(-include config.h) +endif() diff --git a/msvc/eglew.h b/msvc/GL/eglew.h similarity index 100% rename from msvc/eglew.h rename to msvc/GL/eglew.h diff --git a/msvc/glew.c b/msvc/GL/glew.c similarity index 99% rename from msvc/glew.c rename to msvc/GL/glew.c index 9a095ed231..e63c2abc0d 100644 --- a/msvc/glew.c +++ b/msvc/GL/glew.c @@ -31,7 +31,7 @@ */ #ifndef GLEW_INCLUDE -#include "glew.h" +#include "GL/glew.h" #else #include GLEW_INCLUDE #endif @@ -50,7 +50,7 @@ # if defined(NOGDI) # undef NOGDI # endif -# include "wglew.h" +# include "GL/wglew.h" #elif !defined(__ANDROID__) && !defined(__native_client__) && !defined(__HAIKU__) && (!defined(__APPLE__) || defined(GLEW_APPLE_GLX)) # include #endif diff --git a/msvc/glew.h b/msvc/GL/glew.h similarity index 100% rename from msvc/glew.h rename to msvc/GL/glew.h diff --git a/msvc/glewinfo.c b/msvc/GL/glewinfo.c similarity index 100% rename from msvc/glewinfo.c rename to msvc/GL/glewinfo.c diff --git a/msvc/glxew.h b/msvc/GL/glxew.h similarity index 100% rename from msvc/glxew.h rename to msvc/GL/glxew.h diff --git a/msvc/wglew.h b/msvc/GL/wglew.h similarity index 100% rename from msvc/wglew.h rename to msvc/GL/wglew.h diff --git a/msvc/projectM.vcxproj b/msvc/projectM.vcxproj index 4066f1e17e..501d85a2a7 100644 --- a/msvc/projectM.vcxproj +++ b/msvc/projectM.vcxproj @@ -133,8 +133,8 @@ - - + + CompileAsC CompileAsC diff --git a/msvc/projectM.vcxproj.filters b/msvc/projectM.vcxproj.filters index 1f55d2772e..c8ef34e335 100644 --- a/msvc/projectM.vcxproj.filters +++ b/msvc/projectM.vcxproj.filters @@ -15,11 +15,11 @@ - + - + diff --git a/presets/CMakeLists.txt b/presets/CMakeLists.txt new file mode 100644 index 0000000000..cfd0e99748 --- /dev/null +++ b/presets/CMakeLists.txt @@ -0,0 +1,25 @@ +if(ENABLE_PRESETS) + + install(DIRECTORY + presets_bltc201 + presets_eyetune + presets_milkdrop + presets_milkdrop_104 + presets_milkdrop_200 + presets_mischa_collection + presets_projectM + presets_stock + presets_tryptonaut + presets_yin + DESTINATION "${PROJECTM_DATADIR_PATH}/presets" + COMPONENT Presets + ) + + if(ENABLE_TESTING) + install(DIRECTORY + tests + DESTINATION "${PROJECTM_DATADIR_PATH}/presets" + COMPONENT Tests + ) + endif() +endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000000..75bfb34fbe --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,8 @@ +add_subdirectory(libprojectM) +add_subdirectory(NativePresets) +add_subdirectory(projectM-qt) +add_subdirectory(projectM-jack) +add_subdirectory(projectM-libvisual) +add_subdirectory(projectM-pulseaudio) +add_subdirectory(projectM-sdl) +add_subdirectory(projectM-test) diff --git a/src/NativePresets/CMakeLists.txt b/src/NativePresets/CMakeLists.txt new file mode 100644 index 0000000000..b77c05bfb1 --- /dev/null +++ b/src/NativePresets/CMakeLists.txt @@ -0,0 +1,70 @@ +if(NOT ENABLE_PRESETS OR NOT ENABLE_NATIVE_PRESETS) + return() +endif() + +# Using a macro to reduce repetition. +macro(add_native_preset name) + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") + add_library(${name}_static STATIC ${name}.cpp) + + set_target_properties(${name}_static PROPERTIES + OUTPUT_NAME ${name} + ) + + target_link_libraries(${name}_static + PRIVATE + projectM_static + GLM::GLM + ) + + install(TARGETS ${name}_static + LIBRARY DESTINATION "${PROJECTM_DATADIR_PATH}/presets" + RUNTIME DESTINATION "${PROJECTM_DATADIR_PATH}/presets" + ARCHIVE DESTINATION "${PROJECTM_DATADIR_PATH}/presets" + COMPONENT Presets + ) + endif() + + add_library(${name}_shared SHARED ${name}.cpp) + + target_compile_definitions(${name}_shared + PRIVATE + ${name}_EXPORTS + ) + + set_target_properties(${name}_shared PROPERTIES + OUTPUT_NAME ${name} + ) + + # On Windows, libprojectM is only built statically. + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + target_link_libraries(${name}_shared + PRIVATE + projectM_static + ) + else() + target_link_libraries(${name}_shared + PRIVATE + projectM_shared + ) + endif() + + target_link_libraries(${name}_shared + PRIVATE + GLM::GLM + ) + + install(TARGETS ${name}_shared + LIBRARY DESTINATION "${PROJECTM_DATADIR_PATH}/presets" + RUNTIME DESTINATION "${PROJECTM_DATADIR_PATH}/presets" + ARCHIVE DESTINATION "${PROJECTM_DATADIR_PATH}/presets" + COMPONENT Presets + ) +endmacro() + +add_native_preset(MstressJuppyDancer) +add_native_preset(RLGFractalDrop7c) +add_native_preset(RovastarDarkSecret) +add_native_preset(RovastarDriftingChaos) +add_native_preset(RovastarFractalSpiral) +add_native_preset(RovastarFractopiaFrantic) diff --git a/src/libprojectM/CMakeLists.txt b/src/libprojectM/CMakeLists.txt new file mode 100644 index 0000000000..3bb393e79d --- /dev/null +++ b/src/libprojectM/CMakeLists.txt @@ -0,0 +1,333 @@ +add_compile_definitions( + DATADIR_PATH="${PROJECTM_DATADIR_PATH}" + GL_SILENCE_DEPRECATION +) + +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + # dirent.h support + set(MSVC_EXTRA_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/msvc") + include_directories("${MSVC_EXTRA_INCLUDE_DIR}") + + add_library(dlfcn OBJECT + msvc/dlfcn.c + msvc/dlfcn.h + ) + + set_target_properties(dlfcn PROPERTIES + FOLDER libprojectM + ) + + # Additional preprocessor definitions for Windows builds + add_compile_definitions( + USE_TEXT_MENU + USE_FTGL + USE_NATIVE_GLEW + STBI_NO_DDS + projectM_FONT_MENU="${CMAKE_SOURCE_DIR}/fonts/VeraMono.ttf" + projectM_FONT_TITLE="${CMAKE_SOURCE_DIR}/fonts/Vera.ttf" + ) +endif() + +add_subdirectory(MilkdropPresetFactory) +add_subdirectory(NativePresetFactory) +add_subdirectory(Renderer) + +# CMake cannot combine multiple static libraries using target_link_libraries. +# This syntax will pull in the compiled object files into the final library. +add_library(projectM_main OBJECT + Common.hpp + ConfigFile.cpp + ConfigFile.h + dlldefs.h + event.h + fatal.h + fftsg.cpp + fftsg.h + FileScanner.cpp + FileScanner.hpp + glError.h + gltext.h + HungarianMethod.hpp + IdleTextures.hpp + KeyHandler.cpp + KeyHandler.hpp + lvtoprojectM.h + PCM.cpp + PCM.hpp + PipelineMerger.cpp + PipelineMerger.hpp + Preset.cpp + Preset.hpp + PresetChooser.cpp + PresetChooser.hpp + PresetFactory.cpp + PresetFactory.hpp + PresetFactoryManager.cpp + PresetFactoryManager.hpp + PresetLoader.cpp + PresetLoader.hpp + projectM.cpp + projectM.hpp + projectM-opengl.h + RandomNumberGenerators.hpp + resource.h + sdltoprojectM.h + TestRunner.cpp + TestRunner.hpp + TimeKeeper.cpp + TimeKeeper.hpp + timer.cpp + timer.h + wipemalloc.cpp + wipemalloc.h + ) + +target_include_directories(projectM_main + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/Renderer" + "${CMAKE_CURRENT_SOURCE_DIR}/Renderer/hlslparser/src" + "${CMAKE_CURRENT_SOURCE_DIR}/MilkdropPresetFactory" + "${CMAKE_CURRENT_SOURCE_DIR}/NativePresetFactory" + "${MSVC_EXTRA_INCLUDE_DIR}" + ) + +add_library(projectM_static STATIC + $ + $ + $ + $ + $ + $ + $<$:$> + ) + +set_target_properties(projectM_static PROPERTIES + OUTPUT_NAME projectM + EXPORT_NAME static + FOLDER libprojectM + ) + +target_include_directories(projectM_static + PUBLIC + "$" + "$" + "$" + "$" + "$" + "$" + "$" + ) + +target_link_libraries(projectM_static + PRIVATE + GLM::GLM + PUBLIC + ${PROJECTM_OPENGL_LIBRARIES} + ) + +if(ENABLE_THREADING) + target_link_libraries(projectM_static + PUBLIC + Threads::Threads + ) +endif() + +if(ENABLE_LLVM) + target_link_libraries(projectM_static + PUBLIC + LLVM::LLVM + ) +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(projectM_static + PUBLIC + "-framework CoreFoundation" + ) +endif() + +install(TARGETS projectM_static + EXPORT libprojectMTargets + LIBRARY DESTINATION "${PROJECTM_LIB_DIR}" + RUNTIME DESTINATION "${PROJECTM_LIB_DIR}" + ARCHIVE DESTINATION "${PROJECTM_LIB_DIR}" + COMPONENT Runtime + ) + +# Building projectM as a Windows DLL is not (yet) supported. +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") + add_library(projectM_shared SHARED + $ + $ + $ + $ + $ + $ + $<$:$> + ) + + set_target_properties(projectM_shared PROPERTIES + OUTPUT_NAME projectM + VERSION "${PROJECTM_LIB_VERSION}" + SOVERSION "${PROJECTM_SO_VERSION}" + EXPORT_NAME shared + FOLDER libprojectM + ) + + target_include_directories(projectM_shared + PUBLIC + "$" + "$" + "$" + "$" + "$" + "$" + "$" + ) + + target_link_libraries(projectM_shared + PRIVATE + GLM::GLM + PUBLIC + ${PROJECTM_OPENGL_LIBRARIES} + ${CMAKE_DL_LIBS} + ) + + if(ENABLE_THREADING) + target_link_libraries(projectM_shared + PUBLIC + Threads::Threads + ) + endif() + + if(ENABLE_LLVM) + target_link_libraries(projectM_shared + PUBLIC + LLVM::LLVM + ) + endif() + + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(projectM_shared + PUBLIC + "-framework CoreFoundation" + ) + endif() + + enable_target_linker_flags_if_supported(projectM_shared PRIVATE --no-undefined) + + install(TARGETS projectM_shared + EXPORT libprojectMTargets + LIBRARY DESTINATION "${PROJECTM_LIB_DIR}" + RUNTIME DESTINATION "${PROJECTM_LIB_DIR}" + ARCHIVE DESTINATION "${PROJECTM_LIB_DIR}" + COMPONENT Runtime + ) + + set(EXPORT_SHARED_LIB_TARGET projectM_shared) +endif() + +install(FILES + Common.hpp + PCM.hpp + dlldefs.h + event.h + fatal.h + projectM.hpp + DESTINATION "${PROJECTM_INCLUDE_DIR}/libprojectM" + COMPONENT Devel + ) + + + +# CMake target exports + +# For use from a local projectM build tree (without installing) +export(TARGETS + MilkdropPresetFactory + NativePresetFactory + Renderer + hlslparser + SOIL2 + projectM_static + ${EXPORT_SHARED_LIB_TARGET} + NAMESPACE libprojectM:: + FILE projectM-exports.cmake + ) + +export(PACKAGE libprojectM) + + + +# For use from an installed package (system install, vcpkg, homebrew etc.) +include(CMakePackageConfigHelpers) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/libprojectM/libprojectMConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion +) + +configure_package_config_file(libprojectMConfig.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/libprojectM/libprojectMConfig.cmake" + INSTALL_DESTINATION "${PROJECTM_LIB_DIR}/cmake/libprojectM" + PATH_VARS PROJECTM_BIN_DIR PROJECTM_INCLUDE_DIR PROJECTM_DATADIR_PATH + ) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/libprojectM/libprojectMConfigVersion.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/libprojectM/libprojectMConfig.cmake" + DESTINATION "${PROJECTM_LIB_DIR}/cmake/libprojectM" + COMPONENT Devel + ) + +install(EXPORT libprojectMTargets + FILE libprojectMTargets.cmake + NAMESPACE libprojectM:: + DESTINATION "${PROJECTM_LIB_DIR}/cmake/libprojectM" + COMPONENT Devel + ) + + + +# pkgconfig export +macro(set_pkg_config_path varname path) + if(IS_ABSOLUTE "${path}") + set(${varname} "${path}") + else() + set(${varname} "\${prefix}/${path}") + endif() +endmacro() + +set(PKGCONFIG_PREFIX "${CMAKE_INSTALL_PREFIX}") +set_pkg_config_path(PKGCONFIG_LIB_DIR "${PROJECTM_LIB_DIR}") +set_pkg_config_path(PKGCONFIG_INCLUDE_DIR "${PROJECTM_INCLUDE_DIR}") +set_pkg_config_path(PKGCONFIG_DATADIR_PATH "${PROJECTM_DATADIR_PATH}") + +configure_file(libprojectM.pc.cmake.in "${CMAKE_BINARY_DIR}/libprojectM.pc" @ONLY) +install(FILES "${CMAKE_BINARY_DIR}/libprojectM.pc" + DESTINATION "${PROJECTM_LIB_DIR}/pkgconfig" + COMPONENT Devel + ) + + + +# If the path is not absolute, need to add the prefix path (and hope the result is absolute). +if(NOT IS_ABSOLUTE PROJECTM_DATADIR_PATH) + set(PROJECTM_DATADIR_PATH_ABSOLUTE "${CMAKE_INSTALL_PREFIX}/${PROJECTM_DATADIR_PATH}") +else() + set(PROJECTM_DATADIR_PATH_ABSOLUTE "${PROJECTM_DATADIR_PATH}") +endif() + +configure_file(config.inp.cmake.in "${CMAKE_BINARY_DIR}/config.inp" @ONLY) +install(FILES "${CMAKE_BINARY_DIR}/config.inp" + DESTINATION "${PROJECTM_DATADIR_PATH}/" + COMPONENT Runtime + ) + +install(DIRECTORY "${CMAKE_SOURCE_DIR}/fonts" + DESTINATION "${PROJECTM_DATADIR_PATH}" + COMPONENT Runtime + ) + diff --git a/src/libprojectM/FileScanner.cpp b/src/libprojectM/FileScanner.cpp index 860d2a3044..ef7eddcc29 100644 --- a/src/libprojectM/FileScanner.cpp +++ b/src/libprojectM/FileScanner.cpp @@ -1,188 +1,188 @@ -// -// FileScanner.cpp -// libprojectM -// -// - -#include "FileScanner.hpp" - -FileScanner::FileScanner() {} - -FileScanner::FileScanner(std::vector &rootDirs, std::vector &extensions) : _rootDirs(rootDirs), _extensions(extensions) {} - -void FileScanner::scan(ScanCallback cb) { -#ifdef HAVE_FTS_H - scanPosix(cb); -#else - for (auto dir : _rootDirs) - scanGeneric(cb, dir.c_str()); -#endif -} - -void FileScanner::handleDirectoryError(std::string dir) { -#ifndef HAVE_FTS_H - std::cerr << "[PresetLoader] warning: errno unsupported on win32, etc platforms. fix me" << std::endl; -#else - - std::cerr << dir << " scan error: "; - - switch ( errno ) - { - case ENOENT: - std::cerr << "ENOENT error. The path \"" << dir << "\" probably does not exist. \"man open\" for more info." << std::endl; - break; - case ENOMEM: - std::cerr << "out of memory!" << std::endl; - abort(); - case ENOTDIR: - std::cerr << "directory specified is not a directory! Trying to continue..." << std::endl; - break; - case ENFILE: - std::cerr << "Your system has reached its open file limit. Trying to continue..." << std::endl; - break; - case EMFILE: - std::cerr << "too many files in use by projectM! Bailing!" << std::endl; - break; - case EACCES: - std::cerr << "permissions issue reading the specified preset directory." << std::endl; - break; - default: - break; - } -#endif -} - -std::string FileScanner::extensionMatches(std::string &filename) { - // returns file name without extension - // TODO: optimize me - - std::string lowerCaseFileName(filename); - std::transform(lowerCaseFileName.begin(), lowerCaseFileName.end(), lowerCaseFileName.begin(), tolower); - - // Remove extension - for (auto ext : _extensions) - { - size_t found = lowerCaseFileName.find(ext); - if (found != std::string::npos) - { - std::string name = filename; - name.replace(int(found), ext.size(), ""); - return name; - } - } - - return {}; -} - -bool FileScanner::isValidFilename(std::string &filename) { - if (filename.find("__MACOSX") != std::string::npos) return false; - return true; -} - -// generic implementation using dirent -void FileScanner::scanGeneric(ScanCallback cb, const char *currentDir) { - DIR * m_dir; - - // Allocate a new a stream given the current directory name - if ((m_dir = opendir(currentDir)) == NULL) - { - return; // no files found in here - } - - struct dirent * dir_entry; - - while ((dir_entry = readdir(m_dir)) != NULL) - { - // Convert char * to friendly string - std::string filename(dir_entry->d_name); - - // Some sanity checks - if (! isValidFilename(filename)) continue; - if (filename.length() > 0 && filename[0] == '.') - continue; - - std::string fullPath = std::string(currentDir) + PATH_SEPARATOR + filename; - - if (dir_entry->d_type == DT_DIR) { - // recurse into dir - scanGeneric(cb, fullPath.c_str()); - continue; - } else if (dir_entry->d_type != DT_REG && dir_entry->d_type != DT_LNK) { - // not regular file/link - continue; - } - - auto nameMatched = extensionMatches(filename); - if (! nameMatched.empty()) - cb(fullPath, nameMatched); - } - - if (m_dir) - { - closedir(m_dir); - m_dir = 0; - } -} - -#ifdef HAVE_FTS_H -// more optimized posix "fts" directory traversal -int fts_compare(const FTSENT** one, const FTSENT** two) { - return (strcmp((*one)->fts_name, (*two)->fts_name)); -} -#endif - -void FileScanner::scanPosix(ScanCallback cb) { -#ifdef HAVE_FTS_H - - // efficient directory traversal - FTS* fileSystem = NULL; - FTSENT *node = NULL; - - // list of directories to scan - auto rootDirCount = _rootDirs.size(); - char **dirList = (char **)malloc(sizeof(char*) * (rootDirCount + 1)); - for (unsigned long i = 0; i < rootDirCount; i++) { - dirList[i] = (char *) _rootDirs[i].c_str(); - } - dirList[rootDirCount] = NULL; - - // initialize file hierarchy traversal - fileSystem = fts_open(dirList, FTS_LOGICAL|FTS_NOCHDIR|FTS_NOSTAT, &fts_compare); - if (fileSystem == NULL) { - std::string s; - for (std::size_t i = 0; i < _rootDirs.size(); i++) - s += _rootDirs[i] + ' '; - handleDirectoryError(s); - - free(dirList); - return; - } - - std::string path, name, nameMatched; - - // traverse dirList - while( (node = fts_read(fileSystem)) != NULL) { - switch (node->fts_info) { - case FTS_F: - case FTS_SL: - case FTS_NSOK: - // found a file - path = std::string(node->fts_path); - name = std::string(node->fts_name); - - if (!isValidFilename(path) || !isValidFilename(name)) break; - - // check extension - nameMatched = extensionMatches(name); - if (! nameMatched.empty()) - cb(path, nameMatched); - break; - default: - break; - } - } - fts_close(fileSystem); - free(dirList); - -#endif -} +// +// FileScanner.cpp +// libprojectM +// +// + +#include "FileScanner.hpp" + +FileScanner::FileScanner() {} + +FileScanner::FileScanner(std::vector &rootDirs, std::vector &extensions) : _rootDirs(rootDirs), _extensions(extensions) {} + +void FileScanner::scan(ScanCallback cb) { +#if HAVE_FTS_H + scanPosix(cb); +#else + for (auto dir : _rootDirs) + scanGeneric(cb, dir.c_str()); +#endif +} + +void FileScanner::handleDirectoryError(std::string dir) { +#ifndef HAVE_FTS_H + std::cerr << "[PresetLoader] warning: errno unsupported on win32, etc platforms. fix me" << std::endl; +#else + + std::cerr << dir << " scan error: "; + + switch ( errno ) + { + case ENOENT: + std::cerr << "ENOENT error. The path \"" << dir << "\" probably does not exist. \"man open\" for more info." << std::endl; + break; + case ENOMEM: + std::cerr << "out of memory!" << std::endl; + abort(); + case ENOTDIR: + std::cerr << "directory specified is not a directory! Trying to continue..." << std::endl; + break; + case ENFILE: + std::cerr << "Your system has reached its open file limit. Trying to continue..." << std::endl; + break; + case EMFILE: + std::cerr << "too many files in use by projectM! Bailing!" << std::endl; + break; + case EACCES: + std::cerr << "permissions issue reading the specified preset directory." << std::endl; + break; + default: + break; + } +#endif +} + +std::string FileScanner::extensionMatches(std::string &filename) { + // returns file name without extension + // TODO: optimize me + + std::string lowerCaseFileName(filename); + std::transform(lowerCaseFileName.begin(), lowerCaseFileName.end(), lowerCaseFileName.begin(), tolower); + + // Remove extension + for (auto ext : _extensions) + { + size_t found = lowerCaseFileName.find(ext); + if (found != std::string::npos) + { + std::string name = filename; + name.replace(int(found), ext.size(), ""); + return name; + } + } + + return {}; +} + +bool FileScanner::isValidFilename(std::string &filename) { + if (filename.find("__MACOSX") != std::string::npos) return false; + return true; +} + +// generic implementation using dirent +void FileScanner::scanGeneric(ScanCallback cb, const char *currentDir) { + DIR * m_dir; + + // Allocate a new a stream given the current directory name + if ((m_dir = opendir(currentDir)) == NULL) + { + return; // no files found in here + } + + struct dirent * dir_entry; + + while ((dir_entry = readdir(m_dir)) != NULL) + { + // Convert char * to friendly string + std::string filename(dir_entry->d_name); + + // Some sanity checks + if (! isValidFilename(filename)) continue; + if (filename.length() > 0 && filename[0] == '.') + continue; + + std::string fullPath = std::string(currentDir) + PATH_SEPARATOR + filename; + + if (dir_entry->d_type == DT_DIR) { + // recurse into dir + scanGeneric(cb, fullPath.c_str()); + continue; + } else if (dir_entry->d_type != DT_REG && dir_entry->d_type != DT_LNK) { + // not regular file/link + continue; + } + + auto nameMatched = extensionMatches(filename); + if (! nameMatched.empty()) + cb(fullPath, nameMatched); + } + + if (m_dir) + { + closedir(m_dir); + m_dir = 0; + } +} + +#if HAVE_FTS_H +// more optimized posix "fts" directory traversal +int fts_compare(const FTSENT** one, const FTSENT** two) { + return (strcmp((*one)->fts_name, (*two)->fts_name)); +} +#endif + +void FileScanner::scanPosix(ScanCallback cb) { +#if HAVE_FTS_H + + // efficient directory traversal + FTS* fileSystem = NULL; + FTSENT *node = NULL; + + // list of directories to scan + auto rootDirCount = _rootDirs.size(); + char **dirList = (char **)malloc(sizeof(char*) * (rootDirCount + 1)); + for (unsigned long i = 0; i < rootDirCount; i++) { + dirList[i] = (char *) _rootDirs[i].c_str(); + } + dirList[rootDirCount] = NULL; + + // initialize file hierarchy traversal + fileSystem = fts_open(dirList, FTS_LOGICAL|FTS_NOCHDIR|FTS_NOSTAT, &fts_compare); + if (fileSystem == NULL) { + std::string s; + for (std::size_t i = 0; i < _rootDirs.size(); i++) + s += _rootDirs[i] + ' '; + handleDirectoryError(s); + + free(dirList); + return; + } + + std::string path, name, nameMatched; + + // traverse dirList + while( (node = fts_read(fileSystem)) != NULL) { + switch (node->fts_info) { + case FTS_F: + case FTS_SL: + case FTS_NSOK: + // found a file + path = std::string(node->fts_path); + name = std::string(node->fts_name); + + if (!isValidFilename(path) || !isValidFilename(name)) break; + + // check extension + nameMatched = extensionMatches(name); + if (! nameMatched.empty()) + cb(path, nameMatched); + break; + default: + break; + } + } + fts_close(fileSystem); + free(dirList); + +#endif +} diff --git a/src/libprojectM/FileScanner.hpp b/src/libprojectM/FileScanner.hpp index a2aeeb0d22..20f0e1835e 100644 --- a/src/libprojectM/FileScanner.hpp +++ b/src/libprojectM/FileScanner.hpp @@ -1,49 +1,49 @@ -// -// FileScanner.hpp -// libprojectM -// -// Cross-platform directory traversal with filtering by extension - -#ifndef FileScanner_hpp -#define FileScanner_hpp - -#include -#include -#include -#include -#include "Common.hpp" -#include - -#ifdef HAVE_FTS_H -#include -extern "C" -{ -#include -#include -} -#else -#include "dirent.h" -#endif - -typedef std::function ScanCallback; - -class FileScanner -{ -public: - FileScanner(); - FileScanner(std::vector &rootDirs, std::vector &extensions); - - void scan(ScanCallback cb); - std::string extensionMatches(std::string &filename); - -private: - std::vector _rootDirs; - std::vector _extensions; - - void scanGeneric(ScanCallback cb, const char *dir); - void scanPosix(ScanCallback cb); - void handleDirectoryError(std::string dir); - bool isValidFilename(std::string &filename); -}; - -#endif /* FileScanner_hpp */ +// +// FileScanner.hpp +// libprojectM +// +// Cross-platform directory traversal with filtering by extension + +#ifndef FileScanner_hpp +#define FileScanner_hpp + +#include +#include +#include +#include +#include "Common.hpp" +#include + +#if HAVE_FTS_H +#include +extern "C" +{ +#include +#include +} +#else +#include "dirent.h" +#endif + +typedef std::function ScanCallback; + +class FileScanner +{ +public: + FileScanner(); + FileScanner(std::vector &rootDirs, std::vector &extensions); + + void scan(ScanCallback cb); + std::string extensionMatches(std::string &filename); + +private: + std::vector _rootDirs; + std::vector _extensions; + + void scanGeneric(ScanCallback cb, const char *dir); + void scanPosix(ScanCallback cb); + void handleDirectoryError(std::string dir); + bool isValidFilename(std::string &filename); +}; + +#endif /* FileScanner_hpp */ diff --git a/src/libprojectM/MilkdropPresetFactory/CMakeLists.txt b/src/libprojectM/MilkdropPresetFactory/CMakeLists.txt new file mode 100644 index 0000000000..f1031a84d0 --- /dev/null +++ b/src/libprojectM/MilkdropPresetFactory/CMakeLists.txt @@ -0,0 +1,64 @@ +add_library(MilkdropPresetFactory OBJECT + BuiltinFuncs.cpp + BuiltinFuncs.hpp + BuiltinParams.cpp + BuiltinParams.hpp + CustomShape.cpp + CustomShape.hpp + CustomWave.cpp + CustomWave.hpp + CValue.hpp + Eval.cpp + Eval.hpp + Expr.cpp + Expr.hpp + Func.cpp + Func.hpp + IdlePreset.cpp + IdlePreset.hpp + InitCond.cpp + InitCond.hpp + InitCondUtils.hpp + JitContext.hpp + MilkdropPreset.cpp + MilkdropPresetFactory.cpp + MilkdropPresetFactory.hpp + MilkdropPreset.hpp + Param.cpp + Param.hpp + ParamUtils.hpp + Parser.cpp + Parser.hpp + PerFrameEqn.cpp + PerFrameEqn.hpp + PerPixelEqn.cpp + PerPixelEqn.hpp + PerPointEqn.cpp + PerPointEqn.hpp + PresetFrameIO.cpp + PresetFrameIO.hpp + ) + +target_include_directories(MilkdropPresetFactory + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/.." + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" + ) + +target_link_libraries(MilkdropPresetFactory + PUBLIC + GLM::GLM + ${PROJECTM_OPENGL_LIBRARIES} + ) + +set_target_properties(MilkdropPresetFactory PROPERTIES + FOLDER libprojectM + ) + +if(ENABLE_LLVM) + target_link_libraries(MilkdropPresetFactory + PUBLIC + LLVM::LLVM + ) +endif() diff --git a/src/libprojectM/NativePresetFactory/CMakeLists.txt b/src/libprojectM/NativePresetFactory/CMakeLists.txt new file mode 100644 index 0000000000..3e638ed140 --- /dev/null +++ b/src/libprojectM/NativePresetFactory/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(NativePresetFactory OBJECT + MilkdropCompatability.hpp + NativePreset.hpp + NativePresetFactory.cpp + NativePresetFactory.hpp + ) + +target_include_directories(NativePresetFactory + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/.." + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" + ) + +target_link_libraries(NativePresetFactory + PUBLIC + GLM::GLM + ${PROJECTM_OPENGL_LIBRARIES} + ) + +set_target_properties(NativePresetFactory PROPERTIES + FOLDER libprojectM + ) diff --git a/src/libprojectM/Renderer/CMakeLists.txt b/src/libprojectM/Renderer/CMakeLists.txt new file mode 100644 index 0000000000..2ce3512094 --- /dev/null +++ b/src/libprojectM/Renderer/CMakeLists.txt @@ -0,0 +1,62 @@ +add_subdirectory(hlslparser) +add_subdirectory(SOIL2) + +add_library(Renderer OBJECT + BeatDetect.cpp + BeatDetect.hpp + Filters.cpp + Filters.hpp + MilkdropWaveform.cpp + MilkdropWaveform.hpp + PerlinNoise.cpp + PerlinNoise.hpp + PerlinNoiseWithAlpha.cpp + PerlinNoiseWithAlpha.hpp + PerPixelMesh.cpp + PerPixelMesh.hpp + PipelineContext.cpp + PipelineContext.hpp + Pipeline.cpp + Pipeline.hpp + Renderable.cpp + Renderable.hpp + Renderer.cpp + Renderer.hpp + RenderItemDistanceMetric.cpp + RenderItemDistanceMetric.hpp + RenderItemMatcher.cpp + RenderItemMatcher.hpp + RenderItemMergeFunction.hpp + Shader.cpp + ShaderEngine.cpp + ShaderEngine.hpp + Shader.hpp + StaticGlShaders.cpp + Texture.cpp + Texture.hpp + TextureManager.cpp + TextureManager.hpp + Transformation.hpp + VideoEcho.cpp + VideoEcho.hpp + Waveform.cpp + Waveform.hpp + ) + +target_include_directories(Renderer + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/.." + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" + ) + +target_link_libraries(Renderer + PUBLIC + GLM::GLM + hlslparser + SOIL2 + ) + +set_target_properties(Renderer PROPERTIES + FOLDER libprojectM + ) diff --git a/src/libprojectM/Renderer/SOIL2/CMakeLists.txt b/src/libprojectM/Renderer/SOIL2/CMakeLists.txt new file mode 100644 index 0000000000..7bcb49f7ee --- /dev/null +++ b/src/libprojectM/Renderer/SOIL2/CMakeLists.txt @@ -0,0 +1,36 @@ +add_library(SOIL2 OBJECT + etc1_utils.c + etc1_utils.h + image_DXT.c + image_DXT.h + image_helper.c + image_helper.h + pkm_helper.h + pvr_helper.h + SOIL2.c + SOIL2.h + stb_image.h + stb_image_write.h + stbi_DDS.h + stbi_DDS_c.h + stbi_ext.h + stbi_ext_c.h + stbi_pkm.h + stbi_pkm_c.h + stbi_pvr.h + stbi_pvr_c.h + ) + +target_include_directories(SOIL2 + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" + ) + +target_link_libraries(SOIL2 + PUBLIC + ${PROJECTM_OPENGL_LIBRARIES} + ) + +set_target_properties(SOIL2 PROPERTIES + FOLDER libprojectM + ) diff --git a/src/libprojectM/Renderer/hlslparser/CMakeLists.txt b/src/libprojectM/Renderer/hlslparser/CMakeLists.txt new file mode 100644 index 0000000000..35f4bb645d --- /dev/null +++ b/src/libprojectM/Renderer/hlslparser/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(hlslparser OBJECT + src/CodeWriter.cpp + src/CodeWriter.h + src/Engine.cpp + src/Engine.h + src/GLSLGenerator.cpp + src/GLSLGenerator.h + src/HLSLParser.cpp + src/HLSLParser.h + src/HLSLTokenizer.cpp + src/HLSLTokenizer.h + src/HLSLTree.cpp + src/HLSLTree.h + ) + +target_include_directories(hlslparser + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/src" + ) + +set_target_properties(hlslparser PROPERTIES + FOLDER libprojectM + ) diff --git a/src/libprojectM/config.inp.cmake.in b/src/libprojectM/config.inp.cmake.in new file mode 100644 index 0000000000..d4e6402a69 --- /dev/null +++ b/src/libprojectM/config.inp.cmake.in @@ -0,0 +1,22 @@ +# config.inp +# Configuration File for projectM + +#Texture Size = 1024 # Size of internal rendering texture + +Mesh X = 220 # Width of PerPixel Equation mesh +Mesh Y = 125 # Height of PerPixel Equation mesh +FPS = 35 # Frames Per Second +Fullscreen = false +Window Width = 512 # startup window width +Window Height = 512 # startup window height + +Smooth Transition Duration = 5 # in seconds +Preset Duration = 30 # in seconds +Easter Egg Parameter = 1 + +Hard Cut Sensitivity = 10 # Lower to make hard cuts more frequent +Aspect Correction = true # Custom Shape Aspect Correction + +Preset Path = @PROJECTM_DATADIR_PATH_ABSOLUTE@/presets # preset location +Title Font = Vera.ttf +Menu Font = VeraMono.ttf diff --git a/src/libprojectM/libprojectM.pc.cmake.in b/src/libprojectM/libprojectM.pc.cmake.in new file mode 100644 index 0000000000..81084991da --- /dev/null +++ b/src/libprojectM/libprojectM.pc.cmake.in @@ -0,0 +1,13 @@ +prefix=@PKGCONFIG_PREFIX@ +exec_prefix=${prefix} +libdir=@PKGCONFIG_LIB_DIR@ +includedir=@PKGCONFIG_INCLUDE_DIR@ +pkgdatadir=@PKGCONFIG_DATADIR_PATH@ +sysconfdir=@PKGCONFIG_DATADIR_PATH@ + +Name: libprojectM +Version: @PROJECT_VERSION@ +Description: projectM - OpenGL Milkdrop +Requires: +Libs: -L${libdir} -lprojectM +Cflags: -I${includedir} diff --git a/src/libprojectM/libprojectMConfig.cmake.in b/src/libprojectM/libprojectMConfig.cmake.in new file mode 100644 index 0000000000..f64c451153 --- /dev/null +++ b/src/libprojectM/libprojectMConfig.cmake.in @@ -0,0 +1,14 @@ +set(libprojectM_VERSION @PROJECT_VERSION@) + +@PACKAGE_INIT@ + +set_and_check(libprojectM_BIN_DIR "@PACKAGE_PROJECTM_BIN_DIR@") +set_and_check(libprojectM_INCLUDE_DIR "@PACKAGE_PROJECTM_INCLUDE_DIR@") +set_and_check(libprojectM_DATA_DIR "@PACKAGE_PROJECTM_DATADIR_PATH@") + +include(CMakeFindDependencyMacro) + +find_dependency(OpenGL) +if("@ENABLE_THREADING@") # ENABLE_THREADING + find_dependency(Threads) +endif() diff --git a/src/libprojectM/msvc/dirent.h b/src/libprojectM/msvc/dirent.h new file mode 100644 index 0000000000..c885b398a7 --- /dev/null +++ b/src/libprojectM/msvc/dirent.h @@ -0,0 +1,1160 @@ +/* + * Dirent interface for Microsoft Visual Studio + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#ifndef DIRENT_H +#define DIRENT_H + +/* Hide warnings about unreferenced local functions */ +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +#elif defined(_MSC_VER) +# pragma warning(disable:4505) +#elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +#endif + +/* + * Include windows.h without Windows Sockets 1.1 to prevent conflicts with + * Windows Sockets 2.0. + */ +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Indicates that d_type field is available in dirent structure */ +#define _DIRENT_HAVE_D_TYPE + +/* Indicates that d_namlen field is available in dirent structure */ +#define _DIRENT_HAVE_D_NAMLEN + +/* Entries missing from MSVC 6.0 */ +#if !defined(FILE_ATTRIBUTE_DEVICE) +# define FILE_ATTRIBUTE_DEVICE 0x40 +#endif + +/* File type and permission flags for stat(), general mask */ +#if !defined(S_IFMT) +# define S_IFMT _S_IFMT +#endif + +/* Directory bit */ +#if !defined(S_IFDIR) +# define S_IFDIR _S_IFDIR +#endif + +/* Character device bit */ +#if !defined(S_IFCHR) +# define S_IFCHR _S_IFCHR +#endif + +/* Pipe bit */ +#if !defined(S_IFFIFO) +# define S_IFFIFO _S_IFFIFO +#endif + +/* Regular file bit */ +#if !defined(S_IFREG) +# define S_IFREG _S_IFREG +#endif + +/* Read permission */ +#if !defined(S_IREAD) +# define S_IREAD _S_IREAD +#endif + +/* Write permission */ +#if !defined(S_IWRITE) +# define S_IWRITE _S_IWRITE +#endif + +/* Execute permission */ +#if !defined(S_IEXEC) +# define S_IEXEC _S_IEXEC +#endif + +/* Pipe */ +#if !defined(S_IFIFO) +# define S_IFIFO _S_IFIFO +#endif + +/* Block device */ +#if !defined(S_IFBLK) +# define S_IFBLK 0 +#endif + +/* Link */ +#if !defined(S_IFLNK) +# define S_IFLNK 0 +#endif + +/* Socket */ +#if !defined(S_IFSOCK) +# define S_IFSOCK 0 +#endif + +/* Read user permission */ +#if !defined(S_IRUSR) +# define S_IRUSR S_IREAD +#endif + +/* Write user permission */ +#if !defined(S_IWUSR) +# define S_IWUSR S_IWRITE +#endif + +/* Execute user permission */ +#if !defined(S_IXUSR) +# define S_IXUSR 0 +#endif + +/* Read group permission */ +#if !defined(S_IRGRP) +# define S_IRGRP 0 +#endif + +/* Write group permission */ +#if !defined(S_IWGRP) +# define S_IWGRP 0 +#endif + +/* Execute group permission */ +#if !defined(S_IXGRP) +# define S_IXGRP 0 +#endif + +/* Read others permission */ +#if !defined(S_IROTH) +# define S_IROTH 0 +#endif + +/* Write others permission */ +#if !defined(S_IWOTH) +# define S_IWOTH 0 +#endif + +/* Execute others permission */ +#if !defined(S_IXOTH) +# define S_IXOTH 0 +#endif + +/* Maximum length of file name */ +#if !defined(PATH_MAX) +# define PATH_MAX MAX_PATH +#endif +#if !defined(FILENAME_MAX) +# define FILENAME_MAX MAX_PATH +#endif +#if !defined(NAME_MAX) +# define NAME_MAX FILENAME_MAX +#endif + +/* File type flags for d_type */ +#define DT_UNKNOWN 0 +#define DT_REG S_IFREG +#define DT_DIR S_IFDIR +#define DT_FIFO S_IFIFO +#define DT_SOCK S_IFSOCK +#define DT_CHR S_IFCHR +#define DT_BLK S_IFBLK +#define DT_LNK S_IFLNK + +/* Macros for converting between st_mode and d_type */ +#define IFTODT(mode) ((mode) & S_IFMT) +#define DTTOIF(type) (type) + +/* + * File type macros. Note that block devices, sockets and links cannot be + * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are + * only defined for compatibility. These macros should always return false + * on Windows. + */ +#if !defined(S_ISFIFO) +# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#endif +#if !defined(S_ISDIR) +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !defined(S_ISREG) +# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif +#if !defined(S_ISLNK) +# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#endif +#if !defined(S_ISSOCK) +# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) +#endif +#if !defined(S_ISCHR) +# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) +#endif +#if !defined(S_ISBLK) +# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) +#endif + +/* Return the exact length of the file name without zero terminator */ +#define _D_EXACT_NAMLEN(p) ((p)->d_namlen) + +/* Return the maximum size of a file name */ +#define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1) + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Wide-character version */ +struct _wdirent { + /* Always zero */ + long d_ino; + + /* File position within stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + wchar_t d_name[PATH_MAX+1]; +}; +typedef struct _wdirent _wdirent; + +struct _WDIR { + /* Current directory entry */ + struct _wdirent ent; + + /* Private file data */ + WIN32_FIND_DATAW data; + + /* True if data is valid */ + int cached; + + /* Win32 search handle */ + HANDLE handle; + + /* Initial directory name */ + wchar_t *patt; +}; +typedef struct _WDIR _WDIR; + +/* Multi-byte character version */ +struct dirent { + /* Always zero */ + long d_ino; + + /* File position within stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + char d_name[PATH_MAX+1]; +}; +typedef struct dirent dirent; + +struct DIR { + struct dirent ent; + struct _WDIR *wdirp; +}; +typedef struct DIR DIR; + + +/* Dirent functions */ +static DIR *opendir (const char *dirname); +static _WDIR *_wopendir (const wchar_t *dirname); + +static struct dirent *readdir (DIR *dirp); +static struct _wdirent *_wreaddir (_WDIR *dirp); + +static int readdir_r( + DIR *dirp, struct dirent *entry, struct dirent **result); +static int _wreaddir_r( + _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result); + +static int closedir (DIR *dirp); +static int _wclosedir (_WDIR *dirp); + +static void rewinddir (DIR* dirp); +static void _wrewinddir (_WDIR* dirp); + +static int scandir (const char *dirname, struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)); + +static int alphasort (const struct dirent **a, const struct dirent **b); + +static int versionsort (const struct dirent **a, const struct dirent **b); + + +/* For compatibility with Symbian */ +#define wdirent _wdirent +#define WDIR _WDIR +#define wopendir _wopendir +#define wreaddir _wreaddir +#define wclosedir _wclosedir +#define wrewinddir _wrewinddir + + +/* Internal utility functions */ +static WIN32_FIND_DATAW *dirent_first (_WDIR *dirp); +static WIN32_FIND_DATAW *dirent_next (_WDIR *dirp); + +static int dirent_mbstowcs_s( + size_t *pReturnValue, + wchar_t *wcstr, + size_t sizeInWords, + const char *mbstr, + size_t count); + +static int dirent_wcstombs_s( + size_t *pReturnValue, + char *mbstr, + size_t sizeInBytes, + const wchar_t *wcstr, + size_t count); + +static void dirent_set_errno (int error); + + +/* + * Open directory stream DIRNAME for read and return a pointer to the + * internal working area that is used to retrieve individual directory + * entries. + */ +static _WDIR* +_wopendir( + const wchar_t *dirname) +{ + _WDIR *dirp; + DWORD n; + wchar_t *p; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno (ENOENT); + return NULL; + } + + /* Allocate new _WDIR structure */ + dirp = (_WDIR*) malloc (sizeof (struct _WDIR)); + if (!dirp) { + return NULL; + } + + /* Reset _WDIR structure */ + dirp->handle = INVALID_HANDLE_VALUE; + dirp->patt = NULL; + dirp->cached = 0; + + /* + * Compute the length of full path plus zero terminator + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + n = GetFullPathNameW (dirname, 0, NULL, NULL); +#else + /* WinRT */ + n = wcslen (dirname); +#endif + + /* Allocate room for absolute directory name and search pattern */ + dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16); + if (dirp->patt == NULL) { + goto exit_closedir; + } + + /* + * Convert relative directory name to an absolute one. This + * allows rewinddir() to function correctly even when current + * working directory is changed between opendir() and rewinddir(). + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + n = GetFullPathNameW (dirname, n, dirp->patt, NULL); + if (n <= 0) { + goto exit_closedir; + } +#else + /* WinRT */ + wcsncpy_s (dirp->patt, n+1, dirname, n); +#endif + + /* Append search pattern \* to the directory name */ + p = dirp->patt + n; + switch (p[-1]) { + case '\\': + case '/': + case ':': + /* Directory ends in path separator, e.g. c:\temp\ */ + /*NOP*/; + break; + + default: + /* Directory name doesn't end in path separator */ + *p++ = '\\'; + } + *p++ = '*'; + *p = '\0'; + + /* Open directory stream and retrieve the first entry */ + if (!dirent_first (dirp)) { + goto exit_closedir; + } + + /* Success */ + return dirp; + + /* Failure */ +exit_closedir: + _wclosedir (dirp); + return NULL; +} + +/* + * Read next directory entry. + * + * Returns pointer to static directory entry which may be overwritten by + * subsequent calls to _wreaddir(). + */ +static struct _wdirent* +_wreaddir( + _WDIR *dirp) +{ + struct _wdirent *entry; + + /* + * Read directory entry to buffer. We can safely ignore the return value + * as entry will be set to NULL in case of error. + */ + (void) _wreaddir_r (dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry. + * + * Returns zero on success. If end of directory stream is reached, then sets + * result to NULL and returns zero. + */ +static int +_wreaddir_r( + _WDIR *dirp, + struct _wdirent *entry, + struct _wdirent **result) +{ + WIN32_FIND_DATAW *datap; + + /* Read next directory entry */ + datap = dirent_next (dirp); + if (datap) { + size_t n; + DWORD attr; + + /* + * Copy file name as wide-character string. If the file name is too + * long to fit in to the destination buffer, then truncate file name + * to PATH_MAX characters and zero-terminate the buffer. + */ + n = 0; + while (n < PATH_MAX && datap->cFileName[n] != 0) { + entry->d_name[n] = datap->cFileName[n]; + n++; + } + entry->d_name[n] = 0; + + /* Length of file name excluding zero terminator */ + entry->d_namlen = n; + + /* File type */ + attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { + entry->d_type = DT_CHR; + } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { + entry->d_type = DT_DIR; + } else { + entry->d_type = DT_REG; + } + + /* Reset dummy fields */ + entry->d_ino = 0; + entry->d_off = 0; + entry->d_reclen = sizeof (struct _wdirent); + + /* Set result address */ + *result = entry; + + } else { + + /* Return NULL to indicate end of directory */ + *result = NULL; + + } + + return /*OK*/0; +} + +/* + * Close directory stream opened by opendir() function. This invalidates the + * DIR structure as well as any directory entry read previously by + * _wreaddir(). + */ +static int +_wclosedir( + _WDIR *dirp) +{ + int ok; + if (dirp) { + + /* Release search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) { + FindClose (dirp->handle); + } + + /* Release search pattern */ + free (dirp->patt); + + /* Release directory structure */ + free (dirp); + ok = /*success*/0; + + } else { + + /* Invalid directory stream */ + dirent_set_errno (EBADF); + ok = /*failure*/-1; + + } + return ok; +} + +/* + * Rewind directory stream such that _wreaddir() returns the very first + * file name again. + */ +static void +_wrewinddir( + _WDIR* dirp) +{ + if (dirp) { + /* Release existing search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) { + FindClose (dirp->handle); + } + + /* Open new search handle */ + dirent_first (dirp); + } +} + +/* Get first directory entry (internal) */ +static WIN32_FIND_DATAW* +dirent_first( + _WDIR *dirp) +{ + WIN32_FIND_DATAW *datap; + DWORD error; + + /* Open directory and retrieve the first entry */ + dirp->handle = FindFirstFileExW( + dirp->patt, FindExInfoStandard, &dirp->data, + FindExSearchNameMatch, NULL, 0); + if (dirp->handle != INVALID_HANDLE_VALUE) { + + /* a directory entry is now waiting in memory */ + datap = &dirp->data; + dirp->cached = 1; + + } else { + + /* Failed to open directory: no directory entry in memory */ + dirp->cached = 0; + datap = NULL; + + /* Set error code */ + error = GetLastError (); + switch (error) { + case ERROR_ACCESS_DENIED: + /* No read access to directory */ + dirent_set_errno (EACCES); + break; + + case ERROR_DIRECTORY: + /* Directory name is invalid */ + dirent_set_errno (ENOTDIR); + break; + + case ERROR_PATH_NOT_FOUND: + default: + /* Cannot find the file */ + dirent_set_errno (ENOENT); + } + + } + return datap; +} + +/* + * Get next directory entry (internal). + * + * Returns + */ +static WIN32_FIND_DATAW* +dirent_next( + _WDIR *dirp) +{ + WIN32_FIND_DATAW *p; + + /* Get next directory entry */ + if (dirp->cached != 0) { + + /* A valid directory entry already in memory */ + p = &dirp->data; + dirp->cached = 0; + + } else if (dirp->handle != INVALID_HANDLE_VALUE) { + + /* Get the next directory entry from stream */ + if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) { + /* Got a file */ + p = &dirp->data; + } else { + /* The very last entry has been processed or an error occurred */ + FindClose (dirp->handle); + dirp->handle = INVALID_HANDLE_VALUE; + p = NULL; + } + + } else { + + /* End of directory stream reached */ + p = NULL; + + } + + return p; +} + +/* + * Open directory stream using plain old C-string. + */ +static DIR* +opendir( + const char *dirname) +{ + struct DIR *dirp; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno (ENOENT); + return NULL; + } + + /* Allocate memory for DIR structure */ + dirp = (DIR*) malloc (sizeof (struct DIR)); + if (!dirp) { + return NULL; + } + { + int error; + wchar_t wname[PATH_MAX + 1]; + size_t n; + + /* Convert directory name to wide-character string */ + error = dirent_mbstowcs_s( + &n, wname, PATH_MAX + 1, dirname, PATH_MAX + 1); + if (error) { + /* + * Cannot convert file name to wide-character string. This + * occurs if the string contains invalid multi-byte sequences or + * the output buffer is too small to contain the resulting + * string. + */ + goto exit_free; + } + + + /* Open directory stream using wide-character name */ + dirp->wdirp = _wopendir (wname); + if (!dirp->wdirp) { + goto exit_free; + } + + } + + /* Success */ + return dirp; + + /* Failure */ +exit_free: + free (dirp); + return NULL; +} + +/* + * Read next directory entry. + */ +static struct dirent* +readdir( + DIR *dirp) +{ + struct dirent *entry; + + /* + * Read directory entry to buffer. We can safely ignore the return value + * as entry will be set to NULL in case of error. + */ + (void) readdir_r (dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry into called-allocated buffer. + * + * Returns zero on success. If the end of directory stream is reached, then + * sets result to NULL and returns zero. + */ +static int +readdir_r( + DIR *dirp, + struct dirent *entry, + struct dirent **result) +{ + WIN32_FIND_DATAW *datap; + + /* Read next directory entry */ + datap = dirent_next (dirp->wdirp); + if (datap) { + size_t n; + int error; + + /* Attempt to convert file name to multi-byte string */ + error = dirent_wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, datap->cFileName, PATH_MAX + 1); + + /* + * If the file name cannot be represented by a multi-byte string, + * then attempt to use old 8+3 file name. This allows traditional + * Unix-code to access some file names despite of unicode + * characters, although file names may seem unfamiliar to the user. + * + * Be ware that the code below cannot come up with a short file + * name unless the file system provides one. At least + * VirtualBox shared folders fail to do this. + */ + if (error && datap->cAlternateFileName[0] != '\0') { + error = dirent_wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, + datap->cAlternateFileName, PATH_MAX + 1); + } + + if (!error) { + DWORD attr; + + /* Length of file name excluding zero terminator */ + entry->d_namlen = n - 1; + + /* File attributes */ + attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { + entry->d_type = DT_CHR; + } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { + entry->d_type = DT_DIR; + } else { + entry->d_type = DT_REG; + } + + /* Reset dummy fields */ + entry->d_ino = 0; + entry->d_off = 0; + entry->d_reclen = sizeof (struct dirent); + + } else { + + /* + * Cannot convert file name to multi-byte string so construct + * an erroneous directory entry and return that. Note that + * we cannot return NULL as that would stop the processing + * of directory entries completely. + */ + entry->d_name[0] = '?'; + entry->d_name[1] = '\0'; + entry->d_namlen = 1; + entry->d_type = DT_UNKNOWN; + entry->d_ino = 0; + entry->d_off = -1; + entry->d_reclen = 0; + + } + + /* Return pointer to directory entry */ + *result = entry; + + } else { + + /* No more directory entries */ + *result = NULL; + + } + + return /*OK*/0; +} + +/* + * Close directory stream. + */ +static int +closedir( + DIR *dirp) +{ + int ok; + if (dirp) { + + /* Close wide-character directory stream */ + ok = _wclosedir (dirp->wdirp); + dirp->wdirp = NULL; + + /* Release multi-byte character version */ + free (dirp); + + } else { + + /* Invalid directory stream */ + dirent_set_errno (EBADF); + ok = /*failure*/-1; + + } + return ok; +} + +/* + * Rewind directory stream to beginning. + */ +static void +rewinddir( + DIR* dirp) +{ + /* Rewind wide-character string directory stream */ + _wrewinddir (dirp->wdirp); +} + +/* + * Scan directory for entries. + */ +static int +scandir( + const char *dirname, + struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)) +{ + struct dirent **files = NULL; + size_t size = 0; + size_t allocated = 0; + const size_t init_size = 1; + DIR *dir = NULL; + struct dirent *entry; + struct dirent *tmp = NULL; + size_t i; + int result = 0; + + /* Open directory stream */ + dir = opendir (dirname); + if (dir) { + + /* Read directory entries to memory */ + while (1) { + + /* Enlarge pointer table to make room for another pointer */ + if (size >= allocated) { + void *p; + size_t num_entries; + + /* Compute number of entries in the enlarged pointer table */ + if (size < init_size) { + /* Allocate initial pointer table */ + num_entries = init_size; + } else { + /* Double the size */ + num_entries = size * 2; + } + + /* Allocate first pointer table or enlarge existing table */ + p = realloc (files, sizeof (void*) * num_entries); + if (p != NULL) { + /* Got the memory */ + files = (dirent**) p; + allocated = num_entries; + } else { + /* Out of memory */ + result = -1; + break; + } + + } + + /* Allocate room for temporary directory entry */ + if (tmp == NULL) { + tmp = (struct dirent*) malloc (sizeof (struct dirent)); + if (tmp == NULL) { + /* Cannot allocate temporary directory entry */ + result = -1; + break; + } + } + + /* Read directory entry to temporary area */ + if (readdir_r (dir, tmp, &entry) == /*OK*/0) { + + /* Did we get an entry? */ + if (entry != NULL) { + int pass; + + /* Determine whether to include the entry in result */ + if (filter) { + /* Let the filter function decide */ + pass = filter (tmp); + } else { + /* No filter function, include everything */ + pass = 1; + } + + if (pass) { + /* Store the temporary entry to pointer table */ + files[size++] = tmp; + tmp = NULL; + + /* Keep up with the number of files */ + result++; + } + + } else { + + /* + * End of directory stream reached => sort entries and + * exit. + */ + qsort (files, size, sizeof (void*), + (int (*) (const void*, const void*)) compare); + break; + + } + + } else { + /* Error reading directory entry */ + result = /*Error*/ -1; + break; + } + + } + + } else { + /* Cannot open directory */ + result = /*Error*/ -1; + } + + /* Release temporary directory entry */ + free (tmp); + + /* Release allocated memory on error */ + if (result < 0) { + for (i = 0; i < size; i++) { + free (files[i]); + } + free (files); + files = NULL; + } + + /* Close directory stream */ + if (dir) { + closedir (dir); + } + + /* Pass pointer table to caller */ + if (namelist) { + *namelist = files; + } + return result; +} + +/* Alphabetical sorting */ +static int +alphasort( + const struct dirent **a, const struct dirent **b) +{ + return strcoll ((*a)->d_name, (*b)->d_name); +} + +/* Sort versions */ +static int +versionsort( + const struct dirent **a, const struct dirent **b) +{ + /* FIXME: implement strverscmp and use that */ + return alphasort (a, b); +} + +/* Convert multi-byte string to wide character string */ +static int +dirent_mbstowcs_s( + size_t *pReturnValue, + wchar_t *wcstr, + size_t sizeInWords, + const char *mbstr, + size_t count) +{ + int error; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 or later */ + error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count); + +#else + + /* Older Visual Studio or non-Microsoft compiler */ + size_t n; + + /* Convert to wide-character string (or count characters) */ + n = mbstowcs (wcstr, mbstr, sizeInWords); + if (!wcstr || n < count) { + + /* Zero-terminate output buffer */ + if (wcstr && sizeInWords) { + if (n >= sizeInWords) { + n = sizeInWords - 1; + } + wcstr[n] = 0; + } + + /* Length of resulting multi-byte string WITH zero terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + error = 0; + + } else { + + /* Could not convert string */ + error = 1; + + } + +#endif + return error; +} + +/* Convert wide-character string to multi-byte string */ +static int +dirent_wcstombs_s( + size_t *pReturnValue, + char *mbstr, + size_t sizeInBytes, /* max size of mbstr */ + const wchar_t *wcstr, + size_t count) +{ + int error; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 or later */ + error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count); + +#else + + /* Older Visual Studio or non-Microsoft compiler */ + size_t n; + + /* Convert to multi-byte string (or count the number of bytes needed) */ + n = wcstombs (mbstr, wcstr, sizeInBytes); + if (!mbstr || n < count) { + + /* Zero-terminate output buffer */ + if (mbstr && sizeInBytes) { + if (n >= sizeInBytes) { + n = sizeInBytes - 1; + } + mbstr[n] = '\0'; + } + + /* Length of resulting multi-bytes string WITH zero-terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + error = 0; + + } else { + + /* Cannot convert string */ + error = 1; + + } + +#endif + return error; +} + +/* Set errno variable */ +static void +dirent_set_errno( + int error) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 and later */ + _set_errno (error); + +#else + + /* Non-Microsoft compiler or older Microsoft compiler */ + errno = error; + +#endif +} + + +#ifdef __cplusplus +} +#endif +#endif /*DIRENT_H*/ diff --git a/src/libprojectM/msvc/dlfcn.c b/src/libprojectM/msvc/dlfcn.c new file mode 100644 index 0000000000..b356558b2a --- /dev/null +++ b/src/libprojectM/msvc/dlfcn.c @@ -0,0 +1,489 @@ +/* + * dlfcn-win32 + * Copyright (c) 2007 Ramiro Polla + * Copyright (c) 2015 Tiancheng "Timothy" Gu + * + * dlfcn-win32 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * dlfcn-win32 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with dlfcn-win32; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef _DEBUG +#define _CRTDBG_MAP_ALLOC +#include +#include +#endif +#define PSAPI_VERSION 1 +#include +#include +#include +#include + +#ifdef SHARED +#define DLFCN_WIN32_EXPORTS +#endif +#include "dlfcn.h" + +#if ((defined(_WIN32) || defined(WIN32)) && (defined(_MSC_VER)) ) +#define snprintf sprintf_s +#endif + +#ifdef UNICODE +#include +#define CHAR wchar_t +#define UNICODE_L(s) L##s +#else +#define CHAR char +#define UNICODE_L(s) s +#endif + +/* Note: + * MSDN says these functions are not thread-safe. We make no efforts to have + * any kind of thread safety. + */ + +typedef struct global_object { + HMODULE hModule; + struct global_object *previous; + struct global_object *next; +} global_object; + +static global_object first_object; +static global_object first_automatic_object; +static int auto_ref_count = 0; + +/* These functions implement a double linked list for the global objects. */ +static global_object *global_search( global_object *start, HMODULE hModule ) +{ + global_object *pobject; + + if( hModule == NULL ) + return NULL; + + for( pobject = start; pobject; pobject = pobject->next ) + if( pobject->hModule == hModule ) + return pobject; + + return NULL; +} + +static void global_add( global_object *start, HMODULE hModule ) +{ + global_object *pobject; + global_object *nobject; + + if( hModule == NULL ) + return; + + pobject = global_search( start, hModule ); + + /* Do not add object again if it's already on the list */ + if( pobject ) + return; + + if( start == &first_automatic_object ) + { + pobject = global_search( &first_object, hModule ); + if( pobject ) + return; + } + + for( pobject = start; pobject->next; pobject = pobject->next ); + + nobject = (global_object*) malloc( sizeof( global_object ) ); + + /* Should this be enough to fail global_add, and therefore also fail + * dlopen? + */ + if( !nobject ) + return; + + pobject->next = nobject; + nobject->next = NULL; + nobject->previous = pobject; + nobject->hModule = hModule; +} + +static void global_rem( global_object *start, HMODULE hModule ) +{ + global_object *pobject; + + if( hModule == NULL ) + return; + + pobject = global_search( start, hModule ); + + if( !pobject ) + return; + + if( pobject->next ) + pobject->next->previous = pobject->previous; + if( pobject->previous ) + pobject->previous->next = pobject->next; + + free( pobject ); +} + +/* POSIX says dlerror( ) doesn't have to be thread-safe, so we use one + * static buffer. + * MSDN says the buffer cannot be larger than 64K bytes, so we set it to + * the limit. + */ +static CHAR error_buffer[65535]; +static CHAR *current_error; +static char dlerror_buffer[65536]; + +static int copy_string( CHAR *dest, int dest_size, const CHAR *src ) +{ + int i = 0; + + /* gcc should optimize this out */ + if( !src || !dest ) + return 0; + + for( i = 0 ; i < dest_size-1 ; i++ ) + { + if( !src[i] ) + break; + else + dest[i] = src[i]; + } + dest[i] = '\0'; + + return i; +} + +static void save_err_str( const CHAR *str ) +{ + DWORD dwMessageId; + DWORD pos; + + dwMessageId = GetLastError( ); + + if( dwMessageId == 0 ) + return; + + /* Format error message to: + * "": + */ + pos = copy_string( error_buffer, sizeof(error_buffer), UNICODE_L("\"") ); + pos += copy_string( error_buffer+pos, sizeof(error_buffer)-pos, str ); + pos += copy_string( error_buffer+pos, sizeof(error_buffer)-pos, UNICODE_L("\": ") ); + pos += FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwMessageId, + MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), + error_buffer+pos, sizeof(error_buffer)-pos, NULL ); + + if( pos > 1 ) + { + /* POSIX says the string must not have trailing */ + if( error_buffer[pos-2] == '\r' && error_buffer[pos-1] == '\n' ) + error_buffer[pos-2] = '\0'; + } + + current_error = error_buffer; +} + +static void save_err_ptr_str( const void *ptr ) +{ + CHAR ptr_buf[19]; /* 0x up to 64 bits. */ + +#ifdef UNICODE + +# if ((defined(_WIN32) || defined(WIN32)) && (defined(_MSC_VER)) ) + swprintf_s( ptr_buf, 19, UNICODE_L("0x%p"), ptr ); +# else + swprintf(ptr_buf, 19, UNICODE_L("0x%p"), ptr); +# endif + +#else + snprintf( ptr_buf, 19, "0x%p", ptr ); +#endif + + save_err_str( ptr_buf ); +} + +void *dlopen( const char *file, int mode ) +{ + HMODULE hModule; + UINT uMode; + + current_error = NULL; + + /* Do not let Windows display the critical-error-handler message box */ + uMode = SetErrorMode( SEM_FAILCRITICALERRORS ); + + if( file == 0 ) + { + HMODULE hAddtnlMods[1024]; // Already loaded modules + HANDLE hCurrentProc = GetCurrentProcess( ); + DWORD cbNeeded; + + /* POSIX says that if the value of file is 0, a handle on a global + * symbol object must be provided. That object must be able to access + * all symbols from the original program file, and any objects loaded + * with the RTLD_GLOBAL flag. + * The return value from GetModuleHandle( ) allows us to retrieve + * symbols only from the original program file. For objects loaded with + * the RTLD_GLOBAL flag, we create our own list later on. For objects + * outside of the program file but already loaded (e.g. linked DLLs) + * they are added below. + */ + hModule = GetModuleHandle( NULL ); + + if( !hModule ) + save_err_ptr_str( file ); + + + /* GetModuleHandle( NULL ) only returns the current program file. So + * if we want to get ALL loaded module including those in linked DLLs, + * we have to use EnumProcessModules( ). + */ + if( EnumProcessModules( hCurrentProc, hAddtnlMods, + sizeof( hAddtnlMods ), &cbNeeded ) != 0 ) + { + DWORD i; + for( i = 0; i < cbNeeded / sizeof( HMODULE ); i++ ) + { + global_add( &first_automatic_object, hAddtnlMods[i] ); + } + } + auto_ref_count++; + } + else + { + CHAR lpFileName[MAX_PATH]; + int i; + + /* MSDN says backslashes *must* be used instead of forward slashes. */ + for( i = 0 ; i < sizeof(lpFileName) - 1 ; i ++ ) + { + if( !file[i] ) + break; + else if( file[i] == '/' ) + lpFileName[i] = '\\'; + else + lpFileName[i] = file[i]; + } + lpFileName[i] = '\0'; + + /* POSIX says the search path is implementation-defined. + * LOAD_WITH_ALTERED_SEARCH_PATH is used to make it behave more closely + * to UNIX's search paths (start with system folders instead of current + * folder). + */ + hModule = LoadLibraryEx(lpFileName, NULL, + LOAD_WITH_ALTERED_SEARCH_PATH ); + + /* If the object was loaded with RTLD_GLOBAL, add it to list of global + * objects, so that its symbols may be retrieved even if the handle for + * the original program file is passed. POSIX says that if the same + * file is specified in multiple invocations, and any of them are + * RTLD_GLOBAL, even if any further invocations use RTLD_LOCAL, the + * symbols will remain global. + */ + if( !hModule ) + save_err_str( lpFileName ); + else if( (mode & RTLD_GLOBAL) ) + global_add( &first_object, hModule ); + } + + /* Return to previous state of the error-mode bit flags. */ + SetErrorMode( uMode ); + + return (void *) hModule; +} + +static void free_auto( ) +{ + global_object *pobject = first_automatic_object.next; + if( pobject ) + { + global_object *next; + for ( ; pobject; pobject = next ) + { + next = pobject->next; + free( pobject ); + } + first_automatic_object.next = NULL; + } +} + +int dlclose( void *handle ) +{ + HMODULE hModule = (HMODULE) handle; + BOOL ret; + + current_error = NULL; + + ret = FreeLibrary( hModule ); + + /* If the object was loaded with RTLD_GLOBAL, remove it from list of global + * objects. + */ + if( ret ) + { + HMODULE cur = GetModuleHandle( NULL ); + global_rem( &first_object, hModule ); + if( hModule == cur ) + { + auto_ref_count--; + if( auto_ref_count < 0 ) + auto_ref_count = 0; + if( !auto_ref_count ) + free_auto( ); + } + } + else + save_err_ptr_str( handle ); + + /* dlclose's return value in inverted in relation to FreeLibrary's. */ + ret = !ret; + + return (int) ret; +} + +void *dlsym( void *handle, const char *name ) +{ + FARPROC symbol; + HMODULE hModule; + +#ifdef UNICODE + wchar_t namew[MAX_PATH]; + wmemset(namew, 0, MAX_PATH); +#endif + + current_error = NULL; + + symbol = GetProcAddress( (HMODULE) handle, name ); + + if( symbol != NULL ) + goto end; + + /* If the handle for the original program file is passed, also search + * in all globally loaded objects. + */ + + hModule = GetModuleHandle( NULL ); + + if( hModule == handle ) + { + global_object *pobject; + + for( pobject = &first_object; pobject; pobject = pobject->next ) + { + if( pobject->hModule ) + { + symbol = GetProcAddress( pobject->hModule, name ); + if( symbol != NULL ) + goto end; + } + } + + for( pobject = &first_automatic_object; pobject; pobject = pobject->next ) + { + if( pobject->hModule ) + { + symbol = GetProcAddress( pobject->hModule, name ); + if( symbol != NULL ) + goto end; + } + } + } + +end: + if( symbol == NULL ) + { +#ifdef UNICODE + size_t converted_chars; + + size_t str_len = strlen(name) + 1; + +#if ((defined(_WIN32) || defined(WIN32)) && (defined(_MSC_VER)) ) + errno_t err = mbstowcs_s(&converted_chars, namew, str_len, name, str_len); + if (err != 0) + return NULL; +#else + mbstowcs(namew, name, str_len); +#endif + + save_err_str( namew ); +#else + save_err_str( name ); +#endif + } + + // warning C4054: 'type cast' : from function pointer 'FARPROC' to data pointer 'void *' +#ifdef _MSC_VER +#pragma warning( suppress: 4054 ) +#endif + return (void*) symbol; +} + +char *dlerror( void ) +{ + char *error_pointer = dlerror_buffer; + + /* If this is the second consecutive call to dlerror, return NULL */ + if (current_error == NULL) + { + return NULL; + } + +#ifdef UNICODE + errno_t err = 0; + size_t converted_chars = 0; + size_t str_len = wcslen(current_error) + 1; + memset(error_pointer, 0, 65535); + +# if ((defined(_WIN32) || defined(WIN32)) && (defined(_MSC_VER)) ) + err = wcstombs_s(&converted_chars, + error_pointer, str_len * sizeof(char), + current_error, str_len * sizeof(wchar_t)); + + if (err != 0) + return NULL; +# else + wcstombs(error_pointer, current_error, str_len); +# endif + +#else + memcpy(error_pointer, current_error, strlen(current_error) + 1); +#endif + + /* POSIX says that invoking dlerror( ) a second time, immediately following + * a prior invocation, shall result in NULL being returned. + */ + current_error = NULL; + + return error_pointer; +} + +#ifdef SHARED +BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) +{ + (void) hinstDLL; + /* + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms682583(v=vs.85).aspx + * + * When handling DLL_PROCESS_DETACH, a DLL should free resources such as heap + * memory only if the DLL is being unloaded dynamically (the lpReserved + * parameter is NULL). + */ + if( fdwReason == DLL_PROCESS_DETACH && !lpvReserved ) + { + auto_ref_count = 0; + free_auto( ); + } + return TRUE; +} +#endif diff --git a/src/libprojectM/msvc/dlfcn.h b/src/libprojectM/msvc/dlfcn.h new file mode 100644 index 0000000000..711e431c8c --- /dev/null +++ b/src/libprojectM/msvc/dlfcn.h @@ -0,0 +1,59 @@ +/* + * dlfcn-win32 + * Copyright (c) 2007 Ramiro Polla + * + * dlfcn-win32 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * dlfcn-win32 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with dlfcn-win32; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DLFCN_H +#define DLFCN_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(DLFCN_WIN32_EXPORTS) +# define DLFCN_EXPORT __declspec(dllexport) +#else +# define DLFCN_EXPORT +#endif + +/* POSIX says these are implementation-defined. + * To simplify use with Windows API, we treat them the same way. + */ + +#define RTLD_LAZY 0 +#define RTLD_NOW 0 + +#define RTLD_GLOBAL (1 << 1) +#define RTLD_LOCAL (1 << 2) + +/* These two were added in The Open Group Base Specifications Issue 6. + * Note: All other RTLD_* flags in any dlfcn.h are not standard compliant. + */ + +#define RTLD_DEFAULT 0 +#define RTLD_NEXT 0 + +DLFCN_EXPORT void *dlopen ( const char *file, int mode ); +DLFCN_EXPORT int dlclose(void *handle); +DLFCN_EXPORT void *dlsym(void *handle, const char *name); +DLFCN_EXPORT char *dlerror(void); + +#ifdef __cplusplus +} +#endif + +#endif /* DLFCN_H */ diff --git a/src/libprojectM/omptl/CMakeLists.txt b/src/libprojectM/omptl/CMakeLists.txt new file mode 100644 index 0000000000..3fea46350d --- /dev/null +++ b/src/libprojectM/omptl/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(omptl INTERFACE) + +target_sources(omptl + omptl + omptl_algorithm + omptl_algorithm_par.h + omptl_algorithm_ser.h + omptl_numeric + omptl_numeric_extentions.h + omptl_numeric_extentions_par.h + omptl_numeric_extentions_ser.h + omptl_numeric_par.h + omptl_numeric_ser.h + omptl_tools.h + ) diff --git a/src/libprojectM/omptl/omptl_numeric_extentions_par.h b/src/libprojectM/omptl/omptl_numeric_extentions_par.h index 5d152470dd..986620681e 100644 --- a/src/libprojectM/omptl/omptl_numeric_extentions_par.h +++ b/src/libprojectM/omptl/omptl_numeric_extentions_par.h @@ -123,7 +123,7 @@ struct _TransformAccumulate< ::std::input_iterator_tag > template T transform_accumulate(Iterator first, Iterator last, const T init, UnaryFunction unary_op, BinaryFunction binary_op, - const unsigned P = omp_get_max_threads()) + const unsigned P) { return ::omptl::_TransformAccumulate ::iterator_category> @@ -134,7 +134,7 @@ T transform_accumulate(Iterator first, Iterator last, const T init, template T transform_accumulate(Iterator first, Iterator last, const T init, UnaryFunction unary_op, - const unsigned P=omp_get_max_threads()) + const unsigned P) { typedef typename UnaryFunction::result_type RT; return ::omptl::transform_accumulate(first, last, init, unary_op, diff --git a/src/libprojectM/projectM-opengl.h b/src/libprojectM/projectM-opengl.h index a9d4cd48b7..2e2c420d5a 100644 --- a/src/libprojectM/projectM-opengl.h +++ b/src/libprojectM/projectM-opengl.h @@ -1,50 +1,50 @@ -/** - Include appropriate OpenGL headers for this platform. -**/ - -#ifndef __PROJECTM_OPENGL_H__ -#define __PROJECTM_OPENGL_H__ - -// stuff that needs to be ported to newer GL calls -#define GL_TRANSITION - -// Enable openGL extra checks, better not be enabled in release build -#define OGL_DEBUG 0 - -// Unlock FPS for rendering benchmarks, it disables Vblank/Vsync and prints drawn frame count within a 5s test run -#define UNLOCK_FPS 0 - -// If a shader compilation failure occurs, it dumps shader source into /tmp instead of stderr -#define DUMP_SHADERS_ON_ERROR 0 - -#ifdef __APPLE__ -# include -# include -#elif defined(EYETUNE_WINRT) -#define GL_GLEXT_PROTOTYPES -#define GLM_FORCE_CXX03 -#include -#include -#include -#include -#include -#include -#elif defined(_WIN32) -#define GLM_FORCE_CXX03 -# include -#include "glew.h" -#include "wglew.h" -#else /* linux/unix/other */ -# ifdef USE_GLES -# include -# else -# if !defined(GL_GLEXT_PROTOTYPES) -# define GL_GLEXT_PROTOTYPES -# endif -# include -# include -# endif -#endif - - -#endif // __PROJECTM_OPENGL_H__ +/** + Include appropriate OpenGL headers for this platform. +**/ + +#ifndef __PROJECTM_OPENGL_H__ +#define __PROJECTM_OPENGL_H__ + +// stuff that needs to be ported to newer GL calls +#define GL_TRANSITION + +// Enable openGL extra checks, better not be enabled in release build +#define OGL_DEBUG 0 + +// Unlock FPS for rendering benchmarks, it disables Vblank/Vsync and prints drawn frame count within a 5s test run +#define UNLOCK_FPS 0 + +// If a shader compilation failure occurs, it dumps shader source into /tmp instead of stderr +#define DUMP_SHADERS_ON_ERROR 0 + +#ifdef __APPLE__ +# include +# include +#elif defined(EYETUNE_WINRT) +#define GL_GLEXT_PROTOTYPES +#define GLM_FORCE_CXX03 +#include +#include +#include +#include +#include +#include +#elif defined(_WIN32) +#define GLM_FORCE_CXX03 +# include +#include "GL/glew.h" +#include "GL/wglew.h" +#else /* linux/unix/other */ +# ifdef USE_GLES +# include +# else +# if !defined(GL_GLEXT_PROTOTYPES) +# define GL_GLEXT_PROTOTYPES +# endif +# include +# include +# endif +#endif + + +#endif // __PROJECTM_OPENGL_H__ diff --git a/src/libprojectM/projectM.cpp b/src/libprojectM/projectM.cpp index f7b86452ec..8bd68f4cc8 100644 --- a/src/libprojectM/projectM.cpp +++ b/src/libprojectM/projectM.cpp @@ -55,7 +55,7 @@ #include "TimeKeeper.hpp" #include "RenderItemMergeFunction.hpp" -#ifdef USE_THREADS +#if USE_THREADS #include "pthread.h" #include "BackgroundWorker.h" @@ -74,7 +74,7 @@ constexpr int kMaxSwitchRetries = 10; projectM::~projectM() { -#ifdef USE_THREADS +#if USE_THREADS void *status; worker_sync.finish_up(); pthread_join(thread, &status); @@ -283,7 +283,7 @@ void projectM::readSettings (const Settings & settings ) _settings.aspectCorrection = settings.aspectCorrection; } -#ifdef USE_THREADS +#if USE_THREADS static void *thread_callback(void *prjm) { projectM *p = (projectM *)prjm; @@ -396,13 +396,13 @@ Pipeline * projectM::renderFrameOnlyPass1(Pipeline *pPipeline) /*pPipeline is a // printf("start thread\n"); assert ( m_activePreset2.get() ); -#ifdef USE_THREADS +#if USE_THREADS worker_sync.wake_up_bg(); #endif m_activePreset->Render(*beatDetect, pipelineContext()); -#ifdef USE_THREADS +#if USE_THREADS worker_sync.wait_for_bg_to_finish(); #else evaluateSecondPreset(); @@ -571,7 +571,7 @@ void projectM::projectM_init ( int gx, int gy, int fps, int texsize, int width, initPresetTools(gx, gy); -#ifdef USE_THREADS +#if USE_THREADS #ifdef SYNC_PRESET_SWITCHES pthread_mutex_init(&preset_mutex, NULL); diff --git a/src/projectM-jack/CMakeLists.txt b/src/projectM-jack/CMakeLists.txt new file mode 100644 index 0000000000..a1b521aa12 --- /dev/null +++ b/src/projectM-jack/CMakeLists.txt @@ -0,0 +1,11 @@ +if(NOT ENABLE_JACK) + return() +endif() + +if(ENABLE_QT) + include(projectM-jack-qt.cmake) +endif() + +if(ENABLE_SDL) + include(projectM-jack-sdl.cmake) +endif() diff --git a/src/projectM-jack/projectM-jack-qt.cmake b/src/projectM-jack/projectM-jack-qt.cmake new file mode 100644 index 0000000000..683666b5c2 --- /dev/null +++ b/src/projectM-jack/projectM-jack-qt.cmake @@ -0,0 +1,30 @@ +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +add_executable(projectM-jack-qt + ConfigFile.h + ConfigFile.cpp + qprojectM-jack.cpp + ) + +target_compile_definitions(projectM-jack-qt + PRIVATE + PROJECTM_PREFIX="${CMAKE_INSTALL_PREFIX}" + RESOURCE_PREFIX="share/projectM" + ) + +target_link_libraries(projectM-jack-qt + PRIVATE + projectM-qt + JACK::JACK + ${CMAKE_DL_LIBS} + ) + +set_target_properties(projectM-jack-qt PROPERTIES + OUTPUT_NAME projectM-jack + ) + +install(TARGETS projectM-jack-qt + RUNTIME DESTINATION "${PROJECTM_BIN_DIR}" + COMPONENT Applications + ) diff --git a/src/projectM-jack/projectM-jack-sdl.cmake b/src/projectM-jack/projectM-jack-sdl.cmake new file mode 100644 index 0000000000..aee3b67ee2 --- /dev/null +++ b/src/projectM-jack/projectM-jack-sdl.cmake @@ -0,0 +1,25 @@ +add_executable(projectM-jack-sdl + ConfigFile.h + ConfigFile.cpp + projectM-jack.cpp + video_init.cpp + video_init.h + ) + +target_compile_definitions(projectM-jack-sdl + PRIVATE + PROJECTM_PREFIX="${CMAKE_INSTALL_PREFIX}" + ) + +target_link_libraries(projectM-jack-sdl + PRIVATE + projectM_static + JACK::JACK + SDL2::SDL2 + ${CMAKE_DL_LIBS} + ) + +install(TARGETS projectM-jack-sdl + RUNTIME DESTINATION "${PROJECTM_BIN_DIR}" + COMPONENT Applications + ) diff --git a/src/projectM-jack/projectM-jack.cpp b/src/projectM-jack/projectM-jack.cpp index 36645ea2f9..dd60977c4c 100644 --- a/src/projectM-jack/projectM-jack.cpp +++ b/src/projectM-jack/projectM-jack.cpp @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include "sdltoprojectM.h" @@ -41,199 +41,208 @@ std::string read_config(); jack_port_t *input_port; jack_client_t *client; -jack_default_audio_sample_t out[1024*1000]; +jack_default_audio_sample_t out[1024 * 1000]; + +SDL_Window *screen; +SDL_Renderer *renderer; #ifdef DEBUG FILE *debugFile = NULL; #endif -volatile enum client_state_t { - Init, - Run, - Exit -} client_state = Init; - -SDL_Surface *screen; +volatile enum client_state_t { Init, Run, Exit } client_state = Init; projectM *globalPM = NULL; int dumpFrame = 0; int frameNumber = 0; - -int texsize=512; -int gx=32,gy=24; -int wvw=512,wvh=512; -int fvw=1024,fvh=768; -int fps=30, fullscreen=0; +int texsize = 512; +int gx = 32, gy = 24; +int wvw = 512, wvh = 512; +int fvw = 1024, fvh = 768; +int fps = 30, fullscreen = 0; std::string read_config() { - int n; - - char num[512]; - FILE *in; - FILE *out; - - char* home; - char projectM_home[1024]; - char projectM_config[1024]; - - strcpy(projectM_config, PROJECTM_PREFIX); - strcpy(projectM_config+strlen(PROJECTM_PREFIX), CONFIG_FILE); - projectM_config[strlen(PROJECTM_PREFIX)+strlen(CONFIG_FILE)]='\0'; - printf("dir:%s \n",projectM_config); - home=getenv("HOME"); - strcpy(projectM_home, home); - strcpy(projectM_home+strlen(home), "/.projectM/config.inp"); - projectM_home[strlen(home)+strlen("/.projectM/config.inp")]='\0'; - - - if ((in = fopen(projectM_home, "r")) != 0) - { - printf("reading ~/.projectM/config.inp \n"); - fclose(in); - return std::string(projectM_home); - } - else - { - printf("trying to create ~/.projectM/config.inp \n"); - - strcpy(projectM_home, home); - strcpy(projectM_home+strlen(home), "/.projectM"); - projectM_home[strlen(home)+strlen("/.projectM")]='\0'; - mkdir(projectM_home,0755); - - strcpy(projectM_home, home); - strcpy(projectM_home+strlen(home), "/.projectM/config.inp"); - projectM_home[strlen(home)+strlen("/.projectM/config.inp")]='\0'; - - if((out = fopen(projectM_home,"w"))!=0) - { - - if ((in = fopen(projectM_config, "r")) != 0) - { - - while(fgets(num,80,in)!=NULL) - { - fputs(num,out); - } - fclose(in); - fclose(out); - - - if ((in = fopen(projectM_home, "r")) != 0) - { - printf("created ~/.projectM/config.inp successfully\n"); - fclose(in); - return std::string(projectM_home); - } - else{printf("This shouldn't happen, using implementation defaults\n");abort();} - } - else{printf("Cannot find projectM default config, using implementation defaults\n");abort();} - } - else - { - printf("Cannot create ~/.projectM/config.inp, using default config file\n"); - if ((in = fopen(projectM_config, "r")) != 0) - { printf("Successfully opened default config file\n"); - fclose(in); - return std::string(projectM_config);} - else{ printf("Using implementation defaults, your system is really messed up, I'm surprised we even got this far\n"); abort();} - - } - - } - - - abort(); + int n; + + char num[512]; + FILE *in; + FILE *out; + + char *home; + char projectM_home[1024]; + char projectM_config[1024]; + + strcpy(projectM_config, PROJECTM_PREFIX); + strcpy(projectM_config + strlen(PROJECTM_PREFIX), CONFIG_FILE); + projectM_config[strlen(PROJECTM_PREFIX) + strlen(CONFIG_FILE)] = '\0'; + printf("dir:%s \n", projectM_config); + home = getenv("HOME"); + strcpy(projectM_home, home); + strcpy(projectM_home + strlen(home), "/.projectM/config.inp"); + projectM_home[strlen(home) + strlen("/.projectM/config.inp")] = '\0'; + + if ((in = fopen(projectM_home, "r")) != 0) + { + printf("reading ~/.projectM/config.inp \n"); + fclose(in); + return std::string(projectM_home); + } + else + { + printf("trying to create ~/.projectM/config.inp \n"); + + strcpy(projectM_home, home); + strcpy(projectM_home + strlen(home), "/.projectM"); + projectM_home[strlen(home) + strlen("/.projectM")] = '\0'; + mkdir(projectM_home, 0755); + + strcpy(projectM_home, home); + strcpy(projectM_home + strlen(home), "/.projectM/config.inp"); + projectM_home[strlen(home) + strlen("/.projectM/config.inp")] = '\0'; + + if ((out = fopen(projectM_home, "w")) != 0) + { + + if ((in = fopen(projectM_config, "r")) != 0) + { + + while (fgets(num, 80, in) != NULL) + { + fputs(num, out); + } + fclose(in); + fclose(out); + + if ((in = fopen(projectM_home, "r")) != 0) + { + printf("created ~/.projectM/config.inp successfully\n"); + fclose(in); + return std::string(projectM_home); + } + else + { + printf("This shouldn't happen, using implementation defaults\n"); + abort(); + } + } + else + { + printf("Cannot find projectM default config, using implementation defaults\n"); + abort(); + } + } + else + { + printf("Cannot create ~/.projectM/config.inp, using default config file\n"); + if ((in = fopen(projectM_config, "r")) != 0) + { + printf("Successfully opened default config file\n"); + fclose(in); + return std::string(projectM_config); + } + else + { + printf( + "Using implementation defaults, your system is really messed up, I'm surprised we even " + "got this far\n"); + abort(); + } + } + } } -void renderLoop() { - - int i; - int x, y; - int index; - short pcm_data[2][512]; - - while ( 1 ) { - projectMEvent evt; - projectMKeycode key; - projectMModifier mod; - - /** Process SDL events */ - SDL_Event event; - while ( SDL_PollEvent( &event ) ) { - /** Translate into projectM codes and process */ - evt = sdl2pmEvent( event ); - key = sdl2pmKeycode( event.key.keysym.sym ); - mod = sdl2pmModifier( event.key.keysym.mod ); - - if ( evt == PROJECTM_KEYDOWN ) { - - - if(key == PROJECTM_K_f) - { - if (++fullscreen%2) - { - resize_display(fvw, fvh, fullscreen%2); - globalPM->projectM_resetGL( fvw, fvh ); - } - else - { - resize_display(wvw, wvh, fullscreen%2); - globalPM->projectM_resetGL( wvw, wvh ); - } - } - else if(key == PROJECTM_K_q || key == PROJECTM_K_ESCAPE) {delete(globalPM); exit (1);} - else {globalPM->key_handler(evt,key,mod);} - - } - else if ( evt == PROJECTM_VIDEORESIZE ) - { - wvw=event.resize.w; - wvh=event.resize.h; - resize_display(wvw, wvh, fullscreen%2); - globalPM->projectM_resetGL( wvw, wvh ); - - } - - } - - +void renderLoop() +{ - /** Render the new frame */ - globalPM->renderFrame( ); + int i; + int x, y; + int index; + short pcm_data[2][512]; + + while (1) + { + projectMEvent evt; + projectMKeycode key; + projectMModifier mod; + + /** Process SDL events */ + SDL_Event event; + while (SDL_PollEvent(&event)) + { + /** Translate into projectM codes and process */ + evt = sdl2pmEvent(event); + key = sdl2pmKeycode(event.key.keysym.sym); + mod = sdl2pmModifier(event.key.keysym.mod); + + if (evt == PROJECTM_KEYDOWN) + { + + if (key == PROJECTM_K_f) + { + if (++fullscreen % 2) + { + resize_display(fvw, fvh, fullscreen % 2); + globalPM->projectM_resetGL(fvw, fvh); + } + else + { + resize_display(wvw, wvh, fullscreen % 2); + globalPM->projectM_resetGL(wvw, wvh); + } + } + else if (key == PROJECTM_K_q || key == PROJECTM_K_ESCAPE) + { + delete (globalPM); + exit(1); + } + else + { + globalPM->key_handler(evt, key, mod); + } + } + else if (evt == PROJECTM_VIDEORESIZE) + { + wvw = event.window.data1; + wvh = event.window.data2; + resize_display(wvw, wvh, fullscreen % 2); + globalPM->projectM_resetGL(wvw, wvh); + } + } + /** Render the new frame */ + globalPM->renderFrame(); - SDL_GL_SwapBuffers(); - } + SDL_GL_SwapWindow(screen); + } - printf("Worker thread: Exiting\n"); - } + printf("Worker thread: Exiting\n"); +} -int -process (jack_nframes_t nframes, void *arg) +int process(jack_nframes_t nframes, void *arg) { jack_default_audio_sample_t *in; - in = (jack_default_audio_sample_t*)jack_port_get_buffer (input_port, nframes); + in = (jack_default_audio_sample_t *)jack_port_get_buffer(input_port, nframes); - //memcpy (out, in,sizeof (jack_default_audio_sample_t) * nframes); + // memcpy (out, in,sizeof (jack_default_audio_sample_t) * nframes); - globalPM->pcm()->addPCMfloat(in,nframes); - //printf("%x %f\n",nframes,in[128]); + globalPM->pcm()->addPCMfloat(in, nframes); + // printf("%x %f\n",nframes,in[128]); return 0; } -void jack_shutdown (void *arg) +void jack_shutdown(void *arg) { - exit (1); + exit(1); } -int main( int argc, char **argv ) { +int main(int argc, char **argv) +{ const char **ports; const char *client_name; const char *server_name = NULL; @@ -242,112 +251,120 @@ int main( int argc, char **argv ) { int i; char projectM_data[1024]; - std::string config_file; config_file = read_config(); ConfigFile config(config_file); - wvw = config.read( "Window Width", 512 ); - wvh = config.read( "Window Height", 512 ); + wvw = config.read("Window Width", 512); + wvh = config.read("Window Height", 512); int fullscreen = 0; - if (config.read("Fullscreen", true)) fullscreen = 1; - else fullscreen = 0; - + if (config.read("Fullscreen", true)) + fullscreen = 1; + else + fullscreen = 0; #ifdef DEBUG int value; int rgb_size[3]; #endif - const SDL_VideoInfo* info = NULL; int bpp = 0; /* Flags we will pass into SDL_SetVideoMode. */ int flags = 0; - //JACK INIT + // JACK INIT //---------------------------------------------- - if (argc >= 2) { /* client name specified? */ + if (argc >= 2) + { /* client name specified? */ client_name = argv[1]; - if (argc >= 3) { /* server name specified? */ + if (argc >= 3) + { /* server name specified? */ server_name = argv[2]; // options |= JackServerName; } - } else { /* use basename of argv[0] */ + } + else + { /* use basename of argv[0] */ client_name = strrchr(argv[0], '/'); - if (client_name == 0) { + if (client_name == 0) + { client_name = argv[0]; - } else { + } + else + { client_name++; } } /* open a client connection to the JACK server */ - client = jack_client_open (client_name, options, &status, server_name); - if (client == NULL) { - fprintf (stderr, "jack_client_open() failed, " - "status = 0x%2.0x\n", status); - if (status & JackServerFailed) { - fprintf (stderr, "Unable to connect to JACK server\n"); + client = jack_client_open(client_name, options, &status, server_name); + if (client == NULL) + { + fprintf(stderr, + "jack_client_open() failed, " + "status = 0x%2.0x\n", + status); + if (status & JackServerFailed) + { + fprintf(stderr, "Unable to connect to JACK server\n"); } - exit (1); + exit(1); } - if (status & JackServerStarted) { - fprintf (stderr, "JACK server started\n"); + if (status & JackServerStarted) + { + fprintf(stderr, "JACK server started\n"); } - if (status & JackNameNotUnique) { + if (status & JackNameNotUnique) + { client_name = jack_get_client_name(client); - fprintf (stderr, "unique name `%s' assigned\n", client_name); + fprintf(stderr, "unique name `%s' assigned\n", client_name); } /* tell the JACK server to call `process()' whenever - there is work to be done. + there is work to be done. */ - jack_set_process_callback (client, process, 0); + jack_set_process_callback(client, process, 0); /* tell the JACK server to call `jack_shutdown()' if - it ever shuts down, either entirely, or if it - just decides to stop calling us. + it ever shuts down, either entirely, or if it + just decides to stop calling us. */ - jack_on_shutdown (client, jack_shutdown, 0); + jack_on_shutdown(client, jack_shutdown, 0); /* display the current sample rate. */ - printf ("engine sample rate: %d\n", - jack_get_sample_rate (client)); + printf("engine sample rate: %d\n", jack_get_sample_rate(client)); /* create two ports */ - input_port = jack_port_register (client, "input", - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsInput, 0); + input_port = jack_port_register(client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); - if (input_port == NULL) { + if (input_port == NULL) + { fprintf(stderr, "no more JACK ports available\n"); - exit (1); + exit(1); } /* Tell the JACK server that we are ready to roll. Our * process() callback will start running now. */ - - // END JACK INIT ---------------------------------- - init_display(wvw,wvh,&fvw,&fvh,fullscreen); + init_display(wvw, wvh, &fvw, &fvh, fullscreen); /** Setup some window stuff */ - SDL_WM_SetCaption( PROJECTM_TITLE, NULL ); globalPM = new projectM(config_file); /** Initialise projectM */ - //JACK BEGIN----------------------------- + // JACK BEGIN----------------------------- - if (jack_activate (client)) { - fprintf (stderr, "cannot activate client"); - exit (1); + if (jack_activate(client)) + { + fprintf(stderr, "cannot activate client"); + exit(1); } /* Connect the ports. You can't do this before the client is @@ -358,30 +375,32 @@ int main( int argc, char **argv ) { * it. */ - ports = jack_get_ports (client, NULL, NULL, JackPortIsOutput); - if (ports == NULL) { + ports = jack_get_ports(client, NULL, NULL, JackPortIsOutput); + if (ports == NULL) + { fprintf(stderr, "no physical capture ports\n"); - exit (1); + exit(1); } + i = 0; + while (ports[i] != NULL) + { + printf("Connecting to Jack port %s\n", ports[i]); + if (jack_connect(client, ports[i], jack_port_name(input_port))) + { + fprintf(stderr, "cannot connect input ports\n"); + } + i++; + } - i=0; - while (ports[i]!=NULL) - { - printf("Connecting to Jack port %s\n",ports[i]); - if (jack_connect (client, ports[i], jack_port_name (input_port))) { - fprintf (stderr, "cannot connect input ports\n"); - } - i++; - } - - free (ports); + free(ports); //----------------------------------END - /** Initialise the thread */ renderLoop(); - return 1; + close_display(); + + return 0; } diff --git a/src/projectM-jack/sdltoprojectM.h b/src/projectM-jack/sdltoprojectM.h index e921569374..63c0184ba2 100644 --- a/src/projectM-jack/sdltoprojectM.h +++ b/src/projectM-jack/sdltoprojectM.h @@ -47,13 +47,13 @@ #ifdef WIN32 #include #else -#include +#include #endif projectMEvent sdl2pmEvent( SDL_Event event ) { \ switch ( event.type ) { \ - case SDL_VIDEORESIZE: + case SDL_WINDOWEVENT_RESIZED: return PROJECTM_VIDEORESIZE; \ case SDL_KEYUP: \ return PROJECTM_KEYUP; \ @@ -64,7 +64,7 @@ projectMEvent sdl2pmEvent( SDL_Event event ) { \ } \ } \ -projectMKeycode sdl2pmKeycode( SDLKey keysym ) { \ +projectMKeycode sdl2pmKeycode( SDL_Keycode keysym ) { \ switch ( keysym ) { \ case SDLK_F1: \ return PROJECTM_K_F1; \ @@ -164,7 +164,7 @@ projectMKeycode sdl2pmKeycode( SDLKey keysym ) { \ } \ } \ -projectMModifier sdl2pmModifier( SDLMod mod ) { \ +projectMModifier sdl2pmModifier( Uint16 mod ) { \ return PROJECTM_KMOD_LSHIFT; \ } \ diff --git a/src/projectM-jack/video_init.cpp b/src/projectM-jack/video_init.cpp index 6e9404982f..224e5bdc26 100644 --- a/src/projectM-jack/video_init.cpp +++ b/src/projectM-jack/video_init.cpp @@ -19,158 +19,186 @@ * */ -//video_init.c - SDL/Opengl Windowing Creation/Resizing Functions +// video_init.c - SDL/Opengl Windowing Creation/Resizing Functions // -//by Peter Sperl +// by Peter Sperl // -//Opens an SDL Window and creates an OpenGL session -//also able to handle resizing and fullscreening of windows -//just call init_display again with differant variables +// Opens an SDL Window and creates an OpenGL session +// also able to handle resizing and fullscreening of windows +// just call init_display again with differant variables -#include +#include + +#include #include #include -extern SDL_Surface *screen; +extern SDL_Window *screen; +extern SDL_Renderer *renderer; + extern int texsize; -void setup_opengl( int w, int h ); +void setup_opengl(int w, int h); -void close_display() { - SDL_Quit(); +void close_display() +{ + SDL_Quit(); } -void resize_display(int w, int h, int f) { - int flags; - if (f) flags = SDL_OPENGL|SDL_HWSURFACE|SDL_FULLSCREEN; - else flags = SDL_OPENGL|SDL_HWSURFACE|SDL_RESIZABLE; -// SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); - screen = SDL_SetVideoMode( w, h, 0, flags ) ; - if(screen == 0 ) { - fprintf( stderr, "Video mode set failed: %s\n", SDL_GetError( ) ); - return; - } - setup_opengl(w,h); - SDL_ShowCursor(f ? SDL_DISABLE : SDL_ENABLE); +void resize_display(int w, int h, int f) +{ + /* Information about the current video settings. */ + SDL_DisplayMode info; + + // SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); + if (SDL_GetWindowDisplayMode(screen, &info) != 0) + { + /* This should probably never happen. */ + fprintf(stderr, "Video query failed: %s\n", SDL_GetError()); + // projectM_vtable.disable_plugin (&projectM_vtable); + return; + } + + info.w = w; + info.h = h; + + if (SDL_SetWindowDisplayMode(screen, &info)) + { + fprintf(stderr, "Video mode set failed: %s\n", SDL_GetError()); + return; + } + + setup_opengl(w, h); + SDL_ShowCursor(f ? SDL_DISABLE : SDL_ENABLE); } -//init_display +// init_display // -//Sets screen to new width and height (w,h) -//Also switches between fullscreen and windowed -//with the boolean f (fullscreen) -void init_display(int w, int h, int *fvw, int *fvh, int f) +// Sets screen to new width and height (w,h) +// Also switches between fullscreen and windowed +// with the boolean f (fullscreen) +void init_display(int w, int h, int *fvw, int *fvh, int fullscreen) { - - /* Information about the current video settings. */ - const SDL_VideoInfo* info = NULL; - int bpp = 0; - /* Flags we will pass into SDL_SetVideoMode. */ - int flags = 0; - /* First, initialize SDL's video subsystem. */ - if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_TIMER ) < 0 ) { - /* Failed, exit. */ - fprintf( stderr, "Video initialization failed: %s\n", - SDL_GetError( ) ); - //projectM_vtable.disable_plugin (&projectM_vtable); - return; - - } - /* Let's get some video information. */ - info = SDL_GetVideoInfo( ); - if( !info ) { - /* This should probably never happen. */ - fprintf( stderr, "Video query failed: %s\n", - SDL_GetError( ) ); - // projectM_vtable.disable_plugin (&projectM_vtable); - return; - } - - printf("Screen Resolution: %d x %d\n", info->current_w, info->current_h); - *fvw = info->current_w; - *fvh = info->current_h; - - bpp = info->vfmt->BitsPerPixel; - - //SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 ); - //SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 ); - //SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 ); - - // SDL_GL_SetAttribute( SDL_GL_ACCUM_RED_SIZE, 8 ); - // SDL_GL_SetAttribute( SDL_GL_ACCUM_GREEN_SIZE, 8 ); - // SDL_GL_SetAttribute( SDL_GL_ACCUM_BLUE_SIZE, 8 ); - SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 ); - SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 ); - SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); - - if (f==0) - flags = SDL_OPENGL|SDL_HWSURFACE|SDL_RESIZABLE; - else flags = SDL_OPENGL|SDL_HWSURFACE|SDL_FULLSCREEN; - - screen= SDL_SetVideoMode( w, h, bpp, flags ) ; - - if(screen == 0 ) { - /* - * This could happen for a variety of reasons, - * including DISPLAY not being set, the specified - * resolution not being available, etc. - */ - fprintf( stderr, "Video mode set failed: %s\n", - SDL_GetError( ) ); - - // projectM_vtable.disable_plugin (&projectM_vtable); - return; - - } - - - // setup_opengl(w,h); - //gluOrtho2D(0, w, 0, h); -} + int bpp = 0; + /* Flags we will pass into SDL_SetVideoMode. */ + int flags = 0; + /* First, initialize SDL's video subsystem. */ + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) + { + /* Failed, exit. */ + fprintf(stderr, "Video initialization failed: %s\n", SDL_GetError()); + // projectM_vtable.disable_plugin (&projectM_vtable); + return; + } + + screen = SDL_CreateWindow(PROJECTM_TITLE, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, + fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + if (screen == nullptr) + { + fprintf(stderr, "Video mode set failed: %s\n", SDL_GetError()); + return; + } + + renderer = SDL_CreateRenderer(screen, -1, 0); + + /* Information about the current video settings. */ + SDL_DisplayMode info; + + /* Let's get some video information. */ + if (SDL_GetWindowDisplayMode(screen, &info) != 0) + { + /* This should probably never happen. */ + fprintf(stderr, "Video mode query failed: %s\n", SDL_GetError()); + // projectM_vtable.disable_plugin (&projectM_vtable); + return; + } + + printf("Screen Resolution: %d x %d\n", info.w, info.h); + *fvw = info.w; + *fvh = info.h; + + // SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 ); + // SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 ); + // SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 ); + + // SDL_GL_SetAttribute( SDL_GL_ACCUM_RED_SIZE, 8 ); + // SDL_GL_SetAttribute( SDL_GL_ACCUM_GREEN_SIZE, 8 ); + // SDL_GL_SetAttribute( SDL_GL_ACCUM_BLUE_SIZE, 8 ); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + if (SDL_SetWindowFullscreen(screen, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0) != 0) + { + /* + * This could happen for a variety of reasons, + * including DISPLAY not being set, the specified + * resolution not being available, etc. + */ + fprintf(stderr, "Fullscreen mode set failed: %s\n", SDL_GetError()); + + return; + } + + info.w = w; + info.h = h; + + if (SDL_SetWindowDisplayMode(screen, &info) != 0) + { + /* + * This could happen for a variety of reasons, + * including DISPLAY not being set, the specified + * resolution not being available, etc. + */ + fprintf(stderr, "Video mode set failed: %s\n", SDL_GetError()); + + // projectM_vtable.disable_plugin (&projectM_vtable); + return; + } + + setup_opengl(w,h); +} - void setup_opengl( int w, int h ) +void setup_opengl(int w, int h) { - - /* Our shading model--Gouraud (smooth). */ - glShadeModel( GL_SMOOTH); - /* Culling. */ - // glCullFace( GL_BACK ); - // glFrontFace( GL_CCW ); - // glEnable( GL_CULL_FACE ); - /* Set the clear color. */ - glClearColor( 0, 0, 0, 0 ); - /* Setup our viewport. */ - glViewport( 0, 0, w, h ); - /* - * Change to the projection matrix and set - * our viewing volume. - */ - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); - - // gluOrtho2D(0.0, (GLfloat) width, 0.0, (GLfloat) height); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - // glFrustum(0.0, height, 0.0,width,10,40); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - -glDrawBuffer(GL_BACK); - glReadBuffer(GL_BACK); - glEnable(GL_BLEND); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - // glBlendFunc(GL_SRC_ALPHA, GL_ONE); - glEnable(GL_LINE_SMOOTH); - glEnable(GL_POINT_SMOOTH); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT); - - // glCopyTexImage2D(GL_TEXTURE_2D,0,GL_RGB,0,0,texsize,texsize,0); - //glCopyTexSubImage2D(GL_TEXTURE_2D,0,0,0,0,0,texsize,texsize); - glLineStipple(2, 0xAAAA); - - -} + /* Our shading model--Gouraud (smooth). */ + glShadeModel(GL_SMOOTH); + /* Culling. */ + // glCullFace( GL_BACK ); + // glFrontFace( GL_CCW ); + // glEnable( GL_CULL_FACE ); + /* Set the clear color. */ + glClearColor(0, 0, 0, 0); + /* Setup our viewport. */ + glViewport(0, 0, w, h); + /* + * Change to the projection matrix and set + * our viewing volume. + */ + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + + // gluOrtho2D(0.0, (GLfloat) width, 0.0, (GLfloat) height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + // glFrustum(0.0, height, 0.0,width,10,40); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glDrawBuffer(GL_BACK); + glReadBuffer(GL_BACK); + glEnable(GL_BLEND); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + // glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glEnable(GL_LINE_SMOOTH); + glEnable(GL_POINT_SMOOTH); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + + // glCopyTexImage2D(GL_TEXTURE_2D,0,GL_RGB,0,0,texsize,texsize,0); + // glCopyTexSubImage2D(GL_TEXTURE_2D,0,0,0,0,0,texsize,texsize); + glLineStipple(2, 0xAAAA); +} diff --git a/src/projectM-libvisual/CMakeLists.txt b/src/projectM-libvisual/CMakeLists.txt new file mode 100644 index 0000000000..3db75fd76a --- /dev/null +++ b/src/projectM-libvisual/CMakeLists.txt @@ -0,0 +1,30 @@ +if(NOT ENABLE_LIBVISUAL) + return() +endif() + + +add_library(projectM_libvisual SHARED + actor_projectM.cpp + ConfigFile.cpp + ConfigFile.h + lvtoprojectM.h + ) + +target_compile_definitions(projectM_libvisual + PRIVATE + PROJECTM_PREFIX="${CMAKE_INSTALL_PREFIX}" + ) + +target_link_libraries(projectM_libvisual + PUBLIC + projectM_shared + libvisual::libvisual + ${CMAKE_DL_LIBS} + ) + +install(TARGETS projectM_libvisual + LIBRARY DESTINATION "${PROJECTM_LIBVISUAL_DIR}" + RUNTIME DESTINATION "${PROJECTM_LIBVISUAL_DIR}" + ARCHIVE DESTINATION "${PROJECTM_LIBVISUAL_DIR}" + COMPONENT libvisual + ) diff --git a/src/projectM-pulseaudio/CMakeLists.txt b/src/projectM-pulseaudio/CMakeLists.txt new file mode 100644 index 0000000000..fb4d085989 --- /dev/null +++ b/src/projectM-pulseaudio/CMakeLists.txt @@ -0,0 +1,58 @@ +if(NOT ENABLE_QT OR NOT ENABLE_PULSEAUDIO) + return() +endif() + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +add_executable(projectM-pulseaudio + ConfigFile.h + ConfigFile.cpp + PulseDeviceChooserDialog.ui + qprojectM-pulseaudio.cpp + QPulseAudioDeviceChooser.cpp + QPulseAudioDeviceChooser.hpp + QPulseAudioDeviceModel.cpp + QPulseAudioDeviceModel.hpp + QPulseAudioThread.cpp + QPulseAudioDeviceModel.hpp + ) + +target_compile_definitions(projectM-pulseaudio + PRIVATE + PROJECTM_PREFIX="${CMAKE_INSTALL_PREFIX}" + ) + +# Application includes a header file from libprojectM with its full path in the source dir. +target_include_directories(projectM-pulseaudio + PRIVATE + "${CMAKE_SOURCE_DIR}" + ) + +target_link_libraries(projectM-pulseaudio + PRIVATE + projectM-qt + Pulseaudio::Pulseaudio + ${CMAKE_DL_LIBS} + ) + +install(TARGETS projectM-pulseaudio + RUNTIME DESTINATION "${PROJECTM_BIN_DIR}" + COMPONENT Applications + ) + +install(FILES "${CMAKE_SOURCE_DIR}/src/projectM-qt/images/icons/prjm16-transparent.svg" + DESTINATION "share/icons/hicolor/scalable/apps" + COMPONENT Applications + RENAME projectM.svg + ) + +install(FILES projectM-pulseaudio.desktop + DESTINATION "share/applications" + COMPONENT Applications + ) + +install(FILES projectM-pulseaudio.1 + DESTINATION "share/man/man1" + COMPONENT Applications + ) diff --git a/src/projectM-qt/CMakeLists.txt b/src/projectM-qt/CMakeLists.txt new file mode 100644 index 0000000000..92d55b5e37 --- /dev/null +++ b/src/projectM-qt/CMakeLists.txt @@ -0,0 +1,47 @@ +if(NOT ENABLE_QT) + return() +endif() + +# Automatically create .moc files and run the UIC and resource compilers +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +add_library(projectM-qt STATIC + application.qrc + configfile.cpp + configfile.hpp + nullable.hpp + qplaylistfiledialog.cpp + qplaylistfiledialog.hpp + qplaylistmodel.cpp + qplaylistmodel.hpp + qplaylisttableview.hpp + qpreseteditordialog.cpp + qpreseteditordialog.hpp + qpreseteditordialog.ui + qpresetfiledialog.hpp + qpresettextedit.cpp + qpresettextedit.hpp + qprojectm.hpp + qprojectm_mainwindow.cpp + qprojectm_mainwindow.hpp + qprojectm_mainwindow.ui + qprojectmconfigdialog.cpp + qprojectmconfigdialog.hpp + qprojectmconfigdialog.ui + qprojectmwidget.hpp + qxmlplaylisthandler.hpp + ) + +target_include_directories(projectM-qt + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" + ) + +target_link_libraries(projectM-qt + PUBLIC + projectM_static + Qt5::Gui + Qt5::OpenGL + ) diff --git a/src/projectM-qt/Makefile.am b/src/projectM-qt/Makefile.am index ec01c15ede..24a5bfed3b 100644 --- a/src/projectM-qt/Makefile.am +++ b/src/projectM-qt/Makefile.am @@ -33,7 +33,7 @@ ui_qprojectmconfigdialog.h: qprojectmconfigdialog.ui $(UIC) -o $@ $< application_qrc.cpp: application.qrc - $(RCC) -o $@ $< + $(RCC) -name application -o $@ $< BUILT_SOURCES = \ diff --git a/src/projectM-qt/qprojectm.hpp b/src/projectM-qt/qprojectm.hpp index 5e70ec7cf7..490f533f15 100644 --- a/src/projectM-qt/qprojectm.hpp +++ b/src/projectM-qt/qprojectm.hpp @@ -54,4 +54,5 @@ class QProjectM : public QObject, public projectM { }; + #endif diff --git a/src/projectM-qt/qprojectm_mainwindow.cpp b/src/projectM-qt/qprojectm_mainwindow.cpp index 0df86b24a2..edf0e01568 100644 --- a/src/projectM-qt/qprojectm_mainwindow.cpp +++ b/src/projectM-qt/qprojectm_mainwindow.cpp @@ -38,8 +38,6 @@ #include "nullable.hpp" #include "qprojectmwidget.hpp" -extern int qInitResources(); - class PlaylistWriteFunctor { public: PlaylistWriteFunctor(const QVector::iterator & begin, @@ -79,7 +77,7 @@ QProjectM_MainWindow::QProjectM_MainWindow ( const std::string & config_file, QM _menuAndStatusBarsVisible(true), m_QPresetFileDialog ( new QPresetFileDialog ( this ) ), m_QPlaylistFileDialog( new QPlaylistFileDialog ( this )) { - qInitResources(); + Q_INIT_RESOURCE(application); ui = new Ui::QProjectM_MainWindow(); ui->setupUi ( this ); diff --git a/src/projectM-sdl/CMakeLists.txt b/src/projectM-sdl/CMakeLists.txt new file mode 100644 index 0000000000..c0c1fbc2d7 --- /dev/null +++ b/src/projectM-sdl/CMakeLists.txt @@ -0,0 +1,30 @@ +add_executable(projectMSDL + audioCapture.cpp + audioCapture.hpp + loopback.cpp + loopback.hpp + pmSDL.hpp + pmSDL.cpp + projectM_SDL_main.cpp + setup.cpp + setup.hpp + ) + +target_compile_definitions(projectMSDL + PRIVATE + PROJECTM_PREFIX="${CMAKE_INSTALL_PREFIX}" + ) + +target_link_libraries(projectMSDL + PRIVATE + projectM_static + GLM::GLM + SDL2::SDL2 + SDL2::SDL2main + ${CMAKE_DL_LIBS} + ) + +install(TARGETS projectMSDL + RUNTIME DESTINATION "${PROJECTM_BIN_DIR}" + COMPONENT Applications + ) diff --git a/src/projectM-sdl/setup.cpp b/src/projectM-sdl/setup.cpp index 96b237ab63..eb27f39b19 100644 --- a/src/projectM-sdl/setup.cpp +++ b/src/projectM-sdl/setup.cpp @@ -1,334 +1,339 @@ -#include "setup.hpp" - -#include -#include - -#if OGL_DEBUG -void debugGL(GLenum source, - GLenum type, - GLuint id, - GLenum severity, - GLsizei length, - const GLchar* message, - const void* userParam) { - - /*if (type != GL_DEBUG_TYPE_OTHER)*/ - { - std::cerr << " -- \n" << "Type: " << - type << "; Source: " << - source <<"; ID: " << id << "; Severity: " << - severity << "\n" << message << "\n"; - } -} -#endif - -// return path to config file to use -std::string getConfigFilePath(std::string datadir_path) { - char* home = NULL; - std::string projectM_home; - std::string projectM_config = DATADIR_PATH; - - projectM_config = datadir_path; - -#ifdef _MSC_VER - home=getenv("USERPROFILE"); -#else - home=getenv("HOME"); -#endif - - projectM_home = std::string(home); - projectM_home += "/.projectM"; - - // Create the ~/.projectM directory. If it already exists, mkdir will do nothing -#if defined _MSC_VER - _mkdir(projectM_home.c_str()); -#else - mkdir(projectM_home.c_str(), 0755); -#endif - - projectM_home += "/config.inp"; - projectM_config += "/config.inp"; - - std::ifstream f_home(projectM_home); - std::ifstream f_config(projectM_config); - std::cout << "f_home " << f_home.good() << "\n"; - - if (f_config.good() && !f_home.good()) { - std::ifstream f_src; - std::ofstream f_dst; - - f_src.open(projectM_config, std::ios::in | std::ios::binary); - f_dst.open(projectM_home, std::ios::out | std::ios::binary); - f_dst << f_src.rdbuf(); - f_dst.close(); - f_src.close(); - return std::string(projectM_home); - } else if (f_home.good()) { - SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Read ~/.projectM/config.inp\n"); - return std::string(projectM_home); - } else if (f_config.good()) { - SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot create ~/.projectM/config.inp, using %s\n", projectM_config.c_str()); - return std::string(projectM_config); - } else { - SDL_LogWarn(SDL_LOG_CATEGORY_ERROR, "Using implementation defaults, your system is really messed up, I'm surprised we even got this far\n"); - return ""; - } -} - - -void seedRand() { -#ifndef WIN32 - srand((int)(time(NULL))); -#endif -} - -void initGL() { -#ifdef USE_GLES - // use GLES 2.0 (this may need adjusting) - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); -#else - // Disabling compatibility profile - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); -#endif -} - -void dumpOpenGLInfo() { - SDL_Log("- GL_VERSION: %s", glGetString(GL_VERSION)); - SDL_Log("- GL_SHADING_LANGUAGE_VERSION: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); - SDL_Log("- GL_VENDOR: %s", glGetString(GL_VENDOR)); -} - -void initStereoscopicView(SDL_Window *win) { -#if STEREOSCOPIC_SB - // enable stereo - if (SDL_GL_SetAttribute(SDL_GL_STEREO, 1) == 0) - { - SDL_Log("SDL_GL_STEREO: true"); - } - - // requires fullscreen mode - SDL_ShowCursor(false); - SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN); -#endif -} - -void enableGLDebugOutput() { -#if OGL_DEBUG && !USE_GLES - glEnable(GL_DEBUG_OUTPUT); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - glDebugMessageCallback(debugGL, NULL); -#endif -} - -void testAllPresets(projectMSDL *app) { - uint buildErrors = 0; - for(unsigned int i = 0; i < app->getPlaylistSize(); i++) { - std::cout << i << "\t" << app->getPresetName(i) << std::endl; - app->selectPreset(i); - if (app->getErrorLoadingCurrentPreset()) { - buildErrors++; - } - } - - if (app->getPlaylistSize()) { - fprintf(stdout, "Preset loading errors: %d/%d [%d%%]\n", buildErrors, app->getPlaylistSize(), (buildErrors*100) / app->getPlaylistSize()); - } - - delete app; -} - -// initialize SDL, openGL, config -projectMSDL *setupSDLApp() { - projectMSDL *app; - seedRand(); - - if (!initLoopback()) - { - SDL_Log("Failed to initialize audio loopback devide."); - exit(1); - } - -#if UNLOCK_FPS - setenv("vblank_mode", "0", 1); -#endif - - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); - - if (! SDL_VERSION_ATLEAST(2, 0, 5)) { - SDL_Log("SDL version 2.0.5 or greater is required. You have %i.%i.%i", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL); - exit(1); - } - - // default window size to usable bounds (e.g. minus menubar and dock) - SDL_Rect initialWindowBounds; -#if SDL_VERSION_ATLEAST(2, 0, 5) - // new and better - SDL_GetDisplayUsableBounds(0, &initialWindowBounds); -#else - SDL_GetDisplayBounds(0, &initialWindowBounds); -#endif - int width = initialWindowBounds.w; - int height = initialWindowBounds.h; - - initGL(); - - SDL_Window *win = SDL_CreateWindow("projectM", 0, 0, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - SDL_GL_GetDrawableSize(win,&width,&height); - - initStereoscopicView(win); - - SDL_GLContext glCtx = SDL_GL_CreateContext(win); - dumpOpenGLInfo(); - - SDL_SetWindowTitle(win, "projectM"); - - SDL_GL_MakeCurrent(win, glCtx); // associate GL context with main window - int avsync = SDL_GL_SetSwapInterval(-1); // try to enable adaptive vsync - if (avsync == -1) { // adaptive vsync not supported - SDL_GL_SetSwapInterval(1); // enable updates synchronized with vertical retrace - } - - std::string base_path = DATADIR_PATH; - SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Using data directory: %s\n", base_path.c_str()); - - // load configuration file - std::string configFilePath = getConfigFilePath(base_path); - - if (! configFilePath.empty()) { - // found config file, use it - app = new projectMSDL(glCtx, configFilePath, 0); - SDL_Log("Using config from %s", configFilePath.c_str()); - } else { - // use some sane defaults if config file not found - base_path = SDL_GetBasePath(); - SDL_Log("Config file not found, using built-in settings. Data directory=%s\n", base_path.c_str()); - - // Get max refresh rate from attached displays to use as built-in max FPS. - int i = 0; - int maxRefreshRate = 0; - SDL_DisplayMode current; - for (i = 0; i < SDL_GetNumVideoDisplays(); ++i) - { - if (SDL_GetCurrentDisplayMode(i, ¤t) == 0) - { - if (current.refresh_rate > maxRefreshRate) maxRefreshRate = current.refresh_rate; - } - } - if (maxRefreshRate <= 60) maxRefreshRate = 60; - - float heightWidthRatio = (float)height / (float)width; - projectM::Settings settings; - settings.windowWidth = width; - settings.windowHeight = height; - settings.meshX = 128; - settings.meshY = settings.meshX * heightWidthRatio; - settings.fps = maxRefreshRate; - settings.smoothPresetDuration = 3; // seconds - settings.presetDuration = 22; // seconds - settings.hardcutEnabled = true; - settings.hardcutDuration = 60; - settings.hardcutSensitivity = 1.0; - settings.beatSensitivity = 1.0; - settings.aspectCorrection = 1; - settings.shuffleEnabled = 1; - settings.softCutRatingsEnabled = 1; // ??? - // get path to our app, use CWD or resource dir for presets/fonts/etc - settings.presetURL = base_path + "presets"; - // settings.presetURL = base_path + "presets/presets_shader_test"; - settings.menuFontURL = base_path + "fonts/Vera.ttf"; - settings.titleFontURL = base_path + "fonts/Vera.ttf"; - // init with settings - app = new projectMSDL(glCtx, settings, 0); - } - - // center window and full desktop screen - SDL_DisplayMode dm; - if (SDL_GetDesktopDisplayMode(0, &dm) == 0) { - width = dm.w; - height = dm.h; - } else { - SDL_Log("SDL_GetDesktopDisplayMode failed: %s", SDL_GetError()); - } - SDL_SetWindowPosition(win, initialWindowBounds.x, initialWindowBounds.y); - SDL_SetWindowSize(win, width, height); - app->resize(width, height); - - // Create a help menu specific to SDL - std::string modKey = "CTRL"; - -#if __APPLE__ - modKey = "CMD"; -#endif - - std::string sdlHelpMenu = "\n" - "F1: This help menu""\n" - "F3: Show preset name""\n" - "F4: Show details and statistics""\n" - "F5: Show FPS""\n" - "L or SPACE: Lock/Unlock Preset""\n" - "R: Random preset""\n" - "N: Next preset""\n" - "P: Previous preset""\n" - "UP: Increase Beat Sensitivity""\n" - "DOWN: Decrease Beat Sensitivity""\n" -#ifdef PROJECTM_TOUCH_ENABLED - "Left Click: Drop Random Waveform on Screen""\n" - "Right Click: Remove Random Waveform""\n" + - modKey + "+Right Click: Remove All Random Waveforms""\n" -#endif - + modKey + "+I: Audio Input (listen to next device)""\n" + - modKey + "+M: Change Monitor""\n" + - modKey + "+S: Stretch Monitors""\n" + - modKey + "+F: Fullscreen""\n" + - modKey + "+Q: Quit"; - app->setHelpText(sdlHelpMenu.c_str()); - app->init(win); - -#if STEREOSCOPIC_SBS - app->toggleFullScreen(); -#endif -#if FAKE_AUDIO - app->fakeAudio = true; -#endif - - enableGLDebugOutput(); - configureLoopback(app); - -#if !FAKE_AUDIO && !WASAPI_LOOPBACK - // get an audio input device - if (app->openAudioInput()) - app->beginAudioCapture(); -#endif - -#if TEST_ALL_PRESETS - testAllPresets(app); - return 0; -#endif - - return app; -} - -int64_t startUnlockedFPSCounter() { - using namespace std::chrono; - auto currentTime = steady_clock::now(); - auto currentTimeMs = time_point_cast(currentTime); - auto elapsedMs = currentTime.time_since_epoch(); - - return elapsedMs.count(); -} - -void advanceUnlockedFPSCounterFrame(int64_t startFrame) { - static int32_t frameCount = 0; - - frameCount++; - auto currentElapsedMs = startUnlockedFPSCounter(); - if (currentElapsedMs - startFrame > 5000) - { - printf("Frames[%d]\n", frameCount); - exit(0); - } -} +#include "setup.hpp" + +#include +#include + +#if OGL_DEBUG +void debugGL(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam) { + + /*if (type != GL_DEBUG_TYPE_OTHER)*/ + { + std::cerr << " -- \n" << "Type: " << + type << "; Source: " << + source <<"; ID: " << id << "; Severity: " << + severity << "\n" << message << "\n"; + } +} +#endif + +// return path to config file to use +std::string getConfigFilePath(std::string datadir_path) { + char* home = NULL; + std::string projectM_home; + std::string projectM_config = DATADIR_PATH; + + projectM_config = datadir_path; + +#ifdef _MSC_VER + home=getenv("USERPROFILE"); +#else + home=getenv("HOME"); +#endif + + projectM_home = std::string(home); + projectM_home += "/.projectM"; + + // Create the ~/.projectM directory. If it already exists, mkdir will do nothing +#if defined _MSC_VER + _mkdir(projectM_home.c_str()); +#else + mkdir(projectM_home.c_str(), 0755); +#endif + + projectM_home += "/config.inp"; + projectM_config += "/config.inp"; + + std::ifstream f_home(projectM_home); + std::ifstream f_config(projectM_config); + std::cout << "f_home " << f_home.good() << "\n"; + + if (f_config.good() && !f_home.good()) { + std::ifstream f_src; + std::ofstream f_dst; + + f_src.open(projectM_config, std::ios::in | std::ios::binary); + f_dst.open(projectM_home, std::ios::out | std::ios::binary); + f_dst << f_src.rdbuf(); + f_dst.close(); + f_src.close(); + return std::string(projectM_home); + } else if (f_home.good()) { + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Read ~/.projectM/config.inp\n"); + return std::string(projectM_home); + } else if (f_config.good()) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot create ~/.projectM/config.inp, using %s\n", projectM_config.c_str()); + return std::string(projectM_config); + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_ERROR, "Using implementation defaults, your system is really messed up, I'm surprised we even got this far\n"); + return ""; + } +} + + +void seedRand() { +#ifndef WIN32 + srand((int)(time(NULL))); +#endif +} + +void initGL() { +#if USE_GLES + // use GLES 2.0 (this may need adjusting) + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); +#else + // Disabling compatibility profile + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); +#endif +} + +void dumpOpenGLInfo() { + SDL_Log("- GL_VERSION: %s", glGetString(GL_VERSION)); + SDL_Log("- GL_SHADING_LANGUAGE_VERSION: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); + SDL_Log("- GL_VENDOR: %s", glGetString(GL_VENDOR)); +} + +void initStereoscopicView(SDL_Window *win) { +#if STEREOSCOPIC_SB + // enable stereo + if (SDL_GL_SetAttribute(SDL_GL_STEREO, 1) == 0) + { + SDL_Log("SDL_GL_STEREO: true"); + } + + // requires fullscreen mode + SDL_ShowCursor(false); + SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN); +#endif +} + +void enableGLDebugOutput() { +#if OGL_DEBUG && !USE_GLES + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(debugGL, NULL); +#endif +} + +void testAllPresets(projectMSDL *app) { + uint buildErrors = 0; + for(unsigned int i = 0; i < app->getPlaylistSize(); i++) { + std::cout << i << "\t" << app->getPresetName(i) << std::endl; + app->selectPreset(i); + if (app->getErrorLoadingCurrentPreset()) { + buildErrors++; + } + } + + if (app->getPlaylistSize()) { + fprintf(stdout, "Preset loading errors: %d/%d [%d%%]\n", buildErrors, app->getPlaylistSize(), (buildErrors*100) / app->getPlaylistSize()); + } + + delete app; +} + +// initialize SDL, openGL, config +projectMSDL *setupSDLApp() { + projectMSDL *app; + seedRand(); + + if (!initLoopback()) + { + SDL_Log("Failed to initialize audio loopback devide."); + exit(1); + } + +#if UNLOCK_FPS + setenv("vblank_mode", "0", 1); +#endif + + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); + + if (! SDL_VERSION_ATLEAST(2, 0, 5)) { + SDL_Log("SDL version 2.0.5 or greater is required. You have %i.%i.%i", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL); + exit(1); + } + + // default window size to usable bounds (e.g. minus menubar and dock) + SDL_Rect initialWindowBounds; +#if SDL_VERSION_ATLEAST(2, 0, 5) + // new and better + SDL_GetDisplayUsableBounds(0, &initialWindowBounds); +#else + SDL_GetDisplayBounds(0, &initialWindowBounds); +#endif + int width = initialWindowBounds.w; + int height = initialWindowBounds.h; + + initGL(); + + SDL_Window *win = SDL_CreateWindow("projectM", 0, 0, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_GL_GetDrawableSize(win,&width,&height); + + initStereoscopicView(win); + + SDL_GLContext glCtx = SDL_GL_CreateContext(win); + +#if defined(WIN32) + GLenum err = glewInit(); +#endif /** WIN32 */ + + dumpOpenGLInfo(); + + SDL_SetWindowTitle(win, "projectM"); + + SDL_GL_MakeCurrent(win, glCtx); // associate GL context with main window + int avsync = SDL_GL_SetSwapInterval(-1); // try to enable adaptive vsync + if (avsync == -1) { // adaptive vsync not supported + SDL_GL_SetSwapInterval(1); // enable updates synchronized with vertical retrace + } + + std::string base_path = DATADIR_PATH; + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Using data directory: %s\n", base_path.c_str()); + + // load configuration file + std::string configFilePath = getConfigFilePath(base_path); + + if (! configFilePath.empty()) { + // found config file, use it + app = new projectMSDL(glCtx, configFilePath, 0); + SDL_Log("Using config from %s", configFilePath.c_str()); + } else { + // use some sane defaults if config file not found + base_path = SDL_GetBasePath(); + SDL_Log("Config file not found, using built-in settings. Data directory=%s\n", base_path.c_str()); + + // Get max refresh rate from attached displays to use as built-in max FPS. + int i = 0; + int maxRefreshRate = 0; + SDL_DisplayMode current; + for (i = 0; i < SDL_GetNumVideoDisplays(); ++i) + { + if (SDL_GetCurrentDisplayMode(i, ¤t) == 0) + { + if (current.refresh_rate > maxRefreshRate) maxRefreshRate = current.refresh_rate; + } + } + if (maxRefreshRate <= 60) maxRefreshRate = 60; + + float heightWidthRatio = (float)height / (float)width; + projectM::Settings settings; + settings.windowWidth = width; + settings.windowHeight = height; + settings.meshX = 128; + settings.meshY = settings.meshX * heightWidthRatio; + settings.fps = maxRefreshRate; + settings.smoothPresetDuration = 3; // seconds + settings.presetDuration = 22; // seconds + settings.hardcutEnabled = true; + settings.hardcutDuration = 60; + settings.hardcutSensitivity = 1.0; + settings.beatSensitivity = 1.0; + settings.aspectCorrection = 1; + settings.shuffleEnabled = 1; + settings.softCutRatingsEnabled = 1; // ??? + // get path to our app, use CWD or resource dir for presets/fonts/etc + settings.presetURL = base_path + "presets"; + // settings.presetURL = base_path + "presets/presets_shader_test"; + settings.menuFontURL = base_path + "fonts/Vera.ttf"; + settings.titleFontURL = base_path + "fonts/Vera.ttf"; + // init with settings + app = new projectMSDL(glCtx, settings, 0); + } + + // center window and full desktop screen + SDL_DisplayMode dm; + if (SDL_GetDesktopDisplayMode(0, &dm) == 0) { + width = dm.w; + height = dm.h; + } else { + SDL_Log("SDL_GetDesktopDisplayMode failed: %s", SDL_GetError()); + } + SDL_SetWindowPosition(win, initialWindowBounds.x, initialWindowBounds.y); + SDL_SetWindowSize(win, width, height); + app->resize(width, height); + + // Create a help menu specific to SDL + std::string modKey = "CTRL"; + +#if __APPLE__ + modKey = "CMD"; +#endif + + std::string sdlHelpMenu = "\n" + "F1: This help menu""\n" + "F3: Show preset name""\n" + "F4: Show details and statistics""\n" + "F5: Show FPS""\n" + "L or SPACE: Lock/Unlock Preset""\n" + "R: Random preset""\n" + "N: Next preset""\n" + "P: Previous preset""\n" + "UP: Increase Beat Sensitivity""\n" + "DOWN: Decrease Beat Sensitivity""\n" +#ifdef PROJECTM_TOUCH_ENABLED + "Left Click: Drop Random Waveform on Screen""\n" + "Right Click: Remove Random Waveform""\n" + + modKey + "+Right Click: Remove All Random Waveforms""\n" +#endif + + modKey + "+I: Audio Input (listen to next device)""\n" + + modKey + "+M: Change Monitor""\n" + + modKey + "+S: Stretch Monitors""\n" + + modKey + "+F: Fullscreen""\n" + + modKey + "+Q: Quit"; + app->setHelpText(sdlHelpMenu.c_str()); + app->init(win); + +#if STEREOSCOPIC_SBS + app->toggleFullScreen(); +#endif +#if FAKE_AUDIO + app->fakeAudio = true; +#endif + + enableGLDebugOutput(); + configureLoopback(app); + +#if !FAKE_AUDIO && !WASAPI_LOOPBACK + // get an audio input device + if (app->openAudioInput()) + app->beginAudioCapture(); +#endif + +#if TEST_ALL_PRESETS + testAllPresets(app); + return 0; +#endif + + return app; +} + +int64_t startUnlockedFPSCounter() { + using namespace std::chrono; + auto currentTime = steady_clock::now(); + auto currentTimeMs = time_point_cast(currentTime); + auto elapsedMs = currentTime.time_since_epoch(); + + return elapsedMs.count(); +} + +void advanceUnlockedFPSCounterFrame(int64_t startFrame) { + static int32_t frameCount = 0; + + frameCount++; + auto currentElapsedMs = startUnlockedFPSCounter(); + if (currentElapsedMs - startFrame > 5000) + { + printf("Frames[%d]\n", frameCount); + exit(0); + } +} diff --git a/src/projectM-test/CMakeLists.txt b/src/projectM-test/CMakeLists.txt new file mode 100644 index 0000000000..d8b07a4474 --- /dev/null +++ b/src/projectM-test/CMakeLists.txt @@ -0,0 +1,37 @@ +if(NOT ENABLE_TESTING) + return() +endif() + +add_executable(projectM-unittest + ConfigFile.cpp + ConfigFile.h + getConfigFilename.cpp + getConfigFilename.h + projectM-unittest.cpp + ) + +target_compile_definitions(projectM-unittest + PRIVATE + PROJECTM_PREFIX="${CMAKE_INSTALL_PREFIX}" + ) + +# Test includes a header file from libprojectM with its full path in the source dir. +target_include_directories(projectM-unittest + PRIVATE + "${CMAKE_SOURCE_DIR}" + ) + +target_link_libraries(projectM-unittest + PRIVATE + projectM_static + SDL2::SDL2 + SDL2::SDL2main + ${CMAKE_DL_LIBS} + ) + +# Normally there's no need to install test applications, but will +# keep it for now to resemble the autotools package structure. +install(TARGETS projectM-unittest + RUNTIME DESTINATION "${PROJECTM_BIN_DIR}" + COMPONENT Tests + ) diff --git a/src/projectM-test/getConfigFilename.cpp b/src/projectM-test/getConfigFilename.cpp index fc5e24dd86..dd48a446e1 100644 --- a/src/projectM-test/getConfigFilename.cpp +++ b/src/projectM-test/getConfigFilename.cpp @@ -1,93 +1,101 @@ -// -// File: getConfigFilename.cpp -// -// Author: fatray -// -// Created on 05 December 2007, 23:39 -// -// FIXME: portability - - -// I hacked include on to silence my compiler, is it valid? -#include -#include -#include -#include "getConfigFilename.h" -#include -#include -#include - -// get the full pathname of a configfile -std::string getConfigFilename() -{ - char num[512]; - FILE *in; - FILE *out; - - char* home; - // FIXME: fixed length buffers are not ideal. - char projectM_home[1024]; - char projectM_config[1024]; - - strcpy(projectM_config, PROJECTM_PREFIX); - strcpy(projectM_config + strlen(PROJECTM_PREFIX), CONFIG_FILE); - projectM_config[strlen(PROJECTM_PREFIX) + strlen(CONFIG_FILE)] = '\0'; - printf("dir:%s \n", projectM_config); - home = getenv("HOME"); - strcpy(projectM_home, home); - strcpy(projectM_home + strlen(home), "/.projectM/config.inp"); - projectM_home[strlen(home) + strlen("/.projectM/config.inp")] = '\0'; - - if ((in = fopen(projectM_home, "r"))) - { - printf("reading ~/.projectM/config.inp \n"); - fclose(in); - return std::string(projectM_home); - } - - printf("trying to create ~/.projectM/config.inp \n"); - - projectM_home[strlen(home) + strlen("/.projectM")] = '\0'; - mkdir(projectM_home, 0755); - - strcpy(projectM_home + strlen(home), "/.projectM/config.inp"); - projectM_home[strlen(home) + strlen("/.projectM/config.inp")] = '\0'; - - if((out = fopen(projectM_home, "w"))) - { - if ((in = fopen(projectM_config, "r"))) - { - while(fgets(num, 80, in)!=NULL) - { - fputs(num, out); - } - fclose(in); - fclose(out); - - - if ((in = fopen(projectM_home, "r"))) - { - printf("created ~/.projectM/config.inp successfully\n"); - fclose(in); - return std::string(projectM_home); - } - - printf("This shouldn't happen, using implementation defaults\n"); - abort(); - } - printf("Cannot find projectM default config, using implementation defaults\n"); - abort(); - } - - printf("Cannot create ~/.projectM/config.inp, using default config file\n"); - if ((in = fopen(projectM_config, "r"))) - { - printf("Successfully opened default config file\n"); - fclose(in); - return std::string(projectM_config); - } - - printf("Using implementation defaults, your system is really messed up, I'm surprised we even got this far\n"); - abort(); -} - +// +// File: getConfigFilename.cpp +// +// Author: fatray +// +// Created on 05 December 2007, 23:39 +// +// FIXME: portability + + +// I hacked include on to silence my compiler, is it valid? +#include +#include +#include +#include "getConfigFilename.h" +#include +#include +#include +#ifdef WIN32 +#include +#include +#endif + +// get the full pathname of a configfile +std::string getConfigFilename() +{ + char num[512]; + FILE *in; + FILE *out; + + char* home; + // FIXME: fixed length buffers are not ideal. + char projectM_home[1024]; + char projectM_config[1024]; + + strcpy(projectM_config, PROJECTM_PREFIX); + strcpy(projectM_config + strlen(PROJECTM_PREFIX), CONFIG_FILE); + projectM_config[strlen(PROJECTM_PREFIX) + strlen(CONFIG_FILE)] = '\0'; + printf("dir:%s \n", projectM_config); + home = getenv("HOME"); + strcpy(projectM_home, home); + strcpy(projectM_home + strlen(home), "/.projectM/config.inp"); + projectM_home[strlen(home) + strlen("/.projectM/config.inp")] = '\0'; + + if ((in = fopen(projectM_home, "r"))) + { + printf("reading ~/.projectM/config.inp \n"); + fclose(in); + return std::string(projectM_home); + } + + printf("trying to create ~/.projectM/config.inp \n"); + + projectM_home[strlen(home) + strlen("/.projectM")] = '\0'; +#ifdef WIN32 + CreateDirectory(home, NULL); +#else + mkdir(projectM_home, 0755); +#endif + + strcpy(projectM_home + strlen(home), "/.projectM/config.inp"); + projectM_home[strlen(home) + strlen("/.projectM/config.inp")] = '\0'; + + if((out = fopen(projectM_home, "w"))) + { + if ((in = fopen(projectM_config, "r"))) + { + while(fgets(num, 80, in)!=NULL) + { + fputs(num, out); + } + fclose(in); + fclose(out); + + + if ((in = fopen(projectM_home, "r"))) + { + printf("created ~/.projectM/config.inp successfully\n"); + fclose(in); + return std::string(projectM_home); + } + + printf("This shouldn't happen, using implementation defaults\n"); + abort(); + } + printf("Cannot find projectM default config, using implementation defaults\n"); + abort(); + } + + printf("Cannot create ~/.projectM/config.inp, using default config file\n"); + if ((in = fopen(projectM_config, "r"))) + { + printf("Successfully opened default config file\n"); + fclose(in); + return std::string(projectM_config); + } + + printf("Using implementation defaults, your system is really messed up, I'm surprised we even got this far\n"); + abort(); +} + diff --git a/src/projectM-test/projectM-unittest.cpp b/src/projectM-test/projectM-unittest.cpp index d1431b6c65..67d5fcb380 100644 --- a/src/projectM-test/projectM-unittest.cpp +++ b/src/projectM-test/projectM-unittest.cpp @@ -1,44 +1,50 @@ -/** - * projectM -- Milkdrop-esque visualisation SDK - * Copyright (C)2019-2019 projectM Team - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * See 'LICENSE.txt' included within this release - * - */ - -#include -#include -#include -#include -#include -#include - -int main(int argc, char **argv) -{ - SDL_Window *win = SDL_CreateWindow("projectM", 0, 0, 100, 100, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); - SDL_GLContext glCtx = SDL_GL_CreateContext(win); - - // try unit tests - if (!TestRunner::run()) - { - printf("unit tests failed\n"); - exit(1); - } - printf("unit tests succeeded\n"); - - SDL_GL_DeleteContext(glCtx); - exit(0); -} +/** + * projectM -- Milkdrop-esque visualisation SDK + * Copyright (C)2019-2019 projectM Team + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * See 'LICENSE.txt' included within this release + * + */ + +#include +#include +#include +#include +#include +#include +#ifdef WIN32 +#include +#endif /** WIN32 */ + +int main(int argc, char *argv[]) +{ + SDL_Window *win = SDL_CreateWindow("projectM", 0, 0, 100, 100, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + SDL_GLContext glCtx = SDL_GL_CreateContext(win); +#ifdef WIN32 + GLenum err = glewInit(); +#endif /** WIN32 */ + + // try unit tests + if (!TestRunner::run()) + { + printf("unit tests failed\n"); + exit(1); + } + printf("unit tests succeeded\n"); + + SDL_GL_DeleteContext(glCtx); + exit(0); +}