Phase 4 step 2a
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m36s
CI / Windows Release Package (push) Successful in 2m42s

This commit is contained in:
Aiden
2026-05-11 17:26:24 +10:00
parent 539fcd3351
commit 0ec5a4cfed
7 changed files with 358 additions and 67 deletions

View File

@@ -64,6 +64,8 @@ set(APP_SOURCES
"${APP_DIR}/gl/OpenGLComposite.cpp" "${APP_DIR}/gl/OpenGLComposite.cpp"
"${APP_DIR}/gl/OpenGLComposite.h" "${APP_DIR}/gl/OpenGLComposite.h"
"${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp" "${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp"
"${APP_DIR}/gl/RenderCommandQueue.cpp"
"${APP_DIR}/gl/RenderCommandQueue.h"
"${APP_DIR}/gl/RenderEngine.cpp" "${APP_DIR}/gl/RenderEngine.cpp"
"${APP_DIR}/gl/RenderEngine.h" "${APP_DIR}/gl/RenderEngine.h"
"${APP_DIR}/gl/RenderFrameState.h" "${APP_DIR}/gl/RenderFrameState.h"
@@ -368,6 +370,22 @@ endif()
add_test(NAME Std140BufferTests COMMAND Std140BufferTests) add_test(NAME Std140BufferTests COMMAND Std140BufferTests)
add_executable(RenderCommandQueueTests
"${APP_DIR}/gl/RenderCommandQueue.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCommandQueueTests.cpp"
)
target_include_directories(RenderCommandQueueTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/gl"
)
if(MSVC)
target_compile_options(RenderCommandQueueTests PRIVATE /W3)
endif()
add_test(NAME RenderCommandQueueTests COMMAND RenderCommandQueueTests)
add_executable(ShaderPackageRegistryTests add_executable(ShaderPackageRegistryTests
"${APP_DIR}/runtime/support/RuntimeJson.cpp" "${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp" "${APP_DIR}/shader/ShaderPackageRegistry.cpp"

View File

@@ -0,0 +1,116 @@
#include "RenderCommandQueue.h"
void RenderCommandQueue::RequestPreviewPresent(const RenderPreviewPresentRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mHasPreviewPresentRequest)
++mCoalescedCount;
else
++mEnqueuedCount;
mPreviewPresentRequest = request;
mHasPreviewPresentRequest = true;
}
bool RenderCommandQueue::TryTakePreviewPresent(RenderPreviewPresentRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mHasPreviewPresentRequest)
return false;
request = mPreviewPresentRequest;
mPreviewPresentRequest = {};
mHasPreviewPresentRequest = false;
return true;
}
void RenderCommandQueue::RequestScreenshotCapture(const RenderScreenshotCaptureRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mHasScreenshotCaptureRequest)
++mCoalescedCount;
else
++mEnqueuedCount;
mScreenshotCaptureRequest = request;
mHasScreenshotCaptureRequest = true;
}
bool RenderCommandQueue::TryTakeScreenshotCapture(RenderScreenshotCaptureRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mHasScreenshotCaptureRequest)
return false;
request = mScreenshotCaptureRequest;
mScreenshotCaptureRequest = {};
mHasScreenshotCaptureRequest = false;
return true;
}
void RenderCommandQueue::RequestRenderReset(RenderCommandResetScope scope)
{
if (scope == RenderCommandResetScope::None)
return;
std::lock_guard<std::mutex> lock(mMutex);
if (mRenderResetScope != RenderCommandResetScope::None)
++mCoalescedCount;
else
++mEnqueuedCount;
mRenderResetScope = MergeResetScopes(mRenderResetScope, scope);
}
bool RenderCommandQueue::TryTakeRenderReset(RenderCommandResetScope& scope)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mRenderResetScope == RenderCommandResetScope::None)
return false;
scope = mRenderResetScope;
mRenderResetScope = RenderCommandResetScope::None;
return true;
}
RenderCommandQueueMetrics RenderCommandQueue::GetMetrics() const
{
std::lock_guard<std::mutex> lock(mMutex);
RenderCommandQueueMetrics metrics;
metrics.depth =
(mHasPreviewPresentRequest ? 1u : 0u) +
(mHasScreenshotCaptureRequest ? 1u : 0u) +
(mRenderResetScope != RenderCommandResetScope::None ? 1u : 0u);
metrics.enqueuedCount = mEnqueuedCount;
metrics.coalescedCount = mCoalescedCount;
return metrics;
}
RenderCommandResetScope RenderCommandQueue::MergeResetScopes(RenderCommandResetScope current, RenderCommandResetScope requested)
{
if (current == RenderCommandResetScope::TemporalHistoryAndFeedback ||
requested == RenderCommandResetScope::TemporalHistoryAndFeedback)
{
return RenderCommandResetScope::TemporalHistoryAndFeedback;
}
if ((current == RenderCommandResetScope::TemporalHistoryOnly && requested == RenderCommandResetScope::ShaderFeedbackOnly) ||
(current == RenderCommandResetScope::ShaderFeedbackOnly && requested == RenderCommandResetScope::TemporalHistoryOnly))
{
return RenderCommandResetScope::TemporalHistoryAndFeedback;
}
if (current == RenderCommandResetScope::TemporalHistoryOnly ||
requested == RenderCommandResetScope::TemporalHistoryOnly)
{
return RenderCommandResetScope::TemporalHistoryOnly;
}
if (current == RenderCommandResetScope::ShaderFeedbackOnly ||
requested == RenderCommandResetScope::ShaderFeedbackOnly)
{
return RenderCommandResetScope::ShaderFeedbackOnly;
}
return RenderCommandResetScope::None;
}

