#include "RenderEngine.h" #include #include 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), mMutex(mutex), mHdc(hdc), mHglrc(hglrc), mFrameStateResolver(runtimeSnapshotProvider) { } 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; mFrameStateResolver.StoreCommittedSnapshot(preparedBuild.renderSnapshot, mShaderPrograms.CommittedLayerStates()); ResetTemporalHistoryState(); if (!preserveFeedbackState) ResetShaderFeedbackState(); return true; } void RenderEngine::ResetTemporalHistoryState() { mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly); ProcessRenderResetCommandsOnRenderThread(); } void RenderEngine::ResetShaderFeedbackState() { mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::ShaderFeedbackOnly); ProcessRenderResetCommandsOnRenderThread(); } void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope) { switch (resetScope) { case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly: mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly); ProcessRenderResetCommandsOnRenderThread(); break; case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback: mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryAndFeedback); ProcessRenderResetCommandsOnRenderThread(); break; case RuntimeCoordinatorRenderResetScope::None: default: break; } } void RenderEngine::ResetTemporalHistoryStateOnRenderThread() { mShaderPrograms.ResetTemporalHistoryState(); } void RenderEngine::ResetShaderFeedbackStateOnRenderThread() { mShaderPrograms.ResetShaderFeedbackState(); } void RenderEngine::ApplyRenderResetOnRenderThread(RenderCommandResetScope resetScope) { switch (resetScope) { case RenderCommandResetScope::ShaderFeedbackOnly: ResetShaderFeedbackStateOnRenderThread(); break; case RenderCommandResetScope::TemporalHistoryOnly: ResetTemporalHistoryStateOnRenderThread(); break; case RenderCommandResetScope::TemporalHistoryAndFeedback: ResetTemporalHistoryStateOnRenderThread(); ResetShaderFeedbackStateOnRenderThread(); break; case RenderCommandResetScope::None: default: break; } } void RenderEngine::ProcessRenderResetCommandsOnRenderThread() { RenderCommandResetScope resetScope = RenderCommandResetScope::None; while (mRenderCommandQueue.TryTakeRenderReset(resetScope)) ApplyRenderResetOnRenderThread(resetScope); } 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; mRenderCommandQueue.RequestPreviewPresent({ outputFrameWidth, outputFrameHeight }); RenderPreviewPresentRequest request; const bool presented = mRenderCommandQueue.TryTakePreviewPresent(request) && PresentPreviewOnRenderThread(request.outputFrameWidth, request.outputFrameHeight); LeaveCriticalSection(&mMutex); return presented; } bool RenderEngine::PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight) { mRenderer.PresentToWindow(mHdc, outputFrameWidth, outputFrameHeight); mLastPreviewPresentTime = std::chrono::steady_clock::now(); return true; } bool RenderEngine::TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState) { if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr) return true; if (!TryEnterCriticalSection(&mMutex)) return false; wglMakeCurrent(mHdc, mHglrc); const bool uploaded = UploadInputFrameOnRenderThread(inputFrame, videoState); wglMakeCurrent(NULL, NULL); LeaveCriticalSection(&mMutex); return uploaded; } bool RenderEngine::UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState) { const long textureSize = inputFrame.rowBytes * static_cast(inputFrame.height); 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); return true; } bool RenderEngine::RenderOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame) { EnterCriticalSection(&mMutex); wglMakeCurrent(mHdc, mHglrc); const bool rendered = RenderOutputFrameOnRenderThread(context, outputFrame); wglMakeCurrent(NULL, NULL); LeaveCriticalSection(&mMutex); return rendered; } bool RenderEngine::RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame) { ProcessRenderResetCommandsOnRenderThread(); return mRenderPipeline.RenderFrame(context, outputFrame); } bool RenderEngine::ResolveRenderFrameState( const RenderFrameInput& input, std::vector* commitRequests, RenderFrameState& frameState) { std::vector liveCommitRequests; const bool resolved = mFrameStateResolver.Resolve( input, mShaderPrograms.CommittedLayerStates(), mRuntimeLiveState, commitRequests ? &liveCommitRequests : nullptr, frameState); if (commitRequests) { for (const RuntimeLiveOscCommitRequest& request : liveCommitRequests) commitRequests->push_back({ request.routeKey, request.layerKey, request.parameterKey, request.value, request.generation }); } return resolved; } void RenderEngine::RenderPreparedFrame(const RenderFrameState& frameState) { RenderLayerStack( frameState.hasInputSource, frameState.layerStates, frameState.inputFrameWidth, frameState.inputFrameHeight, frameState.captureTextureWidth, frameState.inputPixelFormat, frameState.historyCap); } 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::ReadOutputFrameRgbaOnRenderThread(unsigned width, unsigned height, std::vector& bottomUpPixels) { if (width == 0 || height == 0) return false; 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); return true; } bool RenderEngine::CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height, std::vector& topDownPixels) { EnterCriticalSection(&mMutex); wglMakeCurrent(mHdc, mHglrc); mRenderCommandQueue.RequestScreenshotCapture({ width, height }); RenderScreenshotCaptureRequest request; const bool captured = mRenderCommandQueue.TryTakeScreenshotCapture(request) && CaptureOutputFrameRgbaTopDownOnRenderThread(request.width, request.height, topDownPixels); wglMakeCurrent(NULL, NULL); LeaveCriticalSection(&mMutex); return captured; } bool RenderEngine::CaptureOutputFrameRgbaTopDownOnRenderThread(unsigned width, unsigned height, std::vector& topDownPixels) { std::vector bottomUpPixels; if (!ReadOutputFrameRgbaOnRenderThread(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; }