669 lines
20 KiB
C++
669 lines
20 KiB
C++
#include "RenderEngine.h"
|
|
|
|
#include <gl/gl.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <sstream>
|
|
|
|
RenderEngine::RenderEngine(
|
|
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
|
HealthTelemetry& healthTelemetry,
|
|
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),
|
|
mHealthTelemetry(healthTelemetry),
|
|
mHdc(hdc),
|
|
mHglrc(hglrc),
|
|
mFrameStateResolver(runtimeSnapshotProvider)
|
|
{
|
|
}
|
|
|
|
RenderEngine::~RenderEngine()
|
|
{
|
|
StopRenderThread();
|
|
if (!mResourcesDestroyed)
|
|
{
|
|
mRenderer.DestroyResources();
|
|
mResourcesDestroyed = true;
|
|
}
|
|
}
|
|
|
|
bool RenderEngine::StartRenderThread()
|
|
{
|
|
if (mRenderThreadRunning)
|
|
return true;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
mRenderThreadStopping = false;
|
|
}
|
|
|
|
std::promise<bool> ready;
|
|
std::future<bool> readyResult = ready.get_future();
|
|
mRenderThread = std::thread(&RenderEngine::RenderThreadMain, this, std::move(ready));
|
|
if (!readyResult.get())
|
|
{
|
|
if (mRenderThread.joinable())
|
|
mRenderThread.join();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void RenderEngine::StopRenderThread()
|
|
{
|
|
if (mRenderThreadRunning)
|
|
{
|
|
InvokeOnRenderThread([this]() {
|
|
if (!mResourcesDestroyed)
|
|
{
|
|
mRenderer.DestroyResources();
|
|
mResourcesDestroyed = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
mRenderThreadStopping = true;
|
|
}
|
|
mRenderThreadCondition.notify_one();
|
|
|
|
if (mRenderThread.joinable())
|
|
mRenderThread.join();
|
|
}
|
|
|
|
void RenderEngine::RenderThreadMain(std::promise<bool> ready)
|
|
{
|
|
mRenderThreadId = GetCurrentThreadId();
|
|
if (!wglMakeCurrent(mHdc, mHglrc))
|
|
{
|
|
mRenderThreadId = 0;
|
|
ready.set_value(false);
|
|
return;
|
|
}
|
|
|
|
mRenderThreadRunning = true;
|
|
ready.set_value(true);
|
|
|
|
for (;;)
|
|
{
|
|
std::function<void()> task;
|
|
{
|
|
std::unique_lock<std::mutex> lock(mRenderThreadMutex);
|
|
mRenderThreadCondition.wait(lock, [this]() {
|
|
return mRenderThreadStopping || !mRenderThreadTasks.empty();
|
|
});
|
|
|
|
if (mRenderThreadStopping && mRenderThreadTasks.empty())
|
|
break;
|
|
|
|
task = std::move(mRenderThreadTasks.front());
|
|
mRenderThreadTasks.pop();
|
|
}
|
|
|
|
try
|
|
{
|
|
task();
|
|
}
|
|
catch (...)
|
|
{
|
|
OutputDebugStringA("Render thread task failed with an unhandled exception.\n");
|
|
}
|
|
}
|
|
|
|
wglMakeCurrent(NULL, NULL);
|
|
mRenderThreadRunning = false;
|
|
mRenderThreadId = 0;
|
|
}
|
|
|
|
void RenderEngine::ReportRenderThreadRequestFailure(const char* operationName, const char* reason)
|
|
{
|
|
std::ostringstream message;
|
|
message << "Render thread request failed";
|
|
if (operationName && operationName[0] != '\0')
|
|
message << " [" << operationName << "]";
|
|
if (reason && reason[0] != '\0')
|
|
message << ": " << reason;
|
|
message << ".\n";
|
|
OutputDebugStringA(message.str().c_str());
|
|
}
|
|
|
|
bool RenderEngine::IsRenderThreadAccessExpected() const
|
|
{
|
|
return !mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId;
|
|
}
|
|
|
|
void RenderEngine::ReportWrongThreadRenderAccess(const char* operationName) const
|
|
{
|
|
if (IsRenderThreadAccessExpected())
|
|
return;
|
|
|
|
std::ostringstream message;
|
|
message << "Wrong-thread render access detected";
|
|
if (operationName && operationName[0] != '\0')
|
|
message << " [" << operationName << "]";
|
|
message << ".\n";
|
|
OutputDebugStringA(message.str().c_str());
|
|
}
|
|
|
|
bool RenderEngine::CompileDecodeShader(int errorMessageSize, char* errorMessage)
|
|
{
|
|
return InvokeOnRenderThread([this, errorMessageSize, errorMessage]() {
|
|
return mShaderPrograms.CompileDecodeShader(errorMessageSize, errorMessage);
|
|
});
|
|
}
|
|
|
|
bool RenderEngine::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
|
|
{
|
|
return InvokeOnRenderThread([this, errorMessageSize, 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 InvokeOnRenderThread([this, inputFrameWidth, inputFrameHeight, captureTextureWidth, outputFrameWidth, outputFrameHeight, outputPackTextureWidth, &error]() {
|
|
return mRenderer.InitializeResources(
|
|
inputFrameWidth,
|
|
inputFrameHeight,
|
|
captureTextureWidth,
|
|
outputFrameWidth,
|
|
outputFrameHeight,
|
|
outputPackTextureWidth,
|
|
error);
|
|
});
|
|
}
|
|
|
|
bool RenderEngine::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
|
{
|
|
return InvokeOnRenderThread([this, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage]() {
|
|
return mShaderPrograms.CompileLayerPrograms(inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage);
|
|
});
|
|
}
|
|
|
|
bool RenderEngine::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
|
{
|
|
return InvokeOnRenderThread([this, &preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, 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()
|
|
{
|
|
InvokeOnRenderThread([this]() {
|
|
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
|
|
ProcessRenderResetCommandsOnRenderThread();
|
|
});
|
|
}
|
|
|
|
void RenderEngine::ResetShaderFeedbackState()
|
|
{
|
|
InvokeOnRenderThread([this]() {
|
|
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::ShaderFeedbackOnly);
|
|
ProcessRenderResetCommandsOnRenderThread();
|
|
});
|
|
}
|
|
|
|
void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope)
|
|
{
|
|
InvokeOnRenderThread([this, 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()
|
|
{
|
|
ReportWrongThreadRenderAccess("reset-temporal-history");
|
|
mShaderPrograms.ResetTemporalHistoryState();
|
|
}
|
|
|
|
void RenderEngine::ResetShaderFeedbackStateOnRenderThread()
|
|
{
|
|
ReportWrongThreadRenderAccess("reset-shader-feedback");
|
|
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::EnqueuePreviewPresentWake()
|
|
{
|
|
if (!mRenderThreadRunning)
|
|
return;
|
|
|
|
bool shouldNotify = false;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
if (!mRenderThreadStopping && !mPreviewPresentWakePending)
|
|
{
|
|
mPreviewPresentWakePending = true;
|
|
mRenderThreadTasks.push([this]() {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
mPreviewPresentWakePending = false;
|
|
}
|
|
ProcessPreviewPresentCommandsOnRenderThread();
|
|
});
|
|
shouldNotify = true;
|
|
}
|
|
}
|
|
|
|
if (shouldNotify)
|
|
mRenderThreadCondition.notify_one();
|
|
}
|
|
|
|
void RenderEngine::ProcessPreviewPresentCommandsOnRenderThread()
|
|
{
|
|
RenderPreviewPresentRequest request;
|
|
if (mRenderCommandQueue.TryTakePreviewPresent(request))
|
|
PresentPreviewOnRenderThread(request.outputFrameWidth, request.outputFrameHeight);
|
|
}
|
|
|
|
void RenderEngine::EnqueueInputUploadWake()
|
|
{
|
|
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
|
|
return;
|
|
|
|
bool shouldNotify = false;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
if (!mRenderThreadStopping && !mInputUploadWakePending)
|
|
{
|
|
mInputUploadWakePending = true;
|
|
mRenderThreadTasks.push([this]() {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
mInputUploadWakePending = false;
|
|
}
|
|
ProcessInputUploadCommandsOnRenderThread();
|
|
});
|
|
shouldNotify = true;
|
|
}
|
|
}
|
|
|
|
if (shouldNotify)
|
|
mRenderThreadCondition.notify_one();
|
|
}
|
|
|
|
void RenderEngine::ProcessInputUploadCommandsOnRenderThread()
|
|
{
|
|
RenderInputUploadRequest request;
|
|
while (mRenderCommandQueue.TryTakeInputUpload(request))
|
|
{
|
|
if (request.ownedBytes.empty())
|
|
continue;
|
|
|
|
request.inputFrame.bytes = request.ownedBytes.data();
|
|
UploadInputFrameOnRenderThread(request.inputFrame, request.videoState);
|
|
}
|
|
}
|
|
|
|
void RenderEngine::EnqueueScreenshotCaptureWake()
|
|
{
|
|
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
|
|
return;
|
|
|
|
bool shouldNotify = false;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
if (!mRenderThreadStopping && !mScreenshotCaptureWakePending)
|
|
{
|
|
mScreenshotCaptureWakePending = true;
|
|
mRenderThreadTasks.push([this]() {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
mScreenshotCaptureWakePending = false;
|
|
}
|
|
ProcessScreenshotCaptureCommandsOnRenderThread();
|
|
});
|
|
shouldNotify = true;
|
|
}
|
|
}
|
|
|
|
if (shouldNotify)
|
|
mRenderThreadCondition.notify_one();
|
|
}
|
|
|
|
void RenderEngine::ProcessScreenshotCaptureCommandsOnRenderThread()
|
|
{
|
|
RenderScreenshotCaptureRequest request;
|
|
ScreenshotCaptureCallback completion;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
completion = mScreenshotCaptureCompletion;
|
|
}
|
|
|
|
while (mRenderCommandQueue.TryTakeScreenshotCapture(request))
|
|
{
|
|
if (!completion)
|
|
continue;
|
|
|
|
std::vector<unsigned char> topDownPixels;
|
|
if (CaptureOutputFrameRgbaTopDownOnRenderThread(request.width, request.height, topDownPixels))
|
|
completion(request.width, request.height, std::move(topDownPixels));
|
|
}
|
|
}
|
|
|
|
void RenderEngine::ClearOscOverlayState()
|
|
{
|
|
InvokeOnRenderThread([this]() {
|
|
mRuntimeLiveState.Clear();
|
|
});
|
|
}
|
|
|
|
void RenderEngine::ClearOscOverlayStateForLayerKey(const std::string& layerKey)
|
|
{
|
|
InvokeOnRenderThread([this, layerKey]() {
|
|
mRuntimeLiveState.ClearForLayerKey(layerKey);
|
|
});
|
|
}
|
|
|
|
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)
|
|
{
|
|
InvokeOnRenderThread([this, width, 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 (mRenderThreadRunning)
|
|
{
|
|
mRenderCommandQueue.RequestPreviewPresent({ outputFrameWidth, outputFrameHeight });
|
|
EnqueuePreviewPresentWake();
|
|
return true;
|
|
}
|
|
|
|
ReportRenderThreadRequestFailure("preview-present", "render thread is not running");
|
|
return false;
|
|
}
|
|
|
|
bool RenderEngine::PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight)
|
|
{
|
|
ReportWrongThreadRenderAccess("preview-present");
|
|
mRenderer.PresentToWindow(mHdc, outputFrameWidth, outputFrameHeight);
|
|
mLastPreviewPresentTime = std::chrono::steady_clock::now();
|
|
return true;
|
|
}
|
|
|
|
bool RenderEngine::RequestScreenshotCapture(unsigned width, unsigned height, ScreenshotCaptureCallback completion)
|
|
{
|
|
if (width == 0 || height == 0 || !completion)
|
|
return false;
|
|
if (!mRenderThreadRunning)
|
|
return false;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
mScreenshotCaptureCompletion = std::move(completion);
|
|
}
|
|
mRenderCommandQueue.RequestScreenshotCapture({ width, height });
|
|
EnqueueScreenshotCaptureWake();
|
|
return true;
|
|
}
|
|
|
|
bool RenderEngine::QueueInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
|
|
{
|
|
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
|
|
return true;
|
|
if (inputFrame.rowBytes <= 0 || inputFrame.height == 0)
|
|
return false;
|
|
|
|
const std::size_t byteCount = static_cast<std::size_t>(inputFrame.rowBytes) * inputFrame.height;
|
|
RenderInputUploadRequest request;
|
|
request.inputFrame = inputFrame;
|
|
request.videoState = videoState;
|
|
request.ownedBytes.resize(byteCount);
|
|
std::memcpy(request.ownedBytes.data(), inputFrame.bytes, byteCount);
|
|
request.inputFrame.bytes = nullptr;
|
|
|
|
mRenderCommandQueue.RequestInputUpload(request);
|
|
EnqueueInputUploadWake();
|
|
return true;
|
|
}
|
|
|
|
bool RenderEngine::UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
|
|
{
|
|
ReportWrongThreadRenderAccess("input-upload");
|
|
const long textureSize = inputFrame.rowBytes * static_cast<long>(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::RequestOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
|
|
{
|
|
if (mRenderThreadRunning)
|
|
{
|
|
const auto queuedAt = std::chrono::steady_clock::now();
|
|
return TryInvokeOnRenderThread("output-render", [this, &context, &outputFrame, queuedAt]() {
|
|
const auto startedAt = std::chrono::steady_clock::now();
|
|
const double queueWaitMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(startedAt - queuedAt).count();
|
|
mHealthTelemetry.TryRecordOutputRenderQueueWait(queueWaitMilliseconds);
|
|
mRenderCommandQueue.RequestOutputFrame({ context.videoState, context.completion });
|
|
RenderOutputFrameRequest request;
|
|
return mRenderCommandQueue.TryTakeOutputFrame(request) &&
|
|
RenderOutputFrameOnRenderThread({ request.videoState, request.completion }, outputFrame);
|
|
});
|
|
}
|
|
|
|
ReportRenderThreadRequestFailure("output-render", "render thread is not running");
|
|
return false;
|
|
}
|
|
|
|
bool RenderEngine::RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
|
|
{
|
|
ReportWrongThreadRenderAccess("output-render");
|
|
ProcessRenderResetCommandsOnRenderThread();
|
|
ProcessInputUploadCommandsOnRenderThread();
|
|
return mRenderPipeline.RenderFrame(context, outputFrame);
|
|
}
|
|
|
|
bool RenderEngine::ResolveRenderFrameState(
|
|
const RenderFrameInput& input,
|
|
std::vector<OscOverlayCommitRequest>* commitRequests,
|
|
RenderFrameState& frameState)
|
|
{
|
|
std::vector<RuntimeLiveOscCommitRequest> 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<RuntimeRenderState>& layerStates,
|
|
unsigned inputFrameWidth,
|
|
unsigned inputFrameHeight,
|
|
unsigned captureTextureWidth,
|
|
VideoIOPixelFormat inputPixelFormat,
|
|
unsigned historyCap)
|
|
{
|
|
ReportWrongThreadRenderAccess("render-layer-stack");
|
|
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<unsigned char>& bottomUpPixels)
|
|
{
|
|
ReportWrongThreadRenderAccess("read-output-frame-rgba");
|
|
if (width == 0 || height == 0)
|
|
return false;
|
|
|
|
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);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RenderEngine::CaptureOutputFrameRgbaTopDownOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels)
|
|
{
|
|
std::vector<unsigned char> bottomUpPixels;
|
|
if (!ReadOutputFrameRgbaOnRenderThread(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;
|
|
}
|