This commit is contained in:
2026-05-02 15:49:45 +10:00
parent 8b7aa1971e
commit 8d01ea4a3c
12 changed files with 454 additions and 239 deletions

4
.vscode/launch.json vendored
View File

@@ -5,10 +5,10 @@
"name": "Debug LoopThroughWithOpenGLCompositing",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}\\apps\\LoopThroughWithOpenGLCompositing\\x64\\Debug\\LoopThroughWithOpenGLCompositing.exe",
"program": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug\\LoopThroughWithOpenGLCompositing.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}\\apps\\LoopThroughWithOpenGLCompositing\\x64\\Debug",
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
"environment": [],
"console": "internalConsole",
"preLaunchTask": "Build LoopThroughWithOpenGLCompositing Debug x64"

36
.vscode/tasks.json vendored
View File

@@ -4,12 +4,14 @@
{
"label": "Build LoopThroughWithOpenGLCompositing Debug x64",
"type": "process",
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\MSBuild.exe",
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe",
"args": [
"${workspaceFolder}\\apps\\LoopThroughWithOpenGLCompositing\\LoopThroughWithOpenGLCompositing.sln",
"/m",
"/p:Configuration=Debug",
"/p:Platform=x64"
"--build",
"${workspaceFolder}\\build\\vs2022-x64-debug",
"--config",
"Debug",
"--target",
"LoopThroughWithOpenGLCompositing"
],
"group": {
"kind": "build",
@@ -20,12 +22,14 @@
{
"label": "Build LoopThroughWithOpenGLCompositing Release x64",
"type": "process",
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\MSBuild.exe",
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe",
"args": [
"${workspaceFolder}\\apps\\LoopThroughWithOpenGLCompositing\\LoopThroughWithOpenGLCompositing.sln",
"/m",
"/p:Configuration=Release",
"/p:Platform=x64"
"--build",
"${workspaceFolder}\\build\\vs2022-x64-release",
"--config",
"Release",
"--target",
"LoopThroughWithOpenGLCompositing"
],
"group": "build",
"problemMatcher": "$msCompile"
@@ -33,12 +37,14 @@
{
"label": "Clean LoopThroughWithOpenGLCompositing Debug x64",
"type": "process",
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\MSBuild.exe",
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe",
"args": [
"${workspaceFolder}\\apps\\LoopThroughWithOpenGLCompositing\\LoopThroughWithOpenGLCompositing.sln",
"/target:Clean",
"/p:Configuration=Debug",
"/p:Platform=x64"
"--build",
"${workspaceFolder}\\build\\vs2022-x64-debug",
"--config",
"Debug",
"--target",
"clean"
],
"problemMatcher": "$msCompile"
}

View File

@@ -8,6 +8,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
set(APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/apps/LoopThroughWithOpenGLCompositing")
set(GPUDIRECT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdParty/Blackmagic DeckLink SDK 16.0/Win/Samples/NVIDIA_GPUDirect")
set(SHADER_SLANG_SOURCE "${APP_DIR}/video_effect.slang")
if(NOT EXISTS "${APP_DIR}/LoopThroughWithOpenGLCompositing.cpp")
message(FATAL_ERROR "Imported app sources were not found under ${APP_DIR}")
@@ -32,7 +33,7 @@ add_executable(LoopThroughWithOpenGLCompositing WIN32
"${APP_DIR}/targetver.h"
"${APP_DIR}/VideoFrameTransfer.cpp"
"${APP_DIR}/VideoFrameTransfer.h"
"${APP_DIR}/video_effect.frag"
"${SHADER_SLANG_SOURCE}"
)
target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE
@@ -55,7 +56,10 @@ target_compile_definitions(LoopThroughWithOpenGLCompositing PRIVATE
UNICODE
)
set_source_files_properties("${APP_DIR}/video_effect.frag" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties(
"${SHADER_SLANG_SOURCE}"
PROPERTIES HEADER_FILE_ONLY TRUE
)
if(MSVC)
target_compile_options(LoopThroughWithOpenGLCompositing PRIVATE /W3)
@@ -65,9 +69,6 @@ add_custom_command(TARGET LoopThroughWithOpenGLCompositing POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${GPUDIRECT_DIR}/bin/x64/dvp.dll"
"$<TARGET_FILE_DIR:LoopThroughWithOpenGLCompositing>/dvp.dll"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${APP_DIR}/video_effect.frag"
"$<TARGET_FILE_DIR:LoopThroughWithOpenGLCompositing>/video_effect.frag"
)
source_group(TREE "${APP_DIR}" FILES
@@ -85,5 +86,5 @@ source_group(TREE "${APP_DIR}" FILES
"${APP_DIR}/targetver.h"
"${APP_DIR}/VideoFrameTransfer.cpp"
"${APP_DIR}/VideoFrameTransfer.h"
"${APP_DIR}/video_effect.frag"
"${SHADER_SLANG_SOURCE}"
)

View File

@@ -63,6 +63,8 @@ PFNGLDELETEBUFFERSPROC glDeleteBuffers;
PFNGLBINDBUFFERPROC glBindBuffer;
PFNGLBUFFERDATAPROC glBufferData;
PFNGLCREATESHADERPROC glCreateShader;
PFNGLDELETESHADERPROC glDeleteShader;
PFNGLDELETEPROGRAMPROC glDeleteProgram;
PFNGLSHADERSOURCEPROC glShaderSource;
PFNGLCOMPILESHADERPROC glCompileShader;
PFNGLGETSHADERIVPROC glGetShaderiv;
@@ -98,6 +100,8 @@ bool ResolveGLExtensions()
glBindBuffer = (PFNGLBINDBUFFERPROC) wglGetProcAddress("glBindBuffer");
glBufferData = (PFNGLBUFFERDATAPROC) wglGetProcAddress("glBufferData");
glCreateShader = (PFNGLCREATESHADERPROC) wglGetProcAddress("glCreateShader");
glDeleteShader = (PFNGLDELETESHADERPROC) wglGetProcAddress("glDeleteShader");
glDeleteProgram = (PFNGLDELETEPROGRAMPROC) wglGetProcAddress("glDeleteProgram");
glShaderSource = (PFNGLSHADERSOURCEPROC) wglGetProcAddress("glShaderSource");
glCompileShader = (PFNGLCOMPILESHADERPROC) wglGetProcAddress("glCompileShader");
glGetShaderiv = (PFNGLGETSHADERIVPROC) wglGetProcAddress("glGetShaderiv");
@@ -131,6 +135,8 @@ bool ResolveGLExtensions()
&& glBindBuffer
&& glBufferData
&& glCreateShader
&& glDeleteShader
&& glDeleteProgram
&& glShaderSource
&& glCompileShader
&& glGetShaderiv

View File

@@ -90,6 +90,8 @@ typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);
typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader);
typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void);
typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type);
typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader);
typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program);
typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params);
typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params);
@@ -134,6 +136,8 @@ extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
extern PFNGLBINDBUFFERPROC glBindBuffer;
extern PFNGLBUFFERDATAPROC glBufferData;
extern PFNGLCREATESHADERPROC glCreateShader;
extern PFNGLDELETESHADERPROC glDeleteShader;
extern PFNGLDELETEPROGRAMPROC glDeleteProgram;
extern PFNGLSHADERSOURCEPROC glShaderSource;
extern PFNGLCOMPILESHADERPROC glCompileShader;
extern PFNGLGETSHADERIVPROC glGetShaderiv;

