Clean up
Some checks failed
CI / Native Windows Build And Tests (push) Failing after 16s
CI / React UI Build (push) Successful in 38s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
2026-05-18 14:19:29 +10:00
parent 3ffb562ff7
commit f461a05c65
222 changed files with 0 additions and 45423 deletions

View File

@@ -0,0 +1,341 @@
#include "InputFrameTexture.h"
#include <chrono>
#ifndef GL_FRAMEBUFFER_BINDING
#define GL_FRAMEBUFFER_BINDING 0x8CA6
#endif
namespace
{
constexpr GLuint kUyvyTextureUnit = 0;
const char* kDecodeVertexShader = R"GLSL(
#version 430 core
out vec2 vTexCoord;
void main()
{
vec2 positions[3] = vec2[3](
vec2(-1.0, -1.0),
vec2( 3.0, -1.0),
vec2(-1.0, 3.0));
vec2 texCoords[3] = vec2[3](
vec2(0.0, 0.0),
vec2(2.0, 0.0),
vec2(0.0, 2.0));
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
vTexCoord = texCoords[gl_VertexID];
}
)GLSL";
const char* kUyvyDecodeFragmentShader = R"GLSL(
#version 430 core
layout(binding = 0) uniform sampler2D uPackedUyvy;
uniform vec2 uDecodedSize;
in vec2 vTexCoord;
out vec4 fragColor;
vec4 rec709YCbCr2rgba(float yByte, float cbByte, float crByte)
{
float y = (yByte - 16.0) / 219.0;
float cb = (cbByte - 16.0) / 224.0 - 0.5;
float cr = (crByte - 16.0) / 224.0 - 0.5;
return vec4(
y + 1.5748 * cr,
y - 0.1873 * cb - 0.4681 * cr,
y + 1.8556 * cb,
1.0);
}
void main()
{
ivec2 decodedSize = ivec2(uDecodedSize);
ivec2 outputCoord = ivec2(clamp(gl_FragCoord.xy, vec2(0.0), vec2(decodedSize - ivec2(1))));
int sourceY = decodedSize.y - 1 - outputCoord.y;
ivec2 packedCoord = ivec2(clamp(outputCoord.x / 2, 0, max(decodedSize.x / 2 - 1, 0)), sourceY);
vec4 macroPixel = texelFetch(uPackedUyvy, packedCoord, 0) * 255.0;
float ySample = (outputCoord.x & 1) != 0 ? macroPixel.a : macroPixel.g;
fragColor = clamp(rec709YCbCr2rgba(ySample, macroPixel.r, macroPixel.b), vec4(0.0), vec4(1.0));
}
)GLSL";
}
InputFrameTexture::~InputFrameTexture()
{
ShutdownGl();
}
GLuint InputFrameTexture::PollAndUpload(InputFrameMailbox* mailbox)
{
if (mailbox == nullptr)
return mTexture;
InputFrame frame;
if (!mailbox->TryAcquireOldest(frame))
{
++mUploadMisses;
mLastUploadMilliseconds = 0.0;
return mTexture;
}
if (frame.bytes != nullptr && frame.pixelFormat == VideoIOPixelFormat::Bgra8 && EnsureTexture(frame))
{
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 if (frame.bytes != nullptr && frame.pixelFormat == VideoIOPixelFormat::Uyvy8 && EnsureTexture(frame) && EnsureRawUyvyTexture(frame) && EnsureDecodeProgram())
{
mLastFrameFormatSupported = true;
const auto uploadStart = std::chrono::steady_clock::now();
UploadUyvy8Frame(frame);
DecodeUyvy8Frame(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 || frame.pixelFormat == VideoIOPixelFormat::Uyvy8;
mLastUploadMilliseconds = 0.0;
}
mailbox->Release(frame);
return mTexture;
}
void InputFrameTexture::ShutdownGl()
{
if (mTexture != 0)
glDeleteTextures(1, &mTexture);
if (mRawTexture != 0)
glDeleteTextures(1, &mRawTexture);
mTexture = 0;
mRawTexture = 0;
mWidth = 0;
mHeight = 0;
mRawWidth = 0;
mRawHeight = 0;
DestroyDecodeResources();
}
bool InputFrameTexture::EnsureTexture(const InputFrame& frame)
{
if (frame.width == 0 || frame.height == 0)
return false;
if (mTexture != 0 && mWidth == frame.width && mHeight == frame.height)
return true;
ShutdownGl();
glGenTextures(1, &mTexture);
glBindTexture(GL_TEXTURE_2D, mTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA8,
static_cast<GLsizei>(frame.width),
static_cast<GLsizei>(frame.height),
0,
GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV,
nullptr);
glBindTexture(GL_TEXTURE_2D, 0);
mWidth = frame.width;
mHeight = frame.height;
return mTexture != 0;
}
bool InputFrameTexture::EnsureRawUyvyTexture(const InputFrame& frame)
{
if (frame.width == 0 || frame.height == 0)
return false;
const unsigned rawWidth = (frame.width + 1u) / 2u;
if (mRawTexture != 0 && mRawWidth == rawWidth && mRawHeight == frame.height)
return true;
if (mRawTexture != 0)
glDeleteTextures(1, &mRawTexture);
mRawTexture = 0;
glGenTextures(1, &mRawTexture);
glBindTexture(GL_TEXTURE_2D, mRawTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA8,
static_cast<GLsizei>(rawWidth),
static_cast<GLsizei>(frame.height),
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
nullptr);
glBindTexture(GL_TEXTURE_2D, 0);
mRawWidth = rawWidth;
mRawHeight = frame.height;
return mRawTexture != 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);
}
void InputFrameTexture::UploadUyvy8Frame(const InputFrame& frame)
{
glBindTexture(GL_TEXTURE_2D, mRawTexture);
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 + 1u) / 2u),
static_cast<GLsizei>(frame.height),
GL_RGBA,
GL_UNSIGNED_BYTE,
frame.bytes);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindTexture(GL_TEXTURE_2D, 0);
}
void InputFrameTexture::DecodeUyvy8Frame(const InputFrame& frame)
{
GLint previousFramebuffer = 0;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
if (mDecodeFramebuffer == 0)
glGenFramebuffers(1, &mDecodeFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, mDecodeFramebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(previousFramebuffer));
return;
}
glViewport(0, 0, static_cast<GLsizei>(frame.width), static_cast<GLsizei>(frame.height));
glDisable(GL_SCISSOR_TEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glUseProgram(mDecodeProgram);
const GLint decodedSizeLocation = glGetUniformLocation(mDecodeProgram, "uDecodedSize");
if (decodedSizeLocation >= 0)
glUniform2f(decodedSizeLocation, static_cast<GLfloat>(frame.width), static_cast<GLfloat>(frame.height));
glActiveTexture(GL_TEXTURE0 + kUyvyTextureUnit);
glBindTexture(GL_TEXTURE_2D, mRawTexture);
glBindVertexArray(mDecodeVertexArray);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glUseProgram(0);
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(previousFramebuffer));
}
bool InputFrameTexture::EnsureDecodeProgram()
{
if (mDecodeProgram != 0)
return true;
if (!CompileShader(GL_VERTEX_SHADER, kDecodeVertexShader, mDecodeVertexShader))
return false;
if (!CompileShader(GL_FRAGMENT_SHADER, kUyvyDecodeFragmentShader, mDecodeFragmentShader))
return false;
if (!LinkProgram(mDecodeVertexShader, mDecodeFragmentShader, mDecodeProgram))
return false;
glUseProgram(mDecodeProgram);
const GLint samplerLocation = glGetUniformLocation(mDecodeProgram, "uPackedUyvy");
if (samplerLocation >= 0)
glUniform1i(samplerLocation, static_cast<GLint>(kUyvyTextureUnit));
glUseProgram(0);
if (mDecodeVertexArray == 0)
glGenVertexArrays(1, &mDecodeVertexArray);
return mDecodeProgram != 0 && mDecodeVertexArray != 0;
}
void InputFrameTexture::DestroyDecodeResources()
{
if (mDecodeFramebuffer != 0)
glDeleteFramebuffers(1, &mDecodeFramebuffer);
if (mDecodeVertexArray != 0)
glDeleteVertexArrays(1, &mDecodeVertexArray);
if (mDecodeProgram != 0)
glDeleteProgram(mDecodeProgram);
if (mDecodeVertexShader != 0)
glDeleteShader(mDecodeVertexShader);
if (mDecodeFragmentShader != 0)
glDeleteShader(mDecodeFragmentShader);
mDecodeFramebuffer = 0;
mDecodeVertexArray = 0;
mDecodeProgram = 0;
mDecodeVertexShader = 0;
mDecodeFragmentShader = 0;
}
bool InputFrameTexture::CompileShader(GLenum shaderType, const char* source, GLuint& shader)
{
shader = glCreateShader(shaderType);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint compileResult = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
if (compileResult != GL_FALSE)
return true;
glDeleteShader(shader);
shader = 0;
return false;
}
bool InputFrameTexture::LinkProgram(GLuint vertexShader, GLuint fragmentShader, GLuint& program)
{
program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
GLint linkResult = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linkResult);
if (linkResult != GL_FALSE)
return true;
glDeleteProgram(program);
program = 0;
return false;
}

View File

@@ -0,0 +1,50 @@
#pragma once
#include "../frames/InputFrameMailbox.h"
#include "GLExtensions.h"
#include <cstdint>
class InputFrameTexture
{
public:
InputFrameTexture() = default;
InputFrameTexture(const InputFrameTexture&) = delete;
InputFrameTexture& operator=(const InputFrameTexture&) = delete;
~InputFrameTexture();
GLuint PollAndUpload(InputFrameMailbox* mailbox);
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);
bool EnsureRawUyvyTexture(const InputFrame& frame);
bool EnsureDecodeProgram();
void UploadBgra8FrameFlippedVertically(const InputFrame& frame);
void UploadUyvy8Frame(const InputFrame& frame);
void DecodeUyvy8Frame(const InputFrame& frame);
void DestroyDecodeResources();
static bool CompileShader(GLenum shaderType, const char* source, GLuint& shader);
static bool LinkProgram(GLuint vertexShader, GLuint fragmentShader, GLuint& program);
GLuint mTexture = 0;
GLuint mRawTexture = 0;
GLuint mDecodeFramebuffer = 0;
GLuint mDecodeVertexArray = 0;
GLuint mDecodeProgram = 0;
GLuint mDecodeVertexShader = 0;
GLuint mDecodeFragmentShader = 0;
unsigned mWidth = 0;
unsigned mHeight = 0;
unsigned mRawWidth = 0;
unsigned mRawHeight = 0;
uint64_t mUploadedFrames = 0;
uint64_t mUploadMisses = 0;
double mLastUploadMilliseconds = 0.0;
bool mLastFrameFormatSupported = true;
};

View File

