#include "RenderEngine.h" #include "ShaderBuildQueue.h" #include #include namespace { constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150); } RenderEngine::RenderEngine( RuntimeSnapshotProvider& runtimeSnapshotProvider, HealthTelemetry& healthTelemetry, CRITICAL_SECTION& mutex, HDC hdc, HGLRC hglrc, RenderEffectCallback renderEffect, ScreenshotCallback screenshotReady, PreviewPaintCallback previewPaint) : mRenderer(), mRenderPass(mRenderer), mRenderPipeline(mRenderer, runtimeSnapshotProvider, healthTelemetry, std::move(renderEffect), std::move(screenshotReady), std::move(previewPaint)), mShaderPrograms(mRenderer, runtimeSnapshotProvider), mRuntimeSnapshotProvider(runtimeSnapshotProvider), mMutex(mutex), mHdc(hdc), mHglrc(hglrc) { } RenderEngine::~RenderEngine() { mRenderer.DestroyResources(); } bool RenderEngine::CompileDecodeShader(int errorMessageSize, char* errorMessage) { return mShaderPrograms.CompileDecodeShader(errorMessageSize, errorMessage); } bool RenderEngine::CompileOutputPackShader(int errorMessageSize, char* errorMessage) { return mShaderPrograms.CompileOutputPackShader(errorMessageSize, errorMessage); } bool RenderEngine::InitializeResources( unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error) { return mRenderer.InitializeResources( inputFrameWidth, inputFrameHeight, captureTextureWidth, outputFrameWidth, outputFrameHeight, outputPackTextureWidth, error); } bool RenderEngine::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage) { return mShaderPrograms.CompileLayerPrograms(inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage); } bool RenderEngine::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage) { return mShaderPrograms.CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage); } bool RenderEngine::ApplyPreparedShaderBuild( const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, bool preserveFeedbackState, int errorMessageSize, char* errorMessage) { if (!CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage)) return false; mCachedLayerRenderStates = mShaderPrograms.CommittedLayerStates(); mCachedRenderStateVersion = preparedBuild.renderSnapshot.versions.renderStateVersion; mCachedParameterStateVersion = preparedBuild.renderSnapshot.versions.parameterStateVersion; mCachedRenderStateWidth = preparedBuild.renderSnapshot.outputWidth; mCachedRenderStateHeight = preparedBuild.renderSnapshot.outputHeight; ResetTemporalHistoryState(); if (!preserveFeedbackState) ResetShaderFeedbackState(); return true; } RenderEngine::PreparedShaderBuildApplyResult RenderEngine::TryApplyReadyShaderBuild( ShaderBuildQueue& shaderBuildQueue, unsigned inputFrameWidth, unsigned inputFrameHeight, bool preserveFeedbackState) { PreparedShaderBuildApplyResult result; PreparedShaderBuild readyBuild; if (!shaderBuildQueue.TryConsumeReadyBuild(readyBuild)) return result; result.hadReadyBuild = true; char compilerErrorMessage[1024] = {}; if (!ApplyPreparedShaderBuild( readyBuild, inputFrameWidth, inputFrameHeight, preserveFeedbackState, sizeof(compilerErrorMessage), compilerErrorMessage)) { result.errorMessage = compilerErrorMessage; return result; } result.applied = true; return result; } const std::vector& RenderEngine::CommittedLayerStates() const { return mShaderPrograms.CommittedLayerStates(); } void RenderEngine::ResetTemporalHistoryState() { mShaderPrograms.ResetTemporalHistoryState(); } void RenderEngine::ResetShaderFeedbackState() { mShaderPrograms.ResetShaderFeedbackState(); } void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope) { switch (resetScope) { case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly: ResetTemporalHistoryState(); break; case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback: ResetTemporalHistoryState(); ResetShaderFeedbackState(); break; case RuntimeCoordinatorRenderResetScope::None: default: break; } } void RenderEngine::ClearOscOverlayState() { mRuntimeLiveState.Clear(); } void RenderEngine::UpdateOscOverlayState( const std::vector& updates, const std::vector& completedCommits) { std::vector liveCompletions; liveCompletions.reserve(completedCommits.size()); for (const OscOverlayCommitCompletion& completedCommit : completedCommits) liveCompletions.push_back({ completedCommit.routeKey, completedCommit.generation }); mRuntimeLiveState.ApplyOscCommitCompletions(liveCompletions); std::vector liveUpdates; liveUpdates.reserve(updates.size()); for (const OscOverlayUpdate& update : updates) liveUpdates.push_back({ update.routeKey, update.layerKey, update.parameterKey, update.targetValue }); mRuntimeLiveState.ApplyOscUpdates(liveUpdates); } void RenderEngine::ResizeView(int width, int height) { mRenderer.ResizeView(width, height); } bool RenderEngine::TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight) { if (!force) { if (previewFps == 0) return false; 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 false; } } if (!TryEnterCriticalSection(&mMutex)) return false; mRenderer.PresentToWindow(mHdc, outputFrameWidth, outputFrameHeight); mLastPreviewPresentTime = std::chrono::steady_clock::now(); LeaveCriticalSection(&mMutex); return true; } bool RenderEngine::TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState) { if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr) return true; const long textureSize = inputFrame.rowBytes * static_cast(inputFrame.height); if (!TryEnterCriticalSection(&mMutex)) return false; wglMakeCurrent(mHdc, mHglrc); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.TextureUploadBuffer()); glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, inputFrame.bytes, GL_DYNAMIC_DRAW); glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture()); if (inputFrame.pixelFormat == VideoIOPixelFormat::V210) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL); else glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); wglMakeCurrent(NULL, NULL); LeaveCriticalSection(&mMutex); return true; } bool RenderEngine::RenderOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame) { EnterCriticalSection(&mMutex); wglMakeCurrent(mHdc, mHglrc); const bool rendered = mRenderPipeline.RenderFrame(context, outputFrame); wglMakeCurrent(NULL, NULL); LeaveCriticalSection(&mMutex); return rendered; } bool RenderEngine::ResolveRenderLayerStates( bool useCommittedLayerStates, unsigned renderWidth, unsigned renderHeight, double oscSmoothing, std::vector* commitRequests, std::vector& layerStates) { layerStates.clear(); if (useCommittedLayerStates) { layerStates = mShaderPrograms.CommittedLayerStates(); ApplyOscOverlays(layerStates, false, oscSmoothing, commitRequests); mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates); return true; } const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider.GetVersions(); const bool renderStateCacheValid = !mCachedLayerRenderStates.empty() && mCachedRenderStateVersion == versions.renderStateVersion && mCachedRenderStateWidth == renderWidth && mCachedRenderStateHeight == renderHeight; if (renderStateCacheValid) { RuntimeRenderStateSnapshot renderSnapshot; renderSnapshot.outputWidth = renderWidth; renderSnapshot.outputHeight = renderHeight; renderSnapshot.versions.renderStateVersion = mCachedRenderStateVersion; renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion; renderSnapshot.states = mCachedLayerRenderStates; ApplyOscOverlays(renderSnapshot.states, true, oscSmoothing, commitRequests); if (mCachedParameterStateVersion != versions.parameterStateVersion && mRuntimeSnapshotProvider.TryRefreshPublishedSnapshotParameters(renderSnapshot)) { mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; ApplyOscOverlays(renderSnapshot.states, true, oscSmoothing, commitRequests); } mCachedLayerRenderStates = renderSnapshot.states; layerStates = renderSnapshot.states; mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates); return true; } RuntimeRenderStateSnapshot renderSnapshot; if (mRuntimeSnapshotProvider.TryPublishRenderStateSnapshot(renderWidth, renderHeight, renderSnapshot)) { mCachedLayerRenderStates = renderSnapshot.states; mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion; mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; mCachedRenderStateWidth = renderSnapshot.outputWidth; mCachedRenderStateHeight = renderSnapshot.outputHeight; ApplyOscOverlays(mCachedLayerRenderStates, true, oscSmoothing, commitRequests); layerStates = mCachedLayerRenderStates; return true; } ApplyOscOverlays(mCachedLayerRenderStates, true, oscSmoothing, commitRequests); layerStates = mCachedLayerRenderStates; mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates); return !layerStates.empty(); } void RenderEngine::ApplyOscOverlays( std::vector& states, bool allowCommit, double smoothing, std::vector* commitRequests) { std::vector liveCommitRequests; RuntimeLiveStateApplyOptions options; options.allowCommit = allowCommit; options.smoothing = smoothing; options.commitDelay = kOscOverlayCommitDelay; options.now = std::chrono::steady_clock::now(); mRuntimeLiveState.ApplyToLayerStates(states, options, commitRequests ? &liveCommitRequests : nullptr); if (!commitRequests) return; for (const RuntimeLiveOscCommitRequest& request : liveCommitRequests) commitRequests->push_back({ request.routeKey, request.layerKey, request.parameterKey, request.value, request.generation }); } void RenderEngine::RenderLayerStack( bool hasInputSource, const std::vector& layerStates, unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat, unsigned historyCap) { mRenderPass.Render( hasInputSource, layerStates, inputFrameWidth, inputFrameHeight, captureTextureWidth, inputPixelFormat, historyCap, [this](const RuntimeRenderState& state, OpenGLRenderer::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); }); } bool RenderEngine::ReadOutputFrameRgba(unsigned width, unsigned height, std::vector& bottomUpPixels) { if (width == 0 || height == 0) return false; EnterCriticalSection(&mMutex); wglMakeCurrent(mHdc, mHglrc); bottomUpPixels.resize(static_cast(width) * height * 4); 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); wglMakeCurrent(NULL, NULL); LeaveCriticalSection(&mMutex); return true; } bool RenderEngine::CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height, std::vector& topDownPixels) { std::vector bottomUpPixels; if (!ReadOutputFrameRgba(width, height, bottomUpPixels)) return false; topDownPixels.resize(bottomUpPixels.size()); 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)); } return true; }