Organisation
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m42s
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
Aiden
2026-05-11 19:31:06 +10:00
parent d332dceb5b
commit a530325fa1
18 changed files with 29 additions and 25 deletions

View File

@@ -0,0 +1,406 @@
#include "DeckLinkDisplayMode.h"
#include "OpenGLComposite.h"
#include "GLExtensions.h"
#include "PngScreenshotWriter.h"
#include "RenderEngine.h"
#include "RuntimeCoordinator.h"
#include "RuntimeEventDispatcher.h"
#include "RuntimeServiceLiveBridge.h"
#include "RuntimeServices.h"
#include "RuntimeSnapshotProvider.h"
#include "RuntimeStore.h"
#include "RuntimeUpdateController.h"
#include "ShaderBuildQueue.h"
#include "VideoBackend.h"
#include <chrono>
#include <ctime>
#include <filesystem>
#include <iomanip>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC)
{
mRuntimeStore = std::make_unique<RuntimeStore>();
mRuntimeEventDispatcher = std::make_unique<RuntimeEventDispatcher>();
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(mRuntimeStore->GetRenderSnapshotBuilder(), *mRuntimeEventDispatcher);
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore, *mRuntimeEventDispatcher);
mRenderEngine = std::make_unique<RenderEngine>(
*mRuntimeSnapshotProvider,
mRuntimeStore->GetHealthTelemetry(),
hGLDC,
hGLRC,
[this]() { renderEffect(); },
[]() {},
[this]() { paintGL(false); });
mVideoBackend = std::make_unique<VideoBackend>(*mRenderEngine, mRuntimeStore->GetHealthTelemetry(), *mRuntimeEventDispatcher);
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeSnapshotProvider, *mRuntimeEventDispatcher);
mRuntimeServices = std::make_unique<RuntimeServices>(*mRuntimeEventDispatcher);
mRuntimeUpdateController = std::make_unique<RuntimeUpdateController>(
*mRuntimeStore,
*mRuntimeCoordinator,
*mRuntimeEventDispatcher,
*mRuntimeServices,
*mRenderEngine,
*mShaderBuildQueue,
*mVideoBackend);
}
OpenGLComposite::~OpenGLComposite()
{
if (mRuntimeServices)
mRuntimeServices->Stop();
if (mShaderBuildQueue)
mShaderBuildQueue->Stop();
if (mVideoBackend)
mVideoBackend->ReleaseResources();
}
bool OpenGLComposite::InitDeckLink()
{
return InitVideoIO();
}
bool OpenGLComposite::InitVideoIO()
{
VideoFormatSelection videoModes;
std::string initFailureReason;
if (mRuntimeStore && mRuntimeStore->GetRuntimeRepositoryRoot().empty())
{
std::string runtimeError;
if (!mRuntimeStore->InitializeStore(runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
return false;
}
}
if (mRuntimeStore)
{
if (!ResolveConfiguredVideoFormats(
mRuntimeStore->GetConfiguredInputVideoFormat(),
mRuntimeStore->GetConfiguredInputFrameRate(),
mRuntimeStore->GetConfiguredOutputVideoFormat(),
mRuntimeStore->GetConfiguredOutputFrameRate(),
videoModes,
initFailureReason))
{
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink mode configuration error", MB_OK);
return false;
}
}
if (!mVideoBackend->DiscoverDevicesAndModes(videoModes, initFailureReason))
{
const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application."
? "This application requires the DeckLink drivers installed."
: "DeckLink initialization failed";
MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR);
return false;
}
const bool outputAlphaRequired = mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured();
if (!mVideoBackend->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason))
goto error;
if (! CheckOpenGLExtensions())
{
initFailureReason = "OpenGL extension checks failed.";
goto error;
}
if (! InitOpenGLState())
{
initFailureReason = "OpenGL state initialization failed.";
goto error;
}
mVideoBackend->PublishStatus(
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
mVideoBackend->OutputModelName().empty()
? "DeckLink output device selected."
: ("Selected output device: " + mVideoBackend->OutputModelName()));
// Resize window to match output video frame, but scale large formats down by half for viewing.
if (mVideoBackend->OutputFrameWidth() < 1920)
resizeWindow(mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight());
else
resizeWindow(mVideoBackend->OutputFrameWidth() / 2, mVideoBackend->OutputFrameHeight() / 2);
if (!mVideoBackend->ConfigureInput(videoModes.input, initFailureReason))
{
goto error;
}
if (!mVideoBackend->HasInputDevice())
mVideoBackend->ReportNoInputDeviceSignalStatus();
if (!mVideoBackend->ConfigureOutput(videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason))
{
goto error;
}
mVideoBackend->PublishStatus(
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
mVideoBackend->StatusMessage());
return true;
error:
if (!initFailureReason.empty())
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR);
mVideoBackend->ReleaseResources();
return false;
}
void OpenGLComposite::paintGL(bool force)
{
if (!force)
{
if (IsIconic(hGLWnd))
return;
}
const unsigned previewFps = mRuntimeStore ? mRuntimeStore->GetConfiguredPreviewFps() : 30u;
if (!mRenderEngine->TryPresentPreview(force, previewFps, mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight()))
{
ValidateRect(hGLWnd, NULL);
return;
}
ValidateRect(hGLWnd, NULL);
}
void OpenGLComposite::resizeGL(WORD width, WORD height)
{
// We don't set the project or model matrices here since the window data is copied directly from
// an off-screen FBO in paintGL(). Just save the width and height for use in paintGL().
mRenderEngine->ResizeView(width, height);
}
void OpenGLComposite::resizeWindow(int width, int height)
{
RECT r;
if (GetWindowRect(hGLWnd, &r))
{
SetWindowPos(hGLWnd, HWND_TOP, r.left, r.top, r.left + width, r.top + height, 0);
}
}
bool OpenGLComposite::InitOpenGLState()
{
if (! ResolveGLExtensions())
return false;
std::string runtimeError;
if (mRuntimeStore->GetRuntimeRepositoryRoot().empty() && !mRuntimeStore->InitializeStore(runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
return false;
}
if (!mRuntimeServices->Start(*this, *mRuntimeStore, runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime control services failed to start", MB_OK);
return false;
}
// Prepare the runtime shader program generated from the active shader package.
char compilerErrorMessage[1024];
if (!mRenderEngine->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK);
return false;
}
if (!mRenderEngine->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL output pack shader failed to load or compile", MB_OK);
return false;
}
std::string rendererError;
if (!mRenderEngine->InitializeResources(
mVideoBackend->InputFrameWidth(),
mVideoBackend->InputFrameHeight(),
mVideoBackend->CaptureTextureWidth(),
mVideoBackend->OutputFrameWidth(),
mVideoBackend->OutputFrameHeight(),
mVideoBackend->OutputPackTextureWidth(),
rendererError))
{
MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK);
return false;
}
if (!mRenderEngine->CompileLayerPrograms(mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK);
return false;
}
mRuntimeStore->SetCompileStatus(true, "Shader layers compiled successfully.");
mRenderEngine->ResetTemporalHistoryState();
mRenderEngine->ResetShaderFeedbackState();
mRuntimeUpdateController->BroadcastRuntimeState();
mRuntimeServices->BeginPolling(*mRuntimeCoordinator);
return true;
}
bool OpenGLComposite::Start()
{
if (!mRenderEngine->StartRenderThread())
return false;
if (mVideoBackend->Start())
return true;
mRenderEngine->StopRenderThread();
return false;
}
bool OpenGLComposite::Stop()
{
if (mRuntimeServices)
mRuntimeServices->Stop();
const bool wasExternalKeyingActive = mVideoBackend->ExternalKeyingActive();
mVideoBackend->Stop();
if (wasExternalKeyingActive)
mVideoBackend->PublishStatus(
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
"External keying has been disabled.");
if (mRenderEngine)
mRenderEngine->StopRenderThread();
return true;
}
bool OpenGLComposite::ReloadShader(bool preserveFeedbackState)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RequestShaderReload(preserveFeedbackState));
}
bool OpenGLComposite::RequestScreenshot(std::string& error)
{
if (!mRenderEngine || !mVideoBackend)
{
error = "The render engine is not ready.";
return false;
}
const unsigned width = mVideoBackend->OutputFrameWidth();
const unsigned height = mVideoBackend->OutputFrameHeight();
if (width == 0 || height == 0)
{
error = "The output frame size is not available.";
return false;
}
std::filesystem::path outputPath;
try
{
outputPath = BuildScreenshotPath();
std::filesystem::create_directories(outputPath.parent_path());
}
catch (const std::exception& exception)
{
error = exception.what();
return false;
}
if (!mRenderEngine->RequestScreenshotCapture(
width,
height,
[outputPath](unsigned captureWidth, unsigned captureHeight, std::vector<unsigned char> topDownPixels) {
try
{
WritePngFileAsync(outputPath, captureWidth, captureHeight, std::move(topDownPixels));
}
catch (const std::exception& exception)
{
OutputDebugStringA((std::string("Screenshot request failed: ") + exception.what() + "\n").c_str());
}
}))
{
error = "Screenshot capture request failed.";
return false;
}
return true;
}
void OpenGLComposite::renderEffect()
{
if (mRuntimeUpdateController)
mRuntimeUpdateController->ProcessRuntimeWork();
const RenderFrameInput frameInput = BuildRenderFrameInput();
RenderFrame(frameInput);
}
RenderFrameInput OpenGLComposite::BuildRenderFrameInput() const
{
RenderFrameInput frameInput;
frameInput.useCommittedLayerStates = mRuntimeCoordinator && mRuntimeCoordinator->UseCommittedLayerStates();
frameInput.hasInputSource = mVideoBackend->HasInputSource();
frameInput.renderWidth = mVideoBackend->InputFrameWidth();
frameInput.renderHeight = mVideoBackend->InputFrameHeight();
frameInput.inputFrameWidth = mVideoBackend->InputFrameWidth();
frameInput.inputFrameHeight = mVideoBackend->InputFrameHeight();
frameInput.captureTextureWidth = mVideoBackend->CaptureTextureWidth();
frameInput.inputPixelFormat = mVideoBackend->InputPixelFormat();
frameInput.historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0;
frameInput.oscSmoothing = mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0;
return frameInput;
}
void OpenGLComposite::RenderFrame(const RenderFrameInput& frameInput)
{
RenderFrameState frameState;
if (mRuntimeServices)
{
RuntimeServiceLiveBridge::PrepareLiveRenderFrameState(
*mRuntimeServices,
*mRenderEngine,
frameInput,
frameState);
}
else
{
mRenderEngine->ResolveRenderFrameState(frameInput, nullptr, frameState);
}
mRenderEngine->RenderPreparedFrame(frameState);
}
std::filesystem::path OpenGLComposite::BuildScreenshotPath() const
{
const std::filesystem::path root = mRuntimeStore && !mRuntimeStore->GetRuntimeDataRoot().empty()
? mRuntimeStore->GetRuntimeDataRoot()
: std::filesystem::current_path();
const auto now = std::chrono::system_clock::now();
const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
const std::time_t nowTime = std::chrono::system_clock::to_time_t(now);
std::tm localTime = {};
localtime_s(&localTime, &nowTime);
std::ostringstream filename;
filename << "video-shader-toys-"
<< std::put_time(&localTime, "%Y%m%d-%H%M%S")
<< "-" << std::setw(3) << std::setfill('0') << milliseconds.count()
<< ".png";
return root / "screenshots" / filename.str();
}
bool OpenGLComposite::CheckOpenGLExtensions()
{
return true;
}

