diff --git a/BUILDING-cmake.md b/BUILDING-cmake.md index 53d020afb9..ee27dee7fe 100644 --- a/BUILDING-cmake.md +++ b/BUILDING-cmake.md @@ -115,13 +115,14 @@ Note that `ENABLE_GLES` will be forcibly set to `ON` for Emscripten and Android The following table contains a list of build options which are only useful in special circumstances, e.g. when developing libprojectM, trying experimental features or building the library for a special use-case/environment. -| CMake option | Default | Required dependencies | Description | -|------------------------|---------|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `ENABLE_SDL_UI` | `ON` | `SDL2` | Builds the SDL-based test application. Only used for development testing, will not be installed. | -| `ENABLE_INSTALL` | `OFF` | Building as a CMake subproject | Enable projectM install targets when built as a subproject via `add_subdirectory()`. | -| `ENABLE_DEBUG_POSTFIX` | `ON` | | Adds `d` (by default) to the name of any binary file in debug builds. | -| `ENABLE_SYSTEM_GLM` | `OFF` | | Builds against a system-installed GLM library. | -| `ENABLE_CXX_INTERFACE` | `OFF` | | Exports symbols for the `ProjectM` and `PCM` C++ classes and installs the additional the headers. Using the C++ interface is not recommended and unsupported. | +| CMake option | Default | Required dependencies | Description | +|--------------------------|---------|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ENABLE_SDL_UI` | `ON` | `SDL2` | Builds the SDL-based test application. Only used for development testing, will not be installed. | +| `ENABLE_INSTALL` | `OFF` | Building as a CMake subproject | Enable projectM install targets when built as a subproject via `add_subdirectory()`. | +| `ENABLE_DEBUG_POSTFIX` | `ON` | | Adds `d` (by default) to the name of any binary file in debug builds. | +| `ENABLE_SYSTEM_GLM` | `OFF` | | Builds against a system-installed GLM library. | +| `ENABLE_CXX_INTERFACE` | `OFF` | | Exports symbols for the `ProjectM` and `PCM` C++ classes and installs the additional the headers. Using the C++ interface is not recommended and unsupported. | +| `ENABLE_VERBOSE_LOGGING` | `OFF` | | Enables code for `TRACE` and `DEBUG` log levels in release builds. By default, these will only be compiled for `Debug` builds. Enabling this will negatively affect performance, even if the actual log level is set to `INFORMATION` or higher. | ### Path options diff --git a/CMakeLists.txt b/CMakeLists.txt index da747d1568..32000a1bb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ option(ENABLE_DEBUG_POSTFIX "Add \"d\" (by default) after library names for debu option(ENABLE_PLAYLIST "Enable building the playlist management library" ON) option(ENABLE_BOOST_FILESYSTEM "Force the use of boost::filesystem, even if the compiler supports C++17." OFF) option(ENABLE_SDL_UI "Build the SDL2-based developer test UI. Ignored when building with Emscripten or for Android." OFF) +option(ENABLE_VERBOSE_LOGGING "Enables TRACE and DEBUG logging even in release builds, negatively affecting the performance." OFF) option(BUILD_TESTING "Build the libprojectM test suite" OFF) option(BUILD_DOCS "Build documentation" OFF) @@ -208,6 +209,11 @@ else() set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) endif() +# Disable trace/debug logging in release builds unless explicitly requested +add_compile_definitions( + $<$,$>:ENABLE_DEBUG_LOGGING> + ) + if(BUILD_DOCS) find_package(Doxygen REQUIRED) find_package(Sphinx REQUIRED breathe exhale) @@ -220,15 +226,15 @@ if(BUILD_DOCS) set(DOXYGEN_EXCLUDE_PATTERNS "*.cpp") doxygen_add_docs( - projectm_doxygen - src - COMMENT "Generate HTML documentation") + projectm_doxygen + src + COMMENT "Generate HTML documentation") sphinx_add_docs( - projectm_sphinx - BREATHE_PROJECTS projectm_doxygen - BUILDER html - SOURCE_DIRECTORY docs) + projectm_sphinx + BREATHE_PROJECTS projectm_doxygen + BUILDER html + SOURCE_DIRECTORY docs) endif() add_subdirectory(vendor) diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 1674d9aa68..681f099ac6 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -1,12 +1,24 @@ add_library(projectM_api INTERFACE) -target_sources(projectM_api - PRIVATE +configure_file(version.h.in "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/version.h" @ONLY) + +include(GenerateExportHeader) + +set(PROJECTM_EXPORT_HEADER "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/projectM_export.h") + +generate_export_header(projectM_api + BASE_NAME projectM + EXPORT_FILE_NAME "${PROJECTM_EXPORT_HEADER}" + ) + +set(PROJECTM_PUBLIC_HEADERS "${PROJECTM_EXPORT_HEADER}" + "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/version.h" include/projectM-4/audio.h include/projectM-4/callbacks.h include/projectM-4/core.h include/projectM-4/debug.h + include/projectM-4/logging.h include/projectM-4/memory.h include/projectM-4/projectM.h include/projectM-4/render_opengl.h @@ -15,9 +27,15 @@ target_sources(projectM_api include/projectM-4/user_sprites.h ) +target_sources(projectM_api + PRIVATE + ${PROJECTM_PUBLIC_HEADERS} + ) + set_target_properties(projectM_api PROPERTIES EXPORT_NAME API FOLDER libprojectM + PUBLIC_HEADER "${PROJECTM_PUBLIC_HEADERS}" ) target_include_directories(projectM_api @@ -27,17 +45,6 @@ target_include_directories(projectM_api "$" ) -configure_file(version.h.in "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/version.h" @ONLY) - -include(GenerateExportHeader) - -set(PROJECTM_EXPORT_HEADER "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/projectM_export.h") - -generate_export_header(projectM_api - BASE_NAME projectM - EXPORT_FILE_NAME "${PROJECTM_EXPORT_HEADER}" - ) - add_library(libprojectM::API ALIAS projectM_api) @@ -51,16 +58,4 @@ if(ENABLE_INSTALL) PUBLIC_HEADER DESTINATION "${PROJECTM_INCLUDE_DIR}/projectM-4" COMPONENT Devel ) - install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/projectM_export.h" - "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/version.h" - DESTINATION "${PROJECTM_INCLUDE_DIR}/projectM-4" - COMPONENT Devel - ) - - install(DIRECTORY include/projectM-4 - DESTINATION "${PROJECTM_INCLUDE_DIR}" - COMPONENT Devel - ) - endif() \ No newline at end of file diff --git a/src/api/include/projectM-4/audio.h b/src/api/include/projectM-4/audio.h index 81bbec40f8..20f6b50917 100644 --- a/src/api/include/projectM-4/audio.h +++ b/src/api/include/projectM-4/audio.h @@ -1,6 +1,6 @@ /** * @file audio.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Functions to pass in audio data to libprojectM. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/callbacks.h b/src/api/include/projectM-4/callbacks.h index e652855b29..798abb3e56 100644 --- a/src/api/include/projectM-4/callbacks.h +++ b/src/api/include/projectM-4/callbacks.h @@ -1,6 +1,6 @@ /** * @file callbacks.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Functions and prototypes for projectM callbacks. * @since 4.0.0 * @@ -91,4 +91,3 @@ PROJECTM_EXPORT void projectm_set_preset_switch_failed_event_callback(projectm_h #ifdef __cplusplus } // extern "C" #endif - diff --git a/src/api/include/projectM-4/core.h b/src/api/include/projectM-4/core.h index 53c03b45e4..b54e698682 100644 --- a/src/api/include/projectM-4/core.h +++ b/src/api/include/projectM-4/core.h @@ -1,6 +1,6 @@ /** * @file core.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Core functions to instantiate, destroy and control projectM. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/debug.h b/src/api/include/projectM-4/debug.h index 77b6231aef..32ff875f6e 100644 --- a/src/api/include/projectM-4/debug.h +++ b/src/api/include/projectM-4/debug.h @@ -1,6 +1,6 @@ /** * @file debug.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Debug functions for both libprojectM and preset developers. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/logging.h b/src/api/include/projectM-4/logging.h new file mode 100644 index 0000000000..0630d80808 --- /dev/null +++ b/src/api/include/projectM-4/logging.h @@ -0,0 +1,94 @@ +/** + * @file logging.h + * @copyright 2003-2025 projectM Team + * @brief Functions for registering log callbacks and setting log levels. + * @since 4.2.0 + * + * projectM -- Milkdrop-esque visualisation SDK + * Copyright (C)2003-2024 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 + * + */ + +#pragma once + +#include "projectM-4/types.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Callback function that is executed if projectM wants to log a message. + * + * The message pointer is only valid inside the callback. Make a copy of the string inside the + * function to use it after the callback returns. Applications must not free the message pointer. + * + * @param message The message to be logged. + * @param log_level The log level this message was created for. + * @param user_data A user-defined data pointer that was provided when registering the callback, + * e.g. context information. + * @since 4.2.0 + */ +typedef void (*projectm_log_callback)(const char* message, projectm_log_level log_level, void* user_data); + + +/** + * @brief Sets a callback function that will be called for each message to be logged by projectM. + * + * The logging callback is independent of any projectM instance. Applications can set the callback + * globally, or only for a single thread, or both. If both a global and a thread-specific callback + * are set, the thread-specific callback takes precedence and the global callback will not be called. + * + * Since log messages will only be emitted by projectM when within an API call and the callback runs + * in the same thread as the projectM instance, applications can keep track of the instance making + * the call. + * + * If the application runs multiple projectM instances in separate threads, a global log callback + * (or the same thread callback registered in multiple threads) may be called in parallel from each + * thread, so the application has to take care about any possible race conditions in its own + * logging code and make sure it's thread-safe. + * + * To remove a callback, pass NULL as the callback argument. Thread-specific callbacks are removed + * automatically when the thread is terminated, though it is good practice to reset the callback at + * the end of a thread. + * + * @param callback A pointer to the callback function. + * @param current_thread_only If true, the callback is only set for the thread calling the function. + * @param user_data A pointer to any data that will be sent back in the callback, e.g. context + * information. + * @since 4.2.0 + * @note Log messages can contain line breaks. + */ +PROJECTM_EXPORT void projectm_set_log_callback(projectm_log_callback callback, + bool current_thread_only, + void* user_data); + +/** + * Sets the minimum log level for which the callback should be called. + * @param instance The projectM instance handle. + * @param log_level The new log level to set. If set to PROJECTM_LOG_LEVEL_NOTSET, the global or default setting will be used. + * @param current_thread_only If true, the log level is only set for the thread calling the function. + * @since 4.2.0 + */ +PROJECTM_EXPORT void projectm_set_log_level(projectm_log_level log_level, + bool current_thread_only); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/api/include/projectM-4/memory.h b/src/api/include/projectM-4/memory.h index 2e2074eb1a..ba2d8379b6 100644 --- a/src/api/include/projectM-4/memory.h +++ b/src/api/include/projectM-4/memory.h @@ -1,6 +1,6 @@ /** * @file memory.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Memory allocation/deallocation helpers. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/parameters.h b/src/api/include/projectM-4/parameters.h index 5bd7d901dd..3885a3d754 100644 --- a/src/api/include/projectM-4/parameters.h +++ b/src/api/include/projectM-4/parameters.h @@ -1,6 +1,6 @@ /** * @file parameters.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Functions to set and retrieve all sorts of projectM parameters and setting. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/projectM.h b/src/api/include/projectM-4/projectM.h index 2e93d7b5c1..5b646af4d9 100644 --- a/src/api/include/projectM-4/projectM.h +++ b/src/api/include/projectM-4/projectM.h @@ -1,6 +1,6 @@ /** * @file projectM.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Convenience include file that includes all other API headers. * @since 4.0.0 * @@ -29,6 +29,7 @@ #include "projectM-4/callbacks.h" #include "projectM-4/core.h" #include "projectM-4/debug.h" +#include "projectM-4/logging.h" #include "projectM-4/memory.h" #include "projectM-4/parameters.h" #include "projectM-4/render_opengl.h" diff --git a/src/api/include/projectM-4/render_opengl.h b/src/api/include/projectM-4/render_opengl.h index 74027105b0..1191e1cbe9 100644 --- a/src/api/include/projectM-4/render_opengl.h +++ b/src/api/include/projectM-4/render_opengl.h @@ -1,6 +1,6 @@ /** * @file render_opengl.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Functions to configure and render projectM visuals using OpenGL. * * projectM -- Milkdrop-esque visualisation SDK diff --git a/src/api/include/projectM-4/touch.h b/src/api/include/projectM-4/touch.h index b73c5131e6..c25ca6d937 100644 --- a/src/api/include/projectM-4/touch.h +++ b/src/api/include/projectM-4/touch.h @@ -1,6 +1,6 @@ /** * @file touch.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Touch-related functions to add random waveforms. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/types.h b/src/api/include/projectM-4/types.h index 56b3730645..1f05b4b854 100644 --- a/src/api/include/projectM-4/types.h +++ b/src/api/include/projectM-4/types.h @@ -1,6 +1,6 @@ /** * @file types.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Types and enumerations used in the other API headers. * @since 4.0.0 * @@ -78,7 +78,21 @@ typedef enum PROJECTM_TOUCH_TYPE_DOUBLE_LINE //!< Draws a double-line waveform. } projectm_touch_type; +/** + * Log level constants for use with the logging API functions. + * @since 4.2.0 + */ +typedef enum +{ + PROJECTM_LOG_LEVEL_NOTSET = 0, //!< No specific log level, use default (INFO). + PROJECTM_LOG_LEVEL_TRACE = 1, //!< Verbose trace logging. Only enabled in debug builds by default. + PROJECTM_LOG_LEVEL_DEBUG = 2, //!< Development-related debug logging. Only enabled in debug builds by default. + PROJECTM_LOG_LEVEL_INFO = 3, //!< Informational messages. + PROJECTM_LOG_LEVEL_WARN = 4, //!< Warnings about non-critical issues. + PROJECTM_LOG_LEVEL_ERROR = 5, //!< Recoverable errors, e.g. shader compilation or I/O errors. + PROJECTM_LOG_LEVEL_FATAL = 6 //!< Irrecoverable errors preventing projectM from working. +} projectm_log_level; + #ifdef __cplusplus } // extern "C" #endif - diff --git a/src/api/include/projectM-4/user_sprites.h b/src/api/include/projectM-4/user_sprites.h index e777f5916a..1f76646f65 100644 --- a/src/api/include/projectM-4/user_sprites.h +++ b/src/api/include/projectM-4/user_sprites.h @@ -1,6 +1,6 @@ /** * @file user_sprites.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Types and enumerations used in the other API headers. * @since 4.2.0 * diff --git a/src/libprojectM/CMakeLists.txt b/src/libprojectM/CMakeLists.txt index 4dc2f196c6..8acfb82cff 100644 --- a/src/libprojectM/CMakeLists.txt +++ b/src/libprojectM/CMakeLists.txt @@ -13,6 +13,8 @@ add_subdirectory(UserSprites) add_library(projectM_main OBJECT "${PROJECTM_EXPORT_HEADER}" + Logging.cpp + Logging.hpp Preset.hpp PresetFactory.cpp PresetFactory.hpp diff --git a/src/libprojectM/Logging.cpp b/src/libprojectM/Logging.cpp new file mode 100644 index 0000000000..e283eaf524 --- /dev/null +++ b/src/libprojectM/Logging.cpp @@ -0,0 +1,77 @@ +#include "Logging.hpp" + +#include + +namespace libprojectM { + +Logging::UserCallback Logging::m_globalCallback = {}; +thread_local Logging::UserCallback Logging::m_threadCallback = {}; + +Logging::LogLevel Logging::m_globalLogLevel = LogLevel::NotSet; +thread_local Logging::LogLevel Logging::m_threadLogLevel = LogLevel::NotSet; + +const Logging::LogLevel Logging::m_defaultLogLevel = LogLevel::Information; + +void Logging::SetGlobalCallback(const UserCallback callback) +{ + m_globalCallback = callback; +} + +void Logging::SetThreadCallback(const UserCallback callback) +{ + m_threadCallback = callback; +} + +void Logging::SetGlobalLogLevel(const LogLevel logLevel) +{ + m_globalLogLevel = logLevel; +} + +void Logging::SetThreadLogLevel(const LogLevel logLevel) +{ + m_threadLogLevel = logLevel; +} + +auto Logging::GetLogLevel() -> LogLevel +{ + if (m_threadLogLevel != LogLevel::NotSet) + { + return m_threadLogLevel; + } + + if (m_globalLogLevel != LogLevel::NotSet) + { + return m_globalLogLevel; + } + + return m_defaultLogLevel; +} + +auto Logging::HasCallback() -> bool +{ + return GetLoggingCallback().callbackFunction != nullptr; +} + +void Logging::Log(const std::string& message, LogLevel severity) +{ + auto callback = GetLoggingCallback(); + + if (callback.callbackFunction == nullptr) + { + return; + } + + callback.callbackFunction(message.c_str(), static_cast(severity), callback.userData); +} + +auto Logging::GetLoggingCallback() -> UserCallback +{ + if (m_threadCallback.callbackFunction != nullptr) + { + return m_threadCallback; + } + + return m_globalCallback; +} + +} // namespace libprojectM diff --git a/src/libprojectM/Logging.hpp b/src/libprojectM/Logging.hpp new file mode 100644 index 0000000000..1b0ce021b8 --- /dev/null +++ b/src/libprojectM/Logging.hpp @@ -0,0 +1,153 @@ +#pragma once + +#include +#include + +namespace libprojectM { + +/** + * @class Logging + * @brief A simple logger implementation to forward messages to the outside app. + * + * This class wraps logging functionality which can be used at either a global or thread-local + * level, for both callbacks and log levels. + */ +class Logging +{ +public: + /** + * The configurable log levels. If not set, "Information" is used by default. + */ + enum class LogLevel : uint8_t + { + NotSet, //!< Log level not set + Trace, //!< Most verbose logging, used to trace individual function calls or values. + Debug, //!< Debug-related messages for relevant data and developer information. + Information, //!< Not-too-frequent messages possibly relevant for users and developers. + Warning, //!< Something is wrong, but doesn't affect execution in a major way. + Error, //!< A recoverable error occurred which negatively affects projectM, e.g. shader compilation issues. + Fatal //!< An unrecoverable error occurred and the projectM instance cannot continue execution. + }; + + /** + * The application callback function. + */ + using CallbackFunction = void (*)(const char* message, int severity, void* userData); + + /** + * A struct holding the user callback function and data pointer. + */ + struct UserCallback { + CallbackFunction callbackFunction{}; //!< Function pointer of the user callback. + void* userData{}; //!< User data pointer for the callback. + }; + + Logging() = delete; + + /** + * Sets the global callback function pointer used across all threads. + * @param callback A UserCallback struct with the new function and user data pointers. + */ + static void SetGlobalCallback(UserCallback callback); + + /** + * Sets the thread-specific callback function pointer only used in the thread which registered it. + * @param callback A UserCallback struct with the new function and user data pointers. + */ + static void SetThreadCallback(UserCallback callback); + + /** + * Sets the global log level used across all threads. + * @param logLevel The log level to use. If set to LogLevel::NotSet, the value of m_defaultLogLevel is used. + */ + static void SetGlobalLogLevel(LogLevel logLevel); + + /** + * Sets the thread-specific log level only used in the thread which set it. + * @param logLevel The log level to use. If set to LogLevel::NotSet, the value of m_defaultLogLevel is used. + */ + static void SetThreadLogLevel(LogLevel logLevel); + + /** + * Returns the effective log level for the current thread. + * @return The log level set for this thread, or, if LogLevel::NotSet, the global log level. + * If no global log level is set, it returns the value of m_defaultLogLevel. + */ + static auto GetLogLevel() -> LogLevel; + + /** + * Returns whether a callback is registered or not. + * @return true if a callback is registered for the current thread or globally, false if none is registered. + */ + static auto HasCallback() -> bool; + + /** + * @brief Passes a log message with the given severity to the active thread or global callback. + * If no callbacks are registered, this function does nothing. + * @param message + * @param severity + */ + static void Log(const std::string& message, LogLevel severity); + + /** + * The default log level used if no log level is set (LogLevel::Information) + */ + static const LogLevel m_defaultLogLevel; + +private: + /** + * @brief Returns the active callback for this thread. + * If the thread has a local callback, this is returned, otherwise the global callback. + * @return A pointer to the active callback function, or nullptr if none is registered. + */ + static auto GetLoggingCallback() -> UserCallback; + + static UserCallback m_globalCallback; //!< The global callback function. + thread_local static UserCallback m_threadCallback; //!< The thread-specific callback function. + + static LogLevel m_globalLogLevel; //!< The global log level. + thread_local static LogLevel m_threadLogLevel; //!< The thread-specific log level. +}; + +#ifdef ENABLE_DEBUG_LOGGING +#define LOG_TRACE(message) \ + if (Logging::HasCallback() && Logging::GetLogLevel() == Logging::LogLevel::Trace) \ + { \ + Logging::Log(message, Logging::LogLevel::Trace); \ + } + +#define LOG_DEBUG(message) \ + if (Logging::HasCallback() && Logging::GetLogLevel() <= Logging::LogLevel::Debug) \ + { \ + Logging::Log(message, Logging::LogLevel::Debug); \ + } +#else +#define LOG_TRACE(message) +#define LOG_DEBUG(message) +#endif + +#define LOG_INFO(message) \ + if (Logging::HasCallback() && Logging::GetLogLevel() <= Logging::LogLevel::Information) \ + { \ + Logging::Log(message, Logging::LogLevel::Information); \ + } + +#define LOG_WARN(message) \ + if (Logging::HasCallback() && Logging::GetLogLevel() <= Logging::LogLevel::Warning) \ + { \ + Logging::Log(message, Logging::LogLevel::Warning); \ + } + +#define LOG_ERROR(message) \ + if (Logging::HasCallback() && Logging::GetLogLevel() <= Logging::LogLevel::Error) \ + { \ + Logging::Log(message, Logging::LogLevel::Error); \ + } + +#define LOG_FATAL(message) \ + if (Logging::HasCallback() && Logging::GetLogLevel() <= Logging::LogLevel::Fatal) \ + { \ + Logging::Log(message, Logging::LogLevel::Fatal); \ + } + +} // namespace libprojectM diff --git a/src/libprojectM/MilkdropPreset/CMakeLists.txt b/src/libprojectM/MilkdropPreset/CMakeLists.txt index 6f54abf4f0..68851e548c 100644 --- a/src/libprojectM/MilkdropPreset/CMakeLists.txt +++ b/src/libprojectM/MilkdropPreset/CMakeLists.txt @@ -1,6 +1,3 @@ -# Debugging options, for development purposes. -option(ENABLE_DEBUG_MILKDROP_PRESET "Enable STDERR debug output in Milkdrop preset code (Debug builds only)" OFF) - set(SHADER_FILES Shaders/Blur1FragmentShaderGlsl330.frag Shaders/Blur2FragmentShaderGlsl330.frag @@ -151,13 +148,6 @@ if(NOT BUILD_SHARED_LIBS) ) endif() -if(ENABLE_DEBUG_MILKDROP_PRESET) - target_compile_definitions(MilkdropPreset - PRIVATE - MILKDROP_PRESET_DEBUG=1 - ) -endif() - set_target_properties(MilkdropPreset PROPERTIES FOLDER libprojectM ) diff --git a/src/libprojectM/MilkdropPreset/FinalComposite.cpp b/src/libprojectM/MilkdropPreset/FinalComposite.cpp index bb58992b38..693df44935 100644 --- a/src/libprojectM/MilkdropPreset/FinalComposite.cpp +++ b/src/libprojectM/MilkdropPreset/FinalComposite.cpp @@ -2,14 +2,11 @@ #include "PresetState.hpp" +#include #include #include -#ifdef MILKDROP_PRESET_DEBUG -#include -#endif - namespace libprojectM { namespace MilkdropPreset { @@ -45,18 +42,12 @@ void FinalComposite::LoadCompositeShader(const PresetState& presetState) try { m_compositeShader->LoadCode(presetState.compositeShader); -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Composite Shader] Loaded composite shader code." << std::endl; -#endif + LOG_DEBUG("[FinalComposite] Successfully loaded composite shader code."); } catch (Renderer::ShaderException& ex) { -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Composite Shader] Error loading composite warp shader code:" << ex.message() << std::endl; - std::cerr << "[Composite Shader] Using fallback shader." << std::endl; -#else - (void) ex; // silence unused parameter warning -#endif + LOG_WARN("[FinalComposite] Error loading composite warp shader code: " + ex.message() + " - Using fallback shader."); + // Fall back to default shader m_compositeShader = std::make_unique(MilkdropShader::ShaderType::CompositeShader); m_compositeShader->LoadCode(defaultCompositeShader); @@ -64,10 +55,8 @@ void FinalComposite::LoadCompositeShader(const PresetState& presetState) } else { + LOG_DEBUG("[FinalComposite] No composite shader code in preset, loading default."); m_compositeShader->LoadCode(defaultCompositeShader); -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Composite Shader] Loaded default composite shader code." << std::endl; -#endif } } else @@ -91,18 +80,12 @@ void FinalComposite::CompileCompositeShader(PresetState& presetState) try { m_compositeShader->LoadTexturesAndCompile(presetState); -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Composite Shader] Successfully compiled composite shader code." << std::endl; -#endif + LOG_DEBUG("[FinalComposite] Successfully compiled composite shader code."); } catch (Renderer::ShaderException& ex) { -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Composite Shader] Error compiling composite warp shader code:" << ex.message() << std::endl; - std::cerr << "[Composite Shader] Using fallback shader." << std::endl; -#else - (void) ex; // silence unused parameter warning -#endif + LOG_WARN("[FinalComposite] Error compiling composite warp shader code - Using fallback shader."); + // Fall back to default shader m_compositeShader = std::make_unique(MilkdropShader::ShaderType::CompositeShader); m_compositeShader->LoadCode(defaultCompositeShader); diff --git a/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp b/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp index f5f5327f40..81a9309681 100755 --- a/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp +++ b/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp @@ -25,9 +25,7 @@ #include "MilkdropPresetExceptions.hpp" #include "PresetFileParser.hpp" -#ifdef MILKDROP_PRESET_DEBUG -#include -#endif +#include namespace libprojectM { namespace MilkdropPreset { @@ -205,9 +203,7 @@ void MilkdropPreset::PerFrameUpdate() void MilkdropPreset::Load(const std::string& pathname) { -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Preset] Loading preset from file \"" << pathname << "\"." << std::endl; -#endif + LOG_DEBUG("[MilkdropPreset] Loading preset from file \"" + pathname + "\".") SetFilename(ParseFilename(pathname)); @@ -215,10 +211,9 @@ void MilkdropPreset::Load(const std::string& pathname) if (!parser.Read(pathname)) { -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Preset] Could not parse preset file." << std::endl; -#endif - throw MilkdropPresetLoadException("Could not parse preset file \"" + pathname + "\""); + const std::string error = "[MilkdropPreset] Could not parse preset file \"" + pathname + "\"."; + LOG_ERROR(error) + throw MilkdropPresetLoadException(error); } InitializePreset(parser); @@ -226,18 +221,15 @@ void MilkdropPreset::Load(const std::string& pathname) void MilkdropPreset::Load(std::istream& stream) { -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Preset] Loading preset from stream." << std::endl; -#endif + LOG_DEBUG("[MilkdropPreset] Loading preset from stream."); ::libprojectM::PresetFileParser parser; if (!parser.Read(stream)) { -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Preset] Could not parse preset data." << std::endl; -#endif - throw MilkdropPresetLoadException("Could not parse preset data."); + const std::string error = "[MilkdropPreset] Could not parse preset data."; + LOG_ERROR(error) + throw MilkdropPresetLoadException(error); } InitializePreset(parser); diff --git a/src/libprojectM/MilkdropPreset/MilkdropPresetExceptions.hpp b/src/libprojectM/MilkdropPreset/MilkdropPresetExceptions.hpp index 37ec50ab7b..f7ef51f1d5 100644 --- a/src/libprojectM/MilkdropPreset/MilkdropPresetExceptions.hpp +++ b/src/libprojectM/MilkdropPreset/MilkdropPresetExceptions.hpp @@ -12,13 +12,18 @@ namespace MilkdropPreset { class MilkdropPresetLoadException : public std::exception { public: - inline MilkdropPresetLoadException(std::string message) + MilkdropPresetLoadException(std::string message) : m_message(std::move(message)) { } virtual ~MilkdropPresetLoadException() = default; + const char* what() const noexcept override + { + return m_message.c_str(); + } + const std::string& message() const { return m_message; @@ -35,13 +40,18 @@ class MilkdropPresetLoadException : public std::exception class MilkdropCompileException : public std::exception { public: - inline MilkdropCompileException(std::string message) + MilkdropCompileException(std::string message) : m_message(std::move(message)) { } virtual ~MilkdropCompileException() = default; + const char* what() const noexcept override + { + return m_message.c_str(); + } + const std::string& message() const { return m_message; diff --git a/src/libprojectM/MilkdropPreset/MilkdropShader.cpp b/src/libprojectM/MilkdropPreset/MilkdropShader.cpp index f2b203fe30..a19af6512b 100644 --- a/src/libprojectM/MilkdropPreset/MilkdropShader.cpp +++ b/src/libprojectM/MilkdropPreset/MilkdropShader.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -331,10 +332,15 @@ auto MilkdropShader::Shader() -> Renderer::Shader& void MilkdropShader::PreprocessPresetShader(std::string& program) { + std::string shaderTypeString = "composite"; + if (m_type == ShaderType::WarpShader) + { + shaderTypeString = "warp"; + } if (program.length() <= 0) { - throw Renderer::ShaderException("Preset shader is declared, but empty."); + throw Renderer::ShaderException("[MilkdropShader] Preset " + shaderTypeString + " shader is declared, but empty."); } size_t found; @@ -391,7 +397,8 @@ void PS(float4 _vDiffuse : COLOR, } else { - throw Renderer::ShaderException("Preset shader is missing \"shader_body\" entry point."); + LOG_DEBUG("[MilkdropShader] Failed " + shaderTypeString + " shader code:\n" + program); + throw Renderer::ShaderException("[MilkdropShader] Preset " + shaderTypeString + " shader is missing \"shader_body\" entry point."); } // replace the "{" immediately following shader_body with some variable declarations @@ -407,7 +414,8 @@ void PS(float4 _vDiffuse : COLOR, } else { - throw Renderer::ShaderException("Preset shader has no opening braces."); + LOG_DEBUG("[MilkdropShader] Failed " + shaderTypeString + " shader code:\n" + program); + throw Renderer::ShaderException("[MilkdropShader] Preset " + shaderTypeString + " shader has no opening braces."); } // replace "}" with return statement (this can probably be optimized for the GLSL conversion...) @@ -419,7 +427,8 @@ void PS(float4 _vDiffuse : COLOR, } else { - throw Renderer::ShaderException("Preset shader has no closing brace."); + LOG_DEBUG("[MilkdropShader] Failed " + shaderTypeString + " shader code:\n" + program); + throw Renderer::ShaderException("[MilkdropShader] Preset " + shaderTypeString + " shader has no closing brace."); } // Find matching closing brace and cut off excess text after shader's main function @@ -591,7 +600,8 @@ void MilkdropShader::TranspileHLSLShader(const PresetState& presetState, std::st std::string sourcePreprocessed; if (!parser.ApplyPreprocessor("", program.c_str(), program.size(), sourcePreprocessed)) { - throw Renderer::ShaderException("Error translating HLSL " + shaderTypeString + " shader: Preprocessing failed.\nSource:\n" + program); + LOG_DEBUG("[MilkdropShader] Failed " + shaderTypeString + " shader code:\n" + program); + throw Renderer::ShaderException("Error translating HLSL " + shaderTypeString + " shader: Preprocessing failed."); } // Remove previous shader declarations @@ -644,7 +654,9 @@ void MilkdropShader::TranspileHLSLShader(const PresetState& presetState, std::st // First, parse HLSL into a tree if (!parser.Parse("", sourcePreprocessed.c_str(), sourcePreprocessed.size())) { - throw Renderer::ShaderException("Error translating HLSL " + shaderTypeString + " shader: HLSL parsing failed.\nSource:\n" + sourcePreprocessed); + LOG_DEBUG("[MilkdropShader] Failed " + shaderTypeString + " shader code:\n" + program); + LOG_DEBUG("[MilkdropShader] Failed preprocessed " + shaderTypeString + " shader code:\n" + sourcePreprocessed); + throw Renderer::ShaderException("[MilkdropShader] Error translating HLSL " + shaderTypeString + " shader: HLSL parsing failed."); } // Then generate GLSL from the resulting parser tree @@ -652,9 +664,13 @@ void MilkdropShader::TranspileHLSLShader(const PresetState& presetState, std::st MilkdropStaticShaders::Get()->GetGlslGeneratorVersion(), "PS", M4::GLSLGenerator::Options(M4::GLSLGenerator::Flag_AlternateNanPropagation))) { - throw Renderer::ShaderException("Error translating HLSL " + shaderTypeString + " shader: GLSL generating failed.\nSource:\n" + sourcePreprocessed); + LOG_DEBUG("[MilkdropShader] Failed " + shaderTypeString + " shader code:\n" + program); + LOG_DEBUG("[MilkdropShader] Failed preprocessed " + shaderTypeString + " shader code:\n" + sourcePreprocessed); + throw Renderer::ShaderException("[MilkdropShader] Error translating HLSL " + shaderTypeString + " shader: GLSL generating failed.\nSource:\n" + sourcePreprocessed); } + LOG_TRACE("[MilkdropShader] Transpiled GLSL " + shaderTypeString + " shader code:\n" + std::string(generator.GetResult())); + // Now we have GLSL source for the preset shader program (hopefully it's valid!) // Compile the preset shader fragment shader with the standard vertex shader and cross our fingers. if (m_type == ShaderType::WarpShader) diff --git a/src/libprojectM/MilkdropPreset/MilkdropStaticShaders.cpp.in b/src/libprojectM/MilkdropPreset/MilkdropStaticShaders.cpp.in index 8648ed4139..973ca85710 100644 --- a/src/libprojectM/MilkdropPreset/MilkdropStaticShaders.cpp.in +++ b/src/libprojectM/MilkdropPreset/MilkdropStaticShaders.cpp.in @@ -13,17 +13,6 @@ namespace MilkdropPreset { MilkdropStaticShaders::MilkdropStaticShaders(bool useGLES) : m_useGLES(useGLES) { - m_GLSLVersion = Renderer::Shader::GetShaderLanguageVersion(); - - if (m_GLSLVersion.major == 0) - { - throw std::runtime_error("Could not retrieve OpenGL shader language version. Is OpenGL available and the context initialized?"); - } - if (m_GLSLVersion.major < 3) - { - throw std::runtime_error("OpenGL shader language version 3 or higher is required, but not available in the current context."); - } - if (m_useGLES) { // If GLES is enabled, use the embedded specification language variant. diff --git a/src/libprojectM/MilkdropPreset/MilkdropStaticShaders.hpp.in b/src/libprojectM/MilkdropPreset/MilkdropStaticShaders.hpp.in index 5e5942fb7b..6f4a0414f2 100644 --- a/src/libprojectM/MilkdropPreset/MilkdropStaticShaders.hpp.in +++ b/src/libprojectM/MilkdropPreset/MilkdropStaticShaders.hpp.in @@ -64,7 +64,6 @@ private: std::string AddVersionHeader(std::string shader_text); bool m_useGLES{false}; //!< Whether or not to use GLES shaders. - Renderer::Shader::GlslVersion m_GLSLVersion{}; //!< The queried GLSL version. std::string m_versionHeader; //!< The version header to prepended by AddVersionHeader. M4::GLSLGenerator::Version m_GLSLGeneratorVersion; //!< The GLSL generator version to pass to the hlslparser generator. }; diff --git a/src/libprojectM/MilkdropPreset/PerFrameContext.cpp b/src/libprojectM/MilkdropPreset/PerFrameContext.cpp index a408e63742..b2063825be 100644 --- a/src/libprojectM/MilkdropPreset/PerFrameContext.cpp +++ b/src/libprojectM/MilkdropPreset/PerFrameContext.cpp @@ -2,9 +2,7 @@ #include "MilkdropPresetExceptions.hpp" -#ifdef MILKDROP_PRESET_DEBUG -#include -#endif +#include #define REG_VAR(var) \ var = projectm_eval_context_register_variable(perFrameCodeContext, #var); @@ -126,16 +124,22 @@ void PerFrameContext::EvaluateInitCode(PresetState& state) auto* initCode = projectm_eval_code_compile(perFrameCodeContext, state.perFrameInitCode.c_str()); if (initCode == nullptr) { -#ifdef MILKDROP_PRESET_DEBUG + std::string error; int line; int col; auto* errmsg = projectm_eval_get_error(perFrameCodeContext, &line, &col); if (errmsg) { - std::cerr << "[Preset] Could not compile per-frame INIT code: " << errmsg << "(L" << line << " C" << col << ")" << std::endl; + error = "[PerFrameContext] Could not compile per-frame INIT code: "; + error += errmsg; + error += "(L" + std::to_string(line) + " C" + std::to_string(col) + ")"; } -#endif - throw MilkdropCompileException("Could not compile per-frame init code"); + else + { + error = "[PerFrameContext] Could not compile per-frame init code."; + } + LOG_DEBUG("[PerFrameContext] Failed per-frame INIT code:\n" + state.perFrameInitCode); + throw MilkdropCompileException(error); } projectm_eval_code_execute(initCode); @@ -241,16 +245,22 @@ void PerFrameContext::CompilePerFrameCode(const std::string& perFrameCode) perFrameCodeHandle = projectm_eval_code_compile(perFrameCodeContext, perFrameCode.c_str()); if (perFrameCodeHandle == nullptr) { -#ifdef MILKDROP_PRESET_DEBUG + std::string error; int line; int col; auto* errmsg = projectm_eval_get_error(perFrameCodeContext, &line, &col); if (errmsg) { - std::cerr << "[Preset] Could not compile per-frame code: " << errmsg << "(L" << line << " C" << col << ")" << std::endl; + error = "[PerFrameContext] Could not compile per-frame code: "; + error += errmsg; + error += "(L" + std::to_string(line) + " C" + std::to_string(col) + ")"; + } + else + { + error = "[PerFrameContext] Could not compile per-frame code."; } -#endif - throw MilkdropCompileException("Could not compile per-frame code"); + LOG_DEBUG("[PerFrameContext] Failed per-frame code:\n" + perFrameCode); + throw MilkdropCompileException(error); } } diff --git a/src/libprojectM/MilkdropPreset/PerPixelContext.cpp b/src/libprojectM/MilkdropPreset/PerPixelContext.cpp index 378258dc0c..ce19ff47f4 100644 --- a/src/libprojectM/MilkdropPreset/PerPixelContext.cpp +++ b/src/libprojectM/MilkdropPreset/PerPixelContext.cpp @@ -2,9 +2,7 @@ #include "MilkdropPresetExceptions.hpp" -#ifdef MILKDROP_PRESET_DEBUG -#include -#endif +#include #define REG_VAR(var) \ var = projectm_eval_context_register_variable(perPixelCodeContext, #var); @@ -110,13 +108,22 @@ void PerPixelContext::CompilePerPixelCode(const std::string& perPixelCode) perPixelCodeHandle = projectm_eval_code_compile(perPixelCodeContext, perPixelCode.c_str()); if (perPixelCodeHandle == nullptr) { -#ifdef MILKDROP_PRESET_DEBUG + std::string error; int line; int col; auto* errmsg = projectm_eval_get_error(perPixelCodeContext, &line, &col); - std::cerr << "[Preset] Could not compile per-pixel code: " << errmsg << "(L" << line << " C" << col << ")" << std::endl; -#endif - throw MilkdropCompileException("Could not compile per-pixel code"); + if (errmsg) + { + error = "[PerPixelContext] Could not compile per-pixel code: "; + error += errmsg; + error += "(L" + std::to_string(line) + " C" + std::to_string(col) + ")"; + } + else + { + error = "[PerPixelContext] Could not compile per-pixel code."; + } + LOG_DEBUG("[PerPixelContext] Failed per-pixel code:\n" + perPixelCode); + throw MilkdropCompileException(error); } } diff --git a/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp b/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp index 8241910a14..6e2bf9cc1c 100644 --- a/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp +++ b/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp @@ -6,16 +6,13 @@ #include "PerPixelContext.hpp" #include "PresetState.hpp" +#include #include #include #include #include -#ifdef MILKDROP_PRESET_DEBUG -#include -#endif - namespace libprojectM { namespace MilkdropPreset { @@ -57,17 +54,12 @@ void PerPixelMesh::LoadWarpShader(const PresetState& presetState) { m_warpShader = std::make_unique(MilkdropShader::ShaderType::WarpShader); m_warpShader->LoadCode(presetState.warpShader); -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Warp Shader] Loaded preset warp shader code." << std::endl; -#endif + LOG_DEBUG("[PerPixelMesh] Successfully loaded preset warp shader code."); } catch (Renderer::ShaderException& ex) { -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Warp Shader] Error loading warp shader code:" << ex.message() << std::endl; -#else - (void) ex; // silence unused parameter warning -#endif + LOG_ERROR("[PerPixelMesh] Error loading warp shader code:" + ex.message()); + LOG_DEBUG("[PerPixelMesh] Warp shader code:\n" + presetState.warpShader); m_warpShader.reset(); } } @@ -81,17 +73,11 @@ void PerPixelMesh::CompileWarpShader(PresetState& presetState) try { m_warpShader->LoadTexturesAndCompile(presetState); -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Warp Shader] Successfully compiled warp shader code." << std::endl; -#endif + LOG_DEBUG("[PerPixelMesh] Successfully compiled warp shader code."); } - catch (Renderer::ShaderException& ex) + catch (Renderer::ShaderException&) { -#ifdef MILKDROP_PRESET_DEBUG - std::cerr << "[Warp Shader] Error compiling warp shader code:" << ex.message() << std::endl; -#else - (void) ex; // silence unused parameter warning -#endif + LOG_ERROR("[PerPixelMesh] Error compiling warp shader code."); m_warpShader.reset(); } } diff --git a/src/libprojectM/MilkdropPreset/ShapePerFrameContext.cpp b/src/libprojectM/MilkdropPreset/ShapePerFrameContext.cpp index fe804de9ea..5877692f31 100644 --- a/src/libprojectM/MilkdropPreset/ShapePerFrameContext.cpp +++ b/src/libprojectM/MilkdropPreset/ShapePerFrameContext.cpp @@ -4,9 +4,7 @@ #include "MilkdropPresetExceptions.hpp" #include "PerFrameContext.hpp" -#ifdef MILKDROP_PRESET_DEBUG -#include -#endif +#include #define REG_VAR(var) \ var = projectm_eval_context_register_variable(perFrameCodeContext, #var); @@ -146,16 +144,24 @@ void ShapePerFrameContext::EvaluateInitCode(const std::string& perFrameInitCode, auto* initCode = projectm_eval_code_compile(perFrameCodeContext, perFrameInitCode.c_str()); if (initCode == nullptr) { -#ifdef MILKDROP_PRESET_DEBUG + std::string error; int line; int col; auto* errmsg = projectm_eval_get_error(perFrameCodeContext, &line, &col); if (errmsg) { - std::cerr << "[Preset] Could not compile custom shape " << shape.m_index << " per-frame INIT code: " << errmsg << "(L" << line << " C" << col << ")" << std::endl; + error = "[ShapePerFrameContext] Could not compile custom shape "; + error += std::to_string(shape.m_index); + error += " per-frame INIT code: "; + error += std::string(errmsg); + error += "(L" + std::to_string(line) + " C" + std::to_string(col) + ")"; } -#endif - throw MilkdropCompileException("Could not compile custom shape " + std::to_string(shape.m_index) + " per-frame init code"); + else + { + error = "[ShapePerFrameContext] Could not compile custom shape " + std::to_string(shape.m_index) + " per-frame init code."; + } + LOG_DEBUG("[ShapePerFrameContext] Failed custom shape per-frame INIT code:\n" + perFrameInitCode); + throw MilkdropCompileException(error); } projectm_eval_code_execute(initCode); @@ -173,16 +179,24 @@ void ShapePerFrameContext::CompilePerFrameCode(const std::string& perFrameCode, perFrameCodeHandle = projectm_eval_code_compile(perFrameCodeContext, perFrameCode.c_str()); if (perFrameCodeHandle == nullptr) { -#ifdef MILKDROP_PRESET_DEBUG + std::string error; int line; int col; auto* errmsg = projectm_eval_get_error(perFrameCodeContext, &line, &col); if (errmsg) { - std::cerr << "[Preset] Could not compile custom shape " << shape.m_index << " per-frame code: " << errmsg << "(L" << line << " C" << col << ")" << std::endl; + error = "[ShapePerFrameContext] Could not compile custom shape "; + error += std::to_string(shape.m_index); + error += " per-frame code: "; + error += errmsg; + error += "(L" + std::to_string(line) + " C" + std::to_string(col) + ")"; + } + else + { + error = "[ShapePerFrameContext] Could not compile custom shape " + std::to_string(shape.m_index) + " per-frame code."; } -#endif - throw MilkdropCompileException("Could not compile custom shape " + std::to_string(shape.m_index) + " per-frame code"); + LOG_DEBUG("[ShapePerFrameContext] Failed custom shape per-frame code:\n" + perFrameCode); + throw MilkdropCompileException(error); } } diff --git a/src/libprojectM/MilkdropPreset/WaveformPerFrameContext.cpp b/src/libprojectM/MilkdropPreset/WaveformPerFrameContext.cpp index 926a762924..c80a006474 100644 --- a/src/libprojectM/MilkdropPreset/WaveformPerFrameContext.cpp +++ b/src/libprojectM/MilkdropPreset/WaveformPerFrameContext.cpp @@ -4,9 +4,7 @@ #include "MilkdropPresetExceptions.hpp" #include "PerFrameContext.hpp" -#ifdef MILKDROP_PRESET_DEBUG -#include -#endif +#include #define REG_VAR(var) \ var = projectm_eval_context_register_variable(perFrameCodeContext, #var); @@ -107,16 +105,24 @@ void WaveformPerFrameContext::EvaluateInitCode(const std::string& perFrameInitCo auto* initCode = projectm_eval_code_compile(perFrameCodeContext, perFrameInitCode.c_str()); if (initCode == nullptr) { -#ifdef MILKDROP_PRESET_DEBUG + std::string error; int line; int col; auto* errmsg = projectm_eval_get_error(perFrameCodeContext, &line, &col); if (errmsg) { - std::cerr << "[Preset] Could not compile custom wave " << waveform.m_index << " per-frame INIT code: " << errmsg << "(L" << line << " C" << col << ")" << std::endl; + error = "[WaveformPerFrameContext] Could not compile custom wave "; + error += std::to_string(waveform.m_index); + error += " per-frame INIT code: "; + error += errmsg; + error += "(L" + std::to_string(line) + " C" + std::to_string(col) + ")"; } -#endif - throw MilkdropCompileException("Could not compile custom wave " + std::to_string(waveform.m_index) + " per-frame init code"); + else + { + error = "[WaveformPerFrameContext] Could not compile custom wave " + std::to_string(waveform.m_index) + " per-frame init code."; + } + LOG_DEBUG("[WaveformPerFrameContext] Failed custom wave per-frame INIT code:\n" + perFrameInitCode); + throw MilkdropCompileException(error); } projectm_eval_code_execute(initCode); @@ -134,16 +140,24 @@ void WaveformPerFrameContext::CompilePerFrameCode(const std::string& perFrameCod perFrameCodeHandle = projectm_eval_code_compile(perFrameCodeContext, perFrameCode.c_str()); if (perFrameCodeHandle == nullptr) { -#ifdef MILKDROP_PRESET_DEBUG - int line; + std::string error; + int line; int col; auto* errmsg = projectm_eval_get_error(perFrameCodeContext, &line, &col); if (errmsg) { - std::cerr << "[Preset] Could not compile custom wave " << waveform.m_index << " per-frame code: " << errmsg << "(L" << line << " C" << col << ")" << std::endl; + error = "[WaveformPerFrameContext] Could not compile custom wave "; + error += waveform.m_index; + error += " per-frame code: "; + error += errmsg; + error += "(L" + std::to_string(line) + " C" + std::to_string(col) + ")"; + } + else + { + error = "[WaveformPerFrameContext] Could not compile custom wave " + std::to_string(waveform.m_index) + " per-frame code."; } -#endif - throw MilkdropCompileException("Could not compile custom wave " + std::to_string(waveform.m_index) + " per-frame code"); + LOG_DEBUG("[WaveformPerFrameContext] Failed custom wave per-frame code:\n" + perFrameCode); + throw MilkdropCompileException(error); } } diff --git a/src/libprojectM/MilkdropPreset/WaveformPerPointContext.cpp b/src/libprojectM/MilkdropPreset/WaveformPerPointContext.cpp index 5e775dc8ae..e73fed4aec 100644 --- a/src/libprojectM/MilkdropPreset/WaveformPerPointContext.cpp +++ b/src/libprojectM/MilkdropPreset/WaveformPerPointContext.cpp @@ -4,9 +4,7 @@ #include "MilkdropPresetExceptions.hpp" #include "PerFrameContext.hpp" -#ifdef MILKDROP_PRESET_DEBUG -#include -#endif +#include #define REG_VAR(var) \ var = projectm_eval_context_register_variable(perPointCodeContext, #var); @@ -95,16 +93,24 @@ void WaveformPerPointContext::CompilePerPointCode(const std::string& perPointCod perPointCodeHandle = projectm_eval_code_compile(perPointCodeContext, perPointCode.c_str()); if (perPointCodeHandle == nullptr) { -#ifdef MILKDROP_PRESET_DEBUG + std::string error; int line; int col; auto* errmsg = projectm_eval_get_error(perPointCodeContext, &line, &col); if (errmsg) { - std::cerr << "[Preset] Could not compile custom wave " << waveform.m_index << " per-point code: " << errmsg << "(L" << line << " C" << col << ")" << std::endl; + error = "[WaveformPerPointContext] Could not compile custom wave "; + error += std::to_string(waveform.m_index); + error += " per-point code: "; + error += errmsg; + error += "(L" + std::to_string(line) + " C" + std::to_string(col) + ")"; } -#endif - throw MilkdropCompileException("Could not compile custom wave " + std::to_string(waveform.m_index) + " per-point code"); + else + { + error = "[WaveformPerPointContext] Could not compile custom wave " + std::to_string(waveform.m_index) + " per-point code."; + } + LOG_DEBUG("[WaveformPerPointContext] Failed custom wave " + std::to_string(waveform.m_index) + " per-point code:\n" + perPointCode); + throw MilkdropCompileException(error); } } diff --git a/src/libprojectM/PresetFactory.cpp b/src/libprojectM/PresetFactory.cpp index 3c56876b2b..cf19047f12 100644 --- a/src/libprojectM/PresetFactory.cpp +++ b/src/libprojectM/PresetFactory.cpp @@ -1,8 +1,6 @@ #include "PresetFactory.hpp" -#ifdef DEBUG -#include -#endif +#include namespace libprojectM { @@ -17,9 +15,8 @@ std::string PresetFactory::Protocol(const std::string& url, std::string& path) } path = url.substr(pos + 3, url.length()); -#ifdef DEBUG - std::cout << "[PresetFactory] Filename is URL: " << url << std::endl; -#endif + + LOG_DEBUG("[PresetFactory] Preset filename is URL: " + url); return url.substr(0, pos); } diff --git a/src/libprojectM/PresetFactoryManager.cpp b/src/libprojectM/PresetFactoryManager.cpp index a5f8235474..7934f8ef4c 100644 --- a/src/libprojectM/PresetFactoryManager.cpp +++ b/src/libprojectM/PresetFactoryManager.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -36,11 +37,9 @@ void PresetFactoryManager::initialize() registerFactory(milkdropFactory->supportedExtensions(), milkdropFactory); } -// Current behavior if a conflict is occurs is to override the previous request - void PresetFactoryManager::registerFactory(const std::string& extensions, PresetFactory* factory) { - + // Current behavior if an extension conflict occurs is to override the previous once std::stringstream ss(extensions); std::string extension; @@ -50,7 +49,7 @@ void PresetFactoryManager::registerFactory(const std::string& extensions, Preset { if (m_factoryMap.count(extension)) { - std::cerr << "[PresetFactoryManager] Warning: extension \"" << extension << "\" already has a factory. New factory handler ignored." << std::endl; + LOG_ERROR("[PresetFactoryManager] Warning: extension \"" + extension + "\" already has a factory. New factory handler ignored."); } else { @@ -78,7 +77,7 @@ std::unique_ptr PresetFactoryManager::CreatePresetFromFile(const std::st } catch (...) { - throw PresetFactoryException("Uncaught preset factory exception"); + throw PresetFactoryException("[PresetFactoryManager] Uncaught preset factory exception."); } } @@ -98,19 +97,21 @@ std::unique_ptr PresetFactoryManager::CreatePresetFromStream(const std:: } catch (...) { - throw PresetFactoryException("Uncaught preset factory exception"); + throw PresetFactoryException("[PresetFactoryManager] Uncaught preset factory exception."); } } PresetFactory& PresetFactoryManager::factory(const std::string& extension) { - if (!extensionHandled(extension)) { - std::ostringstream os; - os << "No preset factory associated with \"" << extension << "\"." << std::endl; - throw PresetFactoryException(os.str()); + std::string error = "[PresetFactoryManager] No preset factory associated with extension \""; + error += extension; + error += "\""; + LOG_ERROR(error); + throw PresetFactoryException(error); } + return *m_factoryMap[extension]; } diff --git a/src/libprojectM/PresetFactoryManager.hpp b/src/libprojectM/PresetFactoryManager.hpp index 2b4363033b..b4b7b60068 100644 --- a/src/libprojectM/PresetFactoryManager.hpp +++ b/src/libprojectM/PresetFactoryManager.hpp @@ -12,13 +12,18 @@ namespace libprojectM { class PresetFactoryException : public std::exception { public: - inline PresetFactoryException(std::string message) + PresetFactoryException(std::string message) : m_message(std::move(message)) { } virtual ~PresetFactoryException() = default; + const char* what() const noexcept override + { + return m_message.c_str(); + } + const std::string& message() const { return m_message; diff --git a/src/libprojectM/ProjectM.cpp b/src/libprojectM/ProjectM.cpp index f307a44e43..4c6a702370 100644 --- a/src/libprojectM/ProjectM.cpp +++ b/src/libprojectM/ProjectM.cpp @@ -21,6 +21,7 @@ #include "ProjectM.hpp" +#include "Logging.hpp" #include "Preset.hpp" #include "PresetFactoryManager.hpp" #include "TimeKeeper.hpp" @@ -65,6 +66,7 @@ void ProjectM::LoadPresetFile(const std::string& presetFilename, bool smoothTran } catch (const std::exception& ex) { + LOG_ERROR(ex.what()); PresetSwitchFailedEvent(presetFilename, ex.what()); } } @@ -78,6 +80,7 @@ void ProjectM::LoadPresetData(std::istream& presetData, bool smoothTransition) } catch (const std::exception& ex) { + LOG_ERROR(ex.what()); PresetSwitchFailedEvent("", ex.what()); } } @@ -189,16 +192,14 @@ void ProjectM::RenderFrame(uint32_t targetFramebufferObject /*= 0*/) void ProjectM::Initialize() { - /** Initialise start time */ + // Check OpenGL first before allocating any additional memory. + CheckGLSLVersion(); + m_timeKeeper = std::make_unique(m_presetDuration, m_softCutDuration, m_hardCutDuration, m_easterEgg); - /** Nullify frame stash */ - - /** Initialise per-pixel matrix calculations */ - /** We need to initialise this before the builtin param db otherwise bass/mid etc won't bind correctly */ m_textureManager = std::make_unique(m_textureSearchPaths); m_shaderCache = std::make_unique(); @@ -210,14 +211,38 @@ void ProjectM::Initialize() m_presetFactoryManager->initialize(); - /* Set the seed to the current time in seconds */ - srand(time(nullptr)); - LoadIdlePreset(); m_timeKeeper->StartPreset(); } +void ProjectM::CheckGLSLVersion() +{ + auto glslVersion = Renderer::Shader::GetShaderLanguageVersion(); + + if (glslVersion.major == 0) + { + std::string error = "Could not retrieve OpenGL shader language version. Is OpenGL available and the context initialized?"; + LOG_FATAL(error); + throw std::runtime_error(error); + } +#ifdef USE_GLES + if (glslVersion.major < 3) + { + std::string error = "OpenGL ES shading language version 3.00 or higher is required, but the current context only provides version " + std::to_string(glslVersion.major) + "." + std::to_string(glslVersion.minor) + "."; + LOG_FATAL(error); + throw std::runtime_error(error); + } +#else + if (glslVersion.major < 3 || (glslVersion.major == 3 && glslVersion.minor < 30)) + { + std::string error = "OpenGL shading language version 3.30 or higher is required, but the current context only provides version " + std::to_string(glslVersion.major) + "." + std::to_string(glslVersion.minor) + "."; + LOG_FATAL(error); + throw std::runtime_error(error); + } +#endif +} + void ProjectM::LoadIdlePreset() { LoadPresetFile("idle://Geiss & Sperl - Feedback (projectM idle HDR mix).milk", false); diff --git a/src/libprojectM/ProjectM.hpp b/src/libprojectM/ProjectM.hpp index ca9afa6f6d..1a1c53de89 100644 --- a/src/libprojectM/ProjectM.hpp +++ b/src/libprojectM/ProjectM.hpp @@ -259,6 +259,8 @@ class PROJECTM_EXPORT ProjectM private: void Initialize(); + void CheckGLSLVersion(); + void StartPresetTransition(std::unique_ptr&& preset, bool hardCut); void LoadIdlePreset(); diff --git a/src/libprojectM/ProjectMCWrapper.cpp b/src/libprojectM/ProjectMCWrapper.cpp index 506f12ceeb..839731b7ec 100644 --- a/src/libprojectM/ProjectMCWrapper.cpp +++ b/src/libprojectM/ProjectMCWrapper.cpp @@ -2,6 +2,8 @@ #include +#include + #include