#include "DeckLinkDisplayMode.h" #include "DeckLinkSession.h" #include "OpenGLComposite.h" #include "GLExtensions.h" #include "GlRenderConstants.h" #include "OpenGLRenderPass.h" #include "OpenGLRenderPipeline.h" #include "OpenGLShaderPrograms.h" #include "OpenGLVideoIOBridge.h" #include "PngScreenshotWriter.h" #include "RuntimeParameterUtils.h" #include "RuntimeServices.h" #include "ShaderBuildQueue.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150); constexpr double kOscSmoothingReferenceFps = 60.0; constexpr double kOscSmoothingMaxStepSeconds = 0.25; std::string SimplifyOscControlKey(const std::string& text) { std::string simplified; for (unsigned char ch : text) { if (std::isalnum(ch)) simplified.push_back(static_cast(std::tolower(ch))); } return simplified; } bool MatchesOscControlKey(const std::string& candidate, const std::string& key) { return candidate == key || SimplifyOscControlKey(candidate) == SimplifyOscControlKey(key); } double ClampOscAlpha(double value) { return (std::max)(0.0, (std::min)(1.0, value)); } double ComputeTimeBasedOscAlpha(double smoothing, double deltaSeconds) { const double clampedSmoothing = ClampOscAlpha(smoothing); if (clampedSmoothing <= 0.0) return 0.0; if (clampedSmoothing >= 1.0) return 1.0; const double clampedDeltaSeconds = (std::max)(0.0, (std::min)(kOscSmoothingMaxStepSeconds, deltaSeconds)); if (clampedDeltaSeconds <= 0.0) return 0.0; const double frameScale = clampedDeltaSeconds * kOscSmoothingReferenceFps; return ClampOscAlpha(1.0 - std::pow(1.0 - clampedSmoothing, frameScale)); } JsonValue BuildOscCommitValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) { switch (definition.type) { case ShaderParameterType::Boolean: return JsonValue(value.booleanValue); case ShaderParameterType::Enum: return JsonValue(value.enumValue); case ShaderParameterType::Text: return JsonValue(value.textValue); case ShaderParameterType::Trigger: case ShaderParameterType::Float: return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front()); case ShaderParameterType::Vec2: case ShaderParameterType::Color: { JsonValue array = JsonValue::MakeArray(); for (double number : value.numberValues) array.pushBack(JsonValue(number)); return array; } } return JsonValue(); } } OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC), mVideoIO(std::make_unique()), mRenderer(std::make_unique()), mUseCommittedLayerStates(false), mScreenshotRequested(false) { InitializeCriticalSection(&pMutex); mRuntimeHost = std::make_unique(); mRuntimeStore = std::make_unique(*mRuntimeHost); mRuntimeSnapshotProvider = std::make_unique(*mRuntimeHost); mRenderPipeline = std::make_unique( *mRenderer, *mRuntimeHost, [this]() { renderEffect(); }, [this]() { ProcessScreenshotRequest(); }, [this]() { paintGL(false); }); mVideoIOBridge = std::make_unique( *mVideoIO, *mRenderer, *mRenderPipeline, *mRuntimeHost, pMutex, hGLDC, hGLRC); mRenderPass = std::make_unique(*mRenderer); mShaderPrograms = std::make_unique(*mRenderer, *mRuntimeHost, *mRuntimeSnapshotProvider); mShaderBuildQueue = std::make_unique(*mRuntimeSnapshotProvider); mRuntimeServices = std::make_unique(); } OpenGLComposite::~OpenGLComposite() { if (mRuntimeServices) mRuntimeServices->Stop(); if (mShaderBuildQueue) mShaderBuildQueue->Stop(); mVideoIO->ReleaseResources(); mRenderer->DestroyResources(); DeleteCriticalSection(&pMutex); } bool OpenGLComposite::InitDeckLink() { return InitVideoIO(); } bool OpenGLComposite::InitVideoIO() { VideoFormatSelection videoModes; std::string initFailureReason; if (mRuntimeStore && mRuntimeStore->GetRepoRoot().empty()) { std::string runtimeError; if (!mRuntimeStore->Initialize(runtimeError)) { MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK); return false; } } if (mRuntimeStore) { if (!ResolveConfiguredVideoFormats( mRuntimeStore->GetInputVideoFormat(), mRuntimeStore->GetInputFrameRate(), mRuntimeStore->GetOutputVideoFormat(), mRuntimeStore->GetOutputFrameRate(), videoModes, initFailureReason)) { MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink mode configuration error", MB_OK); return false; } } if (!mVideoIO->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->ExternalKeyingEnabled(); if (!mVideoIO->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason)) goto error; if (! CheckOpenGLExtensions()) { initFailureReason = "OpenGL extension checks failed."; goto error; } if (! InitOpenGLState()) { initFailureReason = "OpenGL state initialization failed."; goto error; } PublishVideoIOStatus(mVideoIO->OutputModelName().empty() ? "DeckLink output device selected." : ("Selected output device: " + mVideoIO->OutputModelName())); // Resize window to match output video frame, but scale large formats down by half for viewing. if (mVideoIO->OutputFrameWidth() < 1920) resizeWindow(mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight()); else resizeWindow(mVideoIO->OutputFrameWidth() / 2, mVideoIO->OutputFrameHeight() / 2); if (!mVideoIO->ConfigureInput([this](const VideoIOFrame& frame) { mVideoIOBridge->VideoFrameArrived(frame); }, videoModes.input, initFailureReason)) { goto error; } if (!mVideoIO->HasInputDevice() && mRuntimeHost) { mRuntimeHost->SetSignalStatus(false, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), mVideoIO->InputDisplayModeName()); } if (!mVideoIO->ConfigureOutput([this](const VideoIOCompletion& completion) { mVideoIOBridge->PlayoutFrameCompleted(completion); }, videoModes.output, mRuntimeStore && mRuntimeStore->ExternalKeyingEnabled(), initFailureReason)) { goto error; } PublishVideoIOStatus(mVideoIO->StatusMessage()); return true; error: if (!initFailureReason.empty()) MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR); mVideoIO->ReleaseResources(); return false; } void OpenGLComposite::paintGL(bool force) { if (!force) { if (IsIconic(hGLWnd)) return; const unsigned previewFps = mRuntimeStore ? mRuntimeStore->GetPreviewFps() : 30u; if (previewFps == 0) return; const auto now = std::chrono::steady_clock::now(); const auto minimumInterval = std::chrono::microseconds(1000000 / (previewFps == 0 ? 1u : previewFps)); if (mLastPreviewPresentTime != std::chrono::steady_clock::time_point() && now - mLastPreviewPresentTime < minimumInterval) { return; } } if (!TryEnterCriticalSection(&pMutex)) { ValidateRect(hGLWnd, NULL); return; } mRenderer->PresentToWindow(hGLDC, mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight()); mLastPreviewPresentTime = std::chrono::steady_clock::now(); ValidateRect(hGLWnd, NULL); LeaveCriticalSection(&pMutex); } 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(). mRenderer->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); } } void OpenGLComposite::PublishVideoIOStatus(const std::string& statusMessage) { if (!mRuntimeHost) return; if (!statusMessage.empty()) mVideoIO->SetStatusMessage(statusMessage); mRuntimeHost->SetVideoIOStatus( "decklink", mVideoIO->OutputModelName(), mVideoIO->SupportsInternalKeying(), mVideoIO->SupportsExternalKeying(), mVideoIO->KeyerInterfaceAvailable(), mRuntimeStore ? mRuntimeStore->ExternalKeyingEnabled() : false, mVideoIO->ExternalKeyingActive(), mVideoIO->StatusMessage()); } bool OpenGLComposite::InitOpenGLState() { if (! ResolveGLExtensions()) return false; std::string runtimeError; if (mRuntimeStore->GetRepoRoot().empty() && !mRuntimeStore->Initialize(runtimeError)) { MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK); return false; } if (!mRuntimeServices->Start(*this, *mRuntimeHost, 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 (!mShaderPrograms->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage)) { MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK); return false; } if (!mShaderPrograms->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage)) { MessageBoxA(NULL, compilerErrorMessage, "OpenGL output pack shader failed to load or compile", MB_OK); return false; } std::string rendererError; if (!mRenderer->InitializeResources( mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), mVideoIO->CaptureTextureWidth(), mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight(), mVideoIO->OutputPackTextureWidth(), rendererError)) { MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK); return false; } if (!mShaderPrograms->CompileLayerPrograms(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage)) { MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK); return false; } mCachedLayerRenderStates = mShaderPrograms->CommittedLayerStates(); mUseCommittedLayerStates = false; mShaderPrograms->ResetTemporalHistoryState(); mShaderPrograms->ResetShaderFeedbackState(); broadcastRuntimeState(); mRuntimeServices->BeginPolling(*mRuntimeHost); return true; } bool OpenGLComposite::Start() { return mVideoIO->Start(); } bool OpenGLComposite::Stop() { if (mRuntimeServices) mRuntimeServices->Stop(); const bool wasExternalKeyingActive = mVideoIO->ExternalKeyingActive(); mVideoIO->Stop(); if (wasExternalKeyingActive) PublishVideoIOStatus("External keying has been disabled."); return true; } bool OpenGLComposite::ReloadShader(bool preserveFeedbackState) { mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState; if (mRuntimeHost) { mRuntimeStore->SetCompileStatus(true, "Shader rebuild queued."); mRuntimeStore->ClearReloadRequest(); } RequestShaderBuild(); broadcastRuntimeState(); return true; } bool OpenGLComposite::RequestScreenshot(std::string& error) { (void)error; mScreenshotRequested.store(true); return true; } void OpenGLComposite::renderEffect() { ProcessRuntimePollResults(); std::vector appliedOscUpdates; std::vector completedOscCommits; if (mRuntimeHost && mRuntimeServices) { std::string oscError; if (!mRuntimeServices->ApplyPendingOscUpdates(appliedOscUpdates, oscError) && !oscError.empty()) OutputDebugStringA(("OSC apply failed: " + oscError + "\n").c_str()); mRuntimeServices->ConsumeCompletedOscCommits(completedOscCommits); } for (const RuntimeServices::CompletedOscCommit& completedCommit : completedOscCommits) { auto overlayIt = mOscOverlayStates.find(completedCommit.routeKey); if (overlayIt == mOscOverlayStates.end()) continue; OscOverlayState& overlay = overlayIt->second; if (overlay.commitQueued && overlay.pendingCommitGeneration == completedCommit.generation && overlay.generation == completedCommit.generation) { mOscOverlayStates.erase(overlayIt); } } std::set pendingOscRouteKeys; const auto oscNow = std::chrono::steady_clock::now(); for (const RuntimeServices::AppliedOscUpdate& update : appliedOscUpdates) { const std::string routeKey = update.routeKey; auto overlayIt = mOscOverlayStates.find(routeKey); if (overlayIt == mOscOverlayStates.end()) { OscOverlayState overlay; overlay.layerKey = update.layerKey; overlay.parameterKey = update.parameterKey; overlay.targetValue = update.targetValue; overlay.lastUpdatedTime = oscNow; overlay.lastAppliedTime = oscNow; overlay.generation = 1; mOscOverlayStates[routeKey] = std::move(overlay); } else { overlayIt->second.targetValue = update.targetValue; overlayIt->second.lastUpdatedTime = oscNow; overlayIt->second.generation += 1; overlayIt->second.commitQueued = false; } pendingOscRouteKeys.insert(routeKey); } const auto applyOscOverlays = [&](std::vector& states, bool allowCommit) { if (states.empty() || mOscOverlayStates.empty() || !mRuntimeHost) return; const double smoothing = ClampOscAlpha(mRuntimeStore ? mRuntimeStore->GetOscSmoothing() : 0.0); std::vector overlayKeysToRemove; for (auto& item : mOscOverlayStates) { OscOverlayState& overlay = item.second; auto stateIt = std::find_if(states.begin(), states.end(), [&overlay](const RuntimeRenderState& state) { return MatchesOscControlKey(state.layerId, overlay.layerKey) || MatchesOscControlKey(state.shaderId, overlay.layerKey) || MatchesOscControlKey(state.shaderName, overlay.layerKey); }); if (stateIt == states.end()) continue; auto definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(), [&overlay](const ShaderParameterDefinition& definition) { return MatchesOscControlKey(definition.id, overlay.parameterKey) || MatchesOscControlKey(definition.label, overlay.parameterKey); }); if (definitionIt == stateIt->parameterDefinitions.end()) continue; if (definitionIt->type == ShaderParameterType::Trigger) { if (pendingOscRouteKeys.find(item.first) == pendingOscRouteKeys.end()) continue; ShaderParameterValue& value = stateIt->parameterValues[definitionIt->id]; const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0]; const double triggerTime = stateIt->timeSeconds; value.numberValues = { previousCount + 1.0, triggerTime }; overlayKeysToRemove.push_back(item.first); continue; } ShaderParameterValue targetValue; std::string normalizeError; if (!NormalizeAndValidateParameterValue(*definitionIt, overlay.targetValue, targetValue, normalizeError)) continue; const bool smoothable = smoothing > 0.0 && (definitionIt->type == ShaderParameterType::Float || definitionIt->type == ShaderParameterType::Vec2 || definitionIt->type == ShaderParameterType::Color); if (!smoothable) { overlay.currentValue = targetValue; overlay.hasCurrentValue = true; stateIt->parameterValues[definitionIt->id] = overlay.currentValue; if (allowCommit && !overlay.commitQueued && oscNow - overlay.lastUpdatedTime >= kOscOverlayCommitDelay && mRuntimeServices) { std::string commitError; if (mRuntimeServices->QueueOscCommit(item.first, overlay.layerKey, overlay.parameterKey, overlay.targetValue, overlay.generation, commitError)) { overlay.pendingCommitGeneration = overlay.generation; overlay.commitQueued = true; } } continue; } if (!overlay.hasCurrentValue) { overlay.currentValue = DefaultValueForDefinition(*definitionIt); auto currentIt = stateIt->parameterValues.find(definitionIt->id); if (currentIt != stateIt->parameterValues.end()) overlay.currentValue = currentIt->second; overlay.hasCurrentValue = true; } if (overlay.currentValue.numberValues.size() != targetValue.numberValues.size()) overlay.currentValue.numberValues = targetValue.numberValues; double smoothingAlpha = smoothing; if (overlay.lastAppliedTime != std::chrono::steady_clock::time_point()) { const double deltaSeconds = std::chrono::duration_cast>(oscNow - overlay.lastAppliedTime).count(); smoothingAlpha = ComputeTimeBasedOscAlpha(smoothing, deltaSeconds); } overlay.lastAppliedTime = oscNow; ShaderParameterValue nextValue = targetValue; bool converged = true; for (std::size_t index = 0; index < targetValue.numberValues.size(); ++index) { const double currentNumber = overlay.currentValue.numberValues[index]; const double targetNumber = targetValue.numberValues[index]; const double delta = targetNumber - currentNumber; double nextNumber = currentNumber + delta * smoothingAlpha; if (std::fabs(delta) <= 0.0005) nextNumber = targetNumber; else converged = false; nextValue.numberValues[index] = nextNumber; } if (converged) nextValue.numberValues = targetValue.numberValues; overlay.currentValue = nextValue; overlay.hasCurrentValue = true; stateIt->parameterValues[definitionIt->id] = overlay.currentValue; if (allowCommit && converged && !overlay.commitQueued && oscNow - overlay.lastUpdatedTime >= kOscOverlayCommitDelay && mRuntimeServices) { std::string commitError; JsonValue committedValue = BuildOscCommitValue(*definitionIt, overlay.currentValue); if (mRuntimeServices->QueueOscCommit(item.first, overlay.layerKey, overlay.parameterKey, committedValue, overlay.generation, commitError)) { overlay.pendingCommitGeneration = overlay.generation; overlay.commitQueued = true; } } } for (const std::string& overlayKey : overlayKeysToRemove) mOscOverlayStates.erase(overlayKey); }; const bool hasInputSource = mVideoIO->HasInputSource(); std::vector layerStates; if (mUseCommittedLayerStates) { layerStates = mShaderPrograms->CommittedLayerStates(); applyOscOverlays(layerStates, false); if (mRuntimeSnapshotProvider) mRuntimeSnapshotProvider->RefreshDynamicRenderStateFields(layerStates); } else if (mRuntimeSnapshotProvider) { const unsigned renderWidth = mVideoIO->InputFrameWidth(); const unsigned renderHeight = mVideoIO->InputFrameHeight(); const uint64_t renderStateVersion = mRuntimeSnapshotProvider->GetRenderStateVersion(); const uint64_t parameterStateVersion = mRuntimeSnapshotProvider->GetParameterStateVersion(); const bool renderStateCacheValid = !mCachedLayerRenderStates.empty() && mCachedRenderStateVersion == renderStateVersion && mCachedRenderStateWidth == renderWidth && mCachedRenderStateHeight == renderHeight; if (renderStateCacheValid) { applyOscOverlays(mCachedLayerRenderStates, true); if (mCachedParameterStateVersion != parameterStateVersion && mRuntimeSnapshotProvider->TryRefreshCachedLayerStates(mCachedLayerRenderStates)) { mCachedParameterStateVersion = parameterStateVersion; applyOscOverlays(mCachedLayerRenderStates, true); } layerStates = mCachedLayerRenderStates; mRuntimeSnapshotProvider->RefreshDynamicRenderStateFields(layerStates); } else { if (mRuntimeSnapshotProvider->TryGetLayerRenderStates(renderWidth, renderHeight, layerStates)) { mCachedLayerRenderStates = layerStates; mCachedRenderStateVersion = renderStateVersion; mCachedParameterStateVersion = parameterStateVersion; mCachedRenderStateWidth = renderWidth; mCachedRenderStateHeight = renderHeight; applyOscOverlays(mCachedLayerRenderStates, true); layerStates = mCachedLayerRenderStates; } else { applyOscOverlays(mCachedLayerRenderStates, true); layerStates = mCachedLayerRenderStates; mRuntimeSnapshotProvider->RefreshDynamicRenderStateFields(layerStates); } } } const unsigned historyCap = mRuntimeStore ? mRuntimeStore->GetMaxTemporalHistoryFrames() : 0; mRenderPass->Render( hasInputSource, layerStates, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), mVideoIO->CaptureTextureWidth(), mVideoIO->InputPixelFormat(), historyCap, [this](const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error) { return mShaderPrograms->UpdateTextBindingTexture(state, textBinding, error); }, [this](const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable) { return mShaderPrograms->UpdateGlobalParamsBuffer(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable); }); } void OpenGLComposite::ProcessScreenshotRequest() { if (!mScreenshotRequested.exchange(false)) return; const unsigned width = mVideoIO ? mVideoIO->OutputFrameWidth() : 0; const unsigned height = mVideoIO ? mVideoIO->OutputFrameHeight() : 0; if (width == 0 || height == 0) return; std::vector bottomUpPixels(static_cast(width) * height * 4); std::vector topDownPixels(bottomUpPixels.size()); glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer->OutputFramebuffer()); glReadBuffer(GL_COLOR_ATTACHMENT0); glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ROW_LENGTH, 0); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bottomUpPixels.data()); glPixelStorei(GL_PACK_ALIGNMENT, 4); const std::size_t rowBytes = static_cast(width) * 4; for (unsigned y = 0; y < height; ++y) { const unsigned sourceY = height - 1 - y; std::copy( bottomUpPixels.begin() + static_cast(sourceY * rowBytes), bottomUpPixels.begin() + static_cast((sourceY + 1) * rowBytes), topDownPixels.begin() + static_cast(y * rowBytes)); } try { const std::filesystem::path outputPath = BuildScreenshotPath(); std::filesystem::create_directories(outputPath.parent_path()); WritePngFileAsync(outputPath, width, height, std::move(topDownPixels)); } catch (const std::exception& exception) { OutputDebugStringA((std::string("Screenshot request failed: ") + exception.what() + "\n").c_str()); } } std::filesystem::path OpenGLComposite::BuildScreenshotPath() const { const std::filesystem::path root = mRuntimeStore && !mRuntimeStore->GetRuntimeRoot().empty() ? mRuntimeStore->GetRuntimeRoot() : std::filesystem::current_path(); const auto now = std::chrono::system_clock::now(); const auto milliseconds = std::chrono::duration_cast(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::ProcessRuntimePollResults() { if (!mRuntimeHost || !mRuntimeServices) return true; const RuntimePollEvents events = mRuntimeServices->ConsumePollEvents(); if (events.failed) { mRuntimeStore->SetCompileStatus(false, events.error); broadcastRuntimeState(); return false; } if (events.registryChanged) broadcastRuntimeState(); if (!events.reloadRequested) { PreparedShaderBuild readyBuild; if (!mShaderBuildQueue || !mShaderBuildQueue->TryConsumeReadyBuild(readyBuild)) return true; char compilerErrorMessage[1024] = {}; if (!mShaderPrograms->CommitPreparedLayerPrograms(readyBuild, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage)) { mRuntimeStore->SetCompileStatus(false, compilerErrorMessage); mUseCommittedLayerStates = true; mPreserveFeedbackOnNextShaderBuild = false; broadcastRuntimeState(); return false; } mUseCommittedLayerStates = false; mCachedLayerRenderStates = mShaderPrograms->CommittedLayerStates(); mShaderPrograms->ResetTemporalHistoryState(); if (!mPreserveFeedbackOnNextShaderBuild) mShaderPrograms->ResetShaderFeedbackState(); mPreserveFeedbackOnNextShaderBuild = false; broadcastRuntimeState(); return true; } mRuntimeStore->SetCompileStatus(true, "Shader rebuild queued."); mPreserveFeedbackOnNextShaderBuild = false; RequestShaderBuild(); broadcastRuntimeState(); return true; } void OpenGLComposite::RequestShaderBuild() { if (!mShaderBuildQueue || !mVideoIO) return; mUseCommittedLayerStates = true; if (mRuntimeHost) mRuntimeStore->ClearReloadRequest(); mShaderBuildQueue->RequestBuild(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight()); } void OpenGLComposite::broadcastRuntimeState() { if (mRuntimeServices) mRuntimeServices->BroadcastState(); } void OpenGLComposite::resetTemporalHistoryState() { mShaderPrograms->ResetTemporalHistoryState(); mShaderPrograms->ResetShaderFeedbackState(); } bool OpenGLComposite::CheckOpenGLExtensions() { return true; } ////////////////////////////////////////////