bluenoise-raytracer/raytracer/nvpro_core/nvh/nvprint.cpp
2024-05-25 11:53:25 +02:00

334 lines
8.6 KiB
C++

/*
* Copyright (c) 2014-2023, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "nvprint.hpp"
#include <limits.h>
#include <mutex>
#include <vector>
#ifdef _WIN32
#include <io.h>
#include <windows.h>
#else
#include <signal.h>
#include <unistd.h>
#endif
enum class TriState
{
eUnknown,
eFalse,
eTrue
};
static std::string s_logFileName = "log_nvprosample.txt";
static std::vector<char> s_strBuffer; // Persistent allocation for formatted text.
static FILE* s_fd = nullptr;
static bool s_bLogReady = false;
static bool s_bPrintLogging = true;
static uint32_t s_bPrintFileLogging = LOGBITS_ALL;
static uint32_t s_bPrintConsoleLogging = LOGBITS_ALL;
static uint32_t s_bPrintBreakpoints = 0;
static int s_printLevel = -1; // <0 mean no level prefix
static PFN_NVPRINTCALLBACK s_printCallback = nullptr;
static TriState s_consoleSupportsColor = TriState::eUnknown;
// Lock this when modifying any static variables.
// Because it is a recursive mutex, its owner can lock it multiple times.
static std::recursive_mutex s_mutex;
void nvprintSetLogFileName(const char* name) noexcept
{
std::lock_guard<std::recursive_mutex> lockGuard(s_mutex);
if(name == NULL || s_logFileName == name)
return;
try
{
s_logFileName = name;
}
catch(const std::exception& e)
{
nvprintLevel(LOGLEVEL_ERROR, "nvprintfSetLogFileName could not allocate space for new file name. Additional info below:");
nvprintLevel(LOGLEVEL_ERROR, e.what());
}
if(s_fd)
{
fclose(s_fd);
s_fd = nullptr;
s_bLogReady = false;
}
}
void nvprintSetCallback(PFN_NVPRINTCALLBACK callback)
{
s_printCallback = callback;
}
void nvprintSetLevel(int l)
{
s_printLevel = l;
}
int nvprintGetLevel()
{
return s_printLevel;
}
void nvprintSetLogging(bool b)
{
s_bPrintLogging = b;
}
void nvprintSetFileLogging(bool state, uint32_t mask)
{
std::lock_guard<std::recursive_mutex> lockGuard(s_mutex);
if(state)
{
s_bPrintFileLogging |= mask;
}
else
{
s_bPrintFileLogging &= ~mask;
}
}
void nvprintSetConsoleLogging(bool state, uint32_t mask)
{
std::lock_guard<std::recursive_mutex> lockGuard(s_mutex);
if(state)
{
s_bPrintConsoleLogging |= mask;
}
else
{
s_bPrintConsoleLogging &= ~mask;
}
}
void nvprintSetBreakpoints(bool state, uint32_t mask)
{
std::lock_guard<std::recursive_mutex> lockGuard(s_mutex);
if(state)
{
s_bPrintBreakpoints |= mask;
}
else
{
s_bPrintBreakpoints &= ~mask;
}
}
void nvprintfV(va_list& vlist, const char* fmt, int level) noexcept
{
if(s_bPrintLogging == false)
{
return;
}
// Format the inputs into s_strBuffer.
std::lock_guard<std::recursive_mutex> lockGuard(s_mutex);
{
// Copy vlist as it may be modified by vsnprintf.
va_list vlistCopy;
va_copy(vlistCopy, vlist);
const int charactersNeeded = vsnprintf(s_strBuffer.data(), s_strBuffer.size(), fmt, vlistCopy);
va_end(vlistCopy);
// Check that:
// * vsnprintf did not return an error;
// * The string (plus null terminator) could fit in a vector.
if((charactersNeeded < 0) || (size_t(charactersNeeded) > s_strBuffer.max_size() - 1))
{
// Formatting error
nvprintLevel(LOGLEVEL_ERROR, "nvprintfV: Internal message formatting error.");
return;
}
// Increase the size of s_strBuffer as needed if there wasn't enough space.
if(size_t(charactersNeeded) >= s_strBuffer.size())
{
try
{
// Make sure to add 1, because vsnprintf doesn't count the terminating
// null character. This can potentially throw an exception.
s_strBuffer.resize(size_t(charactersNeeded) + 1, '\0');
}
catch(const std::exception& e)
{
nvprintLevel(LOGLEVEL_ERROR, "nvprintfV: Error resizing buffer to hold message. Additional info below:");
nvprintLevel(LOGLEVEL_ERROR, e.what());
return;
}
// Now format it; we know this will succeed.
(void)vsnprintf(s_strBuffer.data(), s_strBuffer.size(), fmt, vlist);
}
}
nvprintLevel(level, s_strBuffer.data());
}
void nvprintLevel(int level, const std::string& msg) noexcept
{
nvprintLevel(level, msg.c_str());
}
void nvprintLevel(int level, const char* msg) noexcept
{
std::lock_guard<std::recursive_mutex> lockGuard(s_mutex);
#ifdef WIN32
// Note: Maybe we could consider changing to a text encoding of UTF-8 in
// the future, bring in calls to Windows' MultiByteToWideChar, and call
// OutputDebugStringW.
OutputDebugStringA(msg);
#endif
if(s_bPrintFileLogging & (1 << level))
{
if(s_bLogReady == false)
{
s_fd = fopen(s_logFileName.c_str(), "wt");
s_bLogReady = true;
}
if(s_fd)
{
fputs(msg, s_fd);
}
}
if(s_printCallback)
{
s_printCallback(level, msg);
}
if(s_bPrintConsoleLogging & (1 << level))
{
// Determine if the output supports ANSI color sequences only once to avoid
// many calls to isatty.
if(TriState::eUnknown == s_consoleSupportsColor)
{
// Determining this perfectly is difficult; terminfo does it by storing
// a large table of all consoles it knows about. For now, we assume
// all consoles support colors, and all pipes do not.
#ifdef WIN32
bool supportsColor = _isatty(_fileno(stderr)) && _isatty(_fileno(stdout));
// This enables ANSI escape codes from the app side.
// We do this because on Windows 10, cmd.exe is a console, but only
// supports ANSI escape codes by default if the
// HKEY_CURRENT_USER\Console\VirtualTerminalLevel registry key is
// nonzero, which we don't want to assume.
// See https://github.com/nvpro-samples/vk_raytrace/issues/28.
// On failure, turn off colors.
if(supportsColor)
{
for(DWORD stdHandleIndex : {STD_OUTPUT_HANDLE, STD_ERROR_HANDLE})
{
const HANDLE consoleHandle = GetStdHandle(stdHandleIndex);
if(INVALID_HANDLE_VALUE == consoleHandle)
{
supportsColor = false;
break;
}
DWORD consoleMode = 0;
if(0 == GetConsoleMode(consoleHandle, &consoleMode))
{
supportsColor = false;
break;
}
SetConsoleMode(consoleHandle, consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
}
#else
const bool supportsColor = isatty(fileno(stderr)) && isatty(fileno(stdout));
#endif
s_consoleSupportsColor = (supportsColor ? TriState::eTrue : TriState::eFalse);
}
FILE* outStream = (((1 << level) & LOGBITS_ERRORS) ? stderr : stdout);
if(TriState::eTrue == s_consoleSupportsColor)
{
// Set the foreground color depending on level:
// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
if(level == LOGLEVEL_OK)
{
fputs("\033[32m", outStream); // Green
}
else if(level == LOGLEVEL_ERROR)
{
fputs("\033[31m", outStream); // Red
}
else if(level == LOGLEVEL_WARNING)
{
fputs("\033[33m", outStream); // Yellow
}
else if(level == LOGLEVEL_DEBUG)
{
fputs("\033[36m", outStream); // Cyan
}
}
fputs(msg, outStream);
if(TriState::eTrue == s_consoleSupportsColor)
{
// Reset all attributes
fputs("\033[0m", outStream);
}
}
if(s_bPrintBreakpoints & (1 << level))
{
#ifdef WIN32
DebugBreak();
#else
raise(SIGTRAP);
#endif
}
}
void nvprintf(
#ifdef _MSC_VER
_Printf_format_string_
#endif
const char* fmt,
...) noexcept
{
// int r = 0;
va_list vlist;
va_start(vlist, fmt);
nvprintfV(vlist, fmt, s_printLevel);
va_end(vlist);
}
void nvprintfLevel(int level,
#ifdef _MSC_VER
_Printf_format_string_
#endif
const char* fmt,
...) noexcept
{
va_list vlist;
va_start(vlist, fmt);
nvprintfV(vlist, fmt, level);
va_end(vlist);
}