View File

@@ -0,0 +1,59 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <mutex>
enum class RenderCommandResetScope
{
None,
ShaderFeedbackOnly,
TemporalHistoryOnly,
TemporalHistoryAndFeedback
};
struct RenderPreviewPresentRequest
{
unsigned outputFrameWidth = 0;
unsigned outputFrameHeight = 0;
};
struct RenderScreenshotCaptureRequest
{
unsigned width = 0;
unsigned height = 0;
};
struct RenderCommandQueueMetrics
{
std::size_t depth = 0;
uint64_t enqueuedCount = 0;
uint64_t coalescedCount = 0;
};
class RenderCommandQueue
{
public:
void RequestPreviewPresent(const RenderPreviewPresentRequest& request);
bool TryTakePreviewPresent(RenderPreviewPresentRequest& request);
void RequestScreenshotCapture(const RenderScreenshotCaptureRequest& request);
bool TryTakeScreenshotCapture(RenderScreenshotCaptureRequest& request);
void RequestRenderReset(RenderCommandResetScope scope);
bool TryTakeRenderReset(RenderCommandResetScope& scope);
RenderCommandQueueMetrics GetMetrics() const;
private:
static RenderCommandResetScope MergeResetScopes(RenderCommandResetScope current, RenderCommandResetScope requested);
mutable std::mutex mMutex;
bool mHasPreviewPresentRequest = false;
RenderPreviewPresentRequest mPreviewPresentRequest;
bool mHasScreenshotCaptureRequest = false;
RenderScreenshotCaptureRequest mScreenshotCaptureRequest;
RenderCommandResetScope mRenderResetScope = RenderCommandResetScope::None;
uint64_t mEnqueuedCount = 0;
uint64_t mCoalescedCount = 0;
};

View File