@@ -0,0 +1,49 @@
#include "RenderCadenceClock.h"
#include <algorithm>
RenderCadenceClock::RenderCadenceClock(double frameDurationMilliseconds)
{
mFrameDuration = std::chrono::duration_cast<Duration>(std::chrono::duration<double, std::milli>(frameDurationMilliseconds));
if (mFrameDuration <= Duration::zero())
mFrameDuration = std::chrono::milliseconds(16);
Reset();
}
void RenderCadenceClock::Reset(TimePoint now)
{
mNextRenderTime = now;
mPendingFrameAdvance = 1;
mOverrunCount = 0;
mSkippedFrameCount = 0;
}
RenderCadenceClock::Tick RenderCadenceClock::Poll(TimePoint now)
{
Tick tick;
if (now < mNextRenderTime)
{
tick.sleepFor = std::min(Duration(std::chrono::milliseconds(1)), mNextRenderTime - now);
return tick;
}
tick.due = true;
mPendingFrameAdvance = 1;
const Duration lateBy = now - mNextRenderTime;
if (lateBy > mFrameDuration)
{
tick.skippedFrames = static_cast<uint64_t>(lateBy / mFrameDuration);
mPendingFrameAdvance += tick.skippedFrames;
++mOverrunCount;
mSkippedFrameCount += tick.skippedFrames;
}
return tick;
}
void RenderCadenceClock::MarkRendered(TimePoint now)
{
mNextRenderTime += mFrameDuration * mPendingFrameAdvance;
mPendingFrameAdvance = 1;
if (now - mNextRenderTime > mFrameDuration * 4)
mNextRenderTime = now + mFrameDuration;
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <chrono>
#include <cstdint>
class RenderCadenceClock
{
public:
using Clock = std::chrono::steady_clock;
using Duration = Clock::duration;
using TimePoint = Clock::time_point;
struct Tick
{
bool due = false;
uint64_t skippedFrames = 0;
Duration sleepFor = Duration::zero();
};
explicit RenderCadenceClock(double frameDurationMilliseconds = 1000.0 / 60.0);
void Reset(TimePoint now = Clock::now());
Tick Poll(TimePoint now = Clock::now());
void MarkRendered(TimePoint now = Clock::now());
Duration FrameDuration() const { return mFrameDuration; }
TimePoint NextRenderTime() const { return mNextRenderTime; }
uint64_t OverrunCount() const { return mOverrunCount; }
uint64_t SkippedFrameCount() const { return mSkippedFrameCount; }
private:
Duration mFrameDuration;
TimePoint mNextRenderTime = Clock::now();
uint64_t mPendingFrameAdvance = 1;
uint64_t mOverrunCount = 0;
uint64_t mSkippedFrameCount = 0;
};

389
src/render/RenderThread.cpp Normal file
View File

@@ -0,0 +1,389 @@
#include "RenderThread.h"
#include "../frames/InputFrameMailbox.h"
#include "../frames/SystemFrameExchange.h"
#include "../frames/SystemFrameTypes.h"
#include "../logging/Logger.h"
#include "../platform/HiddenGlWindow.h"
#include "InputFrameTexture.h"
#include "readback/Bgra8ReadbackPipeline.h"
#include "GLExtensions.h"
#include "runtime/RuntimeRenderScene.h"
#include "runtime/RuntimeShaderRenderer.h"
#include "SimpleMotionRenderer.h"
#include <algorithm>
#include <memory>
#include <thread>
RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) :
mFrameExchange(frameExchange),
mConfig(config)
{
}
RenderThread::RenderThread(SystemFrameExchange& frameExchange, InputFrameMailbox* inputMailbox, Config config) :
mFrameExchange(frameExchange),
mInputMailbox(inputMailbox),
mConfig(config)
{
}
RenderThread::~RenderThread()
{
Stop();
}
bool RenderThread::Start(std::string& error)
{
if (mThread.joinable())
return true;
{
std::lock_guard<std::mutex> lock(mStartupMutex);
mStarted = false;
mStartupError.clear();
}
mStopping.store(false, std::memory_order_release);
mThread = std::thread([this]() { ThreadMain(); });
std::unique_lock<std::mutex> lock(mStartupMutex);
if (!mStartupCondition.wait_for(lock, std::chrono::seconds(3), [this]() {
return mStarted || !mStartupError.empty();
}))
{
error = "Timed out starting render thread.";
return false;
}
if (!mStartupError.empty())
{
error = mStartupError;
lock.unlock();
if (mThread.joinable())
mThread.join();
return false;
}
return true;
}
void RenderThread::Stop()
{
mStopping.store(true, std::memory_order_release);
if (mThread.joinable())
mThread.join();
}
RenderThread::Metrics RenderThread::GetMetrics() const
{
Metrics metrics;
metrics.renderedFrames = mRenderedFrames.load(std::memory_order_relaxed);
metrics.completedReadbacks = mCompletedReadbacks.load(std::memory_order_relaxed);
metrics.acquireMisses = mAcquireMisses.load(std::memory_order_relaxed);
metrics.pboQueueMisses = mPboQueueMisses.load(std::memory_order_relaxed);
metrics.clockOverruns = mClockOverruns.load(std::memory_order_relaxed);
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.renderFrameMilliseconds = mRenderFrameMilliseconds.load(std::memory_order_relaxed);
metrics.renderFrameBudgetUsedPercent = mRenderFrameBudgetUsedPercent.load(std::memory_order_relaxed);
metrics.renderFrameMaxMilliseconds = mRenderFrameMaxMilliseconds.load(std::memory_order_relaxed);
metrics.readbackQueueMilliseconds = mReadbackQueueMilliseconds.load(std::memory_order_relaxed);
metrics.completedReadbackCopyMilliseconds = mCompletedReadbackCopyMilliseconds.load(std::memory_order_relaxed);
metrics.inputFramesReceived = mInputFramesReceived.load(std::memory_order_relaxed);
metrics.inputFramesDropped = mInputFramesDropped.load(std::memory_order_relaxed);
metrics.inputConsumeMisses = mInputConsumeMisses.load(std::memory_order_relaxed);
metrics.inputUploadMisses = mInputUploadMisses.load(std::memory_order_relaxed);
metrics.inputReadyFrames = mInputReadyFrames.load(std::memory_order_relaxed);
metrics.inputReadingFrames = mInputReadingFrames.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;
}
void RenderThread::ThreadMain()
{
RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Log, "render-thread", "Render thread starting.");
HiddenGlWindow window;
std::string error;
if (!window.Create(mConfig.width, mConfig.height, error))
{
SignalStartupFailure(error.empty() ? "OpenGL context creation failed." : error);
return;
}
std::unique_ptr<HiddenGlWindow> prepareWindow = std::make_unique<HiddenGlWindow>();
if (!prepareWindow->CreateShared(mConfig.width, mConfig.height, window.DeviceContext(), window.Context(), error))
{
SignalStartupFailure(error.empty() ? "Runtime shader prepare shared context creation failed." : error);
return;
}
if (!window.MakeCurrent())
{
SignalStartupFailure("OpenGL context creation failed.");
return;
}
if (!ResolveGLExtensions())
{
SignalStartupFailure("OpenGL extension resolution failed.");
return;
}
SimpleMotionRenderer renderer;
RuntimeRenderScene runtimeRenderScene;
Bgra8ReadbackPipeline readback;
InputFrameTexture inputTexture;
if (!runtimeRenderScene.StartPrepareWorker(std::move(prepareWindow), error))
{
SignalStartupFailure(error.empty() ? "Runtime shader prepare worker initialization failed." : error);
return;
}
if (!renderer.InitializeGl(mConfig.width, mConfig.height) || !readback.Initialize(mConfig.width, mConfig.height, mConfig.pboDepth))
{
SignalStartupFailure("Render pipeline initialization failed.");
return;
}
RenderCadenceClock clock(mConfig.frameDurationMilliseconds);
uint64_t frameIndex = 0;
mRunning.store(true, std::memory_order_release);
SignalStarted();
while (!mStopping.load(std::memory_order_acquire))
{
readback.ConsumeCompleted(
[this](SystemFrame& frame) { return mFrameExchange.AcquireForRender(frame); },
[this](const SystemFrame& frame) { return mFrameExchange.PublishCompleted(frame); },
[this]() {
CountAcquireMiss();
},
[this]() { CountCompleted(); });
PublishReadbackMetrics(readback);
const auto now = RenderCadenceClock::Clock::now();
const RenderCadenceClock::Tick tick = clock.Poll(now);
if (!tick.due)
{
if (tick.sleepFor > RenderCadenceClock::Duration::zero())
std::this_thread::sleep_for(tick.sleepFor);
continue;
}
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);
else if (videoInputTexture != 0)
renderer.RenderTexture(videoInputTexture);
else
renderer.RenderFrame(index);
}))
{
mPboQueueMisses.fetch_add(1, std::memory_order_relaxed);
}
PublishReadbackMetrics(readback);
CountRendered();
++frameIndex;
clock.MarkRendered(RenderCadenceClock::Clock::now());
mClockOverruns.store(clock.OverrunCount(), std::memory_order_relaxed);
mSkippedFrames.store(clock.SkippedFrameCount(), std::memory_order_relaxed);
}
for (std::size_t i = 0; i < mConfig.pboDepth * 2; ++i)
{
readback.ConsumeCompleted(
[this](SystemFrame& frame) { return mFrameExchange.AcquireForRender(frame); },
[this](const SystemFrame& frame) { return mFrameExchange.PublishCompleted(frame); },
[this]() {
CountAcquireMiss();
},
[this]() { CountCompleted(); });
PublishReadbackMetrics(readback);
}
readback.Shutdown();
inputTexture.ShutdownGl();
runtimeRenderScene.ShutdownGl();
renderer.ShutdownGl();
window.ClearCurrent();
mRunning.store(false, std::memory_order_release);
RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Log, "render-thread", "Render thread stopped.");
}
void RenderThread::SignalStarted()
{
std::lock_guard<std::mutex> lock(mStartupMutex);
mStarted = true;
mStartupCondition.notify_all();
}
void RenderThread::SignalStartupFailure(const std::string& error)
{
RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Error, "render-thread", error);
std::lock_guard<std::mutex> lock(mStartupMutex);
mStartupError = error;
mStartupCondition.notify_all();
}
void RenderThread::CountRendered()
{
mRenderedFrames.fetch_add(1, std::memory_order_relaxed);
}
void RenderThread::CountCompleted()
{
mCompletedReadbacks.fetch_add(1, std::memory_order_relaxed);
}
void RenderThread::CountAcquireMiss()
{
mAcquireMisses.fetch_add(1, std::memory_order_relaxed);
}
void RenderThread::PublishReadbackMetrics(const Bgra8ReadbackPipeline& readback)
{
const double renderMilliseconds = readback.LastRenderFrameMilliseconds();
mRenderFrameMilliseconds.store(renderMilliseconds, std::memory_order_relaxed);
if (mConfig.frameDurationMilliseconds > 0.0)
{
mRenderFrameBudgetUsedPercent.store(
(renderMilliseconds / mConfig.frameDurationMilliseconds) * 100.0,
std::memory_order_relaxed);
}
else
{
mRenderFrameBudgetUsedPercent.store(0.0, std::memory_order_relaxed);
}
const double previousMax = mRenderFrameMaxMilliseconds.load(std::memory_order_relaxed);
if (renderMilliseconds > previousMax)
mRenderFrameMaxMilliseconds.store(renderMilliseconds, std::memory_order_relaxed);
mReadbackQueueMilliseconds.store(readback.LastReadbackQueueMilliseconds(), std::memory_order_relaxed);
mCompletedReadbackCopyMilliseconds.store(readback.LastCompletedReadbackCopyMilliseconds(), 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);
mInputConsumeMisses.store(mailboxMetrics.consumeMisses, std::memory_order_relaxed);
mInputReadyFrames.store(mailboxMetrics.readyCount, std::memory_order_relaxed);
mInputReadingFrames.store(mailboxMetrics.readingCount, 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);
mInputConsumeMisses.store(0, std::memory_order_relaxed);
mInputReadyFrames.store(0, std::memory_order_relaxed);
mInputReadingFrames.store(0, std::memory_order_relaxed);
mInputLatestAgeMilliseconds.store(0.0, std::memory_order_relaxed);
mInputSignalPresent.store(false, std::memory_order_relaxed);
}
mInputUploadMisses.store(inputTexture.UploadMisses(), 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())
return;
std::lock_guard<std::mutex> lock(mShaderArtifactMutex);
mPendingShaderArtifact = artifact;
mHasPendingShaderArtifact = true;
}
void RenderThread::SubmitRuntimeRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers)
{
std::lock_guard<std::mutex> lock(mRenderLayersMutex);
mPendingRenderLayers = layers;
mHasPendingRenderLayers = true;
}
bool RenderThread::TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact)
{
std::lock_guard<std::mutex> lock(mShaderArtifactMutex);
if (!mHasPendingShaderArtifact)
return false;
artifact = std::move(mPendingShaderArtifact);
mPendingShaderArtifact = RuntimeShaderArtifact();
mHasPendingShaderArtifact = false;
return true;
}
bool RenderThread::TryTakePendingRenderLayers(std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers)
{
std::lock_guard<std::mutex> lock(mRenderLayersMutex);
if (!mHasPendingRenderLayers)
return false;
layers = std::move(mPendingRenderLayers);
mPendingRenderLayers.clear();
mHasPendingRenderLayers = false;
return true;
}
void RenderThread::TryCommitReadyRuntimeShader(RuntimeRenderScene& runtimeRenderScene)
{
std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> layers;
std::string commitError;
if (TryTakePendingRenderLayers(layers))
{
if (!runtimeRenderScene.CommitRenderLayers(layers, commitError))
{
RenderCadenceCompositor::TryLog(
RenderCadenceCompositor::LogLevel::Error,
"render-thread",
"Runtime render-layer commit failed: " + commitError);
mShaderBuildFailures.fetch_add(1, std::memory_order_relaxed);
return;
}
RenderCadenceCompositor::TryLog(
RenderCadenceCompositor::LogLevel::Log,
"render-thread",
"Runtime render layer snapshot committed.");
mShaderBuildsCommitted.fetch_add(1, std::memory_order_relaxed);
return;
}
RuntimeShaderArtifact artifact;
if (!TryTakePendingRuntimeShaderArtifact(artifact))
return;
RenderCadenceCompositor::RuntimeRenderLayerModel layer;
layer.id = artifact.layerId.empty() ? "runtime-layer-1" : artifact.layerId;
layer.shaderId = artifact.shaderId;
layer.artifact = artifact;
layers.push_back(std::move(layer));
if (!runtimeRenderScene.CommitRenderLayers(layers, commitError))
{
RenderCadenceCompositor::TryLog(
RenderCadenceCompositor::LogLevel::Error,
"render-thread",
"Runtime shader GL commit failed: " + commitError);
mShaderBuildFailures.fetch_add(1, std::memory_order_relaxed);
return;
}
RenderCadenceCompositor::TryLog(
RenderCadenceCompositor::LogLevel::Log,
"render-thread",
"Runtime shader committed: " + artifact.shaderId + ". " + artifact.message);
mShaderBuildsCommitted.fetch_add(1, std::memory_order_relaxed);
}