View File

@@ -210,6 +210,14 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
wglMakeCurrent( NULL, NULL );
break;
case WM_KEYDOWN:
if (pOpenGLComposite && (wParam == 'R' || wParam == 'r'))
{
pOpenGLComposite->ReloadShader();
InvalidateRect(hWnd, NULL, FALSE);
}
break;
default:
return (DefWindowProc(hWnd, message, wParam, lParam));
}

View File

@@ -96,6 +96,7 @@
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>dvp.lib;opengl32.lib;Glu32.lib;%(AdditionalDependencies)</AdditionalDependencies>
@@ -105,8 +106,7 @@
<TargetMachine>MachineX86</TargetMachine>
</Link>
<PostBuildEvent>
<Command>copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)"
copy /y "video_effect.frag" "$(TargetDir)"</Command>
<Command>copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -122,6 +122,7 @@ copy /y "video_effect.frag" "$(TargetDir)"</Command>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>dvp.lib;opengl32.lib;Glu32.lib;%(AdditionalDependencies)</AdditionalDependencies>
@@ -131,8 +132,7 @@ copy /y "video_effect.frag" "$(TargetDir)"</Command>
<TargetMachine>MachineX64</TargetMachine>
</Link>
<PostBuildEvent>
<Command>copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)"
copy /y "video_effect.frag" "$(TargetDir)"</Command>
<Command>copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -146,6 +146,7 @@ copy /y "video_effect.frag" "$(TargetDir)"</Command>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>dvp.lib;opengl32.lib;Glu32.lib;%(AdditionalDependencies)</AdditionalDependencies>
@@ -158,8 +159,7 @@ copy /y "video_effect.frag" "$(TargetDir)"</Command>
</Link>
<PostBuildEvent>
<Message>Copy nececssary DLLs to target directory</Message>
<Command>copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)"
copy /y "video_effect.frag" "$(TargetDir)"</Command>
<Command>copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -176,6 +176,7 @@ copy /y "video_effect.frag" "$(TargetDir)"</Command>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>dvp.lib;opengl32.lib;Glu32.lib;%(AdditionalDependencies)</AdditionalDependencies>
@@ -187,8 +188,7 @@ copy /y "video_effect.frag" "$(TargetDir)"</Command>
<TargetMachine>MachineX64</TargetMachine>
</Link>
<PostBuildEvent>
<Command>copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)"
copy /y "video_effect.frag" "$(TargetDir)"</Command>
<Command>copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
@@ -218,7 +218,7 @@ copy /y "video_effect.frag" "$(TargetDir)"</Command>
<Image Include="small.ico" />
</ItemGroup>
<ItemGroup>
<None Include="video_effect.frag" />
<None Include="video_effect.slang" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="LoopThroughWithOpenGLCompositing.rc" />