View File

@@ -0,0 +1,93 @@
#ifndef __OPENGL_COMPOSITE_H__
#define __OPENGL_COMPOSITE_H__
#include <windows.h>
#include <process.h>
#include <tchar.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include <objbase.h>
#include <atlbase.h>
#include <comutil.h>
#include "GLExtensions.h"
#include "RenderFrameState.h"
#include <functional>
#include <filesystem>
#include <memory>
#include <string>
#include <vector>
class RenderEngine;
class RuntimeCoordinator;
class RuntimeEventDispatcher;
class RuntimeSnapshotProvider;
class RuntimeServices;
class RuntimeStore;
class RuntimeUpdateController;
class ShaderBuildQueue;
class VideoBackend;
class OpenGLComposite
{
public:
OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC);
~OpenGLComposite();
bool InitDeckLink();
bool InitVideoIO();
bool Start();
bool Stop();
bool ReloadShader(bool preserveFeedbackState = false);
std::string GetRuntimeStateJson() const;
bool AddLayer(const std::string& shaderId, std::string& error);
bool RemoveLayer(const std::string& layerId, std::string& error);
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
bool SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error);
bool SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error);
bool UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error);
bool UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error);
bool ResetLayerParameters(const std::string& layerId, std::string& error);
bool SaveStackPreset(const std::string& presetName, std::string& error);
bool LoadStackPreset(const std::string& presetName, std::string& error);
bool RequestScreenshot(std::string& error);
unsigned short GetControlServerPort() const;
unsigned short GetOscPort() const;
std::string GetOscBindAddress() const;
std::string GetControlUrl() const;
std::string GetDocsUrl() const;
std::string GetOscAddress() const;
void resizeGL(WORD width, WORD height);
void paintGL(bool force = false);
private:
void resizeWindow(int width, int height);
bool CheckOpenGLExtensions();
HWND hGLWnd;
HDC hGLDC;
HGLRC hGLRC;
std::unique_ptr<RuntimeStore> mRuntimeStore;
std::unique_ptr<RuntimeCoordinator> mRuntimeCoordinator;
std::unique_ptr<RuntimeSnapshotProvider> mRuntimeSnapshotProvider;
std::unique_ptr<RuntimeEventDispatcher> mRuntimeEventDispatcher;
std::unique_ptr<RenderEngine> mRenderEngine;
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
std::unique_ptr<RuntimeServices> mRuntimeServices;
std::unique_ptr<RuntimeUpdateController> mRuntimeUpdateController;
std::unique_ptr<VideoBackend> mVideoBackend;
bool InitOpenGLState();
void renderEffect();
RenderFrameInput BuildRenderFrameInput() const;
void RenderFrame(const RenderFrameInput& frameInput);
std::filesystem::path BuildScreenshotPath() const;
};
#endif // __OPENGL_COMPOSITE_H__

