Shader ownership change
This commit is contained in:
@@ -745,6 +745,35 @@ endif()
|
||||
|
||||
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
|
||||
"${APP_DIR}/videoio/SystemOutputFramePool.cpp"
|
||||
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
||||
|
||||
@@ -38,7 +38,8 @@ Included now:
|
||||
- latest-N system-memory frame exchange
|
||||
- rendered-frame warmup
|
||||
- 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
|
||||
- non-GL frame-exchange tests
|
||||
|
||||
@@ -106,9 +107,9 @@ Healthy first-run signs:
|
||||
|
||||
## 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:
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppConfig.h"
|
||||
#include "../runtime/RuntimeSlangShaderCompiler.h"
|
||||
#include "../telemetry/TelemetryPrinter.h"
|
||||
#include "../video/DeckLinkOutput.h"
|
||||
#include "../video/DeckLinkOutputThread.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
@@ -78,6 +80,7 @@ public:
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
StartRuntimeShaderBuild();
|
||||
|
||||
if (!mFrameExchange.WaitForCompletedDepth(mConfig.warmupCompletedFrames, mConfig.warmupTimeout))
|
||||
{
|
||||
@@ -116,6 +119,7 @@ public:
|
||||
mTelemetry.Stop();
|
||||
mOutputThread.Stop();
|
||||
mOutput.Stop();
|
||||
StopRuntimeShaderBuild();
|
||||
mRenderThread.Stop();
|
||||
mOutput.ReleaseResources();
|
||||
mStarted = false;
|
||||
@@ -137,12 +141,47 @@ private:
|
||||
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;
|
||||
SystemFrameExchange& mFrameExchange;
|
||||
AppConfig mConfig;
|
||||
DeckLinkOutput mOutput;
|
||||
DeckLinkOutputThread<SystemFrameExchange> mOutputThread;
|
||||
TelemetryPrinter mTelemetry;
|
||||
RuntimeSlangShaderCompiler mShaderCompiler;
|
||||
std::thread mShaderBridgeThread;
|
||||
std::atomic<bool> mShaderBridgeStopping{ false };
|
||||
bool mStarted = false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,7 +93,6 @@ void RenderThread::ThreadMain()
|
||||
SignalStartupFailure("Render pipeline initialization failed.");
|
||||
return;
|
||||
}
|
||||
mSlangCompiler.StartHappyAccidentBuild();
|
||||
|
||||
RenderCadenceClock clock(mConfig.frameDurationMilliseconds);
|
||||
uint64_t frameIndex = 0;
|
||||
@@ -156,7 +155,6 @@ void RenderThread::ThreadMain()
|
||||
readback.Shutdown();
|
||||
runtimeShaderRenderer.ShutdownGl();
|
||||
renderer.ShutdownGl();
|
||||
mSlangCompiler.Stop();
|
||||
window.ClearCurrent();
|
||||
mRunning.store(false, std::memory_order_release);
|
||||
}
|
||||
@@ -193,23 +191,36 @@ void RenderThread::CountAcquireMiss()
|
||||
++mMetrics.acquireMisses;
|
||||
}
|
||||
|
||||
void RenderThread::TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer)
|
||||
void RenderThread::SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact)
|
||||
{
|
||||
RuntimeSlangShaderBuild build;
|
||||
if (!mSlangCompiler.TryConsume(build))
|
||||
if (artifact.fragmentShaderSource.empty())
|
||||
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::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)
|
||||
{
|
||||
RuntimeShaderArtifact artifact;
|
||||
if (!TryTakePendingRuntimeShaderArtifact(artifact))
|
||||
return;
|
||||
|
||||
std::string commitError;
|
||||
if (!runtimeShaderRenderer.CommitFragmentShader(build.fragmentShaderSource, commitError))
|
||||
if (!runtimeShaderRenderer.CommitFragmentShader(artifact.fragmentShaderSource, commitError))
|
||||
{
|
||||
std::cout << "Runtime shader GL commit failed: " << commitError << "\n";
|
||||
OutputDebugStringA(("Runtime shader GL commit failed: " + commitError + "\n").c_str());
|
||||
@@ -218,8 +229,8 @@ void RenderThread::TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeSha
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Runtime shader committed: " << build.shaderId << ". " << build.message << "\n";
|
||||
OutputDebugStringA(("Runtime shader committed: " + build.shaderId + ". " + build.message + "\n").c_str());
|
||||
std::cout << "Runtime shader committed: " << artifact.shaderId << ". " << artifact.message << "\n";
|
||||
OutputDebugStringA(("Runtime shader committed: " + artifact.shaderId + ". " + artifact.message + "\n").c_str());
|
||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||
++mMetrics.shaderBuildsCommitted;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "RenderCadenceClock.h"
|
||||
#include "../runtime/RuntimeSlangShaderCompiler.h"
|
||||
#include "../runtime/RuntimeShaderArtifact.h"
|
||||
#include "RuntimeShaderRenderer.h"
|
||||
|
||||
#include <atomic>
|
||||
@@ -44,6 +44,7 @@ public:
|
||||
|
||||
bool Start(std::string& error);
|
||||
void Stop();
|
||||
void SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact);
|
||||
|
||||
Metrics GetMetrics() const;
|
||||
bool IsRunning() const { return mRunning.load(std::memory_order_acquire); }
|
||||
@@ -56,10 +57,10 @@ private:
|
||||
void CountCompleted();
|
||||
void CountAcquireMiss();
|
||||
void TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer);
|
||||
bool TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact);
|
||||
|
||||
SystemFrameExchange& mFrameExchange;
|
||||
Config mConfig;
|
||||
RuntimeSlangShaderCompiler mSlangCompiler;
|
||||
std::thread mThread;
|
||||
std::atomic<bool> mStopping{ false };
|
||||
std::atomic<bool> mRunning{ false };
|
||||
@@ -71,4 +72,8 @@ private:
|
||||
|
||||
mutable std::mutex mMetricsMutex;
|
||||
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 build;
|
||||
build.shaderId = "happy-accident";
|
||||
build.artifact.shaderId = "happy-accident";
|
||||
|
||||
try
|
||||
{
|
||||
@@ -126,7 +126,7 @@ RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildHappyAccident() const
|
||||
|
||||
std::string error;
|
||||
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.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 double milliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(end - start).count();
|
||||
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;
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeShaderArtifact.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
@@ -9,8 +11,7 @@ struct RuntimeSlangShaderBuild
|
||||
{
|
||||
bool available = false;
|
||||
bool succeeded = false;
|
||||
std::string shaderId;
|
||||
std::string fragmentShaderSource;
|
||||
RuntimeShaderArtifact artifact;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../video/DeckLinkOutput.h"
|
||||
#include "../video/DeckLinkOutputThread.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -34,10 +31,10 @@ struct CadenceTelemetrySnapshot
|
||||
class CadenceTelemetry
|
||||
{
|
||||
public:
|
||||
template <typename SystemFrameExchange, typename OutputThread>
|
||||
template <typename SystemFrameExchange, typename Output, typename OutputThread>
|
||||
CadenceTelemetrySnapshot Sample(
|
||||
const SystemFrameExchange& exchange,
|
||||
const DeckLinkOutput& output,
|
||||
const Output& output,
|
||||
const OutputThread& outputThread)
|
||||
{
|
||||
const auto now = Clock::now();
|
||||
@@ -46,7 +43,7 @@ public:
|
||||
: 0.0;
|
||||
|
||||
const auto exchangeMetrics = exchange.Metrics();
|
||||
const DeckLinkOutputMetrics outputMetrics = output.Metrics();
|
||||
const auto outputMetrics = output.Metrics();
|
||||
const auto threadMetrics = outputThread.Metrics();
|
||||
|
||||
CadenceTelemetrySnapshot snapshot;
|
||||
@@ -80,10 +77,10 @@ public:
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
template <typename SystemFrameExchange, typename OutputThread, typename RenderThread>
|
||||
template <typename SystemFrameExchange, typename Output, typename OutputThread, typename RenderThread>
|
||||
CadenceTelemetrySnapshot Sample(
|
||||
const SystemFrameExchange& exchange,
|
||||
const DeckLinkOutput& output,
|
||||
const Output& output,
|
||||
const OutputThread& outputThread,
|
||||
const RenderThread& renderThread)
|
||||
{
|
||||
|
||||
@@ -31,8 +31,8 @@ public:
|
||||
Stop();
|
||||
}
|
||||
|
||||
template <typename SystemFrameExchange, typename OutputThread, typename RenderThread>
|
||||
void Start(const SystemFrameExchange& exchange, const DeckLinkOutput& output, const OutputThread& outputThread, const RenderThread& renderThread)
|
||||
template <typename SystemFrameExchange, typename Output, typename OutputThread, typename RenderThread>
|
||||
void Start(const SystemFrameExchange& exchange, const Output& output, const OutputThread& outputThread, const RenderThread& renderThread)
|
||||
{
|
||||
if (mRunning)
|
||||
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