129
src/render/RenderThread.h Normal file
View File

@@ -0,0 +1,129 @@
#pragma once
#include "RenderCadenceClock.h"
#include "../runtime/RuntimeLayerModel.h"
#include "../runtime/RuntimeShaderArtifact.h"
#include "runtime/RuntimeRenderScene.h"
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <string>
#include <thread>
class SystemFrameExchange;
class InputFrameMailbox;
class InputFrameTexture;
class Bgra8ReadbackPipeline;
class RenderThread
{
public:
struct Config
{
unsigned width = 1920;
unsigned height = 1080;
double frameDurationMilliseconds = 1000.0 / 59.94;
std::size_t pboDepth = 6;
};
struct Metrics
{
uint64_t renderedFrames = 0;
uint64_t completedReadbacks = 0;
uint64_t acquireMisses = 0;
uint64_t pboQueueMisses = 0;
uint64_t clockOverruns = 0;
uint64_t skippedFrames = 0;
uint64_t shaderBuildsCommitted = 0;
uint64_t shaderBuildFailures = 0;
double renderFrameMilliseconds = 0.0;
double renderFrameBudgetUsedPercent = 0.0;
double renderFrameMaxMilliseconds = 0.0;
double readbackQueueMilliseconds = 0.0;
double completedReadbackCopyMilliseconds = 0.0;
uint64_t inputFramesReceived = 0;
uint64_t inputFramesDropped = 0;
uint64_t inputConsumeMisses = 0;
uint64_t inputUploadMisses = 0;
std::size_t inputReadyFrames = 0;
std::size_t inputReadingFrames = 0;
double inputLatestAgeMilliseconds = 0.0;
double inputUploadMilliseconds = 0.0;
bool inputFormatSupported = true;
bool inputSignalPresent = false;
};
RenderThread(SystemFrameExchange& frameExchange, Config config);
RenderThread(SystemFrameExchange& frameExchange, InputFrameMailbox* inputMailbox, Config config);
RenderThread(const RenderThread&) = delete;
RenderThread& operator=(const RenderThread&) = delete;
~RenderThread();
bool Start(std::string& error);
void Stop();
void SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact);
void SubmitRuntimeRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers);
Metrics GetMetrics() const;
bool IsRunning() const { return mRunning.load(std::memory_order_acquire); }
private:
void ThreadMain();
void SignalStarted();
void SignalStartupFailure(const std::string& error);
void CountRendered();
void CountCompleted();
void CountAcquireMiss();
void PublishReadbackMetrics(const Bgra8ReadbackPipeline& readback);
void PublishInputMetrics(const InputFrameTexture& inputTexture);
void TryCommitReadyRuntimeShader(RuntimeRenderScene& runtimeRenderScene);
bool TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact);
bool TryTakePendingRenderLayers(std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers);
SystemFrameExchange& mFrameExchange;
InputFrameMailbox* mInputMailbox = nullptr;
Config mConfig;
std::thread mThread;
std::atomic<bool> mStopping{ false };
std::atomic<bool> mRunning{ false };
mutable std::mutex mStartupMutex;
std::condition_variable mStartupCondition;
bool mStarted = false;
std::string mStartupError;
std::atomic<uint64_t> mRenderedFrames{ 0 };
std::atomic<uint64_t> mCompletedReadbacks{ 0 };
std::atomic<uint64_t> mAcquireMisses{ 0 };
std::atomic<uint64_t> mPboQueueMisses{ 0 };
std::atomic<uint64_t> mClockOverruns{ 0 };
std::atomic<uint64_t> mSkippedFrames{ 0 };
std::atomic<uint64_t> mShaderBuildsCommitted{ 0 };
std::atomic<uint64_t> mShaderBuildFailures{ 0 };
std::atomic<double> mRenderFrameMilliseconds{ 0.0 };
std::atomic<double> mRenderFrameBudgetUsedPercent{ 0.0 };
std::atomic<double> mRenderFrameMaxMilliseconds{ 0.0 };
std::atomic<double> mReadbackQueueMilliseconds{ 0.0 };
std::atomic<double> mCompletedReadbackCopyMilliseconds{ 0.0 };
std::atomic<uint64_t> mInputFramesReceived{ 0 };
std::atomic<uint64_t> mInputFramesDropped{ 0 };
std::atomic<uint64_t> mInputConsumeMisses{ 0 };
std::atomic<uint64_t> mInputUploadMisses{ 0 };
std::atomic<std::size_t> mInputReadyFrames{ 0 };
std::atomic<std::size_t> mInputReadingFrames{ 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;
RuntimeShaderArtifact mPendingShaderArtifact;
std::mutex mRenderLayersMutex;
bool mHasPendingRenderLayers = false;
std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> mPendingRenderLayers;
};

View File