@@ -1,7 +1,5 @@
#include "RenderEngine.h" #include "RenderEngine.h"
#include "ShaderBuildQueue.h"
#include <gl/gl.h> #include <gl/gl.h>
#include <algorithm> #include <algorithm>
@@ -88,48 +86,16 @@ bool RenderEngine::ApplyPreparedShaderBuild(
return true; return true;
} }
RenderEngine::PreparedShaderBuildApplyResult RenderEngine::TryApplyReadyShaderBuild(
ShaderBuildQueue& shaderBuildQueue,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
bool preserveFeedbackState)
{
PreparedShaderBuildApplyResult result;
PreparedShaderBuild readyBuild;
if (!shaderBuildQueue.TryConsumeReadyBuild(readyBuild))
return result;
result.hadReadyBuild = true;
char compilerErrorMessage[1024] = {};
if (!ApplyPreparedShaderBuild(
readyBuild,
inputFrameWidth,
inputFrameHeight,
preserveFeedbackState,
sizeof(compilerErrorMessage),
compilerErrorMessage))
{
result.errorMessage = compilerErrorMessage;
return result;
}
result.applied = true;
return result;
}
const std::vector<RuntimeRenderState>& RenderEngine::CommittedLayerStates() const
{
return mShaderPrograms.CommittedLayerStates();
}
void RenderEngine::ResetTemporalHistoryState() void RenderEngine::ResetTemporalHistoryState()
{ {
mShaderPrograms.ResetTemporalHistoryState(); mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
ProcessRenderResetCommandsOnRenderThread();
} }
void RenderEngine::ResetShaderFeedbackState() void RenderEngine::ResetShaderFeedbackState()
{ {
mShaderPrograms.ResetShaderFeedbackState(); mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::ShaderFeedbackOnly);
ProcessRenderResetCommandsOnRenderThread();
} }
void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope) void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope)
@@ -137,11 +103,12 @@ void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderRe
switch (resetScope) switch (resetScope)
{ {
case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly: case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly:
ResetTemporalHistoryState(); mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
ProcessRenderResetCommandsOnRenderThread();
break; break;
case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback: case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback:
ResetTemporalHistoryState(); mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryAndFeedback);
ResetShaderFeedbackState(); ProcessRenderResetCommandsOnRenderThread();
break; break;
case RuntimeCoordinatorRenderResetScope::None: case RuntimeCoordinatorRenderResetScope::None:
default: default:
@@ -149,6 +116,43 @@ void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderRe
} }
} }
void RenderEngine::ResetTemporalHistoryStateOnRenderThread()
{
mShaderPrograms.ResetTemporalHistoryState();
}
void RenderEngine::ResetShaderFeedbackStateOnRenderThread()
{
mShaderPrograms.ResetShaderFeedbackState();
}
void RenderEngine::ApplyRenderResetOnRenderThread(RenderCommandResetScope resetScope)
{
switch (resetScope)
{
case RenderCommandResetScope::ShaderFeedbackOnly:
ResetShaderFeedbackStateOnRenderThread();
break;
case RenderCommandResetScope::TemporalHistoryOnly:
ResetTemporalHistoryStateOnRenderThread();
break;
case RenderCommandResetScope::TemporalHistoryAndFeedback:
ResetTemporalHistoryStateOnRenderThread();
ResetShaderFeedbackStateOnRenderThread();
break;
case RenderCommandResetScope::None:
default:
break;
}
}
void RenderEngine::ProcessRenderResetCommandsOnRenderThread()
{
RenderCommandResetScope resetScope = RenderCommandResetScope::None;
while (mRenderCommandQueue.TryTakeRenderReset(resetScope))
ApplyRenderResetOnRenderThread(resetScope);
}
void RenderEngine::ClearOscOverlayState() void RenderEngine::ClearOscOverlayState()
{ {
mRuntimeLiveState.Clear(); mRuntimeLiveState.Clear();
@@ -195,7 +199,10 @@ bool RenderEngine::TryPresentPreview(bool force, unsigned previewFps, unsigned o
if (!TryEnterCriticalSection(&mMutex)) if (!TryEnterCriticalSection(&mMutex))
return false; return false;
const bool presented = PresentPreviewOnRenderThread(outputFrameWidth, outputFrameHeight); mRenderCommandQueue.RequestPreviewPresent({ outputFrameWidth, outputFrameHeight });
RenderPreviewPresentRequest request;
const bool presented = mRenderCommandQueue.TryTakePreviewPresent(request) &&
PresentPreviewOnRenderThread(request.outputFrameWidth, request.outputFrameHeight);
LeaveCriticalSection(&mMutex); LeaveCriticalSection(&mMutex);
return presented; return presented;
} }
@@ -252,6 +259,7 @@ bool RenderEngine::RenderOutputFrame(const RenderPipelineFrameContext& context,
bool RenderEngine::RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame) bool RenderEngine::RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
{ {
ProcessRenderResetCommandsOnRenderThread();
return mRenderPipeline.RenderFrame(context, outputFrame); return mRenderPipeline.RenderFrame(context, outputFrame);
} }
@@ -333,7 +341,10 @@ bool RenderEngine::CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height
{ {
EnterCriticalSection(&mMutex); EnterCriticalSection(&mMutex);
wglMakeCurrent(mHdc, mHglrc); wglMakeCurrent(mHdc, mHglrc);
const bool captured = CaptureOutputFrameRgbaTopDownOnRenderThread(width, height, topDownPixels); mRenderCommandQueue.RequestScreenshotCapture({ width, height });
RenderScreenshotCaptureRequest request;
const bool captured = mRenderCommandQueue.TryTakeScreenshotCapture(request) &&
CaptureOutputFrameRgbaTopDownOnRenderThread(request.width, request.height, topDownPixels);
wglMakeCurrent(NULL, NULL); wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex); LeaveCriticalSection(&mMutex);
return captured; return captured;

View File

@@ -4,6 +4,7 @@
#include "OpenGLRenderPipeline.h" #include "OpenGLRenderPipeline.h"
#include "OpenGLRenderer.h" #include "OpenGLRenderer.h"
#include "OpenGLShaderPrograms.h" #include "OpenGLShaderPrograms.h"
#include "RenderCommandQueue.h"
#include "RenderFrameState.h" #include "RenderFrameState.h"
#include "RenderFrameStateResolver.h" #include "RenderFrameStateResolver.h"
#include "HealthTelemetry.h" #include "HealthTelemetry.h"
@@ -18,8 +19,6 @@
#include <string> #include <string>
#include <vector> #include <vector>
class ShaderBuildQueue;
class RenderEngine class RenderEngine
{ {
public: public:
@@ -50,13 +49,6 @@ public:
uint64_t generation = 0; uint64_t generation = 0;
}; };
struct PreparedShaderBuildApplyResult
{
bool hadReadyBuild = false;
bool applied = false;
std::string errorMessage;
};
RenderEngine( RenderEngine(
RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeSnapshotProvider& runtimeSnapshotProvider,
HealthTelemetry& healthTelemetry, HealthTelemetry& healthTelemetry,
@@ -79,7 +71,6 @@ public:
unsigned outputPackTextureWidth, unsigned outputPackTextureWidth,
std::string& error); std::string& error);
bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage); bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
bool ApplyPreparedShaderBuild( bool ApplyPreparedShaderBuild(
const PreparedShaderBuild& preparedBuild, const PreparedShaderBuild& preparedBuild,
unsigned inputFrameWidth, unsigned inputFrameWidth,
@@ -87,13 +78,7 @@ public:
bool preserveFeedbackState, bool preserveFeedbackState,
int errorMessageSize, int errorMessageSize,
char* errorMessage); char* errorMessage);
PreparedShaderBuildApplyResult TryApplyReadyShaderBuild(
ShaderBuildQueue& shaderBuildQueue,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
bool preserveFeedbackState);
const std::vector<RuntimeRenderState>& CommittedLayerStates() const;
void ResetTemporalHistoryState(); void ResetTemporalHistoryState();
void ResetShaderFeedbackState(); void ResetShaderFeedbackState();
void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope); void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope);
@@ -110,6 +95,10 @@ public:
std::vector<OscOverlayCommitRequest>* commitRequests, std::vector<OscOverlayCommitRequest>* commitRequests,
RenderFrameState& frameState); RenderFrameState& frameState);
void RenderPreparedFrame(const RenderFrameState& frameState); void RenderPreparedFrame(const RenderFrameState& frameState);
bool CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels);
private:
bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
void RenderLayerStack( void RenderLayerStack(
bool hasInputSource, bool hasInputSource,
const std::vector<RuntimeRenderState>& layerStates, const std::vector<RuntimeRenderState>& layerStates,
@@ -118,9 +107,10 @@ public:
unsigned captureTextureWidth, unsigned captureTextureWidth,
VideoIOPixelFormat inputPixelFormat, VideoIOPixelFormat inputPixelFormat,
unsigned historyCap); unsigned historyCap);
bool CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels); void ResetTemporalHistoryStateOnRenderThread();
void ResetShaderFeedbackStateOnRenderThread();
private: void ApplyRenderResetOnRenderThread(RenderCommandResetScope resetScope);
void ProcessRenderResetCommandsOnRenderThread();
bool PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight); bool PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight);
bool UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState); bool UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
bool RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame); bool RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
@@ -136,6 +126,7 @@ private:
HGLRC mHglrc; HGLRC mHglrc;
std::chrono::steady_clock::time_point mLastPreviewPresentTime; std::chrono::steady_clock::time_point mLastPreviewPresentTime;
RenderCommandQueue mRenderCommandQueue;
RenderFrameStateResolver mFrameStateResolver; RenderFrameStateResolver mFrameStateResolver;
RuntimeLiveState mRuntimeLiveState; RuntimeLiveState mRuntimeLiveState;
}; };

