This commit is contained in:
2026-05-02 17:13:53 +10:00
parent 1a4c33b9dc
commit fb1bf01fef
19 changed files with 780 additions and 69 deletions

View File

@@ -247,6 +247,12 @@ bool ControlServer::HandleHttpRequest(SOCKET clientSocket, const std::string& re
if (shaderId && parameterId && value && mCallbacks.updateParameter)
success = mCallbacks.updateParameter(shaderId->asString(), parameterId->asString(), SerializeJson(*value, false), actionError);
}
else if (path == "/api/reset-parameters")
{
const JsonValue* shaderId = root.find("shaderId");
if (shaderId && mCallbacks.resetParameters)
success = mCallbacks.resetParameters(shaderId->asString(), actionError);
}
else if (path == "/api/set-bypass")
{
const JsonValue* bypass = root.find("bypass");

View File

@@ -18,6 +18,7 @@ public:
std::function<std::string()> getStateJson;
std::function<bool(const std::string&, std::string&)> selectShader;
std::function<bool(const std::string&, const std::string&, const std::string&, std::string&)> updateParameter;
std::function<bool(const std::string&, std::string&)> resetParameters;
std::function<bool(bool, std::string&)> setBypass;
std::function<bool(double, std::string&)> setMixAmount;
std::function<bool(std::string&)> reloadShader;

View File

@@ -202,6 +202,7 @@ 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;
switch (message)
{
@@ -289,6 +290,21 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
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
{
@@ -301,15 +317,22 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
}
break;
case WM_ERASEBKGND:
return 1;
case WM_PAINT:
try
{
wglMakeCurrent(hDC, hRC);
PAINTSTRUCT paint = {};
BeginPaint(hWnd, &paint);
EndPaint(hWnd, &paint);
if (pOpenGLComposite)
if (!sInteractiveResize && pOpenGLComposite)
{
wglMakeCurrent(hDC, hRC);
pOpenGLComposite->paintGL();
wglMakeCurrent( NULL, NULL );
wglMakeCurrent( NULL, NULL );
}
}
catch (...)
{

View File

@@ -54,7 +54,8 @@ DEFINE_GUID(IID_PinnedMemoryAllocator,
namespace
{
constexpr GLuint kVideoTextureUnit = 1;
constexpr GLuint kDecodedVideoTextureUnit = 1;
constexpr GLuint kPackedVideoTextureUnit = 2;
constexpr GLuint kGlobalParamsBindingPoint = 0;
const char* kDisplayModeName = "1080p59.94";
const char* kVertexShaderSource =
@@ -67,6 +68,31 @@ const char* kVertexShaderSource =
" 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"
"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"
"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"
" 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"
" fragColor = rec709YCbCr2rgba(ySample, macroPixel.b, macroPixel.r, 1.0);\n"
"}\n";
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
{
@@ -129,9 +155,14 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
mHasNoInputSource(true),
mFastTransferExtensionAvailable(false),
mCaptureTexture(0),
mDecodedTexture(0),
mFBOTexture(0),
mDecodeFrameBuf(0),
mFullscreenVAO(0),
mGlobalParamsUBO(0),
mDecodeProgram(0),
mDecodeVertexShader(0),
mDecodeFragmentShader(0),
mProgram(0),
mVertexShader(0),
mFragmentShader(0),
@@ -195,6 +226,8 @@ OpenGLComposite::~OpenGLComposite()
glDeleteVertexArrays(1, &mFullscreenVAO);
if (mGlobalParamsUBO != 0)
glDeleteBuffers(1, &mGlobalParamsUBO);
if (mDecodeFrameBuf != 0)
glDeleteFramebuffers(1, &mDecodeFrameBuf);
if (mIdFrameBuf != 0)
glDeleteFramebuffers(1, &mIdFrameBuf);
if (mIdColorBuf != 0)
@@ -203,12 +236,15 @@ OpenGLComposite::~OpenGLComposite()
glDeleteRenderbuffers(1, &mIdDepthBuf);
if (mCaptureTexture != 0)
glDeleteTextures(1, &mCaptureTexture);
if (mDecodedTexture != 0)
glDeleteTextures(1, &mDecodedTexture);
if (mFBOTexture != 0)
glDeleteTextures(1, &mFBOTexture);
if (mUnpinnedTextureBuffer != 0)
glDeleteBuffers(1, &mUnpinnedTextureBuffer);
destroyShaderProgram();
destroyDecodeShaderProgram();
if (mControlServer)
mControlServer->Stop();
@@ -421,18 +457,52 @@ error:
void OpenGLComposite::paintGL()
{
if (!TryEnterCriticalSection(&pMutex))
{
ValidateRect(hGLWnd, NULL);
return;
}
// The DeckLink API provides IDeckLinkGLScreenPreviewHelper as a convenient way to view the playout video frames
// in a window. However, it performs a copy from host memory to the GPU which is wasteful in this case since
// we already have the rendered frame to be played out sitting in the GPU in the mIdFrameBuf frame buffer.
// Simply copy the off-screen frame buffer to on-screen frame buffer, scaling to the viewing window size.
// Copy the off-screen frame buffer to the on-screen frame buffer while preserving the
// incoming video aspect ratio. Any extra window area is cleared to black.
int destWidth = mViewWidth;
int destHeight = mViewHeight;
int destX = 0;
int destY = 0;
if (mFrameWidth > 0 && mFrameHeight > 0 && mViewWidth > 0 && mViewHeight > 0)
{
const double frameAspect = static_cast<double>(mFrameWidth) / static_cast<double>(mFrameHeight);
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, mIdFrameBuf);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glViewport(0, 0, mViewWidth, mViewHeight);
glBlitFramebuffer(0, 0, mFrameWidth, mFrameHeight, 0, 0, mViewWidth, mViewHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBlitFramebuffer(0, 0, mFrameWidth, mFrameHeight, destX, destY, destX + destWidth, destY + destHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
SwapBuffers(hGLDC);
ValidateRect(hGLWnd, NULL);
LeaveCriticalSection(&pMutex);
}
void OpenGLComposite::resizeGL(WORD width, WORD height)
@@ -470,6 +540,7 @@ bool OpenGLComposite::InitOpenGLState()
callbacks.updateParameter = [this](const std::string& shaderId, const std::string& parameterId, const std::string& valueJson, std::string& error) {
return UpdateParameterJson(shaderId, parameterId, valueJson, error);
};
callbacks.resetParameters = [this](const std::string& shaderId, std::string& error) { return ResetShaderParameters(shaderId, error); };
callbacks.setBypass = [this](bool bypassEnabled, std::string& error) { return SetBypassEnabled(bypassEnabled, error); };
callbacks.setMixAmount = [this](double mixAmount, std::string& error) { return SetMixAmount(mixAmount, error); };
callbacks.reloadShader = [this](std::string& error) {
@@ -490,6 +561,12 @@ bool OpenGLComposite::InitOpenGLState()
// Prepare the runtime shader program generated from the active shader package.
char compilerErrorMessage[1024];
if (! compileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK);
return false;
}
if (! compileFragmentShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK);
@@ -520,15 +597,34 @@ bool OpenGLComposite::InitOpenGLState()
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, mFrameWidth/2, mFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glGenTextures(1, &mDecodedTexture);
glBindTexture(GL_TEXTURE_2D, mDecodedTexture);
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, mFrameWidth, mFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
// Create Frame Buffer Object (FBO) to perform off-screen rendering of scene.
// This allows the render to be done on a framebuffer with width and height exactly matching the video format.
glGenFramebuffers(1, &mDecodeFrameBuf);
glGenFramebuffers(1, &mIdFrameBuf);
glGenRenderbuffers(1, &mIdColorBuf);
glGenRenderbuffers(1, &mIdDepthBuf);
glGenVertexArrays(1, &mFullscreenVAO);
glGenBuffers(1, &mGlobalParamsUBO);
glBindFramebuffer(GL_FRAMEBUFFER, mDecodeFrameBuf);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mDecodedTexture, 0);
GLenum glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (glStatus != GL_FRAMEBUFFER_COMPLETE)
{
MessageBox(NULL, _T("Cannot initialize decode framebuffer."), _T("OpenGL initialization error."), MB_OK);
return false;
}
glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
// Texture for FBO
@@ -549,7 +645,7 @@ bool OpenGLComposite::InitOpenGLState()
// Attach the texture which stores the playback image
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFBOTexture, 0);
GLenum glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (glStatus != GL_FRAMEBUFFER_COMPLETE)
{
MessageBox(NULL, _T("Cannot initialize framebuffer."), _T("OpenGL initialization error."), MB_OK);
@@ -645,8 +741,18 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame,
wglMakeCurrent( hGLDC, hGLRC );
// Draw the effect output to the off-screen framebuffer.
const auto renderStartTime = std::chrono::steady_clock::now();
glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
renderEffect();
const auto renderEndTime = std::chrono::steady_clock::now();
if (mRuntimeHost)
{
const double frameBudgetMilliseconds = mFrameTimescale != 0
? (static_cast<double>(mFrameDuration) * 1000.0) / static_cast<double>(mFrameTimescale)
: 0.0;
const double renderMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(renderEndTime - renderStartTime).count();
mRuntimeHost->SetPerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
}
if (mRuntimeHost)
mRuntimeHost->AdvanceFrame();
@@ -789,6 +895,58 @@ bool OpenGLComposite::ReloadShader()
return success;
}
bool OpenGLComposite::compileDecodeShader(int errorMessageSize, char* errorMessage)
{
GLsizei errorBufferSize = 0;
GLint compileResult = GL_FALSE;
GLint linkResult = GL_FALSE;
const char* vertexSource = kVertexShaderSource;
const char* fragmentSource = kDecodeFragmentShaderSource;
GLuint newVertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(newVertexShader, 1, (const GLchar**)&vertexSource, NULL);
glCompileShader(newVertexShader);
glGetShaderiv(newVertexShader, GL_COMPILE_STATUS, &compileResult);
if (compileResult == GL_FALSE)
{
glGetShaderInfoLog(newVertexShader, errorMessageSize, &errorBufferSize, errorMessage);
glDeleteShader(newVertexShader);
return false;
}
GLuint newFragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(newFragmentShader, 1, (const GLchar**)&fragmentSource, NULL);
glCompileShader(newFragmentShader);
glGetShaderiv(newFragmentShader, GL_COMPILE_STATUS, &compileResult);
if (compileResult == GL_FALSE)
{
glGetShaderInfoLog(newFragmentShader, errorMessageSize, &errorBufferSize, errorMessage);
glDeleteShader(newVertexShader);
glDeleteShader(newFragmentShader);
return false;
}
GLuint newProgram = glCreateProgram();
glAttachShader(newProgram, newVertexShader);
glAttachShader(newProgram, newFragmentShader);
glLinkProgram(newProgram);
glGetProgramiv(newProgram, GL_LINK_STATUS, &linkResult);
if (linkResult == GL_FALSE)
{
glGetProgramInfoLog(newProgram, errorMessageSize, &errorBufferSize, errorMessage);
glDeleteProgram(newProgram);
glDeleteShader(newVertexShader);
glDeleteShader(newFragmentShader);
return false;
}
destroyDecodeShaderProgram();
mDecodeProgram = newProgram;
mDecodeVertexShader = newVertexShader;
mDecodeFragmentShader = newFragmentShader;
return true;
}
void OpenGLComposite::destroyShaderProgram()
{
if (mProgram != 0)
@@ -810,13 +968,31 @@ void OpenGLComposite::destroyShaderProgram()
}
}
void OpenGLComposite::destroyDecodeShaderProgram()
{
if (mDecodeProgram != 0)
{
glDeleteProgram(mDecodeProgram);
mDecodeProgram = 0;
}
if (mDecodeFragmentShader != 0)
{
glDeleteShader(mDecodeFragmentShader);
mDecodeFragmentShader = 0;
}
if (mDecodeVertexShader != 0)
{
glDeleteShader(mDecodeVertexShader);
mDecodeVertexShader = 0;
}
}
void OpenGLComposite::renderEffect()
{
PollRuntimeChanges();
glViewport(0, 0, mFrameWidth, mFrameHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (mHasNoInputSource)
return;
@@ -828,8 +1004,13 @@ void OpenGLComposite::renderEffect()
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glActiveTexture(GL_TEXTURE0 + kVideoTextureUnit);
glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
renderDecodePass();
glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
glViewport(0, 0, mFrameWidth, mFrameHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
glBindTexture(GL_TEXTURE_2D, mDecodedTexture);
glBindVertexArray(mFullscreenVAO);
glUseProgram(mProgram);
@@ -850,6 +1031,31 @@ void OpenGLComposite::renderEffect()
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU);
}
void OpenGLComposite::renderDecodePass()
{
glBindFramebuffer(GL_FRAMEBUFFER, mDecodeFrameBuf);
glViewport(0, 0, mFrameWidth, mFrameHeight);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0 + kPackedVideoTextureUnit);
glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
glBindVertexArray(mFullscreenVAO);
glUseProgram(mDecodeProgram);
const GLint packedResolutionLocation = glGetUniformLocation(mDecodeProgram, "uPackedVideoResolution");
const GLint decodedResolutionLocation = glGetUniformLocation(mDecodeProgram, "uDecodedVideoResolution");
if (packedResolutionLocation >= 0)
glUniform2f(packedResolutionLocation, static_cast<float>(mFrameWidth / 2), static_cast<float>(mFrameHeight));
if (decodedResolutionLocation >= 0)
glUniform2f(decodedResolutionLocation, static_cast<float>(mFrameWidth), static_cast<float>(mFrameHeight));
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
}
// Compile a fullscreen shader pass from the runtime Slang source into a core-profile
// GLSL program. The renderer owns the fullscreen pass and parameter UBO layout.
bool OpenGLComposite::compileFragmentShader(int errorMessageSize, char* errorMessage)
@@ -1068,6 +1274,15 @@ bool OpenGLComposite::UpdateParameterJson(const std::string& shaderId, const std
return true;
}
bool OpenGLComposite::ResetShaderParameters(const std::string& shaderId, std::string& error)
{
if (!mRuntimeHost->ResetParameters(shaderId, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::SetBypassEnabled(bool bypassEnabled, std::string& error)
{
if (!mRuntimeHost->SetBypass(bypassEnabled, error))

View File

@@ -81,6 +81,7 @@ public:
std::string GetRuntimeStateJson() const;
bool SelectShader(const std::string& shaderId, std::string& error);
bool UpdateParameterJson(const std::string& shaderId, const std::string& parameterId, const std::string& valueJson, std::string& error);
bool ResetShaderParameters(const std::string& shaderId, std::string& error);
bool SetBypassEnabled(bool bypassEnabled, std::string& error);
bool SetMixAmount(double mixAmount, std::string& error);
@@ -116,13 +117,18 @@ private:
// OpenGL data
bool mFastTransferExtensionAvailable;
GLuint mCaptureTexture;
GLuint mDecodedTexture;
GLuint mFBOTexture;
GLuint mUnpinnedTextureBuffer;
GLuint mDecodeFrameBuf;
GLuint mIdFrameBuf;
GLuint mIdColorBuf;
GLuint mIdDepthBuf;
GLuint mFullscreenVAO;
GLuint mGlobalParamsUBO;
GLuint mDecodeProgram;
GLuint mDecodeVertexShader;
GLuint mDecodeFragmentShader;
GLuint mProgram;
GLuint mVertexShader;
GLuint mFragmentShader;
@@ -134,7 +140,10 @@ private:
bool InitOpenGLState();
bool compileFragmentShader(int errorMessageSize, char* errorMessage);
bool compileDecodeShader(int errorMessageSize, char* errorMessage);
void destroyShaderProgram();
void destroyDecodeShaderProgram();
void renderDecodePass();
void renderEffect();
bool PollRuntimeChanges();
void broadcastRuntimeState();

View File

@@ -149,6 +149,9 @@ RuntimeHost::RuntimeHost()
mHasSignal(false),
mSignalWidth(0),
mSignalHeight(0),
mFrameBudgetMilliseconds(0.0),
mRenderMilliseconds(0.0),
mSmoothedRenderMilliseconds(0.0),
mServerPort(8080),
mAutoReloadEnabled(true),
mMixAmount(1.0),
@@ -332,8 +335,25 @@ bool RuntimeHost::UpdateParameter(const std::string& shaderId, const std::string
return false;
mPersistentState.parameterValuesByShader[shaderId][parameterId] = normalized;
if (shaderId == mActiveShaderId)
mReloadRequested = true;
return SavePersistentState(error);
}
bool RuntimeHost::ResetParameters(const std::string& shaderId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
auto shaderIt = mPackagesById.find(shaderId);
if (shaderIt == mPackagesById.end())
{
error = "Unknown shader id: " + shaderId;
return false;
}
std::map<std::string, ShaderParameterValue>& shaderValues = mPersistentState.parameterValuesByShader[shaderId];
shaderValues.clear();
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
shaderValues[definition.id] = DefaultValueForDefinition(definition);
return SavePersistentState(error);
}
@@ -376,6 +396,17 @@ void RuntimeHost::SetSignalStatus(bool hasSignal, unsigned width, unsigned heigh
mSignalModeName = modeName;
}
void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{
std::lock_guard<std::mutex> lock(mMutex);
mFrameBudgetMilliseconds = std::max(frameBudgetMilliseconds, 0.0);
mRenderMilliseconds = std::max(renderMilliseconds, 0.0);
if (mSmoothedRenderMilliseconds <= 0.0)
mSmoothedRenderMilliseconds = mRenderMilliseconds;
else
mSmoothedRenderMilliseconds = mSmoothedRenderMilliseconds * 0.9 + mRenderMilliseconds * 0.1;
}
void RuntimeHost::AdvanceFrame()
{
std::lock_guard<std::mutex> lock(mMutex);
@@ -944,65 +975,17 @@ std::string RuntimeHost::BuildWrapperSlangSource(const ShaderPackage& shaderPack
source << "\t" << SlangTypeForParameter(definition.type).substr(strlen("uniform ")) << " " << definition.id << ";\n";
source << "};\n\n";
source << "Sampler2D<float4> gVideoInput;\n\n";
source << "float4 rec709YCbCr2rgba(float Y, float Cb, float Cr, float a)\n";
source << "{\n";
source << "\tY = (Y * 256.0 - 16.0) / 219.0;\n";
source << "\tCb = (Cb * 256.0 - 16.0) / 224.0 - 0.5;\n";
source << "\tCr = (Cr * 256.0 - 16.0) / 224.0 - 0.5;\n";
source << "\treturn float4(Y + 1.5748 * Cr, Y - 0.1873 * Cb - 0.4681 * Cr, Y + 1.8556 * Cb, a);\n";
source << "}\n\n";
source << "float4 bilinear(float4 W, float4 X, float4 Y, float4 Z, float2 weight)\n";
source << "{\n";
source << "\tfloat4 m0 = lerp(W, Z, weight.x);\n";
source << "\tfloat4 m1 = lerp(X, Y, weight.x);\n";
source << "\treturn lerp(m0, m1, weight.y);\n";
source << "}\n\n";
source << "void textureGatherYUV(Sampler2D<float4> textureSampler, float2 tc, out float4 W, out float4 X, out float4 Y, out float4 Z)\n";
source << "{\n";
source << "\tuint width = 0;\n";
source << "\tuint height = 0;\n";
source << "\ttextureSampler.GetDimensions(width, height);\n";
source << "\tint2 tx = int2(tc * float2(width, height));\n";
source << "\tint2 tmin = int2(0, 0);\n";
source << "\tint2 tmax = int2(int(width), int(height)) - int2(1, 1);\n";
source << "\tW = textureSampler.Load(int3(tx, 0));\n";
source << "\tX = textureSampler.Load(int3(clamp(tx + int2(0, 1), tmin, tmax), 0));\n";
source << "\tY = textureSampler.Load(int3(clamp(tx + int2(1, 1), tmin, tmax), 0));\n";
source << "\tZ = textureSampler.Load(int3(clamp(tx + int2(1, 0), tmin, tmax), 0));\n";
source << "}\n\n";
source << "float4 sampleVideo(float2 tc)\n";
source << "{\n";
source << "\tfloat4 macro, macroU, macroR, macroUR;\n";
source << "\ttextureGatherYUV(gVideoInput, tc, macro, macroU, macroUR, macroR);\n";
source << "\tuint width = 0;\n";
source << "\tuint height = 0;\n";
source << "\tgVideoInput.GetDimensions(width, height);\n";
source << "\tfloat2 off = frac(tc * float2(width, height));\n";
source << "\tfloat4 pixel, pixelR, pixelU, pixelUR;\n";
source << "\tif (off.x > 0.5)\n";
source << "\t{\n";
source << "\t\tpixel = rec709YCbCr2rgba(macro.a, macro.b, macro.r, 1.0);\n";
source << "\t\tpixelR = rec709YCbCr2rgba(macroR.g, macroR.b, macroR.r, 1.0);\n";
source << "\t\tpixelU = rec709YCbCr2rgba(macroU.a, macroU.b, macroU.r, 1.0);\n";
source << "\t\tpixelUR = rec709YCbCr2rgba(macroUR.g, macroUR.b, macroUR.r, 1.0);\n";
source << "\t}\n";
source << "\telse\n";
source << "\t{\n";
source << "\t\tpixel = rec709YCbCr2rgba(macro.g, macro.b, macro.r, 1.0);\n";
source << "\t\tpixelR = rec709YCbCr2rgba(macro.a, macro.b, macro.r, 1.0);\n";
source << "\t\tpixelU = rec709YCbCr2rgba(macroU.g, macroU.b, macroU.r, 1.0);\n";
source << "\t\tpixelUR = rec709YCbCr2rgba(macroU.a, macroU.b, macroU.r, 1.0);\n";
source << "\t}\n";
source << "\treturn bilinear(pixel, pixelU, pixelUR, pixelR, off);\n";
source << "\treturn gVideoInput.Sample(tc);\n";
source << "}\n\n";
source << "#include \"" << shaderPackage.shaderPath.generic_string() << "\"\n\n";
source << "[shader(\"fragment\")]\n";
source << "float4 fragmentMain(FragmentInput input) : SV_Target\n";
source << "{\n";
source << "\tShaderContext context;\n";
source << "\tfloat2 correctedUv = float2(input.texCoord.x, 1.0 - input.texCoord.y);\n";
source << "\tcontext.uv = correctedUv;\n";
source << "\tcontext.sourceColor = sampleVideo(correctedUv);\n";
source << "\tcontext.uv = input.texCoord;\n";
source << "\tcontext.sourceColor = sampleVideo(context.uv);\n";
source << "\tcontext.inputResolution = gInputResolution;\n";
source << "\tcontext.outputResolution = gOutputResolution;\n";
source << "\tcontext.time = gTime;\n";
@@ -1184,6 +1167,13 @@ JsonValue RuntimeHost::BuildStateValue() const
video.set("modeName", JsonValue(mSignalModeName));
root.set("video", video);
JsonValue performance = JsonValue::MakeObject();
performance.set("frameBudgetMs", JsonValue(mFrameBudgetMilliseconds));
performance.set("renderMs", JsonValue(mRenderMilliseconds));
performance.set("smoothedRenderMs", JsonValue(mSmoothedRenderMilliseconds));
performance.set("budgetUsedPercent", JsonValue(mFrameBudgetMilliseconds > 0.0 ? (mSmoothedRenderMilliseconds / mFrameBudgetMilliseconds) * 100.0 : 0.0));
root.set("performance", performance);
JsonValue shaders = JsonValue::MakeArray();
for (const std::string& shaderId : mPackageOrder)
{

View File

@@ -88,11 +88,13 @@ public:
bool SelectShader(const std::string& shaderId, std::string& error);
bool UpdateParameter(const std::string& shaderId, const std::string& parameterId, const JsonValue& newValue, std::string& error);
bool ResetParameters(const std::string& shaderId, std::string& error);
bool SetBypass(bool bypassEnabled, std::string& error);
bool SetMixAmount(double mixAmount, std::string& error);
void SetCompileStatus(bool succeeded, const std::string& message);
void SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
void SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
void AdvanceFrame();
bool BuildActiveFragmentShaderSource(std::string& fragmentShaderSource, std::string& error);
@@ -163,6 +165,9 @@ private:
unsigned mSignalWidth;
unsigned mSignalHeight;
std::string mSignalModeName;
double mFrameBudgetMilliseconds;
double mRenderMilliseconds;
double mSmoothedRenderMilliseconds;
unsigned short mServerPort;
bool mAutoReloadEnabled;
double mMixAmount;