From 0bfffa655294e3547fe9acb92f49678b3a44be05 Mon Sep 17 00:00:00 2001 From: Aiden Date: Wed, 6 May 2026 09:28:46 +1000 Subject: [PATCH] Refactor --- CMakeLists.txt | 99 +- README.md | 1 - .../LoopThroughWithOpenGLCompositing.vcxproj | 20 +- ...roughWithOpenGLCompositing.vcxproj.filters | 12 +- .../{ => control}/ControlServer.cpp | 0 .../{ => control}/ControlServer.h | 0 .../{ => control}/OscServer.cpp | 0 .../{ => control}/OscServer.h | 0 .../control/RuntimeControlBridge.cpp | 50 + .../control/RuntimeControlBridge.h | 15 + .../decklink/DeckLinkDisplayMode.cpp | 110 ++ .../decklink/DeckLinkDisplayMode.h | 9 + .../decklink/DeckLinkFrameTransfer.cpp | 409 ++++++ .../decklink/DeckLinkFrameTransfer.h | 147 ++ .../{ => gl}/GLExtensions.cpp | 0 .../{ => gl}/GLExtensions.h | 0 .../gl/GlScopedObjects.h | 57 + .../{ => gl}/OpenGLComposite.cpp | 1196 +---------------- .../{ => gl}/OpenGLComposite.h | 137 +- .../gl/OpenGLCompositeRuntimeControls.cpp | 127 ++ .../gl/Std140Buffer.h | 48 + .../gl/TextRasterizer.cpp | 316 +++++ .../gl/TextRasterizer.h | 10 + .../gl/TextureAssetLoader.cpp | 114 ++ .../gl/TextureAssetLoader.h | 10 + .../{ => gl}/VideoFrameTransfer.cpp | 0 .../{ => gl}/VideoFrameTransfer.h | 0 .../{ => platform}/NativeHandles.h | 0 .../{ => platform}/NativeSockets.h | 0 .../{ => runtime}/RuntimeHost.cpp | 0 .../{ => runtime}/RuntimeHost.h | 0 .../{ => runtime}/RuntimeJson.cpp | 0 .../{ => runtime}/RuntimeJson.h | 0 .../{ => runtime}/RuntimeParameterUtils.cpp | 0 .../{ => runtime}/RuntimeParameterUtils.h | 0 .../{ => shader}/ShaderCompiler.cpp | 0 .../{ => shader}/ShaderCompiler.h | 0 .../{ => shader}/ShaderPackageRegistry.cpp | 0 .../{ => shader}/ShaderPackageRegistry.h | 0 .../{ => shader}/ShaderTypes.h | 0 tests/Std140BufferTests.cpp | 73 + 41 files changed, 1592 insertions(+), 1368 deletions(-) rename apps/LoopThroughWithOpenGLCompositing/{ => control}/ControlServer.cpp (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => control}/ControlServer.h (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => control}/OscServer.cpp (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => control}/OscServer.h (100%) create mode 100644 apps/LoopThroughWithOpenGLCompositing/control/RuntimeControlBridge.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/control/RuntimeControlBridge.h create mode 100644 apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkDisplayMode.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkDisplayMode.h create mode 100644 apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkFrameTransfer.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkFrameTransfer.h rename apps/LoopThroughWithOpenGLCompositing/{ => gl}/GLExtensions.cpp (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => gl}/GLExtensions.h (100%) create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/GlScopedObjects.h rename apps/LoopThroughWithOpenGLCompositing/{ => gl}/OpenGLComposite.cpp (66%) rename apps/LoopThroughWithOpenGLCompositing/{ => gl}/OpenGLComposite.h (65%) create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/OpenGLCompositeRuntimeControls.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/Std140Buffer.h create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/TextRasterizer.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/TextRasterizer.h create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/TextureAssetLoader.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/TextureAssetLoader.h rename apps/LoopThroughWithOpenGLCompositing/{ => gl}/VideoFrameTransfer.cpp (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => gl}/VideoFrameTransfer.h (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => platform}/NativeHandles.h (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => platform}/NativeSockets.h (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => runtime}/RuntimeHost.cpp (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => runtime}/RuntimeHost.h (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => runtime}/RuntimeJson.cpp (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => runtime}/RuntimeJson.h (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => runtime}/RuntimeParameterUtils.cpp (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => runtime}/RuntimeParameterUtils.h (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => shader}/ShaderCompiler.cpp (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => shader}/ShaderCompiler.h (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => shader}/ShaderPackageRegistry.cpp (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => shader}/ShaderPackageRegistry.h (100%) rename apps/LoopThroughWithOpenGLCompositing/{ => shader}/ShaderTypes.h (100%) create mode 100644 tests/Std140BufferTests.cpp 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 +#include +#include + +#include "DeckLinkAPI_h.h" +#include "VideoFrameTransfer.h" + +extern "C" const IID IID_PinnedMemoryAllocator; + +class OpenGLComposite; + +//////////////////////////////////////////// +// PinnedMemoryAllocator +//////////////////////////////////////////// +class PinnedMemoryAllocator : public IDeckLinkVideoBufferAllocator +{ +public: + PinnedMemoryAllocator(HDC hdc, HGLRC hglrc, VideoFrameTransfer::Direction direction, unsigned cacheSize, unsigned bufferSize); + virtual ~PinnedMemoryAllocator(); + + bool transferFrame(void* address, GLuint gpuTexture); + void waitForTransferComplete(void* address); + unsigned bufferSize() { return mBufferSize; } + + // IUnknown methods + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv) override; + virtual ULONG STDMETHODCALLTYPE AddRef(void) override; + virtual ULONG STDMETHODCALLTYPE Release(void) override; + + // IDeckLinkVideoBufferAllocator methods + virtual HRESULT STDMETHODCALLTYPE AllocateVideoBuffer(IDeckLinkVideoBuffer** allocatedBuffer) override; + +private: + void unPinAddress(void* address); + +private: + HDC mHGLDC; + HGLRC mHGLRC; + std::atomic mRefCount; + VideoFrameTransfer::Direction mDirection; + std::map mFrameTransfer; + unsigned mBufferSize; + std::vector mFrameCache; + unsigned mFrameCacheSize; +}; + +//////////////////////////////////////////// +// InputAllocatorPool +//////////////////////////////////////////// +class InputAllocatorPool : public IDeckLinkVideoBufferAllocatorProvider +{ +public: + InputAllocatorPool(HDC hdc, HGLRC hglrc); + + // IUnknown interface + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppv) override; + + // IDeckLinkVideoBufferAllocatorProvider interface + HRESULT STDMETHODCALLTYPE GetVideoBufferAllocator( + /* [in] */ unsigned int bufferSize, + /* [in] */ unsigned int width, + /* [in] */ unsigned int height, + /* [in] */ unsigned int rowBytes, + /* [in] */ BMDPixelFormat pixelFormat, + /* [out] */ IDeckLinkVideoBufferAllocator** allocator) override; + +private: + std::atomic mRefCount; + std::map > mAllocatorBySize; + HDC mHDC; + HGLRC mHGLRC; +}; + +//////////////////////////////////////////// +// DeckLinkVideoBuffer +//////////////////////////////////////////// +class DeckLinkVideoBuffer : public IDeckLinkVideoBuffer +{ +public: + explicit DeckLinkVideoBuffer(std::shared_ptr& buffer, PinnedMemoryAllocator* parent); + virtual ~DeckLinkVideoBuffer() = default; + + // IUnknown interface + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override; + virtual ULONG STDMETHODCALLTYPE AddRef(void) override; + virtual ULONG STDMETHODCALLTYPE Release(void) override; + + // IDeckLinkVideoBuffer interface + virtual HRESULT STDMETHODCALLTYPE GetBytes(void** buffer) override; + virtual HRESULT STDMETHODCALLTYPE GetSize(uint64_t* size) override; + virtual HRESULT STDMETHODCALLTYPE StartAccess(BMDBufferAccessFlags flags) override; + virtual HRESULT STDMETHODCALLTYPE EndAccess(BMDBufferAccessFlags flags) override; + +private: + CComPtr mParentAllocator; + std::atomic mRefCount; + std::shared_ptr mBuffer; +}; + +//////////////////////////////////////////// +// Capture Delegate Class +//////////////////////////////////////////// +class CaptureDelegate : public IDeckLinkInputCallback +{ + OpenGLComposite* m_pOwner; + LONG mRefCount; + +public: + CaptureDelegate(OpenGLComposite* pOwner); + + // IUnknown needs only a dummy implementation + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv); + virtual ULONG STDMETHODCALLTYPE AddRef(); + virtual ULONG STDMETHODCALLTYPE Release(); + + virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket* audioPacket); + virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode* newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags); +}; + +//////////////////////////////////////////// +// Render Delegate Class +//////////////////////////////////////////// +class PlayoutDelegate : public IDeckLinkVideoOutputCallback +{ + OpenGLComposite* m_pOwner; + LONG mRefCount; + +public: + PlayoutDelegate(OpenGLComposite* pOwner); + + // IUnknown needs only a dummy implementation + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv); + virtual ULONG STDMETHODCALLTYPE AddRef(); + virtual ULONG STDMETHODCALLTYPE Release(); + + virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result); + virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped(); +}; diff --git a/apps/LoopThroughWithOpenGLCompositing/GLExtensions.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/GLExtensions.cpp similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/GLExtensions.cpp rename to apps/LoopThroughWithOpenGLCompositing/gl/GLExtensions.cpp diff --git a/apps/LoopThroughWithOpenGLCompositing/GLExtensions.h b/apps/LoopThroughWithOpenGLCompositing/gl/GLExtensions.h similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/GLExtensions.h rename to apps/LoopThroughWithOpenGLCompositing/gl/GLExtensions.h diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/GlScopedObjects.h b/apps/LoopThroughWithOpenGLCompositing/gl/GlScopedObjects.h new file mode 100644 index 0000000..420e345 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/GlScopedObjects.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +class ScopedGlShader +{ +public: + explicit ScopedGlShader(GLuint shader = 0) : mShader(shader) {} + ~ScopedGlShader() { reset(); } + + ScopedGlShader(const ScopedGlShader&) = delete; + ScopedGlShader& operator=(const ScopedGlShader&) = delete; + + GLuint get() const { return mShader; } + GLuint release() + { + GLuint shader = mShader; + mShader = 0; + return shader; + } + void reset(GLuint shader = 0) + { + if (mShader != 0) + glDeleteShader(mShader); + mShader = shader; + } + +private: + GLuint mShader; +}; + +class ScopedGlProgram +{ +public: + explicit ScopedGlProgram(GLuint program = 0) : mProgram(program) {} + ~ScopedGlProgram() { reset(); } + + ScopedGlProgram(const ScopedGlProgram&) = delete; + ScopedGlProgram& operator=(const ScopedGlProgram&) = delete; + + GLuint get() const { return mProgram; } + GLuint release() + { + GLuint program = mProgram; + mProgram = 0; + return program; + } + void reset(GLuint program = 0) + { + if (mProgram != 0) + glDeleteProgram(mProgram); + mProgram = program; + } + +private: + GLuint mProgram; +}; diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp similarity index 66% rename from apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp rename to apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index 7a03279..2c97251 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -39,17 +39,21 @@ */ #include "ControlServer.h" +#include "DeckLinkDisplayMode.h" +#include "DeckLinkFrameTransfer.h" #include "OpenGLComposite.h" #include "GLExtensions.h" +#include "GlScopedObjects.h" #include "OscServer.h" +#include "RuntimeControlBridge.h" +#include "Std140Buffer.h" +#include "TextRasterizer.h" +#include "TextureAssetLoader.h" #include #include #include #include -#include -#include -#include #include #include #include @@ -57,10 +61,6 @@ #include #include -#include -DEFINE_GUID(IID_PinnedMemoryAllocator, - 0xddf921a6, 0x279d, 0x4dcd, 0x86, 0x26, 0x75, 0x7f, 0x58, 0xa8, 0xc4, 0x35); - namespace { constexpr GLuint kDecodedVideoTextureUnit = 1; @@ -68,12 +68,6 @@ constexpr GLuint kSourceHistoryTextureUnitBase = 2; constexpr GLuint kPackedVideoTextureUnit = 2; constexpr GLuint kGlobalParamsBindingPoint = 0; constexpr unsigned kPrerollFrameCount = 8; -constexpr unsigned kTextTextureWidth = 2048; -constexpr unsigned kTextTextureHeight = 256; -constexpr int kTextSdfSpread = 20; -constexpr unsigned kTextSdfBlurPasses = 1; -constexpr float kTextFontPixelSize = 144.0f; -constexpr float kTextLayoutPadding = 48.0f; const char* kVertexShaderSource = "#version 430 core\n" "out vec2 vTexCoord;\n" @@ -110,31 +104,6 @@ const char* kDecodeFragmentShaderSource = " fragColor = rec709YCbCr2rgba(ySample, macroPixel.b, macroPixel.r, 1.0);\n" "}\n"; -class GdiplusSession -{ -public: - GdiplusSession() - { - Gdiplus::GdiplusStartupInput startupInput; - mStarted = Gdiplus::GdiplusStartup(&mToken, &startupInput, NULL) == Gdiplus::Ok; - } - - ~GdiplusSession() - { - if (mStarted) - Gdiplus::GdiplusShutdown(mToken); - } - - GdiplusSession(const GdiplusSession&) = delete; - GdiplusSession& operator=(const GdiplusSession&) = delete; - - bool started() const { return mStarted; } - -private: - ULONG_PTR mToken = 0; - bool mStarted = false; -}; - void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage) { if (!errorMessage || errorMessageSize <= 0) @@ -143,18 +112,6 @@ void CopyErrorMessage(const std::string& message, int errorMessageSize, char* er strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE); } -std::wstring Utf8ToWide(const std::string& text) -{ - if (text.empty()) - return std::wstring(); - const int required = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, NULL, 0); - if (required <= 1) - return std::wstring(); - std::wstring wide(static_cast(required - 1), L'\0'); - MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, wide.data(), required); - return wide; -} - std::string TextValueForBinding(const RuntimeRenderState& state, const std::string& parameterId) { auto valueIt = state.parameterValues.find(parameterId); @@ -174,184 +131,6 @@ const ShaderFontAsset* FindFontAssetForParameter(const RuntimeRenderState& state return state.fontAssets.empty() ? nullptr : &state.fontAssets.front(); } -std::vector BuildLocalSdf(const std::vector& alpha, unsigned width, unsigned height) -{ - std::vector sdf(static_cast(width) * height * 4, 0); - for (unsigned y = 0; y < height; ++y) - { - for (unsigned x = 0; x < width; ++x) - { - const bool inside = alpha[static_cast(y) * width + x] > 127; - int bestDistanceSq = kTextSdfSpread * kTextSdfSpread; - for (int oy = -kTextSdfSpread; oy <= kTextSdfSpread; ++oy) - { - const int sy = static_cast(y) + oy; - if (sy < 0 || sy >= static_cast(height)) - continue; - for (int ox = -kTextSdfSpread; ox <= kTextSdfSpread; ++ox) - { - const int sx = static_cast(x) + ox; - if (sx < 0 || sx >= static_cast(width)) - continue; - const bool sampleInside = alpha[static_cast(sy) * width + sx] > 127; - if (sampleInside == inside) - continue; - const int distanceSq = ox * ox + oy * oy; - if (distanceSq < bestDistanceSq) - bestDistanceSq = distanceSq; - } - } - - const float distance = std::sqrt(static_cast(bestDistanceSq)); - const float signedDistance = (inside ? 1.0f : -1.0f) * distance; - float normalized = 0.5f + signedDistance / static_cast(kTextSdfSpread * 2); - const unsigned char sourceAlpha = alpha[static_cast(y) * width + x]; - if (sourceAlpha > 0 && sourceAlpha < 255) - normalized = static_cast(sourceAlpha) / 255.0f; - if (normalized < 0.0f) - normalized = 0.0f; - if (normalized > 1.0f) - normalized = 1.0f; - const unsigned char value = static_cast(normalized * 255.0f + 0.5f); - const std::size_t out = (static_cast(y) * width + x) * 4; - sdf[out + 0] = value; - sdf[out + 1] = value; - sdf[out + 2] = value; - sdf[out + 3] = value; - } - } - return sdf; -} - -std::vector BuildTextCoverageTexture(const std::vector& alpha, unsigned width, unsigned height) -{ - std::vector coverage(static_cast(width) * height * 4, 0); - for (unsigned y = 0; y < height; ++y) - { - for (unsigned x = 0; x < width; ++x) - { - const unsigned char value = alpha[static_cast(y) * width + x]; - const std::size_t out = (static_cast(y) * width + x) * 4; - coverage[out + 0] = value; - coverage[out + 1] = value; - coverage[out + 2] = value; - coverage[out + 3] = value; - } - } - return coverage; -} - -std::vector FlipTextTextureForShaderUv(const std::vector& pixels, unsigned width, unsigned height) -{ - std::vector flipped(pixels.size(), 0); - const std::size_t stride = static_cast(width) * 4; - for (unsigned y = 0; y < height; ++y) - { - const std::size_t srcOffset = static_cast(y) * stride; - const std::size_t dstOffset = static_cast(height - 1 - y) * stride; - std::memcpy(flipped.data() + dstOffset, pixels.data() + srcOffset, stride); - } - return flipped; -} - -std::vector BlurTextSdf(const std::vector& pixels, unsigned width, unsigned height, unsigned passes) -{ - std::vector current = pixels; - std::vector next(pixels.size(), 0); - for (unsigned pass = 0; pass < passes; ++pass) - { - for (unsigned y = 0; y < height; ++y) - { - for (unsigned x = 0; x < width; ++x) - { - unsigned weightedTotal = 0; - unsigned weightSum = 0; - for (int oy = -1; oy <= 1; ++oy) - { - const int sy = static_cast(y) + oy; - if (sy < 0 || sy >= static_cast(height)) - continue; - for (int ox = -1; ox <= 1; ++ox) - { - const int sx = static_cast(x) + ox; - if (sx < 0 || sx >= static_cast(width)) - continue; - const unsigned weight = (ox == 0 && oy == 0) ? 4u : ((ox == 0 || oy == 0) ? 2u : 1u); - const std::size_t sample = (static_cast(sy) * width + sx) * 4; - weightedTotal += static_cast(current[sample]) * weight; - weightSum += weight; - } - } - - const unsigned char value = static_cast((weightedTotal + weightSum / 2) / weightSum); - const std::size_t out = (static_cast(y) * width + x) * 4; - next[out + 0] = value; - next[out + 1] = value; - next[out + 2] = value; - next[out + 3] = value; - } - } - current.swap(next); - } - return current; -} - -void WriteTextMaskDebugDump(const std::string& text, const std::vector& alpha, const std::vector& sdf, unsigned width, unsigned height) -{ - try - { - std::filesystem::path debugDir = std::filesystem::current_path() / "runtime"; - std::filesystem::create_directories(debugDir); - - auto writePgm = [width, height](const std::filesystem::path& path, const std::vector& gray, std::size_t stride) - { - std::ofstream out(path, std::ios::binary); - if (!out) - return; - out << "P5\n" << width << " " << height << "\n255\n"; - for (unsigned y = 0; y < height; ++y) - { - for (unsigned x = 0; x < width; ++x) - out.put(static_cast(gray[(static_cast(y) * width + x) * stride])); - } - }; - - writePgm(debugDir / "text-mask-alpha-debug.pgm", alpha, 1); - writePgm(debugDir / "text-mask-sdf-debug.pgm", sdf, 4); - - unsigned alphaMin = 255; - unsigned alphaMax = 0; - unsigned sdfMin = 255; - unsigned sdfMax = 0; - std::size_t alphaLit = 0; - std::size_t sdfLit = 0; - for (unsigned char value : alpha) - { - alphaMin = std::min(alphaMin, value); - alphaMax = std::max(alphaMax, value); - if (value > 0) - ++alphaLit; - } - for (std::size_t index = 0; index < sdf.size(); index += 4) - { - const unsigned char value = sdf[index]; - sdfMin = std::min(sdfMin, value); - sdfMax = std::max(sdfMax, value); - if (value > 127) - ++sdfLit; - } - - std::ostringstream message; - message << "Text mask debug for '" << text << "': alpha min/max/lit=" << alphaMin << "/" << alphaMax << "/" << alphaLit - << ", sdf min/max/gt127=" << sdfMin << "/" << sdfMax << "/" << sdfLit << "\n"; - OutputDebugStringA(message.str().c_str()); - } - catch (...) - { - OutputDebugStringA("Failed to write text mask debug dump.\n"); - } -} - GLint FindSamplerUniformLocation(GLuint program, const std::string& samplerName) { GLint location = glGetUniformLocation(program, samplerName.c_str()); @@ -360,291 +139,6 @@ GLint FindSamplerUniformLocation(GLuint program, const std::string& samplerName) return glGetUniformLocation(program, (samplerName + "_0").c_str()); } -bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector& sdf, std::string& error) -{ - GdiplusSession gdiplus; - if (!gdiplus.started()) - { - error = "Could not start GDI+ for text rendering."; - return false; - } - - Gdiplus::PrivateFontCollection fontCollection; - Gdiplus::FontFamily fallbackFamily(L"Arial"); - Gdiplus::FontFamily* fontFamily = &fallbackFamily; - std::unique_ptr families; - const std::wstring wideFontPath = fontPath.empty() ? std::wstring() : fontPath.wstring(); - if (!wideFontPath.empty()) - { - if (fontCollection.AddFontFile(wideFontPath.c_str()) != Gdiplus::Ok) - { - error = "Could not load packaged font file for text rendering: " + fontPath.string(); - return false; - } - - const INT familyCount = fontCollection.GetFamilyCount(); - if (familyCount <= 0) - { - error = "Packaged font did not contain a usable font family: " + fontPath.string(); - return false; - } - - families.reset(new Gdiplus::FontFamily[familyCount]); - INT found = 0; - if (fontCollection.GetFamilies(familyCount, families.get(), &found) != Gdiplus::Ok || found <= 0) - { - error = "Could not read the packaged font family: " + fontPath.string(); - return false; - } - fontFamily = &families[0]; - } - - Gdiplus::Bitmap bitmap(kTextTextureWidth, kTextTextureHeight, PixelFormat32bppARGB); - Gdiplus::Graphics graphics(&bitmap); - graphics.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); - graphics.Clear(Gdiplus::Color(255, 0, 0, 0)); - graphics.SetCompositingMode(Gdiplus::CompositingModeSourceOver); - graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias); - graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); - Gdiplus::Font font(fontFamily, kTextFontPixelSize, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel); - Gdiplus::SolidBrush brush(Gdiplus::Color(255, 255, 255, 255)); - Gdiplus::StringFormat format; - format.SetAlignment(Gdiplus::StringAlignmentNear); - format.SetLineAlignment(Gdiplus::StringAlignmentCenter); - format.SetFormatFlags(Gdiplus::StringFormatFlagsNoWrap | Gdiplus::StringFormatFlagsMeasureTrailingSpaces); - const Gdiplus::RectF layout( - kTextLayoutPadding, - 0.0f, - static_cast(kTextTextureWidth) - (kTextLayoutPadding * 2.0f), - static_cast(kTextTextureHeight)); - const std::wstring wideText = Utf8ToWide(text); - graphics.DrawString(wideText.c_str(), -1, &font, layout, &format, &brush); - - std::vector alpha(static_cast(kTextTextureWidth) * kTextTextureHeight, 0); - for (unsigned y = 0; y < kTextTextureHeight; ++y) - { - for (unsigned x = 0; x < kTextTextureWidth; ++x) - { - Gdiplus::Color pixel; - bitmap.GetPixel(x, y, &pixel); - BYTE luminance = pixel.GetRed(); - if (pixel.GetGreen() > luminance) - luminance = pixel.GetGreen(); - if (pixel.GetBlue() > luminance) - luminance = pixel.GetBlue(); - alpha[static_cast(y) * kTextTextureWidth + x] = static_cast(luminance); - } - } - sdf = BuildTextCoverageTexture(alpha, kTextTextureWidth, kTextTextureHeight); - sdf = BlurTextSdf(sdf, kTextTextureWidth, kTextTextureHeight, kTextSdfBlurPasses); - sdf = FlipTextTextureForShaderUv(sdf, kTextTextureWidth, kTextTextureHeight); - WriteTextMaskDebugDump(text, alpha, sdf, kTextTextureWidth, kTextTextureHeight); - return true; -} - -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; -} - -class ScopedGlShader -{ -public: - explicit ScopedGlShader(GLuint shader = 0) : mShader(shader) {} - ~ScopedGlShader() { reset(); } - - ScopedGlShader(const ScopedGlShader&) = delete; - ScopedGlShader& operator=(const ScopedGlShader&) = delete; - - GLuint get() const { return mShader; } - GLuint release() - { - GLuint shader = mShader; - mShader = 0; - return shader; - } - void reset(GLuint shader = 0) - { - if (mShader != 0) - glDeleteShader(mShader); - mShader = shader; - } - -private: - GLuint mShader; -}; - -class ScopedGlProgram -{ -public: - explicit ScopedGlProgram(GLuint program = 0) : mProgram(program) {} - ~ScopedGlProgram() { reset(); } - - ScopedGlProgram(const ScopedGlProgram&) = delete; - ScopedGlProgram& operator=(const ScopedGlProgram&) = delete; - - GLuint get() const { return mProgram; } - GLuint release() - { - GLuint program = mProgram; - mProgram = 0; - return program; - } - void reset(GLuint program = 0) - { - if (mProgram != 0) - glDeleteProgram(mProgram); - mProgram = program; - } - -private: - GLuint mProgram; -}; - -std::size_t AlignStd140(std::size_t offset, std::size_t alignment) -{ - const std::size_t mask = alignment - 1; - return (offset + mask) & ~mask; -} - -template -void AppendStd140Value(std::vector& buffer, std::size_t alignment, const TValue& value) -{ - const std::size_t offset = AlignStd140(buffer.size(), alignment); - if (buffer.size() < offset + sizeof(TValue)) - buffer.resize(offset + sizeof(TValue), 0); - std::memcpy(buffer.data() + offset, &value, sizeof(TValue)); -} - -void AppendStd140Float(std::vector& buffer, float value) -{ - AppendStd140Value(buffer, 4, value); -} - -void AppendStd140Int(std::vector& buffer, int value) -{ - AppendStd140Value(buffer, 4, value); -} - -void AppendStd140Vec2(std::vector& buffer, float x, float y) -{ - const std::size_t offset = AlignStd140(buffer.size(), 8); - if (buffer.size() < offset + sizeof(float) * 2) - buffer.resize(offset + sizeof(float) * 2, 0); - float values[2] = { x, y }; - std::memcpy(buffer.data() + offset, values, sizeof(values)); -} - -void AppendStd140Vec4(std::vector& buffer, float x, float y, float z, float w) -{ - const std::size_t offset = AlignStd140(buffer.size(), 16); - if (buffer.size() < offset + sizeof(float) * 4) - buffer.resize(offset + sizeof(float) * 4, 0); - float values[4] = { x, y, z, w }; - std::memcpy(buffer.data() + offset, values, sizeof(values)); -} } OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : @@ -1277,43 +771,9 @@ bool OpenGLComposite::InitOpenGLState() return false; } - ControlServer::Callbacks callbacks; - callbacks.getStateJson = [this]() { return GetRuntimeStateJson(); }; - callbacks.addLayer = [this](const std::string& shaderId, std::string& error) { return AddLayer(shaderId, error); }; - callbacks.removeLayer = [this](const std::string& layerId, std::string& error) { return RemoveLayer(layerId, error); }; - callbacks.moveLayer = [this](const std::string& layerId, int direction, std::string& error) { return MoveLayer(layerId, direction, error); }; - callbacks.moveLayerToIndex = [this](const std::string& layerId, std::size_t targetIndex, std::string& error) { return MoveLayerToIndex(layerId, targetIndex, error); }; - callbacks.setLayerBypass = [this](const std::string& layerId, bool bypassed, std::string& error) { return SetLayerBypass(layerId, bypassed, error); }; - callbacks.setLayerShader = [this](const std::string& layerId, const std::string& shaderId, std::string& error) { return SetLayerShader(layerId, shaderId, error); }; - callbacks.updateLayerParameter = [this](const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error) { - return UpdateLayerParameterJson(layerId, parameterId, valueJson, error); - }; - callbacks.resetLayerParameters = [this](const std::string& layerId, std::string& error) { return ResetLayerParameters(layerId, error); }; - callbacks.saveStackPreset = [this](const std::string& presetName, std::string& error) { return SaveStackPreset(presetName, error); }; - callbacks.loadStackPreset = [this](const std::string& presetName, std::string& error) { return LoadStackPreset(presetName, error); }; - callbacks.reloadShader = [this](std::string& error) { - if (!ReloadShader()) - { - error = "Shader reload failed. See native app status for details."; - return false; - } - return true; - }; - - if (!mControlServer->Start(mRuntimeHost->GetUiRoot(), mRuntimeHost->GetDocsRoot(), mRuntimeHost->GetServerPort(), callbacks, runtimeError)) + if (!StartRuntimeControlServices(*this, *mRuntimeHost, *mControlServer, *mOscServer, runtimeError)) { - MessageBoxA(NULL, runtimeError.c_str(), "Local control server failed to start", MB_OK); - return false; - } - mRuntimeHost->SetServerPort(mControlServer->GetPort()); - - OscServer::Callbacks oscCallbacks; - oscCallbacks.updateParameter = [this](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error) { - return UpdateLayerParameterByControlKeyJson(layerKey, parameterKey, valueJson, error); - }; - if (mRuntimeHost->GetOscPort() > 0 && !mOscServer->Start(mRuntimeHost->GetOscPort(), oscCallbacks, runtimeError)) - { - MessageBoxA(NULL, runtimeError.c_str(), "OSC control server failed to start", MB_OK); + MessageBoxA(NULL, runtimeError.c_str(), "Runtime control services failed to start", MB_OK); return false; } @@ -2027,108 +1487,7 @@ void OpenGLComposite::destroyLayerPrograms() bool OpenGLComposite::loadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error) { - textureId = 0; - - HRESULT comInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED); - const bool shouldUninitializeCom = (comInitResult == S_OK || comInitResult == S_FALSE); - if (FAILED(comInitResult) && comInitResult != RPC_E_CHANGED_MODE) - { - error = "Could not initialize COM to load shader texture assets."; - return false; - } - - CComPtr imagingFactory; - HRESULT result = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&imagingFactory)); - if (FAILED(result) || !imagingFactory) - { - if (shouldUninitializeCom) - CoUninitialize(); - error = "Could not create a WIC imaging factory to load shader texture assets."; - return false; - } - - CComPtr bitmapDecoder; - result = imagingFactory->CreateDecoderFromFilename(textureAsset.path.wstring().c_str(), NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &bitmapDecoder); - if (FAILED(result) || !bitmapDecoder) - { - if (shouldUninitializeCom) - CoUninitialize(); - error = "Could not open shader texture asset: " + textureAsset.path.string(); - return false; - } - - CComPtr bitmapFrame; - result = bitmapDecoder->GetFrame(0, &bitmapFrame); - if (FAILED(result) || !bitmapFrame) - { - if (shouldUninitializeCom) - CoUninitialize(); - error = "Could not decode the first frame of shader texture asset: " + textureAsset.path.string(); - return false; - } - - CComPtr formatConverter; - result = imagingFactory->CreateFormatConverter(&formatConverter); - if (FAILED(result) || !formatConverter) - { - if (shouldUninitializeCom) - CoUninitialize(); - error = "Could not create a WIC format converter for shader texture asset: " + textureAsset.path.string(); - return false; - } - - result = formatConverter->Initialize(bitmapFrame, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, NULL, 0.0, WICBitmapPaletteTypeCustom); - if (FAILED(result)) - { - if (shouldUninitializeCom) - CoUninitialize(); - error = "Could not convert shader texture asset to BGRA: " + textureAsset.path.string(); - return false; - } - - UINT width = 0; - UINT height = 0; - result = formatConverter->GetSize(&width, &height); - if (FAILED(result) || width == 0 || height == 0) - { - if (shouldUninitializeCom) - CoUninitialize(); - error = "Shader texture asset has an invalid size: " + textureAsset.path.string(); - return false; - } - - const UINT stride = width * 4; - std::vector pixels(static_cast(stride) * static_cast(height)); - result = formatConverter->CopyPixels(NULL, stride, static_cast(pixels.size()), pixels.data()); - if (FAILED(result)) - { - if (shouldUninitializeCom) - CoUninitialize(); - error = "Could not read shader texture pixels: " + textureAsset.path.string(); - return false; - } - - std::vector flippedPixels(pixels.size()); - for (UINT row = 0; row < height; ++row) - { - const std::size_t srcOffset = static_cast(row) * stride; - const std::size_t dstOffset = static_cast(height - 1 - row) * stride; - std::memcpy(flippedPixels.data() + dstOffset, pixels.data() + srcOffset, stride); - } - - glGenTextures(1, &textureId); - glBindTexture(GL_TEXTURE_2D, textureId); - 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_RGBA8, static_cast(width), static_cast(height), 0, GL_BGRA, GL_UNSIGNED_BYTE, flippedPixels.data()); - glBindTexture(GL_TEXTURE_2D, 0); - - if (shouldUninitializeCom) - CoUninitialize(); - - return true; + return LoadTextureAsset(textureAsset, textureId, error); } bool OpenGLComposite::renderTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error) @@ -2675,132 +2034,6 @@ bool OpenGLComposite::updateGlobalParamsBuffer(const RuntimeRenderState& state, return true; } -std::string OpenGLComposite::GetRuntimeStateJson() const -{ - return mRuntimeHost ? mRuntimeHost->BuildStateJson() : "{}"; -} - -bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error) -{ - if (!mRuntimeHost->AddLayer(shaderId, error)) - return false; - - ReloadShader(); - resetTemporalHistoryState(); - broadcastRuntimeState(); - return true; -} - -bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error) -{ - if (!mRuntimeHost->RemoveLayer(layerId, error)) - return false; - - ReloadShader(); - resetTemporalHistoryState(); - broadcastRuntimeState(); - return true; -} - -bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error) -{ - if (!mRuntimeHost->MoveLayer(layerId, direction, error)) - return false; - - ReloadShader(); - resetTemporalHistoryState(); - broadcastRuntimeState(); - return true; -} - -bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error) -{ - if (!mRuntimeHost->MoveLayerToIndex(layerId, targetIndex, error)) - return false; - - ReloadShader(); - resetTemporalHistoryState(); - broadcastRuntimeState(); - return true; -} - -bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error) -{ - if (!mRuntimeHost->SetLayerBypass(layerId, bypassed, error)) - return false; - - ReloadShader(); - resetTemporalHistoryState(); - broadcastRuntimeState(); - return true; -} - -bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error) -{ - if (!mRuntimeHost->SetLayerShader(layerId, shaderId, error)) - return false; - - ReloadShader(); - resetTemporalHistoryState(); - broadcastRuntimeState(); - return true; -} - -bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error) -{ - JsonValue parsedValue; - if (!ParseJson(valueJson, parsedValue, error)) - return false; - - if (!mRuntimeHost->UpdateLayerParameter(layerId, parameterId, parsedValue, error)) - return false; - - broadcastRuntimeState(); - return true; -} - -bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error) -{ - JsonValue parsedValue; - if (!ParseJson(valueJson, parsedValue, error)) - return false; - - if (!mRuntimeHost->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue, error)) - return false; - - broadcastRuntimeState(); - return true; -} - -bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error) -{ - if (!mRuntimeHost->ResetLayerParameters(layerId, error)) - return false; - - broadcastRuntimeState(); - return true; -} - -bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error) -{ - if (!mRuntimeHost->SaveStackPreset(presetName, error)) - return false; - - broadcastRuntimeState(); - return true; -} - -bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error) -{ - if (!mRuntimeHost->LoadStackPreset(presetName, error)) - return false; - - ReloadShader(); - resetTemporalHistoryState(); - broadcastRuntimeState(); - return true; -} - bool OpenGLComposite::CheckOpenGLExtensions() { mFastTransferExtensionAvailable = VideoFrameTransfer::checkFastMemoryTransferAvailable(); @@ -2812,412 +2045,3 @@ bool OpenGLComposite::CheckOpenGLExtensions() } //////////////////////////////////////////// -// 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) // large cache size will keep more memory pinned and may result in out of memory errors -{ -} - -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 ); - } -} - -// IUnknown methods -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; -} - -// IDeckLinkMemoryAllocator methods -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 iid, 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* /*audioPacket*/) -{ - 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 notificationEvents, IDeckLinkDisplayMode *newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags) -{ - return S_OK; -} - -//////////////////////////////////////////// -// DeckLink Playout Delegate Class -//////////////////////////////////////////// -PlayoutDelegate::PlayoutDelegate(OpenGLComposite* pOwner) : - m_pOwner(pOwner), - mRefCount(1) -{ -} - -HRESULT PlayoutDelegate::QueryInterface(REFIID iid, 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/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h similarity index 65% rename from apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h rename to apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h index 3be8815..717395c 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h @@ -52,7 +52,7 @@ #include #include "DeckLinkAPI_h.h" -#include "VideoFrameTransfer.h" +#include "GLExtensions.h" #include "RuntimeHost.h" #include @@ -237,139 +237,4 @@ private: unsigned temporalHistoryAvailableCountForLayer(const std::string& layerId) const; }; -//////////////////////////////////////////// -// PinnedMemoryAllocator -//////////////////////////////////////////// -class PinnedMemoryAllocator : public IDeckLinkVideoBufferAllocator -{ -public: - PinnedMemoryAllocator(HDC hdc, HGLRC hglrc, VideoFrameTransfer::Direction direction, unsigned cacheSize, unsigned bufferSize); - virtual ~PinnedMemoryAllocator(); - - bool transferFrame(void* address, GLuint gpuTexture); - void waitForTransferComplete(void* address); - unsigned bufferSize() { return mBufferSize; } - - // IUnknown methods - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; - - // IDeckLinkVideoBufferAllocator methods - virtual HRESULT STDMETHODCALLTYPE AllocateVideoBuffer (IDeckLinkVideoBuffer** allocatedBuffer) override; - -private: - - void unPinAddress(void* address); - -private: - HDC mHGLDC; - HGLRC mHGLRC; - std::atomic mRefCount; - VideoFrameTransfer::Direction mDirection; - std::map mFrameTransfer; - unsigned mBufferSize; - std::vector mFrameCache; - unsigned mFrameCacheSize; -}; - -//////////////////////////////////////////// -// InputAllocatorPool -//////////////////////////////////////////// - -class InputAllocatorPool : public IDeckLinkVideoBufferAllocatorProvider -{ -public: - InputAllocatorPool(HDC hdc, HGLRC hglrc); - - // IUnknown interface - ULONG STDMETHODCALLTYPE AddRef() override; - ULONG STDMETHODCALLTYPE Release() override; - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppv) override; - - // IDeckLinkVideoBufferAllocatorProvider interface - HRESULT STDMETHODCALLTYPE GetVideoBufferAllocator( - /* [in] */ unsigned int bufferSize, - /* [in] */ unsigned int width, - /* [in] */ unsigned int height, - /* [in] */ unsigned int rowBytes, - /* [in] */ BMDPixelFormat pixelFormat, - /* [out] */ IDeckLinkVideoBufferAllocator **allocator) override; - -private: - std::atomic mRefCount; - std::map > mAllocatorBySize; - HDC mHDC; - HGLRC mHGLRC; -}; - -//////////////////////////////////////////// -// DeckLinkVideoBuffer -//////////////////////////////////////////// -class DeckLinkVideoBuffer : public IDeckLinkVideoBuffer -{ -public: - explicit DeckLinkVideoBuffer(std::shared_ptr& buffer, PinnedMemoryAllocator* parent); - virtual ~DeckLinkVideoBuffer() = default; - - // IUnknown interface - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; - - // IDeckLinkVideoBuffer interface - virtual HRESULT STDMETHODCALLTYPE GetBytes(void** buffer) override; - virtual HRESULT STDMETHODCALLTYPE GetSize(uint64_t* size) override; - virtual HRESULT STDMETHODCALLTYPE StartAccess(BMDBufferAccessFlags flags) override; - virtual HRESULT STDMETHODCALLTYPE EndAccess(BMDBufferAccessFlags flags) override; - -private: - CComPtr mParentAllocator; // Dual-purpose: allocator owns mem this points to, and to access transferFrame() via a QueryInterface - std::atomic mRefCount; - std::shared_ptr mBuffer; -}; - - -//////////////////////////////////////////// -// Capture Delegate Class -//////////////////////////////////////////// - -class CaptureDelegate : public IDeckLinkInputCallback -{ - OpenGLComposite* m_pOwner; - LONG mRefCount; - -public: - CaptureDelegate (OpenGLComposite* pOwner); - - // IUnknown needs only a dummy implementation - virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID iid, LPVOID *ppv); - virtual ULONG STDMETHODCALLTYPE AddRef (); - virtual ULONG STDMETHODCALLTYPE Release (); - - virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived (IDeckLinkVideoInputFrame *videoFrame, IDeckLinkAudioInputPacket *audioPacket); - virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged (BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode *newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags); -}; - -//////////////////////////////////////////// -// Render Delegate Class -//////////////////////////////////////////// - -class PlayoutDelegate : public IDeckLinkVideoOutputCallback -{ - OpenGLComposite* m_pOwner; - LONG mRefCount; - -public: - PlayoutDelegate (OpenGLComposite* pOwner); - - // IUnknown needs only a dummy implementation - virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID iid, LPVOID *ppv); - virtual ULONG STDMETHODCALLTYPE AddRef (); - virtual ULONG STDMETHODCALLTYPE Release (); - - virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted (IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result); - virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped (); -}; - #endif // __OPENGL_COMPOSITE_H__ diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLCompositeRuntimeControls.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLCompositeRuntimeControls.cpp new file mode 100644 index 0000000..f2a3a31 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLCompositeRuntimeControls.cpp @@ -0,0 +1,127 @@ +#include "OpenGLComposite.h" + +std::string OpenGLComposite::GetRuntimeStateJson() const +{ + return mRuntimeHost ? mRuntimeHost->BuildStateJson() : "{}"; +} + +bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error) +{ + if (!mRuntimeHost->AddLayer(shaderId, error)) + return false; + + ReloadShader(); + resetTemporalHistoryState(); + broadcastRuntimeState(); + return true; +} + +bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error) +{ + if (!mRuntimeHost->RemoveLayer(layerId, error)) + return false; + + ReloadShader(); + resetTemporalHistoryState(); + broadcastRuntimeState(); + return true; +} + +bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error) +{ + if (!mRuntimeHost->MoveLayer(layerId, direction, error)) + return false; + + ReloadShader(); + resetTemporalHistoryState(); + broadcastRuntimeState(); + return true; +} + +bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error) +{ + if (!mRuntimeHost->MoveLayerToIndex(layerId, targetIndex, error)) + return false; + + ReloadShader(); + resetTemporalHistoryState(); + broadcastRuntimeState(); + return true; +} + +bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error) +{ + if (!mRuntimeHost->SetLayerBypass(layerId, bypassed, error)) + return false; + + ReloadShader(); + resetTemporalHistoryState(); + broadcastRuntimeState(); + return true; +} + +bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error) +{ + if (!mRuntimeHost->SetLayerShader(layerId, shaderId, error)) + return false; + + ReloadShader(); + resetTemporalHistoryState(); + broadcastRuntimeState(); + return true; +} + +bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error) +{ + JsonValue parsedValue; + if (!ParseJson(valueJson, parsedValue, error)) + return false; + + if (!mRuntimeHost->UpdateLayerParameter(layerId, parameterId, parsedValue, error)) + return false; + + broadcastRuntimeState(); + return true; +} + +bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error) +{ + JsonValue parsedValue; + if (!ParseJson(valueJson, parsedValue, error)) + return false; + + if (!mRuntimeHost->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue, error)) + return false; + + broadcastRuntimeState(); + return true; +} + +bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error) +{ + if (!mRuntimeHost->ResetLayerParameters(layerId, error)) + return false; + + broadcastRuntimeState(); + return true; +} + +bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error) +{ + if (!mRuntimeHost->SaveStackPreset(presetName, error)) + return false; + + broadcastRuntimeState(); + return true; +} + +bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error) +{ + if (!mRuntimeHost->LoadStackPreset(presetName, error)) + return false; + + ReloadShader(); + resetTemporalHistoryState(); + broadcastRuntimeState(); + return true; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/Std140Buffer.h b/apps/LoopThroughWithOpenGLCompositing/gl/Std140Buffer.h new file mode 100644 index 0000000..a94ac6d --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/Std140Buffer.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +inline std::size_t AlignStd140(std::size_t offset, std::size_t alignment) +{ + const std::size_t mask = alignment - 1; + return (offset + mask) & ~mask; +} + +template +inline void AppendStd140Value(std::vector& buffer, std::size_t alignment, const TValue& value) +{ + const std::size_t offset = AlignStd140(buffer.size(), alignment); + if (buffer.size() < offset + sizeof(TValue)) + buffer.resize(offset + sizeof(TValue), 0); + std::memcpy(buffer.data() + offset, &value, sizeof(TValue)); +} + +inline void AppendStd140Float(std::vector& buffer, float value) +{ + AppendStd140Value(buffer, 4, value); +} + +inline void AppendStd140Int(std::vector& buffer, int value) +{ + AppendStd140Value(buffer, 4, value); +} + +inline void AppendStd140Vec2(std::vector& buffer, float x, float y) +{ + const std::size_t offset = AlignStd140(buffer.size(), 8); + if (buffer.size() < offset + sizeof(float) * 2) + buffer.resize(offset + sizeof(float) * 2, 0); + float values[2] = { x, y }; + std::memcpy(buffer.data() + offset, values, sizeof(values)); +} + +inline void AppendStd140Vec4(std::vector& buffer, float x, float y, float z, float w) +{ + const std::size_t offset = AlignStd140(buffer.size(), 16); + if (buffer.size() < offset + sizeof(float) * 4) + buffer.resize(offset + sizeof(float) * 4, 0); + float values[4] = { x, y, z, w }; + std::memcpy(buffer.data() + offset, values, sizeof(values)); +} diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/TextRasterizer.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/TextRasterizer.cpp new file mode 100644 index 0000000..f633885 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/TextRasterizer.cpp @@ -0,0 +1,316 @@ +#include "TextRasterizer.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +constexpr int kTextSdfSpread = 20; +constexpr unsigned kTextSdfBlurPasses = 1; +constexpr float kTextFontPixelSize = 144.0f; +constexpr float kTextLayoutPadding = 48.0f; + +class GdiplusSession +{ +public: + GdiplusSession() + { + Gdiplus::GdiplusStartupInput startupInput; + mStarted = Gdiplus::GdiplusStartup(&mToken, &startupInput, NULL) == Gdiplus::Ok; + } + + ~GdiplusSession() + { + if (mStarted) + Gdiplus::GdiplusShutdown(mToken); + } + + GdiplusSession(const GdiplusSession&) = delete; + GdiplusSession& operator=(const GdiplusSession&) = delete; + + bool started() const { return mStarted; } + +private: + ULONG_PTR mToken = 0; + bool mStarted = false; +}; + +std::wstring Utf8ToWide(const std::string& text) +{ + if (text.empty()) + return std::wstring(); + const int required = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, NULL, 0); + if (required <= 1) + return std::wstring(); + std::wstring wide(static_cast(required - 1), L'\0'); + MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, wide.data(), required); + return wide; +} + +std::vector BuildLocalSdf(const std::vector& alpha, unsigned width, unsigned height) +{ + std::vector sdf(static_cast(width) * height * 4, 0); + for (unsigned y = 0; y < height; ++y) + { + for (unsigned x = 0; x < width; ++x) + { + const bool inside = alpha[static_cast(y) * width + x] > 127; + int bestDistanceSq = kTextSdfSpread * kTextSdfSpread; + for (int oy = -kTextSdfSpread; oy <= kTextSdfSpread; ++oy) + { + const int sy = static_cast(y) + oy; + if (sy < 0 || sy >= static_cast(height)) + continue; + for (int ox = -kTextSdfSpread; ox <= kTextSdfSpread; ++ox) + { + const int sx = static_cast(x) + ox; + if (sx < 0 || sx >= static_cast(width)) + continue; + const bool sampleInside = alpha[static_cast(sy) * width + sx] > 127; + if (sampleInside == inside) + continue; + const int distanceSq = ox * ox + oy * oy; + if (distanceSq < bestDistanceSq) + bestDistanceSq = distanceSq; + } + } + + const float distance = std::sqrt(static_cast(bestDistanceSq)); + const float signedDistance = (inside ? 1.0f : -1.0f) * distance; + float normalized = 0.5f + signedDistance / static_cast(kTextSdfSpread * 2); + const unsigned char sourceAlpha = alpha[static_cast(y) * width + x]; + if (sourceAlpha > 0 && sourceAlpha < 255) + normalized = static_cast(sourceAlpha) / 255.0f; + if (normalized < 0.0f) + normalized = 0.0f; + if (normalized > 1.0f) + normalized = 1.0f; + const unsigned char value = static_cast(normalized * 255.0f + 0.5f); + const std::size_t out = (static_cast(y) * width + x) * 4; + sdf[out + 0] = value; + sdf[out + 1] = value; + sdf[out + 2] = value; + sdf[out + 3] = value; + } + } + return sdf; +} + +std::vector BuildTextCoverageTexture(const std::vector& alpha, unsigned width, unsigned height) +{ + std::vector coverage(static_cast(width) * height * 4, 0); + for (unsigned y = 0; y < height; ++y) + { + for (unsigned x = 0; x < width; ++x) + { + const unsigned char value = alpha[static_cast(y) * width + x]; + const std::size_t out = (static_cast(y) * width + x) * 4; + coverage[out + 0] = value; + coverage[out + 1] = value; + coverage[out + 2] = value; + coverage[out + 3] = value; + } + } + return coverage; +} + +std::vector FlipTextTextureForShaderUv(const std::vector& pixels, unsigned width, unsigned height) +{ + std::vector flipped(pixels.size(), 0); + const std::size_t stride = static_cast(width) * 4; + for (unsigned y = 0; y < height; ++y) + { + const std::size_t srcOffset = static_cast(y) * stride; + const std::size_t dstOffset = static_cast(height - 1 - y) * stride; + std::memcpy(flipped.data() + dstOffset, pixels.data() + srcOffset, stride); + } + return flipped; +} + +std::vector BlurTextSdf(const std::vector& pixels, unsigned width, unsigned height, unsigned passes) +{ + std::vector current = pixels; + std::vector next(pixels.size(), 0); + for (unsigned pass = 0; pass < passes; ++pass) + { + for (unsigned y = 0; y < height; ++y) + { + for (unsigned x = 0; x < width; ++x) + { + unsigned weightedTotal = 0; + unsigned weightSum = 0; + for (int oy = -1; oy <= 1; ++oy) + { + const int sy = static_cast(y) + oy; + if (sy < 0 || sy >= static_cast(height)) + continue; + for (int ox = -1; ox <= 1; ++ox) + { + const int sx = static_cast(x) + ox; + if (sx < 0 || sx >= static_cast(width)) + continue; + const unsigned weight = (ox == 0 && oy == 0) ? 4u : ((ox == 0 || oy == 0) ? 2u : 1u); + const std::size_t sample = (static_cast(sy) * width + sx) * 4; + weightedTotal += static_cast(current[sample]) * weight; + weightSum += weight; + } + } + + const unsigned char value = static_cast((weightedTotal + weightSum / 2) / weightSum); + const std::size_t out = (static_cast(y) * width + x) * 4; + next[out + 0] = value; + next[out + 1] = value; + next[out + 2] = value; + next[out + 3] = value; + } + } + current.swap(next); + } + return current; +} + +void WriteTextMaskDebugDump(const std::string& text, const std::vector& alpha, const std::vector& sdf, unsigned width, unsigned height) +{ + try + { + std::filesystem::path debugDir = std::filesystem::current_path() / "runtime"; + std::filesystem::create_directories(debugDir); + + auto writePgm = [width, height](const std::filesystem::path& path, const std::vector& gray, std::size_t stride) + { + std::ofstream out(path, std::ios::binary); + if (!out) + return; + out << "P5\n" << width << " " << height << "\n255\n"; + for (unsigned y = 0; y < height; ++y) + { + for (unsigned x = 0; x < width; ++x) + out.put(static_cast(gray[(static_cast(y) * width + x) * stride])); + } + }; + + writePgm(debugDir / "text-mask-alpha-debug.pgm", alpha, 1); + writePgm(debugDir / "text-mask-sdf-debug.pgm", sdf, 4); + + unsigned alphaMin = 255; + unsigned alphaMax = 0; + unsigned sdfMin = 255; + unsigned sdfMax = 0; + std::size_t alphaLit = 0; + std::size_t sdfLit = 0; + for (unsigned char value : alpha) + { + alphaMin = std::min(alphaMin, value); + alphaMax = std::max(alphaMax, value); + if (value > 0) + ++alphaLit; + } + for (std::size_t index = 0; index < sdf.size(); index += 4) + { + const unsigned char value = sdf[index]; + sdfMin = std::min(sdfMin, value); + sdfMax = std::max(sdfMax, value); + if (value > 127) + ++sdfLit; + } + + std::ostringstream message; + message << "Text mask debug for '" << text << "': alpha min/max/lit=" << alphaMin << "/" << alphaMax << "/" << alphaLit + << ", sdf min/max/gt127=" << sdfMin << "/" << sdfMax << "/" << sdfLit << "\n"; + OutputDebugStringA(message.str().c_str()); + } + catch (...) + { + OutputDebugStringA("Failed to write text mask debug dump.\n"); + } +} +} + +bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector& sdf, std::string& error) +{ + GdiplusSession gdiplus; + if (!gdiplus.started()) + { + error = "Could not start GDI+ for text rendering."; + return false; + } + + Gdiplus::PrivateFontCollection fontCollection; + Gdiplus::FontFamily fallbackFamily(L"Arial"); + Gdiplus::FontFamily* fontFamily = &fallbackFamily; + std::unique_ptr families; + const std::wstring wideFontPath = fontPath.empty() ? std::wstring() : fontPath.wstring(); + if (!wideFontPath.empty()) + { + if (fontCollection.AddFontFile(wideFontPath.c_str()) != Gdiplus::Ok) + { + error = "Could not load packaged font file for text rendering: " + fontPath.string(); + return false; + } + + const INT familyCount = fontCollection.GetFamilyCount(); + if (familyCount <= 0) + { + error = "Packaged font did not contain a usable font family: " + fontPath.string(); + return false; + } + + families.reset(new Gdiplus::FontFamily[familyCount]); + INT found = 0; + if (fontCollection.GetFamilies(familyCount, families.get(), &found) != Gdiplus::Ok || found <= 0) + { + error = "Could not read the packaged font family: " + fontPath.string(); + return false; + } + fontFamily = &families[0]; + } + + Gdiplus::Bitmap bitmap(kTextTextureWidth, kTextTextureHeight, PixelFormat32bppARGB); + Gdiplus::Graphics graphics(&bitmap); + graphics.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); + graphics.Clear(Gdiplus::Color(255, 0, 0, 0)); + graphics.SetCompositingMode(Gdiplus::CompositingModeSourceOver); + graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias); + graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); + Gdiplus::Font font(fontFamily, kTextFontPixelSize, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel); + Gdiplus::SolidBrush brush(Gdiplus::Color(255, 255, 255, 255)); + Gdiplus::StringFormat format; + format.SetAlignment(Gdiplus::StringAlignmentNear); + format.SetLineAlignment(Gdiplus::StringAlignmentCenter); + format.SetFormatFlags(Gdiplus::StringFormatFlagsNoWrap | Gdiplus::StringFormatFlagsMeasureTrailingSpaces); + const Gdiplus::RectF layout( + kTextLayoutPadding, + 0.0f, + static_cast(kTextTextureWidth) - (kTextLayoutPadding * 2.0f), + static_cast(kTextTextureHeight)); + const std::wstring wideText = Utf8ToWide(text); + graphics.DrawString(wideText.c_str(), -1, &font, layout, &format, &brush); + + std::vector alpha(static_cast(kTextTextureWidth) * kTextTextureHeight, 0); + for (unsigned y = 0; y < kTextTextureHeight; ++y) + { + for (unsigned x = 0; x < kTextTextureWidth; ++x) + { + Gdiplus::Color pixel; + bitmap.GetPixel(x, y, &pixel); + BYTE luminance = pixel.GetRed(); + if (pixel.GetGreen() > luminance) + luminance = pixel.GetGreen(); + if (pixel.GetBlue() > luminance) + luminance = pixel.GetBlue(); + alpha[static_cast(y) * kTextTextureWidth + x] = static_cast(luminance); + } + } + sdf = BuildTextCoverageTexture(alpha, kTextTextureWidth, kTextTextureHeight); + sdf = BlurTextSdf(sdf, kTextTextureWidth, kTextTextureHeight, kTextSdfBlurPasses); + sdf = FlipTextTextureForShaderUv(sdf, kTextTextureWidth, kTextTextureHeight); + WriteTextMaskDebugDump(text, alpha, sdf, kTextTextureWidth, kTextTextureHeight); + return true; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/TextRasterizer.h b/apps/LoopThroughWithOpenGLCompositing/gl/TextRasterizer.h new file mode 100644 index 0000000..284249c --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/TextRasterizer.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +#include + +constexpr unsigned kTextTextureWidth = 2048; +constexpr unsigned kTextTextureHeight = 256; + +bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector& sdf, std::string& error); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/TextureAssetLoader.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/TextureAssetLoader.cpp new file mode 100644 index 0000000..dc411b2 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/TextureAssetLoader.cpp @@ -0,0 +1,114 @@ +#include "TextureAssetLoader.h" + +#include +#include + +#include +#include +#include + +bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error) +{ + textureId = 0; + + HRESULT comInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED); + const bool shouldUninitializeCom = (comInitResult == S_OK || comInitResult == S_FALSE); + if (FAILED(comInitResult) && comInitResult != RPC_E_CHANGED_MODE) + { + error = "Could not initialize COM to load shader texture assets."; + return false; + } + + CComPtr imagingFactory; + HRESULT result = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&imagingFactory)); + if (FAILED(result) || !imagingFactory) + { + if (shouldUninitializeCom) + CoUninitialize(); + error = "Could not create a WIC imaging factory to load shader texture assets."; + return false; + } + + CComPtr bitmapDecoder; + result = imagingFactory->CreateDecoderFromFilename(textureAsset.path.wstring().c_str(), NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &bitmapDecoder); + if (FAILED(result) || !bitmapDecoder) + { + if (shouldUninitializeCom) + CoUninitialize(); + error = "Could not open shader texture asset: " + textureAsset.path.string(); + return false; + } + + CComPtr bitmapFrame; + result = bitmapDecoder->GetFrame(0, &bitmapFrame); + if (FAILED(result) || !bitmapFrame) + { + if (shouldUninitializeCom) + CoUninitialize(); + error = "Could not decode the first frame of shader texture asset: " + textureAsset.path.string(); + return false; + } + + CComPtr formatConverter; + result = imagingFactory->CreateFormatConverter(&formatConverter); + if (FAILED(result) || !formatConverter) + { + if (shouldUninitializeCom) + CoUninitialize(); + error = "Could not create a WIC format converter for shader texture asset: " + textureAsset.path.string(); + return false; + } + + result = formatConverter->Initialize(bitmapFrame, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, NULL, 0.0, WICBitmapPaletteTypeCustom); + if (FAILED(result)) + { + if (shouldUninitializeCom) + CoUninitialize(); + error = "Could not convert shader texture asset to BGRA: " + textureAsset.path.string(); + return false; + } + + UINT width = 0; + UINT height = 0; + result = formatConverter->GetSize(&width, &height); + if (FAILED(result) || width == 0 || height == 0) + { + if (shouldUninitializeCom) + CoUninitialize(); + error = "Shader texture asset has an invalid size: " + textureAsset.path.string(); + return false; + } + + const UINT stride = width * 4; + std::vector pixels(static_cast(stride) * static_cast(height)); + result = formatConverter->CopyPixels(NULL, stride, static_cast(pixels.size()), pixels.data()); + if (FAILED(result)) + { + if (shouldUninitializeCom) + CoUninitialize(); + error = "Could not read shader texture pixels: " + textureAsset.path.string(); + return false; + } + + std::vector flippedPixels(pixels.size()); + for (UINT row = 0; row < height; ++row) + { + const std::size_t srcOffset = static_cast(row) * stride; + const std::size_t dstOffset = static_cast(height - 1 - row) * stride; + std::memcpy(flippedPixels.data() + dstOffset, pixels.data() + srcOffset, stride); + } + + glGenTextures(1, &textureId); + glBindTexture(GL_TEXTURE_2D, textureId); + 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_RGBA8, static_cast(width), static_cast(height), 0, GL_BGRA, GL_UNSIGNED_BYTE, flippedPixels.data()); + glBindTexture(GL_TEXTURE_2D, 0); + + if (shouldUninitializeCom) + CoUninitialize(); + + return true; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/TextureAssetLoader.h b/apps/LoopThroughWithOpenGLCompositing/gl/TextureAssetLoader.h new file mode 100644 index 0000000..1334b27 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/TextureAssetLoader.h @@ -0,0 +1,10 @@ +#pragma once + +#include "ShaderTypes.h" + +#include +#include + +#include + +bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error); diff --git a/apps/LoopThroughWithOpenGLCompositing/VideoFrameTransfer.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/VideoFrameTransfer.cpp similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/VideoFrameTransfer.cpp rename to apps/LoopThroughWithOpenGLCompositing/gl/VideoFrameTransfer.cpp diff --git a/apps/LoopThroughWithOpenGLCompositing/VideoFrameTransfer.h b/apps/LoopThroughWithOpenGLCompositing/gl/VideoFrameTransfer.h similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/VideoFrameTransfer.h rename to apps/LoopThroughWithOpenGLCompositing/gl/VideoFrameTransfer.h diff --git a/apps/LoopThroughWithOpenGLCompositing/NativeHandles.h b/apps/LoopThroughWithOpenGLCompositing/platform/NativeHandles.h similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/NativeHandles.h rename to apps/LoopThroughWithOpenGLCompositing/platform/NativeHandles.h diff --git a/apps/LoopThroughWithOpenGLCompositing/NativeSockets.h b/apps/LoopThroughWithOpenGLCompositing/platform/NativeSockets.h similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/NativeSockets.h rename to apps/LoopThroughWithOpenGLCompositing/platform/NativeSockets.h diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp rename to apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h rename to apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeJson.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeJson.cpp similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/RuntimeJson.cpp rename to apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeJson.cpp diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeJson.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeJson.h similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/RuntimeJson.h rename to apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeJson.h diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeParameterUtils.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeParameterUtils.cpp similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/RuntimeParameterUtils.cpp rename to apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeParameterUtils.cpp diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeParameterUtils.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeParameterUtils.h similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/RuntimeParameterUtils.h rename to apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeParameterUtils.h diff --git a/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp b/apps/LoopThroughWithOpenGLCompositing/shader/ShaderCompiler.cpp similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp rename to apps/LoopThroughWithOpenGLCompositing/shader/ShaderCompiler.cpp diff --git a/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.h b/apps/LoopThroughWithOpenGLCompositing/shader/ShaderCompiler.h similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.h rename to apps/LoopThroughWithOpenGLCompositing/shader/ShaderCompiler.h diff --git a/apps/LoopThroughWithOpenGLCompositing/ShaderPackageRegistry.cpp b/apps/LoopThroughWithOpenGLCompositing/shader/ShaderPackageRegistry.cpp similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/ShaderPackageRegistry.cpp rename to apps/LoopThroughWithOpenGLCompositing/shader/ShaderPackageRegistry.cpp diff --git a/apps/LoopThroughWithOpenGLCompositing/ShaderPackageRegistry.h b/apps/LoopThroughWithOpenGLCompositing/shader/ShaderPackageRegistry.h similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/ShaderPackageRegistry.h rename to apps/LoopThroughWithOpenGLCompositing/shader/ShaderPackageRegistry.h diff --git a/apps/LoopThroughWithOpenGLCompositing/ShaderTypes.h b/apps/LoopThroughWithOpenGLCompositing/shader/ShaderTypes.h similarity index 100% rename from apps/LoopThroughWithOpenGLCompositing/ShaderTypes.h rename to apps/LoopThroughWithOpenGLCompositing/shader/ShaderTypes.h diff --git a/tests/Std140BufferTests.cpp b/tests/Std140BufferTests.cpp new file mode 100644 index 0000000..755fe59 --- /dev/null +++ b/tests/Std140BufferTests.cpp @@ -0,0 +1,73 @@ +#include "Std140Buffer.h" + +#include +#include +#include + +namespace +{ +int gFailures = 0; + +void Expect(bool condition, const char* message) +{ + if (condition) + return; + + std::cerr << "FAIL: " << message << "\n"; + ++gFailures; +} + +float ReadFloat(const std::vector& buffer, std::size_t offset) +{ + float value = 0.0f; + std::memcpy(&value, buffer.data() + offset, sizeof(float)); + return value; +} + +int ReadInt(const std::vector& buffer, std::size_t offset) +{ + int value = 0; + std::memcpy(&value, buffer.data() + offset, sizeof(int)); + return value; +} + +void TestScalarPacking() +{ + std::vector buffer; + AppendStd140Float(buffer, 1.25f); + AppendStd140Int(buffer, 7); + + Expect(buffer.size() == 8, "scalar values pack tightly on 4-byte alignment"); + Expect(ReadFloat(buffer, 0) == 1.25f, "float value is written at offset 0"); + Expect(ReadInt(buffer, 4) == 7, "int value is written at offset 4"); +} + +void TestVectorAlignment() +{ + std::vector buffer; + AppendStd140Float(buffer, 1.0f); + AppendStd140Vec2(buffer, 2.0f, 3.0f); + AppendStd140Vec4(buffer, 4.0f, 5.0f, 6.0f, 7.0f); + + Expect(buffer.size() == 32, "vec2 aligns to 8 bytes and vec4 aligns to 16 bytes"); + Expect(ReadFloat(buffer, 8) == 2.0f, "vec2 x value is aligned"); + Expect(ReadFloat(buffer, 12) == 3.0f, "vec2 y value follows x"); + Expect(ReadFloat(buffer, 16) == 4.0f, "vec4 x value is aligned"); + Expect(ReadFloat(buffer, 28) == 7.0f, "vec4 w value is written"); +} +} + +int main() +{ + TestScalarPacking(); + TestVectorAlignment(); + + if (gFailures != 0) + { + std::cerr << gFailures << " Std140Buffer test failure(s).\n"; + return 1; + } + + std::cout << "Std140Buffer tests passed.\n"; + return 0; +}