View File

@@ -7,12 +7,12 @@ Phase 1 named the subsystems. Phase 2 added the typed event substrate. Phase 3 m
## Status ## Status
- Phase 4 design package: proposed. - Phase 4 design package: proposed.
- Phase 4 implementation: Step 1 started. The existing synchronous `RenderEngine` entrypoints now delegate their GL bodies to named `...OnRenderThread(...)` helpers, but no command queue or dedicated render thread exists yet. - Phase 4 implementation: Step 2 started. The existing synchronous `RenderEngine` entrypoints delegate their GL bodies to named `...OnRenderThread(...)` helpers, and preview/screenshot/render-reset requests now pass through a small `RenderCommandQueue` compatibility mailbox. No dedicated render thread exists yet.
- Current alignment: the repo has a named frame-state contract and cleaner render-state preparation, but GL work is still entered through multiple paths protected by one shared `CRITICAL_SECTION`. - Current alignment: the repo has a named frame-state contract and cleaner render-state preparation, but GL work is still entered through multiple paths protected by one shared `CRITICAL_SECTION`.
Current GL ownership footholds: Current GL ownership footholds:
- `RenderEngine` owns GL resources, the current context-binding compatibility shims, and named render-thread helper methods. - `RenderEngine` owns GL resources, the current context-binding compatibility shims, a small render command mailbox, and named render-thread helper methods.
- `RenderFrameInput` / `RenderFrameState` provide the frame-state contract that a render thread can consume. - `RenderFrameInput` / `RenderFrameState` provide the frame-state contract that a render thread can consume.
- `RenderFrameStateResolver` prepares the render-facing layer state before drawing. - `RenderFrameStateResolver` prepares the render-facing layer state before drawing.
- `OpenGLVideoIOBridge` still calls `RenderEngine::TryUploadInputFrame(...)` from the input path and `RenderEngine::RenderOutputFrame(...)` from the output path. - `OpenGLVideoIOBridge` still calls `RenderEngine::TryUploadInputFrame(...)` from the input path and `RenderEngine::RenderOutputFrame(...)` from the output path.
@@ -129,6 +129,13 @@ Non-responsibilities:
Small bounded queue or command mailbox for render-thread work. Small bounded queue or command mailbox for render-thread work.
Current implementation:
- `RenderCommandQueue` exists as a pure C++ mailbox helper.
- Preview present and screenshot capture requests use latest-value coalescing.
- Render-local reset requests coalesce to the strongest pending reset scope.
- `RenderEngine` drains these commands synchronously as compatibility shims until a dedicated render thread is introduced.
Possible commands: Possible commands:
- `UploadInputFrame` - `UploadInputFrame`
@@ -224,9 +231,9 @@ Introduce a small queue/mailbox for render commands.
Start with low-risk commands: Start with low-risk commands:
- preview present request - [x] preview present request
- screenshot request - [x] screenshot request
- render-local reset requests - [x] render-local reset requests
Then move input upload and output render requests once the queue and wakeup behavior are proven. Then move input upload and output render requests once the queue and wakeup behavior are proven.

