logging
All checks were successful
CI / React UI Build (push) Successful in 38s
CI / Native Windows Build And Tests (push) Successful in 3m12s
CI / Windows Release Package (push) Successful in 3m7s

This commit is contained in:
Aiden
2026-05-12 11:58:29 +10:00
parent 511b67c9bc
commit 44b198b14d
10 changed files with 555 additions and 12 deletions

View File

@@ -308,6 +308,8 @@ set(RENDER_CADENCE_APP_SOURCES
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.cpp" "${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.cpp"
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.h" "${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.h"
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameTypes.h" "${RENDER_CADENCE_APP_DIR}/frames/SystemFrameTypes.h"
"${RENDER_CADENCE_APP_DIR}/logging/Logger.cpp"
"${RENDER_CADENCE_APP_DIR}/logging/Logger.h"
"${RENDER_CADENCE_APP_DIR}/platform/HiddenGlWindow.cpp" "${RENDER_CADENCE_APP_DIR}/platform/HiddenGlWindow.cpp"
"${RENDER_CADENCE_APP_DIR}/platform/HiddenGlWindow.h" "${RENDER_CADENCE_APP_DIR}/platform/HiddenGlWindow.h"
"${RENDER_CADENCE_APP_DIR}/render/Bgra8ReadbackPipeline.cpp" "${RENDER_CADENCE_APP_DIR}/render/Bgra8ReadbackPipeline.cpp"
@@ -349,6 +351,7 @@ target_include_directories(RenderCadenceCompositor PRIVATE
"${RENDER_CADENCE_APP_DIR}" "${RENDER_CADENCE_APP_DIR}"
"${RENDER_CADENCE_APP_DIR}/app" "${RENDER_CADENCE_APP_DIR}/app"
"${RENDER_CADENCE_APP_DIR}/frames" "${RENDER_CADENCE_APP_DIR}/frames"
"${RENDER_CADENCE_APP_DIR}/logging"
"${RENDER_CADENCE_APP_DIR}/platform" "${RENDER_CADENCE_APP_DIR}/platform"
"${RENDER_CADENCE_APP_DIR}/render" "${RENDER_CADENCE_APP_DIR}/render"
"${RENDER_CADENCE_APP_DIR}/runtime" "${RENDER_CADENCE_APP_DIR}/runtime"
@@ -803,6 +806,21 @@ endif()
add_test(NAME RenderCadenceCompositorRuntimeShaderParamsTests COMMAND RenderCadenceCompositorRuntimeShaderParamsTests) add_test(NAME RenderCadenceCompositorRuntimeShaderParamsTests COMMAND RenderCadenceCompositorRuntimeShaderParamsTests)
add_executable(RenderCadenceCompositorLoggerTests
"${RENDER_CADENCE_APP_DIR}/logging/Logger.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorLoggerTests.cpp"
)
target_include_directories(RenderCadenceCompositorLoggerTests PRIVATE
"${RENDER_CADENCE_APP_DIR}/logging"
)
if(MSVC)
target_compile_options(RenderCadenceCompositorLoggerTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorLoggerTests COMMAND RenderCadenceCompositorLoggerTests)
add_executable(SystemOutputFramePoolTests add_executable(SystemOutputFramePoolTests
"${APP_DIR}/videoio/SystemOutputFramePool.cpp" "${APP_DIR}/videoio/SystemOutputFramePool.cpp"
"${APP_DIR}/videoio/VideoIOFormat.cpp" "${APP_DIR}/videoio/VideoIOFormat.cpp"

View File

@@ -44,6 +44,7 @@ Included now:
- render-thread-only GL commit once the artifact is ready - render-thread-only GL commit once the artifact is ready
- manifest-driven stateless single-pass shader packages - manifest-driven stateless single-pass shader packages
- default float, vec2, color, boolean, enum, and trigger parameters - default float, vec2, color, boolean, enum, and trigger parameters
- background logging with `log`, `warning`, and `error` levels
- compact telemetry - compact telemetry
- non-GL frame-exchange tests - non-GL frame-exchange tests
@@ -100,6 +101,10 @@ Use `--no-shader` to keep the simple motion fallback only.
## Expected Telemetry ## Expected Telemetry
Startup, shutdown, shader-build, and render-thread event messages are written through the app logger. Telemetry is intentionally separate and remains a compact once-per-second cadence line.
The logger writes to the console, `OutputDebugStringA`, and `logs/render-cadence-compositor.log` by default. Render-thread log calls use the non-blocking path so diagnostics do not become cadence blockers.
The app prints one line per second: The app prints one line per second:
```text ```text

View File

@@ -1,12 +1,14 @@
#include "app/AppConfig.h" #include "app/AppConfig.h"
#include "app/RenderCadenceApp.h" #include "app/RenderCadenceApp.h"
#include "frames/SystemFrameExchange.h" #include "frames/SystemFrameExchange.h"
#include "logging/Logger.h"
#include "render/RenderThread.h" #include "render/RenderThread.h"
#include "VideoIOFormat.h" #include "VideoIOFormat.h"
#include <windows.h> #include <windows.h>
#include <iostream> #include <iostream>
#include <sstream>
#include <string> #include <string>
namespace namespace
@@ -38,17 +40,22 @@ private:
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
RenderCadenceCompositor::AppConfig appConfig = RenderCadenceCompositor::DefaultAppConfig();
RenderCadenceCompositor::Logger::Instance().Start(appConfig.logging);
RenderCadenceCompositor::Log(
"app",
"RenderCadenceCompositor starting. Starts render cadence, system-memory exchange, DeckLink scheduled output, and telemetry. Press Enter to stop.");
ComInitGuard com; ComInitGuard com;
if (!com.Initialize()) if (!com.Initialize())
{ {
std::cerr << "COM initialization failed: 0x" << std::hex << com.Result() << std::dec << "\n"; std::ostringstream message;
message << "COM initialization failed: 0x" << std::hex << com.Result();
RenderCadenceCompositor::LogError("app", message.str());
RenderCadenceCompositor::Logger::Instance().Stop();
return 1; return 1;
} }
std::cout << "RenderCadenceCompositor\n"
<< " Starts render cadence, system-memory exchange, DeckLink scheduled output, and telemetry.\n"
<< " Press Enter to stop.\n";
SystemFrameExchangeConfig frameExchangeConfig; SystemFrameExchangeConfig frameExchangeConfig;
frameExchangeConfig.width = 1920; frameExchangeConfig.width = 1920;
frameExchangeConfig.height = 1080; frameExchangeConfig.height = 1080;
@@ -66,7 +73,6 @@ int main(int argc, char** argv)
RenderThread renderThread(frameExchange, renderConfig); RenderThread renderThread(frameExchange, renderConfig);
RenderCadenceCompositor::AppConfig appConfig = RenderCadenceCompositor::DefaultAppConfig();
for (int index = 1; index < argc; ++index) for (int index = 1; index < argc; ++index)
{ {
const std::string argument = argv[index]; const std::string argument = argv[index];
@@ -87,12 +93,15 @@ int main(int argc, char** argv)
std::string error; std::string error;
if (!app.Start(error)) if (!app.Start(error))
{ {
std::cerr << "RenderCadenceCompositor start failed: " << error << "\n"; RenderCadenceCompositor::LogError("app", "RenderCadenceCompositor start failed: " + error);
RenderCadenceCompositor::Logger::Instance().Stop();
return 1; return 1;
} }
std::string line; std::string line;
std::getline(std::cin, line); std::getline(std::cin, line);
app.Stop(); app.Stop();
RenderCadenceCompositor::Log("app", "RenderCadenceCompositor stopped.");
RenderCadenceCompositor::Logger::Instance().Stop();
return 0; return 0;
} }

View File

@@ -9,6 +9,12 @@ AppConfig DefaultAppConfig()
config.deckLink.outputAlphaRequired = false; config.deckLink.outputAlphaRequired = false;
config.outputThread.targetBufferedFrames = 4; config.outputThread.targetBufferedFrames = 4;
config.telemetry.interval = std::chrono::seconds(1); config.telemetry.interval = std::chrono::seconds(1);
config.logging.minimumLevel = LogLevel::Log;
config.logging.writeToConsole = true;
config.logging.writeToDebugOutput = true;
config.logging.writeToFile = true;
config.logging.filePath = "logs/render-cadence-compositor.log";
config.logging.maxQueuedMessages = 1024;
config.warmupCompletedFrames = 4; config.warmupCompletedFrames = 4;
config.warmupTimeout = std::chrono::seconds(3); config.warmupTimeout = std::chrono::seconds(3);
config.prerollTimeout = std::chrono::seconds(3); config.prerollTimeout = std::chrono::seconds(3);

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "../logging/Logger.h"
#include "../telemetry/TelemetryPrinter.h" #include "../telemetry/TelemetryPrinter.h"
#include "../video/DeckLinkOutput.h" #include "../video/DeckLinkOutput.h"
#include "../video/DeckLinkOutputThread.h" #include "../video/DeckLinkOutputThread.h"
@@ -15,6 +16,7 @@ struct AppConfig
DeckLinkOutputConfig deckLink; DeckLinkOutputConfig deckLink;
DeckLinkOutputThreadConfig outputThread; DeckLinkOutputThreadConfig outputThread;
TelemetryPrinterConfig telemetry; TelemetryPrinterConfig telemetry;
LoggerConfig logging;
std::size_t warmupCompletedFrames = 4; std::size_t warmupCompletedFrames = 4;
std::chrono::milliseconds warmupTimeout = std::chrono::seconds(3); std::chrono::milliseconds warmupTimeout = std::chrono::seconds(3);
std::chrono::milliseconds prerollTimeout = std::chrono::seconds(3); std::chrono::milliseconds prerollTimeout = std::chrono::seconds(3);

View File

@@ -1,13 +1,13 @@
#pragma once #pragma once
#include "AppConfig.h" #include "AppConfig.h"
#include "../logging/Logger.h"
#include "../runtime/RuntimeShaderBridge.h" #include "../runtime/RuntimeShaderBridge.h"
#include "../telemetry/TelemetryPrinter.h" #include "../telemetry/TelemetryPrinter.h"
#include "../video/DeckLinkOutput.h" #include "../video/DeckLinkOutput.h"
#include "../video/DeckLinkOutputThread.h" #include "../video/DeckLinkOutputThread.h"
#include <chrono> #include <chrono>
#include <iostream>
#include <string> #include <string>
#include <thread> #include <thread>
#include <type_traits> #include <type_traits>
@@ -65,6 +65,7 @@ public:
bool Start(std::string& error) bool Start(std::string& error)
{ {
Log("app", "Initializing DeckLink output.");
if (!mOutput.Initialize( if (!mOutput.Initialize(
mConfig.deckLink, mConfig.deckLink,
[this](const VideoIOCompletion& completion) { [this](const VideoIOCompletion& completion) {
@@ -72,44 +73,56 @@ public:
}, },
error)) error))
{ {
LogError("app", "DeckLink output initialization failed: " + error);
return false; return false;
} }
Log("app", "Starting render thread.");
if (!detail::StartRenderThread(mRenderThread, error, 0)) if (!detail::StartRenderThread(mRenderThread, error, 0))
{ {
LogError("app", "Render thread start failed: " + error);
Stop(); Stop();
return false; return false;
} }
StartRuntimeShaderBuild(); StartRuntimeShaderBuild();
Log("app", "Waiting for rendered warmup frames.");
if (!mFrameExchange.WaitForCompletedDepth(mConfig.warmupCompletedFrames, mConfig.warmupTimeout)) if (!mFrameExchange.WaitForCompletedDepth(mConfig.warmupCompletedFrames, mConfig.warmupTimeout))
{ {
error = "Timed out waiting for rendered warmup frames."; error = "Timed out waiting for rendered warmup frames.";
LogError("app", error);
Stop(); Stop();
return false; return false;
} }
Log("app", "Starting DeckLink output thread.");
if (!mOutputThread.Start()) if (!mOutputThread.Start())
{ {
error = "DeckLink output thread failed to start."; error = "DeckLink output thread failed to start.";
LogError("app", error);
Stop(); Stop();
return false; return false;
} }
Log("app", "Waiting for DeckLink preroll frames.");
if (!WaitForPreroll()) if (!WaitForPreroll())
{ {
error = "Timed out waiting for DeckLink preroll frames."; error = "Timed out waiting for DeckLink preroll frames.";
LogError("app", error);
Stop(); Stop();
return false; return false;
} }
Log("app", "Starting DeckLink scheduled playback.");
if (!mOutput.StartScheduledPlayback(error)) if (!mOutput.StartScheduledPlayback(error))
{ {
LogError("app", "DeckLink scheduled playback failed: " + error);
Stop(); Stop();
return false; return false;
} }
mTelemetry.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread); mTelemetry.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread);
Log("app", "RenderCadenceCompositor started.");
mStarted = true; mStarted = true;
return true; return true;
} }
@@ -122,6 +135,8 @@ public:
StopRuntimeShaderBuild(); StopRuntimeShaderBuild();
mRenderThread.Stop(); mRenderThread.Stop();
mOutput.ReleaseResources(); mOutput.ReleaseResources();
if (mStarted)
Log("app", "RenderCadenceCompositor shutdown complete.");
mStarted = false; mStarted = false;
} }
@@ -143,13 +158,20 @@ private:
void StartRuntimeShaderBuild() void StartRuntimeShaderBuild()
{ {
if (mConfig.runtimeShaderId.empty())
{
Log("runtime-shader", "Runtime shader build disabled.");
return;
}
Log("runtime-shader", "Starting background Slang build for shader '" + mConfig.runtimeShaderId + "'.");
mShaderBridge.Start( mShaderBridge.Start(
mConfig.runtimeShaderId, mConfig.runtimeShaderId,
[this](const RuntimeShaderArtifact& artifact) { [this](const RuntimeShaderArtifact& artifact) {
mRenderThread.SubmitRuntimeShaderArtifact(artifact); mRenderThread.SubmitRuntimeShaderArtifact(artifact);
}, },
[](const std::string& message) { [](const std::string& message) {
std::cout << "Runtime Slang build failed: " << message << "\n"; LogError("runtime-shader", "Runtime Slang build failed: " + message);
}); });
} }

View File

@@ -0,0 +1,283 @@
#include "Logger.h"
#include <windows.h>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <ctime>
#include <filesystem>
namespace RenderCadenceCompositor
{
namespace
{
int Rank(LogLevel level)
{
switch (level)
{
case LogLevel::Log:
return 0;
case LogLevel::Warning:
return 1;
case LogLevel::Error:
return 2;
default:
return 0;
}
}
std::string FormatTimestamp(std::chrono::system_clock::time_point timestamp)
{
const std::time_t time = std::chrono::system_clock::to_time_t(timestamp);
std::tm localTime = {};
localtime_s(&localTime, &time);
std::ostringstream stream;
stream << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S");
return stream.str();
}
std::string FormatRecord(const Logger::Record& record)
{
std::ostringstream stream;
stream << FormatTimestamp(record.timestamp)
<< " [" << LogLevelName(record.level) << "]"
<< " [" << record.subsystem << "] "
<< record.message;
return stream.str();
}
}
const char* LogLevelName(LogLevel level)
{
switch (level)
{
case LogLevel::Log:
return "log";
case LogLevel::Warning:
return "warning";
case LogLevel::Error:
return "error";
default:
return "log";
}
}
Logger& Logger::Instance()
{
static Logger logger;
return logger;
}
Logger::~Logger()
{
Stop();
}
void Logger::Start(LoggerConfig config)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mRunning)
return;
mConfig = config;
mStopping = false;
mRunning = true;
OpenFileSink();
{
std::lock_guard<std::mutex> countersLock(mCountersMutex);
mCounters = LoggerCounters();
}
mThread = std::thread([this]() { ThreadMain(); });
}
void Logger::Stop()
{
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mRunning && !mThread.joinable())
return;
mStopping = true;
}
mCondition.notify_all();
if (mThread.joinable())
mThread.join();
std::lock_guard<std::mutex> lock(mMutex);
CloseFileSink();
mRunning = false;
}
void Logger::Write(LogLevel level, const std::string& subsystem, const std::string& message)
{
Enqueue(Record{ std::chrono::system_clock::now(), std::this_thread::get_id(), level, subsystem, message }, true);
}
bool Logger::TryWrite(LogLevel level, const std::string& subsystem, const std::string& message)
{
return Enqueue(Record{ std::chrono::system_clock::now(), std::this_thread::get_id(), level, subsystem, message }, false);
}
void Logger::Log(const std::string& subsystem, const std::string& message)
{
Write(LogLevel::Log, subsystem, message);
}
void Logger::Warning(const std::string& subsystem, const std::string& message)
{
Write(LogLevel::Warning, subsystem, message);
}
void Logger::Error(const std::string& subsystem, const std::string& message)
{
Write(LogLevel::Error, subsystem, message);
}
LoggerCounters Logger::Counters() const
{
std::lock_guard<std::mutex> lock(mCountersMutex);
return mCounters;
}
bool Logger::IsRunning() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mRunning;
}
bool Logger::ShouldWrite(LogLevel level) const
{
return Rank(level) >= Rank(mConfig.minimumLevel);
}
bool Logger::Enqueue(Record record, bool block)
{
if (!ShouldWrite(record.level))
return true;
std::unique_lock<std::mutex> lock(mMutex, std::defer_lock);
if (block)
{
lock.lock();
}
else if (!lock.try_lock())
{
std::lock_guard<std::mutex> countersLock(mCountersMutex);
++mCounters.dropped;
return false;
}
if (!mRunning)
{
lock.unlock();
WriteRecord(record);
return true;
}
if (mQueue.size() >= mConfig.maxQueuedMessages)
{
std::lock_guard<std::mutex> countersLock(mCountersMutex);
++mCounters.dropped;
return false;
}
mQueue.push_back(std::move(record));
{
std::lock_guard<std::mutex> countersLock(mCountersMutex);
++mCounters.queued;
}
lock.unlock();
mCondition.notify_one();
return true;
}
void Logger::ThreadMain()
{
for (;;)
{
Record record;
{
std::unique_lock<std::mutex> lock(mMutex);
mCondition.wait(lock, [this]() {
return mStopping || !mQueue.empty();
});
if (mQueue.empty())
{
if (mStopping)
return;
continue;
}
record = std::move(mQueue.front());
mQueue.pop_front();
}
WriteRecord(record);
}
}
void Logger::WriteRecord(const Record& record)
{
const std::string line = FormatRecord(record);
if (mConfig.writeToDebugOutput)
OutputDebugStringA((line + "\n").c_str());
if (mConfig.writeToConsole)
{
if (record.level == LogLevel::Error)
std::cerr << line << "\n";
else
std::cout << line << "\n";
}
if (mFile.is_open())
{
mFile << line << "\n";
mFile.flush();
}
std::lock_guard<std::mutex> lock(mCountersMutex);
++mCounters.written;
}
void Logger::OpenFileSink()
{
CloseFileSink();
if (!mConfig.writeToFile || mConfig.filePath.empty())
return;
std::error_code error;
const std::filesystem::path logPath(mConfig.filePath);
if (logPath.has_parent_path())
std::filesystem::create_directories(logPath.parent_path(), error);
mFile.open(logPath, std::ios::out | std::ios::app);
}
void Logger::CloseFileSink()
{
if (mFile.is_open())
mFile.close();
}
bool TryLog(LogLevel level, const std::string& subsystem, const std::string& message)
{
return Logger::Instance().TryWrite(level, subsystem, message);
}
void Log(const std::string& subsystem, const std::string& message)
{
Logger::Instance().Log(subsystem, message);
}
void LogWarning(const std::string& subsystem, const std::string& message)
{
Logger::Instance().Warning(subsystem, message);
}
void LogError(const std::string& subsystem, const std::string& message)
{
Logger::Instance().Error(subsystem, message);
}
}

