From 0ec5a4cfed90d8fbe064e26fc50f0645bc08b5f6 Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Mon, 11 May 2026 17:26:24 +1000 Subject: [PATCH] Phase 4 step 2a --- CMakeLists.txt | 18 +++ .../gl/RenderCommandQueue.cpp | 116 ++++++++++++++++++ .../gl/RenderCommandQueue.h | 59 +++++++++ .../gl/RenderEngine.cpp | 97 ++++++++------- .../gl/RenderEngine.h | 29 ++--- .../PHASE_4_RENDER_THREAD_OWNERSHIP_DESIGN.md | 17 ++- tests/RenderCommandQueueTests.cpp | 89 ++++++++++++++ 7 files changed, 358 insertions(+), 67 deletions(-) create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/RenderCommandQueue.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/RenderCommandQueue.h create mode 100644 tests/RenderCommandQueueTests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d9424a6..d8ea0f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,8 @@ set(APP_SOURCES "${APP_DIR}/gl/OpenGLComposite.cpp" "${APP_DIR}/gl/OpenGLComposite.h" "${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp" + "${APP_DIR}/gl/RenderCommandQueue.cpp" + "${APP_DIR}/gl/RenderCommandQueue.h" "${APP_DIR}/gl/RenderEngine.cpp" "${APP_DIR}/gl/RenderEngine.h" "${APP_DIR}/gl/RenderFrameState.h" @@ -368,6 +370,22 @@ endif() 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 "${APP_DIR}/runtime/support/RuntimeJson.cpp" "${APP_DIR}/shader/ShaderPackageRegistry.cpp" diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderCommandQueue.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/RenderCommandQueue.cpp new file mode 100644 index 0000000..e0547ca --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderCommandQueue.cpp @@ -0,0 +1,116 @@ +#include "RenderCommandQueue.h" + +void RenderCommandQueue::RequestPreviewPresent(const RenderPreviewPresentRequest& request) +{ + std::lock_guard lock(mMutex); + if (mHasPreviewPresentRequest) + ++mCoalescedCount; + else + ++mEnqueuedCount; + + mPreviewPresentRequest = request; + mHasPreviewPresentRequest = true; +} + +bool RenderCommandQueue::TryTakePreviewPresent(RenderPreviewPresentRequest& request) +{ + std::lock_guard lock(mMutex); + if (!mHasPreviewPresentRequest) + return false; + + request = mPreviewPresentRequest; + mPreviewPresentRequest = {}; + mHasPreviewPresentRequest = false; + return true; +} + +void RenderCommandQueue::RequestScreenshotCapture(const RenderScreenshotCaptureRequest& request) +{ + std::lock_guard lock(mMutex); + if (mHasScreenshotCaptureRequest) + ++mCoalescedCount; + else + ++mEnqueuedCount; + + mScreenshotCaptureRequest = request; + mHasScreenshotCaptureRequest = true; +} + +bool RenderCommandQueue::TryTakeScreenshotCapture(RenderScreenshotCaptureRequest& request) +{ + std::lock_guard 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 lock(mMutex); + if (mRenderResetScope != RenderCommandResetScope::None) + ++mCoalescedCount; + else + ++mEnqueuedCount; + + mRenderResetScope = MergeResetScopes(mRenderResetScope, scope); +} + +bool RenderCommandQueue::TryTakeRenderReset(RenderCommandResetScope& scope) +{ + std::lock_guard lock(mMutex); + if (mRenderResetScope == RenderCommandResetScope::None) + return false; + + scope = mRenderResetScope; + mRenderResetScope = RenderCommandResetScope::None; + return true; +} + +RenderCommandQueueMetrics RenderCommandQueue::GetMetrics() const +{ + std::lock_guard 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; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderCommandQueue.h b/apps/LoopThroughWithOpenGLCompositing/gl/RenderCommandQueue.h new file mode 100644 index 0000000..b068f20 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderCommandQueue.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +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; +}; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp index 6672f20..73976a2 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp @@ -1,7 +1,5 @@ #include "RenderEngine.h" -#include "ShaderBuildQueue.h" - #include #include @@ -88,48 +86,16 @@ bool RenderEngine::ApplyPreparedShaderBuild( 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& RenderEngine::CommittedLayerStates() const -{ - return mShaderPrograms.CommittedLayerStates(); -} - void RenderEngine::ResetTemporalHistoryState() { - mShaderPrograms.ResetTemporalHistoryState(); + mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly); + ProcessRenderResetCommandsOnRenderThread(); } void RenderEngine::ResetShaderFeedbackState() { - mShaderPrograms.ResetShaderFeedbackState(); + mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::ShaderFeedbackOnly); + ProcessRenderResetCommandsOnRenderThread(); } void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope) @@ -137,11 +103,12 @@ void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderRe switch (resetScope) { case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly: - ResetTemporalHistoryState(); + mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly); + ProcessRenderResetCommandsOnRenderThread(); break; case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback: - ResetTemporalHistoryState(); - ResetShaderFeedbackState(); + mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryAndFeedback); + ProcessRenderResetCommandsOnRenderThread(); break; case RuntimeCoordinatorRenderResetScope::None: 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() { mRuntimeLiveState.Clear(); @@ -195,7 +199,10 @@ bool RenderEngine::TryPresentPreview(bool force, unsigned previewFps, unsigned o if (!TryEnterCriticalSection(&mMutex)) 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); return presented; } @@ -252,6 +259,7 @@ bool RenderEngine::RenderOutputFrame(const RenderPipelineFrameContext& context, bool RenderEngine::RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame) { + ProcessRenderResetCommandsOnRenderThread(); return mRenderPipeline.RenderFrame(context, outputFrame); } @@ -333,7 +341,10 @@ bool RenderEngine::CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height { EnterCriticalSection(&mMutex); 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); LeaveCriticalSection(&mMutex); return captured; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h index 35ba55f..94f52a7 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h @@ -4,6 +4,7 @@ #include "OpenGLRenderPipeline.h" #include "OpenGLRenderer.h" #include "OpenGLShaderPrograms.h" +#include "RenderCommandQueue.h" #include "RenderFrameState.h" #include "RenderFrameStateResolver.h" #include "HealthTelemetry.h" @@ -18,8 +19,6 @@ #include #include -class ShaderBuildQueue; - class RenderEngine { public: @@ -50,13 +49,6 @@ public: uint64_t generation = 0; }; - struct PreparedShaderBuildApplyResult - { - bool hadReadyBuild = false; - bool applied = false; - std::string errorMessage; - }; - RenderEngine( RuntimeSnapshotProvider& runtimeSnapshotProvider, HealthTelemetry& healthTelemetry, @@ -79,7 +71,6 @@ public: unsigned outputPackTextureWidth, std::string& error); 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( const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, @@ -87,13 +78,7 @@ public: bool preserveFeedbackState, int errorMessageSize, char* errorMessage); - PreparedShaderBuildApplyResult TryApplyReadyShaderBuild( - ShaderBuildQueue& shaderBuildQueue, - unsigned inputFrameWidth, - unsigned inputFrameHeight, - bool preserveFeedbackState); - const std::vector& CommittedLayerStates() const; void ResetTemporalHistoryState(); void ResetShaderFeedbackState(); void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope); @@ -110,6 +95,10 @@ public: std::vector* commitRequests, RenderFrameState& frameState); void RenderPreparedFrame(const RenderFrameState& frameState); + bool CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height, std::vector& topDownPixels); + +private: + bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage); void RenderLayerStack( bool hasInputSource, const std::vector& layerStates, @@ -118,9 +107,10 @@ public: unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat, unsigned historyCap); - bool CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height, std::vector& topDownPixels); - -private: + void ResetTemporalHistoryStateOnRenderThread(); + void ResetShaderFeedbackStateOnRenderThread(); + void ApplyRenderResetOnRenderThread(RenderCommandResetScope resetScope); + void ProcessRenderResetCommandsOnRenderThread(); bool PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight); bool UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState); bool RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame); @@ -136,6 +126,7 @@ private: HGLRC mHglrc; std::chrono::steady_clock::time_point mLastPreviewPresentTime; + RenderCommandQueue mRenderCommandQueue; RenderFrameStateResolver mFrameStateResolver; RuntimeLiveState mRuntimeLiveState; }; diff --git a/docs/PHASE_4_RENDER_THREAD_OWNERSHIP_DESIGN.md b/docs/PHASE_4_RENDER_THREAD_OWNERSHIP_DESIGN.md index ae3fc70..3414b5c 100644 --- a/docs/PHASE_4_RENDER_THREAD_OWNERSHIP_DESIGN.md +++ b/docs/PHASE_4_RENDER_THREAD_OWNERSHIP_DESIGN.md @@ -7,12 +7,12 @@ Phase 1 named the subsystems. Phase 2 added the typed event substrate. Phase 3 m ## Status - 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 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. - `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. @@ -129,6 +129,13 @@ Non-responsibilities: 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: - `UploadInputFrame` @@ -224,9 +231,9 @@ Introduce a small queue/mailbox for render commands. Start with low-risk commands: -- preview present request -- screenshot request -- render-local reset requests +- [x] preview present request +- [x] screenshot request +- [x] render-local reset requests Then move input upload and output render requests once the queue and wakeup behavior are proven. diff --git a/tests/RenderCommandQueueTests.cpp b/tests/RenderCommandQueueTests.cpp new file mode 100644 index 0000000..523d677 --- /dev/null +++ b/tests/RenderCommandQueueTests.cpp @@ -0,0 +1,89 @@ +#include "RenderCommandQueue.h" + +#include + +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; +}