Compare commits
47 Commits
v0.0.3
...
120f899b0d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
120f899b0d | ||
|
|
41075bbc61 | ||
|
|
7f0f60c0e3 | ||
|
|
739231d5a1 | ||
|
|
3629227aa9 | ||
|
|
618831d578 | ||
|
|
c38c22834d | ||
|
|
c8a4bd4c7b | ||
|
|
46129a6044 | ||
|
|
8fcb51d140 | ||
|
|
944773c248 | ||
|
|
7777cfc194 | ||
|
|
198639ae3f | ||
|
|
d7ca42b51b | ||
|
|
f11d531e0c | ||
|
|
a3635b5d31 | ||
|
|
bc9aa6fbad | ||
|
|
0c16665610 | ||
|
|
46f2f1ece5 | ||
|
|
4ffbb97abf | ||
|
|
98f5cbe309 | ||
|
|
93d856b3b6 | ||
| 6ea6971dd6 | |||
| 163d70e9bd | |||
| 8afef5065a | |||
| 27bf2ae45c | |||
| 1ea44ba3ae | |||
| 0af9a72937 | |||
| d650cac857 | |||
| a0cc86f189 | |||
| f322abf79a | |||
| eede6938cb | |||
| ad24a20fdb | |||
| 5ae43513a7 | |||
| cc23e73d51 | |||
| f85abef237 | |||
| 596d370f43 | |||
| 87cb55b80b | |||
| f458eb0130 | |||
| 7d8f9a39d1 | |||
| 5b6e30ad13 | |||
| 07a5c91427 | |||
| 53b980913b | |||
| 4e2ac4a091 | |||
| 3eb5bb5de3 | |||
| ebbc11bb34 | |||
| 6d5a606107 |
@@ -59,7 +59,7 @@ jobs:
|
|||||||
shell: powershell
|
shell: powershell
|
||||||
run: cmake --build --preset build-debug
|
run: cmake --build --preset build-debug
|
||||||
|
|
||||||
- name: Run Native Tests
|
- 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
|
||||||
|
|
||||||
|
|||||||
179
CMakeLists.txt
179
CMakeLists.txt
@@ -31,57 +31,69 @@ if(NOT EXISTS "${SLANG_LICENSE_FILE}")
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(APP_SOURCES
|
set(APP_SOURCES
|
||||||
"${APP_DIR}/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/RuntimeServices.cpp"
|
"${APP_DIR}/control/RuntimeServices.cpp"
|
||||||
"${APP_DIR}/control/RuntimeServices.h"
|
"${APP_DIR}/control/RuntimeServices.h"
|
||||||
"${APP_DIR}/decklink/DeckLinkDisplayMode.cpp"
|
"${APP_DIR}/videoio/decklink/DeckLinkAPI_h.h"
|
||||||
"${APP_DIR}/decklink/DeckLinkDisplayMode.h"
|
"${APP_DIR}/videoio/decklink/DeckLinkDisplayMode.cpp"
|
||||||
"${APP_DIR}/decklink/DeckLinkFrameTransfer.cpp"
|
"${APP_DIR}/videoio/decklink/DeckLinkDisplayMode.h"
|
||||||
"${APP_DIR}/decklink/DeckLinkFrameTransfer.h"
|
"${APP_DIR}/videoio/decklink/DeckLinkFrameTransfer.cpp"
|
||||||
"${APP_DIR}/decklink/DeckLinkSession.cpp"
|
"${APP_DIR}/videoio/decklink/DeckLinkFrameTransfer.h"
|
||||||
"${APP_DIR}/decklink/DeckLinkSession.h"
|
"${APP_DIR}/videoio/decklink/DeckLinkSession.cpp"
|
||||||
"${APP_DIR}/decklink/VideoIOFormat.cpp"
|
"${APP_DIR}/videoio/decklink/DeckLinkSession.h"
|
||||||
"${APP_DIR}/decklink/VideoIOFormat.h"
|
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.cpp"
|
||||||
"${APP_DIR}/gl/GLExtensions.cpp"
|
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.h"
|
||||||
"${APP_DIR}/gl/GLExtensions.h"
|
"${APP_DIR}/gl/renderer/GLExtensions.cpp"
|
||||||
"${APP_DIR}/gl/GlobalParamsBuffer.cpp"
|
"${APP_DIR}/gl/renderer/GLExtensions.h"
|
||||||
"${APP_DIR}/gl/GlobalParamsBuffer.h"
|
"${APP_DIR}/gl/shader/GlobalParamsBuffer.cpp"
|
||||||
"${APP_DIR}/gl/GlRenderConstants.h"
|
"${APP_DIR}/gl/shader/GlobalParamsBuffer.h"
|
||||||
"${APP_DIR}/gl/GlScopedObjects.h"
|
"${APP_DIR}/gl/renderer/GlRenderConstants.h"
|
||||||
"${APP_DIR}/gl/GlShaderSources.cpp"
|
"${APP_DIR}/gl/renderer/GlScopedObjects.h"
|
||||||
"${APP_DIR}/gl/GlShaderSources.h"
|
"${APP_DIR}/gl/shader/GlShaderSources.cpp"
|
||||||
|
"${APP_DIR}/gl/shader/GlShaderSources.h"
|
||||||
"${APP_DIR}/gl/OpenGLComposite.cpp"
|
"${APP_DIR}/gl/OpenGLComposite.cpp"
|
||||||
"${APP_DIR}/gl/OpenGLComposite.h"
|
"${APP_DIR}/gl/OpenGLComposite.h"
|
||||||
"${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp"
|
"${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp"
|
||||||
"${APP_DIR}/gl/OpenGLDeckLinkBridge.cpp"
|
"${APP_DIR}/gl/RenderEngine.cpp"
|
||||||
"${APP_DIR}/gl/OpenGLDeckLinkBridge.h"
|
"${APP_DIR}/gl/RenderEngine.h"
|
||||||
"${APP_DIR}/gl/OpenGLRenderPass.cpp"
|
"${APP_DIR}/gl/pipeline/OpenGLRenderPass.cpp"
|
||||||
"${APP_DIR}/gl/OpenGLRenderPass.h"
|
"${APP_DIR}/gl/pipeline/OpenGLRenderPass.h"
|
||||||
"${APP_DIR}/gl/OpenGLRenderer.cpp"
|
"${APP_DIR}/gl/pipeline/OpenGLRenderPipeline.cpp"
|
||||||
"${APP_DIR}/gl/OpenGLRenderer.h"
|
"${APP_DIR}/gl/pipeline/OpenGLRenderPipeline.h"
|
||||||
"${APP_DIR}/gl/OpenGLShaderPrograms.cpp"
|
"${APP_DIR}/gl/pipeline/RenderPassDescriptor.h"
|
||||||
"${APP_DIR}/gl/OpenGLShaderPrograms.h"
|
"${APP_DIR}/gl/pipeline/ShaderFeedbackBuffers.cpp"
|
||||||
"${APP_DIR}/gl/PngScreenshotWriter.cpp"
|
"${APP_DIR}/gl/pipeline/ShaderFeedbackBuffers.h"
|
||||||
"${APP_DIR}/gl/PngScreenshotWriter.h"
|
"${APP_DIR}/gl/renderer/OpenGLRenderer.cpp"
|
||||||
"${APP_DIR}/gl/ShaderProgramCompiler.cpp"
|
"${APP_DIR}/gl/renderer/OpenGLRenderer.h"
|
||||||
"${APP_DIR}/gl/ShaderProgramCompiler.h"
|
"${APP_DIR}/gl/renderer/RenderTargetPool.cpp"
|
||||||
"${APP_DIR}/gl/ShaderBuildQueue.cpp"
|
"${APP_DIR}/gl/renderer/RenderTargetPool.h"
|
||||||
"${APP_DIR}/gl/ShaderBuildQueue.h"
|
"${APP_DIR}/gl/pipeline/OpenGLVideoIOBridge.cpp"
|
||||||
"${APP_DIR}/gl/ShaderTextureBindings.cpp"
|
"${APP_DIR}/gl/pipeline/OpenGLVideoIOBridge.h"
|
||||||
"${APP_DIR}/gl/ShaderTextureBindings.h"
|
"${APP_DIR}/gl/shader/OpenGLShaderPrograms.cpp"
|
||||||
"${APP_DIR}/gl/Std140Buffer.h"
|
"${APP_DIR}/gl/shader/OpenGLShaderPrograms.h"
|
||||||
"${APP_DIR}/gl/TextRasterizer.cpp"
|
"${APP_DIR}/gl/pipeline/PngScreenshotWriter.cpp"
|
||||||
"${APP_DIR}/gl/TextRasterizer.h"
|
"${APP_DIR}/gl/pipeline/PngScreenshotWriter.h"
|
||||||
"${APP_DIR}/gl/TextureAssetLoader.cpp"
|
"${APP_DIR}/gl/shader/ShaderProgramCompiler.cpp"
|
||||||
"${APP_DIR}/gl/TextureAssetLoader.h"
|
"${APP_DIR}/gl/shader/ShaderProgramCompiler.h"
|
||||||
"${APP_DIR}/gl/TemporalHistoryBuffers.cpp"
|
"${APP_DIR}/gl/shader/ShaderBuildQueue.cpp"
|
||||||
"${APP_DIR}/gl/TemporalHistoryBuffers.h"
|
"${APP_DIR}/gl/shader/ShaderBuildQueue.h"
|
||||||
|
"${APP_DIR}/gl/shader/ShaderTextureBindings.cpp"
|
||||||
|
"${APP_DIR}/gl/shader/ShaderTextureBindings.h"
|
||||||
|
"${APP_DIR}/gl/shader/Std140Buffer.h"
|
||||||
|
"${APP_DIR}/gl/shader/TextRasterizer.cpp"
|
||||||
|
"${APP_DIR}/gl/shader/TextRasterizer.h"
|
||||||
|
"${APP_DIR}/gl/shader/TextureAssetLoader.cpp"
|
||||||
|
"${APP_DIR}/gl/shader/TextureAssetLoader.h"
|
||||||
|
"${APP_DIR}/gl/pipeline/TemporalHistoryBuffers.cpp"
|
||||||
|
"${APP_DIR}/gl/pipeline/TemporalHistoryBuffers.h"
|
||||||
"${APP_DIR}/LoopThroughWithOpenGLCompositing.cpp"
|
"${APP_DIR}/LoopThroughWithOpenGLCompositing.cpp"
|
||||||
"${APP_DIR}/LoopThroughWithOpenGLCompositing.h"
|
"${APP_DIR}/LoopThroughWithOpenGLCompositing.h"
|
||||||
"${APP_DIR}/LoopThroughWithOpenGLCompositing.rc"
|
"${APP_DIR}/LoopThroughWithOpenGLCompositing.rc"
|
||||||
@@ -90,12 +102,20 @@ set(APP_SOURCES
|
|||||||
"${APP_DIR}/resource.h"
|
"${APP_DIR}/resource.h"
|
||||||
"${APP_DIR}/runtime/RuntimeHost.cpp"
|
"${APP_DIR}/runtime/RuntimeHost.cpp"
|
||||||
"${APP_DIR}/runtime/RuntimeHost.h"
|
"${APP_DIR}/runtime/RuntimeHost.h"
|
||||||
|
"${APP_DIR}/runtime/HealthTelemetry.cpp"
|
||||||
|
"${APP_DIR}/runtime/HealthTelemetry.h"
|
||||||
|
"${APP_DIR}/runtime/RuntimeCoordinator.cpp"
|
||||||
|
"${APP_DIR}/runtime/RuntimeCoordinator.h"
|
||||||
|
"${APP_DIR}/runtime/RuntimeSnapshotProvider.cpp"
|
||||||
|
"${APP_DIR}/runtime/RuntimeSnapshotProvider.h"
|
||||||
"${APP_DIR}/runtime/RuntimeClock.cpp"
|
"${APP_DIR}/runtime/RuntimeClock.cpp"
|
||||||
"${APP_DIR}/runtime/RuntimeClock.h"
|
"${APP_DIR}/runtime/RuntimeClock.h"
|
||||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
||||||
"${APP_DIR}/runtime/RuntimeJson.h"
|
"${APP_DIR}/runtime/RuntimeJson.h"
|
||||||
"${APP_DIR}/runtime/RuntimeParameterUtils.cpp"
|
"${APP_DIR}/runtime/RuntimeParameterUtils.cpp"
|
||||||
"${APP_DIR}/runtime/RuntimeParameterUtils.h"
|
"${APP_DIR}/runtime/RuntimeParameterUtils.h"
|
||||||
|
"${APP_DIR}/runtime/RuntimeStore.cpp"
|
||||||
|
"${APP_DIR}/runtime/RuntimeStore.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"
|
||||||
@@ -104,6 +124,13 @@ set(APP_SOURCES
|
|||||||
"${APP_DIR}/stdafx.cpp"
|
"${APP_DIR}/stdafx.cpp"
|
||||||
"${APP_DIR}/stdafx.h"
|
"${APP_DIR}/stdafx.h"
|
||||||
"${APP_DIR}/targetver.h"
|
"${APP_DIR}/targetver.h"
|
||||||
|
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
||||||
|
"${APP_DIR}/videoio/VideoIOFormat.h"
|
||||||
|
"${APP_DIR}/videoio/VideoBackend.cpp"
|
||||||
|
"${APP_DIR}/videoio/VideoBackend.h"
|
||||||
|
"${APP_DIR}/videoio/VideoIOTypes.h"
|
||||||
|
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
|
||||||
|
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(LoopThroughWithOpenGLCompositing WIN32 ${APP_SOURCES})
|
add_executable(LoopThroughWithOpenGLCompositing WIN32 ${APP_SOURCES})
|
||||||
@@ -111,11 +138,15 @@ add_executable(LoopThroughWithOpenGLCompositing WIN32 ${APP_SOURCES})
|
|||||||
target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE
|
target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE
|
||||||
"${APP_DIR}"
|
"${APP_DIR}"
|
||||||
"${APP_DIR}/control"
|
"${APP_DIR}/control"
|
||||||
"${APP_DIR}/decklink"
|
|
||||||
"${APP_DIR}/gl"
|
"${APP_DIR}/gl"
|
||||||
|
"${APP_DIR}/gl/pipeline"
|
||||||
|
"${APP_DIR}/gl/renderer"
|
||||||
|
"${APP_DIR}/gl/shader"
|
||||||
"${APP_DIR}/platform"
|
"${APP_DIR}/platform"
|
||||||
"${APP_DIR}/runtime"
|
"${APP_DIR}/runtime"
|
||||||
"${APP_DIR}/shader"
|
"${APP_DIR}/shader"
|
||||||
|
"${APP_DIR}/videoio"
|
||||||
|
"${APP_DIR}/videoio/decklink"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(LoopThroughWithOpenGLCompositing PRIVATE
|
target_link_libraries(LoopThroughWithOpenGLCompositing PRIVATE
|
||||||
@@ -196,6 +227,7 @@ add_executable(Std140BufferTests
|
|||||||
target_include_directories(Std140BufferTests PRIVATE
|
target_include_directories(Std140BufferTests PRIVATE
|
||||||
"${APP_DIR}"
|
"${APP_DIR}"
|
||||||
"${APP_DIR}/gl"
|
"${APP_DIR}/gl"
|
||||||
|
"${APP_DIR}/gl/shader"
|
||||||
)
|
)
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
@@ -222,6 +254,29 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME ShaderPackageRegistryTests COMMAND ShaderPackageRegistryTests)
|
add_test(NAME ShaderPackageRegistryTests COMMAND ShaderPackageRegistryTests)
|
||||||
|
|
||||||
|
add_executable(ShaderSlangValidationTests
|
||||||
|
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
||||||
|
"${APP_DIR}/shader/ShaderCompiler.cpp"
|
||||||
|
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderSlangValidationTests.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(ShaderSlangValidationTests PRIVATE
|
||||||
|
"${APP_DIR}"
|
||||||
|
"${APP_DIR}/platform"
|
||||||
|
"${APP_DIR}/runtime"
|
||||||
|
"${APP_DIR}/shader"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(ShaderSlangValidationTests PRIVATE /W3)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME ShaderSlangValidationTests COMMAND ShaderSlangValidationTests)
|
||||||
|
set_tests_properties(ShaderSlangValidationTests PROPERTIES
|
||||||
|
ENVIRONMENT "SLANG_ROOT=${SLANG_ROOT}"
|
||||||
|
)
|
||||||
|
|
||||||
add_executable(OscServerTests
|
add_executable(OscServerTests
|
||||||
"${APP_DIR}/control/OscServer.cpp"
|
"${APP_DIR}/control/OscServer.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/OscServerTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/OscServerTests.cpp"
|
||||||
@@ -244,13 +299,15 @@ endif()
|
|||||||
add_test(NAME OscServerTests COMMAND OscServerTests)
|
add_test(NAME OscServerTests COMMAND OscServerTests)
|
||||||
|
|
||||||
add_executable(VideoIOFormatTests
|
add_executable(VideoIOFormatTests
|
||||||
"${APP_DIR}/decklink/VideoIOFormat.cpp"
|
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.cpp"
|
||||||
|
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIOFormatTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIOFormatTests.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(VideoIOFormatTests PRIVATE
|
target_include_directories(VideoIOFormatTests PRIVATE
|
||||||
"${APP_DIR}"
|
"${APP_DIR}"
|
||||||
"${APP_DIR}/decklink"
|
"${APP_DIR}/videoio"
|
||||||
|
"${APP_DIR}/videoio/decklink"
|
||||||
)
|
)
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
@@ -259,6 +316,40 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME VideoIOFormatTests COMMAND VideoIOFormatTests)
|
add_test(NAME VideoIOFormatTests COMMAND VideoIOFormatTests)
|
||||||
|
|
||||||
|
add_executable(VideoPlayoutSchedulerTests
|
||||||
|
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoPlayoutSchedulerTests.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(VideoPlayoutSchedulerTests PRIVATE
|
||||||
|
"${APP_DIR}"
|
||||||
|
"${APP_DIR}/videoio"
|
||||||
|
"${APP_DIR}/videoio/decklink"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(VideoPlayoutSchedulerTests PRIVATE /W3)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME VideoPlayoutSchedulerTests COMMAND VideoPlayoutSchedulerTests)
|
||||||
|
|
||||||
|
add_executable(VideoIODeviceFakeTests
|
||||||
|
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIODeviceFakeTests.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(VideoIODeviceFakeTests PRIVATE
|
||||||
|
"${APP_DIR}"
|
||||||
|
"${APP_DIR}/videoio"
|
||||||
|
"${APP_DIR}/videoio/decklink"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(VideoIODeviceFakeTests PRIVATE /W3)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME VideoIODeviceFakeTests COMMAND VideoIODeviceFakeTests)
|
||||||
|
|
||||||
install(TARGETS LoopThroughWithOpenGLCompositing
|
install(TARGETS LoopThroughWithOpenGLCompositing
|
||||||
RUNTIME DESTINATION "."
|
RUNTIME DESTINATION "."
|
||||||
)
|
)
|
||||||
@@ -272,7 +363,7 @@ install(FILES "${SLANG_LICENSE_FILE}"
|
|||||||
RENAME "SLANG_LICENSE.txt"
|
RENAME "SLANG_LICENSE.txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/SHADER_CONTRACT.md"
|
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/shaders/SHADER_CONTRACT.md"
|
||||||
DESTINATION "."
|
DESTINATION "."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
109
OSC/Test.json
109
OSC/Test.json
@@ -36,7 +36,7 @@
|
|||||||
"preArgs": "",
|
"preArgs": "",
|
||||||
"typeTags": "",
|
"typeTags": "",
|
||||||
"decimals": 2,
|
"decimals": 2,
|
||||||
"target": "127.0.0.1:9000",
|
"target": "192.168.1.46:9000",
|
||||||
"ignoreDefaults": false,
|
"ignoreDefaults": false,
|
||||||
"bypass": false,
|
"bypass": false,
|
||||||
"onCreate": "",
|
"onCreate": "",
|
||||||
@@ -53,8 +53,8 @@
|
|||||||
"visible": true,
|
"visible": true,
|
||||||
"interaction": true,
|
"interaction": true,
|
||||||
"comments": "XY control for Fisheye Reproject pan and tilt.",
|
"comments": "XY control for Fisheye Reproject pan and tilt.",
|
||||||
"width": 420,
|
"width": 460,
|
||||||
"height": 420,
|
"height": 250,
|
||||||
"expand": false,
|
"expand": false,
|
||||||
"colorText": "auto",
|
"colorText": "auto",
|
||||||
"colorWidget": "auto",
|
"colorWidget": "auto",
|
||||||
@@ -70,14 +70,14 @@
|
|||||||
"css": "",
|
"css": "",
|
||||||
"pips": true,
|
"pips": true,
|
||||||
"snap": false,
|
"snap": false,
|
||||||
"spring": false,
|
"spring": true,
|
||||||
"rangeX": {
|
"rangeX": {
|
||||||
"min": -60,
|
"min": -1,
|
||||||
"max": 60
|
"max": 1
|
||||||
},
|
},
|
||||||
"rangeY": {
|
"rangeY": {
|
||||||
"min": 45,
|
"min": 1,
|
||||||
"max": -45
|
"max": -1
|
||||||
},
|
},
|
||||||
"logScaleX": false,
|
"logScaleX": false,
|
||||||
"logScaleY": false,
|
"logScaleY": false,
|
||||||
@@ -94,13 +94,13 @@
|
|||||||
"address": "/VideoShaderToys/fisheye-reproject/xy",
|
"address": "/VideoShaderToys/fisheye-reproject/xy",
|
||||||
"preArgs": "",
|
"preArgs": "",
|
||||||
"typeTags": "",
|
"typeTags": "",
|
||||||
"decimals": "2f",
|
"decimals": "3f",
|
||||||
"target": "127.0.0.1:9000",
|
"target": "192.168.1.46:9000",
|
||||||
"ignoreDefaults": false,
|
"ignoreDefaults": false,
|
||||||
"bypass": true,
|
"bypass": true,
|
||||||
"onCreate": "",
|
"onCreate": "var state = globalThis.__fisheyePanTiltStick = globalThis.__fisheyePanTiltStick || {};\nstate.target = '192.168.1.46:9000';\nstate.panAddress = '/VideoShaderToys/fisheye-reproject/panDegrees';\nstate.tiltAddress = '/VideoShaderToys/fisheye-reproject/tiltDegrees';\nstate.minPan = -60;\nstate.maxPan = 60;\nstate.minTilt = -45;\nstate.maxTilt = 45;\nstate.pan = 0;\nstate.tilt = 0;\nstate.stickX = 0;\nstate.stickY = 0;\nstate.tickMs = 16;\nstate.stepPan = 0.75;\nstate.stepTilt = 0.75;\nstate.deadzone = 0.14;\nstate.applyCurve = function(input) {\n var amount = Math.abs(input);\n if (amount <= state.deadzone) {\n return 0;\n }\n var normalized = (amount - state.deadzone) / (1 - state.deadzone);\n var softened = normalized * normalized * (3 - (2 * normalized));\n return (input < 0 ? -1 : 1) * softened;\n};\nif (state.timer) {\n clearInterval(state.timer);\n state.timer = null;\n}",
|
||||||
"onValue": "var pan = Array.isArray(value) ? Number(value[0]) : 0;\nvar tilt = Array.isArray(value) ? Number(value[1]) : 0;\nsend('127.0.0.1:9000', '/VideoShaderToys/fisheye-reproject/panDegrees', {type: 'f', value: pan});\nsend('127.0.0.1:9000', '/VideoShaderToys/fisheye-reproject/tiltDegrees', {type: 'f', value: tilt});",
|
"onValue": "var state = globalThis.__fisheyePanTiltStick = globalThis.__fisheyePanTiltStick || {};\nvar stickX = Array.isArray(value) ? Number(value[0]) : 0;\nvar stickY = Array.isArray(value) ? Number(value[1]) : 0;\nstate.stickX = isFinite(stickX) ? state.applyCurve(stickX) : 0;\nstate.stickY = isFinite(stickY) ? state.applyCurve(stickY) : 0;",
|
||||||
"onTouch": "",
|
"onTouch": "var state = globalThis.__fisheyePanTiltStick = globalThis.__fisheyePanTiltStick || {};\nif (value) {\n if (!state.timer) {\n state.timer = setInterval(function() {\n if (!state.stickX && !state.stickY) {\n return;\n }\n state.pan = Math.max(state.minPan, Math.min(state.maxPan, state.pan + (state.stickX * state.stepPan)));\n state.tilt = Math.max(state.minTilt, Math.min(state.maxTilt, state.tilt + (state.stickY * state.stepTilt)));\n send(state.target, state.panAddress, {type: 'f', value: state.pan});\n send(state.target, state.tiltAddress, {type: 'f', value: state.tilt});\n }, state.tickMs);\n }\n} else {\n state.stickX = 0;\n state.stickY = 0;\n if (state.timer) {\n clearInterval(state.timer);\n state.timer = null;\n }\n}",
|
||||||
"pointSize": 20,
|
"pointSize": 20,
|
||||||
"ephemeral": false,
|
"ephemeral": false,
|
||||||
"label": "",
|
"label": "",
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
"interaction": true,
|
"interaction": true,
|
||||||
"comments": "",
|
"comments": "",
|
||||||
"width": 90,
|
"width": 90,
|
||||||
"height": 420,
|
"height": 250,
|
||||||
"expand": false,
|
"expand": false,
|
||||||
"colorText": "auto",
|
"colorText": "auto",
|
||||||
"colorWidget": "auto",
|
"colorWidget": "auto",
|
||||||
@@ -144,90 +144,29 @@
|
|||||||
"gradient": [],
|
"gradient": [],
|
||||||
"snap": false,
|
"snap": false,
|
||||||
"touchZone": "all",
|
"touchZone": "all",
|
||||||
"spring": false,
|
"spring": true,
|
||||||
"doubleTap": false,
|
"doubleTap": false,
|
||||||
"range": {
|
"range": {
|
||||||
"min": 100,
|
"min": -1,
|
||||||
"max": 10
|
"max": 1
|
||||||
},
|
},
|
||||||
"logScale": false,
|
"logScale": false,
|
||||||
"sensitivity": 1,
|
"sensitivity": 1,
|
||||||
"steps": "",
|
"steps": "",
|
||||||
"origin": "auto",
|
"origin": "auto",
|
||||||
"value": "",
|
"value": 0,
|
||||||
"default": 90,
|
"default": 0,
|
||||||
"linkId": "",
|
"linkId": "",
|
||||||
"address": "/VideoShaderToys/fisheye-reproject/virtualFovDegrees",
|
"address": "/VideoShaderToys/fisheye-reproject/virtualFovDegrees",
|
||||||
"preArgs": "",
|
"preArgs": "",
|
||||||
"typeTags": "",
|
"typeTags": "",
|
||||||
"decimals": 2,
|
"decimals": "3f",
|
||||||
"target": "127.0.0.1:9000",
|
"target": "192.168.1.46:9000",
|
||||||
"ignoreDefaults": false,
|
|
||||||
"bypass": false,
|
|
||||||
"onCreate": "",
|
|
||||||
"onValue": "",
|
|
||||||
"onTouch": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "xy",
|
|
||||||
"top": 700,
|
|
||||||
"left": 190,
|
|
||||||
"lock": false,
|
|
||||||
"id": "Pan Pad",
|
|
||||||
"visible": true,
|
|
||||||
"interaction": true,
|
|
||||||
"comments": "",
|
|
||||||
"width": "auto",
|
|
||||||
"height": "auto",
|
|
||||||
"expand": false,
|
|
||||||
"colorText": "auto",
|
|
||||||
"colorWidget": "auto",
|
|
||||||
"colorStroke": "auto",
|
|
||||||
"colorFill": "auto",
|
|
||||||
"alphaStroke": "auto",
|
|
||||||
"alphaFillOff": "auto",
|
|
||||||
"alphaFillOn": "auto",
|
|
||||||
"lineWidth": "auto",
|
|
||||||
"borderRadius": "auto",
|
|
||||||
"padding": "auto",
|
|
||||||
"html": "",
|
|
||||||
"css": "",
|
|
||||||
"pointSize": 20,
|
|
||||||
"ephemeral": false,
|
|
||||||
"pips": true,
|
|
||||||
"label": "",
|
|
||||||
"snap": false,
|
|
||||||
"spring": false,
|
|
||||||
"rangeX": {
|
|
||||||
"min": -1,
|
|
||||||
"max": 1
|
|
||||||
},
|
|
||||||
"rangeY": {
|
|
||||||
"min": -1,
|
|
||||||
"max": 1
|
|
||||||
},
|
|
||||||
"logScaleX": false,
|
|
||||||
"logScaleY": false,
|
|
||||||
"stepsX": false,
|
|
||||||
"stepsY": false,
|
|
||||||
"clipX": "",
|
|
||||||
"clipY": "",
|
|
||||||
"axisLock": "",
|
|
||||||
"doubleTap": false,
|
|
||||||
"sensitivity": 1,
|
|
||||||
"value": "",
|
|
||||||
"default": "",
|
|
||||||
"linkId": "",
|
|
||||||
"address": "/VideoShaderToys/video-transform/pan",
|
|
||||||
"preArgs": "",
|
|
||||||
"typeTags": "",
|
|
||||||
"decimals": 2,
|
|
||||||
"target": "",
|
|
||||||
"ignoreDefaults": false,
|
"ignoreDefaults": false,
|
||||||
"bypass": true,
|
"bypass": true,
|
||||||
"onCreate": "",
|
"onCreate": "var state = globalThis.__fisheyeFovStick = globalThis.__fisheyeFovStick || {};\nstate.target = '192.168.1.46:9000';\nstate.address = '/VideoShaderToys/fisheye-reproject/virtualFovDegrees';\nstate.minFov = 10;\nstate.maxFov = 100;\nstate.fov = 90;\nstate.stick = 0;\nstate.tickMs = 16;\nstate.stepFov = 0.6;\nstate.deadzone = 0.14;\nstate.applyCurve = function(input) {\n var amount = Math.abs(input);\n if (amount <= state.deadzone) {\n return 0;\n }\n var normalized = (amount - state.deadzone) / (1 - state.deadzone);\n var softened = normalized * normalized * (3 - (2 * normalized));\n return (input < 0 ? -1 : 1) * softened;\n};\nif (state.timer) {\n clearInterval(state.timer);\n state.timer = null;\n}",
|
||||||
"onValue": "var x = Array.isArray(value) ? Number(value[0]) : 0;\nvar y = Array.isArray(value) ? Number(value[1]) : 0;\nsend('127.0.0.1:9000', '/VideoShaderToys/video-transform/pan', {type: 'f', value: x}, {type: 'f', value: y});",
|
"onValue": "var state = globalThis.__fisheyeFovStick = globalThis.__fisheyeFovStick || {};\nvar stick = Number(value);\nstate.stick = isFinite(stick) ? state.applyCurve(stick) : 0;",
|
||||||
"onTouch": ""
|
"onTouch": "var state = globalThis.__fisheyeFovStick = globalThis.__fisheyeFovStick || {};\nif (value) {\n if (!state.timer) {\n state.timer = setInterval(function() {\n if (!state.stick) {\n return;\n }\n state.fov = Math.max(state.minFov, Math.min(state.maxFov, state.fov - (state.stick * state.stepFov)));\n send(state.target, state.address, {type: 'f', value: state.fov});\n }, state.tickMs);\n }\n} else {\n state.stick = 0;\n if (state.timer) {\n clearInterval(state.timer);\n state.timer = null;\n }\n}"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tabs": []
|
"tabs": []
|
||||||
|
|||||||
65
README.md
65
README.md
@@ -1,8 +1,8 @@
|
|||||||
# Video Shader
|
# Video Shader
|
||||||
|
|
||||||
Native video shader host with an OpenGL/DeckLink render path, Slang shader packages, and a local React control UI.
|
Native video shader host with an OpenGL render path, pluggable video I/O boundary, DeckLink backend, Slang shader packages, and a local React control UI.
|
||||||
|
|
||||||
The app loads shader packages from `shaders/`, compiles Slang to GLSL at runtime, renders a configurable layer stack, and exposes a browser-based control surface over a local HTTP/WebSocket server.
|
The app loads shader packages from `shaders/`, compiles Slang to GLSL at runtime, renders a configurable layer stack, and exposes a browser-based control surface over a local HTTP/WebSocket server. Shader compilation is prepared off the frame path where possible, then committed on the render thread so editing shader files does not block video output for the whole compile.
|
||||||
|
|
||||||
## Repository Layout
|
## Repository Layout
|
||||||
|
|
||||||
@@ -15,12 +15,20 @@ The app loads shader packages from `shaders/`, compiles Slang to GLSL at runtime
|
|||||||
- `tests/`: focused native tests for pure runtime logic.
|
- `tests/`: focused native tests for pure runtime logic.
|
||||||
- `.gitea/workflows/ci.yml`: Gitea Actions CI for Windows native tests and Ubuntu UI build.
|
- `.gitea/workflows/ci.yml`: Gitea Actions CI for Windows native tests and Ubuntu UI build.
|
||||||
|
|
||||||
|
Native app internals are grouped by boundary:
|
||||||
|
|
||||||
|
- `videoio/`: backend-neutral video I/O contracts, formats, and playout timing.
|
||||||
|
- `videoio/decklink/`: DeckLink-specific device adapter, callbacks, and SDK bindings.
|
||||||
|
- `gl/renderer/`: low-level OpenGL resources and extension helpers.
|
||||||
|
- `gl/pipeline/`: frame pipeline, render passes, video I/O bridge, preview/readback, and screenshots.
|
||||||
|
- `gl/shader/`: shader compilation, texture/text assets, UBO packing, and shader program ownership.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Windows with Visual Studio 2022 C++ tooling.
|
- Windows with Visual Studio 2022 C++ tooling.
|
||||||
- CMake 3.24 or newer.
|
- CMake 3.24 or newer.
|
||||||
- Node.js and npm for the control UI.
|
- Node.js and npm for the control UI.
|
||||||
- Blackmagic Desktop Video drivers and a DeckLink device.
|
- Blackmagic Desktop Video drivers and a DeckLink device for the current production video I/O backend.
|
||||||
- Slang binary release with `slangc.exe`, `slang-compiler.dll`, `slang-glslang.dll`, and `LICENSE`.
|
- Slang binary release with `slangc.exe`, `slang-compiler.dll`, `slang-glslang.dll`, and `LICENSE`.
|
||||||
|
|
||||||
Default expected Slang path:
|
Default expected Slang path:
|
||||||
@@ -54,6 +62,14 @@ npm run build
|
|||||||
|
|
||||||
The native app serves `ui/dist` when it exists, otherwise it falls back to the source UI directory during development.
|
The native app serves `ui/dist` when it exists, otherwise it falls back to the source UI directory during development.
|
||||||
|
|
||||||
|
The control UI provides:
|
||||||
|
|
||||||
|
- A searchable shader library for adding layers.
|
||||||
|
- Compact parameter rows with inline descriptions and OSC copy controls.
|
||||||
|
- Stack save/recall presets.
|
||||||
|
- Manual shader reload.
|
||||||
|
- Screenshot capture from the final output render target.
|
||||||
|
|
||||||
## Package
|
## Package
|
||||||
|
|
||||||
Build the UI, build the native Release target, then install into a portable runtime folder:
|
Build the UI, build the native Release target, then install into a portable runtime folder:
|
||||||
@@ -77,6 +93,8 @@ dist/VideoShader/
|
|||||||
shaders/
|
shaders/
|
||||||
3rdParty/slang/bin/
|
3rdParty/slang/bin/
|
||||||
ui/dist/
|
ui/dist/
|
||||||
|
docs/
|
||||||
|
SHADER_CONTRACT.md
|
||||||
runtime/templates/
|
runtime/templates/
|
||||||
third_party_notices/
|
third_party_notices/
|
||||||
```
|
```
|
||||||
@@ -111,6 +129,9 @@ Current native test coverage includes:
|
|||||||
- JSON parsing and serialization.
|
- JSON parsing and serialization.
|
||||||
- Parameter normalization and preset filename safety.
|
- Parameter normalization and preset filename safety.
|
||||||
- Shader manifest parsing, temporal manifest validation, and package registry scanning.
|
- Shader manifest parsing, temporal manifest validation, and package registry scanning.
|
||||||
|
- Video I/O format helpers, v210/Ay10 row-byte math, v210 pack/unpack math, playout scheduler timing, and fake backend contract coverage.
|
||||||
|
- OSC packet parsing.
|
||||||
|
- Slang validation for every available shader package.
|
||||||
|
|
||||||
## Runtime Configuration
|
## Runtime Configuration
|
||||||
|
|
||||||
@@ -120,7 +141,9 @@ Current native test coverage includes:
|
|||||||
{
|
{
|
||||||
"shaderLibrary": "shaders",
|
"shaderLibrary": "shaders",
|
||||||
"serverPort": 8080,
|
"serverPort": 8080,
|
||||||
|
"oscBindAddress": "127.0.0.1",
|
||||||
"oscPort": 9000,
|
"oscPort": 9000,
|
||||||
|
"oscSmoothing": 0.18,
|
||||||
"inputVideoFormat": "1080p",
|
"inputVideoFormat": "1080p",
|
||||||
"inputFrameRate": "59.94",
|
"inputFrameRate": "59.94",
|
||||||
"outputVideoFormat": "1080p",
|
"outputVideoFormat": "1080p",
|
||||||
@@ -131,7 +154,7 @@ Current native test coverage includes:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`inputVideoFormat`/`inputFrameRate` select the DeckLink capture mode. `outputVideoFormat`/`outputFrameRate` select the playout mode. The shader stack runs at input resolution and the final rendered frame is scaled once into the configured output mode. Common examples include `720p`/`50`, `720p`/`59.94`, `1080i`/`50`, `1080i`/`59.94`, `1080p`/`25`, `1080p`/`50`, `1080p`/`59.94`, and `2160p`/`59.94`, depending on card support.
|
`inputVideoFormat`/`inputFrameRate` select the video capture mode. `outputVideoFormat`/`outputFrameRate` select the playout mode. With the current DeckLink backend, supported modes depend on the installed card and driver. The shader stack runs at input resolution and the final rendered frame is scaled once into the configured output mode. Common examples include `720p`/`50`, `720p`/`59.94`, `1080i`/`50`, `1080i`/`59.94`, `1080p`/`25`, `1080p`/`50`, `1080p`/`59.94`, and `2160p`/`59.94`.
|
||||||
|
|
||||||
Legacy `videoFormat` and `frameRate` keys are still accepted and apply to both input and output unless the explicit input/output keys are present.
|
Legacy `videoFormat` and `frameRate` keys are still accepted and apply to both input and output unless the explicit input/output keys are present.
|
||||||
|
|
||||||
@@ -170,7 +193,11 @@ http://127.0.0.1:<serverPort>/docs
|
|||||||
|
|
||||||
Use those docs to inspect the `/api/state`, layer control, stack preset, and reload endpoints. Live state updates are also sent over the `/ws` WebSocket.
|
Use those docs to inspect the `/api/state`, layer control, stack preset, and reload endpoints. Live state updates are also sent over the `/ws` WebSocket.
|
||||||
|
|
||||||
The control UI also has a Screenshot button. It queues a capture of the final output render target and writes a PNG under:
|
The control UI has a **Reload shaders** button. It rescans `shaders/`, re-reads manifests, queues shader compilation, refreshes shader availability/errors, and keeps the previous working shader stack running if a changed shader fails to compile.
|
||||||
|
|
||||||
|
Each parameter row also includes a small **OSC** button. Clicking it copies that parameter's OSC route to the clipboard.
|
||||||
|
|
||||||
|
The control UI also has a **Screenshot** button. It queues a capture of the final output render target and writes a PNG under:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
runtime/screenshots/
|
runtime/screenshots/
|
||||||
@@ -178,13 +205,13 @@ runtime/screenshots/
|
|||||||
|
|
||||||
## OSC Control
|
## OSC Control
|
||||||
|
|
||||||
The native host also listens for local OSC parameter control on the configured `oscPort`:
|
The native host also listens for OSC parameter control on the configured `oscBindAddress` and `oscPort`:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/VideoShaderToys/{LayerNameOrID}/{ParameterNameOrID}
|
/VideoShaderToys/{LayerNameOrID}/{ParameterNameOrID}
|
||||||
```
|
```
|
||||||
|
|
||||||
For example, `/VideoShaderToys/VHS/intensity` updates the `intensity` parameter on the first matching `VHS` layer. The listener accepts float, integer, string, and boolean OSC values, and validates them through the same shader parameter path as the REST API. See `docs/OSC_CONTROL.md` for details.
|
For example, `/VideoShaderToys/VHS/intensity` updates the `intensity` parameter on the first matching `VHS` layer. The listener accepts float, integer, string, and boolean OSC values, and validates them through the same shader parameter path as the REST API. OSC updates are coalesced and applied once per render tick, UI state broadcasts are throttled, and OSC-driven parameter changes are not autosaved to `runtime/runtime_state.json`. `oscSmoothing` adds a small per-frame easing amount for numeric OSC controls such as floats, `vec2`, and `color`, while booleans, enums, text, and triggers stay immediate. The default bind address is `127.0.0.1`; set `oscBindAddress` to `0.0.0.0` to accept OSC on all IPv4 interfaces. See `docs/OSC_CONTROL.md` for details.
|
||||||
|
|
||||||
## Shader Packages
|
## Shader Packages
|
||||||
|
|
||||||
@@ -194,10 +221,11 @@ Each shader package lives under:
|
|||||||
shaders/<id>/
|
shaders/<id>/
|
||||||
shader.json
|
shader.json
|
||||||
shader.slang
|
shader.slang
|
||||||
|
optional-extra-pass.slang
|
||||||
optional-font-or-texture-assets
|
optional-font-or-texture-assets
|
||||||
```
|
```
|
||||||
|
|
||||||
See `SHADER_CONTRACT.md` for the manifest schema, parameter types, texture assets, font/text assets, temporal history support, and the Slang entry point contract. `shaders/text-overlay/` is the reference live text package and bundles Roboto Regular with its OFL license.
|
See `SHADER_CONTRACT.md` for the manifest schema, parameter types, texture assets, font/text assets, temporal history support, optional render-pass declarations, and the Slang entry point contract. `shaders/text-overlay/` is the reference live text package and bundles Roboto Regular with its OFL license. Broken shader packages are shown as unavailable in the selector with their error text instead of preventing the app from launching.
|
||||||
|
|
||||||
## Generated Files
|
## Generated Files
|
||||||
|
|
||||||
@@ -208,6 +236,7 @@ Runtime-generated files are intentionally ignored:
|
|||||||
- `runtime/shader_cache/active_shader.frag`
|
- `runtime/shader_cache/active_shader.frag`
|
||||||
- `runtime/runtime_state.json` autosaved latest stack and parameter state.
|
- `runtime/runtime_state.json` autosaved latest stack and parameter state.
|
||||||
- `runtime/stack_presets/*.json`
|
- `runtime/stack_presets/*.json`
|
||||||
|
- `runtime/screenshots/*.png` screenshots captured from the final output render target.
|
||||||
|
|
||||||
Only `runtime/templates/` and `runtime/README.md` are tracked.
|
Only `runtime/templates/` and `runtime/README.md` are tracked.
|
||||||
|
|
||||||
@@ -236,16 +265,16 @@ If `SLANG_ROOT` is not set, the workflow falls back to the repo-local default un
|
|||||||
|
|
||||||
- Audio.
|
- Audio.
|
||||||
- Genlock.
|
- Genlock.
|
||||||
- Find a better UI library for react.
|
|
||||||
- Logs.
|
- Logs.
|
||||||
- Continue source cleanup/refactoring. Pass 2 done
|
- Add more video I/O backends now that the DeckLink path is behind `videoio/`.
|
||||||
- Support a separate sound shader `.slang` file in shader packages. (https://www.shadertoy.com/view/XsBXWt)
|
- Support a separate sound shader `.slang` file in shader packages. (https://www.shadertoy.com/view/XsBXWt)
|
||||||
- Add WebView2
|
- Add WebView2 for an embedded native control surface.
|
||||||
- move to MSDF, typography rasterisation
|
- MSDF typography rasterisation
|
||||||
- better shader search UI, pass 1
|
- More shader-library organisation and filtering as the built-in library grows.
|
||||||
- More comprehensive greenscreen shader
|
- Optional linear-light compositing mode.
|
||||||
- linear compositing?
|
- compute shaders or a small 1x1 or nx1 RGBA16f render target for arbitrary data storage
|
||||||
- compute shaders or a small 1x1 or nx1 RGBA16f render target for abritary data store
|
|
||||||
- allow shaders to read other shaders data store based on name? or output over OSC
|
- allow shaders to read other shaders data store based on name? or output over OSC
|
||||||
- Mipmappong for shader declared textures
|
- Mipmapping for shader-declared textures
|
||||||
- unwrap a fish eyelens and mirror it and map it to equirectangulr for environmnet map purposes
|
- Anotate included shaders
|
||||||
|
- allow 3 vector exposed controls
|
||||||
|
- add nearest sampling to the extra shader pass
|
||||||
@@ -531,7 +531,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|||||||
if (!sInteractiveResize && pOpenGLComposite)
|
if (!sInteractiveResize && pOpenGLComposite)
|
||||||
{
|
{
|
||||||
wglMakeCurrent(hDC, hRC);
|
wglMakeCurrent(hDC, hRC);
|
||||||
pOpenGLComposite->paintGL();
|
pOpenGLComposite->paintGL(true);
|
||||||
wglMakeCurrent( NULL, NULL );
|
wglMakeCurrent( NULL, NULL );
|
||||||
RaiseStatusControls(sStatusStrip);
|
RaiseStatusControls(sStatusStrip);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,233 +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;decklink;gl;%(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;decklink;gl;%(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;decklink;gl;%(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;decklink;gl;%(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\GLExtensions.cpp" />
|
|
||||||
<ClCompile Include="LoopThroughWithOpenGLCompositing.cpp" />
|
|
||||||
<ClCompile Include="gl\OpenGLComposite.cpp" />
|
|
||||||
<ClCompile Include="gl\OpenGLRenderPass.cpp" />
|
|
||||||
<ClCompile Include="gl\OpenGLRenderer.cpp" />
|
|
||||||
<ClCompile Include="gl\OpenGLShaderPrograms.cpp" />
|
|
||||||
<ClCompile Include="gl\PngScreenshotWriter.cpp" />
|
|
||||||
<ClCompile Include="gl\ShaderBuildQueue.cpp" />
|
|
||||||
<ClCompile Include="gl\TemporalHistoryBuffers.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="DeckLinkAPI_i.c" />
|
|
||||||
<ClCompile Include="control\RuntimeServices.cpp" />
|
|
||||||
<ClCompile Include="decklink\DeckLinkSession.cpp" />
|
|
||||||
<ClCompile Include="decklink\VideoIOFormat.cpp" />
|
|
||||||
<ClCompile Include="runtime\RuntimeClock.cpp" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClInclude Include="gl\GLExtensions.h" />
|
|
||||||
<ClInclude Include="LoopThroughWithOpenGLCompositing.h" />
|
|
||||||
<ClInclude Include="gl\OpenGLComposite.h" />
|
|
||||||
<ClInclude Include="gl\OpenGLRenderPass.h" />
|
|
||||||
<ClInclude Include="gl\OpenGLRenderer.h" />
|
|
||||||
<ClInclude Include="gl\OpenGLShaderPrograms.h" />
|
|
||||||
<ClInclude Include="gl\PngScreenshotWriter.h" />
|
|
||||||
<ClInclude Include="gl\ShaderBuildQueue.h" />
|
|
||||||
<ClInclude Include="gl\TemporalHistoryBuffers.h" />
|
|
||||||
<ClInclude Include="resource.h" />
|
|
||||||
<ClInclude Include="stdafx.h" />
|
|
||||||
<ClInclude Include="targetver.h" />
|
|
||||||
<ClInclude Include="control\RuntimeServices.h" />
|
|
||||||
<ClInclude Include="decklink\DeckLinkSession.h" />
|
|
||||||
<ClInclude Include="decklink\VideoIOFormat.h" />
|
|
||||||
<ClInclude Include="runtime\RuntimeClock.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,140 +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\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\OpenGLRenderPass.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="gl\OpenGLRenderer.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="gl\OpenGLShaderPrograms.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="gl\PngScreenshotWriter.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="gl\ShaderBuildQueue.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="gl\TemporalHistoryBuffers.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="stdafx.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="DeckLinkAPI_i.c">
|
|
||||||
<Filter>DeckLink API</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="control\RuntimeServices.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="decklink\DeckLinkSession.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="decklink\VideoIOFormat.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="runtime\RuntimeClock.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClInclude Include="gl\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\OpenGLRenderPass.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="gl\OpenGLRenderer.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="gl\OpenGLShaderPrograms.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="gl\PngScreenshotWriter.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="gl\ShaderBuildQueue.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="gl\TemporalHistoryBuffers.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="decklink\DeckLinkSession.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="decklink\VideoIOFormat.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="runtime\RuntimeClock.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>
|
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr DWORD kStateBroadcastIntervalMs = 250;
|
constexpr DWORD kStateBroadcastIntervalMs = 250;
|
||||||
|
constexpr DWORD kStateBroadcastThrottleMs = 50;
|
||||||
|
|
||||||
bool InitializeWinsock(std::string& error)
|
bool InitializeWinsock(std::string& error)
|
||||||
{
|
{
|
||||||
@@ -75,7 +76,7 @@ std::string GuessContentType(const std::filesystem::path& assetPath)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ControlServer::ControlServer()
|
ControlServer::ControlServer()
|
||||||
: mPort(0), mRunning(false)
|
: mPort(0), mRunning(false), mBroadcastPending(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,10 +162,16 @@ void ControlServer::Stop()
|
|||||||
|
|
||||||
void ControlServer::BroadcastState()
|
void ControlServer::BroadcastState()
|
||||||
{
|
{
|
||||||
|
mBroadcastPending = false;
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
BroadcastStateLocked();
|
BroadcastStateLocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ControlServer::RequestBroadcastState()
|
||||||
|
{
|
||||||
|
mBroadcastPending = true;
|
||||||
|
}
|
||||||
|
|
||||||
void ControlServer::ServerLoop()
|
void ControlServer::ServerLoop()
|
||||||
{
|
{
|
||||||
DWORD lastStateBroadcastMs = GetTickCount();
|
DWORD lastStateBroadcastMs = GetTickCount();
|
||||||
@@ -173,7 +180,12 @@ void ControlServer::ServerLoop()
|
|||||||
TryAcceptClient();
|
TryAcceptClient();
|
||||||
|
|
||||||
const DWORD nowMs = GetTickCount();
|
const DWORD nowMs = GetTickCount();
|
||||||
if (nowMs - lastStateBroadcastMs >= kStateBroadcastIntervalMs)
|
if (mBroadcastPending && nowMs - lastStateBroadcastMs >= kStateBroadcastThrottleMs)
|
||||||
|
{
|
||||||
|
BroadcastState();
|
||||||
|
lastStateBroadcastMs = nowMs;
|
||||||
|
}
|
||||||
|
else if (nowMs - lastStateBroadcastMs >= kStateBroadcastIntervalMs)
|
||||||
{
|
{
|
||||||
BroadcastState();
|
BroadcastState();
|
||||||
lastStateBroadcastMs = nowMs;
|
lastStateBroadcastMs = nowMs;
|
||||||
@@ -469,6 +481,7 @@ bool ControlServer::HandleWebSocketUpgrade(UniqueSocket clientSocket, const Http
|
|||||||
client.socket.reset(clientSocket.release());
|
client.socket.reset(clientSocket.release());
|
||||||
client.websocket = true;
|
client.websocket = true;
|
||||||
mClients.push_back(std::move(client));
|
mClients.push_back(std::move(client));
|
||||||
|
mBroadcastPending = false;
|
||||||
BroadcastStateLocked();
|
BroadcastStateLocked();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -501,6 +514,9 @@ bool ControlServer::SendWebSocketText(SOCKET clientSocket, const std::string& pa
|
|||||||
|
|
||||||
void ControlServer::BroadcastStateLocked()
|
void ControlServer::BroadcastStateLocked()
|
||||||
{
|
{
|
||||||
|
if (mClients.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
const std::string stateMessage = mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}";
|
const std::string stateMessage = mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}";
|
||||||
for (auto it = mClients.begin(); it != mClients.end();)
|
for (auto it = mClients.begin(); it != mClients.end();)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public:
|
|||||||
bool Start(const std::filesystem::path& uiRoot, const std::filesystem::path& docsRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error);
|
bool Start(const std::filesystem::path& uiRoot, const std::filesystem::path& docsRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error);
|
||||||
void Stop();
|
void Stop();
|
||||||
void BroadcastState();
|
void BroadcastState();
|
||||||
|
void RequestBroadcastState();
|
||||||
|
|
||||||
unsigned short GetPort() const { return mPort; }
|
unsigned short GetPort() const { return mPort; }
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ private:
|
|||||||
unsigned short mPort;
|
unsigned short mPort;
|
||||||
std::thread mThread;
|
std::thread mThread;
|
||||||
std::atomic<bool> mRunning;
|
std::atomic<bool> mRunning;
|
||||||
|
std::atomic<bool> mBroadcastPending;
|
||||||
mutable std::mutex mMutex;
|
mutable std::mutex mMutex;
|
||||||
std::vector<ClientConnection> mClients;
|
std::vector<ClientConnection> mClients;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,247 @@
|
|||||||
|
#include "ControlServices.h"
|
||||||
|
|
||||||
|
#include "ControlServer.h"
|
||||||
|
#include "OscServer.h"
|
||||||
|
#include "RuntimeControlBridge.h"
|
||||||
|
#include "RuntimeHost.h"
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
ControlServices::ControlServices() :
|
||||||
|
mControlServer(std::make_unique<ControlServer>()),
|
||||||
|
mOscServer(std::make_unique<OscServer>()),
|
||||||
|
mPollRunning(false),
|
||||||
|
mRegistryChanged(false),
|
||||||
|
mReloadRequested(false),
|
||||||
|
mPollFailed(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlServices::~ControlServices()
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ControlServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error)
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
|
||||||
|
if (!StartControlServicesBoundary(composite, runtimeHost, *this, *mControlServer, *mOscServer, error))
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlServices::BeginPolling(RuntimeHost& runtimeHost)
|
||||||
|
{
|
||||||
|
StartPolling(runtimeHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlServices::Stop()
|
||||||
|
{
|
||||||
|
StopPolling();
|
||||||
|
|
||||||
|
if (mOscServer)
|
||||||
|
mOscServer->Stop();
|
||||||
|
|
||||||
|
if (mControlServer)
|
||||||
|
mControlServer->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlServices::BroadcastState()
|
||||||
|
{
|
||||||
|
if (mControlServer)
|
||||||
|
mControlServer->BroadcastState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlServices::RequestBroadcastState()
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
(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);
|
||||||
|
}
|
||||||
|
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::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
|
||||||
|
{
|
||||||
|
completedCommits.clear();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
||||||
|
if (mCompletedOscCommits.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
completedCommits.swap(mCompletedOscCommits);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimePollEvents ControlServices::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);
|
||||||
|
events.error = mPollError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlServices::StartPolling(RuntimeHost& runtimeHost)
|
||||||
|
{
|
||||||
|
if (mPollRunning.exchange(true))
|
||||||
|
return;
|
||||||
|
|
||||||
|
mPollThread = std::thread([this, &runtimeHost]() { PollLoop(runtimeHost); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlServices::StopPolling()
|
||||||
|
{
|
||||||
|
if (!mPollRunning.exchange(false))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (mPollThread.joinable())
|
||||||
|
mPollThread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlServices::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "RuntimeJson.h"
|
||||||
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class ControlServer;
|
||||||
|
class OpenGLComposite;
|
||||||
|
class OscServer;
|
||||||
|
class RuntimeHost;
|
||||||
|
|
||||||
|
struct RuntimePollEvents
|
||||||
|
{
|
||||||
|
bool registryChanged = false;
|
||||||
|
bool reloadRequested = false;
|
||||||
|
bool failed = false;
|
||||||
|
std::string error;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
ControlServices();
|
||||||
|
~ControlServices();
|
||||||
|
|
||||||
|
bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error);
|
||||||
|
void BeginPolling(RuntimeHost& runtimeHost);
|
||||||
|
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 ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
|
||||||
|
RuntimePollEvents ConsumePollEvents();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct PendingOscUpdate
|
||||||
|
{
|
||||||
|
std::string layerKey;
|
||||||
|
std::string parameterKey;
|
||||||
|
std::string valueJson;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PendingOscCommit
|
||||||
|
{
|
||||||
|
std::string routeKey;
|
||||||
|
std::string layerKey;
|
||||||
|
std::string parameterKey;
|
||||||
|
JsonValue value;
|
||||||
|
uint64_t generation = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void StartPolling(RuntimeHost& runtimeHost);
|
||||||
|
void StopPolling();
|
||||||
|
void PollLoop(RuntimeHost& runtimeHost);
|
||||||
|
|
||||||
|
std::unique_ptr<ControlServer> mControlServer;
|
||||||
|
std::unique_ptr<OscServer> mOscServer;
|
||||||
|
std::thread mPollThread;
|
||||||
|
std::atomic<bool> mPollRunning;
|
||||||
|
std::atomic<bool> mRegistryChanged;
|
||||||
|
std::atomic<bool> mReloadRequested;
|
||||||
|
std::atomic<bool> mPollFailed;
|
||||||
|
std::mutex mPollErrorMutex;
|
||||||
|
std::string mPollError;
|
||||||
|
std::mutex mPendingOscMutex;
|
||||||
|
std::map<std::string, PendingOscUpdate> mPendingOscUpdates;
|
||||||
|
std::mutex mPendingOscCommitMutex;
|
||||||
|
std::map<std::string, PendingOscCommit> mPendingOscCommits;
|
||||||
|
std::mutex mCompletedOscCommitMutex;
|
||||||
|
std::vector<CompletedOscCommit> mCompletedOscCommits;
|
||||||
|
};
|
||||||
@@ -55,7 +55,7 @@ OscServer::~OscServer()
|
|||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OscServer::Start(unsigned short port, const Callbacks& callbacks, std::string& error)
|
bool OscServer::Start(const std::string& bindAddress, unsigned short port, const Callbacks& callbacks, std::string& error)
|
||||||
{
|
{
|
||||||
if (port == 0)
|
if (port == 0)
|
||||||
return true;
|
return true;
|
||||||
@@ -78,11 +78,15 @@ bool OscServer::Start(unsigned short port, const Callbacks& callbacks, std::stri
|
|||||||
|
|
||||||
sockaddr_in address = {};
|
sockaddr_in address = {};
|
||||||
address.sin_family = AF_INET;
|
address.sin_family = AF_INET;
|
||||||
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
if (!TryParseBindAddress(bindAddress, address.sin_addr, error))
|
||||||
|
{
|
||||||
|
mSocket.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
address.sin_port = htons(static_cast<u_short>(port));
|
address.sin_port = htons(static_cast<u_short>(port));
|
||||||
if (bind(mSocket.get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) != 0)
|
if (bind(mSocket.get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) != 0)
|
||||||
{
|
{
|
||||||
error = "Could not bind OSC listener to UDP port " + std::to_string(port) + ".";
|
error = "Could not bind OSC listener to " + bindAddress + ":" + std::to_string(port) + ".";
|
||||||
mSocket.reset();
|
mSocket.reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -92,6 +96,24 @@ bool OscServer::Start(unsigned short port, const Callbacks& callbacks, std::stri
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OscServer::TryParseBindAddress(const std::string& bindAddress, in_addr& address, std::string& error)
|
||||||
|
{
|
||||||
|
if (bindAddress.empty())
|
||||||
|
{
|
||||||
|
error = "OSC bind address must not be empty.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
address = {};
|
||||||
|
if (InetPtonA(AF_INET, bindAddress.c_str(), &address) != 1)
|
||||||
|
{
|
||||||
|
error = "Invalid OSC bind address '" + bindAddress + "'. Use an IPv4 address such as 127.0.0.1 or 0.0.0.0.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void OscServer::Stop()
|
void OscServer::Stop()
|
||||||
{
|
{
|
||||||
mRunning = false;
|
mRunning = false;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public:
|
|||||||
OscServer();
|
OscServer();
|
||||||
~OscServer();
|
~OscServer();
|
||||||
|
|
||||||
bool Start(unsigned short port, const Callbacks& callbacks, std::string& error);
|
bool Start(const std::string& bindAddress, unsigned short port, const Callbacks& callbacks, std::string& error);
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
unsigned short GetPort() const { return mPort; }
|
unsigned short GetPort() const { return mPort; }
|
||||||
@@ -37,6 +37,7 @@ private:
|
|||||||
void ServerLoop();
|
void ServerLoop();
|
||||||
bool DecodeMessage(const char* data, int byteCount, OscMessage& message, std::string& error) const;
|
bool DecodeMessage(const char* data, int byteCount, OscMessage& message, std::string& error) const;
|
||||||
bool DispatchMessage(const OscMessage& message, std::string& error) const;
|
bool DispatchMessage(const OscMessage& message, std::string& error) const;
|
||||||
|
static bool TryParseBindAddress(const std::string& bindAddress, in_addr& address, std::string& error);
|
||||||
static bool DecodeArgument(const char* data, int byteCount, int& offset, char valueType, std::string& valueJson);
|
static bool DecodeArgument(const char* data, int byteCount, int& offset, char valueType, std::string& valueJson);
|
||||||
static bool ReadPaddedString(const char* data, int byteCount, int& offset, std::string& value);
|
static bool ReadPaddedString(const char* data, int byteCount, int& offset, std::string& value);
|
||||||
static bool ReadInt32(const char* data, int byteCount, int& offset, int& value);
|
static bool ReadInt32(const char* data, int byteCount, int& offset, int& value);
|
||||||
|
|||||||
@@ -1,13 +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 "RuntimeHost.h"
|
||||||
|
|
||||||
bool StartRuntimeControlServices(
|
bool StartControlServicesBoundary(
|
||||||
OpenGLComposite& composite,
|
OpenGLComposite& composite,
|
||||||
RuntimeHost& runtimeHost,
|
RuntimeHost& runtimeHost,
|
||||||
|
ControlServices& controlServices,
|
||||||
ControlServer& controlServer,
|
ControlServer& controlServer,
|
||||||
OscServer& oscServer,
|
OscServer& oscServer,
|
||||||
std::string& error)
|
std::string& error)
|
||||||
@@ -41,10 +43,10 @@ bool StartRuntimeControlServices(
|
|||||||
runtimeHost.SetServerPort(controlServer.GetPort());
|
runtimeHost.SetServerPort(controlServer.GetPort());
|
||||||
|
|
||||||
OscServer::Callbacks oscCallbacks;
|
OscServer::Callbacks oscCallbacks;
|
||||||
oscCallbacks.updateParameter = [&composite](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 composite.UpdateLayerParameterByControlKeyJson(layerKey, parameterKey, valueJson, actionError);
|
return controlServices.QueueOscUpdate(layerKey, parameterKey, valueJson, actionError);
|
||||||
};
|
};
|
||||||
if (runtimeHost.GetOscPort() > 0 && !oscServer.Start(runtimeHost.GetOscPort(), oscCallbacks, error))
|
if (runtimeHost.GetOscPort() > 0 && !oscServer.Start(runtimeHost.GetOscBindAddress(), runtimeHost.GetOscPort(), oscCallbacks, error))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -3,13 +3,15 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class ControlServer;
|
class ControlServer;
|
||||||
|
class ControlServices;
|
||||||
class OpenGLComposite;
|
class OpenGLComposite;
|
||||||
class OscServer;
|
class OscServer;
|
||||||
class RuntimeHost;
|
class RuntimeHost;
|
||||||
|
|
||||||
bool StartRuntimeControlServices(
|
bool StartControlServicesBoundary(
|
||||||
OpenGLComposite& composite,
|
OpenGLComposite& composite,
|
||||||
RuntimeHost& runtimeHost,
|
RuntimeHost& runtimeHost,
|
||||||
|
ControlServices& controlServices,
|
||||||
ControlServer& controlServer,
|
ControlServer& controlServer,
|
||||||
OscServer& oscServer,
|
OscServer& oscServer,
|
||||||
std::string& error);
|
std::string& error);
|
||||||
|
|||||||
@@ -1,19 +1,7 @@
|
|||||||
#include "RuntimeServices.h"
|
#include "RuntimeServices.h"
|
||||||
|
|
||||||
#include "ControlServer.h"
|
|
||||||
#include "OscServer.h"
|
|
||||||
#include "RuntimeControlBridge.h"
|
|
||||||
#include "RuntimeHost.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
RuntimeServices::RuntimeServices() :
|
RuntimeServices::RuntimeServices() :
|
||||||
mControlServer(std::make_unique<ControlServer>()),
|
mControlServices(std::make_unique<ControlServices>())
|
||||||
mOscServer(std::make_unique<OscServer>()),
|
|
||||||
mPollRunning(false),
|
|
||||||
mRegistryChanged(false),
|
|
||||||
mReloadRequested(false),
|
|
||||||
mPollFailed(false)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,96 +12,72 @@ RuntimeServices::~RuntimeServices()
|
|||||||
|
|
||||||
bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error)
|
bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error)
|
||||||
{
|
{
|
||||||
Stop();
|
return mControlServices && mControlServices->Start(composite, runtimeHost, error);
|
||||||
|
|
||||||
if (!StartRuntimeControlServices(composite, runtimeHost, *mControlServer, *mOscServer, error))
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeServices::BeginPolling(RuntimeHost& runtimeHost)
|
void RuntimeServices::BeginPolling(RuntimeHost& runtimeHost)
|
||||||
{
|
{
|
||||||
StartPolling(runtimeHost);
|
if (mControlServices)
|
||||||
|
mControlServices->BeginPolling(runtimeHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
|
{
|
||||||
|
if (mControlServices)
|
||||||
|
mControlServices->RequestBroadcastState();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeServices::QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
|
||||||
|
{
|
||||||
|
return mControlServices && mControlServices->QueueOscUpdate(layerKey, parameterKey, valueJson, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error)
|
||||||
|
{
|
||||||
|
if (!mControlServices)
|
||||||
|
{
|
||||||
|
appliedUpdates.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mControlServices->ApplyPendingOscUpdates(appliedUpdates, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeServices::QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error)
|
||||||
|
{
|
||||||
|
return mControlServices && mControlServices->QueueOscCommit(routeKey, layerKey, parameterKey, value, generation, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeServices::ClearOscState()
|
||||||
|
{
|
||||||
|
if (mControlServices)
|
||||||
|
mControlServices->ClearOscState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
|
||||||
|
{
|
||||||
|
if (!mControlServices)
|
||||||
|
{
|
||||||
|
completedCommits.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mControlServices->ConsumeCompletedOscCommits(completedCommits);
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimePollEvents RuntimeServices::ConsumePollEvents()
|
RuntimePollEvents RuntimeServices::ConsumePollEvents()
|
||||||
{
|
{
|
||||||
RuntimePollEvents events;
|
return mControlServices ? mControlServices->ConsumePollEvents() : RuntimePollEvents{};
|
||||||
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);
|
|
||||||
events.error = mPollError;
|
|
||||||
}
|
|
||||||
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeServices::StartPolling(RuntimeHost& runtimeHost)
|
|
||||||
{
|
|
||||||
if (mPollRunning.exchange(true))
|
|
||||||
return;
|
|
||||||
|
|
||||||
mPollThread = std::thread([this, &runtimeHost]() { PollLoop(runtimeHost); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeServices::StopPolling()
|
|
||||||
{
|
|
||||||
if (!mPollRunning.exchange(false))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (mPollThread.joinable())
|
|
||||||
mPollThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeServices::PollLoop(RuntimeHost& runtimeHost)
|
|
||||||
{
|
|
||||||
while (mPollRunning)
|
|
||||||
{
|
|
||||||
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,27 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include "ControlServices.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
class ControlServer;
|
|
||||||
class OpenGLComposite;
|
class OpenGLComposite;
|
||||||
class OscServer;
|
|
||||||
class RuntimeHost;
|
class RuntimeHost;
|
||||||
|
|
||||||
struct RuntimePollEvents
|
|
||||||
{
|
|
||||||
bool registryChanged = false;
|
|
||||||
bool reloadRequested = false;
|
|
||||||
bool failed = false;
|
|
||||||
std::string error;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RuntimeServices
|
class RuntimeServices
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using AppliedOscUpdate = ControlServices::AppliedOscUpdate;
|
||||||
|
using CompletedOscCommit = ControlServices::CompletedOscCommit;
|
||||||
|
|
||||||
RuntimeServices();
|
RuntimeServices();
|
||||||
~RuntimeServices();
|
~RuntimeServices();
|
||||||
|
|
||||||
@@ -29,20 +20,14 @@ public:
|
|||||||
void BeginPolling(RuntimeHost& runtimeHost);
|
void BeginPolling(RuntimeHost& runtimeHost);
|
||||||
void Stop();
|
void Stop();
|
||||||
void BroadcastState();
|
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 ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
|
||||||
RuntimePollEvents ConsumePollEvents();
|
RuntimePollEvents ConsumePollEvents();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void StartPolling(RuntimeHost& runtimeHost);
|
std::unique_ptr<ControlServices> mControlServices;
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,518 +0,0 @@
|
|||||||
#include "DeckLinkSession.h"
|
|
||||||
|
|
||||||
#include "GlRenderConstants.h"
|
|
||||||
|
|
||||||
#include <atlbase.h>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
|
||||||
#include <new>
|
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
std::string BstrToUtf8(BSTR value)
|
|
||||||
{
|
|
||||||
if (value == nullptr)
|
|
||||||
return std::string();
|
|
||||||
|
|
||||||
const int requiredBytes = WideCharToMultiByte(CP_UTF8, 0, value, -1, NULL, 0, NULL, NULL);
|
|
||||||
if (requiredBytes <= 1)
|
|
||||||
return std::string();
|
|
||||||
|
|
||||||
std::vector<char> utf8Name(static_cast<std::size_t>(requiredBytes), '\0');
|
|
||||||
if (WideCharToMultiByte(CP_UTF8, 0, value, -1, utf8Name.data(), requiredBytes, NULL, NULL) <= 0)
|
|
||||||
return std::string();
|
|
||||||
|
|
||||||
return std::string(utf8Name.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool InputSupportsFormat(IDeckLinkInput* input, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat)
|
|
||||||
{
|
|
||||||
if (input == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
BOOL supported = FALSE;
|
|
||||||
BMDDisplayMode actualMode = bmdModeUnknown;
|
|
||||||
const HRESULT result = input->DoesSupportVideoMode(
|
|
||||||
bmdVideoConnectionUnspecified,
|
|
||||||
displayMode,
|
|
||||||
pixelFormat,
|
|
||||||
bmdNoVideoInputConversion,
|
|
||||||
bmdSupportedVideoModeDefault,
|
|
||||||
&actualMode,
|
|
||||||
&supported);
|
|
||||||
return result == S_OK && supported != FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OutputSupportsFormat(IDeckLinkOutput* output, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat)
|
|
||||||
{
|
|
||||||
if (output == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
BOOL supported = FALSE;
|
|
||||||
BMDDisplayMode actualMode = bmdModeUnknown;
|
|
||||||
const HRESULT result = output->DoesSupportVideoMode(
|
|
||||||
bmdVideoConnectionUnspecified,
|
|
||||||
displayMode,
|
|
||||||
pixelFormat,
|
|
||||||
bmdNoVideoOutputConversion,
|
|
||||||
bmdSupportedVideoModeDefault,
|
|
||||||
&actualMode,
|
|
||||||
&supported);
|
|
||||||
return result == S_OK && supported != FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DeckLinkSession::~DeckLinkSession()
|
|
||||||
{
|
|
||||||
ReleaseResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeckLinkSession::ReleaseResources()
|
|
||||||
{
|
|
||||||
if (input != nullptr)
|
|
||||||
input->SetCallback(nullptr);
|
|
||||||
captureDelegate.Release();
|
|
||||||
input.Release();
|
|
||||||
|
|
||||||
if (output != nullptr)
|
|
||||||
output->SetScheduledFrameCompletionCallback(nullptr);
|
|
||||||
|
|
||||||
if (keyer != nullptr)
|
|
||||||
{
|
|
||||||
keyer->Disable();
|
|
||||||
externalKeyingActive = false;
|
|
||||||
}
|
|
||||||
keyer.Release();
|
|
||||||
|
|
||||||
playoutDelegate.Release();
|
|
||||||
outputVideoFrameQueue.clear();
|
|
||||||
output.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error)
|
|
||||||
{
|
|
||||||
CComPtr<IDeckLinkIterator> deckLinkIterator;
|
|
||||||
CComPtr<IDeckLinkDisplayMode> inputMode;
|
|
||||||
CComPtr<IDeckLinkDisplayMode> outputMode;
|
|
||||||
|
|
||||||
inputDisplayModeName = videoModes.input.displayName;
|
|
||||||
outputDisplayModeName = videoModes.output.displayName;
|
|
||||||
|
|
||||||
HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast<void**>(&deckLinkIterator));
|
|
||||||
if (FAILED(result))
|
|
||||||
{
|
|
||||||
error = "Please install the Blackmagic DeckLink drivers to use the features of this application.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CComPtr<IDeckLink> deckLink;
|
|
||||||
while (deckLinkIterator->Next(&deckLink) == S_OK)
|
|
||||||
{
|
|
||||||
int64_t duplexMode;
|
|
||||||
bool deviceSupportsInternalKeying = false;
|
|
||||||
bool deviceSupportsExternalKeying = false;
|
|
||||||
std::string modelName;
|
|
||||||
CComPtr<IDeckLinkProfileAttributes> deckLinkAttributes;
|
|
||||||
|
|
||||||
if (deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes) != S_OK)
|
|
||||||
{
|
|
||||||
printf("Could not obtain the IDeckLinkProfileAttributes interface\n");
|
|
||||||
deckLink.Release();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = deckLinkAttributes->GetInt(BMDDeckLinkDuplex, &duplexMode);
|
|
||||||
BOOL attributeFlag = FALSE;
|
|
||||||
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &attributeFlag) == S_OK)
|
|
||||||
deviceSupportsInternalKeying = (attributeFlag != FALSE);
|
|
||||||
attributeFlag = FALSE;
|
|
||||||
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &attributeFlag) == S_OK)
|
|
||||||
deviceSupportsExternalKeying = (attributeFlag != FALSE);
|
|
||||||
CComBSTR modelNameBstr;
|
|
||||||
if (deckLinkAttributes->GetString(BMDDeckLinkModelName, &modelNameBstr) == S_OK)
|
|
||||||
modelName = BstrToUtf8(modelNameBstr);
|
|
||||||
|
|
||||||
if (result != S_OK || duplexMode == bmdDuplexInactive)
|
|
||||||
{
|
|
||||||
deckLink.Release();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool inputUsed = false;
|
|
||||||
if (!input && deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&input) == S_OK)
|
|
||||||
inputUsed = true;
|
|
||||||
|
|
||||||
if (!output && (!inputUsed || (duplexMode == bmdDuplexFull)))
|
|
||||||
{
|
|
||||||
if (deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&output) != S_OK)
|
|
||||||
output.Release();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outputModelName = modelName;
|
|
||||||
supportsInternalKeying = deviceSupportsInternalKeying;
|
|
||||||
supportsExternalKeying = deviceSupportsExternalKeying;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deckLink.Release();
|
|
||||||
|
|
||||||
if (output && input)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!output)
|
|
||||||
{
|
|
||||||
error = "Expected an Output DeckLink device";
|
|
||||||
ReleaseResources();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CComPtr<IDeckLinkDisplayModeIterator> inputDisplayModeIterator;
|
|
||||||
if (input && input->GetDisplayModeIterator(&inputDisplayModeIterator) != S_OK)
|
|
||||||
{
|
|
||||||
error = "Cannot get input Display Mode Iterator.";
|
|
||||||
ReleaseResources();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, videoModes.input.displayMode, &inputMode))
|
|
||||||
{
|
|
||||||
error = "Cannot get specified input BMDDisplayMode for configured mode: " + videoModes.input.displayName;
|
|
||||||
ReleaseResources();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
inputDisplayModeIterator.Release();
|
|
||||||
|
|
||||||
CComPtr<IDeckLinkDisplayModeIterator> outputDisplayModeIterator;
|
|
||||||
if (output->GetDisplayModeIterator(&outputDisplayModeIterator) != S_OK)
|
|
||||||
{
|
|
||||||
error = "Cannot get output Display Mode Iterator.";
|
|
||||||
ReleaseResources();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, videoModes.output.displayMode, &outputMode))
|
|
||||||
{
|
|
||||||
error = "Cannot get specified output BMDDisplayMode for configured mode: " + videoModes.output.displayName;
|
|
||||||
ReleaseResources();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
outputFrameSize = { static_cast<unsigned>(outputMode->GetWidth()), static_cast<unsigned>(outputMode->GetHeight()) };
|
|
||||||
inputFrameSize = inputMode
|
|
||||||
? FrameSize{ static_cast<unsigned>(inputMode->GetWidth()), static_cast<unsigned>(inputMode->GetHeight()) }
|
|
||||||
: outputFrameSize;
|
|
||||||
if (!input)
|
|
||||||
inputDisplayModeName = "No input - black frame";
|
|
||||||
outputMode->GetFrameRate(&frameDuration, &frameTimescale);
|
|
||||||
|
|
||||||
inputFrameRowBytes = inputFrameSize.width * 2u;
|
|
||||||
outputFrameRowBytes = outputFrameSize.width * 4u;
|
|
||||||
captureTextureWidth = inputFrameSize.width / 2u;
|
|
||||||
outputPackTextureWidth = outputFrameSize.width;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoModes, std::string& error)
|
|
||||||
{
|
|
||||||
if (!output)
|
|
||||||
{
|
|
||||||
error = "Expected an Output DeckLink device";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
formatStatusMessage.clear();
|
|
||||||
|
|
||||||
const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, videoModes.input.displayMode, bmdFormat10BitYUV);
|
|
||||||
inputPixelFormat = input != nullptr ? ChoosePreferredVideoIOFormat(inputTenBitSupported) : VideoIOPixelFormat::Uyvy8;
|
|
||||||
if (input != nullptr && !inputTenBitSupported)
|
|
||||||
formatStatusMessage += "DeckLink input does not report 10-bit YUV support for the configured mode; using 8-bit capture. ";
|
|
||||||
|
|
||||||
const bool outputTenBitSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUV);
|
|
||||||
outputPixelFormat = ChoosePreferredVideoIOFormat(outputTenBitSupported);
|
|
||||||
if (!outputTenBitSupported)
|
|
||||||
formatStatusMessage += "DeckLink output does not report 10-bit YUV support for the configured mode; using 8-bit BGRA output. ";
|
|
||||||
|
|
||||||
int deckLinkOutputRowBytes = 0;
|
|
||||||
if (output->RowBytesForPixelFormat(OutputIsTenBit() ? bmdFormat10BitYUV : bmdFormat8BitBGRA, outputFrameSize.width, &deckLinkOutputRowBytes) != S_OK)
|
|
||||||
{
|
|
||||||
error = "DeckLink output setup failed while calculating output row bytes.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
outputFrameRowBytes = static_cast<unsigned>(deckLinkOutputRowBytes);
|
|
||||||
outputPackTextureWidth = OutputIsTenBit()
|
|
||||||
? PackedTextureWidthFromRowBytes(outputFrameRowBytes)
|
|
||||||
: outputFrameSize.width;
|
|
||||||
|
|
||||||
if (InputIsTenBit())
|
|
||||||
{
|
|
||||||
int deckLinkInputRowBytes = 0;
|
|
||||||
if (output->RowBytesForPixelFormat(bmdFormat10BitYUV, inputFrameSize.width, &deckLinkInputRowBytes) == S_OK)
|
|
||||||
inputFrameRowBytes = static_cast<unsigned>(deckLinkInputRowBytes);
|
|
||||||
else
|
|
||||||
inputFrameRowBytes = MinimumV210RowBytes(inputFrameSize.width);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
inputFrameRowBytes = inputFrameSize.width * 2u;
|
|
||||||
}
|
|
||||||
captureTextureWidth = InputIsTenBit()
|
|
||||||
? PackedTextureWidthFromRowBytes(inputFrameRowBytes)
|
|
||||||
: inputFrameSize.width / 2u;
|
|
||||||
|
|
||||||
std::ostringstream status;
|
|
||||||
status << "DeckLink formats: capture " << (input ? VideoIOPixelFormatName(inputPixelFormat) : "none")
|
|
||||||
<< ", output " << (OutputIsTenBit() ? "10-bit YUV v210" : "8-bit BGRA") << ".";
|
|
||||||
if (!formatStatusMessage.empty())
|
|
||||||
status << " " << formatStatusMessage;
|
|
||||||
formatStatusMessage = status.str();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeckLinkSession::ConfigureInput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, const VideoFormat& inputVideoMode, std::string& error)
|
|
||||||
{
|
|
||||||
(void)hdc;
|
|
||||||
(void)hglrc;
|
|
||||||
|
|
||||||
if (!input)
|
|
||||||
{
|
|
||||||
hasNoInputSource = true;
|
|
||||||
inputDisplayModeName = "No input - black frame";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BMDPixelFormat deckLinkInputPixelFormat = DeckLinkPixelFormatForVideoIO(inputPixelFormat);
|
|
||||||
if (input->EnableVideoInput(inputVideoMode.displayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK)
|
|
||||||
{
|
|
||||||
if (inputPixelFormat == VideoIOPixelFormat::V210)
|
|
||||||
{
|
|
||||||
OutputDebugStringA("DeckLink 10-bit input could not be enabled; falling back to 8-bit capture.\n");
|
|
||||||
inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
|
||||||
inputFrameRowBytes = inputFrameSize.width * 2u;
|
|
||||||
captureTextureWidth = inputFrameSize.width / 2u;
|
|
||||||
if (input->EnableVideoInput(inputVideoMode.displayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault) == S_OK)
|
|
||||||
{
|
|
||||||
std::ostringstream status;
|
|
||||||
status << "DeckLink formats: capture " << VideoIOPixelFormatName(inputPixelFormat)
|
|
||||||
<< ", output " << (OutputIsTenBit() ? "10-bit YUV v210" : "8-bit BGRA")
|
|
||||||
<< ". DeckLink 10-bit input enable failed; using 8-bit capture.";
|
|
||||||
formatStatusMessage = status.str();
|
|
||||||
goto input_enabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputDebugStringA("DeckLink input could not be enabled; continuing in output-only black-frame mode.\n");
|
|
||||||
input.Release();
|
|
||||||
hasNoInputSource = true;
|
|
||||||
inputDisplayModeName = "No input - black frame";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
input_enabled:
|
|
||||||
captureDelegate.Attach(new (std::nothrow) CaptureDelegate(owner));
|
|
||||||
if (captureDelegate == nullptr)
|
|
||||||
{
|
|
||||||
error = "DeckLink input setup failed while creating the capture callback.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (input->SetCallback(captureDelegate) != S_OK)
|
|
||||||
{
|
|
||||||
error = "DeckLink input setup failed while installing the capture callback.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error)
|
|
||||||
{
|
|
||||||
(void)hdc;
|
|
||||||
(void)hglrc;
|
|
||||||
|
|
||||||
if (output->EnableVideoOutput(outputVideoMode.displayMode, bmdVideoOutputFlagDefault) != S_OK)
|
|
||||||
{
|
|
||||||
error = "DeckLink output setup failed while enabling video output.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output->QueryInterface(IID_IDeckLinkKeyer, (void**)&keyer) == S_OK && keyer != NULL)
|
|
||||||
keyerInterfaceAvailable = true;
|
|
||||||
|
|
||||||
if (externalKeyingEnabled)
|
|
||||||
{
|
|
||||||
if (!supportsExternalKeying)
|
|
||||||
{
|
|
||||||
statusMessage = "External keying was requested, but the selected DeckLink output does not report external keying support.";
|
|
||||||
}
|
|
||||||
else if (!keyerInterfaceAvailable)
|
|
||||||
{
|
|
||||||
statusMessage = "External keying was requested, but the selected DeckLink output does not expose the IDeckLinkKeyer interface.";
|
|
||||||
}
|
|
||||||
else if (keyer->Enable(TRUE) != S_OK || keyer->SetLevel(255) != S_OK)
|
|
||||||
{
|
|
||||||
statusMessage = "External keying was requested, but enabling the DeckLink keyer failed.";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
externalKeyingActive = true;
|
|
||||||
statusMessage = "External keying is active on the selected DeckLink output.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (supportsExternalKeying)
|
|
||||||
{
|
|
||||||
statusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it.";
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
CComPtr<IDeckLinkMutableVideoFrame> outputFrame;
|
|
||||||
|
|
||||||
const BMDPixelFormat deckLinkOutputPixelFormat = OutputIsTenBit() ? bmdFormat10BitYUV : bmdFormat8BitBGRA;
|
|
||||||
if (output->CreateVideoFrame(outputFrameSize.width, outputFrameSize.height, outputFrameRowBytes, deckLinkOutputPixelFormat, bmdFrameFlagFlipVertical, &outputFrame) != S_OK)
|
|
||||||
{
|
|
||||||
error = "DeckLink output setup failed while creating an output video frame.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
outputVideoFrameQueue.push_back(outputFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
playoutDelegate.Attach(new (std::nothrow) PlayoutDelegate(owner));
|
|
||||||
if (playoutDelegate == nullptr)
|
|
||||||
{
|
|
||||||
error = "DeckLink output setup failed while creating the playout callback.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output->SetScheduledFrameCompletionCallback(playoutDelegate) != S_OK)
|
|
||||||
{
|
|
||||||
error = "DeckLink output setup failed while installing the scheduled-frame callback.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!formatStatusMessage.empty())
|
|
||||||
statusMessage = statusMessage.empty() ? formatStatusMessage : formatStatusMessage + " " + statusMessage;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
double DeckLinkSession::FrameBudgetMilliseconds() const
|
|
||||||
{
|
|
||||||
return frameTimescale != 0
|
|
||||||
? (static_cast<double>(frameDuration) * 1000.0) / static_cast<double>(frameTimescale)
|
|
||||||
: 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkMutableVideoFrame* DeckLinkSession::RotateOutputFrame()
|
|
||||||
{
|
|
||||||
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame = outputVideoFrameQueue.front();
|
|
||||||
outputVideoFrameQueue.push_back(outputVideoFrame);
|
|
||||||
outputVideoFrameQueue.pop_front();
|
|
||||||
return outputVideoFrame.p;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeckLinkSession::AccountForCompletionResult(BMDOutputFrameCompletionResult completionResult)
|
|
||||||
{
|
|
||||||
if (completionResult == bmdOutputFrameDisplayedLate || completionResult == bmdOutputFrameDropped)
|
|
||||||
totalPlayoutFrames += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeckLinkSession::ScheduleOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame)
|
|
||||||
{
|
|
||||||
if (output->ScheduleVideoFrame(outputVideoFrame, (totalPlayoutFrames * frameDuration), frameDuration, frameTimescale) != S_OK)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
totalPlayoutFrames++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeckLinkSession::Start()
|
|
||||||
{
|
|
||||||
totalPlayoutFrames = 0;
|
|
||||||
if (!output)
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, "Cannot start playout because no DeckLink output device is available.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (outputVideoFrameQueue.empty())
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, "Cannot start playout because the output frame queue is empty.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (unsigned i = 0; i < kPrerollFrameCount; i++)
|
|
||||||
{
|
|
||||||
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame = outputVideoFrameQueue.front();
|
|
||||||
outputVideoFrameQueue.push_back(outputVideoFrame);
|
|
||||||
outputVideoFrameQueue.pop_front();
|
|
||||||
|
|
||||||
CComPtr<IDeckLinkVideoBuffer> outputVideoFrameBuffer;
|
|
||||||
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, "Could not query the preroll output frame buffer.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, "Could not write to the preroll output frame buffer.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* pFrame;
|
|
||||||
outputVideoFrameBuffer->GetBytes((void**)&pFrame);
|
|
||||||
memset(pFrame, 0, outputVideoFrame->GetRowBytes() * outputFrameSize.height);
|
|
||||||
|
|
||||||
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
|
|
||||||
|
|
||||||
if (output->ScheduleVideoFrame(outputVideoFrame, (totalPlayoutFrames * frameDuration), frameDuration, frameTimescale) != S_OK)
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, "Could not schedule a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalPlayoutFrames++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input)
|
|
||||||
{
|
|
||||||
if (input->StartStreams() != S_OK)
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, "Could not start the DeckLink input stream.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (output->StartScheduledPlayback(0, frameTimescale, 1.0) != S_OK)
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, "Could not start DeckLink scheduled playback.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeckLinkSession::Stop()
|
|
||||||
{
|
|
||||||
if (keyer != nullptr)
|
|
||||||
{
|
|
||||||
keyer->Disable();
|
|
||||||
externalKeyingActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input)
|
|
||||||
{
|
|
||||||
input->StopStreams();
|
|
||||||
input->DisableVideoInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output)
|
|
||||||
{
|
|
||||||
output->StopScheduledPlayback(0, NULL, 0);
|
|
||||||
output->DisableVideoOutput();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "DeckLinkAPI_h.h"
|
|
||||||
#include "DeckLinkDisplayMode.h"
|
|
||||||
#include "DeckLinkFrameTransfer.h"
|
|
||||||
#include "VideoIOFormat.h"
|
|
||||||
|
|
||||||
#include <atlbase.h>
|
|
||||||
#include <deque>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class OpenGLComposite;
|
|
||||||
|
|
||||||
class DeckLinkSession
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DeckLinkSession() = default;
|
|
||||||
~DeckLinkSession();
|
|
||||||
|
|
||||||
void ReleaseResources();
|
|
||||||
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error);
|
|
||||||
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, std::string& error);
|
|
||||||
bool ConfigureInput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, const VideoFormat& inputVideoMode, std::string& error);
|
|
||||||
bool ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error);
|
|
||||||
bool Start();
|
|
||||||
bool Stop();
|
|
||||||
|
|
||||||
bool HasInputDevice() const { return input != nullptr; }
|
|
||||||
bool HasInputSource() const { return !hasNoInputSource; }
|
|
||||||
void SetInputSourceMissing(bool missing) { hasNoInputSource = missing; }
|
|
||||||
bool InputOutputDimensionsDiffer() const { return inputFrameSize != outputFrameSize; }
|
|
||||||
const FrameSize& InputFrameSize() const { return inputFrameSize; }
|
|
||||||
const FrameSize& OutputFrameSize() const { return outputFrameSize; }
|
|
||||||
unsigned InputFrameWidth() const { return inputFrameSize.width; }
|
|
||||||
unsigned InputFrameHeight() const { return inputFrameSize.height; }
|
|
||||||
unsigned OutputFrameWidth() const { return outputFrameSize.width; }
|
|
||||||
unsigned OutputFrameHeight() const { return outputFrameSize.height; }
|
|
||||||
VideoIOPixelFormat InputPixelFormat() const { return inputPixelFormat; }
|
|
||||||
VideoIOPixelFormat OutputPixelFormat() const { return outputPixelFormat; }
|
|
||||||
bool InputIsTenBit() const { return VideoIOPixelFormatIsTenBit(inputPixelFormat); }
|
|
||||||
bool OutputIsTenBit() const { return VideoIOPixelFormatIsTenBit(outputPixelFormat); }
|
|
||||||
unsigned InputFrameRowBytes() const { return inputFrameRowBytes; }
|
|
||||||
unsigned OutputFrameRowBytes() const { return outputFrameRowBytes; }
|
|
||||||
unsigned CaptureTextureWidth() const { return captureTextureWidth; }
|
|
||||||
unsigned OutputPackTextureWidth() const { return outputPackTextureWidth; }
|
|
||||||
const std::string& FormatStatusMessage() const { return formatStatusMessage; }
|
|
||||||
const std::string& InputDisplayModeName() const { return inputDisplayModeName; }
|
|
||||||
const std::string& OutputModelName() const { return outputModelName; }
|
|
||||||
bool SupportsInternalKeying() const { return supportsInternalKeying; }
|
|
||||||
bool SupportsExternalKeying() const { return supportsExternalKeying; }
|
|
||||||
bool KeyerInterfaceAvailable() const { return keyerInterfaceAvailable; }
|
|
||||||
bool ExternalKeyingActive() const { return externalKeyingActive; }
|
|
||||||
const std::string& StatusMessage() const { return statusMessage; }
|
|
||||||
void SetStatusMessage(const std::string& message) { statusMessage = message; }
|
|
||||||
double FrameBudgetMilliseconds() const;
|
|
||||||
IDeckLinkMutableVideoFrame* RotateOutputFrame();
|
|
||||||
void AccountForCompletionResult(BMDOutputFrameCompletionResult completionResult);
|
|
||||||
bool ScheduleOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame);
|
|
||||||
|
|
||||||
private:
|
|
||||||
CComPtr<CaptureDelegate> captureDelegate;
|
|
||||||
CComPtr<PlayoutDelegate> playoutDelegate;
|
|
||||||
CComPtr<IDeckLinkInput> input;
|
|
||||||
CComPtr<IDeckLinkOutput> output;
|
|
||||||
CComPtr<IDeckLinkKeyer> keyer;
|
|
||||||
std::deque<CComPtr<IDeckLinkMutableVideoFrame>> outputVideoFrameQueue;
|
|
||||||
BMDTimeValue frameDuration = 0;
|
|
||||||
BMDTimeScale frameTimescale = 0;
|
|
||||||
unsigned totalPlayoutFrames = 0;
|
|
||||||
FrameSize inputFrameSize;
|
|
||||||
FrameSize outputFrameSize;
|
|
||||||
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
|
||||||
VideoIOPixelFormat outputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
|
||||||
unsigned inputFrameRowBytes = 0;
|
|
||||||
unsigned outputFrameRowBytes = 0;
|
|
||||||
unsigned captureTextureWidth = 0;
|
|
||||||
unsigned outputPackTextureWidth = 0;
|
|
||||||
std::string inputDisplayModeName = "1080p59.94";
|
|
||||||
std::string outputDisplayModeName = "1080p59.94";
|
|
||||||
bool hasNoInputSource = true;
|
|
||||||
std::string outputModelName;
|
|
||||||
bool supportsInternalKeying = false;
|
|
||||||
bool supportsExternalKeying = false;
|
|
||||||
bool keyerInterfaceAvailable = false;
|
|
||||||
bool externalKeyingActive = false;
|
|
||||||
std::string statusMessage;
|
|
||||||
std::string formatStatusMessage;
|
|
||||||
};
|
|
||||||
@@ -1,47 +1,118 @@
|
|||||||
#include "DeckLinkDisplayMode.h"
|
#include "DeckLinkDisplayMode.h"
|
||||||
#include "DeckLinkSession.h"
|
|
||||||
#include "OpenGLComposite.h"
|
#include "OpenGLComposite.h"
|
||||||
#include "GLExtensions.h"
|
#include "GLExtensions.h"
|
||||||
#include "GlRenderConstants.h"
|
#include "GlRenderConstants.h"
|
||||||
#include "OpenGLDeckLinkBridge.h"
|
|
||||||
#include "OpenGLRenderPass.h"
|
|
||||||
#include "OpenGLShaderPrograms.h"
|
|
||||||
#include "PngScreenshotWriter.h"
|
#include "PngScreenshotWriter.h"
|
||||||
|
#include "RenderEngine.h"
|
||||||
|
#include "RuntimeParameterUtils.h"
|
||||||
#include "RuntimeServices.h"
|
#include "RuntimeServices.h"
|
||||||
#include "ShaderBuildQueue.h"
|
#include "ShaderBuildQueue.h"
|
||||||
|
#include "VideoBackend.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#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) :
|
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
|
||||||
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC),
|
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC),
|
||||||
mDeckLink(std::make_unique<DeckLinkSession>()),
|
|
||||||
mRenderer(std::make_unique<OpenGLRenderer>()),
|
|
||||||
mUseCommittedLayerStates(false),
|
mUseCommittedLayerStates(false),
|
||||||
mScreenshotRequested(false)
|
mScreenshotRequested(false)
|
||||||
{
|
{
|
||||||
InitializeCriticalSection(&pMutex);
|
InitializeCriticalSection(&pMutex);
|
||||||
mRuntimeHost = std::make_unique<RuntimeHost>();
|
mRuntimeHost = std::make_unique<RuntimeHost>();
|
||||||
mDeckLinkBridge = std::make_unique<OpenGLDeckLinkBridge>(
|
mRuntimeStore = std::make_unique<RuntimeStore>(*mRuntimeHost);
|
||||||
*mDeckLink,
|
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(*mRuntimeHost);
|
||||||
*mRenderer,
|
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore);
|
||||||
*mRuntimeHost,
|
mRenderEngine = std::make_unique<RenderEngine>(
|
||||||
|
*mRuntimeSnapshotProvider,
|
||||||
|
mRuntimeHost->GetHealthTelemetry(),
|
||||||
pMutex,
|
pMutex,
|
||||||
hGLDC,
|
hGLDC,
|
||||||
hGLRC,
|
hGLRC,
|
||||||
[this]() { renderEffect(); },
|
[this]() { renderEffect(); },
|
||||||
[this]() { ProcessScreenshotRequest(); },
|
[this]() { ProcessScreenshotRequest(); },
|
||||||
[this]() { paintGL(); });
|
[this]() { paintGL(false); });
|
||||||
mRenderPass = std::make_unique<OpenGLRenderPass>(*mRenderer);
|
mVideoBackend = std::make_unique<VideoBackend>(*mRenderEngine, mRuntimeHost->GetHealthTelemetry());
|
||||||
mShaderPrograms = std::make_unique<OpenGLShaderPrograms>(*mRenderer, *mRuntimeHost);
|
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeSnapshotProvider);
|
||||||
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeHost);
|
|
||||||
mRuntimeServices = std::make_unique<RuntimeServices>();
|
mRuntimeServices = std::make_unique<RuntimeServices>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,34 +122,39 @@ OpenGLComposite::~OpenGLComposite()
|
|||||||
mRuntimeServices->Stop();
|
mRuntimeServices->Stop();
|
||||||
if (mShaderBuildQueue)
|
if (mShaderBuildQueue)
|
||||||
mShaderBuildQueue->Stop();
|
mShaderBuildQueue->Stop();
|
||||||
mDeckLink->ReleaseResources();
|
if (mVideoBackend)
|
||||||
mRenderer->DestroyResources();
|
mVideoBackend->ReleaseResources();
|
||||||
|
|
||||||
DeleteCriticalSection(&pMutex);
|
DeleteCriticalSection(&pMutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::InitDeckLink()
|
bool OpenGLComposite::InitDeckLink()
|
||||||
|
{
|
||||||
|
return InitVideoIO();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenGLComposite::InitVideoIO()
|
||||||
{
|
{
|
||||||
VideoFormatSelection videoModes;
|
VideoFormatSelection videoModes;
|
||||||
std::string initFailureReason;
|
std::string initFailureReason;
|
||||||
|
|
||||||
if (mRuntimeHost && mRuntimeHost->GetRepoRoot().empty())
|
if (mRuntimeStore && mRuntimeStore->GetRuntimeRepositoryRoot().empty())
|
||||||
{
|
{
|
||||||
std::string runtimeError;
|
std::string runtimeError;
|
||||||
if (!mRuntimeHost->Initialize(runtimeError))
|
if (!mRuntimeStore->InitializeStore(runtimeError))
|
||||||
{
|
{
|
||||||
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
|
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mRuntimeHost)
|
if (mRuntimeStore)
|
||||||
{
|
{
|
||||||
if (!ResolveConfiguredVideoFormats(
|
if (!ResolveConfiguredVideoFormats(
|
||||||
mRuntimeHost->GetInputVideoFormat(),
|
mRuntimeStore->GetConfiguredInputVideoFormat(),
|
||||||
mRuntimeHost->GetInputFrameRate(),
|
mRuntimeStore->GetConfiguredInputFrameRate(),
|
||||||
mRuntimeHost->GetOutputVideoFormat(),
|
mRuntimeStore->GetConfiguredOutputVideoFormat(),
|
||||||
mRuntimeHost->GetOutputFrameRate(),
|
mRuntimeStore->GetConfiguredOutputFrameRate(),
|
||||||
videoModes,
|
videoModes,
|
||||||
initFailureReason))
|
initFailureReason))
|
||||||
{
|
{
|
||||||
@@ -87,7 +163,7 @@ bool OpenGLComposite::InitDeckLink()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mDeckLink->DiscoverDevicesAndModes(videoModes, initFailureReason))
|
if (!mVideoBackend->DiscoverDevicesAndModes(videoModes, initFailureReason))
|
||||||
{
|
{
|
||||||
const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application."
|
const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application."
|
||||||
? "This application requires the DeckLink drivers installed."
|
? "This application requires the DeckLink drivers installed."
|
||||||
@@ -95,7 +171,8 @@ bool OpenGLComposite::InitDeckLink()
|
|||||||
MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR);
|
MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!mDeckLink->SelectPreferredFormats(videoModes, initFailureReason))
|
const bool outputAlphaRequired = mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured();
|
||||||
|
if (!mVideoBackend->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason))
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
if (! CheckOpenGLExtensions())
|
if (! CheckOpenGLExtensions())
|
||||||
@@ -110,59 +187,68 @@ bool OpenGLComposite::InitDeckLink()
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
PublishDeckLinkOutputStatus(mDeckLink->OutputModelName().empty()
|
PublishVideoIOStatus(mVideoBackend->OutputModelName().empty()
|
||||||
? "DeckLink output device selected."
|
? "DeckLink output device selected."
|
||||||
: ("Selected output device: " + mDeckLink->OutputModelName()));
|
: ("Selected output device: " + mVideoBackend->OutputModelName()));
|
||||||
|
|
||||||
// Resize window to match output video frame, but scale large formats down by half for viewing.
|
// Resize window to match output video frame, but scale large formats down by half for viewing.
|
||||||
if (mDeckLink->OutputFrameWidth() < 1920)
|
if (mVideoBackend->OutputFrameWidth() < 1920)
|
||||||
resizeWindow(mDeckLink->OutputFrameWidth(), mDeckLink->OutputFrameHeight());
|
resizeWindow(mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight());
|
||||||
else
|
else
|
||||||
resizeWindow(mDeckLink->OutputFrameWidth() / 2, mDeckLink->OutputFrameHeight() / 2);
|
resizeWindow(mVideoBackend->OutputFrameWidth() / 2, mVideoBackend->OutputFrameHeight() / 2);
|
||||||
|
|
||||||
if (!mDeckLink->ConfigureInput(this, hGLDC, hGLRC, videoModes.input, initFailureReason))
|
if (!mVideoBackend->ConfigureInput(videoModes.input, initFailureReason))
|
||||||
{
|
{
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
if (!mDeckLink->HasInputDevice() && mRuntimeHost)
|
if (!mVideoBackend->HasInputDevice() && mRuntimeHost)
|
||||||
{
|
{
|
||||||
mRuntimeHost->SetSignalStatus(false, mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), mDeckLink->InputDisplayModeName());
|
mRuntimeHost->GetHealthTelemetry().ReportSignalStatus(
|
||||||
|
false,
|
||||||
|
mVideoBackend->InputFrameWidth(),
|
||||||
|
mVideoBackend->InputFrameHeight(),
|
||||||
|
mVideoBackend->InputDisplayModeName());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mDeckLink->ConfigureOutput(this, hGLDC, hGLRC, videoModes.output, mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled(), initFailureReason))
|
if (!mVideoBackend->ConfigureOutput(videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason))
|
||||||
{
|
{
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
PublishDeckLinkOutputStatus(mDeckLink->StatusMessage());
|
PublishVideoIOStatus(mVideoBackend->StatusMessage());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
if (!initFailureReason.empty())
|
if (!initFailureReason.empty())
|
||||||
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR);
|
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR);
|
||||||
mDeckLink->ReleaseResources();
|
mVideoBackend->ReleaseResources();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLComposite::paintGL()
|
void OpenGLComposite::paintGL(bool force)
|
||||||
{
|
{
|
||||||
if (!TryEnterCriticalSection(&pMutex))
|
if (!force)
|
||||||
|
{
|
||||||
|
if (IsIconic(hGLWnd))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned previewFps = mRuntimeStore ? mRuntimeStore->GetConfiguredPreviewFps() : 30u;
|
||||||
|
if (!mRenderEngine->TryPresentPreview(force, previewFps, mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight()))
|
||||||
{
|
{
|
||||||
ValidateRect(hGLWnd, NULL);
|
ValidateRect(hGLWnd, NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mRenderer->PresentToWindow(hGLDC, mDeckLink->OutputFrameWidth(), mDeckLink->OutputFrameHeight());
|
|
||||||
ValidateRect(hGLWnd, NULL);
|
ValidateRect(hGLWnd, NULL);
|
||||||
LeaveCriticalSection(&pMutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLComposite::resizeGL(WORD width, WORD height)
|
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
|
// 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().
|
// an off-screen FBO in paintGL(). Just save the width and height for use in paintGL().
|
||||||
mRenderer->ResizeView(width, height);
|
mRenderEngine->ResizeView(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLComposite::resizeWindow(int width, int height)
|
void OpenGLComposite::resizeWindow(int width, int height)
|
||||||
@@ -174,22 +260,23 @@ void OpenGLComposite::resizeWindow(int width, int height)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLComposite::PublishDeckLinkOutputStatus(const std::string& statusMessage)
|
void OpenGLComposite::PublishVideoIOStatus(const std::string& statusMessage)
|
||||||
{
|
{
|
||||||
if (!mRuntimeHost)
|
if (!mRuntimeHost)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!statusMessage.empty())
|
if (!statusMessage.empty())
|
||||||
mDeckLink->SetStatusMessage(statusMessage);
|
mVideoBackend->SetStatusMessage(statusMessage);
|
||||||
|
|
||||||
mRuntimeHost->SetDeckLinkOutputStatus(
|
mRuntimeHost->SetVideoIOStatus(
|
||||||
mDeckLink->OutputModelName(),
|
"decklink",
|
||||||
mDeckLink->SupportsInternalKeying(),
|
mVideoBackend->OutputModelName(),
|
||||||
mDeckLink->SupportsExternalKeying(),
|
mVideoBackend->SupportsInternalKeying(),
|
||||||
mDeckLink->KeyerInterfaceAvailable(),
|
mVideoBackend->SupportsExternalKeying(),
|
||||||
mRuntimeHost->ExternalKeyingEnabled(),
|
mVideoBackend->KeyerInterfaceAvailable(),
|
||||||
mDeckLink->ExternalKeyingActive(),
|
mRuntimeStore ? mRuntimeStore->IsExternalKeyingConfigured() : false,
|
||||||
mDeckLink->StatusMessage());
|
mVideoBackend->ExternalKeyingActive(),
|
||||||
|
mVideoBackend->StatusMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::InitOpenGLState()
|
bool OpenGLComposite::InitOpenGLState()
|
||||||
@@ -198,7 +285,7 @@ bool OpenGLComposite::InitOpenGLState()
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::string runtimeError;
|
std::string runtimeError;
|
||||||
if (mRuntimeHost->GetRepoRoot().empty() && !mRuntimeHost->Initialize(runtimeError))
|
if (mRuntimeStore->GetRuntimeRepositoryRoot().empty() && !mRuntimeStore->InitializeStore(runtimeError))
|
||||||
{
|
{
|
||||||
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
|
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
|
||||||
return false;
|
return false;
|
||||||
@@ -212,59 +299,50 @@ bool OpenGLComposite::InitOpenGLState()
|
|||||||
|
|
||||||
// Prepare the runtime shader program generated from the active shader package.
|
// Prepare the runtime shader program generated from the active shader package.
|
||||||
char compilerErrorMessage[1024];
|
char compilerErrorMessage[1024];
|
||||||
if (!mShaderPrograms->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage))
|
if (!mRenderEngine->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||||
{
|
{
|
||||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK);
|
MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!mShaderPrograms->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage))
|
if (!mRenderEngine->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||||
{
|
{
|
||||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL output pack shader failed to load or compile", MB_OK);
|
MessageBoxA(NULL, compilerErrorMessage, "OpenGL output pack shader failed to load or compile", MB_OK);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mShaderPrograms->CompileLayerPrograms(mDeckLink->InputFrameWidth(), mDeckLink->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();
|
|
||||||
|
|
||||||
std::string rendererError;
|
std::string rendererError;
|
||||||
if (!mRenderer->InitializeResources(
|
if (!mRenderEngine->InitializeResources(
|
||||||
mDeckLink->InputFrameWidth(),
|
mVideoBackend->InputFrameWidth(),
|
||||||
mDeckLink->InputFrameHeight(),
|
mVideoBackend->InputFrameHeight(),
|
||||||
mDeckLink->CaptureTextureWidth(),
|
mVideoBackend->CaptureTextureWidth(),
|
||||||
mDeckLink->OutputFrameWidth(),
|
mVideoBackend->OutputFrameWidth(),
|
||||||
mDeckLink->OutputFrameHeight(),
|
mVideoBackend->OutputFrameHeight(),
|
||||||
mDeckLink->OutputPackTextureWidth(),
|
mVideoBackend->OutputPackTextureWidth(),
|
||||||
rendererError))
|
rendererError))
|
||||||
{
|
{
|
||||||
MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK);
|
MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK);
|
||||||
return false;
|
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.");
|
||||||
|
mUseCommittedLayerStates = false;
|
||||||
|
|
||||||
|
mRenderEngine->ResetTemporalHistoryState();
|
||||||
|
mRenderEngine->ResetShaderFeedbackState();
|
||||||
|
|
||||||
broadcastRuntimeState();
|
broadcastRuntimeState();
|
||||||
mRuntimeServices->BeginPolling(*mRuntimeHost);
|
mRuntimeServices->BeginPolling(*mRuntimeHost);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeckLink delegates still target OpenGLComposite; the bridge owns the per-frame work.
|
|
||||||
void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource)
|
|
||||||
{
|
|
||||||
mDeckLinkBridge->VideoFrameArrived(inputFrame, hasNoInputSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult)
|
|
||||||
{
|
|
||||||
mDeckLinkBridge->PlayoutFrameCompleted(completedFrame, completionResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::Start()
|
bool OpenGLComposite::Start()
|
||||||
{
|
{
|
||||||
return mDeckLink->Start();
|
return mVideoBackend->Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::Stop()
|
bool OpenGLComposite::Stop()
|
||||||
@@ -272,24 +350,18 @@ bool OpenGLComposite::Stop()
|
|||||||
if (mRuntimeServices)
|
if (mRuntimeServices)
|
||||||
mRuntimeServices->Stop();
|
mRuntimeServices->Stop();
|
||||||
|
|
||||||
const bool wasExternalKeyingActive = mDeckLink->ExternalKeyingActive();
|
const bool wasExternalKeyingActive = mVideoBackend->ExternalKeyingActive();
|
||||||
mDeckLink->Stop();
|
mVideoBackend->Stop();
|
||||||
if (wasExternalKeyingActive)
|
if (wasExternalKeyingActive)
|
||||||
PublishDeckLinkOutputStatus("External keying has been disabled.");
|
PublishVideoIOStatus("External keying has been disabled.");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::ReloadShader()
|
bool OpenGLComposite::ReloadShader(bool preserveFeedbackState)
|
||||||
{
|
{
|
||||||
if (mRuntimeHost)
|
return mRuntimeCoordinator &&
|
||||||
{
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RequestShaderReload(preserveFeedbackState));
|
||||||
mRuntimeHost->SetCompileStatus(true, "Shader rebuild queued.");
|
|
||||||
mRuntimeHost->ClearReloadRequest();
|
|
||||||
}
|
|
||||||
RequestShaderBuild();
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::RequestScreenshot(std::string& error)
|
bool OpenGLComposite::RequestScreenshot(std::string& error)
|
||||||
@@ -302,42 +374,209 @@ bool OpenGLComposite::RequestScreenshot(std::string& error)
|
|||||||
void OpenGLComposite::renderEffect()
|
void OpenGLComposite::renderEffect()
|
||||||
{
|
{
|
||||||
ProcessRuntimePollResults();
|
ProcessRuntimePollResults();
|
||||||
|
std::vector<RuntimeServices::AppliedOscUpdate> appliedOscUpdates;
|
||||||
const bool hasInputSource = mDeckLink->HasInputSource();
|
std::vector<RuntimeServices::CompletedOscCommit> completedOscCommits;
|
||||||
std::vector<RuntimeRenderState> layerStates;
|
if (mRuntimeHost && mRuntimeServices)
|
||||||
if (mUseCommittedLayerStates)
|
|
||||||
{
|
{
|
||||||
layerStates = mShaderPrograms->CommittedLayerStates();
|
std::string oscError;
|
||||||
if (mRuntimeHost)
|
if (!mRuntimeServices->ApplyPendingOscUpdates(appliedOscUpdates, oscError) && !oscError.empty())
|
||||||
mRuntimeHost->RefreshDynamicRenderStateFields(layerStates);
|
OutputDebugStringA(("OSC apply failed: " + oscError + "\n").c_str());
|
||||||
|
mRuntimeServices->ConsumeCompletedOscCommits(completedOscCommits);
|
||||||
}
|
}
|
||||||
else if (mRuntimeHost)
|
|
||||||
|
for (const RuntimeServices::CompletedOscCommit& completedCommit : completedOscCommits)
|
||||||
{
|
{
|
||||||
if (mRuntimeHost->TryGetLayerRenderStates(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), layerStates))
|
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)
|
||||||
{
|
{
|
||||||
mCachedLayerRenderStates = layerStates;
|
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
|
else
|
||||||
{
|
{
|
||||||
layerStates = mCachedLayerRenderStates;
|
overlayIt->second.targetValue = update.targetValue;
|
||||||
mRuntimeHost->RefreshDynamicRenderStateFields(layerStates);
|
overlayIt->second.lastUpdatedTime = oscNow;
|
||||||
|
overlayIt->second.generation += 1;
|
||||||
|
overlayIt->second.commitQueued = false;
|
||||||
}
|
}
|
||||||
|
pendingOscRouteKeys.insert(routeKey);
|
||||||
}
|
}
|
||||||
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
|
||||||
mRenderPass->Render(
|
const auto applyOscOverlays = [&](std::vector<RuntimeRenderState>& states, bool allowCommit)
|
||||||
|
{
|
||||||
|
if (states.empty() || mOscOverlayStates.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const double smoothing = ClampOscAlpha(mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0);
|
||||||
|
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 = mVideoBackend->HasInputSource();
|
||||||
|
std::vector<RuntimeRenderState> layerStates;
|
||||||
|
mRenderEngine->ResolveRenderLayerStates(
|
||||||
|
mUseCommittedLayerStates.load(),
|
||||||
|
mVideoBackend->InputFrameWidth(),
|
||||||
|
mVideoBackend->InputFrameHeight(),
|
||||||
|
applyOscOverlays,
|
||||||
|
layerStates);
|
||||||
|
const unsigned historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0;
|
||||||
|
mRenderEngine->RenderLayerStack(
|
||||||
hasInputSource,
|
hasInputSource,
|
||||||
layerStates,
|
layerStates,
|
||||||
mDeckLink->InputFrameWidth(),
|
mVideoBackend->InputFrameWidth(),
|
||||||
mDeckLink->InputFrameHeight(),
|
mVideoBackend->InputFrameHeight(),
|
||||||
mDeckLink->CaptureTextureWidth(),
|
mVideoBackend->CaptureTextureWidth(),
|
||||||
mDeckLink->InputPixelFormat(),
|
mVideoBackend->InputPixelFormat(),
|
||||||
historyCap,
|
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) {
|
|
||||||
return mShaderPrograms->UpdateGlobalParamsBuffer(state, availableSourceHistoryLength, availableTemporalHistoryLength);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLComposite::ProcessScreenshotRequest()
|
void OpenGLComposite::ProcessScreenshotRequest()
|
||||||
@@ -345,30 +584,14 @@ void OpenGLComposite::ProcessScreenshotRequest()
|
|||||||
if (!mScreenshotRequested.exchange(false))
|
if (!mScreenshotRequested.exchange(false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const unsigned width = mDeckLink ? mDeckLink->OutputFrameWidth() : 0;
|
const unsigned width = mVideoBackend ? mVideoBackend->OutputFrameWidth() : 0;
|
||||||
const unsigned height = mDeckLink ? mDeckLink->OutputFrameHeight() : 0;
|
const unsigned height = mVideoBackend ? mVideoBackend->OutputFrameHeight() : 0;
|
||||||
if (width == 0 || height == 0)
|
if (width == 0 || height == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::vector<unsigned char> bottomUpPixels(static_cast<std::size_t>(width) * height * 4);
|
std::vector<unsigned char> topDownPixels;
|
||||||
std::vector<unsigned char> topDownPixels(bottomUpPixels.size());
|
if (!mRenderEngine->CaptureOutputFrameRgbaTopDown(width, height, topDownPixels))
|
||||||
|
return;
|
||||||
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
|
try
|
||||||
{
|
{
|
||||||
@@ -384,8 +607,8 @@ void OpenGLComposite::ProcessScreenshotRequest()
|
|||||||
|
|
||||||
std::filesystem::path OpenGLComposite::BuildScreenshotPath() const
|
std::filesystem::path OpenGLComposite::BuildScreenshotPath() const
|
||||||
{
|
{
|
||||||
const std::filesystem::path root = mRuntimeHost && !mRuntimeHost->GetRuntimeRoot().empty()
|
const std::filesystem::path root = mRuntimeStore && !mRuntimeStore->GetRuntimeDataRoot().empty()
|
||||||
? mRuntimeHost->GetRuntimeRoot()
|
? mRuntimeStore->GetRuntimeDataRoot()
|
||||||
: std::filesystem::current_path();
|
: std::filesystem::current_path();
|
||||||
|
|
||||||
const auto now = std::chrono::system_clock::now();
|
const auto now = std::chrono::system_clock::now();
|
||||||
@@ -405,14 +628,13 @@ std::filesystem::path OpenGLComposite::BuildScreenshotPath() const
|
|||||||
|
|
||||||
bool OpenGLComposite::ProcessRuntimePollResults()
|
bool OpenGLComposite::ProcessRuntimePollResults()
|
||||||
{
|
{
|
||||||
if (!mRuntimeHost || !mRuntimeServices)
|
if (!mRuntimeServices)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
const RuntimePollEvents events = mRuntimeServices->ConsumePollEvents();
|
const RuntimePollEvents events = mRuntimeServices->ConsumePollEvents();
|
||||||
if (events.failed)
|
if (events.failed)
|
||||||
{
|
{
|
||||||
mRuntimeHost->SetCompileStatus(false, events.error);
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandleRuntimePollFailure(events.error));
|
||||||
broadcastRuntimeState();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,36 +648,98 @@ bool OpenGLComposite::ProcessRuntimePollResults()
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
char compilerErrorMessage[1024] = {};
|
char compilerErrorMessage[1024] = {};
|
||||||
if (!mShaderPrograms->CommitPreparedLayerPrograms(readyBuild, mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
|
if (!mRenderEngine->ApplyPreparedShaderBuild(
|
||||||
|
readyBuild,
|
||||||
|
mVideoBackend->InputFrameWidth(),
|
||||||
|
mVideoBackend->InputFrameHeight(),
|
||||||
|
mRuntimeCoordinator && mRuntimeCoordinator->PreserveFeedbackOnNextShaderBuild(),
|
||||||
|
sizeof(compilerErrorMessage),
|
||||||
|
compilerErrorMessage))
|
||||||
{
|
{
|
||||||
mRuntimeHost->SetCompileStatus(false, compilerErrorMessage);
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandlePreparedShaderBuildFailure(compilerErrorMessage));
|
||||||
mUseCommittedLayerStates = true;
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mUseCommittedLayerStates = false;
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandlePreparedShaderBuildSuccess());
|
||||||
mCachedLayerRenderStates = mShaderPrograms->CommittedLayerStates();
|
|
||||||
mShaderPrograms->ResetTemporalHistoryState();
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
mRuntimeHost->SetCompileStatus(true, "Shader rebuild queued.");
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandleRuntimeReloadRequest());
|
||||||
RequestShaderBuild();
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLComposite::RequestShaderBuild()
|
void OpenGLComposite::RequestShaderBuild()
|
||||||
{
|
{
|
||||||
if (!mShaderBuildQueue || !mDeckLink)
|
if (!mShaderBuildQueue || !mVideoBackend)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mUseCommittedLayerStates = true;
|
mShaderBuildQueue->RequestBuild(mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight());
|
||||||
if (mRuntimeHost)
|
}
|
||||||
mRuntimeHost->ClearReloadRequest();
|
|
||||||
mShaderBuildQueue->RequestBuild(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight());
|
bool OpenGLComposite::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error)
|
||||||
|
{
|
||||||
|
if (!result.accepted)
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
*error = result.errorMessage;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.compileStatusChanged && mRuntimeStore)
|
||||||
|
mRuntimeStore->SetCompileStatus(result.compileStatusSucceeded, result.compileStatusMessage);
|
||||||
|
|
||||||
|
if (result.clearReloadRequest && mRuntimeStore)
|
||||||
|
mRuntimeStore->ClearReloadRequest();
|
||||||
|
|
||||||
|
switch (result.committedStateMode)
|
||||||
|
{
|
||||||
|
case RuntimeCoordinatorCommittedStateMode::UseCommittedStates:
|
||||||
|
mUseCommittedLayerStates = true;
|
||||||
|
break;
|
||||||
|
case RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots:
|
||||||
|
mUseCommittedLayerStates = false;
|
||||||
|
break;
|
||||||
|
case RuntimeCoordinatorCommittedStateMode::Unchanged:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.clearTransientOscState)
|
||||||
|
{
|
||||||
|
mOscOverlayStates.clear();
|
||||||
|
if (mRuntimeServices)
|
||||||
|
mRuntimeServices->ClearOscState();
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyRuntimeCoordinatorRenderReset(result.renderResetScope);
|
||||||
|
|
||||||
|
if (result.shaderBuildRequested)
|
||||||
|
RequestShaderBuild();
|
||||||
|
|
||||||
|
if (result.runtimeStateBroadcastRequired)
|
||||||
|
broadcastRuntimeState();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLComposite::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope)
|
||||||
|
{
|
||||||
|
if (!mRenderEngine)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (resetScope)
|
||||||
|
{
|
||||||
|
case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly:
|
||||||
|
mRenderEngine->ResetTemporalHistoryState();
|
||||||
|
break;
|
||||||
|
case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback:
|
||||||
|
mRenderEngine->ResetTemporalHistoryState();
|
||||||
|
mRenderEngine->ResetShaderFeedbackState();
|
||||||
|
break;
|
||||||
|
case RuntimeCoordinatorRenderResetScope::None:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLComposite::broadcastRuntimeState()
|
void OpenGLComposite::broadcastRuntimeState()
|
||||||
@@ -464,11 +748,6 @@ void OpenGLComposite::broadcastRuntimeState()
|
|||||||
mRuntimeServices->BroadcastState();
|
mRuntimeServices->BroadcastState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLComposite::resetTemporalHistoryState()
|
|
||||||
{
|
|
||||||
mShaderPrograms->ResetTemporalHistoryState();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::CheckOpenGLExtensions()
|
bool OpenGLComposite::CheckOpenGLExtensions()
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -10,11 +10,12 @@
|
|||||||
#include <objbase.h>
|
#include <objbase.h>
|
||||||
#include <atlbase.h>
|
#include <atlbase.h>
|
||||||
#include <comutil.h>
|
#include <comutil.h>
|
||||||
#include "DeckLinkAPI_h.h"
|
|
||||||
|
|
||||||
#include "GLExtensions.h"
|
#include "GLExtensions.h"
|
||||||
#include "OpenGLRenderer.h"
|
#include "RuntimeCoordinator.h"
|
||||||
#include "RuntimeHost.h"
|
#include "RuntimeHost.h"
|
||||||
|
#include "RuntimeSnapshotProvider.h"
|
||||||
|
#include "RuntimeStore.h"
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -24,13 +25,12 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
class DeckLinkSession;
|
class RenderEngine;
|
||||||
class OpenGLDeckLinkBridge;
|
|
||||||
class OpenGLRenderPass;
|
|
||||||
class OpenGLShaderPrograms;
|
|
||||||
class RuntimeServices;
|
class RuntimeServices;
|
||||||
class ShaderBuildQueue;
|
class ShaderBuildQueue;
|
||||||
|
class VideoBackend;
|
||||||
|
|
||||||
|
|
||||||
class OpenGLComposite
|
class OpenGLComposite
|
||||||
@@ -40,9 +40,10 @@ public:
|
|||||||
~OpenGLComposite();
|
~OpenGLComposite();
|
||||||
|
|
||||||
bool InitDeckLink();
|
bool InitDeckLink();
|
||||||
|
bool InitVideoIO();
|
||||||
bool Start();
|
bool Start();
|
||||||
bool Stop();
|
bool Stop();
|
||||||
bool ReloadShader();
|
bool ReloadShader(bool preserveFeedbackState = false);
|
||||||
std::string GetRuntimeStateJson() const;
|
std::string GetRuntimeStateJson() const;
|
||||||
bool AddLayer(const std::string& shaderId, std::string& error);
|
bool AddLayer(const std::string& shaderId, std::string& error);
|
||||||
bool RemoveLayer(const std::string& layerId, std::string& error);
|
bool RemoveLayer(const std::string& layerId, std::string& error);
|
||||||
@@ -58,36 +59,46 @@ public:
|
|||||||
bool RequestScreenshot(std::string& error);
|
bool RequestScreenshot(std::string& error);
|
||||||
unsigned short GetControlServerPort() const;
|
unsigned short GetControlServerPort() const;
|
||||||
unsigned short GetOscPort() const;
|
unsigned short GetOscPort() const;
|
||||||
|
std::string GetOscBindAddress() const;
|
||||||
std::string GetControlUrl() const;
|
std::string GetControlUrl() const;
|
||||||
std::string GetDocsUrl() const;
|
std::string GetDocsUrl() const;
|
||||||
std::string GetOscAddress() const;
|
std::string GetOscAddress() const;
|
||||||
|
|
||||||
void resizeGL(WORD width, WORD height);
|
void resizeGL(WORD width, WORD height);
|
||||||
void paintGL();
|
void paintGL(bool force = false);
|
||||||
|
|
||||||
void VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource);
|
|
||||||
void PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void resizeWindow(int width, int height);
|
void resizeWindow(int width, int height);
|
||||||
bool CheckOpenGLExtensions();
|
bool CheckOpenGLExtensions();
|
||||||
void PublishDeckLinkOutputStatus(const std::string& statusMessage);
|
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;
|
CRITICAL_SECTION pMutex;
|
||||||
|
|
||||||
std::unique_ptr<DeckLinkSession> mDeckLink;
|
|
||||||
std::unique_ptr<OpenGLRenderer> mRenderer;
|
|
||||||
std::unique_ptr<RuntimeHost> mRuntimeHost;
|
std::unique_ptr<RuntimeHost> mRuntimeHost;
|
||||||
std::unique_ptr<OpenGLDeckLinkBridge> mDeckLinkBridge;
|
std::unique_ptr<RuntimeStore> mRuntimeStore;
|
||||||
std::unique_ptr<OpenGLRenderPass> mRenderPass;
|
std::unique_ptr<RuntimeCoordinator> mRuntimeCoordinator;
|
||||||
std::unique_ptr<OpenGLShaderPrograms> mShaderPrograms;
|
std::unique_ptr<RuntimeSnapshotProvider> mRuntimeSnapshotProvider;
|
||||||
|
std::unique_ptr<RenderEngine> mRenderEngine;
|
||||||
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<VideoBackend> mVideoBackend;
|
||||||
|
std::map<std::string, OscOverlayState> mOscOverlayStates;
|
||||||
std::atomic<bool> mUseCommittedLayerStates;
|
std::atomic<bool> mUseCommittedLayerStates;
|
||||||
std::atomic<bool> mScreenshotRequested;
|
std::atomic<bool> mScreenshotRequested;
|
||||||
|
|
||||||
@@ -95,10 +106,11 @@ private:
|
|||||||
void renderEffect();
|
void renderEffect();
|
||||||
bool ProcessRuntimePollResults();
|
bool ProcessRuntimePollResults();
|
||||||
void RequestShaderBuild();
|
void RequestShaderBuild();
|
||||||
|
bool ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error = nullptr);
|
||||||
|
void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope);
|
||||||
void ProcessScreenshotRequest();
|
void ProcessScreenshotRequest();
|
||||||
std::filesystem::path BuildScreenshotPath() const;
|
std::filesystem::path BuildScreenshotPath() const;
|
||||||
void broadcastRuntimeState();
|
void broadcastRuntimeState();
|
||||||
void resetTemporalHistoryState();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // __OPENGL_COMPOSITE_H__
|
#endif // __OPENGL_COMPOSITE_H__
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
#include "OpenGLComposite.h"
|
#include "OpenGLComposite.h"
|
||||||
|
#include "RuntimeServices.h"
|
||||||
|
|
||||||
std::string OpenGLComposite::GetRuntimeStateJson() const
|
std::string OpenGLComposite::GetRuntimeStateJson() const
|
||||||
{
|
{
|
||||||
return mRuntimeHost ? mRuntimeHost->BuildStateJson() : "{}";
|
return mRuntimeStore ? mRuntimeStore->BuildPersistentStateJson() : "{}";
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned short OpenGLComposite::GetControlServerPort() const
|
unsigned short OpenGLComposite::GetControlServerPort() const
|
||||||
{
|
{
|
||||||
return mRuntimeHost ? mRuntimeHost->GetServerPort() : 0;
|
return mRuntimeStore ? mRuntimeStore->GetConfiguredControlServerPort() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned short OpenGLComposite::GetOscPort() const
|
unsigned short OpenGLComposite::GetOscPort() const
|
||||||
{
|
{
|
||||||
return mRuntimeHost ? mRuntimeHost->GetOscPort() : 0;
|
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscPort() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OpenGLComposite::GetOscBindAddress() const
|
||||||
|
{
|
||||||
|
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscBindAddress() : "127.0.0.1";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string OpenGLComposite::GetControlUrl() const
|
std::string OpenGLComposite::GetControlUrl() const
|
||||||
@@ -27,67 +33,43 @@ std::string OpenGLComposite::GetDocsUrl() const
|
|||||||
|
|
||||||
std::string OpenGLComposite::GetOscAddress() const
|
std::string OpenGLComposite::GetOscAddress() const
|
||||||
{
|
{
|
||||||
return "udp://127.0.0.1:" + std::to_string(GetOscPort()) + " /VideoShaderToys/{Layer}/{Parameter}";
|
return "udp://" + GetOscBindAddress() + ":" + std::to_string(GetOscPort()) + " /VideoShaderToys/{Layer}/{Parameter}";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
|
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
|
||||||
{
|
{
|
||||||
if (!mRuntimeHost->AddLayer(shaderId, error))
|
return mRuntimeCoordinator &&
|
||||||
return false;
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->AddLayer(shaderId), &error);
|
||||||
|
|
||||||
ReloadShader();
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error)
|
bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error)
|
||||||
{
|
{
|
||||||
if (!mRuntimeHost->RemoveLayer(layerId, error))
|
return mRuntimeCoordinator &&
|
||||||
return false;
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RemoveLayer(layerId), &error);
|
||||||
|
|
||||||
ReloadShader();
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error)
|
bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error)
|
||||||
{
|
{
|
||||||
if (!mRuntimeHost->MoveLayer(layerId, direction, error))
|
return mRuntimeCoordinator &&
|
||||||
return false;
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayer(layerId, direction), &error);
|
||||||
|
|
||||||
ReloadShader();
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
||||||
{
|
{
|
||||||
if (!mRuntimeHost->MoveLayerToIndex(layerId, targetIndex, error))
|
return mRuntimeCoordinator &&
|
||||||
return false;
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayerToIndex(layerId, targetIndex), &error);
|
||||||
|
|
||||||
ReloadShader();
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
|
bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
|
||||||
{
|
{
|
||||||
if (!mRuntimeHost->SetLayerBypass(layerId, bypassed, error))
|
return mRuntimeCoordinator &&
|
||||||
return false;
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerBypass(layerId, bypassed), &error);
|
||||||
|
|
||||||
ReloadShader();
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
|
bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
|
||||||
{
|
{
|
||||||
if (!mRuntimeHost->SetLayerShader(layerId, shaderId, error))
|
return mRuntimeCoordinator &&
|
||||||
return false;
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerShader(layerId, shaderId), &error);
|
||||||
|
|
||||||
ReloadShader();
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error)
|
bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error)
|
||||||
@@ -96,11 +78,8 @@ bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const
|
|||||||
if (!ParseJson(valueJson, parsedValue, error))
|
if (!ParseJson(valueJson, parsedValue, error))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!mRuntimeHost->UpdateLayerParameter(layerId, parameterId, parsedValue, error))
|
return mRuntimeCoordinator &&
|
||||||
return false;
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameter(layerId, parameterId, parsedValue), &error);
|
||||||
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
|
bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
|
||||||
@@ -109,37 +88,24 @@ bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& la
|
|||||||
if (!ParseJson(valueJson, parsedValue, error))
|
if (!ParseJson(valueJson, parsedValue, error))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!mRuntimeHost->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue, error))
|
return mRuntimeCoordinator &&
|
||||||
return false;
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue), &error);
|
||||||
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error)
|
bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error)
|
||||||
{
|
{
|
||||||
if (!mRuntimeHost->ResetLayerParameters(layerId, error))
|
return mRuntimeCoordinator &&
|
||||||
return false;
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->ResetLayerParameters(layerId), &error);
|
||||||
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error)
|
bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error)
|
||||||
{
|
{
|
||||||
if (!mRuntimeHost->SaveStackPreset(presetName, error))
|
return mRuntimeCoordinator &&
|
||||||
return false;
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SaveStackPreset(presetName), &error);
|
||||||
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error)
|
bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error)
|
||||||
{
|
{
|
||||||
if (!mRuntimeHost->LoadStackPreset(presetName, error))
|
return mRuntimeCoordinator &&
|
||||||
return false;
|
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->LoadStackPreset(presetName), &error);
|
||||||
|
|
||||||
ReloadShader();
|
|
||||||
broadcastRuntimeState();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,209 +0,0 @@
|
|||||||
#include "OpenGLDeckLinkBridge.h"
|
|
||||||
|
|
||||||
#include "DeckLinkSession.h"
|
|
||||||
#include "OpenGLRenderer.h"
|
|
||||||
#include "RuntimeHost.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <gl/gl.h>
|
|
||||||
|
|
||||||
OpenGLDeckLinkBridge::OpenGLDeckLinkBridge(
|
|
||||||
DeckLinkSession& deckLink,
|
|
||||||
OpenGLRenderer& renderer,
|
|
||||||
RuntimeHost& runtimeHost,
|
|
||||||
CRITICAL_SECTION& mutex,
|
|
||||||
HDC hdc,
|
|
||||||
HGLRC hglrc,
|
|
||||||
RenderEffectCallback renderEffect,
|
|
||||||
OutputReadyCallback outputReady,
|
|
||||||
PaintCallback paint) :
|
|
||||||
mDeckLink(deckLink),
|
|
||||||
mRenderer(renderer),
|
|
||||||
mRuntimeHost(runtimeHost),
|
|
||||||
mMutex(mutex),
|
|
||||||
mHdc(hdc),
|
|
||||||
mHglrc(hglrc),
|
|
||||||
mRenderEffect(renderEffect),
|
|
||||||
mOutputReady(outputReady),
|
|
||||||
mPaint(paint)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLDeckLinkBridge::RecordFramePacing(BMDOutputFrameCompletionResult completionResult)
|
|
||||||
{
|
|
||||||
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 == bmdOutputFrameDisplayedLate)
|
|
||||||
++mLateFrameCount;
|
|
||||||
else if (completionResult == bmdOutputFrameDropped)
|
|
||||||
++mDroppedFrameCount;
|
|
||||||
else if (completionResult == bmdOutputFrameFlushed)
|
|
||||||
++mFlushedFrameCount;
|
|
||||||
|
|
||||||
mRuntimeHost.TrySetFramePacingStats(
|
|
||||||
mCompletionIntervalMilliseconds,
|
|
||||||
mSmoothedCompletionIntervalMilliseconds,
|
|
||||||
mMaxCompletionIntervalMilliseconds,
|
|
||||||
mLateFrameCount,
|
|
||||||
mDroppedFrameCount,
|
|
||||||
mFlushedFrameCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLDeckLinkBridge::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource)
|
|
||||||
{
|
|
||||||
mDeckLink.SetInputSourceMissing(hasNoInputSource);
|
|
||||||
mRuntimeHost.TrySetSignalStatus(!hasNoInputSource, mDeckLink.InputFrameWidth(), mDeckLink.InputFrameHeight(), mDeckLink.InputDisplayModeName());
|
|
||||||
|
|
||||||
if (!mDeckLink.HasInputSource())
|
|
||||||
return; // don't transfer texture when there's no input
|
|
||||||
|
|
||||||
long textureSize = inputFrame->GetRowBytes() * inputFrame->GetHeight();
|
|
||||||
IDeckLinkVideoBuffer* inputFrameBuffer = NULL;
|
|
||||||
void* videoPixels;
|
|
||||||
|
|
||||||
if (inputFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&inputFrameBuffer) != S_OK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (inputFrameBuffer->StartAccess(bmdBufferAccessRead) != S_OK)
|
|
||||||
{
|
|
||||||
inputFrameBuffer->Release();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
inputFrameBuffer->GetBytes(&videoPixels);
|
|
||||||
|
|
||||||
EnterCriticalSection(&mMutex);
|
|
||||||
|
|
||||||
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, videoPixels, 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 (mDeckLink.InputPixelFormat() == VideoIOPixelFormat::V210)
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDeckLink.CaptureTextureWidth(), mDeckLink.InputFrameHeight(), GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
||||||
else
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDeckLink.CaptureTextureWidth(), mDeckLink.InputFrameHeight(), 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);
|
|
||||||
|
|
||||||
inputFrameBuffer->EndAccess(bmdBufferAccessRead);
|
|
||||||
inputFrameBuffer->Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult)
|
|
||||||
{
|
|
||||||
(void)completedFrame;
|
|
||||||
|
|
||||||
RecordFramePacing(completionResult);
|
|
||||||
|
|
||||||
EnterCriticalSection(&mMutex);
|
|
||||||
|
|
||||||
// Get the first frame from the queue
|
|
||||||
IDeckLinkMutableVideoFrame* outputVideoFrame = mDeckLink.RotateOutputFrame();
|
|
||||||
|
|
||||||
// make GL context current in this thread
|
|
||||||
wglMakeCurrent(mHdc, mHglrc);
|
|
||||||
|
|
||||||
// Draw the effect output to the off-screen framebuffer.
|
|
||||||
const auto renderStartTime = std::chrono::steady_clock::now();
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
|
||||||
mRenderEffect();
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
|
||||||
glBlitFramebuffer(0, 0, mDeckLink.InputFrameWidth(), mDeckLink.InputFrameHeight(), 0, 0, mDeckLink.OutputFrameWidth(), mDeckLink.OutputFrameHeight(), GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
|
||||||
if (mOutputReady)
|
|
||||||
mOutputReady();
|
|
||||||
if (mDeckLink.OutputIsTenBit())
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
|
||||||
glViewport(0, 0, mDeckLink.OutputPackTextureWidth(), mDeckLink.OutputFrameHeight());
|
|
||||||
glDisable(GL_SCISSOR_TEST);
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, mRenderer.OutputTexture());
|
|
||||||
glBindVertexArray(mRenderer.FullscreenVertexArray());
|
|
||||||
glUseProgram(mRenderer.OutputPackProgram());
|
|
||||||
const GLint outputResolutionLocation = glGetUniformLocation(mRenderer.OutputPackProgram(), "uOutputVideoResolution");
|
|
||||||
const GLint activeWordsLocation = glGetUniformLocation(mRenderer.OutputPackProgram(), "uActiveV210Words");
|
|
||||||
if (outputResolutionLocation >= 0)
|
|
||||||
glUniform2f(outputResolutionLocation, static_cast<float>(mDeckLink.OutputFrameWidth()), static_cast<float>(mDeckLink.OutputFrameHeight()));
|
|
||||||
if (activeWordsLocation >= 0)
|
|
||||||
glUniform1f(activeWordsLocation, static_cast<float>(ActiveV210WordsForWidth(mDeckLink.OutputFrameWidth())));
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
|
||||||
glUseProgram(0);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
glFlush();
|
|
||||||
const auto renderEndTime = std::chrono::steady_clock::now();
|
|
||||||
const double frameBudgetMilliseconds = mDeckLink.FrameBudgetMilliseconds();
|
|
||||||
const double renderMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(renderEndTime - renderStartTime).count();
|
|
||||||
mRuntimeHost.TrySetPerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
|
|
||||||
mRuntimeHost.TryAdvanceFrame();
|
|
||||||
|
|
||||||
IDeckLinkVideoBuffer* outputVideoFrameBuffer;
|
|
||||||
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
|
|
||||||
{
|
|
||||||
wglMakeCurrent(NULL, NULL);
|
|
||||||
LeaveCriticalSection(&mMutex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
|
|
||||||
{
|
|
||||||
outputVideoFrameBuffer->Release();
|
|
||||||
wglMakeCurrent(NULL, NULL);
|
|
||||||
LeaveCriticalSection(&mMutex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* pFrame;
|
|
||||||
outputVideoFrameBuffer->GetBytes(&pFrame);
|
|
||||||
|
|
||||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
|
||||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
|
||||||
if (mDeckLink.OutputIsTenBit())
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
|
||||||
glReadPixels(0, 0, mDeckLink.OutputPackTextureWidth(), mDeckLink.OutputFrameHeight(), GL_RGBA, GL_UNSIGNED_BYTE, pFrame);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
|
||||||
glReadPixels(0, 0, mDeckLink.OutputFrameWidth(), mDeckLink.OutputFrameHeight(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pFrame);
|
|
||||||
}
|
|
||||||
mPaint();
|
|
||||||
|
|
||||||
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
|
|
||||||
outputVideoFrameBuffer->Release();
|
|
||||||
|
|
||||||
mDeckLink.AccountForCompletionResult(completionResult);
|
|
||||||
|
|
||||||
// Schedule the next frame for playout
|
|
||||||
mDeckLink.ScheduleOutputFrame(outputVideoFrame);
|
|
||||||
|
|
||||||
wglMakeCurrent(NULL, NULL);
|
|
||||||
|
|
||||||
LeaveCriticalSection(&mMutex);
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "DeckLinkAPI_h.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <functional>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
class DeckLinkSession;
|
|
||||||
class OpenGLRenderer;
|
|
||||||
class RuntimeHost;
|
|
||||||
|
|
||||||
class OpenGLDeckLinkBridge
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using RenderEffectCallback = std::function<void()>;
|
|
||||||
using OutputReadyCallback = std::function<void()>;
|
|
||||||
using PaintCallback = std::function<void()>;
|
|
||||||
|
|
||||||
OpenGLDeckLinkBridge(
|
|
||||||
DeckLinkSession& deckLink,
|
|
||||||
OpenGLRenderer& renderer,
|
|
||||||
RuntimeHost& runtimeHost,
|
|
||||||
CRITICAL_SECTION& mutex,
|
|
||||||
HDC hdc,
|
|
||||||
HGLRC hglrc,
|
|
||||||
RenderEffectCallback renderEffect,
|
|
||||||
OutputReadyCallback outputReady,
|
|
||||||
PaintCallback paint);
|
|
||||||
|
|
||||||
void VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource);
|
|
||||||
void PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void RecordFramePacing(BMDOutputFrameCompletionResult completionResult);
|
|
||||||
|
|
||||||
DeckLinkSession& mDeckLink;
|
|
||||||
OpenGLRenderer& mRenderer;
|
|
||||||
RuntimeHost& mRuntimeHost;
|
|
||||||
CRITICAL_SECTION& mMutex;
|
|
||||||
HDC mHdc;
|
|
||||||
HGLRC mHglrc;
|
|
||||||
RenderEffectCallback mRenderEffect;
|
|
||||||
OutputReadyCallback mOutputReady;
|
|
||||||
PaintCallback mPaint;
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
#include "OpenGLRenderPass.h"
|
|
||||||
|
|
||||||
#include "GlRenderConstants.h"
|
|
||||||
|
|
||||||
OpenGLRenderPass::OpenGLRenderPass(OpenGLRenderer& renderer) :
|
|
||||||
mRenderer(renderer)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPass::Render(
|
|
||||||
bool hasInputSource,
|
|
||||||
const std::vector<RuntimeRenderState>& layerStates,
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
unsigned captureTextureWidth,
|
|
||||||
VideoIOPixelFormat inputPixelFormat,
|
|
||||||
unsigned historyCap,
|
|
||||||
const TextBindingUpdater& updateTextBinding,
|
|
||||||
const GlobalParamsUpdater& updateGlobalParams)
|
|
||||||
{
|
|
||||||
glDisable(GL_SCISSOR_TEST);
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
if (hasInputSource)
|
|
||||||
{
|
|
||||||
RenderDecodePass(inputFrameWidth, inputFrameHeight, captureTextureWidth, inputPixelFormat);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
|
|
||||||
glViewport(0, 0, inputFrameWidth, inputFrameHeight);
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<LayerProgram>& layerPrograms = mRenderer.LayerPrograms();
|
|
||||||
if (layerStates.empty() || layerPrograms.empty())
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
|
||||||
glBlitFramebuffer(0, 0, inputFrameWidth, inputFrameHeight, 0, 0, inputFrameWidth, inputFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GLuint sourceTexture = mRenderer.DecodedTexture();
|
|
||||||
GLuint sourceFrameBuffer = mRenderer.DecodeFramebuffer();
|
|
||||||
for (std::size_t index = 0; index < layerStates.size() && index < layerPrograms.size(); ++index)
|
|
||||||
{
|
|
||||||
const std::size_t remaining = layerStates.size() - index;
|
|
||||||
const bool writeToMain = (remaining % 2) == 1;
|
|
||||||
RenderShaderProgram(
|
|
||||||
sourceTexture,
|
|
||||||
writeToMain ? mRenderer.CompositeFramebuffer() : mRenderer.LayerTempFramebuffer(),
|
|
||||||
layerPrograms[index],
|
|
||||||
layerStates[index],
|
|
||||||
inputFrameWidth,
|
|
||||||
inputFrameHeight,
|
|
||||||
historyCap,
|
|
||||||
updateTextBinding,
|
|
||||||
updateGlobalParams);
|
|
||||||
if (layerStates[index].temporalHistorySource == TemporalHistorySource::PreLayerInput)
|
|
||||||
mRenderer.TemporalHistory().PushPreLayerFramebuffer(layerStates[index].layerId, sourceFrameBuffer, inputFrameWidth, inputFrameHeight);
|
|
||||||
sourceTexture = writeToMain ? mRenderer.CompositeTexture() : mRenderer.LayerTempTexture();
|
|
||||||
sourceFrameBuffer = writeToMain ? mRenderer.CompositeFramebuffer() : mRenderer.LayerTempFramebuffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mRenderer.TemporalHistory().PushSourceFramebuffer(mRenderer.DecodeFramebuffer(), inputFrameWidth, inputFrameHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPass::RenderDecodePass(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat)
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
|
|
||||||
glViewport(0, 0, inputFrameWidth, inputFrameHeight);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
glActiveTexture(GL_TEXTURE0 + kPackedVideoTextureUnit);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
|
|
||||||
glBindVertexArray(mRenderer.FullscreenVertexArray());
|
|
||||||
glUseProgram(mRenderer.DecodeProgram());
|
|
||||||
|
|
||||||
const GLint packedResolutionLocation = glGetUniformLocation(mRenderer.DecodeProgram(), "uPackedVideoResolution");
|
|
||||||
const GLint decodedResolutionLocation = glGetUniformLocation(mRenderer.DecodeProgram(), "uDecodedVideoResolution");
|
|
||||||
const GLint inputPixelFormatLocation = glGetUniformLocation(mRenderer.DecodeProgram(), "uInputPixelFormat");
|
|
||||||
if (packedResolutionLocation >= 0)
|
|
||||||
glUniform2f(packedResolutionLocation, static_cast<float>(captureTextureWidth), static_cast<float>(inputFrameHeight));
|
|
||||||
if (decodedResolutionLocation >= 0)
|
|
||||||
glUniform2f(decodedResolutionLocation, static_cast<float>(inputFrameWidth), static_cast<float>(inputFrameHeight));
|
|
||||||
if (inputPixelFormatLocation >= 0)
|
|
||||||
glUniform1i(inputPixelFormatLocation, inputPixelFormat == VideoIOPixelFormat::V210 ? 1 : 0);
|
|
||||||
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
|
||||||
|
|
||||||
glUseProgram(0);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPass::RenderShaderProgram(
|
|
||||||
GLuint sourceTexture,
|
|
||||||
GLuint destinationFrameBuffer,
|
|
||||||
LayerProgram& layerProgram,
|
|
||||||
const RuntimeRenderState& state,
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
unsigned historyCap,
|
|
||||||
const TextBindingUpdater& updateTextBinding,
|
|
||||||
const GlobalParamsUpdater& updateGlobalParams)
|
|
||||||
{
|
|
||||||
for (LayerProgram::TextBinding& textBinding : layerProgram.textBindings)
|
|
||||||
{
|
|
||||||
std::string textError;
|
|
||||||
if (!updateTextBinding(state, textBinding, textError))
|
|
||||||
OutputDebugStringA((textError + "\n").c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, destinationFrameBuffer);
|
|
||||||
glViewport(0, 0, inputFrameWidth, inputFrameHeight);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
||||||
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, sourceTexture);
|
|
||||||
mRenderer.TemporalHistory().BindSamplers(state, sourceTexture, historyCap);
|
|
||||||
BindLayerTextureAssets(layerProgram);
|
|
||||||
glBindVertexArray(mRenderer.FullscreenVertexArray());
|
|
||||||
glUseProgram(layerProgram.program);
|
|
||||||
updateGlobalParams(state, mRenderer.TemporalHistory().SourceAvailableCount(), mRenderer.TemporalHistory().AvailableCountForLayer(state.layerId));
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
|
||||||
glUseProgram(0);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
UnbindLayerTextureAssets(layerProgram, historyCap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPass::BindLayerTextureAssets(const LayerProgram& layerProgram)
|
|
||||||
{
|
|
||||||
const GLuint shaderTextureBase = layerProgram.shaderTextureBase != 0 ? layerProgram.shaderTextureBase : kSourceHistoryTextureUnitBase;
|
|
||||||
for (std::size_t index = 0; index < layerProgram.textureBindings.size(); ++index)
|
|
||||||
{
|
|
||||||
glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast<GLuint>(index));
|
|
||||||
glBindTexture(GL_TEXTURE_2D, layerProgram.textureBindings[index].texture);
|
|
||||||
}
|
|
||||||
const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(layerProgram.textureBindings.size());
|
|
||||||
for (std::size_t index = 0; index < layerProgram.textBindings.size(); ++index)
|
|
||||||
{
|
|
||||||
glActiveTexture(GL_TEXTURE0 + textTextureBase + static_cast<GLuint>(index));
|
|
||||||
glBindTexture(GL_TEXTURE_2D, layerProgram.textBindings[index].texture);
|
|
||||||
}
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPass::UnbindLayerTextureAssets(const LayerProgram& layerProgram, unsigned historyCap)
|
|
||||||
{
|
|
||||||
for (unsigned index = 0; index < historyCap; ++index)
|
|
||||||
{
|
|
||||||
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + index);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + historyCap + index);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
const GLuint shaderTextureBase = layerProgram.shaderTextureBase != 0 ? layerProgram.shaderTextureBase : kSourceHistoryTextureUnitBase;
|
|
||||||
for (std::size_t index = 0; index < layerProgram.textureBindings.size() + layerProgram.textBindings.size(); ++index)
|
|
||||||
{
|
|
||||||
glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast<GLuint>(index));
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
}
|
|
||||||
311
apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp
Normal file
311
apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
#include "RenderEngine.h"
|
||||||
|
|
||||||
|
#include <gl/gl.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
RenderEngine::RenderEngine(
|
||||||
|
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
||||||
|
HealthTelemetry& healthTelemetry,
|
||||||
|
CRITICAL_SECTION& mutex,
|
||||||
|
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),
|
||||||
|
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
||||||
|
mMutex(mutex),
|
||||||
|
mHdc(hdc),
|
||||||
|
mHglrc(hglrc)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderEngine::~RenderEngine()
|
||||||
|
{
|
||||||
|
mRenderer.DestroyResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderEngine::CompileDecodeShader(int errorMessageSize, char* errorMessage)
|
||||||
|
{
|
||||||
|
return mShaderPrograms.CompileDecodeShader(errorMessageSize, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderEngine::CompileOutputPackShader(int errorMessageSize, char* 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 mRenderer.InitializeResources(
|
||||||
|
inputFrameWidth,
|
||||||
|
inputFrameHeight,
|
||||||
|
captureTextureWidth,
|
||||||
|
outputFrameWidth,
|
||||||
|
outputFrameHeight,
|
||||||
|
outputPackTextureWidth,
|
||||||
|
error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderEngine::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
||||||
|
{
|
||||||
|
return mShaderPrograms.CompileLayerPrograms(inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderEngine::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* 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;
|
||||||
|
|
||||||
|
mCachedLayerRenderStates = mShaderPrograms.CommittedLayerStates();
|
||||||
|
mCachedRenderStateVersion = preparedBuild.renderSnapshot.versions.renderStateVersion;
|
||||||
|
mCachedParameterStateVersion = preparedBuild.renderSnapshot.versions.parameterStateVersion;
|
||||||
|
mCachedRenderStateWidth = preparedBuild.renderSnapshot.outputWidth;
|
||||||
|
mCachedRenderStateHeight = preparedBuild.renderSnapshot.outputHeight;
|
||||||
|
ResetTemporalHistoryState();
|
||||||
|
if (!preserveFeedbackState)
|
||||||
|
ResetShaderFeedbackState();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<RuntimeRenderState>& RenderEngine::CommittedLayerStates() const
|
||||||
|
{
|
||||||
|
return mShaderPrograms.CommittedLayerStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::ResetTemporalHistoryState()
|
||||||
|
{
|
||||||
|
mShaderPrograms.ResetTemporalHistoryState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::ResetShaderFeedbackState()
|
||||||
|
{
|
||||||
|
mShaderPrograms.ResetShaderFeedbackState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::ResizeView(int width, int 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 (!TryEnterCriticalSection(&mMutex))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
mRenderer.PresentToWindow(mHdc, outputFrameWidth, outputFrameHeight);
|
||||||
|
mLastPreviewPresentTime = std::chrono::steady_clock::now();
|
||||||
|
LeaveCriticalSection(&mMutex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderEngine::TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
|
||||||
|
{
|
||||||
|
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height);
|
||||||
|
if (!TryEnterCriticalSection(&mMutex))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
wglMakeCurrent(mHdc, mHglrc);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
wglMakeCurrent(NULL, NULL);
|
||||||
|
LeaveCriticalSection(&mMutex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderEngine::RenderOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
|
||||||
|
{
|
||||||
|
EnterCriticalSection(&mMutex);
|
||||||
|
wglMakeCurrent(mHdc, mHglrc);
|
||||||
|
const bool rendered = mRenderPipeline.RenderFrame(context, outputFrame);
|
||||||
|
wglMakeCurrent(NULL, NULL);
|
||||||
|
LeaveCriticalSection(&mMutex);
|
||||||
|
return rendered;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderEngine::ResolveRenderLayerStates(
|
||||||
|
bool useCommittedLayerStates,
|
||||||
|
unsigned renderWidth,
|
||||||
|
unsigned renderHeight,
|
||||||
|
OverlayApplier overlayApplier,
|
||||||
|
std::vector<RuntimeRenderState>& layerStates)
|
||||||
|
{
|
||||||
|
layerStates.clear();
|
||||||
|
if (useCommittedLayerStates)
|
||||||
|
{
|
||||||
|
layerStates = mShaderPrograms.CommittedLayerStates();
|
||||||
|
if (overlayApplier)
|
||||||
|
overlayApplier(layerStates, false);
|
||||||
|
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider.GetVersions();
|
||||||
|
const bool renderStateCacheValid =
|
||||||
|
!mCachedLayerRenderStates.empty() &&
|
||||||
|
mCachedRenderStateVersion == versions.renderStateVersion &&
|
||||||
|
mCachedRenderStateWidth == renderWidth &&
|
||||||
|
mCachedRenderStateHeight == renderHeight;
|
||||||
|
|
||||||
|
if (renderStateCacheValid)
|
||||||
|
{
|
||||||
|
RuntimeRenderStateSnapshot renderSnapshot;
|
||||||
|
renderSnapshot.outputWidth = renderWidth;
|
||||||
|
renderSnapshot.outputHeight = renderHeight;
|
||||||
|
renderSnapshot.versions.renderStateVersion = mCachedRenderStateVersion;
|
||||||
|
renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion;
|
||||||
|
renderSnapshot.states = mCachedLayerRenderStates;
|
||||||
|
|
||||||
|
if (overlayApplier)
|
||||||
|
overlayApplier(renderSnapshot.states, true);
|
||||||
|
if (mCachedParameterStateVersion != versions.parameterStateVersion &&
|
||||||
|
mRuntimeSnapshotProvider.TryRefreshSnapshotParameters(renderSnapshot))
|
||||||
|
{
|
||||||
|
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
|
||||||
|
if (overlayApplier)
|
||||||
|
overlayApplier(renderSnapshot.states, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
mCachedLayerRenderStates = renderSnapshot.states;
|
||||||
|
layerStates = renderSnapshot.states;
|
||||||
|
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeRenderStateSnapshot renderSnapshot;
|
||||||
|
if (mRuntimeSnapshotProvider.TryGetRenderStateSnapshot(renderWidth, renderHeight, renderSnapshot))
|
||||||
|
{
|
||||||
|
mCachedLayerRenderStates = renderSnapshot.states;
|
||||||
|
mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion;
|
||||||
|
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
|
||||||
|
mCachedRenderStateWidth = renderSnapshot.outputWidth;
|
||||||
|
mCachedRenderStateHeight = renderSnapshot.outputHeight;
|
||||||
|
if (overlayApplier)
|
||||||
|
overlayApplier(mCachedLayerRenderStates, true);
|
||||||
|
layerStates = mCachedLayerRenderStates;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlayApplier)
|
||||||
|
overlayApplier(mCachedLayerRenderStates, true);
|
||||||
|
layerStates = mCachedLayerRenderStates;
|
||||||
|
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates);
|
||||||
|
return !layerStates.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::RenderLayerStack(
|
||||||
|
bool hasInputSource,
|
||||||
|
const std::vector<RuntimeRenderState>& layerStates,
|
||||||
|
unsigned inputFrameWidth,
|
||||||
|
unsigned inputFrameHeight,
|
||||||
|
unsigned captureTextureWidth,
|
||||||
|
VideoIOPixelFormat inputPixelFormat,
|
||||||
|
unsigned historyCap)
|
||||||
|
{
|
||||||
|
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::ReadOutputFrameRgba(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels)
|
||||||
|
{
|
||||||
|
if (width == 0 || height == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
EnterCriticalSection(&mMutex);
|
||||||
|
wglMakeCurrent(mHdc, mHglrc);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
wglMakeCurrent(NULL, NULL);
|
||||||
|
LeaveCriticalSection(&mMutex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderEngine::CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels)
|
||||||
|
{
|
||||||
|
std::vector<unsigned char> bottomUpPixels;
|
||||||
|
if (!ReadOutputFrameRgba(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;
|
||||||
|
}
|
||||||
96
apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h
Normal file
96
apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "OpenGLRenderPass.h"
|
||||||
|
#include "OpenGLRenderPipeline.h"
|
||||||
|
#include "OpenGLRenderer.h"
|
||||||
|
#include "OpenGLShaderPrograms.h"
|
||||||
|
#include "HealthTelemetry.h"
|
||||||
|
#include "RuntimeSnapshotProvider.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <chrono>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class RenderEngine
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using RenderEffectCallback = std::function<void()>;
|
||||||
|
using ScreenshotCallback = std::function<void()>;
|
||||||
|
using PreviewPaintCallback = std::function<void()>;
|
||||||
|
using OverlayApplier = std::function<void(std::vector<RuntimeRenderState>& states, bool allowCommit)>;
|
||||||
|
|
||||||
|
RenderEngine(
|
||||||
|
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
||||||
|
HealthTelemetry& healthTelemetry,
|
||||||
|
CRITICAL_SECTION& mutex,
|
||||||
|
HDC hdc,
|
||||||
|
HGLRC hglrc,
|
||||||
|
RenderEffectCallback renderEffect,
|
||||||
|
ScreenshotCallback screenshotReady,
|
||||||
|
PreviewPaintCallback previewPaint);
|
||||||
|
~RenderEngine();
|
||||||
|
|
||||||
|
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 CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
|
||||||
|
bool ApplyPreparedShaderBuild(
|
||||||
|
const PreparedShaderBuild& preparedBuild,
|
||||||
|
unsigned inputFrameWidth,
|
||||||
|
unsigned inputFrameHeight,
|
||||||
|
bool preserveFeedbackState,
|
||||||
|
int errorMessageSize,
|
||||||
|
char* errorMessage);
|
||||||
|
|
||||||
|
const std::vector<RuntimeRenderState>& CommittedLayerStates() const;
|
||||||
|
void ResetTemporalHistoryState();
|
||||||
|
void ResetShaderFeedbackState();
|
||||||
|
void ResizeView(int width, int height);
|
||||||
|
bool TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight);
|
||||||
|
bool TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
|
||||||
|
bool RenderOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
|
||||||
|
bool ResolveRenderLayerStates(
|
||||||
|
bool useCommittedLayerStates,
|
||||||
|
unsigned renderWidth,
|
||||||
|
unsigned renderHeight,
|
||||||
|
OverlayApplier overlayApplier,
|
||||||
|
std::vector<RuntimeRenderState>& layerStates);
|
||||||
|
void RenderLayerStack(
|
||||||
|
bool hasInputSource,
|
||||||
|
const std::vector<RuntimeRenderState>& layerStates,
|
||||||
|
unsigned inputFrameWidth,
|
||||||
|
unsigned inputFrameHeight,
|
||||||
|
unsigned captureTextureWidth,
|
||||||
|
VideoIOPixelFormat inputPixelFormat,
|
||||||
|
unsigned historyCap);
|
||||||
|
bool ReadOutputFrameRgba(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels);
|
||||||
|
bool CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels);
|
||||||
|
|
||||||
|
private:
|
||||||
|
OpenGLRenderer mRenderer;
|
||||||
|
OpenGLRenderPass mRenderPass;
|
||||||
|
OpenGLRenderPipeline mRenderPipeline;
|
||||||
|
OpenGLShaderPrograms mShaderPrograms;
|
||||||
|
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||||
|
CRITICAL_SECTION& mMutex;
|
||||||
|
HDC mHdc;
|
||||||
|
HGLRC mHglrc;
|
||||||
|
std::vector<RuntimeRenderState> mCachedLayerRenderStates;
|
||||||
|
uint64_t mCachedRenderStateVersion = 0;
|
||||||
|
uint64_t mCachedParameterStateVersion = 0;
|
||||||
|
unsigned mCachedRenderStateWidth = 0;
|
||||||
|
unsigned mCachedRenderStateHeight = 0;
|
||||||
|
std::chrono::steady_clock::time_point mLastPreviewPresentTime;
|
||||||
|
};
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
#include "ShaderProgramCompiler.h"
|
|
||||||
|
|
||||||
#include "GlRenderConstants.h"
|
|
||||||
#include "GlScopedObjects.h"
|
|
||||||
#include "GlShaderSources.h"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
if (!errorMessage || errorMessageSize <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderProgramCompiler::ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, ShaderTextureBindings& textureBindings) :
|
|
||||||
mRenderer(renderer),
|
|
||||||
mRuntimeHost(runtimeHost),
|
|
||||||
mTextureBindings(textureBindings)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderProgramCompiler::CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
std::string fragmentShaderSource;
|
|
||||||
std::string loadError;
|
|
||||||
|
|
||||||
if (!mRuntimeHost.BuildLayerFragmentShaderSource(state.layerId, fragmentShaderSource, loadError))
|
|
||||||
{
|
|
||||||
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CompilePreparedLayerProgram(state, fragmentShaderSource, layerProgram, errorMessageSize, errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::string& fragmentShaderSource, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
GLsizei errorBufferSize = 0;
|
|
||||||
GLint compileResult = GL_FALSE;
|
|
||||||
GLint linkResult = GL_FALSE;
|
|
||||||
std::string loadError;
|
|
||||||
std::vector<LayerProgram::TextureBinding> textureBindings;
|
|
||||||
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
|
|
||||||
const char* fragmentSource = fragmentShaderSource.c_str();
|
|
||||||
|
|
||||||
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
|
|
||||||
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
|
|
||||||
glCompileShader(newVertexShader.get());
|
|
||||||
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
|
|
||||||
if (compileResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
|
|
||||||
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
|
|
||||||
glCompileShader(newFragmentShader.get());
|
|
||||||
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
|
|
||||||
if (compileResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedGlProgram newProgram(glCreateProgram());
|
|
||||||
glAttachShader(newProgram.get(), newVertexShader.get());
|
|
||||||
glAttachShader(newProgram.get(), newFragmentShader.get());
|
|
||||||
glLinkProgram(newProgram.get());
|
|
||||||
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
|
|
||||||
if (linkResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const ShaderTextureAsset& textureAsset : state.textureAssets)
|
|
||||||
{
|
|
||||||
LayerProgram::TextureBinding textureBinding;
|
|
||||||
textureBinding.samplerName = textureAsset.id;
|
|
||||||
textureBinding.sourcePath = textureAsset.path;
|
|
||||||
if (!mTextureBindings.LoadTextureAsset(textureAsset, textureBinding.texture, loadError))
|
|
||||||
{
|
|
||||||
for (LayerProgram::TextureBinding& loadedTexture : textureBindings)
|
|
||||||
{
|
|
||||||
if (loadedTexture.texture != 0)
|
|
||||||
glDeleteTextures(1, &loadedTexture.texture);
|
|
||||||
}
|
|
||||||
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
textureBindings.push_back(textureBinding);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<LayerProgram::TextBinding> textBindings;
|
|
||||||
mTextureBindings.CreateTextBindings(state, textBindings);
|
|
||||||
|
|
||||||
const GLuint globalParamsIndex = glGetUniformBlockIndex(newProgram.get(), "GlobalParams");
|
|
||||||
if (globalParamsIndex != GL_INVALID_INDEX)
|
|
||||||
glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint);
|
|
||||||
|
|
||||||
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames();
|
|
||||||
const GLuint shaderTextureBase = state.isTemporal ? kSourceHistoryTextureUnitBase + historyCap + historyCap : kSourceHistoryTextureUnitBase;
|
|
||||||
glUseProgram(newProgram.get());
|
|
||||||
const GLint videoInputLocation = glGetUniformLocation(newProgram.get(), "gVideoInput");
|
|
||||||
if (videoInputLocation >= 0)
|
|
||||||
glUniform1i(videoInputLocation, static_cast<GLint>(kDecodedVideoTextureUnit));
|
|
||||||
for (unsigned index = 0; index < historyCap; ++index)
|
|
||||||
{
|
|
||||||
const std::string sourceSamplerName = "gSourceHistory" + std::to_string(index);
|
|
||||||
const GLint sourceSamplerLocation = glGetUniformLocation(newProgram.get(), sourceSamplerName.c_str());
|
|
||||||
if (sourceSamplerLocation >= 0)
|
|
||||||
glUniform1i(sourceSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + index));
|
|
||||||
|
|
||||||
const std::string temporalSamplerName = "gTemporalHistory" + std::to_string(index);
|
|
||||||
const GLint temporalSamplerLocation = glGetUniformLocation(newProgram.get(), temporalSamplerName.c_str());
|
|
||||||
if (temporalSamplerLocation >= 0)
|
|
||||||
glUniform1i(temporalSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + historyCap + index));
|
|
||||||
}
|
|
||||||
for (std::size_t index = 0; index < textureBindings.size(); ++index)
|
|
||||||
{
|
|
||||||
const GLint textureSamplerLocation = mTextureBindings.FindSamplerUniformLocation(newProgram.get(), textureBindings[index].samplerName);
|
|
||||||
if (textureSamplerLocation >= 0)
|
|
||||||
glUniform1i(textureSamplerLocation, static_cast<GLint>(shaderTextureBase + static_cast<GLuint>(index)));
|
|
||||||
}
|
|
||||||
const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(textureBindings.size());
|
|
||||||
for (std::size_t index = 0; index < textBindings.size(); ++index)
|
|
||||||
{
|
|
||||||
const GLint textSamplerLocation = mTextureBindings.FindSamplerUniformLocation(newProgram.get(), textBindings[index].samplerName);
|
|
||||||
if (textSamplerLocation >= 0)
|
|
||||||
glUniform1i(textSamplerLocation, static_cast<GLint>(textTextureBase + static_cast<GLuint>(index)));
|
|
||||||
}
|
|
||||||
glUseProgram(0);
|
|
||||||
|
|
||||||
layerProgram.layerId = state.layerId;
|
|
||||||
layerProgram.shaderId = state.shaderId;
|
|
||||||
layerProgram.shaderTextureBase = shaderTextureBase;
|
|
||||||
layerProgram.program = newProgram.release();
|
|
||||||
layerProgram.vertexShader = newVertexShader.release();
|
|
||||||
layerProgram.fragmentShader = newFragmentShader.release();
|
|
||||||
layerProgram.textureBindings.swap(textureBindings);
|
|
||||||
layerProgram.textBindings.swap(textBindings);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderProgramCompiler::CompileDecodeShader(int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
GLsizei errorBufferSize = 0;
|
|
||||||
GLint compileResult = GL_FALSE;
|
|
||||||
GLint linkResult = GL_FALSE;
|
|
||||||
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
|
|
||||||
const char* fragmentSource = kDecodeFragmentShaderSource;
|
|
||||||
|
|
||||||
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
|
|
||||||
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
|
|
||||||
glCompileShader(newVertexShader.get());
|
|
||||||
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
|
|
||||||
if (compileResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
|
|
||||||
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
|
|
||||||
glCompileShader(newFragmentShader.get());
|
|
||||||
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
|
|
||||||
if (compileResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedGlProgram newProgram(glCreateProgram());
|
|
||||||
glAttachShader(newProgram.get(), newVertexShader.get());
|
|
||||||
glAttachShader(newProgram.get(), newFragmentShader.get());
|
|
||||||
glLinkProgram(newProgram.get());
|
|
||||||
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
|
|
||||||
if (linkResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mRenderer.DestroyDecodeShaderProgram();
|
|
||||||
mRenderer.SetDecodeShaderProgram(newProgram.release(), newVertexShader.release(), newFragmentShader.release());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderProgramCompiler::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
GLsizei errorBufferSize = 0;
|
|
||||||
GLint compileResult = GL_FALSE;
|
|
||||||
GLint linkResult = GL_FALSE;
|
|
||||||
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
|
|
||||||
const char* fragmentSource = kOutputPackFragmentShaderSource;
|
|
||||||
|
|
||||||
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
|
|
||||||
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
|
|
||||||
glCompileShader(newVertexShader.get());
|
|
||||||
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
|
|
||||||
if (compileResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
|
|
||||||
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
|
|
||||||
glCompileShader(newFragmentShader.get());
|
|
||||||
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
|
|
||||||
if (compileResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedGlProgram newProgram(glCreateProgram());
|
|
||||||
glAttachShader(newProgram.get(), newVertexShader.get());
|
|
||||||
glAttachShader(newProgram.get(), newFragmentShader.get());
|
|
||||||
glLinkProgram(newProgram.get());
|
|
||||||
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
|
|
||||||
if (linkResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
glUseProgram(newProgram.get());
|
|
||||||
const GLint outputSamplerLocation = glGetUniformLocation(newProgram.get(), "uOutputRgb");
|
|
||||||
if (outputSamplerLocation >= 0)
|
|
||||||
glUniform1i(outputSamplerLocation, 0);
|
|
||||||
glUseProgram(0);
|
|
||||||
|
|
||||||
mRenderer.DestroyOutputPackShaderProgram();
|
|
||||||
mRenderer.SetOutputPackShaderProgram(newProgram.release(), newVertexShader.release(), newFragmentShader.release());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
#include "ShaderTextureBindings.h"
|
|
||||||
|
|
||||||
#include "GlRenderConstants.h"
|
|
||||||
#include "TextRasterizer.h"
|
|
||||||
#include "TextureAssetLoader.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
std::string TextValueForBinding(const RuntimeRenderState& state, const std::string& parameterId)
|
|
||||||
{
|
|
||||||
auto valueIt = state.parameterValues.find(parameterId);
|
|
||||||
return valueIt == state.parameterValues.end() ? std::string() : valueIt->second.textValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ShaderFontAsset* FindFontAssetForParameter(const RuntimeRenderState& state, const ShaderParameterDefinition& definition)
|
|
||||||
{
|
|
||||||
if (!definition.fontId.empty())
|
|
||||||
{
|
|
||||||
for (const ShaderFontAsset& fontAsset : state.fontAssets)
|
|
||||||
{
|
|
||||||
if (fontAsset.id == definition.fontId)
|
|
||||||
return &fontAsset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return state.fontAssets.empty() ? nullptr : &state.fontAssets.front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderTextureBindings::LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
|
|
||||||
{
|
|
||||||
return ::LoadTextureAsset(textureAsset, textureId, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderTextureBindings::CreateTextBindings(const RuntimeRenderState& state, std::vector<LayerProgram::TextBinding>& textBindings)
|
|
||||||
{
|
|
||||||
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
|
||||||
{
|
|
||||||
if (definition.type != ShaderParameterType::Text)
|
|
||||||
continue;
|
|
||||||
LayerProgram::TextBinding textBinding;
|
|
||||||
textBinding.parameterId = definition.id;
|
|
||||||
textBinding.samplerName = definition.id + "Texture";
|
|
||||||
textBinding.fontId = definition.fontId;
|
|
||||||
glGenTextures(1, &textBinding.texture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, textBinding.texture);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
||||||
std::vector<unsigned char> empty(static_cast<std::size_t>(kTextTextureWidth) * kTextTextureHeight * 4, 0);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTextTextureWidth, kTextTextureHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, empty.data());
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
textBindings.push_back(textBinding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderTextureBindings::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error)
|
|
||||||
{
|
|
||||||
const std::string text = TextValueForBinding(state, textBinding.parameterId);
|
|
||||||
if (text == textBinding.renderedText && textBinding.renderedWidth == kTextTextureWidth && textBinding.renderedHeight == kTextTextureHeight)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
auto definitionIt = std::find_if(state.parameterDefinitions.begin(), state.parameterDefinitions.end(),
|
|
||||||
[&textBinding](const ShaderParameterDefinition& definition) { return definition.id == textBinding.parameterId; });
|
|
||||||
if (definitionIt == state.parameterDefinitions.end())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
const ShaderFontAsset* fontAsset = FindFontAssetForParameter(state, *definitionIt);
|
|
||||||
std::filesystem::path fontPath;
|
|
||||||
if (fontAsset)
|
|
||||||
fontPath = fontAsset->path;
|
|
||||||
|
|
||||||
std::vector<unsigned char> sdf;
|
|
||||||
if (!RasterizeTextSdf(text, fontPath, sdf, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
GLint previousActiveTexture = 0;
|
|
||||||
GLint previousUnpackBuffer = 0;
|
|
||||||
glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture);
|
|
||||||
glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &previousUnpackBuffer);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, textBinding.texture);
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextTextureWidth, kTextTextureHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, sdf.data());
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(previousUnpackBuffer));
|
|
||||||
glActiveTexture(static_cast<GLenum>(previousActiveTexture));
|
|
||||||
|
|
||||||
textBinding.renderedText = text;
|
|
||||||
textBinding.renderedWidth = kTextTextureWidth;
|
|
||||||
textBinding.renderedHeight = kTextTextureHeight;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint ShaderTextureBindings::FindSamplerUniformLocation(GLuint program, const std::string& samplerName) const
|
|
||||||
{
|
|
||||||
GLint location = glGetUniformLocation(program, samplerName.c_str());
|
|
||||||
if (location >= 0)
|
|
||||||
return location;
|
|
||||||
return glGetUniformLocation(program, (samplerName + "_0").c_str());
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "OpenGLRenderer.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class ShaderTextureBindings
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
|
||||||
|
|
||||||
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error);
|
|
||||||
void CreateTextBindings(const RuntimeRenderState& state, std::vector<LayerProgram::TextBinding>& textBindings);
|
|
||||||
bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error);
|
|
||||||
GLint FindSamplerUniformLocation(GLuint program, const std::string& samplerName) const;
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
#include "OpenGLRenderPass.h"
|
||||||
|
|
||||||
|
#include "GlRenderConstants.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
OpenGLRenderPass::OpenGLRenderPass(OpenGLRenderer& renderer) :
|
||||||
|
mRenderer(renderer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLRenderPass::Render(
|
||||||
|
bool hasInputSource,
|
||||||
|
const std::vector<RuntimeRenderState>& layerStates,
|
||||||
|
unsigned inputFrameWidth,
|
||||||
|
unsigned inputFrameHeight,
|
||||||
|
unsigned captureTextureWidth,
|
||||||
|
VideoIOPixelFormat inputPixelFormat,
|
||||||
|
unsigned historyCap,
|
||||||
|
const TextBindingUpdater& updateTextBinding,
|
||||||
|
const GlobalParamsUpdater& updateGlobalParams)
|
||||||
|
{
|
||||||
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
if (hasInputSource)
|
||||||
|
{
|
||||||
|
RenderDecodePass(inputFrameWidth, inputFrameHeight, captureTextureWidth, inputPixelFormat);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
|
||||||
|
glViewport(0, 0, inputFrameWidth, inputFrameHeight);
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<LayerProgram>& layerPrograms = mRenderer.LayerPrograms();
|
||||||
|
if (layerStates.empty() || layerPrograms.empty())
|
||||||
|
{
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
||||||
|
glBlitFramebuffer(0, 0, inputFrameWidth, inputFrameHeight, 0, 0, inputFrameWidth, inputFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const std::vector<RenderPassDescriptor>& passes = BuildLayerPassDescriptors(layerStates, layerPrograms);
|
||||||
|
for (const RenderPassDescriptor& pass : passes)
|
||||||
|
{
|
||||||
|
RenderLayerPass(
|
||||||
|
pass,
|
||||||
|
inputFrameWidth,
|
||||||
|
inputFrameHeight,
|
||||||
|
historyCap,
|
||||||
|
updateTextBinding,
|
||||||
|
updateGlobalParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mRenderer.TemporalHistory().PushSourceFramebuffer(mRenderer.DecodeFramebuffer(), inputFrameWidth, inputFrameHeight);
|
||||||
|
mRenderer.FeedbackBuffers().FinalizeFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLRenderPass::RenderDecodePass(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat)
|
||||||
|
{
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
|
||||||
|
glViewport(0, 0, inputFrameWidth, inputFrameHeight);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
glActiveTexture(GL_TEXTURE0 + kPackedVideoTextureUnit);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
|
||||||
|
glBindVertexArray(mRenderer.FullscreenVertexArray());
|
||||||
|
glUseProgram(mRenderer.DecodeProgram());
|
||||||
|
|
||||||
|
const GLint packedResolutionLocation = mRenderer.DecodePackedResolutionLocation();
|
||||||
|
const GLint decodedResolutionLocation = mRenderer.DecodeDecodedResolutionLocation();
|
||||||
|
const GLint inputPixelFormatLocation = mRenderer.DecodeInputPixelFormatLocation();
|
||||||
|
if (packedResolutionLocation >= 0)
|
||||||
|
glUniform2f(packedResolutionLocation, static_cast<float>(captureTextureWidth), static_cast<float>(inputFrameHeight));
|
||||||
|
if (decodedResolutionLocation >= 0)
|
||||||
|
glUniform2f(decodedResolutionLocation, static_cast<float>(inputFrameWidth), static_cast<float>(inputFrameHeight));
|
||||||
|
if (inputPixelFormatLocation >= 0)
|
||||||
|
glUniform1i(inputPixelFormatLocation, inputPixelFormat == VideoIOPixelFormat::V210 ? 1 : 0);
|
||||||
|
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||||
|
|
||||||
|
glUseProgram(0);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<RenderPassDescriptor> OpenGLRenderPass::BuildLayerPassDescriptors(
|
||||||
|
const std::vector<RuntimeRenderState>& layerStates,
|
||||||
|
std::vector<LayerProgram>& layerPrograms) const
|
||||||
|
{
|
||||||
|
// Flatten the layer stack into concrete GL passes. A layer may now contain
|
||||||
|
// several shader passes, but the outer stack still sees one visible output
|
||||||
|
// per layer.
|
||||||
|
std::vector<RenderPassDescriptor>& passes = mPassScratch;
|
||||||
|
passes.clear();
|
||||||
|
const std::size_t passCount = layerStates.size() < layerPrograms.size() ? layerStates.size() : layerPrograms.size();
|
||||||
|
std::size_t descriptorCount = 0;
|
||||||
|
for (std::size_t index = 0; index < passCount; ++index)
|
||||||
|
descriptorCount += layerPrograms[index].passes.size();
|
||||||
|
passes.reserve(descriptorCount);
|
||||||
|
|
||||||
|
GLuint sourceTexture = mRenderer.DecodedTexture();
|
||||||
|
GLuint sourceFramebuffer = mRenderer.DecodeFramebuffer();
|
||||||
|
for (std::size_t index = 0; index < passCount; ++index)
|
||||||
|
{
|
||||||
|
const RuntimeRenderState& state = layerStates[index];
|
||||||
|
LayerProgram& layerProgram = layerPrograms[index];
|
||||||
|
if (layerProgram.passes.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Preserve the original two-target layer ping-pong. Intermediate passes
|
||||||
|
// inside this layer are routed through pooled temporary targets instead.
|
||||||
|
const std::size_t remaining = layerStates.size() - index;
|
||||||
|
const bool writeToMain = (remaining % 2) == 1;
|
||||||
|
const GLuint layerOutputTexture = writeToMain ? mRenderer.CompositeTexture() : mRenderer.LayerTempTexture();
|
||||||
|
const GLuint layerOutputFramebuffer = writeToMain ? mRenderer.CompositeFramebuffer() : mRenderer.LayerTempFramebuffer();
|
||||||
|
const RenderPassOutputTarget layerOutputTarget = writeToMain ? RenderPassOutputTarget::Composite : RenderPassOutputTarget::LayerTemp;
|
||||||
|
|
||||||
|
const GLuint layerInputTexture = sourceTexture;
|
||||||
|
const GLuint layerInputFramebuffer = sourceFramebuffer;
|
||||||
|
GLuint previousPassTexture = layerInputTexture;
|
||||||
|
GLuint previousPassFramebuffer = layerInputFramebuffer;
|
||||||
|
std::map<std::string, std::pair<GLuint, GLuint>> namedOutputs;
|
||||||
|
std::size_t temporaryTargetIndex = 0;
|
||||||
|
|
||||||
|
for (std::size_t passIndex = 0; passIndex < layerProgram.passes.size(); ++passIndex)
|
||||||
|
{
|
||||||
|
PassProgram& passProgram = layerProgram.passes[passIndex];
|
||||||
|
const bool lastPassForLayer = passIndex + 1 == layerProgram.passes.size();
|
||||||
|
const std::string outputName = passProgram.outputName.empty() ? passProgram.passId : passProgram.outputName;
|
||||||
|
const bool writesLayerOutput = outputName == "layerOutput" || lastPassForLayer;
|
||||||
|
|
||||||
|
GLuint passSourceTexture = previousPassTexture;
|
||||||
|
GLuint passSourceFramebuffer = previousPassFramebuffer;
|
||||||
|
if (!passProgram.inputNames.empty())
|
||||||
|
{
|
||||||
|
// v1 multipass uses the first declared input as gVideoInput.
|
||||||
|
// Later inputs are parsed for forward compatibility.
|
||||||
|
const std::string& inputName = passProgram.inputNames.front();
|
||||||
|
if (inputName == "layerInput")
|
||||||
|
{
|
||||||
|
passSourceTexture = layerInputTexture;
|
||||||
|
passSourceFramebuffer = layerInputFramebuffer;
|
||||||
|
}
|
||||||
|
else if (inputName == "previousPass")
|
||||||
|
{
|
||||||
|
passSourceTexture = previousPassTexture;
|
||||||
|
passSourceFramebuffer = previousPassFramebuffer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto namedOutputIt = namedOutputs.find(inputName);
|
||||||
|
if (namedOutputIt != namedOutputs.end())
|
||||||
|
{
|
||||||
|
passSourceTexture = namedOutputIt->second.first;
|
||||||
|
passSourceFramebuffer = namedOutputIt->second.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint passDestinationTexture = layerOutputTexture;
|
||||||
|
GLuint passDestinationFramebuffer = layerOutputFramebuffer;
|
||||||
|
RenderPassOutputTarget outputTarget = layerOutputTarget;
|
||||||
|
if (!writesLayerOutput)
|
||||||
|
{
|
||||||
|
// Temporary targets are reserved when the shader stack is
|
||||||
|
// committed, avoiding texture allocation during playback.
|
||||||
|
if (temporaryTargetIndex < mRenderer.TemporaryRenderTargetCount())
|
||||||
|
{
|
||||||
|
const RenderTarget& temporaryTarget = mRenderer.TemporaryRenderTarget(temporaryTargetIndex);
|
||||||
|
++temporaryTargetIndex;
|
||||||
|
passDestinationTexture = temporaryTarget.texture;
|
||||||
|
passDestinationFramebuffer = temporaryTarget.framebuffer;
|
||||||
|
outputTarget = RenderPassOutputTarget::Temporary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderPassDescriptor pass;
|
||||||
|
pass.kind = RenderPassKind::LayerEffect;
|
||||||
|
pass.outputTarget = outputTarget;
|
||||||
|
pass.passIndex = passes.size();
|
||||||
|
pass.passId = passProgram.passId;
|
||||||
|
pass.layerId = state.layerId;
|
||||||
|
pass.shaderId = state.shaderId;
|
||||||
|
pass.layerInputTexture = layerInputTexture;
|
||||||
|
pass.sourceTexture = passSourceTexture;
|
||||||
|
pass.sourceFramebuffer = passIndex == 0 ? layerInputFramebuffer : passSourceFramebuffer;
|
||||||
|
pass.destinationTexture = passDestinationTexture;
|
||||||
|
pass.destinationFramebuffer = passDestinationFramebuffer;
|
||||||
|
pass.layerProgram = &layerProgram;
|
||||||
|
pass.passProgram = &passProgram;
|
||||||
|
pass.layerState = &state;
|
||||||
|
pass.capturePreLayerHistory = passIndex == 0 && state.temporalHistorySource == TemporalHistorySource::PreLayerInput;
|
||||||
|
pass.captureFeedbackWrite = state.feedback.enabled && passProgram.passId == state.feedback.writePassId;
|
||||||
|
passes.push_back(pass);
|
||||||
|
|
||||||
|
// A later pass can reference either the explicit output name or the
|
||||||
|
// pass id, which keeps small manifests pleasant to write.
|
||||||
|
namedOutputs[outputName] = std::make_pair(passDestinationTexture, passDestinationFramebuffer);
|
||||||
|
namedOutputs[passProgram.passId] = std::make_pair(passDestinationTexture, passDestinationFramebuffer);
|
||||||
|
previousPassTexture = passDestinationTexture;
|
||||||
|
previousPassFramebuffer = passDestinationFramebuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceTexture = layerOutputTexture;
|
||||||
|
sourceFramebuffer = layerOutputFramebuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLRenderPass::RenderLayerPass(
|
||||||
|
const RenderPassDescriptor& pass,
|
||||||
|
unsigned inputFrameWidth,
|
||||||
|
unsigned inputFrameHeight,
|
||||||
|
unsigned historyCap,
|
||||||
|
const TextBindingUpdater& updateTextBinding,
|
||||||
|
const GlobalParamsUpdater& updateGlobalParams)
|
||||||
|
{
|
||||||
|
if (pass.passProgram == nullptr || pass.layerState == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RenderShaderProgram(
|
||||||
|
pass.layerInputTexture,
|
||||||
|
pass.sourceTexture,
|
||||||
|
pass.destinationFramebuffer,
|
||||||
|
*pass.passProgram,
|
||||||
|
*pass.layerState,
|
||||||
|
inputFrameWidth,
|
||||||
|
inputFrameHeight,
|
||||||
|
historyCap,
|
||||||
|
updateTextBinding,
|
||||||
|
updateGlobalParams);
|
||||||
|
|
||||||
|
if (pass.capturePreLayerHistory)
|
||||||
|
mRenderer.TemporalHistory().PushPreLayerFramebuffer(pass.layerId, pass.sourceFramebuffer, inputFrameWidth, inputFrameHeight);
|
||||||
|
if (pass.captureFeedbackWrite)
|
||||||
|
mRenderer.FeedbackBuffers().CaptureFeedbackFramebuffer(pass.layerId, pass.destinationFramebuffer, inputFrameWidth, inputFrameHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLRenderPass::RenderShaderProgram(
|
||||||
|
GLuint layerInputTexture,
|
||||||
|
GLuint sourceTexture,
|
||||||
|
GLuint destinationFrameBuffer,
|
||||||
|
PassProgram& passProgram,
|
||||||
|
const RuntimeRenderState& state,
|
||||||
|
unsigned inputFrameWidth,
|
||||||
|
unsigned inputFrameHeight,
|
||||||
|
unsigned historyCap,
|
||||||
|
const TextBindingUpdater& updateTextBinding,
|
||||||
|
const GlobalParamsUpdater& updateGlobalParams)
|
||||||
|
{
|
||||||
|
for (LayerProgram::TextBinding& textBinding : passProgram.textBindings)
|
||||||
|
{
|
||||||
|
std::string textError;
|
||||||
|
if (!updateTextBinding(state, textBinding, textError))
|
||||||
|
OutputDebugStringA((textError + "\n").c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, destinationFrameBuffer);
|
||||||
|
glViewport(0, 0, inputFrameWidth, inputFrameHeight);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
const std::vector<GLuint> sourceHistoryTextures = mRenderer.TemporalHistory().ResolveSourceHistoryTextures(sourceTexture, state.isTemporal ? historyCap : 0);
|
||||||
|
const std::vector<GLuint> temporalHistoryTextures = mRenderer.TemporalHistory().ResolveTemporalHistoryTextures(state, sourceTexture, state.isTemporal ? historyCap : 0);
|
||||||
|
const GLuint feedbackTexture = mRenderer.FeedbackBuffers().ResolveReadTexture(state);
|
||||||
|
const ShaderTextureBindings::RuntimeTextureBindingPlan texturePlan =
|
||||||
|
mTextureBindings.BuildLayerRuntimeBindingPlan(passProgram, sourceTexture, layerInputTexture, state, feedbackTexture, sourceHistoryTextures, temporalHistoryTextures);
|
||||||
|
mTextureBindings.BindRuntimeTexturePlan(texturePlan);
|
||||||
|
glBindVertexArray(mRenderer.FullscreenVertexArray());
|
||||||
|
glUseProgram(passProgram.program);
|
||||||
|
// The UBO is shared by every pass in a layer; texture routing is what
|
||||||
|
// changes from pass to pass.
|
||||||
|
updateGlobalParams(
|
||||||
|
state,
|
||||||
|
mRenderer.TemporalHistory().SourceAvailableCount(),
|
||||||
|
mRenderer.TemporalHistory().AvailableCountForLayer(state.layerId),
|
||||||
|
mRenderer.FeedbackBuffers().FeedbackAvailable(state));
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||||
|
glUseProgram(0);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
mTextureBindings.UnbindRuntimeTexturePlan(texturePlan);
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "OpenGLRenderer.h"
|
#include "OpenGLRenderer.h"
|
||||||
|
#include "RenderPassDescriptor.h"
|
||||||
|
#include "ShaderTextureBindings.h"
|
||||||
#include "ShaderTypes.h"
|
#include "ShaderTypes.h"
|
||||||
#include "VideoIOFormat.h"
|
#include "VideoIOFormat.h"
|
||||||
|
|
||||||
@@ -12,8 +14,9 @@ class OpenGLRenderPass
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
using LayerProgram = OpenGLRenderer::LayerProgram;
|
||||||
|
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
|
||||||
using TextBindingUpdater = std::function<bool(const RuntimeRenderState&, LayerProgram::TextBinding&, std::string&)>;
|
using TextBindingUpdater = std::function<bool(const RuntimeRenderState&, LayerProgram::TextBinding&, std::string&)>;
|
||||||
using GlobalParamsUpdater = std::function<bool(const RuntimeRenderState&, unsigned, unsigned)>;
|
using GlobalParamsUpdater = std::function<bool(const RuntimeRenderState&, unsigned, unsigned, bool)>;
|
||||||
|
|
||||||
explicit OpenGLRenderPass(OpenGLRenderer& renderer);
|
explicit OpenGLRenderPass(OpenGLRenderer& renderer);
|
||||||
|
|
||||||
@@ -30,18 +33,29 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void RenderDecodePass(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat);
|
void RenderDecodePass(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat);
|
||||||
|
std::vector<RenderPassDescriptor> BuildLayerPassDescriptors(
|
||||||
|
const std::vector<RuntimeRenderState>& layerStates,
|
||||||
|
std::vector<LayerProgram>& layerPrograms) const;
|
||||||
|
void RenderLayerPass(
|
||||||
|
const RenderPassDescriptor& pass,
|
||||||
|
unsigned inputFrameWidth,
|
||||||
|
unsigned inputFrameHeight,
|
||||||
|
unsigned historyCap,
|
||||||
|
const TextBindingUpdater& updateTextBinding,
|
||||||
|
const GlobalParamsUpdater& updateGlobalParams);
|
||||||
void RenderShaderProgram(
|
void RenderShaderProgram(
|
||||||
|
GLuint layerInputTexture,
|
||||||
GLuint sourceTexture,
|
GLuint sourceTexture,
|
||||||
GLuint destinationFrameBuffer,
|
GLuint destinationFrameBuffer,
|
||||||
LayerProgram& layerProgram,
|
PassProgram& passProgram,
|
||||||
const RuntimeRenderState& state,
|
const RuntimeRenderState& state,
|
||||||
unsigned inputFrameWidth,
|
unsigned inputFrameWidth,
|
||||||
unsigned inputFrameHeight,
|
unsigned inputFrameHeight,
|
||||||
unsigned historyCap,
|
unsigned historyCap,
|
||||||
const TextBindingUpdater& updateTextBinding,
|
const TextBindingUpdater& updateTextBinding,
|
||||||
const GlobalParamsUpdater& updateGlobalParams);
|
const GlobalParamsUpdater& updateGlobalParams);
|
||||||
void BindLayerTextureAssets(const LayerProgram& layerProgram);
|
|
||||||
void UnbindLayerTextureAssets(const LayerProgram& layerProgram, unsigned historyCap);
|
|
||||||
|
|
||||||
OpenGLRenderer& mRenderer;
|
OpenGLRenderer& mRenderer;
|
||||||
|
ShaderTextureBindings mTextureBindings;
|
||||||
|
mutable std::vector<RenderPassDescriptor> mPassScratch;
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
#include "OpenGLRenderPipeline.h"
|
||||||
|
|
||||||
|
#include "HealthTelemetry.h"
|
||||||
|
#include "OpenGLRenderer.h"
|
||||||
|
#include "RuntimeSnapshotProvider.h"
|
||||||
|
#include "VideoIOFormat.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <gl/gl.h>
|
||||||
|
|
||||||
|
OpenGLRenderPipeline::OpenGLRenderPipeline(
|
||||||
|
OpenGLRenderer& renderer,
|
||||||
|
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
||||||
|
HealthTelemetry& healthTelemetry,
|
||||||
|
RenderEffectCallback renderEffect,
|
||||||
|
OutputReadyCallback outputReady,
|
||||||
|
PaintCallback paint) :
|
||||||
|
mRenderer(renderer),
|
||||||
|
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
||||||
|
mHealthTelemetry(healthTelemetry),
|
||||||
|
mRenderEffect(renderEffect),
|
||||||
|
mOutputReady(outputReady),
|
||||||
|
mPaint(paint)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenGLRenderPipeline::~OpenGLRenderPipeline()
|
||||||
|
{
|
||||||
|
ResetAsyncReadbackState();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenGLRenderPipeline::RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
|
||||||
|
{
|
||||||
|
const VideoIOState& state = context.videoState;
|
||||||
|
|
||||||
|
const auto renderStartTime = std::chrono::steady_clock::now();
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
||||||
|
mRenderEffect();
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
||||||
|
glBlitFramebuffer(0, 0, state.inputFrameSize.width, state.inputFrameSize.height, 0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
||||||
|
if (mOutputReady)
|
||||||
|
mOutputReady();
|
||||||
|
if (state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10)
|
||||||
|
PackOutputFor10Bit(state);
|
||||||
|
glFlush();
|
||||||
|
|
||||||
|
const auto renderEndTime = std::chrono::steady_clock::now();
|
||||||
|
const double renderMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(renderEndTime - renderStartTime).count();
|
||||||
|
mHealthTelemetry.TryRecordPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds);
|
||||||
|
mRuntimeSnapshotProvider.TryAdvanceFrame();
|
||||||
|
|
||||||
|
ReadOutputFrame(state, outputFrame);
|
||||||
|
if (mPaint)
|
||||||
|
mPaint();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLRenderPipeline::PackOutputFor10Bit(const VideoIOState& state)
|
||||||
|
{
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
||||||
|
glViewport(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height);
|
||||||
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, mRenderer.OutputTexture());
|
||||||
|
glBindVertexArray(mRenderer.FullscreenVertexArray());
|
||||||
|
glUseProgram(mRenderer.OutputPackProgram());
|
||||||
|
|
||||||
|
const GLint outputResolutionLocation = mRenderer.OutputPackResolutionLocation();
|
||||||
|
const GLint activeWordsLocation = mRenderer.OutputPackActiveWordsLocation();
|
||||||
|
const GLint packFormatLocation = mRenderer.OutputPackFormatLocation();
|
||||||
|
if (outputResolutionLocation >= 0)
|
||||||
|
glUniform2f(outputResolutionLocation, static_cast<float>(state.outputFrameSize.width), static_cast<float>(state.outputFrameSize.height));
|
||||||
|
if (activeWordsLocation >= 0)
|
||||||
|
glUniform1f(activeWordsLocation, static_cast<float>(ActiveV210WordsForWidth(state.outputFrameSize.width)));
|
||||||
|
if (packFormatLocation >= 0)
|
||||||
|
glUniform1i(packFormatLocation, state.outputPixelFormat == VideoIOPixelFormat::Yuva10 ? 2 : 1);
|
||||||
|
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||||
|
glUseProgram(0);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenGLRenderPipeline::EnsureAsyncReadbackBuffers(std::size_t requiredBytes)
|
||||||
|
{
|
||||||
|
if (requiredBytes == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (mAsyncReadbackBytes == requiredBytes && mAsyncReadbackSlots[0].pixelPackBuffer != 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
ResetAsyncReadbackState();
|
||||||
|
mAsyncReadbackBytes = requiredBytes;
|
||||||
|
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
||||||
|
{
|
||||||
|
glGenBuffers(1, &slot.pixelPackBuffer);
|
||||||
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
|
||||||
|
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(requiredBytes), nullptr, GL_STREAM_READ);
|
||||||
|
slot.sizeBytes = requiredBytes;
|
||||||
|
slot.inFlight = false;
|
||||||
|
}
|
||||||
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||||
|
mAsyncReadbackWriteIndex = 0;
|
||||||
|
mAsyncReadbackReadIndex = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLRenderPipeline::ResetAsyncReadbackState()
|
||||||
|
{
|
||||||
|
FlushAsyncReadbackPipeline();
|
||||||
|
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
||||||
|
slot.sizeBytes = 0;
|
||||||
|
|
||||||
|
if (mAsyncReadbackSlots[0].pixelPackBuffer != 0)
|
||||||
|
{
|
||||||
|
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
||||||
|
{
|
||||||
|
if (slot.pixelPackBuffer != 0)
|
||||||
|
{
|
||||||
|
glDeleteBuffers(1, &slot.pixelPackBuffer);
|
||||||
|
slot.pixelPackBuffer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mAsyncReadbackWriteIndex = 0;
|
||||||
|
mAsyncReadbackReadIndex = 0;
|
||||||
|
mAsyncReadbackBytes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLRenderPipeline::FlushAsyncReadbackPipeline()
|
||||||
|
{
|
||||||
|
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
||||||
|
{
|
||||||
|
if (slot.fence != nullptr)
|
||||||
|
{
|
||||||
|
glDeleteSync(slot.fence);
|
||||||
|
slot.fence = nullptr;
|
||||||
|
}
|
||||||
|
slot.inFlight = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mAsyncReadbackWriteIndex = 0;
|
||||||
|
mAsyncReadbackReadIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLRenderPipeline::QueueAsyncReadback(const VideoIOState& state)
|
||||||
|
{
|
||||||
|
const bool usePackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10;
|
||||||
|
const std::size_t requiredBytes = static_cast<std::size_t>(state.outputFrameRowBytes) * state.outputFrameSize.height;
|
||||||
|
const GLenum format = usePackedOutput ? GL_RGBA : GL_BGRA;
|
||||||
|
const GLenum type = usePackedOutput ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||||
|
const GLuint framebuffer = usePackedOutput ? mRenderer.OutputPackFramebuffer() : mRenderer.OutputFramebuffer();
|
||||||
|
const GLsizei readWidth = static_cast<GLsizei>(usePackedOutput ? state.outputPackTextureWidth : state.outputFrameSize.width);
|
||||||
|
const GLsizei readHeight = static_cast<GLsizei>(state.outputFrameSize.height);
|
||||||
|
|
||||||
|
if (requiredBytes == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (mAsyncReadbackBytes != requiredBytes
|
||||||
|
|| mAsyncReadbackFormat != format
|
||||||
|
|| mAsyncReadbackType != type
|
||||||
|
|| mAsyncReadbackFramebuffer != framebuffer)
|
||||||
|
{
|
||||||
|
mAsyncReadbackFormat = format;
|
||||||
|
mAsyncReadbackType = type;
|
||||||
|
mAsyncReadbackFramebuffer = framebuffer;
|
||||||
|
if (!EnsureAsyncReadbackBuffers(requiredBytes))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncReadbackSlot& slot = mAsyncReadbackSlots[mAsyncReadbackWriteIndex];
|
||||||
|
if (slot.fence != nullptr)
|
||||||
|
{
|
||||||
|
glDeleteSync(slot.fence);
|
||||||
|
slot.fence = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||||
|
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
|
||||||
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
|
||||||
|
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(requiredBytes), nullptr, GL_STREAM_READ);
|
||||||
|
glReadPixels(0, 0, readWidth, readHeight, format, type, nullptr);
|
||||||
|
slot.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
|
slot.inFlight = slot.fence != nullptr;
|
||||||
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||||
|
|
||||||
|
mAsyncReadbackWriteIndex = (mAsyncReadbackWriteIndex + 1) % mAsyncReadbackSlots.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds)
|
||||||
|
{
|
||||||
|
if (mAsyncReadbackBytes == 0 || outputFrame.bytes == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
AsyncReadbackSlot& slot = mAsyncReadbackSlots[mAsyncReadbackReadIndex];
|
||||||
|
if (!slot.inFlight || slot.fence == nullptr || slot.pixelPackBuffer == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const GLenum waitFlags = timeoutNanoseconds > 0 ? GL_SYNC_FLUSH_COMMANDS_BIT : 0;
|
||||||
|
const GLenum waitResult = glClientWaitSync(slot.fence, waitFlags, timeoutNanoseconds);
|
||||||
|
if (waitResult != GL_ALREADY_SIGNALED && waitResult != GL_CONDITION_SATISFIED)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
glDeleteSync(slot.fence);
|
||||||
|
slot.fence = nullptr;
|
||||||
|
|
||||||
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
|
||||||
|
void* mappedBytes = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
||||||
|
if (mappedBytes == nullptr)
|
||||||
|
{
|
||||||
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||||
|
slot.inFlight = false;
|
||||||
|
mAsyncReadbackReadIndex = (mAsyncReadbackReadIndex + 1) % mAsyncReadbackSlots.size();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memcpy(outputFrame.bytes, mappedBytes, slot.sizeBytes);
|
||||||
|
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||||
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||||
|
|
||||||
|
slot.inFlight = false;
|
||||||
|
mAsyncReadbackReadIndex = (mAsyncReadbackReadIndex + 1) % mAsyncReadbackSlots.size();
|
||||||
|
CacheOutputFrame(outputFrame);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLRenderPipeline::CacheOutputFrame(const VideoIOOutputFrame& outputFrame)
|
||||||
|
{
|
||||||
|
if (outputFrame.bytes == nullptr || outputFrame.height == 0 || outputFrame.rowBytes <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const std::size_t byteCount = static_cast<std::size_t>(outputFrame.rowBytes) * outputFrame.height;
|
||||||
|
mCachedOutputFrame.resize(byteCount);
|
||||||
|
std::memcpy(mCachedOutputFrame.data(), outputFrame.bytes, byteCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLRenderPipeline::ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes)
|
||||||
|
{
|
||||||
|
const bool usePackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10;
|
||||||
|
|
||||||
|
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||||
|
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||||
|
if (usePackedOutput)
|
||||||
|
{
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
||||||
|
glReadPixels(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, destinationBytes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
||||||
|
glReadPixels(0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, destinationBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLRenderPipeline::ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame)
|
||||||
|
{
|
||||||
|
if (TryConsumeAsyncReadback(outputFrame, 500000))
|
||||||
|
{
|
||||||
|
QueueAsyncReadback(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If async readback misses the playout deadline, prefer a fresh synchronous
|
||||||
|
// frame over reusing stale cached output, then restart the async pipeline.
|
||||||
|
if (outputFrame.bytes != nullptr)
|
||||||
|
{
|
||||||
|
ReadOutputFrameSynchronously(state, outputFrame.bytes);
|
||||||
|
CacheOutputFrame(outputFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlushAsyncReadbackPipeline();
|
||||||
|
QueueAsyncReadback(state);
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GLExtensions.h"
|
||||||
|
#include "VideoIOTypes.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class OpenGLRenderer;
|
||||||
|
class HealthTelemetry;
|
||||||
|
class RuntimeSnapshotProvider;
|
||||||
|
|
||||||
|
struct RenderPipelineFrameContext
|
||||||
|
{
|
||||||
|
VideoIOState videoState;
|
||||||
|
VideoIOCompletion completion;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OpenGLRenderPipeline
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using RenderEffectCallback = std::function<void()>;
|
||||||
|
using OutputReadyCallback = std::function<void()>;
|
||||||
|
using PaintCallback = std::function<void()>;
|
||||||
|
|
||||||
|
OpenGLRenderPipeline(
|
||||||
|
OpenGLRenderer& renderer,
|
||||||
|
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
||||||
|
HealthTelemetry& healthTelemetry,
|
||||||
|
RenderEffectCallback renderEffect,
|
||||||
|
OutputReadyCallback outputReady,
|
||||||
|
PaintCallback paint);
|
||||||
|
~OpenGLRenderPipeline();
|
||||||
|
|
||||||
|
bool RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct AsyncReadbackSlot
|
||||||
|
{
|
||||||
|
GLuint pixelPackBuffer = 0;
|
||||||
|
GLsync fence = nullptr;
|
||||||
|
std::size_t sizeBytes = 0;
|
||||||
|
bool inFlight = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool EnsureAsyncReadbackBuffers(std::size_t requiredBytes);
|
||||||
|
void ResetAsyncReadbackState();
|
||||||
|
void FlushAsyncReadbackPipeline();
|
||||||
|
void QueueAsyncReadback(const VideoIOState& state);
|
||||||
|
bool TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds);
|
||||||
|
void CacheOutputFrame(const VideoIOOutputFrame& outputFrame);
|
||||||
|
void ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes);
|
||||||
|
void PackOutputFor10Bit(const VideoIOState& state);
|
||||||
|
void ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame);
|
||||||
|
|
||||||
|
OpenGLRenderer& mRenderer;
|
||||||
|
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||||
|
HealthTelemetry& mHealthTelemetry;
|
||||||
|
RenderEffectCallback mRenderEffect;
|
||||||
|
OutputReadyCallback mOutputReady;
|
||||||
|
PaintCallback mPaint;
|
||||||
|
std::array<AsyncReadbackSlot, 3> mAsyncReadbackSlots;
|
||||||
|
std::size_t mAsyncReadbackWriteIndex = 0;
|
||||||
|
std::size_t mAsyncReadbackReadIndex = 0;
|
||||||
|
std::size_t mAsyncReadbackBytes = 0;
|
||||||
|
GLenum mAsyncReadbackFormat = GL_BGRA;
|
||||||
|
GLenum mAsyncReadbackType = GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||||
|
GLuint mAsyncReadbackFramebuffer = 0;
|
||||||
|
std::vector<unsigned char> mCachedOutputFrame;
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#include "OpenGLVideoIOBridge.h"
|
||||||
|
|
||||||
|
#include "RenderEngine.h"
|
||||||
|
|
||||||
|
OpenGLVideoIOBridge::OpenGLVideoIOBridge(RenderEngine& renderEngine) :
|
||||||
|
mRenderEngine(renderEngine)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLVideoIOBridge::UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state)
|
||||||
|
{
|
||||||
|
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
|
||||||
|
return; // don't transfer texture when there's no input
|
||||||
|
|
||||||
|
mRenderEngine.TryUploadInputFrame(inputFrame, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLVideoIOBridge::RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame)
|
||||||
|
{
|
||||||
|
RenderPipelineFrameContext frameContext;
|
||||||
|
frameContext.videoState = state;
|
||||||
|
frameContext.completion = completion;
|
||||||
|
|
||||||
|
mRenderEngine.RenderOutputFrame(frameContext, outputFrame);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "OpenGLRenderPipeline.h"
|
||||||
|
|
||||||
|
class RenderEngine;
|
||||||
|
|
||||||
|
class OpenGLVideoIOBridge
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit OpenGLVideoIOBridge(RenderEngine& renderEngine);
|
||||||
|
|
||||||
|
void UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state);
|
||||||
|
void RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame);
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderEngine& mRenderEngine;
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "OpenGLRenderer.h"
|
||||||
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
|
#include <gl/gl.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
enum class RenderPassKind
|
||||||
|
{
|
||||||
|
LayerEffect
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RenderPassOutputTarget
|
||||||
|
{
|
||||||
|
Temporary,
|
||||||
|
LayerTemp,
|
||||||
|
Composite
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderPassDescriptor
|
||||||
|
{
|
||||||
|
RenderPassKind kind = RenderPassKind::LayerEffect;
|
||||||
|
RenderPassOutputTarget outputTarget = RenderPassOutputTarget::Composite;
|
||||||
|
std::size_t passIndex = 0;
|
||||||
|
std::string passId;
|
||||||
|
std::string layerId;
|
||||||
|
std::string shaderId;
|
||||||
|
GLuint layerInputTexture = 0;
|
||||||
|
GLuint sourceTexture = 0;
|
||||||
|
GLuint sourceFramebuffer = 0;
|
||||||
|
GLuint destinationTexture = 0;
|
||||||
|
GLuint destinationFramebuffer = 0;
|
||||||
|
OpenGLRenderer::LayerProgram* layerProgram = nullptr;
|
||||||
|
OpenGLRenderer::LayerProgram::PassProgram* passProgram = nullptr;
|
||||||
|
const RuntimeRenderState* layerState = nullptr;
|
||||||
|
bool capturePreLayerHistory = false;
|
||||||
|
bool captureFeedbackWrite = false;
|
||||||
|
};
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
#include "ShaderFeedbackBuffers.h"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void ConfigureFeedbackTexture(unsigned frameWidth, unsigned frameHeight)
|
||||||
|
{
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, frameWidth, frameHeight, 0, GL_RGBA, GL_FLOAT, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderFeedbackBuffers::EnsureResources(const std::vector<RuntimeRenderState>& layerStates, unsigned frameWidth, unsigned frameHeight, std::string& error)
|
||||||
|
{
|
||||||
|
if (!EnsureZeroTexture())
|
||||||
|
{
|
||||||
|
error = "Failed to initialize shader feedback fallback texture.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string> requiredLayerIds;
|
||||||
|
for (const RuntimeRenderState& state : layerStates)
|
||||||
|
{
|
||||||
|
if (!state.feedback.enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
requiredLayerIds.insert(state.layerId);
|
||||||
|
auto surfaceIt = mSurfacesByLayerId.find(state.layerId);
|
||||||
|
if (surfaceIt == mSurfacesByLayerId.end() ||
|
||||||
|
surfaceIt->second.width != frameWidth ||
|
||||||
|
surfaceIt->second.height != frameHeight)
|
||||||
|
{
|
||||||
|
Surface replacement;
|
||||||
|
if (!CreateSurface(replacement, frameWidth, frameHeight, error))
|
||||||
|
return false;
|
||||||
|
mSurfacesByLayerId[state.layerId] = std::move(replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = mSurfacesByLayerId.begin(); it != mSurfacesByLayerId.end();)
|
||||||
|
{
|
||||||
|
if (requiredLayerIds.find(it->first) == requiredLayerIds.end())
|
||||||
|
{
|
||||||
|
DestroySurface(it->second);
|
||||||
|
it = mSurfacesByLayerId.erase(it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderFeedbackBuffers::DestroyResources()
|
||||||
|
{
|
||||||
|
for (auto& entry : mSurfacesByLayerId)
|
||||||
|
DestroySurface(entry.second);
|
||||||
|
mSurfacesByLayerId.clear();
|
||||||
|
|
||||||
|
if (mZeroTexture != 0)
|
||||||
|
{
|
||||||
|
glDeleteTextures(1, &mZeroTexture);
|
||||||
|
mZeroTexture = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderFeedbackBuffers::ResetState()
|
||||||
|
{
|
||||||
|
for (auto& entry : mSurfacesByLayerId)
|
||||||
|
ClearSurfaceState(entry.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint ShaderFeedbackBuffers::ResolveReadTexture(const RuntimeRenderState& state) const
|
||||||
|
{
|
||||||
|
if (!state.feedback.enabled)
|
||||||
|
return mZeroTexture;
|
||||||
|
|
||||||
|
auto surfaceIt = mSurfacesByLayerId.find(state.layerId);
|
||||||
|
if (surfaceIt == mSurfacesByLayerId.end() || !surfaceIt->second.hasData)
|
||||||
|
return mZeroTexture;
|
||||||
|
|
||||||
|
return surfaceIt->second.slots[surfaceIt->second.readIndex].texture != 0
|
||||||
|
? surfaceIt->second.slots[surfaceIt->second.readIndex].texture
|
||||||
|
: mZeroTexture;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderFeedbackBuffers::FeedbackAvailable(const RuntimeRenderState& state) const
|
||||||
|
{
|
||||||
|
if (!state.feedback.enabled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto surfaceIt = mSurfacesByLayerId.find(state.layerId);
|
||||||
|
return surfaceIt != mSurfacesByLayerId.end() && surfaceIt->second.hasData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderFeedbackBuffers::CaptureFeedbackFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight)
|
||||||
|
{
|
||||||
|
auto surfaceIt = mSurfacesByLayerId.find(layerId);
|
||||||
|
if (surfaceIt == mSurfacesByLayerId.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Surface& surface = surfaceIt->second;
|
||||||
|
const unsigned writeIndex = 1u - surface.readIndex;
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebuffer);
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, surface.slots[writeIndex].framebuffer);
|
||||||
|
glBlitFramebuffer(0, 0, frameWidth, frameHeight, 0, 0, frameWidth, frameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||||
|
surface.pendingWrite = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderFeedbackBuffers::FinalizeFrame()
|
||||||
|
{
|
||||||
|
for (auto& entry : mSurfacesByLayerId)
|
||||||
|
{
|
||||||
|
Surface& surface = entry.second;
|
||||||
|
if (!surface.pendingWrite)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
surface.readIndex = 1u - surface.readIndex;
|
||||||
|
surface.hasData = true;
|
||||||
|
surface.pendingWrite = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderFeedbackBuffers::EnsureZeroTexture()
|
||||||
|
{
|
||||||
|
if (mZeroTexture != 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
glGenTextures(1, &mZeroTexture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, mZeroTexture);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
const float zeroPixel[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, 1, 1, 0, GL_RGBA, GL_FLOAT, zeroPixel);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
return mZeroTexture != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderFeedbackBuffers::CreateSurface(Surface& surface, unsigned frameWidth, unsigned frameHeight, std::string& error)
|
||||||
|
{
|
||||||
|
DestroySurface(surface);
|
||||||
|
|
||||||
|
surface.width = frameWidth;
|
||||||
|
surface.height = frameHeight;
|
||||||
|
for (Slot& slot : surface.slots)
|
||||||
|
{
|
||||||
|
glGenTextures(1, &slot.texture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, slot.texture);
|
||||||
|
ConfigureFeedbackTexture(frameWidth, frameHeight);
|
||||||
|
|
||||||
|
glGenFramebuffers(1, &slot.framebuffer);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, slot.framebuffer);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, slot.texture, 0);
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||||
|
{
|
||||||
|
error = "Failed to initialize a shader feedback framebuffer.";
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
DestroySurface(surface);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
ClearSurfaceState(surface);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderFeedbackBuffers::DestroySurface(Surface& surface)
|
||||||
|
{
|
||||||
|
for (Slot& slot : surface.slots)
|
||||||
|
{
|
||||||
|
if (slot.framebuffer != 0)
|
||||||
|
glDeleteFramebuffers(1, &slot.framebuffer);
|
||||||
|
if (slot.texture != 0)
|
||||||
|
glDeleteTextures(1, &slot.texture);
|
||||||
|
slot.framebuffer = 0;
|
||||||
|
slot.texture = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
surface.width = 0;
|
||||||
|
surface.height = 0;
|
||||||
|
surface.readIndex = 0;
|
||||||
|
surface.hasData = false;
|
||||||
|
surface.pendingWrite = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderFeedbackBuffers::ClearSurfaceState(Surface& surface)
|
||||||
|
{
|
||||||
|
surface.readIndex = 0;
|
||||||
|
surface.hasData = false;
|
||||||
|
surface.pendingWrite = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GLExtensions.h"
|
||||||
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class ShaderFeedbackBuffers
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Slot
|
||||||
|
{
|
||||||
|
GLuint texture = 0;
|
||||||
|
GLuint framebuffer = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Surface
|
||||||
|
{
|
||||||
|
Slot slots[2];
|
||||||
|
unsigned width = 0;
|
||||||
|
unsigned height = 0;
|
||||||
|
unsigned readIndex = 0;
|
||||||
|
bool hasData = false;
|
||||||
|
bool pendingWrite = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool EnsureResources(const std::vector<RuntimeRenderState>& layerStates, unsigned frameWidth, unsigned frameHeight, std::string& error);
|
||||||
|
void DestroyResources();
|
||||||
|
void ResetState();
|
||||||
|
GLuint ResolveReadTexture(const RuntimeRenderState& state) const;
|
||||||
|
bool FeedbackAvailable(const RuntimeRenderState& state) const;
|
||||||
|
void CaptureFeedbackFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight);
|
||||||
|
void FinalizeFrame();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool EnsureZeroTexture();
|
||||||
|
bool CreateSurface(Surface& surface, unsigned frameWidth, unsigned frameHeight, std::string& error);
|
||||||
|
void DestroySurface(Surface& surface);
|
||||||
|
void ClearSurfaceState(Surface& surface);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, Surface> mSurfacesByLayerId;
|
||||||
|
GLuint mZeroTexture = 0;
|
||||||
|
};
|
||||||
@@ -19,7 +19,8 @@ bool TemporalHistoryBuffers::ValidateTextureUnitBudget(const std::vector<Runtime
|
|||||||
++textTextureCount;
|
++textTextureCount;
|
||||||
}
|
}
|
||||||
const unsigned totalShaderTextures = static_cast<unsigned>(state.textureAssets.size()) + textTextureCount;
|
const unsigned totalShaderTextures = static_cast<unsigned>(state.textureAssets.size()) + textTextureCount;
|
||||||
const unsigned layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + totalShaderTextures;
|
const unsigned feedbackTextureCount = state.feedback.enabled ? 1u : 0u;
|
||||||
|
const unsigned layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + feedbackTextureCount + totalShaderTextures;
|
||||||
if (layerRequiredUnits > requiredUnits)
|
if (layerRequiredUnits > requiredUnits)
|
||||||
requiredUnits = layerRequiredUnits;
|
requiredUnits = layerRequiredUnits;
|
||||||
}
|
}
|
||||||
@@ -212,6 +213,29 @@ void TemporalHistoryBuffers::BindSamplers(const RuntimeRenderState& state, GLuin
|
|||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<GLuint> TemporalHistoryBuffers::ResolveSourceHistoryTextures(GLuint fallbackTexture, unsigned historyCap) const
|
||||||
|
{
|
||||||
|
std::vector<GLuint> textures;
|
||||||
|
textures.reserve(historyCap);
|
||||||
|
for (unsigned index = 0; index < historyCap; ++index)
|
||||||
|
textures.push_back(ResolveTexture(sourceHistoryRing, fallbackTexture, index));
|
||||||
|
return textures;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GLuint> TemporalHistoryBuffers::ResolveTemporalHistoryTextures(const RuntimeRenderState& state, GLuint fallbackTexture, unsigned historyCap) const
|
||||||
|
{
|
||||||
|
const Ring* temporalRing = nullptr;
|
||||||
|
auto it = preLayerHistoryByLayerId.find(state.layerId);
|
||||||
|
if (it != preLayerHistoryByLayerId.end())
|
||||||
|
temporalRing = &it->second;
|
||||||
|
|
||||||
|
std::vector<GLuint> textures;
|
||||||
|
textures.reserve(historyCap);
|
||||||
|
for (unsigned index = 0; index < historyCap; ++index)
|
||||||
|
textures.push_back(temporalRing ? ResolveTexture(*temporalRing, fallbackTexture, index) : fallbackTexture);
|
||||||
|
return textures;
|
||||||
|
}
|
||||||
|
|
||||||
GLuint TemporalHistoryBuffers::ResolveTexture(const Ring& ring, GLuint fallbackTexture, std::size_t framesAgo) const
|
GLuint TemporalHistoryBuffers::ResolveTexture(const Ring& ring, GLuint fallbackTexture, std::size_t framesAgo) const
|
||||||
{
|
{
|
||||||
if (ring.filledCount == 0 || ring.slots.empty())
|
if (ring.filledCount == 0 || ring.slots.empty())
|
||||||
@@ -40,6 +40,8 @@ public:
|
|||||||
void PushSourceFramebuffer(GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight);
|
void PushSourceFramebuffer(GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight);
|
||||||
void PushPreLayerFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight);
|
void PushPreLayerFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight);
|
||||||
void BindSamplers(const RuntimeRenderState& state, GLuint currentSourceTexture, unsigned historyCap);
|
void BindSamplers(const RuntimeRenderState& state, GLuint currentSourceTexture, unsigned historyCap);
|
||||||
|
std::vector<GLuint> ResolveSourceHistoryTextures(GLuint fallbackTexture, unsigned historyCap) const;
|
||||||
|
std::vector<GLuint> ResolveTemporalHistoryTextures(const RuntimeRenderState& state, GLuint fallbackTexture, unsigned historyCap) const;
|
||||||
GLuint ResolveTexture(const Ring& ring, GLuint fallbackTexture, std::size_t framesAgo) const;
|
GLuint ResolveTexture(const Ring& ring, GLuint fallbackTexture, std::size_t framesAgo) const;
|
||||||
unsigned SourceAvailableCount() const;
|
unsigned SourceAvailableCount() const;
|
||||||
unsigned AvailableCountForLayer(const std::string& layerId) const;
|
unsigned AvailableCountForLayer(const std::string& layerId) const;
|
||||||
@@ -62,6 +62,8 @@ PFNGLGENBUFFERSPROC glGenBuffers;
|
|||||||
PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
||||||
PFNGLBINDBUFFERPROC glBindBuffer;
|
PFNGLBINDBUFFERPROC glBindBuffer;
|
||||||
PFNGLBUFFERDATAPROC glBufferData;
|
PFNGLBUFFERDATAPROC glBufferData;
|
||||||
|
PFNGLMAPBUFFERPROC glMapBuffer;
|
||||||
|
PFNGLUNMAPBUFFERPROC glUnmapBuffer;
|
||||||
PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
||||||
PFNGLBINDBUFFERBASEPROC glBindBufferBase;
|
PFNGLBINDBUFFERBASEPROC glBindBufferBase;
|
||||||
PFNGLACTIVETEXTUREPROC glActiveTexture;
|
PFNGLACTIVETEXTUREPROC glActiveTexture;
|
||||||
@@ -131,6 +133,8 @@ bool ResolveGLExtensions()
|
|||||||
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC) wglGetProcAddress("glDeleteBuffers");
|
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC) wglGetProcAddress("glDeleteBuffers");
|
||||||
glBindBuffer = (PFNGLBINDBUFFERPROC) wglGetProcAddress("glBindBuffer");
|
glBindBuffer = (PFNGLBINDBUFFERPROC) wglGetProcAddress("glBindBuffer");
|
||||||
glBufferData = (PFNGLBUFFERDATAPROC) wglGetProcAddress("glBufferData");
|
glBufferData = (PFNGLBUFFERDATAPROC) wglGetProcAddress("glBufferData");
|
||||||
|
glMapBuffer = (PFNGLMAPBUFFERPROC) wglGetProcAddress("glMapBuffer");
|
||||||
|
glUnmapBuffer = (PFNGLUNMAPBUFFERPROC) wglGetProcAddress("glUnmapBuffer");
|
||||||
glBufferSubData = (PFNGLBUFFERSUBDATAPROC) wglGetProcAddress("glBufferSubData");
|
glBufferSubData = (PFNGLBUFFERSUBDATAPROC) wglGetProcAddress("glBufferSubData");
|
||||||
glBindBufferBase = (PFNGLBINDBUFFERBASEPROC) wglGetProcAddress("glBindBufferBase");
|
glBindBufferBase = (PFNGLBINDBUFFERBASEPROC) wglGetProcAddress("glBindBufferBase");
|
||||||
glActiveTexture = (PFNGLACTIVETEXTUREPROC) wglGetProcAddress("glActiveTexture");
|
glActiveTexture = (PFNGLACTIVETEXTUREPROC) wglGetProcAddress("glActiveTexture");
|
||||||
@@ -176,6 +180,8 @@ bool ResolveGLExtensions()
|
|||||||
&& glDeleteBuffers
|
&& glDeleteBuffers
|
||||||
&& glBindBuffer
|
&& glBindBuffer
|
||||||
&& glBufferData
|
&& glBufferData
|
||||||
|
&& glMapBuffer
|
||||||
|
&& glUnmapBuffer
|
||||||
&& glBufferSubData
|
&& glBufferSubData
|
||||||
&& glBindBufferBase
|
&& glBindBufferBase
|
||||||
&& glActiveTexture
|
&& glActiveTexture
|
||||||
@@ -89,6 +89,11 @@
|
|||||||
#define GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD 0x9160
|
#define GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD 0x9160
|
||||||
#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117
|
#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117
|
||||||
#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001
|
#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001
|
||||||
|
#define GL_ALREADY_SIGNALED 0x911A
|
||||||
|
#define GL_TIMEOUT_EXPIRED 0x911B
|
||||||
|
#define GL_CONDITION_SATISFIED 0x911C
|
||||||
|
#define GL_WAIT_FAILED 0x911D
|
||||||
|
#define GL_READ_ONLY 0x88B8
|
||||||
|
|
||||||
typedef struct __GLsync *GLsync;
|
typedef struct __GLsync *GLsync;
|
||||||
typedef unsigned __int64 GLuint64;
|
typedef unsigned __int64 GLuint64;
|
||||||
@@ -100,6 +105,8 @@ typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer);
|
|||||||
typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);
|
typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);
|
||||||
typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);
|
typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);
|
||||||
typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);
|
typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);
|
||||||
|
typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access);
|
||||||
|
typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target);
|
||||||
typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);
|
typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);
|
||||||
typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader);
|
typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader);
|
||||||
typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void);
|
typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void);
|
||||||
@@ -159,6 +166,8 @@ extern PFNGLGENBUFFERSPROC glGenBuffers;
|
|||||||
extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
||||||
extern PFNGLBINDBUFFERPROC glBindBuffer;
|
extern PFNGLBINDBUFFERPROC glBindBuffer;
|
||||||
extern PFNGLBUFFERDATAPROC glBufferData;
|
extern PFNGLBUFFERDATAPROC glBufferData;
|
||||||
|
extern PFNGLMAPBUFFERPROC glMapBuffer;
|
||||||
|
extern PFNGLUNMAPBUFFERPROC glUnmapBuffer;
|
||||||
extern PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
extern PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
||||||
extern PFNGLBINDBUFFERBASEPROC glBindBufferBase;
|
extern PFNGLBINDBUFFERBASEPROC glBindBufferBase;
|
||||||
extern PFNGLACTIVETEXTUREPROC glActiveTexture;
|
extern PFNGLACTIVETEXTUREPROC glActiveTexture;
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
#include <gl/gl.h>
|
#include <gl/gl.h>
|
||||||
|
|
||||||
|
constexpr GLuint kLayerInputTextureUnit = 0;
|
||||||
constexpr GLuint kDecodedVideoTextureUnit = 1;
|
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 = 8;
|
constexpr unsigned kPrerollFrameCount = 12;
|
||||||
@@ -12,15 +12,6 @@ namespace
|
|||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureDisplayFrameTexture(unsigned width, unsigned height)
|
|
||||||
{
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLRenderer::InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error)
|
bool OpenGLRenderer::InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error)
|
||||||
@@ -35,80 +26,32 @@ bool OpenGLRenderer::InitializeResources(unsigned inputFrameWidth, unsigned inpu
|
|||||||
ConfigureByteFrameTexture(captureTextureWidth, inputFrameHeight);
|
ConfigureByteFrameTexture(captureTextureWidth, inputFrameHeight);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
glGenTextures(1, &mDecodedTexture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, mDecodedTexture);
|
|
||||||
ConfigureDisplayFrameTexture(inputFrameWidth, inputFrameHeight);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
glGenTextures(1, &mLayerTempTexture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, mLayerTempTexture);
|
|
||||||
ConfigureDisplayFrameTexture(inputFrameWidth, inputFrameHeight);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
glGenFramebuffers(1, &mDecodeFrameBuf);
|
|
||||||
glGenFramebuffers(1, &mLayerTempFrameBuf);
|
|
||||||
glGenFramebuffers(1, &mIdFrameBuf);
|
|
||||||
glGenFramebuffers(1, &mOutputFrameBuf);
|
|
||||||
glGenFramebuffers(1, &mOutputPackFrameBuf);
|
|
||||||
glGenRenderbuffers(1, &mIdColorBuf);
|
glGenRenderbuffers(1, &mIdColorBuf);
|
||||||
glGenRenderbuffers(1, &mIdDepthBuf);
|
glGenRenderbuffers(1, &mIdDepthBuf);
|
||||||
glGenVertexArrays(1, &mFullscreenVAO);
|
glGenVertexArrays(1, &mFullscreenVAO);
|
||||||
glGenBuffers(1, &mGlobalParamsUBO);
|
glGenBuffers(1, &mGlobalParamsUBO);
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mDecodeFrameBuf);
|
if (!mRenderTargets.Create(RenderTargetId::Decoded, inputFrameWidth, inputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "decode", error))
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mDecodedTexture, 0);
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
||||||
{
|
|
||||||
error = "Cannot initialize decode framebuffer.";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
if (!mRenderTargets.Create(RenderTargetId::LayerTemp, inputFrameWidth, inputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "layer", error))
|
||||||
|
return false;
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mLayerTempFrameBuf);
|
if (!mRenderTargets.Create(RenderTargetId::Composite, inputFrameWidth, inputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "composite", error))
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mLayerTempTexture, 0);
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
||||||
{
|
|
||||||
error = "Cannot initialize layer framebuffer.";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
|
|
||||||
glGenTextures(1, &mFBOTexture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, mFBOTexture);
|
|
||||||
ConfigureDisplayFrameTexture(inputFrameWidth, inputFrameHeight);
|
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, CompositeFramebuffer());
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, mIdDepthBuf);
|
glBindRenderbuffer(GL_RENDERBUFFER, mIdDepthBuf);
|
||||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, inputFrameWidth, inputFrameHeight);
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, inputFrameWidth, inputFrameHeight);
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, mIdDepthBuf);
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, mIdDepthBuf);
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFBOTexture, 0);
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||||
{
|
{
|
||||||
error = "Cannot initialize framebuffer.";
|
error = "Cannot initialize framebuffer.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
glGenTextures(1, &mOutputTexture);
|
if (!mRenderTargets.Create(RenderTargetId::Output, outputFrameWidth, outputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "output", error))
|
||||||
glBindTexture(GL_TEXTURE_2D, mOutputTexture);
|
|
||||||
ConfigureDisplayFrameTexture(outputFrameWidth, outputFrameHeight);
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mOutputFrameBuf);
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mOutputTexture, 0);
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
||||||
{
|
|
||||||
error = "Cannot initialize output framebuffer.";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
if (!mRenderTargets.Create(RenderTargetId::OutputPack, outputPackTextureWidth, outputFrameHeight, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, "output pack", error))
|
||||||
|
|
||||||
glGenTextures(1, &mOutputPackTexture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, mOutputPackTexture);
|
|
||||||
ConfigureByteFrameTexture(outputPackTextureWidth, outputFrameHeight);
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mOutputPackFrameBuf);
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mOutputPackTexture, 0);
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
||||||
{
|
|
||||||
error = "Cannot initialize output pack framebuffer.";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||||
@@ -120,6 +63,7 @@ bool OpenGLRenderer::InitializeResources(unsigned inputFrameWidth, unsigned inpu
|
|||||||
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO);
|
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO);
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
||||||
|
|
||||||
|
mResourcesInitialized = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +72,9 @@ void OpenGLRenderer::SetDecodeShaderProgram(GLuint program, GLuint vertexShader,
|
|||||||
mDecodeProgram = program;
|
mDecodeProgram = program;
|
||||||
mDecodeVertexShader = vertexShader;
|
mDecodeVertexShader = vertexShader;
|
||||||
mDecodeFragmentShader = fragmentShader;
|
mDecodeFragmentShader = fragmentShader;
|
||||||
|
mDecodePackedResolutionLocation = program != 0 ? glGetUniformLocation(program, "uPackedVideoResolution") : -1;
|
||||||
|
mDecodeDecodedResolutionLocation = program != 0 ? glGetUniformLocation(program, "uDecodedVideoResolution") : -1;
|
||||||
|
mDecodeInputPixelFormatLocation = program != 0 ? glGetUniformLocation(program, "uInputPixelFormat") : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLRenderer::SetOutputPackShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader)
|
void OpenGLRenderer::SetOutputPackShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader)
|
||||||
@@ -135,6 +82,14 @@ void OpenGLRenderer::SetOutputPackShaderProgram(GLuint program, GLuint vertexSha
|
|||||||
mOutputPackProgram = program;
|
mOutputPackProgram = program;
|
||||||
mOutputPackVertexShader = vertexShader;
|
mOutputPackVertexShader = vertexShader;
|
||||||
mOutputPackFragmentShader = fragmentShader;
|
mOutputPackFragmentShader = fragmentShader;
|
||||||
|
mOutputPackResolutionLocation = program != 0 ? glGetUniformLocation(program, "uOutputVideoResolution") : -1;
|
||||||
|
mOutputPackActiveWordsLocation = program != 0 ? glGetUniformLocation(program, "uActiveV210Words") : -1;
|
||||||
|
mOutputPackFormatLocation = program != 0 ? glGetUniformLocation(program, "uOutputPackFormat") : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenGLRenderer::ReserveTemporaryRenderTargets(std::size_t count, unsigned width, unsigned height, std::string& error)
|
||||||
|
{
|
||||||
|
return mRenderTargets.ReserveTemporaryTargets(count, width, height, GL_RGBA16F, GL_RGBA, GL_FLOAT, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLRenderer::ResizeView(int width, int height)
|
void OpenGLRenderer::ResizeView(int width, int height)
|
||||||
@@ -169,7 +124,7 @@ void OpenGLRenderer::PresentToWindow(HDC hdc, unsigned outputFrameWidth, unsigne
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mOutputFrameBuf);
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, OutputFramebuffer());
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
glDisable(GL_SCISSOR_TEST);
|
glDisable(GL_SCISSOR_TEST);
|
||||||
glViewport(0, 0, mViewWidth, mViewHeight);
|
glViewport(0, 0, mViewWidth, mViewHeight);
|
||||||
@@ -186,54 +141,27 @@ void OpenGLRenderer::DestroyResources()
|
|||||||
glDeleteVertexArrays(1, &mFullscreenVAO);
|
glDeleteVertexArrays(1, &mFullscreenVAO);
|
||||||
if (mGlobalParamsUBO != 0)
|
if (mGlobalParamsUBO != 0)
|
||||||
glDeleteBuffers(1, &mGlobalParamsUBO);
|
glDeleteBuffers(1, &mGlobalParamsUBO);
|
||||||
if (mDecodeFrameBuf != 0)
|
|
||||||
glDeleteFramebuffers(1, &mDecodeFrameBuf);
|
|
||||||
if (mLayerTempFrameBuf != 0)
|
|
||||||
glDeleteFramebuffers(1, &mLayerTempFrameBuf);
|
|
||||||
if (mIdFrameBuf != 0)
|
|
||||||
glDeleteFramebuffers(1, &mIdFrameBuf);
|
|
||||||
if (mOutputFrameBuf != 0)
|
|
||||||
glDeleteFramebuffers(1, &mOutputFrameBuf);
|
|
||||||
if (mOutputPackFrameBuf != 0)
|
|
||||||
glDeleteFramebuffers(1, &mOutputPackFrameBuf);
|
|
||||||
if (mIdColorBuf != 0)
|
if (mIdColorBuf != 0)
|
||||||
glDeleteRenderbuffers(1, &mIdColorBuf);
|
glDeleteRenderbuffers(1, &mIdColorBuf);
|
||||||
if (mIdDepthBuf != 0)
|
if (mIdDepthBuf != 0)
|
||||||
glDeleteRenderbuffers(1, &mIdDepthBuf);
|
glDeleteRenderbuffers(1, &mIdDepthBuf);
|
||||||
if (mCaptureTexture != 0)
|
if (mCaptureTexture != 0)
|
||||||
glDeleteTextures(1, &mCaptureTexture);
|
glDeleteTextures(1, &mCaptureTexture);
|
||||||
if (mDecodedTexture != 0)
|
|
||||||
glDeleteTextures(1, &mDecodedTexture);
|
|
||||||
if (mLayerTempTexture != 0)
|
|
||||||
glDeleteTextures(1, &mLayerTempTexture);
|
|
||||||
if (mFBOTexture != 0)
|
|
||||||
glDeleteTextures(1, &mFBOTexture);
|
|
||||||
if (mOutputTexture != 0)
|
|
||||||
glDeleteTextures(1, &mOutputTexture);
|
|
||||||
if (mOutputPackTexture != 0)
|
|
||||||
glDeleteTextures(1, &mOutputPackTexture);
|
|
||||||
if (mTextureUploadBuffer != 0)
|
if (mTextureUploadBuffer != 0)
|
||||||
glDeleteBuffers(1, &mTextureUploadBuffer);
|
glDeleteBuffers(1, &mTextureUploadBuffer);
|
||||||
|
mRenderTargets.Destroy();
|
||||||
|
|
||||||
mFullscreenVAO = 0;
|
mFullscreenVAO = 0;
|
||||||
mGlobalParamsUBO = 0;
|
mGlobalParamsUBO = 0;
|
||||||
mDecodeFrameBuf = 0;
|
|
||||||
mLayerTempFrameBuf = 0;
|
|
||||||
mIdFrameBuf = 0;
|
|
||||||
mOutputFrameBuf = 0;
|
|
||||||
mOutputPackFrameBuf = 0;
|
|
||||||
mIdColorBuf = 0;
|
mIdColorBuf = 0;
|
||||||
mIdDepthBuf = 0;
|
mIdDepthBuf = 0;
|
||||||
mCaptureTexture = 0;
|
mCaptureTexture = 0;
|
||||||
mDecodedTexture = 0;
|
|
||||||
mLayerTempTexture = 0;
|
|
||||||
mFBOTexture = 0;
|
|
||||||
mOutputTexture = 0;
|
|
||||||
mOutputPackTexture = 0;
|
|
||||||
mTextureUploadBuffer = 0;
|
mTextureUploadBuffer = 0;
|
||||||
mGlobalParamsUBOSize = 0;
|
mGlobalParamsUBOSize = 0;
|
||||||
|
mResourcesInitialized = false;
|
||||||
|
|
||||||
mTemporalHistory.DestroyResources();
|
mTemporalHistory.DestroyResources();
|
||||||
|
mFeedbackBuffers.DestroyResources();
|
||||||
DestroyLayerPrograms();
|
DestroyLayerPrograms();
|
||||||
DestroyDecodeShaderProgram();
|
DestroyDecodeShaderProgram();
|
||||||
DestroyOutputPackShaderProgram();
|
DestroyOutputPackShaderProgram();
|
||||||
@@ -241,43 +169,47 @@ void OpenGLRenderer::DestroyResources()
|
|||||||
|
|
||||||
void OpenGLRenderer::DestroySingleLayerProgram(LayerProgram& layerProgram)
|
void OpenGLRenderer::DestroySingleLayerProgram(LayerProgram& layerProgram)
|
||||||
{
|
{
|
||||||
for (LayerProgram::TextureBinding& binding : layerProgram.textureBindings)
|
for (LayerProgram::PassProgram& passProgram : layerProgram.passes)
|
||||||
{
|
{
|
||||||
if (binding.texture != 0)
|
for (LayerProgram::TextureBinding& binding : passProgram.textureBindings)
|
||||||
{
|
{
|
||||||
glDeleteTextures(1, &binding.texture);
|
if (binding.texture != 0)
|
||||||
binding.texture = 0;
|
{
|
||||||
|
glDeleteTextures(1, &binding.texture);
|
||||||
|
binding.texture = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
passProgram.textureBindings.clear();
|
||||||
|
|
||||||
|
for (LayerProgram::TextBinding& binding : passProgram.textBindings)
|
||||||
|
{
|
||||||
|
if (binding.texture != 0)
|
||||||
|
{
|
||||||
|
glDeleteTextures(1, &binding.texture);
|
||||||
|
binding.texture = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
passProgram.textBindings.clear();
|
||||||
|
|
||||||
|
if (passProgram.program != 0)
|
||||||
|
{
|
||||||
|
glDeleteProgram(passProgram.program);
|
||||||
|
passProgram.program = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passProgram.fragmentShader != 0)
|
||||||
|
{
|
||||||
|
glDeleteShader(passProgram.fragmentShader);
|
||||||
|
passProgram.fragmentShader = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passProgram.vertexShader != 0)
|
||||||
|
{
|
||||||
|
glDeleteShader(passProgram.vertexShader);
|
||||||
|
passProgram.vertexShader = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
layerProgram.textureBindings.clear();
|
layerProgram.passes.clear();
|
||||||
|
|
||||||
for (LayerProgram::TextBinding& binding : layerProgram.textBindings)
|
|
||||||
{
|
|
||||||
if (binding.texture != 0)
|
|
||||||
{
|
|
||||||
glDeleteTextures(1, &binding.texture);
|
|
||||||
binding.texture = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
layerProgram.textBindings.clear();
|
|
||||||
|
|
||||||
if (layerProgram.program != 0)
|
|
||||||
{
|
|
||||||
glDeleteProgram(layerProgram.program);
|
|
||||||
layerProgram.program = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layerProgram.fragmentShader != 0)
|
|
||||||
{
|
|
||||||
glDeleteShader(layerProgram.fragmentShader);
|
|
||||||
layerProgram.fragmentShader = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layerProgram.vertexShader != 0)
|
|
||||||
{
|
|
||||||
glDeleteShader(layerProgram.vertexShader);
|
|
||||||
layerProgram.vertexShader = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLRenderer::DestroyLayerPrograms()
|
void OpenGLRenderer::DestroyLayerPrograms()
|
||||||
@@ -294,6 +226,9 @@ void OpenGLRenderer::DestroyDecodeShaderProgram()
|
|||||||
glDeleteProgram(mDecodeProgram);
|
glDeleteProgram(mDecodeProgram);
|
||||||
mDecodeProgram = 0;
|
mDecodeProgram = 0;
|
||||||
}
|
}
|
||||||
|
mDecodePackedResolutionLocation = -1;
|
||||||
|
mDecodeDecodedResolutionLocation = -1;
|
||||||
|
mDecodeInputPixelFormatLocation = -1;
|
||||||
|
|
||||||
if (mDecodeFragmentShader != 0)
|
if (mDecodeFragmentShader != 0)
|
||||||
{
|
{
|
||||||
@@ -315,6 +250,9 @@ void OpenGLRenderer::DestroyOutputPackShaderProgram()
|
|||||||
glDeleteProgram(mOutputPackProgram);
|
glDeleteProgram(mOutputPackProgram);
|
||||||
mOutputPackProgram = 0;
|
mOutputPackProgram = 0;
|
||||||
}
|
}
|
||||||
|
mOutputPackResolutionLocation = -1;
|
||||||
|
mOutputPackActiveWordsLocation = -1;
|
||||||
|
mOutputPackFormatLocation = -1;
|
||||||
|
|
||||||
if (mOutputPackFragmentShader != 0)
|
if (mOutputPackFragmentShader != 0)
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "GLExtensions.h"
|
#include "GLExtensions.h"
|
||||||
|
#include "RenderTargetPool.h"
|
||||||
|
#include "ShaderFeedbackBuffers.h"
|
||||||
#include "ShaderTypes.h"
|
#include "ShaderTypes.h"
|
||||||
#include "TemporalHistoryBuffers.h"
|
#include "TemporalHistoryBuffers.h"
|
||||||
|
|
||||||
@@ -36,37 +38,58 @@ public:
|
|||||||
|
|
||||||
std::string layerId;
|
std::string layerId;
|
||||||
std::string shaderId;
|
std::string shaderId;
|
||||||
GLuint shaderTextureBase = 0;
|
|
||||||
GLuint program = 0;
|
struct PassProgram
|
||||||
GLuint vertexShader = 0;
|
{
|
||||||
GLuint fragmentShader = 0;
|
std::string passId;
|
||||||
std::vector<TextureBinding> textureBindings;
|
std::vector<std::string> inputNames;
|
||||||
std::vector<TextBinding> textBindings;
|
std::string outputName;
|
||||||
|
GLuint shaderTextureBase = 0;
|
||||||
|
GLuint program = 0;
|
||||||
|
GLuint vertexShader = 0;
|
||||||
|
GLuint fragmentShader = 0;
|
||||||
|
std::vector<TextureBinding> textureBindings;
|
||||||
|
std::vector<TextBinding> textBindings;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<PassProgram> passes;
|
||||||
};
|
};
|
||||||
|
|
||||||
GLuint CaptureTexture() const { return mCaptureTexture; }
|
GLuint CaptureTexture() const { return mCaptureTexture; }
|
||||||
GLuint DecodedTexture() const { return mDecodedTexture; }
|
GLuint DecodedTexture() const { return mRenderTargets.Texture(RenderTargetId::Decoded); }
|
||||||
GLuint LayerTempTexture() const { return mLayerTempTexture; }
|
GLuint LayerTempTexture() const { return mRenderTargets.Texture(RenderTargetId::LayerTemp); }
|
||||||
GLuint CompositeTexture() const { return mFBOTexture; }
|
GLuint CompositeTexture() const { return mRenderTargets.Texture(RenderTargetId::Composite); }
|
||||||
GLuint OutputTexture() const { return mOutputTexture; }
|
GLuint OutputTexture() const { return mRenderTargets.Texture(RenderTargetId::Output); }
|
||||||
GLuint OutputPackTexture() const { return mOutputPackTexture; }
|
GLuint OutputPackTexture() const { return mRenderTargets.Texture(RenderTargetId::OutputPack); }
|
||||||
GLuint TextureUploadBuffer() const { return mTextureUploadBuffer; }
|
GLuint TextureUploadBuffer() const { return mTextureUploadBuffer; }
|
||||||
GLuint DecodeFramebuffer() const { return mDecodeFrameBuf; }
|
GLuint DecodeFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::Decoded); }
|
||||||
GLuint LayerTempFramebuffer() const { return mLayerTempFrameBuf; }
|
GLuint LayerTempFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::LayerTemp); }
|
||||||
GLuint CompositeFramebuffer() const { return mIdFrameBuf; }
|
GLuint CompositeFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::Composite); }
|
||||||
GLuint OutputFramebuffer() const { return mOutputFrameBuf; }
|
GLuint OutputFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::Output); }
|
||||||
GLuint OutputPackFramebuffer() const { return mOutputPackFrameBuf; }
|
GLuint OutputPackFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::OutputPack); }
|
||||||
GLuint FullscreenVertexArray() const { return mFullscreenVAO; }
|
GLuint FullscreenVertexArray() const { return mFullscreenVAO; }
|
||||||
GLuint GlobalParamsUBO() const { return mGlobalParamsUBO; }
|
GLuint GlobalParamsUBO() const { return mGlobalParamsUBO; }
|
||||||
GLuint DecodeProgram() const { return mDecodeProgram; }
|
GLuint DecodeProgram() const { return mDecodeProgram; }
|
||||||
GLuint OutputPackProgram() const { return mOutputPackProgram; }
|
GLuint OutputPackProgram() const { return mOutputPackProgram; }
|
||||||
|
GLint DecodePackedResolutionLocation() const { return mDecodePackedResolutionLocation; }
|
||||||
|
GLint DecodeDecodedResolutionLocation() const { return mDecodeDecodedResolutionLocation; }
|
||||||
|
GLint DecodeInputPixelFormatLocation() const { return mDecodeInputPixelFormatLocation; }
|
||||||
|
GLint OutputPackResolutionLocation() const { return mOutputPackResolutionLocation; }
|
||||||
|
GLint OutputPackActiveWordsLocation() const { return mOutputPackActiveWordsLocation; }
|
||||||
|
GLint OutputPackFormatLocation() const { return mOutputPackFormatLocation; }
|
||||||
GLsizeiptr GlobalParamsUBOSize() const { return mGlobalParamsUBOSize; }
|
GLsizeiptr GlobalParamsUBOSize() const { return mGlobalParamsUBOSize; }
|
||||||
void SetGlobalParamsUBOSize(GLsizeiptr size) { mGlobalParamsUBOSize = size; }
|
void SetGlobalParamsUBOSize(GLsizeiptr size) { mGlobalParamsUBOSize = size; }
|
||||||
|
bool ResourcesInitialized() const { return mResourcesInitialized; }
|
||||||
void ReplaceLayerPrograms(std::vector<LayerProgram>& newPrograms) { mLayerPrograms.swap(newPrograms); }
|
void ReplaceLayerPrograms(std::vector<LayerProgram>& newPrograms) { mLayerPrograms.swap(newPrograms); }
|
||||||
std::vector<LayerProgram>& LayerPrograms() { return mLayerPrograms; }
|
std::vector<LayerProgram>& LayerPrograms() { return mLayerPrograms; }
|
||||||
const std::vector<LayerProgram>& LayerPrograms() const { return mLayerPrograms; }
|
const std::vector<LayerProgram>& LayerPrograms() const { return mLayerPrograms; }
|
||||||
|
bool ReserveTemporaryRenderTargets(std::size_t count, unsigned width, unsigned height, std::string& error);
|
||||||
|
const RenderTarget& TemporaryRenderTarget(std::size_t index) const { return mRenderTargets.TemporaryTarget(index); }
|
||||||
|
std::size_t TemporaryRenderTargetCount() const { return mRenderTargets.TemporaryTargetCount(); }
|
||||||
TemporalHistoryBuffers& TemporalHistory() { return mTemporalHistory; }
|
TemporalHistoryBuffers& TemporalHistory() { return mTemporalHistory; }
|
||||||
const TemporalHistoryBuffers& TemporalHistory() const { return mTemporalHistory; }
|
const TemporalHistoryBuffers& TemporalHistory() const { return mTemporalHistory; }
|
||||||
|
ShaderFeedbackBuffers& FeedbackBuffers() { return mFeedbackBuffers; }
|
||||||
|
const ShaderFeedbackBuffers& FeedbackBuffers() const { return mFeedbackBuffers; }
|
||||||
void SetDecodeShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader);
|
void SetDecodeShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader);
|
||||||
void SetOutputPackShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader);
|
void SetOutputPackShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader);
|
||||||
bool InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error);
|
bool InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error);
|
||||||
@@ -80,17 +103,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
GLuint mCaptureTexture = 0;
|
GLuint mCaptureTexture = 0;
|
||||||
GLuint mDecodedTexture = 0;
|
|
||||||
GLuint mLayerTempTexture = 0;
|
|
||||||
GLuint mFBOTexture = 0;
|
|
||||||
GLuint mOutputTexture = 0;
|
|
||||||
GLuint mOutputPackTexture = 0;
|
|
||||||
GLuint mTextureUploadBuffer = 0;
|
GLuint mTextureUploadBuffer = 0;
|
||||||
GLuint mDecodeFrameBuf = 0;
|
|
||||||
GLuint mLayerTempFrameBuf = 0;
|
|
||||||
GLuint mIdFrameBuf = 0;
|
|
||||||
GLuint mOutputFrameBuf = 0;
|
|
||||||
GLuint mOutputPackFrameBuf = 0;
|
|
||||||
GLuint mIdColorBuf = 0;
|
GLuint mIdColorBuf = 0;
|
||||||
GLuint mIdDepthBuf = 0;
|
GLuint mIdDepthBuf = 0;
|
||||||
GLuint mFullscreenVAO = 0;
|
GLuint mFullscreenVAO = 0;
|
||||||
@@ -98,12 +111,21 @@ private:
|
|||||||
GLuint mDecodeProgram = 0;
|
GLuint mDecodeProgram = 0;
|
||||||
GLuint mDecodeVertexShader = 0;
|
GLuint mDecodeVertexShader = 0;
|
||||||
GLuint mDecodeFragmentShader = 0;
|
GLuint mDecodeFragmentShader = 0;
|
||||||
|
GLint mDecodePackedResolutionLocation = -1;
|
||||||
|
GLint mDecodeDecodedResolutionLocation = -1;
|
||||||
|
GLint mDecodeInputPixelFormatLocation = -1;
|
||||||
GLuint mOutputPackProgram = 0;
|
GLuint mOutputPackProgram = 0;
|
||||||
GLuint mOutputPackVertexShader = 0;
|
GLuint mOutputPackVertexShader = 0;
|
||||||
GLuint mOutputPackFragmentShader = 0;
|
GLuint mOutputPackFragmentShader = 0;
|
||||||
|
GLint mOutputPackResolutionLocation = -1;
|
||||||
|
GLint mOutputPackActiveWordsLocation = -1;
|
||||||
|
GLint mOutputPackFormatLocation = -1;
|
||||||
GLsizeiptr mGlobalParamsUBOSize = 0;
|
GLsizeiptr mGlobalParamsUBOSize = 0;
|
||||||
|
bool mResourcesInitialized = false;
|
||||||
int mViewWidth = 0;
|
int mViewWidth = 0;
|
||||||
int mViewHeight = 0;
|
int mViewHeight = 0;
|
||||||
std::vector<LayerProgram> mLayerPrograms;
|
std::vector<LayerProgram> mLayerPrograms;
|
||||||
|
RenderTargetPool mRenderTargets;
|
||||||
TemporalHistoryBuffers mTemporalHistory;
|
TemporalHistoryBuffers mTemporalHistory;
|
||||||
|
ShaderFeedbackBuffers mFeedbackBuffers;
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
#include "RenderTargetPool.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void ConfigureRenderTargetTexture(
|
||||||
|
unsigned width,
|
||||||
|
unsigned height,
|
||||||
|
GLenum internalFormat,
|
||||||
|
GLenum pixelFormat,
|
||||||
|
GLenum pixelType)
|
||||||
|
{
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, pixelFormat, pixelType, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderTargetPool::Create(
|
||||||
|
RenderTargetId id,
|
||||||
|
unsigned width,
|
||||||
|
unsigned height,
|
||||||
|
GLenum internalFormat,
|
||||||
|
GLenum pixelFormat,
|
||||||
|
GLenum pixelType,
|
||||||
|
const char* errorPrefix,
|
||||||
|
std::string& error)
|
||||||
|
{
|
||||||
|
RenderTarget& target = mTargets[TargetIndex(id)];
|
||||||
|
if (target.texture != 0 || target.framebuffer != 0)
|
||||||
|
{
|
||||||
|
error = std::string(errorPrefix) + " render target was already initialized.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glGenTextures(1, &target.texture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, target.texture);
|
||||||
|
ConfigureRenderTargetTexture(width, height, internalFormat, pixelFormat, pixelType);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
glGenFramebuffers(1, &target.framebuffer);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, target.framebuffer);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.texture, 0);
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||||
|
{
|
||||||
|
error = std::string("Cannot initialize ") + errorPrefix + " framebuffer.";
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
target.width = width;
|
||||||
|
target.height = height;
|
||||||
|
target.internalFormat = internalFormat;
|
||||||
|
target.pixelFormat = pixelFormat;
|
||||||
|
target.pixelType = pixelType;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderTargetPool::ReserveTemporaryTargets(
|
||||||
|
std::size_t count,
|
||||||
|
unsigned width,
|
||||||
|
unsigned height,
|
||||||
|
GLenum internalFormat,
|
||||||
|
GLenum pixelFormat,
|
||||||
|
GLenum pixelType,
|
||||||
|
std::string& error)
|
||||||
|
{
|
||||||
|
if (mTemporaryTargets.size() == count)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
DestroyTemporaryTargets();
|
||||||
|
|
||||||
|
mTemporaryTargets.resize(count);
|
||||||
|
for (std::size_t index = 0; index < mTemporaryTargets.size(); ++index)
|
||||||
|
{
|
||||||
|
RenderTarget& target = mTemporaryTargets[index];
|
||||||
|
glGenTextures(1, &target.texture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, target.texture);
|
||||||
|
ConfigureRenderTargetTexture(width, height, internalFormat, pixelFormat, pixelType);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
glGenFramebuffers(1, &target.framebuffer);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, target.framebuffer);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.texture, 0);
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||||
|
{
|
||||||
|
error = "Cannot initialize temporary render target.";
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.width = width;
|
||||||
|
target.height = height;
|
||||||
|
target.internalFormat = internalFormat;
|
||||||
|
target.pixelFormat = pixelFormat;
|
||||||
|
target.pixelType = pixelType;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderTargetPool::DestroyTemporaryTargets()
|
||||||
|
{
|
||||||
|
for (RenderTarget& target : mTemporaryTargets)
|
||||||
|
{
|
||||||
|
if (target.framebuffer != 0)
|
||||||
|
glDeleteFramebuffers(1, &target.framebuffer);
|
||||||
|
if (target.texture != 0)
|
||||||
|
glDeleteTextures(1, &target.texture);
|
||||||
|
}
|
||||||
|
mTemporaryTargets.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderTargetPool::Destroy()
|
||||||
|
{
|
||||||
|
for (RenderTarget& target : mTargets)
|
||||||
|
{
|
||||||
|
if (target.framebuffer != 0)
|
||||||
|
glDeleteFramebuffers(1, &target.framebuffer);
|
||||||
|
if (target.texture != 0)
|
||||||
|
glDeleteTextures(1, &target.texture);
|
||||||
|
target = RenderTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
DestroyTemporaryTargets();
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderTarget& RenderTargetPool::Target(RenderTargetId id) const
|
||||||
|
{
|
||||||
|
return mTargets[TargetIndex(id)];
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GLExtensions.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
enum class RenderTargetId
|
||||||
|
{
|
||||||
|
Decoded,
|
||||||
|
LayerTemp,
|
||||||
|
Composite,
|
||||||
|
Output,
|
||||||
|
OutputPack,
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderTarget
|
||||||
|
{
|
||||||
|
GLuint texture = 0;
|
||||||
|
GLuint framebuffer = 0;
|
||||||
|
unsigned width = 0;
|
||||||
|
unsigned height = 0;
|
||||||
|
GLenum internalFormat = GL_RGBA8;
|
||||||
|
GLenum pixelFormat = GL_RGBA;
|
||||||
|
GLenum pixelType = GL_UNSIGNED_BYTE;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RenderTargetPool
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool Create(
|
||||||
|
RenderTargetId id,
|
||||||
|
unsigned width,
|
||||||
|
unsigned height,
|
||||||
|
GLenum internalFormat,
|
||||||
|
GLenum pixelFormat,
|
||||||
|
GLenum pixelType,
|
||||||
|
const char* errorPrefix,
|
||||||
|
std::string& error);
|
||||||
|
bool ReserveTemporaryTargets(
|
||||||
|
std::size_t count,
|
||||||
|
unsigned width,
|
||||||
|
unsigned height,
|
||||||
|
GLenum internalFormat,
|
||||||
|
GLenum pixelFormat,
|
||||||
|
GLenum pixelType,
|
||||||
|
std::string& error);
|
||||||
|
void DestroyTemporaryTargets();
|
||||||
|
void Destroy();
|
||||||
|
|
||||||
|
GLuint Texture(RenderTargetId id) const { return Target(id).texture; }
|
||||||
|
GLuint Framebuffer(RenderTargetId id) const { return Target(id).framebuffer; }
|
||||||
|
const RenderTarget& Target(RenderTargetId id) const;
|
||||||
|
const RenderTarget& TemporaryTarget(std::size_t index) const { return mTemporaryTargets[index]; }
|
||||||
|
std::size_t TemporaryTargetCount() const { return mTemporaryTargets.size(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::size_t TargetIndex(RenderTargetId id) { return static_cast<std::size_t>(id); }
|
||||||
|
|
||||||
|
std::array<RenderTarget, static_cast<std::size_t>(RenderTargetId::Count)> mTargets;
|
||||||
|
std::vector<RenderTarget> mTemporaryTargets;
|
||||||
|
};
|
||||||
@@ -90,12 +90,17 @@ const char* kOutputPackFragmentShaderSource =
|
|||||||
"layout(binding = 0) uniform sampler2D uOutputRgb;\n"
|
"layout(binding = 0) uniform sampler2D uOutputRgb;\n"
|
||||||
"uniform vec2 uOutputVideoResolution;\n"
|
"uniform vec2 uOutputVideoResolution;\n"
|
||||||
"uniform float uActiveV210Words;\n"
|
"uniform float uActiveV210Words;\n"
|
||||||
|
"uniform int uOutputPackFormat;\n"
|
||||||
"in vec2 vTexCoord;\n"
|
"in vec2 vTexCoord;\n"
|
||||||
"layout(location = 0) out vec4 fragColor;\n"
|
"layout(location = 0) out vec4 fragColor;\n"
|
||||||
"vec3 rgbAt(int x, int y)\n"
|
"vec4 rgbaAt(int x, int y)\n"
|
||||||
"{\n"
|
"{\n"
|
||||||
" ivec2 size = ivec2(max(uOutputVideoResolution, vec2(1.0, 1.0)));\n"
|
" ivec2 size = ivec2(max(uOutputVideoResolution, vec2(1.0, 1.0)));\n"
|
||||||
" return clamp(texelFetch(uOutputRgb, ivec2(clamp(x, 0, size.x - 1), clamp(y, 0, size.y - 1)), 0).rgb, vec3(0.0), vec3(1.0));\n"
|
" return clamp(texelFetch(uOutputRgb, ivec2(clamp(x, 0, size.x - 1), clamp(y, 0, size.y - 1)), 0), vec4(0.0), vec4(1.0));\n"
|
||||||
|
"}\n"
|
||||||
|
"vec3 rgbAt(int x, int y)\n"
|
||||||
|
"{\n"
|
||||||
|
" return rgbaAt(x, y).rgb;\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
"vec3 rgbToLegalYcbcr10(vec3 rgb)\n"
|
"vec3 rgbToLegalYcbcr10(vec3 rgb)\n"
|
||||||
"{\n"
|
"{\n"
|
||||||
@@ -112,9 +117,35 @@ const char* kOutputPackFragmentShaderSource =
|
|||||||
"{\n"
|
"{\n"
|
||||||
" return vec4(float(word & 255u), float((word >> 8) & 255u), float((word >> 16) & 255u), float((word >> 24) & 255u)) / 255.0;\n"
|
" return vec4(float(word & 255u), float((word >> 8) & 255u), float((word >> 16) & 255u), float((word >> 24) & 255u)) / 255.0;\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
|
"vec4 bigEndianWordToBytes(uint word)\n"
|
||||||
|
"{\n"
|
||||||
|
" return vec4(float((word >> 24) & 255u), float((word >> 16) & 255u), float((word >> 8) & 255u), float(word & 255u)) / 255.0;\n"
|
||||||
|
"}\n"
|
||||||
|
"vec4 packAy10Word(ivec2 outCoord)\n"
|
||||||
|
"{\n"
|
||||||
|
" ivec2 size = ivec2(max(uOutputVideoResolution, vec2(1.0, 1.0)));\n"
|
||||||
|
" if (outCoord.x >= size.x)\n"
|
||||||
|
" return vec4(0.0);\n"
|
||||||
|
" int pixelBase = (outCoord.x / 2) * 2;\n"
|
||||||
|
" int y = outCoord.y;\n"
|
||||||
|
" vec4 rgba0 = rgbaAt(pixelBase + 0, y);\n"
|
||||||
|
" vec4 rgba1 = rgbaAt(pixelBase + 1, y);\n"
|
||||||
|
" vec3 c0 = rgbToLegalYcbcr10(rgba0.rgb);\n"
|
||||||
|
" vec3 c1 = rgbToLegalYcbcr10(rgba1.rgb);\n"
|
||||||
|
" float chroma = (outCoord.x & 1) == 0 ? round((c0.y + c1.y) * 0.5) : round((c0.z + c1.z) * 0.5);\n"
|
||||||
|
" float alpha = round(clamp(((outCoord.x & 1) == 0 ? rgba0.a : rgba1.a), 0.0, 1.0) * 1023.0);\n"
|
||||||
|
" float luma = (outCoord.x & 1) == 0 ? c0.x : c1.x;\n"
|
||||||
|
" uint word = ((uint(luma) & 1023u) << 22) | ((uint(chroma) & 1023u) << 12) | ((uint(alpha) & 1023u) << 2);\n"
|
||||||
|
" return bigEndianWordToBytes(word);\n"
|
||||||
|
"}\n"
|
||||||
"void main()\n"
|
"void main()\n"
|
||||||
"{\n"
|
"{\n"
|
||||||
" ivec2 outCoord = ivec2(gl_FragCoord.xy);\n"
|
" ivec2 outCoord = ivec2(gl_FragCoord.xy);\n"
|
||||||
|
" if (uOutputPackFormat == 2)\n"
|
||||||
|
" {\n"
|
||||||
|
" fragColor = packAy10Word(outCoord);\n"
|
||||||
|
" return;\n"
|
||||||
|
" }\n"
|
||||||
" if (float(outCoord.x) >= uActiveV210Words)\n"
|
" if (float(outCoord.x) >= uActiveV210Words)\n"
|
||||||
" {\n"
|
" {\n"
|
||||||
" fragColor = vec4(0.0);\n"
|
" fragColor = vec4(0.0);\n"
|
||||||
@@ -10,9 +10,10 @@ GlobalParamsBuffer::GlobalParamsBuffer(OpenGLRenderer& renderer) :
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GlobalParamsBuffer::Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength)
|
bool GlobalParamsBuffer::Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable)
|
||||||
{
|
{
|
||||||
std::vector<unsigned char> buffer;
|
std::vector<unsigned char>& buffer = mScratchBuffer;
|
||||||
|
buffer.clear();
|
||||||
buffer.reserve(512);
|
buffer.reserve(512);
|
||||||
|
|
||||||
AppendStd140Float(buffer, static_cast<float>(state.timeSeconds));
|
AppendStd140Float(buffer, static_cast<float>(state.timeSeconds));
|
||||||
@@ -32,6 +33,7 @@ bool GlobalParamsBuffer::Update(const RuntimeRenderState& state, unsigned availa
|
|||||||
: 0u;
|
: 0u;
|
||||||
AppendStd140Int(buffer, static_cast<int>(effectiveSourceHistoryLength));
|
AppendStd140Int(buffer, static_cast<int>(effectiveSourceHistoryLength));
|
||||||
AppendStd140Int(buffer, static_cast<int>(effectiveTemporalHistoryLength));
|
AppendStd140Int(buffer, static_cast<int>(effectiveTemporalHistoryLength));
|
||||||
|
AppendStd140Int(buffer, feedbackAvailable ? 1 : 0);
|
||||||
|
|
||||||
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
||||||
{
|
{
|
||||||
@@ -3,13 +3,16 @@
|
|||||||
#include "OpenGLRenderer.h"
|
#include "OpenGLRenderer.h"
|
||||||
#include "ShaderTypes.h"
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class GlobalParamsBuffer
|
class GlobalParamsBuffer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit GlobalParamsBuffer(OpenGLRenderer& renderer);
|
explicit GlobalParamsBuffer(OpenGLRenderer& renderer);
|
||||||
|
|
||||||
bool Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength);
|
bool Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OpenGLRenderer& mRenderer;
|
OpenGLRenderer& mRenderer;
|
||||||
|
std::vector<unsigned char> mScratchBuffer;
|
||||||
};
|
};
|
||||||
@@ -13,21 +13,37 @@ void CopyErrorMessage(const std::string& message, int errorMessageSize, char* er
|
|||||||
|
|
||||||
strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
|
strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::size_t RequiredTemporaryRenderTargets(const std::vector<OpenGLRenderer::LayerProgram>& layerPrograms)
|
||||||
|
{
|
||||||
|
// Only one layer renders at a time, so the pool needs to cover the widest
|
||||||
|
// layer, not the sum of every intermediate pass in the stack.
|
||||||
|
std::size_t requiredTargets = 0;
|
||||||
|
for (const OpenGLRenderer::LayerProgram& layerProgram : layerPrograms)
|
||||||
|
{
|
||||||
|
const std::size_t internalPasses = layerProgram.passes.size() > 0 ? layerProgram.passes.size() - 1 : 0;
|
||||||
|
if (internalPasses > requiredTargets)
|
||||||
|
requiredTargets = internalPasses;
|
||||||
|
}
|
||||||
|
return requiredTargets;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.GetRenderStateSnapshot(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);
|
||||||
@@ -38,7 +54,15 @@ bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsign
|
|||||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (mRenderer.ResourcesInitialized() &&
|
||||||
|
!mRenderer.FeedbackBuffers().EnsureResources(layerStates, inputFrameWidth, inputFrameHeight, temporalError))
|
||||||
|
{
|
||||||
|
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial startup still compiles synchronously; auto-reload uses the build
|
||||||
|
// queue so Slang/file work stays off the playback path.
|
||||||
std::vector<LayerProgram> newPrograms;
|
std::vector<LayerProgram> newPrograms;
|
||||||
newPrograms.reserve(layerStates.size());
|
newPrograms.reserve(layerStates.size());
|
||||||
|
|
||||||
@@ -54,12 +78,18 @@ bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsign
|
|||||||
newPrograms.push_back(layerProgram);
|
newPrograms.push_back(layerProgram);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string targetError;
|
||||||
|
if (!mRenderer.ReserveTemporaryRenderTargets(RequiredTemporaryRenderTargets(newPrograms), inputFrameWidth, inputFrameHeight, targetError))
|
||||||
|
{
|
||||||
|
for (LayerProgram& program : newPrograms)
|
||||||
|
DestroySingleLayerProgram(program);
|
||||||
|
CopyErrorMessage(targetError, errorMessageSize, errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -73,25 +103,33 @@ 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);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mRenderer.ResourcesInitialized() &&
|
||||||
|
!mRenderer.FeedbackBuffers().EnsureResources(preparedBuild.renderSnapshot.states, inputFrameWidth, inputFrameHeight, temporalError))
|
||||||
{
|
{
|
||||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The prepared build already contains GLSL text for each pass. This commit
|
||||||
|
// step performs the short GL work on the render thread.
|
||||||
std::vector<LayerProgram> newPrograms;
|
std::vector<LayerProgram> newPrograms;
|
||||||
newPrograms.reserve(preparedBuild.layers.size());
|
newPrograms.reserve(preparedBuild.layers.size());
|
||||||
|
|
||||||
for (const PreparedLayerShader& preparedLayer : preparedBuild.layers)
|
for (const PreparedLayerShader& preparedLayer : preparedBuild.layers)
|
||||||
{
|
{
|
||||||
LayerProgram layerProgram;
|
LayerProgram layerProgram;
|
||||||
if (!mCompiler.CompilePreparedLayerProgram(preparedLayer.state, preparedLayer.fragmentShaderSource, layerProgram, errorMessageSize, errorMessage))
|
if (!mCompiler.CompilePreparedLayerProgram(preparedLayer.state, preparedLayer.passes, layerProgram, errorMessageSize, errorMessage))
|
||||||
{
|
{
|
||||||
for (LayerProgram& program : newPrograms)
|
for (LayerProgram& program : newPrograms)
|
||||||
DestroySingleLayerProgram(program);
|
DestroySingleLayerProgram(program);
|
||||||
@@ -100,12 +138,18 @@ bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild
|
|||||||
newPrograms.push_back(layerProgram);
|
newPrograms.push_back(layerProgram);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string targetError;
|
||||||
|
if (!mRenderer.ReserveTemporaryRenderTargets(RequiredTemporaryRenderTargets(newPrograms), inputFrameWidth, inputFrameHeight, targetError))
|
||||||
|
{
|
||||||
|
for (LayerProgram& program : newPrograms)
|
||||||
|
DestroySingleLayerProgram(program);
|
||||||
|
CopyErrorMessage(targetError, errorMessageSize, errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -140,12 +184,17 @@ void OpenGLShaderPrograms::ResetTemporalHistoryState()
|
|||||||
mRenderer.TemporalHistory().ResetState();
|
mRenderer.TemporalHistory().ResetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpenGLShaderPrograms::ResetShaderFeedbackState()
|
||||||
|
{
|
||||||
|
mRenderer.FeedbackBuffers().ResetState();
|
||||||
|
}
|
||||||
|
|
||||||
bool OpenGLShaderPrograms::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error)
|
bool OpenGLShaderPrograms::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error)
|
||||||
{
|
{
|
||||||
return mTextureBindings.UpdateTextBindingTexture(state, textBinding, error);
|
return mTextureBindings.UpdateTextBindingTexture(state, textBinding, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLShaderPrograms::UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength)
|
bool OpenGLShaderPrograms::UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable)
|
||||||
{
|
{
|
||||||
return mGlobalParamsBuffer.Update(state, availableSourceHistoryLength, availableTemporalHistoryLength);
|
return mGlobalParamsBuffer.Update(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
@@ -25,13 +25,14 @@ public:
|
|||||||
void DestroySingleLayerProgram(LayerProgram& layerProgram);
|
void DestroySingleLayerProgram(LayerProgram& layerProgram);
|
||||||
void DestroyDecodeShaderProgram();
|
void DestroyDecodeShaderProgram();
|
||||||
void ResetTemporalHistoryState();
|
void ResetTemporalHistoryState();
|
||||||
|
void ResetShaderFeedbackState();
|
||||||
const std::vector<RuntimeRenderState>& CommittedLayerStates() const { return mCommittedLayerStates; }
|
const std::vector<RuntimeRenderState>& CommittedLayerStates() const { return mCommittedLayerStates; }
|
||||||
bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error);
|
bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error);
|
||||||
bool UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength);
|
bool UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OpenGLRenderer& mRenderer;
|
OpenGLRenderer& mRenderer;
|
||||||
RuntimeHost& mRuntimeHost;
|
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||||
ShaderTextureBindings mTextureBindings;
|
ShaderTextureBindings mTextureBindings;
|
||||||
GlobalParamsBuffer mGlobalParamsBuffer;
|
GlobalParamsBuffer mGlobalParamsBuffer;
|
||||||
ShaderProgramCompiler mCompiler;
|
ShaderProgramCompiler mCompiler;
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "ShaderBuildQueue.h"
|
#include "ShaderBuildQueue.h"
|
||||||
|
|
||||||
#include "RuntimeHost.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -10,8 +8,8 @@ namespace
|
|||||||
constexpr auto kShaderBuildDebounce = std::chrono::milliseconds(400);
|
constexpr auto kShaderBuildDebounce = std::chrono::milliseconds(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderBuildQueue::ShaderBuildQueue(RuntimeHost& runtimeHost) :
|
ShaderBuildQueue::ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider) :
|
||||||
mRuntimeHost(runtimeHost),
|
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
||||||
mWorkerThread([this]() { WorkerLoop(); })
|
mWorkerThread([this]() { WorkerLoop(); })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -113,14 +111,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.GetRenderStateSnapshot(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.BuildLayerFragmentShaderSource(state.layerId, layer.fragmentShaderSource, build.message))
|
if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, layer.passes, build.message))
|
||||||
{
|
{
|
||||||
build.succeeded = false;
|
build.succeeded = false;
|
||||||
return build;
|
return build;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "RuntimeSnapshotProvider.h"
|
||||||
#include "ShaderTypes.h"
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
@@ -9,12 +10,10 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class RuntimeHost;
|
|
||||||
|
|
||||||
struct PreparedLayerShader
|
struct PreparedLayerShader
|
||||||
{
|
{
|
||||||
RuntimeRenderState state;
|
RuntimeRenderState state;
|
||||||
std::string fragmentShaderSource;
|
std::vector<ShaderPassBuildSource> passes;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PreparedShaderBuild
|
struct PreparedShaderBuild
|
||||||
@@ -22,14 +21,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);
|
explicit ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider);
|
||||||
~ShaderBuildQueue();
|
~ShaderBuildQueue();
|
||||||
|
|
||||||
ShaderBuildQueue(const ShaderBuildQueue&) = delete;
|
ShaderBuildQueue(const ShaderBuildQueue&) = delete;
|
||||||
@@ -43,7 +42,7 @@ private:
|
|||||||
void WorkerLoop();
|
void WorkerLoop();
|
||||||
PreparedShaderBuild Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight);
|
PreparedShaderBuild Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight);
|
||||||
|
|
||||||
RuntimeHost& mRuntimeHost;
|
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||||
std::thread mWorkerThread;
|
std::thread mWorkerThread;
|
||||||
std::mutex mMutex;
|
std::mutex mMutex;
|
||||||
std::condition_variable mCondition;
|
std::condition_variable mCondition;
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
#include "ShaderProgramCompiler.h"
|
||||||
|
|
||||||
|
#include "GlRenderConstants.h"
|
||||||
|
#include "GlScopedObjects.h"
|
||||||
|
#include "GlShaderSources.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
|
||||||
|
{
|
||||||
|
if (!errorMessage || errorMessageSize <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderProgramCompiler::ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider, ShaderTextureBindings& textureBindings) :
|
||||||
|
mRenderer(renderer),
|
||||||
|
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
||||||
|
mTextureBindings(textureBindings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderProgramCompiler::CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage)
|
||||||
|
{
|
||||||
|
std::vector<ShaderPassBuildSource> passSources;
|
||||||
|
std::string loadError;
|
||||||
|
|
||||||
|
if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, passSources, loadError))
|
||||||
|
{
|
||||||
|
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompilePreparedLayerProgram(state, passSources, layerProgram, errorMessageSize, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::vector<ShaderPassBuildSource>& passSources, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage)
|
||||||
|
{
|
||||||
|
GLsizei errorBufferSize = 0;
|
||||||
|
std::string loadError;
|
||||||
|
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
|
||||||
|
|
||||||
|
layerProgram.layerId = state.layerId;
|
||||||
|
layerProgram.shaderId = state.shaderId;
|
||||||
|
layerProgram.passes.clear();
|
||||||
|
|
||||||
|
for (const auto& passSource : passSources)
|
||||||
|
{
|
||||||
|
GLint compileResult = GL_FALSE;
|
||||||
|
GLint linkResult = GL_FALSE;
|
||||||
|
const char* fragmentSource = passSource.fragmentShaderSource.c_str();
|
||||||
|
|
||||||
|
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
|
||||||
|
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
|
||||||
|
glCompileShader(newVertexShader.get());
|
||||||
|
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
|
||||||
|
if (compileResult == GL_FALSE)
|
||||||
|
{
|
||||||
|
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||||
|
mRenderer.DestroySingleLayerProgram(layerProgram);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
|
||||||
|
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
|
||||||
|
glCompileShader(newFragmentShader.get());
|
||||||
|
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
|
||||||
|
if (compileResult == GL_FALSE)
|
||||||
|
{
|
||||||
|
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||||
|
mRenderer.DestroySingleLayerProgram(layerProgram);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedGlProgram newProgram(glCreateProgram());
|
||||||
|
glAttachShader(newProgram.get(), newVertexShader.get());
|
||||||
|
glAttachShader(newProgram.get(), newFragmentShader.get());
|
||||||
|
glLinkProgram(newProgram.get());
|
||||||
|
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
|
||||||
|
if (linkResult == GL_FALSE)
|
||||||
|
{
|
||||||
|
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||||
|
mRenderer.DestroySingleLayerProgram(layerProgram);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<LayerProgram::TextureBinding> textureBindings;
|
||||||
|
for (const ShaderTextureAsset& textureAsset : state.textureAssets)
|
||||||
|
{
|
||||||
|
LayerProgram::TextureBinding textureBinding;
|
||||||
|
textureBinding.samplerName = textureAsset.id;
|
||||||
|
textureBinding.sourcePath = textureAsset.path;
|
||||||
|
if (!mTextureBindings.LoadTextureAsset(textureAsset, textureBinding.texture, loadError))
|
||||||
|
{
|
||||||
|
for (LayerProgram::TextureBinding& loadedTexture : textureBindings)
|
||||||
|
{
|
||||||
|
if (loadedTexture.texture != 0)
|
||||||
|
glDeleteTextures(1, &loadedTexture.texture);
|
||||||
|
}
|
||||||
|
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
|
||||||
|
mRenderer.DestroySingleLayerProgram(layerProgram);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
textureBindings.push_back(textureBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<LayerProgram::TextBinding> textBindings;
|
||||||
|
mTextureBindings.CreateTextBindings(state, textBindings);
|
||||||
|
|
||||||
|
PassProgram passProgram;
|
||||||
|
passProgram.passId = passSource.passId;
|
||||||
|
passProgram.inputNames = passSource.inputNames;
|
||||||
|
passProgram.outputName = passSource.outputName;
|
||||||
|
passProgram.shaderTextureBase = mTextureBindings.ResolveShaderTextureBase(state, mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames());
|
||||||
|
passProgram.textureBindings.swap(textureBindings);
|
||||||
|
passProgram.textBindings.swap(textBindings);
|
||||||
|
|
||||||
|
const GLuint globalParamsIndex = glGetUniformBlockIndex(newProgram.get(), "GlobalParams");
|
||||||
|
if (globalParamsIndex != GL_INVALID_INDEX)
|
||||||
|
glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint);
|
||||||
|
|
||||||
|
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
|
||||||
|
glUseProgram(newProgram.get());
|
||||||
|
mTextureBindings.AssignLayerSamplerUniforms(newProgram.get(), state, passProgram, historyCap);
|
||||||
|
glUseProgram(0);
|
||||||
|
|
||||||
|
passProgram.program = newProgram.release();
|
||||||
|
passProgram.vertexShader = newVertexShader.release();
|
||||||
|
passProgram.fragmentShader = newFragmentShader.release();
|
||||||
|
layerProgram.passes.push_back(std::move(passProgram));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderProgramCompiler::CompileDecodeShader(int errorMessageSize, char* errorMessage)
|
||||||
|
{
|
||||||
|
GLsizei errorBufferSize = 0;
|
||||||
|
GLint compileResult = GL_FALSE;
|
||||||
|
GLint linkResult = GL_FALSE;
|
||||||
|
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
|
||||||
|
const char* fragmentSource = kDecodeFragmentShaderSource;
|
||||||
|
|
||||||
|
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
|
||||||
|
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
|
||||||
|
glCompileShader(newVertexShader.get());
|
||||||
|
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
|
||||||
|
if (compileResult == GL_FALSE)
|
||||||
|
{
|
||||||
|
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
|
||||||
|
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
|
||||||
|
glCompileShader(newFragmentShader.get());
|
||||||
|
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
|
||||||
|
if (compileResult == GL_FALSE)
|
||||||
|
{
|
||||||
|
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedGlProgram newProgram(glCreateProgram());
|
||||||
|
glAttachShader(newProgram.get(), newVertexShader.get());
|
||||||
|
glAttachShader(newProgram.get(), newFragmentShader.get());
|
||||||
|
glLinkProgram(newProgram.get());
|
||||||
|
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
|
||||||
|
if (linkResult == GL_FALSE)
|
||||||
|
{
|
||||||
|
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mRenderer.DestroyDecodeShaderProgram();
|
||||||
|
mRenderer.SetDecodeShaderProgram(newProgram.release(), newVertexShader.release(), newFragmentShader.release());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderProgramCompiler::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
|
||||||
|
{
|
||||||
|
GLsizei errorBufferSize = 0;
|
||||||
|
GLint compileResult = GL_FALSE;
|
||||||
|
GLint linkResult = GL_FALSE;
|
||||||
|
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
|
||||||
|
const char* fragmentSource = kOutputPackFragmentShaderSource;
|
||||||
|
|
||||||
|
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
|
||||||
|
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
|
||||||
|
glCompileShader(newVertexShader.get());
|
||||||
|
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
|
||||||
|
if (compileResult == GL_FALSE)
|
||||||
|
{
|
||||||
|
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
|
||||||
|
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
|
||||||
|
glCompileShader(newFragmentShader.get());
|
||||||
|
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
|
||||||
|
if (compileResult == GL_FALSE)
|
||||||
|
{
|
||||||
|
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedGlProgram newProgram(glCreateProgram());
|
||||||
|
glAttachShader(newProgram.get(), newVertexShader.get());
|
||||||
|
glAttachShader(newProgram.get(), newFragmentShader.get());
|
||||||
|
glLinkProgram(newProgram.get());
|
||||||
|
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
|
||||||
|
if (linkResult == GL_FALSE)
|
||||||
|
{
|
||||||
|
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glUseProgram(newProgram.get());
|
||||||
|
const GLint outputSamplerLocation = glGetUniformLocation(newProgram.get(), "uOutputRgb");
|
||||||
|
if (outputSamplerLocation >= 0)
|
||||||
|
glUniform1i(outputSamplerLocation, 0);
|
||||||
|
glUseProgram(0);
|
||||||
|
|
||||||
|
mRenderer.DestroyOutputPackShaderProgram();
|
||||||
|
mRenderer.SetOutputPackShaderProgram(newProgram.release(), newVertexShader.release(), newFragmentShader.release());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -1,25 +1,27 @@
|
|||||||
#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>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class ShaderProgramCompiler
|
class ShaderProgramCompiler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
using LayerProgram = OpenGLRenderer::LayerProgram;
|
||||||
|
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
|
||||||
|
|
||||||
ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, ShaderTextureBindings& textureBindings);
|
ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider, ShaderTextureBindings& textureBindings);
|
||||||
|
|
||||||
bool CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
bool CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
||||||
bool CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::string& fragmentShaderSource, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
bool CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::vector<ShaderPassBuildSource>& passSources, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
||||||
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
|
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
|
||||||
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
|
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OpenGLRenderer& mRenderer;
|
OpenGLRenderer& mRenderer;
|
||||||
RuntimeHost& mRuntimeHost;
|
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
||||||
ShaderTextureBindings& mTextureBindings;
|
ShaderTextureBindings& mTextureBindings;
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
#include "ShaderTextureBindings.h"
|
||||||
|
|
||||||
|
#include "GlRenderConstants.h"
|
||||||
|
#include "TextRasterizer.h"
|
||||||
|
#include "TextureAssetLoader.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::string TextValueForBinding(const RuntimeRenderState& state, const std::string& parameterId)
|
||||||
|
{
|
||||||
|
auto valueIt = state.parameterValues.find(parameterId);
|
||||||
|
return valueIt == state.parameterValues.end() ? std::string() : valueIt->second.textValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ShaderFontAsset* FindFontAssetForParameter(const RuntimeRenderState& state, const ShaderParameterDefinition& definition)
|
||||||
|
{
|
||||||
|
if (!definition.fontId.empty())
|
||||||
|
{
|
||||||
|
for (const ShaderFontAsset& fontAsset : state.fontAssets)
|
||||||
|
{
|
||||||
|
if (fontAsset.id == definition.fontId)
|
||||||
|
return &fontAsset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state.fontAssets.empty() ? nullptr : &state.fontAssets.front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderTextureBindings::LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
|
||||||
|
{
|
||||||
|
return ::LoadTextureAsset(textureAsset, textureId, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderTextureBindings::CreateTextBindings(const RuntimeRenderState& state, std::vector<LayerProgram::TextBinding>& textBindings)
|
||||||
|
{
|
||||||
|
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
||||||
|
{
|
||||||
|
if (definition.type != ShaderParameterType::Text)
|
||||||
|
continue;
|
||||||
|
LayerProgram::TextBinding textBinding;
|
||||||
|
textBinding.parameterId = definition.id;
|
||||||
|
textBinding.samplerName = definition.id + "Texture";
|
||||||
|
textBinding.fontId = definition.fontId;
|
||||||
|
glGenTextures(1, &textBinding.texture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, textBinding.texture);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
std::vector<unsigned char> empty(static_cast<std::size_t>(kTextTextureWidth) * kTextTextureHeight * 4, 0);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTextTextureWidth, kTextTextureHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, empty.data());
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
textBindings.push_back(textBinding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderTextureBindings::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error)
|
||||||
|
{
|
||||||
|
const std::string text = TextValueForBinding(state, textBinding.parameterId);
|
||||||
|
if (text == textBinding.renderedText && textBinding.renderedWidth == kTextTextureWidth && textBinding.renderedHeight == kTextTextureHeight)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto definitionIt = std::find_if(state.parameterDefinitions.begin(), state.parameterDefinitions.end(),
|
||||||
|
[&textBinding](const ShaderParameterDefinition& definition) { return definition.id == textBinding.parameterId; });
|
||||||
|
if (definitionIt == state.parameterDefinitions.end())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const ShaderFontAsset* fontAsset = FindFontAssetForParameter(state, *definitionIt);
|
||||||
|
std::filesystem::path fontPath;
|
||||||
|
if (fontAsset)
|
||||||
|
fontPath = fontAsset->path;
|
||||||
|
|
||||||
|
std::vector<unsigned char> sdf;
|
||||||
|
if (!RasterizeTextSdf(text, fontPath, sdf, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
GLint previousActiveTexture = 0;
|
||||||
|
GLint previousUnpackBuffer = 0;
|
||||||
|
glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture);
|
||||||
|
glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &previousUnpackBuffer);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, textBinding.texture);
|
||||||
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextTextureWidth, kTextTextureHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, sdf.data());
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(previousUnpackBuffer));
|
||||||
|
glActiveTexture(static_cast<GLenum>(previousActiveTexture));
|
||||||
|
|
||||||
|
textBinding.renderedText = text;
|
||||||
|
textBinding.renderedWidth = kTextTextureWidth;
|
||||||
|
textBinding.renderedHeight = kTextTextureHeight;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLint ShaderTextureBindings::FindSamplerUniformLocation(GLuint program, const std::string& samplerName) const
|
||||||
|
{
|
||||||
|
GLint location = glGetUniformLocation(program, samplerName.c_str());
|
||||||
|
if (location >= 0)
|
||||||
|
return location;
|
||||||
|
return glGetUniformLocation(program, (samplerName + "_0").c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint ShaderTextureBindings::ResolveFeedbackTextureUnit(const RuntimeRenderState& state, unsigned historyCap) const
|
||||||
|
{
|
||||||
|
return state.isTemporal ? kSourceHistoryTextureUnitBase + historyCap + historyCap : kSourceHistoryTextureUnitBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint ShaderTextureBindings::ResolveShaderTextureBase(const RuntimeRenderState& state, unsigned historyCap) const
|
||||||
|
{
|
||||||
|
return ResolveFeedbackTextureUnit(state, historyCap) + (state.feedback.enabled ? 1u : 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderTextureBindings::AssignLayerSamplerUniforms(GLuint program, const RuntimeRenderState& state, const PassProgram& passProgram, unsigned historyCap) const
|
||||||
|
{
|
||||||
|
const GLuint shaderTextureBase = ResolveShaderTextureBase(state, historyCap);
|
||||||
|
|
||||||
|
const GLint layerInputLocation = FindSamplerUniformLocation(program, "gLayerInput");
|
||||||
|
if (layerInputLocation >= 0)
|
||||||
|
glUniform1i(layerInputLocation, static_cast<GLint>(kLayerInputTextureUnit));
|
||||||
|
|
||||||
|
const GLint videoInputLocation = FindSamplerUniformLocation(program, "gVideoInput");
|
||||||
|
if (videoInputLocation >= 0)
|
||||||
|
glUniform1i(videoInputLocation, static_cast<GLint>(kDecodedVideoTextureUnit));
|
||||||
|
|
||||||
|
for (unsigned index = 0; index < historyCap; ++index)
|
||||||
|
{
|
||||||
|
const std::string sourceSamplerName = "gSourceHistory" + std::to_string(index);
|
||||||
|
const GLint sourceSamplerLocation = glGetUniformLocation(program, sourceSamplerName.c_str());
|
||||||
|
if (sourceSamplerLocation >= 0)
|
||||||
|
glUniform1i(sourceSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + index));
|
||||||
|
|
||||||
|
const std::string temporalSamplerName = "gTemporalHistory" + std::to_string(index);
|
||||||
|
const GLint temporalSamplerLocation = glGetUniformLocation(program, temporalSamplerName.c_str());
|
||||||
|
if (temporalSamplerLocation >= 0)
|
||||||
|
glUniform1i(temporalSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + historyCap + index));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.feedback.enabled)
|
||||||
|
{
|
||||||
|
const GLint feedbackSamplerLocation = FindSamplerUniformLocation(program, "gFeedbackState");
|
||||||
|
if (feedbackSamplerLocation >= 0)
|
||||||
|
glUniform1i(feedbackSamplerLocation, static_cast<GLint>(ResolveFeedbackTextureUnit(state, historyCap)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < passProgram.textureBindings.size(); ++index)
|
||||||
|
{
|
||||||
|
const GLint textureSamplerLocation = FindSamplerUniformLocation(program, passProgram.textureBindings[index].samplerName);
|
||||||
|
if (textureSamplerLocation >= 0)
|
||||||
|
glUniform1i(textureSamplerLocation, static_cast<GLint>(shaderTextureBase + static_cast<GLuint>(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(passProgram.textureBindings.size());
|
||||||
|
for (std::size_t index = 0; index < passProgram.textBindings.size(); ++index)
|
||||||
|
{
|
||||||
|
const GLint textSamplerLocation = FindSamplerUniformLocation(program, passProgram.textBindings[index].samplerName);
|
||||||
|
if (textSamplerLocation >= 0)
|
||||||
|
glUniform1i(textSamplerLocation, static_cast<GLint>(textTextureBase + static_cast<GLuint>(index)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderTextureBindings::RuntimeTextureBindingPlan ShaderTextureBindings::BuildLayerRuntimeBindingPlan(
|
||||||
|
const PassProgram& passProgram,
|
||||||
|
GLuint layerInputTexture,
|
||||||
|
GLuint originalLayerInputTexture,
|
||||||
|
const RuntimeRenderState& state,
|
||||||
|
GLuint feedbackTexture,
|
||||||
|
const std::vector<GLuint>& sourceHistoryTextures,
|
||||||
|
const std::vector<GLuint>& temporalHistoryTextures) const
|
||||||
|
{
|
||||||
|
RuntimeTextureBindingPlan plan;
|
||||||
|
plan.bindings.push_back({ "originalLayerInput", "gLayerInput", originalLayerInputTexture, kLayerInputTextureUnit });
|
||||||
|
plan.bindings.push_back({ "layerInput", "gVideoInput", layerInputTexture, kDecodedVideoTextureUnit });
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < sourceHistoryTextures.size(); ++index)
|
||||||
|
{
|
||||||
|
plan.bindings.push_back({
|
||||||
|
"sourceHistory",
|
||||||
|
"gSourceHistory" + std::to_string(index),
|
||||||
|
sourceHistoryTextures[index],
|
||||||
|
kSourceHistoryTextureUnitBase + static_cast<GLuint>(index)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const GLuint temporalBase = kSourceHistoryTextureUnitBase + static_cast<GLuint>(sourceHistoryTextures.size());
|
||||||
|
for (std::size_t index = 0; index < temporalHistoryTextures.size(); ++index)
|
||||||
|
{
|
||||||
|
plan.bindings.push_back({
|
||||||
|
"temporalHistory",
|
||||||
|
"gTemporalHistory" + std::to_string(index),
|
||||||
|
temporalHistoryTextures[index],
|
||||||
|
temporalBase + static_cast<GLuint>(index)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const GLuint feedbackTextureUnit = ResolveFeedbackTextureUnit(state, static_cast<unsigned>(sourceHistoryTextures.size()));
|
||||||
|
if (state.feedback.enabled)
|
||||||
|
{
|
||||||
|
plan.bindings.push_back({
|
||||||
|
"feedbackState",
|
||||||
|
"gFeedbackState",
|
||||||
|
feedbackTexture,
|
||||||
|
feedbackTextureUnit
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const GLuint shaderTextureBase = passProgram.shaderTextureBase != 0
|
||||||
|
? passProgram.shaderTextureBase
|
||||||
|
: feedbackTextureUnit + (state.feedback.enabled ? 1u : 0u);
|
||||||
|
for (std::size_t index = 0; index < passProgram.textureBindings.size(); ++index)
|
||||||
|
{
|
||||||
|
const LayerProgram::TextureBinding& textureBinding = passProgram.textureBindings[index];
|
||||||
|
plan.bindings.push_back({
|
||||||
|
"shaderTexture",
|
||||||
|
textureBinding.samplerName,
|
||||||
|
textureBinding.texture,
|
||||||
|
shaderTextureBase + static_cast<GLuint>(index)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(passProgram.textureBindings.size());
|
||||||
|
for (std::size_t index = 0; index < passProgram.textBindings.size(); ++index)
|
||||||
|
{
|
||||||
|
const LayerProgram::TextBinding& textBinding = passProgram.textBindings[index];
|
||||||
|
plan.bindings.push_back({
|
||||||
|
"textTexture",
|
||||||
|
textBinding.samplerName,
|
||||||
|
textBinding.texture,
|
||||||
|
textTextureBase + static_cast<GLuint>(index)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderTextureBindings::BindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const
|
||||||
|
{
|
||||||
|
for (const RuntimeTextureBinding& binding : plan.bindings)
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE0 + binding.textureUnit);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, binding.texture);
|
||||||
|
}
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderTextureBindings::UnbindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const
|
||||||
|
{
|
||||||
|
for (const RuntimeTextureBinding& binding : plan.bindings)
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE0 + binding.textureUnit);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "OpenGLRenderer.h"
|
||||||
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class ShaderTextureBindings
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using LayerProgram = OpenGLRenderer::LayerProgram;
|
||||||
|
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
|
||||||
|
|
||||||
|
struct RuntimeTextureBinding
|
||||||
|
{
|
||||||
|
std::string semanticName;
|
||||||
|
std::string samplerName;
|
||||||
|
GLuint texture = 0;
|
||||||
|
GLuint textureUnit = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RuntimeTextureBindingPlan
|
||||||
|
{
|
||||||
|
std::vector<RuntimeTextureBinding> bindings;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error);
|
||||||
|
void CreateTextBindings(const RuntimeRenderState& state, std::vector<LayerProgram::TextBinding>& textBindings);
|
||||||
|
bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error);
|
||||||
|
GLint FindSamplerUniformLocation(GLuint program, const std::string& samplerName) const;
|
||||||
|
GLuint ResolveFeedbackTextureUnit(const RuntimeRenderState& state, unsigned historyCap) const;
|
||||||
|
GLuint ResolveShaderTextureBase(const RuntimeRenderState& state, unsigned historyCap) const;
|
||||||
|
void AssignLayerSamplerUniforms(GLuint program, const RuntimeRenderState& state, const PassProgram& passProgram, unsigned historyCap) const;
|
||||||
|
RuntimeTextureBindingPlan BuildLayerRuntimeBindingPlan(
|
||||||
|
const PassProgram& passProgram,
|
||||||
|
GLuint layerInputTexture,
|
||||||
|
GLuint originalLayerInputTexture,
|
||||||
|
const RuntimeRenderState& state,
|
||||||
|
GLuint feedbackTexture,
|
||||||
|
const std::vector<GLuint>& sourceHistoryTextures,
|
||||||
|
const std::vector<GLuint>& temporalHistoryTextures) const;
|
||||||
|
void BindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const;
|
||||||
|
void UnbindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const;
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "HealthTelemetry.h"
|
||||||
|
|
||||||
|
#include "RuntimeHost.h"
|
||||||
|
|
||||||
|
HealthTelemetry::HealthTelemetry(RuntimeHost& runtimeHost) :
|
||||||
|
mRuntimeHost(runtimeHost)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void HealthTelemetry::ReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
||||||
|
{
|
||||||
|
mRuntimeHost.WriteSignalStatus(hasSignal, width, height, modeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HealthTelemetry::TryReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.TryWriteSignalStatus(hasSignal, width, height, modeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HealthTelemetry::RecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
||||||
|
{
|
||||||
|
mRuntimeHost.WritePerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HealthTelemetry::TryRecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.TryWritePerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HealthTelemetry::RecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||||
|
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
|
||||||
|
{
|
||||||
|
mRuntimeHost.WriteFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
|
||||||
|
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HealthTelemetry::TryRecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||||
|
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.TryWriteFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
|
||||||
|
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class RuntimeHost;
|
||||||
|
|
||||||
|
// Phase 1 compatibility seam for status and timing reporting. The current
|
||||||
|
// implementation still writes through RuntimeHost, but callers can now target
|
||||||
|
// HealthTelemetry as the home for operational visibility work.
|
||||||
|
class HealthTelemetry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit HealthTelemetry(RuntimeHost& runtimeHost);
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
private:
|
||||||
|
RuntimeHost& mRuntimeHost;
|
||||||
|
};
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
#include "RuntimeCoordinator.h"
|
||||||
|
|
||||||
|
#include "RuntimeStore.h"
|
||||||
|
|
||||||
|
RuntimeCoordinator::RuntimeCoordinator(RuntimeStore& runtimeStore) :
|
||||||
|
mRuntimeStore(runtimeStore)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::AddLayer(const std::string& shaderId)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
return ApplyStoreMutation(mRuntimeStore.CreateStoredLayer(shaderId, error), error, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::RemoveLayer(const std::string& layerId)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
return ApplyStoreMutation(mRuntimeStore.DeleteStoredLayer(layerId, error), error, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::MoveLayer(const std::string& layerId, int direction)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
return ApplyStoreMutation(mRuntimeStore.MoveStoredLayer(layerId, direction, error), error, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
return ApplyStoreMutation(mRuntimeStore.MoveStoredLayerToIndex(layerId, targetIndex, error), error, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::SetLayerBypass(const std::string& layerId, bool bypassed)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
return ApplyStoreMutation(mRuntimeStore.SetStoredLayerBypassState(layerId, bypassed, error), error, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::SetLayerShader(const std::string& layerId, const std::string& shaderId)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
return ApplyStoreMutation(mRuntimeStore.SetStoredLayerShaderSelection(layerId, shaderId, error), error, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
return ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(layerId, parameterId, newValue, error), error, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
return ApplyStoreMutation(mRuntimeStore.SetStoredParameterValueByControlKey(layerKey, parameterKey, newValue, error), error, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::ResetLayerParameters(const std::string& layerId)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.ResetStoredLayerParameterValues(layerId, error), error, false, false);
|
||||||
|
if (!result.accepted)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result.clearTransientOscState = true;
|
||||||
|
result.renderResetScope = RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::SaveStackPreset(const std::string& presetName)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
return ApplyStoreMutation(mRuntimeStore.SaveStackPresetSnapshot(presetName, error), error, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::LoadStackPreset(const std::string& presetName)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
return ApplyStoreMutation(mRuntimeStore.LoadStackPresetSnapshot(presetName, error), error, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::RequestShaderReload(bool preserveFeedbackState)
|
||||||
|
{
|
||||||
|
return BuildQueuedReloadResult(preserveFeedbackState);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimePollFailure(const std::string& error)
|
||||||
|
{
|
||||||
|
RuntimeCoordinatorResult result;
|
||||||
|
result.accepted = true;
|
||||||
|
result.runtimeStateBroadcastRequired = true;
|
||||||
|
result.compileStatusChanged = true;
|
||||||
|
result.compileStatusSucceeded = false;
|
||||||
|
result.compileStatusMessage = error;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(const std::string& error)
|
||||||
|
{
|
||||||
|
mPreserveFeedbackOnNextShaderBuild = false;
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult result;
|
||||||
|
result.accepted = true;
|
||||||
|
result.runtimeStateBroadcastRequired = true;
|
||||||
|
result.compileStatusChanged = true;
|
||||||
|
result.compileStatusSucceeded = false;
|
||||||
|
result.compileStatusMessage = error;
|
||||||
|
result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseCommittedStates;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildSuccess()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimeReloadRequest()
|
||||||
|
{
|
||||||
|
return BuildQueuedReloadResult(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeCoordinator::PreserveFeedbackOnNextShaderBuild() const
|
||||||
|
{
|
||||||
|
return mPreserveFeedbackOnNextShaderBuild;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState)
|
||||||
|
{
|
||||||
|
if (!succeeded)
|
||||||
|
{
|
||||||
|
RuntimeCoordinatorResult result;
|
||||||
|
result.accepted = false;
|
||||||
|
result.errorMessage = errorMessage;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reloadRequired)
|
||||||
|
return BuildQueuedReloadResult(preserveFeedbackState);
|
||||||
|
|
||||||
|
return BuildAcceptedNoReloadResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RuntimeCoordinator::BuildQueuedReloadResult(bool preserveFeedbackState)
|
||||||
|
{
|
||||||
|
mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "RuntimeJson.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class RuntimeStore;
|
||||||
|
|
||||||
|
enum class RuntimeCoordinatorCommittedStateMode
|
||||||
|
{
|
||||||
|
Unchanged,
|
||||||
|
UseCommittedStates,
|
||||||
|
UseLiveSnapshots
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RuntimeCoordinatorRenderResetScope
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
TemporalHistoryOnly,
|
||||||
|
TemporalHistoryAndFeedback
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RuntimeCoordinatorResult
|
||||||
|
{
|
||||||
|
bool accepted = false;
|
||||||
|
bool runtimeStateBroadcastRequired = false;
|
||||||
|
bool shaderBuildRequested = false;
|
||||||
|
bool clearTransientOscState = false;
|
||||||
|
bool compileStatusChanged = false;
|
||||||
|
bool compileStatusSucceeded = false;
|
||||||
|
bool clearReloadRequest = false;
|
||||||
|
RuntimeCoordinatorCommittedStateMode committedStateMode = RuntimeCoordinatorCommittedStateMode::Unchanged;
|
||||||
|
RuntimeCoordinatorRenderResetScope renderResetScope = RuntimeCoordinatorRenderResetScope::None;
|
||||||
|
std::string compileStatusMessage;
|
||||||
|
std::string errorMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RuntimeCoordinator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RuntimeCoordinator(RuntimeStore& runtimeStore);
|
||||||
|
|
||||||
|
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 ResetLayerParameters(const std::string& layerId);
|
||||||
|
RuntimeCoordinatorResult SaveStackPreset(const std::string& presetName);
|
||||||
|
RuntimeCoordinatorResult LoadStackPreset(const std::string& presetName);
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult RequestShaderReload(bool preserveFeedbackState = false);
|
||||||
|
RuntimeCoordinatorResult HandleRuntimePollFailure(const std::string& error);
|
||||||
|
RuntimeCoordinatorResult HandlePreparedShaderBuildFailure(const std::string& error);
|
||||||
|
RuntimeCoordinatorResult HandlePreparedShaderBuildSuccess();
|
||||||
|
RuntimeCoordinatorResult HandleRuntimeReloadRequest();
|
||||||
|
bool PreserveFeedbackOnNextShaderBuild() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
RuntimeCoordinatorResult ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState);
|
||||||
|
RuntimeCoordinatorResult BuildQueuedReloadResult(bool preserveFeedbackState);
|
||||||
|
RuntimeCoordinatorResult BuildAcceptedNoReloadResult() const;
|
||||||
|
|
||||||
|
RuntimeStore& mRuntimeStore;
|
||||||
|
bool mPreserveFeedbackOnNextShaderBuild = false;
|
||||||
|
};
|
||||||
@@ -33,6 +33,11 @@ bool IsFiniteNumber(double value)
|
|||||||
return std::isfinite(value) != 0;
|
return std::isfinite(value) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double Clamp01(double value)
|
||||||
|
{
|
||||||
|
return std::max(0.0, std::min(1.0, value));
|
||||||
|
}
|
||||||
|
|
||||||
std::string ToLowerCopy(std::string text)
|
std::string ToLowerCopy(std::string text)
|
||||||
{
|
{
|
||||||
std::transform(text.begin(), text.end(), text.begin(),
|
std::transform(text.begin(), text.end(), text.begin(),
|
||||||
@@ -56,6 +61,20 @@ bool MatchesControlKey(const std::string& candidate, const std::string& key)
|
|||||||
return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key);
|
return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool JsonValueContainsOnlyNumbers(const JsonValue& value)
|
||||||
|
{
|
||||||
|
if (!value.isArray())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (const JsonValue& item : value.asArray())
|
||||||
|
{
|
||||||
|
if (!item.isNumber() || !IsFiniteNumber(item.asNumber()))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
double GenerateStartupRandom()
|
double GenerateStartupRandom()
|
||||||
{
|
{
|
||||||
std::random_device randomDevice;
|
std::random_device randomDevice;
|
||||||
@@ -82,17 +101,6 @@ bool TryParseLayerIdNumber(const std::string& layerId, uint64_t& number)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<double> JsonArrayToNumbers(const JsonValue& value)
|
|
||||||
{
|
|
||||||
std::vector<double> numbers;
|
|
||||||
for (const JsonValue& item : value.asArray())
|
|
||||||
{
|
|
||||||
if (item.isNumber())
|
|
||||||
numbers.push_back(item.asNumber());
|
|
||||||
}
|
|
||||||
return numbers;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LooksLikePackagedRuntimeRoot(const std::filesystem::path& candidate)
|
bool LooksLikePackagedRuntimeRoot(const std::filesystem::path& candidate)
|
||||||
{
|
{
|
||||||
return std::filesystem::exists(candidate / "config" / "runtime-host.json") &&
|
return std::filesystem::exists(candidate / "config" / "runtime-host.json") &&
|
||||||
@@ -629,6 +637,9 @@ bool ParseParameterDefinition(const JsonValue& parameterJson, ShaderParameterDef
|
|||||||
if (!ValidateShaderIdentifier(definition.id, "parameters[].id", manifestPath, error))
|
if (!ValidateShaderIdentifier(definition.id, "parameters[].id", manifestPath, error))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (!OptionalStringField(parameterJson, "description", definition.description, "", manifestPath, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (!ParseParameterDefault(parameterJson, definition, manifestPath, error) ||
|
if (!ParseParameterDefault(parameterJson, definition, manifestPath, error) ||
|
||||||
!ParseParameterNumberField(parameterJson, "min", definition.minNumbers, manifestPath, error) ||
|
!ParseParameterNumberField(parameterJson, "min", definition.minNumbers, manifestPath, error) ||
|
||||||
!ParseParameterNumberField(parameterJson, "max", definition.maxNumbers, manifestPath, error) ||
|
!ParseParameterNumberField(parameterJson, "max", definition.maxNumbers, manifestPath, error) ||
|
||||||
@@ -688,7 +699,8 @@ bool ParseParameterDefinitions(const JsonValue& manifestJson, ShaderPackage& sha
|
|||||||
}
|
}
|
||||||
|
|
||||||
RuntimeHost::RuntimeHost()
|
RuntimeHost::RuntimeHost()
|
||||||
: mReloadRequested(false),
|
: mHealthTelemetry(*this),
|
||||||
|
mReloadRequested(false),
|
||||||
mCompileSucceeded(false),
|
mCompileSucceeded(false),
|
||||||
mHasSignal(false),
|
mHasSignal(false),
|
||||||
mSignalWidth(0),
|
mSignalWidth(0),
|
||||||
@@ -849,6 +861,8 @@ bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested,
|
|||||||
}
|
}
|
||||||
|
|
||||||
reloadRequested = mReloadRequested;
|
reloadRequested = mReloadRequested;
|
||||||
|
if (registryChanged || reloadRequested)
|
||||||
|
MarkRenderStateDirtyLocked();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (const std::exception& exception)
|
catch (const std::exception& exception)
|
||||||
@@ -892,6 +906,7 @@ bool RuntimeHost::AddLayer(const std::string& shaderId, std::string& error)
|
|||||||
EnsureLayerDefaultsLocked(layer, shaderIt->second);
|
EnsureLayerDefaultsLocked(layer, shaderIt->second);
|
||||||
mPersistentState.layers.push_back(layer);
|
mPersistentState.layers.push_back(layer);
|
||||||
mReloadRequested = true;
|
mReloadRequested = true;
|
||||||
|
MarkRenderStateDirtyLocked();
|
||||||
return SavePersistentState(error);
|
return SavePersistentState(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -908,6 +923,7 @@ bool RuntimeHost::RemoveLayer(const std::string& layerId, std::string& error)
|
|||||||
|
|
||||||
mPersistentState.layers.erase(it);
|
mPersistentState.layers.erase(it);
|
||||||
mReloadRequested = true;
|
mReloadRequested = true;
|
||||||
|
MarkRenderStateDirtyLocked();
|
||||||
return SavePersistentState(error);
|
return SavePersistentState(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,6 +945,7 @@ bool RuntimeHost::MoveLayer(const std::string& layerId, int direction, std::stri
|
|||||||
|
|
||||||
std::swap(mPersistentState.layers[index], mPersistentState.layers[newIndex]);
|
std::swap(mPersistentState.layers[index], mPersistentState.layers[newIndex]);
|
||||||
mReloadRequested = true;
|
mReloadRequested = true;
|
||||||
|
MarkRenderStateDirtyLocked();
|
||||||
return SavePersistentState(error);
|
return SavePersistentState(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -957,6 +974,7 @@ bool RuntimeHost::MoveLayerToIndex(const std::string& layerId, std::size_t targe
|
|||||||
mPersistentState.layers.erase(mPersistentState.layers.begin() + static_cast<std::ptrdiff_t>(sourceIndex));
|
mPersistentState.layers.erase(mPersistentState.layers.begin() + static_cast<std::ptrdiff_t>(sourceIndex));
|
||||||
mPersistentState.layers.insert(mPersistentState.layers.begin() + static_cast<std::ptrdiff_t>(targetIndex), movedLayer);
|
mPersistentState.layers.insert(mPersistentState.layers.begin() + static_cast<std::ptrdiff_t>(targetIndex), movedLayer);
|
||||||
mReloadRequested = true;
|
mReloadRequested = true;
|
||||||
|
MarkRenderStateDirtyLocked();
|
||||||
return SavePersistentState(error);
|
return SavePersistentState(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -972,6 +990,7 @@ bool RuntimeHost::SetLayerBypass(const std::string& layerId, bool bypassed, std:
|
|||||||
|
|
||||||
layer->bypass = bypassed;
|
layer->bypass = bypassed;
|
||||||
mReloadRequested = true;
|
mReloadRequested = true;
|
||||||
|
MarkParameterStateDirtyLocked();
|
||||||
return SavePersistentState(error);
|
return SavePersistentState(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -996,6 +1015,7 @@ bool RuntimeHost::SetLayerShader(const std::string& layerId, const std::string&
|
|||||||
layer->parameterValues.clear();
|
layer->parameterValues.clear();
|
||||||
EnsureLayerDefaultsLocked(*layer, shaderIt->second);
|
EnsureLayerDefaultsLocked(*layer, shaderIt->second);
|
||||||
mReloadRequested = true;
|
mReloadRequested = true;
|
||||||
|
MarkRenderStateDirtyLocked();
|
||||||
return SavePersistentState(error);
|
return SavePersistentState(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1032,6 +1052,7 @@ bool RuntimeHost::UpdateLayerParameter(const std::string& layerId, const std::st
|
|||||||
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
|
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
|
||||||
const double triggerTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
|
const double triggerTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
|
||||||
value.numberValues = { previousCount + 1.0, triggerTime };
|
value.numberValues = { previousCount + 1.0, triggerTime };
|
||||||
|
MarkParameterStateDirtyLocked();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1040,10 +1061,16 @@ bool RuntimeHost::UpdateLayerParameter(const std::string& layerId, const std::st
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
layer->parameterValues[parameterId] = normalized;
|
layer->parameterValues[parameterId] = normalized;
|
||||||
|
MarkParameterStateDirtyLocked();
|
||||||
return SavePersistentState(error);
|
return SavePersistentState(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeHost::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error)
|
bool RuntimeHost::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error)
|
||||||
|
{
|
||||||
|
return UpdateLayerParameterByControlKey(layerKey, parameterKey, newValue, true, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeHost::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, std::string& error)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
|
||||||
@@ -1087,6 +1114,7 @@ bool RuntimeHost::UpdateLayerParameterByControlKey(const std::string& layerKey,
|
|||||||
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
|
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
|
||||||
const double triggerTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
|
const double triggerTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
|
||||||
value.numberValues = { previousCount + 1.0, triggerTime };
|
value.numberValues = { previousCount + 1.0, triggerTime };
|
||||||
|
MarkParameterStateDirtyLocked();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1095,7 +1123,141 @@ bool RuntimeHost::UpdateLayerParameterByControlKey(const std::string& layerKey,
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
matchedLayer->parameterValues[parameterIt->id] = normalized;
|
matchedLayer->parameterValues[parameterIt->id] = normalized;
|
||||||
return SavePersistentState(error);
|
MarkParameterStateDirtyLocked();
|
||||||
|
return !persistState || SavePersistentState(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeHost::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)
|
||||||
|
{
|
||||||
|
keepApplying = false;
|
||||||
|
resolvedLayerId.clear();
|
||||||
|
resolvedParameterId.clear();
|
||||||
|
appliedValue = ShaderParameterValue();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
|
||||||
|
LayerPersistentState* matchedLayer = nullptr;
|
||||||
|
const ShaderPackage* matchedPackage = nullptr;
|
||||||
|
for (LayerPersistentState& layer : mPersistentState.layers)
|
||||||
|
{
|
||||||
|
auto shaderIt = mPackagesById.find(layer.shaderId);
|
||||||
|
if (shaderIt == mPackagesById.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (MatchesControlKey(layer.id, layerKey) || MatchesControlKey(shaderIt->second.id, layerKey) ||
|
||||||
|
MatchesControlKey(shaderIt->second.displayName, layerKey))
|
||||||
|
{
|
||||||
|
matchedLayer = &layer;
|
||||||
|
matchedPackage = &shaderIt->second;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matchedLayer || !matchedPackage)
|
||||||
|
{
|
||||||
|
error = "Unknown OSC layer key: " + layerKey;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedLayerId = matchedLayer->id;
|
||||||
|
|
||||||
|
const 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedParameterId = parameterIt->id;
|
||||||
|
|
||||||
|
if (parameterIt->type == ShaderParameterType::Trigger)
|
||||||
|
{
|
||||||
|
ShaderParameterValue& value = matchedLayer->parameterValues[parameterIt->id];
|
||||||
|
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
|
||||||
|
const double triggerTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
|
||||||
|
value.numberValues = { previousCount + 1.0, triggerTime };
|
||||||
|
MarkParameterStateDirtyLocked();
|
||||||
|
appliedValue = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderParameterValue normalizedTarget;
|
||||||
|
if (!NormalizeAndValidateValue(*parameterIt, targetValue, normalizedTarget, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const bool smoothableType =
|
||||||
|
parameterIt->type == ShaderParameterType::Float ||
|
||||||
|
parameterIt->type == ShaderParameterType::Vec2 ||
|
||||||
|
parameterIt->type == ShaderParameterType::Color;
|
||||||
|
const bool smoothableInput = targetValue.isNumber() || JsonValueContainsOnlyNumbers(targetValue);
|
||||||
|
const double alpha = Clamp01(smoothingAmount);
|
||||||
|
|
||||||
|
if (!smoothableType || !smoothableInput || alpha <= 0.0)
|
||||||
|
{
|
||||||
|
matchedLayer->parameterValues[parameterIt->id] = normalizedTarget;
|
||||||
|
MarkParameterStateDirtyLocked();
|
||||||
|
appliedValue = normalizedTarget;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderParameterValue currentValue = DefaultValueForDefinition(*parameterIt);
|
||||||
|
auto currentIt = matchedLayer->parameterValues.find(parameterIt->id);
|
||||||
|
if (currentIt != matchedLayer->parameterValues.end())
|
||||||
|
currentValue = currentIt->second;
|
||||||
|
|
||||||
|
ShaderParameterValue nextValue = normalizedTarget;
|
||||||
|
nextValue.numberValues = normalizedTarget.numberValues;
|
||||||
|
|
||||||
|
if (currentValue.numberValues.size() != normalizedTarget.numberValues.size())
|
||||||
|
currentValue.numberValues = normalizedTarget.numberValues;
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
bool converged = true;
|
||||||
|
for (std::size_t index = 0; index < normalizedTarget.numberValues.size(); ++index)
|
||||||
|
{
|
||||||
|
const double currentNumber = currentValue.numberValues[index];
|
||||||
|
const double targetNumber = normalizedTarget.numberValues[index];
|
||||||
|
const double delta = targetNumber - currentNumber;
|
||||||
|
double nextNumber = currentNumber + delta * alpha;
|
||||||
|
|
||||||
|
if (std::fabs(delta) <= 0.0005)
|
||||||
|
{
|
||||||
|
nextNumber = targetNumber;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
converged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::fabs(nextNumber - currentNumber) > 0.0000001)
|
||||||
|
changed = true;
|
||||||
|
|
||||||
|
nextValue.numberValues[index] = nextNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!converged)
|
||||||
|
{
|
||||||
|
keepApplying = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nextValue.numberValues = normalizedTarget.numberValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed && !keepApplying)
|
||||||
|
{
|
||||||
|
appliedValue = matchedLayer->parameterValues[parameterIt->id];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchedLayer->parameterValues[parameterIt->id] = nextValue;
|
||||||
|
MarkParameterStateDirtyLocked();
|
||||||
|
appliedValue = nextValue;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeHost::ResetLayerParameters(const std::string& layerId, std::string& error)
|
bool RuntimeHost::ResetLayerParameters(const std::string& layerId, std::string& error)
|
||||||
@@ -1118,6 +1280,7 @@ bool RuntimeHost::ResetLayerParameters(const std::string& layerId, std::string&
|
|||||||
|
|
||||||
layer->parameterValues.clear();
|
layer->parameterValues.clear();
|
||||||
EnsureLayerDefaultsLocked(*layer, shaderIt->second);
|
EnsureLayerDefaultsLocked(*layer, shaderIt->second);
|
||||||
|
MarkParameterStateDirtyLocked();
|
||||||
return SavePersistentState(error);
|
return SavePersistentState(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1177,6 +1340,7 @@ bool RuntimeHost::LoadStackPreset(const std::string& presetName, std::string& er
|
|||||||
|
|
||||||
mPersistentState.layers = nextLayers;
|
mPersistentState.layers = nextLayers;
|
||||||
mReloadRequested = true;
|
mReloadRequested = true;
|
||||||
|
MarkRenderStateDirtyLocked();
|
||||||
return SavePersistentState(error);
|
return SavePersistentState(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1188,12 +1352,22 @@ void RuntimeHost::SetCompileStatus(bool succeeded, const std::string& message)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeHost::SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
void RuntimeHost::SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
||||||
|
{
|
||||||
|
mHealthTelemetry.ReportSignalStatus(hasSignal, width, height, modeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeHost::TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
||||||
|
{
|
||||||
|
return mHealthTelemetry.TryReportSignalStatus(hasSignal, width, height, modeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeHost::WriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
SetSignalStatusLocked(hasSignal, width, height, modeName);
|
SetSignalStatusLocked(hasSignal, width, height, modeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeHost::TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
bool RuntimeHost::TryWriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||||
if (!lock.owns_lock())
|
if (!lock.owns_lock())
|
||||||
@@ -1205,16 +1379,41 @@ bool RuntimeHost::TrySetSignalStatus(bool hasSignal, unsigned width, unsigned he
|
|||||||
|
|
||||||
void RuntimeHost::SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
void RuntimeHost::SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
||||||
{
|
{
|
||||||
|
const bool changed = mHasSignal != hasSignal ||
|
||||||
|
mSignalWidth != width ||
|
||||||
|
mSignalHeight != height ||
|
||||||
|
mSignalModeName != modeName;
|
||||||
mHasSignal = hasSignal;
|
mHasSignal = hasSignal;
|
||||||
mSignalWidth = width;
|
mSignalWidth = width;
|
||||||
mSignalHeight = height;
|
mSignalHeight = height;
|
||||||
mSignalModeName = modeName;
|
mSignalModeName = modeName;
|
||||||
|
if (changed)
|
||||||
|
MarkRenderStateDirtyLocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeHost::MarkRenderStateDirtyLocked()
|
||||||
|
{
|
||||||
|
mRenderStateVersion.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeHost::MarkParameterStateDirtyLocked()
|
||||||
|
{
|
||||||
|
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeHost::SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
void RuntimeHost::SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
|
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
|
||||||
|
{
|
||||||
|
SetVideoIOStatus("decklink", modelName, supportsInternalKeying, supportsExternalKeying, keyerInterfaceAvailable,
|
||||||
|
externalKeyingRequested, externalKeyingActive, statusMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeHost::SetVideoIOStatus(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);
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
mDeckLinkOutputStatus.backendName = backendName;
|
||||||
mDeckLinkOutputStatus.modelName = modelName;
|
mDeckLinkOutputStatus.modelName = modelName;
|
||||||
mDeckLinkOutputStatus.supportsInternalKeying = supportsInternalKeying;
|
mDeckLinkOutputStatus.supportsInternalKeying = supportsInternalKeying;
|
||||||
mDeckLinkOutputStatus.supportsExternalKeying = supportsExternalKeying;
|
mDeckLinkOutputStatus.supportsExternalKeying = supportsExternalKeying;
|
||||||
@@ -1225,12 +1424,22 @@ void RuntimeHost::SetDeckLinkOutputStatus(const std::string& modelName, bool sup
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
||||||
|
{
|
||||||
|
mHealthTelemetry.RecordPerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeHost::TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
||||||
|
{
|
||||||
|
return mHealthTelemetry.TryRecordPerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeHost::WritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
SetPerformanceStatsLocked(frameBudgetMilliseconds, renderMilliseconds);
|
SetPerformanceStatsLocked(frameBudgetMilliseconds, renderMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeHost::TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
bool RuntimeHost::TryWritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||||
if (!lock.owns_lock())
|
if (!lock.owns_lock())
|
||||||
@@ -1252,13 +1461,27 @@ void RuntimeHost::SetPerformanceStatsLocked(double frameBudgetMilliseconds, doub
|
|||||||
|
|
||||||
void RuntimeHost::SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
void RuntimeHost::SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
|
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
|
||||||
|
{
|
||||||
|
mHealthTelemetry.RecordFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
|
||||||
|
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeHost::TrySetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||||
|
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
|
||||||
|
{
|
||||||
|
return mHealthTelemetry.TryRecordFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
|
||||||
|
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeHost::WriteFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||||
|
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
SetFramePacingStatsLocked(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
|
SetFramePacingStatsLocked(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
|
||||||
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
|
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeHost::TrySetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
bool RuntimeHost::TryWriteFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
|
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||||
@@ -1292,7 +1515,7 @@ bool RuntimeHost::TryAdvanceFrame()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeHost::BuildLayerFragmentShaderSource(const std::string& layerId, std::string& fragmentShaderSource, std::string& error)
|
bool RuntimeHost::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1316,16 +1539,30 @@ bool RuntimeHost::BuildLayerFragmentShaderSource(const std::string& layerId, std
|
|||||||
}
|
}
|
||||||
|
|
||||||
ShaderCompiler compiler(mRepoRoot, mWrapperPath, mGeneratedGlslPath, mPatchedGlslPath, mConfig.maxTemporalHistoryFrames);
|
ShaderCompiler compiler(mRepoRoot, mWrapperPath, mGeneratedGlslPath, mPatchedGlslPath, mConfig.maxTemporalHistoryFrames);
|
||||||
return compiler.BuildLayerFragmentShaderSource(shaderPackage, fragmentShaderSource, error);
|
// Compile every declared pass while the caller remains backend-neutral.
|
||||||
|
// The GL layer decides how the resulting pass sources are routed.
|
||||||
|
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)
|
catch (const std::exception& exception)
|
||||||
{
|
{
|
||||||
error = std::string("RuntimeHost::BuildLayerFragmentShaderSource exception: ") + exception.what();
|
error = std::string("RuntimeHost::BuildLayerPassFragmentShaderSources exception: ") + exception.what();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
error = "RuntimeHost::BuildLayerFragmentShaderSource threw a non-standard exception.";
|
error = "RuntimeHost::BuildLayerPassFragmentShaderSources threw a non-standard exception.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1349,6 +1586,34 @@ bool RuntimeHost::TryGetLayerRenderStates(unsigned outputWidth, unsigned outputH
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RuntimeHost::TryRefreshCachedLayerStates(std::vector<RuntimeRenderState>& states) const
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||||
|
if (!lock.owns_lock())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (RuntimeRenderState& state : states)
|
||||||
|
{
|
||||||
|
const auto layerIt = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
|
||||||
|
[&state](const LayerPersistentState& layer) { return layer.id == state.layerId; });
|
||||||
|
if (layerIt == mPersistentState.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void RuntimeHost::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
|
void RuntimeHost::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
|
||||||
{
|
{
|
||||||
const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot();
|
const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot();
|
||||||
@@ -1376,6 +1641,7 @@ void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned ou
|
|||||||
RuntimeRenderState state;
|
RuntimeRenderState state;
|
||||||
state.layerId = layer.id;
|
state.layerId = layer.id;
|
||||||
state.shaderId = layer.shaderId;
|
state.shaderId = layer.shaderId;
|
||||||
|
state.shaderName = shaderIt->second.displayName;
|
||||||
state.mixAmount = 1.0;
|
state.mixAmount = 1.0;
|
||||||
state.bypass = layer.bypass ? 1.0 : 0.0;
|
state.bypass = layer.bypass ? 1.0 : 0.0;
|
||||||
state.inputWidth = mSignalWidth;
|
state.inputWidth = mSignalWidth;
|
||||||
@@ -1389,6 +1655,7 @@ void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned ou
|
|||||||
state.temporalHistorySource = shaderIt->second.temporal.historySource;
|
state.temporalHistorySource = shaderIt->second.temporal.historySource;
|
||||||
state.requestedTemporalHistoryLength = shaderIt->second.temporal.requestedHistoryLength;
|
state.requestedTemporalHistoryLength = shaderIt->second.temporal.requestedHistoryLength;
|
||||||
state.effectiveTemporalHistoryLength = shaderIt->second.temporal.effectiveHistoryLength;
|
state.effectiveTemporalHistoryLength = shaderIt->second.temporal.effectiveHistoryLength;
|
||||||
|
state.feedback = shaderIt->second.feedback;
|
||||||
|
|
||||||
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
|
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
|
||||||
{
|
{
|
||||||
@@ -1435,6 +1702,10 @@ bool RuntimeHost::LoadConfig(std::string& error)
|
|||||||
mConfig.serverPort = static_cast<unsigned short>(serverPortValue->asNumber(mConfig.serverPort));
|
mConfig.serverPort = static_cast<unsigned short>(serverPortValue->asNumber(mConfig.serverPort));
|
||||||
if (const JsonValue* oscPortValue = configJson.find("oscPort"))
|
if (const JsonValue* oscPortValue = configJson.find("oscPort"))
|
||||||
mConfig.oscPort = static_cast<unsigned short>(oscPortValue->asNumber(mConfig.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"))
|
if (const JsonValue* autoReloadValue = configJson.find("autoReload"))
|
||||||
mConfig.autoReload = autoReloadValue->asBoolean(mConfig.autoReload);
|
mConfig.autoReload = autoReloadValue->asBoolean(mConfig.autoReload);
|
||||||
if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames"))
|
if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames"))
|
||||||
@@ -1442,6 +1713,11 @@ bool RuntimeHost::LoadConfig(std::string& error)
|
|||||||
const double configuredValue = maxTemporalHistoryFramesValue->asNumber(static_cast<double>(mConfig.maxTemporalHistoryFrames));
|
const double configuredValue = maxTemporalHistoryFramesValue->asNumber(static_cast<double>(mConfig.maxTemporalHistoryFrames));
|
||||||
mConfig.maxTemporalHistoryFrames = configuredValue <= 0.0 ? 0u : static_cast<unsigned>(configuredValue);
|
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"))
|
if (const JsonValue* enableExternalKeyingValue = configJson.find("enableExternalKeying"))
|
||||||
mConfig.enableExternalKeying = enableExternalKeyingValue->asBoolean(mConfig.enableExternalKeying);
|
mConfig.enableExternalKeying = enableExternalKeyingValue->asBoolean(mConfig.enableExternalKeying);
|
||||||
if (const JsonValue* videoFormatValue = configJson.find("videoFormat"))
|
if (const JsonValue* videoFormatValue = configJson.find("videoFormat"))
|
||||||
@@ -1660,42 +1936,11 @@ bool RuntimeHost::ScanShaderPackages(std::string& error)
|
|||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MarkRenderStateDirtyLocked();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeHost::ParseShaderManifest(const std::filesystem::path& manifestPath, ShaderPackage& shaderPackage, std::string& error) const
|
|
||||||
{
|
|
||||||
const std::string manifestText = ReadTextFile(manifestPath, error);
|
|
||||||
if (manifestText.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
JsonValue manifestJson;
|
|
||||||
if (!ParseJson(manifestText, manifestJson, error))
|
|
||||||
return false;
|
|
||||||
if (!manifestJson.isObject())
|
|
||||||
{
|
|
||||||
error = "Shader manifest root must be an object: " + manifestPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ParseShaderMetadata(manifestJson, shaderPackage, manifestPath, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!std::filesystem::exists(shaderPackage.shaderPath))
|
|
||||||
{
|
|
||||||
error = "Shader source not found for package " + shaderPackage.id + ": " + shaderPackage.shaderPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
shaderPackage.shaderWriteTime = std::filesystem::last_write_time(shaderPackage.shaderPath);
|
|
||||||
shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath);
|
|
||||||
|
|
||||||
return ParseTextureAssets(manifestJson, shaderPackage, manifestPath, error) &&
|
|
||||||
ParseFontAssets(manifestJson, shaderPackage, manifestPath, error) &&
|
|
||||||
ParseTemporalSettings(manifestJson, shaderPackage, mConfig.maxTemporalHistoryFrames, manifestPath, error) &&
|
|
||||||
ParseParameterDefinitions(manifestJson, shaderPackage, manifestPath, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeHost::NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const
|
bool RuntimeHost::NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const
|
||||||
{
|
{
|
||||||
return NormalizeAndValidateParameterValue(definition, value, normalizedValue, error);
|
return NormalizeAndValidateParameterValue(definition, value, normalizedValue, error);
|
||||||
@@ -1857,8 +2102,11 @@ JsonValue RuntimeHost::BuildStateValue() const
|
|||||||
JsonValue app = JsonValue::MakeObject();
|
JsonValue app = JsonValue::MakeObject();
|
||||||
app.set("serverPort", JsonValue(static_cast<double>(mServerPort)));
|
app.set("serverPort", JsonValue(static_cast<double>(mServerPort)));
|
||||||
app.set("oscPort", JsonValue(static_cast<double>(mConfig.oscPort)));
|
app.set("oscPort", JsonValue(static_cast<double>(mConfig.oscPort)));
|
||||||
|
app.set("oscBindAddress", JsonValue(mConfig.oscBindAddress));
|
||||||
|
app.set("oscSmoothing", JsonValue(mConfig.oscSmoothing));
|
||||||
app.set("autoReload", JsonValue(mAutoReloadEnabled));
|
app.set("autoReload", JsonValue(mAutoReloadEnabled));
|
||||||
app.set("maxTemporalHistoryFrames", JsonValue(static_cast<double>(mConfig.maxTemporalHistoryFrames)));
|
app.set("maxTemporalHistoryFrames", JsonValue(static_cast<double>(mConfig.maxTemporalHistoryFrames)));
|
||||||
|
app.set("previewFps", JsonValue(static_cast<double>(mConfig.previewFps)));
|
||||||
app.set("enableExternalKeying", JsonValue(mConfig.enableExternalKeying));
|
app.set("enableExternalKeying", JsonValue(mConfig.enableExternalKeying));
|
||||||
app.set("inputVideoFormat", JsonValue(mConfig.inputVideoFormat));
|
app.set("inputVideoFormat", JsonValue(mConfig.inputVideoFormat));
|
||||||
app.set("inputFrameRate", JsonValue(mConfig.inputFrameRate));
|
app.set("inputFrameRate", JsonValue(mConfig.inputFrameRate));
|
||||||
@@ -1889,6 +2137,17 @@ JsonValue RuntimeHost::BuildStateValue() const
|
|||||||
deckLink.set("statusMessage", JsonValue(mDeckLinkOutputStatus.statusMessage));
|
deckLink.set("statusMessage", JsonValue(mDeckLinkOutputStatus.statusMessage));
|
||||||
root.set("decklink", deckLink);
|
root.set("decklink", deckLink);
|
||||||
|
|
||||||
|
JsonValue videoIO = JsonValue::MakeObject();
|
||||||
|
videoIO.set("backend", JsonValue(mDeckLinkOutputStatus.backendName));
|
||||||
|
videoIO.set("modelName", JsonValue(mDeckLinkOutputStatus.modelName));
|
||||||
|
videoIO.set("supportsInternalKeying", JsonValue(mDeckLinkOutputStatus.supportsInternalKeying));
|
||||||
|
videoIO.set("supportsExternalKeying", JsonValue(mDeckLinkOutputStatus.supportsExternalKeying));
|
||||||
|
videoIO.set("keyerInterfaceAvailable", JsonValue(mDeckLinkOutputStatus.keyerInterfaceAvailable));
|
||||||
|
videoIO.set("externalKeyingRequested", JsonValue(mDeckLinkOutputStatus.externalKeyingRequested));
|
||||||
|
videoIO.set("externalKeyingActive", JsonValue(mDeckLinkOutputStatus.externalKeyingActive));
|
||||||
|
videoIO.set("statusMessage", JsonValue(mDeckLinkOutputStatus.statusMessage));
|
||||||
|
root.set("videoIO", videoIO);
|
||||||
|
|
||||||
JsonValue performance = JsonValue::MakeObject();
|
JsonValue performance = JsonValue::MakeObject();
|
||||||
performance.set("frameBudgetMs", JsonValue(mFrameBudgetMilliseconds));
|
performance.set("frameBudgetMs", JsonValue(mFrameBudgetMilliseconds));
|
||||||
performance.set("renderMs", JsonValue(mRenderMilliseconds));
|
performance.set("renderMs", JsonValue(mRenderMilliseconds));
|
||||||
@@ -1924,6 +2183,13 @@ JsonValue RuntimeHost::BuildStateValue() const
|
|||||||
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderIt->second.temporal.effectiveHistoryLength)));
|
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderIt->second.temporal.effectiveHistoryLength)));
|
||||||
shader.set("temporal", temporal);
|
shader.set("temporal", temporal);
|
||||||
}
|
}
|
||||||
|
if (status.available && shaderIt != mPackagesById.end() && shaderIt->second.feedback.enabled)
|
||||||
|
{
|
||||||
|
JsonValue feedback = JsonValue::MakeObject();
|
||||||
|
feedback.set("enabled", JsonValue(true));
|
||||||
|
feedback.set("writePass", JsonValue(shaderIt->second.feedback.writePassId));
|
||||||
|
shader.set("feedback", feedback);
|
||||||
|
}
|
||||||
shaderLibrary.pushBack(shader);
|
shaderLibrary.pushBack(shader);
|
||||||
}
|
}
|
||||||
root.set("shaders", shaderLibrary);
|
root.set("shaders", shaderLibrary);
|
||||||
@@ -1961,6 +2227,13 @@ JsonValue RuntimeHost::SerializeLayerStackLocked() const
|
|||||||
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderIt->second.temporal.effectiveHistoryLength)));
|
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderIt->second.temporal.effectiveHistoryLength)));
|
||||||
layerValue.set("temporal", temporal);
|
layerValue.set("temporal", temporal);
|
||||||
}
|
}
|
||||||
|
if (shaderIt->second.feedback.enabled)
|
||||||
|
{
|
||||||
|
JsonValue feedback = JsonValue::MakeObject();
|
||||||
|
feedback.set("enabled", JsonValue(true));
|
||||||
|
feedback.set("writePass", JsonValue(shaderIt->second.feedback.writePassId));
|
||||||
|
layerValue.set("feedback", feedback);
|
||||||
|
}
|
||||||
|
|
||||||
JsonValue parameters = JsonValue::MakeArray();
|
JsonValue parameters = JsonValue::MakeArray();
|
||||||
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
|
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
|
||||||
@@ -1968,6 +2241,8 @@ JsonValue RuntimeHost::SerializeLayerStackLocked() const
|
|||||||
JsonValue parameter = JsonValue::MakeObject();
|
JsonValue parameter = JsonValue::MakeObject();
|
||||||
parameter.set("id", JsonValue(definition.id));
|
parameter.set("id", JsonValue(definition.id));
|
||||||
parameter.set("label", JsonValue(definition.label));
|
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("type", JsonValue(ShaderParameterTypeToString(definition.type)));
|
||||||
parameter.set("defaultValue", SerializeParameterValue(definition, DefaultValueForDefinition(definition)));
|
parameter.set("defaultValue", SerializeParameterValue(definition, DefaultValueForDefinition(definition)));
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "HealthTelemetry.h"
|
||||||
#include "RuntimeJson.h"
|
#include "RuntimeJson.h"
|
||||||
#include "ShaderTypes.h"
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class RuntimeHost
|
class RuntimeHost
|
||||||
@@ -30,6 +32,8 @@ public:
|
|||||||
bool SetLayerShader(const std::string& layerId, const std::string& shaderId, 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 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, 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 ResetLayerParameters(const std::string& layerId, std::string& error);
|
||||||
bool SaveStackPreset(const std::string& presetName, std::string& error) const;
|
bool SaveStackPreset(const std::string& presetName, std::string& error) const;
|
||||||
bool LoadStackPreset(const std::string& presetName, std::string& error);
|
bool LoadStackPreset(const std::string& presetName, std::string& error);
|
||||||
@@ -39,6 +43,8 @@ public:
|
|||||||
bool TrySetSignalStatus(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,
|
void SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
|
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);
|
void SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||||
bool TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
bool TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||||
void SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
void SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||||
@@ -47,12 +53,17 @@ public:
|
|||||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
||||||
void AdvanceFrame();
|
void AdvanceFrame();
|
||||||
bool TryAdvanceFrame();
|
bool TryAdvanceFrame();
|
||||||
|
HealthTelemetry& GetHealthTelemetry() { return mHealthTelemetry; }
|
||||||
|
const HealthTelemetry& GetHealthTelemetry() const { return mHealthTelemetry; }
|
||||||
|
|
||||||
bool BuildLayerFragmentShaderSource(const std::string& layerId, std::string& fragmentShaderSource, std::string& error);
|
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error);
|
||||||
std::vector<RuntimeRenderState> GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const;
|
std::vector<RuntimeRenderState> GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const;
|
||||||
bool TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) 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;
|
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
|
||||||
std::string BuildStateJson() 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& GetRepoRoot() const { return mRepoRoot; }
|
||||||
const std::filesystem::path& GetUiRoot() const { return mUiRoot; }
|
const std::filesystem::path& GetUiRoot() const { return mUiRoot; }
|
||||||
@@ -60,7 +71,10 @@ public:
|
|||||||
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
|
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
|
||||||
unsigned short GetServerPort() const { return mServerPort; }
|
unsigned short GetServerPort() const { return mServerPort; }
|
||||||
unsigned short GetOscPort() const { return mConfig.oscPort; }
|
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 GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; }
|
||||||
|
unsigned GetPreviewFps() const { return mConfig.previewFps; }
|
||||||
bool ExternalKeyingEnabled() const { return mConfig.enableExternalKeying; }
|
bool ExternalKeyingEnabled() const { return mConfig.enableExternalKeying; }
|
||||||
const std::string& GetInputVideoFormat() const { return mConfig.inputVideoFormat; }
|
const std::string& GetInputVideoFormat() const { return mConfig.inputVideoFormat; }
|
||||||
const std::string& GetInputFrameRate() const { return mConfig.inputFrameRate; }
|
const std::string& GetInputFrameRate() const { return mConfig.inputFrameRate; }
|
||||||
@@ -75,8 +89,11 @@ private:
|
|||||||
std::string shaderLibrary = "shaders";
|
std::string shaderLibrary = "shaders";
|
||||||
unsigned short serverPort = 8080;
|
unsigned short serverPort = 8080;
|
||||||
unsigned short oscPort = 9000;
|
unsigned short oscPort = 9000;
|
||||||
|
std::string oscBindAddress = "127.0.0.1";
|
||||||
|
double oscSmoothing = 0.18;
|
||||||
bool autoReload = true;
|
bool autoReload = true;
|
||||||
unsigned maxTemporalHistoryFrames = 4;
|
unsigned maxTemporalHistoryFrames = 4;
|
||||||
|
unsigned previewFps = 30;
|
||||||
bool enableExternalKeying = false;
|
bool enableExternalKeying = false;
|
||||||
std::string inputVideoFormat = "1080p";
|
std::string inputVideoFormat = "1080p";
|
||||||
std::string inputFrameRate = "59.94";
|
std::string inputFrameRate = "59.94";
|
||||||
@@ -86,6 +103,7 @@ private:
|
|||||||
|
|
||||||
struct DeckLinkOutputStatus
|
struct DeckLinkOutputStatus
|
||||||
{
|
{
|
||||||
|
std::string backendName = "decklink";
|
||||||
std::string modelName;
|
std::string modelName;
|
||||||
bool supportsInternalKeying = false;
|
bool supportsInternalKeying = false;
|
||||||
bool supportsExternalKeying = false;
|
bool supportsExternalKeying = false;
|
||||||
@@ -112,7 +130,6 @@ private:
|
|||||||
bool LoadPersistentState(std::string& error);
|
bool LoadPersistentState(std::string& error);
|
||||||
bool SavePersistentState(std::string& error) const;
|
bool SavePersistentState(std::string& error) const;
|
||||||
bool ScanShaderPackages(std::string& error);
|
bool ScanShaderPackages(std::string& error);
|
||||||
bool ParseShaderManifest(const std::filesystem::path& manifestPath, ShaderPackage& shaderPackage, std::string& error) const;
|
|
||||||
bool NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const;
|
bool NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const;
|
||||||
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition) const;
|
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition) const;
|
||||||
void EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const;
|
void EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const;
|
||||||
@@ -131,12 +148,24 @@ private:
|
|||||||
LayerPersistentState* FindLayerById(const std::string& layerId);
|
LayerPersistentState* FindLayerById(const std::string& layerId);
|
||||||
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
|
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
|
||||||
std::string GenerateLayerId();
|
std::string GenerateLayerId();
|
||||||
|
void WriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||||
|
bool TryWriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||||
void SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
void SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||||
|
void MarkRenderStateDirtyLocked();
|
||||||
|
void MarkParameterStateDirtyLocked();
|
||||||
|
void WritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||||
|
bool TryWritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||||
void SetPerformanceStatsLocked(double frameBudgetMilliseconds, double renderMilliseconds);
|
void SetPerformanceStatsLocked(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||||
|
void WriteFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||||
|
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
||||||
|
bool TryWriteFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||||
|
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
||||||
void SetFramePacingStatsLocked(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
void SetFramePacingStatsLocked(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend class HealthTelemetry;
|
||||||
|
HealthTelemetry mHealthTelemetry;
|
||||||
mutable std::mutex mMutex;
|
mutable std::mutex mMutex;
|
||||||
AppConfig mConfig;
|
AppConfig mConfig;
|
||||||
PersistentState mPersistentState;
|
PersistentState mPersistentState;
|
||||||
@@ -176,6 +205,8 @@ private:
|
|||||||
bool mAutoReloadEnabled;
|
bool mAutoReloadEnabled;
|
||||||
std::chrono::steady_clock::time_point mStartTime;
|
std::chrono::steady_clock::time_point mStartTime;
|
||||||
std::chrono::steady_clock::time_point mLastScanTime;
|
std::chrono::steady_clock::time_point mLastScanTime;
|
||||||
std::atomic<uint64_t> mFrameCounter;
|
std::atomic<uint64_t> mFrameCounter{ 0 };
|
||||||
|
std::atomic<uint64_t> mRenderStateVersion{ 0 };
|
||||||
|
std::atomic<uint64_t> mParameterStateVersion{ 0 };
|
||||||
uint64_t mNextLayerId;
|
uint64_t mNextLayerId;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -661,3 +661,14 @@ std::string SerializeJson(const JsonValue& value, bool pretty)
|
|||||||
SerializeJsonImpl(value, output, pretty, 0);
|
SerializeJsonImpl(value, output, pretty, 0);
|
||||||
return output.str();
|
return output.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<double> JsonArrayToNumbers(const JsonValue& value)
|
||||||
|
{
|
||||||
|
std::vector<double> numbers;
|
||||||
|
for (const JsonValue& item : value.asArray())
|
||||||
|
{
|
||||||
|
if (item.isNumber())
|
||||||
|
numbers.push_back(item.asNumber());
|
||||||
|
}
|
||||||
|
return numbers;
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,3 +62,4 @@ private:
|
|||||||
|
|
||||||
bool ParseJson(const std::string& text, JsonValue& value, std::string& error);
|
bool ParseJson(const std::string& text, JsonValue& value, std::string& error);
|
||||||
std::string SerializeJson(const JsonValue& value, bool pretty = false);
|
std::string SerializeJson(const JsonValue& value, bool pretty = false);
|
||||||
|
std::vector<double> JsonArrayToNumbers(const JsonValue& value);
|
||||||
|
|||||||
@@ -26,17 +26,6 @@ bool IsFiniteNumber(double value)
|
|||||||
return std::isfinite(value) != 0;
|
return std::isfinite(value) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<double> JsonArrayToNumbers(const JsonValue& value)
|
|
||||||
{
|
|
||||||
std::vector<double> numbers;
|
|
||||||
for (const JsonValue& item : value.asArray())
|
|
||||||
{
|
|
||||||
if (item.isNumber())
|
|
||||||
numbers.push_back(item.asNumber());
|
|
||||||
}
|
|
||||||
return numbers;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string NormalizeTextValue(const std::string& text, unsigned maxLength)
|
std::string NormalizeTextValue(const std::string& text, unsigned maxLength)
|
||||||
{
|
{
|
||||||
std::string normalized;
|
std::string normalized;
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
#include "RuntimeSnapshotProvider.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
RuntimeSnapshotProvider::RuntimeSnapshotProvider(RuntimeHost& runtimeHost) :
|
||||||
|
mRuntimeHost(runtimeHost)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.BuildLayerPassFragmentShaderSources(layerId, passSources, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetMaxTemporalHistoryFrames();
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
|
||||||
|
{
|
||||||
|
RuntimeSnapshotVersions versions;
|
||||||
|
versions.renderStateVersion = mRuntimeHost.GetRenderStateVersion();
|
||||||
|
versions.parameterStateVersion = mRuntimeHost.GetParameterStateVersion();
|
||||||
|
return versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeRenderFrameContext RuntimeSnapshotProvider::GetFrameContext() const
|
||||||
|
{
|
||||||
|
std::vector<RuntimeRenderState> stateScratch(1);
|
||||||
|
mRuntimeHost.RefreshDynamicRenderStateFields(stateScratch);
|
||||||
|
|
||||||
|
RuntimeRenderFrameContext frameContext;
|
||||||
|
const RuntimeRenderState& state = stateScratch.front();
|
||||||
|
frameContext.timeSeconds = state.timeSeconds;
|
||||||
|
frameContext.utcTimeSeconds = state.utcTimeSeconds;
|
||||||
|
frameContext.utcOffsetSeconds = state.utcOffsetSeconds;
|
||||||
|
frameContext.startupRandom = state.startupRandom;
|
||||||
|
frameContext.frameCount = state.frameCount;
|
||||||
|
return frameContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeSnapshotProvider::AdvanceFrame()
|
||||||
|
{
|
||||||
|
mRuntimeHost.AdvanceFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeSnapshotProvider::TryAdvanceFrame()
|
||||||
|
{
|
||||||
|
return mRuntimeHost.TryAdvanceFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeRenderStateSnapshot RuntimeSnapshotProvider::GetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
const RuntimeSnapshotVersions versionsBefore = GetVersions();
|
||||||
|
|
||||||
|
RuntimeRenderStateSnapshot snapshot;
|
||||||
|
snapshot.outputWidth = outputWidth;
|
||||||
|
snapshot.outputHeight = outputHeight;
|
||||||
|
snapshot.states = mRuntimeHost.GetLayerRenderStates(outputWidth, outputHeight);
|
||||||
|
|
||||||
|
const RuntimeSnapshotVersions versionsAfter = GetVersions();
|
||||||
|
if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion &&
|
||||||
|
versionsBefore.parameterStateVersion == versionsAfter.parameterStateVersion)
|
||||||
|
{
|
||||||
|
snapshot.versions = versionsAfter;
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeSnapshotProvider::TryGetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const
|
||||||
|
{
|
||||||
|
const RuntimeSnapshotVersions versionsBefore = GetVersions();
|
||||||
|
|
||||||
|
std::vector<RuntimeRenderState> states;
|
||||||
|
if (!mRuntimeHost.TryGetLayerRenderStates(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);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeSnapshotProvider::TryRefreshSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const
|
||||||
|
{
|
||||||
|
const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion;
|
||||||
|
if (!mRuntimeHost.TryRefreshCachedLayerStates(snapshot.states))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const RuntimeSnapshotVersions versions = GetVersions();
|
||||||
|
if (versions.renderStateVersion != expectedRenderStateVersion)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
snapshot.versions = versions;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeSnapshotProvider::ApplyFrameContext(std::vector<RuntimeRenderState>& states, const RuntimeRenderFrameContext& frameContext) const
|
||||||
|
{
|
||||||
|
for (RuntimeRenderState& state : states)
|
||||||
|
{
|
||||||
|
state.timeSeconds = frameContext.timeSeconds;
|
||||||
|
state.utcTimeSeconds = frameContext.utcTimeSeconds;
|
||||||
|
state.utcOffsetSeconds = frameContext.utcOffsetSeconds;
|
||||||
|
state.startupRandom = frameContext.startupRandom;
|
||||||
|
state.frameCount = frameContext.frameCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeSnapshotProvider::ApplyFrameContext(RuntimeRenderStateSnapshot& snapshot, const RuntimeRenderFrameContext& frameContext) const
|
||||||
|
{
|
||||||
|
ApplyFrameContext(snapshot.states, frameContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<RuntimeRenderState> RuntimeSnapshotProvider::GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const
|
||||||
|
{
|
||||||
|
return GetRenderStateSnapshot(outputWidth, outputHeight).states;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeSnapshotProvider::TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
|
||||||
|
{
|
||||||
|
RuntimeRenderStateSnapshot snapshot;
|
||||||
|
if (!TryGetRenderStateSnapshot(outputWidth, outputHeight, snapshot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
states = std::move(snapshot.states);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeSnapshotProvider::TryRefreshCachedLayerStates(std::vector<RuntimeRenderState>& states) const
|
||||||
|
{
|
||||||
|
RuntimeRenderStateSnapshot snapshot;
|
||||||
|
snapshot.versions.renderStateVersion = mRuntimeHost.GetRenderStateVersion();
|
||||||
|
snapshot.versions.parameterStateVersion = mRuntimeHost.GetParameterStateVersion();
|
||||||
|
snapshot.states = states;
|
||||||
|
if (!TryRefreshSnapshotParameters(snapshot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
states = std::move(snapshot.states);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
|
||||||
|
{
|
||||||
|
ApplyFrameContext(states, GetFrameContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t RuntimeSnapshotProvider::GetRenderStateVersion() const
|
||||||
|
{
|
||||||
|
return GetVersions().renderStateVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t RuntimeSnapshotProvider::GetParameterStateVersion() const
|
||||||
|
{
|
||||||
|
return GetVersions().parameterStateVersion;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "RuntimeHost.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct RuntimeSnapshotVersions
|
||||||
|
{
|
||||||
|
uint64_t renderStateVersion = 0;
|
||||||
|
uint64_t parameterStateVersion = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RuntimeRenderFrameContext
|
||||||
|
{
|
||||||
|
double timeSeconds = 0.0;
|
||||||
|
double utcTimeSeconds = 0.0;
|
||||||
|
double utcOffsetSeconds = 0.0;
|
||||||
|
double startupRandom = 0.0;
|
||||||
|
double frameCount = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RuntimeRenderStateSnapshot
|
||||||
|
{
|
||||||
|
RuntimeSnapshotVersions versions;
|
||||||
|
unsigned outputWidth = 0;
|
||||||
|
unsigned outputHeight = 0;
|
||||||
|
std::vector<RuntimeRenderState> states;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RuntimeSnapshotProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RuntimeSnapshotProvider(RuntimeHost& runtimeHost);
|
||||||
|
|
||||||
|
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
|
||||||
|
unsigned GetMaxTemporalHistoryFrames() const;
|
||||||
|
RuntimeSnapshotVersions GetVersions() const;
|
||||||
|
RuntimeRenderFrameContext GetFrameContext() const;
|
||||||
|
void AdvanceFrame();
|
||||||
|
bool TryAdvanceFrame();
|
||||||
|
RuntimeRenderStateSnapshot GetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const;
|
||||||
|
bool TryGetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const;
|
||||||
|
bool TryRefreshSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const;
|
||||||
|
void ApplyFrameContext(std::vector<RuntimeRenderState>& states, const RuntimeRenderFrameContext& frameContext) const;
|
||||||
|
void ApplyFrameContext(RuntimeRenderStateSnapshot& snapshot, const RuntimeRenderFrameContext& frameContext) const;
|
||||||
|
|
||||||
|
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;
|
||||||
|
uint64_t GetRenderStateVersion() const;
|
||||||
|
uint64_t GetParameterStateVersion() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
RuntimeHost& mRuntimeHost;
|
||||||
|
};
|
||||||
161
apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp
Normal file
161
apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
#include "RuntimeStore.h"
|
||||||
|
|
||||||
|
RuntimeStore::RuntimeStore(RuntimeHost& runtimeHost) :
|
||||||
|
mRuntimeHost(runtimeHost)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::InitializeStore(std::string& error)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.Initialize(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RuntimeStore::BuildPersistentStateJson() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.BuildStateJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::CreateStoredLayer(const std::string& shaderId, std::string& error)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.AddLayer(shaderId, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::DeleteStoredLayer(const std::string& layerId, std::string& error)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.RemoveLayer(layerId, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::MoveStoredLayer(const std::string& layerId, int direction, std::string& error)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.MoveLayer(layerId, direction, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::MoveStoredLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.MoveLayerToIndex(layerId, targetIndex, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::SetStoredLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.SetLayerBypass(layerId, bypassed, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, const std::string& shaderId, std::string& error)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.SetLayerShader(layerId, shaderId, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, std::string& error)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.UpdateLayerParameter(layerId, parameterId, newValue, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.UpdateLayerParameterByControlKey(layerKey, parameterKey, newValue, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, std::string& error)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.UpdateLayerParameterByControlKey(layerKey, parameterKey, newValue, persistState, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, std::string& error)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.ResetLayerParameters(layerId, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.SaveStackPreset(presetName, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::string& error)
|
||||||
|
{
|
||||||
|
return mRuntimeHost.LoadStackPreset(presetName, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path& RuntimeStore::GetRuntimeRepositoryRoot() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetRepoRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path& RuntimeStore::GetRuntimeUiRoot() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetUiRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path& RuntimeStore::GetRuntimeDocsRoot() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetDocsRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path& RuntimeStore::GetRuntimeDataRoot() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetRuntimeRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned short RuntimeStore::GetConfiguredControlServerPort() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetServerPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned short RuntimeStore::GetConfiguredOscPort() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetOscPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& RuntimeStore::GetConfiguredOscBindAddress() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetOscBindAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
double RuntimeStore::GetConfiguredOscSmoothing() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetOscSmoothing();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned RuntimeStore::GetConfiguredMaxTemporalHistoryFrames() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetMaxTemporalHistoryFrames();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned RuntimeStore::GetConfiguredPreviewFps() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetPreviewFps();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeStore::IsExternalKeyingConfigured() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.ExternalKeyingEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& RuntimeStore::GetConfiguredInputVideoFormat() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetInputVideoFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& RuntimeStore::GetConfiguredInputFrameRate() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetInputFrameRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& RuntimeStore::GetConfiguredOutputVideoFormat() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetOutputVideoFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& RuntimeStore::GetConfiguredOutputFrameRate() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost.GetOutputFrameRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeStore::SetCompileStatus(bool succeeded, const std::string& message)
|
||||||
|
{
|
||||||
|
mRuntimeHost.SetCompileStatus(succeeded, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeStore::ClearReloadRequest()
|
||||||
|
{
|
||||||
|
mRuntimeHost.ClearReloadRequest();
|
||||||
|
}
|
||||||
50
apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h
Normal file
50
apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "RuntimeHost.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class RuntimeStore
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RuntimeStore(RuntimeHost& runtimeHost);
|
||||||
|
|
||||||
|
bool InitializeStore(std::string& error);
|
||||||
|
std::string BuildPersistentStateJson() const;
|
||||||
|
|
||||||
|
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 JsonValue& newValue, std::string& error);
|
||||||
|
bool SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error);
|
||||||
|
bool SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, 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);
|
||||||
|
|
||||||
|
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 SetCompileStatus(bool succeeded, const std::string& message);
|
||||||
|
void ClearReloadRequest();
|
||||||
|
|
||||||
|
private:
|
||||||
|
RuntimeHost& mRuntimeHost;
|
||||||
|
};
|
||||||
@@ -143,10 +143,10 @@ ShaderCompiler::ShaderCompiler(
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::BuildLayerFragmentShaderSource(const ShaderPackage& shaderPackage, std::string& fragmentShaderSource, std::string& error) const
|
bool ShaderCompiler::BuildPassFragmentShaderSource(const ShaderPackage& shaderPackage, const ShaderPassDefinition& pass, std::string& fragmentShaderSource, std::string& error) const
|
||||||
{
|
{
|
||||||
std::string wrapperSource;
|
std::string wrapperSource;
|
||||||
if (!BuildWrapperSlangSource(shaderPackage, wrapperSource, error))
|
if (!BuildWrapperSlangSource(shaderPackage, pass, wrapperSource, error))
|
||||||
return false;
|
return false;
|
||||||
if (!WriteTextFile(mWrapperPath, wrapperSource, error))
|
if (!WriteTextFile(mWrapperPath, wrapperSource, error))
|
||||||
return false;
|
return false;
|
||||||
@@ -167,7 +167,7 @@ bool ShaderCompiler::BuildLayerFragmentShaderSource(const ShaderPackage& shaderP
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::BuildWrapperSlangSource(const ShaderPackage& shaderPackage, std::string& wrapperSource, std::string& error) const
|
bool ShaderCompiler::BuildWrapperSlangSource(const ShaderPackage& shaderPackage, const ShaderPassDefinition& pass, std::string& wrapperSource, std::string& error) const
|
||||||
{
|
{
|
||||||
const std::filesystem::path templatePath = mRepoRoot / "runtime" / "templates" / "shader_wrapper.slang.in";
|
const std::filesystem::path templatePath = mRepoRoot / "runtime" / "templates" / "shader_wrapper.slang.in";
|
||||||
wrapperSource = ReadTextFile(templatePath, error);
|
wrapperSource = ReadTextFile(templatePath, error);
|
||||||
@@ -178,18 +178,35 @@ bool ShaderCompiler::BuildWrapperSlangSource(const ShaderPackage& shaderPackage,
|
|||||||
const unsigned historySamplerCount = shaderPackage.temporal.enabled ? mMaxTemporalHistoryFrames : 0;
|
const unsigned historySamplerCount = shaderPackage.temporal.enabled ? mMaxTemporalHistoryFrames : 0;
|
||||||
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gSourceHistory", historySamplerCount));
|
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gSourceHistory", historySamplerCount));
|
||||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gTemporalHistory", historySamplerCount));
|
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gTemporalHistory", historySamplerCount));
|
||||||
|
wrapperSource = ReplaceAll(wrapperSource, "{{FEEDBACK_SAMPLER}}", shaderPackage.feedback.enabled ? "Sampler2D<float4> gFeedbackState;\n" : "");
|
||||||
|
wrapperSource = ReplaceAll(wrapperSource, "{{FEEDBACK_HELPER}}",
|
||||||
|
shaderPackage.feedback.enabled
|
||||||
|
? "float4 sampleFeedback(float2 tc)\n{\n\tif (gFeedbackAvailable <= 0)\n\t\treturn float4(0.0, 0.0, 0.0, 0.0);\n\treturn gFeedbackState.Sample(tc);\n}\n"
|
||||||
|
: "float4 sampleFeedback(float2 tc)\n{\n\treturn float4(0.0, 0.0, 0.0, 0.0);\n}\n");
|
||||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEXTURE_SAMPLERS}}", BuildTextureSamplerDeclarations(shaderPackage.textureAssets));
|
wrapperSource = ReplaceAll(wrapperSource, "{{TEXTURE_SAMPLERS}}", BuildTextureSamplerDeclarations(shaderPackage.textureAssets));
|
||||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_SAMPLERS}}", BuildTextSamplerDeclarations(shaderPackage.parameters));
|
wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_SAMPLERS}}", BuildTextSamplerDeclarations(shaderPackage.parameters));
|
||||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_HELPERS}}", BuildTextHelpers(shaderPackage.parameters));
|
wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_HELPERS}}", BuildTextHelpers(shaderPackage.parameters));
|
||||||
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gSourceHistory", historySamplerCount));
|
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gSourceHistory", historySamplerCount));
|
||||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gTemporalHistory", historySamplerCount));
|
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gTemporalHistory", historySamplerCount));
|
||||||
wrapperSource = ReplaceAll(wrapperSource, "{{USER_SHADER_INCLUDE}}", shaderPackage.shaderPath.generic_string());
|
wrapperSource = ReplaceAll(wrapperSource, "{{USER_SHADER_INCLUDE}}", pass.sourcePath.generic_string());
|
||||||
wrapperSource = ReplaceAll(wrapperSource, "{{ENTRY_POINT_CALL}}", shaderPackage.entryPoint + "(context)");
|
wrapperSource = ReplaceAll(wrapperSource, "{{ENTRY_POINT_CALL}}", pass.entryPoint + "(context)");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const
|
bool ShaderCompiler::FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const
|
||||||
{
|
{
|
||||||
|
char slangRootBuffer[MAX_PATH] = {};
|
||||||
|
const DWORD slangRootLength = GetEnvironmentVariableA("SLANG_ROOT", slangRootBuffer, static_cast<DWORD>(sizeof(slangRootBuffer)));
|
||||||
|
if (slangRootLength > 0 && slangRootLength < sizeof(slangRootBuffer))
|
||||||
|
{
|
||||||
|
std::filesystem::path candidate = std::filesystem::path(slangRootBuffer) / "bin" / "slangc.exe";
|
||||||
|
if (std::filesystem::exists(candidate))
|
||||||
|
{
|
||||||
|
compilerPath = candidate;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::filesystem::path thirdPartyRoot = mRepoRoot / "3rdParty";
|
std::filesystem::path thirdPartyRoot = mRepoRoot / "3rdParty";
|
||||||
if (!std::filesystem::exists(thirdPartyRoot))
|
if (!std::filesystem::exists(thirdPartyRoot))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ public:
|
|||||||
const std::filesystem::path& patchedGlslPath,
|
const std::filesystem::path& patchedGlslPath,
|
||||||
unsigned maxTemporalHistoryFrames);
|
unsigned maxTemporalHistoryFrames);
|
||||||
|
|
||||||
bool BuildLayerFragmentShaderSource(const ShaderPackage& shaderPackage, std::string& fragmentShaderSource, std::string& error) const;
|
bool BuildPassFragmentShaderSource(const ShaderPackage& shaderPackage, const ShaderPassDefinition& pass, std::string& fragmentShaderSource, std::string& error) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool BuildWrapperSlangSource(const ShaderPackage& shaderPackage, std::string& wrapperSource, std::string& error) const;
|
bool BuildWrapperSlangSource(const ShaderPackage& shaderPackage, const ShaderPassDefinition& pass, std::string& wrapperSource, std::string& error) const;
|
||||||
bool FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const;
|
bool FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const;
|
||||||
bool RunSlangCompiler(const std::filesystem::path& wrapperPath, const std::filesystem::path& outputPath, std::string& error) const;
|
bool RunSlangCompiler(const std::filesystem::path& wrapperPath, const std::filesystem::path& outputPath, std::string& error) const;
|
||||||
bool PatchGeneratedGlsl(std::string& shaderText, std::string& error) const;
|
bool PatchGeneratedGlsl(std::string& shaderText, std::string& error) const;
|
||||||
|
|||||||
@@ -29,17 +29,6 @@ bool IsFiniteNumber(double value)
|
|||||||
return std::isfinite(value) != 0;
|
return std::isfinite(value) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<double> JsonArrayToNumbers(const JsonValue& value)
|
|
||||||
{
|
|
||||||
std::vector<double> numbers;
|
|
||||||
for (const JsonValue& item : value.asArray())
|
|
||||||
{
|
|
||||||
if (item.isNumber())
|
|
||||||
numbers.push_back(item.asNumber());
|
|
||||||
}
|
|
||||||
return numbers;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& type)
|
bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& type)
|
||||||
{
|
{
|
||||||
if (typeName == "float")
|
if (typeName == "float")
|
||||||
@@ -250,6 +239,107 @@ bool ParseShaderMetadata(const JsonValue& manifestJson, ShaderPackage& shaderPac
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ParsePassDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||||
|
{
|
||||||
|
const JsonValue* passesValue = nullptr;
|
||||||
|
if (!OptionalArrayField(manifestJson, "passes", passesValue, manifestPath, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!passesValue)
|
||||||
|
{
|
||||||
|
// Existing shader packages are treated as a single implicit pass, so
|
||||||
|
// multipass support does not require manifest churn.
|
||||||
|
ShaderPassDefinition pass;
|
||||||
|
pass.id = "main";
|
||||||
|
pass.entryPoint = shaderPackage.entryPoint;
|
||||||
|
pass.sourcePath = shaderPackage.shaderPath;
|
||||||
|
pass.outputName = "layerOutput";
|
||||||
|
if (!std::filesystem::exists(pass.sourcePath))
|
||||||
|
{
|
||||||
|
error = "Shader source not found for package " + shaderPackage.id + ": " + pass.sourcePath.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pass.sourceWriteTime = std::filesystem::last_write_time(pass.sourcePath);
|
||||||
|
shaderPackage.passes.push_back(pass);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passesValue->asArray().empty())
|
||||||
|
{
|
||||||
|
error = "Shader manifest 'passes' field must not be empty in: " + ManifestPathMessage(manifestPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const JsonValue& passJson : passesValue->asArray())
|
||||||
|
{
|
||||||
|
if (!passJson.isObject())
|
||||||
|
{
|
||||||
|
error = "Shader pass entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string passId;
|
||||||
|
std::string sourcePath;
|
||||||
|
if (!RequireNonEmptyStringField(passJson, "id", passId, manifestPath, error) ||
|
||||||
|
!RequireNonEmptyStringField(passJson, "source", sourcePath, manifestPath, error))
|
||||||
|
{
|
||||||
|
error = "Shader pass is missing required 'id' or 'source' in: " + ManifestPathMessage(manifestPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ValidateShaderIdentifier(passId, "passes[].id", manifestPath, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (const ShaderPassDefinition& existingPass : shaderPackage.passes)
|
||||||
|
{
|
||||||
|
if (existingPass.id == passId)
|
||||||
|
{
|
||||||
|
error = "Duplicate shader pass id '" + passId + "' in: " + ManifestPathMessage(manifestPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderPassDefinition pass;
|
||||||
|
pass.id = passId;
|
||||||
|
pass.sourcePath = shaderPackage.directoryPath / sourcePath;
|
||||||
|
if (!OptionalStringField(passJson, "entryPoint", pass.entryPoint, shaderPackage.entryPoint, manifestPath, error) ||
|
||||||
|
!OptionalStringField(passJson, "output", pass.outputName, passId, manifestPath, error))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ValidateShaderIdentifier(pass.entryPoint, "passes[].entryPoint", manifestPath, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const JsonValue* inputsValue = nullptr;
|
||||||
|
if (!OptionalArrayField(passJson, "inputs", inputsValue, manifestPath, error))
|
||||||
|
return false;
|
||||||
|
if (inputsValue)
|
||||||
|
{
|
||||||
|
for (const JsonValue& inputValue : inputsValue->asArray())
|
||||||
|
{
|
||||||
|
if (!inputValue.isString())
|
||||||
|
{
|
||||||
|
error = "Shader pass inputs must be strings in: " + ManifestPathMessage(manifestPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pass.inputNames.push_back(inputValue.asString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep source validation in the registry. Bad pass declarations then
|
||||||
|
// appear as unavailable shaders instead of failing at render time.
|
||||||
|
if (!std::filesystem::exists(pass.sourcePath))
|
||||||
|
{
|
||||||
|
error = "Shader pass source not found for package " + shaderPackage.id + ": " + pass.sourcePath.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pass.sourceWriteTime = std::filesystem::last_write_time(pass.sourcePath);
|
||||||
|
shaderPackage.passes.push_back(pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
shaderPackage.shaderPath = shaderPackage.passes.front().sourcePath;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool ParseTextureAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
bool ParseTextureAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||||
{
|
{
|
||||||
const JsonValue* texturesValue = nullptr;
|
const JsonValue* texturesValue = nullptr;
|
||||||
@@ -383,6 +473,46 @@ bool ParseTemporalSettings(const JsonValue& manifestJson, ShaderPackage& shaderP
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ParseFeedbackSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||||
|
{
|
||||||
|
const JsonValue* feedbackValue = nullptr;
|
||||||
|
if (!OptionalObjectField(manifestJson, "feedback", feedbackValue, manifestPath, error))
|
||||||
|
return false;
|
||||||
|
if (!feedbackValue)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const JsonValue* enabledValue = feedbackValue->find("enabled");
|
||||||
|
if (!enabledValue || !enabledValue->asBoolean(false))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
shaderPackage.feedback.enabled = true;
|
||||||
|
if (!OptionalStringField(*feedbackValue, "writePass", shaderPackage.feedback.writePassId, "", manifestPath, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (shaderPackage.feedback.writePassId.empty())
|
||||||
|
{
|
||||||
|
if (shaderPackage.passes.empty())
|
||||||
|
{
|
||||||
|
error = "Feedback-enabled shader has no passes to target in: " + ManifestPathMessage(manifestPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
shaderPackage.feedback.writePassId = shaderPackage.passes.back().id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ValidateShaderIdentifier(shaderPackage.feedback.writePassId, "feedback.writePass", manifestPath, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto passIt = std::find_if(shaderPackage.passes.begin(), shaderPackage.passes.end(),
|
||||||
|
[&shaderPackage](const ShaderPassDefinition& pass) { return pass.id == shaderPackage.feedback.writePassId; });
|
||||||
|
if (passIt == shaderPackage.passes.end())
|
||||||
|
{
|
||||||
|
error = "Feedback writePass '" + shaderPackage.feedback.writePassId + "' does not match any declared pass in: " + ManifestPathMessage(manifestPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool ParseParameterNumberField(const JsonValue& parameterJson, const char* fieldName, std::vector<double>& values, const std::filesystem::path& manifestPath, std::string& error)
|
bool ParseParameterNumberField(const JsonValue& parameterJson, const char* fieldName, std::vector<double>& values, const std::filesystem::path& manifestPath, std::string& error)
|
||||||
{
|
{
|
||||||
if (const JsonValue* fieldValue = parameterJson.find(fieldName))
|
if (const JsonValue* fieldValue = parameterJson.find(fieldName))
|
||||||
@@ -503,6 +633,9 @@ bool ParseParameterDefinition(const JsonValue& parameterJson, ShaderParameterDef
|
|||||||
if (!ValidateShaderIdentifier(definition.id, "parameters[].id", manifestPath, error))
|
if (!ValidateShaderIdentifier(definition.id, "parameters[].id", manifestPath, error))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (!OptionalStringField(parameterJson, "description", definition.description, "", manifestPath, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (!ParseParameterDefault(parameterJson, definition, manifestPath, error) ||
|
if (!ParseParameterDefault(parameterJson, definition, manifestPath, error) ||
|
||||||
!ParseParameterNumberField(parameterJson, "min", definition.minNumbers, manifestPath, error) ||
|
!ParseParameterNumberField(parameterJson, "min", definition.minNumbers, manifestPath, error) ||
|
||||||
!ParseParameterNumberField(parameterJson, "max", definition.maxNumbers, manifestPath, error) ||
|
!ParseParameterNumberField(parameterJson, "max", definition.maxNumbers, manifestPath, error) ||
|
||||||
@@ -666,17 +799,20 @@ bool ShaderPackageRegistry::ParseManifest(const std::filesystem::path& manifestP
|
|||||||
if (!ParseShaderMetadata(manifestJson, shaderPackage, manifestPath, error))
|
if (!ParseShaderMetadata(manifestJson, shaderPackage, manifestPath, error))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!std::filesystem::exists(shaderPackage.shaderPath))
|
if (!ParsePassDefinitions(manifestJson, shaderPackage, manifestPath, error))
|
||||||
{
|
|
||||||
error = "Shader source not found for package " + shaderPackage.id + ": " + shaderPackage.shaderPath.string();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
shaderPackage.shaderWriteTime = std::filesystem::last_write_time(shaderPackage.shaderPath);
|
shaderPackage.shaderWriteTime = shaderPackage.passes.front().sourceWriteTime;
|
||||||
|
for (const ShaderPassDefinition& pass : shaderPackage.passes)
|
||||||
|
{
|
||||||
|
if (pass.sourceWriteTime > shaderPackage.shaderWriteTime)
|
||||||
|
shaderPackage.shaderWriteTime = pass.sourceWriteTime;
|
||||||
|
}
|
||||||
shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath);
|
shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath);
|
||||||
|
|
||||||
return ParseTextureAssets(manifestJson, shaderPackage, manifestPath, error) &&
|
return ParseTextureAssets(manifestJson, shaderPackage, manifestPath, error) &&
|
||||||
ParseFontAssets(manifestJson, shaderPackage, manifestPath, error) &&
|
ParseFontAssets(manifestJson, shaderPackage, manifestPath, error) &&
|
||||||
ParseTemporalSettings(manifestJson, shaderPackage, mMaxTemporalHistoryFrames, manifestPath, error) &&
|
ParseTemporalSettings(manifestJson, shaderPackage, mMaxTemporalHistoryFrames, manifestPath, error) &&
|
||||||
|
ParseFeedbackSettings(manifestJson, shaderPackage, manifestPath, error) &&
|
||||||
ParseParameterDefinitions(manifestJson, shaderPackage, manifestPath, error);
|
ParseParameterDefinitions(manifestJson, shaderPackage, manifestPath, error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ struct ShaderParameterDefinition
|
|||||||
{
|
{
|
||||||
std::string id;
|
std::string id;
|
||||||
std::string label;
|
std::string label;
|
||||||
|
std::string description;
|
||||||
ShaderParameterType type = ShaderParameterType::Float;
|
ShaderParameterType type = ShaderParameterType::Float;
|
||||||
std::vector<double> defaultNumbers;
|
std::vector<double> defaultNumbers;
|
||||||
std::vector<double> minNumbers;
|
std::vector<double> minNumbers;
|
||||||
@@ -62,6 +63,12 @@ struct TemporalSettings
|
|||||||
unsigned effectiveHistoryLength = 0;
|
unsigned effectiveHistoryLength = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct FeedbackSettings
|
||||||
|
{
|
||||||
|
bool enabled = false;
|
||||||
|
std::string writePassId;
|
||||||
|
};
|
||||||
|
|
||||||
struct ShaderTextureAsset
|
struct ShaderTextureAsset
|
||||||
{
|
{
|
||||||
std::string id;
|
std::string id;
|
||||||
@@ -76,6 +83,24 @@ struct ShaderFontAsset
|
|||||||
std::filesystem::file_time_type writeTime;
|
std::filesystem::file_time_type writeTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ShaderPassDefinition
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::string entryPoint;
|
||||||
|
std::filesystem::path sourcePath;
|
||||||
|
std::filesystem::file_time_type sourceWriteTime;
|
||||||
|
std::vector<std::string> inputNames;
|
||||||
|
std::string outputName;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShaderPassBuildSource
|
||||||
|
{
|
||||||
|
std::string passId;
|
||||||
|
std::string fragmentShaderSource;
|
||||||
|
std::vector<std::string> inputNames;
|
||||||
|
std::string outputName;
|
||||||
|
};
|
||||||
|
|
||||||
struct ShaderPackage
|
struct ShaderPackage
|
||||||
{
|
{
|
||||||
std::string id;
|
std::string id;
|
||||||
@@ -86,10 +111,12 @@ struct ShaderPackage
|
|||||||
std::filesystem::path directoryPath;
|
std::filesystem::path directoryPath;
|
||||||
std::filesystem::path shaderPath;
|
std::filesystem::path shaderPath;
|
||||||
std::filesystem::path manifestPath;
|
std::filesystem::path manifestPath;
|
||||||
|
std::vector<ShaderPassDefinition> passes;
|
||||||
std::vector<ShaderParameterDefinition> parameters;
|
std::vector<ShaderParameterDefinition> parameters;
|
||||||
std::vector<ShaderTextureAsset> textureAssets;
|
std::vector<ShaderTextureAsset> textureAssets;
|
||||||
std::vector<ShaderFontAsset> fontAssets;
|
std::vector<ShaderFontAsset> fontAssets;
|
||||||
TemporalSettings temporal;
|
TemporalSettings temporal;
|
||||||
|
FeedbackSettings feedback;
|
||||||
std::filesystem::file_time_type shaderWriteTime;
|
std::filesystem::file_time_type shaderWriteTime;
|
||||||
std::filesystem::file_time_type manifestWriteTime;
|
std::filesystem::file_time_type manifestWriteTime;
|
||||||
};
|
};
|
||||||
@@ -108,6 +135,7 @@ struct RuntimeRenderState
|
|||||||
{
|
{
|
||||||
std::string layerId;
|
std::string layerId;
|
||||||
std::string shaderId;
|
std::string shaderId;
|
||||||
|
std::string shaderName;
|
||||||
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
||||||
std::map<std::string, ShaderParameterValue> parameterValues;
|
std::map<std::string, ShaderParameterValue> parameterValues;
|
||||||
std::vector<ShaderTextureAsset> textureAssets;
|
std::vector<ShaderTextureAsset> textureAssets;
|
||||||
@@ -127,4 +155,5 @@ struct RuntimeRenderState
|
|||||||
TemporalHistorySource temporalHistorySource = TemporalHistorySource::None;
|
TemporalHistorySource temporalHistorySource = TemporalHistorySource::None;
|
||||||
unsigned requestedTemporalHistoryLength = 0;
|
unsigned requestedTemporalHistoryLength = 0;
|
||||||
unsigned effectiveTemporalHistoryLength = 0;
|
unsigned effectiveTemporalHistoryLength = 0;
|
||||||
|
FeedbackSettings feedback;
|
||||||
};
|
};
|
||||||
|
|||||||
238
apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp
Normal file
238
apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
#include "VideoBackend.h"
|
||||||
|
|
||||||
|
#include "DeckLinkSession.h"
|
||||||
|
#include "OpenGLVideoIOBridge.h"
|
||||||
|
#include "HealthTelemetry.h"
|
||||||
|
#include "RenderEngine.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
VideoBackend::VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry) :
|
||||||
|
mHealthTelemetry(healthTelemetry),
|
||||||
|
mVideoIODevice(std::make_unique<DeckLinkSession>()),
|
||||||
|
mBridge(std::make_unique<OpenGLVideoIOBridge>(renderEngine))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoBackend::~VideoBackend()
|
||||||
|
{
|
||||||
|
ReleaseResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoBackend::ReleaseResources()
|
||||||
|
{
|
||||||
|
if (mVideoIODevice)
|
||||||
|
mVideoIODevice->ReleaseResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error)
|
||||||
|
{
|
||||||
|
return mVideoIODevice->DiscoverDevicesAndModes(videoModes, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error)
|
||||||
|
{
|
||||||
|
return mVideoIODevice->SelectPreferredFormats(videoModes, outputAlphaRequired, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::ConfigureInput(const VideoFormat& inputVideoMode, std::string& error)
|
||||||
|
{
|
||||||
|
return mVideoIODevice->ConfigureInput(
|
||||||
|
[this](const VideoIOFrame& frame) { HandleInputFrame(frame); },
|
||||||
|
inputVideoMode,
|
||||||
|
error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error)
|
||||||
|
{
|
||||||
|
return mVideoIODevice->ConfigureOutput(
|
||||||
|
[this](const VideoIOCompletion& completion) { HandleOutputFrameCompletion(completion); },
|
||||||
|
outputVideoMode,
|
||||||
|
externalKeyingEnabled,
|
||||||
|
error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::Start()
|
||||||
|
{
|
||||||
|
return mVideoIODevice->Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::Stop()
|
||||||
|
{
|
||||||
|
return mVideoIODevice->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
const VideoIOState& VideoBackend::State() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->State();
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoIOState& VideoBackend::MutableState()
|
||||||
|
{
|
||||||
|
return mVideoIODevice->MutableState();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::BeginOutputFrame(VideoIOOutputFrame& frame)
|
||||||
|
{
|
||||||
|
return mVideoIODevice->BeginOutputFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoBackend::EndOutputFrame(VideoIOOutputFrame& frame)
|
||||||
|
{
|
||||||
|
mVideoIODevice->EndOutputFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::ScheduleOutputFrame(const VideoIOOutputFrame& frame)
|
||||||
|
{
|
||||||
|
return mVideoIODevice->ScheduleOutputFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoBackend::AccountForCompletionResult(VideoIOCompletionResult result)
|
||||||
|
{
|
||||||
|
mVideoIODevice->AccountForCompletionResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::HasInputDevice() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->HasInputDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::HasInputSource() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->HasInputSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned VideoBackend::InputFrameWidth() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->InputFrameWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned VideoBackend::InputFrameHeight() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->InputFrameHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned VideoBackend::OutputFrameWidth() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->OutputFrameWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned VideoBackend::OutputFrameHeight() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->OutputFrameHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned VideoBackend::CaptureTextureWidth() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->CaptureTextureWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned VideoBackend::OutputPackTextureWidth() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->OutputPackTextureWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoIOPixelFormat VideoBackend::InputPixelFormat() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->InputPixelFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& VideoBackend::InputDisplayModeName() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->InputDisplayModeName();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& VideoBackend::OutputModelName() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->OutputModelName();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::SupportsInternalKeying() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->SupportsInternalKeying();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::SupportsExternalKeying() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->SupportsExternalKeying();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::KeyerInterfaceAvailable() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->KeyerInterfaceAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::ExternalKeyingActive() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->ExternalKeyingActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& VideoBackend::StatusMessage() const
|
||||||
|
{
|
||||||
|
return mVideoIODevice->StatusMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoBackend::SetStatusMessage(const std::string& message)
|
||||||
|
{
|
||||||
|
mVideoIODevice->SetStatusMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoBackend::HandleInputFrame(const VideoIOFrame& frame)
|
||||||
|
{
|
||||||
|
const VideoIOState& state = mVideoIODevice->State();
|
||||||
|
mHealthTelemetry.TryReportSignalStatus(!frame.hasNoInputSource, state.inputFrameSize.width, state.inputFrameSize.height, state.inputDisplayModeName);
|
||||||
|
|
||||||
|
if (mBridge)
|
||||||
|
mBridge->UploadInputFrame(frame, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoBackend::HandleOutputFrameCompletion(const VideoIOCompletion& completion)
|
||||||
|
{
|
||||||
|
RecordFramePacing(completion.result);
|
||||||
|
|
||||||
|
VideoIOOutputFrame outputFrame;
|
||||||
|
if (!BeginOutputFrame(outputFrame))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const VideoIOState& state = mVideoIODevice->State();
|
||||||
|
if (mBridge)
|
||||||
|
mBridge->RenderScheduledFrame(state, completion, outputFrame);
|
||||||
|
|
||||||
|
EndOutputFrame(outputFrame);
|
||||||
|
AccountForCompletionResult(completion.result);
|
||||||
|
|
||||||
|
// Schedule the next frame after render work is complete so device-side
|
||||||
|
// bookkeeping stays with the backend seam and the bridge stays render-only.
|
||||||
|
ScheduleOutputFrame(outputFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoBackend::RecordFramePacing(VideoIOCompletionResult completionResult)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
mHealthTelemetry.TryRecordFramePacingStats(
|
||||||
|
mCompletionIntervalMilliseconds,
|
||||||
|
mSmoothedCompletionIntervalMilliseconds,
|
||||||
|
mMaxCompletionIntervalMilliseconds,
|
||||||
|
mLateFrameCount,
|
||||||
|
mDroppedFrameCount,
|
||||||
|
mFlushedFrameCount);
|
||||||
|
}
|
||||||
69
apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h
Normal file
69
apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "VideoIOTypes.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class HealthTelemetry;
|
||||||
|
class OpenGLVideoIOBridge;
|
||||||
|
class RenderEngine;
|
||||||
|
class VideoIODevice;
|
||||||
|
|
||||||
|
class VideoBackend
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry);
|
||||||
|
~VideoBackend();
|
||||||
|
|
||||||
|
void ReleaseResources();
|
||||||
|
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);
|
||||||
|
void AccountForCompletionResult(VideoIOCompletionResult result);
|
||||||
|
|
||||||
|
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;
|
||||||
|
void SetStatusMessage(const std::string& message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void HandleInputFrame(const VideoIOFrame& frame);
|
||||||
|
void HandleOutputFrameCompletion(const VideoIOCompletion& completion);
|
||||||
|
void RecordFramePacing(VideoIOCompletionResult completionResult);
|
||||||
|
|
||||||
|
HealthTelemetry& mHealthTelemetry;
|
||||||
|
std::unique_ptr<VideoIODevice> mVideoIODevice;
|
||||||
|
std::unique_ptr<OpenGLVideoIOBridge> mBridge;
|
||||||
|
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;
|
||||||
|
};
|
||||||
@@ -50,22 +50,23 @@ uint16_t Component(uint32_t word, unsigned index)
|
|||||||
|
|
||||||
const char* VideoIOPixelFormatName(VideoIOPixelFormat format)
|
const char* VideoIOPixelFormatName(VideoIOPixelFormat format)
|
||||||
{
|
{
|
||||||
return format == VideoIOPixelFormat::V210 ? "10-bit YUV v210" : "8-bit YUV UYVY";
|
switch (format)
|
||||||
|
{
|
||||||
|
case VideoIOPixelFormat::V210:
|
||||||
|
return "10-bit YUV v210";
|
||||||
|
case VideoIOPixelFormat::Yuva10:
|
||||||
|
return "10-bit YUVA Ay10";
|
||||||
|
case VideoIOPixelFormat::Bgra8:
|
||||||
|
return "8-bit BGRA";
|
||||||
|
case VideoIOPixelFormat::Uyvy8:
|
||||||
|
default:
|
||||||
|
return "8-bit YUV UYVY";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format)
|
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format)
|
||||||
{
|
{
|
||||||
return format == VideoIOPixelFormat::V210;
|
return format == VideoIOPixelFormat::V210 || format == VideoIOPixelFormat::Yuva10;
|
||||||
}
|
|
||||||
|
|
||||||
BMDPixelFormat DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat format)
|
|
||||||
{
|
|
||||||
return format == VideoIOPixelFormat::V210 ? bmdFormat10BitYUV : bmdFormat8BitYUV;
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoIOPixelFormat VideoIOPixelFormatFromDeckLink(BMDPixelFormat format)
|
|
||||||
{
|
|
||||||
return format == bmdFormat10BitYUV ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Uyvy8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported)
|
VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported)
|
||||||
@@ -73,6 +74,31 @@ VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported)
|
|||||||
return tenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Uyvy8;
|
return tenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Uyvy8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned VideoIOBytesPerPixel(VideoIOPixelFormat format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case VideoIOPixelFormat::Uyvy8:
|
||||||
|
return 2u;
|
||||||
|
case VideoIOPixelFormat::Bgra8:
|
||||||
|
return 4u;
|
||||||
|
case VideoIOPixelFormat::Yuva10:
|
||||||
|
return 4u;
|
||||||
|
case VideoIOPixelFormat::V210:
|
||||||
|
default:
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned VideoIORowBytes(VideoIOPixelFormat format, unsigned frameWidth)
|
||||||
|
{
|
||||||
|
if (format == VideoIOPixelFormat::V210)
|
||||||
|
return MinimumV210RowBytes(frameWidth);
|
||||||
|
if (format == VideoIOPixelFormat::Yuva10)
|
||||||
|
return MinimumYuva10RowBytes(frameWidth);
|
||||||
|
return frameWidth * VideoIOBytesPerPixel(format);
|
||||||
|
}
|
||||||
|
|
||||||
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes)
|
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes)
|
||||||
{
|
{
|
||||||
return (rowBytes + 3u) / 4u;
|
return (rowBytes + 3u) / 4u;
|
||||||
@@ -83,6 +109,11 @@ unsigned MinimumV210RowBytes(unsigned frameWidth)
|
|||||||
return ((frameWidth + 5u) / 6u) * 16u;
|
return ((frameWidth + 5u) / 6u) * 16u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned MinimumYuva10RowBytes(unsigned frameWidth)
|
||||||
|
{
|
||||||
|
return ((frameWidth + 63u) / 64u) * 256u;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned ActiveV210WordsForWidth(unsigned frameWidth)
|
unsigned ActiveV210WordsForWidth(unsigned frameWidth)
|
||||||
{
|
{
|
||||||
return ((frameWidth + 5u) / 6u) * 4u;
|
return ((frameWidth + 5u) / 6u) * 4u;
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "DeckLinkAPI_h.h"
|
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
enum class VideoIOPixelFormat
|
enum class VideoIOPixelFormat
|
||||||
{
|
{
|
||||||
Uyvy8,
|
Uyvy8,
|
||||||
V210
|
V210,
|
||||||
|
Yuva10,
|
||||||
|
Bgra8
|
||||||
};
|
};
|
||||||
|
|
||||||
struct V210CodeValues
|
struct V210CodeValues
|
||||||
@@ -27,11 +27,12 @@ struct V210SixPixelBlock
|
|||||||
|
|
||||||
const char* VideoIOPixelFormatName(VideoIOPixelFormat format);
|
const char* VideoIOPixelFormatName(VideoIOPixelFormat format);
|
||||||
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format);
|
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format);
|
||||||
BMDPixelFormat DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat format);
|
|
||||||
VideoIOPixelFormat VideoIOPixelFormatFromDeckLink(BMDPixelFormat format);
|
|
||||||
VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported);
|
VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported);
|
||||||
|
unsigned VideoIOBytesPerPixel(VideoIOPixelFormat format);
|
||||||
|
unsigned VideoIORowBytes(VideoIOPixelFormat format, unsigned frameWidth);
|
||||||
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes);
|
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes);
|
||||||
unsigned MinimumV210RowBytes(unsigned frameWidth);
|
unsigned MinimumV210RowBytes(unsigned frameWidth);
|
||||||
|
unsigned MinimumYuva10RowBytes(unsigned frameWidth);
|
||||||
unsigned ActiveV210WordsForWidth(unsigned frameWidth);
|
unsigned ActiveV210WordsForWidth(unsigned frameWidth);
|
||||||
V210CodeValues Rec709RgbToLegalV210(float red, float green, float blue);
|
V210CodeValues Rec709RgbToLegalV210(float red, float green, float blue);
|
||||||
std::array<uint8_t, 16> PackV210Block(const V210SixPixelBlock& block);
|
std::array<uint8_t, 16> PackV210Block(const V210SixPixelBlock& block);
|
||||||
137
apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOTypes.h
Normal file
137
apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOTypes.h
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "DeckLinkDisplayMode.h"
|
||||||
|
#include "VideoIOFormat.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
enum class VideoIOBackend
|
||||||
|
{
|
||||||
|
DeckLink
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class VideoIOCompletionResult
|
||||||
|
{
|
||||||
|
Completed,
|
||||||
|
DisplayedLate,
|
||||||
|
Dropped,
|
||||||
|
Flushed,
|
||||||
|
Unknown
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VideoIOConfig
|
||||||
|
{
|
||||||
|
VideoFormatSelection videoModes;
|
||||||
|
bool externalKeyingEnabled = false;
|
||||||
|
bool preferTenBit = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VideoIOState
|
||||||
|
{
|
||||||
|
FrameSize inputFrameSize;
|
||||||
|
FrameSize outputFrameSize;
|
||||||
|
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
||||||
|
VideoIOPixelFormat outputPixelFormat = VideoIOPixelFormat::Bgra8;
|
||||||
|
unsigned inputFrameRowBytes = 0;
|
||||||
|
unsigned outputFrameRowBytes = 0;
|
||||||
|
unsigned captureTextureWidth = 0;
|
||||||
|
unsigned outputPackTextureWidth = 0;
|
||||||
|
std::string inputDisplayModeName = "1080p59.94";
|
||||||
|
std::string outputDisplayModeName = "1080p59.94";
|
||||||
|
std::string outputModelName;
|
||||||
|
std::string statusMessage;
|
||||||
|
std::string formatStatusMessage;
|
||||||
|
bool hasInputDevice = false;
|
||||||
|
bool hasInputSource = false;
|
||||||
|
bool supportsInternalKeying = false;
|
||||||
|
bool supportsExternalKeying = false;
|
||||||
|
bool keyerInterfaceAvailable = false;
|
||||||
|
bool externalKeyingActive = false;
|
||||||
|
double frameBudgetMilliseconds = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VideoIOFrame
|
||||||
|
{
|
||||||
|
void* bytes = nullptr;
|
||||||
|
long rowBytes = 0;
|
||||||
|
unsigned width = 0;
|
||||||
|
unsigned height = 0;
|
||||||
|
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Uyvy8;
|
||||||
|
bool hasNoInputSource = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VideoIOOutputFrame
|
||||||
|
{
|
||||||
|
void* bytes = nullptr;
|
||||||
|
long rowBytes = 0;
|
||||||
|
unsigned width = 0;
|
||||||
|
unsigned height = 0;
|
||||||
|
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
|
||||||
|
void* nativeFrame = nullptr;
|
||||||
|
void* nativeBuffer = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VideoIOCompletion
|
||||||
|
{
|
||||||
|
VideoIOCompletionResult result = VideoIOCompletionResult::Completed;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VideoIOScheduleTime
|
||||||
|
{
|
||||||
|
int64_t streamTime = 0;
|
||||||
|
int64_t duration = 0;
|
||||||
|
int64_t timeScale = 0;
|
||||||
|
uint64_t frameIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VideoIODevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using InputFrameCallback = std::function<void(const VideoIOFrame&)>;
|
||||||
|
using OutputFrameCallback = std::function<void(const VideoIOCompletion&)>;
|
||||||
|
|
||||||
|
virtual ~VideoIODevice() = default;
|
||||||
|
virtual void ReleaseResources() = 0;
|
||||||
|
virtual bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) = 0;
|
||||||
|
virtual bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) = 0;
|
||||||
|
virtual bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) = 0;
|
||||||
|
virtual bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) = 0;
|
||||||
|
virtual bool Start() = 0;
|
||||||
|
virtual bool Stop() = 0;
|
||||||
|
virtual const VideoIOState& State() const = 0;
|
||||||
|
virtual VideoIOState& MutableState() = 0;
|
||||||
|
virtual bool BeginOutputFrame(VideoIOOutputFrame& frame) = 0;
|
||||||
|
virtual void EndOutputFrame(VideoIOOutputFrame& frame) = 0;
|
||||||
|
virtual bool ScheduleOutputFrame(const VideoIOOutputFrame& frame) = 0;
|
||||||
|
virtual void AccountForCompletionResult(VideoIOCompletionResult result) = 0;
|
||||||
|
|
||||||
|
bool HasInputDevice() const { return State().hasInputDevice; }
|
||||||
|
bool HasInputSource() const { return State().hasInputSource; }
|
||||||
|
bool InputOutputDimensionsDiffer() const { return State().inputFrameSize != State().outputFrameSize; }
|
||||||
|
const FrameSize& InputFrameSize() const { return State().inputFrameSize; }
|
||||||
|
const FrameSize& OutputFrameSize() const { return State().outputFrameSize; }
|
||||||
|
unsigned InputFrameWidth() const { return State().inputFrameSize.width; }
|
||||||
|
unsigned InputFrameHeight() const { return State().inputFrameSize.height; }
|
||||||
|
unsigned OutputFrameWidth() const { return State().outputFrameSize.width; }
|
||||||
|
unsigned OutputFrameHeight() const { return State().outputFrameSize.height; }
|
||||||
|
VideoIOPixelFormat InputPixelFormat() const { return State().inputPixelFormat; }
|
||||||
|
VideoIOPixelFormat OutputPixelFormat() const { return State().outputPixelFormat; }
|
||||||
|
bool InputIsTenBit() const { return VideoIOPixelFormatIsTenBit(State().inputPixelFormat); }
|
||||||
|
bool OutputIsTenBit() const { return VideoIOPixelFormatIsTenBit(State().outputPixelFormat); }
|
||||||
|
unsigned InputFrameRowBytes() const { return State().inputFrameRowBytes; }
|
||||||
|
unsigned OutputFrameRowBytes() const { return State().outputFrameRowBytes; }
|
||||||
|
unsigned CaptureTextureWidth() const { return State().captureTextureWidth; }
|
||||||
|
unsigned OutputPackTextureWidth() const { return State().outputPackTextureWidth; }
|
||||||
|
const std::string& FormatStatusMessage() const { return State().formatStatusMessage; }
|
||||||
|
const std::string& InputDisplayModeName() const { return State().inputDisplayModeName; }
|
||||||
|
const std::string& OutputModelName() const { return State().outputModelName; }
|
||||||
|
bool SupportsInternalKeying() const { return State().supportsInternalKeying; }
|
||||||
|
bool SupportsExternalKeying() const { return State().supportsExternalKeying; }
|
||||||
|
bool KeyerInterfaceAvailable() const { return State().keyerInterfaceAvailable; }
|
||||||
|
bool ExternalKeyingActive() const { return State().externalKeyingActive; }
|
||||||
|
const std::string& StatusMessage() const { return State().statusMessage; }
|
||||||
|
double FrameBudgetMilliseconds() const { return State().frameBudgetMilliseconds; }
|
||||||
|
void SetStatusMessage(const std::string& message) { MutableState().statusMessage = message; }
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#include "VideoPlayoutScheduler.h"
|
||||||
|
|
||||||
|
void VideoPlayoutScheduler::Configure(int64_t frameDuration, int64_t timeScale)
|
||||||
|
{
|
||||||
|
mFrameDuration = frameDuration;
|
||||||
|
mTimeScale = timeScale;
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoPlayoutScheduler::Reset()
|
||||||
|
{
|
||||||
|
mScheduledFrameIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoIOScheduleTime VideoPlayoutScheduler::NextScheduleTime()
|
||||||
|
{
|
||||||
|
VideoIOScheduleTime time;
|
||||||
|
time.streamTime = static_cast<int64_t>(mScheduledFrameIndex) * mFrameDuration;
|
||||||
|
time.duration = mFrameDuration;
|
||||||
|
time.timeScale = mTimeScale;
|
||||||
|
time.frameIndex = mScheduledFrameIndex;
|
||||||
|
++mScheduledFrameIndex;
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoPlayoutScheduler::AccountForCompletionResult(VideoIOCompletionResult result)
|
||||||
|
{
|
||||||
|
if (result == VideoIOCompletionResult::DisplayedLate || result == VideoIOCompletionResult::Dropped)
|
||||||
|
mScheduledFrameIndex += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double VideoPlayoutScheduler::FrameBudgetMilliseconds() const
|
||||||
|
{
|
||||||
|
return mTimeScale != 0
|
||||||
|
? (static_cast<double>(mFrameDuration) * 1000.0) / static_cast<double>(mTimeScale)
|
||||||
|
: 0.0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "VideoIOTypes.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class VideoPlayoutScheduler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Configure(int64_t frameDuration, int64_t timeScale);
|
||||||
|
void Reset();
|
||||||
|
VideoIOScheduleTime NextScheduleTime();
|
||||||
|
void AccountForCompletionResult(VideoIOCompletionResult result);
|
||||||
|
double FrameBudgetMilliseconds() const;
|
||||||
|
uint64_t ScheduledFrameIndex() const { return mScheduledFrameIndex; }
|
||||||
|
int64_t TimeScale() const { return mTimeScale; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int64_t mFrameDuration = 0;
|
||||||
|
int64_t mTimeScale = 0;
|
||||||
|
uint64_t mScheduledFrameIndex = 0;
|
||||||
|
};
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
#include "DeckLinkFrameTransfer.h"
|
#include "DeckLinkFrameTransfer.h"
|
||||||
|
|
||||||
#include "OpenGLComposite.h"
|
#include "DeckLinkSession.h"
|
||||||
|
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
// DeckLink Capture Delegate Class
|
// DeckLink Capture Delegate Class
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
CaptureDelegate::CaptureDelegate(OpenGLComposite* pOwner) :
|
CaptureDelegate::CaptureDelegate(DeckLinkSession* pOwner) :
|
||||||
m_pOwner(pOwner),
|
m_pOwner(pOwner),
|
||||||
mRefCount(1)
|
mRefCount(1)
|
||||||
{
|
{
|
||||||
@@ -39,7 +39,7 @@ HRESULT CaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* inputF
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool hasNoInputSource = (inputFrame->GetFlags() & bmdFrameHasNoInputSource) == bmdFrameHasNoInputSource;
|
bool hasNoInputSource = (inputFrame->GetFlags() & bmdFrameHasNoInputSource) == bmdFrameHasNoInputSource;
|
||||||
m_pOwner->VideoFrameArrived(inputFrame, hasNoInputSource);
|
m_pOwner->HandleVideoInputFrame(inputFrame, hasNoInputSource);
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ HRESULT CaptureDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvent
|
|||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
// DeckLink Playout Delegate Class
|
// DeckLink Playout Delegate Class
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
PlayoutDelegate::PlayoutDelegate(OpenGLComposite* pOwner) :
|
PlayoutDelegate::PlayoutDelegate(DeckLinkSession* pOwner) :
|
||||||
m_pOwner(pOwner),
|
m_pOwner(pOwner),
|
||||||
mRefCount(1)
|
mRefCount(1)
|
||||||
{
|
{
|
||||||
@@ -94,7 +94,7 @@ HRESULT PlayoutDelegate::ScheduledFrameCompleted(IDeckLinkVideoFrame* completedF
|
|||||||
OutputDebugStringA("ScheduledFrameCompleted() frame did not complete: Unknown error\n");
|
OutputDebugStringA("ScheduledFrameCompleted() frame did not complete: Unknown error\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_pOwner->PlayoutFrameCompleted(completedFrame, result);
|
m_pOwner->HandlePlayoutFrameCompleted(completedFrame, result);
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,18 +6,18 @@
|
|||||||
|
|
||||||
#include "DeckLinkAPI_h.h"
|
#include "DeckLinkAPI_h.h"
|
||||||
|
|
||||||
class OpenGLComposite;
|
class DeckLinkSession;
|
||||||
|
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
// Capture Delegate Class
|
// Capture Delegate Class
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
class CaptureDelegate : public IDeckLinkInputCallback
|
class CaptureDelegate : public IDeckLinkInputCallback
|
||||||
{
|
{
|
||||||
OpenGLComposite* m_pOwner;
|
DeckLinkSession* m_pOwner;
|
||||||
LONG mRefCount;
|
LONG mRefCount;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CaptureDelegate(OpenGLComposite* pOwner);
|
CaptureDelegate(DeckLinkSession* pOwner);
|
||||||
|
|
||||||
// IUnknown needs only a dummy implementation
|
// IUnknown needs only a dummy implementation
|
||||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv);
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv);
|
||||||
@@ -33,11 +33,11 @@ public:
|
|||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
class PlayoutDelegate : public IDeckLinkVideoOutputCallback
|
class PlayoutDelegate : public IDeckLinkVideoOutputCallback
|
||||||
{
|
{
|
||||||
OpenGLComposite* m_pOwner;
|
DeckLinkSession* m_pOwner;
|
||||||
LONG mRefCount;
|
LONG mRefCount;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PlayoutDelegate(OpenGLComposite* pOwner);
|
PlayoutDelegate(DeckLinkSession* pOwner);
|
||||||
|
|
||||||
// IUnknown needs only a dummy implementation
|
// IUnknown needs only a dummy implementation
|
||||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv);
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv);
|
||||||
@@ -0,0 +1,642 @@
|
|||||||
|
#include "DeckLinkSession.h"
|
||||||
|
|
||||||
|
#include "GlRenderConstants.h"
|
||||||
|
|
||||||
|
#include <atlbase.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <new>
|
||||||
|
#include <sstream>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::string BstrToUtf8(BSTR value)
|
||||||
|
{
|
||||||
|
if (value == nullptr)
|
||||||
|
return std::string();
|
||||||
|
|
||||||
|
const int requiredBytes = WideCharToMultiByte(CP_UTF8, 0, value, -1, NULL, 0, NULL, NULL);
|
||||||
|
if (requiredBytes <= 1)
|
||||||
|
return std::string();
|
||||||
|
|
||||||
|
std::vector<char> utf8Name(static_cast<std::size_t>(requiredBytes), '\0');
|
||||||
|
if (WideCharToMultiByte(CP_UTF8, 0, value, -1, utf8Name.data(), requiredBytes, NULL, NULL) <= 0)
|
||||||
|
return std::string();
|
||||||
|
|
||||||
|
return std::string(utf8Name.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputSupportsFormat(IDeckLinkInput* input, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat)
|
||||||
|
{
|
||||||
|
if (input == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
BOOL supported = FALSE;
|
||||||
|
BMDDisplayMode actualMode = bmdModeUnknown;
|
||||||
|
const HRESULT result = input->DoesSupportVideoMode(
|
||||||
|
bmdVideoConnectionUnspecified,
|
||||||
|
displayMode,
|
||||||
|
pixelFormat,
|
||||||
|
bmdNoVideoInputConversion,
|
||||||
|
bmdSupportedVideoModeDefault,
|
||||||
|
&actualMode,
|
||||||
|
&supported);
|
||||||
|
return result == S_OK && supported != FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OutputSupportsFormat(IDeckLinkOutput* output, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat)
|
||||||
|
{
|
||||||
|
if (output == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
BOOL supported = FALSE;
|
||||||
|
BMDDisplayMode actualMode = bmdModeUnknown;
|
||||||
|
const HRESULT result = output->DoesSupportVideoMode(
|
||||||
|
bmdVideoConnectionUnspecified,
|
||||||
|
displayMode,
|
||||||
|
pixelFormat,
|
||||||
|
bmdNoVideoOutputConversion,
|
||||||
|
bmdSupportedVideoModeDefault,
|
||||||
|
&actualMode,
|
||||||
|
&supported);
|
||||||
|
return result == S_OK && supported != FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeckLinkSession::~DeckLinkSession()
|
||||||
|
{
|
||||||
|
ReleaseResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckLinkSession::ReleaseResources()
|
||||||
|
{
|
||||||
|
if (input != nullptr)
|
||||||
|
input->SetCallback(nullptr);
|
||||||
|
captureDelegate.Release();
|
||||||
|
input.Release();
|
||||||
|
|
||||||
|
if (output != nullptr)
|
||||||
|
output->SetScheduledFrameCompletionCallback(nullptr);
|
||||||
|
|
||||||
|
if (keyer != nullptr)
|
||||||
|
{
|
||||||
|
keyer->Disable();
|
||||||
|
mState.externalKeyingActive = false;
|
||||||
|
}
|
||||||
|
keyer.Release();
|
||||||
|
|
||||||
|
playoutDelegate.Release();
|
||||||
|
outputVideoFrameQueue.clear();
|
||||||
|
output.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error)
|
||||||
|
{
|
||||||
|
CComPtr<IDeckLinkIterator> deckLinkIterator;
|
||||||
|
CComPtr<IDeckLinkDisplayMode> inputMode;
|
||||||
|
CComPtr<IDeckLinkDisplayMode> outputMode;
|
||||||
|
|
||||||
|
mState.inputDisplayModeName = videoModes.input.displayName;
|
||||||
|
mState.outputDisplayModeName = videoModes.output.displayName;
|
||||||
|
|
||||||
|
HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast<void**>(&deckLinkIterator));
|
||||||
|
if (FAILED(result))
|
||||||
|
{
|
||||||
|
error = "Please install the Blackmagic DeckLink drivers to use the features of this application.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CComPtr<IDeckLink> deckLink;
|
||||||
|
while (deckLinkIterator->Next(&deckLink) == S_OK)
|
||||||
|
{
|
||||||
|
int64_t duplexMode;
|
||||||
|
bool deviceSupportsInternalKeying = false;
|
||||||
|
bool deviceSupportsExternalKeying = false;
|
||||||
|
std::string modelName;
|
||||||
|
CComPtr<IDeckLinkProfileAttributes> deckLinkAttributes;
|
||||||
|
|
||||||
|
if (deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes) != S_OK)
|
||||||
|
{
|
||||||
|
printf("Could not obtain the IDeckLinkProfileAttributes interface\n");
|
||||||
|
deckLink.Release();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = deckLinkAttributes->GetInt(BMDDeckLinkDuplex, &duplexMode);
|
||||||
|
BOOL attributeFlag = FALSE;
|
||||||
|
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &attributeFlag) == S_OK)
|
||||||
|
deviceSupportsInternalKeying = (attributeFlag != FALSE);
|
||||||
|
attributeFlag = FALSE;
|
||||||
|
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &attributeFlag) == S_OK)
|
||||||
|
deviceSupportsExternalKeying = (attributeFlag != FALSE);
|
||||||
|
CComBSTR modelNameBstr;
|
||||||
|
if (deckLinkAttributes->GetString(BMDDeckLinkModelName, &modelNameBstr) == S_OK)
|
||||||
|
modelName = BstrToUtf8(modelNameBstr);
|
||||||
|
|
||||||
|
if (result != S_OK || duplexMode == bmdDuplexInactive)
|
||||||
|
{
|
||||||
|
deckLink.Release();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inputUsed = false;
|
||||||
|
if (!input && deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&input) == S_OK)
|
||||||
|
inputUsed = true;
|
||||||
|
|
||||||
|
if (!output && (!inputUsed || (duplexMode == bmdDuplexFull)))
|
||||||
|
{
|
||||||
|
if (deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&output) != S_OK)
|
||||||
|
output.Release();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mState.outputModelName = modelName;
|
||||||
|
mState.supportsInternalKeying = deviceSupportsInternalKeying;
|
||||||
|
mState.supportsExternalKeying = deviceSupportsExternalKeying;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deckLink.Release();
|
||||||
|
|
||||||
|
if (output && input)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!output)
|
||||||
|
{
|
||||||
|
error = "Expected an Output DeckLink device";
|
||||||
|
ReleaseResources();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CComPtr<IDeckLinkDisplayModeIterator> inputDisplayModeIterator;
|
||||||
|
if (input && input->GetDisplayModeIterator(&inputDisplayModeIterator) != S_OK)
|
||||||
|
{
|
||||||
|
error = "Cannot get input Display Mode Iterator.";
|
||||||
|
ReleaseResources();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, videoModes.input.displayMode, &inputMode))
|
||||||
|
{
|
||||||
|
error = "Cannot get specified input BMDDisplayMode for configured mode: " + videoModes.input.displayName;
|
||||||
|
ReleaseResources();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
inputDisplayModeIterator.Release();
|
||||||
|
|
||||||
|
CComPtr<IDeckLinkDisplayModeIterator> outputDisplayModeIterator;
|
||||||
|
if (output->GetDisplayModeIterator(&outputDisplayModeIterator) != S_OK)
|
||||||
|
{
|
||||||
|
error = "Cannot get output Display Mode Iterator.";
|
||||||
|
ReleaseResources();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, videoModes.output.displayMode, &outputMode))
|
||||||
|
{
|
||||||
|
error = "Cannot get specified output BMDDisplayMode for configured mode: " + videoModes.output.displayName;
|
||||||
|
ReleaseResources();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mState.outputFrameSize = { static_cast<unsigned>(outputMode->GetWidth()), static_cast<unsigned>(outputMode->GetHeight()) };
|
||||||
|
mState.inputFrameSize = inputMode
|
||||||
|
? FrameSize{ static_cast<unsigned>(inputMode->GetWidth()), static_cast<unsigned>(inputMode->GetHeight()) }
|
||||||
|
: mState.outputFrameSize;
|
||||||
|
if (!input)
|
||||||
|
mState.inputDisplayModeName = "No input - black frame";
|
||||||
|
BMDTimeValue frameDuration = 0;
|
||||||
|
BMDTimeScale frameTimescale = 0;
|
||||||
|
outputMode->GetFrameRate(&frameDuration, &frameTimescale);
|
||||||
|
mScheduler.Configure(frameDuration, frameTimescale);
|
||||||
|
mState.frameBudgetMilliseconds = mScheduler.FrameBudgetMilliseconds();
|
||||||
|
|
||||||
|
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
|
||||||
|
mState.outputFrameRowBytes = mState.outputFrameSize.width * 4u;
|
||||||
|
mState.captureTextureWidth = mState.inputFrameSize.width / 2u;
|
||||||
|
mState.outputPackTextureWidth = mState.outputFrameSize.width;
|
||||||
|
mState.hasInputDevice = input != nullptr;
|
||||||
|
mState.hasInputSource = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error)
|
||||||
|
{
|
||||||
|
if (!output)
|
||||||
|
{
|
||||||
|
error = "Expected an Output DeckLink device";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mState.formatStatusMessage.clear();
|
||||||
|
|
||||||
|
const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, videoModes.input.displayMode, bmdFormat10BitYUV);
|
||||||
|
mState.inputPixelFormat = input != nullptr ? ChoosePreferredVideoIOFormat(inputTenBitSupported) : VideoIOPixelFormat::Uyvy8;
|
||||||
|
if (input != nullptr && !inputTenBitSupported)
|
||||||
|
mState.formatStatusMessage += "DeckLink input does not report 10-bit YUV support for the configured mode; using 8-bit capture. ";
|
||||||
|
|
||||||
|
const bool outputTenBitSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUV);
|
||||||
|
const bool outputTenBitYuvaSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUVA);
|
||||||
|
mState.outputPixelFormat = outputAlphaRequired
|
||||||
|
? (outputTenBitYuvaSupported ? VideoIOPixelFormat::Yuva10 : VideoIOPixelFormat::Bgra8)
|
||||||
|
: (outputTenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Bgra8);
|
||||||
|
if (outputAlphaRequired && outputTenBitYuvaSupported)
|
||||||
|
mState.formatStatusMessage += "External keying requires alpha; using 10-bit YUVA output. ";
|
||||||
|
else if (outputAlphaRequired)
|
||||||
|
mState.formatStatusMessage += "External keying requires alpha, but DeckLink output does not report 10-bit YUVA support for the configured mode; using 8-bit BGRA output. ";
|
||||||
|
else if (!outputTenBitSupported)
|
||||||
|
mState.formatStatusMessage += "DeckLink output does not report 10-bit YUV support for the configured mode; using 8-bit BGRA output. ";
|
||||||
|
|
||||||
|
int deckLinkOutputRowBytes = 0;
|
||||||
|
if (output->RowBytesForPixelFormat(DeckLinkPixelFormatForVideoIO(mState.outputPixelFormat), mState.outputFrameSize.width, &deckLinkOutputRowBytes) != S_OK)
|
||||||
|
{
|
||||||
|
error = "DeckLink output setup failed while calculating output row bytes.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mState.outputFrameRowBytes = static_cast<unsigned>(deckLinkOutputRowBytes);
|
||||||
|
mState.outputPackTextureWidth = OutputIsTenBit()
|
||||||
|
? PackedTextureWidthFromRowBytes(mState.outputFrameRowBytes)
|
||||||
|
: mState.outputFrameSize.width;
|
||||||
|
|
||||||
|
if (InputIsTenBit())
|
||||||
|
{
|
||||||
|
int deckLinkInputRowBytes = 0;
|
||||||
|
if (output->RowBytesForPixelFormat(bmdFormat10BitYUV, mState.inputFrameSize.width, &deckLinkInputRowBytes) == S_OK)
|
||||||
|
mState.inputFrameRowBytes = static_cast<unsigned>(deckLinkInputRowBytes);
|
||||||
|
else
|
||||||
|
mState.inputFrameRowBytes = MinimumV210RowBytes(mState.inputFrameSize.width);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
|
||||||
|
}
|
||||||
|
mState.captureTextureWidth = InputIsTenBit()
|
||||||
|
? PackedTextureWidthFromRowBytes(mState.inputFrameRowBytes)
|
||||||
|
: mState.inputFrameSize.width / 2u;
|
||||||
|
|
||||||
|
std::ostringstream status;
|
||||||
|
status << "DeckLink formats: capture " << (input ? VideoIOPixelFormatName(mState.inputPixelFormat) : "none")
|
||||||
|
<< ", output " << VideoIOPixelFormatName(mState.outputPixelFormat) << ".";
|
||||||
|
if (!mState.formatStatusMessage.empty())
|
||||||
|
status << " " << mState.formatStatusMessage;
|
||||||
|
mState.formatStatusMessage = status.str();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckLinkSession::ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error)
|
||||||
|
{
|
||||||
|
mInputFrameCallback = std::move(callback);
|
||||||
|
|
||||||
|
if (!input)
|
||||||
|
{
|
||||||
|
mState.hasInputSource = false;
|
||||||
|
mState.inputDisplayModeName = "No input - black frame";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BMDPixelFormat deckLinkInputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.inputPixelFormat);
|
||||||
|
if (input->EnableVideoInput(inputVideoMode.displayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK)
|
||||||
|
{
|
||||||
|
if (mState.inputPixelFormat == VideoIOPixelFormat::V210)
|
||||||
|
{
|
||||||
|
OutputDebugStringA("DeckLink 10-bit input could not be enabled; falling back to 8-bit capture.\n");
|
||||||
|
mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
||||||
|
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
|
||||||
|
mState.captureTextureWidth = mState.inputFrameSize.width / 2u;
|
||||||
|
if (input->EnableVideoInput(inputVideoMode.displayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault) == S_OK)
|
||||||
|
{
|
||||||
|
std::ostringstream status;
|
||||||
|
status << "DeckLink formats: capture " << VideoIOPixelFormatName(mState.inputPixelFormat)
|
||||||
|
<< ", output " << VideoIOPixelFormatName(mState.outputPixelFormat)
|
||||||
|
<< ". DeckLink 10-bit input enable failed; using 8-bit capture.";
|
||||||
|
mState.formatStatusMessage = status.str();
|
||||||
|
goto input_enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputDebugStringA("DeckLink input could not be enabled; continuing in output-only black-frame mode.\n");
|
||||||
|
input.Release();
|
||||||
|
mState.hasInputDevice = false;
|
||||||
|
mState.hasInputSource = false;
|
||||||
|
mState.inputDisplayModeName = "No input - black frame";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
input_enabled:
|
||||||
|
captureDelegate.Attach(new (std::nothrow) CaptureDelegate(this));
|
||||||
|
if (captureDelegate == nullptr)
|
||||||
|
{
|
||||||
|
error = "DeckLink input setup failed while creating the capture callback.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (input->SetCallback(captureDelegate) != S_OK)
|
||||||
|
{
|
||||||
|
error = "DeckLink input setup failed while installing the capture callback.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error)
|
||||||
|
{
|
||||||
|
mOutputFrameCallback = std::move(callback);
|
||||||
|
|
||||||
|
if (output->EnableVideoOutput(outputVideoMode.displayMode, bmdVideoOutputFlagDefault) != S_OK)
|
||||||
|
{
|
||||||
|
error = "DeckLink output setup failed while enabling video output.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output->QueryInterface(IID_IDeckLinkKeyer, (void**)&keyer) == S_OK && keyer != NULL)
|
||||||
|
mState.keyerInterfaceAvailable = true;
|
||||||
|
|
||||||
|
if (externalKeyingEnabled)
|
||||||
|
{
|
||||||
|
if (!mState.supportsExternalKeying)
|
||||||
|
{
|
||||||
|
mState.statusMessage = "External keying was requested, but the selected DeckLink output does not report external keying support.";
|
||||||
|
}
|
||||||
|
else if (!mState.keyerInterfaceAvailable)
|
||||||
|
{
|
||||||
|
mState.statusMessage = "External keying was requested, but the selected DeckLink output does not expose the IDeckLinkKeyer interface.";
|
||||||
|
}
|
||||||
|
else if (keyer->Enable(TRUE) != S_OK || keyer->SetLevel(255) != S_OK)
|
||||||
|
{
|
||||||
|
mState.statusMessage = "External keying was requested, but enabling the DeckLink keyer failed.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mState.externalKeyingActive = true;
|
||||||
|
mState.statusMessage = "External keying is active on the selected DeckLink output.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mState.supportsExternalKeying)
|
||||||
|
{
|
||||||
|
mState.statusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it.";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
CComPtr<IDeckLinkMutableVideoFrame> outputFrame;
|
||||||
|
|
||||||
|
const BMDPixelFormat deckLinkOutputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.outputPixelFormat);
|
||||||
|
if (output->CreateVideoFrame(mState.outputFrameSize.width, mState.outputFrameSize.height, mState.outputFrameRowBytes, deckLinkOutputPixelFormat, bmdFrameFlagFlipVertical, &outputFrame) != S_OK)
|
||||||
|
{
|
||||||
|
error = "DeckLink output setup failed while creating an output video frame.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputVideoFrameQueue.push_back(outputFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
playoutDelegate.Attach(new (std::nothrow) PlayoutDelegate(this));
|
||||||
|
if (playoutDelegate == nullptr)
|
||||||
|
{
|
||||||
|
error = "DeckLink output setup failed while creating the playout callback.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output->SetScheduledFrameCompletionCallback(playoutDelegate) != S_OK)
|
||||||
|
{
|
||||||
|
error = "DeckLink output setup failed while installing the scheduled-frame callback.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mState.formatStatusMessage.empty())
|
||||||
|
mState.statusMessage = mState.statusMessage.empty() ? mState.formatStatusMessage : mState.formatStatusMessage + " " + mState.statusMessage;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
double DeckLinkSession::FrameBudgetMilliseconds() const
|
||||||
|
{
|
||||||
|
return mScheduler.FrameBudgetMilliseconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckLinkSession::AcquireNextOutputVideoFrame(CComPtr<IDeckLinkMutableVideoFrame>& outputVideoFrame)
|
||||||
|
{
|
||||||
|
if (outputVideoFrameQueue.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
outputVideoFrame = outputVideoFrameQueue.front();
|
||||||
|
outputVideoFrameQueue.push_back(outputVideoFrame);
|
||||||
|
outputVideoFrameQueue.pop_front();
|
||||||
|
return outputVideoFrame != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckLinkSession::PopulateOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame, VideoIOOutputFrame& frame)
|
||||||
|
{
|
||||||
|
if (outputVideoFrame == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
CComPtr<IDeckLinkVideoBuffer> outputVideoFrameBuffer;
|
||||||
|
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
void* pFrame = nullptr;
|
||||||
|
outputVideoFrameBuffer->GetBytes(&pFrame);
|
||||||
|
|
||||||
|
frame.bytes = pFrame;
|
||||||
|
frame.rowBytes = outputVideoFrame->GetRowBytes();
|
||||||
|
frame.width = mState.outputFrameSize.width;
|
||||||
|
frame.height = mState.outputFrameSize.height;
|
||||||
|
frame.pixelFormat = mState.outputPixelFormat;
|
||||||
|
frame.nativeFrame = outputVideoFrame;
|
||||||
|
frame.nativeBuffer = outputVideoFrameBuffer.Detach();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckLinkSession::ScheduleFrame(IDeckLinkMutableVideoFrame* outputVideoFrame)
|
||||||
|
{
|
||||||
|
const VideoIOScheduleTime scheduleTime = mScheduler.NextScheduleTime();
|
||||||
|
return outputVideoFrame != nullptr &&
|
||||||
|
output->ScheduleVideoFrame(outputVideoFrame, scheduleTime.streamTime, scheduleTime.duration, scheduleTime.timeScale) == S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckLinkSession::ScheduleBlackFrame(IDeckLinkMutableVideoFrame* outputVideoFrame)
|
||||||
|
{
|
||||||
|
if (outputVideoFrame == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
CComPtr<IDeckLinkVideoBuffer> outputVideoFrameBuffer;
|
||||||
|
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
void* pFrame = nullptr;
|
||||||
|
outputVideoFrameBuffer->GetBytes((void**)&pFrame);
|
||||||
|
memset(pFrame, 0, outputVideoFrame->GetRowBytes() * mState.outputFrameSize.height);
|
||||||
|
|
||||||
|
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
|
||||||
|
return ScheduleFrame(outputVideoFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckLinkSession::BeginOutputFrame(VideoIOOutputFrame& frame)
|
||||||
|
{
|
||||||
|
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame;
|
||||||
|
return AcquireNextOutputVideoFrame(outputVideoFrame) && PopulateOutputFrame(outputVideoFrame, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckLinkSession::EndOutputFrame(VideoIOOutputFrame& frame)
|
||||||
|
{
|
||||||
|
IDeckLinkVideoBuffer* outputVideoFrameBuffer = static_cast<IDeckLinkVideoBuffer*>(frame.nativeBuffer);
|
||||||
|
if (outputVideoFrameBuffer != nullptr)
|
||||||
|
{
|
||||||
|
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
|
||||||
|
outputVideoFrameBuffer->Release();
|
||||||
|
}
|
||||||
|
frame.nativeBuffer = nullptr;
|
||||||
|
frame.bytes = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckLinkSession::AccountForCompletionResult(VideoIOCompletionResult completionResult)
|
||||||
|
{
|
||||||
|
mScheduler.AccountForCompletionResult(completionResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckLinkSession::ScheduleOutputFrame(const VideoIOOutputFrame& frame)
|
||||||
|
{
|
||||||
|
IDeckLinkMutableVideoFrame* outputVideoFrame = static_cast<IDeckLinkMutableVideoFrame*>(frame.nativeFrame);
|
||||||
|
return ScheduleFrame(outputVideoFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckLinkSession::Start()
|
||||||
|
{
|
||||||
|
mScheduler.Reset();
|
||||||
|
if (!output)
|
||||||
|
{
|
||||||
|
MessageBoxA(NULL, "Cannot start playout because no DeckLink output device is available.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (outputVideoFrameQueue.empty())
|
||||||
|
{
|
||||||
|
MessageBoxA(NULL, "Cannot start playout because the output frame queue is empty.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < kPrerollFrameCount; i++)
|
||||||
|
{
|
||||||
|
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame;
|
||||||
|
if (!AcquireNextOutputVideoFrame(outputVideoFrame))
|
||||||
|
{
|
||||||
|
MessageBoxA(NULL, "Could not acquire a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ScheduleBlackFrame(outputVideoFrame))
|
||||||
|
{
|
||||||
|
MessageBoxA(NULL, "Could not schedule a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input)
|
||||||
|
{
|
||||||
|
if (input->StartStreams() != S_OK)
|
||||||
|
{
|
||||||
|
MessageBoxA(NULL, "Could not start the DeckLink input stream.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (output->StartScheduledPlayback(0, mScheduler.TimeScale(), 1.0) != S_OK)
|
||||||
|
{
|
||||||
|
MessageBoxA(NULL, "Could not start DeckLink scheduled playback.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckLinkSession::Stop()
|
||||||
|
{
|
||||||
|
if (keyer != nullptr)
|
||||||
|
{
|
||||||
|
keyer->Disable();
|
||||||
|
mState.externalKeyingActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input)
|
||||||
|
{
|
||||||
|
input->StopStreams();
|
||||||
|
input->DisableVideoInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output)
|
||||||
|
{
|
||||||
|
output->StopScheduledPlayback(0, NULL, 0);
|
||||||
|
output->DisableVideoOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckLinkSession::HandleVideoInputFrame(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource)
|
||||||
|
{
|
||||||
|
mState.hasInputSource = !hasNoInputSource;
|
||||||
|
if (hasNoInputSource || mInputFrameCallback == nullptr)
|
||||||
|
{
|
||||||
|
VideoIOFrame frame;
|
||||||
|
frame.width = mState.inputFrameSize.width;
|
||||||
|
frame.height = mState.inputFrameSize.height;
|
||||||
|
frame.pixelFormat = mState.inputPixelFormat;
|
||||||
|
frame.hasNoInputSource = hasNoInputSource;
|
||||||
|
if (mInputFrameCallback)
|
||||||
|
mInputFrameCallback(frame);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CComPtr<IDeckLinkVideoBuffer> inputFrameBuffer;
|
||||||
|
void* videoPixels = nullptr;
|
||||||
|
if (inputFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&inputFrameBuffer) != S_OK)
|
||||||
|
return;
|
||||||
|
if (inputFrameBuffer->StartAccess(bmdBufferAccessRead) != S_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
inputFrameBuffer->GetBytes(&videoPixels);
|
||||||
|
|
||||||
|
VideoIOFrame frame;
|
||||||
|
frame.bytes = videoPixels;
|
||||||
|
frame.rowBytes = inputFrame->GetRowBytes();
|
||||||
|
frame.width = static_cast<unsigned>(inputFrame->GetWidth());
|
||||||
|
frame.height = static_cast<unsigned>(inputFrame->GetHeight());
|
||||||
|
frame.pixelFormat = mState.inputPixelFormat;
|
||||||
|
frame.hasNoInputSource = hasNoInputSource;
|
||||||
|
mInputFrameCallback(frame);
|
||||||
|
|
||||||
|
inputFrameBuffer->EndAccess(bmdBufferAccessRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckLinkSession::HandlePlayoutFrameCompleted(IDeckLinkVideoFrame*, BMDOutputFrameCompletionResult completionResult)
|
||||||
|
{
|
||||||
|
if (!mOutputFrameCallback)
|
||||||
|
return;
|
||||||
|
|
||||||
|
VideoIOCompletion completion;
|
||||||
|
completion.result = TranslateCompletionResult(completionResult);
|
||||||
|
mOutputFrameCallback(completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoIOCompletionResult DeckLinkSession::TranslateCompletionResult(BMDOutputFrameCompletionResult completionResult)
|
||||||
|
{
|
||||||
|
switch (completionResult)
|
||||||
|
{
|
||||||
|
case bmdOutputFrameDisplayedLate:
|
||||||
|
return VideoIOCompletionResult::DisplayedLate;
|
||||||
|
case bmdOutputFrameDropped:
|
||||||
|
return VideoIOCompletionResult::Dropped;
|
||||||
|
case bmdOutputFrameFlushed:
|
||||||
|
return VideoIOCompletionResult::Flushed;
|
||||||
|
case bmdOutputFrameCompleted:
|
||||||
|
return VideoIOCompletionResult::Completed;
|
||||||
|
default:
|
||||||
|
return VideoIOCompletionResult::Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user