View File

@@ -0,0 +1,101 @@
#pragma once
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <fstream>
#include <mutex>
#include <string>
#include <thread>
namespace RenderCadenceCompositor
{
enum class LogLevel
{
Log,
Warning,
Error
};
struct LoggerConfig
{
LogLevel minimumLevel = LogLevel::Log;
bool writeToConsole = true;
bool writeToDebugOutput = true;
bool writeToFile = true;
std::string filePath = "logs/render-cadence-compositor.log";
std::size_t maxQueuedMessages = 1024;
};
struct LoggerCounters
{
uint64_t queued = 0;
uint64_t written = 0;
uint64_t dropped = 0;
};
const char* LogLevelName(LogLevel level);
class Logger
{
public:
struct Record
{
std::chrono::system_clock::time_point timestamp;
std::thread::id threadId;
LogLevel level = LogLevel::Log;
std::string subsystem;
std::string message;
};
static Logger& Instance();
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
~Logger();
void Start(LoggerConfig config = LoggerConfig());
void Stop();
void Write(LogLevel level, const std::string& subsystem, const std::string& message);
bool TryWrite(LogLevel level, const std::string& subsystem, const std::string& message);
void Log(const std::string& subsystem, const std::string& message);
void Warning(const std::string& subsystem, const std::string& message);
void Error(const std::string& subsystem, const std::string& message);
LoggerCounters Counters() const;
bool IsRunning() const;
private:
Logger() = default;
bool ShouldWrite(LogLevel level) const;
bool Enqueue(Record record, bool block);
void ThreadMain();
void WriteRecord(const Record& record);
void OpenFileSink();
void CloseFileSink();
LoggerConfig mConfig;
std::ofstream mFile;
std::thread mThread;
mutable std::mutex mMutex;
std::condition_variable mCondition;
std::deque<Record> mQueue;
bool mStopping = false;
bool mRunning = false;
mutable std::mutex mCountersMutex;
LoggerCounters mCounters;
};
bool TryLog(LogLevel level, const std::string& subsystem, const std::string& message);
void Log(const std::string& subsystem, const std::string& message);
void LogWarning(const std::string& subsystem, const std::string& message);
void LogError(const std::string& subsystem, const std::string& message);
}

