This commit is contained in:
2026-05-02 16:40:21 +10:00
parent 8d01ea4a3c
commit 1a4c33b9dc
23 changed files with 3725 additions and 401 deletions

View File

@@ -38,12 +38,13 @@
** -LICENSE-END-
*/
#include "ControlServer.h"
#include "OpenGLComposite.h"
#include "GLExtensions.h"
#include <filesystem>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
@@ -53,12 +54,11 @@ DEFINE_GUID(IID_PinnedMemoryAllocator,
namespace
{
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";
constexpr GLuint kVideoTextureUnit = 1;
constexpr GLuint kGlobalParamsBindingPoint = 0;
const char* kDisplayModeName = "1080p59.94";
const char* kVertexShaderSource =
"#version 130\n"
"#version 430 core\n"
"out vec2 vTexCoord;\n"
"void main()\n"
"{\n"
@@ -68,34 +68,6 @@ const char* kVertexShaderSource =
" vTexCoord = texCoords[gl_VertexID];\n"
"}\n";
std::string GetExecutableDirectory()
{
char modulePath[MAX_PATH] = {};
DWORD pathLength = GetModuleFileNameA(NULL, modulePath, MAX_PATH);
if (pathLength == 0 || pathLength == MAX_PATH)
return std::string();
std::string path(modulePath, pathLength);
std::string::size_type slashIndex = path.find_last_of("\\/");
if (slashIndex == std::string::npos)
return std::string();
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)
@@ -104,178 +76,47 @@ void CopyErrorMessage(const std::string& message, int errorMessageSize, char* er
strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
}
bool LoadTextFile(const std::string& path, std::string& contents, std::string& error)
std::size_t AlignStd140(std::size_t offset, std::size_t alignment)
{
std::ifstream input(path.c_str(), std::ios::binary);
if (!input)
{
error = "Could not open fragment shader file: " + path;
return false;
}
std::ostringstream buffer;
buffer << input.rdbuf();
contents = buffer.str();
if (contents.empty())
{
error = "Fragment shader file is empty: " + path;
return false;
}
return true;
const std::size_t mask = alignment - 1;
return (offset + mask) & ~mask;
}
std::filesystem::path FindRepoRoot()
template <typename TValue>
void AppendStd140Value(std::vector<unsigned char>& buffer, std::size_t alignment, const TValue& value)
{
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();
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));
}
bool FindSlangCompiler(const std::filesystem::path& repoRoot, std::filesystem::path& slangCompilerPath, std::string& error)
void AppendStd140Float(std::vector<unsigned char>& buffer, float value)
{
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;
AppendStd140Value(buffer, 4, value);
}
bool RunProcessAndWait(const std::string& commandLine, std::string& error)
void AppendStd140Int(std::vector<unsigned char>& buffer, int value)
{
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;
AppendStd140Value(buffer, 4, value);
}
bool PatchGeneratedSlangGLSL(std::string& shaderText, std::string& error)
void AppendStd140Vec2(std::vector<unsigned char>& buffer, float x, float y)
{
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;
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));
}
bool BuildFragmentShaderSourceFromSlang(std::string& shaderSource, std::string& error)
void AppendStd140Vec4(std::vector<unsigned char>& buffer, float x, float y, float z, float w)
{
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;
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));
}
}
@@ -289,14 +130,16 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
mFastTransferExtensionAvailable(false),
mCaptureTexture(0),
mFBOTexture(0),
mFullscreenVAO(0),
mGlobalParamsUBO(0),
mProgram(0),
mVertexShader(0),
mFragmentShader(0),
mUYVYtexUniform(-1),
mRotateAngle(0.0f),
mRotateAngleRate(0.0f)
mGlobalParamsUBOSize(0)
{
InitializeCriticalSection(&pMutex);
mRuntimeHost = std::make_unique<RuntimeHost>();
mControlServer = std::make_unique<ControlServer>();
}
OpenGLComposite::~OpenGLComposite()
@@ -348,7 +191,26 @@ OpenGLComposite::~OpenGLComposite()
mPlayoutAllocator = NULL;
}
if (mFullscreenVAO != 0)
glDeleteVertexArrays(1, &mFullscreenVAO);
if (mGlobalParamsUBO != 0)
glDeleteBuffers(1, &mGlobalParamsUBO);
if (mIdFrameBuf != 0)
glDeleteFramebuffers(1, &mIdFrameBuf);
if (mIdColorBuf != 0)
glDeleteRenderbuffers(1, &mIdColorBuf);
if (mIdDepthBuf != 0)
glDeleteRenderbuffers(1, &mIdDepthBuf);
if (mCaptureTexture != 0)
glDeleteTextures(1, &mCaptureTexture);
if (mFBOTexture != 0)
glDeleteTextures(1, &mFBOTexture);
if (mUnpinnedTextureBuffer != 0)
glDeleteBuffers(1, &mUnpinnedTextureBuffer);
destroyShaderProgram();
if (mControlServer)
mControlServer->Stop();
DeleteCriticalSection(&pMutex);
}
@@ -564,10 +426,10 @@ void OpenGLComposite::paintGL()
// we already have the rendered frame to be played out sitting in the GPU in the mIdFrameBuf frame buffer.
// Simply copy the off-screen frame buffer to on-screen frame buffer, scaling to the viewing window size.
glBindFramebufferEXT(GL_READ_FRAMEBUFFER, mIdFrameBuf);
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, mIdFrameBuf);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glViewport(0, 0, mViewWidth, mViewHeight);
glBlitFramebufferEXT(0, 0, mFrameWidth, mFrameHeight, 0, 0, mViewWidth, mViewHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBlitFramebuffer(0, 0, mFrameWidth, mFrameHeight, 0, 0, mViewWidth, mViewHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
SwapBuffers(hGLDC);
ValidateRect(hGLWnd, NULL);
@@ -595,7 +457,38 @@ bool OpenGLComposite::InitOpenGLState()
if (! ResolveGLExtensions())
return false;
// Prepare the runtime shader program generated from the Slang source file.
std::string runtimeError;
if (!mRuntimeHost->Initialize(runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
return false;
}
ControlServer::Callbacks callbacks;
callbacks.getStateJson = [this]() { return GetRuntimeStateJson(); };
callbacks.selectShader = [this](const std::string& shaderId, std::string& error) { return SelectShader(shaderId, error); };
callbacks.updateParameter = [this](const std::string& shaderId, const std::string& parameterId, const std::string& valueJson, std::string& error) {
return UpdateParameterJson(shaderId, parameterId, valueJson, error);
};
callbacks.setBypass = [this](bool bypassEnabled, std::string& error) { return SetBypassEnabled(bypassEnabled, error); };
callbacks.setMixAmount = [this](double mixAmount, std::string& error) { return SetMixAmount(mixAmount, error); };
callbacks.reloadShader = [this](std::string& error) {
if (!ReloadShader())
{
error = "Shader reload failed. See native app status for details.";
return false;
}
return true;
};
if (!mControlServer->Start(mRuntimeHost->GetUiRoot(), mRuntimeHost->GetServerPort(), callbacks, runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Local control server failed to start", MB_OK);
return false;
}
mRuntimeHost->SetServerPort(mControlServer->GetPort());
// Prepare the runtime shader program generated from the active shader package.
char compilerErrorMessage[1024];
if (! compileFragmentShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
@@ -612,54 +505,68 @@ bool OpenGLComposite::InitOpenGLState()
}
// Setup the texture which will hold the captured video frame pixels
glEnable(GL_TEXTURE_2D);
glGenTextures(1, &mCaptureTexture);
glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
// Parameters to control how texels are sampled from the texture
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Create texture with empty data, we will update it using glTexSubImage2D each frame.
// The captured video is YCbCr 4:2:2 packed into a UYVY macropixel. OpenGL has no YCbCr format
// so treat it as RGBA 4:4:4:4 by halving the width and using GL_RGBA internal format.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mFrameWidth/2, mFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, mFrameWidth/2, mFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
// Create Frame Buffer Object (FBO) to perform off-screen rendering of scene.
// This allows the render to be done on a framebuffer with width and height exactly matching the video format.
glGenFramebuffersEXT(1, &mIdFrameBuf);
glGenRenderbuffersEXT(1, &mIdColorBuf);
glGenRenderbuffersEXT(1, &mIdDepthBuf);
glGenFramebuffers(1, &mIdFrameBuf);
glGenRenderbuffers(1, &mIdColorBuf);
glGenRenderbuffers(1, &mIdDepthBuf);
glGenVertexArrays(1, &mFullscreenVAO);
glGenBuffers(1, &mGlobalParamsUBO);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mIdFrameBuf);
glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
// Texture for FBO
glGenTextures(1, &mFBOTexture);
glBindTexture(GL_TEXTURE_2D, mFBOTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mFrameWidth, mFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
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, mFrameWidth, mFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
// Attach a depth buffer
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, mIdDepthBuf);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, mFrameWidth, mFrameHeight);
glBindRenderbuffer(GL_RENDERBUFFER, mIdDepthBuf);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mFrameWidth, mFrameHeight);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, mIdDepthBuf);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, mIdDepthBuf);
// Attach the texture which stores the playback image
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, mFBOTexture, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFBOTexture, 0);
GLenum glStatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (glStatus != GL_FRAMEBUFFER_COMPLETE_EXT)
GLenum glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (glStatus != GL_FRAMEBUFFER_COMPLETE)
{
MessageBox(NULL, _T("Cannot initialize framebuffer."), _T("OpenGL initialization error."), MB_OK);
return false;
}
glBindTexture(GL_TEXTURE_2D, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindVertexArray(mFullscreenVAO);
glBindVertexArray(0);
glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsUBO);
glBufferData(GL_UNIFORM_BUFFER, 1024, NULL, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
broadcastRuntimeState();
return true;
}
@@ -669,6 +576,9 @@ bool OpenGLComposite::InitOpenGLState()
void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource)
{
mHasNoInputSource = hasNoInputSource;
if (mRuntimeHost)
mRuntimeHost->SetSignalStatus(!hasNoInputSource, mFrameWidth, mFrameHeight, kDisplayModeName);
if (mHasNoInputSource)
return; // don't transfer texture when there's no input
@@ -700,8 +610,6 @@ void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bo
}
else
{
glEnable(GL_TEXTURE_2D);
// Use a straightforward texture buffer
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mUnpinnedTextureBuffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, videoPixels, GL_DYNAMIC_DRAW);
@@ -712,7 +620,6 @@ void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bo
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glDisable(GL_TEXTURE_2D);
}
wglMakeCurrent( NULL, NULL );
@@ -738,8 +645,10 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame,
wglMakeCurrent( hGLDC, hGLRC );
// Draw the effect output to the off-screen framebuffer.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mIdFrameBuf);
glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
renderEffect();
if (mRuntimeHost)
mRuntimeHost->AdvanceFrame();
IDeckLinkVideoBuffer* outputVideoFrameBuffer;
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
@@ -838,6 +747,9 @@ bool OpenGLComposite::Start()
bool OpenGLComposite::Stop()
{
if (mControlServer)
mControlServer->Stop();
mDLInput->StopStreams();
mDLInput->DisableVideoInput();
@@ -855,12 +767,24 @@ bool OpenGLComposite::ReloadShader()
wglMakeCurrent(hGLDC, hGLRC);
bool success = compileFragmentShader(sizeof(compilerErrorMessage), compilerErrorMessage);
if (mRuntimeHost)
mRuntimeHost->ClearReloadRequest();
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&pMutex);
if (!success)
{
if (mRuntimeHost)
mRuntimeHost->SetCompileStatus(false, compilerErrorMessage);
MessageBoxA(NULL, compilerErrorMessage, "Slang shader reload failed", MB_OK);
}
else
{
if (mRuntimeHost)
mRuntimeHost->SetCompileStatus(true, "Shader compiled successfully.");
broadcastRuntimeState();
}
return success;
}
@@ -884,12 +808,12 @@ void OpenGLComposite::destroyShaderProgram()
glDeleteShader(mVertexShader);
mVertexShader = 0;
}
mUYVYtexUniform = -1;
}
void OpenGLComposite::renderEffect()
{
PollRuntimeChanges();
glViewport(0, 0, mFrameWidth, mFrameHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@@ -904,26 +828,30 @@ void OpenGLComposite::renderEffect()
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0 + kVideoTextureUnit);
glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
glBindVertexArray(mFullscreenVAO);
glUseProgram(mProgram);
if (mUYVYtexUniform >= 0)
glUniform1i(mUYVYtexUniform, 0);
if (mRuntimeHost)
{
const RuntimeRenderState state = mRuntimeHost->GetRenderState(mFrameWidth, mFrameHeight);
updateGlobalParamsBuffer(state);
}
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
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.
// Compile a fullscreen shader pass from the runtime Slang source into a core-profile
// GLSL program. The renderer owns the fullscreen pass and parameter UBO layout.
bool OpenGLComposite::compileFragmentShader(int errorMessageSize, char* errorMessage)
{
GLsizei errorBufferSize = 0;
@@ -933,8 +861,9 @@ bool OpenGLComposite::compileFragmentShader(int errorMessageSize, char* errorMes
std::string loadError;
const char* vertexSource = kVertexShaderSource;
if (!BuildFragmentShaderSourceFromSlang(fragmentShaderSource, loadError))
if (!mRuntimeHost->BuildActiveFragmentShaderSource(fragmentShaderSource, loadError))
{
mRuntimeHost->SetCompileStatus(false, loadError);
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
return false;
}
@@ -983,28 +912,182 @@ bool OpenGLComposite::compileFragmentShader(int errorMessageSize, char* errorMes
mProgram = newProgram;
mVertexShader = newVertexShader;
mFragmentShader = newFragmentShader;
mUYVYtexUniform = glGetUniformLocation(mProgram, "UYVYtex");
const RuntimeRenderState state = mRuntimeHost->GetRenderState(mFrameWidth, mFrameHeight);
if (!updateGlobalParamsBuffer(state))
{
CopyErrorMessage("Failed to allocate the runtime parameter UBO.", errorMessageSize, errorMessage);
destroyShaderProgram();
return false;
}
mRuntimeHost->SetCompileStatus(true, "Shader compiled successfully.");
mRuntimeHost->ClearReloadRequest();
return true;
}
bool OpenGLComposite::PollRuntimeChanges()
{
if (!mRuntimeHost)
return true;
bool registryChanged = false;
bool reloadRequested = false;
std::string runtimeError;
if (!mRuntimeHost->PollFileChanges(registryChanged, reloadRequested, runtimeError))
{
mRuntimeHost->SetCompileStatus(false, runtimeError);
broadcastRuntimeState();
return false;
}
if (registryChanged)
broadcastRuntimeState();
if (!reloadRequested)
return true;
char compilerErrorMessage[1024] = {};
if (!compileFragmentShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
mRuntimeHost->SetCompileStatus(false, compilerErrorMessage);
mRuntimeHost->ClearReloadRequest();
broadcastRuntimeState();
return false;
}
broadcastRuntimeState();
return true;
}
void OpenGLComposite::broadcastRuntimeState()
{
if (mControlServer)
mControlServer->BroadcastState();
}
bool OpenGLComposite::updateGlobalParamsBuffer(const RuntimeRenderState& state)
{
std::vector<unsigned char> buffer;
buffer.reserve(512);
AppendStd140Float(buffer, static_cast<float>(state.timeSeconds));
AppendStd140Vec2(buffer, static_cast<float>(state.inputWidth), static_cast<float>(state.inputHeight));
AppendStd140Vec2(buffer, static_cast<float>(state.outputWidth), static_cast<float>(state.outputHeight));
AppendStd140Float(buffer, static_cast<float>(state.frameCount));
AppendStd140Float(buffer, static_cast<float>(state.mixAmount));
AppendStd140Float(buffer, static_cast<float>(state.bypass));
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
{
auto valueIt = state.parameterValues.find(definition.id);
const ShaderParameterValue value = valueIt != state.parameterValues.end()
? valueIt->second
: ShaderParameterValue();
switch (definition.type)
{
case ShaderParameterType::Float:
AppendStd140Float(buffer, value.numberValues.empty() ? 0.0f : static_cast<float>(value.numberValues[0]));
break;
case ShaderParameterType::Vec2:
AppendStd140Vec2(buffer,
value.numberValues.size() > 0 ? static_cast<float>(value.numberValues[0]) : 0.0f,
value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : 0.0f);
break;
case ShaderParameterType::Color:
AppendStd140Vec4(buffer,
value.numberValues.size() > 0 ? static_cast<float>(value.numberValues[0]) : 1.0f,
value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : 1.0f,
value.numberValues.size() > 2 ? static_cast<float>(value.numberValues[2]) : 1.0f,
value.numberValues.size() > 3 ? static_cast<float>(value.numberValues[3]) : 1.0f);
break;
case ShaderParameterType::Boolean:
AppendStd140Int(buffer, value.booleanValue ? 1 : 0);
break;
case ShaderParameterType::Enum:
{
int selectedIndex = 0;
for (std::size_t optionIndex = 0; optionIndex < definition.enumOptions.size(); ++optionIndex)
{
if (definition.enumOptions[optionIndex].value == value.enumValue)
{
selectedIndex = static_cast<int>(optionIndex);
break;
}
}
AppendStd140Int(buffer, selectedIndex);
break;
}
}
}
buffer.resize(AlignStd140(buffer.size(), 16), 0);
glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsUBO);
if (mGlobalParamsUBOSize != static_cast<GLsizeiptr>(buffer.size()))
{
glBufferData(GL_UNIFORM_BUFFER, static_cast<GLsizeiptr>(buffer.size()), buffer.data(), GL_DYNAMIC_DRAW);
mGlobalParamsUBOSize = static_cast<GLsizeiptr>(buffer.size());
}
else
{
glBufferSubData(GL_UNIFORM_BUFFER, 0, static_cast<GLsizeiptr>(buffer.size()), buffer.data());
}
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
return true;
}
std::string OpenGLComposite::GetRuntimeStateJson() const
{
return mRuntimeHost ? mRuntimeHost->BuildStateJson() : "{}";
}
bool OpenGLComposite::SelectShader(const std::string& shaderId, std::string& error)
{
if (!mRuntimeHost->SelectShader(shaderId, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::UpdateParameterJson(const std::string& shaderId, const std::string& parameterId, const std::string& valueJson, std::string& error)
{
JsonValue parsedValue;
if (!ParseJson(valueJson, parsedValue, error))
return false;
if (!mRuntimeHost->UpdateParameter(shaderId, parameterId, parsedValue, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::SetBypassEnabled(bool bypassEnabled, std::string& error)
{
if (!mRuntimeHost->SetBypass(bypassEnabled, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::SetMixAmount(double mixAmount, std::string& error)
{
if (!mRuntimeHost->SetMixAmount(mixAmount, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::CheckOpenGLExtensions()
{
const GLubyte* strExt;
bool hasFBO;
// The GL_EXT_framebuffer_object extension is required but GL_AMD_pinned_memory is optional
strExt = glGetString (GL_EXTENSIONS);
hasFBO = strstr((char*)strExt, "GL_EXT_framebuffer_object") != NULL;
mFastTransferExtensionAvailable = VideoFrameTransfer::checkFastMemoryTransferAvailable();
if (!hasFBO)
{
MessageBox(NULL, _T("Required OpenGL extension \"GL_EXT_framebuffer_object\" is not available."), _T("OpenGL initialization error."), MB_OK);
return false;
}
if (!mFastTransferExtensionAvailable)
OutputDebugStringA("Fast memory transfer extension not available, using regular OpenGL transfer fallback instead\n");