View File

@@ -0,0 +1,89 @@
#include "RenderCommandQueue.h"
#include <iostream>
namespace
{
int gFailures = 0;
void Expect(bool condition, const char* message)
{
if (condition)
return;
std::cerr << "FAIL: " << message << "\n";
++gFailures;
}
void TestPreviewRequestUsesLatestValue()
{
RenderCommandQueue queue;
queue.RequestPreviewPresent({ 1920, 1080 });
queue.RequestPreviewPresent({ 1280, 720 });
const RenderCommandQueueMetrics metrics = queue.GetMetrics();
Expect(metrics.depth == 1, "preview requests coalesce to one pending command");
Expect(metrics.enqueuedCount == 1, "first preview request is counted as enqueued");
Expect(metrics.coalescedCount == 1, "second preview request is counted as coalesced");
RenderPreviewPresentRequest request;
Expect(queue.TryTakePreviewPresent(request), "preview request can be consumed");
Expect(request.outputFrameWidth == 1280 && request.outputFrameHeight == 720, "latest preview request wins");
Expect(!queue.TryTakePreviewPresent(request), "preview request is removed after consume");
Expect(queue.GetMetrics().depth == 0, "preview consume empties queue depth");
}
void TestScreenshotRequestUsesLatestValue()
{
RenderCommandQueue queue;
queue.RequestScreenshotCapture({ 640, 360 });
queue.RequestScreenshotCapture({ 3840, 2160 });
RenderScreenshotCaptureRequest request;
Expect(queue.TryTakeScreenshotCapture(request), "screenshot request can be consumed");
Expect(request.width == 3840 && request.height == 2160, "latest screenshot request wins");
Expect(!queue.TryTakeScreenshotCapture(request), "screenshot request is removed after consume");
}
void TestRenderResetScopesCoalesceToStrongestRequest()
{
RenderCommandQueue queue;
queue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
queue.RequestRenderReset(RenderCommandResetScope::ShaderFeedbackOnly);
RenderCommandResetScope scope = RenderCommandResetScope::None;
Expect(queue.TryTakeRenderReset(scope), "render reset request can be consumed");
Expect(scope == RenderCommandResetScope::TemporalHistoryAndFeedback, "temporal and feedback reset requests merge");
Expect(!queue.TryTakeRenderReset(scope), "render reset request is removed after consume");
queue.RequestRenderReset(RenderCommandResetScope::None);
Expect(queue.GetMetrics().depth == 0, "none reset request is ignored");
}
void TestIndependentCommandKindsShareDepth()
{
RenderCommandQueue queue;
queue.RequestPreviewPresent({ 1, 2 });
queue.RequestScreenshotCapture({ 3, 4 });
queue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
Expect(queue.GetMetrics().depth == 3, "independent command kinds each contribute to depth");
}
}
int main()
{
TestPreviewRequestUsesLatestValue();
TestScreenshotRequestUsesLatestValue();
TestRenderResetScopesCoalesceToStrongestRequest();
TestIndependentCommandKindsShareDepth();
if (gFailures != 0)
{
std::cerr << gFailures << " RenderCommandQueue test failure(s).\n";
return 1;
}
std::cout << "RenderCommandQueue tests passed.\n";
return 0;
}