View File

@@ -2,6 +2,7 @@
#include "../frames/SystemFrameExchange.h" #include "../frames/SystemFrameExchange.h"
#include "../frames/SystemFrameTypes.h" #include "../frames/SystemFrameTypes.h"
#include "../logging/Logger.h"
#include "../platform/HiddenGlWindow.h" #include "../platform/HiddenGlWindow.h"
#include "Bgra8ReadbackPipeline.h" #include "Bgra8ReadbackPipeline.h"
#include "GLExtensions.h" #include "GLExtensions.h"
@@ -10,7 +11,6 @@
#include <algorithm> #include <algorithm>
#include <thread> #include <thread>
#include <windows.h>
RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) : RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) :
mFrameExchange(frameExchange), mFrameExchange(frameExchange),
@@ -71,6 +71,7 @@ RenderThread::Metrics RenderThread::GetMetrics() const
void RenderThread::ThreadMain() void RenderThread::ThreadMain()
{ {
RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Log, "render-thread", "Render thread starting.");
HiddenGlWindow window; HiddenGlWindow window;
std::string error; std::string error;
if (!window.Create(mConfig.width, mConfig.height, error) || !window.MakeCurrent()) if (!window.Create(mConfig.width, mConfig.height, error) || !window.MakeCurrent())
@@ -156,6 +157,7 @@ void RenderThread::ThreadMain()
renderer.ShutdownGl(); renderer.ShutdownGl();
window.ClearCurrent(); window.ClearCurrent();
mRunning.store(false, std::memory_order_release); mRunning.store(false, std::memory_order_release);
RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Log, "render-thread", "Render thread stopped.");
} }
void RenderThread::SignalStarted() void RenderThread::SignalStarted()
@@ -167,6 +169,7 @@ void RenderThread::SignalStarted()
void RenderThread::SignalStartupFailure(const std::string& error) void RenderThread::SignalStartupFailure(const std::string& error)
{ {
RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Error, "render-thread", error);
std::lock_guard<std::mutex> lock(mStartupMutex); std::lock_guard<std::mutex> lock(mStartupMutex);
mStartupError = error; mStartupError = error;
mStartupCondition.notify_all(); mStartupCondition.notify_all();
@@ -221,13 +224,19 @@ void RenderThread::TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeSha
std::string commitError; std::string commitError;
if (!runtimeShaderRenderer.CommitShaderArtifact(artifact, commitError)) if (!runtimeShaderRenderer.CommitShaderArtifact(artifact, commitError))
{ {
OutputDebugStringA(("Runtime shader GL commit failed: " + commitError + "\n").c_str()); RenderCadenceCompositor::TryLog(
RenderCadenceCompositor::LogLevel::Error,
"render-thread",
"Runtime shader GL commit failed: " + commitError);
std::lock_guard<std::mutex> lock(mMetricsMutex); std::lock_guard<std::mutex> lock(mMetricsMutex);
++mMetrics.shaderBuildFailures; ++mMetrics.shaderBuildFailures;
return; return;
} }
OutputDebugStringA(("Runtime shader committed: " + artifact.shaderId + ". " + artifact.message + "\n").c_str()); RenderCadenceCompositor::TryLog(
RenderCadenceCompositor::LogLevel::Log,
"render-thread",
"Runtime shader committed: " + artifact.shaderId + ". " + artifact.message);
std::lock_guard<std::mutex> lock(mMetricsMutex); std::lock_guard<std::mutex> lock(mMetricsMutex);
++mMetrics.shaderBuildsCommitted; ++mMetrics.shaderBuildsCommitted;
} }

