Layer stacking

This commit is contained in:
2026-05-02 18:18:49 +10:00
parent fb1bf01fef
commit 80399c5a15
18 changed files with 3761 additions and 799 deletions

View File

@@ -34,6 +34,38 @@ std::string ToLower(std::string text)
[](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 == ".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";
return "text/html";
}
}
ControlServer::ControlServer()
@@ -204,20 +236,21 @@ bool ControlServer::HandleHttpRequest(SOCKET clientSocket, const std::string& re
closesocket(clientSocket);
return true;
}
if (path == "/app.js" || path == "/styles.css")
{
std::string contentType;
std::string body = LoadUiAsset(path.substr(1), contentType);
SendHttpResponse(clientSocket, "200 OK", contentType, body);
closesocket(clientSocket);
return true;
}
if (path == "/api/state")
{
SendHttpResponse(clientSocket, "200 OK", "application/json", mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}");
closesocket(clientSocket);
return true;
}
std::string contentType;
std::string body = LoadUiAsset(path.substr(1), contentType);
if (!body.empty())
{
SendHttpResponse(clientSocket, "200 OK", contentType, body);
closesocket(clientSocket);
return true;
}
}
else if (method == "POST")
{
@@ -234,36 +267,69 @@ bool ControlServer::HandleHttpRequest(SOCKET clientSocket, const std::string& re
bool success = false;
std::string actionError;
if (path == "/api/select-shader")
if (path == "/api/layers/add")
{
const JsonValue* shaderId = root.find("shaderId");
success = shaderId && mCallbacks.selectShader && mCallbacks.selectShader(shaderId->asString(), actionError);
success = shaderId && mCallbacks.addLayer && mCallbacks.addLayer(shaderId->asString(), actionError);
}
else if (path == "/api/update-parameter")
else if (path == "/api/layers/remove")
{
const JsonValue* layerId = root.find("layerId");
success = layerId && mCallbacks.removeLayer && mCallbacks.removeLayer(layerId->asString(), actionError);
}
else if (path == "/api/layers/move")
{
const JsonValue* layerId = root.find("layerId");
const JsonValue* direction = root.find("direction");
if (layerId && direction && mCallbacks.moveLayer)
success = mCallbacks.moveLayer(layerId->asString(), static_cast<int>(direction->asNumber()), actionError);
}
else if (path == "/api/layers/reorder")
{
const JsonValue* layerId = root.find("layerId");
const JsonValue* targetIndex = root.find("targetIndex");
if (layerId && targetIndex && mCallbacks.moveLayerToIndex)
success = mCallbacks.moveLayerToIndex(layerId->asString(), static_cast<std::size_t>(targetIndex->asNumber()), actionError);
}
else if (path == "/api/layers/set-bypass")
{
const JsonValue* layerId = root.find("layerId");
const JsonValue* bypass = root.find("bypass");
if (layerId && bypass && mCallbacks.setLayerBypass)
success = mCallbacks.setLayerBypass(layerId->asString(), bypass->asBoolean(), actionError);
}
else if (path == "/api/layers/set-shader")
{
const JsonValue* layerId = root.find("layerId");
const JsonValue* shaderId = root.find("shaderId");
if (layerId && shaderId && mCallbacks.setLayerShader)
success = mCallbacks.setLayerShader(layerId->asString(), shaderId->asString(), actionError);
}
else if (path == "/api/layers/update-parameter")
{
const JsonValue* layerId = root.find("layerId");
const JsonValue* parameterId = root.find("parameterId");
const JsonValue* value = root.find("value");
if (shaderId && parameterId && value && mCallbacks.updateParameter)
success = mCallbacks.updateParameter(shaderId->asString(), parameterId->asString(), SerializeJson(*value, false), actionError);
if (layerId && parameterId && value && mCallbacks.updateLayerParameter)
success = mCallbacks.updateLayerParameter(layerId->asString(), parameterId->asString(), SerializeJson(*value, false), actionError);
}
else if (path == "/api/reset-parameters")
else if (path == "/api/layers/reset-parameters")
{
const JsonValue* shaderId = root.find("shaderId");
if (shaderId && mCallbacks.resetParameters)
success = mCallbacks.resetParameters(shaderId->asString(), actionError);
const JsonValue* layerId = root.find("layerId");
if (layerId && mCallbacks.resetLayerParameters)
success = mCallbacks.resetLayerParameters(layerId->asString(), actionError);
}
else if (path == "/api/set-bypass")
else if (path == "/api/stack-presets/save")
{
const JsonValue* bypass = root.find("bypass");
if (bypass && mCallbacks.setBypass)
success = mCallbacks.setBypass(bypass->asBoolean(), actionError);
const JsonValue* presetName = root.find("presetName");
if (presetName && mCallbacks.saveStackPreset)
success = mCallbacks.saveStackPreset(presetName->asString(), actionError);
}
else if (path == "/api/set-mix")
else if (path == "/api/stack-presets/load")
{
const JsonValue* mixAmount = root.find("mixAmount");
if (mixAmount && mCallbacks.setMixAmount)
success = mCallbacks.setMixAmount(mixAmount->asNumber(), actionError);
const JsonValue* presetName = root.find("presetName");
if (presetName && mCallbacks.loadStackPreset)
success = mCallbacks.loadStackPreset(presetName->asString(), actionError);
}
else if (path == "/api/reload")
{
@@ -357,17 +423,16 @@ void ControlServer::BroadcastStateLocked()
std::string ControlServer::LoadUiAsset(const std::string& relativePath, std::string& contentType) const
{
const std::filesystem::path assetPath = mUiRoot / relativePath;
const std::filesystem::path sanitizedPath = std::filesystem::path(relativePath).lexically_normal();
if (!IsSafeUiPath(sanitizedPath))
return std::string();
const std::filesystem::path assetPath = mUiRoot / sanitizedPath;
std::ifstream input(assetPath, std::ios::binary);
if (!input)
return "<!doctype html><title>Missing UI asset</title><p>UI asset missing.</p>";
return std::string();
if (assetPath.extension() == ".js")
contentType = "text/javascript";
else if (assetPath.extension() == ".css")
contentType = "text/css";
else
contentType = "text/html";
contentType = GuessContentType(assetPath);
std::ostringstream buffer;
buffer << input.rdbuf();

View File

@@ -16,11 +16,16 @@ public:
struct Callbacks
{
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(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;
};

View File

@@ -156,16 +156,15 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
mFastTransferExtensionAvailable(false),
mCaptureTexture(0),
mDecodedTexture(0),
mLayerTempTexture(0),
mFBOTexture(0),
mDecodeFrameBuf(0),
mLayerTempFrameBuf(0),
mFullscreenVAO(0),
mGlobalParamsUBO(0),
mDecodeProgram(0),
mDecodeVertexShader(0),
mDecodeFragmentShader(0),
mProgram(0),
mVertexShader(0),
mFragmentShader(0),
mGlobalParamsUBOSize(0)
{
InitializeCriticalSection(&pMutex);
@@ -228,6 +227,8 @@ OpenGLComposite::~OpenGLComposite()
glDeleteBuffers(1, &mGlobalParamsUBO);
if (mDecodeFrameBuf != 0)
glDeleteFramebuffers(1, &mDecodeFrameBuf);
if (mLayerTempFrameBuf != 0)
glDeleteFramebuffers(1, &mLayerTempFrameBuf);
if (mIdFrameBuf != 0)
glDeleteFramebuffers(1, &mIdFrameBuf);
if (mIdColorBuf != 0)
@@ -238,12 +239,14 @@ OpenGLComposite::~OpenGLComposite()
glDeleteTextures(1, &mCaptureTexture);
if (mDecodedTexture != 0)
glDeleteTextures(1, &mDecodedTexture);
if (mLayerTempTexture != 0)
glDeleteTextures(1, &mLayerTempTexture);
if (mFBOTexture != 0)
glDeleteTextures(1, &mFBOTexture);
if (mUnpinnedTextureBuffer != 0)
glDeleteBuffers(1, &mUnpinnedTextureBuffer);
destroyShaderProgram();
destroyLayerPrograms();
destroyDecodeShaderProgram();
if (mControlServer)
mControlServer->Stop();
@@ -536,13 +539,18 @@ bool OpenGLComposite::InitOpenGLState()
ControlServer::Callbacks callbacks;
callbacks.getStateJson = [this]() { return GetRuntimeStateJson(); };
callbacks.selectShader = [this](const std::string& shaderId, std::string& error) { return SelectShader(shaderId, error); };
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.addLayer = [this](const std::string& shaderId, std::string& error) { return AddLayer(shaderId, error); };
callbacks.removeLayer = [this](const std::string& layerId, std::string& error) { return RemoveLayer(layerId, error); };
callbacks.moveLayer = [this](const std::string& layerId, int direction, std::string& error) { return MoveLayer(layerId, direction, error); };
callbacks.moveLayerToIndex = [this](const std::string& layerId, std::size_t targetIndex, std::string& error) { return MoveLayerToIndex(layerId, targetIndex, error); };
callbacks.setLayerBypass = [this](const std::string& layerId, bool bypassed, std::string& error) { return SetLayerBypass(layerId, bypassed, error); };
callbacks.setLayerShader = [this](const std::string& layerId, const std::string& shaderId, std::string& error) { return SetLayerShader(layerId, shaderId, error); };
callbacks.updateLayerParameter = [this](const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error) {
return UpdateLayerParameterJson(layerId, 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.resetLayerParameters = [this](const std::string& layerId, std::string& error) { return ResetLayerParameters(layerId, error); };
callbacks.saveStackPreset = [this](const std::string& presetName, std::string& error) { return SaveStackPreset(presetName, error); };
callbacks.loadStackPreset = [this](const std::string& presetName, std::string& error) { return LoadStackPreset(presetName, error); };
callbacks.reloadShader = [this](std::string& error) {
if (!ReloadShader())
{
@@ -567,7 +575,7 @@ bool OpenGLComposite::InitOpenGLState()
return false;
}
if (! compileFragmentShader(sizeof(compilerErrorMessage), compilerErrorMessage))
if (! compileLayerPrograms(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK);
return false;
@@ -606,10 +614,20 @@ bool OpenGLComposite::InitOpenGLState()
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);
glGenTextures(1, &mLayerTempTexture);
glBindTexture(GL_TEXTURE_2D, mLayerTempTexture);
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, &mLayerTempFrameBuf);
glGenFramebuffers(1, &mIdFrameBuf);
glGenRenderbuffers(1, &mIdColorBuf);
glGenRenderbuffers(1, &mIdDepthBuf);
@@ -625,6 +643,15 @@ bool OpenGLComposite::InitOpenGLState()
return false;
}
glBindFramebuffer(GL_FRAMEBUFFER, mLayerTempFrameBuf);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mLayerTempTexture, 0);
glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (glStatus != GL_FRAMEBUFFER_COMPLETE)
{
MessageBox(NULL, _T("Cannot initialize layer framebuffer."), _T("OpenGL initialization error."), MB_OK);
return false;
}
glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
// Texture for FBO
@@ -872,7 +899,7 @@ bool OpenGLComposite::ReloadShader()
EnterCriticalSection(&pMutex);
wglMakeCurrent(hGLDC, hGLRC);
bool success = compileFragmentShader(sizeof(compilerErrorMessage), compilerErrorMessage);
bool success = compileLayerPrograms(sizeof(compilerErrorMessage), compilerErrorMessage);
if (mRuntimeHost)
mRuntimeHost->ClearReloadRequest();
@@ -895,6 +922,98 @@ bool OpenGLComposite::ReloadShader()
return success;
}
bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage)
{
GLsizei errorBufferSize = 0;
GLint compileResult = GL_FALSE;
GLint linkResult = GL_FALSE;
std::string fragmentShaderSource;
std::string loadError;
const char* vertexSource = kVertexShaderSource;
if (!mRuntimeHost->BuildLayerFragmentShaderSource(state.layerId, fragmentShaderSource, loadError))
{
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
return false;
}
const char* fragmentSource = fragmentShaderSource.c_str();
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;
}
layerProgram.layerId = state.layerId;
layerProgram.shaderId = state.shaderId;
layerProgram.program = newProgram;
layerProgram.vertexShader = newVertexShader;
layerProgram.fragmentShader = newFragmentShader;
return true;
}
bool OpenGLComposite::compileLayerPrograms(int errorMessageSize, char* errorMessage)
{
const std::vector<RuntimeRenderState> layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mFrameWidth, mFrameHeight) : std::vector<RuntimeRenderState>();
std::vector<LayerProgram> newPrograms;
newPrograms.reserve(layerStates.size());
for (const RuntimeRenderState& state : layerStates)
{
LayerProgram layerProgram;
if (!compileSingleLayerProgram(state, layerProgram, errorMessageSize, errorMessage))
{
for (LayerProgram& program : newPrograms)
destroySingleLayerProgram(program);
return false;
}
newPrograms.push_back(layerProgram);
}
destroyLayerPrograms();
mLayerPrograms.swap(newPrograms);
if (mRuntimeHost)
{
mRuntimeHost->SetCompileStatus(true, "Shader layers compiled successfully.");
mRuntimeHost->ClearReloadRequest();
}
return true;
}
bool OpenGLComposite::compileDecodeShader(int errorMessageSize, char* errorMessage)
{
GLsizei errorBufferSize = 0;
@@ -947,27 +1066,34 @@ bool OpenGLComposite::compileDecodeShader(int errorMessageSize, char* errorMessa
return true;
}
void OpenGLComposite::destroyShaderProgram()
void OpenGLComposite::destroySingleLayerProgram(LayerProgram& layerProgram)
{
if (mProgram != 0)
if (layerProgram.program != 0)
{
glDeleteProgram(mProgram);
mProgram = 0;
glDeleteProgram(layerProgram.program);
layerProgram.program = 0;
}
if (mFragmentShader != 0)
if (layerProgram.fragmentShader != 0)
{
glDeleteShader(mFragmentShader);
mFragmentShader = 0;
glDeleteShader(layerProgram.fragmentShader);
layerProgram.fragmentShader = 0;
}
if (mVertexShader != 0)
if (layerProgram.vertexShader != 0)
{
glDeleteShader(mVertexShader);
mVertexShader = 0;
glDeleteShader(layerProgram.vertexShader);
layerProgram.vertexShader = 0;
}
}
void OpenGLComposite::destroyLayerPrograms()
{
for (LayerProgram& layerProgram : mLayerPrograms)
destroySingleLayerProgram(layerProgram);
mLayerPrograms.clear();
}
void OpenGLComposite::destroyDecodeShaderProgram()
{
if (mDecodeProgram != 0)
@@ -1006,29 +1132,45 @@ void OpenGLComposite::renderEffect()
glDisable(GL_DEPTH_TEST);
renderDecodePass();
glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
const std::vector<RuntimeRenderState> layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mFrameWidth, mFrameHeight) : std::vector<RuntimeRenderState>();
if (layerStates.empty() || mLayerPrograms.empty())
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, mDecodeFrameBuf);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mIdFrameBuf);
glBlitFramebuffer(0, 0, mFrameWidth, mFrameHeight, 0, 0, mFrameWidth, mFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
}
else
{
GLuint sourceTexture = mDecodedTexture;
for (std::size_t index = 0; index < layerStates.size() && index < mLayerPrograms.size(); ++index)
{
const std::size_t remaining = layerStates.size() - index;
const bool writeToMain = (remaining % 2) == 1;
renderShaderProgram(sourceTexture, writeToMain ? mIdFrameBuf : mLayerTempFrameBuf, mLayerPrograms[index], layerStates[index]);
sourceTexture = writeToMain ? mFBOTexture : mLayerTempTexture;
}
}
if (mFastTransferExtensionAvailable)
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU);
}
void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinationFrameBuffer, const LayerProgram& layerProgram, const RuntimeRenderState& state)
{
glBindFramebuffer(GL_FRAMEBUFFER, destinationFrameBuffer);
glViewport(0, 0, mFrameWidth, mFrameHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
glBindTexture(GL_TEXTURE_2D, mDecodedTexture);
glBindTexture(GL_TEXTURE_2D, sourceTexture);
glBindVertexArray(mFullscreenVAO);
glUseProgram(mProgram);
if (mRuntimeHost)
{
const RuntimeRenderState state = mRuntimeHost->GetRenderState(mFrameWidth, mFrameHeight);
updateGlobalParamsBuffer(state);
}
glUseProgram(layerProgram.program);
updateGlobalParamsBuffer(state);
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
if (mFastTransferExtensionAvailable)
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU);
}
void OpenGLComposite::renderDecodePass()
@@ -1056,82 +1198,6 @@ void OpenGLComposite::renderDecodePass()
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)
{
GLsizei errorBufferSize = 0;
GLint compileResult = GL_FALSE;
GLint linkResult = GL_FALSE;
std::string fragmentShaderSource;
std::string loadError;
const char* vertexSource = kVertexShaderSource;
if (!mRuntimeHost->BuildActiveFragmentShaderSource(fragmentShaderSource, loadError))
{
mRuntimeHost->SetCompileStatus(false, loadError);
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
return false;
}
const char* fragmentSource = fragmentShaderSource.c_str();
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;
}
destroyShaderProgram();
mProgram = newProgram;
mVertexShader = newVertexShader;
mFragmentShader = newFragmentShader;
const RuntimeRenderState state = mRuntimeHost->GetRenderState(mFrameWidth, mFrameHeight);
if (!updateGlobalParamsBuffer(state))
{
CopyErrorMessage("Failed to allocate the runtime parameter UBO.", errorMessageSize, errorMessage);
destroyShaderProgram();
return false;
}
mRuntimeHost->SetCompileStatus(true, "Shader compiled successfully.");
mRuntimeHost->ClearReloadRequest();
return true;
}
bool OpenGLComposite::PollRuntimeChanges()
{
if (!mRuntimeHost)
@@ -1154,7 +1220,7 @@ bool OpenGLComposite::PollRuntimeChanges()
return true;
char compilerErrorMessage[1024] = {};
if (!compileFragmentShader(sizeof(compilerErrorMessage), compilerErrorMessage))
if (!compileLayerPrograms(sizeof(compilerErrorMessage), compilerErrorMessage))
{
mRuntimeHost->SetCompileStatus(false, compilerErrorMessage);
mRuntimeHost->ClearReloadRequest();
@@ -1251,9 +1317,9 @@ std::string OpenGLComposite::GetRuntimeStateJson() const
return mRuntimeHost ? mRuntimeHost->BuildStateJson() : "{}";
}
bool OpenGLComposite::SelectShader(const std::string& shaderId, std::string& error)
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
{
if (!mRuntimeHost->SelectShader(shaderId, error))
if (!mRuntimeHost->AddLayer(shaderId, error))
return false;
ReloadShader();
@@ -1261,40 +1327,93 @@ bool OpenGLComposite::SelectShader(const std::string& shaderId, std::string& err
return true;
}
bool OpenGLComposite::UpdateParameterJson(const std::string& shaderId, const std::string& parameterId, const std::string& valueJson, std::string& error)
bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error)
{
if (!mRuntimeHost->RemoveLayer(layerId, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error)
{
if (!mRuntimeHost->MoveLayer(layerId, direction, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
if (!mRuntimeHost->MoveLayerToIndex(layerId, targetIndex, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
{
if (!mRuntimeHost->SetLayerBypass(layerId, bypassed, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
{
if (!mRuntimeHost->SetLayerShader(layerId, shaderId, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
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;
if (!mRuntimeHost->UpdateParameter(shaderId, parameterId, parsedValue, error))
if (!mRuntimeHost->UpdateLayerParameter(layerId, parameterId, parsedValue, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::ResetShaderParameters(const std::string& shaderId, std::string& error)
bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error)
{
if (!mRuntimeHost->ResetParameters(shaderId, error))
if (!mRuntimeHost->ResetLayerParameters(layerId, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::SetBypassEnabled(bool bypassEnabled, std::string& error)
bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error)
{
if (!mRuntimeHost->SetBypass(bypassEnabled, error))
if (!mRuntimeHost->SaveStackPreset(presetName, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::SetMixAmount(double mixAmount, std::string& error)
bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error)
{
if (!mRuntimeHost->SetMixAmount(mixAmount, error))
if (!mRuntimeHost->LoadStackPreset(presetName, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}

View File

@@ -79,11 +79,16 @@ public:
bool Stop();
bool ReloadShader();
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);
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 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);
void resizeGL(WORD width, WORD height);
void paintGL();
@@ -118,9 +123,11 @@ private:
bool mFastTransferExtensionAvailable;
GLuint mCaptureTexture;
GLuint mDecodedTexture;
GLuint mLayerTempTexture;
GLuint mFBOTexture;
GLuint mUnpinnedTextureBuffer;
GLuint mDecodeFrameBuf;
GLuint mLayerTempFrameBuf;
GLuint mIdFrameBuf;
GLuint mIdColorBuf;
GLuint mIdDepthBuf;
@@ -129,21 +136,31 @@ private:
GLuint mDecodeProgram;
GLuint mDecodeVertexShader;
GLuint mDecodeFragmentShader;
GLuint mProgram;
GLuint mVertexShader;
GLuint mFragmentShader;
GLsizeiptr mGlobalParamsUBOSize;
int mViewWidth;
int mViewHeight;
std::unique_ptr<RuntimeHost> mRuntimeHost;
std::unique_ptr<ControlServer> mControlServer;
struct LayerProgram
{
std::string layerId;
std::string shaderId;
GLuint program = 0;
GLuint vertexShader = 0;
GLuint fragmentShader = 0;
};
std::vector<LayerProgram> mLayerPrograms;
bool InitOpenGLState();
bool compileFragmentShader(int errorMessageSize, char* errorMessage);
bool compileLayerPrograms(int errorMessageSize, char* errorMessage);
bool compileSingleLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
bool compileDecodeShader(int errorMessageSize, char* errorMessage);
void destroyShaderProgram();
void destroyLayerPrograms();
void destroySingleLayerProgram(LayerProgram& layerProgram);
void destroyDecodeShaderProgram();
void renderDecodePass();
void renderShaderProgram(GLuint sourceTexture, GLuint destinationFrameBuffer, const LayerProgram& layerProgram, const RuntimeRenderState& state);
void renderEffect();
bool PollRuntimeChanges();
void broadcastRuntimeState();

View File

@@ -39,6 +39,13 @@ bool IsFiniteNumber(double value)
return std::isfinite(value) != 0;
}
std::string ToLowerCopy(std::string text)
{
std::transform(text.begin(), text.end(), text.begin(),
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
return text;
}
std::vector<double> JsonArrayToNumbers(const JsonValue& value)
{
std::vector<double> numbers;
@@ -107,11 +114,6 @@ std::string SlangTypeForParameter(ShaderParameterType type)
return "uniform float";
}
std::string GlslTypeForUniformDeclaration(const std::string& declaration)
{
return Trim(declaration);
}
bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& type)
{
if (typeName == "float")
@@ -154,11 +156,10 @@ RuntimeHost::RuntimeHost()
mSmoothedRenderMilliseconds(0.0),
mServerPort(8080),
mAutoReloadEnabled(true),
mMixAmount(1.0),
mBypass(false),
mStartTime(std::chrono::steady_clock::now()),
mLastScanTime(std::chrono::steady_clock::time_point::min()),
mFrameCounter(0)
mFrameCounter(0),
mNextLayerId(0)
{
}
@@ -178,8 +179,22 @@ bool RuntimeHost::Initialize(std::string& error)
if (!ScanShaderPackages(error))
return false;
if (mActiveShaderId.empty() && !mPackageOrder.empty())
mActiveShaderId = mPackageOrder.front();
for (LayerPersistentState& layer : mPersistentState.layers)
{
auto shaderIt = mPackagesById.find(layer.shaderId);
if (shaderIt != mPackagesById.end())
EnsureLayerDefaultsLocked(layer, shaderIt->second);
}
if (mPersistentState.layers.empty() && !mPackageOrder.empty())
{
LayerPersistentState layer;
layer.id = GenerateLayerId();
layer.shaderId = mPackageOrder.front();
layer.bypass = false;
EnsureLayerDefaultsLocked(layer, mPackagesById[layer.shaderId]);
mPersistentState.layers.push_back(layer);
}
mServerPort = mConfig.serverPort;
mAutoReloadEnabled = mConfig.autoReload;
@@ -226,7 +241,13 @@ bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested,
std::string scanError;
std::map<std::string, ShaderPackage> previousPackages = mPackagesById;
std::vector<std::string> previousOrder = mPackageOrder;
const std::string previousActive = mActiveShaderId;
std::map<std::string, std::pair<std::filesystem::file_time_type, std::filesystem::file_time_type>> previousLayerShaderTimes;
for (const LayerPersistentState& layer : mPersistentState.layers)
{
auto previous = previousPackages.find(layer.shaderId);
if (previous != previousPackages.end())
previousLayerShaderTimes[layer.id] = std::make_pair(previous->second.shaderWriteTime, previous->second.manifestWriteTime);
}
if (!ScanShaderPackages(scanError))
{
@@ -254,20 +275,23 @@ bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested,
}
}
auto previousActiveIt = previousPackages.find(previousActive);
auto activeIt = mPackagesById.find(mActiveShaderId);
if (previousActiveIt != previousPackages.end() && activeIt != mPackagesById.end())
for (LayerPersistentState& layer : mPersistentState.layers)
{
if (previousActiveIt->second.shaderWriteTime != activeIt->second.shaderWriteTime ||
previousActiveIt->second.manifestWriteTime != activeIt->second.manifestWriteTime)
auto active = mPackagesById.find(layer.shaderId);
auto previous = previousLayerShaderTimes.find(layer.id);
if (active == mPackagesById.end())
continue;
EnsureLayerDefaultsLocked(layer, active->second);
if (previous != previousLayerShaderTimes.end())
{
mReloadRequested = true;
if (previous->second.first != active->second.shaderWriteTime ||
previous->second.second != active->second.manifestWriteTime)
{
mReloadRequested = true;
}
}
}
if (previousActive != mActiveShaderId)
mReloadRequested = true;
reloadRequested = mReloadRequested;
return true;
}
@@ -295,24 +319,115 @@ void RuntimeHost::ClearReloadRequest()
mReloadRequested = false;
}
bool RuntimeHost::SelectShader(const std::string& shaderId, std::string& error)
bool RuntimeHost::AddLayer(const std::string& shaderId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mPackagesById.find(shaderId) == mPackagesById.end())
auto shaderIt = mPackagesById.find(shaderId);
if (shaderIt == mPackagesById.end())
{
error = "Unknown shader id: " + shaderId;
return false;
}
mActiveShaderId = shaderId;
mPersistentState.activeShaderId = shaderId;
LayerPersistentState layer;
layer.id = GenerateLayerId();
layer.shaderId = shaderId;
layer.bypass = false;
EnsureLayerDefaultsLocked(layer, shaderIt->second);
mPersistentState.layers.push_back(layer);
mReloadRequested = true;
return SavePersistentState(error);
}
bool RuntimeHost::UpdateParameter(const std::string& shaderId, const std::string& parameterId, const JsonValue& newValue, std::string& error)
bool RuntimeHost::RemoveLayer(const std::string& layerId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mPersistentState.layers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
mPersistentState.layers.erase(it);
mReloadRequested = true;
return SavePersistentState(error);
}
bool RuntimeHost::MoveLayer(const std::string& layerId, int direction, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mPersistentState.layers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
const std::ptrdiff_t index = std::distance(mPersistentState.layers.begin(), it);
const std::ptrdiff_t newIndex = index + direction;
if (newIndex < 0 || newIndex >= static_cast<std::ptrdiff_t>(mPersistentState.layers.size()))
return true;
std::swap(mPersistentState.layers[index], mPersistentState.layers[newIndex]);
mReloadRequested = true;
return SavePersistentState(error);
}
bool RuntimeHost::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mPersistentState.layers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
if (mPersistentState.layers.empty())
return true;
if (targetIndex >= mPersistentState.layers.size())
targetIndex = mPersistentState.layers.size() - 1;
const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(mPersistentState.layers.begin(), it));
if (sourceIndex == targetIndex)
return true;
LayerPersistentState movedLayer = *it;
mPersistentState.layers.erase(mPersistentState.layers.begin() + static_cast<std::ptrdiff_t>(sourceIndex));
mPersistentState.layers.insert(mPersistentState.layers.begin() + static_cast<std::ptrdiff_t>(targetIndex), movedLayer);
mReloadRequested = true;
return SavePersistentState(error);
}
bool RuntimeHost::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
layer->bypass = bypassed;
mReloadRequested = true;
return SavePersistentState(error);
}
bool RuntimeHost::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
auto shaderIt = mPackagesById.find(shaderId);
if (shaderIt == mPackagesById.end())
@@ -321,6 +436,31 @@ bool RuntimeHost::UpdateParameter(const std::string& shaderId, const std::string
return false;
}
layer->shaderId = shaderId;
layer->parameterValues.clear();
EnsureLayerDefaultsLocked(*layer, shaderIt->second);
mReloadRequested = true;
return SavePersistentState(error);
}
bool RuntimeHost::UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
auto shaderIt = mPackagesById.find(layer->shaderId);
if (shaderIt == mPackagesById.end())
{
error = "Unknown shader id: " + layer->shaderId;
return false;
}
const ShaderPackage& shaderPackage = shaderIt->second;
auto parameterIt = std::find_if(shaderPackage.parameters.begin(), shaderPackage.parameters.end(),
[&parameterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
@@ -334,49 +474,89 @@ bool RuntimeHost::UpdateParameter(const std::string& shaderId, const std::string
if (!NormalizeAndValidateValue(*parameterIt, newValue, normalized, error))
return false;
mPersistentState.parameterValuesByShader[shaderId][parameterId] = normalized;
layer->parameterValues[parameterId] = normalized;
return SavePersistentState(error);
}
bool RuntimeHost::ResetParameters(const std::string& shaderId, std::string& error)
bool RuntimeHost::ResetLayerParameters(const std::string& layerId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
auto shaderIt = mPackagesById.find(shaderId);
LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
auto shaderIt = mPackagesById.find(layer->shaderId);
if (shaderIt == mPackagesById.end())
{
error = "Unknown shader id: " + shaderId;
error = "Unknown shader id: " + layer->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);
layer->parameterValues.clear();
EnsureLayerDefaultsLocked(*layer, shaderIt->second);
return SavePersistentState(error);
}
bool RuntimeHost::SetBypass(bool bypassEnabled, std::string& error)
bool RuntimeHost::SaveStackPreset(const std::string& presetName, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
mBypass = bypassEnabled;
mPersistentState.bypass = bypassEnabled;
return SavePersistentState(error);
}
bool RuntimeHost::SetMixAmount(double mixAmount, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!IsFiniteNumber(mixAmount))
const std::string safeStem = MakeSafePresetFileStem(presetName);
if (safeStem.empty())
{
error = "Mix amount must be a finite number.";
error = "Preset name must include at least one letter or number.";
return false;
}
mMixAmount = std::clamp(mixAmount, 0.0, 1.0);
mPersistentState.mixAmount = mMixAmount;
JsonValue root = JsonValue::MakeObject();
root.set("version", JsonValue(1.0));
root.set("name", JsonValue(Trim(presetName)));
root.set("layers", SerializeLayerStackLocked());
return WriteTextFile(mPresetRoot / (safeStem + ".json"), SerializeJson(root, true), error);
}
bool RuntimeHost::LoadStackPreset(const std::string& presetName, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
const std::string safeStem = MakeSafePresetFileStem(presetName);
if (safeStem.empty())
{
error = "Preset name must include at least one letter or number.";
return false;
}
const std::filesystem::path presetPath = mPresetRoot / (safeStem + ".json");
std::string presetText = ReadTextFile(presetPath, error);
if (presetText.empty())
return false;
JsonValue root;
if (!ParseJson(presetText, root, error))
return false;
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;
if (!DeserializeLayerStackLocked(*layersValue, nextLayers, error))
return false;
if (nextLayers.empty())
{
error = "Preset does not contain any valid layers.";
return false;
}
mPersistentState.layers = nextLayers;
mReloadRequested = true;
return SavePersistentState(error);
}
@@ -413,17 +593,24 @@ void RuntimeHost::AdvanceFrame()
++mFrameCounter;
}
bool RuntimeHost::BuildActiveFragmentShaderSource(std::string& fragmentShaderSource, std::string& error)
bool RuntimeHost::BuildLayerFragmentShaderSource(const std::string& layerId, std::string& fragmentShaderSource, std::string& error)
{
try
{
ShaderPackage shaderPackage;
{
std::lock_guard<std::mutex> lock(mMutex);
auto it = mPackagesById.find(mActiveShaderId);
const LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
auto it = mPackagesById.find(layer->shaderId);
if (it == mPackagesById.end())
{
error = "No active shader is selected.";
error = "Unknown shader id: " + layer->shaderId;
return false;
}
shaderPackage = it->second;
@@ -450,49 +637,53 @@ bool RuntimeHost::BuildActiveFragmentShaderSource(std::string& fragmentShaderSou
}
catch (const std::exception& exception)
{
error = std::string("RuntimeHost::BuildActiveFragmentShaderSource exception: ") + exception.what();
error = std::string("RuntimeHost::BuildLayerFragmentShaderSource exception: ") + exception.what();
return false;
}
catch (...)
{
error = "RuntimeHost::BuildActiveFragmentShaderSource threw a non-standard exception.";
error = "RuntimeHost::BuildLayerFragmentShaderSource threw a non-standard exception.";
return false;
}
}
RuntimeRenderState RuntimeHost::GetRenderState(unsigned outputWidth, unsigned outputHeight) const
std::vector<RuntimeRenderState> RuntimeHost::GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const
{
std::lock_guard<std::mutex> lock(mMutex);
RuntimeRenderState state;
state.activeShaderId = mActiveShaderId;
state.timeSeconds = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
state.frameCount = static_cast<double>(mFrameCounter);
state.mixAmount = mMixAmount;
state.bypass = mBypass ? 1.0 : 0.0;
state.inputWidth = mSignalWidth;
state.inputHeight = mSignalHeight;
state.outputWidth = outputWidth;
state.outputHeight = outputHeight;
std::vector<RuntimeRenderState> states;
auto shaderIt = mPackagesById.find(mActiveShaderId);
if (shaderIt != mPackagesById.end())
for (const LayerPersistentState& layer : mPersistentState.layers)
{
auto shaderIt = mPackagesById.find(layer.shaderId);
if (shaderIt == mPackagesById.end())
continue;
RuntimeRenderState state;
state.layerId = layer.id;
state.shaderId = layer.shaderId;
state.timeSeconds = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
state.frameCount = static_cast<double>(mFrameCounter);
state.mixAmount = 1.0;
state.bypass = layer.bypass ? 1.0 : 0.0;
state.inputWidth = mSignalWidth;
state.inputHeight = mSignalHeight;
state.outputWidth = outputWidth;
state.outputHeight = outputHeight;
state.parameterDefinitions = shaderIt->second.parameters;
auto persistedIt = mPersistentState.parameterValuesByShader.find(mActiveShaderId);
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
{
ShaderParameterValue value = DefaultValueForDefinition(definition);
if (persistedIt != mPersistentState.parameterValuesByShader.end())
{
auto valueIt = persistedIt->second.find(definition.id);
if (valueIt != persistedIt->second.end())
value = valueIt->second;
}
auto valueIt = layer.parameterValues.find(definition.id);
if (valueIt != layer.parameterValues.end())
value = valueIt->second;
state.parameterValues[definition.id] = value;
}
states.push_back(state);
}
return state;
return states;
}
std::string RuntimeHost::BuildStateJson() const
@@ -543,62 +734,102 @@ bool RuntimeHost::LoadPersistentState(std::string& error)
if (!ParseJson(stateText, root, error))
return false;
if (const JsonValue* activeShaderValue = root.find("activeShaderId"))
mPersistentState.activeShaderId = activeShaderValue->asString();
if (const JsonValue* mixAmountValue = root.find("mixAmount"))
mPersistentState.mixAmount = mixAmountValue->asNumber(1.0);
if (const JsonValue* bypassValue = root.find("bypass"))
mPersistentState.bypass = bypassValue->asBoolean(false);
if (const JsonValue* valuesByShader = root.find("parameterValuesByShader"))
if (const JsonValue* layersValue = root.find("layers"))
{
for (const auto& shaderItem : valuesByShader->asObject())
for (const JsonValue& layerValue : layersValue->asArray())
{
std::map<std::string, ShaderParameterValue>& shaderValues = mPersistentState.parameterValuesByShader[shaderItem.first];
for (const auto& parameterItem : shaderItem.second.asObject())
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"))
{
ShaderParameterValue value;
const JsonValue& jsonValue = parameterItem.second;
if (jsonValue.isBoolean())
for (const auto& parameterItem : parameterValues->asObject())
{
value.booleanValue = jsonValue.asBoolean();
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;
}
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);
}
shaderValues[parameterItem.first] = value;
}
if (!layer.shaderId.empty())
mPersistentState.layers.push_back(layer);
}
}
else
{
// Migrate from the older single-shader state shape.
std::string activeShaderId;
if (const JsonValue* activeShaderValue = root.find("activeShaderId"))
activeShaderId = activeShaderValue->asString();
if (!activeShaderId.empty())
{
LayerPersistentState layer;
layer.id = GenerateLayerId();
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;
}
}
}
mPersistentState.layers.push_back(layer);
}
}
mActiveShaderId = mPersistentState.activeShaderId;
mMixAmount = std::clamp(mPersistentState.mixAmount, 0.0, 1.0);
mBypass = mPersistentState.bypass;
return true;
}
bool RuntimeHost::SavePersistentState(std::string& error) const
{
JsonValue root = JsonValue::MakeObject();
root.set("activeShaderId", JsonValue(mActiveShaderId));
root.set("mixAmount", JsonValue(mMixAmount));
root.set("bypass", JsonValue(mBypass));
JsonValue valuesByShader = JsonValue::MakeObject();
for (const auto& shaderItem : mPersistentState.parameterValuesByShader)
JsonValue layers = JsonValue::MakeArray();
for (const LayerPersistentState& layer : mPersistentState.layers)
{
JsonValue shaderValues = JsonValue::MakeObject();
auto packageIt = mPackagesById.find(shaderItem.first);
for (const auto& parameterItem : shaderItem.second)
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();
auto packageIt = mPackagesById.find(layer.shaderId);
for (const auto& parameterItem : layer.parameterValues)
{
const ShaderParameterDefinition* definition = nullptr;
if (packageIt != mPackagesById.end())
@@ -614,11 +845,13 @@ bool RuntimeHost::SavePersistentState(std::string& error) const
}
if (definition)
shaderValues.set(parameterItem.first, SerializeParameterValue(*definition, parameterItem.second));
parameterValues.set(parameterItem.first, SerializeParameterValue(*definition, parameterItem.second));
}
valuesByShader.set(shaderItem.first, shaderValues);
layerValue.set("parameterValues", parameterValues);
layers.pushBack(layerValue);
}
root.set("parameterValuesByShader", valuesByShader);
root.set("layers", layers);
return WriteTextFile(mRuntimeStatePath, SerializeJson(root, true), error);
}
@@ -653,7 +886,6 @@ bool RuntimeHost::ScanShaderPackages(std::string& error)
return false;
}
EnsureParameterDefaultsLocked(shaderPackage);
packageOrder.push_back(shaderPackage.id);
packagesById[shaderPackage.id] = shaderPackage;
}
@@ -662,16 +894,12 @@ bool RuntimeHost::ScanShaderPackages(std::string& error)
mPackagesById.swap(packagesById);
mPackageOrder.swap(packageOrder);
if (!mActiveShaderId.empty() && mPackagesById.find(mActiveShaderId) == mPackagesById.end())
for (auto it = mPersistentState.layers.begin(); it != mPersistentState.layers.end();)
{
mActiveShaderId.clear();
mPersistentState.activeShaderId.clear();
}
if (mActiveShaderId.empty() && !mPackageOrder.empty())
{
mActiveShaderId = mPackageOrder.front();
mPersistentState.activeShaderId = mActiveShaderId;
if (mPackagesById.find(it->shaderId) == mPackagesById.end())
it = mPersistentState.layers.erase(it);
else
++it;
}
return true;
@@ -739,21 +967,13 @@ bool RuntimeHost::ParseShaderManifest(const std::filesystem::path& manifestPath,
if (const JsonValue* defaultValue = parameterJson.find("default"))
{
if (definition.type == ShaderParameterType::Boolean)
{
definition.defaultBoolean = defaultValue->asBoolean(false);
}
else if (definition.type == ShaderParameterType::Enum)
{
definition.defaultEnumValue = defaultValue->asString();
}
else if (defaultValue->isNumber())
{
definition.defaultNumbers.push_back(defaultValue->asNumber());
}
else if (defaultValue->isArray())
{
definition.defaultNumbers = JsonArrayToNumbers(*defaultValue);
}
}
if (const JsonValue* minValue = parameterJson.find("min"))
@@ -934,13 +1154,12 @@ ShaderParameterValue RuntimeHost::DefaultValueForDefinition(const ShaderParamete
return value;
}
void RuntimeHost::EnsureParameterDefaultsLocked(ShaderPackage& shaderPackage)
void RuntimeHost::EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const
{
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
{
auto& shaderValues = mPersistentState.parameterValuesByShader[shaderPackage.id];
if (shaderValues.find(definition.id) == shaderValues.end())
shaderValues[definition.id] = DefaultValueForDefinition(definition);
if (layerState.parameterValues.find(definition.id) == layerState.parameterValues.end())
layerState.parameterValues[definition.id] = DefaultValueForDefinition(definition);
}
}
@@ -1127,10 +1346,12 @@ bool RuntimeHost::ResolvePaths(std::string& error)
return false;
}
mUiRoot = mRepoRoot / "ui";
const std::filesystem::path builtUiRoot = mRepoRoot / "ui" / "dist";
mUiRoot = std::filesystem::exists(builtUiRoot) ? builtUiRoot : (mRepoRoot / "ui");
mConfigPath = mRepoRoot / "config" / "runtime-host.json";
mShaderRoot = mRepoRoot / mConfig.shaderLibrary;
mRuntimeRoot = mRepoRoot / "runtime";
mPresetRoot = mRuntimeRoot / "stack_presets";
mRuntimeStatePath = mRuntimeRoot / "runtime_state.json";
mWrapperPath = mRuntimeRoot / "shader_cache" / "active_shader_wrapper.slang";
mGeneratedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.raw.frag";
@@ -1138,6 +1359,7 @@ bool RuntimeHost::ResolvePaths(std::string& error)
std::error_code fsError;
std::filesystem::create_directories(mRuntimeRoot / "shader_cache", fsError);
std::filesystem::create_directories(mPresetRoot, fsError);
return true;
}
@@ -1153,11 +1375,9 @@ JsonValue RuntimeHost::BuildStateValue() const
root.set("app", app);
JsonValue runtime = JsonValue::MakeObject();
runtime.set("activeShaderId", JsonValue(mActiveShaderId));
runtime.set("layerCount", JsonValue(static_cast<double>(mPersistentState.layers.size())));
runtime.set("compileSucceeded", JsonValue(mCompileSucceeded));
runtime.set("compileMessage", JsonValue(mCompileMessage));
runtime.set("mixAmount", JsonValue(mMixAmount));
runtime.set("bypass", JsonValue(mBypass));
root.set("runtime", runtime);
JsonValue video = JsonValue::MakeObject();
@@ -1174,23 +1394,49 @@ JsonValue RuntimeHost::BuildStateValue() const
performance.set("budgetUsedPercent", JsonValue(mFrameBudgetMilliseconds > 0.0 ? (mSmoothedRenderMilliseconds / mFrameBudgetMilliseconds) * 100.0 : 0.0));
root.set("performance", performance);
JsonValue shaders = JsonValue::MakeArray();
JsonValue shaderLibrary = JsonValue::MakeArray();
for (const std::string& shaderId : mPackageOrder)
{
auto shaderIt = mPackagesById.find(shaderId);
if (shaderIt == mPackagesById.end())
continue;
const ShaderPackage& shaderPackage = shaderIt->second;
JsonValue shader = JsonValue::MakeObject();
shader.set("id", JsonValue(shaderPackage.id));
shader.set("name", JsonValue(shaderPackage.displayName));
shader.set("description", JsonValue(shaderPackage.description));
shader.set("category", JsonValue(shaderPackage.category));
shader.set("id", JsonValue(shaderIt->second.id));
shader.set("name", JsonValue(shaderIt->second.displayName));
shader.set("description", JsonValue(shaderIt->second.description));
shader.set("category", JsonValue(shaderIt->second.category));
shaderLibrary.pushBack(shader);
}
root.set("shaders", shaderLibrary);
JsonValue stackPresets = JsonValue::MakeArray();
for (const std::string& presetName : GetStackPresetNamesLocked())
stackPresets.pushBack(JsonValue(presetName));
root.set("stackPresets", stackPresets);
root.set("layers", SerializeLayerStackLocked());
return root;
}
JsonValue RuntimeHost::SerializeLayerStackLocked() const
{
JsonValue layers = JsonValue::MakeArray();
for (const LayerPersistentState& layer : mPersistentState.layers)
{
auto shaderIt = mPackagesById.find(layer.shaderId);
if (shaderIt == mPackagesById.end())
continue;
JsonValue layerValue = JsonValue::MakeObject();
layerValue.set("id", JsonValue(layer.id));
layerValue.set("shaderId", JsonValue(layer.shaderId));
layerValue.set("shaderName", JsonValue(shaderIt->second.displayName));
layerValue.set("bypass", JsonValue(layer.bypass));
JsonValue parameters = JsonValue::MakeArray();
auto persistedIt = mPersistentState.parameterValuesByShader.find(shaderPackage.id);
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
{
JsonValue parameter = JsonValue::MakeObject();
parameter.set("id", JsonValue(definition.id));
@@ -1218,7 +1464,6 @@ JsonValue RuntimeHost::BuildStateValue() const
stepValue.pushBack(JsonValue(number));
parameter.set("step", stepValue);
}
if (definition.type == ShaderParameterType::Enum)
{
JsonValue options = JsonValue::MakeArray();
@@ -1233,22 +1478,119 @@ JsonValue RuntimeHost::BuildStateValue() const
}
ShaderParameterValue value = DefaultValueForDefinition(definition);
if (persistedIt != mPersistentState.parameterValuesByShader.end())
{
auto valueIt = persistedIt->second.find(definition.id);
if (valueIt != persistedIt->second.end())
value = valueIt->second;
}
auto valueIt = layer.parameterValues.find(definition.id);
if (valueIt != layer.parameterValues.end())
value = valueIt->second;
parameter.set("value", SerializeParameterValue(definition, value));
parameters.pushBack(parameter);
}
shader.set("parameters", parameters);
shaders.pushBack(shader);
layerValue.set("parameters", parameters);
layers.pushBack(layerValue);
}
root.set("shaders", shaders);
return layers;
}
return root;
bool RuntimeHost::DeserializeLayerStackLocked(const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, 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();
auto shaderIt = mPackagesById.find(shaderId);
if (shaderIt == mPackagesById.end())
{
error = "Preset references unknown shader id: " + shaderId;
return false;
}
LayerPersistentState layer;
layer.id = GenerateLayerId();
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(shaderIt->second.parameters.begin(), shaderIt->second.parameters.end(),
[&parameterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
if (definitionIt == shaderIt->second.parameters.end())
continue;
ShaderParameterValue normalizedValue;
if (!NormalizeAndValidateValue(*definitionIt, *valueValue, normalizedValue, error))
return false;
layer.parameterValues[parameterId] = normalizedValue;
}
}
EnsureLayerDefaultsLocked(layer, shaderIt->second);
layers.push_back(layer);
}
return true;
}
std::vector<std::string> RuntimeHost::GetStackPresetNamesLocked() const
{
std::vector<std::string> presetNames;
std::error_code fsError;
if (!std::filesystem::exists(mPresetRoot, fsError))
return presetNames;
for (const auto& entry : std::filesystem::directory_iterator(mPresetRoot, fsError))
{
if (!entry.is_regular_file())
continue;
if (ToLowerCopy(entry.path().extension().string()) != ".json")
continue;
presetNames.push_back(entry.path().stem().string());
}
std::sort(presetNames.begin(), presetNames.end());
return presetNames;
}
std::string RuntimeHost::MakeSafePresetFileStem(const std::string& presetName) const
{
std::string trimmed = Trim(presetName);
std::string safe;
safe.reserve(trimmed.size());
for (unsigned char ch : trimmed)
{
if (std::isalnum(ch))
safe.push_back(static_cast<char>(std::tolower(ch)));
else if (ch == ' ' || ch == '-' || ch == '_')
{
if (safe.empty() || safe.back() == '-')
continue;
safe.push_back('-');
}
}
while (!safe.empty() && safe.back() == '-')
safe.pop_back();
return safe;
}
JsonValue RuntimeHost::SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const
@@ -1272,3 +1614,28 @@ JsonValue RuntimeHost::SerializeParameterValue(const ShaderParameterDefinition&
}
return JsonValue();
}
RuntimeHost::LayerPersistentState* RuntimeHost::FindLayerById(const std::string& layerId)
{
auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
return it == mPersistentState.layers.end() ? nullptr : &*it;
}
const RuntimeHost::LayerPersistentState* RuntimeHost::FindLayerById(const std::string& layerId) const
{
auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
return it == mPersistentState.layers.end() ? nullptr : &*it;
}
std::string RuntimeHost::GenerateLayerId()
{
while (true)
{
++mNextLayerId;
const std::string candidate = "layer-" + std::to_string(mNextLayerId);
if (!FindLayerById(candidate))
return candidate;
}
}