View File

@@ -0,0 +1,126 @@
#include "OpenGLComposite.h"
#include "RuntimeCoordinator.h"
#include "RuntimeJson.h"
#include "RuntimeServices.h"
#include "RuntimeStore.h"
#include "RuntimeUpdateController.h"
std::string OpenGLComposite::GetRuntimeStateJson() const
{
return mRuntimeStore ? mRuntimeStore->BuildPersistentStateJson() : "{}";
}
unsigned short OpenGLComposite::GetControlServerPort() const
{
return mRuntimeStore ? mRuntimeStore->GetConfiguredControlServerPort() : 0;
}
unsigned short OpenGLComposite::GetOscPort() const
{
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscPort() : 0;
}
std::string OpenGLComposite::GetOscBindAddress() const
{
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscBindAddress() : "127.0.0.1";
}
std::string OpenGLComposite::GetControlUrl() const
{
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/";
}
std::string OpenGLComposite::GetDocsUrl() const
{
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/docs";
}
std::string OpenGLComposite::GetOscAddress() const
{
return "udp://" + GetOscBindAddress() + ":" + std::to_string(GetOscPort()) + " /VideoShaderToys/{Layer}/{Parameter}";
}
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->AddLayer(shaderId), &error);
}
bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RemoveLayer(layerId), &error);
}
bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayer(layerId, direction), &error);
}
bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayerToIndex(layerId, targetIndex), &error);
}
bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerBypass(layerId, bypassed), &error);
}
bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerShader(layerId, shaderId), &error);
}
bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error)
{
JsonValue parsedValue;
if (!ParseJson(valueJson, parsedValue, error))
return false;
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameter(layerId, parameterId, parsedValue), &error);
}
bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
{
JsonValue parsedValue;
if (!ParseJson(valueJson, parsedValue, error))
return false;
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue), &error);
}
bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->ResetLayerParameters(layerId), &error);
}
bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SaveStackPreset(presetName), &error);
}
bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->LoadStackPreset(presetName), &error);
}