@@ -0,0 +1,313 @@
#include "SimpleMotionRenderer.h"
#include "GLExtensions.h"
#include <algorithm>
#include <array>
#include <cmath>
namespace
{
constexpr GLuint kInputTextureUnit = 0;
const char* kTextureVertexShader = R"GLSL(
#version 430 core
out vec2 vTexCoord;
void main()
{
vec2 positions[3] = vec2[3](
vec2(-1.0, -1.0),
vec2( 3.0, -1.0),
vec2(-1.0, 3.0));
vec2 texCoords[3] = vec2[3](
vec2(0.0, 0.0),
vec2(2.0, 0.0),
vec2(0.0, 2.0));
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
vTexCoord = texCoords[gl_VertexID];
}
)GLSL";
const char* kTextureFragmentShader = R"GLSL(
#version 430 core
layout(binding = 0) uniform sampler2D uInputTexture;
in vec2 vTexCoord;
out vec4 fragColor;
void main()
{
fragColor = texture(uInputTexture, clamp(vTexCoord, vec2(0.0), vec2(1.0)));
}
)GLSL";
const char* kPatternFragmentShader = R"GLSL(
#version 430 core
uniform vec2 uResolution;
uniform float uFrameIndex;
in vec2 vTexCoord;
out vec4 fragColor;
vec3 hexColor(float r, float g, float b)
{
return vec3(r, g, b) / 255.0;
}
vec3 smpteTop(float x)
{
if (x < 240.0) return hexColor(102.0, 102.0, 102.0);
if (x < 445.0) return hexColor(191.0, 191.0, 191.0);
if (x < 651.0) return hexColor(191.0, 191.0, 0.0);
if (x < 857.0) return hexColor(0.0, 191.0, 191.0);
if (x < 1063.0) return hexColor(0.0, 191.0, 0.0);
if (x < 1269.0) return hexColor(191.0, 0.0, 191.0);
if (x < 1475.0) return hexColor(191.0, 0.0, 0.0);
if (x < 1680.0) return hexColor(0.0, 0.0, 191.0);
return hexColor(102.0, 102.0, 102.0);
}
vec3 smpteMiddleA(float x)
{
if (x < 240.0) return hexColor(0.0, 255.0, 255.0);
if (x < 445.0) return hexColor(0.0, 63.0, 105.0);
if (x < 1680.0) return hexColor(191.0, 191.0, 191.0);
return hexColor(0.0, 0.0, 255.0);
}
vec3 smpteMiddleB(float x)
{
if (x < 240.0) return hexColor(255.0, 255.0, 0.0);
if (x < 445.0) return hexColor(65.0, 0.0, 119.0);
if (x < 1475.0)
{
float ramp = clamp((x - 445.0) / (1475.0 - 445.0), 0.0, 1.0);
return vec3(ramp);
}
if (x < 1680.0) return vec3(1.0);
return hexColor(255.0, 0.0, 0.0);
}
vec3 smpteBottom(float x)
{
if (x < 240.0) return hexColor(38.0, 38.0, 38.0);
if (x < 549.0) return vec3(0.0);
if (x < 960.0) return vec3(1.0);
if (x < 1268.0) return vec3(0.0);
if (x < 1337.0) return hexColor(5.0, 5.0, 5.0);
if (x < 1405.0) return vec3(0.0);
if (x < 1474.0) return hexColor(10.0, 10.0, 10.0);
if (x < 1680.0) return vec3(0.0);
return hexColor(38.0, 38.0, 38.0);
}
vec3 smpteColor(vec2 uv)
{
vec2 pixel = vec2(clamp(uv.x, 0.0, 1.0), 1.0 - clamp(uv.y, 0.0, 1.0)) * vec2(1920.0, 1080.0);
if (pixel.y < 630.0) return smpteTop(pixel.x);
if (pixel.y < 720.0) return smpteMiddleA(pixel.x);
if (pixel.y < 810.0) return smpteMiddleB(pixel.x);
return smpteBottom(pixel.x);
}
void main()
{
vec2 uv = clamp(vTexCoord, vec2(0.0), vec2(1.0));
vec3 color = smpteColor(uv);
float t = uFrameIndex / 60.0;
vec2 cubeSize = vec2(0.16, 0.20);
vec2 cubeMin = vec2(
(0.5 + 0.5 * sin(t * 1.7)) * (1.0 - cubeSize.x),
(0.5 + 0.5 * sin(t * 1.1 + 0.8)) * (1.0 - cubeSize.y));
vec2 cubeMax = cubeMin + cubeSize;
bool insideCube = uv.x >= cubeMin.x && uv.x <= cubeMax.x && uv.y >= cubeMin.y && uv.y <= cubeMax.y;
if (insideCube)
{
vec2 local = (uv - cubeMin) / cubeSize;
vec3 cubeColor = vec3(1.0, 0.74 + 0.18 * sin(t * 2.1), 0.08);
float edge = step(local.x, 0.04) + step(local.y, 0.04) + step(0.96, local.x) + step(0.96, local.y);
color = edge > 0.0 ? vec3(1.0) : cubeColor;
}
fragColor = vec4(color, 1.0);
}
)GLSL";
}
bool SimpleMotionRenderer::InitializeGl(unsigned width, unsigned height)
{
mWidth = width;
mHeight = height;
return mWidth > 0 && mHeight > 0;
}
void SimpleMotionRenderer::RenderFrame(uint64_t frameIndex)
{
if (!EnsurePatternProgram())
{
glViewport(0, 0, static_cast<GLsizei>(mWidth), static_cast<GLsizei>(mHeight));
glDisable(GL_SCISSOR_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
return;
}
glViewport(0, 0, static_cast<GLsizei>(mWidth), static_cast<GLsizei>(mHeight));
glDisable(GL_SCISSOR_TEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glUseProgram(mPatternProgram);
const GLint resolutionLocation = glGetUniformLocation(mPatternProgram, "uResolution");
if (resolutionLocation >= 0)
glUniform2f(resolutionLocation, static_cast<float>(mWidth), static_cast<float>(mHeight));
const GLint frameIndexLocation = glGetUniformLocation(mPatternProgram, "uFrameIndex");
if (frameIndexLocation >= 0)
glUniform1f(frameIndexLocation, static_cast<float>(frameIndex));
glBindVertexArray(mPatternVertexArray);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glUseProgram(0);
}
void SimpleMotionRenderer::RenderTexture(GLuint texture)
{
if (texture == 0 || !EnsureTextureProgram())
{
RenderFrame(0);
return;
}
glViewport(0, 0, static_cast<GLsizei>(mWidth), static_cast<GLsizei>(mHeight));
glDisable(GL_SCISSOR_TEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glActiveTexture(GL_TEXTURE0 + kInputTextureUnit);
glBindTexture(GL_TEXTURE_2D, texture);
glUseProgram(mTextureProgram);
glBindVertexArray(mTextureVertexArray);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glUseProgram(0);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
}
void SimpleMotionRenderer::ShutdownGl()
{
DestroyPatternProgram();
DestroyTextureProgram();
mWidth = 0;
mHeight = 0;
}
bool SimpleMotionRenderer::EnsurePatternProgram()
{
if (mPatternProgram != 0)
return true;
if (!CompileShader(GL_VERTEX_SHADER, kTextureVertexShader, mPatternVertexShader))
return false;
if (!CompileShader(GL_FRAGMENT_SHADER, kPatternFragmentShader, mPatternFragmentShader))
{
DestroyPatternProgram();
return false;
}
mPatternProgram = glCreateProgram();
glAttachShader(mPatternProgram, mPatternVertexShader);
glAttachShader(mPatternProgram, mPatternFragmentShader);
glLinkProgram(mPatternProgram);
GLint linkResult = GL_FALSE;
glGetProgramiv(mPatternProgram, GL_LINK_STATUS, &linkResult);
if (linkResult == GL_FALSE)
{
DestroyPatternProgram();
return false;
}
glGenVertexArrays(1, &mPatternVertexArray);
return mPatternVertexArray != 0;
}
bool SimpleMotionRenderer::EnsureTextureProgram()
{
if (mTextureProgram != 0)
return true;
if (!CompileShader(GL_VERTEX_SHADER, kTextureVertexShader, mTextureVertexShader))
return false;
if (!CompileShader(GL_FRAGMENT_SHADER, kTextureFragmentShader, mTextureFragmentShader))
{
DestroyTextureProgram();
return false;
}
mTextureProgram = glCreateProgram();
glAttachShader(mTextureProgram, mTextureVertexShader);
glAttachShader(mTextureProgram, mTextureFragmentShader);
glLinkProgram(mTextureProgram);
GLint linkResult = GL_FALSE;
glGetProgramiv(mTextureProgram, GL_LINK_STATUS, &linkResult);
if (linkResult == GL_FALSE)
{
DestroyTextureProgram();
return false;
}
glUseProgram(mTextureProgram);
const GLint inputLocation = glGetUniformLocation(mTextureProgram, "uInputTexture");
if (inputLocation >= 0)
glUniform1i(inputLocation, static_cast<GLint>(kInputTextureUnit));
glUseProgram(0);
glGenVertexArrays(1, &mTextureVertexArray);
return mTextureVertexArray != 0;
}
void SimpleMotionRenderer::DestroyTextureProgram()
{
if (mTextureProgram != 0)
glDeleteProgram(mTextureProgram);
if (mTextureVertexShader != 0)
glDeleteShader(mTextureVertexShader);
if (mTextureFragmentShader != 0)
glDeleteShader(mTextureFragmentShader);
if (mTextureVertexArray != 0)
glDeleteVertexArrays(1, &mTextureVertexArray);
mTextureProgram = 0;
mTextureVertexShader = 0;
mTextureFragmentShader = 0;
mTextureVertexArray = 0;
}
void SimpleMotionRenderer::DestroyPatternProgram()
{
if (mPatternProgram != 0)
glDeleteProgram(mPatternProgram);
if (mPatternVertexShader != 0)
glDeleteShader(mPatternVertexShader);
if (mPatternFragmentShader != 0)
glDeleteShader(mPatternFragmentShader);
if (mPatternVertexArray != 0)
glDeleteVertexArrays(1, &mPatternVertexArray);
mPatternProgram = 0;
mPatternVertexShader = 0;
mPatternFragmentShader = 0;
mPatternVertexArray = 0;
}
bool SimpleMotionRenderer::CompileShader(GLenum shaderType, const char* source, GLuint& shader)
{
shader = glCreateShader(shaderType);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint compileResult = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
if (compileResult != GL_FALSE)
return true;
glDeleteShader(shader);
shader = 0;
return false;
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include "GLExtensions.h"
#include <cstdint>
class SimpleMotionRenderer
{
public:
SimpleMotionRenderer() = default;
bool InitializeGl(unsigned width, unsigned height);
void RenderFrame(uint64_t frameIndex);
void RenderTexture(GLuint texture);
void ShutdownGl();
unsigned Width() const { return mWidth; }
unsigned Height() const { return mHeight; }
private:
bool EnsureTextureProgram();
bool EnsurePatternProgram();
void DestroyTextureProgram();
void DestroyPatternProgram();
static bool CompileShader(GLenum shaderType, const char* source, GLuint& shader);
unsigned mWidth = 0;
unsigned mHeight = 0;
GLuint mPatternProgram = 0;
GLuint mPatternVertexShader = 0;
GLuint mPatternFragmentShader = 0;
GLuint mPatternVertexArray = 0;
GLuint mTextureProgram = 0;
GLuint mTextureVertexShader = 0;
GLuint mTextureFragmentShader = 0;
GLuint mTextureVertexArray = 0;
};

View File

@@ -0,0 +1,160 @@
#include "Bgra8ReadbackPipeline.h"
#include "../frames/SystemFrameTypes.h"
#include <chrono>
#include <cstring>
namespace
{
double MillisecondsSince(std::chrono::steady_clock::time_point start)
{
return std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(
std::chrono::steady_clock::now() - start).count();
}
}
Bgra8ReadbackPipeline::~Bgra8ReadbackPipeline()
{
Shutdown();
}
bool Bgra8ReadbackPipeline::Initialize(unsigned width, unsigned height, std::size_t pboDepth)
{
Shutdown();
mWidth = width;
mHeight = height;
mRowBytes = VideoIORowBytes(VideoIOPixelFormat::Bgra8, width);
if (mWidth == 0 || mHeight == 0 || mRowBytes == 0)
return false;
if (!CreateRenderTarget())
{
Shutdown();
return false;
}
const std::size_t byteCount = static_cast<std::size_t>(mRowBytes) * static_cast<std::size_t>(mHeight);
if (!mPboRing.Initialize(pboDepth, byteCount))
{
Shutdown();
return false;
}
return true;
}
void Bgra8ReadbackPipeline::Shutdown()
{
mPboRing.Shutdown();
DestroyRenderTarget();
mWidth = 0;
mHeight = 0;
mRowBytes = 0;
}
bool Bgra8ReadbackPipeline::RenderAndQueue(uint64_t frameIndex, const RenderCallback& renderFrame)
{
if (mFramebuffer == 0 || !renderFrame)
return false;
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
const auto renderStart = std::chrono::steady_clock::now();
renderFrame(frameIndex);
mLastRenderFrameMilliseconds = MillisecondsSince(renderStart);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
const auto queueStart = std::chrono::steady_clock::now();
const bool queued = mPboRing.QueueReadback(mFramebuffer, mWidth, mHeight, frameIndex);
mLastReadbackQueueMilliseconds = MillisecondsSince(queueStart);
return queued;
}
void Bgra8ReadbackPipeline::ConsumeCompleted(
const AcquireFrameCallback& acquireFrame,
const PublishFrameCallback& publishFrame,
const CounterCallback& onAcquireMiss,
const CounterCallback& onCompleted)
{
if (!acquireFrame || !publishFrame)
return;
PboReadbackRing::CompletedReadback readback;
while (mPboRing.TryAcquireCompleted(readback))
{
const auto copyStart = std::chrono::steady_clock::now();
glBindBuffer(GL_PIXEL_PACK_BUFFER, readback.pbo);
void* mapped = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
if (!mapped)
{
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
mPboRing.ReleaseCompleted(readback);
mLastCompletedReadbackCopyMilliseconds = MillisecondsSince(copyStart);
continue;
}
SystemFrame frame;
if (acquireFrame(frame))
{
const std::size_t byteCount = static_cast<std::size_t>(frame.rowBytes) * static_cast<std::size_t>(frame.height);
if (frame.bytes != nullptr && byteCount <= readback.byteCount)
{
std::memcpy(frame.bytes, mapped, byteCount);
frame.frameIndex = readback.frameIndex;
frame.pixelFormat = VideoIOPixelFormat::Bgra8;
publishFrame(frame);
if (onCompleted)
onCompleted();
}
}
else if (onAcquireMiss)
{
onAcquireMiss();
}
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
mPboRing.ReleaseCompleted(readback);
mLastCompletedReadbackCopyMilliseconds = MillisecondsSince(copyStart);
}
}
bool Bgra8ReadbackPipeline::CreateRenderTarget()
{
glGenFramebuffers(1, &mFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glGenTextures(1, &mTexture);
glBindTexture(GL_TEXTURE_2D, mTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA8,
static_cast<GLsizei>(mWidth),
static_cast<GLsizei>(mHeight),
0,
GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV,
nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0);
const bool complete = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return complete;
}
void Bgra8ReadbackPipeline::DestroyRenderTarget()
{
if (mFramebuffer != 0)
glDeleteFramebuffers(1, &mFramebuffer);
if (mTexture != 0)
glDeleteTextures(1, &mTexture);
mFramebuffer = 0;
mTexture = 0;
}

View File

@@ -0,0 +1,58 @@
#pragma once
#include "PboReadbackRing.h"
#include "VideoIOFormat.h"
#include <cstddef>
#include <cstdint>
#include <functional>
struct SystemFrame;
class Bgra8ReadbackPipeline
{
public:
using RenderCallback = std::function<void(uint64_t frameIndex)>;
using AcquireFrameCallback = std::function<bool(SystemFrame& frame)>;
using PublishFrameCallback = std::function<bool(const SystemFrame& frame)>;
using CounterCallback = std::function<void()>;
Bgra8ReadbackPipeline() = default;
Bgra8ReadbackPipeline(const Bgra8ReadbackPipeline&) = delete;
Bgra8ReadbackPipeline& operator=(const Bgra8ReadbackPipeline&) = delete;
~Bgra8ReadbackPipeline();
bool Initialize(unsigned width, unsigned height, std::size_t pboDepth);
void Shutdown();
bool RenderAndQueue(uint64_t frameIndex, const RenderCallback& renderFrame);
void ConsumeCompleted(
const AcquireFrameCallback& acquireFrame,
const PublishFrameCallback& publishFrame,
const CounterCallback& onAcquireMiss = {},
const CounterCallback& onCompleted = {});
GLuint Framebuffer() const { return mFramebuffer; }
unsigned Width() const { return mWidth; }
unsigned Height() const { return mHeight; }
unsigned RowBytes() const { return mRowBytes; }
VideoIOPixelFormat PixelFormat() const { return VideoIOPixelFormat::Bgra8; }
uint64_t PboQueueMisses() const { return mPboRing.QueueMisses(); }
double LastRenderFrameMilliseconds() const { return mLastRenderFrameMilliseconds; }
double LastReadbackQueueMilliseconds() const { return mLastReadbackQueueMilliseconds; }
double LastCompletedReadbackCopyMilliseconds() const { return mLastCompletedReadbackCopyMilliseconds; }
private:
bool CreateRenderTarget();
void DestroyRenderTarget();
unsigned mWidth = 0;
unsigned mHeight = 0;
unsigned mRowBytes = 0;
GLuint mFramebuffer = 0;
GLuint mTexture = 0;
double mLastRenderFrameMilliseconds = 0.0;
double mLastReadbackQueueMilliseconds = 0.0;
double mLastCompletedReadbackCopyMilliseconds = 0.0;
PboReadbackRing mPboRing;
};

View File

@@ -0,0 +1,138 @@
#include "PboReadbackRing.h"
#include <algorithm>
PboReadbackRing::~PboReadbackRing()
{
Shutdown();
}
bool PboReadbackRing::Initialize(std::size_t depth, std::size_t byteCount)
{
Shutdown();
if (depth == 0 || byteCount == 0)
return false;
mSlots.resize(depth);
mByteCount = byteCount;
for (Slot& slot : mSlots)
{
glGenBuffers(1, &slot.pbo);
if (slot.pbo == 0)
{
Shutdown();
return false;
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(mByteCount), nullptr, GL_STREAM_READ);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
return true;
}
void PboReadbackRing::Shutdown()
{
for (Slot& slot : mSlots)
{
if (slot.fence)
glDeleteSync(slot.fence);
if (slot.pbo != 0)
glDeleteBuffers(1, &slot.pbo);
slot = {};
}
mSlots.clear();
mWriteIndex = 0;
mReadIndex = 0;
mByteCount = 0;
}
bool PboReadbackRing::QueueReadback(GLuint framebuffer, unsigned width, unsigned height, uint64_t frameIndex)
{
if (mSlots.empty())
return false;
Slot& slot = mSlots[mWriteIndex];
if (slot.inFlight || slot.acquired)
{
++mQueueMisses;
return false;
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(mByteCount), nullptr, GL_STREAM_READ);
glReadPixels(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
slot.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
slot.inFlight = slot.fence != nullptr;
slot.frameIndex = frameIndex;
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
if (!slot.inFlight)
return false;
mWriteIndex = (mWriteIndex + 1) % mSlots.size();
return true;
}
bool PboReadbackRing::TryAcquireCompleted(CompletedReadback& readback)
{
if (mSlots.empty())
return false;
for (std::size_t checked = 0; checked < mSlots.size(); ++checked)
{
Slot& slot = mSlots[mReadIndex];
if (!slot.inFlight || slot.acquired || slot.fence == nullptr)
{
mReadIndex = (mReadIndex + 1) % mSlots.size();
continue;
}
const GLenum waitResult = glClientWaitSync(slot.fence, 0, 0);
if (waitResult != GL_ALREADY_SIGNALED && waitResult != GL_CONDITION_SATISFIED)
return false;
slot.acquired = true;
readback.pbo = slot.pbo;
readback.frameIndex = slot.frameIndex;
readback.byteCount = mByteCount;
return true;
}
return false;
}
void PboReadbackRing::ReleaseCompleted(const CompletedReadback& readback)
{
for (std::size_t index = 0; index < mSlots.size(); ++index)
{
Slot& slot = mSlots[index];
if (!slot.acquired || slot.pbo != readback.pbo)
continue;
ResetSlot(slot);
mReadIndex = (index + 1) % mSlots.size();
return;
}
}
void PboReadbackRing::DrainCompleted()
{
for (std::size_t pass = 0; pass < mSlots.size() * 2; ++pass)
{
CompletedReadback readback;
if (!TryAcquireCompleted(readback))
break;
ReleaseCompleted(readback);
}
}
void PboReadbackRing::ResetSlot(Slot& slot)
{
if (slot.fence)
glDeleteSync(slot.fence);
slot.fence = nullptr;
slot.inFlight = false;
slot.acquired = false;
slot.frameIndex = 0;
}

View File

@@ -0,0 +1,52 @@
#pragma once
#include "GLExtensions.h"
#include <cstddef>
#include <cstdint>
#include <vector>
class PboReadbackRing
{
public:
struct CompletedReadback
{
GLuint pbo = 0;
uint64_t frameIndex = 0;
std::size_t byteCount = 0;
};
PboReadbackRing() = default;
PboReadbackRing(const PboReadbackRing&) = delete;
PboReadbackRing& operator=(const PboReadbackRing&) = delete;
~PboReadbackRing();
bool Initialize(std::size_t depth, std::size_t byteCount);
void Shutdown();
bool QueueReadback(GLuint framebuffer, unsigned width, unsigned height, uint64_t frameIndex);
bool TryAcquireCompleted(CompletedReadback& readback);
void ReleaseCompleted(const CompletedReadback& readback);
void DrainCompleted();
std::size_t Depth() const { return mSlots.size(); }
uint64_t QueueMisses() const { return mQueueMisses; }
private:
struct Slot
{
GLuint pbo = 0;
GLsync fence = nullptr;
bool inFlight = false;
bool acquired = false;
uint64_t frameIndex = 0;
};
void ResetSlot(Slot& slot);
std::vector<Slot> mSlots;
std::size_t mWriteIndex = 0;
std::size_t mReadIndex = 0;
std::size_t mByteCount = 0;
uint64_t mQueueMisses = 0;
};

View File

@@ -0,0 +1,271 @@
#include "RuntimeRenderScene.h"
#include "../../platform/HiddenGlWindow.h"
#include <algorithm>
#include <functional>
#include <utility>
#ifndef GL_FRAMEBUFFER_BINDING
#define GL_FRAMEBUFFER_BINDING 0x8CA6
#endif
RuntimeRenderScene::~RuntimeRenderScene()
{
ShutdownGl();
}
bool RuntimeRenderScene::StartPrepareWorker(std::unique_ptr<HiddenGlWindow> sharedWindow, std::string& error)
{
return mPrepareWorker.Start(std::move(sharedWindow), error);
}
bool RuntimeRenderScene::CommitRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers, std::string& error)
{
ConsumePreparedPrograms();
std::vector<std::string> nextOrder;
std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> layersToPrepare;
nextOrder.reserve(layers.size());
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
{
if (!layer.bypass)
nextOrder.push_back(layer.id);
}
for (auto layerIt = mLayers.begin(); layerIt != mLayers.end();)
{
const bool stillPresent = std::find(nextOrder.begin(), nextOrder.end(), layerIt->layerId) != nextOrder.end();
if (stillPresent)
{
++layerIt;
continue;
}
for (LayerProgram::PassProgram& pass : layerIt->passes)
{
if (pass.renderer)
pass.renderer->ShutdownGl();
}
ReleasePendingPrograms(*layerIt);
layerIt = mLayers.erase(layerIt);
}
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
{
if (layer.bypass)
continue;
if (layer.artifact.passes.empty() && layer.artifact.fragmentShaderSource.empty())
continue;
const std::string fingerprint = Fingerprint(layer.artifact);
LayerProgram* program = FindLayer(layer.id);
if (!program)
{
LayerProgram next;
next.layerId = layer.id;
mLayers.push_back(std::move(next));
program = &mLayers.back();
}
bool hasReadyPass = false;
for (const LayerProgram::PassProgram& pass : program->passes)
{
if (pass.renderer && pass.renderer->HasProgram())
{
hasReadyPass = true;
break;
}
}
if (program->shaderId == layer.shaderId && program->sourceFingerprint == fingerprint && hasReadyPass)
{
for (LayerProgram::PassProgram& pass : program->passes)
{
if (pass.renderer)
pass.renderer->UpdateArtifactState(layer.artifact);
}
continue;
}
if (program->pendingFingerprint == fingerprint)
continue;
ReleasePendingPrograms(*program);
program->shaderId = layer.shaderId;
program->pendingFingerprint = fingerprint;
layersToPrepare.push_back(layer);
}
mLayerOrder = std::move(nextOrder);
if (!layersToPrepare.empty())
mPrepareWorker.Submit(layersToPrepare);
error.clear();
return true;
}
void RuntimeRenderScene::ShutdownGl()
{
mPrepareWorker.Stop();
for (LayerProgram& layer : mLayers)
{
for (LayerProgram::PassProgram& pass : layer.passes)
{
if (pass.renderer)
pass.renderer->ShutdownGl();
}
ReleasePendingPrograms(layer);
}
mLayers.clear();
mLayerOrder.clear();
DestroyLayerTargets();
}
void RuntimeRenderScene::ConsumePreparedPrograms()
{
RuntimePreparedShaderProgram preparedProgram;
while (mPrepareWorker.TryConsume(preparedProgram))
{
if (!preparedProgram.succeeded)
{
preparedProgram.ReleaseGl();
continue;
}
LayerProgram* layer = FindLayer(preparedProgram.layerId);
if (!layer || layer->pendingFingerprint != preparedProgram.sourceFingerprint)
{
preparedProgram.ReleaseGl();
continue;
}
bool replacesExistingPendingPass = false;
for (RuntimePreparedShaderProgram& existing : layer->pendingPreparedPrograms)
{
if (existing.passId != preparedProgram.passId)
continue;
existing.ReleaseGl();
existing = std::move(preparedProgram);
replacesExistingPendingPass = true;
break;
}
if (!replacesExistingPendingPass)
layer->pendingPreparedPrograms.push_back(std::move(preparedProgram));
TryCommitPendingPrograms(*layer);
}
}
void RuntimeRenderScene::ReleasePendingPrograms(LayerProgram& layer)
{
for (RuntimePreparedShaderProgram& program : layer.pendingPreparedPrograms)
program.ReleaseGl();
layer.pendingPreparedPrograms.clear();
}
void RuntimeRenderScene::TryCommitPendingPrograms(LayerProgram& layer)
{
if (layer.pendingPreparedPrograms.empty())
return;
const RuntimeShaderArtifact& artifact = layer.pendingPreparedPrograms.front().artifact;
const std::size_t expectedPassCount = artifact.passes.empty() ? 1 : artifact.passes.size();
if (layer.pendingPreparedPrograms.size() < expectedPassCount)
return;
std::vector<LayerProgram::PassProgram> nextPasses;
nextPasses.reserve(expectedPassCount);
for (const RuntimeShaderPassArtifact& passArtifact : artifact.passes)
{
auto preparedIt = std::find_if(
layer.pendingPreparedPrograms.begin(),
layer.pendingPreparedPrograms.end(),
[&passArtifact](const RuntimePreparedShaderProgram& prepared) {
return prepared.passId == passArtifact.passId;
});
if (preparedIt == layer.pendingPreparedPrograms.end())
return;
std::unique_ptr<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>();
std::string error;
if (!nextRenderer->CommitPreparedProgram(*preparedIt, error))
{
ReleasePendingPrograms(layer);
return;
}
LayerProgram::PassProgram nextPass;
nextPass.passId = preparedIt->passId;
nextPass.inputNames = preparedIt->inputNames;
nextPass.outputName = preparedIt->outputName.empty() ? preparedIt->passId : preparedIt->outputName;
nextPass.renderer = std::move(nextRenderer);
nextPasses.push_back(std::move(nextPass));
}
if (artifact.passes.empty())
{
RuntimePreparedShaderProgram& prepared = layer.pendingPreparedPrograms.front();
std::unique_ptr<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>();
std::string error;
if (!nextRenderer->CommitPreparedProgram(prepared, error))
{
ReleasePendingPrograms(layer);
return;
}
LayerProgram::PassProgram nextPass;
nextPass.passId = prepared.passId;
nextPass.inputNames = prepared.inputNames;
nextPass.outputName = prepared.outputName.empty() ? prepared.passId : prepared.outputName;
nextPass.renderer = std::move(nextRenderer);
nextPasses.push_back(std::move(nextPass));
}
for (LayerProgram::PassProgram& pass : layer.passes)
{
if (pass.renderer)
pass.renderer->ShutdownGl();
}
layer.passes = std::move(nextPasses);
layer.shaderId = artifact.shaderId;
layer.sourceFingerprint = layer.pendingPreparedPrograms.front().sourceFingerprint;
layer.pendingFingerprint.clear();
layer.pendingPreparedPrograms.clear();
}
RuntimeRenderScene::LayerProgram* RuntimeRenderScene::FindLayer(const std::string& layerId)
{
for (LayerProgram& layer : mLayers)
{
if (layer.layerId == layerId)
return &layer;
}
return nullptr;
}
const RuntimeRenderScene::LayerProgram* RuntimeRenderScene::FindLayer(const std::string& layerId) const
{
for (const LayerProgram& layer : mLayers)
{
if (layer.layerId == layerId)
return &layer;
}
return nullptr;
}
RuntimeRenderScene::LayerProgram::PassProgram* RuntimeRenderScene::FindPass(LayerProgram& layer, const std::string& passId)
{
for (LayerProgram::PassProgram& pass : layer.passes)
{
if (pass.passId == passId)
return &pass;
}
return nullptr;
}
std::string RuntimeRenderScene::Fingerprint(const RuntimeShaderArtifact& artifact)
{
const std::hash<std::string> hasher;
std::string source;
for (const RuntimeShaderPassArtifact& pass : artifact.passes)
source += pass.passId + ":" + pass.outputName + ":" + pass.fragmentShaderSource + "\n";
if (source.empty())
source = artifact.fragmentShaderSource;
return artifact.shaderId + ":" + std::to_string(source.size()) + ":" + std::to_string(hasher(source));
}

View File

@@ -0,0 +1,64 @@
#pragma once
#include "../runtime/RuntimeLayerModel.h"
#include "RuntimeShaderPrepareWorker.h"
#include "RuntimeShaderRenderer.h"
#include <windows.h>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
class RuntimeRenderScene
{
public:
RuntimeRenderScene() = default;
RuntimeRenderScene(const RuntimeRenderScene&) = delete;
RuntimeRenderScene& operator=(const RuntimeRenderScene&) = delete;
~RuntimeRenderScene();
bool StartPrepareWorker(std::unique_ptr<HiddenGlWindow> sharedWindow, std::string& error);
bool CommitRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers, std::string& error);
bool HasLayers();
void RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint videoInputTexture = 0);
void ShutdownGl();
private:
struct LayerProgram
{
std::string layerId;
std::string shaderId;
std::string sourceFingerprint;
std::string pendingFingerprint;
std::vector<RuntimePreparedShaderProgram> pendingPreparedPrograms;
struct PassProgram
{
std::string passId;
std::vector<std::string> inputNames;
std::string outputName;
std::unique_ptr<RuntimeShaderRenderer> renderer;
};
std::vector<PassProgram> passes;
};
void ConsumePreparedPrograms();
void ReleasePendingPrograms(LayerProgram& layer);
void TryCommitPendingPrograms(LayerProgram& layer);
bool EnsureLayerTargets(unsigned width, unsigned height);
void DestroyLayerTargets();
GLuint RenderLayer(LayerProgram& layer, uint64_t frameIndex, unsigned width, unsigned height, GLuint videoInputTexture, GLuint layerInputTexture, GLuint outputFramebuffer, bool renderToOutput);
LayerProgram* FindLayer(const std::string& layerId);
const LayerProgram* FindLayer(const std::string& layerId) const;
LayerProgram::PassProgram* FindPass(LayerProgram& layer, const std::string& passId);
static std::string Fingerprint(const RuntimeShaderArtifact& artifact);
RuntimeShaderPrepareWorker mPrepareWorker;
std::vector<LayerProgram> mLayers;
std::vector<std::string> mLayerOrder;
GLuint mLayerFramebuffers[4] = {};
GLuint mLayerTextures[4] = {};
unsigned mLayerTargetWidth = 0;
unsigned mLayerTargetHeight = 0;
};

View File

@@ -0,0 +1,219 @@
#include "RuntimeRenderScene.h"
#include <string>
#include <vector>
#ifndef GL_FRAMEBUFFER_BINDING
#define GL_FRAMEBUFFER_BINDING 0x8CA6
#endif
bool RuntimeRenderScene::HasLayers()
{
ConsumePreparedPrograms();
for (const std::string& layerId : mLayerOrder)
{
const LayerProgram* layer = FindLayer(layerId);
if (!layer)
continue;
for (const LayerProgram::PassProgram& pass : layer->passes)
{
if (pass.renderer && pass.renderer->HasProgram())
return true;
}
}
return false;
}
void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint videoInputTexture)
{
ConsumePreparedPrograms();
std::vector<LayerProgram*> readyLayers;
for (const std::string& layerId : mLayerOrder)
{
LayerProgram* layer = FindLayer(layerId);
if (!layer)
continue;
for (const LayerProgram::PassProgram& pass : layer->passes)
{
if (pass.renderer && pass.renderer->HasProgram())
{
readyLayers.push_back(layer);
break;
}
}
}
if (readyLayers.empty())
return;
GLint outputFramebuffer = 0;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &outputFramebuffer);
if (readyLayers.size() == 1)
{
RenderLayer(*readyLayers.front(), frameIndex, width, height, videoInputTexture, videoInputTexture, static_cast<GLuint>(outputFramebuffer), true);
return;
}
if (!EnsureLayerTargets(width, height))
{
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer));
RenderLayer(*readyLayers.back(), frameIndex, width, height, videoInputTexture, videoInputTexture, static_cast<GLuint>(outputFramebuffer), true);
return;
}
// Shader source contract:
// - gVideoInput is the decoded current 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)
{
const bool isFinalLayer = layerIndex == readyLayers.size() - 1;
if (isFinalLayer)
{
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer));
RenderLayer(*readyLayers[layerIndex], frameIndex, width, height, videoInputTexture, layerInputTexture, static_cast<GLuint>(outputFramebuffer), true);
continue;
}
RenderLayer(*readyLayers[layerIndex], frameIndex, width, height, videoInputTexture, layerInputTexture, mLayerFramebuffers[nextTargetIndex], true);
layerInputTexture = mLayerTextures[nextTargetIndex];
nextTargetIndex = 1 - nextTargetIndex;
}
}
GLuint RuntimeRenderScene::RenderLayer(
LayerProgram& layer,
uint64_t frameIndex,
unsigned width,
unsigned height,
GLuint videoInputTexture,
GLuint layerInputTexture,
GLuint outputFramebuffer,
bool renderToOutput)
{
GLuint namedOutputs[2] = {};
std::string namedOutputNames[2];
std::size_t nextTargetIndex = 2;
GLuint lastOutputTexture = layerInputTexture;
for (LayerProgram::PassProgram& pass : layer.passes)
{
if (!pass.renderer || !pass.renderer->HasProgram())
continue;
GLuint sourceTexture = videoInputTexture;
if (!pass.inputNames.empty())
{
const std::string& inputName = pass.inputNames.front();
if (inputName == "videoInput")
{
sourceTexture = videoInputTexture;
}
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)
{
sourceTexture = namedOutputs[index];
break;
}
}
}
}
const bool writesLayerOutput = pass.outputName == "layerOutput";
if (writesLayerOutput && renderToOutput)
{
glBindFramebuffer(GL_FRAMEBUFFER, outputFramebuffer);
pass.renderer->RenderFrame(frameIndex, width, height, sourceTexture, layerInputTexture);
lastOutputTexture = 0;
continue;
}
if (!EnsureLayerTargets(width, height))
continue;
const std::size_t targetIndex = nextTargetIndex;
nextTargetIndex = nextTargetIndex == 2 ? 3 : 2;
glBindFramebuffer(GL_FRAMEBUFFER, mLayerFramebuffers[targetIndex]);
pass.renderer->RenderFrame(frameIndex, width, height, sourceTexture, layerInputTexture);
const std::size_t namedIndex = targetIndex - 2;
namedOutputs[namedIndex] = mLayerTextures[targetIndex];
namedOutputNames[namedIndex] = pass.outputName;
lastOutputTexture = mLayerTextures[targetIndex];
}
return lastOutputTexture;
}
bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height)
{
if (width == 0 || height == 0)
return false;
if (mLayerFramebuffers[0] != 0 && mLayerFramebuffers[1] != 0 && mLayerFramebuffers[2] != 0 && mLayerFramebuffers[3] != 0
&& mLayerTextures[0] != 0 && mLayerTextures[1] != 0 && mLayerTextures[2] != 0 && mLayerTextures[3] != 0
&& mLayerTargetWidth == width && mLayerTargetHeight == height)
return true;
DestroyLayerTargets();
mLayerTargetWidth = width;
mLayerTargetHeight = height;
glGenFramebuffers(4, mLayerFramebuffers);
glGenTextures(4, mLayerTextures);
for (int index = 0; index < 4; ++index)
{
glBindTexture(GL_TEXTURE_2D, mLayerTextures[index]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA8,
static_cast<GLsizei>(width),
static_cast<GLsizei>(height),
0,
GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV,
nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, mLayerFramebuffers[index]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mLayerTextures[index], 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
DestroyLayerTargets();
return false;
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
return true;
}
void RuntimeRenderScene::DestroyLayerTargets()
{
if (mLayerFramebuffers[0] != 0 || mLayerFramebuffers[1] != 0 || mLayerFramebuffers[2] != 0 || mLayerFramebuffers[3] != 0)
glDeleteFramebuffers(4, mLayerFramebuffers);
if (mLayerTextures[0] != 0 || mLayerTextures[1] != 0 || mLayerTextures[2] != 0 || mLayerTextures[3] != 0)
glDeleteTextures(4, mLayerTextures);
for (int index = 0; index < 4; ++index)
{
mLayerFramebuffers[index] = 0;
mLayerTextures[index] = 0;
}
mLayerTargetWidth = 0;
mLayerTargetHeight = 0;
}

View File

@@ -0,0 +1,123 @@
#include "RuntimeShaderParams.h"
#include "Std140Buffer.h"
#include <chrono>
namespace
{
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition)
{
ShaderParameterValue value;
switch (definition.type)
{
case ShaderParameterType::Float:
value.numberValues = definition.defaultNumbers.empty() ? std::vector<double>{ 0.0 } : definition.defaultNumbers;
break;
case ShaderParameterType::Vec2:
value.numberValues = definition.defaultNumbers.size() == 2 ? definition.defaultNumbers : std::vector<double>{ 0.0, 0.0 };
break;
case ShaderParameterType::Color:
value.numberValues = definition.defaultNumbers.size() == 4 ? definition.defaultNumbers : std::vector<double>{ 1.0, 1.0, 1.0, 1.0 };
break;
case ShaderParameterType::Boolean:
value.booleanValue = definition.defaultBoolean;
break;
case ShaderParameterType::Enum:
value.enumValue = definition.defaultEnumValue;
break;
case ShaderParameterType::Text:
value.textValue = definition.defaultTextValue;
break;
case ShaderParameterType::Trigger:
value.numberValues = { 0.0, -1000000.0 };
break;
}
return value;
}
int EnumIndexForDefault(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
{
for (std::size_t optionIndex = 0; optionIndex < definition.enumOptions.size(); ++optionIndex)
{
if (definition.enumOptions[optionIndex].value == value.enumValue)
return static_cast<int>(optionIndex);
}
return 0;
}
double UtcSecondsOfDay()
{
const auto now = std::chrono::system_clock::now();
const auto seconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
const long long secondsPerDay = 24 * 60 * 60;
long long daySeconds = seconds % secondsPerDay;
if (daySeconds < 0)
daySeconds += secondsPerDay;
return static_cast<double>(daySeconds);
}
}
std::vector<unsigned char> BuildRuntimeShaderGlobalParamsStd140(
const RuntimeShaderArtifact& artifact,
uint64_t frameIndex,
unsigned width,
unsigned height)
{
std::vector<unsigned char> buffer;
buffer.reserve(512);
AppendStd140Float(buffer, static_cast<float>(frameIndex) / 60.0f);
AppendStd140Vec2(buffer, static_cast<float>(width), static_cast<float>(height));
AppendStd140Vec2(buffer, static_cast<float>(width), static_cast<float>(height));
AppendStd140Float(buffer, static_cast<float>(UtcSecondsOfDay()));
AppendStd140Float(buffer, 0.0f);
AppendStd140Float(buffer, 0.37f);
AppendStd140Float(buffer, static_cast<float>(frameIndex));
AppendStd140Float(buffer, 1.0f);
AppendStd140Float(buffer, 0.0f);
AppendStd140Int(buffer, 0);
AppendStd140Int(buffer, 0);
AppendStd140Int(buffer, 0);
for (const ShaderParameterDefinition& definition : artifact.parameterDefinitions)
{
const auto valueIt = artifact.parameterValues.find(definition.id);
const ShaderParameterValue value = valueIt == artifact.parameterValues.end()
? DefaultValueForDefinition(definition)
: valueIt->second;
switch (definition.type)
{
case ShaderParameterType::Float:
AppendStd140Float(buffer, value.numberValues.empty() ? 0.0f : static_cast<float>(value.numberValues[0]));
break;
case ShaderParameterType::Vec2:
AppendStd140Vec2(buffer,
value.numberValues.size() > 0 ? static_cast<float>(value.numberValues[0]) : 0.0f,
value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : 0.0f);
break;
case ShaderParameterType::Color:
AppendStd140Vec4(buffer,
value.numberValues.size() > 0 ? static_cast<float>(value.numberValues[0]) : 1.0f,
value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : 1.0f,
value.numberValues.size() > 2 ? static_cast<float>(value.numberValues[2]) : 1.0f,
value.numberValues.size() > 3 ? static_cast<float>(value.numberValues[3]) : 1.0f);
break;
case ShaderParameterType::Boolean:
AppendStd140Int(buffer, value.booleanValue ? 1 : 0);
break;
case ShaderParameterType::Enum:
AppendStd140Int(buffer, EnumIndexForDefault(definition, value));
break;
case ShaderParameterType::Text:
break;
case ShaderParameterType::Trigger:
AppendStd140Int(buffer, value.numberValues.empty() ? 0 : static_cast<int>(value.numberValues[0]));
AppendStd140Float(buffer, value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : -1000000.0f);
break;
}
}
buffer.resize(AlignStd140(buffer.size(), 16), 0);
return buffer;
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "../runtime/RuntimeShaderArtifact.h"
#include <cstdint>
#include <vector>
std::vector<unsigned char> BuildRuntimeShaderGlobalParamsStd140(
const RuntimeShaderArtifact& artifact,
uint64_t frameIndex,
unsigned width,
unsigned height);

View File

@@ -0,0 +1,179 @@
#include "RuntimeShaderPrepareWorker.h"
#include "../../platform/HiddenGlWindow.h"
#include "RuntimeShaderRenderer.h"
#include <algorithm>
#include <chrono>
#include <functional>
#include <utility>
RuntimeShaderPrepareWorker::~RuntimeShaderPrepareWorker()
{
Stop();
}
bool RuntimeShaderPrepareWorker::Start(std::unique_ptr<HiddenGlWindow> sharedWindow, std::string& error)
{
if (mThread.joinable())
return true;
if (!sharedWindow || sharedWindow->DeviceContext() == nullptr || sharedWindow->Context() == nullptr)
{
error = "Runtime shader prepare worker needs an existing shared GL context.";
return false;
}
mWindow = std::move(sharedWindow);
mStopping.store(false, std::memory_order_release);
mStarted.store(false, std::memory_order_release);
{
std::lock_guard<std::mutex> lock(mMutex);
mStartupReady = false;
mStartupError.clear();
}
mThread = std::thread([this]() { ThreadMain(); });
std::unique_lock<std::mutex> lock(mMutex);
if (!mStartupCondition.wait_for(lock, std::chrono::seconds(3), [this]() {
return mStartupReady || !mStartupError.empty();
}))
{
error = "Timed out starting runtime shader prepare worker.";
lock.unlock();
Stop();
return false;
}
if (!mStartupError.empty())
{
error = mStartupError;
lock.unlock();
Stop();
return false;
}
return true;
}
void RuntimeShaderPrepareWorker::Stop()
{
mStopping.store(true, std::memory_order_release);
mCondition.notify_all();
if (mThread.joinable())
mThread.join();
std::deque<RuntimePreparedShaderProgram> completed;
{
std::lock_guard<std::mutex> lock(mMutex);
mRequests.clear();
completed.swap(mCompleted);
}
for (RuntimePreparedShaderProgram& program : completed)
program.ReleaseGl();
mWindow.reset();
mStarted.store(false, std::memory_order_release);
}
void RuntimeShaderPrepareWorker::Submit(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers)
{
std::lock_guard<std::mutex> lock(mMutex);
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
{
if (layer.artifact.passes.empty() && layer.artifact.fragmentShaderSource.empty())
continue;
std::vector<RuntimeShaderPassArtifact> passes = layer.artifact.passes;
if (passes.empty())
{
RuntimeShaderPassArtifact pass;
pass.passId = "main";
pass.fragmentShaderSource = layer.artifact.fragmentShaderSource;
pass.outputName = "layerOutput";
passes.push_back(std::move(pass));
}
auto sameLayer = [&layer](const PrepareRequest& existing) {
return existing.layerId == layer.id;
};
mRequests.erase(std::remove_if(mRequests.begin(), mRequests.end(), sameLayer), mRequests.end());
for (const RuntimeShaderPassArtifact& pass : passes)
{
PrepareRequest request;
request.layerId = layer.id;
request.shaderId = layer.shaderId;
request.passId = pass.passId;
request.sourceFingerprint = Fingerprint(layer.artifact);
request.artifact = layer.artifact;
request.passArtifact = pass;
mRequests.push_back(std::move(request));
}
}
mCondition.notify_one();
}
bool RuntimeShaderPrepareWorker::TryConsume(RuntimePreparedShaderProgram& preparedProgram)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mCompleted.empty())
return false;
preparedProgram = std::move(mCompleted.front());
mCompleted.pop_front();
return true;
}
void RuntimeShaderPrepareWorker::ThreadMain()
{
if (!mWindow || !mWindow->MakeCurrent())
{
std::lock_guard<std::mutex> lock(mMutex);
mStartupError = "Runtime shader prepare worker could not make shared GL context current.";
mStartupCondition.notify_all();
return;
}
{
std::lock_guard<std::mutex> lock(mMutex);
mStartupReady = true;
}
mStarted.store(true, std::memory_order_release);
mStartupCondition.notify_all();
while (!mStopping.load(std::memory_order_acquire))
{
PrepareRequest request;
{
std::unique_lock<std::mutex> lock(mMutex);
mCondition.wait(lock, [this]() {
return mStopping.load(std::memory_order_acquire) || !mRequests.empty();
});
if (mStopping.load(std::memory_order_acquire))
break;
request = std::move(mRequests.front());
mRequests.pop_front();
}
RuntimePreparedShaderProgram preparedProgram;
RuntimeShaderRenderer::BuildPreparedPassProgram(
request.layerId,
request.sourceFingerprint,
request.artifact,
request.passArtifact,
preparedProgram);
glFlush();
std::lock_guard<std::mutex> lock(mMutex);
mCompleted.push_back(std::move(preparedProgram));
}
mWindow->ClearCurrent();
}
std::string RuntimeShaderPrepareWorker::Fingerprint(const RuntimeShaderArtifact& artifact)
{
const std::hash<std::string> hasher;
std::string source;
for (const RuntimeShaderPassArtifact& pass : artifact.passes)
source += pass.passId + ":" + pass.outputName + ":" + pass.fragmentShaderSource + "\n";
if (source.empty())
source = artifact.fragmentShaderSource;
return artifact.shaderId + ":" + std::to_string(source.size()) + ":" + std::to_string(hasher(source));
}

View File

@@ -0,0 +1,58 @@
#pragma once
#include "RuntimeShaderProgram.h"
#include "../../runtime/RuntimeLayerModel.h"
#include <windows.h>
#include <atomic>
#include <condition_variable>
#include <deque>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
class HiddenGlWindow;
class RuntimeShaderPrepareWorker
{
public:
RuntimeShaderPrepareWorker() = default;
RuntimeShaderPrepareWorker(const RuntimeShaderPrepareWorker&) = delete;
RuntimeShaderPrepareWorker& operator=(const RuntimeShaderPrepareWorker&) = delete;
~RuntimeShaderPrepareWorker();
bool Start(std::unique_ptr<HiddenGlWindow> sharedWindow, std::string& error);
void Stop();
void Submit(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers);
bool TryConsume(RuntimePreparedShaderProgram& preparedProgram);
private:
struct PrepareRequest
{
std::string layerId;
std::string shaderId;
std::string passId;
std::string sourceFingerprint;
RuntimeShaderArtifact artifact;
RuntimeShaderPassArtifact passArtifact;
};
void ThreadMain();
static std::string Fingerprint(const RuntimeShaderArtifact& artifact);
std::unique_ptr<HiddenGlWindow> mWindow;
std::mutex mMutex;
std::condition_variable mCondition;
std::deque<PrepareRequest> mRequests;
std::deque<RuntimePreparedShaderProgram> mCompleted;
std::condition_variable mStartupCondition;
std::thread mThread;
std::atomic<bool> mStopping{ false };
std::atomic<bool> mStarted{ false };
bool mStartupReady = false;
std::string mStartupError;
};

View File

@@ -0,0 +1,37 @@
#pragma once
#include "GLExtensions.h"
#include "../../runtime/RuntimeShaderArtifact.h"
#include <string>
#include <vector>
struct RuntimePreparedShaderProgram
{
std::string layerId;
std::string shaderId;
std::string passId;
std::string sourceFingerprint;
RuntimeShaderArtifact artifact;
RuntimeShaderPassArtifact passArtifact;
std::vector<std::string> inputNames;
std::string outputName;
GLuint program = 0;
GLuint vertexShader = 0;
GLuint fragmentShader = 0;
bool succeeded = false;
std::string error;
void ReleaseGl()
{
if (program != 0)
glDeleteProgram(program);
if (vertexShader != 0)
glDeleteShader(vertexShader);
if (fragmentShader != 0)
glDeleteShader(fragmentShader);
program = 0;
vertexShader = 0;
fragmentShader = 0;
}
};

View File

@@ -0,0 +1,320 @@
#include "RuntimeShaderRenderer.h"
#include "RuntimeShaderParams.h"
#include <array>
#include <cstring>
#include <string>
namespace
{
constexpr GLuint kGlobalParamsBindingPoint = 0;
constexpr GLuint kVideoInputTextureUnit = 0;
constexpr GLuint kLayerInputTextureUnit = 1;
const char* kVertexShaderSource = R"GLSL(
#version 430 core
out vec2 vTexCoord;
void main()
{
vec2 positions[3] = vec2[3](
vec2(-1.0, -1.0),
vec2( 3.0, -1.0),
vec2(-1.0, 3.0));
vec2 texCoords[3] = vec2[3](
vec2(0.0, 0.0),
vec2(2.0, 0.0),
vec2(0.0, 2.0));
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
vTexCoord = texCoords[gl_VertexID];
}
)GLSL";
}
RuntimeShaderRenderer::~RuntimeShaderRenderer()
{
ShutdownGl();
}
bool RuntimeShaderRenderer::CommitPreparedProgram(RuntimePreparedShaderProgram& preparedProgram, std::string& error)
{
if (!preparedProgram.succeeded || preparedProgram.program == 0)
{
error = preparedProgram.error.empty() ? "Prepared runtime shader program is not valid." : preparedProgram.error;
return false;
}
if (!EnsureStaticGlResources(error))
return false;
DestroyProgram();
mProgram = preparedProgram.program;
mVertexShader = preparedProgram.vertexShader;
mFragmentShader = preparedProgram.fragmentShader;
mArtifact = preparedProgram.artifact;
preparedProgram.program = 0;
preparedProgram.vertexShader = 0;
preparedProgram.fragmentShader = 0;
return true;
}
void RuntimeShaderRenderer::UpdateArtifactState(const RuntimeShaderArtifact& artifact)
{
mArtifact.parameterDefinitions = artifact.parameterDefinitions;
mArtifact.parameterValues = artifact.parameterValues;
mArtifact.message = artifact.message;
}
bool RuntimeShaderRenderer::BuildPreparedProgram(
const std::string& layerId,
const std::string& sourceFingerprint,
const RuntimeShaderArtifact& artifact,
RuntimePreparedShaderProgram& preparedProgram)
{
RuntimeShaderPassArtifact passArtifact;
passArtifact.passId = "main";
passArtifact.fragmentShaderSource = artifact.fragmentShaderSource;
passArtifact.outputName = "layerOutput";
if (!artifact.passes.empty())
passArtifact = artifact.passes.front();
return BuildPreparedPassProgram(layerId, sourceFingerprint, artifact, passArtifact, preparedProgram);
}
bool RuntimeShaderRenderer::BuildPreparedPassProgram(
const std::string& layerId,
const std::string& sourceFingerprint,
const RuntimeShaderArtifact& artifact,
const RuntimeShaderPassArtifact& passArtifact,
RuntimePreparedShaderProgram& preparedProgram)
{
preparedProgram = RuntimePreparedShaderProgram();
preparedProgram.layerId = layerId;
preparedProgram.shaderId = artifact.shaderId;
preparedProgram.passId = passArtifact.passId;
preparedProgram.sourceFingerprint = sourceFingerprint;
preparedProgram.artifact = artifact;
preparedProgram.passArtifact = passArtifact;
preparedProgram.inputNames = passArtifact.inputNames;
preparedProgram.outputName = passArtifact.outputName.empty() ? passArtifact.passId : passArtifact.outputName;
if (passArtifact.fragmentShaderSource.empty())
{
preparedProgram.error = "Cannot prepare an empty fragment shader.";
return false;
}
if (!BuildProgram(
passArtifact.fragmentShaderSource,
preparedProgram.program,
preparedProgram.vertexShader,
preparedProgram.fragmentShader,
preparedProgram.error))
{
preparedProgram.ReleaseGl();
return false;
}
preparedProgram.succeeded = true;
AssignSamplerUniforms(preparedProgram.program);
return true;
}
void RuntimeShaderRenderer::RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint sourceTexture, GLuint layerInputTexture)
{
if (mProgram == 0)
return;
glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height));
glDisable(GL_SCISSOR_TEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
UpdateGlobalParams(frameIndex, width, height);
BindRuntimeTextures(sourceTexture, layerInputTexture);
glBindVertexArray(mVertexArray);
glUseProgram(mProgram);
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
glBindVertexArray(0);
}
void RuntimeShaderRenderer::ShutdownGl()
{
DestroyProgram();
DestroyStaticGlResources();
}
bool RuntimeShaderRenderer::EnsureStaticGlResources(std::string& error)
{
if (mVertexArray == 0)
glGenVertexArrays(1, &mVertexArray);
if (mGlobalParamsBuffer == 0)
{
glGenBuffers(1, &mGlobalParamsBuffer);
glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsBuffer);
glBufferData(GL_UNIFORM_BUFFER, 1024, nullptr, GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
if (mFallbackSourceTexture == 0)
{
const unsigned char pixels[] = {
0, 0, 0, 255,
96, 64, 32, 255,
64, 96, 160, 255,
255, 255, 255, 255
};
glGenTextures(1, &mFallbackSourceTexture);
glBindTexture(GL_TEXTURE_2D, mFallbackSourceTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
glBindTexture(GL_TEXTURE_2D, 0);
}
if (mVertexArray == 0 || mGlobalParamsBuffer == 0 || mFallbackSourceTexture == 0)
{
error = "Failed to create runtime shader GL resources.";
return false;
}
return true;
}
bool RuntimeShaderRenderer::BuildProgram(const std::string& fragmentShaderSource, GLuint& program, GLuint& vertexShader, GLuint& fragmentShader, std::string& error)
{
program = 0;
vertexShader = 0;
fragmentShader = 0;
if (!CompileShader(GL_VERTEX_SHADER, kVertexShaderSource, vertexShader, error))
return false;
if (!CompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource.c_str(), fragmentShader, error))
{
glDeleteShader(vertexShader);
vertexShader = 0;
return false;
}
program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
GLint linkResult = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linkResult);
if (linkResult == GL_FALSE)
{
std::array<char, 4096> log = {};
GLsizei length = 0;
glGetProgramInfoLog(program, static_cast<GLsizei>(log.size()), &length, log.data());
error = std::string(log.data(), static_cast<std::size_t>(length));
glDeleteProgram(program);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
program = 0;
vertexShader = 0;
fragmentShader = 0;
return false;
}
const GLuint globalParamsIndex = glGetUniformBlockIndex(program, "GlobalParams");
if (globalParamsIndex != GL_INVALID_INDEX)
glUniformBlockBinding(program, globalParamsIndex, kGlobalParamsBindingPoint);
return true;
}
void RuntimeShaderRenderer::AssignSamplerUniforms(GLuint program)
{
glUseProgram(program);
const GLint videoInputLocation = glGetUniformLocation(program, "gVideoInput");
if (videoInputLocation >= 0)
glUniform1i(videoInputLocation, static_cast<GLint>(kVideoInputTextureUnit));
const GLint videoInputArrayLocation = glGetUniformLocation(program, "gVideoInput_0");
if (videoInputArrayLocation >= 0)
glUniform1i(videoInputArrayLocation, static_cast<GLint>(kVideoInputTextureUnit));
const GLint layerInputLocation = glGetUniformLocation(program, "gLayerInput");
if (layerInputLocation >= 0)
glUniform1i(layerInputLocation, static_cast<GLint>(kLayerInputTextureUnit));
const GLint layerInputArrayLocation = glGetUniformLocation(program, "gLayerInput_0");
if (layerInputArrayLocation >= 0)
glUniform1i(layerInputArrayLocation, static_cast<GLint>(kLayerInputTextureUnit));
glUseProgram(0);
}
void RuntimeShaderRenderer::UpdateGlobalParams(uint64_t frameIndex, unsigned width, unsigned height)
{
std::vector<unsigned char>& buffer = mGlobalParamsScratch;
buffer = BuildRuntimeShaderGlobalParamsStd140(mArtifact, frameIndex, width, height);
glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsBuffer);
const GLsizeiptr bufferSize = static_cast<GLsizeiptr>(buffer.size());
if (mGlobalParamsBufferSize != bufferSize)
{
glBufferData(GL_UNIFORM_BUFFER, bufferSize, buffer.data(), GL_DYNAMIC_DRAW);
mGlobalParamsBufferSize = bufferSize;
}
else
{
glBufferSubData(GL_UNIFORM_BUFFER, 0, bufferSize, buffer.data());
}
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsBuffer);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
void RuntimeShaderRenderer::BindRuntimeTextures(GLuint sourceTexture, GLuint layerInputTexture)
{
const GLuint resolvedSourceTexture = sourceTexture != 0 ? sourceTexture : mFallbackSourceTexture;
const GLuint resolvedLayerInputTexture = layerInputTexture != 0 ? layerInputTexture : resolvedSourceTexture;
glActiveTexture(GL_TEXTURE0 + kVideoInputTextureUnit);
glBindTexture(GL_TEXTURE_2D, resolvedSourceTexture);
glActiveTexture(GL_TEXTURE0 + kLayerInputTextureUnit);
glBindTexture(GL_TEXTURE_2D, resolvedLayerInputTexture);
glActiveTexture(GL_TEXTURE0);
}
bool RuntimeShaderRenderer::CompileShader(GLenum shaderType, const char* source, GLuint& shader, std::string& error)
{
shader = glCreateShader(shaderType);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint compileResult = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
if (compileResult != GL_FALSE)
return true;
std::array<char, 4096> log = {};
GLsizei length = 0;
glGetShaderInfoLog(shader, static_cast<GLsizei>(log.size()), &length, log.data());
error = std::string(log.data(), static_cast<std::size_t>(length));
glDeleteShader(shader);
shader = 0;
return false;
}
void RuntimeShaderRenderer::DestroyProgram()
{
if (mProgram != 0)
glDeleteProgram(mProgram);
if (mVertexShader != 0)
glDeleteShader(mVertexShader);
if (mFragmentShader != 0)
glDeleteShader(mFragmentShader);
mProgram = 0;
mVertexShader = 0;
mFragmentShader = 0;
}
void RuntimeShaderRenderer::DestroyStaticGlResources()
{
if (mGlobalParamsBuffer != 0)
glDeleteBuffers(1, &mGlobalParamsBuffer);
if (mVertexArray != 0)
glDeleteVertexArrays(1, &mVertexArray);
if (mFallbackSourceTexture != 0)
glDeleteTextures(1, &mFallbackSourceTexture);
mGlobalParamsBuffer = 0;
mGlobalParamsBufferSize = 0;
mVertexArray = 0;
mFallbackSourceTexture = 0;
}

