From 8d01ea4a3c938f88eff0ffa38f850a9ecff0febe Mon Sep 17 00:00:00 2001 From: Aiden Date: Sat, 2 May 2026 15:49:45 +1000 Subject: [PATCH] Update --- .vscode/launch.json | 4 +- .vscode/tasks.json | 36 +- CMakeLists.txt | 13 +- .../GLExtensions.cpp | 6 + .../GLExtensions.h | 4 + .../LoopThroughWithOpenGLCompositing.cpp | 8 + .../LoopThroughWithOpenGLCompositing.vcxproj | 18 +- ...roughWithOpenGLCompositing.vcxproj.filters | 7 +- .../OpenGLComposite.cpp | 452 ++++++++++++------ .../OpenGLComposite.h | 5 + .../video_effect.frag | 61 --- .../video_effect.slang | 79 +++ 12 files changed, 454 insertions(+), 239 deletions(-) delete mode 100644 apps/LoopThroughWithOpenGLCompositing/video_effect.frag create mode 100644 apps/LoopThroughWithOpenGLCompositing/video_effect.slang diff --git a/.vscode/launch.json b/.vscode/launch.json index 911cdaf..675b62d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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" diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ddcdea2..d3d6a2f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -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" } diff --git a/CMakeLists.txt b/CMakeLists.txt index e1f2b90..cc72ae1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" "$/dvp.dll" - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${APP_DIR}/video_effect.frag" - "$/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}" ) diff --git a/apps/LoopThroughWithOpenGLCompositing/GLExtensions.cpp b/apps/LoopThroughWithOpenGLCompositing/GLExtensions.cpp index 7c783d7..ff4c9e2 100644 --- a/apps/LoopThroughWithOpenGLCompositing/GLExtensions.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/GLExtensions.cpp @@ -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 diff --git a/apps/LoopThroughWithOpenGLCompositing/GLExtensions.h b/apps/LoopThroughWithOpenGLCompositing/GLExtensions.h index d8020fb..3278bf3 100644 --- a/apps/LoopThroughWithOpenGLCompositing/GLExtensions.h +++ b/apps/LoopThroughWithOpenGLCompositing/GLExtensions.h @@ -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; diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp index 7aca22f..c45ed97 100644 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp @@ -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)); } diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj index d1e3a73..dfe345a 100644 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj +++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj @@ -96,6 +96,7 @@ Level3 EditAndContinue + stdcpp17 dvp.lib;opengl32.lib;Glu32.lib;%(AdditionalDependencies) @@ -105,8 +106,7 @@ MachineX86 - copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)" -copy /y "video_effect.frag" "$(TargetDir)" + copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)" @@ -122,6 +122,7 @@ copy /y "video_effect.frag" "$(TargetDir)" Level3 ProgramDatabase + stdcpp17 dvp.lib;opengl32.lib;Glu32.lib;%(AdditionalDependencies) @@ -131,8 +132,7 @@ copy /y "video_effect.frag" "$(TargetDir)" MachineX64 - copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)" -copy /y "video_effect.frag" "$(TargetDir)" + copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)" @@ -146,6 +146,7 @@ copy /y "video_effect.frag" "$(TargetDir)" Level3 ProgramDatabase + stdcpp17 dvp.lib;opengl32.lib;Glu32.lib;%(AdditionalDependencies) @@ -158,8 +159,7 @@ copy /y "video_effect.frag" "$(TargetDir)" Copy nececssary DLLs to target directory - copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)" -copy /y "video_effect.frag" "$(TargetDir)" + copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)" @@ -176,6 +176,7 @@ copy /y "video_effect.frag" "$(TargetDir)" Level3 ProgramDatabase + stdcpp17 dvp.lib;opengl32.lib;Glu32.lib;%(AdditionalDependencies) @@ -187,8 +188,7 @@ copy /y "video_effect.frag" "$(TargetDir)" MachineX64 - copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)" -copy /y "video_effect.frag" "$(TargetDir)" + copy /y "..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\Samples\NVIDIA_GPUDirect\bin\$(Platform)\dvp.dll" "$(TargetDir)" @@ -218,7 +218,7 @@ copy /y "video_effect.frag" "$(TargetDir)" - + diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters index aeb0f34..ebcaca8 100644 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters +++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters @@ -78,4 +78,9 @@ DeckLink API - \ No newline at end of file + + + Resource Files + + + diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp index da7cb67..b3fbed4 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp @@ -41,9 +41,11 @@ #include "OpenGLComposite.h" #include "GLExtensions.h" +#include #include #include #include +#include #include 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 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 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; } diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h index 5c28f08..33a5f8e 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h @@ -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(); }; //////////////////////////////////////////// diff --git a/apps/LoopThroughWithOpenGLCompositing/video_effect.frag b/apps/LoopThroughWithOpenGLCompositing/video_effect.frag deleted file mode 100644 index 36d1421..0000000 --- a/apps/LoopThroughWithOpenGLCompositing/video_effect.frag +++ /dev/null @@ -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); -} diff --git a/apps/LoopThroughWithOpenGLCompositing/video_effect.slang b/apps/LoopThroughWithOpenGLCompositing/video_effect.slang new file mode 100644 index 0000000..a1aed89 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/video_effect.slang @@ -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 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 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); +}