Refactor
Some checks failed
CI / Native Windows Build And Tests (push) Has been cancelled
CI / React UI Build (push) Has been cancelled
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
2026-05-06 09:28:46 +10:00
parent 437199f3f0
commit 0bfffa6552
41 changed files with 1592 additions and 1368 deletions

View File

@@ -36,43 +36,62 @@ if(NOT EXISTS "${SLANG_LICENSE_FILE}")
endif() endif()
set(APP_SOURCES set(APP_SOURCES
"${APP_DIR}/ControlServer.cpp"
"${APP_DIR}/ControlServer.h"
"${APP_DIR}/DeckLinkAPI_i.c" "${APP_DIR}/DeckLinkAPI_i.c"
"${APP_DIR}/GLExtensions.cpp" "${APP_DIR}/control/ControlServer.cpp"
"${APP_DIR}/GLExtensions.h" "${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.cpp"
"${APP_DIR}/LoopThroughWithOpenGLCompositing.h" "${APP_DIR}/LoopThroughWithOpenGLCompositing.h"
"${APP_DIR}/LoopThroughWithOpenGLCompositing.rc" "${APP_DIR}/LoopThroughWithOpenGLCompositing.rc"
"${APP_DIR}/NativeHandles.h" "${APP_DIR}/platform/NativeHandles.h"
"${APP_DIR}/NativeSockets.h" "${APP_DIR}/platform/NativeSockets.h"
"${APP_DIR}/OpenGLComposite.cpp"
"${APP_DIR}/OpenGLComposite.h"
"${APP_DIR}/OscServer.cpp"
"${APP_DIR}/OscServer.h"
"${APP_DIR}/resource.h" "${APP_DIR}/resource.h"
"${APP_DIR}/RuntimeHost.cpp" "${APP_DIR}/runtime/RuntimeHost.cpp"
"${APP_DIR}/RuntimeHost.h" "${APP_DIR}/runtime/RuntimeHost.h"
"${APP_DIR}/RuntimeJson.cpp" "${APP_DIR}/runtime/RuntimeJson.cpp"
"${APP_DIR}/RuntimeJson.h" "${APP_DIR}/runtime/RuntimeJson.h"
"${APP_DIR}/RuntimeParameterUtils.cpp" "${APP_DIR}/runtime/RuntimeParameterUtils.cpp"
"${APP_DIR}/RuntimeParameterUtils.h" "${APP_DIR}/runtime/RuntimeParameterUtils.h"
"${APP_DIR}/ShaderCompiler.cpp" "${APP_DIR}/shader/ShaderCompiler.cpp"
"${APP_DIR}/ShaderCompiler.h" "${APP_DIR}/shader/ShaderCompiler.h"
"${APP_DIR}/ShaderPackageRegistry.cpp" "${APP_DIR}/shader/ShaderPackageRegistry.cpp"
"${APP_DIR}/ShaderPackageRegistry.h" "${APP_DIR}/shader/ShaderPackageRegistry.h"
"${APP_DIR}/ShaderTypes.h" "${APP_DIR}/shader/ShaderTypes.h"
"${APP_DIR}/stdafx.cpp" "${APP_DIR}/stdafx.cpp"
"${APP_DIR}/stdafx.h" "${APP_DIR}/stdafx.h"
"${APP_DIR}/targetver.h" "${APP_DIR}/targetver.h"
"${APP_DIR}/VideoFrameTransfer.cpp"
"${APP_DIR}/VideoFrameTransfer.h"
) )
add_executable(LoopThroughWithOpenGLCompositing WIN32 ${APP_SOURCES}) add_executable(LoopThroughWithOpenGLCompositing WIN32 ${APP_SOURCES})
target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/control"
"${APP_DIR}/decklink"
"${APP_DIR}/gl"
"${APP_DIR}/platform"
"${APP_DIR}/runtime"
"${APP_DIR}/shader"
"${GPUDIRECT_DIR}/include" "${GPUDIRECT_DIR}/include"
) )
@@ -100,12 +119,13 @@ if(MSVC)
endif() endif()
add_executable(RuntimeJsonTests add_executable(RuntimeJsonTests
"${APP_DIR}/RuntimeJson.cpp" "${APP_DIR}/runtime/RuntimeJson.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeJsonTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeJsonTests.cpp"
) )
target_include_directories(RuntimeJsonTests PRIVATE target_include_directories(RuntimeJsonTests PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/runtime"
) )
if(MSVC) if(MSVC)
@@ -116,13 +136,15 @@ enable_testing()
add_test(NAME RuntimeJsonTests COMMAND RuntimeJsonTests) add_test(NAME RuntimeJsonTests COMMAND RuntimeJsonTests)
add_executable(RuntimeParameterUtilsTests add_executable(RuntimeParameterUtilsTests
"${APP_DIR}/RuntimeJson.cpp" "${APP_DIR}/runtime/RuntimeJson.cpp"
"${APP_DIR}/RuntimeParameterUtils.cpp" "${APP_DIR}/runtime/RuntimeParameterUtils.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeParameterUtilsTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeParameterUtilsTests.cpp"
) )
target_include_directories(RuntimeParameterUtilsTests PRIVATE target_include_directories(RuntimeParameterUtilsTests PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/runtime"
"${APP_DIR}/shader"
) )
if(MSVC) if(MSVC)
@@ -131,14 +153,31 @@ endif()
add_test(NAME RuntimeParameterUtilsTests COMMAND RuntimeParameterUtilsTests) 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 add_executable(ShaderPackageRegistryTests
"${APP_DIR}/RuntimeJson.cpp" "${APP_DIR}/runtime/RuntimeJson.cpp"
"${APP_DIR}/ShaderPackageRegistry.cpp" "${APP_DIR}/shader/ShaderPackageRegistry.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderPackageRegistryTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderPackageRegistryTests.cpp"
) )
target_include_directories(ShaderPackageRegistryTests PRIVATE target_include_directories(ShaderPackageRegistryTests PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/runtime"
"${APP_DIR}/shader"
) )
if(MSVC) if(MSVC)
@@ -148,12 +187,14 @@ endif()
add_test(NAME ShaderPackageRegistryTests COMMAND ShaderPackageRegistryTests) add_test(NAME ShaderPackageRegistryTests COMMAND ShaderPackageRegistryTests)
add_executable(OscServerTests add_executable(OscServerTests
"${APP_DIR}/OscServer.cpp" "${APP_DIR}/control/OscServer.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/OscServerTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/OscServerTests.cpp"
) )
target_include_directories(OscServerTests PRIVATE target_include_directories(OscServerTests PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/control"
"${APP_DIR}/platform"
) )
target_link_libraries(OscServerTests PRIVATE target_link_libraries(OscServerTests PRIVATE

View File

@@ -236,7 +236,6 @@ improve text rendering
genlock genlock
find a better UI libary find a better UI libary
Logs Logs
anamorphic desqueeze
refactor, cleanup of source files refactor, cleanup of source files
display URL (Maybe clicakable) for control in the windows app (Not on the output) display URL (Maybe clicakable) for control in the windows app (Not on the output)
Sound shader as seperate .slang in shader package? Sound shader as seperate .slang in shader package?

View File

@@ -89,7 +89,7 @@
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile> <ClCompile>
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>.;control;decklink;gl;..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -115,7 +115,7 @@
</Midl> </Midl>
<ClCompile> <ClCompile>
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>.;control;decklink;gl;..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -139,7 +139,7 @@
<ClCompile> <ClCompile>
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>.;control;decklink;gl;..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
@@ -169,7 +169,7 @@
<ClCompile> <ClCompile>
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>.;control;decklink;gl;..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
@@ -192,26 +192,26 @@
</PostBuildEvent> </PostBuildEvent>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="GLExtensions.cpp" /> <ClCompile Include="gl\GLExtensions.cpp" />
<ClCompile Include="LoopThroughWithOpenGLCompositing.cpp" /> <ClCompile Include="LoopThroughWithOpenGLCompositing.cpp" />
<ClCompile Include="OpenGLComposite.cpp" /> <ClCompile Include="gl\OpenGLComposite.cpp" />
<ClCompile Include="stdafx.cpp"> <ClCompile Include="stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile> </ClCompile>
<ClCompile Include="VideoFrameTransfer.cpp" /> <ClCompile Include="gl\VideoFrameTransfer.cpp" />
<ClCompile Include="DeckLinkAPI_i.c" /> <ClCompile Include="DeckLinkAPI_i.c" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="GLExtensions.h" /> <ClInclude Include="gl\GLExtensions.h" />
<ClInclude Include="LoopThroughWithOpenGLCompositing.h" /> <ClInclude Include="LoopThroughWithOpenGLCompositing.h" />
<ClInclude Include="OpenGLComposite.h" /> <ClInclude Include="gl\OpenGLComposite.h" />
<ClInclude Include="resource.h" /> <ClInclude Include="resource.h" />
<ClInclude Include="stdafx.h" /> <ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" /> <ClInclude Include="targetver.h" />
<ClInclude Include="VideoFrameTransfer.h" /> <ClInclude Include="gl\VideoFrameTransfer.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Image Include="LoopThroughWithOpenGLCompositing.ico" /> <Image Include="LoopThroughWithOpenGLCompositing.ico" />

View File

@@ -18,19 +18,19 @@
</Filter> </Filter>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="GLExtensions.cpp"> <ClCompile Include="gl\GLExtensions.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="LoopThroughWithOpenGLCompositing.cpp"> <ClCompile Include="LoopThroughWithOpenGLCompositing.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="OpenGLComposite.cpp"> <ClCompile Include="gl\OpenGLComposite.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="stdafx.cpp"> <ClCompile Include="stdafx.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="VideoFrameTransfer.cpp"> <ClCompile Include="gl\VideoFrameTransfer.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="DeckLinkAPI_i.c"> <ClCompile Include="DeckLinkAPI_i.c">
@@ -38,13 +38,13 @@
</ClCompile> </ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="GLExtensions.h"> <ClInclude Include="gl\GLExtensions.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="LoopThroughWithOpenGLCompositing.h"> <ClInclude Include="LoopThroughWithOpenGLCompositing.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="OpenGLComposite.h"> <ClInclude Include="gl\OpenGLComposite.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="resource.h"> <ClInclude Include="resource.h">
@@ -56,7 +56,7 @@
<ClInclude Include="targetver.h"> <ClInclude Include="targetver.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="VideoFrameTransfer.h"> <ClInclude Include="gl\VideoFrameTransfer.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
</ItemGroup> </ItemGroup>

View File

@@ -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;
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include <string>
class ControlServer;
class OpenGLComposite;
class OscServer;
class RuntimeHost;
bool StartRuntimeControlServices(
OpenGLComposite& composite,
RuntimeHost& runtimeHost,
ControlServer& controlServer,
OscServer& oscServer,
std::string& error);

View File

@@ -0,0 +1,110 @@
#include "DeckLinkDisplayMode.h"
#include <cctype>
std::string NormalizeModeToken(const std::string& value)
{
std::string normalized;
for (unsigned char ch : value)
{
if (std::isalnum(ch))
normalized.push_back(static_cast<char>(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;
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include "DeckLinkAPI_h.h"
#include <string>
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);

View File

@@ -0,0 +1,409 @@
#include "DeckLinkFrameTransfer.h"
#include "OpenGLComposite.h"
#include <initguid.h>
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<IDeckLinkVideoBufferAllocator*>(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<void> 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<void>(memBuffer, deleter);
}
else
{
// Re-use most recently released address
sharedMemBuffer = std::shared_ptr<void>(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<IDeckLinkVideoBufferAllocatorProvider*>(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<PinnedMemoryAllocator> 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<void>& 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<IDeckLinkVideoBuffer*>(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;
}

View File

@@ -0,0 +1,147 @@
#pragma once
#include <windows.h>
#include <gl/gl.h>
#include <atlbase.h>
#include <atomic>
#include <map>
#include <memory>
#include <vector>
#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<ULONG> mRefCount;
VideoFrameTransfer::Direction mDirection;
std::map<void*, VideoFrameTransfer*> mFrameTransfer;
unsigned mBufferSize;
std::vector<void*> 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<ULONG> mRefCount;
std::map<unsigned int, CComPtr<PinnedMemoryAllocator> > mAllocatorBySize;
HDC mHDC;
HGLRC mHGLRC;
};
////////////////////////////////////////////
// DeckLinkVideoBuffer
////////////////////////////////////////////
class DeckLinkVideoBuffer : public IDeckLinkVideoBuffer
{
public:
explicit DeckLinkVideoBuffer(std::shared_ptr<void>& 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<PinnedMemoryAllocator> mParentAllocator;
std::atomic<ULONG> mRefCount;
std::shared_ptr<void> 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();
};

View File

@@ -0,0 +1,57 @@
#pragma once
#include <gl/gl.h>
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;
};

View File

@@ -52,7 +52,7 @@
#include <comutil.h> #include <comutil.h>
#include "DeckLinkAPI_h.h" #include "DeckLinkAPI_h.h"
#include "VideoFrameTransfer.h" #include "GLExtensions.h"
#include "RuntimeHost.h" #include "RuntimeHost.h"
#include <atomic> #include <atomic>
@@ -237,139 +237,4 @@ private:
unsigned temporalHistoryAvailableCountForLayer(const std::string& layerId) const; 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<ULONG> mRefCount;
VideoFrameTransfer::Direction mDirection;
std::map<void*, VideoFrameTransfer*> mFrameTransfer;
unsigned mBufferSize;
std::vector<void*> 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<ULONG> mRefCount;
std::map<unsigned int, CComPtr<PinnedMemoryAllocator> > mAllocatorBySize;
HDC mHDC;
HGLRC mHGLRC;
};
////////////////////////////////////////////
// DeckLinkVideoBuffer
////////////////////////////////////////////
class DeckLinkVideoBuffer : public IDeckLinkVideoBuffer
{
public:
explicit DeckLinkVideoBuffer(std::shared_ptr<void>& 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<PinnedMemoryAllocator> mParentAllocator; // Dual-purpose: allocator owns mem this points to, and to access transferFrame() via a QueryInterface
std::atomic<ULONG> mRefCount;
std::shared_ptr<void> 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__ #endif // __OPENGL_COMPOSITE_H__

View File

@@ -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;
}

View File

@@ -0,0 +1,48 @@
#pragma once
#include <cstddef>
#include <cstring>
#include <vector>
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 <typename TValue>
inline void AppendStd140Value(std::vector<unsigned char>& 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<unsigned char>& buffer, float value)
{
AppendStd140Value(buffer, 4, value);
}
inline void AppendStd140Int(std::vector<unsigned char>& buffer, int value)
{
AppendStd140Value(buffer, 4, value);
}
inline void AppendStd140Vec2(std::vector<unsigned char>& 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<unsigned char>& 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));
}

View File

@@ -0,0 +1,316 @@
#include "TextRasterizer.h"
#include <windows.h>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <fstream>
#include <gdiplus.h>
#include <memory>
#include <sstream>
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<std::size_t>(required - 1), L'\0');
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, wide.data(), required);
return wide;
}
std::vector<unsigned char> BuildLocalSdf(const std::vector<unsigned char>& alpha, unsigned width, unsigned height)
{
std::vector<unsigned char> sdf(static_cast<std::size_t>(width) * height * 4, 0);
for (unsigned y = 0; y < height; ++y)
{
for (unsigned x = 0; x < width; ++x)
{
const bool inside = alpha[static_cast<std::size_t>(y) * width + x] > 127;
int bestDistanceSq = kTextSdfSpread * kTextSdfSpread;
for (int oy = -kTextSdfSpread; oy <= kTextSdfSpread; ++oy)
{
const int sy = static_cast<int>(y) + oy;
if (sy < 0 || sy >= static_cast<int>(height))
continue;
for (int ox = -kTextSdfSpread; ox <= kTextSdfSpread; ++ox)
{
const int sx = static_cast<int>(x) + ox;
if (sx < 0 || sx >= static_cast<int>(width))
continue;
const bool sampleInside = alpha[static_cast<std::size_t>(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<float>(bestDistanceSq));
const float signedDistance = (inside ? 1.0f : -1.0f) * distance;
float normalized = 0.5f + signedDistance / static_cast<float>(kTextSdfSpread * 2);
const unsigned char sourceAlpha = alpha[static_cast<std::size_t>(y) * width + x];
if (sourceAlpha > 0 && sourceAlpha < 255)
normalized = static_cast<float>(sourceAlpha) / 255.0f;
if (normalized < 0.0f)
normalized = 0.0f;
if (normalized > 1.0f)
normalized = 1.0f;
const unsigned char value = static_cast<unsigned char>(normalized * 255.0f + 0.5f);
const std::size_t out = (static_cast<std::size_t>(y) * width + x) * 4;
sdf[out + 0] = value;
sdf[out + 1] = value;
sdf[out + 2] = value;
sdf[out + 3] = value;
}
}
return sdf;
}
std::vector<unsigned char> BuildTextCoverageTexture(const std::vector<unsigned char>& alpha, unsigned width, unsigned height)
{
std::vector<unsigned char> coverage(static_cast<std::size_t>(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<std::size_t>(y) * width + x];
const std::size_t out = (static_cast<std::size_t>(y) * width + x) * 4;
coverage[out + 0] = value;
coverage[out + 1] = value;
coverage[out + 2] = value;
coverage[out + 3] = value;
}
}
return coverage;
}
std::vector<unsigned char> FlipTextTextureForShaderUv(const std::vector<unsigned char>& pixels, unsigned width, unsigned height)
{
std::vector<unsigned char> flipped(pixels.size(), 0);
const std::size_t stride = static_cast<std::size_t>(width) * 4;
for (unsigned y = 0; y < height; ++y)
{
const std::size_t srcOffset = static_cast<std::size_t>(y) * stride;
const std::size_t dstOffset = static_cast<std::size_t>(height - 1 - y) * stride;
std::memcpy(flipped.data() + dstOffset, pixels.data() + srcOffset, stride);
}
return flipped;
}
std::vector<unsigned char> BlurTextSdf(const std::vector<unsigned char>& pixels, unsigned width, unsigned height, unsigned passes)
{
std::vector<unsigned char> current = pixels;
std::vector<unsigned char> 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<int>(y) + oy;
if (sy < 0 || sy >= static_cast<int>(height))
continue;
for (int ox = -1; ox <= 1; ++ox)
{
const int sx = static_cast<int>(x) + ox;
if (sx < 0 || sx >= static_cast<int>(width))
continue;
const unsigned weight = (ox == 0 && oy == 0) ? 4u : ((ox == 0 || oy == 0) ? 2u : 1u);
const std::size_t sample = (static_cast<std::size_t>(sy) * width + sx) * 4;
weightedTotal += static_cast<unsigned>(current[sample]) * weight;
weightSum += weight;
}
}
const unsigned char value = static_cast<unsigned char>((weightedTotal + weightSum / 2) / weightSum);
const std::size_t out = (static_cast<std::size_t>(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<unsigned char>& alpha, const std::vector<unsigned char>& 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<unsigned char>& 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<char>(gray[(static_cast<std::size_t>(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<unsigned>(alphaMin, value);
alphaMax = std::max<unsigned>(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<unsigned>(sdfMin, value);
sdfMax = std::max<unsigned>(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<unsigned char>& 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<Gdiplus::FontFamily[]> 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<Gdiplus::REAL>(kTextTextureWidth) - (kTextLayoutPadding * 2.0f),
static_cast<Gdiplus::REAL>(kTextTextureHeight));
const std::wstring wideText = Utf8ToWide(text);
graphics.DrawString(wideText.c_str(), -1, &font, layout, &format, &brush);
std::vector<unsigned char> alpha(static_cast<std::size_t>(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<std::size_t>(y) * kTextTextureWidth + x] = static_cast<unsigned char>(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;
}

View File

@@ -0,0 +1,10 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
constexpr unsigned kTextTextureWidth = 2048;
constexpr unsigned kTextTextureHeight = 256;
bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector<unsigned char>& sdf, std::string& error);

View File

@@ -0,0 +1,114 @@
#include "TextureAssetLoader.h"
#include <windows.h>
#include <wincodec.h>
#include <atlbase.h>
#include <cstring>
#include <vector>
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<IWICImagingFactory> 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<IWICBitmapDecoder> 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<IWICBitmapFrameDecode> 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<IWICFormatConverter> 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<unsigned char> pixels(static_cast<std::size_t>(stride) * static_cast<std::size_t>(height));
result = formatConverter->CopyPixels(NULL, stride, static_cast<UINT>(pixels.size()), pixels.data());
if (FAILED(result))
{
if (shouldUninitializeCom)
CoUninitialize();
error = "Could not read shader texture pixels: " + textureAsset.path.string();
return false;
}
std::vector<unsigned char> flippedPixels(pixels.size());
for (UINT row = 0; row < height; ++row)
{
const std::size_t srcOffset = static_cast<std::size_t>(row) * stride;
const std::size_t dstOffset = static_cast<std::size_t>(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<GLsizei>(width), static_cast<GLsizei>(height), 0, GL_BGRA, GL_UNSIGNED_BYTE, flippedPixels.data());
glBindTexture(GL_TEXTURE_2D, 0);
if (shouldUninitializeCom)
CoUninitialize();
return true;
}

View File

@@ -0,0 +1,10 @@
#pragma once
#include "ShaderTypes.h"
#include <windows.h>
#include <gl/gl.h>
#include <string>
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error);

View File

@@ -0,0 +1,73 @@
#include "Std140Buffer.h"
#include <cstring>
#include <iostream>
#include <vector>
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<unsigned char>& buffer, std::size_t offset)
{
float value = 0.0f;
std::memcpy(&value, buffer.data() + offset, sizeof(float));
return value;
}
int ReadInt(const std::vector<unsigned char>& buffer, std::size_t offset)
{
int value = 0;
std::memcpy(&value, buffer.data() + offset, sizeof(int));
return value;
}
void TestScalarPacking()
{
std::vector<unsigned char> 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<unsigned char> 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;
}