View File

@@ -0,0 +1,56 @@
#pragma once
#include "GLExtensions.h"
#include "RuntimeShaderProgram.h"
#include "../../runtime/RuntimeShaderArtifact.h"
#include <cstdint>
#include <string>
#include <vector>
class RuntimeShaderRenderer
{
public:
RuntimeShaderRenderer() = default;
RuntimeShaderRenderer(const RuntimeShaderRenderer&) = delete;
RuntimeShaderRenderer& operator=(const RuntimeShaderRenderer&) = delete;
~RuntimeShaderRenderer();
bool CommitPreparedProgram(RuntimePreparedShaderProgram& preparedProgram, std::string& error);
bool HasProgram() const { return mProgram != 0; }
void UpdateArtifactState(const RuntimeShaderArtifact& artifact);
void RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint sourceTexture = 0, GLuint layerInputTexture = 0);
void ShutdownGl();
static bool BuildPreparedProgram(
const std::string& layerId,
const std::string& sourceFingerprint,
const RuntimeShaderArtifact& artifact,
RuntimePreparedShaderProgram& preparedProgram);
static bool BuildPreparedPassProgram(
const std::string& layerId,
const std::string& sourceFingerprint,
const RuntimeShaderArtifact& artifact,
const RuntimeShaderPassArtifact& passArtifact,
RuntimePreparedShaderProgram& preparedProgram);
private:
bool EnsureStaticGlResources(std::string& error);
static bool CompileShader(GLenum shaderType, const char* source, GLuint& shader, std::string& error);
static bool BuildProgram(const std::string& fragmentShaderSource, GLuint& program, GLuint& vertexShader, GLuint& fragmentShader, std::string& error);
static void AssignSamplerUniforms(GLuint program);
void UpdateGlobalParams(uint64_t frameIndex, unsigned width, unsigned height);
void BindRuntimeTextures(GLuint sourceTexture, GLuint layerInputTexture);
void DestroyProgram();
void DestroyStaticGlResources();
RuntimeShaderArtifact mArtifact;
GLuint mProgram = 0;
GLuint mVertexShader = 0;
GLuint mFragmentShader = 0;
GLuint mVertexArray = 0;
GLuint mGlobalParamsBuffer = 0;
GLsizeiptr mGlobalParamsBufferSize = 0;
GLuint mFallbackSourceTexture = 0;
std::vector<unsigned char> mGlobalParamsScratch;
};