Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dead request(please close as rejected): Debug MacOS #162

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
327 changes: 324 additions & 3 deletions sources/Platform/MacOS/MacOSDebug.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,339 @@
#ifndef LLGL_MACOS_DEBUG_H
#define LLGL_MACOS_DEBUG_H


#include <signal.h>

// 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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a large number of debug levels and I think it goes a bit beyond the scope of LLGL. I personally see this level of fine-tuned logging mechanism on the application level but not in a low level library that acts as interface with the graphics APIs. There are currently two enums for error logging: LLGL::Log::ReportType and LLGL::ErrorType. The former one only has two entries, Default (for stdout output stream) and Error (for stderr output stream) to keep it simple. The latter one is solely for the RenderingDebugger.

It can't hurt to add more debugging functionality to LLGL, but I think a more extensive logging output with timestamps, verbosity setting and more should be kept to the application. If you still think this is something you want to see LLGL to provide out of the box, I suggest we move these features to the utility functions. Anything that is not mandatory for LLGL's core functionality is kept in the LLGL/Utils/ folder. You could add most of these features as an extension to the public LLGL::Log namespace, for example with a function that registers a more advanced logging callback, something similar to LLGL::RegisterCallbackStd() which registers a callback that forwards all log output to either stdout or stderr output streams.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, I get the importance of keeping LLGL lightweight for application developers, but as contributors, having fine-grained debugging tools directly within LLGL would be a huge advantage. This may just be the bare metal nerd in me but being able to "pick at each grain of sand on the beach to see the bugs" with advanced logging and debugging features would make diagnosing issues much easier, especially if we use them in tests.

I like the idea of moving advanced logging to LLGL/Utils/ for optional use and keeping the original debug tools they way they are.
This way:

App developers can extend our logging system if they find it useful.
Contributors get powerful built-in debug tools without cluttering the core library.
Tests benefit from deeper logging insights without requiring external setups.
And who knows, some application developers might find our extended tools useful and either extend them further or integrate them into their debugging workflows. Keeping things structured this way lets LLGL stay clean and minimal while offering flexibility and power where needed.

Does this structure sound like a good balance between keeping LLGL lightweight and utility?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned, I'm not opposed to adding more debugging functionality to this project. I just think most of this doesn't have to be MacOS specific. Maybe start with writing your own LLGL::Log callback with more detailed output.

{
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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For flag enumerations, please use the same semantic as in the LLGL interface, i.e. a struct with only one anonymous enum such as CommandBufferFlags for instance. The rationale behind it is to have the same semantic as for scoped enums to reduce cluttering the global scope, i.e. you write the flags as DebugFormatFlags::Default instead of just Default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback on the flag enumerations. As an embedded system and low-level programmer, I was raised by my uncle to primarily work in C and assembly language where the :: scope resolution operator isn't used. I am far newer to C++ than asm or C so it slipped my mind that's why I didn't initially use the CommandBufferFlags::flags syntax - it's just not a pattern I'm used to from my C programming background.
I appreciate you explaining the rationale behind using structs with anonymous enums in the LLGL interface to keep things consistent with the scoped enum semantics in C++. That makes a lot of sense to avoid cluttering the global namespace.
Moving forward, I'll work to keep this convention in mind and use the EnumName::ValueName syntax for flag enumerations in this project to maintain consistency and readable code. Thanks again for pointing this out and helping me better understand the preferred style here. I'm always happy to learn and adapt. Please let me know if you spot any other areas where I can improve my contributions!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally started this project with the intent to keep this strictly C++, that's why I use std::uint32_t everywhere, but I slowly started going back to having plain C structs/types etc. in the code. So there are also some C-style enums in the code (you can find some here in D3D11BindingLocator.h:33 and GLResourceHeap.cpp:67 for instance) but those are not in the public interface and usually connected to low-level code that I like to call "bit-level hacking". Anything in the public interface of LLGL should make use of scoped enums or struct/enum combos for flags.

{
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);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and all the other Debug... functions have the same interface. This adds an unnecessary amount of new functions to the interface, even though it's not in the public interface, it is still exposed to other backends. So this should be boiled down to only one function that takes your new enum as first parameter. You can still keep those functions separate in the .mm file, but the interface should be thinner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the feedback about the interface, but I want to clarify my intent. The different debug functions like DebugTrace(), DebugWarning(), etc., were designed to provide semantic clarity for developers. Each function represents a specific logging context that helps developers quickly understand the nature of the log message.

However, I understand the concern about interface complexity. My goal was to create expressive logging that helps developers quickly identify the context of a log message. The multiple functions allow for more readable code like:

🔧 [D] Loading graphics driver: NVIDIA GeForce RTX 3080
📝 [N] Detected 4K display resolution: 3840x2160
ℹ️ [I] Initializing render context
🔍 [V] Allocating 2GB video memory
🔬 [T] [ShaderCompiler::Compile] Compiling vertex shader
⚠️ [W] Shader optimization level set to default
🔧 [D] [TextureManager::Load] Loading texture: landscape_background.dds
📝 [N] Texture compression detected: BC7
ℹ️ [I] [RenderQueue::Prepare] Preparing render command buffer
🔍 [V] Sorting render passes
❌ [E] Vertex buffer allocation failed
💥 [F] Critical shader compilation error
🔬 [T] [RenderSystem::Render] Attempting fallback rendering mode
⚠️ [W] Performance degradation expected
📝 [N] Reduced shader complexity
ℹ️ [I] Rendering first frame
🔧 [D] Frame submission to GPU
❌ [E] Memory mapping error detected
💥 [F] Unrecoverable rendering state 

log end here 

ps
however now i know there is util area and I also dont have a good way of testing I probably would just delete this all. 

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess using emojis is a matter of preference, there might have been a time when I would have jumped on that idea, but I backed a bit away from adding too many colorful debug outputs, so I kept it with a few color options with text. I still mostly reject this idea due to the associated risks I mentioned.


/**
* \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
Loading
Loading