Phase 4
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m43s
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
Aiden
2026-05-11 18:25:47 +10:00
parent 20476bdf63
commit bfc32c4a1e
10 changed files with 313 additions and 119 deletions

View File

@@ -3,11 +3,11 @@
#include <gl/gl.h>
#include <algorithm>
#include <cstring>
RenderEngine::RenderEngine(
RuntimeSnapshotProvider& runtimeSnapshotProvider,
HealthTelemetry& healthTelemetry,
CRITICAL_SECTION& mutex,
HDC hdc,
HGLRC hglrc,
RenderEffectCallback renderEffect,
@@ -17,7 +17,6 @@ RenderEngine::RenderEngine(
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)
@@ -136,6 +135,24 @@ void RenderEngine::ReportRenderThreadRequestFailure(const char* operationName, c
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]() {
@@ -241,11 +258,13 @@ void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderRe
void RenderEngine::ResetTemporalHistoryStateOnRenderThread()
{
ReportWrongThreadRenderAccess("reset-temporal-history");
mShaderPrograms.ResetTemporalHistoryState();
}
void RenderEngine::ResetShaderFeedbackStateOnRenderThread()
{
ReportWrongThreadRenderAccess("reset-shader-feedback");
mShaderPrograms.ResetShaderFeedbackState();
}
@@ -276,6 +295,124 @@ void RenderEngine::ProcessRenderResetCommandsOnRenderThread()
ApplyRenderResetOnRenderThread(resetScope);
}
void RenderEngine::EnqueuePreviewPresentWake()
{
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
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()
{
mRuntimeLiveState.Clear();
@@ -323,32 +460,62 @@ bool RenderEngine::TryPresentPreview(bool force, unsigned previewFps, unsigned o
if (mRenderThreadRunning)
{
return TryInvokeOnRenderThread("preview-present", [this, outputFrameWidth, outputFrameHeight]() {
mRenderCommandQueue.RequestPreviewPresent({ outputFrameWidth, outputFrameHeight });
RenderPreviewPresentRequest request;
return mRenderCommandQueue.TryTakePreviewPresent(request) &&
PresentPreviewOnRenderThread(request.outputFrameWidth, request.outputFrameHeight);
});
mRenderCommandQueue.RequestPreviewPresent({ outputFrameWidth, outputFrameHeight });
if (GetCurrentThreadId() == mRenderThreadId)
ProcessPreviewPresentCommandsOnRenderThread();
else
EnqueuePreviewPresentWake();
return true;
}
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;
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::TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
{
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
@@ -364,21 +531,13 @@ bool RenderEngine::TryUploadInputFrame(const VideoIOFrame& inputFrame, const Vid
});
}
if (!TryEnterCriticalSection(&mMutex))
return false;
wglMakeCurrent(mHdc, mHglrc);
mRenderCommandQueue.RequestInputUpload({ inputFrame, videoState });
RenderInputUploadRequest request;
const bool uploaded = mRenderCommandQueue.TryTakeInputUpload(request) &&
UploadInputFrameOnRenderThread(request.inputFrame, request.videoState);
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex);
return uploaded;
ReportRenderThreadRequestFailure("input-upload", "render thread is not running");
return false;
}
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);
@@ -395,7 +554,7 @@ bool RenderEngine::UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame
return true;
}
bool RenderEngine::RenderOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
bool RenderEngine::RequestOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
{
if (mRenderThreadRunning)
{
@@ -407,20 +566,20 @@ bool RenderEngine::RenderOutputFrame(const RenderPipelineFrameContext& context,
});
}
EnterCriticalSection(&mMutex);
wglMakeCurrent(mHdc, mHglrc);
mRenderCommandQueue.RequestOutputFrame({ context.videoState, context.completion });
RenderOutputFrameRequest request;
const bool rendered = mRenderCommandQueue.TryTakeOutputFrame(request) &&
RenderOutputFrameOnRenderThread({ request.videoState, request.completion }, outputFrame);
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex);
return rendered;
ReportRenderThreadRequestFailure("output-render", "render thread is not running");
return false;
}
bool RenderEngine::RenderOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
{
return RequestOutputFrame(context, outputFrame);
}
bool RenderEngine::RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
{
ReportWrongThreadRenderAccess("output-render");
ProcessRenderResetCommandsOnRenderThread();
ProcessInputUploadCommandsOnRenderThread();
return mRenderPipeline.RenderFrame(context, outputFrame);
}
@@ -466,6 +625,7 @@ void RenderEngine::RenderLayerStack(
VideoIOPixelFormat inputPixelFormat,
unsigned historyCap)
{
ReportWrongThreadRenderAccess("render-layer-stack");
mRenderPass.Render(
hasInputSource,
layerStates,
@@ -484,6 +644,7 @@ void RenderEngine::RenderLayerStack(
bool RenderEngine::ReadOutputFrameRgbaOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels)
{
ReportWrongThreadRenderAccess("read-output-frame-rgba");
if (width == 0 || height == 0)
return false;
@@ -510,15 +671,8 @@ bool RenderEngine::CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height
});
}
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;
ReportRenderThreadRequestFailure("screenshot-capture", "render thread is not running");
return false;
}
bool RenderEngine::CaptureOutputFrameRgbaTopDownOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels)