Refactor
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<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>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
@@ -115,7 +115,7 @@
|
||||
</Midl>
|
||||
<ClCompile>
|
||||
<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>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
@@ -139,7 +139,7 @@
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<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>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
@@ -169,7 +169,7 @@
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<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>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
@@ -192,26 +192,26 @@
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="GLExtensions.cpp" />
|
||||
<ClCompile Include="gl\GLExtensions.cpp" />
|
||||
<ClCompile Include="LoopThroughWithOpenGLCompositing.cpp" />
|
||||
<ClCompile Include="OpenGLComposite.cpp" />
|
||||
<ClCompile Include="gl\OpenGLComposite.cpp" />
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VideoFrameTransfer.cpp" />
|
||||
<ClCompile Include="gl\VideoFrameTransfer.cpp" />
|
||||
<ClCompile Include="DeckLinkAPI_i.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="GLExtensions.h" />
|
||||
<ClInclude Include="gl\GLExtensions.h" />
|
||||
<ClInclude Include="LoopThroughWithOpenGLCompositing.h" />
|
||||
<ClInclude Include="OpenGLComposite.h" />
|
||||
<ClInclude Include="gl\OpenGLComposite.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
<ClInclude Include="targetver.h" />
|
||||
<ClInclude Include="VideoFrameTransfer.h" />
|
||||
<ClInclude Include="gl\VideoFrameTransfer.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="LoopThroughWithOpenGLCompositing.ico" />
|
||||
|
||||
@@ -18,19 +18,19 @@
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="GLExtensions.cpp">
|
||||
<ClCompile Include="gl\GLExtensions.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="LoopThroughWithOpenGLCompositing.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="OpenGLComposite.cpp">
|
||||
<ClCompile Include="gl\OpenGLComposite.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VideoFrameTransfer.cpp">
|
||||
<ClCompile Include="gl\VideoFrameTransfer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DeckLinkAPI_i.c">
|
||||
@@ -38,13 +38,13 @@
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="GLExtensions.h">
|
||||
<ClInclude Include="gl\GLExtensions.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="LoopThroughWithOpenGLCompositing.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="OpenGLComposite.h">
|
||||
<ClInclude Include="gl\OpenGLComposite.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h">
|
||||
@@ -56,7 +56,7 @@
|
||||
<ClInclude Include="targetver.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="VideoFrameTransfer.h">
|
||||
<ClInclude Include="gl\VideoFrameTransfer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
57
apps/LoopThroughWithOpenGLCompositing/gl/GlScopedObjects.h
Normal file
57
apps/LoopThroughWithOpenGLCompositing/gl/GlScopedObjects.h
Normal 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;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -52,7 +52,7 @@
|
||||
#include <comutil.h>
|
||||
#include "DeckLinkAPI_h.h"
|
||||
|
||||
#include "VideoFrameTransfer.h"
|
||||
#include "GLExtensions.h"
|
||||
#include "RuntimeHost.h"
|
||||
|
||||
#include <atomic>
|
||||
@@ -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<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__
|
||||
@@ -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;
|
||||
}
|
||||
48
apps/LoopThroughWithOpenGLCompositing/gl/Std140Buffer.h
Normal file
48
apps/LoopThroughWithOpenGLCompositing/gl/Std140Buffer.h
Normal 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));
|
||||
}
|
||||
316
apps/LoopThroughWithOpenGLCompositing/gl/TextRasterizer.cpp
Normal file
316
apps/LoopThroughWithOpenGLCompositing/gl/TextRasterizer.cpp
Normal 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;
|
||||
}
|
||||
10
apps/LoopThroughWithOpenGLCompositing/gl/TextRasterizer.h
Normal file
10
apps/LoopThroughWithOpenGLCompositing/gl/TextRasterizer.h
Normal 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);
|
||||
114
apps/LoopThroughWithOpenGLCompositing/gl/TextureAssetLoader.cpp
Normal file
114
apps/LoopThroughWithOpenGLCompositing/gl/TextureAssetLoader.cpp
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
73
tests/Std140BufferTests.cpp
Normal file
73
tests/Std140BufferTests.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user