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
|
- name: Build Debug
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: cmake --build --preset build-debug
|
run: cmake --build --preset build-debug --parallel
|
||||||
|
|
||||||
- name: Run Native Tests And Shader Validation
|
- name: Run Native Tests And Shader Validation
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: cmake --build --preset build-debug --target RUN_TESTS
|
run: cmake --build --preset build-debug --target RUN_TESTS --parallel
|
||||||
|
|
||||||
ui-ubuntu:
|
ui-ubuntu:
|
||||||
name: React UI Build
|
name: React UI Build
|
||||||
@@ -136,7 +136,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Release
|
- name: Build Release
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: cmake --build --preset build-release
|
run: cmake --build --preset build-release --parallel
|
||||||
|
|
||||||
- name: Install Runtime Package
|
- name: Install Runtime Package
|
||||||
shell: powershell
|
shell: powershell
|
||||||
|
|||||||
49
.vscode/launch.json
vendored
49
.vscode/launch.json
vendored
@@ -11,6 +11,55 @@
|
|||||||
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||||
"environment": [],
|
"environment": [],
|
||||||
"console": "internalConsole",
|
"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"
|
"preLaunchTask": "Build LoopThroughWithOpenGLCompositing Debug x64"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
6
.vscode/tasks.json
vendored
6
.vscode/tasks.json
vendored
@@ -11,7 +11,8 @@
|
|||||||
"--config",
|
"--config",
|
||||||
"Debug",
|
"Debug",
|
||||||
"--target",
|
"--target",
|
||||||
"LoopThroughWithOpenGLCompositing"
|
"LoopThroughWithOpenGLCompositing",
|
||||||
|
"--parallel"
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@@ -29,7 +30,8 @@
|
|||||||
"--config",
|
"--config",
|
||||||
"Release",
|
"Release",
|
||||||
"--target",
|
"--target",
|
||||||
"LoopThroughWithOpenGLCompositing"
|
"LoopThroughWithOpenGLCompositing",
|
||||||
|
"--parallel"
|
||||||
],
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
|
|||||||
347
CMakeLists.txt
347
CMakeLists.txt
@@ -34,10 +34,14 @@ set(APP_SOURCES
|
|||||||
"${APP_DIR}/videoio/decklink/DeckLinkAPI_i.c"
|
"${APP_DIR}/videoio/decklink/DeckLinkAPI_i.c"
|
||||||
"${APP_DIR}/control/ControlServer.cpp"
|
"${APP_DIR}/control/ControlServer.cpp"
|
||||||
"${APP_DIR}/control/ControlServer.h"
|
"${APP_DIR}/control/ControlServer.h"
|
||||||
|
"${APP_DIR}/control/ControlServices.cpp"
|
||||||
|
"${APP_DIR}/control/ControlServices.h"
|
||||||
"${APP_DIR}/control/OscServer.cpp"
|
"${APP_DIR}/control/OscServer.cpp"
|
||||||
"${APP_DIR}/control/OscServer.h"
|
"${APP_DIR}/control/OscServer.h"
|
||||||
"${APP_DIR}/control/RuntimeControlBridge.cpp"
|
"${APP_DIR}/control/RuntimeControlBridge.cpp"
|
||||||
"${APP_DIR}/control/RuntimeControlBridge.h"
|
"${APP_DIR}/control/RuntimeControlBridge.h"
|
||||||
|
"${APP_DIR}/control/RuntimeServiceLiveBridge.cpp"
|
||||||
|
"${APP_DIR}/control/RuntimeServiceLiveBridge.h"
|
||||||
"${APP_DIR}/control/RuntimeServices.cpp"
|
"${APP_DIR}/control/RuntimeServices.cpp"
|
||||||
"${APP_DIR}/control/RuntimeServices.h"
|
"${APP_DIR}/control/RuntimeServices.h"
|
||||||
"${APP_DIR}/videoio/decklink/DeckLinkAPI_h.h"
|
"${APP_DIR}/videoio/decklink/DeckLinkAPI_h.h"
|
||||||
@@ -57,9 +61,18 @@ set(APP_SOURCES
|
|||||||
"${APP_DIR}/gl/renderer/GlScopedObjects.h"
|
"${APP_DIR}/gl/renderer/GlScopedObjects.h"
|
||||||
"${APP_DIR}/gl/shader/GlShaderSources.cpp"
|
"${APP_DIR}/gl/shader/GlShaderSources.cpp"
|
||||||
"${APP_DIR}/gl/shader/GlShaderSources.h"
|
"${APP_DIR}/gl/shader/GlShaderSources.h"
|
||||||
"${APP_DIR}/gl/OpenGLComposite.cpp"
|
"${APP_DIR}/gl/composite/OpenGLComposite.cpp"
|
||||||
"${APP_DIR}/gl/OpenGLComposite.h"
|
"${APP_DIR}/gl/composite/OpenGLComposite.h"
|
||||||
"${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp"
|
"${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.cpp"
|
||||||
"${APP_DIR}/gl/pipeline/OpenGLRenderPass.h"
|
"${APP_DIR}/gl/pipeline/OpenGLRenderPass.h"
|
||||||
"${APP_DIR}/gl/pipeline/OpenGLRenderPipeline.cpp"
|
"${APP_DIR}/gl/pipeline/OpenGLRenderPipeline.cpp"
|
||||||
@@ -96,14 +109,50 @@ set(APP_SOURCES
|
|||||||
"${APP_DIR}/platform/NativeHandles.h"
|
"${APP_DIR}/platform/NativeHandles.h"
|
||||||
"${APP_DIR}/platform/NativeSockets.h"
|
"${APP_DIR}/platform/NativeSockets.h"
|
||||||
"${APP_DIR}/resource.h"
|
"${APP_DIR}/resource.h"
|
||||||
"${APP_DIR}/runtime/RuntimeHost.cpp"
|
"${APP_DIR}/runtime/coordination/RuntimeCoordinator.cpp"
|
||||||
"${APP_DIR}/runtime/RuntimeHost.h"
|
"${APP_DIR}/runtime/coordination/RuntimeCoordinator.h"
|
||||||
"${APP_DIR}/runtime/RuntimeClock.cpp"
|
"${APP_DIR}/runtime/events/RuntimeEventCoalescingQueue.h"
|
||||||
"${APP_DIR}/runtime/RuntimeClock.h"
|
"${APP_DIR}/runtime/events/RuntimeEventDispatcher.h"
|
||||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
"${APP_DIR}/runtime/events/RuntimeEvent.h"
|
||||||
"${APP_DIR}/runtime/RuntimeJson.h"
|
"${APP_DIR}/runtime/events/RuntimeEventPayloads.h"
|
||||||
"${APP_DIR}/runtime/RuntimeParameterUtils.cpp"
|
"${APP_DIR}/runtime/events/RuntimeEventQueue.h"
|
||||||
"${APP_DIR}/runtime/RuntimeParameterUtils.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.cpp"
|
||||||
"${APP_DIR}/shader/ShaderCompiler.h"
|
"${APP_DIR}/shader/ShaderCompiler.h"
|
||||||
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||||
@@ -114,7 +163,20 @@ set(APP_SOURCES
|
|||||||
"${APP_DIR}/targetver.h"
|
"${APP_DIR}/targetver.h"
|
||||||
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
||||||
"${APP_DIR}/videoio/VideoIOFormat.h"
|
"${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/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.cpp"
|
||||||
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"
|
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"
|
||||||
)
|
)
|
||||||
@@ -125,11 +187,23 @@ target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE
|
|||||||
"${APP_DIR}"
|
"${APP_DIR}"
|
||||||
"${APP_DIR}/control"
|
"${APP_DIR}/control"
|
||||||
"${APP_DIR}/gl"
|
"${APP_DIR}/gl"
|
||||||
|
"${APP_DIR}/gl/composite"
|
||||||
|
"${APP_DIR}/gl/frame"
|
||||||
"${APP_DIR}/gl/pipeline"
|
"${APP_DIR}/gl/pipeline"
|
||||||
"${APP_DIR}/gl/renderer"
|
"${APP_DIR}/gl/renderer"
|
||||||
"${APP_DIR}/gl/shader"
|
"${APP_DIR}/gl/shader"
|
||||||
|
"${APP_DIR}/gl/threading"
|
||||||
"${APP_DIR}/platform"
|
"${APP_DIR}/platform"
|
||||||
"${APP_DIR}/runtime"
|
"${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}/shader"
|
||||||
"${APP_DIR}/videoio"
|
"${APP_DIR}/videoio"
|
||||||
"${APP_DIR}/videoio/decklink"
|
"${APP_DIR}/videoio/decklink"
|
||||||
@@ -156,13 +230,14 @@ if(MSVC)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(RuntimeJsonTests
|
add_executable(RuntimeJsonTests
|
||||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeJsonTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeJsonTests.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(RuntimeJsonTests PRIVATE
|
target_include_directories(RuntimeJsonTests PRIVATE
|
||||||
"${APP_DIR}"
|
"${APP_DIR}"
|
||||||
"${APP_DIR}/runtime"
|
"${APP_DIR}/runtime"
|
||||||
|
"${APP_DIR}/runtime/support"
|
||||||
)
|
)
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
@@ -173,13 +248,14 @@ enable_testing()
|
|||||||
add_test(NAME RuntimeJsonTests COMMAND RuntimeJsonTests)
|
add_test(NAME RuntimeJsonTests COMMAND RuntimeJsonTests)
|
||||||
|
|
||||||
add_executable(RuntimeClockTests
|
add_executable(RuntimeClockTests
|
||||||
"${APP_DIR}/runtime/RuntimeClock.cpp"
|
"${APP_DIR}/runtime/telemetry/RuntimeClock.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeClockTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeClockTests.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(RuntimeClockTests PRIVATE
|
target_include_directories(RuntimeClockTests PRIVATE
|
||||||
"${APP_DIR}"
|
"${APP_DIR}"
|
||||||
"${APP_DIR}/runtime"
|
"${APP_DIR}/runtime"
|
||||||
|
"${APP_DIR}/runtime/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
@@ -188,15 +264,33 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME RuntimeClockTests COMMAND RuntimeClockTests)
|
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
|
add_executable(RuntimeParameterUtilsTests
|
||||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||||
"${APP_DIR}/runtime/RuntimeParameterUtils.cpp"
|
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeParameterUtilsTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeParameterUtilsTests.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(RuntimeParameterUtilsTests PRIVATE
|
target_include_directories(RuntimeParameterUtilsTests PRIVATE
|
||||||
"${APP_DIR}"
|
"${APP_DIR}"
|
||||||
"${APP_DIR}/runtime"
|
"${APP_DIR}/runtime"
|
||||||
|
"${APP_DIR}/runtime/support"
|
||||||
"${APP_DIR}/shader"
|
"${APP_DIR}/shader"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -206,6 +300,121 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME RuntimeParameterUtilsTests COMMAND RuntimeParameterUtilsTests)
|
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
|
add_executable(Std140BufferTests
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/Std140BufferTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/Std140BufferTests.cpp"
|
||||||
)
|
)
|
||||||
@@ -222,8 +431,27 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME Std140BufferTests COMMAND Std140BufferTests)
|
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
|
add_executable(ShaderPackageRegistryTests
|
||||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||||
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderPackageRegistryTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderPackageRegistryTests.cpp"
|
||||||
)
|
)
|
||||||
@@ -231,6 +459,7 @@ add_executable(ShaderPackageRegistryTests
|
|||||||
target_include_directories(ShaderPackageRegistryTests PRIVATE
|
target_include_directories(ShaderPackageRegistryTests PRIVATE
|
||||||
"${APP_DIR}"
|
"${APP_DIR}"
|
||||||
"${APP_DIR}/runtime"
|
"${APP_DIR}/runtime"
|
||||||
|
"${APP_DIR}/runtime/support"
|
||||||
"${APP_DIR}/shader"
|
"${APP_DIR}/shader"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -241,7 +470,7 @@ endif()
|
|||||||
add_test(NAME ShaderPackageRegistryTests COMMAND ShaderPackageRegistryTests)
|
add_test(NAME ShaderPackageRegistryTests COMMAND ShaderPackageRegistryTests)
|
||||||
|
|
||||||
add_executable(ShaderSlangValidationTests
|
add_executable(ShaderSlangValidationTests
|
||||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||||
"${APP_DIR}/shader/ShaderCompiler.cpp"
|
"${APP_DIR}/shader/ShaderCompiler.cpp"
|
||||||
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderSlangValidationTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderSlangValidationTests.cpp"
|
||||||
@@ -251,6 +480,7 @@ target_include_directories(ShaderSlangValidationTests PRIVATE
|
|||||||
"${APP_DIR}"
|
"${APP_DIR}"
|
||||||
"${APP_DIR}/platform"
|
"${APP_DIR}/platform"
|
||||||
"${APP_DIR}/runtime"
|
"${APP_DIR}/runtime"
|
||||||
|
"${APP_DIR}/runtime/support"
|
||||||
"${APP_DIR}/shader"
|
"${APP_DIR}/shader"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -319,6 +549,89 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME VideoPlayoutSchedulerTests COMMAND VideoPlayoutSchedulerTests)
|
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
|
add_executable(VideoIODeviceFakeTests
|
||||||
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIODeviceFakeTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIODeviceFakeTests.cpp"
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Configure and build the native app:
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
cmake --preset vs2022-x64-debug
|
cmake --preset vs2022-x64-debug
|
||||||
cmake --build --preset build-debug
|
cmake --build --preset build-debug --parallel
|
||||||
```
|
```
|
||||||
|
|
||||||
Build the React control UI:
|
Build the React control UI:
|
||||||
@@ -80,7 +80,7 @@ npm ci
|
|||||||
npm run build
|
npm run build
|
||||||
cd ..
|
cd ..
|
||||||
cmake --preset vs2022-x64-release
|
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
|
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:
|
Run native tests:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
cmake --build --preset build-debug --target RUN_TESTS
|
cmake --build --preset build-debug --target RUN_TESTS --parallel
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the UI production build check:
|
Run the UI production build check:
|
||||||
|
|||||||
@@ -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 "stdafx.h"
|
||||||
#include "resource.h"
|
#include "resource.h"
|
||||||
#include "OpenGLComposite.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
|
// Deselect the current rendering context and delete it
|
||||||
wglMakeCurrent(hDC, NULL);
|
wglMakeCurrent(NULL, NULL);
|
||||||
wglDeleteContext(hRC);
|
wglDeleteContext(hRC);
|
||||||
|
|
||||||
// Tell the application to terminate after the window is gone
|
// 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)
|
if (!sInteractiveResize && pOpenGLComposite)
|
||||||
{
|
{
|
||||||
wglMakeCurrent(hDC, hRC);
|
|
||||||
pOpenGLComposite->paintGL(true);
|
pOpenGLComposite->paintGL(true);
|
||||||
wglMakeCurrent( NULL, NULL );
|
|
||||||
RaiseStatusControls(sStatusStrip);
|
RaiseStatusControls(sStatusStrip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
wglMakeCurrent( NULL, NULL );
|
|
||||||
ShowUnhandledExceptionMessage("Paint failed inside the OpenGL runtime.");
|
ShowUnhandledExceptionMessage("Paint failed inside the OpenGL runtime.");
|
||||||
}
|
}
|
||||||
break;
|
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
|
#pragma once
|
||||||
|
|
||||||
#include "resource.h"
|
#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 "RuntimeControlBridge.h"
|
||||||
|
|
||||||
|
#include "ControlServices.h"
|
||||||
#include "ControlServer.h"
|
#include "ControlServer.h"
|
||||||
#include "OpenGLComposite.h"
|
#include "OpenGLComposite.h"
|
||||||
#include "OscServer.h"
|
#include "OscServer.h"
|
||||||
#include "RuntimeHost.h"
|
#include "RuntimeStore.h"
|
||||||
#include "RuntimeServices.h"
|
|
||||||
|
|
||||||
bool StartRuntimeControlServices(
|
bool StartControlServicesBoundary(
|
||||||
OpenGLComposite& composite,
|
OpenGLComposite& composite,
|
||||||
RuntimeHost& runtimeHost,
|
RuntimeStore& runtimeStore,
|
||||||
RuntimeServices& runtimeServices,
|
ControlServices& controlServices,
|
||||||
ControlServer& controlServer,
|
ControlServer& controlServer,
|
||||||
OscServer& oscServer,
|
OscServer& oscServer,
|
||||||
std::string& error)
|
std::string& error)
|
||||||
@@ -38,15 +38,16 @@ bool StartRuntimeControlServices(
|
|||||||
return true;
|
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;
|
return false;
|
||||||
runtimeHost.SetServerPort(controlServer.GetPort());
|
runtimeStore.SetBoundControlServerPort(controlServer.GetPort());
|
||||||
|
|
||||||
OscServer::Callbacks oscCallbacks;
|
OscServer::Callbacks oscCallbacks;
|
||||||
oscCallbacks.updateParameter = [&runtimeServices](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) {
|
oscCallbacks.updateParameter = [&controlServices](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) {
|
||||||
return runtimeServices.QueueOscUpdate(layerKey, parameterKey, valueJson, 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 false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -3,15 +3,15 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class ControlServer;
|
class ControlServer;
|
||||||
|
class ControlServices;
|
||||||
class OpenGLComposite;
|
class OpenGLComposite;
|
||||||
class OscServer;
|
class OscServer;
|
||||||
class RuntimeHost;
|
class RuntimeStore;
|
||||||
class RuntimeServices;
|
|
||||||
|
|
||||||
bool StartRuntimeControlServices(
|
bool StartControlServicesBoundary(
|
||||||
OpenGLComposite& composite,
|
OpenGLComposite& composite,
|
||||||
RuntimeHost& runtimeHost,
|
RuntimeStore& runtimeStore,
|
||||||
RuntimeServices& runtimeServices,
|
ControlServices& controlServices,
|
||||||
ControlServer& controlServer,
|
ControlServer& controlServer,
|
||||||
OscServer& oscServer,
|
OscServer& oscServer,
|
||||||
std::string& error);
|
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 "RuntimeServices.h"
|
||||||
|
|
||||||
#include "ControlServer.h"
|
#include "RuntimeStore.h"
|
||||||
#include "OscServer.h"
|
|
||||||
#include "RuntimeControlBridge.h"
|
|
||||||
#include "RuntimeHost.h"
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
RuntimeServices::RuntimeServices() :
|
RuntimeServices::RuntimeServices(RuntimeEventDispatcher& runtimeEventDispatcher) :
|
||||||
mControlServer(std::make_unique<ControlServer>()),
|
mControlServices(std::make_unique<ControlServices>(runtimeEventDispatcher))
|
||||||
mOscServer(std::make_unique<OscServer>()),
|
|
||||||
mPollRunning(false),
|
|
||||||
mRegistryChanged(false),
|
|
||||||
mReloadRequested(false),
|
|
||||||
mPollFailed(false)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,227 +12,75 @@ RuntimeServices::~RuntimeServices()
|
|||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error)
|
bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error)
|
||||||
{
|
{
|
||||||
Stop();
|
return mControlServices && mControlServices->Start(composite, runtimeStore, error);
|
||||||
|
|
||||||
if (!StartRuntimeControlServices(composite, runtimeHost, *this, *mControlServer, *mOscServer, error))
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeServices::BeginPolling(RuntimeHost& runtimeHost)
|
void RuntimeServices::BeginPolling(RuntimeCoordinator& runtimeCoordinator)
|
||||||
{
|
{
|
||||||
StartPolling(runtimeHost);
|
if (mControlServices)
|
||||||
|
mControlServices->BeginPolling(runtimeCoordinator);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeServices::Stop()
|
void RuntimeServices::Stop()
|
||||||
{
|
{
|
||||||
StopPolling();
|
if (mControlServices)
|
||||||
|
mControlServices->Stop();
|
||||||
if (mOscServer)
|
|
||||||
mOscServer->Stop();
|
|
||||||
|
|
||||||
if (mControlServer)
|
|
||||||
mControlServer->Stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeServices::BroadcastState()
|
void RuntimeServices::BroadcastState()
|
||||||
{
|
{
|
||||||
if (mControlServer)
|
if (mControlServices)
|
||||||
mControlServer->BroadcastState();
|
mControlServices->BroadcastState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeServices::RequestBroadcastState()
|
void RuntimeServices::RequestBroadcastState()
|
||||||
{
|
{
|
||||||
if (mControlServer)
|
if (mControlServices)
|
||||||
mControlServer->RequestBroadcastState();
|
mControlServices->RequestBroadcastState();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeServices::QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
|
bool RuntimeServices::QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
|
||||||
{
|
{
|
||||||
(void)error;
|
return mControlServices && mControlServices->QueueOscUpdate(layerKey, parameterKey, valueJson, 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 RuntimeServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error)
|
bool RuntimeServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error)
|
||||||
{
|
{
|
||||||
appliedUpdates.clear();
|
if (!mControlServices)
|
||||||
|
|
||||||
std::map<std::string, PendingOscUpdate> pending;
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
appliedUpdates.clear();
|
||||||
if (mPendingOscUpdates.empty())
|
return true;
|
||||||
return true;
|
|
||||||
pending.swap(mPendingOscUpdates);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& entry : pending)
|
return mControlServices->ApplyPendingOscUpdates(appliedUpdates, error);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeServices::QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& 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;
|
return mControlServices && mControlServices->QueueOscCommit(routeKey, layerKey, parameterKey, value, generation, 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeServices::ClearOscState()
|
void RuntimeServices::ClearOscState()
|
||||||
{
|
{
|
||||||
{
|
if (mControlServices)
|
||||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
mControlServices->ClearOscState();
|
||||||
mPendingOscUpdates.clear();
|
}
|
||||||
}
|
|
||||||
{
|
void RuntimeServices::ClearOscStateForLayerKey(const std::string& layerKey)
|
||||||
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
{
|
||||||
mPendingOscCommits.clear();
|
if (mControlServices)
|
||||||
}
|
mControlServices->ClearOscStateForLayerKey(layerKey);
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
|
||||||
mCompletedOscCommits.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
|
void RuntimeServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
|
||||||
{
|
{
|
||||||
completedCommits.clear();
|
if (!mControlServices)
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mPollErrorMutex);
|
completedCommits.clear();
|
||||||
events.error = mPollError;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return events;
|
mControlServices->ConsumeCompletedOscCommits(completedCommits);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "RuntimeJson.h"
|
#include "ControlServices.h"
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
class ControlServer;
|
|
||||||
class OpenGLComposite;
|
class OpenGLComposite;
|
||||||
class OscServer;
|
class RuntimeCoordinator;
|
||||||
class RuntimeHost;
|
class RuntimeEventDispatcher;
|
||||||
|
class RuntimeStore;
|
||||||
struct RuntimePollEvents
|
|
||||||
{
|
|
||||||
bool registryChanged = false;
|
|
||||||
bool reloadRequested = false;
|
|
||||||
bool failed = false;
|
|
||||||
std::string error;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RuntimeServices
|
class RuntimeServices
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct AppliedOscUpdate
|
using AppliedOscUpdate = ControlServices::AppliedOscUpdate;
|
||||||
{
|
using CompletedOscCommit = ControlServices::CompletedOscCommit;
|
||||||
std::string routeKey;
|
|
||||||
std::string layerKey;
|
|
||||||
std::string parameterKey;
|
|
||||||
JsonValue targetValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CompletedOscCommit
|
explicit RuntimeServices(RuntimeEventDispatcher& runtimeEventDispatcher);
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
RuntimeServices();
|
|
||||||
~RuntimeServices();
|
~RuntimeServices();
|
||||||
|
|
||||||
bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error);
|
bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);
|
||||||
void BeginPolling(RuntimeHost& runtimeHost);
|
void BeginPolling(RuntimeCoordinator& runtimeCoordinator);
|
||||||
void Stop();
|
void Stop();
|
||||||
void BroadcastState();
|
void BroadcastState();
|
||||||
void RequestBroadcastState();
|
void RequestBroadcastState();
|
||||||
@@ -52,43 +27,9 @@ public:
|
|||||||
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, 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);
|
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 ClearOscState();
|
||||||
|
void ClearOscStateForLayerKey(const std::string& layerKey);
|
||||||
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
|
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
|
||||||
RuntimePollEvents ConsumePollEvents();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct PendingOscUpdate
|
std::unique_ptr<ControlServices> mControlServices;
|
||||||
{
|
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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__
|
#define __OPENGL_COMPOSITE_H__
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <process.h>
|
|
||||||
#include <tchar.h>
|
|
||||||
#include <gl/gl.h>
|
|
||||||
#include <gl/glu.h>
|
|
||||||
|
|
||||||
#include <objbase.h>
|
#include <objbase.h>
|
||||||
#include <atlbase.h>
|
|
||||||
#include <comutil.h>
|
|
||||||
|
|
||||||
#include "GLExtensions.h"
|
#include "RenderFrameState.h"
|
||||||
#include "OpenGLRenderer.h"
|
|
||||||
#include "RuntimeHost.h"
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <atomic>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
#include <deque>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
class VideoIODevice;
|
class RenderEngine;
|
||||||
class OpenGLVideoIOBridge;
|
class RuntimeCoordinator;
|
||||||
class OpenGLRenderPass;
|
class RuntimeEventDispatcher;
|
||||||
class OpenGLRenderPipeline;
|
class RuntimeSnapshotProvider;
|
||||||
class OpenGLShaderPrograms;
|
|
||||||
class RuntimeServices;
|
class RuntimeServices;
|
||||||
|
class RuntimeStore;
|
||||||
|
class RuntimeUpdateController;
|
||||||
class ShaderBuildQueue;
|
class ShaderBuildQueue;
|
||||||
|
class VideoBackend;
|
||||||
|
|
||||||
|
|
||||||
class OpenGLComposite
|
class OpenGLComposite
|
||||||
@@ -71,55 +58,26 @@ public:
|
|||||||
private:
|
private:
|
||||||
void resizeWindow(int width, int height);
|
void resizeWindow(int width, int height);
|
||||||
bool CheckOpenGLExtensions();
|
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;
|
HWND hGLWnd;
|
||||||
HDC hGLDC;
|
HDC hGLDC;
|
||||||
HGLRC hGLRC;
|
HGLRC hGLRC;
|
||||||
CRITICAL_SECTION pMutex;
|
|
||||||
|
|
||||||
std::unique_ptr<VideoIODevice> mVideoIO;
|
std::unique_ptr<RuntimeStore> mRuntimeStore;
|
||||||
std::unique_ptr<OpenGLRenderer> mRenderer;
|
std::unique_ptr<RuntimeCoordinator> mRuntimeCoordinator;
|
||||||
std::unique_ptr<RuntimeHost> mRuntimeHost;
|
std::unique_ptr<RuntimeSnapshotProvider> mRuntimeSnapshotProvider;
|
||||||
std::unique_ptr<OpenGLVideoIOBridge> mVideoIOBridge;
|
std::unique_ptr<RuntimeEventDispatcher> mRuntimeEventDispatcher;
|
||||||
std::unique_ptr<OpenGLRenderPass> mRenderPass;
|
std::unique_ptr<RenderEngine> mRenderEngine;
|
||||||
std::unique_ptr<OpenGLRenderPipeline> mRenderPipeline;
|
|
||||||
std::unique_ptr<OpenGLShaderPrograms> mShaderPrograms;
|
|
||||||
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
|
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
|
||||||
std::unique_ptr<RuntimeServices> mRuntimeServices;
|
std::unique_ptr<RuntimeServices> mRuntimeServices;
|
||||||
std::vector<RuntimeRenderState> mCachedLayerRenderStates;
|
std::unique_ptr<RuntimeUpdateController> mRuntimeUpdateController;
|
||||||
uint64_t mCachedRenderStateVersion = 0;
|
std::unique_ptr<VideoBackend> mVideoBackend;
|
||||||
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;
|
|
||||||
|
|
||||||
bool InitOpenGLState();
|
bool InitOpenGLState();
|
||||||
void renderEffect();
|
void renderEffect();
|
||||||
bool ProcessRuntimePollResults();
|
RenderFrameInput BuildRenderFrameInput() const;
|
||||||
void RequestShaderBuild();
|
void RenderFrame(const RenderFrameInput& frameInput);
|
||||||
void ProcessScreenshotRequest();
|
|
||||||
std::filesystem::path BuildScreenshotPath() const;
|
std::filesystem::path BuildScreenshotPath() const;
|
||||||
void broadcastRuntimeState();
|
|
||||||
void resetTemporalHistoryState();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // __OPENGL_COMPOSITE_H__
|
#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 "OpenGLRenderPipeline.h"
|
||||||
|
|
||||||
|
#include "HealthTelemetry.h"
|
||||||
#include "OpenGLRenderer.h"
|
#include "OpenGLRenderer.h"
|
||||||
#include "RuntimeHost.h"
|
#include "RuntimeSnapshotProvider.h"
|
||||||
#include "VideoIOFormat.h"
|
#include "VideoIOFormat.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cstdlib>
|
||||||
#include <gl/gl.h>
|
#include <gl/gl.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
OpenGLRenderPipeline::OpenGLRenderPipeline(
|
OpenGLRenderPipeline::OpenGLRenderPipeline(
|
||||||
OpenGLRenderer& renderer,
|
OpenGLRenderer& renderer,
|
||||||
RuntimeHost& runtimeHost,
|
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
||||||
|
HealthTelemetry& healthTelemetry,
|
||||||
RenderEffectCallback renderEffect,
|
RenderEffectCallback renderEffect,
|
||||||
OutputReadyCallback outputReady,
|
OutputReadyCallback outputReady,
|
||||||
PaintCallback paint) :
|
PaintCallback paint) :
|
||||||
mRenderer(renderer),
|
mRenderer(renderer),
|
||||||
mRuntimeHost(runtimeHost),
|
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
||||||
|
mHealthTelemetry(healthTelemetry),
|
||||||
mRenderEffect(renderEffect),
|
mRenderEffect(renderEffect),
|
||||||
mOutputReady(outputReady),
|
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());
|
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
||||||
if (mOutputReady)
|
if (mOutputReady)
|
||||||
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);
|
PackOutputFor10Bit(state);
|
||||||
glFlush();
|
glFlush();
|
||||||
|
|
||||||
const auto renderEndTime = std::chrono::steady_clock::now();
|
const auto renderEndTime = std::chrono::steady_clock::now();
|
||||||
const double renderMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(renderEndTime - renderStartTime).count();
|
const double renderMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(renderEndTime - renderStartTime).count();
|
||||||
mRuntimeHost.TrySetPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds);
|
mHealthTelemetry.TryRecordPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds);
|
||||||
mRuntimeHost.TryAdvanceFrame();
|
mRuntimeSnapshotProvider.AdvanceFrame();
|
||||||
|
|
||||||
ReadOutputFrame(state, outputFrame);
|
OutputReadbackTiming readbackTiming = ReadOutputFrame(state, outputFrame);
|
||||||
if (mPaint)
|
mHealthTelemetry.TryRecordOutputRenderPipelineTiming(
|
||||||
mPaint();
|
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;
|
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)
|
void OpenGLRenderPipeline::PackOutputFor10Bit(const VideoIOState& state)
|
||||||
{
|
{
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
||||||
@@ -90,11 +130,17 @@ bool OpenGLRenderPipeline::EnsureAsyncReadbackBuffers(std::size_t requiredBytes)
|
|||||||
if (requiredBytes == 0)
|
if (requiredBytes == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (mAsyncReadbackBytes == requiredBytes && mAsyncReadbackSlots[0].pixelPackBuffer != 0)
|
if (mAsyncReadbackBytes == requiredBytes &&
|
||||||
|
mAsyncReadbackSlots.size() == mAsyncReadbackDepth &&
|
||||||
|
!mAsyncReadbackSlots.empty() &&
|
||||||
|
mAsyncReadbackSlots[0].pixelPackBuffer != 0)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
ResetAsyncReadbackState();
|
ResetAsyncReadbackState();
|
||||||
mAsyncReadbackBytes = requiredBytes;
|
mAsyncReadbackBytes = requiredBytes;
|
||||||
|
mAsyncReadbackSlots.resize(mAsyncReadbackDepth);
|
||||||
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
||||||
{
|
{
|
||||||
glGenBuffers(1, &slot.pixelPackBuffer);
|
glGenBuffers(1, &slot.pixelPackBuffer);
|
||||||
@@ -115,7 +161,7 @@ void OpenGLRenderPipeline::ResetAsyncReadbackState()
|
|||||||
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
||||||
slot.sizeBytes = 0;
|
slot.sizeBytes = 0;
|
||||||
|
|
||||||
if (mAsyncReadbackSlots[0].pixelPackBuffer != 0)
|
if (!mAsyncReadbackSlots.empty() && mAsyncReadbackSlots[0].pixelPackBuffer != 0)
|
||||||
{
|
{
|
||||||
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
||||||
{
|
{
|
||||||
@@ -130,6 +176,7 @@ void OpenGLRenderPipeline::ResetAsyncReadbackState()
|
|||||||
mAsyncReadbackWriteIndex = 0;
|
mAsyncReadbackWriteIndex = 0;
|
||||||
mAsyncReadbackReadIndex = 0;
|
mAsyncReadbackReadIndex = 0;
|
||||||
mAsyncReadbackBytes = 0;
|
mAsyncReadbackBytes = 0;
|
||||||
|
mAsyncReadbackSlots.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLRenderPipeline::FlushAsyncReadbackPipeline()
|
void OpenGLRenderPipeline::FlushAsyncReadbackPipeline()
|
||||||
@@ -148,18 +195,29 @@ void OpenGLRenderPipeline::FlushAsyncReadbackPipeline()
|
|||||||
mAsyncReadbackReadIndex = 0;
|
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 std::size_t requiredBytes = static_cast<std::size_t>(state.outputFrameRowBytes) * state.outputFrameSize.height;
|
||||||
const GLenum format = usePackedOutput ? GL_RGBA : GL_BGRA;
|
const GLenum format = useTenBitPackedOutput ? GL_RGBA : GL_BGRA;
|
||||||
const GLenum type = usePackedOutput ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8_REV;
|
const GLenum type = useTenBitPackedOutput ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||||
const GLuint framebuffer = usePackedOutput ? mRenderer.OutputPackFramebuffer() : mRenderer.OutputFramebuffer();
|
const GLuint framebuffer = usePackFramebuffer ? mRenderer.OutputPackFramebuffer() : mRenderer.OutputFramebuffer();
|
||||||
const GLsizei readWidth = static_cast<GLsizei>(usePackedOutput ? state.outputPackTextureWidth : state.outputFrameSize.width);
|
const GLsizei readWidth = static_cast<GLsizei>(useTenBitPackedOutput ? state.outputPackTextureWidth : state.outputFrameSize.width);
|
||||||
const GLsizei readHeight = static_cast<GLsizei>(state.outputFrameSize.height);
|
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)
|
if (requiredBytes == 0)
|
||||||
return;
|
{
|
||||||
|
finishTiming();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (mAsyncReadbackBytes != requiredBytes
|
if (mAsyncReadbackBytes != requiredBytes
|
||||||
|| mAsyncReadbackFormat != format
|
|| mAsyncReadbackFormat != format
|
||||||
@@ -170,30 +228,56 @@ void OpenGLRenderPipeline::QueueAsyncReadback(const VideoIOState& state)
|
|||||||
mAsyncReadbackType = type;
|
mAsyncReadbackType = type;
|
||||||
mAsyncReadbackFramebuffer = framebuffer;
|
mAsyncReadbackFramebuffer = framebuffer;
|
||||||
if (!EnsureAsyncReadbackBuffers(requiredBytes))
|
if (!EnsureAsyncReadbackBuffers(requiredBytes))
|
||||||
return;
|
{
|
||||||
|
finishTiming();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mAsyncReadbackSlots.empty())
|
||||||
|
{
|
||||||
|
finishTiming();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncReadbackSlot& slot = mAsyncReadbackSlots[mAsyncReadbackWriteIndex];
|
AsyncReadbackSlot& slot = mAsyncReadbackSlots[mAsyncReadbackWriteIndex];
|
||||||
if (slot.fence != nullptr)
|
if (slot.inFlight)
|
||||||
{
|
{
|
||||||
glDeleteSync(slot.fence);
|
finishTiming();
|
||||||
slot.fence = nullptr;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto stageStartTime = std::chrono::steady_clock::now();
|
||||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
|
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);
|
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);
|
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);
|
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;
|
slot.inFlight = slot.fence != nullptr;
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||||
|
|
||||||
mAsyncReadbackWriteIndex = (mAsyncReadbackWriteIndex + 1) % mAsyncReadbackSlots.size();
|
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)
|
if (mAsyncReadbackBytes == 0 || outputFrame.bytes == nullptr)
|
||||||
return false;
|
return false;
|
||||||
@@ -203,15 +287,24 @@ bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFra
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
const GLenum waitFlags = timeoutNanoseconds > 0 ? GL_SYNC_FLUSH_COMMANDS_BIT : 0;
|
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 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)
|
if (waitResult != GL_ALREADY_SIGNALED && waitResult != GL_CONDITION_SATISFIED)
|
||||||
|
{
|
||||||
|
timing.asyncReadbackMissed = true;
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
glDeleteSync(slot.fence);
|
glDeleteSync(slot.fence);
|
||||||
slot.fence = nullptr;
|
slot.fence = nullptr;
|
||||||
|
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
|
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);
|
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)
|
if (mappedBytes == nullptr)
|
||||||
{
|
{
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||||
@@ -220,7 +313,10 @@ bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFra
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto copyStartTime = std::chrono::steady_clock::now();
|
||||||
std::memcpy(outputFrame.bytes, mappedBytes, slot.sizeBytes);
|
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);
|
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||||
|
|
||||||
@@ -240,40 +336,144 @@ void OpenGLRenderPipeline::CacheOutputFrame(const VideoIOOutputFrame& outputFram
|
|||||||
std::memcpy(mCachedOutputFrame.data(), outputFrame.bytes, byteCount);
|
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 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_ALIGNMENT, 4);
|
||||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||||
if (usePackedOutput)
|
if (usePackFramebuffer)
|
||||||
{
|
{
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
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
|
else
|
||||||
{
|
{
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
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);
|
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);
|
if (outputFrame.bytes != nullptr)
|
||||||
return;
|
{
|
||||||
|
ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
|
||||||
|
CacheOutputFrame(outputFrame);
|
||||||
|
}
|
||||||
|
return timing;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If async readback misses the playout deadline, prefer a fresh synchronous
|
if (mOutputReadbackMode == OutputReadbackMode::CachedOnly)
|
||||||
// frame over reusing stale cached output, then restart the async pipeline.
|
|
||||||
if (outputFrame.bytes != nullptr)
|
|
||||||
{
|
{
|
||||||
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);
|
CacheOutputFrame(outputFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
FlushAsyncReadbackPipeline();
|
if (!queued)
|
||||||
QueueAsyncReadback(state);
|
(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 "GLExtensions.h"
|
||||||
#include "VideoIOTypes.h"
|
#include "VideoIOTypes.h"
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class OpenGLRenderer;
|
class OpenGLRenderer;
|
||||||
class RuntimeHost;
|
class HealthTelemetry;
|
||||||
|
class RuntimeSnapshotProvider;
|
||||||
|
|
||||||
struct RenderPipelineFrameContext
|
struct RenderPipelineFrameContext
|
||||||
{
|
{
|
||||||
@@ -25,7 +25,8 @@ public:
|
|||||||
|
|
||||||
OpenGLRenderPipeline(
|
OpenGLRenderPipeline(
|
||||||
OpenGLRenderer& renderer,
|
OpenGLRenderer& renderer,
|
||||||
RuntimeHost& runtimeHost,
|
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
||||||
|
HealthTelemetry& healthTelemetry,
|
||||||
RenderEffectCallback renderEffect,
|
RenderEffectCallback renderEffect,
|
||||||
OutputReadyCallback outputReady,
|
OutputReadyCallback outputReady,
|
||||||
PaintCallback paint);
|
PaintCallback paint);
|
||||||
@@ -34,6 +35,13 @@ public:
|
|||||||
bool RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
|
bool RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum class OutputReadbackMode
|
||||||
|
{
|
||||||
|
AsyncPbo,
|
||||||
|
Synchronous,
|
||||||
|
CachedOnly
|
||||||
|
};
|
||||||
|
|
||||||
struct AsyncReadbackSlot
|
struct AsyncReadbackSlot
|
||||||
{
|
{
|
||||||
GLuint pixelPackBuffer = 0;
|
GLuint pixelPackBuffer = 0;
|
||||||
@@ -42,22 +50,46 @@ private:
|
|||||||
bool inFlight = false;
|
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);
|
bool EnsureAsyncReadbackBuffers(std::size_t requiredBytes);
|
||||||
void ResetAsyncReadbackState();
|
void ResetAsyncReadbackState();
|
||||||
void FlushAsyncReadbackPipeline();
|
void FlushAsyncReadbackPipeline();
|
||||||
void QueueAsyncReadback(const VideoIOState& state);
|
bool QueueAsyncReadback(const VideoIOState& state, OutputReadbackTiming& timing);
|
||||||
bool TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds);
|
bool TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds, OutputReadbackTiming& timing);
|
||||||
void CacheOutputFrame(const VideoIOOutputFrame& outputFrame);
|
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 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;
|
OpenGLRenderer& mRenderer;
|
||||||
RuntimeHost& mRuntimeHost;
|
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||||
|
HealthTelemetry& mHealthTelemetry;
|
||||||
RenderEffectCallback mRenderEffect;
|
RenderEffectCallback mRenderEffect;
|
||||||
OutputReadyCallback mOutputReady;
|
OutputReadyCallback mOutputReady;
|
||||||
PaintCallback mPaint;
|
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 mAsyncReadbackWriteIndex = 0;
|
||||||
std::size_t mAsyncReadbackReadIndex = 0;
|
std::size_t mAsyncReadbackReadIndex = 0;
|
||||||
std::size_t mAsyncReadbackBytes = 0;
|
std::size_t mAsyncReadbackBytes = 0;
|
||||||
|
|||||||
@@ -1,124 +1,25 @@
|
|||||||
#include "OpenGLVideoIOBridge.h"
|
#include "OpenGLVideoIOBridge.h"
|
||||||
|
|
||||||
#include "OpenGLRenderer.h"
|
#include "RenderEngine.h"
|
||||||
#include "RuntimeHost.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
OpenGLVideoIOBridge::OpenGLVideoIOBridge(RenderEngine& renderEngine) :
|
||||||
#include <gl/gl.h>
|
mRenderEngine(renderEngine)
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
|
||||||
return; // don't transfer texture when there's no input
|
return; // don't transfer texture when there's no input
|
||||||
|
|
||||||
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height);
|
mRenderEngine.QueueInputFrame(inputFrame, state);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
RenderPipelineFrameContext frameContext;
|
||||||
frameContext.videoState = state;
|
frameContext.videoState = state;
|
||||||
frameContext.completion = completion;
|
frameContext.completion = completion;
|
||||||
|
|
||||||
EnterCriticalSection(&mMutex);
|
return mRenderEngine.RequestOutputFrame(frameContext, outputFrame);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,43 +2,16 @@
|
|||||||
|
|
||||||
#include "OpenGLRenderPipeline.h"
|
#include "OpenGLRenderPipeline.h"
|
||||||
|
|
||||||
#include <windows.h>
|
class RenderEngine;
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
class RuntimeHost;
|
|
||||||
|
|
||||||
class OpenGLVideoIOBridge
|
class OpenGLVideoIOBridge
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
OpenGLVideoIOBridge(
|
explicit OpenGLVideoIOBridge(RenderEngine& renderEngine);
|
||||||
VideoIODevice& videoIO,
|
|
||||||
OpenGLRenderer& renderer,
|
|
||||||
OpenGLRenderPipeline& renderPipeline,
|
|
||||||
RuntimeHost& runtimeHost,
|
|
||||||
CRITICAL_SECTION& mutex,
|
|
||||||
HDC hdc,
|
|
||||||
HGLRC hglrc);
|
|
||||||
|
|
||||||
void VideoFrameArrived(const VideoIOFrame& inputFrame);
|
void UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state);
|
||||||
void PlayoutFrameCompleted(const VideoIOCompletion& completion);
|
bool RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RecordFramePacing(VideoIOCompletionResult completionResult);
|
RenderEngine& mRenderEngine;
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,4 +7,3 @@ constexpr GLuint kDecodedVideoTextureUnit = 1;
|
|||||||
constexpr GLuint kSourceHistoryTextureUnitBase = 2;
|
constexpr GLuint kSourceHistoryTextureUnitBase = 2;
|
||||||
constexpr GLuint kPackedVideoTextureUnit = 2;
|
constexpr GLuint kPackedVideoTextureUnit = 2;
|
||||||
constexpr GLuint kGlobalParamsBindingPoint = 0;
|
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),
|
mRenderer(renderer),
|
||||||
mRuntimeHost(runtimeHost),
|
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
||||||
mGlobalParamsBuffer(renderer),
|
mGlobalParamsBuffer(renderer),
|
||||||
mCompiler(renderer, runtimeHost, mTextureBindings)
|
mCompiler(renderer, runtimeSnapshotProvider, mTextureBindings)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
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;
|
std::string temporalError;
|
||||||
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames();
|
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
|
||||||
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(layerStates, historyCap, temporalError))
|
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(layerStates, historyCap, temporalError))
|
||||||
{
|
{
|
||||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||||
@@ -87,10 +89,7 @@ bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsign
|
|||||||
|
|
||||||
DestroyLayerPrograms();
|
DestroyLayerPrograms();
|
||||||
mRenderer.ReplaceLayerPrograms(newPrograms);
|
mRenderer.ReplaceLayerPrograms(newPrograms);
|
||||||
mCommittedLayerStates = layerStates;
|
mCommittedLayerStates = renderSnapshot.states;
|
||||||
|
|
||||||
mRuntimeHost.SetCompileStatus(true, "Shader layers compiled successfully.");
|
|
||||||
mRuntimeHost.ClearReloadRequest();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -104,19 +103,19 @@ bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string temporalError;
|
std::string temporalError;
|
||||||
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames();
|
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
|
||||||
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.layerStates, historyCap, temporalError))
|
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.renderSnapshot.states, historyCap, temporalError))
|
||||||
{
|
{
|
||||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||||
return false;
|
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);
|
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (mRenderer.ResourcesInitialized() &&
|
if (mRenderer.ResourcesInitialized() &&
|
||||||
!mRenderer.FeedbackBuffers().EnsureResources(preparedBuild.layerStates, inputFrameWidth, inputFrameHeight, temporalError))
|
!mRenderer.FeedbackBuffers().EnsureResources(preparedBuild.renderSnapshot.states, inputFrameWidth, inputFrameHeight, temporalError))
|
||||||
{
|
{
|
||||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||||
return false;
|
return false;
|
||||||
@@ -150,10 +149,7 @@ bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild
|
|||||||
|
|
||||||
DestroyLayerPrograms();
|
DestroyLayerPrograms();
|
||||||
mRenderer.ReplaceLayerPrograms(newPrograms);
|
mRenderer.ReplaceLayerPrograms(newPrograms);
|
||||||
mCommittedLayerStates = preparedBuild.layerStates;
|
mCommittedLayerStates = preparedBuild.renderSnapshot.states;
|
||||||
|
|
||||||
mRuntimeHost.SetCompileStatus(true, "Shader layers compiled successfully.");
|
|
||||||
mRuntimeHost.ClearReloadRequest();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include "GlobalParamsBuffer.h"
|
#include "GlobalParamsBuffer.h"
|
||||||
#include "OpenGLRenderer.h"
|
#include "OpenGLRenderer.h"
|
||||||
#include "RuntimeHost.h"
|
#include "RuntimeSnapshotProvider.h"
|
||||||
#include "ShaderBuildQueue.h"
|
#include "ShaderBuildQueue.h"
|
||||||
#include "ShaderTypes.h"
|
#include "ShaderTypes.h"
|
||||||
#include "ShaderProgramCompiler.h"
|
#include "ShaderProgramCompiler.h"
|
||||||
@@ -15,7 +15,7 @@ class OpenGLShaderPrograms
|
|||||||
public:
|
public:
|
||||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
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 CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
|
||||||
bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, 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:
|
private:
|
||||||
OpenGLRenderer& mRenderer;
|
OpenGLRenderer& mRenderer;
|
||||||
RuntimeHost& mRuntimeHost;
|
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||||
ShaderTextureBindings mTextureBindings;
|
ShaderTextureBindings mTextureBindings;
|
||||||
GlobalParamsBuffer mGlobalParamsBuffer;
|
GlobalParamsBuffer mGlobalParamsBuffer;
|
||||||
ShaderProgramCompiler mCompiler;
|
ShaderProgramCompiler mCompiler;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "ShaderBuildQueue.h"
|
#include "ShaderBuildQueue.h"
|
||||||
|
|
||||||
#include "RuntimeHost.h"
|
#include "RuntimeEventDispatcher.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -10,8 +10,9 @@ namespace
|
|||||||
constexpr auto kShaderBuildDebounce = std::chrono::milliseconds(400);
|
constexpr auto kShaderBuildDebounce = std::chrono::milliseconds(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderBuildQueue::ShaderBuildQueue(RuntimeHost& runtimeHost) :
|
ShaderBuildQueue::ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeEventDispatcher& runtimeEventDispatcher) :
|
||||||
mRuntimeHost(runtimeHost),
|
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
||||||
|
mRuntimeEventDispatcher(runtimeEventDispatcher),
|
||||||
mWorkerThread([this]() { WorkerLoop(); })
|
mWorkerThread([this]() { WorkerLoop(); })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -46,6 +47,18 @@ bool ShaderBuildQueue::TryConsumeReadyBuild(PreparedShaderBuild& build)
|
|||||||
return true;
|
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()
|
void ShaderBuildQueue::Stop()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
@@ -99,13 +112,20 @@ void ShaderBuildQueue::WorkerLoop()
|
|||||||
|
|
||||||
PreparedShaderBuild build = Build(generation, outputWidth, outputHeight);
|
PreparedShaderBuild build = Build(generation, outputWidth, outputHeight);
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
bool shouldPublish = false;
|
||||||
if (mStopping)
|
{
|
||||||
return;
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
if (generation != mRequestedGeneration)
|
if (mStopping)
|
||||||
continue;
|
return;
|
||||||
mReadyBuild = std::move(build);
|
if (generation != mRequestedGeneration)
|
||||||
mHasReadyBuild = true;
|
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;
|
PreparedShaderBuild build;
|
||||||
build.generation = generation;
|
build.generation = generation;
|
||||||
build.layerStates = mRuntimeHost.GetLayerRenderStates(outputWidth, outputHeight);
|
build.renderSnapshot = mRuntimeSnapshotProvider.PublishRenderStateSnapshot(outputWidth, outputHeight);
|
||||||
build.layers.reserve(build.layerStates.size());
|
build.layers.reserve(build.renderSnapshot.states.size());
|
||||||
|
|
||||||
for (const RuntimeRenderState& state : build.layerStates)
|
for (const RuntimeRenderState& state : build.renderSnapshot.states)
|
||||||
{
|
{
|
||||||
PreparedLayerShader layer;
|
PreparedLayerShader layer;
|
||||||
layer.state = state;
|
layer.state = state;
|
||||||
if (!mRuntimeHost.BuildLayerPassFragmentShaderSources(state.layerId, layer.passes, build.message))
|
if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, layer.passes, build.message))
|
||||||
{
|
{
|
||||||
build.succeeded = false;
|
build.succeeded = false;
|
||||||
return build;
|
return build;
|
||||||
@@ -132,3 +152,15 @@ PreparedShaderBuild ShaderBuildQueue::Build(uint64_t generation, unsigned output
|
|||||||
build.message = "Shader layers prepared successfully.";
|
build.message = "Shader layers prepared successfully.";
|
||||||
return build;
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include "RuntimeSnapshotProvider.h"
|
||||||
#include "ShaderTypes.h"
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
@@ -9,7 +10,7 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class RuntimeHost;
|
class RuntimeEventDispatcher;
|
||||||
|
|
||||||
struct PreparedLayerShader
|
struct PreparedLayerShader
|
||||||
{
|
{
|
||||||
@@ -22,14 +23,14 @@ struct PreparedShaderBuild
|
|||||||
uint64_t generation = 0;
|
uint64_t generation = 0;
|
||||||
bool succeeded = false;
|
bool succeeded = false;
|
||||||
std::string message;
|
std::string message;
|
||||||
std::vector<RuntimeRenderState> layerStates;
|
RuntimeRenderStateSnapshot renderSnapshot;
|
||||||
std::vector<PreparedLayerShader> layers;
|
std::vector<PreparedLayerShader> layers;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ShaderBuildQueue
|
class ShaderBuildQueue
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit ShaderBuildQueue(RuntimeHost& runtimeHost);
|
ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeEventDispatcher& runtimeEventDispatcher);
|
||||||
~ShaderBuildQueue();
|
~ShaderBuildQueue();
|
||||||
|
|
||||||
ShaderBuildQueue(const ShaderBuildQueue&) = delete;
|
ShaderBuildQueue(const ShaderBuildQueue&) = delete;
|
||||||
@@ -37,13 +38,16 @@ public:
|
|||||||
|
|
||||||
void RequestBuild(unsigned outputWidth, unsigned outputHeight);
|
void RequestBuild(unsigned outputWidth, unsigned outputHeight);
|
||||||
bool TryConsumeReadyBuild(PreparedShaderBuild& build);
|
bool TryConsumeReadyBuild(PreparedShaderBuild& build);
|
||||||
|
bool TryConsumeReadyBuild(uint64_t expectedGeneration, PreparedShaderBuild& build);
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void WorkerLoop();
|
void WorkerLoop();
|
||||||
PreparedShaderBuild Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight);
|
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::thread mWorkerThread;
|
||||||
std::mutex mMutex;
|
std::mutex mMutex;
|
||||||
std::condition_variable mCondition;
|
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),
|
mRenderer(renderer),
|
||||||
mRuntimeHost(runtimeHost),
|
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
||||||
mTextureBindings(textureBindings)
|
mTextureBindings(textureBindings)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ bool ShaderProgramCompiler::CompileLayerProgram(const RuntimeRenderState& state,
|
|||||||
std::vector<ShaderPassBuildSource> passSources;
|
std::vector<ShaderPassBuildSource> passSources;
|
||||||
std::string loadError;
|
std::string loadError;
|
||||||
|
|
||||||
if (!mRuntimeHost.BuildLayerPassFragmentShaderSources(state.layerId, passSources, loadError))
|
if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, passSources, loadError))
|
||||||
{
|
{
|
||||||
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
|
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
|
||||||
return false;
|
return false;
|
||||||
@@ -117,7 +117,7 @@ bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState
|
|||||||
passProgram.passId = passSource.passId;
|
passProgram.passId = passSource.passId;
|
||||||
passProgram.inputNames = passSource.inputNames;
|
passProgram.inputNames = passSource.inputNames;
|
||||||
passProgram.outputName = passSource.outputName;
|
passProgram.outputName = passSource.outputName;
|
||||||
passProgram.shaderTextureBase = mTextureBindings.ResolveShaderTextureBase(state, mRuntimeHost.GetMaxTemporalHistoryFrames());
|
passProgram.shaderTextureBase = mTextureBindings.ResolveShaderTextureBase(state, mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames());
|
||||||
passProgram.textureBindings.swap(textureBindings);
|
passProgram.textureBindings.swap(textureBindings);
|
||||||
passProgram.textBindings.swap(textBindings);
|
passProgram.textBindings.swap(textBindings);
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState
|
|||||||
if (globalParamsIndex != GL_INVALID_INDEX)
|
if (globalParamsIndex != GL_INVALID_INDEX)
|
||||||
glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint);
|
glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint);
|
||||||
|
|
||||||
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames();
|
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
|
||||||
glUseProgram(newProgram.get());
|
glUseProgram(newProgram.get());
|
||||||
mTextureBindings.AssignLayerSamplerUniforms(newProgram.get(), state, passProgram, historyCap);
|
mTextureBindings.AssignLayerSamplerUniforms(newProgram.get(), state, passProgram, historyCap);
|
||||||
glUseProgram(0);
|
glUseProgram(0);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "OpenGLRenderer.h"
|
#include "OpenGLRenderer.h"
|
||||||
#include "RuntimeHost.h"
|
#include "RuntimeSnapshotProvider.h"
|
||||||
#include "ShaderTextureBindings.h"
|
#include "ShaderTextureBindings.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -13,7 +13,7 @@ public:
|
|||||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
using LayerProgram = OpenGLRenderer::LayerProgram;
|
||||||
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
|
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 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);
|
bool CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::vector<ShaderPassBuildSource>& passSources, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
||||||
@@ -22,6 +22,6 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
OpenGLRenderer& mRenderer;
|
OpenGLRenderer& mRenderer;
|
||||||
RuntimeHost& mRuntimeHost;
|
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||||
ShaderTextureBindings& mTextureBindings;
|
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