From cc9bd6a6a63257d30b93051b24fe98854b0d7ed4 Mon Sep 17 00:00:00 2001 From: Kenneth Treadaway <121038701+CyberKenneth@users.noreply.github.com> Date: Mon, 17 Feb 2025 22:50:28 -0600 Subject: [PATCH 1/2] Update MacOSDebug.mm This update is a alpha level that is ment to extend the Debug suite for macOS, adding improved debugging support and enhancements, such as logging thread management, platform-specific memory debugging tools, and advanced debug output formatting. The extended debug suite includes configurable logging levels, timestamps, function/file contexts, and thread identifiers, with the goal of making debug output more informative and precise. This is intended for better handling of cross-platform debugging scenarios on macOS, although some issues may require further testing on macOS devices. Changes: Refined and extended the debug functionality with additional configurations for log level, thread mode, and output formatting. Integrated platform-specific checks for macOS version and available memory tools. Improved logging mechanism with a mutex and thread-local storage for better concurrency handling. Enhanced the debug output format with optional timestamps, thread identification, function names, and file/line references. Added fallbacks for debugging on macOS versions that do not support certain memory debugging features. added in a section for contributors to get credits if ok with Lukas. Author: Kenneth Treadaway (extended this debug suite) Original Author: Lukas Hermanns --- sources/Platform/MacOS/MacOSDebug.mm | 519 ++++++++++++++++++++++++++- 1 file changed, 516 insertions(+), 3 deletions(-) diff --git a/sources/Platform/MacOS/MacOSDebug.mm b/sources/Platform/MacOS/MacOSDebug.mm index 5e8dd05e90..5b57a7a90e 100644 --- a/sources/Platform/MacOS/MacOSDebug.mm +++ b/sources/Platform/MacOS/MacOSDebug.mm @@ -3,18 +3,352 @@ * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + * Contributors + * Lukas Hermanns (original author) + * Kenneth Treadaway (Extended this Debug suite) + * + * Notes + * KT I do not own a Mac so this is the best I can do from where I am sitting would appreciate it if any kinks are worked out on the platform. I used apple website and opensource github to rough + * what you see out so no guarantee of working yet. */ #include "../Debug.h" #include - +#include +#include +#include +#include +#include +#include +#include #import +#import +/** + * @file AvailabilityMacros.h + * @brief Defines platform and version availability macros for cross-platform development + * + * This header provides a set of preprocessor macros that help manage API availability + * across different platforms, operating system versions, and SDK versions. These macros + * are crucial for: + * - Conditionally compiling code based on platform support + * - Marking deprecated APIs + * - Handling platform-specific feature availability + * + * Key Macro Categories: + * 1. Platform Availability Macros + * - Define which platforms support specific APIs or features + * - Help manage conditional compilation for different OS versions + * + * 2. Deprecation Macros + * - Mark APIs that are no longer recommended + * - Provide compiler warnings for usage of deprecated functions + * + * 3. Availability Annotations + * - Specify when an API was introduced + * - Indicate the minimum OS version required for a particular feature + * + * Usage Examples: + * @code + * // Conditionally compile code for specific platform versions + * #if defined(MAC_OS_X_VERSION_10_12) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) + * // Code specific to macOS Sierra and later + * #endif + * + * // Check API availability + * #if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_15 + * // Use newer API only if minimum deployment target supports it + * #endif + * @endcode + * + * Typical Macro Definitions: + * - Platform version macros (e.g., MAC_OS_X_VERSION_10_12) + * - Availability and deprecation annotations + * - Conditional compilation directives + * + * @note This header is typically platform-specific and may vary between + * different operating systems and development environments. + * + * @warning Incorrect usage of these macros can lead to compilation issues + * or unexpected runtime behavior. + * + * @see https://github.com/apple-oss-distributions/xnu/blob/xnu-11215.61.5/EXTERNAL_HEADERS/AvailabilityMacros.h + */ namespace LLGL { +namespace +{ + // Thread-local variable to track if this thread is already logging + thread_local bool g_isLoggingThread = false; + std::atomic g_currentOwner{}; + std::mutex g_debugMutex; + + struct DebugConfig + { + DebugLevel minLevel = DebugLevel::Info; + DebugThreadMode threadMode = DebugThreadMode::Unsafe; + DebugFormatFlags formatFlags = DebugFormatFlags::Default; + bool toolsEnabled = false; + char dateFormat[64] = "%Y-%m-%d %H:%M:%S"; + }; + + DebugConfig g_config; + + /** + * \brief Optimized lock acquisition for debug output. + */ + std::unique_lock GetOptimizedLock() + { + // Fast path - check if we're already the logging thread + if (g_isLoggingThread) + return std::unique_lock(); + + // Determine if we need a lock + if (g_config.threadMode == DebugThreadMode::Unsafe) + return std::unique_lock(); + + // Try to acquire lock without blocking first + auto lock = std::unique_lock(g_debugMutex, std::try_to_lock); + if (!lock) + { + // If lock failed, take slow path with blocking acquisition + lock = std::unique_lock(g_debugMutex); + } + + // Mark this thread as the logging thread + g_isLoggingThread = true; + g_currentOwner.store(std::this_thread::get_id()); + + return lock; + } + + /** + * \brief RAII helper to reset thread logging state. + */ + class LoggingThreadGuard + { + public: + ~LoggingThreadGuard() + { + if (g_currentOwner.load() == std::this_thread::get_id()) + g_isLoggingThread = false; + } + }; + + /** + * \brief Safely sets an environment variable with error checking. + */ + bool SetEnvSafe(const char* name, const char* value, int overwrite) + { + if (::setenv(name, value, overwrite) != 0) + { + #ifdef LLGL_DEBUG + // Log directly to stderr to avoid recursive calls + ::fprintf(stderr, "LLGL Debug: Failed to set environment variable %s\n", name); + #endif + return false; + } + return true; + } + + /** + * \brief Checks if this macOS version supports memory debugging tools. + */ + bool IsMemoryToolsSupported() + { + // Malloc debugging features available since OS X 10.6 Snow Leopard + #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 + return true; + #else + // For older versions, check at runtime + NSOperatingSystemVersion osVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; + return (osVersion.majorVersion > 10 || + (osVersion.majorVersion == 10 && osVersion.minorVersion >= 6)); + #endif + } + + /** + * \brief Gets level indicator for debug output. + */ + NSString* GetLevelIndicator(const DebugLevel level) + { + switch (level) + { + case DebugLevel::Trace: return @"đŸ”Ŧ"; + case DebugLevel::Verbose: return @"🔍"; + case DebugLevel::Debug: return @"🔧"; + case DebugLevel::Notice: return @"📝"; + case DebugLevel::Info: return @"ℹī¸"; + case DebugLevel::Warning: return @"⚠ī¸"; + case DebugLevel::Error: return @"❌"; + case DebugLevel::Fatal: return @"đŸ’Ĩ"; + case DebugLevel::Silent: return @""; + default: return @" "; + } + } + + /** + * \brief Gets level name string. + */ + const char* GetLevelName(const DebugLevel level) + { + switch (level) + { + case DebugLevel::Trace: return "Trace"; + case DebugLevel::Verbose: return "Verbose"; + case DebugLevel::Debug: return "Debug"; + case DebugLevel::Notice: return "Notice"; + case DebugLevel::Info: return "Info"; + case DebugLevel::Warning: return "Warning"; + case DebugLevel::Error: return "Error"; + case DebugLevel::Fatal: return "Fatal"; + case DebugLevel::Silent: return "Silent"; + default: return "Unknown"; + } + } + + /** + * \brief Gets current thread identifier string. + */ + std::string GetThreadId() + { + if (g_config.threadMode == DebugThreadMode::Identify) + { + std::thread::id threadId = std::this_thread::get_id(); + std::stringstream ss; + ss << threadId; + return "[Thread:" + ss.str() + "] "; + } + return ""; + } + + /** + * \brief Gets formatted timestamp based on the current configuration. + */ + std::string GetTimestamp() + { + if ((g_config.formatFlags & DebugFormatFlags::IncludeTimestamp) == 0) + return ""; + + std::time_t now = std::time(nullptr); + char timestamp[128]; + std::strftime(timestamp, sizeof(timestamp), g_config.dateFormat, std::localtime(&now)); + return std::string("[") + timestamp + "] "; + } + + /** + * \brief Gets function context based on the current configuration. + */ + std::string GetFunctionContext(const char* function) + { + if (!function || (g_config.formatFlags & DebugFormatFlags::IncludeFunction) == 0) + return ""; + + return std::string("[") + function + "] "; + } + + /** + * \brief Gets file and line context based on the current configuration. + */ + std::string GetFileLineContext(const char* file, int line) + { + std::string result; + + if (file && (g_config.formatFlags & DebugFormatFlags::IncludeFile)) + { + // Extract filename only, not full path + const char* filename = file; + const char* lastSlash = std::strrchr(file, '/'); + if (lastSlash) + filename = lastSlash + 1; + + result += std::string("[") + filename; + + if (line > 0 && (g_config.formatFlags & DebugFormatFlags::IncludeLine)) + result += ":" + std::to_string(line); + + result += "] "; + } + else if (line > 0 && (g_config.formatFlags & DebugFormatFlags::IncludeLine)) + { + result += "[line:" + std::to_string(line) + "] "; + } + + return result; + } + + /** + * \brief Outputs debug message with specified level. + */ + void DebugOutputWithLevel(const char* text, DebugLevel level, const char* function = nullptr, const char* file = nullptr, int line = 0) + { + // Skip if below minimum level or silent + if (level < g_config.minLevel || level == DebugLevel::Silent) + return; + + // Use optimized locking strategy + auto lock = GetOptimizedLock(); + LoggingThreadGuard guard; + + #ifdef LLGL_DEBUG + @autoreleasepool + { + /* Format message with all requested information */ + std::string timestamp = GetTimestamp(); + std::string threadId = GetThreadId(); + std::string functionContext = GetFunctionContext(function); + std::string fileLineContext = GetFileLineContext(file, line); + + // Build complete message + NSString* levelMsg = nil; + if (text) + { + levelMsg = [NSString stringWithFormat:@"%@ %s%s%s%s%s", + GetLevelIndicator(level), + timestamp.c_str(), + threadId.c_str(), + functionContext.c_str(), + fileLineContext.c_str(), + text]; + } + else + { + levelMsg = [NSString stringWithFormat:@"%@ %s%s%s%s(null)", + GetLevelIndicator(level), + timestamp.c_str(), + threadId.c_str(), + functionContext.c_str(), + fileLineContext.c_str()]; + } + + NSLog(@"%@", levelMsg); + + /* Break into debugger for fatal errors */ + if (level == DebugLevel::Fatal && isatty(STDERR_FILENO)) + { + LLGL_DEBUG_BREAK(); + } + } + #else + /* Print to standard error stream with minimal formatting */ + std::string timestamp = GetTimestamp(); + std::string threadId = GetThreadId(); + const char* levelName = (level > DebugLevel::Info) ? GetLevelName(level) : ""; + + ::fprintf(stderr, "%s%s%s%s\n", + timestamp.c_str(), + levelName[0] ? "[" : "", + levelName, + levelName[0] ? "] " : "", + threadId.c_str(), + text ? text : "(null)"); + #endif + } +} +// Original debug function (maintained for compatibility) LLGL_EXPORT void DebugPuts(const char* text) { #ifdef LLGL_DEBUG @@ -26,9 +360,188 @@ LLGL_EXPORT void DebugPuts(const char* text) #endif } +// Enhanced debug functions +LLGL_EXPORT void DebugTrace(const char* text, const char* function, const char* file, int line) +{ + DebugOutputWithLevel(text, DebugLevel::Trace, function, file, line); +} + +LLGL_EXPORT void DebugVerbose(const char* text, const char* function, const char* file, int line) +{ + DebugOutputWithLevel(text, DebugLevel::Verbose, function, file, line); +} + +LLGL_EXPORT void DebugOutput(const char* text, const char* function, const char* file, int line) +{ + DebugOutputWithLevel(text, DebugLevel::Debug, function, file, line); +} -} // /namespace LLGL +LLGL_EXPORT void DebugNotice(const char* text, const char* function, const char* file, int line) +{ + DebugOutputWithLevel(text, DebugLevel::Notice, function, file, line); +} +LLGL_EXPORT void DebugInfo(const char* text, const char* function, const char* file, int line) +{ + DebugOutputWithLevel(text, DebugLevel::Info, function, file, line); +} +LLGL_EXPORT void DebugWarning(const char* text, const char* function, const char* file, int line) +{ + DebugOutputWithLevel(text, DebugLevel::Warning, function, file, line); +} -// ================================================================================ +LLGL_EXPORT void DebugError(const char* text, const char* function, const char* file, int line) +{ + DebugOutputWithLevel(text, DebugLevel::Error, function, file, line); +} + +LLGL_EXPORT void DebugFatal(const char* text, const char* function, const char* file, int line) +{ + DebugOutputWithLevel(text, DebugLevel::Fatal, function, file, line); +} + +LLGL_EXPORT bool EnableDebugMemoryTools() +{ + #ifdef LLGL_DEBUG + if (!g_config.toolsEnabled) + { + /* Check if memory tools are supported on this macOS version */ + if (!IsMemoryToolsSupported()) + { + // Log directly to avoid potential recursion + ::fprintf(stderr, "LLGL Debug: Memory debugging tools not supported on this macOS version\n"); + return false; + } + + /* Enable Malloc Stack logging for memory debugger */ + if (!SetEnvSafe("MallocStackLogging", "1", 1)) + return false; + + /* Enable Guard Pages for buffer overflow detection */ + if (!SetEnvSafe("MallocGuardEdges", "1", 1)) + return false; + + /* Enable scribbling to detect use-after-free */ + if (!SetEnvSafe("MallocScribble", "1", 1)) + return false; + + g_config.toolsEnabled = true; + return true; + } + #endif + return false; +} + +LLGL_EXPORT void DisableDebugMemoryTools() +{ + #ifdef LLGL_DEBUG + if (g_config.toolsEnabled) + { + /* Disable previously enabled memory tracking */ + SetEnvSafe("MallocStackLogging", "0", 1); + SetEnvSafe("MallocGuardEdges", "0", 1); + SetEnvSafe("MallocScribble", "0", 1); + g_config.toolsEnabled = false; + } + #endif +} + +LLGL_EXPORT void SetDebugLevel(DebugLevel level) +{ + auto lock = GetOptimizedLock(); + LoggingThreadGuard guard; + g_config.minLevel = level; +} + +LLGL_EXPORT void SetDebugThreadMode(DebugThreadMode mode) +{ + // Need to lock even when switching to unsafe mode + std::lock_guard guard(g_debugMutex); + g_config.threadMode = mode; +} + +LLGL_EXPORT void SetDebugFormatFlags(DebugFormatFlags flags) +{ + auto lock = GetOptimizedLock(); + LoggingThreadGuard guard; + g_config.formatFlags = flags; +} + +LLGL_EXPORT void SetDebugDateFormat(const char* format) +{ + if (!format) + return; + + auto lock = GetOptimizedLock(); + LoggingThreadGuard guard; + ::strncpy(g_config.dateFormat, format, sizeof(g_config.dateFormat) - 1); + g_config.dateFormat[sizeof(g_config.dateFormat) - 1] = '\0'; +} + +LLGL_EXPORT void LoadDebugConfigFromEnvironment() +{ + #ifdef LLGL_DEBUG + const char* levelStr = ::getenv("LLGL_DEBUG_LEVEL"); + if (levelStr) + { + if (::strcasecmp(levelStr, "trace") == 0) + SetDebugLevel(DebugLevel::Trace); + else if (::strcasecmp(levelStr, "verbose") == 0) + SetDebugLevel(DebugLevel::Verbose); + else if (::strcasecmp(levelStr, "debug") == 0) + SetDebugLevel(DebugLevel::Debug); + else if (::strcasecmp(levelStr, "notice") == 0) + SetDebugLevel(DebugLevel::Notice); + else if (::strcasecmp(levelStr, "info") == 0) + SetDebugLevel(DebugLevel::Info); + else if (::strcasecmp(levelStr, "warning") == 0) + SetDebugLevel(DebugLevel::Warning); + else if (::strcasecmp(levelStr, "error") == 0) + SetDebugLevel(DebugLevel::Error); + else if (::strcasecmp(levelStr, "fatal") == 0) + SetDebugLevel(DebugLevel::Fatal); + else if (::strcasecmp(levelStr, "silent") == 0) + SetDebugLevel(DebugLevel::Silent); + } + + const char* memoryStr = ::getenv("LLGL_DEBUG_MEMORY"); + if (memoryStr && ::atoi(memoryStr) == 1) + { + EnableDebugMemoryTools(); + } + + const char* threadModeStr = ::getenv("LLGL_DEBUG_THREAD_MODE"); + if (threadModeStr) + { + if (::strcasecmp(threadModeStr, "unsafe") == 0) + SetDebugThreadMode(DebugThreadMode::Unsafe); + else if (::strcasecmp(threadModeStr, "safe") == 0) + SetDebugThreadMode(DebugThreadMode::Safe); + else if (::strcasecmp(threadModeStr, "identify") == 0) + SetDebugThreadMode(DebugThreadMode::Identify); + } + + const char* formatStr = ::getenv("LLGL_DEBUG_FORMAT"); + if (formatStr) + { + int formatFlags = DebugFormatFlags::Default; + if (::strstr(formatStr, "timestamp")) + formatFlags |= DebugFormatFlags::IncludeTimestamp; + if (::strstr(formatStr, "function")) + formatFlags |= DebugFormatFlags::IncludeFunction; + if (::strstr(formatStr, "file")) + formatFlags |= DebugFormatFlags::IncludeFile; + if (::strstr(formatStr, "line")) + formatFlags |= DebugFormatFlags::IncludeLine; + + SetDebugFormatFlags(static_cast(formatFlags)); + } + + const char* dateFormatStr = ::getenv("LLGL_DEBUG_DATE_FORMAT"); + if (dateFormatStr) + { + SetDebugDateFormat(dateFormatStr); + } + #endif +} From dcd3d22f20f1c158ea662565ec8d0c09780764aa Mon Sep 17 00:00:00 2001 From: Kenneth Treadaway <121038701+CyberKenneth@users.noreply.github.com> Date: Mon, 17 Feb 2025 22:56:44 -0600 Subject: [PATCH 2/2] Update MacOSDebug.h Key features of this debug system include: Multiple debug levels (Trace, Verbose, Debug, Notice, Info, Warning, Error, Fatal) Thread-safe logging Configurable output formatting Environment variable-based configuration Emoji-based level indicators Memory debugging tools for MacOS Debugger break functionality for fatal errors --- sources/Platform/MacOS/MacOSDebug.h | 327 +++++++++++++++++++++++++++- 1 file changed, 324 insertions(+), 3 deletions(-) diff --git a/sources/Platform/MacOS/MacOSDebug.h b/sources/Platform/MacOS/MacOSDebug.h index 6c4faeda18..bf930c48c8 100644 --- a/sources/Platform/MacOS/MacOSDebug.h +++ b/sources/Platform/MacOS/MacOSDebug.h @@ -8,18 +8,339 @@ #ifndef LLGL_MACOS_DEBUG_H #define LLGL_MACOS_DEBUG_H - #include +// Original debug break functionality #ifdef SIGTRAP # define LLGL_DEBUG_BREAK() raise(SIGTRAP) #else # define LLGL_DEBUG_BREAK() #endif - +// Convenience macros for debug functions with automatic function context +#ifdef LLGL_DEBUG +# define LLGL_DEBUG_TRACE(msg) LLGL::DebugTrace(msg, __FUNCTION__, __FILE__, __LINE__) +# define LLGL_DEBUG_VERBOSE(msg) LLGL::DebugVerbose(msg, __FUNCTION__, __FILE__, __LINE__) +# define LLGL_DEBUG_OUTPUT(msg) LLGL::DebugOutput(msg, __FUNCTION__, __FILE__, __LINE__) +# define LLGL_DEBUG_NOTICE(msg) LLGL::DebugNotice(msg, __FUNCTION__, __FILE__, __LINE__) +# define LLGL_DEBUG_INFO(msg) LLGL::DebugInfo(msg, __FUNCTION__, __FILE__, __LINE__) +# define LLGL_DEBUG_WARNING(msg) LLGL::DebugWarning(msg, __FUNCTION__, __FILE__, __LINE__) +# define LLGL_DEBUG_ERROR(msg) LLGL::DebugError(msg, __FUNCTION__, __FILE__, __LINE__) +# define LLGL_DEBUG_FATAL(msg) LLGL::DebugFatal(msg, __FUNCTION__, __FILE__, __LINE__) +#else +# define LLGL_DEBUG_TRACE(msg) +# define LLGL_DEBUG_VERBOSE(msg) +# define LLGL_DEBUG_OUTPUT(msg) +# define LLGL_DEBUG_NOTICE(msg) +# define LLGL_DEBUG_INFO(msg) +# define LLGL_DEBUG_WARNING(msg) +# define LLGL_DEBUG_ERROR(msg) +# define LLGL_DEBUG_FATAL(msg) #endif +namespace LLGL +{ + +/** + * \brief Debug message severity levels. + */ +enum class DebugLevel +{ + Trace, //!< Most detailed level for function entry/exit tracing + Verbose, //!< Detailed information for granular debugging + Debug, //!< Standard debug information + Notice, //!< Noteworthy events that aren't warnings + Info, //!< Information message for general debugging + Warning, //!< Warning message for potential issues + Error, //!< Error message for recoverable problems + Fatal, //!< Fatal error that should terminate the application + Silent //!< No output (for completely disabling logging) +}; + +/** + * \brief Thread safety mode for debugging operations. + */ +enum class DebugThreadMode +{ + Unsafe, //!< No thread safety (fastest) + Safe, //!< Thread-safe debug operations + Identify //!< Thread-safe and includes thread ID in messages +}; + +/** + * \brief Format flags for debug message output. + */ +enum DebugFormatFlags +{ + Default = 0, + IncludeTimestamp = (1 << 0), //!< Include timestamp in messages + IncludeFunction = (1 << 1), //!< Include function name in messages + IncludeFile = (1 << 2), //!< Include source file in messages + IncludeLine = (1 << 3), //!< Include line number in messages + Full = IncludeTimestamp | IncludeFunction | IncludeFile | IncludeLine +}; + +/** + * \brief Outputs the specified text message to the debug console. + * \param[in] text Specifies the message to output. + * + * \remarks This is the original debug output function for backward compatibility. + * + * \note The newer debug functions (DebugInfo, DebugWarning, etc.) provide more features. + */ +LLGL_EXPORT void DebugPuts(const char* text); + +/** + * \brief Outputs a trace message to the debug console. + * \param[in] text Specifies the trace message to output. + * \param[in] function Optional function name for context. + * \param[in] file Optional source file name. + * \param[in] line Optional line number. + * + * \remarks Trace level is the most detailed level, intended for function entry/exit tracing. + * + * \code + * // Example usage: + * LLGL_DEBUG_TRACE("Entering render loop"); + * // Or direct function call: + * LLGL::DebugTrace("Entering render loop", "RenderSystem::Render"); + * \endcode + */ +LLGL_EXPORT void DebugTrace(const char* text, const char* function = nullptr, const char* file = nullptr, int line = 0); + +/** + * \brief Outputs a verbose message to the debug console. + * \param[in] text Specifies the verbose message to output. + * \param[in] function Optional function name for context. + * \param[in] file Optional source file name. + * \param[in] line Optional line number. + * + * \remarks Verbose messages are for detailed debugging information. + * + * \code + * // Example usage: + * LLGL_DEBUG_VERBOSE("Processing vertex buffer with 1024 vertices"); + * \endcode + */ +LLGL_EXPORT void DebugVerbose(const char* text, const char* function = nullptr, const char* file = nullptr, int line = 0); + +/** + * \brief Outputs a debug message to the debug console. + * \param[in] text Specifies the debug message to output. + * \param[in] function Optional function name for context. + * \param[in] file Optional source file name. + * \param[in] line Optional line number. + * + * \code + * // Example usage: + * LLGL_DEBUG_OUTPUT("Created texture with format RGB8"); + * \endcode + */ +LLGL_EXPORT void DebugOutput(const char* text, const char* function = nullptr, const char* file = nullptr, int line = 0); + +/** + * \brief Outputs a notice message to the debug console. + * \param[in] text Specifies the notice message to output. + * \param[in] function Optional function name for context. + * \param[in] file Optional source file name. + * \param[in] line Optional line number. + * + * \remarks Notice messages highlight significant events that aren't warnings. + * + * \code + * // Example usage: + * LLGL_DEBUG_NOTICE("Switching to fallback shader"); + * \endcode + */ +LLGL_EXPORT void DebugNotice(const char* text, const char* function = nullptr, const char* file = nullptr, int line = 0); + +/** + * \brief Outputs an info message to the debug console. + * \param[in] text Specifies the info message to output. + * \param[in] function Optional function name for context. + * \param[in] file Optional source file name. + * \param[in] line Optional line number. + * + * \code + * // Example usage: + * LLGL_DEBUG_INFO("Initializing renderer"); + * \endcode + */ +LLGL_EXPORT void DebugInfo(const char* text, const char* function = nullptr, const char* file = nullptr, int line = 0); + +/** + * \brief Outputs a warning message to the debug console. + * \param[in] text Specifies the warning message to output. + * \param[in] function Optional function name for context. + * \param[in] file Optional source file name. + * \param[in] line Optional line number. + * + * \code + * // Example usage: + * LLGL_DEBUG_WARNING("Deprecated shader feature used"); + * \endcode + */ +LLGL_EXPORT void DebugWarning(const char* text, const char* function = nullptr, const char* file = nullptr, int line = 0); + +/** + * \brief Outputs an error message to the debug console. + * \param[in] text Specifies the error message to output. + * \param[in] function Optional function name for context. + * \param[in] file Optional source file name. + * \param[in] line Optional line number. + * + * \code + * // Example usage: + * LLGL_DEBUG_ERROR("Failed to create texture"); + * \endcode + */ +LLGL_EXPORT void DebugError(const char* text, const char* function = nullptr, const char* file = nullptr, int line = 0); + +/** + * \brief Outputs a fatal error message to the debug console and breaks into debugger if attached. + * \param[in] text Specifies the fatal error message to output. + * \param[in] function Optional function name for context. + * \param[in] file Optional source file name. + * \param[in] line Optional line number. + * + * \remarks Fatal errors will trigger a debugger breakpoint if a debugger is attached. + * + * \code + * // Example usage: + * LLGL_DEBUG_FATAL("Critical initialization failure"); + * \endcode + */ +LLGL_EXPORT void DebugFatal(const char* text, const char* function = nullptr, const char* file = nullptr, int line = 0); + +/** + * \brief Enables memory debugging tools in Xcode. + * + * \return True if memory tools were successfully enabled, false if not supported. + * + * \remarks This enables: + * - MallocStackLogging: Records allocation call stacks + * - MallocGuardEdges: Adds guard pages for buffer overflow detection + * - MallocScribble: Helps detect use-after-free bugs + * + * \note Memory tools are disabled by default and only available in debug builds. + * + * \code + * // Example usage: + * if (LLGL::EnableDebugMemoryTools()) { + * std::cout << "Memory debugging enabled" << std::endl; + * } + * \endcode + */ +LLGL_EXPORT bool EnableDebugMemoryTools(); + +/** + * \brief Disables previously enabled memory debugging tools. + * + * \code + * // Example usage: + * LLGL::EnableDebugMemoryTools(); + * // ... debug memory-intensive code ... + * LLGL::DisableDebugMemoryTools(); + * \endcode + */ +LLGL_EXPORT void DisableDebugMemoryTools(); + +/** + * \brief Sets minimum debug level for output messages. + * \param[in] level Minimum level to output. + * + * \remarks Messages with severity below this level will be suppressed. + * + * \code + * // Example usage: + * // Only show warnings and more severe messages + * LLGL::SetDebugLevel(LLGL::DebugLevel::Warning); + * \endcode + */ +LLGL_EXPORT void SetDebugLevel(DebugLevel level); + +/** + * \brief Sets thread safety mode for debug operations. + * \param[in] mode Thread safety mode to use. + * + * \remarks Different thread modes offer trade-offs between performance and thread safety: + * - Unsafe: No thread safety, fastest but may cause interleaved output + * - Safe: Thread-safe debug operations + * - Identify: Thread-safe and includes thread ID in messages + * + * \code + * // Example usage for multi-threaded application: + * LLGL::SetDebugThreadMode(LLGL::DebugThreadMode::Identify); + * \endcode + */ +LLGL_EXPORT void SetDebugThreadMode(DebugThreadMode mode); + +/** + * \brief Sets format flags for debug message output. + * \param[in] flags Format flags to use. + * + * \see DebugFormatFlags + * + * \code + * // Example usage - include timestamps and function names: + * LLGL::SetDebugFormatFlags( + * LLGL::DebugFormatFlags::IncludeTimestamp | + * LLGL::DebugFormatFlags::IncludeFunction + * ); + * + * // Use all available formatting: + * LLGL::SetDebugFormatFlags(LLGL::DebugFormatFlags::Full); + * \endcode + */ +LLGL_EXPORT void SetDebugFormatFlags(DebugFormatFlags flags); + +/** + * \brief Sets date format for the timestamp in debug messages. + * \param[in] format C-style strftime format string. + * + * \remarks Only applies if the IncludeTimestamp flag is set. + * + * \code + * // Example usage - show only time in 24-hour format: + * LLGL::SetDebugDateFormat("%H:%M:%S"); + * + * // Example usage - show date and time: + * LLGL::SetDebugDateFormat("%Y-%m-%d %H:%M:%S"); + * \endcode + */ +LLGL_EXPORT void SetDebugDateFormat(const char* format); + +/** + * \brief Loads debug configuration from environment variables. + * + * \remarks Checks for: + * - LLGL_DEBUG_LEVEL: Minimum debug level (values: trace, verbose, debug, notice, info, warning, error, fatal, silent) + * - LLGL_DEBUG_MEMORY: Enable memory tools (values: 0, 1) + * - LLGL_DEBUG_THREAD_MODE: Thread safety (values: unsafe, safe, identify) + * - LLGL_DEBUG_FORMAT: Format flags (values: timestamp, function, file, line) + * - LLGL_DEBUG_DATE_FORMAT: Custom date format for timestamps + * + * \code + * // Example usage at application startup: + * int main(int argc, char* argv[]) { + * LLGL::LoadDebugConfigFromEnvironment(); + * // ... rest of application ... + * } + * \endcode + * + * \note This allows configuration without recompiling by setting environment variables: + * + * \code + * # Example shell configuration: + * export LLGL_DEBUG_LEVEL=warning + * export LLGL_DEBUG_MEMORY=1 + * export LLGL_DEBUG_THREAD_MODE=identify + * export LLGL_DEBUG_FORMAT="timestamp function" + * export LLGL_DEBUG_DATE_FORMAT="%H:%M:%S" + * \endcode + */ +LLGL_EXPORT void LoadDebugConfigFromEnvironment(); +} // namespace LLGL -// ================================================================================ +#endif // LLGL_MACOS_DEBUG_H