View File

@@ -0,0 +1,88 @@
#include "Logger.h"
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
namespace
{
int gFailures = 0;
void Expect(bool condition, const std::string& message)
{
if (condition)
return;
++gFailures;
std::cerr << "FAILED: " << message << "\n";
}
void TestLevelNames()
{
using namespace RenderCadenceCompositor;
Expect(std::string(LogLevelName(LogLevel::Log)) == "log", "log level name");
Expect(std::string(LogLevelName(LogLevel::Warning)) == "warning", "warning level name");
Expect(std::string(LogLevelName(LogLevel::Error)) == "error", "error level name");
}
void TestLevelFiltering()
{
using namespace RenderCadenceCompositor;
LoggerConfig config;
config.minimumLevel = LogLevel::Warning;
config.writeToConsole = false;
config.writeToDebugOutput = false;
config.writeToFile = false;
config.maxQueuedMessages = 16;
Logger& logger = Logger::Instance();
logger.Start(config);
logger.Write(LogLevel::Log, "test", "filtered");
logger.Write(LogLevel::Warning, "test", "kept");
logger.Write(LogLevel::Error, "test", "kept");
logger.Stop();
const LoggerCounters counters = logger.Counters();
Expect(counters.queued == 2, "logger queues only messages at or above minimum level");
Expect(counters.written == 2, "logger writes queued messages before stop returns");
Expect(counters.dropped == 0, "logger does not drop under queue capacity");
}
void TestTryWriteDropsWhenQueueIsFull()
{
using namespace RenderCadenceCompositor;
LoggerConfig config;
config.minimumLevel = LogLevel::Log;
config.writeToConsole = false;
config.writeToDebugOutput = false;
config.writeToFile = false;
config.maxQueuedMessages = 0;
Logger& logger = Logger::Instance();
logger.Start(config);
const bool sawDrop = !logger.TryWrite(LogLevel::Log, "test", "message");
logger.Stop();
Expect(sawDrop || logger.Counters().dropped > 0, "try-write reports or counts queue pressure");
}
}
int main()
{
TestLevelNames();
TestLevelFiltering();
TestTryWriteDropsWhenQueueIsFull();
if (gFailures != 0)
{
std::cerr << gFailures << " RenderCadenceCompositorLogger test failure(s).\n";
return 1;
}
std::cout << "RenderCadenceCompositorLogger tests passed.\n";
return 0;
}