View File

@@ -62,7 +62,8 @@ struct ShaderPackage
struct RuntimeRenderState
{
std::string activeShaderId;
std::string layerId;
std::string shaderId;
std::vector<ShaderParameterDefinition> parameterDefinitions;
std::map<std::string, ShaderParameterValue> parameterValues;
double timeSeconds = 0.0;
@@ -86,19 +87,24 @@ public:
bool ManualReloadRequested();
void ClearReloadRequest();
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);
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 UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, std::string& error);
bool ResetLayerParameters(const std::string& layerId, std::string& error);
bool SaveStackPreset(const std::string& presetName, std::string& error) const;
bool LoadStackPreset(const std::string& presetName, 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);
RuntimeRenderState GetRenderState(unsigned outputWidth, unsigned outputHeight) const;
bool BuildLayerFragmentShaderSource(const std::string& layerId, std::string& fragmentShaderSource, std::string& error);
std::vector<RuntimeRenderState> GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const;
std::string BuildStateJson() const;
const std::filesystem::path& GetRepoRoot() const { return mRepoRoot; }
@@ -116,12 +122,17 @@ private:
bool autoReload = true;
};
struct LayerPersistentState
{
std::string id;
std::string shaderId;
bool bypass = false;
std::map<std::string, ShaderParameterValue> parameterValues;
};
struct PersistentState
{
std::string activeShaderId;
double mixAmount = 1.0;
bool bypass = false;
std::map<std::string, std::map<std::string, ShaderParameterValue>> parameterValuesByShader;
std::vector<LayerPersistentState> layers;
};
bool LoadConfig(std::string& error);
@@ -131,7 +142,7 @@ private:
bool ParseShaderManifest(const std::filesystem::path& manifestPath, ShaderPackage& shaderPackage, std::string& error) const;
bool NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const;
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition) const;
void EnsureParameterDefaultsLocked(ShaderPackage& shaderPackage);
void EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const;
std::string BuildWrapperSlangSource(const ShaderPackage& shaderPackage) const;
bool FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const;
bool RunSlangCompiler(const std::filesystem::path& wrapperPath, const std::filesystem::path& outputPath, std::string& error) const;
@@ -140,7 +151,14 @@ private:
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
bool ResolvePaths(std::string& error);
JsonValue BuildStateValue() const;
JsonValue SerializeLayerStackLocked() const;
bool DeserializeLayerStackLocked(const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, std::string& error);
std::vector<std::string> GetStackPresetNamesLocked() const;
std::string MakeSafePresetFileStem(const std::string& presetName) const;
JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const;
LayerPersistentState* FindLayerById(const std::string& layerId);
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
std::string GenerateLayerId();
private:
mutable std::mutex mMutex;
@@ -150,6 +168,7 @@ private:
std::filesystem::path mUiRoot;
std::filesystem::path mShaderRoot;
std::filesystem::path mRuntimeRoot;
std::filesystem::path mPresetRoot;
std::filesystem::path mRuntimeStatePath;
std::filesystem::path mConfigPath;
std::filesystem::path mWrapperPath;
@@ -157,7 +176,6 @@ private:
std::filesystem::path mPatchedGlslPath;
std::map<std::string, ShaderPackage> mPackagesById;
std::vector<std::string> mPackageOrder;
std::string mActiveShaderId;
bool mReloadRequested;
bool mCompileSucceeded;
std::string mCompileMessage;
@@ -170,9 +188,8 @@ private:
double mSmoothedRenderMilliseconds;
unsigned short mServerPort;
bool mAutoReloadEnabled;
double mMixAmount;
bool mBypass;
std::chrono::steady_clock::time_point mStartTime;
std::chrono::steady_clock::time_point mLastScanTime;
uint64_t mFrameCounter;
uint64_t mNextLayerId;
};