input testing
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 3m2s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
Aiden
2026-05-12 20:06:23 +10:00
parent 2c5e925b97
commit ce28904891
19 changed files with 911 additions and 198 deletions

View File

@@ -1,5 +1,7 @@
#include "InputFrameTexture.h"
#include <chrono>
InputFrameTexture::~InputFrameTexture()
{
ShutdownGl();
@@ -14,28 +16,24 @@ GLuint InputFrameTexture::PollAndUpload(InputFrameMailbox* mailbox)
if (!mailbox->TryAcquireLatest(frame))
{
++mUploadMisses;
mLastUploadMilliseconds = 0.0;
return mTexture;
}
if (frame.bytes != nullptr && frame.pixelFormat == VideoIOPixelFormat::Bgra8 && EnsureTexture(frame))
{
glBindTexture(GL_TEXTURE_2D, mTexture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH, frame.rowBytes > 0 ? static_cast<GLint>(frame.rowBytes / 4) : 0);
glTexSubImage2D(
GL_TEXTURE_2D,
0,
0,
0,
static_cast<GLsizei>(frame.width),
static_cast<GLsizei>(frame.height),
GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV,
frame.bytes);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindTexture(GL_TEXTURE_2D, 0);
mLastFrameFormatSupported = true;
const auto uploadStart = std::chrono::steady_clock::now();
UploadBgra8FrameFlippedVertically(frame);
const auto uploadEnd = std::chrono::steady_clock::now();
mLastUploadMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(uploadEnd - uploadStart).count();
++mUploadedFrames;
}
else
{
mLastFrameFormatSupported = frame.pixelFormat == VideoIOPixelFormat::Bgra8;
mLastUploadMilliseconds = 0.0;
}
mailbox->Release(frame);
return mTexture;
@@ -81,3 +79,30 @@ bool InputFrameTexture::EnsureTexture(const InputFrame& frame)
mHeight = frame.height;
return mTexture != 0;
}
void InputFrameTexture::UploadBgra8FrameFlippedVertically(const InputFrame& frame)
{
glBindTexture(GL_TEXTURE_2D, mTexture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH, frame.rowBytes > 0 ? static_cast<GLint>(frame.rowBytes / 4) : 0);
const unsigned char* sourceBytes = static_cast<const unsigned char*>(frame.bytes);
for (unsigned destinationY = 0; destinationY < frame.height; ++destinationY)
{
const unsigned sourceY = frame.height - 1u - destinationY;
const unsigned char* sourceRow = sourceBytes + static_cast<std::size_t>(sourceY) * static_cast<std::size_t>(frame.rowBytes);
glTexSubImage2D(
GL_TEXTURE_2D,
0,
0,
static_cast<GLint>(destinationY),
static_cast<GLsizei>(frame.width),
1,
GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV,
sourceRow);
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindTexture(GL_TEXTURE_2D, 0);
}

View File

@@ -17,14 +17,19 @@ public:
GLuint Texture() const { return mTexture; }
uint64_t UploadedFrames() const { return mUploadedFrames; }
uint64_t UploadMisses() const { return mUploadMisses; }
double LastUploadMilliseconds() const { return mLastUploadMilliseconds; }
bool LastFrameFormatSupported() const { return mLastFrameFormatSupported; }
void ShutdownGl();
private:
bool EnsureTexture(const InputFrame& frame);
void UploadBgra8FrameFlippedVertically(const InputFrame& frame);
GLuint mTexture = 0;
unsigned mWidth = 0;
unsigned mHeight = 0;
uint64_t mUploadedFrames = 0;
uint64_t mUploadMisses = 0;
double mLastUploadMilliseconds = 0.0;
bool mLastFrameFormatSupported = true;
};

View File

