diff --git a/CMakeLists.txt b/CMakeLists.txt
index b1bc11c..5a11ef1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,43 +36,62 @@ if(NOT EXISTS "${SLANG_LICENSE_FILE}")
endif()
set(APP_SOURCES
- "${APP_DIR}/ControlServer.cpp"
- "${APP_DIR}/ControlServer.h"
"${APP_DIR}/DeckLinkAPI_i.c"
- "${APP_DIR}/GLExtensions.cpp"
- "${APP_DIR}/GLExtensions.h"
+ "${APP_DIR}/control/ControlServer.cpp"
+ "${APP_DIR}/control/ControlServer.h"
+ "${APP_DIR}/control/OscServer.cpp"
+ "${APP_DIR}/control/OscServer.h"
+ "${APP_DIR}/control/RuntimeControlBridge.cpp"
+ "${APP_DIR}/control/RuntimeControlBridge.h"
+ "${APP_DIR}/decklink/DeckLinkDisplayMode.cpp"
+ "${APP_DIR}/decklink/DeckLinkDisplayMode.h"
+ "${APP_DIR}/decklink/DeckLinkFrameTransfer.cpp"
+ "${APP_DIR}/decklink/DeckLinkFrameTransfer.h"
+ "${APP_DIR}/gl/GLExtensions.cpp"
+ "${APP_DIR}/gl/GLExtensions.h"
+ "${APP_DIR}/gl/GlScopedObjects.h"
+ "${APP_DIR}/gl/OpenGLComposite.cpp"
+ "${APP_DIR}/gl/OpenGLComposite.h"
+ "${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp"
+ "${APP_DIR}/gl/Std140Buffer.h"
+ "${APP_DIR}/gl/TextRasterizer.cpp"
+ "${APP_DIR}/gl/TextRasterizer.h"
+ "${APP_DIR}/gl/TextureAssetLoader.cpp"
+ "${APP_DIR}/gl/TextureAssetLoader.h"
+ "${APP_DIR}/gl/VideoFrameTransfer.cpp"
+ "${APP_DIR}/gl/VideoFrameTransfer.h"
"${APP_DIR}/LoopThroughWithOpenGLCompositing.cpp"
"${APP_DIR}/LoopThroughWithOpenGLCompositing.h"
"${APP_DIR}/LoopThroughWithOpenGLCompositing.rc"
- "${APP_DIR}/NativeHandles.h"
- "${APP_DIR}/NativeSockets.h"
- "${APP_DIR}/OpenGLComposite.cpp"
- "${APP_DIR}/OpenGLComposite.h"
- "${APP_DIR}/OscServer.cpp"
- "${APP_DIR}/OscServer.h"
+ "${APP_DIR}/platform/NativeHandles.h"
+ "${APP_DIR}/platform/NativeSockets.h"
"${APP_DIR}/resource.h"
- "${APP_DIR}/RuntimeHost.cpp"
- "${APP_DIR}/RuntimeHost.h"
- "${APP_DIR}/RuntimeJson.cpp"
- "${APP_DIR}/RuntimeJson.h"
- "${APP_DIR}/RuntimeParameterUtils.cpp"
- "${APP_DIR}/RuntimeParameterUtils.h"
- "${APP_DIR}/ShaderCompiler.cpp"
- "${APP_DIR}/ShaderCompiler.h"
- "${APP_DIR}/ShaderPackageRegistry.cpp"
- "${APP_DIR}/ShaderPackageRegistry.h"
- "${APP_DIR}/ShaderTypes.h"
+ "${APP_DIR}/runtime/RuntimeHost.cpp"
+ "${APP_DIR}/runtime/RuntimeHost.h"
+ "${APP_DIR}/runtime/RuntimeJson.cpp"
+ "${APP_DIR}/runtime/RuntimeJson.h"
+ "${APP_DIR}/runtime/RuntimeParameterUtils.cpp"
+ "${APP_DIR}/runtime/RuntimeParameterUtils.h"
+ "${APP_DIR}/shader/ShaderCompiler.cpp"
+ "${APP_DIR}/shader/ShaderCompiler.h"
+ "${APP_DIR}/shader/ShaderPackageRegistry.cpp"
+ "${APP_DIR}/shader/ShaderPackageRegistry.h"
+ "${APP_DIR}/shader/ShaderTypes.h"
"${APP_DIR}/stdafx.cpp"
"${APP_DIR}/stdafx.h"
"${APP_DIR}/targetver.h"
- "${APP_DIR}/VideoFrameTransfer.cpp"
- "${APP_DIR}/VideoFrameTransfer.h"
)
add_executable(LoopThroughWithOpenGLCompositing WIN32 ${APP_SOURCES})
target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE
"${APP_DIR}"
+ "${APP_DIR}/control"
+ "${APP_DIR}/decklink"
+ "${APP_DIR}/gl"
+ "${APP_DIR}/platform"
+ "${APP_DIR}/runtime"
+ "${APP_DIR}/shader"
"${GPUDIRECT_DIR}/include"
)
@@ -100,12 +119,13 @@ if(MSVC)
endif()
add_executable(RuntimeJsonTests
- "${APP_DIR}/RuntimeJson.cpp"
+ "${APP_DIR}/runtime/RuntimeJson.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeJsonTests.cpp"
)
target_include_directories(RuntimeJsonTests PRIVATE
"${APP_DIR}"
+ "${APP_DIR}/runtime"
)
if(MSVC)
@@ -116,13 +136,15 @@ enable_testing()
add_test(NAME RuntimeJsonTests COMMAND RuntimeJsonTests)
add_executable(RuntimeParameterUtilsTests
- "${APP_DIR}/RuntimeJson.cpp"
- "${APP_DIR}/RuntimeParameterUtils.cpp"
+ "${APP_DIR}/runtime/RuntimeJson.cpp"
+ "${APP_DIR}/runtime/RuntimeParameterUtils.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeParameterUtilsTests.cpp"
)
target_include_directories(RuntimeParameterUtilsTests PRIVATE
"${APP_DIR}"
+ "${APP_DIR}/runtime"
+ "${APP_DIR}/shader"
)
if(MSVC)
@@ -131,14 +153,31 @@ endif()
add_test(NAME RuntimeParameterUtilsTests COMMAND RuntimeParameterUtilsTests)
+add_executable(Std140BufferTests
+ "${CMAKE_CURRENT_SOURCE_DIR}/tests/Std140BufferTests.cpp"
+)
+
+target_include_directories(Std140BufferTests PRIVATE
+ "${APP_DIR}"
+ "${APP_DIR}/gl"
+)
+
+if(MSVC)
+ target_compile_options(Std140BufferTests PRIVATE /W3)
+endif()
+
+add_test(NAME Std140BufferTests COMMAND Std140BufferTests)
+
add_executable(ShaderPackageRegistryTests
- "${APP_DIR}/RuntimeJson.cpp"
- "${APP_DIR}/ShaderPackageRegistry.cpp"
+ "${APP_DIR}/runtime/RuntimeJson.cpp"
+ "${APP_DIR}/shader/ShaderPackageRegistry.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderPackageRegistryTests.cpp"
)
target_include_directories(ShaderPackageRegistryTests PRIVATE
"${APP_DIR}"
+ "${APP_DIR}/runtime"
+ "${APP_DIR}/shader"
)
if(MSVC)
@@ -148,12 +187,14 @@ endif()
add_test(NAME ShaderPackageRegistryTests COMMAND ShaderPackageRegistryTests)
add_executable(OscServerTests
- "${APP_DIR}/OscServer.cpp"
+ "${APP_DIR}/control/OscServer.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/OscServerTests.cpp"
)
target_include_directories(OscServerTests PRIVATE
"${APP_DIR}"
+ "${APP_DIR}/control"
+ "${APP_DIR}/platform"
)
target_link_libraries(OscServerTests PRIVATE
diff --git a/README.md b/README.md
index effddb8..bbf01a6 100644
--- a/README.md
+++ b/README.md
@@ -236,7 +236,6 @@ improve text rendering
genlock
find a better UI libary
Logs
-anamorphic desqueeze
refactor, cleanup of source files
display URL (Maybe clicakable) for control in the windows app (Not on the output)
Sound shader as seperate .slang in shader package?
diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj
index dfe345a..6b11a6d 100644
--- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj
+++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj
@@ -89,7 +89,7 @@
Disabled
- ..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)
+ .;control;decklink;gl;..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)
WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)
EnableFastChecks
MultiThreadedDebugDLL
@@ -115,7 +115,7 @@
Disabled
- ..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)
+ .;control;decklink;gl;..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)
WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)
EnableFastChecks
MultiThreadedDebugDLL
@@ -139,7 +139,7 @@
MaxSpeed
true
- ..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)
+ .;control;decklink;gl;..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)
WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)
MultiThreadedDLL
true
@@ -169,7 +169,7 @@
MaxSpeed
true
- ..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)
+ .;control;decklink;gl;..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)
WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)
MultiThreadedDLL
true
@@ -192,26 +192,26 @@
-
+
-
+
Create
Create
Create
Create
-
+
-
+
-
+
-
+
diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters
index ebcaca8..5826e94 100644
--- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters
+++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters
@@ -18,19 +18,19 @@
-
+
Source Files
Source Files
-
+
Source Files
Source Files
-
+
Source Files
@@ -38,13 +38,13 @@
-
+
Header Files
Header Files
-
+
Header Files
@@ -56,7 +56,7 @@
Header Files
-
+
Header Files
diff --git a/apps/LoopThroughWithOpenGLCompositing/ControlServer.cpp b/apps/LoopThroughWithOpenGLCompositing/control/ControlServer.cpp
similarity index 100%
rename from apps/LoopThroughWithOpenGLCompositing/ControlServer.cpp
rename to apps/LoopThroughWithOpenGLCompositing/control/ControlServer.cpp
diff --git a/apps/LoopThroughWithOpenGLCompositing/ControlServer.h b/apps/LoopThroughWithOpenGLCompositing/control/ControlServer.h
similarity index 100%
rename from apps/LoopThroughWithOpenGLCompositing/ControlServer.h
rename to apps/LoopThroughWithOpenGLCompositing/control/ControlServer.h
diff --git a/apps/LoopThroughWithOpenGLCompositing/OscServer.cpp b/apps/LoopThroughWithOpenGLCompositing/control/OscServer.cpp
similarity index 100%
rename from apps/LoopThroughWithOpenGLCompositing/OscServer.cpp
rename to apps/LoopThroughWithOpenGLCompositing/control/OscServer.cpp
diff --git a/apps/LoopThroughWithOpenGLCompositing/OscServer.h b/apps/LoopThroughWithOpenGLCompositing/control/OscServer.h
similarity index 100%
rename from apps/LoopThroughWithOpenGLCompositing/OscServer.h
rename to apps/LoopThroughWithOpenGLCompositing/control/OscServer.h
diff --git a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeControlBridge.cpp b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeControlBridge.cpp
new file mode 100644
index 0000000..c314701
--- /dev/null
+++ b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeControlBridge.cpp
@@ -0,0 +1,50 @@
+#include "RuntimeControlBridge.h"
+
+#include "ControlServer.h"
+#include "OpenGLComposite.h"
+#include "OscServer.h"
+#include "RuntimeHost.h"
+
+bool StartRuntimeControlServices(
+ OpenGLComposite& composite,
+ RuntimeHost& runtimeHost,
+ ControlServer& controlServer,
+ OscServer& oscServer,
+ std::string& error)
+{
+ ControlServer::Callbacks callbacks;
+ callbacks.getStateJson = [&composite]() { return composite.GetRuntimeStateJson(); };
+ callbacks.addLayer = [&composite](const std::string& shaderId, std::string& actionError) { return composite.AddLayer(shaderId, actionError); };
+ callbacks.removeLayer = [&composite](const std::string& layerId, std::string& actionError) { return composite.RemoveLayer(layerId, actionError); };
+ callbacks.moveLayer = [&composite](const std::string& layerId, int direction, std::string& actionError) { return composite.MoveLayer(layerId, direction, actionError); };
+ callbacks.moveLayerToIndex = [&composite](const std::string& layerId, std::size_t targetIndex, std::string& actionError) { return composite.MoveLayerToIndex(layerId, targetIndex, actionError); };
+ callbacks.setLayerBypass = [&composite](const std::string& layerId, bool bypassed, std::string& actionError) { return composite.SetLayerBypass(layerId, bypassed, actionError); };
+ callbacks.setLayerShader = [&composite](const std::string& layerId, const std::string& shaderId, std::string& actionError) { return composite.SetLayerShader(layerId, shaderId, actionError); };
+ callbacks.updateLayerParameter = [&composite](const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& actionError) {
+ return composite.UpdateLayerParameterJson(layerId, parameterId, valueJson, actionError);
+ };
+ callbacks.resetLayerParameters = [&composite](const std::string& layerId, std::string& actionError) { return composite.ResetLayerParameters(layerId, actionError); };
+ callbacks.saveStackPreset = [&composite](const std::string& presetName, std::string& actionError) { return composite.SaveStackPreset(presetName, actionError); };
+ callbacks.loadStackPreset = [&composite](const std::string& presetName, std::string& actionError) { return composite.LoadStackPreset(presetName, actionError); };
+ callbacks.reloadShader = [&composite](std::string& actionError) {
+ if (!composite.ReloadShader())
+ {
+ actionError = "Shader reload failed. See native app status for details.";
+ return false;
+ }
+ return true;
+ };
+
+ if (!controlServer.Start(runtimeHost.GetUiRoot(), runtimeHost.GetDocsRoot(), runtimeHost.GetServerPort(), callbacks, error))
+ return false;
+ runtimeHost.SetServerPort(controlServer.GetPort());
+
+ OscServer::Callbacks oscCallbacks;
+ oscCallbacks.updateParameter = [&composite](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) {
+ return composite.UpdateLayerParameterByControlKeyJson(layerKey, parameterKey, valueJson, actionError);
+ };
+ if (runtimeHost.GetOscPort() > 0 && !oscServer.Start(runtimeHost.GetOscPort(), oscCallbacks, error))
+ return false;
+
+ return true;
+}
diff --git a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeControlBridge.h b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeControlBridge.h
new file mode 100644
index 0000000..5646a6e
--- /dev/null
+++ b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeControlBridge.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include
+
+class ControlServer;
+class OpenGLComposite;
+class OscServer;
+class RuntimeHost;
+
+bool StartRuntimeControlServices(
+ OpenGLComposite& composite,
+ RuntimeHost& runtimeHost,
+ ControlServer& controlServer,
+ OscServer& oscServer,
+ std::string& error);
diff --git a/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkDisplayMode.cpp b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkDisplayMode.cpp
new file mode 100644
index 0000000..b0b219a
--- /dev/null
+++ b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkDisplayMode.cpp
@@ -0,0 +1,110 @@
+#include "DeckLinkDisplayMode.h"
+
+#include
+
+std::string NormalizeModeToken(const std::string& value)
+{
+ std::string normalized;
+ for (unsigned char ch : value)
+ {
+ if (std::isalnum(ch))
+ normalized.push_back(static_cast(std::tolower(ch)));
+ }
+ return normalized;
+}
+
+bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName)
+{
+ const std::string formatToken = NormalizeModeToken(videoFormat);
+ const std::string frameToken = NormalizeModeToken(frameRate);
+ const std::string combinedToken = formatToken + frameToken;
+
+ struct ModeOption
+ {
+ const char* token;
+ BMDDisplayMode mode;
+ const char* displayName;
+ };
+
+ static const ModeOption options[] =
+ {
+ { "720p50", bmdModeHD720p50, "720p50" },
+ { "hd720p50", bmdModeHD720p50, "720p50" },
+ { "720p5994", bmdModeHD720p5994, "720p59.94" },
+ { "hd720p5994", bmdModeHD720p5994, "720p59.94" },
+ { "720p60", bmdModeHD720p60, "720p60" },
+ { "hd720p60", bmdModeHD720p60, "720p60" },
+ { "1080i50", bmdModeHD1080i50, "1080i50" },
+ { "hd1080i50", bmdModeHD1080i50, "1080i50" },
+ { "1080i5994", bmdModeHD1080i5994, "1080i59.94" },
+ { "hd1080i5994", bmdModeHD1080i5994, "1080i59.94" },
+ { "1080i60", bmdModeHD1080i6000, "1080i60" },
+ { "hd1080i60", bmdModeHD1080i6000, "1080i60" },
+ { "1080p2398", bmdModeHD1080p2398, "1080p23.98" },
+ { "hd1080p2398", bmdModeHD1080p2398, "1080p23.98" },
+ { "1080p24", bmdModeHD1080p24, "1080p24" },
+ { "hd1080p24", bmdModeHD1080p24, "1080p24" },
+ { "1080p25", bmdModeHD1080p25, "1080p25" },
+ { "hd1080p25", bmdModeHD1080p25, "1080p25" },
+ { "1080p2997", bmdModeHD1080p2997, "1080p29.97" },
+ { "hd1080p2997", bmdModeHD1080p2997, "1080p29.97" },
+ { "1080p30", bmdModeHD1080p30, "1080p30" },
+ { "hd1080p30", bmdModeHD1080p30, "1080p30" },
+ { "1080p50", bmdModeHD1080p50, "1080p50" },
+ { "hd1080p50", bmdModeHD1080p50, "1080p50" },
+ { "1080p5994", bmdModeHD1080p5994, "1080p59.94" },
+ { "hd1080p5994", bmdModeHD1080p5994, "1080p59.94" },
+ { "1080p60", bmdModeHD1080p6000, "1080p60" },
+ { "hd1080p60", bmdModeHD1080p6000, "1080p60" },
+ { "2160p2398", bmdMode4K2160p2398, "2160p23.98" },
+ { "4k2160p2398", bmdMode4K2160p2398, "2160p23.98" },
+ { "2160p24", bmdMode4K2160p24, "2160p24" },
+ { "4k2160p24", bmdMode4K2160p24, "2160p24" },
+ { "2160p25", bmdMode4K2160p25, "2160p25" },
+ { "4k2160p25", bmdMode4K2160p25, "2160p25" },
+ { "2160p2997", bmdMode4K2160p2997, "2160p29.97" },
+ { "4k2160p2997", bmdMode4K2160p2997, "2160p29.97" },
+ { "2160p30", bmdMode4K2160p30, "2160p30" },
+ { "4k2160p30", bmdMode4K2160p30, "2160p30" },
+ { "2160p50", bmdMode4K2160p50, "2160p50" },
+ { "4k2160p50", bmdMode4K2160p50, "2160p50" },
+ { "2160p5994", bmdMode4K2160p5994, "2160p59.94" },
+ { "4k2160p5994", bmdMode4K2160p5994, "2160p59.94" },
+ { "2160p60", bmdMode4K2160p60, "2160p60" },
+ { "4k2160p60", bmdMode4K2160p60, "2160p60" }
+ };
+
+ for (const ModeOption& option : options)
+ {
+ if (combinedToken == option.token || (frameToken.empty() && formatToken == option.token))
+ {
+ displayMode = option.mode;
+ displayModeName = option.displayName;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode)
+{
+ if (!iterator || !foundMode)
+ return false;
+
+ *foundMode = NULL;
+ IDeckLinkDisplayMode* candidate = NULL;
+ while (iterator->Next(&candidate) == S_OK)
+ {
+ if (candidate->GetDisplayMode() == targetMode)
+ {
+ *foundMode = candidate;
+ return true;
+ }
+
+ candidate->Release();
+ candidate = NULL;
+ }
+
+ return false;
+}
diff --git a/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkDisplayMode.h b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkDisplayMode.h
new file mode 100644
index 0000000..3896f4b
--- /dev/null
+++ b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkDisplayMode.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "DeckLinkAPI_h.h"
+
+#include
+
+std::string NormalizeModeToken(const std::string& value);
+bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName);
+bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode);
diff --git a/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkFrameTransfer.cpp b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkFrameTransfer.cpp
new file mode 100644
index 0000000..f561c9d
--- /dev/null
+++ b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkFrameTransfer.cpp
@@ -0,0 +1,409 @@
+#include "DeckLinkFrameTransfer.h"
+
+#include "OpenGLComposite.h"
+
+#include
+
+DEFINE_GUID(IID_PinnedMemoryAllocator,
+ 0xddf921a6, 0x279d, 0x4dcd, 0x86, 0x26, 0x75, 0x7f, 0x58, 0xa8, 0xc4, 0x35);
+
+////////////////////////////////////////////
+// PinnedMemoryAllocator
+////////////////////////////////////////////
+
+// PinnedMemoryAllocator implements the IDeckLinkVideoBufferAllocator interface to be used instead of the
+// built-in buffer allocator.
+//
+// For this sample application a custom buffer allocator is used to ensure each address
+// of buffer memory is aligned on a 4kB boundary required by the OpenGL pinned memory extension.
+// If the pinned memory extension is not available, this allocator will still be used and
+// demonstrates how to cache buffer allocations for efficiency.
+//
+// The frame cache delays the releasing of buffers until the cache fills up, thereby avoiding an
+// allocate plus pin operation for every frame, followed by an unpin and deallocate on every frame.
+PinnedMemoryAllocator::PinnedMemoryAllocator(HDC hdc, HGLRC hglrc, VideoFrameTransfer::Direction direction, unsigned cacheSize, unsigned bufferSize) :
+ mHGLDC(hdc),
+ mHGLRC(hglrc),
+ mRefCount(1),
+ mDirection(direction),
+ mBufferSize(bufferSize),
+ mFrameCacheSize(cacheSize)
+{
+}
+
+PinnedMemoryAllocator::~PinnedMemoryAllocator()
+{
+ // Cleanup any unused buffers that remain in the cache
+ while (!mFrameCache.empty())
+ {
+ unPinAddress(mFrameCache.back());
+ VirtualFree(mFrameCache.back(), 0, MEM_RELEASE);
+ mFrameCache.pop_back();
+ }
+
+ for (auto iter = mFrameTransfer.begin(); iter != mFrameTransfer.end(); ++iter)
+ delete iter->second;
+ mFrameTransfer.clear();
+}
+
+bool PinnedMemoryAllocator::transferFrame(void* address, GLuint gpuTexture)
+{
+ if (mFrameTransfer.count(address) == 0)
+ {
+ // VideoFrameTransfer prepares and pins address
+ mFrameTransfer[address] = new VideoFrameTransfer(mBufferSize, address, mDirection);
+ }
+
+ return mFrameTransfer[address]->performFrameTransfer();
+}
+
+void PinnedMemoryAllocator::waitForTransferComplete(void* address)
+{
+ if (mFrameTransfer.count(address))
+ mFrameTransfer[address]->waitForTransferComplete();
+}
+
+void PinnedMemoryAllocator::unPinAddress(void* address)
+{
+ // un-pin address only if it has been pinned for transfer
+ if (mFrameTransfer.count(address) > 0)
+ {
+ wglMakeCurrent(mHGLDC, mHGLRC);
+ mFrameTransfer.erase(address);
+ wglMakeCurrent(NULL, NULL);
+ }
+}
+
+HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::QueryInterface(REFIID iid, LPVOID* ppv)
+{
+ if (!ppv)
+ return E_POINTER;
+
+ if (iid == IID_IUnknown || iid == IID_PinnedMemoryAllocator)
+ {
+ *ppv = this;
+ }
+ else if (iid == IID_IDeckLinkVideoBufferAllocator)
+ {
+ *ppv = static_cast(this);
+ }
+ else
+ {
+ *ppv = nullptr;
+ return E_NOINTERFACE;
+ }
+ AddRef();
+ return S_OK;
+}
+
+ULONG STDMETHODCALLTYPE PinnedMemoryAllocator::AddRef(void)
+{
+ return ++mRefCount;
+}
+
+ULONG STDMETHODCALLTYPE PinnedMemoryAllocator::Release(void)
+{
+ int newCount = --mRefCount;
+ if (newCount == 0)
+ delete this;
+ return newCount;
+}
+
+HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::AllocateVideoBuffer(IDeckLinkVideoBuffer** allocatedBuffer)
+{
+ std::shared_ptr sharedMemBuffer;
+
+ // Manage caching of allocated buffers via shared_ptr deleter.
+ auto deleter = [this](void* buffer) mutable {
+ if (mFrameCache.size() < mFrameCacheSize)
+ {
+ mFrameCache.push_back(buffer);
+ }
+ else
+ {
+ // No room left in cache, so un-pin (if it was pinned) and free this buffer
+ unPinAddress(buffer);
+ VirtualFree(buffer, 0, MEM_RELEASE);
+ }
+ // We AddRef this class once the deleter is used because this class owns the mem
+ Release();
+ };
+
+ if (mFrameCache.empty())
+ {
+ // Allocate memory on a page boundary
+ void* memBuffer = VirtualAlloc(NULL, mBufferSize, MEM_COMMIT | MEM_RESERVE | MEM_WRITE_WATCH, PAGE_READWRITE);
+ if (!memBuffer)
+ return E_OUTOFMEMORY;
+
+ sharedMemBuffer = std::shared_ptr(memBuffer, deleter);
+ }
+ else
+ {
+ // Re-use most recently released address
+ sharedMemBuffer = std::shared_ptr(mFrameCache.back(), deleter);
+ mFrameCache.pop_back();
+ }
+
+ // This class owns the mem so the buffer we return needs to AddRef() this, and Release() in the deleter
+ AddRef();
+
+ *allocatedBuffer = new DeckLinkVideoBuffer(sharedMemBuffer, this);
+ return S_OK;
+}
+
+////////////////////////////////////////////
+// InputAllocatorPool Class
+////////////////////////////////////////////
+InputAllocatorPool::InputAllocatorPool(HDC hdc, HGLRC hglrc)
+{
+ mHDC = hdc;
+ mHGLRC = hglrc;
+}
+
+HRESULT InputAllocatorPool::QueryInterface(REFIID iid, void** ppv)
+{
+ if (!ppv)
+ return E_POINTER;
+
+ if (iid == IID_IUnknown)
+ {
+ *ppv = this;
+ }
+ else if (iid == IID_IDeckLinkVideoBufferAllocatorProvider)
+ {
+ *ppv = static_cast(this);
+ }
+ else
+ {
+ *ppv = nullptr;
+ return E_NOINTERFACE;
+ }
+ AddRef();
+ return S_OK;
+}
+
+ULONG InputAllocatorPool::AddRef(void)
+{
+ return ++mRefCount;
+}
+
+ULONG InputAllocatorPool::Release(void)
+{
+ int newCount = --mRefCount;
+ if (newCount == 0)
+ delete this;
+ return newCount;
+}
+
+HRESULT InputAllocatorPool::GetVideoBufferAllocator(
+ /* [in] */ unsigned int bufferSize,
+ /* [in] */ unsigned int,
+ /* [in] */ unsigned int,
+ /* [in] */ unsigned int,
+ /* [in] */ BMDPixelFormat,
+ /* [out] */ IDeckLinkVideoBufferAllocator** allocator)
+{
+ if (!allocator)
+ return E_POINTER;
+
+ auto existing = mAllocatorBySize.find(bufferSize);
+ if (existing != mAllocatorBySize.end())
+ {
+ *allocator = &*existing->second;
+ (*allocator)->AddRef();
+ return S_OK;
+ }
+
+ CComPtr newAllocator;
+ newAllocator.Attach(new (std::nothrow) PinnedMemoryAllocator(mHDC, mHGLRC, VideoFrameTransfer::CPUtoGPU, 3, bufferSize));
+ if (!newAllocator)
+ return E_OUTOFMEMORY;
+
+ mAllocatorBySize.emplace(std::make_pair(bufferSize, newAllocator));
+ *allocator = newAllocator.Detach();
+ return S_OK;
+}
+
+////////////////////////////////////////////
+// DeckLink Video Buffer Class
+////////////////////////////////////////////
+DeckLinkVideoBuffer::DeckLinkVideoBuffer(std::shared_ptr& buffer, PinnedMemoryAllocator* parent) :
+ mParentAllocator(parent),
+ mRefCount(1),
+ mBuffer(buffer)
+{
+}
+
+HRESULT STDMETHODCALLTYPE DeckLinkVideoBuffer::QueryInterface(REFIID riid, void** ppvObject)
+{
+ HRESULT result = S_OK;
+
+ if (ppvObject == nullptr)
+ return E_POINTER;
+
+ if (riid == IID_IUnknown)
+ {
+ *ppvObject = this;
+ AddRef();
+ }
+ else if (riid == IID_IDeckLinkVideoBuffer)
+ {
+ *ppvObject = static_cast(this);
+ AddRef();
+ }
+ else if (riid == IID_PinnedMemoryAllocator)
+ {
+ result = mParentAllocator->QueryInterface(riid, ppvObject);
+ }
+ else
+ {
+ *ppvObject = nullptr;
+ result = E_NOINTERFACE;
+ }
+
+ return result;
+}
+
+ULONG STDMETHODCALLTYPE DeckLinkVideoBuffer::AddRef()
+{
+ return ++mRefCount;
+}
+
+ULONG STDMETHODCALLTYPE DeckLinkVideoBuffer::Release()
+{
+ int newValue = --mRefCount;
+ if (newValue == 0)
+ delete this;
+
+ return newValue;
+}
+
+HRESULT STDMETHODCALLTYPE DeckLinkVideoBuffer::GetBytes(void** buffer)
+{
+ if (buffer == nullptr)
+ return E_POINTER;
+
+ *buffer = mBuffer.get();
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE DeckLinkVideoBuffer::GetSize(uint64_t* size)
+{
+ if (size == nullptr)
+ return E_POINTER;
+
+ *size = mParentAllocator->bufferSize();
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE DeckLinkVideoBuffer::StartAccess(BMDBufferAccessFlags)
+{
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE DeckLinkVideoBuffer::EndAccess(BMDBufferAccessFlags)
+{
+ return S_OK;
+}
+
+////////////////////////////////////////////
+// DeckLink Capture Delegate Class
+////////////////////////////////////////////
+CaptureDelegate::CaptureDelegate(OpenGLComposite* pOwner) :
+ m_pOwner(pOwner),
+ mRefCount(1)
+{
+}
+
+HRESULT CaptureDelegate::QueryInterface(REFIID, LPVOID* ppv)
+{
+ *ppv = NULL;
+ return E_NOINTERFACE;
+}
+
+ULONG CaptureDelegate::AddRef()
+{
+ return InterlockedIncrement(&mRefCount);
+}
+
+ULONG CaptureDelegate::Release()
+{
+ int newCount = InterlockedDecrement(&mRefCount);
+ if (newCount == 0)
+ delete this;
+ return newCount;
+}
+
+HRESULT CaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* inputFrame, IDeckLinkAudioInputPacket*)
+{
+ if (!inputFrame)
+ {
+ // It's possible to receive a NULL inputFrame, but a valid audioPacket. Ignore audio-only frame.
+ return S_OK;
+ }
+
+ bool hasNoInputSource = (inputFrame->GetFlags() & bmdFrameHasNoInputSource) == bmdFrameHasNoInputSource;
+ m_pOwner->VideoFrameArrived(inputFrame, hasNoInputSource);
+ return S_OK;
+}
+
+HRESULT CaptureDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents, IDeckLinkDisplayMode*, BMDDetectedVideoInputFormatFlags)
+{
+ return S_OK;
+}
+
+////////////////////////////////////////////
+// DeckLink Playout Delegate Class
+////////////////////////////////////////////
+PlayoutDelegate::PlayoutDelegate(OpenGLComposite* pOwner) :
+ m_pOwner(pOwner),
+ mRefCount(1)
+{
+}
+
+HRESULT PlayoutDelegate::QueryInterface(REFIID, LPVOID* ppv)
+{
+ *ppv = NULL;
+ return E_NOINTERFACE;
+}
+
+ULONG PlayoutDelegate::AddRef()
+{
+ return InterlockedIncrement(&mRefCount);
+}
+
+ULONG PlayoutDelegate::Release()
+{
+ int newCount = InterlockedDecrement(&mRefCount);
+ if (newCount == 0)
+ delete this;
+ return newCount;
+}
+
+HRESULT PlayoutDelegate::ScheduledFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result)
+{
+ switch (result)
+ {
+ case bmdOutputFrameDisplayedLate:
+ OutputDebugStringA("ScheduledFrameCompleted() frame did not complete: Frame Displayed Late\n");
+ break;
+ case bmdOutputFrameDropped:
+ OutputDebugStringA("ScheduledFrameCompleted() frame did not complete: Frame Dropped\n");
+ break;
+ case bmdOutputFrameCompleted:
+ case bmdOutputFrameFlushed:
+ // Don't log bmdOutputFrameFlushed result since it is expected when Stop() is called
+ break;
+ default:
+ OutputDebugStringA("ScheduledFrameCompleted() frame did not complete: Unknown error\n");
+ }
+
+ m_pOwner->PlayoutFrameCompleted(completedFrame, result);
+ return S_OK;
+}
+
+HRESULT PlayoutDelegate::ScheduledPlaybackHasStopped()
+{
+ return S_OK;
+}
diff --git a/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkFrameTransfer.h b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkFrameTransfer.h
new file mode 100644
index 0000000..ac7ab58
--- /dev/null
+++ b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkFrameTransfer.h
@@ -0,0 +1,147 @@
+#pragma once
+
+#include
+#include
+
+#include
+#include
+#include