Clean up
This commit is contained in:
@@ -1,920 +0,0 @@
|
|||||||
#include "DeckLinkSession.h"
|
|
||||||
#include "GLExtensions.h"
|
|
||||||
#include "VideoIOFormat.h"
|
|
||||||
#include "VideoPlayoutPolicy.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cmath>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <deque>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <iostream>
|
|
||||||
#include <mutex>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
constexpr unsigned kDefaultWidth = 1920;
|
|
||||||
constexpr unsigned kDefaultHeight = 1080;
|
|
||||||
constexpr std::size_t kSystemFrameSlots = 12;
|
|
||||||
constexpr std::size_t kPboDepth = 6;
|
|
||||||
constexpr std::size_t kWarmupFrames = 4;
|
|
||||||
constexpr std::size_t kDeckLinkTargetBufferedFrames = 4;
|
|
||||||
|
|
||||||
enum class ProbeSlotState
|
|
||||||
{
|
|
||||||
Free,
|
|
||||||
Rendering,
|
|
||||||
Completed,
|
|
||||||
Scheduled
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ProbeFrame
|
|
||||||
{
|
|
||||||
void* bytes = nullptr;
|
|
||||||
long rowBytes = 0;
|
|
||||||
unsigned width = 0;
|
|
||||||
unsigned height = 0;
|
|
||||||
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
|
|
||||||
std::size_t index = 0;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
uint64_t frameIndex = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ProbeMetrics
|
|
||||||
{
|
|
||||||
uint64_t renderedFrames = 0;
|
|
||||||
uint64_t completedFrames = 0;
|
|
||||||
uint64_t scheduledFrames = 0;
|
|
||||||
uint64_t completedDrops = 0;
|
|
||||||
uint64_t acquireMisses = 0;
|
|
||||||
uint64_t scheduleUnderruns = 0;
|
|
||||||
uint64_t pboQueueMisses = 0;
|
|
||||||
std::size_t freeCount = 0;
|
|
||||||
std::size_t renderingCount = 0;
|
|
||||||
std::size_t completedCount = 0;
|
|
||||||
std::size_t scheduledCount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LatestFrameStore
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LatestFrameStore(unsigned width, unsigned height, std::size_t capacity) :
|
|
||||||
mWidth(width),
|
|
||||||
mHeight(height),
|
|
||||||
mRowBytes(VideoIORowBytes(VideoIOPixelFormat::Bgra8, width))
|
|
||||||
{
|
|
||||||
mSlots.resize(capacity);
|
|
||||||
const std::size_t byteCount = static_cast<std::size_t>(mRowBytes) * static_cast<std::size_t>(mHeight);
|
|
||||||
for (Slot& slot : mSlots)
|
|
||||||
{
|
|
||||||
slot.bytes.resize(byteCount);
|
|
||||||
slot.generation = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AcquireForRender(ProbeFrame& frame)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (!AcquireFreeLocked(frame))
|
|
||||||
{
|
|
||||||
if (!DropOldestCompletedLocked() || !AcquireFreeLocked(frame))
|
|
||||||
{
|
|
||||||
++mMetrics.acquireMisses;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PublishCompleted(const ProbeFrame& frame)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (!IsValidLocked(frame))
|
|
||||||
return false;
|
|
||||||
Slot& slot = mSlots[frame.index];
|
|
||||||
if (slot.state != ProbeSlotState::Rendering)
|
|
||||||
return false;
|
|
||||||
slot.state = ProbeSlotState::Completed;
|
|
||||||
slot.frameIndex = frame.frameIndex;
|
|
||||||
mCompletedIndices.push_back(frame.index);
|
|
||||||
++mMetrics.completedFrames;
|
|
||||||
mCondition.notify_all();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConsumeCompleted(ProbeFrame& frame)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
while (!mCompletedIndices.empty())
|
|
||||||
{
|
|
||||||
const std::size_t index = mCompletedIndices.front();
|
|
||||||
mCompletedIndices.pop_front();
|
|
||||||
if (index >= mSlots.size() || mSlots[index].state != ProbeSlotState::Completed)
|
|
||||||
continue;
|
|
||||||
mSlots[index].state = ProbeSlotState::Scheduled;
|
|
||||||
FillFrameLocked(index, frame);
|
|
||||||
++mMetrics.scheduledFrames;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
++mMetrics.scheduleUnderruns;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReleaseByBytes(void* bytes)
|
|
||||||
{
|
|
||||||
if (bytes == nullptr)
|
|
||||||
return false;
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
for (std::size_t index = 0; index < mSlots.size(); ++index)
|
|
||||||
{
|
|
||||||
if (mSlots[index].bytes.data() != bytes)
|
|
||||||
continue;
|
|
||||||
mSlots[index].state = ProbeSlotState::Free;
|
|
||||||
++mSlots[index].generation;
|
|
||||||
RemoveCompletedIndexLocked(index);
|
|
||||||
mCondition.notify_all();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WaitForCompletedDepth(std::size_t targetDepth, std::chrono::milliseconds timeout)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
|
||||||
return mCondition.wait_for(lock, timeout, [&]() {
|
|
||||||
return CompletedCountLocked() >= targetDepth;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ProbeMetrics Metrics() const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
ProbeMetrics metrics = mMetrics;
|
|
||||||
for (const Slot& slot : mSlots)
|
|
||||||
{
|
|
||||||
switch (slot.state)
|
|
||||||
{
|
|
||||||
case ProbeSlotState::Free:
|
|
||||||
++metrics.freeCount;
|
|
||||||
break;
|
|
||||||
case ProbeSlotState::Rendering:
|
|
||||||
++metrics.renderingCount;
|
|
||||||
break;
|
|
||||||
case ProbeSlotState::Completed:
|
|
||||||
++metrics.completedCount;
|
|
||||||
break;
|
|
||||||
case ProbeSlotState::Scheduled:
|
|
||||||
++metrics.scheduledCount;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return metrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CountRenderedFrame()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
++mMetrics.renderedFrames;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CountPboQueueMiss()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
++mMetrics.pboQueueMisses;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Slot
|
|
||||||
{
|
|
||||||
std::vector<unsigned char> bytes;
|
|
||||||
ProbeSlotState state = ProbeSlotState::Free;
|
|
||||||
uint64_t generation = 1;
|
|
||||||
uint64_t frameIndex = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool AcquireFreeLocked(ProbeFrame& frame)
|
|
||||||
{
|
|
||||||
for (std::size_t index = 0; index < mSlots.size(); ++index)
|
|
||||||
{
|
|
||||||
if (mSlots[index].state != ProbeSlotState::Free)
|
|
||||||
continue;
|
|
||||||
mSlots[index].state = ProbeSlotState::Rendering;
|
|
||||||
++mSlots[index].generation;
|
|
||||||
FillFrameLocked(index, frame);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DropOldestCompletedLocked()
|
|
||||||
{
|
|
||||||
while (!mCompletedIndices.empty())
|
|
||||||
{
|
|
||||||
const std::size_t index = mCompletedIndices.front();
|
|
||||||
mCompletedIndices.pop_front();
|
|
||||||
if (index >= mSlots.size() || mSlots[index].state != ProbeSlotState::Completed)
|
|
||||||
continue;
|
|
||||||
mSlots[index].state = ProbeSlotState::Free;
|
|
||||||
++mSlots[index].generation;
|
|
||||||
++mMetrics.completedDrops;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FillFrameLocked(std::size_t index, ProbeFrame& frame) const
|
|
||||||
{
|
|
||||||
const Slot& slot = mSlots[index];
|
|
||||||
frame.bytes = const_cast<unsigned char*>(slot.bytes.data());
|
|
||||||
frame.rowBytes = static_cast<long>(mRowBytes);
|
|
||||||
frame.width = mWidth;
|
|
||||||
frame.height = mHeight;
|
|
||||||
frame.pixelFormat = VideoIOPixelFormat::Bgra8;
|
|
||||||
frame.index = index;
|
|
||||||
frame.generation = slot.generation;
|
|
||||||
frame.frameIndex = slot.frameIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsValidLocked(const ProbeFrame& frame) const
|
|
||||||
{
|
|
||||||
return frame.index < mSlots.size() && mSlots[frame.index].generation == frame.generation;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RemoveCompletedIndexLocked(std::size_t index)
|
|
||||||
{
|
|
||||||
mCompletedIndices.erase(std::remove(mCompletedIndices.begin(), mCompletedIndices.end(), index), mCompletedIndices.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t CompletedCountLocked() const
|
|
||||||
{
|
|
||||||
std::size_t count = 0;
|
|
||||||
for (const Slot& slot : mSlots)
|
|
||||||
{
|
|
||||||
if (slot.state == ProbeSlotState::Completed)
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned mWidth = 0;
|
|
||||||
unsigned mHeight = 0;
|
|
||||||
unsigned mRowBytes = 0;
|
|
||||||
std::vector<Slot> mSlots;
|
|
||||||
std::deque<std::size_t> mCompletedIndices;
|
|
||||||
mutable std::mutex mMutex;
|
|
||||||
std::condition_variable mCondition;
|
|
||||||
ProbeMetrics mMetrics;
|
|
||||||
};
|
|
||||||
|
|
||||||
LRESULT CALLBACK ProbeWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
||||||
{
|
|
||||||
return DefWindowProc(hwnd, message, wParam, lParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
class HiddenOpenGLContext
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
~HiddenOpenGLContext()
|
|
||||||
{
|
|
||||||
Destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Create(unsigned width, unsigned height, std::string& error)
|
|
||||||
{
|
|
||||||
mInstance = GetModuleHandle(nullptr);
|
|
||||||
WNDCLASSA wc = {};
|
|
||||||
wc.style = CS_OWNDC;
|
|
||||||
wc.lpfnWndProc = ProbeWindowProc;
|
|
||||||
wc.hInstance = mInstance;
|
|
||||||
wc.lpszClassName = "DeckLinkRenderCadenceProbeWindow";
|
|
||||||
RegisterClassA(&wc);
|
|
||||||
|
|
||||||
mWindow = CreateWindowA(
|
|
||||||
wc.lpszClassName,
|
|
||||||
"DeckLink Render Cadence Probe",
|
|
||||||
WS_OVERLAPPEDWINDOW,
|
|
||||||
CW_USEDEFAULT,
|
|
||||||
CW_USEDEFAULT,
|
|
||||||
static_cast<int>(width),
|
|
||||||
static_cast<int>(height),
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
mInstance,
|
|
||||||
nullptr);
|
|
||||||
if (!mWindow)
|
|
||||||
{
|
|
||||||
error = "CreateWindowA failed.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mDc = GetDC(mWindow);
|
|
||||||
if (!mDc)
|
|
||||||
{
|
|
||||||
error = "GetDC failed.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
PIXELFORMATDESCRIPTOR pfd = {};
|
|
||||||
pfd.nSize = sizeof(pfd);
|
|
||||||
pfd.nVersion = 1;
|
|
||||||
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
|
||||||
pfd.iPixelType = PFD_TYPE_RGBA;
|
|
||||||
pfd.cColorBits = 32;
|
|
||||||
pfd.cDepthBits = 0;
|
|
||||||
pfd.iLayerType = PFD_MAIN_PLANE;
|
|
||||||
|
|
||||||
const int pixelFormat = ChoosePixelFormat(mDc, &pfd);
|
|
||||||
if (pixelFormat == 0 || !SetPixelFormat(mDc, pixelFormat, &pfd))
|
|
||||||
{
|
|
||||||
error = "Could not choose/set a pixel format.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mGlrc = wglCreateContext(mDc);
|
|
||||||
if (!mGlrc)
|
|
||||||
{
|
|
||||||
error = "wglCreateContext failed.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MakeCurrent()
|
|
||||||
{
|
|
||||||
return mDc && mGlrc && wglMakeCurrent(mDc, mGlrc);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClearCurrent()
|
|
||||||
{
|
|
||||||
wglMakeCurrent(nullptr, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Destroy()
|
|
||||||
{
|
|
||||||
ClearCurrent();
|
|
||||||
if (mGlrc)
|
|
||||||
{
|
|
||||||
wglDeleteContext(mGlrc);
|
|
||||||
mGlrc = nullptr;
|
|
||||||
}
|
|
||||||
if (mWindow && mDc)
|
|
||||||
{
|
|
||||||
ReleaseDC(mWindow, mDc);
|
|
||||||
mDc = nullptr;
|
|
||||||
}
|
|
||||||
if (mWindow)
|
|
||||||
{
|
|
||||||
DestroyWindow(mWindow);
|
|
||||||
mWindow = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
HINSTANCE mInstance = nullptr;
|
|
||||||
HWND mWindow = nullptr;
|
|
||||||
HDC mDc = nullptr;
|
|
||||||
HGLRC mGlrc = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RenderCadenceProbe
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
RenderCadenceProbe(LatestFrameStore& frameStore, unsigned width, unsigned height, double frameDurationMs) :
|
|
||||||
mFrameStore(frameStore),
|
|
||||||
mWidth(width),
|
|
||||||
mHeight(height),
|
|
||||||
mFrameDuration(std::chrono::duration_cast<Clock::duration>(std::chrono::duration<double, std::milli>(frameDurationMs)))
|
|
||||||
{
|
|
||||||
if (mFrameDuration <= Clock::duration::zero())
|
|
||||||
mFrameDuration = std::chrono::milliseconds(16);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Start(std::string& error)
|
|
||||||
{
|
|
||||||
mStopping = false;
|
|
||||||
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;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Stop()
|
|
||||||
{
|
|
||||||
mStopping = true;
|
|
||||||
if (mThread.joinable())
|
|
||||||
mThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct PboSlot
|
|
||||||
{
|
|
||||||
GLuint pbo = 0;
|
|
||||||
GLsync fence = nullptr;
|
|
||||||
bool inFlight = false;
|
|
||||||
uint64_t frameIndex = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
using Clock = std::chrono::steady_clock;
|
|
||||||
|
|
||||||
void ThreadMain()
|
|
||||||
{
|
|
||||||
std::string error;
|
|
||||||
HiddenOpenGLContext context;
|
|
||||||
if (!context.Create(mWidth, mHeight, error) || !context.MakeCurrent())
|
|
||||||
{
|
|
||||||
SignalStartupFailure(error.empty() ? "OpenGL context creation failed." : error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!ResolveGLExtensions())
|
|
||||||
{
|
|
||||||
SignalStartupFailure("OpenGL extension resolution failed.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!CreateRenderTargets())
|
|
||||||
{
|
|
||||||
SignalStartupFailure("OpenGL render target creation failed.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CreatePbos();
|
|
||||||
SignalStarted();
|
|
||||||
|
|
||||||
auto nextRenderTime = Clock::now();
|
|
||||||
while (!mStopping)
|
|
||||||
{
|
|
||||||
ConsumeCompletedPbos();
|
|
||||||
|
|
||||||
const auto now = Clock::now();
|
|
||||||
if (now < nextRenderTime)
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for((std::min)(std::chrono::milliseconds(1), std::chrono::duration_cast<std::chrono::milliseconds>(nextRenderTime - now)));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderPattern(mFrameIndex);
|
|
||||||
if (!QueueReadback(mFrameIndex))
|
|
||||||
mFrameStore.CountPboQueueMiss();
|
|
||||||
mFrameStore.CountRenderedFrame();
|
|
||||||
++mFrameIndex;
|
|
||||||
nextRenderTime += mFrameDuration;
|
|
||||||
if (Clock::now() - nextRenderTime > mFrameDuration * 4)
|
|
||||||
nextRenderTime = Clock::now() + mFrameDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
FlushPbos();
|
|
||||||
DestroyPbos();
|
|
||||||
DestroyRenderTargets();
|
|
||||||
context.ClearCurrent();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CreateRenderTargets()
|
|
||||||
{
|
|
||||||
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 DestroyRenderTargets()
|
|
||||||
{
|
|
||||||
if (mFramebuffer != 0)
|
|
||||||
glDeleteFramebuffers(1, &mFramebuffer);
|
|
||||||
if (mTexture != 0)
|
|
||||||
glDeleteTextures(1, &mTexture);
|
|
||||||
mFramebuffer = 0;
|
|
||||||
mTexture = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreatePbos()
|
|
||||||
{
|
|
||||||
mPbos.resize(kPboDepth);
|
|
||||||
const std::size_t byteCount = static_cast<std::size_t>(VideoIORowBytes(VideoIOPixelFormat::Bgra8, mWidth)) * mHeight;
|
|
||||||
for (PboSlot& slot : mPbos)
|
|
||||||
{
|
|
||||||
glGenBuffers(1, &slot.pbo);
|
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pbo);
|
|
||||||
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(byteCount), nullptr, GL_STREAM_READ);
|
|
||||||
}
|
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DestroyPbos()
|
|
||||||
{
|
|
||||||
for (PboSlot& slot : mPbos)
|
|
||||||
{
|
|
||||||
if (slot.fence)
|
|
||||||
glDeleteSync(slot.fence);
|
|
||||||
if (slot.pbo != 0)
|
|
||||||
glDeleteBuffers(1, &slot.pbo);
|
|
||||||
slot = {};
|
|
||||||
}
|
|
||||||
mPbos.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlushPbos()
|
|
||||||
{
|
|
||||||
for (std::size_t i = 0; i < mPbos.size() * 2; ++i)
|
|
||||||
ConsumeCompletedPbos();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderPattern(uint64_t frameIndex)
|
|
||||||
{
|
|
||||||
const float t = static_cast<float>(frameIndex) / 60.0f;
|
|
||||||
const float red = 0.1f + 0.4f * (0.5f + 0.5f * std::sin(t));
|
|
||||||
const float green = 0.1f + 0.4f * (0.5f + 0.5f * std::sin(t * 0.73f + 1.0f));
|
|
||||||
const float blue = 0.15f + 0.3f * (0.5f + 0.5f * std::sin(t * 0.41f + 2.0f));
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
|
|
||||||
glViewport(0, 0, static_cast<GLsizei>(mWidth), static_cast<GLsizei>(mHeight));
|
|
||||||
glDisable(GL_SCISSOR_TEST);
|
|
||||||
glClearColor(red, green, blue, 1.0f);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
|
|
||||||
const int boxWidth = static_cast<int>(mWidth / 6);
|
|
||||||
const int boxHeight = static_cast<int>(mHeight / 5);
|
|
||||||
const float phase = 0.5f + 0.5f * std::sin(t * 1.7f);
|
|
||||||
const int x = static_cast<int>(phase * static_cast<float>(mWidth - boxWidth));
|
|
||||||
const int y = static_cast<int>((0.5f + 0.5f * std::sin(t * 1.1f + 0.8f)) * static_cast<float>(mHeight - boxHeight));
|
|
||||||
|
|
||||||
glEnable(GL_SCISSOR_TEST);
|
|
||||||
glScissor(x, y, boxWidth, boxHeight);
|
|
||||||
glClearColor(1.0f - red, 0.85f, 0.15f + blue, 1.0f);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
glDisable(GL_SCISSOR_TEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QueueReadback(uint64_t frameIndex)
|
|
||||||
{
|
|
||||||
if (mPbos.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
PboSlot& slot = mPbos[mWriteIndex];
|
|
||||||
if (slot.inFlight)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const std::size_t byteCount = static_cast<std::size_t>(VideoIORowBytes(VideoIOPixelFormat::Bgra8, mWidth)) * mHeight;
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebuffer);
|
|
||||||
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>(byteCount), nullptr, GL_STREAM_READ);
|
|
||||||
glReadPixels(0, 0, static_cast<GLsizei>(mWidth), static_cast<GLsizei>(mHeight), 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);
|
|
||||||
mWriteIndex = (mWriteIndex + 1) % mPbos.size();
|
|
||||||
return slot.inFlight;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConsumeCompletedPbos()
|
|
||||||
{
|
|
||||||
for (std::size_t checked = 0; checked < mPbos.size(); ++checked)
|
|
||||||
{
|
|
||||||
PboSlot& slot = mPbos[mReadIndex];
|
|
||||||
if (!slot.inFlight || slot.fence == nullptr)
|
|
||||||
{
|
|
||||||
mReadIndex = (mReadIndex + 1) % mPbos.size();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLenum waitResult = glClientWaitSync(slot.fence, 0, 0);
|
|
||||||
if (waitResult != GL_ALREADY_SIGNALED && waitResult != GL_CONDITION_SATISFIED)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ProbeFrame frame;
|
|
||||||
if (mFrameStore.AcquireForRender(frame))
|
|
||||||
{
|
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pbo);
|
|
||||||
void* mapped = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
|
||||||
if (mapped)
|
|
||||||
{
|
|
||||||
const std::size_t byteCount = static_cast<std::size_t>(frame.rowBytes) * frame.height;
|
|
||||||
std::memcpy(frame.bytes, mapped, byteCount);
|
|
||||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
|
||||||
frame.frameIndex = slot.frameIndex;
|
|
||||||
mFrameStore.PublishCompleted(frame);
|
|
||||||
}
|
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
glDeleteSync(slot.fence);
|
|
||||||
slot.fence = nullptr;
|
|
||||||
slot.inFlight = false;
|
|
||||||
mReadIndex = (mReadIndex + 1) % mPbos.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SignalStarted()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mStartupMutex);
|
|
||||||
mStarted = true;
|
|
||||||
mStartupCondition.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SignalStartupFailure(const std::string& error)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mStartupMutex);
|
|
||||||
mStartupError = error;
|
|
||||||
mStartupCondition.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
LatestFrameStore& mFrameStore;
|
|
||||||
unsigned mWidth = 0;
|
|
||||||
unsigned mHeight = 0;
|
|
||||||
Clock::duration mFrameDuration;
|
|
||||||
std::thread mThread;
|
|
||||||
std::atomic<bool> mStopping{ false };
|
|
||||||
std::mutex mStartupMutex;
|
|
||||||
std::condition_variable mStartupCondition;
|
|
||||||
bool mStarted = false;
|
|
||||||
std::string mStartupError;
|
|
||||||
GLuint mFramebuffer = 0;
|
|
||||||
GLuint mTexture = 0;
|
|
||||||
std::vector<PboSlot> mPbos;
|
|
||||||
std::size_t mWriteIndex = 0;
|
|
||||||
std::size_t mReadIndex = 0;
|
|
||||||
uint64_t mFrameIndex = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DeckLinkProbePlayout
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DeckLinkProbePlayout(DeckLinkSession& session, LatestFrameStore& frameStore) :
|
|
||||||
mSession(session),
|
|
||||||
mFrameStore(frameStore)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Start()
|
|
||||||
{
|
|
||||||
mStopping = false;
|
|
||||||
mThread = std::thread([this]() { ThreadMain(); });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Stop()
|
|
||||||
{
|
|
||||||
mStopping = true;
|
|
||||||
if (mThread.joinable())
|
|
||||||
mThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThreadMain()
|
|
||||||
{
|
|
||||||
while (!mStopping)
|
|
||||||
{
|
|
||||||
const ProbeMetrics metrics = mFrameStore.Metrics();
|
|
||||||
if (metrics.scheduledCount >= kDeckLinkTargetBufferedFrames)
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProbeFrame frame;
|
|
||||||
if (!mFrameStore.ConsumeCompleted(frame))
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoIOOutputFrame outputFrame;
|
|
||||||
outputFrame.bytes = frame.bytes;
|
|
||||||
outputFrame.nativeBuffer = frame.bytes;
|
|
||||||
outputFrame.rowBytes = frame.rowBytes;
|
|
||||||
outputFrame.width = frame.width;
|
|
||||||
outputFrame.height = frame.height;
|
|
||||||
outputFrame.pixelFormat = frame.pixelFormat;
|
|
||||||
|
|
||||||
if (!mSession.ScheduleOutputFrame(outputFrame))
|
|
||||||
{
|
|
||||||
mFrameStore.ReleaseByBytes(frame.bytes);
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
DeckLinkSession& mSession;
|
|
||||||
LatestFrameStore& mFrameStore;
|
|
||||||
std::thread mThread;
|
|
||||||
std::atomic<bool> mStopping{ false };
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string CompletionResultToString(VideoIOCompletionResult result)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
case VideoIOCompletionResult::Completed:
|
|
||||||
return "completed";
|
|
||||||
case VideoIOCompletionResult::DisplayedLate:
|
|
||||||
return "late";
|
|
||||||
case VideoIOCompletionResult::Dropped:
|
|
||||||
return "dropped";
|
|
||||||
case VideoIOCompletionResult::Flushed:
|
|
||||||
return "flushed";
|
|
||||||
case VideoIOCompletionResult::Unknown:
|
|
||||||
default:
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrintUsage()
|
|
||||||
{
|
|
||||||
std::cout << "DeckLinkRenderCadenceProbe\n"
|
|
||||||
<< " Renders a simple OpenGL BGRA8 motion pattern on one GL thread,\n"
|
|
||||||
<< " copies completed PBO readbacks into latest-N system memory slots,\n"
|
|
||||||
<< " warms up rendered frames, then feeds DeckLink scheduled playback.\n\n"
|
|
||||||
<< "Press Enter to stop.\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComInitGuard
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
~ComInitGuard()
|
|
||||||
{
|
|
||||||
if (mInitialized)
|
|
||||||
CoUninitialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Initialize()
|
|
||||||
{
|
|
||||||
const HRESULT result = CoInitialize(nullptr);
|
|
||||||
mInitialized = SUCCEEDED(result);
|
|
||||||
mResult = result;
|
|
||||||
return mInitialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
HRESULT Result() const { return mResult; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool mInitialized = false;
|
|
||||||
HRESULT mResult = S_OK;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
PrintUsage();
|
|
||||||
|
|
||||||
ComInitGuard com;
|
|
||||||
if (!com.Initialize())
|
|
||||||
{
|
|
||||||
std::cerr << "COM initialization failed: 0x" << std::hex << com.Result() << std::dec << "\n";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
LatestFrameStore frameStore(kDefaultWidth, kDefaultHeight, kSystemFrameSlots);
|
|
||||||
DeckLinkSession deckLink;
|
|
||||||
std::atomic<uint64_t> completions{ 0 };
|
|
||||||
std::atomic<uint64_t> late{ 0 };
|
|
||||||
std::atomic<uint64_t> dropped{ 0 };
|
|
||||||
|
|
||||||
VideoFormatSelection formats;
|
|
||||||
std::string error;
|
|
||||||
if (!deckLink.DiscoverDevicesAndModes(formats, error))
|
|
||||||
{
|
|
||||||
std::cerr << "DeckLink discovery failed: " << error << "\n";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (!deckLink.SelectPreferredFormats(formats, false, error))
|
|
||||||
{
|
|
||||||
std::cerr << "DeckLink format selection failed: " << error << "\n";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (!deckLink.ConfigureOutput(
|
|
||||||
[&](const VideoIOCompletion& completion) {
|
|
||||||
frameStore.ReleaseByBytes(completion.outputFrameBuffer);
|
|
||||||
++completions;
|
|
||||||
if (completion.result == VideoIOCompletionResult::DisplayedLate)
|
|
||||||
++late;
|
|
||||||
else if (completion.result == VideoIOCompletionResult::Dropped)
|
|
||||||
++dropped;
|
|
||||||
},
|
|
||||||
formats.output,
|
|
||||||
false,
|
|
||||||
error))
|
|
||||||
{
|
|
||||||
std::cerr << "DeckLink output configuration failed: " << error << "\n";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (!deckLink.PrepareOutputSchedule())
|
|
||||||
{
|
|
||||||
std::cerr << "DeckLink schedule preparation failed.\n";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const VideoIOState& state = deckLink.State();
|
|
||||||
if (state.outputFrameSize.width != kDefaultWidth || state.outputFrameSize.height != kDefaultHeight)
|
|
||||||
{
|
|
||||||
std::cerr << "This probe currently expects 1920x1080 output. Selected mode is "
|
|
||||||
<< state.outputFrameSize.width << "x" << state.outputFrameSize.height << ".\n";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderCadenceProbe renderer(frameStore, state.outputFrameSize.width, state.outputFrameSize.height, state.frameBudgetMilliseconds);
|
|
||||||
if (!renderer.Start(error))
|
|
||||||
{
|
|
||||||
std::cerr << "Render thread start failed: " << error << "\n";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "Warming up " << kWarmupFrames << " rendered frames at cadence...\n";
|
|
||||||
if (!frameStore.WaitForCompletedDepth(kWarmupFrames, std::chrono::seconds(3)))
|
|
||||||
{
|
|
||||||
std::cerr << "Timed out waiting for rendered warmup frames.\n";
|
|
||||||
renderer.Stop();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
DeckLinkProbePlayout playout(deckLink, frameStore);
|
|
||||||
playout.Start();
|
|
||||||
|
|
||||||
const auto prerollDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
|
|
||||||
while (std::chrono::steady_clock::now() < prerollDeadline)
|
|
||||||
{
|
|
||||||
if (frameStore.Metrics().scheduledCount >= kDeckLinkTargetBufferedFrames)
|
|
||||||
break;
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!deckLink.StartScheduledPlayback())
|
|
||||||
{
|
|
||||||
std::cerr << "DeckLink scheduled playback failed to start.\n";
|
|
||||||
playout.Stop();
|
|
||||||
renderer.Stop();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::atomic<bool> metricsStopping{ false };
|
|
||||||
std::thread metricsThread([&]() {
|
|
||||||
uint64_t lastRendered = 0;
|
|
||||||
uint64_t lastScheduled = 0;
|
|
||||||
auto lastTime = std::chrono::steady_clock::now();
|
|
||||||
while (!metricsStopping)
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
||||||
const auto now = std::chrono::steady_clock::now();
|
|
||||||
const double seconds = std::chrono::duration_cast<std::chrono::duration<double>>(now - lastTime).count();
|
|
||||||
const ProbeMetrics metrics = frameStore.Metrics();
|
|
||||||
const double renderFps = seconds > 0.0 ? static_cast<double>(metrics.renderedFrames - lastRendered) / seconds : 0.0;
|
|
||||||
const double scheduleFps = seconds > 0.0 ? static_cast<double>(metrics.scheduledFrames - lastScheduled) / seconds : 0.0;
|
|
||||||
lastRendered = metrics.renderedFrames;
|
|
||||||
lastScheduled = metrics.scheduledFrames;
|
|
||||||
lastTime = now;
|
|
||||||
|
|
||||||
std::cout << std::fixed << std::setprecision(1)
|
|
||||||
<< "renderFps=" << renderFps
|
|
||||||
<< " scheduleFps=" << scheduleFps
|
|
||||||
<< " free=" << metrics.freeCount
|
|
||||||
<< " completed=" << metrics.completedCount
|
|
||||||
<< " scheduled=" << metrics.scheduledCount
|
|
||||||
<< " drops=" << metrics.completedDrops
|
|
||||||
<< " pboMiss=" << metrics.pboQueueMisses
|
|
||||||
<< " completions=" << completions.load()
|
|
||||||
<< " late=" << late.load()
|
|
||||||
<< " dropped=" << dropped.load()
|
|
||||||
<< " decklinkBuffered=" << deckLink.State().actualDeckLinkBufferedFrames
|
|
||||||
<< "\n";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
std::string line;
|
|
||||||
std::getline(std::cin, line);
|
|
||||||
|
|
||||||
metricsStopping = true;
|
|
||||||
if (metricsThread.joinable())
|
|
||||||
metricsThread.join();
|
|
||||||
playout.Stop();
|
|
||||||
deckLink.Stop();
|
|
||||||
renderer.Stop();
|
|
||||||
deckLink.ReleaseResources();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# DeckLink Render Cadence Probe
|
|
||||||
|
|
||||||
This is a deliberately small architecture probe for the Phase 7.7 playout model.
|
|
||||||
|
|
||||||
It is not the main app and does not use the main runtime, shader stack, preview path, input upload path, or render engine.
|
|
||||||
|
|
||||||
## What It Tests
|
|
||||||
|
|
||||||
The probe validates the clean playout spine:
|
|
||||||
|
|
||||||
```text
|
|
||||||
single OpenGL render thread
|
|
||||||
owns its own hidden GL context
|
|
||||||
renders a simple moving BGRA8 pattern at output cadence
|
|
||||||
queues GPU readback through a PBO ring
|
|
||||||
copies completed readbacks into latest-N system-memory slots
|
|
||||||
|
|
||||||
system-memory frame store
|
|
||||||
owns free / rendering / completed / scheduled slots
|
|
||||||
drops old completed unscheduled frames when render cadence needs space
|
|
||||||
protects scheduled frames until DeckLink completion
|
|
||||||
|
|
||||||
DeckLink playout thread
|
|
||||||
consumes completed system-memory frames
|
|
||||||
keeps a small scheduled buffer filled
|
|
||||||
does not render
|
|
||||||
```
|
|
||||||
|
|
||||||
Startup warms up rendered frames before starting DeckLink scheduled playback.
|
|
||||||
|
|
||||||
## How To Build
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cmake --build --preset build-debug --target DeckLinkRenderCadenceProbe -- /m:1
|
|
||||||
```
|
|
||||||
|
|
||||||
The executable is:
|
|
||||||
|
|
||||||
```text
|
|
||||||
build\vs2022-x64-debug\Debug\DeckLinkRenderCadenceProbe.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
## How To Run
|
|
||||||
|
|
||||||
Run it from a terminal so you can see the telemetry:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
build\vs2022-x64-debug\Debug\DeckLinkRenderCadenceProbe.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
Press Enter to stop.
|
|
||||||
|
|
||||||
The first version assumes `1080p59.94` / `1920x1080` output and BGRA8 system-memory frames.
|
|
||||||
|
|
||||||
## What To Watch
|
|
||||||
|
|
||||||
The probe prints one line per second:
|
|
||||||
|
|
||||||
- `renderFps`: cadence render throughput
|
|
||||||
- `scheduleFps`: DeckLink scheduling throughput
|
|
||||||
- `free`: free system-memory slots
|
|
||||||
- `completed`: rendered, unscheduled slots
|
|
||||||
- `scheduled`: slots currently owned by DeckLink
|
|
||||||
- `drops`: old completed unscheduled frames recycled by the latest-N cache
|
|
||||||
- `pboMiss`: PBO ring was full when trying to queue readback
|
|
||||||
- `late`: DeckLink displayed-late completions
|
|
||||||
- `dropped`: DeckLink dropped completions
|
|
||||||
- `decklinkBuffered`: actual DeckLink buffered-frame count when available
|
|
||||||
|
|
||||||
For a healthy architecture proof, expect:
|
|
||||||
|
|
||||||
- `renderFps` close to the selected output cadence
|
|
||||||
- `scheduleFps` close to the selected output cadence after warmup
|
|
||||||
- `scheduled` hovering near the target buffer depth
|
|
||||||
- `late` and `dropped` not increasing continuously
|
|
||||||
- visible motion that is smooth on the DeckLink output
|
|
||||||
|
|
||||||
## Interpretation
|
|
||||||
|
|
||||||
If this probe is smooth at 59.94/60, the broad architecture is viable and the main app's remaining stutters are likely caused by integration details such as input upload, shared render-thread work, preview/screenshot work, or runtime/render-state coupling.
|
|
||||||
|
|
||||||
If this probe is not smooth, the problem is lower level: DeckLink scheduling, OpenGL readback, Windows scheduling, or hardware/driver behavior.
|
|
||||||
|
|
||||||
## Initial Result
|
|
||||||
|
|
||||||
Date: 2026-05-12
|
|
||||||
|
|
||||||
User-visible result:
|
|
||||||
|
|
||||||
- output looked smooth
|
|
||||||
|
|
||||||
Representative telemetry:
|
|
||||||
|
|
||||||
```text
|
|
||||||
renderFps=59.9 scheduleFps=59.9 free=7 completed=1 scheduled=4 drops=0 pboMiss=0 completions=119 late=0 dropped=0 decklinkBuffered=4
|
|
||||||
renderFps=59.9 scheduleFps=59.9 free=7 completed=1 scheduled=4 drops=0 pboMiss=0 completions=179 late=0 dropped=0 decklinkBuffered=4
|
|
||||||
renderFps=59.8 scheduleFps=59.8 free=7 completed=1 scheduled=4 drops=0 pboMiss=0 completions=239 late=0 dropped=0 decklinkBuffered=4
|
|
||||||
renderFps=60.8 scheduleFps=59.8 free=7 completed=1 scheduled=4 drops=0 pboMiss=0 completions=299 late=0 dropped=0 decklinkBuffered=4
|
|
||||||
renderFps=59.9 scheduleFps=59.9 free=7 completed=1 scheduled=4 drops=0 pboMiss=0 completions=360 late=0 dropped=0 decklinkBuffered=4
|
|
||||||
renderFps=59.8 scheduleFps=60.8 free=8 completed=0 scheduled=4 drops=0 pboMiss=0 completions=420 late=0 dropped=0 decklinkBuffered=4
|
|
||||||
```
|
|
||||||
|
|
||||||
Read:
|
|
||||||
|
|
||||||
- the clean architecture can sustain the selected output cadence on the test machine
|
|
||||||
- BGRA8 PBO readback is viable when isolated from the main app's other render-thread work
|
|
||||||
- latest-N system-memory buffering stayed stable
|
|
||||||
- DeckLink actual buffered depth stayed at 4
|
|
||||||
- there were no late frames, dropped frames, completed-frame drops, or PBO misses in the sampled output
|
|
||||||
|
|
||||||
Implication:
|
|
||||||
|
|
||||||
The main app's remaining stutters are likely integration/ownership issues rather than a fundamental DeckLink/OpenGL/BGRA8 readback limit. The highest-value suspects are input upload before output render, shared render-thread queue contention, preview/screenshot work, and runtime/render-state work on the output path.
|
|
||||||
@@ -1,542 +0,0 @@
|
|||||||
#include "stdafx.h"
|
|
||||||
#include "resource.h"
|
|
||||||
#include "OpenGLComposite.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <shellapi.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#ifndef WGL_CONTEXT_MAJOR_VERSION_ARB
|
|
||||||
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
|
|
||||||
#endif
|
|
||||||
#ifndef WGL_CONTEXT_MINOR_VERSION_ARB
|
|
||||||
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
|
|
||||||
#endif
|
|
||||||
#ifndef WGL_CONTEXT_PROFILE_MASK_ARB
|
|
||||||
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
|
|
||||||
#endif
|
|
||||||
#ifndef WGL_CONTEXT_CORE_PROFILE_BIT_ARB
|
|
||||||
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define MAX_LOADSTRING 100
|
|
||||||
|
|
||||||
// Declaration for Window procedure
|
|
||||||
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
|
|
||||||
typedef HGLRC (WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hdc, HGLRC hShareContext, const int* attribList);
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
const int kStatusPanelWidth = 680;
|
|
||||||
const int kStatusPanelHeight = 92;
|
|
||||||
const int kStatusPadding = 8;
|
|
||||||
const int kStatusLabelWidth = 58;
|
|
||||||
const int kStatusButtonWidth = 86;
|
|
||||||
const int kStatusRowHeight = 24;
|
|
||||||
const int kStatusGap = 6;
|
|
||||||
const UINT kCreateStatusStripMessage = WM_APP + 1;
|
|
||||||
|
|
||||||
enum StatusControlId
|
|
||||||
{
|
|
||||||
kControlUrlEditId = 2001,
|
|
||||||
kDocsUrlEditId = 2002,
|
|
||||||
kOscAddressEditId = 2003,
|
|
||||||
kOpenControlButtonId = 2004,
|
|
||||||
kOpenDocsButtonId = 2005
|
|
||||||
};
|
|
||||||
|
|
||||||
struct StatusStripControls
|
|
||||||
{
|
|
||||||
HWND panel = NULL;
|
|
||||||
HWND controlLabel = NULL;
|
|
||||||
HWND controlUrl = NULL;
|
|
||||||
HWND openControl = NULL;
|
|
||||||
HWND docsLabel = NULL;
|
|
||||||
HWND docsUrl = NULL;
|
|
||||||
HWND openDocs = NULL;
|
|
||||||
HWND oscLabel = NULL;
|
|
||||||
HWND oscAddress = NULL;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool StatusStripCreated(const StatusStripControls& controls)
|
|
||||||
{
|
|
||||||
return controls.panel != NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
HWND CreateStatusChild(HWND parent, const char* className, const char* text, DWORD style, DWORD exStyle, int controlId)
|
|
||||||
{
|
|
||||||
return CreateWindowExA(
|
|
||||||
exStyle,
|
|
||||||
className,
|
|
||||||
text,
|
|
||||||
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | style,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
parent,
|
|
||||||
reinterpret_cast<HMENU>(static_cast<INT_PTR>(controlId)),
|
|
||||||
reinterpret_cast<HINSTANCE>(GetWindowLongPtr(parent, GWLP_HINSTANCE)),
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreateStatusStrip(HWND hWnd, StatusStripControls& controls)
|
|
||||||
{
|
|
||||||
controls.panel = CreateStatusChild(hWnd, "STATIC", "", SS_LEFT, WS_EX_CLIENTEDGE, 0);
|
|
||||||
controls.controlLabel = CreateStatusChild(hWnd, "STATIC", "Control", SS_LEFT, 0, 0);
|
|
||||||
controls.controlUrl = CreateStatusChild(hWnd, "EDIT", "", ES_AUTOHSCROLL | ES_READONLY | WS_TABSTOP, WS_EX_CLIENTEDGE, kControlUrlEditId);
|
|
||||||
controls.openControl = CreateStatusChild(hWnd, "BUTTON", "Open", BS_PUSHBUTTON | WS_TABSTOP, 0, kOpenControlButtonId);
|
|
||||||
controls.docsLabel = CreateStatusChild(hWnd, "STATIC", "Docs", SS_LEFT, 0, 0);
|
|
||||||
controls.docsUrl = CreateStatusChild(hWnd, "EDIT", "", ES_AUTOHSCROLL | ES_READONLY | WS_TABSTOP, WS_EX_CLIENTEDGE, kDocsUrlEditId);
|
|
||||||
controls.openDocs = CreateStatusChild(hWnd, "BUTTON", "Open", BS_PUSHBUTTON | WS_TABSTOP, 0, kOpenDocsButtonId);
|
|
||||||
controls.oscLabel = CreateStatusChild(hWnd, "STATIC", "OSC", SS_LEFT, 0, 0);
|
|
||||||
controls.oscAddress = CreateStatusChild(hWnd, "EDIT", "", ES_AUTOHSCROLL | ES_READONLY | WS_TABSTOP, WS_EX_CLIENTEDGE, kOscAddressEditId);
|
|
||||||
|
|
||||||
HFONT guiFont = reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
|
|
||||||
HWND children[] = {
|
|
||||||
controls.controlLabel,
|
|
||||||
controls.controlUrl,
|
|
||||||
controls.openControl,
|
|
||||||
controls.docsLabel,
|
|
||||||
controls.docsUrl,
|
|
||||||
controls.openDocs,
|
|
||||||
controls.oscLabel,
|
|
||||||
controls.oscAddress
|
|
||||||
};
|
|
||||||
for (HWND child : children)
|
|
||||||
{
|
|
||||||
if (child)
|
|
||||||
SendMessage(child, WM_SETFONT, reinterpret_cast<WPARAM>(guiFont), TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetWindowTextA(controls.controlUrl, "Starting control server...");
|
|
||||||
SetWindowTextA(controls.docsUrl, "Starting API docs...");
|
|
||||||
SetWindowTextA(controls.oscAddress, "Starting OSC listener...");
|
|
||||||
}
|
|
||||||
|
|
||||||
void RaiseStatusControls(const StatusStripControls& controls)
|
|
||||||
{
|
|
||||||
if (!StatusStripCreated(controls))
|
|
||||||
return;
|
|
||||||
|
|
||||||
SetWindowPos(controls.panel, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
||||||
|
|
||||||
HWND interactiveControls[] = {
|
|
||||||
controls.controlLabel,
|
|
||||||
controls.controlUrl,
|
|
||||||
controls.openControl,
|
|
||||||
controls.docsLabel,
|
|
||||||
controls.docsUrl,
|
|
||||||
controls.openDocs,
|
|
||||||
controls.oscLabel,
|
|
||||||
controls.oscAddress
|
|
||||||
};
|
|
||||||
for (HWND control : interactiveControls)
|
|
||||||
{
|
|
||||||
if (control)
|
|
||||||
SetWindowPos(control, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LayoutStatusStrip(HWND hWnd, const StatusStripControls& controls)
|
|
||||||
{
|
|
||||||
RECT clientRect = {};
|
|
||||||
if (!GetClientRect(hWnd, &clientRect) || !controls.panel)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const int clientWidth = static_cast<int>(clientRect.right - clientRect.left);
|
|
||||||
const int clientHeight = static_cast<int>(clientRect.bottom - clientRect.top);
|
|
||||||
const int panelWidth = std::max(280, std::min(kStatusPanelWidth, clientWidth - (kStatusPadding * 2)));
|
|
||||||
const int panelHeight = kStatusPanelHeight;
|
|
||||||
const int panelLeft = kStatusPadding;
|
|
||||||
const int panelTop = std::max(kStatusPadding, clientHeight - panelHeight - kStatusPadding);
|
|
||||||
MoveWindow(controls.panel, panelLeft, panelTop, panelWidth, panelHeight, TRUE);
|
|
||||||
|
|
||||||
const int rowX = panelLeft + kStatusPadding;
|
|
||||||
const int editX = rowX + kStatusLabelWidth + kStatusGap;
|
|
||||||
const int buttonX = panelLeft + panelWidth - kStatusPadding - kStatusButtonWidth;
|
|
||||||
const int editWidth = std::max(80, buttonX - editX - kStatusGap);
|
|
||||||
const int oscWidth = std::max(80, panelLeft + panelWidth - editX - kStatusPadding);
|
|
||||||
const int row1 = panelTop + kStatusPadding;
|
|
||||||
const int row2 = row1 + kStatusRowHeight + kStatusGap;
|
|
||||||
const int row3 = row2 + kStatusRowHeight + kStatusGap;
|
|
||||||
|
|
||||||
MoveWindow(controls.controlLabel, rowX, row1 + 3, kStatusLabelWidth, kStatusRowHeight, TRUE);
|
|
||||||
MoveWindow(controls.controlUrl, editX, row1, editWidth, kStatusRowHeight, TRUE);
|
|
||||||
MoveWindow(controls.openControl, buttonX, row1, kStatusButtonWidth, kStatusRowHeight, TRUE);
|
|
||||||
MoveWindow(controls.docsLabel, rowX, row2 + 3, kStatusLabelWidth, kStatusRowHeight, TRUE);
|
|
||||||
MoveWindow(controls.docsUrl, editX, row2, editWidth, kStatusRowHeight, TRUE);
|
|
||||||
MoveWindow(controls.openDocs, buttonX, row2, kStatusButtonWidth, kStatusRowHeight, TRUE);
|
|
||||||
MoveWindow(controls.oscLabel, rowX, row3 + 3, kStatusLabelWidth, kStatusRowHeight, TRUE);
|
|
||||||
MoveWindow(controls.oscAddress, editX, row3, oscWidth, kStatusRowHeight, TRUE);
|
|
||||||
RaiseStatusControls(controls);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateStatusStrip(const StatusStripControls& controls, const OpenGLComposite& composite)
|
|
||||||
{
|
|
||||||
if (!StatusStripCreated(controls))
|
|
||||||
return;
|
|
||||||
|
|
||||||
SetWindowTextA(controls.controlUrl, composite.GetControlUrl().c_str());
|
|
||||||
SetWindowTextA(controls.docsUrl, composite.GetDocsUrl().c_str());
|
|
||||||
SetWindowTextA(controls.oscAddress, composite.GetOscAddress().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenUrl(const char* url)
|
|
||||||
{
|
|
||||||
ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShowUnhandledExceptionMessage(const char* prefix)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (const std::exception& exception)
|
|
||||||
{
|
|
||||||
std::string message = std::string(prefix) + "\n\n" + exception.what();
|
|
||||||
MessageBoxA(NULL, message.c_str(), "Unhandled exception", MB_OK | MB_ICONERROR);
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, prefix, "Unhandled exception", MB_OK | MB_ICONERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select the pixel format for a given device context
|
|
||||||
void SetDCPixelFormat(HDC hDC)
|
|
||||||
{
|
|
||||||
int nPixelFormat;
|
|
||||||
|
|
||||||
static PIXELFORMATDESCRIPTOR pfd = {
|
|
||||||
sizeof(PIXELFORMATDESCRIPTOR), // Size of this structure
|
|
||||||
1, // Version of this structure
|
|
||||||
PFD_DRAW_TO_WINDOW | // Draw to Window (not to bitmap)
|
|
||||||
PFD_SUPPORT_OPENGL | // Support OpenGL calls in window
|
|
||||||
PFD_DOUBLEBUFFER, // Double buffered mode
|
|
||||||
PFD_TYPE_RGBA, // RGBA Color mode
|
|
||||||
32, // Want 32 bit color
|
|
||||||
0,0,0,0,0,0, // Not used to select mode
|
|
||||||
0,0, // Not used to select mode
|
|
||||||
0,0,0,0,0, // Not used to select mode
|
|
||||||
16, // Size of depth buffer
|
|
||||||
0, // Not used
|
|
||||||
0, // Not used
|
|
||||||
0, // Not used
|
|
||||||
0, // Not used
|
|
||||||
0,0,0 }; // Not used
|
|
||||||
|
|
||||||
// Choose a pixel format that best matches that described in pfd
|
|
||||||
nPixelFormat = ChoosePixelFormat(hDC, &pfd);
|
|
||||||
|
|
||||||
// Set the pixel format for the device context
|
|
||||||
SetPixelFormat(hDC, nPixelFormat, &pfd);
|
|
||||||
}
|
|
||||||
|
|
||||||
HGLRC CreateModernOpenGLContext(HDC hDC)
|
|
||||||
{
|
|
||||||
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB =
|
|
||||||
reinterpret_cast<PFNWGLCREATECONTEXTATTRIBSARBPROC>(wglGetProcAddress("wglCreateContextAttribsARB"));
|
|
||||||
if (!wglCreateContextAttribsARB)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
const int versionCandidates[][2] =
|
|
||||||
{
|
|
||||||
{ 4, 5 },
|
|
||||||
{ 4, 3 },
|
|
||||||
{ 3, 3 }
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& version : versionCandidates)
|
|
||||||
{
|
|
||||||
const int attribs[] =
|
|
||||||
{
|
|
||||||
WGL_CONTEXT_MAJOR_VERSION_ARB, version[0],
|
|
||||||
WGL_CONTEXT_MINOR_VERSION_ARB, version[1],
|
|
||||||
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
HGLRC modernContext = wglCreateContextAttribsARB(hDC, 0, attribs);
|
|
||||||
if (modernContext != NULL)
|
|
||||||
return modernContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
|
||||||
{
|
|
||||||
MSG msg; // Windows message structure
|
|
||||||
WNDCLASS wc; // Windows class structure
|
|
||||||
HWND hWnd; // Storeage for window handle
|
|
||||||
TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
|
|
||||||
TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
|
|
||||||
|
|
||||||
// Initialize global strings
|
|
||||||
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
|
|
||||||
LoadString(hInstance, IDC_OPENGLOUTPUT, szWindowClass, MAX_LOADSTRING);
|
|
||||||
|
|
||||||
// Register Window style
|
|
||||||
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
|
||||||
wc.lpfnWndProc = (WNDPROC) WndProc;
|
|
||||||
wc.cbClsExtra = 0;
|
|
||||||
wc.cbWndExtra = 0;
|
|
||||||
wc.hInstance = hInstance;
|
|
||||||
wc.hIcon = NULL;
|
|
||||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
||||||
|
|
||||||
// No need for background brush for OpenGL window
|
|
||||||
wc.hbrBackground = NULL;
|
|
||||||
wc.lpszMenuName = NULL;
|
|
||||||
wc.lpszClassName = szWindowClass;
|
|
||||||
|
|
||||||
// Register the window class
|
|
||||||
if (RegisterClass(&wc) == 0)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
// Create the main application window
|
|
||||||
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
|
|
||||||
CW_USEDEFAULT, 0, 250, 250, NULL, NULL, hInstance, NULL);
|
|
||||||
|
|
||||||
// If window was not created, quit
|
|
||||||
if (hWnd == NULL)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
// Display the window
|
|
||||||
ShowWindow(hWnd,SW_SHOW);
|
|
||||||
UpdateWindow(hWnd);
|
|
||||||
|
|
||||||
// Process application messages until the application closes
|
|
||||||
while (GetMessage(&msg, NULL, 0, 0))
|
|
||||||
{
|
|
||||||
TranslateMessage(&msg);
|
|
||||||
DispatchMessage(&msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int)msg.wParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Window procedure, handles all messages for this program
|
|
||||||
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
||||||
{
|
|
||||||
static HGLRC hRC = NULL; // Permenant Rendering context
|
|
||||||
static HDC hDC = NULL; // Private GDI Device context
|
|
||||||
static OpenGLComposite* pOpenGLComposite = NULL;
|
|
||||||
static bool sInteractiveResize = false;
|
|
||||||
static StatusStripControls sStatusStrip;
|
|
||||||
|
|
||||||
switch (message)
|
|
||||||
{
|
|
||||||
// Window creation, setup for OpenGL context
|
|
||||||
case WM_CREATE:
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Store the device context
|
|
||||||
hDC = GetDC(hWnd);
|
|
||||||
|
|
||||||
// Select the pixel format
|
|
||||||
SetDCPixelFormat(hDC);
|
|
||||||
|
|
||||||
// Create the rendering context and make it current
|
|
||||||
hRC = wglCreateContext(hDC);
|
|
||||||
wglMakeCurrent(hDC, hRC);
|
|
||||||
|
|
||||||
HGLRC modernRC = CreateModernOpenGLContext(hDC);
|
|
||||||
if (modernRC == NULL)
|
|
||||||
{
|
|
||||||
MessageBox(NULL, _T("This application requires an OpenGL 3.3+ core profile context."), _T("OpenGL initialization Error."), MB_OK);
|
|
||||||
PostMessage(hWnd, WM_CLOSE, 0, 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
wglMakeCurrent(NULL, NULL);
|
|
||||||
wglDeleteContext(hRC);
|
|
||||||
hRC = modernRC;
|
|
||||||
wglMakeCurrent(hDC, hRC);
|
|
||||||
|
|
||||||
// Initialize COM
|
|
||||||
HRESULT result;
|
|
||||||
result = CoInitialize(NULL);
|
|
||||||
if (FAILED(result))
|
|
||||||
{
|
|
||||||
MessageBox(NULL, _T("Initialization of COM failed."), _T("Application initialization Error."),MB_OK);
|
|
||||||
PostMessage(hWnd, WM_CLOSE, 0, 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup OpenGL and DeckLink capture and playout object
|
|
||||||
pOpenGLComposite = new OpenGLComposite(hWnd, hDC, hRC);
|
|
||||||
|
|
||||||
if (pOpenGLComposite->InitDeckLink())
|
|
||||||
{
|
|
||||||
wglMakeCurrent( NULL, NULL );
|
|
||||||
if (pOpenGLComposite->Start())
|
|
||||||
{
|
|
||||||
PostMessage(hWnd, kCreateStatusStripMessage, 0, 0);
|
|
||||||
break; // success
|
|
||||||
}
|
|
||||||
MessageBoxA(NULL, "The OpenGL/DeckLink runtime initialized, but playout failed to start. See the previous DeckLink start message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, "The OpenGL/DeckLink runtime failed to initialize. See the previous initialization message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Failed to initialize - cleanup
|
|
||||||
delete pOpenGLComposite;
|
|
||||||
pOpenGLComposite = NULL;
|
|
||||||
PostMessage(hWnd, WM_CLOSE, 0, 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
ShowUnhandledExceptionMessage("Startup failed while creating the OpenGL/DeckLink runtime.");
|
|
||||||
PostMessage(hWnd, WM_CLOSE, 0, 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case kCreateStatusStripMessage:
|
|
||||||
if (pOpenGLComposite)
|
|
||||||
{
|
|
||||||
if (!StatusStripCreated(sStatusStrip))
|
|
||||||
CreateStatusStrip(hWnd, sStatusStrip);
|
|
||||||
|
|
||||||
UpdateStatusStrip(sStatusStrip, *pOpenGLComposite);
|
|
||||||
LayoutStatusStrip(hWnd, sStatusStrip);
|
|
||||||
RECT clientRect = {};
|
|
||||||
if (GetClientRect(hWnd, &clientRect))
|
|
||||||
{
|
|
||||||
pOpenGLComposite->resizeGL(
|
|
||||||
static_cast<WORD>(clientRect.right - clientRect.left),
|
|
||||||
static_cast<WORD>(clientRect.bottom - clientRect.top));
|
|
||||||
}
|
|
||||||
InvalidateRect(hWnd, NULL, FALSE);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WM_DESTROY:
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (pOpenGLComposite)
|
|
||||||
{
|
|
||||||
pOpenGLComposite->Stop();
|
|
||||||
delete pOpenGLComposite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
ShowUnhandledExceptionMessage("Shutdown failed while tearing down the OpenGL/DeckLink runtime.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deselect the current rendering context and delete it
|
|
||||||
wglMakeCurrent(NULL, NULL);
|
|
||||||
wglDeleteContext(hRC);
|
|
||||||
|
|
||||||
// Tell the application to terminate after the window is gone
|
|
||||||
PostQuitMessage(0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WM_ENTERSIZEMOVE:
|
|
||||||
sInteractiveResize = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WM_EXITSIZEMOVE:
|
|
||||||
sInteractiveResize = false;
|
|
||||||
if (pOpenGLComposite)
|
|
||||||
{
|
|
||||||
RECT clientRect = {};
|
|
||||||
if (GetClientRect(hWnd, &clientRect))
|
|
||||||
{
|
|
||||||
pOpenGLComposite->resizeGL(
|
|
||||||
static_cast<WORD>(clientRect.right - clientRect.left),
|
|
||||||
static_cast<WORD>(clientRect.bottom - clientRect.top));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InvalidateRect(hWnd, NULL, FALSE);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WM_SIZE:
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (StatusStripCreated(sStatusStrip))
|
|
||||||
LayoutStatusStrip(hWnd, sStatusStrip);
|
|
||||||
if (pOpenGLComposite)
|
|
||||||
pOpenGLComposite->resizeGL(LOWORD(lParam), HIWORD(lParam));
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
ShowUnhandledExceptionMessage("Resize failed inside the OpenGL runtime.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WM_ERASEBKGND:
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
case WM_PAINT:
|
|
||||||
try
|
|
||||||
{
|
|
||||||
PAINTSTRUCT paint = {};
|
|
||||||
BeginPaint(hWnd, &paint);
|
|
||||||
EndPaint(hWnd, &paint);
|
|
||||||
|
|
||||||
if (!sInteractiveResize && pOpenGLComposite)
|
|
||||||
{
|
|
||||||
pOpenGLComposite->paintGL(true);
|
|
||||||
RaiseStatusControls(sStatusStrip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
ShowUnhandledExceptionMessage("Paint failed inside the OpenGL runtime.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WM_KEYDOWN:
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (pOpenGLComposite && (wParam == 'R' || wParam == 'r'))
|
|
||||||
{
|
|
||||||
pOpenGLComposite->ReloadShader();
|
|
||||||
InvalidateRect(hWnd, NULL, FALSE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
ShowUnhandledExceptionMessage("Shader reload failed inside the OpenGL runtime.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WM_COMMAND:
|
|
||||||
switch (LOWORD(wParam))
|
|
||||||
{
|
|
||||||
case kOpenControlButtonId:
|
|
||||||
if (pOpenGLComposite)
|
|
||||||
{
|
|
||||||
std::string url = pOpenGLComposite->GetControlUrl();
|
|
||||||
OpenUrl(url.c_str());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case kOpenDocsButtonId:
|
|
||||||
if (pOpenGLComposite)
|
|
||||||
{
|
|
||||||
std::string url = pOpenGLComposite->GetDocsUrl();
|
|
||||||
OpenUrl(url.c_str());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return (DefWindowProc(hWnd, message, wParam, lParam));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (0L);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "resource.h"
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
@@ -1,95 +0,0 @@
|
|||||||
// Microsoft Visual C++ generated resource script.
|
|
||||||
//
|
|
||||||
#include "resource.h"
|
|
||||||
|
|
||||||
#define APSTUDIO_READONLY_SYMBOLS
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Generated from the TEXTINCLUDE 2 resource.
|
|
||||||
//
|
|
||||||
#ifndef APSTUDIO_INVOKED
|
|
||||||
#include "targetver.h"
|
|
||||||
#endif
|
|
||||||
#define APSTUDIO_HIDDEN_SYMBOLS
|
|
||||||
#include "windows.h"
|
|
||||||
#undef APSTUDIO_HIDDEN_SYMBOLS
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
#undef APSTUDIO_READONLY_SYMBOLS
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
// English (U.S.) resources
|
|
||||||
|
|
||||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
|
||||||
#ifdef _WIN32
|
|
||||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|
||||||
#pragma code_page(1252)
|
|
||||||
#endif //_WIN32
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Icon
|
|
||||||
//
|
|
||||||
|
|
||||||
// Icon with lowest ID value placed first to ensure application icon
|
|
||||||
// remains consistent on all systems.
|
|
||||||
IDI_OPENGLOUTPUT ICON "LoopThroughWithOpenGLCompositing.ico"
|
|
||||||
IDI_SMALL ICON "small.ico"
|
|
||||||
|
|
||||||
#ifdef APSTUDIO_INVOKED
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// TEXTINCLUDE
|
|
||||||
//
|
|
||||||
|
|
||||||
1 TEXTINCLUDE
|
|
||||||
BEGIN
|
|
||||||
"resource.h\0"
|
|
||||||
END
|
|
||||||
|
|
||||||
2 TEXTINCLUDE
|
|
||||||
BEGIN
|
|
||||||
"#ifndef APSTUDIO_INVOKED\r\n"
|
|
||||||
"#include ""targetver.h""\r\n"
|
|
||||||
"#endif\r\n"
|
|
||||||
"#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
|
|
||||||
"#include ""windows.h""\r\n"
|
|
||||||
"#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
|
|
||||||
"\0"
|
|
||||||
END
|
|
||||||
|
|
||||||
3 TEXTINCLUDE
|
|
||||||
BEGIN
|
|
||||||
"\r\n"
|
|
||||||
"\0"
|
|
||||||
END
|
|
||||||
|
|
||||||
#endif // APSTUDIO_INVOKED
|
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// String Table
|
|
||||||
//
|
|
||||||
|
|
||||||
STRINGTABLE
|
|
||||||
BEGIN
|
|
||||||
IDS_APP_TITLE "Video Shader Toys"
|
|
||||||
IDC_OPENGLOUTPUT "OPENGLOUTPUT"
|
|
||||||
END
|
|
||||||
|
|
||||||
#endif // English (U.S.) resources
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef APSTUDIO_INVOKED
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Generated from the TEXTINCLUDE 3 resource.
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
#endif // not APSTUDIO_INVOKED
|
|
||||||
|
|
||||||
@@ -1,648 +0,0 @@
|
|||||||
#include "stdafx.h"
|
|
||||||
#include "ControlServer.h"
|
|
||||||
|
|
||||||
#include "RuntimeJson.h"
|
|
||||||
|
|
||||||
#include <Wincrypt.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#pragma comment(lib, "Ws2_32.lib")
|
|
||||||
#pragma comment(lib, "Crypt32.lib")
|
|
||||||
#pragma comment(lib, "Advapi32.lib")
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
constexpr DWORD kStateBroadcastIntervalMs = 250;
|
|
||||||
constexpr DWORD kStateBroadcastThrottleMs = 50;
|
|
||||||
|
|
||||||
bool InitializeWinsock(std::string& error)
|
|
||||||
{
|
|
||||||
WSADATA wsaData = {};
|
|
||||||
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
|
||||||
if (result != 0)
|
|
||||||
{
|
|
||||||
error = "WSAStartup failed.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ToLower(std::string text)
|
|
||||||
{
|
|
||||||
std::transform(text.begin(), text.end(), text.begin(),
|
|
||||||
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsSafeUiPath(const std::filesystem::path& relativePath)
|
|
||||||
{
|
|
||||||
for (const std::filesystem::path& part : relativePath)
|
|
||||||
{
|
|
||||||
if (part == "..")
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !relativePath.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GuessContentType(const std::filesystem::path& assetPath)
|
|
||||||
{
|
|
||||||
const std::string extension = ToLower(assetPath.extension().string());
|
|
||||||
if (extension == ".js" || extension == ".mjs")
|
|
||||||
return "text/javascript";
|
|
||||||
if (extension == ".css")
|
|
||||||
return "text/css";
|
|
||||||
if (extension == ".json")
|
|
||||||
return "application/json";
|
|
||||||
if (extension == ".yaml" || extension == ".yml")
|
|
||||||
return "application/yaml";
|
|
||||||
if (extension == ".svg")
|
|
||||||
return "image/svg+xml";
|
|
||||||
if (extension == ".png")
|
|
||||||
return "image/png";
|
|
||||||
if (extension == ".jpg" || extension == ".jpeg")
|
|
||||||
return "image/jpeg";
|
|
||||||
if (extension == ".ico")
|
|
||||||
return "image/x-icon";
|
|
||||||
if (extension == ".map")
|
|
||||||
return "application/json";
|
|
||||||
if (extension == ".md")
|
|
||||||
return "text/markdown";
|
|
||||||
return "text/html";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlServer::ControlServer()
|
|
||||||
: mPort(0), mRunning(false), mBroadcastPending(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlServer::~ControlServer()
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServer::Start(const std::filesystem::path& uiRoot, const std::filesystem::path& docsRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error)
|
|
||||||
{
|
|
||||||
mUiRoot = uiRoot;
|
|
||||||
mDocsRoot = docsRoot;
|
|
||||||
mCallbacks = callbacks;
|
|
||||||
|
|
||||||
if (!InitializeWinsock(error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
mListenSocket.reset(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
|
|
||||||
if (!mListenSocket.valid())
|
|
||||||
{
|
|
||||||
error = "Could not create listening socket.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
u_long nonBlocking = 1;
|
|
||||||
ioctlsocket(mListenSocket.get(), FIONBIO, &nonBlocking);
|
|
||||||
|
|
||||||
sockaddr_in address = {};
|
|
||||||
address.sin_family = AF_INET;
|
|
||||||
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
||||||
|
|
||||||
bool bound = false;
|
|
||||||
for (unsigned short offset = 0; offset < 20; ++offset)
|
|
||||||
{
|
|
||||||
address.sin_port = htons(static_cast<u_short>(preferredPort + offset));
|
|
||||||
if (bind(mListenSocket.get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) == 0)
|
|
||||||
{
|
|
||||||
mPort = preferredPort + offset;
|
|
||||||
bound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bound)
|
|
||||||
{
|
|
||||||
error = "Could not bind the local control server to any port in the preferred range.";
|
|
||||||
mListenSocket.reset();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listen(mListenSocket.get(), SOMAXCONN) != 0)
|
|
||||||
{
|
|
||||||
error = "Could not start listening on the local control server socket.";
|
|
||||||
mListenSocket.reset();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mRunning = true;
|
|
||||||
mThread = std::thread(&ControlServer::ServerLoop, this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServer::Stop()
|
|
||||||
{
|
|
||||||
const bool wasActive = mRunning || mListenSocket.valid() || mThread.joinable();
|
|
||||||
mRunning = false;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
for (ClientConnection& client : mClients)
|
|
||||||
client.socket.reset();
|
|
||||||
mClients.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
mListenSocket.reset();
|
|
||||||
|
|
||||||
if (mThread.joinable())
|
|
||||||
mThread.join();
|
|
||||||
|
|
||||||
if (wasActive)
|
|
||||||
WSACleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServer::BroadcastState()
|
|
||||||
{
|
|
||||||
mBroadcastPending = false;
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
BroadcastStateLocked();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServer::RequestBroadcastState()
|
|
||||||
{
|
|
||||||
mBroadcastPending = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServer::ServerLoop()
|
|
||||||
{
|
|
||||||
DWORD lastStateBroadcastMs = GetTickCount();
|
|
||||||
while (mRunning)
|
|
||||||
{
|
|
||||||
TryAcceptClient();
|
|
||||||
|
|
||||||
const DWORD nowMs = GetTickCount();
|
|
||||||
if (mBroadcastPending && nowMs - lastStateBroadcastMs >= kStateBroadcastThrottleMs)
|
|
||||||
{
|
|
||||||
BroadcastState();
|
|
||||||
lastStateBroadcastMs = nowMs;
|
|
||||||
}
|
|
||||||
else if (nowMs - lastStateBroadcastMs >= kStateBroadcastIntervalMs)
|
|
||||||
{
|
|
||||||
BroadcastState();
|
|
||||||
lastStateBroadcastMs = nowMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
Sleep(25);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServer::HandleHttpClient(UniqueSocket clientSocket)
|
|
||||||
{
|
|
||||||
std::string request;
|
|
||||||
char buffer[8192];
|
|
||||||
int received = recv(clientSocket.get(), buffer, sizeof(buffer), 0);
|
|
||||||
if (received <= 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
request.assign(buffer, buffer + received);
|
|
||||||
return HandleHttpRequest(std::move(clientSocket), request);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServer::TryAcceptClient()
|
|
||||||
{
|
|
||||||
sockaddr_in clientAddress = {};
|
|
||||||
int addressSize = sizeof(clientAddress);
|
|
||||||
UniqueSocket clientSocket(accept(mListenSocket.get(), reinterpret_cast<sockaddr*>(&clientAddress), &addressSize));
|
|
||||||
if (!clientSocket.valid())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return HandleHttpClient(std::move(clientSocket));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServer::SendHttpResponse(SOCKET clientSocket, const std::string& status, const std::string& contentType, const std::string& body)
|
|
||||||
{
|
|
||||||
std::ostringstream response;
|
|
||||||
response << "HTTP/1.1 " << status << "\r\n";
|
|
||||||
response << "Content-Type: " << contentType << "\r\n";
|
|
||||||
response << "Content-Length: " << body.size() << "\r\n";
|
|
||||||
response << "Connection: close\r\n\r\n";
|
|
||||||
response << body;
|
|
||||||
|
|
||||||
const std::string payload = response.str();
|
|
||||||
return send(clientSocket, payload.c_str(), static_cast<int>(payload.size()), 0) == static_cast<int>(payload.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServer::SendHttpResponse(SOCKET clientSocket, const HttpResponse& response)
|
|
||||||
{
|
|
||||||
return SendHttpResponse(clientSocket, response.status, response.contentType, response.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServer::HandleHttpRequest(UniqueSocket clientSocket, const std::string& request)
|
|
||||||
{
|
|
||||||
HttpRequest httpRequest;
|
|
||||||
if (!ParseHttpRequest(request, httpRequest))
|
|
||||||
{
|
|
||||||
SendHttpResponse(clientSocket.get(), "400 Bad Request", "text/plain", "Bad Request");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ToLower(GetHeaderValue(httpRequest, "Upgrade")) == "websocket")
|
|
||||||
return HandleWebSocketUpgrade(std::move(clientSocket), httpRequest);
|
|
||||||
|
|
||||||
const HttpResponse response = RouteHttpRequest(httpRequest);
|
|
||||||
SendHttpResponse(clientSocket.get(), response);
|
|
||||||
if (response.broadcastState)
|
|
||||||
BroadcastState();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlServer::HttpResponse ControlServer::RouteHttpRequest(const HttpRequest& request)
|
|
||||||
{
|
|
||||||
if (request.method == "GET")
|
|
||||||
return ServeGetRequest(request);
|
|
||||||
|
|
||||||
if (request.method == "POST")
|
|
||||||
return HandleApiPost(request);
|
|
||||||
|
|
||||||
return { "404 Not Found", "text/plain", "Not Found" };
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlServer::HttpResponse ControlServer::ServeGetRequest(const HttpRequest& request) const
|
|
||||||
{
|
|
||||||
if (request.path == "/" || request.path == "/index.html")
|
|
||||||
return ServeUiAsset("index.html");
|
|
||||||
|
|
||||||
if (request.path == "/api/state")
|
|
||||||
return { "200 OK", "application/json", mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}" };
|
|
||||||
|
|
||||||
if (request.path == "/openapi.yaml" || request.path == "/docs/openapi.yaml")
|
|
||||||
return ServeOpenApiSpec();
|
|
||||||
|
|
||||||
if (request.path == "/docs" || request.path == "/docs/")
|
|
||||||
return ServeSwaggerDocs();
|
|
||||||
|
|
||||||
const std::string docsPrefix = "/docs/";
|
|
||||||
if (request.path.rfind(docsPrefix, 0) == 0)
|
|
||||||
return ServeDocsAsset(request.path.substr(docsPrefix.size()));
|
|
||||||
|
|
||||||
if (request.path.size() > 1)
|
|
||||||
{
|
|
||||||
const HttpResponse assetResponse = ServeUiAsset(request.path.substr(1));
|
|
||||||
if (!assetResponse.body.empty())
|
|
||||||
return assetResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { "404 Not Found", "text/plain", "Not Found" };
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlServer::HttpResponse ControlServer::ServeUiAsset(const std::string& relativePath) const
|
|
||||||
{
|
|
||||||
std::string contentType;
|
|
||||||
const std::string body = LoadUiAsset(relativePath, contentType);
|
|
||||||
return body.empty()
|
|
||||||
? HttpResponse{ "404 Not Found", "text/plain", "Not Found" }
|
|
||||||
: HttpResponse{ "200 OK", contentType, body };
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlServer::HttpResponse ControlServer::ServeDocsAsset(const std::string& relativePath) const
|
|
||||||
{
|
|
||||||
const std::filesystem::path sanitizedPath = std::filesystem::path(relativePath).lexically_normal();
|
|
||||||
if (!IsSafeUiPath(sanitizedPath))
|
|
||||||
return { "404 Not Found", "text/plain", "Not Found" };
|
|
||||||
|
|
||||||
const std::filesystem::path docsPath = mDocsRoot / sanitizedPath;
|
|
||||||
const std::string body = LoadTextFile(docsPath);
|
|
||||||
return body.empty()
|
|
||||||
? HttpResponse{ "404 Not Found", "text/plain", "Not Found" }
|
|
||||||
: HttpResponse{ "200 OK", GuessContentType(docsPath), body };
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlServer::HttpResponse ControlServer::ServeOpenApiSpec() const
|
|
||||||
{
|
|
||||||
const std::filesystem::path specPath = mDocsRoot / "openapi.yaml";
|
|
||||||
const std::string body = LoadTextFile(specPath);
|
|
||||||
return body.empty()
|
|
||||||
? HttpResponse{ "404 Not Found", "text/plain", "OpenAPI spec not found" }
|
|
||||||
: HttpResponse{ "200 OK", GuessContentType(specPath), body };
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlServer::HttpResponse ControlServer::ServeSwaggerDocs() const
|
|
||||||
{
|
|
||||||
std::ostringstream html;
|
|
||||||
html << "<!doctype html>\n"
|
|
||||||
<< "<html lang=\"en\">\n"
|
|
||||||
<< "<head>\n"
|
|
||||||
<< " <meta charset=\"utf-8\">\n"
|
|
||||||
<< " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
|
|
||||||
<< " <title>Video Shader Toys API Docs</title>\n"
|
|
||||||
<< " <link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\">\n"
|
|
||||||
<< "</head>\n"
|
|
||||||
<< "<body>\n"
|
|
||||||
<< " <div id=\"swagger-ui\"></div>\n"
|
|
||||||
<< " <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n"
|
|
||||||
<< " <script>SwaggerUIBundle({url:'/docs/openapi.yaml',dom_id:'#swagger-ui'});</script>\n"
|
|
||||||
<< "</body>\n"
|
|
||||||
<< "</html>\n";
|
|
||||||
return { "200 OK", "text/html", html.str() };
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlServer::HttpResponse ControlServer::HandleApiPost(const HttpRequest& request)
|
|
||||||
{
|
|
||||||
JsonValue root;
|
|
||||||
std::string parseError;
|
|
||||||
if (!ParseJson(request.body, root, parseError))
|
|
||||||
return { "400 Bad Request", "application/json", BuildJsonResponse(false, parseError) };
|
|
||||||
|
|
||||||
std::string actionError;
|
|
||||||
const bool success = InvokePostRoute(request.path, root, actionError);
|
|
||||||
return {
|
|
||||||
success ? "200 OK" : "400 Bad Request",
|
|
||||||
"application/json",
|
|
||||||
BuildJsonResponse(success, actionError),
|
|
||||||
success
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServer::InvokePostRoute(const std::string& path, const JsonValue& root, std::string& actionError)
|
|
||||||
{
|
|
||||||
using PostHandler = std::function<bool(const JsonValue&, std::string&)>;
|
|
||||||
const std::map<std::string, PostHandler> postRoutes =
|
|
||||||
{
|
|
||||||
{ "/api/layers/add", [this](const JsonValue& json, std::string& error)
|
|
||||||
{
|
|
||||||
const JsonValue* shaderId = json.find("shaderId");
|
|
||||||
return shaderId && mCallbacks.addLayer && mCallbacks.addLayer(shaderId->asString(), error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "/api/layers/remove", [this](const JsonValue& json, std::string& error)
|
|
||||||
{
|
|
||||||
const JsonValue* layerId = json.find("layerId");
|
|
||||||
return layerId && mCallbacks.removeLayer && mCallbacks.removeLayer(layerId->asString(), error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "/api/layers/move", [this](const JsonValue& json, std::string& error)
|
|
||||||
{
|
|
||||||
const JsonValue* layerId = json.find("layerId");
|
|
||||||
const JsonValue* direction = json.find("direction");
|
|
||||||
return layerId && direction && mCallbacks.moveLayer &&
|
|
||||||
mCallbacks.moveLayer(layerId->asString(), static_cast<int>(direction->asNumber()), error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "/api/layers/reorder", [this](const JsonValue& json, std::string& error)
|
|
||||||
{
|
|
||||||
const JsonValue* layerId = json.find("layerId");
|
|
||||||
const JsonValue* targetIndex = json.find("targetIndex");
|
|
||||||
return layerId && targetIndex && mCallbacks.moveLayerToIndex &&
|
|
||||||
mCallbacks.moveLayerToIndex(layerId->asString(), static_cast<std::size_t>(targetIndex->asNumber()), error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "/api/layers/set-bypass", [this](const JsonValue& json, std::string& error)
|
|
||||||
{
|
|
||||||
const JsonValue* layerId = json.find("layerId");
|
|
||||||
const JsonValue* bypass = json.find("bypass");
|
|
||||||
return layerId && bypass && mCallbacks.setLayerBypass &&
|
|
||||||
mCallbacks.setLayerBypass(layerId->asString(), bypass->asBoolean(), error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "/api/layers/set-shader", [this](const JsonValue& json, std::string& error)
|
|
||||||
{
|
|
||||||
const JsonValue* layerId = json.find("layerId");
|
|
||||||
const JsonValue* shaderId = json.find("shaderId");
|
|
||||||
return layerId && shaderId && mCallbacks.setLayerShader &&
|
|
||||||
mCallbacks.setLayerShader(layerId->asString(), shaderId->asString(), error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "/api/layers/update-parameter", [this](const JsonValue& json, std::string& error)
|
|
||||||
{
|
|
||||||
const JsonValue* layerId = json.find("layerId");
|
|
||||||
const JsonValue* parameterId = json.find("parameterId");
|
|
||||||
const JsonValue* value = json.find("value");
|
|
||||||
return layerId && parameterId && value && mCallbacks.updateLayerParameter &&
|
|
||||||
mCallbacks.updateLayerParameter(layerId->asString(), parameterId->asString(), SerializeJson(*value, false), error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "/api/layers/reset-parameters", [this](const JsonValue& json, std::string& error)
|
|
||||||
{
|
|
||||||
const JsonValue* layerId = json.find("layerId");
|
|
||||||
return layerId && mCallbacks.resetLayerParameters &&
|
|
||||||
mCallbacks.resetLayerParameters(layerId->asString(), error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "/api/stack-presets/save", [this](const JsonValue& json, std::string& error)
|
|
||||||
{
|
|
||||||
const JsonValue* presetName = json.find("presetName");
|
|
||||||
return presetName && mCallbacks.saveStackPreset &&
|
|
||||||
mCallbacks.saveStackPreset(presetName->asString(), error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "/api/stack-presets/load", [this](const JsonValue& json, std::string& error)
|
|
||||||
{
|
|
||||||
const JsonValue* presetName = json.find("presetName");
|
|
||||||
return presetName && mCallbacks.loadStackPreset &&
|
|
||||||
mCallbacks.loadStackPreset(presetName->asString(), error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "/api/reload", [this](const JsonValue&, std::string& error)
|
|
||||||
{
|
|
||||||
return mCallbacks.reloadShader && mCallbacks.reloadShader(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "/api/screenshot", [this](const JsonValue&, std::string& error)
|
|
||||||
{
|
|
||||||
return mCallbacks.requestScreenshot && mCallbacks.requestScreenshot(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto route = postRoutes.find(path);
|
|
||||||
return route != postRoutes.end() && route->second(root, actionError);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServer::HandleWebSocketUpgrade(UniqueSocket clientSocket, const HttpRequest& request)
|
|
||||||
{
|
|
||||||
const std::string clientKey = GetHeaderValue(request, "Sec-WebSocket-Key");
|
|
||||||
if (clientKey.empty())
|
|
||||||
{
|
|
||||||
SendHttpResponse(clientSocket.get(), "400 Bad Request", "text/plain", "Missing Sec-WebSocket-Key");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostringstream response;
|
|
||||||
response << "HTTP/1.1 101 Switching Protocols\r\n";
|
|
||||||
response << "Upgrade: websocket\r\n";
|
|
||||||
response << "Connection: Upgrade\r\n";
|
|
||||||
response << "Sec-WebSocket-Accept: " << ComputeWebSocketAcceptKey(clientKey) << "\r\n\r\n";
|
|
||||||
|
|
||||||
const std::string payload = response.str();
|
|
||||||
send(clientSocket.get(), payload.c_str(), static_cast<int>(payload.size()), 0);
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
ClientConnection client;
|
|
||||||
client.socket.reset(clientSocket.release());
|
|
||||||
client.websocket = true;
|
|
||||||
mClients.push_back(std::move(client));
|
|
||||||
mBroadcastPending = false;
|
|
||||||
BroadcastStateLocked();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServer::SendWebSocketText(SOCKET clientSocket, const std::string& payload)
|
|
||||||
{
|
|
||||||
std::string frame;
|
|
||||||
frame.push_back(static_cast<char>(0x81));
|
|
||||||
if (payload.size() <= 125)
|
|
||||||
{
|
|
||||||
frame.push_back(static_cast<char>(payload.size()));
|
|
||||||
}
|
|
||||||
else if (payload.size() <= 65535)
|
|
||||||
{
|
|
||||||
frame.push_back(126);
|
|
||||||
frame.push_back(static_cast<char>((payload.size() >> 8) & 0xFF));
|
|
||||||
frame.push_back(static_cast<char>(payload.size() & 0xFF));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
frame.push_back(127);
|
|
||||||
for (int shift = 56; shift >= 0; shift -= 8)
|
|
||||||
frame.push_back(static_cast<char>((payload.size() >> shift) & 0xFF));
|
|
||||||
}
|
|
||||||
frame.append(payload);
|
|
||||||
|
|
||||||
return send(clientSocket, frame.data(), static_cast<int>(frame.size()), 0) == static_cast<int>(frame.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServer::BroadcastStateLocked()
|
|
||||||
{
|
|
||||||
if (mClients.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const std::string stateMessage = mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}";
|
|
||||||
for (auto it = mClients.begin(); it != mClients.end();)
|
|
||||||
{
|
|
||||||
if (!SendWebSocketText(it->socket.get(), stateMessage))
|
|
||||||
{
|
|
||||||
it = mClients.erase(it);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ControlServer::LoadUiAsset(const std::string& relativePath, std::string& contentType) const
|
|
||||||
{
|
|
||||||
const std::filesystem::path sanitizedPath = std::filesystem::path(relativePath).lexically_normal();
|
|
||||||
if (!IsSafeUiPath(sanitizedPath))
|
|
||||||
return std::string();
|
|
||||||
|
|
||||||
const std::filesystem::path assetPath = mUiRoot / sanitizedPath;
|
|
||||||
contentType = GuessContentType(assetPath);
|
|
||||||
return LoadTextFile(assetPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ControlServer::LoadTextFile(const std::filesystem::path& path) const
|
|
||||||
{
|
|
||||||
std::ifstream input(path, std::ios::binary);
|
|
||||||
if (!input)
|
|
||||||
return std::string();
|
|
||||||
|
|
||||||
std::ostringstream buffer;
|
|
||||||
buffer << input.rdbuf();
|
|
||||||
return buffer.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ControlServer::BuildJsonResponse(bool success, const std::string& error) const
|
|
||||||
{
|
|
||||||
JsonValue response = JsonValue::MakeObject();
|
|
||||||
response.set("ok", JsonValue(success));
|
|
||||||
if (!error.empty())
|
|
||||||
response.set("error", JsonValue(error));
|
|
||||||
return SerializeJson(response, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ControlServer::Base64Encode(const unsigned char* data, DWORD dataLength)
|
|
||||||
{
|
|
||||||
DWORD outputLength = 0;
|
|
||||||
CryptBinaryToStringA(data, dataLength, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &outputLength);
|
|
||||||
std::string encoded(outputLength, '\0');
|
|
||||||
CryptBinaryToStringA(data, dataLength, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, &encoded[0], &outputLength);
|
|
||||||
if (!encoded.empty() && encoded.back() == '\0')
|
|
||||||
encoded.pop_back();
|
|
||||||
return encoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ControlServer::ComputeWebSocketAcceptKey(const std::string& clientKey)
|
|
||||||
{
|
|
||||||
const std::string combined = clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
||||||
HCRYPTPROV provider = 0;
|
|
||||||
HCRYPTHASH hash = 0;
|
|
||||||
BYTE digest[20] = {};
|
|
||||||
DWORD digestLength = sizeof(digest);
|
|
||||||
|
|
||||||
CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
|
|
||||||
CryptCreateHash(provider, CALG_SHA1, 0, 0, &hash);
|
|
||||||
CryptHashData(hash, reinterpret_cast<const BYTE*>(combined.data()), static_cast<DWORD>(combined.size()), 0);
|
|
||||||
CryptGetHashParam(hash, HP_HASHVAL, digest, &digestLength, 0);
|
|
||||||
|
|
||||||
if (hash)
|
|
||||||
CryptDestroyHash(hash);
|
|
||||||
if (provider)
|
|
||||||
CryptReleaseContext(provider, 0);
|
|
||||||
|
|
||||||
return Base64Encode(digest, digestLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ControlServer::GetHeaderValue(const HttpRequest& request, const std::string& headerName)
|
|
||||||
{
|
|
||||||
const auto header = request.headers.find(ToLower(headerName));
|
|
||||||
return header == request.headers.end() ? std::string() : header->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServer::ParseHttpRequest(const std::string& rawRequest, HttpRequest& request)
|
|
||||||
{
|
|
||||||
const std::size_t requestLineEnd = rawRequest.find("\r\n");
|
|
||||||
if (requestLineEnd == std::string::npos)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const std::string requestLine = rawRequest.substr(0, requestLineEnd);
|
|
||||||
const std::size_t methodEnd = requestLine.find(' ');
|
|
||||||
if (methodEnd == std::string::npos)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const std::size_t pathEnd = requestLine.find(' ', methodEnd + 1);
|
|
||||||
if (pathEnd == std::string::npos)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
request.method = requestLine.substr(0, methodEnd);
|
|
||||||
request.path = requestLine.substr(methodEnd + 1, pathEnd - methodEnd - 1);
|
|
||||||
request.headers.clear();
|
|
||||||
|
|
||||||
const std::size_t headersStart = requestLineEnd + 2;
|
|
||||||
const std::size_t bodySeparator = rawRequest.find("\r\n\r\n", headersStart);
|
|
||||||
const std::size_t headersEnd = bodySeparator == std::string::npos ? rawRequest.size() : bodySeparator;
|
|
||||||
|
|
||||||
for (std::size_t lineStart = headersStart; lineStart < headersEnd;)
|
|
||||||
{
|
|
||||||
const std::size_t lineEnd = rawRequest.find("\r\n", lineStart);
|
|
||||||
const std::size_t currentLineEnd = lineEnd == std::string::npos ? headersEnd : std::min(lineEnd, headersEnd);
|
|
||||||
const std::string line = rawRequest.substr(lineStart, currentLineEnd - lineStart);
|
|
||||||
const std::size_t separator = line.find(':');
|
|
||||||
if (separator != std::string::npos)
|
|
||||||
{
|
|
||||||
const std::string key = ToLower(line.substr(0, separator));
|
|
||||||
std::string value = line.substr(separator + 1);
|
|
||||||
const std::size_t first = value.find_first_not_of(" \t");
|
|
||||||
const std::size_t last = value.find_last_not_of(" \t");
|
|
||||||
request.headers[key] = first == std::string::npos ? std::string() : value.substr(first, last - first + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lineEnd == std::string::npos || lineEnd >= headersEnd)
|
|
||||||
break;
|
|
||||||
lineStart = lineEnd + 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.body = bodySeparator == std::string::npos ? std::string() : rawRequest.substr(bodySeparator + 4);
|
|
||||||
return !request.method.empty() && !request.path.empty();
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "NativeSockets.h"
|
|
||||||
|
|
||||||
#include <winsock2.h>
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class JsonValue;
|
|
||||||
|
|
||||||
class ControlServer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct Callbacks
|
|
||||||
{
|
|
||||||
std::function<std::string()> getStateJson;
|
|
||||||
std::function<bool(const std::string&, std::string&)> addLayer;
|
|
||||||
std::function<bool(const std::string&, std::string&)> removeLayer;
|
|
||||||
std::function<bool(const std::string&, int, std::string&)> moveLayer;
|
|
||||||
std::function<bool(const std::string&, std::size_t, std::string&)> moveLayerToIndex;
|
|
||||||
std::function<bool(const std::string&, bool, std::string&)> setLayerBypass;
|
|
||||||
std::function<bool(const std::string&, const std::string&, std::string&)> setLayerShader;
|
|
||||||
std::function<bool(const std::string&, const std::string&, const std::string&, std::string&)> updateLayerParameter;
|
|
||||||
std::function<bool(const std::string&, std::string&)> resetLayerParameters;
|
|
||||||
std::function<bool(const std::string&, std::string&)> saveStackPreset;
|
|
||||||
std::function<bool(const std::string&, std::string&)> loadStackPreset;
|
|
||||||
std::function<bool(std::string&)> reloadShader;
|
|
||||||
std::function<bool(std::string&)> requestScreenshot;
|
|
||||||
};
|
|
||||||
|
|
||||||
ControlServer();
|
|
||||||
~ControlServer();
|
|
||||||
|
|
||||||
bool Start(const std::filesystem::path& uiRoot, const std::filesystem::path& docsRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error);
|
|
||||||
void Stop();
|
|
||||||
void BroadcastState();
|
|
||||||
void RequestBroadcastState();
|
|
||||||
|
|
||||||
unsigned short GetPort() const { return mPort; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct ClientConnection
|
|
||||||
{
|
|
||||||
UniqueSocket socket;
|
|
||||||
bool websocket = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HttpRequest
|
|
||||||
{
|
|
||||||
std::string method;
|
|
||||||
std::string path;
|
|
||||||
std::map<std::string, std::string> headers;
|
|
||||||
std::string body;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HttpResponse
|
|
||||||
{
|
|
||||||
std::string status;
|
|
||||||
std::string contentType;
|
|
||||||
std::string body;
|
|
||||||
bool broadcastState = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
void ServerLoop();
|
|
||||||
bool HandleHttpClient(UniqueSocket clientSocket);
|
|
||||||
bool TryAcceptClient();
|
|
||||||
bool SendHttpResponse(SOCKET clientSocket, const HttpResponse& response);
|
|
||||||
bool SendHttpResponse(SOCKET clientSocket, const std::string& status, const std::string& contentType, const std::string& body);
|
|
||||||
bool HandleHttpRequest(UniqueSocket clientSocket, const std::string& request);
|
|
||||||
bool HandleWebSocketUpgrade(UniqueSocket clientSocket, const HttpRequest& request);
|
|
||||||
HttpResponse RouteHttpRequest(const HttpRequest& request);
|
|
||||||
HttpResponse ServeGetRequest(const HttpRequest& request) const;
|
|
||||||
HttpResponse ServeUiAsset(const std::string& relativePath) const;
|
|
||||||
HttpResponse ServeDocsAsset(const std::string& relativePath) const;
|
|
||||||
HttpResponse ServeOpenApiSpec() const;
|
|
||||||
HttpResponse ServeSwaggerDocs() const;
|
|
||||||
HttpResponse HandleApiPost(const HttpRequest& request);
|
|
||||||
bool InvokePostRoute(const std::string& path, const JsonValue& root, std::string& actionError);
|
|
||||||
bool SendWebSocketText(SOCKET clientSocket, const std::string& payload);
|
|
||||||
void BroadcastStateLocked();
|
|
||||||
std::string LoadUiAsset(const std::string& relativePath, std::string& contentType) const;
|
|
||||||
std::string LoadTextFile(const std::filesystem::path& path) const;
|
|
||||||
std::string BuildJsonResponse(bool success, const std::string& error = std::string()) const;
|
|
||||||
static std::string Base64Encode(const unsigned char* data, DWORD dataLength);
|
|
||||||
static std::string ComputeWebSocketAcceptKey(const std::string& clientKey);
|
|
||||||
static std::string GetHeaderValue(const HttpRequest& request, const std::string& headerName);
|
|
||||||
static bool ParseHttpRequest(const std::string& rawRequest, HttpRequest& request);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::filesystem::path mUiRoot;
|
|
||||||
std::filesystem::path mDocsRoot;
|
|
||||||
Callbacks mCallbacks;
|
|
||||||
UniqueSocket mListenSocket;
|
|
||||||
unsigned short mPort;
|
|
||||||
std::thread mThread;
|
|
||||||
std::atomic<bool> mRunning;
|
|
||||||
std::atomic<bool> mBroadcastPending;
|
|
||||||
mutable std::mutex mMutex;
|
|
||||||
std::vector<ClientConnection> mClients;
|
|
||||||
};
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
#include "ControlServices.h"
|
|
||||||
|
|
||||||
#include "ControlServer.h"
|
|
||||||
#include "OscServer.h"
|
|
||||||
#include "RuntimeControlBridge.h"
|
|
||||||
#include "RuntimeEventDispatcher.h"
|
|
||||||
#include "RuntimeStore.h"
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
constexpr auto kCompatibilityPollFallbackInterval = std::chrono::milliseconds(250);
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlServices::ControlServices(RuntimeEventDispatcher& runtimeEventDispatcher) :
|
|
||||||
mControlServer(std::make_unique<ControlServer>()),
|
|
||||||
mOscServer(std::make_unique<OscServer>()),
|
|
||||||
mRuntimeEventDispatcher(runtimeEventDispatcher),
|
|
||||||
mPollRunning(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlServices::~ControlServices()
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServices::Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error)
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
|
|
||||||
if (!StartControlServicesBoundary(composite, runtimeStore, *this, *mControlServer, *mOscServer, error))
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::BeginPolling(RuntimeCoordinator& runtimeCoordinator)
|
|
||||||
{
|
|
||||||
StartPolling(runtimeCoordinator);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::Stop()
|
|
||||||
{
|
|
||||||
StopPolling();
|
|
||||||
|
|
||||||
if (mOscServer)
|
|
||||||
mOscServer->Stop();
|
|
||||||
|
|
||||||
if (mControlServer)
|
|
||||||
mControlServer->Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::BroadcastState()
|
|
||||||
{
|
|
||||||
if (mControlServer)
|
|
||||||
mControlServer->BroadcastState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::RequestBroadcastState()
|
|
||||||
{
|
|
||||||
PublishRuntimeStateBroadcastRequested("control-service-request");
|
|
||||||
|
|
||||||
if (mControlServer)
|
|
||||||
mControlServer->RequestBroadcastState();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServices::QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
|
|
||||||
{
|
|
||||||
(void)error;
|
|
||||||
|
|
||||||
PendingOscUpdate update;
|
|
||||||
update.layerKey = layerKey;
|
|
||||||
update.parameterKey = parameterKey;
|
|
||||||
update.valueJson = valueJson;
|
|
||||||
|
|
||||||
const std::string routeKey = layerKey + "\n" + parameterKey;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
|
||||||
mPendingOscUpdates[routeKey] = std::move(update);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error)
|
|
||||||
{
|
|
||||||
appliedUpdates.clear();
|
|
||||||
|
|
||||||
std::map<std::string, PendingOscUpdate> pending;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
|
||||||
if (mPendingOscUpdates.empty())
|
|
||||||
return true;
|
|
||||||
pending.swap(mPendingOscUpdates);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& entry : pending)
|
|
||||||
{
|
|
||||||
JsonValue targetValue;
|
|
||||||
std::string parseError;
|
|
||||||
if (!ParseJson(entry.second.valueJson, targetValue, parseError))
|
|
||||||
{
|
|
||||||
OutputDebugStringA(("OSC queued value parse failed: " + parseError + "\n").c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppliedOscUpdate appliedUpdate;
|
|
||||||
appliedUpdate.routeKey = entry.first;
|
|
||||||
appliedUpdate.layerKey = entry.second.layerKey;
|
|
||||||
appliedUpdate.parameterKey = entry.second.parameterKey;
|
|
||||||
appliedUpdate.targetValue = targetValue;
|
|
||||||
appliedUpdates.push_back(std::move(appliedUpdate));
|
|
||||||
PublishOscValueReceived(entry.second, entry.first);
|
|
||||||
}
|
|
||||||
|
|
||||||
(void)error;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ControlServices::QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error)
|
|
||||||
{
|
|
||||||
(void)error;
|
|
||||||
|
|
||||||
PendingOscCommit commit;
|
|
||||||
commit.routeKey = routeKey;
|
|
||||||
commit.layerKey = layerKey;
|
|
||||||
commit.parameterKey = parameterKey;
|
|
||||||
commit.value = value;
|
|
||||||
commit.generation = generation;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
|
||||||
mPendingOscCommits[routeKey] = std::move(commit);
|
|
||||||
}
|
|
||||||
WakePolling();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::ClearOscState()
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
|
||||||
mPendingOscUpdates.clear();
|
|
||||||
}
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
|
||||||
mPendingOscCommits.clear();
|
|
||||||
}
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
|
||||||
mCompletedOscCommits.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::ClearOscStateForLayerKey(const std::string& layerKey)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
|
||||||
for (auto it = mPendingOscUpdates.begin(); it != mPendingOscUpdates.end();)
|
|
||||||
{
|
|
||||||
if (it->second.layerKey == layerKey)
|
|
||||||
it = mPendingOscUpdates.erase(it);
|
|
||||||
else
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
|
||||||
for (auto it = mPendingOscCommits.begin(); it != mPendingOscCommits.end();)
|
|
||||||
{
|
|
||||||
if (it->second.layerKey == layerKey)
|
|
||||||
it = mPendingOscCommits.erase(it);
|
|
||||||
else
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
|
||||||
for (auto it = mCompletedOscCommits.begin(); it != mCompletedOscCommits.end();)
|
|
||||||
{
|
|
||||||
if (it->routeKey.rfind(layerKey + "\n", 0) == 0)
|
|
||||||
it = mCompletedOscCommits.erase(it);
|
|
||||||
else
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
|
|
||||||
{
|
|
||||||
completedCommits.clear();
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
|
||||||
if (mCompletedOscCommits.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
completedCommits.swap(mCompletedOscCommits);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::StartPolling(RuntimeCoordinator& runtimeCoordinator)
|
|
||||||
{
|
|
||||||
if (mPollRunning.exchange(true))
|
|
||||||
return;
|
|
||||||
|
|
||||||
mPollThread = std::thread([this, &runtimeCoordinator]() { PollLoop(runtimeCoordinator); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::StopPolling()
|
|
||||||
{
|
|
||||||
if (!mPollRunning.exchange(false))
|
|
||||||
return;
|
|
||||||
|
|
||||||
WakePolling();
|
|
||||||
if (mPollThread.joinable())
|
|
||||||
mPollThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::PollLoop(RuntimeCoordinator& runtimeCoordinator)
|
|
||||||
{
|
|
||||||
while (mPollRunning)
|
|
||||||
{
|
|
||||||
std::map<std::string, PendingOscCommit> pendingCommits;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
|
||||||
pendingCommits.swap(mPendingOscCommits);
|
|
||||||
}
|
|
||||||
for (const auto& entry : pendingCommits)
|
|
||||||
{
|
|
||||||
PublishOscCommitRequested(entry.second);
|
|
||||||
const RuntimeCoordinatorResult result = runtimeCoordinator.CommitOscParameterByControlKey(
|
|
||||||
entry.second.layerKey,
|
|
||||||
entry.second.parameterKey,
|
|
||||||
entry.second.value);
|
|
||||||
if (result.accepted)
|
|
||||||
{
|
|
||||||
CompletedOscCommit completedCommit;
|
|
||||||
completedCommit.routeKey = entry.second.routeKey;
|
|
||||||
completedCommit.generation = entry.second.generation;
|
|
||||||
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
|
||||||
mCompletedOscCommits.push_back(std::move(completedCommit));
|
|
||||||
PublishOscOverlaySettled(entry.second);
|
|
||||||
}
|
|
||||||
else if (!result.errorMessage.empty())
|
|
||||||
{
|
|
||||||
OutputDebugStringA(("OSC commit failed: " + result.errorMessage + "\n").c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool registryChanged = false;
|
|
||||||
const RuntimeCoordinatorResult pollResult = runtimeCoordinator.PollRuntimeStoreChanges(registryChanged);
|
|
||||||
if (pollResult.compileStatusChanged && !pollResult.compileStatusSucceeded && !pollResult.compileStatusMessage.empty())
|
|
||||||
OutputDebugStringA(("Runtime poll failed: " + pollResult.compileStatusMessage + "\n").c_str());
|
|
||||||
|
|
||||||
std::unique_lock<std::mutex> wakeLock(mPollWakeMutex);
|
|
||||||
mPollWakeCondition.wait_for(wakeLock, kCompatibilityPollFallbackInterval, [this]() {
|
|
||||||
return !mPollRunning.load() || mPollWakeRequested;
|
|
||||||
});
|
|
||||||
mPollWakeRequested = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::WakePolling()
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mPollWakeMutex);
|
|
||||||
mPollWakeRequested = true;
|
|
||||||
}
|
|
||||||
mPollWakeCondition.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::PublishRuntimeStateBroadcastRequested(const std::string& reason)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
RuntimeStateBroadcastRequestedEvent event;
|
|
||||||
event.reason = reason;
|
|
||||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices"))
|
|
||||||
OutputDebugStringA("RuntimeStateBroadcastRequested event publish failed.\n");
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
OutputDebugStringA("RuntimeStateBroadcastRequested event publish threw.\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::PublishOscValueReceived(const PendingOscUpdate& update, const std::string& routeKey)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
OscValueReceivedEvent event;
|
|
||||||
event.routeKey = routeKey;
|
|
||||||
event.layerKey = update.layerKey;
|
|
||||||
event.parameterKey = update.parameterKey;
|
|
||||||
event.valueJson = update.valueJson;
|
|
||||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices"))
|
|
||||||
OutputDebugStringA("OscValueReceived event publish failed.\n");
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
OutputDebugStringA("OscValueReceived event publish threw.\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::PublishOscCommitRequested(const PendingOscCommit& commit)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
OscCommitRequestedEvent event;
|
|
||||||
event.routeKey = commit.routeKey;
|
|
||||||
event.layerKey = commit.layerKey;
|
|
||||||
event.parameterKey = commit.parameterKey;
|
|
||||||
event.valueJson = SerializeJson(commit.value, false);
|
|
||||||
event.generation = commit.generation;
|
|
||||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices"))
|
|
||||||
OutputDebugStringA("OscCommitRequested event publish failed.\n");
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
OutputDebugStringA("OscCommitRequested event publish threw.\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ControlServices::PublishOscOverlaySettled(const PendingOscCommit& commit)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
OscOverlayEvent event;
|
|
||||||
event.routeKey = commit.routeKey;
|
|
||||||
event.layerKey = commit.layerKey;
|
|
||||||
event.parameterKey = commit.parameterKey;
|
|
||||||
event.generation = commit.generation;
|
|
||||||
event.settled = true;
|
|
||||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices"))
|
|
||||||
OutputDebugStringA("OscOverlaySettled event publish failed.\n");
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
OutputDebugStringA("OscOverlaySettled event publish threw.\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeJson.h"
|
|
||||||
#include "RuntimeCoordinator.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <chrono>
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class ControlServer;
|
|
||||||
class OpenGLComposite;
|
|
||||||
class OscServer;
|
|
||||||
class RuntimeEventDispatcher;
|
|
||||||
class RuntimeStore;
|
|
||||||
|
|
||||||
class ControlServices
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct AppliedOscUpdate
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
std::string layerKey;
|
|
||||||
std::string parameterKey;
|
|
||||||
JsonValue targetValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CompletedOscCommit
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit ControlServices(RuntimeEventDispatcher& runtimeEventDispatcher);
|
|
||||||
~ControlServices();
|
|
||||||
|
|
||||||
bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);
|
|
||||||
void BeginPolling(RuntimeCoordinator& runtimeCoordinator);
|
|
||||||
void Stop();
|
|
||||||
void BroadcastState();
|
|
||||||
void RequestBroadcastState();
|
|
||||||
bool QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error);
|
|
||||||
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error);
|
|
||||||
bool QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error);
|
|
||||||
void ClearOscState();
|
|
||||||
void ClearOscStateForLayerKey(const std::string& layerKey);
|
|
||||||
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct PendingOscUpdate
|
|
||||||
{
|
|
||||||
std::string layerKey;
|
|
||||||
std::string parameterKey;
|
|
||||||
std::string valueJson;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PendingOscCommit
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
std::string layerKey;
|
|
||||||
std::string parameterKey;
|
|
||||||
JsonValue value;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
void StartPolling(RuntimeCoordinator& runtimeCoordinator);
|
|
||||||
void StopPolling();
|
|
||||||
void PollLoop(RuntimeCoordinator& runtimeCoordinator);
|
|
||||||
void WakePolling();
|
|
||||||
void PublishRuntimeStateBroadcastRequested(const std::string& reason);
|
|
||||||
void PublishOscValueReceived(const PendingOscUpdate& update, const std::string& routeKey);
|
|
||||||
void PublishOscCommitRequested(const PendingOscCommit& commit);
|
|
||||||
void PublishOscOverlaySettled(const PendingOscCommit& commit);
|
|
||||||
|
|
||||||
std::unique_ptr<ControlServer> mControlServer;
|
|
||||||
std::unique_ptr<OscServer> mOscServer;
|
|
||||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
|
||||||
std::thread mPollThread;
|
|
||||||
std::atomic<bool> mPollRunning;
|
|
||||||
std::mutex mPollWakeMutex;
|
|
||||||
std::condition_variable mPollWakeCondition;
|
|
||||||
bool mPollWakeRequested = false;
|
|
||||||
std::mutex mPendingOscMutex;
|
|
||||||
std::map<std::string, PendingOscUpdate> mPendingOscUpdates;
|
|
||||||
std::mutex mPendingOscCommitMutex;
|
|
||||||
std::map<std::string, PendingOscCommit> mPendingOscCommits;
|
|
||||||
std::mutex mCompletedOscCommitMutex;
|
|
||||||
std::vector<CompletedOscCommit> mCompletedOscCommits;
|
|
||||||
};
|
|
||||||
@@ -1,336 +0,0 @@
|
|||||||
#include "stdafx.h"
|
|
||||||
#include "OscServer.h"
|
|
||||||
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstring>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#pragma comment(lib, "Ws2_32.lib")
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
bool InitializeWinsock(std::string& error)
|
|
||||||
{
|
|
||||||
WSADATA wsaData = {};
|
|
||||||
const int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
|
||||||
if (result != 0)
|
|
||||||
{
|
|
||||||
error = "WSAStartup failed.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> SplitAddress(const std::string& address)
|
|
||||||
{
|
|
||||||
std::vector<std::string> parts;
|
|
||||||
std::size_t start = !address.empty() && address[0] == '/' ? 1 : 0;
|
|
||||||
|
|
||||||
while (start <= address.size())
|
|
||||||
{
|
|
||||||
const std::size_t slash = address.find('/', start);
|
|
||||||
const std::size_t end = slash == std::string::npos ? address.size() : slash;
|
|
||||||
if (end > start)
|
|
||||||
parts.push_back(address.substr(start, end - start));
|
|
||||||
if (slash == std::string::npos)
|
|
||||||
break;
|
|
||||||
start = slash + 1;
|
|
||||||
}
|
|
||||||
return parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
OscServer::OscServer()
|
|
||||||
: mPort(0), mRunning(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
OscServer::~OscServer()
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OscServer::Start(const std::string& bindAddress, unsigned short port, const Callbacks& callbacks, std::string& error)
|
|
||||||
{
|
|
||||||
if (port == 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
mCallbacks = callbacks;
|
|
||||||
mPort = port;
|
|
||||||
|
|
||||||
if (!InitializeWinsock(error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
mSocket.reset(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
|
|
||||||
if (!mSocket.valid())
|
|
||||||
{
|
|
||||||
error = "Could not create OSC UDP socket.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD timeoutMilliseconds = 100;
|
|
||||||
setsockopt(mSocket.get(), SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeoutMilliseconds), sizeof(timeoutMilliseconds));
|
|
||||||
|
|
||||||
sockaddr_in address = {};
|
|
||||||
address.sin_family = AF_INET;
|
|
||||||
if (!TryParseBindAddress(bindAddress, address.sin_addr, error))
|
|
||||||
{
|
|
||||||
mSocket.reset();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
address.sin_port = htons(static_cast<u_short>(port));
|
|
||||||
if (bind(mSocket.get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) != 0)
|
|
||||||
{
|
|
||||||
error = "Could not bind OSC listener to " + bindAddress + ":" + std::to_string(port) + ".";
|
|
||||||
mSocket.reset();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mRunning = true;
|
|
||||||
mThread = std::thread(&OscServer::ServerLoop, this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OscServer::TryParseBindAddress(const std::string& bindAddress, in_addr& address, std::string& error)
|
|
||||||
{
|
|
||||||
if (bindAddress.empty())
|
|
||||||
{
|
|
||||||
error = "OSC bind address must not be empty.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
address = {};
|
|
||||||
if (InetPtonA(AF_INET, bindAddress.c_str(), &address) != 1)
|
|
||||||
{
|
|
||||||
error = "Invalid OSC bind address '" + bindAddress + "'. Use an IPv4 address such as 127.0.0.1 or 0.0.0.0.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OscServer::Stop()
|
|
||||||
{
|
|
||||||
mRunning = false;
|
|
||||||
mSocket.reset();
|
|
||||||
if (mThread.joinable())
|
|
||||||
mThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OscServer::ServerLoop()
|
|
||||||
{
|
|
||||||
std::array<char, 4096> buffer = {};
|
|
||||||
while (mRunning)
|
|
||||||
{
|
|
||||||
sockaddr_in sender = {};
|
|
||||||
int senderLength = sizeof(sender);
|
|
||||||
const int byteCount = recvfrom(mSocket.get(), buffer.data(), static_cast<int>(buffer.size()), 0,
|
|
||||||
reinterpret_cast<sockaddr*>(&sender), &senderLength);
|
|
||||||
if (byteCount <= 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
OscMessage message;
|
|
||||||
std::string error;
|
|
||||||
if (DecodeMessage(buffer.data(), byteCount, message, error))
|
|
||||||
{
|
|
||||||
if (!DispatchMessage(message, error) && !error.empty())
|
|
||||||
OutputDebugStringA(("OSC dispatch failed: " + error + "\n").c_str());
|
|
||||||
}
|
|
||||||
else if (!error.empty())
|
|
||||||
{
|
|
||||||
OutputDebugStringA(("OSC decode failed: " + error + "\n").c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OscServer::DecodeMessage(const char* data, int byteCount, OscMessage& message, std::string& error) const
|
|
||||||
{
|
|
||||||
int offset = 0;
|
|
||||||
if (!ReadPaddedString(data, byteCount, offset, message.address) || message.address.empty() || message.address[0] != '/')
|
|
||||||
{
|
|
||||||
error = "Invalid OSC address.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string typeTags;
|
|
||||||
if (!ReadPaddedString(data, byteCount, offset, typeTags) || typeTags.empty() || typeTags[0] != ',')
|
|
||||||
{
|
|
||||||
error = "Invalid OSC type tag string.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeTags.size() < 2)
|
|
||||||
{
|
|
||||||
error = "OSC message has no parameter value.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> values;
|
|
||||||
for (std::size_t index = 1; index < typeTags.size(); ++index)
|
|
||||||
{
|
|
||||||
std::string valueJson;
|
|
||||||
if (!DecodeArgument(data, byteCount, offset, typeTags[index], valueJson))
|
|
||||||
{
|
|
||||||
error = "Unsupported or malformed OSC value type.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
values.push_back(valueJson);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values.size() == 1)
|
|
||||||
{
|
|
||||||
message.valueJson = values.front();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostringstream arrayJson;
|
|
||||||
arrayJson << "[";
|
|
||||||
for (std::size_t index = 0; index < values.size(); ++index)
|
|
||||||
{
|
|
||||||
if (index > 0)
|
|
||||||
arrayJson << ",";
|
|
||||||
arrayJson << values[index];
|
|
||||||
}
|
|
||||||
arrayJson << "]";
|
|
||||||
message.valueJson = arrayJson.str();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OscServer::DispatchMessage(const OscMessage& message, std::string& error) const
|
|
||||||
{
|
|
||||||
const std::vector<std::string> parts = SplitAddress(message.address);
|
|
||||||
if (parts.size() != 3 || parts[0] != "VideoShaderToys")
|
|
||||||
{
|
|
||||||
error = "Unsupported OSC address: " + message.address;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mCallbacks.updateParameter &&
|
|
||||||
mCallbacks.updateParameter(parts[1], parts[2], message.valueJson, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OscServer::DecodeArgument(const char* data, int byteCount, int& offset, char valueType, std::string& valueJson)
|
|
||||||
{
|
|
||||||
if (valueType == 'f')
|
|
||||||
{
|
|
||||||
double value = 0.0;
|
|
||||||
if (!ReadFloat32(data, byteCount, offset, value))
|
|
||||||
return false;
|
|
||||||
std::ostringstream stream;
|
|
||||||
stream << std::setprecision(9) << value;
|
|
||||||
valueJson = stream.str();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueType == 'd')
|
|
||||||
{
|
|
||||||
double value = 0.0;
|
|
||||||
if (!ReadFloat64(data, byteCount, offset, value))
|
|
||||||
return false;
|
|
||||||
std::ostringstream stream;
|
|
||||||
stream << std::setprecision(17) << value;
|
|
||||||
valueJson = stream.str();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueType == 'i')
|
|
||||||
{
|
|
||||||
int value = 0;
|
|
||||||
if (!ReadInt32(data, byteCount, offset, value))
|
|
||||||
return false;
|
|
||||||
valueJson = std::to_string(value);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueType == 's')
|
|
||||||
{
|
|
||||||
std::string value;
|
|
||||||
if (!ReadPaddedString(data, byteCount, offset, value))
|
|
||||||
return false;
|
|
||||||
valueJson = BuildJsonString(value);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueType == 'T' || valueType == 'F')
|
|
||||||
{
|
|
||||||
valueJson = valueType == 'T' ? "true" : "false";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OscServer::ReadPaddedString(const char* data, int byteCount, int& offset, std::string& value)
|
|
||||||
{
|
|
||||||
if (offset < 0 || offset >= byteCount)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const int start = offset;
|
|
||||||
while (offset < byteCount && data[offset] != '\0')
|
|
||||||
++offset;
|
|
||||||
if (offset >= byteCount)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
value.assign(data + start, data + offset);
|
|
||||||
++offset;
|
|
||||||
while (offset % 4 != 0)
|
|
||||||
++offset;
|
|
||||||
return offset <= byteCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OscServer::ReadInt32(const char* data, int byteCount, int& offset, int& value)
|
|
||||||
{
|
|
||||||
if (offset + 4 > byteCount)
|
|
||||||
return false;
|
|
||||||
const unsigned char* bytes = reinterpret_cast<const unsigned char*>(data + offset);
|
|
||||||
value = static_cast<int>((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]);
|
|
||||||
offset += 4;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OscServer::ReadFloat32(const char* data, int byteCount, int& offset, double& value)
|
|
||||||
{
|
|
||||||
int bits = 0;
|
|
||||||
if (!ReadInt32(data, byteCount, offset, bits))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
float floatValue = 0.0f;
|
|
||||||
const unsigned int unsignedBits = static_cast<unsigned int>(bits);
|
|
||||||
std::memcpy(&floatValue, &unsignedBits, sizeof(floatValue));
|
|
||||||
value = static_cast<double>(floatValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OscServer::ReadFloat64(const char* data, int byteCount, int& offset, double& value)
|
|
||||||
{
|
|
||||||
if (offset + 8 > byteCount)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const unsigned char* bytes = reinterpret_cast<const unsigned char*>(data + offset);
|
|
||||||
uint64_t bits = 0;
|
|
||||||
for (int index = 0; index < 8; ++index)
|
|
||||||
bits = (bits << 8) | static_cast<uint64_t>(bytes[index]);
|
|
||||||
|
|
||||||
std::memcpy(&value, &bits, sizeof(value));
|
|
||||||
offset += 8;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string OscServer::BuildJsonString(const std::string& value)
|
|
||||||
{
|
|
||||||
std::ostringstream stream;
|
|
||||||
stream << '"';
|
|
||||||
for (char ch : value)
|
|
||||||
{
|
|
||||||
if (ch == '"' || ch == '\\')
|
|
||||||
stream << '\\';
|
|
||||||
stream << ch;
|
|
||||||
}
|
|
||||||
stream << '"';
|
|
||||||
return stream.str();
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "NativeSockets.h"
|
|
||||||
|
|
||||||
#include <winsock2.h>
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
class OscServer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct Callbacks
|
|
||||||
{
|
|
||||||
std::function<bool(const std::string&, const std::string&, const std::string&, std::string&)> updateParameter;
|
|
||||||
};
|
|
||||||
|
|
||||||
OscServer();
|
|
||||||
~OscServer();
|
|
||||||
|
|
||||||
bool Start(const std::string& bindAddress, unsigned short port, const Callbacks& callbacks, std::string& error);
|
|
||||||
void Stop();
|
|
||||||
|
|
||||||
unsigned short GetPort() const { return mPort; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend struct OscServerTestAccess;
|
|
||||||
|
|
||||||
struct OscMessage
|
|
||||||
{
|
|
||||||
std::string address;
|
|
||||||
std::string valueJson;
|
|
||||||
};
|
|
||||||
|
|
||||||
void ServerLoop();
|
|
||||||
bool DecodeMessage(const char* data, int byteCount, OscMessage& message, std::string& error) const;
|
|
||||||
bool DispatchMessage(const OscMessage& message, std::string& error) const;
|
|
||||||
static bool TryParseBindAddress(const std::string& bindAddress, in_addr& address, std::string& error);
|
|
||||||
static bool DecodeArgument(const char* data, int byteCount, int& offset, char valueType, std::string& valueJson);
|
|
||||||
static bool ReadPaddedString(const char* data, int byteCount, int& offset, std::string& value);
|
|
||||||
static bool ReadInt32(const char* data, int byteCount, int& offset, int& value);
|
|
||||||
static bool ReadFloat32(const char* data, int byteCount, int& offset, double& value);
|
|
||||||
static bool ReadFloat64(const char* data, int byteCount, int& offset, double& value);
|
|
||||||
static std::string BuildJsonString(const std::string& value);
|
|
||||||
|
|
||||||
Callbacks mCallbacks;
|
|
||||||
UniqueSocket mSocket;
|
|
||||||
unsigned short mPort;
|
|
||||||
std::thread mThread;
|
|
||||||
std::atomic<bool> mRunning;
|
|
||||||
};
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#include "RuntimeControlBridge.h"
|
|
||||||
|
|
||||||
#include "ControlServices.h"
|
|
||||||
#include "ControlServer.h"
|
|
||||||
#include "OpenGLComposite.h"
|
|
||||||
#include "OscServer.h"
|
|
||||||
#include "RuntimeStore.h"
|
|
||||||
|
|
||||||
bool StartControlServicesBoundary(
|
|
||||||
OpenGLComposite& composite,
|
|
||||||
RuntimeStore& runtimeStore,
|
|
||||||
ControlServices& controlServices,
|
|
||||||
ControlServer& controlServer,
|
|
||||||
OscServer& oscServer,
|
|
||||||
std::string& error)
|
|
||||||
{
|
|
||||||
ControlServer::Callbacks callbacks;
|
|
||||||
callbacks.getStateJson = [&composite]() { return composite.GetRuntimeStateJson(); };
|
|
||||||
callbacks.addLayer = [&composite](const std::string& shaderId, std::string& actionError) { return composite.AddLayer(shaderId, actionError); };
|
|
||||||
callbacks.removeLayer = [&composite](const std::string& layerId, std::string& actionError) { return composite.RemoveLayer(layerId, actionError); };
|
|
||||||
callbacks.moveLayer = [&composite](const std::string& layerId, int direction, std::string& actionError) { return composite.MoveLayer(layerId, direction, actionError); };
|
|
||||||
callbacks.moveLayerToIndex = [&composite](const std::string& layerId, std::size_t targetIndex, std::string& actionError) { return composite.MoveLayerToIndex(layerId, targetIndex, actionError); };
|
|
||||||
callbacks.setLayerBypass = [&composite](const std::string& layerId, bool bypassed, std::string& actionError) { return composite.SetLayerBypass(layerId, bypassed, actionError); };
|
|
||||||
callbacks.setLayerShader = [&composite](const std::string& layerId, const std::string& shaderId, std::string& actionError) { return composite.SetLayerShader(layerId, shaderId, actionError); };
|
|
||||||
callbacks.updateLayerParameter = [&composite](const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& actionError) {
|
|
||||||
return composite.UpdateLayerParameterJson(layerId, parameterId, valueJson, actionError);
|
|
||||||
};
|
|
||||||
callbacks.resetLayerParameters = [&composite](const std::string& layerId, std::string& actionError) { return composite.ResetLayerParameters(layerId, actionError); };
|
|
||||||
callbacks.saveStackPreset = [&composite](const std::string& presetName, std::string& actionError) { return composite.SaveStackPreset(presetName, actionError); };
|
|
||||||
callbacks.loadStackPreset = [&composite](const std::string& presetName, std::string& actionError) { return composite.LoadStackPreset(presetName, actionError); };
|
|
||||||
callbacks.requestScreenshot = [&composite](std::string& actionError) { return composite.RequestScreenshot(actionError); };
|
|
||||||
callbacks.reloadShader = [&composite](std::string& actionError) {
|
|
||||||
if (!composite.ReloadShader())
|
|
||||||
{
|
|
||||||
actionError = "Shader reload failed. See native app status for details.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!controlServer.Start(runtimeStore.GetRuntimeUiRoot(), runtimeStore.GetRuntimeDocsRoot(), runtimeStore.GetConfiguredControlServerPort(), callbacks, error))
|
|
||||||
return false;
|
|
||||||
runtimeStore.SetBoundControlServerPort(controlServer.GetPort());
|
|
||||||
|
|
||||||
OscServer::Callbacks oscCallbacks;
|
|
||||||
oscCallbacks.updateParameter = [&controlServices](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) {
|
|
||||||
return controlServices.QueueOscUpdate(layerKey, parameterKey, valueJson, actionError);
|
|
||||||
};
|
|
||||||
if (runtimeStore.GetConfiguredOscPort() > 0 &&
|
|
||||||
!oscServer.Start(runtimeStore.GetConfiguredOscBindAddress(), runtimeStore.GetConfiguredOscPort(), oscCallbacks, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class ControlServer;
|
|
||||||
class ControlServices;
|
|
||||||
class OpenGLComposite;
|
|
||||||
class OscServer;
|
|
||||||
class RuntimeStore;
|
|
||||||
|
|
||||||
bool StartControlServicesBoundary(
|
|
||||||
OpenGLComposite& composite,
|
|
||||||
RuntimeStore& runtimeStore,
|
|
||||||
ControlServices& controlServices,
|
|
||||||
ControlServer& controlServer,
|
|
||||||
OscServer& oscServer,
|
|
||||||
std::string& error);
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
#include "RuntimeServiceLiveBridge.h"
|
|
||||||
|
|
||||||
#include "RenderEngine.h"
|
|
||||||
#include "RuntimeServices.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
void DrainServiceEvents(RuntimeServices& runtimeServices, RenderEngine& renderEngine)
|
|
||||||
{
|
|
||||||
std::vector<RuntimeServices::AppliedOscUpdate> appliedOscUpdates;
|
|
||||||
std::vector<RuntimeServices::CompletedOscCommit> completedOscCommits;
|
|
||||||
|
|
||||||
std::string oscError;
|
|
||||||
if (!runtimeServices.ApplyPendingOscUpdates(appliedOscUpdates, oscError) && !oscError.empty())
|
|
||||||
OutputDebugStringA(("OSC apply failed: " + oscError + "\n").c_str());
|
|
||||||
runtimeServices.ConsumeCompletedOscCommits(completedOscCommits);
|
|
||||||
|
|
||||||
std::vector<RenderEngine::OscOverlayUpdate> overlayUpdates;
|
|
||||||
overlayUpdates.reserve(appliedOscUpdates.size());
|
|
||||||
for (const RuntimeServices::AppliedOscUpdate& update : appliedOscUpdates)
|
|
||||||
{
|
|
||||||
overlayUpdates.push_back({ update.routeKey, update.layerKey, update.parameterKey, update.targetValue });
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<RenderEngine::OscOverlayCommitCompletion> overlayCommitCompletions;
|
|
||||||
overlayCommitCompletions.reserve(completedOscCommits.size());
|
|
||||||
for (const RuntimeServices::CompletedOscCommit& completedCommit : completedOscCommits)
|
|
||||||
{
|
|
||||||
overlayCommitCompletions.push_back({ completedCommit.routeKey, completedCommit.generation });
|
|
||||||
}
|
|
||||||
|
|
||||||
renderEngine.UpdateOscOverlayState(overlayUpdates, overlayCommitCompletions);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QueueServiceCommitRequests(
|
|
||||||
RuntimeServices& runtimeServices,
|
|
||||||
const std::vector<RenderEngine::OscOverlayCommitRequest>& commitRequests)
|
|
||||||
{
|
|
||||||
for (const RenderEngine::OscOverlayCommitRequest& commitRequest : commitRequests)
|
|
||||||
{
|
|
||||||
std::string commitError;
|
|
||||||
if (!runtimeServices.QueueOscCommit(
|
|
||||||
commitRequest.routeKey,
|
|
||||||
commitRequest.layerKey,
|
|
||||||
commitRequest.parameterKey,
|
|
||||||
commitRequest.value,
|
|
||||||
commitRequest.generation,
|
|
||||||
commitError) &&
|
|
||||||
!commitError.empty())
|
|
||||||
{
|
|
||||||
OutputDebugStringA(("OSC commit queue failed: " + commitError + "\n").c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeServiceLiveBridge::PrepareLiveRenderFrameState(
|
|
||||||
RuntimeServices& runtimeServices,
|
|
||||||
RenderEngine& renderEngine,
|
|
||||||
const RenderFrameInput& input,
|
|
||||||
RenderFrameState& frameState)
|
|
||||||
{
|
|
||||||
DrainServiceEvents(runtimeServices, renderEngine);
|
|
||||||
|
|
||||||
std::vector<RenderEngine::OscOverlayCommitRequest> commitRequests;
|
|
||||||
const bool resolved = renderEngine.ResolveRenderFrameState(input, &commitRequests, frameState);
|
|
||||||
|
|
||||||
QueueServiceCommitRequests(runtimeServices, commitRequests);
|
|
||||||
return resolved;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RenderFrameState.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class RenderEngine;
|
|
||||||
class RuntimeServices;
|
|
||||||
|
|
||||||
class RuntimeServiceLiveBridge
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static bool PrepareLiveRenderFrameState(
|
|
||||||
RuntimeServices& runtimeServices,
|
|
||||||
RenderEngine& renderEngine,
|
|
||||||
const RenderFrameInput& input,
|
|
||||||
RenderFrameState& frameState);
|
|
||||||
};
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
#include "RuntimeServices.h"
|
|
||||||
|
|
||||||
#include "RuntimeStore.h"
|
|
||||||
|
|
||||||
RuntimeServices::RuntimeServices(RuntimeEventDispatcher& runtimeEventDispatcher) :
|
|
||||||
mControlServices(std::make_unique<ControlServices>(runtimeEventDispatcher))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeServices::~RuntimeServices()
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error)
|
|
||||||
{
|
|
||||||
return mControlServices && mControlServices->Start(composite, runtimeStore, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeServices::BeginPolling(RuntimeCoordinator& runtimeCoordinator)
|
|
||||||
{
|
|
||||||
if (mControlServices)
|
|
||||||
mControlServices->BeginPolling(runtimeCoordinator);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeServices::Stop()
|
|
||||||
{
|
|
||||||
if (mControlServices)
|
|
||||||
mControlServices->Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeServices::BroadcastState()
|
|
||||||
{
|
|
||||||
if (mControlServices)
|
|
||||||
mControlServices->BroadcastState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeServices::RequestBroadcastState()
|
|
||||||
{
|
|
||||||
if (mControlServices)
|
|
||||||
mControlServices->RequestBroadcastState();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeServices::QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
|
|
||||||
{
|
|
||||||
return mControlServices && mControlServices->QueueOscUpdate(layerKey, parameterKey, valueJson, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error)
|
|
||||||
{
|
|
||||||
if (!mControlServices)
|
|
||||||
{
|
|
||||||
appliedUpdates.clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mControlServices->ApplyPendingOscUpdates(appliedUpdates, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeServices::QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error)
|
|
||||||
{
|
|
||||||
return mControlServices && mControlServices->QueueOscCommit(routeKey, layerKey, parameterKey, value, generation, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeServices::ClearOscState()
|
|
||||||
{
|
|
||||||
if (mControlServices)
|
|
||||||
mControlServices->ClearOscState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeServices::ClearOscStateForLayerKey(const std::string& layerKey)
|
|
||||||
{
|
|
||||||
if (mControlServices)
|
|
||||||
mControlServices->ClearOscStateForLayerKey(layerKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
|
|
||||||
{
|
|
||||||
if (!mControlServices)
|
|
||||||
{
|
|
||||||
completedCommits.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mControlServices->ConsumeCompletedOscCommits(completedCommits);
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "ControlServices.h"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
class OpenGLComposite;
|
|
||||||
class RuntimeCoordinator;
|
|
||||||
class RuntimeEventDispatcher;
|
|
||||||
class RuntimeStore;
|
|
||||||
|
|
||||||
class RuntimeServices
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using AppliedOscUpdate = ControlServices::AppliedOscUpdate;
|
|
||||||
using CompletedOscCommit = ControlServices::CompletedOscCommit;
|
|
||||||
|
|
||||||
explicit RuntimeServices(RuntimeEventDispatcher& runtimeEventDispatcher);
|
|
||||||
~RuntimeServices();
|
|
||||||
|
|
||||||
bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);
|
|
||||||
void BeginPolling(RuntimeCoordinator& runtimeCoordinator);
|
|
||||||
void Stop();
|
|
||||||
void BroadcastState();
|
|
||||||
void RequestBroadcastState();
|
|
||||||
bool QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error);
|
|
||||||
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error);
|
|
||||||
bool QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error);
|
|
||||||
void ClearOscState();
|
|
||||||
void ClearOscStateForLayerKey(const std::string& layerKey);
|
|
||||||
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<ControlServices> mControlServices;
|
|
||||||
};
|
|
||||||
@@ -1,668 +0,0 @@
|
|||||||
#include "RenderEngine.h"
|
|
||||||
|
|
||||||
#include <gl/gl.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
RenderEngine::RenderEngine(
|
|
||||||
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
|
||||||
HealthTelemetry& healthTelemetry,
|
|
||||||
HDC hdc,
|
|
||||||
HGLRC hglrc,
|
|
||||||
RenderEffectCallback renderEffect,
|
|
||||||
ScreenshotCallback screenshotReady,
|
|
||||||
PreviewPaintCallback previewPaint) :
|
|
||||||
mRenderer(),
|
|
||||||
mRenderPass(mRenderer),
|
|
||||||
mRenderPipeline(mRenderer, runtimeSnapshotProvider, healthTelemetry, std::move(renderEffect), std::move(screenshotReady), std::move(previewPaint)),
|
|
||||||
mShaderPrograms(mRenderer, runtimeSnapshotProvider),
|
|
||||||
mHealthTelemetry(healthTelemetry),
|
|
||||||
mHdc(hdc),
|
|
||||||
mHglrc(hglrc),
|
|
||||||
mFrameStateResolver(runtimeSnapshotProvider)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderEngine::~RenderEngine()
|
|
||||||
{
|
|
||||||
StopRenderThread();
|
|
||||||
if (!mResourcesDestroyed)
|
|
||||||
{
|
|
||||||
mRenderer.DestroyResources();
|
|
||||||
mResourcesDestroyed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::StartRenderThread()
|
|
||||||
{
|
|
||||||
if (mRenderThreadRunning)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
mRenderThreadStopping = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::promise<bool> ready;
|
|
||||||
std::future<bool> readyResult = ready.get_future();
|
|
||||||
mRenderThread = std::thread(&RenderEngine::RenderThreadMain, this, std::move(ready));
|
|
||||||
if (!readyResult.get())
|
|
||||||
{
|
|
||||||
if (mRenderThread.joinable())
|
|
||||||
mRenderThread.join();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::StopRenderThread()
|
|
||||||
{
|
|
||||||
if (mRenderThreadRunning)
|
|
||||||
{
|
|
||||||
InvokeOnRenderThread([this]() {
|
|
||||||
if (!mResourcesDestroyed)
|
|
||||||
{
|
|
||||||
mRenderer.DestroyResources();
|
|
||||||
mResourcesDestroyed = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
mRenderThreadStopping = true;
|
|
||||||
}
|
|
||||||
mRenderThreadCondition.notify_one();
|
|
||||||
|
|
||||||
if (mRenderThread.joinable())
|
|
||||||
mRenderThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::RenderThreadMain(std::promise<bool> ready)
|
|
||||||
{
|
|
||||||
mRenderThreadId = GetCurrentThreadId();
|
|
||||||
if (!wglMakeCurrent(mHdc, mHglrc))
|
|
||||||
{
|
|
||||||
mRenderThreadId = 0;
|
|
||||||
ready.set_value(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mRenderThreadRunning = true;
|
|
||||||
ready.set_value(true);
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
std::function<void()> task;
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
mRenderThreadCondition.wait(lock, [this]() {
|
|
||||||
return mRenderThreadStopping || !mRenderThreadTasks.empty();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (mRenderThreadStopping && mRenderThreadTasks.empty())
|
|
||||||
break;
|
|
||||||
|
|
||||||
task = std::move(mRenderThreadTasks.front());
|
|
||||||
mRenderThreadTasks.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
task();
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
OutputDebugStringA("Render thread task failed with an unhandled exception.\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wglMakeCurrent(NULL, NULL);
|
|
||||||
mRenderThreadRunning = false;
|
|
||||||
mRenderThreadId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ReportRenderThreadRequestFailure(const char* operationName, const char* reason)
|
|
||||||
{
|
|
||||||
std::ostringstream message;
|
|
||||||
message << "Render thread request failed";
|
|
||||||
if (operationName && operationName[0] != '\0')
|
|
||||||
message << " [" << operationName << "]";
|
|
||||||
if (reason && reason[0] != '\0')
|
|
||||||
message << ": " << reason;
|
|
||||||
message << ".\n";
|
|
||||||
OutputDebugStringA(message.str().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::IsRenderThreadAccessExpected() const
|
|
||||||
{
|
|
||||||
return !mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ReportWrongThreadRenderAccess(const char* operationName) const
|
|
||||||
{
|
|
||||||
if (IsRenderThreadAccessExpected())
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::ostringstream message;
|
|
||||||
message << "Wrong-thread render access detected";
|
|
||||||
if (operationName && operationName[0] != '\0')
|
|
||||||
message << " [" << operationName << "]";
|
|
||||||
message << ".\n";
|
|
||||||
OutputDebugStringA(message.str().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::CompileDecodeShader(int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
return InvokeOnRenderThread([this, errorMessageSize, errorMessage]() {
|
|
||||||
return mShaderPrograms.CompileDecodeShader(errorMessageSize, errorMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
return InvokeOnRenderThread([this, errorMessageSize, errorMessage]() {
|
|
||||||
return mShaderPrograms.CompileOutputPackShader(errorMessageSize, errorMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::InitializeResources(
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
unsigned captureTextureWidth,
|
|
||||||
unsigned outputFrameWidth,
|
|
||||||
unsigned outputFrameHeight,
|
|
||||||
unsigned outputPackTextureWidth,
|
|
||||||
std::string& error)
|
|
||||||
{
|
|
||||||
return InvokeOnRenderThread([this, inputFrameWidth, inputFrameHeight, captureTextureWidth, outputFrameWidth, outputFrameHeight, outputPackTextureWidth, &error]() {
|
|
||||||
return mRenderer.InitializeResources(
|
|
||||||
inputFrameWidth,
|
|
||||||
inputFrameHeight,
|
|
||||||
captureTextureWidth,
|
|
||||||
outputFrameWidth,
|
|
||||||
outputFrameHeight,
|
|
||||||
outputPackTextureWidth,
|
|
||||||
error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
return InvokeOnRenderThread([this, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage]() {
|
|
||||||
return mShaderPrograms.CompileLayerPrograms(inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
return InvokeOnRenderThread([this, &preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage]() {
|
|
||||||
return mShaderPrograms.CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::ApplyPreparedShaderBuild(
|
|
||||||
const PreparedShaderBuild& preparedBuild,
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
bool preserveFeedbackState,
|
|
||||||
int errorMessageSize,
|
|
||||||
char* errorMessage)
|
|
||||||
{
|
|
||||||
if (!CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
mFrameStateResolver.StoreCommittedSnapshot(preparedBuild.renderSnapshot, mShaderPrograms.CommittedLayerStates());
|
|
||||||
ResetTemporalHistoryState();
|
|
||||||
if (!preserveFeedbackState)
|
|
||||||
ResetShaderFeedbackState();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ResetTemporalHistoryState()
|
|
||||||
{
|
|
||||||
InvokeOnRenderThread([this]() {
|
|
||||||
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
|
|
||||||
ProcessRenderResetCommandsOnRenderThread();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ResetShaderFeedbackState()
|
|
||||||
{
|
|
||||||
InvokeOnRenderThread([this]() {
|
|
||||||
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::ShaderFeedbackOnly);
|
|
||||||
ProcessRenderResetCommandsOnRenderThread();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope)
|
|
||||||
{
|
|
||||||
InvokeOnRenderThread([this, resetScope]() {
|
|
||||||
switch (resetScope)
|
|
||||||
{
|
|
||||||
case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly:
|
|
||||||
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
|
|
||||||
ProcessRenderResetCommandsOnRenderThread();
|
|
||||||
break;
|
|
||||||
case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback:
|
|
||||||
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryAndFeedback);
|
|
||||||
ProcessRenderResetCommandsOnRenderThread();
|
|
||||||
break;
|
|
||||||
case RuntimeCoordinatorRenderResetScope::None:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ResetTemporalHistoryStateOnRenderThread()
|
|
||||||
{
|
|
||||||
ReportWrongThreadRenderAccess("reset-temporal-history");
|
|
||||||
mShaderPrograms.ResetTemporalHistoryState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ResetShaderFeedbackStateOnRenderThread()
|
|
||||||
{
|
|
||||||
ReportWrongThreadRenderAccess("reset-shader-feedback");
|
|
||||||
mShaderPrograms.ResetShaderFeedbackState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ApplyRenderResetOnRenderThread(RenderCommandResetScope resetScope)
|
|
||||||
{
|
|
||||||
switch (resetScope)
|
|
||||||
{
|
|
||||||
case RenderCommandResetScope::ShaderFeedbackOnly:
|
|
||||||
ResetShaderFeedbackStateOnRenderThread();
|
|
||||||
break;
|
|
||||||
case RenderCommandResetScope::TemporalHistoryOnly:
|
|
||||||
ResetTemporalHistoryStateOnRenderThread();
|
|
||||||
break;
|
|
||||||
case RenderCommandResetScope::TemporalHistoryAndFeedback:
|
|
||||||
ResetTemporalHistoryStateOnRenderThread();
|
|
||||||
ResetShaderFeedbackStateOnRenderThread();
|
|
||||||
break;
|
|
||||||
case RenderCommandResetScope::None:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ProcessRenderResetCommandsOnRenderThread()
|
|
||||||
{
|
|
||||||
RenderCommandResetScope resetScope = RenderCommandResetScope::None;
|
|
||||||
while (mRenderCommandQueue.TryTakeRenderReset(resetScope))
|
|
||||||
ApplyRenderResetOnRenderThread(resetScope);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::EnqueuePreviewPresentWake()
|
|
||||||
{
|
|
||||||
if (!mRenderThreadRunning)
|
|
||||||
return;
|
|
||||||
|
|
||||||
bool shouldNotify = false;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
if (!mRenderThreadStopping && !mPreviewPresentWakePending)
|
|
||||||
{
|
|
||||||
mPreviewPresentWakePending = true;
|
|
||||||
mRenderThreadTasks.push([this]() {
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
mPreviewPresentWakePending = false;
|
|
||||||
}
|
|
||||||
ProcessPreviewPresentCommandsOnRenderThread();
|
|
||||||
});
|
|
||||||
shouldNotify = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldNotify)
|
|
||||||
mRenderThreadCondition.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ProcessPreviewPresentCommandsOnRenderThread()
|
|
||||||
{
|
|
||||||
RenderPreviewPresentRequest request;
|
|
||||||
if (mRenderCommandQueue.TryTakePreviewPresent(request))
|
|
||||||
PresentPreviewOnRenderThread(request.outputFrameWidth, request.outputFrameHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::EnqueueInputUploadWake()
|
|
||||||
{
|
|
||||||
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
|
|
||||||
return;
|
|
||||||
|
|
||||||
bool shouldNotify = false;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
if (!mRenderThreadStopping && !mInputUploadWakePending)
|
|
||||||
{
|
|
||||||
mInputUploadWakePending = true;
|
|
||||||
mRenderThreadTasks.push([this]() {
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
mInputUploadWakePending = false;
|
|
||||||
}
|
|
||||||
ProcessInputUploadCommandsOnRenderThread();
|
|
||||||
});
|
|
||||||
shouldNotify = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldNotify)
|
|
||||||
mRenderThreadCondition.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ProcessInputUploadCommandsOnRenderThread()
|
|
||||||
{
|
|
||||||
RenderInputUploadRequest request;
|
|
||||||
while (mRenderCommandQueue.TryTakeInputUpload(request))
|
|
||||||
{
|
|
||||||
if (request.ownedBytes.empty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
request.inputFrame.bytes = request.ownedBytes.data();
|
|
||||||
UploadInputFrameOnRenderThread(request.inputFrame, request.videoState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::EnqueueScreenshotCaptureWake()
|
|
||||||
{
|
|
||||||
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
|
|
||||||
return;
|
|
||||||
|
|
||||||
bool shouldNotify = false;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
if (!mRenderThreadStopping && !mScreenshotCaptureWakePending)
|
|
||||||
{
|
|
||||||
mScreenshotCaptureWakePending = true;
|
|
||||||
mRenderThreadTasks.push([this]() {
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
mScreenshotCaptureWakePending = false;
|
|
||||||
}
|
|
||||||
ProcessScreenshotCaptureCommandsOnRenderThread();
|
|
||||||
});
|
|
||||||
shouldNotify = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldNotify)
|
|
||||||
mRenderThreadCondition.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ProcessScreenshotCaptureCommandsOnRenderThread()
|
|
||||||
{
|
|
||||||
RenderScreenshotCaptureRequest request;
|
|
||||||
ScreenshotCaptureCallback completion;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
completion = mScreenshotCaptureCompletion;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (mRenderCommandQueue.TryTakeScreenshotCapture(request))
|
|
||||||
{
|
|
||||||
if (!completion)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
std::vector<unsigned char> topDownPixels;
|
|
||||||
if (CaptureOutputFrameRgbaTopDownOnRenderThread(request.width, request.height, topDownPixels))
|
|
||||||
completion(request.width, request.height, std::move(topDownPixels));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ClearOscOverlayState()
|
|
||||||
{
|
|
||||||
InvokeOnRenderThread([this]() {
|
|
||||||
mRuntimeLiveState.Clear();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ClearOscOverlayStateForLayerKey(const std::string& layerKey)
|
|
||||||
{
|
|
||||||
InvokeOnRenderThread([this, layerKey]() {
|
|
||||||
mRuntimeLiveState.ClearForLayerKey(layerKey);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::UpdateOscOverlayState(
|
|
||||||
const std::vector<OscOverlayUpdate>& updates,
|
|
||||||
const std::vector<OscOverlayCommitCompletion>& completedCommits)
|
|
||||||
{
|
|
||||||
std::vector<RuntimeLiveOscCommitCompletion> liveCompletions;
|
|
||||||
liveCompletions.reserve(completedCommits.size());
|
|
||||||
for (const OscOverlayCommitCompletion& completedCommit : completedCommits)
|
|
||||||
liveCompletions.push_back({ completedCommit.routeKey, completedCommit.generation });
|
|
||||||
mRuntimeLiveState.ApplyOscCommitCompletions(liveCompletions);
|
|
||||||
|
|
||||||
std::vector<RuntimeLiveOscUpdate> liveUpdates;
|
|
||||||
liveUpdates.reserve(updates.size());
|
|
||||||
for (const OscOverlayUpdate& update : updates)
|
|
||||||
liveUpdates.push_back({ update.routeKey, update.layerKey, update.parameterKey, update.targetValue });
|
|
||||||
mRuntimeLiveState.ApplyOscUpdates(liveUpdates);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::ResizeView(int width, int height)
|
|
||||||
{
|
|
||||||
InvokeOnRenderThread([this, width, height]() {
|
|
||||||
mRenderer.ResizeView(width, height);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight)
|
|
||||||
{
|
|
||||||
if (!force)
|
|
||||||
{
|
|
||||||
if (previewFps == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const auto now = std::chrono::steady_clock::now();
|
|
||||||
const auto minimumInterval = std::chrono::microseconds(1000000 / (previewFps == 0 ? 1u : previewFps));
|
|
||||||
if (mLastPreviewPresentTime != std::chrono::steady_clock::time_point() &&
|
|
||||||
now - mLastPreviewPresentTime < minimumInterval)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mRenderThreadRunning)
|
|
||||||
{
|
|
||||||
mRenderCommandQueue.RequestPreviewPresent({ outputFrameWidth, outputFrameHeight });
|
|
||||||
EnqueuePreviewPresentWake();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReportRenderThreadRequestFailure("preview-present", "render thread is not running");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight)
|
|
||||||
{
|
|
||||||
ReportWrongThreadRenderAccess("preview-present");
|
|
||||||
mRenderer.PresentToWindow(mHdc, outputFrameWidth, outputFrameHeight);
|
|
||||||
mLastPreviewPresentTime = std::chrono::steady_clock::now();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::RequestScreenshotCapture(unsigned width, unsigned height, ScreenshotCaptureCallback completion)
|
|
||||||
{
|
|
||||||
if (width == 0 || height == 0 || !completion)
|
|
||||||
return false;
|
|
||||||
if (!mRenderThreadRunning)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
mScreenshotCaptureCompletion = std::move(completion);
|
|
||||||
}
|
|
||||||
mRenderCommandQueue.RequestScreenshotCapture({ width, height });
|
|
||||||
EnqueueScreenshotCaptureWake();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::QueueInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
|
|
||||||
{
|
|
||||||
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
|
|
||||||
return true;
|
|
||||||
if (inputFrame.rowBytes <= 0 || inputFrame.height == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const std::size_t byteCount = static_cast<std::size_t>(inputFrame.rowBytes) * inputFrame.height;
|
|
||||||
RenderInputUploadRequest request;
|
|
||||||
request.inputFrame = inputFrame;
|
|
||||||
request.videoState = videoState;
|
|
||||||
request.ownedBytes.resize(byteCount);
|
|
||||||
std::memcpy(request.ownedBytes.data(), inputFrame.bytes, byteCount);
|
|
||||||
request.inputFrame.bytes = nullptr;
|
|
||||||
|
|
||||||
mRenderCommandQueue.RequestInputUpload(request);
|
|
||||||
EnqueueInputUploadWake();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
|
|
||||||
{
|
|
||||||
ReportWrongThreadRenderAccess("input-upload");
|
|
||||||
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height);
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.TextureUploadBuffer());
|
|
||||||
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, inputFrame.bytes, GL_DYNAMIC_DRAW);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
|
|
||||||
if (inputFrame.pixelFormat == VideoIOPixelFormat::V210)
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
||||||
else
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::RequestOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
|
|
||||||
{
|
|
||||||
if (mRenderThreadRunning)
|
|
||||||
{
|
|
||||||
const auto queuedAt = std::chrono::steady_clock::now();
|
|
||||||
return TryInvokeOnRenderThread("output-render", [this, &context, &outputFrame, queuedAt]() {
|
|
||||||
const auto startedAt = std::chrono::steady_clock::now();
|
|
||||||
const double queueWaitMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(startedAt - queuedAt).count();
|
|
||||||
mHealthTelemetry.TryRecordOutputRenderQueueWait(queueWaitMilliseconds);
|
|
||||||
mRenderCommandQueue.RequestOutputFrame({ context.videoState, context.completion });
|
|
||||||
RenderOutputFrameRequest request;
|
|
||||||
return mRenderCommandQueue.TryTakeOutputFrame(request) &&
|
|
||||||
RenderOutputFrameOnRenderThread({ request.videoState, request.completion }, outputFrame);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ReportRenderThreadRequestFailure("output-render", "render thread is not running");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
|
|
||||||
{
|
|
||||||
ReportWrongThreadRenderAccess("output-render");
|
|
||||||
ProcessRenderResetCommandsOnRenderThread();
|
|
||||||
ProcessInputUploadCommandsOnRenderThread();
|
|
||||||
return mRenderPipeline.RenderFrame(context, outputFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::ResolveRenderFrameState(
|
|
||||||
const RenderFrameInput& input,
|
|
||||||
std::vector<OscOverlayCommitRequest>* commitRequests,
|
|
||||||
RenderFrameState& frameState)
|
|
||||||
{
|
|
||||||
std::vector<RuntimeLiveOscCommitRequest> liveCommitRequests;
|
|
||||||
const bool resolved = mFrameStateResolver.Resolve(
|
|
||||||
input,
|
|
||||||
mShaderPrograms.CommittedLayerStates(),
|
|
||||||
mRuntimeLiveState,
|
|
||||||
commitRequests ? &liveCommitRequests : nullptr,
|
|
||||||
frameState);
|
|
||||||
|
|
||||||
if (commitRequests)
|
|
||||||
{
|
|
||||||
for (const RuntimeLiveOscCommitRequest& request : liveCommitRequests)
|
|
||||||
commitRequests->push_back({ request.routeKey, request.layerKey, request.parameterKey, request.value, request.generation });
|
|
||||||
}
|
|
||||||
return resolved;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::RenderPreparedFrame(const RenderFrameState& frameState)
|
|
||||||
{
|
|
||||||
RenderLayerStack(
|
|
||||||
frameState.hasInputSource,
|
|
||||||
frameState.layerStates,
|
|
||||||
frameState.inputFrameWidth,
|
|
||||||
frameState.inputFrameHeight,
|
|
||||||
frameState.captureTextureWidth,
|
|
||||||
frameState.inputPixelFormat,
|
|
||||||
frameState.historyCap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderEngine::RenderLayerStack(
|
|
||||||
bool hasInputSource,
|
|
||||||
const std::vector<RuntimeRenderState>& layerStates,
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
unsigned captureTextureWidth,
|
|
||||||
VideoIOPixelFormat inputPixelFormat,
|
|
||||||
unsigned historyCap)
|
|
||||||
{
|
|
||||||
ReportWrongThreadRenderAccess("render-layer-stack");
|
|
||||||
mRenderPass.Render(
|
|
||||||
hasInputSource,
|
|
||||||
layerStates,
|
|
||||||
inputFrameWidth,
|
|
||||||
inputFrameHeight,
|
|
||||||
captureTextureWidth,
|
|
||||||
inputPixelFormat,
|
|
||||||
historyCap,
|
|
||||||
[this](const RuntimeRenderState& state, OpenGLRenderer::LayerProgram::TextBinding& textBinding, std::string& error) {
|
|
||||||
return mShaderPrograms.UpdateTextBindingTexture(state, textBinding, error);
|
|
||||||
},
|
|
||||||
[this](const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable) {
|
|
||||||
return mShaderPrograms.UpdateGlobalParamsBuffer(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::ReadOutputFrameRgbaOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels)
|
|
||||||
{
|
|
||||||
ReportWrongThreadRenderAccess("read-output-frame-rgba");
|
|
||||||
if (width == 0 || height == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
bottomUpPixels.resize(static_cast<std::size_t>(width) * height * 4);
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
|
||||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
|
||||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
|
||||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
|
||||||
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bottomUpPixels.data());
|
|
||||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderEngine::CaptureOutputFrameRgbaTopDownOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels)
|
|
||||||
{
|
|
||||||
std::vector<unsigned char> bottomUpPixels;
|
|
||||||
if (!ReadOutputFrameRgbaOnRenderThread(width, height, bottomUpPixels))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
topDownPixels.resize(bottomUpPixels.size());
|
|
||||||
const std::size_t rowBytes = static_cast<std::size_t>(width) * 4;
|
|
||||||
for (unsigned y = 0; y < height; ++y)
|
|
||||||
{
|
|
||||||
const unsigned sourceY = height - 1 - y;
|
|
||||||
std::copy(
|
|
||||||
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>(sourceY * rowBytes),
|
|
||||||
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>((sourceY + 1) * rowBytes),
|
|
||||||
topDownPixels.begin() + static_cast<std::ptrdiff_t>(y * rowBytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "OpenGLRenderPass.h"
|
|
||||||
#include "OpenGLRenderPipeline.h"
|
|
||||||
#include "OpenGLRenderer.h"
|
|
||||||
#include "OpenGLShaderPrograms.h"
|
|
||||||
#include "RenderCommandQueue.h"
|
|
||||||
#include "RenderFrameState.h"
|
|
||||||
#include "RenderFrameStateResolver.h"
|
|
||||||
#include "HealthTelemetry.h"
|
|
||||||
#include "RuntimeCoordinator.h"
|
|
||||||
#include "RuntimeSnapshotProvider.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <chrono>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <functional>
|
|
||||||
#include <future>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <queue>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class RenderEngine
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using RenderEffectCallback = std::function<void()>;
|
|
||||||
using ScreenshotCallback = std::function<void()>;
|
|
||||||
using ScreenshotCaptureCallback = std::function<void(unsigned, unsigned, std::vector<unsigned char>)>;
|
|
||||||
using PreviewPaintCallback = std::function<void()>;
|
|
||||||
|
|
||||||
struct OscOverlayUpdate
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
std::string layerKey;
|
|
||||||
std::string parameterKey;
|
|
||||||
JsonValue targetValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OscOverlayCommitCompletion
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OscOverlayCommitRequest
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
std::string layerKey;
|
|
||||||
std::string parameterKey;
|
|
||||||
JsonValue value;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
RenderEngine(
|
|
||||||
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
|
||||||
HealthTelemetry& healthTelemetry,
|
|
||||||
HDC hdc,
|
|
||||||
HGLRC hglrc,
|
|
||||||
RenderEffectCallback renderEffect,
|
|
||||||
ScreenshotCallback screenshotReady,
|
|
||||||
PreviewPaintCallback previewPaint);
|
|
||||||
~RenderEngine();
|
|
||||||
|
|
||||||
bool StartRenderThread();
|
|
||||||
void StopRenderThread();
|
|
||||||
|
|
||||||
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
|
|
||||||
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
|
|
||||||
bool InitializeResources(
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
unsigned captureTextureWidth,
|
|
||||||
unsigned outputFrameWidth,
|
|
||||||
unsigned outputFrameHeight,
|
|
||||||
unsigned outputPackTextureWidth,
|
|
||||||
std::string& error);
|
|
||||||
bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
|
|
||||||
bool ApplyPreparedShaderBuild(
|
|
||||||
const PreparedShaderBuild& preparedBuild,
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
bool preserveFeedbackState,
|
|
||||||
int errorMessageSize,
|
|
||||||
char* errorMessage);
|
|
||||||
|
|
||||||
void ResetTemporalHistoryState();
|
|
||||||
void ResetShaderFeedbackState();
|
|
||||||
void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope);
|
|
||||||
void ClearOscOverlayState();
|
|
||||||
void ClearOscOverlayStateForLayerKey(const std::string& layerKey);
|
|
||||||
void UpdateOscOverlayState(
|
|
||||||
const std::vector<OscOverlayUpdate>& updates,
|
|
||||||
const std::vector<OscOverlayCommitCompletion>& completedCommits);
|
|
||||||
void ResizeView(int width, int height);
|
|
||||||
bool TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight);
|
|
||||||
bool RequestScreenshotCapture(unsigned width, unsigned height, ScreenshotCaptureCallback completion);
|
|
||||||
bool QueueInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
|
|
||||||
bool RequestOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
|
|
||||||
bool ResolveRenderFrameState(
|
|
||||||
const RenderFrameInput& input,
|
|
||||||
std::vector<OscOverlayCommitRequest>* commitRequests,
|
|
||||||
RenderFrameState& frameState);
|
|
||||||
void RenderPreparedFrame(const RenderFrameState& frameState);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr std::chrono::milliseconds kRenderThreadRequestTimeout{ 250 };
|
|
||||||
|
|
||||||
struct RenderThreadTaskState
|
|
||||||
{
|
|
||||||
std::atomic<bool> started = false;
|
|
||||||
std::atomic<bool> cancelled = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename Func>
|
|
||||||
auto InvokeOnRenderThread(Func&& func) -> decltype(func())
|
|
||||||
{
|
|
||||||
using Result = decltype(func());
|
|
||||||
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
|
|
||||||
return func();
|
|
||||||
|
|
||||||
auto task = std::make_shared<std::packaged_task<Result()>>(std::forward<Func>(func));
|
|
||||||
std::future<Result> result = task->get_future();
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
mRenderThreadTasks.push([task]() { (*task)(); });
|
|
||||||
}
|
|
||||||
mRenderThreadCondition.notify_one();
|
|
||||||
return result.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Func>
|
|
||||||
bool TryInvokeOnRenderThread(const char* operationName, Func&& func)
|
|
||||||
{
|
|
||||||
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
|
|
||||||
return func();
|
|
||||||
|
|
||||||
auto state = std::make_shared<RenderThreadTaskState>();
|
|
||||||
auto task = std::make_shared<std::packaged_task<bool()>>(
|
|
||||||
[state, func = std::forward<Func>(func)]() mutable {
|
|
||||||
state->started = true;
|
|
||||||
if (state->cancelled)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return func();
|
|
||||||
});
|
|
||||||
std::future<bool> result = task->get_future();
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
|
|
||||||
if (mRenderThreadStopping)
|
|
||||||
{
|
|
||||||
ReportRenderThreadRequestFailure(operationName, "render thread is stopping");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
mRenderThreadTasks.push([task]() { (*task)(); });
|
|
||||||
}
|
|
||||||
mRenderThreadCondition.notify_one();
|
|
||||||
|
|
||||||
if (result.wait_for(kRenderThreadRequestTimeout) == std::future_status::ready)
|
|
||||||
return result.get();
|
|
||||||
|
|
||||||
if (!state->started)
|
|
||||||
{
|
|
||||||
state->cancelled = true;
|
|
||||||
ReportRenderThreadRequestFailure(operationName, "timed out before execution");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReportRenderThreadRequestFailure(operationName, "exceeded timeout while executing; waiting for safe completion");
|
|
||||||
return result.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderThreadMain(std::promise<bool> ready);
|
|
||||||
void ReportRenderThreadRequestFailure(const char* operationName, const char* reason);
|
|
||||||
bool IsRenderThreadAccessExpected() const;
|
|
||||||
void ReportWrongThreadRenderAccess(const char* operationName) const;
|
|
||||||
bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
|
|
||||||
void RenderLayerStack(
|
|
||||||
bool hasInputSource,
|
|
||||||
const std::vector<RuntimeRenderState>& layerStates,
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
unsigned captureTextureWidth,
|
|
||||||
VideoIOPixelFormat inputPixelFormat,
|
|
||||||
unsigned historyCap);
|
|
||||||
void ResetTemporalHistoryStateOnRenderThread();
|
|
||||||
void ResetShaderFeedbackStateOnRenderThread();
|
|
||||||
void ApplyRenderResetOnRenderThread(RenderCommandResetScope resetScope);
|
|
||||||
void ProcessRenderResetCommandsOnRenderThread();
|
|
||||||
void EnqueuePreviewPresentWake();
|
|
||||||
void ProcessPreviewPresentCommandsOnRenderThread();
|
|
||||||
void EnqueueInputUploadWake();
|
|
||||||
void ProcessInputUploadCommandsOnRenderThread();
|
|
||||||
void EnqueueScreenshotCaptureWake();
|
|
||||||
void ProcessScreenshotCaptureCommandsOnRenderThread();
|
|
||||||
bool PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight);
|
|
||||||
bool UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
|
|
||||||
bool RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
|
|
||||||
bool ReadOutputFrameRgbaOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels);
|
|
||||||
bool CaptureOutputFrameRgbaTopDownOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels);
|
|
||||||
|
|
||||||
OpenGLRenderer mRenderer;
|
|
||||||
OpenGLRenderPass mRenderPass;
|
|
||||||
OpenGLRenderPipeline mRenderPipeline;
|
|
||||||
OpenGLShaderPrograms mShaderPrograms;
|
|
||||||
HealthTelemetry& mHealthTelemetry;
|
|
||||||
HDC mHdc;
|
|
||||||
HGLRC mHglrc;
|
|
||||||
|
|
||||||
std::chrono::steady_clock::time_point mLastPreviewPresentTime;
|
|
||||||
RenderCommandQueue mRenderCommandQueue;
|
|
||||||
RenderFrameStateResolver mFrameStateResolver;
|
|
||||||
RuntimeLiveState mRuntimeLiveState;
|
|
||||||
std::thread mRenderThread;
|
|
||||||
std::atomic<DWORD> mRenderThreadId = 0;
|
|
||||||
std::mutex mRenderThreadMutex;
|
|
||||||
std::condition_variable mRenderThreadCondition;
|
|
||||||
std::queue<std::function<void()>> mRenderThreadTasks;
|
|
||||||
std::atomic<bool> mRenderThreadRunning = false;
|
|
||||||
bool mRenderThreadStopping = false;
|
|
||||||
bool mPreviewPresentWakePending = false;
|
|
||||||
bool mInputUploadWakePending = false;
|
|
||||||
bool mScreenshotCaptureWakePending = false;
|
|
||||||
ScreenshotCaptureCallback mScreenshotCaptureCompletion;
|
|
||||||
bool mResourcesDestroyed = false;
|
|
||||||
};
|
|
||||||
@@ -1,428 +0,0 @@
|
|||||||
#include "DeckLinkDisplayMode.h"
|
|
||||||
#include "OpenGLComposite.h"
|
|
||||||
#include "GLExtensions.h"
|
|
||||||
#include "PngScreenshotWriter.h"
|
|
||||||
#include "RenderEngine.h"
|
|
||||||
#include "RuntimeCoordinator.h"
|
|
||||||
#include "RuntimeEventDispatcher.h"
|
|
||||||
#include "RuntimeServiceLiveBridge.h"
|
|
||||||
#include "RuntimeServices.h"
|
|
||||||
#include "RuntimeSnapshotProvider.h"
|
|
||||||
#include "RuntimeStore.h"
|
|
||||||
#include "RuntimeUpdateController.h"
|
|
||||||
#include "ShaderBuildQueue.h"
|
|
||||||
#include "VideoBackend.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <ctime>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <memory>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
|
|
||||||
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC)
|
|
||||||
{
|
|
||||||
mRuntimeStore = std::make_unique<RuntimeStore>();
|
|
||||||
mRuntimeEventDispatcher = std::make_unique<RuntimeEventDispatcher>();
|
|
||||||
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(mRuntimeStore->GetRenderSnapshotBuilder(), *mRuntimeEventDispatcher);
|
|
||||||
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore, *mRuntimeEventDispatcher);
|
|
||||||
mRenderEngine = std::make_unique<RenderEngine>(
|
|
||||||
*mRuntimeSnapshotProvider,
|
|
||||||
mRuntimeStore->GetHealthTelemetry(),
|
|
||||||
hGLDC,
|
|
||||||
hGLRC,
|
|
||||||
[this]() { renderEffect(); },
|
|
||||||
[]() {},
|
|
||||||
[this]() { paintGL(false); });
|
|
||||||
mVideoBackend = std::make_unique<VideoBackend>(*mRenderEngine, mRuntimeStore->GetHealthTelemetry(), *mRuntimeEventDispatcher);
|
|
||||||
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeSnapshotProvider, *mRuntimeEventDispatcher);
|
|
||||||
mRuntimeServices = std::make_unique<RuntimeServices>(*mRuntimeEventDispatcher);
|
|
||||||
mRuntimeUpdateController = std::make_unique<RuntimeUpdateController>(
|
|
||||||
*mRuntimeStore,
|
|
||||||
*mRuntimeCoordinator,
|
|
||||||
*mRuntimeEventDispatcher,
|
|
||||||
*mRuntimeServices,
|
|
||||||
*mRenderEngine,
|
|
||||||
*mShaderBuildQueue,
|
|
||||||
*mVideoBackend);
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenGLComposite::~OpenGLComposite()
|
|
||||||
{
|
|
||||||
if (mRuntimeServices)
|
|
||||||
mRuntimeServices->Stop();
|
|
||||||
if (mShaderBuildQueue)
|
|
||||||
mShaderBuildQueue->Stop();
|
|
||||||
if (mVideoBackend)
|
|
||||||
mVideoBackend->ReleaseResources();
|
|
||||||
if (mRuntimeStore)
|
|
||||||
{
|
|
||||||
std::string persistenceError;
|
|
||||||
if (!mRuntimeStore->FlushPersistenceForShutdown(std::chrono::seconds(2), persistenceError))
|
|
||||||
OutputDebugStringA((std::string("Persistence shutdown flush failed: ") + persistenceError + "\n").c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::InitDeckLink()
|
|
||||||
{
|
|
||||||
return InitVideoIO();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::InitVideoIO()
|
|
||||||
{
|
|
||||||
VideoFormatSelection videoModes;
|
|
||||||
std::string initFailureReason;
|
|
||||||
|
|
||||||
if (mRuntimeStore && mRuntimeStore->GetRuntimeRepositoryRoot().empty())
|
|
||||||
{
|
|
||||||
std::string runtimeError;
|
|
||||||
if (!mRuntimeStore->InitializeStore(runtimeError))
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mRuntimeStore)
|
|
||||||
{
|
|
||||||
if (!ResolveConfiguredVideoFormats(
|
|
||||||
mRuntimeStore->GetConfiguredInputVideoFormat(),
|
|
||||||
mRuntimeStore->GetConfiguredInputFrameRate(),
|
|
||||||
mRuntimeStore->GetConfiguredOutputVideoFormat(),
|
|
||||||
mRuntimeStore->GetConfiguredOutputFrameRate(),
|
|
||||||
videoModes,
|
|
||||||
initFailureReason))
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink mode configuration error", MB_OK);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mVideoBackend->DiscoverDevicesAndModes(videoModes, initFailureReason))
|
|
||||||
{
|
|
||||||
const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application."
|
|
||||||
? "This application requires the DeckLink drivers installed."
|
|
||||||
: "DeckLink initialization failed";
|
|
||||||
MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const bool outputAlphaRequired = mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured();
|
|
||||||
if (!mVideoBackend->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason))
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
if (! CheckOpenGLExtensions())
|
|
||||||
{
|
|
||||||
initFailureReason = "OpenGL extension checks failed.";
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! InitOpenGLState())
|
|
||||||
{
|
|
||||||
initFailureReason = "OpenGL state initialization failed.";
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
mVideoBackend->PublishStatus(
|
|
||||||
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
|
|
||||||
mVideoBackend->OutputModelName().empty()
|
|
||||||
? "DeckLink output device selected."
|
|
||||||
: ("Selected output device: " + mVideoBackend->OutputModelName()));
|
|
||||||
|
|
||||||
// Resize window to match output video frame, but scale large formats down by half for viewing.
|
|
||||||
if (mVideoBackend->OutputFrameWidth() < 1920)
|
|
||||||
resizeWindow(mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight());
|
|
||||||
else
|
|
||||||
resizeWindow(mVideoBackend->OutputFrameWidth() / 2, mVideoBackend->OutputFrameHeight() / 2);
|
|
||||||
|
|
||||||
if (!mVideoBackend->ConfigureInput(videoModes.input, initFailureReason))
|
|
||||||
{
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
if (!mVideoBackend->HasInputDevice())
|
|
||||||
mVideoBackend->ReportNoInputDeviceSignalStatus();
|
|
||||||
|
|
||||||
if (!mVideoBackend->ConfigureOutput(videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason))
|
|
||||||
{
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
mVideoBackend->PublishStatus(
|
|
||||||
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
|
|
||||||
mVideoBackend->StatusMessage());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
error:
|
|
||||||
if (!initFailureReason.empty())
|
|
||||||
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR);
|
|
||||||
mVideoBackend->ReleaseResources();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLComposite::paintGL(bool force)
|
|
||||||
{
|
|
||||||
if (mRuntimeUpdateController)
|
|
||||||
mRuntimeUpdateController->ProcessRuntimeWork();
|
|
||||||
|
|
||||||
if (!force)
|
|
||||||
{
|
|
||||||
if (IsIconic(hGLWnd))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unsigned previewFps = mRuntimeStore ? mRuntimeStore->GetConfiguredPreviewFps() : 30u;
|
|
||||||
if (!force && mVideoBackend && mVideoBackend->ShouldPrioritizeOutputOverPreview())
|
|
||||||
{
|
|
||||||
ValidateRect(hGLWnd, NULL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mRenderEngine->TryPresentPreview(force, previewFps, mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight()))
|
|
||||||
{
|
|
||||||
ValidateRect(hGLWnd, NULL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ValidateRect(hGLWnd, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLComposite::resizeGL(WORD width, WORD height)
|
|
||||||
{
|
|
||||||
// We don't set the project or model matrices here since the window data is copied directly from
|
|
||||||
// an off-screen FBO in paintGL(). Just save the width and height for use in paintGL().
|
|
||||||
mRenderEngine->ResizeView(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLComposite::resizeWindow(int width, int height)
|
|
||||||
{
|
|
||||||
RECT r;
|
|
||||||
if (GetWindowRect(hGLWnd, &r))
|
|
||||||
{
|
|
||||||
SetWindowPos(hGLWnd, HWND_TOP, r.left, r.top, r.left + width, r.top + height, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::InitOpenGLState()
|
|
||||||
{
|
|
||||||
if (! ResolveGLExtensions())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
std::string runtimeError;
|
|
||||||
if (mRuntimeStore->GetRuntimeRepositoryRoot().empty() && !mRuntimeStore->InitializeStore(runtimeError))
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mRuntimeServices->Start(*this, *mRuntimeStore, runtimeError))
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, runtimeError.c_str(), "Runtime control services failed to start", MB_OK);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the runtime shader program generated from the active shader package.
|
|
||||||
char compilerErrorMessage[1024];
|
|
||||||
if (!mRenderEngine->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage))
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!mRenderEngine->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage))
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL output pack shader failed to load or compile", MB_OK);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string rendererError;
|
|
||||||
if (!mRenderEngine->InitializeResources(
|
|
||||||
mVideoBackend->InputFrameWidth(),
|
|
||||||
mVideoBackend->InputFrameHeight(),
|
|
||||||
mVideoBackend->CaptureTextureWidth(),
|
|
||||||
mVideoBackend->OutputFrameWidth(),
|
|
||||||
mVideoBackend->OutputFrameHeight(),
|
|
||||||
mVideoBackend->OutputPackTextureWidth(),
|
|
||||||
rendererError))
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mRenderEngine->CompileLayerPrograms(mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
|
|
||||||
{
|
|
||||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
mRuntimeStore->SetCompileStatus(true, "Shader layers compiled successfully.");
|
|
||||||
|
|
||||||
mRenderEngine->ResetTemporalHistoryState();
|
|
||||||
mRenderEngine->ResetShaderFeedbackState();
|
|
||||||
|
|
||||||
mRuntimeUpdateController->BroadcastRuntimeState();
|
|
||||||
mRuntimeServices->BeginPolling(*mRuntimeCoordinator);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::Start()
|
|
||||||
{
|
|
||||||
if (!mRenderEngine->StartRenderThread())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (mRuntimeUpdateController)
|
|
||||||
mRuntimeUpdateController->ProcessRuntimeWork();
|
|
||||||
|
|
||||||
if (mVideoBackend->Start())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
mRenderEngine->StopRenderThread();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::Stop()
|
|
||||||
{
|
|
||||||
if (mRuntimeServices)
|
|
||||||
mRuntimeServices->Stop();
|
|
||||||
|
|
||||||
const bool wasExternalKeyingActive = mVideoBackend->ExternalKeyingActive();
|
|
||||||
mVideoBackend->Stop();
|
|
||||||
if (wasExternalKeyingActive)
|
|
||||||
mVideoBackend->PublishStatus(
|
|
||||||
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
|
|
||||||
"External keying has been disabled.");
|
|
||||||
|
|
||||||
if (mRenderEngine)
|
|
||||||
mRenderEngine->StopRenderThread();
|
|
||||||
|
|
||||||
if (mRuntimeStore)
|
|
||||||
{
|
|
||||||
std::string persistenceError;
|
|
||||||
if (!mRuntimeStore->FlushPersistenceForShutdown(std::chrono::seconds(2), persistenceError))
|
|
||||||
OutputDebugStringA((std::string("Persistence shutdown flush failed: ") + persistenceError + "\n").c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::ReloadShader(bool preserveFeedbackState)
|
|
||||||
{
|
|
||||||
return mRuntimeCoordinator &&
|
|
||||||
mRuntimeUpdateController &&
|
|
||||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RequestShaderReload(preserveFeedbackState));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::RequestScreenshot(std::string& error)
|
|
||||||
{
|
|
||||||
if (!mRenderEngine || !mVideoBackend)
|
|
||||||
{
|
|
||||||
error = "The render engine is not ready.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unsigned width = mVideoBackend->OutputFrameWidth();
|
|
||||||
const unsigned height = mVideoBackend->OutputFrameHeight();
|
|
||||||
if (width == 0 || height == 0)
|
|
||||||
{
|
|
||||||
error = "The output frame size is not available.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path outputPath;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
outputPath = BuildScreenshotPath();
|
|
||||||
std::filesystem::create_directories(outputPath.parent_path());
|
|
||||||
}
|
|
||||||
catch (const std::exception& exception)
|
|
||||||
{
|
|
||||||
error = exception.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mRenderEngine->RequestScreenshotCapture(
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
[outputPath](unsigned captureWidth, unsigned captureHeight, std::vector<unsigned char> topDownPixels) {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
WritePngFileAsync(outputPath, captureWidth, captureHeight, std::move(topDownPixels));
|
|
||||||
}
|
|
||||||
catch (const std::exception& exception)
|
|
||||||
{
|
|
||||||
OutputDebugStringA((std::string("Screenshot request failed: ") + exception.what() + "\n").c_str());
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
{
|
|
||||||
error = "Screenshot capture request failed.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLComposite::renderEffect()
|
|
||||||
{
|
|
||||||
const RenderFrameInput frameInput = BuildRenderFrameInput();
|
|
||||||
RenderFrame(frameInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderFrameInput OpenGLComposite::BuildRenderFrameInput() const
|
|
||||||
{
|
|
||||||
RenderFrameInput frameInput;
|
|
||||||
frameInput.useCommittedLayerStates = mRuntimeCoordinator && mRuntimeCoordinator->UseCommittedLayerStates();
|
|
||||||
frameInput.hasInputSource = mVideoBackend->HasInputSource();
|
|
||||||
frameInput.renderWidth = mVideoBackend->InputFrameWidth();
|
|
||||||
frameInput.renderHeight = mVideoBackend->InputFrameHeight();
|
|
||||||
frameInput.inputFrameWidth = mVideoBackend->InputFrameWidth();
|
|
||||||
frameInput.inputFrameHeight = mVideoBackend->InputFrameHeight();
|
|
||||||
frameInput.captureTextureWidth = mVideoBackend->CaptureTextureWidth();
|
|
||||||
frameInput.inputPixelFormat = mVideoBackend->InputPixelFormat();
|
|
||||||
frameInput.historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0;
|
|
||||||
frameInput.oscSmoothing = mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0;
|
|
||||||
return frameInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLComposite::RenderFrame(const RenderFrameInput& frameInput)
|
|
||||||
{
|
|
||||||
RenderFrameState frameState;
|
|
||||||
if (mRuntimeServices)
|
|
||||||
{
|
|
||||||
RuntimeServiceLiveBridge::PrepareLiveRenderFrameState(
|
|
||||||
*mRuntimeServices,
|
|
||||||
*mRenderEngine,
|
|
||||||
frameInput,
|
|
||||||
frameState);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mRenderEngine->ResolveRenderFrameState(frameInput, nullptr, frameState);
|
|
||||||
}
|
|
||||||
mRenderEngine->RenderPreparedFrame(frameState);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path OpenGLComposite::BuildScreenshotPath() const
|
|
||||||
{
|
|
||||||
const std::filesystem::path root = mRuntimeStore && !mRuntimeStore->GetRuntimeDataRoot().empty()
|
|
||||||
? mRuntimeStore->GetRuntimeDataRoot()
|
|
||||||
: std::filesystem::current_path();
|
|
||||||
|
|
||||||
const auto now = std::chrono::system_clock::now();
|
|
||||||
const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
|
|
||||||
const std::time_t nowTime = std::chrono::system_clock::to_time_t(now);
|
|
||||||
std::tm localTime = {};
|
|
||||||
localtime_s(&localTime, &nowTime);
|
|
||||||
|
|
||||||
std::ostringstream filename;
|
|
||||||
filename << "video-shader-toys-"
|
|
||||||
<< std::put_time(&localTime, "%Y%m%d-%H%M%S")
|
|
||||||
<< "-" << std::setw(3) << std::setfill('0') << milliseconds.count()
|
|
||||||
<< ".png";
|
|
||||||
|
|
||||||
return root / "screenshots" / filename.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::CheckOpenGLExtensions()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
#ifndef __OPENGL_COMPOSITE_H__
|
|
||||||
#define __OPENGL_COMPOSITE_H__
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include <objbase.h>
|
|
||||||
|
|
||||||
#include "RenderFrameState.h"
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class RenderEngine;
|
|
||||||
class RuntimeCoordinator;
|
|
||||||
class RuntimeEventDispatcher;
|
|
||||||
class RuntimeSnapshotProvider;
|
|
||||||
class RuntimeServices;
|
|
||||||
class RuntimeStore;
|
|
||||||
class RuntimeUpdateController;
|
|
||||||
class ShaderBuildQueue;
|
|
||||||
class VideoBackend;
|
|
||||||
|
|
||||||
|
|
||||||
class OpenGLComposite
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC);
|
|
||||||
~OpenGLComposite();
|
|
||||||
|
|
||||||
bool InitDeckLink();
|
|
||||||
bool InitVideoIO();
|
|
||||||
bool Start();
|
|
||||||
bool Stop();
|
|
||||||
bool ReloadShader(bool preserveFeedbackState = false);
|
|
||||||
std::string GetRuntimeStateJson() const;
|
|
||||||
bool AddLayer(const std::string& shaderId, std::string& error);
|
|
||||||
bool RemoveLayer(const std::string& layerId, std::string& error);
|
|
||||||
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
|
|
||||||
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
|
|
||||||
bool SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error);
|
|
||||||
bool SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error);
|
|
||||||
bool UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error);
|
|
||||||
bool UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error);
|
|
||||||
bool ResetLayerParameters(const std::string& layerId, std::string& error);
|
|
||||||
bool SaveStackPreset(const std::string& presetName, std::string& error);
|
|
||||||
bool LoadStackPreset(const std::string& presetName, std::string& error);
|
|
||||||
bool RequestScreenshot(std::string& error);
|
|
||||||
unsigned short GetControlServerPort() const;
|
|
||||||
unsigned short GetOscPort() const;
|
|
||||||
std::string GetOscBindAddress() const;
|
|
||||||
std::string GetControlUrl() const;
|
|
||||||
std::string GetDocsUrl() const;
|
|
||||||
std::string GetOscAddress() const;
|
|
||||||
|
|
||||||
void resizeGL(WORD width, WORD height);
|
|
||||||
void paintGL(bool force = false);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void resizeWindow(int width, int height);
|
|
||||||
bool CheckOpenGLExtensions();
|
|
||||||
|
|
||||||
HWND hGLWnd;
|
|
||||||
HDC hGLDC;
|
|
||||||
HGLRC hGLRC;
|
|
||||||
|
|
||||||
std::unique_ptr<RuntimeStore> mRuntimeStore;
|
|
||||||
std::unique_ptr<RuntimeCoordinator> mRuntimeCoordinator;
|
|
||||||
std::unique_ptr<RuntimeSnapshotProvider> mRuntimeSnapshotProvider;
|
|
||||||
std::unique_ptr<RuntimeEventDispatcher> mRuntimeEventDispatcher;
|
|
||||||
std::unique_ptr<RenderEngine> mRenderEngine;
|
|
||||||
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
|
|
||||||
std::unique_ptr<RuntimeServices> mRuntimeServices;
|
|
||||||
std::unique_ptr<RuntimeUpdateController> mRuntimeUpdateController;
|
|
||||||
std::unique_ptr<VideoBackend> mVideoBackend;
|
|
||||||
|
|
||||||
bool InitOpenGLState();
|
|
||||||
void renderEffect();
|
|
||||||
RenderFrameInput BuildRenderFrameInput() const;
|
|
||||||
void RenderFrame(const RenderFrameInput& frameInput);
|
|
||||||
std::filesystem::path BuildScreenshotPath() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // __OPENGL_COMPOSITE_H__
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
#include "OpenGLComposite.h"
|
|
||||||
#include "RuntimeCoordinator.h"
|
|
||||||
#include "RuntimeJson.h"
|
|
||||||
#include "RuntimeServices.h"
|
|
||||||
#include "RuntimeStore.h"
|
|
||||||
#include "RuntimeUpdateController.h"
|
|
||||||
|
|
||||||
std::string OpenGLComposite::GetRuntimeStateJson() const
|
|
||||||
{
|
|
||||||
return mRuntimeStore ? mRuntimeStore->BuildPersistentStateJson() : "{}";
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned short OpenGLComposite::GetControlServerPort() const
|
|
||||||
{
|
|
||||||
return mRuntimeStore ? mRuntimeStore->GetConfiguredControlServerPort() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned short OpenGLComposite::GetOscPort() const
|
|
||||||
{
|
|
||||||
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscPort() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string OpenGLComposite::GetOscBindAddress() const
|
|
||||||
{
|
|
||||||
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscBindAddress() : "127.0.0.1";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string OpenGLComposite::GetControlUrl() const
|
|
||||||
{
|
|
||||||
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string OpenGLComposite::GetDocsUrl() const
|
|
||||||
{
|
|
||||||
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/docs";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string OpenGLComposite::GetOscAddress() const
|
|
||||||
{
|
|
||||||
return "udp://" + GetOscBindAddress() + ":" + std::to_string(GetOscPort()) + " /VideoShaderToys/{Layer}/{Parameter}";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
|
|
||||||
{
|
|
||||||
return mRuntimeCoordinator &&
|
|
||||||
mRuntimeUpdateController &&
|
|
||||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->AddLayer(shaderId), &error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error)
|
|
||||||
{
|
|
||||||
return mRuntimeCoordinator &&
|
|
||||||
mRuntimeUpdateController &&
|
|
||||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RemoveLayer(layerId), &error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error)
|
|
||||||
{
|
|
||||||
return mRuntimeCoordinator &&
|
|
||||||
mRuntimeUpdateController &&
|
|
||||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayer(layerId, direction), &error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
|
||||||
{
|
|
||||||
return mRuntimeCoordinator &&
|
|
||||||
mRuntimeUpdateController &&
|
|
||||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayerToIndex(layerId, targetIndex), &error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
|
|
||||||
{
|
|
||||||
return mRuntimeCoordinator &&
|
|
||||||
mRuntimeUpdateController &&
|
|
||||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerBypass(layerId, bypassed), &error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
|
|
||||||
{
|
|
||||||
return mRuntimeCoordinator &&
|
|
||||||
mRuntimeUpdateController &&
|
|
||||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerShader(layerId, shaderId), &error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error)
|
|
||||||
{
|
|
||||||
JsonValue parsedValue;
|
|
||||||
if (!ParseJson(valueJson, parsedValue, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return mRuntimeCoordinator &&
|
|
||||||
mRuntimeUpdateController &&
|
|
||||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameter(layerId, parameterId, parsedValue), &error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
|
|
||||||
{
|
|
||||||
JsonValue parsedValue;
|
|
||||||
if (!ParseJson(valueJson, parsedValue, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return mRuntimeCoordinator &&
|
|
||||||
mRuntimeUpdateController &&
|
|
||||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue), &error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error)
|
|
||||||
{
|
|
||||||
return mRuntimeCoordinator &&
|
|
||||||
mRuntimeUpdateController &&
|
|
||||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->ResetLayerParameters(layerId), &error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error)
|
|
||||||
{
|
|
||||||
return mRuntimeCoordinator &&
|
|
||||||
mRuntimeUpdateController &&
|
|
||||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SaveStackPreset(presetName), &error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error)
|
|
||||||
{
|
|
||||||
return mRuntimeCoordinator &&
|
|
||||||
mRuntimeUpdateController &&
|
|
||||||
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->LoadStackPreset(presetName), &error);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
#include "VideoIOTypes.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct RenderFrameInput
|
|
||||||
{
|
|
||||||
bool useCommittedLayerStates = false;
|
|
||||||
bool hasInputSource = false;
|
|
||||||
unsigned renderWidth = 0;
|
|
||||||
unsigned renderHeight = 0;
|
|
||||||
unsigned inputFrameWidth = 0;
|
|
||||||
unsigned inputFrameHeight = 0;
|
|
||||||
unsigned captureTextureWidth = 0;
|
|
||||||
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
|
||||||
unsigned historyCap = 0;
|
|
||||||
double oscSmoothing = 0.0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderFrameState
|
|
||||||
{
|
|
||||||
bool hasInputSource = false;
|
|
||||||
unsigned inputFrameWidth = 0;
|
|
||||||
unsigned inputFrameHeight = 0;
|
|
||||||
unsigned captureTextureWidth = 0;
|
|
||||||
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
|
||||||
unsigned historyCap = 0;
|
|
||||||
std::vector<RuntimeRenderState> layerStates;
|
|
||||||
};
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
#include "RenderFrameStateResolver.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150);
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderFrameStateResolver::RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider) :
|
|
||||||
mRuntimeSnapshotProvider(runtimeSnapshotProvider)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderFrameStateResolver::StoreCommittedSnapshot(
|
|
||||||
const RuntimeRenderStateSnapshot& snapshot,
|
|
||||||
const std::vector<RuntimeRenderState>& committedLayerStates)
|
|
||||||
{
|
|
||||||
mCachedLayerRenderStates = committedLayerStates;
|
|
||||||
mCachedRenderStateVersion = snapshot.versions.renderStateVersion;
|
|
||||||
mCachedParameterStateVersion = snapshot.versions.parameterStateVersion;
|
|
||||||
mCachedRenderStateWidth = snapshot.outputWidth;
|
|
||||||
mCachedRenderStateHeight = snapshot.outputHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderFrameStateResolver::Resolve(
|
|
||||||
const RenderFrameInput& input,
|
|
||||||
const std::vector<RuntimeRenderState>& committedLayerStates,
|
|
||||||
RuntimeLiveState& liveState,
|
|
||||||
std::vector<RuntimeLiveOscCommitRequest>* commitRequests,
|
|
||||||
RenderFrameState& frameState)
|
|
||||||
{
|
|
||||||
frameState.hasInputSource = input.hasInputSource;
|
|
||||||
frameState.inputFrameWidth = input.inputFrameWidth;
|
|
||||||
frameState.inputFrameHeight = input.inputFrameHeight;
|
|
||||||
frameState.captureTextureWidth = input.captureTextureWidth;
|
|
||||||
frameState.inputPixelFormat = input.inputPixelFormat;
|
|
||||||
frameState.historyCap = input.historyCap;
|
|
||||||
frameState.layerStates.clear();
|
|
||||||
|
|
||||||
if (input.useCommittedLayerStates)
|
|
||||||
{
|
|
||||||
frameState.layerStates = ComposeLayerStates(committedLayerStates, liveState, false, input.oscSmoothing, commitRequests);
|
|
||||||
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider.GetVersions();
|
|
||||||
const bool renderStateCacheValid =
|
|
||||||
!mCachedLayerRenderStates.empty() &&
|
|
||||||
mCachedRenderStateVersion == versions.renderStateVersion &&
|
|
||||||
mCachedRenderStateWidth == input.renderWidth &&
|
|
||||||
mCachedRenderStateHeight == input.renderHeight;
|
|
||||||
|
|
||||||
if (renderStateCacheValid)
|
|
||||||
{
|
|
||||||
RuntimeRenderStateSnapshot renderSnapshot;
|
|
||||||
renderSnapshot.outputWidth = input.renderWidth;
|
|
||||||
renderSnapshot.outputHeight = input.renderHeight;
|
|
||||||
renderSnapshot.versions.renderStateVersion = mCachedRenderStateVersion;
|
|
||||||
renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion;
|
|
||||||
renderSnapshot.states = mCachedLayerRenderStates;
|
|
||||||
|
|
||||||
renderSnapshot.states = ComposeLayerStates(renderSnapshot.states, liveState, true, input.oscSmoothing, commitRequests);
|
|
||||||
if (mCachedParameterStateVersion != versions.parameterStateVersion &&
|
|
||||||
mRuntimeSnapshotProvider.TryRefreshPublishedSnapshotParameters(renderSnapshot))
|
|
||||||
{
|
|
||||||
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
|
|
||||||
renderSnapshot.states = ComposeLayerStates(renderSnapshot.states, liveState, true, input.oscSmoothing, commitRequests);
|
|
||||||
}
|
|
||||||
|
|
||||||
mCachedLayerRenderStates = renderSnapshot.states;
|
|
||||||
frameState.layerStates = renderSnapshot.states;
|
|
||||||
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeRenderStateSnapshot renderSnapshot;
|
|
||||||
if (mRuntimeSnapshotProvider.TryPublishRenderStateSnapshot(input.renderWidth, input.renderHeight, renderSnapshot))
|
|
||||||
{
|
|
||||||
mCachedLayerRenderStates = renderSnapshot.states;
|
|
||||||
mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion;
|
|
||||||
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
|
|
||||||
mCachedRenderStateWidth = renderSnapshot.outputWidth;
|
|
||||||
mCachedRenderStateHeight = renderSnapshot.outputHeight;
|
|
||||||
mCachedLayerRenderStates = ComposeLayerStates(mCachedLayerRenderStates, liveState, true, input.oscSmoothing, commitRequests);
|
|
||||||
frameState.layerStates = mCachedLayerRenderStates;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
frameState.layerStates = ComposeLayerStates(mCachedLayerRenderStates, liveState, true, input.oscSmoothing, commitRequests);
|
|
||||||
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
|
|
||||||
return !frameState.layerStates.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<RuntimeRenderState> RenderFrameStateResolver::ComposeLayerStates(
|
|
||||||
const std::vector<RuntimeRenderState>& baseStates,
|
|
||||||
RuntimeLiveState& liveState,
|
|
||||||
bool allowCommit,
|
|
||||||
double smoothing,
|
|
||||||
std::vector<RuntimeLiveOscCommitRequest>* commitRequests) const
|
|
||||||
{
|
|
||||||
LayeredRenderStateInput input;
|
|
||||||
input.committedLiveLayerStates = &baseStates;
|
|
||||||
input.transientAutomationOverlay = &liveState;
|
|
||||||
input.allowTransientAutomationCommits = allowCommit;
|
|
||||||
input.collectTransientAutomationCommitRequests = commitRequests != nullptr;
|
|
||||||
input.transientAutomationSmoothing = smoothing;
|
|
||||||
input.transientAutomationCommitDelay = kOscOverlayCommitDelay;
|
|
||||||
input.now = std::chrono::steady_clock::now();
|
|
||||||
const RenderStateCompositionResult result = mRenderStateComposer.BuildFrameState(input);
|
|
||||||
|
|
||||||
if (commitRequests)
|
|
||||||
{
|
|
||||||
for (const RuntimeLiveOscCommitRequest& request : result.commitRequests)
|
|
||||||
commitRequests->push_back(request);
|
|
||||||
}
|
|
||||||
return result.layerStates;
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RenderFrameState.h"
|
|
||||||
#include "RenderStateComposer.h"
|
|
||||||
#include "RuntimeSnapshotProvider.h"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class RenderFrameStateResolver
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider);
|
|
||||||
|
|
||||||
void StoreCommittedSnapshot(
|
|
||||||
const RuntimeRenderStateSnapshot& snapshot,
|
|
||||||
const std::vector<RuntimeRenderState>& committedLayerStates);
|
|
||||||
bool Resolve(
|
|
||||||
const RenderFrameInput& input,
|
|
||||||
const std::vector<RuntimeRenderState>& committedLayerStates,
|
|
||||||
RuntimeLiveState& liveState,
|
|
||||||
std::vector<RuntimeLiveOscCommitRequest>* commitRequests,
|
|
||||||
RenderFrameState& frameState);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<RuntimeRenderState> ComposeLayerStates(
|
|
||||||
const std::vector<RuntimeRenderState>& baseStates,
|
|
||||||
RuntimeLiveState& liveState,
|
|
||||||
bool allowCommit,
|
|
||||||
double smoothing,
|
|
||||||
std::vector<RuntimeLiveOscCommitRequest>* commitRequests) const;
|
|
||||||
|
|
||||||
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
|
||||||
RenderStateComposer mRenderStateComposer;
|
|
||||||
std::vector<RuntimeRenderState> mCachedLayerRenderStates;
|
|
||||||
uint64_t mCachedRenderStateVersion = 0;
|
|
||||||
uint64_t mCachedParameterStateVersion = 0;
|
|
||||||
unsigned mCachedRenderStateWidth = 0;
|
|
||||||
unsigned mCachedRenderStateHeight = 0;
|
|
||||||
};
|
|
||||||
@@ -1,378 +0,0 @@
|
|||||||
#include "RuntimeUpdateController.h"
|
|
||||||
|
|
||||||
#include "RenderEngine.h"
|
|
||||||
#include "RuntimeEventDispatcher.h"
|
|
||||||
#include "RuntimeServices.h"
|
|
||||||
#include "RuntimeStore.h"
|
|
||||||
#include "ShaderBuildQueue.h"
|
|
||||||
#include "VideoBackend.h"
|
|
||||||
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorRenderResetScope ToRuntimeCoordinatorRenderResetScope(RuntimeEventRenderResetScope scope)
|
|
||||||
{
|
|
||||||
switch (scope)
|
|
||||||
{
|
|
||||||
case RuntimeEventRenderResetScope::TemporalHistoryOnly:
|
|
||||||
return RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly;
|
|
||||||
case RuntimeEventRenderResetScope::TemporalHistoryAndFeedback:
|
|
||||||
return RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback;
|
|
||||||
case RuntimeEventRenderResetScope::None:
|
|
||||||
default:
|
|
||||||
return RuntimeCoordinatorRenderResetScope::None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeUpdateController::RuntimeUpdateController(
|
|
||||||
RuntimeStore& runtimeStore,
|
|
||||||
RuntimeCoordinator& runtimeCoordinator,
|
|
||||||
RuntimeEventDispatcher& runtimeEventDispatcher,
|
|
||||||
RuntimeServices& runtimeServices,
|
|
||||||
RenderEngine& renderEngine,
|
|
||||||
ShaderBuildQueue& shaderBuildQueue,
|
|
||||||
VideoBackend& videoBackend) :
|
|
||||||
mRuntimeStore(runtimeStore),
|
|
||||||
mRuntimeCoordinator(runtimeCoordinator),
|
|
||||||
mRuntimeEventDispatcher(runtimeEventDispatcher),
|
|
||||||
mRuntimeServices(runtimeServices),
|
|
||||||
mRenderEngine(renderEngine),
|
|
||||||
mShaderBuildQueue(shaderBuildQueue),
|
|
||||||
mVideoBackend(videoBackend)
|
|
||||||
{
|
|
||||||
mRuntimeEventDispatcher.Subscribe(
|
|
||||||
RuntimeEventType::RuntimeStateBroadcastRequested,
|
|
||||||
[this](const RuntimeEvent& event) { HandleRuntimeStateBroadcastRequested(event); });
|
|
||||||
mRuntimeEventDispatcher.Subscribe(
|
|
||||||
RuntimeEventType::RuntimeReloadRequested,
|
|
||||||
[this](const RuntimeEvent& event) { HandleRuntimeReloadRequested(event); });
|
|
||||||
mRuntimeEventDispatcher.Subscribe(
|
|
||||||
RuntimeEventType::RuntimePersistenceRequested,
|
|
||||||
[this](const RuntimeEvent& event) { HandleRuntimePersistenceRequested(event); });
|
|
||||||
mRuntimeEventDispatcher.Subscribe(
|
|
||||||
RuntimeEventType::ShaderBuildRequested,
|
|
||||||
[this](const RuntimeEvent& event) { HandleShaderBuildRequested(event); });
|
|
||||||
mRuntimeEventDispatcher.Subscribe(
|
|
||||||
RuntimeEventType::ShaderBuildPrepared,
|
|
||||||
[this](const RuntimeEvent& event) { HandleShaderBuildPrepared(event); });
|
|
||||||
mRuntimeEventDispatcher.Subscribe(
|
|
||||||
RuntimeEventType::ShaderBuildFailed,
|
|
||||||
[this](const RuntimeEvent& event) { HandleShaderBuildFailed(event); });
|
|
||||||
mRuntimeEventDispatcher.Subscribe(
|
|
||||||
RuntimeEventType::CompileStatusChanged,
|
|
||||||
[this](const RuntimeEvent& event) { HandleCompileStatusChanged(event); });
|
|
||||||
mRuntimeEventDispatcher.Subscribe(
|
|
||||||
RuntimeEventType::RenderResetRequested,
|
|
||||||
[this](const RuntimeEvent& event) { HandleRenderResetRequested(event); });
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeUpdateController::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error)
|
|
||||||
{
|
|
||||||
if (!result.accepted)
|
|
||||||
{
|
|
||||||
if (error)
|
|
||||||
*error = result.errorMessage;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.compileStatusChanged)
|
|
||||||
{
|
|
||||||
mRuntimeStore.SetCompileStatus(result.compileStatusSucceeded, result.compileStatusMessage);
|
|
||||||
++mPendingCoordinatorCompileStatusEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.clearReloadRequest)
|
|
||||||
mRuntimeStore.ClearReloadRequest();
|
|
||||||
|
|
||||||
mRuntimeCoordinator.ApplyCommittedStateMode(result.committedStateMode);
|
|
||||||
|
|
||||||
switch (result.transientOscInvalidation)
|
|
||||||
{
|
|
||||||
case RuntimeCoordinatorTransientOscInvalidation::All:
|
|
||||||
mRenderEngine.ClearOscOverlayState();
|
|
||||||
mRuntimeServices.ClearOscState();
|
|
||||||
break;
|
|
||||||
case RuntimeCoordinatorTransientOscInvalidation::Layer:
|
|
||||||
mRenderEngine.ClearOscOverlayStateForLayerKey(result.transientOscLayerKey);
|
|
||||||
mRuntimeServices.ClearOscStateForLayerKey(result.transientOscLayerKey);
|
|
||||||
break;
|
|
||||||
case RuntimeCoordinatorTransientOscInvalidation::None:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
mRenderEngine.ApplyRuntimeCoordinatorRenderReset(result.renderResetScope);
|
|
||||||
if (result.renderResetScope != RuntimeCoordinatorRenderResetScope::None)
|
|
||||||
++mPendingCoordinatorRenderResetEvents;
|
|
||||||
|
|
||||||
if (result.shaderBuildRequested)
|
|
||||||
{
|
|
||||||
RequestShaderBuild();
|
|
||||||
++mPendingCoordinatorShaderBuildEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.runtimeStateBroadcastRequired)
|
|
||||||
BroadcastRuntimeState();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeUpdateController::ProcessRuntimeWork()
|
|
||||||
{
|
|
||||||
DispatchRuntimeEvents();
|
|
||||||
|
|
||||||
return ConsumeReadyShaderBuild(0, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeUpdateController::RequestShaderBuild()
|
|
||||||
{
|
|
||||||
mShaderBuildQueue.RequestBuild(mVideoBackend.InputFrameWidth(), mVideoBackend.InputFrameHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeUpdateController::BroadcastRuntimeState()
|
|
||||||
{
|
|
||||||
RuntimeStateBroadcastRequestedEvent event;
|
|
||||||
event.reason = "runtime-state-changed";
|
|
||||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeUpdateController"))
|
|
||||||
{
|
|
||||||
mRuntimeServices.BroadcastState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchRuntimeEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeUpdateController::HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event)
|
|
||||||
{
|
|
||||||
if (event.source == "ControlServices")
|
|
||||||
return;
|
|
||||||
|
|
||||||
mRuntimeServices.BroadcastState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeUpdateController::HandleRuntimeReloadRequested(const RuntimeEvent& event)
|
|
||||||
{
|
|
||||||
const RuntimeReloadRequestedEvent* payload = std::get_if<RuntimeReloadRequestedEvent>(&event.payload);
|
|
||||||
if (!payload)
|
|
||||||
return;
|
|
||||||
|
|
||||||
mRuntimeStore.ClearReloadRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeUpdateController::HandleRuntimePersistenceRequested(const RuntimeEvent& event)
|
|
||||||
{
|
|
||||||
const RuntimePersistenceRequestedEvent* payload = std::get_if<RuntimePersistenceRequestedEvent>(&event.payload);
|
|
||||||
if (!payload)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::string error;
|
|
||||||
mRuntimeStore.RequestPersistence(payload->request, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeUpdateController::HandleShaderBuildRequested(const RuntimeEvent& event)
|
|
||||||
{
|
|
||||||
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
|
|
||||||
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Requested)
|
|
||||||
return;
|
|
||||||
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorShaderBuildEvents))
|
|
||||||
return;
|
|
||||||
|
|
||||||
RequestShaderBuild();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeUpdateController::HandleShaderBuildPrepared(const RuntimeEvent& event)
|
|
||||||
{
|
|
||||||
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
|
|
||||||
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Prepared)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ConsumeReadyShaderBuild(payload->generation, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeUpdateController::HandleShaderBuildFailed(const RuntimeEvent& event)
|
|
||||||
{
|
|
||||||
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
|
|
||||||
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Failed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ConsumeReadyShaderBuild(payload->generation, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeUpdateController::HandleCompileStatusChanged(const RuntimeEvent& event)
|
|
||||||
{
|
|
||||||
const CompileStatusChangedEvent* payload = std::get_if<CompileStatusChangedEvent>(&event.payload);
|
|
||||||
if (!payload)
|
|
||||||
return;
|
|
||||||
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorCompileStatusEvents))
|
|
||||||
return;
|
|
||||||
|
|
||||||
mRuntimeStore.SetCompileStatus(payload->succeeded, payload->message);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeUpdateController::HandleRenderResetRequested(const RuntimeEvent& event)
|
|
||||||
{
|
|
||||||
const RenderResetEvent* payload = std::get_if<RenderResetEvent>(&event.payload);
|
|
||||||
if (!payload || payload->applied)
|
|
||||||
return;
|
|
||||||
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorRenderResetEvents))
|
|
||||||
return;
|
|
||||||
|
|
||||||
mRenderEngine.ApplyRuntimeCoordinatorRenderReset(ToRuntimeCoordinatorRenderResetScope(payload->scope));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeUpdateController::ConsumeReadyShaderBuild(uint64_t expectedGeneration, bool publishPreparedEvent, bool publishFailureEvent)
|
|
||||||
{
|
|
||||||
PreparedShaderBuild readyBuild;
|
|
||||||
const bool consumed = expectedGeneration == 0
|
|
||||||
? mShaderBuildQueue.TryConsumeReadyBuild(readyBuild)
|
|
||||||
: mShaderBuildQueue.TryConsumeReadyBuild(expectedGeneration, readyBuild);
|
|
||||||
if (!consumed)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
const unsigned inputWidth = mVideoBackend.InputFrameWidth();
|
|
||||||
const unsigned inputHeight = mVideoBackend.InputFrameHeight();
|
|
||||||
if (!readyBuild.succeeded)
|
|
||||||
{
|
|
||||||
if (publishFailureEvent)
|
|
||||||
{
|
|
||||||
PublishShaderBuildLifecycleEvent(
|
|
||||||
RuntimeEventShaderBuildPhase::Failed,
|
|
||||||
readyBuild.generation,
|
|
||||||
inputWidth,
|
|
||||||
inputHeight,
|
|
||||||
false,
|
|
||||||
readyBuild.message);
|
|
||||||
DispatchRuntimeEvents();
|
|
||||||
}
|
|
||||||
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(readyBuild.message));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (publishPreparedEvent)
|
|
||||||
{
|
|
||||||
PublishShaderBuildLifecycleEvent(
|
|
||||||
RuntimeEventShaderBuildPhase::Prepared,
|
|
||||||
readyBuild.generation,
|
|
||||||
inputWidth,
|
|
||||||
inputHeight,
|
|
||||||
true,
|
|
||||||
readyBuild.message);
|
|
||||||
DispatchRuntimeEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
char compilerErrorMessage[1024] = {};
|
|
||||||
if (!mRenderEngine.ApplyPreparedShaderBuild(
|
|
||||||
readyBuild,
|
|
||||||
inputWidth,
|
|
||||||
inputHeight,
|
|
||||||
mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild(),
|
|
||||||
sizeof(compilerErrorMessage),
|
|
||||||
compilerErrorMessage))
|
|
||||||
{
|
|
||||||
const std::string errorMessage = compilerErrorMessage;
|
|
||||||
if (publishFailureEvent)
|
|
||||||
{
|
|
||||||
PublishShaderBuildLifecycleEvent(
|
|
||||||
RuntimeEventShaderBuildPhase::Failed,
|
|
||||||
readyBuild.generation,
|
|
||||||
inputWidth,
|
|
||||||
inputHeight,
|
|
||||||
false,
|
|
||||||
errorMessage);
|
|
||||||
DispatchRuntimeEvents();
|
|
||||||
}
|
|
||||||
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(errorMessage));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishShaderBuildLifecycleEvent(
|
|
||||||
RuntimeEventShaderBuildPhase::Applied,
|
|
||||||
readyBuild.generation,
|
|
||||||
inputWidth,
|
|
||||||
inputHeight,
|
|
||||||
true,
|
|
||||||
"Shader layers applied successfully.");
|
|
||||||
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildSuccess());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeUpdateController::PublishShaderBuildLifecycleEvent(
|
|
||||||
RuntimeEventShaderBuildPhase phase,
|
|
||||||
uint64_t generation,
|
|
||||||
unsigned inputWidth,
|
|
||||||
unsigned inputHeight,
|
|
||||||
bool succeeded,
|
|
||||||
const std::string& message)
|
|
||||||
{
|
|
||||||
ShaderBuildEvent event;
|
|
||||||
event.phase = phase;
|
|
||||||
event.generation = generation;
|
|
||||||
event.inputWidth = inputWidth;
|
|
||||||
event.inputHeight = inputHeight;
|
|
||||||
event.preserveFeedbackState = mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild();
|
|
||||||
event.succeeded = succeeded;
|
|
||||||
event.message = message;
|
|
||||||
mRuntimeEventDispatcher.PublishPayload(event, "RuntimeUpdateController");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeUpdateController::ShouldSuppressCoordinatorFollowUp(const RuntimeEvent& event, std::size_t& pendingSuppressions)
|
|
||||||
{
|
|
||||||
if (event.source != "RuntimeCoordinator")
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (pendingSuppressions > 0)
|
|
||||||
--pendingSuppressions;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeEventDispatchResult RuntimeUpdateController::DispatchRuntimeEvents(std::size_t maxEvents)
|
|
||||||
{
|
|
||||||
RuntimeEventDispatchResult result = mRuntimeEventDispatcher.DispatchPending(maxEvents);
|
|
||||||
const RuntimeEventQueueMetrics queueMetrics = mRuntimeEventDispatcher.GetQueueMetrics();
|
|
||||||
HealthTelemetry& telemetry = mRuntimeStore.GetHealthTelemetry();
|
|
||||||
telemetry.TryRecordRuntimeEventDispatchStats(
|
|
||||||
result.dispatchedEvents,
|
|
||||||
result.handlerInvocations,
|
|
||||||
result.handlerFailures,
|
|
||||||
result.dispatchDurationMilliseconds);
|
|
||||||
telemetry.TryRecordRuntimeEventQueueMetrics(
|
|
||||||
"runtime-events",
|
|
||||||
queueMetrics.depth,
|
|
||||||
queueMetrics.capacity,
|
|
||||||
static_cast<uint64_t>(queueMetrics.droppedCount),
|
|
||||||
queueMetrics.oldestEventAgeMilliseconds);
|
|
||||||
PublishRuntimeEventHealthObservations(result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeUpdateController::PublishRuntimeEventHealthObservations(const RuntimeEventDispatchResult& result)
|
|
||||||
{
|
|
||||||
const RuntimeEventQueueMetrics queueMetrics = mRuntimeEventDispatcher.GetQueueMetrics();
|
|
||||||
if (queueMetrics.depth != mLastReportedRuntimeEventQueueDepth ||
|
|
||||||
queueMetrics.droppedCount != mLastReportedRuntimeEventDroppedCount ||
|
|
||||||
queueMetrics.coalescedCount != mLastReportedRuntimeEventCoalescedCount)
|
|
||||||
{
|
|
||||||
QueueDepthChangedEvent queueDepth;
|
|
||||||
queueDepth.queueName = "runtime-events";
|
|
||||||
queueDepth.depth = queueMetrics.depth;
|
|
||||||
queueDepth.capacity = queueMetrics.capacity;
|
|
||||||
queueDepth.droppedCount = queueMetrics.droppedCount;
|
|
||||||
queueDepth.coalescedCount = queueMetrics.coalescedCount;
|
|
||||||
mRuntimeEventDispatcher.PublishPayload(queueDepth, "HealthTelemetry");
|
|
||||||
mLastReportedRuntimeEventQueueDepth = queueMetrics.depth;
|
|
||||||
mLastReportedRuntimeEventDroppedCount = queueMetrics.droppedCount;
|
|
||||||
mLastReportedRuntimeEventCoalescedCount = queueMetrics.coalescedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.handlerInvocations == 0 && result.handlerFailures == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
TimingSampleRecordedEvent timing;
|
|
||||||
timing.subsystem = "RuntimeEventDispatcher";
|
|
||||||
timing.metric = "dispatchDuration";
|
|
||||||
timing.value = result.dispatchDurationMilliseconds;
|
|
||||||
timing.unit = "ms";
|
|
||||||
mRuntimeEventDispatcher.PublishPayload(timing, "HealthTelemetry");
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeCoordinator.h"
|
|
||||||
#include "RuntimeEventPayloads.h"
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class RenderEngine;
|
|
||||||
struct RuntimeEvent;
|
|
||||||
struct RuntimeEventDispatchResult;
|
|
||||||
class RuntimeEventDispatcher;
|
|
||||||
class RuntimeServices;
|
|
||||||
class RuntimeStore;
|
|
||||||
class ShaderBuildQueue;
|
|
||||||
class VideoBackend;
|
|
||||||
|
|
||||||
class RuntimeUpdateController
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
RuntimeUpdateController(
|
|
||||||
RuntimeStore& runtimeStore,
|
|
||||||
RuntimeCoordinator& runtimeCoordinator,
|
|
||||||
RuntimeEventDispatcher& runtimeEventDispatcher,
|
|
||||||
RuntimeServices& runtimeServices,
|
|
||||||
RenderEngine& renderEngine,
|
|
||||||
ShaderBuildQueue& shaderBuildQueue,
|
|
||||||
VideoBackend& videoBackend);
|
|
||||||
|
|
||||||
bool ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error = nullptr);
|
|
||||||
bool ProcessRuntimeWork();
|
|
||||||
void RequestShaderBuild();
|
|
||||||
void BroadcastRuntimeState();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event);
|
|
||||||
void HandleRuntimeReloadRequested(const RuntimeEvent& event);
|
|
||||||
void HandleRuntimePersistenceRequested(const RuntimeEvent& event);
|
|
||||||
void HandleShaderBuildRequested(const RuntimeEvent& event);
|
|
||||||
void HandleShaderBuildPrepared(const RuntimeEvent& event);
|
|
||||||
void HandleShaderBuildFailed(const RuntimeEvent& event);
|
|
||||||
void HandleCompileStatusChanged(const RuntimeEvent& event);
|
|
||||||
void HandleRenderResetRequested(const RuntimeEvent& event);
|
|
||||||
bool ConsumeReadyShaderBuild(uint64_t expectedGeneration, bool publishPreparedEvent, bool publishFailureEvent);
|
|
||||||
void PublishShaderBuildLifecycleEvent(
|
|
||||||
RuntimeEventShaderBuildPhase phase,
|
|
||||||
uint64_t generation,
|
|
||||||
unsigned inputWidth,
|
|
||||||
unsigned inputHeight,
|
|
||||||
bool succeeded,
|
|
||||||
const std::string& message);
|
|
||||||
bool ShouldSuppressCoordinatorFollowUp(const RuntimeEvent& event, std::size_t& pendingSuppressions);
|
|
||||||
RuntimeEventDispatchResult DispatchRuntimeEvents(std::size_t maxEvents = 0);
|
|
||||||
void PublishRuntimeEventHealthObservations(const RuntimeEventDispatchResult& result);
|
|
||||||
|
|
||||||
RuntimeStore& mRuntimeStore;
|
|
||||||
RuntimeCoordinator& mRuntimeCoordinator;
|
|
||||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
|
||||||
RuntimeServices& mRuntimeServices;
|
|
||||||
RenderEngine& mRenderEngine;
|
|
||||||
ShaderBuildQueue& mShaderBuildQueue;
|
|
||||||
VideoBackend& mVideoBackend;
|
|
||||||
std::size_t mPendingCoordinatorShaderBuildEvents = 0;
|
|
||||||
std::size_t mPendingCoordinatorCompileStatusEvents = 0;
|
|
||||||
std::size_t mPendingCoordinatorRenderResetEvents = 0;
|
|
||||||
std::size_t mLastReportedRuntimeEventQueueDepth = static_cast<std::size_t>(-1);
|
|
||||||
std::size_t mLastReportedRuntimeEventDroppedCount = static_cast<std::size_t>(-1);
|
|
||||||
std::size_t mLastReportedRuntimeEventCoalescedCount = static_cast<std::size_t>(-1);
|
|
||||||
};
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
#include "OpenGLRenderPass.h"
|
|
||||||
|
|
||||||
#include "GlRenderConstants.h"
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
OpenGLRenderPass::OpenGLRenderPass(OpenGLRenderer& renderer) :
|
|
||||||
mRenderer(renderer)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPass::Render(
|
|
||||||
bool hasInputSource,
|
|
||||||
const std::vector<RuntimeRenderState>& layerStates,
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
unsigned captureTextureWidth,
|
|
||||||
VideoIOPixelFormat inputPixelFormat,
|
|
||||||
unsigned historyCap,
|
|
||||||
const TextBindingUpdater& updateTextBinding,
|
|
||||||
const GlobalParamsUpdater& updateGlobalParams)
|
|
||||||
{
|
|
||||||
glDisable(GL_SCISSOR_TEST);
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
if (hasInputSource)
|
|
||||||
{
|
|
||||||
RenderDecodePass(inputFrameWidth, inputFrameHeight, captureTextureWidth, inputPixelFormat);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
|
|
||||||
glViewport(0, 0, inputFrameWidth, inputFrameHeight);
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<LayerProgram>& layerPrograms = mRenderer.LayerPrograms();
|
|
||||||
if (layerStates.empty() || layerPrograms.empty())
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
|
||||||
glBlitFramebuffer(0, 0, inputFrameWidth, inputFrameHeight, 0, 0, inputFrameWidth, inputFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const std::vector<RenderPassDescriptor>& passes = BuildLayerPassDescriptors(layerStates, layerPrograms);
|
|
||||||
for (const RenderPassDescriptor& pass : passes)
|
|
||||||
{
|
|
||||||
RenderLayerPass(
|
|
||||||
pass,
|
|
||||||
inputFrameWidth,
|
|
||||||
inputFrameHeight,
|
|
||||||
historyCap,
|
|
||||||
updateTextBinding,
|
|
||||||
updateGlobalParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mRenderer.TemporalHistory().PushSourceFramebuffer(mRenderer.DecodeFramebuffer(), inputFrameWidth, inputFrameHeight);
|
|
||||||
mRenderer.FeedbackBuffers().FinalizeFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPass::RenderDecodePass(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat)
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
|
|
||||||
glViewport(0, 0, inputFrameWidth, inputFrameHeight);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
glActiveTexture(GL_TEXTURE0 + kPackedVideoTextureUnit);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
|
|
||||||
glBindVertexArray(mRenderer.FullscreenVertexArray());
|
|
||||||
glUseProgram(mRenderer.DecodeProgram());
|
|
||||||
|
|
||||||
const GLint packedResolutionLocation = mRenderer.DecodePackedResolutionLocation();
|
|
||||||
const GLint decodedResolutionLocation = mRenderer.DecodeDecodedResolutionLocation();
|
|
||||||
const GLint inputPixelFormatLocation = mRenderer.DecodeInputPixelFormatLocation();
|
|
||||||
if (packedResolutionLocation >= 0)
|
|
||||||
glUniform2f(packedResolutionLocation, static_cast<float>(captureTextureWidth), static_cast<float>(inputFrameHeight));
|
|
||||||
if (decodedResolutionLocation >= 0)
|
|
||||||
glUniform2f(decodedResolutionLocation, static_cast<float>(inputFrameWidth), static_cast<float>(inputFrameHeight));
|
|
||||||
if (inputPixelFormatLocation >= 0)
|
|
||||||
glUniform1i(inputPixelFormatLocation, inputPixelFormat == VideoIOPixelFormat::V210 ? 1 : 0);
|
|
||||||
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
|
||||||
|
|
||||||
glUseProgram(0);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<RenderPassDescriptor> OpenGLRenderPass::BuildLayerPassDescriptors(
|
|
||||||
const std::vector<RuntimeRenderState>& layerStates,
|
|
||||||
std::vector<LayerProgram>& layerPrograms) const
|
|
||||||
{
|
|
||||||
// Flatten the layer stack into concrete GL passes. A layer may now contain
|
|
||||||
// several shader passes, but the outer stack still sees one visible output
|
|
||||||
// per layer.
|
|
||||||
std::vector<RenderPassDescriptor>& passes = mPassScratch;
|
|
||||||
passes.clear();
|
|
||||||
const std::size_t passCount = layerStates.size() < layerPrograms.size() ? layerStates.size() : layerPrograms.size();
|
|
||||||
std::size_t descriptorCount = 0;
|
|
||||||
for (std::size_t index = 0; index < passCount; ++index)
|
|
||||||
descriptorCount += layerPrograms[index].passes.size();
|
|
||||||
passes.reserve(descriptorCount);
|
|
||||||
|
|
||||||
GLuint sourceTexture = mRenderer.DecodedTexture();
|
|
||||||
GLuint sourceFramebuffer = mRenderer.DecodeFramebuffer();
|
|
||||||
for (std::size_t index = 0; index < passCount; ++index)
|
|
||||||
{
|
|
||||||
const RuntimeRenderState& state = layerStates[index];
|
|
||||||
LayerProgram& layerProgram = layerPrograms[index];
|
|
||||||
if (layerProgram.passes.empty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Preserve the original two-target layer ping-pong. Intermediate passes
|
|
||||||
// inside this layer are routed through pooled temporary targets instead.
|
|
||||||
const std::size_t remaining = layerStates.size() - index;
|
|
||||||
const bool writeToMain = (remaining % 2) == 1;
|
|
||||||
const GLuint layerOutputTexture = writeToMain ? mRenderer.CompositeTexture() : mRenderer.LayerTempTexture();
|
|
||||||
const GLuint layerOutputFramebuffer = writeToMain ? mRenderer.CompositeFramebuffer() : mRenderer.LayerTempFramebuffer();
|
|
||||||
const RenderPassOutputTarget layerOutputTarget = writeToMain ? RenderPassOutputTarget::Composite : RenderPassOutputTarget::LayerTemp;
|
|
||||||
|
|
||||||
const GLuint layerInputTexture = sourceTexture;
|
|
||||||
const GLuint layerInputFramebuffer = sourceFramebuffer;
|
|
||||||
GLuint previousPassTexture = layerInputTexture;
|
|
||||||
GLuint previousPassFramebuffer = layerInputFramebuffer;
|
|
||||||
std::map<std::string, std::pair<GLuint, GLuint>> namedOutputs;
|
|
||||||
std::size_t temporaryTargetIndex = 0;
|
|
||||||
|
|
||||||
for (std::size_t passIndex = 0; passIndex < layerProgram.passes.size(); ++passIndex)
|
|
||||||
{
|
|
||||||
PassProgram& passProgram = layerProgram.passes[passIndex];
|
|
||||||
const bool lastPassForLayer = passIndex + 1 == layerProgram.passes.size();
|
|
||||||
const std::string outputName = passProgram.outputName.empty() ? passProgram.passId : passProgram.outputName;
|
|
||||||
const bool writesLayerOutput = outputName == "layerOutput" || lastPassForLayer;
|
|
||||||
|
|
||||||
GLuint passSourceTexture = previousPassTexture;
|
|
||||||
GLuint passSourceFramebuffer = previousPassFramebuffer;
|
|
||||||
if (!passProgram.inputNames.empty())
|
|
||||||
{
|
|
||||||
// v1 multipass uses the first declared input as gVideoInput.
|
|
||||||
// Later inputs are parsed for forward compatibility.
|
|
||||||
const std::string& inputName = passProgram.inputNames.front();
|
|
||||||
if (inputName == "layerInput")
|
|
||||||
{
|
|
||||||
passSourceTexture = layerInputTexture;
|
|
||||||
passSourceFramebuffer = layerInputFramebuffer;
|
|
||||||
}
|
|
||||||
else if (inputName == "previousPass")
|
|
||||||
{
|
|
||||||
passSourceTexture = previousPassTexture;
|
|
||||||
passSourceFramebuffer = previousPassFramebuffer;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto namedOutputIt = namedOutputs.find(inputName);
|
|
||||||
if (namedOutputIt != namedOutputs.end())
|
|
||||||
{
|
|
||||||
passSourceTexture = namedOutputIt->second.first;
|
|
||||||
passSourceFramebuffer = namedOutputIt->second.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint passDestinationTexture = layerOutputTexture;
|
|
||||||
GLuint passDestinationFramebuffer = layerOutputFramebuffer;
|
|
||||||
RenderPassOutputTarget outputTarget = layerOutputTarget;
|
|
||||||
if (!writesLayerOutput)
|
|
||||||
{
|
|
||||||
// Temporary targets are reserved when the shader stack is
|
|
||||||
// committed, avoiding texture allocation during playback.
|
|
||||||
if (temporaryTargetIndex < mRenderer.TemporaryRenderTargetCount())
|
|
||||||
{
|
|
||||||
const RenderTarget& temporaryTarget = mRenderer.TemporaryRenderTarget(temporaryTargetIndex);
|
|
||||||
++temporaryTargetIndex;
|
|
||||||
passDestinationTexture = temporaryTarget.texture;
|
|
||||||
passDestinationFramebuffer = temporaryTarget.framebuffer;
|
|
||||||
outputTarget = RenderPassOutputTarget::Temporary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderPassDescriptor pass;
|
|
||||||
pass.kind = RenderPassKind::LayerEffect;
|
|
||||||
pass.outputTarget = outputTarget;
|
|
||||||
pass.passIndex = passes.size();
|
|
||||||
pass.passId = passProgram.passId;
|
|
||||||
pass.layerId = state.layerId;
|
|
||||||
pass.shaderId = state.shaderId;
|
|
||||||
pass.layerInputTexture = layerInputTexture;
|
|
||||||
pass.sourceTexture = passSourceTexture;
|
|
||||||
pass.sourceFramebuffer = passIndex == 0 ? layerInputFramebuffer : passSourceFramebuffer;
|
|
||||||
pass.destinationTexture = passDestinationTexture;
|
|
||||||
pass.destinationFramebuffer = passDestinationFramebuffer;
|
|
||||||
pass.layerProgram = &layerProgram;
|
|
||||||
pass.passProgram = &passProgram;
|
|
||||||
pass.layerState = &state;
|
|
||||||
pass.capturePreLayerHistory = passIndex == 0 && state.temporalHistorySource == TemporalHistorySource::PreLayerInput;
|
|
||||||
pass.captureFeedbackWrite = state.feedback.enabled && passProgram.passId == state.feedback.writePassId;
|
|
||||||
passes.push_back(pass);
|
|
||||||
|
|
||||||
// A later pass can reference either the explicit output name or the
|
|
||||||
// pass id, which keeps small manifests pleasant to write.
|
|
||||||
namedOutputs[outputName] = std::make_pair(passDestinationTexture, passDestinationFramebuffer);
|
|
||||||
namedOutputs[passProgram.passId] = std::make_pair(passDestinationTexture, passDestinationFramebuffer);
|
|
||||||
previousPassTexture = passDestinationTexture;
|
|
||||||
previousPassFramebuffer = passDestinationFramebuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceTexture = layerOutputTexture;
|
|
||||||
sourceFramebuffer = layerOutputFramebuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
return passes;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPass::RenderLayerPass(
|
|
||||||
const RenderPassDescriptor& pass,
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
unsigned historyCap,
|
|
||||||
const TextBindingUpdater& updateTextBinding,
|
|
||||||
const GlobalParamsUpdater& updateGlobalParams)
|
|
||||||
{
|
|
||||||
if (pass.passProgram == nullptr || pass.layerState == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
RenderShaderProgram(
|
|
||||||
pass.layerInputTexture,
|
|
||||||
pass.sourceTexture,
|
|
||||||
pass.destinationFramebuffer,
|
|
||||||
*pass.passProgram,
|
|
||||||
*pass.layerState,
|
|
||||||
inputFrameWidth,
|
|
||||||
inputFrameHeight,
|
|
||||||
historyCap,
|
|
||||||
updateTextBinding,
|
|
||||||
updateGlobalParams);
|
|
||||||
|
|
||||||
if (pass.capturePreLayerHistory)
|
|
||||||
mRenderer.TemporalHistory().PushPreLayerFramebuffer(pass.layerId, pass.sourceFramebuffer, inputFrameWidth, inputFrameHeight);
|
|
||||||
if (pass.captureFeedbackWrite)
|
|
||||||
mRenderer.FeedbackBuffers().CaptureFeedbackFramebuffer(pass.layerId, pass.destinationFramebuffer, inputFrameWidth, inputFrameHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPass::RenderShaderProgram(
|
|
||||||
GLuint layerInputTexture,
|
|
||||||
GLuint sourceTexture,
|
|
||||||
GLuint destinationFrameBuffer,
|
|
||||||
PassProgram& passProgram,
|
|
||||||
const RuntimeRenderState& state,
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
unsigned historyCap,
|
|
||||||
const TextBindingUpdater& updateTextBinding,
|
|
||||||
const GlobalParamsUpdater& updateGlobalParams)
|
|
||||||
{
|
|
||||||
for (LayerProgram::TextBinding& textBinding : passProgram.textBindings)
|
|
||||||
{
|
|
||||||
std::string textError;
|
|
||||||
if (!updateTextBinding(state, textBinding, textError))
|
|
||||||
OutputDebugStringA((textError + "\n").c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, destinationFrameBuffer);
|
|
||||||
glViewport(0, 0, inputFrameWidth, inputFrameHeight);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
||||||
const std::vector<GLuint> sourceHistoryTextures = mRenderer.TemporalHistory().ResolveSourceHistoryTextures(sourceTexture, state.isTemporal ? historyCap : 0);
|
|
||||||
const std::vector<GLuint> temporalHistoryTextures = mRenderer.TemporalHistory().ResolveTemporalHistoryTextures(state, sourceTexture, state.isTemporal ? historyCap : 0);
|
|
||||||
const GLuint feedbackTexture = mRenderer.FeedbackBuffers().ResolveReadTexture(state);
|
|
||||||
const ShaderTextureBindings::RuntimeTextureBindingPlan texturePlan =
|
|
||||||
mTextureBindings.BuildLayerRuntimeBindingPlan(passProgram, sourceTexture, layerInputTexture, state, feedbackTexture, sourceHistoryTextures, temporalHistoryTextures);
|
|
||||||
mTextureBindings.BindRuntimeTexturePlan(texturePlan);
|
|
||||||
glBindVertexArray(mRenderer.FullscreenVertexArray());
|
|
||||||
glUseProgram(passProgram.program);
|
|
||||||
// The UBO is shared by every pass in a layer; texture routing is what
|
|
||||||
// changes from pass to pass.
|
|
||||||
updateGlobalParams(
|
|
||||||
state,
|
|
||||||
mRenderer.TemporalHistory().SourceAvailableCount(),
|
|
||||||
mRenderer.TemporalHistory().AvailableCountForLayer(state.layerId),
|
|
||||||
mRenderer.FeedbackBuffers().FeedbackAvailable(state));
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
|
||||||
glUseProgram(0);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
mTextureBindings.UnbindRuntimeTexturePlan(texturePlan);
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "OpenGLRenderer.h"
|
|
||||||
#include "RenderPassDescriptor.h"
|
|
||||||
#include "ShaderTextureBindings.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
#include "VideoIOFormat.h"
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class OpenGLRenderPass
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
|
||||||
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
|
|
||||||
using TextBindingUpdater = std::function<bool(const RuntimeRenderState&, LayerProgram::TextBinding&, std::string&)>;
|
|
||||||
using GlobalParamsUpdater = std::function<bool(const RuntimeRenderState&, unsigned, unsigned, bool)>;
|
|
||||||
|
|
||||||
explicit OpenGLRenderPass(OpenGLRenderer& renderer);
|
|
||||||
|
|
||||||
void Render(
|
|
||||||
bool hasInputSource,
|
|
||||||
const std::vector<RuntimeRenderState>& layerStates,
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
unsigned captureTextureWidth,
|
|
||||||
VideoIOPixelFormat inputPixelFormat,
|
|
||||||
unsigned historyCap,
|
|
||||||
const TextBindingUpdater& updateTextBinding,
|
|
||||||
const GlobalParamsUpdater& updateGlobalParams);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void RenderDecodePass(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat);
|
|
||||||
std::vector<RenderPassDescriptor> BuildLayerPassDescriptors(
|
|
||||||
const std::vector<RuntimeRenderState>& layerStates,
|
|
||||||
std::vector<LayerProgram>& layerPrograms) const;
|
|
||||||
void RenderLayerPass(
|
|
||||||
const RenderPassDescriptor& pass,
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
unsigned historyCap,
|
|
||||||
const TextBindingUpdater& updateTextBinding,
|
|
||||||
const GlobalParamsUpdater& updateGlobalParams);
|
|
||||||
void RenderShaderProgram(
|
|
||||||
GLuint layerInputTexture,
|
|
||||||
GLuint sourceTexture,
|
|
||||||
GLuint destinationFrameBuffer,
|
|
||||||
PassProgram& passProgram,
|
|
||||||
const RuntimeRenderState& state,
|
|
||||||
unsigned inputFrameWidth,
|
|
||||||
unsigned inputFrameHeight,
|
|
||||||
unsigned historyCap,
|
|
||||||
const TextBindingUpdater& updateTextBinding,
|
|
||||||
const GlobalParamsUpdater& updateGlobalParams);
|
|
||||||
|
|
||||||
OpenGLRenderer& mRenderer;
|
|
||||||
ShaderTextureBindings mTextureBindings;
|
|
||||||
mutable std::vector<RenderPassDescriptor> mPassScratch;
|
|
||||||
};
|
|
||||||
@@ -1,479 +0,0 @@
|
|||||||
#include "OpenGLRenderPipeline.h"
|
|
||||||
|
|
||||||
#include "HealthTelemetry.h"
|
|
||||||
#include "OpenGLRenderer.h"
|
|
||||||
#include "RuntimeSnapshotProvider.h"
|
|
||||||
#include "VideoIOFormat.h"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <gl/gl.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
OpenGLRenderPipeline::OpenGLRenderPipeline(
|
|
||||||
OpenGLRenderer& renderer,
|
|
||||||
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
|
||||||
HealthTelemetry& healthTelemetry,
|
|
||||||
RenderEffectCallback renderEffect,
|
|
||||||
OutputReadyCallback outputReady,
|
|
||||||
PaintCallback paint) :
|
|
||||||
mRenderer(renderer),
|
|
||||||
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
|
||||||
mHealthTelemetry(healthTelemetry),
|
|
||||||
mRenderEffect(renderEffect),
|
|
||||||
mOutputReady(outputReady),
|
|
||||||
mPaint(paint),
|
|
||||||
mOutputReadbackMode(ReadOutputReadbackModeFromEnvironment()),
|
|
||||||
mAsyncReadbackDepth(ReadAsyncReadbackDepthFromEnvironment())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenGLRenderPipeline::~OpenGLRenderPipeline()
|
|
||||||
{
|
|
||||||
ResetAsyncReadbackState();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLRenderPipeline::RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
|
|
||||||
{
|
|
||||||
const VideoIOState& state = context.videoState;
|
|
||||||
|
|
||||||
const auto renderStartTime = std::chrono::steady_clock::now();
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
|
||||||
mRenderEffect();
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
|
||||||
glBlitFramebuffer(0, 0, state.inputFrameSize.width, state.inputFrameSize.height, 0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
|
||||||
if (mOutputReady)
|
|
||||||
mOutputReady();
|
|
||||||
if (state.outputPixelFormat == VideoIOPixelFormat::Bgra8)
|
|
||||||
PackOutputForBgra8(state);
|
|
||||||
else if (state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10)
|
|
||||||
PackOutputFor10Bit(state);
|
|
||||||
glFlush();
|
|
||||||
|
|
||||||
const auto renderEndTime = std::chrono::steady_clock::now();
|
|
||||||
const double renderMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(renderEndTime - renderStartTime).count();
|
|
||||||
mHealthTelemetry.TryRecordPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds);
|
|
||||||
mRuntimeSnapshotProvider.AdvanceFrame();
|
|
||||||
|
|
||||||
OutputReadbackTiming readbackTiming = ReadOutputFrame(state, outputFrame);
|
|
||||||
mHealthTelemetry.TryRecordOutputRenderPipelineTiming(
|
|
||||||
renderMilliseconds,
|
|
||||||
readbackTiming.fenceWaitMilliseconds,
|
|
||||||
readbackTiming.mapMilliseconds,
|
|
||||||
readbackTiming.copyMilliseconds,
|
|
||||||
readbackTiming.cachedCopyMilliseconds,
|
|
||||||
readbackTiming.asyncQueueMilliseconds,
|
|
||||||
readbackTiming.asyncQueueBufferMilliseconds,
|
|
||||||
readbackTiming.asyncQueueSetupMilliseconds,
|
|
||||||
readbackTiming.asyncQueueReadPixelsMilliseconds,
|
|
||||||
readbackTiming.asyncQueueFenceMilliseconds,
|
|
||||||
readbackTiming.syncReadMilliseconds,
|
|
||||||
readbackTiming.asyncReadbackMissed,
|
|
||||||
readbackTiming.cachedFallbackUsed,
|
|
||||||
readbackTiming.syncFallbackUsed);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPipeline::PackOutputForBgra8(const VideoIOState& state)
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
|
||||||
glBlitFramebuffer(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
state.outputFrameSize.width,
|
|
||||||
state.outputFrameSize.height,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
state.outputFrameSize.width,
|
|
||||||
state.outputFrameSize.height,
|
|
||||||
GL_COLOR_BUFFER_BIT,
|
|
||||||
GL_NEAREST);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPipeline::PackOutputFor10Bit(const VideoIOState& state)
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
|
||||||
glViewport(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height);
|
|
||||||
glDisable(GL_SCISSOR_TEST);
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, mRenderer.OutputTexture());
|
|
||||||
glBindVertexArray(mRenderer.FullscreenVertexArray());
|
|
||||||
glUseProgram(mRenderer.OutputPackProgram());
|
|
||||||
|
|
||||||
const GLint outputResolutionLocation = mRenderer.OutputPackResolutionLocation();
|
|
||||||
const GLint activeWordsLocation = mRenderer.OutputPackActiveWordsLocation();
|
|
||||||
const GLint packFormatLocation = mRenderer.OutputPackFormatLocation();
|
|
||||||
if (outputResolutionLocation >= 0)
|
|
||||||
glUniform2f(outputResolutionLocation, static_cast<float>(state.outputFrameSize.width), static_cast<float>(state.outputFrameSize.height));
|
|
||||||
if (activeWordsLocation >= 0)
|
|
||||||
glUniform1f(activeWordsLocation, static_cast<float>(ActiveV210WordsForWidth(state.outputFrameSize.width)));
|
|
||||||
if (packFormatLocation >= 0)
|
|
||||||
glUniform1i(packFormatLocation, state.outputPixelFormat == VideoIOPixelFormat::Yuva10 ? 2 : 1);
|
|
||||||
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
|
||||||
glUseProgram(0);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLRenderPipeline::EnsureAsyncReadbackBuffers(std::size_t requiredBytes)
|
|
||||||
{
|
|
||||||
if (requiredBytes == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (mAsyncReadbackBytes == requiredBytes &&
|
|
||||||
mAsyncReadbackSlots.size() == mAsyncReadbackDepth &&
|
|
||||||
!mAsyncReadbackSlots.empty() &&
|
|
||||||
mAsyncReadbackSlots[0].pixelPackBuffer != 0)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ResetAsyncReadbackState();
|
|
||||||
mAsyncReadbackBytes = requiredBytes;
|
|
||||||
mAsyncReadbackSlots.resize(mAsyncReadbackDepth);
|
|
||||||
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
|
||||||
{
|
|
||||||
glGenBuffers(1, &slot.pixelPackBuffer);
|
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
|
|
||||||
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(requiredBytes), nullptr, GL_STREAM_READ);
|
|
||||||
slot.sizeBytes = requiredBytes;
|
|
||||||
slot.inFlight = false;
|
|
||||||
}
|
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
||||||
mAsyncReadbackWriteIndex = 0;
|
|
||||||
mAsyncReadbackReadIndex = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPipeline::ResetAsyncReadbackState()
|
|
||||||
{
|
|
||||||
FlushAsyncReadbackPipeline();
|
|
||||||
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
|
||||||
slot.sizeBytes = 0;
|
|
||||||
|
|
||||||
if (!mAsyncReadbackSlots.empty() && mAsyncReadbackSlots[0].pixelPackBuffer != 0)
|
|
||||||
{
|
|
||||||
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
|
||||||
{
|
|
||||||
if (slot.pixelPackBuffer != 0)
|
|
||||||
{
|
|
||||||
glDeleteBuffers(1, &slot.pixelPackBuffer);
|
|
||||||
slot.pixelPackBuffer = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mAsyncReadbackWriteIndex = 0;
|
|
||||||
mAsyncReadbackReadIndex = 0;
|
|
||||||
mAsyncReadbackBytes = 0;
|
|
||||||
mAsyncReadbackSlots.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPipeline::FlushAsyncReadbackPipeline()
|
|
||||||
{
|
|
||||||
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
|
|
||||||
{
|
|
||||||
if (slot.fence != nullptr)
|
|
||||||
{
|
|
||||||
glDeleteSync(slot.fence);
|
|
||||||
slot.fence = nullptr;
|
|
||||||
}
|
|
||||||
slot.inFlight = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mAsyncReadbackWriteIndex = 0;
|
|
||||||
mAsyncReadbackReadIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLRenderPipeline::QueueAsyncReadback(const VideoIOState& state, OutputReadbackTiming& timing)
|
|
||||||
{
|
|
||||||
const auto queueStartTime = std::chrono::steady_clock::now();
|
|
||||||
const bool useTenBitPackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 ||
|
|
||||||
state.outputPixelFormat == VideoIOPixelFormat::Yuva10;
|
|
||||||
const bool usePackFramebuffer = state.outputPixelFormat == VideoIOPixelFormat::Bgra8 || useTenBitPackedOutput;
|
|
||||||
const std::size_t requiredBytes = static_cast<std::size_t>(state.outputFrameRowBytes) * state.outputFrameSize.height;
|
|
||||||
const GLenum format = useTenBitPackedOutput ? GL_RGBA : GL_BGRA;
|
|
||||||
const GLenum type = useTenBitPackedOutput ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8_REV;
|
|
||||||
const GLuint framebuffer = usePackFramebuffer ? mRenderer.OutputPackFramebuffer() : mRenderer.OutputFramebuffer();
|
|
||||||
const GLsizei readWidth = static_cast<GLsizei>(useTenBitPackedOutput ? state.outputPackTextureWidth : state.outputFrameSize.width);
|
|
||||||
const GLsizei readHeight = static_cast<GLsizei>(state.outputFrameSize.height);
|
|
||||||
|
|
||||||
const auto finishTiming = [&timing, queueStartTime]() {
|
|
||||||
const auto queueEndTime = std::chrono::steady_clock::now();
|
|
||||||
timing.asyncQueueMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(queueEndTime - queueStartTime).count();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (requiredBytes == 0)
|
|
||||||
{
|
|
||||||
finishTiming();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mAsyncReadbackBytes != requiredBytes
|
|
||||||
|| mAsyncReadbackFormat != format
|
|
||||||
|| mAsyncReadbackType != type
|
|
||||||
|| mAsyncReadbackFramebuffer != framebuffer)
|
|
||||||
{
|
|
||||||
mAsyncReadbackFormat = format;
|
|
||||||
mAsyncReadbackType = type;
|
|
||||||
mAsyncReadbackFramebuffer = framebuffer;
|
|
||||||
if (!EnsureAsyncReadbackBuffers(requiredBytes))
|
|
||||||
{
|
|
||||||
finishTiming();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mAsyncReadbackSlots.empty())
|
|
||||||
{
|
|
||||||
finishTiming();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncReadbackSlot& slot = mAsyncReadbackSlots[mAsyncReadbackWriteIndex];
|
|
||||||
if (slot.inFlight)
|
|
||||||
{
|
|
||||||
finishTiming();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto stageStartTime = std::chrono::steady_clock::now();
|
|
||||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
|
||||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
|
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
|
|
||||||
auto stageEndTime = std::chrono::steady_clock::now();
|
|
||||||
timing.asyncQueueSetupMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
|
|
||||||
|
|
||||||
stageStartTime = std::chrono::steady_clock::now();
|
|
||||||
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(requiredBytes), nullptr, GL_STREAM_READ);
|
|
||||||
stageEndTime = std::chrono::steady_clock::now();
|
|
||||||
timing.asyncQueueBufferMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
|
|
||||||
|
|
||||||
stageStartTime = std::chrono::steady_clock::now();
|
|
||||||
glReadPixels(0, 0, readWidth, readHeight, format, type, nullptr);
|
|
||||||
stageEndTime = std::chrono::steady_clock::now();
|
|
||||||
timing.asyncQueueReadPixelsMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
|
|
||||||
|
|
||||||
stageStartTime = std::chrono::steady_clock::now();
|
|
||||||
slot.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
||||||
stageEndTime = std::chrono::steady_clock::now();
|
|
||||||
timing.asyncQueueFenceMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
|
|
||||||
slot.inFlight = slot.fence != nullptr;
|
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
||||||
|
|
||||||
mAsyncReadbackWriteIndex = (mAsyncReadbackWriteIndex + 1) % mAsyncReadbackSlots.size();
|
|
||||||
finishTiming();
|
|
||||||
return slot.inFlight;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds, OutputReadbackTiming& timing)
|
|
||||||
{
|
|
||||||
if (mAsyncReadbackBytes == 0 || outputFrame.bytes == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
AsyncReadbackSlot& slot = mAsyncReadbackSlots[mAsyncReadbackReadIndex];
|
|
||||||
if (!slot.inFlight || slot.fence == nullptr || slot.pixelPackBuffer == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const GLenum waitFlags = timeoutNanoseconds > 0 ? GL_SYNC_FLUSH_COMMANDS_BIT : 0;
|
|
||||||
const auto waitStartTime = std::chrono::steady_clock::now();
|
|
||||||
const GLenum waitResult = glClientWaitSync(slot.fence, waitFlags, timeoutNanoseconds);
|
|
||||||
const auto waitEndTime = std::chrono::steady_clock::now();
|
|
||||||
timing.fenceWaitMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(waitEndTime - waitStartTime).count();
|
|
||||||
if (waitResult != GL_ALREADY_SIGNALED && waitResult != GL_CONDITION_SATISFIED)
|
|
||||||
{
|
|
||||||
timing.asyncReadbackMissed = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
glDeleteSync(slot.fence);
|
|
||||||
slot.fence = nullptr;
|
|
||||||
|
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
|
|
||||||
const auto mapStartTime = std::chrono::steady_clock::now();
|
|
||||||
void* mappedBytes = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
|
||||||
const auto mapEndTime = std::chrono::steady_clock::now();
|
|
||||||
timing.mapMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(mapEndTime - mapStartTime).count();
|
|
||||||
if (mappedBytes == nullptr)
|
|
||||||
{
|
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
||||||
slot.inFlight = false;
|
|
||||||
mAsyncReadbackReadIndex = (mAsyncReadbackReadIndex + 1) % mAsyncReadbackSlots.size();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto copyStartTime = std::chrono::steady_clock::now();
|
|
||||||
std::memcpy(outputFrame.bytes, mappedBytes, slot.sizeBytes);
|
|
||||||
const auto copyEndTime = std::chrono::steady_clock::now();
|
|
||||||
timing.copyMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(copyEndTime - copyStartTime).count();
|
|
||||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
||||||
|
|
||||||
slot.inFlight = false;
|
|
||||||
mAsyncReadbackReadIndex = (mAsyncReadbackReadIndex + 1) % mAsyncReadbackSlots.size();
|
|
||||||
CacheOutputFrame(outputFrame);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPipeline::CacheOutputFrame(const VideoIOOutputFrame& outputFrame)
|
|
||||||
{
|
|
||||||
if (outputFrame.bytes == nullptr || outputFrame.height == 0 || outputFrame.rowBytes <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const std::size_t byteCount = static_cast<std::size_t>(outputFrame.rowBytes) * outputFrame.height;
|
|
||||||
mCachedOutputFrame.resize(byteCount);
|
|
||||||
std::memcpy(mCachedOutputFrame.data(), outputFrame.bytes, byteCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLRenderPipeline::TryCopyCachedOutputFrame(VideoIOOutputFrame& outputFrame, OutputReadbackTiming& timing) const
|
|
||||||
{
|
|
||||||
if (outputFrame.bytes == nullptr || outputFrame.height == 0 || outputFrame.rowBytes <= 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const std::size_t byteCount = static_cast<std::size_t>(outputFrame.rowBytes) * outputFrame.height;
|
|
||||||
if (mCachedOutputFrame.size() != byteCount)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const auto copyStartTime = std::chrono::steady_clock::now();
|
|
||||||
std::memcpy(outputFrame.bytes, mCachedOutputFrame.data(), byteCount);
|
|
||||||
const auto copyEndTime = std::chrono::steady_clock::now();
|
|
||||||
timing.cachedCopyMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(copyEndTime - copyStartTime).count();
|
|
||||||
timing.cachedFallbackUsed = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderPipeline::ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes, OutputReadbackTiming& timing)
|
|
||||||
{
|
|
||||||
const auto readStartTime = std::chrono::steady_clock::now();
|
|
||||||
const bool usePackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10;
|
|
||||||
const bool usePackFramebuffer = state.outputPixelFormat == VideoIOPixelFormat::Bgra8 || usePackedOutput;
|
|
||||||
|
|
||||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
|
||||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
|
||||||
if (usePackFramebuffer)
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
|
||||||
if (usePackedOutput)
|
|
||||||
glReadPixels(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, destinationBytes);
|
|
||||||
else
|
|
||||||
glReadPixels(0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, destinationBytes);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
|
||||||
glReadPixels(0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, destinationBytes);
|
|
||||||
}
|
|
||||||
const auto readEndTime = std::chrono::steady_clock::now();
|
|
||||||
timing.syncReadMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(readEndTime - readStartTime).count();
|
|
||||||
timing.syncFallbackUsed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenGLRenderPipeline::OutputReadbackTiming OpenGLRenderPipeline::ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame)
|
|
||||||
{
|
|
||||||
OutputReadbackTiming timing;
|
|
||||||
|
|
||||||
if (mOutputReadbackMode == OutputReadbackMode::Synchronous)
|
|
||||||
{
|
|
||||||
if (outputFrame.bytes != nullptr)
|
|
||||||
{
|
|
||||||
ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
|
|
||||||
CacheOutputFrame(outputFrame);
|
|
||||||
}
|
|
||||||
return timing;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mOutputReadbackMode == OutputReadbackMode::CachedOnly)
|
|
||||||
{
|
|
||||||
if (TryCopyCachedOutputFrame(outputFrame, timing))
|
|
||||||
return timing;
|
|
||||||
|
|
||||||
if (outputFrame.bytes != nullptr)
|
|
||||||
{
|
|
||||||
ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
|
|
||||||
CacheOutputFrame(outputFrame);
|
|
||||||
}
|
|
||||||
return timing;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryConsumeAsyncReadback(outputFrame, 0, timing))
|
|
||||||
{
|
|
||||||
(void)QueueAsyncReadback(state, timing);
|
|
||||||
return timing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool queued = QueueAsyncReadback(state, timing);
|
|
||||||
|
|
||||||
if (queued && TryConsumeAsyncReadback(outputFrame, 0, timing))
|
|
||||||
return timing;
|
|
||||||
|
|
||||||
if (TryCopyCachedOutputFrame(outputFrame, timing))
|
|
||||||
{
|
|
||||||
return timing;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bootstrap only: until the first async readback has produced cached output,
|
|
||||||
// use one synchronous readback so DeckLink has a valid frame to schedule.
|
|
||||||
if (outputFrame.bytes != nullptr && mCachedOutputFrame.empty())
|
|
||||||
{
|
|
||||||
ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
|
|
||||||
CacheOutputFrame(outputFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!queued)
|
|
||||||
(void)QueueAsyncReadback(state, timing);
|
|
||||||
return timing;
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenGLRenderPipeline::OutputReadbackMode OpenGLRenderPipeline::ReadOutputReadbackModeFromEnvironment()
|
|
||||||
{
|
|
||||||
char* mode = nullptr;
|
|
||||||
std::size_t modeSize = 0;
|
|
||||||
if (_dupenv_s(&mode, &modeSize, "VST_OUTPUT_READBACK_MODE") != 0 || mode == nullptr)
|
|
||||||
return OutputReadbackMode::AsyncPbo;
|
|
||||||
|
|
||||||
const std::string modeValue(mode);
|
|
||||||
std::free(mode);
|
|
||||||
if (modeValue == "async_pbo")
|
|
||||||
return OutputReadbackMode::AsyncPbo;
|
|
||||||
if (modeValue == "sync")
|
|
||||||
return OutputReadbackMode::Synchronous;
|
|
||||||
if (modeValue == "cached_only")
|
|
||||||
return OutputReadbackMode::CachedOnly;
|
|
||||||
|
|
||||||
return OutputReadbackMode::AsyncPbo;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t OpenGLRenderPipeline::ReadAsyncReadbackDepthFromEnvironment()
|
|
||||||
{
|
|
||||||
char* depthValue = nullptr;
|
|
||||||
std::size_t depthValueSize = 0;
|
|
||||||
if (_dupenv_s(&depthValue, &depthValueSize, "VST_OUTPUT_READBACK_DEPTH") != 0 || depthValue == nullptr)
|
|
||||||
return 6;
|
|
||||||
|
|
||||||
const std::string value(depthValue);
|
|
||||||
std::free(depthValue);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const unsigned long requestedDepth = std::stoul(value);
|
|
||||||
if (requestedDepth < 3)
|
|
||||||
return 3;
|
|
||||||
if (requestedDepth > 12)
|
|
||||||
return 12;
|
|
||||||
return static_cast<std::size_t>(requestedDepth);
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
return 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "GLExtensions.h"
|
|
||||||
#include "VideoIOTypes.h"
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class OpenGLRenderer;
|
|
||||||
class HealthTelemetry;
|
|
||||||
class RuntimeSnapshotProvider;
|
|
||||||
|
|
||||||
struct RenderPipelineFrameContext
|
|
||||||
{
|
|
||||||
VideoIOState videoState;
|
|
||||||
VideoIOCompletion completion;
|
|
||||||
};
|
|
||||||
|
|
||||||
class OpenGLRenderPipeline
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using RenderEffectCallback = std::function<void()>;
|
|
||||||
using OutputReadyCallback = std::function<void()>;
|
|
||||||
using PaintCallback = std::function<void()>;
|
|
||||||
|
|
||||||
OpenGLRenderPipeline(
|
|
||||||
OpenGLRenderer& renderer,
|
|
||||||
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
|
||||||
HealthTelemetry& healthTelemetry,
|
|
||||||
RenderEffectCallback renderEffect,
|
|
||||||
OutputReadyCallback outputReady,
|
|
||||||
PaintCallback paint);
|
|
||||||
~OpenGLRenderPipeline();
|
|
||||||
|
|
||||||
bool RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum class OutputReadbackMode
|
|
||||||
{
|
|
||||||
AsyncPbo,
|
|
||||||
Synchronous,
|
|
||||||
CachedOnly
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AsyncReadbackSlot
|
|
||||||
{
|
|
||||||
GLuint pixelPackBuffer = 0;
|
|
||||||
GLsync fence = nullptr;
|
|
||||||
std::size_t sizeBytes = 0;
|
|
||||||
bool inFlight = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OutputReadbackTiming
|
|
||||||
{
|
|
||||||
double fenceWaitMilliseconds = 0.0;
|
|
||||||
double mapMilliseconds = 0.0;
|
|
||||||
double copyMilliseconds = 0.0;
|
|
||||||
double cachedCopyMilliseconds = 0.0;
|
|
||||||
double asyncQueueMilliseconds = 0.0;
|
|
||||||
double asyncQueueBufferMilliseconds = 0.0;
|
|
||||||
double asyncQueueSetupMilliseconds = 0.0;
|
|
||||||
double asyncQueueReadPixelsMilliseconds = 0.0;
|
|
||||||
double asyncQueueFenceMilliseconds = 0.0;
|
|
||||||
double syncReadMilliseconds = 0.0;
|
|
||||||
bool asyncReadbackMissed = false;
|
|
||||||
bool cachedFallbackUsed = false;
|
|
||||||
bool syncFallbackUsed = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool EnsureAsyncReadbackBuffers(std::size_t requiredBytes);
|
|
||||||
void ResetAsyncReadbackState();
|
|
||||||
void FlushAsyncReadbackPipeline();
|
|
||||||
bool QueueAsyncReadback(const VideoIOState& state, OutputReadbackTiming& timing);
|
|
||||||
bool TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds, OutputReadbackTiming& timing);
|
|
||||||
void CacheOutputFrame(const VideoIOOutputFrame& outputFrame);
|
|
||||||
bool TryCopyCachedOutputFrame(VideoIOOutputFrame& outputFrame, OutputReadbackTiming& timing) const;
|
|
||||||
void ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes, OutputReadbackTiming& timing);
|
|
||||||
void PackOutputForBgra8(const VideoIOState& state);
|
|
||||||
void PackOutputFor10Bit(const VideoIOState& state);
|
|
||||||
OutputReadbackTiming ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame);
|
|
||||||
static OutputReadbackMode ReadOutputReadbackModeFromEnvironment();
|
|
||||||
static std::size_t ReadAsyncReadbackDepthFromEnvironment();
|
|
||||||
|
|
||||||
OpenGLRenderer& mRenderer;
|
|
||||||
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
|
||||||
HealthTelemetry& mHealthTelemetry;
|
|
||||||
RenderEffectCallback mRenderEffect;
|
|
||||||
OutputReadyCallback mOutputReady;
|
|
||||||
PaintCallback mPaint;
|
|
||||||
OutputReadbackMode mOutputReadbackMode = OutputReadbackMode::AsyncPbo;
|
|
||||||
std::vector<AsyncReadbackSlot> mAsyncReadbackSlots;
|
|
||||||
std::size_t mAsyncReadbackDepth = 0;
|
|
||||||
std::size_t mAsyncReadbackWriteIndex = 0;
|
|
||||||
std::size_t mAsyncReadbackReadIndex = 0;
|
|
||||||
std::size_t mAsyncReadbackBytes = 0;
|
|
||||||
GLenum mAsyncReadbackFormat = GL_BGRA;
|
|
||||||
GLenum mAsyncReadbackType = GL_UNSIGNED_INT_8_8_8_8_REV;
|
|
||||||
GLuint mAsyncReadbackFramebuffer = 0;
|
|
||||||
std::vector<unsigned char> mCachedOutputFrame;
|
|
||||||
};
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#include "OpenGLVideoIOBridge.h"
|
|
||||||
|
|
||||||
#include "RenderEngine.h"
|
|
||||||
|
|
||||||
OpenGLVideoIOBridge::OpenGLVideoIOBridge(RenderEngine& renderEngine) :
|
|
||||||
mRenderEngine(renderEngine)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLVideoIOBridge::UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state)
|
|
||||||
{
|
|
||||||
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
|
|
||||||
return; // don't transfer texture when there's no input
|
|
||||||
|
|
||||||
mRenderEngine.QueueInputFrame(inputFrame, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLVideoIOBridge::RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame)
|
|
||||||
{
|
|
||||||
RenderPipelineFrameContext frameContext;
|
|
||||||
frameContext.videoState = state;
|
|
||||||
frameContext.completion = completion;
|
|
||||||
|
|
||||||
return mRenderEngine.RequestOutputFrame(frameContext, outputFrame);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "OpenGLRenderPipeline.h"
|
|
||||||
|
|
||||||
class RenderEngine;
|
|
||||||
|
|
||||||
class OpenGLVideoIOBridge
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit OpenGLVideoIOBridge(RenderEngine& renderEngine);
|
|
||||||
|
|
||||||
void UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state);
|
|
||||||
bool RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame);
|
|
||||||
|
|
||||||
private:
|
|
||||||
RenderEngine& mRenderEngine;
|
|
||||||
};
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
#include "PngScreenshotWriter.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include <wincodec.h>
|
|
||||||
#include <atlbase.h>
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
std::string HResultToString(HRESULT hr)
|
|
||||||
{
|
|
||||||
std::ostringstream stream;
|
|
||||||
stream << "HRESULT 0x" << std::hex << static_cast<unsigned long>(hr);
|
|
||||||
return stream.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WritePngFile(
|
|
||||||
const std::filesystem::path& outputPath,
|
|
||||||
unsigned width,
|
|
||||||
unsigned height,
|
|
||||||
const std::vector<unsigned char>& bgraPixels,
|
|
||||||
std::string& error)
|
|
||||||
{
|
|
||||||
if (width == 0 || height == 0 || bgraPixels.size() < static_cast<std::size_t>(width) * height * 4)
|
|
||||||
{
|
|
||||||
error = "Invalid screenshot dimensions or pixel buffer.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HRESULT initializeResult = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
||||||
const bool shouldUninitialize = SUCCEEDED(initializeResult);
|
|
||||||
if (FAILED(initializeResult) && initializeResult != RPC_E_CHANGED_MODE)
|
|
||||||
{
|
|
||||||
error = "CoInitializeEx failed: " + HResultToString(initializeResult);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CComPtr<IWICImagingFactory> factory;
|
|
||||||
HRESULT result = CoCreateInstance(
|
|
||||||
CLSID_WICImagingFactory,
|
|
||||||
nullptr,
|
|
||||||
CLSCTX_INPROC_SERVER,
|
|
||||||
IID_PPV_ARGS(&factory));
|
|
||||||
if (FAILED(result))
|
|
||||||
{
|
|
||||||
error = "Could not create WIC imaging factory: " + HResultToString(result);
|
|
||||||
if (shouldUninitialize)
|
|
||||||
CoUninitialize();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CComPtr<IWICStream> stream;
|
|
||||||
result = factory->CreateStream(&stream);
|
|
||||||
if (SUCCEEDED(result))
|
|
||||||
result = stream->InitializeFromFilename(outputPath.wstring().c_str(), GENERIC_WRITE);
|
|
||||||
if (FAILED(result))
|
|
||||||
{
|
|
||||||
error = "Could not open screenshot output file: " + HResultToString(result);
|
|
||||||
if (shouldUninitialize)
|
|
||||||
CoUninitialize();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CComPtr<IWICBitmapEncoder> encoder;
|
|
||||||
result = factory->CreateEncoder(GUID_ContainerFormatPng, nullptr, &encoder);
|
|
||||||
if (SUCCEEDED(result))
|
|
||||||
result = encoder->Initialize(stream, WICBitmapEncoderNoCache);
|
|
||||||
if (FAILED(result))
|
|
||||||
{
|
|
||||||
error = "Could not initialize PNG encoder: " + HResultToString(result);
|
|
||||||
if (shouldUninitialize)
|
|
||||||
CoUninitialize();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CComPtr<IWICBitmapFrameEncode> frame;
|
|
||||||
CComPtr<IPropertyBag2> propertyBag;
|
|
||||||
result = encoder->CreateNewFrame(&frame, &propertyBag);
|
|
||||||
if (SUCCEEDED(result))
|
|
||||||
result = frame->Initialize(propertyBag);
|
|
||||||
if (SUCCEEDED(result))
|
|
||||||
result = frame->SetSize(width, height);
|
|
||||||
|
|
||||||
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppBGRA;
|
|
||||||
if (SUCCEEDED(result))
|
|
||||||
result = frame->SetPixelFormat(&pixelFormat);
|
|
||||||
if (SUCCEEDED(result) && pixelFormat != GUID_WICPixelFormat32bppBGRA)
|
|
||||||
{
|
|
||||||
error = "PNG encoder did not accept BGRA pixel format.";
|
|
||||||
result = E_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
const UINT stride = width * 4;
|
|
||||||
const UINT imageSize = stride * height;
|
|
||||||
if (SUCCEEDED(result))
|
|
||||||
result = frame->WritePixels(height, stride, imageSize, const_cast<BYTE*>(bgraPixels.data()));
|
|
||||||
if (SUCCEEDED(result))
|
|
||||||
result = frame->Commit();
|
|
||||||
if (SUCCEEDED(result))
|
|
||||||
result = encoder->Commit();
|
|
||||||
|
|
||||||
if (shouldUninitialize)
|
|
||||||
CoUninitialize();
|
|
||||||
|
|
||||||
if (FAILED(result))
|
|
||||||
{
|
|
||||||
error = "Could not write screenshot PNG: " + HResultToString(result);
|
|
||||||
std::error_code ignored;
|
|
||||||
std::filesystem::remove(outputPath, ignored);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WritePngFileAsync(
|
|
||||||
const std::filesystem::path& outputPath,
|
|
||||||
unsigned width,
|
|
||||||
unsigned height,
|
|
||||||
std::vector<unsigned char> rgbaPixels)
|
|
||||||
{
|
|
||||||
std::thread(
|
|
||||||
[outputPath, width, height, pixels = std::move(rgbaPixels)]() mutable
|
|
||||||
{
|
|
||||||
for (std::size_t index = 0; index + 3 < pixels.size(); index += 4)
|
|
||||||
std::swap(pixels[index], pixels[index + 2]);
|
|
||||||
|
|
||||||
std::string error;
|
|
||||||
if (!WritePngFile(outputPath, width, height, pixels, error))
|
|
||||||
OutputDebugStringA(("Screenshot write failed: " + error + "\n").c_str());
|
|
||||||
else
|
|
||||||
OutputDebugStringA(("Screenshot written: " + outputPath.string() + "\n").c_str());
|
|
||||||
}).detach();
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
void WritePngFileAsync(
|
|
||||||
const std::filesystem::path& outputPath,
|
|
||||||
unsigned width,
|
|
||||||
unsigned height,
|
|
||||||
std::vector<unsigned char> rgbaPixels);
|
|
||||||
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "OpenGLRenderer.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <gl/gl.h>
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
enum class RenderPassKind
|
|
||||||
{
|
|
||||||
LayerEffect
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class RenderPassOutputTarget
|
|
||||||
{
|
|
||||||
Temporary,
|
|
||||||
LayerTemp,
|
|
||||||
Composite
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderPassDescriptor
|
|
||||||
{
|
|
||||||
RenderPassKind kind = RenderPassKind::LayerEffect;
|
|
||||||
RenderPassOutputTarget outputTarget = RenderPassOutputTarget::Composite;
|
|
||||||
std::size_t passIndex = 0;
|
|
||||||
std::string passId;
|
|
||||||
std::string layerId;
|
|
||||||
std::string shaderId;
|
|
||||||
GLuint layerInputTexture = 0;
|
|
||||||
GLuint sourceTexture = 0;
|
|
||||||
GLuint sourceFramebuffer = 0;
|
|
||||||
GLuint destinationTexture = 0;
|
|
||||||
GLuint destinationFramebuffer = 0;
|
|
||||||
OpenGLRenderer::LayerProgram* layerProgram = nullptr;
|
|
||||||
OpenGLRenderer::LayerProgram::PassProgram* passProgram = nullptr;
|
|
||||||
const RuntimeRenderState* layerState = nullptr;
|
|
||||||
bool capturePreLayerHistory = false;
|
|
||||||
bool captureFeedbackWrite = false;
|
|
||||||
};
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
#include "ShaderFeedbackBuffers.h"
|
|
||||||
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
void ConfigureFeedbackTexture(unsigned frameWidth, unsigned frameHeight)
|
|
||||||
{
|
|
||||||
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_RGBA16F, frameWidth, frameHeight, 0, GL_RGBA, GL_FLOAT, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderFeedbackBuffers::EnsureResources(const std::vector<RuntimeRenderState>& layerStates, unsigned frameWidth, unsigned frameHeight, std::string& error)
|
|
||||||
{
|
|
||||||
if (!EnsureZeroTexture())
|
|
||||||
{
|
|
||||||
error = "Failed to initialize shader feedback fallback texture.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<std::string> requiredLayerIds;
|
|
||||||
for (const RuntimeRenderState& state : layerStates)
|
|
||||||
{
|
|
||||||
if (!state.feedback.enabled)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
requiredLayerIds.insert(state.layerId);
|
|
||||||
auto surfaceIt = mSurfacesByLayerId.find(state.layerId);
|
|
||||||
if (surfaceIt == mSurfacesByLayerId.end() ||
|
|
||||||
surfaceIt->second.width != frameWidth ||
|
|
||||||
surfaceIt->second.height != frameHeight)
|
|
||||||
{
|
|
||||||
Surface replacement;
|
|
||||||
if (!CreateSurface(replacement, frameWidth, frameHeight, error))
|
|
||||||
return false;
|
|
||||||
mSurfacesByLayerId[state.layerId] = std::move(replacement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = mSurfacesByLayerId.begin(); it != mSurfacesByLayerId.end();)
|
|
||||||
{
|
|
||||||
if (requiredLayerIds.find(it->first) == requiredLayerIds.end())
|
|
||||||
{
|
|
||||||
DestroySurface(it->second);
|
|
||||||
it = mSurfacesByLayerId.erase(it);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderFeedbackBuffers::DestroyResources()
|
|
||||||
{
|
|
||||||
for (auto& entry : mSurfacesByLayerId)
|
|
||||||
DestroySurface(entry.second);
|
|
||||||
mSurfacesByLayerId.clear();
|
|
||||||
|
|
||||||
if (mZeroTexture != 0)
|
|
||||||
{
|
|
||||||
glDeleteTextures(1, &mZeroTexture);
|
|
||||||
mZeroTexture = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderFeedbackBuffers::ResetState()
|
|
||||||
{
|
|
||||||
for (auto& entry : mSurfacesByLayerId)
|
|
||||||
ClearSurfaceState(entry.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint ShaderFeedbackBuffers::ResolveReadTexture(const RuntimeRenderState& state) const
|
|
||||||
{
|
|
||||||
if (!state.feedback.enabled)
|
|
||||||
return mZeroTexture;
|
|
||||||
|
|
||||||
auto surfaceIt = mSurfacesByLayerId.find(state.layerId);
|
|
||||||
if (surfaceIt == mSurfacesByLayerId.end() || !surfaceIt->second.hasData)
|
|
||||||
return mZeroTexture;
|
|
||||||
|
|
||||||
return surfaceIt->second.slots[surfaceIt->second.readIndex].texture != 0
|
|
||||||
? surfaceIt->second.slots[surfaceIt->second.readIndex].texture
|
|
||||||
: mZeroTexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderFeedbackBuffers::FeedbackAvailable(const RuntimeRenderState& state) const
|
|
||||||
{
|
|
||||||
if (!state.feedback.enabled)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto surfaceIt = mSurfacesByLayerId.find(state.layerId);
|
|
||||||
return surfaceIt != mSurfacesByLayerId.end() && surfaceIt->second.hasData;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderFeedbackBuffers::CaptureFeedbackFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight)
|
|
||||||
{
|
|
||||||
auto surfaceIt = mSurfacesByLayerId.find(layerId);
|
|
||||||
if (surfaceIt == mSurfacesByLayerId.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
Surface& surface = surfaceIt->second;
|
|
||||||
const unsigned writeIndex = 1u - surface.readIndex;
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebuffer);
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, surface.slots[writeIndex].framebuffer);
|
|
||||||
glBlitFramebuffer(0, 0, frameWidth, frameHeight, 0, 0, frameWidth, frameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
||||||
surface.pendingWrite = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderFeedbackBuffers::FinalizeFrame()
|
|
||||||
{
|
|
||||||
for (auto& entry : mSurfacesByLayerId)
|
|
||||||
{
|
|
||||||
Surface& surface = entry.second;
|
|
||||||
if (!surface.pendingWrite)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
surface.readIndex = 1u - surface.readIndex;
|
|
||||||
surface.hasData = true;
|
|
||||||
surface.pendingWrite = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderFeedbackBuffers::EnsureZeroTexture()
|
|
||||||
{
|
|
||||||
if (mZeroTexture != 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
glGenTextures(1, &mZeroTexture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, mZeroTexture);
|
|
||||||
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);
|
|
||||||
const float zeroPixel[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, 1, 1, 0, GL_RGBA, GL_FLOAT, zeroPixel);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
return mZeroTexture != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderFeedbackBuffers::CreateSurface(Surface& surface, unsigned frameWidth, unsigned frameHeight, std::string& error)
|
|
||||||
{
|
|
||||||
DestroySurface(surface);
|
|
||||||
|
|
||||||
surface.width = frameWidth;
|
|
||||||
surface.height = frameHeight;
|
|
||||||
for (Slot& slot : surface.slots)
|
|
||||||
{
|
|
||||||
glGenTextures(1, &slot.texture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, slot.texture);
|
|
||||||
ConfigureFeedbackTexture(frameWidth, frameHeight);
|
|
||||||
|
|
||||||
glGenFramebuffers(1, &slot.framebuffer);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, slot.framebuffer);
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, slot.texture, 0);
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
||||||
{
|
|
||||||
error = "Failed to initialize a shader feedback framebuffer.";
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
DestroySurface(surface);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
ClearSurfaceState(surface);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderFeedbackBuffers::DestroySurface(Surface& surface)
|
|
||||||
{
|
|
||||||
for (Slot& slot : surface.slots)
|
|
||||||
{
|
|
||||||
if (slot.framebuffer != 0)
|
|
||||||
glDeleteFramebuffers(1, &slot.framebuffer);
|
|
||||||
if (slot.texture != 0)
|
|
||||||
glDeleteTextures(1, &slot.texture);
|
|
||||||
slot.framebuffer = 0;
|
|
||||||
slot.texture = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
surface.width = 0;
|
|
||||||
surface.height = 0;
|
|
||||||
surface.readIndex = 0;
|
|
||||||
surface.hasData = false;
|
|
||||||
surface.pendingWrite = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderFeedbackBuffers::ClearSurfaceState(Surface& surface)
|
|
||||||
{
|
|
||||||
surface.readIndex = 0;
|
|
||||||
surface.hasData = false;
|
|
||||||
surface.pendingWrite = false;
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "GLExtensions.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class ShaderFeedbackBuffers
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct Slot
|
|
||||||
{
|
|
||||||
GLuint texture = 0;
|
|
||||||
GLuint framebuffer = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Surface
|
|
||||||
{
|
|
||||||
Slot slots[2];
|
|
||||||
unsigned width = 0;
|
|
||||||
unsigned height = 0;
|
|
||||||
unsigned readIndex = 0;
|
|
||||||
bool hasData = false;
|
|
||||||
bool pendingWrite = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool EnsureResources(const std::vector<RuntimeRenderState>& layerStates, unsigned frameWidth, unsigned frameHeight, std::string& error);
|
|
||||||
void DestroyResources();
|
|
||||||
void ResetState();
|
|
||||||
GLuint ResolveReadTexture(const RuntimeRenderState& state) const;
|
|
||||||
bool FeedbackAvailable(const RuntimeRenderState& state) const;
|
|
||||||
void CaptureFeedbackFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight);
|
|
||||||
void FinalizeFrame();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool EnsureZeroTexture();
|
|
||||||
bool CreateSurface(Surface& surface, unsigned frameWidth, unsigned frameHeight, std::string& error);
|
|
||||||
void DestroySurface(Surface& surface);
|
|
||||||
void ClearSurfaceState(Surface& surface);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::map<std::string, Surface> mSurfacesByLayerId;
|
|
||||||
GLuint mZeroTexture = 0;
|
|
||||||
};
|
|
||||||
@@ -1,261 +0,0 @@
|
|||||||
#include "TemporalHistoryBuffers.h"
|
|
||||||
|
|
||||||
#include "GlRenderConstants.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <sstream>
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
bool TemporalHistoryBuffers::ValidateTextureUnitBudget(const std::vector<RuntimeRenderState>& layerStates, unsigned historyCap, std::string& error) const
|
|
||||||
{
|
|
||||||
unsigned requiredUnits = kSourceHistoryTextureUnitBase;
|
|
||||||
for (const RuntimeRenderState& state : layerStates)
|
|
||||||
{
|
|
||||||
unsigned textTextureCount = 0;
|
|
||||||
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
|
||||||
{
|
|
||||||
if (definition.type == ShaderParameterType::Text)
|
|
||||||
++textTextureCount;
|
|
||||||
}
|
|
||||||
const unsigned totalShaderTextures = static_cast<unsigned>(state.textureAssets.size()) + textTextureCount;
|
|
||||||
const unsigned feedbackTextureCount = state.feedback.enabled ? 1u : 0u;
|
|
||||||
const unsigned layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + feedbackTextureCount + totalShaderTextures;
|
|
||||||
if (layerRequiredUnits > requiredUnits)
|
|
||||||
requiredUnits = layerRequiredUnits;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint maxTextureUnits = 0;
|
|
||||||
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
|
|
||||||
const unsigned availableUnits = maxTextureUnits > 0 ? static_cast<unsigned>(maxTextureUnits) : 0u;
|
|
||||||
if (requiredUnits > availableUnits)
|
|
||||||
{
|
|
||||||
std::ostringstream message;
|
|
||||||
message << "The current history and shader texture asset configuration requires " << requiredUnits
|
|
||||||
<< " fragment texture units, but only " << maxTextureUnits << " are available.";
|
|
||||||
error = message.str();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TemporalHistoryBuffers::EnsureResources(const std::vector<RuntimeRenderState>& layerStates, unsigned historyCap, unsigned frameWidth, unsigned frameHeight, std::string& error)
|
|
||||||
{
|
|
||||||
const bool sourceHistoryNeeded = std::any_of(layerStates.begin(), layerStates.end(),
|
|
||||||
[](const RuntimeRenderState& state) { return state.isTemporal && state.effectiveTemporalHistoryLength > 0; });
|
|
||||||
const unsigned sourceHistoryLength = sourceHistoryNeeded ? historyCap : 0;
|
|
||||||
|
|
||||||
if (sourceHistoryRing.effectiveLength != sourceHistoryLength)
|
|
||||||
{
|
|
||||||
if (!CreateRing(sourceHistoryRing, sourceHistoryLength, TemporalHistorySource::Source, frameWidth, frameHeight, error))
|
|
||||||
return false;
|
|
||||||
mNeedsReset = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<std::string> requiredPreLayerIds;
|
|
||||||
for (const RuntimeRenderState& state : layerStates)
|
|
||||||
{
|
|
||||||
if (!state.isTemporal || state.temporalHistorySource != TemporalHistorySource::PreLayerInput)
|
|
||||||
continue;
|
|
||||||
requiredPreLayerIds.insert(state.layerId);
|
|
||||||
auto historyIt = preLayerHistoryByLayerId.find(state.layerId);
|
|
||||||
if (historyIt == preLayerHistoryByLayerId.end() || historyIt->second.effectiveLength != state.effectiveTemporalHistoryLength)
|
|
||||||
{
|
|
||||||
Ring replacement;
|
|
||||||
if (!CreateRing(replacement, state.effectiveTemporalHistoryLength, TemporalHistorySource::PreLayerInput, frameWidth, frameHeight, error))
|
|
||||||
return false;
|
|
||||||
preLayerHistoryByLayerId[state.layerId] = std::move(replacement);
|
|
||||||
mNeedsReset = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = preLayerHistoryByLayerId.begin(); it != preLayerHistoryByLayerId.end();)
|
|
||||||
{
|
|
||||||
if (requiredPreLayerIds.find(it->first) == requiredPreLayerIds.end())
|
|
||||||
{
|
|
||||||
DestroyRing(it->second);
|
|
||||||
it = preLayerHistoryByLayerId.erase(it);
|
|
||||||
mNeedsReset = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mNeedsReset)
|
|
||||||
ResetState();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TemporalHistoryBuffers::CreateRing(Ring& ring, unsigned effectiveLength, TemporalHistorySource historySource, unsigned frameWidth, unsigned frameHeight, std::string& error)
|
|
||||||
{
|
|
||||||
DestroyRing(ring);
|
|
||||||
ring.effectiveLength = effectiveLength;
|
|
||||||
ring.historySource = historySource;
|
|
||||||
if (effectiveLength == 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
ring.slots.resize(effectiveLength);
|
|
||||||
for (Slot& slot : ring.slots)
|
|
||||||
{
|
|
||||||
glGenTextures(1, &slot.texture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, slot.texture);
|
|
||||||
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_RGBA16F, frameWidth, frameHeight, 0, GL_RGBA, GL_FLOAT, NULL);
|
|
||||||
|
|
||||||
glGenFramebuffers(1, &slot.framebuffer);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, slot.framebuffer);
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, slot.texture, 0);
|
|
||||||
const GLenum framebufferStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
||||||
if (framebufferStatus != GL_FRAMEBUFFER_COMPLETE)
|
|
||||||
{
|
|
||||||
error = "Failed to initialize a temporal history framebuffer.";
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
DestroyRing(ring);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TemporalHistoryBuffers::DestroyRing(Ring& ring)
|
|
||||||
{
|
|
||||||
for (Slot& slot : ring.slots)
|
|
||||||
{
|
|
||||||
if (slot.framebuffer != 0)
|
|
||||||
glDeleteFramebuffers(1, &slot.framebuffer);
|
|
||||||
if (slot.texture != 0)
|
|
||||||
glDeleteTextures(1, &slot.texture);
|
|
||||||
slot.framebuffer = 0;
|
|
||||||
slot.texture = 0;
|
|
||||||
}
|
|
||||||
ring.slots.clear();
|
|
||||||
ring.nextWriteIndex = 0;
|
|
||||||
ring.filledCount = 0;
|
|
||||||
ring.effectiveLength = 0;
|
|
||||||
ring.historySource = TemporalHistorySource::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TemporalHistoryBuffers::DestroyResources()
|
|
||||||
{
|
|
||||||
DestroyRing(sourceHistoryRing);
|
|
||||||
for (auto& historyEntry : preLayerHistoryByLayerId)
|
|
||||||
DestroyRing(historyEntry.second);
|
|
||||||
preLayerHistoryByLayerId.clear();
|
|
||||||
mNeedsReset = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TemporalHistoryBuffers::ResetState()
|
|
||||||
{
|
|
||||||
sourceHistoryRing.nextWriteIndex = 0;
|
|
||||||
sourceHistoryRing.filledCount = 0;
|
|
||||||
for (auto& historyEntry : preLayerHistoryByLayerId)
|
|
||||||
{
|
|
||||||
historyEntry.second.nextWriteIndex = 0;
|
|
||||||
historyEntry.second.filledCount = 0;
|
|
||||||
}
|
|
||||||
mNeedsReset = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TemporalHistoryBuffers::PushFramebuffer(GLuint sourceFramebuffer, Ring& ring, unsigned frameWidth, unsigned frameHeight)
|
|
||||||
{
|
|
||||||
if (ring.effectiveLength == 0 || ring.slots.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
Slot& targetSlot = ring.slots[ring.nextWriteIndex];
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebuffer);
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetSlot.framebuffer);
|
|
||||||
glBlitFramebuffer(0, 0, frameWidth, frameHeight, 0, 0, frameWidth, frameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
||||||
ring.nextWriteIndex = (ring.nextWriteIndex + 1) % ring.slots.size();
|
|
||||||
ring.filledCount = std::min<std::size_t>(ring.filledCount + 1, ring.slots.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void TemporalHistoryBuffers::PushSourceFramebuffer(GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight)
|
|
||||||
{
|
|
||||||
PushFramebuffer(sourceFramebuffer, sourceHistoryRing, frameWidth, frameHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TemporalHistoryBuffers::PushPreLayerFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight)
|
|
||||||
{
|
|
||||||
auto historyIt = preLayerHistoryByLayerId.find(layerId);
|
|
||||||
if (historyIt != preLayerHistoryByLayerId.end())
|
|
||||||
PushFramebuffer(sourceFramebuffer, historyIt->second, frameWidth, frameHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TemporalHistoryBuffers::BindSamplers(const RuntimeRenderState& state, GLuint currentSourceTexture, unsigned historyCap)
|
|
||||||
{
|
|
||||||
for (unsigned index = 0; index < historyCap; ++index)
|
|
||||||
{
|
|
||||||
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + index);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, ResolveTexture(sourceHistoryRing, currentSourceTexture, index));
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLuint temporalBase = kSourceHistoryTextureUnitBase + historyCap;
|
|
||||||
const Ring* temporalRing = nullptr;
|
|
||||||
auto it = preLayerHistoryByLayerId.find(state.layerId);
|
|
||||||
if (it != preLayerHistoryByLayerId.end())
|
|
||||||
temporalRing = &it->second;
|
|
||||||
|
|
||||||
for (unsigned index = 0; index < historyCap; ++index)
|
|
||||||
{
|
|
||||||
glActiveTexture(GL_TEXTURE0 + temporalBase + index);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, temporalRing ? ResolveTexture(*temporalRing, currentSourceTexture, index) : currentSourceTexture);
|
|
||||||
}
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<GLuint> TemporalHistoryBuffers::ResolveSourceHistoryTextures(GLuint fallbackTexture, unsigned historyCap) const
|
|
||||||
{
|
|
||||||
std::vector<GLuint> textures;
|
|
||||||
textures.reserve(historyCap);
|
|
||||||
for (unsigned index = 0; index < historyCap; ++index)
|
|
||||||
textures.push_back(ResolveTexture(sourceHistoryRing, fallbackTexture, index));
|
|
||||||
return textures;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<GLuint> TemporalHistoryBuffers::ResolveTemporalHistoryTextures(const RuntimeRenderState& state, GLuint fallbackTexture, unsigned historyCap) const
|
|
||||||
{
|
|
||||||
const Ring* temporalRing = nullptr;
|
|
||||||
auto it = preLayerHistoryByLayerId.find(state.layerId);
|
|
||||||
if (it != preLayerHistoryByLayerId.end())
|
|
||||||
temporalRing = &it->second;
|
|
||||||
|
|
||||||
std::vector<GLuint> textures;
|
|
||||||
textures.reserve(historyCap);
|
|
||||||
for (unsigned index = 0; index < historyCap; ++index)
|
|
||||||
textures.push_back(temporalRing ? ResolveTexture(*temporalRing, fallbackTexture, index) : fallbackTexture);
|
|
||||||
return textures;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint TemporalHistoryBuffers::ResolveTexture(const Ring& ring, GLuint fallbackTexture, std::size_t framesAgo) const
|
|
||||||
{
|
|
||||||
if (ring.filledCount == 0 || ring.slots.empty())
|
|
||||||
return fallbackTexture;
|
|
||||||
|
|
||||||
const std::size_t clampedOffset = std::min<std::size_t>(framesAgo, ring.filledCount - 1);
|
|
||||||
const std::size_t newestIndex = (ring.nextWriteIndex + ring.slots.size() - 1) % ring.slots.size();
|
|
||||||
const std::size_t slotIndex = (newestIndex + ring.slots.size() - clampedOffset) % ring.slots.size();
|
|
||||||
return ring.slots[slotIndex].texture != 0 ? ring.slots[slotIndex].texture : fallbackTexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned TemporalHistoryBuffers::SourceAvailableCount() const
|
|
||||||
{
|
|
||||||
return static_cast<unsigned>(sourceHistoryRing.filledCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned TemporalHistoryBuffers::AvailableCountForLayer(const std::string& layerId) const
|
|
||||||
{
|
|
||||||
auto it = preLayerHistoryByLayerId.find(layerId);
|
|
||||||
if (it == preLayerHistoryByLayerId.end())
|
|
||||||
return 0;
|
|
||||||
return static_cast<unsigned>(it->second.filledCount);
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "GLExtensions.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#include <gl/gl.h>
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct RuntimeRenderState;
|
|
||||||
|
|
||||||
class TemporalHistoryBuffers
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct Slot
|
|
||||||
{
|
|
||||||
GLuint texture = 0;
|
|
||||||
GLuint framebuffer = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Ring
|
|
||||||
{
|
|
||||||
std::vector<Slot> slots;
|
|
||||||
std::size_t nextWriteIndex = 0;
|
|
||||||
std::size_t filledCount = 0;
|
|
||||||
unsigned effectiveLength = 0;
|
|
||||||
TemporalHistorySource historySource = TemporalHistorySource::None;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool ValidateTextureUnitBudget(const std::vector<RuntimeRenderState>& layerStates, unsigned historyCap, std::string& error) const;
|
|
||||||
bool EnsureResources(const std::vector<RuntimeRenderState>& layerStates, unsigned historyCap, unsigned frameWidth, unsigned frameHeight, std::string& error);
|
|
||||||
bool CreateRing(Ring& ring, unsigned effectiveLength, TemporalHistorySource historySource, unsigned frameWidth, unsigned frameHeight, std::string& error);
|
|
||||||
void DestroyRing(Ring& ring);
|
|
||||||
void DestroyResources();
|
|
||||||
void ResetState();
|
|
||||||
void PushFramebuffer(GLuint sourceFramebuffer, Ring& ring, unsigned frameWidth, unsigned frameHeight);
|
|
||||||
void PushSourceFramebuffer(GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight);
|
|
||||||
void PushPreLayerFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight);
|
|
||||||
void BindSamplers(const RuntimeRenderState& state, GLuint currentSourceTexture, unsigned historyCap);
|
|
||||||
std::vector<GLuint> ResolveSourceHistoryTextures(GLuint fallbackTexture, unsigned historyCap) const;
|
|
||||||
std::vector<GLuint> ResolveTemporalHistoryTextures(const RuntimeRenderState& state, GLuint fallbackTexture, unsigned historyCap) const;
|
|
||||||
GLuint ResolveTexture(const Ring& ring, GLuint fallbackTexture, std::size_t framesAgo) const;
|
|
||||||
unsigned SourceAvailableCount() const;
|
|
||||||
unsigned AvailableCountForLayer(const std::string& layerId) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ring sourceHistoryRing;
|
|
||||||
std::map<std::string, Ring> preLayerHistoryByLayerId;
|
|
||||||
bool mNeedsReset = true;
|
|
||||||
};
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2012 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, execute,
|
|
||||||
** and transmit the Software, and to prepare derivative works of the Software,
|
|
||||||
** and to permit third-parties to whom the Software is furnished to do so, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) the copyright notices in the Software and this entire statement,
|
|
||||||
** including the above license grant, this restriction and the following
|
|
||||||
** disclaimer, must be included in all copies of the Software, in whole or in
|
|
||||||
** part, and all derivative works of the Software, unless such copies or
|
|
||||||
** derivative works are solely in the form of machine-executable object code
|
|
||||||
** generated by a source language processor.
|
|
||||||
**
|
|
||||||
** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
|
||||||
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
|
||||||
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
|
||||||
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
** DEALINGS IN THE SOFTWARE.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
//
|
|
||||||
// GLExtensions.cpp
|
|
||||||
// LoopThroughWithOpenGLCompositing
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "GLExtensions.h"
|
|
||||||
|
|
||||||
PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers;
|
|
||||||
PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers;
|
|
||||||
PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer;
|
|
||||||
PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage;
|
|
||||||
PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers;
|
|
||||||
PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers;
|
|
||||||
PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer;
|
|
||||||
PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D;
|
|
||||||
PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer;
|
|
||||||
PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus;
|
|
||||||
PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer;
|
|
||||||
PFNGLFENCESYNCPROC glFenceSync;
|
|
||||||
PFNGLCLIENTWAITSYNCPROC glClientWaitSync;
|
|
||||||
PFNGLDELETESYNCPROC glDeleteSync;
|
|
||||||
PFNGLGENBUFFERSPROC glGenBuffers;
|
|
||||||
PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
|
||||||
PFNGLBINDBUFFERPROC glBindBuffer;
|
|
||||||
PFNGLBUFFERDATAPROC glBufferData;
|
|
||||||
PFNGLMAPBUFFERPROC glMapBuffer;
|
|
||||||
PFNGLUNMAPBUFFERPROC glUnmapBuffer;
|
|
||||||
PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
|
||||||
PFNGLBINDBUFFERBASEPROC glBindBufferBase;
|
|
||||||
PFNGLACTIVETEXTUREPROC glActiveTexture;
|
|
||||||
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
|
|
||||||
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
|
|
||||||
PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
|
|
||||||
PFNGLCREATESHADERPROC glCreateShader;
|
|
||||||
PFNGLDELETESHADERPROC glDeleteShader;
|
|
||||||
PFNGLDELETEPROGRAMPROC glDeleteProgram;
|
|
||||||
PFNGLSHADERSOURCEPROC glShaderSource;
|
|
||||||
PFNGLCOMPILESHADERPROC glCompileShader;
|
|
||||||
PFNGLGETSHADERIVPROC glGetShaderiv;
|
|
||||||
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
|
|
||||||
PFNGLCREATEPROGRAMPROC glCreateProgram;
|
|
||||||
PFNGLATTACHSHADERPROC glAttachShader;
|
|
||||||
PFNGLLINKPROGRAMPROC glLinkProgram;
|
|
||||||
PFNGLGETPROGRAMIVPROC glGetProgramiv;
|
|
||||||
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
|
|
||||||
PFNGLUSEPROGRAMPROC glUseProgram;
|
|
||||||
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
|
|
||||||
PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex;
|
|
||||||
PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding;
|
|
||||||
PFNGLUNIFORM1IPROC glUniform1i;
|
|
||||||
PFNGLUNIFORM1FPROC glUniform1f;
|
|
||||||
PFNGLUNIFORM2FPROC glUniform2f;
|
|
||||||
PFNGLUNIFORM4FPROC glUniform4f;
|
|
||||||
|
|
||||||
bool ResolveGLExtensions()
|
|
||||||
{
|
|
||||||
glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC) wglGetProcAddress("glGenFramebuffers");
|
|
||||||
if (!glGenFramebuffers)
|
|
||||||
glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC) wglGetProcAddress("glGenFramebuffersEXT");
|
|
||||||
glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC) wglGetProcAddress("glGenRenderbuffers");
|
|
||||||
if (!glGenRenderbuffers)
|
|
||||||
glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC) wglGetProcAddress("glGenRenderbuffersEXT");
|
|
||||||
glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC) wglGetProcAddress("glBindRenderbuffer");
|
|
||||||
if (!glBindRenderbuffer)
|
|
||||||
glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC) wglGetProcAddress("glBindRenderbufferEXT");
|
|
||||||
glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC) wglGetProcAddress("glRenderbufferStorage");
|
|
||||||
if (!glRenderbufferStorage)
|
|
||||||
glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC) wglGetProcAddress("glRenderbufferStorageEXT");
|
|
||||||
glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC) wglGetProcAddress("glDeleteFramebuffers");
|
|
||||||
if (!glDeleteFramebuffers)
|
|
||||||
glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC) wglGetProcAddress("glDeleteFramebuffersEXT");
|
|
||||||
glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC) wglGetProcAddress("glDeleteRenderbuffers");
|
|
||||||
if (!glDeleteRenderbuffers)
|
|
||||||
glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC) wglGetProcAddress("glDeleteRenderbuffersEXT");
|
|
||||||
glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC) wglGetProcAddress("glBindFramebuffer");
|
|
||||||
if (!glBindFramebuffer)
|
|
||||||
glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC) wglGetProcAddress("glBindFramebufferEXT");
|
|
||||||
glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC) wglGetProcAddress("glFramebufferTexture2D");
|
|
||||||
if (!glFramebufferTexture2D)
|
|
||||||
glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC) wglGetProcAddress("glFramebufferTexture2DEXT");
|
|
||||||
glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC) wglGetProcAddress("glFramebufferRenderbuffer");
|
|
||||||
if (!glFramebufferRenderbuffer)
|
|
||||||
glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC) wglGetProcAddress("glFramebufferRenderbufferEXT");
|
|
||||||
glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC) wglGetProcAddress("glCheckFramebufferStatus");
|
|
||||||
if (!glCheckFramebufferStatus)
|
|
||||||
glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC) wglGetProcAddress("glCheckFramebufferStatusEXT");
|
|
||||||
glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC) wglGetProcAddress("glBlitFramebuffer");
|
|
||||||
if (!glBlitFramebuffer)
|
|
||||||
glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC) wglGetProcAddress("glBlitFramebufferEXT");
|
|
||||||
glFenceSync = (PFNGLFENCESYNCPROC) wglGetProcAddress("glFenceSync");
|
|
||||||
glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC) wglGetProcAddress("glClientWaitSync");
|
|
||||||
glDeleteSync = (PFNGLDELETESYNCPROC) wglGetProcAddress("glDeleteSync");
|
|
||||||
glGenBuffers = (PFNGLGENBUFFERSPROC) wglGetProcAddress("glGenBuffers");
|
|
||||||
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC) wglGetProcAddress("glDeleteBuffers");
|
|
||||||
glBindBuffer = (PFNGLBINDBUFFERPROC) wglGetProcAddress("glBindBuffer");
|
|
||||||
glBufferData = (PFNGLBUFFERDATAPROC) wglGetProcAddress("glBufferData");
|
|
||||||
glMapBuffer = (PFNGLMAPBUFFERPROC) wglGetProcAddress("glMapBuffer");
|
|
||||||
glUnmapBuffer = (PFNGLUNMAPBUFFERPROC) wglGetProcAddress("glUnmapBuffer");
|
|
||||||
glBufferSubData = (PFNGLBUFFERSUBDATAPROC) wglGetProcAddress("glBufferSubData");
|
|
||||||
glBindBufferBase = (PFNGLBINDBUFFERBASEPROC) wglGetProcAddress("glBindBufferBase");
|
|
||||||
glActiveTexture = (PFNGLACTIVETEXTUREPROC) wglGetProcAddress("glActiveTexture");
|
|
||||||
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC) wglGetProcAddress("glGenVertexArrays");
|
|
||||||
glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC) wglGetProcAddress("glDeleteVertexArrays");
|
|
||||||
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC) wglGetProcAddress("glBindVertexArray");
|
|
||||||
glCreateShader = (PFNGLCREATESHADERPROC) wglGetProcAddress("glCreateShader");
|
|
||||||
glDeleteShader = (PFNGLDELETESHADERPROC) wglGetProcAddress("glDeleteShader");
|
|
||||||
glDeleteProgram = (PFNGLDELETEPROGRAMPROC) wglGetProcAddress("glDeleteProgram");
|
|
||||||
glShaderSource = (PFNGLSHADERSOURCEPROC) wglGetProcAddress("glShaderSource");
|
|
||||||
glCompileShader = (PFNGLCOMPILESHADERPROC) wglGetProcAddress("glCompileShader");
|
|
||||||
glGetShaderiv = (PFNGLGETSHADERIVPROC) wglGetProcAddress("glGetShaderiv");
|
|
||||||
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC) wglGetProcAddress("glGetShaderInfoLog");
|
|
||||||
glCreateProgram = (PFNGLCREATEPROGRAMPROC) wglGetProcAddress("glCreateProgram");
|
|
||||||
glAttachShader = (PFNGLATTACHSHADERPROC) wglGetProcAddress("glAttachShader");
|
|
||||||
glLinkProgram = (PFNGLLINKPROGRAMPROC) wglGetProcAddress("glLinkProgram");
|
|
||||||
glGetProgramiv = (PFNGLGETPROGRAMIVPROC) wglGetProcAddress("glGetProgramiv");
|
|
||||||
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC) wglGetProcAddress("glGetProgramInfoLog");
|
|
||||||
glUseProgram = (PFNGLUSEPROGRAMPROC) wglGetProcAddress("glUseProgram");
|
|
||||||
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC) wglGetProcAddress("glGetUniformLocation");
|
|
||||||
glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC) wglGetProcAddress("glGetUniformBlockIndex");
|
|
||||||
glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC) wglGetProcAddress("glUniformBlockBinding");
|
|
||||||
glUniform1i = (PFNGLUNIFORM1IPROC) wglGetProcAddress("glUniform1i");
|
|
||||||
glUniform1f = (PFNGLUNIFORM1FPROC) wglGetProcAddress("glUniform1f");
|
|
||||||
glUniform2f = (PFNGLUNIFORM2FPROC) wglGetProcAddress("glUniform2f");
|
|
||||||
glUniform4f = (PFNGLUNIFORM4FPROC) wglGetProcAddress("glUniform4f");
|
|
||||||
|
|
||||||
return glGenFramebuffers
|
|
||||||
&& glGenRenderbuffers
|
|
||||||
&& glBindRenderbuffer
|
|
||||||
&& glRenderbufferStorage
|
|
||||||
&& glDeleteFramebuffers
|
|
||||||
&& glDeleteRenderbuffers
|
|
||||||
&& glBindFramebuffer
|
|
||||||
&& glFramebufferTexture2D
|
|
||||||
&& glFramebufferRenderbuffer
|
|
||||||
&& glCheckFramebufferStatus
|
|
||||||
&& glBlitFramebuffer
|
|
||||||
&& glFenceSync
|
|
||||||
&& glClientWaitSync
|
|
||||||
&& glDeleteSync
|
|
||||||
&& glGenBuffers
|
|
||||||
&& glDeleteBuffers
|
|
||||||
&& glBindBuffer
|
|
||||||
&& glBufferData
|
|
||||||
&& glMapBuffer
|
|
||||||
&& glUnmapBuffer
|
|
||||||
&& glBufferSubData
|
|
||||||
&& glBindBufferBase
|
|
||||||
&& glActiveTexture
|
|
||||||
&& glGenVertexArrays
|
|
||||||
&& glDeleteVertexArrays
|
|
||||||
&& glBindVertexArray
|
|
||||||
&& glCreateShader
|
|
||||||
&& glDeleteShader
|
|
||||||
&& glDeleteProgram
|
|
||||||
&& glShaderSource
|
|
||||||
&& glCompileShader
|
|
||||||
&& glGetShaderiv
|
|
||||||
&& glGetShaderInfoLog
|
|
||||||
&& glCreateProgram
|
|
||||||
&& glAttachShader
|
|
||||||
&& glLinkProgram
|
|
||||||
&& glGetProgramiv
|
|
||||||
&& glGetProgramInfoLog
|
|
||||||
&& glUseProgram
|
|
||||||
&& glGetUniformLocation
|
|
||||||
&& glGetUniformBlockIndex
|
|
||||||
&& glUniformBlockBinding
|
|
||||||
&& glUniform1i
|
|
||||||
&& glUniform1f
|
|
||||||
&& glUniform2f
|
|
||||||
&& glUniform4f
|
|
||||||
;
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2012 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, execute,
|
|
||||||
** and transmit the Software, and to prepare derivative works of the Software,
|
|
||||||
** and to permit third-parties to whom the Software is furnished to do so, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) the copyright notices in the Software and this entire statement,
|
|
||||||
** including the above license grant, this restriction and the following
|
|
||||||
** disclaimer, must be included in all copies of the Software, in whole or in
|
|
||||||
** part, and all derivative works of the Software, unless such copies or
|
|
||||||
** derivative works are solely in the form of machine-executable object code
|
|
||||||
** generated by a source language processor.
|
|
||||||
**
|
|
||||||
** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
|
||||||
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
|
||||||
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
|
||||||
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
** DEALINGS IN THE SOFTWARE.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
//
|
|
||||||
// GLExtensions.h
|
|
||||||
// LoopThroughWithOpenGLCompositing
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef __GLEXTENSIONS_H__
|
|
||||||
#define __GLEXTENSIONS_H__
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include <gl/gl.h>
|
|
||||||
|
|
||||||
#ifndef APIENTRYP
|
|
||||||
#define APIENTRYP APIENTRY *
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define GL_BGRA 0x80E1
|
|
||||||
#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
|
|
||||||
#define GL_STREAM_DRAW 0x88E0
|
|
||||||
#define GL_STREAM_READ 0x88E1
|
|
||||||
#define GL_STREAM_COPY 0x88E2
|
|
||||||
#define GL_DYNAMIC_DRAW 0x88E8
|
|
||||||
#define GL_UNIFORM_BUFFER 0x8A11
|
|
||||||
#define GL_RGBA8 0x8058
|
|
||||||
#define GL_RGBA16F 0x881A
|
|
||||||
#define GL_TEXTURE0 0x84C0
|
|
||||||
#define GL_ACTIVE_TEXTURE 0x84E0
|
|
||||||
#define GL_ARRAY_BUFFER 0x8892
|
|
||||||
#define GL_PIXEL_PACK_BUFFER 0x88EB
|
|
||||||
#define GL_PIXEL_UNPACK_BUFFER 0x88EC
|
|
||||||
#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF
|
|
||||||
#define GL_FRAGMENT_SHADER 0x8B30
|
|
||||||
#define GL_VERTEX_SHADER 0x8B31
|
|
||||||
#define GL_COMPILE_STATUS 0x8B81
|
|
||||||
#define GL_LINK_STATUS 0x8B82
|
|
||||||
#define GL_INVALID_INDEX 0xFFFFFFFFu
|
|
||||||
#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872
|
|
||||||
#define GL_RENDERBUFFER_EXT 0x8D41
|
|
||||||
#define GL_FRAMEBUFFER_EXT 0x8D40
|
|
||||||
#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5
|
|
||||||
#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0
|
|
||||||
#define GL_READ_FRAMEBUFFER 0x8CA8
|
|
||||||
#define GL_DRAW_FRAMEBUFFER 0x8CA9
|
|
||||||
#define GL_RENDERBUFFER 0x8D41
|
|
||||||
#define GL_FRAMEBUFFER 0x8D40
|
|
||||||
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
|
|
||||||
#define GL_COLOR_ATTACHMENT0 0x8CE0
|
|
||||||
#define GL_DEPTH_COMPONENT24 0x81A6
|
|
||||||
#define GL_CLAMP_TO_EDGE 0x812F
|
|
||||||
#define GL_DEPTH_ATTACHMENT_EXT 0x8D00
|
|
||||||
#define GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD 0x9160
|
|
||||||
#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117
|
|
||||||
#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001
|
|
||||||
#define GL_ALREADY_SIGNALED 0x911A
|
|
||||||
#define GL_TIMEOUT_EXPIRED 0x911B
|
|
||||||
#define GL_CONDITION_SATISFIED 0x911C
|
|
||||||
#define GL_WAIT_FAILED 0x911D
|
|
||||||
#define GL_READ_ONLY 0x88B8
|
|
||||||
|
|
||||||
typedef struct __GLsync *GLsync;
|
|
||||||
typedef unsigned __int64 GLuint64;
|
|
||||||
typedef ptrdiff_t GLintptr;
|
|
||||||
typedef ptrdiff_t GLsizeiptr;
|
|
||||||
typedef char GLchar;
|
|
||||||
|
|
||||||
typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer);
|
|
||||||
typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);
|
|
||||||
typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);
|
|
||||||
typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);
|
|
||||||
typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access);
|
|
||||||
typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target);
|
|
||||||
typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);
|
|
||||||
typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader);
|
|
||||||
typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void);
|
|
||||||
typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type);
|
|
||||||
typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader);
|
|
||||||
typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program);
|
|
||||||
typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params);
|
|
||||||
typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
|
|
||||||
typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params);
|
|
||||||
typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
|
|
||||||
typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name);
|
|
||||||
typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program);
|
|
||||||
typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar* *string, const GLint *length);
|
|
||||||
typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program);
|
|
||||||
typedef void (APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0);
|
|
||||||
typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0);
|
|
||||||
typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1);
|
|
||||||
typedef void (APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
|
|
||||||
typedef GLuint (APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC) (GLuint program, const GLchar* uniformBlockName);
|
|
||||||
typedef void (APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
|
|
||||||
typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture);
|
|
||||||
typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC) (GLenum target, GLuint index, GLuint buffer);
|
|
||||||
typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
|
|
||||||
typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array);
|
|
||||||
typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint* arrays);
|
|
||||||
typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint* arrays);
|
|
||||||
typedef GLsync (APIENTRYP PFNGLFENCESYNCPROC) (GLenum condition, GLbitfield flags);
|
|
||||||
typedef void (APIENTRYP PFNGLDELETESYNCPROC) (GLsync sync);
|
|
||||||
typedef GLenum (APIENTRYP PFNGLCLIENTWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout);
|
|
||||||
typedef void (APIENTRYP PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer);
|
|
||||||
typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers);
|
|
||||||
typedef void (APIENTRYP PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers);
|
|
||||||
typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
|
|
||||||
typedef void (APIENTRYP PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer);
|
|
||||||
typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint *framebuffers);
|
|
||||||
typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers);
|
|
||||||
typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target);
|
|
||||||
typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
|
||||||
typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
|
|
||||||
typedef void (APIENTRYP PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
|
||||||
|
|
||||||
extern PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers;
|
|
||||||
extern PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers;
|
|
||||||
extern PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer;
|
|
||||||
extern PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage;
|
|
||||||
extern PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers;
|
|
||||||
extern PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers;
|
|
||||||
extern PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer;
|
|
||||||
extern PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D;
|
|
||||||
extern PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer;
|
|
||||||
extern PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus;
|
|
||||||
extern PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer;
|
|
||||||
extern PFNGLFENCESYNCPROC glFenceSync;
|
|
||||||
extern PFNGLCLIENTWAITSYNCPROC glClientWaitSync;
|
|
||||||
extern PFNGLDELETESYNCPROC glDeleteSync;
|
|
||||||
extern PFNGLGENBUFFERSPROC glGenBuffers;
|
|
||||||
extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
|
||||||
extern PFNGLBINDBUFFERPROC glBindBuffer;
|
|
||||||
extern PFNGLBUFFERDATAPROC glBufferData;
|
|
||||||
extern PFNGLMAPBUFFERPROC glMapBuffer;
|
|
||||||
extern PFNGLUNMAPBUFFERPROC glUnmapBuffer;
|
|
||||||
extern PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
|
||||||
extern PFNGLBINDBUFFERBASEPROC glBindBufferBase;
|
|
||||||
extern PFNGLACTIVETEXTUREPROC glActiveTexture;
|
|
||||||
extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
|
|
||||||
extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
|
|
||||||
extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
|
|
||||||
extern PFNGLCREATESHADERPROC glCreateShader;
|
|
||||||
extern PFNGLDELETESHADERPROC glDeleteShader;
|
|
||||||
extern PFNGLDELETEPROGRAMPROC glDeleteProgram;
|
|
||||||
extern PFNGLSHADERSOURCEPROC glShaderSource;
|
|
||||||
extern PFNGLCOMPILESHADERPROC glCompileShader;
|
|
||||||
extern PFNGLGETSHADERIVPROC glGetShaderiv;
|
|
||||||
extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
|
|
||||||
extern PFNGLCREATEPROGRAMPROC glCreateProgram;
|
|
||||||
extern PFNGLATTACHSHADERPROC glAttachShader;
|
|
||||||
extern PFNGLLINKPROGRAMPROC glLinkProgram;
|
|
||||||
extern PFNGLGETPROGRAMIVPROC glGetProgramiv;
|
|
||||||
extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
|
|
||||||
extern PFNGLUSEPROGRAMPROC glUseProgram;
|
|
||||||
extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
|
|
||||||
extern PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex;
|
|
||||||
extern PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding;
|
|
||||||
extern PFNGLUNIFORM1IPROC glUniform1i;
|
|
||||||
extern PFNGLUNIFORM1FPROC glUniform1f;
|
|
||||||
extern PFNGLUNIFORM2FPROC glUniform2f;
|
|
||||||
extern PFNGLUNIFORM4FPROC glUniform4f;
|
|
||||||
|
|
||||||
bool ResolveGLExtensions();
|
|
||||||
|
|
||||||
#endif // __GLEXTENSIONS_H__
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <gl/gl.h>
|
|
||||||
|
|
||||||
constexpr GLuint kLayerInputTextureUnit = 0;
|
|
||||||
constexpr GLuint kDecodedVideoTextureUnit = 1;
|
|
||||||
constexpr GLuint kSourceHistoryTextureUnitBase = 2;
|
|
||||||
constexpr GLuint kPackedVideoTextureUnit = 2;
|
|
||||||
constexpr GLuint kGlobalParamsBindingPoint = 0;
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <gl/gl.h>
|
|
||||||
|
|
||||||
class ScopedGlShader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit ScopedGlShader(GLuint shader = 0) : mShader(shader) {}
|
|
||||||
~ScopedGlShader() { reset(); }
|
|
||||||
|
|
||||||
ScopedGlShader(const ScopedGlShader&) = delete;
|
|
||||||
ScopedGlShader& operator=(const ScopedGlShader&) = delete;
|
|
||||||
|
|
||||||
GLuint get() const { return mShader; }
|
|
||||||
GLuint release()
|
|
||||||
{
|
|
||||||
GLuint shader = mShader;
|
|
||||||
mShader = 0;
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
void reset(GLuint shader = 0)
|
|
||||||
{
|
|
||||||
if (mShader != 0)
|
|
||||||
glDeleteShader(mShader);
|
|
||||||
mShader = shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint mShader;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ScopedGlProgram
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit ScopedGlProgram(GLuint program = 0) : mProgram(program) {}
|
|
||||||
~ScopedGlProgram() { reset(); }
|
|
||||||
|
|
||||||
ScopedGlProgram(const ScopedGlProgram&) = delete;
|
|
||||||
ScopedGlProgram& operator=(const ScopedGlProgram&) = delete;
|
|
||||||
|
|
||||||
GLuint get() const { return mProgram; }
|
|
||||||
GLuint release()
|
|
||||||
{
|
|
||||||
GLuint program = mProgram;
|
|
||||||
mProgram = 0;
|
|
||||||
return program;
|
|
||||||
}
|
|
||||||
void reset(GLuint program = 0)
|
|
||||||
{
|
|
||||||
if (mProgram != 0)
|
|
||||||
glDeleteProgram(mProgram);
|
|
||||||
mProgram = program;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint mProgram;
|
|
||||||
};
|
|
||||||
@@ -1,268 +0,0 @@
|
|||||||
#include "OpenGLRenderer.h"
|
|
||||||
|
|
||||||
#include "GlRenderConstants.h"
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
void ConfigureByteFrameTexture(unsigned width, unsigned height)
|
|
||||||
{
|
|
||||||
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, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLRenderer::InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error)
|
|
||||||
{
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
|
|
||||||
glGenBuffers(1, &mTextureUploadBuffer);
|
|
||||||
|
|
||||||
glGenTextures(1, &mCaptureTexture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
|
|
||||||
ConfigureByteFrameTexture(captureTextureWidth, inputFrameHeight);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
glGenRenderbuffers(1, &mIdColorBuf);
|
|
||||||
glGenRenderbuffers(1, &mIdDepthBuf);
|
|
||||||
glGenVertexArrays(1, &mFullscreenVAO);
|
|
||||||
glGenBuffers(1, &mGlobalParamsUBO);
|
|
||||||
|
|
||||||
if (!mRenderTargets.Create(RenderTargetId::Decoded, inputFrameWidth, inputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "decode", error))
|
|
||||||
return false;
|
|
||||||
if (!mRenderTargets.Create(RenderTargetId::LayerTemp, inputFrameWidth, inputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "layer", error))
|
|
||||||
return false;
|
|
||||||
if (!mRenderTargets.Create(RenderTargetId::Composite, inputFrameWidth, inputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "composite", error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, CompositeFramebuffer());
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, mIdDepthBuf);
|
|
||||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, inputFrameWidth, inputFrameHeight);
|
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, mIdDepthBuf);
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
||||||
{
|
|
||||||
error = "Cannot initialize framebuffer.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mRenderTargets.Create(RenderTargetId::Output, outputFrameWidth, outputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "output", error))
|
|
||||||
return false;
|
|
||||||
if (!mRenderTargets.Create(RenderTargetId::OutputPack, outputPackTextureWidth, outputFrameHeight, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, "output pack", error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
glBindVertexArray(mFullscreenVAO);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsUBO);
|
|
||||||
glBufferData(GL_UNIFORM_BUFFER, 1024, NULL, GL_DYNAMIC_DRAW);
|
|
||||||
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO);
|
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
|
||||||
|
|
||||||
mResourcesInitialized = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderer::SetDecodeShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader)
|
|
||||||
{
|
|
||||||
mDecodeProgram = program;
|
|
||||||
mDecodeVertexShader = vertexShader;
|
|
||||||
mDecodeFragmentShader = fragmentShader;
|
|
||||||
mDecodePackedResolutionLocation = program != 0 ? glGetUniformLocation(program, "uPackedVideoResolution") : -1;
|
|
||||||
mDecodeDecodedResolutionLocation = program != 0 ? glGetUniformLocation(program, "uDecodedVideoResolution") : -1;
|
|
||||||
mDecodeInputPixelFormatLocation = program != 0 ? glGetUniformLocation(program, "uInputPixelFormat") : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderer::SetOutputPackShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader)
|
|
||||||
{
|
|
||||||
mOutputPackProgram = program;
|
|
||||||
mOutputPackVertexShader = vertexShader;
|
|
||||||
mOutputPackFragmentShader = fragmentShader;
|
|
||||||
mOutputPackResolutionLocation = program != 0 ? glGetUniformLocation(program, "uOutputVideoResolution") : -1;
|
|
||||||
mOutputPackActiveWordsLocation = program != 0 ? glGetUniformLocation(program, "uActiveV210Words") : -1;
|
|
||||||
mOutputPackFormatLocation = program != 0 ? glGetUniformLocation(program, "uOutputPackFormat") : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLRenderer::ReserveTemporaryRenderTargets(std::size_t count, unsigned width, unsigned height, std::string& error)
|
|
||||||
{
|
|
||||||
return mRenderTargets.ReserveTemporaryTargets(count, width, height, GL_RGBA16F, GL_RGBA, GL_FLOAT, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderer::ResizeView(int width, int height)
|
|
||||||
{
|
|
||||||
mViewWidth = width;
|
|
||||||
mViewHeight = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderer::PresentToWindow(HDC hdc, unsigned outputFrameWidth, unsigned outputFrameHeight)
|
|
||||||
{
|
|
||||||
int destWidth = mViewWidth;
|
|
||||||
int destHeight = mViewHeight;
|
|
||||||
int destX = 0;
|
|
||||||
int destY = 0;
|
|
||||||
|
|
||||||
if (outputFrameWidth > 0 && outputFrameHeight > 0 && mViewWidth > 0 && mViewHeight > 0)
|
|
||||||
{
|
|
||||||
const double frameAspect = static_cast<double>(outputFrameWidth) / static_cast<double>(outputFrameHeight);
|
|
||||||
const double viewAspect = static_cast<double>(mViewWidth) / static_cast<double>(mViewHeight);
|
|
||||||
|
|
||||||
if (viewAspect > frameAspect)
|
|
||||||
{
|
|
||||||
destHeight = mViewHeight;
|
|
||||||
destWidth = static_cast<int>(destHeight * frameAspect + 0.5);
|
|
||||||
destX = (mViewWidth - destWidth) / 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
destWidth = mViewWidth;
|
|
||||||
destHeight = static_cast<int>(destWidth / frameAspect + 0.5);
|
|
||||||
destY = (mViewHeight - destHeight) / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, OutputFramebuffer());
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
||||||
glDisable(GL_SCISSOR_TEST);
|
|
||||||
glViewport(0, 0, mViewWidth, mViewHeight);
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
glBlitFramebuffer(0, 0, outputFrameWidth, outputFrameHeight, destX, destY, destX + destWidth, destY + destHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
||||||
|
|
||||||
SwapBuffers(hdc);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderer::DestroyResources()
|
|
||||||
{
|
|
||||||
if (mFullscreenVAO != 0)
|
|
||||||
glDeleteVertexArrays(1, &mFullscreenVAO);
|
|
||||||
if (mGlobalParamsUBO != 0)
|
|
||||||
glDeleteBuffers(1, &mGlobalParamsUBO);
|
|
||||||
if (mIdColorBuf != 0)
|
|
||||||
glDeleteRenderbuffers(1, &mIdColorBuf);
|
|
||||||
if (mIdDepthBuf != 0)
|
|
||||||
glDeleteRenderbuffers(1, &mIdDepthBuf);
|
|
||||||
if (mCaptureTexture != 0)
|
|
||||||
glDeleteTextures(1, &mCaptureTexture);
|
|
||||||
if (mTextureUploadBuffer != 0)
|
|
||||||
glDeleteBuffers(1, &mTextureUploadBuffer);
|
|
||||||
mRenderTargets.Destroy();
|
|
||||||
|
|
||||||
mFullscreenVAO = 0;
|
|
||||||
mGlobalParamsUBO = 0;
|
|
||||||
mIdColorBuf = 0;
|
|
||||||
mIdDepthBuf = 0;
|
|
||||||
mCaptureTexture = 0;
|
|
||||||
mTextureUploadBuffer = 0;
|
|
||||||
mGlobalParamsUBOSize = 0;
|
|
||||||
mResourcesInitialized = false;
|
|
||||||
|
|
||||||
mTemporalHistory.DestroyResources();
|
|
||||||
mFeedbackBuffers.DestroyResources();
|
|
||||||
DestroyLayerPrograms();
|
|
||||||
DestroyDecodeShaderProgram();
|
|
||||||
DestroyOutputPackShaderProgram();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderer::DestroySingleLayerProgram(LayerProgram& layerProgram)
|
|
||||||
{
|
|
||||||
for (LayerProgram::PassProgram& passProgram : layerProgram.passes)
|
|
||||||
{
|
|
||||||
for (LayerProgram::TextureBinding& binding : passProgram.textureBindings)
|
|
||||||
{
|
|
||||||
if (binding.texture != 0)
|
|
||||||
{
|
|
||||||
glDeleteTextures(1, &binding.texture);
|
|
||||||
binding.texture = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
passProgram.textureBindings.clear();
|
|
||||||
|
|
||||||
for (LayerProgram::TextBinding& binding : passProgram.textBindings)
|
|
||||||
{
|
|
||||||
if (binding.texture != 0)
|
|
||||||
{
|
|
||||||
glDeleteTextures(1, &binding.texture);
|
|
||||||
binding.texture = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
passProgram.textBindings.clear();
|
|
||||||
|
|
||||||
if (passProgram.program != 0)
|
|
||||||
{
|
|
||||||
glDeleteProgram(passProgram.program);
|
|
||||||
passProgram.program = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (passProgram.fragmentShader != 0)
|
|
||||||
{
|
|
||||||
glDeleteShader(passProgram.fragmentShader);
|
|
||||||
passProgram.fragmentShader = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (passProgram.vertexShader != 0)
|
|
||||||
{
|
|
||||||
glDeleteShader(passProgram.vertexShader);
|
|
||||||
passProgram.vertexShader = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
layerProgram.passes.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderer::DestroyLayerPrograms()
|
|
||||||
{
|
|
||||||
for (LayerProgram& layerProgram : mLayerPrograms)
|
|
||||||
DestroySingleLayerProgram(layerProgram);
|
|
||||||
mLayerPrograms.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderer::DestroyDecodeShaderProgram()
|
|
||||||
{
|
|
||||||
if (mDecodeProgram != 0)
|
|
||||||
{
|
|
||||||
glDeleteProgram(mDecodeProgram);
|
|
||||||
mDecodeProgram = 0;
|
|
||||||
}
|
|
||||||
mDecodePackedResolutionLocation = -1;
|
|
||||||
mDecodeDecodedResolutionLocation = -1;
|
|
||||||
mDecodeInputPixelFormatLocation = -1;
|
|
||||||
|
|
||||||
if (mDecodeFragmentShader != 0)
|
|
||||||
{
|
|
||||||
glDeleteShader(mDecodeFragmentShader);
|
|
||||||
mDecodeFragmentShader = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mDecodeVertexShader != 0)
|
|
||||||
{
|
|
||||||
glDeleteShader(mDecodeVertexShader);
|
|
||||||
mDecodeVertexShader = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLRenderer::DestroyOutputPackShaderProgram()
|
|
||||||
{
|
|
||||||
if (mOutputPackProgram != 0)
|
|
||||||
{
|
|
||||||
glDeleteProgram(mOutputPackProgram);
|
|
||||||
mOutputPackProgram = 0;
|
|
||||||
}
|
|
||||||
mOutputPackResolutionLocation = -1;
|
|
||||||
mOutputPackActiveWordsLocation = -1;
|
|
||||||
mOutputPackFormatLocation = -1;
|
|
||||||
|
|
||||||
if (mOutputPackFragmentShader != 0)
|
|
||||||
{
|
|
||||||
glDeleteShader(mOutputPackFragmentShader);
|
|
||||||
mOutputPackFragmentShader = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mOutputPackVertexShader != 0)
|
|
||||||
{
|
|
||||||
glDeleteShader(mOutputPackVertexShader);
|
|
||||||
mOutputPackVertexShader = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "GLExtensions.h"
|
|
||||||
#include "RenderTargetPool.h"
|
|
||||||
#include "ShaderFeedbackBuffers.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
#include "TemporalHistoryBuffers.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <gl/gl.h>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class OpenGLRenderer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct LayerProgram
|
|
||||||
{
|
|
||||||
struct TextureBinding
|
|
||||||
{
|
|
||||||
std::string samplerName;
|
|
||||||
std::filesystem::path sourcePath;
|
|
||||||
GLuint texture = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TextBinding
|
|
||||||
{
|
|
||||||
std::string parameterId;
|
|
||||||
std::string samplerName;
|
|
||||||
std::string fontId;
|
|
||||||
GLuint texture = 0;
|
|
||||||
std::string renderedText;
|
|
||||||
unsigned renderedWidth = 0;
|
|
||||||
unsigned renderedHeight = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string layerId;
|
|
||||||
std::string shaderId;
|
|
||||||
|
|
||||||
struct PassProgram
|
|
||||||
{
|
|
||||||
std::string passId;
|
|
||||||
std::vector<std::string> inputNames;
|
|
||||||
std::string outputName;
|
|
||||||
GLuint shaderTextureBase = 0;
|
|
||||||
GLuint program = 0;
|
|
||||||
GLuint vertexShader = 0;
|
|
||||||
GLuint fragmentShader = 0;
|
|
||||||
std::vector<TextureBinding> textureBindings;
|
|
||||||
std::vector<TextBinding> textBindings;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<PassProgram> passes;
|
|
||||||
};
|
|
||||||
|
|
||||||
GLuint CaptureTexture() const { return mCaptureTexture; }
|
|
||||||
GLuint DecodedTexture() const { return mRenderTargets.Texture(RenderTargetId::Decoded); }
|
|
||||||
GLuint LayerTempTexture() const { return mRenderTargets.Texture(RenderTargetId::LayerTemp); }
|
|
||||||
GLuint CompositeTexture() const { return mRenderTargets.Texture(RenderTargetId::Composite); }
|
|
||||||
GLuint OutputTexture() const { return mRenderTargets.Texture(RenderTargetId::Output); }
|
|
||||||
GLuint OutputPackTexture() const { return mRenderTargets.Texture(RenderTargetId::OutputPack); }
|
|
||||||
GLuint TextureUploadBuffer() const { return mTextureUploadBuffer; }
|
|
||||||
GLuint DecodeFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::Decoded); }
|
|
||||||
GLuint LayerTempFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::LayerTemp); }
|
|
||||||
GLuint CompositeFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::Composite); }
|
|
||||||
GLuint OutputFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::Output); }
|
|
||||||
GLuint OutputPackFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::OutputPack); }
|
|
||||||
GLuint FullscreenVertexArray() const { return mFullscreenVAO; }
|
|
||||||
GLuint GlobalParamsUBO() const { return mGlobalParamsUBO; }
|
|
||||||
GLuint DecodeProgram() const { return mDecodeProgram; }
|
|
||||||
GLuint OutputPackProgram() const { return mOutputPackProgram; }
|
|
||||||
GLint DecodePackedResolutionLocation() const { return mDecodePackedResolutionLocation; }
|
|
||||||
GLint DecodeDecodedResolutionLocation() const { return mDecodeDecodedResolutionLocation; }
|
|
||||||
GLint DecodeInputPixelFormatLocation() const { return mDecodeInputPixelFormatLocation; }
|
|
||||||
GLint OutputPackResolutionLocation() const { return mOutputPackResolutionLocation; }
|
|
||||||
GLint OutputPackActiveWordsLocation() const { return mOutputPackActiveWordsLocation; }
|
|
||||||
GLint OutputPackFormatLocation() const { return mOutputPackFormatLocation; }
|
|
||||||
GLsizeiptr GlobalParamsUBOSize() const { return mGlobalParamsUBOSize; }
|
|
||||||
void SetGlobalParamsUBOSize(GLsizeiptr size) { mGlobalParamsUBOSize = size; }
|
|
||||||
bool ResourcesInitialized() const { return mResourcesInitialized; }
|
|
||||||
void ReplaceLayerPrograms(std::vector<LayerProgram>& newPrograms) { mLayerPrograms.swap(newPrograms); }
|
|
||||||
std::vector<LayerProgram>& LayerPrograms() { return mLayerPrograms; }
|
|
||||||
const std::vector<LayerProgram>& LayerPrograms() const { return mLayerPrograms; }
|
|
||||||
bool ReserveTemporaryRenderTargets(std::size_t count, unsigned width, unsigned height, std::string& error);
|
|
||||||
const RenderTarget& TemporaryRenderTarget(std::size_t index) const { return mRenderTargets.TemporaryTarget(index); }
|
|
||||||
std::size_t TemporaryRenderTargetCount() const { return mRenderTargets.TemporaryTargetCount(); }
|
|
||||||
TemporalHistoryBuffers& TemporalHistory() { return mTemporalHistory; }
|
|
||||||
const TemporalHistoryBuffers& TemporalHistory() const { return mTemporalHistory; }
|
|
||||||
ShaderFeedbackBuffers& FeedbackBuffers() { return mFeedbackBuffers; }
|
|
||||||
const ShaderFeedbackBuffers& FeedbackBuffers() const { return mFeedbackBuffers; }
|
|
||||||
void SetDecodeShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader);
|
|
||||||
void SetOutputPackShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader);
|
|
||||||
bool InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error);
|
|
||||||
void ResizeView(int width, int height);
|
|
||||||
void PresentToWindow(HDC hdc, unsigned outputFrameWidth, unsigned outputFrameHeight);
|
|
||||||
void DestroyResources();
|
|
||||||
void DestroySingleLayerProgram(LayerProgram& layerProgram);
|
|
||||||
void DestroyLayerPrograms();
|
|
||||||
void DestroyDecodeShaderProgram();
|
|
||||||
void DestroyOutputPackShaderProgram();
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint mCaptureTexture = 0;
|
|
||||||
GLuint mTextureUploadBuffer = 0;
|
|
||||||
GLuint mIdColorBuf = 0;
|
|
||||||
GLuint mIdDepthBuf = 0;
|
|
||||||
GLuint mFullscreenVAO = 0;
|
|
||||||
GLuint mGlobalParamsUBO = 0;
|
|
||||||
GLuint mDecodeProgram = 0;
|
|
||||||
GLuint mDecodeVertexShader = 0;
|
|
||||||
GLuint mDecodeFragmentShader = 0;
|
|
||||||
GLint mDecodePackedResolutionLocation = -1;
|
|
||||||
GLint mDecodeDecodedResolutionLocation = -1;
|
|
||||||
GLint mDecodeInputPixelFormatLocation = -1;
|
|
||||||
GLuint mOutputPackProgram = 0;
|
|
||||||
GLuint mOutputPackVertexShader = 0;
|
|
||||||
GLuint mOutputPackFragmentShader = 0;
|
|
||||||
GLint mOutputPackResolutionLocation = -1;
|
|
||||||
GLint mOutputPackActiveWordsLocation = -1;
|
|
||||||
GLint mOutputPackFormatLocation = -1;
|
|
||||||
GLsizeiptr mGlobalParamsUBOSize = 0;
|
|
||||||
bool mResourcesInitialized = false;
|
|
||||||
int mViewWidth = 0;
|
|
||||||
int mViewHeight = 0;
|
|
||||||
std::vector<LayerProgram> mLayerPrograms;
|
|
||||||
RenderTargetPool mRenderTargets;
|
|
||||||
TemporalHistoryBuffers mTemporalHistory;
|
|
||||||
ShaderFeedbackBuffers mFeedbackBuffers;
|
|
||||||
};
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
#include "RenderTargetPool.h"
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
void ConfigureRenderTargetTexture(
|
|
||||||
unsigned width,
|
|
||||||
unsigned height,
|
|
||||||
GLenum internalFormat,
|
|
||||||
GLenum pixelFormat,
|
|
||||||
GLenum pixelType)
|
|
||||||
{
|
|
||||||
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, internalFormat, width, height, 0, pixelFormat, pixelType, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderTargetPool::Create(
|
|
||||||
RenderTargetId id,
|
|
||||||
unsigned width,
|
|
||||||
unsigned height,
|
|
||||||
GLenum internalFormat,
|
|
||||||
GLenum pixelFormat,
|
|
||||||
GLenum pixelType,
|
|
||||||
const char* errorPrefix,
|
|
||||||
std::string& error)
|
|
||||||
{
|
|
||||||
RenderTarget& target = mTargets[TargetIndex(id)];
|
|
||||||
if (target.texture != 0 || target.framebuffer != 0)
|
|
||||||
{
|
|
||||||
error = std::string(errorPrefix) + " render target was already initialized.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
glGenTextures(1, &target.texture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, target.texture);
|
|
||||||
ConfigureRenderTargetTexture(width, height, internalFormat, pixelFormat, pixelType);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
glGenFramebuffers(1, &target.framebuffer);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, target.framebuffer);
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.texture, 0);
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
||||||
{
|
|
||||||
error = std::string("Cannot initialize ") + errorPrefix + " framebuffer.";
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
target.width = width;
|
|
||||||
target.height = height;
|
|
||||||
target.internalFormat = internalFormat;
|
|
||||||
target.pixelFormat = pixelFormat;
|
|
||||||
target.pixelType = pixelType;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderTargetPool::ReserveTemporaryTargets(
|
|
||||||
std::size_t count,
|
|
||||||
unsigned width,
|
|
||||||
unsigned height,
|
|
||||||
GLenum internalFormat,
|
|
||||||
GLenum pixelFormat,
|
|
||||||
GLenum pixelType,
|
|
||||||
std::string& error)
|
|
||||||
{
|
|
||||||
if (mTemporaryTargets.size() == count)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
DestroyTemporaryTargets();
|
|
||||||
|
|
||||||
mTemporaryTargets.resize(count);
|
|
||||||
for (std::size_t index = 0; index < mTemporaryTargets.size(); ++index)
|
|
||||||
{
|
|
||||||
RenderTarget& target = mTemporaryTargets[index];
|
|
||||||
glGenTextures(1, &target.texture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, target.texture);
|
|
||||||
ConfigureRenderTargetTexture(width, height, internalFormat, pixelFormat, pixelType);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
glGenFramebuffers(1, &target.framebuffer);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, target.framebuffer);
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.texture, 0);
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
||||||
{
|
|
||||||
error = "Cannot initialize temporary render target.";
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
target.width = width;
|
|
||||||
target.height = height;
|
|
||||||
target.internalFormat = internalFormat;
|
|
||||||
target.pixelFormat = pixelFormat;
|
|
||||||
target.pixelType = pixelType;
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderTargetPool::DestroyTemporaryTargets()
|
|
||||||
{
|
|
||||||
for (RenderTarget& target : mTemporaryTargets)
|
|
||||||
{
|
|
||||||
if (target.framebuffer != 0)
|
|
||||||
glDeleteFramebuffers(1, &target.framebuffer);
|
|
||||||
if (target.texture != 0)
|
|
||||||
glDeleteTextures(1, &target.texture);
|
|
||||||
}
|
|
||||||
mTemporaryTargets.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderTargetPool::Destroy()
|
|
||||||
{
|
|
||||||
for (RenderTarget& target : mTargets)
|
|
||||||
{
|
|
||||||
if (target.framebuffer != 0)
|
|
||||||
glDeleteFramebuffers(1, &target.framebuffer);
|
|
||||||
if (target.texture != 0)
|
|
||||||
glDeleteTextures(1, &target.texture);
|
|
||||||
target = RenderTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
DestroyTemporaryTargets();
|
|
||||||
}
|
|
||||||
|
|
||||||
const RenderTarget& RenderTargetPool::Target(RenderTargetId id) const
|
|
||||||
{
|
|
||||||
return mTargets[TargetIndex(id)];
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "GLExtensions.h"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
enum class RenderTargetId
|
|
||||||
{
|
|
||||||
Decoded,
|
|
||||||
LayerTemp,
|
|
||||||
Composite,
|
|
||||||
Output,
|
|
||||||
OutputPack,
|
|
||||||
Count
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderTarget
|
|
||||||
{
|
|
||||||
GLuint texture = 0;
|
|
||||||
GLuint framebuffer = 0;
|
|
||||||
unsigned width = 0;
|
|
||||||
unsigned height = 0;
|
|
||||||
GLenum internalFormat = GL_RGBA8;
|
|
||||||
GLenum pixelFormat = GL_RGBA;
|
|
||||||
GLenum pixelType = GL_UNSIGNED_BYTE;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RenderTargetPool
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
bool Create(
|
|
||||||
RenderTargetId id,
|
|
||||||
unsigned width,
|
|
||||||
unsigned height,
|
|
||||||
GLenum internalFormat,
|
|
||||||
GLenum pixelFormat,
|
|
||||||
GLenum pixelType,
|
|
||||||
const char* errorPrefix,
|
|
||||||
std::string& error);
|
|
||||||
bool ReserveTemporaryTargets(
|
|
||||||
std::size_t count,
|
|
||||||
unsigned width,
|
|
||||||
unsigned height,
|
|
||||||
GLenum internalFormat,
|
|
||||||
GLenum pixelFormat,
|
|
||||||
GLenum pixelType,
|
|
||||||
std::string& error);
|
|
||||||
void DestroyTemporaryTargets();
|
|
||||||
void Destroy();
|
|
||||||
|
|
||||||
GLuint Texture(RenderTargetId id) const { return Target(id).texture; }
|
|
||||||
GLuint Framebuffer(RenderTargetId id) const { return Target(id).framebuffer; }
|
|
||||||
const RenderTarget& Target(RenderTargetId id) const;
|
|
||||||
const RenderTarget& TemporaryTarget(std::size_t index) const { return mTemporaryTargets[index]; }
|
|
||||||
std::size_t TemporaryTargetCount() const { return mTemporaryTargets.size(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::size_t TargetIndex(RenderTargetId id) { return static_cast<std::size_t>(id); }
|
|
||||||
|
|
||||||
std::array<RenderTarget, static_cast<std::size_t>(RenderTargetId::Count)> mTargets;
|
|
||||||
std::vector<RenderTarget> mTemporaryTargets;
|
|
||||||
};
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
#include "GlShaderSources.h"
|
|
||||||
|
|
||||||
const char* kFullscreenTriangleVertexShaderSource =
|
|
||||||
"#version 430 core\n"
|
|
||||||
"out vec2 vTexCoord;\n"
|
|
||||||
"void main()\n"
|
|
||||||
"{\n"
|
|
||||||
" vec2 positions[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\n"
|
|
||||||
" vec2 texCoords[3] = vec2[3](vec2(0.0, 0.0), vec2(2.0, 0.0), vec2(0.0, 2.0));\n"
|
|
||||||
" gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);\n"
|
|
||||||
" vTexCoord = texCoords[gl_VertexID];\n"
|
|
||||||
"}\n";
|
|
||||||
|
|
||||||
const char* kDecodeFragmentShaderSource =
|
|
||||||
"#version 430 core\n"
|
|
||||||
"layout(binding = 2) uniform sampler2D uPackedVideoInput;\n"
|
|
||||||
"uniform vec2 uPackedVideoResolution;\n"
|
|
||||||
"uniform vec2 uDecodedVideoResolution;\n"
|
|
||||||
"uniform int uInputPixelFormat;\n"
|
|
||||||
"in vec2 vTexCoord;\n"
|
|
||||||
"layout(location = 0) out vec4 fragColor;\n"
|
|
||||||
"vec4 rec709YCbCr2rgba(float Y, float Cb, float Cr, float a)\n"
|
|
||||||
"{\n"
|
|
||||||
" Y = (Y * 256.0 - 16.0) / 219.0;\n"
|
|
||||||
" Cb = (Cb * 256.0 - 16.0) / 224.0 - 0.5;\n"
|
|
||||||
" Cr = (Cr * 256.0 - 16.0) / 224.0 - 0.5;\n"
|
|
||||||
" return vec4(Y + 1.5748 * Cr, Y - 0.1873 * Cb - 0.4681 * Cr, Y + 1.8556 * Cb, a);\n"
|
|
||||||
"}\n"
|
|
||||||
"vec4 rec709YCbCr10_2rgba(float Y, float Cb, float Cr, float a)\n"
|
|
||||||
"{\n"
|
|
||||||
" Y = (Y - 64.0) / 876.0;\n"
|
|
||||||
" Cb = (Cb - 64.0) / 896.0 - 0.5;\n"
|
|
||||||
" Cr = (Cr - 64.0) / 896.0 - 0.5;\n"
|
|
||||||
" return vec4(Y + 1.5748 * Cr, Y - 0.1873 * Cb - 0.4681 * Cr, Y + 1.8556 * Cb, a);\n"
|
|
||||||
"}\n"
|
|
||||||
"uint loadV210Word(ivec2 coord)\n"
|
|
||||||
"{\n"
|
|
||||||
" vec4 b = round(texelFetch(uPackedVideoInput, coord, 0) * 255.0);\n"
|
|
||||||
" return uint(b.r) | (uint(b.g) << 8) | (uint(b.b) << 16) | (uint(b.a) << 24);\n"
|
|
||||||
"}\n"
|
|
||||||
"float v210Component(uint word, int index)\n"
|
|
||||||
"{\n"
|
|
||||||
" return float((word >> uint(index * 10)) & 1023u);\n"
|
|
||||||
"}\n"
|
|
||||||
"vec4 decodeUyvy8(ivec2 outputCoord, ivec2 packedSize)\n"
|
|
||||||
"{\n"
|
|
||||||
" ivec2 packedCoord = ivec2(clamp(outputCoord.x / 2, 0, packedSize.x - 1), clamp(outputCoord.y, 0, packedSize.y - 1));\n"
|
|
||||||
" vec4 macroPixel = texelFetch(uPackedVideoInput, packedCoord, 0);\n"
|
|
||||||
" float ySample = (outputCoord.x & 1) != 0 ? macroPixel.a : macroPixel.g;\n"
|
|
||||||
" return rec709YCbCr2rgba(ySample, macroPixel.b, macroPixel.r, 1.0);\n"
|
|
||||||
"}\n"
|
|
||||||
"vec4 decodeV210(ivec2 outputCoord, ivec2 packedSize)\n"
|
|
||||||
"{\n"
|
|
||||||
" int group = outputCoord.x / 6;\n"
|
|
||||||
" int pixel = outputCoord.x - group * 6;\n"
|
|
||||||
" int wordBase = group * 4;\n"
|
|
||||||
" ivec2 rowBase = ivec2(wordBase, clamp(outputCoord.y, 0, packedSize.y - 1));\n"
|
|
||||||
" uint w0 = loadV210Word(ivec2(min(rowBase.x + 0, packedSize.x - 1), rowBase.y));\n"
|
|
||||||
" uint w1 = loadV210Word(ivec2(min(rowBase.x + 1, packedSize.x - 1), rowBase.y));\n"
|
|
||||||
" uint w2 = loadV210Word(ivec2(min(rowBase.x + 2, packedSize.x - 1), rowBase.y));\n"
|
|
||||||
" uint w3 = loadV210Word(ivec2(min(rowBase.x + 3, packedSize.x - 1), rowBase.y));\n"
|
|
||||||
" float y0 = v210Component(w0, 1);\n"
|
|
||||||
" float y1 = v210Component(w1, 0);\n"
|
|
||||||
" float y2 = v210Component(w1, 2);\n"
|
|
||||||
" float y3 = v210Component(w2, 1);\n"
|
|
||||||
" float y4 = v210Component(w3, 0);\n"
|
|
||||||
" float y5 = v210Component(w3, 2);\n"
|
|
||||||
" float cb0 = v210Component(w0, 0);\n"
|
|
||||||
" float cr0 = v210Component(w0, 2);\n"
|
|
||||||
" float cb2 = v210Component(w1, 1);\n"
|
|
||||||
" float cr2 = v210Component(w2, 0);\n"
|
|
||||||
" float cb4 = v210Component(w2, 2);\n"
|
|
||||||
" float cr4 = v210Component(w3, 1);\n"
|
|
||||||
" float ySample = pixel == 0 ? y0 : pixel == 1 ? y1 : pixel == 2 ? y2 : pixel == 3 ? y3 : pixel == 4 ? y4 : y5;\n"
|
|
||||||
" float cbSample = pixel < 2 ? cb0 : pixel < 4 ? cb2 : cb4;\n"
|
|
||||||
" float crSample = pixel < 2 ? cr0 : pixel < 4 ? cr2 : cr4;\n"
|
|
||||||
" return rec709YCbCr10_2rgba(ySample, cbSample, crSample, 1.0);\n"
|
|
||||||
"}\n"
|
|
||||||
"void main()\n"
|
|
||||||
"{\n"
|
|
||||||
" vec2 correctedUv = vec2(vTexCoord.x, 1.0 - vTexCoord.y);\n"
|
|
||||||
" ivec2 decodedSize = ivec2(max(uDecodedVideoResolution, vec2(1.0, 1.0)));\n"
|
|
||||||
" ivec2 outputCoord = clamp(ivec2(correctedUv * vec2(decodedSize)), ivec2(0, 0), decodedSize - ivec2(1, 1));\n"
|
|
||||||
" ivec2 packedSize = ivec2(max(uPackedVideoResolution, vec2(1.0, 1.0)));\n"
|
|
||||||
" fragColor = uInputPixelFormat == 1 ? decodeV210(outputCoord, packedSize) : decodeUyvy8(outputCoord, packedSize);\n"
|
|
||||||
"}\n";
|
|
||||||
|
|
||||||
const char* kOutputPackFragmentShaderSource =
|
|
||||||
"#version 430 core\n"
|
|
||||||
"layout(binding = 0) uniform sampler2D uOutputRgb;\n"
|
|
||||||
"uniform vec2 uOutputVideoResolution;\n"
|
|
||||||
"uniform float uActiveV210Words;\n"
|
|
||||||
"uniform int uOutputPackFormat;\n"
|
|
||||||
"in vec2 vTexCoord;\n"
|
|
||||||
"layout(location = 0) out vec4 fragColor;\n"
|
|
||||||
"vec4 rgbaAt(int x, int y)\n"
|
|
||||||
"{\n"
|
|
||||||
" ivec2 size = ivec2(max(uOutputVideoResolution, vec2(1.0, 1.0)));\n"
|
|
||||||
" return clamp(texelFetch(uOutputRgb, ivec2(clamp(x, 0, size.x - 1), clamp(y, 0, size.y - 1)), 0), vec4(0.0), vec4(1.0));\n"
|
|
||||||
"}\n"
|
|
||||||
"vec3 rgbAt(int x, int y)\n"
|
|
||||||
"{\n"
|
|
||||||
" return rgbaAt(x, y).rgb;\n"
|
|
||||||
"}\n"
|
|
||||||
"vec3 rgbToLegalYcbcr10(vec3 rgb)\n"
|
|
||||||
"{\n"
|
|
||||||
" float y = dot(rgb, vec3(0.2126, 0.7152, 0.0722));\n"
|
|
||||||
" float cb = (rgb.b - y) / 1.8556 + 0.5;\n"
|
|
||||||
" float cr = (rgb.r - y) / 1.5748 + 0.5;\n"
|
|
||||||
" return vec3(clamp(round(64.0 + y * 876.0), 64.0, 940.0), clamp(round(64.0 + cb * 896.0), 64.0, 960.0), clamp(round(64.0 + cr * 896.0), 64.0, 960.0));\n"
|
|
||||||
"}\n"
|
|
||||||
"uint makeWord(float a, float b, float c)\n"
|
|
||||||
"{\n"
|
|
||||||
" return (uint(a) & 1023u) | ((uint(b) & 1023u) << 10) | ((uint(c) & 1023u) << 20);\n"
|
|
||||||
"}\n"
|
|
||||||
"vec4 wordToBytes(uint word)\n"
|
|
||||||
"{\n"
|
|
||||||
" return vec4(float(word & 255u), float((word >> 8) & 255u), float((word >> 16) & 255u), float((word >> 24) & 255u)) / 255.0;\n"
|
|
||||||
"}\n"
|
|
||||||
"vec4 bigEndianWordToBytes(uint word)\n"
|
|
||||||
"{\n"
|
|
||||||
" return vec4(float((word >> 24) & 255u), float((word >> 16) & 255u), float((word >> 8) & 255u), float(word & 255u)) / 255.0;\n"
|
|
||||||
"}\n"
|
|
||||||
"vec4 packAy10Word(ivec2 outCoord)\n"
|
|
||||||
"{\n"
|
|
||||||
" ivec2 size = ivec2(max(uOutputVideoResolution, vec2(1.0, 1.0)));\n"
|
|
||||||
" if (outCoord.x >= size.x)\n"
|
|
||||||
" return vec4(0.0);\n"
|
|
||||||
" int pixelBase = (outCoord.x / 2) * 2;\n"
|
|
||||||
" int y = outCoord.y;\n"
|
|
||||||
" vec4 rgba0 = rgbaAt(pixelBase + 0, y);\n"
|
|
||||||
" vec4 rgba1 = rgbaAt(pixelBase + 1, y);\n"
|
|
||||||
" vec3 c0 = rgbToLegalYcbcr10(rgba0.rgb);\n"
|
|
||||||
" vec3 c1 = rgbToLegalYcbcr10(rgba1.rgb);\n"
|
|
||||||
" float chroma = (outCoord.x & 1) == 0 ? round((c0.y + c1.y) * 0.5) : round((c0.z + c1.z) * 0.5);\n"
|
|
||||||
" float alpha = round(clamp(((outCoord.x & 1) == 0 ? rgba0.a : rgba1.a), 0.0, 1.0) * 1023.0);\n"
|
|
||||||
" float luma = (outCoord.x & 1) == 0 ? c0.x : c1.x;\n"
|
|
||||||
" uint word = ((uint(luma) & 1023u) << 22) | ((uint(chroma) & 1023u) << 12) | ((uint(alpha) & 1023u) << 2);\n"
|
|
||||||
" return bigEndianWordToBytes(word);\n"
|
|
||||||
"}\n"
|
|
||||||
"void main()\n"
|
|
||||||
"{\n"
|
|
||||||
" ivec2 outCoord = ivec2(gl_FragCoord.xy);\n"
|
|
||||||
" if (uOutputPackFormat == 2)\n"
|
|
||||||
" {\n"
|
|
||||||
" fragColor = packAy10Word(outCoord);\n"
|
|
||||||
" return;\n"
|
|
||||||
" }\n"
|
|
||||||
" if (float(outCoord.x) >= uActiveV210Words)\n"
|
|
||||||
" {\n"
|
|
||||||
" fragColor = vec4(0.0);\n"
|
|
||||||
" return;\n"
|
|
||||||
" }\n"
|
|
||||||
" int group = outCoord.x / 4;\n"
|
|
||||||
" int wordIndex = outCoord.x - group * 4;\n"
|
|
||||||
" int pixelBase = group * 6;\n"
|
|
||||||
" int y = outCoord.y;\n"
|
|
||||||
" vec3 c0 = rgbToLegalYcbcr10(rgbAt(pixelBase + 0, y));\n"
|
|
||||||
" vec3 c1 = rgbToLegalYcbcr10(rgbAt(pixelBase + 1, y));\n"
|
|
||||||
" vec3 c2 = rgbToLegalYcbcr10(rgbAt(pixelBase + 2, y));\n"
|
|
||||||
" vec3 c3 = rgbToLegalYcbcr10(rgbAt(pixelBase + 3, y));\n"
|
|
||||||
" vec3 c4 = rgbToLegalYcbcr10(rgbAt(pixelBase + 4, y));\n"
|
|
||||||
" vec3 c5 = rgbToLegalYcbcr10(rgbAt(pixelBase + 5, y));\n"
|
|
||||||
" float cb0 = round((c0.y + c1.y) * 0.5);\n"
|
|
||||||
" float cr0 = round((c0.z + c1.z) * 0.5);\n"
|
|
||||||
" float cb2 = round((c2.y + c3.y) * 0.5);\n"
|
|
||||||
" float cr2 = round((c2.z + c3.z) * 0.5);\n"
|
|
||||||
" float cb4 = round((c4.y + c5.y) * 0.5);\n"
|
|
||||||
" float cr4 = round((c4.z + c5.z) * 0.5);\n"
|
|
||||||
" uint word = wordIndex == 0 ? makeWord(cb0, c0.x, cr0) : wordIndex == 1 ? makeWord(c1.x, cb2, c2.x) : wordIndex == 2 ? makeWord(cr2, c3.x, cb4) : makeWord(c4.x, cr4, c5.x);\n"
|
|
||||||
" fragColor = wordToBytes(word);\n"
|
|
||||||
"}\n";
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
extern const char* kFullscreenTriangleVertexShaderSource;
|
|
||||||
extern const char* kDecodeFragmentShaderSource;
|
|
||||||
extern const char* kOutputPackFragmentShaderSource;
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
#include "GlobalParamsBuffer.h"
|
|
||||||
|
|
||||||
#include "GlRenderConstants.h"
|
|
||||||
#include "Std140Buffer.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
GlobalParamsBuffer::GlobalParamsBuffer(OpenGLRenderer& renderer) :
|
|
||||||
mRenderer(renderer)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GlobalParamsBuffer::Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable)
|
|
||||||
{
|
|
||||||
std::vector<unsigned char>& buffer = mScratchBuffer;
|
|
||||||
buffer.clear();
|
|
||||||
buffer.reserve(512);
|
|
||||||
|
|
||||||
AppendStd140Float(buffer, static_cast<float>(state.timeSeconds));
|
|
||||||
AppendStd140Vec2(buffer, static_cast<float>(state.inputWidth), static_cast<float>(state.inputHeight));
|
|
||||||
AppendStd140Vec2(buffer, static_cast<float>(state.outputWidth), static_cast<float>(state.outputHeight));
|
|
||||||
AppendStd140Float(buffer, static_cast<float>(state.utcTimeSeconds));
|
|
||||||
AppendStd140Float(buffer, static_cast<float>(state.utcOffsetSeconds));
|
|
||||||
AppendStd140Float(buffer, static_cast<float>(state.startupRandom));
|
|
||||||
AppendStd140Float(buffer, static_cast<float>(state.frameCount));
|
|
||||||
AppendStd140Float(buffer, static_cast<float>(state.mixAmount));
|
|
||||||
AppendStd140Float(buffer, static_cast<float>(state.bypass));
|
|
||||||
const unsigned effectiveSourceHistoryLength = availableSourceHistoryLength < state.effectiveTemporalHistoryLength
|
|
||||||
? availableSourceHistoryLength
|
|
||||||
: state.effectiveTemporalHistoryLength;
|
|
||||||
const unsigned effectiveTemporalHistoryLength = (state.temporalHistorySource == TemporalHistorySource::PreLayerInput)
|
|
||||||
? (availableTemporalHistoryLength < state.effectiveTemporalHistoryLength ? availableTemporalHistoryLength : state.effectiveTemporalHistoryLength)
|
|
||||||
: 0u;
|
|
||||||
AppendStd140Int(buffer, static_cast<int>(effectiveSourceHistoryLength));
|
|
||||||
AppendStd140Int(buffer, static_cast<int>(effectiveTemporalHistoryLength));
|
|
||||||
AppendStd140Int(buffer, feedbackAvailable ? 1 : 0);
|
|
||||||
|
|
||||||
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
|
||||||
{
|
|
||||||
auto valueIt = state.parameterValues.find(definition.id);
|
|
||||||
const ShaderParameterValue value = valueIt != state.parameterValues.end()
|
|
||||||
? valueIt->second
|
|
||||||
: ShaderParameterValue();
|
|
||||||
|
|
||||||
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:
|
|
||||||
{
|
|
||||||
int selectedIndex = 0;
|
|
||||||
for (std::size_t optionIndex = 0; optionIndex < definition.enumOptions.size(); ++optionIndex)
|
|
||||||
{
|
|
||||||
if (definition.enumOptions[optionIndex].value == value.enumValue)
|
|
||||||
{
|
|
||||||
selectedIndex = static_cast<int>(optionIndex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AppendStd140Int(buffer, selectedIndex);
|
|
||||||
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);
|
|
||||||
|
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, mRenderer.GlobalParamsUBO());
|
|
||||||
if (mRenderer.GlobalParamsUBOSize() != static_cast<GLsizeiptr>(buffer.size()))
|
|
||||||
{
|
|
||||||
glBufferData(GL_UNIFORM_BUFFER, static_cast<GLsizeiptr>(buffer.size()), buffer.data(), GL_DYNAMIC_DRAW);
|
|
||||||
mRenderer.SetGlobalParamsUBOSize(static_cast<GLsizeiptr>(buffer.size()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
glBufferSubData(GL_UNIFORM_BUFFER, 0, static_cast<GLsizeiptr>(buffer.size()), buffer.data());
|
|
||||||
}
|
|
||||||
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mRenderer.GlobalParamsUBO());
|
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "OpenGLRenderer.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class GlobalParamsBuffer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit GlobalParamsBuffer(OpenGLRenderer& renderer);
|
|
||||||
|
|
||||||
bool Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable);
|
|
||||||
|
|
||||||
private:
|
|
||||||
OpenGLRenderer& mRenderer;
|
|
||||||
std::vector<unsigned char> mScratchBuffer;
|
|
||||||
};
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
#include "OpenGLShaderPrograms.h"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
if (!errorMessage || errorMessageSize <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t RequiredTemporaryRenderTargets(const std::vector<OpenGLRenderer::LayerProgram>& layerPrograms)
|
|
||||||
{
|
|
||||||
// Only one layer renders at a time, so the pool needs to cover the widest
|
|
||||||
// layer, not the sum of every intermediate pass in the stack.
|
|
||||||
std::size_t requiredTargets = 0;
|
|
||||||
for (const OpenGLRenderer::LayerProgram& layerProgram : layerPrograms)
|
|
||||||
{
|
|
||||||
const std::size_t internalPasses = layerProgram.passes.size() > 0 ? layerProgram.passes.size() - 1 : 0;
|
|
||||||
if (internalPasses > requiredTargets)
|
|
||||||
requiredTargets = internalPasses;
|
|
||||||
}
|
|
||||||
return requiredTargets;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenGLShaderPrograms::OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider) :
|
|
||||||
mRenderer(renderer),
|
|
||||||
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
|
||||||
mGlobalParamsBuffer(renderer),
|
|
||||||
mCompiler(renderer, runtimeSnapshotProvider, mTextureBindings)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
const RuntimeRenderStateSnapshot renderSnapshot =
|
|
||||||
mRuntimeSnapshotProvider.PublishRenderStateSnapshot(inputFrameWidth, inputFrameHeight);
|
|
||||||
const std::vector<RuntimeRenderState>& layerStates = renderSnapshot.states;
|
|
||||||
std::string temporalError;
|
|
||||||
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
|
|
||||||
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(layerStates, historyCap, temporalError))
|
|
||||||
{
|
|
||||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!mRenderer.TemporalHistory().EnsureResources(layerStates, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
|
|
||||||
{
|
|
||||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (mRenderer.ResourcesInitialized() &&
|
|
||||||
!mRenderer.FeedbackBuffers().EnsureResources(layerStates, inputFrameWidth, inputFrameHeight, temporalError))
|
|
||||||
{
|
|
||||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial startup still compiles synchronously; auto-reload uses the build
|
|
||||||
// queue so Slang/file work stays off the playback path.
|
|
||||||
std::vector<LayerProgram> newPrograms;
|
|
||||||
newPrograms.reserve(layerStates.size());
|
|
||||||
|
|
||||||
for (const RuntimeRenderState& state : layerStates)
|
|
||||||
{
|
|
||||||
LayerProgram layerProgram;
|
|
||||||
if (!mCompiler.CompileLayerProgram(state, layerProgram, errorMessageSize, errorMessage))
|
|
||||||
{
|
|
||||||
for (LayerProgram& program : newPrograms)
|
|
||||||
DestroySingleLayerProgram(program);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
newPrograms.push_back(layerProgram);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string targetError;
|
|
||||||
if (!mRenderer.ReserveTemporaryRenderTargets(RequiredTemporaryRenderTargets(newPrograms), inputFrameWidth, inputFrameHeight, targetError))
|
|
||||||
{
|
|
||||||
for (LayerProgram& program : newPrograms)
|
|
||||||
DestroySingleLayerProgram(program);
|
|
||||||
CopyErrorMessage(targetError, errorMessageSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DestroyLayerPrograms();
|
|
||||||
mRenderer.ReplaceLayerPrograms(newPrograms);
|
|
||||||
mCommittedLayerStates = renderSnapshot.states;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
if (!preparedBuild.succeeded)
|
|
||||||
{
|
|
||||||
CopyErrorMessage(preparedBuild.message, errorMessageSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string temporalError;
|
|
||||||
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
|
|
||||||
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.renderSnapshot.states, historyCap, temporalError))
|
|
||||||
{
|
|
||||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!mRenderer.TemporalHistory().EnsureResources(preparedBuild.renderSnapshot.states, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
|
|
||||||
{
|
|
||||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (mRenderer.ResourcesInitialized() &&
|
|
||||||
!mRenderer.FeedbackBuffers().EnsureResources(preparedBuild.renderSnapshot.states, inputFrameWidth, inputFrameHeight, temporalError))
|
|
||||||
{
|
|
||||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The prepared build already contains GLSL text for each pass. This commit
|
|
||||||
// step performs the short GL work on the render thread.
|
|
||||||
std::vector<LayerProgram> newPrograms;
|
|
||||||
newPrograms.reserve(preparedBuild.layers.size());
|
|
||||||
|
|
||||||
for (const PreparedLayerShader& preparedLayer : preparedBuild.layers)
|
|
||||||
{
|
|
||||||
LayerProgram layerProgram;
|
|
||||||
if (!mCompiler.CompilePreparedLayerProgram(preparedLayer.state, preparedLayer.passes, layerProgram, errorMessageSize, errorMessage))
|
|
||||||
{
|
|
||||||
for (LayerProgram& program : newPrograms)
|
|
||||||
DestroySingleLayerProgram(program);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
newPrograms.push_back(layerProgram);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string targetError;
|
|
||||||
if (!mRenderer.ReserveTemporaryRenderTargets(RequiredTemporaryRenderTargets(newPrograms), inputFrameWidth, inputFrameHeight, targetError))
|
|
||||||
{
|
|
||||||
for (LayerProgram& program : newPrograms)
|
|
||||||
DestroySingleLayerProgram(program);
|
|
||||||
CopyErrorMessage(targetError, errorMessageSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DestroyLayerPrograms();
|
|
||||||
mRenderer.ReplaceLayerPrograms(newPrograms);
|
|
||||||
mCommittedLayerStates = preparedBuild.renderSnapshot.states;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLShaderPrograms::CompileDecodeShader(int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
return mCompiler.CompileDecodeShader(errorMessageSize, errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLShaderPrograms::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
return mCompiler.CompileOutputPackShader(errorMessageSize, errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLShaderPrograms::DestroySingleLayerProgram(LayerProgram& layerProgram)
|
|
||||||
{
|
|
||||||
mRenderer.DestroySingleLayerProgram(layerProgram);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLShaderPrograms::DestroyLayerPrograms()
|
|
||||||
{
|
|
||||||
mRenderer.DestroyLayerPrograms();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLShaderPrograms::DestroyDecodeShaderProgram()
|
|
||||||
{
|
|
||||||
mRenderer.DestroyDecodeShaderProgram();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLShaderPrograms::ResetTemporalHistoryState()
|
|
||||||
{
|
|
||||||
mRenderer.TemporalHistory().ResetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLShaderPrograms::ResetShaderFeedbackState()
|
|
||||||
{
|
|
||||||
mRenderer.FeedbackBuffers().ResetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLShaderPrograms::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error)
|
|
||||||
{
|
|
||||||
return mTextureBindings.UpdateTextBindingTexture(state, textBinding, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLShaderPrograms::UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable)
|
|
||||||
{
|
|
||||||
return mGlobalParamsBuffer.Update(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable);
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "GlobalParamsBuffer.h"
|
|
||||||
#include "OpenGLRenderer.h"
|
|
||||||
#include "RuntimeSnapshotProvider.h"
|
|
||||||
#include "ShaderBuildQueue.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
#include "ShaderProgramCompiler.h"
|
|
||||||
#include "ShaderTextureBindings.h"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class OpenGLShaderPrograms
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
|
||||||
|
|
||||||
OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider);
|
|
||||||
|
|
||||||
bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
|
|
||||||
bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
|
|
||||||
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
|
|
||||||
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
|
|
||||||
void DestroyLayerPrograms();
|
|
||||||
void DestroySingleLayerProgram(LayerProgram& layerProgram);
|
|
||||||
void DestroyDecodeShaderProgram();
|
|
||||||
void ResetTemporalHistoryState();
|
|
||||||
void ResetShaderFeedbackState();
|
|
||||||
const std::vector<RuntimeRenderState>& CommittedLayerStates() const { return mCommittedLayerStates; }
|
|
||||||
bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error);
|
|
||||||
bool UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable);
|
|
||||||
|
|
||||||
private:
|
|
||||||
OpenGLRenderer& mRenderer;
|
|
||||||
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
|
||||||
ShaderTextureBindings mTextureBindings;
|
|
||||||
GlobalParamsBuffer mGlobalParamsBuffer;
|
|
||||||
ShaderProgramCompiler mCompiler;
|
|
||||||
std::vector<RuntimeRenderState> mCommittedLayerStates;
|
|
||||||
};
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
#include "ShaderBuildQueue.h"
|
|
||||||
|
|
||||||
#include "RuntimeEventDispatcher.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
constexpr auto kShaderBuildDebounce = std::chrono::milliseconds(400);
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderBuildQueue::ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeEventDispatcher& runtimeEventDispatcher) :
|
|
||||||
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
|
||||||
mRuntimeEventDispatcher(runtimeEventDispatcher),
|
|
||||||
mWorkerThread([this]() { WorkerLoop(); })
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderBuildQueue::~ShaderBuildQueue()
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderBuildQueue::RequestBuild(unsigned outputWidth, unsigned outputHeight)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
mHasRequest = true;
|
|
||||||
++mRequestedGeneration;
|
|
||||||
mRequestedOutputWidth = outputWidth;
|
|
||||||
mRequestedOutputHeight = outputHeight;
|
|
||||||
mHasReadyBuild = false;
|
|
||||||
}
|
|
||||||
mCondition.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderBuildQueue::TryConsumeReadyBuild(PreparedShaderBuild& build)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (!mHasReadyBuild)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
build = std::move(mReadyBuild);
|
|
||||||
mReadyBuild = PreparedShaderBuild();
|
|
||||||
mHasReadyBuild = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderBuildQueue::TryConsumeReadyBuild(uint64_t expectedGeneration, PreparedShaderBuild& build)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (!mHasReadyBuild || mReadyBuild.generation != expectedGeneration)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
build = std::move(mReadyBuild);
|
|
||||||
mReadyBuild = PreparedShaderBuild();
|
|
||||||
mHasReadyBuild = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderBuildQueue::Stop()
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (mStopping)
|
|
||||||
return;
|
|
||||||
mStopping = true;
|
|
||||||
}
|
|
||||||
mCondition.notify_one();
|
|
||||||
if (mWorkerThread.joinable())
|
|
||||||
mWorkerThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderBuildQueue::WorkerLoop()
|
|
||||||
{
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
uint64_t generation = 0;
|
|
||||||
unsigned outputWidth = 0;
|
|
||||||
unsigned outputHeight = 0;
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
|
||||||
mCondition.wait(lock, [this]() { return mStopping || mHasRequest; });
|
|
||||||
if (mStopping)
|
|
||||||
return;
|
|
||||||
|
|
||||||
generation = mRequestedGeneration;
|
|
||||||
outputWidth = mRequestedOutputWidth;
|
|
||||||
outputHeight = mRequestedOutputHeight;
|
|
||||||
mHasRequest = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
|
||||||
if (mCondition.wait_for(lock, kShaderBuildDebounce, [this, generation]() {
|
|
||||||
return mStopping || (mHasRequest && mRequestedGeneration != generation);
|
|
||||||
}))
|
|
||||||
{
|
|
||||||
if (mStopping)
|
|
||||||
return;
|
|
||||||
|
|
||||||
generation = mRequestedGeneration;
|
|
||||||
outputWidth = mRequestedOutputWidth;
|
|
||||||
outputHeight = mRequestedOutputHeight;
|
|
||||||
mHasRequest = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
PreparedShaderBuild build = Build(generation, outputWidth, outputHeight);
|
|
||||||
|
|
||||||
bool shouldPublish = false;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (mStopping)
|
|
||||||
return;
|
|
||||||
if (generation != mRequestedGeneration)
|
|
||||||
continue;
|
|
||||||
mReadyBuild = build;
|
|
||||||
mHasReadyBuild = true;
|
|
||||||
shouldPublish = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldPublish)
|
|
||||||
PublishBuildLifecycleEvent(build, outputWidth, outputHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PreparedShaderBuild ShaderBuildQueue::Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight)
|
|
||||||
{
|
|
||||||
PreparedShaderBuild build;
|
|
||||||
build.generation = generation;
|
|
||||||
build.renderSnapshot = mRuntimeSnapshotProvider.PublishRenderStateSnapshot(outputWidth, outputHeight);
|
|
||||||
build.layers.reserve(build.renderSnapshot.states.size());
|
|
||||||
|
|
||||||
for (const RuntimeRenderState& state : build.renderSnapshot.states)
|
|
||||||
{
|
|
||||||
PreparedLayerShader layer;
|
|
||||||
layer.state = state;
|
|
||||||
if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, layer.passes, build.message))
|
|
||||||
{
|
|
||||||
build.succeeded = false;
|
|
||||||
return build;
|
|
||||||
}
|
|
||||||
build.layers.push_back(std::move(layer));
|
|
||||||
}
|
|
||||||
|
|
||||||
build.succeeded = true;
|
|
||||||
build.message = "Shader layers prepared successfully.";
|
|
||||||
return build;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderBuildQueue::PublishBuildLifecycleEvent(const PreparedShaderBuild& build, unsigned outputWidth, unsigned outputHeight) const
|
|
||||||
{
|
|
||||||
ShaderBuildEvent event;
|
|
||||||
event.phase = build.succeeded ? RuntimeEventShaderBuildPhase::Prepared : RuntimeEventShaderBuildPhase::Failed;
|
|
||||||
event.generation = build.generation;
|
|
||||||
event.inputWidth = outputWidth;
|
|
||||||
event.inputHeight = outputHeight;
|
|
||||||
event.succeeded = build.succeeded;
|
|
||||||
event.message = build.message;
|
|
||||||
mRuntimeEventDispatcher.PublishPayload(event, "ShaderBuildQueue");
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeSnapshotProvider.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class RuntimeEventDispatcher;
|
|
||||||
|
|
||||||
struct PreparedLayerShader
|
|
||||||
{
|
|
||||||
RuntimeRenderState state;
|
|
||||||
std::vector<ShaderPassBuildSource> passes;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PreparedShaderBuild
|
|
||||||
{
|
|
||||||
uint64_t generation = 0;
|
|
||||||
bool succeeded = false;
|
|
||||||
std::string message;
|
|
||||||
RuntimeRenderStateSnapshot renderSnapshot;
|
|
||||||
std::vector<PreparedLayerShader> layers;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ShaderBuildQueue
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeEventDispatcher& runtimeEventDispatcher);
|
|
||||||
~ShaderBuildQueue();
|
|
||||||
|
|
||||||
ShaderBuildQueue(const ShaderBuildQueue&) = delete;
|
|
||||||
ShaderBuildQueue& operator=(const ShaderBuildQueue&) = delete;
|
|
||||||
|
|
||||||
void RequestBuild(unsigned outputWidth, unsigned outputHeight);
|
|
||||||
bool TryConsumeReadyBuild(PreparedShaderBuild& build);
|
|
||||||
bool TryConsumeReadyBuild(uint64_t expectedGeneration, PreparedShaderBuild& build);
|
|
||||||
void Stop();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void WorkerLoop();
|
|
||||||
PreparedShaderBuild Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight);
|
|
||||||
void PublishBuildLifecycleEvent(const PreparedShaderBuild& build, unsigned outputWidth, unsigned outputHeight) const;
|
|
||||||
|
|
||||||
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
|
||||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
|
||||||
std::thread mWorkerThread;
|
|
||||||
std::mutex mMutex;
|
|
||||||
std::condition_variable mCondition;
|
|
||||||
bool mStopping = false;
|
|
||||||
bool mHasRequest = false;
|
|
||||||
uint64_t mRequestedGeneration = 0;
|
|
||||||
unsigned mRequestedOutputWidth = 0;
|
|
||||||
unsigned mRequestedOutputHeight = 0;
|
|
||||||
bool mHasReadyBuild = false;
|
|
||||||
PreparedShaderBuild mReadyBuild;
|
|
||||||
};
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
#include "ShaderProgramCompiler.h"
|
|
||||||
|
|
||||||
#include "GlRenderConstants.h"
|
|
||||||
#include "GlScopedObjects.h"
|
|
||||||
#include "GlShaderSources.h"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
if (!errorMessage || errorMessageSize <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderProgramCompiler::ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider, ShaderTextureBindings& textureBindings) :
|
|
||||||
mRenderer(renderer),
|
|
||||||
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
|
|
||||||
mTextureBindings(textureBindings)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderProgramCompiler::CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
std::vector<ShaderPassBuildSource> passSources;
|
|
||||||
std::string loadError;
|
|
||||||
|
|
||||||
if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, passSources, loadError))
|
|
||||||
{
|
|
||||||
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CompilePreparedLayerProgram(state, passSources, layerProgram, errorMessageSize, errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::vector<ShaderPassBuildSource>& passSources, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
GLsizei errorBufferSize = 0;
|
|
||||||
std::string loadError;
|
|
||||||
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
|
|
||||||
|
|
||||||
layerProgram.layerId = state.layerId;
|
|
||||||
layerProgram.shaderId = state.shaderId;
|
|
||||||
layerProgram.passes.clear();
|
|
||||||
|
|
||||||
for (const auto& passSource : passSources)
|
|
||||||
{
|
|
||||||
GLint compileResult = GL_FALSE;
|
|
||||||
GLint linkResult = GL_FALSE;
|
|
||||||
const char* fragmentSource = passSource.fragmentShaderSource.c_str();
|
|
||||||
|
|
||||||
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
|
|
||||||
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
|
|
||||||
glCompileShader(newVertexShader.get());
|
|
||||||
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
|
|
||||||
if (compileResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
mRenderer.DestroySingleLayerProgram(layerProgram);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
|
|
||||||
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
|
|
||||||
glCompileShader(newFragmentShader.get());
|
|
||||||
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
|
|
||||||
if (compileResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
mRenderer.DestroySingleLayerProgram(layerProgram);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedGlProgram newProgram(glCreateProgram());
|
|
||||||
glAttachShader(newProgram.get(), newVertexShader.get());
|
|
||||||
glAttachShader(newProgram.get(), newFragmentShader.get());
|
|
||||||
glLinkProgram(newProgram.get());
|
|
||||||
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
|
|
||||||
if (linkResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
mRenderer.DestroySingleLayerProgram(layerProgram);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<LayerProgram::TextureBinding> textureBindings;
|
|
||||||
for (const ShaderTextureAsset& textureAsset : state.textureAssets)
|
|
||||||
{
|
|
||||||
LayerProgram::TextureBinding textureBinding;
|
|
||||||
textureBinding.samplerName = textureAsset.id;
|
|
||||||
textureBinding.sourcePath = textureAsset.path;
|
|
||||||
if (!mTextureBindings.LoadTextureAsset(textureAsset, textureBinding.texture, loadError))
|
|
||||||
{
|
|
||||||
for (LayerProgram::TextureBinding& loadedTexture : textureBindings)
|
|
||||||
{
|
|
||||||
if (loadedTexture.texture != 0)
|
|
||||||
glDeleteTextures(1, &loadedTexture.texture);
|
|
||||||
}
|
|
||||||
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
|
|
||||||
mRenderer.DestroySingleLayerProgram(layerProgram);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
textureBindings.push_back(textureBinding);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<LayerProgram::TextBinding> textBindings;
|
|
||||||
mTextureBindings.CreateTextBindings(state, textBindings);
|
|
||||||
|
|
||||||
PassProgram passProgram;
|
|
||||||
passProgram.passId = passSource.passId;
|
|
||||||
passProgram.inputNames = passSource.inputNames;
|
|
||||||
passProgram.outputName = passSource.outputName;
|
|
||||||
passProgram.shaderTextureBase = mTextureBindings.ResolveShaderTextureBase(state, mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames());
|
|
||||||
passProgram.textureBindings.swap(textureBindings);
|
|
||||||
passProgram.textBindings.swap(textBindings);
|
|
||||||
|
|
||||||
const GLuint globalParamsIndex = glGetUniformBlockIndex(newProgram.get(), "GlobalParams");
|
|
||||||
if (globalParamsIndex != GL_INVALID_INDEX)
|
|
||||||
glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint);
|
|
||||||
|
|
||||||
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
|
|
||||||
glUseProgram(newProgram.get());
|
|
||||||
mTextureBindings.AssignLayerSamplerUniforms(newProgram.get(), state, passProgram, historyCap);
|
|
||||||
glUseProgram(0);
|
|
||||||
|
|
||||||
passProgram.program = newProgram.release();
|
|
||||||
passProgram.vertexShader = newVertexShader.release();
|
|
||||||
passProgram.fragmentShader = newFragmentShader.release();
|
|
||||||
layerProgram.passes.push_back(std::move(passProgram));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderProgramCompiler::CompileDecodeShader(int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
GLsizei errorBufferSize = 0;
|
|
||||||
GLint compileResult = GL_FALSE;
|
|
||||||
GLint linkResult = GL_FALSE;
|
|
||||||
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
|
|
||||||
const char* fragmentSource = kDecodeFragmentShaderSource;
|
|
||||||
|
|
||||||
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
|
|
||||||
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
|
|
||||||
glCompileShader(newVertexShader.get());
|
|
||||||
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
|
|
||||||
if (compileResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
|
|
||||||
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
|
|
||||||
glCompileShader(newFragmentShader.get());
|
|
||||||
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
|
|
||||||
if (compileResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedGlProgram newProgram(glCreateProgram());
|
|
||||||
glAttachShader(newProgram.get(), newVertexShader.get());
|
|
||||||
glAttachShader(newProgram.get(), newFragmentShader.get());
|
|
||||||
glLinkProgram(newProgram.get());
|
|
||||||
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
|
|
||||||
if (linkResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mRenderer.DestroyDecodeShaderProgram();
|
|
||||||
mRenderer.SetDecodeShaderProgram(newProgram.release(), newVertexShader.release(), newFragmentShader.release());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderProgramCompiler::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
|
|
||||||
{
|
|
||||||
GLsizei errorBufferSize = 0;
|
|
||||||
GLint compileResult = GL_FALSE;
|
|
||||||
GLint linkResult = GL_FALSE;
|
|
||||||
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
|
|
||||||
const char* fragmentSource = kOutputPackFragmentShaderSource;
|
|
||||||
|
|
||||||
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
|
|
||||||
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
|
|
||||||
glCompileShader(newVertexShader.get());
|
|
||||||
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
|
|
||||||
if (compileResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
|
|
||||||
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
|
|
||||||
glCompileShader(newFragmentShader.get());
|
|
||||||
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
|
|
||||||
if (compileResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedGlProgram newProgram(glCreateProgram());
|
|
||||||
glAttachShader(newProgram.get(), newVertexShader.get());
|
|
||||||
glAttachShader(newProgram.get(), newFragmentShader.get());
|
|
||||||
glLinkProgram(newProgram.get());
|
|
||||||
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
|
|
||||||
if (linkResult == GL_FALSE)
|
|
||||||
{
|
|
||||||
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
glUseProgram(newProgram.get());
|
|
||||||
const GLint outputSamplerLocation = glGetUniformLocation(newProgram.get(), "uOutputRgb");
|
|
||||||
if (outputSamplerLocation >= 0)
|
|
||||||
glUniform1i(outputSamplerLocation, 0);
|
|
||||||
glUseProgram(0);
|
|
||||||
|
|
||||||
mRenderer.DestroyOutputPackShaderProgram();
|
|
||||||
mRenderer.SetOutputPackShaderProgram(newProgram.release(), newVertexShader.release(), newFragmentShader.release());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "OpenGLRenderer.h"
|
|
||||||
#include "RuntimeSnapshotProvider.h"
|
|
||||||
#include "ShaderTextureBindings.h"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class ShaderProgramCompiler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
|
||||||
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
|
|
||||||
|
|
||||||
ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider, ShaderTextureBindings& textureBindings);
|
|
||||||
|
|
||||||
bool CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
|
||||||
bool CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::vector<ShaderPassBuildSource>& passSources, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
|
||||||
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
|
|
||||||
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
|
|
||||||
|
|
||||||
private:
|
|
||||||
OpenGLRenderer& mRenderer;
|
|
||||||
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
|
|
||||||
ShaderTextureBindings& mTextureBindings;
|
|
||||||
};
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
#include "ShaderTextureBindings.h"
|
|
||||||
|
|
||||||
#include "GlRenderConstants.h"
|
|
||||||
#include "TextRasterizer.h"
|
|
||||||
#include "TextureAssetLoader.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
std::string TextValueForBinding(const RuntimeRenderState& state, const std::string& parameterId)
|
|
||||||
{
|
|
||||||
auto valueIt = state.parameterValues.find(parameterId);
|
|
||||||
return valueIt == state.parameterValues.end() ? std::string() : valueIt->second.textValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ShaderFontAsset* FindFontAssetForParameter(const RuntimeRenderState& state, const ShaderParameterDefinition& definition)
|
|
||||||
{
|
|
||||||
if (!definition.fontId.empty())
|
|
||||||
{
|
|
||||||
for (const ShaderFontAsset& fontAsset : state.fontAssets)
|
|
||||||
{
|
|
||||||
if (fontAsset.id == definition.fontId)
|
|
||||||
return &fontAsset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return state.fontAssets.empty() ? nullptr : &state.fontAssets.front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderTextureBindings::LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
|
|
||||||
{
|
|
||||||
return ::LoadTextureAsset(textureAsset, textureId, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderTextureBindings::CreateTextBindings(const RuntimeRenderState& state, std::vector<LayerProgram::TextBinding>& textBindings)
|
|
||||||
{
|
|
||||||
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
|
||||||
{
|
|
||||||
if (definition.type != ShaderParameterType::Text)
|
|
||||||
continue;
|
|
||||||
LayerProgram::TextBinding textBinding;
|
|
||||||
textBinding.parameterId = definition.id;
|
|
||||||
textBinding.samplerName = definition.id + "Texture";
|
|
||||||
textBinding.fontId = definition.fontId;
|
|
||||||
glGenTextures(1, &textBinding.texture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, textBinding.texture);
|
|
||||||
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);
|
|
||||||
std::vector<unsigned char> empty(static_cast<std::size_t>(kTextTextureWidth) * kTextTextureHeight * 4, 0);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTextTextureWidth, kTextTextureHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, empty.data());
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
textBindings.push_back(textBinding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderTextureBindings::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error)
|
|
||||||
{
|
|
||||||
const std::string text = TextValueForBinding(state, textBinding.parameterId);
|
|
||||||
if (text == textBinding.renderedText && textBinding.renderedWidth == kTextTextureWidth && textBinding.renderedHeight == kTextTextureHeight)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
auto definitionIt = std::find_if(state.parameterDefinitions.begin(), state.parameterDefinitions.end(),
|
|
||||||
[&textBinding](const ShaderParameterDefinition& definition) { return definition.id == textBinding.parameterId; });
|
|
||||||
if (definitionIt == state.parameterDefinitions.end())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
const ShaderFontAsset* fontAsset = FindFontAssetForParameter(state, *definitionIt);
|
|
||||||
std::filesystem::path fontPath;
|
|
||||||
if (fontAsset)
|
|
||||||
fontPath = fontAsset->path;
|
|
||||||
|
|
||||||
std::vector<unsigned char> sdf;
|
|
||||||
if (!RasterizeTextSdf(text, fontPath, sdf, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
GLint previousActiveTexture = 0;
|
|
||||||
GLint previousUnpackBuffer = 0;
|
|
||||||
glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture);
|
|
||||||
glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &previousUnpackBuffer);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, textBinding.texture);
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextTextureWidth, kTextTextureHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, sdf.data());
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(previousUnpackBuffer));
|
|
||||||
glActiveTexture(static_cast<GLenum>(previousActiveTexture));
|
|
||||||
|
|
||||||
textBinding.renderedText = text;
|
|
||||||
textBinding.renderedWidth = kTextTextureWidth;
|
|
||||||
textBinding.renderedHeight = kTextTextureHeight;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint ShaderTextureBindings::FindSamplerUniformLocation(GLuint program, const std::string& samplerName) const
|
|
||||||
{
|
|
||||||
GLint location = glGetUniformLocation(program, samplerName.c_str());
|
|
||||||
if (location >= 0)
|
|
||||||
return location;
|
|
||||||
return glGetUniformLocation(program, (samplerName + "_0").c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint ShaderTextureBindings::ResolveFeedbackTextureUnit(const RuntimeRenderState& state, unsigned historyCap) const
|
|
||||||
{
|
|
||||||
return state.isTemporal ? kSourceHistoryTextureUnitBase + historyCap + historyCap : kSourceHistoryTextureUnitBase;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint ShaderTextureBindings::ResolveShaderTextureBase(const RuntimeRenderState& state, unsigned historyCap) const
|
|
||||||
{
|
|
||||||
return ResolveFeedbackTextureUnit(state, historyCap) + (state.feedback.enabled ? 1u : 0u);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderTextureBindings::AssignLayerSamplerUniforms(GLuint program, const RuntimeRenderState& state, const PassProgram& passProgram, unsigned historyCap) const
|
|
||||||
{
|
|
||||||
const GLuint shaderTextureBase = ResolveShaderTextureBase(state, historyCap);
|
|
||||||
|
|
||||||
const GLint layerInputLocation = FindSamplerUniformLocation(program, "gLayerInput");
|
|
||||||
if (layerInputLocation >= 0)
|
|
||||||
glUniform1i(layerInputLocation, static_cast<GLint>(kLayerInputTextureUnit));
|
|
||||||
|
|
||||||
const GLint videoInputLocation = FindSamplerUniformLocation(program, "gVideoInput");
|
|
||||||
if (videoInputLocation >= 0)
|
|
||||||
glUniform1i(videoInputLocation, static_cast<GLint>(kDecodedVideoTextureUnit));
|
|
||||||
|
|
||||||
for (unsigned index = 0; index < historyCap; ++index)
|
|
||||||
{
|
|
||||||
const std::string sourceSamplerName = "gSourceHistory" + std::to_string(index);
|
|
||||||
const GLint sourceSamplerLocation = glGetUniformLocation(program, sourceSamplerName.c_str());
|
|
||||||
if (sourceSamplerLocation >= 0)
|
|
||||||
glUniform1i(sourceSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + index));
|
|
||||||
|
|
||||||
const std::string temporalSamplerName = "gTemporalHistory" + std::to_string(index);
|
|
||||||
const GLint temporalSamplerLocation = glGetUniformLocation(program, temporalSamplerName.c_str());
|
|
||||||
if (temporalSamplerLocation >= 0)
|
|
||||||
glUniform1i(temporalSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + historyCap + index));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.feedback.enabled)
|
|
||||||
{
|
|
||||||
const GLint feedbackSamplerLocation = FindSamplerUniformLocation(program, "gFeedbackState");
|
|
||||||
if (feedbackSamplerLocation >= 0)
|
|
||||||
glUniform1i(feedbackSamplerLocation, static_cast<GLint>(ResolveFeedbackTextureUnit(state, historyCap)));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (std::size_t index = 0; index < passProgram.textureBindings.size(); ++index)
|
|
||||||
{
|
|
||||||
const GLint textureSamplerLocation = FindSamplerUniformLocation(program, passProgram.textureBindings[index].samplerName);
|
|
||||||
if (textureSamplerLocation >= 0)
|
|
||||||
glUniform1i(textureSamplerLocation, static_cast<GLint>(shaderTextureBase + static_cast<GLuint>(index)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(passProgram.textureBindings.size());
|
|
||||||
for (std::size_t index = 0; index < passProgram.textBindings.size(); ++index)
|
|
||||||
{
|
|
||||||
const GLint textSamplerLocation = FindSamplerUniformLocation(program, passProgram.textBindings[index].samplerName);
|
|
||||||
if (textSamplerLocation >= 0)
|
|
||||||
glUniform1i(textSamplerLocation, static_cast<GLint>(textTextureBase + static_cast<GLuint>(index)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderTextureBindings::RuntimeTextureBindingPlan ShaderTextureBindings::BuildLayerRuntimeBindingPlan(
|
|
||||||
const PassProgram& passProgram,
|
|
||||||
GLuint layerInputTexture,
|
|
||||||
GLuint originalLayerInputTexture,
|
|
||||||
const RuntimeRenderState& state,
|
|
||||||
GLuint feedbackTexture,
|
|
||||||
const std::vector<GLuint>& sourceHistoryTextures,
|
|
||||||
const std::vector<GLuint>& temporalHistoryTextures) const
|
|
||||||
{
|
|
||||||
RuntimeTextureBindingPlan plan;
|
|
||||||
plan.bindings.push_back({ "originalLayerInput", "gLayerInput", originalLayerInputTexture, kLayerInputTextureUnit });
|
|
||||||
plan.bindings.push_back({ "layerInput", "gVideoInput", layerInputTexture, kDecodedVideoTextureUnit });
|
|
||||||
|
|
||||||
for (std::size_t index = 0; index < sourceHistoryTextures.size(); ++index)
|
|
||||||
{
|
|
||||||
plan.bindings.push_back({
|
|
||||||
"sourceHistory",
|
|
||||||
"gSourceHistory" + std::to_string(index),
|
|
||||||
sourceHistoryTextures[index],
|
|
||||||
kSourceHistoryTextureUnitBase + static_cast<GLuint>(index)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLuint temporalBase = kSourceHistoryTextureUnitBase + static_cast<GLuint>(sourceHistoryTextures.size());
|
|
||||||
for (std::size_t index = 0; index < temporalHistoryTextures.size(); ++index)
|
|
||||||
{
|
|
||||||
plan.bindings.push_back({
|
|
||||||
"temporalHistory",
|
|
||||||
"gTemporalHistory" + std::to_string(index),
|
|
||||||
temporalHistoryTextures[index],
|
|
||||||
temporalBase + static_cast<GLuint>(index)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLuint feedbackTextureUnit = ResolveFeedbackTextureUnit(state, static_cast<unsigned>(sourceHistoryTextures.size()));
|
|
||||||
if (state.feedback.enabled)
|
|
||||||
{
|
|
||||||
plan.bindings.push_back({
|
|
||||||
"feedbackState",
|
|
||||||
"gFeedbackState",
|
|
||||||
feedbackTexture,
|
|
||||||
feedbackTextureUnit
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLuint shaderTextureBase = passProgram.shaderTextureBase != 0
|
|
||||||
? passProgram.shaderTextureBase
|
|
||||||
: feedbackTextureUnit + (state.feedback.enabled ? 1u : 0u);
|
|
||||||
for (std::size_t index = 0; index < passProgram.textureBindings.size(); ++index)
|
|
||||||
{
|
|
||||||
const LayerProgram::TextureBinding& textureBinding = passProgram.textureBindings[index];
|
|
||||||
plan.bindings.push_back({
|
|
||||||
"shaderTexture",
|
|
||||||
textureBinding.samplerName,
|
|
||||||
textureBinding.texture,
|
|
||||||
shaderTextureBase + static_cast<GLuint>(index)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(passProgram.textureBindings.size());
|
|
||||||
for (std::size_t index = 0; index < passProgram.textBindings.size(); ++index)
|
|
||||||
{
|
|
||||||
const LayerProgram::TextBinding& textBinding = passProgram.textBindings[index];
|
|
||||||
plan.bindings.push_back({
|
|
||||||
"textTexture",
|
|
||||||
textBinding.samplerName,
|
|
||||||
textBinding.texture,
|
|
||||||
textTextureBase + static_cast<GLuint>(index)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return plan;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderTextureBindings::BindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const
|
|
||||||
{
|
|
||||||
for (const RuntimeTextureBinding& binding : plan.bindings)
|
|
||||||
{
|
|
||||||
glActiveTexture(GL_TEXTURE0 + binding.textureUnit);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, binding.texture);
|
|
||||||
}
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderTextureBindings::UnbindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const
|
|
||||||
{
|
|
||||||
for (const RuntimeTextureBinding& binding : plan.bindings)
|
|
||||||
{
|
|
||||||
glActiveTexture(GL_TEXTURE0 + binding.textureUnit);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "OpenGLRenderer.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class ShaderTextureBindings
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
|
||||||
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
|
|
||||||
|
|
||||||
struct RuntimeTextureBinding
|
|
||||||
{
|
|
||||||
std::string semanticName;
|
|
||||||
std::string samplerName;
|
|
||||||
GLuint texture = 0;
|
|
||||||
GLuint textureUnit = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeTextureBindingPlan
|
|
||||||
{
|
|
||||||
std::vector<RuntimeTextureBinding> bindings;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error);
|
|
||||||
void CreateTextBindings(const RuntimeRenderState& state, std::vector<LayerProgram::TextBinding>& textBindings);
|
|
||||||
bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error);
|
|
||||||
GLint FindSamplerUniformLocation(GLuint program, const std::string& samplerName) const;
|
|
||||||
GLuint ResolveFeedbackTextureUnit(const RuntimeRenderState& state, unsigned historyCap) const;
|
|
||||||
GLuint ResolveShaderTextureBase(const RuntimeRenderState& state, unsigned historyCap) const;
|
|
||||||
void AssignLayerSamplerUniforms(GLuint program, const RuntimeRenderState& state, const PassProgram& passProgram, unsigned historyCap) const;
|
|
||||||
RuntimeTextureBindingPlan BuildLayerRuntimeBindingPlan(
|
|
||||||
const PassProgram& passProgram,
|
|
||||||
GLuint layerInputTexture,
|
|
||||||
GLuint originalLayerInputTexture,
|
|
||||||
const RuntimeRenderState& state,
|
|
||||||
GLuint feedbackTexture,
|
|
||||||
const std::vector<GLuint>& sourceHistoryTextures,
|
|
||||||
const std::vector<GLuint>& temporalHistoryTextures) const;
|
|
||||||
void BindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const;
|
|
||||||
void UnbindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const;
|
|
||||||
};
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstring>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
inline std::size_t AlignStd140(std::size_t offset, std::size_t alignment)
|
|
||||||
{
|
|
||||||
const std::size_t mask = alignment - 1;
|
|
||||||
return (offset + mask) & ~mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename TValue>
|
|
||||||
inline void AppendStd140Value(std::vector<unsigned char>& buffer, std::size_t alignment, const TValue& value)
|
|
||||||
{
|
|
||||||
const std::size_t offset = AlignStd140(buffer.size(), alignment);
|
|
||||||
if (buffer.size() < offset + sizeof(TValue))
|
|
||||||
buffer.resize(offset + sizeof(TValue), 0);
|
|
||||||
std::memcpy(buffer.data() + offset, &value, sizeof(TValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void AppendStd140Float(std::vector<unsigned char>& buffer, float value)
|
|
||||||
{
|
|
||||||
AppendStd140Value(buffer, 4, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void AppendStd140Int(std::vector<unsigned char>& buffer, int value)
|
|
||||||
{
|
|
||||||
AppendStd140Value(buffer, 4, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void AppendStd140Vec2(std::vector<unsigned char>& buffer, float x, float y)
|
|
||||||
{
|
|
||||||
const std::size_t offset = AlignStd140(buffer.size(), 8);
|
|
||||||
if (buffer.size() < offset + sizeof(float) * 2)
|
|
||||||
buffer.resize(offset + sizeof(float) * 2, 0);
|
|
||||||
float values[2] = { x, y };
|
|
||||||
std::memcpy(buffer.data() + offset, values, sizeof(values));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void AppendStd140Vec4(std::vector<unsigned char>& buffer, float x, float y, float z, float w)
|
|
||||||
{
|
|
||||||
const std::size_t offset = AlignStd140(buffer.size(), 16);
|
|
||||||
if (buffer.size() < offset + sizeof(float) * 4)
|
|
||||||
buffer.resize(offset + sizeof(float) * 4, 0);
|
|
||||||
float values[4] = { x, y, z, w };
|
|
||||||
std::memcpy(buffer.data() + offset, values, sizeof(values));
|
|
||||||
}
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
#include "TextRasterizer.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstring>
|
|
||||||
#include <gdiplus.h>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
constexpr int kTextSdfSpread = 20;
|
|
||||||
constexpr float kTextFontPixelSize = 144.0f;
|
|
||||||
constexpr float kTextLayoutPadding = 48.0f;
|
|
||||||
constexpr float kSdfInfinity = 1.0e20f;
|
|
||||||
|
|
||||||
class GdiplusSession
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
GdiplusSession()
|
|
||||||
{
|
|
||||||
Gdiplus::GdiplusStartupInput startupInput;
|
|
||||||
mStarted = Gdiplus::GdiplusStartup(&mToken, &startupInput, NULL) == Gdiplus::Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
~GdiplusSession()
|
|
||||||
{
|
|
||||||
if (mStarted)
|
|
||||||
Gdiplus::GdiplusShutdown(mToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
GdiplusSession(const GdiplusSession&) = delete;
|
|
||||||
GdiplusSession& operator=(const GdiplusSession&) = delete;
|
|
||||||
|
|
||||||
bool started() const { return mStarted; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
ULONG_PTR mToken = 0;
|
|
||||||
bool mStarted = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::wstring Utf8ToWide(const std::string& text)
|
|
||||||
{
|
|
||||||
if (text.empty())
|
|
||||||
return std::wstring();
|
|
||||||
const int required = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, NULL, 0);
|
|
||||||
if (required <= 1)
|
|
||||||
return std::wstring();
|
|
||||||
std::wstring wide(static_cast<std::size_t>(required - 1), L'\0');
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, wide.data(), required);
|
|
||||||
return wide;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DistanceTransform1D(const std::vector<float>& input, std::vector<float>& output, unsigned count)
|
|
||||||
{
|
|
||||||
std::vector<unsigned> locations(count, 0);
|
|
||||||
std::vector<float> boundaries(static_cast<std::size_t>(count) + 1, 0.0f);
|
|
||||||
|
|
||||||
unsigned segment = 0;
|
|
||||||
locations[0] = 0;
|
|
||||||
boundaries[0] = -kSdfInfinity;
|
|
||||||
boundaries[1] = kSdfInfinity;
|
|
||||||
|
|
||||||
for (unsigned q = 1; q < count; ++q)
|
|
||||||
{
|
|
||||||
float intersection = 0.0f;
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
const unsigned location = locations[segment];
|
|
||||||
intersection =
|
|
||||||
((input[q] + static_cast<float>(q * q)) - (input[location] + static_cast<float>(location * location))) /
|
|
||||||
(2.0f * static_cast<float>(q) - 2.0f * static_cast<float>(location));
|
|
||||||
if (intersection > boundaries[segment] || segment == 0)
|
|
||||||
break;
|
|
||||||
--segment;
|
|
||||||
}
|
|
||||||
|
|
||||||
++segment;
|
|
||||||
locations[segment] = q;
|
|
||||||
boundaries[segment] = intersection;
|
|
||||||
boundaries[segment + 1] = kSdfInfinity;
|
|
||||||
}
|
|
||||||
|
|
||||||
segment = 0;
|
|
||||||
for (unsigned q = 0; q < count; ++q)
|
|
||||||
{
|
|
||||||
while (boundaries[segment + 1] < static_cast<float>(q))
|
|
||||||
++segment;
|
|
||||||
const unsigned location = locations[segment];
|
|
||||||
const float delta = static_cast<float>(q) - static_cast<float>(location);
|
|
||||||
output[q] = delta * delta + input[location];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<float> DistanceTransform2D(const std::vector<unsigned char>& targetMask, unsigned width, unsigned height)
|
|
||||||
{
|
|
||||||
std::vector<float> rowInput(width, 0.0f);
|
|
||||||
std::vector<float> rowOutput(width, 0.0f);
|
|
||||||
std::vector<float> columnInput(height, 0.0f);
|
|
||||||
std::vector<float> columnOutput(height, 0.0f);
|
|
||||||
std::vector<float> rowDistance(static_cast<std::size_t>(width) * height, 0.0f);
|
|
||||||
std::vector<float> distance(static_cast<std::size_t>(width) * height, 0.0f);
|
|
||||||
|
|
||||||
for (unsigned y = 0; y < height; ++y)
|
|
||||||
{
|
|
||||||
for (unsigned x = 0; x < width; ++x)
|
|
||||||
rowInput[x] = targetMask[static_cast<std::size_t>(y) * width + x] ? 0.0f : kSdfInfinity;
|
|
||||||
DistanceTransform1D(rowInput, rowOutput, width);
|
|
||||||
for (unsigned x = 0; x < width; ++x)
|
|
||||||
rowDistance[static_cast<std::size_t>(y) * width + x] = rowOutput[x];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (unsigned x = 0; x < width; ++x)
|
|
||||||
{
|
|
||||||
for (unsigned y = 0; y < height; ++y)
|
|
||||||
columnInput[y] = rowDistance[static_cast<std::size_t>(y) * width + x];
|
|
||||||
DistanceTransform1D(columnInput, columnOutput, height);
|
|
||||||
for (unsigned y = 0; y < height; ++y)
|
|
||||||
distance[static_cast<std::size_t>(y) * width + x] = columnOutput[y];
|
|
||||||
}
|
|
||||||
|
|
||||||
return distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<unsigned char> BuildTextSdfTexture(const std::vector<unsigned char>& alpha, unsigned width, unsigned height)
|
|
||||||
{
|
|
||||||
std::vector<unsigned char> insideMask(static_cast<std::size_t>(width) * height, 0);
|
|
||||||
std::vector<unsigned char> outsideMask(static_cast<std::size_t>(width) * height, 0);
|
|
||||||
for (std::size_t index = 0; index < alpha.size(); ++index)
|
|
||||||
{
|
|
||||||
const bool inside = alpha[index] > 127;
|
|
||||||
insideMask[index] = inside ? 1 : 0;
|
|
||||||
outsideMask[index] = inside ? 0 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<float> distanceToInside = DistanceTransform2D(insideMask, width, height);
|
|
||||||
const std::vector<float> distanceToOutside = DistanceTransform2D(outsideMask, width, height);
|
|
||||||
std::vector<unsigned char> sdf(static_cast<std::size_t>(width) * height * 4, 0);
|
|
||||||
|
|
||||||
for (unsigned y = 0; y < height; ++y)
|
|
||||||
{
|
|
||||||
const unsigned flippedY = height - 1 - y;
|
|
||||||
for (unsigned x = 0; x < width; ++x)
|
|
||||||
{
|
|
||||||
const std::size_t source = static_cast<std::size_t>(y) * width + x;
|
|
||||||
const float signedDistance = std::sqrt(distanceToOutside[source]) - std::sqrt(distanceToInside[source]);
|
|
||||||
const float normalized = std::clamp(
|
|
||||||
0.5f + signedDistance / static_cast<float>(kTextSdfSpread * 2),
|
|
||||||
0.0f,
|
|
||||||
1.0f);
|
|
||||||
const unsigned char value = static_cast<unsigned char>(normalized * 255.0f + 0.5f);
|
|
||||||
const std::size_t out = (static_cast<std::size_t>(flippedY) * width + x) * 4;
|
|
||||||
sdf[out + 0] = value;
|
|
||||||
sdf[out + 1] = value;
|
|
||||||
sdf[out + 2] = value;
|
|
||||||
sdf[out + 3] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sdf;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector<unsigned char>& sdf, std::string& error)
|
|
||||||
{
|
|
||||||
GdiplusSession gdiplus;
|
|
||||||
if (!gdiplus.started())
|
|
||||||
{
|
|
||||||
error = "Could not start GDI+ for text rendering.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gdiplus::PrivateFontCollection fontCollection;
|
|
||||||
Gdiplus::FontFamily fallbackFamily(L"Arial");
|
|
||||||
Gdiplus::FontFamily* fontFamily = &fallbackFamily;
|
|
||||||
std::unique_ptr<Gdiplus::FontFamily[]> families;
|
|
||||||
const std::wstring wideFontPath = fontPath.empty() ? std::wstring() : fontPath.wstring();
|
|
||||||
if (!wideFontPath.empty())
|
|
||||||
{
|
|
||||||
if (fontCollection.AddFontFile(wideFontPath.c_str()) != Gdiplus::Ok)
|
|
||||||
{
|
|
||||||
error = "Could not load packaged font file for text rendering: " + fontPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const INT familyCount = fontCollection.GetFamilyCount();
|
|
||||||
if (familyCount <= 0)
|
|
||||||
{
|
|
||||||
error = "Packaged font did not contain a usable font family: " + fontPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
families.reset(new Gdiplus::FontFamily[familyCount]);
|
|
||||||
INT found = 0;
|
|
||||||
if (fontCollection.GetFamilies(familyCount, families.get(), &found) != Gdiplus::Ok || found <= 0)
|
|
||||||
{
|
|
||||||
error = "Could not read the packaged font family: " + fontPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
fontFamily = &families[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
Gdiplus::Bitmap bitmap(kTextTextureWidth, kTextTextureHeight, PixelFormat32bppARGB);
|
|
||||||
Gdiplus::Graphics graphics(&bitmap);
|
|
||||||
graphics.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
|
|
||||||
graphics.Clear(Gdiplus::Color(255, 0, 0, 0));
|
|
||||||
graphics.SetCompositingMode(Gdiplus::CompositingModeSourceOver);
|
|
||||||
graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias);
|
|
||||||
graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
|
|
||||||
Gdiplus::Font font(fontFamily, kTextFontPixelSize, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel);
|
|
||||||
Gdiplus::SolidBrush brush(Gdiplus::Color(255, 255, 255, 255));
|
|
||||||
Gdiplus::StringFormat format;
|
|
||||||
format.SetAlignment(Gdiplus::StringAlignmentNear);
|
|
||||||
format.SetLineAlignment(Gdiplus::StringAlignmentCenter);
|
|
||||||
format.SetFormatFlags(Gdiplus::StringFormatFlagsNoWrap | Gdiplus::StringFormatFlagsMeasureTrailingSpaces);
|
|
||||||
const Gdiplus::RectF layout(
|
|
||||||
kTextLayoutPadding,
|
|
||||||
0.0f,
|
|
||||||
static_cast<Gdiplus::REAL>(kTextTextureWidth) - (kTextLayoutPadding * 2.0f),
|
|
||||||
static_cast<Gdiplus::REAL>(kTextTextureHeight));
|
|
||||||
const std::wstring wideText = Utf8ToWide(text);
|
|
||||||
graphics.DrawString(wideText.c_str(), -1, &font, layout, &format, &brush);
|
|
||||||
|
|
||||||
std::vector<unsigned char> alpha(static_cast<std::size_t>(kTextTextureWidth) * kTextTextureHeight, 0);
|
|
||||||
for (unsigned y = 0; y < kTextTextureHeight; ++y)
|
|
||||||
{
|
|
||||||
for (unsigned x = 0; x < kTextTextureWidth; ++x)
|
|
||||||
{
|
|
||||||
Gdiplus::Color pixel;
|
|
||||||
bitmap.GetPixel(x, y, &pixel);
|
|
||||||
BYTE luminance = pixel.GetRed();
|
|
||||||
if (pixel.GetGreen() > luminance)
|
|
||||||
luminance = pixel.GetGreen();
|
|
||||||
if (pixel.GetBlue() > luminance)
|
|
||||||
luminance = pixel.GetBlue();
|
|
||||||
alpha[static_cast<std::size_t>(y) * kTextTextureWidth + x] = static_cast<unsigned char>(luminance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sdf = BuildTextSdfTexture(alpha, kTextTextureWidth, kTextTextureHeight);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
constexpr unsigned kTextTextureWidth = 4096;
|
|
||||||
constexpr unsigned kTextTextureHeight = 512;
|
|
||||||
|
|
||||||
bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector<unsigned char>& sdf, std::string& error);
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
#include "TextureAssetLoader.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include <wincodec.h>
|
|
||||||
|
|
||||||
#include <atlbase.h>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cctype>
|
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#ifndef GL_RGBA32F
|
|
||||||
#define GL_RGBA32F 0x8814
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
std::string LowercaseExtension(const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
std::string extension = path.extension().string();
|
|
||||||
std::transform(extension.begin(), extension.end(), extension.begin(),
|
|
||||||
[](unsigned char value) { return static_cast<char>(std::tolower(value)); });
|
|
||||||
return extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LoadCubeTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
|
|
||||||
{
|
|
||||||
std::ifstream file(textureAsset.path);
|
|
||||||
if (!file)
|
|
||||||
{
|
|
||||||
error = "Could not open shader LUT asset: " + textureAsset.path.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned lutSize = 0;
|
|
||||||
std::vector<float> values;
|
|
||||||
std::string line;
|
|
||||||
while (std::getline(file, line))
|
|
||||||
{
|
|
||||||
const std::size_t commentStart = line.find('#');
|
|
||||||
if (commentStart != std::string::npos)
|
|
||||||
line.resize(commentStart);
|
|
||||||
|
|
||||||
std::istringstream stream(line);
|
|
||||||
std::string firstToken;
|
|
||||||
if (!(stream >> firstToken))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (firstToken == "TITLE" || firstToken == "DOMAIN_MIN" || firstToken == "DOMAIN_MAX")
|
|
||||||
continue;
|
|
||||||
if (firstToken == "LUT_3D_SIZE")
|
|
||||||
{
|
|
||||||
stream >> lutSize;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (firstToken == "LUT_1D_SIZE")
|
|
||||||
{
|
|
||||||
error = "Only 3D .cube LUT assets are supported: " + textureAsset.path.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
float red = 0.0f;
|
|
||||||
float green = 0.0f;
|
|
||||||
float blue = 0.0f;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
red = std::stof(firstToken);
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
error = "Unsupported .cube directive in shader LUT asset: " + firstToken;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!(stream >> green >> blue))
|
|
||||||
{
|
|
||||||
error = "Malformed RGB entry in shader LUT asset: " + textureAsset.path.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
values.push_back(red);
|
|
||||||
values.push_back(green);
|
|
||||||
values.push_back(blue);
|
|
||||||
values.push_back(1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lutSize == 0)
|
|
||||||
{
|
|
||||||
error = "Shader LUT asset is missing LUT_3D_SIZE: " + textureAsset.path.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::size_t expectedFloats = static_cast<std::size_t>(lutSize) * lutSize * lutSize * 4;
|
|
||||||
if (values.size() != expectedFloats)
|
|
||||||
{
|
|
||||||
error = "Shader LUT asset entry count does not match LUT_3D_SIZE: " + textureAsset.path.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLsizei atlasWidth = static_cast<GLsizei>(lutSize * lutSize);
|
|
||||||
const GLsizei atlasHeight = static_cast<GLsizei>(lutSize);
|
|
||||||
glGenTextures(1, &textureId);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
|
||||||
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);
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, atlasWidth, atlasHeight, 0, GL_RGBA, GL_FLOAT, values.data());
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
|
|
||||||
{
|
|
||||||
textureId = 0;
|
|
||||||
if (LowercaseExtension(textureAsset.path) == ".cube")
|
|
||||||
return LoadCubeTextureAsset(textureAsset, textureId, error);
|
|
||||||
|
|
||||||
HRESULT comInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
|
||||||
const bool shouldUninitializeCom = (comInitResult == S_OK || comInitResult == S_FALSE);
|
|
||||||
if (FAILED(comInitResult) && comInitResult != RPC_E_CHANGED_MODE)
|
|
||||||
{
|
|
||||||
error = "Could not initialize COM to load shader texture assets.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CComPtr<IWICImagingFactory> imagingFactory;
|
|
||||||
HRESULT result = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&imagingFactory));
|
|
||||||
if (FAILED(result) || !imagingFactory)
|
|
||||||
{
|
|
||||||
if (shouldUninitializeCom)
|
|
||||||
CoUninitialize();
|
|
||||||
error = "Could not create a WIC imaging factory to load shader texture assets.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CComPtr<IWICBitmapDecoder> bitmapDecoder;
|
|
||||||
result = imagingFactory->CreateDecoderFromFilename(textureAsset.path.wstring().c_str(), NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &bitmapDecoder);
|
|
||||||
if (FAILED(result) || !bitmapDecoder)
|
|
||||||
{
|
|
||||||
if (shouldUninitializeCom)
|
|
||||||
CoUninitialize();
|
|
||||||
error = "Could not open shader texture asset: " + textureAsset.path.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CComPtr<IWICBitmapFrameDecode> bitmapFrame;
|
|
||||||
result = bitmapDecoder->GetFrame(0, &bitmapFrame);
|
|
||||||
if (FAILED(result) || !bitmapFrame)
|
|
||||||
{
|
|
||||||
if (shouldUninitializeCom)
|
|
||||||
CoUninitialize();
|
|
||||||
error = "Could not decode the first frame of shader texture asset: " + textureAsset.path.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CComPtr<IWICFormatConverter> formatConverter;
|
|
||||||
result = imagingFactory->CreateFormatConverter(&formatConverter);
|
|
||||||
if (FAILED(result) || !formatConverter)
|
|
||||||
{
|
|
||||||
if (shouldUninitializeCom)
|
|
||||||
CoUninitialize();
|
|
||||||
error = "Could not create a WIC format converter for shader texture asset: " + textureAsset.path.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = formatConverter->Initialize(bitmapFrame, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, NULL, 0.0, WICBitmapPaletteTypeCustom);
|
|
||||||
if (FAILED(result))
|
|
||||||
{
|
|
||||||
if (shouldUninitializeCom)
|
|
||||||
CoUninitialize();
|
|
||||||
error = "Could not convert shader texture asset to BGRA: " + textureAsset.path.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UINT width = 0;
|
|
||||||
UINT height = 0;
|
|
||||||
result = formatConverter->GetSize(&width, &height);
|
|
||||||
if (FAILED(result) || width == 0 || height == 0)
|
|
||||||
{
|
|
||||||
if (shouldUninitializeCom)
|
|
||||||
CoUninitialize();
|
|
||||||
error = "Shader texture asset has an invalid size: " + textureAsset.path.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const UINT stride = width * 4;
|
|
||||||
std::vector<unsigned char> pixels(static_cast<std::size_t>(stride) * static_cast<std::size_t>(height));
|
|
||||||
result = formatConverter->CopyPixels(NULL, stride, static_cast<UINT>(pixels.size()), pixels.data());
|
|
||||||
if (FAILED(result))
|
|
||||||
{
|
|
||||||
if (shouldUninitializeCom)
|
|
||||||
CoUninitialize();
|
|
||||||
error = "Could not read shader texture pixels: " + textureAsset.path.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<unsigned char> flippedPixels(pixels.size());
|
|
||||||
for (UINT row = 0; row < height; ++row)
|
|
||||||
{
|
|
||||||
const std::size_t srcOffset = static_cast<std::size_t>(row) * stride;
|
|
||||||
const std::size_t dstOffset = static_cast<std::size_t>(height - 1 - row) * stride;
|
|
||||||
std::memcpy(flippedPixels.data() + dstOffset, pixels.data() + srcOffset, stride);
|
|
||||||
}
|
|
||||||
|
|
||||||
glGenTextures(1, &textureId);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
|
||||||
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_BYTE, flippedPixels.data());
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
if (shouldUninitializeCom)
|
|
||||||
CoUninitialize();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "GLExtensions.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include <gl/gl.h>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error);
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
#include "RenderCommandQueue.h"
|
|
||||||
|
|
||||||
void RenderCommandQueue::RequestPreviewPresent(const RenderPreviewPresentRequest& request)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (mHasPreviewPresentRequest)
|
|
||||||
++mCoalescedCount;
|
|
||||||
else
|
|
||||||
++mEnqueuedCount;
|
|
||||||
|
|
||||||
mPreviewPresentRequest = request;
|
|
||||||
mHasPreviewPresentRequest = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderCommandQueue::TryTakePreviewPresent(RenderPreviewPresentRequest& request)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (!mHasPreviewPresentRequest)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
request = mPreviewPresentRequest;
|
|
||||||
mPreviewPresentRequest = {};
|
|
||||||
mHasPreviewPresentRequest = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderCommandQueue::RequestScreenshotCapture(const RenderScreenshotCaptureRequest& request)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (mHasScreenshotCaptureRequest)
|
|
||||||
++mCoalescedCount;
|
|
||||||
else
|
|
||||||
++mEnqueuedCount;
|
|
||||||
|
|
||||||
mScreenshotCaptureRequest = request;
|
|
||||||
mHasScreenshotCaptureRequest = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderCommandQueue::TryTakeScreenshotCapture(RenderScreenshotCaptureRequest& request)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (!mHasScreenshotCaptureRequest)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
request = mScreenshotCaptureRequest;
|
|
||||||
mScreenshotCaptureRequest = {};
|
|
||||||
mHasScreenshotCaptureRequest = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderCommandQueue::RequestInputUpload(const RenderInputUploadRequest& request)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (mHasInputUploadRequest)
|
|
||||||
++mCoalescedCount;
|
|
||||||
else
|
|
||||||
++mEnqueuedCount;
|
|
||||||
|
|
||||||
mInputUploadRequest = request;
|
|
||||||
mHasInputUploadRequest = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderCommandQueue::TryTakeInputUpload(RenderInputUploadRequest& request)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (!mHasInputUploadRequest)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
request = mInputUploadRequest;
|
|
||||||
mInputUploadRequest = {};
|
|
||||||
mHasInputUploadRequest = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderCommandQueue::RequestOutputFrame(const RenderOutputFrameRequest& request)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
mOutputFrameRequests.push_back(request);
|
|
||||||
++mEnqueuedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderCommandQueue::TryTakeOutputFrame(RenderOutputFrameRequest& request)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (mOutputFrameRequests.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
request = mOutputFrameRequests.front();
|
|
||||||
mOutputFrameRequests.pop_front();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderCommandQueue::RequestRenderReset(RenderCommandResetScope scope)
|
|
||||||
{
|
|
||||||
if (scope == RenderCommandResetScope::None)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (mRenderResetScope != RenderCommandResetScope::None)
|
|
||||||
++mCoalescedCount;
|
|
||||||
else
|
|
||||||
++mEnqueuedCount;
|
|
||||||
|
|
||||||
mRenderResetScope = MergeResetScopes(mRenderResetScope, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderCommandQueue::TryTakeRenderReset(RenderCommandResetScope& scope)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (mRenderResetScope == RenderCommandResetScope::None)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
scope = mRenderResetScope;
|
|
||||||
mRenderResetScope = RenderCommandResetScope::None;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderCommandQueueMetrics RenderCommandQueue::GetMetrics() const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
RenderCommandQueueMetrics metrics;
|
|
||||||
metrics.depth =
|
|
||||||
(mHasPreviewPresentRequest ? 1u : 0u) +
|
|
||||||
(mHasScreenshotCaptureRequest ? 1u : 0u) +
|
|
||||||
(mHasInputUploadRequest ? 1u : 0u) +
|
|
||||||
mOutputFrameRequests.size() +
|
|
||||||
(mRenderResetScope != RenderCommandResetScope::None ? 1u : 0u);
|
|
||||||
metrics.enqueuedCount = mEnqueuedCount;
|
|
||||||
metrics.coalescedCount = mCoalescedCount;
|
|
||||||
return metrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderCommandResetScope RenderCommandQueue::MergeResetScopes(RenderCommandResetScope current, RenderCommandResetScope requested)
|
|
||||||
{
|
|
||||||
if (current == RenderCommandResetScope::TemporalHistoryAndFeedback ||
|
|
||||||
requested == RenderCommandResetScope::TemporalHistoryAndFeedback)
|
|
||||||
{
|
|
||||||
return RenderCommandResetScope::TemporalHistoryAndFeedback;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((current == RenderCommandResetScope::TemporalHistoryOnly && requested == RenderCommandResetScope::ShaderFeedbackOnly) ||
|
|
||||||
(current == RenderCommandResetScope::ShaderFeedbackOnly && requested == RenderCommandResetScope::TemporalHistoryOnly))
|
|
||||||
{
|
|
||||||
return RenderCommandResetScope::TemporalHistoryAndFeedback;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current == RenderCommandResetScope::TemporalHistoryOnly ||
|
|
||||||
requested == RenderCommandResetScope::TemporalHistoryOnly)
|
|
||||||
{
|
|
||||||
return RenderCommandResetScope::TemporalHistoryOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current == RenderCommandResetScope::ShaderFeedbackOnly ||
|
|
||||||
requested == RenderCommandResetScope::ShaderFeedbackOnly)
|
|
||||||
{
|
|
||||||
return RenderCommandResetScope::ShaderFeedbackOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
return RenderCommandResetScope::None;
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "VideoIOTypes.h"
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <deque>
|
|
||||||
#include <mutex>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
enum class RenderCommandResetScope
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
ShaderFeedbackOnly,
|
|
||||||
TemporalHistoryOnly,
|
|
||||||
TemporalHistoryAndFeedback
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderPreviewPresentRequest
|
|
||||||
{
|
|
||||||
unsigned outputFrameWidth = 0;
|
|
||||||
unsigned outputFrameHeight = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderScreenshotCaptureRequest
|
|
||||||
{
|
|
||||||
unsigned width = 0;
|
|
||||||
unsigned height = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderInputUploadRequest
|
|
||||||
{
|
|
||||||
VideoIOFrame inputFrame;
|
|
||||||
VideoIOState videoState;
|
|
||||||
std::vector<unsigned char> ownedBytes;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderOutputFrameRequest
|
|
||||||
{
|
|
||||||
VideoIOState videoState;
|
|
||||||
VideoIOCompletion completion;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderCommandQueueMetrics
|
|
||||||
{
|
|
||||||
std::size_t depth = 0;
|
|
||||||
uint64_t enqueuedCount = 0;
|
|
||||||
uint64_t coalescedCount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RenderCommandQueue
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void RequestPreviewPresent(const RenderPreviewPresentRequest& request);
|
|
||||||
bool TryTakePreviewPresent(RenderPreviewPresentRequest& request);
|
|
||||||
|
|
||||||
void RequestScreenshotCapture(const RenderScreenshotCaptureRequest& request);
|
|
||||||
bool TryTakeScreenshotCapture(RenderScreenshotCaptureRequest& request);
|
|
||||||
|
|
||||||
void RequestInputUpload(const RenderInputUploadRequest& request);
|
|
||||||
bool TryTakeInputUpload(RenderInputUploadRequest& request);
|
|
||||||
|
|
||||||
void RequestOutputFrame(const RenderOutputFrameRequest& request);
|
|
||||||
bool TryTakeOutputFrame(RenderOutputFrameRequest& request);
|
|
||||||
|
|
||||||
void RequestRenderReset(RenderCommandResetScope scope);
|
|
||||||
bool TryTakeRenderReset(RenderCommandResetScope& scope);
|
|
||||||
|
|
||||||
RenderCommandQueueMetrics GetMetrics() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static RenderCommandResetScope MergeResetScopes(RenderCommandResetScope current, RenderCommandResetScope requested);
|
|
||||||
|
|
||||||
mutable std::mutex mMutex;
|
|
||||||
bool mHasPreviewPresentRequest = false;
|
|
||||||
RenderPreviewPresentRequest mPreviewPresentRequest;
|
|
||||||
bool mHasScreenshotCaptureRequest = false;
|
|
||||||
RenderScreenshotCaptureRequest mScreenshotCaptureRequest;
|
|
||||||
bool mHasInputUploadRequest = false;
|
|
||||||
RenderInputUploadRequest mInputUploadRequest;
|
|
||||||
std::deque<RenderOutputFrameRequest> mOutputFrameRequests;
|
|
||||||
RenderCommandResetScope mRenderResetScope = RenderCommandResetScope::None;
|
|
||||||
uint64_t mEnqueuedCount = 0;
|
|
||||||
uint64_t mCoalescedCount = 0;
|
|
||||||
};
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
class UniqueHandle
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit UniqueHandle(HANDLE handle = NULL) : mHandle(handle) {}
|
|
||||||
~UniqueHandle() { reset(); }
|
|
||||||
|
|
||||||
UniqueHandle(const UniqueHandle&) = delete;
|
|
||||||
UniqueHandle& operator=(const UniqueHandle&) = delete;
|
|
||||||
|
|
||||||
UniqueHandle(UniqueHandle&& other) noexcept : mHandle(other.release()) {}
|
|
||||||
|
|
||||||
UniqueHandle& operator=(UniqueHandle&& other) noexcept
|
|
||||||
{
|
|
||||||
if (this != &other)
|
|
||||||
reset(other.release());
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
HANDLE get() const { return mHandle; }
|
|
||||||
bool valid() const { return mHandle != NULL && mHandle != INVALID_HANDLE_VALUE; }
|
|
||||||
|
|
||||||
HANDLE release()
|
|
||||||
{
|
|
||||||
HANDLE handle = mHandle;
|
|
||||||
mHandle = NULL;
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset(HANDLE handle = NULL)
|
|
||||||
{
|
|
||||||
if (valid())
|
|
||||||
CloseHandle(mHandle);
|
|
||||||
mHandle = handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
HANDLE mHandle;
|
|
||||||
};
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <winsock2.h>
|
|
||||||
|
|
||||||
class UniqueSocket
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit UniqueSocket(SOCKET socket = INVALID_SOCKET) : mSocket(socket) {}
|
|
||||||
~UniqueSocket() { reset(); }
|
|
||||||
|
|
||||||
UniqueSocket(const UniqueSocket&) = delete;
|
|
||||||
UniqueSocket& operator=(const UniqueSocket&) = delete;
|
|
||||||
|
|
||||||
UniqueSocket(UniqueSocket&& other) noexcept : mSocket(other.release()) {}
|
|
||||||
|
|
||||||
UniqueSocket& operator=(UniqueSocket&& other) noexcept
|
|
||||||
{
|
|
||||||
if (this != &other)
|
|
||||||
reset(other.release());
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
SOCKET get() const { return mSocket; }
|
|
||||||
bool valid() const { return mSocket != INVALID_SOCKET; }
|
|
||||||
|
|
||||||
SOCKET release()
|
|
||||||
{
|
|
||||||
SOCKET socket = mSocket;
|
|
||||||
mSocket = INVALID_SOCKET;
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset(SOCKET socket = INVALID_SOCKET)
|
|
||||||
{
|
|
||||||
if (valid())
|
|
||||||
closesocket(mSocket);
|
|
||||||
mSocket = socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
SOCKET mSocket;
|
|
||||||
};
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
//{{NO_DEPENDENCIES}}
|
|
||||||
// Microsoft Visual C++ generated include file.
|
|
||||||
// Used by LoopThroughWithOpenGLCompositing.rc
|
|
||||||
//
|
|
||||||
#define IDC_MYICON 2
|
|
||||||
#define IDD_OPENGLOUTPUT_DIALOG 102
|
|
||||||
#define IDS_APP_TITLE 103
|
|
||||||
#define IDI_OPENGLOUTPUT 107
|
|
||||||
#define IDI_SMALL 108
|
|
||||||
#define IDC_OPENGLOUTPUT 109
|
|
||||||
#define IDR_MAINFRAME 128
|
|
||||||
#define IDC_STATIC -1
|
|
||||||
|
|
||||||
// Next default values for new objects
|
|
||||||
//
|
|
||||||
#ifdef APSTUDIO_INVOKED
|
|
||||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
|
||||||
#define _APS_NO_MFC 1
|
|
||||||
#define _APS_NEXT_RESOURCE_VALUE 129
|
|
||||||
#define _APS_NEXT_COMMAND_VALUE 32771
|
|
||||||
#define _APS_NEXT_CONTROL_VALUE 1000
|
|
||||||
#define _APS_NEXT_SYMED_VALUE 110
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
@@ -1,613 +0,0 @@
|
|||||||
#include "RuntimeCoordinator.h"
|
|
||||||
|
|
||||||
#include "RuntimeEventDispatcher.h"
|
|
||||||
#include "RuntimeEventPayloads.h"
|
|
||||||
#include "RuntimeParameterUtils.h"
|
|
||||||
#include "RuntimeStore.h"
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
RuntimeEventRenderResetScope ToRuntimeEventRenderResetScope(RuntimeCoordinatorRenderResetScope scope)
|
|
||||||
{
|
|
||||||
switch (scope)
|
|
||||||
{
|
|
||||||
case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly:
|
|
||||||
return RuntimeEventRenderResetScope::TemporalHistoryOnly;
|
|
||||||
case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback:
|
|
||||||
return RuntimeEventRenderResetScope::TemporalHistoryAndFeedback;
|
|
||||||
case RuntimeCoordinatorRenderResetScope::None:
|
|
||||||
default:
|
|
||||||
return RuntimeEventRenderResetScope::None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinator::RuntimeCoordinator(RuntimeStore& runtimeStore, RuntimeEventDispatcher& runtimeEventDispatcher) :
|
|
||||||
mRuntimeStore(runtimeStore),
|
|
||||||
mRuntimeEventDispatcher(runtimeEventDispatcher)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::AddLayer(const std::string& shaderId)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
std::string error;
|
|
||||||
if (!ValidateShaderExists(shaderId, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
|
||||||
PublishCoordinatorResult("AddLayer", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.CreateStoredLayer(shaderId, error), error, true, true, true);
|
|
||||||
PublishCoordinatorResult("AddLayer", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::RemoveLayer(const std::string& layerId)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
std::string error;
|
|
||||||
if (!ValidateLayerExists(layerId, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
|
||||||
PublishCoordinatorResult("RemoveLayer", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.DeleteStoredLayer(layerId, error), error, true, true, true);
|
|
||||||
if (result.accepted)
|
|
||||||
{
|
|
||||||
result.transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::Layer;
|
|
||||||
result.transientOscLayerKey = layerId;
|
|
||||||
}
|
|
||||||
PublishCoordinatorResult("RemoveLayer", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::MoveLayer(const std::string& layerId, int direction)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
std::string error;
|
|
||||||
bool shouldMove = false;
|
|
||||||
if (!ResolveLayerMove(layerId, direction, shouldMove, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
|
||||||
PublishCoordinatorResult("MoveLayer", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (!shouldMove)
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.MoveStoredLayer(layerId, direction, error), error, true, true, true);
|
|
||||||
PublishCoordinatorResult("MoveLayer", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
std::string error;
|
|
||||||
bool shouldMove = false;
|
|
||||||
if (!ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
|
||||||
PublishCoordinatorResult("MoveLayerToIndex", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (!shouldMove)
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.MoveStoredLayerToIndex(layerId, targetIndex, error), error, true, true, true);
|
|
||||||
PublishCoordinatorResult("MoveLayerToIndex", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::SetLayerBypass(const std::string& layerId, bool bypassed)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
std::string error;
|
|
||||||
if (!ValidateLayerExists(layerId, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
|
||||||
PublishCoordinatorResult("SetLayerBypass", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredLayerBypassState(layerId, bypassed, error), error, true, false, true);
|
|
||||||
PublishCoordinatorResult("SetLayerBypass", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::SetLayerShader(const std::string& layerId, const std::string& shaderId)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
std::string error;
|
|
||||||
if (!ValidateLayerExists(layerId, error) || !ValidateShaderExists(shaderId, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
|
||||||
PublishCoordinatorResult("SetLayerShader", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredLayerShaderSelection(layerId, shaderId, error), error, true, false, true);
|
|
||||||
PublishCoordinatorResult("SetLayerShader", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
std::string error;
|
|
||||||
ResolvedParameterMutation mutation;
|
|
||||||
if (!BuildParameterMutationById(layerId, parameterId, newValue, true, mutation, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
|
||||||
PublishCoordinatorResult("UpdateLayerParameter", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false, mutation.persistState);
|
|
||||||
PublishCoordinatorResult("UpdateLayerParameter", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
std::string error;
|
|
||||||
ResolvedParameterMutation mutation;
|
|
||||||
if (!BuildParameterMutationByControlKey(layerKey, parameterKey, newValue, true, mutation, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
|
||||||
PublishCoordinatorResult("UpdateLayerParameterByControlKey", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false, mutation.persistState);
|
|
||||||
PublishCoordinatorResult("UpdateLayerParameterByControlKey", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::CommitOscParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
constexpr RuntimeCoordinatorOscCommitPersistence kDefaultOscCommitPersistence =
|
|
||||||
RuntimeCoordinatorOscCommitPersistence::SessionOnly;
|
|
||||||
constexpr bool kPersistSettledOscCommits =
|
|
||||||
kDefaultOscCommitPersistence == RuntimeCoordinatorOscCommitPersistence::Persistent;
|
|
||||||
|
|
||||||
std::string error;
|
|
||||||
ResolvedParameterMutation mutation;
|
|
||||||
if (!BuildParameterMutationByControlKey(layerKey, parameterKey, newValue, kPersistSettledOscCommits, mutation, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
|
||||||
PublishCoordinatorResult("CommitOscParameterByControlKey", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false, mutation.persistState);
|
|
||||||
PublishCoordinatorResult("CommitOscParameterByControlKey", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::ResetLayerParameters(const std::string& layerId)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
std::string error;
|
|
||||||
if (!ValidateLayerExists(layerId, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
|
||||||
PublishCoordinatorResult("ResetLayerParameters", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.ResetStoredLayerParameterValues(layerId, error), error, false, false, true);
|
|
||||||
if (!result.accepted)
|
|
||||||
{
|
|
||||||
PublishCoordinatorResult("ResetLayerParameters", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::Layer;
|
|
||||||
result.transientOscLayerKey = layerId;
|
|
||||||
result.renderResetScope = RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback;
|
|
||||||
PublishCoordinatorResult("ResetLayerParameters", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::SaveStackPreset(const std::string& presetName)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
std::string error;
|
|
||||||
if (!ValidatePresetName(presetName, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
|
||||||
PublishCoordinatorResult("SaveStackPreset", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SaveStackPresetSnapshot(presetName, error), error, false, false, true);
|
|
||||||
PublishCoordinatorResult("SaveStackPreset", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::LoadStackPreset(const std::string& presetName)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
std::string error;
|
|
||||||
if (!ValidatePresetName(presetName, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
|
|
||||||
PublishCoordinatorResult("LoadStackPreset", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.LoadStackPresetSnapshot(presetName, error), error, true, false, true);
|
|
||||||
PublishCoordinatorResult("LoadStackPreset", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::RequestShaderReload(bool preserveFeedbackState)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
PublishManualReloadRequested(preserveFeedbackState, "RequestShaderReload");
|
|
||||||
RuntimeCoordinatorResult result = BuildQueuedReloadResult(preserveFeedbackState);
|
|
||||||
PublishCoordinatorFollowUpEvents("RequestShaderReload", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::PollRuntimeStoreChanges(bool& registryChanged)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
|
|
||||||
registryChanged = false;
|
|
||||||
bool reloadRequested = false;
|
|
||||||
std::string error;
|
|
||||||
if (!mRuntimeStore.PollStoredFileChanges(registryChanged, reloadRequested, error))
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = HandleRuntimePollFailure(error);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reloadRequested)
|
|
||||||
{
|
|
||||||
PublishFileChangeDetected("PollRuntimeStoreChanges", registryChanged, reloadRequested);
|
|
||||||
RuntimeCoordinatorResult result = BuildQueuedReloadResult(false);
|
|
||||||
PublishCoordinatorFollowUpEvents("PollRuntimeStoreChanges", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (registryChanged)
|
|
||||||
{
|
|
||||||
PublishFileChangeDetected("PollRuntimeStoreChanges", registryChanged, reloadRequested);
|
|
||||||
RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
|
|
||||||
PublishCoordinatorFollowUpEvents("PollRuntimeStoreChanges", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result;
|
|
||||||
result.accepted = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimePollFailure(const std::string& error)
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result;
|
|
||||||
result.accepted = true;
|
|
||||||
result.runtimeStateBroadcastRequired = true;
|
|
||||||
result.compileStatusChanged = true;
|
|
||||||
result.compileStatusSucceeded = false;
|
|
||||||
result.compileStatusMessage = error;
|
|
||||||
PublishCoordinatorFollowUpEvents("HandleRuntimePollFailure", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(const std::string& error)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
mPreserveFeedbackOnNextShaderBuild = false;
|
|
||||||
mUseCommittedLayerStates = true;
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result;
|
|
||||||
result.accepted = true;
|
|
||||||
result.runtimeStateBroadcastRequired = true;
|
|
||||||
result.compileStatusChanged = true;
|
|
||||||
result.compileStatusSucceeded = false;
|
|
||||||
result.compileStatusMessage = error;
|
|
||||||
result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseCommittedStates;
|
|
||||||
PublishCoordinatorFollowUpEvents("HandlePreparedShaderBuildFailure", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildSuccess()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
mUseCommittedLayerStates = false;
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result;
|
|
||||||
result.accepted = true;
|
|
||||||
result.runtimeStateBroadcastRequired = true;
|
|
||||||
result.compileStatusChanged = true;
|
|
||||||
result.compileStatusSucceeded = true;
|
|
||||||
result.compileStatusMessage = "Shader layers compiled successfully.";
|
|
||||||
result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots;
|
|
||||||
mPreserveFeedbackOnNextShaderBuild = false;
|
|
||||||
PublishCoordinatorFollowUpEvents("HandlePreparedShaderBuildSuccess", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimeReloadRequest()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
PublishManualReloadRequested(false, "HandleRuntimeReloadRequest");
|
|
||||||
RuntimeCoordinatorResult result = BuildQueuedReloadResult(false);
|
|
||||||
PublishCoordinatorFollowUpEvents("HandleRuntimeReloadRequest", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeCoordinator::ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
switch (mode)
|
|
||||||
{
|
|
||||||
case RuntimeCoordinatorCommittedStateMode::UseCommittedStates:
|
|
||||||
mUseCommittedLayerStates = true;
|
|
||||||
break;
|
|
||||||
case RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots:
|
|
||||||
mUseCommittedLayerStates = false;
|
|
||||||
break;
|
|
||||||
case RuntimeCoordinatorCommittedStateMode::Unchanged:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeCoordinator::UseCommittedLayerStates() const
|
|
||||||
{
|
|
||||||
return mUseCommittedLayerStates.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeCoordinator::PreserveFeedbackOnNextShaderBuild() const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
return mPreserveFeedbackOnNextShaderBuild;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeCoordinator::BuildParameterMutationById(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue,
|
|
||||||
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
|
|
||||||
{
|
|
||||||
RuntimeStore::StoredParameterSnapshot snapshot;
|
|
||||||
if (!mRuntimeStore.TryGetStoredParameterById(layerId, parameterId, snapshot, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return BuildParameterMutationFromSnapshot(snapshot.layerId, snapshot.definition, snapshot.currentValue, snapshot.hasCurrentValue,
|
|
||||||
newValue, persistState, mutation, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeCoordinator::BuildParameterMutationByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue,
|
|
||||||
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
|
|
||||||
{
|
|
||||||
RuntimeStore::StoredParameterSnapshot snapshot;
|
|
||||||
if (!mRuntimeStore.TryGetStoredParameterByControlKey(layerKey, parameterKey, snapshot, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return BuildParameterMutationFromSnapshot(snapshot.layerId, snapshot.definition, snapshot.currentValue, snapshot.hasCurrentValue,
|
|
||||||
newValue, persistState, mutation, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeCoordinator::BuildParameterMutationFromSnapshot(const std::string& layerId, const ShaderParameterDefinition& definition,
|
|
||||||
const ShaderParameterValue& currentValue, bool hasCurrentValue, const JsonValue& newValue,
|
|
||||||
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
|
|
||||||
{
|
|
||||||
mutation.layerId = layerId;
|
|
||||||
mutation.parameterId = definition.id;
|
|
||||||
mutation.persistState = persistState;
|
|
||||||
|
|
||||||
if (definition.type == ShaderParameterType::Trigger)
|
|
||||||
{
|
|
||||||
const double previousCount = !hasCurrentValue || currentValue.numberValues.empty()
|
|
||||||
? 0.0
|
|
||||||
: currentValue.numberValues[0];
|
|
||||||
const double triggerTime = mRuntimeStore.GetRuntimeElapsedSeconds();
|
|
||||||
mutation.value.numberValues = { previousCount + 1.0, triggerTime };
|
|
||||||
mutation.persistState = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NormalizeAndValidateParameterValue(definition, newValue, mutation.value, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeCoordinator::ValidateLayerExists(const std::string& layerId, std::string& error) const
|
|
||||||
{
|
|
||||||
if (mRuntimeStore.HasStoredLayer(layerId))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
error = "Unknown layer id: " + layerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeCoordinator::ValidateShaderExists(const std::string& shaderId, std::string& error) const
|
|
||||||
{
|
|
||||||
if (mRuntimeStore.HasStoredShader(shaderId))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
error = "Unknown shader id: " + shaderId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeCoordinator::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
|
|
||||||
{
|
|
||||||
return mRuntimeStore.ResolveStoredLayerMove(layerId, direction, shouldMove, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeCoordinator::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
|
|
||||||
{
|
|
||||||
return mRuntimeStore.ResolveStoredLayerMoveToIndex(layerId, targetIndex, shouldMove, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeCoordinator::ValidatePresetName(const std::string& presetName, std::string& error) const
|
|
||||||
{
|
|
||||||
if (mRuntimeStore.IsValidStackPresetName(presetName))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
error = "Preset name must include at least one letter or number.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState, bool persistenceRequested)
|
|
||||||
{
|
|
||||||
if (!succeeded)
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result;
|
|
||||||
result.accepted = false;
|
|
||||||
result.errorMessage = errorMessage;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reloadRequired)
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result = BuildQueuedReloadResult(preserveFeedbackState);
|
|
||||||
result.persistenceRequested = persistenceRequested;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
|
|
||||||
result.persistenceRequested = persistenceRequested;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::BuildQueuedReloadResult(bool preserveFeedbackState)
|
|
||||||
{
|
|
||||||
mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState;
|
|
||||||
mUseCommittedLayerStates = true;
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult result;
|
|
||||||
result.accepted = true;
|
|
||||||
result.runtimeStateBroadcastRequired = true;
|
|
||||||
result.shaderBuildRequested = true;
|
|
||||||
result.compileStatusChanged = true;
|
|
||||||
result.compileStatusSucceeded = true;
|
|
||||||
result.compileStatusMessage = "Shader rebuild queued.";
|
|
||||||
result.clearReloadRequest = true;
|
|
||||||
result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseCommittedStates;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RuntimeCoordinator::BuildAcceptedNoReloadResult() const
|
|
||||||
{
|
|
||||||
RuntimeCoordinatorResult result;
|
|
||||||
result.accepted = true;
|
|
||||||
result.runtimeStateBroadcastRequired = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeCoordinator::PublishFileChangeDetected(const std::string& reason, bool registryChanged, bool reloadRequested) const
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
FileChangeDetectedEvent event;
|
|
||||||
event.path = reason;
|
|
||||||
event.shaderPackageCandidate = registryChanged || reloadRequested;
|
|
||||||
event.runtimeConfigCandidate = false;
|
|
||||||
event.presetCandidate = false;
|
|
||||||
mRuntimeEventDispatcher.PublishPayload(event, "RuntimeCoordinator");
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeCoordinator::PublishManualReloadRequested(bool preserveFeedbackState, const std::string& reason) const
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ManualReloadRequestedEvent event;
|
|
||||||
event.preserveFeedbackState = preserveFeedbackState;
|
|
||||||
event.reason = reason;
|
|
||||||
mRuntimeEventDispatcher.PublishPayload(event, "RuntimeCoordinator");
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeCoordinator::PublishCoordinatorResult(const std::string& action, const RuntimeCoordinatorResult& result) const
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
RuntimeMutationEvent mutation;
|
|
||||||
mutation.action = action;
|
|
||||||
mutation.accepted = result.accepted;
|
|
||||||
mutation.runtimeStateChanged = result.accepted && result.runtimeStateBroadcastRequired;
|
|
||||||
mutation.runtimeStateBroadcastRequired = result.runtimeStateBroadcastRequired;
|
|
||||||
mutation.shaderBuildRequested = result.shaderBuildRequested;
|
|
||||||
mutation.persistenceRequested = result.persistenceRequested;
|
|
||||||
mutation.clearTransientOscState = result.transientOscInvalidation != RuntimeCoordinatorTransientOscInvalidation::None;
|
|
||||||
mutation.renderResetScope = ToRuntimeEventRenderResetScope(result.renderResetScope);
|
|
||||||
mutation.errorMessage = result.errorMessage;
|
|
||||||
mRuntimeEventDispatcher.PublishPayload(mutation, "RuntimeCoordinator");
|
|
||||||
|
|
||||||
PublishCoordinatorFollowUpEvents(action, result);
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeCoordinator::PublishCoordinatorFollowUpEvents(const std::string& action, const RuntimeCoordinatorResult& result) const
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!result.accepted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (result.runtimeStateBroadcastRequired)
|
|
||||||
{
|
|
||||||
RuntimeStateChangedEvent stateChanged;
|
|
||||||
stateChanged.reason = action;
|
|
||||||
stateChanged.renderVisible = result.renderResetScope != RuntimeCoordinatorRenderResetScope::None;
|
|
||||||
stateChanged.persistenceRequested = result.persistenceRequested;
|
|
||||||
mRuntimeEventDispatcher.PublishPayload(stateChanged, "RuntimeCoordinator");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.persistenceRequested)
|
|
||||||
{
|
|
||||||
RuntimePersistenceRequestedEvent persistenceRequested;
|
|
||||||
persistenceRequested.request = PersistenceRequest::RuntimeStateRequest(action);
|
|
||||||
mRuntimeEventDispatcher.PublishPayload(persistenceRequested, "RuntimeCoordinator");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.shaderBuildRequested)
|
|
||||||
{
|
|
||||||
RuntimeReloadRequestedEvent reloadRequested;
|
|
||||||
reloadRequested.preserveFeedbackState = mPreserveFeedbackOnNextShaderBuild;
|
|
||||||
reloadRequested.reason = action;
|
|
||||||
mRuntimeEventDispatcher.PublishPayload(reloadRequested, "RuntimeCoordinator");
|
|
||||||
|
|
||||||
ShaderBuildEvent shaderBuild;
|
|
||||||
shaderBuild.phase = RuntimeEventShaderBuildPhase::Requested;
|
|
||||||
shaderBuild.preserveFeedbackState = mPreserveFeedbackOnNextShaderBuild;
|
|
||||||
shaderBuild.succeeded = true;
|
|
||||||
shaderBuild.message = result.compileStatusMessage;
|
|
||||||
mRuntimeEventDispatcher.PublishPayload(shaderBuild, "RuntimeCoordinator");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.compileStatusChanged)
|
|
||||||
{
|
|
||||||
CompileStatusChangedEvent compileStatus;
|
|
||||||
compileStatus.succeeded = result.compileStatusSucceeded;
|
|
||||||
compileStatus.message = result.compileStatusMessage;
|
|
||||||
mRuntimeEventDispatcher.PublishPayload(compileStatus, "RuntimeCoordinator");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeJson.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class RuntimeStore;
|
|
||||||
class RuntimeEventDispatcher;
|
|
||||||
|
|
||||||
enum class RuntimeCoordinatorCommittedStateMode
|
|
||||||
{
|
|
||||||
Unchanged,
|
|
||||||
UseCommittedStates,
|
|
||||||
UseLiveSnapshots
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class RuntimeCoordinatorRenderResetScope
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
TemporalHistoryOnly,
|
|
||||||
TemporalHistoryAndFeedback
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class RuntimeCoordinatorTransientOscInvalidation
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
Layer,
|
|
||||||
All
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class RuntimeCoordinatorOscCommitPersistence
|
|
||||||
{
|
|
||||||
SessionOnly,
|
|
||||||
Persistent
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeCoordinatorResult
|
|
||||||
{
|
|
||||||
bool accepted = false;
|
|
||||||
bool runtimeStateBroadcastRequired = false;
|
|
||||||
bool shaderBuildRequested = false;
|
|
||||||
bool persistenceRequested = false;
|
|
||||||
bool compileStatusChanged = false;
|
|
||||||
bool compileStatusSucceeded = false;
|
|
||||||
bool clearReloadRequest = false;
|
|
||||||
RuntimeCoordinatorCommittedStateMode committedStateMode = RuntimeCoordinatorCommittedStateMode::Unchanged;
|
|
||||||
RuntimeCoordinatorRenderResetScope renderResetScope = RuntimeCoordinatorRenderResetScope::None;
|
|
||||||
RuntimeCoordinatorTransientOscInvalidation transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::None;
|
|
||||||
std::string transientOscLayerKey;
|
|
||||||
std::string compileStatusMessage;
|
|
||||||
std::string errorMessage;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RuntimeCoordinator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
RuntimeCoordinator(RuntimeStore& runtimeStore, RuntimeEventDispatcher& runtimeEventDispatcher);
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult AddLayer(const std::string& shaderId);
|
|
||||||
RuntimeCoordinatorResult RemoveLayer(const std::string& layerId);
|
|
||||||
RuntimeCoordinatorResult MoveLayer(const std::string& layerId, int direction);
|
|
||||||
RuntimeCoordinatorResult MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex);
|
|
||||||
RuntimeCoordinatorResult SetLayerBypass(const std::string& layerId, bool bypassed);
|
|
||||||
RuntimeCoordinatorResult SetLayerShader(const std::string& layerId, const std::string& shaderId);
|
|
||||||
RuntimeCoordinatorResult UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue);
|
|
||||||
RuntimeCoordinatorResult UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue);
|
|
||||||
RuntimeCoordinatorResult CommitOscParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue);
|
|
||||||
RuntimeCoordinatorResult ResetLayerParameters(const std::string& layerId);
|
|
||||||
RuntimeCoordinatorResult SaveStackPreset(const std::string& presetName);
|
|
||||||
RuntimeCoordinatorResult LoadStackPreset(const std::string& presetName);
|
|
||||||
|
|
||||||
RuntimeCoordinatorResult RequestShaderReload(bool preserveFeedbackState = false);
|
|
||||||
RuntimeCoordinatorResult PollRuntimeStoreChanges(bool& registryChanged);
|
|
||||||
RuntimeCoordinatorResult HandleRuntimePollFailure(const std::string& error);
|
|
||||||
RuntimeCoordinatorResult HandlePreparedShaderBuildFailure(const std::string& error);
|
|
||||||
RuntimeCoordinatorResult HandlePreparedShaderBuildSuccess();
|
|
||||||
RuntimeCoordinatorResult HandleRuntimeReloadRequest();
|
|
||||||
void ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode);
|
|
||||||
bool UseCommittedLayerStates() const;
|
|
||||||
bool PreserveFeedbackOnNextShaderBuild() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct ResolvedParameterMutation
|
|
||||||
{
|
|
||||||
std::string layerId;
|
|
||||||
std::string parameterId;
|
|
||||||
ShaderParameterValue value;
|
|
||||||
bool persistState = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool BuildParameterMutationById(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue,
|
|
||||||
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
|
|
||||||
bool BuildParameterMutationByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue,
|
|
||||||
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
|
|
||||||
bool BuildParameterMutationFromSnapshot(const std::string& layerId, const ShaderParameterDefinition& definition,
|
|
||||||
const ShaderParameterValue& currentValue, bool hasCurrentValue, const JsonValue& newValue,
|
|
||||||
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
|
|
||||||
bool ValidateLayerExists(const std::string& layerId, std::string& error) const;
|
|
||||||
bool ValidateShaderExists(const std::string& shaderId, std::string& error) const;
|
|
||||||
bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
|
|
||||||
bool ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
|
|
||||||
bool ValidatePresetName(const std::string& presetName, std::string& error) const;
|
|
||||||
RuntimeCoordinatorResult ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState, bool persistenceRequested);
|
|
||||||
RuntimeCoordinatorResult BuildQueuedReloadResult(bool preserveFeedbackState);
|
|
||||||
RuntimeCoordinatorResult BuildAcceptedNoReloadResult() const;
|
|
||||||
void PublishFileChangeDetected(const std::string& reason, bool registryChanged, bool reloadRequested) const;
|
|
||||||
void PublishManualReloadRequested(bool preserveFeedbackState, const std::string& reason) const;
|
|
||||||
void PublishCoordinatorResult(const std::string& action, const RuntimeCoordinatorResult& result) const;
|
|
||||||
void PublishCoordinatorFollowUpEvents(const std::string& action, const RuntimeCoordinatorResult& result) const;
|
|
||||||
|
|
||||||
RuntimeStore& mRuntimeStore;
|
|
||||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
|
||||||
mutable std::mutex mMutex;
|
|
||||||
bool mPreserveFeedbackOnNextShaderBuild = false;
|
|
||||||
std::atomic<bool> mUseCommittedLayerStates{ false };
|
|
||||||
};
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeEventPayloads.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <utility>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
using RuntimeEventPayload = std::variant<
|
|
||||||
std::monostate,
|
|
||||||
OscValueReceivedEvent,
|
|
||||||
OscValueCoalescedEvent,
|
|
||||||
OscCommitRequestedEvent,
|
|
||||||
HttpControlMutationRequestedEvent,
|
|
||||||
WebSocketClientConnectedEvent,
|
|
||||||
RuntimeStateBroadcastRequestedEvent,
|
|
||||||
FileChangeDetectedEvent,
|
|
||||||
ManualReloadRequestedEvent,
|
|
||||||
RuntimeMutationEvent,
|
|
||||||
RuntimeStateChangedEvent,
|
|
||||||
RuntimePersistenceRequestedEvent,
|
|
||||||
RuntimeReloadRequestedEvent,
|
|
||||||
ShaderPackagesChangedEvent,
|
|
||||||
RenderSnapshotPublishRequestedEvent,
|
|
||||||
RuntimeStatePresentationChangedEvent,
|
|
||||||
ShaderBuildEvent,
|
|
||||||
CompileStatusChangedEvent,
|
|
||||||
RenderSnapshotPublishedEvent,
|
|
||||||
RenderResetEvent,
|
|
||||||
OscOverlayEvent,
|
|
||||||
FrameRenderedEvent,
|
|
||||||
PreviewFrameAvailableEvent,
|
|
||||||
InputSignalChangedEvent,
|
|
||||||
InputFrameArrivedEvent,
|
|
||||||
OutputFrameScheduledEvent,
|
|
||||||
OutputFrameCompletedEvent,
|
|
||||||
BackendStateChangedEvent,
|
|
||||||
SubsystemWarningEvent,
|
|
||||||
SubsystemRecoveredEvent,
|
|
||||||
TimingSampleRecordedEvent,
|
|
||||||
QueueDepthChangedEvent>;
|
|
||||||
|
|
||||||
inline RuntimeEventType RuntimeEventPayloadType(const RuntimeEventPayload& payload)
|
|
||||||
{
|
|
||||||
return std::visit([](const auto& value) -> RuntimeEventType {
|
|
||||||
using PayloadType = std::decay_t<decltype(value)>;
|
|
||||||
if constexpr (std::is_same_v<PayloadType, std::monostate>)
|
|
||||||
return RuntimeEventType::Unknown;
|
|
||||||
else
|
|
||||||
return RuntimeEventPayloadType(value);
|
|
||||||
}, payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RuntimeEvent
|
|
||||||
{
|
|
||||||
RuntimeEventType type = RuntimeEventType::Unknown;
|
|
||||||
uint64_t sequence = 0;
|
|
||||||
std::chrono::steady_clock::time_point createdAt = std::chrono::steady_clock::now();
|
|
||||||
std::string source;
|
|
||||||
RuntimeEventPayload payload;
|
|
||||||
|
|
||||||
bool HasPayload() const
|
|
||||||
{
|
|
||||||
return !std::holds_alternative<std::monostate>(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PayloadMatchesType() const
|
|
||||||
{
|
|
||||||
return RuntimeEventPayloadType(payload) == type;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Payload>
|
|
||||||
RuntimeEvent MakeRuntimeEvent(Payload payload, std::string source = {}, uint64_t sequence = 0,
|
|
||||||
std::chrono::steady_clock::time_point createdAt = std::chrono::steady_clock::now())
|
|
||||||
{
|
|
||||||
RuntimeEvent event;
|
|
||||||
event.type = RuntimeEventPayloadType(payload);
|
|
||||||
event.sequence = sequence;
|
|
||||||
event.createdAt = createdAt;
|
|
||||||
event.source = std::move(source);
|
|
||||||
event.payload = std::move(payload);
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeEvent.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <deque>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct RuntimeEventCoalescingQueueMetrics
|
|
||||||
{
|
|
||||||
std::size_t depth = 0;
|
|
||||||
std::size_t capacity = 0;
|
|
||||||
std::size_t droppedCount = 0;
|
|
||||||
std::size_t coalescedCount = 0;
|
|
||||||
double oldestEventAgeMilliseconds = 0.0;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline std::string RuntimeEventDefaultCoalescingKey(const RuntimeEvent& event)
|
|
||||||
{
|
|
||||||
if (const auto* payload = std::get_if<OscValueReceivedEvent>(&event.payload))
|
|
||||||
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->routeKey;
|
|
||||||
if (const auto* payload = std::get_if<OscCommitRequestedEvent>(&event.payload))
|
|
||||||
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->routeKey;
|
|
||||||
if (const auto* payload = std::get_if<FileChangeDetectedEvent>(&event.payload))
|
|
||||||
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->path;
|
|
||||||
if (const auto* payload = std::get_if<ShaderBuildEvent>(&event.payload))
|
|
||||||
return std::string(RuntimeEventTypeName(event.type)) + ":" +
|
|
||||||
std::to_string(payload->inputWidth) + "x" +
|
|
||||||
std::to_string(payload->inputHeight) + ":" +
|
|
||||||
(payload->preserveFeedbackState ? "preserve" : "reset");
|
|
||||||
if (const auto* payload = std::get_if<RenderSnapshotPublishRequestedEvent>(&event.payload))
|
|
||||||
return std::string(RuntimeEventTypeName(event.type)) + ":" +
|
|
||||||
std::to_string(payload->outputWidth) + "x" +
|
|
||||||
std::to_string(payload->outputHeight);
|
|
||||||
if (const auto* payload = std::get_if<TimingSampleRecordedEvent>(&event.payload))
|
|
||||||
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->subsystem + ":" + payload->metric;
|
|
||||||
if (const auto* payload = std::get_if<QueueDepthChangedEvent>(&event.payload))
|
|
||||||
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->queueName;
|
|
||||||
|
|
||||||
return std::string(RuntimeEventTypeName(event.type));
|
|
||||||
}
|
|
||||||
|
|
||||||
class RuntimeEventCoalescingQueue
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using KeySelector = std::function<std::string(const RuntimeEvent&)>;
|
|
||||||
|
|
||||||
explicit RuntimeEventCoalescingQueue(std::size_t capacity = 256, KeySelector keySelector = RuntimeEventDefaultCoalescingKey) :
|
|
||||||
mCapacity(capacity),
|
|
||||||
mKeySelector(std::move(keySelector))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Push(RuntimeEvent event)
|
|
||||||
{
|
|
||||||
const std::string key = mKeySelector(event);
|
|
||||||
if (key.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
auto found = mEntries.find(key);
|
|
||||||
if (found != mEntries.end())
|
|
||||||
{
|
|
||||||
const auto firstCreatedAt = found->second.event.createdAt;
|
|
||||||
found->second.event = std::move(event);
|
|
||||||
found->second.event.createdAt = firstCreatedAt;
|
|
||||||
++found->second.coalescedCount;
|
|
||||||
++mCoalescedCount;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mEntries.size() >= mCapacity)
|
|
||||||
{
|
|
||||||
++mDroppedCount;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mOrder.push_back(key);
|
|
||||||
Entry entry;
|
|
||||||
entry.event = std::move(event);
|
|
||||||
mEntries.emplace(key, std::move(entry));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<RuntimeEvent> Drain(std::size_t maxEvents = 0)
|
|
||||||
{
|
|
||||||
std::vector<RuntimeEvent> events;
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
const std::size_t count = maxEvents == 0 || maxEvents > mOrder.size() ? mOrder.size() : maxEvents;
|
|
||||||
events.reserve(count);
|
|
||||||
|
|
||||||
for (std::size_t index = 0; index < count; ++index)
|
|
||||||
{
|
|
||||||
const std::string key = std::move(mOrder.front());
|
|
||||||
mOrder.pop_front();
|
|
||||||
|
|
||||||
auto found = mEntries.find(key);
|
|
||||||
if (found == mEntries.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
events.push_back(std::move(found->second.event));
|
|
||||||
mEntries.erase(found);
|
|
||||||
}
|
|
||||||
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeEventCoalescingQueueMetrics GetMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
|
|
||||||
RuntimeEventCoalescingQueueMetrics metrics;
|
|
||||||
metrics.depth = mEntries.size();
|
|
||||||
metrics.capacity = mCapacity;
|
|
||||||
metrics.droppedCount = mDroppedCount;
|
|
||||||
metrics.coalescedCount = mCoalescedCount;
|
|
||||||
|
|
||||||
if (!mOrder.empty())
|
|
||||||
{
|
|
||||||
const auto found = mEntries.find(mOrder.front());
|
|
||||||
if (found != mEntries.end())
|
|
||||||
{
|
|
||||||
const auto age = now - found->second.event.createdAt;
|
|
||||||
metrics.oldestEventAgeMilliseconds = std::chrono::duration<double, std::milli>(age).count();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return metrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t Depth() const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
return mEntries.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Entry
|
|
||||||
{
|
|
||||||
RuntimeEvent event;
|
|
||||||
std::size_t coalescedCount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
mutable std::mutex mMutex;
|
|
||||||
std::size_t mCapacity = 0;
|
|
||||||
KeySelector mKeySelector;
|
|
||||||
std::deque<std::string> mOrder;
|
|
||||||
std::map<std::string, Entry> mEntries;
|
|
||||||
std::size_t mDroppedCount = 0;
|
|
||||||
std::size_t mCoalescedCount = 0;
|
|
||||||
};
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeEventCoalescingQueue.h"
|
|
||||||
#include "RuntimeEventQueue.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <atomic>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include <mutex>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct RuntimeEventDispatchResult
|
|
||||||
{
|
|
||||||
std::size_t dispatchedEvents = 0;
|
|
||||||
std::size_t handlerInvocations = 0;
|
|
||||||
std::size_t handlerFailures = 0;
|
|
||||||
double dispatchDurationMilliseconds = 0.0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RuntimeEventDispatcher
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using Handler = std::function<void(const RuntimeEvent&)>;
|
|
||||||
|
|
||||||
explicit RuntimeEventDispatcher(std::size_t queueCapacity = 1024) :
|
|
||||||
mQueue(queueCapacity),
|
|
||||||
mCoalescingQueue(queueCapacity)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Publish(RuntimeEvent event)
|
|
||||||
{
|
|
||||||
if (!event.PayloadMatchesType())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (event.sequence == 0)
|
|
||||||
event.sequence = mNextSequence.fetch_add(1);
|
|
||||||
|
|
||||||
if (ShouldCoalesce(event))
|
|
||||||
return mCoalescingQueue.Push(std::move(event));
|
|
||||||
|
|
||||||
return mQueue.Push(std::move(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Payload>
|
|
||||||
bool PublishPayload(Payload payload, std::string source = {})
|
|
||||||
{
|
|
||||||
return Publish(MakeRuntimeEvent(std::move(payload), std::move(source)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Subscribe(RuntimeEventType type, Handler handler)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mHandlerMutex);
|
|
||||||
mHandlers[type].push_back(std::move(handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubscribeAll(Handler handler)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mHandlerMutex);
|
|
||||||
mAllHandlers.push_back(std::move(handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeEventDispatchResult DispatchPending(std::size_t maxEvents = 0)
|
|
||||||
{
|
|
||||||
const auto startedAt = std::chrono::steady_clock::now();
|
|
||||||
RuntimeEventDispatchResult result;
|
|
||||||
FlushCoalescedToFifo(maxEvents);
|
|
||||||
std::vector<RuntimeEvent> events = mQueue.Drain(maxEvents);
|
|
||||||
result.dispatchedEvents = events.size();
|
|
||||||
|
|
||||||
for (const RuntimeEvent& event : events)
|
|
||||||
{
|
|
||||||
std::vector<Handler> handlers = HandlersFor(event.type);
|
|
||||||
result.handlerInvocations += handlers.size();
|
|
||||||
|
|
||||||
for (const Handler& handler : handlers)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
handler(event);
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
++result.handlerFailures;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.dispatchDurationMilliseconds =
|
|
||||||
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - startedAt).count();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TryPop(RuntimeEvent& event)
|
|
||||||
{
|
|
||||||
return mQueue.TryPop(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeEventQueueMetrics GetQueueMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
|
|
||||||
{
|
|
||||||
RuntimeEventQueueMetrics metrics = mQueue.GetMetrics(now);
|
|
||||||
const RuntimeEventCoalescingQueueMetrics coalescingMetrics = mCoalescingQueue.GetMetrics(now);
|
|
||||||
if (metrics.depth == 0)
|
|
||||||
metrics.oldestEventAgeMilliseconds = coalescingMetrics.oldestEventAgeMilliseconds;
|
|
||||||
else if (coalescingMetrics.depth > 0)
|
|
||||||
metrics.oldestEventAgeMilliseconds = (std::max)(metrics.oldestEventAgeMilliseconds, coalescingMetrics.oldestEventAgeMilliseconds);
|
|
||||||
metrics.depth += coalescingMetrics.depth;
|
|
||||||
metrics.capacity += coalescingMetrics.capacity;
|
|
||||||
metrics.droppedCount += coalescingMetrics.droppedCount;
|
|
||||||
metrics.coalescedCount = coalescingMetrics.coalescedCount;
|
|
||||||
return metrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t QueueDepth() const
|
|
||||||
{
|
|
||||||
return mQueue.Depth() + mCoalescingQueue.Depth();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static bool ShouldCoalesce(const RuntimeEvent& event)
|
|
||||||
{
|
|
||||||
switch (event.type)
|
|
||||||
{
|
|
||||||
case RuntimeEventType::OscValueReceived:
|
|
||||||
case RuntimeEventType::OscCommitRequested:
|
|
||||||
case RuntimeEventType::RuntimeStateBroadcastRequested:
|
|
||||||
case RuntimeEventType::FileChangeDetected:
|
|
||||||
case RuntimeEventType::RuntimeReloadRequested:
|
|
||||||
case RuntimeEventType::ShaderBuildRequested:
|
|
||||||
case RuntimeEventType::RenderSnapshotPublishRequested:
|
|
||||||
case RuntimeEventType::TimingSampleRecorded:
|
|
||||||
case RuntimeEventType::QueueDepthChanged:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlushCoalescedToFifo(std::size_t maxEvents)
|
|
||||||
{
|
|
||||||
const std::size_t fifoDepth = mQueue.Depth();
|
|
||||||
if (maxEvents != 0 && fifoDepth >= maxEvents)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const std::size_t flushLimit = maxEvents == 0 ? 0 : maxEvents - fifoDepth;
|
|
||||||
std::vector<RuntimeEvent> events = mCoalescingQueue.Drain(flushLimit);
|
|
||||||
for (RuntimeEvent& event : events)
|
|
||||||
mQueue.Push(std::move(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Handler> HandlersFor(RuntimeEventType type) const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mHandlerMutex);
|
|
||||||
std::vector<Handler> handlers = mAllHandlers;
|
|
||||||
|
|
||||||
const auto found = mHandlers.find(type);
|
|
||||||
if (found != mHandlers.end())
|
|
||||||
handlers.insert(handlers.end(), found->second.begin(), found->second.end());
|
|
||||||
|
|
||||||
return handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeEventQueue mQueue;
|
|
||||||
RuntimeEventCoalescingQueue mCoalescingQueue;
|
|
||||||
std::atomic<uint64_t> mNextSequence{ 1 };
|
|
||||||
mutable std::mutex mHandlerMutex;
|
|
||||||
std::map<RuntimeEventType, std::vector<Handler>> mHandlers;
|
|
||||||
std::vector<Handler> mAllHandlers;
|
|
||||||
};
|
|
||||||
@@ -1,442 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeEventType.h"
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include "PersistenceRequest.h"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
enum class RuntimeEventSeverity
|
|
||||||
{
|
|
||||||
Debug,
|
|
||||||
Info,
|
|
||||||
Warning,
|
|
||||||
Error
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class RuntimeEventRenderResetScope
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
TemporalHistoryOnly,
|
|
||||||
TemporalHistoryAndFeedback
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class RuntimeEventShaderBuildPhase
|
|
||||||
{
|
|
||||||
Requested,
|
|
||||||
Prepared,
|
|
||||||
Applied,
|
|
||||||
Failed
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OscValueReceivedEvent
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
std::string layerKey;
|
|
||||||
std::string parameterKey;
|
|
||||||
std::string valueJson;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OscValueCoalescedEvent
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
std::size_t coalescedCount = 0;
|
|
||||||
uint64_t latestGeneration = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OscCommitRequestedEvent
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
std::string layerKey;
|
|
||||||
std::string parameterKey;
|
|
||||||
std::string valueJson;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HttpControlMutationRequestedEvent
|
|
||||||
{
|
|
||||||
std::string method;
|
|
||||||
std::string path;
|
|
||||||
std::string bodyJson;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WebSocketClientConnectedEvent
|
|
||||||
{
|
|
||||||
std::string clientId;
|
|
||||||
std::size_t connectedClientCount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeStateBroadcastRequestedEvent
|
|
||||||
{
|
|
||||||
std::string reason;
|
|
||||||
bool coalescable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FileChangeDetectedEvent
|
|
||||||
{
|
|
||||||
std::string path;
|
|
||||||
bool shaderPackageCandidate = false;
|
|
||||||
bool runtimeConfigCandidate = false;
|
|
||||||
bool presetCandidate = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ManualReloadRequestedEvent
|
|
||||||
{
|
|
||||||
bool preserveFeedbackState = false;
|
|
||||||
std::string reason;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeMutationEvent
|
|
||||||
{
|
|
||||||
std::string action;
|
|
||||||
bool accepted = false;
|
|
||||||
bool runtimeStateChanged = false;
|
|
||||||
bool runtimeStateBroadcastRequired = false;
|
|
||||||
bool shaderBuildRequested = false;
|
|
||||||
bool persistenceRequested = false;
|
|
||||||
bool clearTransientOscState = false;
|
|
||||||
RuntimeEventRenderResetScope renderResetScope = RuntimeEventRenderResetScope::None;
|
|
||||||
std::string errorMessage;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeStateChangedEvent
|
|
||||||
{
|
|
||||||
std::string reason;
|
|
||||||
bool renderVisible = false;
|
|
||||||
bool persistenceRequested = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimePersistenceRequestedEvent
|
|
||||||
{
|
|
||||||
PersistenceRequest request;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeReloadRequestedEvent
|
|
||||||
{
|
|
||||||
bool preserveFeedbackState = false;
|
|
||||||
std::string reason;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ShaderPackagesChangedEvent
|
|
||||||
{
|
|
||||||
bool registryChanged = false;
|
|
||||||
std::size_t packageCount = 0;
|
|
||||||
std::string reason;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderSnapshotPublishRequestedEvent
|
|
||||||
{
|
|
||||||
unsigned inputWidth = 0;
|
|
||||||
unsigned inputHeight = 0;
|
|
||||||
unsigned outputWidth = 0;
|
|
||||||
unsigned outputHeight = 0;
|
|
||||||
std::string reason;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeStatePresentationChangedEvent
|
|
||||||
{
|
|
||||||
std::string reason;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ShaderBuildEvent
|
|
||||||
{
|
|
||||||
RuntimeEventShaderBuildPhase phase = RuntimeEventShaderBuildPhase::Requested;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
unsigned inputWidth = 0;
|
|
||||||
unsigned inputHeight = 0;
|
|
||||||
bool preserveFeedbackState = false;
|
|
||||||
bool succeeded = false;
|
|
||||||
std::string message;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CompileStatusChangedEvent
|
|
||||||
{
|
|
||||||
bool succeeded = false;
|
|
||||||
std::string message;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderSnapshotPublishedEvent
|
|
||||||
{
|
|
||||||
uint64_t snapshotVersion = 0;
|
|
||||||
uint64_t structureVersion = 0;
|
|
||||||
uint64_t parameterVersion = 0;
|
|
||||||
uint64_t packageVersion = 0;
|
|
||||||
unsigned outputWidth = 0;
|
|
||||||
unsigned outputHeight = 0;
|
|
||||||
std::size_t layerCount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderResetEvent
|
|
||||||
{
|
|
||||||
RuntimeEventRenderResetScope scope = RuntimeEventRenderResetScope::None;
|
|
||||||
bool applied = false;
|
|
||||||
std::string reason;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OscOverlayEvent
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
std::string layerKey;
|
|
||||||
std::string parameterKey;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
bool settled = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FrameRenderedEvent
|
|
||||||
{
|
|
||||||
uint64_t frameIndex = 0;
|
|
||||||
double renderMilliseconds = 0.0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PreviewFrameAvailableEvent
|
|
||||||
{
|
|
||||||
uint64_t frameIndex = 0;
|
|
||||||
unsigned width = 0;
|
|
||||||
unsigned height = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InputSignalChangedEvent
|
|
||||||
{
|
|
||||||
bool hasSignal = false;
|
|
||||||
unsigned width = 0;
|
|
||||||
unsigned height = 0;
|
|
||||||
std::string modeName;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InputFrameArrivedEvent
|
|
||||||
{
|
|
||||||
uint64_t frameIndex = 0;
|
|
||||||
unsigned width = 0;
|
|
||||||
unsigned height = 0;
|
|
||||||
long rowBytes = 0;
|
|
||||||
std::string pixelFormat;
|
|
||||||
bool hasNoInputSource = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OutputFrameScheduledEvent
|
|
||||||
{
|
|
||||||
uint64_t frameIndex = 0;
|
|
||||||
int64_t streamTime = 0;
|
|
||||||
int64_t duration = 0;
|
|
||||||
int64_t timeScale = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OutputFrameCompletedEvent
|
|
||||||
{
|
|
||||||
uint64_t frameIndex = 0;
|
|
||||||
std::string result;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BackendStateChangedEvent
|
|
||||||
{
|
|
||||||
std::string backendName;
|
|
||||||
std::string state;
|
|
||||||
std::string message;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SubsystemWarningEvent
|
|
||||||
{
|
|
||||||
std::string subsystem;
|
|
||||||
std::string warningKey;
|
|
||||||
RuntimeEventSeverity severity = RuntimeEventSeverity::Warning;
|
|
||||||
std::string message;
|
|
||||||
bool cleared = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SubsystemRecoveredEvent
|
|
||||||
{
|
|
||||||
std::string subsystem;
|
|
||||||
std::string recoveryKey;
|
|
||||||
std::string message;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TimingSampleRecordedEvent
|
|
||||||
{
|
|
||||||
std::string subsystem;
|
|
||||||
std::string metric;
|
|
||||||
double value = 0.0;
|
|
||||||
std::string unit;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct QueueDepthChangedEvent
|
|
||||||
{
|
|
||||||
std::string queueName;
|
|
||||||
std::size_t depth = 0;
|
|
||||||
std::size_t capacity = 0;
|
|
||||||
std::size_t droppedCount = 0;
|
|
||||||
std::size_t coalescedCount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const OscValueReceivedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::OscValueReceived;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const OscValueCoalescedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::OscValueCoalesced;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const OscCommitRequestedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::OscCommitRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const HttpControlMutationRequestedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::HttpControlMutationRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const WebSocketClientConnectedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::WebSocketClientConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStateBroadcastRequestedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::RuntimeStateBroadcastRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const FileChangeDetectedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::FileChangeDetected;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const ManualReloadRequestedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::ManualReloadRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline RuntimeEventType RuntimeEventPayloadType(const RuntimeMutationEvent& event)
|
|
||||||
{
|
|
||||||
return event.accepted ? RuntimeEventType::RuntimeMutationAccepted : RuntimeEventType::RuntimeMutationRejected;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStateChangedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::RuntimeStateChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimePersistenceRequestedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::RuntimePersistenceRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeReloadRequestedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::RuntimeReloadRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const ShaderPackagesChangedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::ShaderPackagesChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RenderSnapshotPublishRequestedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::RenderSnapshotPublishRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStatePresentationChangedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::RuntimeStatePresentationChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline RuntimeEventType RuntimeEventPayloadType(const ShaderBuildEvent& event)
|
|
||||||
{
|
|
||||||
switch (event.phase)
|
|
||||||
{
|
|
||||||
case RuntimeEventShaderBuildPhase::Requested:
|
|
||||||
return RuntimeEventType::ShaderBuildRequested;
|
|
||||||
case RuntimeEventShaderBuildPhase::Prepared:
|
|
||||||
return RuntimeEventType::ShaderBuildPrepared;
|
|
||||||
case RuntimeEventShaderBuildPhase::Applied:
|
|
||||||
return RuntimeEventType::ShaderBuildApplied;
|
|
||||||
case RuntimeEventShaderBuildPhase::Failed:
|
|
||||||
return RuntimeEventType::ShaderBuildFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return RuntimeEventType::ShaderBuildRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const CompileStatusChangedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::CompileStatusChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const RenderSnapshotPublishedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::RenderSnapshotPublished;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline RuntimeEventType RuntimeEventPayloadType(const RenderResetEvent& event)
|
|
||||||
{
|
|
||||||
return event.applied ? RuntimeEventType::RenderResetApplied : RuntimeEventType::RenderResetRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline RuntimeEventType RuntimeEventPayloadType(const OscOverlayEvent& event)
|
|
||||||
{
|
|
||||||
return event.settled ? RuntimeEventType::OscOverlaySettled : RuntimeEventType::OscOverlayApplied;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const FrameRenderedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::FrameRendered;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const PreviewFrameAvailableEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::PreviewFrameAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const InputSignalChangedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::InputSignalChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const InputFrameArrivedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::InputFrameArrived;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const OutputFrameScheduledEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::OutputFrameScheduled;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline RuntimeEventType RuntimeEventPayloadType(const OutputFrameCompletedEvent& event)
|
|
||||||
{
|
|
||||||
if (event.result == "DisplayedLate")
|
|
||||||
return RuntimeEventType::OutputLateFrameDetected;
|
|
||||||
if (event.result == "Dropped")
|
|
||||||
return RuntimeEventType::OutputDroppedFrameDetected;
|
|
||||||
return RuntimeEventType::OutputFrameCompleted;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const BackendStateChangedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::BackendStateChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline RuntimeEventType RuntimeEventPayloadType(const SubsystemWarningEvent& event)
|
|
||||||
{
|
|
||||||
return event.cleared ? RuntimeEventType::SubsystemWarningCleared : RuntimeEventType::SubsystemWarningRaised;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const SubsystemRecoveredEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::SubsystemRecovered;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const TimingSampleRecordedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::TimingSampleRecorded;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr RuntimeEventType RuntimeEventPayloadType(const QueueDepthChangedEvent&)
|
|
||||||
{
|
|
||||||
return RuntimeEventType::QueueDepthChanged;
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeEvent.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <deque>
|
|
||||||
#include <mutex>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct RuntimeEventQueueMetrics
|
|
||||||
{
|
|
||||||
std::size_t depth = 0;
|
|
||||||
std::size_t capacity = 0;
|
|
||||||
std::size_t droppedCount = 0;
|
|
||||||
std::size_t coalescedCount = 0;
|
|
||||||
double oldestEventAgeMilliseconds = 0.0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RuntimeEventQueue
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit RuntimeEventQueue(std::size_t capacity = 1024) :
|
|
||||||
mCapacity(capacity)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Push(RuntimeEvent event)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (mEvents.size() >= mCapacity)
|
|
||||||
{
|
|
||||||
++mDroppedCount;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mEvents.push_back(std::move(event));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TryPop(RuntimeEvent& event)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (mEvents.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
event = std::move(mEvents.front());
|
|
||||||
mEvents.pop_front();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<RuntimeEvent> Drain(std::size_t maxEvents = 0)
|
|
||||||
{
|
|
||||||
std::vector<RuntimeEvent> events;
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
const std::size_t count = maxEvents == 0 || maxEvents > mEvents.size() ? mEvents.size() : maxEvents;
|
|
||||||
events.reserve(count);
|
|
||||||
for (std::size_t index = 0; index < count; ++index)
|
|
||||||
{
|
|
||||||
events.push_back(std::move(mEvents.front()));
|
|
||||||
mEvents.pop_front();
|
|
||||||
}
|
|
||||||
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeEventQueueMetrics GetMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
|
|
||||||
RuntimeEventQueueMetrics metrics;
|
|
||||||
metrics.depth = mEvents.size();
|
|
||||||
metrics.capacity = mCapacity;
|
|
||||||
metrics.droppedCount = mDroppedCount;
|
|
||||||
if (!mEvents.empty())
|
|
||||||
{
|
|
||||||
const auto age = now - mEvents.front().createdAt;
|
|
||||||
metrics.oldestEventAgeMilliseconds = std::chrono::duration<double, std::milli>(age).count();
|
|
||||||
}
|
|
||||||
return metrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t Depth() const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
return mEvents.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t Capacity() const
|
|
||||||
{
|
|
||||||
return mCapacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
mutable std::mutex mMutex;
|
|
||||||
std::deque<RuntimeEvent> mEvents;
|
|
||||||
std::size_t mCapacity = 0;
|
|
||||||
std::size_t mDroppedCount = 0;
|
|
||||||
};
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
enum class RuntimeEventType
|
|
||||||
{
|
|
||||||
Unknown = 0,
|
|
||||||
|
|
||||||
// Control ingress.
|
|
||||||
OscValueReceived,
|
|
||||||
OscValueCoalesced,
|
|
||||||
OscCommitRequested,
|
|
||||||
HttpControlMutationRequested,
|
|
||||||
WebSocketClientConnected,
|
|
||||||
RuntimeStateBroadcastRequested,
|
|
||||||
FileChangeDetected,
|
|
||||||
ManualReloadRequested,
|
|
||||||
|
|
||||||
// Runtime policy and state.
|
|
||||||
RuntimeMutationAccepted,
|
|
||||||
RuntimeMutationRejected,
|
|
||||||
RuntimeStateChanged,
|
|
||||||
RuntimePersistenceRequested,
|
|
||||||
RuntimeReloadRequested,
|
|
||||||
ShaderPackagesChanged,
|
|
||||||
RenderSnapshotPublishRequested,
|
|
||||||
RuntimeStatePresentationChanged,
|
|
||||||
|
|
||||||
// Shader build lifecycle.
|
|
||||||
ShaderBuildRequested,
|
|
||||||
ShaderBuildPrepared,
|
|
||||||
ShaderBuildApplied,
|
|
||||||
ShaderBuildFailed,
|
|
||||||
CompileStatusChanged,
|
|
||||||
|
|
||||||
// Render lifecycle.
|
|
||||||
RenderSnapshotPublished,
|
|
||||||
RenderResetRequested,
|
|
||||||
RenderResetApplied,
|
|
||||||
OscOverlayApplied,
|
|
||||||
OscOverlaySettled,
|
|
||||||
FrameRendered,
|
|
||||||
PreviewFrameAvailable,
|
|
||||||
|
|
||||||
// Video backend lifecycle.
|
|
||||||
InputSignalChanged,
|
|
||||||
InputFrameArrived,
|
|
||||||
OutputFrameScheduled,
|
|
||||||
OutputFrameCompleted,
|
|
||||||
OutputLateFrameDetected,
|
|
||||||
OutputDroppedFrameDetected,
|
|
||||||
BackendStateChanged,
|
|
||||||
|
|
||||||
// Health and telemetry.
|
|
||||||
SubsystemWarningRaised,
|
|
||||||
SubsystemWarningCleared,
|
|
||||||
SubsystemRecovered,
|
|
||||||
TimingSampleRecorded,
|
|
||||||
QueueDepthChanged
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr std::string_view RuntimeEventTypeName(RuntimeEventType type)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case RuntimeEventType::Unknown:
|
|
||||||
return "Unknown";
|
|
||||||
case RuntimeEventType::OscValueReceived:
|
|
||||||
return "OscValueReceived";
|
|
||||||
case RuntimeEventType::OscValueCoalesced:
|
|
||||||
return "OscValueCoalesced";
|
|
||||||
case RuntimeEventType::OscCommitRequested:
|
|
||||||
return "OscCommitRequested";
|
|
||||||
case RuntimeEventType::HttpControlMutationRequested:
|
|
||||||
return "HttpControlMutationRequested";
|
|
||||||
case RuntimeEventType::WebSocketClientConnected:
|
|
||||||
return "WebSocketClientConnected";
|
|
||||||
case RuntimeEventType::RuntimeStateBroadcastRequested:
|
|
||||||
return "RuntimeStateBroadcastRequested";
|
|
||||||
case RuntimeEventType::FileChangeDetected:
|
|
||||||
return "FileChangeDetected";
|
|
||||||
case RuntimeEventType::ManualReloadRequested:
|
|
||||||
return "ManualReloadRequested";
|
|
||||||
case RuntimeEventType::RuntimeMutationAccepted:
|
|
||||||
return "RuntimeMutationAccepted";
|
|
||||||
case RuntimeEventType::RuntimeMutationRejected:
|
|
||||||
return "RuntimeMutationRejected";
|
|
||||||
case RuntimeEventType::RuntimeStateChanged:
|
|
||||||
return "RuntimeStateChanged";
|
|
||||||
case RuntimeEventType::RuntimePersistenceRequested:
|
|
||||||
return "RuntimePersistenceRequested";
|
|
||||||
case RuntimeEventType::RuntimeReloadRequested:
|
|
||||||
return "RuntimeReloadRequested";
|
|
||||||
case RuntimeEventType::ShaderPackagesChanged:
|
|
||||||
return "ShaderPackagesChanged";
|
|
||||||
case RuntimeEventType::RenderSnapshotPublishRequested:
|
|
||||||
return "RenderSnapshotPublishRequested";
|
|
||||||
case RuntimeEventType::RuntimeStatePresentationChanged:
|
|
||||||
return "RuntimeStatePresentationChanged";
|
|
||||||
case RuntimeEventType::ShaderBuildRequested:
|
|
||||||
return "ShaderBuildRequested";
|
|
||||||
case RuntimeEventType::ShaderBuildPrepared:
|
|
||||||
return "ShaderBuildPrepared";
|
|
||||||
case RuntimeEventType::ShaderBuildApplied:
|
|
||||||
return "ShaderBuildApplied";
|
|
||||||
case RuntimeEventType::ShaderBuildFailed:
|
|
||||||
return "ShaderBuildFailed";
|
|
||||||
case RuntimeEventType::CompileStatusChanged:
|
|
||||||
return "CompileStatusChanged";
|
|
||||||
case RuntimeEventType::RenderSnapshotPublished:
|
|
||||||
return "RenderSnapshotPublished";
|
|
||||||
case RuntimeEventType::RenderResetRequested:
|
|
||||||
return "RenderResetRequested";
|
|
||||||
case RuntimeEventType::RenderResetApplied:
|
|
||||||
return "RenderResetApplied";
|
|
||||||
case RuntimeEventType::OscOverlayApplied:
|
|
||||||
return "OscOverlayApplied";
|
|
||||||
case RuntimeEventType::OscOverlaySettled:
|
|
||||||
return "OscOverlaySettled";
|
|
||||||
case RuntimeEventType::FrameRendered:
|
|
||||||
return "FrameRendered";
|
|
||||||
case RuntimeEventType::PreviewFrameAvailable:
|
|
||||||
return "PreviewFrameAvailable";
|
|
||||||
case RuntimeEventType::InputSignalChanged:
|
|
||||||
return "InputSignalChanged";
|
|
||||||
case RuntimeEventType::InputFrameArrived:
|
|
||||||
return "InputFrameArrived";
|
|
||||||
case RuntimeEventType::OutputFrameScheduled:
|
|
||||||
return "OutputFrameScheduled";
|
|
||||||
case RuntimeEventType::OutputFrameCompleted:
|
|
||||||
return "OutputFrameCompleted";
|
|
||||||
case RuntimeEventType::OutputLateFrameDetected:
|
|
||||||
return "OutputLateFrameDetected";
|
|
||||||
case RuntimeEventType::OutputDroppedFrameDetected:
|
|
||||||
return "OutputDroppedFrameDetected";
|
|
||||||
case RuntimeEventType::BackendStateChanged:
|
|
||||||
return "BackendStateChanged";
|
|
||||||
case RuntimeEventType::SubsystemWarningRaised:
|
|
||||||
return "SubsystemWarningRaised";
|
|
||||||
case RuntimeEventType::SubsystemWarningCleared:
|
|
||||||
return "SubsystemWarningCleared";
|
|
||||||
case RuntimeEventType::SubsystemRecovered:
|
|
||||||
return "SubsystemRecovered";
|
|
||||||
case RuntimeEventType::TimingSampleRecorded:
|
|
||||||
return "TimingSampleRecorded";
|
|
||||||
case RuntimeEventType::QueueDepthChanged:
|
|
||||||
return "QueueDepthChanged";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
#include "CommittedLiveState.h"
|
|
||||||
|
|
||||||
bool CommittedLiveState::LoadPersistentStateValue(const JsonValue& root)
|
|
||||||
{
|
|
||||||
return mLayerStack.LoadPersistentStateValue(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValue CommittedLiveState::BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const
|
|
||||||
{
|
|
||||||
return mLayerStack.BuildPersistentStateValue(shaderCatalog);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommittedLiveState::NormalizeLayerIds()
|
|
||||||
{
|
|
||||||
mLayerStack.NormalizeLayerIds();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommittedLiveState::EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog)
|
|
||||||
{
|
|
||||||
mLayerStack.EnsureDefaultsForAllLayers(shaderCatalog);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommittedLiveState::EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog)
|
|
||||||
{
|
|
||||||
mLayerStack.EnsureDefaultLayer(shaderCatalog);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommittedLiveState::RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog)
|
|
||||||
{
|
|
||||||
mLayerStack.RemoveLayersWithMissingPackages(shaderCatalog);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error)
|
|
||||||
{
|
|
||||||
return mLayerStack.CreateLayer(shaderCatalog, shaderId, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::DeleteLayer(const std::string& layerId, std::string& error)
|
|
||||||
{
|
|
||||||
return mLayerStack.DeleteLayer(layerId, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::MoveLayer(const std::string& layerId, int direction, std::string& error)
|
|
||||||
{
|
|
||||||
return mLayerStack.MoveLayer(layerId, direction, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
|
||||||
{
|
|
||||||
return mLayerStack.MoveLayerToIndex(layerId, targetIndex, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
|
|
||||||
{
|
|
||||||
return mLayerStack.SetLayerBypassState(layerId, bypassed, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error)
|
|
||||||
{
|
|
||||||
return mLayerStack.SetLayerShaderSelection(shaderCatalog, layerId, shaderId, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error)
|
|
||||||
{
|
|
||||||
return mLayerStack.SetParameterValue(layerId, parameterId, value, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error)
|
|
||||||
{
|
|
||||||
return mLayerStack.ResetLayerParameterValues(shaderCatalog, layerId, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::HasLayer(const std::string& layerId) const
|
|
||||||
{
|
|
||||||
return mLayerStack.HasLayer(layerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const
|
|
||||||
{
|
|
||||||
return mLayerStack.TryGetParameterById(shaderCatalog, layerId, parameterId, snapshot, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const
|
|
||||||
{
|
|
||||||
return mLayerStack.TryGetParameterByControlKey(shaderCatalog, layerKey, parameterKey, snapshot, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
|
|
||||||
{
|
|
||||||
return mLayerStack.ResolveLayerMove(layerId, direction, shouldMove, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
|
|
||||||
{
|
|
||||||
return mLayerStack.ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValue CommittedLiveState::BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const
|
|
||||||
{
|
|
||||||
return mLayerStack.BuildStackPresetValue(shaderCatalog, presetName);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedLiveState::LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error)
|
|
||||||
{
|
|
||||||
return mLayerStack.LoadStackPresetValue(shaderCatalog, root, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
CommittedLiveStateReadModel CommittedLiveState::BuildReadModel(const ShaderPackageCatalog& shaderCatalog) const
|
|
||||||
{
|
|
||||||
CommittedLiveStateReadModel model;
|
|
||||||
model.layers = mLayerStack.Layers();
|
|
||||||
model.packagesById = shaderCatalog.CaptureSnapshot().packagesById;
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<CommittedLiveState::LayerPersistentState> CommittedLiveState::CopyLayerStates() const
|
|
||||||
{
|
|
||||||
return mLayerStack.Layers();
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<CommittedLiveState::LayerPersistentState>& CommittedLiveState::Layers() const
|
|
||||||
{
|
|
||||||
return mLayerStack.Layers();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<CommittedLiveState::LayerPersistentState>& CommittedLiveState::Layers()
|
|
||||||
{
|
|
||||||
return mLayerStack.Layers();
|
|
||||||
}
|
|
||||||
|
|
||||||
const CommittedLiveState::LayerPersistentState* CommittedLiveState::FindLayerById(const std::string& layerId) const
|
|
||||||
{
|
|
||||||
return mLayerStack.FindLayerById(layerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const LayerStackStore& CommittedLiveState::LayerStack() const
|
|
||||||
{
|
|
||||||
return mLayerStack;
|
|
||||||
}
|
|
||||||
|
|
||||||
LayerStackStore& CommittedLiveState::LayerStack()
|
|
||||||
{
|
|
||||||
return mLayerStack;
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "LayerStackStore.h"
|
|
||||||
#include "RuntimeStoreReadModels.h"
|
|
||||||
#include "ShaderPackageCatalog.h"
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class CommittedLiveState
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using LayerPersistentState = LayerStackStore::LayerPersistentState;
|
|
||||||
using StoredParameterSnapshot = LayerStackStore::StoredParameterSnapshot;
|
|
||||||
|
|
||||||
bool LoadPersistentStateValue(const JsonValue& root);
|
|
||||||
JsonValue BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const;
|
|
||||||
void NormalizeLayerIds();
|
|
||||||
void EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog);
|
|
||||||
void EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog);
|
|
||||||
void RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog);
|
|
||||||
|
|
||||||
bool CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error);
|
|
||||||
bool DeleteLayer(const std::string& layerId, std::string& error);
|
|
||||||
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
|
|
||||||
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
|
|
||||||
bool SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error);
|
|
||||||
bool SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error);
|
|
||||||
bool SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error);
|
|
||||||
bool ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error);
|
|
||||||
|
|
||||||
bool HasLayer(const std::string& layerId) const;
|
|
||||||
bool TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const;
|
|
||||||
bool TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const;
|
|
||||||
bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
|
|
||||||
bool ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
|
|
||||||
|
|
||||||
JsonValue BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const;
|
|
||||||
bool LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error);
|
|
||||||
|
|
||||||
CommittedLiveStateReadModel BuildReadModel(const ShaderPackageCatalog& shaderCatalog) const;
|
|
||||||
std::vector<LayerPersistentState> CopyLayerStates() const;
|
|
||||||
const std::vector<LayerPersistentState>& Layers() const;
|
|
||||||
std::vector<LayerPersistentState>& Layers();
|
|
||||||
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
|
|
||||||
const LayerStackStore& LayerStack() const;
|
|
||||||
LayerStackStore& LayerStack();
|
|
||||||
|
|
||||||
private:
|
|
||||||
LayerStackStore mLayerStack;
|
|
||||||
};
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#include "RenderStateComposer.h"
|
|
||||||
|
|
||||||
RenderStateCompositionResult RenderStateComposer::BuildFrameState(const LayeredRenderStateInput& input) const
|
|
||||||
{
|
|
||||||
RenderStateCompositionResult result;
|
|
||||||
const std::vector<RuntimeRenderState>* layerStates =
|
|
||||||
input.committedLiveLayerStates ? input.committedLiveLayerStates : input.basePersistedLayerStates;
|
|
||||||
if (!layerStates)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
result.layerStates = *layerStates;
|
|
||||||
result.hasLayerStates = !result.layerStates.empty();
|
|
||||||
if (input.transientAutomationOverlay)
|
|
||||||
{
|
|
||||||
RuntimeLiveStateApplyOptions options;
|
|
||||||
options.allowCommit = input.allowTransientAutomationCommits;
|
|
||||||
options.smoothing = input.transientAutomationSmoothing;
|
|
||||||
options.commitDelay = input.transientAutomationCommitDelay;
|
|
||||||
options.now = input.now;
|
|
||||||
input.transientAutomationOverlay->ApplyToLayerStates(
|
|
||||||
result.layerStates,
|
|
||||||
options,
|
|
||||||
input.collectTransientAutomationCommitRequests ? &result.commitRequests : nullptr);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeLiveState.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct LayeredRenderStateInput
|
|
||||||
{
|
|
||||||
const std::vector<RuntimeRenderState>* basePersistedLayerStates = nullptr;
|
|
||||||
const std::vector<RuntimeRenderState>* committedLiveLayerStates = nullptr;
|
|
||||||
RuntimeLiveState* transientAutomationOverlay = nullptr;
|
|
||||||
bool allowTransientAutomationCommits = false;
|
|
||||||
bool collectTransientAutomationCommitRequests = true;
|
|
||||||
double transientAutomationSmoothing = 0.0;
|
|
||||||
std::chrono::milliseconds transientAutomationCommitDelay = std::chrono::milliseconds(150);
|
|
||||||
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderStateCompositionResult
|
|
||||||
{
|
|
||||||
std::vector<RuntimeRenderState> layerStates;
|
|
||||||
std::vector<RuntimeLiveOscCommitRequest> commitRequests;
|
|
||||||
bool hasLayerStates = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RenderStateComposer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
RenderStateCompositionResult BuildFrameState(const LayeredRenderStateInput& input) const;
|
|
||||||
};
|
|
||||||
@@ -1,329 +0,0 @@
|
|||||||
#include "RuntimeLiveState.h"
|
|
||||||
|
|
||||||
#include "RuntimeParameterUtils.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cctype>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
constexpr double kOscSmoothingReferenceFps = 60.0;
|
|
||||||
constexpr double kOscSmoothingMaxStepSeconds = 0.25;
|
|
||||||
|
|
||||||
std::string SimplifyOscControlKey(const std::string& text)
|
|
||||||
{
|
|
||||||
std::string simplified;
|
|
||||||
for (unsigned char ch : text)
|
|
||||||
{
|
|
||||||
if (std::isalnum(ch))
|
|
||||||
simplified.push_back(static_cast<char>(std::tolower(ch)));
|
|
||||||
}
|
|
||||||
return simplified;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MatchesOscControlKey(const std::string& candidate, const std::string& key)
|
|
||||||
{
|
|
||||||
return candidate == key || SimplifyOscControlKey(candidate) == SimplifyOscControlKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
double ClampOscAlpha(double value)
|
|
||||||
{
|
|
||||||
return (std::max)(0.0, (std::min)(1.0, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
double ComputeTimeBasedOscAlpha(double smoothing, double deltaSeconds)
|
|
||||||
{
|
|
||||||
const double clampedSmoothing = ClampOscAlpha(smoothing);
|
|
||||||
if (clampedSmoothing <= 0.0)
|
|
||||||
return 0.0;
|
|
||||||
if (clampedSmoothing >= 1.0)
|
|
||||||
return 1.0;
|
|
||||||
|
|
||||||
const double clampedDeltaSeconds = (std::max)(0.0, (std::min)(kOscSmoothingMaxStepSeconds, deltaSeconds));
|
|
||||||
if (clampedDeltaSeconds <= 0.0)
|
|
||||||
return 0.0;
|
|
||||||
|
|
||||||
const double frameScale = clampedDeltaSeconds * kOscSmoothingReferenceFps;
|
|
||||||
return ClampOscAlpha(1.0 - std::pow(1.0 - clampedSmoothing, frameScale));
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValue BuildOscCommitValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
|
|
||||||
{
|
|
||||||
switch (definition.type)
|
|
||||||
{
|
|
||||||
case ShaderParameterType::Boolean:
|
|
||||||
return JsonValue(value.booleanValue);
|
|
||||||
case ShaderParameterType::Enum:
|
|
||||||
return JsonValue(value.enumValue);
|
|
||||||
case ShaderParameterType::Text:
|
|
||||||
return JsonValue(value.textValue);
|
|
||||||
case ShaderParameterType::Trigger:
|
|
||||||
case ShaderParameterType::Float:
|
|
||||||
return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
|
|
||||||
case ShaderParameterType::Vec2:
|
|
||||||
case ShaderParameterType::Color:
|
|
||||||
{
|
|
||||||
JsonValue array = JsonValue::MakeArray();
|
|
||||||
for (double number : value.numberValues)
|
|
||||||
array.pushBack(JsonValue(number));
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsonValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeLiveState::Clear()
|
|
||||||
{
|
|
||||||
mOscOverlayStates.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeLiveState::ClearForLayerKey(const std::string& layerKey)
|
|
||||||
{
|
|
||||||
for (auto it = mOscOverlayStates.begin(); it != mOscOverlayStates.end();)
|
|
||||||
{
|
|
||||||
if (OverlayMatchesLayerKey(it->second, layerKey))
|
|
||||||
it = mOscOverlayStates.erase(it);
|
|
||||||
else
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeLiveState::OverlayMatchesLayerKey(const OscOverlayState& overlay, const std::string& layerKey)
|
|
||||||
{
|
|
||||||
return MatchesOscControlKey(overlay.layerKey, layerKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeLiveState::TryResolveOverlayTarget(
|
|
||||||
const OscOverlayState& overlay,
|
|
||||||
const std::vector<RuntimeRenderState>& states,
|
|
||||||
std::vector<RuntimeRenderState>::const_iterator& stateIt,
|
|
||||||
std::vector<ShaderParameterDefinition>::const_iterator& definitionIt)
|
|
||||||
{
|
|
||||||
stateIt = std::find_if(states.begin(), states.end(),
|
|
||||||
[&overlay](const RuntimeRenderState& state)
|
|
||||||
{
|
|
||||||
return MatchesOscControlKey(state.layerId, overlay.layerKey) ||
|
|
||||||
MatchesOscControlKey(state.shaderId, overlay.layerKey) ||
|
|
||||||
MatchesOscControlKey(state.shaderName, overlay.layerKey);
|
|
||||||
});
|
|
||||||
if (stateIt == states.end())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(),
|
|
||||||
[&overlay](const ShaderParameterDefinition& definition)
|
|
||||||
{
|
|
||||||
return MatchesOscControlKey(definition.id, overlay.parameterKey) ||
|
|
||||||
MatchesOscControlKey(definition.label, overlay.parameterKey);
|
|
||||||
});
|
|
||||||
return definitionIt != stateIt->parameterDefinitions.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t RuntimeLiveState::OverlayCount() const
|
|
||||||
{
|
|
||||||
return mOscOverlayStates.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeLiveState::ApplyOscUpdates(const std::vector<RuntimeLiveOscUpdate>& updates)
|
|
||||||
{
|
|
||||||
const auto now = std::chrono::steady_clock::now();
|
|
||||||
for (const RuntimeLiveOscUpdate& update : updates)
|
|
||||||
{
|
|
||||||
auto overlayIt = mOscOverlayStates.find(update.routeKey);
|
|
||||||
if (overlayIt == mOscOverlayStates.end())
|
|
||||||
{
|
|
||||||
OscOverlayState overlay;
|
|
||||||
overlay.layerKey = update.layerKey;
|
|
||||||
overlay.parameterKey = update.parameterKey;
|
|
||||||
overlay.targetValue = update.targetValue;
|
|
||||||
overlay.lastUpdatedTime = now;
|
|
||||||
overlay.lastAppliedTime = now;
|
|
||||||
overlay.generation = 1;
|
|
||||||
mOscOverlayStates[update.routeKey] = std::move(overlay);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
overlayIt->second.targetValue = update.targetValue;
|
|
||||||
overlayIt->second.lastUpdatedTime = now;
|
|
||||||
overlayIt->second.generation += 1;
|
|
||||||
overlayIt->second.commitQueued = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeLiveState::ApplyOscCommitCompletions(const std::vector<RuntimeLiveOscCommitCompletion>& completedCommits)
|
|
||||||
{
|
|
||||||
for (const RuntimeLiveOscCommitCompletion& completedCommit : completedCommits)
|
|
||||||
{
|
|
||||||
auto overlayIt = mOscOverlayStates.find(completedCommit.routeKey);
|
|
||||||
if (overlayIt == mOscOverlayStates.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
OscOverlayState& overlay = overlayIt->second;
|
|
||||||
if (overlay.commitQueued &&
|
|
||||||
overlay.pendingCommitGeneration == completedCommit.generation &&
|
|
||||||
overlay.generation == completedCommit.generation)
|
|
||||||
{
|
|
||||||
mOscOverlayStates.erase(overlayIt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeLiveState::PruneIncompatibleOverlays(const std::vector<RuntimeRenderState>& states)
|
|
||||||
{
|
|
||||||
for (auto it = mOscOverlayStates.begin(); it != mOscOverlayStates.end();)
|
|
||||||
{
|
|
||||||
std::vector<RuntimeRenderState>::const_iterator stateIt;
|
|
||||||
std::vector<ShaderParameterDefinition>::const_iterator definitionIt;
|
|
||||||
if (TryResolveOverlayTarget(it->second, states, stateIt, definitionIt))
|
|
||||||
{
|
|
||||||
ShaderParameterValue targetValue;
|
|
||||||
std::string normalizeError;
|
|
||||||
if (NormalizeAndValidateParameterValue(*definitionIt, it->second.targetValue, targetValue, normalizeError))
|
|
||||||
{
|
|
||||||
++it;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it = mOscOverlayStates.erase(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeLiveState::ApplyToLayerStates(
|
|
||||||
std::vector<RuntimeRenderState>& states,
|
|
||||||
const RuntimeLiveStateApplyOptions& options,
|
|
||||||
std::vector<RuntimeLiveOscCommitRequest>* commitRequests)
|
|
||||||
{
|
|
||||||
if (states.empty() || mOscOverlayStates.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
PruneIncompatibleOverlays(states);
|
|
||||||
if (mOscOverlayStates.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto now = options.now;
|
|
||||||
const double clampedSmoothing = ClampOscAlpha(options.smoothing);
|
|
||||||
std::vector<std::string> overlayKeysToRemove;
|
|
||||||
|
|
||||||
for (auto& item : mOscOverlayStates)
|
|
||||||
{
|
|
||||||
const std::string& routeKey = item.first;
|
|
||||||
OscOverlayState& overlay = item.second;
|
|
||||||
auto stateIt = std::find_if(states.begin(), states.end(),
|
|
||||||
[&overlay](const RuntimeRenderState& state)
|
|
||||||
{
|
|
||||||
return MatchesOscControlKey(state.layerId, overlay.layerKey) ||
|
|
||||||
MatchesOscControlKey(state.shaderId, overlay.layerKey) ||
|
|
||||||
MatchesOscControlKey(state.shaderName, overlay.layerKey);
|
|
||||||
});
|
|
||||||
if (stateIt == states.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(),
|
|
||||||
[&overlay](const ShaderParameterDefinition& definition)
|
|
||||||
{
|
|
||||||
return MatchesOscControlKey(definition.id, overlay.parameterKey) ||
|
|
||||||
MatchesOscControlKey(definition.label, overlay.parameterKey);
|
|
||||||
});
|
|
||||||
if (definitionIt == stateIt->parameterDefinitions.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ShaderParameterValue targetValue;
|
|
||||||
std::string normalizeError;
|
|
||||||
if (!NormalizeAndValidateParameterValue(*definitionIt, overlay.targetValue, targetValue, normalizeError))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (definitionIt->type == ShaderParameterType::Trigger)
|
|
||||||
{
|
|
||||||
ShaderParameterValue& value = stateIt->parameterValues[definitionIt->id];
|
|
||||||
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
|
|
||||||
const double triggerTime = stateIt->timeSeconds;
|
|
||||||
value.numberValues = { previousCount + 1.0, triggerTime };
|
|
||||||
overlayKeysToRemove.push_back(routeKey);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool smoothable =
|
|
||||||
clampedSmoothing > 0.0 &&
|
|
||||||
(definitionIt->type == ShaderParameterType::Float ||
|
|
||||||
definitionIt->type == ShaderParameterType::Vec2 ||
|
|
||||||
definitionIt->type == ShaderParameterType::Color);
|
|
||||||
if (!smoothable)
|
|
||||||
{
|
|
||||||
overlay.currentValue = targetValue;
|
|
||||||
overlay.hasCurrentValue = true;
|
|
||||||
stateIt->parameterValues[definitionIt->id] = overlay.currentValue;
|
|
||||||
if (options.allowCommit &&
|
|
||||||
!overlay.commitQueued &&
|
|
||||||
now - overlay.lastUpdatedTime >= options.commitDelay &&
|
|
||||||
commitRequests)
|
|
||||||
{
|
|
||||||
commitRequests->push_back({ routeKey, overlay.layerKey, overlay.parameterKey, overlay.targetValue, overlay.generation });
|
|
||||||
overlay.pendingCommitGeneration = overlay.generation;
|
|
||||||
overlay.commitQueued = true;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!overlay.hasCurrentValue)
|
|
||||||
{
|
|
||||||
overlay.currentValue = DefaultValueForDefinition(*definitionIt);
|
|
||||||
auto currentIt = stateIt->parameterValues.find(definitionIt->id);
|
|
||||||
if (currentIt != stateIt->parameterValues.end())
|
|
||||||
overlay.currentValue = currentIt->second;
|
|
||||||
overlay.hasCurrentValue = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overlay.currentValue.numberValues.size() != targetValue.numberValues.size())
|
|
||||||
overlay.currentValue.numberValues = targetValue.numberValues;
|
|
||||||
|
|
||||||
double smoothingAlpha = clampedSmoothing;
|
|
||||||
if (overlay.lastAppliedTime != std::chrono::steady_clock::time_point())
|
|
||||||
{
|
|
||||||
const double deltaSeconds =
|
|
||||||
std::chrono::duration_cast<std::chrono::duration<double>>(now - overlay.lastAppliedTime).count();
|
|
||||||
smoothingAlpha = ComputeTimeBasedOscAlpha(clampedSmoothing, deltaSeconds);
|
|
||||||
}
|
|
||||||
overlay.lastAppliedTime = now;
|
|
||||||
|
|
||||||
ShaderParameterValue nextValue = targetValue;
|
|
||||||
bool converged = true;
|
|
||||||
for (std::size_t index = 0; index < targetValue.numberValues.size(); ++index)
|
|
||||||
{
|
|
||||||
const double currentNumber = overlay.currentValue.numberValues[index];
|
|
||||||
const double targetNumber = targetValue.numberValues[index];
|
|
||||||
const double delta = targetNumber - currentNumber;
|
|
||||||
double nextNumber = currentNumber + delta * smoothingAlpha;
|
|
||||||
if (std::fabs(delta) <= 0.0005)
|
|
||||||
nextNumber = targetNumber;
|
|
||||||
else
|
|
||||||
converged = false;
|
|
||||||
nextValue.numberValues[index] = nextNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (converged)
|
|
||||||
nextValue.numberValues = targetValue.numberValues;
|
|
||||||
|
|
||||||
overlay.currentValue = nextValue;
|
|
||||||
overlay.hasCurrentValue = true;
|
|
||||||
stateIt->parameterValues[definitionIt->id] = overlay.currentValue;
|
|
||||||
if (options.allowCommit &&
|
|
||||||
converged &&
|
|
||||||
!overlay.commitQueued &&
|
|
||||||
now - overlay.lastUpdatedTime >= options.commitDelay &&
|
|
||||||
commitRequests)
|
|
||||||
{
|
|
||||||
commitRequests->push_back({ routeKey, overlay.layerKey, overlay.parameterKey, BuildOscCommitValue(*definitionIt, overlay.currentValue), overlay.generation });
|
|
||||||
overlay.pendingCommitGeneration = overlay.generation;
|
|
||||||
overlay.commitQueued = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const std::string& overlayKey : overlayKeysToRemove)
|
|
||||||
mOscOverlayStates.erase(overlayKey);
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeJson.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct RuntimeLiveOscUpdate
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
std::string layerKey;
|
|
||||||
std::string parameterKey;
|
|
||||||
JsonValue targetValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeLiveOscCommitCompletion
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeLiveOscCommitRequest
|
|
||||||
{
|
|
||||||
std::string routeKey;
|
|
||||||
std::string layerKey;
|
|
||||||
std::string parameterKey;
|
|
||||||
JsonValue value;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeLiveStateApplyOptions
|
|
||||||
{
|
|
||||||
bool allowCommit = false;
|
|
||||||
double smoothing = 0.0;
|
|
||||||
std::chrono::milliseconds commitDelay = std::chrono::milliseconds(150);
|
|
||||||
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
|
||||||
};
|
|
||||||
|
|
||||||
class RuntimeLiveState
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void Clear();
|
|
||||||
void ClearForLayerKey(const std::string& layerKey);
|
|
||||||
std::size_t OverlayCount() const;
|
|
||||||
void ApplyOscUpdates(const std::vector<RuntimeLiveOscUpdate>& updates);
|
|
||||||
void ApplyOscCommitCompletions(const std::vector<RuntimeLiveOscCommitCompletion>& completedCommits);
|
|
||||||
void PruneIncompatibleOverlays(const std::vector<RuntimeRenderState>& states);
|
|
||||||
void ApplyToLayerStates(
|
|
||||||
std::vector<RuntimeRenderState>& states,
|
|
||||||
const RuntimeLiveStateApplyOptions& options,
|
|
||||||
std::vector<RuntimeLiveOscCommitRequest>* commitRequests);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct OscOverlayState
|
|
||||||
{
|
|
||||||
std::string layerKey;
|
|
||||||
std::string parameterKey;
|
|
||||||
JsonValue targetValue;
|
|
||||||
ShaderParameterValue currentValue;
|
|
||||||
bool hasCurrentValue = false;
|
|
||||||
std::chrono::steady_clock::time_point lastUpdatedTime;
|
|
||||||
std::chrono::steady_clock::time_point lastAppliedTime;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
uint64_t pendingCommitGeneration = 0;
|
|
||||||
bool commitQueued = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool OverlayMatchesLayerKey(const OscOverlayState& overlay, const std::string& layerKey);
|
|
||||||
static bool TryResolveOverlayTarget(
|
|
||||||
const OscOverlayState& overlay,
|
|
||||||
const std::vector<RuntimeRenderState>& states,
|
|
||||||
std::vector<RuntimeRenderState>::const_iterator& stateIt,
|
|
||||||
std::vector<ShaderParameterDefinition>::const_iterator& definitionIt);
|
|
||||||
|
|
||||||
std::map<std::string, OscOverlayState> mOscOverlayStates;
|
|
||||||
};
|
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
#include "RuntimeStateLayerModel.h"
|
|
||||||
|
|
||||||
const char* RuntimeStateLayerKindName(RuntimeStateLayerKind kind)
|
|
||||||
{
|
|
||||||
switch (kind)
|
|
||||||
{
|
|
||||||
case RuntimeStateLayerKind::BasePersisted:
|
|
||||||
return "base persisted";
|
|
||||||
case RuntimeStateLayerKind::CommittedLive:
|
|
||||||
return "committed live";
|
|
||||||
case RuntimeStateLayerKind::TransientAutomation:
|
|
||||||
return "transient automation";
|
|
||||||
case RuntimeStateLayerKind::RenderLocal:
|
|
||||||
return "render local";
|
|
||||||
case RuntimeStateLayerKind::HealthConfig:
|
|
||||||
return "health/config";
|
|
||||||
default:
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int RuntimeStateLayerCompositionPrecedence(RuntimeStateLayerKind kind)
|
|
||||||
{
|
|
||||||
switch (kind)
|
|
||||||
{
|
|
||||||
case RuntimeStateLayerKind::BasePersisted:
|
|
||||||
return 0;
|
|
||||||
case RuntimeStateLayerKind::CommittedLive:
|
|
||||||
return 1;
|
|
||||||
case RuntimeStateLayerKind::TransientAutomation:
|
|
||||||
return 2;
|
|
||||||
case RuntimeStateLayerKind::RenderLocal:
|
|
||||||
case RuntimeStateLayerKind::HealthConfig:
|
|
||||||
default:
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeStateLayerIsDurable(RuntimeStateLayerKind kind)
|
|
||||||
{
|
|
||||||
return kind == RuntimeStateLayerKind::BasePersisted;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeStateLayerParticipatesInParameterComposition(RuntimeStateLayerKind kind)
|
|
||||||
{
|
|
||||||
return RuntimeStateLayerCompositionPrecedence(kind) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeStateLayerIsRenderLocal(RuntimeStateLayerKind kind)
|
|
||||||
{
|
|
||||||
return kind == RuntimeStateLayerKind::RenderLocal;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeStateLayerKind ClassifyRuntimeStateField(RuntimeStateField field)
|
|
||||||
{
|
|
||||||
switch (field)
|
|
||||||
{
|
|
||||||
case RuntimeStateField::PersistedLayerStack:
|
|
||||||
case RuntimeStateField::PersistedParameterValues:
|
|
||||||
case RuntimeStateField::StackPresets:
|
|
||||||
return RuntimeStateLayerKind::BasePersisted;
|
|
||||||
case RuntimeStateField::CommittedSessionParameterValues:
|
|
||||||
case RuntimeStateField::CommittedLayerBypass:
|
|
||||||
case RuntimeStateField::RuntimeCompileReloadFlags:
|
|
||||||
return RuntimeStateLayerKind::CommittedLive;
|
|
||||||
case RuntimeStateField::TransientOscOverlay:
|
|
||||||
case RuntimeStateField::TransientAutomationCommitState:
|
|
||||||
return RuntimeStateLayerKind::TransientAutomation;
|
|
||||||
case RuntimeStateField::RenderLocalTemporalHistory:
|
|
||||||
case RuntimeStateField::RenderLocalFeedbackState:
|
|
||||||
case RuntimeStateField::RenderLocalInputFrames:
|
|
||||||
case RuntimeStateField::RenderLocalOutputFrames:
|
|
||||||
return RuntimeStateLayerKind::RenderLocal;
|
|
||||||
case RuntimeStateField::RuntimeConfiguration:
|
|
||||||
case RuntimeStateField::HealthTelemetry:
|
|
||||||
default:
|
|
||||||
return RuntimeStateLayerKind::HealthConfig;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<RuntimeStateLayerDescriptor> GetRuntimeStateLayerInventory()
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
{
|
|
||||||
RuntimeStateLayerKind::BasePersisted,
|
|
||||||
"Base persisted state",
|
|
||||||
"RuntimeStore / LayerStackStore",
|
|
||||||
"Survives restart",
|
|
||||||
"Written to disk",
|
|
||||||
"Default layer stack, shader selections, saved parameter values"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateLayerKind::CommittedLive,
|
|
||||||
"Committed live state",
|
|
||||||
"RuntimeCoordinator / CommittedLiveState",
|
|
||||||
"Current running session",
|
|
||||||
"May request persistence depending on mutation policy",
|
|
||||||
"Operator/session truth until changed again"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateLayerKind::TransientAutomation,
|
|
||||||
"Transient automation overlay",
|
|
||||||
"RuntimeLiveState / RuntimeServiceLiveBridge",
|
|
||||||
"High-rate and short-lived",
|
|
||||||
"Not persisted directly",
|
|
||||||
"Temporary OSC/automation target applied over committed truth"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateLayerKind::RenderLocal,
|
|
||||||
"Render-local state",
|
|
||||||
"RenderEngine",
|
|
||||||
"Render-thread/resource lifetime",
|
|
||||||
"Not persisted",
|
|
||||||
"Temporal history, feedback, input/output queues, and GL-local caches"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateLayerKind::HealthConfig,
|
|
||||||
"Health/config state",
|
|
||||||
"RuntimeConfigStore / HealthTelemetry",
|
|
||||||
"Config survives restart; health is observational",
|
|
||||||
"Config is file-backed; health is reported, not composed",
|
|
||||||
"Does not participate in parameter composition"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<RuntimeStateFieldDescriptor> GetRuntimeStateFieldInventory()
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
{
|
|
||||||
RuntimeStateField::PersistedLayerStack,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::PersistedLayerStack),
|
|
||||||
"persisted layer stack",
|
|
||||||
"LayerStackStore",
|
|
||||||
"Durable layer order, ids, shader selections, and bypass flags"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::PersistedParameterValues,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::PersistedParameterValues),
|
|
||||||
"persisted parameter values",
|
|
||||||
"LayerStackStore",
|
|
||||||
"Saved parameter values used as the baseline for snapshots and presets"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::StackPresets,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::StackPresets),
|
|
||||||
"stack presets",
|
|
||||||
"RuntimeStore / LayerStackStore",
|
|
||||||
"Durable preset files and preset serialization shape"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::CommittedSessionParameterValues,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::CommittedSessionParameterValues),
|
|
||||||
"committed session parameter values",
|
|
||||||
"RuntimeCoordinator policy, CommittedLiveState backing",
|
|
||||||
"Operator/API truth after accepted mutations"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::CommittedLayerBypass,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::CommittedLayerBypass),
|
|
||||||
"committed layer bypass",
|
|
||||||
"RuntimeCoordinator policy, CommittedLiveState backing",
|
|
||||||
"Current operator/API bypass state"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::RuntimeCompileReloadFlags,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::RuntimeCompileReloadFlags),
|
|
||||||
"runtime compile/reload flags",
|
|
||||||
"RuntimeCoordinator / RuntimeUpdateController",
|
|
||||||
"Session coordination state used to request snapshot or render rebuild work"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::TransientOscOverlay,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::TransientOscOverlay),
|
|
||||||
"transient OSC overlays",
|
|
||||||
"RuntimeLiveState",
|
|
||||||
"High-rate automation values applied above committed state"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::TransientAutomationCommitState,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::TransientAutomationCommitState),
|
|
||||||
"transient automation commit state",
|
|
||||||
"RuntimeLiveState / RuntimeServiceLiveBridge",
|
|
||||||
"Generation and completion bookkeeping for settled overlay commits"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::RenderLocalTemporalHistory,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::RenderLocalTemporalHistory),
|
|
||||||
"render-local temporal history",
|
|
||||||
"RenderEngine",
|
|
||||||
"GL/resource history that must stay out of parameter layering"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::RenderLocalFeedbackState,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::RenderLocalFeedbackState),
|
|
||||||
"render-local feedback state",
|
|
||||||
"RenderEngine",
|
|
||||||
"Feedback buffers and ping-pong resources"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::RenderLocalInputFrames,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::RenderLocalInputFrames),
|
|
||||||
"render-local input frames",
|
|
||||||
"RenderEngine",
|
|
||||||
"Latest accepted input frame payloads and upload staging"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::RenderLocalOutputFrames,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::RenderLocalOutputFrames),
|
|
||||||
"render-local output frames",
|
|
||||||
"RenderEngine",
|
|
||||||
"Readback, packed output, screenshot, and preview staging"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::RuntimeConfiguration,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::RuntimeConfiguration),
|
|
||||||
"runtime configuration",
|
|
||||||
"RuntimeConfigStore",
|
|
||||||
"File-backed config, not a live parameter layer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RuntimeStateField::HealthTelemetry,
|
|
||||||
ClassifyRuntimeStateField(RuntimeStateField::HealthTelemetry),
|
|
||||||
"health telemetry",
|
|
||||||
"HealthTelemetry",
|
|
||||||
"Operational observations, not source state for render values"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
enum class RuntimeStateLayerKind
|
|
||||||
{
|
|
||||||
BasePersisted,
|
|
||||||
CommittedLive,
|
|
||||||
TransientAutomation,
|
|
||||||
RenderLocal,
|
|
||||||
HealthConfig
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class RuntimeStateField
|
|
||||||
{
|
|
||||||
PersistedLayerStack,
|
|
||||||
PersistedParameterValues,
|
|
||||||
StackPresets,
|
|
||||||
CommittedSessionParameterValues,
|
|
||||||
CommittedLayerBypass,
|
|
||||||
RuntimeCompileReloadFlags,
|
|
||||||
TransientOscOverlay,
|
|
||||||
TransientAutomationCommitState,
|
|
||||||
RenderLocalTemporalHistory,
|
|
||||||
RenderLocalFeedbackState,
|
|
||||||
RenderLocalInputFrames,
|
|
||||||
RenderLocalOutputFrames,
|
|
||||||
RuntimeConfiguration,
|
|
||||||
HealthTelemetry
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeStateLayerDescriptor
|
|
||||||
{
|
|
||||||
RuntimeStateLayerKind kind = RuntimeStateLayerKind::BasePersisted;
|
|
||||||
const char* name = "";
|
|
||||||
const char* owner = "";
|
|
||||||
const char* lifetime = "";
|
|
||||||
const char* persistence = "";
|
|
||||||
const char* renderRole = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeStateFieldDescriptor
|
|
||||||
{
|
|
||||||
RuntimeStateField field = RuntimeStateField::PersistedLayerStack;
|
|
||||||
RuntimeStateLayerKind layerKind = RuntimeStateLayerKind::BasePersisted;
|
|
||||||
const char* name = "";
|
|
||||||
const char* currentOwner = "";
|
|
||||||
const char* notes = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const char* RuntimeStateLayerKindName(RuntimeStateLayerKind kind);
|
|
||||||
int RuntimeStateLayerCompositionPrecedence(RuntimeStateLayerKind kind);
|
|
||||||
bool RuntimeStateLayerIsDurable(RuntimeStateLayerKind kind);
|
|
||||||
bool RuntimeStateLayerParticipatesInParameterComposition(RuntimeStateLayerKind kind);
|
|
||||||
bool RuntimeStateLayerIsRenderLocal(RuntimeStateLayerKind kind);
|
|
||||||
|
|
||||||
RuntimeStateLayerKind ClassifyRuntimeStateField(RuntimeStateField field);
|
|
||||||
std::vector<RuntimeStateLayerDescriptor> GetRuntimeStateLayerInventory();
|
|
||||||
std::vector<RuntimeStateFieldDescriptor> GetRuntimeStateFieldInventory();
|
|
||||||
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
enum class PersistenceTargetKind
|
|
||||||
{
|
|
||||||
RuntimeState,
|
|
||||||
StackPreset,
|
|
||||||
RuntimeConfig
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PersistenceRequest
|
|
||||||
{
|
|
||||||
PersistenceTargetKind targetKind = PersistenceTargetKind::RuntimeState;
|
|
||||||
std::string reason;
|
|
||||||
std::string debounceKey = "runtime-state";
|
|
||||||
bool debounceAllowed = true;
|
|
||||||
bool flushRequested = false;
|
|
||||||
uint64_t sequence = 0;
|
|
||||||
|
|
||||||
static PersistenceRequest RuntimeStateRequest(const std::string& reason)
|
|
||||||
{
|
|
||||||
PersistenceRequest request;
|
|
||||||
request.targetKind = PersistenceTargetKind::RuntimeState;
|
|
||||||
request.reason = reason;
|
|
||||||
request.debounceKey = "runtime-state";
|
|
||||||
request.debounceAllowed = true;
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PersistenceSnapshot
|
|
||||||
{
|
|
||||||
PersistenceTargetKind targetKind = PersistenceTargetKind::RuntimeState;
|
|
||||||
std::filesystem::path targetPath;
|
|
||||||
std::string contents;
|
|
||||||
std::string reason;
|
|
||||||
std::string debounceKey;
|
|
||||||
bool debounceAllowed = false;
|
|
||||||
bool flushRequested = false;
|
|
||||||
uint64_t generation = 0;
|
|
||||||
};
|
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
#include "PersistenceWriter.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
PersistenceWriter::PersistenceWriter(std::chrono::milliseconds debounceDelay, SnapshotSink sink) :
|
|
||||||
mDebounceDelay(debounceDelay),
|
|
||||||
mSink(std::move(sink))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
PersistenceWriter::~PersistenceWriter()
|
|
||||||
{
|
|
||||||
std::string error;
|
|
||||||
StopAndFlush((std::chrono::milliseconds::max)(), error);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PersistenceWriter::SetResultCallback(ResultCallback callback)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
mResultCallback = std::move(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PersistenceWriter::WriteSnapshot(const PersistenceSnapshot& snapshot, std::string& error)
|
|
||||||
{
|
|
||||||
if (!ValidateSnapshot(snapshot, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const bool succeeded = WriteSnapshotThroughSink(snapshot, error);
|
|
||||||
PublishWriteResult(snapshot, succeeded, error, false);
|
|
||||||
return succeeded;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PersistenceWriter::EnqueueSnapshot(const PersistenceSnapshot& snapshot, std::string& error)
|
|
||||||
{
|
|
||||||
if (!ValidateSnapshot(snapshot, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (!mAcceptingRequests)
|
|
||||||
{
|
|
||||||
error = "Persistence writer is stopping.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
StartWorkerLocked();
|
|
||||||
|
|
||||||
const auto now = std::chrono::steady_clock::now();
|
|
||||||
if (snapshot.debounceAllowed)
|
|
||||||
{
|
|
||||||
const std::string debounceKey = snapshot.debounceKey.empty() ? snapshot.targetPath.string() : snapshot.debounceKey;
|
|
||||||
PendingSnapshot& pending = mDebouncedSnapshots[debounceKey];
|
|
||||||
if (!pending.snapshot.targetPath.empty())
|
|
||||||
++mCoalescedCount;
|
|
||||||
else
|
|
||||||
++mEnqueuedCount;
|
|
||||||
|
|
||||||
pending.snapshot = snapshot;
|
|
||||||
pending.readyAt = snapshot.flushRequested ? now : now + mDebounceDelay;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mImmediateSnapshots.push_back(snapshot);
|
|
||||||
++mEnqueuedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
mCondition.notify_one();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PersistenceWriter::StopAndFlush(std::chrono::milliseconds timeout, std::string& error)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
mAcceptingRequests = false;
|
|
||||||
mStopping = true;
|
|
||||||
const auto now = std::chrono::steady_clock::now();
|
|
||||||
for (auto& entry : mDebouncedSnapshots)
|
|
||||||
entry.second.readyAt = now;
|
|
||||||
}
|
|
||||||
mCondition.notify_all();
|
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
|
||||||
if (mWorkerRunning)
|
|
||||||
{
|
|
||||||
if (timeout == (std::chrono::milliseconds::max)())
|
|
||||||
{
|
|
||||||
mCondition.wait(lock, [this]() { return !mWorkerRunning; });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto deadline = std::chrono::steady_clock::now() + timeout;
|
|
||||||
if (!mCondition.wait_until(lock, deadline, [this]() { return !mWorkerRunning; }))
|
|
||||||
{
|
|
||||||
error = "Timed out while flushing persistence writer.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
if (mWorker.joinable())
|
|
||||||
mWorker.join();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
PersistenceWriterMetrics PersistenceWriter::GetMetrics() const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
PersistenceWriterMetrics metrics;
|
|
||||||
metrics.pendingCount = PendingCountLocked();
|
|
||||||
metrics.enqueuedCount = mEnqueuedCount;
|
|
||||||
metrics.coalescedCount = mCoalescedCount;
|
|
||||||
metrics.writtenCount = mWrittenCount;
|
|
||||||
metrics.failedCount = mFailedCount;
|
|
||||||
return metrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PersistenceWriter::ValidateSnapshot(const PersistenceSnapshot& snapshot, std::string& error) const
|
|
||||||
{
|
|
||||||
if (snapshot.targetPath.empty())
|
|
||||||
{
|
|
||||||
error = "Persistence snapshot target path is empty.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PersistenceWriter::WriteSnapshotThroughSink(const PersistenceSnapshot& snapshot, std::string& error) const
|
|
||||||
{
|
|
||||||
if (mSink)
|
|
||||||
return mSink(snapshot, error);
|
|
||||||
|
|
||||||
std::error_code fsError;
|
|
||||||
std::filesystem::create_directories(snapshot.targetPath.parent_path(), fsError);
|
|
||||||
|
|
||||||
const std::filesystem::path temporaryPath = snapshot.targetPath.string() + ".tmp";
|
|
||||||
std::ofstream output(temporaryPath, std::ios::binary | std::ios::trunc);
|
|
||||||
if (!output)
|
|
||||||
{
|
|
||||||
error = "Could not write file: " + temporaryPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
output << snapshot.contents;
|
|
||||||
output.close();
|
|
||||||
if (!output.good())
|
|
||||||
{
|
|
||||||
error = "Could not finish writing file: " + temporaryPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!MoveFileExA(temporaryPath.string().c_str(), snapshot.targetPath.string().c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
|
|
||||||
{
|
|
||||||
const DWORD lastError = GetLastError();
|
|
||||||
std::filesystem::remove(temporaryPath, fsError);
|
|
||||||
error = "Could not replace file: " + snapshot.targetPath.string() + " (Win32 error " + std::to_string(lastError) + ")";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PersistenceWriter::PublishWriteResult(const PersistenceSnapshot& snapshot, bool succeeded, const std::string& errorMessage, bool newerRequestPending)
|
|
||||||
{
|
|
||||||
ResultCallback callback;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
callback = mResultCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!callback)
|
|
||||||
return;
|
|
||||||
|
|
||||||
PersistenceWriteResult result;
|
|
||||||
result.targetKind = snapshot.targetKind;
|
|
||||||
result.targetPath = snapshot.targetPath.string();
|
|
||||||
result.reason = snapshot.reason;
|
|
||||||
result.succeeded = succeeded;
|
|
||||||
result.errorMessage = errorMessage;
|
|
||||||
result.newerRequestPending = newerRequestPending;
|
|
||||||
callback(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PersistenceWriter::StartWorkerLocked()
|
|
||||||
{
|
|
||||||
if (mWorkerRunning)
|
|
||||||
return;
|
|
||||||
|
|
||||||
mWorkerRunning = true;
|
|
||||||
mWorker = std::thread([this]() { WorkerMain(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void PersistenceWriter::WorkerMain()
|
|
||||||
{
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
PersistenceSnapshot snapshot;
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (!mImmediateSnapshots.empty())
|
|
||||||
{
|
|
||||||
snapshot = std::move(mImmediateSnapshots.front());
|
|
||||||
mImmediateSnapshots.pop_front();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mDebouncedSnapshots.empty())
|
|
||||||
{
|
|
||||||
const auto now = std::chrono::steady_clock::now();
|
|
||||||
auto readyIt = mDebouncedSnapshots.end();
|
|
||||||
auto nextReadyAt = (std::chrono::steady_clock::time_point::max)();
|
|
||||||
for (auto it = mDebouncedSnapshots.begin(); it != mDebouncedSnapshots.end(); ++it)
|
|
||||||
{
|
|
||||||
if (it->second.readyAt <= now)
|
|
||||||
{
|
|
||||||
readyIt = it;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (it->second.readyAt < nextReadyAt)
|
|
||||||
nextReadyAt = it->second.readyAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readyIt != mDebouncedSnapshots.end())
|
|
||||||
{
|
|
||||||
snapshot = std::move(readyIt->second.snapshot);
|
|
||||||
mDebouncedSnapshots.erase(readyIt);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
mCondition.wait_until(lock, nextReadyAt);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mStopping)
|
|
||||||
{
|
|
||||||
mWorkerRunning = false;
|
|
||||||
mCondition.notify_all();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mCondition.wait(lock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string error;
|
|
||||||
const bool succeeded = WriteSnapshotThroughSink(snapshot, error);
|
|
||||||
bool newerRequestPending = false;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
if (succeeded)
|
|
||||||
++mWrittenCount;
|
|
||||||
else
|
|
||||||
++mFailedCount;
|
|
||||||
newerRequestPending = PendingCountLocked() > 0;
|
|
||||||
}
|
|
||||||
PublishWriteResult(snapshot, succeeded, error, newerRequestPending);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t PersistenceWriter::PendingCountLocked() const
|
|
||||||
{
|
|
||||||
return mImmediateSnapshots.size() + mDebouncedSnapshots.size();
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "PersistenceRequest.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <deque>
|
|
||||||
#include <functional>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
struct PersistenceWriterMetrics
|
|
||||||
{
|
|
||||||
std::size_t pendingCount = 0;
|
|
||||||
uint64_t enqueuedCount = 0;
|
|
||||||
uint64_t coalescedCount = 0;
|
|
||||||
uint64_t writtenCount = 0;
|
|
||||||
uint64_t failedCount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PersistenceWriteResult
|
|
||||||
{
|
|
||||||
PersistenceTargetKind targetKind = PersistenceTargetKind::RuntimeState;
|
|
||||||
std::string targetPath;
|
|
||||||
std::string reason;
|
|
||||||
bool succeeded = false;
|
|
||||||
std::string errorMessage;
|
|
||||||
bool newerRequestPending = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PersistenceWriter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using SnapshotSink = std::function<bool(const PersistenceSnapshot&, std::string&)>;
|
|
||||||
using ResultCallback = std::function<void(const PersistenceWriteResult&)>;
|
|
||||||
|
|
||||||
explicit PersistenceWriter(
|
|
||||||
std::chrono::milliseconds debounceDelay = std::chrono::milliseconds(50),
|
|
||||||
SnapshotSink sink = SnapshotSink());
|
|
||||||
~PersistenceWriter();
|
|
||||||
|
|
||||||
void SetResultCallback(ResultCallback callback);
|
|
||||||
bool WriteSnapshot(const PersistenceSnapshot& snapshot, std::string& error);
|
|
||||||
bool EnqueueSnapshot(const PersistenceSnapshot& snapshot, std::string& error);
|
|
||||||
bool StopAndFlush(std::chrono::milliseconds timeout, std::string& error);
|
|
||||||
PersistenceWriterMetrics GetMetrics() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct PendingSnapshot
|
|
||||||
{
|
|
||||||
PersistenceSnapshot snapshot;
|
|
||||||
std::chrono::steady_clock::time_point readyAt;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool ValidateSnapshot(const PersistenceSnapshot& snapshot, std::string& error) const;
|
|
||||||
bool WriteSnapshotThroughSink(const PersistenceSnapshot& snapshot, std::string& error) const;
|
|
||||||
void PublishWriteResult(const PersistenceSnapshot& snapshot, bool succeeded, const std::string& errorMessage, bool newerRequestPending);
|
|
||||||
void StartWorkerLocked();
|
|
||||||
void WorkerMain();
|
|
||||||
std::size_t PendingCountLocked() const;
|
|
||||||
|
|
||||||
std::chrono::milliseconds mDebounceDelay;
|
|
||||||
SnapshotSink mSink;
|
|
||||||
ResultCallback mResultCallback;
|
|
||||||
mutable std::mutex mMutex;
|
|
||||||
std::condition_variable mCondition;
|
|
||||||
std::thread mWorker;
|
|
||||||
bool mWorkerRunning = false;
|
|
||||||
bool mStopping = false;
|
|
||||||
bool mAcceptingRequests = true;
|
|
||||||
std::unordered_map<std::string, PendingSnapshot> mDebouncedSnapshots;
|
|
||||||
std::deque<PersistenceSnapshot> mImmediateSnapshots;
|
|
||||||
uint64_t mEnqueuedCount = 0;
|
|
||||||
uint64_t mCoalescedCount = 0;
|
|
||||||
uint64_t mWrittenCount = 0;
|
|
||||||
uint64_t mFailedCount = 0;
|
|
||||||
};
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
#include "RuntimeStateJson.h"
|
|
||||||
|
|
||||||
#include "RuntimeParameterUtils.h"
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
std::string ShaderParameterTypeToString(ShaderParameterType type)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case ShaderParameterType::Float: return "float";
|
|
||||||
case ShaderParameterType::Vec2: return "vec2";
|
|
||||||
case ShaderParameterType::Color: return "color";
|
|
||||||
case ShaderParameterType::Boolean: return "bool";
|
|
||||||
case ShaderParameterType::Enum: return "enum";
|
|
||||||
case ShaderParameterType::Text: return "text";
|
|
||||||
case ShaderParameterType::Trigger: return "trigger";
|
|
||||||
}
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValue RuntimeStateJson::SerializeLayerStack(const LayerStackStore& layerStack, const ShaderPackageCatalog& shaderCatalog)
|
|
||||||
{
|
|
||||||
std::map<std::string, ShaderPackage> packagesById;
|
|
||||||
for (const std::string& packageId : shaderCatalog.PackageOrder())
|
|
||||||
{
|
|
||||||
ShaderPackage shaderPackage;
|
|
||||||
if (shaderCatalog.CopyPackage(packageId, shaderPackage))
|
|
||||||
packagesById[packageId] = shaderPackage;
|
|
||||||
}
|
|
||||||
return SerializeLayerStack(layerStack.Layers(), packagesById);
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValue RuntimeStateJson::SerializeLayerStack(const std::vector<LayerStackStore::LayerPersistentState>& layerStates, const std::map<std::string, ShaderPackage>& packagesById)
|
|
||||||
{
|
|
||||||
JsonValue layersValue = JsonValue::MakeArray();
|
|
||||||
for (const LayerStackStore::LayerPersistentState& layer : layerStates)
|
|
||||||
{
|
|
||||||
auto shaderIt = packagesById.find(layer.shaderId);
|
|
||||||
if (shaderIt == packagesById.end())
|
|
||||||
continue;
|
|
||||||
const ShaderPackage& shaderPackage = shaderIt->second;
|
|
||||||
|
|
||||||
JsonValue layerValue = JsonValue::MakeObject();
|
|
||||||
layerValue.set("id", JsonValue(layer.id));
|
|
||||||
layerValue.set("shaderId", JsonValue(layer.shaderId));
|
|
||||||
layerValue.set("shaderName", JsonValue(shaderPackage.displayName));
|
|
||||||
layerValue.set("bypass", JsonValue(layer.bypass));
|
|
||||||
if (shaderPackage.temporal.enabled)
|
|
||||||
{
|
|
||||||
JsonValue temporal = JsonValue::MakeObject();
|
|
||||||
temporal.set("enabled", JsonValue(true));
|
|
||||||
temporal.set("historySource", JsonValue(TemporalHistorySourceToString(shaderPackage.temporal.historySource)));
|
|
||||||
temporal.set("requestedHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.requestedHistoryLength)));
|
|
||||||
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.effectiveHistoryLength)));
|
|
||||||
layerValue.set("temporal", temporal);
|
|
||||||
}
|
|
||||||
if (shaderPackage.feedback.enabled)
|
|
||||||
{
|
|
||||||
JsonValue feedback = JsonValue::MakeObject();
|
|
||||||
feedback.set("enabled", JsonValue(true));
|
|
||||||
feedback.set("writePass", JsonValue(shaderPackage.feedback.writePassId));
|
|
||||||
layerValue.set("feedback", feedback);
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValue parameters = JsonValue::MakeArray();
|
|
||||||
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
|
|
||||||
{
|
|
||||||
JsonValue parameter = JsonValue::MakeObject();
|
|
||||||
parameter.set("id", JsonValue(definition.id));
|
|
||||||
parameter.set("label", JsonValue(definition.label));
|
|
||||||
if (!definition.description.empty())
|
|
||||||
parameter.set("description", JsonValue(definition.description));
|
|
||||||
parameter.set("type", JsonValue(ShaderParameterTypeToString(definition.type)));
|
|
||||||
parameter.set("defaultValue", SerializeParameterValue(definition, DefaultValueForDefinition(definition)));
|
|
||||||
|
|
||||||
if (!definition.minNumbers.empty())
|
|
||||||
{
|
|
||||||
JsonValue minValue = JsonValue::MakeArray();
|
|
||||||
for (double number : definition.minNumbers)
|
|
||||||
minValue.pushBack(JsonValue(number));
|
|
||||||
parameter.set("min", minValue);
|
|
||||||
}
|
|
||||||
if (!definition.maxNumbers.empty())
|
|
||||||
{
|
|
||||||
JsonValue maxValue = JsonValue::MakeArray();
|
|
||||||
for (double number : definition.maxNumbers)
|
|
||||||
maxValue.pushBack(JsonValue(number));
|
|
||||||
parameter.set("max", maxValue);
|
|
||||||
}
|
|
||||||
if (!definition.stepNumbers.empty())
|
|
||||||
{
|
|
||||||
JsonValue stepValue = JsonValue::MakeArray();
|
|
||||||
for (double number : definition.stepNumbers)
|
|
||||||
stepValue.pushBack(JsonValue(number));
|
|
||||||
parameter.set("step", stepValue);
|
|
||||||
}
|
|
||||||
if (definition.type == ShaderParameterType::Enum)
|
|
||||||
{
|
|
||||||
JsonValue options = JsonValue::MakeArray();
|
|
||||||
for (const ShaderParameterOption& option : definition.enumOptions)
|
|
||||||
{
|
|
||||||
JsonValue optionValue = JsonValue::MakeObject();
|
|
||||||
optionValue.set("value", JsonValue(option.value));
|
|
||||||
optionValue.set("label", JsonValue(option.label));
|
|
||||||
options.pushBack(optionValue);
|
|
||||||
}
|
|
||||||
parameter.set("options", options);
|
|
||||||
}
|
|
||||||
if (definition.type == ShaderParameterType::Text)
|
|
||||||
{
|
|
||||||
parameter.set("maxLength", JsonValue(static_cast<double>(definition.maxLength)));
|
|
||||||
if (!definition.fontId.empty())
|
|
||||||
parameter.set("font", JsonValue(definition.fontId));
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderParameterValue value = DefaultValueForDefinition(definition);
|
|
||||||
auto valueIt = layer.parameterValues.find(definition.id);
|
|
||||||
if (valueIt != layer.parameterValues.end())
|
|
||||||
value = valueIt->second;
|
|
||||||
parameter.set("value", SerializeParameterValue(definition, value));
|
|
||||||
parameters.pushBack(parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
layerValue.set("parameters", parameters);
|
|
||||||
layersValue.pushBack(layerValue);
|
|
||||||
}
|
|
||||||
return layersValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValue RuntimeStateJson::SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
|
|
||||||
{
|
|
||||||
switch (definition.type)
|
|
||||||
{
|
|
||||||
case ShaderParameterType::Boolean:
|
|
||||||
return JsonValue(value.booleanValue);
|
|
||||||
case ShaderParameterType::Enum:
|
|
||||||
return JsonValue(value.enumValue);
|
|
||||||
case ShaderParameterType::Text:
|
|
||||||
return JsonValue(value.textValue);
|
|
||||||
case ShaderParameterType::Trigger:
|
|
||||||
return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
|
|
||||||
case ShaderParameterType::Float:
|
|
||||||
return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
|
|
||||||
case ShaderParameterType::Vec2:
|
|
||||||
case ShaderParameterType::Color:
|
|
||||||
{
|
|
||||||
JsonValue array = JsonValue::MakeArray();
|
|
||||||
for (double number : value.numberValues)
|
|
||||||
array.pushBack(JsonValue(number));
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JsonValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string RuntimeStateJson::TemporalHistorySourceToString(TemporalHistorySource source)
|
|
||||||
{
|
|
||||||
switch (source)
|
|
||||||
{
|
|
||||||
case TemporalHistorySource::Source:
|
|
||||||
return "source";
|
|
||||||
case TemporalHistorySource::PreLayerInput:
|
|
||||||
return "preLayerInput";
|
|
||||||
case TemporalHistorySource::None:
|
|
||||||
default:
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "LayerStackStore.h"
|
|
||||||
#include "RuntimeJson.h"
|
|
||||||
#include "ShaderPackageCatalog.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class RuntimeStateJson
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static JsonValue SerializeLayerStack(const LayerStackStore& layerStack, const ShaderPackageCatalog& shaderCatalog);
|
|
||||||
static JsonValue SerializeLayerStack(const std::vector<LayerStackStore::LayerPersistentState>& layers, const std::map<std::string, ShaderPackage>& packagesById);
|
|
||||||
static JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value);
|
|
||||||
static std::string TemporalHistorySourceToString(TemporalHistorySource source);
|
|
||||||
};
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
#include "RuntimeStatePresenter.h"
|
|
||||||
|
|
||||||
#include "RuntimeStateJson.h"
|
|
||||||
#include "RuntimeStore.h"
|
|
||||||
|
|
||||||
std::string RuntimeStatePresenter::BuildRuntimeStateJson(const RuntimeStore& runtimeStore)
|
|
||||||
{
|
|
||||||
return SerializeJson(BuildRuntimeStateValue(runtimeStore), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValue RuntimeStatePresenter::BuildRuntimeStateValue(const RuntimeStore& runtimeStore)
|
|
||||||
{
|
|
||||||
const RuntimeStatePresentationReadModel model = runtimeStore.BuildRuntimeStatePresentationReadModel();
|
|
||||||
const HealthTelemetry::Snapshot& telemetrySnapshot = model.telemetry;
|
|
||||||
|
|
||||||
JsonValue root = JsonValue::MakeObject();
|
|
||||||
|
|
||||||
JsonValue app = JsonValue::MakeObject();
|
|
||||||
app.set("serverPort", JsonValue(static_cast<double>(model.serverPort)));
|
|
||||||
app.set("oscPort", JsonValue(static_cast<double>(model.config.oscPort)));
|
|
||||||
app.set("oscBindAddress", JsonValue(model.config.oscBindAddress));
|
|
||||||
app.set("oscSmoothing", JsonValue(model.config.oscSmoothing));
|
|
||||||
app.set("autoReload", JsonValue(model.autoReloadEnabled));
|
|
||||||
app.set("maxTemporalHistoryFrames", JsonValue(static_cast<double>(model.config.maxTemporalHistoryFrames)));
|
|
||||||
app.set("previewFps", JsonValue(static_cast<double>(model.config.previewFps)));
|
|
||||||
app.set("enableExternalKeying", JsonValue(model.config.enableExternalKeying));
|
|
||||||
app.set("inputVideoFormat", JsonValue(model.config.inputVideoFormat));
|
|
||||||
app.set("inputFrameRate", JsonValue(model.config.inputFrameRate));
|
|
||||||
app.set("outputVideoFormat", JsonValue(model.config.outputVideoFormat));
|
|
||||||
app.set("outputFrameRate", JsonValue(model.config.outputFrameRate));
|
|
||||||
root.set("app", app);
|
|
||||||
|
|
||||||
JsonValue runtime = JsonValue::MakeObject();
|
|
||||||
runtime.set("layerCount", JsonValue(static_cast<double>(model.layerStack.LayerCount())));
|
|
||||||
runtime.set("compileSucceeded", JsonValue(model.compileSucceeded));
|
|
||||||
runtime.set("compileMessage", JsonValue(model.compileMessage));
|
|
||||||
root.set("runtime", runtime);
|
|
||||||
|
|
||||||
JsonValue video = JsonValue::MakeObject();
|
|
||||||
video.set("hasSignal", JsonValue(telemetrySnapshot.signal.hasSignal));
|
|
||||||
video.set("width", JsonValue(static_cast<double>(telemetrySnapshot.signal.width)));
|
|
||||||
video.set("height", JsonValue(static_cast<double>(telemetrySnapshot.signal.height)));
|
|
||||||
video.set("modeName", JsonValue(telemetrySnapshot.signal.modeName));
|
|
||||||
root.set("video", video);
|
|
||||||
|
|
||||||
JsonValue deckLink = JsonValue::MakeObject();
|
|
||||||
deckLink.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName));
|
|
||||||
deckLink.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying));
|
|
||||||
deckLink.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying));
|
|
||||||
deckLink.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable));
|
|
||||||
deckLink.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested));
|
|
||||||
deckLink.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive));
|
|
||||||
deckLink.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage));
|
|
||||||
deckLink.set("actualBufferedFramesAvailable", JsonValue(telemetrySnapshot.backendPlayout.actualDeckLinkBufferedFramesAvailable));
|
|
||||||
deckLink.set("actualBufferedFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.actualDeckLinkBufferedFrames)));
|
|
||||||
deckLink.set("targetBufferedFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.targetDeckLinkBufferedFrames)));
|
|
||||||
deckLink.set("scheduleCallMs", JsonValue(telemetrySnapshot.backendPlayout.deckLinkScheduleCallMilliseconds));
|
|
||||||
deckLink.set("scheduleFailures", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.deckLinkScheduleFailureCount)));
|
|
||||||
root.set("decklink", deckLink);
|
|
||||||
|
|
||||||
JsonValue videoIO = JsonValue::MakeObject();
|
|
||||||
videoIO.set("backend", JsonValue(telemetrySnapshot.videoIO.backendName));
|
|
||||||
videoIO.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName));
|
|
||||||
videoIO.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying));
|
|
||||||
videoIO.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying));
|
|
||||||
videoIO.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable));
|
|
||||||
videoIO.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested));
|
|
||||||
videoIO.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive));
|
|
||||||
videoIO.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage));
|
|
||||||
root.set("videoIO", videoIO);
|
|
||||||
|
|
||||||
JsonValue performance = JsonValue::MakeObject();
|
|
||||||
performance.set("frameBudgetMs", JsonValue(telemetrySnapshot.performance.frameBudgetMilliseconds));
|
|
||||||
performance.set("renderMs", JsonValue(telemetrySnapshot.performance.renderMilliseconds));
|
|
||||||
performance.set("smoothedRenderMs", JsonValue(telemetrySnapshot.performance.smoothedRenderMilliseconds));
|
|
||||||
performance.set("budgetUsedPercent", JsonValue(
|
|
||||||
telemetrySnapshot.performance.frameBudgetMilliseconds > 0.0
|
|
||||||
? (telemetrySnapshot.performance.smoothedRenderMilliseconds / telemetrySnapshot.performance.frameBudgetMilliseconds) * 100.0
|
|
||||||
: 0.0));
|
|
||||||
performance.set("completionIntervalMs", JsonValue(telemetrySnapshot.performance.completionIntervalMilliseconds));
|
|
||||||
performance.set("smoothedCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.smoothedCompletionIntervalMilliseconds));
|
|
||||||
performance.set("maxCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.maxCompletionIntervalMilliseconds));
|
|
||||||
performance.set("lateFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.lateFrameCount)));
|
|
||||||
performance.set("droppedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.droppedFrameCount)));
|
|
||||||
performance.set("flushedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.flushedFrameCount)));
|
|
||||||
root.set("performance", performance);
|
|
||||||
|
|
||||||
JsonValue readyQueue = JsonValue::MakeObject();
|
|
||||||
readyQueue.set("depth", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueDepth)));
|
|
||||||
readyQueue.set("capacity", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueCapacity)));
|
|
||||||
readyQueue.set("minDepth", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.minReadyQueueDepth)));
|
|
||||||
readyQueue.set("maxDepth", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.maxReadyQueueDepth)));
|
|
||||||
readyQueue.set("zeroDepthCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueZeroDepthCount)));
|
|
||||||
readyQueue.set("pushedCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueuePushedCount)));
|
|
||||||
readyQueue.set("poppedCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueuePoppedCount)));
|
|
||||||
readyQueue.set("droppedCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueDroppedCount)));
|
|
||||||
readyQueue.set("underrunCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueUnderrunCount)));
|
|
||||||
|
|
||||||
JsonValue systemMemory = JsonValue::MakeObject();
|
|
||||||
systemMemory.set("freeFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFramePoolFree)));
|
|
||||||
systemMemory.set("readyFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFramePoolReady)));
|
|
||||||
systemMemory.set("scheduledFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFramePoolScheduled)));
|
|
||||||
systemMemory.set("underrunCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFrameUnderrunCount)));
|
|
||||||
systemMemory.set("repeatCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFrameRepeatCount)));
|
|
||||||
systemMemory.set("dropCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFrameDropCount)));
|
|
||||||
systemMemory.set("ageAtScheduleMs", JsonValue(telemetrySnapshot.backendPlayout.systemFrameAgeAtScheduleMilliseconds));
|
|
||||||
systemMemory.set("ageAtCompletionMs", JsonValue(telemetrySnapshot.backendPlayout.systemFrameAgeAtCompletionMilliseconds));
|
|
||||||
|
|
||||||
JsonValue outputRender = JsonValue::MakeObject();
|
|
||||||
outputRender.set("renderMs", JsonValue(telemetrySnapshot.backendPlayout.outputRenderMilliseconds));
|
|
||||||
outputRender.set("smoothedRenderMs", JsonValue(telemetrySnapshot.backendPlayout.smoothedOutputRenderMilliseconds));
|
|
||||||
outputRender.set("maxRenderMs", JsonValue(telemetrySnapshot.backendPlayout.maxOutputRenderMilliseconds));
|
|
||||||
outputRender.set("acquireFrameMs", JsonValue(telemetrySnapshot.backendPlayout.outputFrameAcquireMilliseconds));
|
|
||||||
outputRender.set("renderRequestMs", JsonValue(telemetrySnapshot.backendPlayout.outputFrameRenderRequestMilliseconds));
|
|
||||||
outputRender.set("endAccessMs", JsonValue(telemetrySnapshot.backendPlayout.outputFrameEndAccessMilliseconds));
|
|
||||||
outputRender.set("queueWaitMs", JsonValue(telemetrySnapshot.backendPlayout.outputRenderQueueWaitMilliseconds));
|
|
||||||
outputRender.set("drawMs", JsonValue(telemetrySnapshot.backendPlayout.outputRenderDrawMilliseconds));
|
|
||||||
outputRender.set("fenceWaitMs", JsonValue(telemetrySnapshot.backendPlayout.outputReadbackFenceWaitMilliseconds));
|
|
||||||
outputRender.set("mapMs", JsonValue(telemetrySnapshot.backendPlayout.outputReadbackMapMilliseconds));
|
|
||||||
outputRender.set("readbackCopyMs", JsonValue(telemetrySnapshot.backendPlayout.outputReadbackCopyMilliseconds));
|
|
||||||
outputRender.set("cachedCopyMs", JsonValue(telemetrySnapshot.backendPlayout.outputCachedCopyMilliseconds));
|
|
||||||
outputRender.set("asyncQueueMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueMilliseconds));
|
|
||||||
outputRender.set("asyncQueueBufferMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueBufferMilliseconds));
|
|
||||||
outputRender.set("asyncQueueSetupMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueSetupMilliseconds));
|
|
||||||
outputRender.set("asyncQueueReadPixelsMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueReadPixelsMilliseconds));
|
|
||||||
outputRender.set("asyncQueueFenceMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueFenceMilliseconds));
|
|
||||||
outputRender.set("syncReadMs", JsonValue(telemetrySnapshot.backendPlayout.outputSyncReadMilliseconds));
|
|
||||||
outputRender.set("asyncReadbackMissCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.outputAsyncReadbackMissCount)));
|
|
||||||
outputRender.set("cachedFallbackCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.outputCachedFallbackCount)));
|
|
||||||
outputRender.set("syncFallbackCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.outputSyncFallbackCount)));
|
|
||||||
|
|
||||||
JsonValue recovery = JsonValue::MakeObject();
|
|
||||||
recovery.set("completionResult", JsonValue(telemetrySnapshot.backendPlayout.completionResult));
|
|
||||||
recovery.set("completedFrameIndex", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.completedFrameIndex)));
|
|
||||||
recovery.set("scheduledFrameIndex", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.scheduledFrameIndex)));
|
|
||||||
recovery.set("scheduledLeadFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.scheduledLeadFrames)));
|
|
||||||
recovery.set("syntheticScheduledLeadFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.scheduledLeadFrames)));
|
|
||||||
recovery.set("measuredLagFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.measuredLagFrames)));
|
|
||||||
recovery.set("catchUpFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.catchUpFrames)));
|
|
||||||
recovery.set("lateStreak", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.lateStreak)));
|
|
||||||
recovery.set("dropStreak", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.dropStreak)));
|
|
||||||
|
|
||||||
JsonValue deckLinkPlayout = JsonValue::MakeObject();
|
|
||||||
deckLinkPlayout.set("actualBufferedFramesAvailable", JsonValue(telemetrySnapshot.backendPlayout.actualDeckLinkBufferedFramesAvailable));
|
|
||||||
deckLinkPlayout.set("actualBufferedFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.actualDeckLinkBufferedFrames)));
|
|
||||||
deckLinkPlayout.set("targetBufferedFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.targetDeckLinkBufferedFrames)));
|
|
||||||
deckLinkPlayout.set("scheduleCallMs", JsonValue(telemetrySnapshot.backendPlayout.deckLinkScheduleCallMilliseconds));
|
|
||||||
deckLinkPlayout.set("scheduleFailures", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.deckLinkScheduleFailureCount)));
|
|
||||||
|
|
||||||
JsonValue scheduler = JsonValue::MakeObject();
|
|
||||||
scheduler.set("syntheticLeadFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.scheduledLeadFrames)));
|
|
||||||
|
|
||||||
JsonValue backendPlayout = JsonValue::MakeObject();
|
|
||||||
backendPlayout.set("lifecycleState", JsonValue(telemetrySnapshot.backendPlayout.lifecycleState));
|
|
||||||
backendPlayout.set("degraded", JsonValue(telemetrySnapshot.backendPlayout.degraded));
|
|
||||||
backendPlayout.set("statusMessage", JsonValue(telemetrySnapshot.backendPlayout.statusMessage));
|
|
||||||
backendPlayout.set("lateFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.lateFrameCount)));
|
|
||||||
backendPlayout.set("droppedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.droppedFrameCount)));
|
|
||||||
backendPlayout.set("flushedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.flushedFrameCount)));
|
|
||||||
backendPlayout.set("readyQueue", readyQueue);
|
|
||||||
backendPlayout.set("systemMemory", systemMemory);
|
|
||||||
backendPlayout.set("outputRender", outputRender);
|
|
||||||
backendPlayout.set("decklink", deckLinkPlayout);
|
|
||||||
backendPlayout.set("scheduler", scheduler);
|
|
||||||
backendPlayout.set("recovery", recovery);
|
|
||||||
root.set("backendPlayout", backendPlayout);
|
|
||||||
|
|
||||||
JsonValue eventQueue = JsonValue::MakeObject();
|
|
||||||
eventQueue.set("name", JsonValue(telemetrySnapshot.runtimeEvents.queue.queueName));
|
|
||||||
eventQueue.set("depth", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.queue.depth)));
|
|
||||||
eventQueue.set("capacity", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.queue.capacity)));
|
|
||||||
eventQueue.set("droppedCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.queue.droppedCount)));
|
|
||||||
eventQueue.set("oldestEventAgeMs", JsonValue(telemetrySnapshot.runtimeEvents.queue.oldestEventAgeMilliseconds));
|
|
||||||
|
|
||||||
JsonValue eventDispatch = JsonValue::MakeObject();
|
|
||||||
eventDispatch.set("dispatchCallCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.dispatchCallCount)));
|
|
||||||
eventDispatch.set("dispatchedEventCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.dispatchedEventCount)));
|
|
||||||
eventDispatch.set("handlerInvocationCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.handlerInvocationCount)));
|
|
||||||
eventDispatch.set("handlerFailureCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.handlerFailureCount)));
|
|
||||||
eventDispatch.set("lastDispatchDurationMs", JsonValue(telemetrySnapshot.runtimeEvents.dispatch.lastDispatchDurationMilliseconds));
|
|
||||||
eventDispatch.set("maxDispatchDurationMs", JsonValue(telemetrySnapshot.runtimeEvents.dispatch.maxDispatchDurationMilliseconds));
|
|
||||||
|
|
||||||
JsonValue runtimeEvents = JsonValue::MakeObject();
|
|
||||||
runtimeEvents.set("queue", eventQueue);
|
|
||||||
runtimeEvents.set("dispatch", eventDispatch);
|
|
||||||
root.set("runtimeEvents", runtimeEvents);
|
|
||||||
|
|
||||||
JsonValue shaderLibrary = JsonValue::MakeArray();
|
|
||||||
for (const ShaderPackageStatus& status : model.packageStatuses)
|
|
||||||
{
|
|
||||||
JsonValue shader = JsonValue::MakeObject();
|
|
||||||
shader.set("id", JsonValue(status.id));
|
|
||||||
shader.set("name", JsonValue(status.displayName));
|
|
||||||
shader.set("description", JsonValue(status.description));
|
|
||||||
shader.set("category", JsonValue(status.category));
|
|
||||||
shader.set("available", JsonValue(status.available));
|
|
||||||
if (!status.available)
|
|
||||||
shader.set("error", JsonValue(status.error));
|
|
||||||
|
|
||||||
auto shaderIt = model.shaderCatalog.packagesById.find(status.id);
|
|
||||||
if (status.available && shaderIt != model.shaderCatalog.packagesById.end() && shaderIt->second.temporal.enabled)
|
|
||||||
{
|
|
||||||
const ShaderPackage& shaderPackage = shaderIt->second;
|
|
||||||
JsonValue temporal = JsonValue::MakeObject();
|
|
||||||
temporal.set("enabled", JsonValue(true));
|
|
||||||
temporal.set("historySource", JsonValue(RuntimeStateJson::TemporalHistorySourceToString(shaderPackage.temporal.historySource)));
|
|
||||||
temporal.set("requestedHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.requestedHistoryLength)));
|
|
||||||
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.effectiveHistoryLength)));
|
|
||||||
shader.set("temporal", temporal);
|
|
||||||
}
|
|
||||||
if (status.available && shaderIt != model.shaderCatalog.packagesById.end() && shaderIt->second.feedback.enabled)
|
|
||||||
{
|
|
||||||
const ShaderPackage& shaderPackage = shaderIt->second;
|
|
||||||
JsonValue feedback = JsonValue::MakeObject();
|
|
||||||
feedback.set("enabled", JsonValue(true));
|
|
||||||
feedback.set("writePass", JsonValue(shaderPackage.feedback.writePassId));
|
|
||||||
shader.set("feedback", feedback);
|
|
||||||
}
|
|
||||||
shaderLibrary.pushBack(shader);
|
|
||||||
}
|
|
||||||
root.set("shaders", shaderLibrary);
|
|
||||||
|
|
||||||
JsonValue stackPresets = JsonValue::MakeArray();
|
|
||||||
for (const std::string& presetName : model.stackPresetNames)
|
|
||||||
stackPresets.pushBack(JsonValue(presetName));
|
|
||||||
root.set("stackPresets", stackPresets);
|
|
||||||
|
|
||||||
root.set("layers", RuntimeStateJson::SerializeLayerStack(model.layerStack.Layers(), model.shaderCatalog.packagesById));
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeJson.h"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class RuntimeStore;
|
|
||||||
|
|
||||||
class RuntimeStatePresenter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static std::string BuildRuntimeStateJson(const RuntimeStore& runtimeStore);
|
|
||||||
static JsonValue BuildRuntimeStateValue(const RuntimeStore& runtimeStore);
|
|
||||||
};
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
#include "RenderSnapshotBuilder.h"
|
|
||||||
|
|
||||||
#include "RuntimeClock.h"
|
|
||||||
#include "RuntimeParameterUtils.h"
|
|
||||||
#include "RuntimeStore.h"
|
|
||||||
#include "ShaderCompiler.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <chrono>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <mutex>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
RenderSnapshotBuilder::RenderSnapshotBuilder(RuntimeStore& runtimeStore) :
|
|
||||||
mRuntimeStore(runtimeStore)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderSnapshotBuilder::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ShaderPackage shaderPackage;
|
|
||||||
if (!mRuntimeStore.CopyShaderPackageForStoredLayer(layerId, shaderPackage, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const ShaderCompilerInputs inputs = mRuntimeStore.GetShaderCompilerInputs();
|
|
||||||
|
|
||||||
ShaderCompiler compiler(
|
|
||||||
inputs.repoRoot,
|
|
||||||
inputs.wrapperPath,
|
|
||||||
inputs.generatedGlslPath,
|
|
||||||
inputs.patchedGlslPath,
|
|
||||||
inputs.maxTemporalHistoryFrames);
|
|
||||||
passSources.clear();
|
|
||||||
passSources.reserve(shaderPackage.passes.size());
|
|
||||||
for (const ShaderPassDefinition& pass : shaderPackage.passes)
|
|
||||||
{
|
|
||||||
ShaderPassBuildSource passSource;
|
|
||||||
passSource.passId = pass.id;
|
|
||||||
passSource.inputNames = pass.inputNames;
|
|
||||||
passSource.outputName = pass.outputName;
|
|
||||||
if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, passSource.fragmentShaderSource, error))
|
|
||||||
return false;
|
|
||||||
passSources.push_back(std::move(passSource));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (const std::exception& exception)
|
|
||||||
{
|
|
||||||
error = std::string("RenderSnapshotBuilder::BuildLayerPassFragmentShaderSources exception: ") + exception.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
error = "RenderSnapshotBuilder::BuildLayerPassFragmentShaderSources threw a non-standard exception.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned RenderSnapshotBuilder::GetMaxTemporalHistoryFrames() const
|
|
||||||
{
|
|
||||||
return mRuntimeStore.GetConfiguredMaxTemporalHistoryFrames();
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeSnapshotVersions RenderSnapshotBuilder::GetVersions() const
|
|
||||||
{
|
|
||||||
RuntimeSnapshotVersions versions;
|
|
||||||
versions.renderStateVersion = mRenderStateVersion.load(std::memory_order_relaxed);
|
|
||||||
versions.parameterStateVersion = mParameterStateVersion.load(std::memory_order_relaxed);
|
|
||||||
return versions;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderSnapshotBuilder::AdvanceFrame()
|
|
||||||
{
|
|
||||||
++mFrameCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderSnapshotBuilder::BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
|
|
||||||
{
|
|
||||||
BuildLayerRenderStates(outputWidth, outputHeight, mRuntimeStore.BuildRenderSnapshotReadModel(), states);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderSnapshotBuilder::TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
|
|
||||||
{
|
|
||||||
BuildLayerRenderStates(outputWidth, outputHeight, mRuntimeStore.BuildRenderSnapshotReadModel(), states);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderSnapshotBuilder::TryRefreshLayerParameters(std::vector<RuntimeRenderState>& states) const
|
|
||||||
{
|
|
||||||
RefreshLayerParameters(mRuntimeStore.CopyCommittedLiveLayerStates(), states);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderSnapshotBuilder::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
|
|
||||||
{
|
|
||||||
RefreshDynamicRenderStateFields(mRuntimeStore.GetRenderTimingSnapshot(), states);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderSnapshotBuilder::MarkRenderStateDirty()
|
|
||||||
{
|
|
||||||
mRenderStateVersion.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderSnapshotBuilder::MarkParameterStateDirty()
|
|
||||||
{
|
|
||||||
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderSnapshotBuilder::BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, const RenderSnapshotReadModel& readModel, std::vector<RuntimeRenderState>& states) const
|
|
||||||
{
|
|
||||||
states.clear();
|
|
||||||
|
|
||||||
for (const LayerStackStore::LayerPersistentState& layer : readModel.committedLiveState.layers)
|
|
||||||
{
|
|
||||||
auto shaderIt = readModel.committedLiveState.packagesById.find(layer.shaderId);
|
|
||||||
if (shaderIt == readModel.committedLiveState.packagesById.end())
|
|
||||||
continue;
|
|
||||||
const ShaderPackage& shaderPackage = shaderIt->second;
|
|
||||||
|
|
||||||
RuntimeRenderState state;
|
|
||||||
state.layerId = layer.id;
|
|
||||||
state.shaderId = layer.shaderId;
|
|
||||||
state.shaderName = shaderPackage.displayName;
|
|
||||||
state.mixAmount = 1.0;
|
|
||||||
state.bypass = layer.bypass ? 1.0 : 0.0;
|
|
||||||
state.inputWidth = readModel.signalStatus.width;
|
|
||||||
state.inputHeight = readModel.signalStatus.height;
|
|
||||||
state.outputWidth = outputWidth;
|
|
||||||
state.outputHeight = outputHeight;
|
|
||||||
state.parameterDefinitions = shaderPackage.parameters;
|
|
||||||
state.textureAssets = shaderPackage.textureAssets;
|
|
||||||
state.fontAssets = shaderPackage.fontAssets;
|
|
||||||
state.isTemporal = shaderPackage.temporal.enabled;
|
|
||||||
state.temporalHistorySource = shaderPackage.temporal.historySource;
|
|
||||||
state.requestedTemporalHistoryLength = shaderPackage.temporal.requestedHistoryLength;
|
|
||||||
state.effectiveTemporalHistoryLength = shaderPackage.temporal.effectiveHistoryLength;
|
|
||||||
state.feedback = shaderPackage.feedback;
|
|
||||||
|
|
||||||
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
|
|
||||||
{
|
|
||||||
ShaderParameterValue value = DefaultValueForDefinition(definition);
|
|
||||||
auto valueIt = layer.parameterValues.find(definition.id);
|
|
||||||
if (valueIt != layer.parameterValues.end())
|
|
||||||
value = valueIt->second;
|
|
||||||
state.parameterValues[definition.id] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
states.push_back(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
RefreshDynamicRenderStateFields(readModel.timing, states);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderSnapshotBuilder::RefreshLayerParameters(const std::vector<LayerStackStore::LayerPersistentState>& layers, std::vector<RuntimeRenderState>& states) const
|
|
||||||
{
|
|
||||||
for (RuntimeRenderState& state : states)
|
|
||||||
{
|
|
||||||
const auto layerIt = std::find_if(layers.begin(), layers.end(),
|
|
||||||
[&state](const LayerStackStore::LayerPersistentState& layer) { return layer.id == state.layerId; });
|
|
||||||
if (layerIt == layers.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
state.bypass = layerIt->bypass ? 1.0 : 0.0;
|
|
||||||
state.parameterValues.clear();
|
|
||||||
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
|
||||||
{
|
|
||||||
ShaderParameterValue value = DefaultValueForDefinition(definition);
|
|
||||||
auto valueIt = layerIt->parameterValues.find(definition.id);
|
|
||||||
if (valueIt != layerIt->parameterValues.end())
|
|
||||||
value = valueIt->second;
|
|
||||||
state.parameterValues[definition.id] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderSnapshotBuilder::RefreshDynamicRenderStateFields(const RenderTimingSnapshot& timing, std::vector<RuntimeRenderState>& states) const
|
|
||||||
{
|
|
||||||
const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot();
|
|
||||||
const double timeSeconds = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - timing.startTime).count();
|
|
||||||
const double frameCount = static_cast<double>(mFrameCounter.load(std::memory_order_relaxed));
|
|
||||||
|
|
||||||
for (RuntimeRenderState& state : states)
|
|
||||||
{
|
|
||||||
state.timeSeconds = timeSeconds;
|
|
||||||
state.utcTimeSeconds = clock.utcTimeSeconds;
|
|
||||||
state.utcOffsetSeconds = clock.utcOffsetSeconds;
|
|
||||||
state.startupRandom = timing.startupRandom;
|
|
||||||
state.frameCount = frameCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeStoreReadModels.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class RuntimeStore;
|
|
||||||
|
|
||||||
struct RuntimeSnapshotVersions
|
|
||||||
{
|
|
||||||
uint64_t renderStateVersion = 0;
|
|
||||||
uint64_t parameterStateVersion = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RenderSnapshotBuilder
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit RenderSnapshotBuilder(RuntimeStore& runtimeStore);
|
|
||||||
|
|
||||||
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
|
|
||||||
unsigned GetMaxTemporalHistoryFrames() const;
|
|
||||||
RuntimeSnapshotVersions GetVersions() const;
|
|
||||||
void AdvanceFrame();
|
|
||||||
void BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
|
|
||||||
bool TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
|
|
||||||
bool TryRefreshLayerParameters(std::vector<RuntimeRenderState>& states) const;
|
|
||||||
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
|
|
||||||
void MarkRenderStateDirty();
|
|
||||||
void MarkParameterStateDirty();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, const RenderSnapshotReadModel& readModel, std::vector<RuntimeRenderState>& states) const;
|
|
||||||
void RefreshLayerParameters(const std::vector<LayerStackStore::LayerPersistentState>& layers, std::vector<RuntimeRenderState>& states) const;
|
|
||||||
void RefreshDynamicRenderStateFields(const RenderTimingSnapshot& timing, std::vector<RuntimeRenderState>& states) const;
|
|
||||||
|
|
||||||
RuntimeStore& mRuntimeStore;
|
|
||||||
std::atomic<uint64_t> mFrameCounter{ 0 };
|
|
||||||
std::atomic<uint64_t> mRenderStateVersion{ 0 };
|
|
||||||
std::atomic<uint64_t> mParameterStateVersion{ 0 };
|
|
||||||
};
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
#include "RuntimeSnapshotProvider.h"
|
|
||||||
|
|
||||||
#include "RuntimeEventDispatcher.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
RuntimeSnapshotProvider::RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder, RuntimeEventDispatcher& runtimeEventDispatcher) :
|
|
||||||
mRenderSnapshotBuilder(renderSnapshotBuilder),
|
|
||||||
mRuntimeEventDispatcher(runtimeEventDispatcher)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return mRenderSnapshotBuilder.BuildLayerPassFragmentShaderSources(layerId, passSources, error);
|
|
||||||
}
|
|
||||||
catch (const std::exception& exception)
|
|
||||||
{
|
|
||||||
error = std::string("RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources exception: ") + exception.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
error = "RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources threw a non-standard exception.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
|
|
||||||
{
|
|
||||||
return mRenderSnapshotBuilder.GetMaxTemporalHistoryFrames();
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
|
|
||||||
{
|
|
||||||
return mRenderSnapshotBuilder.GetVersions();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeSnapshotProvider::AdvanceFrame()
|
|
||||||
{
|
|
||||||
mRenderSnapshotBuilder.AdvanceFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const
|
|
||||||
{
|
|
||||||
PublishRenderSnapshotPublishRequested(outputWidth, outputHeight, "publish-render-state-snapshot");
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
const RuntimeSnapshotVersions versionsBefore = GetVersions();
|
|
||||||
RuntimeRenderStateSnapshot publishedSnapshot;
|
|
||||||
if (TryGetPublishedRenderStateSnapshot(outputWidth, outputHeight, versionsBefore, publishedSnapshot))
|
|
||||||
{
|
|
||||||
PublishRenderSnapshotPublished(publishedSnapshot);
|
|
||||||
return publishedSnapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeRenderStateSnapshot snapshot;
|
|
||||||
snapshot.outputWidth = outputWidth;
|
|
||||||
snapshot.outputHeight = outputHeight;
|
|
||||||
mRenderSnapshotBuilder.BuildLayerRenderStates(outputWidth, outputHeight, snapshot.states);
|
|
||||||
|
|
||||||
const RuntimeSnapshotVersions versionsAfter = GetVersions();
|
|
||||||
if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion &&
|
|
||||||
versionsBefore.parameterStateVersion == versionsAfter.parameterStateVersion)
|
|
||||||
{
|
|
||||||
snapshot.versions = versionsAfter;
|
|
||||||
StorePublishedRenderStateSnapshot(snapshot);
|
|
||||||
PublishRenderSnapshotPublished(snapshot);
|
|
||||||
return snapshot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeSnapshotProvider::TryPublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const
|
|
||||||
{
|
|
||||||
PublishRenderSnapshotPublishRequested(outputWidth, outputHeight, "try-publish-render-state-snapshot");
|
|
||||||
|
|
||||||
const RuntimeSnapshotVersions versionsBefore = GetVersions();
|
|
||||||
if (TryGetPublishedRenderStateSnapshot(outputWidth, outputHeight, versionsBefore, snapshot))
|
|
||||||
{
|
|
||||||
PublishRenderSnapshotPublished(snapshot);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<RuntimeRenderState> states;
|
|
||||||
if (!mRenderSnapshotBuilder.TryBuildLayerRenderStates(outputWidth, outputHeight, states))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const RuntimeSnapshotVersions versionsAfter = GetVersions();
|
|
||||||
if (versionsBefore.renderStateVersion != versionsAfter.renderStateVersion ||
|
|
||||||
versionsBefore.parameterStateVersion != versionsAfter.parameterStateVersion)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot.outputWidth = outputWidth;
|
|
||||||
snapshot.outputHeight = outputHeight;
|
|
||||||
snapshot.versions = versionsAfter;
|
|
||||||
snapshot.states = std::move(states);
|
|
||||||
StorePublishedRenderStateSnapshot(snapshot);
|
|
||||||
PublishRenderSnapshotPublished(snapshot);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const
|
|
||||||
{
|
|
||||||
const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion;
|
|
||||||
if (!mRenderSnapshotBuilder.TryRefreshLayerParameters(snapshot.states))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const RuntimeSnapshotVersions versions = GetVersions();
|
|
||||||
if (versions.renderStateVersion != expectedRenderStateVersion)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
snapshot.versions = versions;
|
|
||||||
StorePublishedRenderStateSnapshot(snapshot);
|
|
||||||
PublishRenderSnapshotPublished(snapshot);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
|
|
||||||
{
|
|
||||||
mRenderSnapshotBuilder.RefreshDynamicRenderStateFields(states);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeSnapshotProvider::TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight,
|
|
||||||
const RuntimeSnapshotVersions& versions, RuntimeRenderStateSnapshot& snapshot) const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mPublishedSnapshotMutex);
|
|
||||||
if (!mHasPublishedRenderStateSnapshot ||
|
|
||||||
!SnapshotMatches(mPublishedRenderStateSnapshot, outputWidth, outputHeight, versions))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot = mPublishedRenderStateSnapshot;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeSnapshotProvider::StorePublishedRenderStateSnapshot(const RuntimeRenderStateSnapshot& snapshot) const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mPublishedSnapshotMutex);
|
|
||||||
mPublishedRenderStateSnapshot = snapshot;
|
|
||||||
mHasPublishedRenderStateSnapshot = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeSnapshotProvider::SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight,
|
|
||||||
const RuntimeSnapshotVersions& versions)
|
|
||||||
{
|
|
||||||
return snapshot.outputWidth == outputWidth &&
|
|
||||||
snapshot.outputHeight == outputHeight &&
|
|
||||||
snapshot.versions.renderStateVersion == versions.renderStateVersion &&
|
|
||||||
snapshot.versions.parameterStateVersion == versions.parameterStateVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeSnapshotProvider::PublishRenderSnapshotPublishRequested(unsigned outputWidth, unsigned outputHeight, const std::string& reason) const
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
RenderSnapshotPublishRequestedEvent event;
|
|
||||||
event.outputWidth = outputWidth;
|
|
||||||
event.outputHeight = outputHeight;
|
|
||||||
event.reason = reason;
|
|
||||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeSnapshotProvider"))
|
|
||||||
OutputDebugStringA("RenderSnapshotPublishRequested event publish failed.\n");
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
OutputDebugStringA("RenderSnapshotPublishRequested event publish threw.\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeSnapshotProvider::PublishRenderSnapshotPublished(const RuntimeRenderStateSnapshot& snapshot) const
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
RenderSnapshotPublishedEvent event;
|
|
||||||
event.snapshotVersion = snapshot.versions.renderStateVersion;
|
|
||||||
event.structureVersion = snapshot.versions.renderStateVersion;
|
|
||||||
event.parameterVersion = snapshot.versions.parameterStateVersion;
|
|
||||||
event.outputWidth = snapshot.outputWidth;
|
|
||||||
event.outputHeight = snapshot.outputHeight;
|
|
||||||
event.layerCount = snapshot.states.size();
|
|
||||||
if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeSnapshotProvider"))
|
|
||||||
OutputDebugStringA("RenderSnapshotPublished event publish failed.\n");
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
OutputDebugStringA("RenderSnapshotPublished event publish threw.\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RenderSnapshotBuilder.h"
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class RuntimeEventDispatcher;
|
|
||||||
|
|
||||||
struct RuntimeRenderStateSnapshot
|
|
||||||
{
|
|
||||||
RuntimeSnapshotVersions versions;
|
|
||||||
unsigned outputWidth = 0;
|
|
||||||
unsigned outputHeight = 0;
|
|
||||||
std::vector<RuntimeRenderState> states;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RuntimeSnapshotProvider
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder, RuntimeEventDispatcher& runtimeEventDispatcher);
|
|
||||||
|
|
||||||
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
|
|
||||||
unsigned GetMaxTemporalHistoryFrames() const;
|
|
||||||
RuntimeSnapshotVersions GetVersions() const;
|
|
||||||
void AdvanceFrame();
|
|
||||||
RuntimeRenderStateSnapshot PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const;
|
|
||||||
bool TryPublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const;
|
|
||||||
bool TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const;
|
|
||||||
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight,
|
|
||||||
const RuntimeSnapshotVersions& versions, RuntimeRenderStateSnapshot& snapshot) const;
|
|
||||||
void StorePublishedRenderStateSnapshot(const RuntimeRenderStateSnapshot& snapshot) const;
|
|
||||||
static bool SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight,
|
|
||||||
const RuntimeSnapshotVersions& versions);
|
|
||||||
void PublishRenderSnapshotPublishRequested(unsigned outputWidth, unsigned outputHeight, const std::string& reason) const;
|
|
||||||
void PublishRenderSnapshotPublished(const RuntimeRenderStateSnapshot& snapshot) const;
|
|
||||||
|
|
||||||
RenderSnapshotBuilder& mRenderSnapshotBuilder;
|
|
||||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
|
||||||
mutable std::mutex mPublishedSnapshotMutex;
|
|
||||||
mutable bool mHasPublishedRenderStateSnapshot = false;
|
|
||||||
mutable RuntimeRenderStateSnapshot mPublishedRenderStateSnapshot;
|
|
||||||
};
|
|
||||||
@@ -1,738 +0,0 @@
|
|||||||
#include "LayerStackStore.h"
|
|
||||||
|
|
||||||
#include "RuntimeParameterUtils.h"
|
|
||||||
#include "RuntimeStateJson.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cctype>
|
|
||||||
#include <cmath>
|
|
||||||
#include <set>
|
|
||||||
#include <sstream>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
std::string TrimCopy(const std::string& text)
|
|
||||||
{
|
|
||||||
std::size_t start = 0;
|
|
||||||
while (start < text.size() && std::isspace(static_cast<unsigned char>(text[start])))
|
|
||||||
++start;
|
|
||||||
|
|
||||||
std::size_t end = text.size();
|
|
||||||
while (end > start && std::isspace(static_cast<unsigned char>(text[end - 1])))
|
|
||||||
--end;
|
|
||||||
|
|
||||||
return text.substr(start, end - start);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string SimplifyControlKey(const std::string& text)
|
|
||||||
{
|
|
||||||
std::string simplified;
|
|
||||||
for (unsigned char ch : text)
|
|
||||||
{
|
|
||||||
if (std::isalnum(ch))
|
|
||||||
simplified.push_back(static_cast<char>(std::tolower(ch)));
|
|
||||||
}
|
|
||||||
return simplified;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MatchesControlKey(const std::string& candidate, const std::string& key)
|
|
||||||
{
|
|
||||||
return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TryParseLayerIdNumber(const std::string& layerId, uint64_t& number)
|
|
||||||
{
|
|
||||||
const std::string prefix = "layer-";
|
|
||||||
if (layerId.rfind(prefix, 0) != 0 || layerId.size() == prefix.size())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
uint64_t parsed = 0;
|
|
||||||
for (std::size_t index = prefix.size(); index < layerId.size(); ++index)
|
|
||||||
{
|
|
||||||
const unsigned char ch = static_cast<unsigned char>(layerId[index]);
|
|
||||||
if (!std::isdigit(ch))
|
|
||||||
return false;
|
|
||||||
parsed = parsed * 10 + static_cast<uint64_t>(ch - '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
number = parsed;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::LoadPersistentStateValue(const JsonValue& root)
|
|
||||||
{
|
|
||||||
if (const JsonValue* layersValue = root.find("layers"))
|
|
||||||
{
|
|
||||||
for (const JsonValue& layerValue : layersValue->asArray())
|
|
||||||
{
|
|
||||||
if (!layerValue.isObject())
|
|
||||||
continue;
|
|
||||||
LayerPersistentState layer;
|
|
||||||
if (const JsonValue* idValue = layerValue.find("id"))
|
|
||||||
layer.id = idValue->asString();
|
|
||||||
if (const JsonValue* shaderIdValue = layerValue.find("shaderId"))
|
|
||||||
layer.shaderId = shaderIdValue->asString();
|
|
||||||
if (const JsonValue* bypassValue = layerValue.find("bypass"))
|
|
||||||
layer.bypass = bypassValue->asBoolean(false);
|
|
||||||
else if (const JsonValue* enabledValue = layerValue.find("enabled"))
|
|
||||||
layer.bypass = !enabledValue->asBoolean(true);
|
|
||||||
|
|
||||||
if (const JsonValue* parameterValues = layerValue.find("parameterValues"))
|
|
||||||
{
|
|
||||||
for (const auto& parameterItem : parameterValues->asObject())
|
|
||||||
{
|
|
||||||
ShaderParameterValue value;
|
|
||||||
const JsonValue& jsonValue = parameterItem.second;
|
|
||||||
if (jsonValue.isBoolean())
|
|
||||||
value.booleanValue = jsonValue.asBoolean();
|
|
||||||
else if (jsonValue.isString())
|
|
||||||
value.enumValue = jsonValue.asString();
|
|
||||||
else if (jsonValue.isNumber())
|
|
||||||
value.numberValues.push_back(jsonValue.asNumber());
|
|
||||||
else if (jsonValue.isArray())
|
|
||||||
value.numberValues = JsonArrayToNumbers(jsonValue);
|
|
||||||
layer.parameterValues[parameterItem.first] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!layer.shaderId.empty())
|
|
||||||
mLayers.push_back(layer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::string activeShaderId;
|
|
||||||
if (const JsonValue* activeShaderValue = root.find("activeShaderId"))
|
|
||||||
activeShaderId = activeShaderValue->asString();
|
|
||||||
|
|
||||||
if (!activeShaderId.empty())
|
|
||||||
{
|
|
||||||
LayerPersistentState layer;
|
|
||||||
layer.id = GenerateLayerId(mLayers, mNextLayerId);
|
|
||||||
layer.shaderId = activeShaderId;
|
|
||||||
layer.bypass = false;
|
|
||||||
|
|
||||||
if (const JsonValue* valuesByShader = root.find("parameterValuesByShader"))
|
|
||||||
{
|
|
||||||
const JsonValue* shaderValues = valuesByShader->find(activeShaderId);
|
|
||||||
if (shaderValues)
|
|
||||||
{
|
|
||||||
for (const auto& parameterItem : shaderValues->asObject())
|
|
||||||
{
|
|
||||||
ShaderParameterValue value;
|
|
||||||
const JsonValue& jsonValue = parameterItem.second;
|
|
||||||
if (jsonValue.isBoolean())
|
|
||||||
value.booleanValue = jsonValue.asBoolean();
|
|
||||||
else if (jsonValue.isString())
|
|
||||||
value.enumValue = jsonValue.asString();
|
|
||||||
else if (jsonValue.isNumber())
|
|
||||||
value.numberValues.push_back(jsonValue.asNumber());
|
|
||||||
else if (jsonValue.isArray())
|
|
||||||
value.numberValues = JsonArrayToNumbers(jsonValue);
|
|
||||||
layer.parameterValues[parameterItem.first] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mLayers.push_back(layer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValue LayerStackStore::BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const
|
|
||||||
{
|
|
||||||
JsonValue root = JsonValue::MakeObject();
|
|
||||||
JsonValue layers = JsonValue::MakeArray();
|
|
||||||
for (const LayerPersistentState& layer : mLayers)
|
|
||||||
{
|
|
||||||
JsonValue layerValue = JsonValue::MakeObject();
|
|
||||||
layerValue.set("id", JsonValue(layer.id));
|
|
||||||
layerValue.set("shaderId", JsonValue(layer.shaderId));
|
|
||||||
layerValue.set("bypass", JsonValue(layer.bypass));
|
|
||||||
|
|
||||||
JsonValue parameterValues = JsonValue::MakeObject();
|
|
||||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
|
|
||||||
for (const auto& parameterItem : layer.parameterValues)
|
|
||||||
{
|
|
||||||
const ShaderParameterDefinition* definition = nullptr;
|
|
||||||
if (shaderPackage)
|
|
||||||
{
|
|
||||||
for (const ShaderParameterDefinition& candidate : shaderPackage->parameters)
|
|
||||||
{
|
|
||||||
if (candidate.id == parameterItem.first)
|
|
||||||
{
|
|
||||||
definition = &candidate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (definition)
|
|
||||||
parameterValues.set(parameterItem.first, RuntimeStateJson::SerializeParameterValue(*definition, parameterItem.second));
|
|
||||||
}
|
|
||||||
|
|
||||||
layerValue.set("parameterValues", parameterValues);
|
|
||||||
layers.pushBack(layerValue);
|
|
||||||
}
|
|
||||||
root.set("layers", layers);
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LayerStackStore::NormalizeLayerIds()
|
|
||||||
{
|
|
||||||
std::set<std::string> usedIds;
|
|
||||||
uint64_t maxLayerNumber = mNextLayerId;
|
|
||||||
|
|
||||||
for (LayerPersistentState& layer : mLayers)
|
|
||||||
{
|
|
||||||
uint64_t layerNumber = 0;
|
|
||||||
const bool hasReusableId = !layer.id.empty() &&
|
|
||||||
usedIds.find(layer.id) == usedIds.end() &&
|
|
||||||
TryParseLayerIdNumber(layer.id, layerNumber);
|
|
||||||
|
|
||||||
if (hasReusableId)
|
|
||||||
{
|
|
||||||
usedIds.insert(layer.id);
|
|
||||||
maxLayerNumber = (std::max)(maxLayerNumber, layerNumber);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
++maxLayerNumber;
|
|
||||||
layer.id = "layer-" + std::to_string(maxLayerNumber);
|
|
||||||
}
|
|
||||||
while (usedIds.find(layer.id) != usedIds.end());
|
|
||||||
|
|
||||||
usedIds.insert(layer.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
mNextLayerId = maxLayerNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LayerStackStore::EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog)
|
|
||||||
{
|
|
||||||
for (LayerPersistentState& layer : mLayers)
|
|
||||||
{
|
|
||||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
|
|
||||||
if (shaderPackage)
|
|
||||||
EnsureLayerDefaults(layer, *shaderPackage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LayerStackStore::EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog)
|
|
||||||
{
|
|
||||||
if (!mLayers.empty() || shaderCatalog.PackageOrder().empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
LayerPersistentState layer;
|
|
||||||
layer.id = GenerateLayerId(mLayers, mNextLayerId);
|
|
||||||
layer.shaderId = shaderCatalog.PackageOrder().front();
|
|
||||||
layer.bypass = false;
|
|
||||||
if (const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId))
|
|
||||||
EnsureLayerDefaults(layer, *shaderPackage);
|
|
||||||
mLayers.push_back(layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LayerStackStore::RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog)
|
|
||||||
{
|
|
||||||
for (auto it = mLayers.begin(); it != mLayers.end();)
|
|
||||||
{
|
|
||||||
if (!shaderCatalog.HasPackage(it->shaderId))
|
|
||||||
it = mLayers.erase(it);
|
|
||||||
else
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error)
|
|
||||||
{
|
|
||||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
|
|
||||||
if (!shaderPackage)
|
|
||||||
{
|
|
||||||
error = "Unknown shader id: " + shaderId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LayerPersistentState layer;
|
|
||||||
layer.id = GenerateLayerId(mLayers, mNextLayerId);
|
|
||||||
layer.shaderId = shaderId;
|
|
||||||
layer.bypass = false;
|
|
||||||
EnsureLayerDefaults(layer, *shaderPackage);
|
|
||||||
mLayers.push_back(layer);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::DeleteLayer(const std::string& layerId, std::string& error)
|
|
||||||
{
|
|
||||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
|
||||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
|
||||||
if (it == mLayers.end())
|
|
||||||
{
|
|
||||||
error = "Unknown layer id: " + layerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mLayers.erase(it);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::MoveLayer(const std::string& layerId, int direction, std::string& error)
|
|
||||||
{
|
|
||||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
|
||||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
|
||||||
if (it == mLayers.end())
|
|
||||||
{
|
|
||||||
error = "Unknown layer id: " + layerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::ptrdiff_t index = std::distance(mLayers.begin(), it);
|
|
||||||
const std::ptrdiff_t newIndex = index + direction;
|
|
||||||
if (newIndex < 0 || newIndex >= static_cast<std::ptrdiff_t>(mLayers.size()))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
std::swap(mLayers[index], mLayers[newIndex]);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
|
||||||
{
|
|
||||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
|
||||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
|
||||||
if (it == mLayers.end())
|
|
||||||
{
|
|
||||||
error = "Unknown layer id: " + layerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mLayers.empty())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (targetIndex >= mLayers.size())
|
|
||||||
targetIndex = mLayers.size() - 1;
|
|
||||||
|
|
||||||
const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(mLayers.begin(), it));
|
|
||||||
if (sourceIndex == targetIndex)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
LayerPersistentState movedLayer = *it;
|
|
||||||
mLayers.erase(mLayers.begin() + static_cast<std::ptrdiff_t>(sourceIndex));
|
|
||||||
mLayers.insert(mLayers.begin() + static_cast<std::ptrdiff_t>(targetIndex), movedLayer);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
|
|
||||||
{
|
|
||||||
LayerPersistentState* layer = FindLayerById(layerId);
|
|
||||||
if (!layer)
|
|
||||||
{
|
|
||||||
error = "Unknown layer id: " + layerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
layer->bypass = bypassed;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error)
|
|
||||||
{
|
|
||||||
LayerPersistentState* layer = FindLayerById(layerId);
|
|
||||||
if (!layer)
|
|
||||||
{
|
|
||||||
error = "Unknown layer id: " + layerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
|
|
||||||
if (!shaderPackage)
|
|
||||||
{
|
|
||||||
error = "Unknown shader id: " + shaderId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
layer->shaderId = shaderId;
|
|
||||||
layer->parameterValues.clear();
|
|
||||||
EnsureLayerDefaults(*layer, *shaderPackage);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error)
|
|
||||||
{
|
|
||||||
LayerPersistentState* layer = FindLayerById(layerId);
|
|
||||||
if (!layer)
|
|
||||||
{
|
|
||||||
error = "Unknown layer id: " + layerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
layer->parameterValues[parameterId] = value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error)
|
|
||||||
{
|
|
||||||
LayerPersistentState* layer = FindLayerById(layerId);
|
|
||||||
if (!layer)
|
|
||||||
{
|
|
||||||
error = "Unknown layer id: " + layerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer->shaderId);
|
|
||||||
if (!shaderPackage)
|
|
||||||
{
|
|
||||||
error = "Unknown shader id: " + layer->shaderId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
layer->parameterValues.clear();
|
|
||||||
EnsureLayerDefaults(*layer, *shaderPackage);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::HasLayer(const std::string& layerId) const
|
|
||||||
{
|
|
||||||
return FindLayerById(layerId) != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const
|
|
||||||
{
|
|
||||||
const LayerPersistentState* layer = FindLayerById(layerId);
|
|
||||||
if (!layer)
|
|
||||||
{
|
|
||||||
error = "Unknown layer id: " + layerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer->shaderId);
|
|
||||||
if (!shaderPackage)
|
|
||||||
{
|
|
||||||
error = "Unknown shader id: " + layer->shaderId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto parameterIt = std::find_if(shaderPackage->parameters.begin(), shaderPackage->parameters.end(),
|
|
||||||
[¶meterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
|
|
||||||
if (parameterIt == shaderPackage->parameters.end())
|
|
||||||
{
|
|
||||||
error = "Unknown parameter id: " + parameterId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot = StoredParameterSnapshot();
|
|
||||||
snapshot.layerId = layer->id;
|
|
||||||
snapshot.definition = *parameterIt;
|
|
||||||
auto valueIt = layer->parameterValues.find(parameterIt->id);
|
|
||||||
if (valueIt != layer->parameterValues.end())
|
|
||||||
{
|
|
||||||
snapshot.currentValue = valueIt->second;
|
|
||||||
snapshot.hasCurrentValue = true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const
|
|
||||||
{
|
|
||||||
const LayerPersistentState* matchedLayer = nullptr;
|
|
||||||
const ShaderPackage* matchedPackage = nullptr;
|
|
||||||
|
|
||||||
for (const LayerPersistentState& layer : mLayers)
|
|
||||||
{
|
|
||||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
|
|
||||||
if (!shaderPackage)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (MatchesControlKey(layer.id, layerKey) || MatchesControlKey(shaderPackage->id, layerKey) ||
|
|
||||||
MatchesControlKey(shaderPackage->displayName, layerKey))
|
|
||||||
{
|
|
||||||
matchedLayer = &layer;
|
|
||||||
matchedPackage = shaderPackage;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!matchedLayer || !matchedPackage)
|
|
||||||
{
|
|
||||||
error = "Unknown OSC layer key: " + layerKey;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto parameterIt = std::find_if(matchedPackage->parameters.begin(), matchedPackage->parameters.end(),
|
|
||||||
[¶meterKey](const ShaderParameterDefinition& definition)
|
|
||||||
{
|
|
||||||
return MatchesControlKey(definition.id, parameterKey) || MatchesControlKey(definition.label, parameterKey);
|
|
||||||
});
|
|
||||||
if (parameterIt == matchedPackage->parameters.end())
|
|
||||||
{
|
|
||||||
error = "Unknown OSC parameter key: " + parameterKey;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot = StoredParameterSnapshot();
|
|
||||||
snapshot.layerId = matchedLayer->id;
|
|
||||||
snapshot.definition = *parameterIt;
|
|
||||||
auto valueIt = matchedLayer->parameterValues.find(parameterIt->id);
|
|
||||||
if (valueIt != matchedLayer->parameterValues.end())
|
|
||||||
{
|
|
||||||
snapshot.currentValue = valueIt->second;
|
|
||||||
snapshot.hasCurrentValue = true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
|
|
||||||
{
|
|
||||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
|
||||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
|
||||||
if (it == mLayers.end())
|
|
||||||
{
|
|
||||||
error = "Unknown layer id: " + layerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::ptrdiff_t index = std::distance(mLayers.begin(), it);
|
|
||||||
const std::ptrdiff_t newIndex = index + direction;
|
|
||||||
shouldMove = newIndex >= 0 && newIndex < static_cast<std::ptrdiff_t>(mLayers.size()) && newIndex != index;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
|
|
||||||
{
|
|
||||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
|
||||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
|
||||||
if (it == mLayers.end())
|
|
||||||
{
|
|
||||||
error = "Unknown layer id: " + layerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mLayers.empty())
|
|
||||||
{
|
|
||||||
shouldMove = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::size_t clampedTargetIndex = (std::min)(targetIndex, mLayers.size() - 1);
|
|
||||||
const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(mLayers.begin(), it));
|
|
||||||
shouldMove = sourceIndex != clampedTargetIndex;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValue LayerStackStore::BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const
|
|
||||||
{
|
|
||||||
JsonValue root = JsonValue::MakeObject();
|
|
||||||
root.set("version", JsonValue(1.0));
|
|
||||||
root.set("name", JsonValue(TrimCopy(presetName)));
|
|
||||||
root.set("layers", RuntimeStateJson::SerializeLayerStack(*this, shaderCatalog));
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error)
|
|
||||||
{
|
|
||||||
const JsonValue* layersValue = root.find("layers");
|
|
||||||
if (!layersValue || !layersValue->isArray())
|
|
||||||
{
|
|
||||||
error = "Preset file is missing a valid 'layers' array.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<LayerPersistentState> nextLayers;
|
|
||||||
uint64_t nextLayerId = mNextLayerId;
|
|
||||||
if (!DeserializeLayerStack(shaderCatalog, *layersValue, nextLayers, nextLayerId, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (nextLayers.empty())
|
|
||||||
{
|
|
||||||
error = "Preset does not contain any valid layers.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mLayers = std::move(nextLayers);
|
|
||||||
mNextLayerId = nextLayerId;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string LayerStackStore::MakeSafePresetFileStem(const std::string& presetName)
|
|
||||||
{
|
|
||||||
return ::MakeSafePresetFileStem(presetName);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<LayerStackStore::LayerPersistentState>& LayerStackStore::Layers() const
|
|
||||||
{
|
|
||||||
return mLayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<LayerStackStore::LayerPersistentState>& LayerStackStore::Layers()
|
|
||||||
{
|
|
||||||
return mLayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t LayerStackStore::LayerCount() const
|
|
||||||
{
|
|
||||||
return mLayers.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
const LayerStackStore::LayerPersistentState* LayerStackStore::FindLayerById(const std::string& layerId) const
|
|
||||||
{
|
|
||||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
|
||||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
|
||||||
return it == mLayers.end() ? nullptr : &*it;
|
|
||||||
}
|
|
||||||
|
|
||||||
LayerStackStore::LayerPersistentState* LayerStackStore::FindLayerById(const std::string& layerId)
|
|
||||||
{
|
|
||||||
auto it = std::find_if(mLayers.begin(), mLayers.end(),
|
|
||||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
|
||||||
return it == mLayers.end() ? nullptr : &*it;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderParameterValue LayerStackStore::DefaultValueForDefinition(const ShaderParameterDefinition& definition)
|
|
||||||
{
|
|
||||||
return ::DefaultValueForDefinition(definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LayerStackStore::EnsureLayerDefaults(LayerPersistentState& layerState, const ShaderPackage& shaderPackage)
|
|
||||||
{
|
|
||||||
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
|
|
||||||
{
|
|
||||||
auto valueIt = layerState.parameterValues.find(definition.id);
|
|
||||||
if (valueIt == layerState.parameterValues.end())
|
|
||||||
{
|
|
||||||
layerState.parameterValues[definition.id] = DefaultValueForDefinition(definition);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValue valueJson;
|
|
||||||
bool shouldNormalize = true;
|
|
||||||
switch (definition.type)
|
|
||||||
{
|
|
||||||
case ShaderParameterType::Float:
|
|
||||||
if (valueIt->second.numberValues.empty())
|
|
||||||
shouldNormalize = false;
|
|
||||||
else
|
|
||||||
valueJson = JsonValue(valueIt->second.numberValues.front());
|
|
||||||
break;
|
|
||||||
case ShaderParameterType::Vec2:
|
|
||||||
case ShaderParameterType::Color:
|
|
||||||
valueJson = JsonValue::MakeArray();
|
|
||||||
for (double number : valueIt->second.numberValues)
|
|
||||||
valueJson.pushBack(JsonValue(number));
|
|
||||||
break;
|
|
||||||
case ShaderParameterType::Boolean:
|
|
||||||
valueJson = JsonValue(valueIt->second.booleanValue);
|
|
||||||
break;
|
|
||||||
case ShaderParameterType::Enum:
|
|
||||||
valueJson = JsonValue(valueIt->second.enumValue);
|
|
||||||
break;
|
|
||||||
case ShaderParameterType::Text:
|
|
||||||
{
|
|
||||||
const std::string textValue = !valueIt->second.textValue.empty()
|
|
||||||
? valueIt->second.textValue
|
|
||||||
: valueIt->second.enumValue;
|
|
||||||
if (textValue.empty())
|
|
||||||
{
|
|
||||||
valueIt->second = DefaultValueForDefinition(definition);
|
|
||||||
shouldNormalize = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
valueJson = JsonValue(textValue);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ShaderParameterType::Trigger:
|
|
||||||
if (valueIt->second.numberValues.empty())
|
|
||||||
valueJson = JsonValue(0.0);
|
|
||||||
else
|
|
||||||
valueJson = JsonValue((std::max)(0.0, std::floor(valueIt->second.numberValues.front())));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shouldNormalize)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ShaderParameterValue normalizedValue;
|
|
||||||
std::string normalizeError;
|
|
||||||
if (NormalizeAndValidateParameterValue(definition, valueJson, normalizedValue, normalizeError))
|
|
||||||
valueIt->second = normalizedValue;
|
|
||||||
else
|
|
||||||
valueIt->second = DefaultValueForDefinition(definition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayerStackStore::DeserializeLayerStack(const ShaderPackageCatalog& shaderCatalog, const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId, std::string& error)
|
|
||||||
{
|
|
||||||
for (const JsonValue& layerValue : layersValue.asArray())
|
|
||||||
{
|
|
||||||
if (!layerValue.isObject())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const JsonValue* shaderIdValue = layerValue.find("shaderId");
|
|
||||||
if (!shaderIdValue)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const std::string shaderId = shaderIdValue->asString();
|
|
||||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
|
|
||||||
if (!shaderPackage)
|
|
||||||
{
|
|
||||||
error = "Preset references unknown shader id: " + shaderId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LayerPersistentState layer;
|
|
||||||
layer.id = GenerateLayerId(layers, nextLayerId);
|
|
||||||
layer.shaderId = shaderId;
|
|
||||||
if (const JsonValue* bypassValue = layerValue.find("bypass"))
|
|
||||||
layer.bypass = bypassValue->asBoolean(false);
|
|
||||||
|
|
||||||
if (const JsonValue* parametersValue = layerValue.find("parameters"))
|
|
||||||
{
|
|
||||||
for (const JsonValue& parameterValue : parametersValue->asArray())
|
|
||||||
{
|
|
||||||
if (!parameterValue.isObject())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const JsonValue* parameterIdValue = parameterValue.find("id");
|
|
||||||
const JsonValue* valueValue = parameterValue.find("value");
|
|
||||||
if (!parameterIdValue || !valueValue)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const std::string parameterId = parameterIdValue->asString();
|
|
||||||
auto definitionIt = std::find_if(shaderPackage->parameters.begin(), shaderPackage->parameters.end(),
|
|
||||||
[¶meterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
|
|
||||||
if (definitionIt == shaderPackage->parameters.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ShaderParameterValue normalizedValue;
|
|
||||||
if (!NormalizeAndValidateParameterValue(*definitionIt, *valueValue, normalizedValue, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
layer.parameterValues[parameterId] = normalizedValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureLayerDefaults(layer, *shaderPackage);
|
|
||||||
layers.push_back(layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string LayerStackStore::GenerateLayerId(std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
++nextLayerId;
|
|
||||||
const std::string candidate = "layer-" + std::to_string(nextLayerId);
|
|
||||||
auto it = std::find_if(layers.begin(), layers.end(),
|
|
||||||
[&candidate](const LayerPersistentState& layer) { return layer.id == candidate; });
|
|
||||||
if (it == layers.end())
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RuntimeJson.h"
|
|
||||||
#include "ShaderPackageCatalog.h"
|
|
||||||
#include "ShaderTypes.h"
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class LayerStackStore
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct LayerPersistentState
|
|
||||||
{
|
|
||||||
std::string id;
|
|
||||||
std::string shaderId;
|
|
||||||
bool bypass = false;
|
|
||||||
std::map<std::string, ShaderParameterValue> parameterValues;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct StoredParameterSnapshot
|
|
||||||
{
|
|
||||||
std::string layerId;
|
|
||||||
ShaderParameterDefinition definition;
|
|
||||||
ShaderParameterValue currentValue;
|
|
||||||
bool hasCurrentValue = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool LoadPersistentStateValue(const JsonValue& root);
|
|
||||||
JsonValue BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const;
|
|
||||||
void NormalizeLayerIds();
|
|
||||||
void EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog);
|
|
||||||
void EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog);
|
|
||||||
void RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog);
|
|
||||||
|
|
||||||
bool CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error);
|
|
||||||
bool DeleteLayer(const std::string& layerId, std::string& error);
|
|
||||||
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
|
|
||||||
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
|
|
||||||
bool SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error);
|
|
||||||
bool SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error);
|
|
||||||
bool SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error);
|
|
||||||
bool ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error);
|
|
||||||
|
|
||||||
bool HasLayer(const std::string& layerId) const;
|
|
||||||
bool TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const;
|
|
||||||
bool TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const;
|
|
||||||
bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
|
|
||||||
bool ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
|
|
||||||
|
|
||||||
JsonValue BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const;
|
|
||||||
bool LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error);
|
|
||||||
static std::string MakeSafePresetFileStem(const std::string& presetName);
|
|
||||||
|
|
||||||
const std::vector<LayerPersistentState>& Layers() const;
|
|
||||||
std::vector<LayerPersistentState>& Layers();
|
|
||||||
std::size_t LayerCount() const;
|
|
||||||
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
|
|
||||||
LayerPersistentState* FindLayerById(const std::string& layerId);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition);
|
|
||||||
static void EnsureLayerDefaults(LayerPersistentState& layerState, const ShaderPackage& shaderPackage);
|
|
||||||
static bool DeserializeLayerStack(const ShaderPackageCatalog& shaderCatalog, const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId, std::string& error);
|
|
||||||
static std::string GenerateLayerId(std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId);
|
|
||||||
|
|
||||||
std::vector<LayerPersistentState> mLayers;
|
|
||||||
uint64_t mNextLayerId = 0;
|
|
||||||
};
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user