Compare commits
71 Commits
v0.0.5
...
f1f4e3421b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1f4e3421b | ||
|
|
ac729dc2b9 | ||
|
|
bf23cd880a | ||
|
|
9e3412712c | ||
|
|
a434a88108 | ||
|
|
c5cead6003 | ||
|
|
f8adbbe0fe | ||
|
|
0a7954e879 | ||
|
|
f288455709 | ||
|
|
50d5880835 | ||
|
|
52eaf16a8c | ||
|
|
6b0638336a | ||
|
|
0da6ad6802 | ||
|
|
dd3cd6b66c | ||
|
|
1d08dec5fe | ||
|
|
0d57920bc1 | ||
|
|
1629dbc77a | ||
|
|
205c90e52e | ||
|
|
ab38bfad24 | ||
|
|
68503256dc | ||
|
|
a91cc91a21 | ||
|
|
a530325fa1 | ||
|
|
d332dceb5b | ||
|
|
79855d788c | ||
|
|
ff10b66d1d | ||
|
|
fdcc38c6ae | ||
|
|
718e4dcadd | ||
|
|
7740fe209c | ||
|
|
77590f4a62 | ||
|
|
e8a3805fff | ||
|
|
99fd903144 | ||
|
|
761df3b2d0 | ||
|
|
f141d20026 | ||
|
|
bfc32c4a1e | ||
|
|
20476bdf63 | ||
|
|
0ec5a4cfed | ||
|
|
539fcd3351 | ||
|
|
ebc10a9925 | ||
|
|
e5c5920ccd | ||
|
|
3b641dd07a | ||
|
|
e00e2574ed | ||
|
|
e459155d51 | ||
|
|
06f3dd4942 | ||
|
|
0808171677 | ||
|
|
00b6ad4c36 | ||
|
|
d4f6a4a268 | ||
|
|
6e600be112 | ||
|
|
a9b08f7f27 | ||
|
|
ccfc0237fd | ||
|
|
b3705d96cc | ||
|
|
5503ce85a9 | ||
|
|
41677b71ec | ||
|
|
9cbb5d8004 | ||
|
|
cbf1b541dc | ||
|
|
5cbdbd6813 | ||
|
|
b2369c418b | ||
|
|
c4883d3413 | ||
|
|
53e78890a8 | ||
|
|
36b398ea95 | ||
|
|
ba4643dfa3 | ||
|
|
27dbb55f7b | ||
|
|
f6b26bf28b | ||
|
|
861593123d | ||
|
|
34c145e80b | ||
|
|
a24cdc0630 | ||
|
|
120f899b0d | ||
|
|
41075bbc61 | ||
|
|
7f0f60c0e3 | ||
|
|
739231d5a1 | ||
|
|
3629227aa9 | ||
|
|
618831d578 |
@@ -57,11 +57,11 @@ jobs:
|
||||
|
||||
- name: Build Debug
|
||||
shell: powershell
|
||||
run: cmake --build --preset build-debug
|
||||
run: cmake --build --preset build-debug --parallel
|
||||
|
||||
- name: Run Native Tests And Shader Validation
|
||||
shell: powershell
|
||||
run: cmake --build --preset build-debug --target RUN_TESTS
|
||||
run: cmake --build --preset build-debug --target RUN_TESTS --parallel
|
||||
|
||||
ui-ubuntu:
|
||||
name: React UI Build
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
|
||||
- name: Build Release
|
||||
shell: powershell
|
||||
run: cmake --build --preset build-release
|
||||
run: cmake --build --preset build-release --parallel
|
||||
|
||||
- name: Install Runtime Package
|
||||
shell: powershell
|
||||
|
||||
49
.vscode/launch.json
vendored
49
.vscode/launch.json
vendored
@@ -11,6 +11,55 @@
|
||||
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"environment": [],
|
||||
"console": "internalConsole",
|
||||
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"requireExactSource": true,
|
||||
"logging": {
|
||||
"moduleLoad": true
|
||||
},
|
||||
"preLaunchTask": "Build LoopThroughWithOpenGLCompositing Debug x64"
|
||||
},
|
||||
{
|
||||
"name": "Debug LoopThroughWithOpenGLCompositing - sync readback experiment",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug\\LoopThroughWithOpenGLCompositing.exe",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"environment": [
|
||||
{
|
||||
"name": "VST_OUTPUT_READBACK_MODE",
|
||||
"value": "sync"
|
||||
}
|
||||
],
|
||||
"console": "internalConsole",
|
||||
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"requireExactSource": true,
|
||||
"logging": {
|
||||
"moduleLoad": true
|
||||
},
|
||||
"preLaunchTask": "Build LoopThroughWithOpenGLCompositing Debug x64"
|
||||
},
|
||||
{
|
||||
"name": "Debug LoopThroughWithOpenGLCompositing - cached output experiment",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug\\LoopThroughWithOpenGLCompositing.exe",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"environment": [
|
||||
{
|
||||
"name": "VST_OUTPUT_READBACK_MODE",
|
||||
"value": "cached_only"
|
||||
}
|
||||
],
|
||||
"console": "internalConsole",
|
||||
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"requireExactSource": true,
|
||||
"logging": {
|
||||
"moduleLoad": true
|
||||
},
|
||||
"preLaunchTask": "Build LoopThroughWithOpenGLCompositing Debug x64"
|
||||
}
|
||||
]
|
||||
|
||||
6
.vscode/tasks.json
vendored
6
.vscode/tasks.json
vendored
@@ -11,7 +11,8 @@
|
||||
"--config",
|
||||
"Debug",
|
||||
"--target",
|
||||
"LoopThroughWithOpenGLCompositing"
|
||||
"LoopThroughWithOpenGLCompositing",
|
||||
"--parallel"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
@@ -29,7 +30,8 @@
|
||||
"--config",
|
||||
"Release",
|
||||
"--target",
|
||||
"LoopThroughWithOpenGLCompositing"
|
||||
"LoopThroughWithOpenGLCompositing",
|
||||
"--parallel"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
|
||||
347
CMakeLists.txt
347
CMakeLists.txt
@@ -34,10 +34,14 @@ set(APP_SOURCES
|
||||
"${APP_DIR}/videoio/decklink/DeckLinkAPI_i.c"
|
||||
"${APP_DIR}/control/ControlServer.cpp"
|
||||
"${APP_DIR}/control/ControlServer.h"
|
||||
"${APP_DIR}/control/ControlServices.cpp"
|
||||
"${APP_DIR}/control/ControlServices.h"
|
||||
"${APP_DIR}/control/OscServer.cpp"
|
||||
"${APP_DIR}/control/OscServer.h"
|
||||
"${APP_DIR}/control/RuntimeControlBridge.cpp"
|
||||
"${APP_DIR}/control/RuntimeControlBridge.h"
|
||||
"${APP_DIR}/control/RuntimeServiceLiveBridge.cpp"
|
||||
"${APP_DIR}/control/RuntimeServiceLiveBridge.h"
|
||||
"${APP_DIR}/control/RuntimeServices.cpp"
|
||||
"${APP_DIR}/control/RuntimeServices.h"
|
||||
"${APP_DIR}/videoio/decklink/DeckLinkAPI_h.h"
|
||||
@@ -57,9 +61,18 @@ set(APP_SOURCES
|
||||
"${APP_DIR}/gl/renderer/GlScopedObjects.h"
|
||||
"${APP_DIR}/gl/shader/GlShaderSources.cpp"
|
||||
"${APP_DIR}/gl/shader/GlShaderSources.h"
|
||||
"${APP_DIR}/gl/OpenGLComposite.cpp"
|
||||
"${APP_DIR}/gl/OpenGLComposite.h"
|
||||
"${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp"
|
||||
"${APP_DIR}/gl/composite/OpenGLComposite.cpp"
|
||||
"${APP_DIR}/gl/composite/OpenGLComposite.h"
|
||||
"${APP_DIR}/gl/composite/OpenGLCompositeRuntimeControls.cpp"
|
||||
"${APP_DIR}/gl/threading/RenderCommandQueue.cpp"
|
||||
"${APP_DIR}/gl/threading/RenderCommandQueue.h"
|
||||
"${APP_DIR}/gl/RenderEngine.cpp"
|
||||
"${APP_DIR}/gl/RenderEngine.h"
|
||||
"${APP_DIR}/gl/frame/RenderFrameState.h"
|
||||
"${APP_DIR}/gl/frame/RenderFrameStateResolver.cpp"
|
||||
"${APP_DIR}/gl/frame/RenderFrameStateResolver.h"
|
||||
"${APP_DIR}/gl/frame/RuntimeUpdateController.cpp"
|
||||
"${APP_DIR}/gl/frame/RuntimeUpdateController.h"
|
||||
"${APP_DIR}/gl/pipeline/OpenGLRenderPass.cpp"
|
||||
"${APP_DIR}/gl/pipeline/OpenGLRenderPass.h"
|
||||
"${APP_DIR}/gl/pipeline/OpenGLRenderPipeline.cpp"
|
||||
@@ -96,14 +109,50 @@ set(APP_SOURCES
|
||||
"${APP_DIR}/platform/NativeHandles.h"
|
||||
"${APP_DIR}/platform/NativeSockets.h"
|
||||
"${APP_DIR}/resource.h"
|
||||
"${APP_DIR}/runtime/RuntimeHost.cpp"
|
||||
"${APP_DIR}/runtime/RuntimeHost.h"
|
||||
"${APP_DIR}/runtime/RuntimeClock.cpp"
|
||||
"${APP_DIR}/runtime/RuntimeClock.h"
|
||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${APP_DIR}/runtime/RuntimeJson.h"
|
||||
"${APP_DIR}/runtime/RuntimeParameterUtils.cpp"
|
||||
"${APP_DIR}/runtime/RuntimeParameterUtils.h"
|
||||
"${APP_DIR}/runtime/coordination/RuntimeCoordinator.cpp"
|
||||
"${APP_DIR}/runtime/coordination/RuntimeCoordinator.h"
|
||||
"${APP_DIR}/runtime/events/RuntimeEventCoalescingQueue.h"
|
||||
"${APP_DIR}/runtime/events/RuntimeEventDispatcher.h"
|
||||
"${APP_DIR}/runtime/events/RuntimeEvent.h"
|
||||
"${APP_DIR}/runtime/events/RuntimeEventPayloads.h"
|
||||
"${APP_DIR}/runtime/events/RuntimeEventQueue.h"
|
||||
"${APP_DIR}/runtime/events/RuntimeEventType.h"
|
||||
"${APP_DIR}/runtime/live/CommittedLiveState.cpp"
|
||||
"${APP_DIR}/runtime/live/CommittedLiveState.h"
|
||||
"${APP_DIR}/runtime/live/RenderStateComposer.cpp"
|
||||
"${APP_DIR}/runtime/live/RenderStateComposer.h"
|
||||
"${APP_DIR}/runtime/live/RuntimeStateLayerModel.cpp"
|
||||
"${APP_DIR}/runtime/live/RuntimeStateLayerModel.h"
|
||||
"${APP_DIR}/runtime/live/RuntimeLiveState.cpp"
|
||||
"${APP_DIR}/runtime/live/RuntimeLiveState.h"
|
||||
"${APP_DIR}/runtime/persistence/PersistenceRequest.h"
|
||||
"${APP_DIR}/runtime/persistence/PersistenceWriter.cpp"
|
||||
"${APP_DIR}/runtime/persistence/PersistenceWriter.h"
|
||||
"${APP_DIR}/runtime/presentation/RuntimeStateJson.cpp"
|
||||
"${APP_DIR}/runtime/presentation/RuntimeStateJson.h"
|
||||
"${APP_DIR}/runtime/presentation/RuntimeStatePresenter.cpp"
|
||||
"${APP_DIR}/runtime/presentation/RuntimeStatePresenter.h"
|
||||
"${APP_DIR}/runtime/snapshot/RenderSnapshotBuilder.cpp"
|
||||
"${APP_DIR}/runtime/snapshot/RenderSnapshotBuilder.h"
|
||||
"${APP_DIR}/runtime/snapshot/RuntimeSnapshotProvider.cpp"
|
||||
"${APP_DIR}/runtime/snapshot/RuntimeSnapshotProvider.h"
|
||||
"${APP_DIR}/runtime/store/LayerStackStore.cpp"
|
||||
"${APP_DIR}/runtime/store/LayerStackStore.h"
|
||||
"${APP_DIR}/runtime/store/RuntimeConfigStore.cpp"
|
||||
"${APP_DIR}/runtime/store/RuntimeConfigStore.h"
|
||||
"${APP_DIR}/runtime/store/RuntimeStore.cpp"
|
||||
"${APP_DIR}/runtime/store/RuntimeStore.h"
|
||||
"${APP_DIR}/runtime/store/RuntimeStoreReadModels.h"
|
||||
"${APP_DIR}/runtime/store/ShaderPackageCatalog.cpp"
|
||||
"${APP_DIR}/runtime/store/ShaderPackageCatalog.h"
|
||||
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeJson.h"
|
||||
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeParameterUtils.h"
|
||||
"${APP_DIR}/runtime/telemetry/HealthTelemetry.cpp"
|
||||
"${APP_DIR}/runtime/telemetry/HealthTelemetry.h"
|
||||
"${APP_DIR}/runtime/telemetry/RuntimeClock.cpp"
|
||||
"${APP_DIR}/runtime/telemetry/RuntimeClock.h"
|
||||
"${APP_DIR}/shader/ShaderCompiler.cpp"
|
||||
"${APP_DIR}/shader/ShaderCompiler.h"
|
||||
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
@@ -114,7 +163,20 @@ set(APP_SOURCES
|
||||
"${APP_DIR}/targetver.h"
|
||||
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
||||
"${APP_DIR}/videoio/VideoIOFormat.h"
|
||||
"${APP_DIR}/videoio/VideoBackend.cpp"
|
||||
"${APP_DIR}/videoio/VideoBackend.h"
|
||||
"${APP_DIR}/videoio/VideoBackendLifecycle.cpp"
|
||||
"${APP_DIR}/videoio/VideoBackendLifecycle.h"
|
||||
"${APP_DIR}/videoio/VideoIOTypes.h"
|
||||
"${APP_DIR}/videoio/OutputProductionController.cpp"
|
||||
"${APP_DIR}/videoio/OutputProductionController.h"
|
||||
"${APP_DIR}/videoio/RenderCadenceController.cpp"
|
||||
"${APP_DIR}/videoio/RenderCadenceController.h"
|
||||
"${APP_DIR}/videoio/RenderOutputQueue.cpp"
|
||||
"${APP_DIR}/videoio/RenderOutputQueue.h"
|
||||
"${APP_DIR}/videoio/SystemOutputFramePool.cpp"
|
||||
"${APP_DIR}/videoio/SystemOutputFramePool.h"
|
||||
"${APP_DIR}/videoio/VideoPlayoutPolicy.h"
|
||||
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
|
||||
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"
|
||||
)
|
||||
@@ -125,11 +187,23 @@ target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/control"
|
||||
"${APP_DIR}/gl"
|
||||
"${APP_DIR}/gl/composite"
|
||||
"${APP_DIR}/gl/frame"
|
||||
"${APP_DIR}/gl/pipeline"
|
||||
"${APP_DIR}/gl/renderer"
|
||||
"${APP_DIR}/gl/shader"
|
||||
"${APP_DIR}/gl/threading"
|
||||
"${APP_DIR}/platform"
|
||||
"${APP_DIR}/runtime"
|
||||
"${APP_DIR}/runtime/coordination"
|
||||
"${APP_DIR}/runtime/events"
|
||||
"${APP_DIR}/runtime/live"
|
||||
"${APP_DIR}/runtime/persistence"
|
||||
"${APP_DIR}/runtime/presentation"
|
||||
"${APP_DIR}/runtime/snapshot"
|
||||
"${APP_DIR}/runtime/store"
|
||||
"${APP_DIR}/runtime/support"
|
||||
"${APP_DIR}/runtime/telemetry"
|
||||
"${APP_DIR}/shader"
|
||||
"${APP_DIR}/videoio"
|
||||
"${APP_DIR}/videoio/decklink"
|
||||
@@ -156,13 +230,14 @@ if(MSVC)
|
||||
endif()
|
||||
|
||||
add_executable(RuntimeJsonTests
|
||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeJsonTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(RuntimeJsonTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/runtime"
|
||||
"${APP_DIR}/runtime/support"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
@@ -173,13 +248,14 @@ enable_testing()
|
||||
add_test(NAME RuntimeJsonTests COMMAND RuntimeJsonTests)
|
||||
|
||||
add_executable(RuntimeClockTests
|
||||
"${APP_DIR}/runtime/RuntimeClock.cpp"
|
||||
"${APP_DIR}/runtime/telemetry/RuntimeClock.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeClockTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(RuntimeClockTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/runtime"
|
||||
"${APP_DIR}/runtime/telemetry"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
@@ -188,15 +264,33 @@ endif()
|
||||
|
||||
add_test(NAME RuntimeClockTests COMMAND RuntimeClockTests)
|
||||
|
||||
add_executable(HealthTelemetryTests
|
||||
"${APP_DIR}/runtime/telemetry/HealthTelemetry.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/HealthTelemetryTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(HealthTelemetryTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/runtime"
|
||||
"${APP_DIR}/runtime/telemetry"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(HealthTelemetryTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME HealthTelemetryTests COMMAND HealthTelemetryTests)
|
||||
|
||||
add_executable(RuntimeParameterUtilsTests
|
||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${APP_DIR}/runtime/RuntimeParameterUtils.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeParameterUtilsTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(RuntimeParameterUtilsTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/runtime"
|
||||
"${APP_DIR}/runtime/support"
|
||||
"${APP_DIR}/shader"
|
||||
)
|
||||
|
||||
@@ -206,6 +300,121 @@ endif()
|
||||
|
||||
add_test(NAME RuntimeParameterUtilsTests COMMAND RuntimeParameterUtilsTests)
|
||||
|
||||
add_executable(RuntimeEventTypeTests
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeEventTypeTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(RuntimeEventTypeTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/runtime"
|
||||
"${APP_DIR}/runtime/events"
|
||||
"${APP_DIR}/runtime/persistence"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(RuntimeEventTypeTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME RuntimeEventTypeTests COMMAND RuntimeEventTypeTests)
|
||||
|
||||
add_executable(RuntimeLiveStateTests
|
||||
"${APP_DIR}/runtime/live/RenderStateComposer.cpp"
|
||||
"${APP_DIR}/runtime/live/RuntimeLiveState.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeLiveStateTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(RuntimeLiveStateTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/runtime"
|
||||
"${APP_DIR}/runtime/live"
|
||||
"${APP_DIR}/runtime/support"
|
||||
"${APP_DIR}/shader"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(RuntimeLiveStateTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME RuntimeLiveStateTests COMMAND RuntimeLiveStateTests)
|
||||
|
||||
add_executable(RuntimeStateLayerModelTests
|
||||
"${APP_DIR}/runtime/live/RuntimeStateLayerModel.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeStateLayerModelTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(RuntimeStateLayerModelTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/runtime"
|
||||
"${APP_DIR}/runtime/live"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(RuntimeStateLayerModelTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME RuntimeStateLayerModelTests COMMAND RuntimeStateLayerModelTests)
|
||||
|
||||
add_executable(PersistenceWriterTests
|
||||
"${APP_DIR}/runtime/persistence/PersistenceWriter.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/PersistenceWriterTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(PersistenceWriterTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/runtime"
|
||||
"${APP_DIR}/runtime/persistence"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(PersistenceWriterTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME PersistenceWriterTests COMMAND PersistenceWriterTests)
|
||||
|
||||
add_executable(RuntimeSubsystemTests
|
||||
"${APP_DIR}/runtime/coordination/RuntimeCoordinator.cpp"
|
||||
"${APP_DIR}/runtime/live/CommittedLiveState.cpp"
|
||||
"${APP_DIR}/runtime/snapshot/RenderSnapshotBuilder.cpp"
|
||||
"${APP_DIR}/runtime/persistence/PersistenceWriter.cpp"
|
||||
"${APP_DIR}/runtime/store/LayerStackStore.cpp"
|
||||
"${APP_DIR}/runtime/store/RuntimeConfigStore.cpp"
|
||||
"${APP_DIR}/runtime/store/RuntimeStore.cpp"
|
||||
"${APP_DIR}/runtime/store/ShaderPackageCatalog.cpp"
|
||||
"${APP_DIR}/runtime/presentation/RuntimeStateJson.cpp"
|
||||
"${APP_DIR}/runtime/presentation/RuntimeStatePresenter.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
|
||||
"${APP_DIR}/runtime/telemetry/HealthTelemetry.cpp"
|
||||
"${APP_DIR}/runtime/telemetry/RuntimeClock.cpp"
|
||||
"${APP_DIR}/shader/ShaderCompiler.cpp"
|
||||
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeSubsystemTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(RuntimeSubsystemTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/platform"
|
||||
"${APP_DIR}/runtime"
|
||||
"${APP_DIR}/runtime/coordination"
|
||||
"${APP_DIR}/runtime/events"
|
||||
"${APP_DIR}/runtime/live"
|
||||
"${APP_DIR}/runtime/persistence"
|
||||
"${APP_DIR}/runtime/presentation"
|
||||
"${APP_DIR}/runtime/snapshot"
|
||||
"${APP_DIR}/runtime/store"
|
||||
"${APP_DIR}/runtime/support"
|
||||
"${APP_DIR}/runtime/telemetry"
|
||||
"${APP_DIR}/shader"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(RuntimeSubsystemTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME RuntimeSubsystemTests COMMAND RuntimeSubsystemTests)
|
||||
|
||||
add_executable(Std140BufferTests
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/Std140BufferTests.cpp"
|
||||
)
|
||||
@@ -222,8 +431,27 @@ endif()
|
||||
|
||||
add_test(NAME Std140BufferTests COMMAND Std140BufferTests)
|
||||
|
||||
add_executable(RenderCommandQueueTests
|
||||
"${APP_DIR}/gl/threading/RenderCommandQueue.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCommandQueueTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(RenderCommandQueueTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/gl"
|
||||
"${APP_DIR}/gl/threading"
|
||||
"${APP_DIR}/videoio"
|
||||
"${APP_DIR}/videoio/decklink"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(RenderCommandQueueTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME RenderCommandQueueTests COMMAND RenderCommandQueueTests)
|
||||
|
||||
add_executable(ShaderPackageRegistryTests
|
||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderPackageRegistryTests.cpp"
|
||||
)
|
||||
@@ -231,6 +459,7 @@ add_executable(ShaderPackageRegistryTests
|
||||
target_include_directories(ShaderPackageRegistryTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/runtime"
|
||||
"${APP_DIR}/runtime/support"
|
||||
"${APP_DIR}/shader"
|
||||
)
|
||||
|
||||
@@ -241,7 +470,7 @@ endif()
|
||||
add_test(NAME ShaderPackageRegistryTests COMMAND ShaderPackageRegistryTests)
|
||||
|
||||
add_executable(ShaderSlangValidationTests
|
||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||
"${APP_DIR}/shader/ShaderCompiler.cpp"
|
||||
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderSlangValidationTests.cpp"
|
||||
@@ -251,6 +480,7 @@ target_include_directories(ShaderSlangValidationTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/platform"
|
||||
"${APP_DIR}/runtime"
|
||||
"${APP_DIR}/runtime/support"
|
||||
"${APP_DIR}/shader"
|
||||
)
|
||||
|
||||
@@ -319,6 +549,89 @@ endif()
|
||||
|
||||
add_test(NAME VideoPlayoutSchedulerTests COMMAND VideoPlayoutSchedulerTests)
|
||||
|
||||
add_executable(OutputProductionControllerTests
|
||||
"${APP_DIR}/videoio/OutputProductionController.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/OutputProductionControllerTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(OutputProductionControllerTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/videoio"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(OutputProductionControllerTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME OutputProductionControllerTests COMMAND OutputProductionControllerTests)
|
||||
|
||||
add_executable(RenderOutputQueueTests
|
||||
"${APP_DIR}/videoio/RenderOutputQueue.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderOutputQueueTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(RenderOutputQueueTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/videoio"
|
||||
"${APP_DIR}/videoio/decklink"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(RenderOutputQueueTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME RenderOutputQueueTests COMMAND RenderOutputQueueTests)
|
||||
|
||||
add_executable(RenderCadenceControllerTests
|
||||
"${APP_DIR}/videoio/RenderCadenceController.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceControllerTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(RenderCadenceControllerTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/videoio"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(RenderCadenceControllerTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME RenderCadenceControllerTests COMMAND RenderCadenceControllerTests)
|
||||
|
||||
add_executable(SystemOutputFramePoolTests
|
||||
"${APP_DIR}/videoio/SystemOutputFramePool.cpp"
|
||||
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/SystemOutputFramePoolTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(SystemOutputFramePoolTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/videoio"
|
||||
"${APP_DIR}/videoio/decklink"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(SystemOutputFramePoolTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME SystemOutputFramePoolTests COMMAND SystemOutputFramePoolTests)
|
||||
|
||||
add_executable(VideoBackendLifecycleTests
|
||||
"${APP_DIR}/videoio/VideoBackendLifecycle.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoBackendLifecycleTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(VideoBackendLifecycleTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/videoio"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(VideoBackendLifecycleTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME VideoBackendLifecycleTests COMMAND VideoBackendLifecycleTests)
|
||||
|
||||
add_executable(VideoIODeviceFakeTests
|
||||
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIODeviceFakeTests.cpp"
|
||||
|
||||
@@ -49,7 +49,7 @@ Configure and build the native app:
|
||||
|
||||
```powershell
|
||||
cmake --preset vs2022-x64-debug
|
||||
cmake --build --preset build-debug
|
||||
cmake --build --preset build-debug --parallel
|
||||
```
|
||||
|
||||
Build the React control UI:
|
||||
@@ -80,7 +80,7 @@ npm ci
|
||||
npm run build
|
||||
cd ..
|
||||
cmake --preset vs2022-x64-release
|
||||
cmake --build --preset build-release
|
||||
cmake --build --preset build-release --parallel
|
||||
cmake --install build/vs2022-x64-release --config Release --prefix dist/VideoShader
|
||||
```
|
||||
|
||||
@@ -114,7 +114,7 @@ Compress-Archive -Path dist/VideoShader/* -DestinationPath dist/VideoShader.zip
|
||||
Run native tests:
|
||||
|
||||
```powershell
|
||||
cmake --build --preset build-debug --target RUN_TESTS
|
||||
cmake --build --preset build-debug --target RUN_TESTS --parallel
|
||||
```
|
||||
|
||||
Run the UI production build check:
|
||||
@@ -277,4 +277,4 @@ If `SLANG_ROOT` is not set, the workflow falls back to the repo-local default un
|
||||
- Mipmapping for shader-declared textures
|
||||
- Anotate included shaders
|
||||
- allow 3 vector exposed controls
|
||||
- add nearest sampling to the extra shader pass
|
||||
- add nearest sampling to the extra shader pass
|
||||
|
||||
@@ -1,47 +1,3 @@
|
||||
/* -LICENSE-START-
|
||||
** Copyright (c) 2012 Blackmagic Design
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person or organization
|
||||
** obtaining a copy of the software and accompanying documentation (the
|
||||
** "Software") to use, reproduce, display, distribute, sub-license, execute,
|
||||
** and transmit the Software, and to prepare derivative works of the Software,
|
||||
** and to permit third-parties to whom the Software is furnished to do so, in
|
||||
** accordance with:
|
||||
**
|
||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
||||
** Agreement for the Software Development Kit ("EULA") available at
|
||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
||||
**
|
||||
** (2) if the Software is obtained from any third party, such licensing terms
|
||||
** as notified by that third party,
|
||||
**
|
||||
** and all subject to the following:
|
||||
**
|
||||
** (3) the copyright notices in the Software and this entire statement,
|
||||
** including the above license grant, this restriction and the following
|
||||
** disclaimer, must be included in all copies of the Software, in whole or in
|
||||
** part, and all derivative works of the Software, unless such copies or
|
||||
** derivative works are solely in the form of machine-executable object code
|
||||
** generated by a source language processor.
|
||||
**
|
||||
** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
** DEALINGS IN THE SOFTWARE.
|
||||
**
|
||||
** A copy of the Software is available free of charge at
|
||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
||||
**
|
||||
** -LICENSE-END-
|
||||
*/
|
||||
//
|
||||
// LoopThroughWithOpenGLCompositing.cpp
|
||||
// LoopThroughWithOpenGLCompositing
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "resource.h"
|
||||
#include "OpenGLComposite.h"
|
||||
@@ -478,7 +434,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
}
|
||||
|
||||
// Deselect the current rendering context and delete it
|
||||
wglMakeCurrent(hDC, NULL);
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
wglDeleteContext(hRC);
|
||||
|
||||
// Tell the application to terminate after the window is gone
|
||||
@@ -530,15 +486,12 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
|
||||
if (!sInteractiveResize && pOpenGLComposite)
|
||||
{
|
||||
wglMakeCurrent(hDC, hRC);
|
||||
pOpenGLComposite->paintGL(true);
|
||||
wglMakeCurrent( NULL, NULL );
|
||||
RaiseStatusControls(sStatusStrip);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
wglMakeCurrent( NULL, NULL );
|
||||
ShowUnhandledExceptionMessage("Paint failed inside the OpenGL runtime.");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,47 +1,3 @@
|
||||
/* -LICENSE-START-
|
||||
** Copyright (c) 2012 Blackmagic Design
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person or organization
|
||||
** obtaining a copy of the software and accompanying documentation (the
|
||||
** "Software") to use, reproduce, display, distribute, sub-license, execute,
|
||||
** and transmit the Software, and to prepare derivative works of the Software,
|
||||
** and to permit third-parties to whom the Software is furnished to do so, in
|
||||
** accordance with:
|
||||
**
|
||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
||||
** Agreement for the Software Development Kit ("EULA") available at
|
||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
||||
**
|
||||
** (2) if the Software is obtained from any third party, such licensing terms
|
||||
** as notified by that third party,
|
||||
**
|
||||
** and all subject to the following:
|
||||
**
|
||||
** (3) the copyright notices in the Software and this entire statement,
|
||||
** including the above license grant, this restriction and the following
|
||||
** disclaimer, must be included in all copies of the Software, in whole or in
|
||||
** part, and all derivative works of the Software, unless such copies or
|
||||
** derivative works are solely in the form of machine-executable object code
|
||||
** generated by a source language processor.
|
||||
**
|
||||
** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
** DEALINGS IN THE SOFTWARE.
|
||||
**
|
||||
** A copy of the Software is available free of charge at
|
||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
||||
**
|
||||
** -LICENSE-END-
|
||||
*/
|
||||
//
|
||||
// LoopThroughWithOpenGLCompositing.h
|
||||
// LoopThroughWithOpenGLCompositing
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2013
|
||||
VisualStudioVersion = 12.0.21005.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LoopThroughWithOpenGLCompositing", "LoopThroughWithOpenGLCompositing.vcxproj", "{92C79085-CA51-4008-95DB-5403D2E19885}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Win32 = Release|Win32
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{92C79085-CA51-4008-95DB-5403D2E19885}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{92C79085-CA51-4008-95DB-5403D2E19885}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{92C79085-CA51-4008-95DB-5403D2E19885}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{92C79085-CA51-4008-95DB-5403D2E19885}.Debug|x64.Build.0 = Debug|x64
|
||||
{92C79085-CA51-4008-95DB-5403D2E19885}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{92C79085-CA51-4008-95DB-5403D2E19885}.Release|Win32.Build.0 = Release|Win32
|
||||
{92C79085-CA51-4008-95DB-5403D2E19885}.Release|x64.ActiveCfg = Release|x64
|
||||
{92C79085-CA51-4008-95DB-5403D2E19885}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,245 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{92C79085-CA51-4008-95DB-5403D2E19885}</ProjectGuid>
|
||||
<RootNamespace>LoopThroughWithOpenGLCompositing</RootNamespace>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<_ProjectFileVersion>12.0.21005.1</_ProjectFileVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
|
||||
<IntDir>$(Configuration)\</IntDir>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(Platform)\$(Configuration)\</IntDir>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
|
||||
<IntDir>$(Configuration)\</IntDir>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(Platform)\$(Configuration)\</IntDir>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<PrecompiledHeader />
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<TargetMachine>MachineX86</TargetMachine>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Midl>
|
||||
<TargetEnvironment>X64</TargetEnvironment>
|
||||
</Midl>
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<PrecompiledHeader />
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<TargetMachine>MachineX64</TargetMachine>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<PrecompiledHeader />
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<TargetMachine>MachineX86</TargetMachine>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Midl>
|
||||
<TargetEnvironment>X64</TargetEnvironment>
|
||||
</Midl>
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<PrecompiledHeader />
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<TargetMachine>MachineX64</TargetMachine>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="gl\renderer\GLExtensions.cpp" />
|
||||
<ClCompile Include="LoopThroughWithOpenGLCompositing.cpp" />
|
||||
<ClCompile Include="gl\OpenGLComposite.cpp" />
|
||||
<ClCompile Include="gl\pipeline\OpenGLRenderPass.cpp" />
|
||||
<ClCompile Include="gl\pipeline\OpenGLRenderPipeline.cpp" />
|
||||
<ClCompile Include="gl\renderer\OpenGLRenderer.cpp" />
|
||||
<ClCompile Include="gl\renderer\RenderTargetPool.cpp" />
|
||||
<ClCompile Include="gl\shader\OpenGLShaderPrograms.cpp" />
|
||||
<ClCompile Include="gl\pipeline\PngScreenshotWriter.cpp" />
|
||||
<ClCompile Include="gl\shader\ShaderBuildQueue.cpp" />
|
||||
<ClCompile Include="gl\pipeline\TemporalHistoryBuffers.cpp" />
|
||||
<ClCompile Include="gl\pipeline\OpenGLVideoIOBridge.cpp" />
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="videoio\decklink\DeckLinkAPI_i.c" />
|
||||
<ClCompile Include="control\RuntimeServices.cpp" />
|
||||
<ClCompile Include="videoio\decklink\DeckLinkSession.cpp" />
|
||||
<ClCompile Include="videoio\decklink\DeckLinkVideoIOFormat.cpp" />
|
||||
<ClCompile Include="runtime\RuntimeClock.cpp" />
|
||||
<ClCompile Include="videoio\VideoIOFormat.cpp" />
|
||||
<ClCompile Include="videoio\VideoPlayoutScheduler.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="gl\renderer\GLExtensions.h" />
|
||||
<ClInclude Include="LoopThroughWithOpenGLCompositing.h" />
|
||||
<ClInclude Include="gl\OpenGLComposite.h" />
|
||||
<ClInclude Include="gl\pipeline\OpenGLRenderPass.h" />
|
||||
<ClInclude Include="gl\pipeline\OpenGLRenderPipeline.h" />
|
||||
<ClInclude Include="gl\pipeline\RenderPassDescriptor.h" />
|
||||
<ClInclude Include="gl\renderer\OpenGLRenderer.h" />
|
||||
<ClInclude Include="gl\renderer\RenderTargetPool.h" />
|
||||
<ClInclude Include="gl\shader\OpenGLShaderPrograms.h" />
|
||||
<ClInclude Include="gl\pipeline\PngScreenshotWriter.h" />
|
||||
<ClInclude Include="gl\shader\ShaderBuildQueue.h" />
|
||||
<ClInclude Include="gl\pipeline\TemporalHistoryBuffers.h" />
|
||||
<ClInclude Include="gl\pipeline\OpenGLVideoIOBridge.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
<ClInclude Include="targetver.h" />
|
||||
<ClInclude Include="control\RuntimeServices.h" />
|
||||
<ClInclude Include="videoio\decklink\DeckLinkSession.h" />
|
||||
<ClInclude Include="videoio\decklink\DeckLinkVideoIOFormat.h" />
|
||||
<ClInclude Include="runtime\RuntimeClock.h" />
|
||||
<ClInclude Include="videoio\VideoIOFormat.h" />
|
||||
<ClInclude Include="videoio\VideoIOTypes.h" />
|
||||
<ClInclude Include="videoio\VideoPlayoutScheduler.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="LoopThroughWithOpenGLCompositing.ico" />
|
||||
<Image Include="small.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="video_effect.slang" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="LoopThroughWithOpenGLCompositing.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\include\DeckLinkAPI.idl" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -1,176 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="DeckLink API">
|
||||
<UniqueIdentifier>{1eab21d6-58f8-49e0-929b-8a4482e04756}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="gl\renderer\GLExtensions.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="LoopThroughWithOpenGLCompositing.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gl\OpenGLComposite.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gl\pipeline\OpenGLRenderPass.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gl\pipeline\OpenGLRenderPipeline.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gl\renderer\OpenGLRenderer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gl\renderer\RenderTargetPool.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gl\shader\OpenGLShaderPrograms.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gl\pipeline\PngScreenshotWriter.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gl\shader\ShaderBuildQueue.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gl\pipeline\TemporalHistoryBuffers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gl\pipeline\OpenGLVideoIOBridge.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="videoio\decklink\DeckLinkAPI_i.c">
|
||||
<Filter>DeckLink API</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="control\RuntimeServices.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="videoio\decklink\DeckLinkSession.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="videoio\decklink\DeckLinkVideoIOFormat.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="runtime\RuntimeClock.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="videoio\VideoIOFormat.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="videoio\VideoPlayoutScheduler.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="gl\renderer\GLExtensions.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="LoopThroughWithOpenGLCompositing.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gl\OpenGLComposite.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gl\pipeline\OpenGLRenderPass.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gl\pipeline\OpenGLRenderPipeline.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gl\pipeline\RenderPassDescriptor.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gl\renderer\OpenGLRenderer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gl\renderer\RenderTargetPool.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gl\shader\OpenGLShaderPrograms.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gl\pipeline\PngScreenshotWriter.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gl\shader\ShaderBuildQueue.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gl\pipeline\TemporalHistoryBuffers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gl\pipeline\OpenGLVideoIOBridge.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="stdafx.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="targetver.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="control\RuntimeServices.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="videoio\decklink\DeckLinkSession.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="videoio\decklink\DeckLinkVideoIOFormat.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="runtime\RuntimeClock.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="videoio\VideoIOFormat.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="videoio\VideoIOTypes.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="videoio\VideoPlayoutScheduler.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="LoopThroughWithOpenGLCompositing.ico">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
<Image Include="small.ico">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="LoopThroughWithOpenGLCompositing.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="..\..\include\DeckLinkAPI.idl">
|
||||
<Filter>DeckLink API</Filter>
|
||||
</Midl>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="video_effect.slang">
|
||||
<Filter>Resource Files</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,343 @@
|
||||
#include "ControlServices.h"
|
||||
|
||||
#include "ControlServer.h"
|
||||
#include "OscServer.h"
|
||||
#include "RuntimeControlBridge.h"
|
||||
#include "RuntimeEventDispatcher.h"
|
||||
#include "RuntimeStore.h"
|
||||
#include <windows.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto kCompatibilityPollFallbackInterval = std::chrono::milliseconds(250);
|
||||
}
|
||||
|
||||
ControlServices::ControlServices(RuntimeEventDispatcher& runtimeEventDispatcher) :
|
||||
mControlServer(std::make_unique<ControlServer>()),
|
||||
mOscServer(std::make_unique<OscServer>()),
|
||||
mRuntimeEventDispatcher(runtimeEventDispatcher),
|
||||
mPollRunning(false)
|
||||
{
|
||||
}
|
||||
|
||||
ControlServices::~ControlServices()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool ControlServices::Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error)
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (!StartControlServicesBoundary(composite, runtimeStore, *this, *mControlServer, *mOscServer, error))
|
||||
{
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ControlServices::BeginPolling(RuntimeCoordinator& runtimeCoordinator)
|
||||
{
|
||||
StartPolling(runtimeCoordinator);
|
||||
}
|
||||
|
||||
void ControlServices::Stop()
|
||||
{
|
||||
StopPolling();
|
||||
|
||||
if (mOscServer)
|
||||
mOscServer->Stop();
|
||||
|
||||
if (mControlServer)
|
||||
mControlServer->Stop();
|
||||
}
|
||||
|
||||
void ControlServices::BroadcastState()
|
||||
{
|
||||
if (mControlServer)
|
||||
mControlServer->BroadcastState();
|
||||
}
|
||||
|
||||
void ControlServices::RequestBroadcastState()
|
||||
{
|
||||
PublishRuntimeStateBroadcastRequested("control-service-request");
|
||||
|
||||
if (mControlServer)
|
||||
mControlServer->RequestBroadcastState();
|
||||
}
|
||||
|
||||
bool ControlServices::QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
|
||||
{
|
||||
(void)error;
|
||||
|
||||
PendingOscUpdate update;
|
||||
update.layerKey = layerKey;
|
||||
update.parameterKey = parameterKey;
|
||||
update.valueJson = valueJson;
|
||||
|
||||
const std::string routeKey = layerKey + "\n" + parameterKey;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
||||
mPendingOscUpdates[routeKey] = std::move(update);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ControlServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error)
|
||||
{
|
||||
appliedUpdates.clear();
|
||||
|
||||
std::map<std::string, PendingOscUpdate> pending;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
||||
if (mPendingOscUpdates.empty())
|
||||
return true;
|
||||
pending.swap(mPendingOscUpdates);
|
||||
}
|
||||
|
||||
for (const auto& entry : pending)
|
||||
{
|
||||
JsonValue targetValue;
|
||||
std::string parseError;
|
||||
if (!ParseJson(entry.second.valueJson, targetValue, parseError))
|
||||
{
|
||||
OutputDebugStringA(("OSC queued value parse failed: " + parseError + "\n").c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
AppliedOscUpdate appliedUpdate;
|
||||
appliedUpdate.routeKey = entry.first;
|
||||
appliedUpdate.layerKey = entry.second.layerKey;
|
||||
appliedUpdate.parameterKey = entry.second.parameterKey;
|
||||
appliedUpdate.targetValue = targetValue;
|
||||
appliedUpdates.push_back(std::move(appliedUpdate));
|
||||
PublishOscValueReceived(entry.second, entry.first);
|
||||
}
|
||||
|
||||
(void)error;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ControlServices::QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error)
|
||||
{
|
||||
(void)error;
|
||||
|
||||
PendingOscCommit commit;
|
||||
commit.routeKey = routeKey;
|
||||
commit.layerKey = layerKey;
|
||||
commit.parameterKey = parameterKey;
|
||||
commit.value = value;
|
||||
commit.generation = generation;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
||||
mPendingOscCommits[routeKey] = std::move(commit);
|
||||
}
|
||||
WakePolling();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ControlServices::ClearOscState()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
||||
mPendingOscUpdates.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
||||
mPendingOscCommits.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
||||
mCompletedOscCommits.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ControlServices::ClearOscStateForLayerKey(const std::string& layerKey)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
||||
for (auto it = mPendingOscUpdates.begin(); it != mPendingOscUpdates.end();)
|
||||
{
|
||||
if (it->second.layerKey == layerKey)
|
||||
it = mPendingOscUpdates.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
||||
for (auto it = mPendingOscCommits.begin(); it != mPendingOscCommits.end();)
|
||||
{
|
||||
if (it->second.layerKey == layerKey)
|
||||
it = mPendingOscCommits.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
||||
for (auto it = mCompletedOscCommits.begin(); it != mCompletedOscCommits.end();)
|
||||
{
|
||||
if (it->routeKey.rfind(layerKey + "\n", 0) == 0)
|
||||
it = mCompletedOscCommits.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControlServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
|
||||
{
|
||||
completedCommits.clear();
|
||||
|
||||
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
||||
if (mCompletedOscCommits.empty())
|
||||
return;
|
||||
|
||||
completedCommits.swap(mCompletedOscCommits);
|
||||
}
|
||||
|
||||
void ControlServices::StartPolling(RuntimeCoordinator& runtimeCoordinator)
|
||||
{
|
||||
if (mPollRunning.exchange(true))
|
||||
return;
|
||||
|
||||
mPollThread = std::thread([this, &runtimeCoordinator]() { PollLoop(runtimeCoordinator); });
|
||||
}
|
||||
|
||||
void ControlServices::StopPolling()
|
||||
{
|
||||
if (!mPollRunning.exchange(false))
|
||||
return;
|
||||
|
||||
WakePolling();
|
||||
if (mPollThread.joinable())
|
||||
mPollThread.join();
|
||||
}
|
||||
|
||||
void ControlServices::PollLoop(RuntimeCoordinator& runtimeCoordinator)
|
||||
{
|
||||
while (mPollRunning)
|
||||
{
|
||||
std::map<std::string, PendingOscCommit> pendingCommits;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
||||
pendingCommits.swap(mPendingOscCommits);
|
||||
}
|
||||
for (const auto& entry : pendingCommits)
|
||||
{
|
||||
PublishOscCommitRequested(entry.second);
|
||||
const RuntimeCoordinatorResult result = runtimeCoordinator.CommitOscParameterByControlKey(
|
||||
entry.second.layerKey,
|
||||
entry.second.parameterKey,
|
||||
entry.second.value);
|
||||
if (result.accepted)
|
||||
{
|
||||
CompletedOscCommit completedCommit;
|
||||
completedCommit.routeKey = entry.second.routeKey;
|
||||
completedCommit.generation = entry.second.generation;
|
||||
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
||||
mCompletedOscCommits.push_back(std::move(completedCommit));
|
||||
PublishOscOverlaySettled(entry.second);
|
||||
}
|
||||
else if (!result.errorMessage.empty())
|
||||
{
|
||||
OutputDebugStringA(("OSC commit failed: " + result.errorMessage + "\n").c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool registryChanged = false;
|
||||
const RuntimeCoordinatorResult pollResult = runtimeCoordinator.PollRuntimeStoreChanges(registryChanged);
|
||||
if (pollResult.compileStatusChanged && !pollResult.compileStatusSucceeded && !pollResult.compileStatusMessage.empty())
|
||||
OutputDebugStringA(("Runtime poll failed: " + pollResult.compileStatusMessage + "\n").c_str());
|
||||
|
||||
std::unique_lock<std::mutex> wakeLock(mPollWakeMutex);
|
||||
mPollWakeCondition.wait_for(wakeLock, kCompatibilityPollFallbackInterval, [this]() {
|
||||
return !mPollRunning.load() || mPollWakeRequested;
|
||||
});
|
||||
mPollWakeRequested = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ControlServices::WakePolling()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPollWakeMutex);
|
||||
mPollWakeRequested = true;
|
||||
}
|
||||
mPollWakeCondition.notify_one();
|
||||
}
|
||||
|
||||
void ControlServices::PublishRuntimeStateBroadcastRequested(const std::string& reason)
|
||||
{
|
||||
try
|
||||
{
|
||||
RuntimeStateBroadcastRequestedEvent event;
|
||||
event.reason = reason;
|
||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices"))
|
||||
OutputDebugStringA("RuntimeStateBroadcastRequested event publish failed.\n");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OutputDebugStringA("RuntimeStateBroadcastRequested event publish threw.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ControlServices::PublishOscValueReceived(const PendingOscUpdate& update, const std::string& routeKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
OscValueReceivedEvent event;
|
||||
event.routeKey = routeKey;
|
||||
event.layerKey = update.layerKey;
|
||||
event.parameterKey = update.parameterKey;
|
||||
event.valueJson = update.valueJson;
|
||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices"))
|
||||
OutputDebugStringA("OscValueReceived event publish failed.\n");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OutputDebugStringA("OscValueReceived event publish threw.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ControlServices::PublishOscCommitRequested(const PendingOscCommit& commit)
|
||||
{
|
||||
try
|
||||
{
|
||||
OscCommitRequestedEvent event;
|
||||
event.routeKey = commit.routeKey;
|
||||
event.layerKey = commit.layerKey;
|
||||
event.parameterKey = commit.parameterKey;
|
||||
event.valueJson = SerializeJson(commit.value, false);
|
||||
event.generation = commit.generation;
|
||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices"))
|
||||
OutputDebugStringA("OscCommitRequested event publish failed.\n");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OutputDebugStringA("OscCommitRequested event publish threw.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ControlServices::PublishOscOverlaySettled(const PendingOscCommit& commit)
|
||||
{
|
||||
try
|
||||
{
|
||||
OscOverlayEvent event;
|
||||
event.routeKey = commit.routeKey;
|
||||
event.layerKey = commit.layerKey;
|
||||
event.parameterKey = commit.parameterKey;
|
||||
event.generation = commit.generation;
|
||||
event.settled = true;
|
||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices"))
|
||||
OutputDebugStringA("OscOverlaySettled event publish failed.\n");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OutputDebugStringA("OscOverlaySettled event publish threw.\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
#include "RuntimeCoordinator.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
class ControlServer;
|
||||
class OpenGLComposite;
|
||||
class OscServer;
|
||||
class RuntimeEventDispatcher;
|
||||
class RuntimeStore;
|
||||
|
||||
class ControlServices
|
||||
{
|
||||
public:
|
||||
struct AppliedOscUpdate
|
||||
{
|
||||
std::string routeKey;
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
JsonValue targetValue;
|
||||
};
|
||||
|
||||
struct CompletedOscCommit
|
||||
{
|
||||
std::string routeKey;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
explicit ControlServices(RuntimeEventDispatcher& runtimeEventDispatcher);
|
||||
~ControlServices();
|
||||
|
||||
bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);
|
||||
void BeginPolling(RuntimeCoordinator& runtimeCoordinator);
|
||||
void Stop();
|
||||
void BroadcastState();
|
||||
void RequestBroadcastState();
|
||||
bool QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error);
|
||||
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error);
|
||||
bool QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error);
|
||||
void ClearOscState();
|
||||
void ClearOscStateForLayerKey(const std::string& layerKey);
|
||||
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
|
||||
|
||||
private:
|
||||
struct PendingOscUpdate
|
||||
{
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
std::string valueJson;
|
||||
};
|
||||
|
||||
struct PendingOscCommit
|
||||
{
|
||||
std::string routeKey;
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
JsonValue value;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
void StartPolling(RuntimeCoordinator& runtimeCoordinator);
|
||||
void StopPolling();
|
||||
void PollLoop(RuntimeCoordinator& runtimeCoordinator);
|
||||
void WakePolling();
|
||||
void PublishRuntimeStateBroadcastRequested(const std::string& reason);
|
||||
void PublishOscValueReceived(const PendingOscUpdate& update, const std::string& routeKey);
|
||||
void PublishOscCommitRequested(const PendingOscCommit& commit);
|
||||
void PublishOscOverlaySettled(const PendingOscCommit& commit);
|
||||
|
||||
std::unique_ptr<ControlServer> mControlServer;
|
||||
std::unique_ptr<OscServer> mOscServer;
|
||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
||||
std::thread mPollThread;
|
||||
std::atomic<bool> mPollRunning;
|
||||
std::mutex mPollWakeMutex;
|
||||
std::condition_variable mPollWakeCondition;
|
||||
bool mPollWakeRequested = false;
|
||||
std::mutex mPendingOscMutex;
|
||||
std::map<std::string, PendingOscUpdate> mPendingOscUpdates;
|
||||
std::mutex mPendingOscCommitMutex;
|
||||
std::map<std::string, PendingOscCommit> mPendingOscCommits;
|
||||
std::mutex mCompletedOscCommitMutex;
|
||||
std::vector<CompletedOscCommit> mCompletedOscCommits;
|
||||
};
|
||||
@@ -1,15 +1,15 @@
|
||||
#include "RuntimeControlBridge.h"
|
||||
|
||||
#include "ControlServices.h"
|
||||
#include "ControlServer.h"
|
||||
#include "OpenGLComposite.h"
|
||||
#include "OscServer.h"
|
||||
#include "RuntimeHost.h"
|
||||
#include "RuntimeServices.h"
|
||||
#include "RuntimeStore.h"
|
||||
|
||||
bool StartRuntimeControlServices(
|
||||
bool StartControlServicesBoundary(
|
||||
OpenGLComposite& composite,
|
||||
RuntimeHost& runtimeHost,
|
||||
RuntimeServices& runtimeServices,
|
||||
RuntimeStore& runtimeStore,
|
||||
ControlServices& controlServices,
|
||||
ControlServer& controlServer,
|
||||
OscServer& oscServer,
|
||||
std::string& error)
|
||||
@@ -38,15 +38,16 @@ bool StartRuntimeControlServices(
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!controlServer.Start(runtimeHost.GetUiRoot(), runtimeHost.GetDocsRoot(), runtimeHost.GetServerPort(), callbacks, error))
|
||||
if (!controlServer.Start(runtimeStore.GetRuntimeUiRoot(), runtimeStore.GetRuntimeDocsRoot(), runtimeStore.GetConfiguredControlServerPort(), callbacks, error))
|
||||
return false;
|
||||
runtimeHost.SetServerPort(controlServer.GetPort());
|
||||
runtimeStore.SetBoundControlServerPort(controlServer.GetPort());
|
||||
|
||||
OscServer::Callbacks oscCallbacks;
|
||||
oscCallbacks.updateParameter = [&runtimeServices](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) {
|
||||
return runtimeServices.QueueOscUpdate(layerKey, parameterKey, valueJson, actionError);
|
||||
oscCallbacks.updateParameter = [&controlServices](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) {
|
||||
return controlServices.QueueOscUpdate(layerKey, parameterKey, valueJson, actionError);
|
||||
};
|
||||
if (runtimeHost.GetOscPort() > 0 && !oscServer.Start(runtimeHost.GetOscBindAddress(), runtimeHost.GetOscPort(), oscCallbacks, error))
|
||||
if (runtimeStore.GetConfiguredOscPort() > 0 &&
|
||||
!oscServer.Start(runtimeStore.GetConfiguredOscBindAddress(), runtimeStore.GetConfiguredOscPort(), oscCallbacks, error))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
#include <string>
|
||||
|
||||
class ControlServer;
|
||||
class ControlServices;
|
||||
class OpenGLComposite;
|
||||
class OscServer;
|
||||
class RuntimeHost;
|
||||
class RuntimeServices;
|
||||
class RuntimeStore;
|
||||
|
||||
bool StartRuntimeControlServices(
|
||||
bool StartControlServicesBoundary(
|
||||
OpenGLComposite& composite,
|
||||
RuntimeHost& runtimeHost,
|
||||
RuntimeServices& runtimeServices,
|
||||
RuntimeStore& runtimeStore,
|
||||
ControlServices& controlServices,
|
||||
ControlServer& controlServer,
|
||||
OscServer& oscServer,
|
||||
std::string& error);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "RuntimeServiceLiveBridge.h"
|
||||
|
||||
#include "RenderEngine.h"
|
||||
#include "RuntimeServices.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
void DrainServiceEvents(RuntimeServices& runtimeServices, RenderEngine& renderEngine)
|
||||
{
|
||||
std::vector<RuntimeServices::AppliedOscUpdate> appliedOscUpdates;
|
||||
std::vector<RuntimeServices::CompletedOscCommit> completedOscCommits;
|
||||
|
||||
std::string oscError;
|
||||
if (!runtimeServices.ApplyPendingOscUpdates(appliedOscUpdates, oscError) && !oscError.empty())
|
||||
OutputDebugStringA(("OSC apply failed: " + oscError + "\n").c_str());
|
||||
runtimeServices.ConsumeCompletedOscCommits(completedOscCommits);
|
||||
|
||||
std::vector<RenderEngine::OscOverlayUpdate> overlayUpdates;
|
||||
overlayUpdates.reserve(appliedOscUpdates.size());
|
||||
for (const RuntimeServices::AppliedOscUpdate& update : appliedOscUpdates)
|
||||
{
|
||||
overlayUpdates.push_back({ update.routeKey, update.layerKey, update.parameterKey, update.targetValue });
|
||||
}
|
||||
|
||||
std::vector<RenderEngine::OscOverlayCommitCompletion> overlayCommitCompletions;
|
||||
overlayCommitCompletions.reserve(completedOscCommits.size());
|
||||
for (const RuntimeServices::CompletedOscCommit& completedCommit : completedOscCommits)
|
||||
{
|
||||
overlayCommitCompletions.push_back({ completedCommit.routeKey, completedCommit.generation });
|
||||
}
|
||||
|
||||
renderEngine.UpdateOscOverlayState(overlayUpdates, overlayCommitCompletions);
|
||||
}
|
||||
|
||||
void QueueServiceCommitRequests(
|
||||
RuntimeServices& runtimeServices,
|
||||
const std::vector<RenderEngine::OscOverlayCommitRequest>& commitRequests)
|
||||
{
|
||||
for (const RenderEngine::OscOverlayCommitRequest& commitRequest : commitRequests)
|
||||
{
|
||||
std::string commitError;
|
||||
if (!runtimeServices.QueueOscCommit(
|
||||
commitRequest.routeKey,
|
||||
commitRequest.layerKey,
|
||||
commitRequest.parameterKey,
|
||||
commitRequest.value,
|
||||
commitRequest.generation,
|
||||
commitError) &&
|
||||
!commitError.empty())
|
||||
{
|
||||
OutputDebugStringA(("OSC commit queue failed: " + commitError + "\n").c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeServiceLiveBridge::PrepareLiveRenderFrameState(
|
||||
RuntimeServices& runtimeServices,
|
||||
RenderEngine& renderEngine,
|
||||
const RenderFrameInput& input,
|
||||
RenderFrameState& frameState)
|
||||
{
|
||||
DrainServiceEvents(runtimeServices, renderEngine);
|
||||
|
||||
std::vector<RenderEngine::OscOverlayCommitRequest> commitRequests;
|
||||
const bool resolved = renderEngine.ResolveRenderFrameState(input, &commitRequests, frameState);
|
||||
|
||||
QueueServiceCommitRequests(runtimeServices, commitRequests);
|
||||
return resolved;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "RenderFrameState.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
class RenderEngine;
|
||||
class RuntimeServices;
|
||||
|
||||
class RuntimeServiceLiveBridge
|
||||
{
|
||||
public:
|
||||
static bool PrepareLiveRenderFrameState(
|
||||
RuntimeServices& runtimeServices,
|
||||
RenderEngine& renderEngine,
|
||||
const RenderFrameInput& input,
|
||||
RenderFrameState& frameState);
|
||||
};
|
||||
@@ -1,18 +1,9 @@
|
||||
#include "RuntimeServices.h"
|
||||
|
||||
#include "ControlServer.h"
|
||||
#include "OscServer.h"
|
||||
#include "RuntimeControlBridge.h"
|
||||
#include "RuntimeHost.h"
|
||||
#include <windows.h>
|
||||
#include "RuntimeStore.h"
|
||||
|
||||
RuntimeServices::RuntimeServices() :
|
||||
mControlServer(std::make_unique<ControlServer>()),
|
||||
mOscServer(std::make_unique<OscServer>()),
|
||||
mPollRunning(false),
|
||||
mRegistryChanged(false),
|
||||
mReloadRequested(false),
|
||||
mPollFailed(false)
|
||||
RuntimeServices::RuntimeServices(RuntimeEventDispatcher& runtimeEventDispatcher) :
|
||||
mControlServices(std::make_unique<ControlServices>(runtimeEventDispatcher))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -21,227 +12,75 @@ RuntimeServices::~RuntimeServices()
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error)
|
||||
bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error)
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (!StartRuntimeControlServices(composite, runtimeHost, *this, *mControlServer, *mOscServer, error))
|
||||
{
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return mControlServices && mControlServices->Start(composite, runtimeStore, error);
|
||||
}
|
||||
|
||||
void RuntimeServices::BeginPolling(RuntimeHost& runtimeHost)
|
||||
void RuntimeServices::BeginPolling(RuntimeCoordinator& runtimeCoordinator)
|
||||
{
|
||||
StartPolling(runtimeHost);
|
||||
if (mControlServices)
|
||||
mControlServices->BeginPolling(runtimeCoordinator);
|
||||
}
|
||||
|
||||
void RuntimeServices::Stop()
|
||||
{
|
||||
StopPolling();
|
||||
|
||||
if (mOscServer)
|
||||
mOscServer->Stop();
|
||||
|
||||
if (mControlServer)
|
||||
mControlServer->Stop();
|
||||
if (mControlServices)
|
||||
mControlServices->Stop();
|
||||
}
|
||||
|
||||
void RuntimeServices::BroadcastState()
|
||||
{
|
||||
if (mControlServer)
|
||||
mControlServer->BroadcastState();
|
||||
if (mControlServices)
|
||||
mControlServices->BroadcastState();
|
||||
}
|
||||
|
||||
void RuntimeServices::RequestBroadcastState()
|
||||
{
|
||||
if (mControlServer)
|
||||
mControlServer->RequestBroadcastState();
|
||||
if (mControlServices)
|
||||
mControlServices->RequestBroadcastState();
|
||||
}
|
||||
|
||||
bool RuntimeServices::QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
|
||||
{
|
||||
(void)error;
|
||||
|
||||
PendingOscUpdate update;
|
||||
update.layerKey = layerKey;
|
||||
update.parameterKey = parameterKey;
|
||||
update.valueJson = valueJson;
|
||||
|
||||
const std::string routeKey = layerKey + "\n" + parameterKey;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
||||
mPendingOscUpdates[routeKey] = std::move(update);
|
||||
}
|
||||
return true;
|
||||
return mControlServices && mControlServices->QueueOscUpdate(layerKey, parameterKey, valueJson, error);
|
||||
}
|
||||
|
||||
bool RuntimeServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error)
|
||||
{
|
||||
appliedUpdates.clear();
|
||||
|
||||
std::map<std::string, PendingOscUpdate> pending;
|
||||
if (!mControlServices)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
||||
if (mPendingOscUpdates.empty())
|
||||
return true;
|
||||
pending.swap(mPendingOscUpdates);
|
||||
appliedUpdates.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& entry : pending)
|
||||
{
|
||||
JsonValue targetValue;
|
||||
std::string parseError;
|
||||
if (!ParseJson(entry.second.valueJson, targetValue, parseError))
|
||||
{
|
||||
OutputDebugStringA(("OSC queued value parse failed: " + parseError + "\n").c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
AppliedOscUpdate appliedUpdate;
|
||||
appliedUpdate.routeKey = entry.first;
|
||||
appliedUpdate.layerKey = entry.second.layerKey;
|
||||
appliedUpdate.parameterKey = entry.second.parameterKey;
|
||||
appliedUpdate.targetValue = targetValue;
|
||||
appliedUpdates.push_back(std::move(appliedUpdate));
|
||||
}
|
||||
|
||||
(void)error;
|
||||
return true;
|
||||
return mControlServices->ApplyPendingOscUpdates(appliedUpdates, error);
|
||||
}
|
||||
|
||||
bool RuntimeServices::QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error)
|
||||
{
|
||||
(void)error;
|
||||
|
||||
PendingOscCommit commit;
|
||||
commit.routeKey = routeKey;
|
||||
commit.layerKey = layerKey;
|
||||
commit.parameterKey = parameterKey;
|
||||
commit.value = value;
|
||||
commit.generation = generation;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
||||
mPendingOscCommits[routeKey] = std::move(commit);
|
||||
}
|
||||
return true;
|
||||
return mControlServices && mControlServices->QueueOscCommit(routeKey, layerKey, parameterKey, value, generation, error);
|
||||
}
|
||||
|
||||
void RuntimeServices::ClearOscState()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
||||
mPendingOscUpdates.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
||||
mPendingOscCommits.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
||||
mCompletedOscCommits.clear();
|
||||
}
|
||||
if (mControlServices)
|
||||
mControlServices->ClearOscState();
|
||||
}
|
||||
|
||||
void RuntimeServices::ClearOscStateForLayerKey(const std::string& layerKey)
|
||||
{
|
||||
if (mControlServices)
|
||||
mControlServices->ClearOscStateForLayerKey(layerKey);
|
||||
}
|
||||
|
||||
void RuntimeServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
|
||||
{
|
||||
completedCommits.clear();
|
||||
|
||||
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
||||
if (mCompletedOscCommits.empty())
|
||||
return;
|
||||
|
||||
completedCommits.swap(mCompletedOscCommits);
|
||||
}
|
||||
|
||||
RuntimePollEvents RuntimeServices::ConsumePollEvents()
|
||||
{
|
||||
RuntimePollEvents events;
|
||||
events.registryChanged = mRegistryChanged.exchange(false);
|
||||
events.reloadRequested = mReloadRequested.exchange(false);
|
||||
events.failed = mPollFailed.exchange(false);
|
||||
|
||||
if (events.failed)
|
||||
if (!mControlServices)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPollErrorMutex);
|
||||
events.error = mPollError;
|
||||
completedCommits.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
void RuntimeServices::StartPolling(RuntimeHost& runtimeHost)
|
||||
{
|
||||
if (mPollRunning.exchange(true))
|
||||
return;
|
||||
|
||||
mPollThread = std::thread([this, &runtimeHost]() { PollLoop(runtimeHost); });
|
||||
}
|
||||
|
||||
void RuntimeServices::StopPolling()
|
||||
{
|
||||
if (!mPollRunning.exchange(false))
|
||||
return;
|
||||
|
||||
if (mPollThread.joinable())
|
||||
mPollThread.join();
|
||||
}
|
||||
|
||||
void RuntimeServices::PollLoop(RuntimeHost& runtimeHost)
|
||||
{
|
||||
while (mPollRunning)
|
||||
{
|
||||
std::map<std::string, PendingOscCommit> pendingCommits;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
||||
pendingCommits.swap(mPendingOscCommits);
|
||||
}
|
||||
for (const auto& entry : pendingCommits)
|
||||
{
|
||||
std::string commitError;
|
||||
if (runtimeHost.UpdateLayerParameterByControlKey(
|
||||
entry.second.layerKey,
|
||||
entry.second.parameterKey,
|
||||
entry.second.value,
|
||||
false,
|
||||
commitError))
|
||||
{
|
||||
CompletedOscCommit completedCommit;
|
||||
completedCommit.routeKey = entry.second.routeKey;
|
||||
completedCommit.generation = entry.second.generation;
|
||||
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
||||
mCompletedOscCommits.push_back(std::move(completedCommit));
|
||||
}
|
||||
else if (!commitError.empty())
|
||||
{
|
||||
OutputDebugStringA(("OSC commit failed: " + commitError + "\n").c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool registryChanged = false;
|
||||
bool reloadRequested = false;
|
||||
std::string runtimeError;
|
||||
if (!runtimeHost.PollFileChanges(registryChanged, reloadRequested, runtimeError))
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPollErrorMutex);
|
||||
mPollError = runtimeError;
|
||||
}
|
||||
mPollFailed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (registryChanged)
|
||||
mRegistryChanged = true;
|
||||
if (reloadRequested)
|
||||
mReloadRequested = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 25 && mPollRunning; ++i)
|
||||
Sleep(10);
|
||||
}
|
||||
mControlServices->ConsumeCompletedOscCommits(completedCommits);
|
||||
}
|
||||
|
||||
@@ -1,50 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
#include "ShaderTypes.h"
|
||||
#include "ControlServices.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
class ControlServer;
|
||||
class OpenGLComposite;
|
||||
class OscServer;
|
||||
class RuntimeHost;
|
||||
|
||||
struct RuntimePollEvents
|
||||
{
|
||||
bool registryChanged = false;
|
||||
bool reloadRequested = false;
|
||||
bool failed = false;
|
||||
std::string error;
|
||||
};
|
||||
class RuntimeCoordinator;
|
||||
class RuntimeEventDispatcher;
|
||||
class RuntimeStore;
|
||||
|
||||
class RuntimeServices
|
||||
{
|
||||
public:
|
||||
struct AppliedOscUpdate
|
||||
{
|
||||
std::string routeKey;
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
JsonValue targetValue;
|
||||
};
|
||||
using AppliedOscUpdate = ControlServices::AppliedOscUpdate;
|
||||
using CompletedOscCommit = ControlServices::CompletedOscCommit;
|
||||
|
||||
struct CompletedOscCommit
|
||||
{
|
||||
std::string routeKey;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
RuntimeServices();
|
||||
explicit RuntimeServices(RuntimeEventDispatcher& runtimeEventDispatcher);
|
||||
~RuntimeServices();
|
||||
|
||||
bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error);
|
||||
void BeginPolling(RuntimeHost& runtimeHost);
|
||||
bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);
|
||||
void BeginPolling(RuntimeCoordinator& runtimeCoordinator);
|
||||
void Stop();
|
||||
void BroadcastState();
|
||||
void RequestBroadcastState();
|
||||
@@ -52,43 +27,9 @@ public:
|
||||
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error);
|
||||
bool QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error);
|
||||
void ClearOscState();
|
||||
void ClearOscStateForLayerKey(const std::string& layerKey);
|
||||
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
|
||||
RuntimePollEvents ConsumePollEvents();
|
||||
|
||||
private:
|
||||
struct PendingOscUpdate
|
||||
{
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
std::string valueJson;
|
||||
};
|
||||
|
||||
struct PendingOscCommit
|
||||
{
|
||||
std::string routeKey;
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
JsonValue value;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
void StartPolling(RuntimeHost& runtimeHost);
|
||||
void StopPolling();
|
||||
void PollLoop(RuntimeHost& runtimeHost);
|
||||
|
||||
std::unique_ptr<ControlServer> mControlServer;
|
||||
std::unique_ptr<OscServer> mOscServer;
|
||||
std::thread mPollThread;
|
||||
std::atomic<bool> mPollRunning;
|
||||
std::atomic<bool> mRegistryChanged;
|
||||
std::atomic<bool> mReloadRequested;
|
||||
std::atomic<bool> mPollFailed;
|
||||
std::mutex mPollErrorMutex;
|
||||
std::string mPollError;
|
||||
std::mutex mPendingOscMutex;
|
||||
std::map<std::string, PendingOscUpdate> mPendingOscUpdates;
|
||||
std::mutex mPendingOscCommitMutex;
|
||||
std::map<std::string, PendingOscCommit> mPendingOscCommits;
|
||||
std::mutex mCompletedOscCommitMutex;
|
||||
std::vector<CompletedOscCommit> mCompletedOscCommits;
|
||||
std::unique_ptr<ControlServices> mControlServices;
|
||||
};
|
||||
|
||||
@@ -1,800 +0,0 @@
|
||||
#include "DeckLinkDisplayMode.h"
|
||||
#include "DeckLinkSession.h"
|
||||
#include "OpenGLComposite.h"
|
||||
#include "GLExtensions.h"
|
||||
#include "GlRenderConstants.h"
|
||||
#include "OpenGLRenderPass.h"
|
||||
#include "OpenGLRenderPipeline.h"
|
||||
#include "OpenGLShaderPrograms.h"
|
||||
#include "OpenGLVideoIOBridge.h"
|
||||
#include "PngScreenshotWriter.h"
|
||||
#include "RuntimeParameterUtils.h"
|
||||
#include "RuntimeServices.h"
|
||||
#include "ShaderBuildQueue.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150);
|
||||
constexpr double kOscSmoothingReferenceFps = 60.0;
|
||||
constexpr double kOscSmoothingMaxStepSeconds = 0.25;
|
||||
|
||||
std::string SimplifyOscControlKey(const std::string& text)
|
||||
{
|
||||
std::string simplified;
|
||||
for (unsigned char ch : text)
|
||||
{
|
||||
if (std::isalnum(ch))
|
||||
simplified.push_back(static_cast<char>(std::tolower(ch)));
|
||||
}
|
||||
return simplified;
|
||||
}
|
||||
|
||||
bool MatchesOscControlKey(const std::string& candidate, const std::string& key)
|
||||
{
|
||||
return candidate == key || SimplifyOscControlKey(candidate) == SimplifyOscControlKey(key);
|
||||
}
|
||||
|
||||
double ClampOscAlpha(double value)
|
||||
{
|
||||
return (std::max)(0.0, (std::min)(1.0, value));
|
||||
}
|
||||
|
||||
double ComputeTimeBasedOscAlpha(double smoothing, double deltaSeconds)
|
||||
{
|
||||
const double clampedSmoothing = ClampOscAlpha(smoothing);
|
||||
if (clampedSmoothing <= 0.0)
|
||||
return 0.0;
|
||||
if (clampedSmoothing >= 1.0)
|
||||
return 1.0;
|
||||
|
||||
const double clampedDeltaSeconds = (std::max)(0.0, (std::min)(kOscSmoothingMaxStepSeconds, deltaSeconds));
|
||||
if (clampedDeltaSeconds <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
const double frameScale = clampedDeltaSeconds * kOscSmoothingReferenceFps;
|
||||
return ClampOscAlpha(1.0 - std::pow(1.0 - clampedSmoothing, frameScale));
|
||||
}
|
||||
|
||||
JsonValue BuildOscCommitValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
|
||||
{
|
||||
switch (definition.type)
|
||||
{
|
||||
case ShaderParameterType::Boolean:
|
||||
return JsonValue(value.booleanValue);
|
||||
case ShaderParameterType::Enum:
|
||||
return JsonValue(value.enumValue);
|
||||
case ShaderParameterType::Text:
|
||||
return JsonValue(value.textValue);
|
||||
case ShaderParameterType::Trigger:
|
||||
case ShaderParameterType::Float:
|
||||
return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
|
||||
case ShaderParameterType::Vec2:
|
||||
case ShaderParameterType::Color:
|
||||
{
|
||||
JsonValue array = JsonValue::MakeArray();
|
||||
for (double number : value.numberValues)
|
||||
array.pushBack(JsonValue(number));
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
return JsonValue();
|
||||
}
|
||||
}
|
||||
|
||||
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
|
||||
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC),
|
||||
mVideoIO(std::make_unique<DeckLinkSession>()),
|
||||
mRenderer(std::make_unique<OpenGLRenderer>()),
|
||||
mUseCommittedLayerStates(false),
|
||||
mScreenshotRequested(false)
|
||||
{
|
||||
InitializeCriticalSection(&pMutex);
|
||||
mRuntimeHost = std::make_unique<RuntimeHost>();
|
||||
mRenderPipeline = std::make_unique<OpenGLRenderPipeline>(
|
||||
*mRenderer,
|
||||
*mRuntimeHost,
|
||||
[this]() { renderEffect(); },
|
||||
[this]() { ProcessScreenshotRequest(); },
|
||||
[this]() { paintGL(false); });
|
||||
mVideoIOBridge = std::make_unique<OpenGLVideoIOBridge>(
|
||||
*mVideoIO,
|
||||
*mRenderer,
|
||||
*mRenderPipeline,
|
||||
*mRuntimeHost,
|
||||
pMutex,
|
||||
hGLDC,
|
||||
hGLRC);
|
||||
mRenderPass = std::make_unique<OpenGLRenderPass>(*mRenderer);
|
||||
mShaderPrograms = std::make_unique<OpenGLShaderPrograms>(*mRenderer, *mRuntimeHost);
|
||||
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeHost);
|
||||
mRuntimeServices = std::make_unique<RuntimeServices>();
|
||||
}
|
||||
|
||||
OpenGLComposite::~OpenGLComposite()
|
||||
{
|
||||
if (mRuntimeServices)
|
||||
mRuntimeServices->Stop();
|
||||
if (mShaderBuildQueue)
|
||||
mShaderBuildQueue->Stop();
|
||||
mVideoIO->ReleaseResources();
|
||||
mRenderer->DestroyResources();
|
||||
|
||||
DeleteCriticalSection(&pMutex);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::InitDeckLink()
|
||||
{
|
||||
return InitVideoIO();
|
||||
}
|
||||
|
||||
bool OpenGLComposite::InitVideoIO()
|
||||
{
|
||||
VideoFormatSelection videoModes;
|
||||
std::string initFailureReason;
|
||||
|
||||
if (mRuntimeHost && mRuntimeHost->GetRepoRoot().empty())
|
||||
{
|
||||
std::string runtimeError;
|
||||
if (!mRuntimeHost->Initialize(runtimeError))
|
||||
{
|
||||
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mRuntimeHost)
|
||||
{
|
||||
if (!ResolveConfiguredVideoFormats(
|
||||
mRuntimeHost->GetInputVideoFormat(),
|
||||
mRuntimeHost->GetInputFrameRate(),
|
||||
mRuntimeHost->GetOutputVideoFormat(),
|
||||
mRuntimeHost->GetOutputFrameRate(),
|
||||
videoModes,
|
||||
initFailureReason))
|
||||
{
|
||||
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink mode configuration error", MB_OK);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mVideoIO->DiscoverDevicesAndModes(videoModes, initFailureReason))
|
||||
{
|
||||
const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application."
|
||||
? "This application requires the DeckLink drivers installed."
|
||||
: "DeckLink initialization failed";
|
||||
MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
const bool outputAlphaRequired = mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled();
|
||||
if (!mVideoIO->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason))
|
||||
goto error;
|
||||
|
||||
if (! CheckOpenGLExtensions())
|
||||
{
|
||||
initFailureReason = "OpenGL extension checks failed.";
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (! InitOpenGLState())
|
||||
{
|
||||
initFailureReason = "OpenGL state initialization failed.";
|
||||
goto error;
|
||||
}
|
||||
|
||||
PublishVideoIOStatus(mVideoIO->OutputModelName().empty()
|
||||
? "DeckLink output device selected."
|
||||
: ("Selected output device: " + mVideoIO->OutputModelName()));
|
||||
|
||||
// Resize window to match output video frame, but scale large formats down by half for viewing.
|
||||
if (mVideoIO->OutputFrameWidth() < 1920)
|
||||
resizeWindow(mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight());
|
||||
else
|
||||
resizeWindow(mVideoIO->OutputFrameWidth() / 2, mVideoIO->OutputFrameHeight() / 2);
|
||||
|
||||
if (!mVideoIO->ConfigureInput([this](const VideoIOFrame& frame) { mVideoIOBridge->VideoFrameArrived(frame); }, videoModes.input, initFailureReason))
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
if (!mVideoIO->HasInputDevice() && mRuntimeHost)
|
||||
{
|
||||
mRuntimeHost->SetSignalStatus(false, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), mVideoIO->InputDisplayModeName());
|
||||
}
|
||||
|
||||
if (!mVideoIO->ConfigureOutput([this](const VideoIOCompletion& completion) { mVideoIOBridge->PlayoutFrameCompleted(completion); }, videoModes.output, mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled(), initFailureReason))
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
PublishVideoIOStatus(mVideoIO->StatusMessage());
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (!initFailureReason.empty())
|
||||
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR);
|
||||
mVideoIO->ReleaseResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
void OpenGLComposite::paintGL(bool force)
|
||||
{
|
||||
if (!force)
|
||||
{
|
||||
if (IsIconic(hGLWnd))
|
||||
return;
|
||||
|
||||
const unsigned previewFps = mRuntimeHost ? mRuntimeHost->GetPreviewFps() : 30u;
|
||||
if (previewFps == 0)
|
||||
return;
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto minimumInterval = std::chrono::microseconds(1000000 / (previewFps == 0 ? 1u : previewFps));
|
||||
if (mLastPreviewPresentTime != std::chrono::steady_clock::time_point() &&
|
||||
now - mLastPreviewPresentTime < minimumInterval)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TryEnterCriticalSection(&pMutex))
|
||||
{
|
||||
ValidateRect(hGLWnd, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
mRenderer->PresentToWindow(hGLDC, mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight());
|
||||
mLastPreviewPresentTime = std::chrono::steady_clock::now();
|
||||
ValidateRect(hGLWnd, NULL);
|
||||
LeaveCriticalSection(&pMutex);
|
||||
}
|
||||
|
||||
void OpenGLComposite::resizeGL(WORD width, WORD height)
|
||||
{
|
||||
// We don't set the project or model matrices here since the window data is copied directly from
|
||||
// an off-screen FBO in paintGL(). Just save the width and height for use in paintGL().
|
||||
mRenderer->ResizeView(width, height);
|
||||
}
|
||||
|
||||
void OpenGLComposite::resizeWindow(int width, int height)
|
||||
{
|
||||
RECT r;
|
||||
if (GetWindowRect(hGLWnd, &r))
|
||||
{
|
||||
SetWindowPos(hGLWnd, HWND_TOP, r.left, r.top, r.left + width, r.top + height, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLComposite::PublishVideoIOStatus(const std::string& statusMessage)
|
||||
{
|
||||
if (!mRuntimeHost)
|
||||
return;
|
||||
|
||||
if (!statusMessage.empty())
|
||||
mVideoIO->SetStatusMessage(statusMessage);
|
||||
|
||||
mRuntimeHost->SetVideoIOStatus(
|
||||
"decklink",
|
||||
mVideoIO->OutputModelName(),
|
||||
mVideoIO->SupportsInternalKeying(),
|
||||
mVideoIO->SupportsExternalKeying(),
|
||||
mVideoIO->KeyerInterfaceAvailable(),
|
||||
mRuntimeHost->ExternalKeyingEnabled(),
|
||||
mVideoIO->ExternalKeyingActive(),
|
||||
mVideoIO->StatusMessage());
|
||||
}
|
||||
|
||||
bool OpenGLComposite::InitOpenGLState()
|
||||
{
|
||||
if (! ResolveGLExtensions())
|
||||
return false;
|
||||
|
||||
std::string runtimeError;
|
||||
if (mRuntimeHost->GetRepoRoot().empty() && !mRuntimeHost->Initialize(runtimeError))
|
||||
{
|
||||
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mRuntimeServices->Start(*this, *mRuntimeHost, runtimeError))
|
||||
{
|
||||
MessageBoxA(NULL, runtimeError.c_str(), "Runtime control services failed to start", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepare the runtime shader program generated from the active shader package.
|
||||
char compilerErrorMessage[1024];
|
||||
if (!mShaderPrograms->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||
{
|
||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK);
|
||||
return false;
|
||||
}
|
||||
if (!mShaderPrograms->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||
{
|
||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL output pack shader failed to load or compile", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string rendererError;
|
||||
if (!mRenderer->InitializeResources(
|
||||
mVideoIO->InputFrameWidth(),
|
||||
mVideoIO->InputFrameHeight(),
|
||||
mVideoIO->CaptureTextureWidth(),
|
||||
mVideoIO->OutputFrameWidth(),
|
||||
mVideoIO->OutputFrameHeight(),
|
||||
mVideoIO->OutputPackTextureWidth(),
|
||||
rendererError))
|
||||
{
|
||||
MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mShaderPrograms->CompileLayerPrograms(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||
{
|
||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK);
|
||||
return false;
|
||||
}
|
||||
mCachedLayerRenderStates = mShaderPrograms->CommittedLayerStates();
|
||||
mUseCommittedLayerStates = false;
|
||||
|
||||
mShaderPrograms->ResetTemporalHistoryState();
|
||||
mShaderPrograms->ResetShaderFeedbackState();
|
||||
|
||||
broadcastRuntimeState();
|
||||
mRuntimeServices->BeginPolling(*mRuntimeHost);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::Start()
|
||||
{
|
||||
return mVideoIO->Start();
|
||||
}
|
||||
|
||||
bool OpenGLComposite::Stop()
|
||||
{
|
||||
if (mRuntimeServices)
|
||||
mRuntimeServices->Stop();
|
||||
|
||||
const bool wasExternalKeyingActive = mVideoIO->ExternalKeyingActive();
|
||||
mVideoIO->Stop();
|
||||
if (wasExternalKeyingActive)
|
||||
PublishVideoIOStatus("External keying has been disabled.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::ReloadShader(bool preserveFeedbackState)
|
||||
{
|
||||
mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState;
|
||||
if (mRuntimeHost)
|
||||
{
|
||||
mRuntimeHost->SetCompileStatus(true, "Shader rebuild queued.");
|
||||
mRuntimeHost->ClearReloadRequest();
|
||||
}
|
||||
RequestShaderBuild();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::RequestScreenshot(std::string& error)
|
||||
{
|
||||
(void)error;
|
||||
mScreenshotRequested.store(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLComposite::renderEffect()
|
||||
{
|
||||
ProcessRuntimePollResults();
|
||||
std::vector<RuntimeServices::AppliedOscUpdate> appliedOscUpdates;
|
||||
std::vector<RuntimeServices::CompletedOscCommit> completedOscCommits;
|
||||
if (mRuntimeHost && mRuntimeServices)
|
||||
{
|
||||
std::string oscError;
|
||||
if (!mRuntimeServices->ApplyPendingOscUpdates(appliedOscUpdates, oscError) && !oscError.empty())
|
||||
OutputDebugStringA(("OSC apply failed: " + oscError + "\n").c_str());
|
||||
mRuntimeServices->ConsumeCompletedOscCommits(completedOscCommits);
|
||||
}
|
||||
|
||||
for (const RuntimeServices::CompletedOscCommit& completedCommit : completedOscCommits)
|
||||
{
|
||||
auto overlayIt = mOscOverlayStates.find(completedCommit.routeKey);
|
||||
if (overlayIt == mOscOverlayStates.end())
|
||||
continue;
|
||||
|
||||
OscOverlayState& overlay = overlayIt->second;
|
||||
if (overlay.commitQueued &&
|
||||
overlay.pendingCommitGeneration == completedCommit.generation &&
|
||||
overlay.generation == completedCommit.generation)
|
||||
{
|
||||
mOscOverlayStates.erase(overlayIt);
|
||||
}
|
||||
}
|
||||
|
||||
std::set<std::string> pendingOscRouteKeys;
|
||||
const auto oscNow = std::chrono::steady_clock::now();
|
||||
for (const RuntimeServices::AppliedOscUpdate& update : appliedOscUpdates)
|
||||
{
|
||||
const std::string routeKey = update.routeKey;
|
||||
auto overlayIt = mOscOverlayStates.find(routeKey);
|
||||
if (overlayIt == mOscOverlayStates.end())
|
||||
{
|
||||
OscOverlayState overlay;
|
||||
overlay.layerKey = update.layerKey;
|
||||
overlay.parameterKey = update.parameterKey;
|
||||
overlay.targetValue = update.targetValue;
|
||||
overlay.lastUpdatedTime = oscNow;
|
||||
overlay.lastAppliedTime = oscNow;
|
||||
overlay.generation = 1;
|
||||
mOscOverlayStates[routeKey] = std::move(overlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
overlayIt->second.targetValue = update.targetValue;
|
||||
overlayIt->second.lastUpdatedTime = oscNow;
|
||||
overlayIt->second.generation += 1;
|
||||
overlayIt->second.commitQueued = false;
|
||||
}
|
||||
pendingOscRouteKeys.insert(routeKey);
|
||||
}
|
||||
|
||||
const auto applyOscOverlays = [&](std::vector<RuntimeRenderState>& states, bool allowCommit)
|
||||
{
|
||||
if (states.empty() || mOscOverlayStates.empty() || !mRuntimeHost)
|
||||
return;
|
||||
|
||||
const double smoothing = ClampOscAlpha(mRuntimeHost->GetOscSmoothing());
|
||||
std::vector<std::string> overlayKeysToRemove;
|
||||
for (auto& item : mOscOverlayStates)
|
||||
{
|
||||
OscOverlayState& overlay = item.second;
|
||||
auto stateIt = std::find_if(states.begin(), states.end(),
|
||||
[&overlay](const RuntimeRenderState& state)
|
||||
{
|
||||
return MatchesOscControlKey(state.layerId, overlay.layerKey) ||
|
||||
MatchesOscControlKey(state.shaderId, overlay.layerKey) ||
|
||||
MatchesOscControlKey(state.shaderName, overlay.layerKey);
|
||||
});
|
||||
if (stateIt == states.end())
|
||||
continue;
|
||||
|
||||
auto definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(),
|
||||
[&overlay](const ShaderParameterDefinition& definition)
|
||||
{
|
||||
return MatchesOscControlKey(definition.id, overlay.parameterKey) ||
|
||||
MatchesOscControlKey(definition.label, overlay.parameterKey);
|
||||
});
|
||||
if (definitionIt == stateIt->parameterDefinitions.end())
|
||||
continue;
|
||||
|
||||
if (definitionIt->type == ShaderParameterType::Trigger)
|
||||
{
|
||||
if (pendingOscRouteKeys.find(item.first) == pendingOscRouteKeys.end())
|
||||
continue;
|
||||
|
||||
ShaderParameterValue& value = stateIt->parameterValues[definitionIt->id];
|
||||
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
|
||||
const double triggerTime = stateIt->timeSeconds;
|
||||
value.numberValues = { previousCount + 1.0, triggerTime };
|
||||
overlayKeysToRemove.push_back(item.first);
|
||||
continue;
|
||||
}
|
||||
|
||||
ShaderParameterValue targetValue;
|
||||
std::string normalizeError;
|
||||
if (!NormalizeAndValidateParameterValue(*definitionIt, overlay.targetValue, targetValue, normalizeError))
|
||||
continue;
|
||||
|
||||
const bool smoothable =
|
||||
smoothing > 0.0 &&
|
||||
(definitionIt->type == ShaderParameterType::Float ||
|
||||
definitionIt->type == ShaderParameterType::Vec2 ||
|
||||
definitionIt->type == ShaderParameterType::Color);
|
||||
if (!smoothable)
|
||||
{
|
||||
overlay.currentValue = targetValue;
|
||||
overlay.hasCurrentValue = true;
|
||||
stateIt->parameterValues[definitionIt->id] = overlay.currentValue;
|
||||
if (allowCommit &&
|
||||
!overlay.commitQueued &&
|
||||
oscNow - overlay.lastUpdatedTime >= kOscOverlayCommitDelay &&
|
||||
mRuntimeServices)
|
||||
{
|
||||
std::string commitError;
|
||||
if (mRuntimeServices->QueueOscCommit(item.first, overlay.layerKey, overlay.parameterKey, overlay.targetValue, overlay.generation, commitError))
|
||||
{
|
||||
overlay.pendingCommitGeneration = overlay.generation;
|
||||
overlay.commitQueued = true;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!overlay.hasCurrentValue)
|
||||
{
|
||||
overlay.currentValue = DefaultValueForDefinition(*definitionIt);
|
||||
auto currentIt = stateIt->parameterValues.find(definitionIt->id);
|
||||
if (currentIt != stateIt->parameterValues.end())
|
||||
overlay.currentValue = currentIt->second;
|
||||
overlay.hasCurrentValue = true;
|
||||
}
|
||||
|
||||
if (overlay.currentValue.numberValues.size() != targetValue.numberValues.size())
|
||||
overlay.currentValue.numberValues = targetValue.numberValues;
|
||||
|
||||
double smoothingAlpha = smoothing;
|
||||
if (overlay.lastAppliedTime != std::chrono::steady_clock::time_point())
|
||||
{
|
||||
const double deltaSeconds =
|
||||
std::chrono::duration_cast<std::chrono::duration<double>>(oscNow - overlay.lastAppliedTime).count();
|
||||
smoothingAlpha = ComputeTimeBasedOscAlpha(smoothing, deltaSeconds);
|
||||
}
|
||||
overlay.lastAppliedTime = oscNow;
|
||||
|
||||
ShaderParameterValue nextValue = targetValue;
|
||||
bool converged = true;
|
||||
for (std::size_t index = 0; index < targetValue.numberValues.size(); ++index)
|
||||
{
|
||||
const double currentNumber = overlay.currentValue.numberValues[index];
|
||||
const double targetNumber = targetValue.numberValues[index];
|
||||
const double delta = targetNumber - currentNumber;
|
||||
double nextNumber = currentNumber + delta * smoothingAlpha;
|
||||
if (std::fabs(delta) <= 0.0005)
|
||||
nextNumber = targetNumber;
|
||||
else
|
||||
converged = false;
|
||||
nextValue.numberValues[index] = nextNumber;
|
||||
}
|
||||
|
||||
if (converged)
|
||||
nextValue.numberValues = targetValue.numberValues;
|
||||
|
||||
overlay.currentValue = nextValue;
|
||||
overlay.hasCurrentValue = true;
|
||||
stateIt->parameterValues[definitionIt->id] = overlay.currentValue;
|
||||
if (allowCommit &&
|
||||
converged &&
|
||||
!overlay.commitQueued &&
|
||||
oscNow - overlay.lastUpdatedTime >= kOscOverlayCommitDelay &&
|
||||
mRuntimeServices)
|
||||
{
|
||||
std::string commitError;
|
||||
JsonValue committedValue = BuildOscCommitValue(*definitionIt, overlay.currentValue);
|
||||
if (mRuntimeServices->QueueOscCommit(item.first, overlay.layerKey, overlay.parameterKey, committedValue, overlay.generation, commitError))
|
||||
{
|
||||
overlay.pendingCommitGeneration = overlay.generation;
|
||||
overlay.commitQueued = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::string& overlayKey : overlayKeysToRemove)
|
||||
mOscOverlayStates.erase(overlayKey);
|
||||
};
|
||||
|
||||
const bool hasInputSource = mVideoIO->HasInputSource();
|
||||
std::vector<RuntimeRenderState> layerStates;
|
||||
if (mUseCommittedLayerStates)
|
||||
{
|
||||
layerStates = mShaderPrograms->CommittedLayerStates();
|
||||
applyOscOverlays(layerStates, false);
|
||||
if (mRuntimeHost)
|
||||
mRuntimeHost->RefreshDynamicRenderStateFields(layerStates);
|
||||
}
|
||||
else if (mRuntimeHost)
|
||||
{
|
||||
const unsigned renderWidth = mVideoIO->InputFrameWidth();
|
||||
const unsigned renderHeight = mVideoIO->InputFrameHeight();
|
||||
const uint64_t renderStateVersion = mRuntimeHost->GetRenderStateVersion();
|
||||
const uint64_t parameterStateVersion = mRuntimeHost->GetParameterStateVersion();
|
||||
const bool renderStateCacheValid =
|
||||
!mCachedLayerRenderStates.empty() &&
|
||||
mCachedRenderStateVersion == renderStateVersion &&
|
||||
mCachedRenderStateWidth == renderWidth &&
|
||||
mCachedRenderStateHeight == renderHeight;
|
||||
|
||||
if (renderStateCacheValid)
|
||||
{
|
||||
applyOscOverlays(mCachedLayerRenderStates, true);
|
||||
if (mCachedParameterStateVersion != parameterStateVersion &&
|
||||
mRuntimeHost->TryRefreshCachedLayerStates(mCachedLayerRenderStates))
|
||||
{
|
||||
mCachedParameterStateVersion = parameterStateVersion;
|
||||
applyOscOverlays(mCachedLayerRenderStates, true);
|
||||
}
|
||||
layerStates = mCachedLayerRenderStates;
|
||||
mRuntimeHost->RefreshDynamicRenderStateFields(layerStates);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mRuntimeHost->TryGetLayerRenderStates(renderWidth, renderHeight, layerStates))
|
||||
{
|
||||
mCachedLayerRenderStates = layerStates;
|
||||
mCachedRenderStateVersion = renderStateVersion;
|
||||
mCachedParameterStateVersion = parameterStateVersion;
|
||||
mCachedRenderStateWidth = renderWidth;
|
||||
mCachedRenderStateHeight = renderHeight;
|
||||
applyOscOverlays(mCachedLayerRenderStates, true);
|
||||
layerStates = mCachedLayerRenderStates;
|
||||
}
|
||||
else
|
||||
{
|
||||
applyOscOverlays(mCachedLayerRenderStates, true);
|
||||
layerStates = mCachedLayerRenderStates;
|
||||
mRuntimeHost->RefreshDynamicRenderStateFields(layerStates);
|
||||
}
|
||||
}
|
||||
}
|
||||
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
||||
mRenderPass->Render(
|
||||
hasInputSource,
|
||||
layerStates,
|
||||
mVideoIO->InputFrameWidth(),
|
||||
mVideoIO->InputFrameHeight(),
|
||||
mVideoIO->CaptureTextureWidth(),
|
||||
mVideoIO->InputPixelFormat(),
|
||||
historyCap,
|
||||
[this](const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error) {
|
||||
return mShaderPrograms->UpdateTextBindingTexture(state, textBinding, error);
|
||||
},
|
||||
[this](const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable) {
|
||||
return mShaderPrograms->UpdateGlobalParamsBuffer(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLComposite::ProcessScreenshotRequest()
|
||||
{
|
||||
if (!mScreenshotRequested.exchange(false))
|
||||
return;
|
||||
|
||||
const unsigned width = mVideoIO ? mVideoIO->OutputFrameWidth() : 0;
|
||||
const unsigned height = mVideoIO ? mVideoIO->OutputFrameHeight() : 0;
|
||||
if (width == 0 || height == 0)
|
||||
return;
|
||||
|
||||
std::vector<unsigned char> bottomUpPixels(static_cast<std::size_t>(width) * height * 4);
|
||||
std::vector<unsigned char> topDownPixels(bottomUpPixels.size());
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer->OutputFramebuffer());
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bottomUpPixels.data());
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
|
||||
const std::size_t rowBytes = static_cast<std::size_t>(width) * 4;
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
{
|
||||
const unsigned sourceY = height - 1 - y;
|
||||
std::copy(
|
||||
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>(sourceY * rowBytes),
|
||||
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>((sourceY + 1) * rowBytes),
|
||||
topDownPixels.begin() + static_cast<std::ptrdiff_t>(y * rowBytes));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const std::filesystem::path outputPath = BuildScreenshotPath();
|
||||
std::filesystem::create_directories(outputPath.parent_path());
|
||||
WritePngFileAsync(outputPath, width, height, std::move(topDownPixels));
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
OutputDebugStringA((std::string("Screenshot request failed: ") + exception.what() + "\n").c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path OpenGLComposite::BuildScreenshotPath() const
|
||||
{
|
||||
const std::filesystem::path root = mRuntimeHost && !mRuntimeHost->GetRuntimeRoot().empty()
|
||||
? mRuntimeHost->GetRuntimeRoot()
|
||||
: std::filesystem::current_path();
|
||||
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
|
||||
const std::time_t nowTime = std::chrono::system_clock::to_time_t(now);
|
||||
std::tm localTime = {};
|
||||
localtime_s(&localTime, &nowTime);
|
||||
|
||||
std::ostringstream filename;
|
||||
filename << "video-shader-toys-"
|
||||
<< std::put_time(&localTime, "%Y%m%d-%H%M%S")
|
||||
<< "-" << std::setw(3) << std::setfill('0') << milliseconds.count()
|
||||
<< ".png";
|
||||
|
||||
return root / "screenshots" / filename.str();
|
||||
}
|
||||
|
||||
bool OpenGLComposite::ProcessRuntimePollResults()
|
||||
{
|
||||
if (!mRuntimeHost || !mRuntimeServices)
|
||||
return true;
|
||||
|
||||
const RuntimePollEvents events = mRuntimeServices->ConsumePollEvents();
|
||||
if (events.failed)
|
||||
{
|
||||
mRuntimeHost->SetCompileStatus(false, events.error);
|
||||
broadcastRuntimeState();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (events.registryChanged)
|
||||
broadcastRuntimeState();
|
||||
|
||||
if (!events.reloadRequested)
|
||||
{
|
||||
PreparedShaderBuild readyBuild;
|
||||
if (!mShaderBuildQueue || !mShaderBuildQueue->TryConsumeReadyBuild(readyBuild))
|
||||
return true;
|
||||
|
||||
char compilerErrorMessage[1024] = {};
|
||||
if (!mShaderPrograms->CommitPreparedLayerPrograms(readyBuild, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||
{
|
||||
mRuntimeHost->SetCompileStatus(false, compilerErrorMessage);
|
||||
mUseCommittedLayerStates = true;
|
||||
mPreserveFeedbackOnNextShaderBuild = false;
|
||||
broadcastRuntimeState();
|
||||
return false;
|
||||
}
|
||||
|
||||
mUseCommittedLayerStates = false;
|
||||
mCachedLayerRenderStates = mShaderPrograms->CommittedLayerStates();
|
||||
mShaderPrograms->ResetTemporalHistoryState();
|
||||
if (!mPreserveFeedbackOnNextShaderBuild)
|
||||
mShaderPrograms->ResetShaderFeedbackState();
|
||||
mPreserveFeedbackOnNextShaderBuild = false;
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
mRuntimeHost->SetCompileStatus(true, "Shader rebuild queued.");
|
||||
mPreserveFeedbackOnNextShaderBuild = false;
|
||||
RequestShaderBuild();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLComposite::RequestShaderBuild()
|
||||
{
|
||||
if (!mShaderBuildQueue || !mVideoIO)
|
||||
return;
|
||||
|
||||
mUseCommittedLayerStates = true;
|
||||
if (mRuntimeHost)
|
||||
mRuntimeHost->ClearReloadRequest();
|
||||
mShaderBuildQueue->RequestBuild(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight());
|
||||
}
|
||||
|
||||
void OpenGLComposite::broadcastRuntimeState()
|
||||
{
|
||||
if (mRuntimeServices)
|
||||
mRuntimeServices->BroadcastState();
|
||||
}
|
||||
|
||||
void OpenGLComposite::resetTemporalHistoryState()
|
||||
{
|
||||
mShaderPrograms->ResetTemporalHistoryState();
|
||||
mShaderPrograms->ResetShaderFeedbackState();
|
||||
}
|
||||
|
||||
bool OpenGLComposite::CheckOpenGLExtensions()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
#include "OpenGLComposite.h"
|
||||
#include "RuntimeServices.h"
|
||||
|
||||
std::string OpenGLComposite::GetRuntimeStateJson() const
|
||||
{
|
||||
return mRuntimeHost ? mRuntimeHost->BuildStateJson() : "{}";
|
||||
}
|
||||
|
||||
unsigned short OpenGLComposite::GetControlServerPort() const
|
||||
{
|
||||
return mRuntimeHost ? mRuntimeHost->GetServerPort() : 0;
|
||||
}
|
||||
|
||||
unsigned short OpenGLComposite::GetOscPort() const
|
||||
{
|
||||
return mRuntimeHost ? mRuntimeHost->GetOscPort() : 0;
|
||||
}
|
||||
|
||||
std::string OpenGLComposite::GetOscBindAddress() const
|
||||
{
|
||||
return mRuntimeHost ? mRuntimeHost->GetOscBindAddress() : "127.0.0.1";
|
||||
}
|
||||
|
||||
std::string OpenGLComposite::GetControlUrl() const
|
||||
{
|
||||
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/";
|
||||
}
|
||||
|
||||
std::string OpenGLComposite::GetDocsUrl() const
|
||||
{
|
||||
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/docs";
|
||||
}
|
||||
|
||||
std::string OpenGLComposite::GetOscAddress() const
|
||||
{
|
||||
return "udp://" + GetOscBindAddress() + ":" + std::to_string(GetOscPort()) + " /VideoShaderToys/{Layer}/{Parameter}";
|
||||
}
|
||||
|
||||
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
|
||||
{
|
||||
if (!mRuntimeHost->AddLayer(shaderId, error))
|
||||
return false;
|
||||
|
||||
ReloadShader(true);
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error)
|
||||
{
|
||||
if (!mRuntimeHost->RemoveLayer(layerId, error))
|
||||
return false;
|
||||
|
||||
ReloadShader(true);
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error)
|
||||
{
|
||||
if (!mRuntimeHost->MoveLayer(layerId, direction, error))
|
||||
return false;
|
||||
|
||||
ReloadShader(true);
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
||||
{
|
||||
if (!mRuntimeHost->MoveLayerToIndex(layerId, targetIndex, error))
|
||||
return false;
|
||||
|
||||
ReloadShader(true);
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
|
||||
{
|
||||
if (!mRuntimeHost->SetLayerBypass(layerId, bypassed, error))
|
||||
return false;
|
||||
|
||||
ReloadShader();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
|
||||
{
|
||||
if (!mRuntimeHost->SetLayerShader(layerId, shaderId, error))
|
||||
return false;
|
||||
|
||||
ReloadShader();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error)
|
||||
{
|
||||
JsonValue parsedValue;
|
||||
if (!ParseJson(valueJson, parsedValue, error))
|
||||
return false;
|
||||
|
||||
if (!mRuntimeHost->UpdateLayerParameter(layerId, parameterId, parsedValue, error))
|
||||
return false;
|
||||
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
|
||||
{
|
||||
JsonValue parsedValue;
|
||||
if (!ParseJson(valueJson, parsedValue, error))
|
||||
return false;
|
||||
|
||||
if (!mRuntimeHost->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue, error))
|
||||
return false;
|
||||
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error)
|
||||
{
|
||||
if (!mRuntimeHost->ResetLayerParameters(layerId, error))
|
||||
return false;
|
||||
|
||||
mOscOverlayStates.clear();
|
||||
if (mRuntimeServices)
|
||||
mRuntimeServices->ClearOscState();
|
||||
resetTemporalHistoryState();
|
||||
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error)
|
||||
{
|
||||
if (!mRuntimeHost->SaveStackPreset(presetName, error))
|
||||
return false;
|
||||
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error)
|
||||
{
|
||||
if (!mRuntimeHost->LoadStackPreset(presetName, error))
|
||||
return false;
|
||||
|
||||
ReloadShader();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
668
apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp
Normal file
668
apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp
Normal file
@@ -0,0 +1,668 @@
|
||||
#include "RenderEngine.h"
|
||||
|
||||
#include <gl/gl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
|
||||
RenderEngine::RenderEngine(
|
||||
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
||||
HealthTelemetry& healthTelemetry,
|
||||
HDC hdc,
|
||||
HGLRC hglrc,
|
||||
RenderEffectCallback renderEffect,
|
||||
ScreenshotCallback screenshotReady,
|
||||
PreviewPaintCallback previewPaint) :
|
||||
mRenderer(),
|
||||
mRenderPass(mRenderer),
|
||||
mRenderPipeline(mRenderer, runtimeSnapshotProvider, healthTelemetry, std::move(renderEffect), std::move(screenshotReady), std::move(previewPaint)),
|
||||
mShaderPrograms(mRenderer, runtimeSnapshotProvider),
|
||||
mHealthTelemetry(healthTelemetry),
|
||||
mHdc(hdc),
|
||||
mHglrc(hglrc),
|
||||
mFrameStateResolver(runtimeSnapshotProvider)
|
||||
{
|
||||
}
|
||||
|
||||
RenderEngine::~RenderEngine()
|
||||
{
|
||||
StopRenderThread();
|
||||
if (!mResourcesDestroyed)
|
||||
{
|
||||
mRenderer.DestroyResources();
|
||||
mResourcesDestroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderEngine::StartRenderThread()
|
||||
{
|
||||
if (mRenderThreadRunning)
|
||||
return true;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
||||
mRenderThreadStopping = false;
|
||||
}
|
||||
|
||||
std::promise<bool> ready;
|
||||
std::future<bool> readyResult = ready.get_future();
|
||||
mRenderThread = std::thread(&RenderEngine::RenderThreadMain, this, std::move(ready));
|
||||
if (!readyResult.get())
|
||||
{
|
||||
if (mRenderThread.joinable())
|
||||
mRenderThread.join();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderEngine::StopRenderThread()
|
||||
{
|
||||
if (mRenderThreadRunning)
|
||||
{
|
||||
InvokeOnRenderThread([this]() {
|
||||
if (!mResourcesDestroyed)
|
||||
{
|
||||
mRenderer.DestroyResources();
|
||||
mResourcesDestroyed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
||||
mRenderThreadStopping = true;
|
||||
}
|
||||
mRenderThreadCondition.notify_one();
|
||||
|
||||
if (mRenderThread.joinable())
|
||||
mRenderThread.join();
|
||||
}
|
||||
|
||||
void RenderEngine::RenderThreadMain(std::promise<bool> ready)
|
||||
{
|
||||
mRenderThreadId = GetCurrentThreadId();
|
||||
if (!wglMakeCurrent(mHdc, mHglrc))
|
||||
{
|
||||
mRenderThreadId = 0;
|
||||
ready.set_value(false);
|
||||
return;
|
||||
}
|
||||
|
||||
mRenderThreadRunning = true;
|
||||
ready.set_value(true);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
std::function<void()> task;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mRenderThreadMutex);
|
||||
mRenderThreadCondition.wait(lock, [this]() {
|
||||
return mRenderThreadStopping || !mRenderThreadTasks.empty();
|
||||
});
|
||||
|
||||
if (mRenderThreadStopping && mRenderThreadTasks.empty())
|
||||
break;
|
||||
|
||||
task = std::move(mRenderThreadTasks.front());
|
||||
mRenderThreadTasks.pop();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
task();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OutputDebugStringA("Render thread task failed with an unhandled exception.\n");
|
||||
}
|
||||
}
|
||||
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
mRenderThreadRunning = false;
|
||||
mRenderThreadId = 0;
|
||||
}
|
||||
|
||||
void RenderEngine::ReportRenderThreadRequestFailure(const char* operationName, const char* reason)
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "Render thread request failed";
|
||||
if (operationName && operationName[0] != '\0')
|
||||
message << " [" << operationName << "]";
|
||||
if (reason && reason[0] != '\0')
|
||||
message << ": " << reason;
|
||||
message << ".\n";
|
||||
OutputDebugStringA(message.str().c_str());
|
||||
}
|
||||
|
||||
bool RenderEngine::IsRenderThreadAccessExpected() const
|
||||
{
|
||||
return !mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId;
|
||||
}
|
||||
|
||||
void RenderEngine::ReportWrongThreadRenderAccess(const char* operationName) const
|
||||
{
|
||||
if (IsRenderThreadAccessExpected())
|
||||
return;
|
||||
|
||||
std::ostringstream message;
|
||||
message << "Wrong-thread render access detected";
|
||||
if (operationName && operationName[0] != '\0')
|
||||
message << " [" << operationName << "]";
|
||||
message << ".\n";
|
||||
OutputDebugStringA(message.str().c_str());
|
||||
}
|
||||
|
||||
bool RenderEngine::CompileDecodeShader(int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
return InvokeOnRenderThread([this, errorMessageSize, errorMessage]() {
|
||||
return mShaderPrograms.CompileDecodeShader(errorMessageSize, errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
bool RenderEngine::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
return InvokeOnRenderThread([this, errorMessageSize, errorMessage]() {
|
||||
return mShaderPrograms.CompileOutputPackShader(errorMessageSize, errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
bool RenderEngine::InitializeResources(
|
||||
unsigned inputFrameWidth,
|
||||
unsigned inputFrameHeight,
|
||||
unsigned captureTextureWidth,
|
||||
unsigned outputFrameWidth,
|
||||
unsigned outputFrameHeight,
|
||||
unsigned outputPackTextureWidth,
|
||||
std::string& error)
|
||||
{
|
||||
return InvokeOnRenderThread([this, inputFrameWidth, inputFrameHeight, captureTextureWidth, outputFrameWidth, outputFrameHeight, outputPackTextureWidth, &error]() {
|
||||
return mRenderer.InitializeResources(
|
||||
inputFrameWidth,
|
||||
inputFrameHeight,
|
||||
captureTextureWidth,
|
||||
outputFrameWidth,
|
||||
outputFrameHeight,
|
||||
outputPackTextureWidth,
|
||||
error);
|
||||
});
|
||||
}
|
||||
|
||||
bool RenderEngine::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
return InvokeOnRenderThread([this, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage]() {
|
||||
return mShaderPrograms.CompileLayerPrograms(inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
bool RenderEngine::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
return InvokeOnRenderThread([this, &preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage]() {
|
||||
return mShaderPrograms.CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
bool RenderEngine::ApplyPreparedShaderBuild(
|
||||
const PreparedShaderBuild& preparedBuild,
|
||||
unsigned inputFrameWidth,
|
||||
unsigned inputFrameHeight,
|
||||
bool preserveFeedbackState,
|
||||
int errorMessageSize,
|
||||
char* errorMessage)
|
||||
{
|
||||
if (!CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage))
|
||||
return false;
|
||||
|
||||
mFrameStateResolver.StoreCommittedSnapshot(preparedBuild.renderSnapshot, mShaderPrograms.CommittedLayerStates());
|
||||
ResetTemporalHistoryState();
|
||||
if (!preserveFeedbackState)
|
||||
ResetShaderFeedbackState();
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderEngine::ResetTemporalHistoryState()
|
||||
{
|
||||
InvokeOnRenderThread([this]() {
|
||||
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
|
||||
ProcessRenderResetCommandsOnRenderThread();
|
||||
});
|
||||
}
|
||||
|
||||
void RenderEngine::ResetShaderFeedbackState()
|
||||
{
|
||||
InvokeOnRenderThread([this]() {
|
||||
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::ShaderFeedbackOnly);
|
||||
ProcessRenderResetCommandsOnRenderThread();
|
||||
});
|
||||
}
|
||||
|
||||
void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope)
|
||||
{
|
||||
InvokeOnRenderThread([this, resetScope]() {
|
||||
switch (resetScope)
|
||||
{
|
||||
case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly:
|
||||
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
|
||||
ProcessRenderResetCommandsOnRenderThread();
|
||||
break;
|
||||
case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback:
|
||||
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryAndFeedback);
|
||||
ProcessRenderResetCommandsOnRenderThread();
|
||||
break;
|
||||
case RuntimeCoordinatorRenderResetScope::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void RenderEngine::ResetTemporalHistoryStateOnRenderThread()
|
||||
{
|
||||
ReportWrongThreadRenderAccess("reset-temporal-history");
|
||||
mShaderPrograms.ResetTemporalHistoryState();
|
||||
}
|
||||
|
||||
void RenderEngine::ResetShaderFeedbackStateOnRenderThread()
|
||||
{
|
||||
ReportWrongThreadRenderAccess("reset-shader-feedback");
|
||||
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::EnqueuePreviewPresentWake()
|
||||
{
|
||||
if (!mRenderThreadRunning)
|
||||
return;
|
||||
|
||||
bool shouldNotify = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
||||
if (!mRenderThreadStopping && !mPreviewPresentWakePending)
|
||||
{
|
||||
mPreviewPresentWakePending = true;
|
||||
mRenderThreadTasks.push([this]() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
||||
mPreviewPresentWakePending = false;
|
||||
}
|
||||
ProcessPreviewPresentCommandsOnRenderThread();
|
||||
});
|
||||
shouldNotify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldNotify)
|
||||
mRenderThreadCondition.notify_one();
|
||||
}
|
||||
|
||||
void RenderEngine::ProcessPreviewPresentCommandsOnRenderThread()
|
||||
{
|
||||
RenderPreviewPresentRequest request;
|
||||
if (mRenderCommandQueue.TryTakePreviewPresent(request))
|
||||
PresentPreviewOnRenderThread(request.outputFrameWidth, request.outputFrameHeight);
|
||||
}
|
||||
|
||||
void RenderEngine::EnqueueInputUploadWake()
|
||||
{
|
||||
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
|
||||
return;
|
||||
|
||||
bool shouldNotify = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
||||
if (!mRenderThreadStopping && !mInputUploadWakePending)
|
||||
{
|
||||
mInputUploadWakePending = true;
|
||||
mRenderThreadTasks.push([this]() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
||||
mInputUploadWakePending = false;
|
||||
}
|
||||
ProcessInputUploadCommandsOnRenderThread();
|
||||
});
|
||||
shouldNotify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldNotify)
|
||||
mRenderThreadCondition.notify_one();
|
||||
}
|
||||
|
||||
void RenderEngine::ProcessInputUploadCommandsOnRenderThread()
|
||||
{
|
||||
RenderInputUploadRequest request;
|
||||
while (mRenderCommandQueue.TryTakeInputUpload(request))
|
||||
{
|
||||
if (request.ownedBytes.empty())
|
||||
continue;
|
||||
|
||||
request.inputFrame.bytes = request.ownedBytes.data();
|
||||
UploadInputFrameOnRenderThread(request.inputFrame, request.videoState);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderEngine::EnqueueScreenshotCaptureWake()
|
||||
{
|
||||
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
|
||||
return;
|
||||
|
||||
bool shouldNotify = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
||||
if (!mRenderThreadStopping && !mScreenshotCaptureWakePending)
|
||||
{
|
||||
mScreenshotCaptureWakePending = true;
|
||||
mRenderThreadTasks.push([this]() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
||||
mScreenshotCaptureWakePending = false;
|
||||
}
|
||||
ProcessScreenshotCaptureCommandsOnRenderThread();
|
||||
});
|
||||
shouldNotify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldNotify)
|
||||
mRenderThreadCondition.notify_one();
|
||||
}
|
||||
|
||||
void RenderEngine::ProcessScreenshotCaptureCommandsOnRenderThread()
|
||||
{
|
||||
RenderScreenshotCaptureRequest request;
|
||||
ScreenshotCaptureCallback completion;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
||||
completion = mScreenshotCaptureCompletion;
|
||||
}
|
||||
|
||||
while (mRenderCommandQueue.TryTakeScreenshotCapture(request))
|
||||
{
|
||||
if (!completion)
|
||||
continue;
|
||||
|
||||
std::vector<unsigned char> topDownPixels;
|
||||
if (CaptureOutputFrameRgbaTopDownOnRenderThread(request.width, request.height, topDownPixels))
|
||||
completion(request.width, request.height, std::move(topDownPixels));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderEngine::ClearOscOverlayState()
|
||||
{
|
||||
InvokeOnRenderThread([this]() {
|
||||
mRuntimeLiveState.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
void RenderEngine::ClearOscOverlayStateForLayerKey(const std::string& layerKey)
|
||||
{
|
||||
InvokeOnRenderThread([this, layerKey]() {
|
||||
mRuntimeLiveState.ClearForLayerKey(layerKey);
|
||||
});
|
||||
}
|
||||
|
||||
void RenderEngine::UpdateOscOverlayState(
|
||||
const std::vector<OscOverlayUpdate>& updates,
|
||||
const std::vector<OscOverlayCommitCompletion>& completedCommits)
|
||||
{
|
||||
std::vector<RuntimeLiveOscCommitCompletion> liveCompletions;
|
||||
liveCompletions.reserve(completedCommits.size());
|
||||
for (const OscOverlayCommitCompletion& completedCommit : completedCommits)
|
||||
liveCompletions.push_back({ completedCommit.routeKey, completedCommit.generation });
|
||||
mRuntimeLiveState.ApplyOscCommitCompletions(liveCompletions);
|
||||
|
||||
std::vector<RuntimeLiveOscUpdate> liveUpdates;
|
||||
liveUpdates.reserve(updates.size());
|
||||
for (const OscOverlayUpdate& update : updates)
|
||||
liveUpdates.push_back({ update.routeKey, update.layerKey, update.parameterKey, update.targetValue });
|
||||
mRuntimeLiveState.ApplyOscUpdates(liveUpdates);
|
||||
}
|
||||
|
||||
void RenderEngine::ResizeView(int width, int height)
|
||||
{
|
||||
InvokeOnRenderThread([this, width, height]() {
|
||||
mRenderer.ResizeView(width, height);
|
||||
});
|
||||
}
|
||||
|
||||
bool RenderEngine::TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight)
|
||||
{
|
||||
if (!force)
|
||||
{
|
||||
if (previewFps == 0)
|
||||
return false;
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto minimumInterval = std::chrono::microseconds(1000000 / (previewFps == 0 ? 1u : previewFps));
|
||||
if (mLastPreviewPresentTime != std::chrono::steady_clock::time_point() &&
|
||||
now - mLastPreviewPresentTime < minimumInterval)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mRenderThreadRunning)
|
||||
{
|
||||
mRenderCommandQueue.RequestPreviewPresent({ outputFrameWidth, outputFrameHeight });
|
||||
EnqueuePreviewPresentWake();
|
||||
return true;
|
||||
}
|
||||
|
||||
ReportRenderThreadRequestFailure("preview-present", "render thread is not running");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RenderEngine::PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight)
|
||||
{
|
||||
ReportWrongThreadRenderAccess("preview-present");
|
||||
mRenderer.PresentToWindow(mHdc, outputFrameWidth, outputFrameHeight);
|
||||
mLastPreviewPresentTime = std::chrono::steady_clock::now();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderEngine::RequestScreenshotCapture(unsigned width, unsigned height, ScreenshotCaptureCallback completion)
|
||||
{
|
||||
if (width == 0 || height == 0 || !completion)
|
||||
return false;
|
||||
if (!mRenderThreadRunning)
|
||||
return false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
||||
mScreenshotCaptureCompletion = std::move(completion);
|
||||
}
|
||||
mRenderCommandQueue.RequestScreenshotCapture({ width, height });
|
||||
EnqueueScreenshotCaptureWake();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderEngine::QueueInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
|
||||
{
|
||||
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
|
||||
return true;
|
||||
if (inputFrame.rowBytes <= 0 || inputFrame.height == 0)
|
||||
return false;
|
||||
|
||||
const std::size_t byteCount = static_cast<std::size_t>(inputFrame.rowBytes) * inputFrame.height;
|
||||
RenderInputUploadRequest request;
|
||||
request.inputFrame = inputFrame;
|
||||
request.videoState = videoState;
|
||||
request.ownedBytes.resize(byteCount);
|
||||
std::memcpy(request.ownedBytes.data(), inputFrame.bytes, byteCount);
|
||||
request.inputFrame.bytes = nullptr;
|
||||
|
||||
mRenderCommandQueue.RequestInputUpload(request);
|
||||
EnqueueInputUploadWake();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderEngine::UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
|
||||
{
|
||||
ReportWrongThreadRenderAccess("input-upload");
|
||||
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.TextureUploadBuffer());
|
||||
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, inputFrame.bytes, GL_DYNAMIC_DRAW);
|
||||
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
|
||||
if (inputFrame.pixelFormat == VideoIOPixelFormat::V210)
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
else
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderEngine::RequestOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
|
||||
{
|
||||
if (mRenderThreadRunning)
|
||||
{
|
||||
const auto queuedAt = std::chrono::steady_clock::now();
|
||||
return TryInvokeOnRenderThread("output-render", [this, &context, &outputFrame, queuedAt]() {
|
||||
const auto startedAt = std::chrono::steady_clock::now();
|
||||
const double queueWaitMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(startedAt - queuedAt).count();
|
||||
mHealthTelemetry.TryRecordOutputRenderQueueWait(queueWaitMilliseconds);
|
||||
mRenderCommandQueue.RequestOutputFrame({ context.videoState, context.completion });
|
||||
RenderOutputFrameRequest request;
|
||||
return mRenderCommandQueue.TryTakeOutputFrame(request) &&
|
||||
RenderOutputFrameOnRenderThread({ request.videoState, request.completion }, outputFrame);
|
||||
});
|
||||
}
|
||||
|
||||
ReportRenderThreadRequestFailure("output-render", "render thread is not running");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RenderEngine::RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
|
||||
{
|
||||
ReportWrongThreadRenderAccess("output-render");
|
||||
ProcessRenderResetCommandsOnRenderThread();
|
||||
ProcessInputUploadCommandsOnRenderThread();
|
||||
return mRenderPipeline.RenderFrame(context, outputFrame);
|
||||
}
|
||||
|
||||
bool RenderEngine::ResolveRenderFrameState(
|
||||
const RenderFrameInput& input,
|
||||
std::vector<OscOverlayCommitRequest>* commitRequests,
|
||||
RenderFrameState& frameState)
|
||||
{
|
||||
std::vector<RuntimeLiveOscCommitRequest> liveCommitRequests;
|
||||
const bool resolved = mFrameStateResolver.Resolve(
|
||||
input,
|
||||
mShaderPrograms.CommittedLayerStates(),
|
||||
mRuntimeLiveState,
|
||||
commitRequests ? &liveCommitRequests : nullptr,
|
||||
frameState);
|
||||
|
||||
if (commitRequests)
|
||||
{
|
||||
for (const RuntimeLiveOscCommitRequest& request : liveCommitRequests)
|
||||
commitRequests->push_back({ request.routeKey, request.layerKey, request.parameterKey, request.value, request.generation });
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
void RenderEngine::RenderPreparedFrame(const RenderFrameState& frameState)
|
||||
{
|
||||
RenderLayerStack(
|
||||
frameState.hasInputSource,
|
||||
frameState.layerStates,
|
||||
frameState.inputFrameWidth,
|
||||
frameState.inputFrameHeight,
|
||||
frameState.captureTextureWidth,
|
||||
frameState.inputPixelFormat,
|
||||
frameState.historyCap);
|
||||
}
|
||||
|
||||
void RenderEngine::RenderLayerStack(
|
||||
bool hasInputSource,
|
||||
const std::vector<RuntimeRenderState>& layerStates,
|
||||
unsigned inputFrameWidth,
|
||||
unsigned inputFrameHeight,
|
||||
unsigned captureTextureWidth,
|
||||
VideoIOPixelFormat inputPixelFormat,
|
||||
unsigned historyCap)
|
||||
{
|
||||
ReportWrongThreadRenderAccess("render-layer-stack");
|
||||
mRenderPass.Render(
|
||||
hasInputSource,
|
||||
layerStates,
|
||||
inputFrameWidth,
|
||||
inputFrameHeight,
|
||||
captureTextureWidth,
|
||||
inputPixelFormat,
|
||||
historyCap,
|
||||
[this](const RuntimeRenderState& state, OpenGLRenderer::LayerProgram::TextBinding& textBinding, std::string& error) {
|
||||
return mShaderPrograms.UpdateTextBindingTexture(state, textBinding, error);
|
||||
},
|
||||
[this](const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable) {
|
||||
return mShaderPrograms.UpdateGlobalParamsBuffer(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable);
|
||||
});
|
||||
}
|
||||
|
||||
bool RenderEngine::ReadOutputFrameRgbaOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels)
|
||||
{
|
||||
ReportWrongThreadRenderAccess("read-output-frame-rgba");
|
||||
if (width == 0 || height == 0)
|
||||
return false;
|
||||
|
||||
bottomUpPixels.resize(static_cast<std::size_t>(width) * height * 4);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bottomUpPixels.data());
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderEngine::CaptureOutputFrameRgbaTopDownOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels)
|
||||
{
|
||||
std::vector<unsigned char> bottomUpPixels;
|
||||
if (!ReadOutputFrameRgbaOnRenderThread(width, height, bottomUpPixels))
|
||||
return false;
|
||||
|
||||
topDownPixels.resize(bottomUpPixels.size());
|
||||
const std::size_t rowBytes = static_cast<std::size_t>(width) * 4;
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
{
|
||||
const unsigned sourceY = height - 1 - y;
|
||||
std::copy(
|
||||
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>(sourceY * rowBytes),
|
||||
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>((sourceY + 1) * rowBytes),
|
||||
topDownPixels.begin() + static_cast<std::ptrdiff_t>(y * rowBytes));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
232
apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h
Normal file
232
apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h
Normal file
@@ -0,0 +1,232 @@
|
||||
#pragma once
|
||||
|
||||
#include "OpenGLRenderPass.h"
|
||||
#include "OpenGLRenderPipeline.h"
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "OpenGLShaderPrograms.h"
|
||||
#include "RenderCommandQueue.h"
|
||||
#include "RenderFrameState.h"
|
||||
#include "RenderFrameStateResolver.h"
|
||||
#include "HealthTelemetry.h"
|
||||
#include "RuntimeCoordinator.h"
|
||||
#include "RuntimeSnapshotProvider.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class RenderEngine
|
||||
{
|
||||
public:
|
||||
using RenderEffectCallback = std::function<void()>;
|
||||
using ScreenshotCallback = std::function<void()>;
|
||||
using ScreenshotCaptureCallback = std::function<void(unsigned, unsigned, std::vector<unsigned char>)>;
|
||||
using PreviewPaintCallback = std::function<void()>;
|
||||
|
||||
struct OscOverlayUpdate
|
||||
{
|
||||
std::string routeKey;
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
JsonValue targetValue;
|
||||
};
|
||||
|
||||
struct OscOverlayCommitCompletion
|
||||
{
|
||||
std::string routeKey;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
struct OscOverlayCommitRequest
|
||||
{
|
||||
std::string routeKey;
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
JsonValue value;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
RenderEngine(
|
||||
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
||||
HealthTelemetry& healthTelemetry,
|
||||
HDC hdc,
|
||||
HGLRC hglrc,
|
||||
RenderEffectCallback renderEffect,
|
||||
ScreenshotCallback screenshotReady,
|
||||
PreviewPaintCallback previewPaint);
|
||||
~RenderEngine();
|
||||
|
||||
bool StartRenderThread();
|
||||
void StopRenderThread();
|
||||
|
||||
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
|
||||
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
|
||||
bool InitializeResources(
|
||||
unsigned inputFrameWidth,
|
||||
unsigned inputFrameHeight,
|
||||
unsigned captureTextureWidth,
|
||||
unsigned outputFrameWidth,
|
||||
unsigned outputFrameHeight,
|
||||
unsigned outputPackTextureWidth,
|
||||
std::string& error);
|
||||
bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
|
||||
bool ApplyPreparedShaderBuild(
|
||||
const PreparedShaderBuild& preparedBuild,
|
||||
unsigned inputFrameWidth,
|
||||
unsigned inputFrameHeight,
|
||||
bool preserveFeedbackState,
|
||||
int errorMessageSize,
|
||||
char* errorMessage);
|
||||
|
||||
void ResetTemporalHistoryState();
|
||||
void ResetShaderFeedbackState();
|
||||
void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope);
|
||||
void ClearOscOverlayState();
|
||||
void ClearOscOverlayStateForLayerKey(const std::string& layerKey);
|
||||
void UpdateOscOverlayState(
|
||||
const std::vector<OscOverlayUpdate>& updates,
|
||||
const std::vector<OscOverlayCommitCompletion>& completedCommits);
|
||||
void ResizeView(int width, int height);
|
||||
bool TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight);
|
||||
bool RequestScreenshotCapture(unsigned width, unsigned height, ScreenshotCaptureCallback completion);
|
||||
bool QueueInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
|
||||
bool RequestOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
|
||||
bool ResolveRenderFrameState(
|
||||
const RenderFrameInput& input,
|
||||
std::vector<OscOverlayCommitRequest>* commitRequests,
|
||||
RenderFrameState& frameState);
|
||||
void RenderPreparedFrame(const RenderFrameState& frameState);
|
||||
|
||||
private:
|
||||
static constexpr std::chrono::milliseconds kRenderThreadRequestTimeout{ 250 };
|
||||
|
||||
struct RenderThreadTaskState
|
||||
{
|
||||
std::atomic<bool> started = false;
|
||||
std::atomic<bool> cancelled = false;
|
||||
};
|
||||
|
||||
template<typename Func>
|
||||
auto InvokeOnRenderThread(Func&& func) -> decltype(func())
|
||||
{
|
||||
using Result = decltype(func());
|
||||
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
|
||||
return func();
|
||||
|
||||
auto task = std::make_shared<std::packaged_task<Result()>>(std::forward<Func>(func));
|
||||
std::future<Result> result = task->get_future();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
||||
mRenderThreadTasks.push([task]() { (*task)(); });
|
||||
}
|
||||
mRenderThreadCondition.notify_one();
|
||||
return result.get();
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
bool TryInvokeOnRenderThread(const char* operationName, Func&& func)
|
||||
{
|
||||
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
|
||||
return func();
|
||||
|
||||
auto state = std::make_shared<RenderThreadTaskState>();
|
||||
auto task = std::make_shared<std::packaged_task<bool()>>(
|
||||
[state, func = std::forward<Func>(func)]() mutable {
|
||||
state->started = true;
|
||||
if (state->cancelled)
|
||||
return false;
|
||||
|
||||
return func();
|
||||
});
|
||||
std::future<bool> result = task->get_future();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
||||
if (mRenderThreadStopping)
|
||||
{
|
||||
ReportRenderThreadRequestFailure(operationName, "render thread is stopping");
|
||||
return false;
|
||||
}
|
||||
mRenderThreadTasks.push([task]() { (*task)(); });
|
||||
}
|
||||
mRenderThreadCondition.notify_one();
|
||||
|
||||
if (result.wait_for(kRenderThreadRequestTimeout) == std::future_status::ready)
|
||||
return result.get();
|
||||
|
||||
if (!state->started)
|
||||
{
|
||||
state->cancelled = true;
|
||||
ReportRenderThreadRequestFailure(operationName, "timed out before execution");
|
||||
return false;
|
||||
}
|
||||
|
||||
ReportRenderThreadRequestFailure(operationName, "exceeded timeout while executing; waiting for safe completion");
|
||||
return result.get();
|
||||
}
|
||||
|
||||
void RenderThreadMain(std::promise<bool> ready);
|
||||
void ReportRenderThreadRequestFailure(const char* operationName, const char* reason);
|
||||
bool IsRenderThreadAccessExpected() const;
|
||||
void ReportWrongThreadRenderAccess(const char* operationName) const;
|
||||
bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
|
||||
void RenderLayerStack(
|
||||
bool hasInputSource,
|
||||
const std::vector<RuntimeRenderState>& layerStates,
|
||||
unsigned inputFrameWidth,
|
||||
unsigned inputFrameHeight,
|
||||
unsigned captureTextureWidth,
|
||||
VideoIOPixelFormat inputPixelFormat,
|
||||
unsigned historyCap);
|
||||
void ResetTemporalHistoryStateOnRenderThread();
|
||||
void ResetShaderFeedbackStateOnRenderThread();
|
||||
void ApplyRenderResetOnRenderThread(RenderCommandResetScope resetScope);
|
||||
void ProcessRenderResetCommandsOnRenderThread();
|
||||
void EnqueuePreviewPresentWake();
|
||||
void ProcessPreviewPresentCommandsOnRenderThread();
|
||||
void EnqueueInputUploadWake();
|
||||
void ProcessInputUploadCommandsOnRenderThread();
|
||||
void EnqueueScreenshotCaptureWake();
|
||||
void ProcessScreenshotCaptureCommandsOnRenderThread();
|
||||
bool PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight);
|
||||
bool UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
|
||||
bool RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
|
||||
bool ReadOutputFrameRgbaOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels);
|
||||
bool CaptureOutputFrameRgbaTopDownOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels);
|
||||
|
||||
OpenGLRenderer mRenderer;
|
||||
OpenGLRenderPass mRenderPass;
|
||||
OpenGLRenderPipeline mRenderPipeline;
|
||||
OpenGLShaderPrograms mShaderPrograms;
|
||||
HealthTelemetry& mHealthTelemetry;
|
||||
HDC mHdc;
|
||||
HGLRC mHglrc;
|
||||
|
||||
std::chrono::steady_clock::time_point mLastPreviewPresentTime;
|
||||
RenderCommandQueue mRenderCommandQueue;
|
||||
RenderFrameStateResolver mFrameStateResolver;
|
||||
RuntimeLiveState mRuntimeLiveState;
|
||||
std::thread mRenderThread;
|
||||
std::atomic<DWORD> mRenderThreadId = 0;
|
||||
std::mutex mRenderThreadMutex;
|
||||
std::condition_variable mRenderThreadCondition;
|
||||
std::queue<std::function<void()>> mRenderThreadTasks;
|
||||
std::atomic<bool> mRenderThreadRunning = false;
|
||||
bool mRenderThreadStopping = false;
|
||||
bool mPreviewPresentWakePending = false;
|
||||
bool mInputUploadWakePending = false;
|
||||
bool mScreenshotCaptureWakePending = false;
|
||||
ScreenshotCaptureCallback mScreenshotCaptureCompletion;
|
||||
bool mResourcesDestroyed = false;
|
||||
};
|
||||
@@ -0,0 +1,428 @@
|
||||
#include "DeckLinkDisplayMode.h"
|
||||
#include "OpenGLComposite.h"
|
||||
#include "GLExtensions.h"
|
||||
#include "PngScreenshotWriter.h"
|
||||
#include "RenderEngine.h"
|
||||
#include "RuntimeCoordinator.h"
|
||||
#include "RuntimeEventDispatcher.h"
|
||||
#include "RuntimeServiceLiveBridge.h"
|
||||
#include "RuntimeServices.h"
|
||||
#include "RuntimeSnapshotProvider.h"
|
||||
#include "RuntimeStore.h"
|
||||
#include "RuntimeUpdateController.h"
|
||||
#include "ShaderBuildQueue.h"
|
||||
#include "VideoBackend.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
|
||||
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC)
|
||||
{
|
||||
mRuntimeStore = std::make_unique<RuntimeStore>();
|
||||
mRuntimeEventDispatcher = std::make_unique<RuntimeEventDispatcher>();
|
||||
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(mRuntimeStore->GetRenderSnapshotBuilder(), *mRuntimeEventDispatcher);
|
||||
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore, *mRuntimeEventDispatcher);
|
||||
mRenderEngine = std::make_unique<RenderEngine>(
|
||||
*mRuntimeSnapshotProvider,
|
||||
mRuntimeStore->GetHealthTelemetry(),
|
||||
hGLDC,
|
||||
hGLRC,
|
||||
[this]() { renderEffect(); },
|
||||
[]() {},
|
||||
[this]() { paintGL(false); });
|
||||
mVideoBackend = std::make_unique<VideoBackend>(*mRenderEngine, mRuntimeStore->GetHealthTelemetry(), *mRuntimeEventDispatcher);
|
||||
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeSnapshotProvider, *mRuntimeEventDispatcher);
|
||||
mRuntimeServices = std::make_unique<RuntimeServices>(*mRuntimeEventDispatcher);
|
||||
mRuntimeUpdateController = std::make_unique<RuntimeUpdateController>(
|
||||
*mRuntimeStore,
|
||||
*mRuntimeCoordinator,
|
||||
*mRuntimeEventDispatcher,
|
||||
*mRuntimeServices,
|
||||
*mRenderEngine,
|
||||
*mShaderBuildQueue,
|
||||
*mVideoBackend);
|
||||
}
|
||||
|
||||
OpenGLComposite::~OpenGLComposite()
|
||||
{
|
||||
if (mRuntimeServices)
|
||||
mRuntimeServices->Stop();
|
||||
if (mShaderBuildQueue)
|
||||
mShaderBuildQueue->Stop();
|
||||
if (mVideoBackend)
|
||||
mVideoBackend->ReleaseResources();
|
||||
if (mRuntimeStore)
|
||||
{
|
||||
std::string persistenceError;
|
||||
if (!mRuntimeStore->FlushPersistenceForShutdown(std::chrono::seconds(2), persistenceError))
|
||||
OutputDebugStringA((std::string("Persistence shutdown flush failed: ") + persistenceError + "\n").c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenGLComposite::InitDeckLink()
|
||||
{
|
||||
return InitVideoIO();
|
||||
}
|
||||
|
||||
bool OpenGLComposite::InitVideoIO()
|
||||
{
|
||||
VideoFormatSelection videoModes;
|
||||
std::string initFailureReason;
|
||||
|
||||
if (mRuntimeStore && mRuntimeStore->GetRuntimeRepositoryRoot().empty())
|
||||
{
|
||||
std::string runtimeError;
|
||||
if (!mRuntimeStore->InitializeStore(runtimeError))
|
||||
{
|
||||
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mRuntimeStore)
|
||||
{
|
||||
if (!ResolveConfiguredVideoFormats(
|
||||
mRuntimeStore->GetConfiguredInputVideoFormat(),
|
||||
mRuntimeStore->GetConfiguredInputFrameRate(),
|
||||
mRuntimeStore->GetConfiguredOutputVideoFormat(),
|
||||
mRuntimeStore->GetConfiguredOutputFrameRate(),
|
||||
videoModes,
|
||||
initFailureReason))
|
||||
{
|
||||
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink mode configuration error", MB_OK);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mVideoBackend->DiscoverDevicesAndModes(videoModes, initFailureReason))
|
||||
{
|
||||
const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application."
|
||||
? "This application requires the DeckLink drivers installed."
|
||||
: "DeckLink initialization failed";
|
||||
MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
const bool outputAlphaRequired = mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured();
|
||||
if (!mVideoBackend->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason))
|
||||
goto error;
|
||||
|
||||
if (! CheckOpenGLExtensions())
|
||||
{
|
||||
initFailureReason = "OpenGL extension checks failed.";
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (! InitOpenGLState())
|
||||
{
|
||||
initFailureReason = "OpenGL state initialization failed.";
|
||||
goto error;
|
||||
}
|
||||
|
||||
mVideoBackend->PublishStatus(
|
||||
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
|
||||
mVideoBackend->OutputModelName().empty()
|
||||
? "DeckLink output device selected."
|
||||
: ("Selected output device: " + mVideoBackend->OutputModelName()));
|
||||
|
||||
// Resize window to match output video frame, but scale large formats down by half for viewing.
|
||||
if (mVideoBackend->OutputFrameWidth() < 1920)
|
||||
resizeWindow(mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight());
|
||||
else
|
||||
resizeWindow(mVideoBackend->OutputFrameWidth() / 2, mVideoBackend->OutputFrameHeight() / 2);
|
||||
|
||||
if (!mVideoBackend->ConfigureInput(videoModes.input, initFailureReason))
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
if (!mVideoBackend->HasInputDevice())
|
||||
mVideoBackend->ReportNoInputDeviceSignalStatus();
|
||||
|
||||
if (!mVideoBackend->ConfigureOutput(videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason))
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
mVideoBackend->PublishStatus(
|
||||
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
|
||||
mVideoBackend->StatusMessage());
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (!initFailureReason.empty())
|
||||
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR);
|
||||
mVideoBackend->ReleaseResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
void OpenGLComposite::paintGL(bool force)
|
||||
{
|
||||
if (mRuntimeUpdateController)
|
||||
mRuntimeUpdateController->ProcessRuntimeWork();
|
||||
|
||||
if (!force)
|
||||
{
|
||||
if (IsIconic(hGLWnd))
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned previewFps = mRuntimeStore ? mRuntimeStore->GetConfiguredPreviewFps() : 30u;
|
||||
if (!force && mVideoBackend && mVideoBackend->ShouldPrioritizeOutputOverPreview())
|
||||
{
|
||||
ValidateRect(hGLWnd, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mRenderEngine->TryPresentPreview(force, previewFps, mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight()))
|
||||
{
|
||||
ValidateRect(hGLWnd, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
ValidateRect(hGLWnd, NULL);
|
||||
}
|
||||
|
||||
void OpenGLComposite::resizeGL(WORD width, WORD height)
|
||||
{
|
||||
// We don't set the project or model matrices here since the window data is copied directly from
|
||||
// an off-screen FBO in paintGL(). Just save the width and height for use in paintGL().
|
||||
mRenderEngine->ResizeView(width, height);
|
||||
}
|
||||
|
||||
void OpenGLComposite::resizeWindow(int width, int height)
|
||||
{
|
||||
RECT r;
|
||||
if (GetWindowRect(hGLWnd, &r))
|
||||
{
|
||||
SetWindowPos(hGLWnd, HWND_TOP, r.left, r.top, r.left + width, r.top + height, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenGLComposite::InitOpenGLState()
|
||||
{
|
||||
if (! ResolveGLExtensions())
|
||||
return false;
|
||||
|
||||
std::string runtimeError;
|
||||
if (mRuntimeStore->GetRuntimeRepositoryRoot().empty() && !mRuntimeStore->InitializeStore(runtimeError))
|
||||
{
|
||||
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mRuntimeServices->Start(*this, *mRuntimeStore, runtimeError))
|
||||
{
|
||||
MessageBoxA(NULL, runtimeError.c_str(), "Runtime control services failed to start", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepare the runtime shader program generated from the active shader package.
|
||||
char compilerErrorMessage[1024];
|
||||
if (!mRenderEngine->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||
{
|
||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK);
|
||||
return false;
|
||||
}
|
||||
if (!mRenderEngine->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||
{
|
||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL output pack shader failed to load or compile", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string rendererError;
|
||||
if (!mRenderEngine->InitializeResources(
|
||||
mVideoBackend->InputFrameWidth(),
|
||||
mVideoBackend->InputFrameHeight(),
|
||||
mVideoBackend->CaptureTextureWidth(),
|
||||
mVideoBackend->OutputFrameWidth(),
|
||||
mVideoBackend->OutputFrameHeight(),
|
||||
mVideoBackend->OutputPackTextureWidth(),
|
||||
rendererError))
|
||||
{
|
||||
MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mRenderEngine->CompileLayerPrograms(mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||
{
|
||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK);
|
||||
return false;
|
||||
}
|
||||
mRuntimeStore->SetCompileStatus(true, "Shader layers compiled successfully.");
|
||||
|
||||
mRenderEngine->ResetTemporalHistoryState();
|
||||
mRenderEngine->ResetShaderFeedbackState();
|
||||
|
||||
mRuntimeUpdateController->BroadcastRuntimeState();
|
||||
mRuntimeServices->BeginPolling(*mRuntimeCoordinator);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::Start()
|
||||
{
|
||||
if (!mRenderEngine->StartRenderThread())
|
||||
return false;
|
||||
|
||||
if (mRuntimeUpdateController)
|
||||
mRuntimeUpdateController->ProcessRuntimeWork();
|
||||
|
||||
if (mVideoBackend->Start())
|
||||
return true;
|
||||
|
||||
mRenderEngine->StopRenderThread();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::Stop()
|
||||
{
|
||||
if (mRuntimeServices)
|
||||
mRuntimeServices->Stop();
|
||||
|
||||
const bool wasExternalKeyingActive = mVideoBackend->ExternalKeyingActive();
|
||||
mVideoBackend->Stop();
|
||||
if (wasExternalKeyingActive)
|
||||
mVideoBackend->PublishStatus(
|
||||
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
|
||||
"External keying has been disabled.");
|
||||
|
||||
if (mRenderEngine)
|
||||
mRenderEngine->StopRenderThread();
|
||||
|
||||
if (mRuntimeStore)
|
||||
{
|
||||
std::string persistenceError;
|
||||
if (!mRuntimeStore->FlushPersistenceForShutdown(std::chrono::seconds(2), persistenceError))
|
||||
OutputDebugStringA((std::string("Persistence shutdown flush failed: ") + persistenceError + "\n").c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::ReloadShader(bool preserveFeedbackState)
|
||||
{
|
||||
return mRuntimeCoordinator &&
|
||||
mRuntimeUpdateController &&
|
||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RequestShaderReload(preserveFeedbackState));
|
||||
}
|
||||
|
||||
bool OpenGLComposite::RequestScreenshot(std::string& error)
|
||||
{
|
||||
if (!mRenderEngine || !mVideoBackend)
|
||||
{
|
||||
error = "The render engine is not ready.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const unsigned width = mVideoBackend->OutputFrameWidth();
|
||||
const unsigned height = mVideoBackend->OutputFrameHeight();
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
error = "The output frame size is not available.";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::filesystem::path outputPath;
|
||||
try
|
||||
{
|
||||
outputPath = BuildScreenshotPath();
|
||||
std::filesystem::create_directories(outputPath.parent_path());
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
error = exception.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mRenderEngine->RequestScreenshotCapture(
|
||||
width,
|
||||
height,
|
||||
[outputPath](unsigned captureWidth, unsigned captureHeight, std::vector<unsigned char> topDownPixels) {
|
||||
try
|
||||
{
|
||||
WritePngFileAsync(outputPath, captureWidth, captureHeight, std::move(topDownPixels));
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
OutputDebugStringA((std::string("Screenshot request failed: ") + exception.what() + "\n").c_str());
|
||||
}
|
||||
}))
|
||||
{
|
||||
error = "Screenshot capture request failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLComposite::renderEffect()
|
||||
{
|
||||
const RenderFrameInput frameInput = BuildRenderFrameInput();
|
||||
RenderFrame(frameInput);
|
||||
}
|
||||
|
||||
RenderFrameInput OpenGLComposite::BuildRenderFrameInput() const
|
||||
{
|
||||
RenderFrameInput frameInput;
|
||||
frameInput.useCommittedLayerStates = mRuntimeCoordinator && mRuntimeCoordinator->UseCommittedLayerStates();
|
||||
frameInput.hasInputSource = mVideoBackend->HasInputSource();
|
||||
frameInput.renderWidth = mVideoBackend->InputFrameWidth();
|
||||
frameInput.renderHeight = mVideoBackend->InputFrameHeight();
|
||||
frameInput.inputFrameWidth = mVideoBackend->InputFrameWidth();
|
||||
frameInput.inputFrameHeight = mVideoBackend->InputFrameHeight();
|
||||
frameInput.captureTextureWidth = mVideoBackend->CaptureTextureWidth();
|
||||
frameInput.inputPixelFormat = mVideoBackend->InputPixelFormat();
|
||||
frameInput.historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0;
|
||||
frameInput.oscSmoothing = mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0;
|
||||
return frameInput;
|
||||
}
|
||||
|
||||
void OpenGLComposite::RenderFrame(const RenderFrameInput& frameInput)
|
||||
{
|
||||
RenderFrameState frameState;
|
||||
if (mRuntimeServices)
|
||||
{
|
||||
RuntimeServiceLiveBridge::PrepareLiveRenderFrameState(
|
||||
*mRuntimeServices,
|
||||
*mRenderEngine,
|
||||
frameInput,
|
||||
frameState);
|
||||
}
|
||||
else
|
||||
{
|
||||
mRenderEngine->ResolveRenderFrameState(frameInput, nullptr, frameState);
|
||||
}
|
||||
mRenderEngine->RenderPreparedFrame(frameState);
|
||||
}
|
||||
|
||||
std::filesystem::path OpenGLComposite::BuildScreenshotPath() const
|
||||
{
|
||||
const std::filesystem::path root = mRuntimeStore && !mRuntimeStore->GetRuntimeDataRoot().empty()
|
||||
? mRuntimeStore->GetRuntimeDataRoot()
|
||||
: std::filesystem::current_path();
|
||||
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
|
||||
const std::time_t nowTime = std::chrono::system_clock::to_time_t(now);
|
||||
std::tm localTime = {};
|
||||
localtime_s(&localTime, &nowTime);
|
||||
|
||||
std::ostringstream filename;
|
||||
filename << "video-shader-toys-"
|
||||
<< std::put_time(&localTime, "%Y%m%d-%H%M%S")
|
||||
<< "-" << std::setw(3) << std::setfill('0') << milliseconds.count()
|
||||
<< ".png";
|
||||
|
||||
return root / "screenshots" / filename.str();
|
||||
}
|
||||
|
||||
bool OpenGLComposite::CheckOpenGLExtensions()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2,36 +2,23 @@
|
||||
#define __OPENGL_COMPOSITE_H__
|
||||
|
||||
#include <windows.h>
|
||||
#include <process.h>
|
||||
#include <tchar.h>
|
||||
#include <gl/gl.h>
|
||||
#include <gl/glu.h>
|
||||
|
||||
#include <objbase.h>
|
||||
#include <atlbase.h>
|
||||
#include <comutil.h>
|
||||
|
||||
#include "GLExtensions.h"
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "RuntimeHost.h"
|
||||
#include "RenderFrameState.h"
|
||||
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <chrono>
|
||||
|
||||
class VideoIODevice;
|
||||
class OpenGLVideoIOBridge;
|
||||
class OpenGLRenderPass;
|
||||
class OpenGLRenderPipeline;
|
||||
class OpenGLShaderPrograms;
|
||||
class RenderEngine;
|
||||
class RuntimeCoordinator;
|
||||
class RuntimeEventDispatcher;
|
||||
class RuntimeSnapshotProvider;
|
||||
class RuntimeServices;
|
||||
class RuntimeStore;
|
||||
class RuntimeUpdateController;
|
||||
class ShaderBuildQueue;
|
||||
class VideoBackend;
|
||||
|
||||
|
||||
class OpenGLComposite
|
||||
@@ -71,55 +58,26 @@ public:
|
||||
private:
|
||||
void resizeWindow(int width, int height);
|
||||
bool CheckOpenGLExtensions();
|
||||
void PublishVideoIOStatus(const std::string& statusMessage);
|
||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
||||
struct OscOverlayState
|
||||
{
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
JsonValue targetValue;
|
||||
ShaderParameterValue currentValue;
|
||||
bool hasCurrentValue = false;
|
||||
std::chrono::steady_clock::time_point lastUpdatedTime;
|
||||
std::chrono::steady_clock::time_point lastAppliedTime;
|
||||
uint64_t generation = 0;
|
||||
uint64_t pendingCommitGeneration = 0;
|
||||
bool commitQueued = false;
|
||||
};
|
||||
|
||||
HWND hGLWnd;
|
||||
HDC hGLDC;
|
||||
HGLRC hGLRC;
|
||||
CRITICAL_SECTION pMutex;
|
||||
|
||||
std::unique_ptr<VideoIODevice> mVideoIO;
|
||||
std::unique_ptr<OpenGLRenderer> mRenderer;
|
||||
std::unique_ptr<RuntimeHost> mRuntimeHost;
|
||||
std::unique_ptr<OpenGLVideoIOBridge> mVideoIOBridge;
|
||||
std::unique_ptr<OpenGLRenderPass> mRenderPass;
|
||||
std::unique_ptr<OpenGLRenderPipeline> mRenderPipeline;
|
||||
std::unique_ptr<OpenGLShaderPrograms> mShaderPrograms;
|
||||
std::unique_ptr<RuntimeStore> mRuntimeStore;
|
||||
std::unique_ptr<RuntimeCoordinator> mRuntimeCoordinator;
|
||||
std::unique_ptr<RuntimeSnapshotProvider> mRuntimeSnapshotProvider;
|
||||
std::unique_ptr<RuntimeEventDispatcher> mRuntimeEventDispatcher;
|
||||
std::unique_ptr<RenderEngine> mRenderEngine;
|
||||
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
|
||||
std::unique_ptr<RuntimeServices> mRuntimeServices;
|
||||
std::vector<RuntimeRenderState> mCachedLayerRenderStates;
|
||||
uint64_t mCachedRenderStateVersion = 0;
|
||||
uint64_t mCachedParameterStateVersion = 0;
|
||||
unsigned mCachedRenderStateWidth = 0;
|
||||
unsigned mCachedRenderStateHeight = 0;
|
||||
std::map<std::string, OscOverlayState> mOscOverlayStates;
|
||||
std::atomic<bool> mUseCommittedLayerStates;
|
||||
std::atomic<bool> mScreenshotRequested;
|
||||
std::chrono::steady_clock::time_point mLastPreviewPresentTime;
|
||||
bool mPreserveFeedbackOnNextShaderBuild = false;
|
||||
std::unique_ptr<RuntimeUpdateController> mRuntimeUpdateController;
|
||||
std::unique_ptr<VideoBackend> mVideoBackend;
|
||||
|
||||
bool InitOpenGLState();
|
||||
void renderEffect();
|
||||
bool ProcessRuntimePollResults();
|
||||
void RequestShaderBuild();
|
||||
void ProcessScreenshotRequest();
|
||||
RenderFrameInput BuildRenderFrameInput() const;
|
||||
void RenderFrame(const RenderFrameInput& frameInput);
|
||||
std::filesystem::path BuildScreenshotPath() const;
|
||||
void broadcastRuntimeState();
|
||||
void resetTemporalHistoryState();
|
||||
};
|
||||
|
||||
#endif // __OPENGL_COMPOSITE_H__
|
||||
@@ -0,0 +1,126 @@
|
||||
#include "OpenGLComposite.h"
|
||||
#include "RuntimeCoordinator.h"
|
||||
#include "RuntimeJson.h"
|
||||
#include "RuntimeServices.h"
|
||||
#include "RuntimeStore.h"
|
||||
#include "RuntimeUpdateController.h"
|
||||
|
||||
std::string OpenGLComposite::GetRuntimeStateJson() const
|
||||
{
|
||||
return mRuntimeStore ? mRuntimeStore->BuildPersistentStateJson() : "{}";
|
||||
}
|
||||
|
||||
unsigned short OpenGLComposite::GetControlServerPort() const
|
||||
{
|
||||
return mRuntimeStore ? mRuntimeStore->GetConfiguredControlServerPort() : 0;
|
||||
}
|
||||
|
||||
unsigned short OpenGLComposite::GetOscPort() const
|
||||
{
|
||||
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscPort() : 0;
|
||||
}
|
||||
|
||||
std::string OpenGLComposite::GetOscBindAddress() const
|
||||
{
|
||||
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscBindAddress() : "127.0.0.1";
|
||||
}
|
||||
|
||||
std::string OpenGLComposite::GetControlUrl() const
|
||||
{
|
||||
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/";
|
||||
}
|
||||
|
||||
std::string OpenGLComposite::GetDocsUrl() const
|
||||
{
|
||||
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/docs";
|
||||
}
|
||||
|
||||
std::string OpenGLComposite::GetOscAddress() const
|
||||
{
|
||||
return "udp://" + GetOscBindAddress() + ":" + std::to_string(GetOscPort()) + " /VideoShaderToys/{Layer}/{Parameter}";
|
||||
}
|
||||
|
||||
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
|
||||
{
|
||||
return mRuntimeCoordinator &&
|
||||
mRuntimeUpdateController &&
|
||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->AddLayer(shaderId), &error);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error)
|
||||
{
|
||||
return mRuntimeCoordinator &&
|
||||
mRuntimeUpdateController &&
|
||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RemoveLayer(layerId), &error);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error)
|
||||
{
|
||||
return mRuntimeCoordinator &&
|
||||
mRuntimeUpdateController &&
|
||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayer(layerId, direction), &error);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
||||
{
|
||||
return mRuntimeCoordinator &&
|
||||
mRuntimeUpdateController &&
|
||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayerToIndex(layerId, targetIndex), &error);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
|
||||
{
|
||||
return mRuntimeCoordinator &&
|
||||
mRuntimeUpdateController &&
|
||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerBypass(layerId, bypassed), &error);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
|
||||
{
|
||||
return mRuntimeCoordinator &&
|
||||
mRuntimeUpdateController &&
|
||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerShader(layerId, shaderId), &error);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error)
|
||||
{
|
||||
JsonValue parsedValue;
|
||||
if (!ParseJson(valueJson, parsedValue, error))
|
||||
return false;
|
||||
|
||||
return mRuntimeCoordinator &&
|
||||
mRuntimeUpdateController &&
|
||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameter(layerId, parameterId, parsedValue), &error);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
|
||||
{
|
||||
JsonValue parsedValue;
|
||||
if (!ParseJson(valueJson, parsedValue, error))
|
||||
return false;
|
||||
|
||||
return mRuntimeCoordinator &&
|
||||
mRuntimeUpdateController &&
|
||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue), &error);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error)
|
||||
{
|
||||
return mRuntimeCoordinator &&
|
||||
mRuntimeUpdateController &&
|
||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->ResetLayerParameters(layerId), &error);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error)
|
||||
{
|
||||
return mRuntimeCoordinator &&
|
||||
mRuntimeUpdateController &&
|
||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SaveStackPreset(presetName), &error);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error)
|
||||
{
|
||||
return mRuntimeCoordinator &&
|
||||
mRuntimeUpdateController &&
|
||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->LoadStackPreset(presetName), &error);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "ShaderTypes.h"
|
||||
#include "VideoIOTypes.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
struct RenderFrameInput
|
||||
{
|
||||
bool useCommittedLayerStates = false;
|
||||
bool hasInputSource = false;
|
||||
unsigned renderWidth = 0;
|
||||
unsigned renderHeight = 0;
|
||||
unsigned inputFrameWidth = 0;
|
||||
unsigned inputFrameHeight = 0;
|
||||
unsigned captureTextureWidth = 0;
|
||||
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
||||
unsigned historyCap = 0;
|
||||
double oscSmoothing = 0.0;
|
||||
};
|
||||
|
||||
struct RenderFrameState
|
||||
{
|
||||
bool hasInputSource = false;
|
||||
unsigned inputFrameWidth = 0;
|
||||
unsigned inputFrameHeight = 0;
|
||||
unsigned captureTextureWidth = 0;
|
||||
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
||||
unsigned historyCap = 0;
|
||||
std::vector<RuntimeRenderState> layerStates;
|
||||
};
|
||||
@@ -0,0 +1,119 @@
|
||||
#include "RenderFrameStateResolver.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150);
|
||||
}
|
||||
|
||||
RenderFrameStateResolver::RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider) :
|
||||
mRuntimeSnapshotProvider(runtimeSnapshotProvider)
|
||||
{
|
||||
}
|
||||
|
||||
void RenderFrameStateResolver::StoreCommittedSnapshot(
|
||||
const RuntimeRenderStateSnapshot& snapshot,
|
||||
const std::vector<RuntimeRenderState>& committedLayerStates)
|
||||
{
|
||||
mCachedLayerRenderStates = committedLayerStates;
|
||||
mCachedRenderStateVersion = snapshot.versions.renderStateVersion;
|
||||
mCachedParameterStateVersion = snapshot.versions.parameterStateVersion;
|
||||
mCachedRenderStateWidth = snapshot.outputWidth;
|
||||
mCachedRenderStateHeight = snapshot.outputHeight;
|
||||
}
|
||||
|
||||
bool RenderFrameStateResolver::Resolve(
|
||||
const RenderFrameInput& input,
|
||||
const std::vector<RuntimeRenderState>& committedLayerStates,
|
||||
RuntimeLiveState& liveState,
|
||||
std::vector<RuntimeLiveOscCommitRequest>* commitRequests,
|
||||
RenderFrameState& frameState)
|
||||
{
|
||||
frameState.hasInputSource = input.hasInputSource;
|
||||
frameState.inputFrameWidth = input.inputFrameWidth;
|
||||
frameState.inputFrameHeight = input.inputFrameHeight;
|
||||
frameState.captureTextureWidth = input.captureTextureWidth;
|
||||
frameState.inputPixelFormat = input.inputPixelFormat;
|
||||
frameState.historyCap = input.historyCap;
|
||||
frameState.layerStates.clear();
|
||||
|
||||
if (input.useCommittedLayerStates)
|
||||
{
|
||||
frameState.layerStates = ComposeLayerStates(committedLayerStates, liveState, false, input.oscSmoothing, commitRequests);
|
||||
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
|
||||
return true;
|
||||
}
|
||||
|
||||
const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider.GetVersions();
|
||||
const bool renderStateCacheValid =
|
||||
!mCachedLayerRenderStates.empty() &&
|
||||
mCachedRenderStateVersion == versions.renderStateVersion &&
|
||||
mCachedRenderStateWidth == input.renderWidth &&
|
||||
mCachedRenderStateHeight == input.renderHeight;
|
||||
|
||||
if (renderStateCacheValid)
|
||||
{
|
||||
RuntimeRenderStateSnapshot renderSnapshot;
|
||||
renderSnapshot.outputWidth = input.renderWidth;
|
||||
renderSnapshot.outputHeight = input.renderHeight;
|
||||
renderSnapshot.versions.renderStateVersion = mCachedRenderStateVersion;
|
||||
renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion;
|
||||
renderSnapshot.states = mCachedLayerRenderStates;
|
||||
|
||||
renderSnapshot.states = ComposeLayerStates(renderSnapshot.states, liveState, true, input.oscSmoothing, commitRequests);
|
||||
if (mCachedParameterStateVersion != versions.parameterStateVersion &&
|
||||
mRuntimeSnapshotProvider.TryRefreshPublishedSnapshotParameters(renderSnapshot))
|
||||
{
|
||||
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
|
||||
renderSnapshot.states = ComposeLayerStates(renderSnapshot.states, liveState, true, input.oscSmoothing, commitRequests);
|
||||
}
|
||||
|
||||
mCachedLayerRenderStates = renderSnapshot.states;
|
||||
frameState.layerStates = renderSnapshot.states;
|
||||
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
|
||||
return true;
|
||||
}
|
||||
|
||||
RuntimeRenderStateSnapshot renderSnapshot;
|
||||
if (mRuntimeSnapshotProvider.TryPublishRenderStateSnapshot(input.renderWidth, input.renderHeight, renderSnapshot))
|
||||
{
|
||||
mCachedLayerRenderStates = renderSnapshot.states;
|
||||
mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion;
|
||||
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
|
||||
mCachedRenderStateWidth = renderSnapshot.outputWidth;
|
||||
mCachedRenderStateHeight = renderSnapshot.outputHeight;
|
||||
mCachedLayerRenderStates = ComposeLayerStates(mCachedLayerRenderStates, liveState, true, input.oscSmoothing, commitRequests);
|
||||
frameState.layerStates = mCachedLayerRenderStates;
|
||||
return true;
|
||||
}
|
||||
|
||||
frameState.layerStates = ComposeLayerStates(mCachedLayerRenderStates, liveState, true, input.oscSmoothing, commitRequests);
|
||||
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
|
||||
return !frameState.layerStates.empty();
|
||||
}
|
||||
|
||||
std::vector<RuntimeRenderState> RenderFrameStateResolver::ComposeLayerStates(
|
||||
const std::vector<RuntimeRenderState>& baseStates,
|
||||
RuntimeLiveState& liveState,
|
||||
bool allowCommit,
|
||||
double smoothing,
|
||||
std::vector<RuntimeLiveOscCommitRequest>* commitRequests) const
|
||||
{
|
||||
LayeredRenderStateInput input;
|
||||
input.committedLiveLayerStates = &baseStates;
|
||||
input.transientAutomationOverlay = &liveState;
|
||||
input.allowTransientAutomationCommits = allowCommit;
|
||||
input.collectTransientAutomationCommitRequests = commitRequests != nullptr;
|
||||
input.transientAutomationSmoothing = smoothing;
|
||||
input.transientAutomationCommitDelay = kOscOverlayCommitDelay;
|
||||
input.now = std::chrono::steady_clock::now();
|
||||
const RenderStateCompositionResult result = mRenderStateComposer.BuildFrameState(input);
|
||||
|
||||
if (commitRequests)
|
||||
{
|
||||
for (const RuntimeLiveOscCommitRequest& request : result.commitRequests)
|
||||
commitRequests->push_back(request);
|
||||
}
|
||||
return result.layerStates;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "RenderFrameState.h"
|
||||
#include "RenderStateComposer.h"
|
||||
#include "RuntimeSnapshotProvider.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class RenderFrameStateResolver
|
||||
{
|
||||
public:
|
||||
explicit RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider);
|
||||
|
||||
void StoreCommittedSnapshot(
|
||||
const RuntimeRenderStateSnapshot& snapshot,
|
||||
const std::vector<RuntimeRenderState>& committedLayerStates);
|
||||
bool Resolve(
|
||||
const RenderFrameInput& input,
|
||||
const std::vector<RuntimeRenderState>& committedLayerStates,
|
||||
RuntimeLiveState& liveState,
|
||||
std::vector<RuntimeLiveOscCommitRequest>* commitRequests,
|
||||
RenderFrameState& frameState);
|
||||
|
||||
private:
|
||||
std::vector<RuntimeRenderState> ComposeLayerStates(
|
||||
const std::vector<RuntimeRenderState>& baseStates,
|
||||
RuntimeLiveState& liveState,
|
||||
bool allowCommit,
|
||||
double smoothing,
|
||||
std::vector<RuntimeLiveOscCommitRequest>* commitRequests) const;
|
||||
|
||||
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||
RenderStateComposer mRenderStateComposer;
|
||||
std::vector<RuntimeRenderState> mCachedLayerRenderStates;
|
||||
uint64_t mCachedRenderStateVersion = 0;
|
||||
uint64_t mCachedParameterStateVersion = 0;
|
||||
unsigned mCachedRenderStateWidth = 0;
|
||||
unsigned mCachedRenderStateHeight = 0;
|
||||
};
|
||||
@@ -0,0 +1,378 @@
|
||||
#include "RuntimeUpdateController.h"
|
||||
|
||||
#include "RenderEngine.h"
|
||||
#include "RuntimeEventDispatcher.h"
|
||||
#include "RuntimeServices.h"
|
||||
#include "RuntimeStore.h"
|
||||
#include "ShaderBuildQueue.h"
|
||||
#include "VideoBackend.h"
|
||||
|
||||
#include <variant>
|
||||
|
||||
namespace
|
||||
{
|
||||
RuntimeCoordinatorRenderResetScope ToRuntimeCoordinatorRenderResetScope(RuntimeEventRenderResetScope scope)
|
||||
{
|
||||
switch (scope)
|
||||
{
|
||||
case RuntimeEventRenderResetScope::TemporalHistoryOnly:
|
||||
return RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly;
|
||||
case RuntimeEventRenderResetScope::TemporalHistoryAndFeedback:
|
||||
return RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback;
|
||||
case RuntimeEventRenderResetScope::None:
|
||||
default:
|
||||
return RuntimeCoordinatorRenderResetScope::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RuntimeUpdateController::RuntimeUpdateController(
|
||||
RuntimeStore& runtimeStore,
|
||||
RuntimeCoordinator& runtimeCoordinator,
|
||||
RuntimeEventDispatcher& runtimeEventDispatcher,
|
||||
RuntimeServices& runtimeServices,
|
||||
RenderEngine& renderEngine,
|
||||
ShaderBuildQueue& shaderBuildQueue,
|
||||
VideoBackend& videoBackend) :
|
||||
mRuntimeStore(runtimeStore),
|
||||
mRuntimeCoordinator(runtimeCoordinator),
|
||||
mRuntimeEventDispatcher(runtimeEventDispatcher),
|
||||
mRuntimeServices(runtimeServices),
|
||||
mRenderEngine(renderEngine),
|
||||
mShaderBuildQueue(shaderBuildQueue),
|
||||
mVideoBackend(videoBackend)
|
||||
{
|
||||
mRuntimeEventDispatcher.Subscribe(
|
||||
RuntimeEventType::RuntimeStateBroadcastRequested,
|
||||
[this](const RuntimeEvent& event) { HandleRuntimeStateBroadcastRequested(event); });
|
||||
mRuntimeEventDispatcher.Subscribe(
|
||||
RuntimeEventType::RuntimeReloadRequested,
|
||||
[this](const RuntimeEvent& event) { HandleRuntimeReloadRequested(event); });
|
||||
mRuntimeEventDispatcher.Subscribe(
|
||||
RuntimeEventType::RuntimePersistenceRequested,
|
||||
[this](const RuntimeEvent& event) { HandleRuntimePersistenceRequested(event); });
|
||||
mRuntimeEventDispatcher.Subscribe(
|
||||
RuntimeEventType::ShaderBuildRequested,
|
||||
[this](const RuntimeEvent& event) { HandleShaderBuildRequested(event); });
|
||||
mRuntimeEventDispatcher.Subscribe(
|
||||
RuntimeEventType::ShaderBuildPrepared,
|
||||
[this](const RuntimeEvent& event) { HandleShaderBuildPrepared(event); });
|
||||
mRuntimeEventDispatcher.Subscribe(
|
||||
RuntimeEventType::ShaderBuildFailed,
|
||||
[this](const RuntimeEvent& event) { HandleShaderBuildFailed(event); });
|
||||
mRuntimeEventDispatcher.Subscribe(
|
||||
RuntimeEventType::CompileStatusChanged,
|
||||
[this](const RuntimeEvent& event) { HandleCompileStatusChanged(event); });
|
||||
mRuntimeEventDispatcher.Subscribe(
|
||||
RuntimeEventType::RenderResetRequested,
|
||||
[this](const RuntimeEvent& event) { HandleRenderResetRequested(event); });
|
||||
}
|
||||
|
||||
bool RuntimeUpdateController::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error)
|
||||
{
|
||||
if (!result.accepted)
|
||||
{
|
||||
if (error)
|
||||
*error = result.errorMessage;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.compileStatusChanged)
|
||||
{
|
||||
mRuntimeStore.SetCompileStatus(result.compileStatusSucceeded, result.compileStatusMessage);
|
||||
++mPendingCoordinatorCompileStatusEvents;
|
||||
}
|
||||
|
||||
if (result.clearReloadRequest)
|
||||
mRuntimeStore.ClearReloadRequest();
|
||||
|
||||
mRuntimeCoordinator.ApplyCommittedStateMode(result.committedStateMode);
|
||||
|
||||
switch (result.transientOscInvalidation)
|
||||
{
|
||||
case RuntimeCoordinatorTransientOscInvalidation::All:
|
||||
mRenderEngine.ClearOscOverlayState();
|
||||
mRuntimeServices.ClearOscState();
|
||||
break;
|
||||
case RuntimeCoordinatorTransientOscInvalidation::Layer:
|
||||
mRenderEngine.ClearOscOverlayStateForLayerKey(result.transientOscLayerKey);
|
||||
mRuntimeServices.ClearOscStateForLayerKey(result.transientOscLayerKey);
|
||||
break;
|
||||
case RuntimeCoordinatorTransientOscInvalidation::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
mRenderEngine.ApplyRuntimeCoordinatorRenderReset(result.renderResetScope);
|
||||
if (result.renderResetScope != RuntimeCoordinatorRenderResetScope::None)
|
||||
++mPendingCoordinatorRenderResetEvents;
|
||||
|
||||
if (result.shaderBuildRequested)
|
||||
{
|
||||
RequestShaderBuild();
|
||||
++mPendingCoordinatorShaderBuildEvents;
|
||||
}
|
||||
|
||||
if (result.runtimeStateBroadcastRequired)
|
||||
BroadcastRuntimeState();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeUpdateController::ProcessRuntimeWork()
|
||||
{
|
||||
DispatchRuntimeEvents();
|
||||
|
||||
return ConsumeReadyShaderBuild(0, true, true);
|
||||
}
|
||||
|
||||
void RuntimeUpdateController::RequestShaderBuild()
|
||||
{
|
||||
mShaderBuildQueue.RequestBuild(mVideoBackend.InputFrameWidth(), mVideoBackend.InputFrameHeight());
|
||||
}
|
||||
|
||||
void RuntimeUpdateController::BroadcastRuntimeState()
|
||||
{
|
||||
RuntimeStateBroadcastRequestedEvent event;
|
||||
event.reason = "runtime-state-changed";
|
||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeUpdateController"))
|
||||
{
|
||||
mRuntimeServices.BroadcastState();
|
||||
return;
|
||||
}
|
||||
|
||||
DispatchRuntimeEvents();
|
||||
}
|
||||
|
||||
void RuntimeUpdateController::HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event)
|
||||
{
|
||||
if (event.source == "ControlServices")
|
||||
return;
|
||||
|
||||
mRuntimeServices.BroadcastState();
|
||||
}
|
||||
|
||||
void RuntimeUpdateController::HandleRuntimeReloadRequested(const RuntimeEvent& event)
|
||||
{
|
||||
const RuntimeReloadRequestedEvent* payload = std::get_if<RuntimeReloadRequestedEvent>(&event.payload);
|
||||
if (!payload)
|
||||
return;
|
||||
|
||||
mRuntimeStore.ClearReloadRequest();
|
||||
}
|
||||
|
||||
void RuntimeUpdateController::HandleRuntimePersistenceRequested(const RuntimeEvent& event)
|
||||
{
|
||||
const RuntimePersistenceRequestedEvent* payload = std::get_if<RuntimePersistenceRequestedEvent>(&event.payload);
|
||||
if (!payload)
|
||||
return;
|
||||
|
||||
std::string error;
|
||||
mRuntimeStore.RequestPersistence(payload->request, error);
|
||||
}
|
||||
|
||||
void RuntimeUpdateController::HandleShaderBuildRequested(const RuntimeEvent& event)
|
||||
{
|
||||
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
|
||||
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Requested)
|
||||
return;
|
||||
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorShaderBuildEvents))
|
||||
return;
|
||||
|
||||
RequestShaderBuild();
|
||||
}
|
||||
|
||||
void RuntimeUpdateController::HandleShaderBuildPrepared(const RuntimeEvent& event)
|
||||
{
|
||||
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
|
||||
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Prepared)
|
||||
return;
|
||||
|
||||
ConsumeReadyShaderBuild(payload->generation, false, true);
|
||||
}
|
||||
|
||||
void RuntimeUpdateController::HandleShaderBuildFailed(const RuntimeEvent& event)
|
||||
{
|
||||
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
|
||||
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Failed)
|
||||
return;
|
||||
|
||||
ConsumeReadyShaderBuild(payload->generation, false, false);
|
||||
}
|
||||
|
||||
void RuntimeUpdateController::HandleCompileStatusChanged(const RuntimeEvent& event)
|
||||
{
|
||||
const CompileStatusChangedEvent* payload = std::get_if<CompileStatusChangedEvent>(&event.payload);
|
||||
if (!payload)
|
||||
return;
|
||||
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorCompileStatusEvents))
|
||||
return;
|
||||
|
||||
mRuntimeStore.SetCompileStatus(payload->succeeded, payload->message);
|
||||
}
|
||||
|
||||
void RuntimeUpdateController::HandleRenderResetRequested(const RuntimeEvent& event)
|
||||
{
|
||||
const RenderResetEvent* payload = std::get_if<RenderResetEvent>(&event.payload);
|
||||
if (!payload || payload->applied)
|
||||
return;
|
||||
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorRenderResetEvents))
|
||||
return;
|
||||
|
||||
mRenderEngine.ApplyRuntimeCoordinatorRenderReset(ToRuntimeCoordinatorRenderResetScope(payload->scope));
|
||||
}
|
||||
|
||||
bool RuntimeUpdateController::ConsumeReadyShaderBuild(uint64_t expectedGeneration, bool publishPreparedEvent, bool publishFailureEvent)
|
||||
{
|
||||
PreparedShaderBuild readyBuild;
|
||||
const bool consumed = expectedGeneration == 0
|
||||
? mShaderBuildQueue.TryConsumeReadyBuild(readyBuild)
|
||||
: mShaderBuildQueue.TryConsumeReadyBuild(expectedGeneration, readyBuild);
|
||||
if (!consumed)
|
||||
return true;
|
||||
|
||||
const unsigned inputWidth = mVideoBackend.InputFrameWidth();
|
||||
const unsigned inputHeight = mVideoBackend.InputFrameHeight();
|
||||
if (!readyBuild.succeeded)
|
||||
{
|
||||
if (publishFailureEvent)
|
||||
{
|
||||
PublishShaderBuildLifecycleEvent(
|
||||
RuntimeEventShaderBuildPhase::Failed,
|
||||
readyBuild.generation,
|
||||
inputWidth,
|
||||
inputHeight,
|
||||
false,
|
||||
readyBuild.message);
|
||||
DispatchRuntimeEvents();
|
||||
}
|
||||
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(readyBuild.message));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (publishPreparedEvent)
|
||||
{
|
||||
PublishShaderBuildLifecycleEvent(
|
||||
RuntimeEventShaderBuildPhase::Prepared,
|
||||
readyBuild.generation,
|
||||
inputWidth,
|
||||
inputHeight,
|
||||
true,
|
||||
readyBuild.message);
|
||||
DispatchRuntimeEvents();
|
||||
}
|
||||
|
||||
char compilerErrorMessage[1024] = {};
|
||||
if (!mRenderEngine.ApplyPreparedShaderBuild(
|
||||
readyBuild,
|
||||
inputWidth,
|
||||
inputHeight,
|
||||
mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild(),
|
||||
sizeof(compilerErrorMessage),
|
||||
compilerErrorMessage))
|
||||
{
|
||||
const std::string errorMessage = compilerErrorMessage;
|
||||
if (publishFailureEvent)
|
||||
{
|
||||
PublishShaderBuildLifecycleEvent(
|
||||
RuntimeEventShaderBuildPhase::Failed,
|
||||
readyBuild.generation,
|
||||
inputWidth,
|
||||
inputHeight,
|
||||
false,
|
||||
errorMessage);
|
||||
DispatchRuntimeEvents();
|
||||
}
|
||||
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(errorMessage));
|
||||
return false;
|
||||
}
|
||||
|
||||
PublishShaderBuildLifecycleEvent(
|
||||
RuntimeEventShaderBuildPhase::Applied,
|
||||
readyBuild.generation,
|
||||
inputWidth,
|
||||
inputHeight,
|
||||
true,
|
||||
"Shader layers applied successfully.");
|
||||
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildSuccess());
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeUpdateController::PublishShaderBuildLifecycleEvent(
|
||||
RuntimeEventShaderBuildPhase phase,
|
||||
uint64_t generation,
|
||||
unsigned inputWidth,
|
||||
unsigned inputHeight,
|
||||
bool succeeded,
|
||||
const std::string& message)
|
||||
{
|
||||
ShaderBuildEvent event;
|
||||
event.phase = phase;
|
||||
event.generation = generation;
|
||||
event.inputWidth = inputWidth;
|
||||
event.inputHeight = inputHeight;
|
||||
event.preserveFeedbackState = mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild();
|
||||
event.succeeded = succeeded;
|
||||
event.message = message;
|
||||
mRuntimeEventDispatcher.PublishPayload(event, "RuntimeUpdateController");
|
||||
}
|
||||
|
||||
bool RuntimeUpdateController::ShouldSuppressCoordinatorFollowUp(const RuntimeEvent& event, std::size_t& pendingSuppressions)
|
||||
{
|
||||
if (event.source != "RuntimeCoordinator")
|
||||
return false;
|
||||
|
||||
if (pendingSuppressions > 0)
|
||||
--pendingSuppressions;
|
||||
return true;
|
||||
}
|
||||
|
||||
RuntimeEventDispatchResult RuntimeUpdateController::DispatchRuntimeEvents(std::size_t maxEvents)
|
||||
{
|
||||
RuntimeEventDispatchResult result = mRuntimeEventDispatcher.DispatchPending(maxEvents);
|
||||
const RuntimeEventQueueMetrics queueMetrics = mRuntimeEventDispatcher.GetQueueMetrics();
|
||||
HealthTelemetry& telemetry = mRuntimeStore.GetHealthTelemetry();
|
||||
telemetry.TryRecordRuntimeEventDispatchStats(
|
||||
result.dispatchedEvents,
|
||||
result.handlerInvocations,
|
||||
result.handlerFailures,
|
||||
result.dispatchDurationMilliseconds);
|
||||
telemetry.TryRecordRuntimeEventQueueMetrics(
|
||||
"runtime-events",
|
||||
queueMetrics.depth,
|
||||
queueMetrics.capacity,
|
||||
static_cast<uint64_t>(queueMetrics.droppedCount),
|
||||
queueMetrics.oldestEventAgeMilliseconds);
|
||||
PublishRuntimeEventHealthObservations(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void RuntimeUpdateController::PublishRuntimeEventHealthObservations(const RuntimeEventDispatchResult& result)
|
||||
{
|
||||
const RuntimeEventQueueMetrics queueMetrics = mRuntimeEventDispatcher.GetQueueMetrics();
|
||||
if (queueMetrics.depth != mLastReportedRuntimeEventQueueDepth ||
|
||||
queueMetrics.droppedCount != mLastReportedRuntimeEventDroppedCount ||
|
||||
queueMetrics.coalescedCount != mLastReportedRuntimeEventCoalescedCount)
|
||||
{
|
||||
QueueDepthChangedEvent queueDepth;
|
||||
queueDepth.queueName = "runtime-events";
|
||||
queueDepth.depth = queueMetrics.depth;
|
||||
queueDepth.capacity = queueMetrics.capacity;
|
||||
queueDepth.droppedCount = queueMetrics.droppedCount;
|
||||
queueDepth.coalescedCount = queueMetrics.coalescedCount;
|
||||
mRuntimeEventDispatcher.PublishPayload(queueDepth, "HealthTelemetry");
|
||||
mLastReportedRuntimeEventQueueDepth = queueMetrics.depth;
|
||||
mLastReportedRuntimeEventDroppedCount = queueMetrics.droppedCount;
|
||||
mLastReportedRuntimeEventCoalescedCount = queueMetrics.coalescedCount;
|
||||
}
|
||||
|
||||
if (result.handlerInvocations == 0 && result.handlerFailures == 0)
|
||||
return;
|
||||
|
||||
TimingSampleRecordedEvent timing;
|
||||
timing.subsystem = "RuntimeEventDispatcher";
|
||||
timing.metric = "dispatchDuration";
|
||||
timing.value = result.dispatchDurationMilliseconds;
|
||||
timing.unit = "ms";
|
||||
mRuntimeEventDispatcher.PublishPayload(timing, "HealthTelemetry");
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeCoordinator.h"
|
||||
#include "RuntimeEventPayloads.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
class RenderEngine;
|
||||
struct RuntimeEvent;
|
||||
struct RuntimeEventDispatchResult;
|
||||
class RuntimeEventDispatcher;
|
||||
class RuntimeServices;
|
||||
class RuntimeStore;
|
||||
class ShaderBuildQueue;
|
||||
class VideoBackend;
|
||||
|
||||
class RuntimeUpdateController
|
||||
{
|
||||
public:
|
||||
RuntimeUpdateController(
|
||||
RuntimeStore& runtimeStore,
|
||||
RuntimeCoordinator& runtimeCoordinator,
|
||||
RuntimeEventDispatcher& runtimeEventDispatcher,
|
||||
RuntimeServices& runtimeServices,
|
||||
RenderEngine& renderEngine,
|
||||
ShaderBuildQueue& shaderBuildQueue,
|
||||
VideoBackend& videoBackend);
|
||||
|
||||
bool ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error = nullptr);
|
||||
bool ProcessRuntimeWork();
|
||||
void RequestShaderBuild();
|
||||
void BroadcastRuntimeState();
|
||||
|
||||
private:
|
||||
void HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event);
|
||||
void HandleRuntimeReloadRequested(const RuntimeEvent& event);
|
||||
void HandleRuntimePersistenceRequested(const RuntimeEvent& event);
|
||||
void HandleShaderBuildRequested(const RuntimeEvent& event);
|
||||
void HandleShaderBuildPrepared(const RuntimeEvent& event);
|
||||
void HandleShaderBuildFailed(const RuntimeEvent& event);
|
||||
void HandleCompileStatusChanged(const RuntimeEvent& event);
|
||||
void HandleRenderResetRequested(const RuntimeEvent& event);
|
||||
bool ConsumeReadyShaderBuild(uint64_t expectedGeneration, bool publishPreparedEvent, bool publishFailureEvent);
|
||||
void PublishShaderBuildLifecycleEvent(
|
||||
RuntimeEventShaderBuildPhase phase,
|
||||
uint64_t generation,
|
||||
unsigned inputWidth,
|
||||
unsigned inputHeight,
|
||||
bool succeeded,
|
||||
const std::string& message);
|
||||
bool ShouldSuppressCoordinatorFollowUp(const RuntimeEvent& event, std::size_t& pendingSuppressions);
|
||||
RuntimeEventDispatchResult DispatchRuntimeEvents(std::size_t maxEvents = 0);
|
||||
void PublishRuntimeEventHealthObservations(const RuntimeEventDispatchResult& result);
|
||||
|
||||
RuntimeStore& mRuntimeStore;
|
||||
RuntimeCoordinator& mRuntimeCoordinator;
|
||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
||||
RuntimeServices& mRuntimeServices;
|
||||
RenderEngine& mRenderEngine;
|
||||
ShaderBuildQueue& mShaderBuildQueue;
|
||||
VideoBackend& mVideoBackend;
|
||||
std::size_t mPendingCoordinatorShaderBuildEvents = 0;
|
||||
std::size_t mPendingCoordinatorCompileStatusEvents = 0;
|
||||
std::size_t mPendingCoordinatorRenderResetEvents = 0;
|
||||
std::size_t mLastReportedRuntimeEventQueueDepth = static_cast<std::size_t>(-1);
|
||||
std::size_t mLastReportedRuntimeEventDroppedCount = static_cast<std::size_t>(-1);
|
||||
std::size_t mLastReportedRuntimeEventCoalescedCount = static_cast<std::size_t>(-1);
|
||||
};
|
||||
@@ -1,25 +1,32 @@
|
||||
#include "OpenGLRenderPipeline.h"
|
||||
|
||||
#include "HealthTelemetry.h"
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "RuntimeHost.h"
|
||||
#include "RuntimeSnapshotProvider.h"
|
||||
#include "VideoIOFormat.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <gl/gl.h>
|
||||
#include <string>
|
||||
|
||||
OpenGLRenderPipeline::OpenGLRenderPipeline(
|
||||
OpenGLRenderer& renderer,
|
||||
RuntimeHost& runtimeHost,
|
||||
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
||||
HealthTelemetry& healthTelemetry,
|
||||
RenderEffectCallback renderEffect,
|
||||
OutputReadyCallback outputReady,
|
||||
PaintCallback paint) :
|
||||
mRenderer(renderer),
|
||||
mRuntimeHost(runtimeHost),
|
||||
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
||||
mHealthTelemetry(healthTelemetry),
|
||||
mRenderEffect(renderEffect),
|
||||
mOutputReady(outputReady),
|
||||
mPaint(paint)
|
||||
mPaint(paint),
|
||||
mOutputReadbackMode(ReadOutputReadbackModeFromEnvironment()),
|
||||
mAsyncReadbackDepth(ReadAsyncReadbackDepthFromEnvironment())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -41,22 +48,55 @@ bool OpenGLRenderPipeline::RenderFrame(const RenderPipelineFrameContext& context
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
||||
if (mOutputReady)
|
||||
mOutputReady();
|
||||
if (state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10)
|
||||
if (state.outputPixelFormat == VideoIOPixelFormat::Bgra8)
|
||||
PackOutputForBgra8(state);
|
||||
else if (state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10)
|
||||
PackOutputFor10Bit(state);
|
||||
glFlush();
|
||||
|
||||
const auto renderEndTime = std::chrono::steady_clock::now();
|
||||
const double renderMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(renderEndTime - renderStartTime).count();
|
||||
mRuntimeHost.TrySetPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds);
|
||||
mRuntimeHost.TryAdvanceFrame();
|
||||
mHealthTelemetry.TryRecordPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds);
|
||||
mRuntimeSnapshotProvider.AdvanceFrame();
|
||||
|
||||
ReadOutputFrame(state, outputFrame);
|
||||
if (mPaint)
|
||||
mPaint();
|
||||
OutputReadbackTiming readbackTiming = ReadOutputFrame(state, outputFrame);
|
||||
mHealthTelemetry.TryRecordOutputRenderPipelineTiming(
|
||||
renderMilliseconds,
|
||||
readbackTiming.fenceWaitMilliseconds,
|
||||
readbackTiming.mapMilliseconds,
|
||||
readbackTiming.copyMilliseconds,
|
||||
readbackTiming.cachedCopyMilliseconds,
|
||||
readbackTiming.asyncQueueMilliseconds,
|
||||
readbackTiming.asyncQueueBufferMilliseconds,
|
||||
readbackTiming.asyncQueueSetupMilliseconds,
|
||||
readbackTiming.asyncQueueReadPixelsMilliseconds,
|
||||
readbackTiming.asyncQueueFenceMilliseconds,
|
||||
readbackTiming.syncReadMilliseconds,
|
||||
readbackTiming.asyncReadbackMissed,
|
||||
readbackTiming.cachedFallbackUsed,
|
||||
readbackTiming.syncFallbackUsed);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLRenderPipeline::PackOutputForBgra8(const VideoIOState& state)
|
||||
{
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
||||
glBlitFramebuffer(
|
||||
0,
|
||||
0,
|
||||
state.outputFrameSize.width,
|
||||
state.outputFrameSize.height,
|
||||
0,
|
||||
0,
|
||||
state.outputFrameSize.width,
|
||||
state.outputFrameSize.height,
|
||||
GL_COLOR_BUFFER_BIT,
|
||||
GL_NEAREST);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
||||
}
|
||||
|
||||
void OpenGLRenderPipeline::PackOutputFor10Bit(const VideoIOState& state)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
||||
@@ -90,11 +130,17 @@ bool OpenGLRenderPipeline::EnsureAsyncReadbackBuffers(std::size_t requiredBytes)
|
||||
if (requiredBytes == 0)
|
||||
return false;
|
||||
|
||||
if (mAsyncReadbackBytes == requiredBytes && mAsyncReadbackSlots[0].pixelPackBuffer != 0)
|
||||
if (mAsyncReadbackBytes == requiredBytes &&
|
||||
mAsyncReadbackSlots.size() == mAsyncReadbackDepth &&
|
||||
!mAsyncReadbackSlots.empty() &&
|
||||
mAsyncReadbackSlots[0].pixelPackBuffer != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ResetAsyncReadbackState();
|
||||
mAsyncReadbackBytes = requiredBytes;
|
||||
mAsyncReadbackSlots.resize(mAsyncReadbackDepth);
|
||||
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
||||
{
|
||||
glGenBuffers(1, &slot.pixelPackBuffer);
|
||||
@@ -115,7 +161,7 @@ void OpenGLRenderPipeline::ResetAsyncReadbackState()
|
||||
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
||||
slot.sizeBytes = 0;
|
||||
|
||||
if (mAsyncReadbackSlots[0].pixelPackBuffer != 0)
|
||||
if (!mAsyncReadbackSlots.empty() && mAsyncReadbackSlots[0].pixelPackBuffer != 0)
|
||||
{
|
||||
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
||||
{
|
||||
@@ -130,6 +176,7 @@ void OpenGLRenderPipeline::ResetAsyncReadbackState()
|
||||
mAsyncReadbackWriteIndex = 0;
|
||||
mAsyncReadbackReadIndex = 0;
|
||||
mAsyncReadbackBytes = 0;
|
||||
mAsyncReadbackSlots.clear();
|
||||
}
|
||||
|
||||
void OpenGLRenderPipeline::FlushAsyncReadbackPipeline()
|
||||
@@ -148,18 +195,29 @@ void OpenGLRenderPipeline::FlushAsyncReadbackPipeline()
|
||||
mAsyncReadbackReadIndex = 0;
|
||||
}
|
||||
|
||||
void OpenGLRenderPipeline::QueueAsyncReadback(const VideoIOState& state)
|
||||
bool OpenGLRenderPipeline::QueueAsyncReadback(const VideoIOState& state, OutputReadbackTiming& timing)
|
||||
{
|
||||
const bool usePackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10;
|
||||
const auto queueStartTime = std::chrono::steady_clock::now();
|
||||
const bool useTenBitPackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 ||
|
||||
state.outputPixelFormat == VideoIOPixelFormat::Yuva10;
|
||||
const bool usePackFramebuffer = state.outputPixelFormat == VideoIOPixelFormat::Bgra8 || useTenBitPackedOutput;
|
||||
const std::size_t requiredBytes = static_cast<std::size_t>(state.outputFrameRowBytes) * state.outputFrameSize.height;
|
||||
const GLenum format = usePackedOutput ? GL_RGBA : GL_BGRA;
|
||||
const GLenum type = usePackedOutput ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
const GLuint framebuffer = usePackedOutput ? mRenderer.OutputPackFramebuffer() : mRenderer.OutputFramebuffer();
|
||||
const GLsizei readWidth = static_cast<GLsizei>(usePackedOutput ? state.outputPackTextureWidth : state.outputFrameSize.width);
|
||||
const GLenum format = useTenBitPackedOutput ? GL_RGBA : GL_BGRA;
|
||||
const GLenum type = useTenBitPackedOutput ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
const GLuint framebuffer = usePackFramebuffer ? mRenderer.OutputPackFramebuffer() : mRenderer.OutputFramebuffer();
|
||||
const GLsizei readWidth = static_cast<GLsizei>(useTenBitPackedOutput ? state.outputPackTextureWidth : state.outputFrameSize.width);
|
||||
const GLsizei readHeight = static_cast<GLsizei>(state.outputFrameSize.height);
|
||||
|
||||
const auto finishTiming = [&timing, queueStartTime]() {
|
||||
const auto queueEndTime = std::chrono::steady_clock::now();
|
||||
timing.asyncQueueMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(queueEndTime - queueStartTime).count();
|
||||
};
|
||||
|
||||
if (requiredBytes == 0)
|
||||
return;
|
||||
{
|
||||
finishTiming();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mAsyncReadbackBytes != requiredBytes
|
||||
|| mAsyncReadbackFormat != format
|
||||
@@ -170,30 +228,56 @@ void OpenGLRenderPipeline::QueueAsyncReadback(const VideoIOState& state)
|
||||
mAsyncReadbackType = type;
|
||||
mAsyncReadbackFramebuffer = framebuffer;
|
||||
if (!EnsureAsyncReadbackBuffers(requiredBytes))
|
||||
return;
|
||||
{
|
||||
finishTiming();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mAsyncReadbackSlots.empty())
|
||||
{
|
||||
finishTiming();
|
||||
return false;
|
||||
}
|
||||
|
||||
AsyncReadbackSlot& slot = mAsyncReadbackSlots[mAsyncReadbackWriteIndex];
|
||||
if (slot.fence != nullptr)
|
||||
if (slot.inFlight)
|
||||
{
|
||||
glDeleteSync(slot.fence);
|
||||
slot.fence = nullptr;
|
||||
finishTiming();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto stageStartTime = std::chrono::steady_clock::now();
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
|
||||
auto stageEndTime = std::chrono::steady_clock::now();
|
||||
timing.asyncQueueSetupMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
|
||||
|
||||
stageStartTime = std::chrono::steady_clock::now();
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(requiredBytes), nullptr, GL_STREAM_READ);
|
||||
stageEndTime = std::chrono::steady_clock::now();
|
||||
timing.asyncQueueBufferMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
|
||||
|
||||
stageStartTime = std::chrono::steady_clock::now();
|
||||
glReadPixels(0, 0, readWidth, readHeight, format, type, nullptr);
|
||||
stageEndTime = std::chrono::steady_clock::now();
|
||||
timing.asyncQueueReadPixelsMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
|
||||
|
||||
stageStartTime = std::chrono::steady_clock::now();
|
||||
slot.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
stageEndTime = std::chrono::steady_clock::now();
|
||||
timing.asyncQueueFenceMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
|
||||
slot.inFlight = slot.fence != nullptr;
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
mAsyncReadbackWriteIndex = (mAsyncReadbackWriteIndex + 1) % mAsyncReadbackSlots.size();
|
||||
finishTiming();
|
||||
return slot.inFlight;
|
||||
}
|
||||
|
||||
bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds)
|
||||
bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds, OutputReadbackTiming& timing)
|
||||
{
|
||||
if (mAsyncReadbackBytes == 0 || outputFrame.bytes == nullptr)
|
||||
return false;
|
||||
@@ -203,15 +287,24 @@ bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFra
|
||||
return false;
|
||||
|
||||
const GLenum waitFlags = timeoutNanoseconds > 0 ? GL_SYNC_FLUSH_COMMANDS_BIT : 0;
|
||||
const auto waitStartTime = std::chrono::steady_clock::now();
|
||||
const GLenum waitResult = glClientWaitSync(slot.fence, waitFlags, timeoutNanoseconds);
|
||||
const auto waitEndTime = std::chrono::steady_clock::now();
|
||||
timing.fenceWaitMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(waitEndTime - waitStartTime).count();
|
||||
if (waitResult != GL_ALREADY_SIGNALED && waitResult != GL_CONDITION_SATISFIED)
|
||||
{
|
||||
timing.asyncReadbackMissed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
glDeleteSync(slot.fence);
|
||||
slot.fence = nullptr;
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
|
||||
const auto mapStartTime = std::chrono::steady_clock::now();
|
||||
void* mappedBytes = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
||||
const auto mapEndTime = std::chrono::steady_clock::now();
|
||||
timing.mapMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(mapEndTime - mapStartTime).count();
|
||||
if (mappedBytes == nullptr)
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
@@ -220,7 +313,10 @@ bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFra
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto copyStartTime = std::chrono::steady_clock::now();
|
||||
std::memcpy(outputFrame.bytes, mappedBytes, slot.sizeBytes);
|
||||
const auto copyEndTime = std::chrono::steady_clock::now();
|
||||
timing.copyMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(copyEndTime - copyStartTime).count();
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
@@ -240,40 +336,144 @@ void OpenGLRenderPipeline::CacheOutputFrame(const VideoIOOutputFrame& outputFram
|
||||
std::memcpy(mCachedOutputFrame.data(), outputFrame.bytes, byteCount);
|
||||
}
|
||||
|
||||
void OpenGLRenderPipeline::ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes)
|
||||
bool OpenGLRenderPipeline::TryCopyCachedOutputFrame(VideoIOOutputFrame& outputFrame, OutputReadbackTiming& timing) const
|
||||
{
|
||||
if (outputFrame.bytes == nullptr || outputFrame.height == 0 || outputFrame.rowBytes <= 0)
|
||||
return false;
|
||||
|
||||
const std::size_t byteCount = static_cast<std::size_t>(outputFrame.rowBytes) * outputFrame.height;
|
||||
if (mCachedOutputFrame.size() != byteCount)
|
||||
return false;
|
||||
|
||||
const auto copyStartTime = std::chrono::steady_clock::now();
|
||||
std::memcpy(outputFrame.bytes, mCachedOutputFrame.data(), byteCount);
|
||||
const auto copyEndTime = std::chrono::steady_clock::now();
|
||||
timing.cachedCopyMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(copyEndTime - copyStartTime).count();
|
||||
timing.cachedFallbackUsed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLRenderPipeline::ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes, OutputReadbackTiming& timing)
|
||||
{
|
||||
const auto readStartTime = std::chrono::steady_clock::now();
|
||||
const bool usePackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10;
|
||||
const bool usePackFramebuffer = state.outputPixelFormat == VideoIOPixelFormat::Bgra8 || usePackedOutput;
|
||||
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
if (usePackedOutput)
|
||||
if (usePackFramebuffer)
|
||||
{
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
||||
glReadPixels(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, destinationBytes);
|
||||
if (usePackedOutput)
|
||||
glReadPixels(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, destinationBytes);
|
||||
else
|
||||
glReadPixels(0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, destinationBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
||||
glReadPixels(0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, destinationBytes);
|
||||
}
|
||||
const auto readEndTime = std::chrono::steady_clock::now();
|
||||
timing.syncReadMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(readEndTime - readStartTime).count();
|
||||
timing.syncFallbackUsed = true;
|
||||
}
|
||||
|
||||
void OpenGLRenderPipeline::ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame)
|
||||
OpenGLRenderPipeline::OutputReadbackTiming OpenGLRenderPipeline::ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame)
|
||||
{
|
||||
if (TryConsumeAsyncReadback(outputFrame, 500000))
|
||||
OutputReadbackTiming timing;
|
||||
|
||||
if (mOutputReadbackMode == OutputReadbackMode::Synchronous)
|
||||
{
|
||||
QueueAsyncReadback(state);
|
||||
return;
|
||||
if (outputFrame.bytes != nullptr)
|
||||
{
|
||||
ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
|
||||
CacheOutputFrame(outputFrame);
|
||||
}
|
||||
return timing;
|
||||
}
|
||||
|
||||
// If async readback misses the playout deadline, prefer a fresh synchronous
|
||||
// frame over reusing stale cached output, then restart the async pipeline.
|
||||
if (outputFrame.bytes != nullptr)
|
||||
if (mOutputReadbackMode == OutputReadbackMode::CachedOnly)
|
||||
{
|
||||
ReadOutputFrameSynchronously(state, outputFrame.bytes);
|
||||
if (TryCopyCachedOutputFrame(outputFrame, timing))
|
||||
return timing;
|
||||
|
||||
if (outputFrame.bytes != nullptr)
|
||||
{
|
||||
ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
|
||||
CacheOutputFrame(outputFrame);
|
||||
}
|
||||
return timing;
|
||||
}
|
||||
|
||||
if (TryConsumeAsyncReadback(outputFrame, 0, timing))
|
||||
{
|
||||
(void)QueueAsyncReadback(state, timing);
|
||||
return timing;
|
||||
}
|
||||
|
||||
const bool queued = QueueAsyncReadback(state, timing);
|
||||
|
||||
if (queued && TryConsumeAsyncReadback(outputFrame, 0, timing))
|
||||
return timing;
|
||||
|
||||
if (TryCopyCachedOutputFrame(outputFrame, timing))
|
||||
{
|
||||
return timing;
|
||||
}
|
||||
|
||||
// Bootstrap only: until the first async readback has produced cached output,
|
||||
// use one synchronous readback so DeckLink has a valid frame to schedule.
|
||||
if (outputFrame.bytes != nullptr && mCachedOutputFrame.empty())
|
||||
{
|
||||
ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
|
||||
CacheOutputFrame(outputFrame);
|
||||
}
|
||||
|
||||
FlushAsyncReadbackPipeline();
|
||||
QueueAsyncReadback(state);
|
||||
if (!queued)
|
||||
(void)QueueAsyncReadback(state, timing);
|
||||
return timing;
|
||||
}
|
||||
|
||||
OpenGLRenderPipeline::OutputReadbackMode OpenGLRenderPipeline::ReadOutputReadbackModeFromEnvironment()
|
||||
{
|
||||
char* mode = nullptr;
|
||||
std::size_t modeSize = 0;
|
||||
if (_dupenv_s(&mode, &modeSize, "VST_OUTPUT_READBACK_MODE") != 0 || mode == nullptr)
|
||||
return OutputReadbackMode::AsyncPbo;
|
||||
|
||||
const std::string modeValue(mode);
|
||||
std::free(mode);
|
||||
if (modeValue == "async_pbo")
|
||||
return OutputReadbackMode::AsyncPbo;
|
||||
if (modeValue == "sync")
|
||||
return OutputReadbackMode::Synchronous;
|
||||
if (modeValue == "cached_only")
|
||||
return OutputReadbackMode::CachedOnly;
|
||||
|
||||
return OutputReadbackMode::AsyncPbo;
|
||||
}
|
||||
|
||||
std::size_t OpenGLRenderPipeline::ReadAsyncReadbackDepthFromEnvironment()
|
||||
{
|
||||
char* depthValue = nullptr;
|
||||
std::size_t depthValueSize = 0;
|
||||
if (_dupenv_s(&depthValue, &depthValueSize, "VST_OUTPUT_READBACK_DEPTH") != 0 || depthValue == nullptr)
|
||||
return 6;
|
||||
|
||||
const std::string value(depthValue);
|
||||
std::free(depthValue);
|
||||
try
|
||||
{
|
||||
const unsigned long requestedDepth = std::stoul(value);
|
||||
if (requestedDepth < 3)
|
||||
return 3;
|
||||
if (requestedDepth > 12)
|
||||
return 12;
|
||||
return static_cast<std::size_t>(requestedDepth);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
#include "GLExtensions.h"
|
||||
#include "VideoIOTypes.h"
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
class OpenGLRenderer;
|
||||
class RuntimeHost;
|
||||
class HealthTelemetry;
|
||||
class RuntimeSnapshotProvider;
|
||||
|
||||
struct RenderPipelineFrameContext
|
||||
{
|
||||
@@ -25,7 +25,8 @@ public:
|
||||
|
||||
OpenGLRenderPipeline(
|
||||
OpenGLRenderer& renderer,
|
||||
RuntimeHost& runtimeHost,
|
||||
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
||||
HealthTelemetry& healthTelemetry,
|
||||
RenderEffectCallback renderEffect,
|
||||
OutputReadyCallback outputReady,
|
||||
PaintCallback paint);
|
||||
@@ -34,6 +35,13 @@ public:
|
||||
bool RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
|
||||
|
||||
private:
|
||||
enum class OutputReadbackMode
|
||||
{
|
||||
AsyncPbo,
|
||||
Synchronous,
|
||||
CachedOnly
|
||||
};
|
||||
|
||||
struct AsyncReadbackSlot
|
||||
{
|
||||
GLuint pixelPackBuffer = 0;
|
||||
@@ -42,22 +50,46 @@ private:
|
||||
bool inFlight = false;
|
||||
};
|
||||
|
||||
struct OutputReadbackTiming
|
||||
{
|
||||
double fenceWaitMilliseconds = 0.0;
|
||||
double mapMilliseconds = 0.0;
|
||||
double copyMilliseconds = 0.0;
|
||||
double cachedCopyMilliseconds = 0.0;
|
||||
double asyncQueueMilliseconds = 0.0;
|
||||
double asyncQueueBufferMilliseconds = 0.0;
|
||||
double asyncQueueSetupMilliseconds = 0.0;
|
||||
double asyncQueueReadPixelsMilliseconds = 0.0;
|
||||
double asyncQueueFenceMilliseconds = 0.0;
|
||||
double syncReadMilliseconds = 0.0;
|
||||
bool asyncReadbackMissed = false;
|
||||
bool cachedFallbackUsed = false;
|
||||
bool syncFallbackUsed = false;
|
||||
};
|
||||
|
||||
bool EnsureAsyncReadbackBuffers(std::size_t requiredBytes);
|
||||
void ResetAsyncReadbackState();
|
||||
void FlushAsyncReadbackPipeline();
|
||||
void QueueAsyncReadback(const VideoIOState& state);
|
||||
bool TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds);
|
||||
bool QueueAsyncReadback(const VideoIOState& state, OutputReadbackTiming& timing);
|
||||
bool TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds, OutputReadbackTiming& timing);
|
||||
void CacheOutputFrame(const VideoIOOutputFrame& outputFrame);
|
||||
void ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes);
|
||||
bool TryCopyCachedOutputFrame(VideoIOOutputFrame& outputFrame, OutputReadbackTiming& timing) const;
|
||||
void ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes, OutputReadbackTiming& timing);
|
||||
void PackOutputForBgra8(const VideoIOState& state);
|
||||
void PackOutputFor10Bit(const VideoIOState& state);
|
||||
void ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame);
|
||||
OutputReadbackTiming ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame);
|
||||
static OutputReadbackMode ReadOutputReadbackModeFromEnvironment();
|
||||
static std::size_t ReadAsyncReadbackDepthFromEnvironment();
|
||||
|
||||
OpenGLRenderer& mRenderer;
|
||||
RuntimeHost& mRuntimeHost;
|
||||
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||
HealthTelemetry& mHealthTelemetry;
|
||||
RenderEffectCallback mRenderEffect;
|
||||
OutputReadyCallback mOutputReady;
|
||||
PaintCallback mPaint;
|
||||
std::array<AsyncReadbackSlot, 3> mAsyncReadbackSlots;
|
||||
OutputReadbackMode mOutputReadbackMode = OutputReadbackMode::AsyncPbo;
|
||||
std::vector<AsyncReadbackSlot> mAsyncReadbackSlots;
|
||||
std::size_t mAsyncReadbackDepth = 0;
|
||||
std::size_t mAsyncReadbackWriteIndex = 0;
|
||||
std::size_t mAsyncReadbackReadIndex = 0;
|
||||
std::size_t mAsyncReadbackBytes = 0;
|
||||
|
||||
@@ -1,124 +1,25 @@
|
||||
#include "OpenGLVideoIOBridge.h"
|
||||
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "RuntimeHost.h"
|
||||
#include "RenderEngine.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <gl/gl.h>
|
||||
|
||||
OpenGLVideoIOBridge::OpenGLVideoIOBridge(
|
||||
VideoIODevice& videoIO,
|
||||
OpenGLRenderer& renderer,
|
||||
OpenGLRenderPipeline& renderPipeline,
|
||||
RuntimeHost& runtimeHost,
|
||||
CRITICAL_SECTION& mutex,
|
||||
HDC hdc,
|
||||
HGLRC hglrc) :
|
||||
mVideoIO(videoIO),
|
||||
mRenderer(renderer),
|
||||
mRenderPipeline(renderPipeline),
|
||||
mRuntimeHost(runtimeHost),
|
||||
mMutex(mutex),
|
||||
mHdc(hdc),
|
||||
mHglrc(hglrc)
|
||||
OpenGLVideoIOBridge::OpenGLVideoIOBridge(RenderEngine& renderEngine) :
|
||||
mRenderEngine(renderEngine)
|
||||
{
|
||||
}
|
||||
|
||||
void OpenGLVideoIOBridge::RecordFramePacing(VideoIOCompletionResult completionResult)
|
||||
void OpenGLVideoIOBridge::UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state)
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (mLastPlayoutCompletionTime != std::chrono::steady_clock::time_point())
|
||||
{
|
||||
mCompletionIntervalMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(now - mLastPlayoutCompletionTime).count();
|
||||
if (mSmoothedCompletionIntervalMilliseconds <= 0.0)
|
||||
mSmoothedCompletionIntervalMilliseconds = mCompletionIntervalMilliseconds;
|
||||
else
|
||||
mSmoothedCompletionIntervalMilliseconds = mSmoothedCompletionIntervalMilliseconds * 0.9 + mCompletionIntervalMilliseconds * 0.1;
|
||||
if (mCompletionIntervalMilliseconds > mMaxCompletionIntervalMilliseconds)
|
||||
mMaxCompletionIntervalMilliseconds = mCompletionIntervalMilliseconds;
|
||||
}
|
||||
mLastPlayoutCompletionTime = now;
|
||||
|
||||
if (completionResult == VideoIOCompletionResult::DisplayedLate)
|
||||
++mLateFrameCount;
|
||||
else if (completionResult == VideoIOCompletionResult::Dropped)
|
||||
++mDroppedFrameCount;
|
||||
else if (completionResult == VideoIOCompletionResult::Flushed)
|
||||
++mFlushedFrameCount;
|
||||
|
||||
mRuntimeHost.TrySetFramePacingStats(
|
||||
mCompletionIntervalMilliseconds,
|
||||
mSmoothedCompletionIntervalMilliseconds,
|
||||
mMaxCompletionIntervalMilliseconds,
|
||||
mLateFrameCount,
|
||||
mDroppedFrameCount,
|
||||
mFlushedFrameCount);
|
||||
}
|
||||
|
||||
void OpenGLVideoIOBridge::VideoFrameArrived(const VideoIOFrame& inputFrame)
|
||||
{
|
||||
const VideoIOState& state = mVideoIO.State();
|
||||
mRuntimeHost.TrySetSignalStatus(!inputFrame.hasNoInputSource, state.inputFrameSize.width, state.inputFrameSize.height, state.inputDisplayModeName);
|
||||
|
||||
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
|
||||
return; // don't transfer texture when there's no input
|
||||
|
||||
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height);
|
||||
|
||||
// Never let input upload stall the playout/render callback. If the GL bridge
|
||||
// is busy producing an output frame, skip this upload and use the next input.
|
||||
if (!TryEnterCriticalSection(&mMutex))
|
||||
return;
|
||||
|
||||
wglMakeCurrent(mHdc, mHglrc); // make OpenGL context current in this thread
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.TextureUploadBuffer());
|
||||
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, inputFrame.bytes, GL_DYNAMIC_DRAW);
|
||||
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
|
||||
|
||||
// NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data.
|
||||
if (inputFrame.pixelFormat == VideoIOPixelFormat::V210)
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.captureTextureWidth, state.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
else
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.captureTextureWidth, state.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
|
||||
LeaveCriticalSection(&mMutex);
|
||||
mRenderEngine.QueueInputFrame(inputFrame, state);
|
||||
}
|
||||
|
||||
void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& completion)
|
||||
bool OpenGLVideoIOBridge::RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame)
|
||||
{
|
||||
RecordFramePacing(completion.result);
|
||||
|
||||
VideoIOOutputFrame outputFrame;
|
||||
if (!mVideoIO.BeginOutputFrame(outputFrame))
|
||||
return;
|
||||
const VideoIOState& state = mVideoIO.State();
|
||||
RenderPipelineFrameContext frameContext;
|
||||
frameContext.videoState = state;
|
||||
frameContext.completion = completion;
|
||||
|
||||
EnterCriticalSection(&mMutex);
|
||||
|
||||
// make GL context current in this thread
|
||||
wglMakeCurrent(mHdc, mHglrc);
|
||||
|
||||
mRenderPipeline.RenderFrame(frameContext, outputFrame);
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
|
||||
LeaveCriticalSection(&mMutex);
|
||||
|
||||
mVideoIO.EndOutputFrame(outputFrame);
|
||||
|
||||
mVideoIO.AccountForCompletionResult(completion.result);
|
||||
|
||||
// Schedule the next frame for playout after the GL bridge is released so
|
||||
// input uploads are not blocked by non-GL output bookkeeping.
|
||||
mVideoIO.ScheduleOutputFrame(outputFrame);
|
||||
return mRenderEngine.RequestOutputFrame(frameContext, outputFrame);
|
||||
}
|
||||
|
||||
@@ -2,43 +2,16 @@
|
||||
|
||||
#include "OpenGLRenderPipeline.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
class RuntimeHost;
|
||||
class RenderEngine;
|
||||
|
||||
class OpenGLVideoIOBridge
|
||||
{
|
||||
public:
|
||||
OpenGLVideoIOBridge(
|
||||
VideoIODevice& videoIO,
|
||||
OpenGLRenderer& renderer,
|
||||
OpenGLRenderPipeline& renderPipeline,
|
||||
RuntimeHost& runtimeHost,
|
||||
CRITICAL_SECTION& mutex,
|
||||
HDC hdc,
|
||||
HGLRC hglrc);
|
||||
explicit OpenGLVideoIOBridge(RenderEngine& renderEngine);
|
||||
|
||||
void VideoFrameArrived(const VideoIOFrame& inputFrame);
|
||||
void PlayoutFrameCompleted(const VideoIOCompletion& completion);
|
||||
void UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state);
|
||||
bool RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame);
|
||||
|
||||
private:
|
||||
void RecordFramePacing(VideoIOCompletionResult completionResult);
|
||||
|
||||
VideoIODevice& mVideoIO;
|
||||
OpenGLRenderer& mRenderer;
|
||||
OpenGLRenderPipeline& mRenderPipeline;
|
||||
RuntimeHost& mRuntimeHost;
|
||||
CRITICAL_SECTION& mMutex;
|
||||
HDC mHdc;
|
||||
HGLRC mHglrc;
|
||||
std::chrono::steady_clock::time_point mLastPlayoutCompletionTime;
|
||||
double mCompletionIntervalMilliseconds = 0.0;
|
||||
double mSmoothedCompletionIntervalMilliseconds = 0.0;
|
||||
double mMaxCompletionIntervalMilliseconds = 0.0;
|
||||
uint64_t mLateFrameCount = 0;
|
||||
uint64_t mDroppedFrameCount = 0;
|
||||
uint64_t mFlushedFrameCount = 0;
|
||||
RenderEngine& mRenderEngine;
|
||||
};
|
||||
|
||||
@@ -7,4 +7,3 @@ constexpr GLuint kDecodedVideoTextureUnit = 1;
|
||||
constexpr GLuint kSourceHistoryTextureUnitBase = 2;
|
||||
constexpr GLuint kPackedVideoTextureUnit = 2;
|
||||
constexpr GLuint kGlobalParamsBindingPoint = 0;
|
||||
constexpr unsigned kPrerollFrameCount = 12;
|
||||
|
||||
@@ -29,19 +29,21 @@ std::size_t RequiredTemporaryRenderTargets(const std::vector<OpenGLRenderer::Lay
|
||||
}
|
||||
}
|
||||
|
||||
OpenGLShaderPrograms::OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeHost& runtimeHost) :
|
||||
OpenGLShaderPrograms::OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider) :
|
||||
mRenderer(renderer),
|
||||
mRuntimeHost(runtimeHost),
|
||||
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
||||
mGlobalParamsBuffer(renderer),
|
||||
mCompiler(renderer, runtimeHost, mTextureBindings)
|
||||
mCompiler(renderer, runtimeSnapshotProvider, mTextureBindings)
|
||||
{
|
||||
}
|
||||
|
||||
bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
const std::vector<RuntimeRenderState> layerStates = mRuntimeHost.GetLayerRenderStates(inputFrameWidth, inputFrameHeight);
|
||||
const RuntimeRenderStateSnapshot renderSnapshot =
|
||||
mRuntimeSnapshotProvider.PublishRenderStateSnapshot(inputFrameWidth, inputFrameHeight);
|
||||
const std::vector<RuntimeRenderState>& layerStates = renderSnapshot.states;
|
||||
std::string temporalError;
|
||||
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames();
|
||||
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
|
||||
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(layerStates, historyCap, temporalError))
|
||||
{
|
||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||
@@ -87,10 +89,7 @@ bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsign
|
||||
|
||||
DestroyLayerPrograms();
|
||||
mRenderer.ReplaceLayerPrograms(newPrograms);
|
||||
mCommittedLayerStates = layerStates;
|
||||
|
||||
mRuntimeHost.SetCompileStatus(true, "Shader layers compiled successfully.");
|
||||
mRuntimeHost.ClearReloadRequest();
|
||||
mCommittedLayerStates = renderSnapshot.states;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -104,19 +103,19 @@ bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild
|
||||
}
|
||||
|
||||
std::string temporalError;
|
||||
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames();
|
||||
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.layerStates, historyCap, temporalError))
|
||||
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
|
||||
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.renderSnapshot.states, historyCap, temporalError))
|
||||
{
|
||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
if (!mRenderer.TemporalHistory().EnsureResources(preparedBuild.layerStates, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
|
||||
if (!mRenderer.TemporalHistory().EnsureResources(preparedBuild.renderSnapshot.states, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
|
||||
{
|
||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
if (mRenderer.ResourcesInitialized() &&
|
||||
!mRenderer.FeedbackBuffers().EnsureResources(preparedBuild.layerStates, inputFrameWidth, inputFrameHeight, temporalError))
|
||||
!mRenderer.FeedbackBuffers().EnsureResources(preparedBuild.renderSnapshot.states, inputFrameWidth, inputFrameHeight, temporalError))
|
||||
{
|
||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
@@ -150,10 +149,7 @@ bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild
|
||||
|
||||
DestroyLayerPrograms();
|
||||
mRenderer.ReplaceLayerPrograms(newPrograms);
|
||||
mCommittedLayerStates = preparedBuild.layerStates;
|
||||
|
||||
mRuntimeHost.SetCompileStatus(true, "Shader layers compiled successfully.");
|
||||
mRuntimeHost.ClearReloadRequest();
|
||||
mCommittedLayerStates = preparedBuild.renderSnapshot.states;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "GlobalParamsBuffer.h"
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "RuntimeHost.h"
|
||||
#include "RuntimeSnapshotProvider.h"
|
||||
#include "ShaderBuildQueue.h"
|
||||
#include "ShaderTypes.h"
|
||||
#include "ShaderProgramCompiler.h"
|
||||
@@ -15,7 +15,7 @@ class OpenGLShaderPrograms
|
||||
public:
|
||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
||||
|
||||
OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeHost& runtimeHost);
|
||||
OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider);
|
||||
|
||||
bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
|
||||
bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
|
||||
private:
|
||||
OpenGLRenderer& mRenderer;
|
||||
RuntimeHost& mRuntimeHost;
|
||||
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||
ShaderTextureBindings mTextureBindings;
|
||||
GlobalParamsBuffer mGlobalParamsBuffer;
|
||||
ShaderProgramCompiler mCompiler;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "ShaderBuildQueue.h"
|
||||
|
||||
#include "RuntimeHost.h"
|
||||
#include "RuntimeEventDispatcher.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <utility>
|
||||
@@ -10,8 +10,9 @@ namespace
|
||||
constexpr auto kShaderBuildDebounce = std::chrono::milliseconds(400);
|
||||
}
|
||||
|
||||
ShaderBuildQueue::ShaderBuildQueue(RuntimeHost& runtimeHost) :
|
||||
mRuntimeHost(runtimeHost),
|
||||
ShaderBuildQueue::ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeEventDispatcher& runtimeEventDispatcher) :
|
||||
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
||||
mRuntimeEventDispatcher(runtimeEventDispatcher),
|
||||
mWorkerThread([this]() { WorkerLoop(); })
|
||||
{
|
||||
}
|
||||
@@ -46,6 +47,18 @@ bool ShaderBuildQueue::TryConsumeReadyBuild(PreparedShaderBuild& build)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderBuildQueue::TryConsumeReadyBuild(uint64_t expectedGeneration, PreparedShaderBuild& build)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!mHasReadyBuild || mReadyBuild.generation != expectedGeneration)
|
||||
return false;
|
||||
|
||||
build = std::move(mReadyBuild);
|
||||
mReadyBuild = PreparedShaderBuild();
|
||||
mHasReadyBuild = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShaderBuildQueue::Stop()
|
||||
{
|
||||
{
|
||||
@@ -99,13 +112,20 @@ void ShaderBuildQueue::WorkerLoop()
|
||||
|
||||
PreparedShaderBuild build = Build(generation, outputWidth, outputHeight);
|
||||
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mStopping)
|
||||
return;
|
||||
if (generation != mRequestedGeneration)
|
||||
continue;
|
||||
mReadyBuild = std::move(build);
|
||||
mHasReadyBuild = true;
|
||||
bool shouldPublish = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mStopping)
|
||||
return;
|
||||
if (generation != mRequestedGeneration)
|
||||
continue;
|
||||
mReadyBuild = build;
|
||||
mHasReadyBuild = true;
|
||||
shouldPublish = true;
|
||||
}
|
||||
|
||||
if (shouldPublish)
|
||||
PublishBuildLifecycleEvent(build, outputWidth, outputHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,14 +133,14 @@ PreparedShaderBuild ShaderBuildQueue::Build(uint64_t generation, unsigned output
|
||||
{
|
||||
PreparedShaderBuild build;
|
||||
build.generation = generation;
|
||||
build.layerStates = mRuntimeHost.GetLayerRenderStates(outputWidth, outputHeight);
|
||||
build.layers.reserve(build.layerStates.size());
|
||||
build.renderSnapshot = mRuntimeSnapshotProvider.PublishRenderStateSnapshot(outputWidth, outputHeight);
|
||||
build.layers.reserve(build.renderSnapshot.states.size());
|
||||
|
||||
for (const RuntimeRenderState& state : build.layerStates)
|
||||
for (const RuntimeRenderState& state : build.renderSnapshot.states)
|
||||
{
|
||||
PreparedLayerShader layer;
|
||||
layer.state = state;
|
||||
if (!mRuntimeHost.BuildLayerPassFragmentShaderSources(state.layerId, layer.passes, build.message))
|
||||
if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, layer.passes, build.message))
|
||||
{
|
||||
build.succeeded = false;
|
||||
return build;
|
||||
@@ -132,3 +152,15 @@ PreparedShaderBuild ShaderBuildQueue::Build(uint64_t generation, unsigned output
|
||||
build.message = "Shader layers prepared successfully.";
|
||||
return build;
|
||||
}
|
||||
|
||||
void ShaderBuildQueue::PublishBuildLifecycleEvent(const PreparedShaderBuild& build, unsigned outputWidth, unsigned outputHeight) const
|
||||
{
|
||||
ShaderBuildEvent event;
|
||||
event.phase = build.succeeded ? RuntimeEventShaderBuildPhase::Prepared : RuntimeEventShaderBuildPhase::Failed;
|
||||
event.generation = build.generation;
|
||||
event.inputWidth = outputWidth;
|
||||
event.inputHeight = outputHeight;
|
||||
event.succeeded = build.succeeded;
|
||||
event.message = build.message;
|
||||
mRuntimeEventDispatcher.PublishPayload(event, "ShaderBuildQueue");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeSnapshotProvider.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <condition_variable>
|
||||
@@ -9,7 +10,7 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
class RuntimeHost;
|
||||
class RuntimeEventDispatcher;
|
||||
|
||||
struct PreparedLayerShader
|
||||
{
|
||||
@@ -22,14 +23,14 @@ struct PreparedShaderBuild
|
||||
uint64_t generation = 0;
|
||||
bool succeeded = false;
|
||||
std::string message;
|
||||
std::vector<RuntimeRenderState> layerStates;
|
||||
RuntimeRenderStateSnapshot renderSnapshot;
|
||||
std::vector<PreparedLayerShader> layers;
|
||||
};
|
||||
|
||||
class ShaderBuildQueue
|
||||
{
|
||||
public:
|
||||
explicit ShaderBuildQueue(RuntimeHost& runtimeHost);
|
||||
ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeEventDispatcher& runtimeEventDispatcher);
|
||||
~ShaderBuildQueue();
|
||||
|
||||
ShaderBuildQueue(const ShaderBuildQueue&) = delete;
|
||||
@@ -37,13 +38,16 @@ public:
|
||||
|
||||
void RequestBuild(unsigned outputWidth, unsigned outputHeight);
|
||||
bool TryConsumeReadyBuild(PreparedShaderBuild& build);
|
||||
bool TryConsumeReadyBuild(uint64_t expectedGeneration, PreparedShaderBuild& build);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void WorkerLoop();
|
||||
PreparedShaderBuild Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight);
|
||||
void PublishBuildLifecycleEvent(const PreparedShaderBuild& build, unsigned outputWidth, unsigned outputHeight) const;
|
||||
|
||||
RuntimeHost& mRuntimeHost;
|
||||
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
||||
std::thread mWorkerThread;
|
||||
std::mutex mMutex;
|
||||
std::condition_variable mCondition;
|
||||
|
||||
@@ -19,9 +19,9 @@ void CopyErrorMessage(const std::string& message, int errorMessageSize, char* er
|
||||
}
|
||||
}
|
||||
|
||||
ShaderProgramCompiler::ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, ShaderTextureBindings& textureBindings) :
|
||||
ShaderProgramCompiler::ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider, ShaderTextureBindings& textureBindings) :
|
||||
mRenderer(renderer),
|
||||
mRuntimeHost(runtimeHost),
|
||||
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
||||
mTextureBindings(textureBindings)
|
||||
{
|
||||
}
|
||||
@@ -31,7 +31,7 @@ bool ShaderProgramCompiler::CompileLayerProgram(const RuntimeRenderState& state,
|
||||
std::vector<ShaderPassBuildSource> passSources;
|
||||
std::string loadError;
|
||||
|
||||
if (!mRuntimeHost.BuildLayerPassFragmentShaderSources(state.layerId, passSources, loadError))
|
||||
if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, passSources, loadError))
|
||||
{
|
||||
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
@@ -117,7 +117,7 @@ bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState
|
||||
passProgram.passId = passSource.passId;
|
||||
passProgram.inputNames = passSource.inputNames;
|
||||
passProgram.outputName = passSource.outputName;
|
||||
passProgram.shaderTextureBase = mTextureBindings.ResolveShaderTextureBase(state, mRuntimeHost.GetMaxTemporalHistoryFrames());
|
||||
passProgram.shaderTextureBase = mTextureBindings.ResolveShaderTextureBase(state, mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames());
|
||||
passProgram.textureBindings.swap(textureBindings);
|
||||
passProgram.textBindings.swap(textBindings);
|
||||
|
||||
@@ -125,7 +125,7 @@ bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState
|
||||
if (globalParamsIndex != GL_INVALID_INDEX)
|
||||
glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint);
|
||||
|
||||
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames();
|
||||
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
|
||||
glUseProgram(newProgram.get());
|
||||
mTextureBindings.AssignLayerSamplerUniforms(newProgram.get(), state, passProgram, historyCap);
|
||||
glUseProgram(0);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "RuntimeHost.h"
|
||||
#include "RuntimeSnapshotProvider.h"
|
||||
#include "ShaderTextureBindings.h"
|
||||
|
||||
#include <string>
|
||||
@@ -13,7 +13,7 @@ public:
|
||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
||||
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
|
||||
|
||||
ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, ShaderTextureBindings& textureBindings);
|
||||
ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider, ShaderTextureBindings& textureBindings);
|
||||
|
||||
bool CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
||||
bool CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::vector<ShaderPassBuildSource>& passSources, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
||||
@@ -22,6 +22,6 @@ public:
|
||||
|
||||
private:
|
||||
OpenGLRenderer& mRenderer;
|
||||
RuntimeHost& mRuntimeHost;
|
||||
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||
ShaderTextureBindings& mTextureBindings;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
#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::RequestInputUpload(const RenderInputUploadRequest& request)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mHasInputUploadRequest)
|
||||
++mCoalescedCount;
|
||||
else
|
||||
++mEnqueuedCount;
|
||||
|
||||
mInputUploadRequest = request;
|
||||
mHasInputUploadRequest = true;
|
||||
}
|
||||
|
||||
bool RenderCommandQueue::TryTakeInputUpload(RenderInputUploadRequest& request)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!mHasInputUploadRequest)
|
||||
return false;
|
||||
|
||||
request = mInputUploadRequest;
|
||||
mInputUploadRequest = {};
|
||||
mHasInputUploadRequest = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderCommandQueue::RequestOutputFrame(const RenderOutputFrameRequest& request)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mOutputFrameRequests.push_back(request);
|
||||
++mEnqueuedCount;
|
||||
}
|
||||
|
||||
bool RenderCommandQueue::TryTakeOutputFrame(RenderOutputFrameRequest& request)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mOutputFrameRequests.empty())
|
||||
return false;
|
||||
|
||||
request = mOutputFrameRequests.front();
|
||||
mOutputFrameRequests.pop_front();
|
||||
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) +
|
||||
(mHasInputUploadRequest ? 1u : 0u) +
|
||||
mOutputFrameRequests.size() +
|
||||
(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;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoIOTypes.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
enum class RenderCommandResetScope
|
||||
{
|
||||
None,
|
||||
ShaderFeedbackOnly,
|
||||
TemporalHistoryOnly,
|
||||
TemporalHistoryAndFeedback
|
||||
};
|
||||
|
||||
struct RenderPreviewPresentRequest
|
||||
{
|
||||
unsigned outputFrameWidth = 0;
|
||||
unsigned outputFrameHeight = 0;
|
||||
};
|
||||
|
||||
struct RenderScreenshotCaptureRequest
|
||||
{
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
};
|
||||
|
||||
struct RenderInputUploadRequest
|
||||
{
|
||||
VideoIOFrame inputFrame;
|
||||
VideoIOState videoState;
|
||||
std::vector<unsigned char> ownedBytes;
|
||||
};
|
||||
|
||||
struct RenderOutputFrameRequest
|
||||
{
|
||||
VideoIOState videoState;
|
||||
VideoIOCompletion completion;
|
||||
};
|
||||
|
||||
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 RequestInputUpload(const RenderInputUploadRequest& request);
|
||||
bool TryTakeInputUpload(RenderInputUploadRequest& request);
|
||||
|
||||
void RequestOutputFrame(const RenderOutputFrameRequest& request);
|
||||
bool TryTakeOutputFrame(RenderOutputFrameRequest& 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;
|
||||
bool mHasInputUploadRequest = false;
|
||||
RenderInputUploadRequest mInputUploadRequest;
|
||||
std::deque<RenderOutputFrameRequest> mOutputFrameRequests;
|
||||
RenderCommandResetScope mRenderResetScope = RenderCommandResetScope::None;
|
||||
uint64_t mEnqueuedCount = 0;
|
||||
uint64_t mCoalescedCount = 0;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,199 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class RuntimeHost
|
||||
{
|
||||
public:
|
||||
RuntimeHost();
|
||||
|
||||
bool Initialize(std::string& error);
|
||||
|
||||
bool PollFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error);
|
||||
bool ManualReloadRequested();
|
||||
void ClearReloadRequest();
|
||||
|
||||
bool AddLayer(const std::string& shaderId, std::string& error);
|
||||
bool RemoveLayer(const std::string& layerId, std::string& error);
|
||||
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
|
||||
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
|
||||
bool SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error);
|
||||
bool SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error);
|
||||
bool UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, std::string& error);
|
||||
bool UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error);
|
||||
bool UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, std::string& error);
|
||||
bool ApplyOscTargetByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& targetValue, double smoothingAmount, bool& keepApplying, std::string& resolvedLayerId, std::string& resolvedParameterId, ShaderParameterValue& appliedValue, std::string& error);
|
||||
bool ResetLayerParameters(const std::string& layerId, std::string& error);
|
||||
bool SaveStackPreset(const std::string& presetName, std::string& error) const;
|
||||
bool LoadStackPreset(const std::string& presetName, std::string& error);
|
||||
|
||||
void SetCompileStatus(bool succeeded, const std::string& message);
|
||||
void SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||
bool TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||
void SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
|
||||
void SetVideoIOStatus(const std::string& backendName, const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
|
||||
void SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||
bool TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||
void SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
||||
bool TrySetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
||||
void AdvanceFrame();
|
||||
bool TryAdvanceFrame();
|
||||
|
||||
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error);
|
||||
std::vector<RuntimeRenderState> GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const;
|
||||
bool TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
|
||||
bool TryRefreshCachedLayerStates(std::vector<RuntimeRenderState>& states) const;
|
||||
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
|
||||
std::string BuildStateJson() const;
|
||||
uint64_t GetRenderStateVersion() const { return mRenderStateVersion.load(std::memory_order_relaxed); }
|
||||
uint64_t GetParameterStateVersion() const { return mParameterStateVersion.load(std::memory_order_relaxed); }
|
||||
|
||||
const std::filesystem::path& GetRepoRoot() const { return mRepoRoot; }
|
||||
const std::filesystem::path& GetUiRoot() const { return mUiRoot; }
|
||||
const std::filesystem::path& GetDocsRoot() const { return mDocsRoot; }
|
||||
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
|
||||
unsigned short GetServerPort() const { return mServerPort; }
|
||||
unsigned short GetOscPort() const { return mConfig.oscPort; }
|
||||
const std::string& GetOscBindAddress() const { return mConfig.oscBindAddress; }
|
||||
double GetOscSmoothing() const { return mConfig.oscSmoothing; }
|
||||
unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; }
|
||||
unsigned GetPreviewFps() const { return mConfig.previewFps; }
|
||||
bool ExternalKeyingEnabled() const { return mConfig.enableExternalKeying; }
|
||||
const std::string& GetInputVideoFormat() const { return mConfig.inputVideoFormat; }
|
||||
const std::string& GetInputFrameRate() const { return mConfig.inputFrameRate; }
|
||||
const std::string& GetOutputVideoFormat() const { return mConfig.outputVideoFormat; }
|
||||
const std::string& GetOutputFrameRate() const { return mConfig.outputFrameRate; }
|
||||
void SetServerPort(unsigned short port);
|
||||
bool AutoReloadEnabled() const { return mAutoReloadEnabled; }
|
||||
|
||||
private:
|
||||
struct AppConfig
|
||||
{
|
||||
std::string shaderLibrary = "shaders";
|
||||
unsigned short serverPort = 8080;
|
||||
unsigned short oscPort = 9000;
|
||||
std::string oscBindAddress = "127.0.0.1";
|
||||
double oscSmoothing = 0.18;
|
||||
bool autoReload = true;
|
||||
unsigned maxTemporalHistoryFrames = 4;
|
||||
unsigned previewFps = 30;
|
||||
bool enableExternalKeying = false;
|
||||
std::string inputVideoFormat = "1080p";
|
||||
std::string inputFrameRate = "59.94";
|
||||
std::string outputVideoFormat = "1080p";
|
||||
std::string outputFrameRate = "59.94";
|
||||
};
|
||||
|
||||
struct DeckLinkOutputStatus
|
||||
{
|
||||
std::string backendName = "decklink";
|
||||
std::string modelName;
|
||||
bool supportsInternalKeying = false;
|
||||
bool supportsExternalKeying = false;
|
||||
bool keyerInterfaceAvailable = false;
|
||||
bool externalKeyingRequested = false;
|
||||
bool externalKeyingActive = false;
|
||||
std::string statusMessage;
|
||||
};
|
||||
|
||||
struct LayerPersistentState
|
||||
{
|
||||
std::string id;
|
||||
std::string shaderId;
|
||||
bool bypass = false;
|
||||
std::map<std::string, ShaderParameterValue> parameterValues;
|
||||
};
|
||||
|
||||
struct PersistentState
|
||||
{
|
||||
std::vector<LayerPersistentState> layers;
|
||||
};
|
||||
|
||||
bool LoadConfig(std::string& error);
|
||||
bool LoadPersistentState(std::string& error);
|
||||
bool SavePersistentState(std::string& error) const;
|
||||
bool ScanShaderPackages(std::string& error);
|
||||
bool NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const;
|
||||
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition) const;
|
||||
void EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const;
|
||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
||||
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
|
||||
bool ResolvePaths(std::string& error);
|
||||
void BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
|
||||
JsonValue BuildStateValue() const;
|
||||
JsonValue SerializeLayerStackLocked() const;
|
||||
bool DeserializeLayerStackLocked(const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, std::string& error);
|
||||
void NormalizePersistentLayerIdsLocked();
|
||||
std::vector<std::string> GetStackPresetNamesLocked() const;
|
||||
std::string MakeSafePresetFileStem(const std::string& presetName) const;
|
||||
JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const;
|
||||
std::string TemporalHistorySourceToString(TemporalHistorySource source) const;
|
||||
LayerPersistentState* FindLayerById(const std::string& layerId);
|
||||
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
|
||||
std::string GenerateLayerId();
|
||||
void SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||
void MarkRenderStateDirtyLocked();
|
||||
void MarkParameterStateDirtyLocked();
|
||||
void SetPerformanceStatsLocked(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||
void SetFramePacingStatsLocked(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
||||
|
||||
private:
|
||||
mutable std::mutex mMutex;
|
||||
AppConfig mConfig;
|
||||
PersistentState mPersistentState;
|
||||
std::filesystem::path mRepoRoot;
|
||||
std::filesystem::path mUiRoot;
|
||||
std::filesystem::path mDocsRoot;
|
||||
std::filesystem::path mShaderRoot;
|
||||
std::filesystem::path mRuntimeRoot;
|
||||
std::filesystem::path mPresetRoot;
|
||||
std::filesystem::path mRuntimeStatePath;
|
||||
std::filesystem::path mConfigPath;
|
||||
std::filesystem::path mWrapperPath;
|
||||
std::filesystem::path mGeneratedGlslPath;
|
||||
std::filesystem::path mPatchedGlslPath;
|
||||
std::map<std::string, ShaderPackage> mPackagesById;
|
||||
std::vector<std::string> mPackageOrder;
|
||||
std::vector<ShaderPackageStatus> mPackageStatuses;
|
||||
bool mReloadRequested;
|
||||
bool mCompileSucceeded;
|
||||
std::string mCompileMessage;
|
||||
bool mHasSignal;
|
||||
unsigned mSignalWidth;
|
||||
unsigned mSignalHeight;
|
||||
std::string mSignalModeName;
|
||||
double mFrameBudgetMilliseconds;
|
||||
double mRenderMilliseconds;
|
||||
double mSmoothedRenderMilliseconds;
|
||||
double mCompletionIntervalMilliseconds;
|
||||
double mSmoothedCompletionIntervalMilliseconds;
|
||||
double mMaxCompletionIntervalMilliseconds;
|
||||
double mStartupRandom;
|
||||
uint64_t mLateFrameCount;
|
||||
uint64_t mDroppedFrameCount;
|
||||
uint64_t mFlushedFrameCount;
|
||||
DeckLinkOutputStatus mDeckLinkOutputStatus;
|
||||
unsigned short mServerPort;
|
||||
bool mAutoReloadEnabled;
|
||||
std::chrono::steady_clock::time_point mStartTime;
|
||||
std::chrono::steady_clock::time_point mLastScanTime;
|
||||
std::atomic<uint64_t> mFrameCounter{ 0 };
|
||||
std::atomic<uint64_t> mRenderStateVersion{ 0 };
|
||||
std::atomic<uint64_t> mParameterStateVersion{ 0 };
|
||||
uint64_t mNextLayerId;
|
||||
};
|
||||
@@ -0,0 +1,613 @@
|
||||
#include "RuntimeCoordinator.h"
|
||||
|
||||
#include "RuntimeEventDispatcher.h"
|
||||
#include "RuntimeEventPayloads.h"
|
||||
#include "RuntimeParameterUtils.h"
|
||||
#include "RuntimeStore.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
RuntimeEventRenderResetScope ToRuntimeEventRenderResetScope(RuntimeCoordinatorRenderResetScope scope)
|
||||
{
|
||||
switch (scope)
|
||||
{
|
||||
case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly:
|
||||
return RuntimeEventRenderResetScope::TemporalHistoryOnly;
|
||||
case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback:
|
||||
return RuntimeEventRenderResetScope::TemporalHistoryAndFeedback;
|
||||
case RuntimeCoordinatorRenderResetScope::None:
|
||||
default:
|
||||
return RuntimeEventRenderResetScope::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RuntimeCoordinator::RuntimeCoordinator(RuntimeStore& runtimeStore, RuntimeEventDispatcher& runtimeEventDispatcher) :
|
||||
mRuntimeStore(runtimeStore),
|
||||
mRuntimeEventDispatcher(runtimeEventDispatcher)
|
||||
{
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::AddLayer(const std::string& shaderId)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::string error;
|
||||
if (!ValidateShaderExists(shaderId, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
||||
PublishCoordinatorResult("AddLayer", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.CreateStoredLayer(shaderId, error), error, true, true, true);
|
||||
PublishCoordinatorResult("AddLayer", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::RemoveLayer(const std::string& layerId)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::string error;
|
||||
if (!ValidateLayerExists(layerId, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
||||
PublishCoordinatorResult("RemoveLayer", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.DeleteStoredLayer(layerId, error), error, true, true, true);
|
||||
if (result.accepted)
|
||||
{
|
||||
result.transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::Layer;
|
||||
result.transientOscLayerKey = layerId;
|
||||
}
|
||||
PublishCoordinatorResult("RemoveLayer", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::MoveLayer(const std::string& layerId, int direction)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::string error;
|
||||
bool shouldMove = false;
|
||||
if (!ResolveLayerMove(layerId, direction, shouldMove, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
||||
PublishCoordinatorResult("MoveLayer", result);
|
||||
return result;
|
||||
}
|
||||
if (!shouldMove)
|
||||
{
|
||||
RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.MoveStoredLayer(layerId, direction, error), error, true, true, true);
|
||||
PublishCoordinatorResult("MoveLayer", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::string error;
|
||||
bool shouldMove = false;
|
||||
if (!ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
||||
PublishCoordinatorResult("MoveLayerToIndex", result);
|
||||
return result;
|
||||
}
|
||||
if (!shouldMove)
|
||||
{
|
||||
RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.MoveStoredLayerToIndex(layerId, targetIndex, error), error, true, true, true);
|
||||
PublishCoordinatorResult("MoveLayerToIndex", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::SetLayerBypass(const std::string& layerId, bool bypassed)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::string error;
|
||||
if (!ValidateLayerExists(layerId, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
||||
PublishCoordinatorResult("SetLayerBypass", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredLayerBypassState(layerId, bypassed, error), error, true, false, true);
|
||||
PublishCoordinatorResult("SetLayerBypass", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::SetLayerShader(const std::string& layerId, const std::string& shaderId)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::string error;
|
||||
if (!ValidateLayerExists(layerId, error) || !ValidateShaderExists(shaderId, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
||||
PublishCoordinatorResult("SetLayerShader", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredLayerShaderSelection(layerId, shaderId, error), error, true, false, true);
|
||||
PublishCoordinatorResult("SetLayerShader", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::string error;
|
||||
ResolvedParameterMutation mutation;
|
||||
if (!BuildParameterMutationById(layerId, parameterId, newValue, true, mutation, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
||||
PublishCoordinatorResult("UpdateLayerParameter", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false, mutation.persistState);
|
||||
PublishCoordinatorResult("UpdateLayerParameter", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::string error;
|
||||
ResolvedParameterMutation mutation;
|
||||
if (!BuildParameterMutationByControlKey(layerKey, parameterKey, newValue, true, mutation, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
||||
PublishCoordinatorResult("UpdateLayerParameterByControlKey", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false, mutation.persistState);
|
||||
PublishCoordinatorResult("UpdateLayerParameterByControlKey", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::CommitOscParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
constexpr RuntimeCoordinatorOscCommitPersistence kDefaultOscCommitPersistence =
|
||||
RuntimeCoordinatorOscCommitPersistence::SessionOnly;
|
||||
constexpr bool kPersistSettledOscCommits =
|
||||
kDefaultOscCommitPersistence == RuntimeCoordinatorOscCommitPersistence::Persistent;
|
||||
|
||||
std::string error;
|
||||
ResolvedParameterMutation mutation;
|
||||
if (!BuildParameterMutationByControlKey(layerKey, parameterKey, newValue, kPersistSettledOscCommits, mutation, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
||||
PublishCoordinatorResult("CommitOscParameterByControlKey", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false, mutation.persistState);
|
||||
PublishCoordinatorResult("CommitOscParameterByControlKey", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::ResetLayerParameters(const std::string& layerId)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::string error;
|
||||
if (!ValidateLayerExists(layerId, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
||||
PublishCoordinatorResult("ResetLayerParameters", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.ResetStoredLayerParameterValues(layerId, error), error, false, false, true);
|
||||
if (!result.accepted)
|
||||
{
|
||||
PublishCoordinatorResult("ResetLayerParameters", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::Layer;
|
||||
result.transientOscLayerKey = layerId;
|
||||
result.renderResetScope = RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback;
|
||||
PublishCoordinatorResult("ResetLayerParameters", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::SaveStackPreset(const std::string& presetName)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::string error;
|
||||
if (!ValidatePresetName(presetName, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
||||
PublishCoordinatorResult("SaveStackPreset", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SaveStackPresetSnapshot(presetName, error), error, false, false, true);
|
||||
PublishCoordinatorResult("SaveStackPreset", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::LoadStackPreset(const std::string& presetName)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::string error;
|
||||
if (!ValidatePresetName(presetName, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
||||
PublishCoordinatorResult("LoadStackPreset", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.LoadStackPresetSnapshot(presetName, error), error, true, false, true);
|
||||
PublishCoordinatorResult("LoadStackPreset", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::RequestShaderReload(bool preserveFeedbackState)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
PublishManualReloadRequested(preserveFeedbackState, "RequestShaderReload");
|
||||
RuntimeCoordinatorResult result = BuildQueuedReloadResult(preserveFeedbackState);
|
||||
PublishCoordinatorFollowUpEvents("RequestShaderReload", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::PollRuntimeStoreChanges(bool& registryChanged)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
registryChanged = false;
|
||||
bool reloadRequested = false;
|
||||
std::string error;
|
||||
if (!mRuntimeStore.PollStoredFileChanges(registryChanged, reloadRequested, error))
|
||||
{
|
||||
RuntimeCoordinatorResult result = HandleRuntimePollFailure(error);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (reloadRequested)
|
||||
{
|
||||
PublishFileChangeDetected("PollRuntimeStoreChanges", registryChanged, reloadRequested);
|
||||
RuntimeCoordinatorResult result = BuildQueuedReloadResult(false);
|
||||
PublishCoordinatorFollowUpEvents("PollRuntimeStoreChanges", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (registryChanged)
|
||||
{
|
||||
PublishFileChangeDetected("PollRuntimeStoreChanges", registryChanged, reloadRequested);
|
||||
RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
|
||||
PublishCoordinatorFollowUpEvents("PollRuntimeStoreChanges", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result;
|
||||
result.accepted = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimePollFailure(const std::string& error)
|
||||
{
|
||||
RuntimeCoordinatorResult result;
|
||||
result.accepted = true;
|
||||
result.runtimeStateBroadcastRequired = true;
|
||||
result.compileStatusChanged = true;
|
||||
result.compileStatusSucceeded = false;
|
||||
result.compileStatusMessage = error;
|
||||
PublishCoordinatorFollowUpEvents("HandleRuntimePollFailure", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(const std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mPreserveFeedbackOnNextShaderBuild = false;
|
||||
mUseCommittedLayerStates = true;
|
||||
|
||||
RuntimeCoordinatorResult result;
|
||||
result.accepted = true;
|
||||
result.runtimeStateBroadcastRequired = true;
|
||||
result.compileStatusChanged = true;
|
||||
result.compileStatusSucceeded = false;
|
||||
result.compileStatusMessage = error;
|
||||
result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseCommittedStates;
|
||||
PublishCoordinatorFollowUpEvents("HandlePreparedShaderBuildFailure", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildSuccess()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mUseCommittedLayerStates = false;
|
||||
|
||||
RuntimeCoordinatorResult result;
|
||||
result.accepted = true;
|
||||
result.runtimeStateBroadcastRequired = true;
|
||||
result.compileStatusChanged = true;
|
||||
result.compileStatusSucceeded = true;
|
||||
result.compileStatusMessage = "Shader layers compiled successfully.";
|
||||
result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots;
|
||||
mPreserveFeedbackOnNextShaderBuild = false;
|
||||
PublishCoordinatorFollowUpEvents("HandlePreparedShaderBuildSuccess", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimeReloadRequest()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
PublishManualReloadRequested(false, "HandleRuntimeReloadRequest");
|
||||
RuntimeCoordinatorResult result = BuildQueuedReloadResult(false);
|
||||
PublishCoordinatorFollowUpEvents("HandleRuntimeReloadRequest", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void RuntimeCoordinator::ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
switch (mode)
|
||||
{
|
||||
case RuntimeCoordinatorCommittedStateMode::UseCommittedStates:
|
||||
mUseCommittedLayerStates = true;
|
||||
break;
|
||||
case RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots:
|
||||
mUseCommittedLayerStates = false;
|
||||
break;
|
||||
case RuntimeCoordinatorCommittedStateMode::Unchanged:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeCoordinator::UseCommittedLayerStates() const
|
||||
{
|
||||
return mUseCommittedLayerStates.load();
|
||||
}
|
||||
|
||||
bool RuntimeCoordinator::PreserveFeedbackOnNextShaderBuild() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mPreserveFeedbackOnNextShaderBuild;
|
||||
}
|
||||
|
||||
bool RuntimeCoordinator::BuildParameterMutationById(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue,
|
||||
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
|
||||
{
|
||||
RuntimeStore::StoredParameterSnapshot snapshot;
|
||||
if (!mRuntimeStore.TryGetStoredParameterById(layerId, parameterId, snapshot, error))
|
||||
return false;
|
||||
|
||||
return BuildParameterMutationFromSnapshot(snapshot.layerId, snapshot.definition, snapshot.currentValue, snapshot.hasCurrentValue,
|
||||
newValue, persistState, mutation, error);
|
||||
}
|
||||
|
||||
bool RuntimeCoordinator::BuildParameterMutationByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue,
|
||||
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
|
||||
{
|
||||
RuntimeStore::StoredParameterSnapshot snapshot;
|
||||
if (!mRuntimeStore.TryGetStoredParameterByControlKey(layerKey, parameterKey, snapshot, error))
|
||||
return false;
|
||||
|
||||
return BuildParameterMutationFromSnapshot(snapshot.layerId, snapshot.definition, snapshot.currentValue, snapshot.hasCurrentValue,
|
||||
newValue, persistState, mutation, error);
|
||||
}
|
||||
|
||||
bool RuntimeCoordinator::BuildParameterMutationFromSnapshot(const std::string& layerId, const ShaderParameterDefinition& definition,
|
||||
const ShaderParameterValue& currentValue, bool hasCurrentValue, const JsonValue& newValue,
|
||||
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
|
||||
{
|
||||
mutation.layerId = layerId;
|
||||
mutation.parameterId = definition.id;
|
||||
mutation.persistState = persistState;
|
||||
|
||||
if (definition.type == ShaderParameterType::Trigger)
|
||||
{
|
||||
const double previousCount = !hasCurrentValue || currentValue.numberValues.empty()
|
||||
? 0.0
|
||||
: currentValue.numberValues[0];
|
||||
const double triggerTime = mRuntimeStore.GetRuntimeElapsedSeconds();
|
||||
mutation.value.numberValues = { previousCount + 1.0, triggerTime };
|
||||
mutation.persistState = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return NormalizeAndValidateParameterValue(definition, newValue, mutation.value, error);
|
||||
}
|
||||
|
||||
bool RuntimeCoordinator::ValidateLayerExists(const std::string& layerId, std::string& error) const
|
||||
{
|
||||
if (mRuntimeStore.HasStoredLayer(layerId))
|
||||
return true;
|
||||
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RuntimeCoordinator::ValidateShaderExists(const std::string& shaderId, std::string& error) const
|
||||
{
|
||||
if (mRuntimeStore.HasStoredShader(shaderId))
|
||||
return true;
|
||||
|
||||
error = "Unknown shader id: " + shaderId;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RuntimeCoordinator::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
|
||||
{
|
||||
return mRuntimeStore.ResolveStoredLayerMove(layerId, direction, shouldMove, error);
|
||||
}
|
||||
|
||||
bool RuntimeCoordinator::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
|
||||
{
|
||||
return mRuntimeStore.ResolveStoredLayerMoveToIndex(layerId, targetIndex, shouldMove, error);
|
||||
}
|
||||
|
||||
bool RuntimeCoordinator::ValidatePresetName(const std::string& presetName, std::string& error) const
|
||||
{
|
||||
if (mRuntimeStore.IsValidStackPresetName(presetName))
|
||||
return true;
|
||||
|
||||
error = "Preset name must include at least one letter or number.";
|
||||
return false;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState, bool persistenceRequested)
|
||||
{
|
||||
if (!succeeded)
|
||||
{
|
||||
RuntimeCoordinatorResult result;
|
||||
result.accepted = false;
|
||||
result.errorMessage = errorMessage;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (reloadRequired)
|
||||
{
|
||||
RuntimeCoordinatorResult result = BuildQueuedReloadResult(preserveFeedbackState);
|
||||
result.persistenceRequested = persistenceRequested;
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
|
||||
result.persistenceRequested = persistenceRequested;
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::BuildQueuedReloadResult(bool preserveFeedbackState)
|
||||
{
|
||||
mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState;
|
||||
mUseCommittedLayerStates = true;
|
||||
|
||||
RuntimeCoordinatorResult result;
|
||||
result.accepted = true;
|
||||
result.runtimeStateBroadcastRequired = true;
|
||||
result.shaderBuildRequested = true;
|
||||
result.compileStatusChanged = true;
|
||||
result.compileStatusSucceeded = true;
|
||||
result.compileStatusMessage = "Shader rebuild queued.";
|
||||
result.clearReloadRequest = true;
|
||||
result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseCommittedStates;
|
||||
return result;
|
||||
}
|
||||
|
||||
RuntimeCoordinatorResult RuntimeCoordinator::BuildAcceptedNoReloadResult() const
|
||||
{
|
||||
RuntimeCoordinatorResult result;
|
||||
result.accepted = true;
|
||||
result.runtimeStateBroadcastRequired = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void RuntimeCoordinator::PublishFileChangeDetected(const std::string& reason, bool registryChanged, bool reloadRequested) const
|
||||
{
|
||||
try
|
||||
{
|
||||
FileChangeDetectedEvent event;
|
||||
event.path = reason;
|
||||
event.shaderPackageCandidate = registryChanged || reloadRequested;
|
||||
event.runtimeConfigCandidate = false;
|
||||
event.presetCandidate = false;
|
||||
mRuntimeEventDispatcher.PublishPayload(event, "RuntimeCoordinator");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeCoordinator::PublishManualReloadRequested(bool preserveFeedbackState, const std::string& reason) const
|
||||
{
|
||||
try
|
||||
{
|
||||
ManualReloadRequestedEvent event;
|
||||
event.preserveFeedbackState = preserveFeedbackState;
|
||||
event.reason = reason;
|
||||
mRuntimeEventDispatcher.PublishPayload(event, "RuntimeCoordinator");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeCoordinator::PublishCoordinatorResult(const std::string& action, const RuntimeCoordinatorResult& result) const
|
||||
{
|
||||
try
|
||||
{
|
||||
RuntimeMutationEvent mutation;
|
||||
mutation.action = action;
|
||||
mutation.accepted = result.accepted;
|
||||
mutation.runtimeStateChanged = result.accepted && result.runtimeStateBroadcastRequired;
|
||||
mutation.runtimeStateBroadcastRequired = result.runtimeStateBroadcastRequired;
|
||||
mutation.shaderBuildRequested = result.shaderBuildRequested;
|
||||
mutation.persistenceRequested = result.persistenceRequested;
|
||||
mutation.clearTransientOscState = result.transientOscInvalidation != RuntimeCoordinatorTransientOscInvalidation::None;
|
||||
mutation.renderResetScope = ToRuntimeEventRenderResetScope(result.renderResetScope);
|
||||
mutation.errorMessage = result.errorMessage;
|
||||
mRuntimeEventDispatcher.PublishPayload(mutation, "RuntimeCoordinator");
|
||||
|
||||
PublishCoordinatorFollowUpEvents(action, result);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeCoordinator::PublishCoordinatorFollowUpEvents(const std::string& action, const RuntimeCoordinatorResult& result) const
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!result.accepted)
|
||||
return;
|
||||
|
||||
if (result.runtimeStateBroadcastRequired)
|
||||
{
|
||||
RuntimeStateChangedEvent stateChanged;
|
||||
stateChanged.reason = action;
|
||||
stateChanged.renderVisible = result.renderResetScope != RuntimeCoordinatorRenderResetScope::None;
|
||||
stateChanged.persistenceRequested = result.persistenceRequested;
|
||||
mRuntimeEventDispatcher.PublishPayload(stateChanged, "RuntimeCoordinator");
|
||||
}
|
||||
|
||||
if (result.persistenceRequested)
|
||||
{
|
||||
RuntimePersistenceRequestedEvent persistenceRequested;
|
||||
persistenceRequested.request = PersistenceRequest::RuntimeStateRequest(action);
|
||||
mRuntimeEventDispatcher.PublishPayload(persistenceRequested, "RuntimeCoordinator");
|
||||
}
|
||||
|
||||
if (result.shaderBuildRequested)
|
||||
{
|
||||
RuntimeReloadRequestedEvent reloadRequested;
|
||||
reloadRequested.preserveFeedbackState = mPreserveFeedbackOnNextShaderBuild;
|
||||
reloadRequested.reason = action;
|
||||
mRuntimeEventDispatcher.PublishPayload(reloadRequested, "RuntimeCoordinator");
|
||||
|
||||
ShaderBuildEvent shaderBuild;
|
||||
shaderBuild.phase = RuntimeEventShaderBuildPhase::Requested;
|
||||
shaderBuild.preserveFeedbackState = mPreserveFeedbackOnNextShaderBuild;
|
||||
shaderBuild.succeeded = true;
|
||||
shaderBuild.message = result.compileStatusMessage;
|
||||
mRuntimeEventDispatcher.PublishPayload(shaderBuild, "RuntimeCoordinator");
|
||||
}
|
||||
|
||||
if (result.compileStatusChanged)
|
||||
{
|
||||
CompileStatusChangedEvent compileStatus;
|
||||
compileStatus.succeeded = result.compileStatusSucceeded;
|
||||
compileStatus.message = result.compileStatusMessage;
|
||||
mRuntimeEventDispatcher.PublishPayload(compileStatus, "RuntimeCoordinator");
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
class RuntimeStore;
|
||||
class RuntimeEventDispatcher;
|
||||
|
||||
enum class RuntimeCoordinatorCommittedStateMode
|
||||
{
|
||||
Unchanged,
|
||||
UseCommittedStates,
|
||||
UseLiveSnapshots
|
||||
};
|
||||
|
||||
enum class RuntimeCoordinatorRenderResetScope
|
||||
{
|
||||
None,
|
||||
TemporalHistoryOnly,
|
||||
TemporalHistoryAndFeedback
|
||||
};
|
||||
|
||||
enum class RuntimeCoordinatorTransientOscInvalidation
|
||||
{
|
||||
None,
|
||||
Layer,
|
||||
All
|
||||
};
|
||||
|
||||
enum class RuntimeCoordinatorOscCommitPersistence
|
||||
{
|
||||
SessionOnly,
|
||||
Persistent
|
||||
};
|
||||
|
||||
struct RuntimeCoordinatorResult
|
||||
{
|
||||
bool accepted = false;
|
||||
bool runtimeStateBroadcastRequired = false;
|
||||
bool shaderBuildRequested = false;
|
||||
bool persistenceRequested = false;
|
||||
bool compileStatusChanged = false;
|
||||
bool compileStatusSucceeded = false;
|
||||
bool clearReloadRequest = false;
|
||||
RuntimeCoordinatorCommittedStateMode committedStateMode = RuntimeCoordinatorCommittedStateMode::Unchanged;
|
||||
RuntimeCoordinatorRenderResetScope renderResetScope = RuntimeCoordinatorRenderResetScope::None;
|
||||
RuntimeCoordinatorTransientOscInvalidation transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::None;
|
||||
std::string transientOscLayerKey;
|
||||
std::string compileStatusMessage;
|
||||
std::string errorMessage;
|
||||
};
|
||||
|
||||
class RuntimeCoordinator
|
||||
{
|
||||
public:
|
||||
RuntimeCoordinator(RuntimeStore& runtimeStore, RuntimeEventDispatcher& runtimeEventDispatcher);
|
||||
|
||||
RuntimeCoordinatorResult AddLayer(const std::string& shaderId);
|
||||
RuntimeCoordinatorResult RemoveLayer(const std::string& layerId);
|
||||
RuntimeCoordinatorResult MoveLayer(const std::string& layerId, int direction);
|
||||
RuntimeCoordinatorResult MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex);
|
||||
RuntimeCoordinatorResult SetLayerBypass(const std::string& layerId, bool bypassed);
|
||||
RuntimeCoordinatorResult SetLayerShader(const std::string& layerId, const std::string& shaderId);
|
||||
RuntimeCoordinatorResult UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue);
|
||||
RuntimeCoordinatorResult UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue);
|
||||
RuntimeCoordinatorResult CommitOscParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue);
|
||||
RuntimeCoordinatorResult ResetLayerParameters(const std::string& layerId);
|
||||
RuntimeCoordinatorResult SaveStackPreset(const std::string& presetName);
|
||||
RuntimeCoordinatorResult LoadStackPreset(const std::string& presetName);
|
||||
|
||||
RuntimeCoordinatorResult RequestShaderReload(bool preserveFeedbackState = false);
|
||||
RuntimeCoordinatorResult PollRuntimeStoreChanges(bool& registryChanged);
|
||||
RuntimeCoordinatorResult HandleRuntimePollFailure(const std::string& error);
|
||||
RuntimeCoordinatorResult HandlePreparedShaderBuildFailure(const std::string& error);
|
||||
RuntimeCoordinatorResult HandlePreparedShaderBuildSuccess();
|
||||
RuntimeCoordinatorResult HandleRuntimeReloadRequest();
|
||||
void ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode);
|
||||
bool UseCommittedLayerStates() const;
|
||||
bool PreserveFeedbackOnNextShaderBuild() const;
|
||||
|
||||
private:
|
||||
struct ResolvedParameterMutation
|
||||
{
|
||||
std::string layerId;
|
||||
std::string parameterId;
|
||||
ShaderParameterValue value;
|
||||
bool persistState = true;
|
||||
};
|
||||
|
||||
bool BuildParameterMutationById(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue,
|
||||
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
|
||||
bool BuildParameterMutationByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue,
|
||||
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
|
||||
bool BuildParameterMutationFromSnapshot(const std::string& layerId, const ShaderParameterDefinition& definition,
|
||||
const ShaderParameterValue& currentValue, bool hasCurrentValue, const JsonValue& newValue,
|
||||
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
|
||||
bool ValidateLayerExists(const std::string& layerId, std::string& error) const;
|
||||
bool ValidateShaderExists(const std::string& shaderId, std::string& error) const;
|
||||
bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
|
||||
bool ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
|
||||
bool ValidatePresetName(const std::string& presetName, std::string& error) const;
|
||||
RuntimeCoordinatorResult ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState, bool persistenceRequested);
|
||||
RuntimeCoordinatorResult BuildQueuedReloadResult(bool preserveFeedbackState);
|
||||
RuntimeCoordinatorResult BuildAcceptedNoReloadResult() const;
|
||||
void PublishFileChangeDetected(const std::string& reason, bool registryChanged, bool reloadRequested) const;
|
||||
void PublishManualReloadRequested(bool preserveFeedbackState, const std::string& reason) const;
|
||||
void PublishCoordinatorResult(const std::string& action, const RuntimeCoordinatorResult& result) const;
|
||||
void PublishCoordinatorFollowUpEvents(const std::string& action, const RuntimeCoordinatorResult& result) const;
|
||||
|
||||
RuntimeStore& mRuntimeStore;
|
||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
||||
mutable std::mutex mMutex;
|
||||
bool mPreserveFeedbackOnNextShaderBuild = false;
|
||||
std::atomic<bool> mUseCommittedLayerStates{ false };
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeEventPayloads.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
using RuntimeEventPayload = std::variant<
|
||||
std::monostate,
|
||||
OscValueReceivedEvent,
|
||||
OscValueCoalescedEvent,
|
||||
OscCommitRequestedEvent,
|
||||
HttpControlMutationRequestedEvent,
|
||||
WebSocketClientConnectedEvent,
|
||||
RuntimeStateBroadcastRequestedEvent,
|
||||
FileChangeDetectedEvent,
|
||||
ManualReloadRequestedEvent,
|
||||
RuntimeMutationEvent,
|
||||
RuntimeStateChangedEvent,
|
||||
RuntimePersistenceRequestedEvent,
|
||||
RuntimeReloadRequestedEvent,
|
||||
ShaderPackagesChangedEvent,
|
||||
RenderSnapshotPublishRequestedEvent,
|
||||
RuntimeStatePresentationChangedEvent,
|
||||
ShaderBuildEvent,
|
||||
CompileStatusChangedEvent,
|
||||
RenderSnapshotPublishedEvent,
|
||||
RenderResetEvent,
|
||||
OscOverlayEvent,
|
||||
FrameRenderedEvent,
|
||||
PreviewFrameAvailableEvent,
|
||||
InputSignalChangedEvent,
|
||||
InputFrameArrivedEvent,
|
||||
OutputFrameScheduledEvent,
|
||||
OutputFrameCompletedEvent,
|
||||
BackendStateChangedEvent,
|
||||
SubsystemWarningEvent,
|
||||
SubsystemRecoveredEvent,
|
||||
TimingSampleRecordedEvent,
|
||||
QueueDepthChangedEvent>;
|
||||
|
||||
inline RuntimeEventType RuntimeEventPayloadType(const RuntimeEventPayload& payload)
|
||||
{
|
||||
return std::visit([](const auto& value) -> RuntimeEventType {
|
||||
using PayloadType = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_same_v<PayloadType, std::monostate>)
|
||||
return RuntimeEventType::Unknown;
|
||||
else
|
||||
return RuntimeEventPayloadType(value);
|
||||
}, payload);
|
||||
}
|
||||
|
||||
struct RuntimeEvent
|
||||
{
|
||||
RuntimeEventType type = RuntimeEventType::Unknown;
|
||||
uint64_t sequence = 0;
|
||||
std::chrono::steady_clock::time_point createdAt = std::chrono::steady_clock::now();
|
||||
std::string source;
|
||||
RuntimeEventPayload payload;
|
||||
|
||||
bool HasPayload() const
|
||||
{
|
||||
return !std::holds_alternative<std::monostate>(payload);
|
||||
}
|
||||
|
||||
bool PayloadMatchesType() const
|
||||
{
|
||||
return RuntimeEventPayloadType(payload) == type;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Payload>
|
||||
RuntimeEvent MakeRuntimeEvent(Payload payload, std::string source = {}, uint64_t sequence = 0,
|
||||
std::chrono::steady_clock::time_point createdAt = std::chrono::steady_clock::now())
|
||||
{
|
||||
RuntimeEvent event;
|
||||
event.type = RuntimeEventPayloadType(payload);
|
||||
event.sequence = sequence;
|
||||
event.createdAt = createdAt;
|
||||
event.source = std::move(source);
|
||||
event.payload = std::move(payload);
|
||||
return event;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeEvent.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
struct RuntimeEventCoalescingQueueMetrics
|
||||
{
|
||||
std::size_t depth = 0;
|
||||
std::size_t capacity = 0;
|
||||
std::size_t droppedCount = 0;
|
||||
std::size_t coalescedCount = 0;
|
||||
double oldestEventAgeMilliseconds = 0.0;
|
||||
};
|
||||
|
||||
inline std::string RuntimeEventDefaultCoalescingKey(const RuntimeEvent& event)
|
||||
{
|
||||
if (const auto* payload = std::get_if<OscValueReceivedEvent>(&event.payload))
|
||||
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->routeKey;
|
||||
if (const auto* payload = std::get_if<OscCommitRequestedEvent>(&event.payload))
|
||||
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->routeKey;
|
||||
if (const auto* payload = std::get_if<FileChangeDetectedEvent>(&event.payload))
|
||||
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->path;
|
||||
if (const auto* payload = std::get_if<ShaderBuildEvent>(&event.payload))
|
||||
return std::string(RuntimeEventTypeName(event.type)) + ":" +
|
||||
std::to_string(payload->inputWidth) + "x" +
|
||||
std::to_string(payload->inputHeight) + ":" +
|
||||
(payload->preserveFeedbackState ? "preserve" : "reset");
|
||||
if (const auto* payload = std::get_if<RenderSnapshotPublishRequestedEvent>(&event.payload))
|
||||
return std::string(RuntimeEventTypeName(event.type)) + ":" +
|
||||
std::to_string(payload->outputWidth) + "x" +
|
||||
std::to_string(payload->outputHeight);
|
||||
if (const auto* payload = std::get_if<TimingSampleRecordedEvent>(&event.payload))
|
||||
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->subsystem + ":" + payload->metric;
|
||||
if (const auto* payload = std::get_if<QueueDepthChangedEvent>(&event.payload))
|
||||
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->queueName;
|
||||
|
||||
return std::string(RuntimeEventTypeName(event.type));
|
||||
}
|
||||
|
||||
class RuntimeEventCoalescingQueue
|
||||
{
|
||||
public:
|
||||
using KeySelector = std::function<std::string(const RuntimeEvent&)>;
|
||||
|
||||
explicit RuntimeEventCoalescingQueue(std::size_t capacity = 256, KeySelector keySelector = RuntimeEventDefaultCoalescingKey) :
|
||||
mCapacity(capacity),
|
||||
mKeySelector(std::move(keySelector))
|
||||
{
|
||||
}
|
||||
|
||||
bool Push(RuntimeEvent event)
|
||||
{
|
||||
const std::string key = mKeySelector(event);
|
||||
if (key.empty())
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
auto found = mEntries.find(key);
|
||||
if (found != mEntries.end())
|
||||
{
|
||||
const auto firstCreatedAt = found->second.event.createdAt;
|
||||
found->second.event = std::move(event);
|
||||
found->second.event.createdAt = firstCreatedAt;
|
||||
++found->second.coalescedCount;
|
||||
++mCoalescedCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mEntries.size() >= mCapacity)
|
||||
{
|
||||
++mDroppedCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
mOrder.push_back(key);
|
||||
Entry entry;
|
||||
entry.event = std::move(event);
|
||||
mEntries.emplace(key, std::move(entry));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<RuntimeEvent> Drain(std::size_t maxEvents = 0)
|
||||
{
|
||||
std::vector<RuntimeEvent> events;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
const std::size_t count = maxEvents == 0 || maxEvents > mOrder.size() ? mOrder.size() : maxEvents;
|
||||
events.reserve(count);
|
||||
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
const std::string key = std::move(mOrder.front());
|
||||
mOrder.pop_front();
|
||||
|
||||
auto found = mEntries.find(key);
|
||||
if (found == mEntries.end())
|
||||
continue;
|
||||
|
||||
events.push_back(std::move(found->second.event));
|
||||
mEntries.erase(found);
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
RuntimeEventCoalescingQueueMetrics GetMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
RuntimeEventCoalescingQueueMetrics metrics;
|
||||
metrics.depth = mEntries.size();
|
||||
metrics.capacity = mCapacity;
|
||||
metrics.droppedCount = mDroppedCount;
|
||||
metrics.coalescedCount = mCoalescedCount;
|
||||
|
||||
if (!mOrder.empty())
|
||||
{
|
||||
const auto found = mEntries.find(mOrder.front());
|
||||
if (found != mEntries.end())
|
||||
{
|
||||
const auto age = now - found->second.event.createdAt;
|
||||
metrics.oldestEventAgeMilliseconds = std::chrono::duration<double, std::milli>(age).count();
|
||||
}
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
std::size_t Depth() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mEntries.size();
|
||||
}
|
||||
|
||||
private:
|
||||
struct Entry
|
||||
{
|
||||
RuntimeEvent event;
|
||||
std::size_t coalescedCount = 0;
|
||||
};
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
std::size_t mCapacity = 0;
|
||||
KeySelector mKeySelector;
|
||||
std::deque<std::string> mOrder;
|
||||
std::map<std::string, Entry> mEntries;
|
||||
std::size_t mDroppedCount = 0;
|
||||
std::size_t mCoalescedCount = 0;
|
||||
};
|
||||
@@ -0,0 +1,170 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeEventCoalescingQueue.h"
|
||||
#include "RuntimeEventQueue.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
struct RuntimeEventDispatchResult
|
||||
{
|
||||
std::size_t dispatchedEvents = 0;
|
||||
std::size_t handlerInvocations = 0;
|
||||
std::size_t handlerFailures = 0;
|
||||
double dispatchDurationMilliseconds = 0.0;
|
||||
};
|
||||
|
||||
class RuntimeEventDispatcher
|
||||
{
|
||||
public:
|
||||
using Handler = std::function<void(const RuntimeEvent&)>;
|
||||
|
||||
explicit RuntimeEventDispatcher(std::size_t queueCapacity = 1024) :
|
||||
mQueue(queueCapacity),
|
||||
mCoalescingQueue(queueCapacity)
|
||||
{
|
||||
}
|
||||
|
||||
bool Publish(RuntimeEvent event)
|
||||
{
|
||||
if (!event.PayloadMatchesType())
|
||||
return false;
|
||||
|
||||
if (event.sequence == 0)
|
||||
event.sequence = mNextSequence.fetch_add(1);
|
||||
|
||||
if (ShouldCoalesce(event))
|
||||
return mCoalescingQueue.Push(std::move(event));
|
||||
|
||||
return mQueue.Push(std::move(event));
|
||||
}
|
||||
|
||||
template <typename Payload>
|
||||
bool PublishPayload(Payload payload, std::string source = {})
|
||||
{
|
||||
return Publish(MakeRuntimeEvent(std::move(payload), std::move(source)));
|
||||
}
|
||||
|
||||
void Subscribe(RuntimeEventType type, Handler handler)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mHandlerMutex);
|
||||
mHandlers[type].push_back(std::move(handler));
|
||||
}
|
||||
|
||||
void SubscribeAll(Handler handler)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mHandlerMutex);
|
||||
mAllHandlers.push_back(std::move(handler));
|
||||
}
|
||||
|
||||
RuntimeEventDispatchResult DispatchPending(std::size_t maxEvents = 0)
|
||||
{
|
||||
const auto startedAt = std::chrono::steady_clock::now();
|
||||
RuntimeEventDispatchResult result;
|
||||
FlushCoalescedToFifo(maxEvents);
|
||||
std::vector<RuntimeEvent> events = mQueue.Drain(maxEvents);
|
||||
result.dispatchedEvents = events.size();
|
||||
|
||||
for (const RuntimeEvent& event : events)
|
||||
{
|
||||
std::vector<Handler> handlers = HandlersFor(event.type);
|
||||
result.handlerInvocations += handlers.size();
|
||||
|
||||
for (const Handler& handler : handlers)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(event);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
++result.handlerFailures;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.dispatchDurationMilliseconds =
|
||||
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - startedAt).count();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TryPop(RuntimeEvent& event)
|
||||
{
|
||||
return mQueue.TryPop(event);
|
||||
}
|
||||
|
||||
RuntimeEventQueueMetrics GetQueueMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
|
||||
{
|
||||
RuntimeEventQueueMetrics metrics = mQueue.GetMetrics(now);
|
||||
const RuntimeEventCoalescingQueueMetrics coalescingMetrics = mCoalescingQueue.GetMetrics(now);
|
||||
if (metrics.depth == 0)
|
||||
metrics.oldestEventAgeMilliseconds = coalescingMetrics.oldestEventAgeMilliseconds;
|
||||
else if (coalescingMetrics.depth > 0)
|
||||
metrics.oldestEventAgeMilliseconds = (std::max)(metrics.oldestEventAgeMilliseconds, coalescingMetrics.oldestEventAgeMilliseconds);
|
||||
metrics.depth += coalescingMetrics.depth;
|
||||
metrics.capacity += coalescingMetrics.capacity;
|
||||
metrics.droppedCount += coalescingMetrics.droppedCount;
|
||||
metrics.coalescedCount = coalescingMetrics.coalescedCount;
|
||||
return metrics;
|
||||
}
|
||||
|
||||
std::size_t QueueDepth() const
|
||||
{
|
||||
return mQueue.Depth() + mCoalescingQueue.Depth();
|
||||
}
|
||||
|
||||
private:
|
||||
static bool ShouldCoalesce(const RuntimeEvent& event)
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case RuntimeEventType::OscValueReceived:
|
||||
case RuntimeEventType::OscCommitRequested:
|
||||
case RuntimeEventType::RuntimeStateBroadcastRequested:
|
||||
case RuntimeEventType::FileChangeDetected:
|
||||
case RuntimeEventType::RuntimeReloadRequested:
|
||||
case RuntimeEventType::ShaderBuildRequested:
|
||||
case RuntimeEventType::RenderSnapshotPublishRequested:
|
||||
case RuntimeEventType::TimingSampleRecorded:
|
||||
case RuntimeEventType::QueueDepthChanged:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void FlushCoalescedToFifo(std::size_t maxEvents)
|
||||
{
|
||||
const std::size_t fifoDepth = mQueue.Depth();
|
||||
if (maxEvents != 0 && fifoDepth >= maxEvents)
|
||||
return;
|
||||
|
||||
const std::size_t flushLimit = maxEvents == 0 ? 0 : maxEvents - fifoDepth;
|
||||
std::vector<RuntimeEvent> events = mCoalescingQueue.Drain(flushLimit);
|
||||
for (RuntimeEvent& event : events)
|
||||
mQueue.Push(std::move(event));
|
||||
}
|
||||
|
||||
std::vector<Handler> HandlersFor(RuntimeEventType type) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mHandlerMutex);
|
||||
std::vector<Handler> handlers = mAllHandlers;
|
||||
|
||||
const auto found = mHandlers.find(type);
|
||||
if (found != mHandlers.end())
|
||||
handlers.insert(handlers.end(), found->second.begin(), found->second.end());
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
RuntimeEventQueue mQueue;
|
||||
RuntimeEventCoalescingQueue mCoalescingQueue;
|
||||
std::atomic<uint64_t> mNextSequence{ 1 };
|
||||
mutable std::mutex mHandlerMutex;
|
||||
std::map<RuntimeEventType, std::vector<Handler>> mHandlers;
|
||||
std::vector<Handler> mAllHandlers;
|
||||
};
|
||||
@@ -0,0 +1,442 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeEventType.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include "PersistenceRequest.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
enum class RuntimeEventSeverity
|
||||
{
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
};
|
||||
|
||||
enum class RuntimeEventRenderResetScope
|
||||
{
|
||||
None,
|
||||
TemporalHistoryOnly,
|
||||
TemporalHistoryAndFeedback
|
||||
};
|
||||
|
||||
enum class RuntimeEventShaderBuildPhase
|
||||
{
|
||||
Requested,
|
||||
Prepared,
|
||||
Applied,
|
||||
Failed
|
||||
};
|
||||
|
||||
struct OscValueReceivedEvent
|
||||
{
|
||||
std::string routeKey;
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
std::string valueJson;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
struct OscValueCoalescedEvent
|
||||
{
|
||||
std::string routeKey;
|
||||
std::size_t coalescedCount = 0;
|
||||
uint64_t latestGeneration = 0;
|
||||
};
|
||||
|
||||
struct OscCommitRequestedEvent
|
||||
{
|
||||
std::string routeKey;
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
std::string valueJson;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
struct HttpControlMutationRequestedEvent
|
||||
{
|
||||
std::string method;
|
||||
std::string path;
|
||||
std::string bodyJson;
|
||||
};
|
||||
|
||||
struct WebSocketClientConnectedEvent
|
||||
{
|
||||
std::string clientId;
|
||||
std::size_t connectedClientCount = 0;
|
||||
};
|
||||
|
||||
struct RuntimeStateBroadcastRequestedEvent
|
||||
{
|
||||
std::string reason;
|
||||
bool coalescable = true;
|
||||
};
|
||||
|
||||
struct FileChangeDetectedEvent
|
||||
{
|
||||
std::string path;
|
||||
bool shaderPackageCandidate = false;
|
||||
bool runtimeConfigCandidate = false;
|
||||
bool presetCandidate = false;
|
||||
};
|
||||
|
||||
struct ManualReloadRequestedEvent
|
||||
{
|
||||
bool preserveFeedbackState = false;
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
struct RuntimeMutationEvent
|
||||
{
|
||||
std::string action;
|
||||
bool accepted = false;
|
||||
bool runtimeStateChanged = false;
|
||||
bool runtimeStateBroadcastRequired = false;
|
||||
bool shaderBuildRequested = false;
|
||||
bool persistenceRequested = false;
|
||||
bool clearTransientOscState = false;
|
||||
RuntimeEventRenderResetScope renderResetScope = RuntimeEventRenderResetScope::None;
|
||||
std::string errorMessage;
|
||||
};
|
||||
|
||||
struct RuntimeStateChangedEvent
|
||||
{
|
||||
std::string reason;
|
||||
bool renderVisible = false;
|
||||
bool persistenceRequested = false;
|
||||
};
|
||||
|
||||
struct RuntimePersistenceRequestedEvent
|
||||
{
|
||||
PersistenceRequest request;
|
||||
};
|
||||
|
||||
struct RuntimeReloadRequestedEvent
|
||||
{
|
||||
bool preserveFeedbackState = false;
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
struct ShaderPackagesChangedEvent
|
||||
{
|
||||
bool registryChanged = false;
|
||||
std::size_t packageCount = 0;
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
struct RenderSnapshotPublishRequestedEvent
|
||||
{
|
||||
unsigned inputWidth = 0;
|
||||
unsigned inputHeight = 0;
|
||||
unsigned outputWidth = 0;
|
||||
unsigned outputHeight = 0;
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
struct RuntimeStatePresentationChangedEvent
|
||||
{
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
struct ShaderBuildEvent
|
||||
{
|
||||
RuntimeEventShaderBuildPhase phase = RuntimeEventShaderBuildPhase::Requested;
|
||||
uint64_t generation = 0;
|
||||
unsigned inputWidth = 0;
|
||||
unsigned inputHeight = 0;
|
||||
bool preserveFeedbackState = false;
|
||||
bool succeeded = false;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct CompileStatusChangedEvent
|
||||
{
|
||||
bool succeeded = false;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct RenderSnapshotPublishedEvent
|
||||
{
|
||||
uint64_t snapshotVersion = 0;
|
||||
uint64_t structureVersion = 0;
|
||||
uint64_t parameterVersion = 0;
|
||||
uint64_t packageVersion = 0;
|
||||
unsigned outputWidth = 0;
|
||||
unsigned outputHeight = 0;
|
||||
std::size_t layerCount = 0;
|
||||
};
|
||||
|
||||
struct RenderResetEvent
|
||||
{
|
||||
RuntimeEventRenderResetScope scope = RuntimeEventRenderResetScope::None;
|
||||
bool applied = false;
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
struct OscOverlayEvent
|
||||
{
|
||||
std::string routeKey;
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
uint64_t generation = 0;
|
||||
bool settled = false;
|
||||
};
|
||||
|
||||
struct FrameRenderedEvent
|
||||
{
|
||||
uint64_t frameIndex = 0;
|
||||
double renderMilliseconds = 0.0;
|
||||
};
|
||||
|
||||
struct PreviewFrameAvailableEvent
|
||||
{
|
||||
uint64_t frameIndex = 0;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
};
|
||||
|
||||
struct InputSignalChangedEvent
|
||||
{
|
||||
bool hasSignal = false;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
std::string modeName;
|
||||
};
|
||||
|
||||
struct InputFrameArrivedEvent
|
||||
{
|
||||
uint64_t frameIndex = 0;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
long rowBytes = 0;
|
||||
std::string pixelFormat;
|
||||
bool hasNoInputSource = false;
|
||||
};
|
||||
|
||||
struct OutputFrameScheduledEvent
|
||||
{
|
||||
uint64_t frameIndex = 0;
|
||||
int64_t streamTime = 0;
|
||||
int64_t duration = 0;
|
||||
int64_t timeScale = 0;
|
||||
};
|
||||
|
||||
struct OutputFrameCompletedEvent
|
||||
{
|
||||
uint64_t frameIndex = 0;
|
||||
std::string result;
|
||||
};
|
||||
|
||||
struct BackendStateChangedEvent
|
||||
{
|
||||
std::string backendName;
|
||||
std::string state;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct SubsystemWarningEvent
|
||||
{
|
||||
std::string subsystem;
|
||||
std::string warningKey;
|
||||
RuntimeEventSeverity severity = RuntimeEventSeverity::Warning;
|
||||
std::string message;
|
||||
bool cleared = false;
|
||||
};
|
||||
|
||||
struct SubsystemRecoveredEvent
|
||||
{
|
||||
std::string subsystem;
|
||||
std::string recoveryKey;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct TimingSampleRecordedEvent
|
||||
{
|
||||
std::string subsystem;
|
||||
std::string metric;
|
||||
double value = 0.0;
|
||||
std::string unit;
|
||||
};
|
||||
|
||||
struct QueueDepthChangedEvent
|
||||
{
|
||||
std::string queueName;
|
||||
std::size_t depth = 0;
|
||||
std::size_t capacity = 0;
|
||||
std::size_t droppedCount = 0;
|
||||
std::size_t coalescedCount = 0;
|
||||
};
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const OscValueReceivedEvent&)
|
||||
{
|
||||
return RuntimeEventType::OscValueReceived;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const OscValueCoalescedEvent&)
|
||||
{
|
||||
return RuntimeEventType::OscValueCoalesced;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const OscCommitRequestedEvent&)
|
||||
{
|
||||
return RuntimeEventType::OscCommitRequested;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const HttpControlMutationRequestedEvent&)
|
||||
{
|
||||
return RuntimeEventType::HttpControlMutationRequested;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const WebSocketClientConnectedEvent&)
|
||||
{
|
||||
return RuntimeEventType::WebSocketClientConnected;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStateBroadcastRequestedEvent&)
|
||||
{
|
||||
return RuntimeEventType::RuntimeStateBroadcastRequested;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const FileChangeDetectedEvent&)
|
||||
{
|
||||
return RuntimeEventType::FileChangeDetected;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const ManualReloadRequestedEvent&)
|
||||
{
|
||||
return RuntimeEventType::ManualReloadRequested;
|
||||
}
|
||||
|
||||
inline RuntimeEventType RuntimeEventPayloadType(const RuntimeMutationEvent& event)
|
||||
{
|
||||
return event.accepted ? RuntimeEventType::RuntimeMutationAccepted : RuntimeEventType::RuntimeMutationRejected;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStateChangedEvent&)
|
||||
{
|
||||
return RuntimeEventType::RuntimeStateChanged;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimePersistenceRequestedEvent&)
|
||||
{
|
||||
return RuntimeEventType::RuntimePersistenceRequested;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeReloadRequestedEvent&)
|
||||
{
|
||||
return RuntimeEventType::RuntimeReloadRequested;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const ShaderPackagesChangedEvent&)
|
||||
{
|
||||
return RuntimeEventType::ShaderPackagesChanged;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RenderSnapshotPublishRequestedEvent&)
|
||||
{
|
||||
return RuntimeEventType::RenderSnapshotPublishRequested;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStatePresentationChangedEvent&)
|
||||
{
|
||||
return RuntimeEventType::RuntimeStatePresentationChanged;
|
||||
}
|
||||
|
||||
inline RuntimeEventType RuntimeEventPayloadType(const ShaderBuildEvent& event)
|
||||
{
|
||||
switch (event.phase)
|
||||
{
|
||||
case RuntimeEventShaderBuildPhase::Requested:
|
||||
return RuntimeEventType::ShaderBuildRequested;
|
||||
case RuntimeEventShaderBuildPhase::Prepared:
|
||||
return RuntimeEventType::ShaderBuildPrepared;
|
||||
case RuntimeEventShaderBuildPhase::Applied:
|
||||
return RuntimeEventType::ShaderBuildApplied;
|
||||
case RuntimeEventShaderBuildPhase::Failed:
|
||||
return RuntimeEventType::ShaderBuildFailed;
|
||||
}
|
||||
|
||||
return RuntimeEventType::ShaderBuildRequested;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const CompileStatusChangedEvent&)
|
||||
{
|
||||
return RuntimeEventType::CompileStatusChanged;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RenderSnapshotPublishedEvent&)
|
||||
{
|
||||
return RuntimeEventType::RenderSnapshotPublished;
|
||||
}
|
||||
|
||||
inline RuntimeEventType RuntimeEventPayloadType(const RenderResetEvent& event)
|
||||
{
|
||||
return event.applied ? RuntimeEventType::RenderResetApplied : RuntimeEventType::RenderResetRequested;
|
||||
}
|
||||
|
||||
inline RuntimeEventType RuntimeEventPayloadType(const OscOverlayEvent& event)
|
||||
{
|
||||
return event.settled ? RuntimeEventType::OscOverlaySettled : RuntimeEventType::OscOverlayApplied;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const FrameRenderedEvent&)
|
||||
{
|
||||
return RuntimeEventType::FrameRendered;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const PreviewFrameAvailableEvent&)
|
||||
{
|
||||
return RuntimeEventType::PreviewFrameAvailable;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const InputSignalChangedEvent&)
|
||||
{
|
||||
return RuntimeEventType::InputSignalChanged;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const InputFrameArrivedEvent&)
|
||||
{
|
||||
return RuntimeEventType::InputFrameArrived;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const OutputFrameScheduledEvent&)
|
||||
{
|
||||
return RuntimeEventType::OutputFrameScheduled;
|
||||
}
|
||||
|
||||
inline RuntimeEventType RuntimeEventPayloadType(const OutputFrameCompletedEvent& event)
|
||||
{
|
||||
if (event.result == "DisplayedLate")
|
||||
return RuntimeEventType::OutputLateFrameDetected;
|
||||
if (event.result == "Dropped")
|
||||
return RuntimeEventType::OutputDroppedFrameDetected;
|
||||
return RuntimeEventType::OutputFrameCompleted;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const BackendStateChangedEvent&)
|
||||
{
|
||||
return RuntimeEventType::BackendStateChanged;
|
||||
}
|
||||
|
||||
inline RuntimeEventType RuntimeEventPayloadType(const SubsystemWarningEvent& event)
|
||||
{
|
||||
return event.cleared ? RuntimeEventType::SubsystemWarningCleared : RuntimeEventType::SubsystemWarningRaised;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const SubsystemRecoveredEvent&)
|
||||
{
|
||||
return RuntimeEventType::SubsystemRecovered;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const TimingSampleRecordedEvent&)
|
||||
{
|
||||
return RuntimeEventType::TimingSampleRecorded;
|
||||
}
|
||||
|
||||
constexpr RuntimeEventType RuntimeEventPayloadType(const QueueDepthChangedEvent&)
|
||||
{
|
||||
return RuntimeEventType::QueueDepthChanged;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeEvent.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
struct RuntimeEventQueueMetrics
|
||||
{
|
||||
std::size_t depth = 0;
|
||||
std::size_t capacity = 0;
|
||||
std::size_t droppedCount = 0;
|
||||
std::size_t coalescedCount = 0;
|
||||
double oldestEventAgeMilliseconds = 0.0;
|
||||
};
|
||||
|
||||
class RuntimeEventQueue
|
||||
{
|
||||
public:
|
||||
explicit RuntimeEventQueue(std::size_t capacity = 1024) :
|
||||
mCapacity(capacity)
|
||||
{
|
||||
}
|
||||
|
||||
bool Push(RuntimeEvent event)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mEvents.size() >= mCapacity)
|
||||
{
|
||||
++mDroppedCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
mEvents.push_back(std::move(event));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryPop(RuntimeEvent& event)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mEvents.empty())
|
||||
return false;
|
||||
|
||||
event = std::move(mEvents.front());
|
||||
mEvents.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<RuntimeEvent> Drain(std::size_t maxEvents = 0)
|
||||
{
|
||||
std::vector<RuntimeEvent> events;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
const std::size_t count = maxEvents == 0 || maxEvents > mEvents.size() ? mEvents.size() : maxEvents;
|
||||
events.reserve(count);
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
events.push_back(std::move(mEvents.front()));
|
||||
mEvents.pop_front();
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
RuntimeEventQueueMetrics GetMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
RuntimeEventQueueMetrics metrics;
|
||||
metrics.depth = mEvents.size();
|
||||
metrics.capacity = mCapacity;
|
||||
metrics.droppedCount = mDroppedCount;
|
||||
if (!mEvents.empty())
|
||||
{
|
||||
const auto age = now - mEvents.front().createdAt;
|
||||
metrics.oldestEventAgeMilliseconds = std::chrono::duration<double, std::milli>(age).count();
|
||||
}
|
||||
return metrics;
|
||||
}
|
||||
|
||||
std::size_t Depth() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mEvents.size();
|
||||
}
|
||||
|
||||
std::size_t Capacity() const
|
||||
{
|
||||
return mCapacity;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mMutex;
|
||||
std::deque<RuntimeEvent> mEvents;
|
||||
std::size_t mCapacity = 0;
|
||||
std::size_t mDroppedCount = 0;
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
enum class RuntimeEventType
|
||||
{
|
||||
Unknown = 0,
|
||||
|
||||
// Control ingress.
|
||||
OscValueReceived,
|
||||
OscValueCoalesced,
|
||||
OscCommitRequested,
|
||||
HttpControlMutationRequested,
|
||||
WebSocketClientConnected,
|
||||
RuntimeStateBroadcastRequested,
|
||||
FileChangeDetected,
|
||||
ManualReloadRequested,
|
||||
|
||||
// Runtime policy and state.
|
||||
RuntimeMutationAccepted,
|
||||
RuntimeMutationRejected,
|
||||
RuntimeStateChanged,
|
||||
RuntimePersistenceRequested,
|
||||
RuntimeReloadRequested,
|
||||
ShaderPackagesChanged,
|
||||
RenderSnapshotPublishRequested,
|
||||
RuntimeStatePresentationChanged,
|
||||
|
||||
// Shader build lifecycle.
|
||||
ShaderBuildRequested,
|
||||
ShaderBuildPrepared,
|
||||
ShaderBuildApplied,
|
||||
ShaderBuildFailed,
|
||||
CompileStatusChanged,
|
||||
|
||||
// Render lifecycle.
|
||||
RenderSnapshotPublished,
|
||||
RenderResetRequested,
|
||||
RenderResetApplied,
|
||||
OscOverlayApplied,
|
||||
OscOverlaySettled,
|
||||
FrameRendered,
|
||||
PreviewFrameAvailable,
|
||||
|
||||
// Video backend lifecycle.
|
||||
InputSignalChanged,
|
||||
InputFrameArrived,
|
||||
OutputFrameScheduled,
|
||||
OutputFrameCompleted,
|
||||
OutputLateFrameDetected,
|
||||
OutputDroppedFrameDetected,
|
||||
BackendStateChanged,
|
||||
|
||||
// Health and telemetry.
|
||||
SubsystemWarningRaised,
|
||||
SubsystemWarningCleared,
|
||||
SubsystemRecovered,
|
||||
TimingSampleRecorded,
|
||||
QueueDepthChanged
|
||||
};
|
||||
|
||||
constexpr std::string_view RuntimeEventTypeName(RuntimeEventType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case RuntimeEventType::Unknown:
|
||||
return "Unknown";
|
||||
case RuntimeEventType::OscValueReceived:
|
||||
return "OscValueReceived";
|
||||
case RuntimeEventType::OscValueCoalesced:
|
||||
return "OscValueCoalesced";
|
||||
case RuntimeEventType::OscCommitRequested:
|
||||
return "OscCommitRequested";
|
||||
case RuntimeEventType::HttpControlMutationRequested:
|
||||
return "HttpControlMutationRequested";
|
||||
case RuntimeEventType::WebSocketClientConnected:
|
||||
return "WebSocketClientConnected";
|
||||
case RuntimeEventType::RuntimeStateBroadcastRequested:
|
||||
return "RuntimeStateBroadcastRequested";
|
||||
case RuntimeEventType::FileChangeDetected:
|
||||
return "FileChangeDetected";
|
||||
case RuntimeEventType::ManualReloadRequested:
|
||||
return "ManualReloadRequested";
|
||||
case RuntimeEventType::RuntimeMutationAccepted:
|
||||
return "RuntimeMutationAccepted";
|
||||
case RuntimeEventType::RuntimeMutationRejected:
|
||||
return "RuntimeMutationRejected";
|
||||
case RuntimeEventType::RuntimeStateChanged:
|
||||
return "RuntimeStateChanged";
|
||||
case RuntimeEventType::RuntimePersistenceRequested:
|
||||
return "RuntimePersistenceRequested";
|
||||
case RuntimeEventType::RuntimeReloadRequested:
|
||||
return "RuntimeReloadRequested";
|
||||
case RuntimeEventType::ShaderPackagesChanged:
|
||||
return "ShaderPackagesChanged";
|
||||
case RuntimeEventType::RenderSnapshotPublishRequested:
|
||||
return "RenderSnapshotPublishRequested";
|
||||
case RuntimeEventType::RuntimeStatePresentationChanged:
|
||||
return "RuntimeStatePresentationChanged";
|
||||
case RuntimeEventType::ShaderBuildRequested:
|
||||
return "ShaderBuildRequested";
|
||||
case RuntimeEventType::ShaderBuildPrepared:
|
||||
return "ShaderBuildPrepared";
|
||||
case RuntimeEventType::ShaderBuildApplied:
|
||||
return "ShaderBuildApplied";
|
||||
case RuntimeEventType::ShaderBuildFailed:
|
||||
return "ShaderBuildFailed";
|
||||
case RuntimeEventType::CompileStatusChanged:
|
||||
return "CompileStatusChanged";
|
||||
case RuntimeEventType::RenderSnapshotPublished:
|
||||
return "RenderSnapshotPublished";
|
||||
case RuntimeEventType::RenderResetRequested:
|
||||
return "RenderResetRequested";
|
||||
case RuntimeEventType::RenderResetApplied:
|
||||
return "RenderResetApplied";
|
||||
case RuntimeEventType::OscOverlayApplied:
|
||||
return "OscOverlayApplied";
|
||||
case RuntimeEventType::OscOverlaySettled:
|
||||
return "OscOverlaySettled";
|
||||
case RuntimeEventType::FrameRendered:
|
||||
return "FrameRendered";
|
||||
case RuntimeEventType::PreviewFrameAvailable:
|
||||
return "PreviewFrameAvailable";
|
||||
case RuntimeEventType::InputSignalChanged:
|
||||
return "InputSignalChanged";
|
||||
case RuntimeEventType::InputFrameArrived:
|
||||
return "InputFrameArrived";
|
||||
case RuntimeEventType::OutputFrameScheduled:
|
||||
return "OutputFrameScheduled";
|
||||
case RuntimeEventType::OutputFrameCompleted:
|
||||
return "OutputFrameCompleted";
|
||||
case RuntimeEventType::OutputLateFrameDetected:
|
||||
return "OutputLateFrameDetected";
|
||||
case RuntimeEventType::OutputDroppedFrameDetected:
|
||||
return "OutputDroppedFrameDetected";
|
||||
case RuntimeEventType::BackendStateChanged:
|
||||
return "BackendStateChanged";
|
||||
case RuntimeEventType::SubsystemWarningRaised:
|
||||
return "SubsystemWarningRaised";
|
||||
case RuntimeEventType::SubsystemWarningCleared:
|
||||
return "SubsystemWarningCleared";
|
||||
case RuntimeEventType::SubsystemRecovered:
|
||||
return "SubsystemRecovered";
|
||||
case RuntimeEventType::TimingSampleRecorded:
|
||||
return "TimingSampleRecorded";
|
||||
case RuntimeEventType::QueueDepthChanged:
|
||||
return "QueueDepthChanged";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
#include "CommittedLiveState.h"
|
||||
|
||||
bool CommittedLiveState::LoadPersistentStateValue(const JsonValue& root)
|
||||
{
|
||||
return mLayerStack.LoadPersistentStateValue(root);
|
||||
}
|
||||
|
||||
JsonValue CommittedLiveState::BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const
|
||||
{
|
||||
return mLayerStack.BuildPersistentStateValue(shaderCatalog);
|
||||
}
|
||||
|
||||
void CommittedLiveState::NormalizeLayerIds()
|
||||
{
|
||||
mLayerStack.NormalizeLayerIds();
|
||||
}
|
||||
|
||||
void CommittedLiveState::EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog)
|
||||
{
|
||||
mLayerStack.EnsureDefaultsForAllLayers(shaderCatalog);
|
||||
}
|
||||
|
||||
void CommittedLiveState::EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog)
|
||||
{
|
||||
mLayerStack.EnsureDefaultLayer(shaderCatalog);
|
||||
}
|
||||
|
||||
void CommittedLiveState::RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog)
|
||||
{
|
||||
mLayerStack.RemoveLayersWithMissingPackages(shaderCatalog);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error)
|
||||
{
|
||||
return mLayerStack.CreateLayer(shaderCatalog, shaderId, error);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::DeleteLayer(const std::string& layerId, std::string& error)
|
||||
{
|
||||
return mLayerStack.DeleteLayer(layerId, error);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::MoveLayer(const std::string& layerId, int direction, std::string& error)
|
||||
{
|
||||
return mLayerStack.MoveLayer(layerId, direction, error);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
||||
{
|
||||
return mLayerStack.MoveLayerToIndex(layerId, targetIndex, error);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
|
||||
{
|
||||
return mLayerStack.SetLayerBypassState(layerId, bypassed, error);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error)
|
||||
{
|
||||
return mLayerStack.SetLayerShaderSelection(shaderCatalog, layerId, shaderId, error);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error)
|
||||
{
|
||||
return mLayerStack.SetParameterValue(layerId, parameterId, value, error);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error)
|
||||
{
|
||||
return mLayerStack.ResetLayerParameterValues(shaderCatalog, layerId, error);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::HasLayer(const std::string& layerId) const
|
||||
{
|
||||
return mLayerStack.HasLayer(layerId);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const
|
||||
{
|
||||
return mLayerStack.TryGetParameterById(shaderCatalog, layerId, parameterId, snapshot, error);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const
|
||||
{
|
||||
return mLayerStack.TryGetParameterByControlKey(shaderCatalog, layerKey, parameterKey, snapshot, error);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
|
||||
{
|
||||
return mLayerStack.ResolveLayerMove(layerId, direction, shouldMove, error);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
|
||||
{
|
||||
return mLayerStack.ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error);
|
||||
}
|
||||
|
||||
JsonValue CommittedLiveState::BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const
|
||||
{
|
||||
return mLayerStack.BuildStackPresetValue(shaderCatalog, presetName);
|
||||
}
|
||||
|
||||
bool CommittedLiveState::LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error)
|
||||
{
|
||||
return mLayerStack.LoadStackPresetValue(shaderCatalog, root, error);
|
||||
}
|
||||
|
||||
CommittedLiveStateReadModel CommittedLiveState::BuildReadModel(const ShaderPackageCatalog& shaderCatalog) const
|
||||
{
|
||||
CommittedLiveStateReadModel model;
|
||||
model.layers = mLayerStack.Layers();
|
||||
model.packagesById = shaderCatalog.CaptureSnapshot().packagesById;
|
||||
return model;
|
||||
}
|
||||
|
||||
std::vector<CommittedLiveState::LayerPersistentState> CommittedLiveState::CopyLayerStates() const
|
||||
{
|
||||
return mLayerStack.Layers();
|
||||
}
|
||||
|
||||
const std::vector<CommittedLiveState::LayerPersistentState>& CommittedLiveState::Layers() const
|
||||
{
|
||||
return mLayerStack.Layers();
|
||||
}
|
||||
|
||||
std::vector<CommittedLiveState::LayerPersistentState>& CommittedLiveState::Layers()
|
||||
{
|
||||
return mLayerStack.Layers();
|
||||
}
|
||||
|
||||
const CommittedLiveState::LayerPersistentState* CommittedLiveState::FindLayerById(const std::string& layerId) const
|
||||
{
|
||||
return mLayerStack.FindLayerById(layerId);
|
||||
}
|
||||
|
||||
const LayerStackStore& CommittedLiveState::LayerStack() const
|
||||
{
|
||||
return mLayerStack;
|
||||
}
|
||||
|
||||
LayerStackStore& CommittedLiveState::LayerStack()
|
||||
{
|
||||
return mLayerStack;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "LayerStackStore.h"
|
||||
#include "RuntimeStoreReadModels.h"
|
||||
#include "ShaderPackageCatalog.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class CommittedLiveState
|
||||
{
|
||||
public:
|
||||
using LayerPersistentState = LayerStackStore::LayerPersistentState;
|
||||
using StoredParameterSnapshot = LayerStackStore::StoredParameterSnapshot;
|
||||
|
||||
bool LoadPersistentStateValue(const JsonValue& root);
|
||||
JsonValue BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const;
|
||||
void NormalizeLayerIds();
|
||||
void EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog);
|
||||
void EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog);
|
||||
void RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog);
|
||||
|
||||
bool CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error);
|
||||
bool DeleteLayer(const std::string& layerId, std::string& error);
|
||||
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
|
||||
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
|
||||
bool SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error);
|
||||
bool SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error);
|
||||
bool SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error);
|
||||
bool ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error);
|
||||
|
||||
bool HasLayer(const std::string& layerId) const;
|
||||
bool TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const;
|
||||
bool TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const;
|
||||
bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
|
||||
bool ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
|
||||
|
||||
JsonValue BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const;
|
||||
bool LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error);
|
||||
|
||||
CommittedLiveStateReadModel BuildReadModel(const ShaderPackageCatalog& shaderCatalog) const;
|
||||
std::vector<LayerPersistentState> CopyLayerStates() const;
|
||||
const std::vector<LayerPersistentState>& Layers() const;
|
||||
std::vector<LayerPersistentState>& Layers();
|
||||
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
|
||||
const LayerStackStore& LayerStack() const;
|
||||
LayerStackStore& LayerStack();
|
||||
|
||||
private:
|
||||
LayerStackStore mLayerStack;
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "RenderStateComposer.h"
|
||||
|
||||
RenderStateCompositionResult RenderStateComposer::BuildFrameState(const LayeredRenderStateInput& input) const
|
||||
{
|
||||
RenderStateCompositionResult result;
|
||||
const std::vector<RuntimeRenderState>* layerStates =
|
||||
input.committedLiveLayerStates ? input.committedLiveLayerStates : input.basePersistedLayerStates;
|
||||
if (!layerStates)
|
||||
return result;
|
||||
|
||||
result.layerStates = *layerStates;
|
||||
result.hasLayerStates = !result.layerStates.empty();
|
||||
if (input.transientAutomationOverlay)
|
||||
{
|
||||
RuntimeLiveStateApplyOptions options;
|
||||
options.allowCommit = input.allowTransientAutomationCommits;
|
||||
options.smoothing = input.transientAutomationSmoothing;
|
||||
options.commitDelay = input.transientAutomationCommitDelay;
|
||||
options.now = input.now;
|
||||
input.transientAutomationOverlay->ApplyToLayerStates(
|
||||
result.layerStates,
|
||||
options,
|
||||
input.collectTransientAutomationCommitRequests ? &result.commitRequests : nullptr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeLiveState.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
|
||||
struct LayeredRenderStateInput
|
||||
{
|
||||
const std::vector<RuntimeRenderState>* basePersistedLayerStates = nullptr;
|
||||
const std::vector<RuntimeRenderState>* committedLiveLayerStates = nullptr;
|
||||
RuntimeLiveState* transientAutomationOverlay = nullptr;
|
||||
bool allowTransientAutomationCommits = false;
|
||||
bool collectTransientAutomationCommitRequests = true;
|
||||
double transientAutomationSmoothing = 0.0;
|
||||
std::chrono::milliseconds transientAutomationCommitDelay = std::chrono::milliseconds(150);
|
||||
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||
};
|
||||
|
||||
struct RenderStateCompositionResult
|
||||
{
|
||||
std::vector<RuntimeRenderState> layerStates;
|
||||
std::vector<RuntimeLiveOscCommitRequest> commitRequests;
|
||||
bool hasLayerStates = false;
|
||||
};
|
||||
|
||||
class RenderStateComposer
|
||||
{
|
||||
public:
|
||||
RenderStateCompositionResult BuildFrameState(const LayeredRenderStateInput& input) const;
|
||||
};
|
||||
@@ -0,0 +1,329 @@
|
||||
#include "RuntimeLiveState.h"
|
||||
|
||||
#include "RuntimeParameterUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr double kOscSmoothingReferenceFps = 60.0;
|
||||
constexpr double kOscSmoothingMaxStepSeconds = 0.25;
|
||||
|
||||
std::string SimplifyOscControlKey(const std::string& text)
|
||||
{
|
||||
std::string simplified;
|
||||
for (unsigned char ch : text)
|
||||
{
|
||||
if (std::isalnum(ch))
|
||||
simplified.push_back(static_cast<char>(std::tolower(ch)));
|
||||
}
|
||||
return simplified;
|
||||
}
|
||||
|
||||
bool MatchesOscControlKey(const std::string& candidate, const std::string& key)
|
||||
{
|
||||
return candidate == key || SimplifyOscControlKey(candidate) == SimplifyOscControlKey(key);
|
||||
}
|
||||
|
||||
double ClampOscAlpha(double value)
|
||||
{
|
||||
return (std::max)(0.0, (std::min)(1.0, value));
|
||||
}
|
||||
|
||||
double ComputeTimeBasedOscAlpha(double smoothing, double deltaSeconds)
|
||||
{
|
||||
const double clampedSmoothing = ClampOscAlpha(smoothing);
|
||||
if (clampedSmoothing <= 0.0)
|
||||
return 0.0;
|
||||
if (clampedSmoothing >= 1.0)
|
||||
return 1.0;
|
||||
|
||||
const double clampedDeltaSeconds = (std::max)(0.0, (std::min)(kOscSmoothingMaxStepSeconds, deltaSeconds));
|
||||
if (clampedDeltaSeconds <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
const double frameScale = clampedDeltaSeconds * kOscSmoothingReferenceFps;
|
||||
return ClampOscAlpha(1.0 - std::pow(1.0 - clampedSmoothing, frameScale));
|
||||
}
|
||||
|
||||
JsonValue BuildOscCommitValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
|
||||
{
|
||||
switch (definition.type)
|
||||
{
|
||||
case ShaderParameterType::Boolean:
|
||||
return JsonValue(value.booleanValue);
|
||||
case ShaderParameterType::Enum:
|
||||
return JsonValue(value.enumValue);
|
||||
case ShaderParameterType::Text:
|
||||
return JsonValue(value.textValue);
|
||||
case ShaderParameterType::Trigger:
|
||||
case ShaderParameterType::Float:
|
||||
return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
|
||||
case ShaderParameterType::Vec2:
|
||||
case ShaderParameterType::Color:
|
||||
{
|
||||
JsonValue array = JsonValue::MakeArray();
|
||||
for (double number : value.numberValues)
|
||||
array.pushBack(JsonValue(number));
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
return JsonValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RuntimeLiveState::Clear()
|
||||
{
|
||||
mOscOverlayStates.clear();
|
||||
}
|
||||
|
||||
void RuntimeLiveState::ClearForLayerKey(const std::string& layerKey)
|
||||
{
|
||||
for (auto it = mOscOverlayStates.begin(); it != mOscOverlayStates.end();)
|
||||
{
|
||||
if (OverlayMatchesLayerKey(it->second, layerKey))
|
||||
it = mOscOverlayStates.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeLiveState::OverlayMatchesLayerKey(const OscOverlayState& overlay, const std::string& layerKey)
|
||||
{
|
||||
return MatchesOscControlKey(overlay.layerKey, layerKey);
|
||||
}
|
||||
|
||||
bool RuntimeLiveState::TryResolveOverlayTarget(
|
||||
const OscOverlayState& overlay,
|
||||
const std::vector<RuntimeRenderState>& states,
|
||||
std::vector<RuntimeRenderState>::const_iterator& stateIt,
|
||||
std::vector<ShaderParameterDefinition>::const_iterator& definitionIt)
|
||||
{
|
||||
stateIt = std::find_if(states.begin(), states.end(),
|
||||
[&overlay](const RuntimeRenderState& state)
|
||||
{
|
||||
return MatchesOscControlKey(state.layerId, overlay.layerKey) ||
|
||||
MatchesOscControlKey(state.shaderId, overlay.layerKey) ||
|
||||
MatchesOscControlKey(state.shaderName, overlay.layerKey);
|
||||
});
|
||||
if (stateIt == states.end())
|
||||
return false;
|
||||
|
||||
definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(),
|
||||
[&overlay](const ShaderParameterDefinition& definition)
|
||||
{
|
||||
return MatchesOscControlKey(definition.id, overlay.parameterKey) ||
|
||||
MatchesOscControlKey(definition.label, overlay.parameterKey);
|
||||
});
|
||||
return definitionIt != stateIt->parameterDefinitions.end();
|
||||
}
|
||||
|
||||
std::size_t RuntimeLiveState::OverlayCount() const
|
||||
{
|
||||
return mOscOverlayStates.size();
|
||||
}
|
||||
|
||||
void RuntimeLiveState::ApplyOscUpdates(const std::vector<RuntimeLiveOscUpdate>& updates)
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
for (const RuntimeLiveOscUpdate& update : updates)
|
||||
{
|
||||
auto overlayIt = mOscOverlayStates.find(update.routeKey);
|
||||
if (overlayIt == mOscOverlayStates.end())
|
||||
{
|
||||
OscOverlayState overlay;
|
||||
overlay.layerKey = update.layerKey;
|
||||
overlay.parameterKey = update.parameterKey;
|
||||
overlay.targetValue = update.targetValue;
|
||||
overlay.lastUpdatedTime = now;
|
||||
overlay.lastAppliedTime = now;
|
||||
overlay.generation = 1;
|
||||
mOscOverlayStates[update.routeKey] = std::move(overlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
overlayIt->second.targetValue = update.targetValue;
|
||||
overlayIt->second.lastUpdatedTime = now;
|
||||
overlayIt->second.generation += 1;
|
||||
overlayIt->second.commitQueued = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeLiveState::ApplyOscCommitCompletions(const std::vector<RuntimeLiveOscCommitCompletion>& completedCommits)
|
||||
{
|
||||
for (const RuntimeLiveOscCommitCompletion& completedCommit : completedCommits)
|
||||
{
|
||||
auto overlayIt = mOscOverlayStates.find(completedCommit.routeKey);
|
||||
if (overlayIt == mOscOverlayStates.end())
|
||||
continue;
|
||||
|
||||
OscOverlayState& overlay = overlayIt->second;
|
||||
if (overlay.commitQueued &&
|
||||
overlay.pendingCommitGeneration == completedCommit.generation &&
|
||||
overlay.generation == completedCommit.generation)
|
||||
{
|
||||
mOscOverlayStates.erase(overlayIt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeLiveState::PruneIncompatibleOverlays(const std::vector<RuntimeRenderState>& states)
|
||||
{
|
||||
for (auto it = mOscOverlayStates.begin(); it != mOscOverlayStates.end();)
|
||||
{
|
||||
std::vector<RuntimeRenderState>::const_iterator stateIt;
|
||||
std::vector<ShaderParameterDefinition>::const_iterator definitionIt;
|
||||
if (TryResolveOverlayTarget(it->second, states, stateIt, definitionIt))
|
||||
{
|
||||
ShaderParameterValue targetValue;
|
||||
std::string normalizeError;
|
||||
if (NormalizeAndValidateParameterValue(*definitionIt, it->second.targetValue, targetValue, normalizeError))
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
it = mOscOverlayStates.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeLiveState::ApplyToLayerStates(
|
||||
std::vector<RuntimeRenderState>& states,
|
||||
const RuntimeLiveStateApplyOptions& options,
|
||||
std::vector<RuntimeLiveOscCommitRequest>* commitRequests)
|
||||
{
|
||||
if (states.empty() || mOscOverlayStates.empty())
|
||||
return;
|
||||
|
||||
PruneIncompatibleOverlays(states);
|
||||
if (mOscOverlayStates.empty())
|
||||
return;
|
||||
|
||||
const auto now = options.now;
|
||||
const double clampedSmoothing = ClampOscAlpha(options.smoothing);
|
||||
std::vector<std::string> overlayKeysToRemove;
|
||||
|
||||
for (auto& item : mOscOverlayStates)
|
||||
{
|
||||
const std::string& routeKey = item.first;
|
||||
OscOverlayState& overlay = item.second;
|
||||
auto stateIt = std::find_if(states.begin(), states.end(),
|
||||
[&overlay](const RuntimeRenderState& state)
|
||||
{
|
||||
return MatchesOscControlKey(state.layerId, overlay.layerKey) ||
|
||||
MatchesOscControlKey(state.shaderId, overlay.layerKey) ||
|
||||
MatchesOscControlKey(state.shaderName, overlay.layerKey);
|
||||
});
|
||||
if (stateIt == states.end())
|
||||
continue;
|
||||
|
||||
auto definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(),
|
||||
[&overlay](const ShaderParameterDefinition& definition)
|
||||
{
|
||||
return MatchesOscControlKey(definition.id, overlay.parameterKey) ||
|
||||
MatchesOscControlKey(definition.label, overlay.parameterKey);
|
||||
});
|
||||
if (definitionIt == stateIt->parameterDefinitions.end())
|
||||
continue;
|
||||
|
||||
ShaderParameterValue targetValue;
|
||||
std::string normalizeError;
|
||||
if (!NormalizeAndValidateParameterValue(*definitionIt, overlay.targetValue, targetValue, normalizeError))
|
||||
continue;
|
||||
|
||||
if (definitionIt->type == ShaderParameterType::Trigger)
|
||||
{
|
||||
ShaderParameterValue& value = stateIt->parameterValues[definitionIt->id];
|
||||
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
|
||||
const double triggerTime = stateIt->timeSeconds;
|
||||
value.numberValues = { previousCount + 1.0, triggerTime };
|
||||
overlayKeysToRemove.push_back(routeKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool smoothable =
|
||||
clampedSmoothing > 0.0 &&
|
||||
(definitionIt->type == ShaderParameterType::Float ||
|
||||
definitionIt->type == ShaderParameterType::Vec2 ||
|
||||
definitionIt->type == ShaderParameterType::Color);
|
||||
if (!smoothable)
|
||||
{
|
||||
overlay.currentValue = targetValue;
|
||||
overlay.hasCurrentValue = true;
|
||||
stateIt->parameterValues[definitionIt->id] = overlay.currentValue;
|
||||
if (options.allowCommit &&
|
||||
!overlay.commitQueued &&
|
||||
now - overlay.lastUpdatedTime >= options.commitDelay &&
|
||||
commitRequests)
|
||||
{
|
||||
commitRequests->push_back({ routeKey, overlay.layerKey, overlay.parameterKey, overlay.targetValue, overlay.generation });
|
||||
overlay.pendingCommitGeneration = overlay.generation;
|
||||
overlay.commitQueued = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!overlay.hasCurrentValue)
|
||||
{
|
||||
overlay.currentValue = DefaultValueForDefinition(*definitionIt);
|
||||
auto currentIt = stateIt->parameterValues.find(definitionIt->id);
|
||||
if (currentIt != stateIt->parameterValues.end())
|
||||
overlay.currentValue = currentIt->second;
|
||||
overlay.hasCurrentValue = true;
|
||||
}
|
||||
|
||||
if (overlay.currentValue.numberValues.size() != targetValue.numberValues.size())
|
||||
overlay.currentValue.numberValues = targetValue.numberValues;
|
||||
|
||||
double smoothingAlpha = clampedSmoothing;
|
||||
if (overlay.lastAppliedTime != std::chrono::steady_clock::time_point())
|
||||
{
|
||||
const double deltaSeconds =
|
||||
std::chrono::duration_cast<std::chrono::duration<double>>(now - overlay.lastAppliedTime).count();
|
||||
smoothingAlpha = ComputeTimeBasedOscAlpha(clampedSmoothing, deltaSeconds);
|
||||
}
|
||||
overlay.lastAppliedTime = now;
|
||||
|
||||
ShaderParameterValue nextValue = targetValue;
|
||||
bool converged = true;
|
||||
for (std::size_t index = 0; index < targetValue.numberValues.size(); ++index)
|
||||
{
|
||||
const double currentNumber = overlay.currentValue.numberValues[index];
|
||||
const double targetNumber = targetValue.numberValues[index];
|
||||
const double delta = targetNumber - currentNumber;
|
||||
double nextNumber = currentNumber + delta * smoothingAlpha;
|
||||
if (std::fabs(delta) <= 0.0005)
|
||||
nextNumber = targetNumber;
|
||||
else
|
||||
converged = false;
|
||||
nextValue.numberValues[index] = nextNumber;
|
||||
}
|
||||
|
||||
if (converged)
|
||||
nextValue.numberValues = targetValue.numberValues;
|
||||
|
||||
overlay.currentValue = nextValue;
|
||||
overlay.hasCurrentValue = true;
|
||||
stateIt->parameterValues[definitionIt->id] = overlay.currentValue;
|
||||
if (options.allowCommit &&
|
||||
converged &&
|
||||
!overlay.commitQueued &&
|
||||
now - overlay.lastUpdatedTime >= options.commitDelay &&
|
||||
commitRequests)
|
||||
{
|
||||
commitRequests->push_back({ routeKey, overlay.layerKey, overlay.parameterKey, BuildOscCommitValue(*definitionIt, overlay.currentValue), overlay.generation });
|
||||
overlay.pendingCommitGeneration = overlay.generation;
|
||||
overlay.commitQueued = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::string& overlayKey : overlayKeysToRemove)
|
||||
mOscOverlayStates.erase(overlayKey);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct RuntimeLiveOscUpdate
|
||||
{
|
||||
std::string routeKey;
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
JsonValue targetValue;
|
||||
};
|
||||
|
||||
struct RuntimeLiveOscCommitCompletion
|
||||
{
|
||||
std::string routeKey;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
struct RuntimeLiveOscCommitRequest
|
||||
{
|
||||
std::string routeKey;
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
JsonValue value;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
struct RuntimeLiveStateApplyOptions
|
||||
{
|
||||
bool allowCommit = false;
|
||||
double smoothing = 0.0;
|
||||
std::chrono::milliseconds commitDelay = std::chrono::milliseconds(150);
|
||||
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||
};
|
||||
|
||||
class RuntimeLiveState
|
||||
{
|
||||
public:
|
||||
void Clear();
|
||||
void ClearForLayerKey(const std::string& layerKey);
|
||||
std::size_t OverlayCount() const;
|
||||
void ApplyOscUpdates(const std::vector<RuntimeLiveOscUpdate>& updates);
|
||||
void ApplyOscCommitCompletions(const std::vector<RuntimeLiveOscCommitCompletion>& completedCommits);
|
||||
void PruneIncompatibleOverlays(const std::vector<RuntimeRenderState>& states);
|
||||
void ApplyToLayerStates(
|
||||
std::vector<RuntimeRenderState>& states,
|
||||
const RuntimeLiveStateApplyOptions& options,
|
||||
std::vector<RuntimeLiveOscCommitRequest>* commitRequests);
|
||||
|
||||
private:
|
||||
struct OscOverlayState
|
||||
{
|
||||
std::string layerKey;
|
||||
std::string parameterKey;
|
||||
JsonValue targetValue;
|
||||
ShaderParameterValue currentValue;
|
||||
bool hasCurrentValue = false;
|
||||
std::chrono::steady_clock::time_point lastUpdatedTime;
|
||||
std::chrono::steady_clock::time_point lastAppliedTime;
|
||||
uint64_t generation = 0;
|
||||
uint64_t pendingCommitGeneration = 0;
|
||||
bool commitQueued = false;
|
||||
};
|
||||
|
||||
static bool OverlayMatchesLayerKey(const OscOverlayState& overlay, const std::string& layerKey);
|
||||
static bool TryResolveOverlayTarget(
|
||||
const OscOverlayState& overlay,
|
||||
const std::vector<RuntimeRenderState>& states,
|
||||
std::vector<RuntimeRenderState>::const_iterator& stateIt,
|
||||
std::vector<ShaderParameterDefinition>::const_iterator& definitionIt);
|
||||
|
||||
std::map<std::string, OscOverlayState> mOscOverlayStates;
|
||||
};
|
||||
@@ -0,0 +1,229 @@
|
||||
#include "RuntimeStateLayerModel.h"
|
||||
|
||||
const char* RuntimeStateLayerKindName(RuntimeStateLayerKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case RuntimeStateLayerKind::BasePersisted:
|
||||
return "base persisted";
|
||||
case RuntimeStateLayerKind::CommittedLive:
|
||||
return "committed live";
|
||||
case RuntimeStateLayerKind::TransientAutomation:
|
||||
return "transient automation";
|
||||
case RuntimeStateLayerKind::RenderLocal:
|
||||
return "render local";
|
||||
case RuntimeStateLayerKind::HealthConfig:
|
||||
return "health/config";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
int RuntimeStateLayerCompositionPrecedence(RuntimeStateLayerKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case RuntimeStateLayerKind::BasePersisted:
|
||||
return 0;
|
||||
case RuntimeStateLayerKind::CommittedLive:
|
||||
return 1;
|
||||
case RuntimeStateLayerKind::TransientAutomation:
|
||||
return 2;
|
||||
case RuntimeStateLayerKind::RenderLocal:
|
||||
case RuntimeStateLayerKind::HealthConfig:
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeStateLayerIsDurable(RuntimeStateLayerKind kind)
|
||||
{
|
||||
return kind == RuntimeStateLayerKind::BasePersisted;
|
||||
}
|
||||
|
||||
bool RuntimeStateLayerParticipatesInParameterComposition(RuntimeStateLayerKind kind)
|
||||
{
|
||||
return RuntimeStateLayerCompositionPrecedence(kind) >= 0;
|
||||
}
|
||||
|
||||
bool RuntimeStateLayerIsRenderLocal(RuntimeStateLayerKind kind)
|
||||
{
|
||||
return kind == RuntimeStateLayerKind::RenderLocal;
|
||||
}
|
||||
|
||||
RuntimeStateLayerKind ClassifyRuntimeStateField(RuntimeStateField field)
|
||||
{
|
||||
switch (field)
|
||||
{
|
||||
case RuntimeStateField::PersistedLayerStack:
|
||||
case RuntimeStateField::PersistedParameterValues:
|
||||
case RuntimeStateField::StackPresets:
|
||||
return RuntimeStateLayerKind::BasePersisted;
|
||||
case RuntimeStateField::CommittedSessionParameterValues:
|
||||
case RuntimeStateField::CommittedLayerBypass:
|
||||
case RuntimeStateField::RuntimeCompileReloadFlags:
|
||||
return RuntimeStateLayerKind::CommittedLive;
|
||||
case RuntimeStateField::TransientOscOverlay:
|
||||
case RuntimeStateField::TransientAutomationCommitState:
|
||||
return RuntimeStateLayerKind::TransientAutomation;
|
||||
case RuntimeStateField::RenderLocalTemporalHistory:
|
||||
case RuntimeStateField::RenderLocalFeedbackState:
|
||||
case RuntimeStateField::RenderLocalInputFrames:
|
||||
case RuntimeStateField::RenderLocalOutputFrames:
|
||||
return RuntimeStateLayerKind::RenderLocal;
|
||||
case RuntimeStateField::RuntimeConfiguration:
|
||||
case RuntimeStateField::HealthTelemetry:
|
||||
default:
|
||||
return RuntimeStateLayerKind::HealthConfig;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RuntimeStateLayerDescriptor> GetRuntimeStateLayerInventory()
|
||||
{
|
||||
return {
|
||||
{
|
||||
RuntimeStateLayerKind::BasePersisted,
|
||||
"Base persisted state",
|
||||
"RuntimeStore / LayerStackStore",
|
||||
"Survives restart",
|
||||
"Written to disk",
|
||||
"Default layer stack, shader selections, saved parameter values"
|
||||
},
|
||||
{
|
||||
RuntimeStateLayerKind::CommittedLive,
|
||||
"Committed live state",
|
||||
"RuntimeCoordinator / CommittedLiveState",
|
||||
"Current running session",
|
||||
"May request persistence depending on mutation policy",
|
||||
"Operator/session truth until changed again"
|
||||
},
|
||||
{
|
||||
RuntimeStateLayerKind::TransientAutomation,
|
||||
"Transient automation overlay",
|
||||
"RuntimeLiveState / RuntimeServiceLiveBridge",
|
||||
"High-rate and short-lived",
|
||||
"Not persisted directly",
|
||||
"Temporary OSC/automation target applied over committed truth"
|
||||
},
|
||||
{
|
||||
RuntimeStateLayerKind::RenderLocal,
|
||||
"Render-local state",
|
||||
"RenderEngine",
|
||||
"Render-thread/resource lifetime",
|
||||
"Not persisted",
|
||||
"Temporal history, feedback, input/output queues, and GL-local caches"
|
||||
},
|
||||
{
|
||||
RuntimeStateLayerKind::HealthConfig,
|
||||
"Health/config state",
|
||||
"RuntimeConfigStore / HealthTelemetry",
|
||||
"Config survives restart; health is observational",
|
||||
"Config is file-backed; health is reported, not composed",
|
||||
"Does not participate in parameter composition"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<RuntimeStateFieldDescriptor> GetRuntimeStateFieldInventory()
|
||||
{
|
||||
return {
|
||||
{
|
||||
RuntimeStateField::PersistedLayerStack,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::PersistedLayerStack),
|
||||
"persisted layer stack",
|
||||
"LayerStackStore",
|
||||
"Durable layer order, ids, shader selections, and bypass flags"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::PersistedParameterValues,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::PersistedParameterValues),
|
||||
"persisted parameter values",
|
||||
"LayerStackStore",
|
||||
"Saved parameter values used as the baseline for snapshots and presets"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::StackPresets,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::StackPresets),
|
||||
"stack presets",
|
||||
"RuntimeStore / LayerStackStore",
|
||||
"Durable preset files and preset serialization shape"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::CommittedSessionParameterValues,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::CommittedSessionParameterValues),
|
||||
"committed session parameter values",
|
||||
"RuntimeCoordinator policy, CommittedLiveState backing",
|
||||
"Operator/API truth after accepted mutations"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::CommittedLayerBypass,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::CommittedLayerBypass),
|
||||
"committed layer bypass",
|
||||
"RuntimeCoordinator policy, CommittedLiveState backing",
|
||||
"Current operator/API bypass state"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::RuntimeCompileReloadFlags,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::RuntimeCompileReloadFlags),
|
||||
"runtime compile/reload flags",
|
||||
"RuntimeCoordinator / RuntimeUpdateController",
|
||||
"Session coordination state used to request snapshot or render rebuild work"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::TransientOscOverlay,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::TransientOscOverlay),
|
||||
"transient OSC overlays",
|
||||
"RuntimeLiveState",
|
||||
"High-rate automation values applied above committed state"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::TransientAutomationCommitState,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::TransientAutomationCommitState),
|
||||
"transient automation commit state",
|
||||
"RuntimeLiveState / RuntimeServiceLiveBridge",
|
||||
"Generation and completion bookkeeping for settled overlay commits"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::RenderLocalTemporalHistory,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::RenderLocalTemporalHistory),
|
||||
"render-local temporal history",
|
||||
"RenderEngine",
|
||||
"GL/resource history that must stay out of parameter layering"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::RenderLocalFeedbackState,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::RenderLocalFeedbackState),
|
||||
"render-local feedback state",
|
||||
"RenderEngine",
|
||||
"Feedback buffers and ping-pong resources"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::RenderLocalInputFrames,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::RenderLocalInputFrames),
|
||||
"render-local input frames",
|
||||
"RenderEngine",
|
||||
"Latest accepted input frame payloads and upload staging"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::RenderLocalOutputFrames,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::RenderLocalOutputFrames),
|
||||
"render-local output frames",
|
||||
"RenderEngine",
|
||||
"Readback, packed output, screenshot, and preview staging"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::RuntimeConfiguration,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::RuntimeConfiguration),
|
||||
"runtime configuration",
|
||||
"RuntimeConfigStore",
|
||||
"File-backed config, not a live parameter layer"
|
||||
},
|
||||
{
|
||||
RuntimeStateField::HealthTelemetry,
|
||||
ClassifyRuntimeStateField(RuntimeStateField::HealthTelemetry),
|
||||
"health telemetry",
|
||||
"HealthTelemetry",
|
||||
"Operational observations, not source state for render values"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum class RuntimeStateLayerKind
|
||||
{
|
||||
BasePersisted,
|
||||
CommittedLive,
|
||||
TransientAutomation,
|
||||
RenderLocal,
|
||||
HealthConfig
|
||||
};
|
||||
|
||||
enum class RuntimeStateField
|
||||
{
|
||||
PersistedLayerStack,
|
||||
PersistedParameterValues,
|
||||
StackPresets,
|
||||
CommittedSessionParameterValues,
|
||||
CommittedLayerBypass,
|
||||
RuntimeCompileReloadFlags,
|
||||
TransientOscOverlay,
|
||||
TransientAutomationCommitState,
|
||||
RenderLocalTemporalHistory,
|
||||
RenderLocalFeedbackState,
|
||||
RenderLocalInputFrames,
|
||||
RenderLocalOutputFrames,
|
||||
RuntimeConfiguration,
|
||||
HealthTelemetry
|
||||
};
|
||||
|
||||
struct RuntimeStateLayerDescriptor
|
||||
{
|
||||
RuntimeStateLayerKind kind = RuntimeStateLayerKind::BasePersisted;
|
||||
const char* name = "";
|
||||
const char* owner = "";
|
||||
const char* lifetime = "";
|
||||
const char* persistence = "";
|
||||
const char* renderRole = "";
|
||||
};
|
||||
|
||||
struct RuntimeStateFieldDescriptor
|
||||
{
|
||||
RuntimeStateField field = RuntimeStateField::PersistedLayerStack;
|
||||
RuntimeStateLayerKind layerKind = RuntimeStateLayerKind::BasePersisted;
|
||||
const char* name = "";
|
||||
const char* currentOwner = "";
|
||||
const char* notes = "";
|
||||
};
|
||||
|
||||
const char* RuntimeStateLayerKindName(RuntimeStateLayerKind kind);
|
||||
int RuntimeStateLayerCompositionPrecedence(RuntimeStateLayerKind kind);
|
||||
bool RuntimeStateLayerIsDurable(RuntimeStateLayerKind kind);
|
||||
bool RuntimeStateLayerParticipatesInParameterComposition(RuntimeStateLayerKind kind);
|
||||
bool RuntimeStateLayerIsRenderLocal(RuntimeStateLayerKind kind);
|
||||
|
||||
RuntimeStateLayerKind ClassifyRuntimeStateField(RuntimeStateField field);
|
||||
std::vector<RuntimeStateLayerDescriptor> GetRuntimeStateLayerInventory();
|
||||
std::vector<RuntimeStateFieldDescriptor> GetRuntimeStateFieldInventory();
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
enum class PersistenceTargetKind
|
||||
{
|
||||
RuntimeState,
|
||||
StackPreset,
|
||||
RuntimeConfig
|
||||
};
|
||||
|
||||
struct PersistenceRequest
|
||||
{
|
||||
PersistenceTargetKind targetKind = PersistenceTargetKind::RuntimeState;
|
||||
std::string reason;
|
||||
std::string debounceKey = "runtime-state";
|
||||
bool debounceAllowed = true;
|
||||
bool flushRequested = false;
|
||||
uint64_t sequence = 0;
|
||||
|
||||
static PersistenceRequest RuntimeStateRequest(const std::string& reason)
|
||||
{
|
||||
PersistenceRequest request;
|
||||
request.targetKind = PersistenceTargetKind::RuntimeState;
|
||||
request.reason = reason;
|
||||
request.debounceKey = "runtime-state";
|
||||
request.debounceAllowed = true;
|
||||
return request;
|
||||
}
|
||||
};
|
||||
|
||||
struct PersistenceSnapshot
|
||||
{
|
||||
PersistenceTargetKind targetKind = PersistenceTargetKind::RuntimeState;
|
||||
std::filesystem::path targetPath;
|
||||
std::string contents;
|
||||
std::string reason;
|
||||
std::string debounceKey;
|
||||
bool debounceAllowed = false;
|
||||
bool flushRequested = false;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
@@ -0,0 +1,271 @@
|
||||
#include "PersistenceWriter.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <utility>
|
||||
|
||||
PersistenceWriter::PersistenceWriter(std::chrono::milliseconds debounceDelay, SnapshotSink sink) :
|
||||
mDebounceDelay(debounceDelay),
|
||||
mSink(std::move(sink))
|
||||
{
|
||||
}
|
||||
|
||||
PersistenceWriter::~PersistenceWriter()
|
||||
{
|
||||
std::string error;
|
||||
StopAndFlush((std::chrono::milliseconds::max)(), error);
|
||||
}
|
||||
|
||||
void PersistenceWriter::SetResultCallback(ResultCallback callback)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mResultCallback = std::move(callback);
|
||||
}
|
||||
|
||||
bool PersistenceWriter::WriteSnapshot(const PersistenceSnapshot& snapshot, std::string& error)
|
||||
{
|
||||
if (!ValidateSnapshot(snapshot, error))
|
||||
return false;
|
||||
|
||||
const bool succeeded = WriteSnapshotThroughSink(snapshot, error);
|
||||
PublishWriteResult(snapshot, succeeded, error, false);
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
bool PersistenceWriter::EnqueueSnapshot(const PersistenceSnapshot& snapshot, std::string& error)
|
||||
{
|
||||
if (!ValidateSnapshot(snapshot, error))
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!mAcceptingRequests)
|
||||
{
|
||||
error = "Persistence writer is stopping.";
|
||||
return false;
|
||||
}
|
||||
|
||||
StartWorkerLocked();
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (snapshot.debounceAllowed)
|
||||
{
|
||||
const std::string debounceKey = snapshot.debounceKey.empty() ? snapshot.targetPath.string() : snapshot.debounceKey;
|
||||
PendingSnapshot& pending = mDebouncedSnapshots[debounceKey];
|
||||
if (!pending.snapshot.targetPath.empty())
|
||||
++mCoalescedCount;
|
||||
else
|
||||
++mEnqueuedCount;
|
||||
|
||||
pending.snapshot = snapshot;
|
||||
pending.readyAt = snapshot.flushRequested ? now : now + mDebounceDelay;
|
||||
}
|
||||
else
|
||||
{
|
||||
mImmediateSnapshots.push_back(snapshot);
|
||||
++mEnqueuedCount;
|
||||
}
|
||||
|
||||
mCondition.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PersistenceWriter::StopAndFlush(std::chrono::milliseconds timeout, std::string& error)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mAcceptingRequests = false;
|
||||
mStopping = true;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
for (auto& entry : mDebouncedSnapshots)
|
||||
entry.second.readyAt = now;
|
||||
}
|
||||
mCondition.notify_all();
|
||||
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mWorkerRunning)
|
||||
{
|
||||
if (timeout == (std::chrono::milliseconds::max)())
|
||||
{
|
||||
mCondition.wait(lock, [this]() { return !mWorkerRunning; });
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto deadline = std::chrono::steady_clock::now() + timeout;
|
||||
if (!mCondition.wait_until(lock, deadline, [this]() { return !mWorkerRunning; }))
|
||||
{
|
||||
error = "Timed out while flushing persistence writer.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
if (mWorker.joinable())
|
||||
mWorker.join();
|
||||
return true;
|
||||
}
|
||||
|
||||
PersistenceWriterMetrics PersistenceWriter::GetMetrics() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
PersistenceWriterMetrics metrics;
|
||||
metrics.pendingCount = PendingCountLocked();
|
||||
metrics.enqueuedCount = mEnqueuedCount;
|
||||
metrics.coalescedCount = mCoalescedCount;
|
||||
metrics.writtenCount = mWrittenCount;
|
||||
metrics.failedCount = mFailedCount;
|
||||
return metrics;
|
||||
}
|
||||
|
||||
bool PersistenceWriter::ValidateSnapshot(const PersistenceSnapshot& snapshot, std::string& error) const
|
||||
{
|
||||
if (snapshot.targetPath.empty())
|
||||
{
|
||||
error = "Persistence snapshot target path is empty.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PersistenceWriter::WriteSnapshotThroughSink(const PersistenceSnapshot& snapshot, std::string& error) const
|
||||
{
|
||||
if (mSink)
|
||||
return mSink(snapshot, error);
|
||||
|
||||
std::error_code fsError;
|
||||
std::filesystem::create_directories(snapshot.targetPath.parent_path(), fsError);
|
||||
|
||||
const std::filesystem::path temporaryPath = snapshot.targetPath.string() + ".tmp";
|
||||
std::ofstream output(temporaryPath, std::ios::binary | std::ios::trunc);
|
||||
if (!output)
|
||||
{
|
||||
error = "Could not write file: " + temporaryPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
output << snapshot.contents;
|
||||
output.close();
|
||||
if (!output.good())
|
||||
{
|
||||
error = "Could not finish writing file: " + temporaryPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MoveFileExA(temporaryPath.string().c_str(), snapshot.targetPath.string().c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
|
||||
{
|
||||
const DWORD lastError = GetLastError();
|
||||
std::filesystem::remove(temporaryPath, fsError);
|
||||
error = "Could not replace file: " + snapshot.targetPath.string() + " (Win32 error " + std::to_string(lastError) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PersistenceWriter::PublishWriteResult(const PersistenceSnapshot& snapshot, bool succeeded, const std::string& errorMessage, bool newerRequestPending)
|
||||
{
|
||||
ResultCallback callback;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
callback = mResultCallback;
|
||||
}
|
||||
|
||||
if (!callback)
|
||||
return;
|
||||
|
||||
PersistenceWriteResult result;
|
||||
result.targetKind = snapshot.targetKind;
|
||||
result.targetPath = snapshot.targetPath.string();
|
||||
result.reason = snapshot.reason;
|
||||
result.succeeded = succeeded;
|
||||
result.errorMessage = errorMessage;
|
||||
result.newerRequestPending = newerRequestPending;
|
||||
callback(result);
|
||||
}
|
||||
|
||||
void PersistenceWriter::StartWorkerLocked()
|
||||
{
|
||||
if (mWorkerRunning)
|
||||
return;
|
||||
|
||||
mWorkerRunning = true;
|
||||
mWorker = std::thread([this]() { WorkerMain(); });
|
||||
}
|
||||
|
||||
void PersistenceWriter::WorkerMain()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
PersistenceSnapshot snapshot;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
for (;;)
|
||||
{
|
||||
if (!mImmediateSnapshots.empty())
|
||||
{
|
||||
snapshot = std::move(mImmediateSnapshots.front());
|
||||
mImmediateSnapshots.pop_front();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!mDebouncedSnapshots.empty())
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
auto readyIt = mDebouncedSnapshots.end();
|
||||
auto nextReadyAt = (std::chrono::steady_clock::time_point::max)();
|
||||
for (auto it = mDebouncedSnapshots.begin(); it != mDebouncedSnapshots.end(); ++it)
|
||||
{
|
||||
if (it->second.readyAt <= now)
|
||||
{
|
||||
readyIt = it;
|
||||
break;
|
||||
}
|
||||
if (it->second.readyAt < nextReadyAt)
|
||||
nextReadyAt = it->second.readyAt;
|
||||
}
|
||||
|
||||
if (readyIt != mDebouncedSnapshots.end())
|
||||
{
|
||||
snapshot = std::move(readyIt->second.snapshot);
|
||||
mDebouncedSnapshots.erase(readyIt);
|
||||
break;
|
||||
}
|
||||
|
||||
mCondition.wait_until(lock, nextReadyAt);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mStopping)
|
||||
{
|
||||
mWorkerRunning = false;
|
||||
mCondition.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
mCondition.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
std::string error;
|
||||
const bool succeeded = WriteSnapshotThroughSink(snapshot, error);
|
||||
bool newerRequestPending = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (succeeded)
|
||||
++mWrittenCount;
|
||||
else
|
||||
++mFailedCount;
|
||||
newerRequestPending = PendingCountLocked() > 0;
|
||||
}
|
||||
PublishWriteResult(snapshot, succeeded, error, newerRequestPending);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t PersistenceWriter::PendingCountLocked() const
|
||||
{
|
||||
return mImmediateSnapshots.size() + mDebouncedSnapshots.size();
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include "PersistenceRequest.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
struct PersistenceWriterMetrics
|
||||
{
|
||||
std::size_t pendingCount = 0;
|
||||
uint64_t enqueuedCount = 0;
|
||||
uint64_t coalescedCount = 0;
|
||||
uint64_t writtenCount = 0;
|
||||
uint64_t failedCount = 0;
|
||||
};
|
||||
|
||||
struct PersistenceWriteResult
|
||||
{
|
||||
PersistenceTargetKind targetKind = PersistenceTargetKind::RuntimeState;
|
||||
std::string targetPath;
|
||||
std::string reason;
|
||||
bool succeeded = false;
|
||||
std::string errorMessage;
|
||||
bool newerRequestPending = false;
|
||||
};
|
||||
|
||||
class PersistenceWriter
|
||||
{
|
||||
public:
|
||||
using SnapshotSink = std::function<bool(const PersistenceSnapshot&, std::string&)>;
|
||||
using ResultCallback = std::function<void(const PersistenceWriteResult&)>;
|
||||
|
||||
explicit PersistenceWriter(
|
||||
std::chrono::milliseconds debounceDelay = std::chrono::milliseconds(50),
|
||||
SnapshotSink sink = SnapshotSink());
|
||||
~PersistenceWriter();
|
||||
|
||||
void SetResultCallback(ResultCallback callback);
|
||||
bool WriteSnapshot(const PersistenceSnapshot& snapshot, std::string& error);
|
||||
bool EnqueueSnapshot(const PersistenceSnapshot& snapshot, std::string& error);
|
||||
bool StopAndFlush(std::chrono::milliseconds timeout, std::string& error);
|
||||
PersistenceWriterMetrics GetMetrics() const;
|
||||
|
||||
private:
|
||||
struct PendingSnapshot
|
||||
{
|
||||
PersistenceSnapshot snapshot;
|
||||
std::chrono::steady_clock::time_point readyAt;
|
||||
};
|
||||
|
||||
bool ValidateSnapshot(const PersistenceSnapshot& snapshot, std::string& error) const;
|
||||
bool WriteSnapshotThroughSink(const PersistenceSnapshot& snapshot, std::string& error) const;
|
||||
void PublishWriteResult(const PersistenceSnapshot& snapshot, bool succeeded, const std::string& errorMessage, bool newerRequestPending);
|
||||
void StartWorkerLocked();
|
||||
void WorkerMain();
|
||||
std::size_t PendingCountLocked() const;
|
||||
|
||||
std::chrono::milliseconds mDebounceDelay;
|
||||
SnapshotSink mSink;
|
||||
ResultCallback mResultCallback;
|
||||
mutable std::mutex mMutex;
|
||||
std::condition_variable mCondition;
|
||||
std::thread mWorker;
|
||||
bool mWorkerRunning = false;
|
||||
bool mStopping = false;
|
||||
bool mAcceptingRequests = true;
|
||||
std::unordered_map<std::string, PendingSnapshot> mDebouncedSnapshots;
|
||||
std::deque<PersistenceSnapshot> mImmediateSnapshots;
|
||||
uint64_t mEnqueuedCount = 0;
|
||||
uint64_t mCoalescedCount = 0;
|
||||
uint64_t mWrittenCount = 0;
|
||||
uint64_t mFailedCount = 0;
|
||||
};
|
||||
@@ -0,0 +1,170 @@
|
||||
#include "RuntimeStateJson.h"
|
||||
|
||||
#include "RuntimeParameterUtils.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string ShaderParameterTypeToString(ShaderParameterType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ShaderParameterType::Float: return "float";
|
||||
case ShaderParameterType::Vec2: return "vec2";
|
||||
case ShaderParameterType::Color: return "color";
|
||||
case ShaderParameterType::Boolean: return "bool";
|
||||
case ShaderParameterType::Enum: return "enum";
|
||||
case ShaderParameterType::Text: return "text";
|
||||
case ShaderParameterType::Trigger: return "trigger";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
JsonValue RuntimeStateJson::SerializeLayerStack(const LayerStackStore& layerStack, const ShaderPackageCatalog& shaderCatalog)
|
||||
{
|
||||
std::map<std::string, ShaderPackage> packagesById;
|
||||
for (const std::string& packageId : shaderCatalog.PackageOrder())
|
||||
{
|
||||
ShaderPackage shaderPackage;
|
||||
if (shaderCatalog.CopyPackage(packageId, shaderPackage))
|
||||
packagesById[packageId] = shaderPackage;
|
||||
}
|
||||
return SerializeLayerStack(layerStack.Layers(), packagesById);
|
||||
}
|
||||
|
||||
JsonValue RuntimeStateJson::SerializeLayerStack(const std::vector<LayerStackStore::LayerPersistentState>& layerStates, const std::map<std::string, ShaderPackage>& packagesById)
|
||||
{
|
||||
JsonValue layersValue = JsonValue::MakeArray();
|
||||
for (const LayerStackStore::LayerPersistentState& layer : layerStates)
|
||||
{
|
||||
auto shaderIt = packagesById.find(layer.shaderId);
|
||||
if (shaderIt == packagesById.end())
|
||||
continue;
|
||||
const ShaderPackage& shaderPackage = shaderIt->second;
|
||||
|
||||
JsonValue layerValue = JsonValue::MakeObject();
|
||||
layerValue.set("id", JsonValue(layer.id));
|
||||
layerValue.set("shaderId", JsonValue(layer.shaderId));
|
||||
layerValue.set("shaderName", JsonValue(shaderPackage.displayName));
|
||||
layerValue.set("bypass", JsonValue(layer.bypass));
|
||||
if (shaderPackage.temporal.enabled)
|
||||
{
|
||||
JsonValue temporal = JsonValue::MakeObject();
|
||||
temporal.set("enabled", JsonValue(true));
|
||||
temporal.set("historySource", JsonValue(TemporalHistorySourceToString(shaderPackage.temporal.historySource)));
|
||||
temporal.set("requestedHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.requestedHistoryLength)));
|
||||
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.effectiveHistoryLength)));
|
||||
layerValue.set("temporal", temporal);
|
||||
}
|
||||
if (shaderPackage.feedback.enabled)
|
||||
{
|
||||
JsonValue feedback = JsonValue::MakeObject();
|
||||
feedback.set("enabled", JsonValue(true));
|
||||
feedback.set("writePass", JsonValue(shaderPackage.feedback.writePassId));
|
||||
layerValue.set("feedback", feedback);
|
||||
}
|
||||
|
||||
JsonValue parameters = JsonValue::MakeArray();
|
||||
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
|
||||
{
|
||||
JsonValue parameter = JsonValue::MakeObject();
|
||||
parameter.set("id", JsonValue(definition.id));
|
||||
parameter.set("label", JsonValue(definition.label));
|
||||
if (!definition.description.empty())
|
||||
parameter.set("description", JsonValue(definition.description));
|
||||
parameter.set("type", JsonValue(ShaderParameterTypeToString(definition.type)));
|
||||
parameter.set("defaultValue", SerializeParameterValue(definition, DefaultValueForDefinition(definition)));
|
||||
|
||||
if (!definition.minNumbers.empty())
|
||||
{
|
||||
JsonValue minValue = JsonValue::MakeArray();
|
||||
for (double number : definition.minNumbers)
|
||||
minValue.pushBack(JsonValue(number));
|
||||
parameter.set("min", minValue);
|
||||
}
|
||||
if (!definition.maxNumbers.empty())
|
||||
{
|
||||
JsonValue maxValue = JsonValue::MakeArray();
|
||||
for (double number : definition.maxNumbers)
|
||||
maxValue.pushBack(JsonValue(number));
|
||||
parameter.set("max", maxValue);
|
||||
}
|
||||
if (!definition.stepNumbers.empty())
|
||||
{
|
||||
JsonValue stepValue = JsonValue::MakeArray();
|
||||
for (double number : definition.stepNumbers)
|
||||
stepValue.pushBack(JsonValue(number));
|
||||
parameter.set("step", stepValue);
|
||||
}
|
||||
if (definition.type == ShaderParameterType::Enum)
|
||||
{
|
||||
JsonValue options = JsonValue::MakeArray();
|
||||
for (const ShaderParameterOption& option : definition.enumOptions)
|
||||
{
|
||||
JsonValue optionValue = JsonValue::MakeObject();
|
||||
optionValue.set("value", JsonValue(option.value));
|
||||
optionValue.set("label", JsonValue(option.label));
|
||||
options.pushBack(optionValue);
|
||||
}
|
||||
parameter.set("options", options);
|
||||
}
|
||||
if (definition.type == ShaderParameterType::Text)
|
||||
{
|
||||
parameter.set("maxLength", JsonValue(static_cast<double>(definition.maxLength)));
|
||||
if (!definition.fontId.empty())
|
||||
parameter.set("font", JsonValue(definition.fontId));
|
||||
}
|
||||
|
||||
ShaderParameterValue value = DefaultValueForDefinition(definition);
|
||||
auto valueIt = layer.parameterValues.find(definition.id);
|
||||
if (valueIt != layer.parameterValues.end())
|
||||
value = valueIt->second;
|
||||
parameter.set("value", SerializeParameterValue(definition, value));
|
||||
parameters.pushBack(parameter);
|
||||
}
|
||||
|
||||
layerValue.set("parameters", parameters);
|
||||
layersValue.pushBack(layerValue);
|
||||
}
|
||||
return layersValue;
|
||||
}
|
||||
|
||||
JsonValue RuntimeStateJson::SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
|
||||
{
|
||||
switch (definition.type)
|
||||
{
|
||||
case ShaderParameterType::Boolean:
|
||||
return JsonValue(value.booleanValue);
|
||||
case ShaderParameterType::Enum:
|
||||
return JsonValue(value.enumValue);
|
||||
case ShaderParameterType::Text:
|
||||
return JsonValue(value.textValue);
|
||||
case ShaderParameterType::Trigger:
|
||||
return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
|
||||
case ShaderParameterType::Float:
|
||||
return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
|
||||
case ShaderParameterType::Vec2:
|
||||
case ShaderParameterType::Color:
|
||||
{
|
||||
JsonValue array = JsonValue::MakeArray();
|
||||
for (double number : value.numberValues)
|
||||
array.pushBack(JsonValue(number));
|
||||
return array;
|
||||
}
|
||||
}
|
||||
return JsonValue();
|
||||
}
|
||||
|
||||
std::string RuntimeStateJson::TemporalHistorySourceToString(TemporalHistorySource source)
|
||||
{
|
||||
switch (source)
|
||||
{
|
||||
case TemporalHistorySource::Source:
|
||||
return "source";
|
||||
case TemporalHistorySource::PreLayerInput:
|
||||
return "preLayerInput";
|
||||
case TemporalHistorySource::None:
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "LayerStackStore.h"
|
||||
#include "RuntimeJson.h"
|
||||
#include "ShaderPackageCatalog.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class RuntimeStateJson
|
||||
{
|
||||
public:
|
||||
static JsonValue SerializeLayerStack(const LayerStackStore& layerStack, const ShaderPackageCatalog& shaderCatalog);
|
||||
static JsonValue SerializeLayerStack(const std::vector<LayerStackStore::LayerPersistentState>& layers, const std::map<std::string, ShaderPackage>& packagesById);
|
||||
static JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value);
|
||||
static std::string TemporalHistorySourceToString(TemporalHistorySource source);
|
||||
};
|
||||
@@ -0,0 +1,230 @@
|
||||
#include "RuntimeStatePresenter.h"
|
||||
|
||||
#include "RuntimeStateJson.h"
|
||||
#include "RuntimeStore.h"
|
||||
|
||||
std::string RuntimeStatePresenter::BuildRuntimeStateJson(const RuntimeStore& runtimeStore)
|
||||
{
|
||||
return SerializeJson(BuildRuntimeStateValue(runtimeStore), true);
|
||||
}
|
||||
|
||||
JsonValue RuntimeStatePresenter::BuildRuntimeStateValue(const RuntimeStore& runtimeStore)
|
||||
{
|
||||
const RuntimeStatePresentationReadModel model = runtimeStore.BuildRuntimeStatePresentationReadModel();
|
||||
const HealthTelemetry::Snapshot& telemetrySnapshot = model.telemetry;
|
||||
|
||||
JsonValue root = JsonValue::MakeObject();
|
||||
|
||||
JsonValue app = JsonValue::MakeObject();
|
||||
app.set("serverPort", JsonValue(static_cast<double>(model.serverPort)));
|
||||
app.set("oscPort", JsonValue(static_cast<double>(model.config.oscPort)));
|
||||
app.set("oscBindAddress", JsonValue(model.config.oscBindAddress));
|
||||
app.set("oscSmoothing", JsonValue(model.config.oscSmoothing));
|
||||
app.set("autoReload", JsonValue(model.autoReloadEnabled));
|
||||
app.set("maxTemporalHistoryFrames", JsonValue(static_cast<double>(model.config.maxTemporalHistoryFrames)));
|
||||
app.set("previewFps", JsonValue(static_cast<double>(model.config.previewFps)));
|
||||
app.set("enableExternalKeying", JsonValue(model.config.enableExternalKeying));
|
||||
app.set("inputVideoFormat", JsonValue(model.config.inputVideoFormat));
|
||||
app.set("inputFrameRate", JsonValue(model.config.inputFrameRate));
|
||||
app.set("outputVideoFormat", JsonValue(model.config.outputVideoFormat));
|
||||
app.set("outputFrameRate", JsonValue(model.config.outputFrameRate));
|
||||
root.set("app", app);
|
||||
|
||||
JsonValue runtime = JsonValue::MakeObject();
|
||||
runtime.set("layerCount", JsonValue(static_cast<double>(model.layerStack.LayerCount())));
|
||||
runtime.set("compileSucceeded", JsonValue(model.compileSucceeded));
|
||||
runtime.set("compileMessage", JsonValue(model.compileMessage));
|
||||
root.set("runtime", runtime);
|
||||
|
||||
JsonValue video = JsonValue::MakeObject();
|
||||
video.set("hasSignal", JsonValue(telemetrySnapshot.signal.hasSignal));
|
||||
video.set("width", JsonValue(static_cast<double>(telemetrySnapshot.signal.width)));
|
||||
video.set("height", JsonValue(static_cast<double>(telemetrySnapshot.signal.height)));
|
||||
video.set("modeName", JsonValue(telemetrySnapshot.signal.modeName));
|
||||
root.set("video", video);
|
||||
|
||||
JsonValue deckLink = JsonValue::MakeObject();
|
||||
deckLink.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName));
|
||||
deckLink.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying));
|
||||
deckLink.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying));
|
||||
deckLink.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable));
|
||||
deckLink.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested));
|
||||
deckLink.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive));
|
||||
deckLink.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage));
|
||||
deckLink.set("actualBufferedFramesAvailable", JsonValue(telemetrySnapshot.backendPlayout.actualDeckLinkBufferedFramesAvailable));
|
||||
deckLink.set("actualBufferedFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.actualDeckLinkBufferedFrames)));
|
||||
deckLink.set("targetBufferedFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.targetDeckLinkBufferedFrames)));
|
||||
deckLink.set("scheduleCallMs", JsonValue(telemetrySnapshot.backendPlayout.deckLinkScheduleCallMilliseconds));
|
||||
deckLink.set("scheduleFailures", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.deckLinkScheduleFailureCount)));
|
||||
root.set("decklink", deckLink);
|
||||
|
||||
JsonValue videoIO = JsonValue::MakeObject();
|
||||
videoIO.set("backend", JsonValue(telemetrySnapshot.videoIO.backendName));
|
||||
videoIO.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName));
|
||||
videoIO.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying));
|
||||
videoIO.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying));
|
||||
videoIO.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable));
|
||||
videoIO.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested));
|
||||
videoIO.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive));
|
||||
videoIO.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage));
|
||||
root.set("videoIO", videoIO);
|
||||
|
||||
JsonValue performance = JsonValue::MakeObject();
|
||||
performance.set("frameBudgetMs", JsonValue(telemetrySnapshot.performance.frameBudgetMilliseconds));
|
||||
performance.set("renderMs", JsonValue(telemetrySnapshot.performance.renderMilliseconds));
|
||||
performance.set("smoothedRenderMs", JsonValue(telemetrySnapshot.performance.smoothedRenderMilliseconds));
|
||||
performance.set("budgetUsedPercent", JsonValue(
|
||||
telemetrySnapshot.performance.frameBudgetMilliseconds > 0.0
|
||||
? (telemetrySnapshot.performance.smoothedRenderMilliseconds / telemetrySnapshot.performance.frameBudgetMilliseconds) * 100.0
|
||||
: 0.0));
|
||||
performance.set("completionIntervalMs", JsonValue(telemetrySnapshot.performance.completionIntervalMilliseconds));
|
||||
performance.set("smoothedCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.smoothedCompletionIntervalMilliseconds));
|
||||
performance.set("maxCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.maxCompletionIntervalMilliseconds));
|
||||
performance.set("lateFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.lateFrameCount)));
|
||||
performance.set("droppedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.droppedFrameCount)));
|
||||
performance.set("flushedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.flushedFrameCount)));
|
||||
root.set("performance", performance);
|
||||
|
||||
JsonValue readyQueue = JsonValue::MakeObject();
|
||||
readyQueue.set("depth", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueDepth)));
|
||||
readyQueue.set("capacity", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueCapacity)));
|
||||
readyQueue.set("minDepth", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.minReadyQueueDepth)));
|
||||
readyQueue.set("maxDepth", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.maxReadyQueueDepth)));
|
||||
readyQueue.set("zeroDepthCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueZeroDepthCount)));
|
||||
readyQueue.set("pushedCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueuePushedCount)));
|
||||
readyQueue.set("poppedCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueuePoppedCount)));
|
||||
readyQueue.set("droppedCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueDroppedCount)));
|
||||
readyQueue.set("underrunCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueUnderrunCount)));
|
||||
|
||||
JsonValue systemMemory = JsonValue::MakeObject();
|
||||
systemMemory.set("freeFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFramePoolFree)));
|
||||
systemMemory.set("readyFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFramePoolReady)));
|
||||
systemMemory.set("scheduledFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFramePoolScheduled)));
|
||||
systemMemory.set("underrunCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFrameUnderrunCount)));
|
||||
systemMemory.set("repeatCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFrameRepeatCount)));
|
||||
systemMemory.set("dropCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFrameDropCount)));
|
||||
systemMemory.set("ageAtScheduleMs", JsonValue(telemetrySnapshot.backendPlayout.systemFrameAgeAtScheduleMilliseconds));
|
||||
systemMemory.set("ageAtCompletionMs", JsonValue(telemetrySnapshot.backendPlayout.systemFrameAgeAtCompletionMilliseconds));
|
||||
|
||||
JsonValue outputRender = JsonValue::MakeObject();
|
||||
outputRender.set("renderMs", JsonValue(telemetrySnapshot.backendPlayout.outputRenderMilliseconds));
|
||||
outputRender.set("smoothedRenderMs", JsonValue(telemetrySnapshot.backendPlayout.smoothedOutputRenderMilliseconds));
|
||||
outputRender.set("maxRenderMs", JsonValue(telemetrySnapshot.backendPlayout.maxOutputRenderMilliseconds));
|
||||
outputRender.set("acquireFrameMs", JsonValue(telemetrySnapshot.backendPlayout.outputFrameAcquireMilliseconds));
|
||||
outputRender.set("renderRequestMs", JsonValue(telemetrySnapshot.backendPlayout.outputFrameRenderRequestMilliseconds));
|
||||
outputRender.set("endAccessMs", JsonValue(telemetrySnapshot.backendPlayout.outputFrameEndAccessMilliseconds));
|
||||
outputRender.set("queueWaitMs", JsonValue(telemetrySnapshot.backendPlayout.outputRenderQueueWaitMilliseconds));
|
||||
outputRender.set("drawMs", JsonValue(telemetrySnapshot.backendPlayout.outputRenderDrawMilliseconds));
|
||||
outputRender.set("fenceWaitMs", JsonValue(telemetrySnapshot.backendPlayout.outputReadbackFenceWaitMilliseconds));
|
||||
outputRender.set("mapMs", JsonValue(telemetrySnapshot.backendPlayout.outputReadbackMapMilliseconds));
|
||||
outputRender.set("readbackCopyMs", JsonValue(telemetrySnapshot.backendPlayout.outputReadbackCopyMilliseconds));
|
||||
outputRender.set("cachedCopyMs", JsonValue(telemetrySnapshot.backendPlayout.outputCachedCopyMilliseconds));
|
||||
outputRender.set("asyncQueueMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueMilliseconds));
|
||||
outputRender.set("asyncQueueBufferMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueBufferMilliseconds));
|
||||
outputRender.set("asyncQueueSetupMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueSetupMilliseconds));
|
||||
outputRender.set("asyncQueueReadPixelsMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueReadPixelsMilliseconds));
|
||||
outputRender.set("asyncQueueFenceMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueFenceMilliseconds));
|
||||
outputRender.set("syncReadMs", JsonValue(telemetrySnapshot.backendPlayout.outputSyncReadMilliseconds));
|
||||
outputRender.set("asyncReadbackMissCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.outputAsyncReadbackMissCount)));
|
||||
outputRender.set("cachedFallbackCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.outputCachedFallbackCount)));
|
||||
outputRender.set("syncFallbackCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.outputSyncFallbackCount)));
|
||||
|
||||
JsonValue recovery = JsonValue::MakeObject();
|
||||
recovery.set("completionResult", JsonValue(telemetrySnapshot.backendPlayout.completionResult));
|
||||
recovery.set("completedFrameIndex", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.completedFrameIndex)));
|
||||
recovery.set("scheduledFrameIndex", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.scheduledFrameIndex)));
|
||||
recovery.set("scheduledLeadFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.scheduledLeadFrames)));
|
||||
recovery.set("syntheticScheduledLeadFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.scheduledLeadFrames)));
|
||||
recovery.set("measuredLagFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.measuredLagFrames)));
|
||||
recovery.set("catchUpFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.catchUpFrames)));
|
||||
recovery.set("lateStreak", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.lateStreak)));
|
||||
recovery.set("dropStreak", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.dropStreak)));
|
||||
|
||||
JsonValue deckLinkPlayout = JsonValue::MakeObject();
|
||||
deckLinkPlayout.set("actualBufferedFramesAvailable", JsonValue(telemetrySnapshot.backendPlayout.actualDeckLinkBufferedFramesAvailable));
|
||||
deckLinkPlayout.set("actualBufferedFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.actualDeckLinkBufferedFrames)));
|
||||
deckLinkPlayout.set("targetBufferedFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.targetDeckLinkBufferedFrames)));
|
||||
deckLinkPlayout.set("scheduleCallMs", JsonValue(telemetrySnapshot.backendPlayout.deckLinkScheduleCallMilliseconds));
|
||||
deckLinkPlayout.set("scheduleFailures", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.deckLinkScheduleFailureCount)));
|
||||
|
||||
JsonValue scheduler = JsonValue::MakeObject();
|
||||
scheduler.set("syntheticLeadFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.scheduledLeadFrames)));
|
||||
|
||||
JsonValue backendPlayout = JsonValue::MakeObject();
|
||||
backendPlayout.set("lifecycleState", JsonValue(telemetrySnapshot.backendPlayout.lifecycleState));
|
||||
backendPlayout.set("degraded", JsonValue(telemetrySnapshot.backendPlayout.degraded));
|
||||
backendPlayout.set("statusMessage", JsonValue(telemetrySnapshot.backendPlayout.statusMessage));
|
||||
backendPlayout.set("lateFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.lateFrameCount)));
|
||||
backendPlayout.set("droppedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.droppedFrameCount)));
|
||||
backendPlayout.set("flushedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.flushedFrameCount)));
|
||||
backendPlayout.set("readyQueue", readyQueue);
|
||||
backendPlayout.set("systemMemory", systemMemory);
|
||||
backendPlayout.set("outputRender", outputRender);
|
||||
backendPlayout.set("decklink", deckLinkPlayout);
|
||||
backendPlayout.set("scheduler", scheduler);
|
||||
backendPlayout.set("recovery", recovery);
|
||||
root.set("backendPlayout", backendPlayout);
|
||||
|
||||
JsonValue eventQueue = JsonValue::MakeObject();
|
||||
eventQueue.set("name", JsonValue(telemetrySnapshot.runtimeEvents.queue.queueName));
|
||||
eventQueue.set("depth", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.queue.depth)));
|
||||
eventQueue.set("capacity", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.queue.capacity)));
|
||||
eventQueue.set("droppedCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.queue.droppedCount)));
|
||||
eventQueue.set("oldestEventAgeMs", JsonValue(telemetrySnapshot.runtimeEvents.queue.oldestEventAgeMilliseconds));
|
||||
|
||||
JsonValue eventDispatch = JsonValue::MakeObject();
|
||||
eventDispatch.set("dispatchCallCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.dispatchCallCount)));
|
||||
eventDispatch.set("dispatchedEventCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.dispatchedEventCount)));
|
||||
eventDispatch.set("handlerInvocationCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.handlerInvocationCount)));
|
||||
eventDispatch.set("handlerFailureCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.handlerFailureCount)));
|
||||
eventDispatch.set("lastDispatchDurationMs", JsonValue(telemetrySnapshot.runtimeEvents.dispatch.lastDispatchDurationMilliseconds));
|
||||
eventDispatch.set("maxDispatchDurationMs", JsonValue(telemetrySnapshot.runtimeEvents.dispatch.maxDispatchDurationMilliseconds));
|
||||
|
||||
JsonValue runtimeEvents = JsonValue::MakeObject();
|
||||
runtimeEvents.set("queue", eventQueue);
|
||||
runtimeEvents.set("dispatch", eventDispatch);
|
||||
root.set("runtimeEvents", runtimeEvents);
|
||||
|
||||
JsonValue shaderLibrary = JsonValue::MakeArray();
|
||||
for (const ShaderPackageStatus& status : model.packageStatuses)
|
||||
{
|
||||
JsonValue shader = JsonValue::MakeObject();
|
||||
shader.set("id", JsonValue(status.id));
|
||||
shader.set("name", JsonValue(status.displayName));
|
||||
shader.set("description", JsonValue(status.description));
|
||||
shader.set("category", JsonValue(status.category));
|
||||
shader.set("available", JsonValue(status.available));
|
||||
if (!status.available)
|
||||
shader.set("error", JsonValue(status.error));
|
||||
|
||||
auto shaderIt = model.shaderCatalog.packagesById.find(status.id);
|
||||
if (status.available && shaderIt != model.shaderCatalog.packagesById.end() && shaderIt->second.temporal.enabled)
|
||||
{
|
||||
const ShaderPackage& shaderPackage = shaderIt->second;
|
||||
JsonValue temporal = JsonValue::MakeObject();
|
||||
temporal.set("enabled", JsonValue(true));
|
||||
temporal.set("historySource", JsonValue(RuntimeStateJson::TemporalHistorySourceToString(shaderPackage.temporal.historySource)));
|
||||
temporal.set("requestedHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.requestedHistoryLength)));
|
||||
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.effectiveHistoryLength)));
|
||||
shader.set("temporal", temporal);
|
||||
}
|
||||
if (status.available && shaderIt != model.shaderCatalog.packagesById.end() && shaderIt->second.feedback.enabled)
|
||||
{
|
||||
const ShaderPackage& shaderPackage = shaderIt->second;
|
||||
JsonValue feedback = JsonValue::MakeObject();
|
||||
feedback.set("enabled", JsonValue(true));
|
||||
feedback.set("writePass", JsonValue(shaderPackage.feedback.writePassId));
|
||||
shader.set("feedback", feedback);
|
||||
}
|
||||
shaderLibrary.pushBack(shader);
|
||||
}
|
||||
root.set("shaders", shaderLibrary);
|
||||
|
||||
JsonValue stackPresets = JsonValue::MakeArray();
|
||||
for (const std::string& presetName : model.stackPresetNames)
|
||||
stackPresets.pushBack(JsonValue(presetName));
|
||||
root.set("stackPresets", stackPresets);
|
||||
|
||||
root.set("layers", RuntimeStateJson::SerializeLayerStack(model.layerStack.Layers(), model.shaderCatalog.packagesById));
|
||||
return root;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class RuntimeStore;
|
||||
|
||||
class RuntimeStatePresenter
|
||||
{
|
||||
public:
|
||||
static std::string BuildRuntimeStateJson(const RuntimeStore& runtimeStore);
|
||||
static JsonValue BuildRuntimeStateValue(const RuntimeStore& runtimeStore);
|
||||
};
|
||||
@@ -0,0 +1,193 @@
|
||||
#include "RenderSnapshotBuilder.h"
|
||||
|
||||
#include "RuntimeClock.h"
|
||||
#include "RuntimeParameterUtils.h"
|
||||
#include "RuntimeStore.h"
|
||||
#include "ShaderCompiler.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
RenderSnapshotBuilder::RenderSnapshotBuilder(RuntimeStore& runtimeStore) :
|
||||
mRuntimeStore(runtimeStore)
|
||||
{
|
||||
}
|
||||
|
||||
bool RenderSnapshotBuilder::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const
|
||||
{
|
||||
try
|
||||
{
|
||||
ShaderPackage shaderPackage;
|
||||
if (!mRuntimeStore.CopyShaderPackageForStoredLayer(layerId, shaderPackage, error))
|
||||
return false;
|
||||
|
||||
const ShaderCompilerInputs inputs = mRuntimeStore.GetShaderCompilerInputs();
|
||||
|
||||
ShaderCompiler compiler(
|
||||
inputs.repoRoot,
|
||||
inputs.wrapperPath,
|
||||
inputs.generatedGlslPath,
|
||||
inputs.patchedGlslPath,
|
||||
inputs.maxTemporalHistoryFrames);
|
||||
passSources.clear();
|
||||
passSources.reserve(shaderPackage.passes.size());
|
||||
for (const ShaderPassDefinition& pass : shaderPackage.passes)
|
||||
{
|
||||
ShaderPassBuildSource passSource;
|
||||
passSource.passId = pass.id;
|
||||
passSource.inputNames = pass.inputNames;
|
||||
passSource.outputName = pass.outputName;
|
||||
if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, passSource.fragmentShaderSource, error))
|
||||
return false;
|
||||
passSources.push_back(std::move(passSource));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
error = std::string("RenderSnapshotBuilder::BuildLayerPassFragmentShaderSources exception: ") + exception.what();
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
error = "RenderSnapshotBuilder::BuildLayerPassFragmentShaderSources threw a non-standard exception.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned RenderSnapshotBuilder::GetMaxTemporalHistoryFrames() const
|
||||
{
|
||||
return mRuntimeStore.GetConfiguredMaxTemporalHistoryFrames();
|
||||
}
|
||||
|
||||
RuntimeSnapshotVersions RenderSnapshotBuilder::GetVersions() const
|
||||
{
|
||||
RuntimeSnapshotVersions versions;
|
||||
versions.renderStateVersion = mRenderStateVersion.load(std::memory_order_relaxed);
|
||||
versions.parameterStateVersion = mParameterStateVersion.load(std::memory_order_relaxed);
|
||||
return versions;
|
||||
}
|
||||
|
||||
void RenderSnapshotBuilder::AdvanceFrame()
|
||||
{
|
||||
++mFrameCounter;
|
||||
}
|
||||
|
||||
void RenderSnapshotBuilder::BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
|
||||
{
|
||||
BuildLayerRenderStates(outputWidth, outputHeight, mRuntimeStore.BuildRenderSnapshotReadModel(), states);
|
||||
}
|
||||
|
||||
bool RenderSnapshotBuilder::TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
|
||||
{
|
||||
BuildLayerRenderStates(outputWidth, outputHeight, mRuntimeStore.BuildRenderSnapshotReadModel(), states);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderSnapshotBuilder::TryRefreshLayerParameters(std::vector<RuntimeRenderState>& states) const
|
||||
{
|
||||
RefreshLayerParameters(mRuntimeStore.CopyCommittedLiveLayerStates(), states);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderSnapshotBuilder::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
|
||||
{
|
||||
RefreshDynamicRenderStateFields(mRuntimeStore.GetRenderTimingSnapshot(), states);
|
||||
}
|
||||
|
||||
void RenderSnapshotBuilder::MarkRenderStateDirty()
|
||||
{
|
||||
mRenderStateVersion.fetch_add(1, std::memory_order_relaxed);
|
||||
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void RenderSnapshotBuilder::MarkParameterStateDirty()
|
||||
{
|
||||
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void RenderSnapshotBuilder::BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, const RenderSnapshotReadModel& readModel, std::vector<RuntimeRenderState>& states) const
|
||||
{
|
||||
states.clear();
|
||||
|
||||
for (const LayerStackStore::LayerPersistentState& layer : readModel.committedLiveState.layers)
|
||||
{
|
||||
auto shaderIt = readModel.committedLiveState.packagesById.find(layer.shaderId);
|
||||
if (shaderIt == readModel.committedLiveState.packagesById.end())
|
||||
continue;
|
||||
const ShaderPackage& shaderPackage = shaderIt->second;
|
||||
|
||||
RuntimeRenderState state;
|
||||
state.layerId = layer.id;
|
||||
state.shaderId = layer.shaderId;
|
||||
state.shaderName = shaderPackage.displayName;
|
||||
state.mixAmount = 1.0;
|
||||
state.bypass = layer.bypass ? 1.0 : 0.0;
|
||||
state.inputWidth = readModel.signalStatus.width;
|
||||
state.inputHeight = readModel.signalStatus.height;
|
||||
state.outputWidth = outputWidth;
|
||||
state.outputHeight = outputHeight;
|
||||
state.parameterDefinitions = shaderPackage.parameters;
|
||||
state.textureAssets = shaderPackage.textureAssets;
|
||||
state.fontAssets = shaderPackage.fontAssets;
|
||||
state.isTemporal = shaderPackage.temporal.enabled;
|
||||
state.temporalHistorySource = shaderPackage.temporal.historySource;
|
||||
state.requestedTemporalHistoryLength = shaderPackage.temporal.requestedHistoryLength;
|
||||
state.effectiveTemporalHistoryLength = shaderPackage.temporal.effectiveHistoryLength;
|
||||
state.feedback = shaderPackage.feedback;
|
||||
|
||||
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
|
||||
{
|
||||
ShaderParameterValue value = DefaultValueForDefinition(definition);
|
||||
auto valueIt = layer.parameterValues.find(definition.id);
|
||||
if (valueIt != layer.parameterValues.end())
|
||||
value = valueIt->second;
|
||||
state.parameterValues[definition.id] = value;
|
||||
}
|
||||
|
||||
states.push_back(state);
|
||||
}
|
||||
|
||||
RefreshDynamicRenderStateFields(readModel.timing, states);
|
||||
}
|
||||
|
||||
void RenderSnapshotBuilder::RefreshLayerParameters(const std::vector<LayerStackStore::LayerPersistentState>& layers, std::vector<RuntimeRenderState>& states) const
|
||||
{
|
||||
for (RuntimeRenderState& state : states)
|
||||
{
|
||||
const auto layerIt = std::find_if(layers.begin(), layers.end(),
|
||||
[&state](const LayerStackStore::LayerPersistentState& layer) { return layer.id == state.layerId; });
|
||||
if (layerIt == layers.end())
|
||||
continue;
|
||||
|
||||
state.bypass = layerIt->bypass ? 1.0 : 0.0;
|
||||
state.parameterValues.clear();
|
||||
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
||||
{
|
||||
ShaderParameterValue value = DefaultValueForDefinition(definition);
|
||||
auto valueIt = layerIt->parameterValues.find(definition.id);
|
||||
if (valueIt != layerIt->parameterValues.end())
|
||||
value = valueIt->second;
|
||||
state.parameterValues[definition.id] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderSnapshotBuilder::RefreshDynamicRenderStateFields(const RenderTimingSnapshot& timing, std::vector<RuntimeRenderState>& states) const
|
||||
{
|
||||
const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot();
|
||||
const double timeSeconds = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - timing.startTime).count();
|
||||
const double frameCount = static_cast<double>(mFrameCounter.load(std::memory_order_relaxed));
|
||||
|
||||
for (RuntimeRenderState& state : states)
|
||||
{
|
||||
state.timeSeconds = timeSeconds;
|
||||
state.utcTimeSeconds = clock.utcTimeSeconds;
|
||||
state.utcOffsetSeconds = clock.utcOffsetSeconds;
|
||||
state.startupRandom = timing.startupRandom;
|
||||
state.frameCount = frameCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeStoreReadModels.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class RuntimeStore;
|
||||
|
||||
struct RuntimeSnapshotVersions
|
||||
{
|
||||
uint64_t renderStateVersion = 0;
|
||||
uint64_t parameterStateVersion = 0;
|
||||
};
|
||||
|
||||
class RenderSnapshotBuilder
|
||||
{
|
||||
public:
|
||||
explicit RenderSnapshotBuilder(RuntimeStore& runtimeStore);
|
||||
|
||||
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
|
||||
unsigned GetMaxTemporalHistoryFrames() const;
|
||||
RuntimeSnapshotVersions GetVersions() const;
|
||||
void AdvanceFrame();
|
||||
void BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
|
||||
bool TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
|
||||
bool TryRefreshLayerParameters(std::vector<RuntimeRenderState>& states) const;
|
||||
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
|
||||
void MarkRenderStateDirty();
|
||||
void MarkParameterStateDirty();
|
||||
|
||||
private:
|
||||
void BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, const RenderSnapshotReadModel& readModel, std::vector<RuntimeRenderState>& states) const;
|
||||
void RefreshLayerParameters(const std::vector<LayerStackStore::LayerPersistentState>& layers, std::vector<RuntimeRenderState>& states) const;
|
||||
void RefreshDynamicRenderStateFields(const RenderTimingSnapshot& timing, std::vector<RuntimeRenderState>& states) const;
|
||||
|
||||
RuntimeStore& mRuntimeStore;
|
||||
std::atomic<uint64_t> mFrameCounter{ 0 };
|
||||
std::atomic<uint64_t> mRenderStateVersion{ 0 };
|
||||
std::atomic<uint64_t> mParameterStateVersion{ 0 };
|
||||
};
|
||||
@@ -0,0 +1,196 @@
|
||||
#include "RuntimeSnapshotProvider.h"
|
||||
|
||||
#include "RuntimeEventDispatcher.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
RuntimeSnapshotProvider::RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder, RuntimeEventDispatcher& runtimeEventDispatcher) :
|
||||
mRenderSnapshotBuilder(renderSnapshotBuilder),
|
||||
mRuntimeEventDispatcher(runtimeEventDispatcher)
|
||||
{
|
||||
}
|
||||
|
||||
bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const
|
||||
{
|
||||
try
|
||||
{
|
||||
return mRenderSnapshotBuilder.BuildLayerPassFragmentShaderSources(layerId, passSources, error);
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
error = std::string("RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources exception: ") + exception.what();
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
error = "RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources threw a non-standard exception.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
|
||||
{
|
||||
return mRenderSnapshotBuilder.GetMaxTemporalHistoryFrames();
|
||||
}
|
||||
|
||||
RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
|
||||
{
|
||||
return mRenderSnapshotBuilder.GetVersions();
|
||||
}
|
||||
|
||||
void RuntimeSnapshotProvider::AdvanceFrame()
|
||||
{
|
||||
mRenderSnapshotBuilder.AdvanceFrame();
|
||||
}
|
||||
|
||||
RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const
|
||||
{
|
||||
PublishRenderSnapshotPublishRequested(outputWidth, outputHeight, "publish-render-state-snapshot");
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const RuntimeSnapshotVersions versionsBefore = GetVersions();
|
||||
RuntimeRenderStateSnapshot publishedSnapshot;
|
||||
if (TryGetPublishedRenderStateSnapshot(outputWidth, outputHeight, versionsBefore, publishedSnapshot))
|
||||
{
|
||||
PublishRenderSnapshotPublished(publishedSnapshot);
|
||||
return publishedSnapshot;
|
||||
}
|
||||
|
||||
RuntimeRenderStateSnapshot snapshot;
|
||||
snapshot.outputWidth = outputWidth;
|
||||
snapshot.outputHeight = outputHeight;
|
||||
mRenderSnapshotBuilder.BuildLayerRenderStates(outputWidth, outputHeight, snapshot.states);
|
||||
|
||||
const RuntimeSnapshotVersions versionsAfter = GetVersions();
|
||||
if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion &&
|
||||
versionsBefore.parameterStateVersion == versionsAfter.parameterStateVersion)
|
||||
{
|
||||
snapshot.versions = versionsAfter;
|
||||
StorePublishedRenderStateSnapshot(snapshot);
|
||||
PublishRenderSnapshotPublished(snapshot);
|
||||
return snapshot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeSnapshotProvider::TryPublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const
|
||||
{
|
||||
PublishRenderSnapshotPublishRequested(outputWidth, outputHeight, "try-publish-render-state-snapshot");
|
||||
|
||||
const RuntimeSnapshotVersions versionsBefore = GetVersions();
|
||||
if (TryGetPublishedRenderStateSnapshot(outputWidth, outputHeight, versionsBefore, snapshot))
|
||||
{
|
||||
PublishRenderSnapshotPublished(snapshot);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<RuntimeRenderState> states;
|
||||
if (!mRenderSnapshotBuilder.TryBuildLayerRenderStates(outputWidth, outputHeight, states))
|
||||
return false;
|
||||
|
||||
const RuntimeSnapshotVersions versionsAfter = GetVersions();
|
||||
if (versionsBefore.renderStateVersion != versionsAfter.renderStateVersion ||
|
||||
versionsBefore.parameterStateVersion != versionsAfter.parameterStateVersion)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshot.outputWidth = outputWidth;
|
||||
snapshot.outputHeight = outputHeight;
|
||||
snapshot.versions = versionsAfter;
|
||||
snapshot.states = std::move(states);
|
||||
StorePublishedRenderStateSnapshot(snapshot);
|
||||
PublishRenderSnapshotPublished(snapshot);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const
|
||||
{
|
||||
const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion;
|
||||
if (!mRenderSnapshotBuilder.TryRefreshLayerParameters(snapshot.states))
|
||||
return false;
|
||||
|
||||
const RuntimeSnapshotVersions versions = GetVersions();
|
||||
if (versions.renderStateVersion != expectedRenderStateVersion)
|
||||
return false;
|
||||
|
||||
snapshot.versions = versions;
|
||||
StorePublishedRenderStateSnapshot(snapshot);
|
||||
PublishRenderSnapshotPublished(snapshot);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
|
||||
{
|
||||
mRenderSnapshotBuilder.RefreshDynamicRenderStateFields(states);
|
||||
}
|
||||
|
||||
bool RuntimeSnapshotProvider::TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight,
|
||||
const RuntimeSnapshotVersions& versions, RuntimeRenderStateSnapshot& snapshot) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPublishedSnapshotMutex);
|
||||
if (!mHasPublishedRenderStateSnapshot ||
|
||||
!SnapshotMatches(mPublishedRenderStateSnapshot, outputWidth, outputHeight, versions))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshot = mPublishedRenderStateSnapshot;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeSnapshotProvider::StorePublishedRenderStateSnapshot(const RuntimeRenderStateSnapshot& snapshot) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mPublishedSnapshotMutex);
|
||||
mPublishedRenderStateSnapshot = snapshot;
|
||||
mHasPublishedRenderStateSnapshot = true;
|
||||
}
|
||||
|
||||
bool RuntimeSnapshotProvider::SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight,
|
||||
const RuntimeSnapshotVersions& versions)
|
||||
{
|
||||
return snapshot.outputWidth == outputWidth &&
|
||||
snapshot.outputHeight == outputHeight &&
|
||||
snapshot.versions.renderStateVersion == versions.renderStateVersion &&
|
||||
snapshot.versions.parameterStateVersion == versions.parameterStateVersion;
|
||||
}
|
||||
|
||||
void RuntimeSnapshotProvider::PublishRenderSnapshotPublishRequested(unsigned outputWidth, unsigned outputHeight, const std::string& reason) const
|
||||
{
|
||||
try
|
||||
{
|
||||
RenderSnapshotPublishRequestedEvent event;
|
||||
event.outputWidth = outputWidth;
|
||||
event.outputHeight = outputHeight;
|
||||
event.reason = reason;
|
||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeSnapshotProvider"))
|
||||
OutputDebugStringA("RenderSnapshotPublishRequested event publish failed.\n");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OutputDebugStringA("RenderSnapshotPublishRequested event publish threw.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeSnapshotProvider::PublishRenderSnapshotPublished(const RuntimeRenderStateSnapshot& snapshot) const
|
||||
{
|
||||
try
|
||||
{
|
||||
RenderSnapshotPublishedEvent event;
|
||||
event.snapshotVersion = snapshot.versions.renderStateVersion;
|
||||
event.structureVersion = snapshot.versions.renderStateVersion;
|
||||
event.parameterVersion = snapshot.versions.parameterStateVersion;
|
||||
event.outputWidth = snapshot.outputWidth;
|
||||
event.outputHeight = snapshot.outputHeight;
|
||||
event.layerCount = snapshot.states.size();
|
||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeSnapshotProvider"))
|
||||
OutputDebugStringA("RenderSnapshotPublished event publish failed.\n");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OutputDebugStringA("RenderSnapshotPublished event publish threw.\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "RenderSnapshotBuilder.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class RuntimeEventDispatcher;
|
||||
|
||||
struct RuntimeRenderStateSnapshot
|
||||
{
|
||||
RuntimeSnapshotVersions versions;
|
||||
unsigned outputWidth = 0;
|
||||
unsigned outputHeight = 0;
|
||||
std::vector<RuntimeRenderState> states;
|
||||
};
|
||||
|
||||
class RuntimeSnapshotProvider
|
||||
{
|
||||
public:
|
||||
RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder, RuntimeEventDispatcher& runtimeEventDispatcher);
|
||||
|
||||
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
|
||||
unsigned GetMaxTemporalHistoryFrames() const;
|
||||
RuntimeSnapshotVersions GetVersions() const;
|
||||
void AdvanceFrame();
|
||||
RuntimeRenderStateSnapshot PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const;
|
||||
bool TryPublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const;
|
||||
bool TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const;
|
||||
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
|
||||
|
||||
private:
|
||||
bool TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight,
|
||||
const RuntimeSnapshotVersions& versions, RuntimeRenderStateSnapshot& snapshot) const;
|
||||
void StorePublishedRenderStateSnapshot(const RuntimeRenderStateSnapshot& snapshot) const;
|
||||
static bool SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight,
|
||||
const RuntimeSnapshotVersions& versions);
|
||||
void PublishRenderSnapshotPublishRequested(unsigned outputWidth, unsigned outputHeight, const std::string& reason) const;
|
||||
void PublishRenderSnapshotPublished(const RuntimeRenderStateSnapshot& snapshot) const;
|
||||
|
||||
RenderSnapshotBuilder& mRenderSnapshotBuilder;
|
||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
||||
mutable std::mutex mPublishedSnapshotMutex;
|
||||
mutable bool mHasPublishedRenderStateSnapshot = false;
|
||||
mutable RuntimeRenderStateSnapshot mPublishedRenderStateSnapshot;
|
||||
};
|
||||
@@ -0,0 +1,738 @@
|
||||
#include "LayerStackStore.h"
|
||||
|
||||
#include "RuntimeParameterUtils.h"
|
||||
#include "RuntimeStateJson.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string TrimCopy(const std::string& text)
|
||||
{
|
||||
std::size_t start = 0;
|
||||
while (start < text.size() && std::isspace(static_cast<unsigned char>(text[start])))
|
||||
++start;
|
||||
|
||||
std::size_t end = text.size();
|
||||
while (end > start && std::isspace(static_cast<unsigned char>(text[end - 1])))
|
||||
--end;
|
||||
|
||||
return text.substr(start, end - start);
|
||||
}
|
||||
|
||||
std::string SimplifyControlKey(const std::string& text)
|
||||
{
|
||||
std::string simplified;
|
||||
for (unsigned char ch : text)
|
||||
{
|
||||
if (std::isalnum(ch))
|
||||
simplified.push_back(static_cast<char>(std::tolower(ch)));
|
||||
}
|
||||
return simplified;
|
||||
}
|
||||
|
||||
bool MatchesControlKey(const std::string& candidate, const std::string& key)
|
||||
{
|
||||
return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key);
|
||||
}
|
||||
|
||||
bool TryParseLayerIdNumber(const std::string& layerId, uint64_t& number)
|
||||
{
|
||||
const std::string prefix = "layer-";
|
||||
if (layerId.rfind(prefix, 0) != 0 || layerId.size() == prefix.size())
|
||||
return false;
|
||||
|
||||
uint64_t parsed = 0;
|
||||
for (std::size_t index = prefix.size(); index < layerId.size(); ++index)
|
||||
{
|
||||
const unsigned char ch = static_cast<unsigned char>(layerId[index]);
|
||||
if (!std::isdigit(ch))
|
||||
return false;
|
||||
parsed = parsed * 10 + static_cast<uint64_t>(ch - '0');
|
||||
}
|
||||
|
||||
number = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool LayerStackStore::LoadPersistentStateValue(const JsonValue& root)
|
||||
{
|
||||
if (const JsonValue* layersValue = root.find("layers"))
|
||||
{
|
||||
for (const JsonValue& layerValue : layersValue->asArray())
|
||||
{
|
||||
if (!layerValue.isObject())
|
||||
continue;
|
||||
LayerPersistentState layer;
|
||||
if (const JsonValue* idValue = layerValue.find("id"))
|
||||
layer.id = idValue->asString();
|
||||
if (const JsonValue* shaderIdValue = layerValue.find("shaderId"))
|
||||
layer.shaderId = shaderIdValue->asString();
|
||||
if (const JsonValue* bypassValue = layerValue.find("bypass"))
|
||||
layer.bypass = bypassValue->asBoolean(false);
|
||||
else if (const JsonValue* enabledValue = layerValue.find("enabled"))
|
||||
layer.bypass = !enabledValue->asBoolean(true);
|
||||
|
||||
if (const JsonValue* parameterValues = layerValue.find("parameterValues"))
|
||||
{
|
||||
for (const auto& parameterItem : parameterValues->asObject())
|
||||
{
|
||||
ShaderParameterValue value;
|
||||
const JsonValue& jsonValue = parameterItem.second;
|
||||
if (jsonValue.isBoolean())
|
||||
value.booleanValue = jsonValue.asBoolean();
|
||||
else if (jsonValue.isString())
|
||||
value.enumValue = jsonValue.asString();
|
||||
else if (jsonValue.isNumber())
|
||||
value.numberValues.push_back(jsonValue.asNumber());
|
||||
else if (jsonValue.isArray())
|
||||
value.numberValues = JsonArrayToNumbers(jsonValue);
|
||||
layer.parameterValues[parameterItem.first] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!layer.shaderId.empty())
|
||||
mLayers.push_back(layer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string activeShaderId;
|
||||
if (const JsonValue* activeShaderValue = root.find("activeShaderId"))
|
||||
activeShaderId = activeShaderValue->asString();
|
||||
|
||||
if (!activeShaderId.empty())
|
||||
{
|
||||
LayerPersistentState layer;
|
||||
layer.id = GenerateLayerId(mLayers, mNextLayerId);
|
||||
layer.shaderId = activeShaderId;
|
||||
layer.bypass = false;
|
||||
|
||||
if (const JsonValue* valuesByShader = root.find("parameterValuesByShader"))
|
||||
{
|
||||
const JsonValue* shaderValues = valuesByShader->find(activeShaderId);
|
||||
if (shaderValues)
|
||||
{
|
||||
for (const auto& parameterItem : shaderValues->asObject())
|
||||
{
|
||||
ShaderParameterValue value;
|
||||
const JsonValue& jsonValue = parameterItem.second;
|
||||
if (jsonValue.isBoolean())
|
||||
value.booleanValue = jsonValue.asBoolean();
|
||||
else if (jsonValue.isString())
|
||||
value.enumValue = jsonValue.asString();
|
||||
else if (jsonValue.isNumber())
|
||||
value.numberValues.push_back(jsonValue.asNumber());
|
||||
else if (jsonValue.isArray())
|
||||
value.numberValues = JsonArrayToNumbers(jsonValue);
|
||||
layer.parameterValues[parameterItem.first] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mLayers.push_back(layer);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JsonValue LayerStackStore::BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const
|
||||
{
|
||||
JsonValue root = JsonValue::MakeObject();
|
||||
JsonValue layers = JsonValue::MakeArray();
|
||||
for (const LayerPersistentState& layer : mLayers)
|
||||
{
|
||||
JsonValue layerValue = JsonValue::MakeObject();
|
||||
layerValue.set("id", JsonValue(layer.id));
|
||||
layerValue.set("shaderId", JsonValue(layer.shaderId));
|
||||
layerValue.set("bypass", JsonValue(layer.bypass));
|
||||
|
||||
JsonValue parameterValues = JsonValue::MakeObject();
|
||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
|
||||
for (const auto& parameterItem : layer.parameterValues)
|
||||
{
|
||||
const ShaderParameterDefinition* definition = nullptr;
|
||||
if (shaderPackage)
|
||||
{
|
||||
for (const ShaderParameterDefinition& candidate : shaderPackage->parameters)
|
||||
{
|
||||
if (candidate.id == parameterItem.first)
|
||||
{
|
||||
definition = &candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (definition)
|
||||
parameterValues.set(parameterItem.first, RuntimeStateJson::SerializeParameterValue(*definition, parameterItem.second));
|
||||
}
|
||||
|
||||
layerValue.set("parameterValues", parameterValues);
|
||||
layers.pushBack(layerValue);
|
||||
}
|
||||
root.set("layers", layers);
|
||||
return root;
|
||||
}
|
||||
|
||||
void LayerStackStore::NormalizeLayerIds()
|
||||
{
|
||||
std::set<std::string> usedIds;
|
||||
uint64_t maxLayerNumber = mNextLayerId;
|
||||
|
||||
for (LayerPersistentState& layer : mLayers)
|
||||
{
|
||||
uint64_t layerNumber = 0;
|
||||
const bool hasReusableId = !layer.id.empty() &&
|
||||
usedIds.find(layer.id) == usedIds.end() &&
|
||||
TryParseLayerIdNumber(layer.id, layerNumber);
|
||||
|
||||
if (hasReusableId)
|
||||
{
|
||||
usedIds.insert(layer.id);
|
||||
maxLayerNumber = (std::max)(maxLayerNumber, layerNumber);
|
||||
continue;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
++maxLayerNumber;
|
||||
layer.id = "layer-" + std::to_string(maxLayerNumber);
|
||||
}
|
||||
while (usedIds.find(layer.id) != usedIds.end());
|
||||
|
||||
usedIds.insert(layer.id);
|
||||
}
|
||||
|
||||
mNextLayerId = maxLayerNumber;
|
||||
}
|
||||
|
||||
void LayerStackStore::EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog)
|
||||
{
|
||||
for (LayerPersistentState& layer : mLayers)
|
||||
{
|
||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
|
||||
if (shaderPackage)
|
||||
EnsureLayerDefaults(layer, *shaderPackage);
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackStore::EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog)
|
||||
{
|
||||
if (!mLayers.empty() || shaderCatalog.PackageOrder().empty())
|
||||
return;
|
||||
|
||||
LayerPersistentState layer;
|
||||
layer.id = GenerateLayerId(mLayers, mNextLayerId);
|
||||
layer.shaderId = shaderCatalog.PackageOrder().front();
|
||||
layer.bypass = false;
|
||||
if (const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId))
|
||||
EnsureLayerDefaults(layer, *shaderPackage);
|
||||
mLayers.push_back(layer);
|
||||
}
|
||||
|
||||
void LayerStackStore::RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog)
|
||||
{
|
||||
for (auto it = mLayers.begin(); it != mLayers.end();)
|
||||
{
|
||||
if (!shaderCatalog.HasPackage(it->shaderId))
|
||||
it = mLayers.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
bool LayerStackStore::CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error)
|
||||
{
|
||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
|
||||
if (!shaderPackage)
|
||||
{
|
||||
error = "Unknown shader id: " + shaderId;
|
||||
return false;
|
||||
}
|
||||
|
||||
LayerPersistentState layer;
|
||||
layer.id = GenerateLayerId(mLayers, mNextLayerId);
|
||||
layer.shaderId = shaderId;
|
||||
layer.bypass = false;
|
||||
EnsureLayerDefaults(layer, *shaderPackage);
|
||||
mLayers.push_back(layer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerStackStore::DeleteLayer(const std::string& layerId, std::string& error)
|
||||
{
|
||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
||||
if (it == mLayers.end())
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
mLayers.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerStackStore::MoveLayer(const std::string& layerId, int direction, std::string& error)
|
||||
{
|
||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
||||
if (it == mLayers.end())
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::ptrdiff_t index = std::distance(mLayers.begin(), it);
|
||||
const std::ptrdiff_t newIndex = index + direction;
|
||||
if (newIndex < 0 || newIndex >= static_cast<std::ptrdiff_t>(mLayers.size()))
|
||||
return true;
|
||||
|
||||
std::swap(mLayers[index], mLayers[newIndex]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerStackStore::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
||||
{
|
||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
||||
if (it == mLayers.end())
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mLayers.empty())
|
||||
return true;
|
||||
|
||||
if (targetIndex >= mLayers.size())
|
||||
targetIndex = mLayers.size() - 1;
|
||||
|
||||
const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(mLayers.begin(), it));
|
||||
if (sourceIndex == targetIndex)
|
||||
return true;
|
||||
|
||||
LayerPersistentState movedLayer = *it;
|
||||
mLayers.erase(mLayers.begin() + static_cast<std::ptrdiff_t>(sourceIndex));
|
||||
mLayers.insert(mLayers.begin() + static_cast<std::ptrdiff_t>(targetIndex), movedLayer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerStackStore::SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
|
||||
{
|
||||
LayerPersistentState* layer = FindLayerById(layerId);
|
||||
if (!layer)
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
layer->bypass = bypassed;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerStackStore::SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error)
|
||||
{
|
||||
LayerPersistentState* layer = FindLayerById(layerId);
|
||||
if (!layer)
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
|
||||
if (!shaderPackage)
|
||||
{
|
||||
error = "Unknown shader id: " + shaderId;
|
||||
return false;
|
||||
}
|
||||
|
||||
layer->shaderId = shaderId;
|
||||
layer->parameterValues.clear();
|
||||
EnsureLayerDefaults(*layer, *shaderPackage);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerStackStore::SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error)
|
||||
{
|
||||
LayerPersistentState* layer = FindLayerById(layerId);
|
||||
if (!layer)
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
layer->parameterValues[parameterId] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerStackStore::ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error)
|
||||
{
|
||||
LayerPersistentState* layer = FindLayerById(layerId);
|
||||
if (!layer)
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer->shaderId);
|
||||
if (!shaderPackage)
|
||||
{
|
||||
error = "Unknown shader id: " + layer->shaderId;
|
||||
return false;
|
||||
}
|
||||
|
||||
layer->parameterValues.clear();
|
||||
EnsureLayerDefaults(*layer, *shaderPackage);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerStackStore::HasLayer(const std::string& layerId) const
|
||||
{
|
||||
return FindLayerById(layerId) != nullptr;
|
||||
}
|
||||
|
||||
bool LayerStackStore::TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const
|
||||
{
|
||||
const LayerPersistentState* layer = FindLayerById(layerId);
|
||||
if (!layer)
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer->shaderId);
|
||||
if (!shaderPackage)
|
||||
{
|
||||
error = "Unknown shader id: " + layer->shaderId;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto parameterIt = std::find_if(shaderPackage->parameters.begin(), shaderPackage->parameters.end(),
|
||||
[¶meterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
|
||||
if (parameterIt == shaderPackage->parameters.end())
|
||||
{
|
||||
error = "Unknown parameter id: " + parameterId;
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshot = StoredParameterSnapshot();
|
||||
snapshot.layerId = layer->id;
|
||||
snapshot.definition = *parameterIt;
|
||||
auto valueIt = layer->parameterValues.find(parameterIt->id);
|
||||
if (valueIt != layer->parameterValues.end())
|
||||
{
|
||||
snapshot.currentValue = valueIt->second;
|
||||
snapshot.hasCurrentValue = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerStackStore::TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const
|
||||
{
|
||||
const LayerPersistentState* matchedLayer = nullptr;
|
||||
const ShaderPackage* matchedPackage = nullptr;
|
||||
|
||||
for (const LayerPersistentState& layer : mLayers)
|
||||
{
|
||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
|
||||
if (!shaderPackage)
|
||||
continue;
|
||||
|
||||
if (MatchesControlKey(layer.id, layerKey) || MatchesControlKey(shaderPackage->id, layerKey) ||
|
||||
MatchesControlKey(shaderPackage->displayName, layerKey))
|
||||
{
|
||||
matchedLayer = &layer;
|
||||
matchedPackage = shaderPackage;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchedLayer || !matchedPackage)
|
||||
{
|
||||
error = "Unknown OSC layer key: " + layerKey;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto parameterIt = std::find_if(matchedPackage->parameters.begin(), matchedPackage->parameters.end(),
|
||||
[¶meterKey](const ShaderParameterDefinition& definition)
|
||||
{
|
||||
return MatchesControlKey(definition.id, parameterKey) || MatchesControlKey(definition.label, parameterKey);
|
||||
});
|
||||
if (parameterIt == matchedPackage->parameters.end())
|
||||
{
|
||||
error = "Unknown OSC parameter key: " + parameterKey;
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshot = StoredParameterSnapshot();
|
||||
snapshot.layerId = matchedLayer->id;
|
||||
snapshot.definition = *parameterIt;
|
||||
auto valueIt = matchedLayer->parameterValues.find(parameterIt->id);
|
||||
if (valueIt != matchedLayer->parameterValues.end())
|
||||
{
|
||||
snapshot.currentValue = valueIt->second;
|
||||
snapshot.hasCurrentValue = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerStackStore::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
|
||||
{
|
||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
||||
if (it == mLayers.end())
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::ptrdiff_t index = std::distance(mLayers.begin(), it);
|
||||
const std::ptrdiff_t newIndex = index + direction;
|
||||
shouldMove = newIndex >= 0 && newIndex < static_cast<std::ptrdiff_t>(mLayers.size()) && newIndex != index;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerStackStore::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
|
||||
{
|
||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
||||
if (it == mLayers.end())
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mLayers.empty())
|
||||
{
|
||||
shouldMove = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::size_t clampedTargetIndex = (std::min)(targetIndex, mLayers.size() - 1);
|
||||
const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(mLayers.begin(), it));
|
||||
shouldMove = sourceIndex != clampedTargetIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
JsonValue LayerStackStore::BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const
|
||||
{
|
||||
JsonValue root = JsonValue::MakeObject();
|
||||
root.set("version", JsonValue(1.0));
|
||||
root.set("name", JsonValue(TrimCopy(presetName)));
|
||||
root.set("layers", RuntimeStateJson::SerializeLayerStack(*this, shaderCatalog));
|
||||
return root;
|
||||
}
|
||||
|
||||
bool LayerStackStore::LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error)
|
||||
{
|
||||
const JsonValue* layersValue = root.find("layers");
|
||||
if (!layersValue || !layersValue->isArray())
|
||||
{
|
||||
error = "Preset file is missing a valid 'layers' array.";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<LayerPersistentState> nextLayers;
|
||||
uint64_t nextLayerId = mNextLayerId;
|
||||
if (!DeserializeLayerStack(shaderCatalog, *layersValue, nextLayers, nextLayerId, error))
|
||||
return false;
|
||||
|
||||
if (nextLayers.empty())
|
||||
{
|
||||
error = "Preset does not contain any valid layers.";
|
||||
return false;
|
||||
}
|
||||
|
||||
mLayers = std::move(nextLayers);
|
||||
mNextLayerId = nextLayerId;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string LayerStackStore::MakeSafePresetFileStem(const std::string& presetName)
|
||||
{
|
||||
return ::MakeSafePresetFileStem(presetName);
|
||||
}
|
||||
|
||||
const std::vector<LayerStackStore::LayerPersistentState>& LayerStackStore::Layers() const
|
||||
{
|
||||
return mLayers;
|
||||
}
|
||||
|
||||
std::vector<LayerStackStore::LayerPersistentState>& LayerStackStore::Layers()
|
||||
{
|
||||
return mLayers;
|
||||
}
|
||||
|
||||
std::size_t LayerStackStore::LayerCount() const
|
||||
{
|
||||
return mLayers.size();
|
||||
}
|
||||
|
||||
const LayerStackStore::LayerPersistentState* LayerStackStore::FindLayerById(const std::string& layerId) const
|
||||
{
|
||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
||||
return it == mLayers.end() ? nullptr : &*it;
|
||||
}
|
||||
|
||||
LayerStackStore::LayerPersistentState* LayerStackStore::FindLayerById(const std::string& layerId)
|
||||
{
|
||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
||||
return it == mLayers.end() ? nullptr : &*it;
|
||||
}
|
||||
|
||||
ShaderParameterValue LayerStackStore::DefaultValueForDefinition(const ShaderParameterDefinition& definition)
|
||||
{
|
||||
return ::DefaultValueForDefinition(definition);
|
||||
}
|
||||
|
||||
void LayerStackStore::EnsureLayerDefaults(LayerPersistentState& layerState, const ShaderPackage& shaderPackage)
|
||||
{
|
||||
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
|
||||
{
|
||||
auto valueIt = layerState.parameterValues.find(definition.id);
|
||||
if (valueIt == layerState.parameterValues.end())
|
||||
{
|
||||
layerState.parameterValues[definition.id] = DefaultValueForDefinition(definition);
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonValue valueJson;
|
||||
bool shouldNormalize = true;
|
||||
switch (definition.type)
|
||||
{
|
||||
case ShaderParameterType::Float:
|
||||
if (valueIt->second.numberValues.empty())
|
||||
shouldNormalize = false;
|
||||
else
|
||||
valueJson = JsonValue(valueIt->second.numberValues.front());
|
||||
break;
|
||||
case ShaderParameterType::Vec2:
|
||||
case ShaderParameterType::Color:
|
||||
valueJson = JsonValue::MakeArray();
|
||||
for (double number : valueIt->second.numberValues)
|
||||
valueJson.pushBack(JsonValue(number));
|
||||
break;
|
||||
case ShaderParameterType::Boolean:
|
||||
valueJson = JsonValue(valueIt->second.booleanValue);
|
||||
break;
|
||||
case ShaderParameterType::Enum:
|
||||
valueJson = JsonValue(valueIt->second.enumValue);
|
||||
break;
|
||||
case ShaderParameterType::Text:
|
||||
{
|
||||
const std::string textValue = !valueIt->second.textValue.empty()
|
||||
? valueIt->second.textValue
|
||||
: valueIt->second.enumValue;
|
||||
if (textValue.empty())
|
||||
{
|
||||
valueIt->second = DefaultValueForDefinition(definition);
|
||||
shouldNormalize = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
valueJson = JsonValue(textValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ShaderParameterType::Trigger:
|
||||
if (valueIt->second.numberValues.empty())
|
||||
valueJson = JsonValue(0.0);
|
||||
else
|
||||
valueJson = JsonValue((std::max)(0.0, std::floor(valueIt->second.numberValues.front())));
|
||||
break;
|
||||
}
|
||||
|
||||
if (!shouldNormalize)
|
||||
continue;
|
||||
|
||||
ShaderParameterValue normalizedValue;
|
||||
std::string normalizeError;
|
||||
if (NormalizeAndValidateParameterValue(definition, valueJson, normalizedValue, normalizeError))
|
||||
valueIt->second = normalizedValue;
|
||||
else
|
||||
valueIt->second = DefaultValueForDefinition(definition);
|
||||
}
|
||||
}
|
||||
|
||||
bool LayerStackStore::DeserializeLayerStack(const ShaderPackageCatalog& shaderCatalog, const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId, std::string& error)
|
||||
{
|
||||
for (const JsonValue& layerValue : layersValue.asArray())
|
||||
{
|
||||
if (!layerValue.isObject())
|
||||
continue;
|
||||
|
||||
const JsonValue* shaderIdValue = layerValue.find("shaderId");
|
||||
if (!shaderIdValue)
|
||||
continue;
|
||||
|
||||
const std::string shaderId = shaderIdValue->asString();
|
||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
|
||||
if (!shaderPackage)
|
||||
{
|
||||
error = "Preset references unknown shader id: " + shaderId;
|
||||
return false;
|
||||
}
|
||||
|
||||
LayerPersistentState layer;
|
||||
layer.id = GenerateLayerId(layers, nextLayerId);
|
||||
layer.shaderId = shaderId;
|
||||
if (const JsonValue* bypassValue = layerValue.find("bypass"))
|
||||
layer.bypass = bypassValue->asBoolean(false);
|
||||
|
||||
if (const JsonValue* parametersValue = layerValue.find("parameters"))
|
||||
{
|
||||
for (const JsonValue& parameterValue : parametersValue->asArray())
|
||||
{
|
||||
if (!parameterValue.isObject())
|
||||
continue;
|
||||
|
||||
const JsonValue* parameterIdValue = parameterValue.find("id");
|
||||
const JsonValue* valueValue = parameterValue.find("value");
|
||||
if (!parameterIdValue || !valueValue)
|
||||
continue;
|
||||
|
||||
const std::string parameterId = parameterIdValue->asString();
|
||||
auto definitionIt = std::find_if(shaderPackage->parameters.begin(), shaderPackage->parameters.end(),
|
||||
[¶meterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
|
||||
if (definitionIt == shaderPackage->parameters.end())
|
||||
continue;
|
||||
|
||||
ShaderParameterValue normalizedValue;
|
||||
if (!NormalizeAndValidateParameterValue(*definitionIt, *valueValue, normalizedValue, error))
|
||||
return false;
|
||||
|
||||
layer.parameterValues[parameterId] = normalizedValue;
|
||||
}
|
||||
}
|
||||
|
||||
EnsureLayerDefaults(layer, *shaderPackage);
|
||||
layers.push_back(layer);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string LayerStackStore::GenerateLayerId(std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
++nextLayerId;
|
||||
const std::string candidate = "layer-" + std::to_string(nextLayerId);
|
||||
auto it = std::find_if(layers.begin(), layers.end(),
|
||||
[&candidate](const LayerPersistentState& layer) { return layer.id == candidate; });
|
||||
if (it == layers.end())
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
#include "ShaderPackageCatalog.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class LayerStackStore
|
||||
{
|
||||
public:
|
||||
struct LayerPersistentState
|
||||
{
|
||||
std::string id;
|
||||
std::string shaderId;
|
||||
bool bypass = false;
|
||||
std::map<std::string, ShaderParameterValue> parameterValues;
|
||||
};
|
||||
|
||||
struct StoredParameterSnapshot
|
||||
{
|
||||
std::string layerId;
|
||||
ShaderParameterDefinition definition;
|
||||
ShaderParameterValue currentValue;
|
||||
bool hasCurrentValue = false;
|
||||
};
|
||||
|
||||
bool LoadPersistentStateValue(const JsonValue& root);
|
||||
JsonValue BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const;
|
||||
void NormalizeLayerIds();
|
||||
void EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog);
|
||||
void EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog);
|
||||
void RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog);
|
||||
|
||||
bool CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error);
|
||||
bool DeleteLayer(const std::string& layerId, std::string& error);
|
||||
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
|
||||
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
|
||||
bool SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error);
|
||||
bool SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error);
|
||||
bool SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error);
|
||||
bool ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error);
|
||||
|
||||
bool HasLayer(const std::string& layerId) const;
|
||||
bool TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const;
|
||||
bool TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const;
|
||||
bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
|
||||
bool ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
|
||||
|
||||
JsonValue BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const;
|
||||
bool LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error);
|
||||
static std::string MakeSafePresetFileStem(const std::string& presetName);
|
||||
|
||||
const std::vector<LayerPersistentState>& Layers() const;
|
||||
std::vector<LayerPersistentState>& Layers();
|
||||
std::size_t LayerCount() const;
|
||||
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
|
||||
LayerPersistentState* FindLayerById(const std::string& layerId);
|
||||
|
||||
private:
|
||||
static ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition);
|
||||
static void EnsureLayerDefaults(LayerPersistentState& layerState, const ShaderPackage& shaderPackage);
|
||||
static bool DeserializeLayerStack(const ShaderPackageCatalog& shaderCatalog, const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId, std::string& error);
|
||||
static std::string GenerateLayerId(std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId);
|
||||
|
||||
std::vector<LayerPersistentState> mLayers;
|
||||
uint64_t mNextLayerId = 0;
|
||||
};
|
||||
@@ -0,0 +1,270 @@
|
||||
#include "RuntimeConfigStore.h"
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <windows.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
double Clamp01(double value)
|
||||
{
|
||||
return (std::max)(0.0, (std::min)(1.0, value));
|
||||
}
|
||||
|
||||
bool LooksLikePackagedRuntimeRoot(const std::filesystem::path& candidate)
|
||||
{
|
||||
return std::filesystem::exists(candidate / "config" / "runtime-host.json") &&
|
||||
std::filesystem::exists(candidate / "runtime" / "templates" / "shader_wrapper.slang.in") &&
|
||||
std::filesystem::exists(candidate / "shaders");
|
||||
}
|
||||
|
||||
bool LooksLikeRepoRoot(const std::filesystem::path& candidate)
|
||||
{
|
||||
return std::filesystem::exists(candidate / "CMakeLists.txt") &&
|
||||
std::filesystem::exists(candidate / "apps" / "LoopThroughWithOpenGLCompositing");
|
||||
}
|
||||
|
||||
std::filesystem::path FindRepoRootCandidate()
|
||||
{
|
||||
std::vector<std::filesystem::path> rootsToTry;
|
||||
|
||||
char currentDirectory[MAX_PATH] = {};
|
||||
if (GetCurrentDirectoryA(MAX_PATH, currentDirectory) > 0)
|
||||
rootsToTry.push_back(std::filesystem::path(currentDirectory));
|
||||
|
||||
char modulePath[MAX_PATH] = {};
|
||||
DWORD moduleLength = GetModuleFileNameA(NULL, modulePath, MAX_PATH);
|
||||
if (moduleLength > 0 && moduleLength < MAX_PATH)
|
||||
rootsToTry.push_back(std::filesystem::path(modulePath).parent_path());
|
||||
|
||||
for (const std::filesystem::path& startPath : rootsToTry)
|
||||
{
|
||||
std::filesystem::path candidate = startPath;
|
||||
for (int depth = 0; depth < 10 && !candidate.empty(); ++depth)
|
||||
{
|
||||
if (LooksLikePackagedRuntimeRoot(candidate) || LooksLikeRepoRoot(candidate))
|
||||
return candidate;
|
||||
|
||||
candidate = candidate.parent_path();
|
||||
}
|
||||
}
|
||||
|
||||
return std::filesystem::path();
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeConfigStore::Initialize(std::string& error)
|
||||
{
|
||||
if (!ResolvePaths(error))
|
||||
return false;
|
||||
if (!LoadConfig(error))
|
||||
return false;
|
||||
RefreshConfigDependentPaths();
|
||||
return true;
|
||||
}
|
||||
|
||||
const RuntimeConfigStore::AppConfig& RuntimeConfigStore::GetConfig() const
|
||||
{
|
||||
return mConfig;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeConfigStore::GetRepoRoot() const
|
||||
{
|
||||
return mRepoRoot;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeConfigStore::GetUiRoot() const
|
||||
{
|
||||
return mUiRoot;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeConfigStore::GetDocsRoot() const
|
||||
{
|
||||
return mDocsRoot;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeConfigStore::GetShaderRoot() const
|
||||
{
|
||||
return mShaderRoot;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeConfigStore::GetRuntimeRoot() const
|
||||
{
|
||||
return mRuntimeRoot;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeConfigStore::GetPresetRoot() const
|
||||
{
|
||||
return mPresetRoot;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeConfigStore::GetRuntimeStatePath() const
|
||||
{
|
||||
return mRuntimeStatePath;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeConfigStore::GetWrapperPath() const
|
||||
{
|
||||
return mWrapperPath;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeConfigStore::GetGeneratedGlslPath() const
|
||||
{
|
||||
return mGeneratedGlslPath;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeConfigStore::GetPatchedGlslPath() const
|
||||
{
|
||||
return mPatchedGlslPath;
|
||||
}
|
||||
|
||||
void RuntimeConfigStore::SetBoundControlServerPort(unsigned short port)
|
||||
{
|
||||
mConfig.serverPort = port;
|
||||
}
|
||||
|
||||
bool RuntimeConfigStore::ResolvePaths(std::string& error)
|
||||
{
|
||||
mRepoRoot = FindRepoRootCandidate();
|
||||
if (mRepoRoot.empty())
|
||||
{
|
||||
error = "Could not locate the repository root from the current runtime path.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::filesystem::path builtUiRoot = mRepoRoot / "ui" / "dist";
|
||||
mUiRoot = std::filesystem::exists(builtUiRoot) ? builtUiRoot : (mRepoRoot / "ui");
|
||||
mDocsRoot = mRepoRoot / "docs";
|
||||
mConfigPath = mRepoRoot / "config" / "runtime-host.json";
|
||||
mRuntimeRoot = mRepoRoot / "runtime";
|
||||
mPresetRoot = mRuntimeRoot / "stack_presets";
|
||||
mRuntimeStatePath = mRuntimeRoot / "runtime_state.json";
|
||||
RefreshConfigDependentPaths();
|
||||
|
||||
std::error_code fsError;
|
||||
std::filesystem::create_directories(mRuntimeRoot / "shader_cache", fsError);
|
||||
std::filesystem::create_directories(mPresetRoot, fsError);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeConfigStore::LoadConfig(std::string& error)
|
||||
{
|
||||
if (!std::filesystem::exists(mConfigPath))
|
||||
return true;
|
||||
|
||||
std::string configText = ReadTextFile(mConfigPath, error);
|
||||
if (configText.empty())
|
||||
return false;
|
||||
|
||||
JsonValue configJson;
|
||||
if (!ParseJson(configText, configJson, error))
|
||||
return false;
|
||||
|
||||
if (const JsonValue* shaderLibraryValue = configJson.find("shaderLibrary"))
|
||||
mConfig.shaderLibrary = shaderLibraryValue->asString();
|
||||
if (const JsonValue* serverPortValue = configJson.find("serverPort"))
|
||||
mConfig.serverPort = static_cast<unsigned short>(serverPortValue->asNumber(mConfig.serverPort));
|
||||
if (const JsonValue* oscPortValue = configJson.find("oscPort"))
|
||||
mConfig.oscPort = static_cast<unsigned short>(oscPortValue->asNumber(mConfig.oscPort));
|
||||
if (const JsonValue* oscBindAddressValue = configJson.find("oscBindAddress"))
|
||||
mConfig.oscBindAddress = oscBindAddressValue->asString();
|
||||
if (const JsonValue* oscSmoothingValue = configJson.find("oscSmoothing"))
|
||||
mConfig.oscSmoothing = Clamp01(oscSmoothingValue->asNumber(mConfig.oscSmoothing));
|
||||
if (const JsonValue* autoReloadValue = configJson.find("autoReload"))
|
||||
mConfig.autoReload = autoReloadValue->asBoolean(mConfig.autoReload);
|
||||
if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames"))
|
||||
{
|
||||
const double configuredValue = maxTemporalHistoryFramesValue->asNumber(static_cast<double>(mConfig.maxTemporalHistoryFrames));
|
||||
mConfig.maxTemporalHistoryFrames = configuredValue <= 0.0 ? 0u : static_cast<unsigned>(configuredValue);
|
||||
}
|
||||
if (const JsonValue* previewFpsValue = configJson.find("previewFps"))
|
||||
{
|
||||
const double configuredValue = previewFpsValue->asNumber(static_cast<double>(mConfig.previewFps));
|
||||
mConfig.previewFps = configuredValue <= 0.0 ? 0u : static_cast<unsigned>(configuredValue);
|
||||
}
|
||||
if (const JsonValue* enableExternalKeyingValue = configJson.find("enableExternalKeying"))
|
||||
mConfig.enableExternalKeying = enableExternalKeyingValue->asBoolean(mConfig.enableExternalKeying);
|
||||
if (const JsonValue* videoFormatValue = configJson.find("videoFormat"))
|
||||
{
|
||||
if (videoFormatValue->isString() && !videoFormatValue->asString().empty())
|
||||
{
|
||||
mConfig.inputVideoFormat = videoFormatValue->asString();
|
||||
mConfig.outputVideoFormat = videoFormatValue->asString();
|
||||
}
|
||||
}
|
||||
if (const JsonValue* frameRateValue = configJson.find("frameRate"))
|
||||
{
|
||||
if (frameRateValue->isString() && !frameRateValue->asString().empty())
|
||||
{
|
||||
mConfig.inputFrameRate = frameRateValue->asString();
|
||||
mConfig.outputFrameRate = frameRateValue->asString();
|
||||
}
|
||||
else if (frameRateValue->isNumber())
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << frameRateValue->asNumber();
|
||||
mConfig.inputFrameRate = stream.str();
|
||||
mConfig.outputFrameRate = stream.str();
|
||||
}
|
||||
}
|
||||
if (const JsonValue* inputVideoFormatValue = configJson.find("inputVideoFormat"))
|
||||
{
|
||||
if (inputVideoFormatValue->isString() && !inputVideoFormatValue->asString().empty())
|
||||
mConfig.inputVideoFormat = inputVideoFormatValue->asString();
|
||||
}
|
||||
if (const JsonValue* inputFrameRateValue = configJson.find("inputFrameRate"))
|
||||
{
|
||||
if (inputFrameRateValue->isString() && !inputFrameRateValue->asString().empty())
|
||||
mConfig.inputFrameRate = inputFrameRateValue->asString();
|
||||
else if (inputFrameRateValue->isNumber())
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << inputFrameRateValue->asNumber();
|
||||
mConfig.inputFrameRate = stream.str();
|
||||
}
|
||||
}
|
||||
if (const JsonValue* outputVideoFormatValue = configJson.find("outputVideoFormat"))
|
||||
{
|
||||
if (outputVideoFormatValue->isString() && !outputVideoFormatValue->asString().empty())
|
||||
mConfig.outputVideoFormat = outputVideoFormatValue->asString();
|
||||
}
|
||||
if (const JsonValue* outputFrameRateValue = configJson.find("outputFrameRate"))
|
||||
{
|
||||
if (outputFrameRateValue->isString() && !outputFrameRateValue->asString().empty())
|
||||
mConfig.outputFrameRate = outputFrameRateValue->asString();
|
||||
else if (outputFrameRateValue->isNumber())
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << outputFrameRateValue->asNumber();
|
||||
mConfig.outputFrameRate = stream.str();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string RuntimeConfigStore::ReadTextFile(const std::filesystem::path& path, std::string& error) const
|
||||
{
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input)
|
||||
{
|
||||
error = "Could not open file: " + path.string();
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
void RuntimeConfigStore::RefreshConfigDependentPaths()
|
||||
{
|
||||
mShaderRoot = mRepoRoot / mConfig.shaderLibrary;
|
||||
mWrapperPath = mRuntimeRoot / "shader_cache" / "active_shader_wrapper.slang";
|
||||
mGeneratedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.raw.frag";
|
||||
mPatchedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.frag";
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
class RuntimeConfigStore
|
||||
{
|
||||
public:
|
||||
struct AppConfig
|
||||
{
|
||||
std::string shaderLibrary = "shaders";
|
||||
unsigned short serverPort = 8080;
|
||||
unsigned short oscPort = 9000;
|
||||
std::string oscBindAddress = "127.0.0.1";
|
||||
double oscSmoothing = 0.18;
|
||||
bool autoReload = true;
|
||||
unsigned maxTemporalHistoryFrames = 4;
|
||||
unsigned previewFps = 30;
|
||||
bool enableExternalKeying = false;
|
||||
std::string inputVideoFormat = "1080p";
|
||||
std::string inputFrameRate = "59.94";
|
||||
std::string outputVideoFormat = "1080p";
|
||||
std::string outputFrameRate = "59.94";
|
||||
};
|
||||
|
||||
bool Initialize(std::string& error);
|
||||
|
||||
const AppConfig& GetConfig() const;
|
||||
const std::filesystem::path& GetRepoRoot() const;
|
||||
const std::filesystem::path& GetUiRoot() const;
|
||||
const std::filesystem::path& GetDocsRoot() const;
|
||||
const std::filesystem::path& GetShaderRoot() const;
|
||||
const std::filesystem::path& GetRuntimeRoot() const;
|
||||
const std::filesystem::path& GetPresetRoot() const;
|
||||
const std::filesystem::path& GetRuntimeStatePath() const;
|
||||
const std::filesystem::path& GetWrapperPath() const;
|
||||
const std::filesystem::path& GetGeneratedGlslPath() const;
|
||||
const std::filesystem::path& GetPatchedGlslPath() const;
|
||||
void SetBoundControlServerPort(unsigned short port);
|
||||
|
||||
private:
|
||||
bool ResolvePaths(std::string& error);
|
||||
bool LoadConfig(std::string& error);
|
||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
||||
void RefreshConfigDependentPaths();
|
||||
|
||||
AppConfig mConfig;
|
||||
std::filesystem::path mRepoRoot;
|
||||
std::filesystem::path mUiRoot;
|
||||
std::filesystem::path mDocsRoot;
|
||||
std::filesystem::path mShaderRoot;
|
||||
std::filesystem::path mRuntimeRoot;
|
||||
std::filesystem::path mPresetRoot;
|
||||
std::filesystem::path mRuntimeStatePath;
|
||||
std::filesystem::path mConfigPath;
|
||||
std::filesystem::path mWrapperPath;
|
||||
std::filesystem::path mGeneratedGlslPath;
|
||||
std::filesystem::path mPatchedGlslPath;
|
||||
};
|
||||
@@ -0,0 +1,704 @@
|
||||
#include "RuntimeStore.h"
|
||||
|
||||
#include "RuntimeStatePresenter.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string ToLowerCopy(std::string text)
|
||||
{
|
||||
std::transform(text.begin(), text.end(), text.begin(),
|
||||
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
|
||||
return text;
|
||||
}
|
||||
|
||||
double GenerateStartupRandom()
|
||||
{
|
||||
std::random_device randomDevice;
|
||||
std::uniform_real_distribution<double> distribution(0.0, 1.0);
|
||||
return distribution(randomDevice);
|
||||
}
|
||||
|
||||
std::string PersistenceTargetKindName(PersistenceTargetKind targetKind)
|
||||
{
|
||||
switch (targetKind)
|
||||
{
|
||||
case PersistenceTargetKind::RuntimeState:
|
||||
return "runtime-state";
|
||||
case PersistenceTargetKind::StackPreset:
|
||||
return "stack-preset";
|
||||
case PersistenceTargetKind::RuntimeConfig:
|
||||
return "runtime-config";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RuntimeStore::RuntimeStore() :
|
||||
mRenderSnapshotBuilder(*this),
|
||||
mReloadRequested(false),
|
||||
mCompileSucceeded(false),
|
||||
mStartupRandom(GenerateStartupRandom()),
|
||||
mServerPort(8080),
|
||||
mAutoReloadEnabled(true),
|
||||
mStartTime(std::chrono::steady_clock::now()),
|
||||
mLastScanTime((std::chrono::steady_clock::time_point::min)())
|
||||
{
|
||||
mPersistenceWriter.SetResultCallback([this](const PersistenceWriteResult& result) {
|
||||
mHealthTelemetry.RecordPersistenceWriteResult(
|
||||
result.succeeded,
|
||||
PersistenceTargetKindName(result.targetKind),
|
||||
result.targetPath,
|
||||
result.reason,
|
||||
result.errorMessage,
|
||||
result.newerRequestPending);
|
||||
});
|
||||
}
|
||||
|
||||
HealthTelemetry& RuntimeStore::GetHealthTelemetry()
|
||||
{
|
||||
return mHealthTelemetry;
|
||||
}
|
||||
|
||||
const HealthTelemetry& RuntimeStore::GetHealthTelemetry() const
|
||||
{
|
||||
return mHealthTelemetry;
|
||||
}
|
||||
|
||||
RenderSnapshotBuilder& RuntimeStore::GetRenderSnapshotBuilder()
|
||||
{
|
||||
return mRenderSnapshotBuilder;
|
||||
}
|
||||
|
||||
const RenderSnapshotBuilder& RuntimeStore::GetRenderSnapshotBuilder() const
|
||||
{
|
||||
return mRenderSnapshotBuilder;
|
||||
}
|
||||
|
||||
bool RuntimeStore::InitializeStore(std::string& error)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
if (!mConfigStore.Initialize(error))
|
||||
return false;
|
||||
if (!LoadPersistentState(error))
|
||||
return false;
|
||||
if (!ScanShaderPackages(error))
|
||||
return false;
|
||||
mCommittedLiveState.NormalizeLayerIds();
|
||||
mCommittedLiveState.EnsureDefaultsForAllLayers(mShaderCatalog);
|
||||
mCommittedLiveState.EnsureDefaultLayer(mShaderCatalog);
|
||||
|
||||
mServerPort = mConfigStore.GetConfig().serverPort;
|
||||
mAutoReloadEnabled = mConfigStore.GetConfig().autoReload;
|
||||
mReloadRequested = true;
|
||||
mCompileMessage = "Waiting for shader compile.";
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
error = std::string("RuntimeStore::InitializeStore exception: ") + exception.what();
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
error = "RuntimeStore::InitializeStore threw a non-standard exception.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string RuntimeStore::BuildPersistentStateJson() const
|
||||
{
|
||||
return RuntimeStatePresenter::BuildRuntimeStateJson(*this);
|
||||
}
|
||||
|
||||
PersistenceSnapshot RuntimeStore::BuildRuntimeStatePersistenceSnapshot(const PersistenceRequest& request) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return BuildRuntimeStatePersistenceSnapshotLocked(request);
|
||||
}
|
||||
|
||||
bool RuntimeStore::RequestPersistence(const PersistenceRequest& request, std::string& error)
|
||||
{
|
||||
if (request.targetKind != PersistenceTargetKind::RuntimeState)
|
||||
{
|
||||
error = "Unsupported persistence request target: " + PersistenceTargetKindName(request.targetKind);
|
||||
mHealthTelemetry.RecordPersistenceWriteResult(
|
||||
false,
|
||||
PersistenceTargetKindName(request.targetKind),
|
||||
std::string(),
|
||||
request.reason,
|
||||
error,
|
||||
false);
|
||||
return false;
|
||||
}
|
||||
|
||||
const PersistenceSnapshot snapshot = BuildRuntimeStatePersistenceSnapshot(request);
|
||||
if (mPersistenceWriter.EnqueueSnapshot(snapshot, error))
|
||||
return true;
|
||||
|
||||
mHealthTelemetry.RecordPersistenceWriteResult(
|
||||
false,
|
||||
PersistenceTargetKindName(request.targetKind),
|
||||
snapshot.targetPath.string(),
|
||||
request.reason,
|
||||
error,
|
||||
false);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RuntimeStore::FlushPersistenceForShutdown(std::chrono::milliseconds timeout, std::string& error)
|
||||
{
|
||||
if (mPersistenceWriter.StopAndFlush(timeout, error))
|
||||
return true;
|
||||
|
||||
mHealthTelemetry.RecordPersistenceWriteResult(
|
||||
false,
|
||||
PersistenceTargetKindName(PersistenceTargetKind::RuntimeState),
|
||||
std::string(),
|
||||
"shutdown-flush",
|
||||
error,
|
||||
true);
|
||||
return false;
|
||||
}
|
||||
|
||||
PersistenceSnapshot RuntimeStore::BuildRuntimeStatePersistenceSnapshotLocked(const PersistenceRequest& request) const
|
||||
{
|
||||
PersistenceSnapshot snapshot;
|
||||
snapshot.targetKind = PersistenceTargetKind::RuntimeState;
|
||||
snapshot.targetPath = mConfigStore.GetRuntimeStatePath();
|
||||
snapshot.contents = SerializeJson(mCommittedLiveState.BuildPersistentStateValue(mShaderCatalog), true);
|
||||
snapshot.reason = request.reason;
|
||||
snapshot.debounceKey = request.debounceKey;
|
||||
snapshot.debounceAllowed = request.debounceAllowed;
|
||||
snapshot.flushRequested = request.flushRequested;
|
||||
snapshot.generation = request.sequence;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
registryChanged = false;
|
||||
reloadRequested = false;
|
||||
|
||||
if (!mAutoReloadEnabled)
|
||||
{
|
||||
reloadRequested = mReloadRequested;
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (mLastScanTime != (std::chrono::steady_clock::time_point::min)() &&
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now - mLastScanTime).count() < 250)
|
||||
{
|
||||
reloadRequested = mReloadRequested;
|
||||
return true;
|
||||
}
|
||||
|
||||
mLastScanTime = now;
|
||||
|
||||
std::string scanError;
|
||||
const ShaderPackageCatalog::Snapshot previousCatalog = mShaderCatalog.CaptureSnapshot();
|
||||
if (!ScanShaderPackages(scanError))
|
||||
{
|
||||
error = scanError;
|
||||
return false;
|
||||
}
|
||||
|
||||
registryChanged = mShaderCatalog.HasCatalogChangedSince(previousCatalog);
|
||||
|
||||
mCommittedLiveState.EnsureDefaultsForAllLayers(mShaderCatalog);
|
||||
for (RuntimeStore::LayerPersistentState& layer : mCommittedLiveState.Layers())
|
||||
{
|
||||
const ShaderPackage* active = mShaderCatalog.FindPackage(layer.shaderId);
|
||||
if (!active)
|
||||
continue;
|
||||
if (mShaderCatalog.HasPackageChangedSince(previousCatalog, layer.shaderId))
|
||||
mReloadRequested = true;
|
||||
}
|
||||
|
||||
reloadRequested = mReloadRequested;
|
||||
if (registryChanged || reloadRequested)
|
||||
MarkRenderStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
error = std::string("RuntimeStore::PollStoredFileChanges exception: ") + exception.what();
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
error = "RuntimeStore::PollStoredFileChanges threw a non-standard exception.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeStore::CreateStoredLayer(const std::string& shaderId, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!mCommittedLiveState.CreateLayer(mShaderCatalog, shaderId, error))
|
||||
return false;
|
||||
|
||||
mReloadRequested = true;
|
||||
MarkRenderStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeStore::DeleteStoredLayer(const std::string& layerId, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!mCommittedLiveState.DeleteLayer(layerId, error))
|
||||
return false;
|
||||
|
||||
mReloadRequested = true;
|
||||
MarkRenderStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeStore::MoveStoredLayer(const std::string& layerId, int direction, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
bool shouldMove = false;
|
||||
if (!mCommittedLiveState.ResolveLayerMove(layerId, direction, shouldMove, error))
|
||||
return false;
|
||||
if (!shouldMove)
|
||||
return true;
|
||||
|
||||
if (!mCommittedLiveState.MoveLayer(layerId, direction, error))
|
||||
return false;
|
||||
|
||||
mReloadRequested = true;
|
||||
MarkRenderStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeStore::MoveStoredLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
bool shouldMove = false;
|
||||
if (!mCommittedLiveState.ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error))
|
||||
return false;
|
||||
if (!shouldMove)
|
||||
return true;
|
||||
|
||||
if (!mCommittedLiveState.MoveLayerToIndex(layerId, targetIndex, error))
|
||||
return false;
|
||||
|
||||
mReloadRequested = true;
|
||||
MarkRenderStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeStore::SetStoredLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!mCommittedLiveState.SetLayerBypassState(layerId, bypassed, error))
|
||||
return false;
|
||||
|
||||
mReloadRequested = true;
|
||||
MarkParameterStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, const std::string& shaderId, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!mCommittedLiveState.SetLayerShaderSelection(mShaderCatalog, layerId, shaderId, error))
|
||||
return false;
|
||||
|
||||
mReloadRequested = true;
|
||||
MarkRenderStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, bool persistState, std::string& error)
|
||||
{
|
||||
(void)persistState;
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
if (!mCommittedLiveState.SetParameterValue(layerId, parameterId, value, error))
|
||||
return false;
|
||||
|
||||
MarkParameterStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
if (!mCommittedLiveState.ResetLayerParameterValues(mShaderCatalog, layerId, error))
|
||||
return false;
|
||||
|
||||
MarkParameterStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName);
|
||||
if (safeStem.empty())
|
||||
{
|
||||
error = "Preset name must include at least one letter or number.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return mPersistenceWriter.WriteSnapshot(BuildStackPresetPersistenceSnapshot(presetName), error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName);
|
||||
if (safeStem.empty())
|
||||
{
|
||||
error = "Preset name must include at least one letter or number.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::filesystem::path presetPath = mConfigStore.GetPresetRoot() / (safeStem + ".json");
|
||||
std::string presetText = ReadTextFile(presetPath, error);
|
||||
if (presetText.empty())
|
||||
return false;
|
||||
|
||||
JsonValue root;
|
||||
if (!ParseJson(presetText, root, error))
|
||||
return false;
|
||||
|
||||
if (!mCommittedLiveState.LoadStackPresetValue(mShaderCatalog, root, error))
|
||||
return false;
|
||||
|
||||
mReloadRequested = true;
|
||||
MarkRenderStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeStore::HasStoredLayer(const std::string& layerId) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mCommittedLiveState.HasLayer(layerId);
|
||||
}
|
||||
|
||||
bool RuntimeStore::HasStoredShader(const std::string& shaderId) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mShaderCatalog.HasPackage(shaderId);
|
||||
}
|
||||
|
||||
bool RuntimeStore::TryGetStoredParameterById(const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
return mCommittedLiveState.TryGetParameterById(mShaderCatalog, layerId, parameterId, snapshot, error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::TryGetStoredParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
return mCommittedLiveState.TryGetParameterByControlKey(mShaderCatalog, layerKey, parameterKey, snapshot, error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::ResolveStoredLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mCommittedLiveState.ResolveLayerMove(layerId, direction, shouldMove, error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::ResolveStoredLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mCommittedLiveState.ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::IsValidStackPresetName(const std::string& presetName) const
|
||||
{
|
||||
return !LayerStackStore::MakeSafePresetFileStem(presetName).empty();
|
||||
}
|
||||
|
||||
double RuntimeStore::GetRuntimeElapsedSeconds() const
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::duration<double>>(
|
||||
std::chrono::steady_clock::now() - mStartTime).count();
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeStore::GetRuntimeRepositoryRoot() const
|
||||
{
|
||||
return mConfigStore.GetRepoRoot();
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeStore::GetRuntimeUiRoot() const
|
||||
{
|
||||
return mConfigStore.GetUiRoot();
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeStore::GetRuntimeDocsRoot() const
|
||||
{
|
||||
return mConfigStore.GetDocsRoot();
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeStore::GetRuntimeDataRoot() const
|
||||
{
|
||||
return mConfigStore.GetRuntimeRoot();
|
||||
}
|
||||
|
||||
unsigned short RuntimeStore::GetConfiguredControlServerPort() const
|
||||
{
|
||||
return mServerPort;
|
||||
}
|
||||
|
||||
unsigned short RuntimeStore::GetConfiguredOscPort() const
|
||||
{
|
||||
return mConfigStore.GetConfig().oscPort;
|
||||
}
|
||||
|
||||
const std::string& RuntimeStore::GetConfiguredOscBindAddress() const
|
||||
{
|
||||
return mConfigStore.GetConfig().oscBindAddress;
|
||||
}
|
||||
|
||||
double RuntimeStore::GetConfiguredOscSmoothing() const
|
||||
{
|
||||
return mConfigStore.GetConfig().oscSmoothing;
|
||||
}
|
||||
|
||||
unsigned RuntimeStore::GetConfiguredMaxTemporalHistoryFrames() const
|
||||
{
|
||||
return mConfigStore.GetConfig().maxTemporalHistoryFrames;
|
||||
}
|
||||
|
||||
unsigned RuntimeStore::GetConfiguredPreviewFps() const
|
||||
{
|
||||
return mConfigStore.GetConfig().previewFps;
|
||||
}
|
||||
|
||||
bool RuntimeStore::IsExternalKeyingConfigured() const
|
||||
{
|
||||
return mConfigStore.GetConfig().enableExternalKeying;
|
||||
}
|
||||
|
||||
const std::string& RuntimeStore::GetConfiguredInputVideoFormat() const
|
||||
{
|
||||
return mConfigStore.GetConfig().inputVideoFormat;
|
||||
}
|
||||
|
||||
const std::string& RuntimeStore::GetConfiguredInputFrameRate() const
|
||||
{
|
||||
return mConfigStore.GetConfig().inputFrameRate;
|
||||
}
|
||||
|
||||
const std::string& RuntimeStore::GetConfiguredOutputVideoFormat() const
|
||||
{
|
||||
return mConfigStore.GetConfig().outputVideoFormat;
|
||||
}
|
||||
|
||||
const std::string& RuntimeStore::GetConfiguredOutputFrameRate() const
|
||||
{
|
||||
return mConfigStore.GetConfig().outputFrameRate;
|
||||
}
|
||||
|
||||
void RuntimeStore::SetBoundControlServerPort(unsigned short port)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mServerPort = port;
|
||||
mConfigStore.SetBoundControlServerPort(port);
|
||||
}
|
||||
|
||||
void RuntimeStore::SetCompileStatus(bool succeeded, const std::string& message)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mCompileSucceeded = succeeded;
|
||||
mCompileMessage = message;
|
||||
}
|
||||
|
||||
void RuntimeStore::ClearReloadRequest()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mReloadRequested = false;
|
||||
}
|
||||
|
||||
bool RuntimeStore::LoadPersistentState(std::string& error)
|
||||
{
|
||||
if (!std::filesystem::exists(mConfigStore.GetRuntimeStatePath()))
|
||||
return true;
|
||||
|
||||
std::string stateText = ReadTextFile(mConfigStore.GetRuntimeStatePath(), error);
|
||||
if (stateText.empty())
|
||||
return false;
|
||||
|
||||
JsonValue root;
|
||||
if (!ParseJson(stateText, root, error))
|
||||
return false;
|
||||
|
||||
return mCommittedLiveState.LoadPersistentStateValue(root);
|
||||
}
|
||||
|
||||
PersistenceSnapshot RuntimeStore::BuildStackPresetPersistenceSnapshot(const std::string& presetName) const
|
||||
{
|
||||
const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName);
|
||||
|
||||
PersistenceSnapshot snapshot;
|
||||
snapshot.targetKind = PersistenceTargetKind::StackPreset;
|
||||
snapshot.targetPath = mConfigStore.GetPresetRoot() / (safeStem + ".json");
|
||||
snapshot.contents = SerializeJson(mCommittedLiveState.BuildStackPresetValue(mShaderCatalog, presetName), true);
|
||||
snapshot.reason = "SaveStackPreset";
|
||||
snapshot.debounceKey = "stack-preset:" + safeStem;
|
||||
snapshot.debounceAllowed = false;
|
||||
snapshot.flushRequested = true;
|
||||
snapshot.generation = 0;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
bool RuntimeStore::ScanShaderPackages(std::string& error)
|
||||
{
|
||||
if (!mShaderCatalog.Scan(mConfigStore.GetShaderRoot(), mConfigStore.GetConfig().maxTemporalHistoryFrames, error))
|
||||
return false;
|
||||
|
||||
mCommittedLiveState.RemoveLayersWithMissingPackages(mShaderCatalog);
|
||||
|
||||
MarkRenderStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string RuntimeStore::ReadTextFile(const std::filesystem::path& path, std::string& error) const
|
||||
{
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input)
|
||||
{
|
||||
error = "Could not open file: " + path.string();
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
std::vector<std::string> RuntimeStore::GetStackPresetNamesLocked() const
|
||||
{
|
||||
std::vector<std::string> presetNames;
|
||||
std::error_code fsError;
|
||||
if (!std::filesystem::exists(mConfigStore.GetPresetRoot(), fsError))
|
||||
return presetNames;
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(mConfigStore.GetPresetRoot(), fsError))
|
||||
{
|
||||
if (!entry.is_regular_file())
|
||||
continue;
|
||||
if (ToLowerCopy(entry.path().extension().string()) != ".json")
|
||||
continue;
|
||||
presetNames.push_back(entry.path().stem().string());
|
||||
}
|
||||
|
||||
std::sort(presetNames.begin(), presetNames.end());
|
||||
return presetNames;
|
||||
}
|
||||
|
||||
bool RuntimeStore::CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
const RuntimeStore::LayerPersistentState* layer = mCommittedLiveState.FindLayerById(layerId);
|
||||
if (!layer)
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mShaderCatalog.CopyPackage(layer->shaderId, shaderPackage))
|
||||
{
|
||||
error = "Unknown shader id: " + layer->shaderId;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ShaderCompilerInputs RuntimeStore::GetShaderCompilerInputs() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
ShaderCompilerInputs inputs;
|
||||
inputs.repoRoot = mConfigStore.GetRepoRoot();
|
||||
inputs.wrapperPath = mConfigStore.GetWrapperPath();
|
||||
inputs.generatedGlslPath = mConfigStore.GetGeneratedGlslPath();
|
||||
inputs.patchedGlslPath = mConfigStore.GetPatchedGlslPath();
|
||||
inputs.maxTemporalHistoryFrames = mConfigStore.GetConfig().maxTemporalHistoryFrames;
|
||||
return inputs;
|
||||
}
|
||||
|
||||
CommittedLiveStateReadModel RuntimeStore::BuildCommittedLiveStateReadModel() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mCommittedLiveState.BuildReadModel(mShaderCatalog);
|
||||
}
|
||||
|
||||
RenderSnapshotReadModel RuntimeStore::BuildRenderSnapshotReadModel() const
|
||||
{
|
||||
RenderSnapshotReadModel model;
|
||||
model.signalStatus = mHealthTelemetry.GetSignalStatusSnapshot();
|
||||
model.committedLiveState = BuildCommittedLiveStateReadModel();
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
model.timing.startTime = mStartTime;
|
||||
model.timing.startupRandom = mStartupRandom;
|
||||
return model;
|
||||
}
|
||||
|
||||
std::vector<RuntimeStore::LayerPersistentState> RuntimeStore::CopyCommittedLiveLayerStates() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mCommittedLiveState.CopyLayerStates();
|
||||
}
|
||||
|
||||
std::vector<RuntimeStore::LayerPersistentState> RuntimeStore::CopyLayerStates() const
|
||||
{
|
||||
return CopyCommittedLiveLayerStates();
|
||||
}
|
||||
|
||||
RenderTimingSnapshot RuntimeStore::GetRenderTimingSnapshot() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
RenderTimingSnapshot snapshot;
|
||||
snapshot.startTime = mStartTime;
|
||||
snapshot.startupRandom = mStartupRandom;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
RuntimeStatePresentationReadModel RuntimeStore::BuildRuntimeStatePresentationReadModel() const
|
||||
{
|
||||
RuntimeStatePresentationReadModel model;
|
||||
model.telemetry = mHealthTelemetry.GetSnapshot();
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
model.config = mConfigStore.GetConfig();
|
||||
model.layerStack = mCommittedLiveState.LayerStack();
|
||||
model.shaderCatalog = mShaderCatalog.CaptureSnapshot();
|
||||
model.packageStatuses = mShaderCatalog.PackageStatuses();
|
||||
model.stackPresetNames = GetStackPresetNamesLocked();
|
||||
model.serverPort = mServerPort;
|
||||
model.autoReloadEnabled = mAutoReloadEnabled;
|
||||
model.compileSucceeded = mCompileSucceeded;
|
||||
model.compileMessage = mCompileMessage;
|
||||
return model;
|
||||
}
|
||||
|
||||
void RuntimeStore::MarkRenderStateDirtyLocked()
|
||||
{
|
||||
mRenderSnapshotBuilder.MarkRenderStateDirty();
|
||||
}
|
||||
|
||||
void RuntimeStore::MarkParameterStateDirtyLocked()
|
||||
{
|
||||
mRenderSnapshotBuilder.MarkParameterStateDirty();
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include "HealthTelemetry.h"
|
||||
#include "CommittedLiveState.h"
|
||||
#include "LayerStackStore.h"
|
||||
#include "PersistenceWriter.h"
|
||||
#include "RenderSnapshotBuilder.h"
|
||||
#include "RuntimeConfigStore.h"
|
||||
#include "RuntimeJson.h"
|
||||
#include "RuntimeStoreReadModels.h"
|
||||
#include "ShaderPackageCatalog.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class RuntimeStore
|
||||
{
|
||||
public:
|
||||
using StoredParameterSnapshot = LayerStackStore::StoredParameterSnapshot;
|
||||
using LayerPersistentState = LayerStackStore::LayerPersistentState;
|
||||
|
||||
RuntimeStore();
|
||||
HealthTelemetry& GetHealthTelemetry();
|
||||
const HealthTelemetry& GetHealthTelemetry() const;
|
||||
RenderSnapshotBuilder& GetRenderSnapshotBuilder();
|
||||
const RenderSnapshotBuilder& GetRenderSnapshotBuilder() const;
|
||||
|
||||
bool InitializeStore(std::string& error);
|
||||
std::string BuildPersistentStateJson() const;
|
||||
PersistenceSnapshot BuildRuntimeStatePersistenceSnapshot(const PersistenceRequest& request) const;
|
||||
bool RequestPersistence(const PersistenceRequest& request, std::string& error);
|
||||
bool FlushPersistenceForShutdown(std::chrono::milliseconds timeout, std::string& error);
|
||||
bool PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error);
|
||||
|
||||
bool CreateStoredLayer(const std::string& shaderId, std::string& error);
|
||||
bool DeleteStoredLayer(const std::string& layerId, std::string& error);
|
||||
bool MoveStoredLayer(const std::string& layerId, int direction, std::string& error);
|
||||
bool MoveStoredLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
|
||||
bool SetStoredLayerBypassState(const std::string& layerId, bool bypassed, std::string& error);
|
||||
bool SetStoredLayerShaderSelection(const std::string& layerId, const std::string& shaderId, std::string& error);
|
||||
bool SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, bool persistState, std::string& error);
|
||||
bool ResetStoredLayerParameterValues(const std::string& layerId, std::string& error);
|
||||
bool SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const;
|
||||
bool LoadStackPresetSnapshot(const std::string& presetName, std::string& error);
|
||||
bool HasStoredLayer(const std::string& layerId) const;
|
||||
bool HasStoredShader(const std::string& shaderId) const;
|
||||
bool TryGetStoredParameterById(const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const;
|
||||
bool TryGetStoredParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const;
|
||||
bool ResolveStoredLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
|
||||
bool ResolveStoredLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
|
||||
bool IsValidStackPresetName(const std::string& presetName) const;
|
||||
double GetRuntimeElapsedSeconds() const;
|
||||
|
||||
const std::filesystem::path& GetRuntimeRepositoryRoot() const;
|
||||
const std::filesystem::path& GetRuntimeUiRoot() const;
|
||||
const std::filesystem::path& GetRuntimeDocsRoot() const;
|
||||
const std::filesystem::path& GetRuntimeDataRoot() const;
|
||||
unsigned short GetConfiguredControlServerPort() const;
|
||||
unsigned short GetConfiguredOscPort() const;
|
||||
const std::string& GetConfiguredOscBindAddress() const;
|
||||
double GetConfiguredOscSmoothing() const;
|
||||
unsigned GetConfiguredMaxTemporalHistoryFrames() const;
|
||||
unsigned GetConfiguredPreviewFps() const;
|
||||
bool IsExternalKeyingConfigured() const;
|
||||
const std::string& GetConfiguredInputVideoFormat() const;
|
||||
const std::string& GetConfiguredInputFrameRate() const;
|
||||
const std::string& GetConfiguredOutputVideoFormat() const;
|
||||
const std::string& GetConfiguredOutputFrameRate() const;
|
||||
void SetBoundControlServerPort(unsigned short port);
|
||||
|
||||
void SetCompileStatus(bool succeeded, const std::string& message);
|
||||
void ClearReloadRequest();
|
||||
bool CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const;
|
||||
::ShaderCompilerInputs GetShaderCompilerInputs() const;
|
||||
::CommittedLiveStateReadModel BuildCommittedLiveStateReadModel() const;
|
||||
::RenderSnapshotReadModel BuildRenderSnapshotReadModel() const;
|
||||
std::vector<LayerPersistentState> CopyCommittedLiveLayerStates() const;
|
||||
std::vector<LayerPersistentState> CopyLayerStates() const;
|
||||
::RenderTimingSnapshot GetRenderTimingSnapshot() const;
|
||||
::RuntimeStatePresentationReadModel BuildRuntimeStatePresentationReadModel() const;
|
||||
|
||||
private:
|
||||
bool LoadPersistentState(std::string& error);
|
||||
PersistenceSnapshot BuildRuntimeStatePersistenceSnapshotLocked(const PersistenceRequest& request) const;
|
||||
PersistenceSnapshot BuildStackPresetPersistenceSnapshot(const std::string& presetName) const;
|
||||
bool ScanShaderPackages(std::string& error);
|
||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
||||
std::vector<std::string> GetStackPresetNamesLocked() const;
|
||||
void MarkRenderStateDirtyLocked();
|
||||
void MarkParameterStateDirtyLocked();
|
||||
|
||||
RenderSnapshotBuilder mRenderSnapshotBuilder;
|
||||
RuntimeConfigStore mConfigStore;
|
||||
ShaderPackageCatalog mShaderCatalog;
|
||||
CommittedLiveState mCommittedLiveState;
|
||||
HealthTelemetry mHealthTelemetry;
|
||||
mutable PersistenceWriter mPersistenceWriter;
|
||||
mutable std::mutex mMutex;
|
||||
bool mReloadRequested;
|
||||
bool mCompileSucceeded;
|
||||
std::string mCompileMessage;
|
||||
double mStartupRandom;
|
||||
unsigned short mServerPort;
|
||||
bool mAutoReloadEnabled;
|
||||
std::chrono::steady_clock::time_point mStartTime;
|
||||
std::chrono::steady_clock::time_point mLastScanTime;
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "HealthTelemetry.h"
|
||||
#include "LayerStackStore.h"
|
||||
#include "RuntimeConfigStore.h"
|
||||
#include "ShaderPackageCatalog.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct ShaderCompilerInputs
|
||||
{
|
||||
std::filesystem::path repoRoot;
|
||||
std::filesystem::path wrapperPath;
|
||||
std::filesystem::path generatedGlslPath;
|
||||
std::filesystem::path patchedGlslPath;
|
||||
unsigned maxTemporalHistoryFrames = 0;
|
||||
};
|
||||
|
||||
struct RenderTimingSnapshot
|
||||
{
|
||||
std::chrono::steady_clock::time_point startTime;
|
||||
double startupRandom = 0.0;
|
||||
};
|
||||
|
||||
struct CommittedLiveStateReadModel
|
||||
{
|
||||
std::vector<LayerStackStore::LayerPersistentState> layers;
|
||||
std::map<std::string, ShaderPackage> packagesById;
|
||||
};
|
||||
|
||||
struct RenderSnapshotReadModel
|
||||
{
|
||||
CommittedLiveStateReadModel committedLiveState;
|
||||
HealthTelemetry::SignalStatusSnapshot signalStatus;
|
||||
RenderTimingSnapshot timing;
|
||||
};
|
||||
|
||||
struct RuntimeStatePresentationReadModel
|
||||
{
|
||||
RuntimeConfigStore::AppConfig config;
|
||||
HealthTelemetry::Snapshot telemetry;
|
||||
LayerStackStore layerStack;
|
||||
ShaderPackageCatalog::Snapshot shaderCatalog;
|
||||
std::vector<ShaderPackageStatus> packageStatuses;
|
||||
std::vector<std::string> stackPresetNames;
|
||||
unsigned short serverPort = 0;
|
||||
bool autoReloadEnabled = false;
|
||||
bool compileSucceeded = false;
|
||||
std::string compileMessage;
|
||||
};
|
||||
@@ -0,0 +1,127 @@
|
||||
#include "ShaderPackageCatalog.h"
|
||||
|
||||
#include "ShaderPackageRegistry.h"
|
||||
|
||||
bool ShaderPackageCatalog::Scan(const std::filesystem::path& shaderRoot, unsigned maxTemporalHistoryFrames, std::string& error)
|
||||
{
|
||||
std::map<std::string, ShaderPackage> packagesById;
|
||||
std::vector<std::string> packageOrder;
|
||||
std::vector<ShaderPackageStatus> packageStatuses;
|
||||
|
||||
ShaderPackageRegistry registry(maxTemporalHistoryFrames);
|
||||
if (!registry.Scan(shaderRoot, packagesById, packageOrder, packageStatuses, error))
|
||||
return false;
|
||||
|
||||
mPackagesById.swap(packagesById);
|
||||
mPackageOrder.swap(packageOrder);
|
||||
mPackageStatuses.swap(packageStatuses);
|
||||
return true;
|
||||
}
|
||||
|
||||
ShaderPackageCatalog::Snapshot ShaderPackageCatalog::CaptureSnapshot() const
|
||||
{
|
||||
Snapshot snapshot;
|
||||
snapshot.packagesById = mPackagesById;
|
||||
snapshot.packageOrder = mPackageOrder;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
bool ShaderPackageCatalog::HasCatalogChangedSince(const Snapshot& snapshot) const
|
||||
{
|
||||
if (snapshot.packageOrder != mPackageOrder || snapshot.packagesById.size() != mPackagesById.size())
|
||||
return true;
|
||||
|
||||
for (const auto& item : mPackagesById)
|
||||
{
|
||||
auto previous = snapshot.packagesById.find(item.first);
|
||||
if (previous == snapshot.packagesById.end() || !PackagesEquivalent(previous->second, item.second))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ShaderPackageCatalog::HasPackageChangedSince(const Snapshot& snapshot, const std::string& shaderId) const
|
||||
{
|
||||
auto previous = snapshot.packagesById.find(shaderId);
|
||||
auto current = mPackagesById.find(shaderId);
|
||||
if (previous == snapshot.packagesById.end() || current == mPackagesById.end())
|
||||
return previous != snapshot.packagesById.end() || current != mPackagesById.end();
|
||||
|
||||
return !PackagesEquivalent(previous->second, current->second);
|
||||
}
|
||||
|
||||
bool ShaderPackageCatalog::HasPackage(const std::string& shaderId) const
|
||||
{
|
||||
return mPackagesById.find(shaderId) != mPackagesById.end();
|
||||
}
|
||||
|
||||
const ShaderPackage* ShaderPackageCatalog::FindPackage(const std::string& shaderId) const
|
||||
{
|
||||
auto it = mPackagesById.find(shaderId);
|
||||
return it == mPackagesById.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
bool ShaderPackageCatalog::CopyPackage(const std::string& shaderId, ShaderPackage& shaderPackage) const
|
||||
{
|
||||
const ShaderPackage* package = FindPackage(shaderId);
|
||||
if (!package)
|
||||
return false;
|
||||
|
||||
shaderPackage = *package;
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& ShaderPackageCatalog::PackageOrder() const
|
||||
{
|
||||
return mPackageOrder;
|
||||
}
|
||||
|
||||
const std::vector<ShaderPackageStatus>& ShaderPackageCatalog::PackageStatuses() const
|
||||
{
|
||||
return mPackageStatuses;
|
||||
}
|
||||
|
||||
bool ShaderPackageCatalog::PackagesEquivalent(const ShaderPackage& left, const ShaderPackage& right)
|
||||
{
|
||||
return left.shaderWriteTime == right.shaderWriteTime &&
|
||||
left.manifestWriteTime == right.manifestWriteTime &&
|
||||
TextureAssetsEqual(left.textureAssets, right.textureAssets) &&
|
||||
FontAssetsEqual(left.fontAssets, right.fontAssets);
|
||||
}
|
||||
|
||||
bool ShaderPackageCatalog::TextureAssetsEqual(const std::vector<ShaderTextureAsset>& left, const std::vector<ShaderTextureAsset>& right)
|
||||
{
|
||||
if (left.size() != right.size())
|
||||
return false;
|
||||
|
||||
for (std::size_t index = 0; index < left.size(); ++index)
|
||||
{
|
||||
if (left[index].id != right[index].id ||
|
||||
left[index].path != right[index].path ||
|
||||
left[index].writeTime != right[index].writeTime)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderPackageCatalog::FontAssetsEqual(const std::vector<ShaderFontAsset>& left, const std::vector<ShaderFontAsset>& right)
|
||||
{
|
||||
if (left.size() != right.size())
|
||||
return false;
|
||||
|
||||
for (std::size_t index = 0; index < left.size(); ++index)
|
||||
{
|
||||
if (left[index].id != right[index].id ||
|
||||
left[index].path != right[index].path ||
|
||||
left[index].writeTime != right[index].writeTime)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class ShaderPackageCatalog
|
||||
{
|
||||
public:
|
||||
struct Snapshot
|
||||
{
|
||||
std::map<std::string, ShaderPackage> packagesById;
|
||||
std::vector<std::string> packageOrder;
|
||||
};
|
||||
|
||||
bool Scan(const std::filesystem::path& shaderRoot, unsigned maxTemporalHistoryFrames, std::string& error);
|
||||
Snapshot CaptureSnapshot() const;
|
||||
bool HasCatalogChangedSince(const Snapshot& snapshot) const;
|
||||
bool HasPackageChangedSince(const Snapshot& snapshot, const std::string& shaderId) const;
|
||||
bool HasPackage(const std::string& shaderId) const;
|
||||
const ShaderPackage* FindPackage(const std::string& shaderId) const;
|
||||
bool CopyPackage(const std::string& shaderId, ShaderPackage& shaderPackage) const;
|
||||
const std::vector<std::string>& PackageOrder() const;
|
||||
const std::vector<ShaderPackageStatus>& PackageStatuses() const;
|
||||
|
||||
private:
|
||||
static bool PackagesEquivalent(const ShaderPackage& left, const ShaderPackage& right);
|
||||
static bool TextureAssetsEqual(const std::vector<ShaderTextureAsset>& left, const std::vector<ShaderTextureAsset>& right);
|
||||
static bool FontAssetsEqual(const std::vector<ShaderFontAsset>& left, const std::vector<ShaderFontAsset>& right);
|
||||
|
||||
std::map<std::string, ShaderPackage> mPackagesById;
|
||||
std::vector<std::string> mPackageOrder;
|
||||
std::vector<ShaderPackageStatus> mPackageStatuses;
|
||||
};
|
||||
@@ -0,0 +1,500 @@
|
||||
#include "stdafx.h"
|
||||
#include "HealthTelemetry.h"
|
||||
|
||||
void HealthTelemetry::ReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mSignalStatus.hasSignal = hasSignal;
|
||||
mSignalStatus.width = width;
|
||||
mSignalStatus.height = height;
|
||||
mSignalStatus.modeName = modeName;
|
||||
}
|
||||
|
||||
bool HealthTelemetry::TryReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
mSignalStatus.hasSignal = hasSignal;
|
||||
mSignalStatus.width = width;
|
||||
mSignalStatus.height = height;
|
||||
mSignalStatus.modeName = modeName;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HealthTelemetry::ReportVideoIOStatus(const std::string& backendName, const std::string& modelName,
|
||||
bool supportsInternalKeying, bool supportsExternalKeying, bool keyerInterfaceAvailable,
|
||||
bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mVideoIOStatus.backendName = backendName;
|
||||
mVideoIOStatus.modelName = modelName;
|
||||
mVideoIOStatus.supportsInternalKeying = supportsInternalKeying;
|
||||
mVideoIOStatus.supportsExternalKeying = supportsExternalKeying;
|
||||
mVideoIOStatus.keyerInterfaceAvailable = keyerInterfaceAvailable;
|
||||
mVideoIOStatus.externalKeyingRequested = externalKeyingRequested;
|
||||
mVideoIOStatus.externalKeyingActive = externalKeyingActive;
|
||||
mVideoIOStatus.statusMessage = statusMessage;
|
||||
}
|
||||
|
||||
bool HealthTelemetry::TryReportVideoIOStatus(const std::string& backendName, const std::string& modelName,
|
||||
bool supportsInternalKeying, bool supportsExternalKeying, bool keyerInterfaceAvailable,
|
||||
bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
mVideoIOStatus.backendName = backendName;
|
||||
mVideoIOStatus.modelName = modelName;
|
||||
mVideoIOStatus.supportsInternalKeying = supportsInternalKeying;
|
||||
mVideoIOStatus.supportsExternalKeying = supportsExternalKeying;
|
||||
mVideoIOStatus.keyerInterfaceAvailable = keyerInterfaceAvailable;
|
||||
mVideoIOStatus.externalKeyingRequested = externalKeyingRequested;
|
||||
mVideoIOStatus.externalKeyingActive = externalKeyingActive;
|
||||
mVideoIOStatus.statusMessage = statusMessage;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HealthTelemetry::RecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mPerformance.frameBudgetMilliseconds = std::max(frameBudgetMilliseconds, 0.0);
|
||||
mPerformance.renderMilliseconds = std::max(renderMilliseconds, 0.0);
|
||||
if (mPerformance.smoothedRenderMilliseconds <= 0.0)
|
||||
mPerformance.smoothedRenderMilliseconds = mPerformance.renderMilliseconds;
|
||||
else
|
||||
mPerformance.smoothedRenderMilliseconds = mPerformance.smoothedRenderMilliseconds * 0.9 + mPerformance.renderMilliseconds * 0.1;
|
||||
}
|
||||
|
||||
bool HealthTelemetry::TryRecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
mPerformance.frameBudgetMilliseconds = std::max(frameBudgetMilliseconds, 0.0);
|
||||
mPerformance.renderMilliseconds = std::max(renderMilliseconds, 0.0);
|
||||
if (mPerformance.smoothedRenderMilliseconds <= 0.0)
|
||||
mPerformance.smoothedRenderMilliseconds = mPerformance.renderMilliseconds;
|
||||
else
|
||||
mPerformance.smoothedRenderMilliseconds = mPerformance.smoothedRenderMilliseconds * 0.9 + mPerformance.renderMilliseconds * 0.1;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HealthTelemetry::RecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mPerformance.completionIntervalMilliseconds = std::max(completionIntervalMilliseconds, 0.0);
|
||||
mPerformance.smoothedCompletionIntervalMilliseconds = std::max(smoothedCompletionIntervalMilliseconds, 0.0);
|
||||
mPerformance.maxCompletionIntervalMilliseconds = std::max(maxCompletionIntervalMilliseconds, 0.0);
|
||||
mPerformance.lateFrameCount = lateFrameCount;
|
||||
mPerformance.droppedFrameCount = droppedFrameCount;
|
||||
mPerformance.flushedFrameCount = flushedFrameCount;
|
||||
}
|
||||
|
||||
bool HealthTelemetry::TryRecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
mPerformance.completionIntervalMilliseconds = std::max(completionIntervalMilliseconds, 0.0);
|
||||
mPerformance.smoothedCompletionIntervalMilliseconds = std::max(smoothedCompletionIntervalMilliseconds, 0.0);
|
||||
mPerformance.maxCompletionIntervalMilliseconds = std::max(maxCompletionIntervalMilliseconds, 0.0);
|
||||
mPerformance.lateFrameCount = lateFrameCount;
|
||||
mPerformance.droppedFrameCount = droppedFrameCount;
|
||||
mPerformance.flushedFrameCount = flushedFrameCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HealthTelemetry::RecordRuntimeEventQueueMetrics(const std::string& queueName, std::size_t depth, std::size_t capacity,
|
||||
uint64_t droppedCount, double oldestEventAgeMilliseconds)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mRuntimeEvents.queue.queueName = queueName;
|
||||
mRuntimeEvents.queue.depth = depth;
|
||||
mRuntimeEvents.queue.capacity = capacity;
|
||||
mRuntimeEvents.queue.droppedCount = droppedCount;
|
||||
mRuntimeEvents.queue.oldestEventAgeMilliseconds = std::max(oldestEventAgeMilliseconds, 0.0);
|
||||
}
|
||||
|
||||
bool HealthTelemetry::TryRecordRuntimeEventQueueMetrics(const std::string& queueName, std::size_t depth, std::size_t capacity,
|
||||
uint64_t droppedCount, double oldestEventAgeMilliseconds)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
mRuntimeEvents.queue.queueName = queueName;
|
||||
mRuntimeEvents.queue.depth = depth;
|
||||
mRuntimeEvents.queue.capacity = capacity;
|
||||
mRuntimeEvents.queue.droppedCount = droppedCount;
|
||||
mRuntimeEvents.queue.oldestEventAgeMilliseconds = std::max(oldestEventAgeMilliseconds, 0.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HealthTelemetry::RecordRuntimeEventDispatchStats(std::size_t dispatchedEvents, std::size_t handlerInvocations,
|
||||
std::size_t handlerFailures, double dispatchDurationMilliseconds)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
++mRuntimeEvents.dispatch.dispatchCallCount;
|
||||
mRuntimeEvents.dispatch.dispatchedEventCount += static_cast<uint64_t>(dispatchedEvents);
|
||||
mRuntimeEvents.dispatch.handlerInvocationCount += static_cast<uint64_t>(handlerInvocations);
|
||||
mRuntimeEvents.dispatch.handlerFailureCount += static_cast<uint64_t>(handlerFailures);
|
||||
mRuntimeEvents.dispatch.lastDispatchDurationMilliseconds = std::max(dispatchDurationMilliseconds, 0.0);
|
||||
mRuntimeEvents.dispatch.maxDispatchDurationMilliseconds = std::max(
|
||||
mRuntimeEvents.dispatch.maxDispatchDurationMilliseconds,
|
||||
mRuntimeEvents.dispatch.lastDispatchDurationMilliseconds);
|
||||
}
|
||||
|
||||
bool HealthTelemetry::TryRecordRuntimeEventDispatchStats(std::size_t dispatchedEvents, std::size_t handlerInvocations,
|
||||
std::size_t handlerFailures, double dispatchDurationMilliseconds)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
++mRuntimeEvents.dispatch.dispatchCallCount;
|
||||
mRuntimeEvents.dispatch.dispatchedEventCount += static_cast<uint64_t>(dispatchedEvents);
|
||||
mRuntimeEvents.dispatch.handlerInvocationCount += static_cast<uint64_t>(handlerInvocations);
|
||||
mRuntimeEvents.dispatch.handlerFailureCount += static_cast<uint64_t>(handlerFailures);
|
||||
mRuntimeEvents.dispatch.lastDispatchDurationMilliseconds = std::max(dispatchDurationMilliseconds, 0.0);
|
||||
mRuntimeEvents.dispatch.maxDispatchDurationMilliseconds = std::max(
|
||||
mRuntimeEvents.dispatch.maxDispatchDurationMilliseconds,
|
||||
mRuntimeEvents.dispatch.lastDispatchDurationMilliseconds);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HealthTelemetry::RecordPersistenceWriteResult(bool succeeded, const std::string& targetKind, const std::string& targetPath,
|
||||
const std::string& reason, const std::string& errorMessage, bool newerRequestPending)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (succeeded)
|
||||
++mPersistence.writeSuccessCount;
|
||||
else
|
||||
++mPersistence.writeFailureCount;
|
||||
mPersistence.lastWriteSucceeded = succeeded;
|
||||
mPersistence.unsavedChanges = !succeeded || newerRequestPending;
|
||||
mPersistence.newerRequestPending = newerRequestPending;
|
||||
mPersistence.lastTargetKind = targetKind;
|
||||
mPersistence.lastTargetPath = targetPath;
|
||||
mPersistence.lastReason = reason;
|
||||
mPersistence.lastErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
bool HealthTelemetry::TryRecordPersistenceWriteResult(bool succeeded, const std::string& targetKind, const std::string& targetPath,
|
||||
const std::string& reason, const std::string& errorMessage, bool newerRequestPending)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
if (succeeded)
|
||||
++mPersistence.writeSuccessCount;
|
||||
else
|
||||
++mPersistence.writeFailureCount;
|
||||
mPersistence.lastWriteSucceeded = succeeded;
|
||||
mPersistence.unsavedChanges = !succeeded || newerRequestPending;
|
||||
mPersistence.newerRequestPending = newerRequestPending;
|
||||
mPersistence.lastTargetKind = targetKind;
|
||||
mPersistence.lastTargetPath = targetPath;
|
||||
mPersistence.lastReason = reason;
|
||||
mPersistence.lastErrorMessage = errorMessage;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HealthTelemetry::RecordBackendPlayoutHealth(const std::string& lifecycleState, const std::string& completionResult,
|
||||
std::size_t readyQueueDepth, std::size_t readyQueueCapacity, uint64_t readyQueuePushedCount,
|
||||
std::size_t minReadyQueueDepth, std::size_t maxReadyQueueDepth, uint64_t readyQueueZeroDepthCount,
|
||||
uint64_t readyQueuePoppedCount, uint64_t readyQueueDroppedCount, uint64_t readyQueueUnderrunCount,
|
||||
double outputRenderMilliseconds, double smoothedOutputRenderMilliseconds, double maxOutputRenderMilliseconds,
|
||||
double outputFrameAcquireMilliseconds, double outputFrameRenderRequestMilliseconds, double outputFrameEndAccessMilliseconds,
|
||||
uint64_t completedFrameIndex, uint64_t scheduledFrameIndex, uint64_t scheduledLeadFrames,
|
||||
uint64_t measuredLagFrames, uint64_t catchUpFrames, uint64_t lateStreak, uint64_t dropStreak,
|
||||
uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount,
|
||||
bool degraded, const std::string& statusMessage)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mBackendPlayout.lifecycleState = lifecycleState;
|
||||
mBackendPlayout.completionResult = completionResult;
|
||||
mBackendPlayout.readyQueueDepth = readyQueueDepth;
|
||||
mBackendPlayout.readyQueueCapacity = readyQueueCapacity;
|
||||
mBackendPlayout.minReadyQueueDepth = minReadyQueueDepth;
|
||||
mBackendPlayout.maxReadyQueueDepth = maxReadyQueueDepth;
|
||||
mBackendPlayout.readyQueueZeroDepthCount = readyQueueZeroDepthCount;
|
||||
mBackendPlayout.readyQueuePushedCount = readyQueuePushedCount;
|
||||
mBackendPlayout.readyQueuePoppedCount = readyQueuePoppedCount;
|
||||
mBackendPlayout.readyQueueDroppedCount = readyQueueDroppedCount;
|
||||
mBackendPlayout.readyQueueUnderrunCount = readyQueueUnderrunCount;
|
||||
mBackendPlayout.outputRenderMilliseconds = std::max(outputRenderMilliseconds, 0.0);
|
||||
mBackendPlayout.smoothedOutputRenderMilliseconds = std::max(smoothedOutputRenderMilliseconds, 0.0);
|
||||
mBackendPlayout.maxOutputRenderMilliseconds = std::max(maxOutputRenderMilliseconds, 0.0);
|
||||
mBackendPlayout.outputFrameAcquireMilliseconds = std::max(outputFrameAcquireMilliseconds, 0.0);
|
||||
mBackendPlayout.outputFrameRenderRequestMilliseconds = std::max(outputFrameRenderRequestMilliseconds, 0.0);
|
||||
mBackendPlayout.outputFrameEndAccessMilliseconds = std::max(outputFrameEndAccessMilliseconds, 0.0);
|
||||
mBackendPlayout.completedFrameIndex = completedFrameIndex;
|
||||
mBackendPlayout.scheduledFrameIndex = scheduledFrameIndex;
|
||||
mBackendPlayout.scheduledLeadFrames = scheduledLeadFrames;
|
||||
mBackendPlayout.measuredLagFrames = measuredLagFrames;
|
||||
mBackendPlayout.catchUpFrames = catchUpFrames;
|
||||
mBackendPlayout.lateStreak = lateStreak;
|
||||
mBackendPlayout.dropStreak = dropStreak;
|
||||
mBackendPlayout.lateFrameCount = lateFrameCount;
|
||||
mBackendPlayout.droppedFrameCount = droppedFrameCount;
|
||||
mBackendPlayout.flushedFrameCount = flushedFrameCount;
|
||||
mBackendPlayout.degraded = degraded;
|
||||
mBackendPlayout.statusMessage = statusMessage;
|
||||
}
|
||||
|
||||
bool HealthTelemetry::TryRecordBackendPlayoutHealth(const std::string& lifecycleState, const std::string& completionResult,
|
||||
std::size_t readyQueueDepth, std::size_t readyQueueCapacity, uint64_t readyQueuePushedCount,
|
||||
std::size_t minReadyQueueDepth, std::size_t maxReadyQueueDepth, uint64_t readyQueueZeroDepthCount,
|
||||
uint64_t readyQueuePoppedCount, uint64_t readyQueueDroppedCount, uint64_t readyQueueUnderrunCount,
|
||||
double outputRenderMilliseconds, double smoothedOutputRenderMilliseconds, double maxOutputRenderMilliseconds,
|
||||
double outputFrameAcquireMilliseconds, double outputFrameRenderRequestMilliseconds, double outputFrameEndAccessMilliseconds,
|
||||
uint64_t completedFrameIndex, uint64_t scheduledFrameIndex, uint64_t scheduledLeadFrames,
|
||||
uint64_t measuredLagFrames, uint64_t catchUpFrames, uint64_t lateStreak, uint64_t dropStreak,
|
||||
uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount,
|
||||
bool degraded, const std::string& statusMessage)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
mBackendPlayout.lifecycleState = lifecycleState;
|
||||
mBackendPlayout.completionResult = completionResult;
|
||||
mBackendPlayout.readyQueueDepth = readyQueueDepth;
|
||||
mBackendPlayout.readyQueueCapacity = readyQueueCapacity;
|
||||
mBackendPlayout.minReadyQueueDepth = minReadyQueueDepth;
|
||||
mBackendPlayout.maxReadyQueueDepth = maxReadyQueueDepth;
|
||||
mBackendPlayout.readyQueueZeroDepthCount = readyQueueZeroDepthCount;
|
||||
mBackendPlayout.readyQueuePushedCount = readyQueuePushedCount;
|
||||
mBackendPlayout.readyQueuePoppedCount = readyQueuePoppedCount;
|
||||
mBackendPlayout.readyQueueDroppedCount = readyQueueDroppedCount;
|
||||
mBackendPlayout.readyQueueUnderrunCount = readyQueueUnderrunCount;
|
||||
mBackendPlayout.outputRenderMilliseconds = std::max(outputRenderMilliseconds, 0.0);
|
||||
mBackendPlayout.smoothedOutputRenderMilliseconds = std::max(smoothedOutputRenderMilliseconds, 0.0);
|
||||
mBackendPlayout.maxOutputRenderMilliseconds = std::max(maxOutputRenderMilliseconds, 0.0);
|
||||
mBackendPlayout.outputFrameAcquireMilliseconds = std::max(outputFrameAcquireMilliseconds, 0.0);
|
||||
mBackendPlayout.outputFrameRenderRequestMilliseconds = std::max(outputFrameRenderRequestMilliseconds, 0.0);
|
||||
mBackendPlayout.outputFrameEndAccessMilliseconds = std::max(outputFrameEndAccessMilliseconds, 0.0);
|
||||
mBackendPlayout.completedFrameIndex = completedFrameIndex;
|
||||
mBackendPlayout.scheduledFrameIndex = scheduledFrameIndex;
|
||||
mBackendPlayout.scheduledLeadFrames = scheduledLeadFrames;
|
||||
mBackendPlayout.measuredLagFrames = measuredLagFrames;
|
||||
mBackendPlayout.catchUpFrames = catchUpFrames;
|
||||
mBackendPlayout.lateStreak = lateStreak;
|
||||
mBackendPlayout.dropStreak = dropStreak;
|
||||
mBackendPlayout.lateFrameCount = lateFrameCount;
|
||||
mBackendPlayout.droppedFrameCount = droppedFrameCount;
|
||||
mBackendPlayout.flushedFrameCount = flushedFrameCount;
|
||||
mBackendPlayout.degraded = degraded;
|
||||
mBackendPlayout.statusMessage = statusMessage;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HealthTelemetry::RecordOutputRenderQueueWait(double queueWaitMilliseconds)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mBackendPlayout.outputRenderQueueWaitMilliseconds = std::max(queueWaitMilliseconds, 0.0);
|
||||
}
|
||||
|
||||
bool HealthTelemetry::TryRecordOutputRenderQueueWait(double queueWaitMilliseconds)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
mBackendPlayout.outputRenderQueueWaitMilliseconds = std::max(queueWaitMilliseconds, 0.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HealthTelemetry::RecordSystemMemoryPlayoutStats(std::size_t freeFrameCount, std::size_t readyFrameCount,
|
||||
std::size_t scheduledFrameCount, uint64_t underrunCount, uint64_t repeatCount, uint64_t dropCount,
|
||||
double frameAgeAtScheduleMilliseconds, double frameAgeAtCompletionMilliseconds)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mBackendPlayout.systemFramePoolFree = freeFrameCount;
|
||||
mBackendPlayout.systemFramePoolReady = readyFrameCount;
|
||||
mBackendPlayout.systemFramePoolScheduled = scheduledFrameCount;
|
||||
mBackendPlayout.systemFrameUnderrunCount = underrunCount;
|
||||
mBackendPlayout.systemFrameRepeatCount = repeatCount;
|
||||
mBackendPlayout.systemFrameDropCount = dropCount;
|
||||
mBackendPlayout.systemFrameAgeAtScheduleMilliseconds = std::max(frameAgeAtScheduleMilliseconds, 0.0);
|
||||
mBackendPlayout.systemFrameAgeAtCompletionMilliseconds = std::max(frameAgeAtCompletionMilliseconds, 0.0);
|
||||
}
|
||||
|
||||
bool HealthTelemetry::TryRecordSystemMemoryPlayoutStats(std::size_t freeFrameCount, std::size_t readyFrameCount,
|
||||
std::size_t scheduledFrameCount, uint64_t underrunCount, uint64_t repeatCount, uint64_t dropCount,
|
||||
double frameAgeAtScheduleMilliseconds, double frameAgeAtCompletionMilliseconds)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
mBackendPlayout.systemFramePoolFree = freeFrameCount;
|
||||
mBackendPlayout.systemFramePoolReady = readyFrameCount;
|
||||
mBackendPlayout.systemFramePoolScheduled = scheduledFrameCount;
|
||||
mBackendPlayout.systemFrameUnderrunCount = underrunCount;
|
||||
mBackendPlayout.systemFrameRepeatCount = repeatCount;
|
||||
mBackendPlayout.systemFrameDropCount = dropCount;
|
||||
mBackendPlayout.systemFrameAgeAtScheduleMilliseconds = std::max(frameAgeAtScheduleMilliseconds, 0.0);
|
||||
mBackendPlayout.systemFrameAgeAtCompletionMilliseconds = std::max(frameAgeAtCompletionMilliseconds, 0.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HealthTelemetry::RecordDeckLinkBufferTelemetry(bool actualBufferedFramesAvailable, uint64_t actualBufferedFrames,
|
||||
std::size_t targetBufferedFrames, double scheduleCallMilliseconds, uint64_t scheduleFailureCount)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mBackendPlayout.actualDeckLinkBufferedFramesAvailable = actualBufferedFramesAvailable;
|
||||
mBackendPlayout.actualDeckLinkBufferedFrames = actualBufferedFramesAvailable ? actualBufferedFrames : 0;
|
||||
mBackendPlayout.targetDeckLinkBufferedFrames = targetBufferedFrames;
|
||||
mBackendPlayout.deckLinkScheduleCallMilliseconds = std::max(scheduleCallMilliseconds, 0.0);
|
||||
mBackendPlayout.deckLinkScheduleFailureCount = scheduleFailureCount;
|
||||
}
|
||||
|
||||
bool HealthTelemetry::TryRecordDeckLinkBufferTelemetry(bool actualBufferedFramesAvailable, uint64_t actualBufferedFrames,
|
||||
std::size_t targetBufferedFrames, double scheduleCallMilliseconds, uint64_t scheduleFailureCount)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
mBackendPlayout.actualDeckLinkBufferedFramesAvailable = actualBufferedFramesAvailable;
|
||||
mBackendPlayout.actualDeckLinkBufferedFrames = actualBufferedFramesAvailable ? actualBufferedFrames : 0;
|
||||
mBackendPlayout.targetDeckLinkBufferedFrames = targetBufferedFrames;
|
||||
mBackendPlayout.deckLinkScheduleCallMilliseconds = std::max(scheduleCallMilliseconds, 0.0);
|
||||
mBackendPlayout.deckLinkScheduleFailureCount = scheduleFailureCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HealthTelemetry::RecordOutputRenderPipelineTiming(
|
||||
double drawMilliseconds,
|
||||
double fenceWaitMilliseconds,
|
||||
double mapMilliseconds,
|
||||
double readbackCopyMilliseconds,
|
||||
double cachedCopyMilliseconds,
|
||||
double asyncQueueMilliseconds,
|
||||
double asyncQueueBufferMilliseconds,
|
||||
double asyncQueueSetupMilliseconds,
|
||||
double asyncQueueReadPixelsMilliseconds,
|
||||
double asyncQueueFenceMilliseconds,
|
||||
double syncReadMilliseconds,
|
||||
bool asyncReadbackMissed,
|
||||
bool cachedFallbackUsed,
|
||||
bool syncFallbackUsed)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mBackendPlayout.outputRenderDrawMilliseconds = std::max(drawMilliseconds, 0.0);
|
||||
mBackendPlayout.outputReadbackFenceWaitMilliseconds = std::max(fenceWaitMilliseconds, 0.0);
|
||||
mBackendPlayout.outputReadbackMapMilliseconds = std::max(mapMilliseconds, 0.0);
|
||||
mBackendPlayout.outputReadbackCopyMilliseconds = std::max(readbackCopyMilliseconds, 0.0);
|
||||
mBackendPlayout.outputCachedCopyMilliseconds = std::max(cachedCopyMilliseconds, 0.0);
|
||||
mBackendPlayout.outputAsyncQueueMilliseconds = std::max(asyncQueueMilliseconds, 0.0);
|
||||
mBackendPlayout.outputAsyncQueueBufferMilliseconds = std::max(asyncQueueBufferMilliseconds, 0.0);
|
||||
mBackendPlayout.outputAsyncQueueSetupMilliseconds = std::max(asyncQueueSetupMilliseconds, 0.0);
|
||||
mBackendPlayout.outputAsyncQueueReadPixelsMilliseconds = std::max(asyncQueueReadPixelsMilliseconds, 0.0);
|
||||
mBackendPlayout.outputAsyncQueueFenceMilliseconds = std::max(asyncQueueFenceMilliseconds, 0.0);
|
||||
mBackendPlayout.outputSyncReadMilliseconds = std::max(syncReadMilliseconds, 0.0);
|
||||
if (asyncReadbackMissed)
|
||||
++mBackendPlayout.outputAsyncReadbackMissCount;
|
||||
if (cachedFallbackUsed)
|
||||
++mBackendPlayout.outputCachedFallbackCount;
|
||||
if (syncFallbackUsed)
|
||||
++mBackendPlayout.outputSyncFallbackCount;
|
||||
}
|
||||
|
||||
bool HealthTelemetry::TryRecordOutputRenderPipelineTiming(
|
||||
double drawMilliseconds,
|
||||
double fenceWaitMilliseconds,
|
||||
double mapMilliseconds,
|
||||
double readbackCopyMilliseconds,
|
||||
double cachedCopyMilliseconds,
|
||||
double asyncQueueMilliseconds,
|
||||
double asyncQueueBufferMilliseconds,
|
||||
double asyncQueueSetupMilliseconds,
|
||||
double asyncQueueReadPixelsMilliseconds,
|
||||
double asyncQueueFenceMilliseconds,
|
||||
double syncReadMilliseconds,
|
||||
bool asyncReadbackMissed,
|
||||
bool cachedFallbackUsed,
|
||||
bool syncFallbackUsed)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
mBackendPlayout.outputRenderDrawMilliseconds = std::max(drawMilliseconds, 0.0);
|
||||
mBackendPlayout.outputReadbackFenceWaitMilliseconds = std::max(fenceWaitMilliseconds, 0.0);
|
||||
mBackendPlayout.outputReadbackMapMilliseconds = std::max(mapMilliseconds, 0.0);
|
||||
mBackendPlayout.outputReadbackCopyMilliseconds = std::max(readbackCopyMilliseconds, 0.0);
|
||||
mBackendPlayout.outputCachedCopyMilliseconds = std::max(cachedCopyMilliseconds, 0.0);
|
||||
mBackendPlayout.outputAsyncQueueMilliseconds = std::max(asyncQueueMilliseconds, 0.0);
|
||||
mBackendPlayout.outputAsyncQueueBufferMilliseconds = std::max(asyncQueueBufferMilliseconds, 0.0);
|
||||
mBackendPlayout.outputAsyncQueueSetupMilliseconds = std::max(asyncQueueSetupMilliseconds, 0.0);
|
||||
mBackendPlayout.outputAsyncQueueReadPixelsMilliseconds = std::max(asyncQueueReadPixelsMilliseconds, 0.0);
|
||||
mBackendPlayout.outputAsyncQueueFenceMilliseconds = std::max(asyncQueueFenceMilliseconds, 0.0);
|
||||
mBackendPlayout.outputSyncReadMilliseconds = std::max(syncReadMilliseconds, 0.0);
|
||||
if (asyncReadbackMissed)
|
||||
++mBackendPlayout.outputAsyncReadbackMissCount;
|
||||
if (cachedFallbackUsed)
|
||||
++mBackendPlayout.outputCachedFallbackCount;
|
||||
if (syncFallbackUsed)
|
||||
++mBackendPlayout.outputSyncFallbackCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
HealthTelemetry::SignalStatusSnapshot HealthTelemetry::GetSignalStatusSnapshot() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mSignalStatus;
|
||||
}
|
||||
|
||||
HealthTelemetry::VideoIOStatusSnapshot HealthTelemetry::GetVideoIOStatusSnapshot() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mVideoIOStatus;
|
||||
}
|
||||
|
||||
HealthTelemetry::PerformanceSnapshot HealthTelemetry::GetPerformanceSnapshot() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mPerformance;
|
||||
}
|
||||
|
||||
HealthTelemetry::RuntimeEventMetricsSnapshot HealthTelemetry::GetRuntimeEventMetricsSnapshot() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mRuntimeEvents;
|
||||
}
|
||||
|
||||
HealthTelemetry::PersistenceSnapshot HealthTelemetry::GetPersistenceSnapshot() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mPersistence;
|
||||
}
|
||||
|
||||
HealthTelemetry::BackendPlayoutSnapshot HealthTelemetry::GetBackendPlayoutSnapshot() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mBackendPlayout;
|
||||
}
|
||||
|
||||
HealthTelemetry::Snapshot HealthTelemetry::GetSnapshot() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
Snapshot snapshot;
|
||||
snapshot.signal = mSignalStatus;
|
||||
snapshot.videoIO = mVideoIOStatus;
|
||||
snapshot.performance = mPerformance;
|
||||
snapshot.runtimeEvents = mRuntimeEvents;
|
||||
snapshot.persistence = mPersistence;
|
||||
snapshot.backendPlayout = mBackendPlayout;
|
||||
return snapshot;
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
// HealthTelemetry owns the current operational status snapshot directly, so
|
||||
// callers can report health without sharing runtime-store state.
|
||||
class HealthTelemetry
|
||||
{
|
||||
public:
|
||||
struct SignalStatusSnapshot
|
||||
{
|
||||
bool hasSignal = false;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
std::string modeName;
|
||||
};
|
||||
|
||||
struct VideoIOStatusSnapshot
|
||||
{
|
||||
std::string backendName = "decklink";
|
||||
std::string modelName;
|
||||
bool supportsInternalKeying = false;
|
||||
bool supportsExternalKeying = false;
|
||||
bool keyerInterfaceAvailable = false;
|
||||
bool externalKeyingRequested = false;
|
||||
bool externalKeyingActive = false;
|
||||
std::string statusMessage;
|
||||
};
|
||||
|
||||
struct PerformanceSnapshot
|
||||
{
|
||||
double frameBudgetMilliseconds = 0.0;
|
||||
double renderMilliseconds = 0.0;
|
||||
double smoothedRenderMilliseconds = 0.0;
|
||||
double completionIntervalMilliseconds = 0.0;
|
||||
double smoothedCompletionIntervalMilliseconds = 0.0;
|
||||
double maxCompletionIntervalMilliseconds = 0.0;
|
||||
uint64_t lateFrameCount = 0;
|
||||
uint64_t droppedFrameCount = 0;
|
||||
uint64_t flushedFrameCount = 0;
|
||||
};
|
||||
|
||||
struct RuntimeEventQueueSnapshot
|
||||
{
|
||||
std::string queueName = "runtime-events";
|
||||
std::size_t depth = 0;
|
||||
std::size_t capacity = 0;
|
||||
uint64_t droppedCount = 0;
|
||||
double oldestEventAgeMilliseconds = 0.0;
|
||||
};
|
||||
|
||||
struct RuntimeEventDispatchSnapshot
|
||||
{
|
||||
uint64_t dispatchCallCount = 0;
|
||||
uint64_t dispatchedEventCount = 0;
|
||||
uint64_t handlerInvocationCount = 0;
|
||||
uint64_t handlerFailureCount = 0;
|
||||
double lastDispatchDurationMilliseconds = 0.0;
|
||||
double maxDispatchDurationMilliseconds = 0.0;
|
||||
};
|
||||
|
||||
struct RuntimeEventMetricsSnapshot
|
||||
{
|
||||
RuntimeEventQueueSnapshot queue;
|
||||
RuntimeEventDispatchSnapshot dispatch;
|
||||
};
|
||||
|
||||
struct PersistenceSnapshot
|
||||
{
|
||||
uint64_t writeSuccessCount = 0;
|
||||
uint64_t writeFailureCount = 0;
|
||||
bool lastWriteSucceeded = true;
|
||||
bool unsavedChanges = false;
|
||||
bool newerRequestPending = false;
|
||||
std::string lastTargetKind;
|
||||
std::string lastTargetPath;
|
||||
std::string lastReason;
|
||||
std::string lastErrorMessage;
|
||||
};
|
||||
|
||||
struct BackendPlayoutSnapshot
|
||||
{
|
||||
std::string lifecycleState = "NotStarted";
|
||||
std::string completionResult = "Unknown";
|
||||
std::size_t readyQueueDepth = 0;
|
||||
std::size_t readyQueueCapacity = 0;
|
||||
std::size_t minReadyQueueDepth = 0;
|
||||
std::size_t maxReadyQueueDepth = 0;
|
||||
uint64_t readyQueueZeroDepthCount = 0;
|
||||
uint64_t readyQueuePushedCount = 0;
|
||||
uint64_t readyQueuePoppedCount = 0;
|
||||
uint64_t readyQueueDroppedCount = 0;
|
||||
uint64_t readyQueueUnderrunCount = 0;
|
||||
std::size_t systemFramePoolFree = 0;
|
||||
std::size_t systemFramePoolReady = 0;
|
||||
std::size_t systemFramePoolScheduled = 0;
|
||||
uint64_t systemFrameUnderrunCount = 0;
|
||||
uint64_t systemFrameRepeatCount = 0;
|
||||
uint64_t systemFrameDropCount = 0;
|
||||
double systemFrameAgeAtScheduleMilliseconds = 0.0;
|
||||
double systemFrameAgeAtCompletionMilliseconds = 0.0;
|
||||
double outputRenderMilliseconds = 0.0;
|
||||
double smoothedOutputRenderMilliseconds = 0.0;
|
||||
double maxOutputRenderMilliseconds = 0.0;
|
||||
double outputFrameAcquireMilliseconds = 0.0;
|
||||
double outputFrameRenderRequestMilliseconds = 0.0;
|
||||
double outputFrameEndAccessMilliseconds = 0.0;
|
||||
double outputRenderQueueWaitMilliseconds = 0.0;
|
||||
double outputRenderDrawMilliseconds = 0.0;
|
||||
double outputReadbackFenceWaitMilliseconds = 0.0;
|
||||
double outputReadbackMapMilliseconds = 0.0;
|
||||
double outputReadbackCopyMilliseconds = 0.0;
|
||||
double outputCachedCopyMilliseconds = 0.0;
|
||||
double outputAsyncQueueMilliseconds = 0.0;
|
||||
double outputAsyncQueueBufferMilliseconds = 0.0;
|
||||
double outputAsyncQueueSetupMilliseconds = 0.0;
|
||||
double outputAsyncQueueReadPixelsMilliseconds = 0.0;
|
||||
double outputAsyncQueueFenceMilliseconds = 0.0;
|
||||
double outputSyncReadMilliseconds = 0.0;
|
||||
uint64_t outputAsyncReadbackMissCount = 0;
|
||||
uint64_t outputCachedFallbackCount = 0;
|
||||
uint64_t outputSyncFallbackCount = 0;
|
||||
uint64_t completedFrameIndex = 0;
|
||||
uint64_t scheduledFrameIndex = 0;
|
||||
uint64_t scheduledLeadFrames = 0;
|
||||
bool actualDeckLinkBufferedFramesAvailable = false;
|
||||
uint64_t actualDeckLinkBufferedFrames = 0;
|
||||
std::size_t targetDeckLinkBufferedFrames = 0;
|
||||
double deckLinkScheduleCallMilliseconds = 0.0;
|
||||
uint64_t deckLinkScheduleFailureCount = 0;
|
||||
uint64_t measuredLagFrames = 0;
|
||||
uint64_t catchUpFrames = 0;
|
||||
uint64_t lateStreak = 0;
|
||||
uint64_t dropStreak = 0;
|
||||
uint64_t lateFrameCount = 0;
|
||||
uint64_t droppedFrameCount = 0;
|
||||
uint64_t flushedFrameCount = 0;
|
||||
bool degraded = false;
|
||||
std::string statusMessage;
|
||||
};
|
||||
|
||||
struct Snapshot
|
||||
{
|
||||
SignalStatusSnapshot signal;
|
||||
VideoIOStatusSnapshot videoIO;
|
||||
PerformanceSnapshot performance;
|
||||
RuntimeEventMetricsSnapshot runtimeEvents;
|
||||
PersistenceSnapshot persistence;
|
||||
BackendPlayoutSnapshot backendPlayout;
|
||||
};
|
||||
|
||||
HealthTelemetry() = default;
|
||||
|
||||
void ReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||
bool TryReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||
|
||||
void ReportVideoIOStatus(const std::string& backendName, const std::string& modelName,
|
||||
bool supportsInternalKeying, bool supportsExternalKeying, bool keyerInterfaceAvailable,
|
||||
bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
|
||||
bool TryReportVideoIOStatus(const std::string& backendName, const std::string& modelName,
|
||||
bool supportsInternalKeying, bool supportsExternalKeying, bool keyerInterfaceAvailable,
|
||||
bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
|
||||
|
||||
void RecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||
bool TryRecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||
|
||||
void RecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
||||
bool TryRecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
||||
|
||||
void RecordRuntimeEventQueueMetrics(const std::string& queueName, std::size_t depth, std::size_t capacity,
|
||||
uint64_t droppedCount, double oldestEventAgeMilliseconds);
|
||||
bool TryRecordRuntimeEventQueueMetrics(const std::string& queueName, std::size_t depth, std::size_t capacity,
|
||||
uint64_t droppedCount, double oldestEventAgeMilliseconds);
|
||||
|
||||
void RecordRuntimeEventDispatchStats(std::size_t dispatchedEvents, std::size_t handlerInvocations,
|
||||
std::size_t handlerFailures, double dispatchDurationMilliseconds);
|
||||
bool TryRecordRuntimeEventDispatchStats(std::size_t dispatchedEvents, std::size_t handlerInvocations,
|
||||
std::size_t handlerFailures, double dispatchDurationMilliseconds);
|
||||
|
||||
void RecordPersistenceWriteResult(bool succeeded, const std::string& targetKind, const std::string& targetPath,
|
||||
const std::string& reason, const std::string& errorMessage, bool newerRequestPending);
|
||||
bool TryRecordPersistenceWriteResult(bool succeeded, const std::string& targetKind, const std::string& targetPath,
|
||||
const std::string& reason, const std::string& errorMessage, bool newerRequestPending);
|
||||
|
||||
void RecordBackendPlayoutHealth(const std::string& lifecycleState, const std::string& completionResult,
|
||||
std::size_t readyQueueDepth, std::size_t readyQueueCapacity, uint64_t readyQueuePushedCount,
|
||||
std::size_t minReadyQueueDepth, std::size_t maxReadyQueueDepth, uint64_t readyQueueZeroDepthCount,
|
||||
uint64_t readyQueuePoppedCount, uint64_t readyQueueDroppedCount, uint64_t readyQueueUnderrunCount,
|
||||
double outputRenderMilliseconds, double smoothedOutputRenderMilliseconds, double maxOutputRenderMilliseconds,
|
||||
double outputFrameAcquireMilliseconds, double outputFrameRenderRequestMilliseconds, double outputFrameEndAccessMilliseconds,
|
||||
uint64_t completedFrameIndex, uint64_t scheduledFrameIndex, uint64_t scheduledLeadFrames,
|
||||
uint64_t measuredLagFrames, uint64_t catchUpFrames, uint64_t lateStreak, uint64_t dropStreak,
|
||||
uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount,
|
||||
bool degraded, const std::string& statusMessage);
|
||||
bool TryRecordBackendPlayoutHealth(const std::string& lifecycleState, const std::string& completionResult,
|
||||
std::size_t readyQueueDepth, std::size_t readyQueueCapacity, uint64_t readyQueuePushedCount,
|
||||
std::size_t minReadyQueueDepth, std::size_t maxReadyQueueDepth, uint64_t readyQueueZeroDepthCount,
|
||||
uint64_t readyQueuePoppedCount, uint64_t readyQueueDroppedCount, uint64_t readyQueueUnderrunCount,
|
||||
double outputRenderMilliseconds, double smoothedOutputRenderMilliseconds, double maxOutputRenderMilliseconds,
|
||||
double outputFrameAcquireMilliseconds, double outputFrameRenderRequestMilliseconds, double outputFrameEndAccessMilliseconds,
|
||||
uint64_t completedFrameIndex, uint64_t scheduledFrameIndex, uint64_t scheduledLeadFrames,
|
||||
uint64_t measuredLagFrames, uint64_t catchUpFrames, uint64_t lateStreak, uint64_t dropStreak,
|
||||
uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount,
|
||||
bool degraded, const std::string& statusMessage);
|
||||
|
||||
void RecordOutputRenderQueueWait(double queueWaitMilliseconds);
|
||||
bool TryRecordOutputRenderQueueWait(double queueWaitMilliseconds);
|
||||
|
||||
void RecordSystemMemoryPlayoutStats(std::size_t freeFrameCount, std::size_t readyFrameCount,
|
||||
std::size_t scheduledFrameCount, uint64_t underrunCount, uint64_t repeatCount, uint64_t dropCount,
|
||||
double frameAgeAtScheduleMilliseconds, double frameAgeAtCompletionMilliseconds);
|
||||
bool TryRecordSystemMemoryPlayoutStats(std::size_t freeFrameCount, std::size_t readyFrameCount,
|
||||
std::size_t scheduledFrameCount, uint64_t underrunCount, uint64_t repeatCount, uint64_t dropCount,
|
||||
double frameAgeAtScheduleMilliseconds, double frameAgeAtCompletionMilliseconds);
|
||||
|
||||
void RecordDeckLinkBufferTelemetry(bool actualBufferedFramesAvailable, uint64_t actualBufferedFrames,
|
||||
std::size_t targetBufferedFrames, double scheduleCallMilliseconds, uint64_t scheduleFailureCount);
|
||||
bool TryRecordDeckLinkBufferTelemetry(bool actualBufferedFramesAvailable, uint64_t actualBufferedFrames,
|
||||
std::size_t targetBufferedFrames, double scheduleCallMilliseconds, uint64_t scheduleFailureCount);
|
||||
|
||||
void RecordOutputRenderPipelineTiming(
|
||||
double drawMilliseconds,
|
||||
double fenceWaitMilliseconds,
|
||||
double mapMilliseconds,
|
||||
double readbackCopyMilliseconds,
|
||||
double cachedCopyMilliseconds,
|
||||
double asyncQueueMilliseconds,
|
||||
double asyncQueueBufferMilliseconds,
|
||||
double asyncQueueSetupMilliseconds,
|
||||
double asyncQueueReadPixelsMilliseconds,
|
||||
double asyncQueueFenceMilliseconds,
|
||||
double syncReadMilliseconds,
|
||||
bool asyncReadbackMissed,
|
||||
bool cachedFallbackUsed,
|
||||
bool syncFallbackUsed);
|
||||
bool TryRecordOutputRenderPipelineTiming(
|
||||
double drawMilliseconds,
|
||||
double fenceWaitMilliseconds,
|
||||
double mapMilliseconds,
|
||||
double readbackCopyMilliseconds,
|
||||
double cachedCopyMilliseconds,
|
||||
double asyncQueueMilliseconds,
|
||||
double asyncQueueBufferMilliseconds,
|
||||
double asyncQueueSetupMilliseconds,
|
||||
double asyncQueueReadPixelsMilliseconds,
|
||||
double asyncQueueFenceMilliseconds,
|
||||
double syncReadMilliseconds,
|
||||
bool asyncReadbackMissed,
|
||||
bool cachedFallbackUsed,
|
||||
bool syncFallbackUsed);
|
||||
|
||||
SignalStatusSnapshot GetSignalStatusSnapshot() const;
|
||||
VideoIOStatusSnapshot GetVideoIOStatusSnapshot() const;
|
||||
PerformanceSnapshot GetPerformanceSnapshot() const;
|
||||
RuntimeEventMetricsSnapshot GetRuntimeEventMetricsSnapshot() const;
|
||||
PersistenceSnapshot GetPersistenceSnapshot() const;
|
||||
BackendPlayoutSnapshot GetBackendPlayoutSnapshot() const;
|
||||
Snapshot GetSnapshot() const;
|
||||
|
||||
private:
|
||||
mutable std::mutex mMutex;
|
||||
SignalStatusSnapshot mSignalStatus;
|
||||
VideoIOStatusSnapshot mVideoIOStatus;
|
||||
PerformanceSnapshot mPerformance;
|
||||
RuntimeEventMetricsSnapshot mRuntimeEvents;
|
||||
PersistenceSnapshot mPersistence;
|
||||
BackendPlayoutSnapshot mBackendPlayout;
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "OutputProductionController.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::size_t ClampReadyLimit(unsigned value, std::size_t capacity)
|
||||
{
|
||||
const std::size_t requested = static_cast<std::size_t>(value);
|
||||
if (capacity == 0)
|
||||
return requested;
|
||||
return (std::min)(requested, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
OutputProductionController::OutputProductionController(const VideoPlayoutPolicy& policy) :
|
||||
mPolicy(NormalizeVideoPlayoutPolicy(policy))
|
||||
{
|
||||
}
|
||||
|
||||
void OutputProductionController::Configure(const VideoPlayoutPolicy& policy)
|
||||
{
|
||||
mPolicy = NormalizeVideoPlayoutPolicy(policy);
|
||||
}
|
||||
|
||||
OutputProductionDecision OutputProductionController::Decide(const OutputProductionPressure& pressure) const
|
||||
{
|
||||
OutputProductionDecision decision;
|
||||
|
||||
const std::size_t configuredMaxReadyFrames = static_cast<std::size_t>(mPolicy.maxReadyFrames);
|
||||
const std::size_t effectiveMaxReadyFrames = pressure.readyQueueCapacity > 0
|
||||
? (std::min)(configuredMaxReadyFrames, pressure.readyQueueCapacity)
|
||||
: configuredMaxReadyFrames;
|
||||
const std::size_t effectiveTargetReadyFrames = (std::min)(
|
||||
ClampReadyLimit(mPolicy.targetReadyFrames, pressure.readyQueueCapacity),
|
||||
effectiveMaxReadyFrames);
|
||||
|
||||
decision.targetReadyFrames = effectiveTargetReadyFrames;
|
||||
decision.maxReadyFrames = effectiveMaxReadyFrames;
|
||||
|
||||
if (effectiveMaxReadyFrames == 0)
|
||||
{
|
||||
decision.action = OutputProductionAction::Throttle;
|
||||
decision.reason = "no-ready-frame-capacity";
|
||||
return decision;
|
||||
}
|
||||
|
||||
if (pressure.readyQueueDepth >= effectiveMaxReadyFrames)
|
||||
{
|
||||
decision.action = OutputProductionAction::Throttle;
|
||||
decision.reason = "ready-queue-full";
|
||||
return decision;
|
||||
}
|
||||
|
||||
if (pressure.readyQueueDepth < effectiveTargetReadyFrames)
|
||||
{
|
||||
decision.action = OutputProductionAction::Produce;
|
||||
decision.requestedFrames = effectiveTargetReadyFrames - pressure.readyQueueDepth;
|
||||
decision.reason = "ready-queue-below-target";
|
||||
return decision;
|
||||
}
|
||||
|
||||
if ((pressure.lateStreak > 0 || pressure.dropStreak > 0 || pressure.readyQueueUnderrunCount > 0) &&
|
||||
pressure.readyQueueDepth < effectiveMaxReadyFrames)
|
||||
{
|
||||
decision.action = OutputProductionAction::Produce;
|
||||
decision.requestedFrames = 1;
|
||||
decision.reason = "playout-pressure";
|
||||
return decision;
|
||||
}
|
||||
|
||||
decision.action = OutputProductionAction::Wait;
|
||||
decision.reason = "ready-queue-at-target";
|
||||
return decision;
|
||||
}
|
||||
|
||||
const char* OutputProductionActionName(OutputProductionAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case OutputProductionAction::Produce:
|
||||
return "Produce";
|
||||
case OutputProductionAction::Throttle:
|
||||
return "Throttle";
|
||||
case OutputProductionAction::Wait:
|
||||
default:
|
||||
return "Wait";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoPlayoutPolicy.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
enum class OutputProductionAction
|
||||
{
|
||||
Produce,
|
||||
Wait,
|
||||
Throttle
|
||||
};
|
||||
|
||||
struct OutputProductionPressure
|
||||
{
|
||||
std::size_t readyQueueDepth = 0;
|
||||
std::size_t readyQueueCapacity = 0;
|
||||
uint64_t readyQueueUnderrunCount = 0;
|
||||
uint64_t lateStreak = 0;
|
||||
uint64_t dropStreak = 0;
|
||||
};
|
||||
|
||||
struct OutputProductionDecision
|
||||
{
|
||||
OutputProductionAction action = OutputProductionAction::Wait;
|
||||
std::size_t requestedFrames = 0;
|
||||
std::size_t targetReadyFrames = 0;
|
||||
std::size_t maxReadyFrames = 0;
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
class OutputProductionController
|
||||
{
|
||||
public:
|
||||
explicit OutputProductionController(const VideoPlayoutPolicy& policy = VideoPlayoutPolicy());
|
||||
|
||||
void Configure(const VideoPlayoutPolicy& policy);
|
||||
OutputProductionDecision Decide(const OutputProductionPressure& pressure) const;
|
||||
|
||||
private:
|
||||
VideoPlayoutPolicy mPolicy;
|
||||
};
|
||||
|
||||
const char* OutputProductionActionName(OutputProductionAction action);
|
||||
@@ -0,0 +1,102 @@
|
||||
#include "RenderCadenceController.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
void RenderCadenceController::Configure(Duration targetFrameDuration, TimePoint firstRenderTime, const RenderCadencePolicy& policy)
|
||||
{
|
||||
mTargetFrameDuration = IsPositive(targetFrameDuration) ? targetFrameDuration : std::chrono::milliseconds(1);
|
||||
mPolicy = policy;
|
||||
if (mPolicy.skipThresholdFrames < 1.0)
|
||||
mPolicy.skipThresholdFrames = 1.0;
|
||||
Reset(firstRenderTime);
|
||||
}
|
||||
|
||||
void RenderCadenceController::Reset(TimePoint firstRenderTime)
|
||||
{
|
||||
mNextRenderTime = firstRenderTime;
|
||||
mNextFrameIndex = 0;
|
||||
mMetrics = RenderCadenceMetrics();
|
||||
}
|
||||
|
||||
RenderCadenceDecision RenderCadenceController::Tick(TimePoint now)
|
||||
{
|
||||
RenderCadenceDecision decision;
|
||||
decision.frameIndex = mNextFrameIndex;
|
||||
decision.renderTargetTime = mNextRenderTime;
|
||||
decision.nextRenderTime = mNextRenderTime;
|
||||
|
||||
if (now < mNextRenderTime)
|
||||
{
|
||||
decision.action = RenderCadenceAction::Wait;
|
||||
decision.waitDuration = mNextRenderTime - now;
|
||||
decision.reason = "waiting-for-next-render-tick";
|
||||
return decision;
|
||||
}
|
||||
|
||||
const Duration lateness = now - mNextRenderTime;
|
||||
const uint64_t skippedTicks = SkippedTicksForLateness(lateness);
|
||||
if (skippedTicks > 0)
|
||||
{
|
||||
decision.skippedTicks = skippedTicks;
|
||||
decision.frameIndex = mNextFrameIndex + skippedTicks;
|
||||
decision.renderTargetTime = mNextRenderTime + (mTargetFrameDuration * skippedTicks);
|
||||
decision.reason = "late-skip-render-ticks";
|
||||
mMetrics.skippedTickCount += skippedTicks;
|
||||
}
|
||||
else
|
||||
{
|
||||
decision.reason = IsPositive(lateness) ? "late-render-now" : "on-time-render";
|
||||
}
|
||||
|
||||
decision.action = RenderCadenceAction::Render;
|
||||
decision.lateness = now > decision.renderTargetTime
|
||||
? now - decision.renderTargetTime
|
||||
: Duration::zero();
|
||||
mNextFrameIndex = decision.frameIndex + 1;
|
||||
mNextRenderTime = decision.renderTargetTime + mTargetFrameDuration;
|
||||
decision.nextRenderTime = mNextRenderTime;
|
||||
|
||||
++mMetrics.renderedFrameCount;
|
||||
mMetrics.nextFrameIndex = mNextFrameIndex;
|
||||
mMetrics.lastLateness = decision.lateness;
|
||||
if (IsPositive(decision.lateness))
|
||||
{
|
||||
++mMetrics.lateFrameCount;
|
||||
mMetrics.maxLateness = (std::max)(mMetrics.maxLateness, decision.lateness);
|
||||
}
|
||||
|
||||
return decision;
|
||||
}
|
||||
|
||||
uint64_t RenderCadenceController::SkippedTicksForLateness(Duration lateness) const
|
||||
{
|
||||
if (!mPolicy.skipLateTicks || !IsPositive(lateness) || !IsPositive(mTargetFrameDuration))
|
||||
return 0;
|
||||
|
||||
const double lateFrames = static_cast<double>(lateness.count()) / static_cast<double>(mTargetFrameDuration.count());
|
||||
if (lateFrames < mPolicy.skipThresholdFrames)
|
||||
return 0;
|
||||
|
||||
const uint64_t elapsedTicks = static_cast<uint64_t>(std::floor(lateFrames));
|
||||
if (elapsedTicks == 0)
|
||||
return 0;
|
||||
return (std::min)(elapsedTicks, mPolicy.maxSkippedTicksPerDecision);
|
||||
}
|
||||
|
||||
bool RenderCadenceController::IsPositive(Duration duration)
|
||||
{
|
||||
return duration > Duration::zero();
|
||||
}
|
||||
|
||||
const char* RenderCadenceActionName(RenderCadenceAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case RenderCadenceAction::Render:
|
||||
return "Render";
|
||||
case RenderCadenceAction::Wait:
|
||||
default:
|
||||
return "Wait";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
enum class RenderCadenceAction
|
||||
{
|
||||
Wait,
|
||||
Render
|
||||
};
|
||||
|
||||
struct RenderCadencePolicy
|
||||
{
|
||||
bool skipLateTicks = true;
|
||||
uint64_t maxSkippedTicksPerDecision = 4;
|
||||
double skipThresholdFrames = 2.0;
|
||||
};
|
||||
|
||||
struct RenderCadenceDecision
|
||||
{
|
||||
RenderCadenceAction action = RenderCadenceAction::Wait;
|
||||
uint64_t frameIndex = 0;
|
||||
uint64_t skippedTicks = 0;
|
||||
std::chrono::steady_clock::time_point renderTargetTime;
|
||||
std::chrono::steady_clock::time_point nextRenderTime;
|
||||
std::chrono::steady_clock::duration waitDuration = std::chrono::steady_clock::duration::zero();
|
||||
std::chrono::steady_clock::duration lateness = std::chrono::steady_clock::duration::zero();
|
||||
const char* reason = "waiting-for-next-render-tick";
|
||||
};
|
||||
|
||||
struct RenderCadenceMetrics
|
||||
{
|
||||
uint64_t nextFrameIndex = 0;
|
||||
uint64_t renderedFrameCount = 0;
|
||||
uint64_t skippedTickCount = 0;
|
||||
uint64_t lateFrameCount = 0;
|
||||
std::chrono::steady_clock::duration lastLateness = std::chrono::steady_clock::duration::zero();
|
||||
std::chrono::steady_clock::duration maxLateness = std::chrono::steady_clock::duration::zero();
|
||||
};
|
||||
|
||||
class RenderCadenceController
|
||||
{
|
||||
public:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using TimePoint = Clock::time_point;
|
||||
using Duration = Clock::duration;
|
||||
|
||||
void Configure(Duration targetFrameDuration, TimePoint firstRenderTime, const RenderCadencePolicy& policy = RenderCadencePolicy());
|
||||
void Reset(TimePoint firstRenderTime);
|
||||
RenderCadenceDecision Tick(TimePoint now);
|
||||
|
||||
Duration TargetFrameDuration() const { return mTargetFrameDuration; }
|
||||
TimePoint NextRenderTime() const { return mNextRenderTime; }
|
||||
uint64_t NextFrameIndex() const { return mNextFrameIndex; }
|
||||
const RenderCadenceMetrics& Metrics() const { return mMetrics; }
|
||||
|
||||
private:
|
||||
uint64_t SkippedTicksForLateness(Duration lateness) const;
|
||||
static bool IsPositive(Duration duration);
|
||||
|
||||
Duration mTargetFrameDuration = std::chrono::milliseconds(16);
|
||||
TimePoint mNextRenderTime;
|
||||
uint64_t mNextFrameIndex = 0;
|
||||
RenderCadencePolicy mPolicy;
|
||||
RenderCadenceMetrics mMetrics;
|
||||
};
|
||||
|
||||
const char* RenderCadenceActionName(RenderCadenceAction action);
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "RenderOutputQueue.h"
|
||||
|
||||
RenderOutputQueue::RenderOutputQueue(const VideoPlayoutPolicy& policy) :
|
||||
mPolicy(NormalizeVideoPlayoutPolicy(policy))
|
||||
{
|
||||
}
|
||||
|
||||
void RenderOutputQueue::Configure(const VideoPlayoutPolicy& policy)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mPolicy = NormalizeVideoPlayoutPolicy(policy);
|
||||
while (mReadyFrames.size() > CapacityLocked())
|
||||
{
|
||||
ReleaseFrame(mReadyFrames.front());
|
||||
mReadyFrames.pop_front();
|
||||
++mDroppedCount;
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderOutputQueue::Push(RenderOutputFrame frame)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mReadyFrames.size() >= CapacityLocked())
|
||||
{
|
||||
ReleaseFrame(mReadyFrames.front());
|
||||
mReadyFrames.pop_front();
|
||||
++mDroppedCount;
|
||||
}
|
||||
|
||||
mReadyFrames.push_back(frame);
|
||||
++mPushedCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderOutputQueue::TryPop(RenderOutputFrame& frame)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mReadyFrames.empty())
|
||||
{
|
||||
++mUnderrunCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
frame = mReadyFrames.front();
|
||||
mReadyFrames.pop_front();
|
||||
++mPoppedCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderOutputQueue::DropOldestFrame()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mReadyFrames.empty())
|
||||
return false;
|
||||
|
||||
ReleaseFrame(mReadyFrames.front());
|
||||
mReadyFrames.pop_front();
|
||||
++mDroppedCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderOutputQueue::Clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
for (RenderOutputFrame& frame : mReadyFrames)
|
||||
ReleaseFrame(frame);
|
||||
mReadyFrames.clear();
|
||||
}
|
||||
|
||||
RenderOutputQueueMetrics RenderOutputQueue::GetMetrics() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
RenderOutputQueueMetrics metrics;
|
||||
metrics.depth = mReadyFrames.size();
|
||||
metrics.capacity = CapacityLocked();
|
||||
metrics.pushedCount = mPushedCount;
|
||||
metrics.poppedCount = mPoppedCount;
|
||||
metrics.droppedCount = mDroppedCount;
|
||||
metrics.underrunCount = mUnderrunCount;
|
||||
return metrics;
|
||||
}
|
||||
|
||||
std::size_t RenderOutputQueue::CapacityLocked() const
|
||||
{
|
||||
return static_cast<std::size_t>(mPolicy.maxReadyFrames);
|
||||
}
|
||||
|
||||
void RenderOutputQueue::ReleaseFrame(RenderOutputFrame& frame)
|
||||
{
|
||||
if (frame.releaseFrame)
|
||||
frame.releaseFrame(frame.frame);
|
||||
frame.releaseFrame = {};
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoIOTypes.h"
|
||||
#include "VideoPlayoutPolicy.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
struct RenderOutputFrame
|
||||
{
|
||||
VideoIOOutputFrame frame;
|
||||
uint64_t frameIndex = 0;
|
||||
bool stale = false;
|
||||
std::function<void(VideoIOOutputFrame& frame)> releaseFrame;
|
||||
};
|
||||
|
||||
struct RenderOutputQueueMetrics
|
||||
{
|
||||
std::size_t depth = 0;
|
||||
std::size_t capacity = 0;
|
||||
uint64_t pushedCount = 0;
|
||||
uint64_t poppedCount = 0;
|
||||
uint64_t droppedCount = 0;
|
||||
uint64_t underrunCount = 0;
|
||||
};
|
||||
|
||||
class RenderOutputQueue
|
||||
{
|
||||
public:
|
||||
explicit RenderOutputQueue(const VideoPlayoutPolicy& policy = VideoPlayoutPolicy());
|
||||
|
||||
void Configure(const VideoPlayoutPolicy& policy);
|
||||
bool Push(RenderOutputFrame frame);
|
||||
bool TryPop(RenderOutputFrame& frame);
|
||||
bool DropOldestFrame();
|
||||
void Clear();
|
||||
RenderOutputQueueMetrics GetMetrics() const;
|
||||
|
||||
private:
|
||||
std::size_t CapacityLocked() const;
|
||||
static void ReleaseFrame(RenderOutputFrame& frame);
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
VideoPlayoutPolicy mPolicy;
|
||||
std::deque<RenderOutputFrame> mReadyFrames;
|
||||
uint64_t mPushedCount = 0;
|
||||
uint64_t mPoppedCount = 0;
|
||||
uint64_t mDroppedCount = 0;
|
||||
uint64_t mUnderrunCount = 0;
|
||||
};
|
||||
@@ -0,0 +1,260 @@
|
||||
#include "SystemOutputFramePool.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace
|
||||
{
|
||||
SystemOutputFramePoolConfig NormalizeConfig(SystemOutputFramePoolConfig config)
|
||||
{
|
||||
if (config.rowBytes == 0)
|
||||
config.rowBytes = VideoIORowBytes(config.pixelFormat, config.width);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
SystemOutputFramePool::SystemOutputFramePool(const SystemOutputFramePoolConfig& config)
|
||||
{
|
||||
Configure(config);
|
||||
}
|
||||
|
||||
void SystemOutputFramePool::Configure(const SystemOutputFramePoolConfig& config)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mConfig = NormalizeConfig(config);
|
||||
mReadySlots.clear();
|
||||
mSlots.clear();
|
||||
mSlots.resize(mConfig.capacity);
|
||||
|
||||
const std::size_t byteCount = FrameByteCount();
|
||||
for (StoredSlot& slot : mSlots)
|
||||
{
|
||||
slot.bytes.resize(byteCount);
|
||||
slot.state = OutputFrameSlotState::Free;
|
||||
++slot.generation;
|
||||
}
|
||||
|
||||
mAcquireMissCount = 0;
|
||||
mReadyUnderrunCount = 0;
|
||||
}
|
||||
|
||||
SystemOutputFramePoolConfig SystemOutputFramePool::Config() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mConfig;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::AcquireFreeSlot(OutputFrameSlot& slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
for (std::size_t index = 0; index < mSlots.size(); ++index)
|
||||
{
|
||||
if (mSlots[index].state != OutputFrameSlotState::Free)
|
||||
continue;
|
||||
|
||||
mSlots[index].state = OutputFrameSlotState::Rendering;
|
||||
++mSlots[index].generation;
|
||||
FillOutputSlotLocked(index, slot);
|
||||
return true;
|
||||
}
|
||||
|
||||
slot = OutputFrameSlot();
|
||||
++mAcquireMissCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::AcquireRenderingSlot(OutputFrameSlot& slot)
|
||||
{
|
||||
return AcquireFreeSlot(slot);
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::PublishReadySlot(const OutputFrameSlot& slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!TransitionSlotLocked(slot, OutputFrameSlotState::Rendering, OutputFrameSlotState::Completed))
|
||||
return false;
|
||||
|
||||
mReadySlots.push_back(slot.index);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::PublishCompletedSlot(const OutputFrameSlot& slot)
|
||||
{
|
||||
return PublishReadySlot(slot);
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::ConsumeReadySlot(OutputFrameSlot& slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
while (!mReadySlots.empty())
|
||||
{
|
||||
const std::size_t index = mReadySlots.front();
|
||||
mReadySlots.pop_front();
|
||||
if (index >= mSlots.size() || mSlots[index].state != OutputFrameSlotState::Completed)
|
||||
continue;
|
||||
|
||||
FillOutputSlotLocked(index, slot);
|
||||
return true;
|
||||
}
|
||||
|
||||
slot = OutputFrameSlot();
|
||||
++mReadyUnderrunCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::ConsumeCompletedSlot(OutputFrameSlot& slot)
|
||||
{
|
||||
return ConsumeReadySlot(slot);
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::MarkScheduled(const OutputFrameSlot& slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!IsValidSlotLocked(slot))
|
||||
return false;
|
||||
if (mSlots[slot.index].state != OutputFrameSlotState::Completed)
|
||||
return false;
|
||||
|
||||
RemoveReadyIndexLocked(slot.index);
|
||||
mSlots[slot.index].state = OutputFrameSlotState::Scheduled;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::MarkScheduledByBuffer(void* bytes)
|
||||
{
|
||||
if (bytes == nullptr)
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
for (std::size_t index = 0; index < mSlots.size(); ++index)
|
||||
{
|
||||
if (mSlots[index].bytes.empty() || mSlots[index].bytes.data() != bytes)
|
||||
continue;
|
||||
if (mSlots[index].state != OutputFrameSlotState::Completed)
|
||||
return false;
|
||||
|
||||
RemoveReadyIndexLocked(index);
|
||||
mSlots[index].state = OutputFrameSlotState::Scheduled;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::ReleaseSlot(const OutputFrameSlot& slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!IsValidSlotLocked(slot) || mSlots[slot.index].state == OutputFrameSlotState::Free)
|
||||
return false;
|
||||
|
||||
return ReleaseSlotByIndexLocked(slot.index);
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::ReleaseScheduledSlot(const OutputFrameSlot& slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return TransitionSlotLocked(slot, OutputFrameSlotState::Scheduled, OutputFrameSlotState::Free);
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::ReleaseSlotByBuffer(void* bytes)
|
||||
{
|
||||
if (bytes == nullptr)
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
for (std::size_t index = 0; index < mSlots.size(); ++index)
|
||||
{
|
||||
if (!mSlots[index].bytes.empty() && mSlots[index].bytes.data() == bytes)
|
||||
return ReleaseSlotByIndexLocked(index);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SystemOutputFramePool::Clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mReadySlots.clear();
|
||||
for (StoredSlot& slot : mSlots)
|
||||
{
|
||||
slot.state = OutputFrameSlotState::Free;
|
||||
++slot.generation;
|
||||
}
|
||||
}
|
||||
|
||||
SystemOutputFramePoolMetrics SystemOutputFramePool::GetMetrics() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
SystemOutputFramePoolMetrics metrics;
|
||||
metrics.capacity = mSlots.size();
|
||||
metrics.readyCount = mReadySlots.size();
|
||||
metrics.acquireMissCount = mAcquireMissCount;
|
||||
metrics.readyUnderrunCount = mReadyUnderrunCount;
|
||||
|
||||
for (const StoredSlot& slot : mSlots)
|
||||
{
|
||||
switch (slot.state)
|
||||
{
|
||||
case OutputFrameSlotState::Free:
|
||||
++metrics.freeCount;
|
||||
break;
|
||||
case OutputFrameSlotState::Rendering:
|
||||
++metrics.renderingCount;
|
||||
++metrics.acquiredCount;
|
||||
break;
|
||||
case OutputFrameSlotState::Completed:
|
||||
++metrics.completedCount;
|
||||
break;
|
||||
case OutputFrameSlotState::Scheduled:
|
||||
++metrics.scheduledCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::IsValidSlotLocked(const OutputFrameSlot& slot) const
|
||||
{
|
||||
return slot.index < mSlots.size() && mSlots[slot.index].generation == slot.generation;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::TransitionSlotLocked(const OutputFrameSlot& slot, OutputFrameSlotState expectedState, OutputFrameSlotState nextState)
|
||||
{
|
||||
if (!IsValidSlotLocked(slot) || mSlots[slot.index].state != expectedState)
|
||||
return false;
|
||||
|
||||
mSlots[slot.index].state = nextState;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SystemOutputFramePool::FillOutputSlotLocked(std::size_t index, OutputFrameSlot& slot)
|
||||
{
|
||||
StoredSlot& storedSlot = mSlots[index];
|
||||
slot.index = index;
|
||||
slot.generation = storedSlot.generation;
|
||||
slot.frame.bytes = storedSlot.bytes.empty() ? nullptr : storedSlot.bytes.data();
|
||||
slot.frame.rowBytes = static_cast<long>(mConfig.rowBytes);
|
||||
slot.frame.width = mConfig.width;
|
||||
slot.frame.height = mConfig.height;
|
||||
slot.frame.pixelFormat = mConfig.pixelFormat;
|
||||
slot.frame.nativeFrame = nullptr;
|
||||
slot.frame.nativeBuffer = slot.frame.bytes;
|
||||
}
|
||||
|
||||
void SystemOutputFramePool::RemoveReadyIndexLocked(std::size_t index)
|
||||
{
|
||||
mReadySlots.erase(std::remove(mReadySlots.begin(), mReadySlots.end(), index), mReadySlots.end());
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::ReleaseSlotByIndexLocked(std::size_t index)
|
||||
{
|
||||
if (index >= mSlots.size() || mSlots[index].state == OutputFrameSlotState::Free)
|
||||
return false;
|
||||
|
||||
RemoveReadyIndexLocked(index);
|
||||
mSlots[index].state = OutputFrameSlotState::Free;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t SystemOutputFramePool::FrameByteCount() const
|
||||
{
|
||||
return static_cast<std::size_t>(mConfig.rowBytes) * static_cast<std::size_t>(mConfig.height);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoIOTypes.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
enum class OutputFrameSlotState
|
||||
{
|
||||
Free,
|
||||
Rendering,
|
||||
Completed,
|
||||
Scheduled
|
||||
};
|
||||
|
||||
struct SystemOutputFramePoolConfig
|
||||
{
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
|
||||
unsigned rowBytes = 0;
|
||||
std::size_t capacity = 0;
|
||||
};
|
||||
|
||||
struct OutputFrameSlot
|
||||
{
|
||||
VideoIOOutputFrame frame;
|
||||
std::size_t index = 0;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
struct SystemOutputFramePoolMetrics
|
||||
{
|
||||
std::size_t capacity = 0;
|
||||
std::size_t freeCount = 0;
|
||||
std::size_t renderingCount = 0;
|
||||
std::size_t completedCount = 0;
|
||||
std::size_t scheduledCount = 0;
|
||||
std::size_t acquiredCount = 0;
|
||||
std::size_t readyCount = 0;
|
||||
std::size_t consumedCount = 0;
|
||||
uint64_t acquireMissCount = 0;
|
||||
uint64_t readyUnderrunCount = 0;
|
||||
};
|
||||
|
||||
class SystemOutputFramePool
|
||||
{
|
||||
public:
|
||||
SystemOutputFramePool() = default;
|
||||
explicit SystemOutputFramePool(const SystemOutputFramePoolConfig& config);
|
||||
|
||||
void Configure(const SystemOutputFramePoolConfig& config);
|
||||
SystemOutputFramePoolConfig Config() const;
|
||||
|
||||
bool AcquireFreeSlot(OutputFrameSlot& slot);
|
||||
bool AcquireRenderingSlot(OutputFrameSlot& slot);
|
||||
bool PublishReadySlot(const OutputFrameSlot& slot);
|
||||
bool PublishCompletedSlot(const OutputFrameSlot& slot);
|
||||
bool ConsumeReadySlot(OutputFrameSlot& slot);
|
||||
bool ConsumeCompletedSlot(OutputFrameSlot& slot);
|
||||
bool MarkScheduled(const OutputFrameSlot& slot);
|
||||
bool MarkScheduledByBuffer(void* bytes);
|
||||
bool ReleaseSlot(const OutputFrameSlot& slot);
|
||||
bool ReleaseScheduledSlot(const OutputFrameSlot& slot);
|
||||
bool ReleaseSlotByBuffer(void* bytes);
|
||||
void Clear();
|
||||
|
||||
SystemOutputFramePoolMetrics GetMetrics() const;
|
||||
|
||||
private:
|
||||
struct StoredSlot
|
||||
{
|
||||
std::vector<unsigned char> bytes;
|
||||
OutputFrameSlotState state = OutputFrameSlotState::Free;
|
||||
uint64_t generation = 1;
|
||||
};
|
||||
|
||||
bool IsValidSlotLocked(const OutputFrameSlot& slot) const;
|
||||
bool TransitionSlotLocked(const OutputFrameSlot& slot, OutputFrameSlotState expectedState, OutputFrameSlotState nextState);
|
||||
void FillOutputSlotLocked(std::size_t index, OutputFrameSlot& slot);
|
||||
void RemoveReadyIndexLocked(std::size_t index);
|
||||
bool ReleaseSlotByIndexLocked(std::size_t index);
|
||||
std::size_t FrameByteCount() const;
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
SystemOutputFramePoolConfig mConfig;
|
||||
std::vector<StoredSlot> mSlots;
|
||||
std::deque<std::size_t> mReadySlots;
|
||||
uint64_t mAcquireMissCount = 0;
|
||||
uint64_t mReadyUnderrunCount = 0;
|
||||
};
|
||||
1011
apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp
Normal file
1011
apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp
Normal file
File diff suppressed because it is too large
Load Diff
158
apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h
Normal file
158
apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h
Normal file
@@ -0,0 +1,158 @@
|
||||
#pragma once
|
||||
|
||||
#include "OutputProductionController.h"
|
||||
#include "RenderCadenceController.h"
|
||||
#include "RenderOutputQueue.h"
|
||||
#include "SystemOutputFramePool.h"
|
||||
#include "VideoBackendLifecycle.h"
|
||||
#include "VideoIOTypes.h"
|
||||
#include "VideoPlayoutPolicy.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
class HealthTelemetry;
|
||||
class OpenGLVideoIOBridge;
|
||||
class RenderEngine;
|
||||
class RuntimeEventDispatcher;
|
||||
class VideoIODevice;
|
||||
|
||||
class VideoBackend
|
||||
{
|
||||
public:
|
||||
VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry, RuntimeEventDispatcher& runtimeEventDispatcher);
|
||||
~VideoBackend();
|
||||
|
||||
void ReleaseResources();
|
||||
VideoBackendLifecycleState LifecycleState() const;
|
||||
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error);
|
||||
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error);
|
||||
bool ConfigureInput(const VideoFormat& inputVideoMode, std::string& error);
|
||||
bool ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error);
|
||||
bool Start();
|
||||
bool Stop();
|
||||
|
||||
const VideoIOState& State() const;
|
||||
VideoIOState& MutableState();
|
||||
bool BeginOutputFrame(VideoIOOutputFrame& frame);
|
||||
void EndOutputFrame(VideoIOOutputFrame& frame);
|
||||
bool ScheduleOutputFrame(const VideoIOOutputFrame& frame);
|
||||
VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth);
|
||||
void RecordBackendPlayoutHealth(VideoIOCompletionResult result, const VideoPlayoutRecoveryDecision& recoveryDecision);
|
||||
|
||||
bool HasInputDevice() const;
|
||||
bool HasInputSource() const;
|
||||
unsigned InputFrameWidth() const;
|
||||
unsigned InputFrameHeight() const;
|
||||
unsigned OutputFrameWidth() const;
|
||||
unsigned OutputFrameHeight() const;
|
||||
unsigned CaptureTextureWidth() const;
|
||||
unsigned OutputPackTextureWidth() const;
|
||||
VideoIOPixelFormat InputPixelFormat() const;
|
||||
const std::string& InputDisplayModeName() const;
|
||||
const std::string& OutputModelName() const;
|
||||
bool SupportsInternalKeying() const;
|
||||
bool SupportsExternalKeying() const;
|
||||
bool KeyerInterfaceAvailable() const;
|
||||
bool ExternalKeyingActive() const;
|
||||
const std::string& StatusMessage() const;
|
||||
bool ShouldPrioritizeOutputOverPreview() const;
|
||||
void SetStatusMessage(const std::string& message);
|
||||
void PublishStatus(bool externalKeyingConfigured, const std::string& statusMessage = std::string());
|
||||
void ReportNoInputDeviceSignalStatus();
|
||||
|
||||
private:
|
||||
void HandleInputFrame(const VideoIOFrame& frame);
|
||||
void HandleOutputFrameCompletion(const VideoIOCompletion& completion);
|
||||
void StartOutputCompletionWorker();
|
||||
void StopOutputCompletionWorker();
|
||||
void OutputCompletionWorkerMain();
|
||||
void StartOutputProducerWorker();
|
||||
void StopOutputProducerWorker();
|
||||
void OutputProducerWorkerMain();
|
||||
void NotifyOutputProducer();
|
||||
std::chrono::milliseconds OutputProducerWakeInterval() const;
|
||||
void ProcessOutputFrameCompletion(const VideoIOCompletion& completion);
|
||||
std::size_t ProduceReadyOutputFrames(const VideoIOCompletion& completion, std::size_t maxFrames);
|
||||
OutputProductionPressure BuildOutputProductionPressure(const RenderOutputQueueMetrics& metrics) const;
|
||||
bool RenderReadyOutputFrame(const VideoIOState& state, const VideoIOCompletion& completion);
|
||||
std::size_t ScheduleReadyOutputFramesToTarget();
|
||||
bool ScheduleReadyOutputFrame();
|
||||
bool ScheduleBlackUnderrunFrame();
|
||||
void RecordFramePacing(VideoIOCompletionResult completionResult);
|
||||
void RecordReadyQueueDepthSample(const RenderOutputQueueMetrics& metrics);
|
||||
void RecordDeckLinkBufferTelemetry();
|
||||
void RecordSystemMemoryPlayoutStats();
|
||||
void RecordOutputRenderDuration(double renderMilliseconds, double acquireMilliseconds, double renderRequestMilliseconds, double endAccessMilliseconds);
|
||||
bool ApplyLifecycleTransition(VideoBackendLifecycleState state, const std::string& message);
|
||||
bool ApplyLifecycleFailure(const std::string& message);
|
||||
void PublishBackendStateChanged(const std::string& state, const std::string& message);
|
||||
void PublishInputSignalChanged(const VideoIOFrame& frame, const VideoIOState& state);
|
||||
void PublishInputFrameArrived(const VideoIOFrame& frame);
|
||||
void PublishOutputFrameScheduled(const VideoIOOutputFrame& frame);
|
||||
void PublishOutputFrameCompleted(const VideoIOCompletion& completion);
|
||||
void PublishTimingSample(const std::string& subsystem, const std::string& metric, double value, const std::string& unit);
|
||||
static std::string CompletionResultName(VideoIOCompletionResult result);
|
||||
static std::string PixelFormatName(VideoIOPixelFormat pixelFormat);
|
||||
|
||||
HealthTelemetry& mHealthTelemetry;
|
||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
||||
VideoBackendLifecycle mLifecycle;
|
||||
VideoPlayoutPolicy mPlayoutPolicy;
|
||||
OutputProductionController mOutputProductionController;
|
||||
RenderCadenceController mRenderCadenceController;
|
||||
RenderOutputQueue mReadyOutputQueue;
|
||||
SystemOutputFramePool mSystemOutputFramePool;
|
||||
std::unique_ptr<VideoIODevice> mVideoIODevice;
|
||||
std::unique_ptr<OpenGLVideoIOBridge> mBridge;
|
||||
std::mutex mOutputCompletionMutex;
|
||||
std::condition_variable mOutputCompletionCondition;
|
||||
std::deque<VideoIOCompletion> mPendingOutputCompletions;
|
||||
std::thread mOutputCompletionWorker;
|
||||
std::mutex mOutputProducerMutex;
|
||||
std::condition_variable mOutputProducerCondition;
|
||||
std::thread mOutputProducerWorker;
|
||||
VideoIOCompletion mLastOutputProductionCompletion;
|
||||
std::chrono::steady_clock::time_point mLastOutputProductionTime;
|
||||
std::mutex mOutputProductionMutex;
|
||||
std::mutex mOutputSchedulingMutex;
|
||||
mutable std::mutex mOutputMetricsMutex;
|
||||
bool mOutputCompletionWorkerRunning = false;
|
||||
bool mOutputCompletionWorkerStopping = false;
|
||||
bool mOutputProducerWorkerRunning = false;
|
||||
bool mOutputProducerWorkerStopping = false;
|
||||
uint64_t mNextReadyOutputFrameIndex = 0;
|
||||
uint64_t mInputFrameIndex = 0;
|
||||
uint64_t mOutputFrameScheduleIndex = 0;
|
||||
uint64_t mOutputFrameCompletionIndex = 0;
|
||||
bool mHasLastInputSignal = false;
|
||||
bool mLastInputSignal = false;
|
||||
unsigned mLastInputSignalWidth = 0;
|
||||
unsigned mLastInputSignalHeight = 0;
|
||||
std::string mLastInputSignalModeName;
|
||||
std::chrono::steady_clock::time_point mLastPlayoutCompletionTime;
|
||||
double mCompletionIntervalMilliseconds = 0.0;
|
||||
double mSmoothedCompletionIntervalMilliseconds = 0.0;
|
||||
double mMaxCompletionIntervalMilliseconds = 0.0;
|
||||
bool mHasReadyQueueDepthBaseline = false;
|
||||
std::size_t mMinReadyQueueDepth = 0;
|
||||
std::size_t mMaxReadyQueueDepth = 0;
|
||||
uint64_t mReadyQueueZeroDepthCount = 0;
|
||||
double mOutputRenderMilliseconds = 0.0;
|
||||
double mSmoothedOutputRenderMilliseconds = 0.0;
|
||||
double mMaxOutputRenderMilliseconds = 0.0;
|
||||
double mOutputFrameAcquireMilliseconds = 0.0;
|
||||
double mOutputFrameRenderRequestMilliseconds = 0.0;
|
||||
double mOutputFrameEndAccessMilliseconds = 0.0;
|
||||
uint64_t mLastLateStreak = 0;
|
||||
uint64_t mLastDropStreak = 0;
|
||||
uint64_t mLateFrameCount = 0;
|
||||
uint64_t mDroppedFrameCount = 0;
|
||||
uint64_t mFlushedFrameCount = 0;
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "VideoBackendLifecycle.h"
|
||||
|
||||
VideoBackendLifecycleState VideoBackendLifecycle::State() const
|
||||
{
|
||||
return mState;
|
||||
}
|
||||
|
||||
const std::string& VideoBackendLifecycle::FailureReason() const
|
||||
{
|
||||
return mFailureReason;
|
||||
}
|
||||
|
||||
VideoBackendLifecycleTransition VideoBackendLifecycle::TransitionTo(VideoBackendLifecycleState next, const std::string& reason)
|
||||
{
|
||||
VideoBackendLifecycleTransition transition;
|
||||
transition.previous = mState;
|
||||
transition.current = next;
|
||||
transition.reason = reason;
|
||||
transition.accepted = CanTransition(mState, next);
|
||||
if (!transition.accepted)
|
||||
{
|
||||
transition.current = mState;
|
||||
transition.errorMessage = std::string("Invalid video backend lifecycle transition from ") +
|
||||
StateName(mState) + " to " + StateName(next) + ".";
|
||||
return transition;
|
||||
}
|
||||
|
||||
mState = next;
|
||||
transition.current = mState;
|
||||
if (mState != VideoBackendLifecycleState::Failed)
|
||||
mFailureReason.clear();
|
||||
return transition;
|
||||
}
|
||||
|
||||
VideoBackendLifecycleTransition VideoBackendLifecycle::Fail(const std::string& reason)
|
||||
{
|
||||
VideoBackendLifecycleTransition transition = TransitionTo(VideoBackendLifecycleState::Failed, reason);
|
||||
if (transition.accepted)
|
||||
mFailureReason = reason;
|
||||
return transition;
|
||||
}
|
||||
|
||||
bool VideoBackendLifecycle::CanTransition(VideoBackendLifecycleState current, VideoBackendLifecycleState next)
|
||||
{
|
||||
if (current == next)
|
||||
return true;
|
||||
|
||||
switch (current)
|
||||
{
|
||||
case VideoBackendLifecycleState::Uninitialized:
|
||||
return next == VideoBackendLifecycleState::Discovering ||
|
||||
next == VideoBackendLifecycleState::Stopped ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Discovering:
|
||||
return next == VideoBackendLifecycleState::Discovered ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Discovered:
|
||||
return next == VideoBackendLifecycleState::Configuring ||
|
||||
next == VideoBackendLifecycleState::Stopped ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Configuring:
|
||||
return next == VideoBackendLifecycleState::Configured ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Configured:
|
||||
return next == VideoBackendLifecycleState::Prerolling ||
|
||||
next == VideoBackendLifecycleState::Stopped ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Prerolling:
|
||||
return next == VideoBackendLifecycleState::Running ||
|
||||
next == VideoBackendLifecycleState::Stopping ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Running:
|
||||
return next == VideoBackendLifecycleState::Degraded ||
|
||||
next == VideoBackendLifecycleState::Stopping ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Degraded:
|
||||
return next == VideoBackendLifecycleState::Running ||
|
||||
next == VideoBackendLifecycleState::Stopping ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Stopping:
|
||||
return next == VideoBackendLifecycleState::Stopped ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Stopped:
|
||||
return next == VideoBackendLifecycleState::Discovering ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Failed:
|
||||
return next == VideoBackendLifecycleState::Stopped ||
|
||||
next == VideoBackendLifecycleState::Discovering;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const char* VideoBackendLifecycle::StateName(VideoBackendLifecycleState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case VideoBackendLifecycleState::Uninitialized:
|
||||
return "uninitialized";
|
||||
case VideoBackendLifecycleState::Discovering:
|
||||
return "discovering";
|
||||
case VideoBackendLifecycleState::Discovered:
|
||||
return "discovered";
|
||||
case VideoBackendLifecycleState::Configuring:
|
||||
return "configuring";
|
||||
case VideoBackendLifecycleState::Configured:
|
||||
return "configured";
|
||||
case VideoBackendLifecycleState::Prerolling:
|
||||
return "prerolling";
|
||||
case VideoBackendLifecycleState::Running:
|
||||
return "running";
|
||||
case VideoBackendLifecycleState::Degraded:
|
||||
return "degraded";
|
||||
case VideoBackendLifecycleState::Stopping:
|
||||
return "stopping";
|
||||
case VideoBackendLifecycleState::Stopped:
|
||||
return "stopped";
|
||||
case VideoBackendLifecycleState::Failed:
|
||||
return "failed";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user