Shader ownership change
This commit is contained in:
@@ -745,6 +745,35 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME RenderCadenceCompositorFrameExchangeTests COMMAND RenderCadenceCompositorFrameExchangeTests)
|
add_test(NAME RenderCadenceCompositorFrameExchangeTests COMMAND RenderCadenceCompositorFrameExchangeTests)
|
||||||
|
|
||||||
|
add_executable(RenderCadenceCompositorClockTests
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/render/RenderCadenceClock.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorClockTests.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(RenderCadenceCompositorClockTests PRIVATE
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(RenderCadenceCompositorClockTests PRIVATE /W3)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME RenderCadenceCompositorClockTests COMMAND RenderCadenceCompositorClockTests)
|
||||||
|
|
||||||
|
add_executable(RenderCadenceCompositorTelemetryTests
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorTelemetryTests.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(RenderCadenceCompositorTelemetryTests PRIVATE
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/telemetry"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(RenderCadenceCompositorTelemetryTests PRIVATE /W3)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME RenderCadenceCompositorTelemetryTests COMMAND RenderCadenceCompositorTelemetryTests)
|
||||||
|
|
||||||
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"
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ Included now:
|
|||||||
- latest-N system-memory frame exchange
|
- latest-N system-memory frame exchange
|
||||||
- rendered-frame warmup
|
- rendered-frame warmup
|
||||||
- background Slang compile of `shaders/happy-accident`
|
- background Slang compile of `shaders/happy-accident`
|
||||||
- render-thread-only GL commit once compiled shader source is ready
|
- app-owned submission of a completed shader artifact
|
||||||
|
- render-thread-only GL commit once the artifact is ready
|
||||||
- compact telemetry
|
- compact telemetry
|
||||||
- non-GL frame-exchange tests
|
- non-GL frame-exchange tests
|
||||||
|
|
||||||
@@ -106,9 +107,9 @@ Healthy first-run signs:
|
|||||||
|
|
||||||
## Runtime Slang Shader Test
|
## Runtime Slang Shader Test
|
||||||
|
|
||||||
On startup the app begins compiling `shaders/happy-accident` on a background thread.
|
On startup the app begins compiling `shaders/happy-accident` on a background thread owned by the app orchestration layer.
|
||||||
|
|
||||||
The render thread keeps drawing the simple motion renderer while Slang compiles. It only attempts the OpenGL shader compile/link once a complete GLSL fragment shader is ready. If either the Slang build or GL commit fails, the app keeps rendering the simple motion fallback.
|
The render thread keeps drawing the simple motion renderer while Slang compiles. It does not choose packages, launch Slang, or track build lifecycle. It only receives a completed shader artifact and attempts the OpenGL shader compile/link at a frame boundary. If either the Slang build or GL commit fails, the app keeps rendering the simple motion fallback.
|
||||||
|
|
||||||
Successful handoff signs:
|
Successful handoff signs:
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "AppConfig.h"
|
#include "AppConfig.h"
|
||||||
|
#include "../runtime/RuntimeSlangShaderCompiler.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>
|
||||||
@@ -78,6 +80,7 @@ public:
|
|||||||
Stop();
|
Stop();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
StartRuntimeShaderBuild();
|
||||||
|
|
||||||
if (!mFrameExchange.WaitForCompletedDepth(mConfig.warmupCompletedFrames, mConfig.warmupTimeout))
|
if (!mFrameExchange.WaitForCompletedDepth(mConfig.warmupCompletedFrames, mConfig.warmupTimeout))
|
||||||
{
|
{
|
||||||
@@ -116,6 +119,7 @@ public:
|
|||||||
mTelemetry.Stop();
|
mTelemetry.Stop();
|
||||||
mOutputThread.Stop();
|
mOutputThread.Stop();
|
||||||
mOutput.Stop();
|
mOutput.Stop();
|
||||||
|
StopRuntimeShaderBuild();
|
||||||
mRenderThread.Stop();
|
mRenderThread.Stop();
|
||||||
mOutput.ReleaseResources();
|
mOutput.ReleaseResources();
|
||||||
mStarted = false;
|
mStarted = false;
|
||||||
@@ -137,12 +141,47 @@ private:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StartRuntimeShaderBuild()
|
||||||
|
{
|
||||||
|
mShaderCompiler.StartHappyAccidentBuild();
|
||||||
|
mShaderBridgeStopping = false;
|
||||||
|
mShaderBridgeThread = std::thread([this]() { ShaderBridgeMain(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void StopRuntimeShaderBuild()
|
||||||
|
{
|
||||||
|
mShaderBridgeStopping = true;
|
||||||
|
if (mShaderBridgeThread.joinable())
|
||||||
|
mShaderBridgeThread.join();
|
||||||
|
mShaderCompiler.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderBridgeMain()
|
||||||
|
{
|
||||||
|
while (!mShaderBridgeStopping)
|
||||||
|
{
|
||||||
|
RuntimeSlangShaderBuild build;
|
||||||
|
if (mShaderCompiler.TryConsume(build))
|
||||||
|
{
|
||||||
|
if (build.succeeded)
|
||||||
|
mRenderThread.SubmitRuntimeShaderArtifact(build.artifact);
|
||||||
|
else
|
||||||
|
std::cout << "Runtime Slang build failed: " << build.message << "\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RenderThread& mRenderThread;
|
RenderThread& mRenderThread;
|
||||||
SystemFrameExchange& mFrameExchange;
|
SystemFrameExchange& mFrameExchange;
|
||||||
AppConfig mConfig;
|
AppConfig mConfig;
|
||||||
DeckLinkOutput mOutput;
|
DeckLinkOutput mOutput;
|
||||||
DeckLinkOutputThread<SystemFrameExchange> mOutputThread;
|
DeckLinkOutputThread<SystemFrameExchange> mOutputThread;
|
||||||
TelemetryPrinter mTelemetry;
|
TelemetryPrinter mTelemetry;
|
||||||
|
RuntimeSlangShaderCompiler mShaderCompiler;
|
||||||
|
std::thread mShaderBridgeThread;
|
||||||
|
std::atomic<bool> mShaderBridgeStopping{ false };
|
||||||
bool mStarted = false;
|
bool mStarted = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ void RenderThread::ThreadMain()
|
|||||||
SignalStartupFailure("Render pipeline initialization failed.");
|
SignalStartupFailure("Render pipeline initialization failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mSlangCompiler.StartHappyAccidentBuild();
|
|
||||||
|
|
||||||
RenderCadenceClock clock(mConfig.frameDurationMilliseconds);
|
RenderCadenceClock clock(mConfig.frameDurationMilliseconds);
|
||||||
uint64_t frameIndex = 0;
|
uint64_t frameIndex = 0;
|
||||||
@@ -156,7 +155,6 @@ void RenderThread::ThreadMain()
|
|||||||
readback.Shutdown();
|
readback.Shutdown();
|
||||||
runtimeShaderRenderer.ShutdownGl();
|
runtimeShaderRenderer.ShutdownGl();
|
||||||
renderer.ShutdownGl();
|
renderer.ShutdownGl();
|
||||||
mSlangCompiler.Stop();
|
|
||||||
window.ClearCurrent();
|
window.ClearCurrent();
|
||||||
mRunning.store(false, std::memory_order_release);
|
mRunning.store(false, std::memory_order_release);
|
||||||
}
|
}
|
||||||
@@ -193,23 +191,36 @@ void RenderThread::CountAcquireMiss()
|
|||||||
++mMetrics.acquireMisses;
|
++mMetrics.acquireMisses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RenderThread::SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact)
|
||||||
|
{
|
||||||
|
if (artifact.fragmentShaderSource.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mShaderArtifactMutex);
|
||||||
|
mPendingShaderArtifact = artifact;
|
||||||
|
mHasPendingShaderArtifact = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderThread::TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mShaderArtifactMutex);
|
||||||
|
if (!mHasPendingShaderArtifact)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
artifact = std::move(mPendingShaderArtifact);
|
||||||
|
mPendingShaderArtifact = RuntimeShaderArtifact();
|
||||||
|
mHasPendingShaderArtifact = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void RenderThread::TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer)
|
void RenderThread::TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer)
|
||||||
{
|
{
|
||||||
RuntimeSlangShaderBuild build;
|
RuntimeShaderArtifact artifact;
|
||||||
if (!mSlangCompiler.TryConsume(build))
|
if (!TryTakePendingRuntimeShaderArtifact(artifact))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!build.succeeded)
|
|
||||||
{
|
|
||||||
std::cout << "Runtime Slang build failed: " << build.message << "\n";
|
|
||||||
OutputDebugStringA(("Runtime Slang build failed: " + build.message + "\n").c_str());
|
|
||||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
|
||||||
++mMetrics.shaderBuildFailures;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string commitError;
|
std::string commitError;
|
||||||
if (!runtimeShaderRenderer.CommitFragmentShader(build.fragmentShaderSource, commitError))
|
if (!runtimeShaderRenderer.CommitFragmentShader(artifact.fragmentShaderSource, commitError))
|
||||||
{
|
{
|
||||||
std::cout << "Runtime shader GL commit failed: " << commitError << "\n";
|
std::cout << "Runtime shader GL commit failed: " << commitError << "\n";
|
||||||
OutputDebugStringA(("Runtime shader GL commit failed: " + commitError + "\n").c_str());
|
OutputDebugStringA(("Runtime shader GL commit failed: " + commitError + "\n").c_str());
|
||||||
@@ -218,8 +229,8 @@ void RenderThread::TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeSha
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "Runtime shader committed: " << build.shaderId << ". " << build.message << "\n";
|
std::cout << "Runtime shader committed: " << artifact.shaderId << ". " << artifact.message << "\n";
|
||||||
OutputDebugStringA(("Runtime shader committed: " + build.shaderId + ". " + build.message + "\n").c_str());
|
OutputDebugStringA(("Runtime shader committed: " + artifact.shaderId + ". " + artifact.message + "\n").c_str());
|
||||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||||
++mMetrics.shaderBuildsCommitted;
|
++mMetrics.shaderBuildsCommitted;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "RenderCadenceClock.h"
|
#include "RenderCadenceClock.h"
|
||||||
#include "../runtime/RuntimeSlangShaderCompiler.h"
|
#include "../runtime/RuntimeShaderArtifact.h"
|
||||||
#include "RuntimeShaderRenderer.h"
|
#include "RuntimeShaderRenderer.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -44,6 +44,7 @@ public:
|
|||||||
|
|
||||||
bool Start(std::string& error);
|
bool Start(std::string& error);
|
||||||
void Stop();
|
void Stop();
|
||||||
|
void SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact);
|
||||||
|
|
||||||
Metrics GetMetrics() const;
|
Metrics GetMetrics() const;
|
||||||
bool IsRunning() const { return mRunning.load(std::memory_order_acquire); }
|
bool IsRunning() const { return mRunning.load(std::memory_order_acquire); }
|
||||||
@@ -56,10 +57,10 @@ private:
|
|||||||
void CountCompleted();
|
void CountCompleted();
|
||||||
void CountAcquireMiss();
|
void CountAcquireMiss();
|
||||||
void TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer);
|
void TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer);
|
||||||
|
bool TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact);
|
||||||
|
|
||||||
SystemFrameExchange& mFrameExchange;
|
SystemFrameExchange& mFrameExchange;
|
||||||
Config mConfig;
|
Config mConfig;
|
||||||
RuntimeSlangShaderCompiler mSlangCompiler;
|
|
||||||
std::thread mThread;
|
std::thread mThread;
|
||||||
std::atomic<bool> mStopping{ false };
|
std::atomic<bool> mStopping{ false };
|
||||||
std::atomic<bool> mRunning{ false };
|
std::atomic<bool> mRunning{ false };
|
||||||
@@ -71,4 +72,8 @@ private:
|
|||||||
|
|
||||||
mutable std::mutex mMetricsMutex;
|
mutable std::mutex mMetricsMutex;
|
||||||
Metrics mMetrics;
|
Metrics mMetrics;
|
||||||
|
|
||||||
|
std::mutex mShaderArtifactMutex;
|
||||||
|
bool mHasPendingShaderArtifact = false;
|
||||||
|
RuntimeShaderArtifact mPendingShaderArtifact;
|
||||||
};
|
};
|
||||||
|
|||||||
10
apps/RenderCadenceCompositor/runtime/RuntimeShaderArtifact.h
Normal file
10
apps/RenderCadenceCompositor/runtime/RuntimeShaderArtifact.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct RuntimeShaderArtifact
|
||||||
|
{
|
||||||
|
std::string shaderId;
|
||||||
|
std::string fragmentShaderSource;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
@@ -89,7 +89,7 @@ bool RuntimeSlangShaderCompiler::TryConsume(RuntimeSlangShaderBuild& build)
|
|||||||
RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildHappyAccident() const
|
RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildHappyAccident() const
|
||||||
{
|
{
|
||||||
RuntimeSlangShaderBuild build;
|
RuntimeSlangShaderBuild build;
|
||||||
build.shaderId = "happy-accident";
|
build.artifact.shaderId = "happy-accident";
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -126,7 +126,7 @@ RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildHappyAccident() const
|
|||||||
|
|
||||||
std::string error;
|
std::string error;
|
||||||
const auto start = std::chrono::steady_clock::now();
|
const auto start = std::chrono::steady_clock::now();
|
||||||
if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, build.fragmentShaderSource, error))
|
if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, build.artifact.fragmentShaderSource, error))
|
||||||
{
|
{
|
||||||
build.succeeded = false;
|
build.succeeded = false;
|
||||||
build.message = error.empty() ? "Happy Accident Slang compile failed." : error;
|
build.message = error.empty() ? "Happy Accident Slang compile failed." : error;
|
||||||
@@ -136,7 +136,8 @@ RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildHappyAccident() const
|
|||||||
const auto end = std::chrono::steady_clock::now();
|
const auto end = std::chrono::steady_clock::now();
|
||||||
const double milliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(end - start).count();
|
const double milliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(end - start).count();
|
||||||
build.succeeded = true;
|
build.succeeded = true;
|
||||||
build.message = "Happy Accident Slang compile completed in " + std::to_string(milliseconds) + " ms.";
|
build.artifact.message = "Happy Accident Slang compile completed in " + std::to_string(milliseconds) + " ms.";
|
||||||
|
build.message = build.artifact.message;
|
||||||
return build;
|
return build;
|
||||||
}
|
}
|
||||||
catch (const std::exception& exception)
|
catch (const std::exception& exception)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "RuntimeShaderArtifact.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -9,8 +11,7 @@ struct RuntimeSlangShaderBuild
|
|||||||
{
|
{
|
||||||
bool available = false;
|
bool available = false;
|
||||||
bool succeeded = false;
|
bool succeeded = false;
|
||||||
std::string shaderId;
|
RuntimeShaderArtifact artifact;
|
||||||
std::string fragmentShaderSource;
|
|
||||||
std::string message;
|
std::string message;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../video/DeckLinkOutput.h"
|
|
||||||
#include "../video/DeckLinkOutputThread.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -34,10 +31,10 @@ struct CadenceTelemetrySnapshot
|
|||||||
class CadenceTelemetry
|
class CadenceTelemetry
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
template <typename SystemFrameExchange, typename OutputThread>
|
template <typename SystemFrameExchange, typename Output, typename OutputThread>
|
||||||
CadenceTelemetrySnapshot Sample(
|
CadenceTelemetrySnapshot Sample(
|
||||||
const SystemFrameExchange& exchange,
|
const SystemFrameExchange& exchange,
|
||||||
const DeckLinkOutput& output,
|
const Output& output,
|
||||||
const OutputThread& outputThread)
|
const OutputThread& outputThread)
|
||||||
{
|
{
|
||||||
const auto now = Clock::now();
|
const auto now = Clock::now();
|
||||||
@@ -46,7 +43,7 @@ public:
|
|||||||
: 0.0;
|
: 0.0;
|
||||||
|
|
||||||
const auto exchangeMetrics = exchange.Metrics();
|
const auto exchangeMetrics = exchange.Metrics();
|
||||||
const DeckLinkOutputMetrics outputMetrics = output.Metrics();
|
const auto outputMetrics = output.Metrics();
|
||||||
const auto threadMetrics = outputThread.Metrics();
|
const auto threadMetrics = outputThread.Metrics();
|
||||||
|
|
||||||
CadenceTelemetrySnapshot snapshot;
|
CadenceTelemetrySnapshot snapshot;
|
||||||
@@ -80,10 +77,10 @@ public:
|
|||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename SystemFrameExchange, typename OutputThread, typename RenderThread>
|
template <typename SystemFrameExchange, typename Output, typename OutputThread, typename RenderThread>
|
||||||
CadenceTelemetrySnapshot Sample(
|
CadenceTelemetrySnapshot Sample(
|
||||||
const SystemFrameExchange& exchange,
|
const SystemFrameExchange& exchange,
|
||||||
const DeckLinkOutput& output,
|
const Output& output,
|
||||||
const OutputThread& outputThread,
|
const OutputThread& outputThread,
|
||||||
const RenderThread& renderThread)
|
const RenderThread& renderThread)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ public:
|
|||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename SystemFrameExchange, typename OutputThread, typename RenderThread>
|
template <typename SystemFrameExchange, typename Output, typename OutputThread, typename RenderThread>
|
||||||
void Start(const SystemFrameExchange& exchange, const DeckLinkOutput& output, const OutputThread& outputThread, const RenderThread& renderThread)
|
void Start(const SystemFrameExchange& exchange, const Output& output, const OutputThread& outputThread, const RenderThread& renderThread)
|
||||||
{
|
{
|
||||||
if (mRunning)
|
if (mRunning)
|
||||||
return;
|
return;
|
||||||
|
|||||||
94
tests/RenderCadenceCompositorClockTests.cpp
Normal file
94
tests/RenderCadenceCompositorClockTests.cpp
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#include "RenderCadenceClock.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
int gFailures = 0;
|
||||||
|
|
||||||
|
void Expect(bool condition, const char* message)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::cerr << "FAIL: " << message << "\n";
|
||||||
|
++gFailures;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestEarlyPollWaitsWithoutAdvancing()
|
||||||
|
{
|
||||||
|
using Clock = RenderCadenceClock::Clock;
|
||||||
|
RenderCadenceClock cadence(16.0);
|
||||||
|
const auto start = Clock::now();
|
||||||
|
cadence.Reset(start);
|
||||||
|
|
||||||
|
const auto tick = cadence.Poll(start - std::chrono::milliseconds(1));
|
||||||
|
Expect(!tick.due, "early poll is not due");
|
||||||
|
Expect(tick.sleepFor > RenderCadenceClock::Duration::zero(), "early poll returns a sleep duration");
|
||||||
|
Expect(cadence.OverrunCount() == 0, "early poll does not count overrun");
|
||||||
|
Expect(cadence.SkippedFrameCount() == 0, "early poll does not skip frames");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestDuePollRendersWithoutSkipping()
|
||||||
|
{
|
||||||
|
using Clock = RenderCadenceClock::Clock;
|
||||||
|
RenderCadenceClock cadence(16.0);
|
||||||
|
const auto start = Clock::now();
|
||||||
|
cadence.Reset(start);
|
||||||
|
|
||||||
|
const auto tick = cadence.Poll(start);
|
||||||
|
Expect(tick.due, "exact cadence time is due");
|
||||||
|
Expect(tick.skippedFrames == 0, "exact cadence time skips no frames");
|
||||||
|
Expect(cadence.OverrunCount() == 0, "exact cadence time is not an overrun");
|
||||||
|
|
||||||
|
cadence.MarkRendered(start);
|
||||||
|
Expect(cadence.NextRenderTime() > start, "mark rendered advances next render time");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestLatePollRecordsSkippedFrames()
|
||||||
|
{
|
||||||
|
using Clock = RenderCadenceClock::Clock;
|
||||||
|
RenderCadenceClock cadence(10.0);
|
||||||
|
const auto start = Clock::now();
|
||||||
|
cadence.Reset(start);
|
||||||
|
|
||||||
|
const auto tick = cadence.Poll(start + std::chrono::milliseconds(35));
|
||||||
|
Expect(tick.due, "late poll is due");
|
||||||
|
Expect(tick.skippedFrames == 3, "late poll records skipped frame intervals");
|
||||||
|
Expect(cadence.OverrunCount() == 1, "late poll records overrun");
|
||||||
|
Expect(cadence.SkippedFrameCount() == 3, "late poll accumulates skipped frames");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestMarkRenderedRebasesAfterLargeStall()
|
||||||
|
{
|
||||||
|
using Clock = RenderCadenceClock::Clock;
|
||||||
|
RenderCadenceClock cadence(10.0);
|
||||||
|
const auto start = Clock::now();
|
||||||
|
cadence.Reset(start);
|
||||||
|
|
||||||
|
const auto stalled = start + std::chrono::milliseconds(100);
|
||||||
|
cadence.MarkRendered(stalled);
|
||||||
|
|
||||||
|
const auto next = cadence.NextRenderTime();
|
||||||
|
Expect(next > stalled, "large stall rebases next render time after now");
|
||||||
|
Expect(next - stalled <= std::chrono::milliseconds(11), "large stall rebases to roughly one frame ahead");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
TestEarlyPollWaitsWithoutAdvancing();
|
||||||
|
TestDuePollRendersWithoutSkipping();
|
||||||
|
TestLatePollRecordsSkippedFrames();
|
||||||
|
TestMarkRenderedRebasesAfterLargeStall();
|
||||||
|
|
||||||
|
if (gFailures != 0)
|
||||||
|
{
|
||||||
|
std::cerr << gFailures << " cadence clock test failure(s).\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "RenderCadenceCompositor cadence clock tests passed.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
146
tests/RenderCadenceCompositorTelemetryTests.cpp
Normal file
146
tests/RenderCadenceCompositorTelemetryTests.cpp
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
#include "CadenceTelemetry.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
int gFailures = 0;
|
||||||
|
|
||||||
|
void Expect(bool condition, const char* message)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::cerr << "FAIL: " << message << "\n";
|
||||||
|
++gFailures;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FakeExchangeMetrics
|
||||||
|
{
|
||||||
|
std::size_t freeCount = 0;
|
||||||
|
std::size_t completedCount = 0;
|
||||||
|
std::size_t scheduledCount = 0;
|
||||||
|
uint64_t completedFrames = 0;
|
||||||
|
uint64_t scheduledFrames = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FakeExchange
|
||||||
|
{
|
||||||
|
FakeExchangeMetrics metrics;
|
||||||
|
FakeExchangeMetrics Metrics() const { return metrics; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FakeOutputThreadMetrics
|
||||||
|
{
|
||||||
|
uint64_t completedPollMisses = 0;
|
||||||
|
uint64_t scheduleFailures = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FakeOutputThread
|
||||||
|
{
|
||||||
|
FakeOutputThreadMetrics metrics;
|
||||||
|
FakeOutputThreadMetrics Metrics() const { return metrics; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FakeOutputMetrics
|
||||||
|
{
|
||||||
|
uint64_t completions = 0;
|
||||||
|
uint64_t displayedLate = 0;
|
||||||
|
uint64_t dropped = 0;
|
||||||
|
uint64_t scheduleFailures = 0;
|
||||||
|
bool actualBufferedFramesAvailable = false;
|
||||||
|
uint64_t actualBufferedFrames = 0;
|
||||||
|
double scheduleCallMilliseconds = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FakeOutput
|
||||||
|
{
|
||||||
|
FakeOutputMetrics metrics;
|
||||||
|
FakeOutputMetrics Metrics() const { return metrics; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FakeRenderThreadMetrics
|
||||||
|
{
|
||||||
|
uint64_t shaderBuildsCommitted = 0;
|
||||||
|
uint64_t shaderBuildFailures = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FakeRenderThread
|
||||||
|
{
|
||||||
|
FakeRenderThreadMetrics metrics;
|
||||||
|
FakeRenderThreadMetrics GetMetrics() const { return metrics; }
|
||||||
|
};
|
||||||
|
|
||||||
|
void TestTelemetrySamplesCompletedPollMissesAndShaderCounts()
|
||||||
|
{
|
||||||
|
RenderCadenceCompositor::CadenceTelemetry telemetry;
|
||||||
|
FakeExchange exchange;
|
||||||
|
exchange.metrics.freeCount = 7;
|
||||||
|
exchange.metrics.completedCount = 1;
|
||||||
|
exchange.metrics.scheduledCount = 4;
|
||||||
|
exchange.metrics.completedFrames = 100;
|
||||||
|
exchange.metrics.scheduledFrames = 96;
|
||||||
|
|
||||||
|
FakeOutput output;
|
||||||
|
output.metrics.actualBufferedFramesAvailable = true;
|
||||||
|
output.metrics.actualBufferedFrames = 4;
|
||||||
|
|
||||||
|
FakeOutputThread outputThread;
|
||||||
|
outputThread.metrics.completedPollMisses = 12;
|
||||||
|
outputThread.metrics.scheduleFailures = 0;
|
||||||
|
|
||||||
|
FakeRenderThread renderThread;
|
||||||
|
renderThread.metrics.shaderBuildsCommitted = 1;
|
||||||
|
renderThread.metrics.shaderBuildFailures = 0;
|
||||||
|
|
||||||
|
const auto snapshot = telemetry.Sample(exchange, output, outputThread, renderThread);
|
||||||
|
Expect(snapshot.freeFrames == 7, "free frame count is sampled");
|
||||||
|
Expect(snapshot.completedFrames == 1, "completed frame count is sampled");
|
||||||
|
Expect(snapshot.scheduledFrames == 4, "scheduled frame count is sampled");
|
||||||
|
Expect(snapshot.completedPollMisses == 12, "completed poll misses are sampled");
|
||||||
|
Expect(snapshot.shaderBuildsCommitted == 1, "shader committed count is sampled");
|
||||||
|
Expect(snapshot.shaderBuildFailures == 0, "shader failure count is sampled");
|
||||||
|
Expect(snapshot.deckLinkBufferedAvailable, "buffer telemetry availability is sampled");
|
||||||
|
Expect(snapshot.deckLinkBuffered == 4, "buffer depth is sampled");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestTelemetryComputesRatesFromDeltas()
|
||||||
|
{
|
||||||
|
RenderCadenceCompositor::CadenceTelemetry telemetry;
|
||||||
|
FakeExchange exchange;
|
||||||
|
FakeOutput output;
|
||||||
|
FakeOutputThread outputThread;
|
||||||
|
FakeRenderThread renderThread;
|
||||||
|
|
||||||
|
exchange.metrics.completedFrames = 10;
|
||||||
|
exchange.metrics.scheduledFrames = 10;
|
||||||
|
(void)telemetry.Sample(exchange, output, outputThread, renderThread);
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
|
|
||||||
|
exchange.metrics.completedFrames = 20;
|
||||||
|
exchange.metrics.scheduledFrames = 19;
|
||||||
|
const auto snapshot = telemetry.Sample(exchange, output, outputThread, renderThread);
|
||||||
|
Expect(snapshot.sampleSeconds > 0.0, "second telemetry sample has elapsed time");
|
||||||
|
Expect(snapshot.renderFps > 0.0, "render fps is computed from completed frame delta");
|
||||||
|
Expect(snapshot.scheduleFps > 0.0, "schedule fps is computed from scheduled frame delta");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
TestTelemetrySamplesCompletedPollMissesAndShaderCounts();
|
||||||
|
TestTelemetryComputesRatesFromDeltas();
|
||||||
|
|
||||||
|
if (gFailures != 0)
|
||||||
|
{
|
||||||
|
std::cerr << gFailures << " telemetry test failure(s).\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "RenderCadenceCompositor telemetry tests passed.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user