diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.rc b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.rc index dd71b71..9b4097f 100644 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.rc +++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.rc @@ -74,7 +74,7 @@ END STRINGTABLE BEGIN - IDS_APP_TITLE "LoopThroughWithOpenGLCompositing" + IDS_APP_TITLE "Video Shader Toys" IDC_OPENGLOUTPUT "OPENGLOUTPUT" END diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp index aac3f62..056b0df 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -61,7 +62,6 @@ constexpr GLuint kDecodedVideoTextureUnit = 1; constexpr GLuint kSourceHistoryTextureUnitBase = 2; constexpr GLuint kPackedVideoTextureUnit = 2; constexpr GLuint kGlobalParamsBindingPoint = 0; -const char* kDisplayModeName = "1080p59.94"; const char* kVertexShaderSource = "#version 430 core\n" "out vec2 vTexCoord;\n" @@ -106,6 +106,91 @@ void CopyErrorMessage(const std::string& message, int errorMessageSize, char* er strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE); } +std::string NormalizeModeToken(const std::string& value) +{ + std::string normalized; + for (unsigned char ch : value) + { + if (std::isalnum(ch)) + normalized.push_back(static_cast(std::tolower(ch))); + } + return normalized; +} + +bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName) +{ + const std::string formatToken = NormalizeModeToken(videoFormat); + const std::string frameToken = NormalizeModeToken(frameRate); + const std::string combinedToken = formatToken + frameToken; + + struct ModeOption + { + const char* token; + BMDDisplayMode mode; + const char* displayName; + }; + + static const ModeOption options[] = + { + { "720p50", bmdModeHD720p50, "720p50" }, + { "hd720p50", bmdModeHD720p50, "720p50" }, + { "720p5994", bmdModeHD720p5994, "720p59.94" }, + { "hd720p5994", bmdModeHD720p5994, "720p59.94" }, + { "720p60", bmdModeHD720p60, "720p60" }, + { "hd720p60", bmdModeHD720p60, "720p60" }, + { "1080i50", bmdModeHD1080i50, "1080i50" }, + { "hd1080i50", bmdModeHD1080i50, "1080i50" }, + { "1080i5994", bmdModeHD1080i5994, "1080i59.94" }, + { "hd1080i5994", bmdModeHD1080i5994, "1080i59.94" }, + { "1080i60", bmdModeHD1080i6000, "1080i60" }, + { "hd1080i60", bmdModeHD1080i6000, "1080i60" }, + { "1080p2398", bmdModeHD1080p2398, "1080p23.98" }, + { "hd1080p2398", bmdModeHD1080p2398, "1080p23.98" }, + { "1080p24", bmdModeHD1080p24, "1080p24" }, + { "hd1080p24", bmdModeHD1080p24, "1080p24" }, + { "1080p25", bmdModeHD1080p25, "1080p25" }, + { "hd1080p25", bmdModeHD1080p25, "1080p25" }, + { "1080p2997", bmdModeHD1080p2997, "1080p29.97" }, + { "hd1080p2997", bmdModeHD1080p2997, "1080p29.97" }, + { "1080p30", bmdModeHD1080p30, "1080p30" }, + { "hd1080p30", bmdModeHD1080p30, "1080p30" }, + { "1080p50", bmdModeHD1080p50, "1080p50" }, + { "hd1080p50", bmdModeHD1080p50, "1080p50" }, + { "1080p5994", bmdModeHD1080p5994, "1080p59.94" }, + { "hd1080p5994", bmdModeHD1080p5994, "1080p59.94" }, + { "1080p60", bmdModeHD1080p6000, "1080p60" }, + { "hd1080p60", bmdModeHD1080p6000, "1080p60" }, + { "2160p2398", bmdMode4K2160p2398, "2160p23.98" }, + { "4k2160p2398", bmdMode4K2160p2398, "2160p23.98" }, + { "2160p24", bmdMode4K2160p24, "2160p24" }, + { "4k2160p24", bmdMode4K2160p24, "2160p24" }, + { "2160p25", bmdMode4K2160p25, "2160p25" }, + { "4k2160p25", bmdMode4K2160p25, "2160p25" }, + { "2160p2997", bmdMode4K2160p2997, "2160p29.97" }, + { "4k2160p2997", bmdMode4K2160p2997, "2160p29.97" }, + { "2160p30", bmdMode4K2160p30, "2160p30" }, + { "4k2160p30", bmdMode4K2160p30, "2160p30" }, + { "2160p50", bmdMode4K2160p50, "2160p50" }, + { "4k2160p50", bmdMode4K2160p50, "2160p50" }, + { "2160p5994", bmdMode4K2160p5994, "2160p59.94" }, + { "4k2160p5994", bmdMode4K2160p5994, "2160p59.94" }, + { "2160p60", bmdMode4K2160p60, "2160p60" }, + { "4k2160p60", bmdMode4K2160p60, "2160p60" } + }; + + for (const ModeOption& option : options) + { + if (combinedToken == option.token || (frameToken.empty() && formatToken == option.token)) + { + displayMode = option.mode; + displayModeName = option.displayName; + return true; + } + } + + return false; +} + class ScopedGlShader { public: @@ -210,6 +295,7 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : mDLInput(NULL), mDLOutput(NULL), mDLKeyer(NULL), mPlayoutAllocator(NULL), mFrameWidth(0), mFrameHeight(0), + mDisplayModeName("1080p59.94"), mHasNoInputSource(true), mDeckLinkSupportsInternalKeying(false), mDeckLinkSupportsExternalKeying(false), @@ -340,9 +426,32 @@ bool OpenGLComposite::InitDeckLink() IDeckLinkDisplayModeIterator* pDLDisplayModeIterator = NULL; IDeckLinkDisplayMode* pDLDisplayMode = NULL; BMDDisplayMode displayMode = bmdModeHD1080p5994; // mode to use for capture and playout + std::string displayModeName = "1080p59.94"; int outputFrameRowBytes; HRESULT result; + if (mRuntimeHost && mRuntimeHost->GetRepoRoot().empty()) + { + std::string runtimeError; + if (!mRuntimeHost->Initialize(runtimeError)) + { + MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK); + return false; + } + } + + if (mRuntimeHost) + { + if (!ResolveConfiguredDisplayMode(mRuntimeHost->GetVideoFormat(), mRuntimeHost->GetFrameRate(), displayMode, displayModeName)) + { + const std::string error = "Unsupported DeckLink video format/frameRate in config/runtime-host.json: " + + mRuntimeHost->GetVideoFormat() + " / " + mRuntimeHost->GetFrameRate(); + MessageBoxA(NULL, error.c_str(), "DeckLink mode configuration error", MB_OK); + return false; + } + } + mDisplayModeName = displayModeName; + result = CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&pDLIterator); if (FAILED(result)) { @@ -444,7 +553,8 @@ bool OpenGLComposite::InitDeckLink() if (pDLDisplayMode == NULL) { - MessageBox(NULL, _T("Cannot get specified BMDDisplayMode."), _T("DeckLink error."), MB_OK); + const std::string error = "Cannot get specified BMDDisplayMode for configured mode: " + displayModeName; + MessageBoxA(NULL, error.c_str(), "DeckLink error.", MB_OK); goto error; } @@ -697,7 +807,7 @@ bool OpenGLComposite::InitOpenGLState() return false; std::string runtimeError; - if (!mRuntimeHost->Initialize(runtimeError)) + if (mRuntimeHost->GetRepoRoot().empty() && !mRuntimeHost->Initialize(runtimeError)) { MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK); return false; @@ -867,7 +977,7 @@ void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bo { mHasNoInputSource = hasNoInputSource; if (mRuntimeHost) - mRuntimeHost->SetSignalStatus(!hasNoInputSource, mFrameWidth, mFrameHeight, kDisplayModeName); + mRuntimeHost->SetSignalStatus(!hasNoInputSource, mFrameWidth, mFrameHeight, mDisplayModeName); if (mHasNoInputSource) return; // don't transfer texture when there's no input diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h index f800149..664119c 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h @@ -118,6 +118,7 @@ private: unsigned mTotalPlayoutFrames; unsigned mFrameWidth; unsigned mFrameHeight; + std::string mDisplayModeName; bool mHasNoInputSource; std::string mDeckLinkOutputModelName; bool mDeckLinkSupportsInternalKeying; diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp index c0fb0a3..1f3e663 100644 --- a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp @@ -1118,6 +1118,22 @@ bool RuntimeHost::LoadConfig(std::string& error) } if (const JsonValue* enableExternalKeyingValue = configJson.find("enableExternalKeying")) mConfig.enableExternalKeying = enableExternalKeyingValue->asBoolean(mConfig.enableExternalKeying); + if (const JsonValue* videoFormatValue = configJson.find("videoFormat")) + { + if (videoFormatValue->isString() && !videoFormatValue->asString().empty()) + mConfig.videoFormat = videoFormatValue->asString(); + } + if (const JsonValue* frameRateValue = configJson.find("frameRate")) + { + if (frameRateValue->isString() && !frameRateValue->asString().empty()) + mConfig.frameRate = frameRateValue->asString(); + else if (frameRateValue->isNumber()) + { + std::ostringstream stream; + stream << frameRateValue->asNumber(); + mConfig.frameRate = stream.str(); + } + } mAutoReloadEnabled = mConfig.autoReload; return true; @@ -1398,6 +1414,8 @@ JsonValue RuntimeHost::BuildStateValue() const app.set("autoReload", JsonValue(mAutoReloadEnabled)); app.set("maxTemporalHistoryFrames", JsonValue(static_cast(mConfig.maxTemporalHistoryFrames))); app.set("enableExternalKeying", JsonValue(mConfig.enableExternalKeying)); + app.set("videoFormat", JsonValue(mConfig.videoFormat)); + app.set("frameRate", JsonValue(mConfig.frameRate)); root.set("app", app); JsonValue runtime = JsonValue::MakeObject(); diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h index d257a19..c8a7c94 100644 --- a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h +++ b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h @@ -49,6 +49,8 @@ public: unsigned short GetServerPort() const { return mServerPort; } unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; } bool ExternalKeyingEnabled() const { return mConfig.enableExternalKeying; } + const std::string& GetVideoFormat() const { return mConfig.videoFormat; } + const std::string& GetFrameRate() const { return mConfig.frameRate; } void SetServerPort(unsigned short port); bool AutoReloadEnabled() const { return mAutoReloadEnabled; } @@ -60,6 +62,8 @@ private: bool autoReload = true; unsigned maxTemporalHistoryFrames = 4; bool enableExternalKeying = false; + std::string videoFormat = "1080p"; + std::string frameRate = "59.94"; }; struct DeckLinkOutputStatus diff --git a/config/runtime-host.json b/config/runtime-host.json index 6f1634b..7a30fd5 100644 --- a/config/runtime-host.json +++ b/config/runtime-host.json @@ -1,6 +1,8 @@ { "shaderLibrary": "shaders", "serverPort": 8080, + "videoFormat": "1080p", + "frameRate": "59.94", "autoReload": true, "maxTemporalHistoryFrames": 12, "enableExternalKeying": true