Working
This commit is contained in:
@@ -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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user