View File

@@ -78,4 +78,9 @@
<Filter>DeckLink API</Filter>
</Midl>
</ItemGroup>
</Project>
<ItemGroup>
<None Include="video_effect.slang">
<Filter>Resource Files</Filter>
</None>
</ItemGroup>
</Project>

View File

@@ -41,9 +41,11 @@
#include "OpenGLComposite.h"
#include "GLExtensions.h"
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <initguid.h>
DEFINE_GUID(IID_PinnedMemoryAllocator,
@@ -51,7 +53,20 @@ DEFINE_GUID(IID_PinnedMemoryAllocator,
namespace
{
const char* kFragmentShaderFilename = "video_effect.frag";
const char* kSlangShaderRelativePath = "apps/LoopThroughWithOpenGLCompositing/video_effect.slang";
const char* kRuntimeShaderCacheDirectory = "shader_cache";
const char* kRuntimeRawShaderFilename = "video_effect.raw.frag";
const char* kRuntimePatchedShaderFilename = "video_effect.frag";
const char* kVertexShaderSource =
"#version 130\n"
"out vec2 vTexCoord;\n"
"void main()\n"
"{\n"
" vec2 positions[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\n"
" vec2 texCoords[3] = vec2[3](vec2(0.0, 0.0), vec2(2.0, 0.0), vec2(0.0, 2.0));\n"
" gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);\n"
" vTexCoord = texCoords[gl_VertexID];\n"
"}\n";
std::string GetExecutableDirectory()
{
@@ -68,6 +83,19 @@ std::string GetExecutableDirectory()
return path.substr(0, slashIndex);
}
bool ReplaceAll(std::string& text, const std::string& from, const std::string& to)
{
bool replaced = false;
std::string::size_type startPos = 0;
while ((startPos = text.find(from, startPos)) != std::string::npos)
{
text.replace(startPos, from.length(), to);
startPos += to.length();
replaced = true;
}
return replaced;
}
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
{
if (!errorMessage || errorMessageSize <= 0)
@@ -97,6 +125,158 @@ bool LoadTextFile(const std::string& path, std::string& contents, std::string& e
return true;
}
std::filesystem::path FindRepoRoot()
{
std::vector<std::filesystem::path> rootsToTry;
char currentDirBuffer[MAX_PATH] = {};
if (GetCurrentDirectoryA(MAX_PATH, currentDirBuffer) > 0)
rootsToTry.push_back(std::filesystem::path(currentDirBuffer));
std::string executableDirectory = GetExecutableDirectory();
if (!executableDirectory.empty())
rootsToTry.push_back(std::filesystem::path(executableDirectory));
for (const std::filesystem::path& startPath : rootsToTry)
{
std::filesystem::path candidate = startPath;
for (int depth = 0; depth < 8 && !candidate.empty(); ++depth)
{
if (std::filesystem::exists(candidate / kSlangShaderRelativePath))
return candidate;
candidate = candidate.parent_path();
}
}
return std::filesystem::path();
}
bool FindSlangCompiler(const std::filesystem::path& repoRoot, std::filesystem::path& slangCompilerPath, std::string& error)
{
std::filesystem::path thirdPartyPath = repoRoot / "3rdParty";
if (!std::filesystem::exists(thirdPartyPath))
{
error = "Could not locate the 3rdParty directory from the application runtime path.";
return false;
}
for (const auto& entry : std::filesystem::directory_iterator(thirdPartyPath))
{
if (!entry.is_directory())
continue;
std::filesystem::path candidate = entry.path() / "bin" / "slangc.exe";
if (std::filesystem::exists(candidate))
{
slangCompilerPath = candidate;
return true;
}
}
error = "Could not find slangc.exe under 3rdParty.";
return false;
}
bool RunProcessAndWait(const std::string& commandLine, std::string& error)
{
STARTUPINFOA startupInfo = {};
PROCESS_INFORMATION processInfo = {};
startupInfo.cb = sizeof(startupInfo);
std::vector<char> mutableCommandLine(commandLine.begin(), commandLine.end());
mutableCommandLine.push_back('\0');
if (!CreateProcessA(NULL, mutableCommandLine.data(), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &processInfo))
{
error = "Failed to start slangc.exe.";
return false;
}
WaitForSingleObject(processInfo.hProcess, INFINITE);
DWORD exitCode = 0;
GetExitCodeProcess(processInfo.hProcess, &exitCode);
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
if (exitCode != 0)
{
error = "slangc.exe returned a non-zero exit code while compiling the runtime shader.";
return false;
}
return true;
}
bool PatchGeneratedSlangGLSL(std::string& shaderText, std::string& error)
{
bool replacedVersion = ReplaceAll(shaderText, "#version 450", "#version 130");
ReplaceAll(shaderText, "#extension GL_EXT_samplerless_texture_functions : require\n", "");
ReplaceAll(shaderText, "layout(row_major) uniform;\n", "");
ReplaceAll(shaderText, "layout(row_major) buffer;\n", "");
ReplaceAll(shaderText, "layout(binding = 0)\nuniform texture2D UYVYtex_0;", "uniform sampler2D UYVYtex;");
ReplaceAll(shaderText, "layout(location = 0)\nout vec4 entryPointParam_fragmentMain_0;\n", "");
ReplaceAll(shaderText, "layout(location = 0)\nin vec2 input_texCoord_0;\n", "in vec2 vTexCoord;\n");
ReplaceAll(shaderText, "UYVYtex_0", "UYVYtex");
ReplaceAll(shaderText, "input_texCoord_0", "vTexCoord");
ReplaceAll(shaderText, "entryPointParam_fragmentMain_0 =", "gl_FragColor =");
if (!replacedVersion)
{
error = "Generated Slang GLSL did not contain the expected version header.";
return false;
}
return true;
}
bool BuildFragmentShaderSourceFromSlang(std::string& shaderSource, std::string& error)
{
std::filesystem::path repoRoot = FindRepoRoot();
if (repoRoot.empty())
{
error = "Could not locate the repository root to load video_effect.slang.";
return false;
}
std::filesystem::path slangSourcePath = repoRoot / kSlangShaderRelativePath;
if (!std::filesystem::exists(slangSourcePath))
{
error = "Could not find video_effect.slang.";
return false;
}
std::filesystem::path slangCompilerPath;
if (!FindSlangCompiler(repoRoot, slangCompilerPath, error))
return false;
std::filesystem::path shaderCachePath = std::filesystem::path(GetExecutableDirectory()) / kRuntimeShaderCacheDirectory;
std::filesystem::create_directories(shaderCachePath);
std::filesystem::path rawShaderPath = shaderCachePath / kRuntimeRawShaderFilename;
std::filesystem::path patchedShaderPath = shaderCachePath / kRuntimePatchedShaderFilename;
std::string commandLine = "\"" + slangCompilerPath.string() + "\" \"" + slangSourcePath.string()
+ "\" -target glsl -profile glsl_430 -entry fragmentMain -stage fragment -o \"" + rawShaderPath.string() + "\"";
if (!RunProcessAndWait(commandLine, error))
return false;
if (!LoadTextFile(rawShaderPath.string(), shaderSource, error))
return false;
if (!PatchGeneratedSlangGLSL(shaderSource, error))
return false;
std::ofstream patchedShaderOutput(patchedShaderPath.string().c_str(), std::ios::binary);
if (patchedShaderOutput)
patchedShaderOutput << shaderSource;
return true;
}
}
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
@@ -109,6 +289,10 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
mFastTransferExtensionAvailable(false),
mCaptureTexture(0),
mFBOTexture(0),
mProgram(0),
mVertexShader(0),
mFragmentShader(0),
mUYVYtexUniform(-1),
mRotateAngle(0.0f),
mRotateAngleRate(0.0f)
{
@@ -164,6 +348,8 @@ OpenGLComposite::~OpenGLComposite()
mPlayoutAllocator = NULL;
}
destroyShaderProgram();
DeleteCriticalSection(&pMutex);
}
@@ -177,7 +363,6 @@ bool OpenGLComposite::InitDeckLink()
IDeckLinkDisplayMode* pDLDisplayMode = NULL;
BMDDisplayMode displayMode = bmdModeHD1080p5994; // mode to use for capture and playout
int outputFrameRowBytes;
float fps;
HRESULT result;
result = CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&pDLIterator);
@@ -267,10 +452,7 @@ bool OpenGLComposite::InitDeckLink()
if (! InitOpenGLState())
goto error;
// Compute a rotate angle rate so box will spin at a rate independent of video mode frame rate
pDLDisplayMode->GetFrameRate(&mFrameDuration, &mFrameTimescale);
fps = (float)mFrameTimescale / (float)mFrameDuration;
mRotateAngleRate = 35.0f / fps; // rotate box through 35 degrees every second
// Resize window to match video frame, but scale large formats down by half for viewing
if (mFrameWidth < 1920)
@@ -413,7 +595,7 @@ bool OpenGLComposite::InitOpenGLState()
if (! ResolveGLExtensions())
return false;
// Prepare the shader used to perform colour space conversion on the video texture
// Prepare the runtime shader program generated from the Slang source file.
char compilerErrorMessage[1024];
if (! compileFragmentShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
@@ -421,13 +603,8 @@ bool OpenGLComposite::InitOpenGLState()
return false;
}
// Setup the scene
glShadeModel( GL_SMOOTH ); // Enable smooth shading
glClearColor( 0.0f, 0.0f, 0.0f, 0.5f ); // Black background
glClearDepth( 1.0f ); // Depth buffer setup
glEnable( GL_DEPTH_TEST ); // Enable depth testing
glDepthFunc( GL_LEQUAL ); // Type of depth test to do
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
glDisable(GL_DEPTH_TEST);
if (! mFastTransferExtensionAvailable)
{
@@ -546,8 +723,8 @@ void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bo
inputFrameBuffer->Release();
}
// Draw the captured video frame texture onto a box, rendering to the off-screen frame buffer.
// Read the rendered scene back from the frame buffer and schedule it for playout.
// Render the live video texture through the runtime shader into the off-screen framebuffer.
// Read the result back from the frame buffer and schedule it for playout.
void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult)
{
EnterCriticalSection(&pMutex);
@@ -560,111 +737,9 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame,
// make GL context current in this thread
wglMakeCurrent( hGLDC, hGLRC );
// Draw OpenGL scene to the off-screen frame buffer
// Draw the effect output to the off-screen framebuffer.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mIdFrameBuf);
// Setup view and projection
GLfloat aspectRatio = (GLfloat)mFrameWidth / (GLfloat)mFrameHeight;
glViewport (0, 0, mFrameWidth, mFrameHeight);
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluPerspective( 45.0f, aspectRatio, 0.1f, 100.0f );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glScalef( aspectRatio, 1.0f, 1.0f ); // Scale x for correct aspect ratio
glTranslatef( 0.0f, 0.0f, -4.0f ); // Move into screen
glRotatef( mRotateAngle, 1.0f, 1.0f, 1.0f ); // Rotate model around a vector
mRotateAngle -= mRotateAngleRate; // update the rotation angle for next iteration
glFinish(); // Ensure changes to GL state are complete
// Draw a colourful frame around the front face of the box
// (provides a pleasing nesting effect when you connect the playout output to the capture input)
glBegin(GL_QUAD_STRIP);
glColor3f( 1.0f, 0.0f, 0.0f );
glVertex3f( 1.2f, 1.2f, 1.0f);
glVertex3f( 1.0f, 1.0f, 1.0f);
glColor3f( 0.0f, 0.0f, 1.0f );
glVertex3f( 1.2f, -1.2f, 1.0f);
glVertex3f( 1.0f, -1.0f, 1.0f);
glColor3f( 0.0f, 1.0f, 0.0f );
glVertex3f(-1.2f, -1.2f, 1.0f);
glVertex3f(-1.0f, -1.0f, 1.0f);
glColor3f( 1.0f, 1.0f, 0.0f );
glVertex3f(-1.2f, 1.2f, 1.0f);
glVertex3f(-1.0f, 1.0f, 1.0f);
glColor3f( 1.0f, 0.0f, 0.0f );
glVertex3f( 1.2f, 1.2f, 1.0f);
glVertex3f( 1.0f, 1.0f, 1.0f);
glEnd();
if (mHasNoInputSource)
{
// Draw a big X when no input is available on capture
glBegin( GL_QUADS );
glColor3f( 1.0f, 0.0f, 1.0f );
glVertex3f( 0.8f, 0.9f, 1.0f );
glVertex3f( 0.9f, 0.8f, 1.0f );
glColor3f( 1.0f, 1.0f, 0.0f );
glVertex3f( -0.8f, -0.9f, 1.0f );
glVertex3f( -0.9f, -0.8f, 1.0f );
glColor3f( 1.0f, 0.0f, 1.0f );
glVertex3f( -0.8f, 0.9f, 1.0f );
glVertex3f( -0.9f, 0.8f, 1.0f );
glColor3f( 1.0f, 1.0f, 0.0f );
glVertex3f( 0.8f, -0.9f, 1.0f );
glVertex3f( 0.9f, -0.8f, 1.0f );
glEnd();
}
else
{
if (mFastTransferExtensionAvailable)
{
// Signal that we're about to draw using mCaptureTexture onto mFBOTexture
VideoFrameTransfer::beginTextureInUse(VideoFrameTransfer::CPUtoGPU);
}
// Pass texture unit 0 to the fragment shader as a uniform variable
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
glUseProgram(mProgram);
GLint locUYVYtex = glGetUniformLocation(mProgram, "UYVYtex");
glUniform1i(locUYVYtex, 0); // Bind texture unit 0
// Draw front and back faces of box applying video texture to each face
glBegin(GL_QUADS);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f ); // Top right of front side
glTexCoord2f(0.0f, 0.0f); glVertex3f( -1.0f, 1.0f, 1.0f ); // Top left of front side
glTexCoord2f(0.0f, 1.0f); glVertex3f( -1.0f, -1.0f, 1.0f ); // Bottom left of front side
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, -1.0f, 1.0f ); // Bottom right of front side
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f ); // Top right of back side
glTexCoord2f(0.0f, 1.0f); glVertex3f( -1.0f, -1.0f, -1.0f ); // Top left of back side
glTexCoord2f(0.0f, 0.0f); glVertex3f( -1.0f, 1.0f, -1.0f ); // Bottom left of back side
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, -1.0f ); // Bottom right of back side
glEnd();
// Draw left and right sides of box with partially transparent video texture
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBegin(GL_QUADS);
glTexCoord2f(0.1f, 0.0f); glVertex3f( -1.0f, 1.0f, 1.0f ); // Top right of left side
glTexCoord2f(1.0f, 0.0f); glVertex3f( -1.0f, 1.0f, -1.0f ); // Top left of left side
glTexCoord2f(1.0f, 1.0f); glVertex3f( -1.0f, -1.0f, -1.0f ); // Bottom left of left side
glTexCoord2f(0.1f, 1.0f); glVertex3f( -1.0f, -1.0f, 1.0f ); // Bottom right of left side
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, -1.0f ); // Top right of right side
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f ); // Top left of right side
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, 1.0f ); // Bottom left of right side
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f ); // Bottom right of right side
glEnd();
glDisable(GL_BLEND);
glUseProgram(0);
glDisable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
}
renderEffect();
IDeckLinkVideoBuffer* outputVideoFrameBuffer;
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
@@ -772,26 +847,93 @@ bool OpenGLComposite::Stop()
return true;
}
// Setup fragment shader to take YCbCr 4:2:2 video texture in UYVY macropixel format
// and perform colour space conversion to RGBA in the GPU.
bool OpenGLComposite::compileFragmentShader(int errorMessageSize, char* errorMessage)
bool OpenGLComposite::ReloadShader()
{
GLsizei errorBufferSize;
GLint compileResult, linkResult;
std::string shaderPath = GetExecutableDirectory();
std::string fragmentShaderSource;
std::string loadError;
char compilerErrorMessage[1024];
if (shaderPath.empty())
EnterCriticalSection(&pMutex);
wglMakeCurrent(hGLDC, hGLRC);
bool success = compileFragmentShader(sizeof(compilerErrorMessage), compilerErrorMessage);
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&pMutex);
if (!success)
MessageBoxA(NULL, compilerErrorMessage, "Slang shader reload failed", MB_OK);
return success;
}
void OpenGLComposite::destroyShaderProgram()
{
if (mProgram != 0)
{
CopyErrorMessage("Could not determine executable directory for fragment shader loading.", errorMessageSize, errorMessage);
return false;
glDeleteProgram(mProgram);
mProgram = 0;
}
shaderPath += "\\";
shaderPath += kFragmentShaderFilename;
if (mFragmentShader != 0)
{
glDeleteShader(mFragmentShader);
mFragmentShader = 0;
}
if (!LoadTextFile(shaderPath, fragmentShaderSource, loadError))
if (mVertexShader != 0)
{
glDeleteShader(mVertexShader);
mVertexShader = 0;
}
mUYVYtexUniform = -1;
}
void OpenGLComposite::renderEffect()
{
glViewport(0, 0, mFrameWidth, mFrameHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (mHasNoInputSource)
return;
if (mFastTransferExtensionAvailable)
{
// Signal that we're about to draw using mCaptureTexture onto mFBOTexture.
VideoFrameTransfer::beginTextureInUse(VideoFrameTransfer::CPUtoGPU);
}
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
glUseProgram(mProgram);
if (mUYVYtexUniform >= 0)
glUniform1i(mUYVYtexUniform, 0);
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
if (mFastTransferExtensionAvailable)
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU);
}
// Compile a fullscreen shader pass from the runtime Slang source. The Slang compiler
// emits modern GLSL which we patch into a compatibility-profile shader that can run
// inside the sample's WGL context.
bool OpenGLComposite::compileFragmentShader(int errorMessageSize, char* errorMessage)
{
GLsizei errorBufferSize = 0;
GLint compileResult = GL_FALSE;
GLint linkResult = GL_FALSE;
std::string fragmentShaderSource;
std::string loadError;
const char* vertexSource = kVertexShaderSource;
if (!BuildFragmentShaderSourceFromSlang(fragmentShaderSource, loadError))
{
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
return false;
@@ -799,30 +941,50 @@ bool OpenGLComposite::compileFragmentShader(int errorMessageSize, char* errorMes
const char* fragmentSource = fragmentShaderSource.c_str();
mFragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(mFragmentShader, 1, (const GLchar**)&fragmentSource, NULL);
glCompileShader(mFragmentShader);
glGetShaderiv(mFragmentShader, GL_COMPILE_STATUS, &compileResult);
GLuint newVertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(newVertexShader, 1, (const GLchar**)&vertexSource, NULL);
glCompileShader(newVertexShader);
glGetShaderiv(newVertexShader, GL_COMPILE_STATUS, &compileResult);
if (compileResult == GL_FALSE)
{
glGetShaderInfoLog(mFragmentShader, errorMessageSize, &errorBufferSize, errorMessage);
glGetShaderInfoLog(newVertexShader, errorMessageSize, &errorBufferSize, errorMessage);
glDeleteShader(newVertexShader);
return false;
}
mProgram = glCreateProgram();
GLuint newFragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(newFragmentShader, 1, (const GLchar**)&fragmentSource, NULL);
glCompileShader(newFragmentShader);
glGetShaderiv(newFragmentShader, GL_COMPILE_STATUS, &compileResult);
if (compileResult == GL_FALSE)
{
glGetShaderInfoLog(newFragmentShader, errorMessageSize, &errorBufferSize, errorMessage);
glDeleteShader(newVertexShader);
glDeleteShader(newFragmentShader);
return false;
}
glAttachShader(mProgram, mFragmentShader);
glLinkProgram(mProgram);
glGetProgramiv(mProgram, GL_LINK_STATUS, &linkResult);
GLuint newProgram = glCreateProgram();
glAttachShader(newProgram, newVertexShader);
glAttachShader(newProgram, newFragmentShader);
glLinkProgram(newProgram);
glGetProgramiv(newProgram, GL_LINK_STATUS, &linkResult);
if (linkResult == GL_FALSE)
{
glGetProgramInfoLog(mProgram, errorMessageSize, &errorBufferSize, errorMessage);
glGetProgramInfoLog(newProgram, errorMessageSize, &errorBufferSize, errorMessage);
glDeleteProgram(newProgram);
glDeleteShader(newVertexShader);
glDeleteShader(newFragmentShader);
return false;
}
destroyShaderProgram();
mProgram = newProgram;
mVertexShader = newVertexShader;
mFragmentShader = newFragmentShader;
mUYVYtexUniform = glGetUniformLocation(mProgram, "UYVYtex");
return true;
}

View File

@@ -74,6 +74,7 @@ public:
bool InitDeckLink();
bool Start();
bool Stop();
bool ReloadShader();
void resizeGL(WORD width, WORD height);
void paintGL();
@@ -113,7 +114,9 @@ private:
GLuint mIdColorBuf;
GLuint mIdDepthBuf;
GLuint mProgram;
GLuint mVertexShader;
GLuint mFragmentShader;
GLint mUYVYtexUniform;
GLfloat mRotateAngle;
GLfloat mRotateAngleRate;
int mViewWidth;
@@ -121,6 +124,8 @@ private:
bool InitOpenGLState();
bool compileFragmentShader(int errorMessageSize, char* errorMessage);
void destroyShaderProgram();
void renderEffect();
};
////////////////////////////////////////////

View File

@@ -1,61 +0,0 @@
#version 130
uniform sampler2D UYVYtex;
vec4 rec709YCbCr2rgba(float Y, float Cb, float Cr, float a)
{
float r, g, b;
Y = (Y * 256.0 - 16.0) / 219.0;
Cb = (Cb * 256.0 - 16.0) / 224.0 - 0.5;
Cr = (Cr * 256.0 - 16.0) / 224.0 - 0.5;
r = Y + 1.5748 * Cr;
g = Y - 0.1873 * Cb - 0.4681 * Cr;
b = Y + 1.8556 * Cb;
return vec4(r, g, b, a);
}
vec4 bilinear(vec4 W, vec4 X, vec4 Y, vec4 Z, vec2 weight)
{
vec4 m0 = mix(W, Z, weight.x);
vec4 m1 = mix(X, Y, weight.x);
return mix(m0, m1, weight.y);
}
void textureGatherYUV(sampler2D UYVYsampler, vec2 tc, out vec4 W, out vec4 X, out vec4 Y, out vec4 Z)
{
ivec2 tx = ivec2(tc * textureSize(UYVYsampler, 0));
ivec2 tmin = ivec2(0, 0);
ivec2 tmax = textureSize(UYVYsampler, 0) - ivec2(1, 1);
W = texelFetch(UYVYsampler, tx, 0);
X = texelFetch(UYVYsampler, clamp(tx + ivec2(0, 1), tmin, tmax), 0);
Y = texelFetch(UYVYsampler, clamp(tx + ivec2(1, 1), tmin, tmax), 0);
Z = texelFetch(UYVYsampler, clamp(tx + ivec2(1, 0), tmin, tmax), 0);
}
void main(void)
{
vec2 tc = gl_TexCoord[0].st;
float alpha = 0.7;
vec4 macro, macro_u, macro_r, macro_ur;
vec4 pixel, pixel_r, pixel_u, pixel_ur;
textureGatherYUV(UYVYtex, tc, macro, macro_u, macro_ur, macro_r);
vec2 off = fract(tc * textureSize(UYVYtex, 0));
if (off.x > 0.5)
{
pixel = rec709YCbCr2rgba(macro.a, macro.b, macro.r, alpha);
pixel_r = rec709YCbCr2rgba(macro_r.g, macro_r.b, macro_r.r, alpha);
pixel_u = rec709YCbCr2rgba(macro_u.a, macro_u.b, macro_u.r, alpha);
pixel_ur = rec709YCbCr2rgba(macro_ur.g, macro_ur.b, macro_ur.r, alpha);
}
else
{
pixel = rec709YCbCr2rgba(macro.g, macro.b, macro.r, alpha);
pixel_r = rec709YCbCr2rgba(macro.a, macro.b, macro.r, alpha);
pixel_u = rec709YCbCr2rgba(macro_u.g, macro_u.b, macro_u.r, alpha);
pixel_ur = rec709YCbCr2rgba(macro_u.a, macro_u.b, macro_u.r, alpha);
}
gl_FragColor = bilinear(pixel, pixel_u, pixel_ur, pixel_r, off);
}

View File

@@ -0,0 +1,79 @@
// Source-of-truth shader in Slang.
// The current OpenGL sample still runs the checked-in GLSL fallback because it uses
// legacy fixed-function texture coordinates in its fragment stage.
struct FragmentInput
{
float4 position : SV_Position;
float2 texCoord : TEXCOORD0;
};
Texture2D<float4> UYVYtex;
float4 rec709YCbCr2rgba(float Y, float Cb, float Cr, float a)
{
Y = (Y * 256.0 - 16.0) / 219.0;
Cb = (Cb * 256.0 - 16.0) / 224.0 - 0.5;
Cr = (Cr * 256.0 - 16.0) / 224.0 - 0.5;
float r = Y + 1.5748 * Cr;
float g = Y - 0.1873 * Cb - 0.4681 * Cr;
float b = Y + 1.8556 * Cb;
return float4(r, g, b, a);
}
float4 bilinear(float4 W, float4 X, float4 Y, float4 Z, float2 weight)
{
float4 m0 = lerp(W, Z, weight.x);
float4 m1 = lerp(X, Y, weight.x);
return lerp(m0, m1, weight.y);
}
void textureGatherYUV(Texture2D<float4> textureSampler, float2 tc, out float4 W, out float4 X, out float4 Y, out float4 Z)
{
uint width = 0;
uint height = 0;
textureSampler.GetDimensions(width, height);
int2 tx = int2(tc * float2(width, height));
int2 tmin = int2(0, 0);
int2 tmax = int2(int(width), int(height)) - int2(1, 1);
W = textureSampler.Load(int3(tx, 0));
X = textureSampler.Load(int3(clamp(tx + int2(0, 1), tmin, tmax), 0));
Y = textureSampler.Load(int3(clamp(tx + int2(1, 1), tmin, tmax), 0));
Z = textureSampler.Load(int3(clamp(tx + int2(1, 0), tmin, tmax), 0));
}
[shader("fragment")]
float4 fragmentMain(FragmentInput input) : SV_Target
{
float2 tc = input.texCoord;
float alpha = 0.7;
float4 macro, macroU, macroR, macroUR;
float4 pixel, pixelR, pixelU, pixelUR;
textureGatherYUV(UYVYtex, tc, macro, macroU, macroUR, macroR);
uint width = 0;
uint height = 0;
UYVYtex.GetDimensions(width, height);
float2 off = frac(tc * float2(width, height));
if (off.x > 0.5)
{
pixel = rec709YCbCr2rgba(macro.a, macro.b, macro.r, alpha);
pixelR = rec709YCbCr2rgba(macroR.g, macroR.b, macroR.r, alpha);
pixelU = rec709YCbCr2rgba(macroU.a, macroU.b, macroU.r, alpha);
pixelUR = rec709YCbCr2rgba(macroUR.g, macroUR.b, macroUR.r, alpha);
}
else
{
pixel = rec709YCbCr2rgba(macro.g, macro.b, macro.r, alpha);
pixelR = rec709YCbCr2rgba(macro.a, macro.b, macro.r, alpha);
pixelU = rec709YCbCr2rgba(macroU.g, macroU.b, macroU.r, alpha);
pixelUR = rec709YCbCr2rgba(macroU.a, macroU.b, macroU.r, alpha);
}
return bilinear(pixel, pixelU, pixelUR, pixelR, off);
}