@@ -85,6 +85,12 @@ RenderThread::Metrics RenderThread::GetMetrics() const
metrics.skippedFrames = mSkippedFrames.load(std::memory_order_relaxed);
metrics.shaderBuildsCommitted = mShaderBuildsCommitted.load(std::memory_order_relaxed);
metrics.shaderBuildFailures = mShaderBuildFailures.load(std::memory_order_relaxed);
metrics.inputFramesReceived = mInputFramesReceived.load(std::memory_order_relaxed);
metrics.inputFramesDropped = mInputFramesDropped.load(std::memory_order_relaxed);
metrics.inputLatestAgeMilliseconds = mInputLatestAgeMilliseconds.load(std::memory_order_relaxed);
metrics.inputUploadMilliseconds = mInputUploadMilliseconds.load(std::memory_order_relaxed);
metrics.inputFormatSupported = mInputFormatSupported.load(std::memory_order_relaxed);
metrics.inputSignalPresent = mInputSignalPresent.load(std::memory_order_relaxed);
return metrics;
}
@@ -156,6 +162,7 @@ void RenderThread::ThreadMain()
TryCommitReadyRuntimeShader(runtimeRenderScene);
const GLuint videoInputTexture = inputTexture.PollAndUpload(mInputMailbox);
PublishInputMetrics(inputTexture);
if (!readback.RenderAndQueue(frameIndex, [this, &renderer, &runtimeRenderScene, videoInputTexture](uint64_t index) {
if (runtimeRenderScene.HasLayers())
runtimeRenderScene.RenderFrame(index, mConfig.width, mConfig.height, videoInputTexture);
@@ -226,6 +233,28 @@ void RenderThread::CountAcquireMiss()
mAcquireMisses.fetch_add(1, std::memory_order_relaxed);
}
void RenderThread::PublishInputMetrics(const InputFrameTexture& inputTexture)
{
if (mInputMailbox != nullptr)
{
const InputFrameMailboxMetrics mailboxMetrics = mInputMailbox->Metrics();
mInputFramesReceived.store(mailboxMetrics.submittedFrames, std::memory_order_relaxed);
mInputFramesDropped.store(mailboxMetrics.droppedReadyFrames + mailboxMetrics.submitMisses, std::memory_order_relaxed);
mInputLatestAgeMilliseconds.store(mailboxMetrics.latestFrameAgeMilliseconds, std::memory_order_relaxed);
mInputSignalPresent.store(mailboxMetrics.hasSubmittedFrame, std::memory_order_relaxed);
}
else
{
mInputFramesReceived.store(0, std::memory_order_relaxed);
mInputFramesDropped.store(0, std::memory_order_relaxed);
mInputLatestAgeMilliseconds.store(0.0, std::memory_order_relaxed);
mInputSignalPresent.store(false, std::memory_order_relaxed);
}
mInputUploadMilliseconds.store(inputTexture.LastUploadMilliseconds(), std::memory_order_relaxed);
mInputFormatSupported.store(inputTexture.LastFrameFormatSupported(), std::memory_order_relaxed);
}
void RenderThread::SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact)
{
if (artifact.fragmentShaderSource.empty())

View File

@@ -15,6 +15,7 @@
class SystemFrameExchange;
class InputFrameMailbox;
class InputFrameTexture;
class RenderThread
{
@@ -37,6 +38,12 @@ public:
uint64_t skippedFrames = 0;
uint64_t shaderBuildsCommitted = 0;
uint64_t shaderBuildFailures = 0;
uint64_t inputFramesReceived = 0;
uint64_t inputFramesDropped = 0;
double inputLatestAgeMilliseconds = 0.0;
double inputUploadMilliseconds = 0.0;
bool inputFormatSupported = true;
bool inputSignalPresent = false;
};
RenderThread(SystemFrameExchange& frameExchange, Config config);
@@ -60,6 +67,7 @@ private:
void CountRendered();
void CountCompleted();
void CountAcquireMiss();
void PublishInputMetrics(const InputFrameTexture& inputTexture);
void TryCommitReadyRuntimeShader(RuntimeRenderScene& runtimeRenderScene);
bool TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact);
bool TryTakePendingRenderLayers(std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers);
@@ -84,6 +92,12 @@ private:
std::atomic<uint64_t> mSkippedFrames{ 0 };
std::atomic<uint64_t> mShaderBuildsCommitted{ 0 };
std::atomic<uint64_t> mShaderBuildFailures{ 0 };
std::atomic<uint64_t> mInputFramesReceived{ 0 };
std::atomic<uint64_t> mInputFramesDropped{ 0 };
std::atomic<double> mInputLatestAgeMilliseconds{ 0.0 };
std::atomic<double> mInputUploadMilliseconds{ 0.0 };
std::atomic<bool> mInputFormatSupported{ true };
std::atomic<bool> mInputSignalPresent{ false };
std::mutex mShaderArtifactMutex;
bool mHasPendingShaderArtifact = false;

View File

@@ -159,6 +159,9 @@ void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsign
return;
}
// Shader source contract:
// - gVideoInput is the raw/latest input texture for every layer in the stack.
// - gLayerInput starts as gVideoInput for the first layer, then becomes the previous layer output.
GLuint layerInputTexture = videoInputTexture;
std::size_t nextTargetIndex = 0;
for (std::size_t layerIndex = 0; layerIndex < readyLayers.size(); ++layerIndex)
@@ -324,7 +327,7 @@ GLuint RuntimeRenderScene::RenderLayer(
if (!pass.renderer || !pass.renderer->HasProgram())
continue;
GLuint sourceTexture = layerInputTexture;
GLuint sourceTexture = videoInputTexture;
if (!pass.inputNames.empty())
{
const std::string& inputName = pass.inputNames.front();
@@ -334,6 +337,9 @@ GLuint RuntimeRenderScene::RenderLayer(
}
else if (inputName != "layerInput")
{
// Named intermediate pass inputs currently use the gVideoInput binding slot as the
// selected pass source. Layer stack shaders should use gLayerInput for previous-layer
// sampling and gVideoInput for the original input frame.
for (std::size_t index = 0; index < 2; ++index)
{
if (namedOutputNames[index] == inputName)