Files
video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp
Aiden 0808171677
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m32s
CI / Windows Release Package (push) Successful in 2m34s
GROUND WORK PHASE 3
2026-05-11 16:32:51 +10:00

403 lines
13 KiB
C++

#include "RenderEngine.h"
#include "ShaderBuildQueue.h"
#include <gl/gl.h>
#include <algorithm>
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<RuntimeRenderState>& 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<OscOverlayUpdate>& updates,
const std::vector<OscOverlayCommitCompletion>& completedCommits)
{
std::vector<RuntimeLiveOscCommitCompletion> liveCompletions;
liveCompletions.reserve(completedCommits.size());
for (const OscOverlayCommitCompletion& completedCommit : completedCommits)
liveCompletions.push_back({ completedCommit.routeKey, completedCommit.generation });
mRuntimeLiveState.ApplyOscCommitCompletions(liveCompletions);
std::vector<RuntimeLiveOscUpdate> 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<long>(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<OscOverlayCommitRequest>* commitRequests,
std::vector<RuntimeRenderState>& 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<RuntimeRenderState>& states,
bool allowCommit,
double smoothing,
std::vector<OscOverlayCommitRequest>* commitRequests)
{
std::vector<RuntimeLiveOscCommitRequest> 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<RuntimeRenderState>& 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<unsigned char>& bottomUpPixels)
{
if (width == 0 || height == 0)
return false;
EnterCriticalSection(&mMutex);
wglMakeCurrent(mHdc, mHglrc);
bottomUpPixels.resize(static_cast<std::size_t>(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<unsigned char>& topDownPixels)
{
std::vector<unsigned char> bottomUpPixels;
if (!ReadOutputFrameRgba(width, height, bottomUpPixels))
return false;
topDownPixels.resize(bottomUpPixels.size());
const std::size_t rowBytes = static_cast<std::size_t>(width) * 4;
for (unsigned y = 0; y < height; ++y)
{
const unsigned sourceY = height - 1 - y;
std::copy(
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>(sourceY * rowBytes),
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>((sourceY + 1) * rowBytes),
topDownPixels.begin() + static_cast<std::ptrdiff_t>(y * rowBytes));
}
return true;
}